diff --git a/docs/source/Tools/Tools.rst b/docs/source/Tools/Tools.rst index d7160eec2f..528341a1fa 100644 --- a/docs/source/Tools/Tools.rst +++ b/docs/source/Tools/Tools.rst @@ -1130,7 +1130,8 @@ URL with Settings This holds the full URL without file name where the files must be fetched from. -Since builds made after 2022/05/13, the URL may also contain system variables. +Since builds made after 2022/05/03, the URL may also contain system variables. + This allows for an URL like: ``http://192.168.10.127/%mac%`` System variables will be converted into an URL encoded form, which may end up like this: @@ -1158,7 +1159,7 @@ This can render both inaccessible. Provisioning ============ -Added: 2022/05/13 +Added: 2022/05/03 When the build is made with ``FEATURE_CUSTOM_PROVISIONING`` defined, this Settings Archive screen does allow for more settings helping deployment and remote administration of ESPEasy nodes. diff --git a/lib/WifiEspNow/.clang-format b/lib/WifiEspNow/.clang-format new file mode 100644 index 0000000000..2a701e9d44 --- /dev/null +++ b/lib/WifiEspNow/.clang-format @@ -0,0 +1,12 @@ +--- +BasedOnStyle: Mozilla +AllowShortFunctionsOnASingleLine: Empty +BinPackArguments: true +BinPackParameters: true +ColumnLimit: 100 +Cpp11BracedListStyle: true +FixNamespaceComments: true +ReflowComments: false +SortIncludes: true +SortUsingDeclarations: true +SpacesInContainerLiterals: false diff --git a/lib/WifiEspNow/.gitignore b/lib/WifiEspNow/.gitignore new file mode 100644 index 0000000000..12e0396105 --- /dev/null +++ b/lib/WifiEspNow/.gitignore @@ -0,0 +1,3 @@ +/.vscode +/docs/html +/examples/*/build diff --git a/lib/WifiEspNow/.travis.yml b/lib/WifiEspNow/.travis.yml new file mode 100644 index 0000000000..a61d493091 --- /dev/null +++ b/lib/WifiEspNow/.travis.yml @@ -0,0 +1,57 @@ +language: bash +os: + - linux +env: + matrix: + - PLATFORM=esp8266 \ + HARDWARE=esp8266com/esp8266 \ + COREGIT=https://github.com/esp8266/Arduino.git \ + CORECOMMIT=192aaa42919dc65e5532ea4b60b002c4e19ce0ec \ + BOARD='--board esp8266com:esp8266:nodemcuv2 --pref custom_xtal=nodemcuv2_80 --pref custom_eesz=nodemcuv2_4M1M' + - PLATFORM=esp32 \ + HARDWARE=espressif/esp32 \ + COREGIT=https://github.com/espressif/arduino-esp32.git \ + CORECOMMIT=7df50a97d17b0953ea01cad0355410a66bd8b8b4 \ + BOARD='--board espressif:esp32:heltec_wifi_kit_32 --pref custom_FlashFreq=heltec_wifi_kit_32_80' + +before_install: + - sudo iptables -A INPUT -p udp -m udp --dport 5353 -j DROP # https://github.com/per1234/arduino-ci-script/issues/1 + - sudo apt-get update; sudo apt-get install xvfb + - /sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_1.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :1 -ac -screen 0 1280x1024x16 + - export DISPLAY=:1.0 + +install: + - mkdir -p $HOME/arduino_ide; cd $HOME/arduino_ide + - curl -L http://downloads.arduino.cc/arduino-1.8.9-linux64.tar.xz | tar xJ --strip 1 + - export PATH="$HOME/arduino_ide:$PATH" + - | + mkdir -p $HOME/Arduino/hardware/$HARDWARE + cd $HOME/Arduino/hardware/$HARDWARE + git init + git fetch $COREGIT + git -c advice.detachedHead=false checkout $CORECOMMIT + git submodule update --init + cd tools + python get.py + - mkdir -p $HOME/Arduino/libraries; cd $HOME/Arduino/libraries + +before_script: + - cp -r $TRAVIS_BUILD_DIR $HOME/Arduino/libraries/WifiEspNow + - arduino $BOARD --save-prefs + - arduino --get-pref + +script: + - | + NFAIL=0 + for SKETCH in $(find $TRAVIS_BUILD_DIR -name '*.ino'); do + echo + echo + if [[ -f $(dirname $SKETCH)/skip-$PLATFORM.txt ]]; then + echo -e '\033[0;34m'Skipping $SKETCH '\033[0m' + continue + fi + echo -e '\033[0;36m'Building $SKETCH '\033[0m' + echo + arduino --verify $SKETCH || NFAIL=$((NFAIL+1)) + done + [[ $NFAIL -eq 0 ]] diff --git a/lib/WifiEspNow/COPYING b/lib/WifiEspNow/COPYING new file mode 100644 index 0000000000..eb931ee991 --- /dev/null +++ b/lib/WifiEspNow/COPYING @@ -0,0 +1,842 @@ + GNU LESSER 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. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser 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 +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. + +********************************************************************** + + 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/lib/WifiEspNow/README.md b/lib/WifiEspNow/README.md new file mode 100644 index 0000000000..493088cfa1 --- /dev/null +++ b/lib/WifiEspNow/README.md @@ -0,0 +1,25 @@ +# ESP-NOW Arduino library for ESP8266 and ESP32 + +[![GitHub build status](https://img.shields.io/github/workflow/status/yoursunny/WifiEspNow/build)](https://github.com/yoursunny/WifiEspNow/actions) [![GitHub code size](https://img.shields.io/github/languages/code-size/yoursunny/WifiEspNow?style=flat)](https://github.com/yoursunny/WifiEspNow) + +**WifiEspNow** is an Arduino library for ESP-NOW, a connectionless WiFi communication protocol defined by Espressif. +Refer to [ESP-NOW reference](https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/network/esp_now.html) for more information about how ESP-NOW works and its limitations. + +* [Doxygen documentation](https://wifiespnow.yoursunny.dev/) + +## Features + +[`WifiEspNow`](src/WifiEspNow.h) is a simple wrapper of ESP-NOW functions in ESP-IDF. +On ESP8266, it supports unicast only. +On ESP32, it supports both unicast and multicast. + +[`WifiEspNowBroadcast`](src/WifiEspNowBroadcast.h) implements *pseudo* broadcast over ESP-NOW. +Each device advertises a specific WiFi SSID, and discovers each other through BSSID scanning. +Then, messages are transmitted separately toward every peer via ESP-NOW unicast. +This is my custom protocol, which differs from `WifiEspNow` multicast. + +## Installation + +1. Clone this repository under `$HOME/Arduino/libraries` directory. +2. Add `#include ` or `#include ` to your sketch. +3. Check out the [examples](examples/) for how to use. diff --git a/lib/WifiEspNow/docs/Doxyfile b/lib/WifiEspNow/docs/Doxyfile new file mode 100644 index 0000000000..74d064ea0d --- /dev/null +++ b/lib/WifiEspNow/docs/Doxyfile @@ -0,0 +1,20 @@ +PROJECT_NAME = WifiEspNow +PROJECT_BRIEF = "ESP-NOW Arduino library for ESP8266 and ESP32" +OUTPUT_DIRECTORY = "." +MARKDOWN_SUPPORT = NO +AUTOLINK_SUPPORT = NO +BUILTIN_STL_SUPPORT = YES +EXTRACT_ALL = YES +EXTRACT_PRIV_VIRTUAL = YES +QUIET = YES +INPUT = "../src" "../examples" +FILE_PATTERNS = "*.h" "*.hpp" "*.c" "*.cpp" "*.ino" +EXTENSION_MAPPING = "ino=C++" +RECURSIVE = YES +PREDEFINED = "ARDUINO=100" "ARDUINO_ARCH_ESP32=1" +HTML_COLORSTYLE_HUE = 82 +HTML_COLORSTYLE_SAT = 255 +HTML_COLORSTYLE_GAMMA = 200 +GENERATE_LATEX = NO +CLASS_DIAGRAMS = NO +HAVE_DOT = NO diff --git a/lib/WifiEspNow/docs/_redirects b/lib/WifiEspNow/docs/_redirects new file mode 100644 index 0000000000..6ef0d8963c --- /dev/null +++ b/lib/WifiEspNow/docs/_redirects @@ -0,0 +1,2 @@ +https://wifiespnow.netlify.app/* https://wifiespnow.yoursunny.dev/:splat 301! +https://wifiespnow.yoursunny.cn/* https://wifiespnow.yoursunny.dev/:splat 301! diff --git a/lib/WifiEspNow/docs/build.sh b/lib/WifiEspNow/docs/build.sh new file mode 100644 index 0000000000..422c404380 --- /dev/null +++ b/lib/WifiEspNow/docs/build.sh @@ -0,0 +1,8 @@ +#!/bin/bash +set -euo pipefail +cd "$( dirname "${BASH_SOURCE[0]}" )" + +doxygen Doxyfile 2>&1 | ./filter-Doxygen-warning.awk 1>&2 + +find html -name '*.html' | xargs sed -i '/<\/head>/ i\ ' +cp _redirects html/ diff --git a/lib/WifiEspNow/docs/filter-Doxygen-warning.awk b/lib/WifiEspNow/docs/filter-Doxygen-warning.awk new file mode 100644 index 0000000000..ab23b479ce --- /dev/null +++ b/lib/WifiEspNow/docs/filter-Doxygen-warning.awk @@ -0,0 +1,14 @@ +#!/usr/bin/gawk -f +/warning: The following parameters? of .* not documented:/ { + skip = 1 + next +} +$0 ~ /^ parameter/ { + if (!skip) { + print + } +} +$0 !~ /^ / { + skip = 0 + print +} \ No newline at end of file diff --git a/lib/WifiEspNow/examples/EspNowBroadcast/EspNowBroadcast.ino b/lib/WifiEspNow/examples/EspNowBroadcast/EspNowBroadcast.ino new file mode 100644 index 0000000000..fcfdde85f6 --- /dev/null +++ b/lib/WifiEspNow/examples/EspNowBroadcast/EspNowBroadcast.ino @@ -0,0 +1,113 @@ +/** + * @file + * + * EspNowBroadcast.ino demonstrates how to perform ESP-NOW pseudo broadcast with @c WifiEspNowBroadcast . + * You need two or more ESP8266 or ESP32 devices to run this example. + * + * All devices should run the same program. + * You may need to modify the PIN numbers so that you can observe the effect. + * + * With the program running on several devices: + * @li Press the button to transmit a message. + * @li When a device receives a message, it will toggle its LED between "on" and "off" states. + */ + +#include +#if defined(ARDUINO_ARCH_ESP8266) +#include +#elif defined(ARDUINO_ARCH_ESP32) +#include +#endif + +/** + * @brief PIN number of a button. + * + * The default `0` is the "flash" button on NodeMCU, Witty Cloud, Heltec WiFi_Kit_32, etc. + */ +static const int BUTTON_PIN = 0; + +/** + * @brief PIN number of an LED. + * + * The default `2` is the blue LED on ESP-12F. + */ +static const int LED_PIN = 2; + +int ledState = HIGH; + +void +processRx(const uint8_t mac[WIFIESPNOW_ALEN], const uint8_t* buf, size_t count, void* arg) +{ + Serial.printf("Message from %02X:%02X:%02X:%02X:%02X:%02X\n", mac[0], mac[1], mac[2], mac[3], + mac[4], mac[5]); + for (size_t i = 0; i < count; ++i) { + Serial.print(static_cast(buf[i])); + } + Serial.println(); + + digitalWrite(LED_PIN, ledState); + ledState = 1 - ledState; +} + +void +setup() +{ + Serial.begin(115200); + Serial.println(); + + WiFi.persistent(false); + bool ok = WifiEspNowBroadcast.begin("ESPNOW", 3); + if (!ok) { + Serial.println("WifiEspNowBroadcast.begin() failed"); + ESP.restart(); + } + // WifiEspNowBroadcast.begin() function sets WiFi to AP+STA mode. + // The AP interface is also controlled by WifiEspNowBroadcast. + // You may use the STA interface after calling WifiEspNowBroadcast.begin(). + // For best results, ensure all devices are using the same WiFi channel. + + WifiEspNowBroadcast.onReceive(processRx, nullptr); + + pinMode(BUTTON_PIN, INPUT_PULLUP); + pinMode(LED_PIN, OUTPUT); + digitalWrite(LED_PIN, ledState); + + Serial.print("MAC address of this node is "); + Serial.println(WiFi.softAPmacAddress()); + Serial.println("Press the button to send a message"); +} + +void +sendMessage() +{ + char msg[60]; + int len = snprintf(msg, sizeof(msg), "hello ESP-NOW from %s at %lu", + WiFi.softAPmacAddress().c_str(), millis()); + WifiEspNowBroadcast.send(reinterpret_cast(msg), len); + + Serial.println("Sending message"); + Serial.println(msg); + Serial.print("Recipients:"); + const int MAX_PEERS = 20; + WifiEspNowPeerInfo peers[MAX_PEERS]; + int nPeers = std::min(WifiEspNow.listPeers(peers, MAX_PEERS), MAX_PEERS); + for (int i = 0; i < nPeers; ++i) { + Serial.printf(" %02X:%02X:%02X:%02X:%02X:%02X", peers[i].mac[0], peers[i].mac[1], + peers[i].mac[2], peers[i].mac[3], peers[i].mac[4], peers[i].mac[5]); + } + Serial.println(); +} + +void +loop() +{ + if (digitalRead(BUTTON_PIN) == LOW) { // button is pressed + sendMessage(); + + while (digitalRead(BUTTON_PIN) == LOW) // wait for button release + ; + } + + WifiEspNowBroadcast.loop(); + delay(10); +} diff --git a/lib/WifiEspNow/examples/EspNowUnicast/EspNowUnicast.ino b/lib/WifiEspNow/examples/EspNowUnicast/EspNowUnicast.ino new file mode 100644 index 0000000000..13958cda06 --- /dev/null +++ b/lib/WifiEspNow/examples/EspNowUnicast/EspNowUnicast.ino @@ -0,0 +1,91 @@ +/** + * @file + * + * EspNowUnicast.ino demonstrates how to transmit unicast ESP-NOW messages with @c WifiEspNow . + * You need two ESP8266 or ESP32 devices to run this example. + * + * Unicast communication requires the sender to specify the MAC address of the recipient. + * Thus, you must modify this program for each device. + * + * The recommended workflow is: + * @li 1. Flash the program onto device A. + * @li 2. Run the program on device A, look at serial console for its MAC address. + * @li 3. Copy the MAC address of device A, paste it in the @c PEER variable below. + * @li 4. Flash the program that contains A's MAC address onto device B. + * @li 5. Run the program on device A, look at serial console for its MAC address. + * @li 6. Copy the MAC address of device B, paste it in the @c PEER variable below. + * @li 7. Flash the program that contains B's MAC address onto device A. + */ + +#include +#if defined(ARDUINO_ARCH_ESP8266) +#include +#elif defined(ARDUINO_ARCH_ESP32) +#include +#endif + +// The recipient MAC address. It must be modified for each device. +static uint8_t PEER[]{0x02, 0x00, 0x00, 0x45, 0x53, 0x50}; + +void +printReceivedMessage(const uint8_t mac[WIFIESPNOW_ALEN], const uint8_t* buf, size_t count, + void* arg) +{ + Serial.printf("Message from %02X:%02X:%02X:%02X:%02X:%02X\n", mac[0], mac[1], mac[2], mac[3], + mac[4], mac[5]); + for (int i = 0; i < static_cast(count); ++i) { + Serial.print(static_cast(buf[i])); + } + Serial.println(); +} + +void +setup() +{ + Serial.begin(115200); + Serial.println(); + + WiFi.persistent(false); + WiFi.mode(WIFI_AP); + WiFi.disconnect(); + WiFi.softAP("ESPNOW", nullptr, 3); + WiFi.softAPdisconnect(false); + // WiFi must be powered on to use ESP-NOW unicast. + // It could be either AP or STA mode, and does not have to be connected. + // For best results, ensure both devices are using the same WiFi channel. + + Serial.print("MAC address of this node is "); + Serial.println(WiFi.softAPmacAddress()); + + uint8_t mac[6]; + WiFi.softAPmacAddress(mac); + Serial.println(); + Serial.println("You can paste the following into the program for the other device:"); + Serial.printf("static uint8_t PEER[]{0x%02X, 0x%02X, 0x%02X, 0x%02X, 0x%02X, 0x%02X};\n", mac[0], + mac[1], mac[2], mac[3], mac[4], mac[5]); + Serial.println(); + + bool ok = WifiEspNow.begin(); + if (!ok) { + Serial.println("WifiEspNow.begin() failed"); + ESP.restart(); + } + + WifiEspNow.onReceive(printReceivedMessage, nullptr); + + ok = WifiEspNow.addPeer(PEER); + if (!ok) { + Serial.println("WifiEspNow.addPeer() failed"); + ESP.restart(); + } +} + +void +loop() +{ + char msg[60]; + int len = snprintf(msg, sizeof(msg), "hello ESP-NOW from %s at %lu", + WiFi.softAPmacAddress().c_str(), millis()); + WifiEspNow.send(PEER, reinterpret_cast(msg), len); + delay(1000); +} diff --git a/lib/WifiEspNow/library.properties b/lib/WifiEspNow/library.properties new file mode 100644 index 0000000000..6861fc1df8 --- /dev/null +++ b/lib/WifiEspNow/library.properties @@ -0,0 +1,9 @@ +name=WifiEspNow +version=0.0.20210821 +author=Junxiao Shi +maintainer=Junxiao Shi +sentence=ESP-NOW for ESP8266 and ESP32. +paragraph=This library supports ESP-NOW, a connectionless WiFi communication protocol defined by Espressif. It contains a simple wrapper of ESP-NOW functions, as well as a custom pseudo broadcast protocol. +category=Communication +url=https://wifiespnow.yoursunny.dev/ +architectures=esp8266,esp32 diff --git a/lib/WifiEspNow/mk/format-code.sh b/lib/WifiEspNow/mk/format-code.sh new file mode 100644 index 0000000000..8b8b736063 --- /dev/null +++ b/lib/WifiEspNow/mk/format-code.sh @@ -0,0 +1,6 @@ +#!/bin/bash +set -eo pipefail +cd "$( dirname "${BASH_SOURCE[0]}" )"/.. + +find -name '*.[hc]pp' -or -name '*.ino' | \ + xargs clang-format-11 -i -style=file diff --git a/lib/WifiEspNow/src/WifiEspNow.cpp b/lib/WifiEspNow/src/WifiEspNow.cpp new file mode 100644 index 0000000000..892df918e8 --- /dev/null +++ b/lib/WifiEspNow/src/WifiEspNow.cpp @@ -0,0 +1,168 @@ +#include "WifiEspNow.h" + +#include + +#if defined(ARDUINO_ARCH_ESP8266) +#include +#include +#elif defined(ARDUINO_ARCH_ESP32) +#include +#else +#error "This library supports ESP8266 and ESP32 only." +#endif + +WifiEspNowClass WifiEspNow; + +bool +WifiEspNowClass::begin() +{ + end(); + m_ready = + esp_now_init() == 0 && +#ifdef ARDUINO_ARCH_ESP8266 + esp_now_set_self_role(ESP_NOW_ROLE_COMBO) == 0 && +#endif + esp_now_register_recv_cb(reinterpret_cast(WifiEspNowClass::rx)) == 0 && + esp_now_register_send_cb(reinterpret_cast(WifiEspNowClass::tx)) == 0; + return m_ready; +} + +void +WifiEspNowClass::end() +{ + if (!m_ready) { + return; + } + esp_now_deinit(); + m_ready = false; +} + +bool +WifiEspNowClass::setPrimaryKey(const uint8_t key[WIFIESPNOW_KEYLEN]) +{ + return m_ready && key != nullptr && +#if defined(ARDUINO_ARCH_ESP8266) + esp_now_set_kok(const_cast(key), WIFIESPNOW_KEYLEN) == 0; +#elif defined(ARDUINO_ARCH_ESP32) + esp_now_set_pmk(key) == ESP_OK; +#endif +} + +int +WifiEspNowClass::listPeers(WifiEspNowPeerInfo* peers, int maxPeers) const +{ + if (!m_ready) { + return 0; + } + int n = 0; +#if defined(ARDUINO_ARCH_ESP8266) + for (u8* mac = esp_now_fetch_peer(true); mac != nullptr; mac = esp_now_fetch_peer(false)) { + uint8_t channel = static_cast(esp_now_get_peer_channel(mac)); +#elif defined(ARDUINO_ARCH_ESP32) + esp_now_peer_info_t peer; + for (esp_err_t e = esp_now_fetch_peer(true, &peer); e == ESP_OK; + e = esp_now_fetch_peer(false, &peer)) { + uint8_t* mac = peer.peer_addr; + uint8_t channel = peer.channel; +#endif + if (n < maxPeers) { + memcpy(peers[n].mac, mac, 6); + peers[n].channel = channel; + } + ++n; + } + return n; +} + +bool +WifiEspNowClass::hasPeer(const uint8_t mac[WIFIESPNOW_ALEN]) const +{ + return m_ready && +#if defined(ARDUINO_ARCH_ESP8266) + esp_now_is_peer_exist(const_cast(mac)) > 0; +#elif defined(ARDUINO_ARCH_ESP32) + esp_now_is_peer_exist(mac); +#endif +} + +#if defined(ARDUINO_ARCH_ESP8266) +bool +WifiEspNowClass::addPeer(const uint8_t mac[WIFIESPNOW_ALEN], int channel, + const uint8_t key[WIFIESPNOW_KEYLEN]) +{ + if (!m_ready) { + return false; + } + + if (this->hasPeer(mac)) { + return esp_now_set_peer_channel(const_cast(mac), static_cast(channel)) == 0 && + esp_now_set_peer_key(const_cast(mac), const_cast(key), + key == nullptr ? 0 : WIFIESPNOW_KEYLEN) == 0; + } + return esp_now_add_peer(const_cast(mac), ESP_NOW_ROLE_SLAVE, static_cast(channel), + const_cast(key), key == nullptr ? 0 : WIFIESPNOW_KEYLEN) == 0; +} +#elif defined(ARDUINO_ARCH_ESP32) +bool +WifiEspNowClass::addPeer(const uint8_t mac[WIFIESPNOW_ALEN], int channel, + const uint8_t key[WIFIESPNOW_KEYLEN], int netif) +{ + if (!m_ready) { + return false; + } + + esp_now_peer_info_t pi{}; + static_assert(WIFIESPNOW_ALEN == sizeof(pi.peer_addr), ""); + std::copy_n(mac, WIFIESPNOW_ALEN, pi.peer_addr); + if (key != nullptr) { + static_assert(WIFIESPNOW_KEYLEN == sizeof(pi.lmk), ""); + std::copy_n(key, WIFIESPNOW_KEYLEN, pi.lmk); + pi.encrypt = true; + } + pi.channel = static_cast(channel); + pi.ifidx = static_cast(netif); + + if (hasPeer(mac)) { + return esp_now_mod_peer(&pi) == ESP_OK; + } + return esp_now_add_peer(&pi) == ESP_OK; +} +#endif + +bool +WifiEspNowClass::removePeer(const uint8_t mac[WIFIESPNOW_ALEN]) +{ + return m_ready && esp_now_del_peer(const_cast(mac)) == 0; +} + +void +WifiEspNowClass::onReceive(RxCallback cb, void* arg) +{ + m_rxCb = cb; + m_rxArg = arg; +} + +bool +WifiEspNowClass::send(const uint8_t mac[WIFIESPNOW_ALEN], const uint8_t* buf, size_t count) +{ + if (!m_ready || count > WIFIESPNOW_MAXMSGLEN || count == 0) { + return false; + } + WifiEspNow.m_txRes = WifiEspNowSendStatus::NONE; + return esp_now_send(const_cast(mac), const_cast(buf), + static_cast(count)) == 0; +} + +void +WifiEspNowClass::rx(const uint8_t* mac, const uint8_t* data, uint8_t len) +{ + if (WifiEspNow.m_rxCb != nullptr) { + (*WifiEspNow.m_rxCb)(mac, data, len, WifiEspNow.m_rxArg); + } +} + +void +WifiEspNowClass::tx(const uint8_t* mac, uint8_t status) +{ + WifiEspNow.m_txRes = status == 0 ? WifiEspNowSendStatus::OK : WifiEspNowSendStatus::FAIL; +} diff --git a/lib/WifiEspNow/src/WifiEspNow.h b/lib/WifiEspNow/src/WifiEspNow.h new file mode 100644 index 0000000000..fa51097bb2 --- /dev/null +++ b/lib/WifiEspNow/src/WifiEspNow.h @@ -0,0 +1,148 @@ +/** + * @mainpage WifiEspNow + * + * https://github.com/yoursunny/WifiEspNow + */ + +#ifndef WIFIESPNOW_H +#define WIFIESPNOW_H + +#if defined(ARDUINO_ARCH_ESP8266) +#include +#elif defined(ARDUINO_ARCH_ESP32) +#include +#endif + +#include +#include + +/** @brief Address length. */ +static const int WIFIESPNOW_ALEN = 6; + +/** @brief Key length. */ +static const int WIFIESPNOW_KEYLEN = 16; + +/** @brief Maximum message length. */ +static const int WIFIESPNOW_MAXMSGLEN = 250; + +struct WifiEspNowPeerInfo { + uint8_t mac[WIFIESPNOW_ALEN]; + uint8_t channel; +}; + +/** @brief Result of send operation. */ +enum class WifiEspNowSendStatus : uint8_t { + NONE = 0, ///< result unknown, send in progress + OK = 1, ///< unicast message acknowledged by peer; multicast message transmitted + FAIL = 2, ///< sending failed +}; + +class WifiEspNowClass +{ +public: + /** + * @brief Initialize ESP-NOW. + * @return whether success. + */ + bool + begin(); + + /** @brief Stop ESP-NOW. */ + void + end(); + + /** + * @brief Set primary key, also known as KOK or PMK. + * @param key primary encryption key. + * @return whether success. + */ + bool + setPrimaryKey(const uint8_t key[WIFIESPNOW_KEYLEN]); + + /** + * @brief List current peers. + * @param[out] peers buffer for peer information. + * @param maxPeers buffer size. + * @return total number of peers, @c std::min(retval,maxPeers) is written to @p peers . + */ + int + listPeers(WifiEspNowPeerInfo* peers, int maxPeers) const; + + /** + * @brief Test whether peer exists. + * @param mac peer MAC address. + * @return whether peer exists. + */ + bool + hasPeer(const uint8_t mac[WIFIESPNOW_ALEN]) const; + + /** + * @brief Add a peer or change peer channel. + * @param mac peer MAC address. + * @param channel peer channel, 0 for current channel. + * @param key encryption key, nullptr to disable encryption. + * @param netif (ESP32 only) WiFi interface. + * @return whether success. + */ +#if defined(ARDUINO_ARCH_ESP8266) + bool + addPeer(const uint8_t mac[WIFIESPNOW_ALEN], int channel = 0, const uint8_t key[WIFIESPNOW_KEYLEN] = nullptr); +#elif defined(ARDUINO_ARCH_ESP32) + bool + addPeer(const uint8_t mac[WIFIESPNOW_ALEN], int channel = 0, const uint8_t key[WIFIESPNOW_KEYLEN] = nullptr, int netif = ESP_IF_WIFI_AP); +#endif + + /** + * @brief Remove a peer. + * @param mac peer MAC address. + * @return whether success. + */ + bool + removePeer(const uint8_t mac[WIFIESPNOW_ALEN]); + + using RxCallback = void (*)(const uint8_t mac[WIFIESPNOW_ALEN], const uint8_t* buf, size_t count, void* arg); + + /** + * @brief Set receive callback. + * @param cb the callback. + * @param arg an arbitrary argument passed to the callback. + * @note Only one callback is allowed; this replaces any previous callback. + */ + void + onReceive(RxCallback cb, void* arg); + + /** + * @brief Send a message. + * @param mac destination MAC address, nullptr for all peers. + * @param buf payload. + * @param count payload size, must not exceed @p WIFIESPNOW_MAXMSGLEN . + * @return whether success (message queued for transmission). + */ + bool + send(const uint8_t mac[WIFIESPNOW_ALEN], const uint8_t* buf, size_t count); + + /** @brief Retrieve status of last sent message. */ + WifiEspNowSendStatus + getSendStatus() const + { + return m_txRes; + } + +private: + static void + rx(const uint8_t* mac, const uint8_t* data, uint8_t len); + + static void + tx(const uint8_t* mac, uint8_t status); + +private: + RxCallback m_rxCb = nullptr; + void* m_rxArg = nullptr; + WifiEspNowSendStatus m_txRes = WifiEspNowSendStatus::NONE; + bool m_ready = false; +}; + +/** @brief ESP-NOW API. */ +extern WifiEspNowClass WifiEspNow; + +#endif // WIFIESPNOW_H diff --git a/lib/WifiEspNow/src/WifiEspNowBroadcast.cpp b/lib/WifiEspNow/src/WifiEspNowBroadcast.cpp new file mode 100644 index 0000000000..bfbc31b7ce --- /dev/null +++ b/lib/WifiEspNow/src/WifiEspNowBroadcast.cpp @@ -0,0 +1,196 @@ +#include "WifiEspNowBroadcast.h" + +#if defined(ARDUINO_ARCH_ESP8266) +#include +#include +#elif defined(ARDUINO_ARCH_ESP32) +#include +#include +#else +#error "This library supports ESP8266 and ESP32 only." +#endif + +// #define WIFIESPNOW_DEBUG +#ifdef WIFIESPNOW_DEBUG +#define LOG(...) \ + do { \ + Serial.printf("[WifiEspNowBroadcast] " __VA_ARGS__); \ + Serial.println(); \ + } while (false) +#else +#define LOG(...) \ + do { \ + } while (false) +#endif + +WifiEspNowBroadcastClass WifiEspNowBroadcast; + +bool +WifiEspNowBroadcastClass::begin(const char* ssid, int channel, int scanFreq) +{ + m_ssid = ssid; + m_nextScan = 0; + m_scanFreq = scanFreq; + + // AP mode for announcing our presence, STA mode for scanning + WiFi.mode(WIFI_AP_STA); + // disconnect from any previously saved SSID, so that the specified channel can take effect + WiFi.disconnect(); + // establish AP at the specified channel to announce our presence + WiFi.softAP(ssid, nullptr, channel); + + return WifiEspNow.begin(); +} + +void +WifiEspNowBroadcastClass::end() +{ + WifiEspNow.end(); + WiFi.softAPdisconnect(); + m_ssid = ""; +} + +void +WifiEspNowBroadcastClass::loop() +{ + if (millis() >= m_nextScan && !m_isScanning && WiFi.scanComplete() != WIFI_SCAN_RUNNING) { + this->scan(); + } +#ifdef ARDUINO_ARCH_ESP32 + if (m_isScanning && WiFi.scanComplete() >= 0) { + this->processScan(); + } +#endif +} + +bool +WifiEspNowBroadcastClass::setKey(const uint8_t primary[WIFIESPNOW_KEYLEN], + const uint8_t peer[WIFIESPNOW_KEYLEN]) +{ + if (peer == nullptr) { + m_hasPeerKey = false; + return true; + } + m_hasPeerKey = true; + std::copy_n(peer, WIFIESPNOW_KEYLEN, m_peerKey); + return WifiEspNow.setPrimaryKey(primary); +} + +void +WifiEspNowBroadcastClass::scan() +{ + LOG("scan()"); + m_isScanning = true; +#if defined(ARDUINO_ARCH_ESP8266) + scan_config sc{}; +#elif defined(ARDUINO_ARCH_ESP32) + wifi_scan_config_t sc{}; +#endif + sc.ssid = reinterpret_cast(const_cast(m_ssid.c_str())); +#if defined(ARDUINO_ARCH_ESP8266) + wifi_station_scan(&sc, reinterpret_cast(WifiEspNowBroadcastClass::processScan)); +#elif defined(ARDUINO_ARCH_ESP32) + esp_wifi_scan_start(&sc, false); +#endif +} + +#if defined(ARDUINO_ARCH_ESP8266) +void +WifiEspNowBroadcastClass::processScan(void* result, int status) +{ + WifiEspNowBroadcast.processScan2(result, status); +} + +void +WifiEspNowBroadcastClass::processScan2(void* result, int status) + +#define FOREACH_AP(f) \ + do { \ + for (bss_info* it = reinterpret_cast(result); it; it = STAILQ_NEXT(it, next)) { \ + (f)(it->bssid, it->channel); \ + } \ + } while (false) + +#define DELETE_APS \ + do { \ + } while (false) + +#elif defined(ARDUINO_ARCH_ESP32) +void +WifiEspNowBroadcastClass::processScan() + +// ESP32 WiFiScanClass::_scanDone is always invoked after a scan complete event, so we can use +// Arduino's copy of AP records, but we must check SSID, and should not always delete AP records. + +#define FOREACH_AP(f) \ + do { \ + int nNetworks = WiFi.scanComplete(); \ + for (uint8_t i = 0; static_cast(i) < nNetworks; ++i) { \ + if (WiFi.SSID(i) != m_ssid) { \ + continue; \ + } \ + (f)(WiFi.BSSID(i), static_cast(WiFi.channel(i))); \ + } \ + } while (false) + +#define DELETE_APS \ + do { \ + bool hasOtherSsid = false; \ + int nNetworks = WiFi.scanComplete(); \ + for (uint8_t i = 0; static_cast(i) < nNetworks; ++i) { \ + if (WiFi.SSID(i) == m_ssid) { \ + continue; \ + } \ + hasOtherSsid = true; \ + break; \ + } \ + if (!hasOtherSsid) { \ + WiFi.scanDelete(); \ + } \ + } while (false) + +#endif +{ + m_isScanning = false; + m_nextScan = millis() + m_scanFreq; +#ifdef ARDUINO_ARCH_ESP8266 + if (status != 0) { + return; + } +#endif + + LOG("processScan()"); + + const int MAX_PEERS = 20; + WifiEspNowPeerInfo oldPeers[MAX_PEERS]; + int nOldPeers = std::min(WifiEspNow.listPeers(oldPeers, MAX_PEERS), MAX_PEERS); + const uint8_t PEER_FOUND = 0xFF; // assigned to .channel to indicate peer is matched + + FOREACH_AP([&](const uint8_t* bssid, uint8_t channel) { + for (int i = 0; i < nOldPeers; ++i) { + WifiEspNowPeerInfo* p = &oldPeers[i]; + if (std::equal(p->mac, p->mac + WIFIESPNOW_ALEN, bssid)) { + p->channel = PEER_FOUND; + break; + } + } + }); + + for (int i = 0; i < nOldPeers; ++i) { + WifiEspNowPeerInfo* p = &oldPeers[i]; + if (p->channel == PEER_FOUND) { + continue; + } + LOG("processScan removePeer(%02x:%02x:%02x:%02x:%02x:%02x)", p->mac[0], p->mac[1], p->mac[2], + p->mac[3], p->mac[4], p->mac[5]); + WifiEspNow.removePeer(p->mac); + } + + FOREACH_AP([&](const uint8_t* mac, uint8_t channel) { + LOG("processScan addPeer(%02x:%02x:%02x:%02x:%02x:%02x)", mac[0], mac[1], mac[2], mac[3], + mac[4], mac[5]); + WifiEspNow.addPeer(mac, channel, m_hasPeerKey ? m_peerKey : nullptr); + }); + + DELETE_APS; +} diff --git a/lib/WifiEspNow/src/WifiEspNowBroadcast.h b/lib/WifiEspNow/src/WifiEspNowBroadcast.h new file mode 100644 index 0000000000..1c48758ce1 --- /dev/null +++ b/lib/WifiEspNow/src/WifiEspNowBroadcast.h @@ -0,0 +1,102 @@ +#ifndef WIFIESPNOW_BROADCAST_H +#define WIFIESPNOW_BROADCAST_H + +#include "WifiEspNow.h" + +#include + +class WifiEspNowBroadcastClass +{ +public: + /** + * @brief Initialize ESP-NOW with pseudo broadcast. + * @param ssid AP SSID to announce and find peers. + * @param channel AP channel, used if there is no STA connection. + * @param scanFreq how often to scan for peers (milliseconds). + * @return whether success. + */ + bool + begin(const char* ssid, int channel = 1, int scanFreq = 15000); + + /** @brief Stop ESP-NOW. */ + void + end(); + + /** + * @brief Refresh peers if scanning is due. + * + * This should be invoked in Arduino sketch @c loop() function. + */ + void + loop(); + + /** + * @brief Set encryption keys. + * @param primary primary key, also known as KOK or PMK. + * @param peer peer key, also known as LMK; nullptr to disable encryption. + * The same peer key is applied to every discovered peer. + * @return whether success. + */ + bool + setKey(const uint8_t primary[WIFIESPNOW_KEYLEN], const uint8_t peer[WIFIESPNOW_KEYLEN] = nullptr); + + /** + * @brief Set receive callback. + * @param cb the callback. + * @param arg an arbitrary argument passed to the callback. + * @note Only one callback is allowed; this replaces any previous callback. + */ + void + onReceive(WifiEspNowClass::RxCallback cb, void* arg) + { + WifiEspNow.onReceive(cb, arg); + } + + /** + * @brief Broadcast a message. + * @param buf payload. + * @param count payload size, must not exceed @c WIFIESPNOW_MAXMSGLEN . + * @return whether success (message queued for transmission). + */ + bool + send(const uint8_t* buf, size_t count) + { + return WifiEspNow.send(nullptr, buf, count); + } + +private: + void + scan(); + +#if defined(ARDUINO_ARCH_ESP8266) + static void + processScan(void* result, int status); + + void + processScan2(void* result, int status); +#elif defined(ARDUINO_ARCH_ESP32) + void + processScan(); +#endif + +private: + String m_ssid; + uint8_t m_peerKey[WIFIESPNOW_KEYLEN]; + int m_scanFreq = 0; + unsigned long m_nextScan = 0; + bool m_isScanning = false; + bool m_hasPeerKey = false; +}; + +/** + * @brief ESP-NOW pseudo broadcast. + * + * In pseudo broadcast mode, every node announces itself as a group member by advertising a + * certain AP SSID. A node periodically scans other BSSIDs announcing the same SSID, and adds + * them as ESP-NOW peers. Messages are sent to all known peers. + * + * Pseudo broadcast does not depend on ESP-NOW API to support broadcast. + */ +extern WifiEspNowBroadcastClass WifiEspNowBroadcast; + +#endif // WIFIESPNOW_BROADCAST_H diff --git a/platformio_core_defs.ini b/platformio_core_defs.ini index 1f9b9e7e0c..13da550191 100644 --- a/platformio_core_defs.ini +++ b/platformio_core_defs.ini @@ -42,6 +42,7 @@ build_flags = -D NDEBUG -fno-strict-aliasing -I$PROJECT_DIR/src/include -include "ESPEasy_config.h" + -Werror=return-type lib_ignore = ESP32_ping ESP32WebServer diff --git a/platformio_esp32_envs.ini b/platformio_esp32_envs.ini index 9e261988dd..bf2a920b1d 100644 --- a/platformio_esp32_envs.ini +++ b/platformio_esp32_envs.ini @@ -1,341 +1,341 @@ -[esp32_base_idf5] -extends = common, core_esp32_IDF5_4_1__3_2_0_LittleFS -upload_speed = 460800 -upload_before_reset = default_reset -upload_after_reset = hard_reset -extra_scripts = pre:tools/pio/pre_esp32.py - post:tools/pio/post_esp32.py - ${extra_scripts_default.extra_scripts} - post:tools/pio/copy_files.py -; you can disable debug linker flag to reduce binary size (comment out line below), but the backtraces will become less readable -; tools/pio/extra_linker_flags.py -; fix the platform package to use gcc-ar and gcc-ranlib to enable lto linker plugin -; more detail: https://embeddedartistry.com/blog/2020/04/13/prefer-gcc-ar-to-ar-in-your-buildsystems/ -; pre:tools/pio/apply_patches.py - -; When using LTO, make sure NOT to use -mtext-section-literals -; -mtext-section-literals may be required when building large builds -; However LTO cannot optimize builds with text section literals and thus will result in quite a lot larger builds (80k - 140k larger) -build_unflags = -Wall - -fno-lto -build_flags = ${core_esp32_IDF5_4_1__3_2_0_LittleFS.build_flags} -; ${mqtt_flags.build_flags} - -DMQTT_MAX_PACKET_SIZE=2048 - -DCONFIG_FREERTOS_ASSERT_DISABLE - -DCONFIG_LWIP_ESP_GRATUITOUS_ARP - -fno-strict-aliasing - -flto=auto - -Wswitch - -DCORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_NONE -; -DCORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_INFO -; -DCORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_VERBOSE - -DLWIP_IPV6=1 -monitor_filters = esp32_exception_decoder -lib_ignore = - ${core_esp32_IDF5_4_1__3_2_0_LittleFS.lib_ignore} - - -[esp32_always] -lib_ignore = ESP8266Ping - ESP8266HTTPUpdateServer - ESP8266WiFi - ESP8266WebServer - ESP8266mDNS - ESPEasy_ESP8266Ping - RABurton ESP8266 Mutex - TinyWireM - LittleFS_esp32 - Adafruit NeoPixel - ${esp32_base_idf5.lib_ignore} - - - -[esp32_common_LittleFS_ETH] -extends = esp32_base_idf5 -build_flags = ${esp32_base_idf5.build_flags} -; -mtext-section-literals - -DFEATURE_ETHERNET=1 - -DESP32_CLASSIC - -DUSE_LITTLEFS -build_unflags = ${esp32_base_idf5.build_unflags} -extra_scripts = ${esp32_base_idf5.extra_scripts} -board_build.filesystem = littlefs -lib_ignore = ${esp32_always.lib_ignore} - ESP32_ping - ESP32 BLE Arduino - ${esp32_base_idf5.lib_ignore} - - -[esp32_IRExt_LittleFS_ETH] -extends = esp32_common_LittleFS_ETH -lib_ignore = ${esp32_always.lib_ignore} - ESP32_ping -build_flags = ${esp32_common_LittleFS_ETH.build_flags} - -DFEATURE_ETHERNET=1 - -DFEATURE_ARDUINO_OTA=1 - -DPLUGIN_BUILD_NORMAL_IRext - -DCOLLECTION_FEATURE_RTTTL=1 - -DLIMIT_BUILD_SIZE -extra_scripts = ${esp32_common_LittleFS_ETH.extra_scripts} - pre:tools/pio/ir_build_check.py - - -[esp32_custom_base_LittleFS] -extends = esp32_common_LittleFS_ETH -build_flags = ${esp32_common_LittleFS_ETH.build_flags} - -DPLUGIN_BUILD_CUSTOM -extra_scripts = ${esp32_common_LittleFS_ETH.extra_scripts} - pre:tools/pio/pre_custom_esp32.py - - -[env:custom_ESP32_4M316k_LittleFS_ETH] -extends = esp32_custom_base_LittleFS -board = esp32_4M -build_flags = ${esp32_custom_base_LittleFS.build_flags} - -DFEATURE_ETHERNET=1 - - -[env:custom_ESP32_16M8M_LittleFS_ETH] -extends = env:custom_ESP32_4M316k_LittleFS_ETH -board = esp32_16M8M -board_upload.flash_size = 16MB -build_flags = ${env:custom_ESP32_4M316k_LittleFS_ETH.build_flags} - - -[env:custom_IR_ESP32_4M316k_LittleFS_ETH] -extends = esp32_common_LittleFS_ETH -board = esp32_4M -build_flags = ${esp32_common_LittleFS_ETH.build_flags} - -DPLUGIN_BUILD_CUSTOM - -DPLUGIN_BUILD_IR - -lib_ignore = ${esp32_always.lib_ignore} - ESP32_ping -extra_scripts = ${esp32_common_LittleFS_ETH.extra_scripts} - pre:tools/pio/pre_custom_esp32_IR.py - pre:tools/pio/ir_build_check.py - -[env:custom_ESP32_4M2M_NO_OTA_LittleFS_ETH] -extends = env:custom_ESP32_4M316k_LittleFS_ETH -board = esp32_4M2M -build_flags = ${env:custom_ESP32_4M316k_LittleFS_ETH.build_flags} - -DNO_HTTP_UPDATER - - - -[env:collection_A_ESP32_4M316k_LittleFS_ETH] -extends = esp32_common_LittleFS_ETH -board = esp32_4M -build_flags = ${esp32_common_LittleFS_ETH.build_flags} - -DFEATURE_ARDUINO_OTA=1 - -DPLUGIN_SET_COLLECTION_ESP32 - -DCOLLECTION_FEATURE_RTTTL=1 - - -[env:collection_B_ESP32_4M316k_LittleFS_ETH] -extends = esp32_common_LittleFS_ETH -board = esp32_4M -build_flags = ${esp32_common_LittleFS_ETH.build_flags} - -DFEATURE_ARDUINO_OTA=1 - -DPLUGIN_SET_COLLECTION_B_ESP32 - -DCOLLECTION_FEATURE_RTTTL=1 - - -[env:collection_C_ESP32_4M316k_LittleFS_ETH] -extends = esp32_common_LittleFS_ETH -board = esp32_4M -build_flags = ${esp32_common_LittleFS_ETH.build_flags} - -DFEATURE_ARDUINO_OTA=1 - -DPLUGIN_SET_COLLECTION_C_ESP32 - -DCOLLECTION_FEATURE_RTTTL=1 - - -[env:collection_D_ESP32_4M316k_LittleFS_ETH] -extends = esp32_common_LittleFS_ETH -board = esp32_4M -build_flags = ${esp32_common_LittleFS_ETH.build_flags} - -DFEATURE_ARDUINO_OTA=1 - -DPLUGIN_SET_COLLECTION_D_ESP32 - -DCOLLECTION_FEATURE_RTTTL=1 - - -[env:collection_E_ESP32_4M316k_LittleFS_ETH] -extends = esp32_common_LittleFS_ETH -board = esp32_4M -build_flags = ${esp32_common_LittleFS_ETH.build_flags} - -DFEATURE_ARDUINO_OTA=1 - -DPLUGIN_SET_COLLECTION_E_ESP32 - -DCOLLECTION_FEATURE_RTTTL=1 - - -[env:collection_F_ESP32_4M316k_LittleFS_ETH] -extends = esp32_common_LittleFS_ETH -board = esp32_4M -build_flags = ${esp32_common_LittleFS_ETH.build_flags} - -DFEATURE_ARDUINO_OTA=1 - -DPLUGIN_SET_COLLECTION_F_ESP32 - -DCOLLECTION_FEATURE_RTTTL=1 - - -[env:collection_G_ESP32_4M316k_LittleFS_ETH] -extends = esp32_common_LittleFS_ETH -board = esp32_4M -build_flags = ${esp32_common_LittleFS_ETH.build_flags} - -DFEATURE_ARDUINO_OTA=1 - -DPLUGIN_SET_COLLECTION_G_ESP32 - -DCOLLECTION_FEATURE_RTTTL=1 - - - -[env:collection_A_ESP32_IRExt_4M316k_LittleFS_ETH] -extends = esp32_IRExt_LittleFS_ETH -board = esp32_4M -build_flags = ${esp32_IRExt_LittleFS_ETH.build_flags} - -DPLUGIN_SET_COLLECTION_ESP32 - -[env:collection_B_ESP32_IRExt_4M316k_LittleFS_ETH] -extends = esp32_IRExt_LittleFS_ETH -board = esp32_4M -build_flags = ${esp32_IRExt_LittleFS_ETH.build_flags} - -DPLUGIN_SET_COLLECTION_B_ESP32 - -[env:collection_C_ESP32_IRExt_4M316k_LittleFS_ETH] -extends = esp32_IRExt_LittleFS_ETH -board = esp32_4M -build_flags = ${esp32_IRExt_LittleFS_ETH.build_flags} - -DPLUGIN_SET_COLLECTION_C_ESP32 - -[env:collection_D_ESP32_IRExt_4M316k_LittleFS_ETH] -extends = esp32_IRExt_LittleFS_ETH -board = esp32_4M -build_flags = ${esp32_IRExt_LittleFS_ETH.build_flags} - -DPLUGIN_SET_COLLECTION_D_ESP32 - -[env:collection_E_ESP32_IRExt_4M316k_LittleFS_ETH] -extends = esp32_IRExt_LittleFS_ETH -board = esp32_4M -build_flags = ${esp32_IRExt_LittleFS_ETH.build_flags} - -DPLUGIN_SET_COLLECTION_E_ESP32 - -[env:collection_F_ESP32_IRExt_4M316k_LittleFS_ETH] -extends = esp32_IRExt_LittleFS_ETH -board = esp32_4M -build_flags = ${esp32_IRExt_LittleFS_ETH.build_flags} - -DPLUGIN_SET_COLLECTION_F_ESP32 - -[env:collection_G_ESP32_IRExt_4M316k_LittleFS_ETH] -extends = esp32_IRExt_LittleFS_ETH -board = esp32_4M -build_flags = ${esp32_IRExt_LittleFS_ETH.build_flags} - -DPLUGIN_SET_COLLECTION_G_ESP32 - -[env:energy_ESP32_4M316k_LittleFS_ETH] -extends = esp32_common_LittleFS_ETH -board = esp32_4M -build_flags = ${esp32_common_LittleFS_ETH.build_flags} - -DFEATURE_ARDUINO_OTA=1 - -DPLUGIN_ENERGY_COLLECTION - - -[env:display_ESP32_4M316k_LittleFS_ETH] -extends = esp32_common_LittleFS_ETH -board = esp32_4M -build_flags = ${esp32_common_LittleFS_ETH.build_flags} - -DFEATURE_ARDUINO_OTA=1 - -DPLUGIN_DISPLAY_COLLECTION - -D ST7789_EXTRA_INIT=1 - -D P116_EXTRA_ST7789=1 - - - -[env:climate_ESP32_4M316k_LittleFS_ETH] -extends = esp32_common_LittleFS_ETH -board = esp32_4M -lib_ignore = ${esp32_always.lib_ignore} - ESP32_ping - ${esp32_common_LittleFS_ETH.lib_ignore} - ${no_ir.lib_ignore} -build_flags = ${esp32_common_LittleFS_ETH.build_flags} - -DPLUGIN_CLIMATE_COLLECTION - - -[env:neopixel_ESP32_4M316k_LittleFS_ETH] -extends = esp32_common_LittleFS_ETH -board = esp32_4M -build_flags = ${esp32_common_LittleFS_ETH.build_flags} - -DFEATURE_ARDUINO_OTA=1 - -DFEATURE_SD=1 - -D PLUGIN_NEOPIXEL_COLLECTION - - - - -[env:custom_IR_ESP32_16M8M_LittleFS_ETH] -extends = esp32_common_LittleFS_ETH -board = esp32_16M8M -board_upload.flash_size = 16MB -build_flags = ${esp32_common_LittleFS_ETH.build_flags} - -DPLUGIN_BUILD_CUSTOM - -DPLUGIN_BUILD_IR -lib_ignore = ${esp32_always.lib_ignore} - ESP32_ping - ${esp32_common_LittleFS_ETH.lib_ignore} -extra_scripts = ${esp32_common_LittleFS_ETH.extra_scripts} - pre:tools/pio/pre_custom_esp32_IR.py - pre:tools/pio/ir_build_check.py - - - -[env:normal_ESP32_4M316k_LittleFS_ETH] -extends = esp32_common_LittleFS_ETH -board = esp32_4M -lib_ignore = ${esp32_always.lib_ignore} - ESP32_ping - ${esp32_common_LittleFS_ETH.lib_ignore} - ${no_ir.lib_ignore} -build_flags = ${esp32_common_LittleFS_ETH.build_flags} - - - -[env:normal_ESP32_IRExt_4M316k_LittleFS_ETH] -extends = esp32_IRExt_LittleFS_ETH -board = esp32_4M -build_flags = ${esp32_IRExt_LittleFS_ETH.build_flags} - - - - -; ESP32 MAX builds 16M flash ------------------------------ - -; A Lolin D32 PRO with 16MB Flash, allowing 4MB sketch size, and file storage using LittleFS filesystem -[env:max_ESP32_16M8M_LittleFS_ETH] -extends = esp32_common_LittleFS_ETH -board = esp32_16M8M -board_upload.flash_size = 16MB -lib_ignore = ${esp32_always.lib_ignore} - ESP32_ping - ${esp32_common_LittleFS_ETH.lib_ignore} -build_flags = ${esp32_common_LittleFS_ETH.build_flags} - -DFEATURE_ARDUINO_OTA=1 - -DPLUGIN_BUILD_MAX_ESP32 - -DPLUGIN_BUILD_IR_EXTENDED - -D ST7789_EXTRA_INIT=1 - -D P116_EXTRA_ST7789=1 -extra_scripts = ${esp32_common_LittleFS_ETH.extra_scripts} -board_build.filesystem = littlefs - -; If you have a board with Ethernet integrated and 16MB Flash, then this configuration could be enabled, it's based on the max_ESP32_16M8M_LittleFS definition -; [env:max_ESP32_16M8M_LittleFS_ETH] -; extends = env:max_ESP32_16M8M_LittleFS -; board = ${env:max_ESP32_16M8M_LittleFS.board} -; build_flags = ${env:max_ESP32_16M8M_LittleFS.build_flags} - - - - - - - - - - +[esp32_base_idf5] +extends = common, core_esp32_IDF5_4_1__3_2_0_LittleFS +upload_speed = 460800 +upload_before_reset = default_reset +upload_after_reset = hard_reset +extra_scripts = pre:tools/pio/pre_esp32.py + post:tools/pio/post_esp32.py + ${extra_scripts_default.extra_scripts} + post:tools/pio/copy_files.py +; you can disable debug linker flag to reduce binary size (comment out line below), but the backtraces will become less readable +; tools/pio/extra_linker_flags.py +; fix the platform package to use gcc-ar and gcc-ranlib to enable lto linker plugin +; more detail: https://embeddedartistry.com/blog/2020/04/13/prefer-gcc-ar-to-ar-in-your-buildsystems/ +; pre:tools/pio/apply_patches.py + +; When using LTO, make sure NOT to use -mtext-section-literals +; -mtext-section-literals may be required when building large builds +; However LTO cannot optimize builds with text section literals and thus will result in quite a lot larger builds (80k - 140k larger) +build_unflags = -Wall + -fno-lto +build_flags = ${core_esp32_IDF5_4_1__3_2_0_LittleFS.build_flags} +; ${mqtt_flags.build_flags} + -DMQTT_MAX_PACKET_SIZE=2048 + -DCONFIG_FREERTOS_ASSERT_DISABLE + -DCONFIG_LWIP_ESP_GRATUITOUS_ARP + -fno-strict-aliasing + -flto=auto + -Wswitch + -DCORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_NONE +; -DCORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_INFO +; -DCORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_VERBOSE + -DLWIP_IPV6=1 +monitor_filters = esp32_exception_decoder +lib_ignore = + ${core_esp32_IDF5_4_1__3_2_0_LittleFS.lib_ignore} + + +[esp32_always] +lib_ignore = ESP8266Ping + ESP8266HTTPUpdateServer + ESP8266WiFi + ESP8266WebServer + ESP8266mDNS + ESPEasy_ESP8266Ping + RABurton ESP8266 Mutex + TinyWireM + LittleFS_esp32 + Adafruit NeoPixel + ${esp32_base_idf5.lib_ignore} + + + +[esp32_common_LittleFS_ETH] +extends = esp32_base_idf5 +build_flags = ${esp32_base_idf5.build_flags} +; -mtext-section-literals + -DFEATURE_ETHERNET=1 + -DESP32_CLASSIC + -DUSE_LITTLEFS +build_unflags = ${esp32_base_idf5.build_unflags} +extra_scripts = ${esp32_base_idf5.extra_scripts} +board_build.filesystem = littlefs +lib_ignore = ${esp32_always.lib_ignore} + ESP32_ping + ESP32 BLE Arduino + ${esp32_base_idf5.lib_ignore} + + +[esp32_IRExt_LittleFS_ETH] +extends = esp32_common_LittleFS_ETH +lib_ignore = ${esp32_always.lib_ignore} + ESP32_ping +build_flags = ${esp32_common_LittleFS_ETH.build_flags} + -DFEATURE_ETHERNET=1 + -DFEATURE_ARDUINO_OTA=1 + -DPLUGIN_BUILD_NORMAL_IRext + -DCOLLECTION_FEATURE_RTTTL=1 + -DLIMIT_BUILD_SIZE +extra_scripts = ${esp32_common_LittleFS_ETH.extra_scripts} + pre:tools/pio/ir_build_check.py + + +[esp32_custom_base_LittleFS] +extends = esp32_common_LittleFS_ETH +build_flags = ${esp32_common_LittleFS_ETH.build_flags} + -DPLUGIN_BUILD_CUSTOM +extra_scripts = ${esp32_common_LittleFS_ETH.extra_scripts} + pre:tools/pio/pre_custom_esp32.py + + +[env:custom_ESP32_4M316k_LittleFS_ETH] +extends = esp32_custom_base_LittleFS +board = esp32_4M +build_flags = ${esp32_custom_base_LittleFS.build_flags} + -DFEATURE_ETHERNET=1 + + +[env:custom_ESP32_16M8M_LittleFS_ETH] +extends = env:custom_ESP32_4M316k_LittleFS_ETH +board = esp32_16M8M +board_upload.flash_size = 16MB +build_flags = ${env:custom_ESP32_4M316k_LittleFS_ETH.build_flags} + + +[env:custom_IR_ESP32_4M316k_LittleFS_ETH] +extends = esp32_common_LittleFS_ETH +board = esp32_4M +build_flags = ${esp32_common_LittleFS_ETH.build_flags} + -DPLUGIN_BUILD_CUSTOM + -DPLUGIN_BUILD_IR + +lib_ignore = ${esp32_always.lib_ignore} + ESP32_ping +extra_scripts = ${esp32_common_LittleFS_ETH.extra_scripts} + pre:tools/pio/pre_custom_esp32_IR.py + pre:tools/pio/ir_build_check.py + +[env:custom_ESP32_4M2M_NO_OTA_LittleFS_ETH] +extends = env:custom_ESP32_4M316k_LittleFS_ETH +board = esp32_4M2M +build_flags = ${env:custom_ESP32_4M316k_LittleFS_ETH.build_flags} + -DNO_HTTP_UPDATER + + + +[env:collection_A_ESP32_4M316k_LittleFS_ETH] +extends = esp32_common_LittleFS_ETH +board = esp32_4M +build_flags = ${esp32_common_LittleFS_ETH.build_flags} + -DFEATURE_ARDUINO_OTA=1 + -DPLUGIN_SET_COLLECTION_ESP32 + -DCOLLECTION_FEATURE_RTTTL=1 + + +[env:collection_B_ESP32_4M316k_LittleFS_ETH] +extends = esp32_common_LittleFS_ETH +board = esp32_4M +build_flags = ${esp32_common_LittleFS_ETH.build_flags} + -DFEATURE_ARDUINO_OTA=1 + -DPLUGIN_SET_COLLECTION_B_ESP32 + -DCOLLECTION_FEATURE_RTTTL=1 + + +[env:collection_C_ESP32_4M316k_LittleFS_ETH] +extends = esp32_common_LittleFS_ETH +board = esp32_4M +build_flags = ${esp32_common_LittleFS_ETH.build_flags} + -DFEATURE_ARDUINO_OTA=1 + -DPLUGIN_SET_COLLECTION_C_ESP32 + -DCOLLECTION_FEATURE_RTTTL=1 + + +[env:collection_D_ESP32_4M316k_LittleFS_ETH] +extends = esp32_common_LittleFS_ETH +board = esp32_4M +build_flags = ${esp32_common_LittleFS_ETH.build_flags} + -DFEATURE_ARDUINO_OTA=1 + -DPLUGIN_SET_COLLECTION_D_ESP32 + -DCOLLECTION_FEATURE_RTTTL=1 + + +[env:collection_E_ESP32_4M316k_LittleFS_ETH] +extends = esp32_common_LittleFS_ETH +board = esp32_4M +build_flags = ${esp32_common_LittleFS_ETH.build_flags} + -DFEATURE_ARDUINO_OTA=1 + -DPLUGIN_SET_COLLECTION_E_ESP32 + -DCOLLECTION_FEATURE_RTTTL=1 + + +[env:collection_F_ESP32_4M316k_LittleFS_ETH] +extends = esp32_common_LittleFS_ETH +board = esp32_4M +build_flags = ${esp32_common_LittleFS_ETH.build_flags} + -DFEATURE_ARDUINO_OTA=1 + -DPLUGIN_SET_COLLECTION_F_ESP32 + -DCOLLECTION_FEATURE_RTTTL=1 + + +[env:collection_G_ESP32_4M316k_LittleFS_ETH] +extends = esp32_common_LittleFS_ETH +board = esp32_4M +build_flags = ${esp32_common_LittleFS_ETH.build_flags} + -DFEATURE_ARDUINO_OTA=1 + -DPLUGIN_SET_COLLECTION_G_ESP32 + -DCOLLECTION_FEATURE_RTTTL=1 + + + +[env:collection_A_ESP32_IRExt_4M316k_LittleFS_ETH] +extends = esp32_IRExt_LittleFS_ETH +board = esp32_4M +build_flags = ${esp32_IRExt_LittleFS_ETH.build_flags} + -DPLUGIN_SET_COLLECTION_ESP32 + +[env:collection_B_ESP32_IRExt_4M316k_LittleFS_ETH] +extends = esp32_IRExt_LittleFS_ETH +board = esp32_4M +build_flags = ${esp32_IRExt_LittleFS_ETH.build_flags} + -DPLUGIN_SET_COLLECTION_B_ESP32 + +[env:collection_C_ESP32_IRExt_4M316k_LittleFS_ETH] +extends = esp32_IRExt_LittleFS_ETH +board = esp32_4M +build_flags = ${esp32_IRExt_LittleFS_ETH.build_flags} + -DPLUGIN_SET_COLLECTION_C_ESP32 + +[env:collection_D_ESP32_IRExt_4M316k_LittleFS_ETH] +extends = esp32_IRExt_LittleFS_ETH +board = esp32_4M +build_flags = ${esp32_IRExt_LittleFS_ETH.build_flags} + -DPLUGIN_SET_COLLECTION_D_ESP32 + +[env:collection_E_ESP32_IRExt_4M316k_LittleFS_ETH] +extends = esp32_IRExt_LittleFS_ETH +board = esp32_4M +build_flags = ${esp32_IRExt_LittleFS_ETH.build_flags} + -DPLUGIN_SET_COLLECTION_E_ESP32 + +[env:collection_F_ESP32_IRExt_4M316k_LittleFS_ETH] +extends = esp32_IRExt_LittleFS_ETH +board = esp32_4M +build_flags = ${esp32_IRExt_LittleFS_ETH.build_flags} + -DPLUGIN_SET_COLLECTION_F_ESP32 + +[env:collection_G_ESP32_IRExt_4M316k_LittleFS_ETH] +extends = esp32_IRExt_LittleFS_ETH +board = esp32_4M +build_flags = ${esp32_IRExt_LittleFS_ETH.build_flags} + -DPLUGIN_SET_COLLECTION_G_ESP32 + +[env:energy_ESP32_4M316k_LittleFS_ETH] +extends = esp32_common_LittleFS_ETH +board = esp32_4M +build_flags = ${esp32_common_LittleFS_ETH.build_flags} + -DFEATURE_ARDUINO_OTA=1 + -DPLUGIN_ENERGY_COLLECTION + + +[env:display_ESP32_4M316k_LittleFS_ETH] +extends = esp32_common_LittleFS_ETH +board = esp32_4M +build_flags = ${esp32_common_LittleFS_ETH.build_flags} + -DFEATURE_ARDUINO_OTA=1 + -DPLUGIN_DISPLAY_COLLECTION + -D ST7789_EXTRA_INIT=1 + -D P116_EXTRA_ST7789=1 + + + +[env:climate_ESP32_4M316k_LittleFS_ETH] +extends = esp32_common_LittleFS_ETH +board = esp32_4M +lib_ignore = ${esp32_always.lib_ignore} + ESP32_ping + ${esp32_common_LittleFS_ETH.lib_ignore} + ${no_ir.lib_ignore} +build_flags = ${esp32_common_LittleFS_ETH.build_flags} + -DPLUGIN_CLIMATE_COLLECTION + + +[env:neopixel_ESP32_4M316k_LittleFS_ETH] +extends = esp32_common_LittleFS_ETH +board = esp32_4M +build_flags = ${esp32_common_LittleFS_ETH.build_flags} + -DFEATURE_ARDUINO_OTA=1 + -DFEATURE_SD=1 + -D PLUGIN_NEOPIXEL_COLLECTION + + + + +[env:custom_IR_ESP32_16M8M_LittleFS_ETH] +extends = esp32_common_LittleFS_ETH +board = esp32_16M8M +board_upload.flash_size = 16MB +build_flags = ${esp32_common_LittleFS_ETH.build_flags} + -DPLUGIN_BUILD_CUSTOM + -DPLUGIN_BUILD_IR +lib_ignore = ${esp32_always.lib_ignore} + ESP32_ping + ${esp32_common_LittleFS_ETH.lib_ignore} +extra_scripts = ${esp32_common_LittleFS_ETH.extra_scripts} + pre:tools/pio/pre_custom_esp32_IR.py + pre:tools/pio/ir_build_check.py + + + +[env:normal_ESP32_4M316k_LittleFS_ETH] +extends = esp32_common_LittleFS_ETH +board = esp32_4M +lib_ignore = ${esp32_always.lib_ignore} + ESP32_ping + ${esp32_common_LittleFS_ETH.lib_ignore} + ${no_ir.lib_ignore} +build_flags = ${esp32_common_LittleFS_ETH.build_flags} + + + +[env:normal_ESP32_IRExt_4M316k_LittleFS_ETH] +extends = esp32_IRExt_LittleFS_ETH +board = esp32_4M +build_flags = ${esp32_IRExt_LittleFS_ETH.build_flags} + + + + +; ESP32 MAX builds 16M flash ------------------------------ + +; A Lolin D32 PRO with 16MB Flash, allowing 4MB sketch size, and file storage using LittleFS filesystem +[env:max_ESP32_16M8M_LittleFS_ETH] +extends = esp32_common_LittleFS_ETH +board = esp32_16M8M +board_upload.flash_size = 16MB +lib_ignore = ${esp32_always.lib_ignore} + ESP32_ping + ${esp32_common_LittleFS_ETH.lib_ignore} +build_flags = ${esp32_common_LittleFS_ETH.build_flags} + -DFEATURE_ARDUINO_OTA=1 + -DPLUGIN_BUILD_MAX_ESP32 + -DPLUGIN_BUILD_IR_EXTENDED + -D ST7789_EXTRA_INIT=1 + -D P116_EXTRA_ST7789=1 +extra_scripts = ${esp32_common_LittleFS_ETH.extra_scripts} +board_build.filesystem = littlefs + +; If you have a board with Ethernet integrated and 16MB Flash, then this configuration could be enabled, it's based on the max_ESP32_16M8M_LittleFS definition +; [env:max_ESP32_16M8M_LittleFS_ETH] +; extends = env:max_ESP32_16M8M_LittleFS +; board = ${env:max_ESP32_16M8M_LittleFS.board} +; build_flags = ${env:max_ESP32_16M8M_LittleFS.build_flags} + + + + + + + + + + diff --git a/src/Custom-sample.h b/src/Custom-sample.h index 232ae1963f..c4a7b53c22 100644 --- a/src/Custom-sample.h +++ b/src/Custom-sample.h @@ -1,609 +1,610 @@ -#ifndef ESPEASY_CUSTOM_H -#define ESPEASY_CUSTOM_H - -/* - To modify the stock configuration without changing the EspEasy.ino file : - 1) rename this file to "Custom.h" (It is ignored by Git) - 2) define your own settings below - 3) Build one of the environments with Custom in their name, they will automatically use this file if it exists - */ - - -/* - ####################################################################################################### - Your Own Default Settings - ####################################################################################################### - You can basically ovveride ALL macro defined in ESPEasy.ino. - Don't forget to first #undef each existing #define that you add below. - But since this Custom.h is included before other defines are made, you don't have to undef a lot of defines. - Here are some examples: - */ - -// --- Feature Flagging --------------------------------------------------------- -// Can be set to 1 to enable, 0 to disable, or not set to use the default (usually via define_plugin_sets.h) - -#define FEATURE_RULES_EASY_COLOR_CODE 1 // Use code highlighting, autocompletion and command suggestions in Rules -#define FEATURE_ESPEASY_P2P 1 // (1/0) enables the ESP Easy P2P protocol -#define FEATURE_ARDUINO_OTA 1 // enables the Arduino OTA capabilities -#define FEATURE_THINGSPEAK_EVENT 0 // Generates an event when requesting last value of a field in thingspeak via SendToHTTP(e.g. sendToHTTP,api.thingspeak.com,80,/channels/1667332/fields/5/last) -#define FEATURE_OPENMETEO_EVENT 0 // Generates an event with the response of a open-meteo request (https://open-meteo.com/en/docs) -#define FEATURE_JSON_EVENT 0 // Generates an event with the values of a JSON repsonse of an HTTP call. Keys are stored in json.keys one key per line (e.g.: Body.Data.DAY_ENERGY.Values.1) -// #define FEATURE_SD 1 // Enable SD card support -// #define FEATURE_DOWNLOAD 1 // Enable downloading a file from an url - -#ifdef BUILD_GIT -# undef BUILD_GIT -#endif // ifdef BUILD_GIT - -#define BUILD_GIT "My Build: " __DATE__ " " __TIME__ - - -#define DEFAULT_NAME "MyEspEasyDevice" // Enter your device friendly name -#define UNIT 0 // Unit Number -#define DEFAULT_DELAY 60 // Sleep Delay in seconds - -// --- Wifi AP Mode (when your Wifi Network is not reachable) ---------------------------------------- -#define DEFAULT_AP_IP 192, 168, 4, 1 // Enter IP address (comma separated) for AP (config) mode -#define DEFAULT_AP_SUBNET 255, 255, 255, 0 // Enter IP address (comma separated) for AP (config) mode -#define DEFAULT_AP_KEY "configesp" // Enter network WPA key for AP (config) mode - -// --- Wifi Client Mode ----------------------------------------------------------------------------- -#define DEFAULT_SSID "MyHomeSSID" // Enter your network SSID -#define DEFAULT_KEY "MySuperSecretPassword" // Enter your network WPA key -#define DEFAULT_SSID2 "" // Enter your fallback network SSID -#define DEFAULT_KEY2 "" // Enter your fallback network WPA key -#define DEFAULT_WIFI_INCLUDE_HIDDEN_SSID false // Allow to connect to hidden SSID APs -#define DEFAULT_USE_STATIC_IP false // (true|false) enabled or disabled static IP -#define DEFAULT_IP "192.168.0.50" // Enter your IP address -#define DEFAULT_DNS "192.168.0.1" // Enter your DNS -#define DEFAULT_GW "192.168.0.1" // Enter your Gateway -#define DEFAULT_SUBNET "255.255.255.0" // Enter your Subnet -#define DEFAULT_IPRANGE_LOW "0.0.0.0" // Allowed IP range to access webserver -#define DEFAULT_IPRANGE_HIGH "255.255.255.255" // Allowed IP range to access webserver -#define DEFAULT_IP_BLOCK_LEVEL 1 // 0: ALL_ALLOWED 1: LOCAL_SUBNET_ALLOWED 2: -// ONLY_IP_RANGE_ALLOWED -#define DEFAULT_ADMIN_USERNAME "admin" -#define DEFAULT_ADMIN_PASS "" - -#define DEFAULT_WIFI_CONNECTION_TIMEOUT 10000 // minimum timeout in ms for WiFi to be connected. -#define DEFAULT_WIFI_FORCE_BG_MODE false // when set, only allow to connect in 802.11B or G mode (not N) -#define DEFAULT_WIFI_RESTART_WIFI_CONN_LOST false // Perform wifi off and on when connection was lost. -#define DEFAULT_ECO_MODE false // When set, make idle calls between executing tasks. -#define DEFAULT_WIFI_NONE_SLEEP false // When set, the wifi will be set to no longer sleep (more power -// used and need reboot to reset mode) -#define DEFAULT_GRATUITOUS_ARP false // When set, the node will send periodical gratuitous ARP - // packets to announce itself. -#define DEFAULT_TOLERANT_LAST_ARG_PARSE false // When set, the last argument of some commands will be parsed to the end of the line - // See: https://github.com/letscontrolit/ESPEasy/issues/2724 -#define DEFAULT_SEND_TO_HTTP_ACK false // Wait for ack with SendToHttp command. - -#define DEFAULT_AP_DONT_FORCE_SETUP false // Allow optional usage of Sensor without WIFI avaiable // When set you can use the Sensor in AP-Mode without beeing forced to /setup -#define DEFAULT_DONT_ALLOW_START_AP false // Usually the AP will be started when no WiFi is defined, or the defined one cannot be found. This flag may prevent it. - -// --- Default Controller ------------------------------------------------------------------------------ -#define DEFAULT_CONTROLLER false // true or false enabled or disabled, set 1st controller - // defaults -#define DEFAULT_CONTROLLER_ENABLED true // Enable default controller by default -#define DEFAULT_CONTROLLER_USER "" // Default controller user -#define DEFAULT_CONTROLLER_PASS "" // Default controller Password - -// using a default template, you also need to set a DEFAULT PROTOCOL to a suitable MQTT protocol ! -#define DEFAULT_PUB "sensors/espeasy/%sysname%/%tskname%/%valname%" // Enter your pub -#define DEFAULT_SUB "sensors/espeasy/%sysname%/#" // Enter your sub -#define DEFAULT_SERVER "192.168.0.8" // Enter your Server IP address -#define DEFAULT_SERVER_HOST "" // Server hostname -#define DEFAULT_SERVER_USEDNS false // true: Use hostname. false: use IP -#define DEFAULT_USE_EXTD_CONTROLLER_CREDENTIALS false // true: Allow longer user credentials for controllers - -#define DEFAULT_PORT 8080 // Enter your Server port value -#define DEFAULT_CONTROLLER_TIMEOUT 100 // Default timeout in msec - -#define DEFAULT_PROTOCOL 0 // Protocol used for controller communications - // 0 = Stand-alone (no controller set) - // 1 = Domoticz HTTP - // 2 = Domoticz MQTT - // 3 = Nodo Telnet - // 4 = ThingSpeak - // 5 = Home Assistant (openHAB) MQTT - // 6 = PiDome MQTT - // 7 = EmonCMS - // 8 = Generic HTTP - // 9 = FHEM HTTP - -#ifdef ESP8266 -#define DEFAULT_PIN_I2C_SDA 4 -#endif -#ifdef ESP32 -#define DEFAULT_PIN_I2C_SDA -1 // Undefined -#endif -#ifdef ESP8266 -#define DEFAULT_PIN_I2C_SCL 5 -#endif -#ifdef ESP32 -#define DEFAULT_PIN_I2C_SCL -1 // Undefined -#endif -#define DEFAULT_PIN_I2C2_SDA -1 // Undefined -#define DEFAULT_PIN_I2C3_SDA -1 // Undefined -#define DEFAULT_PIN_I2C2_SCL -1 // Undefined -#define DEFAULT_PIN_I2C3_SCL -1 // Undefined -#define DEFAULT_I2C_CLOCK_SPEED 400000 // Use 100 kHz if working with old I2C chips -#define FEATURE_I2C_DEVICE_SCAN 1 - -#define DEFAULT_SPI 0 //0=disabled 1=enabled and for ESP32 there is option 2 =HSPI - -#define DEFAULT_PIN_STATUS_LED (-1) -#define DEFAULT_PIN_STATUS_LED_INVERSED true - -#define DEFAULT_PIN_RESET_BUTTON (-1) - - -#define DEFAULT_USE_RULES false // (true|false) Enable Rules? -#define DEFAULT_RULES_OLDENGINE true - -#define DEFAULT_MQTT_RETAIN false // (true|false) Retain MQTT messages? -#define DEFAULT_CONTROLLER_DELETE_OLDEST false // (true|false) to delete oldest message when queue is full -#define DEFAULT_CONTROLLER_MUST_CHECK_REPLY false // (true|false) Check Acknowledgment -#define DEFAULT_MQTT_DELAY 100 // Time in milliseconds to retain MQTT messages -#define DEFAULT_MQTT_LWT_TOPIC "" // Default lwt topic -#define DEFAULT_MQTT_LWT_CONNECT_MESSAGE "Connected" // Default lwt message -#define DEFAULT_MQTT_LWT_DISCONNECT_MESSAGE "Connection Lost" // Default lwt message -#define DEFAULT_MQTT_USE_UNITNAME_AS_CLIENTID 0 - -#define DEFAULT_USE_NTP false // (true|false) Use NTP Server -#define DEFAULT_NTP_HOST "" // NTP Server Hostname -#define DEFAULT_TIME_ZONE 0 // Time Offset (in minutes) -#define DEFAULT_USE_DST false // (true|false) Use Daily Time Saving - -#define DEFAULT_LATITUDE 0.0f // Default Latitude -#define DEFAULT_LONGITUDE 0.0f // Default Longitude - -#define DEFAULT_SYSLOG_IP "" // Syslog IP Address -#define DEFAULT_SYSLOG_PORT 0 // Standard syslog port: 514 -#define DEFAULT_SYSLOG_FACILITY 0 // kern -#define DEFAULT_SYSLOG_LEVEL 0 // Syslog Log Level -#define DEFAULT_SERIAL_LOG_LEVEL LOG_LEVEL_INFO // Serial Log Level -#define DEFAULT_WEB_LOG_LEVEL LOG_LEVEL_INFO // Web Log Level -#define DEFAULT_SD_LOG_LEVEL 0 // SD Card Log Level -#define DEFAULT_USE_SD_LOG false // (true|false) Enable Logging to the SD card - -#define DEFAULT_USE_SERIAL true // (true|false) Enable Logging to the Serial Port -#define DEFAULT_SERIAL_BAUD 115200 // Serial Port Baud Rate - -#define DEFAULT_SYNC_UDP_PORT 8266 // Used for ESPEasy p2p. (IANA registered port: 8266) - - -// Factory Reset defaults -#define DEFAULT_FACTORY_RESET_KEEP_UNIT_NAME true -#define DEFAULT_FACTORY_RESET_KEEP_WIFI true -#define DEFAULT_FACTORY_RESET_KEEP_NETWORK true -#define DEFAULT_FACTORY_RESET_KEEP_NTP_DST true -#define DEFAULT_FACTORY_RESET_KEEP_CONSOLE_LOG true - - -#define BUILD_NO_DEBUG - -// Custom built-in url for hosting JavaScript and CSS files. -#define CUSTOM_BUILD_CDN_URL "https://cdn.jsdelivr.net/gh/letscontrolit/ESPEasy@mega/static/" - - - -// Special SSID/key setup only to be used in custom builds. - -// Deployment SSID will be used only when the configured SSIDs are not reachable and/or no credentials are set. -// This to make deployment of large number of nodes easier -#define CUSTOM_DEPLOYMENT_SSID "" // Enter SSID not shown in UI, to be used on custom builds to ease deployment -#define CUSTOM_DEPLOYMENT_KEY "" // Enter key not shown in UI, to be used on custom builds to ease deployment -#define CUSTOM_SUPPORT_SSID "" // Enter SSID not shown in UI, to be used on custom builds to ease support -#define CUSTOM_SUPPORT_KEY "" // Enter key not shown in UI, to be used on custom builds to ease support - - -// Emergency fallback SSID will only be attempted in the first 10 minutes after reboot. -// When found, the unit will connect to it and depending on the built in flag, it will either just connect to it, or clear set credentials. -// Use case: User connects to a public AP which does need to agree on an agreement page for the rules of conduct (e.g. open APs) -// This is seen as a valid connection, so the unit will not reconnect to another node and thus becomes inaccessible. -#define CUSTOM_EMERGENCY_FALLBACK_SSID "" // Enter SSID not shown in UI, to be used to regain access to the node -#define CUSTOM_EMERGENCY_FALLBACK_KEY "" // Enter key not shown in UI, to be used to regain access to the node - -#define CUSTOM_EMERGENCY_FALLBACK_RESET_CREDENTIALS false -#define CUSTOM_EMERGENCY_FALLBACK_START_AP false - -#define CUSTOM_EMERGENCY_FALLBACK_ALLOW_MINUTES_UPTIME 10 - -// Allow for remote provisioning of a node. -// This is only allowed for custom builds. -// To setup the configuration of the provisioning file, one must also define FEATURE_SETTINGS_ARCHIVE -// Default setting is to not allow to configure a node remotely, unless explicitly enabled. -// #define FEATURE_CUSTOM_PROVISIONING 1 - -#define FEATURE_SSDP 1 - -#define FEATURE_EXT_RTC 1 // Support for external RTC clock modules like PCF8563/PCF8523/DS3231/DS1307 - -#define FEATURE_PLUGIN_STATS 1 // Support collecting historic data + computing stats on historic data -#ifdef ESP8266 -// # define PLUGIN_STATS_NR_ELEMENTS 16 -#endif // ifdef ESP8266 -# ifdef ESP32 -// # define PLUGIN_STATS_NR_ELEMENTS 64 -#endif // ifdef ESP32 -#define FEATURE_CHART_JS 1 // Support for drawing charts, like PluginStats historic data - -// Optional alternative CDN links: -// Chart.js: (only used when FEATURE_CHART_JS is enabled) -// #define CDN_URL_CHART_JS "https://cdn.jsdelivr.net/npm/chart.js@4.1.2/dist/chart.umd.min.js" -// JQuery: -// #define CDN_URL_JQUERY "https://code.jquery.com/jquery-3.6.0.min.js" - - -// #define FEATURE_SETTINGS_ARCHIVE 1 -// #define FEATURE_I2CMULTIPLEXER 1 -// #define FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES 1 -// #define PLUGIN_USES_ADAFRUITGFX // Used by Display plugins using Adafruit GFX library -// #define ADAGFX_ARGUMENT_VALIDATION 0 // Disable argument validation in AdafruitGFX_helper -// #define ADAGFX_SUPPORT_7COLOR 0 // Disable the support of 7-color eInk displays by AdafruitGFX_helper -// #define FEATURE_SEND_TO_HTTP 1 // Enable availability of the SendToHTTP command -// #define FEATURE_POST_TO_HTTP 1 // Enable availability of the PostToHTTP command -// #define FEATURE_PUT_TO_HTTP 1 // Enable availability of the PutToHTTP command -// #define FEATURE_I2C_DEVICE_CHECK 0 // Disable the I2C Device check feature -// #define FEATURE_I2C_GET_ADDRESS 0 // Disable fetching the I2C address from I2C plugins. Will be enabled when FEATURE_I2C_DEVICE_CHECK is enabled -// #define FEATURE_RTTTL 1 // Enable rtttl command -// #define FEATURE_ANYRTTTL_LIB 1 // Use AnyRttl library for RTTTL handling -// #define FEATURE_ANYRTTTL_ASYNC 1 // When AnyRttl enabled, use Async (nonblocking) mode instead of the default Blocking mode -// #define FEATURE_RTTTL_EVENTS 1 // Enable RTTTL events for Async use, for blocking it doesn't make sense -// #define FEATURE_STRING_VARIABLES 1 // Enable String variable support (enabled on ESP32, NOT supported on ESP8266 for memory restrictions!) - -#if FEATURE_CUSTOM_PROVISIONING -// For device models, see src/src/DataTypes/DeviceModel.h -// #ifdef ESP32 -// #define DEFAULT_FACTORY_DEFAULT_DEVICE_MODEL 0 // DeviceModel_default -// #endif -// #ifdef ESP8266 -// #define DEFAULT_FACTORY_DEFAULT_DEVICE_MODEL 0 // DeviceModel_default -// #endif -// #define DEFAULT_PROVISIONING_FETCH_RULES1 false -// #define DEFAULT_PROVISIONING_FETCH_RULES2 false -// #define DEFAULT_PROVISIONING_FETCH_RULES3 false -// #define DEFAULT_PROVISIONING_FETCH_RULES4 false -// #define DEFAULT_PROVISIONING_FETCH_NOTIFICATIONS false -// #define DEFAULT_PROVISIONING_FETCH_SECURITY false -// #define DEFAULT_PROVISIONING_FETCH_CONFIG false -// #define DEFAULT_PROVISIONING_FETCH_PROVISIONING false -// #define DEFAULT_PROVISIONING_FETCH_FIRMWARE false -// #define DEFAULT_PROVISIONING_SAVE_URL false -// #define DEFAULT_PROVISIONING_SAVE_CREDENTIALS false -// #define DEFAULT_PROVISIONING_ALLOW_FETCH_COMMAND false -// #define DEFAULT_PROVISIONING_URL "" -// #define DEFAULT_PROVISIONING_USER "" -// #define DEFAULT_PROVISIONING_PASS "" -#endif - - - - -/* - ####################################################################################################### - Defining web interface - ####################################################################################################### - */ - -#define MENU_INDEX_MAIN_VISIBLE true -/* -#define MENU_INDEX_CONFIG_VISIBLE false -#define MENU_INDEX_CONTROLLERS_VISIBLE false -#define MENU_INDEX_HARDWARE_VISIBLE false -#define MENU_INDEX_DEVICES_VISIBLE false -#define MENU_INDEX_RULES_VISIBLE false -#define MENU_INDEX_NOTIFICATIONS_VISIBLE false -#define MENU_INDEX_TOOLS_VISIBLE false -*/ - -#define MAIN_PAGE_SHOW_SYSINFO_BUTTON true -#define MAIN_PAGE_SHOW_WiFi_SETUP_BUTTON true -#define MAIN_PAGE_SHOW_BASIC_INFO_NOT_LOGGED_IN false - -#define MAIN_PAGE_SHOW_NODE_LIST_BUILD true -#define MAIN_PAGE_SHOW_NODE_LIST_TYPE true - -#define SETUP_PAGE_SHOW_CONFIG_BUTTON true - -// #define FEATURE_AUTO_DARK_MODE 0 // 0 = Disable auto-dark mode -// #define FEATURE_EXTENDED_TASK_VALUE_TYPES 0 // 0 = Disable extra task value types like 64 bit ints, double, etc. in Dummy tasks -// #define FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE 0 // 0 = switch to float as floating point type for rules/formula processing. - -//#define WEBPAGE_TEMPLATE_HIDE_HELP_BUTTON - -#define SHOW_SYSINFO_JSON 1 //Enables the sysinfo_json page (by default is enabled when WEBSERVER_NEW_UI is enabled too) - -/* - ####################################################################################################### - CSS / template - ####################################################################################################### - */ -/* -#define WEBPAGE_TEMPLATE_DEFAULT_HEADER "

ESP Easy Mega: {{title}}


" -#define WEBPAGE_TEMPLATE_DEFAULT_FOOTER "" -#define WEBPAGE_TEMPLATE_AP_HEADER "

Welcome to ESP Easy Mega AP

" -#define WEBPAGE_TEMPLATE_HIDE_HELP_BUTTON -*/ -// Embed Custom CSS in Custom.h: -/* -#define WEBSERVER_EMBED_CUSTOM_CSS -static const char DATA_ESPEASY_DEFAULT_MIN_CSS[] PROGMEM = { -... -,0}; -*/ - - -/* - ####################################################################################################### - Special settings (rendering settings incompatible with other builds) - ####################################################################################################### - */ - -// #define FEATURE_NON_STANDARD_24_TASKS 1 - -/* - ####################################################################################################### - Your Own selection of plugins and controllers - ####################################################################################################### - */ - -#define CONTROLLER_SET_NONE -#define NOTIFIER_SET_NONE -#define PLUGIN_SET_NONE - - -/* - ####################################################################################################### - ########### Plugins - ####################################################################################################### - */ - -// #define FEATURE_SERVO 1 // Uncomment and set to 0 to explicitly disable SERVO support - - -// #define USES_P001 // Switch -// #define USES_P002 // ADC -// #define USES_P003 // Pulse -// #define USES_P004 // 1-Wire Temperature (Dallas/Maxim DS18B20) -// #define USES_P005 // DHT11/12/22 SONOFF2301/7021/MS01 -// #define USES_P006 // BMP085/180 -// #define USES_P007 // PCF8591 -// #define USES_P008 // Wiegand (RFID) -// #define USES_P009 // MCP23017 - -// #define USES_P010 // BH1750 -// #define USES_P011 // ProMini Extender -// #define USES_P012 // LCD2004 -// #define USES_P013 // HC-SR04/RCW-0001 -// #define USES_P014 // SI70xx/HTU21D -// #define USES_P015 // TSL2561 -// #define USES_P017 // PN532 -// #define USES_P018 // GP2Y10 -// #define USES_P019 // PCF8574 - -// #define USES_P020 // Ser2Net -// #define USES_P021 // Level Control -// #define USES_P022 // PCA9685 -// #define USES_P023 // OLED SSD1306 -// #define USES_P024 // MLX90614 -// #define USES_P025 // ADS1x15 -// #define USES_P026 // SysInfo -// #define USES_P027 // INA219 -// #define USES_P028 // BMx280 -// #define USES_P029 // Domoticz MQTT Helper - -// #define USES_P031 // SHT1x -// #define USES_P032 // MS5611 (GY-63) -// #define USES_P033 // Dummy Device -// #define USES_P034 // DHT12 -// #define USES_P036 // OLED SSD1306/SH1106 Framed -// #define P036_FEATURE_DISPLAY_PREVIEW 1 // Enable Preview feature, shows on-display content on Devices overview page -// #define P036_FEATURE_ALIGN_PREVIEW 1 // Enable center/right-align feature when preview is enabled (auto-disabled for 1M builds) -// #define P036_ENABLE_TICKER 1 // Enable ticker function -// #define USES_P037 // MQTT Import -// #define P037_MAPPING_SUPPORT 1 // Enable Value mapping support -// #define P037_FILTER_SUPPORT 1 // Enable filtering support -// #define P037_JSON_SUPPORT 1 // Enable Json support -// #define USES_P038 // NeoPixel -// #define P038_FEATURE_NEOPIXELFOR 1 // Enable NeoPixelFor/NeoPixelForHSV commands (default enabled for ESP32) -// #define USES_P039 // Thermocouple - -// #define USES_P040 // RFID - ID12LA/RDM6300 -// #define USES_P041 // NeoPixel (Word Clock) -// #define USES_P042 // NeoPixel (Candle) -// #define USES_P043 // ClkOutput -// #define USES_P044 // P1 Wifi Gateway (Merged with P020, when P044 is enabled, then P020 is also enabled) -// #define USES_P045 // MPU6050 -// #define USES_P046 // Ventus W266 -// #define USES_P047 // Soil moisture sensor -// #define USES_P048 // Motoshield v2 -// #define USES_P049 // MH-Z19 - -// #define USES_P050 // TCS34725 RGB Color Sensor with IR filter and White LED -// #define USES_P051 // AM2320 -// #define USES_P052 // SenseAir -// #define USES_P053 // PMSx003 / PMSx003ST -// #define USES_P054 // DMX512 -// #define USES_P055 // Chiming -// #define USES_P056 // SDS011/018/198 -// #define USES_P057 // HT16K33_LED -// #define USES_P058 // HT16K33_KeyPad -// #define USES_P059 // Rotary Encoder - -// #define USES_P060 // MCP3221 -// #define USES_P061 // PCF8574 / MCP23017 / PCA8575 -// #define USES_P062 // MPR121 -// #define USES_P063 // TTP229 -// #define USES_P064 // APDS9960 Gesture -// #define USES_P065 // DRF0299 -// #define USES_P066 // VEML6040 -// #define USES_P067 // HX711_Load_Cell -// #define USES_P068 // SHT3x -// #define USES_P069 // LM75A - -// #define USES_P070 // NeoPixel_Clock -// #define USES_P071 // Kamstrup401 -// #define USES_P072 // HDC1000/HDC1008/HDC1010/HDC1050/HDC1080 -// #define USES_P073 // 7-segment display -// #define P073_USE_74HC595 1 // Enable 74HC595 displays -// #define P073_USE_74HC595_OVERRIDE 1 // Allow 74HC595 displays feature for ESP8266 builds -// #define USES_P074 // TSL2591 -// #define USES_P075 // Nextion -// #define USES_P076 // HLW8012/BL0937 (Shelly Plug S, Sonoff POW R1, Huafan SS, KMC 70011, Aplic WDP303075, SK03 Outdoor, BlitzWolf SHP, Teckin, Teckin US, Gosund SP1 v23) -// #define USES_P077 // CSE7766 (Sonoff S31, Sonoff POW R2, Sonoff POW R3xx(D), Sonoff Dual R3) -// #define USES_P078 // Eastron SDMxxx Modbus -// #define USES_P079 // Wemos / Lolin Motorshield - -// #define USES_P080 // iButton Sensor DS1990A -// #define USES_P081 // Cron -// #define USES_P082 // GPS -// #define USES_P083 // SGP30 TVOC -// #define USES_P084 // VEML6070 -// #define USES_P085 // AcuDC24x -// #define USES_P086 // Receiving values according Homie convention. Works together with C014 Homie controller -// #define USES_P087 // Serial Proxy -// #define USES_P088 // HeatpumpIR -// #define USES_P089 // Ping - -// #define USES_P090 // CCS811 TVOC -// #define USES_P091 // Serial MCU controlled switch -// #define USES_P092 // DLbus -// #define USES_P093 // Mitsubishi Heat Pump -// #define USES_P094 // CUL Reader -// #define USES_P095 // ILI934x / ILI948x -// #define USES_P096 // eInk -// #define USES_P097 // ESP32 Touch -// #define USES_P098 // PWM Motor -// #define USES_P099 // XPT2046 touchscreen - -// #define USES_P100 // DS2423 counter -// #define USES_P101 // Wake On Lan -// #define USES_P102 // PZEM-004Tv30-Multiple -// #define USES_P103 // Atlas Scientific EZO Sensors (pH, ORP, EZO, DO) -// #define USES_P104 // MAX7219 dot matrix -// #define USES_P105 // AHT10/AHT2x -// #define USES_P106 // BME68x -// #define USES_P107 // SI1145 -// #define USES_P108 // DDS238-x ZN Modbus energy meters -// #define USES_P109 // ThermoOLED - -// #define USES_P110 // VL53L0X Time of Flight sensor -// #define USES_P111 // MFRC522 RFID reader -// #define USES_P112 // AS7265x -// #define USES_P113 // VL53L1X ToF -// #define USES_P114 // VEML6075 -// #define USES_P115 // MAX1704x -// #define USES_P116 // ST77xx -// #define USES_P117 // SCD30 -// #define USES_P118 // Itho -// #define USES_P119 // ITG3205 Gyro - -// #define USES_P120 // ADXL345 I2C Acceleration / Gravity -// #define USES_P121 // HMC5883L -// #define USES_P122 // SHT2x -// #define USES_P123 // I2C Touchscreens -// #define USES_P124 // I2C Multi Relay -// #define USES_P125 // ADXL345 SPI Acceleration / Gravity -// #define USES_P126 // 74HC595 Shift register -// #define USES_P127 // CDM7160 -// #define USES_P128 // NeoPixel (BusFX) -// #define P128_USES_GRB // Default -// #define P128_USES_GRBW // Select 1 option, only first one enabled from this list will be used -// #define P128_USES_RGB -// #define P128_USES_RGBW -// #define P128_USES_BRG -// #define P128_USES_BGR -// #define P128_USES_RBG -// #define P128_ENABLE_FAKETV 1 // Enable(1)/Disable(0) FakeTV effect, disabled by default on ESP8266 (.bin size issue), enabled by default on ESP32 -// #define USES_P129 // 74HC165 Input shiftregisters - -// #define USES_P131 // NeoPixel Matrix -// #define USES_P132 // INA3221 -// #define USES_P133 // LTR390 UV -// #define USES_P134 // A02YYUW -// #define USES_P135 // SCD4x -// #define P135_FEATURE_RESET_COMMANDS 1 // Enable/Disable quite spacious (~950 bytes) 'selftest' and 'factoryreset' subcommands -// #define USES_P137 // AXP192 -// #define USES_P138 // IP5306 -// #define USES_P139 // AXP2101 - -// #define USES_P140 // CardKB -// #define UN_USES_P140 // **DISABLE** I2C CardKB for ESP32 (Enabled by default for ESP32) -// #define USES_P141 // PCD8544 Nokia 5110 LCD -// #define USES_P142 // Position - AS5600 -// #define USES_P143 // I2C Rotary encoders -// #define P143_FEATURE_INCLUDE_M5STACK 0 // Enabled by default, can be turned off here -// #define P143_FEATURE_INCLUDE_DFROBOT 0 // Enabled by default, can be turned off here -// #define P143_FEATURE_COUNTER_COLORMAPPING 0 // Enabled by default, can be turned off here - -// #define USES_P144 // PM1006(K) (Vindriktning) -// #define USES_P145 // MQxxx (MQ135 CO2, MQ3 Alcohol) -// #define USES_P146 // Cache Reader -// #define USES_P147 // SGP4x -// #define P147_FEATURE_GASINDEXALGORITHM 0 // Enabled by default, can be turned off here -// #define USES_P148 // POWR3xxD/THR3xxD - -// #define USES_P150 // TMP117 Temperature -// #define USES_P151 // Honeywell Pressure -// #define USES_P152 // ESP32 DAC -// #define USES_P153 // SHT4x -// #define USES_P154 // BMP3xx I2C - -// #define USES_P159 // Presence - LD2410 Radar detection - -// #define USES_P162 // Output - MCP42xxx Digipot -// #define USES_P163 // Environment - RadSens I2C radiation counter -// #define USES_P164 // Gases - ENS16x TVOC/eCO2 -// #define USES_P165 // Display - NeoPixel (7-segment) -// #define USES_P166 // Output - GP8403 Dual channel DAC (Digital Analog Converter) -// #define USES_P167 // Environment - Sensirion SEN5x / Ikea Vindstyrka -// #define USES_P168 // Light - VEML6030/VEML7700 -// #define USES_P169 // Environment - AS3935 Lightning Detector - -// #define USES_P170 // Input - I2C Liquid level sensor -// #define USES_P172 // BMP3xx SPI. -// #define USES_P173 // Environment - SHTC3 -// #define USES_P175 // Dust - PMSx003i I2C -// #define USES_P176 // Communication - Victron VE.Direct -// #define USES_P178 // LU9685 Servo controller - -/* - ####################################################################################################### - ########### Controllers - ####################################################################################################### - */ - - -// #define USES_C001 // Domoticz HTTP -// #define USES_C002 // Domoticz MQTT -// #define USES_C003 // Nodo telnet -// #define USES_C004 // ThingSpeak -// #define USES_C005 // Home Assistant (openHAB) MQTT -// #define USES_C006 // PiDome MQTT -// #define USES_C007 // Emoncms -// #define USES_C008 // Generic HTTP -// #define USES_C009 // FHEM HTTP -// #define USES_C010 // Generic UDP -// #define USES_C011 // Generic HTTP Advanced -// #define USES_C012 // Blynk HTTP -// #define USES_C013 // ESPEasy P2P network -// #define USES_C014 // homie 3 & 4dev MQTT -// #define USES_C015 // Blynk -// #define USES_C016 // Cache controller -// #define USES_C017 // Zabbix -// #define USES_C018 // TTN/RN2483 - - -/* - ####################################################################################################### - ########### Notifiers - ####################################################################################################### - */ - - -// #define USES_N001 // Email -// #define USES_N002 // Buzzer - - -#endif // ESPEASY_CUSTOM_H +#ifndef ESPEASY_CUSTOM_H +#define ESPEASY_CUSTOM_H + +/* + To modify the stock configuration without changing the EspEasy.ino file : + 1) rename this file to "Custom.h" (It is ignored by Git) + 2) define your own settings below + 3) Build one of the environments with Custom in their name, they will automatically use this file if it exists + */ + + +/* + ####################################################################################################### + Your Own Default Settings + ####################################################################################################### + You can basically ovveride ALL macro defined in ESPEasy.ino. + Don't forget to first #undef each existing #define that you add below. + But since this Custom.h is included before other defines are made, you don't have to undef a lot of defines. + Here are some examples: + */ + +// --- Feature Flagging --------------------------------------------------------- +// Can be set to 1 to enable, 0 to disable, or not set to use the default (usually via define_plugin_sets.h) + +#define FEATURE_RULES_EASY_COLOR_CODE 1 // Use code highlighting, autocompletion and command suggestions in Rules +#define FEATURE_ESPEASY_P2P 1 // (1/0) enables the ESP Easy P2P protocol +#define FEATURE_ARDUINO_OTA 1 // enables the Arduino OTA capabilities +#define FEATURE_THINGSPEAK_EVENT 0 // Generates an event when requesting last value of a field in thingspeak via SendToHTTP(e.g. sendToHTTP,api.thingspeak.com,80,/channels/1667332/fields/5/last) +#define FEATURE_OPENMETEO_EVENT 0 // Generates an event with the response of a open-meteo request (https://open-meteo.com/en/docs) +#define FEATURE_JSON_EVENT 0 // Generates an event with the values of a JSON repsonse of an HTTP call. Keys are stored in json.keys one key per line (e.g.: Body.Data.DAY_ENERGY.Values.1) +// #define FEATURE_SD 1 // Enable SD card support +// #define FEATURE_DOWNLOAD 1 // Enable downloading a file from an url + +#ifdef BUILD_GIT +# undef BUILD_GIT +#endif // ifdef BUILD_GIT + +#define BUILD_GIT "My Build: " __DATE__ " " __TIME__ + + +#define DEFAULT_NAME "MyEspEasyDevice" // Enter your device friendly name +#define UNIT 0 // Unit Number +#define DEFAULT_DELAY 60 // Sleep Delay in seconds + +// --- Wifi AP Mode (when your Wifi Network is not reachable) ---------------------------------------- +#define DEFAULT_AP_IP 192, 168, 4, 1 // Enter IP address (comma separated) for AP (config) mode +#define DEFAULT_AP_SUBNET 255, 255, 255, 0 // Enter IP address (comma separated) for AP (config) mode +#define DEFAULT_AP_KEY "configesp" // Enter network WPA key for AP (config) mode + +// --- Wifi Client Mode ----------------------------------------------------------------------------- +#define DEFAULT_SSID "MyHomeSSID" // Enter your network SSID +#define DEFAULT_KEY "MySuperSecretPassword" // Enter your network WPA key +#define DEFAULT_SSID2 "" // Enter your fallback network SSID +#define DEFAULT_KEY2 "" // Enter your fallback network WPA key +#define DEFAULT_WIFI_INCLUDE_HIDDEN_SSID false // Allow to connect to hidden SSID APs +#define DEFAULT_USE_STATIC_IP false // (true|false) enabled or disabled static IP +#define DEFAULT_IP "192.168.0.50" // Enter your IP address +#define DEFAULT_DNS "192.168.0.1" // Enter your DNS +#define DEFAULT_GW "192.168.0.1" // Enter your Gateway +#define DEFAULT_SUBNET "255.255.255.0" // Enter your Subnet +#define DEFAULT_IPRANGE_LOW "0.0.0.0" // Allowed IP range to access webserver +#define DEFAULT_IPRANGE_HIGH "255.255.255.255" // Allowed IP range to access webserver +#define DEFAULT_IP_BLOCK_LEVEL 1 // 0: ALL_ALLOWED 1: LOCAL_SUBNET_ALLOWED 2: +// ONLY_IP_RANGE_ALLOWED +#define DEFAULT_ADMIN_USERNAME "admin" +#define DEFAULT_ADMIN_PASS "" + +#define DEFAULT_WIFI_CONNECTION_TIMEOUT 10000 // minimum timeout in ms for WiFi to be connected. +#define DEFAULT_WIFI_FORCE_BG_MODE false // when set, only allow to connect in 802.11B or G mode (not N) +#define DEFAULT_WIFI_RESTART_WIFI_CONN_LOST false // Perform wifi off and on when connection was lost. +#define DEFAULT_ECO_MODE false // When set, make idle calls between executing tasks. +#define DEFAULT_WIFI_NONE_SLEEP false // When set, the wifi will be set to no longer sleep (more power +// used and need reboot to reset mode) +#define DEFAULT_GRATUITOUS_ARP false // When set, the node will send periodical gratuitous ARP + // packets to announce itself. +#define DEFAULT_TOLERANT_LAST_ARG_PARSE false // When set, the last argument of some commands will be parsed to the end of the line + // See: https://github.com/letscontrolit/ESPEasy/issues/2724 +#define DEFAULT_SEND_TO_HTTP_ACK false // Wait for ack with SendToHttp command. + +#define DEFAULT_AP_DONT_FORCE_SETUP false // Allow optional usage of Sensor without WIFI avaiable // When set you can use the Sensor in AP-Mode without beeing forced to /setup +#define DEFAULT_DONT_ALLOW_START_AP false // Usually the AP will be started when no WiFi is defined, or the defined one cannot be found. This flag may prevent it. + +// --- Default Controller ------------------------------------------------------------------------------ +#define DEFAULT_CONTROLLER false // true or false enabled or disabled, set 1st controller + // defaults +#define DEFAULT_CONTROLLER_ENABLED true // Enable default controller by default +#define DEFAULT_CONTROLLER_USER "" // Default controller user +#define DEFAULT_CONTROLLER_PASS "" // Default controller Password + +// using a default template, you also need to set a DEFAULT PROTOCOL to a suitable MQTT protocol ! +#define DEFAULT_PUB "sensors/espeasy/%sysname%/%tskname%/%valname%" // Enter your pub +#define DEFAULT_SUB "sensors/espeasy/%sysname%/#" // Enter your sub +#define DEFAULT_SERVER "192.168.0.8" // Enter your Server IP address +#define DEFAULT_SERVER_HOST "" // Server hostname +#define DEFAULT_SERVER_USEDNS false // true: Use hostname. false: use IP +#define DEFAULT_USE_EXTD_CONTROLLER_CREDENTIALS false // true: Allow longer user credentials for controllers + +#define DEFAULT_PORT 8080 // Enter your Server port value +#define DEFAULT_CONTROLLER_TIMEOUT 100 // Default timeout in msec +#define DEFAULT_CONTROLLER_FALLBACK_MESH false // Let the (MQTT) controller use the mesh as a fallback route when there's no network connection + +#define DEFAULT_PROTOCOL 0 // Protocol used for controller communications + // 0 = Stand-alone (no controller set) + // 1 = Domoticz HTTP + // 2 = Domoticz MQTT + // 3 = Nodo Telnet + // 4 = ThingSpeak + // 5 = Home Assistant (openHAB) MQTT + // 6 = PiDome MQTT + // 7 = EmonCMS + // 8 = Generic HTTP + // 9 = FHEM HTTP + +#ifdef ESP8266 +#define DEFAULT_PIN_I2C_SDA 4 +#endif +#ifdef ESP32 +#define DEFAULT_PIN_I2C_SDA -1 // Undefined +#endif +#ifdef ESP8266 +#define DEFAULT_PIN_I2C_SCL 5 +#endif +#ifdef ESP32 +#define DEFAULT_PIN_I2C_SCL -1 // Undefined +#endif +#define DEFAULT_PIN_I2C2_SDA -1 // Undefined +#define DEFAULT_PIN_I2C3_SDA -1 // Undefined +#define DEFAULT_PIN_I2C2_SCL -1 // Undefined +#define DEFAULT_PIN_I2C3_SCL -1 // Undefined +#define DEFAULT_I2C_CLOCK_SPEED 400000 // Use 100 kHz if working with old I2C chips +#define FEATURE_I2C_DEVICE_SCAN 1 + +#define DEFAULT_SPI 0 //0=disabled 1=enabled and for ESP32 there is option 2 =HSPI + +#define DEFAULT_PIN_STATUS_LED (-1) +#define DEFAULT_PIN_STATUS_LED_INVERSED true + +#define DEFAULT_PIN_RESET_BUTTON (-1) + + +#define DEFAULT_USE_RULES false // (true|false) Enable Rules? +#define DEFAULT_RULES_OLDENGINE true + +#define DEFAULT_MQTT_RETAIN false // (true|false) Retain MQTT messages? +#define DEFAULT_CONTROLLER_DELETE_OLDEST false // (true|false) to delete oldest message when queue is full +#define DEFAULT_CONTROLLER_MUST_CHECK_REPLY false // (true|false) Check Acknowledgment +#define DEFAULT_MQTT_DELAY 100 // Time in milliseconds to retain MQTT messages +#define DEFAULT_MQTT_LWT_TOPIC "" // Default lwt topic +#define DEFAULT_MQTT_LWT_CONNECT_MESSAGE "Connected" // Default lwt message +#define DEFAULT_MQTT_LWT_DISCONNECT_MESSAGE "Connection Lost" // Default lwt message +#define DEFAULT_MQTT_USE_UNITNAME_AS_CLIENTID 0 + +#define DEFAULT_USE_NTP false // (true|false) Use NTP Server +#define DEFAULT_NTP_HOST "" // NTP Server Hostname +#define DEFAULT_TIME_ZONE 0 // Time Offset (in minutes) +#define DEFAULT_USE_DST false // (true|false) Use Daily Time Saving + +#define DEFAULT_LATITUDE 0.0f // Default Latitude +#define DEFAULT_LONGITUDE 0.0f // Default Longitude + +#define DEFAULT_SYSLOG_IP "" // Syslog IP Address +#define DEFAULT_SYSLOG_PORT 0 // Standard syslog port: 514 +#define DEFAULT_SYSLOG_FACILITY 0 // kern +#define DEFAULT_SYSLOG_LEVEL 0 // Syslog Log Level +#define DEFAULT_SERIAL_LOG_LEVEL LOG_LEVEL_INFO // Serial Log Level +#define DEFAULT_WEB_LOG_LEVEL LOG_LEVEL_INFO // Web Log Level +#define DEFAULT_SD_LOG_LEVEL 0 // SD Card Log Level +#define DEFAULT_USE_SD_LOG false // (true|false) Enable Logging to the SD card + +#define DEFAULT_USE_SERIAL true // (true|false) Enable Logging to the Serial Port +#define DEFAULT_SERIAL_BAUD 115200 // Serial Port Baud Rate + +#define DEFAULT_SYNC_UDP_PORT 8266 // Used for ESPEasy p2p. (IANA registered port: 8266) + + +// Factory Reset defaults +#define DEFAULT_FACTORY_RESET_KEEP_UNIT_NAME true +#define DEFAULT_FACTORY_RESET_KEEP_WIFI true +#define DEFAULT_FACTORY_RESET_KEEP_NETWORK true +#define DEFAULT_FACTORY_RESET_KEEP_NTP_DST true +#define DEFAULT_FACTORY_RESET_KEEP_CONSOLE_LOG true + + +#define BUILD_NO_DEBUG + +// Custom built-in url for hosting JavaScript and CSS files. +#define CUSTOM_BUILD_CDN_URL "https://cdn.jsdelivr.net/gh/letscontrolit/ESPEasy@mega/static/" + + + +// Special SSID/key setup only to be used in custom builds. + +// Deployment SSID will be used only when the configured SSIDs are not reachable and/or no credentials are set. +// This to make deployment of large number of nodes easier +#define CUSTOM_DEPLOYMENT_SSID "" // Enter SSID not shown in UI, to be used on custom builds to ease deployment +#define CUSTOM_DEPLOYMENT_KEY "" // Enter key not shown in UI, to be used on custom builds to ease deployment +#define CUSTOM_SUPPORT_SSID "" // Enter SSID not shown in UI, to be used on custom builds to ease support +#define CUSTOM_SUPPORT_KEY "" // Enter key not shown in UI, to be used on custom builds to ease support + + +// Emergency fallback SSID will only be attempted in the first 10 minutes after reboot. +// When found, the unit will connect to it and depending on the built in flag, it will either just connect to it, or clear set credentials. +// Use case: User connects to a public AP which does need to agree on an agreement page for the rules of conduct (e.g. open APs) +// This is seen as a valid connection, so the unit will not reconnect to another node and thus becomes inaccessible. +#define CUSTOM_EMERGENCY_FALLBACK_SSID "" // Enter SSID not shown in UI, to be used to regain access to the node +#define CUSTOM_EMERGENCY_FALLBACK_KEY "" // Enter key not shown in UI, to be used to regain access to the node + +#define CUSTOM_EMERGENCY_FALLBACK_RESET_CREDENTIALS false +#define CUSTOM_EMERGENCY_FALLBACK_START_AP false + +#define CUSTOM_EMERGENCY_FALLBACK_ALLOW_MINUTES_UPTIME 10 + +// Allow for remote provisioning of a node. +// This is only allowed for custom builds. +// To setup the configuration of the provisioning file, one must also define FEATURE_SETTINGS_ARCHIVE +// Default setting is to not allow to configure a node remotely, unless explicitly enabled. +// #define FEATURE_CUSTOM_PROVISIONING 1 + +#define FEATURE_SSDP 1 + +#define FEATURE_EXT_RTC 1 // Support for external RTC clock modules like PCF8563/PCF8523/DS3231/DS1307 + +#define FEATURE_PLUGIN_STATS 1 // Support collecting historic data + computing stats on historic data +#ifdef ESP8266 +// # define PLUGIN_STATS_NR_ELEMENTS 16 +#endif // ifdef ESP8266 +# ifdef ESP32 +// # define PLUGIN_STATS_NR_ELEMENTS 64 +#endif // ifdef ESP32 +#define FEATURE_CHART_JS 1 // Support for drawing charts, like PluginStats historic data + +// Optional alternative CDN links: +// Chart.js: (only used when FEATURE_CHART_JS is enabled) +// #define CDN_URL_CHART_JS "https://cdn.jsdelivr.net/npm/chart.js@4.1.2/dist/chart.umd.min.js" +// JQuery: +// #define CDN_URL_JQUERY "https://code.jquery.com/jquery-3.6.0.min.js" + + +// #define FEATURE_SETTINGS_ARCHIVE 1 +// #define FEATURE_I2CMULTIPLEXER 1 +// #define FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES 1 +// #define PLUGIN_USES_ADAFRUITGFX // Used by Display plugins using Adafruit GFX library +// #define ADAGFX_ARGUMENT_VALIDATION 0 // Disable argument validation in AdafruitGFX_helper +// #define ADAGFX_SUPPORT_7COLOR 0 // Disable the support of 7-color eInk displays by AdafruitGFX_helper +// #define FEATURE_SEND_TO_HTTP 1 // Enable availability of the SendToHTTP command +// #define FEATURE_POST_TO_HTTP 1 // Enable availability of the PostToHTTP command +// #define FEATURE_PUT_TO_HTTP 1 // Enable availability of the PutToHTTP command +// #define FEATURE_I2C_DEVICE_CHECK 0 // Disable the I2C Device check feature +// #define FEATURE_I2C_GET_ADDRESS 0 // Disable fetching the I2C address from I2C plugins. Will be enabled when FEATURE_I2C_DEVICE_CHECK is enabled +// #define FEATURE_RTTTL 1 // Enable rtttl command +// #define FEATURE_ANYRTTTL_LIB 1 // Use AnyRttl library for RTTTL handling +// #define FEATURE_ANYRTTTL_ASYNC 1 // When AnyRttl enabled, use Async (nonblocking) mode instead of the default Blocking mode +// #define FEATURE_RTTTL_EVENTS 1 // Enable RTTTL events for Async use, for blocking it doesn't make sense +// #define FEATURE_STRING_VARIABLES 1 // Enable String variable support (enabled on ESP32, NOT supported on ESP8266 for memory restrictions!) + +#if FEATURE_CUSTOM_PROVISIONING +// For device models, see src/src/DataTypes/DeviceModel.h +// #ifdef ESP32 +// #define DEFAULT_FACTORY_DEFAULT_DEVICE_MODEL 0 // DeviceModel_default +// #endif +// #ifdef ESP8266 +// #define DEFAULT_FACTORY_DEFAULT_DEVICE_MODEL 0 // DeviceModel_default +// #endif +// #define DEFAULT_PROVISIONING_FETCH_RULES1 false +// #define DEFAULT_PROVISIONING_FETCH_RULES2 false +// #define DEFAULT_PROVISIONING_FETCH_RULES3 false +// #define DEFAULT_PROVISIONING_FETCH_RULES4 false +// #define DEFAULT_PROVISIONING_FETCH_NOTIFICATIONS false +// #define DEFAULT_PROVISIONING_FETCH_SECURITY false +// #define DEFAULT_PROVISIONING_FETCH_CONFIG false +// #define DEFAULT_PROVISIONING_FETCH_PROVISIONING false +// #define DEFAULT_PROVISIONING_FETCH_FIRMWARE false +// #define DEFAULT_PROVISIONING_SAVE_URL false +// #define DEFAULT_PROVISIONING_SAVE_CREDENTIALS false +// #define DEFAULT_PROVISIONING_ALLOW_FETCH_COMMAND false +// #define DEFAULT_PROVISIONING_URL "" +// #define DEFAULT_PROVISIONING_USER "" +// #define DEFAULT_PROVISIONING_PASS "" +#endif + + + + +/* + ####################################################################################################### + Defining web interface + ####################################################################################################### + */ + +#define MENU_INDEX_MAIN_VISIBLE true +/* +#define MENU_INDEX_CONFIG_VISIBLE false +#define MENU_INDEX_CONTROLLERS_VISIBLE false +#define MENU_INDEX_HARDWARE_VISIBLE false +#define MENU_INDEX_DEVICES_VISIBLE false +#define MENU_INDEX_RULES_VISIBLE false +#define MENU_INDEX_NOTIFICATIONS_VISIBLE false +#define MENU_INDEX_TOOLS_VISIBLE false +*/ + +#define MAIN_PAGE_SHOW_SYSINFO_BUTTON true +#define MAIN_PAGE_SHOW_WiFi_SETUP_BUTTON true +#define MAIN_PAGE_SHOW_BASIC_INFO_NOT_LOGGED_IN false + +#define MAIN_PAGE_SHOW_NODE_LIST_BUILD true +#define MAIN_PAGE_SHOW_NODE_LIST_TYPE true + +#define SETUP_PAGE_SHOW_CONFIG_BUTTON true + +// #define FEATURE_AUTO_DARK_MODE 0 // 0 = Disable auto-dark mode +// #define FEATURE_EXTENDED_TASK_VALUE_TYPES 0 // 0 = Disable extra task value types like 64 bit ints, double, etc. in Dummy tasks +// #define FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE 0 // 0 = switch to float as floating point type for rules/formula processing. + +//#define WEBPAGE_TEMPLATE_HIDE_HELP_BUTTON + +#define SHOW_SYSINFO_JSON 1 //Enables the sysinfo_json page (by default is enabled when WEBSERVER_NEW_UI is enabled too) + +/* + ####################################################################################################### + CSS / template + ####################################################################################################### + */ +/* +#define WEBPAGE_TEMPLATE_DEFAULT_HEADER "

ESP Easy Mega: {{title}}


" +#define WEBPAGE_TEMPLATE_DEFAULT_FOOTER "" +#define WEBPAGE_TEMPLATE_AP_HEADER "

Welcome to ESP Easy Mega AP

" +#define WEBPAGE_TEMPLATE_HIDE_HELP_BUTTON +*/ +// Embed Custom CSS in Custom.h: +/* +#define WEBSERVER_EMBED_CUSTOM_CSS +static const char DATA_ESPEASY_DEFAULT_MIN_CSS[] PROGMEM = { +... +,0}; +*/ + + +/* + ####################################################################################################### + Special settings (rendering settings incompatible with other builds) + ####################################################################################################### + */ + +// #define FEATURE_NON_STANDARD_24_TASKS 1 + +/* + ####################################################################################################### + Your Own selection of plugins and controllers + ####################################################################################################### + */ + +#define CONTROLLER_SET_NONE +#define NOTIFIER_SET_NONE +#define PLUGIN_SET_NONE + + +/* + ####################################################################################################### + ########### Plugins + ####################################################################################################### + */ + +// #define FEATURE_SERVO 1 // Uncomment and set to 0 to explicitly disable SERVO support + + +// #define USES_P001 // Switch +// #define USES_P002 // ADC +// #define USES_P003 // Pulse +// #define USES_P004 // 1-Wire Temperature (Dallas/Maxim DS18B20) +// #define USES_P005 // DHT11/12/22 SONOFF2301/7021/MS01 +// #define USES_P006 // BMP085/180 +// #define USES_P007 // PCF8591 +// #define USES_P008 // Wiegand (RFID) +// #define USES_P009 // MCP23017 + +// #define USES_P010 // BH1750 +// #define USES_P011 // ProMini Extender +// #define USES_P012 // LCD2004 +// #define USES_P013 // HC-SR04/RCW-0001 +// #define USES_P014 // SI70xx/HTU21D +// #define USES_P015 // TSL2561 +// #define USES_P017 // PN532 +// #define USES_P018 // GP2Y10 +// #define USES_P019 // PCF8574 + +// #define USES_P020 // Ser2Net +// #define USES_P021 // Level Control +// #define USES_P022 // PCA9685 +// #define USES_P023 // OLED SSD1306 +// #define USES_P024 // MLX90614 +// #define USES_P025 // ADS1x15 +// #define USES_P026 // SysInfo +// #define USES_P027 // INA219 +// #define USES_P028 // BMx280 +// #define USES_P029 // Domoticz MQTT Helper + +// #define USES_P031 // SHT1x +// #define USES_P032 // MS5611 (GY-63) +// #define USES_P033 // Dummy Device +// #define USES_P034 // DHT12 +// #define USES_P036 // OLED SSD1306/SH1106 Framed +// #define P036_FEATURE_DISPLAY_PREVIEW 1 // Enable Preview feature, shows on-display content on Devices overview page +// #define P036_FEATURE_ALIGN_PREVIEW 1 // Enable center/right-align feature when preview is enabled (auto-disabled for 1M builds) +// #define P036_ENABLE_TICKER 1 // Enable ticker function +// #define USES_P037 // MQTT Import +// #define P037_MAPPING_SUPPORT 1 // Enable Value mapping support +// #define P037_FILTER_SUPPORT 1 // Enable filtering support +// #define P037_JSON_SUPPORT 1 // Enable Json support +// #define USES_P038 // NeoPixel +// #define P038_FEATURE_NEOPIXELFOR 1 // Enable NeoPixelFor/NeoPixelForHSV commands (default enabled for ESP32) +// #define USES_P039 // Thermocouple + +// #define USES_P040 // RFID - ID12LA/RDM6300 +// #define USES_P041 // NeoPixel (Word Clock) +// #define USES_P042 // NeoPixel (Candle) +// #define USES_P043 // ClkOutput +// #define USES_P044 // P1 Wifi Gateway (Merged with P020, when P044 is enabled, then P020 is also enabled) +// #define USES_P045 // MPU6050 +// #define USES_P046 // Ventus W266 +// #define USES_P047 // Soil moisture sensor +// #define USES_P048 // Motoshield v2 +// #define USES_P049 // MH-Z19 + +// #define USES_P050 // TCS34725 RGB Color Sensor with IR filter and White LED +// #define USES_P051 // AM2320 +// #define USES_P052 // SenseAir +// #define USES_P053 // PMSx003 / PMSx003ST +// #define USES_P054 // DMX512 +// #define USES_P055 // Chiming +// #define USES_P056 // SDS011/018/198 +// #define USES_P057 // HT16K33_LED +// #define USES_P058 // HT16K33_KeyPad +// #define USES_P059 // Rotary Encoder + +// #define USES_P060 // MCP3221 +// #define USES_P061 // PCF8574 / MCP23017 / PCA8575 +// #define USES_P062 // MPR121 +// #define USES_P063 // TTP229 +// #define USES_P064 // APDS9960 Gesture +// #define USES_P065 // DRF0299 +// #define USES_P066 // VEML6040 +// #define USES_P067 // HX711_Load_Cell +// #define USES_P068 // SHT3x +// #define USES_P069 // LM75A + +// #define USES_P070 // NeoPixel_Clock +// #define USES_P071 // Kamstrup401 +// #define USES_P072 // HDC1000/HDC1008/HDC1010/HDC1050/HDC1080 +// #define USES_P073 // 7-segment display +// #define P073_USE_74HC595 1 // Enable 74HC595 displays +// #define P073_USE_74HC595_OVERRIDE 1 // Allow 74HC595 displays feature for ESP8266 builds +// #define USES_P074 // TSL2591 +// #define USES_P075 // Nextion +// #define USES_P076 // HLW8012/BL0937 (Shelly Plug S, Sonoff POW R1, Huafan SS, KMC 70011, Aplic WDP303075, SK03 Outdoor, BlitzWolf SHP, Teckin, Teckin US, Gosund SP1 v23) +// #define USES_P077 // CSE7766 (Sonoff S31, Sonoff POW R2, Sonoff POW R3xx(D), Sonoff Dual R3) +// #define USES_P078 // Eastron SDMxxx Modbus +// #define USES_P079 // Wemos / Lolin Motorshield + +// #define USES_P080 // iButton Sensor DS1990A +// #define USES_P081 // Cron +// #define USES_P082 // GPS +// #define USES_P083 // SGP30 TVOC +// #define USES_P084 // VEML6070 +// #define USES_P085 // AcuDC24x +// #define USES_P086 // Receiving values according Homie convention. Works together with C014 Homie controller +// #define USES_P087 // Serial Proxy +// #define USES_P088 // HeatpumpIR +// #define USES_P089 // Ping + +// #define USES_P090 // CCS811 TVOC +// #define USES_P091 // Serial MCU controlled switch +// #define USES_P092 // DLbus +// #define USES_P093 // Mitsubishi Heat Pump +// #define USES_P094 // CUL Reader +// #define USES_P095 // ILI934x / ILI948x +// #define USES_P096 // eInk +// #define USES_P097 // ESP32 Touch +// #define USES_P098 // PWM Motor +// #define USES_P099 // XPT2046 touchscreen + +// #define USES_P100 // DS2423 counter +// #define USES_P101 // Wake On Lan +// #define USES_P102 // PZEM-004Tv30-Multiple +// #define USES_P103 // Atlas Scientific EZO Sensors (pH, ORP, EZO, DO) +// #define USES_P104 // MAX7219 dot matrix +// #define USES_P105 // AHT10/AHT2x +// #define USES_P106 // BME68x +// #define USES_P107 // SI1145 +// #define USES_P108 // DDS238-x ZN Modbus energy meters +// #define USES_P109 // ThermoOLED + +// #define USES_P110 // VL53L0X Time of Flight sensor +// #define USES_P111 // MFRC522 RFID reader +// #define USES_P112 // AS7265x +// #define USES_P113 // VL53L1X ToF +// #define USES_P114 // VEML6075 +// #define USES_P115 // MAX1704x +// #define USES_P116 // ST77xx +// #define USES_P117 // SCD30 +// #define USES_P118 // Itho +// #define USES_P119 // ITG3205 Gyro + +// #define USES_P120 // ADXL345 I2C Acceleration / Gravity +// #define USES_P121 // HMC5883L +// #define USES_P122 // SHT2x +// #define USES_P123 // I2C Touchscreens +// #define USES_P124 // I2C Multi Relay +// #define USES_P125 // ADXL345 SPI Acceleration / Gravity +// #define USES_P126 // 74HC595 Shift register +// #define USES_P127 // CDM7160 +// #define USES_P128 // NeoPixel (BusFX) +// #define P128_USES_GRB // Default +// #define P128_USES_GRBW // Select 1 option, only first one enabled from this list will be used +// #define P128_USES_RGB +// #define P128_USES_RGBW +// #define P128_USES_BRG +// #define P128_USES_BGR +// #define P128_USES_RBG +// #define P128_ENABLE_FAKETV 1 // Enable(1)/Disable(0) FakeTV effect, disabled by default on ESP8266 (.bin size issue), enabled by default on ESP32 +// #define USES_P129 // 74HC165 Input shiftregisters + +// #define USES_P131 // NeoPixel Matrix +// #define USES_P132 // INA3221 +// #define USES_P133 // LTR390 UV +// #define USES_P134 // A02YYUW +// #define USES_P135 // SCD4x +// #define P135_FEATURE_RESET_COMMANDS 1 // Enable/Disable quite spacious (~950 bytes) 'selftest' and 'factoryreset' subcommands +// #define USES_P137 // AXP192 +// #define USES_P138 // IP5306 +// #define USES_P139 // AXP2101 + +// #define USES_P140 // CardKB +// #define UN_USES_P140 // **DISABLE** I2C CardKB for ESP32 (Enabled by default for ESP32) +// #define USES_P141 // PCD8544 Nokia 5110 LCD +// #define USES_P142 // Position - AS5600 +// #define USES_P143 // I2C Rotary encoders +// #define P143_FEATURE_INCLUDE_M5STACK 0 // Enabled by default, can be turned off here +// #define P143_FEATURE_INCLUDE_DFROBOT 0 // Enabled by default, can be turned off here +// #define P143_FEATURE_COUNTER_COLORMAPPING 0 // Enabled by default, can be turned off here + +// #define USES_P144 // PM1006(K) (Vindriktning) +// #define USES_P145 // MQxxx (MQ135 CO2, MQ3 Alcohol) +// #define USES_P146 // Cache Reader +// #define USES_P147 // SGP4x +// #define P147_FEATURE_GASINDEXALGORITHM 0 // Enabled by default, can be turned off here +// #define USES_P148 // POWR3xxD/THR3xxD + +// #define USES_P150 // TMP117 Temperature +// #define USES_P151 // Honeywell Pressure +// #define USES_P152 // ESP32 DAC +// #define USES_P153 // SHT4x +// #define USES_P154 // BMP3xx I2C + +// #define USES_P159 // Presence - LD2410 Radar detection + +// #define USES_P162 // Output - MCP42xxx Digipot +// #define USES_P163 // Environment - RadSens I2C radiation counter +// #define USES_P164 // Gases - ENS16x TVOC/eCO2 +// #define USES_P165 // Display - NeoPixel (7-segment) +// #define USES_P166 // Output - GP8403 Dual channel DAC (Digital Analog Converter) +// #define USES_P167 // Environment - Sensirion SEN5x / Ikea Vindstyrka +// #define USES_P168 // Light - VEML6030/VEML7700 +// #define USES_P169 // Environment - AS3935 Lightning Detector + +// #define USES_P170 // Input - I2C Liquid level sensor +// #define USES_P172 // BMP3xx SPI. +// #define USES_P173 // Environment - SHTC3 +// #define USES_P175 // Dust - PMSx003i I2C +// #define USES_P176 // Communication - Victron VE.Direct +// #define USES_P178 // LU9685 Servo controller + +/* + ####################################################################################################### + ########### Controllers + ####################################################################################################### + */ + + +// #define USES_C001 // Domoticz HTTP +// #define USES_C002 // Domoticz MQTT +// #define USES_C003 // Nodo telnet +// #define USES_C004 // ThingSpeak +// #define USES_C005 // Home Assistant (openHAB) MQTT +// #define USES_C006 // PiDome MQTT +// #define USES_C007 // Emoncms +// #define USES_C008 // Generic HTTP +// #define USES_C009 // FHEM HTTP +// #define USES_C010 // Generic UDP +// #define USES_C011 // Generic HTTP Advanced +// #define USES_C012 // Blynk HTTP +// #define USES_C013 // ESPEasy P2P network +// #define USES_C014 // homie 3 & 4dev MQTT +// #define USES_C015 // Blynk +// #define USES_C016 // Cache controller +// #define USES_C017 // Zabbix +// #define USES_C018 // TTN/RN2483 + + +/* + ####################################################################################################### + ########### Notifiers + ####################################################################################################### + */ + + +// #define USES_N001 // Email +// #define USES_N002 // Buzzer + + +#endif // ESPEASY_CUSTOM_H diff --git a/src/ESPEasy.ino b/src/ESPEasy.ino index d0fb777615..ee9d83ba2c 100644 --- a/src/ESPEasy.ino +++ b/src/ESPEasy.ino @@ -1,151 +1,150 @@ - -#ifdef CONTINUOUS_INTEGRATION -# pragma GCC diagnostic error "-Wall" -#else // ifdef CONTINUOUS_INTEGRATION -# pragma GCC diagnostic warning "-Wall" -#endif // ifdef CONTINUOUS_INTEGRATION - -// Include this as first, to make sure all defines are active during the entire compile. -// See: https://www.letscontrolit.com/forum/viewtopic.php?f=4&t=7980 -// If Custom.h build from Arduino IDE is needed, uncomment #define USE_CUSTOM_H in ESPEasy_common.h -#include "ESPEasy_common.h" - -#ifdef USE_CUSTOM_H - -// make the compiler show a warning to confirm that this file is inlcuded - # warning "**** Using Settings from Custom.h File ***" -#endif // ifdef USE_CUSTOM_H - - -// Needed due to preprocessor issues. -#ifdef PLUGIN_SET_GENERIC_ESP32 - # ifndef ESP32 - # define ESP32 - # endif // ifndef ESP32 -#endif // ifdef PLUGIN_SET_GENERIC_ESP32 - - -/****************************************************************************************************************************\ - * Arduino project "ESP Easy" © Copyright www.letscontrolit.com - * - * 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 received a copy of the GNU General Public License along with this program in file 'License.txt'. - * - * IDE download : https://www.arduino.cc/en/Main/Software - * ESP8266 Package : https://github.com/esp8266/Arduino - * - * Source Code : https://github.com/ESP8266nu/ESPEasy - * Support : http://www.letscontrolit.com - * Discussion : http://www.letscontrolit.com/forum/ - * - * Additional information about licensing can be found at : http://www.gnu.org/licenses - \*************************************************************************************************************************/ - -// This file incorporates work covered by the following copyright and permission notice: - -/****************************************************************************************************************************\ - * Arduino project "Nodo" © Copyright 2010..2015 Paul Tonkes - * - * 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 received a copy of the GNU General Public License along with this program in file 'License.txt'. - * - * Voor toelichting op de licentievoorwaarden zie : http://www.gnu.org/licenses - * Uitgebreide documentatie is te vinden op : http://www.nodo-domotica.nl - * Compiler voor deze programmacode te downloaden op : http://arduino.cc - \*************************************************************************************************************************/ - -// Simple Arduino sketch for ESP module, supporting: -// ================================================================================= -// Simple switch inputs and direct GPIO output control to drive relays, mosfets, etc -// Analog input (ESP-7/12 only) -// Pulse counters -// Dallas OneWire DS18b20 temperature sensors -// DHT11/22/12 humidity sensors -// BMP085 I2C Barometric Pressure sensor -// PCF8591 4 port Analog to Digital converter (I2C) -// RFID Wiegand-26 reader -// MCP23017 I2C IO Expanders -// BH1750 I2C Luminosity sensor -// Arduino Pro Mini with IO extender sketch, connected through I2C -// LCD I2C display 4x20 chars -// HC-SR04 Ultrasonic distance sensor -// SI7021 I2C temperature/humidity sensors -// TSL2561 I2C Luminosity sensor -// TSOP4838 IR receiver -// PN532 RFID reader -// Sharp GP2Y10 dust sensor -// PCF8574 I2C IO Expanders -// PCA9685 I2C 16 channel PWM driver -// OLED I2C display with SSD1306 driver -// MLX90614 I2C IR temperature sensor -// ADS1115 I2C ADC -// INA219 I2C voltage/current sensor -// BME280 I2C temp/hum/baro sensor -// MSP5611 I2C temp/baro sensor -// BMP280 I2C Barometric Pressure sensor -// SHT1X temperature/humidity sensors -// Ser2Net server -// DL-Bus (Technische Alternative) - -// Define globals before plugin sets to allow a personal override of the selected plugins -#include "ESPEasy-Globals.h" - -// Must be included after all the defines, since it is using TASKS_MAX -#include "_Plugin_Helper.h" - -// Plugin helper needs the defined controller sets, thus include after 'define_plugin_sets.h' -#include "src/Helpers/_CPlugin_Helper.h" - - -#include "src/ESPEasyCore/ESPEasy_setup.h" -#include "src/ESPEasyCore/ESPEasy_loop.h" - - -#ifdef PHASE_LOCKED_WAVEFORM -# include -#endif // ifdef PHASE_LOCKED_WAVEFORM - -#if FEATURE_ADC_VCC -ADC_MODE(ADC_VCC); -#endif // if FEATURE_ADC_VCC - - - -#ifdef CORE_POST_2_5_0 - -/*********************************************************************************************\ -* Pre-init -\*********************************************************************************************/ -void preinit(); -void preinit() { - system_phy_set_powerup_option(3); - // Global WiFi constructors are not called yet - // (global class instances like WiFi, Serial... are not yet initialized).. - // No global object methods or C++ exceptions can be called in here! - // The below is a static class method, which is similar to a function, so it's ok. - #ifndef CORE_POST_3_0_0 - //ESP8266WiFiClass::preinitWiFiOff(); - #endif - - // Prevent RF calibration on power up. - // TD-er: disabled on 2021-06-07 as it may cause several issues with some boards. - // It cannot be made a setting as we can't read anything of our own settings. - //system_phy_set_powerup_option(RF_NO_CAL); -} - -#endif // ifdef CORE_POST_2_5_0 - - -void setup() { - ESPEasy_setup(); -} - -void loop() { - ESPEasy_loop(); -} + +#ifdef CONTINUOUS_INTEGRATION +# pragma GCC diagnostic error "-Wall" +#else // ifdef CONTINUOUS_INTEGRATION +# pragma GCC diagnostic warning "-Wall" +#endif // ifdef CONTINUOUS_INTEGRATION + +// Include this as first, to make sure all defines are active during the entire compile. +// See: https://www.letscontrolit.com/forum/viewtopic.php?f=4&t=7980 +// If Custom.h build from Arduino IDE is needed, uncomment #define USE_CUSTOM_H in ESPEasy_common.h +#include "ESPEasy_common.h" + +#ifdef USE_CUSTOM_H + +// make the compiler show a warning to confirm that this file is inlcuded + # warning "**** Using Settings from Custom.h File ***" +#endif // ifdef USE_CUSTOM_H + + +// Needed due to preprocessor issues. +#ifdef PLUGIN_SET_GENERIC_ESP32 + # ifndef ESP32 + # define ESP32 + # endif // ifndef ESP32 +#endif // ifdef PLUGIN_SET_GENERIC_ESP32 + + +/****************************************************************************************************************************\ + * Arduino project "ESP Easy" © Copyright www.letscontrolit.com + * + * 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 received a copy of the GNU General Public License along with this program in file 'License.txt'. + * + * IDE download : https://www.arduino.cc/en/Main/Software + * ESP8266 Package : https://github.com/esp8266/Arduino + * + * Source Code : https://github.com/ESP8266nu/ESPEasy + * Support : http://www.letscontrolit.com + * Discussion : http://www.letscontrolit.com/forum/ + * + * Additional information about licensing can be found at : http://www.gnu.org/licenses + \*************************************************************************************************************************/ + +// This file incorporates work covered by the following copyright and permission notice: + +/****************************************************************************************************************************\ + * Arduino project "Nodo" © Copyright 2010..2015 Paul Tonkes + * + * 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 received a copy of the GNU General Public License along with this program in file 'License.txt'. + * + * Voor toelichting op de licentievoorwaarden zie : http://www.gnu.org/licenses + * Uitgebreide documentatie is te vinden op : http://www.nodo-domotica.nl + * Compiler voor deze programmacode te downloaden op : http://arduino.cc + \*************************************************************************************************************************/ + +// Simple Arduino sketch for ESP module, supporting: +// ================================================================================= +// Simple switch inputs and direct GPIO output control to drive relays, mosfets, etc +// Analog input (ESP-7/12 only) +// Pulse counters +// Dallas OneWire DS18b20 temperature sensors +// DHT11/22/12 humidity sensors +// BMP085 I2C Barometric Pressure sensor +// PCF8591 4 port Analog to Digital converter (I2C) +// RFID Wiegand-26 reader +// MCP23017 I2C IO Expanders +// BH1750 I2C Luminosity sensor +// Arduino Pro Mini with IO extender sketch, connected through I2C +// LCD I2C display 4x20 chars +// HC-SR04 Ultrasonic distance sensor +// SI7021 I2C temperature/humidity sensors +// TSL2561 I2C Luminosity sensor +// TSOP4838 IR receiver +// PN532 RFID reader +// Sharp GP2Y10 dust sensor +// PCF8574 I2C IO Expanders +// PCA9685 I2C 16 channel PWM driver +// OLED I2C display with SSD1306 driver +// MLX90614 I2C IR temperature sensor +// ADS1115 I2C ADC +// INA219 I2C voltage/current sensor +// BME280 I2C temp/hum/baro sensor +// MSP5611 I2C temp/baro sensor +// BMP280 I2C Barometric Pressure sensor +// SHT1X temperature/humidity sensors +// Ser2Net server +// DL-Bus (Technische Alternative) + +// Define globals before plugin sets to allow a personal override of the selected plugins +#include "ESPEasy-Globals.h" + +// Must be included after all the defines, since it is using TASKS_MAX +#include "_Plugin_Helper.h" + +// Plugin helper needs the defined controller sets, thus include after 'define_plugin_sets.h' +#include "src/Helpers/_CPlugin_Helper.h" + + +#include "src/ESPEasyCore/ESPEasy_setup.h" +#include "src/ESPEasyCore/ESPEasy_loop.h" + +#ifdef PHASE_LOCKED_WAVEFORM +# include +#endif // ifdef PHASE_LOCKED_WAVEFORM + +#if FEATURE_ADC_VCC +ADC_MODE(ADC_VCC); +#endif // if FEATURE_ADC_VCC + + + +#ifdef CORE_POST_2_5_0 + +/*********************************************************************************************\ +* Pre-init +\*********************************************************************************************/ +void preinit(); +void preinit() { + system_phy_set_powerup_option(3); + // Global WiFi constructors are not called yet + // (global class instances like WiFi, Serial... are not yet initialized).. + // No global object methods or C++ exceptions can be called in here! + // The below is a static class method, which is similar to a function, so it's ok. + #ifndef CORE_POST_3_0_0 + //ESP8266WiFiClass::preinitWiFiOff(); + #endif + + // Prevent RF calibration on power up. + // TD-er: disabled on 2021-06-07 as it may cause several issues with some boards. + // It cannot be made a setting as we can't read anything of our own settings. + //system_phy_set_powerup_option(RF_NO_CAL); +} + +#endif // ifdef CORE_POST_2_5_0 + + +void setup() { + ESPEasy_setup(); +} + +void loop() { + ESPEasy_loop(); +} diff --git a/src/_C014.cpp b/src/_C014.cpp index ff73bc5b68..62c20ba91d 100644 --- a/src/_C014.cpp +++ b/src/_C014.cpp @@ -307,7 +307,7 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& # if FEATURE_ESPEASY_P2P // $fw/name Device → Controller Name of the firmware running on the device. Allowed characters are the same as the device ID Yes Yes - CPlugin_014_sendMQTTdevice(pubname, event->TaskIndex, F("$fw/name"), getNodeTypeDisplayString(NODE_TYPE_ID), + CPlugin_014_sendMQTTdevice(pubname, event->TaskIndex, F("$fw/name"), toNodeTypeDisplayString(NODE_TYPE_ID), errorCounter); # endif // if FEATURE_ESPEASY_P2P diff --git a/src/_C019.cpp b/src/_C019.cpp new file mode 100644 index 0000000000..3a49eaa773 --- /dev/null +++ b/src/_C019.cpp @@ -0,0 +1,180 @@ +#include "src/Helpers/_CPlugin_Helper.h" + +#ifdef USES_C019 + +// ####################################################################################################### +// ################### Controller Plugin 019: ESPEasy-NOW ################################################ +// ####################################################################################################### + +# include "ESPEasy_common.h" +# include "src/ControllerQueue/C019_queue_element.h" +# include "src/Controller_config/C019_config.h" +# include "src/Globals/ESPEasy_now_handler.h" +# include "src/Globals/SendData_DuplicateChecker.h" +# include "src/Helpers/C019_ESPEasyNow_helper.h" +# include "src/ControllerQueue/C019_queue_element.h" + + +# define CPLUGIN_019 +# define CPLUGIN_ID_019 19 +# define CPLUGIN_NAME_019 ESPEASY_NOW_NAME + +bool CPlugin_019(CPlugin::Function function, struct EventStruct *event, String& string) +{ + bool success = false; + + switch (function) + { + case CPlugin::Function::CPLUGIN_PROTOCOL_ADD: + { + ProtocolStruct& proto = getProtocolStruct(event->idx); // = CPLUGIN_ID_019; + proto.usesMQTT = false; + proto.usesTemplate = false; + proto.usesAccount = false; + proto.usesPassword = false; + proto.usesExtCreds = false; + proto.defaultPort = 0; + proto.usesID = false; + proto.Custom = false; + proto.usesHost = false; + proto.usesPort = false; + proto.usesQueue = false; + proto.usesCheckReply = false; + proto.usesTimeout = false; + proto.usesSampleSets = false; + proto.needsNetwork = false; + break; + } + + case CPlugin::Function::CPLUGIN_GET_DEVICENAME: + { + string = F(CPLUGIN_NAME_019); + break; + } + + case CPlugin::Function::CPLUGIN_INIT: + { + // success = init_c019_delay_queue(event->ControllerIndex); + if (use_EspEasy_now) { ESPEasy_now_handler.end(); } + + ESPEasy_now_handler.loadConfig(event); + ESPEasy_now_handler.do_begin(); + success = true; + break; + } + + case CPlugin::Function::CPLUGIN_WEBFORM_LOAD: + { + C019_ConfigStruct_ptr customConfig(new (std::nothrow) C019_ConfigStruct); + + if (customConfig) { + customConfig->webform_load(event); + } + + break; + } + + case CPlugin::Function::CPLUGIN_WEBFORM_SAVE: + { + C019_ConfigStruct_ptr customConfig(new (std::nothrow) C019_ConfigStruct); + + if (customConfig) { + customConfig->webform_save(event); + } + + break; + } + + case CPlugin::Function::CPLUGIN_PROTOCOL_TEMPLATE: + { + C019_ConfigStruct_ptr customConfig(new (std::nothrow) C019_ConfigStruct); + + if (customConfig) { + // customConfig->webform_save(event); + } + break; + } + + case CPlugin::Function::CPLUGIN_PROTOCOL_SEND: + { + std::unique_ptr element(new C019_queue_element(event)); + + success = C019_DelayHandler->addToQueue(std::move(element)); + + Scheduler.scheduleNextDelayQueue(SchedulerIntervalTimer_e::TIMER_C019_DELAY_QUEUE, C019_DelayHandler->getNextScheduleTime()); + break; + } + + case CPlugin::Function::CPLUGIN_PROTOCOL_RECV: + { + C019_ESPEasyNow_helper::process_receive(event); + + break; + } + + case CPlugin::Function::CPLUGIN_FIFTY_PER_SECOND: + { + // FIXME: This must be removed from the run50TimesPerSecond() function + if (ESPEasy_now_handler.loop()) { + // FIXME TD-er: Must check if enabled, etc. + } + START_TIMER; + SendData_DuplicateChecker.loop(); + STOP_TIMER(ESPEASY_NOW_DEDUP_LOOP); + break; + } + + case CPlugin::Function::CPLUGIN_FLUSH: + { + process_c019_delay_queue(); + delay(0); + break; + } + + default: + break; + } + + return success; +} + +bool do_process_c019_delay_queue(cpluginID_t cpluginID, const Queue_element_base& element_base, + ControllerSettingsStruct& ControllerSettings) { + const C019_queue_element& element = static_cast(element_base); + + ESPEasy_Now_p2p_data data; + + const taskIndex_t taskIndex = element.event.TaskIndex; + + data.dataType = ESPEasy_Now_p2p_data_type::PluginData; + data.sourceTaskIndex = taskIndex; + data.plugin_id = getPluginID_from_TaskIndex(taskIndex).value; + data.sourceUnit = Settings.Unit; + data.idx = Settings.TaskDeviceID[element._controller_idx][taskIndex]; + data.sensorType = element.event.sensorType; + data.valueCount = getValueCountForTask(taskIndex); + + if (Sensor_VType::SENSOR_TYPE_ULONG == data.sensorType) { + data.addString(element.event.String2); + } else { + const TaskValues_Data_t * taskValues = UserVar.getRawTaskValues_Data(taskIndex); + if (taskValues != nullptr) { + data.addBinaryData((uint8_t *)(taskValues), sizeof(TaskValues_Data_t)); + } + } + + if (element.packed.length() > 0) { + data.addString(element.packed); + } + + MAC_address broadcast; + + // FIXME TD-er: Must add a way to reach further than WiFi reach + for (int i = 0; i < 6; ++i) { + broadcast.mac[i] = 0xFF; + } + + return ESPEasy_now_handler.sendESPEasyNow_p2p(cpluginID, broadcast, data); +} + +#endif // ifdef USES_C019 diff --git a/src/_P026_Sysinfo.ino b/src/_P026_Sysinfo.ino index a70a5ab9c8..be218393a4 100644 --- a/src/_P026_Sysinfo.ino +++ b/src/_P026_Sysinfo.ino @@ -1,155 +1,155 @@ -#include "_Plugin_Helper.h" -#ifdef USES_P026 - -// ####################################################################################################### -// #################################### Plugin 026: System Info ########################################## -// ####################################################################################################### - -/** Changelog: - * 2025-07-09 tonhuisman: Set default Value Type and UoM when changing a task value setting - * 2025-06-14 tonhuisman: Add support for Custom Value Type per task value - * 2025-01-12 tonhuisman: Add support for MQTT AutoDiscovery (not supported yet for SysInfo) - * 2023-09-24 tonhuisman: Add support for getting all values via Get Config option [#] where is the default - * name as set for an output value. None is ignored. Not available in MINIMAL_OTA builds. - * Move all includes to P026_data_struct.h - * 2023-09-23 tonhuisman: Add Internal temperature option for ESP32 - * Format source using Uncrustify - * Move #if check to P026_data_struct.h as Arduino compiler doesn't support that :( - * Move other defines to P026_data_struct.h - * 2023-09-23 tonhuisman: Start changelog - */ - -# define PLUGIN_026 -# define PLUGIN_ID_026 26 -# define PLUGIN_NAME_026 "Generic - System Info" - -# include "src/PluginStructs/P026_data_struct.h" // Arduino doesn't do #if in .ino sources :( - - -boolean Plugin_026(uint8_t function, struct EventStruct *event, String& string) -{ - boolean success = false; - - switch (function) - { - case PLUGIN_DEVICE_ADD: - { - auto& dev = Device[++deviceCount]; - dev.Number = PLUGIN_ID_026; - dev.VType = Sensor_VType::SENSOR_TYPE_QUAD; - dev.ValueCount = 4; - dev.SendDataOption = true; - dev.TimerOption = true; - dev.FormulaOption = true; - dev.OutputDataType = Output_Data_type_t::Simple; - dev.PluginStats = true; - dev.CustomVTypeVar = true; - break; - } - - case PLUGIN_GET_DEVICENAME: - { - string = F(PLUGIN_NAME_026); - break; - } - - case PLUGIN_GET_DEVICEVALUENAMES: - { - success = P026_data_struct::GetDeviceValueNames(event); - break; - } - - case PLUGIN_GET_DEVICEVALUECOUNT: - { - event->Par1 = P026_NR_OUTPUT_VALUES; - success = true; - break; - } - - case PLUGIN_GET_DEVICEVTYPE: - { - event->sensorType = static_cast(PCONFIG(P026_SENSOR_TYPE_INDEX)); - event->idx = P026_SENSOR_TYPE_INDEX; - success = true; - break; - } - - # if FEATURE_MQTT_DISCOVER || FEATURE_CUSTOM_TASKVAR_VTYPE - case PLUGIN_GET_DISCOVERY_VTYPES: - { - # if FEATURE_CUSTOM_TASKVAR_VTYPE - - for (uint8_t i = 0; i < event->Par5; ++i) { - event->ParN[i] = ExtraTaskSettings.getTaskVarCustomVType(i); // Custom/User selection - } - # else // if FEATURE_CUSTOM_TASKVAR_VTYPE - event->Par1 = static_cast(Sensor_VType::SENSOR_TYPE_NONE); // Not yet supported - # endif // if FEATURE_CUSTOM_TASKVAR_VTYPE - success = true; - break; - } - # endif // if FEATURE_MQTT_DISCOVER || FEATURE_CUSTOM_TASKVAR_VTYPE - - - case PLUGIN_SET_DEFAULTS: - { - PCONFIG(0) = 0; // "Uptime" - # if FEATURE_MQTT_DISCOVER && FEATURE_CUSTOM_TASKVAR_VTYPE - ExtraTaskSettings.setTaskVarCustomVType(0, static_cast(Sensor_VType::SENSOR_TYPE_DURATION)); - # endif // if FEATURE_MQTT_DISCOVER && FEATURE_CUSTOM_TASKVAR_VTYPE - - for (uint8_t i = 1; i < VARS_PER_TASK; ++i) { - PCONFIG(i) = 11; // "None" - } - PCONFIG(P026_SENSOR_TYPE_INDEX) = static_cast(Sensor_VType::SENSOR_TYPE_QUAD); - success = true; - break; - } - - case PLUGIN_WEBFORM_LOAD_OUTPUT_SELECTOR: - { - success = P026_data_struct::WebformLoadOutputSelector(event); - break; - } - - case PLUGIN_WEBFORM_LOAD: - { - break; - } - - case PLUGIN_WEBFORM_SAVE: - { - success = P026_data_struct::WebformSave(event); - break; - } - - case PLUGIN_INIT: - { - success = true; - break; - } - - case PLUGIN_READ: - { - success = P026_data_struct::Plugin_Read(event); - break; - } - # ifndef PLUGIN_BUILD_MINIMAL_OTA - case PLUGIN_GET_CONFIG_VALUE: - { - success = P026_data_struct::Plugin_GetConfigValue(event, string); - break; - } - # endif // ifndef PLUGIN_BUILD_MINIMAL_OTA -# if FEATURE_PACKED_RAW_DATA - case PLUGIN_GET_PACKED_RAW_DATA: - { - success = P026_data_struct::Plugin_GetPackedRawData(event, string); - break; - } -# endif // if FEATURE_PACKED_RAW_DATA - } - return success; -} - -#endif // USES_P026 +#include "_Plugin_Helper.h" +#ifdef USES_P026 + +// ####################################################################################################### +// #################################### Plugin 026: System Info ########################################## +// ####################################################################################################### + +/** Changelog: + * 2025-07-09 tonhuisman: Set default Value Type and UoM when changing a task value setting + * 2025-06-14 tonhuisman: Add support for Custom Value Type per task value + * 2025-01-12 tonhuisman: Add support for MQTT AutoDiscovery (not supported yet for SysInfo) + * 2023-09-24 tonhuisman: Add support for getting all values via Get Config option [#] where is the default + * name as set for an output value. None is ignored. Not available in MINIMAL_OTA builds. + * Move all includes to P026_data_struct.h + * 2023-09-23 tonhuisman: Add Internal temperature option for ESP32 + * Format source using Uncrustify + * Move #if check to P026_data_struct.h as Arduino compiler doesn't support that :( + * Move other defines to P026_data_struct.h + * 2023-09-23 tonhuisman: Start changelog + */ + +# define PLUGIN_026 +# define PLUGIN_ID_026 26 +# define PLUGIN_NAME_026 "Generic - System Info" + +# include "src/PluginStructs/P026_data_struct.h" // Arduino doesn't do #if in .ino sources :( + + +boolean Plugin_026(uint8_t function, struct EventStruct *event, String& string) +{ + boolean success = false; + + switch (function) + { + case PLUGIN_DEVICE_ADD: + { + auto& dev = Device[++deviceCount]; + dev.Number = PLUGIN_ID_026; + dev.VType = Sensor_VType::SENSOR_TYPE_QUAD; + dev.ValueCount = 4; + dev.SendDataOption = true; + dev.TimerOption = true; + dev.FormulaOption = true; + dev.OutputDataType = Output_Data_type_t::Simple; + dev.PluginStats = true; + dev.CustomVTypeVar = true; + break; + } + + case PLUGIN_GET_DEVICENAME: + { + string = F(PLUGIN_NAME_026); + break; + } + + case PLUGIN_GET_DEVICEVALUENAMES: + { + success = P026_data_struct::GetDeviceValueNames(event); + break; + } + + case PLUGIN_GET_DEVICEVALUECOUNT: + { + event->Par1 = P026_NR_OUTPUT_VALUES; + success = true; + break; + } + + case PLUGIN_GET_DEVICEVTYPE: + { + event->sensorType = static_cast(PCONFIG(P026_SENSOR_TYPE_INDEX)); + event->idx = P026_SENSOR_TYPE_INDEX; + success = true; + break; + } + + # if FEATURE_MQTT_DISCOVER || FEATURE_CUSTOM_TASKVAR_VTYPE + case PLUGIN_GET_DISCOVERY_VTYPES: + { + # if FEATURE_CUSTOM_TASKVAR_VTYPE + + for (uint8_t i = 0; i < event->Par5; ++i) { + event->ParN[i] = ExtraTaskSettings.getTaskVarCustomVType(i); // Custom/User selection + } + # else // if FEATURE_CUSTOM_TASKVAR_VTYPE + event->Par1 = static_cast(Sensor_VType::SENSOR_TYPE_NONE); // Not yet supported + # endif // if FEATURE_CUSTOM_TASKVAR_VTYPE + success = true; + break; + } + # endif // if FEATURE_MQTT_DISCOVER || FEATURE_CUSTOM_TASKVAR_VTYPE + + + case PLUGIN_SET_DEFAULTS: + { + PCONFIG(0) = 0; // "Uptime" + # if FEATURE_MQTT_DISCOVER && FEATURE_CUSTOM_TASKVAR_VTYPE + ExtraTaskSettings.setTaskVarCustomVType(0, static_cast(Sensor_VType::SENSOR_TYPE_DURATION)); + # endif // if FEATURE_MQTT_DISCOVER && FEATURE_CUSTOM_TASKVAR_VTYPE + + for (uint8_t i = 1; i < VARS_PER_TASK; ++i) { + PCONFIG(i) = 11; // "None" + } + PCONFIG(P026_SENSOR_TYPE_INDEX) = static_cast(Sensor_VType::SENSOR_TYPE_QUAD); + success = true; + break; + } + + case PLUGIN_WEBFORM_LOAD_OUTPUT_SELECTOR: + { + success = P026_data_struct::WebformLoadOutputSelector(event); + break; + } + + case PLUGIN_WEBFORM_LOAD: + { + break; + } + + case PLUGIN_WEBFORM_SAVE: + { + success = P026_data_struct::WebformSave(event); + break; + } + + case PLUGIN_INIT: + { + success = true; + break; + } + + case PLUGIN_READ: + { + success = P026_data_struct::Plugin_Read(event); + break; + } + # ifndef PLUGIN_BUILD_MINIMAL_OTA + case PLUGIN_GET_CONFIG_VALUE: + { + success = P026_data_struct::Plugin_GetConfigValue(event, string); + break; + } + # endif // ifndef PLUGIN_BUILD_MINIMAL_OTA +# if FEATURE_PACKED_RAW_DATA + case PLUGIN_GET_PACKED_RAW_DATA: + { + success = P026_data_struct::Plugin_GetPackedRawData(event, string); + break; + } +# endif // if FEATURE_PACKED_RAW_DATA + } + return success; +} + +#endif // USES_P026 diff --git a/src/_P094_CULReader.ino b/src/_P094_CULReader.ino index 0d7aa9cdca..c78c330cd4 100644 --- a/src/_P094_CULReader.ino +++ b/src/_P094_CULReader.ino @@ -1,602 +1,603 @@ -#include "_Plugin_Helper.h" -#ifdef USES_P094 - -// ####################################################################################################### -// #################### Plugin 094 Brick4U CUL Reader #################################################### -// ####################################################################################################### -// -// Interact with Brick4U CUL receiver -// Allows to control the mode of the CUL receiver -// - -/** Changelog: - * 2025-06-14 tonhuisman: Add support for Custom Value Type per task value - * 2025-01-12 tonhuisman: Add support for MQTT AutoDiscovery (not supported for CUL-reader) - */ - -# include "src/ESPEasyCore/ESPEasyNetwork.h" - -# include "src/Helpers/ESPEasy_Storage.h" -# include "src/Helpers/StringConverter.h" -# include "src/PluginStructs/P094_data_struct.h" - -# include - -# define PLUGIN_094 -# define PLUGIN_ID_094 94 -# define PLUGIN_NAME_094 "Communication - CUL Reader" -# define PLUGIN_VALUENAME1_094 "v" - - -bool Plugin_094_match_all(taskIndex_t taskIndex, - const String& received, - const String& source, - bool fromCUL); - -void Plugin_094_setFlags(struct EventStruct *event); - -// Plugin settings: -// Validate: -// - [0..9] -// - "+", "-", "." -// - [A..Z] -// - [a..z] -// - ASCII 32 - 217 -// Sentence start: char -// Sentence end: CR/CRLF/LF/char -// Max length sentence: 1k max -// Interpret as: -// - Float -// - int -// - String -// Init string (incl parsing CRLF like characters) -// Timeout between sentences. - - -boolean Plugin_094(uint8_t function, struct EventStruct *event, String& string) { - boolean success = false; - - switch (function) { - case PLUGIN_DEVICE_ADD: { - auto& dev = Device[++deviceCount]; - dev.Number = PLUGIN_ID_094; - dev.Type = DEVICE_TYPE_SERIAL; - dev.VType = Sensor_VType::SENSOR_TYPE_STRING; - dev.OutputDataType = Output_Data_type_t::Default; - dev.ValueCount = 1; - dev.SendDataOption = true; - dev.TimerOption = true; - dev.DuplicateDetection = true; - - // FIXME TD-er: Not sure if access to any existing task data is needed when saving - dev.ExitTaskBeforeSave = true; - dev.CustomVTypeVar = true; - break; - } - - case PLUGIN_GET_DEVICENAME: { - string = F(PLUGIN_NAME_094); - break; - } - - case PLUGIN_GET_DEVICEVALUENAMES: - { - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_094)); - break; - } - - # if FEATURE_MQTT_DISCOVER || FEATURE_CUSTOM_TASKVAR_VTYPE - case PLUGIN_GET_DISCOVERY_VTYPES: - { - # if FEATURE_CUSTOM_TASKVAR_VTYPE - - for (uint8_t i = 0; i < event->Par5; ++i) { - event->ParN[i] = ExtraTaskSettings.getTaskVarCustomVType(i); // Custom/User selection - } - # else // if FEATURE_CUSTOM_TASKVAR_VTYPE - event->Par1 = static_cast(Sensor_VType::SENSOR_TYPE_NONE); // Not yet supported - # endif // if FEATURE_CUSTOM_TASKVAR_VTYPE - success = true; - break; - } - # endif // if FEATURE_MQTT_DISCOVER || FEATURE_CUSTOM_TASKVAR_VTYPE - - case PLUGIN_GET_DEVICEGPIONAMES: { - serialHelper_getGpioNames(event, false, true); // TX optional - break; - } - - case PLUGIN_WEBFORM_SHOW_VALUES: - { - P094_data_struct *P094_data = - static_cast(getPluginTaskData(event->TaskIndex)); - - if ((nullptr != P094_data) && P094_data->isInitialized()) { - uint32_t success, error, length_last; - P094_data->getSentencesReceived(success, error, length_last); - uint8_t varNr = VARS_PER_TASK; - pluginWebformShowValue(event->TaskIndex, varNr++, F("Success"), String(success)); - pluginWebformShowValue(event->TaskIndex, varNr++, F("Error"), String(error)); - pluginWebformShowValue(event->TaskIndex, varNr++, F("Length Last"), String(length_last), true); - - // success = true; - } - break; - } - - case PLUGIN_SET_DEFAULTS: - { - P094_BAUDRATE = P094_DEFAULT_BAUDRATE; - P094_DEBUG_SENTENCE_LENGTH = 0; - - success = true; - break; - } - - case PLUGIN_WEBFORM_SHOW_CONFIG: - { - string += serialHelper_getSerialTypeLabel(event); - success = true; - break; - } - - case PLUGIN_WEBFORM_SHOW_SERIAL_PARAMS: - { - addFormNumericBox(F("Baudrate"), P094_BAUDRATE_LABEL, P094_BAUDRATE, 2400, 115200); - addUnit(F("baud")); - break; - } - - case PLUGIN_WEBFORM_LOAD: - { - addFormCheckBox(F("Append system time"), F("systime"), P094_GET_APPEND_RECEIVE_SYSTIME); - -# if P094_DEBUG_OPTIONS - addFormSubHeader(F("Debug Options")); - addFormNumericBox(F("(debug) Generated length"), P094_DEBUG_SENTENCE_LABEL, P094_DEBUG_SENTENCE_LENGTH, 0, 1024); - addFormCheckBox(F("(debug) Generate CUL data"), F("debug_data"), P094_GET_GENERATE_DEBUG_CUL_DATA); -# endif // if P094_DEBUG_OPTIONS - - addFormSubHeader(F("Filtering")); - addFormCheckBox(F("Mute Messages"), F("mute"), P094_GET_MUTE_MESSAGES); - P094_html_show_matchForms(event); - addFormCheckBox(F("Enable Interval Filter"), F("interval_filter"), P094_GET_INTERVAL_FILTER); - - addFormSubHeader(F("Statistics")); - addFormCheckBox(F("Collect W-MBus Stats"), F("collect_stats"), P094_GET_COLLECT_STATS); - addFormNote(F("Collect reception statistics of W-MBus devices received by the CUL reader")); - - - P094_html_show_stats(event); - - success = true; - break; - } - - case PLUGIN_WEBFORM_SAVE: { - P094_BAUDRATE = getFormItemInt(P094_BAUDRATE_LABEL); - P094_DEBUG_SENTENCE_LENGTH = getFormItemInt(P094_DEBUG_SENTENCE_LABEL); - - P094_DISABLE_WINDOW_TIME_MS = getFormItemInt(F("disableTime")); - P094_NR_FILTERS = getFormItemInt(F("nrfilters")); - - - P094_SET_APPEND_RECEIVE_SYSTIME(isFormItemChecked(F("systime"))); -# if P094_DEBUG_OPTIONS - P094_SET_GENERATE_DEBUG_CUL_DATA(isFormItemChecked(F("debug_data"))); -# endif // if P094_DEBUG_OPTIONS - P094_SET_INTERVAL_FILTER(isFormItemChecked(F("interval_filter"))); - P094_SET_MUTE_MESSAGES(isFormItemChecked(F("mute"))); - P094_SET_COLLECT_STATS(isFormItemChecked(F("collect_stats"))); - - - P094_data_struct *P094_data = - static_cast(getPluginTaskData(event->TaskIndex)); - - - const bool localAllocated = nullptr == P094_data; - - if (localAllocated) { - P094_data = new (std::nothrow) P094_data_struct(); - } - - if ((nullptr != P094_data)) { - P094_data->WebformSaveFilters(event, P094_NR_FILTERS); - success = true; - - if (localAllocated) { - delete P094_data; - } - } - - break; - } - - case PLUGIN_INIT: { - const int16_t serial_rx = CONFIG_PIN1; - const int16_t serial_tx = CONFIG_PIN2; - const ESPEasySerialPort port = static_cast(CONFIG_PORT); - initPluginTaskData(event->TaskIndex, new (std::nothrow) P094_data_struct()); - P094_data_struct *P094_data = - static_cast(getPluginTaskData(event->TaskIndex)); - - if (nullptr == P094_data) { - return success; - } - - if (P094_data->init( - port, - serial_rx, - serial_tx, - P094_BAUDRATE)) { - P094_data->setFlags( - P094_DISABLE_WINDOW_TIME_MS, - P094_GET_INTERVAL_FILTER, - P094_GET_MUTE_MESSAGES, - P094_GET_COLLECT_STATS); - P094_data->loadFilters(event, P094_NR_FILTERS); -# if P094_DEBUG_OPTIONS - P094_data->setGenerate_DebugCulData(P094_GET_GENERATE_DEBUG_CUL_DATA); -# endif // if P094_DEBUG_OPTIONS - success = true; - - serialHelper_log_GpioDescription(port, serial_rx, serial_tx); - } else { - clearPluginTaskData(event->TaskIndex); - } - break; - } - - case PLUGIN_ONCE_A_SECOND: - { - P094_data_struct *P094_data = - static_cast(getPluginTaskData(event->TaskIndex)); - - if (nullptr != P094_data) { - P094_data->interval_filter_purgeExpired(); - - if (P094_data->dump_next_stats(event->String2)) { - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - addLogMove(LOG_LEVEL_INFO, concat(F("CUL Reader: "), event->String2)); - } - - // Do not create events for dumping stats. - const bool sendEvents = false; - sendData(event, sendEvents); - } - } - break; - } - - case PLUGIN_FIFTY_PER_SECOND: { - if (Settings.TaskDeviceEnabled[event->TaskIndex]) { - P094_data_struct *P094_data = - static_cast(getPluginTaskData(event->TaskIndex)); - - if ((nullptr != P094_data) && P094_data->loop()) { - // Scheduler.schedule_task_device_timer(event->TaskIndex, millis() + 10); - delay(0); // Processing a full sentence may take a while, run some - // background tasks. - P094_data->getSentence(event->String2, P094_GET_APPEND_RECEIVE_SYSTIME); - - if (event->String2.length() > 0) { - const bool fromCUL = true; - const String source = NetworkGetHostname(); - - if (Plugin_094_match_all(event->TaskIndex, event->String2, source, fromCUL)) { - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log; - - if (log.reserve(128)) { - log = F("CUL Reader: Sending: "); - const size_t messageLength = event->String2.length(); - - if (messageLength < 100) { - log += event->String2; - } else { - // Split string so we get start and end - log += event->String2.substring(0, 40); - log += F("..."); - log += event->String2.substring(messageLength - 40); - } - addLogMove(LOG_LEVEL_INFO, log); - } - } - - // Filter length options: - // - 22 char, for hash-value then we filter the exact meter including serial and meter type, (that will also prevent very quit - // sending meters, which normaly is a fault) - // - 38 char, The exact message, because we have 2 uint8_t from the value payload - // sendData_checkDuplicates(event, event->String2.substring(0, 22)); - sendData(event); - } - } - } - success = true; - } - break; - } - - case PLUGIN_READ: { - if (P094_DEBUG_SENTENCE_LENGTH > 0) { - P094_data_struct *P094_data = - static_cast(getPluginTaskData(event->TaskIndex)); - - if ((nullptr != P094_data)) { - # if P094_DEBUG_OPTIONS - const uint32_t debug_count = P094_data->getDebugCounter(); - event->String2.reserve(P094_DEBUG_SENTENCE_LENGTH); - event->String2 += String(debug_count); - event->String2 += '_'; - const char c = '0' + debug_count % 10; - - for (long i = event->String2.length(); i < P094_DEBUG_SENTENCE_LENGTH; ++i) { - event->String2 += c; - } - # endif // if P094_DEBUG_OPTIONS - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - addLogMove(LOG_LEVEL_INFO, strformat(F("CUL Reader: Sending: %s..."), - event->String2.substring(0, 20).c_str())); - } - - // sendData_checkDuplicates(event, event->String2.substring(0, 22)); - sendData(event); - } - } - break; - } - - case PLUGIN_GET_CONFIG_VALUE: - { - const String command = parseString(string, 1); - - P094_data_struct *P094_data = - static_cast(getPluginTaskData(event->TaskIndex)); - - if (nullptr != P094_data) { - if (equals(command, F("getfiltermd5"))) { - // to output a MD5 of the currently active filters. - string = P094_data->getFiltersMD5(); - success = true; - } else if (equals(command, F("getfilterenabled"))) { - string = P094_GET_INTERVAL_FILTER; - success = true; - } else if (equals(command, F("getmuteenabled"))) { - string = P094_GET_MUTE_MESSAGES; - success = true; - } - } - break; - } - - - case PLUGIN_WRITE: { - const String cmd = parseString(string, 1); - const String subcmd = parseString(string, 2); - - if (cmd.startsWith(F("culreader"))) { - P094_data_struct *P094_data = - static_cast(getPluginTaskData(event->TaskIndex)); - - if (equals(subcmd, F("enablefilter"))) { - // culreader,enablefilter - P094_SET_INTERVAL_FILTER(1); - success = true; - } else if (equals(subcmd, F("disablefilter"))) { - // culreader,disablefilter - P094_SET_INTERVAL_FILTER(0); - success = true; - } else if (equals(subcmd, F("mute"))) { - // culreader,mute - P094_SET_MUTE_MESSAGES(1); - success = true; - } else if (equals(subcmd, F("unmute"))) { - // culreader,unmute - P094_SET_MUTE_MESSAGES(0); - success = true; - } else if ((nullptr != P094_data)) { - if (equals(cmd, F("culreader_write")) || - equals(subcmd, F("write"))) { - // culreader,write, - String param1 = parseStringKeepCase(string, 2); - parseSystemVariables(param1, false); - P094_data->sendString(param1); - addLogMove(LOG_LEVEL_INFO, param1); - success = true; - } else if (equals(subcmd, F("dumpstats"))) { - // culreader,dumpstats - P094_data->prepare_dump_stats(); - success = true; - } else if (equals(subcmd, F("clearfilters"))) { - // culreader,clearfilters - P094_data->clearFilters(); - success = true; - } else if (equals(subcmd, F("savefilters"))) { - // culreader,savefilters - P094_data->saveFilters(event); - SaveSettings(); - success = true; - } else if (equals(subcmd, F("addfilter"))) { - // culreader,addfilter, - // Examples for a filter definition - // EBZ.02.12345678;all - // *.02.*;15m - // TCH.44.*;once - // *.*.*;5m - success = true; - P094_data->addFilter(event, parseString(string, 3)); - } else if (equals(subcmd, F("setfilters"))) { - // culreader,setfilters,|...| - // Examples for a filter definition - // culreader,setfilters,EBZ.02.12345678;all|*.02.*;15m|TCH.44.*;once|*.*.*;5m - success = true; - P094_data->clearFilters(); - P094_NR_FILTERS = 0; - const String argument = parseString(string, 3); - - if (!argument.isEmpty()) { - int argNr = 1; - - while (argNr > 0) { - const String filter = parseString(argument, argNr, '|'); - - if (!filter.isEmpty()) - { - P094_data->addFilter(event, filter); - ++argNr; - } else { - argNr = 0; - } - } - } - P094_data->saveFilters(event); - SaveSettings(); - } - } - - if (success) { - Plugin_094_setFlags(event); - } - } - - - break; - } -# ifdef USES_ESPEASY_NOW - case PLUGIN_FILTEROUT_CONTROLLER_DATA: - { - // event->String1 => topic; - // event->String2 => payload; - if (Settings.TaskDeviceEnabled[event->TaskIndex]) { - const bool fromCUL = false; - - if (!Plugin_094_match_all(event->TaskIndex, event->String2, event->String1, fromCUL)) { - // Inverse as we check for filtering 'out' the messages. - success = true; - } - } - - break; - } -# endif // ifdef USES_ESPEASY_NOW - } - return success; -} - -bool Plugin_094_match_all(taskIndex_t taskIndex, const String& received, const String& source, bool fromCUL) -{ - P094_data_struct *P094_data = - static_cast(getPluginTaskData(taskIndex)); - - if ((nullptr == P094_data)) { - return false; - } - - if (P094_data->disableFilterWindowActive()) { - addLog(LOG_LEVEL_INFO, F("CUL Reader: Disable Filter Window active")); - return true; - } - - mBusPacket_t packet; - bool res = P094_data->parsePacket(received, packet); - - # ifdef ESP8266 - - if (res && fromCUL) { - # endif // ifdef ESP8266 - - // Only collect stats from the actual CUL receiver, not when processing forwarded packets. - // On ESP8266: only collect stats on the filtered nodes or else we will likely run out of memory - P094_data->collect_stats_add(packet, source); - # ifdef ESP8266 -} - - # endif // ifdef ESP8266 - - return res; -} - -void Plugin_094_setFlags(struct EventStruct *event) -{ - P094_data_struct *P094_data = - static_cast(getPluginTaskData(event->TaskIndex)); - - if (nullptr != P094_data) { - P094_data->setFlags( - P094_DISABLE_WINDOW_TIME_MS, - P094_GET_INTERVAL_FILTER, - P094_GET_MUTE_MESSAGES, - P094_GET_COLLECT_STATS); - } -} - -void P094_html_show_matchForms(struct EventStruct *event) { - addFormNumericBox(F("Filter Off Window after send"), - F("disableTime"), - P094_DISABLE_WINDOW_TIME_MS, - 0, - 60000); - addUnit(F("msec")); - addFormNote(F("0 = Do not turn off filter after sending to the connected device.")); - - addFormNumericBox( - F("Nr Filters"), - F("nrfilters"), - P094_NR_FILTERS, - 0, - P094_MAX_NR_FILTERS); - - - P094_data_struct *P094_data = - static_cast(getPluginTaskData(event->TaskIndex)); - - const bool localAllocated = nullptr == P094_data; - - if (localAllocated) { - P094_data = new (std::nothrow) P094_data_struct(); - - if (nullptr != P094_data) { - P094_data->loadFilters(event, P094_NR_FILTERS); - } - } - - if ((nullptr != P094_data)) { - P094_data->WebformLoadFilters(P094_NR_FILTERS); - - if (localAllocated) { - delete P094_data; - } - } -} - -void P094_html_show_stats(struct EventStruct *event) { - P094_data_struct *P094_data = - static_cast(getPluginTaskData(event->TaskIndex)); - - if ((nullptr == P094_data) || !P094_data->isInitialized()) { - return; - } - - P094_data->html_show_interval_filter_stats(); - - P094_data->html_show_mBus_stats(); - - { - addRowLabel(F("Current Sentence")); - addHtml(P094_data->peekSentence()); - } - - { - addRowLabel(F("Sentences (pass/fail)")); - uint32_t success, error, length_last; - P094_data->getSentencesReceived(success, error, length_last); - addHtmlInt(success); - addHtml('/'); - addHtmlInt(error); - addRowLabel(F("Length Last Sentence")); - addHtmlInt(length_last); - } -} - -#endif // USES_P094 +#include "_Plugin_Helper.h" +#ifdef USES_P094 + +// ####################################################################################################### +// #################### Plugin 094 Brick4U CUL Reader #################################################### +// ####################################################################################################### +// +// Interact with Brick4U CUL receiver +// Allows to control the mode of the CUL receiver +// + +/** Changelog: + * 2025-06-14 tonhuisman: Add support for Custom Value Type per task value + * 2025-01-12 tonhuisman: Add support for MQTT AutoDiscovery (not supported for CUL-reader) + */ + +# include "src/ESPEasyCore/ESPEasyNetwork.h" + +# include "src/Helpers/ESPEasy_Storage.h" +# include "src/Helpers/StringConverter.h" +# include "src/PluginStructs/P094_data_struct.h" + +# include + +# define PLUGIN_094 +# define PLUGIN_ID_094 94 +# define PLUGIN_NAME_094 "Communication - CUL Reader" +# define PLUGIN_VALUENAME1_094 "v" + + +bool Plugin_094_match_all(taskIndex_t taskIndex, + const String& received, + const String& source, + bool fromCUL); + +void Plugin_094_setFlags(struct EventStruct *event); + +// Plugin settings: +// Validate: +// - [0..9] +// - "+", "-", "." +// - [A..Z] +// - [a..z] +// - ASCII 32 - 217 +// Sentence start: char +// Sentence end: CR/CRLF/LF/char +// Max length sentence: 1k max +// Interpret as: +// - Float +// - int +// - String +// Init string (incl parsing CRLF like characters) +// Timeout between sentences. + + +boolean Plugin_094(uint8_t function, struct EventStruct *event, String& string) { + boolean success = false; + + switch (function) { + case PLUGIN_DEVICE_ADD: { + auto& dev = Device[++deviceCount]; + dev.Number = PLUGIN_ID_094; + dev.Type = DEVICE_TYPE_SERIAL; + dev.VType = Sensor_VType::SENSOR_TYPE_STRING; + dev.OutputDataType = Output_Data_type_t::Default; + dev.ValueCount = 1; + dev.SendDataOption = true; + dev.TimerOption = true; + dev.DuplicateDetection = true; + + // FIXME TD-er: Not sure if access to any existing task data is needed when saving + dev.ExitTaskBeforeSave = true; + dev.CustomVTypeVar = true; + break; + } + + case PLUGIN_GET_DEVICENAME: { + string = F(PLUGIN_NAME_094); + break; + } + + case PLUGIN_GET_DEVICEVALUENAMES: + { + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_094)); + break; + } + + # if FEATURE_MQTT_DISCOVER || FEATURE_CUSTOM_TASKVAR_VTYPE + case PLUGIN_GET_DISCOVERY_VTYPES: + { + # if FEATURE_CUSTOM_TASKVAR_VTYPE + + for (uint8_t i = 0; i < event->Par5; ++i) { + event->ParN[i] = ExtraTaskSettings.getTaskVarCustomVType(i); // Custom/User selection + } + # else // if FEATURE_CUSTOM_TASKVAR_VTYPE + event->Par1 = static_cast(Sensor_VType::SENSOR_TYPE_NONE); // Not yet supported + # endif // if FEATURE_CUSTOM_TASKVAR_VTYPE + success = true; + break; + } + # endif // if FEATURE_MQTT_DISCOVER || FEATURE_CUSTOM_TASKVAR_VTYPE + + case PLUGIN_GET_DEVICEGPIONAMES: { + serialHelper_getGpioNames(event, false, true); // TX optional + break; + } + + case PLUGIN_WEBFORM_SHOW_VALUES: + { + P094_data_struct *P094_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + if ((nullptr != P094_data) && P094_data->isInitialized()) { + uint32_t success, error, length_last; + P094_data->getSentencesReceived(success, error, length_last); + uint8_t varNr = VARS_PER_TASK; + pluginWebformShowValue(event->TaskIndex, varNr++, F("Success"), String(success)); + pluginWebformShowValue(event->TaskIndex, varNr++, F("Error"), String(error)); + pluginWebformShowValue(event->TaskIndex, varNr++, F("Length Last"), String(length_last), true); + + // success = true; + } + break; + } + + case PLUGIN_SET_DEFAULTS: + { + P094_BAUDRATE = P094_DEFAULT_BAUDRATE; + P094_DEBUG_SENTENCE_LENGTH = 0; + + success = true; + break; + } + + case PLUGIN_WEBFORM_SHOW_CONFIG: + { + string += serialHelper_getSerialTypeLabel(event); + success = true; + break; + } + + case PLUGIN_WEBFORM_SHOW_SERIAL_PARAMS: + { + addFormNumericBox(F("Baudrate"), P094_BAUDRATE_LABEL, P094_BAUDRATE, 2400, 115200); + addUnit(F("baud")); + break; + } + + case PLUGIN_WEBFORM_LOAD: + { + addFormCheckBox(F("Append system time"), F("systime"), P094_GET_APPEND_RECEIVE_SYSTIME); + +# if P094_DEBUG_OPTIONS + addFormSubHeader(F("Debug Options")); + addFormNumericBox(F("(debug) Generated length"), P094_DEBUG_SENTENCE_LABEL, P094_DEBUG_SENTENCE_LENGTH, 0, 1024); + addFormCheckBox(F("(debug) Generate CUL data"), F("debug_data"), P094_GET_GENERATE_DEBUG_CUL_DATA); +# endif // if P094_DEBUG_OPTIONS + + addFormSubHeader(F("Filtering")); + addFormCheckBox(F("Mute Messages"), F("mute"), P094_GET_MUTE_MESSAGES); + P094_html_show_matchForms(event); + addFormCheckBox(F("Enable Interval Filter"), F("interval_filter"), P094_GET_INTERVAL_FILTER); + + addFormSubHeader(F("Statistics")); + addFormCheckBox(F("Collect W-MBus Stats"), F("collect_stats"), P094_GET_COLLECT_STATS); + addFormNote(F("Collect reception statistics of W-MBus devices received by the CUL reader")); + + + P094_html_show_stats(event); + + success = true; + break; + } + + case PLUGIN_WEBFORM_SAVE: { + P094_BAUDRATE = getFormItemInt(P094_BAUDRATE_LABEL); + P094_DEBUG_SENTENCE_LENGTH = getFormItemInt(P094_DEBUG_SENTENCE_LABEL); + + P094_DISABLE_WINDOW_TIME_MS = getFormItemInt(F("disableTime")); + P094_NR_FILTERS = getFormItemInt(F("nrfilters")); + + + P094_SET_APPEND_RECEIVE_SYSTIME(isFormItemChecked(F("systime"))); +# if P094_DEBUG_OPTIONS + P094_SET_GENERATE_DEBUG_CUL_DATA(isFormItemChecked(F("debug_data"))); +# endif // if P094_DEBUG_OPTIONS + P094_SET_INTERVAL_FILTER(isFormItemChecked(F("interval_filter"))); + P094_SET_MUTE_MESSAGES(isFormItemChecked(F("mute"))); + P094_SET_COLLECT_STATS(isFormItemChecked(F("collect_stats"))); + + + P094_data_struct *P094_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + + const bool localAllocated = nullptr == P094_data; + + if (localAllocated) { + P094_data = new (std::nothrow) P094_data_struct(); + } + + if ((nullptr != P094_data)) { + P094_data->WebformSaveFilters(event, P094_NR_FILTERS); + success = true; + + if (localAllocated) { + delete P094_data; + } + } + + break; + } + + case PLUGIN_INIT: { + const int16_t serial_rx = CONFIG_PIN1; + const int16_t serial_tx = CONFIG_PIN2; + const ESPEasySerialPort port = static_cast(CONFIG_PORT); + initPluginTaskData(event->TaskIndex, new (std::nothrow) P094_data_struct()); + P094_data_struct *P094_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr == P094_data) { + return success; + } + + if (P094_data->init( + port, + serial_rx, + serial_tx, + P094_BAUDRATE)) { + P094_data->setFlags( + P094_DISABLE_WINDOW_TIME_MS, + P094_GET_INTERVAL_FILTER, + P094_GET_MUTE_MESSAGES, + P094_GET_COLLECT_STATS); + P094_data->loadFilters(event, P094_NR_FILTERS); +# if P094_DEBUG_OPTIONS + P094_data->setGenerate_DebugCulData(P094_GET_GENERATE_DEBUG_CUL_DATA); +# endif // if P094_DEBUG_OPTIONS + success = true; + + serialHelper_log_GpioDescription(port, serial_rx, serial_tx); + } else { + clearPluginTaskData(event->TaskIndex); + } + break; + } + + case PLUGIN_ONCE_A_SECOND: + { + P094_data_struct *P094_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != P094_data) { + P094_data->interval_filter_purgeExpired(); + + if (P094_data->dump_next_stats(event->String2)) { + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLogMove(LOG_LEVEL_INFO, concat(F("CUL Reader: "), event->String2)); + } + + // Do not create events for dumping stats. + const bool sendEvents = false; + sendData(event, sendEvents); + } + } + break; + } + + case PLUGIN_FIFTY_PER_SECOND: { + if (Settings.TaskDeviceEnabled[event->TaskIndex]) { + P094_data_struct *P094_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + if ((nullptr != P094_data) && P094_data->loop()) { + // Scheduler.schedule_task_device_timer(event->TaskIndex, millis() + 10); + delay(0); // Processing a full sentence may take a while, run some + // background tasks. + P094_data->getSentence(event->String2, P094_GET_APPEND_RECEIVE_SYSTIME); + + if (event->String2.length() > 0) { + const bool fromCUL = true; + const String source = NetworkGetHostname(); + + if (Plugin_094_match_all(event->TaskIndex, event->String2, source, fromCUL)) { + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + String log; + + if (log.reserve(128)) { + log = F("CUL Reader: Sending: "); + const size_t messageLength = event->String2.length(); + + if (messageLength < 100) { + log += event->String2; + } else { + // Split string so we get start and end + log += event->String2.substring(0, 40); + log += F("..."); + log += event->String2.substring(messageLength - 40); + } + addLogMove(LOG_LEVEL_INFO, log); + } + } + + // Filter length options: + // - 22 char, for hash-value then we filter the exact meter including serial and meter type, (that will also prevent very quit + // sending meters, which normaly is a fault) + // - 38 char, The exact message, because we have 2 uint8_t from the value payload + // sendData_checkDuplicates(event, event->String2.substring(0, 22)); + sendData(event); + } + } + } + success = true; + } + break; + } + + case PLUGIN_READ: { + if (P094_DEBUG_SENTENCE_LENGTH > 0) { + P094_data_struct *P094_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + if ((nullptr != P094_data)) { + # if P094_DEBUG_OPTIONS + const uint32_t debug_count = P094_data->getDebugCounter(); + event->String2.reserve(P094_DEBUG_SENTENCE_LENGTH); + event->String2 += String(debug_count); + event->String2 += '_'; + const char c = '0' + debug_count % 10; + + for (long i = event->String2.length(); i < P094_DEBUG_SENTENCE_LENGTH; ++i) { + event->String2 += c; + } + # endif // if P094_DEBUG_OPTIONS + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLogMove(LOG_LEVEL_INFO, strformat(F("CUL Reader: Sending: %s..."), + event->String2.substring(0, 20).c_str())); + } + + // sendData_checkDuplicates(event, event->String2.substring(0, 22)); + sendData(event); + } + } + break; + } + + case PLUGIN_GET_CONFIG_VALUE: + { + const String command = parseString(string, 1); + + P094_data_struct *P094_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != P094_data) { + if (equals(command, F("getfiltermd5"))) { + // to output a MD5 of the currently active filters. + string = P094_data->getFiltersMD5(); + success = true; + } else if (equals(command, F("getfilterenabled"))) { + string = P094_GET_INTERVAL_FILTER; + success = true; + } else if (equals(command, F("getmuteenabled"))) { + string = P094_GET_MUTE_MESSAGES; + success = true; + } + } + break; + } + + + case PLUGIN_WRITE: { + const String cmd = parseString(string, 1); + const String subcmd = parseString(string, 2); + + if (cmd.startsWith(F("culreader"))) { + P094_data_struct *P094_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + if (equals(subcmd, F("enablefilter"))) { + // culreader,enablefilter + P094_SET_INTERVAL_FILTER(1); + success = true; + } else if (equals(subcmd, F("disablefilter"))) { + // culreader,disablefilter + P094_SET_INTERVAL_FILTER(0); + success = true; + } else if (equals(subcmd, F("mute"))) { + // culreader,mute + P094_SET_MUTE_MESSAGES(1); + success = true; + } else if (equals(subcmd, F("unmute"))) { + // culreader,unmute + P094_SET_MUTE_MESSAGES(0); + success = true; + } else if ((nullptr != P094_data)) { + if (equals(cmd, F("culreader_write")) || + equals(subcmd, F("write"))) { + // culreader,write, + String param1 = parseStringKeepCase(string, 2); + parseSystemVariables(param1, false); + P094_data->sendString(param1); + addLogMove(LOG_LEVEL_INFO, param1); + success = true; + } else if (equals(subcmd, F("dumpstats"))) { + // culreader,dumpstats + P094_data->prepare_dump_stats(); + success = true; + } else if (equals(subcmd, F("clearfilters"))) { + // culreader,clearfilters + P094_data->clearFilters(); + success = true; + } else if (equals(subcmd, F("savefilters"))) { + // culreader,savefilters + P094_data->saveFilters(event); + SaveSettings(); + success = true; + } else if (equals(subcmd, F("addfilter"))) { + // culreader,addfilter, + // Examples for a filter definition + // EBZ.02.12345678;all + // *.02.*;15m + // TCH.44.*;once + // *.*.*;5m + success = true; + P094_data->addFilter(event, parseString(string, 3)); + } else if (equals(subcmd, F("setfilters"))) { + // culreader,setfilters,|...| + // Examples for a filter definition + // culreader,setfilters,EBZ.02.12345678;all|*.02.*;15m|TCH.44.*;once|*.*.*;5m + success = true; + P094_data->clearFilters(); + P094_NR_FILTERS = 0; + const String argument = parseString(string, 3); + + if (!argument.isEmpty()) { + int argNr = 1; + + while (argNr > 0) { + const String filter = parseString(argument, argNr, '|'); + + if (!filter.isEmpty()) + { + P094_data->addFilter(event, filter); + ++argNr; + } else { + argNr = 0; + } + } + } + P094_data->saveFilters(event); + SaveSettings(); + } + } + + if (success) { + Plugin_094_setFlags(event); + } + } + + + break; + } +# ifdef USES_ESPEASY_NOW + case PLUGIN_FILTEROUT_CONTROLLER_DATA: + { + // event->String1 => topic; + // event->String2 => payload; + if (Settings.TaskDeviceEnabled[event->TaskIndex]) { + const bool fromCUL = false; + + if (!Plugin_094_match_all(event->TaskIndex, event->String2, event->String1, fromCUL)) { + // Inverse as we check for filtering 'out' the messages. + success = true; + } + } + + break; + } +# endif // ifdef USES_ESPEASY_NOW + } + return success; +} + +bool Plugin_094_match_all(taskIndex_t taskIndex, const String& received, const String& source, bool fromCUL) +{ + P094_data_struct *P094_data = + static_cast(getPluginTaskData(taskIndex)); + + if ((nullptr == P094_data)) { + return false; + } + + if (P094_data->disableFilterWindowActive()) { + addLog(LOG_LEVEL_INFO, F("CUL Reader: Disable Filter Window active")); + return true; + } + + mBusPacket_t packet; + bool res = P094_data->parsePacket(received, packet); + + + # ifdef ESP8266 + + if (res && fromCUL) { + # endif // ifdef ESP8266 + + // Only collect stats from the actual CUL receiver, not when processing forwarded packets. + // On ESP8266: only collect stats on the filtered nodes or else we will likely run out of memory + P094_data->collect_stats_add(packet, source); + # ifdef ESP8266 +} + + # endif // ifdef ESP8266 + + return res; +} + +void Plugin_094_setFlags(struct EventStruct *event) +{ + P094_data_struct *P094_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != P094_data) { + P094_data->setFlags( + P094_DISABLE_WINDOW_TIME_MS, + P094_GET_INTERVAL_FILTER, + P094_GET_MUTE_MESSAGES, + P094_GET_COLLECT_STATS); + } +} + +void P094_html_show_matchForms(struct EventStruct *event) { + addFormNumericBox(F("Filter Off Window after send"), + F("disableTime"), + P094_DISABLE_WINDOW_TIME_MS, + 0, + 60000); + addUnit(F("msec")); + addFormNote(F("0 = Do not turn off filter after sending to the connected device.")); + + addFormNumericBox( + F("Nr Filters"), + F("nrfilters"), + P094_NR_FILTERS, + 0, + P094_MAX_NR_FILTERS); + + + P094_data_struct *P094_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + const bool localAllocated = nullptr == P094_data; + + if (localAllocated) { + P094_data = new (std::nothrow) P094_data_struct(); + + if (nullptr != P094_data) { + P094_data->loadFilters(event, P094_NR_FILTERS); + } + } + + if ((nullptr != P094_data)) { + P094_data->WebformLoadFilters(P094_NR_FILTERS); + + if (localAllocated) { + delete P094_data; + } + } +} + +void P094_html_show_stats(struct EventStruct *event) { + P094_data_struct *P094_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + if ((nullptr == P094_data) || !P094_data->isInitialized()) { + return; + } + + P094_data->html_show_interval_filter_stats(); + + P094_data->html_show_mBus_stats(); + + { + addRowLabel(F("Current Sentence")); + addHtml(P094_data->peekSentence()); + } + + { + addRowLabel(F("Sentences (pass/fail)")); + uint32_t success, error, length_last; + P094_data->getSentencesReceived(success, error, length_last); + addHtmlInt(success); + addHtml('/'); + addHtmlInt(error); + addRowLabel(F("Length Last Sentence")); + addHtmlInt(length_last); + } +} + +#endif // USES_P094 diff --git a/src/check_defines_custom.h b/src/check_defines_custom.h new file mode 100644 index 0000000000..02e33737b2 --- /dev/null +++ b/src/check_defines_custom.h @@ -0,0 +1,76 @@ +#ifndef CHECK_DEFINES_CUSTOM_H +#define CHECK_DEFINES_CUSTOM_H + +/** + * Check if any of the now renamed #define variables is still used and output a warning during compilation + * + * Changelog: + * ---------- + * 2022-07-26 tonhuisman: Initial checks, disable formatting by Uncrustify as it messes up the url's + */ + +/* *INDENT-OFF* */ + +#ifdef USE_DOWNLOAD +# warning "Custom.h has '#define USE_DOWNLOAD' to be replaced with '#define FEATURE_DOWNLOAD 1', see https://github.com/letscontrolit/ESPEasy/pull/4153" +# define FEATURE_DOWNLOAD 1 // Define correct alternative +# undef USE_DOWNLOAD // remove +#endif // ifdef USE_DOWNLOAD + +#ifdef USE_CUSTOM_PROVISIONING +# warning "Custom.h has '#define USE_CUSTOM_PROVISIONING' to be replaced with '#define FEATURE_CUSTOM_PROVISIONING 1', see https://github.com/letscontrolit/ESPEasy/pull/4153" +# define FEATURE_CUSTOM_PROVISIONING 1 +# undef USE_CUSTOM_PROVISIONING +#endif // ifdef USE_CUSTOM_PROVISIONING + +#ifdef USE_SETTINGS_ARCHIVE +# warning "Custom.h has '#define USE_SETTINGS_ARCHIVE' to be replaced with '#define FEATURE_SETTINGS_ARCHIVE 1', see https://github.com/letscontrolit/ESPEasy/pull/4153" +# define FEATURE_SETTINGS_ARCHIVE 1 +# undef USE_SETTINGS_ARCHIVE +#endif // ifdef USE_SETTINGS_ARCHIVE + +#ifdef USE_TRIGONOMETRIC_FUNCTIONS_RULES +# warning "Custom.h has '#define USE_TRIGONOMETRIC_FUNCTIONS_RULES' to be replaced with '#define FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES 1', see https://github.com/letscontrolit/ESPEasy/pull/4153" +# define FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES 1 +# undef USE_TRIGONOMETRIC_FUNCTIONS_RULES +#endif // ifdef USE_TRIGONOMETRIC_FUNCTIONS_RULES + +#if defined(FEATURE_I2CMULTIPLEXER) && (2 - FEATURE_I2CMULTIPLEXER - 2 == 4) // 'Defined but empty' check +# warning "Custom.h has '#define FEATURE_I2CMULTIPLEXER' to be replaced with '#define FEATURE_I2CMULTIPLEXER 1', see https://github.com/letscontrolit/ESPEasy/pull/4153" +# undef FEATURE_I2CMULTIPLEXER +# define FEATURE_I2CMULTIPLEXER 1 +#endif // if defined(FEATURE_I2CMULTIPLEXER) && (2-FEATURE_I2CMULTIPLEXER-2 == 4) + +#ifdef USE_SSDP +# warning "Custom.h has '#define USE_SSDP' to be replaced with '#define FEATURE_SSDP 1', see https://github.com/letscontrolit/ESPEasy/pull/4153" +# define FEATURE_SSDP 1 +# undef USE_SSDP +#endif // ifdef USE_SSDP + +#ifdef USE_TIMING_STATS +# warning "Custom.h has '#define USE_TIMING_STATS' to be replaced with '#define FEATURE_TIMING_STATS 1', see https://github.com/letscontrolit/ESPEasy/pull/4153" +# define FEATURE_TIMING_STATS 1 +# undef USE_TIMING_STATS +#endif // ifdef USE_TIMING_STATS + +#ifdef USE_EXT_RTC +# warning "Custom.h has '#define USE_EXT_RTC' to be replaced with '#define FEATURE_EXT_RTC 1', see https://github.com/letscontrolit/ESPEasy/pull/4153" +# define FEATURE_EXT_RTC 1 +# undef USE_EXT_RTC +#endif // ifdef USE_EXT_RTC + +#ifdef ENABLE_TOOLTIPS +# warning "Custom.h has '#define ENABLE_TOOLTIPS' to be replaced with '#define FEATURE_TOOLTIPS 1', see https://github.com/letscontrolit/ESPEasy/pull/4153" +# define FEATURE_TOOLTIPS 1 +# undef ENABLE_TOOLTIPS +#endif // ifdef ENABLE_TOOLTIPS + +#ifdef HAS_ETHERNET +# warning "Custom.h has '#define HAS_ETHERNET' to be replaced with '#define FEATURE_ETHERNET 1', see https://github.com/letscontrolit/ESPEasy/pull/4153" +# define FEATURE_ETHERNET 1 +# undef HAS_ETHERNET +#endif // ifdef HAS_ETHERNET + +/* *INDENT-ON* */ + +#endif // ifndef CHECK_DEFINES_CUSTOM_H diff --git a/src/src/Commands/ESPEasy_Now_cmd.cpp b/src/src/Commands/ESPEasy_Now_cmd.cpp new file mode 100644 index 0000000000..63a862908f --- /dev/null +++ b/src/src/Commands/ESPEasy_Now_cmd.cpp @@ -0,0 +1,25 @@ +#include "../Commands/ESPEasy_Now_cmd.h" + + +#include "../../ESPEasy_common.h" + +#ifdef USES_ESPEASY_NOW + +#include "../Commands/Common.h" +#include "../Globals/ESPEasy_now_handler.h" + +const __FlashStringHelper * Command_ESPEasy_Now_Disable(struct EventStruct *event, const char *Line) +{ + temp_disable_EspEasy_now_timer = millis() + (5*60*1000); + ESPEasy_now_handler.end(); + return return_command_success_flashstr(); +} + +const __FlashStringHelper * Command_ESPEasy_Now_Enable(struct EventStruct *event, const char *Line) +{ + // Do not set to 0, but to some moment that will be considered passed when checked the next time. + temp_disable_EspEasy_now_timer = millis(); + return return_command_success_flashstr(); +} + +#endif // ifdef USES_ESPEASY_NOW diff --git a/src/src/Commands/ESPEasy_Now_cmd.h b/src/src/Commands/ESPEasy_Now_cmd.h new file mode 100644 index 0000000000..7e718c1200 --- /dev/null +++ b/src/src/Commands/ESPEasy_Now_cmd.h @@ -0,0 +1,19 @@ +#ifndef COMMANDS_ESPEASY_NOW_CMD_H +#define COMMANDS_ESPEASY_NOW_CMD_H + +#include "../../ESPEasy_common.h" + +#ifdef USES_ESPEASY_NOW + +#include "../Globals/ESPEasy_now_state.h" +#include + + +const __FlashStringHelper * Command_ESPEasy_Now_Disable(struct EventStruct *event, + const char *Line); +const __FlashStringHelper * Command_ESPEasy_Now_Enable(struct EventStruct *event, + const char *Line); + +#endif // ifdef USES_ESPEASY_NOW + +#endif // COMMANDS_ESPEASY_NOW_CMD_H diff --git a/src/src/Commands/InternalCommands.cpp b/src/src/Commands/InternalCommands.cpp index dce54f5a3e..118a646073 100644 --- a/src/src/Commands/InternalCommands.cpp +++ b/src/src/Commands/InternalCommands.cpp @@ -1,498 +1,504 @@ -#include "../Commands/InternalCommands.h" - -#include "../../ESPEasy_common.h" - -#include "../../_Plugin_Helper.h" -#include "../Globals/Settings.h" - -#if FEATURE_BLYNK -# include "../Commands/Blynk.h" -# include "../Commands/Blynk_c015.h" -#endif // if FEATURE_BLYNK - -#include "../Commands/Common.h" -#include "../Commands/Controller.h" -#include "../Commands/Diagnostic.h" -#include "../Commands/GPIO.h" -#include "../Commands/HTTP.h" -#include "../Commands/InternalCommands_decoder.h" -#include "../Commands/i2c.h" - -#if FEATURE_MQTT -# include "../Commands/MQTT.h" -#endif // if FEATURE_MQTT - -#include "../Commands/Networks.h" -#if FEATURE_NOTIFIER -# include "../Commands/Notifications.h" -#endif // if FEATURE_NOTIFIER -#if FEATURE_DALLAS_HELPER && FEATURE_COMMAND_OWSCAN -#include "../Commands/OneWire.h" -#endif // if FEATURE_DALLAS_HELPER && FEATURE_COMMAND_OWSCAN -#include "../Commands/Provisioning.h" -#include "../Commands/RTC.h" -#include "../Commands/Rules.h" -#include "../Commands/SDCARD.h" -#include "../Commands/Settings.h" -#if FEATURE_SERVO -# include "../Commands/Servo.h" -#endif // if FEATURE_SERVO -#include "../Commands/System.h" -#include "../Commands/Tasks.h" -#include "../Commands/Time.h" -#include "../Commands/Timer.h" -#include "../Commands/UPD.h" -#include "../Commands/wd.h" -#include "../Commands/WiFi.h" - -#include "../DataStructs/TimingStats.h" - -#include "../ESPEasyCore/ESPEasy_Log.h" - -#include "../Helpers/Misc.h" -#include "../Helpers/StringConverter.h" -#include "../Helpers/StringParser.h" - - -bool checkNrArguments(const char *cmd, const String& Line, int nrArguments) { - if (nrArguments < 0) { return true; } - - // 0 arguments means argument on pos1 is valid (the command) and argpos 2 should not be there. - if (HasArgv(Line.c_str(), nrArguments + 2)) { - #ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_ERROR)) { - String log; - - if (reserve_special(log, 128)) { - log += F("Too many arguments: cmd="); - log += cmd; - - if (nrArguments < 1) { - log += Line; - } else { - // Check for one more argument than allowed, since we apparently have one. - bool done = false; - int i = 1; - - while (!done) { - String parameter; - - if (i == nrArguments) { - parameter = tolerantParseStringKeepCase(Line, i + 1); - } else { - parameter = parseStringKeepCase(Line, i + 1); - } - done = parameter.isEmpty(); - - if (!done) { - if (i <= nrArguments) { - if (Settings.TolerantLastArgParse() && (i == nrArguments)) { - log += F(" (fixed)"); - } - log += F(" Arg"); - } else { - log += F(" ExtraArg"); - } - log += i; - log += '='; - log += parameter; - } - ++i; - } - } - log += F(" lineLength="); - log += Line.length(); - addLogMove(LOG_LEVEL_ERROR, log); - } - addLogMove(LOG_LEVEL_ERROR, strformat(F("Line: _%s_"), Line.c_str())); - - addLogMove(LOG_LEVEL_ERROR, concat(Settings.TolerantLastArgParse() ? - F("Command executed, but may fail.") : F("Command not executed!"), - F(" See: https://github.com/letscontrolit/ESPEasy/issues/2724"))); - } - #endif // ifndef BUILD_NO_DEBUG - - if (Settings.TolerantLastArgParse()) { - return true; - } - return false; - } - return true; -} - -bool checkSourceFlags(EventValueSource::Enum source, EventValueSourceGroup::Enum group) { - if (EventValueSource::partOfGroup(source, group)) { - return true; - } - addLog(LOG_LEVEL_ERROR, return_incorrect_source()); - return false; -} - -command_case_data::command_case_data(const char *cmd, struct EventStruct *event, const char *line) : - cmd(cmd), event(event), line(line) -{ - cmd_lc = cmd; - cmd_lc.toLowerCase(); -} - -InternalCommands::InternalCommands(const char *cmd, struct EventStruct *event, const char *line) - : _data(cmd, event, line) {} - - -// Wrapper to reduce generated code by macro -bool InternalCommands::do_command_case_all(command_function_fs pFunc, - int nrArguments) -{ - return do_command_case(_data, pFunc, nrArguments, EventValueSourceGroup::Enum::ALL); -} - -bool InternalCommands::do_command_case_all(command_function pFunc, - int nrArguments) -{ - return do_command_case(_data, pFunc, nrArguments, EventValueSourceGroup::Enum::ALL); -} - -// Wrapper to reduce generated code by macro -bool InternalCommands::do_command_case_all_restricted(command_function_fs pFunc, - int nrArguments) -{ - return do_command_case(_data, pFunc, nrArguments, EventValueSourceGroup::Enum::RESTRICTED); -} - -bool InternalCommands::do_command_case_all_restricted(command_function pFunc, - int nrArguments) -{ - return do_command_case(_data, pFunc, nrArguments, EventValueSourceGroup::Enum::RESTRICTED); -} - -bool do_command_case_check(command_case_data & data, - int nrArguments, - EventValueSourceGroup::Enum group) -{ - // The data struct is re-used on each attempt to process an internal command. - // Re-initialize the only two members that may have been altered by a previous call. - data.retval = false; - free_string(data.status); - - if (!checkSourceFlags(data.event->Source, group)) { - data.status = return_incorrect_source(); - return false; - } - - // FIXME TD-er: Do not check nr arguments from MQTT source. - // See https://github.com/letscontrolit/ESPEasy/issues/3344 - // C005 does recreate command partly from topic and published message - // e.g. ESP_Easy/Bathroom_pir_env/GPIO/14 with data 0 or 1 - // This only allows for 2 parameters, but some commands need more arguments (default to "0") - const bool mustCheckNrArguments = data.event->Source != EventValueSource::Enum::VALUE_SOURCE_MQTT; - - if (mustCheckNrArguments) { - if (!checkNrArguments(data.cmd, data.line, nrArguments)) { - data.status = return_incorrect_nr_arguments(); - - // data.retval = false; - return true; // Command is handled - } - } - data.retval = true; // Mark the command should be executed. - return true; // Command is handled -} - -bool InternalCommands::do_command_case(command_case_data & data, - command_function_fs pFunc, - int nrArguments, - EventValueSourceGroup::Enum group) -{ - if (do_command_case_check(data, nrArguments, group)) { - // It has been handled, check if we need to execute it. - // FIXME TD-er: Must change command function signature to use const String& - START_TIMER; - data.status = pFunc(data.event, data.line.c_str()); - STOP_TIMER(COMMAND_EXEC_INTERNAL); - return true; - } - return false; -} - -bool InternalCommands::do_command_case(command_case_data & data, - command_function pFunc, - int nrArguments, - EventValueSourceGroup::Enum group) -{ - if (do_command_case_check(data, nrArguments, group)) { - // It has been handled, check if we need to execute it. - // FIXME TD-er: Must change command function signature to use const String& - START_TIMER; - data.status = pFunc(data.event, data.line.c_str()); - STOP_TIMER(COMMAND_EXEC_INTERNAL); - return true; - } - return false; -} - -bool InternalCommands::executeInternalCommand() -{ - // Simple macro to match command to function call. - - // EventValueSourceGroup::Enum::ALL - #define COMMAND_CASE_A(C, NARGS) \ - do_command_case_all(&C, NARGS); break; - - // EventValueSourceGroup::Enum::RESTRICTED - #define COMMAND_CASE_R(C, NARGS) \ - do_command_case_all_restricted(&C, NARGS); break; - - - const ESPEasy_cmd_e cmd = match_ESPEasy_internal_command(_data.cmd_lc); - - _data.retval = false; - - if (cmd == ESPEasy_cmd_e::NotMatched) { - return false; - } - - // FIXME TD-er: Should we execute command when number of arguments is wrong? - - // FIXME TD-er: must determine nr arguments where NARGS is set to -1 - switch (cmd) { - case ESPEasy_cmd_e::accessinfo: COMMAND_CASE_A(Command_AccessInfo_Ls, 0); // Network Command - case ESPEasy_cmd_e::asyncevent: COMMAND_CASE_A(Command_Rules_Async_Events, -1); // Rule.h -#ifndef BUILD_NO_DIAGNOSTIC_COMMANDS - case ESPEasy_cmd_e::background: COMMAND_CASE_R(Command_Background, 1); // Diagnostic.h -#endif // ifndef BUILD_NO_DIAGNOSTIC_COMMANDS -#ifdef USES_C012 - case ESPEasy_cmd_e::blynkget: COMMAND_CASE_A(Command_Blynk_Get, -1); -#endif // ifdef USES_C012 -#ifdef USES_C015 - case ESPEasy_cmd_e::blynkset: COMMAND_CASE_R(Command_Blynk_Set, -1); -#endif // ifdef USES_C015 - case ESPEasy_cmd_e::build: COMMAND_CASE_A(Command_Settings_Build, 1); // Settings.h - case ESPEasy_cmd_e::clearaccessblock: COMMAND_CASE_R(Command_AccessInfo_Clear, 0); // Network Command - case ESPEasy_cmd_e::clearpassword: COMMAND_CASE_R(Command_Settings_Password_Clear, 1); // Settings.h - case ESPEasy_cmd_e::clearrtcram: COMMAND_CASE_R(Command_RTC_Clear, 0); // RTC.h -#ifdef ESP8266 - case ESPEasy_cmd_e::clearsdkwifi: COMMAND_CASE_R(Command_System_Erase_SDK_WiFiconfig, 0); // System.h - case ESPEasy_cmd_e::clearwifirfcal: COMMAND_CASE_R(Command_System_Erase_RFcal, 0); // System.h -#endif // ifdef ESP8266 - case ESPEasy_cmd_e::config: COMMAND_CASE_R(Command_Task_RemoteConfig, -1); // Tasks.h - case ESPEasy_cmd_e::controllerdisable: COMMAND_CASE_R(Command_Controller_Disable, 1); // Controller.h - case ESPEasy_cmd_e::controllerenable: COMMAND_CASE_R(Command_Controller_Enable, 1); // Controller.h - case ESPEasy_cmd_e::datetime: COMMAND_CASE_R(Command_DateTime, 2); // Time.h - case ESPEasy_cmd_e::debug: COMMAND_CASE_R(Command_Debug, 1); // Diagnostic.h - case ESPEasy_cmd_e::dec: COMMAND_CASE_A(Command_Rules_Dec, -1); // Rules.h - case ESPEasy_cmd_e::deepsleep: COMMAND_CASE_R(Command_System_deepSleep, 1); // System.h - case ESPEasy_cmd_e::delay: COMMAND_CASE_R(Command_Delay, 1); // Timers.h -#if FEATURE_PLUGIN_PRIORITY - case ESPEasy_cmd_e::disableprioritytask: COMMAND_CASE_R(Command_PriorityTask_Disable, 1); // Tasks.h -#endif // if FEATURE_PLUGIN_PRIORITY - case ESPEasy_cmd_e::dns: COMMAND_CASE_R(Command_DNS, 1); // Network Command - case ESPEasy_cmd_e::dst: COMMAND_CASE_R(Command_DST, 1); // Time.h -#if FEATURE_ETHERNET - case ESPEasy_cmd_e::ethphyadr: COMMAND_CASE_R(Command_ETH_Phy_Addr, 1); // Network Command - case ESPEasy_cmd_e::ethpinmdc: COMMAND_CASE_R(Command_ETH_Pin_mdc, 1); // Network Command - case ESPEasy_cmd_e::ethpinmdio: COMMAND_CASE_R(Command_ETH_Pin_mdio, 1); // Network Command - case ESPEasy_cmd_e::ethpinpower: COMMAND_CASE_R(Command_ETH_Pin_power, 1); // Network Command - case ESPEasy_cmd_e::ethphytype: COMMAND_CASE_R(Command_ETH_Phy_Type, 1); // Network Command - case ESPEasy_cmd_e::ethclockmode: COMMAND_CASE_R(Command_ETH_Clock_Mode, 1); // Network Command - case ESPEasy_cmd_e::ethip: COMMAND_CASE_R(Command_ETH_IP, 1); // Network Command - case ESPEasy_cmd_e::ethgateway: COMMAND_CASE_R(Command_ETH_Gateway, 1); // Network Command - case ESPEasy_cmd_e::ethsubnet: COMMAND_CASE_R(Command_ETH_Subnet, 1); // Network Command - case ESPEasy_cmd_e::ethdns: COMMAND_CASE_R(Command_ETH_DNS, 1); // Network Command - case ESPEasy_cmd_e::ethdisconnect: COMMAND_CASE_A(Command_ETH_Disconnect, 0); // Network Command - case ESPEasy_cmd_e::ethwifimode: COMMAND_CASE_R(Command_ETH_Wifi_Mode, 1); // Network Command -#endif // FEATURE_ETHERNET - case ESPEasy_cmd_e::erasesdkwifi: COMMAND_CASE_R(Command_WiFi_Erase, 0); // WiFi.h - case ESPEasy_cmd_e::event: COMMAND_CASE_A(Command_Rules_Events, -1); // Rule.h - case ESPEasy_cmd_e::executerules: COMMAND_CASE_A(Command_Rules_Execute, -1); // Rule.h - case ESPEasy_cmd_e::factoryreset: COMMAND_CASE_R(Command_Settings_FactoryReset, 0); // Settings.h - case ESPEasy_cmd_e::gateway: COMMAND_CASE_R(Command_Gateway, 1); // Network Command - case ESPEasy_cmd_e::gpio: COMMAND_CASE_A(Command_GPIO, 2); // Gpio.h - case ESPEasy_cmd_e::gpiotoggle: COMMAND_CASE_A(Command_GPIO_Toggle, 1); // Gpio.h - case ESPEasy_cmd_e::hiddenssid: COMMAND_CASE_R(Command_Wifi_HiddenSSID, 1); // wifi.h - case ESPEasy_cmd_e::i2cscanner: COMMAND_CASE_R(Command_i2c_Scanner, -1); // i2c.h - case ESPEasy_cmd_e::inc: COMMAND_CASE_A(Command_Rules_Inc, -1); // Rules.h - case ESPEasy_cmd_e::ip: COMMAND_CASE_R(Command_IP, 1); // Network Command -#if FEATURE_USE_IPV6 - case ESPEasy_cmd_e::ip6: COMMAND_CASE_A(Command_show_all_IP6, 0); // Network Command -#endif -#ifndef BUILD_NO_DIAGNOSTIC_COMMANDS - case ESPEasy_cmd_e::jsonportstatus: COMMAND_CASE_A(Command_JSONPortStatus, -1); // Diagnostic.h -#endif // ifndef BUILD_NO_DIAGNOSTIC_COMMANDS - case ESPEasy_cmd_e::let: COMMAND_CASE_A(Command_Rules_Let, 2); // Rules.h - #if FEATURE_STRING_VARIABLES - case ESPEasy_cmd_e::letstr: COMMAND_CASE_A(Command_Rules_LetStr, 2); // Rules.h - #endif // if FEATURE_STRING_VARIABLES - case ESPEasy_cmd_e::load: COMMAND_CASE_A(Command_Settings_Load, 0); // Settings.h - case ESPEasy_cmd_e::logentry: COMMAND_CASE_A(Command_logentry, -1); // Diagnostic.h - case ESPEasy_cmd_e::looptimerset: COMMAND_CASE_A(Command_Loop_Timer_Set, 3); // Timers.h - case ESPEasy_cmd_e::looptimerset_ms: COMMAND_CASE_A(Command_Loop_Timer_Set_ms, 3); // Timers.h - case ESPEasy_cmd_e::looptimersetandrun: COMMAND_CASE_A(Command_Loop_Timer_SetAndRun, 3); // Timers.h - case ESPEasy_cmd_e::looptimersetandrun_ms: COMMAND_CASE_A(Command_Loop_Timer_SetAndRun_ms, 3); // Timers.h - case ESPEasy_cmd_e::longpulse: COMMAND_CASE_A(Command_GPIO_LongPulse, 5); // GPIO.h - case ESPEasy_cmd_e::longpulse_ms: COMMAND_CASE_A(Command_GPIO_LongPulse_Ms, 5); // GPIO.h -#ifndef BUILD_NO_DIAGNOSTIC_COMMANDS - case ESPEasy_cmd_e::logportstatus: COMMAND_CASE_A(Command_logPortStatus, 0); // Diagnostic.h - case ESPEasy_cmd_e::lowmem: COMMAND_CASE_A(Command_Lowmem, 0); // Diagnostic.h -#endif // ifndef BUILD_NO_DIAGNOSTIC_COMMANDS -#ifdef USES_P009 - case ESPEasy_cmd_e::mcpgpio: COMMAND_CASE_A(Command_GPIO, 2); // Gpio.h - case ESPEasy_cmd_e::mcpgpiorange: COMMAND_CASE_A(Command_GPIO_McpGPIORange, -1); // Gpio.h - case ESPEasy_cmd_e::mcpgpiopattern: COMMAND_CASE_A(Command_GPIO_McpGPIOPattern, -1); // Gpio.h - case ESPEasy_cmd_e::mcpgpiotoggle: COMMAND_CASE_A(Command_GPIO_Toggle, 1); // Gpio.h - case ESPEasy_cmd_e::mcplongpulse: COMMAND_CASE_A(Command_GPIO_LongPulse, 3); // GPIO.h - case ESPEasy_cmd_e::mcplongpulse_ms: COMMAND_CASE_A(Command_GPIO_LongPulse_Ms, 3); // GPIO.h - case ESPEasy_cmd_e::mcpmode: COMMAND_CASE_A(Command_GPIO_Mode, 2); // Gpio.h - case ESPEasy_cmd_e::mcpmoderange: COMMAND_CASE_A(Command_GPIO_ModeRange, 3); // Gpio.h - case ESPEasy_cmd_e::mcppulse: COMMAND_CASE_A(Command_GPIO_Pulse, 3); // GPIO.h -#endif // ifdef USES_P009 - case ESPEasy_cmd_e::monitor: COMMAND_CASE_A(Command_GPIO_Monitor, 2); // GPIO.h - case ESPEasy_cmd_e::monitorrange: COMMAND_CASE_A(Command_GPIO_MonitorRange, 3); // GPIO.h -#ifndef BUILD_NO_DIAGNOSTIC_COMMANDS - case ESPEasy_cmd_e::malloc: COMMAND_CASE_A(Command_Malloc, 1); // Diagnostic.h - case ESPEasy_cmd_e::meminfo: COMMAND_CASE_A(Command_MemInfo, 0); // Diagnostic.h - case ESPEasy_cmd_e::meminfodetail: COMMAND_CASE_A(Command_MemInfo_detail, 0); // Diagnostic.h -#endif // ifndef BUILD_NO_DIAGNOSTIC_COMMANDS - case ESPEasy_cmd_e::name: COMMAND_CASE_R(Command_Settings_Name, 1); // Settings.h - case ESPEasy_cmd_e::nosleep: COMMAND_CASE_R(Command_System_NoSleep, 1); // System.h -#if FEATURE_NOTIFIER - case ESPEasy_cmd_e::notify: COMMAND_CASE_R(Command_Notifications_Notify, -1); // Notifications.h -#endif // if FEATURE_NOTIFIER - case ESPEasy_cmd_e::ntphost: COMMAND_CASE_R(Command_NTPHost, 1); // Time.h -#if FEATURE_DALLAS_HELPER && FEATURE_COMMAND_OWSCAN - case ESPEasy_cmd_e::owscan: COMMAND_CASE_R(Command_OneWire_Owscan, -1); // OneWire.h -#endif // if FEATURE_DALLAS_HELPER && FEATURE_COMMAND_OWSCAN -#ifdef USES_P019 - case ESPEasy_cmd_e::pcfgpio: COMMAND_CASE_A(Command_GPIO, 2); // Gpio.h - case ESPEasy_cmd_e::pcfgpiorange: COMMAND_CASE_A(Command_GPIO_PcfGPIORange, -1); // Gpio.h - case ESPEasy_cmd_e::pcfgpiopattern: COMMAND_CASE_A(Command_GPIO_PcfGPIOPattern, -1); // Gpio.h - case ESPEasy_cmd_e::pcfgpiotoggle: COMMAND_CASE_A(Command_GPIO_Toggle, 1); // Gpio.h - case ESPEasy_cmd_e::pcflongpulse: COMMAND_CASE_A(Command_GPIO_LongPulse, 3); // GPIO.h - case ESPEasy_cmd_e::pcflongpulse_ms: COMMAND_CASE_A(Command_GPIO_LongPulse_Ms, 3); // GPIO.h - case ESPEasy_cmd_e::pcfmode: COMMAND_CASE_A(Command_GPIO_Mode, 2); // Gpio.h - case ESPEasy_cmd_e::pcfmoderange: COMMAND_CASE_A(Command_GPIO_ModeRange, 3); // Gpio.h ************ - case ESPEasy_cmd_e::pcfpulse: COMMAND_CASE_A(Command_GPIO_Pulse, 3); // GPIO.h -#endif // ifdef USES_P019 - case ESPEasy_cmd_e::password: COMMAND_CASE_R(Command_Settings_Password, 1); // Settings.h -#if FEATURE_POST_TO_HTTP - case ESPEasy_cmd_e::posttohttp: COMMAND_CASE_A(Command_HTTP_PostToHTTP, -1); // HTTP.h -#endif // if FEATURE_POST_TO_HTTP -#if FEATURE_CUSTOM_PROVISIONING - case ESPEasy_cmd_e::provision: COMMAND_CASE_A(Command_Provisioning_Dispatcher, -1); // Provisioning.h -# ifdef PLUGIN_BUILD_MAX_ESP32 - - // FIXME DEPRECATED: Fallback for temporary backward compatibility - case ESPEasy_cmd_e::provisionconfig: COMMAND_CASE_A(Command_Provisioning_ConfigFallback, 0); // Provisioning.h - case ESPEasy_cmd_e::provisionsecurity: COMMAND_CASE_A(Command_Provisioning_SecurityFallback, 0); // Provisioning.h -# if FEATURE_NOTIFIER - case ESPEasy_cmd_e::provisionnotification: COMMAND_CASE_A(Command_Provisioning_NotificationFallback, 0); // Provisioning.h -# endif // if FEATURE_NOTIFIER - case ESPEasy_cmd_e::provisionprovision: COMMAND_CASE_A(Command_Provisioning_ProvisionFallback, 0); // Provisioning.h - case ESPEasy_cmd_e::provisionrules: COMMAND_CASE_A(Command_Provisioning_RulesFallback, 1); // Provisioning.h - case ESPEasy_cmd_e::provisionfirmware: COMMAND_CASE_A(Command_Provisioning_FirmwareFallback, 1); // Provisioning.h -# endif // ifdef PLUGIN_BUILD_MAX_ESP32 -#endif // if FEATURE_CUSTOM_PROVISIONING - case ESPEasy_cmd_e::pulse: COMMAND_CASE_A(Command_GPIO_Pulse, 3); // GPIO.h -#if FEATURE_MQTT - case ESPEasy_cmd_e::publish: COMMAND_CASE_A(Command_MQTT_Publish, -1); // MQTT.h - case ESPEasy_cmd_e::publishr: COMMAND_CASE_A(Command_MQTT_PublishR, -1); // MQTT.h -#endif // if FEATURE_MQTT -#if FEATURE_PUT_TO_HTTP - case ESPEasy_cmd_e::puttohttp: COMMAND_CASE_A(Command_HTTP_PutToHTTP, -1); // HTTP.h -#endif // if FEATURE_PUT_TO_HTTP - case ESPEasy_cmd_e::pwm: COMMAND_CASE_A(Command_GPIO_PWM, 4); // GPIO.h - case ESPEasy_cmd_e::reboot: COMMAND_CASE_A(Command_System_Reboot, 0); // System.h - case ESPEasy_cmd_e::resetflashwritecounter: COMMAND_CASE_A(Command_RTC_resetFlashWriteCounter, 0); // RTC.h - case ESPEasy_cmd_e::restart: COMMAND_CASE_A(Command_System_Reboot, 0); // System.h - case ESPEasy_cmd_e::rtttl: COMMAND_CASE_A(Command_GPIO_RTTTL, -1); // GPIO.h - case ESPEasy_cmd_e::rules: COMMAND_CASE_A(Command_Rules_UseRules, 1); // Rule.h - case ESPEasy_cmd_e::save: COMMAND_CASE_R(Command_Settings_Save, 0); // Settings.h - case ESPEasy_cmd_e::scheduletaskrun: COMMAND_CASE_A(Command_ScheduleTask_Run, 2); // Tasks.h - -#if FEATURE_SD - case ESPEasy_cmd_e::sdcard: COMMAND_CASE_R(Command_SD_LS, 0); // SDCARDS.h - case ESPEasy_cmd_e::sdremove: COMMAND_CASE_R(Command_SD_Remove, 1); // SDCARDS.h -#endif // if FEATURE_SD - -#if FEATURE_ESPEASY_P2P - - // FIXME TD-er: These send commands, can we determine the nr of arguments? - case ESPEasy_cmd_e::sendto: COMMAND_CASE_A(Command_UPD_SendTo, 2); // UDP.h -#endif // if FEATURE_ESPEASY_P2P -#if FEATURE_SEND_TO_HTTP - case ESPEasy_cmd_e::sendtohttp: COMMAND_CASE_A(Command_HTTP_SendToHTTP, 3); // HTTP.h -#endif // FEATURE_SEND_TO_HTTP - case ESPEasy_cmd_e::sendtoudp: COMMAND_CASE_A(Command_UDP_SendToUPD, 3); // UDP.h - case ESPEasy_cmd_e::sendtoudpmix: COMMAND_CASE_A(Command_UDP_SendToUPDMix, 3); // UDP.h -#ifndef BUILD_NO_DIAGNOSTIC_COMMANDS - case ESPEasy_cmd_e::serialfloat: COMMAND_CASE_R(Command_SerialFloat, 0); // Diagnostic.h -#endif // ifndef BUILD_NO_DIAGNOSTIC_COMMANDS - case ESPEasy_cmd_e::settings: COMMAND_CASE_R(Command_Settings_Print, 0); // Settings.h -#if FEATURE_SERVO - case ESPEasy_cmd_e::servo: COMMAND_CASE_A(Command_Servo, 3); // Servo.h -#endif // if FEATURE_SERVO - - case ESPEasy_cmd_e::status: COMMAND_CASE_A(Command_GPIO_Status, 2); // GPIO.h - case ESPEasy_cmd_e::subnet: COMMAND_CASE_R(Command_Subnet, 1); // Network Command -#if FEATURE_MQTT - case ESPEasy_cmd_e::subscribe: COMMAND_CASE_A(Command_MQTT_Subscribe, 1); // MQTT.h -#endif // if FEATURE_MQTT -#ifndef BUILD_NO_DIAGNOSTIC_COMMANDS - case ESPEasy_cmd_e::sysload: COMMAND_CASE_A(Command_SysLoad, 0); // Diagnostic.h -#endif // ifndef BUILD_NO_DIAGNOSTIC_COMMANDS - case ESPEasy_cmd_e::taskclear: COMMAND_CASE_R(Command_Task_Clear, 1); // Tasks.h - case ESPEasy_cmd_e::taskclearall: COMMAND_CASE_R(Command_Task_ClearAll, 0); // Tasks.h - case ESPEasy_cmd_e::taskdisable: COMMAND_CASE_R(Command_Task_Disable, 1); // Tasks.h - case ESPEasy_cmd_e::taskenable: COMMAND_CASE_R(Command_Task_Enable, 1); // Tasks.h - case ESPEasy_cmd_e::taskrun: COMMAND_CASE_A(Command_Task_Run, 1); // Tasks.h - case ESPEasy_cmd_e::taskrunat: COMMAND_CASE_A(Command_Task_Run, 2); // Tasks.h - case ESPEasy_cmd_e::taskvalueset: COMMAND_CASE_A(Command_Task_ValueSet, 3); // Tasks.h - #if FEATURE_STRING_VARIABLES - case ESPEasy_cmd_e::taskvaluesetderived: COMMAND_CASE_A(Command_Task_ValueSetDerived, 5); // Tasks.h - case ESPEasy_cmd_e::taskvaluesetpresentation: COMMAND_CASE_A(Command_Task_ValueSetPresentation, 3); // Tasks.h - #endif // if FEATURE_STRING_VARIABLES - case ESPEasy_cmd_e::taskvaluetoggle: COMMAND_CASE_A(Command_Task_ValueToggle, 2); // Tasks.h - case ESPEasy_cmd_e::taskvaluesetandrun: COMMAND_CASE_A(Command_Task_ValueSetAndRun, 3); // Tasks.h - case ESPEasy_cmd_e::timerpause: COMMAND_CASE_A(Command_Timer_Pause, 1); // Timers.h - case ESPEasy_cmd_e::timerresume: COMMAND_CASE_A(Command_Timer_Resume, 1); // Timers.h - case ESPEasy_cmd_e::timerset: COMMAND_CASE_A(Command_Timer_Set, 2); // Timers.h - case ESPEasy_cmd_e::timerset_ms: COMMAND_CASE_A(Command_Timer_Set_ms, 2); // Timers.h - case ESPEasy_cmd_e::timezone: COMMAND_CASE_R(Command_TimeZone, 1); // Time.h - case ESPEasy_cmd_e::tone: COMMAND_CASE_A(Command_GPIO_Tone, 3); // GPIO.h - case ESPEasy_cmd_e::udpport: COMMAND_CASE_R(Command_UDP_Port, 1); // UDP.h -#if FEATURE_ESPEASY_P2P - case ESPEasy_cmd_e::udptest: COMMAND_CASE_R(Command_UDP_Test, 2); // UDP.h -#endif // if FEATURE_ESPEASY_P2P - case ESPEasy_cmd_e::unit: COMMAND_CASE_R(Command_Settings_Unit, 1); // Settings.h - case ESPEasy_cmd_e::unmonitor: COMMAND_CASE_A(Command_GPIO_UnMonitor, 2); // GPIO.h - case ESPEasy_cmd_e::unmonitorrange: COMMAND_CASE_A(Command_GPIO_UnMonitorRange, 3); // GPIO.h - case ESPEasy_cmd_e::usentp: COMMAND_CASE_R(Command_useNTP, 1); // Time.h -#ifndef LIMIT_BUILD_SIZE - case ESPEasy_cmd_e::wdconfig: COMMAND_CASE_R(Command_WD_Config, 3); // WD.h - case ESPEasy_cmd_e::wdread: COMMAND_CASE_R(Command_WD_Read, 2); // WD.h -#endif // ifndef LIMIT_BUILD_SIZE - - case ESPEasy_cmd_e::wifiallowap: COMMAND_CASE_R(Command_Wifi_AllowAP, 0); // WiFi.h - case ESPEasy_cmd_e::wifiapmode: COMMAND_CASE_R(Command_Wifi_APMode, 0); // WiFi.h - case ESPEasy_cmd_e::wificonnect: COMMAND_CASE_A(Command_Wifi_Connect, 0); // WiFi.h - case ESPEasy_cmd_e::wifidisconnect: COMMAND_CASE_A(Command_Wifi_Disconnect, 0); // WiFi.h - case ESPEasy_cmd_e::wifikey: COMMAND_CASE_R(Command_Wifi_Key, 1); // WiFi.h - case ESPEasy_cmd_e::wifikey2: COMMAND_CASE_R(Command_Wifi_Key2, 1); // WiFi.h - case ESPEasy_cmd_e::wifimode: COMMAND_CASE_R(Command_Wifi_Mode, 1); // WiFi.h - case ESPEasy_cmd_e::wifiscan: COMMAND_CASE_R(Command_Wifi_Scan, 0); // WiFi.h - case ESPEasy_cmd_e::wifissid: COMMAND_CASE_R(Command_Wifi_SSID, 1); // WiFi.h - case ESPEasy_cmd_e::wifissid2: COMMAND_CASE_R(Command_Wifi_SSID2, 1); // WiFi.h - case ESPEasy_cmd_e::wifistamode: COMMAND_CASE_R(Command_Wifi_STAMode, 0); // WiFi.h - - - case ESPEasy_cmd_e::NotMatched: - return false; - - // Do not add default: here - // The compiler will then warn when a command is not included - } - - #undef COMMAND_CASE_R - #undef COMMAND_CASE_A - return _data.retval; -} +#include "../Commands/InternalCommands.h" + +#include "../../ESPEasy_common.h" + +#include "../../_Plugin_Helper.h" +#include "../Globals/Settings.h" + +#if FEATURE_BLYNK +# include "../Commands/Blynk.h" +# include "../Commands/Blynk_c015.h" +#endif // if FEATURE_BLYNK + +#include "../Commands/Common.h" +#include "../Commands/Controller.h" +#include "../Commands/Diagnostic.h" +#include "../Commands/ESPEasy_Now_cmd.h" +#include "../Commands/GPIO.h" +#include "../Commands/HTTP.h" +#include "../Commands/InternalCommands_decoder.h" +#include "../Commands/i2c.h" + +#if FEATURE_MQTT +# include "../Commands/MQTT.h" +#endif // if FEATURE_MQTT + +#include "../Commands/Networks.h" +#if FEATURE_NOTIFIER +# include "../Commands/Notifications.h" +#endif // if FEATURE_NOTIFIER +#if FEATURE_DALLAS_HELPER && FEATURE_COMMAND_OWSCAN +#include "../Commands/OneWire.h" +#endif // if FEATURE_DALLAS_HELPER && FEATURE_COMMAND_OWSCAN +#include "../Commands/Provisioning.h" +#include "../Commands/RTC.h" +#include "../Commands/Rules.h" +#include "../Commands/SDCARD.h" +#include "../Commands/Settings.h" +#if FEATURE_SERVO +# include "../Commands/Servo.h" +#endif // if FEATURE_SERVO +#include "../Commands/System.h" +#include "../Commands/Tasks.h" +#include "../Commands/Time.h" +#include "../Commands/Timer.h" +#include "../Commands/UPD.h" +#include "../Commands/wd.h" +#include "../Commands/WiFi.h" + +#include "../DataStructs/TimingStats.h" + +#include "../ESPEasyCore/ESPEasy_Log.h" + +#include "../Helpers/Misc.h" +#include "../Helpers/StringConverter.h" +#include "../Helpers/StringParser.h" + + +bool checkNrArguments(const char *cmd, const String& Line, int nrArguments) { + if (nrArguments < 0) { return true; } + + // 0 arguments means argument on pos1 is valid (the command) and argpos 2 should not be there. + if (HasArgv(Line.c_str(), nrArguments + 2)) { + #ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_ERROR)) { + String log; + + if (reserve_special(log, 128)) { + log += F("Too many arguments: cmd="); + log += cmd; + + if (nrArguments < 1) { + log += Line; + } else { + // Check for one more argument than allowed, since we apparently have one. + bool done = false; + int i = 1; + + while (!done) { + String parameter; + + if (i == nrArguments) { + parameter = tolerantParseStringKeepCase(Line, i + 1); + } else { + parameter = parseStringKeepCase(Line, i + 1); + } + done = parameter.isEmpty(); + + if (!done) { + if (i <= nrArguments) { + if (Settings.TolerantLastArgParse() && (i == nrArguments)) { + log += F(" (fixed)"); + } + log += F(" Arg"); + } else { + log += F(" ExtraArg"); + } + log += i; + log += '='; + log += parameter; + } + ++i; + } + } + log += F(" lineLength="); + log += Line.length(); + addLogMove(LOG_LEVEL_ERROR, log); + } + addLogMove(LOG_LEVEL_ERROR, strformat(F("Line: _%s_"), Line.c_str())); + + addLogMove(LOG_LEVEL_ERROR, concat(Settings.TolerantLastArgParse() ? + F("Command executed, but may fail.") : F("Command not executed!"), + F(" See: https://github.com/letscontrolit/ESPEasy/issues/2724"))); + } + #endif // ifndef BUILD_NO_DEBUG + + if (Settings.TolerantLastArgParse()) { + return true; + } + return false; + } + return true; +} + +bool checkSourceFlags(EventValueSource::Enum source, EventValueSourceGroup::Enum group) { + if (EventValueSource::partOfGroup(source, group)) { + return true; + } + addLog(LOG_LEVEL_ERROR, return_incorrect_source()); + return false; +} + +command_case_data::command_case_data(const char *cmd, struct EventStruct *event, const char *line) : + cmd(cmd), event(event), line(line) +{ + cmd_lc = cmd; + cmd_lc.toLowerCase(); +} + +InternalCommands::InternalCommands(const char *cmd, struct EventStruct *event, const char *line) + : _data(cmd, event, line) {} + + +// Wrapper to reduce generated code by macro +bool InternalCommands::do_command_case_all(command_function_fs pFunc, + int nrArguments) +{ + return do_command_case(_data, pFunc, nrArguments, EventValueSourceGroup::Enum::ALL); +} + +bool InternalCommands::do_command_case_all(command_function pFunc, + int nrArguments) +{ + return do_command_case(_data, pFunc, nrArguments, EventValueSourceGroup::Enum::ALL); +} + +// Wrapper to reduce generated code by macro +bool InternalCommands::do_command_case_all_restricted(command_function_fs pFunc, + int nrArguments) +{ + return do_command_case(_data, pFunc, nrArguments, EventValueSourceGroup::Enum::RESTRICTED); +} + +bool InternalCommands::do_command_case_all_restricted(command_function pFunc, + int nrArguments) +{ + return do_command_case(_data, pFunc, nrArguments, EventValueSourceGroup::Enum::RESTRICTED); +} + +bool do_command_case_check(command_case_data & data, + int nrArguments, + EventValueSourceGroup::Enum group) +{ + // The data struct is re-used on each attempt to process an internal command. + // Re-initialize the only two members that may have been altered by a previous call. + data.retval = false; + free_string(data.status); + + if (!checkSourceFlags(data.event->Source, group)) { + data.status = return_incorrect_source(); + return false; + } + + // FIXME TD-er: Do not check nr arguments from MQTT source. + // See https://github.com/letscontrolit/ESPEasy/issues/3344 + // C005 does recreate command partly from topic and published message + // e.g. ESP_Easy/Bathroom_pir_env/GPIO/14 with data 0 or 1 + // This only allows for 2 parameters, but some commands need more arguments (default to "0") + const bool mustCheckNrArguments = data.event->Source != EventValueSource::Enum::VALUE_SOURCE_MQTT; + + if (mustCheckNrArguments) { + if (!checkNrArguments(data.cmd, data.line, nrArguments)) { + data.status = return_incorrect_nr_arguments(); + + // data.retval = false; + return true; // Command is handled + } + } + data.retval = true; // Mark the command should be executed. + return true; // Command is handled +} + +bool InternalCommands::do_command_case(command_case_data & data, + command_function_fs pFunc, + int nrArguments, + EventValueSourceGroup::Enum group) +{ + if (do_command_case_check(data, nrArguments, group)) { + // It has been handled, check if we need to execute it. + // FIXME TD-er: Must change command function signature to use const String& + START_TIMER; + data.status = pFunc(data.event, data.line.c_str()); + STOP_TIMER(COMMAND_EXEC_INTERNAL); + return true; + } + return false; +} + +bool InternalCommands::do_command_case(command_case_data & data, + command_function pFunc, + int nrArguments, + EventValueSourceGroup::Enum group) +{ + if (do_command_case_check(data, nrArguments, group)) { + // It has been handled, check if we need to execute it. + // FIXME TD-er: Must change command function signature to use const String& + START_TIMER; + data.status = pFunc(data.event, data.line.c_str()); + STOP_TIMER(COMMAND_EXEC_INTERNAL); + return true; + } + return false; +} + +bool InternalCommands::executeInternalCommand() +{ + // Simple macro to match command to function call. + + // EventValueSourceGroup::Enum::ALL + #define COMMAND_CASE_A(C, NARGS) \ + do_command_case_all(&C, NARGS); break; + + // EventValueSourceGroup::Enum::RESTRICTED + #define COMMAND_CASE_R(C, NARGS) \ + do_command_case_all_restricted(&C, NARGS); break; + + + const ESPEasy_cmd_e cmd = match_ESPEasy_internal_command(_data.cmd_lc); + + _data.retval = false; + + if (cmd == ESPEasy_cmd_e::NotMatched) { + return false; + } + + // FIXME TD-er: Should we execute command when number of arguments is wrong? + + // FIXME TD-er: must determine nr arguments where NARGS is set to -1 + + switch (cmd) { + case ESPEasy_cmd_e::accessinfo: COMMAND_CASE_A(Command_AccessInfo_Ls, 0); // Network Command + case ESPEasy_cmd_e::asyncevent: COMMAND_CASE_A(Command_Rules_Async_Events, -1); // Rule.h +#ifndef BUILD_NO_DIAGNOSTIC_COMMANDS + case ESPEasy_cmd_e::background: COMMAND_CASE_R(Command_Background, 1); // Diagnostic.h +#endif // ifndef BUILD_NO_DIAGNOSTIC_COMMANDS +#ifdef USES_C012 + case ESPEasy_cmd_e::blynkget: COMMAND_CASE_A(Command_Blynk_Get, -1); +#endif // ifdef USES_C012 +#ifdef USES_C015 + case ESPEasy_cmd_e::blynkset: COMMAND_CASE_R(Command_Blynk_Set, -1); +#endif // ifdef USES_C015 + case ESPEasy_cmd_e::build: COMMAND_CASE_A(Command_Settings_Build, 1); // Settings.h + case ESPEasy_cmd_e::clearaccessblock: COMMAND_CASE_R(Command_AccessInfo_Clear, 0); // Network Command + case ESPEasy_cmd_e::clearpassword: COMMAND_CASE_R(Command_Settings_Password_Clear, 1); // Settings.h + case ESPEasy_cmd_e::clearrtcram: COMMAND_CASE_R(Command_RTC_Clear, 0); // RTC.h +#ifdef ESP8266 + case ESPEasy_cmd_e::clearsdkwifi: COMMAND_CASE_R(Command_System_Erase_SDK_WiFiconfig, 0); // System.h + case ESPEasy_cmd_e::clearwifirfcal: COMMAND_CASE_R(Command_System_Erase_RFcal, 0); // System.h +#endif // ifdef ESP8266 + case ESPEasy_cmd_e::config: COMMAND_CASE_R(Command_Task_RemoteConfig, -1); // Tasks.h + case ESPEasy_cmd_e::controllerdisable: COMMAND_CASE_R(Command_Controller_Disable, 1); // Controller.h + case ESPEasy_cmd_e::controllerenable: COMMAND_CASE_R(Command_Controller_Enable, 1); // Controller.h + case ESPEasy_cmd_e::datetime: COMMAND_CASE_R(Command_DateTime, 2); // Time.h + case ESPEasy_cmd_e::debug: COMMAND_CASE_R(Command_Debug, 1); // Diagnostic.h + case ESPEasy_cmd_e::dec: COMMAND_CASE_A(Command_Rules_Dec, -1); // Rules.h + case ESPEasy_cmd_e::deepsleep: COMMAND_CASE_R(Command_System_deepSleep, 1); // System.h + case ESPEasy_cmd_e::delay: COMMAND_CASE_R(Command_Delay, 1); // Timers.h +#if FEATURE_PLUGIN_PRIORITY + case ESPEasy_cmd_e::disableprioritytask: COMMAND_CASE_R(Command_PriorityTask_Disable, 1); // Tasks.h +#endif // if FEATURE_PLUGIN_PRIORITY + case ESPEasy_cmd_e::dns: COMMAND_CASE_R(Command_DNS, 1); // Network Command + case ESPEasy_cmd_e::dst: COMMAND_CASE_R(Command_DST, 1); // Time.h +#if FEATURE_ETHERNET + case ESPEasy_cmd_e::ethphyadr: COMMAND_CASE_R(Command_ETH_Phy_Addr, 1); // Network Command + case ESPEasy_cmd_e::ethpinmdc: COMMAND_CASE_R(Command_ETH_Pin_mdc, 1); // Network Command + case ESPEasy_cmd_e::ethpinmdio: COMMAND_CASE_R(Command_ETH_Pin_mdio, 1); // Network Command + case ESPEasy_cmd_e::ethpinpower: COMMAND_CASE_R(Command_ETH_Pin_power, 1); // Network Command + case ESPEasy_cmd_e::ethphytype: COMMAND_CASE_R(Command_ETH_Phy_Type, 1); // Network Command + case ESPEasy_cmd_e::ethclockmode: COMMAND_CASE_R(Command_ETH_Clock_Mode, 1); // Network Command + case ESPEasy_cmd_e::ethip: COMMAND_CASE_R(Command_ETH_IP, 1); // Network Command + case ESPEasy_cmd_e::ethgateway: COMMAND_CASE_R(Command_ETH_Gateway, 1); // Network Command + case ESPEasy_cmd_e::ethsubnet: COMMAND_CASE_R(Command_ETH_Subnet, 1); // Network Command + case ESPEasy_cmd_e::ethdns: COMMAND_CASE_R(Command_ETH_DNS, 1); // Network Command + case ESPEasy_cmd_e::ethdisconnect: COMMAND_CASE_A(Command_ETH_Disconnect, 0); // Network Command + case ESPEasy_cmd_e::ethwifimode: COMMAND_CASE_R(Command_ETH_Wifi_Mode, 1); // Network Command +#endif // FEATURE_ETHERNET + case ESPEasy_cmd_e::erasesdkwifi: COMMAND_CASE_R(Command_WiFi_Erase, 0); // WiFi.h +#ifdef USES_ESPEASY_NOW + case ESPEasy_cmd_e::espeasynowdisable: COMMAND_CASE_R(Command_ESPEasy_Now_Disable, 0); // ESPEasy_Now_cmd.h + case ESPEasy_cmd_e::espeasynowenable: COMMAND_CASE_R(Command_ESPEasy_Now_Enable, 0); // ESPEasy_Now_cmd.h +#endif + case ESPEasy_cmd_e::event: COMMAND_CASE_A(Command_Rules_Events, -1); // Rule.h + case ESPEasy_cmd_e::executerules: COMMAND_CASE_A(Command_Rules_Execute, -1); // Rule.h + case ESPEasy_cmd_e::factoryreset: COMMAND_CASE_R(Command_Settings_FactoryReset, 0); // Settings.h + case ESPEasy_cmd_e::gateway: COMMAND_CASE_R(Command_Gateway, 1); // Network Command + case ESPEasy_cmd_e::gpio: COMMAND_CASE_A(Command_GPIO, 2); // Gpio.h + case ESPEasy_cmd_e::gpiotoggle: COMMAND_CASE_A(Command_GPIO_Toggle, 1); // Gpio.h + case ESPEasy_cmd_e::hiddenssid: COMMAND_CASE_R(Command_Wifi_HiddenSSID, 1); // wifi.h + case ESPEasy_cmd_e::i2cscanner: COMMAND_CASE_R(Command_i2c_Scanner, -1); // i2c.h + case ESPEasy_cmd_e::inc: COMMAND_CASE_A(Command_Rules_Inc, -1); // Rules.h + case ESPEasy_cmd_e::ip: COMMAND_CASE_R(Command_IP, 1); // Network Command +#if FEATURE_USE_IPV6 + case ESPEasy_cmd_e::ip6: COMMAND_CASE_A(Command_show_all_IP6, 0); // Network Command +#endif +#ifndef BUILD_NO_DIAGNOSTIC_COMMANDS + case ESPEasy_cmd_e::jsonportstatus: COMMAND_CASE_A(Command_JSONPortStatus, -1); // Diagnostic.h +#endif // ifndef BUILD_NO_DIAGNOSTIC_COMMANDS + case ESPEasy_cmd_e::let: COMMAND_CASE_A(Command_Rules_Let, 2); // Rules.h + #if FEATURE_STRING_VARIABLES + case ESPEasy_cmd_e::letstr: COMMAND_CASE_A(Command_Rules_LetStr, 2); // Rules.h + #endif // if FEATURE_STRING_VARIABLES + case ESPEasy_cmd_e::load: COMMAND_CASE_A(Command_Settings_Load, 0); // Settings.h + case ESPEasy_cmd_e::logentry: COMMAND_CASE_A(Command_logentry, -1); // Diagnostic.h + case ESPEasy_cmd_e::looptimerset: COMMAND_CASE_A(Command_Loop_Timer_Set, 3); // Timers.h + case ESPEasy_cmd_e::looptimerset_ms: COMMAND_CASE_A(Command_Loop_Timer_Set_ms, 3); // Timers.h + case ESPEasy_cmd_e::looptimersetandrun: COMMAND_CASE_A(Command_Loop_Timer_SetAndRun, 3); // Timers.h + case ESPEasy_cmd_e::looptimersetandrun_ms: COMMAND_CASE_A(Command_Loop_Timer_SetAndRun_ms, 3); // Timers.h + case ESPEasy_cmd_e::longpulse: COMMAND_CASE_A(Command_GPIO_LongPulse, 5); // GPIO.h + case ESPEasy_cmd_e::longpulse_ms: COMMAND_CASE_A(Command_GPIO_LongPulse_Ms, 5); // GPIO.h +#ifndef BUILD_NO_DIAGNOSTIC_COMMANDS + case ESPEasy_cmd_e::logportstatus: COMMAND_CASE_A(Command_logPortStatus, 0); // Diagnostic.h + case ESPEasy_cmd_e::lowmem: COMMAND_CASE_A(Command_Lowmem, 0); // Diagnostic.h +#endif // ifndef BUILD_NO_DIAGNOSTIC_COMMANDS +#ifdef USES_P009 + case ESPEasy_cmd_e::mcpgpio: COMMAND_CASE_A(Command_GPIO, 2); // Gpio.h + case ESPEasy_cmd_e::mcpgpiorange: COMMAND_CASE_A(Command_GPIO_McpGPIORange, -1); // Gpio.h + case ESPEasy_cmd_e::mcpgpiopattern: COMMAND_CASE_A(Command_GPIO_McpGPIOPattern, -1); // Gpio.h + case ESPEasy_cmd_e::mcpgpiotoggle: COMMAND_CASE_A(Command_GPIO_Toggle, 1); // Gpio.h + case ESPEasy_cmd_e::mcplongpulse: COMMAND_CASE_A(Command_GPIO_LongPulse, 3); // GPIO.h + case ESPEasy_cmd_e::mcplongpulse_ms: COMMAND_CASE_A(Command_GPIO_LongPulse_Ms, 3); // GPIO.h + case ESPEasy_cmd_e::mcpmode: COMMAND_CASE_A(Command_GPIO_Mode, 2); // Gpio.h + case ESPEasy_cmd_e::mcpmoderange: COMMAND_CASE_A(Command_GPIO_ModeRange, 3); // Gpio.h + case ESPEasy_cmd_e::mcppulse: COMMAND_CASE_A(Command_GPIO_Pulse, 3); // GPIO.h +#endif // ifdef USES_P009 + case ESPEasy_cmd_e::monitor: COMMAND_CASE_A(Command_GPIO_Monitor, 2); // GPIO.h + case ESPEasy_cmd_e::monitorrange: COMMAND_CASE_A(Command_GPIO_MonitorRange, 3); // GPIO.h +#ifndef BUILD_NO_DIAGNOSTIC_COMMANDS + case ESPEasy_cmd_e::malloc: COMMAND_CASE_A(Command_Malloc, 1); // Diagnostic.h + case ESPEasy_cmd_e::meminfo: COMMAND_CASE_A(Command_MemInfo, 0); // Diagnostic.h + case ESPEasy_cmd_e::meminfodetail: COMMAND_CASE_A(Command_MemInfo_detail, 0); // Diagnostic.h +#endif // ifndef BUILD_NO_DIAGNOSTIC_COMMANDS + case ESPEasy_cmd_e::name: COMMAND_CASE_R(Command_Settings_Name, 1); // Settings.h + case ESPEasy_cmd_e::nosleep: COMMAND_CASE_R(Command_System_NoSleep, 1); // System.h +#if FEATURE_NOTIFIER + case ESPEasy_cmd_e::notify: COMMAND_CASE_R(Command_Notifications_Notify, -1); // Notifications.h +#endif // if FEATURE_NOTIFIER + case ESPEasy_cmd_e::ntphost: COMMAND_CASE_R(Command_NTPHost, 1); // Time.h +#if FEATURE_DALLAS_HELPER && FEATURE_COMMAND_OWSCAN + case ESPEasy_cmd_e::owscan: COMMAND_CASE_R(Command_OneWire_Owscan, -1); // OneWire.h +#endif // if FEATURE_DALLAS_HELPER && FEATURE_COMMAND_OWSCAN +#ifdef USES_P019 + case ESPEasy_cmd_e::pcfgpio: COMMAND_CASE_A(Command_GPIO, 2); // Gpio.h + case ESPEasy_cmd_e::pcfgpiorange: COMMAND_CASE_A(Command_GPIO_PcfGPIORange, -1); // Gpio.h + case ESPEasy_cmd_e::pcfgpiopattern: COMMAND_CASE_A(Command_GPIO_PcfGPIOPattern, -1); // Gpio.h + case ESPEasy_cmd_e::pcfgpiotoggle: COMMAND_CASE_A(Command_GPIO_Toggle, 1); // Gpio.h + case ESPEasy_cmd_e::pcflongpulse: COMMAND_CASE_A(Command_GPIO_LongPulse, 3); // GPIO.h + case ESPEasy_cmd_e::pcflongpulse_ms: COMMAND_CASE_A(Command_GPIO_LongPulse_Ms, 3); // GPIO.h + case ESPEasy_cmd_e::pcfmode: COMMAND_CASE_A(Command_GPIO_Mode, 2); // Gpio.h + case ESPEasy_cmd_e::pcfmoderange: COMMAND_CASE_A(Command_GPIO_ModeRange, 3); // Gpio.h ************ + case ESPEasy_cmd_e::pcfpulse: COMMAND_CASE_A(Command_GPIO_Pulse, 3); // GPIO.h +#endif // ifdef USES_P019 + case ESPEasy_cmd_e::password: COMMAND_CASE_R(Command_Settings_Password, 1); // Settings.h +#if FEATURE_POST_TO_HTTP + case ESPEasy_cmd_e::posttohttp: COMMAND_CASE_A(Command_HTTP_PostToHTTP, -1); // HTTP.h +#endif // if FEATURE_POST_TO_HTTP +#if FEATURE_CUSTOM_PROVISIONING + case ESPEasy_cmd_e::provision: COMMAND_CASE_A(Command_Provisioning_Dispatcher, -1); // Provisioning.h +# ifdef PLUGIN_BUILD_MAX_ESP32 + + // FIXME DEPRECATED: Fallback for temporary backward compatibility + case ESPEasy_cmd_e::provisionconfig: COMMAND_CASE_A(Command_Provisioning_ConfigFallback, 0); // Provisioning.h + case ESPEasy_cmd_e::provisionsecurity: COMMAND_CASE_A(Command_Provisioning_SecurityFallback, 0); // Provisioning.h +# if FEATURE_NOTIFIER + case ESPEasy_cmd_e::provisionnotification: COMMAND_CASE_A(Command_Provisioning_NotificationFallback, 0); // Provisioning.h +# endif // if FEATURE_NOTIFIER + case ESPEasy_cmd_e::provisionprovision: COMMAND_CASE_A(Command_Provisioning_ProvisionFallback, 0); // Provisioning.h + case ESPEasy_cmd_e::provisionrules: COMMAND_CASE_A(Command_Provisioning_RulesFallback, 1); // Provisioning.h + case ESPEasy_cmd_e::provisionfirmware: COMMAND_CASE_A(Command_Provisioning_FirmwareFallback, 1); // Provisioning.h +# endif // ifdef PLUGIN_BUILD_MAX_ESP32 +#endif // if FEATURE_CUSTOM_PROVISIONING + case ESPEasy_cmd_e::pulse: COMMAND_CASE_A(Command_GPIO_Pulse, 3); // GPIO.h +#if FEATURE_MQTT + case ESPEasy_cmd_e::publish: COMMAND_CASE_A(Command_MQTT_Publish, -1); // MQTT.h + case ESPEasy_cmd_e::publishr: COMMAND_CASE_A(Command_MQTT_PublishR, -1); // MQTT.h +#endif // if FEATURE_MQTT +#if FEATURE_PUT_TO_HTTP + case ESPEasy_cmd_e::puttohttp: COMMAND_CASE_A(Command_HTTP_PutToHTTP, -1); // HTTP.h +#endif // if FEATURE_PUT_TO_HTTP + case ESPEasy_cmd_e::pwm: COMMAND_CASE_A(Command_GPIO_PWM, 4); // GPIO.h + case ESPEasy_cmd_e::reboot: COMMAND_CASE_A(Command_System_Reboot, 0); // System.h + case ESPEasy_cmd_e::resetflashwritecounter: COMMAND_CASE_A(Command_RTC_resetFlashWriteCounter, 0); // RTC.h + case ESPEasy_cmd_e::restart: COMMAND_CASE_A(Command_System_Reboot, 0); // System.h + case ESPEasy_cmd_e::rtttl: COMMAND_CASE_A(Command_GPIO_RTTTL, -1); // GPIO.h + case ESPEasy_cmd_e::rules: COMMAND_CASE_A(Command_Rules_UseRules, 1); // Rule.h + case ESPEasy_cmd_e::save: COMMAND_CASE_R(Command_Settings_Save, 0); // Settings.h + case ESPEasy_cmd_e::scheduletaskrun: COMMAND_CASE_A(Command_ScheduleTask_Run, 2); // Tasks.h + +#if FEATURE_SD + case ESPEasy_cmd_e::sdcard: COMMAND_CASE_R(Command_SD_LS, 0); // SDCARDS.h + case ESPEasy_cmd_e::sdremove: COMMAND_CASE_R(Command_SD_Remove, 1); // SDCARDS.h +#endif // if FEATURE_SD + +#if FEATURE_ESPEASY_P2P + + // FIXME TD-er: These send commands, can we determine the nr of arguments? + case ESPEasy_cmd_e::sendto: COMMAND_CASE_A(Command_UPD_SendTo, 2); // UDP.h +#endif // if FEATURE_ESPEASY_P2P +#if FEATURE_SEND_TO_HTTP + case ESPEasy_cmd_e::sendtohttp: COMMAND_CASE_A(Command_HTTP_SendToHTTP, 3); // HTTP.h +#endif // FEATURE_SEND_TO_HTTP + case ESPEasy_cmd_e::sendtoudp: COMMAND_CASE_A(Command_UDP_SendToUPD, 3); // UDP.h + case ESPEasy_cmd_e::sendtoudpmix: COMMAND_CASE_A(Command_UDP_SendToUPDMix, 3); // UDP.h +#ifndef BUILD_NO_DIAGNOSTIC_COMMANDS + case ESPEasy_cmd_e::serialfloat: COMMAND_CASE_R(Command_SerialFloat, 0); // Diagnostic.h +#endif // ifndef BUILD_NO_DIAGNOSTIC_COMMANDS + case ESPEasy_cmd_e::settings: COMMAND_CASE_R(Command_Settings_Print, 0); // Settings.h +#if FEATURE_SERVO + case ESPEasy_cmd_e::servo: COMMAND_CASE_A(Command_Servo, 3); // Servo.h +#endif // if FEATURE_SERVO + + case ESPEasy_cmd_e::status: COMMAND_CASE_A(Command_GPIO_Status, 2); // GPIO.h + case ESPEasy_cmd_e::subnet: COMMAND_CASE_R(Command_Subnet, 1); // Network Command +#if FEATURE_MQTT + case ESPEasy_cmd_e::subscribe: COMMAND_CASE_A(Command_MQTT_Subscribe, 1); // MQTT.h +#endif // if FEATURE_MQTT +#ifndef BUILD_NO_DIAGNOSTIC_COMMANDS + case ESPEasy_cmd_e::sysload: COMMAND_CASE_A(Command_SysLoad, 0); // Diagnostic.h +#endif // ifndef BUILD_NO_DIAGNOSTIC_COMMANDS + case ESPEasy_cmd_e::taskclear: COMMAND_CASE_R(Command_Task_Clear, 1); // Tasks.h + case ESPEasy_cmd_e::taskclearall: COMMAND_CASE_R(Command_Task_ClearAll, 0); // Tasks.h + case ESPEasy_cmd_e::taskdisable: COMMAND_CASE_R(Command_Task_Disable, 1); // Tasks.h + case ESPEasy_cmd_e::taskenable: COMMAND_CASE_R(Command_Task_Enable, 1); // Tasks.h + case ESPEasy_cmd_e::taskrun: COMMAND_CASE_A(Command_Task_Run, 1); // Tasks.h + case ESPEasy_cmd_e::taskrunat: COMMAND_CASE_A(Command_Task_Run, 2); // Tasks.h + case ESPEasy_cmd_e::taskvalueset: COMMAND_CASE_A(Command_Task_ValueSet, 3); // Tasks.h + #if FEATURE_STRING_VARIABLES + case ESPEasy_cmd_e::taskvaluesetderived: COMMAND_CASE_A(Command_Task_ValueSetDerived, 5); // Tasks.h + case ESPEasy_cmd_e::taskvaluesetpresentation: COMMAND_CASE_A(Command_Task_ValueSetPresentation, 3); // Tasks.h + #endif // if FEATURE_STRING_VARIABLES + case ESPEasy_cmd_e::taskvaluetoggle: COMMAND_CASE_A(Command_Task_ValueToggle, 2); // Tasks.h + case ESPEasy_cmd_e::taskvaluesetandrun: COMMAND_CASE_A(Command_Task_ValueSetAndRun, 3); // Tasks.h + case ESPEasy_cmd_e::timerpause: COMMAND_CASE_A(Command_Timer_Pause, 1); // Timers.h + case ESPEasy_cmd_e::timerresume: COMMAND_CASE_A(Command_Timer_Resume, 1); // Timers.h + case ESPEasy_cmd_e::timerset: COMMAND_CASE_A(Command_Timer_Set, 2); // Timers.h + case ESPEasy_cmd_e::timerset_ms: COMMAND_CASE_A(Command_Timer_Set_ms, 2); // Timers.h + case ESPEasy_cmd_e::timezone: COMMAND_CASE_R(Command_TimeZone, 1); // Time.h + case ESPEasy_cmd_e::tone: COMMAND_CASE_A(Command_GPIO_Tone, 3); // GPIO.h + case ESPEasy_cmd_e::udpport: COMMAND_CASE_R(Command_UDP_Port, 1); // UDP.h +#if FEATURE_ESPEASY_P2P + case ESPEasy_cmd_e::udptest: COMMAND_CASE_R(Command_UDP_Test, 2); // UDP.h +#endif // if FEATURE_ESPEASY_P2P + case ESPEasy_cmd_e::unit: COMMAND_CASE_R(Command_Settings_Unit, 1); // Settings.h + case ESPEasy_cmd_e::unmonitor: COMMAND_CASE_A(Command_GPIO_UnMonitor, 2); // GPIO.h + case ESPEasy_cmd_e::unmonitorrange: COMMAND_CASE_A(Command_GPIO_UnMonitorRange, 3); // GPIO.h + case ESPEasy_cmd_e::usentp: COMMAND_CASE_R(Command_useNTP, 1); // Time.h +#ifndef LIMIT_BUILD_SIZE + case ESPEasy_cmd_e::wdconfig: COMMAND_CASE_R(Command_WD_Config, 3); // WD.h + case ESPEasy_cmd_e::wdread: COMMAND_CASE_R(Command_WD_Read, 2); // WD.h +#endif // ifndef LIMIT_BUILD_SIZE + + case ESPEasy_cmd_e::wifiallowap: COMMAND_CASE_R(Command_Wifi_AllowAP, 0); // WiFi.h + case ESPEasy_cmd_e::wifiapmode: COMMAND_CASE_R(Command_Wifi_APMode, 0); // WiFi.h + case ESPEasy_cmd_e::wificonnect: COMMAND_CASE_A(Command_Wifi_Connect, 0); // WiFi.h + case ESPEasy_cmd_e::wifidisconnect: COMMAND_CASE_A(Command_Wifi_Disconnect, 0); // WiFi.h + case ESPEasy_cmd_e::wifikey: COMMAND_CASE_R(Command_Wifi_Key, 1); // WiFi.h + case ESPEasy_cmd_e::wifikey2: COMMAND_CASE_R(Command_Wifi_Key2, 1); // WiFi.h + case ESPEasy_cmd_e::wifimode: COMMAND_CASE_R(Command_Wifi_Mode, 1); // WiFi.h + case ESPEasy_cmd_e::wifiscan: COMMAND_CASE_R(Command_Wifi_Scan, 0); // WiFi.h + case ESPEasy_cmd_e::wifissid: COMMAND_CASE_R(Command_Wifi_SSID, 1); // WiFi.h + case ESPEasy_cmd_e::wifissid2: COMMAND_CASE_R(Command_Wifi_SSID2, 1); // WiFi.h + case ESPEasy_cmd_e::wifistamode: COMMAND_CASE_R(Command_Wifi_STAMode, 0); // WiFi.h + + + case ESPEasy_cmd_e::NotMatched: + return false; + + // Do not add default: here + // The compiler will then warn when a command is not included + } + + #undef COMMAND_CASE_R + #undef COMMAND_CASE_A + return _data.retval; +} diff --git a/src/src/Commands/InternalCommands_decoder.cpp b/src/src/Commands/InternalCommands_decoder.cpp index 0c6115f6eb..5254a1086d 100644 --- a/src/src/Commands/InternalCommands_decoder.cpp +++ b/src/src/Commands/InternalCommands_decoder.cpp @@ -1,497 +1,501 @@ -#include "../Commands/InternalCommands_decoder.h" - -#include "../DataStructs/TimingStats.h" -#include "../Helpers/StringConverter.h" - -// Keep the order of elements in ESPEasy_cmd_e enum -// the same as in the PROGMEM strings below -// -// The first item in the PROGMEM strings below should be an enum -// which is always included in each build as it is used to set an offset -// to compute the final enum value. -// -// Keep the offset used in match_ESPEasy_internal_command in sync -// when adding new commands - - -const char Internal_commands_ab[] PROGMEM = - "accessinfo|" - "asyncevent|" - "build|" -#ifndef BUILD_NO_DIAGNOSTIC_COMMANDS - "background|" -#endif // ifndef BUILD_NO_DIAGNOSTIC_COMMANDS -#ifdef USES_C012 - "blynkget|" -#endif // #ifdef USES_C012 -#ifdef USES_C015 - "blynkset|" -#endif // #ifdef USES_C015 -; - -#define Int_cmd_c_offset ESPEasy_cmd_e::clearaccessblock -const char Internal_commands_c[] PROGMEM = - "clearaccessblock|" - "clearpassword|" - "clearrtcram|" -#ifdef ESP8266 - "clearsdkwifi|" - "clearwifirfcal|" -#endif // #ifdef ESP8266 - "config|" - "controllerdisable|" - "controllerenable|" -; - -#define Int_cmd_d_offset ESPEasy_cmd_e::datetime -const char Internal_commands_d[] PROGMEM = - "datetime|" - "debug|" - "dec|" - "deepsleep|" - "delay|" -#if FEATURE_PLUGIN_PRIORITY - "disableprioritytask|" -#endif // #if FEATURE_PLUGIN_PRIORITY - "dns|" - "dst|" -; - -#define Int_cmd_e_offset ESPEasy_cmd_e::erasesdkwifi -const char Internal_commands_e[] PROGMEM = - "erasesdkwifi|" - "event|" - "executerules|" -#if FEATURE_ETHERNET - "ethphyadr|" - "ethpinmdc|" - "ethpinmdio|" - "ethpinpower|" - "ethphytype|" - "ethclockmode|" - "ethip|" - "ethgateway|" - "ethsubnet|" - "ethdns|" - "ethdisconnect|" - "ethwifimode|" -#endif // FEATURE_ETHERNET -; - -#define Int_cmd_fghij_offset ESPEasy_cmd_e::factoryreset -const char Internal_commands_fghij[] PROGMEM = - "factoryreset|" - "gateway|" - "gpio|" - "gpiotoggle|" - "hiddenssid|" - "i2cscanner|" - "inc|" - "ip|" -#if FEATURE_USE_IPV6 - "ip6|" -#endif -#ifndef BUILD_NO_DIAGNOSTIC_COMMANDS - "jsonportstatus|" -#endif // ifndef BUILD_NO_DIAGNOSTIC_COMMANDS -; - -#define Int_cmd_l_offset ESPEasy_cmd_e::let -const char Internal_commands_l[] PROGMEM = - "let|" - #if FEATURE_STRING_VARIABLES - "letstr|" - #endif - "load|" - "logentry|" - "looptimerset|" - "looptimerset_ms|" - "looptimersetandrun|" - "looptimersetandrun_ms|" - "longpulse|" - "longpulse_ms|" -#ifndef BUILD_NO_DIAGNOSTIC_COMMANDS - "logportstatus|" - "lowmem|" -#endif // ifndef BUILD_NO_DIAGNOSTIC_COMMANDS -; - -#define Int_cmd_m_offset ESPEasy_cmd_e::monitor -const char Internal_commands_m[] PROGMEM = - "monitor|" - "monitorrange|" -#ifdef USES_P009 - "mcpgpio|" - "mcpgpiorange|" - "mcpgpiopattern|" - "mcpgpiotoggle|" - "mcplongpulse|" - "mcplongpulse_ms|" - "mcpmode|" - "mcpmoderange|" - "mcppulse|" -#endif // #ifdef USES_P009 -#ifndef BUILD_NO_DIAGNOSTIC_COMMANDS - "malloc|" - "meminfo|" - "meminfodetail|" -#endif // ifndef BUILD_NO_DIAGNOSTIC_COMMANDS -; - -#define Int_cmd_no_offset ESPEasy_cmd_e::name -const char Internal_commands_no[] PROGMEM = - "name|" - "nosleep|" -#if FEATURE_NOTIFIER - "notify|" -#endif // #if FEATURE_NOTIFIER - "ntphost|" -#if FEATURE_DALLAS_HELPER && FEATURE_COMMAND_OWSCAN - "owscan|" -#endif // if FEATURE_DALLAS_HELPER && FEATURE_COMMAND_OWSCAN -; - -#define Int_cmd_p_offset ESPEasy_cmd_e::password -const char Internal_commands_p[] PROGMEM = - "password|" -#ifdef USES_P019 - "pcfgpio|" - "pcfgpiorange|" - "pcfgpiopattern|" - "pcfgpiotoggle|" - "pcflongpulse|" - "pcflongpulse_ms|" - "pcfmode|" - "pcfmoderange|" - "pcfpulse|" -#endif // #ifdef USES_P019 -#if FEATURE_POST_TO_HTTP - "posttohttp|" -#endif // #if FEATURE_POST_TO_HTTP -#if FEATURE_CUSTOM_PROVISIONING - "provision|" - # ifdef PLUGIN_BUILD_MAX_ESP32 // FIXME DEPRECATED: Fallback for temporary backward compatibility - "provisionconfig|" - "provisionsecurity|" - # if FEATURE_NOTIFIER - "provisionnotification|" - # endif // #if FEATURE_NOTIFIER - "provisionprovision|" - "provisionrules|" - "provisionfirmware|" - # endif // #ifdef PLUGIN_BUILD_MAX_ESP32 -#endif // #if FEATURE_CUSTOM_PROVISIONING - "pulse|" -#if FEATURE_MQTT - "publish|" - "publishr|" -#endif // #if FEATURE_MQTT -#if FEATURE_PUT_TO_HTTP - "puttohttp|" -#endif // #if FEATURE_PUT_TO_HTTP - "pwm|" -; - -#define Int_cmd_r_offset ESPEasy_cmd_e::reboot -const char Internal_commands_r[] PROGMEM = - "reboot|" - "resetflashwritecounter|" - "restart|" - "rtttl|" - "rules|" -; - -#define Int_cmd_s_offset ESPEasy_cmd_e::save -const char Internal_commands_s[] PROGMEM = - "save|" - "scheduletaskrun|" -#if FEATURE_SD - "sdcard|" - "sdremove|" -#endif // #if FEATURE_SD -#if FEATURE_ESPEASY_P2P - "sendto|" -#endif // #if FEATURE_ESPEASY_P2P -#if FEATURE_SEND_TO_HTTP - "sendtohttp|" -#endif // FEATURE_SEND_TO_HTTP - "sendtoudp|" - "sendtoudpmix|" -#ifndef BUILD_NO_DIAGNOSTIC_COMMANDS - "serialfloat|" -#endif // ifndef BUILD_NO_DIAGNOSTIC_COMMANDS - "settings|" -#if FEATURE_SERVO - "servo|" -#endif // #if FEATURE_SERVO - "status|" - "subnet|" -#if FEATURE_MQTT - "subscribe|" -#endif // #if FEATURE_MQTT -#ifndef BUILD_NO_DIAGNOSTIC_COMMANDS - "sysload|" -#endif // ifndef BUILD_NO_DIAGNOSTIC_COMMANDS -; - -#define Int_cmd_t_offset ESPEasy_cmd_e::taskclear -const char Internal_commands_t[] PROGMEM = - "taskclear|" - "taskclearall|" - "taskdisable|" - "taskenable|" - "taskrun|" - "taskrunat|" - "taskvalueset|" - #if FEATURE_STRING_VARIABLES - "taskvaluesetderived|" - "taskvaluesetpresentation|" - #endif // if FEATURE_STRING_VARIABLES - "taskvaluetoggle|" - "taskvaluesetandrun|" - "timerpause|" - "timerresume|" - "timerset|" - "timerset_ms|" - "timezone|" - "tone|" -; - -#define Int_cmd_u_offset ESPEasy_cmd_e::udpport -const char Internal_commands_u[] PROGMEM = - "udpport|" -#if FEATURE_ESPEASY_P2P - "udptest|" -#endif // #if FEATURE_ESPEASY_P2P - "unit|" - "unmonitor|" - "unmonitorrange|" - "usentp|" -; - -#define Int_cmd_w_offset ESPEasy_cmd_e::wifiallowap -const char Internal_commands_w[] PROGMEM = - "wifiallowap|" - "wifiapmode|" - "wificonnect|" - "wifidisconnect|" - "wifikey|" - "wifikey2|" - "wifimode|" - "wifiscan|" - "wifissid|" - "wifissid2|" - "wifistamode|" -#ifndef LIMIT_BUILD_SIZE - "wdconfig|" - "wdread|" -#endif // ifndef LIMIT_BUILD_SIZE -; - -const char* getInternalCommand_Haystack_Offset(const char firstLetter, int& offset) -{ - const char *haystack = nullptr; - - offset = static_cast(ESPEasy_cmd_e::NotMatched); - - // Keep the offset in sync when adding new commands - switch (firstLetter) - { - case 'a': - case 'b': - offset = 0; - haystack = Internal_commands_ab; - break; - case 'c': - offset = static_cast(Int_cmd_c_offset); - haystack = Internal_commands_c; - break; - case 'd': - offset = static_cast(Int_cmd_d_offset); - haystack = Internal_commands_d; - break; - case 'e': - offset = static_cast(Int_cmd_e_offset); - haystack = Internal_commands_e; - break; - case 'f': - case 'g': - case 'h': - case 'i': - case 'j': - offset = static_cast(Int_cmd_fghij_offset); - haystack = Internal_commands_fghij; - break; - case 'l': - offset = static_cast(Int_cmd_l_offset); - haystack = Internal_commands_l; - break; - case 'm': - offset = static_cast(Int_cmd_m_offset); - haystack = Internal_commands_m; - break; - case 'n': - case 'o': - offset = static_cast(Int_cmd_no_offset); - haystack = Internal_commands_no; - break; - case 'p': - offset = static_cast(Int_cmd_p_offset); - haystack = Internal_commands_p; - break; - case 'r': - offset = static_cast(Int_cmd_r_offset); - haystack = Internal_commands_r; - break; - case 's': - offset = static_cast(Int_cmd_s_offset); - haystack = Internal_commands_s; - break; - case 't': - offset = static_cast(Int_cmd_t_offset); - haystack = Internal_commands_t; - break; - case 'u': - offset = static_cast(Int_cmd_u_offset); - haystack = Internal_commands_u; - break; - case 'w': - offset = static_cast(Int_cmd_w_offset); - haystack = Internal_commands_w; - break; - - default: - return nullptr; - } - return haystack; -} - -ESPEasy_cmd_e match_ESPEasy_internal_command(const String& cmd) -{ - START_TIMER; - ESPEasy_cmd_e res = ESPEasy_cmd_e::NotMatched; - - if (cmd.length() < 2) { - // No commands less than 2 characters - return res; - } - - int offset = 0; - const char *haystack = getInternalCommand_Haystack_Offset(cmd[0], offset); - - if (haystack == nullptr) { -/* - addLog(LOG_LEVEL_ERROR, strformat( - F("Internal command: No Haystack/offset '%s', offset: %d"), - cmd.c_str(), - offset)); -*/ - return res; - } - - - if (haystack != nullptr) { - const int command_i = GetCommandCode(cmd.c_str(), haystack); - - if (command_i != -1) { - res = static_cast(command_i + offset); - } -/* - else { - addLog(LOG_LEVEL_ERROR, strformat( - F("Internal command: Not found '%s', haystack: %s"), - cmd.c_str(), - String(haystack).c_str())); - } -*/ - } - STOP_TIMER(COMMAND_DECODE_INTERNAL); - return res; -} - -#ifndef BUILD_NO_DEBUG -bool toString(ESPEasy_cmd_e cmd, String& str) -{ - if (cmd == ESPEasy_cmd_e::NotMatched) { - return false; - } - char c = 'z'; - bool found = false; - int offset; - const char *haystack = nullptr; - - while (!found && c >= 'a') { - haystack = getInternalCommand_Haystack_Offset(c, offset); - - if (haystack != nullptr) { - if (offset <= static_cast(cmd)) { - found = true; - } - } - --c; - } - - if (found) { - const int index = static_cast(cmd) - offset; -/* - addLog(LOG_LEVEL_INFO, strformat( - F("Internal command: cmd=%d offset=%d index=%d"), - static_cast(cmd), - offset, - index)); -*/ - - if ((index >= 0) && (haystack != nullptr)) { - // Likely long enough to parse any command - char temp[32]{}; - str = GetTextIndexed(temp, sizeof(temp), index, haystack); - return !str.isEmpty(); - } - } - return false; -} - -bool checkAll_internalCommands() -{ - constexpr int last = static_cast(ESPEasy_cmd_e::NotMatched); - bool no_error = true; - - for (int i = 0; i < last; ++i) { - const ESPEasy_cmd_e cmd = static_cast(i); - String cmd_str; - - if (!toString(cmd, cmd_str)) { - no_error = false; -// addLog(LOG_LEVEL_ERROR, concat(F("Internal command: no matching string for "), i)); - } else { - const ESPEasy_cmd_e cmd_found = match_ESPEasy_internal_command(cmd_str); - - if (cmd_found != cmd) { - if (cmd_str.isEmpty()) { - addLog(LOG_LEVEL_ERROR, strformat( - F("Internal command: mismatch (%d)"), i)); - } - else { - addLog(LOG_LEVEL_ERROR, strformat( - F("Internal command: mismatch '%s' (%d)"), - cmd_str.c_str(), - i)); - } - no_error = false; - } - } - } - - if (no_error) { - addLog(LOG_LEVEL_INFO, F("Internal command: All checked OK")); - } -/* - else { - const int index = static_cast(match_ESPEasy_internal_command(F("build"))); - addLog(LOG_LEVEL_ERROR, concat(F("Internal command: index 'build'="), index)); - } -*/ - return no_error; -} - -#endif // ifndef BUILD_NO_DEBUG +#include "../Commands/InternalCommands_decoder.h" + +#include "../DataStructs/TimingStats.h" +#include "../Helpers/StringConverter.h" + +// Keep the order of elements in ESPEasy_cmd_e enum +// the same as in the PROGMEM strings below +// +// The first item in the PROGMEM strings below should be an enum +// which is always included in each build as it is used to set an offset +// to compute the final enum value. +// +// Keep the offset used in match_ESPEasy_internal_command in sync +// when adding new commands + + +const char Internal_commands_ab[] PROGMEM = + "accessinfo|" + "asyncevent|" + "build|" +#ifndef BUILD_NO_DIAGNOSTIC_COMMANDS + "background|" +#endif // ifndef BUILD_NO_DIAGNOSTIC_COMMANDS +#ifdef USES_C012 + "blynkget|" +#endif // #ifdef USES_C012 +#ifdef USES_C015 + "blynkset|" +#endif // #ifdef USES_C015 +; + +#define Int_cmd_c_offset ESPEasy_cmd_e::clearaccessblock +const char Internal_commands_c[] PROGMEM = + "clearaccessblock|" + "clearpassword|" + "clearrtcram|" +#ifdef ESP8266 + "clearsdkwifi|" + "clearwifirfcal|" +#endif // #ifdef ESP8266 + "config|" + "controllerdisable|" + "controllerenable|" +; + +#define Int_cmd_d_offset ESPEasy_cmd_e::datetime +const char Internal_commands_d[] PROGMEM = + "datetime|" + "debug|" + "dec|" + "deepsleep|" + "delay|" +#if FEATURE_PLUGIN_PRIORITY + "disableprioritytask|" +#endif // #if FEATURE_PLUGIN_PRIORITY + "dns|" + "dst|" +; + +#define Int_cmd_e_offset ESPEasy_cmd_e::erasesdkwifi +const char Internal_commands_e[] PROGMEM = + "erasesdkwifi|" +#ifdef USES_ESPEASY_NOW + "espeasynowdisable|" + "espeasynowenable|" +#endif + "event|" + "executerules|" +#if FEATURE_ETHERNET + "ethphyadr|" + "ethpinmdc|" + "ethpinmdio|" + "ethpinpower|" + "ethphytype|" + "ethclockmode|" + "ethip|" + "ethgateway|" + "ethsubnet|" + "ethdns|" + "ethdisconnect|" + "ethwifimode|" +#endif // FEATURE_ETHERNET +; + +#define Int_cmd_fghij_offset ESPEasy_cmd_e::factoryreset +const char Internal_commands_fghij[] PROGMEM = + "factoryreset|" + "gateway|" + "gpio|" + "gpiotoggle|" + "hiddenssid|" + "i2cscanner|" + "inc|" + "ip|" +#if FEATURE_USE_IPV6 + "ip6|" +#endif +#ifndef BUILD_NO_DIAGNOSTIC_COMMANDS + "jsonportstatus|" +#endif // ifndef BUILD_NO_DIAGNOSTIC_COMMANDS +; + +#define Int_cmd_l_offset ESPEasy_cmd_e::let +const char Internal_commands_l[] PROGMEM = + "let|" + #if FEATURE_STRING_VARIABLES + "letstr|" + #endif + "load|" + "logentry|" + "looptimerset|" + "looptimerset_ms|" + "looptimersetandrun|" + "looptimersetandrun_ms|" + "longpulse|" + "longpulse_ms|" +#ifndef BUILD_NO_DIAGNOSTIC_COMMANDS + "logportstatus|" + "lowmem|" +#endif // ifndef BUILD_NO_DIAGNOSTIC_COMMANDS +; + +#define Int_cmd_m_offset ESPEasy_cmd_e::monitor +const char Internal_commands_m[] PROGMEM = + "monitor|" + "monitorrange|" +#ifdef USES_P009 + "mcpgpio|" + "mcpgpiorange|" + "mcpgpiopattern|" + "mcpgpiotoggle|" + "mcplongpulse|" + "mcplongpulse_ms|" + "mcpmode|" + "mcpmoderange|" + "mcppulse|" +#endif // #ifdef USES_P009 +#ifndef BUILD_NO_DIAGNOSTIC_COMMANDS + "malloc|" + "meminfo|" + "meminfodetail|" +#endif // ifndef BUILD_NO_DIAGNOSTIC_COMMANDS +; + +#define Int_cmd_no_offset ESPEasy_cmd_e::name +const char Internal_commands_no[] PROGMEM = + "name|" + "nosleep|" +#if FEATURE_NOTIFIER + "notify|" +#endif // #if FEATURE_NOTIFIER + "ntphost|" +#if FEATURE_DALLAS_HELPER && FEATURE_COMMAND_OWSCAN + "owscan|" +#endif // if FEATURE_DALLAS_HELPER && FEATURE_COMMAND_OWSCAN +; + +#define Int_cmd_p_offset ESPEasy_cmd_e::password +const char Internal_commands_p[] PROGMEM = + "password|" +#ifdef USES_P019 + "pcfgpio|" + "pcfgpiorange|" + "pcfgpiopattern|" + "pcfgpiotoggle|" + "pcflongpulse|" + "pcflongpulse_ms|" + "pcfmode|" + "pcfmoderange|" + "pcfpulse|" +#endif // #ifdef USES_P019 +#if FEATURE_POST_TO_HTTP + "posttohttp|" +#endif // #if FEATURE_POST_TO_HTTP +#if FEATURE_CUSTOM_PROVISIONING + "provision|" + # ifdef PLUGIN_BUILD_MAX_ESP32 // FIXME DEPRECATED: Fallback for temporary backward compatibility + "provisionconfig|" + "provisionsecurity|" + # if FEATURE_NOTIFIER + "provisionnotification|" + # endif // #if FEATURE_NOTIFIER + "provisionprovision|" + "provisionrules|" + "provisionfirmware|" + # endif // #ifdef PLUGIN_BUILD_MAX_ESP32 +#endif // #if FEATURE_CUSTOM_PROVISIONING + "pulse|" +#if FEATURE_MQTT + "publish|" + "publishr|" +#endif // #if FEATURE_MQTT +#if FEATURE_PUT_TO_HTTP + "puttohttp|" +#endif // #if FEATURE_PUT_TO_HTTP + "pwm|" +; + +#define Int_cmd_r_offset ESPEasy_cmd_e::reboot +const char Internal_commands_r[] PROGMEM = + "reboot|" + "resetflashwritecounter|" + "restart|" + "rtttl|" + "rules|" +; + +#define Int_cmd_s_offset ESPEasy_cmd_e::save +const char Internal_commands_s[] PROGMEM = + "save|" + "scheduletaskrun|" +#if FEATURE_SD + "sdcard|" + "sdremove|" +#endif // #if FEATURE_SD +#if FEATURE_ESPEASY_P2P + "sendto|" +#endif // #if FEATURE_ESPEASY_P2P +#if FEATURE_SEND_TO_HTTP + "sendtohttp|" +#endif // FEATURE_SEND_TO_HTTP + "sendtoudp|" + "sendtoudpmix|" +#ifndef BUILD_NO_DIAGNOSTIC_COMMANDS + "serialfloat|" +#endif // ifndef BUILD_NO_DIAGNOSTIC_COMMANDS + "settings|" +#if FEATURE_SERVO + "servo|" +#endif // #if FEATURE_SERVO + "status|" + "subnet|" +#if FEATURE_MQTT + "subscribe|" +#endif // #if FEATURE_MQTT +#ifndef BUILD_NO_DIAGNOSTIC_COMMANDS + "sysload|" +#endif // ifndef BUILD_NO_DIAGNOSTIC_COMMANDS +; + +#define Int_cmd_t_offset ESPEasy_cmd_e::taskclear +const char Internal_commands_t[] PROGMEM = + "taskclear|" + "taskclearall|" + "taskdisable|" + "taskenable|" + "taskrun|" + "taskrunat|" + "taskvalueset|" + #if FEATURE_STRING_VARIABLES + "taskvaluesetderived|" + "taskvaluesetpresentation|" + #endif // if FEATURE_STRING_VARIABLES + "taskvaluetoggle|" + "taskvaluesetandrun|" + "timerpause|" + "timerresume|" + "timerset|" + "timerset_ms|" + "timezone|" + "tone|" +; + +#define Int_cmd_u_offset ESPEasy_cmd_e::udpport +const char Internal_commands_u[] PROGMEM = + "udpport|" +#if FEATURE_ESPEASY_P2P + "udptest|" +#endif // #if FEATURE_ESPEASY_P2P + "unit|" + "unmonitor|" + "unmonitorrange|" + "usentp|" +; + +#define Int_cmd_w_offset ESPEasy_cmd_e::wifiallowap +const char Internal_commands_w[] PROGMEM = + "wifiallowap|" + "wifiapmode|" + "wificonnect|" + "wifidisconnect|" + "wifikey|" + "wifikey2|" + "wifimode|" + "wifiscan|" + "wifissid|" + "wifissid2|" + "wifistamode|" +#ifndef LIMIT_BUILD_SIZE + "wdconfig|" + "wdread|" +#endif // ifndef LIMIT_BUILD_SIZE +; + +const char* getInternalCommand_Haystack_Offset(const char firstLetter, int& offset) +{ + const char *haystack = nullptr; + + offset = static_cast(ESPEasy_cmd_e::NotMatched); + + // Keep the offset in sync when adding new commands + switch (firstLetter) + { + case 'a': + case 'b': + offset = 0; + haystack = Internal_commands_ab; + break; + case 'c': + offset = static_cast(Int_cmd_c_offset); + haystack = Internal_commands_c; + break; + case 'd': + offset = static_cast(Int_cmd_d_offset); + haystack = Internal_commands_d; + break; + case 'e': + offset = static_cast(Int_cmd_e_offset); + haystack = Internal_commands_e; + break; + case 'f': + case 'g': + case 'h': + case 'i': + case 'j': + offset = static_cast(Int_cmd_fghij_offset); + haystack = Internal_commands_fghij; + break; + case 'l': + offset = static_cast(Int_cmd_l_offset); + haystack = Internal_commands_l; + break; + case 'm': + offset = static_cast(Int_cmd_m_offset); + haystack = Internal_commands_m; + break; + case 'n': + case 'o': + offset = static_cast(Int_cmd_no_offset); + haystack = Internal_commands_no; + break; + case 'p': + offset = static_cast(Int_cmd_p_offset); + haystack = Internal_commands_p; + break; + case 'r': + offset = static_cast(Int_cmd_r_offset); + haystack = Internal_commands_r; + break; + case 's': + offset = static_cast(Int_cmd_s_offset); + haystack = Internal_commands_s; + break; + case 't': + offset = static_cast(Int_cmd_t_offset); + haystack = Internal_commands_t; + break; + case 'u': + offset = static_cast(Int_cmd_u_offset); + haystack = Internal_commands_u; + break; + case 'w': + offset = static_cast(Int_cmd_w_offset); + haystack = Internal_commands_w; + break; + + default: + return nullptr; + } + return haystack; +} + +ESPEasy_cmd_e match_ESPEasy_internal_command(const String& cmd) +{ + START_TIMER; + ESPEasy_cmd_e res = ESPEasy_cmd_e::NotMatched; + + if (cmd.length() < 2) { + // No commands less than 2 characters + return res; + } + + int offset = 0; + const char *haystack = getInternalCommand_Haystack_Offset(cmd[0], offset); + + if (haystack == nullptr) { +/* + addLog(LOG_LEVEL_ERROR, strformat( + F("Internal command: No Haystack/offset '%s', offset: %d"), + cmd.c_str(), + offset)); +*/ + return res; + } + + + if (haystack != nullptr) { + const int command_i = GetCommandCode(cmd.c_str(), haystack); + + if (command_i != -1) { + res = static_cast(command_i + offset); + } +/* + else { + addLog(LOG_LEVEL_ERROR, strformat( + F("Internal command: Not found '%s', haystack: %s"), + cmd.c_str(), + String(haystack).c_str())); + } +*/ + } + STOP_TIMER(COMMAND_DECODE_INTERNAL); + return res; +} + +#ifndef BUILD_NO_DEBUG +bool toString(ESPEasy_cmd_e cmd, String& str) +{ + if (cmd == ESPEasy_cmd_e::NotMatched) { + return false; + } + char c = 'z'; + bool found = false; + int offset; + const char *haystack = nullptr; + + while (!found && c >= 'a') { + haystack = getInternalCommand_Haystack_Offset(c, offset); + + if (haystack != nullptr) { + if (offset <= static_cast(cmd)) { + found = true; + } + } + --c; + } + + if (found) { + const int index = static_cast(cmd) - offset; +/* + addLog(LOG_LEVEL_INFO, strformat( + F("Internal command: cmd=%d offset=%d index=%d"), + static_cast(cmd), + offset, + index)); +*/ + + if ((index >= 0) && (haystack != nullptr)) { + // Likely long enough to parse any command + char temp[32]{}; + str = GetTextIndexed(temp, sizeof(temp), index, haystack); + return !str.isEmpty(); + } + } + return false; +} + +bool checkAll_internalCommands() +{ + constexpr int last = static_cast(ESPEasy_cmd_e::NotMatched); + bool no_error = true; + + for (int i = 0; i < last; ++i) { + const ESPEasy_cmd_e cmd = static_cast(i); + String cmd_str; + + if (!toString(cmd, cmd_str)) { + no_error = false; +// addLog(LOG_LEVEL_ERROR, concat(F("Internal command: no matching string for "), i)); + } else { + const ESPEasy_cmd_e cmd_found = match_ESPEasy_internal_command(cmd_str); + + if (cmd_found != cmd) { + if (cmd_str.isEmpty()) { + addLog(LOG_LEVEL_ERROR, strformat( + F("Internal command: mismatch (%d)"), i)); + } + else { + addLog(LOG_LEVEL_ERROR, strformat( + F("Internal command: mismatch '%s' (%d)"), + cmd_str.c_str(), + i)); + } + no_error = false; + } + } + } + + if (no_error) { + addLog(LOG_LEVEL_INFO, F("Internal command: All checked OK")); + } +/* + else { + const int index = static_cast(match_ESPEasy_internal_command(F("build"))); + addLog(LOG_LEVEL_ERROR, concat(F("Internal command: index 'build'="), index)); + } +*/ + return no_error; +} + +#endif // ifndef BUILD_NO_DEBUG diff --git a/src/src/Commands/InternalCommands_decoder.h b/src/src/Commands/InternalCommands_decoder.h index b5c3f5fa32..f0824cdc48 100644 --- a/src/src/Commands/InternalCommands_decoder.h +++ b/src/src/Commands/InternalCommands_decoder.h @@ -1,259 +1,263 @@ -#ifndef COMMANDS_INTERNALCOMMANDS_DECODER_H -#define COMMANDS_INTERNALCOMMANDS_DECODER_H - -#include "../../ESPEasy_common.h" - - -enum class ESPEasy_cmd_e : uint8_t { - accessinfo, - asyncevent, - build, -#ifndef BUILD_NO_DIAGNOSTIC_COMMANDS - background, -#endif // ifndef BUILD_NO_DIAGNOSTIC_COMMANDS -#ifdef USES_C012 - blynkget, -#endif // #ifdef USES_C012 -#ifdef USES_C015 - blynkset, -#endif // #ifdef USES_C015 - - - clearaccessblock, - clearpassword, - clearrtcram, -#ifdef ESP8266 - clearsdkwifi, - clearwifirfcal, -#endif // #ifdef ESP8266 - config, - controllerdisable, - controllerenable, - - datetime, - debug, - dec, - deepsleep, - delay, -#if FEATURE_PLUGIN_PRIORITY - disableprioritytask, -#endif // #if FEATURE_PLUGIN_PRIORITY - dns, - dst, - - erasesdkwifi, - event, - executerules, -#if FEATURE_ETHERNET - ethphyadr, - ethpinmdc, - ethpinmdio, - ethpinpower, - ethphytype, - ethclockmode, - ethip, - ethgateway, - ethsubnet, - ethdns, - ethdisconnect, - ethwifimode, -#endif // FEATURE_ETHERNET - - factoryreset, - - gateway, - gpio, - gpiotoggle, - hiddenssid, - - i2cscanner, - inc, - ip, -#if FEATURE_USE_IPV6 - ip6, -#endif -#ifndef BUILD_NO_DIAGNOSTIC_COMMANDS - jsonportstatus, -#endif // ifndef BUILD_NO_DIAGNOSTIC_COMMANDS - - let, - #if FEATURE_STRING_VARIABLES - letstr, - #endif // if FEATURE_STRING_VARIABLES - load, - logentry, - looptimerset, - looptimerset_ms, - looptimersetandrun, - looptimersetandrun_ms, - longpulse, - longpulse_ms, -#ifndef BUILD_NO_DIAGNOSTIC_COMMANDS - logportstatus, - lowmem, -#endif // ifndef BUILD_NO_DIAGNOSTIC_COMMANDS - - monitor, - monitorrange, -#ifdef USES_P009 - mcpgpio, - mcpgpiorange, - mcpgpiopattern, - mcpgpiotoggle, - mcplongpulse, - mcplongpulse_ms, - mcpmode, - mcpmoderange, - mcppulse, -#endif // #ifdef USES_P009 -#ifndef BUILD_NO_DIAGNOSTIC_COMMANDS - malloc, - meminfo, - meminfodetail, -#endif // ifndef BUILD_NO_DIAGNOSTIC_COMMANDS - - name, - nosleep, -#if FEATURE_NOTIFIER - notify, -#endif // #if FEATURE_NOTIFIER - ntphost, - -#if FEATURE_DALLAS_HELPER && FEATURE_COMMAND_OWSCAN - owscan, -#endif // if FEATURE_DALLAS_HELPER && FEATURE_COMMAND_OWSCAN - - password, -#ifdef USES_P019 - pcfgpio, - pcfgpiorange, - pcfgpiopattern, - pcfgpiotoggle, - pcflongpulse, - pcflongpulse_ms, - pcfmode, - pcfmoderange, - pcfpulse, -#endif // #ifdef USES_P019 -#if FEATURE_POST_TO_HTTP - posttohttp, -#endif // #if FEATURE_POST_TO_HTTP -#if FEATURE_CUSTOM_PROVISIONING - provision, -# ifdef PLUGIN_BUILD_MAX_ESP32 // FIXME DEPRECATED: Fallback for temporary backward compatibility - provisionconfig, - provisionsecurity, -# if FEATURE_NOTIFIER - provisionnotification, -# endif // #if FEATURE_NOTIFIER - provisionprovision, - provisionrules, - provisionfirmware, -# endif // #ifdef PLUGIN_BUILD_MAX_ESP32 -#endif // #if FEATURE_CUSTOM_PROVISIONING - pulse, -#if FEATURE_MQTT - publish, - publishr, -#endif // #if FEATURE_MQTT -#if FEATURE_PUT_TO_HTTP - puttohttp, -#endif // #if FEATURE_PUT_TO_HTTP - pwm, - - reboot, - resetflashwritecounter, - restart, - rtttl, - rules, - - save, - scheduletaskrun, -#if FEATURE_SD - sdcard, - sdremove, -#endif // #if FEATURE_SD -#if FEATURE_ESPEASY_P2P - sendto, -#endif // #if FEATURE_ESPEASY_P2P -#if FEATURE_SEND_TO_HTTP - sendtohttp, -#endif // FEATURE_SEND_TO_HTTP - sendtoudp, - sendtoudpmix, -#ifndef BUILD_NO_DIAGNOSTIC_COMMANDS - serialfloat, -#endif // ifndef BUILD_NO_DIAGNOSTIC_COMMANDS - settings, -#if FEATURE_SERVO - servo, -#endif // #if FEATURE_SERVO - status, - subnet, -#if FEATURE_MQTT - subscribe, -#endif // #if FEATURE_MQTT -#ifndef BUILD_NO_DIAGNOSTIC_COMMANDS - sysload, -#endif // ifndef BUILD_NO_DIAGNOSTIC_COMMANDS - - taskclear, - taskclearall, - taskdisable, - taskenable, - taskrun, - taskrunat, - taskvalueset, - #if FEATURE_STRING_VARIABLES - taskvaluesetderived, - taskvaluesetpresentation, - #endif // if FEATURE_STRING_VARIABLES - taskvaluetoggle, - taskvaluesetandrun, - timerpause, - timerresume, - timerset, - timerset_ms, - timezone, - tone, - - udpport, -#if FEATURE_ESPEASY_P2P - udptest, -#endif // #if FEATURE_ESPEASY_P2P - unit, - unmonitor, - unmonitorrange, - usentp, - - wifiallowap, - wifiapmode, - wificonnect, - wifidisconnect, - wifikey, - wifikey2, - wifimode, - wifiscan, - wifissid, - wifissid2, - wifistamode, -#ifndef LIMIT_BUILD_SIZE - wdconfig, - wdread, -#endif // ifndef LIMIT_BUILD_SIZE - - - NotMatched // Keep as last one -}; - - -ESPEasy_cmd_e match_ESPEasy_internal_command(const String& cmd); - -#ifndef BUILD_NO_DEBUG -bool toString(ESPEasy_cmd_e cmd, String& str); - -// Added for checking at runtime to see if all commands will be matched -bool checkAll_internalCommands(); -#endif - -#endif // ifndef COMMANDS_INTERNALCOMMANDS_DECODER_H +#ifndef COMMANDS_INTERNALCOMMANDS_DECODER_H +#define COMMANDS_INTERNALCOMMANDS_DECODER_H + +#include "../../ESPEasy_common.h" + + +enum class ESPEasy_cmd_e : uint8_t { + accessinfo, + asyncevent, + build, +#ifndef BUILD_NO_DIAGNOSTIC_COMMANDS + background, +#endif // ifndef BUILD_NO_DIAGNOSTIC_COMMANDS +#ifdef USES_C012 + blynkget, +#endif // #ifdef USES_C012 +#ifdef USES_C015 + blynkset, +#endif // #ifdef USES_C015 + + + clearaccessblock, + clearpassword, + clearrtcram, +#ifdef ESP8266 + clearsdkwifi, + clearwifirfcal, +#endif // #ifdef ESP8266 + config, + controllerdisable, + controllerenable, + + datetime, + debug, + dec, + deepsleep, + delay, +#if FEATURE_PLUGIN_PRIORITY + disableprioritytask, +#endif // #if FEATURE_PLUGIN_PRIORITY + dns, + dst, + + erasesdkwifi, +#ifdef USES_ESPEASY_NOW + espeasynowdisable, + espeasynowenable, +#endif + event, + executerules, +#if FEATURE_ETHERNET + ethphyadr, + ethpinmdc, + ethpinmdio, + ethpinpower, + ethphytype, + ethclockmode, + ethip, + ethgateway, + ethsubnet, + ethdns, + ethdisconnect, + ethwifimode, +#endif // FEATURE_ETHERNET + + factoryreset, + + gateway, + gpio, + gpiotoggle, + hiddenssid, + + i2cscanner, + inc, + ip, +#if FEATURE_USE_IPV6 + ip6, +#endif +#ifndef BUILD_NO_DIAGNOSTIC_COMMANDS + jsonportstatus, +#endif // ifndef BUILD_NO_DIAGNOSTIC_COMMANDS + + let, + #if FEATURE_STRING_VARIABLES + letstr, + #endif // if FEATURE_STRING_VARIABLES + load, + logentry, + looptimerset, + looptimerset_ms, + looptimersetandrun, + looptimersetandrun_ms, + longpulse, + longpulse_ms, +#ifndef BUILD_NO_DIAGNOSTIC_COMMANDS + logportstatus, + lowmem, +#endif // ifndef BUILD_NO_DIAGNOSTIC_COMMANDS + + monitor, + monitorrange, +#ifdef USES_P009 + mcpgpio, + mcpgpiorange, + mcpgpiopattern, + mcpgpiotoggle, + mcplongpulse, + mcplongpulse_ms, + mcpmode, + mcpmoderange, + mcppulse, +#endif // #ifdef USES_P009 +#ifndef BUILD_NO_DIAGNOSTIC_COMMANDS + malloc, + meminfo, + meminfodetail, +#endif // ifndef BUILD_NO_DIAGNOSTIC_COMMANDS + + name, + nosleep, +#if FEATURE_NOTIFIER + notify, +#endif // #if FEATURE_NOTIFIER + ntphost, + +#if FEATURE_DALLAS_HELPER && FEATURE_COMMAND_OWSCAN + owscan, +#endif // if FEATURE_DALLAS_HELPER && FEATURE_COMMAND_OWSCAN + + password, +#ifdef USES_P019 + pcfgpio, + pcfgpiorange, + pcfgpiopattern, + pcfgpiotoggle, + pcflongpulse, + pcflongpulse_ms, + pcfmode, + pcfmoderange, + pcfpulse, +#endif // #ifdef USES_P019 +#if FEATURE_POST_TO_HTTP + posttohttp, +#endif // #if FEATURE_POST_TO_HTTP +#if FEATURE_CUSTOM_PROVISIONING + provision, +# ifdef PLUGIN_BUILD_MAX_ESP32 // FIXME DEPRECATED: Fallback for temporary backward compatibility + provisionconfig, + provisionsecurity, +# if FEATURE_NOTIFIER + provisionnotification, +# endif // #if FEATURE_NOTIFIER + provisionprovision, + provisionrules, + provisionfirmware, +# endif // #ifdef PLUGIN_BUILD_MAX_ESP32 +#endif // #if FEATURE_CUSTOM_PROVISIONING + pulse, +#if FEATURE_MQTT + publish, + publishr, +#endif // #if FEATURE_MQTT +#if FEATURE_PUT_TO_HTTP + puttohttp, +#endif // #if FEATURE_PUT_TO_HTTP + pwm, + + reboot, + resetflashwritecounter, + restart, + rtttl, + rules, + + save, + scheduletaskrun, +#if FEATURE_SD + sdcard, + sdremove, +#endif // #if FEATURE_SD +#if FEATURE_ESPEASY_P2P + sendto, +#endif // #if FEATURE_ESPEASY_P2P +#if FEATURE_SEND_TO_HTTP + sendtohttp, +#endif // FEATURE_SEND_TO_HTTP + sendtoudp, + sendtoudpmix, +#ifndef BUILD_NO_DIAGNOSTIC_COMMANDS + serialfloat, +#endif // ifndef BUILD_NO_DIAGNOSTIC_COMMANDS + settings, +#if FEATURE_SERVO + servo, +#endif // #if FEATURE_SERVO + status, + subnet, +#if FEATURE_MQTT + subscribe, +#endif // #if FEATURE_MQTT +#ifndef BUILD_NO_DIAGNOSTIC_COMMANDS + sysload, +#endif // ifndef BUILD_NO_DIAGNOSTIC_COMMANDS + + taskclear, + taskclearall, + taskdisable, + taskenable, + taskrun, + taskrunat, + taskvalueset, + #if FEATURE_STRING_VARIABLES + taskvaluesetderived, + taskvaluesetpresentation, + #endif // if FEATURE_STRING_VARIABLES + taskvaluetoggle, + taskvaluesetandrun, + timerpause, + timerresume, + timerset, + timerset_ms, + timezone, + tone, + + udpport, +#if FEATURE_ESPEASY_P2P + udptest, +#endif // #if FEATURE_ESPEASY_P2P + unit, + unmonitor, + unmonitorrange, + usentp, + + wifiallowap, + wifiapmode, + wificonnect, + wifidisconnect, + wifikey, + wifikey2, + wifimode, + wifiscan, + wifissid, + wifissid2, + wifistamode, +#ifndef LIMIT_BUILD_SIZE + wdconfig, + wdread, +#endif // ifndef LIMIT_BUILD_SIZE + + + NotMatched // Keep as last one +}; + + +ESPEasy_cmd_e match_ESPEasy_internal_command(const String& cmd); + +#ifndef BUILD_NO_DEBUG +bool toString(ESPEasy_cmd_e cmd, String& str); + +// Added for checking at runtime to see if all commands will be matched +bool checkAll_internalCommands(); +#endif + +#endif // ifndef COMMANDS_INTERNALCOMMANDS_DECODER_H diff --git a/src/src/ControllerQueue/C011_queue_element.h b/src/src/ControllerQueue/C011_queue_element.h index 91d99167cd..0c1fe56280 100644 --- a/src/src/ControllerQueue/C011_queue_element.h +++ b/src/src/ControllerQueue/C011_queue_element.h @@ -1,55 +1,56 @@ -#ifndef CONTROLLERQUEUE_C011_QUEUE_ELEMENT_H -#define CONTROLLERQUEUE_C011_QUEUE_ELEMENT_H - -#include "../../ESPEasy_common.h" - -#ifdef USES_C011 - -#include "../ControllerQueue/Queue_element_base.h" -#include "../CustomBuild/ESPEasyLimits.h" -#include "../DataStructs/DeviceStruct.h" -#include "../DataStructs/UnitMessageCount.h" -#include "../Globals/CPlugins.h" -#include "../Globals/Plugins.h" - -struct EventStruct; - - -/*********************************************************************************************\ -* C011_queue_element for queueing requests for C011: Generic HTTP Advanced. -\*********************************************************************************************/ -class C011_queue_element : public Queue_element_base { -public: - - C011_queue_element() = default; - - C011_queue_element(C011_queue_element&& other) = default; - - C011_queue_element(const C011_queue_element& other) = delete; - - C011_queue_element(const struct EventStruct *event); - - bool isDuplicate(const Queue_element_base& other) const; - - const UnitMessageCount_t* getUnitMessageCount() const { - return nullptr; - } - - UnitMessageCount_t* getUnitMessageCount() { - return nullptr; - } - - size_t getSize() const; - - String uri; - String HttpMethod; - String header; - String postStr; - int idx = 0; - Sensor_VType sensorType = Sensor_VType::SENSOR_TYPE_NONE; -}; - -#endif // USES_C011 - - -#endif // CONTROLLERQUEUE_C011_QUEUE_ELEMENT_H +#ifndef CONTROLLERQUEUE_C011_QUEUE_ELEMENT_H +#define CONTROLLERQUEUE_C011_QUEUE_ELEMENT_H + +#include "../../ESPEasy_common.h" + +#ifdef USES_C011 + +#include "../ControllerQueue/Queue_element_base.h" +#include "../CustomBuild/ESPEasyLimits.h" +#include "../DataStructs/DeviceStruct.h" +#include "../DataStructs/MessageRouteInfo.h" +#include "../Globals/CPlugins.h" +#include "../Globals/Plugins.h" + +struct EventStruct; + + +/*********************************************************************************************\ +* C011_queue_element for queueing requests for C011: Generic HTTP Advanced. +\*********************************************************************************************/ +class C011_queue_element : public Queue_element_base { +public: + + C011_queue_element() = default; + + C011_queue_element(C011_queue_element&& other) = default; + + C011_queue_element(const C011_queue_element& other) = delete; + + C011_queue_element(const struct EventStruct *event); + + bool isDuplicate(const Queue_element_base& other) const; + + + const MessageRouteInfo_t* getMessageRouteInfo() const { + return nullptr; + } + + MessageRouteInfo_t* getMessageRouteInfo() { + return nullptr; + } + + size_t getSize() const; + + String uri; + String HttpMethod; + String header; + String postStr; + int idx = 0; + Sensor_VType sensorType = Sensor_VType::SENSOR_TYPE_NONE; +}; + +#endif // USES_C011 + + +#endif // CONTROLLERQUEUE_C011_QUEUE_ELEMENT_H diff --git a/src/src/ControllerQueue/C015_queue_element.h b/src/src/ControllerQueue/C015_queue_element.h index fec2c66589..937b6741c3 100644 --- a/src/src/ControllerQueue/C015_queue_element.h +++ b/src/src/ControllerQueue/C015_queue_element.h @@ -1,60 +1,60 @@ -#ifndef CONTROLLERQUEUE_C015_QUEUE_ELEMENT_H -#define CONTROLLERQUEUE_C015_QUEUE_ELEMENT_H - -#include "../../ESPEasy_common.h" - -#ifdef USES_C015 - -#include "../ControllerQueue/Queue_element_base.h" -#include "../CustomBuild/ESPEasyLimits.h" -#include "../DataStructs/UnitMessageCount.h" -#include "../Globals/CPlugins.h" -#include "../Globals/Plugins.h" - -struct EventStruct; - - -/*********************************************************************************************\ -* C015_queue_element for queueing requests for 015: Blynk -* Using SimpleQueueElement_formatted_Strings -\*********************************************************************************************/ - -class C015_queue_element : public Queue_element_base { -public: - - C015_queue_element() = default; - - C015_queue_element(const C015_queue_element& other) = delete; - - C015_queue_element(C015_queue_element&& other); - - C015_queue_element(const struct EventStruct *event, - uint8_t value_count); - - C015_queue_element & operator=(C015_queue_element&& other); - - bool checkDone(bool succesfull) const; - - size_t getSize() const; - - bool isDuplicate(const Queue_element_base& other) const; - - const UnitMessageCount_t* getUnitMessageCount() const { - return nullptr; - } - - UnitMessageCount_t* getUnitMessageCount() { - return nullptr; - } - - String txt[VARS_PER_TASK] = {}; - int vPin[VARS_PER_TASK] = { 0 }; - int idx = 0; - mutable uint8_t valuesSent = 0; // Value must be set by const function checkDone() - uint8_t valueCount = 0; -}; - -#endif // USES_C015 - - -#endif // CONTROLLERQUEUE_C015_QUEUE_ELEMENT_H +#ifndef CONTROLLERQUEUE_C015_QUEUE_ELEMENT_H +#define CONTROLLERQUEUE_C015_QUEUE_ELEMENT_H + +#include "../../ESPEasy_common.h" + +#ifdef USES_C015 + +#include "../ControllerQueue/Queue_element_base.h" +#include "../CustomBuild/ESPEasyLimits.h" +#include "../DataStructs/MessageRouteInfo.h" +#include "../Globals/CPlugins.h" +#include "../Globals/Plugins.h" + +struct EventStruct; + + +/*********************************************************************************************\ +* C015_queue_element for queueing requests for 015: Blynk +* Using SimpleQueueElement_formatted_Strings +\*********************************************************************************************/ + +class C015_queue_element : public Queue_element_base { +public: + + C015_queue_element() = default; + + C015_queue_element(const C015_queue_element& other) = delete; + + C015_queue_element(C015_queue_element&& other); + + C015_queue_element(const struct EventStruct *event, + uint8_t value_count); + + C015_queue_element & operator=(C015_queue_element&& other); + + bool checkDone(bool succesfull) const; + + size_t getSize() const; + + bool isDuplicate(const Queue_element_base& other) const; + + const MessageRouteInfo_t* getMessageRouteInfo() const { + return nullptr; + } + + MessageRouteInfo_t* getMessageRouteInfo() { + return nullptr; + } + + String txt[VARS_PER_TASK] = {}; + int vPin[VARS_PER_TASK] = { 0 }; + int idx = 0; + mutable uint8_t valuesSent = 0; // Value must be set by const function checkDone() + uint8_t valueCount = 0; +}; + +#endif // USES_C015 + + +#endif // CONTROLLERQUEUE_C015_QUEUE_ELEMENT_H diff --git a/src/src/ControllerQueue/C016_queue_element.h b/src/src/ControllerQueue/C016_queue_element.h index 5c35d3df11..828ef7b313 100644 --- a/src/src/ControllerQueue/C016_queue_element.h +++ b/src/src/ControllerQueue/C016_queue_element.h @@ -1,76 +1,77 @@ -#ifndef CONTROLLERQUEUE_C016_QUEUE_ELEMENT_H -#define CONTROLLERQUEUE_C016_QUEUE_ELEMENT_H - -#include "../../ESPEasy_common.h" -#ifdef USES_C016 - - -# include "../ControllerQueue/Queue_element_base.h" -# include "../CustomBuild/ESPEasyLimits.h" -# include "../DataTypes/ControllerIndex.h" -# include "../DataTypes/TaskValues_Data.h" -# include "../DataStructs/DeviceStruct.h" -# include "../DataStructs/UnitMessageCount.h" -# include "../Globals/Plugins.h" - -struct EventStruct; - - -// The binary format to store the samples using the Cache Controller -// Do NOT change order of members! -struct C016_binary_element { - TaskValues_Data_t values{}; - unsigned long unixTime{}; - taskIndex_t TaskIndex{ INVALID_TASK_INDEX }; - pluginID_t pluginID{ INVALID_PLUGIN_ID }; - Sensor_VType sensorType{ Sensor_VType::SENSOR_TYPE_NONE }; - uint8_t valueCount{}; -}; - - -/*********************************************************************************************\ -* C016_queue_element for queueing requests for C016: Cached HTTP. -\*********************************************************************************************/ - -// TD-er: This one has a fixed uint8_t order and is stored. -// This also means the order of members should not be changed! -class C016_queue_element : public Queue_element_base { -public: - - C016_queue_element(); - - C016_queue_element(const C016_queue_element& other) = delete; - - C016_queue_element(C016_queue_element&& other); - - C016_queue_element(const struct EventStruct *event, - uint8_t value_count); - - C016_queue_element & operator=(C016_queue_element&& other); - - - size_t getSize() const; - - bool isDuplicate(const Queue_element_base& other) const; - - const UnitMessageCount_t* getUnitMessageCount() const { - return nullptr; - } - - UnitMessageCount_t* getUnitMessageCount() { - return nullptr; - } - - C016_binary_element getBinary() const; - - TaskValues_Data_t values{}; - - unsigned long unixTime = 0; - Sensor_VType sensorType{ Sensor_VType::SENSOR_TYPE_NONE }; - uint8_t valueCount{}; -}; - -#endif // USES_C016 - - -#endif // CONTROLLERQUEUE_C016_QUEUE_ELEMENT_H +#ifndef CONTROLLERQUEUE_C016_QUEUE_ELEMENT_H +#define CONTROLLERQUEUE_C016_QUEUE_ELEMENT_H + +#include "../../ESPEasy_common.h" + +#ifdef USES_C016 + + +# include "../ControllerQueue/Queue_element_base.h" +# include "../CustomBuild/ESPEasyLimits.h" +# include "../DataTypes/ControllerIndex.h" +# include "../DataTypes/TaskValues_Data.h" +# include "../DataStructs/DeviceStruct.h" +# include "../DataStructs/MessageRouteInfo.h" +# include "../Globals/Plugins.h" + +struct EventStruct; + + +// The binary format to store the samples using the Cache Controller +// Do NOT change order of members! +struct C016_binary_element { + TaskValues_Data_t values{}; + unsigned long unixTime{}; + taskIndex_t TaskIndex{ INVALID_TASK_INDEX }; + pluginID_t pluginID{ INVALID_PLUGIN_ID }; + Sensor_VType sensorType{ Sensor_VType::SENSOR_TYPE_NONE }; + uint8_t valueCount{}; +}; + + +/*********************************************************************************************\ +* C016_queue_element for queueing requests for C016: Cached HTTP. +\*********************************************************************************************/ + +// TD-er: This one has a fixed uint8_t order and is stored. +// This also means the order of members should not be changed! +class C016_queue_element : public Queue_element_base { +public: + + C016_queue_element(); + + C016_queue_element(const C016_queue_element& other) = delete; + + C016_queue_element(C016_queue_element&& other); + + C016_queue_element(const struct EventStruct *event, + uint8_t value_count); + + C016_queue_element & operator=(C016_queue_element&& other); + + + size_t getSize() const; + + bool isDuplicate(const Queue_element_base& other) const; + + const MessageRouteInfo_t* getMessageRouteInfo() const { + return nullptr; + } + + MessageRouteInfo_t* getMessageRouteInfo() { + return nullptr; + } + + C016_binary_element getBinary() const; + + TaskValues_Data_t values{}; + + unsigned long unixTime = 0; + Sensor_VType sensorType{ Sensor_VType::SENSOR_TYPE_NONE }; + uint8_t valueCount{}; +}; + +#endif // USES_C016 + + +#endif // CONTROLLERQUEUE_C016_QUEUE_ELEMENT_H diff --git a/src/src/ControllerQueue/C018_queue_element.cpp b/src/src/ControllerQueue/C018_queue_element.cpp index f3096731b6..c6bbd41ffe 100644 --- a/src/src/ControllerQueue/C018_queue_element.cpp +++ b/src/src/ControllerQueue/C018_queue_element.cpp @@ -1,41 +1,40 @@ -#include "../ControllerQueue/C018_queue_element.h" - -#ifdef USES_C018 - -# include "../DataStructs/ESPEasy_EventStruct.h" -# include "../DataStructs/UnitMessageCount.h" - -# include "../ESPEasyCore/ESPEasy_Log.h" - -# include "../Helpers/_CPlugin_LoRa_TTN_helper.h" -# include "../Helpers/StringConverter.h" - -C018_queue_element::C018_queue_element(struct EventStruct *event, uint8_t sampleSetCount) -{ - _controller_idx = event->ControllerIndex; - _taskIndex = event->TaskIndex; - # if FEATURE_PACKED_RAW_DATA - move_special(packed, getPackedFromPlugin(event, sampleSetCount)); - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - addLogMove(LOG_LEVEL_INFO, concat(F("C018 queue element: "), packed)); - } - # endif // if FEATURE_PACKED_RAW_DATA -} - -size_t C018_queue_element::getSize() const { - return sizeof(*this) + packed.length(); -} - -bool C018_queue_element::isDuplicate(const Queue_element_base& other) const { - const C018_queue_element& oth = static_cast(other); - - if ((oth._controller_idx != _controller_idx) || - (oth._taskIndex != _taskIndex) || - (oth.packed != packed)) { - return false; - } - return true; -} - -#endif // ifdef USES_C018 +#include "../ControllerQueue/C018_queue_element.h" + +#ifdef USES_C018 + +# include "../DataStructs/ESPEasy_EventStruct.h" + +# include "../ESPEasyCore/ESPEasy_Log.h" + +# include "../Helpers/_CPlugin_LoRa_TTN_helper.h" +# include "../Helpers/StringConverter.h" + +C018_queue_element::C018_queue_element(struct EventStruct *event, uint8_t sampleSetCount) +{ + _controller_idx = event->ControllerIndex; + _taskIndex = event->TaskIndex; + # if FEATURE_PACKED_RAW_DATA + move_special(packed, getPackedFromPlugin(event, sampleSetCount)); + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLogMove(LOG_LEVEL_INFO, concat(F("C018 queue element: "), packed)); + } + # endif // if FEATURE_PACKED_RAW_DATA +} + +size_t C018_queue_element::getSize() const { + return sizeof(*this) + packed.length(); +} + +bool C018_queue_element::isDuplicate(const Queue_element_base& other) const { + const C018_queue_element& oth = static_cast(other); + + if ((oth._controller_idx != _controller_idx) || + (oth._taskIndex != _taskIndex) || + (oth.packed != packed)) { + return false; + } + return true; +} + +#endif // ifdef USES_C018 diff --git a/src/src/ControllerQueue/C018_queue_element.h b/src/src/ControllerQueue/C018_queue_element.h index b7c9fb8ae2..312f69faed 100644 --- a/src/src/ControllerQueue/C018_queue_element.h +++ b/src/src/ControllerQueue/C018_queue_element.h @@ -1,51 +1,51 @@ -#ifndef CONTROLLERQUEUE_C018_QUEUE_ELEMENT_H -#define CONTROLLERQUEUE_C018_QUEUE_ELEMENT_H - -#include "../../ESPEasy_common.h" - -#ifdef USES_C018 - -# include "../ControllerQueue/Queue_element_base.h" -# include "../CustomBuild/ESPEasyLimits.h" -# include "../Globals/CPlugins.h" - - -struct EventStruct; -struct UnitMessageCount_t; - -/*********************************************************************************************\ -* C018_queue_element for queueing requests for C018: TTN/RN2483 -\*********************************************************************************************/ - - -class C018_queue_element : public Queue_element_base { -public: - - C018_queue_element() = default; - - C018_queue_element(const C018_queue_element& other) = delete; - - C018_queue_element(C018_queue_element&& other) = default; - - C018_queue_element(struct EventStruct *event, - uint8_t sampleSetCount); - - size_t getSize() const; - - bool isDuplicate(const Queue_element_base& other) const; - - const UnitMessageCount_t* getUnitMessageCount() const { - return nullptr; - } - - UnitMessageCount_t* getUnitMessageCount() { - return nullptr; - } - - String packed; -}; - -#endif // USES_C018 - - -#endif // CONTROLLERQUEUE_C018_QUEUE_ELEMENT_H +#ifndef CONTROLLERQUEUE_C018_QUEUE_ELEMENT_H +#define CONTROLLERQUEUE_C018_QUEUE_ELEMENT_H + +#include "../../ESPEasy_common.h" + +#ifdef USES_C018 + +# include "../ControllerQueue/Queue_element_base.h" +# include "../CustomBuild/ESPEasyLimits.h" +# include "../DataStructs/MessageRouteInfo.h" +# include "../Globals/CPlugins.h" + + +struct EventStruct; + +/*********************************************************************************************\ +* C018_queue_element for queueing requests for C018: TTN/RN2483 +\*********************************************************************************************/ + + +class C018_queue_element : public Queue_element_base { +public: + + C018_queue_element() = default; + + C018_queue_element(const C018_queue_element& other) = delete; + + C018_queue_element(C018_queue_element&& other) = default; + + C018_queue_element(struct EventStruct *event, + uint8_t sampleSetCount); + + size_t getSize() const; + + bool isDuplicate(const Queue_element_base& other) const; + + const MessageRouteInfo_t* getMessageRouteInfo() const { + return nullptr; + } + + MessageRouteInfo_t* getMessageRouteInfo() { + return nullptr; + } + + String packed; +}; + +#endif // USES_C018 + + +#endif // CONTROLLERQUEUE_C018_QUEUE_ELEMENT_H diff --git a/src/src/ControllerQueue/C019_queue_element.cpp b/src/src/ControllerQueue/C019_queue_element.cpp new file mode 100644 index 0000000000..35272cab54 --- /dev/null +++ b/src/src/ControllerQueue/C019_queue_element.cpp @@ -0,0 +1,54 @@ +#include "../ControllerQueue/C019_queue_element.h" + + +#ifdef USES_C019 + +#include "../DataStructs/ESPEasy_EventStruct.h" +#include "../ESPEasyCore/ESPEasy_Log.h" + + +#if FEATURE_PACKED_RAW_DATA +String getPackedFromPlugin(struct EventStruct *event, + uint8_t sampleSetCount); +#endif + +C019_queue_element::C019_queue_element(struct EventStruct *event_p) +{ + _controller_idx = event_p->ControllerIndex; + _taskIndex = event_p->TaskIndex; + + event.deep_copy(event_p); + #if FEATURE_PACKED_RAW_DATA + packed = getPackedFromPlugin(event_p, 0); + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + String log = F("C019 queue element: "); + log += packed; + addLog(LOG_LEVEL_INFO, log); + } + #endif + + // Extra check to make sure sensorType is correct. + event.sensorType = event.getSensorType(); +} + +size_t C019_queue_element::getSize() const { + return sizeof(*this) + packed.length(); +} + +bool C019_queue_element::isDuplicate(const Queue_element_base& other) const { + const C019_queue_element& oth = static_cast(other); + + if ((oth._controller_idx != _controller_idx) || + (oth._taskIndex != _taskIndex) || + (oth.plugin_id != plugin_id) || + (oth.packed != packed)) { + return false; + } + + // FIXME TD-er: Must check event too? + return false; +} + + +#endif \ No newline at end of file diff --git a/src/src/ControllerQueue/C019_queue_element.h b/src/src/ControllerQueue/C019_queue_element.h new file mode 100644 index 0000000000..e17999f1e6 --- /dev/null +++ b/src/src/ControllerQueue/C019_queue_element.h @@ -0,0 +1,48 @@ +#ifndef CONTROLLERQUEUE_C019_QUEUE_ELEMENT_H +#define CONTROLLERQUEUE_C019_QUEUE_ELEMENT_H + +#include "../../ESPEasy_common.h" + +#ifdef USES_C019 + +# include "../ControllerQueue/Queue_element_base.h" +# include "../CustomBuild/ESPEasyLimits.h" +# include "../DataStructs/ESPEasy_EventStruct.h" +# include "../DataStructs/MessageRouteInfo.h" +# include "../Globals/CPlugins.h" + +/*********************************************************************************************\ +* C019_queue_element for queueing requests for C019: ESPEasy-NOW +\*********************************************************************************************/ + + +class C019_queue_element : public Queue_element_base { +public: + + C019_queue_element() = default; + + C019_queue_element(const C019_queue_element& other) = delete; + + C019_queue_element(C019_queue_element&& other) = default; + + C019_queue_element(struct EventStruct *event); + + size_t getSize() const; + + bool isDuplicate(const Queue_element_base& other) const; + + + const MessageRouteInfo_t* getMessageRouteInfo() const { return nullptr; } + + MessageRouteInfo_t* getMessageRouteInfo() { return nullptr; } + + + String packed; + pluginID_t plugin_id = INVALID_PLUGIN_ID; + EventStruct event; +}; + +#endif //USES_C019 + + +#endif // CONTROLLERQUEUE_C019_QUEUE_ELEMENT_H diff --git a/src/src/ControllerQueue/ControllerDelayHandlerStruct.cpp b/src/src/ControllerQueue/ControllerDelayHandlerStruct.cpp index 9bdb7ac0f7..9aa973d131 100644 --- a/src/src/ControllerQueue/ControllerDelayHandlerStruct.cpp +++ b/src/src/ControllerQueue/ControllerDelayHandlerStruct.cpp @@ -1,284 +1,289 @@ -#include "../ControllerQueue/ControllerDelayHandlerStruct.h" - - -ControllerDelayHandlerStruct::ControllerDelayHandlerStruct() : - lastSend(0), - minTimeBetweenMessages(CONTROLLER_DELAY_QUEUE_DELAY_DFLT), - expire_timeout(0), - max_queue_depth(CONTROLLER_DELAY_QUEUE_DEPTH_DFLT), - attempt(0), - max_retries(CONTROLLER_DELAY_QUEUE_RETRY_DFLT), - delete_oldest(false), - must_check_reply(false), - deduplicate(false), - useLocalSystemTime(false) {} - -bool ControllerDelayHandlerStruct::cacheControllerSettings(controllerIndex_t ControllerIndex) -{ - MakeControllerSettings(ControllerSettings); - - if (!AllocatedControllerSettings()) { - return false; - } - LoadControllerSettings(ControllerIndex, *ControllerSettings); - cacheControllerSettings(*ControllerSettings); - return true; -} - -void ControllerDelayHandlerStruct::cacheControllerSettings(const ControllerSettingsStruct& settings) { - minTimeBetweenMessages = settings.MinimalTimeBetweenMessages; - max_queue_depth = settings.MaxQueueDepth; - max_retries = settings.MaxRetry; - delete_oldest = settings.DeleteOldest; - must_check_reply = settings.MustCheckReply; - deduplicate = settings.deduplicate(); - useLocalSystemTime = settings.useLocalSystemTime(); - - if (settings.allowExpire()) { - expire_timeout = max_queue_depth * max_retries * (minTimeBetweenMessages + settings.ClientTimeout); - - if (expire_timeout < CONTROLLER_QUEUE_MINIMAL_EXPIRE_TIME) { - expire_timeout = CONTROLLER_QUEUE_MINIMAL_EXPIRE_TIME; - } - } else { - expire_timeout = 0; - } - - // Set some sound limits when not configured - if (max_queue_depth == 0) { max_queue_depth = CONTROLLER_DELAY_QUEUE_DEPTH_DFLT; } - - if (max_retries == 0) { max_retries = CONTROLLER_DELAY_QUEUE_RETRY_DFLT; } - - if (minTimeBetweenMessages == 0) { minTimeBetweenMessages = CONTROLLER_DELAY_QUEUE_DELAY_DFLT; } - - // No less than 10 msec between messages. - if (minTimeBetweenMessages < 10) { minTimeBetweenMessages = 10; } -} - -bool ControllerDelayHandlerStruct::readyToProcess(const Queue_element_base& element) const { - const protocolIndex_t protocolIndex = getProtocolIndex_from_ControllerIndex(element._controller_idx); - - if (protocolIndex == INVALID_PROTOCOL_INDEX) { - return false; - } - - if (getProtocolStruct(protocolIndex).needsNetwork) { - return NetworkConnected(10); - } - return true; -} - -bool ControllerDelayHandlerStruct::queueFull(controllerIndex_t controller_idx) const { - if (sendQueue.size() >= max_queue_depth) { return true; } - - // Number of elements is not exceeding the limit, check memory - int freeHeap = FreeMem(); - { - /* - #ifdef USE_SECOND_HEAP - const int freeHeap2 = FreeMem2ndHeap(); - - if (freeHeap2 < freeHeap) { - freeHeap = freeHeap2; - } - #endif // ifdef USE_SECOND_HEAP - */ - } - -#ifdef ESP32 - if (freeHeap > 50000) -#else - if (freeHeap > 5000) -#endif - { - return false; // Memory is not an issue. - } -#ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - String log = F("Controller-"); - log += controller_idx + 1; - log += F(" : Memory used: "); - log += getQueueMemorySize(); - log += F(" bytes "); - log += sendQueue.size(); - log += F(" items "); - log += freeHeap; - log += F(" free"); - addLogMove(LOG_LEVEL_DEBUG, log); - } -#endif // ifndef BUILD_NO_DEBUG - return true; -} - -// Return true if message is already present in the queue -bool ControllerDelayHandlerStruct::isDuplicate(const Queue_element_base& element) const { - // Some controllers may receive duplicate messages, due to lost acknowledgement - // This is actually the same message, so this should not be processed. - if (!unitLastMessageCount.isNew(element.getUnitMessageCount())) { - return true; - } - - // The unit message count is still stored to make sure a new one with the same count - // is considered a duplicate, even when the queue is empty. - unitLastMessageCount.add(element.getUnitMessageCount()); - - // the setting 'deduplicate' does look at the content of the message and only compares it to messages in the queue. - if (deduplicate && !sendQueue.empty()) { - // Use reverse iterator here, as it is more likely a duplicate is added shortly after another. - auto it = sendQueue.rbegin(); // Same as back() - - for (; it != sendQueue.rend(); ++it) { - if (element.isDuplicate(*(it->get()))) { -#ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - const cpluginID_t cpluginID = getCPluginID_from_ControllerIndex(it->get()->_controller_idx); - addLogMove(LOG_LEVEL_DEBUG, concat(get_formatted_Controller_number(cpluginID), F(" : Remove duplicate"))); - } -#endif // ifndef BUILD_NO_DEBUG - return true; - } - } - } - return false; -} - -// Try to add to the queue, if permitted by "delete_oldest" -// Return true when item was added, or skipped as it was considered a duplicate -bool ControllerDelayHandlerStruct::addToQueue(std::unique_ptrelement) { - if (!element) { - return false; - } - if (isDuplicate(*element)) { - return true; - } - - if (delete_oldest) { - // Force add to the queue. - // If max buffer is reached, the oldest in the queue (first to be served) will be removed. - while (queueFull(element->_controller_idx)) { - sendQueue.pop_front(); - attempt = 0; - } - } - - if (!queueFull(element->_controller_idx)) { - #ifdef USE_SECOND_HEAP - // Do not store in 2nd heap, std::list cannot handle 2nd heap well - HeapSelectDram ephemeral; - #endif // ifdef USE_SECOND_HEAP - - sendQueue.push_back(std::move(element)); - - return true; - } -#ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - const cpluginID_t cpluginID = getCPluginID_from_ControllerIndex((*element)._controller_idx); - addLogMove(LOG_LEVEL_DEBUG, concat(get_formatted_Controller_number(cpluginID), F(" : queue full"))); - } -#endif // ifndef BUILD_NO_DEBUG - return false; -} - -// Get the next element. -// Remove front element when max_retries is reached. -Queue_element_base * ControllerDelayHandlerStruct::getNext() { - if (sendQueue.empty()) { return nullptr; } - - if (attempt > max_retries) { - sendQueue.pop_front(); - attempt = 0; - } - - if (expire_timeout != 0) { - bool done = false; - - while (!done && !sendQueue.empty()) { - if ((sendQueue.front().get() != nullptr) && (timePassedSince(sendQueue.front()->_timestamp) < static_cast(expire_timeout))) { - done = true; - } else { - sendQueue.pop_front(); - attempt = 0; - } - } - } - - if (sendQueue.empty()) { return nullptr; } - return sendQueue.front().get(); -} - -// Mark as processed and return time to schedule for next process. -// Return 0 when nothing to process. -// @param remove_from_queue indicates whether the elements should be removed from the queue. -unsigned long ControllerDelayHandlerStruct::markProcessed(bool remove_from_queue) { - if (sendQueue.empty()) { return 0; } - - if (remove_from_queue) { - sendQueue.pop_front(); - attempt = 0; - lastSend = millis(); - } else { - ++attempt; - } - return getNextScheduleTime(); -} - -unsigned long ControllerDelayHandlerStruct::getNextScheduleTime() const { - if (sendQueue.empty()) { return 0; } - unsigned long nextTime = lastSend + minTimeBetweenMessages; - - if (timePassedSince(nextTime) > 0) { - nextTime = millis(); - } - - if (nextTime == 0) { nextTime = 1; // Just to make sure it will be executed - } - return nextTime; -} - -// Set the "lastSend" to "now" + some additional delay. -// This will cause the next schedule time to be delayed to -// msecFromNow + minTimeBetweenMessages -void ControllerDelayHandlerStruct::setAdditionalDelay(unsigned long msecFromNow) { - lastSend = millis() + msecFromNow; -} - -size_t ControllerDelayHandlerStruct::getQueueMemorySize() const { - size_t totalSize = 0; - - for (auto it = sendQueue.begin(); it != sendQueue.end(); ++it) { - if (it->get() != nullptr) { - totalSize += it->get()->getSize(); - } - } - return totalSize; -} - -void ControllerDelayHandlerStruct::process( - cpluginID_t cpluginID, - do_process_function func, - TimingStatsElements timerstats_id, - SchedulerIntervalTimer_e timerID) -{ - Queue_element_base *element(static_cast(getNext())); - - if (element == nullptr) { return; } - - if (readyToProcess(*element)) { - MakeControllerSettings(ControllerSettings); - - if (AllocatedControllerSettings()) { - LoadControllerSettings(element->_controller_idx, *ControllerSettings); - cacheControllerSettings(*ControllerSettings); - START_TIMER; - markProcessed(func(cpluginID, *element, *ControllerSettings)); - #if FEATURE_TIMING_STATS - STOP_TIMER_VAR(timerstats_id); - #endif - } - } - Scheduler.scheduleNextDelayQueue(timerID, getNextScheduleTime()); -} +#include "../ControllerQueue/ControllerDelayHandlerStruct.h" + + +ControllerDelayHandlerStruct::ControllerDelayHandlerStruct() : + lastSend(0), + minTimeBetweenMessages(CONTROLLER_DELAY_QUEUE_DELAY_DFLT), + expire_timeout(0), + max_queue_depth(CONTROLLER_DELAY_QUEUE_DEPTH_DFLT), + attempt(0), + max_retries(CONTROLLER_DELAY_QUEUE_RETRY_DFLT), + delete_oldest(false), + must_check_reply(false), + deduplicate(false), + useLocalSystemTime(false) {} + +bool ControllerDelayHandlerStruct::cacheControllerSettings(controllerIndex_t ControllerIndex) +{ + MakeControllerSettings(ControllerSettings); + + if (!AllocatedControllerSettings()) { + return false; + } + LoadControllerSettings(ControllerIndex, *ControllerSettings); + cacheControllerSettings(*ControllerSettings); + return true; +} + +void ControllerDelayHandlerStruct::cacheControllerSettings(const ControllerSettingsStruct& settings) { + minTimeBetweenMessages = settings.MinimalTimeBetweenMessages; + max_queue_depth = settings.MaxQueueDepth; + max_retries = settings.MaxRetry; + delete_oldest = settings.DeleteOldest; + must_check_reply = settings.MustCheckReply; + deduplicate = settings.deduplicate(); + useLocalSystemTime = settings.useLocalSystemTime(); +#ifdef USES_ESPEASY_NOW + enableESPEasyNowFallback = settings.enableESPEasyNowFallback(); +#endif + + if (settings.allowExpire()) { + expire_timeout = max_queue_depth * max_retries * (minTimeBetweenMessages + settings.ClientTimeout); + + if (expire_timeout < CONTROLLER_QUEUE_MINIMAL_EXPIRE_TIME) { + expire_timeout = CONTROLLER_QUEUE_MINIMAL_EXPIRE_TIME; + } + } else { + expire_timeout = 0; + } + + // Set some sound limits when not configured + if (max_queue_depth == 0) { max_queue_depth = CONTROLLER_DELAY_QUEUE_DEPTH_DFLT; } + + if (max_retries == 0) { max_retries = CONTROLLER_DELAY_QUEUE_RETRY_DFLT; } + + if (minTimeBetweenMessages == 0) { minTimeBetweenMessages = CONTROLLER_DELAY_QUEUE_DELAY_DFLT; } + + // No less than 10 msec between messages. + if (minTimeBetweenMessages < 10) { minTimeBetweenMessages = 10; } +} + +bool ControllerDelayHandlerStruct::readyToProcess(const Queue_element_base& element) const { + const protocolIndex_t protocolIndex = getProtocolIndex_from_ControllerIndex(element._controller_idx); + + if (protocolIndex == INVALID_PROTOCOL_INDEX) { + return false; + } + + if (!enableESPEasyNowFallback && getProtocolStruct(protocolIndex).needsNetwork) { + return NetworkConnected(10); + } + return true; +} + +bool ControllerDelayHandlerStruct::queueFull(controllerIndex_t controller_idx) const { + if (sendQueue.size() >= max_queue_depth) { + return true; + } + + // Number of elements is not exceeding the limit, check memory + int freeHeap = FreeMem(); + { + /* + #ifdef USE_SECOND_HEAP + const int freeHeap2 = FreeMem2ndHeap(); + + if (freeHeap2 < freeHeap) { + freeHeap = freeHeap2; + } + #endif // ifdef USE_SECOND_HEAP + */ + } + +#ifdef ESP32 + if (freeHeap > 50000) +#else + if (freeHeap > 5000) +#endif + { + return false; // Memory is not an issue. + } +#ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + String log = F("Controller-"); + log += controller_idx + 1; + log += F(" : Memory used: "); + log += getQueueMemorySize(); + log += F(" bytes "); + log += sendQueue.size(); + log += F(" items "); + log += freeHeap; + log += F(" free"); + addLogMove(LOG_LEVEL_DEBUG, log); + } +#endif // ifndef BUILD_NO_DEBUG + return true; +} + +// Return true if message is already present in the queue +bool ControllerDelayHandlerStruct::isDuplicate(const Queue_element_base& element) const { + // Some controllers may receive duplicate messages, due to lost acknowledgement + // This is actually the same message, so this should not be processed. + if (!unitMessageRouteInfo_map.isNew(element.getMessageRouteInfo())) { + return true; + } + + // The unit message count is still stored to make sure a new one with the same count + // is considered a duplicate, even when the queue is empty. + unitMessageRouteInfo_map.add(element.getMessageRouteInfo()); + + // the setting 'deduplicate' does look at the content of the message and only compares it to messages in the queue. + if (deduplicate && !sendQueue.empty()) { + // Use reverse iterator here, as it is more likely a duplicate is added shortly after another. + auto it = sendQueue.rbegin(); // Same as back() + + for (; it != sendQueue.rend(); ++it) { + if (element.isDuplicate(*(it->get()))) { +#ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + const cpluginID_t cpluginID = getCPluginID_from_ControllerIndex(it->get()->_controller_idx); + addLogMove(LOG_LEVEL_DEBUG, concat(get_formatted_Controller_number(cpluginID), F(" : Remove duplicate"))); + } +#endif // ifndef BUILD_NO_DEBUG + return true; + } + } + } + return false; +} + +// Try to add to the queue, if permitted by "delete_oldest" +// Return true when item was added, or skipped as it was considered a duplicate +bool ControllerDelayHandlerStruct::addToQueue(std::unique_ptrelement) { + if (!element) { + return false; + } + if (isDuplicate(*element)) { + return true; + } + + if (delete_oldest) { + // Force add to the queue. + // If max buffer is reached, the oldest in the queue (first to be served) will be removed. + while (queueFull(element->_controller_idx)) { + sendQueue.pop_front(); + attempt = 0; + } + } + + if (!queueFull(element->_controller_idx)) { + #ifdef USE_SECOND_HEAP + // Do not store in 2nd heap, std::list cannot handle 2nd heap well + HeapSelectDram ephemeral; + #endif // ifdef USE_SECOND_HEAP + + sendQueue.push_back(std::move(element)); + + return true; + } +#ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + const cpluginID_t cpluginID = getCPluginID_from_ControllerIndex((*element)._controller_idx); + addLogMove(LOG_LEVEL_DEBUG, concat(get_formatted_Controller_number(cpluginID), F(" : queue full"))); + } +#endif // ifndef BUILD_NO_DEBUG + return false; +} + +// Get the next element. +// Remove front element when max_retries is reached. +Queue_element_base * ControllerDelayHandlerStruct::getNext() { + if (sendQueue.empty()) { return nullptr; } + + if (attempt > max_retries) { + sendQueue.pop_front(); + attempt = 0; + } + + if (expire_timeout != 0) { + bool done = false; + + while (!done && !sendQueue.empty()) { + if ((sendQueue.front().get() != nullptr) && (timePassedSince(sendQueue.front()->_timestamp) < static_cast(expire_timeout))) { + done = true; + } else { + sendQueue.pop_front(); + attempt = 0; + } + } + } + + if (sendQueue.empty()) { return nullptr; } + return sendQueue.front().get(); +} + +// Mark as processed and return time to schedule for next process. +// Return 0 when nothing to process. +// @param remove_from_queue indicates whether the elements should be removed from the queue. +unsigned long ControllerDelayHandlerStruct::markProcessed(bool remove_from_queue) { + if (sendQueue.empty()) { return 0; } + + if (remove_from_queue) { + sendQueue.pop_front(); + attempt = 0; + lastSend = millis(); + } else { + ++attempt; + } + return getNextScheduleTime(); +} + +unsigned long ControllerDelayHandlerStruct::getNextScheduleTime() const { + if (sendQueue.empty()) { return 0; } + unsigned long nextTime = lastSend + minTimeBetweenMessages; + + if (timePassedSince(nextTime) > 0) { + nextTime = millis(); + } + + if (nextTime == 0) { nextTime = 1; // Just to make sure it will be executed + } + return nextTime; +} + +// Set the "lastSend" to "now" + some additional delay. +// This will cause the next schedule time to be delayed to +// msecFromNow + minTimeBetweenMessages +void ControllerDelayHandlerStruct::setAdditionalDelay(unsigned long msecFromNow) { + lastSend = millis() + msecFromNow; +} + +size_t ControllerDelayHandlerStruct::getQueueMemorySize() const { + size_t totalSize = 0; + + for (auto it = sendQueue.begin(); it != sendQueue.end(); ++it) { + if (it->get() != nullptr) { + totalSize += it->get()->getSize(); + } + } + return totalSize; +} + +void ControllerDelayHandlerStruct::process( + cpluginID_t cpluginID, + do_process_function func, + TimingStatsElements timerstats_id, + SchedulerIntervalTimer_e timerID) +{ + Queue_element_base *element(static_cast(getNext())); + + if (element == nullptr) { return; } + + if (enableESPEasyNowFallback || readyToProcess(*element)) { + MakeControllerSettings(ControllerSettings); + + if (AllocatedControllerSettings()) { + LoadControllerSettings(element->_controller_idx, *ControllerSettings); + cacheControllerSettings(*ControllerSettings); + START_TIMER; + markProcessed(func(cpluginID, *element, *ControllerSettings)); + #if FEATURE_TIMING_STATS + STOP_TIMER_VAR(timerstats_id); + #endif + } + } + Scheduler.scheduleNextDelayQueue(timerID, getNextScheduleTime()); +} diff --git a/src/src/ControllerQueue/ControllerDelayHandlerStruct.h b/src/src/ControllerQueue/ControllerDelayHandlerStruct.h index f5442cf502..1f5915bac7 100644 --- a/src/src/ControllerQueue/ControllerDelayHandlerStruct.h +++ b/src/src/ControllerQueue/ControllerDelayHandlerStruct.h @@ -1,94 +1,95 @@ -#ifndef CONTROLLERQUEUE_CONTROLLER_DELAY_HANDLER_STRUCT_H -#define CONTROLLERQUEUE_CONTROLLER_DELAY_HANDLER_STRUCT_H - -#include "../../ESPEasy_common.h" - -#include "../ControllerQueue/Queue_element_base.h" - -#include "../DataStructs/ControllerSettingsStruct.h" -#include "../DataStructs/TimingStats.h" -#include "../DataStructs/UnitMessageCount.h" -#include "../ESPEasyCore/ESPEasy_Log.h" -#include "../Globals/CPlugins.h" -#include "../Globals/ESPEasy_Scheduler.h" -#include "../Helpers/_CPlugin_Helper.h" -#include "../Helpers/ESPEasy_Storage.h" -#include "../Helpers/ESPEasy_time_calc.h" -#include "../Helpers/Memory.h" -#include "../Helpers/Networking.h" -#include "../Helpers/Scheduler.h" -#include "../Helpers/StringConverter.h" - - -#include -#include // For std::shared_ptr -#include // std::nothrow - -#ifndef CONTROLLER_QUEUE_MINIMAL_EXPIRE_TIME - # define CONTROLLER_QUEUE_MINIMAL_EXPIRE_TIME 10000 -#endif // ifndef CONTROLLER_QUEUE_MINIMAL_EXPIRE_TIME - -typedef bool (*do_process_function)(cpluginID_t, - const Queue_element_base&, - ControllerSettingsStruct&); - -/*********************************************************************************************\ -* ControllerDelayHandlerStruct -\*********************************************************************************************/ -struct ControllerDelayHandlerStruct { - ControllerDelayHandlerStruct(); - - bool cacheControllerSettings(controllerIndex_t ControllerIndex); - void cacheControllerSettings(const ControllerSettingsStruct& settings); - - bool readyToProcess(const Queue_element_base& element) const; - - bool queueFull(controllerIndex_t controller_idx) const; - - // Return true if message is already present in the queue - bool isDuplicate(const Queue_element_base& element) const; - - // Try to add to the queue, if permitted by "delete_oldest" - // Return true when item was added, or skipped as it was considered a duplicate - bool addToQueue(std::unique_ptrelement); - - // Get the next element. - // Remove front element when max_retries is reached. - Queue_element_base* getNext(); - - // Mark as processed and return time to schedule for next process. - // Return 0 when nothing to process. - // @param remove_from_queue indicates whether the elements should be removed from the queue. - unsigned long markProcessed(bool remove_from_queue); - - unsigned long getNextScheduleTime() const; - - // Set the "lastSend" to "now" + some additional delay. - // This will cause the next schedule time to be delayed to - // msecFromNow + minTimeBetweenMessages - void setAdditionalDelay(unsigned long msecFromNow); - - size_t getQueueMemorySize() const; - - void process( - cpluginID_t cpluginID, - do_process_function func, - TimingStatsElements timerstats_id, - SchedulerIntervalTimer_e timerID); - - std::list >sendQueue; - mutable UnitLastMessageCount_map unitLastMessageCount; - unsigned long lastSend = 0; - unsigned int minTimeBetweenMessages = CONTROLLER_DELAY_QUEUE_DELAY_DFLT; - unsigned long expire_timeout = 0; - uint8_t max_queue_depth = CONTROLLER_DELAY_QUEUE_DEPTH_DFLT; - uint8_t attempt = 0; - uint8_t max_retries = CONTROLLER_DELAY_QUEUE_RETRY_DFLT; - bool delete_oldest = false; - bool must_check_reply = false; - bool deduplicate = false; - bool useLocalSystemTime = false; -}; - - -#endif // CONTROLLERQUEUE_CONTROLLER_DELAY_HANDLER_STRUCT_H +#ifndef CONTROLLERQUEUE_CONTROLLER_DELAY_HANDLER_STRUCT_H +#define CONTROLLERQUEUE_CONTROLLER_DELAY_HANDLER_STRUCT_H + +#include "../../ESPEasy_common.h" + +#include "../ControllerQueue/Queue_element_base.h" + +#include "../DataStructs/ControllerSettingsStruct.h" +#include "../DataStructs/TimingStats.h" +#include "../DataStructs/MessageRouteInfo.h" +#include "../ESPEasyCore/ESPEasy_Log.h" +#include "../Globals/CPlugins.h" +#include "../Globals/ESPEasy_Scheduler.h" +#include "../Helpers/_CPlugin_Helper.h" +#include "../Helpers/ESPEasy_Storage.h" +#include "../Helpers/ESPEasy_time_calc.h" +#include "../Helpers/Memory.h" +#include "../Helpers/Networking.h" +#include "../Helpers/Scheduler.h" +#include "../Helpers/StringConverter.h" + + +#include +#include // For std::shared_ptr +#include // std::nothrow + +#ifndef CONTROLLER_QUEUE_MINIMAL_EXPIRE_TIME + # define CONTROLLER_QUEUE_MINIMAL_EXPIRE_TIME 10000 +#endif // ifndef CONTROLLER_QUEUE_MINIMAL_EXPIRE_TIME + +typedef bool (*do_process_function)(cpluginID_t, + const Queue_element_base&, + ControllerSettingsStruct&); + +/*********************************************************************************************\ +* ControllerDelayHandlerStruct +\*********************************************************************************************/ +struct ControllerDelayHandlerStruct { + ControllerDelayHandlerStruct(); + + bool cacheControllerSettings(controllerIndex_t ControllerIndex); + void cacheControllerSettings(const ControllerSettingsStruct& settings); + + bool readyToProcess(const Queue_element_base& element) const; + + bool queueFull(controllerIndex_t controller_idx) const; + + // Return true if message is already present in the queue + bool isDuplicate(const Queue_element_base& element) const; + + // Try to add to the queue, if permitted by "delete_oldest" + // Return true when item was added, or skipped as it was considered a duplicate + bool addToQueue(std::unique_ptrelement); + + // Get the next element. + // Remove front element when max_retries is reached. + Queue_element_base* getNext(); + + // Mark as processed and return time to schedule for next process. + // Return 0 when nothing to process. + // @param remove_from_queue indicates whether the elements should be removed from the queue. + unsigned long markProcessed(bool remove_from_queue); + + unsigned long getNextScheduleTime() const; + + // Set the "lastSend" to "now" + some additional delay. + // This will cause the next schedule time to be delayed to + // msecFromNow + minTimeBetweenMessages + void setAdditionalDelay(unsigned long msecFromNow); + + size_t getQueueMemorySize() const; + + void process( + cpluginID_t cpluginID, + do_process_function func, + TimingStatsElements timerstats_id, + SchedulerIntervalTimer_e timerID); + + std::list >sendQueue; + mutable UnitMessageRouteInfo_map unitMessageRouteInfo_map; + unsigned long lastSend = 0; + unsigned int minTimeBetweenMessages = CONTROLLER_DELAY_QUEUE_DELAY_DFLT; + unsigned long expire_timeout = 0; + uint8_t max_queue_depth = CONTROLLER_DELAY_QUEUE_DEPTH_DFLT; + uint8_t attempt = 0; + uint8_t max_retries = CONTROLLER_DELAY_QUEUE_RETRY_DFLT; + bool delete_oldest = false; + bool must_check_reply = false; + bool deduplicate = false; + bool useLocalSystemTime = false; + bool enableESPEasyNowFallback = false; +}; + + +#endif // CONTROLLERQUEUE_CONTROLLER_DELAY_HANDLER_STRUCT_H diff --git a/src/src/ControllerQueue/DelayQueueElements.cpp b/src/src/ControllerQueue/DelayQueueElements.cpp index 24bad6b34e..8a1dedce12 100644 --- a/src/src/ControllerQueue/DelayQueueElements.cpp +++ b/src/src/ControllerQueue/DelayQueueElements.cpp @@ -143,11 +143,9 @@ DEFINE_Cxxx_DELAY_QUEUE_MACRO_CPP(0, 18) // -V522 #endif // ifdef USES_C018 -/* - #ifdef USES_C019 - DEFINE_Cxxx_DELAY_QUEUE_MACRO_CPP(0, 19) // -V522 - #endif - */ +#ifdef USES_C019 + DEFINE_Cxxx_DELAY_QUEUE_MACRO_CPP(0, 19) // -V522 +#endif /* #ifdef USES_C020 diff --git a/src/src/ControllerQueue/DelayQueueElements.h b/src/src/ControllerQueue/DelayQueueElements.h index 3c44e89edf..632cd7e07f 100644 --- a/src/src/ControllerQueue/DelayQueueElements.h +++ b/src/src/ControllerQueue/DelayQueueElements.h @@ -1,294 +1,294 @@ -#ifndef DELAY_QUEUE_ELEMENTS_H -#define DELAY_QUEUE_ELEMENTS_H - - -#include "../../ESPEasy_common.h" - - -#include "../ControllerQueue/ControllerDelayHandlerStruct.h" -#include "../ControllerQueue/Queue_element_base.h" -#include "../DataStructs/ControllerSettingsStruct.h" - - -// The most logical place to have these queue element handlers defined would be in their -// respective _Cxxx.ino file. -// But the PlatformIO/Arduino build process may then run into issues when compiling. -// Either some of the functions may not be (forward) declared yet when being called from the scheduler code. -// Or the forward declaration of a function may be generated by the pre-processor when expanding the macro to generate them. -// The #ifdef USES_Cxxx check is then no longer present in the generated ESPEasy.ino.cpp file which will lead to build errors -// when not all controllers are included in the build. -// -// To overcome build errors, one MUST forward declare the do_process_cXXX_delay_queue function in the .ino file of the controller itself. -// If someone finds a better way, please let me know. -// See: https://github.com/platformio/platformio-core/issues/2972 -// -// N.B. These queue element classes should be defined as class (not a struct), to be used as template. -// - - - - -// Uncrustify must not be used on macros, so turn it off. -// Also make sure to wrap the forward declaration of this function in the same wrappers -// as it may not split the forward declaration into multiple lines. -// -// *INDENT-OFF* - - - -// Define the function wrappers to handle the calling to Cxxx_DelayHandler etc. -// If someone knows how to add leading zeros in macros, please be my guest :) - - -// This macro defines the code needed to create the 'process_c##NNN####M##_delay_queue()' -// function and all needed objects and forward declarations. -// It is a macro to prevent common typo errors. -// This function will perform the (re)scheduling and mark if it is processed (and can be removed) -// The controller itself must implement the 'do_process_c004_delay_queue' function to actually -// send the data. -// Its return value must state whether it can be marked 'Processed'. -// N.B. some controllers only can send one value per iteration, so a returned "false" can mean it -// was still successful. The controller should keep track of the last value sent -// in the element stored in the queue. -#define DEFINE_Cxxx_DELAY_QUEUE_MACRO(NNN, M) \ - extern struct ControllerDelayHandlerStruct *C##NNN####M##_DelayHandler; \ - bool do_process_c##NNN####M##_delay_queue(cpluginID_t cpluginID, const Queue_element_base & element, ControllerSettingsStruct & ControllerSettings); \ - void process_c##NNN####M##_delay_queue(); \ - bool init_c##NNN####M##_delay_queue(controllerIndex_t ControllerIndex); \ - void exit_c##NNN####M##_delay_queue(); \ - - -# ifdef USE_SECOND_HEAP - -#define DEFINE_Cxxx_DELAY_QUEUE_MACRO_CPP(NNN, M) \ - ControllerDelayHandlerStruct *C##NNN####M##_DelayHandler = nullptr; \ - void process_c##NNN####M##_delay_queue() { \ - if (C##NNN####M##_DelayHandler == nullptr) return; \ - C##NNN####M##_DelayHandler->process( \ - M, do_process_c##NNN####M##_delay_queue, TimingStatsElements::C##NNN####M##_DELAY_QUEUE, \ - SchedulerIntervalTimer_e::TIMER_C##NNN####M##_DELAY_QUEUE); \ - } \ - bool init_c##NNN####M##_delay_queue(controllerIndex_t ControllerIndex) { \ - if (C##NNN####M##_DelayHandler == nullptr) { \ - HeapSelectDram ephemeral; \ - C##NNN####M##_DelayHandler = new (std::nothrow) (ControllerDelayHandlerStruct); \ - } \ - if (C##NNN####M##_DelayHandler == nullptr) { return false; } \ - return C##NNN####M##_DelayHandler->cacheControllerSettings(ControllerIndex); \ - } \ - void exit_c##NNN####M##_delay_queue() { \ - if (C##NNN####M##_DelayHandler != nullptr) { \ - delete C##NNN####M##_DelayHandler; \ - C##NNN####M##_DelayHandler = nullptr; \ - } \ - } \ - -#else - -#define DEFINE_Cxxx_DELAY_QUEUE_MACRO_CPP(NNN, M) \ - ControllerDelayHandlerStruct *C##NNN####M##_DelayHandler = nullptr; \ - void process_c##NNN####M##_delay_queue() { \ - if (C##NNN####M##_DelayHandler == nullptr) return; \ - C##NNN####M##_DelayHandler->process( \ - M, do_process_c##NNN####M##_delay_queue, TimingStatsElements::C##NNN####M##_DELAY_QUEUE, \ - SchedulerIntervalTimer_e::TIMER_C##NNN####M##_DELAY_QUEUE); \ - } \ - bool init_c##NNN####M##_delay_queue(controllerIndex_t ControllerIndex) { \ - if (C##NNN####M##_DelayHandler == nullptr) { \ - C##NNN####M##_DelayHandler = new (std::nothrow) (ControllerDelayHandlerStruct); \ - } \ - if (C##NNN####M##_DelayHandler == nullptr) { return false; } \ - return C##NNN####M##_DelayHandler->cacheControllerSettings(ControllerIndex); \ - } \ - void exit_c##NNN####M##_delay_queue() { \ - if (C##NNN####M##_DelayHandler != nullptr) { \ - delete C##NNN####M##_DelayHandler; \ - C##NNN####M##_DelayHandler = nullptr; \ - } \ - } \ - - -#endif - - - -// Uncrustify must not be used on macros, but we're now done, so turn Uncrustify on again. -// *INDENT-ON* - - - - -#if FEATURE_MQTT -# include "../ControllerQueue/MQTT_queue_element.h" -extern struct ControllerDelayHandlerStruct *MQTTDelayHandler; - -bool init_mqtt_delay_queue(controllerIndex_t ControllerIndex, - String & pubname, - bool & retainFlag); -void exit_mqtt_delay_queue(); -#endif // if FEATURE_MQTT - - -/*********************************************************************************************\ -* C001_queue_element for queueing requests for C001. -\*********************************************************************************************/ -#ifdef USES_C001 -# include "../ControllerQueue/SimpleQueueElement_string_only.h" -typedef simple_queue_element_string_only C001_queue_element; -DEFINE_Cxxx_DELAY_QUEUE_MACRO(00, 1) -#endif // ifdef USES_C001 - -/*********************************************************************************************\ -* C003_queue_element for queueing requests for C003 Nodo Telnet. -\*********************************************************************************************/ -#ifdef USES_C003 -# include "../ControllerQueue/SimpleQueueElement_string_only.h" -typedef simple_queue_element_string_only C003_queue_element; -DEFINE_Cxxx_DELAY_QUEUE_MACRO(00, 3) -#endif // ifdef USES_C003 - -#ifdef USES_C004 -# include "../ControllerQueue/SimpleQueueElement_formatted_Strings.h" -typedef SimpleQueueElement_formatted_Strings C004_queue_element; -DEFINE_Cxxx_DELAY_QUEUE_MACRO(00, 4) -#endif // ifdef USES_C004 - -#ifdef USES_C007 -# include "../ControllerQueue/SimpleQueueElement_formatted_Strings.h" -typedef SimpleQueueElement_formatted_Strings C007_queue_element; -DEFINE_Cxxx_DELAY_QUEUE_MACRO(00, 7) -#endif // ifdef USES_C007 - - -/*********************************************************************************************\ -* C008_queue_element for queueing requests for 008: Generic HTTP -* Using SimpleQueueElement_formatted_Strings -\*********************************************************************************************/ -#ifdef USES_C008 -# include "../ControllerQueue/SimpleQueueElement_formatted_Strings.h" -typedef SimpleQueueElement_formatted_Strings C008_queue_element; -DEFINE_Cxxx_DELAY_QUEUE_MACRO(00, 8) -#endif // ifdef USES_C008 - -#ifdef USES_C009 -# include "../ControllerQueue/SimpleQueueElement_formatted_Strings.h" -typedef SimpleQueueElement_formatted_Strings C009_queue_element; -DEFINE_Cxxx_DELAY_QUEUE_MACRO(00, 9) -#endif // ifdef USES_C009 - - -/*********************************************************************************************\ -* C010_queue_element for queueing requests for 010: Generic UDP -* Using SimpleQueueElement_formatted_Strings -\*********************************************************************************************/ -#ifdef USES_C010 -# include "../ControllerQueue/SimpleQueueElement_formatted_Strings.h" -typedef SimpleQueueElement_formatted_Strings C010_queue_element; -DEFINE_Cxxx_DELAY_QUEUE_MACRO(0, 10) -#endif // ifdef USES_C010 - - -/*********************************************************************************************\ -* C011_queue_element for queueing requests for 011: Generic HTTP Advanced -\*********************************************************************************************/ -#ifdef USES_C011 -# include "../ControllerQueue/C011_queue_element.h" -DEFINE_Cxxx_DELAY_QUEUE_MACRO(0, 11) -#endif // ifdef USES_C011 - - -/*********************************************************************************************\ -* C012_queue_element for queueing requests for 012: Blynk -* Using SimpleQueueElement_formatted_Strings -\*********************************************************************************************/ -#ifdef USES_C012 -# include "../ControllerQueue/SimpleQueueElement_formatted_Strings.h" -typedef SimpleQueueElement_formatted_Strings C012_queue_element; -DEFINE_Cxxx_DELAY_QUEUE_MACRO(0, 12) -#endif // ifdef USES_C012 - -/* - #ifdef USES_C013 - DEFINE_Cxxx_DELAY_QUEUE_MACRO(0, 13) - #endif - */ - -/* - #ifdef USES_C014 - DEFINE_Cxxx_DELAY_QUEUE_MACRO(0, 14) - #endif - */ - - -#ifdef USES_C015 -# include "../ControllerQueue/C015_queue_element.h" -DEFINE_Cxxx_DELAY_QUEUE_MACRO(0, 15) -#endif // ifdef USES_C015 - - -#ifdef USES_C016 -# include "../ControllerQueue/C016_queue_element.h" -DEFINE_Cxxx_DELAY_QUEUE_MACRO(0, 16) -#endif // ifdef USES_C016 - - -#ifdef USES_C017 -# include "../ControllerQueue/SimpleQueueElement_formatted_Strings.h" -typedef SimpleQueueElement_formatted_Strings C017_queue_element; -DEFINE_Cxxx_DELAY_QUEUE_MACRO(0, 17) -#endif // ifdef USES_C017 - -#ifdef USES_C018 -# include "../ControllerQueue/C018_queue_element.h" -DEFINE_Cxxx_DELAY_QUEUE_MACRO(0, 18) -#endif // ifdef USES_C018 - - -/* - #ifdef USES_C019 - DEFINE_Cxxx_DELAY_QUEUE_MACRO(0, 19) - #endif - */ - -/* - #ifdef USES_C020 - DEFINE_Cxxx_DELAY_QUEUE_MACRO(0, 20) - #endif - */ - -/* - #ifdef USES_C021 - DEFINE_Cxxx_DELAY_QUEUE_MACRO(0, 21) - #endif - */ - -/* - #ifdef USES_C022 - DEFINE_Cxxx_DELAY_QUEUE_MACRO(0, 22) - #endif - */ - -/* - #ifdef USES_C023 - DEFINE_Cxxx_DELAY_QUEUE_MACRO(0, 23) - #endif - */ - -/* - #ifdef USES_C024 - DEFINE_Cxxx_DELAY_QUEUE_MACRO(0, 24) - #endif - */ - -/* - #ifdef USES_C025 - DEFINE_Cxxx_DELAY_QUEUE_MACRO(0, 25) - #endif - */ - - -// When extending this, search for EXTEND_CONTROLLER_IDS -// in the code to find all places that need to be updated too. - - -#endif // ifndef DELAY_QUEUE_ELEMENTS_H +#ifndef DELAY_QUEUE_ELEMENTS_H +#define DELAY_QUEUE_ELEMENTS_H + + +#include "../../ESPEasy_common.h" + + +#include "../ControllerQueue/ControllerDelayHandlerStruct.h" +#include "../ControllerQueue/Queue_element_base.h" +#include "../DataStructs/ControllerSettingsStruct.h" + + +// The most logical place to have these queue element handlers defined would be in their +// respective _Cxxx.ino file. +// But the PlatformIO/Arduino build process may then run into issues when compiling. +// Either some of the functions may not be (forward) declared yet when being called from the scheduler code. +// Or the forward declaration of a function may be generated by the pre-processor when expanding the macro to generate them. +// The #ifdef USES_Cxxx check is then no longer present in the generated ESPEasy.ino.cpp file which will lead to build errors +// when not all controllers are included in the build. +// +// To overcome build errors, one MUST forward declare the do_process_cXXX_delay_queue function in the .ino file of the controller itself. +// If someone finds a better way, please let me know. +// See: https://github.com/platformio/platformio-core/issues/2972 +// +// N.B. These queue element classes should be defined as class (not a struct), to be used as template. +// + + + + +// Uncrustify must not be used on macros, so turn it off. +// Also make sure to wrap the forward declaration of this function in the same wrappers +// as it may not split the forward declaration into multiple lines. +// +// *INDENT-OFF* + + + +// Define the function wrappers to handle the calling to Cxxx_DelayHandler etc. +// If someone knows how to add leading zeros in macros, please be my guest :) + + +// This macro defines the code needed to create the 'process_c##NNN####M##_delay_queue()' +// function and all needed objects and forward declarations. +// It is a macro to prevent common typo errors. +// This function will perform the (re)scheduling and mark if it is processed (and can be removed) +// The controller itself must implement the 'do_process_c004_delay_queue' function to actually +// send the data. +// Its return value must state whether it can be marked 'Processed'. +// N.B. some controllers only can send one value per iteration, so a returned "false" can mean it +// was still successful. The controller should keep track of the last value sent +// in the element stored in the queue. +#define DEFINE_Cxxx_DELAY_QUEUE_MACRO(NNN, M) \ + extern struct ControllerDelayHandlerStruct *C##NNN####M##_DelayHandler; \ + bool do_process_c##NNN####M##_delay_queue(cpluginID_t cpluginID, const Queue_element_base & element, ControllerSettingsStruct & ControllerSettings); \ + void process_c##NNN####M##_delay_queue(); \ + bool init_c##NNN####M##_delay_queue(controllerIndex_t ControllerIndex); \ + void exit_c##NNN####M##_delay_queue(); \ + + +# ifdef USE_SECOND_HEAP + +#define DEFINE_Cxxx_DELAY_QUEUE_MACRO_CPP(NNN, M) \ + ControllerDelayHandlerStruct *C##NNN####M##_DelayHandler = nullptr; \ + void process_c##NNN####M##_delay_queue() { \ + if (C##NNN####M##_DelayHandler == nullptr) return; \ + C##NNN####M##_DelayHandler->process( \ + M, do_process_c##NNN####M##_delay_queue, TimingStatsElements::C##NNN####M##_DELAY_QUEUE, \ + SchedulerIntervalTimer_e::TIMER_C##NNN####M##_DELAY_QUEUE); \ + } \ + bool init_c##NNN####M##_delay_queue(controllerIndex_t ControllerIndex) { \ + if (C##NNN####M##_DelayHandler == nullptr) { \ + HeapSelectDram ephemeral; \ + C##NNN####M##_DelayHandler = new (std::nothrow) (ControllerDelayHandlerStruct); \ + } \ + if (C##NNN####M##_DelayHandler == nullptr) { return false; } \ + return C##NNN####M##_DelayHandler->cacheControllerSettings(ControllerIndex); \ + } \ + void exit_c##NNN####M##_delay_queue() { \ + if (C##NNN####M##_DelayHandler != nullptr) { \ + delete C##NNN####M##_DelayHandler; \ + C##NNN####M##_DelayHandler = nullptr; \ + } \ + } \ + +#else + +#define DEFINE_Cxxx_DELAY_QUEUE_MACRO_CPP(NNN, M) \ + ControllerDelayHandlerStruct *C##NNN####M##_DelayHandler = nullptr; \ + void process_c##NNN####M##_delay_queue() { \ + if (C##NNN####M##_DelayHandler == nullptr) return; \ + C##NNN####M##_DelayHandler->process( \ + M, do_process_c##NNN####M##_delay_queue, TimingStatsElements::C##NNN####M##_DELAY_QUEUE, \ + SchedulerIntervalTimer_e::TIMER_C##NNN####M##_DELAY_QUEUE); \ + } \ + bool init_c##NNN####M##_delay_queue(controllerIndex_t ControllerIndex) { \ + if (C##NNN####M##_DelayHandler == nullptr) { \ + C##NNN####M##_DelayHandler = new (std::nothrow) (ControllerDelayHandlerStruct); \ + } \ + if (C##NNN####M##_DelayHandler == nullptr) { return false; } \ + return C##NNN####M##_DelayHandler->cacheControllerSettings(ControllerIndex); \ + } \ + void exit_c##NNN####M##_delay_queue() { \ + if (C##NNN####M##_DelayHandler != nullptr) { \ + delete C##NNN####M##_DelayHandler; \ + C##NNN####M##_DelayHandler = nullptr; \ + } \ + } \ + + +#endif + + + +// Uncrustify must not be used on macros, but we're now done, so turn Uncrustify on again. +// *INDENT-ON* + + + + +#if FEATURE_MQTT +# include "../ControllerQueue/MQTT_queue_element.h" +extern struct ControllerDelayHandlerStruct *MQTTDelayHandler; + +bool init_mqtt_delay_queue(controllerIndex_t ControllerIndex, + String & pubname, + bool & retainFlag); +void exit_mqtt_delay_queue(); +#endif // if FEATURE_MQTT + + +/*********************************************************************************************\ +* C001_queue_element for queueing requests for C001. +\*********************************************************************************************/ +#ifdef USES_C001 +# include "../ControllerQueue/SimpleQueueElement_string_only.h" +typedef simple_queue_element_string_only C001_queue_element; +DEFINE_Cxxx_DELAY_QUEUE_MACRO(00, 1) +#endif // ifdef USES_C001 + +/*********************************************************************************************\ +* C003_queue_element for queueing requests for C003 Nodo Telnet. +\*********************************************************************************************/ +#ifdef USES_C003 +# include "../ControllerQueue/SimpleQueueElement_string_only.h" +typedef simple_queue_element_string_only C003_queue_element; +DEFINE_Cxxx_DELAY_QUEUE_MACRO(00, 3) +#endif // ifdef USES_C003 + +#ifdef USES_C004 +# include "../ControllerQueue/SimpleQueueElement_formatted_Strings.h" +typedef SimpleQueueElement_formatted_Strings C004_queue_element; +DEFINE_Cxxx_DELAY_QUEUE_MACRO(00, 4) +#endif // ifdef USES_C004 + +#ifdef USES_C007 +# include "../ControllerQueue/SimpleQueueElement_formatted_Strings.h" +typedef SimpleQueueElement_formatted_Strings C007_queue_element; +DEFINE_Cxxx_DELAY_QUEUE_MACRO(00, 7) +#endif // ifdef USES_C007 + + +/*********************************************************************************************\ +* C008_queue_element for queueing requests for 008: Generic HTTP +* Using SimpleQueueElement_formatted_Strings +\*********************************************************************************************/ +#ifdef USES_C008 +# include "../ControllerQueue/SimpleQueueElement_formatted_Strings.h" +typedef SimpleQueueElement_formatted_Strings C008_queue_element; +DEFINE_Cxxx_DELAY_QUEUE_MACRO(00, 8) +#endif // ifdef USES_C008 + +#ifdef USES_C009 +# include "../ControllerQueue/SimpleQueueElement_formatted_Strings.h" +typedef SimpleQueueElement_formatted_Strings C009_queue_element; +DEFINE_Cxxx_DELAY_QUEUE_MACRO(00, 9) +#endif // ifdef USES_C009 + + +/*********************************************************************************************\ +* C010_queue_element for queueing requests for 010: Generic UDP +* Using SimpleQueueElement_formatted_Strings +\*********************************************************************************************/ +#ifdef USES_C010 +# include "../ControllerQueue/SimpleQueueElement_formatted_Strings.h" +typedef SimpleQueueElement_formatted_Strings C010_queue_element; +DEFINE_Cxxx_DELAY_QUEUE_MACRO(0, 10) +#endif // ifdef USES_C010 + + +/*********************************************************************************************\ +* C011_queue_element for queueing requests for 011: Generic HTTP Advanced +\*********************************************************************************************/ +#ifdef USES_C011 +# include "../ControllerQueue/C011_queue_element.h" +DEFINE_Cxxx_DELAY_QUEUE_MACRO(0, 11) +#endif // ifdef USES_C011 + + +/*********************************************************************************************\ +* C012_queue_element for queueing requests for 012: Blynk +* Using SimpleQueueElement_formatted_Strings +\*********************************************************************************************/ +#ifdef USES_C012 +# include "../ControllerQueue/SimpleQueueElement_formatted_Strings.h" +typedef SimpleQueueElement_formatted_Strings C012_queue_element; +DEFINE_Cxxx_DELAY_QUEUE_MACRO(0, 12) +#endif // ifdef USES_C012 + +/* + #ifdef USES_C013 + DEFINE_Cxxx_DELAY_QUEUE_MACRO(0, 13) + #endif + */ + +/* + #ifdef USES_C014 + DEFINE_Cxxx_DELAY_QUEUE_MACRO(0, 14) + #endif + */ + + +#ifdef USES_C015 +# include "../ControllerQueue/C015_queue_element.h" +DEFINE_Cxxx_DELAY_QUEUE_MACRO(0, 15) +#endif // ifdef USES_C015 + + +#ifdef USES_C016 +# include "../ControllerQueue/C016_queue_element.h" +DEFINE_Cxxx_DELAY_QUEUE_MACRO(0, 16) +#endif // ifdef USES_C016 + + +#ifdef USES_C017 +# include "../ControllerQueue/SimpleQueueElement_formatted_Strings.h" +typedef SimpleQueueElement_formatted_Strings C017_queue_element; +DEFINE_Cxxx_DELAY_QUEUE_MACRO(0, 17) +#endif // ifdef USES_C017 + +#ifdef USES_C018 +# include "../ControllerQueue/C018_queue_element.h" +DEFINE_Cxxx_DELAY_QUEUE_MACRO(0, 18) +#endif // ifdef USES_C018 + + +#ifdef USES_C019 +# include "../ControllerQueue/C019_queue_element.h" +DEFINE_Cxxx_DELAY_QUEUE_MACRO(0, 19) +#endif // ifdef USES_C019 + + +/* + #ifdef USES_C020 + DEFINE_Cxxx_DELAY_QUEUE_MACRO(0, 20) + #endif + */ + +/* + #ifdef USES_C021 + DEFINE_Cxxx_DELAY_QUEUE_MACRO(0, 21) + #endif + */ + +/* + #ifdef USES_C022 + DEFINE_Cxxx_DELAY_QUEUE_MACRO(0, 22) + #endif + */ + +/* + #ifdef USES_C023 + DEFINE_Cxxx_DELAY_QUEUE_MACRO(0, 23) + #endif + */ + +/* + #ifdef USES_C024 + DEFINE_Cxxx_DELAY_QUEUE_MACRO(0, 24) + #endif + */ + +/* + #ifdef USES_C025 + DEFINE_Cxxx_DELAY_QUEUE_MACRO(0, 25) + #endif + */ + + +// When extending this, search for EXTEND_CONTROLLER_IDS +// in the code to find all places that need to be updated too. + + +#endif // ifndef DELAY_QUEUE_ELEMENTS_H diff --git a/src/src/ControllerQueue/MQTT_queue_element.h b/src/src/ControllerQueue/MQTT_queue_element.h index 4e0436cb56..46cb47d0ff 100644 --- a/src/src/ControllerQueue/MQTT_queue_element.h +++ b/src/src/ControllerQueue/MQTT_queue_element.h @@ -1,60 +1,60 @@ -#ifndef CONTROLLERQUEUE_MQTT_QUEUE_ELEMENT_H -#define CONTROLLERQUEUE_MQTT_QUEUE_ELEMENT_H - -#include "../../ESPEasy_common.h" - -#if FEATURE_MQTT - -# include "../ControllerQueue/Queue_element_base.h" -# include "../DataStructs/UnitMessageCount.h" -# include "../Globals/CPlugins.h" - -/*********************************************************************************************\ -* MQTT_queue_element for all MQTT base controllers -\*********************************************************************************************/ -class MQTT_queue_element : public Queue_element_base { -public: - - MQTT_queue_element() = default; - - MQTT_queue_element(const MQTT_queue_element& other) = delete; - - MQTT_queue_element(MQTT_queue_element&& other) = default; - - explicit MQTT_queue_element(int ctrl_idx, - taskIndex_t TaskIndex, - const String& topic, - const String& payload, - bool retained, - bool callbackTask); - - explicit MQTT_queue_element(int ctrl_idx, - taskIndex_t TaskIndex, - String && topic, - String && payload, - bool retained, - bool callbackTask); - - size_t getSize() const; - - bool isDuplicate(const Queue_element_base& other) const; - - const UnitMessageCount_t* getUnitMessageCount() const { - return &UnitMessageCount; - } - - UnitMessageCount_t* getUnitMessageCount() { - return &UnitMessageCount; - } - - void removeEmptyTopics(); - - String _topic{}; - String _payload{}; - UnitMessageCount_t UnitMessageCount{}; - bool _retained = false; -}; - -#endif // if FEATURE_MQTT - -#endif // CONTROLLERQUEUE_MQTT_QUEUE_ELEMENT_H +#ifndef CONTROLLERQUEUE_MQTT_QUEUE_ELEMENT_H +#define CONTROLLERQUEUE_MQTT_QUEUE_ELEMENT_H + +#include "../../ESPEasy_common.h" + +#if FEATURE_MQTT + +# include "../ControllerQueue/Queue_element_base.h" +# include "../Globals/CPlugins.h" +#include "../DataStructs/MessageRouteInfo.h" + +/*********************************************************************************************\ +* MQTT_queue_element for all MQTT base controllers +\*********************************************************************************************/ +class MQTT_queue_element : public Queue_element_base { +public: + + MQTT_queue_element() = default; + + MQTT_queue_element(const MQTT_queue_element& other) = delete; + + MQTT_queue_element(MQTT_queue_element&& other) = default; + + explicit MQTT_queue_element(int ctrl_idx, + taskIndex_t TaskIndex, + const String& topic, + const String& payload, + bool retained, + bool callbackTask); + + explicit MQTT_queue_element(int ctrl_idx, + taskIndex_t TaskIndex, + String && topic, + String && payload, + bool retained, + bool callbackTask); + + size_t getSize() const; + + bool isDuplicate(const Queue_element_base& other) const; + + const MessageRouteInfo_t* getMessageRouteInfo() const { + return &MessageRouteInfo; + } + + MessageRouteInfo_t* getMessageRouteInfo() { + return &MessageRouteInfo; + } + + void removeEmptyTopics(); + + String _topic{}; + String _payload{}; + bool _retained = false; + MessageRouteInfo_t MessageRouteInfo{}; +}; + +#endif // if FEATURE_MQTT + +#endif // CONTROLLERQUEUE_MQTT_QUEUE_ELEMENT_H diff --git a/src/src/ControllerQueue/Queue_element_base.h b/src/src/ControllerQueue/Queue_element_base.h index 68e5f6a432..7f529c1e34 100644 --- a/src/src/ControllerQueue/Queue_element_base.h +++ b/src/src/ControllerQueue/Queue_element_base.h @@ -1,39 +1,40 @@ -#ifndef CONTROLLERQUEUE_QUEUE_ELEMENT_BASE_H -#define CONTROLLERQUEUE_QUEUE_ELEMENT_BASE_H - - -#include "../../ESPEasy_common.h" - -#include "../DataStructs/UnitMessageCount.h" -#include "../Globals/CPlugins.h" - -/*********************************************************************************************\ -* Base class for all controller queue elements -\*********************************************************************************************/ -class Queue_element_base { -public: - Queue_element_base(); - - virtual ~Queue_element_base(); - - virtual size_t getSize() const = 0; - - virtual bool isDuplicate(const Queue_element_base& other) const = 0; - - virtual const UnitMessageCount_t* getUnitMessageCount() const = 0; - virtual UnitMessageCount_t * getUnitMessageCount() = 0; - - unsigned long _timestamp; - controllerIndex_t _controller_idx; - taskIndex_t _taskIndex; - - // Call PLUGIN_PROCESS_CONTROLLER_DATA which may process the data. - // Typical use case is dumping large data which would otherwise take up lot of RAM. - bool _call_PLUGIN_PROCESS_CONTROLLER_DATA; - - // Some formatting of values can be done when actually sending it. - // This may require less RAM than keeping formatted strings in memory - bool _processByController; -}; - -#endif // ifndef CONTROLLERQUEUE_QUEUE_ELEMENT_BASE_H +#ifndef CONTROLLERQUEUE_QUEUE_ELEMENT_BASE_H +#define CONTROLLERQUEUE_QUEUE_ELEMENT_BASE_H + + +#include "../../ESPEasy_common.h" + +#include "../DataStructs/MessageRouteInfo.h" +#include "../Globals/CPlugins.h" + +/*********************************************************************************************\ +* Base class for all controller queue elements +\*********************************************************************************************/ +class Queue_element_base { +public: + Queue_element_base(); + + virtual ~Queue_element_base(); + + virtual size_t getSize() const = 0; + + virtual bool isDuplicate(const Queue_element_base& other) const = 0; + + virtual const MessageRouteInfo_t* getMessageRouteInfo() const = 0; + virtual MessageRouteInfo_t * getMessageRouteInfo() = 0; + + unsigned long _timestamp; + controllerIndex_t _controller_idx; + taskIndex_t _taskIndex; + + // Call PLUGIN_PROCESS_CONTROLLER_DATA which may process the data. + // Typical use case is dumping large data which would otherwise take up lot of RAM. + bool _call_PLUGIN_PROCESS_CONTROLLER_DATA; + + // Some formatting of values can be done when actually sending it. + // This may require less RAM than keeping formatted strings in memory + bool _processByController; + +}; + +#endif // ifndef CONTROLLERQUEUE_QUEUE_ELEMENT_BASE_H diff --git a/src/src/ControllerQueue/SimpleQueueElement_formatted_Strings.h b/src/src/ControllerQueue/SimpleQueueElement_formatted_Strings.h index f812063e5e..dbc2bc43dd 100644 --- a/src/src/ControllerQueue/SimpleQueueElement_formatted_Strings.h +++ b/src/src/ControllerQueue/SimpleQueueElement_formatted_Strings.h @@ -1,62 +1,62 @@ -#ifndef CONTROLQUEUE_QUEUE_ELEMENT_SINGLE_VALUE_BASE_H -#define CONTROLQUEUE_QUEUE_ELEMENT_SINGLE_VALUE_BASE_H - - -#include "../../ESPEasy_common.h" -#include "../ControllerQueue/Queue_element_base.h" -#include "../CustomBuild/ESPEasyLimits.h" -#include "../DataStructs/DeviceStruct.h" -#include "../DataStructs/UnitMessageCount.h" -#include "../Globals/CPlugins.h" -#include "../Globals/Plugins.h" - -struct EventStruct; - -/*********************************************************************************************\ -* Base element class for keeping task value strings in a controller queue -* Can also be used for controllers only sending a single value at a time. -\*********************************************************************************************/ -class SimpleQueueElement_formatted_Strings : public Queue_element_base { -public: - - SimpleQueueElement_formatted_Strings() = default; - - // Constructor formatting the task values using the default formatter - SimpleQueueElement_formatted_Strings(struct EventStruct *event); - - // Constructor not formatting the values - SimpleQueueElement_formatted_Strings(const struct EventStruct *event, - uint8_t value_count); - - - SimpleQueueElement_formatted_Strings(const SimpleQueueElement_formatted_Strings& rval) = delete; - - SimpleQueueElement_formatted_Strings(SimpleQueueElement_formatted_Strings&& rval); - - SimpleQueueElement_formatted_Strings& operator=(SimpleQueueElement_formatted_Strings&& other); - - - // For controllers that only send a single value per request and thus need to keep track of the number of values already sent. - bool checkDone(bool succesfull) const; - - size_t getSize() const; - - bool isDuplicate(const Queue_element_base& other) const; - - const UnitMessageCount_t* getUnitMessageCount() const { - return nullptr; - } - - UnitMessageCount_t* getUnitMessageCount() { - return nullptr; - } - - String txt[VARS_PER_TASK] = {}; - int idx = 0; - Sensor_VType sensorType = Sensor_VType::SENSOR_TYPE_NONE; - mutable uint8_t valuesSent = 0; // Value must be set by const function checkDone() - uint8_t valueCount = 0; -}; - - -#endif // CONTROLQUEUE_QUEUE_ELEMENT_SINGLE_VALUE_BASE_H +#ifndef CONTROLQUEUE_QUEUE_ELEMENT_SINGLE_VALUE_BASE_H +#define CONTROLQUEUE_QUEUE_ELEMENT_SINGLE_VALUE_BASE_H + + +#include "../../ESPEasy_common.h" +#include "../ControllerQueue/Queue_element_base.h" +#include "../CustomBuild/ESPEasyLimits.h" +#include "../DataStructs/DeviceStruct.h" +#include "../DataStructs/MessageRouteInfo.h" +#include "../Globals/CPlugins.h" +#include "../Globals/Plugins.h" + +struct EventStruct; + +/*********************************************************************************************\ +* Base element class for keeping task value strings in a controller queue +* Can also be used for controllers only sending a single value at a time. +\*********************************************************************************************/ +class SimpleQueueElement_formatted_Strings : public Queue_element_base { +public: + + SimpleQueueElement_formatted_Strings() = default; + + // Constructor formatting the task values using the default formatter + SimpleQueueElement_formatted_Strings(struct EventStruct *event); + + // Constructor not formatting the values + SimpleQueueElement_formatted_Strings(const struct EventStruct *event, + uint8_t value_count); + + + SimpleQueueElement_formatted_Strings(const SimpleQueueElement_formatted_Strings& rval) = delete; + + SimpleQueueElement_formatted_Strings(SimpleQueueElement_formatted_Strings&& rval); + + SimpleQueueElement_formatted_Strings& operator=(SimpleQueueElement_formatted_Strings&& other); + + + // For controllers that only send a single value per request and thus need to keep track of the number of values already sent. + bool checkDone(bool succesfull) const; + + size_t getSize() const; + + bool isDuplicate(const Queue_element_base& other) const; + + const MessageRouteInfo_t * getMessageRouteInfo() const { + return nullptr; + } + + MessageRouteInfo_t* getMessageRouteInfo() { + return nullptr; + } + + String txt[VARS_PER_TASK] = {}; + int idx = 0; + Sensor_VType sensorType = Sensor_VType::SENSOR_TYPE_NONE; + mutable uint8_t valuesSent = 0; // Value must be set by const function checkDone() + uint8_t valueCount = 0; +}; + + +#endif // CONTROLQUEUE_QUEUE_ELEMENT_SINGLE_VALUE_BASE_H diff --git a/src/src/ControllerQueue/SimpleQueueElement_string_only.h b/src/src/ControllerQueue/SimpleQueueElement_string_only.h index 488eeabe31..56666f6fa0 100644 --- a/src/src/ControllerQueue/SimpleQueueElement_string_only.h +++ b/src/src/ControllerQueue/SimpleQueueElement_string_only.h @@ -1,42 +1,43 @@ -#ifndef CONTROLLERQUEUE_SIMPLE_QUEUE_ELEMENT_STRING_ONLY_H -#define CONTROLLERQUEUE_SIMPLE_QUEUE_ELEMENT_STRING_ONLY_H - -#include "../../ESPEasy_common.h" -#include "../ControllerQueue/Queue_element_base.h" -#include "../DataStructs/UnitMessageCount.h" -#include "../Globals/CPlugins.h" - - -/*********************************************************************************************\ -* Simple queue element, only storing controller index and some String -\*********************************************************************************************/ -class simple_queue_element_string_only : public Queue_element_base { -public: - - simple_queue_element_string_only() = default; - - simple_queue_element_string_only(const simple_queue_element_string_only& other) = delete; - - simple_queue_element_string_only(simple_queue_element_string_only&& other) = default; - - explicit simple_queue_element_string_only(int ctrl_idx, - taskIndex_t TaskIndex, - String && req); - - size_t getSize() const; - - bool isDuplicate(const Queue_element_base& other) const; - - const UnitMessageCount_t* getUnitMessageCount() const { - return nullptr; - } - - UnitMessageCount_t* getUnitMessageCount() { - return nullptr; - } - - String txt; -}; - - -#endif // CONTROLLERQUEUE_SIMPLE_QUEUE_ELEMENT_STRING_ONLY_H +#ifndef CONTROLLERQUEUE_SIMPLE_QUEUE_ELEMENT_STRING_ONLY_H +#define CONTROLLERQUEUE_SIMPLE_QUEUE_ELEMENT_STRING_ONLY_H + +#include "../../ESPEasy_common.h" +#include "../DataStructs/MessageRouteInfo.h" +#include "../ControllerQueue/Queue_element_base.h" +#include "../DataStructs/MessageRouteInfo.h" +#include "../Globals/CPlugins.h" + + +/*********************************************************************************************\ +* Simple queue element, only storing controller index and some String +\*********************************************************************************************/ +class simple_queue_element_string_only : public Queue_element_base { +public: + + simple_queue_element_string_only() = default; + + simple_queue_element_string_only(const simple_queue_element_string_only& other) = delete; + + simple_queue_element_string_only(simple_queue_element_string_only&& other) = default; + + explicit simple_queue_element_string_only(int ctrl_idx, + taskIndex_t TaskIndex, + String && req); + + size_t getSize() const; + + bool isDuplicate(const Queue_element_base& other) const; + + const MessageRouteInfo_t* getMessageRouteInfo() const { + return nullptr; + } + + MessageRouteInfo_t* getMessageRouteInfo() { + return nullptr; + } + + String txt; +}; + + +#endif // CONTROLLERQUEUE_SIMPLE_QUEUE_ELEMENT_STRING_ONLY_H diff --git a/src/src/Controller_config/C018_config.cpp b/src/src/Controller_config/C018_config.cpp index 77369d0849..be87546599 100644 --- a/src/src/Controller_config/C018_config.cpp +++ b/src/src/Controller_config/C018_config.cpp @@ -1,206 +1,207 @@ -#include "../Controller_config/C018_config.h" - -#ifdef USES_C018 - -# include "../Controller_struct/C018_data_struct.h" - -# define C018_BAUDRATE_LABEL "baudrate" - -void C018_ConfigStruct::validate() { - ZERO_TERMINATE(DeviceEUI); - ZERO_TERMINATE(DeviceAddr); - ZERO_TERMINATE(NetworkSessionKey); - ZERO_TERMINATE(AppSessionKey); - - if ((baudrate < 2400) || (baudrate > 115200)) { - reset(); - } - - if (stackVersion >= RN2xx3_datatypes::TTN_stack_version::TTN_NOT_SET) { - stackVersion = RN2xx3_datatypes::TTN_stack_version::TTN_v3; - } - - switch (frequencyplan) { - case RN2xx3_datatypes::Freq_plan::SINGLE_CHANNEL_EU: - case RN2xx3_datatypes::Freq_plan::TTN_EU: - case RN2xx3_datatypes::Freq_plan::DEFAULT_EU: - - if ((rx2_freq < 867000000) || (rx2_freq > 870000000)) { - rx2_freq = 0; - } - break; - case RN2xx3_datatypes::Freq_plan::TTN_US: - // FIXME TD-er: Need to find the ranges for US (and other regions) - break; - default: - rx2_freq = 0; - break; - } -} - -void C018_ConfigStruct::reset() { - ZERO_FILL(DeviceEUI); - ZERO_FILL(DeviceAddr); - ZERO_FILL(NetworkSessionKey); - ZERO_FILL(AppSessionKey); - baudrate = 57600; - rxpin = -1; - txpin = -1; - resetpin = -1; - sf = 7; - frequencyplan = RN2xx3_datatypes::Freq_plan::TTN_EU; - rx2_freq = 0; - stackVersion = RN2xx3_datatypes::TTN_stack_version::TTN_v3; - joinmethod = C018_USE_OTAA; -} - -void C018_ConfigStruct::webform_load(C018_data_struct *C018_data) { - validate(); - ESPEasySerialPort port = static_cast(serialPort); - - { - addFormTextBox(F("Device EUI"), F("deveui"), DeviceEUI, C018_DEVICE_EUI_LEN - 1); - String deveui_note = F("Leave empty to use HW DevEUI: "); - - if (C018_data != nullptr) { - deveui_note += C018_data->hweui(); - } - addFormNote(deveui_note, F("deveui_note")); - } - - addFormTextBox(F("Device Addr"), F("devaddr"), DeviceAddr, C018_DEVICE_ADDR_LEN - 1); - addFormTextBox(F("Network Session Key"), F("nskey"), NetworkSessionKey, C018_NETWORK_SESSION_KEY_LEN - 1); - addFormTextBox(F("App Session Key"), F("appskey"), AppSessionKey, C018_APP_SESSION_KEY_LEN - 1); - - { - const __FlashStringHelper *options[] = { F("OTAA"), F("ABP") }; - //const int values[] = { C018_USE_OTAA, C018_USE_ABP }; - FormSelectorOptions selector(NR_ELEMENTS(options), options); - // Script to toggle OTAA/ABP fields visibility when changing selection. - selector.onChangeCall = F("joinChanged(this)"); - selector.addFormSelector(F("Activation Method"), F("joinmethod"), joinmethod); - - } - html_add_script(F("document.getElementById('joinmethod').onchange();"), false); - - addTableSeparator(F("Connection Configuration"), 2, 3); - { - const __FlashStringHelper *options[] = { F("SINGLE_CHANNEL_EU"), F("TTN_EU"), F("TTN_US"), F("DEFAULT_EU") }; - int values[] = - { - RN2xx3_datatypes::Freq_plan::SINGLE_CHANNEL_EU, - RN2xx3_datatypes::Freq_plan::TTN_EU, - RN2xx3_datatypes::Freq_plan::TTN_US, - RN2xx3_datatypes::Freq_plan::DEFAULT_EU - }; - const FormSelectorOptions selector( NR_ELEMENTS(options), options, values); - selector.addFormSelector(F("Frequency Plan"), F("frequencyplan"), frequencyplan); - addFormNumericBox(F("RX2 Frequency"), F("rx2freq"), rx2_freq, 0); - addUnit(F("Hz")); - addFormNote(F("0 = default, or else override default")); - } - { - const __FlashStringHelper *options[] = { F("TTN v2"), F("TTN v3") }; - constexpr int values[] { - RN2xx3_datatypes::TTN_stack_version::TTN_v2, - RN2xx3_datatypes::TTN_stack_version::TTN_v3 - }; - const FormSelectorOptions selector(NR_ELEMENTS(options), options, values); - selector.addFormSelector(F("TTN Stack"), F("ttnstack"), stackVersion); - } - - addFormNumericBox(F("Spread Factor"), F("sf"), sf, 7, 12); - addFormCheckBox(F("Adaptive Data Rate (ADR)"), F("adr"), adr); - - - addTableSeparator(F("Serial Port Configuration"), 2, 3); - - serialHelper_webformLoad(port, rxpin, txpin, true); - - // Show serial port selection - addFormPinSelect(PinSelectPurpose::Generic_input, formatGpioName_serialRX(false), F("taskdevicepin1"), rxpin); - addFormPinSelect(PinSelectPurpose::Generic_output, formatGpioName_serialTX(false), F("taskdevicepin2"), txpin); - - html_add_script(F("document.getElementById('serPort').onchange();"), false); - - addFormNumericBox(F("Baudrate"), F(C018_BAUDRATE_LABEL), baudrate, 2400, 115200); - addUnit(F("baud")); - addFormNote(F("Module default baudrate: 57600 bps")); - - // Optional reset pin RN2xx3 - addFormPinSelect(PinSelectPurpose::Generic_output, formatGpioName_output_optional(F("Reset")), F("taskdevicepin3"), resetpin); - - addTableSeparator(F("Device Status"), 2, 3); - - if (C018_data != nullptr) { - // Some information on detected device - addRowLabel(F("Hardware DevEUI")); - addHtml(C018_data->hweui()); - addRowLabel(F("Version Number")); - addHtml(C018_data->sysver()); - - addRowLabel(F("Voltage")); - addHtmlFloat(static_cast(C018_data->getVbat()) / 1000.0f, 3); - - addRowLabel(F("Device Addr")); - addHtml(C018_data->getDevaddr()); - - uint32_t dnctr, upctr; - - if (C018_data->getFrameCounters(dnctr, upctr)) { - addRowLabel(F("Frame Counters (down/up)")); - String values = String(dnctr); - values += '/'; - values += upctr; - addHtml(values); - } - - addRowLabel(F("Last Command Error")); - addHtml(C018_data->getLastError()); - - addRowLabel(F("Sample Set Counter")); - addHtmlInt(static_cast(C018_data->getSampleSetCount())); - - addRowLabel(F("Data Rate")); - addHtml(C018_data->getDataRate()); - - { - RN2xx3_status status = C018_data->getStatus(); - - addRowLabel(F("Status RAW value")); - addHtmlInt(status.getRawStatus()); - - addRowLabel(F("Activation Status")); - addEnabled(status.Joined); - - addRowLabel(F("Silent Immediately")); - addHtmlInt(static_cast(status.SilentImmediately ? 1 : 0)); - } - } -} - -void C018_ConfigStruct::webform_save() { - reset(); - String deveui = webArg(F("deveui")); - String devaddr = webArg(F("devaddr")); - String nskey = webArg(F("nskey")); - String appskey = webArg(F("appskey")); - - strlcpy(DeviceEUI, deveui.c_str(), sizeof(DeviceEUI)); - strlcpy(DeviceAddr, devaddr.c_str(), sizeof(DeviceAddr)); - strlcpy(NetworkSessionKey, nskey.c_str(), sizeof(NetworkSessionKey)); - strlcpy(AppSessionKey, appskey.c_str(), sizeof(AppSessionKey)); - baudrate = getFormItemInt(F(C018_BAUDRATE_LABEL), baudrate); - rxpin = getFormItemInt(F("taskdevicepin1"), rxpin); - txpin = getFormItemInt(F("taskdevicepin2"), txpin); - resetpin = getFormItemInt(F("taskdevicepin3"), resetpin); - sf = getFormItemInt(F("sf"), sf); - frequencyplan = getFormItemInt(F("frequencyplan"), frequencyplan); - rx2_freq = getFormItemInt(F("rx2freq"), rx2_freq); - joinmethod = getFormItemInt(F("joinmethod"), joinmethod); - stackVersion = getFormItemInt(F("ttnstack"), stackVersion); - adr = isFormItemChecked(F("adr")); - serialHelper_webformSave(serialPort, rxpin, txpin); -} - -#endif // ifdef USES_C018 +#include "../Controller_config/C018_config.h" + +#ifdef USES_C018 + +# include "../Controller_struct/C018_data_struct.h" + +# define C018_BAUDRATE_LABEL "baudrate" + +void C018_ConfigStruct::validate() { + ZERO_TERMINATE(DeviceEUI); + ZERO_TERMINATE(DeviceAddr); + ZERO_TERMINATE(NetworkSessionKey); + ZERO_TERMINATE(AppSessionKey); + + if ((baudrate < 2400) || (baudrate > 115200)) { + reset(); + } + + if (stackVersion >= RN2xx3_datatypes::TTN_stack_version::TTN_NOT_SET) { + stackVersion = RN2xx3_datatypes::TTN_stack_version::TTN_v3; + } + + switch (frequencyplan) { + case RN2xx3_datatypes::Freq_plan::SINGLE_CHANNEL_EU: + case RN2xx3_datatypes::Freq_plan::TTN_EU: + case RN2xx3_datatypes::Freq_plan::DEFAULT_EU: + + if ((rx2_freq < 867000000) || (rx2_freq > 870000000)) { + rx2_freq = 0; + } + break; + case RN2xx3_datatypes::Freq_plan::TTN_US: + // FIXME TD-er: Need to find the ranges for US (and other regions) + break; + default: + rx2_freq = 0; + break; + } +} + +void C018_ConfigStruct::reset() { + ZERO_FILL(DeviceEUI); + ZERO_FILL(DeviceAddr); + ZERO_FILL(NetworkSessionKey); + ZERO_FILL(AppSessionKey); + baudrate = 57600; + rxpin = -1; + txpin = -1; + resetpin = -1; + sf = 7; + frequencyplan = RN2xx3_datatypes::Freq_plan::TTN_EU; + rx2_freq = 0; + stackVersion = RN2xx3_datatypes::TTN_stack_version::TTN_v3; + joinmethod = C018_USE_OTAA; +} + +void C018_ConfigStruct::webform_load(C018_data_struct *C018_data) { + validate(); + ESPEasySerialPort port = static_cast(serialPort); + + { + addFormTextBox(F("Device EUI"), F("deveui"), DeviceEUI, C018_DEVICE_EUI_LEN - 1); + String deveui_note = F("Leave empty to use HW DevEUI: "); + + if (C018_data != nullptr) { + deveui_note += C018_data->hweui(); + } + addFormNote(deveui_note, F("deveui_note")); + } + + addFormTextBox(F("Device Addr"), F("devaddr"), DeviceAddr, C018_DEVICE_ADDR_LEN - 1); + addFormTextBox(F("Network Session Key"), F("nskey"), NetworkSessionKey, C018_NETWORK_SESSION_KEY_LEN - 1); + addFormTextBox(F("App Session Key"), F("appskey"), AppSessionKey, C018_APP_SESSION_KEY_LEN - 1); + + { + const __FlashStringHelper *options[] = { F("OTAA"), F("ABP") }; + //const int values[] = { C018_USE_OTAA, C018_USE_ABP }; + FormSelectorOptions selector(NR_ELEMENTS(options), options); + // Script to toggle OTAA/ABP fields visibility when changing selection. + selector.onChangeCall = F("joinChanged(this)"); + selector.addFormSelector(F("Activation Method"), F("joinmethod"), joinmethod); + + } + html_add_script(F("document.getElementById('joinmethod').onchange();"), false); + + addTableSeparator(F("Connection Configuration"), 2, 3); + { + const __FlashStringHelper *options[] = { F("SINGLE_CHANNEL_EU"), F("TTN_EU"), F("TTN_US"), F("DEFAULT_EU") }; + int values[] = + { + RN2xx3_datatypes::Freq_plan::SINGLE_CHANNEL_EU, + RN2xx3_datatypes::Freq_plan::TTN_EU, + RN2xx3_datatypes::Freq_plan::TTN_US, + RN2xx3_datatypes::Freq_plan::DEFAULT_EU + }; + const FormSelectorOptions selector( NR_ELEMENTS(options), options, values); + selector.addFormSelector(F("Frequency Plan"), F("frequencyplan"), frequencyplan); + addFormNumericBox(F("RX2 Frequency"), F("rx2freq"), rx2_freq, 0); + addUnit(F("Hz")); + addFormNote(F("0 = default, or else override default")); + } + { + const __FlashStringHelper *options[] = { F("TTN v2"), F("TTN v3") }; + constexpr int values[] { + RN2xx3_datatypes::TTN_stack_version::TTN_v2, + RN2xx3_datatypes::TTN_stack_version::TTN_v3 + }; + const FormSelectorOptions selector(NR_ELEMENTS(options), options, values); + selector.addFormSelector(F("TTN Stack"), F("ttnstack"), stackVersion); + } + + addFormNumericBox(F("Spread Factor"), F("sf"), sf, 7, 12); + addFormCheckBox(F("Adaptive Data Rate (ADR)"), F("adr"), adr); + + + addTableSeparator(F("Serial Port Configuration"), 2, 3); + + serialHelper_webformLoad(port, rxpin, txpin, true); + + // Show serial port selection + addFormPinSelect(PinSelectPurpose::Generic_input, formatGpioName_serialRX(false), F("taskdevicepin1"), rxpin); + addFormPinSelect(PinSelectPurpose::Generic_output, formatGpioName_serialTX(false), F("taskdevicepin2"), txpin); + + html_add_script(F("document.getElementById('serPort').onchange();"), false); + + addFormNumericBox(F("Baudrate"), F(C018_BAUDRATE_LABEL), baudrate, 2400, 115200); + addUnit(F("baud")); + addFormNote(F("Module default baudrate: 57600 bps")); + + // Optional reset pin RN2xx3 + addFormPinSelect(PinSelectPurpose::Generic_output, formatGpioName_output_optional(F("Reset")), F("taskdevicepin3"), resetpin); + + addTableSeparator(F("Device Status"), 2, 3); + + if (C018_data != nullptr) { + // Some information on detected device + addRowLabel(F("Hardware DevEUI")); + addHtml(C018_data->hweui()); + addRowLabel(F("Version Number")); + addHtml(C018_data->sysver()); + + addRowLabel(F("Voltage")); + addHtmlFloat(static_cast(C018_data->getVbat()) / 1000.0f, 3); + + addRowLabel(F("Device Addr")); + addHtml(C018_data->getDevaddr()); + + uint32_t dnctr, upctr; + + if (C018_data->getFrameCounters(dnctr, upctr)) { + addRowLabel(F("Frame Counters (down/up)")); + String values = String(dnctr); + values += '/'; + values += upctr; + addHtml(values); + } + + addRowLabel(F("Last Command Error")); + addHtml(C018_data->getLastError()); + + addRowLabel(F("Sample Set Counter")); + addHtmlInt(static_cast(C018_data->getSampleSetCount())); + + addRowLabel(F("Data Rate")); + addHtml(C018_data->getDataRate()); + + { + RN2xx3_status status = C018_data->getStatus(); + + addRowLabel(F("Status RAW value")); + addHtmlInt(status.getRawStatus()); + + addRowLabel(F("Activation Status")); + addEnabled(status.Joined); + + addRowLabel(F("Silent Immediately")); + addHtmlInt(static_cast(status.SilentImmediately ? 1 : 0)); + } + } +} + +void C018_ConfigStruct::webform_save() { + reset(); + + const String deveui = webArg(F("deveui")); + const String devaddr = webArg(F("devaddr")); + const String nskey = webArg(F("nskey")); + const String appskey = webArg(F("appskey")); + + strlcpy(DeviceEUI, deveui.c_str(), sizeof(DeviceEUI)); + strlcpy(DeviceAddr, devaddr.c_str(), sizeof(DeviceAddr)); + strlcpy(NetworkSessionKey, nskey.c_str(), sizeof(NetworkSessionKey)); + strlcpy(AppSessionKey, appskey.c_str(), sizeof(AppSessionKey)); + baudrate = getFormItemInt(F(C018_BAUDRATE_LABEL), baudrate); + rxpin = getFormItemInt(F("taskdevicepin1"), rxpin); + txpin = getFormItemInt(F("taskdevicepin2"), txpin); + resetpin = getFormItemInt(F("taskdevicepin3"), resetpin); + sf = getFormItemInt(F("sf"), sf); + frequencyplan = getFormItemInt(F("frequencyplan"), frequencyplan); + rx2_freq = getFormItemInt(F("rx2freq"), rx2_freq); + joinmethod = getFormItemInt(F("joinmethod"), joinmethod); + stackVersion = getFormItemInt(F("ttnstack"), stackVersion); + adr = isFormItemChecked(F("adr")); + serialHelper_webformSave(serialPort, rxpin, txpin); +} + +#endif // ifdef USES_C018 diff --git a/src/src/Controller_config/C019_config.cpp b/src/src/Controller_config/C019_config.cpp new file mode 100644 index 0000000000..e8f3322bf6 --- /dev/null +++ b/src/src/Controller_config/C019_config.cpp @@ -0,0 +1,137 @@ +#include "../Controller_config/C019_config.h" + +#ifdef USES_C019 + +# include "../Globals/Settings.h" + +# define C019_FORWARDFILTERING_ELEMENTS 3 + +void C019_ForwardFiltering::fromStringArray(String strings[], uint8_t filterNr) +{ + const uint8_t index = C019_FORWARDFILTERING_ELEMENTS * filterNr; + + taskIndex = strings[index + 0].toInt(); + matchTopic = std::move(strings[index + 1]); +} + +void C019_ForwardFiltering::toStringArray(String strings[], uint8_t filterNr) const +{ + const uint8_t index = C019_FORWARDFILTERING_ELEMENTS * filterNr; + + strings[index + 0] = taskIndex; + strings[index + 1] = std::move(matchTopic); +} + +void C019_ConfigStruct::validate() { + if (nrTaskFilters > TASKS_MAX) { nrTaskFilters = 0; } + + if (!validControllerIndex(forwardControllerIdx)) { + forwardControllerIdx = INVALID_CONTROLLER_INDEX; + } +} + +void C019_ConfigStruct::reset() { + nrTaskFilters = 0; + filters.clear(); + forwardControllerIdx = INVALID_CONTROLLER_INDEX; +} + +void C019_ConfigStruct::init(struct EventStruct *event) { + constexpr size_t filtersOffset = offsetof(C019_ConfigStruct, filters); + + LoadCustomControllerSettings(event->ControllerIndex, reinterpret_cast(this), filtersOffset); + filters.resize(nrTaskFilters); + { + const int nrStrings = nrTaskFilters * C019_FORWARDFILTERING_ELEMENTS; + String strings[nrStrings]; + LoadStringArray(SettingsType::Enum::CustomControllerSettings_Type, + event->ControllerIndex, + strings, + nrStrings, + 0, + filtersOffset); + + for (uint8_t i = 0; i < nrTaskFilters; ++i) { + filters[i].fromStringArray(strings, i); + } + } + + if (wifiChannel < 0) { + wifiChannel = Settings.ForceESPEasyNOWchannel; + } +} + +void C019_ConfigStruct::webform_load(struct EventStruct *event) { + init(event); + addFormNumericBox(LabelType::ESPEASY_NOW_FORCED_CHANNEL, Settings.ForceESPEasyNOWchannel, 0, 14); + addFormNote(F("Force channel to use for " + ESPEASY_NOW_NAME + "-only mode (0 = use any channel)")); + + addFormCheckBox(F("Forward from " ESPEASY_NOW_NAME " to MQTT"), F("fwd_mqtt"), forwardMQTT); + + addTableSeparator(F("MQTT Forward Filtering"), 2, 3); + addFormCheckBox(F("Filter Forward MQTT"), F("filter_fwd"), filterMQTT_forward); + addFormNumericBox(F("Nr Filter Tasks"), F("nrfiltertasks"), nrTaskFilters, 0, TASKS_MAX); + + for (uint8_t i = 0; i < nrTaskFilters; ++i) { + addTableSeparator(concat(F("Filter "), i + 1), 2, 2); + addFormTextBox(F("Match Topic"), + concat(F("topic_match"), i), + filters[i].matchTopic, + C019_MQTT_TOPIC_LENGTH - 1); + addRowLabel(F("Task")); + addTaskSelect( + concat(F("ftask"), i), + filters[i].taskIndex, + EMPTY_STRING); // No 'wide' selector + } + addFormSeparator(2); +} + +void C019_ConfigStruct::webform_save(struct EventStruct *event) { + reset(); + + forwardMQTT = isFormItemChecked(F("fwd_mqtt")); + filterMQTT_forward = isFormItemChecked(F("filter_fwd")); + nrTaskFilters = getFormItemInt(F("nrfiltertasks")); + + // FIXME TD-er: For now have it as duplicate setting... + Settings.ForceESPEasyNOWchannel = getFormItemInt(getInternalLabel(LabelType::ESPEASY_NOW_FORCED_CHANNEL)); + wifiChannel = Settings.ForceESPEasyNOWchannel; + + + constexpr size_t filtersOffset = offsetof(C019_ConfigStruct, filters); + + SaveCustomControllerSettings(event->ControllerIndex, reinterpret_cast(this), filtersOffset); + + if (nrTaskFilters > 0) { + const int nrStrings = nrTaskFilters * C019_FORWARDFILTERING_ELEMENTS; + String strings[nrStrings]; + + for (uint8_t i = 0; i < nrTaskFilters; ++i) { + C019_ForwardFiltering filter; + + filter.taskIndex = getFormItemInt(concat(F("ftask"), i), INVALID_TASK_INDEX); + filter.matchTopic = webArg(concat(F("topic_match"), i)); + filter.toStringArray(strings, i); + } + SaveStringArray(SettingsType::Enum::CustomControllerSettings_Type, + event->ControllerIndex, + strings, + nrStrings, + 0, + filtersOffset); + } +} + +taskIndex_t C019_ConfigStruct::matchTopic(const String& topic) const +{ + for (auto it = filters.begin(); it != filters.end(); ++it) { + // FIXME TD-er: Must match MQTT topic wildcards + if (topic.indexOf(it->matchTopic) != -1) { return it->taskIndex; } + } + return INVALID_TASK_INDEX; +} + +#endif // ifdef USES_C019 diff --git a/src/src/Controller_config/C019_config.h b/src/src/Controller_config/C019_config.h new file mode 100644 index 0000000000..f368e681b0 --- /dev/null +++ b/src/src/Controller_config/C019_config.h @@ -0,0 +1,69 @@ +#ifndef CONTROLLER_CONFIG_C019_CONFIG_H +#define CONTROLLER_CONFIG_C019_CONFIG_H + +#include "src/Helpers/_CPlugin_Helper.h" + +#ifdef USES_C019 + +# include + +# define C019_MQTT_TOPIC_LENGTH 128 + +struct C019_ForwardFiltering +{ + void fromStringArray(String strings[], + uint8_t filterNr); + void toStringArray(String strings[], + uint8_t filterNr) const; + + taskIndex_t taskIndex = INVALID_TASK_INDEX; + String matchTopic; +}; + +struct C019_ConfigStruct +{ + C019_ConfigStruct() = default; + + void validate(); + + void reset(); + + void init(struct EventStruct *event); + + // Send all to the web interface + void webform_load(struct EventStruct *event); + + // Collect all data from the web interface + void webform_save(struct EventStruct *event); + + taskIndex_t matchTopic(const String& topic) const; + + + uint8_t configVersion = 1; // Format version of the stored data + uint8_t nrTaskFilters = 0; // Number of task/topics to configure for filtering + int8_t wifiChannel = -1; + controllerIndex_t forwardControllerIdx = INVALID_CONTROLLER_INDEX; // Controller index to forward filtered data to + + union { + struct { + uint32_t forwardMQTT : 1; + uint32_t filterMQTT_forward : 1; + + + uint32_t notUsed : 30; // All bits should add up to 32 + }; + + uint32_t variousBits = 0; + }; + + uint32_t reserved = 0; // Need to have the start of 'filters' at the same position. + + std::vectorfilters; +}; + + +typedef std::shared_ptr C019_ConfigStruct_ptr; + +#endif // ifdef USES_C019 + +#endif // ifndef CONTROLLER_CONFIG_C019_CONFIG_H diff --git a/src/src/Controller_struct/C019_data_struct.cpp b/src/src/Controller_struct/C019_data_struct.cpp new file mode 100644 index 0000000000..3f6bc0c7ad --- /dev/null +++ b/src/src/Controller_struct/C019_data_struct.cpp @@ -0,0 +1 @@ +#include "../Controller_struct/C019_data_struct.h" \ No newline at end of file diff --git a/src/src/Controller_struct/C019_data_struct.h b/src/src/Controller_struct/C019_data_struct.h new file mode 100644 index 0000000000..d898c6a2c5 --- /dev/null +++ b/src/src/Controller_struct/C019_data_struct.h @@ -0,0 +1,6 @@ +#ifndef CONTROLLER_STRUCT_C019_DATA_STRUCT_H +#define CONTROLLER_STRUCT_C019_DATA_STRUCT_H + + + +#endif \ No newline at end of file diff --git a/src/src/CustomBuild/ESPEasyDefaults.h b/src/src/CustomBuild/ESPEasyDefaults.h index 9b67a0cecd..85e4db8b30 100644 --- a/src/src/CustomBuild/ESPEasyDefaults.h +++ b/src/src/CustomBuild/ESPEasyDefaults.h @@ -1,500 +1,512 @@ -#ifndef CUSTOMBUILD_ESPEASY_DEFAULTS_H_ -#define CUSTOMBUILD_ESPEASY_DEFAULTS_H_ - -// Needed to make sure Custom.h is used. -#include "../../ESPEasy_common.h" - -#include "../DataTypes/NetworkMedium.h" - -#include "../Helpers/Hardware_defines.h" - -// ******************************************************************************** -// User specific configuration -// ******************************************************************************** - -// Set default configuration settings if you want (not mandatory) -// You can always change these during runtime and save to eeprom -// After loading firmware, issue a 'reset' command to load the defaults. -// --- Basic Config Settings ------------------------------------------------------------------------ -#ifndef DEFAULT_NAME -#define DEFAULT_NAME "ESP_Easy" // Enter your device friendly name -#endif -#ifndef UNIT -#define UNIT 0 // Unit Number -#endif -#ifndef DEFAULT_DELAY -#define DEFAULT_DELAY 60 // Sleep Delay in seconds -#endif - -// --- Wifi AP Mode (when your Wifi Network is not reachable) ---------------------------------------- -#ifndef DEFAULT_AP_IP -#define DEFAULT_AP_IP 192,168,4,1 // Enter IP address (comma separated) for AP (config) mode -#endif -#ifndef DEFAULT_AP_SUBNET -#define DEFAULT_AP_SUBNET 255,255,255,0 // Enter IP address (comma separated) for AP (config) mode -#endif -#ifndef DEFAULT_AP_KEY -#define DEFAULT_AP_KEY "configesp" // Enter network WPA key for AP (config) mode -#endif - -// --- Wifi Client Mode ----------------------------------------------------------------------------- -#ifndef DEFAULT_SSID -#define DEFAULT_SSID "ssid" // Enter your Wifi network SSID -#endif -#ifndef DEFAULT_KEY -#define DEFAULT_KEY "wpakey" // Enter your Wifi network WPA key -#endif -#ifndef DEFAULT_SSID2 -#define DEFAULT_SSID2 "" // Enter your fallback Wifi network SSID -#endif -#ifndef DEFAULT_KEY2 -#define DEFAULT_KEY2 "" // Enter your fallback Wifi network WPA key -#endif -#ifndef DEFAULT_WIFI_INCLUDE_HIDDEN_SSID -#define DEFAULT_WIFI_INCLUDE_HIDDEN_SSID false // Allow to connect to hidden SSID APs -#endif -#ifndef DEFAULT_USE_STATIC_IP -#define DEFAULT_USE_STATIC_IP false // (true|false) enabled or disabled static IP -#endif -#ifndef DEFAULT_IP -#define DEFAULT_IP "192.168.0.50" // Enter your IP address -#endif -#ifndef DEFAULT_DNS -#define DEFAULT_DNS "192.168.0.1" // Enter your DNS -#endif -#ifndef DEFAULT_GW -#define DEFAULT_GW "192.168.0.1" // Enter your Gateway -#endif -#ifndef DEFAULT_SUBNET -#define DEFAULT_SUBNET "255.255.255.0" // Enter your Subnet -#endif -#ifndef DEFAULT_IPRANGE_LOW -#define DEFAULT_IPRANGE_LOW "0.0.0.0" // Allowed IP range to access webserver -#endif -#ifndef DEFAULT_IPRANGE_HIGH -#define DEFAULT_IPRANGE_HIGH "255.255.255.255" // Allowed IP range to access webserver -#endif -#ifndef DEFAULT_IP_BLOCK_LEVEL -#define DEFAULT_IP_BLOCK_LEVEL 1 // 0: ALL_ALLOWED 1: LOCAL_SUBNET_ALLOWED 2: ONLY_IP_RANGE_ALLOWED -#endif -#ifndef DEFAULT_ADMIN_USERNAME -#define DEFAULT_ADMIN_USERNAME "admin" -#endif -#ifndef DEFAULT_ADMIN_PASS -#define DEFAULT_ADMIN_PASS "" -#endif - -#ifndef DEFAULT_WIFI_CONNECTION_TIMEOUT -#define DEFAULT_WIFI_CONNECTION_TIMEOUT 20000 // minimum timeout in ms for WiFi to be connected. -#endif -#ifndef DEFAULT_WIFI_FORCE_BG_MODE -#define DEFAULT_WIFI_FORCE_BG_MODE false // when set, only allow to connect in 802.11B or G mode (not N) -#endif -#ifndef DEFAULT_WIFI_RESTART_WIFI_CONN_LOST -#define DEFAULT_WIFI_RESTART_WIFI_CONN_LOST false // Perform wifi off and on when connection was lost. -#endif -#ifndef DEFAULT_ECO_MODE -#ifdef CORE32SOLO1 -// ESP32-solo1 will be the "go to build" for unknown devices. -// So best to use the CPU frequency reported by the ESP's e-fuses. -// When enabling eco power mode, the max. CPU frequency is set to the frequency read from these efuses. -// Also, if a vendor really needs to cut the last cent from the BOM by picking the solo1, what else might be done to cut costs? -// Wouldn't be surprised if the power supply of those units isn't that good. -#define DEFAULT_ECO_MODE true // When set, make idle calls between executing tasks. -#else -#define DEFAULT_ECO_MODE false // When set, make idle calls between executing tasks. -#endif -#endif -#ifndef DEFAULT_WIFI_NONE_SLEEP -#define DEFAULT_WIFI_NONE_SLEEP false // When set, the wifi will be set to no longer sleep (more power used and need reboot to reset mode) -#endif -#ifndef DEFAULT_GRATUITOUS_ARP -#define DEFAULT_GRATUITOUS_ARP false // When set, the node will send periodical gratuitous ARP packets to announce itself. -#endif -#ifndef DEFAULT_TOLERANT_LAST_ARG_PARSE -#define DEFAULT_TOLERANT_LAST_ARG_PARSE false // When set, the last argument of some commands will be parsed to the end of the line - // See: https://github.com/letscontrolit/ESPEasy/issues/2724 -#endif -#ifndef DEFAULT_SEND_TO_HTTP_ACK -#define DEFAULT_SEND_TO_HTTP_ACK false // Wait for ack with SendToHttp command. -#endif - -#ifndef DEFAULT_AP_DONT_FORCE_SETUP -#define DEFAULT_AP_DONT_FORCE_SETUP false // Allow optional usage of Sensor without WIFI avaiable // When set you can use the Sensor in AP-Mode without beeing forced to /setup -#endif - -#ifndef DEFAULT_DONT_ALLOW_START_AP -#define DEFAULT_DONT_ALLOW_START_AP false // Usually the AP will be started when no WiFi is defined, or the defined one cannot be found. This flag may prevent it. -#endif - -// --- Default Controller ------------------------------------------------------------------------------ -#ifndef DEFAULT_CONTROLLER -#define DEFAULT_CONTROLLER true // true or false enabled or disabled, set 1st controller defaults -#endif - -#ifndef DEFAULT_CONTROLLER_ENABLED -#define DEFAULT_CONTROLLER_ENABLED false // Enable default controller by default -#endif - -#ifndef DEFAULT_CONTROLLER_USER -#define DEFAULT_CONTROLLER_USER "" // Default controller user -#endif -#ifndef DEFAULT_CONTROLLER_PASS -#define DEFAULT_CONTROLLER_PASS "" // Default controller Password -#endif -#ifndef DEFAULT_CONTROLLER_TIMEOUT -#define DEFAULT_CONTROLLER_TIMEOUT 100 -#endif - -// using a default template, you also need to set a DEFAULT PROTOCOL to a suitable MQTT protocol ! -#ifndef DEFAULT_PUB -#define DEFAULT_PUB "sensors/espeasy/%sysname%/%tskname%/%valname%" // Enter your pub -#endif -#ifndef DEFAULT_SUB -#define DEFAULT_SUB "sensors/espeasy/%sysname%/#" // Enter your sub -#endif -#ifndef DEFAULT_SERVER -#define DEFAULT_SERVER "192.168.0.8" // Enter your Server IP address -#endif -#ifndef DEFAULT_SERVER_HOST -#define DEFAULT_SERVER_HOST "" // Server hostname -#endif -#ifndef DEFAULT_SERVER_USEDNS -#define DEFAULT_SERVER_USEDNS false // true: Use hostname. false: use IP -#endif -#ifndef DEFAULT_USE_EXTD_CONTROLLER_CREDENTIALS -#define DEFAULT_USE_EXTD_CONTROLLER_CREDENTIALS false // true: Allow longer user credentials for controllers -#endif - -#ifndef DEFAULT_PORT -#define DEFAULT_PORT 8080 // Enter your Server port value -#endif - - - -#ifndef DEFAULT_PROTOCOL -#define DEFAULT_PROTOCOL 0 // Protocol used for controller communications - // 0 = Stand-alone (no controller set) - // 1 = Domoticz HTTP - // 2 = Domoticz MQTT - // 3 = Nodo Telnet - // 4 = ThingSpeak - // 5 = Home Assistant (openHAB) MQTT - // 6 = PiDome MQTT - // 7 = EmonCMS - // 8 = Generic HTTP - // 9 = FHEM HTTP -#endif - -#ifndef DEFAULT_CONSOLE_PORT -#if USES_HWCDC -#define DEFAULT_CONSOLE_PORT 7 // 7 = ESPEasySerialPort::usb_hw_cdc -#elif USES_USBCDC -#define DEFAULT_CONSOLE_PORT 8 // 8 = ESPEasySerialPort::usb_cdc_0 -#else -#define DEFAULT_CONSOLE_PORT 2 // 2 = ESPEasySerialPort::serial0 -#endif -#endif -#ifndef DEFAULT_CONSOLE_PORT_RXPIN -#define DEFAULT_CONSOLE_PORT_RXPIN SOC_RX0 -#endif -#ifndef DEFAULT_CONSOLE_PORT_TXPIN -#define DEFAULT_CONSOLE_PORT_TXPIN SOC_TX0 -#endif -#ifndef DEFAULT_CONSOLE_SER0_FALLBACK -#if USES_HWCDC -#define DEFAULT_CONSOLE_SER0_FALLBACK 1 -#elif USES_USBCDC -#define DEFAULT_CONSOLE_SER0_FALLBACK 1 -#else -#define DEFAULT_CONSOLE_SER0_FALLBACK 0 -#endif -#endif - - -#ifndef DEFAULT_PIN_I2C_SDA -#ifdef ESP8266 -#define DEFAULT_PIN_I2C_SDA 4 -#endif -#ifdef ESP32 -#define DEFAULT_PIN_I2C_SDA -1 // Undefined -#endif -#endif -#ifndef DEFAULT_PIN_I2C2_SDA -#define DEFAULT_PIN_I2C2_SDA -1 // Undefined -#endif -#ifndef DEFAULT_PIN_I2C3_SDA -#define DEFAULT_PIN_I2C3_SDA -1 // Undefined -#endif -#ifndef DEFAULT_PIN_I2C_SCL -#ifdef ESP8266 -#define DEFAULT_PIN_I2C_SCL 5 -#endif -#ifdef ESP32 -#define DEFAULT_PIN_I2C_SCL -1 // Undefined -#endif -#endif -#ifndef DEFAULT_PIN_I2C2_SCL -#define DEFAULT_PIN_I2C2_SCL -1 // Undefined -#endif -#ifndef DEFAULT_PIN_I2C3_SCL -#define DEFAULT_PIN_I2C3_SCL -1 // Undefined -#endif -#ifndef DEFAULT_I2C_CLOCK_SPEED -#define DEFAULT_I2C_CLOCK_SPEED 400000 // Use 100 kHz if working with old I2C chips -#endif -#ifndef DEFAULT_I2C_CLOCK_SPEED_SLOW -#define DEFAULT_I2C_CLOCK_SPEED_SLOW 100000 // Use 100 kHz for old/slow I2C chips -#endif -#ifndef FEATURE_I2C_DEVICE_SCAN -#define FEATURE_I2C_DEVICE_SCAN 1 // Show device name in I2C scan -#endif - -#ifndef DEFAULT_PIN_STATUS_LED -#define DEFAULT_PIN_STATUS_LED (-1) -#endif -#ifndef DEFAULT_PIN_STATUS_LED_INVERSED -#define DEFAULT_PIN_STATUS_LED_INVERSED true -#endif - -#ifndef DEFAULT_PIN_RESET_BUTTON -#define DEFAULT_PIN_RESET_BUTTON (-1) -#endif -#ifndef DEFAULT_ETH_PHY_ADDR -#define DEFAULT_ETH_PHY_ADDR 0 -#endif -#ifndef DEFAULT_ETH_PHY_TYPE -#define DEFAULT_ETH_PHY_TYPE EthPhyType_t::notSet -#endif -#ifndef DEFAULT_ETH_PIN_MDC -#define DEFAULT_ETH_PIN_MDC -1 -#endif -#ifndef DEFAULT_ETH_PIN_MDIO -#define DEFAULT_ETH_PIN_MDIO -1 -#endif -#ifndef DEFAULT_ETH_PIN_POWER -#define DEFAULT_ETH_PIN_POWER -1 -#endif -#ifndef DEFAULT_ETH_CLOCK_MODE -#define DEFAULT_ETH_CLOCK_MODE EthClockMode_t::Ext_crystal_osc -#endif -#ifndef DEFAULT_NETWORK_MEDIUM - #define DEFAULT_NETWORK_MEDIUM NetworkMedium_t::WIFI -#endif -#ifndef DEFAULT_JSON_BOOL_WITHOUT_QUOTES -#define DEFAULT_JSON_BOOL_WITHOUT_QUOTES false -#endif -#ifndef DEFAULT_ENABLE_TIMING_STATS -#define DEFAULT_ENABLE_TIMING_STATS false -#endif - - - -// --- Advanced Settings --------------------------------------------------------------------------------- -#if defined(ESP32) - #define USE_RTOS_MULTITASKING -#endif -#ifdef M5STACK_ESP -// #include -#endif - -#ifndef DEFAULT_USE_RULES -#define DEFAULT_USE_RULES false // (true|false) Enable Rules? -#endif -#ifndef DEFAULT_RULES_OLDENGINE -#define DEFAULT_RULES_OLDENGINE true -#endif - -#ifndef DEFAULT_MQTT_RETAIN -#define DEFAULT_MQTT_RETAIN false // (true|false) Retain MQTT messages? -#endif - -#ifndef DEFAULT_CONTROLLER_DELETE_OLDEST -#define DEFAULT_CONTROLLER_DELETE_OLDEST false // (true|false) to delete oldest message when queue is full -#endif - -#ifndef DEFAULT_CONTROLLER_MUST_CHECK_REPLY -#define DEFAULT_CONTROLLER_MUST_CHECK_REPLY false // (true|false) Check Acknowledgment -#endif - -#ifndef DEFAULT_MQTT_DELAY -#define DEFAULT_MQTT_DELAY 100 // Time in milliseconds to retain MQTT messages -#endif -#ifndef DEFAULT_MQTT_LWT_TOPIC -#define DEFAULT_MQTT_LWT_TOPIC "" // Default lwt topic -#endif -#ifndef DEFAULT_MQTT_LWT_CONNECT_MESSAGE -#define DEFAULT_MQTT_LWT_CONNECT_MESSAGE "Connected" // Default lwt message -#endif -#ifndef DEFAULT_MQTT_LWT_DISCONNECT_MESSAGE -#define DEFAULT_MQTT_LWT_DISCONNECT_MESSAGE "Connection Lost" // Default lwt message -#endif -#ifndef DEFAULT_MQTT_USE_UNITNAME_AS_CLIENTID -#define DEFAULT_MQTT_USE_UNITNAME_AS_CLIENTID 0 -#endif - -#ifndef DEFAULT_USE_NTP -#define DEFAULT_USE_NTP false // (true|false) Use NTP Server -#endif -#ifndef DEFAULT_NTP_HOST -#define DEFAULT_NTP_HOST "" // NTP Server Hostname -#endif -#ifndef DEFAULT_TIME_ZONE -#define DEFAULT_TIME_ZONE 0 // Time Offset (in minutes) -#endif -#ifndef DEFAULT_USE_DST -#define DEFAULT_USE_DST false // (true|false) Use Daily Time Saving -#endif - -#ifndef DEFAULT_SYSLOG_IP -#define DEFAULT_SYSLOG_IP "" // Syslog IP Address -#endif -#ifndef DEFAULT_SYSLOG_LEVEL -#define DEFAULT_SYSLOG_LEVEL 0 // Syslog Log Level -#endif -#ifndef DEFAULT_SERIAL_LOG_LEVEL -#define DEFAULT_SERIAL_LOG_LEVEL LOG_LEVEL_INFO // Serial Log Level -#endif -#ifndef DEFAULT_WEB_LOG_LEVEL -#define DEFAULT_WEB_LOG_LEVEL LOG_LEVEL_INFO // Web Log Level -#endif -#ifndef DEFAULT_SD_LOG_LEVEL -#define DEFAULT_SD_LOG_LEVEL 0 // SD Card Log Level -#endif -#ifndef DEFAULT_USE_SD_LOG -#define DEFAULT_USE_SD_LOG false // (true|false) Enable Logging to the SD card -#endif - -#ifndef DEFAULT_USE_SERIAL -#define DEFAULT_USE_SERIAL true // (true|false) Enable Logging to the Serial Port -#endif -#ifndef DEFAULT_SERIAL_BAUD -#define DEFAULT_SERIAL_BAUD 115200 // Serial Port Baud Rate -#endif -#ifndef DEFAULT_SYSLOG_FACILITY -#define DEFAULT_SYSLOG_FACILITY 0 // kern -#endif -#ifndef DEFAULT_SYSLOG_PORT -#define DEFAULT_SYSLOG_PORT 0 -#endif - -#ifndef DEFAULT_SYNC_UDP_PORT -#define DEFAULT_SYNC_UDP_PORT 8266 // Used for ESPEasy p2p. (IANA registered port: 8266) -#endif - - -// Factory Reset defaults -#ifndef DEFAULT_FACTORY_RESET_KEEP_UNIT_NAME -#define DEFAULT_FACTORY_RESET_KEEP_UNIT_NAME true -#endif -#ifndef DEFAULT_FACTORY_RESET_KEEP_WIFI -#define DEFAULT_FACTORY_RESET_KEEP_WIFI true -#endif -#ifndef DEFAULT_FACTORY_RESET_KEEP_NETWORK -#define DEFAULT_FACTORY_RESET_KEEP_NETWORK true -#endif -#ifndef DEFAULT_FACTORY_RESET_KEEP_NTP_DST -#define DEFAULT_FACTORY_RESET_KEEP_NTP_DST true -#endif -#ifndef DEFAULT_FACTORY_RESET_KEEP_CONSOLE_LOG -#define DEFAULT_FACTORY_RESET_KEEP_CONSOLE_LOG true -#endif - - - -// --- Defaults to be used for custom automatic provisioning builds ------------------------------------ -#if FEATURE_CUSTOM_PROVISIONING - #ifndef DEFAULT_FACTORY_DEFAULT_DEVICE_MODEL - #define DEFAULT_FACTORY_DEFAULT_DEVICE_MODEL 0 // DeviceModel_default - #endif - #ifndef DEFAULT_PROVISIONING_FETCH_RULES1 - #define DEFAULT_PROVISIONING_FETCH_RULES1 false - #endif - #ifndef DEFAULT_PROVISIONING_FETCH_RULES2 - #define DEFAULT_PROVISIONING_FETCH_RULES2 false - #endif - #ifndef DEFAULT_PROVISIONING_FETCH_RULES3 - #define DEFAULT_PROVISIONING_FETCH_RULES3 false - #endif - #ifndef DEFAULT_PROVISIONING_FETCH_RULES4 - #define DEFAULT_PROVISIONING_FETCH_RULES4 false - #endif - #ifndef DEFAULT_PROVISIONING_FETCH_NOTIFICATIONS - #define DEFAULT_PROVISIONING_FETCH_NOTIFICATIONS false - #endif - #ifndef DEFAULT_PROVISIONING_FETCH_SECURITY - #define DEFAULT_PROVISIONING_FETCH_SECURITY false - #endif - #ifndef DEFAULT_PROVISIONING_FETCH_CONFIG - #define DEFAULT_PROVISIONING_FETCH_CONFIG false - #endif - #ifndef DEFAULT_PROVISIONING_FETCH_PROVISIONING - #define DEFAULT_PROVISIONING_FETCH_PROVISIONING false - #endif - #ifndef DEFAULT_PROVISIONING_FETCH_FIRMWARE - #define DEFAULT_PROVISIONING_FETCH_FIRMWARE false - #endif - #ifndef DEFAULT_PROVISIONING_SAVE_URL - #define DEFAULT_PROVISIONING_SAVE_URL false - #endif - #ifndef DEFAULT_PROVISIONING_SAVE_CREDENTIALS - #define DEFAULT_PROVISIONING_SAVE_CREDENTIALS false - #endif - #ifndef DEFAULT_PROVISIONING_ALLOW_FETCH_COMMAND - #define DEFAULT_PROVISIONING_ALLOW_FETCH_COMMAND false - #endif - #ifndef DEFAULT_PROVISIONING_URL - #define DEFAULT_PROVISIONING_URL "" - #endif - #ifndef DEFAULT_PROVISIONING_USER - #define DEFAULT_PROVISIONING_USER "" - #endif - #ifndef DEFAULT_PROVISIONING_PASS - #define DEFAULT_PROVISIONING_PASS "" - #endif -#endif // if FEATURE_CUSTOM_PROVISIONING - -#ifndef BUILD_IN_WEBHEADER -#define BUILD_IN_WEBHEADER false -#endif -#ifndef BUILD_IN_WEBFOOTER -#define BUILD_IN_WEBFOOTER true // If not defined show build in footer of webpage -#endif - -#ifndef GITHUB_RELEASES_LINK_PREFIX -# define GITHUB_RELEASES_LINK_PREFIX "" -#endif -#ifndef GITHUB_RELEASES_LINK_SUFFIX -# define GITHUB_RELEASES_LINK_SUFFIX "" -#endif - - -#if FEATURE_STRING_VARIABLES -# define TASK_VALUE_DERIVED_PREFIX_TEMPLATE "_%s_%s-derived" // Includes a not-allowed varname character (-) to prevent undesired manipulation via LetStr -# define TASK_VALUE_UOM_PREFIX_TEMPLATE "_%s_%s-uom" -# define TASK_VALUE_NAME_PREFIX_TEMPLATE "_%s_%s-name" -# define TASK_VALUE_VTYPE_PREFIX_TEMPLATE "_%s_%s-vtype" -# define TASK_VALUE_PRESENTATION_PREFIX_TEMPLATE "_%s_%s-presentation" -# define TASK_VALUE_PRESENTATION_PREFIX_STRING "$" // Keep these string and char prefixes the same! -# define TASK_VALUE_PRESENTATION_PREFIX_CHAR '$' -#endif // if FEATURE_STRING_VARIABLES - - -// --- We define the default features to be enabled here -#ifndef FEATURE_ESPEASY_P2P - #define FEATURE_ESPEASY_P2P 1 -#endif - -/* -// --- Experimental Advanced Settings (NOT ACTIVES at this time) ------------------------------------ - -#define DEFAULT_USE_GLOBAL_SYNC false // (true|false) - -#define DEFAULT_IP_OCTET 0 // -#define DEFAULT_WD_IC2_ADDRESS 0 // -#define DEFAULT_USE_SSDP false // (true|false) -#define DEFAULT_CON_FAIL_THRES 0 // -#define DEFAULT_I2C_CLOCK_LIMIT 0 // -*/ - -#endif // CUSTOMBUILD_ESPEASY_DEFAULTS_H_ +#ifndef CUSTOMBUILD_ESPEASY_DEFAULTS_H_ +#define CUSTOMBUILD_ESPEASY_DEFAULTS_H_ + +// Needed to make sure Custom.h is used. +#include "../../ESPEasy_common.h" + +#include "../DataTypes/NetworkMedium.h" + +#include "../Helpers/Hardware_defines.h" + +// ******************************************************************************** +// User specific configuration +// ******************************************************************************** + +// Set default configuration settings if you want (not mandatory) +// You can always change these during runtime and save to eeprom +// After loading firmware, issue a 'reset' command to load the defaults. +// --- Basic Config Settings ------------------------------------------------------------------------ +#ifndef DEFAULT_NAME +#define DEFAULT_NAME "ESP_Easy" // Enter your device friendly name +#endif +#ifndef UNIT +#define UNIT 0 // Unit Number +#endif +#ifndef DEFAULT_DELAY +#define DEFAULT_DELAY 60 // Sleep Delay in seconds +#endif + +// --- Wifi AP Mode (when your Wifi Network is not reachable) ---------------------------------------- +#ifndef DEFAULT_AP_IP +#define DEFAULT_AP_IP 192,168,4,1 // Enter IP address (comma separated) for AP (config) mode +#endif +#ifndef DEFAULT_AP_SUBNET +#define DEFAULT_AP_SUBNET 255,255,255,0 // Enter IP address (comma separated) for AP (config) mode +#endif +#ifndef DEFAULT_AP_KEY +#define DEFAULT_AP_KEY "configesp" // Enter network WPA key for AP (config) mode +#endif + +// --- Wifi Client Mode ----------------------------------------------------------------------------- +#ifndef DEFAULT_SSID +#define DEFAULT_SSID "ssid" // Enter your Wifi network SSID +#endif +#ifndef DEFAULT_KEY +#define DEFAULT_KEY "wpakey" // Enter your Wifi network WPA key +#endif +#ifndef DEFAULT_SSID2 +#define DEFAULT_SSID2 "" // Enter your fallback Wifi network SSID +#endif +#ifndef DEFAULT_KEY2 +#define DEFAULT_KEY2 "" // Enter your fallback Wifi network WPA key +#endif +#ifndef DEFAULT_WIFI_INCLUDE_HIDDEN_SSID +#define DEFAULT_WIFI_INCLUDE_HIDDEN_SSID false // Allow to connect to hidden SSID APs +#endif +#ifndef DEFAULT_USE_STATIC_IP +#define DEFAULT_USE_STATIC_IP false // (true|false) enabled or disabled static IP +#endif +#ifndef DEFAULT_IP +#define DEFAULT_IP "192.168.0.50" // Enter your IP address +#endif +#ifndef DEFAULT_DNS +#define DEFAULT_DNS "192.168.0.1" // Enter your DNS +#endif +#ifndef DEFAULT_GW +#define DEFAULT_GW "192.168.0.1" // Enter your Gateway +#endif +#ifndef DEFAULT_SUBNET +#define DEFAULT_SUBNET "255.255.255.0" // Enter your Subnet +#endif +#ifndef DEFAULT_IPRANGE_LOW +#define DEFAULT_IPRANGE_LOW "0.0.0.0" // Allowed IP range to access webserver +#endif +#ifndef DEFAULT_IPRANGE_HIGH +#define DEFAULT_IPRANGE_HIGH "255.255.255.255" // Allowed IP range to access webserver +#endif +#ifndef DEFAULT_IP_BLOCK_LEVEL +#define DEFAULT_IP_BLOCK_LEVEL 1 // 0: ALL_ALLOWED 1: LOCAL_SUBNET_ALLOWED 2: ONLY_IP_RANGE_ALLOWED +#endif +#ifndef DEFAULT_ADMIN_USERNAME +#define DEFAULT_ADMIN_USERNAME "admin" +#endif +#ifndef DEFAULT_ADMIN_PASS +#define DEFAULT_ADMIN_PASS "" +#endif + + +#ifndef DEFAULT_APPEND_UNIT_TO_HOSTNAME +#define DEFAULT_APPEND_UNIT_TO_HOSTNAME false +#endif + +#ifndef DEFAULT_WIFI_CONNECTION_TIMEOUT +#define DEFAULT_WIFI_CONNECTION_TIMEOUT 20000 // minimum timeout in ms for WiFi to be connected. +#endif +#ifndef DEFAULT_WIFI_FORCE_BG_MODE +#define DEFAULT_WIFI_FORCE_BG_MODE false // when set, only allow to connect in 802.11B or G mode (not N) +#endif +#ifndef DEFAULT_WIFI_RESTART_WIFI_CONN_LOST +#define DEFAULT_WIFI_RESTART_WIFI_CONN_LOST false // Perform wifi off and on when connection was lost. +#endif +#ifndef DEFAULT_ECO_MODE +#ifdef CORE32SOLO1 +// ESP32-solo1 will be the "go to build" for unknown devices. +// So best to use the CPU frequency reported by the ESP's e-fuses. +// When enabling eco power mode, the max. CPU frequency is set to the frequency read from these efuses. +// Also, if a vendor really needs to cut the last cent from the BOM by picking the solo1, what else might be done to cut costs? +// Wouldn't be surprised if the power supply of those units isn't that good. +#define DEFAULT_ECO_MODE true // When set, make idle calls between executing tasks. +#else +#define DEFAULT_ECO_MODE false // When set, make idle calls between executing tasks. +#endif +#endif +#ifndef DEFAULT_WIFI_NONE_SLEEP +#define DEFAULT_WIFI_NONE_SLEEP false // When set, the wifi will be set to no longer sleep (more power used and need reboot to reset mode) +#endif +#ifndef DEFAULT_GRATUITOUS_ARP +#define DEFAULT_GRATUITOUS_ARP false // When set, the node will send periodical gratuitous ARP packets to announce itself. +#endif +#ifndef DEFAULT_TOLERANT_LAST_ARG_PARSE +#define DEFAULT_TOLERANT_LAST_ARG_PARSE false // When set, the last argument of some commands will be parsed to the end of the line + // See: https://github.com/letscontrolit/ESPEasy/issues/2724 +#endif +#ifndef DEFAULT_SEND_TO_HTTP_ACK +#define DEFAULT_SEND_TO_HTTP_ACK false // Wait for ack with SendToHttp command. +#endif + +#ifndef DEFAULT_USE_ESPEASYNOW +#define DEFAULT_USE_ESPEASYNOW false +#endif + +#ifndef DEFAULT_AP_DONT_FORCE_SETUP +#define DEFAULT_AP_DONT_FORCE_SETUP false // Allow optional usage of Sensor without WIFI avaiable // When set you can use the Sensor in AP-Mode without beeing forced to /setup +#endif + +#ifndef DEFAULT_DONT_ALLOW_START_AP +#define DEFAULT_DONT_ALLOW_START_AP false // Usually the AP will be started when no WiFi is defined, or the defined one cannot be found. This flag may prevent it. +#endif + +// --- Default Controller ------------------------------------------------------------------------------ +#ifndef DEFAULT_CONTROLLER +#define DEFAULT_CONTROLLER true // true or false enabled or disabled, set 1st controller defaults +#endif + +#ifndef DEFAULT_CONTROLLER_ENABLED +#define DEFAULT_CONTROLLER_ENABLED false // Enable default controller by default +#endif + +#ifndef DEFAULT_CONTROLLER_USER +#define DEFAULT_CONTROLLER_USER "" // Default controller user +#endif +#ifndef DEFAULT_CONTROLLER_PASS +#define DEFAULT_CONTROLLER_PASS "" // Default controller Password +#endif +#ifndef DEFAULT_CONTROLLER_TIMEOUT +#define DEFAULT_CONTROLLER_TIMEOUT 100 // Default timeout in msec +#endif +#ifndef DEFAULT_CONTROLLER_FALLBACK_MESH +#define DEFAULT_CONTROLLER_FALLBACK_MESH false // Let the (MQTT) controller use the mesh as a fallback route when there's no network connection +#endif + +// using a default template, you also need to set a DEFAULT PROTOCOL to a suitable MQTT protocol ! +#ifndef DEFAULT_PUB +#define DEFAULT_PUB "sensors/espeasy/%sysname%/%tskname%/%valname%" // Enter your pub +#endif +#ifndef DEFAULT_SUB +#define DEFAULT_SUB "sensors/espeasy/%sysname%/#" // Enter your sub +#endif +#ifndef DEFAULT_SERVER +#define DEFAULT_SERVER "192.168.0.8" // Enter your Server IP address +#endif +#ifndef DEFAULT_SERVER_HOST +#define DEFAULT_SERVER_HOST "" // Server hostname +#endif +#ifndef DEFAULT_SERVER_USEDNS +#define DEFAULT_SERVER_USEDNS false // true: Use hostname. false: use IP +#endif +#ifndef DEFAULT_USE_EXTD_CONTROLLER_CREDENTIALS +#define DEFAULT_USE_EXTD_CONTROLLER_CREDENTIALS false // true: Allow longer user credentials for controllers +#endif + +#ifndef DEFAULT_PORT +#define DEFAULT_PORT 8080 // Enter your Server port value +#endif + + + +#ifndef DEFAULT_PROTOCOL +#define DEFAULT_PROTOCOL 0 // Protocol used for controller communications + // 0 = Stand-alone (no controller set) + // 1 = Domoticz HTTP + // 2 = Domoticz MQTT + // 3 = Nodo Telnet + // 4 = ThingSpeak + // 5 = Home Assistant (openHAB) MQTT + // 6 = PiDome MQTT + // 7 = EmonCMS + // 8 = Generic HTTP + // 9 = FHEM HTTP +#endif + +#ifndef DEFAULT_CONSOLE_PORT +#if USES_HWCDC +#define DEFAULT_CONSOLE_PORT 7 // 7 = ESPEasySerialPort::usb_hw_cdc +#elif USES_USBCDC +#define DEFAULT_CONSOLE_PORT 8 // 8 = ESPEasySerialPort::usb_cdc_0 +#else +#define DEFAULT_CONSOLE_PORT 2 // 2 = ESPEasySerialPort::serial0 +#endif +#endif +#ifndef DEFAULT_CONSOLE_PORT_RXPIN +#define DEFAULT_CONSOLE_PORT_RXPIN SOC_RX0 +#endif +#ifndef DEFAULT_CONSOLE_PORT_TXPIN +#define DEFAULT_CONSOLE_PORT_TXPIN SOC_TX0 +#endif +#ifndef DEFAULT_CONSOLE_SER0_FALLBACK +#if USES_HWCDC +#define DEFAULT_CONSOLE_SER0_FALLBACK 1 +#elif USES_USBCDC +#define DEFAULT_CONSOLE_SER0_FALLBACK 1 +#else +#define DEFAULT_CONSOLE_SER0_FALLBACK 0 +#endif +#endif + + +#ifndef DEFAULT_PIN_I2C_SDA +#ifdef ESP8266 +#define DEFAULT_PIN_I2C_SDA 4 +#endif +#ifdef ESP32 +#define DEFAULT_PIN_I2C_SDA -1 // Undefined +#endif +#endif +#ifndef DEFAULT_PIN_I2C2_SDA +#define DEFAULT_PIN_I2C2_SDA -1 // Undefined +#endif +#ifndef DEFAULT_PIN_I2C3_SDA +#define DEFAULT_PIN_I2C3_SDA -1 // Undefined +#endif +#ifndef DEFAULT_PIN_I2C_SCL +#ifdef ESP8266 +#define DEFAULT_PIN_I2C_SCL 5 +#endif +#ifdef ESP32 +#define DEFAULT_PIN_I2C_SCL -1 // Undefined +#endif +#endif +#ifndef DEFAULT_PIN_I2C2_SCL +#define DEFAULT_PIN_I2C2_SCL -1 // Undefined +#endif +#ifndef DEFAULT_PIN_I2C3_SCL +#define DEFAULT_PIN_I2C3_SCL -1 // Undefined +#endif +#ifndef DEFAULT_I2C_CLOCK_SPEED +#define DEFAULT_I2C_CLOCK_SPEED 400000 // Use 100 kHz if working with old I2C chips +#endif +#ifndef DEFAULT_I2C_CLOCK_SPEED_SLOW +#define DEFAULT_I2C_CLOCK_SPEED_SLOW 100000 // Use 100 kHz for old/slow I2C chips +#endif +#ifndef FEATURE_I2C_DEVICE_SCAN +#define FEATURE_I2C_DEVICE_SCAN 1 // Show device name in I2C scan +#endif + +#ifndef DEFAULT_PIN_STATUS_LED +#define DEFAULT_PIN_STATUS_LED (-1) +#endif +#ifndef DEFAULT_PIN_STATUS_LED_INVERSED +#define DEFAULT_PIN_STATUS_LED_INVERSED true +#endif + +#ifndef DEFAULT_PIN_RESET_BUTTON +#define DEFAULT_PIN_RESET_BUTTON (-1) +#endif +#ifndef DEFAULT_ETH_PHY_ADDR +#define DEFAULT_ETH_PHY_ADDR 0 +#endif +#ifndef DEFAULT_ETH_PHY_TYPE +#define DEFAULT_ETH_PHY_TYPE EthPhyType_t::notSet +#endif +#ifndef DEFAULT_ETH_PIN_MDC +#define DEFAULT_ETH_PIN_MDC -1 +#endif +#ifndef DEFAULT_ETH_PIN_MDIO +#define DEFAULT_ETH_PIN_MDIO -1 +#endif +#ifndef DEFAULT_ETH_PIN_POWER +#define DEFAULT_ETH_PIN_POWER -1 +#endif +#ifndef DEFAULT_ETH_CLOCK_MODE +#define DEFAULT_ETH_CLOCK_MODE EthClockMode_t::Ext_crystal_osc +#endif +#ifndef DEFAULT_NETWORK_MEDIUM + #define DEFAULT_NETWORK_MEDIUM NetworkMedium_t::WIFI +#endif +#ifndef DEFAULT_JSON_BOOL_WITHOUT_QUOTES +#define DEFAULT_JSON_BOOL_WITHOUT_QUOTES false +#endif +#ifndef DEFAULT_ENABLE_TIMING_STATS +#define DEFAULT_ENABLE_TIMING_STATS false +#endif + + + +// --- Advanced Settings --------------------------------------------------------------------------------- +#if defined(ESP32) + #define USE_RTOS_MULTITASKING +#endif +#ifdef M5STACK_ESP +// #include +#endif + +#ifndef DEFAULT_USE_RULES +#define DEFAULT_USE_RULES false // (true|false) Enable Rules? +#endif +#ifndef DEFAULT_RULES_OLDENGINE +#define DEFAULT_RULES_OLDENGINE true +#endif + +#ifndef DEFAULT_MQTT_RETAIN +#define DEFAULT_MQTT_RETAIN false // (true|false) Retain MQTT messages? +#endif + +#ifndef DEFAULT_CONTROLLER_DELETE_OLDEST +#define DEFAULT_CONTROLLER_DELETE_OLDEST false // (true|false) to delete oldest message when queue is full +#endif + +#ifndef DEFAULT_CONTROLLER_MUST_CHECK_REPLY +#define DEFAULT_CONTROLLER_MUST_CHECK_REPLY false // (true|false) Check Acknowledgment +#endif + +#ifndef DEFAULT_MQTT_DELAY +#define DEFAULT_MQTT_DELAY 100 // Time in milliseconds to retain MQTT messages +#endif +#ifndef DEFAULT_MQTT_LWT_TOPIC +#define DEFAULT_MQTT_LWT_TOPIC "" // Default lwt topic +#endif +#ifndef DEFAULT_MQTT_LWT_CONNECT_MESSAGE +#define DEFAULT_MQTT_LWT_CONNECT_MESSAGE "Connected" // Default lwt message +#endif +#ifndef DEFAULT_MQTT_LWT_DISCONNECT_MESSAGE +#define DEFAULT_MQTT_LWT_DISCONNECT_MESSAGE "Connection Lost" // Default lwt message +#endif +#ifndef DEFAULT_MQTT_USE_UNITNAME_AS_CLIENTID +#define DEFAULT_MQTT_USE_UNITNAME_AS_CLIENTID 0 +#endif + +#ifndef DEFAULT_USE_NTP +#define DEFAULT_USE_NTP false // (true|false) Use NTP Server +#endif +#ifndef DEFAULT_NTP_HOST +#define DEFAULT_NTP_HOST "" // NTP Server Hostname +#endif +#ifndef DEFAULT_TIME_ZONE +#define DEFAULT_TIME_ZONE 0 // Time Offset (in minutes) +#endif +#ifndef DEFAULT_USE_DST +#define DEFAULT_USE_DST false // (true|false) Use Daily Time Saving +#endif + +#ifndef DEFAULT_SYSLOG_IP +#define DEFAULT_SYSLOG_IP "" // Syslog IP Address +#endif +#ifndef DEFAULT_SYSLOG_LEVEL +#define DEFAULT_SYSLOG_LEVEL 0 // Syslog Log Level +#endif +#ifndef DEFAULT_SERIAL_LOG_LEVEL +#define DEFAULT_SERIAL_LOG_LEVEL LOG_LEVEL_INFO // Serial Log Level +#endif +#ifndef DEFAULT_WEB_LOG_LEVEL +#define DEFAULT_WEB_LOG_LEVEL LOG_LEVEL_INFO // Web Log Level +#endif +#ifndef DEFAULT_SD_LOG_LEVEL +#define DEFAULT_SD_LOG_LEVEL 0 // SD Card Log Level +#endif +#ifndef DEFAULT_USE_SD_LOG +#define DEFAULT_USE_SD_LOG false // (true|false) Enable Logging to the SD card +#endif + +#ifndef DEFAULT_USE_SERIAL +#define DEFAULT_USE_SERIAL true // (true|false) Enable Logging to the Serial Port +#endif +#ifndef DEFAULT_SERIAL_BAUD +#define DEFAULT_SERIAL_BAUD 115200 // Serial Port Baud Rate +#endif +#ifndef DEFAULT_SYSLOG_FACILITY +#define DEFAULT_SYSLOG_FACILITY 0 // kern +#endif +#ifndef DEFAULT_SYSLOG_PORT +#define DEFAULT_SYSLOG_PORT 0 +#endif + +#ifndef DEFAULT_SYNC_UDP_PORT +#define DEFAULT_SYNC_UDP_PORT 8266 // Used for ESPEasy p2p. (IANA registered port: 8266) +#endif + + +// Factory Reset defaults +#ifndef DEFAULT_FACTORY_RESET_KEEP_UNIT_NAME +#define DEFAULT_FACTORY_RESET_KEEP_UNIT_NAME true +#endif +#ifndef DEFAULT_FACTORY_RESET_KEEP_WIFI +#define DEFAULT_FACTORY_RESET_KEEP_WIFI true +#endif +#ifndef DEFAULT_FACTORY_RESET_KEEP_NETWORK +#define DEFAULT_FACTORY_RESET_KEEP_NETWORK true +#endif +#ifndef DEFAULT_FACTORY_RESET_KEEP_NTP_DST +#define DEFAULT_FACTORY_RESET_KEEP_NTP_DST true +#endif +#ifndef DEFAULT_FACTORY_RESET_KEEP_CONSOLE_LOG +#define DEFAULT_FACTORY_RESET_KEEP_CONSOLE_LOG true +#endif + + + +// --- Defaults to be used for custom automatic provisioning builds ------------------------------------ +#if FEATURE_CUSTOM_PROVISIONING + #ifndef DEFAULT_FACTORY_DEFAULT_DEVICE_MODEL + #define DEFAULT_FACTORY_DEFAULT_DEVICE_MODEL 0 // DeviceModel_default + #endif + #ifndef DEFAULT_PROVISIONING_FETCH_RULES1 + #define DEFAULT_PROVISIONING_FETCH_RULES1 false + #endif + #ifndef DEFAULT_PROVISIONING_FETCH_RULES2 + #define DEFAULT_PROVISIONING_FETCH_RULES2 false + #endif + #ifndef DEFAULT_PROVISIONING_FETCH_RULES3 + #define DEFAULT_PROVISIONING_FETCH_RULES3 false + #endif + #ifndef DEFAULT_PROVISIONING_FETCH_RULES4 + #define DEFAULT_PROVISIONING_FETCH_RULES4 false + #endif + #ifndef DEFAULT_PROVISIONING_FETCH_NOTIFICATIONS + #define DEFAULT_PROVISIONING_FETCH_NOTIFICATIONS false + #endif + #ifndef DEFAULT_PROVISIONING_FETCH_SECURITY + #define DEFAULT_PROVISIONING_FETCH_SECURITY false + #endif + #ifndef DEFAULT_PROVISIONING_FETCH_CONFIG + #define DEFAULT_PROVISIONING_FETCH_CONFIG false + #endif + #ifndef DEFAULT_PROVISIONING_FETCH_PROVISIONING + #define DEFAULT_PROVISIONING_FETCH_PROVISIONING false + #endif + #ifndef DEFAULT_PROVISIONING_FETCH_FIRMWARE + #define DEFAULT_PROVISIONING_FETCH_FIRMWARE false + #endif + #ifndef DEFAULT_PROVISIONING_SAVE_URL + #define DEFAULT_PROVISIONING_SAVE_URL false + #endif + #ifndef DEFAULT_PROVISIONING_SAVE_CREDENTIALS + #define DEFAULT_PROVISIONING_SAVE_CREDENTIALS false + #endif + #ifndef DEFAULT_PROVISIONING_ALLOW_FETCH_COMMAND + #define DEFAULT_PROVISIONING_ALLOW_FETCH_COMMAND false + #endif + #ifndef DEFAULT_PROVISIONING_URL + #define DEFAULT_PROVISIONING_URL "" + #endif + #ifndef DEFAULT_PROVISIONING_USER + #define DEFAULT_PROVISIONING_USER "" + #endif + #ifndef DEFAULT_PROVISIONING_PASS + #define DEFAULT_PROVISIONING_PASS "" + #endif +#endif // if FEATURE_CUSTOM_PROVISIONING + +#ifndef BUILD_IN_WEBHEADER +#define BUILD_IN_WEBHEADER false +#endif +#ifndef BUILD_IN_WEBFOOTER +#define BUILD_IN_WEBFOOTER true // If not defined show build in footer of webpage +#endif + +#ifndef GITHUB_RELEASES_LINK_PREFIX +# define GITHUB_RELEASES_LINK_PREFIX "" +#endif +#ifndef GITHUB_RELEASES_LINK_SUFFIX +# define GITHUB_RELEASES_LINK_SUFFIX "" +#endif + + +#if FEATURE_STRING_VARIABLES +# define TASK_VALUE_DERIVED_PREFIX_TEMPLATE "_%s_%s-derived" // Includes a not-allowed varname character (-) to prevent undesired manipulation via LetStr +# define TASK_VALUE_UOM_PREFIX_TEMPLATE "_%s_%s-uom" +# define TASK_VALUE_NAME_PREFIX_TEMPLATE "_%s_%s-name" +# define TASK_VALUE_VTYPE_PREFIX_TEMPLATE "_%s_%s-vtype" +# define TASK_VALUE_PRESENTATION_PREFIX_TEMPLATE "_%s_%s-presentation" +# define TASK_VALUE_PRESENTATION_PREFIX_STRING "$" // Keep these string and char prefixes the same! +# define TASK_VALUE_PRESENTATION_PREFIX_CHAR '$' +#endif // if FEATURE_STRING_VARIABLES + + +// --- We define the default features to be enabled here +#ifndef FEATURE_ESPEASY_P2P + #define FEATURE_ESPEASY_P2P 1 +#endif + +/* +// --- Experimental Advanced Settings (NOT ACTIVES at this time) ------------------------------------ + +#define DEFAULT_USE_GLOBAL_SYNC false // (true|false) + +#define DEFAULT_IP_OCTET 0 // +#define DEFAULT_WD_IC2_ADDRESS 0 // +#define DEFAULT_USE_SSDP false // (true|false) +#define DEFAULT_CON_FAIL_THRES 0 // +#define DEFAULT_I2C_CLOCK_LIMIT 0 // +*/ + +#endif // CUSTOMBUILD_ESPEASY_DEFAULTS_H_ diff --git a/src/src/CustomBuild/ESPEasyLimits.h b/src/src/CustomBuild/ESPEasyLimits.h index 73b5ecdeb8..884d93dda8 100644 --- a/src/src/CustomBuild/ESPEasyLimits.h +++ b/src/src/CustomBuild/ESPEasyLimits.h @@ -1,163 +1,173 @@ -#ifndef CUSTOMBUILD_ESPEASY_LIMITS_H -#define CUSTOMBUILD_ESPEASY_LIMITS_H - -#include "../../include/ESPEasy_config.h" - -// *********************************************************************** -// * These limits have direct impact on the settings files -// * Do not change them! -// * Else settings files will no longer be compatible with official builds -// * Some of these are related to the values defined in StorageLayout.h -// *********************************************************************** - - -// Performing a 2-stage define assignment using the _TMP defines -// See: https://github.com/letscontrolit/ESPEasy/issues/2621 -#if FEATURE_NON_STANDARD_24_TASKS - #define TASKS_MAX_TMP 24 -#else - #define TASKS_MAX_TMP 12 -#endif - - -#if defined(ESP8266) - #ifndef TASKS_MAX - #define TASKS_MAX TASKS_MAX_TMP - #endif - #ifndef MAX_GPIO - #define MAX_GPIO 16 - #endif -#endif -#if defined(ESP32) - #ifndef TASKS_MAX - #define TASKS_MAX 32 - #endif - - #ifndef MAX_GPIO - #if ESP_IDF_VERSION_MAJOR > 3 // IDF 4+ - #include - #define MAX_GPIO (GPIO_NUM_MAX - 1) - #else // ESP32 Before IDF 4.0 - #define MAX_GPIO 39 - #endif - #endif - -#endif - -#ifndef CONTROLLER_MAX - #define CONTROLLER_MAX 3 // max 4! -#endif -#ifndef NOTIFICATION_MAX - #define NOTIFICATION_MAX 3 // max 4! -#endif -#ifndef VARS_PER_TASK - #define VARS_PER_TASK 4 -#endif -#ifndef PLUGIN_CONFIGVAR_MAX - #define PLUGIN_CONFIGVAR_MAX 8 -#endif -#ifndef PLUGIN_CONFIGFLOATVAR_MAX - #define PLUGIN_CONFIGFLOATVAR_MAX 4 -#endif -#ifndef PLUGIN_CONFIGLONGVAR_MAX - #define PLUGIN_CONFIGLONGVAR_MAX 4 -#endif -#ifndef PLUGIN_EXTRACONFIGVAR_MAX - #define PLUGIN_EXTRACONFIGVAR_MAX 16 -#endif -#ifndef NAME_FORMULA_LENGTH_MAX - #define NAME_FORMULA_LENGTH_MAX 40 -#endif - -#define USERVAR_MAX_INDEX (VARS_PER_TASK * TASKS_MAX) - -// *********************************************************************** -// * The next limits affect memory usage -// *********************************************************************** -#ifndef DEVICES_MAX - // TODO TD-er: This should be set automatically by counting the number of included plugins. - # ifdef ESP32 - # define DEVICES_MAX 175 - #else - #if defined(PLUGIN_BUILD_COLLECTION) || defined(PLUGIN_BUILD_DEV) - # define DEVICES_MAX 95 - # else - # define DEVICES_MAX 60 - # endif - #endif -#endif - -#ifndef DEVICE_INDEX_MAX - #define DEVICE_INDEX_MAX 255 -#endif -#ifndef PLUGIN_MAX - #define PLUGIN_MAX 255 -#endif -#ifndef CPLUGIN_MAX - #define CPLUGIN_MAX 255 -#endif -#ifndef NPLUGIN_MAX - #define NPLUGIN_MAX 4 -#endif - -#ifndef UNIT_NUMBER_MAX - #define UNIT_NUMBER_MAX 254 // Stored in Settings.Unit unit 255 = broadcast -#endif - - -// *********************************************************************** -// * Limits regarding Rules -// *********************************************************************** - -#ifndef RULES_TIMER_MAX - #define RULES_TIMER_MAX 256 -#endif -//#ifndef PINSTATE_TABLE_MAX -//#define PINSTATE_TABLE_MAX 32 -//#endif -#ifndef RULES_MAX_SIZE - #define RULES_MAX_SIZE 2048 -#endif -#ifndef RULES_MAX_NESTING_LEVEL - #define RULES_MAX_NESTING_LEVEL 3 -#endif -#ifndef RULESETS_MAX - #define RULESETS_MAX 4 -#endif -#ifndef RULES_BUFFER_SIZE - #define RULES_BUFFER_SIZE 64 -#endif - -#ifndef RULES_IF_MAX_NESTING_LEVEL - #define RULES_IF_MAX_NESTING_LEVEL 4 -#endif - - -// *********************************************************************** -// * Extended SecuritySettings -// *********************************************************************** -#ifndef EXT_SECURITY_MAX_USER_LENGTH - #define EXT_SECURITY_MAX_USER_LENGTH 128 -#endif -#ifndef EXT_SECURITY_MAX_PASS_LENGTH - #define EXT_SECURITY_MAX_PASS_LENGTH 128 -#endif - -// *********************************************************************** -// * Other operational limits -// *********************************************************************** - -#ifndef MAX_FLASHWRITES_PER_DAY - #define MAX_FLASHWRITES_PER_DAY 100 // per 24 hour window -#endif -#ifndef UDP_PACKETSIZE_MAX - #define UDP_PACKETSIZE_MAX 512 // Currently only needed for C013_Receive -#endif -#ifndef TIMER_GRATUITOUS_ARP_MAX - #define TIMER_GRATUITOUS_ARP_MAX 5000 -#endif - -#define DOMOTICZ_MAX_IDX 999999999 // Looks like it is an unsigned int, so could be up to 4 bln. - - -#endif // CUSTOMBUILD_ESPEASY_LIMITS_H +#ifndef CUSTOMBUILD_ESPEASY_LIMITS_H +#define CUSTOMBUILD_ESPEASY_LIMITS_H + +#include "../../include/ESPEasy_config.h" + +// *********************************************************************** +// * These limits have direct impact on the settings files +// * Do not change them! +// * Else settings files will no longer be compatible with official builds +// * Some of these are related to the values defined in StorageLayout.h +// *********************************************************************** + + +// Performing a 2-stage define assignment using the _TMP defines +// See: https://github.com/letscontrolit/ESPEasy/issues/2621 +#if FEATURE_NON_STANDARD_24_TASKS + #define TASKS_MAX_TMP 24 +#else + #define TASKS_MAX_TMP 12 +#endif + + +#if defined(ESP8266) + #ifndef TASKS_MAX + #define TASKS_MAX TASKS_MAX_TMP + #endif + #ifndef MAX_GPIO + #define MAX_GPIO 16 + #endif +#endif +#if defined(ESP32) + #ifndef TASKS_MAX + #define TASKS_MAX 32 + #endif + + #ifndef MAX_GPIO + #if ESP_IDF_VERSION_MAJOR > 3 // IDF 4+ + #include + #define MAX_GPIO (GPIO_NUM_MAX - 1) + #else // ESP32 Before IDF 4.0 + #define MAX_GPIO 39 + #endif + #endif + +#endif + +#ifndef CONTROLLER_MAX + #define CONTROLLER_MAX 3 // max 4! +#endif +#ifndef NOTIFICATION_MAX + #define NOTIFICATION_MAX 3 // max 4! +#endif +#ifndef VARS_PER_TASK + #define VARS_PER_TASK 4 +#endif +#ifndef PLUGIN_CONFIGVAR_MAX + #define PLUGIN_CONFIGVAR_MAX 8 +#endif +#ifndef PLUGIN_CONFIGFLOATVAR_MAX + #define PLUGIN_CONFIGFLOATVAR_MAX 4 +#endif +#ifndef PLUGIN_CONFIGLONGVAR_MAX + #define PLUGIN_CONFIGLONGVAR_MAX 4 +#endif +#ifndef PLUGIN_EXTRACONFIGVAR_MAX + #define PLUGIN_EXTRACONFIGVAR_MAX 16 +#endif +#ifndef NAME_FORMULA_LENGTH_MAX + #define NAME_FORMULA_LENGTH_MAX 40 +#endif + +#define USERVAR_MAX_INDEX (VARS_PER_TASK * TASKS_MAX) + + +// *********************************************************************** +// * ESPEasy-NOW related limits +// *********************************************************************** + +#ifndef ESPEASY_NOW_PEER_MAX + #define ESPEASY_NOW_PEER_MAX 10 // 10 when using encryption, 20 without encryption +#endif + + +// *********************************************************************** +// * The next limits affect memory usage +// *********************************************************************** +#ifndef DEVICES_MAX + // TODO TD-er: This should be set automatically by counting the number of included plugins. + # ifdef ESP32 + # define DEVICES_MAX 175 + #else + #if defined(PLUGIN_BUILD_COLLECTION) || defined(PLUGIN_BUILD_DEV) + # define DEVICES_MAX 95 + # else + # define DEVICES_MAX 60 + # endif + #endif +#endif + +#ifndef DEVICE_INDEX_MAX + #define DEVICE_INDEX_MAX 255 +#endif +#ifndef PLUGIN_MAX + #define PLUGIN_MAX 255 +#endif +#ifndef CPLUGIN_MAX + #define CPLUGIN_MAX 255 +#endif +#ifndef NPLUGIN_MAX + #define NPLUGIN_MAX 4 +#endif + +#ifndef UNIT_NUMBER_MAX + #define UNIT_NUMBER_MAX 254 // Stored in Settings.Unit unit 255 = broadcast +#endif + + +// *********************************************************************** +// * Limits regarding Rules +// *********************************************************************** + +#ifndef RULES_TIMER_MAX + #define RULES_TIMER_MAX 256 +#endif +//#ifndef PINSTATE_TABLE_MAX +//#define PINSTATE_TABLE_MAX 32 +//#endif +#ifndef RULES_MAX_SIZE + #define RULES_MAX_SIZE 2048 +#endif +#ifndef RULES_MAX_NESTING_LEVEL + #define RULES_MAX_NESTING_LEVEL 3 +#endif +#ifndef RULESETS_MAX + #define RULESETS_MAX 4 +#endif +#ifndef RULES_BUFFER_SIZE + #define RULES_BUFFER_SIZE 64 +#endif + +#ifndef RULES_IF_MAX_NESTING_LEVEL + #define RULES_IF_MAX_NESTING_LEVEL 4 +#endif + + +// *********************************************************************** +// * Extended SecuritySettings +// *********************************************************************** +#ifndef EXT_SECURITY_MAX_USER_LENGTH + #define EXT_SECURITY_MAX_USER_LENGTH 128 +#endif +#ifndef EXT_SECURITY_MAX_PASS_LENGTH + #define EXT_SECURITY_MAX_PASS_LENGTH 128 +#endif + +// *********************************************************************** +// * Other operational limits +// *********************************************************************** + +#ifndef MAX_FLASHWRITES_PER_DAY + #define MAX_FLASHWRITES_PER_DAY 100 // per 24 hour window +#endif +#ifndef UDP_PACKETSIZE_MAX + #define UDP_PACKETSIZE_MAX 512 // Currently only needed for C013_Receive +#endif +#ifndef TIMER_GRATUITOUS_ARP_MAX + #define TIMER_GRATUITOUS_ARP_MAX 5000 +#endif + +#define DOMOTICZ_MAX_IDX 999999999 // Looks like it is an unsigned int, so could be up to 4 bln. + + +#endif // CUSTOMBUILD_ESPEASY_LIMITS_H diff --git a/src/src/CustomBuild/define_plugin_sets.h b/src/src/CustomBuild/define_plugin_sets.h index 75413d8f15..478cf6e82c 100644 --- a/src/src/CustomBuild/define_plugin_sets.h +++ b/src/src/CustomBuild/define_plugin_sets.h @@ -2269,7 +2269,7 @@ To create/register a plugin, you have to : #ifdef PLUGIN_SET_MAX // Features #ifndef USES_ESPEASY_NOW -// #define USES_ESPEASY_NOW + #define USES_ESPEASY_NOW #endif #ifndef FEATURE_SERVO #define FEATURE_SERVO 1 @@ -2561,6 +2561,9 @@ To create/register a plugin, you have to : #ifndef USES_C018 #define USES_C018 // TTN RN2483 #endif + #ifndef USES_C019 + #define USES_C019 // ESPEasy-NOW + #endif // Notifiers diff --git a/src/src/DataStructs/ControllerSettingsStruct.cpp b/src/src/DataStructs/ControllerSettingsStruct.cpp index 9afc59e96b..1cc799c5bd 100644 --- a/src/src/DataStructs/ControllerSettingsStruct.cpp +++ b/src/src/DataStructs/ControllerSettingsStruct.cpp @@ -227,101 +227,111 @@ bool ControllerSettingsStruct::updateIPcache() { /* bool ControllerSettingsStruct::mqtt_cleanSession() const { - return bitRead(VariousFlags, 1); - } - - void ControllerSettingsStruct::mqtt_cleanSession(bool value) - { - bitWrite(VariousFlags, 1, value); - } - - bool ControllerSettingsStruct::mqtt_sendLWT() const - { - return !bitRead(VariousFlags, 2); - } - - void ControllerSettingsStruct::mqtt_sendLWT(bool value) - { - bitWrite(VariousFlags, 2, !value); - } - - bool ControllerSettingsStruct::mqtt_willRetain() const - { - return !bitRead(VariousFlags, 3); - } - - void ControllerSettingsStruct::mqtt_willRetain(bool value) - { - bitWrite(VariousFlags, 3, !value); - } - - bool ControllerSettingsStruct::mqtt_uniqueMQTTclientIdReconnect() const - { - return bitRead(VariousFlags, 4); - } - - void ControllerSettingsStruct::mqtt_uniqueMQTTclientIdReconnect(bool value) - { - bitWrite(VariousFlags, 4, value); - } - - bool ControllerSettingsStruct::mqtt_retainFlag() const - { - return bitRead(VariousFlags, 5); - } - - void ControllerSettingsStruct::mqtt_retainFlag(bool value) - { - bitWrite(VariousFlags, 5, value); - } - - bool ControllerSettingsStruct::useExtendedCredentials() const - { - return bitRead(VariousFlags, 6); - } - - void ControllerSettingsStruct::useExtendedCredentials(bool value) - { - bitWrite(VariousFlags, 6, value); - } - - bool ControllerSettingsStruct::sendBinary() const - { - return bitRead(VariousFlags, 7); - } - - void ControllerSettingsStruct::sendBinary(bool value) - { - bitWrite(VariousFlags, 7, value); - } - - bool ControllerSettingsStruct::allowExpire() const - { - return bitRead(VariousFlags, 9); - } - - void ControllerSettingsStruct::allowExpire(bool value) - { - bitWrite(VariousFlags, 9, value); - } - - bool ControllerSettingsStruct::deduplicate() const - { - return bitRead(VariousFlags, 10); - } - - void ControllerSettingsStruct::deduplicate(bool value) - { - bitWrite(VariousFlags, 10, value); - } - - bool ControllerSettingsStruct::useLocalSystemTime() const - { - return bitRead(VariousFlags, 11); - } - - void ControllerSettingsStruct::useLocalSystemTime(bool value) - { + return bitRead(VariousFlags, 1); +} + +void ControllerSettingsStruct::mqtt_cleanSession(bool value) +{ + bitWrite(VariousFlags, 1, value); +} + +bool ControllerSettingsStruct::mqtt_sendLWT() const +{ + return !bitRead(VariousFlags, 2); +} + +void ControllerSettingsStruct::mqtt_sendLWT(bool value) +{ + bitWrite(VariousFlags, 2, !value); +} + +bool ControllerSettingsStruct::mqtt_willRetain() const +{ + return !bitRead(VariousFlags, 3); +} + +void ControllerSettingsStruct::mqtt_willRetain(bool value) +{ + bitWrite(VariousFlags, 3, !value); +} + +bool ControllerSettingsStruct::mqtt_uniqueMQTTclientIdReconnect() const +{ + return bitRead(VariousFlags, 4); +} + +void ControllerSettingsStruct::mqtt_uniqueMQTTclientIdReconnect(bool value) +{ + bitWrite(VariousFlags, 4, value); +} + +bool ControllerSettingsStruct::mqtt_retainFlag() const +{ + return bitRead(VariousFlags, 5); +} + +void ControllerSettingsStruct::mqtt_retainFlag(bool value) +{ + bitWrite(VariousFlags, 5, value); +} + +bool ControllerSettingsStruct::useExtendedCredentials() const +{ + return bitRead(VariousFlags, 6); +} + +void ControllerSettingsStruct::useExtendedCredentials(bool value) +{ + bitWrite(VariousFlags, 6, value); +} + +bool ControllerSettingsStruct::sendBinary() const +{ + return bitRead(VariousFlags, 7); +} + +void ControllerSettingsStruct::sendBinary(bool value) +{ + bitWrite(VariousFlags, 7, value); +} + +bool ControllerSettingsStruct::enableESPEasyNowFallback() const +{ + return bitRead(VariousFlags, 8); +} + +void ControllerSettingsStruct::enableESPEasyNowFallback(bool value) +{ + bitWrite(VariousFlags, 8, value); +} + +bool ControllerSettingsStruct::allowExpire() const +{ + return bitRead(VariousFlags, 9); +} + +void ControllerSettingsStruct::allowExpire(bool value) +{ + bitWrite(VariousFlags, 9, value); +} + +bool ControllerSettingsStruct::deduplicate() const +{ + return bitRead(VariousFlags, 10); +} + +void ControllerSettingsStruct::deduplicate(bool value) +{ + bitWrite(VariousFlags, 10, value); +} + +bool ControllerSettingsStruct::useLocalSystemTime() const +{ + return bitRead(VariousFlags, 11); +} + +void ControllerSettingsStruct::useLocalSystemTime(bool value) +{ bitWrite(VariousFlags, 11, value); } */ diff --git a/src/src/DataStructs/ControllerSettingsStruct.h b/src/src/DataStructs/ControllerSettingsStruct.h index 065237e403..1cae77ede5 100644 --- a/src/src/DataStructs/ControllerSettingsStruct.h +++ b/src/src/DataStructs/ControllerSettingsStruct.h @@ -1,6 +1,8 @@ #ifndef DATASTRUCTS_CONTROLLERSETTINGSSTRUCT_H #define DATASTRUCTS_CONTROLLERSETTINGSSTRUCT_H +#include "../../ESPEasy_common.h" + /*********************************************************************************************\ * ControllerSettingsStruct definition \*********************************************************************************************/ @@ -119,6 +121,9 @@ struct ControllerSettingsStruct CONTROLLER_AUTO_DISCOVERY_CONFIG, CONTROLLER_RETAINED_DISCOVERY_OPTION, #endif +#ifdef USES_ESPEASY_NOW + CONTROLLER_ENABLE_ESPEASY_NOW_FALLBACK, +#endif // Keep this as last, is used to loop over all parameters CONTROLLER_ENABLED @@ -177,6 +182,9 @@ struct ControllerSettingsStruct bool sendBinary() const { return VariousBits1.sendBinary; } void sendBinary(bool value) { VariousBits1.sendBinary = value; } + bool enableESPEasyNowFallback() const { return VariousBits1.enableESPEasyNowFallback; } + void enableESPEasyNowFallback(bool value) { VariousBits1.enableESPEasyNowFallback = value; } + bool allowExpire() const { return VariousBits1.allowExpire; } void allowExpire(bool value) { VariousBits1.allowExpire = value; } @@ -232,7 +240,7 @@ struct ControllerSettingsStruct uint32_t mqtt_retainFlag : 1; // Bit 05 uint32_t useExtendedCredentials : 1; // Bit 06 uint32_t sendBinary : 1; // Bit 07 - uint32_t unused_08 : 1; // Bit 08 + uint32_t enableESPEasyNowFallback : 1; // Bit 08 uint32_t allowExpire : 1; // Bit 09 uint32_t deduplicate : 1; // Bit 10 uint32_t useLocalSystemTime : 1; // Bit 11 @@ -254,6 +262,7 @@ struct ControllerSettingsStruct uint32_t unused_30 : 1; // Bit 30 uint32_t unused_31 : 1; // Bit 31 } VariousBits1; + char ClientID[65]; // Used to define the Client ID used by the controller char MqttAutoDiscoveryTopic[104]; char MqttAutoDiscoveryConfig[25]; // Alternative for /config suffix, defaults to /config diff --git a/src/src/DataStructs/ESPEasy_Now_DuplicateCheck.cpp b/src/src/DataStructs/ESPEasy_Now_DuplicateCheck.cpp new file mode 100644 index 0000000000..9dac6b1086 --- /dev/null +++ b/src/src/DataStructs/ESPEasy_Now_DuplicateCheck.cpp @@ -0,0 +1,12 @@ +#include "../DataStructs/ESPEasy_Now_DuplicateCheck.h" + +#ifdef USES_ESPEASY_NOW + +ESPEasy_Now_DuplicateCheck::ESPEasy_Now_DuplicateCheck() + : _key(0), _type(ESPEasy_Now_DuplicateCheck::message_t::KeyToCheck) {} + +ESPEasy_Now_DuplicateCheck::ESPEasy_Now_DuplicateCheck(uint32_t key, + message_t message_type) + : _key(key), _type(message_type) {} + +#endif \ No newline at end of file diff --git a/src/src/DataStructs/ESPEasy_Now_DuplicateCheck.h b/src/src/DataStructs/ESPEasy_Now_DuplicateCheck.h new file mode 100644 index 0000000000..be5e58fc2b --- /dev/null +++ b/src/src/DataStructs/ESPEasy_Now_DuplicateCheck.h @@ -0,0 +1,30 @@ +#ifndef DATASTRUCTS_ESPEASY_NOW_DUPLICATECHECK_H +#define DATASTRUCTS_ESPEASY_NOW_DUPLICATECHECK_H + +#include "../../ESPEasy_common.h" + +#ifdef USES_ESPEASY_NOW + +#include + +class ESPEasy_Now_DuplicateCheck { +public: + + enum class message_t : uint8_t { + KeyToCheck = 0, + AlreadyProcessed = 1 + }; + + ESPEasy_Now_DuplicateCheck(); + + ESPEasy_Now_DuplicateCheck(uint32_t key, + message_t message_type); + + const uint32_t _key; + const message_t _type; +}; + +#endif + + +#endif // DATASTRUCTS_ESPEASY_NOW_DUPLICATECHECK_H diff --git a/src/src/DataStructs/ESPEasy_Now_MQTT_queue_check_packet.cpp b/src/src/DataStructs/ESPEasy_Now_MQTT_queue_check_packet.cpp new file mode 100644 index 0000000000..11237d25ef --- /dev/null +++ b/src/src/DataStructs/ESPEasy_Now_MQTT_queue_check_packet.cpp @@ -0,0 +1,28 @@ +#include "../DataStructs/ESPEasy_Now_MQTT_queue_check_packet.h" + + +#ifdef USES_ESPEASY_NOW + +ESPEasy_Now_MQTT_queue_check_packet::ESPEasy_Now_MQTT_queue_check_packet() {} + +void ESPEasy_Now_MQTT_queue_check_packet::markSendTime() { + _millis_out = millis(); + state = ESPEasy_Now_MQTT_QueueCheckState::Enum::Unset; +} + +void ESPEasy_Now_MQTT_queue_check_packet::setState(bool isFull) +{ + state = isFull ? ESPEasy_Now_MQTT_QueueCheckState::Enum::Full : ESPEasy_Now_MQTT_QueueCheckState::Enum::Empty; +} + +bool ESPEasy_Now_MQTT_queue_check_packet::isFull() const +{ + return state == ESPEasy_Now_MQTT_QueueCheckState::Enum::Full; +} + +bool ESPEasy_Now_MQTT_queue_check_packet::isSet() const +{ + return state != ESPEasy_Now_MQTT_QueueCheckState::Enum::Unset; +} + +#endif // ifdef USES_ESPEASY_NOW diff --git a/src/src/DataStructs/ESPEasy_Now_MQTT_queue_check_packet.h b/src/src/DataStructs/ESPEasy_Now_MQTT_queue_check_packet.h new file mode 100644 index 0000000000..41e6870c8d --- /dev/null +++ b/src/src/DataStructs/ESPEasy_Now_MQTT_queue_check_packet.h @@ -0,0 +1,32 @@ +#ifndef DATASTRUCT_ESPEASY_NOW_MQTT_QUEUE_CHECK_PACKET_H +#define DATASTRUCT_ESPEASY_NOW_MQTT_QUEUE_CHECK_PACKET_H + +#include "../../ESPEasy_common.h" + +#ifdef USES_ESPEASY_NOW + +#include + +#include "../Globals/ESPEasy_now_state.h" +#include "../DataTypes/ESPEasy_Now_MQTT_queue_check_state.h" + +class ESPEasy_Now_MQTT_queue_check_packet { +public: + + ESPEasy_Now_MQTT_queue_check_packet(); + + void setState(bool isFull); + + bool isFull() const; + + bool isSet() const; + + void markSendTime(); + + unsigned long _millis_out = millis(); + ESPEasy_Now_MQTT_QueueCheckState::Enum state = ESPEasy_Now_MQTT_QueueCheckState::Enum::Unset; +}; + +#endif // ifdef USES_ESPEASY_NOW + +#endif // DATASTRUCT_ESPEASY_NOW_MQTT_QUEUE_CHECK_PACKET_H diff --git a/src/src/DataStructs/ESPEasy_Now_NTP_query.cpp b/src/src/DataStructs/ESPEasy_Now_NTP_query.cpp new file mode 100644 index 0000000000..02e8934c50 --- /dev/null +++ b/src/src/DataStructs/ESPEasy_Now_NTP_query.cpp @@ -0,0 +1,222 @@ +#include "../DataStructs/ESPEasy_Now_NTP_query.h" + +#ifdef USES_ESPEASY_NOW + + +// Typical time wander for ESP nodes is 0.04 ms/sec +// Meaning per 25 sec, the time may wander 1 msec. +# define TIME_WANDER_FACTOR 25000 + +// Max time it may take to get a reply. +# define MAX_DELAY_FOR_REPLY 5000 + +# define MINIMUM_TIME_BETWEEN_UPDATES (1800 * 1000) // Half an hour + +# include "../DataStructs/MAC_address.h" +# include "../ESPEasyCore/ESPEasy_Log.h" +# include "../Globals/ESPEasy_time.h" +# include "../Helpers/ESPEasy_time_calc.h" + +ESPEasy_Now_NTP_query::ESPEasy_Now_NTP_query() +{ + reset(true); +} + +bool ESPEasy_Now_NTP_query::getMac(MAC_address& mac) const +{ + if (_timeSource == timeSource_t::No_time_source) { return false; } + + mac.set(_mac); + return true; +} + + +void ESPEasy_Now_NTP_query::find_best_NTP(const MAC_address& mac, + timeSource_t timeSource, + unsigned long timePassedSinceLastTimeSync) +{ + if (_millis_out != 0) { + if (timePassedSince(_millis_out) > MAX_DELAY_FOR_REPLY) { + reset(false); + + // A query was sent, but didn't get a reply in time. + } else { + // A query is still pending, so don't add new possible best NTP. + return; + } + } + bool updated = false; + const unsigned long expectedWander_ms = + computeExpectedWander(timeSource, timePassedSinceLastTimeSync); + + // First check if it matches the current best candidate. + bool matches_current_best = mac == _mac; + + if (matches_current_best) { + // Update expected wander based on current time since last sync + updated = true; + } else { + if (_expectedWander_ms > expectedWander_ms) { + // We found a good new candidate + bool matches_prev_fail = mac == _mac_prev_fail; + + if (matches_prev_fail) { + // No need to retry. + return; + } + mac.get(_mac); + updated = true; + } + } + + if (updated) { + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + String log; + log.reserve(64); + log += F(ESPEASY_NOW_NAME); + log += F(": Best NTP peer: "); + log += MAC_address(_mac).toString(); + log += F(" Wander "); + log += _expectedWander_ms; + log += F(" ms -> "); + log += expectedWander_ms; + log += F(" ms"); + addLog(LOG_LEVEL_INFO, log); + } + _timeSource = timeSource; + _expectedWander_ms = expectedWander_ms; + } +} + +void ESPEasy_Now_NTP_query::reset(bool success) +{ + // FIXME TD-er: For now also clear failed MAC. Have to think of a mechanism for multiple failed attempts + success = true; + _unixTime_d = 0.0; + _millis_in = 0; + _millis_out = 0; + _expectedWander_ms = (1 << 30); + _timeSource = timeSource_t::No_time_source; + + for (uint8_t i = 0; i < 6; ++i) { + _mac_prev_fail[i] = success ? 0 : _mac[i]; + } + + for (uint8_t i = 0; i < 6; ++i) { + _mac[i] = 0; + } +} + +bool ESPEasy_Now_NTP_query::hasLowerWander() const +{ + const timeSource_t timeSource = node_time.getTimeSource(); + const long timePassed = timePassedSince(node_time.lastSyncTime_ms); + unsigned long currentExpectedWander_ms = computeExpectedWander(timeSource, timePassed); + + if (timeSource < _timeSource) { + // Current time source is of better category than the best other. + // So only update from the other if ours was too long ago. + if (currentExpectedWander_ms < 1000) { return false; } + } + + if (timeSource == _timeSource) { + // Same time source category. + // Try to limit the number of time updates by only accepting updates if time since last sync is over N seconds + if (timePassed < MINIMUM_TIME_BETWEEN_UPDATES) { return false; } + } + + return _expectedWander_ms < currentExpectedWander_ms; +} + +bool ESPEasy_Now_NTP_query::isBroadcast() const +{ + for (uint8_t i = 0; i < 6; ++i) { + if (_mac[i] != 0xFF) { return false; } + } + return true; +} + +void ESPEasy_Now_NTP_query::markSendTime() +{ + _millis_out = millis(); +} + +void ESPEasy_Now_NTP_query::createBroadcastNTP() +{ + for (uint8_t i = 0; i < 6; ++i) + { + _mac[i] = 0xFF; + } + createReply(0); +} + +void ESPEasy_Now_NTP_query::createReply(unsigned long queryReceiveTimestamp) +{ + const timeSource_t timeSource = node_time.getTimeSource(); + _millis_in = queryReceiveTimestamp; + uint32_t unix_time_frac{}; + _unixTime_d = node_time.getUnixTime(unix_time_frac); + _unixTime_d += unix_time_frac_to_micros(unix_time_frac) / 1000000.0; + _timeSource = timeSource; + _expectedWander_ms = computeExpectedWander(timeSource, timePassedSince(node_time.lastSyncTime_ms)); + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + String log; + log.reserve(64); + log += F(ESPEASY_NOW_NAME); + log += F(": Create NTP reply to: "); + log += MAC_address(_mac).toString(); + log += F(" Wander "); + log += _expectedWander_ms; + log += F(" ms"); + addLog(LOG_LEVEL_INFO, log); + } + markSendTime(); +} + +bool ESPEasy_Now_NTP_query::processReply(const ESPEasy_Now_NTP_query& received, unsigned long receiveTimestamp) +{ + if (!received.hasLowerWander()) { return false; } + + bool isBroadcast = received.isBroadcast(); + long air_time = 0; + + if (isBroadcast) { + // Average air time for an ESP-now message is roughly 1 msec. + air_time = 1; + } else { + // Not a broadcast NTP update, so we must have initiated it ourselves. + // Check how long it took to get a reply. + const long timediff_source = timeDiff(_millis_out, receiveTimestamp); + + if (timediff_source > MAX_DELAY_FOR_REPLY) { + // Took too long, discard it. + reset(false); + return false; + } + + const long timediff_reply = timeDiff(received._millis_in, received._millis_out); + air_time = (timediff_source - timediff_reply) / 2; + } + double compensation_ms = air_time + timePassedSince(receiveTimestamp); + double new_unixTime_d = received._unixTime_d + (compensation_ms / 1000); + + node_time.setExternalTimeSource(new_unixTime_d, timeSource_t::ESP_now_peer); + node_time.now_(); + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + String log; + log.reserve(64); + log = F(ESPEASY_NOW_NAME); + log += F(": NTP air time: "); + log += air_time; + log += F(" Compensation: "); + log += String(compensation_ms, 1); + log += F(" ms"); + addLog(LOG_LEVEL_INFO, log); + } + reset(true); + return true; +} + +#endif \ No newline at end of file diff --git a/src/src/DataStructs/ESPEasy_Now_NTP_query.h b/src/src/DataStructs/ESPEasy_Now_NTP_query.h new file mode 100644 index 0000000000..e54a26b059 --- /dev/null +++ b/src/src/DataStructs/ESPEasy_Now_NTP_query.h @@ -0,0 +1,60 @@ +#ifndef DATASTRUCT_ESPEASY_NOW_NTP_QUERY_H +#define DATASTRUCT_ESPEASY_NOW_NTP_QUERY_H + +#include "../../ESPEasy_common.h" + +#ifdef USES_ESPEASY_NOW + +#include +#include + + +#include "../Globals/ESPEasy_now_state.h" + +#include "../DataTypes/ESPEasyTimeSource.h" + +class MAC_address; + +class ESPEasy_Now_NTP_query { +public: + + ESPEasy_Now_NTP_query(); + + bool getMac(MAC_address& mac) const; + + void find_best_NTP(const MAC_address& mac, + timeSource_t timeSource, + unsigned long timePassedSinceLastTimeSync); + + void reset(bool success); + + bool hasLowerWander() const; + + bool isBroadcast() const; + + void markSendTime(); + + void createBroadcastNTP(); + + void createReply(unsigned long queryReceiveTimestamp); + + bool processReply(const ESPEasy_Now_NTP_query& received, + unsigned long receiveTimestamp); + + // FIXME TD-er: These members could all be private, but the processReply function must then be a friend function. + // However this gives lots of warnings as the class is then a friend of itself. + + double _unixTime_d; + unsigned long _millis_in; + unsigned long _millis_out; + unsigned long _expectedWander_ms; + timeSource_t _timeSource = timeSource_t::No_time_source; + uint8_t _mac[6] = { 0 }; + + // if previous best choice failed, keep track of it. + // so we will not get stuck retrying the one that does not reply + uint8_t _mac_prev_fail[6] = { 0 }; +}; + +#endif +#endif // DATASTRUCT_ESPEASY_NOW_NTP_QUERY_H diff --git a/src/src/DataStructs/ESPEasy_Now_p2p_data.cpp b/src/src/DataStructs/ESPEasy_Now_p2p_data.cpp new file mode 100644 index 0000000000..7aa717faac --- /dev/null +++ b/src/src/DataStructs/ESPEasy_Now_p2p_data.cpp @@ -0,0 +1,123 @@ +#include "../DataStructs/ESPEasy_Now_p2p_data.h" + +#include + + +ESPEasy_Now_p2p_data::~ESPEasy_Now_p2p_data() { + if (data != nullptr) { + delete data; + data = nullptr; + } +} + +bool ESPEasy_Now_p2p_data::validate() const { + if (data != nullptr) { + if (dataSize == 0) { return false; } + } + + if (dataOffset == 0) { return false; } + + if (!validTaskIndex(sourceTaskIndex)) { return false; } + + // TODO TD-er: Change when pluginID_t is 16 bit. + + if (!validPluginID(pluginID_t::toPluginID(plugin_id))) { return false; } + + // TODO TD-er: Must add more sanity checks here. + return true; +} + +bool ESPEasy_Now_p2p_data::addFloat(float value) { + return addBinaryData((uint8_t *)(&value), sizeof(float)); +} + +bool ESPEasy_Now_p2p_data::getFloat(float& value, size_t& offset) const { + if ((offset + sizeof(float)) >= dataSize) { + return false; + } + memcpy((uint8_t *)(&value), &data[offset], sizeof(float)); + offset += sizeof(float); + return true; +} + +bool ESPEasy_Now_p2p_data::addString(const String& value) { + return addBinaryData((uint8_t *)(value.c_str()), value.length() + 1); // Include null termination +} + +bool ESPEasy_Now_p2p_data::getString(String& value, size_t& offset) const { + int maxStrLen = dataSize - offset; + + if (maxStrLen < 2) { + return false; + } + const size_t str_len = strnlen(reinterpret_cast(&data[offset]), maxStrLen); + + value.reserve(str_len); + + for (size_t i = 0; i < str_len; ++i) { + value += data[offset]; + ++offset; + } + return true; +} + +size_t ESPEasy_Now_p2p_data::getTotalSize() const { + return dataOffset + dataSize; +} + +bool ESPEasy_Now_p2p_data::addBinaryData(uint8_t *binaryData, size_t size) { + size_t oldSize; + + if (allocate(size, oldSize)) { + memcpy(&data[oldSize], binaryData, size); + return true; + } + return false; +} + +const uint8_t * ESPEasy_Now_p2p_data::getBinaryData(size_t offset, size_t& size) const { + if (offset >= dataSize) { + size = 0; + return nullptr; + } + + const size_t available = dataSize - offset; + + if (size > available) { + size = available; + } + return &data[offset]; +} + +uint8_t * ESPEasy_Now_p2p_data::prepareBinaryData(size_t& size) { + size_t oldSize; + dataSize = 0; + data = nullptr; + + if (!allocate(size, oldSize)) { + size = 0; + return nullptr; + } + size = dataSize; + return data; +} + +bool ESPEasy_Now_p2p_data::allocate(size_t size, size_t& oldSize) { + oldSize = dataSize; + const size_t newSize = oldSize + size; + + uint8_t *tmp_ptr = new (std::nothrow) uint8_t[newSize]; + + if (tmp_ptr == nullptr) { + return false; + } + memset(tmp_ptr, 0, newSize); + + if (data != nullptr) { + memcpy(tmp_ptr, data, oldSize); + delete[] data; + } + data = tmp_ptr; + dataSize = newSize; + return true; +} diff --git a/src/src/DataStructs/ESPEasy_Now_p2p_data.h b/src/src/DataStructs/ESPEasy_Now_p2p_data.h new file mode 100644 index 0000000000..357dc19b97 --- /dev/null +++ b/src/src/DataStructs/ESPEasy_Now_p2p_data.h @@ -0,0 +1,94 @@ +#ifndef DATASTRUCTS_ESPEASY_NOW_P2P_DATA_H +#define DATASTRUCTS_ESPEASY_NOW_P2P_DATA_H + +#include + +#include "../CustomBuild/ESPEasyLimits.h" +#include "../DataStructs/DeviceStruct.h" +#include "../DataTypes/PluginID.h" +#include "../DataTypes/TaskIndex.h" +#include "../Globals/Plugins.h" + +#define ESPEASY_NOW_P2P_DATA_VERSION 1 + +enum class ESPEasy_Now_p2p_data_type : uint8_t { + NotSet = 0, + PluginDescription = 1, + PluginData = 2 +}; + +struct __attribute__((__packed__)) ESPEasy_Now_p2p_data { + + ~ESPEasy_Now_p2p_data(); + + bool validate() const; + + // Add float at the end of the binary data + // @param value The float to store + // @retval True when successful. Only fails if no data could be allocated + bool addFloat(float value); + + // Get a float, starting at @offset in the data array + // @param value The value to be read + // @param offset The offset in the data array to start. Will be incremented with sizeof(float) + // @retval True when the offset was in the range of the data. + bool getFloat(float & value, + size_t& offset) const; + + bool addString(const String& value); + + bool getString(String & value, + size_t& offset) const; + + // Return the size of the header + data + size_t getTotalSize() const; + + bool addBinaryData(uint8_t *binaryData, + size_t size); + + // Get a pointer to the data starting at offset + // @param offset is the offset in the data array to start (will not be updated) + // @param size is the expected size of the data. Value may be lower if there is less data available than requested. + // @retval Pointer to the data, if available. nullptr if offset is not valid. + const uint8_t* getBinaryData(size_t offset, + size_t& size) const; + + // Clear and resize data array to size. + // This function is meant to be used only on the receiving end to reconstruct the data array. + // Meaning the pointer will just be cleared, no call to delete the pointer. + // @retval pointer to the first element of the data array + uint8_t* prepareBinaryData(size_t& size); + +private: + + // Allocate extra data at the end of the data array + // @param size The extra size in bytes to allocate + // @retval The old size of the data array. + bool allocate(size_t size, + size_t& oldSize); + +public: + + ESPEasy_Now_p2p_data_type dataType = ESPEasy_Now_p2p_data_type::NotSet; + uint16_t dataSize = 0; + + // The sizeof(data) is intended use of sizeof to return the size of a pointer + const uint8_t dataOffset = sizeof(ESPEasy_Now_p2p_data) - sizeof(uint8_t *); + taskIndex_t sourceTaskIndex = INVALID_TASK_INDEX; + taskIndex_t destTaskIndex = INVALID_TASK_INDEX; + uint16_t plugin_id = INVALID_PLUGIN_ID.value; // FIXME TD-er: Must change to pluginID_t as soon as that's changed to 16 + // bit + uint16_t sourceUnit = 0; + uint16_t destUnit = 0; + uint16_t idx = 0; + Sensor_VType sensorType = Sensor_VType::SENSOR_TYPE_NONE; + uint8_t valueCount = 0; + +private: + + // Must use a pointer here instead of a struct, or else this object cannot be made packed. + uint8_t *data = nullptr; +}; + + +#endif // DATASTRUCTS_ESPEASY_NOW_P2P_DATA_H diff --git a/src/src/DataStructs/ESPEasy_Now_packet.cpp b/src/src/DataStructs/ESPEasy_Now_packet.cpp new file mode 100644 index 0000000000..1cf151e879 --- /dev/null +++ b/src/src/DataStructs/ESPEasy_Now_packet.cpp @@ -0,0 +1,248 @@ +# include "../DataStructs/ESPEasy_Now_packet.h" + +#ifdef USES_ESPEASY_NOW + +# include "../DataStructs/ESPEasy_now_hdr.h" +# include "../Helpers/CRC_functions.h" +# include "../Helpers/Memory.h" + +# define ESPEASY_NOW_MAX_PACKET_SIZE 200 + + +ESPEasy_Now_packet::ESPEasy_Now_packet(const ESPEasy_now_hdr& header, size_t payloadSize) +{ + const size_t requestedSize = payloadSize + sizeof(ESPEasy_now_hdr); + setSize(requestedSize); + setHeader(header); +} + +ESPEasy_Now_packet::ESPEasy_Now_packet(ESPEasy_Now_packet&& other) +: _buf(std::move(other._buf)), _valid(other._valid) +{ + for (size_t i = 0; i < 6; ++i) { + _mac[i] = other._mac[i]; + other._mac[i] = 0; + } + other._valid = false; +} + +ESPEasy_Now_packet& ESPEasy_Now_packet::operator=(ESPEasy_Now_packet&& other) +{ + if (&other == this) { + return *this; + } + + for (size_t i = 0; i < 6; ++i) { + _mac[i] = other._mac[i]; + other._mac[i] = 0; + } + _buf = std::move(other._buf); + _valid = other._valid; + other._valid = false; + return *this; +} + +bool ESPEasy_Now_packet::setReceivedPacket(const MAC_address& mac, + const uint8_t *buf, + size_t packetSize) +{ + setSize(packetSize); + mac.get(_mac); + const size_t bufsize = _buf.size(); + if (packetSize > bufsize) { + // Cannot store the whole packet, so consider it as invalid. + _valid = false; + } else { + _valid = true; + memcpy(&_buf[0], buf, packetSize); + } + return _valid; +} + +void ESPEasy_Now_packet::setSize(size_t packetSize) +{ + if (packetSize > ESPEASY_NOW_MAX_PACKET_SIZE) { + packetSize = ESPEASY_NOW_MAX_PACKET_SIZE; + } + const size_t maxFreeBlock = getMaxFreeBlock(); + if (packetSize > maxFreeBlock) { + packetSize = maxFreeBlock; + } + + _buf.resize(packetSize); + _valid = _buf.size() >= packetSize; +} + +bool ESPEasy_Now_packet::valid() const +{ + return _valid && (getSize() >= sizeof(ESPEasy_now_hdr)); +} + +uint16_t ESPEasy_Now_packet::computeChecksum() const +{ + if (!_valid) return 0u; + return calc_CRC16(reinterpret_cast(begin()), getPayloadSize()); +} + +bool ESPEasy_Now_packet::checksumValid() const +{ + return getHeader().checksum == computeChecksum(); +} + +size_t ESPEasy_Now_packet::getSize() const +{ + if (_valid) { + return _buf.size(); + } + return 0u; +} + +size_t ESPEasy_Now_packet::getPayloadSize() const +{ + const size_t size = getSize(); + + if (size < sizeof(ESPEasy_now_hdr)) { + // should not happen + return 0; + } + return size - sizeof(ESPEasy_now_hdr); +} + +size_t ESPEasy_Now_packet::getMaxPayloadSize() +{ + return ESPEASY_NOW_MAX_PACKET_SIZE - sizeof(ESPEasy_now_hdr); +} + +ESPEasy_now_hdr ESPEasy_Now_packet::getHeader() const +{ + if (_valid && getSize() >= sizeof(ESPEasy_now_hdr)) { + ESPEasy_now_hdr header(&_buf[0]); + return header; + } + + ESPEasy_now_hdr header; + return header; +} + +void ESPEasy_Now_packet::setHeader(ESPEasy_now_hdr header) +{ + if (_buf.size() < sizeof(ESPEasy_now_hdr)) { + // Not even the header will fit, so this is an invalid packet. + _valid = false; + return; + } + header.checksum = computeChecksum(); + memcpy(&_buf[0], &header, sizeof(ESPEasy_now_hdr)); + _valid = true; +} + +size_t ESPEasy_Now_packet::addString(const String& string, size_t& payload_pos) +{ + size_t length = string.length() + 1; // Try to Store the extra null-termination (may fail) + + return addBinaryData(reinterpret_cast(string.c_str()), length, payload_pos); +} + +void ESPEasy_Now_packet::setMac(const MAC_address& mac) +{ + memcpy(_mac, mac.mac, 6); +} + +void ESPEasy_Now_packet::setBroadcast() +{ + for (uint8_t i = 0; i < 6; ++i) + { + _mac[i] = 0xFF; + } +} + +size_t ESPEasy_Now_packet::addBinaryData(const uint8_t *data, size_t length, + size_t& payload_pos) +{ + size_t bytes_left = getPayloadSize(); + if (!_valid || bytes_left == 0) { return 0; } + + if (payload_pos > bytes_left) { return 0; } + bytes_left -= payload_pos; + + if (length > bytes_left) { + length = bytes_left; + } + size_t buf_pos = sizeof(ESPEasy_now_hdr) + payload_pos; + memcpy(&_buf[buf_pos], data, length); + payload_pos += length; + return length; +} + +size_t ESPEasy_Now_packet::getBinaryData(uint8_t *data, size_t length, + size_t& payload_pos) const +{ + size_t bytes_left = getPayloadSize(); + + if (payload_pos > bytes_left) { return 0; } + bytes_left -= payload_pos; + + if (length > bytes_left) { + length = bytes_left; + } + size_t buf_pos = sizeof(ESPEasy_now_hdr) + payload_pos; + memcpy(data, &_buf[buf_pos], length); + payload_pos += length; + return length; +} + +String ESPEasy_Now_packet::getString(size_t& payload_pos) const +{ + String res; + size_t bytes_left = getPayloadSize(); + + if (payload_pos > bytes_left) { return res; } + bytes_left -= payload_pos; + size_t buf_pos = sizeof(ESPEasy_now_hdr) + payload_pos; + size_t strlength = strnlen(reinterpret_cast(&_buf[buf_pos]), bytes_left); + res.reserve(strlength); + const size_t max_buf_pos = buf_pos + strlength; + + for (; buf_pos < max_buf_pos; ++buf_pos, ++payload_pos) { + res += static_cast(_buf[buf_pos]); + } + return res; +} + +const char* ESPEasy_Now_packet::get_c_str(size_t& payload_pos, size_t& str_length) const +{ + size_t bytes_left = getPayloadSize(); + + if (payload_pos > bytes_left) { return nullptr; } + bytes_left -= payload_pos; + size_t buf_pos = sizeof(ESPEasy_now_hdr) + payload_pos; + str_length = strnlen(reinterpret_cast(&_buf[buf_pos]), bytes_left); + payload_pos += str_length; + return reinterpret_cast(&_buf[buf_pos]); +} + + +String ESPEasy_Now_packet::getLogString() const +{ + ESPEasy_now_hdr header = getHeader(); + String log; + + log.reserve(40); + log += MAC_address(_mac).toString(); + log += F(" payload: "); + log += getPayloadSize(); + log += F(" ("); + log += header.packet_nr + 1; + log += '/'; + log += header.nr_packets; + log += ')'; + return log; +} + +const uint8_t * ESPEasy_Now_packet::begin() const +{ + if (!_valid) return nullptr; + return &_buf[sizeof(ESPEasy_now_hdr)]; +} + +#endif // ifdef USES_ESPEASY_NOW \ No newline at end of file diff --git a/src/src/DataStructs/ESPEasy_Now_packet.h b/src/src/DataStructs/ESPEasy_Now_packet.h new file mode 100644 index 0000000000..dce2669647 --- /dev/null +++ b/src/src/DataStructs/ESPEasy_Now_packet.h @@ -0,0 +1,113 @@ +#ifndef DATASTRUCTS_ESPEASY_NOW_PACKET_H +#define DATASTRUCTS_ESPEASY_NOW_PACKET_H + +#include "../../ESPEasy_common.h" +#ifdef USES_ESPEASY_NOW + +# include "../DataStructs/MAC_address.h" +# include "../Globals/ESPEasy_now_state.h" + +# include +# include +# include +# include + +class ESPEasy_now_hdr; + +class ESPEasy_Now_packet { +public: + + // Constructor for sending a packet + // Actual allocated size may be lower than requested. + explicit ESPEasy_Now_packet(const ESPEasy_now_hdr& header, + size_t payloadSize); + + ESPEasy_Now_packet() = default; + + ESPEasy_Now_packet(const ESPEasy_Now_packet& other) = delete; + + ESPEasy_Now_packet(ESPEasy_Now_packet&& other); + + ESPEasy_Now_packet & operator=(const ESPEasy_Now_packet& other) = delete; + ESPEasy_Now_packet & operator=(ESPEasy_Now_packet&& other); + + bool ICACHE_FLASH_ATTR setReceivedPacket(const MAC_address& mac, + const uint8_t *buf, + size_t packetSize); + + // A packet may become invalid if it was not possible to allocate enough memory for the buffer + bool ICACHE_FLASH_ATTR valid() const; + + bool checksumValid() const; + + size_t getSize() const; + + size_t getPayloadSize() const; + + static size_t getMaxPayloadSize(); + + ESPEasy_now_hdr getHeader() const; + + void setHeader(ESPEasy_now_hdr header); + + void setMac(const MAC_address& mac); + + void setBroadcast(); + + size_t addBinaryData(const uint8_t *data, + size_t length, + size_t & payload_pos); + + size_t getBinaryData(uint8_t *data, + size_t length, + size_t & payload_pos) const; + + // Add a string to the packet, starting at payload position payload_pos + // Return the number of bytes added (can be 1 more than the given string) + size_t addString(const String& string, + size_t & payload_pos); + + // Return a string starting from position pos in the buffer. + // payload_pos will contain the new position to start for a next string + String getString(size_t& payload_pos) const; + + // Get a pointer to the start of the string starting from position pos in the buffer. + // The char pointer will be guaranteed null terminated. + // payload_pos will contain the new position to start for a next string + // @param str_length will contain the length of the found string + const char* get_c_str(size_t& payload_pos, + size_t& str_length) const; + + // Get pointer to the begin of the payload + const uint8_t * begin() const; + + const uint8_t * operator[](size_t idx) const { + if (_valid) { + return &_buf[idx]; + } + return nullptr; + } + + String getLogString() const; + + uint8_t _mac[6] = { 0 }; + +private: + + std::vector_buf; + + bool _valid = false; + + void setSize(size_t packetSize); + + uint16_t computeChecksum() const; +}; + +typedef std::list ESPEasy_Now_packet_list; + +typedef std::map ESPEasy_Now_packet_map; + +#endif // ifdef USES_ESPEASY_NOW + + +#endif // DATASTRUCTS_ESPEASY_NOW_PACKET_H diff --git a/src/src/DataStructs/ESPEasy_now_Node_statistics.cpp b/src/src/DataStructs/ESPEasy_now_Node_statistics.cpp new file mode 100644 index 0000000000..00388b602c --- /dev/null +++ b/src/src/DataStructs/ESPEasy_now_Node_statistics.cpp @@ -0,0 +1,135 @@ +#include "../DataStructs/ESPEasy_now_Node_statistics.h" + +#ifdef USES_ESPEASY_NOW + +#include "../ESPEasyCore/ESPEasy_Log.h" +#include "../Helpers/ESPEasy_time_calc.h" +#include "../Helpers/StringConverter.h" + + +unsigned long ESPEasy_now_Node_statistics_t::getAge() const +{ + return timePassedSince(last_update_route[last_route_index]); +} + +void ESPEasy_now_Node_statistics_t::addRoute(uint8_t unit, const ESPEasy_now_traceroute_struct& route) +{ + if (route.getDistance() == 255) { + return; + } + + if ((last_update_route[last_route_index] != 0) && (timePassedSince(last_update_route[last_route_index]) < 1000)) { + // Handling a burst of updates, only add those which have a higher success rate. + if (routes[last_route_index] < route) { + return; + } + } + + ++last_route_index; + + if (last_route_index >= ESPEASY_NOW_NODE_STATISTICS_NR_ROUTES) { + last_route_index = 0; + } + routes[last_route_index] = route; + routes[last_route_index].addUnit(unit); + routes[last_route_index].setSuccessRate_last_node(unit, success_rate); + last_update = millis(); + last_update_route[last_route_index] = last_update; + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLog(LOG_LEVEL_INFO, concat(F(ESPEASY_NOW_NAME ": addRoute: "), route.toString())); + } +} + +void ESPEasy_now_Node_statistics_t::setDiscoveryRoute(uint8_t unit, const ESPEasy_now_traceroute_struct& route) +{ + discovery_route = route; + discovery_route.addUnit(unit); + discovery_route.setSuccessRate_last_node(unit, success_rate); +} + +void ESPEasy_now_Node_statistics_t::updateSuccessRate(uint8_t unit, bool success) +{ + if (success) { + if (timePassedSince(last_update) < 100) { + // Apply some rate limiter. + return; + + // if (success_rate > 100) { --success_rate; } + } else if (success_rate < 255) { ++success_rate; } + last_update = millis(); + } else { + if (success_rate > 0) { --success_rate; } + } + + for (unsigned int i = 0; i < ESPEASY_NOW_NODE_STATISTICS_NR_ROUTES; ++i) { + if ((last_update_route[i] != 0) && (timePassedSince(last_update_route[i]) > 125000)) { + last_update_route[i] = 0; + routes[i].clear(); + } else { + routes[i].setSuccessRate_last_node(unit, success_rate); + } + } + discovery_route.setSuccessRate_last_node(unit, success_rate); +} + +uint8_t ESPEasy_now_Node_statistics_t::getNodeSuccessRate() const +{ + return success_rate; +} + +ESPEasy_now_traceroute_struct& ESPEasy_now_Node_statistics_t::latestRoute() +{ + return routes[last_route_index]; +} + +const ESPEasy_now_traceroute_struct& ESPEasy_now_Node_statistics_t::latestRoute() const +{ + return routes[last_route_index]; +} + +const ESPEasy_now_traceroute_struct * ESPEasy_now_Node_statistics_t::bestRoute() const +{ + int bestIndex = -1; + int bestSuccessRate = 0; + uint8_t bestDistance = 255; + + for (int i = 0; i < ESPEASY_NOW_NODE_STATISTICS_NR_ROUTES; ++i) { + const uint8_t distance = routes[i].getDistance(); + const int successRate = routes[i].computeSuccessRate(); + + if (distance == bestDistance) { + if ((successRate > bestSuccessRate) && (distance < 255)) { + bestSuccessRate = successRate; + bestIndex = i; + } + } else if (distance < bestDistance) { + bestIndex = i; + bestDistance = distance; + bestSuccessRate = successRate; + } + } + + if (bestIndex >= 0) { + return &routes[bestIndex]; + } + + return nullptr; +} + +const ESPEasy_now_traceroute_struct& ESPEasy_now_Node_statistics_t::discoveryRoute() const +{ + return discovery_route; +} + +ESPEasy_Now_MQTT_QueueCheckState::Enum ESPEasy_now_Node_statistics_t::getMQTTQueueState() const +{ + return mqtt_queue_state; +} + +void ESPEasy_now_Node_statistics_t::setMQTTQueueState(ESPEasy_Now_MQTT_QueueCheckState::Enum state) +{ + mqtt_queue_state = state; +} + +#endif // ifdef USES_ESPEASY_NOW diff --git a/src/src/DataStructs/ESPEasy_now_Node_statistics.h b/src/src/DataStructs/ESPEasy_now_Node_statistics.h new file mode 100644 index 0000000000..986c630b5a --- /dev/null +++ b/src/src/DataStructs/ESPEasy_now_Node_statistics.h @@ -0,0 +1,61 @@ +#ifndef DATASTRUCTS_ESPEASY_NOW_NODE_STATISTICS_H +#define DATASTRUCTS_ESPEASY_NOW_NODE_STATISTICS_H + +#include "../../ESPEasy_common.h" + +#ifdef USES_ESPEASY_NOW + +#include "../DataStructs/ESPEasy_Now_MQTT_queue_check_packet.h" +#include "../DataStructs/ESPEasy_now_traceroute.h" + +#include + +#define ESPEASY_NOW_NODE_STATISTICS_NR_ROUTES 3 + +struct ESPEasy_now_Node_statistics_t { + unsigned long getAge() const; + + // Store route received via traceroute packet + void addRoute(uint8_t unit, const ESPEasy_now_traceroute_struct& route); + + // Store route received via Discovery packet + void setDiscoveryRoute(uint8_t unit, const ESPEasy_now_traceroute_struct& route); + + void updateSuccessRate(uint8_t unit, bool success); + + uint8_t getNodeSuccessRate() const; + + ESPEasy_now_traceroute_struct & latestRoute(); + + const ESPEasy_now_traceroute_struct& latestRoute() const; + + const ESPEasy_now_traceroute_struct* bestRoute() const; + + const ESPEasy_now_traceroute_struct& discoveryRoute() const; + + ESPEasy_Now_MQTT_QueueCheckState::Enum getMQTTQueueState() const; + + void setMQTTQueueState(ESPEasy_Now_MQTT_QueueCheckState::Enum state); + +private: + + ESPEasy_now_traceroute_struct discovery_route; + ESPEasy_now_traceroute_struct routes[ESPEASY_NOW_NODE_STATISTICS_NR_ROUTES]; + + unsigned long last_update_route[ESPEASY_NOW_NODE_STATISTICS_NR_ROUTES] = { 0 }; + + unsigned long last_update = 0; + + uint8_t last_route_index = 0; + + // Increase on success, decrease on fail, with limits of 255 and 0. + uint8_t success_rate = 127; + + ESPEasy_Now_MQTT_QueueCheckState::Enum mqtt_queue_state = ESPEasy_Now_MQTT_QueueCheckState::Enum::Unset; +}; + +typedef std::map ESPEasy_now_Node_statisticsMap; + +#endif // ifdef USES_ESPEASY_NOW + +#endif // ifndef DATASTRUCTS_ESPEASY_NOW_NODE_STATISTICS_H diff --git a/src/src/DataStructs/ESPEasy_now_hdr.cpp b/src/src/DataStructs/ESPEasy_now_hdr.cpp new file mode 100644 index 0000000000..f769ce1620 --- /dev/null +++ b/src/src/DataStructs/ESPEasy_now_hdr.cpp @@ -0,0 +1,52 @@ +#include "../DataStructs/ESPEasy_now_hdr.h" + +#ifdef USES_ESPEASY_NOW + +const __FlashStringHelper * ESPEasy_now_hdr::toString(ESPEasy_now_hdr::message_t messageType) +{ + switch (messageType) { + case ESPEasy_now_hdr::message_t::NotSet: return F("-"); + case ESPEasy_now_hdr::message_t::Acknowledgement: return F("Ack"); + case ESPEasy_now_hdr::message_t::Announcement: return F("Ann"); + case ESPEasy_now_hdr::message_t::MQTTControllerMessage: return F("MQTTctr"); + case ESPEasy_now_hdr::message_t::NTP_Query: return F("NTP"); + case ESPEasy_now_hdr::message_t::SendData_DuplicateCheck: return F("DupChk"); + case ESPEasy_now_hdr::message_t::MQTTCheckControllerQueue: return F("MQTTchk"); + case ESPEasy_now_hdr::message_t::P2P_data: return F("p2p"); + case ESPEasy_now_hdr::message_t::TraceRoute: return F("tracert"); + case ESPEasy_now_hdr::message_t::ChecksumError: return F("ChkErr"); + } + return F("?"); +} + +ESPEasy_now_hdr::ESPEasy_now_hdr() {} + +ESPEasy_now_hdr::ESPEasy_now_hdr(ESPEasy_now_hdr::message_t messageType) + : message_type(messageType) {} + +ESPEasy_now_hdr::ESPEasy_now_hdr(const uint8_t *buf) +{ + memcpy(this, buf, sizeof(ESPEasy_now_hdr)); +} + +ESPEasy_now_hdr::ESPEasy_now_hdr(const ESPEasy_now_hdr& other) +{ + *this = other; +} + +ESPEasy_now_hdr& ESPEasy_now_hdr::operator=(const ESPEasy_now_hdr& other) +{ + if (&other == this) { + return *this; + } + header_version = other.header_version; + message_type = other.message_type; + packet_nr = other.packet_nr; + nr_packets = other.nr_packets; + message_count = other.message_count; + payload_size = other.payload_size; + checksum = other.checksum; + return *this; +} + +#endif // ifdef USES_ESPEASY_NOW diff --git a/src/src/DataStructs/ESPEasy_now_hdr.h b/src/src/DataStructs/ESPEasy_now_hdr.h new file mode 100644 index 0000000000..a0883bff02 --- /dev/null +++ b/src/src/DataStructs/ESPEasy_now_hdr.h @@ -0,0 +1,58 @@ +#ifndef DATASTRUCTS_ESPEASY_NOW_HDR_H +#define DATASTRUCTS_ESPEASY_NOW_HDR_H + +/*********************************************************************************************\ +* ESPEasy_now_message_struct +\*********************************************************************************************/ + +#include "../../ESPEasy_common.h" + +#ifdef USES_ESPEASY_NOW + +#include "../Globals/ESPEasy_now_state.h" + +# define ESPEASY_NOW_HEADER_VERSION 2 + +class __attribute__((__packed__)) ESPEasy_now_hdr { +public: + + // Do not change the order of this enum as the value will be sent to other nodes. + enum class message_t : uint8_t { + NotSet = 0, + Acknowledgement, + Announcement, + MQTTControllerMessage, + NTP_Query, + SendData_DuplicateCheck, + MQTTCheckControllerQueue, + P2P_data, + TraceRoute, + + ChecksumError = 255 + }; + + static const __FlashStringHelper * toString(message_t messageType); + + ESPEasy_now_hdr(); + + ESPEasy_now_hdr(message_t messageType); + + ESPEasy_now_hdr(const uint8_t *buf); + + ESPEasy_now_hdr(const ESPEasy_now_hdr& other); + + ESPEasy_now_hdr& operator=(const ESPEasy_now_hdr& other); + + + uint8_t header_version = ESPEASY_NOW_HEADER_VERSION; // To be used later to detect newer versions + message_t message_type = message_t::NotSet; + uint8_t packet_nr = 0; // Current message number (start at 0) + uint8_t nr_packets = 1; // The highest message number of this sequence + uint8_t message_count = 1; // A set of messages all have the same message_count + uint8_t payload_size = 0; // Size of the payload + uint16_t checksum = 1; // checksum of the packet, initialize to value which cannot match on an empty packet. +}; + +#endif // ifdef USES_ESPEASY_NOW + +#endif // DATASTRUCTS_ESPEASY_NOW_HDR_H diff --git a/src/src/DataStructs/ESPEasy_now_merger.cpp b/src/src/DataStructs/ESPEasy_now_merger.cpp new file mode 100644 index 0000000000..3ba7288cc7 --- /dev/null +++ b/src/src/DataStructs/ESPEasy_now_merger.cpp @@ -0,0 +1,283 @@ +#include "../DataStructs/ESPEasy_now_merger.h" + +#ifdef USES_ESPEASY_NOW + +# include "../Helpers/ESPEasy_time_calc.h" + +# define ESPEASY_NOW_MESSAGE_TIMEOUT 5000 + + +ESPEasy_now_merger::ESPEasy_now_merger() { + _firstPacketTimestamp = millis(); +} + +void ESPEasy_now_merger::addPacket( + uint8_t packet_nr, + const MAC_address& mac, + const uint8_t *buf, + size_t packetSize) +{ + # ifdef ESP8266 + const uint16_t maxFreeBlock = ESP.getMaxFreeBlockSize(); + + if (2 * packetSize > maxFreeBlock) { + // Not enough free memory to process the block. + // Since this message will never be complete, set the timer to an expired value. + _firstPacketTimestamp -= ESPEASY_NOW_MESSAGE_TIMEOUT; + return; + } + # endif // ifdef ESP8266 + + // FIXME TD-er: How to handle duplicates? + // We might receive some packets several times if the sender does not receive our acknowledgement + ESPEasy_Now_packet packet; + packet.setReceivedPacket(mac, buf, packetSize); + + if (packet.valid()) { + _queue[packet_nr] = std::move(packet); + _firstPacketTimestamp = millis(); + } +} + +bool ESPEasy_now_merger::messageComplete() const +{ + return _queue.size() >= getExpectedNrPackets(); +} + +bool ESPEasy_now_merger::expired() const +{ + return timePassedSince(_firstPacketTimestamp) > ESPEASY_NOW_MESSAGE_TIMEOUT; +} + +bool ESPEasy_now_merger::valid() const +{ + for (auto it = _queue.begin(); it != _queue.end(); ++it) { + if (!it->second.valid()) { + return false; + } + } + return true; +} + +ESPEasy_Now_packet_map::const_iterator ESPEasy_now_merger::find(uint8_t packet_nr) const +{ + return _queue.find(packet_nr); +} + +uint8_t ESPEasy_now_merger::getExpectedNrPackets() const +{ + for (auto it = _queue.begin(); it != _queue.end(); ++it) { + if (it->second.valid()) { + return it->second.getHeader().nr_packets; + } + } + return 255; +} + +ESPEasy_now_hdr::message_t ESPEasy_now_merger::getMessageType() const +{ + for (auto it = _queue.begin(); it != _queue.end(); ++it) { + if (it->second.valid()) { + return it->second.getHeader().message_type; + } + } + return ESPEasy_now_hdr::message_t::NotSet; +} + +unsigned long ESPEasy_now_merger::getFirstPacketTimestamp() const +{ + return _firstPacketTimestamp; +} + +bool ESPEasy_now_merger::getMessageCount(uint8_t& count) const +{ + auto it = _queue.find(0); + + if (it == _queue.end()) { + return false; + } + count = it->second.getHeader().message_count; + return true; +} + +bool ESPEasy_now_merger::getMac(uint8_t *mac) const +{ + auto it = _queue.find(0); + + if (it == _queue.end()) { + return false; + } + memcpy(mac, it->second._mac, 6); + return true; +} + +bool ESPEasy_now_merger::getMac(MAC_address& mac) const +{ + return getMac(mac.mac); +} + +String ESPEasy_now_merger::getLogString() const +{ + MAC_address mac; + + getMac(mac); + String log; + + log.reserve(64); + log += mac.toString(); + log += F(" payload: "); + log += getPayloadSize(); + log += F(" type: "); + log += ESPEasy_now_hdr::toString(getMessageType()); + log += F(" ("); + log += _queue.size(); + log += '/'; + log += getExpectedNrPackets(); + log += ')'; + return log; +} + +size_t ESPEasy_now_merger::getPayloadSize() const +{ + if (!messageComplete()) { return 0; } + size_t payloadSize = 0; + + for (auto it = _queue.begin(); it != _queue.end(); ++it) { + payloadSize += it->second.getPayloadSize(); + } + return payloadSize; +} + +String ESPEasy_now_merger::getString(size_t& payload_pos) const +{ + String res; + + getString(res, payload_pos); + return res; +} + +bool ESPEasy_now_merger::getString(String& string, size_t& payload_pos) const +{ + size_t stringLength = 0; + { + // Compute the expected string size, so we don't have to perform re-allocations + size_t tmp_payload_pos = payload_pos; + stringLength = str_len(tmp_payload_pos); + + if (stringLength == 0) { + return false; + } + + if (!string.reserve(stringLength)) { + return false; + } + } + + const size_t bufsize = 128; + uint8_t buf[bufsize] = { 0 }; + + bool done = false; + + // We do fetch more data from the message than the string size, so copy payload_pos first + size_t tmp_payload_pos = payload_pos; + + while (!done) { + size_t received = getBinaryData(&buf[0], bufsize, tmp_payload_pos); + + for (size_t buf_pos = 0; buf_pos < received && !done; ++buf_pos) { + char c = static_cast(buf[buf_pos]); + + if (c == 0) { + done = true; + } else { + string += c; + } + } + + if (received < bufsize) { done = true; } + } + payload_pos += string.length() + 1; // Store the position of the null termination + return true; +} + +size_t ESPEasy_now_merger::str_len(size_t& payload_pos) const +{ + const size_t bufsize = 128; + uint8_t buf[bufsize] = { 0 }; + bool done = false; + + // We do fetch more data from the message than the string size, so copy payload_pos first + size_t tmp_payload_pos = payload_pos; + size_t res = 0; + + while (!done) { + size_t received = getBinaryData(&buf[0], bufsize, tmp_payload_pos); + + for (size_t buf_pos = 0; buf_pos < received && !done; ++buf_pos) { + char c = static_cast(buf[buf_pos]); + + if (c == 0) { + done = true; + } else { + ++res; + } + } + + if (received < bufsize) { done = true; } + } + payload_pos += res + 1; // Store the position of the null termination + return res; +} + +size_t ESPEasy_now_merger::getBinaryData(uint8_t *data, size_t length, size_t& payload_pos) const +{ + size_t payload_pos_in_packet; + uint8_t packet_nr = findPacketWithPayloadPos(payload_pos, payload_pos_in_packet); + + if (packet_nr >= getExpectedNrPackets()) { + return 0; + } + + auto it = _queue.find(packet_nr); + size_t data_pos = 0; + + while (it != _queue.end() && data_pos < length) { + size_t added_length = it->second.getBinaryData(data, length - data_pos, payload_pos_in_packet); + data += added_length; + data_pos += added_length; + + // Continue in next packet + payload_pos_in_packet = 0; + ++packet_nr; + it = _queue.find(packet_nr); + } + payload_pos += data_pos; + return data_pos; +} + +uint8_t ESPEasy_now_merger::findPacketWithPayloadPos(size_t payload_pos, size_t& payload_pos_in_packet) const +{ + // First find the place in the queue to continue based on the payload_pos + uint8_t packet_nr = 0; + + payload_pos_in_packet = 0; + auto it = _queue.find(packet_nr); + + // Position in message payload at the start of a packet + size_t packet_start_payload_pos = 0; + + while (it != _queue.end() && packet_start_payload_pos <= payload_pos) { + if ((packet_start_payload_pos + it->second.getPayloadSize()) >= payload_pos) { + // Message payload position is in current packet. + payload_pos_in_packet = payload_pos - packet_start_payload_pos; + return packet_nr; + } + packet_start_payload_pos += it->second.getPayloadSize(); + + ++packet_nr; + it = _queue.find(packet_nr); + } + return 255; // Error value +} + +#endif // ifdef USES_ESPEASY_NOW diff --git a/src/src/DataStructs/ESPEasy_now_merger.h b/src/src/DataStructs/ESPEasy_now_merger.h new file mode 100644 index 0000000000..eab83010c1 --- /dev/null +++ b/src/src/DataStructs/ESPEasy_now_merger.h @@ -0,0 +1,89 @@ +#ifndef DATASTRUCTS_ESPEASY_NOW_MERGER_H +#define DATASTRUCTS_ESPEASY_NOW_MERGER_H + +#include "../../ESPEasy_common.h" + +#ifdef USES_ESPEASY_NOW + +#include "../DataStructs/ESPEasy_now_hdr.h" +#include "../DataStructs/ESPEasy_Now_packet.h" +#include "../DataStructs/MAC_address.h" + +#include + + +// Class to process all incoming messages from a single sender. +// One or more packets form a complete message. +// This class only contains packets which have the same: +// - Sender mac +// - Message Count +// - Message Type +class ESPEasy_now_merger { +public: + + ESPEasy_now_merger(); + ESPEasy_now_merger(const ESPEasy_now_merger& other) = delete; + ESPEasy_now_merger(ESPEasy_now_merger&& other) = default; + + ESPEasy_now_merger& operator=(ESPEasy_now_merger&& other) = default; + + void ICACHE_FLASH_ATTR addPacket( + uint8_t packet_nr, + const MAC_address& mac, + const uint8_t *buf, + size_t packetSize); + + // Check if all parts of the packet have been received + bool messageComplete() const; + + // Check of set has expired (not all packets received within timeout) + bool expired() const; + + bool valid() const; + + ESPEasy_Now_packet_map::const_iterator find(uint8_t packet_nr) const; + + // Get combined size of all packets. + size_t getPayloadSize() const; + + // Return a string starting from position pos in the buffer. + // payload_pos will contain the new position to start for a next string + String getString(size_t& payload_pos) const; + bool getString(String& string, + size_t& payload_pos) const; + + // Compute the length of a string starting from position pos in the buffer. + // payload_pos will contain the new position to start for a next string + size_t str_len(size_t& payload_pos) const; + + size_t getBinaryData(uint8_t *data, + size_t length, + size_t & payload_pos) const; + + uint8_t getExpectedNrPackets() const; + ESPEasy_now_hdr::message_t getMessageType() const; + + bool getMac(uint8_t *mac) const; + bool getMac(MAC_address& mac) const; + + String getLogString() const; + + unsigned long getFirstPacketTimestamp() const; + + bool getMessageCount(uint8_t& count) const; + + +private: + + // Find packet + payload position in packet for payload_pos of entire message + uint8_t findPacketWithPayloadPos(size_t payload_pos, + size_t& payload_pos_in_packet) const; + + unsigned long _firstPacketTimestamp = 0; + std::map_queue; + uint8_t _nr_packets = 255; +}; + +#endif // ifdef USES_ESPEASY_NOW + +#endif // DATASTRUCTS_ESPEASY_NOW_MERGER_H diff --git a/src/src/DataStructs/ESPEasy_now_splitter.cpp b/src/src/DataStructs/ESPEasy_now_splitter.cpp new file mode 100644 index 0000000000..432769e7d6 --- /dev/null +++ b/src/src/DataStructs/ESPEasy_now_splitter.cpp @@ -0,0 +1,249 @@ +#include "../DataStructs/ESPEasy_now_splitter.h" + +#ifdef USES_ESPEASY_NOW + +# include "../ESPEasyCore/ESPEasyWifi.h" +# include "../ESPEasyCore/ESPEasy_Log.h" +# include "../ESPEasyCore/ESPEasyWifi.h" +# include "../DataStructs/ESPEasy_now_hdr.h" +# include "../DataStructs/TimingStats.h" +# include "../Globals/Nodes.h" +# include "../Globals/Settings.h" +# include "../Globals/ESPEasy_now_peermanager.h" +# include "../Helpers/ESPEasy_time_calc.h" + +static uint8_t ESPEasy_now_message_count = 1; + +ESPEasy_now_splitter::ESPEasy_now_splitter(ESPEasy_now_hdr::message_t message_type, size_t totalSize) + : _header(message_type), _totalSize(totalSize) +{ + _header.message_count = ++ESPEasy_now_message_count; +} + +size_t ESPEasy_now_splitter::addBinaryData(const uint8_t *data, size_t length) +{ + if (data == nullptr || length == 0) { + return 0; + } + size_t data_left = length; + + while ((data_left > 0) && (_totalSize > _bytesStored)) { + if (!createNextPacket()) { + return length - data_left; + } + const size_t bytesAdded = _queue.back().addBinaryData(data, data_left, _payload_pos); + if (bytesAdded == 0) { + return length - data_left; + } + data_left -= bytesAdded; + data += bytesAdded; + _bytesStored += bytesAdded; + } + return length; +} + +size_t ESPEasy_now_splitter::addString(const String& string) +{ + size_t length = string.length() + 1; // Store the extra null-termination + + return addBinaryData(reinterpret_cast(string.c_str()), length); +} + +bool ESPEasy_now_splitter::createNextPacket() +{ + size_t current_PayloadSize = ESPEasy_Now_packet::getMaxPayloadSize(); + + if (_queue.size() > 0) { + current_PayloadSize = _queue.back().getPayloadSize(); + } + + if (current_PayloadSize > _payload_pos) { + // No need to create a new file yet + + /* + String log; + log = F("createNextPacket "); + log += data_left; + addLog(LOG_LEVEL_INFO, log); + */ + return true; + } + + // Determine size of next packet + size_t message_bytes_left = _totalSize - _bytesStored; + _header.payload_size = message_bytes_left - sizeof(ESPEasy_now_hdr); + + ESPEasy_Now_packet newPacket(_header, message_bytes_left); + if (!newPacket.valid()) { + return false; + } + _queue.push_back(std::move(newPacket)); + _payload_pos = 0; + + // Set the packet number for the next packet. + // Total packet count will be set right before sending them. + _header.packet_nr++; + return true; +} + +bool ESPEasy_now_splitter::sendToBroadcast() +{ + return send(ESPEasy_now_peermanager.getBroadcastMAC(), 0); +} + +bool ESPEasy_now_splitter::send(const MAC_address& mac, + int channel) +{ + if (!prepareForSend(mac)) { + return false; + } + + const size_t nr_packets = _queue.size(); + + for (uint8_t i = 0; i < nr_packets; ++i) { + if (!send(_queue[i], channel)) { return false; } + } + return true; +} + +WifiEspNowSendStatus ESPEasy_now_splitter::send(const MAC_address& mac, size_t timeout, int channel) +{ + START_TIMER; + if (!prepareForSend(mac)) { + return WifiEspNowSendStatus::FAIL; + } + + WifiEspNowSendStatus sendStatus = WifiEspNowSendStatus::NONE; + const size_t nr_packets = _queue.size(); + + for (uint8_t i = 0; i < nr_packets; ++i) { + uint8_t retry = 2; + + while (retry > 0) { + --retry; + send(_queue[i], channel); + sendStatus = waitForSendStatus(timeout); + + if (sendStatus == WifiEspNowSendStatus::OK) { + retry = 0; + } + # ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + String log; + log.reserve(85); + + switch (sendStatus) { + case WifiEspNowSendStatus::NONE: + { + log += F(ESPEASY_NOW_NAME); + log += F(": TIMEOUT to: "); + break; + } + case WifiEspNowSendStatus::FAIL: + { + log += F(ESPEASY_NOW_NAME); + log += F(": Sent FAILED to: "); + break; + } + case WifiEspNowSendStatus::OK: + { + log += F(ESPEASY_NOW_NAME); + log += F(": Sent to: "); + break; + } + } + log += _queue[i].getLogString(); + addLog(LOG_LEVEL_DEBUG, log); + } + # endif // ifndef BUILD_NO_DEBUG + + if (retry != 0) { + delay(10); + } + } + + switch (sendStatus) { + case WifiEspNowSendStatus::NONE: + case WifiEspNowSendStatus::FAIL: + { + Nodes.updateSuccessRate(mac, false); + STOP_TIMER(ESPEASY_NOW_SEND_MSG_FAIL); + return sendStatus; + } + case WifiEspNowSendStatus::OK: + { + Nodes.updateSuccessRate(mac, true); + ESPEasy_now_peermanager.addPeer(mac, channel); + break; + } + } + } + STOP_TIMER(ESPEASY_NOW_SEND_MSG_SUC); + return sendStatus; +} + +bool ESPEasy_now_splitter::send(const ESPEasy_Now_packet& packet, int channel) +{ + bool res = false; + START_TIMER; + if (ESPEasy_now_peermanager.addPeer(packet._mac, channel)) { + #ifdef ESP8266 + // Set TX power based on RSSI of other ESPEasy-NOW node. + // For broadcast messages power must be max. + float tx_pwr = 30; // Will be set higher based on RSSI when needed. + int8_t rssi = -99; // Assume worst RSSI for broadcast + SetWiFiTXpower(tx_pwr, rssi); + #endif + if (packet.valid()) { + res = WifiEspNow.send(packet._mac, packet[0], packet.getSize()); + } + } + + STOP_TIMER(ESPEASY_NOW_SEND_PCKT); + + delay(0); + return res; +} + +WifiEspNowSendStatus ESPEasy_now_splitter::waitForSendStatus(size_t timeout) const +{ + WifiEspNowSendStatus sendStatus = WifiEspNowSendStatus::NONE; + + if (timeout < 20) { + // ESP-now keeps sending for 20 msec using lower transfer rated every 2nd attempt. + timeout = 20; + } + + size_t timer = millis() + timeout; + + while (!timeOutReached(timer) && sendStatus == WifiEspNowSendStatus::NONE) { + sendStatus = WifiEspNow.getSendStatus(); + delay(1); + } + return sendStatus; +} + +bool ESPEasy_now_splitter::prepareForSend(const MAC_address& mac) +{ + if (!use_EspEasy_now) return false; + size_t nr_packets = _queue.size(); + + for (uint8_t i = 0; i < nr_packets; ++i) { + ESPEasy_now_hdr header = _queue[i].getHeader(); + header.nr_packets = nr_packets; + header.payload_size = _queue[i].getPayloadSize(); + _queue[i].setHeader(header); + _queue[i].setMac(mac); + if (!_queue[i].valid()) { + if (loglevelActiveFor(LOG_LEVEL_ERROR)) { + addLog(LOG_LEVEL_ERROR, F(ESPEASY_NOW_NAME ": Could not prepare for send")); + } + return false; + } + } + return true; +} + + +#endif // ifdef USES_ESPEASY_NOW diff --git a/src/src/DataStructs/ESPEasy_now_splitter.h b/src/src/DataStructs/ESPEasy_now_splitter.h new file mode 100644 index 0000000000..9f94cdb992 --- /dev/null +++ b/src/src/DataStructs/ESPEasy_now_splitter.h @@ -0,0 +1,57 @@ +#ifndef DATASTRUCTS_ESPEASY_NOW_SPLITTER_H +#define DATASTRUCTS_ESPEASY_NOW_SPLITTER_H + +#include "../../ESPEasy_common.h" + +#ifdef USES_ESPEASY_NOW + +# include "../DataStructs/ESPEasy_Now_packet.h" +# include "../DataStructs/ESPEasy_now_hdr.h" + +# include "../Globals/ESPEasy_now_peermanager.h" + +class ESPEasy_now_splitter { +public: + + ESPEasy_now_splitter(ESPEasy_now_hdr::message_t message_type, + size_t totalSize); + + size_t addBinaryData(const uint8_t *data, + size_t length); + + size_t addString(const String& string); + + bool sendToBroadcast(); + bool send(const MAC_address& mac, + int channel = 0); + + WifiEspNowSendStatus send(const MAC_address& mac, + size_t timeout, + int channel); + +private: + + // Create next packet when needed. + // return false when it was needed, but failed to do so. + bool createNextPacket(); + + size_t getPayloadPos() const; + + bool send(const ESPEasy_Now_packet& packet, + int channel); + + bool prepareForSend(const MAC_address& mac); + + + WifiEspNowSendStatus waitForSendStatus(size_t timeout) const; + + std::vector_queue; + ESPEasy_now_hdr _header; + size_t _payload_pos = 255; // Position in the last packet where we left of. + const size_t _totalSize = 0; // Total size as we intend to send. + size_t _bytesStored = 0; // Total number of bytes already stored as payload +}; + +#endif // ifdef USES_ESPEASY_NOW + +#endif // DATASTRUCTS_ESPEASY_NOW_SPLITTER_H diff --git a/src/src/DataStructs/ESPEasy_now_traceroute.cpp b/src/src/DataStructs/ESPEasy_now_traceroute.cpp new file mode 100644 index 0000000000..8c803b3295 --- /dev/null +++ b/src/src/DataStructs/ESPEasy_now_traceroute.cpp @@ -0,0 +1,169 @@ +#include "../DataStructs/ESPEasy_now_traceroute.h" + +#include + +ESPEasy_now_traceroute_struct::ESPEasy_now_traceroute_struct(uint8_t size) { + unit_vector.resize(size); +} + +void ESPEasy_now_traceroute_struct::clear() +{ + unit_vector.clear(); +} + +uint8_t ESPEasy_now_traceroute_struct::getUnit(uint8_t distance, uint8_t& successRate) const +{ + if (unit_vector.size() < static_cast((distance + 1) * 2)) { + successRate = 0; + return 0; + } + successRate = unit_vector[(distance * 2) + 1]; + return unit_vector[distance * 2]; +} + +void ESPEasy_now_traceroute_struct::addUnit(uint8_t unit) +{ + // Only add the unit if it isn't already part of the traceroute. + const size_t index = unit_vector.size(); + for (size_t i = 0; i < index; i+=2) { + if (unit_vector[i] == unit) { + return; + } + } + + unit_vector.resize(index + 2); + unit_vector[index] = unit; + // Set default success rate to average + unit_vector[index + 1] = 127; +} + +uint8_t ESPEasy_now_traceroute_struct::getDistance() const +{ + if (unit_vector.size() == 0) { return 255; } + return (unit_vector.size() / 2) - 1; +} + +const uint8_t * ESPEasy_now_traceroute_struct::getData(uint8_t& size) const +{ + size = static_cast(std::min(unit_vector.size(), 255u)); + return &(unit_vector[0]); +} + +uint8_t * ESPEasy_now_traceroute_struct::get() +{ + if (unit_vector.size() == 0) { + return nullptr; + } + return &(unit_vector[0]); +} + +void ESPEasy_now_traceroute_struct::setSuccessRate_last_node(uint8_t unit, uint8_t successRate) +{ + int index = unit_vector.size() - 2; + int attempt = 0; + + while (index >= 0 && attempt < 2) { + if (unit_vector[index] == unit) { + unit_vector[index + 1] = successRate; + return; + } + index -= 2; + ++attempt; + } +} + +bool ESPEasy_now_traceroute_struct::operator<(const ESPEasy_now_traceroute_struct& other) const +{ + const int this_success = computeSuccessRate(); + const int other_success = other.computeSuccessRate(); + if (getDistance() == other.getDistance()) { + // Same distance, just pick the highest success rate + return this_success > other_success; + } + + // If success rate of a route differs > 10 points, prefer the one with highest success rate + if (this_success > (other_success + 10)) return true; + if (other_success > (this_success + 10)) return false; + + // Both have somewhat equal success rate, pick one with shortest distance. + return getDistance() < other.getDistance(); +} + +bool ESPEasy_now_traceroute_struct::sameRoute(const ESPEasy_now_traceroute_struct& other) const +{ + const uint8_t max_distance = getDistance(); + if (max_distance != other.getDistance()) { + return false; + } + for (uint8_t distance = 0; distance <= max_distance; ++distance) { + uint8_t success_rate = 0; + if (getUnit(distance, success_rate) != other.getUnit(distance, success_rate)) { + return false; + } + } + return true; +} + +int ESPEasy_now_traceroute_struct::computeSuccessRate() const +{ + const uint8_t max_distance = getDistance(); + if (max_distance == 255) { + // No values stored, so huge penalty + return 0; + } + if (max_distance == 0) { + // End point, so assume best success rate + return 255; + } + + int res = 0; + for (uint8_t distance = 0; distance <= max_distance; ++distance) { + uint8_t successRate = 0; + getUnit(distance, successRate); + + if (successRate < 50 && distance < max_distance) { + return 0; + } + + res += successRate; + } + if (max_distance > 1) { + res /= max_distance; + } + /* + res -= 10 * max_distance; + if (res < 50) res = 50; + */ + return res; +} + +bool ESPEasy_now_traceroute_struct::unitInTraceRoute(uint8_t unit) const +{ + const uint8_t max_distance = getDistance(); + for (uint8_t distance = 0; distance <= max_distance; ++distance) { + uint8_t success_rate = 0; + if (getUnit(distance, success_rate) == unit) { + return true; + } + } + return false; +} + +String ESPEasy_now_traceroute_struct::toString() const +{ + const uint8_t max_distance = getDistance(); + + if (max_distance == 255) { + return String(); + } + String res; + res.reserve(4*unit_vector.size()); + for (uint8_t distance = 0; distance <= max_distance; ++distance) { + uint8_t success_rate = 0; + res += getUnit(distance, success_rate); + res += '/'; + res += success_rate; + res += ' '; + } + return res; +} \ No newline at end of file diff --git a/src/src/DataStructs/ESPEasy_now_traceroute.h b/src/src/DataStructs/ESPEasy_now_traceroute.h new file mode 100644 index 0000000000..b1c575059e --- /dev/null +++ b/src/src/DataStructs/ESPEasy_now_traceroute.h @@ -0,0 +1,60 @@ +#ifndef DATASTRUCTS_ESPEASY_NOW_TRACEROUTE_H +#define DATASTRUCTS_ESPEASY_NOW_TRACEROUTE_H + +#include "../../ESPEasy_common.h" + +#include +#include + +/*********************************************************************************************\ +* ESPEasy-NOW Trace route +\*********************************************************************************************/ +struct ESPEasy_now_traceroute_struct +{ + ESPEasy_now_traceroute_struct() = default; + + ESPEasy_now_traceroute_struct(uint8_t size); + + void clear(); + + // Return the unit and RSSI given a distance. + // @retval 0 when giving invalid distance. + uint8_t getUnit(uint8_t distance, + uint8_t& successRate) const; + + // Append unit at the end (thus furthest distance) + void addUnit(uint8_t unit); + + uint8_t getDistance() const; + + const uint8_t* getData(uint8_t& size) const; + + // Get pointer to the raw data. + // Make sure the array is large enough to store the data. + uint8_t* get(); + + void setSuccessRate_last_node(uint8_t unit, uint8_t successRate); + + // Return true when this tracerouteis more favorable + bool operator<(const ESPEasy_now_traceroute_struct& other) const; + + bool sameRoute(const ESPEasy_now_traceroute_struct& other) const; + // For debugging purposes + String toString() const; + + // Compute success rate + int computeSuccessRate() const; + + bool unitInTraceRoute(uint8_t unit) const; + +private: + + // Node with distance 0 at front, so index/2 equals distance. + // index%2 == 0 is unit + // index%2 == 1 is SuccessRate + std::vectorunit_vector; +}; + +typedef std::map TraceRouteMap; + +#endif // ifndef DATASTRUCTS_ESPEASY_NOW_TRACEROUTE_H diff --git a/src/src/DataStructs/MAC_address.h b/src/src/DataStructs/MAC_address.h index 1f5ab6b3f1..0c1bb7a7b7 100644 --- a/src/src/DataStructs/MAC_address.h +++ b/src/src/DataStructs/MAC_address.h @@ -1,79 +1,80 @@ -#ifndef DATASTRUCTS_MAC_ADDRESS_H -#define DATASTRUCTS_MAC_ADDRESS_H - -#include -#include - -class __attribute__((__packed__)) MAC_address { -public: - - MAC_address() = default; - - MAC_address(const uint8_t new_mac[6]); - - MAC_address(const MAC_address& other); - - MAC_address& operator=(const MAC_address& other); - - bool operator==(const MAC_address& other) const { - return mac_addr_cmp(other.mac); - } - - bool operator!=(const MAC_address& other) const { - return !mac_addr_cmp(other.mac); - } - - bool operator==(const uint8_t other[6]) const { - return mac_addr_cmp(other); - } - - bool operator!=(const uint8_t other[6]) const { - return !mac_addr_cmp(other); - } - - // Parse string with MAC address. - // Returns false if the given string has no valid formatted mac address. - bool set(const char *string); - - void set(const uint8_t other[6]); - - void get(uint8_t mac_out[6]) const; - - bool all_zero() const; - - bool all_one() const; - - String toString() const; - - // An universally administered address (UAA) is uniquely assigned to a device by its manufacturer. - // The first three octets (in transmission order) identify the organization that issued - // the identifier and are known as the organizationally unique identifier (OUI) - bool isUniversal() const { - return (mac[0] & 2) == 0; - } - - // A locally administered address (LAA) is assigned to a device by a network administrator, overriding the burned-in address. - bool isLocal() const { - return !isUniversal(); - } - - // Unicast frames are meant to be received by a single network device. - // See: https://en.wikipedia.org/wiki/MAC_address#Unicast_vs._multicast - bool isUnicast() const { - return (mac[0] & 1) == 0; - } - - // Multicast frames are meant to be received by multiple network devices - // See: https://en.wikipedia.org/wiki/MAC_address#Unicast_vs._multicast - bool isMulticast() const { - return !isUnicast(); - } - - uint8_t mac[6] = { 0 }; - -private: - - bool mac_addr_cmp(const uint8_t other[6]) const; -}; - -#endif // DATASTRUCTS_MAC_ADDRESS_H \ No newline at end of file +#ifndef DATASTRUCTS_MAC_ADDRESS_H +#define DATASTRUCTS_MAC_ADDRESS_H + +#include +#include + +class __attribute__((__packed__)) MAC_address { +public: + + MAC_address() = default; + + MAC_address(const uint8_t new_mac[6]); + + MAC_address(const MAC_address& other); + + MAC_address& operator=(const MAC_address& other); + + bool operator==(const MAC_address& other) const { + return mac_addr_cmp(other.mac); + } + + bool operator!=(const MAC_address& other) const { + return !mac_addr_cmp(other.mac); + } + + bool operator==(const uint8_t other[6]) const { + return mac_addr_cmp(other); + } + + bool operator!=(const uint8_t other[6]) const { + return !mac_addr_cmp(other); + } + + // Parse string with MAC address. + // Returns false if the given string has no valid formatted mac address. + bool set(const char *string); + + void set(const uint8_t other[6]); + + void get(uint8_t mac_out[6]) const; + + bool all_zero() const; + + bool all_one() const; + + String toString() const; + + + // An universally administered address (UAA) is uniquely assigned to a device by its manufacturer. + // The first three octets (in transmission order) identify the organization that issued + // the identifier and are known as the organizationally unique identifier (OUI) + bool isUniversal() const { + return (mac[0] & 2) == 0; + } + + // A locally administered address (LAA) is assigned to a device by a network administrator, overriding the burned-in address. + bool isLocal() const { + return !isUniversal(); + } + + // Unicast frames are meant to be received by a single network device. + // See: https://en.wikipedia.org/wiki/MAC_address#Unicast_vs._multicast + bool isUnicast() const { + return (mac[0] & 1) == 0; + } + + // Multicast frames are meant to be received by multiple network devices + // See: https://en.wikipedia.org/wiki/MAC_address#Unicast_vs._multicast + bool isMulticast() const { + return !isUnicast(); + } + + uint8_t mac[6] = { 0 }; + +private: + + bool mac_addr_cmp(const uint8_t other[6]) const; +}; + +#endif // DATASTRUCTS_MAC_ADDRESS_H diff --git a/src/src/DataStructs/MessageRouteInfo.cpp b/src/src/DataStructs/MessageRouteInfo.cpp new file mode 100644 index 0000000000..b253fb2638 --- /dev/null +++ b/src/src/DataStructs/MessageRouteInfo.cpp @@ -0,0 +1,140 @@ +#include "../DataStructs/MessageRouteInfo.h" + + +MessageRouteInfo_t::MessageRouteInfo_t(const uint8_t* serializedData, size_t size) { + deserialize(serializedData, size); +} + +MessageRouteInfo_t::MessageRouteInfo_t(const uint8_t_vector& serializedData) { + if (serializedData.size() > 0) { + deserialize(&(serializedData[0]), serializedData.size()); + } +} + +bool MessageRouteInfo_t::deserialize(const uint8_t_vector& serializedData) { + if (serializedData.size() > 0) { + return deserialize(&(serializedData[0]), serializedData.size()); + } + return false; +} + +bool MessageRouteInfo_t::deserialize(const uint8_t* serializedData, size_t size) { + if (size >= 4 && serializedData != nullptr) { + size_t index = 0; + unit = serializedData[index++]; + count = serializedData[index++]; + dest_unit = serializedData[index++]; + const size_t traceSize = serializedData[index++]; + if (size >= (traceSize + 4)) { + trace.resize(traceSize); + for (size_t i = 0; i < traceSize; ++i) { + trace[i] = serializedData[index++]; + } + return true; + } + } + return false; +} + +MessageRouteInfo_t::uint8_t_vector MessageRouteInfo_t::serialize() const { + uint8_t_vector res; + res.resize(getSerializedSize()); + + size_t index = 0; + res[index++] = unit; + res[index++] = count; + res[index++] = dest_unit; + res[index++] = static_cast(std::min(trace.size(), 255u)); + for (size_t i = 0; i < trace.size(); ++i) { + res[index++] = trace[i]; + } + return res; +} + +size_t MessageRouteInfo_t::getSerializedSize() const { + return 4 + trace.size(); +} + +bool MessageRouteInfo_t::appendUnit(uint8_t unitnr) { + if (unitnr == 0 || unitnr == 255) { + return false; + } + if (unit == 0) { + unit = unitnr; + } else { + // First check we're not adding the same unitnr twice. + auto it = trace.rbegin(); + if (it != trace.rend()) { + if (*it == unitnr) { + return true; + } + } + + trace.push_back(unitnr); + } + return true; +} + +bool MessageRouteInfo_t::traceHasUnit(uint8_t unitnr) const { + return countUnitInTrace(unitnr) != 0; +} + +size_t MessageRouteInfo_t::countUnitInTrace(uint8_t unitnr) const { + size_t res = 0; + if (unitnr == 0 || unitnr == 255) { + return res; + } + for (auto it = trace.begin(); it != trace.end(); ++it) { + if (*it == unitnr) { + ++res; + } + } + if (unitnr == unit) { + ++res; + } + return res; +} + +uint8_t MessageRouteInfo_t::getLastUnitNotMatching(uint8_t unitnr) const { + for (auto it = trace.rbegin(); it != trace.rend(); ++it) { + if (*it != unitnr) { + return *it; + } + } + // Try the source unit + if (unitnr != unit) return unit; + return 0; +} + +String MessageRouteInfo_t::toString() const { + String res; + res = F("src: "); + res += unit; + res += F(" dst: "); + res += dest_unit; + res += F(" path:"); + for (auto it = trace.begin(); it != trace.end(); ++it) { + res += ' '; + res += *it; + } + return res; +} + + +bool UnitMessageRouteInfo_map::isNew(const MessageRouteInfo_t *info) const { + if (info == nullptr) { return true; } + auto it = _map.find(info->unit); + + if (it != _map.end()) { + return it->second.count != info->count; + } + return true; +} + +void UnitMessageRouteInfo_map::add(const MessageRouteInfo_t *info) { + if (info == nullptr) { return; } + + if ((info->unit != 0) && (info->unit != 255)) { + _map[info->unit] = *info; + } +} diff --git a/src/src/DataStructs/MessageRouteInfo.h b/src/src/DataStructs/MessageRouteInfo.h new file mode 100644 index 0000000000..0e4ad227f7 --- /dev/null +++ b/src/src/DataStructs/MessageRouteInfo.h @@ -0,0 +1,72 @@ +#ifndef DATASTRUCTS_MESSAGEROUTEINFO_H +#define DATASTRUCTS_MESSAGEROUTEINFO_H + +#include "../../ESPEasy_common.h" + +#include +#include + +// For deduplication, some controllers may add a unit ID and current counter. +// This count will wrap around, so it is just to detect if a message is received multiple times. +// The unit ID is the unit where the message originates from and thus should be kept along when forwarding. +struct MessageRouteInfo_t { + typedef std::vector uint8_t_vector; + + + MessageRouteInfo_t() {} + + MessageRouteInfo_t(uint8_t unitnr, uint8_t messageCount) : unit(unitnr), count(messageCount) {} + + MessageRouteInfo_t(const uint8_t* serializedData, size_t size); + + MessageRouteInfo_t(const uint8_t_vector& serializedData); + + MessageRouteInfo_t(const MessageRouteInfo_t& other) = default; + MessageRouteInfo_t(MessageRouteInfo_t&& other) = default; + MessageRouteInfo_t& operator=(const MessageRouteInfo_t& other) = default; + MessageRouteInfo_t& operator=(MessageRouteInfo_t&& other) = default; + + bool deserialize(const uint8_t_vector& serializedData); + bool deserialize(const uint8_t* serializedData, size_t size); + + uint8_t_vector serialize() const; + + size_t getSerializedSize() const; + + bool appendUnit(uint8_t unitnr); + + bool traceHasUnit(uint8_t unitnr) const; + + size_t countUnitInTrace(uint8_t unitnr) const; + + uint8_t getLastUnitNotMatching(uint8_t unitnr) const; + + String toString() const; + + // Source unit + uint8_t unit = 0; // Initialize to "not set" + + // Message counter of the source unit, used to check for duplicates. + uint8_t count = 0; + + // Destination unit (not yet used) + // Can be useful for sending messages to other units in the mesh + uint8_t dest_unit = 0; // Initialize to "not set" + + // List of all unit numbers that may have forwarded the message. + // Used to detect loops + uint8_t_vector trace; +}; + +struct UnitMessageRouteInfo_map { + bool isNew(const MessageRouteInfo_t *count) const; + + void add(const MessageRouteInfo_t *count); + +private: + + std::map _map; +}; + + +#endif // ifndef DATASTRUCTS_MESSAGEROUTEINFO_H diff --git a/src/src/DataStructs/NodeStruct.cpp b/src/src/DataStructs/NodeStruct.cpp index f60632f618..298297a455 100644 --- a/src/src/DataStructs/NodeStruct.cpp +++ b/src/src/DataStructs/NodeStruct.cpp @@ -1,328 +1,334 @@ -#include "../DataStructs/NodeStruct.h" - -#if FEATURE_ESPEASY_P2P -#include "../../ESPEasy-Globals.h" -#include "../DataTypes/NodeTypeID.h" -#include "../ESPEasyCore/ESPEasyNetwork.h" -#include "../Globals/SecuritySettings.h" -#include "../Globals/Settings.h" -#include "../Helpers/ESPEasy_time_calc.h" -#include "../Helpers/StringConverter.h" - - -#define NODE_STRUCT_AGE_TIMEOUT 300000 // 5 minutes - -NodeStruct::NodeStruct() : - ESPEasyNowPeer(0), - useAP_ESPEasyNow(0), - scaled_rssi(0) -#if FEATURE_USE_IPV6 - ,hasIPv4(0) - ,hasIPv6_mac_based_link_local(0) - ,hasIPv6_mac_based_link_global(0) - ,unused(0) -#endif -{} - -bool NodeStruct::valid() const { - // FIXME TD-er: Must make some sanity checks to see if it is a valid message - return true; -} - -bool NodeStruct::validate(const IPAddress& remoteIP) { - if (build < 20107) { - // webserverPort introduced in 20107 - webgui_portnumber = 80; - for (uint8_t i = 0; i < 6; ++i) { - ap_mac[i] = 0; - } - load = 0; - distance = 255; - timeSource = static_cast(timeSource_t::No_time_source); - channel = 0; - ESPEasyNowPeer = 0; - useAP_ESPEasyNow = 0; - setRSSI(0); - lastUpdated = 0; - } - if (build < 20253) { - version = 0; -#if FEATURE_USE_IPV6 - hasIPv4 = 0; - hasIPv6_mac_based_link_local = 0; - hasIPv6_mac_based_link_global = 0; - - unused = 0; -#else - unused = 0; -#endif - - unix_time_frac = 0; - unix_time_sec = 0; - } - -#if FEATURE_USE_IPV6 - // Check if we're in the same global subnet - if (Settings.EnableIPv6() && - hasIPv6_mac_based_link_global && - remoteIP.type() == IPv6) { - const IPAddress this_global = NetworkGlobalIP6(); - // Check first 64 bit to see if we're in the same global scope - for (int i = 0; i < 8 && hasIPv6_mac_based_link_global; ++i) { - if (this_global[i] != remoteIP[i]) - hasIPv6_mac_based_link_global = false; - } - } -#endif - - // FIXME TD-er: Must make some sanity checks to see if it is a valid message - return valid(); -} - -bool NodeStruct::operator<(const NodeStruct &other) const { - const bool thisExpired = isExpired(); - if (thisExpired != other.isExpired()) { - return !thisExpired; - } - - const bool markedAsPriority = markedAsPriorityPeer(); - if (markedAsPriority != other.markedAsPriorityPeer()) { - return markedAsPriority; - } - - if (ESPEasyNowPeer != other.ESPEasyNowPeer) { - // One is confirmed, so prefer that one. - return ESPEasyNowPeer; - } - - const int8_t thisRssi = getRSSI(); - const int8_t otherRssi = other.getRSSI(); - - int score_this = getLoad(); - int score_other = other.getLoad(); - - if (distance != other.distance) { - if (!isExpired() && !other.isExpired()) { - // Distance is not the same, so take distance into account. - return distance < other.distance; -/* - int distance_penalty = distance - other.distance; - distance_penalty = distance_penalty * distance_penalty * 10; - if (distance > other.distance) { - score_this += distance_penalty; - } else { - score_other += distance_penalty; - } -*/ - } - } - - if (thisRssi >= 0 || otherRssi >= 0) { - // One or both have no RSSI, so cannot use RSSI in computing score - } else { - // RSSI value is negative, so subtract the value - // RSSI range from -38 ... 99 - // Shift RSSI and add a weighing factor to make sure - // A load of 100% with RSSI of -40 is preferred over a load of 20% with an RSSI of -80. - score_this -= (thisRssi + 38) * 2; - score_other -= (otherRssi + 38) * 2; - } - return score_this < score_other; -} - - -const __FlashStringHelper * NodeStruct::getNodeTypeDisplayString() const { - return toNodeTypeDisplayString(nodeType); -} - -String NodeStruct::getNodeName() const { - String res; - size_t length = strnlen(reinterpret_cast(nodeName), sizeof(nodeName)); - - if (reserve_special(res, length)) { - for (size_t i = 0; i < length; ++i) { - res += static_cast(nodeName[i]); - } - } - return res; -} - -IPAddress NodeStruct::IP() const { - return IPAddress(ip[0], ip[1], ip[2], ip[3]); -} - -#if FEATURE_USE_IPV6 -IPAddress NodeStruct::IPv6_link_local(bool stripZone) const -{ - if (Settings.EnableIPv6() && hasIPv6_mac_based_link_local) { - // Base IPv6 on MAC address - IPAddress ipv6; - if (IPv6_link_local_from_MAC(sta_mac, ipv6)) { - if (stripZone) { - return IPAddress(IPv6, &ipv6[0], 0); - } - return ipv6; - } - } - return IN6ADDR_ANY; -} - -IPAddress NodeStruct::IPv6_global() const -{ - if (Settings.EnableIPv6() && hasIPv6_mac_based_link_global) { - // Base IPv6 on MAC address - IPAddress ipv6; - if (IPv6_global_from_MAC(sta_mac, ipv6)) { - return ipv6; - } - } - return IN6ADDR_ANY; -} - -bool NodeStruct::hasIPv6() const { - if (!Settings.EnableIPv6()) return false; - return hasIPv6_mac_based_link_local || - hasIPv6_mac_based_link_global; -} -#endif - - -MAC_address NodeStruct::STA_MAC() const { - return MAC_address(sta_mac); -} - -MAC_address NodeStruct::ESPEasy_Now_MAC() const { - if (ESPEasyNowPeer == 0) return MAC_address(); - if (useAP_ESPEasyNow) { - return MAC_address(ap_mac); - } - return MAC_address(sta_mac); -} - -unsigned long NodeStruct::getAge() const { - return timePassedSince(lastUpdated); -} - -bool NodeStruct::isExpired() const { - return getAge() > NODE_STRUCT_AGE_TIMEOUT; -} - -float NodeStruct::getLoad() const { - return load / 2.55; -} - -String NodeStruct::getSummary() const { - String res; - if (reserve_special(res, 48)) { - res = F("Unit: "); - res += unit; - res += F(" \""); - res += getNodeName(); - res += '"'; - res += F(" load: "); - res += String(getLoad(), 1); - res += F(" RSSI: "); - res += getRSSI(); - res += F(" ch: "); - res += channel; - res += F(" dst: "); - res += distance; - } - return res; -} - -bool NodeStruct::setESPEasyNow_mac(const MAC_address& received_mac) -{ - if (received_mac.all_zero()) return false; - if (received_mac == sta_mac) { - ESPEasyNowPeer = 1; - useAP_ESPEasyNow = 0; - return true; - } - - if (received_mac == ap_mac) { - ESPEasyNowPeer = 1; - useAP_ESPEasyNow = 1; - return true; - } - return false; -} - -int8_t NodeStruct::getRSSI() const -{ - if (scaled_rssi == 0) { - return 0; // Not set - } - - if (scaled_rssi == 0x3F) { - return 31; // Error state - } - - // scaled_rssi = 1 ... 62 - // output = -38 ... -99 - int8_t rssi = scaled_rssi + 37; - return rssi * -1; -} - -void NodeStruct::setRSSI(int8_t rssi) -{ - if (rssi == 0) { - // Not set - scaled_rssi = 0; - return; - } - - if (rssi > 0) { - // Error state - scaled_rssi = 0x3F; - return; - } - rssi *= -1; - rssi -= 37; - - if (rssi < 1) { - scaled_rssi = 1; - return; - } - - if (rssi >= 0x3F) { - scaled_rssi = 0x3F - 1; - return; - } - scaled_rssi = rssi; -} - -bool NodeStruct::markedAsPriorityPeer() const -{ -#ifdef USES_ESPEASY_NOW - for (int i = 0; i < ESPEASY_NOW_PEER_MAX; ++i) { - if (SecuritySettings.peerMacSet(i)) { - if (match(SecuritySettings.EspEasyNowPeerMAC[i])) { - return true; - } - } - } -#endif - return false; -} - -bool NodeStruct::match(const MAC_address& mac) const -{ - return (mac == sta_mac || mac == ap_mac); -} - -bool NodeStruct::isThisNode() const -{ - // Check to see if we process a node we've sent ourselves. - if (WifiSoftAPmacAddress() == ap_mac) return true; - if (WifiSTAmacAddress() == sta_mac) return true; - - return false; -} - -void NodeStruct::setAP_MAC(const MAC_address& mac) -{ - mac.get(ap_mac); -} - -#endif +#include "../DataStructs/NodeStruct.h" + +#if FEATURE_ESPEASY_P2P +#include "../../ESPEasy-Globals.h" +#include "../DataTypes/NodeTypeID.h" +#include "../ESPEasyCore/ESPEasyNetwork.h" +#include "../Globals/SecuritySettings.h" +#include "../Globals/Settings.h" +#include "../Helpers/ESPEasy_time_calc.h" +#include "../Helpers/StringConverter.h" + + +#define NODE_STRUCT_AGE_TIMEOUT 300000 // 5 minutes + +NodeStruct::NodeStruct() : + ESPEasyNowPeer(0), + useAP_ESPEasyNow(0), + scaled_rssi(0) +#if FEATURE_USE_IPV6 + ,hasIPv4(0) + ,hasIPv6_mac_based_link_local(0) + ,hasIPv6_mac_based_link_global(0) + ,unused(0) +#endif +{} + +bool NodeStruct::valid() const { + // FIXME TD-er: Must make some sanity checks to see if it is a valid message + return true; +} + +bool NodeStruct::validate() { + IPAddress tmp(0, 0, 0, 0); + return validate(tmp); +} + +bool NodeStruct::validate(const IPAddress& remoteIP) { + if (build < 20107) { + // webserverPort introduced in 20107 + webgui_portnumber = 80; + for (uint8_t i = 0; i < 6; ++i) { + ap_mac[i] = 0; + } + load = 0; + distance = 255; + timeSource = static_cast(timeSource_t::No_time_source); + channel = 0; + ESPEasyNowPeer = 0; + useAP_ESPEasyNow = 0; + setRSSI(0); + lastUpdated = 0; + } + if (build < 20253) { + version = 0; +#if FEATURE_USE_IPV6 + hasIPv4 = 0; + hasIPv6_mac_based_link_local = 0; + hasIPv6_mac_based_link_global = 0; + + unused = 0; +#else + unused = 0; +#endif + + unix_time_frac = 0; + unix_time_sec = 0; + } + +#if FEATURE_USE_IPV6 + // Check if we're in the same global subnet + if (Settings.EnableIPv6() && + hasIPv6_mac_based_link_global && + remoteIP.type() == IPv6) { + const IPAddress this_global = NetworkGlobalIP6(); + // Check first 64 bit to see if we're in the same global scope + for (int i = 0; i < 8 && hasIPv6_mac_based_link_global; ++i) { + if (this_global[i] != remoteIP[i]) + hasIPv6_mac_based_link_global = false; + } + } +#endif + + // FIXME TD-er: Must make some sanity checks to see if it is a valid message + return valid(); +} + +bool NodeStruct::operator<(const NodeStruct &other) const { + const bool thisExpired = isExpired(); + if (thisExpired != other.isExpired()) { + return !thisExpired; + } + + const bool markedAsPriority = markedAsPriorityPeer(); + if (markedAsPriority != other.markedAsPriorityPeer()) { + return markedAsPriority; + } + + if (ESPEasyNowPeer != other.ESPEasyNowPeer) { + // One is confirmed, so prefer that one. + return ESPEasyNowPeer; + } + + const int8_t thisRssi = getRSSI(); + const int8_t otherRssi = other.getRSSI(); + + int score_this = getLoad(); + int score_other = other.getLoad(); + + if (distance != other.distance) { + if (!isExpired() && !other.isExpired()) { + // Distance is not the same, so take distance into account. + return distance < other.distance; +/* + int distance_penalty = distance - other.distance; + distance_penalty = distance_penalty * distance_penalty * 10; + if (distance > other.distance) { + score_this += distance_penalty; + } else { + score_other += distance_penalty; + } +*/ + } + } + + if (thisRssi >= 0 || otherRssi >= 0) { + // One or both have no RSSI, so cannot use RSSI in computing score + } else { + // RSSI value is negative, so subtract the value + // RSSI range from -38 ... 99 + // Shift RSSI and add a weighing factor to make sure + // A load of 100% with RSSI of -40 is preferred over a load of 20% with an RSSI of -80. + score_this -= (thisRssi + 38) * 2; + score_other -= (otherRssi + 38) * 2; + } + return score_this < score_other; +} + + +const __FlashStringHelper * NodeStruct::getNodeTypeDisplayString() const { + return toNodeTypeDisplayString(nodeType); +} + +String NodeStruct::getNodeName() const { + String res; + size_t length = strnlen(reinterpret_cast(nodeName), sizeof(nodeName)); + + if (reserve_special(res, length)) { + for (size_t i = 0; i < length; ++i) { + res += static_cast(nodeName[i]); + } + } + return res; +} + +IPAddress NodeStruct::IP() const { + return IPAddress(ip[0], ip[1], ip[2], ip[3]); +} + +#if FEATURE_USE_IPV6 +IPAddress NodeStruct::IPv6_link_local(bool stripZone) const +{ + if (Settings.EnableIPv6() && hasIPv6_mac_based_link_local) { + // Base IPv6 on MAC address + IPAddress ipv6; + if (IPv6_link_local_from_MAC(sta_mac, ipv6)) { + if (stripZone) { + return IPAddress(IPv6, &ipv6[0], 0); + } + return ipv6; + } + } + return IN6ADDR_ANY; +} + +IPAddress NodeStruct::IPv6_global() const +{ + if (Settings.EnableIPv6() && hasIPv6_mac_based_link_global) { + // Base IPv6 on MAC address + IPAddress ipv6; + if (IPv6_global_from_MAC(sta_mac, ipv6)) { + return ipv6; + } + } + return IN6ADDR_ANY; +} + +bool NodeStruct::hasIPv6() const { + if (!Settings.EnableIPv6()) return false; + return hasIPv6_mac_based_link_local || + hasIPv6_mac_based_link_global; +} +#endif + + +MAC_address NodeStruct::STA_MAC() const { + return MAC_address(sta_mac); +} + +MAC_address NodeStruct::ESPEasy_Now_MAC() const { + if (ESPEasyNowPeer == 0) return MAC_address(); + if (useAP_ESPEasyNow) { + return MAC_address(ap_mac); + } + return MAC_address(sta_mac); +} + +unsigned long NodeStruct::getAge() const { + return timePassedSince(lastUpdated); +} + +bool NodeStruct::isExpired() const { + return getAge() > NODE_STRUCT_AGE_TIMEOUT; +} + +float NodeStruct::getLoad() const { + return load / 2.55; +} + +String NodeStruct::getSummary() const { + String res; + if (reserve_special(res, 48)) { + res = F("Unit: "); + res += unit; + res += F(" \""); + res += getNodeName(); + res += '"'; + res += F(" load: "); + res += String(getLoad(), 1); + res += F(" RSSI: "); + res += getRSSI(); + res += F(" ch: "); + res += channel; + res += F(" dst: "); + res += distance; + } + return res; +} + +bool NodeStruct::setESPEasyNow_mac(const MAC_address& received_mac) +{ + if (!received_mac.all_zero()) { + if (received_mac == sta_mac) { + ESPEasyNowPeer = 1; + useAP_ESPEasyNow = 0; + return true; + } + + if (received_mac == ap_mac) { + ESPEasyNowPeer = 1; + useAP_ESPEasyNow = 1; + return true; + } + } + return false; +} + +int8_t NodeStruct::getRSSI() const +{ + if (scaled_rssi == 0) { + return 0; // Not set + } + + if (scaled_rssi == 0x3F) { + return 31; // Error state + } + + // scaled_rssi = 1 ... 62 + // output = -38 ... -99 + int8_t rssi = scaled_rssi + 37; + return rssi * -1; +} + +void NodeStruct::setRSSI(int8_t rssi) +{ + if (rssi == 0) { + // Not set + scaled_rssi = 0; + return; + } + + if (rssi > 0) { + // Error state + scaled_rssi = 0x3F; + return; + } + rssi *= -1; + rssi -= 37; + + if (rssi < 1) { + scaled_rssi = 1; + return; + } + + if (rssi >= 0x3F) { + scaled_rssi = 0x3F - 1; + return; + } + scaled_rssi = rssi; +} + +bool NodeStruct::markedAsPriorityPeer() const +{ +#ifdef USES_ESPEASY_NOW + for (int i = 0; i < ESPEASY_NOW_PEER_MAX; ++i) { + if (SecuritySettings.peerMacSet(i)) { + if (match(SecuritySettings.EspEasyNowPeerMAC[i])) { + return true; + } + } + } +#endif + return false; +} + +bool NodeStruct::match(const MAC_address& mac) const +{ + return (mac == sta_mac || mac == ap_mac); +} + +bool NodeStruct::isThisNode() const +{ + // Check to see if we process a node we've sent ourselves. + if (WifiSoftAPmacAddress() == ap_mac) return true; + if (WifiSTAmacAddress() == sta_mac) return true; + + return false; +} + +void NodeStruct::setAP_MAC(const MAC_address& mac) +{ + mac.get(ap_mac); +} + +#endif diff --git a/src/src/DataStructs/NodeStruct.h b/src/src/DataStructs/NodeStruct.h index 26025676b1..f4b1329128 100644 --- a/src/src/DataStructs/NodeStruct.h +++ b/src/src/DataStructs/NodeStruct.h @@ -1,122 +1,123 @@ -#ifndef DATASTRUCTS_NODESTRUCT_H -#define DATASTRUCTS_NODESTRUCT_H - -#include "../../ESPEasy_common.h" - -#if FEATURE_ESPEASY_P2P -#include "../Helpers/ESPEasy_time.h" -#include "../DataStructs/MAC_address.h" - -#include -#include - - -/*********************************************************************************************\ -* NodeStruct -\*********************************************************************************************/ -struct __attribute__((__packed__)) NodeStruct -{ - NodeStruct(); - - bool valid() const; - bool validate(const IPAddress& remoteIP); - - // Compare nodes. - // Return true when this node has better credentials to be used as ESPEasy-NOW neighbor - // - Shorter distance to a network connected gateway node. - // - confirmed ESPEasy-NOW peer - // - better RSSI - // - lower load (TODO TD-er) - bool operator<(const NodeStruct &other) const; - - const __FlashStringHelper * getNodeTypeDisplayString() const; - - String getNodeName() const; - - IPAddress IP() const; - - #if FEATURE_USE_IPV6 - IPAddress IPv6_link_local(bool stripZone = false) const; - IPAddress IPv6_global() const; - - bool hasIPv6() const; - #endif - - MAC_address STA_MAC() const; - - MAC_address ESPEasy_Now_MAC() const; - - unsigned long getAge() const; - - bool isExpired() const; - - float getLoad() const; - - String getSummary() const; - - bool setESPEasyNow_mac(const MAC_address& received_mac); - - int8_t getRSSI() const; - - void setRSSI(int8_t rssi); - - bool markedAsPriorityPeer() const; - - bool match(const MAC_address& mac) const; - - bool isThisNode() const; - - void setAP_MAC(const MAC_address& mac); - - - // Do not change the order of this data, as it is being sent via P2P UDP. - // 6 byte mac (STA or ETH interface) - // 4 byte ip - // 1 byte unit - // 2 byte build - // 25 char name - // 1 byte node type id - - // Added starting build '20107': - // 2 bytes webserver port - // 6 bytes AP MAC - // 1 byte system load - // 1 byte administrative distance - - - uint8_t sta_mac[6] = { 0 }; // STA mode MAC (or MAC from ETH device) - uint8_t ip[4] = { 0 }; - uint8_t unit = 0; - uint16_t build = 0; - uint8_t nodeName[25] = { 0 }; - uint8_t nodeType = 0; - uint16_t webgui_portnumber = 80; - uint8_t ap_mac[6] = { 0 }; // AP mode MAC - uint8_t load = 127; // Default to average load - uint8_t distance = 255; // Administrative distance for routing - uint8_t timeSource = static_cast(timeSource_t::No_time_source); - uint8_t channel = 0; // The WiFi channel used - uint8_t ESPEasyNowPeer : 1; // Signalling if the node is an ESPEasy-NOW peer - uint8_t useAP_ESPEasyNow : 1; // ESPEasy-NOW can either use STA or AP for communications. - uint8_t scaled_rssi : 6; // "shortened" RSSI value - - // When sending system info, this value contains the time since last time sync. - // When kept as node info, this is the last time stamp the node info was updated. - unsigned long lastUpdated = (1 << 30); - uint8_t version = 1; - #if FEATURE_USE_IPV6 - uint8_t hasIPv4 : 1; - // Whether the IPv6 address can be derived from the given sta_mac member - uint8_t hasIPv6_mac_based_link_local : 1; - uint8_t hasIPv6_mac_based_link_global : 1; - - uint8_t unused : 5; - #else - uint8_t unused = 0; - #endif - uint32_t unix_time_sec = 0; - uint32_t unix_time_frac = 0; -}; -typedef std::map NodesMap; -#endif // if FEATURE_ESPEASY_P2P -#endif // DATASTRUCTS_NODESTRUCT_H +#ifndef DATASTRUCTS_NODESTRUCT_H +#define DATASTRUCTS_NODESTRUCT_H + +#include "../../ESPEasy_common.h" + +#if FEATURE_ESPEASY_P2P +#include "../Helpers/ESPEasy_time.h" +#include "../DataStructs/MAC_address.h" + +#include +#include + + +/*********************************************************************************************\ +* NodeStruct +\*********************************************************************************************/ +struct __attribute__((__packed__)) NodeStruct +{ + NodeStruct(); + + bool valid() const; + bool validate(); + bool validate(const IPAddress& remoteIP); + + // Compare nodes. + // Return true when this node has better credentials to be used as ESPEasy-NOW neighbor + // - Shorter distance to a network connected gateway node. + // - confirmed ESPEasy-NOW peer + // - better RSSI + // - lower load (TODO TD-er) + bool operator<(const NodeStruct &other) const; + + const __FlashStringHelper * getNodeTypeDisplayString() const; + + String getNodeName() const; + + IPAddress IP() const; + + #if FEATURE_USE_IPV6 + IPAddress IPv6_link_local(bool stripZone = false) const; + IPAddress IPv6_global() const; + + bool hasIPv6() const; + #endif + + MAC_address STA_MAC() const; + + MAC_address ESPEasy_Now_MAC() const; + + unsigned long getAge() const; + + bool isExpired() const; + + float getLoad() const; + + String getSummary() const; + + bool setESPEasyNow_mac(const MAC_address& received_mac); + + int8_t getRSSI() const; + + void setRSSI(int8_t rssi); + + bool markedAsPriorityPeer() const; + + bool match(const MAC_address& mac) const; + + bool isThisNode() const; + + void setAP_MAC(const MAC_address& mac); + + + // Do not change the order of this data, as it is being sent via P2P UDP. + // 6 byte mac (STA or ETH interface) + // 4 byte ip + // 1 byte unit + // 2 byte build + // 25 char name + // 1 byte node type id + + // Added starting build '20107': + // 2 bytes webserver port + // 6 bytes AP MAC + // 1 byte system load + // 1 byte administrative distance + + + uint8_t sta_mac[6] = { 0 }; // STA mode MAC (or MAC from ETH device) + uint8_t ip[4] = { 0 }; + uint8_t unit = 0; + uint16_t build = 0; + uint8_t nodeName[25] = { 0 }; + uint8_t nodeType = 0; + uint16_t webgui_portnumber = 80; + uint8_t ap_mac[6] = { 0 }; // AP mode MAC + uint8_t load = 127; // Default to average load + uint8_t distance = 255; // Administrative distance for routing + uint8_t timeSource = static_cast(timeSource_t::No_time_source); + uint8_t channel = 0; // The WiFi channel used + uint8_t ESPEasyNowPeer : 1; // Signalling if the node is an ESPEasy-NOW peer + uint8_t useAP_ESPEasyNow : 1; // ESPEasy-NOW can either use STA or AP for communications. + uint8_t scaled_rssi : 6; // "shortened" RSSI value + + // When sending system info, this value contains the time since last time sync. + // When kept as node info, this is the last time stamp the node info was updated. + unsigned long lastUpdated = (1 << 30); + uint8_t version = 1; + #if FEATURE_USE_IPV6 + uint8_t hasIPv4 : 1; + // Whether the IPv6 address can be derived from the given sta_mac member + uint8_t hasIPv6_mac_based_link_local : 1; + uint8_t hasIPv6_mac_based_link_global : 1; + + uint8_t unused : 5; + #else + uint8_t unused = 0; + #endif + uint32_t unix_time_sec = 0; + uint32_t unix_time_frac = 0; +}; +typedef std::map NodesMap; +#endif // if FEATURE_ESPEASY_P2P +#endif // DATASTRUCTS_NODESTRUCT_H diff --git a/src/src/DataStructs/NodesHandler.cpp b/src/src/DataStructs/NodesHandler.cpp index d2e997db6b..f1cc0f9807 100644 --- a/src/src/DataStructs/NodesHandler.cpp +++ b/src/src/DataStructs/NodesHandler.cpp @@ -1,746 +1,782 @@ -#include "../DataStructs/NodesHandler.h" - -#include "../../ESPEasy_common.h" - -#if FEATURE_ESPEASY_P2P -#include "../../ESPEasy-Globals.h" - -#ifdef USES_ESPEASY_NOW -#include "../Globals/ESPEasy_now_peermanager.h" -#include "../Globals/ESPEasy_now_state.h" -#endif - -#include "../Globals/EventQueue.h" - -#include "../DataTypes/NodeTypeID.h" - -#if FEATURE_MQTT -#include "../ESPEasyCore/Controller.h" -#endif - -#include "../ESPEasyCore/ESPEasy_Log.h" -#include "../ESPEasyCore/ESPEasyNetwork.h" -#include "../ESPEasyCore/ESPEasyWifi.h" -#include "../Globals/ESPEasy_time.h" -#include "../Globals/ESPEasyWiFiEvent.h" -#include "../Globals/MQTT.h" -#include "../Globals/NetworkState.h" -#include "../Globals/RTC.h" -#include "../Globals/Settings.h" -#include "../Helpers/ESPEasy_time_calc.h" -#include "../Helpers/Misc.h" -#include "../Helpers/PeriodicalActions.h" -#include "../Helpers/StringConverter.h" -#include "../Helpers/StringGenerator_System.h" - -#define ESPEASY_NOW_ALLOWED_AGE_NO_TRACEROUTE 35000 - -bool NodesHandler::addNode(const NodeStruct& node) -{ - int8_t rssi = 0; - MAC_address match_sta; - MAC_address match_ap; - MAC_address ESPEasy_NOW_MAC; - - bool isNewNode = true; - - // Erase any existing node with matching MAC address - for (auto it = _nodes.begin(); it != _nodes.end(); ) - { - const MAC_address sta = it->second.sta_mac; - const MAC_address ap = it->second.ap_mac; - if ((!sta.all_zero() && node.match(sta)) || (!ap.all_zero() && node.match(ap))) { - rssi = it->second.getRSSI(); - if (!sta.all_zero()) - match_sta = sta; - if (!ap.all_zero()) - match_ap = ap; - ESPEasy_NOW_MAC = it->second.ESPEasy_Now_MAC(); - - isNewNode = false; - { - _nodes_mutex.lock(); - it = _nodes.erase(it); - _nodes_mutex.unlock(); - } - } else { - ++it; - } - } - bool ntp_candidate_updated = false; - { - _nodes_mutex.lock(); - { - #ifdef USE_SECOND_HEAP - // FIXME TD-er: Must check whether this is working well as the NodesMap is a std::map - HeapSelectIram ephemeral; - #endif - _nodes[node.unit] = node; - } - // Make sure to set first as NTP candidate, as it was set to time since - // last time sync by the sender node before sending. - ntp_candidate_updated = _ntp_candidate.set(node); - // Now set lastUpdated so we can keep track of its age. - _nodes[node.unit].lastUpdated = millis(); - if (node.getRSSI() >= 0 && rssi < 0) { - _nodes[node.unit].setRSSI(rssi); - } - const MAC_address node_ap(node.ap_mac); - if (node_ap.all_zero()) { - _nodes[node.unit].setAP_MAC(node_ap); - } - if (node.ESPEasy_Now_MAC().all_zero()) { - _nodes[node.unit].setESPEasyNow_mac(ESPEasy_NOW_MAC); - } - _nodes_mutex.unlock(); - } - - // Check whether the current time source is considered "worse" than received from p2p node. - if (!node_time.systemTimePresent() || - (node_time.getTimeSource() > timeSource_t::ESPEASY_p2p_UDP) || - (timePassedSince(node_time.lastSyncTime_ms) > EXT_TIME_SOURCE_MIN_UPDATE_INTERVAL_MSEC)) { - double unixTime{}; - uint8_t unit = 0; - int32_t wander = -1; - const timeSource_t timeSource = _ntp_candidate.getUnixTime(unixTime, wander, unit); - - if (timeSource != timeSource_t::No_time_source) { - bool shouldUpdate = false; - - if (!node_time.systemTimePresent()) { - if (timeSource < timeSource_t::ESP_now_peer || getUptimeMinutes() > 0) { - // After 1 minute we should have had at least 2 loops of p2p nodes announcing their time source. - // Though no need to wait longer if we already got a good source - shouldUpdate = true; - } - } else if (ntp_candidate_updated) { - // Got a better time source - shouldUpdate = true; - } - - if (shouldUpdate) { - node_time.setExternalTimeSource_withTimeWander(unixTime, timeSource, wander, unit); - } - } - } - - if (isNewNode) { - if (Settings.UseRules && (node.unit != 0)) - { - // Generate event announcing new p2p node - // TODO TD-er: Maybe also add other info like ESP type, IP-address, etc? - eventQueue.addMove(strformat( - F("p2pNode#Connected=%d,'%s','%s'"), - node.unit, - node.getNodeName().c_str(), - formatSystemBuildNr(node.build).c_str() - )); - } - } - - return isNewNode; -} - -#ifdef USES_ESPEASY_NOW -bool NodesHandler::addNode(const NodeStruct& node, const ESPEasy_now_traceroute_struct& traceRoute) -{ - const bool isNewNode = addNode(node); - { - _nodeStats_mutex.lock(); - _nodeStats[node.unit].setDiscoveryRoute(node.unit, traceRoute); - _nodeStats_mutex.unlock(); - } - - ESPEasy_now_peermanager.addPeer(node.ESPEasy_Now_MAC(), node.channel); - - if (!node.isThisNode()) { - if (traceRoute.getDistance() != 255) { - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log; - if (reserve_special(log, 80)) { - log = F(ESPEASY_NOW_NAME); - log += F(": Node: "); - log += String(node.unit); - log += F(" DiscoveryRoute received: "); - log += traceRoute.toString(); - addLog(LOG_LEVEL_INFO, log); - } - } - } else {} - } - return isNewNode; -} -#endif - -bool NodesHandler::hasNode(uint8_t unit_nr) const -{ - return _nodes.find(unit_nr) != _nodes.end(); -} - -bool NodesHandler::hasNode(const uint8_t *mac) const -{ - return getNodeByMac(mac) != nullptr; -} - -NodeStruct * NodesHandler::getNode(uint8_t unit_nr) -{ - auto it = _nodes.find(unit_nr); - - if (it == _nodes.end()) { - return nullptr; - } - return &(it->second); -} - -const NodeStruct * NodesHandler::getNode(uint8_t unit_nr) const -{ - auto it = _nodes.find(unit_nr); - - if (it == _nodes.end()) { - return nullptr; - } - return &(it->second); -} - -NodeStruct * NodesHandler::getNodeByMac(const MAC_address& mac) -{ - if (mac.all_zero()) { - return nullptr; - } - delay(0); - - for (auto it = _nodes.begin(); it != _nodes.end(); ++it) - { - if (mac == it->second.sta_mac) { - return &(it->second); - } - - if (mac == it->second.ap_mac) { - return &(it->second); - } - } - return nullptr; -} - -const NodeStruct * NodesHandler::getNodeByMac(const MAC_address& mac) const -{ - bool match_STA; - - return getNodeByMac(mac, match_STA); -} - -const NodeStruct * NodesHandler::getNodeByMac(const MAC_address& mac, bool& match_STA) const -{ - if (mac.all_zero()) { - return nullptr; - } - delay(0); - - for (auto it = _nodes.begin(); it != _nodes.end(); ++it) - { - if (mac == it->second.sta_mac) { - match_STA = true; - return &(it->second); - } - - if (mac == it->second.ap_mac) { - match_STA = false; - return &(it->second); - } - } - return nullptr; -} - -const NodeStruct * NodesHandler::getPreferredNode() const { - MAC_address dummy; - - return getPreferredNode_notMatching(dummy); -} - -const NodeStruct* NodesHandler::getPreferredNode_notMatching(uint8_t unit_nr) const { - MAC_address not_matching; - if (unit_nr != 0 && unit_nr != 255) { - const NodeStruct* node = getNode(unit_nr); - if (node != nullptr) { - not_matching = node->ESPEasy_Now_MAC(); - } - } - return getPreferredNode_notMatching(not_matching); -} - -const NodeStruct * NodesHandler::getPreferredNode_notMatching(const MAC_address& not_matching) const { - MAC_address this_mac = NetworkMacAddress(); - const NodeStruct *thisNode = getNodeByMac(this_mac); - const NodeStruct *reject = getNodeByMac(not_matching); - - const NodeStruct *res = nullptr; - - for (auto it = _nodes.begin(); it != _nodes.end(); ++it) - { - if ((&(it->second) != reject) && (&(it->second) != thisNode)) { - bool mustSet = false; - if (res == nullptr) { - mustSet = true; - } else { - #ifdef USES_ESPEASY_NOW - - uint8_t distance_new, distance_res = 255; - - const int successRate_new = getRouteSuccessRate(it->second.unit, distance_new); - const int successRate_res = getRouteSuccessRate(res->unit, distance_res); - - if (successRate_new == 0 || successRate_res == 0) { - // One of the nodes does not (yet) have a route. - if (successRate_new == 0 && successRate_res == 0) { - distance_new = it->second.distance; - distance_res = res->distance; - } else if (successRate_res == 0) { - // The new one has a route, so must set the new one. - distance_res = res->distance; - if (distance_new < 255) { - mustSet = true; - } - } - } - - if (distance_new == distance_res) { - if (successRate_new > successRate_res && distance_new < 255) { - mustSet = true; - } - } else if (distance_new < distance_res) { - if (it->second.getAge() < ESPEASY_NOW_ALLOWED_AGE_NO_TRACEROUTE) { - // Only allow this new one if it was seen recently - // as it does not (yet) have a traceroute. - mustSet = true; - } - } - #else - if (it->second < *res) { - mustSet = true; - } - #endif - } - if (mustSet) { - #ifdef USES_ESPEASY_NOW - if (it->second.ESPEasyNowPeer && it->second.distance < 255) { - res = &(it->second); - } - #else - res = &(it->second); - #endif - } - } - } - -/* - #ifdef USES_ESPEASY_NOW - if (res != nullptr) - { - uint8_t distance_res = 255; - const int successRate_res = getRouteSuccessRate(res->unit, distance_res); - if (distance_res == 255) { - return nullptr; - } - } - #endif -*/ - - return res; -} - -#ifdef USES_ESPEASY_NOW -const ESPEasy_now_traceroute_struct* NodesHandler::getTraceRoute(uint8_t unit) const -{ - auto trace_it = _nodeStats.find(unit); - if (trace_it == _nodeStats.end()) { - return nullptr; - } - return trace_it->second.bestRoute(); -} - -const ESPEasy_now_traceroute_struct* NodesHandler::getDiscoveryRoute(uint8_t unit) const -{ - auto trace_it = _nodeStats.find(unit); - if (trace_it == _nodeStats.end()) { - return nullptr; - } - return &(trace_it->second.discoveryRoute()); -} - -void NodesHandler::setTraceRoute(const MAC_address& mac, const ESPEasy_now_traceroute_struct& traceRoute) -{ - if (traceRoute.computeSuccessRate() == 0) { - // No need to store traceroute with low success rate. - return; - } - NodeStruct* node = getNodeByMac(mac); - if (node != nullptr) { - auto trace_it = _nodeStats.find(node->unit); - if (trace_it != _nodeStats.end()) { - _lastTimeValidDistance = millis(); - trace_it->second.addRoute(node->unit, traceRoute); - } - } -} - -#endif - - -void NodesHandler::updateThisNode() { - NodeStruct thisNode; - - // Set local data - { - MAC_address mac = NetworkMacAddress(); - mac.get(thisNode.sta_mac); - } - WiFi.softAPmacAddress(thisNode.ap_mac); - { - const bool addIP = NetworkConnected(); - #ifdef USES_ESPEASY_NOW - if (use_EspEasy_now) { - thisNode.useAP_ESPEasyNow = 1; - } - #endif - if (addIP) { - const IPAddress localIP = NetworkLocalIP(); - - for (uint8_t i = 0; i < 4; ++i) { - thisNode.ip[i] = localIP[i]; - } - } - } - #ifdef USES_ESPEASY_NOW - thisNode.channel = getESPEasyNOW_channel(); - #else - thisNode.channel = WiFiEventData.usedChannel; - #endif - if (thisNode.channel == 0) { - thisNode.channel = WiFi.channel(); - } - - thisNode.unit = Settings.Unit; - thisNode.build = Settings.Build; - memcpy(thisNode.nodeName, Settings.getName().c_str(), 25); - thisNode.nodeType = NODE_TYPE_ID; - - thisNode.webgui_portnumber = Settings.WebserverPort; - const int load_int = getCPUload() * 2.55; - - if (load_int > 255) { - thisNode.load = 255; - } else { - thisNode.load = load_int; - } - thisNode.timeSource = static_cast(node_time.getTimeSource()); - - switch (node_time.getTimeSource()) { - case timeSource_t::No_time_source: - thisNode.lastUpdated = (1 << 30); - break; - default: - { - thisNode.lastUpdated = timePassedSince(node_time.lastSyncTime_ms); - break; - } - } - if (node_time.systemTimePresent()) { - // NodeStruct is a packed struct, so we cannot directly use its members as a reference. - uint32_t unix_time_frac = 0; - thisNode.unix_time_sec = node_time.getUnixTime(unix_time_frac); - thisNode.unix_time_frac = unix_time_frac; - } - #ifdef USES_ESPEASY_NOW - if (Settings.UseESPEasyNow()) { - thisNode.ESPEasyNowPeer = 1; - } - #endif - - const uint8_t lastDistance = _distance; - #ifdef USES_ESPEASY_NOW - ESPEasy_now_traceroute_struct thisTraceRoute; - #endif - if (isEndpoint()) { - _distance = 0; - _lastTimeValidDistance = millis(); - if (lastDistance != _distance) { - _recentlyBecameDistanceZero = true; - } - #ifdef USES_ESPEASY_NOW - thisNode.distance = _distance; - thisNode.setRSSI(WiFi.RSSI()); - thisTraceRoute.addUnit(thisNode.unit); - #endif - } else { - _distance = 255; - #ifdef USES_ESPEASY_NOW - const NodeStruct *preferred = getPreferredNode_notMatching(thisNode.sta_mac); - - if (preferred != nullptr) { - if (!preferred->isExpired()) { - // Only take the distance of another node if it is running a build which does not send out traceroute - // If it is a build sending traceroute, only consider having a distance if you know how to reach the gateway node - // This does impose an issue when a gateway node is running an older version, as the next hops never will have a traceroute too. - // Therefore the reported build for those units will be faked to be an older version. - if (preferred->build < 20113) { - if (preferred->distance != 255) { - _distance = preferred->distance + 1; - thisNode.build = 20112; - } - } else { - const ESPEasy_now_traceroute_struct* tracert_ptr = getTraceRoute(preferred->unit); - if (tracert_ptr != nullptr && tracert_ptr->getDistance() < 255) { - // Make a copy of the traceroute - thisTraceRoute = *tracert_ptr; - thisTraceRoute.addUnit(thisNode.unit); - if (preferred->distance != 255) { - // Traceroute is only updated when a node is connected. - // Thus the traceroute may be outdated, while the node info will already indicate if a node has lost its route to the gateway node. - // So we only must set the distance of this node if the preferred node has a distance. - _distance = thisTraceRoute.getDistance(); // This node is already included in the traceroute. - } - } - } - } - } - #endif - } - thisNode.distance = _distance; - - #if FEATURE_USE_IPV6 - thisNode.hasIPv4 = thisNode.IP() != INADDR_NONE; - thisNode.hasIPv6_mac_based_link_local = is_IPv6_link_local_from_MAC(thisNode.sta_mac); - thisNode.hasIPv6_mac_based_link_global = is_IPv6_global_from_MAC(thisNode.sta_mac); - #endif - - #ifdef USES_ESPEASY_NOW - addNode(thisNode, thisTraceRoute); - if (thisNode.distance == 0) { - // Since we're the end node, claim highest success rate - updateSuccessRate(thisNode.unit, 255); - } - #else - addNode(thisNode); - #endif -} - -const NodeStruct * NodesHandler::getThisNode() { -// node_time.now(); - updateThisNode(); - MAC_address this_mac = NetworkMacAddress(); - return getNodeByMac(this_mac.mac); -} - -uint8_t NodesHandler::getDistance() const { - // Perform extra check since _distance is only updated once every 30 seconds. - // And we don't want to tell other nodes we have distance 0 when we haven't. - if (isEndpoint()) return 0; - if (_distance == 0) { - // Outdated info, so return "we don't know" - return 255; - } - return _distance; -} - - -NodesMap::const_iterator NodesHandler::begin() const { - return _nodes.begin(); -} - -NodesMap::const_iterator NodesHandler::end() const { - return _nodes.end(); -} - -NodesMap::const_iterator NodesHandler::find(uint8_t unit_nr) const -{ - return _nodes.find(unit_nr); -} - -bool NodesHandler::refreshNodeList(unsigned long max_age_allowed, unsigned long& max_age) -{ - max_age = 0; - bool nodeRemoved = false; - - for (auto it = _nodes.begin(); it != _nodes.end();) { - unsigned long age = it->second.getAge(); - if (age > max_age_allowed) { - bool mustErase = true; - #ifdef USES_ESPEASY_NOW - auto route_it = _nodeStats.find(it->second.unit); - if (route_it != _nodeStats.end()) { - if (route_it->second.getAge() > max_age_allowed) { - _nodeStats_mutex.lock(); - _nodeStats.erase(route_it); - _nodeStats_mutex.unlock(); - } else { - mustErase = false; - } - } - #endif - if (mustErase) { - if (Settings.UseRules && it->second.unit != 0) - { - // Add event about removing node from nodeslist. - eventQueue.addMove(strformat(F("p2pNode#Disconnected=%d"), it->second.unit)); - } - { - _nodes_mutex.lock(); - it = _nodes.erase(it); - _nodes_mutex.unlock(); - } - nodeRemoved = true; - } - } else { - ++it; - - if (age > max_age) { - max_age = age; - } - } - } - return nodeRemoved; -} - -// FIXME TD-er: should be a check per controller to see if it will accept messages -bool NodesHandler::isEndpoint() const -{ - // FIXME TD-er: Must check controller to see if it needs wifi (e.g. LoRa or cache controller do not need it) - #if FEATURE_MQTT - controllerIndex_t enabledMqttController = firstEnabledMQTT_ControllerIndex(); - if (validControllerIndex(enabledMqttController)) { - // FIXME TD-er: Must call updateMQTTclient_connected() and see what effect - // the MQTTclient_connected state has when using ESPEasy-NOW. - return MQTTclient_connected; - } - #endif - - if (!NetworkConnected()) return false; - - return false; -} - -#ifdef USES_ESPEASY_NOW -uint8_t NodesHandler::getESPEasyNOW_channel() const -{ - if (active_network_medium == NetworkMedium_t::WIFI && NetworkConnected()) { - return WiFi.channel(); - } - if (Settings.ForceESPEasyNOWchannel > 0) { - return Settings.ForceESPEasyNOWchannel; - } - if (isEndpoint()) { - if (active_network_medium == NetworkMedium_t::WIFI) { - return WiFi.channel(); - } - } - const NodeStruct *preferred = getPreferredNode(); - if (preferred != nullptr) { - if (preferred->distance < 255) { - return preferred->channel; - } - } - return WiFiEventData.usedChannel; -} -#endif - -bool NodesHandler::recentlyBecameDistanceZero() { - if (!_recentlyBecameDistanceZero) { - return false; - } - _recentlyBecameDistanceZero = false; - return true; -} - -void NodesHandler::setRSSI(const MAC_address& mac, int rssi) -{ - setRSSI(getNodeByMac(mac), rssi); -} - -void NodesHandler::setRSSI(uint8_t unit, int rssi) -{ - setRSSI(getNode(unit), rssi); -} - -void NodesHandler::setRSSI(NodeStruct * node, int rssi) -{ - if (node != nullptr) { - node->setRSSI(rssi); - } -} - -bool NodesHandler::lastTimeValidDistanceExpired() const -{ -// if (_lastTimeValidDistance == 0) return false; - return timePassedSince(_lastTimeValidDistance) > 120000; // 2 minutes -} - -#ifdef USES_ESPEASY_NOW -void NodesHandler::updateSuccessRate(uint8_t unit, bool success) -{ - auto it = _nodeStats.find(unit); - if (it != _nodeStats.end()) { - it->second.updateSuccessRate(unit, success); - } -} - -void NodesHandler::updateSuccessRate(const MAC_address& mac, bool success) -{ - const NodeStruct * node = getNodeByMac(mac); - if (node == nullptr) { - return; - } - updateSuccessRate(node->unit, success); -} - -int NodesHandler::getRouteSuccessRate(uint8_t unit, uint8_t& distance) const -{ - distance = 255; - auto it = _nodeStats.find(unit); - if (it != _nodeStats.end()) { - const ESPEasy_now_traceroute_struct* route = it->second.bestRoute(); - if (route != nullptr) { - distance = route->getDistance(); - return route->computeSuccessRate(); - } - } - return 0; -} - -uint8_t NodesHandler::getSuccessRate(uint8_t unit) const -{ - auto it = _nodeStats.find(unit); - if (it != _nodeStats.end()) { - return it->second.getNodeSuccessRate(); - } - return 127; -} - -ESPEasy_Now_MQTT_QueueCheckState::Enum NodesHandler::getMQTTQueueState(uint8_t unit) const -{ - auto it = _nodeStats.find(unit); - if (it != _nodeStats.end()) { - return it->second.getMQTTQueueState(); - } - return ESPEasy_Now_MQTT_QueueCheckState::Enum::Unset; - -} - -void NodesHandler::setMQTTQueueState(uint8_t unit, ESPEasy_Now_MQTT_QueueCheckState::Enum state) -{ - auto it = _nodeStats.find(unit); - if (it != _nodeStats.end()) { - it->second.setMQTTQueueState(state); - } -} - -void NodesHandler::setMQTTQueueState(const MAC_address& mac, ESPEasy_Now_MQTT_QueueCheckState::Enum state) -{ - const NodeStruct * node = getNodeByMac(mac); - if (node != nullptr) { - setMQTTQueueState(node->unit, state); - } -} - -#endif - +#include "../DataStructs/NodesHandler.h" + +#include "../../ESPEasy_common.h" + +#if FEATURE_ESPEASY_P2P +#include "../../ESPEasy-Globals.h" + +#ifdef USES_ESPEASY_NOW +#include "../Globals/ESPEasy_now_peermanager.h" +#include "../Globals/ESPEasy_now_state.h" +#include "../Globals/EventQueue.h" +#endif + +#include "../Globals/EventQueue.h" + +#include "../DataTypes/NodeTypeID.h" + +#if FEATURE_MQTT +#include "../ESPEasyCore/Controller.h" +#endif + +#include "../ESPEasyCore/ESPEasy_Log.h" +#include "../ESPEasyCore/ESPEasyNetwork.h" +#include "../ESPEasyCore/ESPEasyWifi.h" +#include "../Globals/ESPEasy_time.h" +#include "../Globals/ESPEasyWiFiEvent.h" +#include "../Globals/MQTT.h" +#include "../Globals/NetworkState.h" +#include "../Globals/RTC.h" +#include "../Globals/Settings.h" +#include "../Helpers/ESPEasy_time_calc.h" +#include "../Helpers/Misc.h" +#include "../Helpers/PeriodicalActions.h" +#include "../Helpers/StringConverter.h" +#include "../Helpers/StringGenerator_System.h" + +#define ESPEASY_NOW_ALLOWED_AGE_NO_TRACEROUTE 35000 + +bool NodesHandler::addNode(const NodeStruct& node) +{ + int8_t rssi = 0; + MAC_address match_sta; + MAC_address match_ap; + MAC_address ESPEasy_NOW_MAC; + + bool isNewNode = true; + + // Erase any existing node with matching MAC address + for (auto it = _nodes.begin(); it != _nodes.end(); ) + { + const MAC_address sta = it->second.sta_mac; + const MAC_address ap = it->second.ap_mac; + if ((!sta.all_zero() && node.match(sta)) || (!ap.all_zero() && node.match(ap))) { + rssi = it->second.getRSSI(); + if (!sta.all_zero()) + match_sta = sta; + if (!ap.all_zero()) + match_ap = ap; + ESPEasy_NOW_MAC = it->second.ESPEasy_Now_MAC(); + + isNewNode = false; + { + _nodes_mutex.lock(); + it = _nodes.erase(it); + _nodes_mutex.unlock(); + } + } else { + ++it; + } + } + bool ntp_candidate_updated = false; + { + _nodes_mutex.lock(); + { + #ifdef USE_SECOND_HEAP + // FIXME TD-er: Must check whether this is working well as the NodesMap is a std::map + HeapSelectIram ephemeral; + #endif + _nodes[node.unit] = node; + } + // Make sure to set first as NTP candidate, as it was set to time since + // last time sync by the sender node before sending. + ntp_candidate_updated = _ntp_candidate.set(node); + // Now set lastUpdated so we can keep track of its age. + _nodes[node.unit].lastUpdated = millis(); + if (node.getRSSI() >= 0 && rssi < 0) { + _nodes[node.unit].setRSSI(rssi); + } + const MAC_address node_ap(node.ap_mac); + if (node_ap.all_zero()) { + _nodes[node.unit].setAP_MAC(node_ap); + } + if (node.ESPEasy_Now_MAC().all_zero()) { + _nodes[node.unit].setESPEasyNow_mac(ESPEasy_NOW_MAC); + } + _nodes_mutex.unlock(); + } + + // Check whether the current time source is considered "worse" than received from p2p node. + if (!node_time.systemTimePresent() || + (node_time.getTimeSource() > timeSource_t::ESPEASY_p2p_UDP) || + (timePassedSince(node_time.lastSyncTime_ms) > EXT_TIME_SOURCE_MIN_UPDATE_INTERVAL_MSEC)) { + double unixTime{}; + uint8_t unit = 0; + int32_t wander = -1; + const timeSource_t timeSource = _ntp_candidate.getUnixTime(unixTime, wander, unit); + + if (timeSource != timeSource_t::No_time_source) { + bool shouldUpdate = false; + + if (!node_time.systemTimePresent()) { + if (timeSource < timeSource_t::ESP_now_peer || getUptimeMinutes() > 0) { + // After 1 minute we should have had at least 2 loops of p2p nodes announcing their time source. + // Though no need to wait longer if we already got a good source + shouldUpdate = true; + } + } else if (ntp_candidate_updated) { + // Got a better time source + shouldUpdate = true; + } + + if (shouldUpdate) { + node_time.setExternalTimeSource_withTimeWander(unixTime, timeSource, wander, unit); + } + } + } + + if (isNewNode) { + if (Settings.UseRules && (node.unit != 0)) + { + // Generate event announcing new p2p node + // TODO TD-er: Maybe also add other info like ESP type, IP-address, etc? + eventQueue.addMove(strformat( + F("p2pNode#Connected=%d,'%s','%s'"), + node.unit, + node.getNodeName().c_str(), + formatSystemBuildNr(node.build).c_str() + )); + } + } + + return isNewNode; +} + +#ifdef USES_ESPEASY_NOW +bool NodesHandler::addNode(const NodeStruct& node, const ESPEasy_now_traceroute_struct& traceRoute) +{ + const bool isNewNode = addNode(node); + { + _nodeStats_mutex.lock(); + _nodeStats[node.unit].setDiscoveryRoute(node.unit, traceRoute); + _nodeStats_mutex.unlock(); + } + + ESPEasy_now_peermanager.addPeer(node.ESPEasy_Now_MAC(), node.channel); + + if (!node.isThisNode()) { + if (traceRoute.getDistance() != 255) { + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + String log; + if (reserve_special(log, 80)) { + log = F(ESPEASY_NOW_NAME); + log += F(": Node: "); + log += String(node.unit); + log += F(" DiscoveryRoute received: "); + log += traceRoute.toString(); + addLog(LOG_LEVEL_INFO, log); + } + } + } else {} + } + return isNewNode; +} +#endif + +bool NodesHandler::hasNode(uint8_t unit_nr) const +{ + return _nodes.find(unit_nr) != _nodes.end(); +} + +bool NodesHandler::hasNode(const uint8_t *mac) const +{ + return getNodeByMac(mac) != nullptr; +} + +NodeStruct * NodesHandler::getNode(uint8_t unit_nr) +{ + auto it = _nodes.find(unit_nr); + + if (it == _nodes.end()) { + return nullptr; + } + return &(it->second); +} + +const NodeStruct * NodesHandler::getNode(uint8_t unit_nr) const +{ + auto it = _nodes.find(unit_nr); + + if (it == _nodes.end()) { + return nullptr; + } + return &(it->second); +} + +NodeStruct * NodesHandler::getNodeByMac(const MAC_address& mac) +{ + if (mac.all_zero()) { + return nullptr; + } + delay(0); + + for (auto it = _nodes.begin(); it != _nodes.end(); ++it) + { + if (mac == it->second.sta_mac) { + return &(it->second); + } + + if (mac == it->second.ap_mac) { + return &(it->second); + } + } + return nullptr; +} + +const NodeStruct * NodesHandler::getNodeByMac(const MAC_address& mac) const +{ + bool match_STA; + + return getNodeByMac(mac, match_STA); +} + +const NodeStruct * NodesHandler::getNodeByMac(const MAC_address& mac, bool& match_STA) const +{ + if (mac.all_zero()) { + return nullptr; + } + delay(0); + + for (auto it = _nodes.begin(); it != _nodes.end(); ++it) + { + if (mac == it->second.sta_mac) { + match_STA = true; + return &(it->second); + } + + if (mac == it->second.ap_mac) { + match_STA = false; + return &(it->second); + } + } + return nullptr; +} + +const NodeStruct * NodesHandler::getPreferredNode() const { + MAC_address dummy; + + return getPreferredNode_notMatching(dummy); +} + +const NodeStruct* NodesHandler::getPreferredNode_notMatching(uint8_t unit_nr, bool checkMQTT_QueueState) const { + MAC_address not_matching; + if (unit_nr != 0 && unit_nr != 255) { + const NodeStruct* node = getNode(unit_nr); + if (node != nullptr) { + not_matching = node->ESPEasy_Now_MAC(); + } + } + return getPreferredNode_notMatching(not_matching, checkMQTT_QueueState); +} + +const NodeStruct * NodesHandler::getPreferredNode_notMatching(const MAC_address& not_matching, bool checkMQTT_QueueState) const { + const NodeStruct *thisNodeSTA = getNodeByMac(NetworkMacAddress()); + const NodeStruct *thisNodeAP = getNodeByMac(WifiSoftAPmacAddress()); + const NodeStruct *reject = getNodeByMac(not_matching); + + const NodeStruct *res = nullptr; + + for (auto it = _nodes.begin(); it != _nodes.end(); ++it) + { + if ((&(it->second) != reject) && + (&(it->second) != thisNodeSTA) && + (&(it->second) != thisNodeAP)) { + bool mustSet = false; + if (res == nullptr) { + mustSet = true; + } else { + #ifdef USES_ESPEASY_NOW + + uint8_t distance_new, distance_res = 255; + + const int successRate_new = getRouteSuccessRate(it->second.unit, distance_new); + const int successRate_res = getRouteSuccessRate(res->unit, distance_res); + + if (successRate_new == 0 || successRate_res == 0) { + // One of the nodes does not (yet) have a route. + if (successRate_new == 0 && successRate_res == 0) { + distance_new = it->second.distance; + distance_res = res->distance; + } else if (successRate_res == 0) { + // The new one has a route, so must set the new one. + distance_res = res->distance; + if (distance_new < 255) { + mustSet = true; + } + } + } + + if (distance_new == distance_res) { + if (successRate_new > successRate_res && distance_new < 255) { + mustSet = true; + } + } else if (distance_new < distance_res) { + if (it->second.getAge() < ESPEASY_NOW_ALLOWED_AGE_NO_TRACEROUTE) { + // Only allow this new one if it was seen recently + // as it does not (yet) have a traceroute. + mustSet = true; + } + } + #else + if (it->second < *res) { + mustSet = true; + } + #endif + } + #ifdef USES_ESPEASY_NOW + if (checkMQTT_QueueState) + { + if (res != nullptr) { + const bool mqttState_res_empty = getMQTTQueueState(res->unit) == ESPEasy_Now_MQTT_QueueCheckState::Enum::Empty; + const bool mqttState_new_empty = getMQTTQueueState(it->second.unit) == ESPEasy_Now_MQTT_QueueCheckState::Enum::Empty; + if (!mqttState_res_empty && mqttState_new_empty) { + mustSet = true; + } else if (mqttState_res_empty && !mqttState_new_empty) { + mustSet = false; + } + } + } + #endif + + if (mustSet) { + #ifdef USES_ESPEASY_NOW + if (it->second.ESPEasyNowPeer && it->second.distance < 255) { + res = &(it->second); + } + #else + res = &(it->second); + #endif + } + } + } + +/* + #ifdef USES_ESPEASY_NOW + if (res != nullptr) + { + uint8_t distance_res = 255; + const int successRate_res = getRouteSuccessRate(res->unit, distance_res); + if (distance_res == 255) { + return nullptr; + } + } + #endif +*/ + + return res; +} + +#ifdef USES_ESPEASY_NOW +const ESPEasy_now_traceroute_struct* NodesHandler::getTraceRoute(uint8_t unit) const +{ + auto trace_it = _nodeStats.find(unit); + if (trace_it == _nodeStats.end()) { + return nullptr; + } + return trace_it->second.bestRoute(); +} + +const ESPEasy_now_traceroute_struct* NodesHandler::getDiscoveryRoute(uint8_t unit) const +{ + auto trace_it = _nodeStats.find(unit); + if (trace_it == _nodeStats.end()) { + return nullptr; + } + return &(trace_it->second.discoveryRoute()); +} + +void NodesHandler::setTraceRoute(const MAC_address& mac, const ESPEasy_now_traceroute_struct& traceRoute) +{ + if (traceRoute.computeSuccessRate() == 0) { + // No need to store traceroute with low success rate. + return; + } + NodeStruct* node = getNodeByMac(mac); + if (node != nullptr) { + auto trace_it = _nodeStats.find(node->unit); + if (trace_it != _nodeStats.end()) { + _lastTimeValidDistance = millis(); + trace_it->second.addRoute(node->unit, traceRoute); + } + } +} + +#endif + + +void NodesHandler::updateThisNode() { + NodeStruct thisNode; + + // Set local data + { + MAC_address mac = NetworkMacAddress(); + mac.get(thisNode.sta_mac); + } + WiFi.softAPmacAddress(thisNode.ap_mac); + { + const bool addIP = NetworkConnected(); + #ifdef USES_ESPEASY_NOW + if (use_EspEasy_now) { + thisNode.useAP_ESPEasyNow = 1; + } + #endif + if (addIP) { + const IPAddress localIP = NetworkLocalIP(); + + for (uint8_t i = 0; i < 4; ++i) { + thisNode.ip[i] = localIP[i]; + } + } + } + #ifdef USES_ESPEASY_NOW + thisNode.channel = getESPEasyNOW_channel(); + #else + thisNode.channel = WiFiEventData.usedChannel; + #endif + if (thisNode.channel == 0) { + thisNode.channel = WiFi.channel(); + } + + thisNode.unit = Settings.Unit; + thisNode.build = Settings.Build; + memcpy(thisNode.nodeName, Settings.getName().c_str(), 25); + thisNode.nodeType = NODE_TYPE_ID; + + thisNode.webgui_portnumber = Settings.WebserverPort; + const int load_int = getCPUload() * 2.55; + + if (load_int > 255) { + thisNode.load = 255; + } else { + thisNode.load = load_int; + } + thisNode.timeSource = static_cast(node_time.getTimeSource()); + + switch (node_time.getTimeSource()) { + case timeSource_t::No_time_source: + thisNode.lastUpdated = (1 << 30); + break; + default: + { + thisNode.lastUpdated = timePassedSince(node_time.lastSyncTime_ms); + break; + } + } + if (node_time.systemTimePresent()) { + // NodeStruct is a packed struct, so we cannot directly use its members as a reference. + uint32_t unix_time_frac = 0; + thisNode.unix_time_sec = node_time.getUnixTime(unix_time_frac); + thisNode.unix_time_frac = unix_time_frac; + } + #ifdef USES_ESPEASY_NOW + if (Settings.UseESPEasyNow()) { + thisNode.ESPEasyNowPeer = 1; + } + #endif + + const uint8_t lastDistance = _distance; + #ifdef USES_ESPEASY_NOW + ESPEasy_now_traceroute_struct thisTraceRoute; + #endif + if (isEndpoint()) { + _distance = 0; + _lastTimeValidDistance = millis(); + if (lastDistance != _distance) { + _recentlyBecameDistanceZero = true; + } + #ifdef USES_ESPEASY_NOW + thisNode.distance = _distance; + thisNode.setRSSI(WiFi.RSSI()); + thisTraceRoute.addUnit(thisNode.unit); + #endif + } else { + _distance = 255; + #ifdef USES_ESPEASY_NOW + const NodeStruct *preferred = getPreferredNode_notMatching(thisNode.sta_mac); + + if (preferred != nullptr) { + if (!preferred->isExpired()) { + // Only take the distance of another node if it is running a build which does not send out traceroute + // If it is a build sending traceroute, only consider having a distance if you know how to reach the gateway node + // This does impose an issue when a gateway node is running an older version, as the next hops never will have a traceroute too. + // Therefore the reported build for those units will be faked to be an older version. + if (preferred->build < 20113) { + if (preferred->distance != 255) { + _distance = preferred->distance + 1; + thisNode.build = 20112; + } + } else { + const ESPEasy_now_traceroute_struct* tracert_ptr = getTraceRoute(preferred->unit); + if (tracert_ptr != nullptr && tracert_ptr->getDistance() < 255) { + // Make a copy of the traceroute + thisTraceRoute = *tracert_ptr; + thisTraceRoute.addUnit(thisNode.unit); + if (preferred->distance != 255) { + // Traceroute is only updated when a node is connected. + // Thus the traceroute may be outdated, while the node info will already indicate if a node has lost its route to the gateway node. + // So we only must set the distance of this node if the preferred node has a distance. + _distance = thisTraceRoute.getDistance(); // This node is already included in the traceroute. + } + } + } + } + } + #endif + } + thisNode.distance = _distance; + + #if FEATURE_USE_IPV6 + thisNode.hasIPv4 = thisNode.IP() != INADDR_NONE; + thisNode.hasIPv6_mac_based_link_local = is_IPv6_link_local_from_MAC(thisNode.sta_mac); + thisNode.hasIPv6_mac_based_link_global = is_IPv6_global_from_MAC(thisNode.sta_mac); + #endif + + #ifdef USES_ESPEASY_NOW + addNode(thisNode, thisTraceRoute); + if (thisNode.distance == 0) { + // Since we're the end node, claim highest success rate + updateSuccessRate(thisNode.unit, 255); + } + if (thisNode.distance != lastDistance) { + if (Settings.UseRules) { + eventQueue.addMove(std::move(concat(F("nodep2p#distance="), thisNode.distance))); + } + } + #else + addNode(thisNode); + #endif +} + +const NodeStruct * NodesHandler::getThisNode() { +// node_time.now(); + updateThisNode(); + MAC_address this_mac = NetworkMacAddress(); + return getNodeByMac(this_mac.mac); +} + +uint8_t NodesHandler::getDistance() const { + // Perform extra check since _distance is only updated once every 30 seconds. + // And we don't want to tell other nodes we have distance 0 when we haven't. + if (isEndpoint()) return 0; + if (_distance == 0) { + // Outdated info, so return "we don't know" + return 255; + } + return _distance; +} + + +NodesMap::const_iterator NodesHandler::begin() const { + return _nodes.begin(); +} + +NodesMap::const_iterator NodesHandler::end() const { + return _nodes.end(); +} + +NodesMap::const_iterator NodesHandler::find(uint8_t unit_nr) const +{ + return _nodes.find(unit_nr); +} + +bool NodesHandler::refreshNodeList(unsigned long max_age_allowed, unsigned long& max_age) +{ + max_age = 0; + bool nodeRemoved = false; + + for (auto it = _nodes.begin(); it != _nodes.end();) { + unsigned long age = it->second.getAge(); + if (age > max_age_allowed) { + bool mustErase = true; + #ifdef USES_ESPEASY_NOW + auto route_it = _nodeStats.find(it->second.unit); + if (route_it != _nodeStats.end()) { + if (route_it->second.getAge() > max_age_allowed) { + _nodeStats_mutex.lock(); + _nodeStats.erase(route_it); + _nodeStats_mutex.unlock(); + } else { + mustErase = false; + } + } + #endif + if (mustErase) { + if (Settings.UseRules && it->second.unit != 0) + { + // Add event about removing node from nodeslist. + eventQueue.addMove(strformat(F("p2pNode#Disconnected=%d"), it->second.unit)); + } + { + _nodes_mutex.lock(); + it = _nodes.erase(it); + _nodes_mutex.unlock(); + } + nodeRemoved = true; + } + } else { + ++it; + + if (age > max_age) { + max_age = age; + } + } + } + return nodeRemoved; +} + +// FIXME TD-er: should be a check per controller to see if it will accept messages +bool NodesHandler::isEndpoint() const +{ + // FIXME TD-er: Must check controller to see if it needs wifi (e.g. LoRa or cache controller do not need it) + #if FEATURE_MQTT + controllerIndex_t enabledMqttController = firstEnabledMQTT_ControllerIndex(); + if (validControllerIndex(enabledMqttController)) { + // FIXME TD-er: Must call updateMQTTclient_connected() and see what effect + // the MQTTclient_connected state has when using ESPEasy-NOW. + return MQTTclient_connected; + } + #endif + + if (!NetworkConnected()) return false; + + return false; +} + +#ifdef USES_ESPEASY_NOW +uint8_t NodesHandler::getESPEasyNOW_channel() const +{ + if (Settings.ForceESPEasyNOWchannel > 0) { + return Settings.ForceESPEasyNOWchannel; + } + if (active_network_medium == NetworkMedium_t::WIFI && NetworkConnected()) { + // FIXME TD-er: We can't be sure about the actual WiFi channel reported here. + return WiFi.channel(); + } + if (isEndpoint()) { + if (active_network_medium == NetworkMedium_t::WIFI) { + // FIXME TD-er: We can't be sure about the actual WiFi channel reported here. + return WiFi.channel(); + } + } + const NodeStruct *preferred = getPreferredNode(); + if (preferred != nullptr) { + if (preferred->distance < 255) { + return preferred->channel; + } + } + return WiFiEventData.usedChannel; +} +#endif + +bool NodesHandler::recentlyBecameDistanceZero() { + if (!_recentlyBecameDistanceZero) { + return false; + } + _recentlyBecameDistanceZero = false; + return true; +} + +bool NodesHandler::setRSSI(const MAC_address& mac, int rssi) +{ + return setRSSI(getNodeByMac(mac), rssi); +} + +bool NodesHandler::setRSSI(uint8_t unit, int rssi) +{ + return setRSSI(getNode(unit), rssi); +} + +bool NodesHandler::setRSSI(NodeStruct * node, int rssi) +{ + if (node != nullptr) { + node->setRSSI(rssi); + return true; + } + return false; +} + +bool NodesHandler::lastTimeValidDistanceExpired() const +{ +// if (_lastTimeValidDistance == 0) return false; + return timePassedSince(_lastTimeValidDistance) > 120000; // 2 minutes +} + +#ifdef USES_ESPEASY_NOW +void NodesHandler::updateSuccessRate(uint8_t unit, bool success) +{ + auto it = _nodeStats.find(unit); + if (it != _nodeStats.end()) { + it->second.updateSuccessRate(unit, success); + } +} + +void NodesHandler::updateSuccessRate(const MAC_address& mac, bool success) +{ + const NodeStruct * node = getNodeByMac(mac); + if (node == nullptr) { + return; + } + updateSuccessRate(node->unit, success); +} + +int NodesHandler::getRouteSuccessRate(uint8_t unit, uint8_t& distance) const +{ + distance = 255; + auto it = _nodeStats.find(unit); + if (it != _nodeStats.end()) { + const ESPEasy_now_traceroute_struct* route = it->second.bestRoute(); + if (route != nullptr) { + distance = route->getDistance(); + return route->computeSuccessRate(); + } + } + return 0; +} + +uint8_t NodesHandler::getSuccessRate(uint8_t unit) const +{ + auto it = _nodeStats.find(unit); + if (it != _nodeStats.end()) { + return it->second.getNodeSuccessRate(); + } + return 127; +} + +ESPEasy_Now_MQTT_QueueCheckState::Enum NodesHandler::getMQTTQueueState(uint8_t unit) const +{ + auto it = _nodeStats.find(unit); + if (it != _nodeStats.end()) { + return it->second.getMQTTQueueState(); + } + return ESPEasy_Now_MQTT_QueueCheckState::Enum::Unset; +} + +void NodesHandler::setMQTTQueueState(uint8_t unit, ESPEasy_Now_MQTT_QueueCheckState::Enum state) +{ + auto it = _nodeStats.find(unit); + if (it != _nodeStats.end()) { + it->second.setMQTTQueueState(state); + } +} + +void NodesHandler::setMQTTQueueState(const MAC_address& mac, ESPEasy_Now_MQTT_QueueCheckState::Enum state) +{ + const NodeStruct * node = getNodeByMac(mac); + if (node != nullptr) { + setMQTTQueueState(node->unit, state); + } +} + +void NodesHandler::updateMQTT_checkQueue() { + for (auto it = _nodes.begin(); it != _nodes.end(); ++it) { + if (it->second.ESPEasyNowPeer) { + if (getMQTTQueueState(it->second.unit) != ESPEasy_Now_MQTT_QueueCheckState::Enum::Empty) { + ESPEasy_now_MQTT_check_queue.push_back(it->second.ESPEasy_Now_MAC()); + } + } + } +} + +#endif + #endif \ No newline at end of file diff --git a/src/src/DataStructs/NodesHandler.h b/src/src/DataStructs/NodesHandler.h index 79c16b188d..d66e7eb99a 100644 --- a/src/src/DataStructs/NodesHandler.h +++ b/src/src/DataStructs/NodesHandler.h @@ -1,148 +1,154 @@ -#ifndef DATASTRUCTS_NODESHANDLER_H -#define DATASTRUCTS_NODESHANDLER_H - -#include "../../ESPEasy_common.h" -#if FEATURE_ESPEASY_P2P - -#include "../DataStructs/MAC_address.h" -#include "../DataStructs/NodeStruct.h" -#include "../DataStructs/NTP_candidate.h" - - -#ifdef USES_ESPEASY_NOW -# include "../DataStructs/ESPEasy_now_traceroute.h" -# include "../DataStructs/ESPEasy_now_Node_statistics.h" -# include "../DataStructs/ESPEasy_Now_MQTT_queue_check_packet.h" -# include "../DataTypes/ESPEasy_Now_MQTT_queue_check_state.h" -# include "../Globals/ESPEasy_now_peermanager.h" -#endif // ifdef USES_ESPEASY_NOW - -#include "../Helpers/ESPEasyMutex.h" - - -class NodesHandler { -public: - - // Add node to the list of known nodes. - // @retval true when the node was not yet present in the list. - bool addNode(const NodeStruct& node); - -#ifdef USES_ESPEASY_NOW - bool addNode(const NodeStruct & node, - const ESPEasy_now_traceroute_struct& traceRoute); -#endif // ifdef USES_ESPEASY_NOW - - - bool hasNode(uint8_t unit_nr) const; - - bool hasNode(const uint8_t *mac) const; - - NodeStruct * getNode(uint8_t unit_nr); - const NodeStruct * getNode(uint8_t unit_nr) const; - - NodeStruct * getNodeByMac(const MAC_address& mac); - const NodeStruct * getNodeByMac(const MAC_address& mac) const; - const NodeStruct * getNodeByMac(const MAC_address& mac, - bool & match_STA) const; - - NodesMap::const_iterator begin() const; - NodesMap::const_iterator end() const; - NodesMap::const_iterator find(uint8_t unit_nr) const; - - // Remove nodes in list older than max_age_allowed (msec) - // Returns oldest age, max_age (msec) not removed from the list. - // Return true if a node has been removed. - bool refreshNodeList(unsigned long max_age_allowed, - unsigned long& max_age); - - - const NodeStruct * getPreferredNode() const; - const NodeStruct * getPreferredNode_notMatching(uint8_t unit_nr) const; - const NodeStruct * getPreferredNode_notMatching(const MAC_address& not_matching) const; - -#ifdef USES_ESPEASY_NOW - const ESPEasy_now_traceroute_struct* getTraceRoute(uint8_t unit) const; - const ESPEasy_now_traceroute_struct* getDiscoveryRoute(uint8_t unit) const; - - void setTraceRoute(const MAC_address & mac, - const ESPEasy_now_traceroute_struct& traceRoute); -#endif // ifdef USES_ESPEASY_NOW - - // Update the node referring to this unit with the most recent info. - void updateThisNode(); - - const NodeStruct* getThisNode(); - - uint8_t getDistance() const; - - bool lastTimeValidDistanceExpired() const; - - unsigned long get_lastTimeValidDistance() const { - return _lastTimeValidDistance; - } - - bool isEndpoint() const; - -#ifdef USES_ESPEASY_NOW - uint8_t getESPEasyNOW_channel() const; -#endif // ifdef USES_ESPEASY_NOW - - bool recentlyBecameDistanceZero(); - - void setRSSI(const MAC_address& mac, - int rssi); - - void setRSSI(uint8_t unit, - int rssi); - -#ifdef USES_ESPEASY_NOW - void updateSuccessRate(uint8_t unit, - bool success); - void updateSuccessRate(const MAC_address& mac, - bool success); - - int getRouteSuccessRate(uint8_t unit, - uint8_t& distance) const; - - uint8_t getSuccessRate(uint8_t unit) const; - - ESPEasy_Now_MQTT_QueueCheckState::Enum getMQTTQueueState(uint8_t unit) const; - - void setMQTTQueueState(uint8_t unit, - ESPEasy_Now_MQTT_QueueCheckState::Enum state); - void setMQTTQueueState(const MAC_address & mac, - ESPEasy_Now_MQTT_QueueCheckState::Enum state); - -#endif // ifdef USES_ESPEASY_NOW - - timeSource_t getUnixTime(double &unix_time, int32_t& wander, uint8_t& unit) const { - return _ntp_candidate.getUnixTime(unix_time, wander, unit); - } - - -private: - - void setRSSI(NodeStruct *node, - int rssi); - - unsigned long _lastTimeValidDistance = 0; - - uint8_t _distance = 255; // Cached value - - NodesMap _nodes; - ESPEasy_Mutex _nodes_mutex; - - NTP_candidate_struct _ntp_candidate; - - -#ifdef USES_ESPEASY_NOW - ESPEasy_now_Node_statisticsMap _nodeStats; - ESPEasy_Mutex _nodeStats_mutex; -#endif // ifdef USES_ESPEASY_NOW - - bool _recentlyBecameDistanceZero = false; -}; - -#endif - +#ifndef DATASTRUCTS_NODESHANDLER_H +#define DATASTRUCTS_NODESHANDLER_H + +#include "../../ESPEasy_common.h" +#if FEATURE_ESPEASY_P2P + +#include "../DataStructs/MAC_address.h" +#include "../DataStructs/NodeStruct.h" +#include "../DataStructs/NTP_candidate.h" + + +#ifdef USES_ESPEASY_NOW +# include "../DataStructs/ESPEasy_now_traceroute.h" +# include "../DataStructs/ESPEasy_now_Node_statistics.h" +# include "../DataStructs/ESPEasy_Now_MQTT_queue_check_packet.h" +# include "../DataTypes/ESPEasy_Now_MQTT_queue_check_state.h" +# include "../Globals/ESPEasy_now_peermanager.h" +#endif // ifdef USES_ESPEASY_NOW + +#include "../Helpers/ESPEasyMutex.h" + + +class NodesHandler { +public: + + // Add node to the list of known nodes. + // @retval true when the node was not yet present in the list. + bool addNode(const NodeStruct& node); + +#ifdef USES_ESPEASY_NOW + bool addNode(const NodeStruct & node, + const ESPEasy_now_traceroute_struct& traceRoute); +#endif // ifdef USES_ESPEASY_NOW + + + bool hasNode(uint8_t unit_nr) const; + + bool hasNode(const uint8_t *mac) const; + + NodeStruct * getNode(uint8_t unit_nr); + const NodeStruct * getNode(uint8_t unit_nr) const; + + NodeStruct * getNodeByMac(const MAC_address& mac); + const NodeStruct * getNodeByMac(const MAC_address& mac) const; + const NodeStruct * getNodeByMac(const MAC_address& mac, + bool & match_STA) const; + + NodesMap::const_iterator begin() const; + NodesMap::const_iterator end() const; + NodesMap::const_iterator find(uint8_t unit_nr) const; + + // Remove nodes in list older than max_age_allowed (msec) + // Returns oldest age, max_age (msec) not removed from the list. + // Return true if a node has been removed. + bool refreshNodeList(unsigned long max_age_allowed, + unsigned long& max_age); + + + const NodeStruct * getPreferredNode() const; + const NodeStruct * getPreferredNode_notMatching(uint8_t unit_nr, bool checkMQTT_QueueState = false) const; + const NodeStruct * getPreferredNode_notMatching(const MAC_address& not_matching, bool checkMQTT_QueueState = false) const; + +#ifdef USES_ESPEASY_NOW + const ESPEasy_now_traceroute_struct* getTraceRoute(uint8_t unit) const; + const ESPEasy_now_traceroute_struct* getDiscoveryRoute(uint8_t unit) const; + + void setTraceRoute(const MAC_address & mac, + const ESPEasy_now_traceroute_struct& traceRoute); +#endif // ifdef USES_ESPEASY_NOW + + // Update the node referring to this unit with the most recent info. + void updateThisNode(); + + const NodeStruct* getThisNode(); + + uint8_t getDistance() const; + + bool lastTimeValidDistanceExpired() const; + + unsigned long get_lastTimeValidDistance() const { + return _lastTimeValidDistance; + } + + bool isEndpoint() const; + +#ifdef USES_ESPEASY_NOW + uint8_t getESPEasyNOW_channel() const; +#endif // ifdef USES_ESPEASY_NOW + + bool recentlyBecameDistanceZero(); + + bool setRSSI(const MAC_address& mac, + int rssi); + + bool setRSSI(uint8_t unit, + int rssi); + +#ifdef USES_ESPEASY_NOW + void updateSuccessRate(uint8_t unit, + bool success); + void updateSuccessRate(const MAC_address& mac, + bool success); + + int getRouteSuccessRate(uint8_t unit, + uint8_t& distance) const; + + uint8_t getSuccessRate(uint8_t unit) const; + + ESPEasy_Now_MQTT_QueueCheckState::Enum getMQTTQueueState(uint8_t unit) const; + + void setMQTTQueueState(uint8_t unit, + ESPEasy_Now_MQTT_QueueCheckState::Enum state); + void setMQTTQueueState(const MAC_address & mac, + ESPEasy_Now_MQTT_QueueCheckState::Enum state); + +#endif // ifdef USES_ESPEASY_NOW + + timeSource_t getUnixTime(double &unix_time, int32_t& wander, uint8_t& unit) const { + return _ntp_candidate.getUnixTime(unix_time, wander, unit); + } + + +#ifdef USES_ESPEASY_NOW + void updateMQTT_checkQueue(); + std::list ESPEasy_now_traceroute_queue; + std::list ESPEasy_now_MQTT_check_queue; +#endif + +private: + + bool setRSSI(NodeStruct *node, + int rssi); + + unsigned long _lastTimeValidDistance = 0; + + uint8_t _distance = 255; // Cached value + + NodesMap _nodes; + ESPEasy_Mutex _nodes_mutex; + + NTP_candidate_struct _ntp_candidate; + + +#ifdef USES_ESPEASY_NOW + ESPEasy_now_Node_statisticsMap _nodeStats; + ESPEasy_Mutex _nodeStats_mutex; +#endif // ifdef USES_ESPEASY_NOW + + bool _recentlyBecameDistanceZero = false; +}; + +#endif + #endif // ifndef DATASTRUCTS_NODESHANDLER_H \ No newline at end of file diff --git a/src/src/DataStructs/SecurityStruct.cpp b/src/src/DataStructs/SecurityStruct.cpp index 42510b2eee..9fa7e87dcd 100644 --- a/src/src/DataStructs/SecurityStruct.cpp +++ b/src/src/DataStructs/SecurityStruct.cpp @@ -1,103 +1,118 @@ -#include "../DataStructs/SecurityStruct.h" - -#include "../../ESPEasy_common.h" -#include "../CustomBuild/ESPEasyLimits.h" -#include "../ESPEasyCore/ESPEasy_Log.h" -#include "../Globals/CPlugins.h" - -SecurityStruct::SecurityStruct() { - ZERO_FILL(WifiSSID); - ZERO_FILL(WifiKey); - ZERO_FILL(WifiSSID2); - ZERO_FILL(WifiKey2); - ZERO_FILL(WifiAPKey); - - for (controllerIndex_t i = 0; i < CONTROLLER_MAX; ++i) { - ZERO_FILL(ControllerUser[i]); - ZERO_FILL(ControllerPassword[i]); - } - ZERO_FILL(Password); -} - -ChecksumType SecurityStruct::computeChecksum() const { - constexpr size_t len_upto_md5 = offsetof(SecurityStruct, md5); - return ChecksumType( - reinterpret_cast(this), - sizeof(SecurityStruct), - len_upto_md5); -} - -bool SecurityStruct::checksumMatch() const { - return computeChecksum().matchChecksum(md5); -} - -bool SecurityStruct::updateChecksum() { - const ChecksumType checksum = computeChecksum(); - if (checksum.matchChecksum(md5)) { - return false; - } - checksum.getChecksum(md5); - return true; -} - -void SecurityStruct::validate() { - ZERO_TERMINATE(WifiSSID); - ZERO_TERMINATE(WifiKey); - ZERO_TERMINATE(WifiSSID2); - ZERO_TERMINATE(WifiKey2); - ZERO_TERMINATE(WifiAPKey); - - for (controllerIndex_t i = 0; i < CONTROLLER_MAX; ++i) { - ZERO_TERMINATE(ControllerUser[i]); - ZERO_TERMINATE(ControllerPassword[i]); - } - ZERO_TERMINATE(Password); -} - -void SecurityStruct::forceSave() { - memset(md5, 0, 16); -} - -void SecurityStruct::clearWiFiCredentials() { - ZERO_FILL(WifiSSID); - ZERO_FILL(WifiKey); - ZERO_FILL(WifiSSID2); - ZERO_FILL(WifiKey2); - #ifndef BUILD_MINIMAL_OTA - addLog(LOG_LEVEL_INFO, F("WiFi : Clear WiFi credentials from settings")); - #endif -} - -void SecurityStruct::clearWiFiCredentials(SecurityStruct::WiFiCredentialsSlot slot) { - if (slot == SecurityStruct::WiFiCredentialsSlot::first) { - ZERO_FILL(WifiSSID); - ZERO_FILL(WifiKey); - } else if (slot == SecurityStruct::WiFiCredentialsSlot::second) { - ZERO_FILL(WifiSSID2); - ZERO_FILL(WifiKey2); - } -} - -bool SecurityStruct::hasWiFiCredentials() const { - return hasWiFiCredentials(SecurityStruct::WiFiCredentialsSlot::first) || - hasWiFiCredentials(SecurityStruct::WiFiCredentialsSlot::second); -} - -bool SecurityStruct::hasWiFiCredentials(SecurityStruct::WiFiCredentialsSlot slot) const { - if (slot == SecurityStruct::WiFiCredentialsSlot::first) - return (WifiSSID[0] != 0 && !String(WifiSSID).equalsIgnoreCase(F("ssid"))); - if (slot == SecurityStruct::WiFiCredentialsSlot::second) - return (WifiSSID2[0] != 0 && !String(WifiSSID2).equalsIgnoreCase(F("ssid"))); - - return false; -} - -String SecurityStruct::getPassword() const { - String res; - const size_t passLength = strnlen(Password, sizeof(Password)); - res.reserve(passLength); - for (size_t i = 0; i < passLength; ++i) { - res += Password[i]; - } - return res; +#include "../DataStructs/SecurityStruct.h" + +#include "../../ESPEasy_common.h" +#include "../CustomBuild/ESPEasyLimits.h" +#include "../ESPEasyCore/ESPEasy_Log.h" +#include "../Globals/CPlugins.h" + +SecurityStruct::SecurityStruct() { + ZERO_FILL(WifiSSID); + ZERO_FILL(WifiKey); + ZERO_FILL(WifiSSID2); + ZERO_FILL(WifiKey2); + ZERO_FILL(WifiAPKey); + + for (controllerIndex_t i = 0; i < CONTROLLER_MAX; ++i) { + ZERO_FILL(ControllerUser[i]); + ZERO_FILL(ControllerPassword[i]); + } + for (uint8_t i = 0; i < ESPEASY_NOW_PEER_MAX; ++i) { + ZERO_FILL(EspEasyNowPeerMAC[i]); + } + ZERO_FILL(Password); +} + +ChecksumType SecurityStruct::computeChecksum() const { + constexpr size_t len_upto_md5 = offsetof(SecurityStruct, md5); + return ChecksumType( + reinterpret_cast(this), + sizeof(SecurityStruct), + len_upto_md5); +} + +bool SecurityStruct::checksumMatch() const { + return computeChecksum().matchChecksum(md5); +} + +bool SecurityStruct::updateChecksum() { + const ChecksumType checksum = computeChecksum(); + if (checksum.matchChecksum(md5)) { + return false; + } + checksum.getChecksum(md5); + return true; +} + +void SecurityStruct::validate() { + ZERO_TERMINATE(WifiSSID); + ZERO_TERMINATE(WifiKey); + ZERO_TERMINATE(WifiSSID2); + ZERO_TERMINATE(WifiKey2); + ZERO_TERMINATE(WifiAPKey); + + for (controllerIndex_t i = 0; i < CONTROLLER_MAX; ++i) { + ZERO_TERMINATE(ControllerUser[i]); + ZERO_TERMINATE(ControllerPassword[i]); + } + ZERO_TERMINATE(Password); +} + +bool SecurityStruct::peerMacSet(uint8_t peer_index) const { + if (peer_index >= ESPEASY_NOW_PEER_MAX) { + return false; + } + for (int i = 0; i < 6; ++i) { + if (EspEasyNowPeerMAC[peer_index][i] != 0) { + return true; + } + } + return false; +} + +void SecurityStruct::forceSave() { + memset(md5, 0, 16); +} + +void SecurityStruct::clearWiFiCredentials() { + ZERO_FILL(WifiSSID); + ZERO_FILL(WifiKey); + ZERO_FILL(WifiSSID2); + ZERO_FILL(WifiKey2); + #ifndef BUILD_MINIMAL_OTA + addLog(LOG_LEVEL_INFO, F("WiFi : Clear WiFi credentials from settings")); + #endif +} + +void SecurityStruct::clearWiFiCredentials(SecurityStruct::WiFiCredentialsSlot slot) { + if (slot == SecurityStruct::WiFiCredentialsSlot::first) { + ZERO_FILL(WifiSSID); + ZERO_FILL(WifiKey); + } else if (slot == SecurityStruct::WiFiCredentialsSlot::second) { + ZERO_FILL(WifiSSID2); + ZERO_FILL(WifiKey2); + } +} + +bool SecurityStruct::hasWiFiCredentials() const { + return hasWiFiCredentials(SecurityStruct::WiFiCredentialsSlot::first) || + hasWiFiCredentials(SecurityStruct::WiFiCredentialsSlot::second); +} + +bool SecurityStruct::hasWiFiCredentials(SecurityStruct::WiFiCredentialsSlot slot) const { + if (slot == SecurityStruct::WiFiCredentialsSlot::first) + return (WifiSSID[0] != 0 && !String(WifiSSID).equalsIgnoreCase(F("ssid"))); + if (slot == SecurityStruct::WiFiCredentialsSlot::second) + return (WifiSSID2[0] != 0 && !String(WifiSSID2).equalsIgnoreCase(F("ssid"))); + + return false; +} + +String SecurityStruct::getPassword() const { + String res; + const size_t passLength = strnlen(Password, sizeof(Password)); + res.reserve(passLength); + for (size_t i = 0; i < passLength; ++i) { + res += Password[i]; + } + return res; } \ No newline at end of file diff --git a/src/src/DataStructs/SecurityStruct.h b/src/src/DataStructs/SecurityStruct.h index 68bdb12c7f..485b8328b5 100644 --- a/src/src/DataStructs/SecurityStruct.h +++ b/src/src/DataStructs/SecurityStruct.h @@ -1,63 +1,67 @@ -#ifndef DATASTRUCTS_SECURITYSTRUCT_H -#define DATASTRUCTS_SECURITYSTRUCT_H - -#include "../../ESPEasy_common.h" -#include "../CustomBuild/ESPEasyLimits.h" -#include "../DataStructs/ChecksumType.h" - -/*********************************************************************************************\ - * SecurityStruct -\*********************************************************************************************/ -struct SecurityStruct -{ - enum class WiFiCredentialsSlot { - first, - second - }; - - - SecurityStruct(); - - ChecksumType computeChecksum() const; - - // Return true when stored checksum matches. - bool checksumMatch() const; - - // Check and update checksum when content was changed. - // Return true when stored checksum is updated. - bool updateChecksum(); - - void validate(); - - // Clear the checksum to make sure file will be saved - void forceSave(); - - void clearWiFiCredentials(); - - void clearWiFiCredentials(WiFiCredentialsSlot slot); - - bool hasWiFiCredentials() const; - - bool hasWiFiCredentials(WiFiCredentialsSlot slot) const; - - String getPassword() const; - - char WifiSSID[32]; - char WifiKey[64]; - char WifiSSID2[32]; - char WifiKey2[64]; - char WifiAPKey[64]; - char ControllerUser[CONTROLLER_MAX][26]; - char ControllerPassword[CONTROLLER_MAX][64]; - char Password[26]; - uint8_t AllowedIPrangeLow[4] = {0}; // TD-er: Use these - uint8_t AllowedIPrangeHigh[4] = {0}; - uint8_t IPblockLevel = 0; - - //its safe to extend this struct, up to 4096 bytes, default values in config are 0. Make sure crc is last - uint8_t ProgmemMd5[16] = {0}; // crc of the binary that last saved the struct to file. - uint8_t md5[16] = {0}; -}; - - -#endif // DATASTRUCTS_SECURITYSTRUCT_H +#ifndef DATASTRUCTS_SECURITYSTRUCT_H +#define DATASTRUCTS_SECURITYSTRUCT_H + +#include "../../ESPEasy_common.h" +#include "../CustomBuild/ESPEasyLimits.h" +#include "../DataStructs/ChecksumType.h" + +/*********************************************************************************************\ + * SecurityStruct +\*********************************************************************************************/ +struct SecurityStruct +{ + enum class WiFiCredentialsSlot { + first, + second + }; + + + SecurityStruct(); + + ChecksumType computeChecksum() const; + + // Return true when stored checksum matches. + bool checksumMatch() const; + + // Check and update checksum when content was changed. + // Return true when stored checksum is updated. + bool updateChecksum(); + + void validate(); + + bool peerMacSet(uint8_t peer_index) const; + + // Clear the checksum to make sure file will be saved + void forceSave(); + + void clearWiFiCredentials(); + + void clearWiFiCredentials(WiFiCredentialsSlot slot); + + bool hasWiFiCredentials() const; + + bool hasWiFiCredentials(WiFiCredentialsSlot slot) const; + + String getPassword() const; + + char WifiSSID[32]; + char WifiKey[64]; + char WifiSSID2[32]; + char WifiKey2[64]; + char WifiAPKey[64]; + char ControllerUser[CONTROLLER_MAX][26]; + char ControllerPassword[CONTROLLER_MAX][64]; + char Password[26]; + uint8_t AllowedIPrangeLow[4] = {0}; // TD-er: Use these + uint8_t AllowedIPrangeHigh[4] = {0}; + uint8_t IPblockLevel = 0; + + //its safe to extend this struct, up to 4096 bytes, default values in config are 0. Make sure crc is last + uint8_t ProgmemMd5[16] = {0}; // crc of the binary that last saved the struct to file. + uint8_t md5[16] = {0}; + + uint8_t EspEasyNowPeerMAC[ESPEASY_NOW_PEER_MAX][6]; +}; + + +#endif // DATASTRUCTS_SECURITYSTRUCT_H diff --git a/src/src/DataStructs/SendData_DuplicateChecker_data.cpp b/src/src/DataStructs/SendData_DuplicateChecker_data.cpp new file mode 100644 index 0000000000..1f588fdff1 --- /dev/null +++ b/src/src/DataStructs/SendData_DuplicateChecker_data.cpp @@ -0,0 +1,19 @@ +#include "../DataStructs/SendData_DuplicateChecker_data.h" + +#include "../ESPEasyCore/Controller.h" +#include "../Helpers/ESPEasy_time_calc.h" + +#define TIMEOUT_ASK_FOR_DUPLICATE 100 + +SendData_DuplicateChecker_data::SendData_DuplicateChecker_data(EventStruct *event) { + _event.deep_copy(event); +} + +bool SendData_DuplicateChecker_data::doSend() +{ + if (timePassedSince(_timestamp) > TIMEOUT_ASK_FOR_DUPLICATE) { + sendData(&_event); + return true; + } + return false; +} diff --git a/src/src/DataStructs/SendData_DuplicateChecker_data.h b/src/src/DataStructs/SendData_DuplicateChecker_data.h new file mode 100644 index 0000000000..a61e04fe8c --- /dev/null +++ b/src/src/DataStructs/SendData_DuplicateChecker_data.h @@ -0,0 +1,20 @@ +#ifndef DATASTRUCTS_SENDDATA_DUPLICATECHECKER_DATA_H +#define DATASTRUCTS_SENDDATA_DUPLICATECHECKER_DATA_H + +#include "../DataStructs/ESPEasy_EventStruct.h" + +class SendData_DuplicateChecker_data { +public: + + SendData_DuplicateChecker_data(struct EventStruct *event); + + bool doSend(); + +private: + + EventStruct _event; + unsigned long _timestamp = millis(); +}; + + +#endif // DATASTRUCTS_SENDDATA_DUPLICATECHECKER_DATA_H diff --git a/src/src/DataStructs/SendData_DuplicateChecker_struct.cpp b/src/src/DataStructs/SendData_DuplicateChecker_struct.cpp new file mode 100644 index 0000000000..2139f9db59 --- /dev/null +++ b/src/src/DataStructs/SendData_DuplicateChecker_struct.cpp @@ -0,0 +1,121 @@ +#include "../DataStructs/SendData_DuplicateChecker_struct.h" + +#include "../ESPEasyCore/ESPEasy_Log.h" +#include "../Globals/Plugins.h" +#include "../Helpers/CRC_functions.h" +#include "../Helpers/ESPEasy_time_calc.h" + + +#define HISTORIC_ELEMENT_LIFETIME 10000 // 10 seconds + +const uint32_t SendData_DuplicateChecker_struct::DUPLICATE_CHECKER_INVALID_KEY = 0; + +uint32_t create_compare_key(taskIndex_t taskIndex, const String& compare_key) +{ + pluginID_t id = getPluginID_from_TaskIndex(taskIndex); + + if (id == INVALID_PLUGIN_ID) { + return SendData_DuplicateChecker_struct::DUPLICATE_CHECKER_INVALID_KEY; + } + uint32_t key = calc_CRC32(reinterpret_cast(compare_key.c_str()), compare_key.length()); + key += id.value; + + // consider the 0 as invalid key, so never return 0 on a valid key + if (key == SendData_DuplicateChecker_struct::DUPLICATE_CHECKER_INVALID_KEY) { + ++key; + } + return key; +} + +uint32_t SendData_DuplicateChecker_struct::add(struct EventStruct *event, const String& compare_key) +{ + uint32_t key = create_compare_key(event->TaskIndex, compare_key); + + if (key != DUPLICATE_CHECKER_INVALID_KEY) { + if (historicKey(key)) { + // Item already exists in the queue, no need to ask around + return DUPLICATE_CHECKER_INVALID_KEY; + } + { + _queue_mutex.lock(); + _queue.emplace(std::make_pair(key, SendData_DuplicateChecker_data(event))); + _queue_mutex.unlock(); + } + { + _historic_mutex.lock(); + _historic[key] = millis(); + _historic_mutex.unlock(); + } + } + return key; +} + +bool SendData_DuplicateChecker_struct::historicKey(uint32_t key) +{ + // Consider invalid key always as historic key so it will never be processed. + if (key == DUPLICATE_CHECKER_INVALID_KEY) { return true; } + auto it = _historic.find(key); + + if (it == _historic.end()) + { + // Someone asked about it, so mark it here + _historic_mutex.lock(); + _historic[key] = millis(); + _historic_mutex.unlock(); + return false; + } + + // Apparently we've seen another instance of that message, renew the last seen timestamp + it->second = millis(); + return true; +} + +void SendData_DuplicateChecker_struct::remove(uint32_t key) +{ + auto it = _queue.find(key); + + if (it != _queue.end()) { + #ifndef BUILD_NO_DEBUG + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + addLog(LOG_LEVEL_DEBUG, F(ESPEASY_NOW_NAME ": message not sent as processed elsewhere")); + } + #endif + { + _queue_mutex.lock(); + _queue.erase(it); + _queue_mutex.unlock(); + } + } +} + +void SendData_DuplicateChecker_struct::loop() +{ + purge_old_historic(); + + for (auto it = _queue.begin(); it != _queue.end();) { + if (it->second.doSend()) { + _queue_mutex.lock(); + _queue.erase(it); + _queue_mutex.unlock(); + + // Processed one, others will be processed later + return; + } else { + ++it; + } + } +} + +void SendData_DuplicateChecker_struct::purge_old_historic() +{ + for (auto it = _historic.begin(); it != _historic.end();) + { + if (timePassedSince(it->second) > HISTORIC_ELEMENT_LIFETIME) { + _historic_mutex.lock(); + it = _historic.erase(it); + _historic_mutex.unlock(); + } else { + ++it; + } + } +} diff --git a/src/src/DataStructs/SendData_DuplicateChecker_struct.h b/src/src/DataStructs/SendData_DuplicateChecker_struct.h new file mode 100644 index 0000000000..79af19bdc0 --- /dev/null +++ b/src/src/DataStructs/SendData_DuplicateChecker_struct.h @@ -0,0 +1,49 @@ +#ifndef DATASTRUCTS_SENDDATA_DUPLICATECHECKER_STRUCT_H +#define DATASTRUCTS_SENDDATA_DUPLICATECHECKER_STRUCT_H + + +#include "../DataStructs/SendData_DuplicateChecker_data.h" + +#include "../Helpers/ESPEasyMutex.h" + +#include + +// This duplicate checker keeps track of keys of previous messages for some time. +// If a message has been seen before, it will not be processed. +// New messages will be asked to peers. +// If a peer already has the message processed, then it has to reply within the timeout. +// Messages queried for by a peer will also be added to the historic list of seen messages. +class SendData_DuplicateChecker_struct { +public: + + static const uint32_t DUPLICATE_CHECKER_INVALID_KEY; + + // Add event to the queue and return key to ask around. + uint32_t add(struct EventStruct *event, + const String & compare_key); + + // Check to see if we already know about some key. + // If not yet seen, add it to our historic list since someone else is already processing it + bool historicKey(uint32_t key); + + // Another node already reported to have handled the key + void remove(uint32_t key); + + // Send out items in the queue not marked by other nodes as already seen. + void loop(); + +private: + + void purge_old_historic(); + + // Map of key + event + std::map_queue; + ESPEasy_Mutex _queue_mutex; + + // Map of key + timestamp last seen. + std::map_historic; + ESPEasy_Mutex _historic_mutex; + +}; + +#endif // DATASTRUCTS_SENDDATA_DUPLICATECHECKER_STRUCT_H diff --git a/src/src/DataStructs/TimingStats.cpp b/src/src/DataStructs/TimingStats.cpp index d74f1bd243..5076d14250 100644 --- a/src/src/DataStructs/TimingStats.cpp +++ b/src/src/DataStructs/TimingStats.cpp @@ -1,340 +1,352 @@ -#include "../DataStructs/TimingStats.h" - -#if FEATURE_TIMING_STATS - -# include "../DataTypes/ESPEasy_plugin_functions.h" -# include "../Globals/CPlugins.h" -# include "../Helpers/_CPlugin_Helper.h" -# include "../Helpers/StringConverter.h" - -std::map pluginStats; -std::map controllerStats; -std::map miscStats; -unsigned long timingstats_last_reset(0); - - -void TimingStats::add(int32_t duration_usec) { - // Max duration in usec is roughly 35 minutes. - // For timing stats more than enough - if (duration_usec < 0) return; - _timeTotal += static_cast(duration_usec); - ++_count; - - if (static_cast(duration_usec) > _maxVal) { _maxVal = duration_usec; } - - if (static_cast(duration_usec) < _minVal) { _minVal = duration_usec; } -} - -void TimingStats::reset() { - _timeTotal = 0u; - _count = 0u; - _maxVal = 0u; - _minVal = 4294967295u; -} - -bool TimingStats::isEmpty() const { - return _count == 0u; -} - -float TimingStats::getAvg() const { - if (_count == 0) { return 0.0f; } - return static_cast(_timeTotal) / static_cast(_count); -} - -uint32_t TimingStats::getMinMax(uint32_t& minVal, uint32_t& maxVal) const { - minVal = _minVal; - maxVal = _maxVal; - return _count; -} - -bool TimingStats::thresholdExceeded(const uint32_t& threshold) const { - if (_count == 0) { - return false; - } - return _maxVal > threshold; -} - -/********************************************************************************************\ - Functions used for displaying timing stats - \*********************************************************************************************/ -const __FlashStringHelper* getPluginFunctionName(int function) { - switch (function) { - case PLUGIN_INIT_ALL: return F("INIT_ALL"); - case PLUGIN_INIT: return F("INIT"); - case PLUGIN_READ: return F("READ"); - case PLUGIN_ONCE_A_SECOND: return F("ONCE_A_SECOND"); - case PLUGIN_TEN_PER_SECOND: return F("TEN_PER_SECOND"); - case PLUGIN_DEVICE_ADD: return F("DEVICE_ADD"); - case PLUGIN_EVENTLIST_ADD: return F("EVENTLIST_ADD"); - case PLUGIN_WEBFORM_SAVE: return F("WEBFORM_SAVE"); - case PLUGIN_WEBFORM_LOAD: return F("WEBFORM_LOAD"); - case PLUGIN_WEBFORM_SHOW_VALUES: return F("WEBFORM_SHOW_VALUES"); - case PLUGIN_FORMAT_USERVAR: return F("FORMAT_USERVAR"); - case PLUGIN_GET_DEVICENAME: return F("GET_DEVICENAME"); - case PLUGIN_GET_DEVICEVALUENAMES: return F("GET_DEVICEVALUENAMES"); - case PLUGIN_GET_DEVICEVALUECOUNT: return F("GET_DEVICEVALUECOUNT"); - case PLUGIN_GET_DEVICEVTYPE: return F("GET_DEVICEVTYPE"); - case PLUGIN_WRITE: return F("WRITE"); - case PLUGIN_WEBFORM_SHOW_CONFIG: return F("WEBFORM_SHOW_CONFIG"); - #if FEATURE_PLUGIN_STATS - case PLUGIN_WEBFORM_LOAD_SHOW_STATS: return F("WEBFORM_LOAD_SHOW_STATS"); - #endif - case PLUGIN_SERIAL_IN: return F("SERIAL_IN"); - case PLUGIN_UDP_IN: return F("UDP_IN"); - case PLUGIN_CLOCK_IN: return F("CLOCK_IN"); - case PLUGIN_TASKTIMER_IN: return F("TASKTIMER_IN"); - case PLUGIN_FIFTY_PER_SECOND: return F("FIFTY_PER_SECOND"); - case PLUGIN_SET_CONFIG: return F("SET_CONFIG"); - case PLUGIN_GET_DEVICEGPIONAMES: return F("GET_DEVICEGPIONAMES"); - case PLUGIN_EXIT: return F("EXIT"); - case PLUGIN_GET_CONFIG_VALUE: return F("GET_CONFIG"); -// case PLUGIN_UNCONDITIONAL_POLL: return F("UNCONDITIONAL_POLL"); - case PLUGIN_REQUEST: return F("REQUEST"); - case PLUGIN_PROCESS_CONTROLLER_DATA: return F("PROCESS_CONTROLLER_DATA"); - case PLUGIN_I2C_GET_ADDRESS: return F("I2C_CHECK_DEVICE"); - case PLUGIN_READ_ERROR_OCCURED: return F("PLUGIN_READ_ERROR_OCCURED"); - } - return F("Unknown"); -} - -bool mustLogFunction(int function) { - if (!Settings.EnableTimingStats()) { return false; } - - switch (function) { -// case PLUGIN_INIT_ALL: return false; -// case PLUGIN_INIT: return false; - case PLUGIN_READ: return true; - case PLUGIN_ONCE_A_SECOND: return true; - case PLUGIN_TEN_PER_SECOND: return true; -// case PLUGIN_DEVICE_ADD: return false; -// case PLUGIN_EVENTLIST_ADD: return false; -// case PLUGIN_WEBFORM_SAVE: return false; -// case PLUGIN_WEBFORM_LOAD: return false; -// case PLUGIN_WEBFORM_SHOW_VALUES: return false; - case PLUGIN_FORMAT_USERVAR: return true; - case PLUGIN_GET_DEVICENAME: return true; -// case PLUGIN_GET_DEVICEVALUENAMES: return false; -// case PLUGIN_GET_DEVICEVALUECOUNT: return true; -// case PLUGIN_GET_DEVICEVTYPE: return true; - case PLUGIN_WRITE: return true; -// case PLUGIN_WEBFORM_SHOW_CONFIG: return false; - case PLUGIN_SERIAL_IN: return true; -// case PLUGIN_UDP_IN: return false; -// case PLUGIN_CLOCK_IN: return false; - case PLUGIN_TASKTIMER_IN: return true; - case PLUGIN_FIFTY_PER_SECOND: return true; -// case PLUGIN_SET_CONFIG: return false; -// case PLUGIN_GET_DEVICEGPIONAMES: return false; -// case PLUGIN_EXIT: return false; -// case PLUGIN_GET_CONFIG_VALUE: return false; -// case PLUGIN_UNCONDITIONAL_POLL: return false; - case PLUGIN_REQUEST: return true; - case PLUGIN_I2C_GET_ADDRESS: return true; - case PLUGIN_PROCESS_CONTROLLER_DATA: return true; - case PLUGIN_READ_ERROR_OCCURED: return true; - } - return false; -} - -const __FlashStringHelper* getCPluginCFunctionName(CPlugin::Function function) { - switch (function) { - case CPlugin::Function::CPLUGIN_PROTOCOL_ADD: return F("CPLUGIN_PROTOCOL_ADD"); - case CPlugin::Function::CPLUGIN_CONNECT_SUCCESS: return F("CPLUGIN_CONNECT_SUCCESS"); - case CPlugin::Function::CPLUGIN_CONNECT_FAIL: return F("CPLUGIN_CONNECT_FAIL"); - case CPlugin::Function::CPLUGIN_PROTOCOL_TEMPLATE: return F("CPLUGIN_PROTOCOL_TEMPLATE"); - case CPlugin::Function::CPLUGIN_PROTOCOL_SEND: return F("CPLUGIN_PROTOCOL_SEND"); - case CPlugin::Function::CPLUGIN_PROTOCOL_RECV: return F("CPLUGIN_PROTOCOL_RECV"); - case CPlugin::Function::CPLUGIN_GET_DEVICENAME: return F("CPLUGIN_GET_DEVICENAME"); - case CPlugin::Function::CPLUGIN_WEBFORM_SAVE: return F("CPLUGIN_WEBFORM_SAVE"); - case CPlugin::Function::CPLUGIN_WEBFORM_LOAD: return F("CPLUGIN_WEBFORM_LOAD"); - case CPlugin::Function::CPLUGIN_GET_PROTOCOL_DISPLAY_NAME: return F("CPLUGIN_GET_PROTOCOL_DISPLAY_NAME"); - case CPlugin::Function::CPLUGIN_TASK_CHANGE_NOTIFICATION: return F("CPLUGIN_TASK_CHANGE_NOTIFICATION"); - case CPlugin::Function::CPLUGIN_INIT: return F("CPLUGIN_INIT"); - case CPlugin::Function::CPLUGIN_UDP_IN: return F("CPLUGIN_UDP_IN"); - case CPlugin::Function::CPLUGIN_FLUSH: return F("CPLUGIN_FLUSH"); - case CPlugin::Function::CPLUGIN_TEN_PER_SECOND: return F("CPLUGIN_TEN_PER_SECOND"); - case CPlugin::Function::CPLUGIN_FIFTY_PER_SECOND: return F("CPLUGIN_FIFTY_PER_SECOND"); - case CPlugin::Function::CPLUGIN_INIT_ALL: return F("CPLUGIN_INIT_ALL"); - case CPlugin::Function::CPLUGIN_EXIT: return F("CPLUGIN_EXIT"); - case CPlugin::Function::CPLUGIN_WRITE: return F("CPLUGIN_WRITE"); - - case CPlugin::Function::CPLUGIN_GOT_CONNECTED: - case CPlugin::Function::CPLUGIN_GOT_INVALID: - case CPlugin::Function::CPLUGIN_INTERVAL: - case CPlugin::Function::CPLUGIN_ACKNOWLEDGE: - case CPlugin::Function::CPLUGIN_WEBFORM_SHOW_HOST_CONFIG: - break; - } - return F("Unknown"); -} - -bool mustLogCFunction(CPlugin::Function function) { - if (!Settings.EnableTimingStats()) { return false; } - - switch (function) { - case CPlugin::Function::CPLUGIN_PROTOCOL_ADD: return false; - case CPlugin::Function::CPLUGIN_CONNECT_SUCCESS: return true; - case CPlugin::Function::CPLUGIN_CONNECT_FAIL: return true; - case CPlugin::Function::CPLUGIN_PROTOCOL_TEMPLATE: return false; - case CPlugin::Function::CPLUGIN_PROTOCOL_SEND: return true; - case CPlugin::Function::CPLUGIN_PROTOCOL_RECV: return true; - case CPlugin::Function::CPLUGIN_GET_DEVICENAME: return false; - case CPlugin::Function::CPLUGIN_WEBFORM_SAVE: return false; - case CPlugin::Function::CPLUGIN_WEBFORM_LOAD: return false; - case CPlugin::Function::CPLUGIN_GET_PROTOCOL_DISPLAY_NAME: return false; - case CPlugin::Function::CPLUGIN_TASK_CHANGE_NOTIFICATION: return false; - case CPlugin::Function::CPLUGIN_INIT: return false; - case CPlugin::Function::CPLUGIN_UDP_IN: return true; - case CPlugin::Function::CPLUGIN_FLUSH: return false; - case CPlugin::Function::CPLUGIN_TEN_PER_SECOND: return true; - case CPlugin::Function::CPLUGIN_FIFTY_PER_SECOND: return true; - case CPlugin::Function::CPLUGIN_INIT_ALL: return false; - case CPlugin::Function::CPLUGIN_EXIT: return false; - case CPlugin::Function::CPLUGIN_WRITE: return true; - - case CPlugin::Function::CPLUGIN_GOT_CONNECTED: - case CPlugin::Function::CPLUGIN_GOT_INVALID: - case CPlugin::Function::CPLUGIN_INTERVAL: - case CPlugin::Function::CPLUGIN_ACKNOWLEDGE: - case CPlugin::Function::CPLUGIN_WEBFORM_SHOW_HOST_CONFIG: - break; - } - return false; -} - -// Return flash string type to reduce bin size -const __FlashStringHelper* getMiscStatsName_F(TimingStatsElements stat) { - switch (stat) { - case TimingStatsElements::LOADFILE_STATS: return F("Load File"); - case TimingStatsElements::SAVEFILE_STATS: return F("Save File"); - case TimingStatsElements::LOOP_STATS: return F("Loop"); - case TimingStatsElements::PLUGIN_CALL_50PS: return F("Plugin call 50 p/s"); - case TimingStatsElements::PLUGIN_CALL_10PS: return F("Plugin call 10 p/s"); - case TimingStatsElements::PLUGIN_CALL_10PSU: return F("Plugin call 10 p/s U"); - case TimingStatsElements::PLUGIN_CALL_1PS: return F("Plugin call 1 p/s"); - case TimingStatsElements::CPLUGIN_CALL_50PS: return F("CPlugin call 50 p/s"); - case TimingStatsElements::CPLUGIN_CALL_10PS: return F("CPlugin call 10 p/s"); - case TimingStatsElements::SENSOR_SEND_TASK: return F("SensorSendTask()"); - case TimingStatsElements::COMMAND_EXEC_INTERNAL: return F("Exec Internal Command"); - case TimingStatsElements::COMMAND_DECODE_INTERNAL: return F("Decode Internal Command"); - case TimingStatsElements::CONSOLE_LOOP: return F("Console loop()"); - case TimingStatsElements::CONSOLE_WRITE_SERIAL: return F("Console out"); - case TimingStatsElements::SEND_DATA_STATS: return F("sendData()"); - case TimingStatsElements::COMPUTE_FORMULA_STATS: return F("Compute formula"); - case TimingStatsElements::COMPUTE_STATS: return F("Compute()"); - case TimingStatsElements::PLUGIN_CALL_DEVICETIMER_IN: return F("PLUGIN_DEVICETIMER_IN"); - case TimingStatsElements::SET_NEW_TIMER: return F("setNewTimerAt()"); - case TimingStatsElements::MQTT_DELAY_QUEUE: return F("Delay queue MQTT"); - case TimingStatsElements::TRY_CONNECT_HOST_TCP: return F("try_connect_host() (TCP)"); - case TimingStatsElements::TRY_CONNECT_HOST_UDP: return F("try_connect_host() (UDP)"); - case TimingStatsElements::HOST_BY_NAME_STATS: return F("hostByName()"); - case TimingStatsElements::CONNECT_CLIENT_STATS: return F("connectClient()"); - case TimingStatsElements::LOAD_CUSTOM_TASK_STATS: return F("LoadCustomTaskSettings()"); - case TimingStatsElements::WIFI_ISCONNECTED_STATS: return F("WiFi.isConnected()"); - case TimingStatsElements::WIFI_NOTCONNECTED_STATS: return F("WiFi.isConnected() (fail)"); - case TimingStatsElements::LOAD_TASK_SETTINGS: return F("LoadTaskSettings()"); - case TimingStatsElements::SAVE_TASK_SETTINGS: return F("SaveTaskSettings()"); - case TimingStatsElements::LOAD_CONTROLLER_SETTINGS: return F("LoadControllerSettings()"); - #ifdef ESP32 - case TimingStatsElements::LOAD_CONTROLLER_SETTINGS_C: return F("LoadControllerSettings() (cached)"); - #endif - case TimingStatsElements::SAVE_CONTROLLER_SETTINGS: return F("SaveControllerSettings()"); - case TimingStatsElements::TRY_OPEN_FILE: return F("TryOpenFile()"); - case TimingStatsElements::FS_GC_SUCCESS: return F("ESPEASY_FS GC success"); - case TimingStatsElements::FS_GC_FAIL: return F("ESPEASY_FS GC fail"); - case TimingStatsElements::RULES_PROCESSING: return F("rulesProcessing()"); - case TimingStatsElements::RULES_PARSE_LINE: return F("parseCompleteNonCommentLine()"); - case TimingStatsElements::RULES_PROCESS_MATCHED: return F("processMatchedRule()"); - case TimingStatsElements::RULES_MATCH: return F("rulesMatch()"); - case TimingStatsElements::GRAT_ARP_STATS: return F("sendGratuitousARP()"); - case TimingStatsElements::SAVE_TO_RTC: return F("saveToRTC()"); - case TimingStatsElements::BACKGROUND_TASKS: return F("backgroundtasks()"); - case TimingStatsElements::UPDATE_RTTTL: return F("update_rtttl()"); - case TimingStatsElements::CHECK_UDP: return F("checkUDP()"); - case TimingStatsElements::C013_SEND_UDP: return F("C013_sendUDP() SUCCESS"); - case TimingStatsElements::C013_SEND_UDP_FAIL: return F("C013_sendUDP() FAIL"); - case TimingStatsElements::C013_RECEIVE_SENSOR_DATA: return F("C013 Receive sensor data"); - case TimingStatsElements::WEBSERVER_HANDLE_CLIENT: return F("web_server.handleClient()"); - case TimingStatsElements::PROCESS_SYSTEM_EVENT_QUEUE: return F("process_system_event_queue()"); - case TimingStatsElements::FORMAT_USER_VAR: return F("doFormatUserVar()"); - case TimingStatsElements::IS_NUMERICAL: return F("isNumerical()"); - case TimingStatsElements::HANDLE_SCHEDULER_IDLE: return F("handle_schedule() idle"); - case TimingStatsElements::HANDLE_SCHEDULER_TASK: return F("handle_schedule() task"); - case TimingStatsElements::PARSE_TEMPLATE_PADDED: return F("parseTemplate_padded()"); - case TimingStatsElements::PARSE_SYSVAR: return F("parseSystemVariables()"); - case TimingStatsElements::PARSE_SYSVAR_NOCHANGE: return F("parseSystemVariables() No change"); - case TimingStatsElements::HANDLE_SERVING_WEBPAGE: return F("handle webpage"); - case TimingStatsElements::HANDLE_SERVING_WEBPAGE_JSON: return F("handle webpage JSON"); - case TimingStatsElements::WIFI_SCAN_ASYNC: return F("WiFi Scan Async"); - case TimingStatsElements::WIFI_SCAN_SYNC: return F("WiFi Scan Sync (blocking)"); - case TimingStatsElements::NTP_SUCCESS: return F("NTP Success"); - case TimingStatsElements::NTP_FAIL: return F("NTP Fail"); - case TimingStatsElements::SYSTIME_UPDATED: return F("Systime Set"); - case TimingStatsElements::C018_AIR_TIME: return F("C018 LoRa TTN - Air Time"); -#ifdef LIMIT_BUILD_SIZE - default: break; -#else - // Include all elements of the enum, to allow the compiler to check if we missed some - case TimingStatsElements::C001_DELAY_QUEUE: - case TimingStatsElements::C002_DELAY_QUEUE: - case TimingStatsElements::C003_DELAY_QUEUE: - case TimingStatsElements::C004_DELAY_QUEUE: - case TimingStatsElements::C005_DELAY_QUEUE: - case TimingStatsElements::C006_DELAY_QUEUE: - case TimingStatsElements::C007_DELAY_QUEUE: - case TimingStatsElements::C008_DELAY_QUEUE: - case TimingStatsElements::C009_DELAY_QUEUE: - case TimingStatsElements::C010_DELAY_QUEUE: - case TimingStatsElements::C011_DELAY_QUEUE: - case TimingStatsElements::C012_DELAY_QUEUE: - case TimingStatsElements::C013_DELAY_QUEUE: - case TimingStatsElements::C014_DELAY_QUEUE: - case TimingStatsElements::C015_DELAY_QUEUE: - case TimingStatsElements::C016_DELAY_QUEUE: - case TimingStatsElements::C017_DELAY_QUEUE: - case TimingStatsElements::C018_DELAY_QUEUE: - case TimingStatsElements::C019_DELAY_QUEUE: - case TimingStatsElements::C020_DELAY_QUEUE: - case TimingStatsElements::C021_DELAY_QUEUE: - case TimingStatsElements::C022_DELAY_QUEUE: - case TimingStatsElements::C023_DELAY_QUEUE: - case TimingStatsElements::C024_DELAY_QUEUE: - case TimingStatsElements::C025_DELAY_QUEUE: - break; - -#endif - } - return F("Unknown"); -} - -String getMiscStatsName(TimingStatsElements stat) { - if ((stat >= TimingStatsElements::C001_DELAY_QUEUE) && - (stat <= TimingStatsElements::C025_DELAY_QUEUE)) { - return concat( - F("Delay queue "), - get_formatted_Controller_number(static_cast(static_cast(stat) - static_cast(TimingStatsElements::C001_DELAY_QUEUE) + 1))); - } - return getMiscStatsName_F(static_cast(stat)); -} - -void stopTimerTask(deviceIndex_t T, int F, uint32_t statisticsTimerStart) -{ - if (mustLogFunction(F)) { pluginStats[static_cast(T.value) * 256 + (F)].add(usecPassedSince_fast(statisticsTimerStart)); } -} - -void stopTimerController(protocolIndex_t T, CPlugin::Function F, uint32_t statisticsTimerStart) -{ - if (mustLogCFunction(F)) { controllerStats[static_cast(T) * 256 + static_cast(F)].add(usecPassedSince_fast(statisticsTimerStart)); } -} - -void stopTimer(TimingStatsElements L, uint32_t statisticsTimerStart) -{ - if (Settings.EnableTimingStats()) { miscStats[L].add(usecPassedSince_fast(statisticsTimerStart)); } -} - -void addMiscTimerStat(TimingStatsElements L, int32_t T) -{ - if (Settings.EnableTimingStats()) { miscStats[L].add(T); } -} - -#endif // if FEATURE_TIMING_STATS +#include "../DataStructs/TimingStats.h" + +#if FEATURE_TIMING_STATS + +# include "../DataTypes/ESPEasy_plugin_functions.h" +# include "../Globals/CPlugins.h" +# include "../Helpers/_CPlugin_Helper.h" +# include "../Helpers/StringConverter.h" + +std::map pluginStats; +std::map controllerStats; +std::map miscStats; +unsigned long timingstats_last_reset(0); + + +void TimingStats::add(int32_t duration_usec) { + // Max duration in usec is roughly 35 minutes. + // For timing stats more than enough + if (duration_usec < 0) return; + _timeTotal += static_cast(duration_usec); + ++_count; + + if (static_cast(duration_usec) > _maxVal) { _maxVal = duration_usec; } + + if (static_cast(duration_usec) < _minVal) { _minVal = duration_usec; } +} + +void TimingStats::reset() { + _timeTotal = 0u; + _count = 0u; + _maxVal = 0u; + _minVal = 4294967295u; +} + +bool TimingStats::isEmpty() const { + return _count == 0u; +} + +float TimingStats::getAvg() const { + if (_count == 0) { return 0.0f; } + return static_cast(_timeTotal) / static_cast(_count); +} + +uint32_t TimingStats::getMinMax(uint32_t& minVal, uint32_t& maxVal) const { + minVal = _minVal; + maxVal = _maxVal; + return _count; +} + +bool TimingStats::thresholdExceeded(const uint32_t& threshold) const { + if (_count == 0) { + return false; + } + return _maxVal > threshold; +} + +/********************************************************************************************\ + Functions used for displaying timing stats + \*********************************************************************************************/ +const __FlashStringHelper* getPluginFunctionName(int function) { + switch (function) { + case PLUGIN_INIT_ALL: return F("INIT_ALL"); + case PLUGIN_INIT: return F("INIT"); + case PLUGIN_READ: return F("READ"); + case PLUGIN_ONCE_A_SECOND: return F("ONCE_A_SECOND"); + case PLUGIN_TEN_PER_SECOND: return F("TEN_PER_SECOND"); + case PLUGIN_DEVICE_ADD: return F("DEVICE_ADD"); + case PLUGIN_EVENTLIST_ADD: return F("EVENTLIST_ADD"); + case PLUGIN_WEBFORM_SAVE: return F("WEBFORM_SAVE"); + case PLUGIN_WEBFORM_LOAD: return F("WEBFORM_LOAD"); + case PLUGIN_WEBFORM_SHOW_VALUES: return F("WEBFORM_SHOW_VALUES"); + case PLUGIN_FORMAT_USERVAR: return F("FORMAT_USERVAR"); + case PLUGIN_GET_DEVICENAME: return F("GET_DEVICENAME"); + case PLUGIN_GET_DEVICEVALUENAMES: return F("GET_DEVICEVALUENAMES"); + case PLUGIN_GET_DEVICEVALUECOUNT: return F("GET_DEVICEVALUECOUNT"); + case PLUGIN_GET_DEVICEVTYPE: return F("GET_DEVICEVTYPE"); + case PLUGIN_WRITE: return F("WRITE"); + case PLUGIN_WEBFORM_SHOW_CONFIG: return F("WEBFORM_SHOW_CONFIG"); + #if FEATURE_PLUGIN_STATS + case PLUGIN_WEBFORM_LOAD_SHOW_STATS: return F("WEBFORM_LOAD_SHOW_STATS"); + #endif + case PLUGIN_SERIAL_IN: return F("SERIAL_IN"); + case PLUGIN_UDP_IN: return F("UDP_IN"); + case PLUGIN_CLOCK_IN: return F("CLOCK_IN"); + case PLUGIN_TASKTIMER_IN: return F("TASKTIMER_IN"); + case PLUGIN_FIFTY_PER_SECOND: return F("FIFTY_PER_SECOND"); + case PLUGIN_SET_CONFIG: return F("SET_CONFIG"); + case PLUGIN_GET_DEVICEGPIONAMES: return F("GET_DEVICEGPIONAMES"); + case PLUGIN_EXIT: return F("EXIT"); + case PLUGIN_GET_CONFIG_VALUE: return F("GET_CONFIG"); +// case PLUGIN_UNCONDITIONAL_POLL: return F("UNCONDITIONAL_POLL"); + case PLUGIN_REQUEST: return F("REQUEST"); + case PLUGIN_PROCESS_CONTROLLER_DATA: return F("PROCESS_CONTROLLER_DATA"); + case PLUGIN_I2C_GET_ADDRESS: return F("I2C_CHECK_DEVICE"); + case PLUGIN_READ_ERROR_OCCURED: return F("PLUGIN_READ_ERROR_OCCURED"); + } + return F("Unknown"); +} + +bool mustLogFunction(int function) { + if (!Settings.EnableTimingStats()) { return false; } + + switch (function) { +// case PLUGIN_INIT_ALL: return false; +// case PLUGIN_INIT: return false; + case PLUGIN_READ: return true; + case PLUGIN_ONCE_A_SECOND: return true; + case PLUGIN_TEN_PER_SECOND: return true; +// case PLUGIN_DEVICE_ADD: return false; +// case PLUGIN_EVENTLIST_ADD: return false; +// case PLUGIN_WEBFORM_SAVE: return false; +// case PLUGIN_WEBFORM_LOAD: return false; +// case PLUGIN_WEBFORM_SHOW_VALUES: return false; + case PLUGIN_FORMAT_USERVAR: return true; + case PLUGIN_GET_DEVICENAME: return true; +// case PLUGIN_GET_DEVICEVALUENAMES: return false; +// case PLUGIN_GET_DEVICEVALUECOUNT: return true; +// case PLUGIN_GET_DEVICEVTYPE: return true; + case PLUGIN_WRITE: return true; +// case PLUGIN_WEBFORM_SHOW_CONFIG: return false; + case PLUGIN_SERIAL_IN: return true; +// case PLUGIN_UDP_IN: return false; +// case PLUGIN_CLOCK_IN: return false; + case PLUGIN_TASKTIMER_IN: return true; + case PLUGIN_FIFTY_PER_SECOND: return true; +// case PLUGIN_SET_CONFIG: return false; +// case PLUGIN_GET_DEVICEGPIONAMES: return false; +// case PLUGIN_EXIT: return false; +// case PLUGIN_GET_CONFIG_VALUE: return false; +// case PLUGIN_UNCONDITIONAL_POLL: return false; + case PLUGIN_REQUEST: return true; + case PLUGIN_I2C_GET_ADDRESS: return true; + case PLUGIN_PROCESS_CONTROLLER_DATA: return true; + case PLUGIN_READ_ERROR_OCCURED: return true; + } + return false; +} + +const __FlashStringHelper* getCPluginCFunctionName(CPlugin::Function function) { + switch (function) { + case CPlugin::Function::CPLUGIN_PROTOCOL_ADD: return F("CPLUGIN_PROTOCOL_ADD"); + case CPlugin::Function::CPLUGIN_CONNECT_SUCCESS: return F("CPLUGIN_CONNECT_SUCCESS"); + case CPlugin::Function::CPLUGIN_CONNECT_FAIL: return F("CPLUGIN_CONNECT_FAIL"); + case CPlugin::Function::CPLUGIN_PROTOCOL_TEMPLATE: return F("CPLUGIN_PROTOCOL_TEMPLATE"); + case CPlugin::Function::CPLUGIN_PROTOCOL_SEND: return F("CPLUGIN_PROTOCOL_SEND"); + case CPlugin::Function::CPLUGIN_PROTOCOL_RECV: return F("CPLUGIN_PROTOCOL_RECV"); + case CPlugin::Function::CPLUGIN_GET_DEVICENAME: return F("CPLUGIN_GET_DEVICENAME"); + case CPlugin::Function::CPLUGIN_WEBFORM_SAVE: return F("CPLUGIN_WEBFORM_SAVE"); + case CPlugin::Function::CPLUGIN_WEBFORM_LOAD: return F("CPLUGIN_WEBFORM_LOAD"); + case CPlugin::Function::CPLUGIN_GET_PROTOCOL_DISPLAY_NAME: return F("CPLUGIN_GET_PROTOCOL_DISPLAY_NAME"); + case CPlugin::Function::CPLUGIN_TASK_CHANGE_NOTIFICATION: return F("CPLUGIN_TASK_CHANGE_NOTIFICATION"); + case CPlugin::Function::CPLUGIN_INIT: return F("CPLUGIN_INIT"); + case CPlugin::Function::CPLUGIN_UDP_IN: return F("CPLUGIN_UDP_IN"); + case CPlugin::Function::CPLUGIN_FLUSH: return F("CPLUGIN_FLUSH"); + case CPlugin::Function::CPLUGIN_TEN_PER_SECOND: return F("CPLUGIN_TEN_PER_SECOND"); + case CPlugin::Function::CPLUGIN_FIFTY_PER_SECOND: return F("CPLUGIN_FIFTY_PER_SECOND"); + case CPlugin::Function::CPLUGIN_INIT_ALL: return F("CPLUGIN_INIT_ALL"); + case CPlugin::Function::CPLUGIN_EXIT: return F("CPLUGIN_EXIT"); + case CPlugin::Function::CPLUGIN_WRITE: return F("CPLUGIN_WRITE"); + + case CPlugin::Function::CPLUGIN_GOT_CONNECTED: + case CPlugin::Function::CPLUGIN_GOT_INVALID: + case CPlugin::Function::CPLUGIN_INTERVAL: + case CPlugin::Function::CPLUGIN_ACKNOWLEDGE: + case CPlugin::Function::CPLUGIN_WEBFORM_SHOW_HOST_CONFIG: + break; + } + return F("Unknown"); +} + +bool mustLogCFunction(CPlugin::Function function) { + if (!Settings.EnableTimingStats()) { return false; } + + switch (function) { + case CPlugin::Function::CPLUGIN_PROTOCOL_ADD: return false; + case CPlugin::Function::CPLUGIN_CONNECT_SUCCESS: return true; + case CPlugin::Function::CPLUGIN_CONNECT_FAIL: return true; + case CPlugin::Function::CPLUGIN_PROTOCOL_TEMPLATE: return false; + case CPlugin::Function::CPLUGIN_PROTOCOL_SEND: return true; + case CPlugin::Function::CPLUGIN_PROTOCOL_RECV: return true; + case CPlugin::Function::CPLUGIN_GET_DEVICENAME: return false; + case CPlugin::Function::CPLUGIN_WEBFORM_SAVE: return false; + case CPlugin::Function::CPLUGIN_WEBFORM_LOAD: return false; + case CPlugin::Function::CPLUGIN_GET_PROTOCOL_DISPLAY_NAME: return false; + case CPlugin::Function::CPLUGIN_TASK_CHANGE_NOTIFICATION: return false; + case CPlugin::Function::CPLUGIN_INIT: return false; + case CPlugin::Function::CPLUGIN_UDP_IN: return true; + case CPlugin::Function::CPLUGIN_FLUSH: return false; + case CPlugin::Function::CPLUGIN_TEN_PER_SECOND: return true; + case CPlugin::Function::CPLUGIN_FIFTY_PER_SECOND: return true; + case CPlugin::Function::CPLUGIN_INIT_ALL: return false; + case CPlugin::Function::CPLUGIN_EXIT: return false; + case CPlugin::Function::CPLUGIN_WRITE: return true; + + case CPlugin::Function::CPLUGIN_GOT_CONNECTED: + case CPlugin::Function::CPLUGIN_GOT_INVALID: + case CPlugin::Function::CPLUGIN_INTERVAL: + case CPlugin::Function::CPLUGIN_ACKNOWLEDGE: + case CPlugin::Function::CPLUGIN_WEBFORM_SHOW_HOST_CONFIG: + break; + } + return false; +} + +// Return flash string type to reduce bin size +const __FlashStringHelper* getMiscStatsName_F(TimingStatsElements stat) { + switch (stat) { + case TimingStatsElements::LOADFILE_STATS: return F("Load File"); + case TimingStatsElements::SAVEFILE_STATS: return F("Save File"); + case TimingStatsElements::LOOP_STATS: return F("Loop"); + case TimingStatsElements::PLUGIN_CALL_50PS: return F("Plugin call 50 p/s"); + case TimingStatsElements::PLUGIN_CALL_10PS: return F("Plugin call 10 p/s"); + case TimingStatsElements::PLUGIN_CALL_10PSU: return F("Plugin call 10 p/s U"); + case TimingStatsElements::PLUGIN_CALL_1PS: return F("Plugin call 1 p/s"); + case TimingStatsElements::CPLUGIN_CALL_50PS: return F("CPlugin call 50 p/s"); + case TimingStatsElements::CPLUGIN_CALL_10PS: return F("CPlugin call 10 p/s"); + case TimingStatsElements::SENSOR_SEND_TASK: return F("SensorSendTask()"); + case TimingStatsElements::COMMAND_EXEC_INTERNAL: return F("Exec Internal Command"); + case TimingStatsElements::COMMAND_DECODE_INTERNAL: return F("Decode Internal Command"); + case TimingStatsElements::CONSOLE_LOOP: return F("Console loop()"); + case TimingStatsElements::CONSOLE_WRITE_SERIAL: return F("Console out"); + case TimingStatsElements::SEND_DATA_STATS: return F("sendData()"); + case TimingStatsElements::COMPUTE_FORMULA_STATS: return F("Compute formula"); + case TimingStatsElements::COMPUTE_STATS: return F("Compute()"); + case TimingStatsElements::PLUGIN_CALL_DEVICETIMER_IN: return F("PLUGIN_DEVICETIMER_IN"); + case TimingStatsElements::SET_NEW_TIMER: return F("setNewTimerAt()"); + case TimingStatsElements::MQTT_DELAY_QUEUE: return F("Delay queue MQTT"); + case TimingStatsElements::TRY_CONNECT_HOST_TCP: return F("try_connect_host() (TCP)"); + case TimingStatsElements::TRY_CONNECT_HOST_UDP: return F("try_connect_host() (UDP)"); + case TimingStatsElements::HOST_BY_NAME_STATS: return F("hostByName()"); + case TimingStatsElements::CONNECT_CLIENT_STATS: return F("connectClient()"); + case TimingStatsElements::LOAD_CUSTOM_TASK_STATS: return F("LoadCustomTaskSettings()"); + case TimingStatsElements::WIFI_ISCONNECTED_STATS: return F("WiFi.isConnected()"); + case TimingStatsElements::WIFI_NOTCONNECTED_STATS: return F("WiFi.isConnected() (fail)"); + case TimingStatsElements::LOAD_TASK_SETTINGS: return F("LoadTaskSettings()"); + case TimingStatsElements::SAVE_TASK_SETTINGS: return F("SaveTaskSettings()"); + case TimingStatsElements::LOAD_CONTROLLER_SETTINGS: return F("LoadControllerSettings()"); + #ifdef ESP32 + case TimingStatsElements::LOAD_CONTROLLER_SETTINGS_C: return F("LoadControllerSettings() (cached)"); + #endif + case TimingStatsElements::SAVE_CONTROLLER_SETTINGS: return F("SaveControllerSettings()"); + case TimingStatsElements::TRY_OPEN_FILE: return F("TryOpenFile()"); + case TimingStatsElements::FS_GC_SUCCESS: return F("ESPEASY_FS GC success"); + case TimingStatsElements::FS_GC_FAIL: return F("ESPEASY_FS GC fail"); + case TimingStatsElements::RULES_PROCESSING: return F("rulesProcessing()"); + case TimingStatsElements::RULES_PARSE_LINE: return F("parseCompleteNonCommentLine()"); + case TimingStatsElements::RULES_PROCESS_MATCHED: return F("processMatchedRule()"); + case TimingStatsElements::RULES_MATCH: return F("rulesMatch()"); + case TimingStatsElements::GRAT_ARP_STATS: return F("sendGratuitousARP()"); + case TimingStatsElements::SAVE_TO_RTC: return F("saveToRTC()"); + case TimingStatsElements::BACKGROUND_TASKS: return F("backgroundtasks()"); + case TimingStatsElements::UPDATE_RTTTL: return F("update_rtttl()"); + case TimingStatsElements::CHECK_UDP: return F("checkUDP()"); + case TimingStatsElements::C013_SEND_UDP: return F("C013_sendUDP() SUCCESS"); + case TimingStatsElements::C013_SEND_UDP_FAIL: return F("C013_sendUDP() FAIL"); + case TimingStatsElements::C013_RECEIVE_SENSOR_DATA: return F("C013 Receive sensor data"); + case TimingStatsElements::WEBSERVER_HANDLE_CLIENT: return F("web_server.handleClient()"); + case TimingStatsElements::PROCESS_SYSTEM_EVENT_QUEUE: return F("process_system_event_queue()"); + case TimingStatsElements::FORMAT_USER_VAR: return F("doFormatUserVar()"); + case TimingStatsElements::IS_NUMERICAL: return F("isNumerical()"); + case TimingStatsElements::HANDLE_SCHEDULER_IDLE: return F("handle_schedule() idle"); + case TimingStatsElements::HANDLE_SCHEDULER_TASK: return F("handle_schedule() task"); + case TimingStatsElements::PARSE_TEMPLATE_PADDED: return F("parseTemplate_padded()"); + case TimingStatsElements::PARSE_SYSVAR: return F("parseSystemVariables()"); + case TimingStatsElements::PARSE_SYSVAR_NOCHANGE: return F("parseSystemVariables() No change"); + case TimingStatsElements::HANDLE_SERVING_WEBPAGE: return F("handle webpage"); + case TimingStatsElements::HANDLE_SERVING_WEBPAGE_JSON: return F("handle webpage JSON"); + case TimingStatsElements::WIFI_SCAN_ASYNC: return F("WiFi Scan Async"); + case TimingStatsElements::WIFI_SCAN_SYNC: return F("WiFi Scan Sync (blocking)"); + case TimingStatsElements::NTP_SUCCESS: return F("NTP Success"); + case TimingStatsElements::NTP_FAIL: return F("NTP Fail"); + case TimingStatsElements::SYSTIME_UPDATED: return F("Systime Set"); + case TimingStatsElements::C018_AIR_TIME: return F("C018 LoRa TTN - Air Time"); + +#ifdef USES_ESPEASY_NOW + case TimingStatsElements::HANDLE_ESPEASY_NOW_LOOP: return F(ESPEASY_NOW_NAME " handle received message"); + case TimingStatsElements::EXPIRED_ESPEASY_NOW_LOOP: return F(ESPEASY_NOW_NAME " incomplete expired"); + case TimingStatsElements::INVALID_ESPEASY_NOW_LOOP: return F(ESPEASY_NOW_NAME " incomplete invalid"); + case TimingStatsElements::RECEIVE_ESPEASY_NOW_LOOP: return F(ESPEASY_NOW_NAME " onReceive()"); + case TimingStatsElements::ESPEASY_NOW_SEND_MSG_SUC: return F(ESPEASY_NOW_NAME " send Message Success"); + case TimingStatsElements::ESPEASY_NOW_SEND_MSG_FAIL: return F(ESPEASY_NOW_NAME " send Message Fail"); + case TimingStatsElements::ESPEASY_NOW_SEND_PCKT: return F(ESPEASY_NOW_NAME " send Packet"); + case TimingStatsElements::ESPEASY_NOW_DEDUP_LOOP: return F(ESPEASY_NOW_NAME " DuplicateCheck loop"); +#endif + +#ifdef LIMIT_BUILD_SIZE + default: break; +#else + // Include all elements of the enum, to allow the compiler to check if we missed some + case TimingStatsElements::C001_DELAY_QUEUE: + case TimingStatsElements::C002_DELAY_QUEUE: + case TimingStatsElements::C003_DELAY_QUEUE: + case TimingStatsElements::C004_DELAY_QUEUE: + case TimingStatsElements::C005_DELAY_QUEUE: + case TimingStatsElements::C006_DELAY_QUEUE: + case TimingStatsElements::C007_DELAY_QUEUE: + case TimingStatsElements::C008_DELAY_QUEUE: + case TimingStatsElements::C009_DELAY_QUEUE: + case TimingStatsElements::C010_DELAY_QUEUE: + case TimingStatsElements::C011_DELAY_QUEUE: + case TimingStatsElements::C012_DELAY_QUEUE: + case TimingStatsElements::C013_DELAY_QUEUE: + case TimingStatsElements::C014_DELAY_QUEUE: + case TimingStatsElements::C015_DELAY_QUEUE: + case TimingStatsElements::C016_DELAY_QUEUE: + case TimingStatsElements::C017_DELAY_QUEUE: + case TimingStatsElements::C018_DELAY_QUEUE: + case TimingStatsElements::C019_DELAY_QUEUE: + case TimingStatsElements::C020_DELAY_QUEUE: + case TimingStatsElements::C021_DELAY_QUEUE: + case TimingStatsElements::C022_DELAY_QUEUE: + case TimingStatsElements::C023_DELAY_QUEUE: + case TimingStatsElements::C024_DELAY_QUEUE: + case TimingStatsElements::C025_DELAY_QUEUE: + break; + +#endif + } + return F("Unknown"); +} + +String getMiscStatsName(TimingStatsElements stat) { + if ((stat >= TimingStatsElements::C001_DELAY_QUEUE) && + (stat <= TimingStatsElements::C025_DELAY_QUEUE)) { + return concat( + F("Delay queue "), + get_formatted_Controller_number(static_cast(static_cast(stat) - static_cast(TimingStatsElements::C001_DELAY_QUEUE) + 1))); + } + return getMiscStatsName_F(static_cast(stat)); +} + +void stopTimerTask(deviceIndex_t T, int F, uint32_t statisticsTimerStart) +{ + if (mustLogFunction(F)) { pluginStats[static_cast(T.value) * 256 + (F)].add(usecPassedSince_fast(statisticsTimerStart)); } +} + +void stopTimerController(protocolIndex_t T, CPlugin::Function F, uint32_t statisticsTimerStart) +{ + if (mustLogCFunction(F)) { controllerStats[static_cast(T) * 256 + static_cast(F)].add(usecPassedSince_fast(statisticsTimerStart)); } +} + +void stopTimer(TimingStatsElements L, uint32_t statisticsTimerStart) +{ + if (Settings.EnableTimingStats()) { miscStats[L].add(usecPassedSince_fast(statisticsTimerStart)); } +} + +void addMiscTimerStat(TimingStatsElements L, int32_t T) +{ + if (Settings.EnableTimingStats()) { miscStats[L].add(T); } +} + +#endif // if FEATURE_TIMING_STATS diff --git a/src/src/DataStructs/TimingStats.h b/src/src/DataStructs/TimingStats.h index 5d70d5dd5d..86de2a7a3c 100644 --- a/src/src/DataStructs/TimingStats.h +++ b/src/src/DataStructs/TimingStats.h @@ -1,213 +1,225 @@ -#ifndef DATASTRUCTS_TIMINGSTATS_H -#define DATASTRUCTS_TIMINGSTATS_H - -#include "../../ESPEasy_common.h" - -#if FEATURE_TIMING_STATS - -# include "../DataTypes/DeviceIndex.h" -# include "../DataTypes/ESPEasy_plugin_functions.h" -# include "../DataTypes/ProtocolIndex.h" -# include "../Globals/Settings.h" -# include "../Helpers/ESPEasy_time_calc.h" - -# include -#endif // if FEATURE_TIMING_STATS - - -/*********************************************************************************************\ -* TimingStats -\*********************************************************************************************/ - -// These TimingStatsElements must not be excluded when FEATURE_TIMING_STATS is not defined. -// The Cxxx_DELAY_QUEUE defines are used in the macros to process the controller queues. -enum class TimingStatsElements { - - // Controller queue - MQTT_DELAY_QUEUE, - - // Do not interrupt this sequence of Cxxx_DELAY_QUEUE - // as its order is used to generate MiscStatsName - C001_DELAY_QUEUE, - C002_DELAY_QUEUE, - C003_DELAY_QUEUE, - C004_DELAY_QUEUE, - C005_DELAY_QUEUE, - C006_DELAY_QUEUE, - C007_DELAY_QUEUE, - C008_DELAY_QUEUE, - C009_DELAY_QUEUE, - C010_DELAY_QUEUE, - C011_DELAY_QUEUE, - C012_DELAY_QUEUE, - C013_DELAY_QUEUE, - C014_DELAY_QUEUE, - C015_DELAY_QUEUE, - C016_DELAY_QUEUE, - C017_DELAY_QUEUE, - C018_DELAY_QUEUE, - C019_DELAY_QUEUE, - C020_DELAY_QUEUE, - C021_DELAY_QUEUE, - C022_DELAY_QUEUE, - C023_DELAY_QUEUE, - C024_DELAY_QUEUE, - C025_DELAY_QUEUE, - - // Controller specific timing stats - C018_AIR_TIME, - - - // Related to Task runs & sending data + rules - PLUGIN_CALL_50PS, - PLUGIN_CALL_10PS, - PLUGIN_CALL_10PSU, - PLUGIN_CALL_1PS, - CPLUGIN_CALL_10PS, - CPLUGIN_CALL_50PS, - SENSOR_SEND_TASK, - SEND_DATA_STATS, - COMPUTE_FORMULA_STATS, - COMPUTE_STATS, - PARSE_SYSVAR, - PARSE_SYSVAR_NOCHANGE, - PARSE_TEMPLATE_PADDED, - IS_NUMERICAL, - FORMAT_USER_VAR, - PROCESS_SYSTEM_EVENT_QUEUE, - RULES_MATCH, - RULES_PROCESSING, - RULES_PROCESS_MATCHED, - RULES_PARSE_LINE, - COMMAND_EXEC_INTERNAL, - COMMAND_DECODE_INTERNAL, - CONSOLE_LOOP, - CONSOLE_WRITE_SERIAL, - - // Related to file access - LOADFILE_STATS, - LOAD_TASK_SETTINGS, - LOAD_CUSTOM_TASK_STATS, - LOAD_CONTROLLER_SETTINGS, - #ifdef ESP32 - LOAD_CONTROLLER_SETTINGS_C, - #endif - SAVEFILE_STATS, - SAVE_TASK_SETTINGS, - SAVE_CONTROLLER_SETTINGS, - TRY_OPEN_FILE, - FS_GC_SUCCESS, - FS_GC_FAIL, - - // Scheduler related - SAVE_TO_RTC, - PLUGIN_CALL_DEVICETIMER_IN, - SET_NEW_TIMER, - HANDLE_SCHEDULER_TASK, - HANDLE_SCHEDULER_IDLE, - BACKGROUND_TASKS, - CHECK_UDP, - C013_SEND_UDP, - C013_SEND_UDP_FAIL, - C013_RECEIVE_SENSOR_DATA, - WEBSERVER_HANDLE_CLIENT, - UPDATE_RTTTL, - - // Web serving - HANDLE_SERVING_WEBPAGE, - HANDLE_SERVING_WEBPAGE_JSON, - - // Network related - TRY_CONNECT_HOST_TCP, - TRY_CONNECT_HOST_UDP, - HOST_BY_NAME_STATS, - GRAT_ARP_STATS, - WIFI_ISCONNECTED_STATS, - WIFI_NOTCONNECTED_STATS, - CONNECT_CLIENT_STATS, - WIFI_SCAN_ASYNC, - WIFI_SCAN_SYNC, - - // Time sync (also network related) - NTP_SUCCESS, - NTP_FAIL, - SYSTIME_UPDATED, - - // Close to the lifetime stats shown on the timing stats page - LOOP_STATS -}; - -#if FEATURE_TIMING_STATS - -class TimingStats { -public: - - TimingStats() = default; - - void add(int32_t duration_usec); - void reset(); - bool isEmpty() const; - float getAvg() const; - uint32_t getMinMax(uint32_t& minVal, - uint32_t& maxVal) const; - bool thresholdExceeded(const uint32_t& threshold) const; - -private: - - uint64_t _timeTotal{}; - uint32_t _count{}; - uint32_t _maxVal{}; - uint32_t _minVal = 4294967295; -}; - - -const __FlashStringHelper* getPluginFunctionName(int function); -bool mustLogFunction(int function); -const __FlashStringHelper* getCPluginCFunctionName(CPlugin::Function function); -bool mustLogCFunction(CPlugin::Function function); -String getMiscStatsName(TimingStatsElements stat); - -void stopTimerTask(deviceIndex_t T, - int F, - uint32_t statisticsTimerStart); -void stopTimerController(protocolIndex_t T, - CPlugin::Function F, - uint32_t statisticsTimerStart); -void stopTimer(TimingStatsElements L, - uint32_t statisticsTimerStart); -void addMiscTimerStat(TimingStatsElements L, - int32_t T); - -extern std::map pluginStats; -extern std::map controllerStats; -extern std::map miscStats; -extern unsigned long timingstats_last_reset; - -# define START_TIMER const uint32_t statisticsTimerStart(micros()); -# define STOP_TIMER_TASK(T, F) stopTimerTask(T, F, statisticsTimerStart); -# define STOP_TIMER_CONTROLLER(T, F) stopTimerController(T, F, statisticsTimerStart); - -// #define STOP_TIMER_LOADFILE miscStats[LOADFILE_STATS].add(usecPassedSince_fast(statisticsTimerStart)); -# define STOP_TIMER(L) stopTimer(TimingStatsElements::L, statisticsTimerStart); -# define STOP_TIMER_VAR(L) stopTimer(L, statisticsTimerStart); - -// Add a timer statistic value in usec. -# define ADD_TIMER_STAT(L, T) addMiscTimerStat(TimingStatsElements::L, T); - -#else // if FEATURE_TIMING_STATS - -# define START_TIMER ; -# define STOP_TIMER_TASK(T, F) ; -# define STOP_TIMER_CONTROLLER(T, F) ; -# define STOP_TIMER(L) ; -# define ADD_TIMER_STAT(L, T) ; - - -// FIXME TD-er: This class is used as a parameter in functions defined in .ino files. -// The Arduino build process tries to forward declare all functions it can find, regardless of defines. -// Meaning we must make sure the forward declaration of the TimingStats class is made, since it is used as an argument in some function. -class TimingStats; - -#endif // if FEATURE_TIMING_STATS - -#endif // DATASTRUCTS_TIMINGSTATS_H +#ifndef DATASTRUCTS_TIMINGSTATS_H +#define DATASTRUCTS_TIMINGSTATS_H + +#include "../../ESPEasy_common.h" + +#if FEATURE_TIMING_STATS + +# include "../DataTypes/DeviceIndex.h" +# include "../DataTypes/ESPEasy_plugin_functions.h" +# include "../DataTypes/ProtocolIndex.h" +# include "../Globals/Settings.h" +# include "../Helpers/ESPEasy_time_calc.h" + +# include +#endif // if FEATURE_TIMING_STATS + + +/*********************************************************************************************\ +* TimingStats +\*********************************************************************************************/ + +// These TimingStatsElements must not be excluded when FEATURE_TIMING_STATS is not defined. +// The Cxxx_DELAY_QUEUE defines are used in the macros to process the controller queues. +enum class TimingStatsElements { + + // Controller queue + MQTT_DELAY_QUEUE, + + // Do not interrupt this sequence of Cxxx_DELAY_QUEUE + // as its order is used to generate MiscStatsName + C001_DELAY_QUEUE, + C002_DELAY_QUEUE, + C003_DELAY_QUEUE, + C004_DELAY_QUEUE, + C005_DELAY_QUEUE, + C006_DELAY_QUEUE, + C007_DELAY_QUEUE, + C008_DELAY_QUEUE, + C009_DELAY_QUEUE, + C010_DELAY_QUEUE, + C011_DELAY_QUEUE, + C012_DELAY_QUEUE, + C013_DELAY_QUEUE, + C014_DELAY_QUEUE, + C015_DELAY_QUEUE, + C016_DELAY_QUEUE, + C017_DELAY_QUEUE, + C018_DELAY_QUEUE, + C019_DELAY_QUEUE, + C020_DELAY_QUEUE, + C021_DELAY_QUEUE, + C022_DELAY_QUEUE, + C023_DELAY_QUEUE, + C024_DELAY_QUEUE, + C025_DELAY_QUEUE, + + // Controller specific timing stats + C018_AIR_TIME, + + + // Related to Task runs & sending data + rules + PLUGIN_CALL_50PS, + PLUGIN_CALL_10PS, + PLUGIN_CALL_10PSU, + PLUGIN_CALL_1PS, + CPLUGIN_CALL_10PS, + CPLUGIN_CALL_50PS, + SENSOR_SEND_TASK, + SEND_DATA_STATS, + COMPUTE_FORMULA_STATS, + COMPUTE_STATS, + PARSE_SYSVAR, + PARSE_SYSVAR_NOCHANGE, + PARSE_TEMPLATE_PADDED, + IS_NUMERICAL, + FORMAT_USER_VAR, + PROCESS_SYSTEM_EVENT_QUEUE, + RULES_MATCH, + RULES_PROCESSING, + RULES_PROCESS_MATCHED, + RULES_PARSE_LINE, + COMMAND_EXEC_INTERNAL, + COMMAND_DECODE_INTERNAL, + CONSOLE_LOOP, + CONSOLE_WRITE_SERIAL, + + // Related to file access + LOADFILE_STATS, + LOAD_TASK_SETTINGS, + LOAD_CUSTOM_TASK_STATS, + LOAD_CONTROLLER_SETTINGS, + #ifdef ESP32 + LOAD_CONTROLLER_SETTINGS_C, + #endif + SAVEFILE_STATS, + SAVE_TASK_SETTINGS, + SAVE_CONTROLLER_SETTINGS, + TRY_OPEN_FILE, + FS_GC_SUCCESS, + FS_GC_FAIL, + + // Scheduler related + SAVE_TO_RTC, + PLUGIN_CALL_DEVICETIMER_IN, + SET_NEW_TIMER, + HANDLE_SCHEDULER_TASK, + HANDLE_SCHEDULER_IDLE, + BACKGROUND_TASKS, + CHECK_UDP, + C013_SEND_UDP, + C013_SEND_UDP_FAIL, + C013_RECEIVE_SENSOR_DATA, + WEBSERVER_HANDLE_CLIENT, + UPDATE_RTTTL, + + // Web serving + HANDLE_SERVING_WEBPAGE, + HANDLE_SERVING_WEBPAGE_JSON, + + // Network related + TRY_CONNECT_HOST_TCP, + TRY_CONNECT_HOST_UDP, + HOST_BY_NAME_STATS, + GRAT_ARP_STATS, + WIFI_ISCONNECTED_STATS, + WIFI_NOTCONNECTED_STATS, + CONNECT_CLIENT_STATS, + WIFI_SCAN_ASYNC, + WIFI_SCAN_SYNC, + + // ESPEasy_NOW related + #ifdef USES_ESPEASY_NOW + HANDLE_ESPEASY_NOW_LOOP , + EXPIRED_ESPEASY_NOW_LOOP , + INVALID_ESPEASY_NOW_LOOP , + RECEIVE_ESPEASY_NOW_LOOP , + ESPEASY_NOW_SEND_MSG_SUC , + ESPEASY_NOW_SEND_MSG_FAIL , + ESPEASY_NOW_SEND_PCKT , + ESPEASY_NOW_DEDUP_LOOP , + #endif + + // Time sync (also network related) + NTP_SUCCESS, + NTP_FAIL, + SYSTIME_UPDATED, + + // Close to the lifetime stats shown on the timing stats page + LOOP_STATS +}; + +#if FEATURE_TIMING_STATS + +class TimingStats { +public: + + TimingStats() = default; + + void add(int32_t duration_usec); + void reset(); + bool isEmpty() const; + float getAvg() const; + uint32_t getMinMax(uint32_t& minVal, + uint32_t& maxVal) const; + bool thresholdExceeded(const uint32_t& threshold) const; + +private: + + uint64_t _timeTotal{}; + uint32_t _count{}; + uint32_t _maxVal{}; + uint32_t _minVal = 4294967295; +}; + + +const __FlashStringHelper* getPluginFunctionName(int function); +bool mustLogFunction(int function); +const __FlashStringHelper* getCPluginCFunctionName(CPlugin::Function function); +bool mustLogCFunction(CPlugin::Function function); +String getMiscStatsName(TimingStatsElements stat); + +void stopTimerTask(deviceIndex_t T, + int F, + uint32_t statisticsTimerStart); +void stopTimerController(protocolIndex_t T, + CPlugin::Function F, + uint32_t statisticsTimerStart); +void stopTimer(TimingStatsElements L, + uint32_t statisticsTimerStart); +void addMiscTimerStat(TimingStatsElements L, + int32_t T); + +extern std::map pluginStats; +extern std::map controllerStats; +extern std::map miscStats; +extern unsigned long timingstats_last_reset; + +# define START_TIMER const uint32_t statisticsTimerStart(micros()); +# define STOP_TIMER_TASK(T, F) stopTimerTask(T, F, statisticsTimerStart); +# define STOP_TIMER_CONTROLLER(T, F) stopTimerController(T, F, statisticsTimerStart); + +// #define STOP_TIMER_LOADFILE miscStats[LOADFILE_STATS].add(usecPassedSince_fast(statisticsTimerStart)); +# define STOP_TIMER(L) stopTimer(TimingStatsElements::L, statisticsTimerStart); +# define STOP_TIMER_VAR(L) stopTimer(L, statisticsTimerStart); + +// Add a timer statistic value in usec. +# define ADD_TIMER_STAT(L, T) addMiscTimerStat(TimingStatsElements::L, T); + +#else // if FEATURE_TIMING_STATS + +# define START_TIMER ; +# define STOP_TIMER_TASK(T, F) ; +# define STOP_TIMER_CONTROLLER(T, F) ; +# define STOP_TIMER(L) ; +# define ADD_TIMER_STAT(L, T) ; + + +// FIXME TD-er: This class is used as a parameter in functions defined in .ino files. +// The Arduino build process tries to forward declare all functions it can find, regardless of defines. +// Meaning we must make sure the forward declaration of the TimingStats class is made, since it is used as an argument in some function. +class TimingStats; + +#endif // if FEATURE_TIMING_STATS + +#endif // DATASTRUCTS_TIMINGSTATS_H diff --git a/src/src/DataStructs/WiFiEventData.cpp b/src/src/DataStructs/WiFiEventData.cpp index a205c6a0ce..f27d36a28b 100644 --- a/src/src/DataStructs/WiFiEventData.cpp +++ b/src/src/DataStructs/WiFiEventData.cpp @@ -1,292 +1,304 @@ -#include "../DataStructs/WiFiEventData.h" - -#include "../ESPEasyCore/ESPEasy_Log.h" - -#include "../Globals/RTC.h" -#include "../Globals/Settings.h" -#include "../Globals/WiFi_AP_Candidates.h" - -#include "../Helpers/ESPEasy_Storage.h" -#include "../Helpers/Networking.h" - - -#define WIFI_RECONNECT_WAIT 30000 // in milliSeconds - -#define CONNECT_TIMEOUT_MAX 4000 // in milliSeconds - - -#if FEATURE_USE_IPV6 -#include - -// ----------------------------------------------------------------------------------------------------------------------- -// ---------------------------------------------------- Private functions ------------------------------------------------ -// ----------------------------------------------------------------------------------------------------------------------- - -esp_netif_t* get_esp_interface_netif(esp_interface_t interface); -#endif - - - -bool WiFiEventData_t::WiFiConnectAllowed() const { - if (WiFi.status() == WL_IDLE_STATUS) { - // FIXME TD-er: What to do now? Set a timer? - //return false; - if (last_wifi_connect_attempt_moment.isSet() && - !last_wifi_connect_attempt_moment.timeoutReached(WIFI_PROCESS_EVENTS_TIMEOUT)) { - return false; - } - } - if (!wifiConnectAttemptNeeded) return false; - if (intent_to_reboot) return false; - if (wifiSetupConnect) return true; - if (wifiConnectInProgress) { - if (last_wifi_connect_attempt_moment.isSet() && - !last_wifi_connect_attempt_moment.timeoutReached(WIFI_PROCESS_EVENTS_TIMEOUT)) { - return false; - } - } - if (lastDisconnectMoment.isSet()) { - // TODO TD-er: Make this time more dynamic. - if (!lastDisconnectMoment.timeoutReached(1000)) { - return false; - } - } - return true; -} - -bool WiFiEventData_t::unprocessedWifiEvents() const { - if (processedConnect && processedDisconnect && processedGotIP && processedDHCPTimeout -#if FEATURE_USE_IPV6 - && processedGotIP6 -#endif - ) - { - return false; - } - if (!processedConnect) { - if (lastConnectMoment.isSet() && lastConnectMoment.timeoutReached(WIFI_PROCESS_EVENTS_TIMEOUT)) { - return false; - } - } - if (!processedGotIP) { - if (lastGetIPmoment.isSet() && lastGetIPmoment.timeoutReached(WIFI_PROCESS_EVENTS_TIMEOUT)) { - return false; - } - } - if (!processedDisconnect) { - if (lastDisconnectMoment.isSet() && lastDisconnectMoment.timeoutReached(WIFI_PROCESS_EVENTS_TIMEOUT)) { - return false; - } - } - if (!processedDHCPTimeout) { - return false; - } - return true; -} - -void WiFiEventData_t::clearAll() { - markWiFiTurnOn(); - lastGetScanMoment.clear(); - last_wifi_connect_attempt_moment.clear(); - timerAPstart.clear(); - - lastWiFiResetMoment.setNow(); - wifi_TX_pwr = 0; - usedChannel = 0; -} - -void WiFiEventData_t::markWiFiTurnOn() { - setWiFiDisconnected(); -// lastDisconnectMoment.clear(); - lastConnectMoment.clear(); - lastGetIPmoment.clear(); - wifi_considered_stable = false; - - clear_processed_flags(); -} - -void WiFiEventData_t::clear_processed_flags() { - // Mark all flags to default to prevent handling old events. - WiFi.scanDelete(); - processedConnect = true; - processedDisconnect = true; - processedGotIP = true; - #if FEATURE_USE_IPV6 - processedGotIP6 = true; - #endif - processedDHCPTimeout = true; - processedConnectAPmode = true; - processedDisconnectAPmode = true; - processedScanDone = true; - wifiConnectAttemptNeeded = true; - wifiConnectInProgress = false; - processingDisconnect.clear(); - dns0_cache = IPAddress(); - dns1_cache = IPAddress(); -} - -void WiFiEventData_t::markWiFiBegin() { - markWiFiTurnOn(); - last_wifi_connect_attempt_moment.setNow(); - wifiConnectInProgress = true; - usedChannel = 0; - ++wifi_connect_attempt; - if (!timerAPstart.isSet()) { - timerAPstart.setMillisFromNow(3 * WIFI_RECONNECT_WAIT); - } -} - - -void WiFiEventData_t::setWiFiDisconnected() { - wifiStatus = ESPEASY_WIFI_DISCONNECTED; - last_wifi_connect_attempt_moment.clear(); - wifiConnectInProgress = false; -#if FEATURE_ESPEASY_P2P - updateUDPport(true); -#endif - -} - -void WiFiEventData_t::setWiFiGotIP() { - bitSet(wifiStatus, ESPEASY_WIFI_GOT_IP); - processedGotIP = true; - if (valid_DNS_address(WiFi.dnsIP(0))) { - dns0_cache = WiFi.dnsIP(0); - } - if (valid_DNS_address(WiFi.dnsIP(1))) { - dns1_cache = WiFi.dnsIP(1); - } -} - -void WiFiEventData_t::setWiFiConnected() { - bitSet(wifiStatus, ESPEASY_WIFI_CONNECTED); - processedConnect = true; -} - -void WiFiEventData_t::setWiFiServicesInitialized() { - if (!unprocessedWifiEvents() && WiFiConnected() && WiFiGotIP()) { - # ifndef BUILD_NO_DEBUG - addLog(LOG_LEVEL_DEBUG, F("WiFi : WiFi services initialized")); - #endif - bitSet(wifiStatus, ESPEASY_WIFI_SERVICES_INITIALIZED); - wifiConnectInProgress = false; - wifiConnectAttemptNeeded = false; - dns0_cache = WiFi.dnsIP(0); - dns1_cache = WiFi.dnsIP(1); - -#if FEATURE_ESPEASY_P2P - updateUDPport(false); -#endif - } -} - -void WiFiEventData_t::markGotIP() { - lastGetIPmoment.setNow(); - - // Create the 'got IP event' so mark the wifiStatus to not have the got IP flag set - // This also implies the services are not fully initialized. - bitClear(wifiStatus, ESPEASY_WIFI_GOT_IP); - bitClear(wifiStatus, ESPEASY_WIFI_SERVICES_INITIALIZED); - processedGotIP = false; -} - -#if FEATURE_USE_IPV6 - void WiFiEventData_t::markGotIPv6(const IPAddress& ip6) { - processedGotIP6 = false; - unprocessed_IP6 = ip6; - } -#endif - - -void WiFiEventData_t::markLostIP() { - bitClear(wifiStatus, ESPEASY_WIFI_GOT_IP); - bitClear(wifiStatus, ESPEASY_WIFI_SERVICES_INITIALIZED); -} - -void WiFiEventData_t::markDisconnect(WiFiDisconnectReason reason) { -/* - #if defined(ESP32) - if ((WiFi.getMode() & WIFI_MODE_STA) == 0) return; - #else // if defined(ESP32) - if ((WiFi.getMode() & WIFI_STA) == 0) return; - #endif // if defined(ESP32) -*/ - lastDisconnectMoment.setNow(); - usedChannel = 0; - - if (last_wifi_connect_attempt_moment.isSet() && !lastConnectMoment.isSet()) { - // There was an unsuccessful connection attempt - lastConnectedDuration_us = last_wifi_connect_attempt_moment.timeDiff(lastDisconnectMoment); - } else { - if (last_wifi_connect_attempt_moment.isSet()) - lastConnectedDuration_us = lastConnectMoment.timeDiff(lastDisconnectMoment); - else - lastConnectedDuration_us = 0; - } - lastDisconnectReason = reason; - processedDisconnect = false; - wifiConnectInProgress = false; -} - -void WiFiEventData_t::markConnected(const String& ssid, const uint8_t bssid[6], uint8_t channel) { - usedChannel = channel; - lastConnectMoment.setNow(); - processedConnect = false; - channel_changed = RTC.lastWiFiChannel != channel; - last_ssid = ssid; - bssid_changed = false; - auth_mode = WiFi_AP_Candidates.getCurrent().enc_type; - - RTC.lastWiFiChannel = channel; - for (uint8_t i = 0; i < 6; ++i) { - if (RTC.lastBSSID[i] != bssid[i]) { - bssid_changed = true; - RTC.lastBSSID[i] = bssid[i]; - } - } -#if FEATURE_USE_IPV6 - if (Settings.EnableIPv6()) { - WiFi.enableIPv6(true); - } -#endif -} - -void WiFiEventData_t::markConnectedAPmode(const uint8_t mac[6]) { - lastMacConnectedAPmode = mac; - processedConnectAPmode = false; -} - -void WiFiEventData_t::markDisconnectedAPmode(const uint8_t mac[6]) { - lastMacDisconnectedAPmode = mac; - processedDisconnectAPmode = false; -} - - - -String WiFiEventData_t::ESPeasyWifiStatusToString() const { - String log; - if (WiFiDisconnected()) { - log = F("DISCONNECTED"); - } else { - if (WiFiConnected()) { - log += F("Conn. "); - } - if (WiFiGotIP()) { - log += F("IP "); - } - if (WiFiServicesInitialized()) { - log += F("Init"); - } - } - return log; -} - - -uint32_t WiFiEventData_t::getSuggestedTimeout(int index, uint32_t minimum_timeout) const { - auto it = connectDurations.find(index); - if (it == connectDurations.end()) { - return 3 * minimum_timeout; - } - const uint32_t res = 3 * it->second; - return constrain(res, minimum_timeout, CONNECT_TIMEOUT_MAX); +#include "../DataStructs/WiFiEventData.h" + +#include "../ESPEasyCore/ESPEasy_Log.h" +#include "../Globals/ESPEasy_now_state.h" +#include "../Globals/RTC.h" +#include "../Globals/Settings.h" +#include "../Globals/WiFi_AP_Candidates.h" + +#include "../Helpers/ESPEasy_Storage.h" +#include "../Helpers/Networking.h" + + +#define WIFI_RECONNECT_WAIT 30000 // in milliSeconds + +#define CONNECT_TIMEOUT_MAX 4000 // in milliSeconds + + +#if FEATURE_USE_IPV6 +#include + +// ----------------------------------------------------------------------------------------------------------------------- +// ---------------------------------------------------- Private functions ------------------------------------------------ +// ----------------------------------------------------------------------------------------------------------------------- + +esp_netif_t* get_esp_interface_netif(esp_interface_t interface); +#endif + + + +bool WiFiEventData_t::WiFiConnectAllowed() const { + if (WiFi.status() == WL_IDLE_STATUS) { + // FIXME TD-er: What to do now? Set a timer? + //return false; + if (last_wifi_connect_attempt_moment.isSet() && + !last_wifi_connect_attempt_moment.timeoutReached(WIFI_PROCESS_EVENTS_TIMEOUT)) { + return false; + } + } + if (!wifiConnectAttemptNeeded) return false; + if (intent_to_reboot) return false; + if (wifiSetupConnect) return true; + if (wifiConnectInProgress) { + if (last_wifi_connect_attempt_moment.isSet() && + !last_wifi_connect_attempt_moment.timeoutReached(WIFI_PROCESS_EVENTS_TIMEOUT)) { + return false; + } + } + if (lastDisconnectMoment.isSet()) { + // TODO TD-er: Make this time more dynamic. + if (!lastDisconnectMoment.timeoutReached(1000)) { + return false; + } + } + return true; +} + +bool WiFiEventData_t::unprocessedWifiEvents() { + if (processedConnect && processedDisconnect && processedGotIP && processedDHCPTimeout +#if FEATURE_USE_IPV6 + && processedGotIP6 +#endif + ) + { + return false; + } + if (!processedConnect) { + if (lastConnectMoment.isSet() && lastConnectMoment.timeoutReached(WIFI_PROCESS_EVENTS_TIMEOUT)) { + processedConnect = true; + } + } + if (!processedGotIP) { + if (lastGetIPmoment.isSet() && lastGetIPmoment.timeoutReached(WIFI_PROCESS_EVENTS_TIMEOUT)) { + processedGotIP = true;; + } + } + if (!processedDisconnect) { + if (lastDisconnectMoment.isSet() && lastDisconnectMoment.timeoutReached(WIFI_PROCESS_EVENTS_TIMEOUT)) { + processedDisconnect = true; + } + } + if (processedConnect && processedDisconnect && processedGotIP && processedDHCPTimeout) + { + return false; + } + + return true; +} + +void WiFiEventData_t::clearAll() { + markWiFiTurnOn(); + lastGetScanMoment.clear(); + last_wifi_connect_attempt_moment.clear(); + timerAPstart.clear(); + + lastWiFiResetMoment.setNow(); + wifi_TX_pwr = 0; + usedChannel = 0; +} + +void WiFiEventData_t::markWiFiTurnOn() { + setWiFiDisconnected(); +// lastDisconnectMoment.clear(); + lastConnectMoment.clear(); + lastGetIPmoment.clear(); + wifi_considered_stable = false; + + clear_processed_flags(); +} + +void WiFiEventData_t::clear_processed_flags() { + // Mark all flags to default to prevent handling old events. + WiFi.scanDelete(); + processedConnect = true; + processedDisconnect = true; + processedGotIP = true; + #if FEATURE_USE_IPV6 + processedGotIP6 = true; + #endif + processedDHCPTimeout = true; + processedConnectAPmode = true; + processedDisconnectAPmode = true; + processedScanDone = true; + #ifdef USES_ESPEASY_NOW + processedProbeRequestAPmode = true; + #endif + wifiConnectAttemptNeeded = true; + wifiConnectInProgress = false; + processingDisconnect.clear(); + dns0_cache = IPAddress(); + dns1_cache = IPAddress(); +} + +void WiFiEventData_t::markWiFiBegin() { + markWiFiTurnOn(); + last_wifi_connect_attempt_moment.setNow(); + wifiConnectInProgress = true; + usedChannel = 0; + ++wifi_connect_attempt; + if (!timerAPstart.isSet()) { + timerAPstart.setMillisFromNow(3 * WIFI_RECONNECT_WAIT); + } + #ifdef USES_ESPEASY_NOW + temp_disable_EspEasy_now_timer = millis() + WIFI_RECONNECT_WAIT; + #endif +} + + +void WiFiEventData_t::setWiFiDisconnected() { + wifiStatus = ESPEASY_WIFI_DISCONNECTED; + last_wifi_connect_attempt_moment.clear(); + wifiConnectInProgress = false; +#if FEATURE_ESPEASY_P2P + updateUDPport(true); +#endif + +} + +void WiFiEventData_t::setWiFiGotIP() { + bitSet(wifiStatus, ESPEASY_WIFI_GOT_IP); + processedGotIP = true; + if (valid_DNS_address(WiFi.dnsIP(0))) { + dns0_cache = WiFi.dnsIP(0); + } + if (valid_DNS_address(WiFi.dnsIP(1))) { + dns1_cache = WiFi.dnsIP(1); + } +} + +void WiFiEventData_t::setWiFiConnected() { + bitSet(wifiStatus, ESPEASY_WIFI_CONNECTED); + processedConnect = true; +} + +void WiFiEventData_t::setWiFiServicesInitialized() { + if (!unprocessedWifiEvents() && WiFiConnected() && WiFiGotIP()) { + # ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_DEBUG, F("WiFi : WiFi services initialized")); + #endif + bitSet(wifiStatus, ESPEASY_WIFI_SERVICES_INITIALIZED); + wifiConnectInProgress = false; + wifiConnectAttemptNeeded = false; + + #ifdef USES_ESPEASY_NOW + temp_disable_EspEasy_now_timer = millis() + WIFI_RECONNECT_WAIT; + #endif + dns0_cache = WiFi.dnsIP(0); + dns1_cache = WiFi.dnsIP(1); + +#if FEATURE_ESPEASY_P2P + updateUDPport(false); +#endif + } +} + +void WiFiEventData_t::markGotIP() { + lastGetIPmoment.setNow(); + + // Create the 'got IP event' so mark the wifiStatus to not have the got IP flag set + // This also implies the services are not fully initialized. + bitClear(wifiStatus, ESPEASY_WIFI_GOT_IP); + bitClear(wifiStatus, ESPEASY_WIFI_SERVICES_INITIALIZED); + processedGotIP = false; +} + +#if FEATURE_USE_IPV6 + void WiFiEventData_t::markGotIPv6(const IPAddress& ip6) { + processedGotIP6 = false; + unprocessed_IP6 = ip6; + } +#endif + + +void WiFiEventData_t::markLostIP() { + bitClear(wifiStatus, ESPEASY_WIFI_GOT_IP); + bitClear(wifiStatus, ESPEASY_WIFI_SERVICES_INITIALIZED); +} + +void WiFiEventData_t::markDisconnect(WiFiDisconnectReason reason) { +/* + #if defined(ESP32) + if ((WiFi.getMode() & WIFI_MODE_STA) == 0) return; + #else // if defined(ESP32) + if ((WiFi.getMode() & WIFI_STA) == 0) return; + #endif // if defined(ESP32) +*/ + lastDisconnectMoment.setNow(); + usedChannel = 0; + + if (last_wifi_connect_attempt_moment.isSet() && !lastConnectMoment.isSet()) { + // There was an unsuccessful connection attempt + lastConnectedDuration_us = last_wifi_connect_attempt_moment.timeDiff(lastDisconnectMoment); + } else { + if (last_wifi_connect_attempt_moment.isSet()) + lastConnectedDuration_us = lastConnectMoment.timeDiff(lastDisconnectMoment); + else + lastConnectedDuration_us = 0; + } + lastDisconnectReason = reason; + processedDisconnect = false; + wifiConnectInProgress = false; +} + +void WiFiEventData_t::markConnected(const String& ssid, const uint8_t bssid[6], uint8_t channel) { + usedChannel = channel; + lastConnectMoment.setNow(); + processedConnect = false; + channel_changed = RTC.lastWiFiChannel != channel; + last_ssid = ssid; + bssid_changed = false; + auth_mode = WiFi_AP_Candidates.getCurrent().enc_type; + + RTC.lastWiFiChannel = channel; + for (uint8_t i = 0; i < 6; ++i) { + if (RTC.lastBSSID[i] != bssid[i]) { + bssid_changed = true; + RTC.lastBSSID[i] = bssid[i]; + } + } +#if FEATURE_USE_IPV6 + if (Settings.EnableIPv6()) { + WiFi.enableIPv6(true); + } +#endif +} + +void WiFiEventData_t::markConnectedAPmode(const uint8_t mac[6]) { + lastMacConnectedAPmode = mac; + processedConnectAPmode = false; +} + +void WiFiEventData_t::markDisconnectedAPmode(const uint8_t mac[6]) { + lastMacDisconnectedAPmode = mac; + processedDisconnectAPmode = false; +} + + + +String WiFiEventData_t::ESPeasyWifiStatusToString() const { + String log; + if (WiFiDisconnected()) { + log = F("DISCONNECTED"); + } else { + if (WiFiConnected()) { + log += F("Conn. "); + } + if (WiFiGotIP()) { + log += F("IP "); + } + if (WiFiServicesInitialized()) { + log += F("Init"); + } + } + return log; +} + + +uint32_t WiFiEventData_t::getSuggestedTimeout(int index, uint32_t minimum_timeout) const { + auto it = connectDurations.find(index); + if (it == connectDurations.end()) { + return 3 * minimum_timeout; + } + const uint32_t res = 3 * it->second; + return constrain(res, minimum_timeout, CONNECT_TIMEOUT_MAX); } \ No newline at end of file diff --git a/src/src/DataStructs/WiFiEventData.h b/src/src/DataStructs/WiFiEventData.h index 1dbd20fdb1..428bc89145 100644 --- a/src/src/DataStructs/WiFiEventData.h +++ b/src/src/DataStructs/WiFiEventData.h @@ -1,163 +1,172 @@ -#ifndef DATASTRUCTS_WIFIEVENTDATA_H -#define DATASTRUCTS_WIFIEVENTDATA_H - -#include "../DataStructs/MAC_address.h" -#include "../DataTypes/WiFiDisconnectReason.h" -#include "../Helpers/LongTermTimer.h" - - -#ifdef ESP32 -# include -# include -# include - -#endif // ifdef ESP32 - -#include - -#ifdef ESP8266 -# include -# include -#endif // ifdef ESP8266 - -#include - -// WifiStatus -#define ESPEASY_WIFI_DISCONNECTED 0 - -// Bit numbers for WiFi status -#define ESPEASY_WIFI_CONNECTED 0 -#define ESPEASY_WIFI_GOT_IP 1 -#define ESPEASY_WIFI_SERVICES_INITIALIZED 2 - - -#define WIFI_PROCESS_EVENTS_TIMEOUT 20000 // in milliSeconds - -struct WiFiEventData_t { - bool WiFiConnectAllowed() const; - - bool unprocessedWifiEvents() const; - - void clearAll(); - void markWiFiTurnOn(); - void clear_processed_flags(); - void markWiFiBegin(); - - bool WiFiDisconnected() const { - return wifiStatus == ESPEASY_WIFI_DISCONNECTED; - } - - bool WiFiGotIP() const { - return bitRead(wifiStatus, ESPEASY_WIFI_GOT_IP); - } - - bool WiFiConnected() const { - return bitRead(wifiStatus, ESPEASY_WIFI_CONNECTED); - } - - bool WiFiServicesInitialized() const { - return bitRead(wifiStatus, ESPEASY_WIFI_SERVICES_INITIALIZED); - } - - void setWiFiDisconnected(); - void setWiFiGotIP(); - void setWiFiConnected(); - void setWiFiServicesInitialized(); - - - void markGotIP(); -#if FEATURE_USE_IPV6 - void markGotIPv6(const IPAddress& ip6); -#endif - void markLostIP(); - void markDisconnect(WiFiDisconnectReason reason); - void markConnected(const String& ssid, - const uint8_t bssid[6], - uint8_t channel); - void markConnectedAPmode(const uint8_t mac[6]); - void markDisconnectedAPmode(const uint8_t mac[6]); - - void setAuthMode(uint8_t newMode) { - auth_mode = newMode; - } - - String ESPeasyWifiStatusToString() const; - - uint32_t getSuggestedTimeout(int index, - uint32_t minimum_timeout) const; - - - // WiFi related data - bool wifiSetup = false; - bool wifiSetupConnect = false; - uint8_t wifiStatus = ESPEASY_WIFI_DISCONNECTED; - LongTermTimer last_wifi_connect_attempt_moment; - unsigned int wifi_connect_attempt = 0; - bool wifi_considered_stable = false; - int wifi_reconnects = -1; // First connection attempt is not a reconnect. - String last_ssid; - float wifi_TX_pwr = 0; - bool bssid_changed = false; - bool channel_changed = false; - - uint8_t auth_mode = 0; - uint8_t lastScanChannel = 0; - uint8_t usedChannel = 0; - - bool eventError = false; - - - WiFiDisconnectReason lastDisconnectReason = WIFI_DISCONNECT_REASON_UNSPECIFIED; - LongTermTimer lastScanMoment; - LongTermTimer lastConnectMoment; - LongTermTimer lastDisconnectMoment; - LongTermTimer lastWiFiResetMoment; - LongTermTimer lastGetIPmoment; - LongTermTimer lastGetScanMoment; - LongTermTimer::Duration lastConnectedDuration_us = 0ll; - LongTermTimer timerAPoff; // Timer to check whether the AP mode should be disabled (0 = disabled) - LongTermTimer timerAPstart; // Timer to start AP mode, started when no valid network is detected. - bool intent_to_reboot = false; - MAC_address lastMacConnectedAPmode; - MAC_address lastMacDisconnectedAPmode; - - IPAddress dns0_cache; - IPAddress dns1_cache; - - #if FEATURE_USE_IPV6 - IPAddress unprocessed_IP6; - #endif - - - // processDisconnect() may clear all WiFi settings, resulting in clearing processedDisconnect - // This can cause recursion, so a semaphore is needed here. - LongTermTimer processingDisconnect; - - - // Semaphore like bools for processing data gathered from WiFi events. - bool processedConnect = true; - bool processedDisconnect = true; - bool processedGotIP = true; - #if FEATURE_USE_IPV6 - bool processedGotIP6 = true; - #endif - bool processedDHCPTimeout = true; - bool processedConnectAPmode = true; - bool processedDisconnectAPmode = true; - bool processedScanDone = true; - bool wifiConnectAttemptNeeded = true; - bool wifiConnectInProgress = false; - bool warnedNoValidWiFiSettings = false; - - bool performedClearWiFiCredentials = false; - - unsigned long connectionFailures = 0; - - std::mapconnectDurations; - -#ifdef ESP32 - WiFiEventId_t wm_event_id = 0; -#endif // ifdef ESP32 -}; - -#endif // ifndef DATASTRUCTS_WIFIEVENTDATA_H +#ifndef DATASTRUCTS_WIFIEVENTDATA_H +#define DATASTRUCTS_WIFIEVENTDATA_H + +#include "../../ESPEasy_common.h" + +#include "../../ESPEasy-Globals.h" + +#include "../DataStructs/MAC_address.h" +#include "../DataTypes/WiFiDisconnectReason.h" +#include "../Helpers/LongTermTimer.h" + + +#ifdef ESP32 +# include +# include +# include + +#endif // ifdef ESP32 + +#include + +#ifdef ESP8266 +# include +# include +#endif // ifdef ESP8266 + +#include + +// WifiStatus +#define ESPEASY_WIFI_DISCONNECTED 0 + +// Bit numbers for WiFi status +#define ESPEASY_WIFI_CONNECTED 0 +#define ESPEASY_WIFI_GOT_IP 1 +#define ESPEASY_WIFI_SERVICES_INITIALIZED 2 + + +#define WIFI_PROCESS_EVENTS_TIMEOUT 20000 // in milliSeconds + +struct WiFiEventData_t { + bool WiFiConnectAllowed() const; + + bool unprocessedWifiEvents(); + + void clearAll(); + void markWiFiTurnOn(); + void clear_processed_flags(); + void markWiFiBegin(); + + bool WiFiDisconnected() const { + return wifiStatus == ESPEASY_WIFI_DISCONNECTED; + } + + bool WiFiGotIP() const { + return bitRead(wifiStatus, ESPEASY_WIFI_GOT_IP); + } + + bool WiFiConnected() const { + return bitRead(wifiStatus, ESPEASY_WIFI_CONNECTED); + } + + bool WiFiServicesInitialized() const { + return bitRead(wifiStatus, ESPEASY_WIFI_SERVICES_INITIALIZED); + } + + void setWiFiDisconnected(); + void setWiFiGotIP(); + void setWiFiConnected(); + void setWiFiServicesInitialized(); + + + void markGotIP(); +#if FEATURE_USE_IPV6 + void markGotIPv6(const IPAddress& ip6); +#endif + void markLostIP(); + void markDisconnect(WiFiDisconnectReason reason); + void markConnected(const String& ssid, + const uint8_t bssid[6], + uint8_t channel); + void markConnectedAPmode(const uint8_t mac[6]); + void markDisconnectedAPmode(const uint8_t mac[6]); + + void setAuthMode(uint8_t newMode) { + auth_mode = newMode; + } + + String ESPeasyWifiStatusToString() const; + + uint32_t getSuggestedTimeout(int index, + uint32_t minimum_timeout) const; + + + + // WiFi related data + bool wifiSetup = false; + bool wifiSetupConnect = false; + uint8_t wifiStatus = ESPEASY_WIFI_DISCONNECTED; + LongTermTimer last_wifi_connect_attempt_moment; + unsigned int wifi_connect_attempt = 0; + bool wifi_considered_stable = false; + int wifi_reconnects = -1; // First connection attempt is not a reconnect. + String last_ssid; + float wifi_TX_pwr = 0; + bool bssid_changed = false; + bool channel_changed = false; + + uint8_t auth_mode = 0; + uint8_t lastScanChannel = 0; + uint8_t usedChannel = 0; + + bool eventError = false; + + + WiFiDisconnectReason lastDisconnectReason = WIFI_DISCONNECT_REASON_UNSPECIFIED; + LongTermTimer lastScanMoment; + LongTermTimer lastConnectMoment; + LongTermTimer lastDisconnectMoment; + LongTermTimer lastWiFiResetMoment; + LongTermTimer lastGetIPmoment; + LongTermTimer lastGetScanMoment; + LongTermTimer::Duration lastConnectedDuration_us = 0ll; + LongTermTimer timerAPoff; // Timer to check whether the AP mode should be disabled (0 = disabled) + LongTermTimer timerAPstart; // Timer to start AP mode, started when no valid network is detected. + LongTermTimer lastAPmodeStationConnectMoment; + bool intent_to_reboot = false; + MAC_address lastMacConnectedAPmode; + MAC_address lastMacDisconnectedAPmode; + + IPAddress dns0_cache; + IPAddress dns1_cache; + + #if FEATURE_USE_IPV6 + IPAddress unprocessed_IP6; + #endif + + + // processDisconnect() may clear all WiFi settings, resulting in clearing processedDisconnect + // This can cause recursion, so a semaphore is needed here. + LongTermTimer processingDisconnect; + + + // Semaphore like bools for processing data gathered from WiFi events. + bool processedConnect = true; + bool processedDisconnect = true; + bool processedGotIP = true; + #if FEATURE_USE_IPV6 + bool processedGotIP6 = true; + #endif + bool processedDHCPTimeout = true; + bool processedConnectAPmode = true; + bool processedDisconnectAPmode = true; + bool processedScanDone = true; + #ifdef USES_ESPEASY_NOW + bool processedProbeRequestAPmode = true; + #endif + bool wifiConnectAttemptNeeded = true; + bool wifiConnectInProgress = false; + bool warnedNoValidWiFiSettings = false; + + bool performedClearWiFiCredentials = false; + + unsigned long connectionFailures = 0; + + std::mapconnectDurations; + +#ifdef ESP32 + WiFiEventId_t wm_event_id = 0; +#endif // ifdef ESP32 +}; + +#endif // ifndef DATASTRUCTS_WIFIEVENTDATA_H diff --git a/src/src/DataStructs_templ/SettingsStruct.cpp b/src/src/DataStructs_templ/SettingsStruct.cpp index 060b702784..25af515c31 100644 --- a/src/src/DataStructs_templ/SettingsStruct.cpp +++ b/src/src/DataStructs_templ/SettingsStruct.cpp @@ -1,1252 +1,1271 @@ -#include "../DataStructs/SettingsStruct.h" - -#include "../../ESPEasy_common.h" - -#ifndef DATASTRUCTS_SETTINGSSTRUCT_CPP -#define DATASTRUCTS_SETTINGSSTRUCT_CPP - - -#include "../CustomBuild/CompiletimeDefines.h" -#include "../CustomBuild/ESPEasyLimits.h" -#include "../DataStructs/DeviceStruct.h" -#include "../DataTypes/SPI_options.h" -#include "../DataTypes/NPluginID.h" -#include "../DataTypes/PluginID.h" -#include "../Globals/Plugins.h" -#include "../Globals/CPlugins.h" -#include "../Helpers/Misc.h" -#include "../Helpers/StringParser.h" - -#if FEATURE_I2C_MULTIPLE -#include "../Helpers/Hardware_device_info.h" -#endif - -#if ESP_IDF_VERSION_MAJOR >= 5 -#include -#include "include/esp32x_fixes.h" -#endif - -/* -// VariousBits1 defaults to 0, keep in mind when adding bit lookups. -template -bool SettingsStruct_tmpl::appendUnitToHostname() const { - return !bitRead(VariousBits1, 1); -} - -template -void SettingsStruct_tmpl::appendUnitToHostname(bool value) { - bitWrite(VariousBits1, 1, !value); -} - -template -bool SettingsStruct_tmpl::uniqueMQTTclientIdReconnect_unused() const { - return bitRead(VariousBits1, 2); -} - -template -void SettingsStruct_tmpl::uniqueMQTTclientIdReconnect_unused(bool value) { - bitWrite(VariousBits1, 2, value); -} - -template -bool SettingsStruct_tmpl::OldRulesEngine() const { - #ifdef WEBSERVER_NEW_RULES - return !bitRead(VariousBits1, 3); - #else - return true; - #endif -} - -template -void SettingsStruct_tmpl::OldRulesEngine(bool value) { - bitWrite(VariousBits1, 3, !value); -} - -template -bool SettingsStruct_tmpl::ForceWiFi_bg_mode() const { - return bitRead(VariousBits1, 4); -} - -template -void SettingsStruct_tmpl::ForceWiFi_bg_mode(bool value) { - bitWrite(VariousBits1, 4, value); -} - -template -bool SettingsStruct_tmpl::WiFiRestart_connection_lost() const { - return bitRead(VariousBits1, 5); -} - -template -void SettingsStruct_tmpl::WiFiRestart_connection_lost(bool value) { - bitWrite(VariousBits1, 5, value); -} - -template -bool SettingsStruct_tmpl::EcoPowerMode() const { - return bitRead(VariousBits1, 6); -} - -template -void SettingsStruct_tmpl::EcoPowerMode(bool value) { - bitWrite(VariousBits1, 6, value); -} - -template -bool SettingsStruct_tmpl::WifiNoneSleep() const { - return bitRead(VariousBits1, 7); -} - -template -void SettingsStruct_tmpl::WifiNoneSleep(bool value) { - bitWrite(VariousBits1, 7, value); -} - -// Enable send gratuitous ARP by default, so invert the values (default = 0) -template -bool SettingsStruct_tmpl::gratuitousARP() const { - return !bitRead(VariousBits1, 8); -} - -template -void SettingsStruct_tmpl::gratuitousARP(bool value) { - bitWrite(VariousBits1, 8, !value); -} - -template -bool SettingsStruct_tmpl::TolerantLastArgParse() const { - return bitRead(VariousBits1, 9); -} - -template -void SettingsStruct_tmpl::TolerantLastArgParse(bool value) { - bitWrite(VariousBits1, 9, value); -} - -template -bool SettingsStruct_tmpl::SendToHttp_ack() const { - return bitRead(VariousBits1, 10); -} - -template -void SettingsStruct_tmpl::SendToHttp_ack(bool value) { - bitWrite(VariousBits1, 10, value); -} - -template -bool SettingsStruct_tmpl::UseESPEasyNow() const { -#ifdef USES_ESPEASY_NOW - return bitRead(VariousBits1, 11); -#else - return false; -#endif -} - -template -void SettingsStruct_tmpl::UseESPEasyNow(bool value) { -#ifdef USES_ESPEASY_NOW - bitWrite(VariousBits1, 11, value); -#endif -} - -template -bool SettingsStruct_tmpl::IncludeHiddenSSID() const { - return bitRead(VariousBits1, 12); -} - -template -void SettingsStruct_tmpl::IncludeHiddenSSID(bool value) { - bitWrite(VariousBits1, 12, value); -} - -template -bool SettingsStruct_tmpl::UseMaxTXpowerForSending() const { - return bitRead(VariousBits1, 13); -} - -template -void SettingsStruct_tmpl::UseMaxTXpowerForSending(bool value) { - bitWrite(VariousBits1, 13, value); -} - -template -bool SettingsStruct_tmpl::ApDontForceSetup() const { - return bitRead(VariousBits1, 14); -} - -template -void SettingsStruct_tmpl::ApDontForceSetup(bool value) { - bitWrite(VariousBits1, 14, value); -} - -// VariousBits1 bit 15 was used by PeriodicalScanWiFi -// Now removed, is reset to 0, can be used for some other setting. - -template -bool SettingsStruct_tmpl::JSONBoolWithoutQuotes() const { - return bitRead(VariousBits1, 16); -} - -template -void SettingsStruct_tmpl::JSONBoolWithoutQuotes(bool value) { - bitWrite(VariousBits1, 16, value); -} -*/ - -template -bool SettingsStruct_tmpl::CombineTaskValues_SingleEvent(taskIndex_t taskIndex) const { - if (validTaskIndex(taskIndex)) { - return bitRead(TaskDeviceSendDataFlags[taskIndex], 0); - } - return false; -} - -template -void SettingsStruct_tmpl::CombineTaskValues_SingleEvent(taskIndex_t taskIndex, bool value) { - if (validTaskIndex(taskIndex)) { - bitWrite(TaskDeviceSendDataFlags[taskIndex], 0, value); - } -} - -#if FEATURE_STRING_VARIABLES -template -bool SettingsStruct_tmpl::ShowDerivedTaskValues(taskIndex_t taskIndex) const { - if (validTaskIndex(taskIndex)) { - return bitRead(TaskDeviceSendDataFlags[taskIndex], 1); - } - return false; -} - -template -void SettingsStruct_tmpl::ShowDerivedTaskValues(taskIndex_t taskIndex, bool value) { - if (validTaskIndex(taskIndex)) { - bitWrite(TaskDeviceSendDataFlags[taskIndex], 1, value); - } -} - -template -bool SettingsStruct_tmpl::EventAndLogDerivedTaskValues(taskIndex_t taskIndex) const { - if (validTaskIndex(taskIndex)) { - return bitRead(TaskDeviceSendDataFlags[taskIndex], 2); - } - return false; -} - -template -void SettingsStruct_tmpl::EventAndLogDerivedTaskValues(taskIndex_t taskIndex, bool value) { - if (validTaskIndex(taskIndex)) { - bitWrite(TaskDeviceSendDataFlags[taskIndex], 2, value); - } -} - -template -bool SettingsStruct_tmpl::SendDerivedTaskValues(taskIndex_t taskIndex, controllerIndex_t controllerIndex) const { - if (validTaskIndex(taskIndex) && validControllerIndex(controllerIndex)) { - return bitRead(TaskDeviceSendDataFlags[taskIndex], 3 + controllerIndex); // ATTENTION: uses bits 3..6!!! - } - return false; -} - -template -void SettingsStruct_tmpl::SendDerivedTaskValues(taskIndex_t taskIndex, controllerIndex_t controllerIndex, bool value) { - if (validTaskIndex(taskIndex) && validControllerIndex(controllerIndex)) { - bitWrite(TaskDeviceSendDataFlags[taskIndex], 3 + controllerIndex, value); // ATTENTION: uses bits 3..6!!! - } -} -#endif // if FEATURE_STRING_VARIABLES -/* -template -bool SettingsStruct_tmpl::DoNotStartAP() const { - return bitRead(VariousBits1, 17); -} - -template -void SettingsStruct_tmpl::DoNotStartAP(bool value) { - bitWrite(VariousBits1, 17, value); -} - - -template -bool SettingsStruct_tmpl::UseAlternativeDeepSleep() const { - return bitRead(VariousBits1, 18); -} - -template -void SettingsStruct_tmpl::UseAlternativeDeepSleep(bool value) { - bitWrite(VariousBits1, 18, value); -} - -template -bool SettingsStruct_tmpl::UseLastWiFiFromRTC() const { - return bitRead(VariousBits1, 19); -} - -template -void SettingsStruct_tmpl::UseLastWiFiFromRTC(bool value) { - bitWrite(VariousBits1, 19, value); -} - -template -bool SettingsStruct_tmpl::EnableTimingStats() const { - return bitRead(VariousBits1, 20); -} - -template -void SettingsStruct_tmpl::EnableTimingStats(bool value) { - bitWrite(VariousBits1, 20, value); -} - -template -bool SettingsStruct_tmpl::AllowTaskValueSetAllPlugins() const { - return bitRead(VariousBits1, 21); -} - -template -void SettingsStruct_tmpl::AllowTaskValueSetAllPlugins(bool value) { - bitWrite(VariousBits1, 21, value); -} - -template -bool SettingsStruct_tmpl::EnableClearHangingI2Cbus() const { - return bitRead(VariousBits1, 22); -} - -template -void SettingsStruct_tmpl::EnableClearHangingI2Cbus(bool value) { - bitWrite(VariousBits1, 22, value); -} - -template -bool SettingsStruct_tmpl::EnableRAMTracking() const { - return bitRead(VariousBits1, 23); -} - -template -void SettingsStruct_tmpl::EnableRAMTracking(bool value) { - bitWrite(VariousBits1, 23, value); -} - -template -bool SettingsStruct_tmpl::EnableRulesCaching() const { - return !bitRead(VariousBits1, 24); -} - -template -void SettingsStruct_tmpl::EnableRulesCaching(bool value) { - bitWrite(VariousBits1, 24, !value); -} - -template -bool SettingsStruct_tmpl::EnableRulesEventReorder() const { - return !bitRead(VariousBits1, 25); -} - -template -void SettingsStruct_tmpl::EnableRulesEventReorder(bool value) { - bitWrite(VariousBits1, 25, !value); -} - -template -bool SettingsStruct_tmpl::AllowOTAUnlimited() const { - return bitRead(VariousBits1, 26); -} - -template -void SettingsStruct_tmpl::AllowOTAUnlimited(bool value) { - bitWrite(VariousBits1, 26, value); -} - -template -bool SettingsStruct_tmpl::SendToHTTP_follow_redirects() const { - return bitRead(VariousBits1, 27); -} - -template -void SettingsStruct_tmpl::SendToHTTP_follow_redirects(bool value) { - bitWrite(VariousBits1, 27, value); -} - -#if FEATURE_AUTO_DARK_MODE -template -uint8_t SettingsStruct_tmpl::getCssMode() const { - return get2BitFromUL(VariousBits1, 28); // Also occupies bit 29! -} - -template -void SettingsStruct_tmpl::setCssMode(uint8_t value) { - set2BitToUL(VariousBits1, 28, value); // Also occupies bit 29! -} -#endif // FEATURE_AUTO_DARK_MODE - -#if FEATURE_I2C_DEVICE_CHECK -template -bool SettingsStruct_tmpl::CheckI2Cdevice() const { // Inverted - return !bitRead(VariousBits1, 30); -} - -template -void SettingsStruct_tmpl::CheckI2Cdevice(bool value) { // Inverted - bitWrite(VariousBits1, 30, !value); -} -#endif // if FEATURE_I2C_DEVICE_CHECK -*/ -/* -template -bool SettingsStruct_tmpl::WaitWiFiConnect() const { - return bitRead(VariousBits2, 0); -} - -template -void SettingsStruct_tmpl::WaitWiFiConnect(bool value) { - bitWrite(VariousBits2, 0, value); -} - - -template -bool SettingsStruct_tmpl::SDK_WiFi_autoreconnect() const { - return bitRead(VariousBits2, 1); -} - -template -void SettingsStruct_tmpl::SDK_WiFi_autoreconnect(bool value) { - bitWrite(VariousBits2, 1, value); -} - - -#if FEATURE_RULES_EASY_COLOR_CODE -template -bool SettingsStruct_tmpl::DisableRulesCodeCompletion() const { - return bitRead(VariousBits2, 2); -} - -template -void SettingsStruct_tmpl::DisableRulesCodeCompletion(bool value) { - bitWrite(VariousBits2, 2, value); -} -#endif // if FEATURE_RULES_EASY_COLOR_CODE - -#if FEATURE_TARSTREAM_SUPPORT -template -bool SettingsStruct_tmpl::DisableSaveConfigAsTar() const { - return bitRead(VariousBits2, 3); // Using bit 4 now... -} - -template -void SettingsStruct_tmpl::DisableSaveConfigAsTar(bool value) { - bitWrite(VariousBits2, 3, value); // Using bit 4 now... -} -#endif // if FEATURE_TARSTREAM_SUPPORT -*/ - - -template -bool SettingsStruct_tmpl::isTaskEnableReadonly(taskIndex_t taskIndex) const { - if (validTaskIndex(taskIndex)) { - return bitRead(VariousTaskBits[taskIndex], 0); - } - return false; -} - -template -void SettingsStruct_tmpl::setTaskEnableReadonly(taskIndex_t taskIndex, bool value) { - if (validTaskIndex(taskIndex)) { - bitWrite(VariousTaskBits[taskIndex], 0, value); - } -} - -#if FEATURE_PLUGIN_PRIORITY -template -bool SettingsStruct_tmpl::isPowerManagerTask(taskIndex_t taskIndex) const { - if (validTaskIndex(taskIndex)) { - return bitRead(VariousTaskBits[taskIndex], 1); - } - return false; -} - -template -void SettingsStruct_tmpl::setPowerManagerTask(taskIndex_t taskIndex, bool value) { - if (validTaskIndex(taskIndex)) { - bitWrite(VariousTaskBits[taskIndex], 1, value); - } -} - -template -bool SettingsStruct_tmpl::isPriorityTask(taskIndex_t taskIndex) const { - if (validTaskIndex(taskIndex)) { - return isPowerManagerTask(taskIndex); // Add more? - } - return false; -} -#endif // if FEATURE_PLUGIN_PRIORITY - -template -ExtTimeSource_e SettingsStruct_tmpl::ExtTimeSource() const { - return static_cast(ExternalTimeSource >> 1); -} - -template -void SettingsStruct_tmpl::ExtTimeSource(ExtTimeSource_e value) { - uint8_t newValue = static_cast(value) << 1; - if (UseNTP()) { - newValue += 1; - } - ExternalTimeSource = newValue; -} - -template -bool SettingsStruct_tmpl::UseNTP() const { - return bitRead(ExternalTimeSource, 0); -} - -template -void SettingsStruct_tmpl::UseNTP(bool value) { - bitWrite(ExternalTimeSource, 0, value); -} - - -template -void SettingsStruct_tmpl::validate() { - if (UDPPort > 65535) { UDPPort = 0; } - - if ((Latitude < -90.0f) || (Latitude > 90.0f)) { Latitude = 0.0f; } - - if ((Longitude < -180.0f) || (Longitude > 180.0f)) { Longitude = 0.0f; } - - if (getVariousBits1() > (1u << 31)) { setVariousBits1(0); } // FIXME: Check really needed/useful? - ZERO_TERMINATE(Name); - ZERO_TERMINATE(NTPHost); - - if ((I2C_clockSpeed == 0) || (I2C_clockSpeed > 3400000)) { I2C_clockSpeed = DEFAULT_I2C_CLOCK_SPEED; } - if (WebserverPort == 0) { WebserverPort = 80;} - if (SyslogPort == 0) { SyslogPort = 514; } - - #if FEATURE_DEFINE_SERIAL_CONSOLE_PORT - if (console_serial_port == 0 && UseSerial) { - console_serial_port = DEFAULT_CONSOLE_PORT; - // Set default RX/TX pins for Serial0 - console_serial_rxpin = DEFAULT_CONSOLE_PORT_RXPIN; - console_serial_txpin = DEFAULT_CONSOLE_PORT_TXPIN; - } -#ifdef ESP8266 - if (console_serial_port == 2) { - // Set default RX/TX pins for Serial0 - console_serial_rxpin = DEFAULT_CONSOLE_PORT_RXPIN; - console_serial_txpin = DEFAULT_CONSOLE_PORT_TXPIN; - } else if (console_serial_port == 3) { - // Set default RX/TX pins for Serial0_swapped - console_serial_rxpin = 13; - console_serial_txpin = 15; - } -#endif - #endif -} - -template -bool SettingsStruct_tmpl::networkSettingsEmpty() const { - return IP[0] == 0 && Gateway[0] == 0 && Subnet[0] == 0 && DNS[0] == 0; -} - -template -void SettingsStruct_tmpl::clearNetworkSettings() { - for (uint8_t i = 0; i < 4; ++i) { - IP[i] = 0; - Gateway[i] = 0; - Subnet[i] = 0; - DNS[i] = 0; - ETH_IP[i] = 0; - ETH_Gateway[i] = 0; - ETH_Subnet[i] = 0; - ETH_DNS[i] = 0; - } -} - -template -void SettingsStruct_tmpl::clearTimeSettings() { - ExternalTimeSource = 0; - ZERO_FILL(NTPHost); - TimeZone = 0; - DST = false; - DST_Start = 0; - DST_End = 0; - Latitude = 0.0f; - Longitude = 0.0f; -} - -template -void SettingsStruct_tmpl::clearNotifications() { - for (uint8_t i = 0; i < NOTIFICATION_MAX; ++i) { - Notification[i] = 0u;// .setInvalid(); - NotificationEnabled[i] = false; - } -} - -template -void SettingsStruct_tmpl::clearControllers() { - for (controllerIndex_t i = 0; i < CONTROLLER_MAX; ++i) { - Protocol[i] = 0; - ControllerEnabled[i] = false; - } -} - -template -void SettingsStruct_tmpl::clearTasks() { - for (taskIndex_t task = 0; task < N_TASKS; ++task) { - clearTask(task); - } -} - -template -void SettingsStruct_tmpl::clearLogSettings() { - SyslogLevel = 0; - SerialLogLevel = 0; - WebLogLevel = 0; - SDLogLevel = 0; - SyslogFacility = DEFAULT_SYSLOG_FACILITY; - ZERO_FILL(Syslog_IP); -} - -template -void SettingsStruct_tmpl::clearUnitNameSettings() { - Unit = 0; - ZERO_FILL(Name); - UDPPort = 0; -} - -template -void SettingsStruct_tmpl::clearMisc() { - PID = ESP_PROJECT_PID; - Version = VERSION; - Build = get_build_nr(); - IP_Octet = 0; - Delay = DEFAULT_DELAY; - Pin_i2c_sda = DEFAULT_PIN_I2C_SDA; - Pin_i2c_scl = DEFAULT_PIN_I2C_SCL; - Pin_status_led = DEFAULT_PIN_STATUS_LED; - Pin_status_led_Inversed = DEFAULT_PIN_STATUS_LED_INVERSED; - Pin_sd_cs = -1; -#ifdef ESP32 - #if FEATURE_I2C_MULTIPLE - Pin_i2c2_sda = DEFAULT_PIN_I2C2_SDA; - Pin_i2c2_scl = DEFAULT_PIN_I2C2_SCL; - Pin_i2c3_sda = DEFAULT_PIN_I2C3_SDA; - Pin_i2c3_scl = DEFAULT_PIN_I2C3_SCL; - #endif - // Ethernet related settings are never used on ESP8266 - ETH_Phy_Addr = DEFAULT_ETH_PHY_ADDR; - ETH_Pin_mdc_cs = DEFAULT_ETH_PIN_MDC; - ETH_Pin_mdio_irq = DEFAULT_ETH_PIN_MDIO; - ETH_Pin_power_rst = DEFAULT_ETH_PIN_POWER; - ETH_Phy_Type = DEFAULT_ETH_PHY_TYPE; - ETH_Clock_Mode = DEFAULT_ETH_CLOCK_MODE; -#endif - NetworkMedium = DEFAULT_NETWORK_MEDIUM; - - I2C_clockSpeed_Slow = DEFAULT_I2C_CLOCK_SPEED_SLOW; - I2C_Multiplexer_Type = I2C_MULTIPLEXER_NONE; - I2C_Multiplexer_Addr = -1; - memset(I2C_Multiplexer_Channel, -1, sizeof(I2C_Multiplexer_Channel)); - I2C_Multiplexer_ResetPin = -1; - - { - // Here we initialize all data to 0, so this is the ONLY reason why PinBootStates - // can now be directly accessed. - // In all other use cases, use the get and set functions for it. - - ZERO_FILL(PinBootStates); - # ifdef ESP32 - ZERO_FILL(PinBootStates_ESP32); - # endif // ifdef ESP32 - } - BaudRate = DEFAULT_SERIAL_BAUD; - MessageDelay_unused = 0; - deepSleep_wakeTime = 0; - CustomCSS = false; - WDI2CAddress = 0; - UseRules = DEFAULT_USE_RULES; - UseSerial = DEFAULT_USE_SERIAL; - UseSSDP = false; - WireClockStretchLimit = 0; - I2C_clockSpeed = DEFAULT_I2C_CLOCK_SPEED; - WebserverPort = 80; - SyslogPort = 514; - GlobalSync = false; - ConnectionFailuresThreshold = 0; - MQTTRetainFlag_unused = false; - InitSPI = DEFAULT_SPI; - deepSleepOnFail = false; - UseValueLogger = false; - ArduinoOTAEnable = false; - UseRTOSMultitasking = false; - Pin_Reset = -1; - StructSize = sizeof(SettingsStruct_tmpl); - MQTTUseUnitNameAsClientId_unused = 0; - setVariousBits1(0); - setVariousBits2(0); - - console_serial_port = DEFAULT_CONSOLE_PORT; - console_serial_rxpin = DEFAULT_CONSOLE_PORT_RXPIN; - console_serial_txpin = DEFAULT_CONSOLE_PORT_TXPIN; - console_serial0_fallback = DEFAULT_CONSOLE_SER0_FALLBACK; - - OldRulesEngine(DEFAULT_RULES_OLDENGINE); - ForceWiFi_bg_mode(DEFAULT_WIFI_FORCE_BG_MODE); - WiFiRestart_connection_lost(DEFAULT_WIFI_RESTART_WIFI_CONN_LOST); - EcoPowerMode(DEFAULT_ECO_MODE); - WifiNoneSleep(DEFAULT_WIFI_NONE_SLEEP); - gratuitousARP(DEFAULT_GRATUITOUS_ARP); - TolerantLastArgParse(DEFAULT_TOLERANT_LAST_ARG_PARSE); - SendToHttp_ack(DEFAULT_SEND_TO_HTTP_ACK); - #ifdef USES_ESPEASY_NOW - UseESPEasyNow(DEFAULT_USE_ESPEASYNOW); - #else - UseESPEasyNow(false); - #endif - ApDontForceSetup(DEFAULT_AP_DONT_FORCE_SETUP); - DoNotStartAP(DEFAULT_DONT_ALLOW_START_AP); -} - - -template -void SettingsStruct_tmpl::clearTask(taskIndex_t task) { - if (task >= N_TASKS) { return; } - - for (controllerIndex_t i = 0; i < CONTROLLER_MAX; ++i) { - TaskDeviceID[i][task] = 0u; - TaskDeviceSendData[i][task] = false; - } - TaskDeviceNumber[task] = 0u; //.setInvalid(); - OLD_TaskDeviceID[task] = 0u; // UNUSED: this can be removed - TaskDevicePin1[task] = -1; - TaskDevicePin2[task] = -1; - TaskDevicePin3[task] = -1; - TaskDevicePort[task] = 0u; - TaskDevicePin1PullUp[task] = false; - - for (uint8_t cv = 0; cv < PLUGIN_CONFIGVAR_MAX; ++cv) { - TaskDevicePluginConfig[task][cv] = 0; - } - TaskDevicePin1Inversed[task] = false; - - for (uint8_t cv = 0; cv < PLUGIN_CONFIGFLOATVAR_MAX; ++cv) { - TaskDevicePluginConfigFloat[task][cv] = 0.0f; - } - - for (uint8_t cv = 0; cv < PLUGIN_CONFIGLONGVAR_MAX; ++cv) { - TaskDevicePluginConfigLong[task][cv] = 0; - } - TaskDeviceSendDataFlags[task] = 0u; - VariousTaskBits[task] = 0; - TaskDeviceDataFeed[task] = 0u; - TaskDeviceTimer[task] = 0u; -// TaskDeviceEnabled[task].value = 0u; // Should also clear any temporary flags. - TaskDeviceEnabled[task] = false; - I2C_Multiplexer_Channel[task] = -1; -} - -template -String SettingsStruct_tmpl::getHostname() const { - return this->getHostname(this->appendUnitToHostname()); -} - -template -String SettingsStruct_tmpl::getHostname(bool appendUnit) const { - String hostname = this->getName(); - - if ((this->Unit != 0) && appendUnit) { // only append non-zero unit number - hostname += '_'; - hostname += this->Unit; - } - return hostname; -} - -template -String SettingsStruct_tmpl::getName() const { - String unitname = this->Name; - - return parseTemplate(unitname); -} - -template -bool SettingsStruct_tmpl::getPinBootStateIndex( - int8_t gpio_pin, - int8_t& index_low - # ifdef ESP32 - , int8_t& index_high - # endif // ifdef ESP32 - ) const { - index_low = -1; -# ifdef ESP32 - index_high = -1; - if ((gpio_pin < 0) || !(GPIO_IS_VALID_GPIO(gpio_pin))) { return false; } -# endif // ifdef ESP32 - constexpr int maxStates = NR_ELEMENTS(PinBootStates); - - if (gpio_pin < maxStates) { - index_low = gpio_pin; - return true; - } -# ifdef ESP32 - constexpr int maxStatesesp32 = NR_ELEMENTS(PinBootStates_ESP32); - - index_high = gpio_pin - maxStates; - -# if defined(ESP32_CLASSIC) || defined(ESP32C2) || defined(ESP32C3)|| defined(ESP32C6) - - // These can all store in the PinBootStates_ESP32 array - return (index_high < maxStatesesp32); - -# elif defined(ESP32S2) - - // First make sure we're not dealing with flash/PSRAM connected pins - if (!((gpio_pin > 21) && (gpio_pin < 33) && (gpio_pin != 26))) { - // Previously used index, to maintain compatibility with previous settings. - - if (index_high >= maxStatesesp32) { - // Now try to fix the bug by inserting the missing ones into unused spots - // This way we don't need to convert existing settings - index_high -= maxStatesesp32; - constexpr int8_t offsetFlashPin = 22 - maxStates; - index_high += offsetFlashPin; - - if (gpio_pin >= 26) { - // Skip index for GPIO 26 - ++index_high; - } - } - return (index_high < maxStatesesp32); - } - -# elif defined(ESP32S3) - - // GPIO 22 ... 32 should never be used. - // Thus: - // - map ... <21> to the beginning of PinBootStates_ESP32 - // - map <33> ... <48> to the end of PinBootStates_ESP32 - if (gpio_pin < 22) { - return true; - } - - if (gpio_pin >= 33) { - index_high = gpio_pin - maxStates + 22 - 33; - return true; - } -# else // if defined(ESP32_CLASSIC) || defined(ESP32C3) - - static_assert(false, "Implement processor architecture"); - -# endif // if defined(ESP32_CLASSIC) || defined(ESP32C3) -# endif // ifdef ESP32 - - return false; -} - -template -PinBootState SettingsStruct_tmpl::getPinBootState(int8_t gpio_pin) const { - if (gpio_pin < 0) return PinBootState::Default_state; -# ifdef ESP8266 - int8_t index_low{}; - - if (getPinBootStateIndex(gpio_pin, index_low)) { - return static_cast(PinBootStates[index_low]); - } - -# endif // ifdef ESP8266 -# ifdef ESP32 - int8_t index_low{}; - int8_t index_high{}; - - if (getPinBootStateIndex(gpio_pin, index_low, index_high)) { - if (index_low >= 0) { - return static_cast(PinBootStates[index_low]); - } - - if (index_high >= 0) { - return static_cast(PinBootStates_ESP32[index_high]); - } - } -# endif // ifdef ESP32 - return PinBootState::Default_state; -} - -template -void SettingsStruct_tmpl::setPinBootState(int8_t gpio_pin, PinBootState state) { - if (gpio_pin < 0) return; -# ifdef ESP8266 - int8_t index_low{}; - - if (getPinBootStateIndex(gpio_pin, index_low)) { - PinBootStates[index_low] = static_cast(state); - } -# endif // ifdef ESP8266 - -# ifdef ESP32 - int8_t index_low{}; - int8_t index_high{}; - - if (getPinBootStateIndex(gpio_pin, index_low, index_high)) { - if (index_low >= 0) { - PinBootStates[index_low] = static_cast(state); - } - - if (index_high >= 0) { - PinBootStates_ESP32[index_high] = static_cast(state); - } - } -# endif // ifdef ESP32 -} - -template -bool SettingsStruct_tmpl::getSPI_pins(int8_t spi_gpios[3]) const { - spi_gpios[0] = -1; - spi_gpios[1] = -1; - spi_gpios[2] = -1; - - if (isSPI_valid()) { - # ifdef ESP32 - const SPI_Options_e SPI_selection = static_cast(InitSPI); - - switch (SPI_selection) { - case SPI_Options_e::Vspi_Fspi: - { - spi_gpios[0] = VSPI_FSPI_SCK; - spi_gpios[1] = VSPI_FSPI_MISO; - spi_gpios[2] = VSPI_FSPI_MOSI; - break; - } -#ifdef ESP32_CLASSIC - case SPI_Options_e::Hspi: - { - spi_gpios[0] = HSPI_SCLK; - spi_gpios[1] = HSPI_MISO; - spi_gpios[2] = HSPI_MOSI; - break; - } -#endif - case SPI_Options_e::UserDefined: - { - spi_gpios[0] = SPI_SCLK_pin; - spi_gpios[1] = SPI_MISO_pin; - spi_gpios[2] = SPI_MOSI_pin; - break; - } - case SPI_Options_e::None: - return false; - } - # endif // ifdef ESP32 - # ifdef ESP8266 - spi_gpios[0] = 14; spi_gpios[1] = 12; spi_gpios[2] = 13; - # endif // ifdef ESP8266 - return true; - } - return false; -} - -#ifdef ESP32 -template -spi_host_device_t SettingsStruct_tmpl::getSPI_host() const -{ - if (isSPI_valid()) { - const SPI_Options_e SPI_selection = static_cast(InitSPI); - switch (SPI_selection) { - case SPI_Options_e::Vspi_Fspi: - { - #if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 - return static_cast(FSPI_HOST); - #else - return static_cast(VSPI_HOST); - #endif - } -#ifdef ESP32_CLASSIC - case SPI_Options_e::Hspi: - { - return static_cast(HSPI_HOST); - } -#endif - case SPI_Options_e::UserDefined: - { - #if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 - return static_cast(FSPI_HOST); - #else - return static_cast(VSPI_HOST); - #endif - } - case SPI_Options_e::None: - break; - } - - } - #if ESP_IDF_VERSION_MAJOR < 5 - #if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 - return static_cast(FSPI_HOST); - #else - return static_cast(VSPI_HOST); - #endif - #else - return spi_host_device_t::SPI_HOST_MAX; - #endif -} -#endif - - -template -bool SettingsStruct_tmpl::isSPI_pin(int8_t pin) const { - if (pin < 0) { return false; } - int8_t spi_gpios[3]; - - if (getSPI_pins(spi_gpios)) { - for (uint8_t i = 0; i < 3; ++i) { - if (spi_gpios[i] == pin) { return true; } - } - } - return false; -} - -template -bool SettingsStruct_tmpl::isSPI_valid() const { - if (InitSPI == static_cast(SPI_Options_e::None)) { return false; } - - if (InitSPI == static_cast(SPI_Options_e::UserDefined)) { - return !((SPI_SCLK_pin == -1) || - (SPI_MISO_pin == -1) || - (SPI_MOSI_pin == -1) || - (SPI_SCLK_pin == SPI_MISO_pin) || - (SPI_MISO_pin == SPI_MOSI_pin) || - (SPI_MOSI_pin == SPI_SCLK_pin)); - } - return true; -} - -template -bool SettingsStruct_tmpl::isI2C_pin(int8_t pin) const { - if (pin < 0) { return false; } - #if !FEATURE_I2C_MULTIPLE - const uint8_t i2cBus = 0; - #else // if !FEATURE_I2C_MULTIPLE - for (uint8_t i2cBus = 0; i2cBus < getI2CBusCount(); ++i2cBus) - #endif // if !FEATURE_I2C_MULTIPLE - { - if ((getI2CSdaPin(i2cBus) == pin) || (getI2CSclPin(i2cBus) == pin)) { - return true; - } - } - return false; -} - -template -bool SettingsStruct_tmpl::isI2CEnabled(uint8_t i2cBus) const { - return (getI2CSdaPin(i2cBus) != -1) && - (getI2CSclPin(i2cBus) != -1) && - (getI2CClockSpeed(i2cBus) > 0) && - (getI2CClockSpeedSlow(i2cBus) > 0); -} - -template -uint8_t SettingsStruct_tmpl::getI2CInterface(taskIndex_t TaskIndex) const { - return get3BitFromUL(I2C_Flags[TaskIndex], I2C_FLAGS_BUS_NUMBER); -} - -template -int8_t SettingsStruct_tmpl::getI2CSdaPin(uint8_t i2cBus) const { - if (0 == i2cBus) { - return Pin_i2c_sda; - #if FEATURE_I2C_MULTIPLE - } else if (1 == i2cBus) { - return Pin_i2c2_sda; - #if FEATURE_I2C_INTERFACE_3 - } else { - return Pin_i2c3_sda; - #endif // if FEATURE_I2C_INTERFACE_3 - #endif // if FEATURE_I2C_MULTIPLE - } - return -1; -} - -template -int8_t SettingsStruct_tmpl::getI2CSclPin(uint8_t i2cBus) const { - if (0 == i2cBus) { - return Pin_i2c_scl; - #if FEATURE_I2C_MULTIPLE - } else if (1 == i2cBus) { - return Pin_i2c2_scl; - #if FEATURE_I2C_INTERFACE_3 - } else { - return Pin_i2c3_scl; - #endif // if FEATURE_I2C_INTERFACE_3 - #endif // if FEATURE_I2C_MULTIPLE - } - return -1; -} - -template -uint32_t SettingsStruct_tmpl::getI2CClockSpeed(uint8_t i2cBus) const { - if (0 == i2cBus) { - return I2C_clockSpeed; - #if FEATURE_I2C_MULTIPLE - } else if (1 == i2cBus) { - return I2C2_clockSpeed; - #if FEATURE_I2C_INTERFACE_3 - } else { - return I2C3_clockSpeed; - #endif // if FEATURE_I2C_INTERFACE_3 - #endif // if FEATURE_I2C_MULTIPLE - } - return 0u; -} - -template -uint32_t SettingsStruct_tmpl::getI2CClockSpeedSlow(uint8_t i2cBus) const { - if (0 == i2cBus) { - return I2C_clockSpeed_Slow; - #if FEATURE_I2C_MULTIPLE - } else if (1 == i2cBus) { - return I2C2_clockSpeed_Slow; - #if FEATURE_I2C_INTERFACE_3 - } else { - return I2C3_clockSpeed_Slow; - #endif // if FEATURE_I2C_INTERFACE_3 - #endif // if FEATURE_I2C_MULTIPLE - } - return 0u; -} - -template -uint32_t SettingsStruct_tmpl::getI2CClockStretch(uint8_t i2cBus) const { - if (0 == i2cBus) { - return WireClockStretchLimit; - } - return 0u; -} - -#if FEATURE_I2C_MULTIPLE -template -uint8_t SettingsStruct_tmpl::getI2CInterfaceRTC() const { - return get3BitFromUL(I2C_peripheral_bus, I2C_PERIPHERAL_BUS_CLOCK); -} - -template -uint8_t SettingsStruct_tmpl::getI2CInterfaceWDT() const { - return get3BitFromUL(I2C_peripheral_bus, I2C_PERIPHERAL_BUS_WDT); -} - -template -uint8_t SettingsStruct_tmpl::getI2CInterfacePCFMCP() const { - return get3BitFromUL(I2C_peripheral_bus, I2C_PERIPHERAL_BUS_PCFMCP); -} -#endif // if FEATURE_I2C_MULTIPLE - -#if FEATURE_I2CMULTIPLEXER -template -int8_t SettingsStruct_tmpl::getI2CMultiplexerType(uint8_t i2cBus) const { - if (0 == i2cBus) { - return I2C_Multiplexer_Type; - #if FEATURE_I2C_MULTIPLE - } else if (1 == i2cBus) { - return I2C2_Multiplexer_Type; - #if FEATURE_I2C_INTERFACE_3 - } else { - return I2C3_Multiplexer_Type; - #endif // if FEATURE_I2C_INTERFACE_3 - #endif // if FEATURE_I2C_MULTIPLE - } - return -1; -} - -template -int8_t SettingsStruct_tmpl::getI2CMultiplexerAddr(uint8_t i2cBus) const { - if (0 == i2cBus) { - return I2C_Multiplexer_Addr; - #if FEATURE_I2C_MULTIPLE - } else if (1 == i2cBus) { - return I2C2_Multiplexer_Addr; - #if FEATURE_I2C_INTERFACE_3 - } else { - return I2C3_Multiplexer_Addr; - #endif // if FEATURE_I2C_INTERFACE_3 - #endif // if FEATURE_I2C_MULTIPLE - } - return -1; -} - -template -int8_t SettingsStruct_tmpl::getI2CMultiplexerResetPin(uint8_t i2cBus) const { - if (0 == i2cBus) { - return I2C_Multiplexer_ResetPin; - #if FEATURE_I2C_MULTIPLE - } else if (1 == i2cBus) { - return I2C2_Multiplexer_ResetPin; - #if FEATURE_I2C_INTERFACE_3 - } else { - return I2C3_Multiplexer_ResetPin; - #endif // if FEATURE_I2C_INTERFACE_3 - #endif // if FEATURE_I2C_MULTIPLE - } - return -1; -} -#endif // if FEATURE_I2CMULTIPLEXER - -template -bool SettingsStruct_tmpl::isEthernetPin(int8_t pin) const { - #if FEATURE_ETHERNET - if (pin < 0) return false; - if (NetworkMedium == NetworkMedium_t::Ethernet && - !isSPI_EthernetType(ETH_Phy_Type)) { - if (19 == pin) return true; // ETH TXD0 - if (21 == pin) return true; // ETH TX EN - if (22 == pin) return true; // ETH TXD1 - if (25 == pin) return true; // ETH RXD0 - if (26 == pin) return true; // ETH RXD1 - if (27 == pin) return true; // ETH CRS_DV - } - #endif // if FEATURE_ETHERNET - return false; -} - - -template -bool SettingsStruct_tmpl::isEthernetPinOptional(int8_t pin) const { - #if FEATURE_ETHERNET - if (pin < 0) return false; - if (NetworkMedium == NetworkMedium_t::Ethernet) { - if (!isSPI_EthernetType(ETH_Phy_Type) && isGpioUsedInETHClockMode(ETH_Clock_Mode, pin)) return true; - if (ETH_Pin_mdc_cs == pin) return true; - if (ETH_Pin_mdio_irq == pin) return true; - if (ETH_Pin_power_rst == pin) return true; - } - #endif // if FEATURE_ETHERNET - return false; -} - -template -int8_t SettingsStruct_tmpl::getTaskDevicePin(taskIndex_t taskIndex, uint8_t pinnr) const { - if (validTaskIndex(taskIndex)) { - switch(pinnr) { - case 1: return TaskDevicePin1[taskIndex]; - case 2: return TaskDevicePin2[taskIndex]; - case 3: return TaskDevicePin3[taskIndex]; - } - } - return -1; -} - -template -float SettingsStruct_tmpl::getWiFi_TX_power() const { - return WiFi_TX_power / 4.0f; -} - -template -void SettingsStruct_tmpl::setWiFi_TX_power(float dBm) { - WiFi_TX_power = dBm * 4.0f; -} - -template -pluginID_t SettingsStruct_tmpl::getPluginID_for_task(taskIndex_t taskIndex) const { - if (validTaskIndex(taskIndex)) { - const uint8_t tdn = TaskDeviceNumber[taskIndex]; - if (tdn > 0) { - return pluginID_t::toPluginID(tdn); - } - } - return INVALID_PLUGIN_ID; -} - -#endif // ifndef DATASTRUCTS_SETTINGSSTRUCT_CPP +#include "../DataStructs/SettingsStruct.h" + +#include "../../ESPEasy_common.h" + +#ifndef DATASTRUCTS_SETTINGSSTRUCT_CPP +#define DATASTRUCTS_SETTINGSSTRUCT_CPP + + +#include "../CustomBuild/CompiletimeDefines.h" +#include "../CustomBuild/ESPEasyLimits.h" +#include "../DataStructs/DeviceStruct.h" +#include "../DataTypes/SPI_options.h" +#include "../DataTypes/NPluginID.h" +#include "../DataTypes/PluginID.h" +#include "../Globals/Plugins.h" +#include "../Globals/CPlugins.h" +#include "../Helpers/Misc.h" +#include "../Helpers/StringParser.h" + +#if FEATURE_I2C_MULTIPLE +#include "../Helpers/Hardware_device_info.h" +#endif + +#if ESP_IDF_VERSION_MAJOR >= 5 +#include +#include "include/esp32x_fixes.h" +#endif + +/* +// VariousBits1 defaults to 0, keep in mind when adding bit lookups. +template +bool SettingsStruct_tmpl::appendUnitToHostname() const { + return !bitRead(VariousBits1, 1); +} + +template +void SettingsStruct_tmpl::appendUnitToHostname(bool value) { + bitWrite(VariousBits1, 1, !value); +} + +template +bool SettingsStruct_tmpl::uniqueMQTTclientIdReconnect_unused() const { + return bitRead(VariousBits1, 2); +} + +template +void SettingsStruct_tmpl::uniqueMQTTclientIdReconnect_unused(bool value) { + bitWrite(VariousBits1, 2, value); +} + +template +bool SettingsStruct_tmpl::OldRulesEngine() const { + #ifdef WEBSERVER_NEW_RULES + return !bitRead(VariousBits1, 3); + #else + return true; + #endif +} + +template +void SettingsStruct_tmpl::OldRulesEngine(bool value) { + bitWrite(VariousBits1, 3, !value); +} + +template +bool SettingsStruct_tmpl::ForceWiFi_bg_mode() const { + return bitRead(VariousBits1, 4); +} + +template +void SettingsStruct_tmpl::ForceWiFi_bg_mode(bool value) { + bitWrite(VariousBits1, 4, value); +} + +template +bool SettingsStruct_tmpl::WiFiRestart_connection_lost() const { + return bitRead(VariousBits1, 5); +} + +template +void SettingsStruct_tmpl::WiFiRestart_connection_lost(bool value) { + bitWrite(VariousBits1, 5, value); +} + +template +bool SettingsStruct_tmpl::EcoPowerMode() const { + return bitRead(VariousBits1, 6); +} + +template +void SettingsStruct_tmpl::EcoPowerMode(bool value) { + bitWrite(VariousBits1, 6, value); +} + +template +bool SettingsStruct_tmpl::WifiNoneSleep() const { + return bitRead(VariousBits1, 7); +} + +template +void SettingsStruct_tmpl::WifiNoneSleep(bool value) { + bitWrite(VariousBits1, 7, value); +} + +// Enable send gratuitous ARP by default, so invert the values (default = 0) +template +bool SettingsStruct_tmpl::gratuitousARP() const { + return !bitRead(VariousBits1, 8); +} + +template +void SettingsStruct_tmpl::gratuitousARP(bool value) { + bitWrite(VariousBits1, 8, !value); +} + +template +bool SettingsStruct_tmpl::TolerantLastArgParse() const { + return bitRead(VariousBits1, 9); +} + +template +void SettingsStruct_tmpl::TolerantLastArgParse(bool value) { + bitWrite(VariousBits1, 9, value); +} + +template +bool SettingsStruct_tmpl::SendToHttp_ack() const { + return bitRead(VariousBits1, 10); +} + +template +void SettingsStruct_tmpl::SendToHttp_ack(bool value) { + bitWrite(VariousBits1, 10, value); +} + +template +bool SettingsStruct_tmpl::UseESPEasyNow() const { +#ifdef USES_ESPEASY_NOW + // First check to see if we have the controller present and enabled + for (controllerIndex_t x = 0; x < CONTROLLER_MAX; x++) { + if (ControllerEnabled[x] && Protocol[x] == 19) { + if (supportedCPluginID(Protocol[x])) { + return ControllerEnabled[x]; + } + } + } + + // FIXME TD-er: Must either remove this bit, or make a settings transition. + return bitRead(VariousBits1, 11); +#else + return false; +#endif +} + +template +void SettingsStruct_tmpl::UseESPEasyNow(bool value) { +#ifdef USES_ESPEASY_NOW + // Update controller enabled state + for (controllerIndex_t x = 0; x < CONTROLLER_MAX; x++) { + if (ControllerEnabled[x] != value && Protocol[x] == 19) { + if (supportedCPluginID(Protocol[x])) { + ControllerEnabled[x] = value; + } + } + } + + bitWrite(VariousBits1, 11, value); +#endif +} + +template +bool SettingsStruct_tmpl::IncludeHiddenSSID() const { + return bitRead(VariousBits1, 12); +} + +template +void SettingsStruct_tmpl::IncludeHiddenSSID(bool value) { + bitWrite(VariousBits1, 12, value); +} + +template +bool SettingsStruct_tmpl::UseMaxTXpowerForSending() const { + return bitRead(VariousBits1, 13); +} + +template +void SettingsStruct_tmpl::UseMaxTXpowerForSending(bool value) { + bitWrite(VariousBits1, 13, value); +} + +template +bool SettingsStruct_tmpl::ApDontForceSetup() const { + return bitRead(VariousBits1, 14); +} + +template +void SettingsStruct_tmpl::ApDontForceSetup(bool value) { + bitWrite(VariousBits1, 14, value); +} + +// VariousBits1 bit 15 was used by PeriodicalScanWiFi +// Now removed, is reset to 0, can be used for some other setting. + +template +bool SettingsStruct_tmpl::JSONBoolWithoutQuotes() const { + return bitRead(VariousBits1, 16); +} + +template +void SettingsStruct_tmpl::JSONBoolWithoutQuotes(bool value) { + bitWrite(VariousBits1, 16, value); +} +*/ + +template +bool SettingsStruct_tmpl::CombineTaskValues_SingleEvent(taskIndex_t taskIndex) const { + if (validTaskIndex(taskIndex)) { + return bitRead(TaskDeviceSendDataFlags[taskIndex], 0); + } + return false; +} + +template +void SettingsStruct_tmpl::CombineTaskValues_SingleEvent(taskIndex_t taskIndex, bool value) { + if (validTaskIndex(taskIndex)) { + bitWrite(TaskDeviceSendDataFlags[taskIndex], 0, value); + } +} + +#if FEATURE_STRING_VARIABLES +template +bool SettingsStruct_tmpl::ShowDerivedTaskValues(taskIndex_t taskIndex) const { + if (validTaskIndex(taskIndex)) { + return bitRead(TaskDeviceSendDataFlags[taskIndex], 1); + } + return false; +} + +template +void SettingsStruct_tmpl::ShowDerivedTaskValues(taskIndex_t taskIndex, bool value) { + if (validTaskIndex(taskIndex)) { + bitWrite(TaskDeviceSendDataFlags[taskIndex], 1, value); + } +} + +template +bool SettingsStruct_tmpl::EventAndLogDerivedTaskValues(taskIndex_t taskIndex) const { + if (validTaskIndex(taskIndex)) { + return bitRead(TaskDeviceSendDataFlags[taskIndex], 2); + } + return false; +} + +template +void SettingsStruct_tmpl::EventAndLogDerivedTaskValues(taskIndex_t taskIndex, bool value) { + if (validTaskIndex(taskIndex)) { + bitWrite(TaskDeviceSendDataFlags[taskIndex], 2, value); + } +} + +template +bool SettingsStruct_tmpl::SendDerivedTaskValues(taskIndex_t taskIndex, controllerIndex_t controllerIndex) const { + if (validTaskIndex(taskIndex) && validControllerIndex(controllerIndex)) { + return bitRead(TaskDeviceSendDataFlags[taskIndex], 3 + controllerIndex); // ATTENTION: uses bits 3..6!!! + } + return false; +} + +template +void SettingsStruct_tmpl::SendDerivedTaskValues(taskIndex_t taskIndex, controllerIndex_t controllerIndex, bool value) { + if (validTaskIndex(taskIndex) && validControllerIndex(controllerIndex)) { + bitWrite(TaskDeviceSendDataFlags[taskIndex], 3 + controllerIndex, value); // ATTENTION: uses bits 3..6!!! + } +} +#endif // if FEATURE_STRING_VARIABLES +/* +template +bool SettingsStruct_tmpl::DoNotStartAP() const { + return bitRead(VariousBits1, 17); +} + +template +void SettingsStruct_tmpl::DoNotStartAP(bool value) { + bitWrite(VariousBits1, 17, value); +} + + +template +bool SettingsStruct_tmpl::UseAlternativeDeepSleep() const { + return bitRead(VariousBits1, 18); +} + +template +void SettingsStruct_tmpl::UseAlternativeDeepSleep(bool value) { + bitWrite(VariousBits1, 18, value); +} + +template +bool SettingsStruct_tmpl::UseLastWiFiFromRTC() const { + return bitRead(VariousBits1, 19); +} + +template +void SettingsStruct_tmpl::UseLastWiFiFromRTC(bool value) { + bitWrite(VariousBits1, 19, value); +} + +template +bool SettingsStruct_tmpl::EnableTimingStats() const { + return bitRead(VariousBits1, 20); +} + +template +void SettingsStruct_tmpl::EnableTimingStats(bool value) { + bitWrite(VariousBits1, 20, value); +} + +template +bool SettingsStruct_tmpl::AllowTaskValueSetAllPlugins() const { + return bitRead(VariousBits1, 21); +} + +template +void SettingsStruct_tmpl::AllowTaskValueSetAllPlugins(bool value) { + bitWrite(VariousBits1, 21, value); +} + +template +bool SettingsStruct_tmpl::EnableClearHangingI2Cbus() const { + return bitRead(VariousBits1, 22); +} + +template +void SettingsStruct_tmpl::EnableClearHangingI2Cbus(bool value) { + bitWrite(VariousBits1, 22, value); +} + +template +bool SettingsStruct_tmpl::EnableRAMTracking() const { + return bitRead(VariousBits1, 23); +} + +template +void SettingsStruct_tmpl::EnableRAMTracking(bool value) { + bitWrite(VariousBits1, 23, value); +} + +template +bool SettingsStruct_tmpl::EnableRulesCaching() const { + return !bitRead(VariousBits1, 24); +} + +template +void SettingsStruct_tmpl::EnableRulesCaching(bool value) { + bitWrite(VariousBits1, 24, !value); +} + +template +bool SettingsStruct_tmpl::EnableRulesEventReorder() const { + return !bitRead(VariousBits1, 25); +} + +template +void SettingsStruct_tmpl::EnableRulesEventReorder(bool value) { + bitWrite(VariousBits1, 25, !value); +} + +template +bool SettingsStruct_tmpl::AllowOTAUnlimited() const { + return bitRead(VariousBits1, 26); +} + +template +void SettingsStruct_tmpl::AllowOTAUnlimited(bool value) { + bitWrite(VariousBits1, 26, value); +} + +template +bool SettingsStruct_tmpl::SendToHTTP_follow_redirects() const { + return bitRead(VariousBits1, 27); +} + +template +void SettingsStruct_tmpl::SendToHTTP_follow_redirects(bool value) { + bitWrite(VariousBits1, 27, value); +} + +#if FEATURE_AUTO_DARK_MODE +template +uint8_t SettingsStruct_tmpl::getCssMode() const { + return get2BitFromUL(VariousBits1, 28); // Also occupies bit 29! +} + +template +void SettingsStruct_tmpl::setCssMode(uint8_t value) { + set2BitToUL(VariousBits1, 28, value); // Also occupies bit 29! +} +#endif // FEATURE_AUTO_DARK_MODE + +#if FEATURE_I2C_DEVICE_CHECK +template +bool SettingsStruct_tmpl::CheckI2Cdevice() const { // Inverted + return !bitRead(VariousBits1, 30); +} + +template +void SettingsStruct_tmpl::CheckI2Cdevice(bool value) { // Inverted + bitWrite(VariousBits1, 30, !value); +} +#endif // if FEATURE_I2C_DEVICE_CHECK +*/ +/* +template +bool SettingsStruct_tmpl::WaitWiFiConnect() const { + return bitRead(VariousBits2, 0); +} + +template +void SettingsStruct_tmpl::WaitWiFiConnect(bool value) { + bitWrite(VariousBits2, 0, value); +} + + +template +bool SettingsStruct_tmpl::SDK_WiFi_autoreconnect() const { + return bitRead(VariousBits2, 1); +} + +template +void SettingsStruct_tmpl::SDK_WiFi_autoreconnect(bool value) { + bitWrite(VariousBits2, 1, value); +} + + +#if FEATURE_RULES_EASY_COLOR_CODE +template +bool SettingsStruct_tmpl::DisableRulesCodeCompletion() const { + return bitRead(VariousBits2, 2); +} + +template +void SettingsStruct_tmpl::DisableRulesCodeCompletion(bool value) { + bitWrite(VariousBits2, 2, value); +} +#endif // if FEATURE_RULES_EASY_COLOR_CODE + +#if FEATURE_TARSTREAM_SUPPORT +template +bool SettingsStruct_tmpl::DisableSaveConfigAsTar() const { + return bitRead(VariousBits2, 3); // Using bit 4 now... +} + +template +void SettingsStruct_tmpl::DisableSaveConfigAsTar(bool value) { + bitWrite(VariousBits2, 3, value); // Using bit 4 now... +} +#endif // if FEATURE_TARSTREAM_SUPPORT +*/ + + +template +bool SettingsStruct_tmpl::isTaskEnableReadonly(taskIndex_t taskIndex) const { + if (validTaskIndex(taskIndex)) { + return bitRead(VariousTaskBits[taskIndex], 0); + } + return false; +} + +template +void SettingsStruct_tmpl::setTaskEnableReadonly(taskIndex_t taskIndex, bool value) { + if (validTaskIndex(taskIndex)) { + bitWrite(VariousTaskBits[taskIndex], 0, value); + } +} + +#if FEATURE_PLUGIN_PRIORITY +template +bool SettingsStruct_tmpl::isPowerManagerTask(taskIndex_t taskIndex) const { + if (validTaskIndex(taskIndex)) { + return bitRead(VariousTaskBits[taskIndex], 1); + } + return false; +} + +template +void SettingsStruct_tmpl::setPowerManagerTask(taskIndex_t taskIndex, bool value) { + if (validTaskIndex(taskIndex)) { + bitWrite(VariousTaskBits[taskIndex], 1, value); + } +} + +template +bool SettingsStruct_tmpl::isPriorityTask(taskIndex_t taskIndex) const { + if (validTaskIndex(taskIndex)) { + return isPowerManagerTask(taskIndex); // Add more? + } + return false; +} +#endif // if FEATURE_PLUGIN_PRIORITY + +template +ExtTimeSource_e SettingsStruct_tmpl::ExtTimeSource() const { + return static_cast(ExternalTimeSource >> 1); +} + +template +void SettingsStruct_tmpl::ExtTimeSource(ExtTimeSource_e value) { + uint8_t newValue = static_cast(value) << 1; + if (UseNTP()) { + newValue += 1; + } + ExternalTimeSource = newValue; +} + +template +bool SettingsStruct_tmpl::UseNTP() const { + return bitRead(ExternalTimeSource, 0); +} + +template +void SettingsStruct_tmpl::UseNTP(bool value) { + bitWrite(ExternalTimeSource, 0, value); +} + + +template +void SettingsStruct_tmpl::validate() { + if (UDPPort > 65535) { UDPPort = 0; } + + if ((Latitude < -90.0f) || (Latitude > 90.0f)) { Latitude = 0.0f; } + + if ((Longitude < -180.0f) || (Longitude > 180.0f)) { Longitude = 0.0f; } + + if (getVariousBits1() > (1u << 31)) { setVariousBits1(0); } // FIXME: Check really needed/useful? + ZERO_TERMINATE(Name); + ZERO_TERMINATE(NTPHost); + + if ((I2C_clockSpeed == 0) || (I2C_clockSpeed > 3400000)) { I2C_clockSpeed = DEFAULT_I2C_CLOCK_SPEED; } + if (WebserverPort == 0) { WebserverPort = 80;} + if (SyslogPort == 0) { SyslogPort = 514; } + + #if FEATURE_DEFINE_SERIAL_CONSOLE_PORT + if (console_serial_port == 0 && UseSerial) { + console_serial_port = DEFAULT_CONSOLE_PORT; + // Set default RX/TX pins for Serial0 + console_serial_rxpin = DEFAULT_CONSOLE_PORT_RXPIN; + console_serial_txpin = DEFAULT_CONSOLE_PORT_TXPIN; + } +#ifdef ESP8266 + if (console_serial_port == 2) { + // Set default RX/TX pins for Serial0 + console_serial_rxpin = DEFAULT_CONSOLE_PORT_RXPIN; + console_serial_txpin = DEFAULT_CONSOLE_PORT_TXPIN; + } else if (console_serial_port == 3) { + // Set default RX/TX pins for Serial0_swapped + console_serial_rxpin = 13; + console_serial_txpin = 15; + } +#endif + #endif +} + +template +bool SettingsStruct_tmpl::networkSettingsEmpty() const { + return IP[0] == 0 && Gateway[0] == 0 && Subnet[0] == 0 && DNS[0] == 0; +} + +template +void SettingsStruct_tmpl::clearNetworkSettings() { + for (uint8_t i = 0; i < 4; ++i) { + IP[i] = 0; + Gateway[i] = 0; + Subnet[i] = 0; + DNS[i] = 0; + ETH_IP[i] = 0; + ETH_Gateway[i] = 0; + ETH_Subnet[i] = 0; + ETH_DNS[i] = 0; + } +} + +template +void SettingsStruct_tmpl::clearTimeSettings() { + ExternalTimeSource = 0; + ZERO_FILL(NTPHost); + TimeZone = 0; + DST = false; + DST_Start = 0; + DST_End = 0; + Latitude = 0.0f; + Longitude = 0.0f; +} + +template +void SettingsStruct_tmpl::clearNotifications() { + for (uint8_t i = 0; i < NOTIFICATION_MAX; ++i) { + Notification[i] = 0u;// .setInvalid(); + NotificationEnabled[i] = false; + } +} + +template +void SettingsStruct_tmpl::clearControllers() { + for (controllerIndex_t i = 0; i < CONTROLLER_MAX; ++i) { + Protocol[i] = 0; + ControllerEnabled[i] = false; + } +} + +template +void SettingsStruct_tmpl::clearTasks() { + for (taskIndex_t task = 0; task < N_TASKS; ++task) { + clearTask(task); + } +} + +template +void SettingsStruct_tmpl::clearLogSettings() { + SyslogLevel = 0; + SerialLogLevel = 0; + WebLogLevel = 0; + SDLogLevel = 0; + SyslogFacility = DEFAULT_SYSLOG_FACILITY; + ZERO_FILL(Syslog_IP); +} + +template +void SettingsStruct_tmpl::clearUnitNameSettings() { + Unit = 0; + ZERO_FILL(Name); + UDPPort = 0; +} + +template +void SettingsStruct_tmpl::clearMisc() { + PID = ESP_PROJECT_PID; + Version = VERSION; + Build = get_build_nr(); + IP_Octet = 0; + Delay = DEFAULT_DELAY; + Pin_i2c_sda = DEFAULT_PIN_I2C_SDA; + Pin_i2c_scl = DEFAULT_PIN_I2C_SCL; + Pin_status_led = DEFAULT_PIN_STATUS_LED; + Pin_status_led_Inversed = DEFAULT_PIN_STATUS_LED_INVERSED; + Pin_sd_cs = -1; +#ifdef ESP32 + #if FEATURE_I2C_MULTIPLE + Pin_i2c2_sda = DEFAULT_PIN_I2C2_SDA; + Pin_i2c2_scl = DEFAULT_PIN_I2C2_SCL; + Pin_i2c3_sda = DEFAULT_PIN_I2C3_SDA; + Pin_i2c3_scl = DEFAULT_PIN_I2C3_SCL; + #endif + // Ethernet related settings are never used on ESP8266 + ETH_Phy_Addr = DEFAULT_ETH_PHY_ADDR; + ETH_Pin_mdc_cs = DEFAULT_ETH_PIN_MDC; + ETH_Pin_mdio_irq = DEFAULT_ETH_PIN_MDIO; + ETH_Pin_power_rst = DEFAULT_ETH_PIN_POWER; + ETH_Phy_Type = DEFAULT_ETH_PHY_TYPE; + ETH_Clock_Mode = DEFAULT_ETH_CLOCK_MODE; +#endif + NetworkMedium = DEFAULT_NETWORK_MEDIUM; + + I2C_clockSpeed_Slow = DEFAULT_I2C_CLOCK_SPEED_SLOW; + I2C_Multiplexer_Type = I2C_MULTIPLEXER_NONE; + I2C_Multiplexer_Addr = -1; + memset(I2C_Multiplexer_Channel, -1, sizeof(I2C_Multiplexer_Channel)); + I2C_Multiplexer_ResetPin = -1; + + { + // Here we initialize all data to 0, so this is the ONLY reason why PinBootStates + // can now be directly accessed. + // In all other use cases, use the get and set functions for it. + + ZERO_FILL(PinBootStates); + # ifdef ESP32 + ZERO_FILL(PinBootStates_ESP32); + # endif // ifdef ESP32 + } + BaudRate = DEFAULT_SERIAL_BAUD; + MessageDelay_unused = 0; + deepSleep_wakeTime = 0; + CustomCSS = false; + WDI2CAddress = 0; + UseRules = DEFAULT_USE_RULES; + UseSerial = DEFAULT_USE_SERIAL; + UseSSDP = false; + WireClockStretchLimit = 0; + I2C_clockSpeed = DEFAULT_I2C_CLOCK_SPEED; + WebserverPort = 80; + SyslogPort = 514; + GlobalSync = false; + ConnectionFailuresThreshold = 0; + MQTTRetainFlag_unused = false; + InitSPI = DEFAULT_SPI; + deepSleepOnFail = false; + UseValueLogger = false; + ArduinoOTAEnable = false; + UseRTOSMultitasking = false; + Pin_Reset = -1; + StructSize = sizeof(SettingsStruct_tmpl); + MQTTUseUnitNameAsClientId_unused = 0; + setVariousBits1(0); + setVariousBits2(0); + + console_serial_port = DEFAULT_CONSOLE_PORT; + console_serial_rxpin = DEFAULT_CONSOLE_PORT_RXPIN; + console_serial_txpin = DEFAULT_CONSOLE_PORT_TXPIN; + console_serial0_fallback = DEFAULT_CONSOLE_SER0_FALLBACK; + + OldRulesEngine(DEFAULT_RULES_OLDENGINE); + ForceWiFi_bg_mode(DEFAULT_WIFI_FORCE_BG_MODE); + WiFiRestart_connection_lost(DEFAULT_WIFI_RESTART_WIFI_CONN_LOST); + EcoPowerMode(DEFAULT_ECO_MODE); + WifiNoneSleep(DEFAULT_WIFI_NONE_SLEEP); + gratuitousARP(DEFAULT_GRATUITOUS_ARP); + TolerantLastArgParse(DEFAULT_TOLERANT_LAST_ARG_PARSE); + SendToHttp_ack(DEFAULT_SEND_TO_HTTP_ACK); + #ifdef USES_ESPEASY_NOW + UseESPEasyNow(DEFAULT_USE_ESPEASYNOW); + #else + UseESPEasyNow(false); + #endif + ApDontForceSetup(DEFAULT_AP_DONT_FORCE_SETUP); + DoNotStartAP(DEFAULT_DONT_ALLOW_START_AP); +} + + +template +void SettingsStruct_tmpl::clearTask(taskIndex_t task) { + if (task >= N_TASKS) { return; } + + for (controllerIndex_t i = 0; i < CONTROLLER_MAX; ++i) { + TaskDeviceID[i][task] = 0u; + TaskDeviceSendData[i][task] = false; + } + TaskDeviceNumber[task] = 0u; //.setInvalid(); + OLD_TaskDeviceID[task] = 0u; // UNUSED: this can be removed + TaskDevicePin1[task] = -1; + TaskDevicePin2[task] = -1; + TaskDevicePin3[task] = -1; + TaskDevicePort[task] = 0u; + TaskDevicePin1PullUp[task] = false; + + for (uint8_t cv = 0; cv < PLUGIN_CONFIGVAR_MAX; ++cv) { + TaskDevicePluginConfig[task][cv] = 0; + } + TaskDevicePin1Inversed[task] = false; + + for (uint8_t cv = 0; cv < PLUGIN_CONFIGFLOATVAR_MAX; ++cv) { + TaskDevicePluginConfigFloat[task][cv] = 0.0f; + } + + for (uint8_t cv = 0; cv < PLUGIN_CONFIGLONGVAR_MAX; ++cv) { + TaskDevicePluginConfigLong[task][cv] = 0; + } + TaskDeviceSendDataFlags[task] = 0u; + VariousTaskBits[task] = 0; + TaskDeviceDataFeed[task] = 0u; + TaskDeviceTimer[task] = 0u; +// TaskDeviceEnabled[task].value = 0u; // Should also clear any temporary flags. + TaskDeviceEnabled[task] = false; + I2C_Multiplexer_Channel[task] = -1; +} + +template +String SettingsStruct_tmpl::getHostname() const { + return this->getHostname(this->appendUnitToHostname()); +} + +template +String SettingsStruct_tmpl::getHostname(bool appendUnit) const { + String hostname = this->getName(); + + if ((this->Unit != 0) && appendUnit) { // only append non-zero unit number + hostname += '_'; + hostname += this->Unit; + } + return hostname; +} + +template +String SettingsStruct_tmpl::getName() const { + String unitname = this->Name; + + return parseTemplate(unitname); +} + +template +bool SettingsStruct_tmpl::getPinBootStateIndex( + int8_t gpio_pin, + int8_t& index_low + # ifdef ESP32 + , int8_t& index_high + # endif // ifdef ESP32 + ) const { + index_low = -1; +# ifdef ESP32 + index_high = -1; + if ((gpio_pin < 0) || !(GPIO_IS_VALID_GPIO(gpio_pin))) { return false; } +# endif // ifdef ESP32 + constexpr int maxStates = NR_ELEMENTS(PinBootStates); + + if (gpio_pin < maxStates) { + index_low = gpio_pin; + return true; + } +# ifdef ESP32 + constexpr int maxStatesesp32 = NR_ELEMENTS(PinBootStates_ESP32); + + index_high = gpio_pin - maxStates; + +# if defined(ESP32_CLASSIC) || defined(ESP32C2) || defined(ESP32C3)|| defined(ESP32C6) + + // These can all store in the PinBootStates_ESP32 array + return (index_high < maxStatesesp32); + +# elif defined(ESP32S2) + + // First make sure we're not dealing with flash/PSRAM connected pins + if (!((gpio_pin > 21) && (gpio_pin < 33) && (gpio_pin != 26))) { + // Previously used index, to maintain compatibility with previous settings. + + if (index_high >= maxStatesesp32) { + // Now try to fix the bug by inserting the missing ones into unused spots + // This way we don't need to convert existing settings + index_high -= maxStatesesp32; + constexpr int8_t offsetFlashPin = 22 - maxStates; + index_high += offsetFlashPin; + + if (gpio_pin >= 26) { + // Skip index for GPIO 26 + ++index_high; + } + } + return (index_high < maxStatesesp32); + } + +# elif defined(ESP32S3) + + // GPIO 22 ... 32 should never be used. + // Thus: + // - map ... <21> to the beginning of PinBootStates_ESP32 + // - map <33> ... <48> to the end of PinBootStates_ESP32 + if (gpio_pin < 22) { + return true; + } + + if (gpio_pin >= 33) { + index_high = gpio_pin - maxStates + 22 - 33; + return true; + } +# else // if defined(ESP32_CLASSIC) || defined(ESP32C3) + + static_assert(false, "Implement processor architecture"); + +# endif // if defined(ESP32_CLASSIC) || defined(ESP32C3) +# endif // ifdef ESP32 + + return false; +} + +template +PinBootState SettingsStruct_tmpl::getPinBootState(int8_t gpio_pin) const { + if (gpio_pin < 0) return PinBootState::Default_state; +# ifdef ESP8266 + int8_t index_low{}; + + if (getPinBootStateIndex(gpio_pin, index_low)) { + return static_cast(PinBootStates[index_low]); + } + +# endif // ifdef ESP8266 +# ifdef ESP32 + int8_t index_low{}; + int8_t index_high{}; + + if (getPinBootStateIndex(gpio_pin, index_low, index_high)) { + if (index_low >= 0) { + return static_cast(PinBootStates[index_low]); + } + + if (index_high >= 0) { + return static_cast(PinBootStates_ESP32[index_high]); + } + } +# endif // ifdef ESP32 + return PinBootState::Default_state; +} + +template +void SettingsStruct_tmpl::setPinBootState(int8_t gpio_pin, PinBootState state) { + if (gpio_pin < 0) return; +# ifdef ESP8266 + int8_t index_low{}; + + if (getPinBootStateIndex(gpio_pin, index_low)) { + PinBootStates[index_low] = static_cast(state); + } +# endif // ifdef ESP8266 + +# ifdef ESP32 + int8_t index_low{}; + int8_t index_high{}; + + if (getPinBootStateIndex(gpio_pin, index_low, index_high)) { + if (index_low >= 0) { + PinBootStates[index_low] = static_cast(state); + } + + if (index_high >= 0) { + PinBootStates_ESP32[index_high] = static_cast(state); + } + } +# endif // ifdef ESP32 +} + +template +bool SettingsStruct_tmpl::getSPI_pins(int8_t spi_gpios[3]) const { + spi_gpios[0] = -1; + spi_gpios[1] = -1; + spi_gpios[2] = -1; + + if (isSPI_valid()) { + # ifdef ESP32 + const SPI_Options_e SPI_selection = static_cast(InitSPI); + + switch (SPI_selection) { + case SPI_Options_e::Vspi_Fspi: + { + spi_gpios[0] = VSPI_FSPI_SCK; + spi_gpios[1] = VSPI_FSPI_MISO; + spi_gpios[2] = VSPI_FSPI_MOSI; + break; + } +#ifdef ESP32_CLASSIC + case SPI_Options_e::Hspi: + { + spi_gpios[0] = HSPI_SCLK; + spi_gpios[1] = HSPI_MISO; + spi_gpios[2] = HSPI_MOSI; + break; + } +#endif + case SPI_Options_e::UserDefined: + { + spi_gpios[0] = SPI_SCLK_pin; + spi_gpios[1] = SPI_MISO_pin; + spi_gpios[2] = SPI_MOSI_pin; + break; + } + case SPI_Options_e::None: + return false; + } + # endif // ifdef ESP32 + # ifdef ESP8266 + spi_gpios[0] = 14; spi_gpios[1] = 12; spi_gpios[2] = 13; + # endif // ifdef ESP8266 + return true; + } + return false; +} + +#ifdef ESP32 +template +spi_host_device_t SettingsStruct_tmpl::getSPI_host() const +{ + if (isSPI_valid()) { + const SPI_Options_e SPI_selection = static_cast(InitSPI); + switch (SPI_selection) { + case SPI_Options_e::Vspi_Fspi: + { + #if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 + return static_cast(FSPI_HOST); + #else + return static_cast(VSPI_HOST); + #endif + } +#ifdef ESP32_CLASSIC + case SPI_Options_e::Hspi: + { + return static_cast(HSPI_HOST); + } +#endif + case SPI_Options_e::UserDefined: + { + #if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 + return static_cast(FSPI_HOST); + #else + return static_cast(VSPI_HOST); + #endif + } + case SPI_Options_e::None: + break; + } + + } + #if ESP_IDF_VERSION_MAJOR < 5 + #if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 + return static_cast(FSPI_HOST); + #else + return static_cast(VSPI_HOST); + #endif + #else + return spi_host_device_t::SPI_HOST_MAX; + #endif +} +#endif + + +template +bool SettingsStruct_tmpl::isSPI_pin(int8_t pin) const { + if (pin < 0) { return false; } + int8_t spi_gpios[3]; + + if (getSPI_pins(spi_gpios)) { + for (uint8_t i = 0; i < 3; ++i) { + if (spi_gpios[i] == pin) { return true; } + } + } + return false; +} + +template +bool SettingsStruct_tmpl::isSPI_valid() const { + if (InitSPI == static_cast(SPI_Options_e::None)) { return false; } + + if (InitSPI == static_cast(SPI_Options_e::UserDefined)) { + return !((SPI_SCLK_pin == -1) || + (SPI_MISO_pin == -1) || + (SPI_MOSI_pin == -1) || + (SPI_SCLK_pin == SPI_MISO_pin) || + (SPI_MISO_pin == SPI_MOSI_pin) || + (SPI_MOSI_pin == SPI_SCLK_pin)); + } + return true; +} + +template +bool SettingsStruct_tmpl::isI2C_pin(int8_t pin) const { + if (pin < 0) { return false; } + #if !FEATURE_I2C_MULTIPLE + const uint8_t i2cBus = 0; + #else // if !FEATURE_I2C_MULTIPLE + for (uint8_t i2cBus = 0; i2cBus < getI2CBusCount(); ++i2cBus) + #endif // if !FEATURE_I2C_MULTIPLE + { + if ((getI2CSdaPin(i2cBus) == pin) || (getI2CSclPin(i2cBus) == pin)) { + return true; + } + } + return false; +} + +template +bool SettingsStruct_tmpl::isI2CEnabled(uint8_t i2cBus) const { + return (getI2CSdaPin(i2cBus) != -1) && + (getI2CSclPin(i2cBus) != -1) && + (getI2CClockSpeed(i2cBus) > 0) && + (getI2CClockSpeedSlow(i2cBus) > 0); +} + +template +uint8_t SettingsStruct_tmpl::getI2CInterface(taskIndex_t TaskIndex) const { + return get3BitFromUL(I2C_Flags[TaskIndex], I2C_FLAGS_BUS_NUMBER); +} + +template +int8_t SettingsStruct_tmpl::getI2CSdaPin(uint8_t i2cBus) const { + if (0 == i2cBus) { + return Pin_i2c_sda; + #if FEATURE_I2C_MULTIPLE + } else if (1 == i2cBus) { + return Pin_i2c2_sda; + #if FEATURE_I2C_INTERFACE_3 + } else { + return Pin_i2c3_sda; + #endif // if FEATURE_I2C_INTERFACE_3 + #endif // if FEATURE_I2C_MULTIPLE + } + return -1; +} + +template +int8_t SettingsStruct_tmpl::getI2CSclPin(uint8_t i2cBus) const { + if (0 == i2cBus) { + return Pin_i2c_scl; + #if FEATURE_I2C_MULTIPLE + } else if (1 == i2cBus) { + return Pin_i2c2_scl; + #if FEATURE_I2C_INTERFACE_3 + } else { + return Pin_i2c3_scl; + #endif // if FEATURE_I2C_INTERFACE_3 + #endif // if FEATURE_I2C_MULTIPLE + } + return -1; +} + +template +uint32_t SettingsStruct_tmpl::getI2CClockSpeed(uint8_t i2cBus) const { + if (0 == i2cBus) { + return I2C_clockSpeed; + #if FEATURE_I2C_MULTIPLE + } else if (1 == i2cBus) { + return I2C2_clockSpeed; + #if FEATURE_I2C_INTERFACE_3 + } else { + return I2C3_clockSpeed; + #endif // if FEATURE_I2C_INTERFACE_3 + #endif // if FEATURE_I2C_MULTIPLE + } + return 0u; +} + +template +uint32_t SettingsStruct_tmpl::getI2CClockSpeedSlow(uint8_t i2cBus) const { + if (0 == i2cBus) { + return I2C_clockSpeed_Slow; + #if FEATURE_I2C_MULTIPLE + } else if (1 == i2cBus) { + return I2C2_clockSpeed_Slow; + #if FEATURE_I2C_INTERFACE_3 + } else { + return I2C3_clockSpeed_Slow; + #endif // if FEATURE_I2C_INTERFACE_3 + #endif // if FEATURE_I2C_MULTIPLE + } + return 0u; +} + +template +uint32_t SettingsStruct_tmpl::getI2CClockStretch(uint8_t i2cBus) const { + if (0 == i2cBus) { + return WireClockStretchLimit; + } + return 0u; +} + +#if FEATURE_I2C_MULTIPLE +template +uint8_t SettingsStruct_tmpl::getI2CInterfaceRTC() const { + return get3BitFromUL(I2C_peripheral_bus, I2C_PERIPHERAL_BUS_CLOCK); +} + +template +uint8_t SettingsStruct_tmpl::getI2CInterfaceWDT() const { + return get3BitFromUL(I2C_peripheral_bus, I2C_PERIPHERAL_BUS_WDT); +} + +template +uint8_t SettingsStruct_tmpl::getI2CInterfacePCFMCP() const { + return get3BitFromUL(I2C_peripheral_bus, I2C_PERIPHERAL_BUS_PCFMCP); +} +#endif // if FEATURE_I2C_MULTIPLE + +#if FEATURE_I2CMULTIPLEXER +template +int8_t SettingsStruct_tmpl::getI2CMultiplexerType(uint8_t i2cBus) const { + if (0 == i2cBus) { + return I2C_Multiplexer_Type; + #if FEATURE_I2C_MULTIPLE + } else if (1 == i2cBus) { + return I2C2_Multiplexer_Type; + #if FEATURE_I2C_INTERFACE_3 + } else { + return I2C3_Multiplexer_Type; + #endif // if FEATURE_I2C_INTERFACE_3 + #endif // if FEATURE_I2C_MULTIPLE + } + return -1; +} + +template +int8_t SettingsStruct_tmpl::getI2CMultiplexerAddr(uint8_t i2cBus) const { + if (0 == i2cBus) { + return I2C_Multiplexer_Addr; + #if FEATURE_I2C_MULTIPLE + } else if (1 == i2cBus) { + return I2C2_Multiplexer_Addr; + #if FEATURE_I2C_INTERFACE_3 + } else { + return I2C3_Multiplexer_Addr; + #endif // if FEATURE_I2C_INTERFACE_3 + #endif // if FEATURE_I2C_MULTIPLE + } + return -1; +} + +template +int8_t SettingsStruct_tmpl::getI2CMultiplexerResetPin(uint8_t i2cBus) const { + if (0 == i2cBus) { + return I2C_Multiplexer_ResetPin; + #if FEATURE_I2C_MULTIPLE + } else if (1 == i2cBus) { + return I2C2_Multiplexer_ResetPin; + #if FEATURE_I2C_INTERFACE_3 + } else { + return I2C3_Multiplexer_ResetPin; + #endif // if FEATURE_I2C_INTERFACE_3 + #endif // if FEATURE_I2C_MULTIPLE + } + return -1; +} +#endif // if FEATURE_I2CMULTIPLEXER + +template +bool SettingsStruct_tmpl::isEthernetPin(int8_t pin) const { + #if FEATURE_ETHERNET + if (pin < 0) return false; + if (NetworkMedium == NetworkMedium_t::Ethernet && + !isSPI_EthernetType(ETH_Phy_Type)) { + if (19 == pin) return true; // ETH TXD0 + if (21 == pin) return true; // ETH TX EN + if (22 == pin) return true; // ETH TXD1 + if (25 == pin) return true; // ETH RXD0 + if (26 == pin) return true; // ETH RXD1 + if (27 == pin) return true; // ETH CRS_DV + } + #endif // if FEATURE_ETHERNET + return false; +} + + +template +bool SettingsStruct_tmpl::isEthernetPinOptional(int8_t pin) const { + #if FEATURE_ETHERNET + if (pin < 0) return false; + if (NetworkMedium == NetworkMedium_t::Ethernet) { + if (!isSPI_EthernetType(ETH_Phy_Type) && isGpioUsedInETHClockMode(ETH_Clock_Mode, pin)) return true; + if (ETH_Pin_mdc_cs == pin) return true; + if (ETH_Pin_mdio_irq == pin) return true; + if (ETH_Pin_power_rst == pin) return true; + } + #endif // if FEATURE_ETHERNET + return false; +} + +template +int8_t SettingsStruct_tmpl::getTaskDevicePin(taskIndex_t taskIndex, uint8_t pinnr) const { + if (validTaskIndex(taskIndex)) { + switch(pinnr) { + case 1: return TaskDevicePin1[taskIndex]; + case 2: return TaskDevicePin2[taskIndex]; + case 3: return TaskDevicePin3[taskIndex]; + } + } + return -1; +} + +template +float SettingsStruct_tmpl::getWiFi_TX_power() const { + return WiFi_TX_power / 4.0f; +} + +template +void SettingsStruct_tmpl::setWiFi_TX_power(float dBm) { + WiFi_TX_power = dBm * 4.0f; +} + +template +pluginID_t SettingsStruct_tmpl::getPluginID_for_task(taskIndex_t taskIndex) const { + if (validTaskIndex(taskIndex)) { + const uint8_t tdn = TaskDeviceNumber[taskIndex]; + if (tdn > 0) { + return pluginID_t::toPluginID(tdn); + } + } + return INVALID_PLUGIN_ID; +} + +#endif // ifndef DATASTRUCTS_SETTINGSSTRUCT_CPP diff --git a/src/src/DataTypes/ESPEasyFileType.h b/src/src/DataTypes/ESPEasyFileType.h index 48dbf5567a..393bb2e4f8 100644 --- a/src/src/DataTypes/ESPEasyFileType.h +++ b/src/src/DataTypes/ESPEasyFileType.h @@ -1,33 +1,33 @@ -#ifndef DATATYPES_ESPEASYFILETYPE_H -#define DATATYPES_ESPEASYFILETYPE_H - -#include "../../ESPEasy_common.h" - -struct FileType { - enum Enum : short { - CONFIG_DAT, - SECURITY_DAT, - RULES_TXT, - NOTIFICATION_DAT, - PROVISIONING_DAT, - - MAX_FILETYPE - }; -}; - -bool matchFileType(const String& filename, FileType::Enum filetype); - -bool isProtectedFileType(const String& filename); - -const __FlashStringHelper * getFileName(FileType::Enum filetype); -String getFileName(FileType::Enum filetype, - unsigned int filenr); - -// filenr = 0...3 for files rules1.txt ... rules4.txt -String getRulesFileName(unsigned int filenr); - -bool getDownloadFiletypeChecked(FileType::Enum filetype, - unsigned int filenr); - - -#endif // ifndef DATATYPES_ESPEASYFILETYPE_H \ No newline at end of file +#ifndef DATATYPES_ESPEASYFILETYPE_H +#define DATATYPES_ESPEASYFILETYPE_H + +#include "../../ESPEasy_common.h" + +struct FileType { + enum Enum : short { + CONFIG_DAT, + SECURITY_DAT, + RULES_TXT, + NOTIFICATION_DAT, + PROVISIONING_DAT, + + MAX_FILETYPE + }; +}; + +bool matchFileType(const String& filename, FileType::Enum filetype); + +bool isProtectedFileType(const String& filename); + +const __FlashStringHelper * getFileName(FileType::Enum filetype); +String getFileName(FileType::Enum filetype, + unsigned int filenr); + +// filenr = 0...3 for files rules1.txt ... rules4.txt +String getRulesFileName(unsigned int filenr); + +bool getDownloadFiletypeChecked(FileType::Enum filetype, + unsigned int filenr); + + +#endif // ifndef DATATYPES_ESPEASYFILETYPE_H diff --git a/src/src/DataTypes/ESPEasyTimeSource.h b/src/src/DataTypes/ESPEasyTimeSource.h index def9bb803e..73c367d894 100644 --- a/src/src/DataTypes/ESPEasyTimeSource.h +++ b/src/src/DataTypes/ESPEasyTimeSource.h @@ -1,52 +1,52 @@ -#ifndef DATATYPES_ESPEASYTIMESOURCE_H -#define DATATYPES_ESPEASYTIMESOURCE_H - - -#include "../../ESPEasy_common.h" - -#include - - -class String; - -#define EXT_TIME_SOURCE_MIN_UPDATE_INTERVAL_MSEC 1800000 -#define EXT_TIME_SOURCE_MIN_UPDATE_INTERVAL_SEC 3600 - -// Time Source type, sort by priority. -// Enum values are sent via NodeStruct, so only add new ones and don't change existing values -// typical time wander of an ESP module is 40 ppm, or 0.04 msec/sec, or roughly 3.5 seconds per 24h. -enum class timeSource_t : uint8_t { - // External time source (considered more reliable) - GPS_PPS_time_source = 5, // 1 - 10 msec accuracy - GPS_time_source = 10, // 10 - 100 msec accuracy - NTP_time_source = 15, // 20 - 100 msec accuracy - - // Manual override has higher priority because it is some kind of external sync - Manual_set = 20, // Unknown accuracy - - // Sources which may drift over time due to lack of external synchronization. - ESP_now_peer = 40, // < 5 msec accuracy between nodes, but time on the whole network may drift - ESPEASY_p2p_UDP = 41, - External_RTC_time_source = 45, // Typically +/- 500 msec off. - GPS_time_source_no_fix = 46, // Typically 500 - 1000 msec off. - - Restore_RTC_time_source = 50, // > 1 sec difference per reboot - No_time_source = 255 // No time set -}; - -const __FlashStringHelper* toString(timeSource_t timeSource); -bool isExternalTimeSource(timeSource_t timeSource); - -// Only use peers if there is no external source available. -// A network without external synced source may drift as a whole -// All nodes in the network may be in sync with each other, but get out of sync with the rest of the world. -// Therefore use a strong bias for external synced nodes. -// But also must make sure the same NTP synced node will be held responsible for the entire network. -uint32_t computeExpectedWander(timeSource_t timeSource, - uint32_t timePassedSinceLastTimeSync = 0u); - -uint32_t updateExpectedWander( - int32_t current_wander, - uint32_t timePassedSinceLastTimeSync); - -#endif /* DATATYPES_ESPEASYTIMESOURCE_H */ \ No newline at end of file +#ifndef DATATYPES_ESPEASYTIMESOURCE_H +#define DATATYPES_ESPEASYTIMESOURCE_H + + +#include "../../ESPEasy_common.h" + +#include + + +class String; + +#define EXT_TIME_SOURCE_MIN_UPDATE_INTERVAL_MSEC 1800000 +#define EXT_TIME_SOURCE_MIN_UPDATE_INTERVAL_SEC 3600 + +// Time Source type, sort by priority. +// Enum values are sent via NodeStruct, so only add new ones and don't change existing values +// typical time wander of an ESP module is 40 ppm, or 0.04 msec/sec, or roughly 3.5 seconds per 24h. +enum class timeSource_t : uint8_t { + // External time source (considered more reliable) + GPS_PPS_time_source = 5, // 1 - 10 msec accuracy + GPS_time_source = 10, // 10 - 100 msec accuracy + NTP_time_source = 15, // 20 - 100 msec accuracy + + // Manual override has higher priority because it is some kind of external sync + Manual_set = 20, // Unknown accuracy + + // Sources which may drift over time due to lack of external synchronization. + ESP_now_peer = 40, // < 5 msec accuracy between nodes, but time on the whole network may drift + ESPEASY_p2p_UDP = 41, + External_RTC_time_source = 45, // Typically +/- 500 msec off. + GPS_time_source_no_fix = 46, // Typically 500 - 1000 msec off. + + Restore_RTC_time_source = 50, // > 1 sec difference per reboot + No_time_source = 255 // No time set +}; + +const __FlashStringHelper* toString(timeSource_t timeSource); +bool isExternalTimeSource(timeSource_t timeSource); + +// Only use peers if there is no external source available. +// A network without external synced source may drift as a whole +// All nodes in the network may be in sync with each other, but get out of sync with the rest of the world. +// Therefore use a strong bias for external synced nodes. +// But also must make sure the same NTP synced node will be held responsible for the entire network. +uint32_t computeExpectedWander(timeSource_t timeSource, + uint32_t timePassedSinceLastTimeSync = 0u); + +uint32_t updateExpectedWander( + int32_t current_wander, + uint32_t timePassedSinceLastTimeSync); + +#endif /* DATATYPES_ESPEASYTIMESOURCE_H */ diff --git a/src/src/DataTypes/ESPEasy_Now_MQTT_queue_check_state.cpp b/src/src/DataTypes/ESPEasy_Now_MQTT_queue_check_state.cpp new file mode 100644 index 0000000000..3d9c985b77 --- /dev/null +++ b/src/src/DataTypes/ESPEasy_Now_MQTT_queue_check_state.cpp @@ -0,0 +1 @@ +#include "../DataTypes/ESPEasy_Now_MQTT_queue_check_state.h" diff --git a/src/src/DataTypes/ESPEasy_Now_MQTT_queue_check_state.h b/src/src/DataTypes/ESPEasy_Now_MQTT_queue_check_state.h new file mode 100644 index 0000000000..f45906dd2d --- /dev/null +++ b/src/src/DataTypes/ESPEasy_Now_MQTT_queue_check_state.h @@ -0,0 +1,18 @@ +#ifndef DATATYPES_ESPEASY_NOW_MQTT_QUEUE_CHECK_STATE_H +#define DATATYPES_ESPEASY_NOW_MQTT_QUEUE_CHECK_STATE_H + + +#include +#include + +#include "../../ESPEasy_common.h" + +struct ESPEasy_Now_MQTT_QueueCheckState { + enum Enum : uint8_t { + Unset = 0, + Empty = 1, + Full = 2 + }; +}; + +#endif // ifndef DATATYPES_ESPEASY_NOW_MQTT_QUEUE_CHECK_STATE_H diff --git a/src/src/DataTypes/ESPEasy_plugin_functions.h b/src/src/DataTypes/ESPEasy_plugin_functions.h index f3e4604383..2ca7b73de5 100644 --- a/src/src/DataTypes/ESPEasy_plugin_functions.h +++ b/src/src/DataTypes/ESPEasy_plugin_functions.h @@ -1,138 +1,139 @@ -#ifndef DATATYPES_ESPEASY_PLUGIN_DEFS_H -#define DATATYPES_ESPEASY_PLUGIN_DEFS_H - -#include "../../ESPEasy_common.h" - - -// ******************************************************************************** -// Plugin (Task) function calls -// ******************************************************************************** -enum PluginFunctions_e { - PLUGIN_INIT_ALL , // Not implemented in a plugin, only called during boot - PLUGIN_INIT , // Init the task, called when task is set to enabled (also at boot) - PLUGIN_READ , // This call can yield new data (when success = true) and then send to controllers - PLUGIN_ONCE_A_SECOND , // Called once a second - PLUGIN_TEN_PER_SECOND , // Called 10x per second (typical for checking new data instead of waiting) - PLUGIN_DEVICE_ADD , // Called at boot for letting a plugin adding itself to list of available plugins/devices - PLUGIN_EVENTLIST_ADD , // Not used. - PLUGIN_WEBFORM_SAVE , // Call from web interface to save settings - PLUGIN_WEBFORM_LOAD , // Call from web interface for presenting settings and status of plugin - PLUGIN_WEBFORM_SHOW_VALUES , // Call from devices overview page to format values in HTML - PLUGIN_GET_DEVICENAME , // Call to get the plugin description (e.g. "Switch input - Switch") - PLUGIN_GET_DEVICEVALUENAMES , // Call to let the plugin generate some default value names when not defined. - PLUGIN_GET_DEVICEVALUECOUNT , // Optional function call to allow tasks to specify the number of output values (e.g. P026_Sysinfo.ino) - PLUGIN_GET_DEVICEVTYPE , // Only needed when Device[deviceCount].OutputDataType is not Output_Data_type_t::Default - PLUGIN_WRITE , // Called to allow a task to process a command. Must return success = true when it can handle the command. -// PLUGIN_EVENT_OUT , // Does not seem to be used - PLUGIN_WEBFORM_SHOW_CONFIG , // Called to show non default pin assignment or addresses like for plugins using serial or 1-Wire - PLUGIN_SERIAL_IN , // Called on received data via serial port Serial0 (N.B. this may conflict with sending commands via serial) - PLUGIN_UDP_IN , // Called for received UDP data via ESPEasy p2p which isn't a standard p2p packet. (See C013 for handling standard p2p packets) - PLUGIN_CLOCK_IN , // Called every new minute - PLUGIN_TASKTIMER_IN , // Called with a previously defined event at a specific time, set via setPluginTaskTimer - PLUGIN_FIFTY_PER_SECOND , // Called 50 times per second - PLUGIN_SET_CONFIG , // Counterpart of PLUGIN_GET_CONFIG_VALUE to allow to set a config via a command. - PLUGIN_GET_DEVICEGPIONAMES , // Allow for specific formatting of the label for standard pin configuration (e.g. "GPIO <- TX") - PLUGIN_EXIT , // Called when a task no longer is enabled (or deleted) - PLUGIN_GET_CONFIG_VALUE , // Similar to PLUGIN_WRITE, but meant to fetch some information. Must return success = true when it can handle the command. Can also be used to access extra unused task values. -// PLUGIN_UNCONDITIONAL_POLL , // Used to be called 10x per sec, but no longer used as GPIO related plugins now use a different technique. - PLUGIN_REQUEST , // Specific command to fetch a state (FIXME TD-er: Seems very similar to PLUGIN_GET_CONFIG_VALUE) - PLUGIN_TIME_CHANGE , // Called when system time is set (e.g. via NTP) - PLUGIN_MONITOR , // Replaces PLUGIN_UNCONDITIONAL_POLL - PLUGIN_SET_DEFAULTS , // Called when assigning a plugin to a task, to set some default config. - PLUGIN_GET_PACKED_RAW_DATA , // Return all data in a compact binary format specific for that plugin. - // Needs FEATURE_PACKED_RAW_DATA - PLUGIN_DEVICETIMER_IN , // Similar to PLUGIN_TASKTIMER_IN, addressed to a plugin instead of a task. - PLUGIN_WEBFORM_SHOW_I2C_PARAMS , // Show I2C parameters like address. - PLUGIN_WEBFORM_SHOW_SERIAL_PARAMS , // When needed, show additional parameters like baudrate or specific serial config - PLUGIN_MQTT_CONNECTION_STATE , // Signal when connection to MQTT broker is re-established - PLUGIN_MQTT_IMPORT , // For P037 MQTT import - PLUGIN_FORMAT_USERVAR , // Allow plugin specific formatting of a task variable (event->idx = variable) - PLUGIN_WEBFORM_SHOW_GPIO_DESCR , // Show GPIO description on devices overview tab -#if FEATURE_PLUGIN_STATS - PLUGIN_WEBFORM_LOAD_SHOW_STATS , // Show PluginStats on task config page -#endif // if FEATURE_PLUGIN_STATS - PLUGIN_I2C_HAS_ADDRESS , // Check the I2C addresses from the plugin, output in 'success' - PLUGIN_I2C_GET_ADDRESS , // Get the current I2C addresses from the plugin, output in 'event->Par1' and 'success' - PLUGIN_GET_DISPLAY_PARAMETERS , // Fetch X/Y resolution and Rotation setting from the plugin, output in 'success' - PLUGIN_WEBFORM_SHOW_ERRORSTATE_OPT , // Show Error State Value options, so be saved during PLUGIN_WEBFORM_SAVE - PLUGIN_INIT_VALUE_RANGES , // Initialize the ranges of values, called just before PLUGIN_INIT - PLUGIN_READ_ERROR_OCCURED , // Function returns "true" when last measurement was an error, called when PLUGIN_READ returns false - PLUGIN_WEBFORM_LOAD_OUTPUT_SELECTOR, // Show the configuration for output type and what value to set to which taskvalue - PLUGIN_PROCESS_CONTROLLER_DATA , // Can be called from the controller to signal the plugin to generate (or handle) sending the data. - PLUGIN_PRIORITY_INIT_ALL , // Pre-initialize all plugins that are set to PowerManager priority (not implemented in plugins) - PLUGIN_PRIORITY_INIT , // Pre-initialize a singe plugins that is set to PowerManager priority - PLUGIN_WEBFORM_LOAD_ALWAYS , // Loaded *after* PLUGIN_WEBFORM_LOAD, also shown for remote data-feed devices -#ifdef USES_ESPEASY_NOW - PLUGIN_FILTEROUT_CONTROLLER_DATA , // Can be called from the controller to query a task whether the data should be processed further. -#endif - PLUGIN_WEBFORM_PRE_SERIAL_PARAMS , // Before serial parameters, convert additional parameters like baudrate or specific serial config - #if FEATURE_MQTT_DISCOVER || FEATURE_CUSTOM_TASKVAR_VTYPE - PLUGIN_GET_DISCOVERY_VTYPES , // Fetch the Sensor_VType values for dynamic value configurations in event->Par1..Par4 - #endif // IF FEATURE_MQTT_DISCOVER || FEATURE_CUSTOM_TASKVAR_VTYPE - - PLUGIN_MAX_FUNCTION // Leave as last one. -}; - - -#define NrBitsPluginFunctions NR_BITS(static_cast(PLUGIN_MAX_FUNCTION)) - -// ******************************************************************************** -// CPlugin (Controller) function calls -// ******************************************************************************** - -class CPlugin { -public: - - // As these function values are also used in the timing stats, make sure there is no overlap with the PLUGIN_xxx numbering. - - enum class Function { - CPLUGIN_PROTOCOL_ADD = 127, // Called at boot for letting a controller adding itself to list of available controllers - CPLUGIN_CONNECT_SUCCESS, // Only used for timing stats - CPLUGIN_CONNECT_FAIL, // Only used for timing stats - CPLUGIN_PROTOCOL_TEMPLATE, - CPLUGIN_PROTOCOL_SEND, - CPLUGIN_PROTOCOL_RECV, - CPLUGIN_GET_DEVICENAME, - CPLUGIN_WEBFORM_SAVE, - CPLUGIN_WEBFORM_LOAD, - CPLUGIN_GET_PROTOCOL_DISPLAY_NAME, - CPLUGIN_TASK_CHANGE_NOTIFICATION, - CPLUGIN_INIT, - CPLUGIN_UDP_IN, - CPLUGIN_FLUSH, // Force offloading data stored in buffers, called before sleep/reboot - CPLUGIN_TEN_PER_SECOND, // Called 10x per second (typical for checking new data instead of waiting) - CPLUGIN_FIFTY_PER_SECOND, // Called 50x per second (typical for checking new data instead of waiting) - CPLUGIN_INIT_ALL, - CPLUGIN_EXIT, - CPLUGIN_WRITE, // Send commands to a controller. - - - // new messages for autodiscover controller plugins (experimental) i.e. C014 - CPLUGIN_GOT_CONNECTED, // call after connected to mqtt server to publich device autodicover features - CPLUGIN_GOT_INVALID, // should be called before major changes i.e. changing the device name to clean up data on the - // controller. !ToDo - CPLUGIN_INTERVAL, // call every interval loop - CPLUGIN_ACKNOWLEDGE, // call for sending acknowledges !ToDo done by direct function call in PluginCall() for now. - - CPLUGIN_WEBFORM_SHOW_HOST_CONFIG // Used for showing host information for the controller. - }; -}; - -// ******************************************************************************** -// NPlugin (Notification) function calls -// ******************************************************************************** -class NPlugin { -public: - - enum class Function { - NPLUGIN_PROTOCOL_ADD = 1, - NPLUGIN_GET_DEVICENAME, - NPLUGIN_WEBFORM_SAVE, - NPLUGIN_WEBFORM_LOAD, - NPLUGIN_WRITE, - NPLUGIN_NOTIFY - }; -}; - - -#endif // DATATYPES_ESPEASY_PLUGIN_DEFS_H +#ifndef DATATYPES_ESPEASY_PLUGIN_DEFS_H +#define DATATYPES_ESPEASY_PLUGIN_DEFS_H + +#include "../../ESPEasy_common.h" + + +// ******************************************************************************** +// Plugin (Task) function calls +// ******************************************************************************** +enum PluginFunctions_e { + PLUGIN_INIT_ALL , // Not implemented in a plugin, only called during boot + PLUGIN_INIT , // Init the task, called when task is set to enabled (also at boot) + PLUGIN_READ , // This call can yield new data (when success = true) and then send to controllers + PLUGIN_ONCE_A_SECOND , // Called once a second + PLUGIN_TEN_PER_SECOND , // Called 10x per second (typical for checking new data instead of waiting) + PLUGIN_DEVICE_ADD , // Called at boot for letting a plugin adding itself to list of available plugins/devices + PLUGIN_EVENTLIST_ADD , // Not used. + PLUGIN_WEBFORM_SAVE , // Call from web interface to save settings + PLUGIN_WEBFORM_LOAD , // Call from web interface for presenting settings and status of plugin + PLUGIN_WEBFORM_SHOW_VALUES , // Call from devices overview page to format values in HTML + PLUGIN_GET_DEVICENAME , // Call to get the plugin description (e.g. "Switch input - Switch") + PLUGIN_GET_DEVICEVALUENAMES , // Call to let the plugin generate some default value names when not defined. + PLUGIN_GET_DEVICEVALUECOUNT , // Optional function call to allow tasks to specify the number of output values (e.g. P026_Sysinfo.ino) + PLUGIN_GET_DEVICEVTYPE , // Only needed when Device[deviceCount].OutputDataType is not Output_Data_type_t::Default + PLUGIN_WRITE , // Called to allow a task to process a command. Must return success = true when it can handle the command. +// PLUGIN_EVENT_OUT , // Does not seem to be used + PLUGIN_WEBFORM_SHOW_CONFIG , // Called to show non default pin assignment or addresses like for plugins using serial or 1-Wire + PLUGIN_SERIAL_IN , // Called on received data via serial port Serial0 (N.B. this may conflict with sending commands via serial) + PLUGIN_UDP_IN , // Called for received UDP data via ESPEasy p2p which isn't a standard p2p packet. (See C013 for handling standard p2p packets) + PLUGIN_CLOCK_IN , // Called every new minute + PLUGIN_TASKTIMER_IN , // Called with a previously defined event at a specific time, set via setPluginTaskTimer + PLUGIN_FIFTY_PER_SECOND , // Called 50 times per second + PLUGIN_SET_CONFIG , // Counterpart of PLUGIN_GET_CONFIG_VALUE to allow to set a config via a command. + PLUGIN_GET_DEVICEGPIONAMES , // Allow for specific formatting of the label for standard pin configuration (e.g. "GPIO <- TX") + PLUGIN_EXIT , // Called when a task no longer is enabled (or deleted) + PLUGIN_GET_CONFIG_VALUE , // Similar to PLUGIN_WRITE, but meant to fetch some information. Must return success = true when it can handle the command. Can also be used to access extra unused task values. +// PLUGIN_UNCONDITIONAL_POLL , // Used to be called 10x per sec, but no longer used as GPIO related plugins now use a different technique. + PLUGIN_REQUEST , // Specific command to fetch a state (FIXME TD-er: Seems very similar to PLUGIN_GET_CONFIG_VALUE) + PLUGIN_TIME_CHANGE , // Called when system time is set (e.g. via NTP) + PLUGIN_MONITOR , // Replaces PLUGIN_UNCONDITIONAL_POLL + PLUGIN_SET_DEFAULTS , // Called when assigning a plugin to a task, to set some default config. + PLUGIN_GET_PACKED_RAW_DATA , // Return all data in a compact binary format specific for that plugin. + // Needs FEATURE_PACKED_RAW_DATA + PLUGIN_DEVICETIMER_IN , // Similar to PLUGIN_TASKTIMER_IN, addressed to a plugin instead of a task. + PLUGIN_WEBFORM_SHOW_I2C_PARAMS , // Show I2C parameters like address. + PLUGIN_WEBFORM_SHOW_SERIAL_PARAMS , // When needed, show additional parameters like baudrate or specific serial config + PLUGIN_MQTT_CONNECTION_STATE , // Signal when connection to MQTT broker is re-established + PLUGIN_MQTT_IMPORT , // For P037 MQTT import + PLUGIN_FORMAT_USERVAR , // Allow plugin specific formatting of a task variable (event->idx = variable) + PLUGIN_WEBFORM_SHOW_GPIO_DESCR , // Show GPIO description on devices overview tab +#if FEATURE_PLUGIN_STATS + PLUGIN_WEBFORM_LOAD_SHOW_STATS , // Show PluginStats on task config page +#endif // if FEATURE_PLUGIN_STATS + + PLUGIN_I2C_HAS_ADDRESS , // Check the I2C addresses from the plugin, output in 'success' + PLUGIN_I2C_GET_ADDRESS , // Get the current I2C addresses from the plugin, output in 'event->Par1' and 'success' + PLUGIN_GET_DISPLAY_PARAMETERS , // Fetch X/Y resolution and Rotation setting from the plugin, output in 'success' + PLUGIN_WEBFORM_SHOW_ERRORSTATE_OPT , // Show Error State Value options, so be saved during PLUGIN_WEBFORM_SAVE + PLUGIN_INIT_VALUE_RANGES , // Initialize the ranges of values, called just before PLUGIN_INIT + PLUGIN_READ_ERROR_OCCURED , // Function returns "true" when last measurement was an error, called when PLUGIN_READ returns false + PLUGIN_WEBFORM_LOAD_OUTPUT_SELECTOR, // Show the configuration for output type and what value to set to which taskvalue + PLUGIN_PROCESS_CONTROLLER_DATA , // Can be called from the controller to signal the plugin to generate (or handle) sending the data. + PLUGIN_PRIORITY_INIT_ALL , // Pre-initialize all plugins that are set to PowerManager priority (not implemented in plugins) + PLUGIN_PRIORITY_INIT , // Pre-initialize a singe plugins that is set to PowerManager priority + PLUGIN_WEBFORM_LOAD_ALWAYS , // Loaded *after* PLUGIN_WEBFORM_LOAD, also shown for remote data-feed devices +#ifdef USES_ESPEASY_NOW + PLUGIN_FILTEROUT_CONTROLLER_DATA , // Can be called from the controller to query a task whether the data should be processed further. +#endif + PLUGIN_WEBFORM_PRE_SERIAL_PARAMS , // Before serial parameters, convert additional parameters like baudrate or specific serial config + #if FEATURE_MQTT_DISCOVER || FEATURE_CUSTOM_TASKVAR_VTYPE + PLUGIN_GET_DISCOVERY_VTYPES , // Fetch the Sensor_VType values for dynamic value configurations in event->Par1..Par4 + #endif // IF FEATURE_MQTT_DISCOVER || FEATURE_CUSTOM_TASKVAR_VTYPE + + PLUGIN_MAX_FUNCTION // Leave as last one. +}; + + +#define NrBitsPluginFunctions NR_BITS(static_cast(PLUGIN_MAX_FUNCTION)) + +// ******************************************************************************** +// CPlugin (Controller) function calls +// ******************************************************************************** + +class CPlugin { +public: + + // As these function values are also used in the timing stats, make sure there is no overlap with the PLUGIN_xxx numbering. + + enum class Function { + CPLUGIN_PROTOCOL_ADD = 127, // Called at boot for letting a controller adding itself to list of available controllers + CPLUGIN_CONNECT_SUCCESS, // Only used for timing stats + CPLUGIN_CONNECT_FAIL, // Only used for timing stats + CPLUGIN_PROTOCOL_TEMPLATE, + CPLUGIN_PROTOCOL_SEND, + CPLUGIN_PROTOCOL_RECV, + CPLUGIN_GET_DEVICENAME, + CPLUGIN_WEBFORM_SAVE, + CPLUGIN_WEBFORM_LOAD, + CPLUGIN_GET_PROTOCOL_DISPLAY_NAME, + CPLUGIN_TASK_CHANGE_NOTIFICATION, + CPLUGIN_INIT, + CPLUGIN_UDP_IN, + CPLUGIN_FLUSH, // Force offloading data stored in buffers, called before sleep/reboot + CPLUGIN_TEN_PER_SECOND, // Called 10x per second (typical for checking new data instead of waiting) + CPLUGIN_FIFTY_PER_SECOND, // Called 50x per second (typical for checking new data instead of waiting) + CPLUGIN_INIT_ALL, + CPLUGIN_EXIT, + CPLUGIN_WRITE, // Send commands to a controller. + + + // new messages for autodiscover controller plugins (experimental) i.e. C014 + CPLUGIN_GOT_CONNECTED, // call after connected to mqtt server to publich device autodicover features + CPLUGIN_GOT_INVALID, // should be called before major changes i.e. changing the device name to clean up data on the + // controller. !ToDo + CPLUGIN_INTERVAL, // call every interval loop + CPLUGIN_ACKNOWLEDGE, // call for sending acknowledges !ToDo done by direct function call in PluginCall() for now. + + CPLUGIN_WEBFORM_SHOW_HOST_CONFIG // Used for showing host information for the controller. + }; +}; + +// ******************************************************************************** +// NPlugin (Notification) function calls +// ******************************************************************************** +class NPlugin { +public: + + enum class Function { + NPLUGIN_PROTOCOL_ADD = 1, + NPLUGIN_GET_DEVICENAME, + NPLUGIN_WEBFORM_SAVE, + NPLUGIN_WEBFORM_LOAD, + NPLUGIN_WRITE, + NPLUGIN_NOTIFY + }; +}; + + +#endif // DATATYPES_ESPEASY_PLUGIN_DEFS_H diff --git a/src/src/DataTypes/EventValueSource.h b/src/src/DataTypes/EventValueSource.h index 12de4c45e3..e9383bd931 100644 --- a/src/src/DataTypes/EventValueSource.h +++ b/src/src/DataTypes/EventValueSource.h @@ -1,52 +1,54 @@ -#ifndef DATATYPES_EVENT_VALUE_SOURCE_H -#define DATATYPES_EVENT_VALUE_SOURCE_H - -#include "../../ESPEasy_common.h" - -struct EventValueSourceGroup { - enum class Enum : uint8_t { - RESTRICTED, - ALL - }; -}; - - -struct EventValueSource { - // Keep the values as they can be used by other/older builds to communicate with ESPEasy - enum class Enum : uint8_t { - VALUE_SOURCE_NOT_SET = 0, - VALUE_SOURCE_SYSTEM = 1, - VALUE_SOURCE_SERIAL = 2, - VALUE_SOURCE_HTTP = 3, - VALUE_SOURCE_MQTT = 4, - VALUE_SOURCE_UDP = 5, - VALUE_SOURCE_WEB_FRONTEND = 6, - VALUE_SOURCE_RULES = 7, - VALUE_SOURCE_RULES_RESTRICTED = 8, - - VALUE_SOURCE_NR_VALUES - }; - - static bool partOfGroup(EventValueSource::Enum source, EventValueSourceGroup::Enum group) - { - switch (source) { - case EventValueSource::Enum::VALUE_SOURCE_NOT_SET: - case EventValueSource::Enum::VALUE_SOURCE_NR_VALUES: - return false; - case EventValueSource::Enum::VALUE_SOURCE_SYSTEM: - case EventValueSource::Enum::VALUE_SOURCE_SERIAL: - case EventValueSource::Enum::VALUE_SOURCE_UDP: - case EventValueSource::Enum::VALUE_SOURCE_WEB_FRONTEND: - case EventValueSource::Enum::VALUE_SOURCE_RULES: - return true; - case EventValueSource::Enum::VALUE_SOURCE_HTTP: - case EventValueSource::Enum::VALUE_SOURCE_MQTT: - case EventValueSource::Enum::VALUE_SOURCE_RULES_RESTRICTED: - return group == EventValueSourceGroup::Enum::ALL; - } - return false; - } -}; - - -#endif // DATATYPES_EVENT_VALUE_SOURCE_H +#ifndef DATATYPES_EVENT_VALUE_SOURCE_H +#define DATATYPES_EVENT_VALUE_SOURCE_H + +#include "../../ESPEasy_common.h" + +struct EventValueSourceGroup { + enum class Enum : uint8_t { + RESTRICTED, + ALL + }; +}; + + +struct EventValueSource { + // Keep the values as they can be used by other/older builds to communicate with ESPEasy + enum class Enum : uint8_t { + VALUE_SOURCE_NOT_SET = 0, + VALUE_SOURCE_SYSTEM = 1, + VALUE_SOURCE_SERIAL = 2, + VALUE_SOURCE_HTTP = 3, + VALUE_SOURCE_MQTT = 4, + VALUE_SOURCE_UDP = 5, + VALUE_SOURCE_WEB_FRONTEND = 6, + VALUE_SOURCE_RULES = 7, + VALUE_SOURCE_RULES_RESTRICTED = 8, + VALUE_SOURCE_ESPEASY_NOW = 9, + + VALUE_SOURCE_NR_VALUES + }; + + static bool partOfGroup(EventValueSource::Enum source, EventValueSourceGroup::Enum group) + { + switch (source) { + case EventValueSource::Enum::VALUE_SOURCE_NOT_SET: + case EventValueSource::Enum::VALUE_SOURCE_NR_VALUES: + return false; + case EventValueSource::Enum::VALUE_SOURCE_SYSTEM: + case EventValueSource::Enum::VALUE_SOURCE_SERIAL: + case EventValueSource::Enum::VALUE_SOURCE_UDP: + case EventValueSource::Enum::VALUE_SOURCE_WEB_FRONTEND: + case EventValueSource::Enum::VALUE_SOURCE_RULES: + return true; + case EventValueSource::Enum::VALUE_SOURCE_HTTP: + case EventValueSource::Enum::VALUE_SOURCE_MQTT: + case EventValueSource::Enum::VALUE_SOURCE_RULES_RESTRICTED: + case EventValueSource::Enum::VALUE_SOURCE_ESPEASY_NOW: + return group == EventValueSourceGroup::Enum::ALL; + } + return false; + } +}; + + +#endif // DATATYPES_EVENT_VALUE_SOURCE_H diff --git a/src/src/DataTypes/NetworkMedium.cpp b/src/src/DataTypes/NetworkMedium.cpp index b66d210e7b..dbbde548d5 100644 --- a/src/src/DataTypes/NetworkMedium.cpp +++ b/src/src/DataTypes/NetworkMedium.cpp @@ -1,33 +1,35 @@ - -#include "../DataTypes/NetworkMedium.h" - -bool isValid(NetworkMedium_t medium) { - switch (medium) { - case NetworkMedium_t::WIFI: - case NetworkMedium_t::Ethernet: -#ifdef USES_ESPEASY_NOW - case NetworkMedium_t::ESPEasyNOW_only: -#endif - return true; - - case NetworkMedium_t::NotSet: - return false; - - // Do not use default: as this allows the compiler to detect any missing cases. - } - return false; -} - -const __FlashStringHelper * toString(NetworkMedium_t medium) { - switch (medium) { - case NetworkMedium_t::WIFI: return F("WiFi"); - case NetworkMedium_t::Ethernet: return F("Ethernet"); -#ifdef USES_ESPEASY_NOW - case NetworkMedium_t::ESPEasyNOW_only: return F(ESPEASY_NOW_NAME " only"); -#endif - case NetworkMedium_t::NotSet: return F("Not Set"); - - // Do not use default: as this allows the compiler to detect any missing cases. - } - return F("Unknown"); + +#include "../DataTypes/NetworkMedium.h" + +#include "../../ESPEasy_common.h" + +bool isValid(NetworkMedium_t medium) { + switch (medium) { + case NetworkMedium_t::WIFI: + case NetworkMedium_t::Ethernet: +#ifdef USES_ESPEASY_NOW + case NetworkMedium_t::ESPEasyNOW_only: +#endif + return true; + + case NetworkMedium_t::NotSet: + break; + + // Do not use default: as this allows the compiler to detect any missing cases. + } + return false; +} + +const __FlashStringHelper * toString(NetworkMedium_t medium) { + switch (medium) { + case NetworkMedium_t::WIFI: return F("WiFi"); + case NetworkMedium_t::Ethernet: return F("Ethernet"); +#ifdef USES_ESPEASY_NOW + case NetworkMedium_t::ESPEasyNOW_only: return F(ESPEASY_NOW_NAME " only"); +#endif + case NetworkMedium_t::NotSet: return F("Not Set"); + + // Do not use default: as this allows the compiler to detect any missing cases. + } + return F("Unknown"); } \ No newline at end of file diff --git a/src/src/DataTypes/SettingsType.cpp b/src/src/DataTypes/SettingsType.cpp index 52ddaf8f66..f7043b9c77 100644 --- a/src/src/DataTypes/SettingsType.cpp +++ b/src/src/DataTypes/SettingsType.cpp @@ -1,266 +1,267 @@ -#include "../DataTypes/SettingsType.h" - -#include "../CustomBuild/StorageLayout.h" -#include "../DataStructs/ControllerSettingsStruct.h" -#include "../DataStructs/ExtraTaskSettingsStruct.h" -#include "../DataStructs/NotificationSettingsStruct.h" -#include "../DataStructs/SecurityStruct.h" -#include "../DataTypes/ESPEasyFileType.h" -#include "../Globals/Settings.h" -#include "../Helpers/StringConverter.h" - -const __FlashStringHelper * SettingsType::getSettingsTypeString(Enum settingsType) { - switch (settingsType) { - case Enum::BasicSettings_Type: return F("Settings"); - case Enum::TaskSettings_Type: return F("TaskSettings"); - case Enum::CustomTaskSettings_Type: return F("CustomTaskSettings"); - case Enum::ControllerSettings_Type: return F("ControllerSettings"); - case Enum::CustomControllerSettings_Type: return F("CustomControllerSettings"); - case Enum::NotificationSettings_Type: - #if FEATURE_NOTIFIER - return F("NotificationSettings"); - #else - break; - #endif - case Enum::SecuritySettings_Type: return F("SecuritySettings"); - case Enum::ExtdControllerCredentials_Type: return F("ExtendedControllerCredentials"); - #if FEATURE_ALTERNATIVE_CDN_URL - case Enum::CdnSettings_Type: return F("CDN_url"); - #endif - - case Enum::SettingsType_MAX: break; - } - return F(""); -} - -/********************************************************************************************\ - Offsets in settings files - \*********************************************************************************************/ -bool SettingsType::getSettingsParameters(Enum settingsType, int index, int& max_index, int& offset, int& max_size, int& struct_size) { - // The defined offsets should be used with () just in case they are the result of a formula in the defines. - struct_size = 0; - max_index = -1; - offset = -1; - - switch (settingsType) { - case Enum::BasicSettings_Type: - { - max_index = 1; - offset = 0; - max_size = (DAT_BASIC_SETTINGS_SIZE); - struct_size = sizeof(SettingsStruct); - break; - } - case Enum::TaskSettings_Type: - { - max_index = TASKS_MAX; - offset = (DAT_OFFSET_TASKS) + (index * (DAT_TASKS_DISTANCE)); - max_size = DAT_TASKS_SIZE; - struct_size = sizeof(ExtraTaskSettingsStruct); - break; - } - case Enum::CustomTaskSettings_Type: - { - if (!getSettingsParameters(Enum::TaskSettings_Type, index, max_index, offset, max_size, struct_size)) - return false; - offset += (DAT_TASKS_CUSTOM_OFFSET); - max_size = (DAT_TASKS_CUSTOM_SIZE + DAT_TASKS_CUSTOM_EXTENSION_SIZE); - - // struct_size may differ. - struct_size = 0; - break; - } - case Enum::ControllerSettings_Type: - { - max_index = CONTROLLER_MAX; - offset = (DAT_OFFSET_CONTROLLER) + (index * (DAT_CONTROLLER_SIZE)); - max_size = DAT_CONTROLLER_SIZE; - struct_size = sizeof(ControllerSettingsStruct); - break; - } - case Enum::CustomControllerSettings_Type: - { - max_index = CONTROLLER_MAX; - offset = (DAT_OFFSET_CUSTOM_CONTROLLER) + (index * (DAT_CUSTOM_CONTROLLER_SIZE)); - max_size = DAT_CUSTOM_CONTROLLER_SIZE; - - // struct_size may differ. - struct_size = 0; - break; - } - case Enum::NotificationSettings_Type: - { -#if FEATURE_NOTIFIER - max_index = NOTIFICATION_MAX; - offset = index * (DAT_NOTIFICATION_SIZE); - max_size = DAT_NOTIFICATION_SIZE; - struct_size = sizeof(NotificationSettingsStruct); - break; -#else - return false; -#endif - } - case Enum::SecuritySettings_Type: - { - max_index = 1; - offset = 0; - max_size = DAT_SECURITYSETTINGS_SIZE; - struct_size = sizeof(SecurityStruct); - break; - } - case Enum::ExtdControllerCredentials_Type: - { - max_index = 1; - offset = DAT_EXTDCONTR_CRED_OFFSET; - max_size = DAT_EXTDCONTR_CRED_SIZE; - - // struct_size may differ. - struct_size = 0; - break; - } -#if FEATURE_ALTERNATIVE_CDN_URL - case Enum::CdnSettings_Type: - { - max_index = 1; - offset = DAT_OFFSET_CDN; - max_size = DAT_CDN_SIZE; - - // struct_size may differ. - struct_size = 0; - } - break; -#endif - - case Enum::SettingsType_MAX: - { - max_index = -1; - offset = -1; - return false; - } - } - return index >= 0 && index < max_index; -} - -bool SettingsType::getSettingsParameters(Enum settingsType, int index, int& offset, int& max_size) { - int max_index = -1; - int struct_size; - - if (!getSettingsParameters(settingsType, index, max_index, offset, max_size, struct_size)) { - return false; - } - - if ((index >= 0) && (index < max_index)) { return true; } - offset = -1; - return false; -} - -int SettingsType::getMaxFilePos(Enum settingsType) { - int max_index, offset, max_size{}; - int struct_size = 0; - - if (getSettingsParameters(settingsType, 0, max_index, offset, max_size, struct_size) && - getSettingsParameters(settingsType, max_index - 1, offset, max_size)) - return offset + max_size - 1; - return -1; -} - -int SettingsType::getFileSize(Enum settingsType) { - SettingsType::SettingsFileEnum file_type = SettingsType::getSettingsFile(settingsType); - int max_file_pos = 0; - - for (int st = 0; st < static_cast(Enum::SettingsType_MAX); ++st) { - if (SettingsType::getSettingsFile(static_cast(st)) == file_type) { - const int filePos = SettingsType::getMaxFilePos(static_cast(st)); - - if (filePos > max_file_pos) { - max_file_pos = filePos; - } - } - } - return max_file_pos; -} - -#ifndef BUILD_MINIMAL_OTA -unsigned int SettingsType::getSVGcolor(Enum settingsType) { - switch (settingsType) { - case Enum::BasicSettings_Type: - return 0x5F0A87; - case Enum::TaskSettings_Type: - return 0xEE6352; - case Enum::CustomTaskSettings_Type: - return 0x59CD90; - case Enum::ControllerSettings_Type: - return 0x3FA7D6; - case Enum::CustomControllerSettings_Type: - return 0xFAC05E; - case Enum::NotificationSettings_Type: - return 0xF79D84; - - case Enum::SecuritySettings_Type: - return 0xff00a2; - case Enum::ExtdControllerCredentials_Type: - return 0xc300ff; -#if FEATURE_ALTERNATIVE_CDN_URL - case Enum::CdnSettings_Type: - return 0xff6600; -#endif - case Enum::SettingsType_MAX: - break; - } - return 0; -} - -#endif // ifndef BUILD_MINIMAL_OTA - -SettingsType::SettingsFileEnum SettingsType::getSettingsFile(Enum settingsType) -{ - switch (settingsType) { - case Enum::BasicSettings_Type: - case Enum::TaskSettings_Type: - case Enum::CustomTaskSettings_Type: - case Enum::ControllerSettings_Type: - case Enum::CustomControllerSettings_Type: -#if FEATURE_ALTERNATIVE_CDN_URL - case Enum::CdnSettings_Type: -#endif - return SettingsFileEnum::FILE_CONFIG_type; - case Enum::NotificationSettings_Type: - return SettingsFileEnum::FILE_NOTIFICATION_type; - case Enum::SecuritySettings_Type: - case Enum::ExtdControllerCredentials_Type: - return SettingsFileEnum::FILE_SECURITY_type; - - case Enum::SettingsType_MAX: - break; - } - return SettingsFileEnum::FILE_UNKNOWN_type; -} - -String SettingsType::getSettingsFileName(Enum settingsType, int index) { - #if FEATURE_EXTENDED_CUSTOM_SETTINGS - if ((Enum::CustomTaskSettings_Type == settingsType) && validTaskIndex(index)) { - return strformat(F(DAT_TASKS_CUSTOM_EXTENSION_FILEMASK), index + 1); // Add 0/1 offset to match displayed task ID - } - #endif // if FEATURE_EXTENDED_CUSTOM_SETTINGS - return getSettingsFileName(getSettingsFile(settingsType)); -} - -const __FlashStringHelper * SettingsType::getSettingsFileName(SettingsType::SettingsFileEnum file_type) { - switch (file_type) { - case SettingsFileEnum::FILE_CONFIG_type: return getFileName(FileType::CONFIG_DAT); - case SettingsFileEnum::FILE_NOTIFICATION_type: return getFileName(FileType::NOTIFICATION_DAT); - case SettingsFileEnum::FILE_SECURITY_type: return getFileName(FileType::SECURITY_DAT); - case SettingsFileEnum::FILE_UNKNOWN_type: break; - } - return F(""); -} - -size_t SettingsType::getInitFileSize(SettingsType::SettingsFileEnum file_type) { - switch (file_type) { - case SettingsFileEnum::FILE_CONFIG_type: return CONFIG_FILE_SIZE; - case SettingsFileEnum::FILE_NOTIFICATION_type: return 4096; - case SettingsFileEnum::FILE_SECURITY_type: return 4096; - case SettingsFileEnum::FILE_UNKNOWN_type: break; - } - return 0; +#include "../DataTypes/SettingsType.h" + +#include "../CustomBuild/StorageLayout.h" +#include "../DataStructs/ControllerSettingsStruct.h" +#include "../DataStructs/ExtraTaskSettingsStruct.h" +#include "../DataStructs/NotificationSettingsStruct.h" +#include "../DataStructs/SecurityStruct.h" +#include "../DataTypes/ESPEasyFileType.h" +#include "../Globals/Settings.h" +#include "../Helpers/StringConverter.h" + +const __FlashStringHelper * SettingsType::getSettingsTypeString(Enum settingsType) { + switch (settingsType) { + case Enum::BasicSettings_Type: return F("Settings"); + case Enum::TaskSettings_Type: return F("TaskSettings"); + case Enum::CustomTaskSettings_Type: return F("CustomTaskSettings"); + case Enum::ControllerSettings_Type: return F("ControllerSettings"); + case Enum::CustomControllerSettings_Type: return F("CustomControllerSettings"); + case Enum::NotificationSettings_Type: + #if FEATURE_NOTIFIER + return F("NotificationSettings"); + #else + break; + #endif + case Enum::SecuritySettings_Type: return F("SecuritySettings"); + case Enum::ExtdControllerCredentials_Type: return F("ExtendedControllerCredentials"); + #if FEATURE_ALTERNATIVE_CDN_URL + case Enum::CdnSettings_Type: return F("CDN_url"); + #endif + + case Enum::SettingsType_MAX: break; + } + return F(""); +} + +/********************************************************************************************\ + Offsets in settings files + \*********************************************************************************************/ +bool SettingsType::getSettingsParameters(Enum settingsType, int index, int& max_index, int& offset, int& max_size, int& struct_size) { + // The defined offsets should be used with () just in case they are the result of a formula in the defines. + struct_size = 0; + max_index = -1; + offset = -1; + + switch (settingsType) { + case Enum::BasicSettings_Type: + { + max_index = 1; + offset = 0; + max_size = (DAT_BASIC_SETTINGS_SIZE); + struct_size = sizeof(SettingsStruct); + break; + } + case Enum::TaskSettings_Type: + { + max_index = TASKS_MAX; + offset = (DAT_OFFSET_TASKS) + (index * (DAT_TASKS_DISTANCE)); + max_size = DAT_TASKS_SIZE; + struct_size = sizeof(ExtraTaskSettingsStruct); + break; + } + case Enum::CustomTaskSettings_Type: + { + if (!getSettingsParameters(Enum::TaskSettings_Type, index, max_index, offset, max_size, struct_size)) + return false; + offset += (DAT_TASKS_CUSTOM_OFFSET); + max_size = (DAT_TASKS_CUSTOM_SIZE + DAT_TASKS_CUSTOM_EXTENSION_SIZE); + + // struct_size may differ. + struct_size = 0; + break; + } + case Enum::ControllerSettings_Type: + { + max_index = CONTROLLER_MAX; + offset = (DAT_OFFSET_CONTROLLER) + (index * (DAT_CONTROLLER_SIZE)); + max_size = DAT_CONTROLLER_SIZE; + struct_size = sizeof(ControllerSettingsStruct); + break; + } + case Enum::CustomControllerSettings_Type: + { + max_index = CONTROLLER_MAX; + offset = (DAT_OFFSET_CUSTOM_CONTROLLER) + (index * (DAT_CUSTOM_CONTROLLER_SIZE)); + max_size = DAT_CUSTOM_CONTROLLER_SIZE; + + // struct_size may differ. + struct_size = 0; + break; + } + case Enum::NotificationSettings_Type: + { +#if FEATURE_NOTIFIER + max_index = NOTIFICATION_MAX; + offset = index * (DAT_NOTIFICATION_SIZE); + max_size = DAT_NOTIFICATION_SIZE; + struct_size = sizeof(NotificationSettingsStruct); + break; +#else + return false; +#endif + } + case Enum::SecuritySettings_Type: + { + max_index = 1; + offset = 0; + max_size = DAT_SECURITYSETTINGS_SIZE; + struct_size = sizeof(SecurityStruct); + break; + } + case Enum::ExtdControllerCredentials_Type: + { + max_index = 1; + offset = DAT_EXTDCONTR_CRED_OFFSET; + max_size = DAT_EXTDCONTR_CRED_SIZE; + + // struct_size may differ. + struct_size = 0; + break; + } +#if FEATURE_ALTERNATIVE_CDN_URL + case Enum::CdnSettings_Type: + { + max_index = 1; + offset = DAT_OFFSET_CDN; + max_size = DAT_CDN_SIZE; + + // struct_size may differ. + struct_size = 0; + } + break; +#endif + + case Enum::SettingsType_MAX: + { + max_index = -1; + offset = -1; + return false; + } + } + return index >= 0 && index < max_index; +} + +bool SettingsType::getSettingsParameters(Enum settingsType, int index, int& offset, int& max_size) { + int max_index = -1; + int struct_size; + + if (!getSettingsParameters(settingsType, index, max_index, offset, max_size, struct_size)) { + return false; + } + + if ((index >= 0) && (index < max_index)) { return true; } + offset = -1; + return false; +} + +int SettingsType::getMaxFilePos(Enum settingsType) { + int max_index, offset, max_size{}; + int struct_size = 0; + + if (getSettingsParameters(settingsType, 0, max_index, offset, max_size, struct_size) && + getSettingsParameters(settingsType, max_index - 1, offset, max_size)) + return offset + max_size - 1; + return -1; +} + +int SettingsType::getFileSize(Enum settingsType) { + SettingsType::SettingsFileEnum file_type = SettingsType::getSettingsFile(settingsType); + int max_file_pos = 0; + + for (int st = 0; st < static_cast(Enum::SettingsType_MAX); ++st) { + if (SettingsType::getSettingsFile(static_cast(st)) == file_type) { + const int filePos = SettingsType::getMaxFilePos(static_cast(st)); + + if (filePos > max_file_pos) { + max_file_pos = filePos; + } + } + } + return max_file_pos; +} + +#ifndef BUILD_MINIMAL_OTA +unsigned int SettingsType::getSVGcolor(Enum settingsType) { + switch (settingsType) { + case Enum::BasicSettings_Type: + return 0x5F0A87; + case Enum::TaskSettings_Type: + return 0xEE6352; + case Enum::CustomTaskSettings_Type: + return 0x59CD90; + case Enum::ControllerSettings_Type: + return 0x3FA7D6; + case Enum::CustomControllerSettings_Type: + return 0xFAC05E; + case Enum::NotificationSettings_Type: + return 0xF79D84; + + case Enum::SecuritySettings_Type: + return 0xff00a2; + case Enum::ExtdControllerCredentials_Type: + return 0xc300ff; +#if FEATURE_ALTERNATIVE_CDN_URL + case Enum::CdnSettings_Type: + return 0xff6600; +#endif + case Enum::SettingsType_MAX: + break; + } + return 0; +} + +#endif // ifndef BUILD_MINIMAL_OTA + +SettingsType::SettingsFileEnum SettingsType::getSettingsFile(Enum settingsType) +{ + switch (settingsType) { + case Enum::BasicSettings_Type: + case Enum::TaskSettings_Type: + case Enum::CustomTaskSettings_Type: + case Enum::ControllerSettings_Type: + case Enum::CustomControllerSettings_Type: +#if FEATURE_ALTERNATIVE_CDN_URL + case Enum::CdnSettings_Type: +#endif + return SettingsFileEnum::FILE_CONFIG_type; + case Enum::NotificationSettings_Type: + return SettingsFileEnum::FILE_NOTIFICATION_type; + case Enum::SecuritySettings_Type: + case Enum::ExtdControllerCredentials_Type: + return SettingsFileEnum::FILE_SECURITY_type; + + case Enum::SettingsType_MAX: + break; + } + return SettingsFileEnum::FILE_UNKNOWN_type; +} + +String SettingsType::getSettingsFileName(Enum settingsType, int index) { + #if FEATURE_EXTENDED_CUSTOM_SETTINGS + if ((Enum::CustomTaskSettings_Type == settingsType) && validTaskIndex(index)) { + return strformat(F(DAT_TASKS_CUSTOM_EXTENSION_FILEMASK), index + 1); // Add 0/1 offset to match displayed task ID + } + #endif // if FEATURE_EXTENDED_CUSTOM_SETTINGS + return getSettingsFileName(getSettingsFile(settingsType)); +} + +const __FlashStringHelper * SettingsType::getSettingsFileName(SettingsType::SettingsFileEnum file_type) { + switch (file_type) { + case SettingsFileEnum::FILE_CONFIG_type: return getFileName(FileType::CONFIG_DAT); + case SettingsFileEnum::FILE_NOTIFICATION_type: return getFileName(FileType::NOTIFICATION_DAT); + case SettingsFileEnum::FILE_SECURITY_type: return getFileName(FileType::SECURITY_DAT); + case SettingsFileEnum::FILE_UNKNOWN_type: break; + } + return F(""); +} + +size_t SettingsType::getInitFileSize(SettingsType::SettingsFileEnum file_type) { + switch (file_type) { + case SettingsFileEnum::FILE_CONFIG_type: return CONFIG_FILE_SIZE; + case SettingsFileEnum::FILE_NOTIFICATION_type: + // fall through + case SettingsFileEnum::FILE_SECURITY_type: return 4096; + case SettingsFileEnum::FILE_UNKNOWN_type: break; + } + return 0; } \ No newline at end of file diff --git a/src/src/ESPEasyCore/Controller.cpp b/src/src/ESPEasyCore/Controller.cpp index 36a1b238ca..8026462462 100644 --- a/src/src/ESPEasyCore/Controller.cpp +++ b/src/src/ESPEasyCore/Controller.cpp @@ -22,6 +22,10 @@ #include "../Globals/Device.h" #include "../Globals/ESPEasyWiFiEvent.h" #include "../Globals/ESPEasy_Scheduler.h" +#ifdef USES_ESPEASY_NOW +# include "../Globals/ESPEasy_now_handler.h" +# include "../Globals/SendData_DuplicateChecker.h" +#endif // ifdef USES_ESPEASY_NOW #include "../Globals/MQTT.h" #include "../Globals/Plugins.h" #include "../Globals/RulesCalculate.h" @@ -88,6 +92,31 @@ void sendData(struct EventStruct *event, bool sendEvents) STOP_TIMER(SEND_DATA_STATS); } +// ******************************************************************************** +// Send to controllers, via a duplicate check +// Some plugins may receive the same data among nodes, so check first if +// another node may already have sent it. +// The compare_key is computed by the sender plugin, with plugin specific knowledge +// to make sure the key describes enough to detect duplicates. +// ******************************************************************************** +void sendData_checkDuplicates(struct EventStruct *event, const String& compare_key) +{ +#ifdef USES_ESPEASY_NOW + uint32_t key = SendData_DuplicateChecker.add(event, compare_key); + + if (key != SendData_DuplicateChecker_struct::DUPLICATE_CHECKER_INVALID_KEY) { + // Must send out request to other nodes to see if any other has already processed it. + uint8_t broadcastMac[6]; + ESPEasy_now_handler.sendSendData_DuplicateCheck( + key, + ESPEasy_Now_DuplicateCheck::message_t::KeyToCheck, + broadcastMac); + } +#else // ifdef USES_ESPEASY_NOW + sendData(event); +#endif // ifdef USES_ESPEASY_NOW +} + bool validUserVar(struct EventStruct *event) { if (!validTaskIndex(event->TaskIndex)) { return false; } const Sensor_VType vtype = event->getSensorType(); @@ -97,6 +126,7 @@ bool validUserVar(struct EventStruct *event) { { return true; } + const uint8_t valueCount = getValueCountForTask(event->TaskIndex); for (int i = 0; i < valueCount; ++i) { @@ -827,6 +857,102 @@ bool MQTT_queueFull(controllerIndex_t controller_idx) { return false; } +# ifdef USES_ESPEASY_NOW + +bool MQTTpublish(controllerIndex_t controller_idx, + taskIndex_t taskIndex, + const ESPEasy_now_merger& message, + const MessageRouteInfo_t& messageRouteInfo, + bool retained, + bool callbackTask) +{ + bool success = false; + + if (!MQTT_queueFull(controller_idx)) + { + { + size_t pos = 0; + const size_t payloadSize = message.getPayloadSize(); + MessageRouteInfo_t routeinfo = messageRouteInfo; + String topic, payload; + + if (message.getString(topic, pos) && message.getString(payload, pos)) { + const size_t bytesLeft = payloadSize - pos; + + if (callbackTask && validTaskIndex(taskIndex)) { + struct EventStruct TempEvent(taskIndex); + String dummy; + TempEvent.String1 = std::move(topic); + TempEvent.String2 = std::move(payload); + + // Filter function to check if data should be forwarded or not. + // Since all plugins/tasks not supporting this function call will return false, + // the "true" result is about the non-standard action; to filter out the message. + if (PluginCall(PLUGIN_FILTEROUT_CONTROLLER_DATA, &TempEvent, dummy)) { + scheduleNextMQTTdelayQueue(); + return true; + } + topic = std::move(TempEvent.String1); + payload = std::move(TempEvent.String2); + } + + if (bytesLeft >= 4) { + bool validMessageRouteInfo = false; + + // There is some MessageRouteInfo left + MessageRouteInfo_t::uint8_t_vector routeInfoData; + routeInfoData.resize(bytesLeft); + + // Use temp position as we don't yet know the true size of the message route info + size_t tmp_pos = pos; + + if (message.getBinaryData(&routeInfoData[0], bytesLeft, tmp_pos) == bytesLeft) { + validMessageRouteInfo = routeinfo.deserialize(routeInfoData); + + if (validMessageRouteInfo) { + // Move pos for the actual number of bytes we read. + pos += routeinfo.getSerializedSize(); + } + if (loglevelActiveFor(LOG_LEVEL_INFO)) + { + String log = F("MQTT : MQTTpublish MessageRouteInfo: "); + log += routeinfo.toString(); + log += F(" bytesLeft: "); + log += bytesLeft; + addLog(LOG_LEVEL_INFO, log); + } + } + + if (!validMessageRouteInfo) { + // Whatever may have been present, it could not be loaded, so clear just to be sure. + routeinfo = messageRouteInfo; + } + + // Append our own unit number + routeinfo.appendUnit(Settings.Unit); + } + std::unique_ptr element = std::unique_ptr( + new MQTT_queue_element( + controller_idx, + taskIndex, + std::move(topic), + std::move(payload), + retained, + false)); + + if (element) { + element->MessageRouteInfo = routeinfo; + success = MQTTDelayHandler->addToQueue(std::move(element)); + } + } + } + } + scheduleNextMQTTdelayQueue(); + return success; +} + +# endif // ifdef USES_ESPEASY_NOW + bool MQTTpublish(controllerIndex_t controller_idx, taskIndex_t taskIndex, const char *topic, @@ -841,6 +967,7 @@ bool MQTTpublish(controllerIndex_t controller_idx, if (MQTT_queueFull(controller_idx)) { return false; } + String topic_str; String payload_str; if (!reserve_special(topic_str, strlen_P(topic)) || @@ -870,12 +997,8 @@ bool MQTTpublish(controllerIndex_t controller_idx, return success; } -bool MQTTpublish(controllerIndex_t controller_idx, - taskIndex_t taskIndex, - String && topic, - String && payload, - bool retained, - bool callbackTask) { +bool MQTTpublish(controllerIndex_t controller_idx, taskIndex_t taskIndex, String&& topic, String&& payload, bool retained, + bool callbackTask) { if (MQTTDelayHandler == nullptr) { return false; } diff --git a/src/src/ESPEasyCore/Controller.h b/src/src/ESPEasyCore/Controller.h index 91825acd0b..3cf3eb647f 100644 --- a/src/src/ESPEasyCore/Controller.h +++ b/src/src/ESPEasyCore/Controller.h @@ -4,6 +4,8 @@ #include "../../ESPEasy_common.h" #include "../DataTypes/EventValueSource.h" +#include "../DataStructs/ESPEasy_now_merger.h" +#include "../DataStructs/MessageRouteInfo.h" #include "../Globals/CPlugins.h" // ******************************************************************************** @@ -13,6 +15,16 @@ void sendData(struct EventStruct *event, bool sendEvents = true); bool validUserVar(struct EventStruct *event); +// ******************************************************************************** +// Send to controllers, via a duplicate check +// Some plugins may receive the same data among nodes, so check first if +// another node may already have sent it. +// The compare_key is computed by the sender plugin, with plugin specific knowledge +// to make sure the key describes enough to detect duplicates. +// ******************************************************************************** +void sendData_checkDuplicates(struct EventStruct *event, const String& compare_key); + + #if FEATURE_MQTT /*********************************************************************************************\ * Handle incoming MQTT messages @@ -64,6 +76,10 @@ controllerIndex_t firstEnabledMQTT_ControllerIndex(); bool MQTT_queueFull(controllerIndex_t controller_idx); +#ifdef USES_ESPEASY_NOW +bool MQTTpublish(controllerIndex_t controller_idx, taskIndex_t taskIndex, const ESPEasy_now_merger& message, const MessageRouteInfo_t& messageRouteInfo, bool retained, bool callbackTask = false); +#endif + bool MQTTpublish(controllerIndex_t controller_idx, taskIndex_t taskIndex, const char *topic, const char *payload, bool retained, bool callbackTask = false); // Publish using the move operator for topic and message diff --git a/src/src/ESPEasyCore/ESPEasyEth.cpp b/src/src/ESPEasyCore/ESPEasyEth.cpp index c0b8a8ac9d..641cae49c0 100644 --- a/src/src/ESPEasyCore/ESPEasyEth.cpp +++ b/src/src/ESPEasyCore/ESPEasyEth.cpp @@ -1,372 +1,375 @@ -#include "../ESPEasyCore/ESPEasyEth.h" - -#if FEATURE_ETHERNET - -#include "../CustomBuild/ESPEasyLimits.h" -#include "../ESPEasyCore/ESPEasyNetwork.h" -#include "../ESPEasyCore/ESPEasyWifi.h" -#include "../ESPEasyCore/ESPEasy_Log.h" -#include "../ESPEasyCore/ESPEasyGPIO.h" -#include "../ESPEasyCore/ESPEasyEthEvent.h" -#include "../Globals/ESPEasyEthEvent.h" -#include "../Globals/NetworkState.h" -#include "../Globals/Settings.h" -#include "../Helpers/Hardware_GPIO.h" -#include "../Helpers/StringConverter.h" -#include "../Helpers/Networking.h" - -#include -#include -#if ESP_IDF_VERSION_MAJOR > 3 - #include -#else - #include -#endif - -#include - -bool ethUseStaticIP() { - return Settings.ETH_IP[0] != 0 && Settings.ETH_IP[0] != 255; -} - -void ethSetupStaticIPconfig() { - const IPAddress IP_zero(0, 0, 0, 0); - if (!ethUseStaticIP()) { - if (!ETH.config(IP_zero, IP_zero, IP_zero, IP_zero)) { - addLog(LOG_LEVEL_ERROR, F("ETH : Cannot set IP config")); - } - return; - } - const IPAddress ip = Settings.ETH_IP; - const IPAddress gw = Settings.ETH_Gateway; - const IPAddress subnet = Settings.ETH_Subnet; - const IPAddress dns = Settings.ETH_DNS; - - EthEventData.dns0_cache = dns; - EthEventData.dns1_cache = IP_zero; - - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("ETH IP : Static IP : "); - log += formatIP(ip); - log += F(" GW: "); - log += formatIP(gw); - log += F(" SN: "); - log += formatIP(subnet); - log += F(" DNS: "); - log += formatIP(dns); - addLogMove(LOG_LEVEL_INFO, log); - } - ETH.config(ip, gw, subnet, dns); - setDNS(0, EthEventData.dns0_cache); - setDNS(1, EthEventData.dns1_cache); -} - -bool ethCheckSettings() { - return isValid(Settings.ETH_Phy_Type) -#if CONFIG_ETH_USE_ESP32_EMAC - && (isValid(Settings.ETH_Clock_Mode)/* || isSPI_EthernetType(Settings.ETH_Phy_Type)*/) -#endif - && isValid(Settings.NetworkMedium) - && validGpio(Settings.ETH_Pin_mdc_cs) - && (isSPI_EthernetType(Settings.ETH_Phy_Type) || - ( validGpio(Settings.ETH_Pin_mdio_irq) && - (validGpio(Settings.ETH_Pin_power_rst) || (Settings.ETH_Pin_power_rst == -1)) - ) - ); // Some boards have fixed power -} - -bool ethPrepare() { - char hostname[40]; - safe_strncpy(hostname, NetworkCreateRFCCompliantHostname().c_str(), sizeof(hostname)); - ETH.setHostname(hostname); - ethSetupStaticIPconfig(); - return true; -} - -void ethPrintSettings() { - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log; - if (log.reserve(115)) { -// log += F("ETH/Wifi mode: "); -// log += toString(active_network_medium); - log += F("ETH PHY Type: "); - log += toString(Settings.ETH_Phy_Type); - log += F(" PHY Addr: "); - log += Settings.ETH_Phy_Addr; - - if (!isSPI_EthernetType(Settings.ETH_Phy_Type)) { - log += F(" Eth Clock mode: "); - log += toString(Settings.ETH_Clock_Mode); - } - log += strformat(isSPI_EthernetType(Settings.ETH_Phy_Type) - ? F(" CS: %d IRQ: %d RST: %d") : F(" MDC: %d MIO: %d PWR: %d"), - Settings.ETH_Pin_mdc_cs, - Settings.ETH_Pin_mdio_irq, - Settings.ETH_Pin_power_rst); - addLogMove(LOG_LEVEL_INFO, log); - } - } -} - -MAC_address ETHMacAddress() { - MAC_address mac; - if(!EthEventData.ethInitSuccess) { - addLog(LOG_LEVEL_ERROR, F("Call NetworkMacAddress() only on connected Ethernet!")); - } else { - #if ESP_IDF_VERSION_MAJOR > 3 - ETH.macAddress(mac.mac); - #else - esp_eth_get_mac(mac.mac); - #endif - } - return mac; -} - -void removeEthEventHandler() -{ - WiFi.removeEvent(EthEventData.wm_event_id); - EthEventData.wm_event_id = 0; -} - -void registerEthEventHandler() -{ - if (EthEventData.wm_event_id != 0) { - removeEthEventHandler(); - } - EthEventData.wm_event_id = WiFi.onEvent(EthEvent); -} - - -bool ETHConnectRelaxed() { - if (EthEventData.ethInitSuccess) { - return EthLinkUp(); - } - ethPrintSettings(); - if (!ethCheckSettings()) - { - addLog(LOG_LEVEL_ERROR, F("ETH: Settings not correct!!!")); - EthEventData.ethInitSuccess = false; - return false; - } - // Re-register event listener - removeEthEventHandler(); - - ethPower(true); - EthEventData.markEthBegin(); - - // Re-register event listener - registerEthEventHandler(); - - if (!EthEventData.ethInitSuccess) { -#if ESP_IDF_VERSION_MAJOR < 5 - EthEventData.ethInitSuccess = ETH.begin( - Settings.ETH_Phy_Addr, - Settings.ETH_Pin_power_rst, - Settings.ETH_Pin_mdc_cs, - Settings.ETH_Pin_mdio_irq, - (eth_phy_type_t)Settings.ETH_Phy_Type, - (eth_clock_mode_t)Settings.ETH_Clock_Mode); -#else -#if FEATURE_USE_IPV6 - if (Settings.EnableIPv6()) { - ETH.enableIPv6(true); - } -#endif - - if (isSPI_EthernetType(Settings.ETH_Phy_Type)) { - spi_host_device_t SPI_host = Settings.getSPI_host(); - if (SPI_host == spi_host_device_t::SPI_HOST_MAX) { - addLog(LOG_LEVEL_ERROR, F("SPI not enabled")); - #ifdef ESP32C3 - // FIXME TD-er: Fallback for ETH01-EVO board - SPI_host = spi_host_device_t::SPI2_HOST; - Settings.InitSPI = static_cast(SPI_Options_e::UserDefined); - Settings.SPI_SCLK_pin = 7; - Settings.SPI_MISO_pin = 3; - Settings.SPI_MOSI_pin = 10; - #endif - } - // else - { -#if ETH_SPI_SUPPORTS_CUSTOM - EthEventData.ethInitSuccess = ETH.begin( - to_ESP_phy_type(Settings.ETH_Phy_Type), - Settings.ETH_Phy_Addr, - Settings.ETH_Pin_mdc_cs, - Settings.ETH_Pin_mdio_irq, - Settings.ETH_Pin_power_rst, - SPI); -#else - EthEventData.ethInitSuccess = ETH.begin( - to_ESP_phy_type(Settings.ETH_Phy_Type), - Settings.ETH_Phy_Addr, - Settings.ETH_Pin_mdc_cs, - Settings.ETH_Pin_mdio_irq, - Settings.ETH_Pin_power_rst, - SPI_host, - static_cast(Settings.SPI_SCLK_pin), - static_cast(Settings.SPI_MISO_pin), - static_cast(Settings.SPI_MOSI_pin)); -#endif - } - } else { -# if CONFIG_ETH_USE_ESP32_EMAC - ethResetGPIOpins(); - EthEventData.ethInitSuccess = ETH.begin( - to_ESP_phy_type(Settings.ETH_Phy_Type), - Settings.ETH_Phy_Addr, - Settings.ETH_Pin_mdc_cs, - Settings.ETH_Pin_mdio_irq, - Settings.ETH_Pin_power_rst, - (eth_clock_mode_t)Settings.ETH_Clock_Mode); -#endif - } - -#endif - } - if (EthEventData.ethInitSuccess) { - // FIXME TD-er: Not sure if this is correctly set to false - //EthEventData.ethConnectAttemptNeeded = false; - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { -#if ESP_IDF_VERSION_MAJOR < 5 - addLog(LOG_LEVEL_INFO, strformat( - F("ETH : MAC: %s speed: %dM %s Link: %s"), - ETH.macAddress().c_str(), - ETH.linkSpeed(), - String(ETH.fullDuplex() ? F("Full Duplex") : F("Half Duplex")).c_str(), - String(ETH.linkUp() ? F("Up") : F("Down")).c_str())); -#else - addLog(LOG_LEVEL_INFO, strformat( - F("ETH : MAC: %s phy addr: %d speed: %dM %s Link: %s"), - ETH.macAddress().c_str(), - ETH.phyAddr(), - ETH.linkSpeed(), - concat( - ETH.fullDuplex() ? F("Full Duplex") : F("Half Duplex"), - ETH.autoNegotiation() ? F("(auto)") : F("")).c_str(), - String(ETH.linkUp() ? F("Up") : F("Down")).c_str())); -#endif - } - - if (EthLinkUp()) { - // We might miss the connected event, since we are already connected. - EthEventData.markConnected(); - } - } else { - addLog(LOG_LEVEL_ERROR, F("ETH : Failed to initialize ETH")); - } - return EthEventData.ethInitSuccess; -} - -void ethPower(bool enable) { - if (isSPI_EthernetType(Settings.ETH_Phy_Type)) - return; - if (Settings.ETH_Pin_power_rst != -1) { - if (GPIO_Internal_Read(Settings.ETH_Pin_power_rst) == enable) { - // Already the desired state - return; - } - addLog(LOG_LEVEL_INFO, enable ? F("ETH power ON") : F("ETH power OFF")); - if (!enable) { - EthEventData.ethInitSuccess = false; - EthEventData.clearAll(); - #ifdef ESP_IDF_VERSION_MAJOR - // FIXME TD-er: See: https://github.com/espressif/arduino-esp32/issues/6105 - // Need to store the last link state, as it will be cleared after destructing the object. - EthEventData.setEthDisconnected(); - if (ETH.linkUp()) { - EthEventData.setEthConnected(); - } - #endif -// ETH = ETHClass(); - } - if (enable) { -// ethResetGPIOpins(); - } -// gpio_reset_pin((gpio_num_t)Settings.ETH_Pin_power_rst); - - GPIO_Write(PLUGIN_GPIO, Settings.ETH_Pin_power_rst, enable ? 1 : 0); - if (!enable) { - if (Settings.ETH_Clock_Mode == EthClockMode_t::Ext_crystal_osc) { - delay(600); // Give some time to discharge any capacitors - // Delay is needed to make sure no clock signal remains present which may cause the ESP to boot into flash mode. - } - } else { - delay(400); // LAN chip needs to initialize before calling Eth.begin() - } - } -} - -void ethResetGPIOpins() { - if (isSPI_EthernetType(Settings.ETH_Phy_Type)) - return; - - // fix an disconnection issue after rebooting Olimex POE - this forces a clean state for all GPIO involved in RMII - // Thanks to @s-hadinger and @Jason2866 - // Resetting state of power pin is done in ethPower() - addLog(LOG_LEVEL_INFO, F("ethResetGPIOpins()")); - gpio_reset_pin((gpio_num_t)Settings.ETH_Pin_mdc_cs); - gpio_reset_pin((gpio_num_t)Settings.ETH_Pin_mdio_irq); -# if CONFIG_ETH_USE_ESP32_EMAC - gpio_reset_pin(GPIO_NUM_19); // EMAC_TXD0 - hardcoded - gpio_reset_pin(GPIO_NUM_21); // EMAC_TX_EN - hardcoded - gpio_reset_pin(GPIO_NUM_22); // EMAC_TXD1 - hardcoded - gpio_reset_pin(GPIO_NUM_25); // EMAC_RXD0 - hardcoded - gpio_reset_pin(GPIO_NUM_26); // EMAC_RXD1 - hardcoded - gpio_reset_pin(GPIO_NUM_27); // EMAC_RX_CRS_DV - hardcoded -#endif - /* - switch (Settings.ETH_Clock_Mode) { - case EthClockMode_t::Ext_crystal_osc: // ETH_CLOCK_GPIO0_IN - case EthClockMode_t::Int_50MHz_GPIO_0: // ETH_CLOCK_GPIO0_OUT - gpio_reset_pin(GPIO_NUM_0); - break; - case EthClockMode_t::Int_50MHz_GPIO_16: // ETH_CLOCK_GPIO16_OUT - gpio_reset_pin(GPIO_NUM_16); - break; - case EthClockMode_t::Int_50MHz_GPIO_17_inv: // ETH_CLOCK_GPIO17_OUT - gpio_reset_pin(GPIO_NUM_17); - break; - } - */ - delay(1); -} - -bool ETHConnected() { - if (EthEventData.EthServicesInitialized()) { - if (EthLinkUp()) { - return true; - } - // Apparently we missed an event - EthEventData.processedDisconnect = false; - } else if (EthEventData.ethInitSuccess) { - if (EthLinkUp()) { - EthEventData.setEthConnected(); - if (NetworkLocalIP() != IPAddress(0, 0, 0, 0) && - !EthEventData.EthGotIP()) { - EthEventData.processedGotIP = false; - } - if (EthEventData.lastConnectMoment.isSet()) { - if (!EthEventData.EthServicesInitialized()) { - if (EthEventData.lastConnectMoment.millisPassedSince() > 10000 && - EthEventData.lastGetIPmoment.isSet()) { - EthEventData.processedGotIP = false; - EthEventData.markLostIP(); - } - } - } - return EthEventData.EthServicesInitialized(); - } else { - if (EthEventData.last_eth_connect_attempt_moment.isSet() && - EthEventData.last_eth_connect_attempt_moment.millisPassedSince() < 5000) { - return false; - } - setNetworkMedium(NetworkMedium_t::WIFI); - } - } - return false; -} - +#include "../ESPEasyCore/ESPEasyEth.h" + +#if FEATURE_ETHERNET + +#include "../CustomBuild/ESPEasyLimits.h" +#include "../ESPEasyCore/ESPEasyGPIO.h" +#include "../ESPEasyCore/ESPEasyNetwork.h" +#include "../ESPEasyCore/ESPEasyWifi.h" +#include "../ESPEasyCore/ESPEasy_Log.h" +#include "../ESPEasyCore/ESPEasyGPIO.h" +#include "../ESPEasyCore/ESPEasyEthEvent.h" +#include "../Globals/ESPEasyEthEvent.h" +#include "../Globals/NetworkState.h" +#include "../Globals/Settings.h" +#include "../Helpers/Hardware_GPIO.h" +#include "../Helpers/StringConverter.h" +#include "../Helpers/Networking.h" + +#include +#include +#if ESP_IDF_VERSION_MAJOR > 3 + #include +#else + #include +#endif + +#include + +bool ethUseStaticIP() { + return Settings.ETH_IP[0] != 0 && Settings.ETH_IP[0] != 255; +} + +void ethSetupStaticIPconfig() { + const IPAddress IP_zero(0, 0, 0, 0); + if (!ethUseStaticIP()) { + if (!ETH.config(IP_zero, IP_zero, IP_zero, IP_zero)) { + addLog(LOG_LEVEL_ERROR, F("ETH : Cannot set IP config")); + } + return; + } + const IPAddress ip = Settings.ETH_IP; + const IPAddress gw = Settings.ETH_Gateway; + const IPAddress subnet = Settings.ETH_Subnet; + const IPAddress dns = Settings.ETH_DNS; + + EthEventData.dns0_cache = dns; + EthEventData.dns1_cache = IP_zero; + + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + String log = F("ETH IP : Static IP : "); + log += formatIP(ip); + log += F(" GW: "); + log += formatIP(gw); + log += F(" SN: "); + log += formatIP(subnet); + log += F(" DNS: "); + log += formatIP(dns); + addLogMove(LOG_LEVEL_INFO, log); + } + ETH.config(ip, gw, subnet, dns); + setDNS(0, EthEventData.dns0_cache); + setDNS(1, EthEventData.dns1_cache); +} + +bool ethCheckSettings() { + return isValid(Settings.ETH_Phy_Type) +#if CONFIG_ETH_USE_ESP32_EMAC + && (isValid(Settings.ETH_Clock_Mode)/* || isSPI_EthernetType(Settings.ETH_Phy_Type)*/) +#endif + && isValid(Settings.NetworkMedium) + && validGpio(Settings.ETH_Pin_mdc_cs) + && (isSPI_EthernetType(Settings.ETH_Phy_Type) || + ( validGpio(Settings.ETH_Pin_mdio_irq) && + (validGpio(Settings.ETH_Pin_power_rst) || (Settings.ETH_Pin_power_rst == -1)) + ) + ); // Some boards have fixed power +} + +bool ethPrepare() { + char hostname[40]; + safe_strncpy(hostname, NetworkCreateRFCCompliantHostname().c_str(), sizeof(hostname)); + ETH.setHostname(hostname); + ethSetupStaticIPconfig(); + return true; +} + +void ethPrintSettings() { + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + String log; + if (log.reserve(115)) { +// log += F("ETH/Wifi mode: "); +// log += toString(active_network_medium); + log += F("ETH PHY Type: "); + log += toString(Settings.ETH_Phy_Type); + log += F(" PHY Addr: "); + log += Settings.ETH_Phy_Addr; + + if (!isSPI_EthernetType(Settings.ETH_Phy_Type)) { + log += F(" Eth Clock mode: "); + log += toString(Settings.ETH_Clock_Mode); + } + log += strformat(isSPI_EthernetType(Settings.ETH_Phy_Type) + ? F(" CS: %d IRQ: %d RST: %d") : F(" MDC: %d MIO: %d PWR: %d"), + Settings.ETH_Pin_mdc_cs, + Settings.ETH_Pin_mdio_irq, + Settings.ETH_Pin_power_rst); + addLogMove(LOG_LEVEL_INFO, log); + } + } +} + +MAC_address ETHMacAddress() { + MAC_address mac; + if(!EthEventData.ethInitSuccess) { + addLog(LOG_LEVEL_ERROR, F("Call NetworkMacAddress() only on connected Ethernet!")); + } else { + #if ESP_IDF_VERSION_MAJOR > 3 + ETH.macAddress(mac.mac); + #else + esp_eth_get_mac(mac.mac); + #endif + } + return mac; +} + +void removeEthEventHandler() +{ + WiFi.removeEvent(EthEventData.wm_event_id); + EthEventData.wm_event_id = 0; +} + +void registerEthEventHandler() +{ + if (EthEventData.wm_event_id != 0) { + removeEthEventHandler(); + } + EthEventData.wm_event_id = WiFi.onEvent(EthEvent); +} + + +bool ETHConnectRelaxed() { + if (EthEventData.ethInitSuccess) { + return EthLinkUp(); + } + ethPrintSettings(); + if (!ethCheckSettings()) + { + addLog(LOG_LEVEL_ERROR, F("ETH: Settings not correct!!!")); + EthEventData.ethInitSuccess = false; + return false; + } + // Re-register event listener + removeEthEventHandler(); + + ethPower(true); + EthEventData.markEthBegin(); + + // Re-register event listener + registerEthEventHandler(); + + if (!EthEventData.ethInitSuccess) { +#if ESP_IDF_VERSION_MAJOR < 5 + EthEventData.ethInitSuccess = ETH.begin( + Settings.ETH_Phy_Addr, + Settings.ETH_Pin_power_rst, + Settings.ETH_Pin_mdc_cs, + Settings.ETH_Pin_mdio_irq, + (eth_phy_type_t)Settings.ETH_Phy_Type, + (eth_clock_mode_t)Settings.ETH_Clock_Mode); +#else +#if FEATURE_USE_IPV6 + if (Settings.EnableIPv6()) { + ETH.enableIPv6(true); + } +#endif + + if (isSPI_EthernetType(Settings.ETH_Phy_Type)) { + spi_host_device_t SPI_host = Settings.getSPI_host(); + if (SPI_host == spi_host_device_t::SPI_HOST_MAX) { + addLog(LOG_LEVEL_ERROR, F("SPI not enabled")); + #ifdef ESP32C3 + // FIXME TD-er: Fallback for ETH01-EVO board + SPI_host = spi_host_device_t::SPI2_HOST; + Settings.InitSPI = static_cast(SPI_Options_e::UserDefined); + Settings.SPI_SCLK_pin = 7; + Settings.SPI_MISO_pin = 3; + Settings.SPI_MOSI_pin = 10; + #endif + } + // else + { +#if ETH_SPI_SUPPORTS_CUSTOM + EthEventData.ethInitSuccess = ETH.begin( + to_ESP_phy_type(Settings.ETH_Phy_Type), + Settings.ETH_Phy_Addr, + Settings.ETH_Pin_mdc_cs, + Settings.ETH_Pin_mdio_irq, + Settings.ETH_Pin_power_rst, + SPI); +#else + EthEventData.ethInitSuccess = ETH.begin( + to_ESP_phy_type(Settings.ETH_Phy_Type), + Settings.ETH_Phy_Addr, + Settings.ETH_Pin_mdc_cs, + Settings.ETH_Pin_mdio_irq, + Settings.ETH_Pin_power_rst, + SPI_host, + static_cast(Settings.SPI_SCLK_pin), + static_cast(Settings.SPI_MISO_pin), + static_cast(Settings.SPI_MOSI_pin)); +#endif + } + } else { +# if CONFIG_ETH_USE_ESP32_EMAC + ethResetGPIOpins(); + EthEventData.ethInitSuccess = ETH.begin( + to_ESP_phy_type(Settings.ETH_Phy_Type), + Settings.ETH_Phy_Addr, + Settings.ETH_Pin_mdc_cs, + Settings.ETH_Pin_mdio_irq, + Settings.ETH_Pin_power_rst, + (eth_clock_mode_t)Settings.ETH_Clock_Mode); +#endif + } + +#endif + } + if (EthEventData.ethInitSuccess) { + // FIXME TD-er: Not sure if this is correctly set to false + //EthEventData.ethConnectAttemptNeeded = false; + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { +#if ESP_IDF_VERSION_MAJOR < 5 + addLog(LOG_LEVEL_INFO, strformat( + F("ETH : MAC: %s speed: %dM %s Link: %s"), + ETH.macAddress().c_str(), + ETH.linkSpeed(), + String(ETH.fullDuplex() ? F("Full Duplex") : F("Half Duplex")).c_str(), + String(ETH.linkUp() ? F("Up") : F("Down")).c_str())); +#else + addLog(LOG_LEVEL_INFO, strformat( + F("ETH : MAC: %s phy addr: %d speed: %dM %s Link: %s"), + ETH.macAddress().c_str(), + ETH.phyAddr(), + ETH.linkSpeed(), + concat( + ETH.fullDuplex() ? F("Full Duplex") : F("Half Duplex"), + ETH.autoNegotiation() ? F("(auto)") : F("")).c_str(), + String(ETH.linkUp() ? F("Up") : F("Down")).c_str())); +#endif + } + + if (EthLinkUp()) { + // We might miss the connected event, since we are already connected. + EthEventData.markConnected(); + } + } else { + addLog(LOG_LEVEL_ERROR, F("ETH : Failed to initialize ETH")); + } + return EthEventData.ethInitSuccess; +} + +void ethPower(bool enable) { + if (isSPI_EthernetType(Settings.ETH_Phy_Type)) + return; + if (Settings.ETH_Pin_power_rst != -1) { + if (GPIO_Internal_Read(Settings.ETH_Pin_power_rst) == enable) { + // Already the desired state + return; + } + addLog(LOG_LEVEL_INFO, enable ? F("ETH power ON") : F("ETH power OFF")); + if (!enable) { + EthEventData.ethInitSuccess = false; + EthEventData.clearAll(); + #ifdef ESP_IDF_VERSION_MAJOR + // FIXME TD-er: See: https://github.com/espressif/arduino-esp32/issues/6105 + // Need to store the last link state, as it will be cleared after destructing the object. + EthEventData.setEthDisconnected(); + if (ETH.linkUp()) { + EthEventData.setEthConnected(); + } + #endif +// ETH = ETHClass(); + } + if (enable) { +// ethResetGPIOpins(); + } +// gpio_reset_pin((gpio_num_t)Settings.ETH_Pin_power_rst); + + GPIO_Write(PLUGIN_GPIO, Settings.ETH_Pin_power_rst, enable ? 1 : 0); + if (!enable) { + if (Settings.ETH_Clock_Mode == EthClockMode_t::Ext_crystal_osc) { + delay(600); // Give some time to discharge any capacitors + // Delay is needed to make sure no clock signal remains present which may cause the ESP to boot into flash mode. + } + } else { + delay(400); // LAN chip needs to initialize before calling Eth.begin() + } + } +} + +void ethResetGPIOpins() { + if (isSPI_EthernetType(Settings.ETH_Phy_Type)) + return; + + // fix an disconnection issue after rebooting Olimex POE - this forces a clean state for all GPIO involved in RMII + // Thanks to @s-hadinger and @Jason2866 + // Resetting state of power pin is done in ethPower() + addLog(LOG_LEVEL_INFO, F("ethResetGPIOpins()")); + gpio_reset_pin((gpio_num_t)Settings.ETH_Pin_mdc_cs); + gpio_reset_pin((gpio_num_t)Settings.ETH_Pin_mdio_irq); +# if CONFIG_ETH_USE_ESP32_EMAC + gpio_reset_pin(GPIO_NUM_19); // EMAC_TXD0 - hardcoded + gpio_reset_pin(GPIO_NUM_21); // EMAC_TX_EN - hardcoded + gpio_reset_pin(GPIO_NUM_22); // EMAC_TXD1 - hardcoded + gpio_reset_pin(GPIO_NUM_25); // EMAC_RXD0 - hardcoded + gpio_reset_pin(GPIO_NUM_26); // EMAC_RXD1 - hardcoded + gpio_reset_pin(GPIO_NUM_27); // EMAC_RX_CRS_DV - hardcoded +#endif + /* + switch (Settings.ETH_Clock_Mode) { + case EthClockMode_t::Ext_crystal_osc: // ETH_CLOCK_GPIO0_IN + case EthClockMode_t::Int_50MHz_GPIO_0: // ETH_CLOCK_GPIO0_OUT + gpio_reset_pin(GPIO_NUM_0); + break; + case EthClockMode_t::Int_50MHz_GPIO_16: // ETH_CLOCK_GPIO16_OUT + gpio_reset_pin(GPIO_NUM_16); + break; + case EthClockMode_t::Int_50MHz_GPIO_17_inv: // ETH_CLOCK_GPIO17_OUT + gpio_reset_pin(GPIO_NUM_17); + break; + } + */ + delay(1); +} + +bool ETHConnected() { + if (EthEventData.EthServicesInitialized()) { + if (EthLinkUp()) { +// stop_eth_dhcps(); + return true; + } + // Apparently we missed an event + EthEventData.processedDisconnect = false; + } else if (EthEventData.ethInitSuccess) { + if (EthLinkUp()) { +// stop_eth_dhcps(); + EthEventData.setEthConnected(); + if (NetworkLocalIP() != IPAddress(0, 0, 0, 0) && + !EthEventData.EthGotIP()) { + EthEventData.processedGotIP = false; + } + if (EthEventData.lastConnectMoment.isSet()) { + if (!EthEventData.EthServicesInitialized()) { + if (EthEventData.lastConnectMoment.millisPassedSince() > 10000 && + EthEventData.lastGetIPmoment.isSet()) { + EthEventData.processedGotIP = false; + EthEventData.markLostIP(); + } + } + } + return EthEventData.EthServicesInitialized(); + } else { + if (EthEventData.last_eth_connect_attempt_moment.isSet() && + EthEventData.last_eth_connect_attempt_moment.millisPassedSince() < 5000) { + return false; + } + setNetworkMedium(NetworkMedium_t::WIFI); + } + } + return false; +} + #endif // if FEATURE_ETHERNET \ No newline at end of file diff --git a/src/src/ESPEasyCore/ESPEasyNetwork.cpp b/src/src/ESPEasyCore/ESPEasyNetwork.cpp index a8fe367094..2ffb2b5b78 100644 --- a/src/src/ESPEasyCore/ESPEasyNetwork.cpp +++ b/src/src/ESPEasyCore/ESPEasyNetwork.cpp @@ -1,432 +1,506 @@ -#include "../ESPEasyCore/ESPEasyNetwork.h" - -#include "../ESPEasyCore/ESPEasy_Log.h" -#include "../ESPEasyCore/ESPEasyEth.h" -#include "../ESPEasyCore/ESPEasyWifi.h" -#include "../Globals/ESPEasy_time.h" -#include "../Globals/ESPEasyWiFiEvent.h" -#include "../Globals/NetworkState.h" -#include "../Globals/Settings.h" - -#include "../Helpers/Network.h" -#include "../Helpers/Networking.h" -#include "../Helpers/StringConverter.h" -#include "../Helpers/MDNS_Helper.h" - -#if FEATURE_ETHERNET -#include "../Globals/ESPEasyEthEvent.h" -#include -#endif - - -#if FEATURE_USE_IPV6 -#include - -// ----------------------------------------------------------------------------------------------------------------------- -// ---------------------------------------------------- Private functions ------------------------------------------------ -// ----------------------------------------------------------------------------------------------------------------------- - -esp_netif_t* get_esp_interface_netif(esp_interface_t interface); -#endif - - -void setNetworkMedium(NetworkMedium_t new_medium) { -#if !(FEATURE_ETHERNET) - if (new_medium == NetworkMedium_t::Ethernet) { - new_medium = NetworkMedium_t::WIFI; - } -#endif - if (active_network_medium == new_medium) { - return; - } - switch (active_network_medium) { - case NetworkMedium_t::Ethernet: - #if FEATURE_ETHERNET - // FIXME TD-er: How to 'end' ETH? -// ETH.end(); - if (new_medium == NetworkMedium_t::WIFI) { - WiFiEventData.clearAll(); -#if ESP_IDF_VERSION_MAJOR >= 5 - WiFi.STA.setDefault(); -#endif - } - #endif - break; - case NetworkMedium_t::WIFI: - WiFiEventData.timerAPoff.setMillisFromNow(WIFI_AP_OFF_TIMER_DURATION); - WiFiEventData.timerAPstart.clear(); - if (new_medium == NetworkMedium_t::Ethernet) { -#if ESP_IDF_VERSION_MAJOR >= 5 -#if FEATURE_ETHERNET - ETH.setDefault(); -#endif -#endif - WifiDisconnect(); - } - break; - case NetworkMedium_t::NotSet: - break; - } - statusLED(true); - active_network_medium = new_medium; - #ifndef BUILD_MINIMAL_OTA - addLog(LOG_LEVEL_INFO, concat(F("Set Network mode: "), toString(active_network_medium))); - #endif -} - - -/*********************************************************************************************\ - Ethernet or Wifi Support for ESP32 Build flag FEATURE_ETHERNET -\*********************************************************************************************/ -void NetworkConnectRelaxed() { - if (NetworkConnected()) return; -#if FEATURE_ETHERNET - if(active_network_medium == NetworkMedium_t::Ethernet) { - if (ETHConnectRelaxed()) { - return; - } - // Failed to start the Ethernet network, probably not present of wrong parameters. - // So set the runtime active medium to WiFi to try connecting to WiFi or at least start the AP. - setNetworkMedium(NetworkMedium_t::WIFI); - } -#endif - // Failed to start the Ethernet network, probably not present of wrong parameters. - // So set the runtime active medium to WiFi to try connecting to WiFi or at least start the AP. - WiFiConnectRelaxed(); -} - -bool NetworkConnected() { - #if FEATURE_ETHERNET - if(active_network_medium == NetworkMedium_t::Ethernet) { - return ETHConnected(); - } - #endif - return WiFiConnected(); -} - -IPAddress NetworkLocalIP() { - #if FEATURE_ETHERNET - if(active_network_medium == NetworkMedium_t::Ethernet) { - if(EthEventData.ethInitSuccess) { - return ETH.localIP(); - } else { - addLog(LOG_LEVEL_ERROR, F("Call NetworkLocalIP() only on connected Ethernet!")); - return IPAddress(); - } - } - #endif - return WiFi.localIP(); -} - -IPAddress NetworkSubnetMask() { - #if FEATURE_ETHERNET - if(active_network_medium == NetworkMedium_t::Ethernet) { - if(EthEventData.ethInitSuccess) { - return ETH.subnetMask(); - } else { - addLog(LOG_LEVEL_ERROR, F("Call NetworkSubnetMask() only on connected Ethernet!")); - return IPAddress(); - } - } - #endif - return WiFi.subnetMask(); -} - -IPAddress NetworkGatewayIP() { - #if FEATURE_ETHERNET - if(active_network_medium == NetworkMedium_t::Ethernet) { - if(EthEventData.ethInitSuccess) { - return ETH.gatewayIP(); - } else { - addLog(LOG_LEVEL_ERROR, F("Call NetworkGatewayIP() only on connected Ethernet!")); - return IPAddress(); - } - } - #endif - return WiFi.gatewayIP(); -} - -IPAddress NetworkDnsIP(uint8_t dns_no) { - scrubDNS(); - #if FEATURE_ETHERNET - if(active_network_medium == NetworkMedium_t::Ethernet) { - if(EthEventData.ethInitSuccess) { - return ETH.dnsIP(dns_no); - } else { - addLog(LOG_LEVEL_ERROR, F("Call NetworkDnsIP(uint8_t dns_no) only on connected Ethernet!")); - return IPAddress(); - } - } - #endif - return WiFi.dnsIP(dns_no); -} - -#if FEATURE_USE_IPV6 -esp_netif_t * getActiveNetworkMediumInterface() { - esp_interface_t iface = ESP_IF_MAX; - #if FEATURE_ETHERNET - if(active_network_medium == NetworkMedium_t::Ethernet) { - if(EthEventData.ethInitSuccess) { - esp_netif_t *res = ETH.netif(); - if (res == nullptr) { - res = get_esp_interface_netif(ESP_IF_ETH); - } - if (res != nullptr) - return res; - } - } else - #endif - { - if (WifiIsSTA(WiFi.getMode())) { - iface = ESP_IF_WIFI_STA; - } - } - if (ESP_IF_MAX == iface) - return nullptr; - return get_esp_interface_netif(iface); -} - -IPAddress NetworkLocalIP6() { - esp_netif_t * iface = getActiveNetworkMediumInterface(); - esp_ip6_addr_t addr; - if (nullptr == iface || - esp_netif_get_ip6_linklocal(iface, &addr)) - { - return IN6ADDR_ANY; - } - - IPAddress res(IPv6, (const uint8_t*)addr.addr, addr.zone); - return res; -} - -IPAddress NetworkGlobalIP6() { - esp_netif_t * iface = getActiveNetworkMediumInterface(); - esp_ip6_addr_t addr; - if (nullptr == iface || - esp_netif_get_ip6_global(iface, &addr)) - { - return IN6ADDR_ANY; - } - - IPAddress res(IPv6, (const uint8_t*)addr.addr, addr.zone); - return res; -} - -IP6Addresses_t NetworkAllIPv6() { - IP6Addresses_t addresses; - esp_netif_t * iface = getActiveNetworkMediumInterface(); - if (nullptr != iface) { - esp_ip6_addr_t esp_ip6_addr[LWIP_IPV6_NUM_ADDRESSES]{}; - - int count = esp_netif_get_all_ip6(iface, esp_ip6_addr); - for (int i = 0; i < count; ++i) { - addresses.emplace_back(IPv6, (const uint8_t*)esp_ip6_addr[i].addr, esp_ip6_addr[i].zone); - } - } - - return addresses; -} - -bool IPv6_from_MAC(const MAC_address& mac, IPAddress& ipv6) -{ - if (ipv6 == IN6ADDR_ANY) { return false; } - int index_offset = 8; - - for (int i = 0; i < 6; ++i, ++index_offset) { - ipv6[index_offset] = mac.mac[i]; - - if (i == 0) { - // invert bit 2 - bitToggle(ipv6[index_offset], 1); - } - - if (i == 2) { - ipv6[++index_offset] = 0xFF; - ipv6[++index_offset] = 0xFE; - } - } -/* - addLog(LOG_LEVEL_INFO, strformat( - F("IPv6_from_MAC: Mac %s IP %s"), - mac.toString().c_str(), - ipv6.toString(true).c_str() - )); -*/ - return true; -} - -bool is_IPv6_based_on_MAC(const MAC_address& mac, const IPAddress& ipv6) -{ - IPAddress tmp = ipv6; - - if (IPv6_from_MAC(mac, tmp)) { - return ipv6 == tmp; - } - return false; -} - -bool IPv6_link_local_from_MAC(const MAC_address& mac, IPAddress& ipv6) -{ - ipv6 = NetworkLocalIP6(); - return IPv6_from_MAC(mac, ipv6); -} - -bool is_IPv6_link_local_from_MAC(const MAC_address& mac) -{ - return is_IPv6_based_on_MAC(mac, NetworkLocalIP6()); -} - -// Assume we're in the same subnet, thus use our own IPv6 global address -bool IPv6_global_from_MAC(const MAC_address& mac, IPAddress& ipv6) -{ - ipv6 = NetworkGlobalIP6(); - return IPv6_from_MAC(mac, ipv6); -} - -bool is_IPv6_global_from_MAC(const MAC_address& mac) -{ - return is_IPv6_based_on_MAC(mac, NetworkGlobalIP6()); -} - -#endif // if FEATURE_USE_IPV6 - - - -MAC_address NetworkMacAddress() { - #if FEATURE_ETHERNET - if(active_network_medium == NetworkMedium_t::Ethernet) { - return ETHMacAddress(); - } - #endif - MAC_address mac; - WiFi.macAddress(mac.mac); - return mac; -} - -String NetworkGetHostname() { - #ifdef ESP32 - #if FEATURE_ETHERNET - if(Settings.NetworkMedium == NetworkMedium_t::Ethernet && EthEventData.ethInitSuccess) { - return String(ETH.getHostname()); - } - #endif - return String(WiFi.getHostname()); - #else - return String(WiFi.hostname()); - #endif -} - -// ******************************************************************************** -// Determine Wifi AP name to set. (also used for mDNS) -// ******************************************************************************** -String NetworkGetHostNameFromSettings(bool force_add_unitnr) -{ - if (force_add_unitnr) return Settings.getHostname(true); - return Settings.getHostname(); -} - -String NetworkCreateRFCCompliantHostname(bool force_add_unitnr) { - // Create hostname with - instead of spaces - return makeRFCCompliantName(NetworkGetHostNameFromSettings(force_add_unitnr)); -} - -String makeRFCCompliantName(const String& name, const char replaceChar, const char allowedChar, const size_t maxlength) { - String hostname(name); - // See RFC952. (when using the default arguments: '-', '-', 24) - // Allowed chars: - // * letters (a-z, A-Z) - // * numerals (0-9) - // * Hyphen (-) - // * Max length 24 - replaceUnicodeByChar(hostname, replaceChar); - for (size_t i = 0; i < hostname.length(); ++i) { - const char c = hostname[i]; - if (!isAlphaNumeric(c) && (c != allowedChar)) { - hostname[i] = replaceChar; - } - } - - // May not start or end with a hyphen - const String dash(replaceChar); - while (hostname.startsWith(dash)) { - hostname = hostname.substring(1); - } - while (hostname.endsWith(dash)) { - hostname = hostname.substring(0, hostname.length() - 1); - } - - // May not contain only numerals - bool onlyNumerals = true; - for (size_t i = 0; onlyNumerals && i < hostname.length(); ++i) { - const char c = hostname[i]; - if (!isdigit(c)) { - onlyNumerals = false; - } - } - if (onlyNumerals) { - hostname = strformat(F("ESPEasy%c%s"), replaceChar, hostname.c_str()); - } - - if ((maxlength > 0) && (hostname.length() > maxlength)) { - hostname = hostname.substring(0, maxlength); - } - - return hostname; -} - -MAC_address WifiSoftAPmacAddress() { - MAC_address mac; - WiFi.softAPmacAddress(mac.mac); - return mac; -} - -MAC_address WifiSTAmacAddress() { - MAC_address mac; - WiFi.macAddress(mac.mac); - return mac; -} - -void CheckRunningServices() { - // First try to get the time, since that may be used in logs - if (Settings.UseNTP() && node_time.getTimeSource() > timeSource_t::NTP_time_source) { - node_time.lastNTPSyncTime_ms = 0; - node_time.initTime(); - } -#if FEATURE_SET_WIFI_TX_PWR - if (active_network_medium == NetworkMedium_t::WIFI) - { - SetWiFiTXpower(); - } -#endif - set_mDNS(); -} - -#if FEATURE_ETHERNET -bool EthFullDuplex() -{ - if (EthEventData.ethInitSuccess) - return ETH.fullDuplex(); - return false; -} - -bool EthLinkUp() -{ - if (EthEventData.ethInitSuccess) { - #if ESP_IDF_VERSION_MAJOR < 5 - // FIXME TD-er: See: https://github.com/espressif/arduino-esp32/issues/6105 - return EthEventData.EthConnected(); - #else - return ETH.linkUp(); - #endif - } - return false; -} - -uint8_t EthLinkSpeed() -{ - if (EthEventData.ethInitSuccess) { - return ETH.linkSpeed(); - } - return 0; -} -#endif +#include "../ESPEasyCore/ESPEasyNetwork.h" + +#include "../ESPEasyCore/ESPEasy_Log.h" +#include "../ESPEasyCore/ESPEasyEth.h" +#include "../ESPEasyCore/ESPEasyWifi.h" + +#ifdef USES_ESPEASY_NOW +#include "../Globals/ESPEasy_now_state.h" +#include "../Globals/ESPEasy_now_handler.h" +#endif + +#include "../Globals/ESPEasy_time.h" +#include "../Globals/ESPEasyWiFiEvent.h" +#include "../Globals/NetworkState.h" +#include "../Globals/Settings.h" + +#include "../Helpers/Network.h" +#include "../Helpers/Networking.h" +#include "../Helpers/StringConverter.h" +#include "../Helpers/MDNS_Helper.h" + +#if FEATURE_ETHERNET +#include "../Globals/ESPEasyEthEvent.h" +#include +#endif + + +#if FEATURE_USE_IPV6 +#include + +// ----------------------------------------------------------------------------------------------------------------------- +// ---------------------------------------------------- Private functions ------------------------------------------------ +// ----------------------------------------------------------------------------------------------------------------------- + +esp_netif_t* get_esp_interface_netif(esp_interface_t interface); +#endif + + +void setNetworkMedium(NetworkMedium_t new_medium) { +#if !(FEATURE_ETHERNET) + if (new_medium == NetworkMedium_t::Ethernet) { + new_medium = NetworkMedium_t::WIFI; + } +#endif + if (active_network_medium == new_medium) { + return; + } + bool process_exit_active_medium = true; +#ifdef USES_ESPEASY_NOW + if (new_medium == NetworkMedium_t::ESPEasyNOW_only) { + if (!Settings.UseESPEasyNow()) { + return; + } + if (WiFiEventData.unprocessedWifiEvents() || + WiFiEventData.wifiConnectInProgress) { + return; + } + if (WiFiEventData.WiFiServicesInitialized()) { + return; + } + if (active_network_medium != NetworkMedium_t::WIFI) { + // Only allow to set to ESPEasyNOW_only from WiFi + return; + } + #ifdef USES_ESPEASY_NOW + if (use_EspEasy_now) { + ESPEasy_now_handler.end(); + active_network_medium = new_medium; + ESPEasy_now_handler.begin(); + process_exit_active_medium = false; + } + + #endif + } +#endif + if (process_exit_active_medium) { + switch (active_network_medium) { + case NetworkMedium_t::Ethernet: + #if FEATURE_ETHERNET + // FIXME TD-er: How to 'end' ETH? + // ETH.end(); + if (new_medium == NetworkMedium_t::WIFI) { + WiFiEventData.clearAll(); +#if ESP_IDF_VERSION_MAJOR >= 5 + WiFi.STA.setDefault(); +#endif + } + #endif + break; + case NetworkMedium_t::WIFI: + WiFiEventData.timerAPoff.setMillisFromNow(WIFI_AP_OFF_TIMER_DURATION); + WiFiEventData.timerAPstart.clear(); + if (new_medium == NetworkMedium_t::Ethernet) { +#if ESP_IDF_VERSION_MAJOR >= 5 +#if FEATURE_ETHERNET + ETH.setDefault(); +#endif +#endif + WifiDisconnect(); + } + break; +#ifdef USES_ESPEASY_NOW + case NetworkMedium_t::ESPEasyNOW_only: + if (use_EspEasy_now) { + ESPEasy_now_handler.end(); + } + + //WiFiEventData.clearAll(); + break; +#endif + case NetworkMedium_t::NotSet: + break; + } + } + statusLED(true); + active_network_medium = new_medium; + last_network_medium_set_moment.setNow(); + #ifndef BUILD_MINIMAL_OTA + addLog(LOG_LEVEL_INFO, concat(F("Set Network mode: "), toString(active_network_medium))); + #endif +} + +bool isESPEasy_now_only() { + #ifdef USES_ESPEASY_NOW + if (active_network_medium == NetworkMedium_t::ESPEasyNOW_only) { + return true; + } + if (use_EspEasy_now) { + return !NetworkConnected(); + } + #endif + return false; +} + + +/*********************************************************************************************\ + Ethernet or Wifi Support for ESP32 Build flag FEATURE_ETHERNET +\*********************************************************************************************/ +void NetworkConnectRelaxed() { + if (NetworkConnected()) return; +#if FEATURE_ETHERNET + if(active_network_medium == NetworkMedium_t::Ethernet) { + if (ETHConnectRelaxed()) { + return; + } + } +#endif + // Failed to start the Ethernet network, probably not present of wrong parameters. + // So set the runtime active medium to WiFi to try connecting to WiFi or at least start the AP. + WiFiConnectRelaxed(); +} + +bool NetworkConnected() { + #if FEATURE_ETHERNET + if(active_network_medium == NetworkMedium_t::Ethernet) { + return ETHConnected(); + } + #endif + return WiFiConnected(); +} + +IPAddress NetworkLocalIP() { + #if FEATURE_ETHERNET + if(active_network_medium == NetworkMedium_t::Ethernet) { + if(EthEventData.ethInitSuccess) { + return ETH.localIP(); + } else { + addLog(LOG_LEVEL_ERROR, F("Call NetworkLocalIP() only on connected Ethernet!")); + return IPAddress(); + } + } + #endif + return WiFi.localIP(); +} + +IPAddress NetworkSubnetMask() { + #if FEATURE_ETHERNET + if(active_network_medium == NetworkMedium_t::Ethernet) { + if(EthEventData.ethInitSuccess) { + return ETH.subnetMask(); + } else { + addLog(LOG_LEVEL_ERROR, F("Call NetworkSubnetMask() only on connected Ethernet!")); + return IPAddress(); + } + } + #endif + return WiFi.subnetMask(); +} + +IPAddress NetworkGatewayIP() { + #if FEATURE_ETHERNET + if(active_network_medium == NetworkMedium_t::Ethernet) { + if(EthEventData.ethInitSuccess) { + return ETH.gatewayIP(); + } else { + addLog(LOG_LEVEL_ERROR, F("Call NetworkGatewayIP() only on connected Ethernet!")); + return IPAddress(); + } + } + #endif + return WiFi.gatewayIP(); +} + +IPAddress NetworkDnsIP(uint8_t dns_no) { + scrubDNS(); + #if FEATURE_ETHERNET + if(active_network_medium == NetworkMedium_t::Ethernet) { + if(EthEventData.ethInitSuccess) { + return ETH.dnsIP(dns_no); + } else { + addLog(LOG_LEVEL_ERROR, F("Call NetworkDnsIP(uint8_t dns_no) only on connected Ethernet!")); + return IPAddress(); + } + } + #endif + return WiFi.dnsIP(dns_no); +} + +#if FEATURE_USE_IPV6 +esp_netif_t * getActiveNetworkMediumInterface() { + esp_interface_t iface = ESP_IF_MAX; + #if FEATURE_ETHERNET + if(active_network_medium == NetworkMedium_t::Ethernet) { + if(EthEventData.ethInitSuccess) { + esp_netif_t *res = ETH.netif(); + if (res == nullptr) { + res = get_esp_interface_netif(ESP_IF_ETH); + } + if (res != nullptr) + return res; + } + } else + #endif + { + if (WifiIsSTA(WiFi.getMode())) { + iface = ESP_IF_WIFI_STA; + } + } + if (ESP_IF_MAX == iface) + return nullptr; + return get_esp_interface_netif(iface); +} + +IPAddress NetworkLocalIP6() { + esp_netif_t * iface = getActiveNetworkMediumInterface(); + esp_ip6_addr_t addr; + if (nullptr == iface || + esp_netif_get_ip6_linklocal(iface, &addr)) + { + return IN6ADDR_ANY; + } + + IPAddress res(IPv6, (const uint8_t*)addr.addr, addr.zone); + return res; +} + +IPAddress NetworkGlobalIP6() { + esp_netif_t * iface = getActiveNetworkMediumInterface(); + esp_ip6_addr_t addr; + if (nullptr == iface || + esp_netif_get_ip6_global(iface, &addr)) + { + return IN6ADDR_ANY; + } + + IPAddress res(IPv6, (const uint8_t*)addr.addr, addr.zone); + return res; +} + +IP6Addresses_t NetworkAllIPv6() { + IP6Addresses_t addresses; + esp_netif_t * iface = getActiveNetworkMediumInterface(); + if (nullptr != iface) { + esp_ip6_addr_t esp_ip6_addr[LWIP_IPV6_NUM_ADDRESSES]{}; + + int count = esp_netif_get_all_ip6(iface, esp_ip6_addr); + for (int i = 0; i < count; ++i) { + addresses.emplace_back(IPv6, (const uint8_t*)esp_ip6_addr[i].addr, esp_ip6_addr[i].zone); + } + } + + return addresses; +} + +bool IPv6_from_MAC(const MAC_address& mac, IPAddress& ipv6) +{ + if (ipv6 == IN6ADDR_ANY) { return false; } + int index_offset = 8; + + for (int i = 0; i < 6; ++i, ++index_offset) { + ipv6[index_offset] = mac.mac[i]; + + if (i == 0) { + // invert bit 2 + bitToggle(ipv6[index_offset], 1); + } + + if (i == 2) { + ipv6[++index_offset] = 0xFF; + ipv6[++index_offset] = 0xFE; + } + } +/* + addLog(LOG_LEVEL_INFO, strformat( + F("IPv6_from_MAC: Mac %s IP %s"), + mac.toString().c_str(), + ipv6.toString(true).c_str() + )); +*/ + return true; +} + +bool is_IPv6_based_on_MAC(const MAC_address& mac, const IPAddress& ipv6) +{ + IPAddress tmp = ipv6; + + if (IPv6_from_MAC(mac, tmp)) { + return ipv6 == tmp; + } + return false; +} + +bool IPv6_link_local_from_MAC(const MAC_address& mac, IPAddress& ipv6) +{ + ipv6 = NetworkLocalIP6(); + return IPv6_from_MAC(mac, ipv6); +} + +bool is_IPv6_link_local_from_MAC(const MAC_address& mac) +{ + return is_IPv6_based_on_MAC(mac, NetworkLocalIP6()); +} + +// Assume we're in the same subnet, thus use our own IPv6 global address +bool IPv6_global_from_MAC(const MAC_address& mac, IPAddress& ipv6) +{ + ipv6 = NetworkGlobalIP6(); + return IPv6_from_MAC(mac, ipv6); +} + +bool is_IPv6_global_from_MAC(const MAC_address& mac) +{ + return is_IPv6_based_on_MAC(mac, NetworkGlobalIP6()); +} + +#endif // if FEATURE_USE_IPV6 + + + +MAC_address NetworkMacAddress() { + #if FEATURE_ETHERNET + if(active_network_medium == NetworkMedium_t::Ethernet) { + return ETHMacAddress(); + } + #endif + return WifiSTAmacAddress(); +} + +String NetworkGetHostname() { + #ifdef ESP32 + #if FEATURE_ETHERNET + if(Settings.NetworkMedium == NetworkMedium_t::Ethernet && EthEventData.ethInitSuccess) { + return String(ETH.getHostname()); + } + #endif + return String(WiFi.getHostname()); + #else + return String(WiFi.hostname()); + #endif +} + +// ******************************************************************************** +// Determine Wifi AP name to set. (also used for mDNS) +// ******************************************************************************** +String NetworkGetHostNameFromSettings(bool force_add_unitnr) +{ + if (force_add_unitnr) return Settings.getHostname(true); + return Settings.getHostname(); +} + +String NetworkCreateRFCCompliantHostname(bool force_add_unitnr) { + // Create hostname with - instead of spaces + return makeRFCCompliantName(NetworkGetHostNameFromSettings(force_add_unitnr)); +} + +String makeRFCCompliantName(const String& name, const char replaceChar, const char allowedChar, const size_t maxlength) { + String hostname(name); + // See RFC952. (when using the default arguments: '-', '-', 24) + // Allowed chars: + // * letters (a-z, A-Z) + // * numerals (0-9) + // * Hyphen (-) + // * Max length 24 + replaceUnicodeByChar(hostname, replaceChar); + for (size_t i = 0; i < hostname.length(); ++i) { + const char c = hostname[i]; + if (!isAlphaNumeric(c) && (c != allowedChar)) { + hostname[i] = replaceChar; + } + } + + // May not start or end with a hyphen + const String dash(replaceChar); + while (hostname.startsWith(dash)) { + hostname = hostname.substring(1); + } + while (hostname.endsWith(dash)) { + hostname = hostname.substring(0, hostname.length() - 1); + } + + // May not contain only numerals + bool onlyNumerals = true; + for (size_t i = 0; onlyNumerals && i < hostname.length(); ++i) { + const char c = hostname[i]; + if (!isdigit(c)) { + onlyNumerals = false; + } + } + if (onlyNumerals) { + hostname = strformat(F("ESPEasy%c%s"), replaceChar, hostname.c_str()); + } + + if ((maxlength > 0) && (hostname.length() > maxlength)) { + hostname = hostname.substring(0, maxlength); + } + + return hostname; +} + +MAC_address WifiSoftAPmacAddress() { + MAC_address mac; + WiFi.softAPmacAddress(mac.mac); + return mac; +} + +MAC_address WifiSTAmacAddress() { + MAC_address mac; + WiFi.macAddress(mac.mac); + return mac; +} + +void CheckRunningServices() { + // First try to get the time, since that may be used in logs + if (Settings.UseNTP() && node_time.getTimeSource() > timeSource_t::NTP_time_source) { + node_time.lastNTPSyncTime_ms = 0; + node_time.initTime(); + } +#if FEATURE_SET_WIFI_TX_PWR + if (active_network_medium == NetworkMedium_t::WIFI + #ifdef USES_ESPEASY_NOW + || active_network_medium == NetworkMedium_t::ESPEasyNOW_only + #endif + ) + { + SetWiFiTXpower(); + } +#endif + set_mDNS(); +} + +#if FEATURE_ETHERNET +bool EthFullDuplex() +{ + if (EthEventData.ethInitSuccess) + return ETH.fullDuplex(); + return false; +} + +bool EthLinkUp() +{ + if (EthEventData.ethInitSuccess) { + #if ESP_IDF_VERSION_MAJOR < 5 + // FIXME TD-er: See: https://github.com/espressif/arduino-esp32/issues/6105 + return EthEventData.EthConnected(); + #else + return ETH.linkUp(); + #endif + } + return false; +} + +uint8_t EthLinkSpeed() +{ + if (EthEventData.ethInitSuccess) { + return ETH.linkSpeed(); + } + return 0; +} + + +void stop_eth_dhcps() { +# if ESP_IDF_VERSION_MAJOR<5 + esp_err_t err = tcpip_adapter_dhcps_stop(TCPIP_ADAPTER_IF_ETH); + if(err != ESP_OK && err != ESP_ERR_TCPIP_ADAPTER_DHCP_ALREADY_STOPPED){ +/* + if (loglevelActiveFor(LOG_LEVEL_ERROR)) { + String log = F("ETH : DHCP server could not be stopped! Error: "); + log += err; + addLog(LOG_LEVEL_ERROR, log); + } +*/ + } +#endif +} + +#endif diff --git a/src/src/ESPEasyCore/ESPEasyNetwork.h b/src/src/ESPEasyCore/ESPEasyNetwork.h index 3c47aa6eb2..4ebd0990f2 100644 --- a/src/src/ESPEasyCore/ESPEasyNetwork.h +++ b/src/src/ESPEasyCore/ESPEasyNetwork.h @@ -1,56 +1,58 @@ -#ifndef ESPEASY_NETWORK_H -#define ESPEASY_NETWORK_H - -#include "../../ESPEasy_common.h" - -#include "../DataStructs/MAC_address.h" - -#include - -#if FEATURE_USE_IPV6 -#include -//typedef uint8_t ip6_addr_type_t; -//typedef std::vector> IP6Addresses_t; -typedef std::vector IP6Addresses_t; -#endif - -void setNetworkMedium(NetworkMedium_t medium); - -void NetworkConnectRelaxed(); -bool NetworkConnected(); -IPAddress NetworkLocalIP(); -IPAddress NetworkSubnetMask(); -IPAddress NetworkGatewayIP(); -IPAddress NetworkDnsIP (uint8_t dns_no); -#if FEATURE_USE_IPV6 - -IPAddress NetworkLocalIP6(); -IPAddress NetworkGlobalIP6(); -IP6Addresses_t NetworkAllIPv6(); - -bool IPv6_link_local_from_MAC(const MAC_address& mac, IPAddress &ipv6); -bool is_IPv6_link_local_from_MAC(const MAC_address& mac); - -// Assume we're in the same subnet, thus use our own IPv6 global address -bool IPv6_global_from_MAC(const MAC_address& mac, IPAddress &ipv6); -bool is_IPv6_global_from_MAC(const MAC_address& mac); - -#endif -MAC_address NetworkMacAddress(); -String NetworkGetHostNameFromSettings(bool force_add_unitnr = false); -String NetworkGetHostname(); -String NetworkCreateRFCCompliantHostname(bool force_add_unitnr = false); -String makeRFCCompliantName(const String& name, const char replaceChar = '-', const char allowedChar = '-', const size_t maxlength = 24); -MAC_address WifiSoftAPmacAddress(); -MAC_address WifiSTAmacAddress(); - -void CheckRunningServices(); - -#if FEATURE_ETHERNET -bool EthFullDuplex(); -bool EthLinkUp(); -uint8_t EthLinkSpeed(); -#endif // if FEATURE_ETHERNET - - +#ifndef ESPEASY_NETWORK_H +#define ESPEASY_NETWORK_H + +#include "../../ESPEasy_common.h" + +#include "../DataStructs/MAC_address.h" + +#include + +#if FEATURE_USE_IPV6 +#include +//typedef uint8_t ip6_addr_type_t; +//typedef std::vector> IP6Addresses_t; +typedef std::vector IP6Addresses_t; +#endif + +void setNetworkMedium(NetworkMedium_t medium); + +bool isESPEasy_now_only(); + +void NetworkConnectRelaxed(); +bool NetworkConnected(); +IPAddress NetworkLocalIP(); +IPAddress NetworkSubnetMask(); +IPAddress NetworkGatewayIP(); +IPAddress NetworkDnsIP (uint8_t dns_no); +#if FEATURE_USE_IPV6 + +IPAddress NetworkLocalIP6(); +IPAddress NetworkGlobalIP6(); +IP6Addresses_t NetworkAllIPv6(); + +bool IPv6_link_local_from_MAC(const MAC_address& mac, IPAddress &ipv6); +bool is_IPv6_link_local_from_MAC(const MAC_address& mac); + +// Assume we're in the same subnet, thus use our own IPv6 global address +bool IPv6_global_from_MAC(const MAC_address& mac, IPAddress &ipv6); +bool is_IPv6_global_from_MAC(const MAC_address& mac); + +#endif +MAC_address NetworkMacAddress(); +String NetworkGetHostNameFromSettings(bool force_add_unitnr = false); +String NetworkGetHostname(); +String NetworkCreateRFCCompliantHostname(bool force_add_unitnr = false); +String makeRFCCompliantName(const String& name, const char replaceChar = '-', const char allowedChar = '-', const size_t maxlength = 24); +MAC_address WifiSoftAPmacAddress(); +MAC_address WifiSTAmacAddress(); + +void CheckRunningServices(); + +#if FEATURE_ETHERNET +bool EthFullDuplex(); +bool EthLinkUp(); +uint8_t EthLinkSpeed(); +void stop_eth_dhcps(); +#endif + #endif \ No newline at end of file diff --git a/src/src/ESPEasyCore/ESPEasyWiFiEvent.cpp b/src/src/ESPEasyCore/ESPEasyWiFiEvent.cpp index ea4d769fd2..a5da7e5150 100644 --- a/src/src/ESPEasyCore/ESPEasyWiFiEvent.cpp +++ b/src/src/ESPEasyCore/ESPEasyWiFiEvent.cpp @@ -1,440 +1,455 @@ -#include "../ESPEasyCore/ESPEasyWiFiEvent.h" - -#if FEATURE_ETHERNET -#include -#endif - -#include "../DataStructs/RTCStruct.h" - -#include "../DataTypes/ESPEasyTimeSource.h" - -#include "../ESPEasyCore/ESPEasyEth.h" -#include "../ESPEasyCore/ESPEasy_Log.h" -#include "../ESPEasyCore/ESPEasyNetwork.h" -#include "../ESPEasyCore/ESPEasyWifi.h" -#include "../ESPEasyCore/ESPEasyWifi_ProcessEvent.h" - -#include "../Globals/ESPEasyWiFiEvent.h" -#include "../Globals/NetworkState.h" -#include "../Globals/RTC.h" -#include "../Globals/WiFi_AP_Candidates.h" - -#include "../Helpers/ESPEasy_time_calc.h" - - -#if FEATURE_ETHERNET -#include "../Globals/ESPEasyEthEvent.h" -#endif - - -#ifdef ESP32 -void setUseStaticIP(bool enabled) { -} - -#endif // ifdef ESP32 -#ifdef ESP8266 -void WiFi_Access_Static_IP::set_use_static_ip(bool enabled) { - _useStaticIp = enabled; -} -void setUseStaticIP(bool enabled) { - WiFi_Access_Static_IP tmp_wifi; - - tmp_wifi.set_use_static_ip(enabled); -} - -#endif // ifdef ESP8266 - - - - -// ******************************************************************************** -// Functions called on events. -// Make sure not to call anything in these functions that result in delay() or yield() -// ******************************************************************************** -#ifdef ESP32 -#include - -static bool ignoreDisconnectEvent = false; - -#if ESP_IDF_VERSION_MAJOR > 3 -void WiFiEvent(WiFiEvent_t event, arduino_event_info_t info) { - switch (event) { - case ARDUINO_EVENT_WIFI_READY: - // ESP32 WiFi ready - break; - case ARDUINO_EVENT_WIFI_STA_START: - # ifndef BUILD_NO_DEBUG - //addLog(LOG_LEVEL_INFO, F("WiFi : Event STA Started")); - #endif - break; - case ARDUINO_EVENT_WIFI_STA_STOP: - # ifndef BUILD_NO_DEBUG - //addLog(LOG_LEVEL_INFO, F("WiFi : Event STA Stopped")); - #endif - break; - case ARDUINO_EVENT_WIFI_AP_START: - # ifndef BUILD_NO_DEBUG - //addLog(LOG_LEVEL_INFO, F("WiFi : Event AP Started")); - #endif - break; - case ARDUINO_EVENT_WIFI_AP_STOP: - # ifndef BUILD_NO_DEBUG - //addLog(LOG_LEVEL_INFO, F("WiFi : Event AP Stopped")); - #endif - break; - case ARDUINO_EVENT_WIFI_STA_LOST_IP: - // ESP32 station lost IP and the IP is reset to 0 - #if FEATURE_ETHERNET - if (active_network_medium == NetworkMedium_t::Ethernet) { - // DNS records are shared among WiFi and Ethernet (very bad design!) - // So we must restore the DNS records for Ethernet in case we started with WiFi and then plugged in Ethernet. - // As soon as WiFi is turned off, the DNS entry for Ethernet is cleared. - EthEventData.markLostIP(); - } - #endif // if FEATURE_ETHERNET - WiFiEventData.markLostIP(); - # ifndef BUILD_NO_DEBUG - //addLog(LOG_LEVEL_INFO, - /* - active_network_medium == NetworkMedium_t::Ethernet ? - F("ETH : Event Lost IP") : - */ -// F("WiFi : Event Lost IP")); - #endif - break; - - case ARDUINO_EVENT_WIFI_AP_PROBEREQRECVED: - // Receive probe request packet in soft-AP interface - // TODO TD-er: Must implement like onProbeRequestAPmode for ESP8266 - # ifndef BUILD_NO_DEBUG - //addLog(LOG_LEVEL_INFO, F("WiFi : Event AP got probed")); - #endif - break; - - case ARDUINO_EVENT_WIFI_STA_AUTHMODE_CHANGE: - #if ESP_IDF_VERSION_MAJOR > 3 - WiFiEventData.setAuthMode(info.wifi_sta_authmode_change.new_mode); - #else - WiFiEventData.setAuthMode(info.auth_change.new_mode); - #endif - break; - - case ARDUINO_EVENT_WIFI_STA_CONNECTED: - { - char ssid_copy[33]; // Ensure space for maximum len SSID (32) plus trailing 0 - #if ESP_IDF_VERSION_MAJOR > 3 - memcpy(ssid_copy, info.wifi_sta_connected.ssid, info.wifi_sta_connected.ssid_len); - ssid_copy[32] = 0; // Potentially add 0-termination if none present earlier - WiFiEventData.markConnected((const char*) ssid_copy, info.wifi_sta_connected.bssid, info.wifi_sta_connected.channel); - WiFiEventData.setAuthMode(info.wifi_sta_connected.authmode); - //addLog(LOG_LEVEL_INFO, F("WiFi : Event WIFI_STA_CONNECTED")); - #else - memcpy(ssid_copy, info.connected.ssid, info.connected.ssid_len); - ssid_copy[32] = 0; // Potentially add 0-termination if none present earlier - WiFiEventData.markConnected((const char*) ssid_copy, info.connected.bssid, info.connected.channel); - #endif - break; - } - case ARDUINO_EVENT_WIFI_STA_DISCONNECTED: - if (!ignoreDisconnectEvent) { - ignoreDisconnectEvent = true; - #if ESP_IDF_VERSION_MAJOR > 3 - WiFiEventData.markDisconnect(static_cast(info.wifi_sta_disconnected.reason)); - if (info.wifi_sta_disconnected.reason == WIFI_REASON_AUTH_EXPIRE) { - // See: https://github.com/espressif/arduino-esp32/issues/8877#issuecomment-1807677897 - #if ESP_IDF_VERSION_MAJOR >= 5 - // FIXME TD-er: Should no longer be needed. - WiFi.STA._setStatus(WL_CONNECTION_LOST); - #else - WiFiSTAClass::_setStatus(WL_CONNECTION_LOST); - #endif - } - #else - WiFiEventData.markDisconnect(static_cast(info.disconnected.reason)); - if (info.disconnected.reason == WIFI_REASON_AUTH_EXPIRE) { - // See: https://github.com/espressif/arduino-esp32/issues/8877#issuecomment-1807677897 - WiFiSTAClass::_setStatus(WL_CONNECTION_LOST); - } - #endif - WiFi.persistent(false); - WiFi.disconnect(true); - } - break; - case ARDUINO_EVENT_WIFI_STA_GOT_IP: - ignoreDisconnectEvent = false; - WiFiEventData.markGotIP(); - break; - #if FEATURE_USE_IPV6 - case ARDUINO_EVENT_WIFI_STA_GOT_IP6: - { - ip_event_got_ip6_t * event = static_cast(&info.got_ip6); - const IPAddress ip(IPv6, (const uint8_t*)event->ip6_info.ip.addr, event->ip6_info.ip.zone); - WiFiEventData.markGotIPv6(ip); - break; - } - case ARDUINO_EVENT_WIFI_AP_GOT_IP6: - addLog(LOG_LEVEL_INFO, F("WIFI : AP got IP6")); - break; - #endif - case ARDUINO_EVENT_WIFI_AP_STACONNECTED: - #if ESP_IDF_VERSION_MAJOR > 3 - WiFiEventData.markConnectedAPmode(info.wifi_ap_staconnected.mac); - #else - WiFiEventData.markConnectedAPmode(info.sta_connected.mac); - #endif - break; - case ARDUINO_EVENT_WIFI_AP_STADISCONNECTED: - #if ESP_IDF_VERSION_MAJOR > 3 - WiFiEventData.markDisconnectedAPmode(info.wifi_ap_stadisconnected.mac); - #else - WiFiEventData.markDisconnectedAPmode(info.sta_disconnected.mac); - #endif - break; - case ARDUINO_EVENT_WIFI_SCAN_DONE: - WiFiEventData.processedScanDone = false; - break; -#if FEATURE_ETHERNET - case ARDUINO_EVENT_ETH_START: - case ARDUINO_EVENT_ETH_CONNECTED: - case ARDUINO_EVENT_ETH_GOT_IP: - case ARDUINO_EVENT_ETH_DISCONNECTED: - case ARDUINO_EVENT_ETH_STOP: - #if ESP_IDF_VERSION_MAJOR > 3 - case ARDUINO_EVENT_ETH_GOT_IP6: - #else - case ARDUINO_EVENT_GOT_IP6: - #endif - // Handled in EthEvent - break; -#endif //FEATURE_ETHERNET - default: - { - - // addLogMove(LOG_LEVEL_ERROR, concat(F("UNKNOWN WIFI/ETH EVENT: "), event)); - } - break; - } -} -#else -void WiFiEvent(system_event_id_t event, system_event_info_t info) { - switch (event) { - case SYSTEM_EVENT_WIFI_READY: - // ESP32 WiFi ready - break; - case SYSTEM_EVENT_STA_START: - # ifndef BUILD_NO_DEBUG - //addLog(LOG_LEVEL_INFO, F("WiFi : Event STA Started")); - #endif - break; - case SYSTEM_EVENT_STA_STOP: - # ifndef BUILD_NO_DEBUG - //addLog(LOG_LEVEL_INFO, F("WiFi : Event STA Stopped")); - #endif - break; - case SYSTEM_EVENT_AP_START: - # ifndef BUILD_NO_DEBUG - //addLog(LOG_LEVEL_INFO, F("WiFi : Event AP Started")); - #endif - break; - case SYSTEM_EVENT_AP_STOP: - # ifndef BUILD_NO_DEBUG - //addLog(LOG_LEVEL_INFO, F("WiFi : Event AP Stopped")); - #endif - break; - case SYSTEM_EVENT_STA_LOST_IP: - // ESP32 station lost IP and the IP is reset to 0 - #if FEATURE_ETHERNET - if (active_network_medium == NetworkMedium_t::Ethernet) { - EthEventData.markLostIP(); - } - else - #endif // if FEATURE_ETHERNET - WiFiEventData.markLostIP(); - # ifndef BUILD_NO_DEBUG - /* - addLog(LOG_LEVEL_INFO, - active_network_medium == NetworkMedium_t::Ethernet ? - F("ETH : Event Lost IP") : F("WiFi : Event Lost IP")); - */ - #endif - break; - - case SYSTEM_EVENT_AP_PROBEREQRECVED: - // Receive probe request packet in soft-AP interface - // TODO TD-er: Must implement like onProbeRequestAPmode for ESP8266 - # ifndef BUILD_NO_DEBUG - //addLog(LOG_LEVEL_INFO, F("WiFi : Event AP got probed")); - #endif - break; - - case SYSTEM_EVENT_STA_AUTHMODE_CHANGE: - #if ESP_IDF_VERSION_MAJOR > 3 - WiFiEventData.setAuthMode(info.wifi_sta_authmode_change.new_mode); - #else - WiFiEventData.setAuthMode(info.auth_change.new_mode); - #endif - break; - - case SYSTEM_EVENT_STA_CONNECTED: - { - char ssid_copy[33] = { 0 }; // Ensure space for maximum len SSID (32) plus trailing 0 - #if ESP_IDF_VERSION_MAJOR > 3 - memcpy(ssid_copy, info.wifi_sta_connected.ssid, info.wifi_sta_connected.ssid_len); - ssid_copy[32] = 0; // Potentially add 0-termination if none present earlier - WiFiEventData.markConnected((const char*) ssid_copy, info.wifi_sta_connected.bssid, info.wifi_sta_connected.channel); - #else - memcpy(ssid_copy, info.connected.ssid, info.connected.ssid_len); - ssid_copy[32] = 0; // Potentially add 0-termination if none present earlier - WiFiEventData.markConnected((const char*) ssid_copy, info.connected.bssid, info.connected.channel); - #endif - break; - } - case SYSTEM_EVENT_STA_DISCONNECTED: - if (!ignoreDisconnectEvent) { - ignoreDisconnectEvent = true; - #if ESP_IDF_VERSION_MAJOR > 3 - WiFiEventData.markDisconnect(static_cast(info.wifi_sta_disconnected.reason)); - #else - WiFiEventData.markDisconnect(static_cast(info.disconnected.reason)); - #endif - WiFi.persistent(false); - WiFi.disconnect(true); - } - break; - case SYSTEM_EVENT_STA_GOT_IP: - ignoreDisconnectEvent = false; - WiFiEventData.markGotIP(); - break; - case SYSTEM_EVENT_AP_STACONNECTED: - #if ESP_IDF_VERSION_MAJOR > 3 - WiFiEventData.markConnectedAPmode(info.wifi_ap_staconnected.mac); - #else - WiFiEventData.markConnectedAPmode(info.sta_connected.mac); - #endif - break; - case SYSTEM_EVENT_AP_STADISCONNECTED: - #if ESP_IDF_VERSION_MAJOR > 3 - WiFiEventData.markDisconnectedAPmode(info.wifi_ap_stadisconnected.mac); - #else - WiFiEventData.markDisconnectedAPmode(info.sta_disconnected.mac); - #endif - break; - case SYSTEM_EVENT_SCAN_DONE: - WiFiEventData.processedScanDone = false; - break; -#if FEATURE_ETHERNET - case SYSTEM_EVENT_ETH_START: - if (ethPrepare()) { - //addLog(LOG_LEVEL_INFO, F("ETH event: Started")); - } else { - //addLog(LOG_LEVEL_ERROR, F("ETH event: Could not prepare ETH!")); - } - break; - case SYSTEM_EVENT_ETH_CONNECTED: - //addLog(LOG_LEVEL_INFO, F("ETH event: Connected")); - EthEventData.markConnected(); - break; - case SYSTEM_EVENT_ETH_GOT_IP: - EthEventData.markGotIP(); - //addLog(LOG_LEVEL_INFO, F("ETH event: Got IP")); - break; - case SYSTEM_EVENT_ETH_DISCONNECTED: - //addLog(LOG_LEVEL_ERROR, F("ETH event: Disconnected")); - EthEventData.markDisconnect(); - break; - case SYSTEM_EVENT_ETH_STOP: - //addLog(LOG_LEVEL_INFO, F("ETH event: Stopped")); - break; - case SYSTEM_EVENT_GOT_IP6: - //addLog(LOG_LEVEL_INFO, F("ETH event: Got IP6")); - break; -#endif //FEATURE_ETHERNET - default: - { - //addLogMove(LOG_LEVEL_ERROR, concat(F("UNKNOWN WIFI/ETH EVENT: "), event)); - } - break; - } -} - -#endif - -#endif // ifdef ESP32 - -#ifdef ESP8266 - -void onConnected(const WiFiEventStationModeConnected& event) { - WiFiEventData.markConnected(event.ssid, event.bssid, event.channel); -} - -void onDisconnect(const WiFiEventStationModeDisconnected& event) { - WiFiEventData.markDisconnect(event.reason); - if (WiFi.status() == WL_CONNECTED) { - // See https://github.com/esp8266/Arduino/issues/5912 - WiFi.persistent(false); - WiFi.disconnect(false); - delay(0); - } -} - -void onGotIP(const WiFiEventStationModeGotIP& event) { - WiFiEventData.markGotIP(); -} - -void onDHCPTimeout() { - WiFiEventData.processedDHCPTimeout = false; -} - -void onConnectedAPmode(const WiFiEventSoftAPModeStationConnected& event) { - WiFiEventData.markConnectedAPmode(event.mac); -} - -void onDisconnectedAPmode(const WiFiEventSoftAPModeStationDisconnected& event) { - WiFiEventData.markDisconnectedAPmode(event.mac); -} - -void onStationModeAuthModeChanged(const WiFiEventStationModeAuthModeChanged& event) { - WiFiEventData.setAuthMode(event.newMode); -} - -#if FEATURE_ESP8266_DIRECT_WIFI_SCAN -void onWiFiScanDone(void *arg, STATUS status) { - if (status == OK) { - auto *head = reinterpret_cast(arg); - int scanCount = 0; - for (bss_info *it = head; it != nullptr; it = STAILQ_NEXT(it, next)) { - WiFi_AP_Candidates.process_WiFiscan(*it); - ++scanCount; - } - WiFi_AP_Candidates.after_process_WiFiscan(); - WiFiEventData.lastGetScanMoment.setNow(); -// WiFiEventData.processedScanDone = true; -# ifndef BUILD_NO_DEBUG -/* - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - addLogMove(LOG_LEVEL_INFO, concat(F("WiFi : Scan finished (ESP8266), found: "), scanCount)); - } -*/ -#endif - WiFi_AP_Candidates.load_knownCredentials(); - if (WiFi_AP_Candidates.addedKnownCandidate() || !NetworkConnected()) { - WiFiEventData.wifiConnectAttemptNeeded = true; - # ifndef BUILD_NO_DEBUG - if (WiFi_AP_Candidates.addedKnownCandidate()) { - //addLog(LOG_LEVEL_INFO, F("WiFi : Added known candidate, try to connect")); - } - #endif - NetworkConnectRelaxed(); - } - - } - - WiFiMode_t mode = WiFi.getMode(); - setWifiMode(WIFI_OFF); - delay(1); - setWifiMode(mode); - delay(1); -} -#endif - -#endif // ifdef ESP8266 +#include "../ESPEasyCore/ESPEasyWiFiEvent.h" + +#if FEATURE_ETHERNET +#include +#endif + +#include "../DataStructs/RTCStruct.h" + +#include "../DataTypes/ESPEasyTimeSource.h" + +#include "../ESPEasyCore/ESPEasyEth.h" +#include "../ESPEasyCore/ESPEasy_Log.h" +#include "../ESPEasyCore/ESPEasyNetwork.h" +#include "../ESPEasyCore/ESPEasyWifi.h" +#include "../ESPEasyCore/ESPEasyWifi_ProcessEvent.h" + +#include "../Globals/ESPEasyWiFiEvent.h" +#include "../Globals/NetworkState.h" +#include "../Globals/RTC.h" +#include "../Globals/WiFi_AP_Candidates.h" + +#include "../Helpers/ESPEasy_time_calc.h" + + +#if FEATURE_ETHERNET +#include "../Globals/ESPEasyEthEvent.h" +#endif + + +#ifdef ESP32 +void setUseStaticIP(bool enabled) { +} + +#endif // ifdef ESP32 +#ifdef ESP8266 +void WiFi_Access_Static_IP::set_use_static_ip(bool enabled) { + _useStaticIp = enabled; +} +void setUseStaticIP(bool enabled) { + WiFi_Access_Static_IP tmp_wifi; + + tmp_wifi.set_use_static_ip(enabled); +} + +#endif // ifdef ESP8266 + + + + +// ******************************************************************************** +// Functions called on events. +// Make sure not to call anything in these functions that result in delay() or yield() +// ******************************************************************************** +#ifdef ESP32 +#include + +static bool ignoreDisconnectEvent = false; + +#if ESP_IDF_VERSION_MAJOR > 3 +void WiFiEvent(WiFiEvent_t event, arduino_event_info_t info) { + switch (event) { + case ARDUINO_EVENT_WIFI_READY: + // ESP32 WiFi ready + break; + case ARDUINO_EVENT_WIFI_STA_START: + # ifndef BUILD_NO_DEBUG + //addLog(LOG_LEVEL_INFO, F("WiFi : Event STA Started")); + #endif + break; + case ARDUINO_EVENT_WIFI_STA_STOP: + # ifndef BUILD_NO_DEBUG + //addLog(LOG_LEVEL_INFO, F("WiFi : Event STA Stopped")); + #endif + break; + case ARDUINO_EVENT_WIFI_AP_START: + # ifndef BUILD_NO_DEBUG + //addLog(LOG_LEVEL_INFO, F("WiFi : Event AP Started")); + #endif + break; + case ARDUINO_EVENT_WIFI_AP_STOP: + # ifndef BUILD_NO_DEBUG + //addLog(LOG_LEVEL_INFO, F("WiFi : Event AP Stopped")); + #endif + break; + case ARDUINO_EVENT_WIFI_STA_LOST_IP: + // ESP32 station lost IP and the IP is reset to 0 + #if FEATURE_ETHERNET + if (active_network_medium == NetworkMedium_t::Ethernet) { + // DNS records are shared among WiFi and Ethernet (very bad design!) + // So we must restore the DNS records for Ethernet in case we started with WiFi and then plugged in Ethernet. + // As soon as WiFi is turned off, the DNS entry for Ethernet is cleared. + EthEventData.markLostIP(); + } + #endif // if FEATURE_ETHERNET + WiFiEventData.markLostIP(); + # ifndef BUILD_NO_DEBUG + //addLog(LOG_LEVEL_INFO, + /* + active_network_medium == NetworkMedium_t::Ethernet ? + F("ETH : Event Lost IP") : + */ +// F("WiFi : Event Lost IP")); + #endif + break; + + case ARDUINO_EVENT_WIFI_AP_PROBEREQRECVED: + // Receive probe request packet in soft-AP interface + #ifdef USES_ESPEASY_NOW + WiFiEventData.processedProbeRequestAPmode = false; + APModeProbeRequestReceived_list.push_back(info.wifi_ap_probereqrecved); + #endif + + # ifndef BUILD_NO_DEBUG + //addLog(LOG_LEVEL_INFO, F("WiFi : Event AP got probed")); + #endif + break; + + case ARDUINO_EVENT_WIFI_STA_AUTHMODE_CHANGE: + #if ESP_IDF_VERSION_MAJOR > 3 + WiFiEventData.setAuthMode(info.wifi_sta_authmode_change.new_mode); + #else + WiFiEventData.setAuthMode(info.auth_change.new_mode); + #endif + break; + + case ARDUINO_EVENT_WIFI_STA_CONNECTED: + { + char ssid_copy[33]; // Ensure space for maximum len SSID (32) plus trailing 0 + #if ESP_IDF_VERSION_MAJOR > 3 + memcpy(ssid_copy, info.wifi_sta_connected.ssid, info.wifi_sta_connected.ssid_len); + ssid_copy[32] = 0; // Potentially add 0-termination if none present earlier + WiFiEventData.markConnected((const char*) ssid_copy, info.wifi_sta_connected.bssid, info.wifi_sta_connected.channel); + WiFiEventData.setAuthMode(info.wifi_sta_connected.authmode); + //addLog(LOG_LEVEL_INFO, F("WiFi : Event WIFI_STA_CONNECTED")); + #else + memcpy(ssid_copy, info.connected.ssid, info.connected.ssid_len); + ssid_copy[32] = 0; // Potentially add 0-termination if none present earlier + WiFiEventData.markConnected((const char*) ssid_copy, info.connected.bssid, info.connected.channel); + #endif + break; + } + case ARDUINO_EVENT_WIFI_STA_DISCONNECTED: + if (!ignoreDisconnectEvent) { + ignoreDisconnectEvent = true; + #if ESP_IDF_VERSION_MAJOR > 3 + WiFiEventData.markDisconnect(static_cast(info.wifi_sta_disconnected.reason)); + if (info.wifi_sta_disconnected.reason == WIFI_REASON_AUTH_EXPIRE) { + // See: https://github.com/espressif/arduino-esp32/issues/8877#issuecomment-1807677897 + #if ESP_IDF_VERSION_MAJOR >= 5 + // FIXME TD-er: Should no longer be needed. + WiFi.STA._setStatus(WL_CONNECTION_LOST); + #else + WiFiSTAClass::_setStatus(WL_CONNECTION_LOST); + #endif + } + #else + WiFiEventData.markDisconnect(static_cast(info.disconnected.reason)); + if (info.disconnected.reason == WIFI_REASON_AUTH_EXPIRE) { + // See: https://github.com/espressif/arduino-esp32/issues/8877#issuecomment-1807677897 + WiFiSTAClass::_setStatus(WL_CONNECTION_LOST); + } + #endif + WiFi.persistent(false); + WiFi.disconnect(true); + } + break; + case ARDUINO_EVENT_WIFI_STA_GOT_IP: + ignoreDisconnectEvent = false; + WiFiEventData.markGotIP(); + break; + #if FEATURE_USE_IPV6 + case ARDUINO_EVENT_WIFI_STA_GOT_IP6: + { + ip_event_got_ip6_t * event = static_cast(&info.got_ip6); + const IPAddress ip(IPv6, (const uint8_t*)event->ip6_info.ip.addr, event->ip6_info.ip.zone); + WiFiEventData.markGotIPv6(ip); + break; + } + case ARDUINO_EVENT_WIFI_AP_GOT_IP6: + addLog(LOG_LEVEL_INFO, F("WIFI : AP got IP6")); + break; + #endif + case ARDUINO_EVENT_WIFI_AP_STACONNECTED: + #if ESP_IDF_VERSION_MAJOR > 3 + WiFiEventData.markConnectedAPmode(info.wifi_ap_staconnected.mac); + #else + WiFiEventData.markConnectedAPmode(info.sta_connected.mac); + #endif + break; + case ARDUINO_EVENT_WIFI_AP_STADISCONNECTED: + #if ESP_IDF_VERSION_MAJOR > 3 + WiFiEventData.markDisconnectedAPmode(info.wifi_ap_stadisconnected.mac); + #else + WiFiEventData.markDisconnectedAPmode(info.sta_disconnected.mac); + #endif + break; + case ARDUINO_EVENT_WIFI_AP_STAIPASSIGNED: + // Assigned an IP to the connected STA client while ESP is in AP mode. + // Not sure if it makes sense to record this information. + break; + case ARDUINO_EVENT_WIFI_SCAN_DONE: + WiFiEventData.processedScanDone = false; + break; +#if FEATURE_ETHERNET + case ARDUINO_EVENT_ETH_START: + case ARDUINO_EVENT_ETH_CONNECTED: + case ARDUINO_EVENT_ETH_GOT_IP: + case ARDUINO_EVENT_ETH_DISCONNECTED: + case ARDUINO_EVENT_ETH_STOP: + #if ESP_IDF_VERSION_MAJOR > 3 + case ARDUINO_EVENT_ETH_GOT_IP6: + #else + case ARDUINO_EVENT_GOT_IP6: + #endif + // Handled in EthEvent + break; +#endif //FEATURE_ETHERNET + default: + { + + // addLogMove(LOG_LEVEL_ERROR, concat(F("UNKNOWN WIFI/ETH EVENT: "), event)); + } + break; + } +} +#else +void WiFiEvent(system_event_id_t event, system_event_info_t info) { + switch (event) { + case SYSTEM_EVENT_WIFI_READY: + // ESP32 WiFi ready + break; + case SYSTEM_EVENT_STA_START: + # ifndef BUILD_NO_DEBUG + //addLog(LOG_LEVEL_INFO, F("WiFi : Event STA Started")); + #endif + break; + case SYSTEM_EVENT_STA_STOP: + # ifndef BUILD_NO_DEBUG + //addLog(LOG_LEVEL_INFO, F("WiFi : Event STA Stopped")); + #endif + break; + case SYSTEM_EVENT_AP_START: + # ifndef BUILD_NO_DEBUG + //addLog(LOG_LEVEL_INFO, F("WiFi : Event AP Started")); + #endif + break; + case SYSTEM_EVENT_AP_STOP: + # ifndef BUILD_NO_DEBUG + //addLog(LOG_LEVEL_INFO, F("WiFi : Event AP Stopped")); + #endif + break; + case SYSTEM_EVENT_STA_LOST_IP: + // ESP32 station lost IP and the IP is reset to 0 + #if FEATURE_ETHERNET + if (active_network_medium == NetworkMedium_t::Ethernet) { + EthEventData.markLostIP(); + } + else + #endif // if FEATURE_ETHERNET + WiFiEventData.markLostIP(); + # ifndef BUILD_NO_DEBUG + /* + addLog(LOG_LEVEL_INFO, + active_network_medium == NetworkMedium_t::Ethernet ? + F("ETH : Event Lost IP") : F("WiFi : Event Lost IP")); + */ + #endif + break; + + case SYSTEM_EVENT_AP_PROBEREQRECVED: + // Receive probe request packet in soft-AP interface + // TODO TD-er: Must implement like onProbeRequestAPmode for ESP8266 + # ifndef BUILD_NO_DEBUG + //addLog(LOG_LEVEL_INFO, F("WiFi : Event AP got probed")); + #endif + break; + + case SYSTEM_EVENT_STA_AUTHMODE_CHANGE: + #if ESP_IDF_VERSION_MAJOR > 3 + WiFiEventData.setAuthMode(info.wifi_sta_authmode_change.new_mode); + #else + WiFiEventData.setAuthMode(info.auth_change.new_mode); + #endif + break; + + case SYSTEM_EVENT_STA_CONNECTED: + { + char ssid_copy[33] = { 0 }; // Ensure space for maximum len SSID (32) plus trailing 0 + #if ESP_IDF_VERSION_MAJOR > 3 + memcpy(ssid_copy, info.wifi_sta_connected.ssid, info.wifi_sta_connected.ssid_len); + ssid_copy[32] = 0; // Potentially add 0-termination if none present earlier + WiFiEventData.markConnected((const char*) ssid_copy, info.wifi_sta_connected.bssid, info.wifi_sta_connected.channel); + #else + memcpy(ssid_copy, info.connected.ssid, info.connected.ssid_len); + ssid_copy[32] = 0; // Potentially add 0-termination if none present earlier + WiFiEventData.markConnected((const char*) ssid_copy, info.connected.bssid, info.connected.channel); + #endif + break; + } + case SYSTEM_EVENT_STA_DISCONNECTED: + if (!ignoreDisconnectEvent) { + ignoreDisconnectEvent = true; + #if ESP_IDF_VERSION_MAJOR > 3 + WiFiEventData.markDisconnect(static_cast(info.wifi_sta_disconnected.reason)); + #else + WiFiEventData.markDisconnect(static_cast(info.disconnected.reason)); + #endif + WiFi.persistent(false); + WiFi.disconnect(true); + } + break; + case SYSTEM_EVENT_STA_GOT_IP: + ignoreDisconnectEvent = false; + WiFiEventData.markGotIP(); + break; + case SYSTEM_EVENT_AP_STACONNECTED: + #if ESP_IDF_VERSION_MAJOR > 3 + WiFiEventData.markConnectedAPmode(info.wifi_ap_staconnected.mac); + #else + WiFiEventData.markConnectedAPmode(info.sta_connected.mac); + #endif + break; + case SYSTEM_EVENT_AP_STADISCONNECTED: + #if ESP_IDF_VERSION_MAJOR > 3 + WiFiEventData.markDisconnectedAPmode(info.wifi_ap_stadisconnected.mac); + #else + WiFiEventData.markDisconnectedAPmode(info.sta_disconnected.mac); + #endif + break; + case SYSTEM_EVENT_SCAN_DONE: + WiFiEventData.processedScanDone = false; + break; +#if FEATURE_ETHERNET + case SYSTEM_EVENT_ETH_START: + if (ethPrepare()) { + //addLog(LOG_LEVEL_INFO, F("ETH event: Started")); + } else { + //addLog(LOG_LEVEL_ERROR, F("ETH event: Could not prepare ETH!")); + } + break; + case SYSTEM_EVENT_ETH_CONNECTED: + //addLog(LOG_LEVEL_INFO, F("ETH event: Connected")); + EthEventData.markConnected(); + break; + case SYSTEM_EVENT_ETH_GOT_IP: + EthEventData.markGotIP(); + //addLog(LOG_LEVEL_INFO, F("ETH event: Got IP")); + break; + case SYSTEM_EVENT_ETH_DISCONNECTED: + //addLog(LOG_LEVEL_ERROR, F("ETH event: Disconnected")); + EthEventData.markDisconnect(); + break; + case SYSTEM_EVENT_ETH_STOP: + //addLog(LOG_LEVEL_INFO, F("ETH event: Stopped")); + break; + case SYSTEM_EVENT_GOT_IP6: + //addLog(LOG_LEVEL_INFO, F("ETH event: Got IP6")); + break; +#endif //FEATURE_ETHERNET + default: + { + //addLogMove(LOG_LEVEL_ERROR, concat(F("UNKNOWN WIFI/ETH EVENT: "), event)); + } + break; + } +} + +#endif + +#endif // ifdef ESP32 + +#ifdef ESP8266 + +void onConnected(const WiFiEventStationModeConnected& event) { + WiFiEventData.markConnected(event.ssid, event.bssid, event.channel); +} + +void onDisconnect(const WiFiEventStationModeDisconnected& event) { + WiFiEventData.markDisconnect(event.reason); + if (WiFi.status() == WL_CONNECTED) { + // See https://github.com/esp8266/Arduino/issues/5912 + WiFi.persistent(false); + WiFi.disconnect(false); + delay(0); + } +} + +void onGotIP(const WiFiEventStationModeGotIP& event) { + WiFiEventData.markGotIP(); +} + +void onDHCPTimeout() { + WiFiEventData.processedDHCPTimeout = false; +} + +void onConnectedAPmode(const WiFiEventSoftAPModeStationConnected& event) { + WiFiEventData.markConnectedAPmode(event.mac); +} + +void onDisconnectedAPmode(const WiFiEventSoftAPModeStationDisconnected& event) { + WiFiEventData.markDisconnectedAPmode(event.mac); +} + +#ifdef USES_ESPEASY_NOW +void onProbeRequestAPmode(const WiFiEventSoftAPModeProbeRequestReceived& event) { + APModeProbeRequestReceived_list.push_back(event); + WiFiEventData.processedProbeRequestAPmode = false; +} +#endif + +void onStationModeAuthModeChanged(const WiFiEventStationModeAuthModeChanged& event) { + WiFiEventData.setAuthMode(event.newMode); +} + +#if FEATURE_ESP8266_DIRECT_WIFI_SCAN +void onWiFiScanDone(void *arg, STATUS status) { + if (status == OK) { + auto *head = reinterpret_cast(arg); + int scanCount = 0; + for (bss_info *it = head; it != nullptr; it = STAILQ_NEXT(it, next)) { + WiFi_AP_Candidates.process_WiFiscan(*it); + ++scanCount; + } + WiFi_AP_Candidates.after_process_WiFiscan(); + WiFiEventData.lastGetScanMoment.setNow(); +// WiFiEventData.processedScanDone = true; +# ifndef BUILD_NO_DEBUG +/* + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLogMove(LOG_LEVEL_INFO, concat(F("WiFi : Scan finished (ESP8266), found: "), scanCount)); + } +*/ +#endif + WiFi_AP_Candidates.load_knownCredentials(); + if (WiFi_AP_Candidates.addedKnownCandidate() || !NetworkConnected()) { + WiFiEventData.wifiConnectAttemptNeeded = true; + # ifndef BUILD_NO_DEBUG + if (WiFi_AP_Candidates.addedKnownCandidate()) { + //addLog(LOG_LEVEL_INFO, F("WiFi : Added known candidate, try to connect")); + } + #endif + NetworkConnectRelaxed(); + } + + } + + WiFiMode_t mode = WiFi.getMode(); + setWifiMode(WIFI_OFF); + delay(1); + setWifiMode(mode); + delay(1); +} +#endif + +#endif // ifdef ESP8266 diff --git a/src/src/ESPEasyCore/ESPEasyWiFiEvent.h b/src/src/ESPEasyCore/ESPEasyWiFiEvent.h index 996692c62e..d0234caa6d 100644 --- a/src/src/ESPEasyCore/ESPEasyWiFiEvent.h +++ b/src/src/ESPEasyCore/ESPEasyWiFiEvent.h @@ -1,74 +1,78 @@ -#ifndef ESPEASY_WIFI_EVENT_H -#define ESPEASY_WIFI_EVENT_H - -#include "../../ESPEasy_common.h" - -#include - -// ******************************************************************************** - -// Work-around for setting _useStaticIP -// See reported issue: https://github.com/esp8266/Arduino/issues/4114 -// ******************************************************************************** -#ifdef ESP32 -#include -#include -#include -class WiFi_Access_Static_IP : public WiFiSTAClass { -public: - - void set_use_static_ip(bool enabled); -}; -#endif - -#ifdef ESP8266 -#include -#include -class WiFi_Access_Static_IP : public ESP8266WiFiSTAClass { -public: - - void set_use_static_ip(bool enabled); -}; -#endif - - -void setUseStaticIP(bool enabled); - - -// ******************************************************************************** -// Functions called on events. -// Make sure not to call anything in these functions that result in delay() or yield() -// ******************************************************************************** -#ifdef ESP32 - #if ESP_IDF_VERSION_MAJOR > 3 - #include - void WiFiEvent(WiFiEvent_t event, arduino_event_info_t info); - #else - void WiFiEvent(system_event_id_t event, system_event_info_t info); - #endif -#endif - -#ifdef ESP8266 - -void onConnected(const WiFiEventStationModeConnected& event); - -void onDisconnect(const WiFiEventStationModeDisconnected& event); - -void onGotIP(const WiFiEventStationModeGotIP& event); - -void onDHCPTimeout(); - -void onConnectedAPmode(const WiFiEventSoftAPModeStationConnected& event); - -void onDisconnectedAPmode(const WiFiEventSoftAPModeStationDisconnected& event); - -void onStationModeAuthModeChanged(const WiFiEventStationModeAuthModeChanged& event); - -#if FEATURE_ESP8266_DIRECT_WIFI_SCAN -void onWiFiScanDone(void *arg, STATUS status); -#endif - -#endif - - +#ifndef ESPEASY_WIFI_EVENT_H +#define ESPEASY_WIFI_EVENT_H + +#include "../../ESPEasy_common.h" + +#include + +// ******************************************************************************** + +// Work-around for setting _useStaticIP +// See reported issue: https://github.com/esp8266/Arduino/issues/4114 +// ******************************************************************************** +#ifdef ESP32 +#include +#include +#include +class WiFi_Access_Static_IP : public WiFiSTAClass { +public: + + void set_use_static_ip(bool enabled); +}; +#endif + +#ifdef ESP8266 +#include +#include +class WiFi_Access_Static_IP : public ESP8266WiFiSTAClass { +public: + + void set_use_static_ip(bool enabled); +}; +#endif + + +void setUseStaticIP(bool enabled); + + +// ******************************************************************************** +// Functions called on events. +// Make sure not to call anything in these functions that result in delay() or yield() +// ******************************************************************************** +#ifdef ESP32 + #if ESP_IDF_VERSION_MAJOR > 3 + #include + void WiFiEvent(WiFiEvent_t event, arduino_event_info_t info); + #else + void WiFiEvent(system_event_id_t event, system_event_info_t info); + #endif +#endif + +#ifdef ESP8266 + +void onConnected(const WiFiEventStationModeConnected& event); + +void onDisconnect(const WiFiEventStationModeDisconnected& event); + +void onGotIP(const WiFiEventStationModeGotIP& event); + +void onDHCPTimeout(); + +void onConnectedAPmode(const WiFiEventSoftAPModeStationConnected& event); + +void onDisconnectedAPmode(const WiFiEventSoftAPModeStationDisconnected& event); + +#ifdef USES_ESPEASY_NOW +void onProbeRequestAPmode(const WiFiEventSoftAPModeProbeRequestReceived& event); +#endif + +void onStationModeAuthModeChanged(const WiFiEventStationModeAuthModeChanged& event); + +#if FEATURE_ESP8266_DIRECT_WIFI_SCAN +void onWiFiScanDone(void *arg, STATUS status); +#endif + +#endif + + #endif // ESPEASY_WIFI_EVENT_H \ No newline at end of file diff --git a/src/src/ESPEasyCore/ESPEasyWifi.cpp b/src/src/ESPEasyCore/ESPEasyWifi.cpp index 398fbdda2d..3e10351a8c 100644 --- a/src/src/ESPEasyCore/ESPEasyWifi.cpp +++ b/src/src/ESPEasyCore/ESPEasyWifi.cpp @@ -1,1728 +1,1826 @@ -#include "../ESPEasyCore/ESPEasyWifi.h" - -#include "../../ESPEasy-Globals.h" -#include "../DataStructs/TimingStats.h" -#include "../ESPEasyCore/ESPEasyNetwork.h" -#include "../ESPEasyCore/ESPEasyWiFiEvent.h" -#include "../ESPEasyCore/ESPEasyWifi_ProcessEvent.h" -#include "../ESPEasyCore/ESPEasy_Log.h" -#include "../ESPEasyCore/Serial.h" -#include "../Globals/ESPEasyWiFiEvent.h" -#include "../Globals/EventQueue.h" -#include "../Globals/NetworkState.h" -#include "../Globals/Nodes.h" -#include "../Globals/RTC.h" -#include "../Globals/SecuritySettings.h" -#include "../Globals/Services.h" -#include "../Globals/Settings.h" -#include "../Globals/WiFi_AP_Candidates.h" -#include "../Helpers/ESPEasy_time_calc.h" -#include "../Helpers/Hardware_defines.h" -#include "../Helpers/Misc.h" -#include "../Helpers/Networking.h" -#include "../Helpers/StringConverter.h" -#include "../Helpers/StringGenerator_WiFi.h" -#include "../Helpers/StringProvider.h" - -#ifdef ESP32 -#include -#include // Needed to call ESP-IDF functions like esp_wifi_.... - -#include -#endif - -// FIXME TD-er: Cleanup of WiFi code -#ifdef ESPEASY_WIFI_CLEANUP_WORK_IN_PROGRESS -bool ESPEasyWiFi_t::begin() { - return true; -} - -void ESPEasyWiFi_t::end() { - - -} - - -void ESPEasyWiFi_t::loop() { - switch (_state) { - case WiFiState_e::OFF: - break; - case WiFiState_e::AP_only: - break; - case WiFiState_e::ErrorRecovery: - // Wait for timeout to expire - // Start again from scratch - break; - case WiFiState_e::STA_Scanning: - case WiFiState_e::STA_AP_Scanning: - // Check if scanning is finished - // When scanning per channel, call for scanning next channel - break; - case WiFiState_e::STA_Connecting: - case WiFiState_e::STA_Reconnecting: - // Check if (re)connecting has finished - break; - case WiFiState_e::STA_Connected: - // Check if still connected - // Reconnect if not. - // Else mark last timestamp seen as connected - break; - } - - - { - // Check if we need to start AP - // Flag captive portal in webserver and/or whether we might be in setup mode - } - -#ifdef USE_IMPROV - { - // Check for Improv mode. - } -#endif - - -} - - -IPAddress ESPEasyWiFi_t::getIP() const { - - IPAddress res; - - - return res; -} - -void ESPEasyWiFi_t::disconnect() { - -} - - -void ESPEasyWiFi_t::checkConnectProgress() { - -} - -void ESPEasyWiFi_t::startScanning() { - _state = WiFiState_e::STA_Scanning; - WifiScan(true); - _last_state_change.setNow(); -} - - -bool ESPEasyWiFi_t::connectSTA() { - if (!WiFi_AP_Candidates.hasCandidateCredentials()) { - if (!WiFiEventData.warnedNoValidWiFiSettings) { - addLog(LOG_LEVEL_ERROR, F("WIFI : No valid wifi settings")); - WiFiEventData.warnedNoValidWiFiSettings = true; - } - WiFiEventData.last_wifi_connect_attempt_moment.clear(); - WiFiEventData.wifi_connect_attempt = 1; - WiFiEventData.wifiConnectAttemptNeeded = false; - - // No need to wait longer to start AP mode. - if (!Settings.DoNotStartAP()) { - setAP(true); - } - return false; - } - WiFiEventData.warnedNoValidWiFiSettings = false; - setSTA(true); - #if defined(ESP8266) - wifi_station_set_hostname(NetworkCreateRFCCompliantHostname().c_str()); - - #endif // if defined(ESP8266) - #if defined(ESP32) - WiFi.config(INADDR_NONE, INADDR_NONE, INADDR_NONE); - #endif // if defined(ESP32) - setConnectionSpeed(); - setupStaticIPconfig(); - - - - // Start the process of connecting or starting AP - if (WiFi_AP_Candidates.getNext(true)) { - // Try to connect to AP - - } else { - // No (known) AP, start scanning - startScanning(); - } - - - return true; -} - -#endif // ESPEASY_WIFI_CLEANUP_WORK_IN_PROGRESS - - -// ******************************************************************************** -// WiFi state -// ******************************************************************************** - -/* - WiFi STA states: - 1 STA off => ESPEASY_WIFI_DISCONNECTED - 2 STA connecting - 3 STA connected => ESPEASY_WIFI_CONNECTED - 4 STA got IP => ESPEASY_WIFI_GOT_IP - 5 STA connected && got IP => ESPEASY_WIFI_SERVICES_INITIALIZED - - N.B. the states are flags, meaning both "connected" and "got IP" must be set - to be considered ESPEASY_WIFI_SERVICES_INITIALIZED - - The flag wifiConnectAttemptNeeded indicates whether a new connect attempt is needed. - This is set to true when: - - Security settings have been saved with AP mode enabled. FIXME TD-er, this may not be the best check. - - WiFi connect timeout reached & No client is connected to the AP mode of the node. - - Wifi is reset - - WiFi setup page has been loaded with SSID/pass values. - - - WiFi AP mode states: - 1 AP on => reset AP disable timer - 2 AP client connect/disconnect => reset AP disable timer - 3 AP off => AP disable timer = 0; - - AP mode will be disabled when both apply: - - AP disable timer (timerAPoff) expired - - No client is connected to the AP. - - AP mode will be enabled when at least one applies: - - No valid WiFi settings - - Start AP timer (timerAPstart) expired - - Start AP timer is set or cleared at: - - Set timerAPstart when "valid WiFi connection" state is observed. - - Disable timerAPstart when ESPEASY_WIFI_SERVICES_INITIALIZED wifi state is reached. - - For the first attempt to connect after a cold boot (RTC values are 0), a WiFi scan will be - performed to find the strongest known SSID. - This will set RTC.lastBSSID and RTC.lastWiFiChannel - - Quick reconnect (using BSSID/channel of last connection) when both apply: - - If wifi_connect_attempt < 3 - - RTC.lastBSSID is known - - RTC.lastWiFiChannel != 0 - - Change of wifi settings when both apply: - - "other" settings valid - - (wifi_connect_attempt % 2) == 0 - - Reset of wifi_connect_attempt to 0 when both apply: - - connection successful - - Connection stable (connected for > 5 minutes) - - */ - - -// ******************************************************************************** -// Check WiFi connected status -// This is basically the state machine to switch between states: -// - Initiate WiFi reconnect -// - Start/stop of AP mode -// ******************************************************************************** -bool WiFiConnected() { - START_TIMER; - - static bool recursiveCall = false; - - static uint32_t lastCheckedTime = 0; - static bool lastState = false; - -#if FEATURE_USE_IPV6 - if (!WiFiEventData.processedGotIP6) { - processGotIPv6(); - } -#endif - - if (!WifiIsSTA(WiFi.getMode())) { - lastState = false; - return lastState; - } - - - const int32_t timePassed = timePassedSince(lastCheckedTime); - if (lastCheckedTime != 0) { - if (timePassed < 100) { - if (WiFiEventData.lastDisconnectMoment.isSet() && - WiFiEventData.lastDisconnectMoment.millisPassedSince() > timePassed) - { - // Try to rate-limit the nr of calls to this function or else it will be called 1000's of times a second. - return lastState; - } - } - if (timePassed < 10) { - // Rate limit time spent in WiFiConnected() to max. 100x per sec to process the rest of this function - return lastState; - } - } - - - - if (WiFiEventData.unprocessedWifiEvents()) { return false; } - - bool wifi_isconnected = WiFi.isConnected(); - #ifdef ESP8266 - // Perform check on SDK function, see: https://github.com/esp8266/Arduino/issues/7432 - station_status_t status = wifi_station_get_connect_status(); - switch(status) { - case STATION_GOT_IP: - wifi_isconnected = true; - break; - case STATION_NO_AP_FOUND: - case STATION_CONNECT_FAIL: - case STATION_WRONG_PASSWORD: - wifi_isconnected = false; - break; - case STATION_IDLE: - case STATION_CONNECTING: - break; - - default: - wifi_isconnected = false; - break; - } - #endif - - if (recursiveCall) return wifi_isconnected; - recursiveCall = true; - - - // For ESP82xx, do not rely on WiFi.status() with event based wifi. - const int32_t wifi_rssi = WiFi.RSSI(); - bool validWiFi = (wifi_rssi < 0) && wifi_isconnected && hasIPaddr(); - /* - if (validWiFi && WiFi.channel() != WiFiEventData.usedChannel) { - validWiFi = false; - } - */ - if (validWiFi != WiFiEventData.WiFiServicesInitialized()) { - // else wifiStatus is no longer in sync. - if (checkAndResetWiFi()) { - // Wifi has been reset, so no longer valid WiFi - validWiFi = false; - } - } - - if (validWiFi) { - // Connected, thus disable any timer to start AP mode. (except when in WiFi setup mode) - if (!WiFiEventData.wifiSetupConnect) { - WiFiEventData.timerAPstart.clear(); - } - STOP_TIMER(WIFI_ISCONNECTED_STATS); - recursiveCall = false; - // Only return true after some time since it got connected. -#if FEATURE_SET_WIFI_TX_PWR - SetWiFiTXpower(); -#endif - lastState = WiFiEventData.wifi_considered_stable || WiFiEventData.lastConnectMoment.timeoutReached(100); - lastCheckedTime = millis(); - return lastState; - } - - if ((WiFiEventData.timerAPstart.isSet()) && WiFiEventData.timerAPstart.timeReached()) { - if (WiFiEventData.timerAPoff.isSet() && !WiFiEventData.timerAPoff.timeReached()) { - if (!Settings.DoNotStartAP()) { - // Timer reached, so enable AP mode. - if (!WifiIsAP(WiFi.getMode())) { - if (!WiFiEventData.wifiConnectAttemptNeeded) { - #ifndef BUILD_MINIMAL_OTA - addLog(LOG_LEVEL_INFO, F("WiFi : WiFiConnected(), start AP")); - #endif - WifiScan(false); - setSTA(false); // Force reset WiFi + reduce power consumption - setAP(true); - } - } - } - } else { - WiFiEventData.timerAPstart.clear(); - WiFiEventData.timerAPoff.clear(); - } - } - - - // When made this far in the code, we apparently do not have valid WiFi connection. - if (!WiFiEventData.timerAPstart.isSet() && !WifiIsAP(WiFi.getMode())) { - // First run we do not have WiFi connection any more, set timer to start AP mode - // Only allow the automatic AP mode in the first N minutes after boot. - if (getUptimeMinutes() < WIFI_ALLOW_AP_AFTERBOOT_PERIOD) { - WiFiEventData.timerAPstart.setMillisFromNow(WIFI_RECONNECT_WAIT); - // Fixme TD-er: Make this more elegant as it now needs to know about the extra time needed for the AP start timer. - WiFiEventData.timerAPoff.setMillisFromNow(WIFI_RECONNECT_WAIT + WIFI_AP_OFF_TIMER_DURATION); - } - } - - const bool timeoutReached = WiFiEventData.last_wifi_connect_attempt_moment.isSet() && - WiFiEventData.last_wifi_connect_attempt_moment.timeoutReached(2 * DEFAULT_WIFI_CONNECTION_TIMEOUT); - - if (timeoutReached && !WiFiEventData.wifiSetup) { - // It took too long to make a connection, set flag we need to try again - //if (!wifiAPmodeActivelyUsed()) { - WiFiEventData.wifiConnectAttemptNeeded = true; - //} - WiFiEventData.wifiConnectInProgress = false; - if (!WiFiEventData.WiFiDisconnected()) { - # ifndef BUILD_NO_DEBUG - addLog(LOG_LEVEL_INFO, F("WiFi : wifiConnectTimeoutReached")); - #endif - WifiDisconnect(); - } - } - delay(0); - STOP_TIMER(WIFI_NOTCONNECTED_STATS); - recursiveCall = false; - return false; -} - -void WiFiConnectRelaxed() { - if (!WiFiEventData.processedDisconnect) { - processDisconnect(); - } - if (!WiFiEventData.WiFiConnectAllowed() || WiFiEventData.wifiConnectInProgress) { - if (WiFiEventData.wifiConnectInProgress) { - if (WiFiEventData.last_wifi_connect_attempt_moment.isSet()) { - if (WiFiEventData.last_wifi_connect_attempt_moment.timeoutReached(WIFI_PROCESS_EVENTS_TIMEOUT)) { - WiFiEventData.wifiConnectInProgress = false; - } - } - } - - if (WiFiEventData.wifiConnectInProgress) { - return; // already connected or connect attempt in progress need to disconnect first - } - } - if (!WiFiEventData.processedScanDone) { - // Scan is still active, so do not yet connect. - return; - } - - if (WiFiEventData.unprocessedWifiEvents()) { - # ifndef BUILD_NO_DEBUG - if (loglevelActiveFor(LOG_LEVEL_ERROR)) { - String log = F("WiFi : Connecting not possible, unprocessed WiFi events: "); - if (!WiFiEventData.processedConnect) { - log += F(" conn"); - } - if (!WiFiEventData.processedDisconnect) { - log += F(" disconn"); - } - if (!WiFiEventData.processedGotIP) { - log += F(" gotIP"); - } -#if FEATURE_USE_IPV6 - if (!WiFiEventData.processedGotIP6) { - log += F(" gotIP6"); - } -#endif - - if (!WiFiEventData.processedDHCPTimeout) { - log += F(" DHCP_t/o"); - } - - addLogMove(LOG_LEVEL_ERROR, log); - logConnectionStatus(); - } - #endif - return; - } - - if (!WiFiEventData.wifiSetupConnect && wifiAPmodeActivelyUsed()) { - return; - } - - - // FIXME TD-er: Should not try to prepare when a scan is still busy. - // This is a logic error which may lead to strange issues if some kind of timeout happens and/or RF calibration was not OK. - // Split this function into separate parts, with the last part being the actual connect attempt either after a scan is complete or quick connect is possible. - - AttemptWiFiConnect(); -} - -void AttemptWiFiConnect() { - if (!WiFiEventData.wifiConnectAttemptNeeded) { - return; - } - - if (WiFiEventData.wifiConnectInProgress) { - return; - } - - setNetworkMedium(NetworkMedium_t::WIFI); - if (active_network_medium != NetworkMedium_t::WIFI) - { - return; - } - - - if (WiFiEventData.wifiSetupConnect) { - // wifiSetupConnect is when run from the setup page. - RTC.clearLastWiFi(); // Force slow connect - WiFiEventData.wifi_connect_attempt = 0; - WiFiEventData.wifiSetupConnect = false; - if (WiFiEventData.timerAPoff.isSet()) { - WiFiEventData.timerAPoff.setMillisFromNow(WIFI_RECONNECT_WAIT + WIFI_AP_OFF_TIMER_DURATION); - } - } - - if (WiFiEventData.last_wifi_connect_attempt_moment.isSet()) { - if (!WiFiEventData.last_wifi_connect_attempt_moment.timeoutReached(DEFAULT_WIFI_CONNECTION_TIMEOUT)) { - return; - } - } - - if (WiFiEventData.unprocessedWifiEvents()) { - return; - } - setSTA(false); - - setSTA(true); - - if (WiFi_AP_Candidates.getNext(WiFiScanAllowed())) { - const WiFi_AP_Candidate candidate = WiFi_AP_Candidates.getCurrent(); - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - addLogMove(LOG_LEVEL_INFO, strformat( - F("WIFI : Connecting %s attempt #%u"), - candidate.toString().c_str(), - WiFiEventData.wifi_connect_attempt)); - } - WiFiEventData.markWiFiBegin(); - if (prepareWiFi()) { - setNetworkMedium(NetworkMedium_t::WIFI); - RTC.clearLastWiFi(); - RTC.lastWiFiSettingsIndex = candidate.index; - - float tx_pwr = 0; // Will be set higher based on RSSI when needed. - // FIXME TD-er: Must check WiFiEventData.wifi_connect_attempt to increase TX power -#if FEATURE_SET_WIFI_TX_PWR - if (Settings.UseMaxTXpowerForSending()) { - tx_pwr = Settings.getWiFi_TX_power(); - } - SetWiFiTXpower(tx_pwr, candidate.rssi); -#endif - // Start connect attempt now, so no longer needed to attempt new connection. - WiFiEventData.wifiConnectAttemptNeeded = false; - WiFiEventData.wifiConnectInProgress = true; - const String key = WiFi_AP_CandidatesList::get_key(candidate.index); - -#if FEATURE_USE_IPV6 - if (Settings.EnableIPv6()) { - WiFi.enableIPv6(true); - } -#endif - -#ifdef ESP32 - if (Settings.IncludeHiddenSSID()) { - wifi_country_t config = { - .cc = "01", - .schan = 1, - .nchan = 14, - .policy = WIFI_COUNTRY_POLICY_MANUAL, - }; - esp_wifi_set_country(&config); - } -#endif - - - if ((Settings.HiddenSSID_SlowConnectPerBSSID() || !candidate.bits.isHidden) - && candidate.allowQuickConnect()) { - WiFi.begin(candidate.ssid.c_str(), key.c_str(), candidate.channel, candidate.bssid.mac); - } else { - WiFi.begin(candidate.ssid.c_str(), key.c_str()); - } -#ifdef ESP32 - // Always wait for a second on ESP32 - WiFi.waitForConnectResult(1000); // https://github.com/arendst/Tasmota/issues/14985 -#else - if (Settings.WaitWiFiConnect() || candidate.bits.isHidden) { -// WiFi.waitForConnectResult(candidate.isHidden ? 3000 : 1000); // https://github.com/arendst/Tasmota/issues/14985 - WiFi.waitForConnectResult(1000); // https://github.com/arendst/Tasmota/issues/14985 - } -#endif - delay(1); - } else { - WiFiEventData.wifiConnectInProgress = false; - } - } else { - if (!wifiAPmodeActivelyUsed() || WiFiEventData.wifiSetupConnect) { - if (!prepareWiFi()) { - //return; - } - - if (WiFiScanAllowed()) { - // Maybe not scan async to give the ESP some slack in power consumption? - const bool async = false; - WifiScan(async); - } - // Limit nr of attempts as we don't have any AP candidates. - WiFiEventData.last_wifi_connect_attempt_moment.setMillisFromNow(60000); - WiFiEventData.timerAPstart.setNow(); - } - } - - logConnectionStatus(); -} - -// ******************************************************************************** -// Set Wifi config -// ******************************************************************************** -bool prepareWiFi() { - #if defined(ESP32) - registerWiFiEventHandler(); - #endif - - if (!WiFi_AP_Candidates.hasCandidateCredentials()) { - if (!WiFiEventData.warnedNoValidWiFiSettings) { - addLog(LOG_LEVEL_ERROR, F("WIFI : No valid wifi settings")); - WiFiEventData.warnedNoValidWiFiSettings = true; - } -// WiFiEventData.last_wifi_connect_attempt_moment.clear(); - WiFiEventData.wifi_connect_attempt = 1; - WiFiEventData.wifiConnectAttemptNeeded = false; - - // No need to wait longer to start AP mode. - if (!Settings.DoNotStartAP()) { - WifiScan(false); -// setAP(true); - } - return false; - } - WiFiEventData.warnedNoValidWiFiSettings = false; - setSTA(true); - - #if defined(ESP8266) - wifi_station_set_hostname(NetworkCreateRFCCompliantHostname().c_str()); - - #endif // if defined(ESP8266) - #if defined(ESP32) - WiFi.config(INADDR_NONE, INADDR_NONE, INADDR_NONE); - #endif // if defined(ESP32) - setConnectionSpeed(); - setupStaticIPconfig(); - WiFiEventData.wifiConnectAttemptNeeded = true; - - return true; -} - -bool checkAndResetWiFi() { - #ifdef ESP8266 - station_status_t status = wifi_station_get_connect_status(); - - switch(status) { - case STATION_GOT_IP: - if (WiFi.RSSI() < 0 && WiFi.localIP().isSet()) { - //if (WiFi.channel() == WiFiEventData.usedChannel || WiFiEventData.usedChannel == 0) { - // This is a valid status, no need to reset - return false; - //} - } - break; - case STATION_NO_AP_FOUND: - case STATION_CONNECT_FAIL: - case STATION_WRONG_PASSWORD: - // Reason to reset WiFi - break; - case STATION_IDLE: - case STATION_CONNECTING: - if (WiFiEventData.last_wifi_connect_attempt_moment.isSet() && !WiFiEventData.last_wifi_connect_attempt_moment.timeoutReached(DEFAULT_WIFI_CONNECTION_TIMEOUT)) { - return false; - } - break; - } - #endif - #ifdef ESP32 - if (WiFi.isConnected()) { - //if (WiFi.channel() == WiFiEventData.usedChannel || WiFiEventData.usedChannel == 0) { - return false; - //} - } - if (WiFiEventData.last_wifi_connect_attempt_moment.isSet() && !WiFiEventData.last_wifi_connect_attempt_moment.timeoutReached(DEFAULT_WIFI_CONNECTION_TIMEOUT)) { - return false; - } - #endif - # ifndef BUILD_NO_DEBUG - String log = F("WiFi : WiFiConnected() out of sync: "); - log += WiFiEventData.ESPeasyWifiStatusToString(); - log += F(" RSSI: "); - log += String(WiFi.RSSI()); - #ifdef ESP8266 - log += F(" status: "); - log += SDKwifiStatusToString(status); - #endif - #endif - - // Call for reset first, to make sure a syslog call will not try to send. - resetWiFi(); - # ifndef BUILD_NO_DEBUG - addLogMove(LOG_LEVEL_INFO, log); - #endif - return true; -} - - -void resetWiFi() { - //if (wifiAPmodeActivelyUsed()) return; - if (WiFiEventData.lastWiFiResetMoment.isSet() && !WiFiEventData.lastWiFiResetMoment.timeoutReached(1000)) { - // Don't reset WiFi too often - return; - } - FeedSW_watchdog(); - WiFiEventData.clearAll(); - WifiDisconnect(); - - // Send this log only after WifiDisconnect() or else sending to syslog may cause issues - #ifndef BUILD_MINIMAL_OTA - addLog(LOG_LEVEL_INFO, F("Reset WiFi.")); - #endif - - // setWifiMode(WIFI_OFF); - - initWiFi(); -} - -#ifdef ESP32 -void removeWiFiEventHandler() -{ - WiFi.removeEvent(WiFiEventData.wm_event_id); - WiFiEventData.wm_event_id = 0; -} - -void registerWiFiEventHandler() -{ - if (WiFiEventData.wm_event_id != 0) { - removeWiFiEventHandler(); - } - WiFiEventData.wm_event_id = WiFi.onEvent(WiFiEvent); -} -#endif - - -void initWiFi() -{ -#ifdef ESP8266 - - // See https://github.com/esp8266/Arduino/issues/5527#issuecomment-460537616 - // FIXME TD-er: Do not destruct WiFi object, it may cause crashes with queued UDP traffic. -// WiFi.~ESP8266WiFiClass(); -// WiFi = ESP8266WiFiClass(); -#endif // ifdef ESP8266 -#ifdef ESP32 - removeWiFiEventHandler(); -#endif - - - WiFi.persistent(false); // Do not use SDK storage of SSID/WPA parameters - // The WiFi.disconnect() ensures that the WiFi is working correctly. If this is not done before receiving WiFi connections, - // those WiFi connections will take a long time to make or sometimes will not work at all. - WiFi.disconnect(false); - delay(1); - if (active_network_medium != NetworkMedium_t::NotSet) { - setSTA(true); - WifiScan(false); - } - setWifiMode(WIFI_OFF); - -#if defined(ESP32) - registerWiFiEventHandler(); -#endif -#ifdef ESP8266 - // WiFi event handlers - static bool handlers_initialized = false; - if (!handlers_initialized) { - stationConnectedHandler = WiFi.onStationModeConnected(onConnected); - stationDisconnectedHandler = WiFi.onStationModeDisconnected(onDisconnect); - stationGotIpHandler = WiFi.onStationModeGotIP(onGotIP); - stationModeDHCPTimeoutHandler = WiFi.onStationModeDHCPTimeout(onDHCPTimeout); - stationModeAuthModeChangeHandler = WiFi.onStationModeAuthModeChanged(onStationModeAuthModeChanged); - APModeStationConnectedHandler = WiFi.onSoftAPModeStationConnected(onConnectedAPmode); - APModeStationDisconnectedHandler = WiFi.onSoftAPModeStationDisconnected(onDisconnectedAPmode); - handlers_initialized = true; - } -#endif - delay(100); -} - -// ******************************************************************************** -// Configure WiFi TX power -// ******************************************************************************** -#if FEATURE_SET_WIFI_TX_PWR -void SetWiFiTXpower() { - SetWiFiTXpower(0); // Just some minimal value, will be adjusted in SetWiFiTXpower -} - -void SetWiFiTXpower(float dBm) { - SetWiFiTXpower(dBm, WiFi.RSSI()); -} - -void SetWiFiTXpower(float dBm, float rssi) { - const WiFiMode_t cur_mode = WiFi.getMode(); - if (cur_mode == WIFI_OFF) { - return; - } - - if (Settings.UseMaxTXpowerForSending()) { - dBm = 30; // Just some max, will be limited later - } - - // Range ESP32 : -1dBm - 20dBm - // Range ESP8266: 0dBm - 20.5dBm - float maxTXpwr; - float threshold = GetRSSIthreshold(maxTXpwr); - #ifdef ESP8266 - float minTXpwr{}; - #endif - #ifdef ESP32 - float minTXpwr = -1.0f; - #endif - - threshold += Settings.WiFi_sensitivity_margin; // Margin in dBm on top of threshold - - // Assume AP sends with max set by ETSI standard. - // 2.4 GHz: 100 mWatt (20 dBm) - // US and some other countries allow 1000 mW (30 dBm) - // We cannot send with over 20 dBm, thus it makes no sense to force higher TX power all the time. - const float newrssi = rssi - 20; - if (newrssi < threshold) { - minTXpwr = threshold - newrssi; - } - if (minTXpwr > maxTXpwr) { - minTXpwr = maxTXpwr; - } - if (dBm > maxTXpwr) { - dBm = maxTXpwr; - } else if (dBm < minTXpwr) { - dBm = minTXpwr; - } - - #ifdef ESP32 - int8_t power = dBm * 4; - if (esp_wifi_set_max_tx_power(power) == ESP_OK) { - if (esp_wifi_get_max_tx_power(&power) == ESP_OK) { - dBm = static_cast(power) / 4.0f; - } - } - #endif - - #ifdef ESP8266 - WiFi.setOutputPower(dBm); - #endif - - if (WiFiEventData.wifi_TX_pwr < dBm) { - // Will increase the TX power, give power supply of the unit some rest - delay(1); - } - - WiFiEventData.wifi_TX_pwr = dBm; - - delay(0); - #ifndef BUILD_NO_DEBUG - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - const int TX_pwr_int = WiFiEventData.wifi_TX_pwr * 4; - const int maxTXpwr_int = maxTXpwr * 4; - if (TX_pwr_int != maxTXpwr_int) { - static int last_log = -1; - if (TX_pwr_int != last_log) { - last_log = TX_pwr_int; - String log = strformat( - F("WiFi : Set TX power to %ddBm sensitivity: %ddBm"), - static_cast(dBm), - static_cast(threshold)); - if (rssi < 0) { - log += strformat(F(" RSSI: %ddBm"), static_cast(rssi)); - } - addLogMove(LOG_LEVEL_DEBUG, log); - } - } - } - #endif -} -#endif - - - - -float GetRSSIthreshold(float& maxTXpwr) { - maxTXpwr = Settings.getWiFi_TX_power(); - float threshold = WIFI_SENSITIVITY_n; - switch (getConnectionProtocol()) { - case WiFiConnectionProtocol::WiFi_Protocol_11b: - threshold = WIFI_SENSITIVITY_11b; - if (maxTXpwr > MAX_TX_PWR_DBM_11b) maxTXpwr = MAX_TX_PWR_DBM_11b; - break; - case WiFiConnectionProtocol::WiFi_Protocol_11g: - threshold = WIFI_SENSITIVITY_54g; - if (maxTXpwr > MAX_TX_PWR_DBM_54g) maxTXpwr = MAX_TX_PWR_DBM_54g; - break; -#ifdef ESP8266 - case WiFiConnectionProtocol::WiFi_Protocol_11n: -#else - case WiFiConnectionProtocol::WiFi_Protocol_HT20: - case WiFiConnectionProtocol::WiFi_Protocol_HT40: - case WiFiConnectionProtocol::WiFi_Protocol_HE20: -#endif - - threshold = WIFI_SENSITIVITY_n; - if (maxTXpwr > MAX_TX_PWR_DBM_n) maxTXpwr = MAX_TX_PWR_DBM_n; - break; -#ifdef ESP32 -#if ESP_IDF_VERSION > ESP_IDF_VERSION_VAL(5, 2, 0) - case WiFiConnectionProtocol::WiFi_Protocol_11a: - case WiFiConnectionProtocol::WiFi_Protocol_VHT20: - // FIXME TD-er: Must determine max. TX power for these 5 GHz modi -#endif - case WiFiConnectionProtocol::WiFi_Protocol_LR: -#endif - case WiFiConnectionProtocol::Unknown: - break; - } - return threshold; -} - -int GetRSSI_quality() { - long rssi = WiFi.RSSI(); - - if (-50 < rssi) { return 10; } - - if (rssi <= -98) { return 0; } - rssi = rssi + 97; // Range 0..47 => 1..9 - return (rssi / 5) + 1; -} - -WiFiConnectionProtocol getConnectionProtocol() { - if (WiFi.RSSI() < 0) { - #ifdef ESP8266 - switch (wifi_get_phy_mode()) { - case PHY_MODE_11B: - return WiFiConnectionProtocol::WiFi_Protocol_11b; - case PHY_MODE_11G: - return WiFiConnectionProtocol::WiFi_Protocol_11g; - case PHY_MODE_11N: - return WiFiConnectionProtocol::WiFi_Protocol_11n; - } - #endif - #ifdef ESP32 - - wifi_phy_mode_t phymode; - esp_wifi_sta_get_negotiated_phymode(&phymode); - switch (phymode) { - case WIFI_PHY_MODE_11B: return WiFiConnectionProtocol::WiFi_Protocol_11b; - case WIFI_PHY_MODE_11G: return WiFiConnectionProtocol::WiFi_Protocol_11g; - case WIFI_PHY_MODE_HT20: return WiFiConnectionProtocol::WiFi_Protocol_HT20; - case WIFI_PHY_MODE_HT40: return WiFiConnectionProtocol::WiFi_Protocol_HT40; - case WIFI_PHY_MODE_HE20: return WiFiConnectionProtocol::WiFi_Protocol_HE20; - case WIFI_PHY_MODE_LR: return WiFiConnectionProtocol::WiFi_Protocol_LR; -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0) - // 5 GHz - case WIFI_PHY_MODE_11A: return WiFiConnectionProtocol::WiFi_Protocol_11a; - case WIFI_PHY_MODE_VHT20: return WiFiConnectionProtocol::WiFi_Protocol_VHT20; -#endif - } - #endif - } - return WiFiConnectionProtocol::Unknown; -} - -#ifdef ESP32 -int64_t WiFi_get_TSF_time() -{ - return esp_wifi_get_tsf_time(WIFI_IF_STA); -} -#endif - - -// ******************************************************************************** -// Disconnect from Wifi AP -// ******************************************************************************** -void WifiDisconnect() -{ - if (!WiFiEventData.processedDisconnect || - WiFiEventData.processingDisconnect.isSet()) { - return; - } - if (WiFi.status() == WL_DISCONNECTED) { - return; - } - // Prevent recursion - static LongTermTimer processingDisconnectTimer; - if (processingDisconnectTimer.isSet() && - !processingDisconnectTimer.timeoutReached(200)) return; - processingDisconnectTimer.setNow(); - # ifndef BUILD_NO_DEBUG - addLog(LOG_LEVEL_INFO, F("WiFi : WifiDisconnect()")); - #endif - #ifdef ESP32 - removeWiFiEventHandler(); - WiFi.disconnect(); - delay(100); - { - const IPAddress ip; - const IPAddress gw; - const IPAddress subnet; - const IPAddress dns; - WiFi.config(ip, gw, subnet, dns); - } - #endif - #ifdef ESP8266 - // Only call disconnect when STA is active - if (WifiIsSTA(WiFi.getMode())) { - wifi_station_disconnect(); - } - station_config conf{}; - memset(&conf, 0, sizeof(conf)); - ETS_UART_INTR_DISABLE(); - wifi_station_set_config_current(&conf); - ETS_UART_INTR_ENABLE(); - #endif - WiFiEventData.setWiFiDisconnected(); - WiFiEventData.markDisconnect(WIFI_DISCONNECT_REASON_UNSPECIFIED); - /* - if (!Settings.UseLastWiFiFromRTC()) { - RTC.clearLastWiFi(); - } - */ - delay(100); - WiFiEventData.processingDisconnect.clear(); - WiFiEventData.processedDisconnect = false; - processDisconnect(); - processingDisconnectTimer.clear(); -} - -// ******************************************************************************** -// Scan WiFi network -// ******************************************************************************** -bool WiFiScanAllowed() { - if (WiFi_AP_Candidates.scanComplete() == WIFI_SCAN_RUNNING) { - return false; - } - if (!WiFiEventData.processedScanDone) { - processScanDone(); - } - if (!WiFiEventData.processedDisconnect) { - processDisconnect(); - } - - if (WiFiEventData.wifiConnectInProgress) { - return false; - } - - if (WiFiEventData.intent_to_reboot) { - return false; - } - - if (WiFiEventData.unprocessedWifiEvents()) { - # ifndef BUILD_NO_DEBUG - if (loglevelActiveFor(LOG_LEVEL_ERROR)) { - String log = F("WiFi : Scan not allowed, unprocessed WiFi events: "); - if (!WiFiEventData.processedConnect) { - log += F(" conn"); - } - if (!WiFiEventData.processedDisconnect) { - log += F(" disconn"); - } - if (!WiFiEventData.processedGotIP) { - log += F(" gotIP"); - } - if (!WiFiEventData.processedDHCPTimeout) { - log += F(" DHCP_t/o"); - } - - addLogMove(LOG_LEVEL_ERROR, log); - logConnectionStatus(); - } - #endif - return false; - } - /* - if (!wifiAPmodeActivelyUsed() && !NetworkConnected()) { - return true; - } - */ - WiFi_AP_Candidates.purge_expired(); - if (WiFiEventData.wifiConnectInProgress) { - return false; - } - if (WiFiEventData.lastScanMoment.isSet()) { - if (NetworkConnected() && WiFi_AP_Candidates.getBestCandidate().usable()) { - # ifndef BUILD_NO_DEBUG - addLog(LOG_LEVEL_ERROR, F("WiFi : Scan not needed, good candidate present")); - #endif - return false; - } - } - - if (WiFiEventData.lastDisconnectMoment.isSet() && WiFiEventData.lastDisconnectMoment.millisPassedSince() < WIFI_RECONNECT_WAIT) { - if (!NetworkConnected()) { - return WiFiEventData.processedConnect; - } - } - if (WiFiEventData.lastScanMoment.isSet()) { - const LongTermTimer::Duration scanInterval = wifiAPmodeActivelyUsed() ? WIFI_SCAN_INTERVAL_AP_USED : WIFI_SCAN_INTERVAL_MINIMAL; - if (WiFiEventData.lastScanMoment.millisPassedSince() < scanInterval) { - return false; - } - } - return WiFiEventData.processedConnect; -} - - -void WifiScan(bool async, uint8_t channel) { - setSTA(true); - if (!WiFiScanAllowed()) { - return; - } -#ifdef ESP32 - // TD-er: Don't run async scan on ESP32. - // Since IDF 4.4 it seems like the active channel may be messed up when running async scan - // Perform a disconnect after scanning. - // See: https://github.com/letscontrolit/ESPEasy/pull/3579#issuecomment-967021347 - async = false; - - if (Settings.IncludeHiddenSSID()) { - wifi_country_t config = { - .cc = "01", - .schan = 1, - .nchan = 14, - .policy = WIFI_COUNTRY_POLICY_MANUAL, - }; - esp_wifi_set_country(&config); - } - - -#endif - - START_TIMER; - WiFiEventData.lastScanMoment.setNow(); - # ifndef BUILD_NO_DEBUG - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - if (channel == 0) { - addLog(LOG_LEVEL_INFO, F("WiFi : Start network scan all channels")); - } else { - addLogMove(LOG_LEVEL_INFO, strformat(F("WiFi : Start network scan ch: %d "), channel)); - } - } - #endif - bool show_hidden = true; - WiFiEventData.processedScanDone = false; - WiFiEventData.lastGetScanMoment.setNow(); - WiFiEventData.lastScanChannel = channel; - - unsigned int nrScans = 1 + (async ? 0 : Settings.NumberExtraWiFiScans); - while (nrScans > 0) { - if (!async) { - WiFi_AP_Candidates.begin_sync_scan(); - FeedSW_watchdog(); - } - --nrScans; -#ifdef ESP8266 -#if FEATURE_ESP8266_DIRECT_WIFI_SCAN - { - static bool FIRST_SCAN = true; - - struct scan_config config; - memset(&config, 0, sizeof(config)); - config.ssid = nullptr; - config.bssid = nullptr; - config.channel = channel; - config.show_hidden = show_hidden ? 1 : 0;; - config.scan_type = WIFI_SCAN_TYPE_ACTIVE; - if (FIRST_SCAN) { - config.scan_time.active.min = 100; - config.scan_time.active.max = 200; - } else { - config.scan_time.active.min = 400; - config.scan_time.active.max = 500; - } - FIRST_SCAN = false; - wifi_station_scan(&config, &onWiFiScanDone); - if (!async) { - // will resume when SYSTEM_EVENT_SCAN_DONE event is fired - do { - delay(0); - } while (!WiFiEventData.processedScanDone); - } - - } -#else - WiFi.scanNetworks(async, show_hidden, channel); -#endif -#endif -#ifdef ESP32 - const bool passive = Settings.PassiveWiFiScan(); - const uint32_t max_ms_per_chan = 120; - WiFi.setScanTimeout(14 * max_ms_per_chan * 2); - WiFi.scanNetworks(async, show_hidden, passive, max_ms_per_chan /*, channel */); -#endif - if (!async) { - FeedSW_watchdog(); - processScanDone(); - } - } -#if FEATURE_TIMING_STATS - if (async) { - STOP_TIMER(WIFI_SCAN_ASYNC); - } else { - STOP_TIMER(WIFI_SCAN_SYNC); - } -#endif - -#ifdef ESP32 -#if ESP_IDF_VERSION_MAJOR<5 - RTC.clearLastWiFi(); - if (WiFiConnected()) { - # ifndef BUILD_NO_DEBUG - addLog(LOG_LEVEL_INFO, F("WiFi : Disconnect after scan")); - #endif - - const bool needReconnect = WiFiEventData.wifiConnectAttemptNeeded; - WifiDisconnect(); - WiFiEventData.wifiConnectAttemptNeeded = needReconnect; - } -#endif -#endif - -} - -// ******************************************************************************** -// Scan all Wifi Access Points -// ******************************************************************************** -void WiFiScan_log_to_serial() -{ - // Direct Serial is allowed here, since this function will only be called from serial input. - serialPrintln(F("WIFI : SSID Scan start")); - if (WiFi_AP_Candidates.scanComplete() <= 0) { - WiFiMode_t cur_wifimode = WiFi.getMode(); - WifiScan(false); - setWifiMode(cur_wifimode); - } - - const int8_t scanCompleteStatus = WiFi_AP_Candidates.scanComplete(); - if (scanCompleteStatus <= 0) { - serialPrintln(F("WIFI : No networks found")); - } - else - { - serialPrint(F("WIFI : ")); - serialPrint(String(scanCompleteStatus)); - serialPrintln(F(" networks found")); - - int i = 0; - - for (auto it = WiFi_AP_Candidates.scanned_begin(); it != WiFi_AP_Candidates.scanned_end(); ++it) - { - ++i; - // Print SSID and RSSI for each network found - serialPrint(F("WIFI : ")); - serialPrint(String(i)); - serialPrint(": "); - serialPrintln(it->toString()); - delay(10); - } - } - serialPrintln(""); -} - -// ******************************************************************************** -// Manage Wifi Modes -// ******************************************************************************** -void setSTA(bool enable) { - switch (WiFi.getMode()) { - case WIFI_OFF: - - if (enable) { setWifiMode(WIFI_STA); } - break; - case WIFI_STA: - - if (!enable) { setWifiMode(WIFI_OFF); } - break; - case WIFI_AP: - - if (enable) { setWifiMode(WIFI_AP_STA); } - break; - case WIFI_AP_STA: - - if (!enable) { setWifiMode(WIFI_AP); } - break; - default: - break; - } -} - -void setAP(bool enable) { - WiFiMode_t wifimode = WiFi.getMode(); - - switch (wifimode) { - case WIFI_OFF: - - if (enable) { - setWifiMode(WIFI_AP); - } - break; - case WIFI_STA: - - if (enable) { setWifiMode(WIFI_AP_STA); } - break; - case WIFI_AP: - - if (!enable) { setWifiMode(WIFI_OFF); } - break; - case WIFI_AP_STA: - - if (!enable) { setWifiMode(WIFI_STA); } - break; - default: - break; - } -} - -// Only internal scope -void setAPinternal(bool enable) -{ - if (enable) { - // create and store unique AP SSID/PW to prevent ESP from starting AP mode with default SSID and No password! - // setup ssid for AP Mode when needed - String softAPSSID = NetworkCreateRFCCompliantHostname(); - String pwd = SecuritySettings.WifiAPKey; - IPAddress subnet(DEFAULT_AP_SUBNET); - - if (!WiFi.softAPConfig(apIP, apIP, subnet)) { - addLog(LOG_LEVEL_ERROR, strformat( - ("WIFI : [AP] softAPConfig failed! IP: %s, GW: %s, SN: %s"), - apIP.toString().c_str(), - apIP.toString().c_str(), - subnet.toString().c_str() - ) - ); - } - - int channel = 1; - if (WifiIsSTA(WiFi.getMode()) && WiFiConnected()) { - channel = WiFi.channel(); - } - - if (WiFi.softAP(softAPSSID.c_str(), pwd.c_str(), channel)) { - eventQueue.add(F("WiFi#APmodeEnabled")); - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - addLogMove(LOG_LEVEL_INFO, strformat( - F("WIFI : AP Mode enabled. SSID: %s IP: %s ch: %d"), - softAPSSID.c_str(), - formatIP(WiFi.softAPIP()).c_str(), - channel)); - } - } else { - if (loglevelActiveFor(LOG_LEVEL_ERROR)) { - addLogMove(LOG_LEVEL_ERROR, strformat( - F("WIFI : Error while starting AP Mode with SSID: %s IP: %s"), - softAPSSID.c_str(), - formatIP(apIP).c_str())); - } - } - #ifdef ESP32 - - #else // ifdef ESP32 - - if (wifi_softap_dhcps_status() != DHCP_STARTED) { - if (!wifi_softap_dhcps_start()) { - addLog(LOG_LEVEL_ERROR, F("WIFI : [AP] wifi_softap_dhcps_start failed!")); - } - } - #endif // ifdef ESP32 - WiFiEventData.timerAPoff.setMillisFromNow(WIFI_AP_OFF_TIMER_DURATION); - } else { - #if FEATURE_DNS_SERVER - if (dnsServerActive) { - dnsServerActive = false; - dnsServer.stop(); - } - #endif // if FEATURE_DNS_SERVER - } -} - -const __FlashStringHelper * getWifiModeString(WiFiMode_t wifimode) -{ - switch (wifimode) { - case WIFI_OFF: return F("OFF"); - case WIFI_STA: return F("STA"); - case WIFI_AP: return F("AP"); - case WIFI_AP_STA: return F("AP+STA"); - default: - break; - } - return F("Unknown"); -} - -void setWifiMode(WiFiMode_t new_mode) { - const WiFiMode_t cur_mode = WiFi.getMode(); - static WiFiMode_t processing_wifi_mode = cur_mode; - if (cur_mode == new_mode) { - return; - } - if (processing_wifi_mode == new_mode) { - // Prevent loops - return; - } - processing_wifi_mode = new_mode; - - if (cur_mode == WIFI_OFF) { - #if defined(ESP32) - // Needs to be set while WiFi is off - WiFi.hostname(NetworkCreateRFCCompliantHostname()); - #endif - WiFiEventData.markWiFiTurnOn(); - } - if (new_mode != WIFI_OFF) { - #ifdef ESP8266 - // See: https://github.com/esp8266/Arduino/issues/6172#issuecomment-500457407 - WiFi.forceSleepWake(); // Make sure WiFi is really active. - #endif - delay(100); - } else { - WifiDisconnect(); -// delay(100); - processDisconnect(); - WiFiEventData.clear_processed_flags(); - } - - addLog(LOG_LEVEL_INFO, concat(F("WIFI : Set WiFi to "), getWifiModeString(new_mode))); - - int retry = 2; - while (!WiFi.mode(new_mode) && retry > 0) { - #ifndef BUILD_MINIMAL_OTA - addLog(LOG_LEVEL_INFO, F("WIFI : Cannot set mode!!!!!")); - #endif - delay(100); - --retry; - } - retry = 2; - while (WiFi.getMode() != new_mode && retry > 0) { - #ifndef BUILD_MINIMAL_OTA - addLog(LOG_LEVEL_INFO, F("WIFI : mode not yet set")); - #endif - delay(100); - --retry; - } - - - if (new_mode == WIFI_OFF) { - WiFiEventData.markWiFiTurnOn(); - #if defined(ESP32) - // Needs to be set while WiFi is off - WiFi.hostname(NetworkCreateRFCCompliantHostname()); - #endif - delay(100); - #if defined(ESP32) - esp_wifi_set_ps(WIFI_PS_NONE); -// esp_wifi_set_ps(WIFI_PS_MAX_MODEM); - #endif - #ifdef ESP8266 - WiFi.forceSleepBegin(); - #endif // ifdef ESP8266 - delay(1); - } else { - #ifdef ESP32 - if (cur_mode == WIFI_OFF) { - registerWiFiEventHandler(); - } - #endif - // Only set power mode when AP is not enabled - // When AP is enabled, the sleep mode is already set to WIFI_NONE_SLEEP - if (!WifiIsAP(new_mode)) { - if (Settings.WifiNoneSleep()) { - #ifdef ESP8266 - WiFi.setSleepMode(WIFI_NONE_SLEEP); - #endif - #ifdef ESP32 - WiFi.setSleep(WIFI_PS_NONE); - #endif - } else if (Settings.EcoPowerMode()) { - // Allow light sleep during idle times - #ifdef ESP8266 - WiFi.setSleepMode(WIFI_LIGHT_SLEEP); - #endif - #ifdef ESP32 - // Maximum modem power saving. - // In this mode, interval to receive beacons is determined by the listen_interval parameter in wifi_sta_config_t - // FIXME TD-er: Must test if this is desired behavior in ESP32. - WiFi.setSleep(WIFI_PS_MAX_MODEM); - #endif - } else { - // Default - #ifdef ESP8266 - WiFi.setSleepMode(WIFI_MODEM_SLEEP); - #endif - #ifdef ESP32 - // Minimum modem power saving. - // In this mode, station wakes up to receive beacon every DTIM period - WiFi.setSleep(WIFI_PS_MIN_MODEM); - #endif - } - } -#if FEATURE_SET_WIFI_TX_PWR - SetWiFiTXpower(); -#endif - if (WifiIsSTA(new_mode)) { -// WiFi.setAutoConnect(Settings.SDK_WiFi_autoreconnect()); - WiFi.setAutoReconnect(Settings.SDK_WiFi_autoreconnect()); - } - delay(100); // Must allow for some time to init. - } - const bool new_mode_AP_enabled = WifiIsAP(new_mode); - - if (WifiIsAP(cur_mode) && !new_mode_AP_enabled) { - eventQueue.add(F("WiFi#APmodeDisabled")); - } - - if (WifiIsAP(cur_mode) != new_mode_AP_enabled) { - // Mode has changed - setAPinternal(new_mode_AP_enabled); - } - #if FEATURE_MDNS - #ifdef ESP8266 - // notifyAPChange() is not present in the ESP32 MDNSResponder - MDNS.notifyAPChange(); - #endif - #endif -} - -bool WifiIsAP(WiFiMode_t wifimode) -{ - #if defined(ESP32) - return (wifimode == WIFI_MODE_AP) || (wifimode == WIFI_MODE_APSTA); - #else // if defined(ESP32) - return (wifimode == WIFI_AP) || (wifimode == WIFI_AP_STA); - #endif // if defined(ESP32) -} - -bool WifiIsSTA(WiFiMode_t wifimode) -{ - #if defined(ESP32) - return (wifimode & WIFI_MODE_STA) != 0; - #else // if defined(ESP32) - return (wifimode & WIFI_STA) != 0; - #endif // if defined(ESP32) -} - -bool WiFiUseStaticIP() { - return Settings.IP[0] != 0 && Settings.IP[0] != 255; -} - -bool wifiAPmodeActivelyUsed() -{ - if (!WifiIsAP(WiFi.getMode()) || (!WiFiEventData.timerAPoff.isSet())) { - // AP not active or soon to be disabled in processDisableAPmode() - return false; - } - return WiFi.softAPgetStationNum() != 0; - - // FIXME TD-er: is effectively checking for AP active enough or must really check for connected clients to prevent automatic wifi - // reconnect? -} - -void setConnectionSpeed() { - #ifdef ESP8266 - // ESP8266 only supports 802.11g mode when running in STA+AP - const bool forcedByAPmode = WifiIsAP(WiFi.getMode()); - WiFiPhyMode_t phyMode = (Settings.ForceWiFi_bg_mode() || forcedByAPmode) ? WIFI_PHY_MODE_11G : WIFI_PHY_MODE_11N; - if (!forcedByAPmode) { - const WiFi_AP_Candidate candidate = WiFi_AP_Candidates.getCurrent(); - if (candidate.phy_known() && (candidate.bits.phy_11g != candidate.bits.phy_11n)) { - if ((WIFI_PHY_MODE_11G == phyMode) && !candidate.bits.phy_11g) { - phyMode = WIFI_PHY_MODE_11N; - #ifndef BUILD_MINIMAL_OTA - addLog(LOG_LEVEL_INFO, F("WIFI : AP is set to 802.11n only")); - #endif - } else if ((WIFI_PHY_MODE_11N == phyMode) && !candidate.bits.phy_11n) { - phyMode = WIFI_PHY_MODE_11G; - #ifndef BUILD_MINIMAL_OTA - addLog(LOG_LEVEL_INFO, F("WIFI : AP is set to 802.11g only")); - #endif - } - } else { - bool useAlternate = WiFiEventData.connectionFailures > 10; - if (useAlternate) { - phyMode = (WIFI_PHY_MODE_11G == phyMode) ? WIFI_PHY_MODE_11N : WIFI_PHY_MODE_11G; - } - } - } else { - // No need to perform a next attempt. - WiFi_AP_Candidates.markAttempt(); - } - - if (WiFi.getPhyMode() == phyMode) { - return; - } - #ifndef BUILD_NO_DEBUG - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = concat(F("WIFI : Set to 802.11"), (WIFI_PHY_MODE_11G == phyMode) ? 'g' : 'n'); - if (forcedByAPmode) { - log += (F(" (AP+STA mode)")); - } - if (Settings.ForceWiFi_bg_mode()) { - log += F(" Force B/G mode"); - } - addLogMove(LOG_LEVEL_INFO, log); - } - #endif - WiFi.setPhyMode(phyMode); - #endif // ifdef ESP8266 - - // Does not (yet) work, so commented out. - #ifdef ESP32 - - // HT20 = 20 MHz channel width. - // HT40 = 40 MHz channel width. - // In theory, HT40 can offer upto 150 Mbps connection speed. - // However since HT40 is using nearly all channels on 2.4 GHz WiFi, - // Thus you are more likely to experience disturbances. - // The response speed and stability is better at HT20 for ESP units. - esp_wifi_set_bandwidth(WIFI_IF_STA, WIFI_BW_HT20); - - uint8_t protocol = WIFI_PROTOCOL_11B | WIFI_PROTOCOL_11G; // Default to BG - - if (!Settings.ForceWiFi_bg_mode() || (WiFiEventData.connectionFailures > 10)) { - // Set to use BGN - protocol |= WIFI_PROTOCOL_11N; - #ifdef ESP32C6 - protocol |= WIFI_PROTOCOL_11AX; - #endif - } - - const WiFi_AP_Candidate candidate = WiFi_AP_Candidates.getCurrent(); - if (candidate.phy_known()) { - // Check to see if the access point is set to "N-only" - if ((protocol & WIFI_PROTOCOL_11N) == 0) { - if (!candidate.bits.phy_11b && !candidate.bits.phy_11g && candidate.bits.phy_11n) { - if (candidate.bits.phy_11n) { - // Set to use BGN - protocol |= WIFI_PROTOCOL_11N; - addLog(LOG_LEVEL_INFO, F("WIFI : AP is set to 802.11n only")); - } -#ifdef ESP32C6 - if (candidate.bits.phy_11ax) { - // Set to use WiFi6 - protocol |= WIFI_PROTOCOL_11AX; - addLog(LOG_LEVEL_INFO, F("WIFI : AP is set to 802.11ax")); - } -#endif - } - } - } - - - if (WifiIsSTA(WiFi.getMode())) { - // Set to use "Long GI" making it more resilliant to reflections - // See: https://www.tp-link.com/us/configuration-guides/q_a_basic_wireless_concepts/?configurationId=2958#_idTextAnchor038 - esp_wifi_config_80211_tx_rate(WIFI_IF_STA, WIFI_PHY_RATE_MCS3_LGI); - esp_wifi_set_protocol(WIFI_IF_STA, protocol); - } - - if (WifiIsAP(WiFi.getMode())) { - esp_wifi_set_protocol(WIFI_IF_AP, protocol); - } - #endif // ifdef ESP32 -} - -void setupStaticIPconfig() { - setUseStaticIP(WiFiUseStaticIP()); - - if (!WiFiUseStaticIP()) { return; } - const IPAddress ip (Settings.IP); - const IPAddress gw (Settings.Gateway); - const IPAddress subnet (Settings.Subnet); - const IPAddress dns (Settings.DNS); - - WiFiEventData.dns0_cache = dns; - - WiFi.config(ip, gw, subnet, dns); - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - addLogMove(LOG_LEVEL_INFO, strformat( - F("IP : Static IP : %s GW: %s SN: %s DNS: %s"), - formatIP(ip).c_str(), - formatIP(gw).c_str(), - formatIP(subnet).c_str(), - getValue(LabelType::DNS).c_str())); - } -} - -// ******************************************************************************** -// Formatting WiFi related strings -// ******************************************************************************** -String formatScanResult(int i, const String& separator) { - int32_t rssi = 0; - - return formatScanResult(i, separator, rssi); -} - -String formatScanResult(int i, const String& separator, int32_t& rssi) { - WiFi_AP_Candidate tmp(i); - rssi = tmp.rssi; - return tmp.toString(separator); -} - - -void logConnectionStatus() { - static unsigned long lastLog = 0; - if (lastLog != 0 && timePassedSince(lastLog) < 1000) { - return; - } - lastLog = millis(); -#ifndef BUILD_NO_DEBUG - #ifdef ESP8266 - const uint8_t arduino_corelib_wifistatus = WiFi.status(); - const uint8_t sdk_wifistatus = wifi_station_get_connect_status(); - - if ((arduino_corelib_wifistatus == WL_CONNECTED) != (sdk_wifistatus == STATION_GOT_IP)) { - if (loglevelActiveFor(LOG_LEVEL_ERROR)) { - String log = F("WiFi : SDK station status differs from Arduino status. SDK-status: "); - log += SDKwifiStatusToString(sdk_wifistatus); - log += F(" Arduino status: "); - log += ArduinoWifiStatusToString(arduino_corelib_wifistatus); - addLogMove(LOG_LEVEL_ERROR, log); - } - } - #endif - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - addLogMove(LOG_LEVEL_INFO, strformat( - F("WIFI : Arduino wifi status: %s ESPeasy internal wifi status: %s"), - ArduinoWifiStatusToString(WiFi.status()).c_str(), - WiFiEventData.ESPeasyWifiStatusToString().c_str())); - } -/* - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log; - - switch (WiFi.status()) { - case WL_NO_SSID_AVAIL: { - log = F("WIFI : No SSID found matching: "); - break; - } - case WL_CONNECT_FAILED: { - log = F("WIFI : Connection failed to: "); - break; - } - case WL_DISCONNECTED: { - log = F("WIFI : WiFi.status() = WL_DISCONNECTED SSID: "); - break; - } - case WL_IDLE_STATUS: { - log = F("WIFI : Connection in IDLE state: "); - break; - } - case WL_CONNECTED: { - break; - } - default: - break; - } - - if (log.length() > 0) { - const char *ssid = getLastWiFiSettingsSSID(); - log += ssid; - addLog(LOG_LEVEL_INFO, log); - } - } - */ -#endif // ifndef BUILD_NO_DEBUG -} +#include "../ESPEasyCore/ESPEasyWifi.h" + +#include "../../ESPEasy-Globals.h" +#include "../DataStructs/TimingStats.h" +#include "../ESPEasyCore/ESPEasyNetwork.h" +#include "../ESPEasyCore/ESPEasyWiFiEvent.h" +#include "../ESPEasyCore/ESPEasyWifi_ProcessEvent.h" +#include "../ESPEasyCore/ESPEasy_Log.h" +#include "../ESPEasyCore/Serial.h" +#include "../Globals/ESPEasyWiFiEvent.h" +#include "../Globals/EventQueue.h" +#include "../Globals/NetworkState.h" +#include "../Globals/Nodes.h" +#include "../Globals/RTC.h" +#include "../Globals/SecuritySettings.h" +#include "../Globals/Services.h" +#include "../Globals/Settings.h" +#include "../Globals/WiFi_AP_Candidates.h" +#include "../Helpers/ESPEasy_time_calc.h" +#include "../Helpers/Hardware_defines.h" +#include "../Helpers/Misc.h" +#include "../Helpers/Networking.h" +#include "../Helpers/StringConverter.h" +#include "../Helpers/StringGenerator_WiFi.h" +#include "../Helpers/StringProvider.h" + +#ifdef USES_ESPEASY_NOW +# include "../Globals/ESPEasy_now_handler.h" +#endif + +#ifdef ESP32 +#include +#include // Needed to call ESP-IDF functions like esp_wifi_.... + +#include +#endif + +// FIXME TD-er: Cleanup of WiFi code +#ifdef ESPEASY_WIFI_CLEANUP_WORK_IN_PROGRESS +bool ESPEasyWiFi_t::begin() { + return true; +} + +void ESPEasyWiFi_t::end() { + + +} + + +void ESPEasyWiFi_t::loop() { + switch (_state) { + case WiFiState_e::OFF: + break; + case WiFiState_e::AP_only: + break; + case WiFiState_e::ErrorRecovery: + // Wait for timeout to expire + // Start again from scratch + break; + case WiFiState_e::STA_Scanning: + case WiFiState_e::STA_AP_Scanning: + // Check if scanning is finished + // When scanning per channel, call for scanning next channel + break; + case WiFiState_e::STA_Connecting: + case WiFiState_e::STA_Reconnecting: + // Check if (re)connecting has finished + break; + case WiFiState_e::STA_Connected: + // Check if still connected + // Reconnect if not. + // Else mark last timestamp seen as connected + break; + } + + + { + // Check if we need to start AP + // Flag captive portal in webserver and/or whether we might be in setup mode + } + +#ifdef USE_IMPROV + { + // Check for Improv mode. + } +#endif + + +} + + +IPAddress ESPEasyWiFi_t::getIP() const { + + IPAddress res; + + + return res; +} + +void ESPEasyWiFi_t::disconnect() { + +} + + +void ESPEasyWiFi_t::checkConnectProgress() { + +} + +void ESPEasyWiFi_t::startScanning() { + _state = WiFiState_e::STA_Scanning; + WifiScan(true); + _last_state_change.setNow(); +} + + +bool ESPEasyWiFi_t::connectSTA() { + if (!WiFi_AP_Candidates.hasCandidateCredentials()) { + if (!WiFiEventData.warnedNoValidWiFiSettings) { + addLog(LOG_LEVEL_ERROR, F("WIFI : No valid wifi settings")); + WiFiEventData.warnedNoValidWiFiSettings = true; + } + WiFiEventData.last_wifi_connect_attempt_moment.clear(); + WiFiEventData.wifi_connect_attempt = 1; + WiFiEventData.wifiConnectAttemptNeeded = false; + + // No need to wait longer to start AP mode. + if (!Settings.DoNotStartAP()) { + setAP(true); + } + return false; + } + WiFiEventData.warnedNoValidWiFiSettings = false; + setSTA(true); + #if defined(ESP8266) + wifi_station_set_hostname(NetworkCreateRFCCompliantHostname().c_str()); + + #endif // if defined(ESP8266) + #if defined(ESP32) + WiFi.config(INADDR_NONE, INADDR_NONE, INADDR_NONE); + #endif // if defined(ESP32) + setConnectionSpeed(); + setupStaticIPconfig(); + + + + // Start the process of connecting or starting AP + if (WiFi_AP_Candidates.getNext(true)) { + // Try to connect to AP + + } else { + // No (known) AP, start scanning + startScanning(); + } + + + return true; +} + +#endif // ESPEASY_WIFI_CLEANUP_WORK_IN_PROGRESS + + +// ******************************************************************************** +// WiFi state +// ******************************************************************************** + +/* + WiFi STA states: + 1 STA off => ESPEASY_WIFI_DISCONNECTED + 2 STA connecting + 3 STA connected => ESPEASY_WIFI_CONNECTED + 4 STA got IP => ESPEASY_WIFI_GOT_IP + 5 STA connected && got IP => ESPEASY_WIFI_SERVICES_INITIALIZED + + N.B. the states are flags, meaning both "connected" and "got IP" must be set + to be considered ESPEASY_WIFI_SERVICES_INITIALIZED + + The flag wifiConnectAttemptNeeded indicates whether a new connect attempt is needed. + This is set to true when: + - Security settings have been saved with AP mode enabled. FIXME TD-er, this may not be the best check. + - WiFi connect timeout reached & No client is connected to the AP mode of the node. + - Wifi is reset + - WiFi setup page has been loaded with SSID/pass values. + + + WiFi AP mode states: + 1 AP on => reset AP disable timer + 2 AP client connect/disconnect => reset AP disable timer + 3 AP off => AP disable timer = 0; + + AP mode will be disabled when both apply: + - AP disable timer (timerAPoff) expired + - No client is connected to the AP. + + AP mode will be enabled when at least one applies: + - No valid WiFi settings + - Start AP timer (timerAPstart) expired + + Start AP timer is set or cleared at: + - Set timerAPstart when "valid WiFi connection" state is observed. + - Disable timerAPstart when ESPEASY_WIFI_SERVICES_INITIALIZED wifi state is reached. + + For the first attempt to connect after a cold boot (RTC values are 0), a WiFi scan will be + performed to find the strongest known SSID. + This will set RTC.lastBSSID and RTC.lastWiFiChannel + + Quick reconnect (using BSSID/channel of last connection) when both apply: + - If wifi_connect_attempt < 3 + - RTC.lastBSSID is known + - RTC.lastWiFiChannel != 0 + + Change of wifi settings when both apply: + - "other" settings valid + - (wifi_connect_attempt % 2) == 0 + + Reset of wifi_connect_attempt to 0 when both apply: + - connection successful + - Connection stable (connected for > 5 minutes) + + */ + + +// ******************************************************************************** +// Check WiFi connected status +// This is basically the state machine to switch between states: +// - Initiate WiFi reconnect +// - Start/stop of AP mode +// ******************************************************************************** +bool WiFiConnected() { + START_TIMER; + + static bool recursiveCall = false; + + static uint32_t lastCheckedTime = 0; + static bool lastState = false; + +#if FEATURE_USE_IPV6 + if (!WiFiEventData.processedGotIP6) { + processGotIPv6(); + } +#endif + + if (!WifiIsSTA(WiFi.getMode())) { + lastState = false; + return lastState; + } + + + const int32_t timePassed = timePassedSince(lastCheckedTime); + if (lastCheckedTime != 0) { + if (timePassed < 100) { + if (WiFiEventData.lastDisconnectMoment.isSet() && + WiFiEventData.lastDisconnectMoment.millisPassedSince() > timePassed) + { + // Try to rate-limit the nr of calls to this function or else it will be called 1000's of times a second. + return lastState; + } + } + if (timePassed < 10) { + // Rate limit time spent in WiFiConnected() to max. 100x per sec to process the rest of this function + return lastState; + } + } + + + + if (WiFiEventData.unprocessedWifiEvents()) { return false; } + + bool wifi_isconnected = WiFi.isConnected(); + #ifdef ESP8266 + // Perform check on SDK function, see: https://github.com/esp8266/Arduino/issues/7432 + station_status_t status = wifi_station_get_connect_status(); + switch(status) { + case STATION_GOT_IP: + wifi_isconnected = true; + break; + case STATION_NO_AP_FOUND: + case STATION_CONNECT_FAIL: + case STATION_WRONG_PASSWORD: + wifi_isconnected = false; + break; + case STATION_IDLE: + case STATION_CONNECTING: + break; + + default: + wifi_isconnected = false; + break; + } + #endif + + if (recursiveCall) return wifi_isconnected; + recursiveCall = true; + + + // For ESP82xx, do not rely on WiFi.status() with event based wifi. + const int32_t wifi_rssi = WiFi.RSSI(); + bool validWiFi = (wifi_rssi < 0) && wifi_isconnected && hasIPaddr(); + /* + if (validWiFi && WiFi.channel() != WiFiEventData.usedChannel) { + validWiFi = false; + } + */ + if (validWiFi != WiFiEventData.WiFiServicesInitialized()) { + // else wifiStatus is no longer in sync. + if (checkAndResetWiFi()) { + // Wifi has been reset, so no longer valid WiFi + validWiFi = false; + } + } + + if (validWiFi) { + // Connected, thus disable any timer to start AP mode. (except when in WiFi setup mode) + if (!WiFiEventData.wifiSetupConnect) { + WiFiEventData.timerAPstart.clear(); + } + STOP_TIMER(WIFI_ISCONNECTED_STATS); + recursiveCall = false; + // Only return true after some time since it got connected. +#if FEATURE_SET_WIFI_TX_PWR + SetWiFiTXpower(); +#endif + lastState = WiFiEventData.wifi_considered_stable || WiFiEventData.lastConnectMoment.timeoutReached(100); + lastCheckedTime = millis(); + return lastState; + } + + if ((WiFiEventData.timerAPstart.isSet()) && WiFiEventData.timerAPstart.timeReached()) { + if (WiFiEventData.timerAPoff.isSet() && !WiFiEventData.timerAPoff.timeReached()) { + if (!Settings.DoNotStartAP()) { + // Timer reached, so enable AP mode. + if (!WifiIsAP(WiFi.getMode())) { + if (!WiFiEventData.wifiConnectAttemptNeeded) { + #ifndef BUILD_MINIMAL_OTA + addLog(LOG_LEVEL_INFO, F("WiFi : WiFiConnected(), start AP")); + #endif + WifiScan(false); + setSTA(false); // Force reset WiFi + reduce power consumption + setAP(true); + } + } + } + } else { + WiFiEventData.timerAPstart.clear(); + WiFiEventData.timerAPoff.clear(); + } + } + + + // When made this far in the code, we apparently do not have valid WiFi connection. + if (!WiFiEventData.timerAPstart.isSet() && !WifiIsAP(WiFi.getMode())) { + // First run we do not have WiFi connection any more, set timer to start AP mode + // Only allow the automatic AP mode in the first N minutes after boot. + if (getUptimeMinutes() < WIFI_ALLOW_AP_AFTERBOOT_PERIOD) { + WiFiEventData.timerAPstart.setMillisFromNow(WIFI_RECONNECT_WAIT); + // Fixme TD-er: Make this more elegant as it now needs to know about the extra time needed for the AP start timer. + WiFiEventData.timerAPoff.setMillisFromNow(WIFI_RECONNECT_WAIT + WIFI_AP_OFF_TIMER_DURATION); + } + } + + const bool timeoutReached = WiFiEventData.last_wifi_connect_attempt_moment.isSet() && + WiFiEventData.last_wifi_connect_attempt_moment.timeoutReached(2 * DEFAULT_WIFI_CONNECTION_TIMEOUT); + + if (timeoutReached && !WiFiEventData.wifiSetup) { + // It took too long to make a connection, set flag we need to try again + //if (!wifiAPmodeActivelyUsed()) { + WiFiEventData.wifiConnectAttemptNeeded = true; + //} + WiFiEventData.wifiConnectInProgress = false; + if (!WiFiEventData.WiFiDisconnected()) { + # ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_INFO, F("WiFi : wifiConnectTimeoutReached")); + #endif + WifiDisconnect(); + } + } + delay(0); + STOP_TIMER(WIFI_NOTCONNECTED_STATS); + recursiveCall = false; + return false; +} + +void WiFiConnectRelaxed() { + if (!WiFiEventData.processedDisconnect) { + processDisconnect(); + } + if (!WiFiEventData.WiFiConnectAllowed() || WiFiEventData.wifiConnectInProgress) { + if (WiFiEventData.wifiConnectInProgress) { + if (WiFiEventData.last_wifi_connect_attempt_moment.isSet()) { + if (WiFiEventData.last_wifi_connect_attempt_moment.timeoutReached(WIFI_PROCESS_EVENTS_TIMEOUT)) { + WiFiEventData.wifiConnectInProgress = false; + } + } + } + + if (WiFiEventData.wifiConnectInProgress) { + return; // already connected or connect attempt in progress need to disconnect first + } + } + if (!WiFiEventData.processedScanDone) { + // Scan is still active, so do not yet connect. + return; + } + + // FIXME TD-er: Still needed to process disconnect here? + if (!WiFiEventData.processedDisconnect) { + processDisconnect(); + } + + if (WiFiEventData.unprocessedWifiEvents()) { + # ifndef BUILD_NO_DEBUG + if (loglevelActiveFor(LOG_LEVEL_ERROR)) { + String log = F("WiFi : Connecting not possible, unprocessed WiFi events: "); + if (!WiFiEventData.processedConnect) { + log += F(" conn"); + } + if (!WiFiEventData.processedDisconnect) { + log += F(" disconn"); + } + if (!WiFiEventData.processedGotIP) { + log += F(" gotIP"); + } +#if FEATURE_USE_IPV6 + if (!WiFiEventData.processedGotIP6) { + log += F(" gotIP6"); + } +#endif + + if (!WiFiEventData.processedDHCPTimeout) { + log += F(" DHCP_t/o"); + } + + addLogMove(LOG_LEVEL_ERROR, log); + logConnectionStatus(); + } + #endif + return; + } + + if (!WiFiEventData.wifiSetupConnect && wifiAPmodeActivelyUsed()) { + return; + } + + + // FIXME TD-er: Should not try to prepare when a scan is still busy. + // This is a logic error which may lead to strange issues if some kind of timeout happens and/or RF calibration was not OK. + // Split this function into separate parts, with the last part being the actual connect attempt either after a scan is complete or quick connect is possible. + + AttemptWiFiConnect(); +} + +void AttemptWiFiConnect() { + if (!WiFiEventData.wifiConnectAttemptNeeded) { + return; + } + + if (WiFiEventData.wifiConnectInProgress) { + return; + } + + setNetworkMedium(NetworkMedium_t::WIFI); + if (active_network_medium != NetworkMedium_t::WIFI + #ifdef USES_ESPEASY_NOW + && active_network_medium != NetworkMedium_t::ESPEasyNOW_only + #endif + ) + { + return; + } + + + if (WiFiEventData.wifiSetupConnect) { + // wifiSetupConnect is when run from the setup page. + RTC.clearLastWiFi(); // Force slow connect + WiFiEventData.wifi_connect_attempt = 0; + WiFiEventData.wifiSetupConnect = false; + if (WiFiEventData.timerAPoff.isSet()) { + WiFiEventData.timerAPoff.setMillisFromNow(WIFI_RECONNECT_WAIT + WIFI_AP_OFF_TIMER_DURATION); + } + } + + if (WiFiEventData.last_wifi_connect_attempt_moment.isSet()) { + if (!WiFiEventData.last_wifi_connect_attempt_moment.timeoutReached(DEFAULT_WIFI_CONNECTION_TIMEOUT)) { + return; + } + } + + if (WiFiEventData.unprocessedWifiEvents()) { + return; + } + setSTA(false); + + setSTA(true); + + if (WiFi_AP_Candidates.getNext(WiFiScanAllowed())) { + const WiFi_AP_Candidate candidate = WiFi_AP_Candidates.getCurrent(); + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLogMove(LOG_LEVEL_INFO, strformat( + F("WIFI : Connecting %s attempt #%u"), + candidate.toString().c_str(), + WiFiEventData.wifi_connect_attempt)); + } + WiFiEventData.markWiFiBegin(); + if (prepareWiFi()) { + setNetworkMedium(NetworkMedium_t::WIFI); + RTC.clearLastWiFi(); + RTC.lastWiFiSettingsIndex = candidate.index; + + float tx_pwr = 0; // Will be set higher based on RSSI when needed. + // FIXME TD-er: Must check WiFiEventData.wifi_connect_attempt to increase TX power +#if FEATURE_SET_WIFI_TX_PWR + if (Settings.UseMaxTXpowerForSending()) { + tx_pwr = Settings.getWiFi_TX_power(); + } + SetWiFiTXpower(tx_pwr, candidate.rssi); +#endif + // Start connect attempt now, so no longer needed to attempt new connection. + WiFiEventData.wifiConnectAttemptNeeded = false; + WiFiEventData.wifiConnectInProgress = true; + const String key = WiFi_AP_CandidatesList::get_key(candidate.index); + +#if FEATURE_USE_IPV6 + if (Settings.EnableIPv6()) { + WiFi.enableIPv6(true); + } +#endif + +#ifdef ESP32 + if (Settings.IncludeHiddenSSID()) { + wifi_country_t config = { + .cc = "01", + .schan = 1, + .nchan = 14, + .policy = WIFI_COUNTRY_POLICY_MANUAL, + }; + esp_wifi_set_country(&config); + } +#endif + + + if ((Settings.HiddenSSID_SlowConnectPerBSSID() || !candidate.bits.isHidden) + && candidate.allowQuickConnect()) { + WiFi.begin(candidate.ssid.c_str(), key.c_str(), candidate.channel, candidate.bssid.mac); + } else { + WiFi.begin(candidate.ssid.c_str(), key.c_str()); + } +#ifdef ESP32 + // Always wait for a second on ESP32 + WiFi.waitForConnectResult(1000); // https://github.com/arendst/Tasmota/issues/14985 +#else + if (Settings.WaitWiFiConnect() || candidate.bits.isHidden) { +// WiFi.waitForConnectResult(candidate.isHidden ? 3000 : 1000); // https://github.com/arendst/Tasmota/issues/14985 + WiFi.waitForConnectResult(1000); // https://github.com/arendst/Tasmota/issues/14985 + } +#endif + delay(1); + } else { + WiFiEventData.wifiConnectInProgress = false; + } + } else { + if (!wifiAPmodeActivelyUsed() || WiFiEventData.wifiSetupConnect) { + if (!prepareWiFi()) { + //return; + } + + if (WiFiScanAllowed()) { + // Maybe not scan async to give the ESP some slack in power consumption? + const bool async = false; + WifiScan(async); + } + // Limit nr of attempts as we don't have any AP candidates. + WiFiEventData.last_wifi_connect_attempt_moment.setMillisFromNow(60000); + WiFiEventData.timerAPstart.setNow(); + } else { +#ifdef USES_ESPEASY_NOW + if (!WiFi_AP_Candidates.addedKnownCandidate()) { + setNetworkMedium(NetworkMedium_t::ESPEasyNOW_only); + } +#endif + } + } + + logConnectionStatus(); +} + +// ******************************************************************************** +// Set Wifi config +// ******************************************************************************** +bool prepareWiFi() { + #if defined(ESP32) + registerWiFiEventHandler(); + #endif + + if (!WiFi_AP_Candidates.hasCandidateCredentials()) { + if (!WiFiEventData.warnedNoValidWiFiSettings) { + addLog(LOG_LEVEL_ERROR, F("WIFI : No valid wifi settings")); + WiFiEventData.warnedNoValidWiFiSettings = true; + } +// WiFiEventData.last_wifi_connect_attempt_moment.clear(); + WiFiEventData.wifi_connect_attempt = 1; + WiFiEventData.wifiConnectAttemptNeeded = false; + + // No need to wait longer to start AP mode. + if (!Settings.DoNotStartAP()) { + WifiScan(false); +// setAP(true); + } + return false; + } + WiFiEventData.warnedNoValidWiFiSettings = false; + #ifdef USES_ESPEASY_NOW + if (Settings.UseESPEasyNow()) { + #ifdef ESP32 + temp_disable_EspEasy_now_timer = millis() + WIFI_RECONNECT_WAIT; + ESPEasy_now_handler.end(); + setSTA(true); + #else + setWifiMode(WIFI_AP_STA); + #endif + +// setWifiMode(WIFI_AP_STA); + } else { + setSTA(true); + } + #else + setSTA(true); + #endif + + #if defined(ESP8266) + wifi_station_set_hostname(NetworkCreateRFCCompliantHostname().c_str()); + + #endif // if defined(ESP8266) + #if defined(ESP32) + WiFi.config(INADDR_NONE, INADDR_NONE, INADDR_NONE); + #endif // if defined(ESP32) + setConnectionSpeed(); + setupStaticIPconfig(); + WiFiEventData.wifiConnectAttemptNeeded = true; + + return true; +} + +bool checkAndResetWiFi() { + #ifdef ESP8266 + station_status_t status = wifi_station_get_connect_status(); + + switch(status) { + case STATION_GOT_IP: + if (WiFi.RSSI() < 0 && WiFi.localIP().isSet()) { + //if (WiFi.channel() == WiFiEventData.usedChannel || WiFiEventData.usedChannel == 0) { + // This is a valid status, no need to reset + if (!WiFiEventData.WiFiServicesInitialized()) { + WiFiEventData.setWiFiServicesInitialized(); + setNetworkMedium(NetworkMedium_t::WIFI); + } + + return false; + //} + } + break; + case STATION_NO_AP_FOUND: + case STATION_CONNECT_FAIL: + case STATION_WRONG_PASSWORD: + // Reason to reset WiFi + break; + case STATION_IDLE: + case STATION_CONNECTING: + if (WiFiEventData.last_wifi_connect_attempt_moment.isSet() && !WiFiEventData.last_wifi_connect_attempt_moment.timeoutReached(DEFAULT_WIFI_CONNECTION_TIMEOUT)) { + return false; + } + break; + } + #endif + #ifdef ESP32 + if (WiFi.isConnected()) { + //if (WiFi.channel() == WiFiEventData.usedChannel || WiFiEventData.usedChannel == 0) { + return false; + //} + } + if (WiFiEventData.last_wifi_connect_attempt_moment.isSet() && !WiFiEventData.last_wifi_connect_attempt_moment.timeoutReached(DEFAULT_WIFI_CONNECTION_TIMEOUT)) { + return false; + } + #endif + # ifndef BUILD_NO_DEBUG + String log = F("WiFi : WiFiConnected() out of sync: "); + log += WiFiEventData.ESPeasyWifiStatusToString(); + log += F(" RSSI: "); + log += String(WiFi.RSSI()); + #ifdef ESP8266 + log += F(" status: "); + log += SDKwifiStatusToString(status); + #endif + #endif + + // Call for reset first, to make sure a syslog call will not try to send. + resetWiFi(); + # ifndef BUILD_NO_DEBUG + addLogMove(LOG_LEVEL_INFO, log); + #endif + return true; +} + + +void resetWiFi() { + //if (wifiAPmodeActivelyUsed()) return; + if (WiFiEventData.lastWiFiResetMoment.isSet() && !WiFiEventData.lastWiFiResetMoment.timeoutReached(1000)) { + // Don't reset WiFi too often + return; + } + FeedSW_watchdog(); + WiFiEventData.clearAll(); + WiFi_AP_Candidates.force_reload(); + WifiDisconnect(); + + // Send this log only after WifiDisconnect() or else sending to syslog may cause issues + #ifndef BUILD_MINIMAL_OTA + addLog(LOG_LEVEL_INFO, F("Reset WiFi.")); + #endif + + // setWifiMode(WIFI_OFF); + + initWiFi(); +} + +#ifdef ESP32 +void removeWiFiEventHandler() +{ + WiFi.removeEvent(WiFiEventData.wm_event_id); + WiFiEventData.wm_event_id = 0; +} + +void registerWiFiEventHandler() +{ + if (WiFiEventData.wm_event_id != 0) { + removeWiFiEventHandler(); + } + WiFiEventData.wm_event_id = WiFi.onEvent(WiFiEvent); +} +#endif + + +void initWiFi() +{ +#ifdef USES_ESPEASY_NOW + ESPEasy_now_handler.end(); +#endif +#ifdef ESP8266 + + // See https://github.com/esp8266/Arduino/issues/5527#issuecomment-460537616 + // FIXME TD-er: Do not destruct WiFi object, it may cause crashes with queued UDP traffic. +// WiFi.~ESP8266WiFiClass(); +// WiFi = ESP8266WiFiClass(); +#endif // ifdef ESP8266 +#ifdef ESP32 + removeWiFiEventHandler(); +#endif + + + WiFi.persistent(false); // Do not use SDK storage of SSID/WPA parameters + // The WiFi.disconnect() ensures that the WiFi is working correctly. If this is not done before receiving WiFi connections, + // those WiFi connections will take a long time to make or sometimes will not work at all. + WiFi.disconnect(false); + delay(1); + if (active_network_medium != NetworkMedium_t::NotSet) { + setSTA(true); + WifiScan(false); + } + setWifiMode(WIFI_OFF); + +#if defined(ESP32) + registerWiFiEventHandler(); +#endif +#ifdef ESP8266 + // WiFi event handlers + static bool handlers_initialized = false; + if (!handlers_initialized) { + stationConnectedHandler = WiFi.onStationModeConnected(onConnected); + stationDisconnectedHandler = WiFi.onStationModeDisconnected(onDisconnect); + stationGotIpHandler = WiFi.onStationModeGotIP(onGotIP); + stationModeDHCPTimeoutHandler = WiFi.onStationModeDHCPTimeout(onDHCPTimeout); + stationModeAuthModeChangeHandler = WiFi.onStationModeAuthModeChanged(onStationModeAuthModeChanged); + APModeStationConnectedHandler = WiFi.onSoftAPModeStationConnected(onConnectedAPmode); + APModeStationDisconnectedHandler = WiFi.onSoftAPModeStationDisconnected(onDisconnectedAPmode); + #ifdef USES_ESPEASY_NOW + APModeProbeRequestReceivedHandler = WiFi.onSoftAPModeProbeRequestReceived(onProbeRequestAPmode); + #endif + handlers_initialized = true; + } +#endif + delay(100); +} + +// ******************************************************************************** +// Configure WiFi TX power +// ******************************************************************************** +#if FEATURE_SET_WIFI_TX_PWR +void SetWiFiTXpower() { + if (Settings.UseESPEasyNow()) { + // Set at max power for use with ESPEasy-NOW. + SetWiFiTXpower(30, -99); + } else { + SetWiFiTXpower(0); // Just some minimal value, will be adjusted in SetWiFiTXpower + } +} + +void SetWiFiTXpower(float dBm) { + SetWiFiTXpower(dBm, WiFi.RSSI()); +} + +void SetWiFiTXpower(float dBm, float rssi) { + /* + const WiFiMode_t cur_mode = WiFi.getMode(); + if (cur_mode == WIFI_OFF) { + return; + } + + if (Settings.UseESPEasyNow()) { + // Do not mess with WiFi TX power when ESPEasy-now is used. + return; + } + + // Range ESP32 : -1dBm - 20dBm + // Range ESP8266: 0dBm - 20.5dBm + float maxTXpwr; + float threshold = GetRSSIthreshold(maxTXpwr); + #ifdef ESP8266 + float minTXpwr{}; + #endif + #ifdef ESP32 + float minTXpwr = -1.0f; + #endif + + threshold += Settings.WiFi_sensitivity_margin; // Margin in dBm on top of threshold + + // Assume AP sends with max set by ETSI standard. + // 2.4 GHz: 100 mWatt (20 dBm) + // US and some other countries allow 1000 mW (30 dBm) + // We cannot send with over 20 dBm, thus it makes no sense to force higher TX power all the time. + const float newrssi = rssi - 20; + if (newrssi < threshold) { + minTXpwr = threshold - newrssi; + } + if (minTXpwr > maxTXpwr) { + minTXpwr = maxTXpwr; + } + if (dBm > maxTXpwr) { + dBm = maxTXpwr; + } else if (dBm < minTXpwr) { + dBm = minTXpwr; + } + + #ifdef ESP32 + int8_t power = dBm * 4; + if (esp_wifi_set_max_tx_power(power) == ESP_OK) { + if (esp_wifi_get_max_tx_power(&power) == ESP_OK) { + dBm = static_cast(power) / 4.0f; + } + } + #endif + + #ifdef ESP8266 + WiFi.setOutputPower(dBm); + #endif + + if (WiFiEventData.wifi_TX_pwr < dBm) { + // Will increase the TX power, give power supply of the unit some rest + delay(1); + } + + WiFiEventData.wifi_TX_pwr = dBm; + + delay(0); + #ifndef BUILD_NO_DEBUG + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + const int TX_pwr_int = WiFiEventData.wifi_TX_pwr * 4; + const int maxTXpwr_int = maxTXpwr * 4; + if (TX_pwr_int != maxTXpwr_int) { + static int last_log = -1; + if (TX_pwr_int != last_log) { + last_log = TX_pwr_int; + String log = strformat( + F("WiFi : Set TX power to %ddBm sensitivity: %ddBm"), + static_cast(dBm), + static_cast(threshold)); + if (rssi < 0) { + log += strformat(F(" RSSI: %ddBm"), static_cast(rssi)); + } + addLogMove(LOG_LEVEL_DEBUG, log); + } + } + } + #endif + */ +} +#endif + + + + +float GetRSSIthreshold(float& maxTXpwr) { + maxTXpwr = Settings.getWiFi_TX_power(); + float threshold = WIFI_SENSITIVITY_n; + switch (getConnectionProtocol()) { + case WiFiConnectionProtocol::WiFi_Protocol_11b: + threshold = WIFI_SENSITIVITY_11b; + if (maxTXpwr > MAX_TX_PWR_DBM_11b) maxTXpwr = MAX_TX_PWR_DBM_11b; + break; + case WiFiConnectionProtocol::WiFi_Protocol_11g: + threshold = WIFI_SENSITIVITY_54g; + if (maxTXpwr > MAX_TX_PWR_DBM_54g) maxTXpwr = MAX_TX_PWR_DBM_54g; + break; +#ifdef ESP8266 + case WiFiConnectionProtocol::WiFi_Protocol_11n: +#else + case WiFiConnectionProtocol::WiFi_Protocol_HT20: + case WiFiConnectionProtocol::WiFi_Protocol_HT40: + case WiFiConnectionProtocol::WiFi_Protocol_HE20: +#endif + + threshold = WIFI_SENSITIVITY_n; + if (maxTXpwr > MAX_TX_PWR_DBM_n) maxTXpwr = MAX_TX_PWR_DBM_n; + break; +#ifdef ESP32 +#if ESP_IDF_VERSION > ESP_IDF_VERSION_VAL(5, 2, 0) + case WiFiConnectionProtocol::WiFi_Protocol_11a: + case WiFiConnectionProtocol::WiFi_Protocol_VHT20: + // FIXME TD-er: Must determine max. TX power for these 5 GHz modi +#endif + case WiFiConnectionProtocol::WiFi_Protocol_LR: +#endif + case WiFiConnectionProtocol::Unknown: + break; + } + return threshold; +} + +int GetRSSI_quality() { + long rssi = WiFi.RSSI(); + + if (-50 < rssi) { return 10; } + + if (rssi <= -98) { return 0; } + rssi = rssi + 97; // Range 0..47 => 1..9 + return (rssi / 5) + 1; +} + +WiFiConnectionProtocol getConnectionProtocol() { + if (WiFi.RSSI() < 0) { + #ifdef ESP8266 + switch (wifi_get_phy_mode()) { + case PHY_MODE_11B: + return WiFiConnectionProtocol::WiFi_Protocol_11b; + case PHY_MODE_11G: + return WiFiConnectionProtocol::WiFi_Protocol_11g; + case PHY_MODE_11N: + return WiFiConnectionProtocol::WiFi_Protocol_11n; + } + #endif + #ifdef ESP32 + + wifi_phy_mode_t phymode; + esp_wifi_sta_get_negotiated_phymode(&phymode); + switch (phymode) { + case WIFI_PHY_MODE_11B: return WiFiConnectionProtocol::WiFi_Protocol_11b; + case WIFI_PHY_MODE_11G: return WiFiConnectionProtocol::WiFi_Protocol_11g; + case WIFI_PHY_MODE_HT20: return WiFiConnectionProtocol::WiFi_Protocol_HT20; + case WIFI_PHY_MODE_HT40: return WiFiConnectionProtocol::WiFi_Protocol_HT40; + case WIFI_PHY_MODE_HE20: return WiFiConnectionProtocol::WiFi_Protocol_HE20; + case WIFI_PHY_MODE_LR: return WiFiConnectionProtocol::WiFi_Protocol_LR; +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0) + // 5 GHz + case WIFI_PHY_MODE_11A: return WiFiConnectionProtocol::WiFi_Protocol_11a; + case WIFI_PHY_MODE_VHT20: return WiFiConnectionProtocol::WiFi_Protocol_VHT20; +#endif + } + #endif + } + return WiFiConnectionProtocol::Unknown; +} + +#ifdef ESP32 +int64_t WiFi_get_TSF_time() +{ + return esp_wifi_get_tsf_time(WIFI_IF_STA); +} +#endif + + +// ******************************************************************************** +// Disconnect from Wifi AP +// ******************************************************************************** +void WifiDisconnect() +{ + if (!WiFiEventData.processedDisconnect || + WiFiEventData.processingDisconnect.isSet()) { + return; + } + if (WiFi.status() == WL_DISCONNECTED) { + return; + } + // Prevent recursion + static LongTermTimer processingDisconnectTimer; + if (processingDisconnectTimer.isSet() && + !processingDisconnectTimer.timeoutReached(200)) return; + processingDisconnectTimer.setNow(); + # ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_INFO, F("WiFi : WifiDisconnect()")); + #endif + #ifdef ESP32 + removeWiFiEventHandler(); + WiFi.disconnect(); + delay(100); + { + const IPAddress ip; + const IPAddress gw; + const IPAddress subnet; + const IPAddress dns; + WiFi.config(ip, gw, subnet, dns); + } + #endif + #ifdef ESP8266 + // Only call disconnect when STA is active + if (WifiIsSTA(WiFi.getMode())) { + wifi_station_disconnect(); + } + station_config conf{}; + memset(&conf, 0, sizeof(conf)); + ETS_UART_INTR_DISABLE(); + wifi_station_set_config_current(&conf); + ETS_UART_INTR_ENABLE(); + #endif + WiFiEventData.setWiFiDisconnected(); + WiFiEventData.markDisconnect(WIFI_DISCONNECT_REASON_UNSPECIFIED); + /* + if (!Settings.UseLastWiFiFromRTC()) { + RTC.clearLastWiFi(); + } + */ + delay(100); + WiFiEventData.processingDisconnect.clear(); + WiFiEventData.processedDisconnect = false; + processDisconnect(); + processingDisconnectTimer.clear(); +} + +// ******************************************************************************** +// Scan WiFi network +// ******************************************************************************** +bool WiFiScanAllowed() { + if (WiFi_AP_Candidates.scanComplete() == WIFI_SCAN_RUNNING) { + return false; + } + if (!WiFiEventData.processedScanDone) { + processScanDone(); + } + if (!WiFiEventData.processedDisconnect) { + processDisconnect(); + } + + if (WiFiEventData.wifiConnectInProgress) { + return false; + } + + if (WiFiEventData.intent_to_reboot) { + return false; + } + + if (WiFiEventData.unprocessedWifiEvents()) { + # ifndef BUILD_NO_DEBUG + if (loglevelActiveFor(LOG_LEVEL_ERROR)) { + String log = F("WiFi : Scan not allowed, unprocessed WiFi events: "); + if (!WiFiEventData.processedConnect) { + log += F(" conn"); + } + if (!WiFiEventData.processedDisconnect) { + log += F(" disconn"); + } + if (!WiFiEventData.processedGotIP) { + log += F(" gotIP"); + } + if (!WiFiEventData.processedDHCPTimeout) { + log += F(" DHCP_t/o"); + } + + addLogMove(LOG_LEVEL_ERROR, log); + logConnectionStatus(); + } + #endif + return false; + } + /* + if (!wifiAPmodeActivelyUsed() && !NetworkConnected()) { + return true; + } + */ + WiFi_AP_Candidates.purge_expired(); + if (WiFiEventData.wifiConnectInProgress) { + return false; + } + if (WiFiEventData.lastScanMoment.isSet()) { + if (NetworkConnected() && WiFi_AP_Candidates.getBestCandidate().usable()) { + # ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_ERROR, F("WiFi : Scan not needed, good candidate present")); + #endif + return false; + } + } + + if (WiFiEventData.lastDisconnectMoment.isSet() && WiFiEventData.lastDisconnectMoment.millisPassedSince() < WIFI_RECONNECT_WAIT) { + if (!NetworkConnected()) { + return WiFiEventData.processedConnect; + } + } + if (WiFiEventData.lastScanMoment.isSet()) { + const LongTermTimer::Duration scanInterval = wifiAPmodeActivelyUsed() ? WIFI_SCAN_INTERVAL_AP_USED : WIFI_SCAN_INTERVAL_MINIMAL; + if (WiFiEventData.lastScanMoment.millisPassedSince() < scanInterval) { + return false; + } + } + return WiFiEventData.processedConnect; +} + + +void WifiScan(bool async, uint8_t channel) { + setSTA(true); + if (!WiFiScanAllowed()) { + return; + } +#ifdef ESP32 + // TD-er: Don't run async scan on ESP32. + // Since IDF 4.4 it seems like the active channel may be messed up when running async scan + // Perform a disconnect after scanning. + // See: https://github.com/letscontrolit/ESPEasy/pull/3579#issuecomment-967021347 + async = false; + + if (Settings.IncludeHiddenSSID()) { + wifi_country_t config = { + .cc = "01", + .schan = 1, + .nchan = 14, + .policy = WIFI_COUNTRY_POLICY_MANUAL, + }; + esp_wifi_set_country(&config); + } + + +#endif + + START_TIMER; + WiFiEventData.lastScanMoment.setNow(); + # ifndef BUILD_NO_DEBUG + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + if (channel == 0) { + addLog(LOG_LEVEL_INFO, F("WiFi : Start network scan all channels")); + } else { + addLogMove(LOG_LEVEL_INFO, strformat(F("WiFi : Start network scan ch: %d "), channel)); + } + } + #endif + bool show_hidden = true; + WiFiEventData.processedScanDone = false; + WiFiEventData.lastGetScanMoment.setNow(); + WiFiEventData.lastScanChannel = channel; + + unsigned int nrScans = 1 + (async ? 0 : Settings.NumberExtraWiFiScans); + while (nrScans > 0) { + if (!async) { + WiFi_AP_Candidates.begin_sync_scan(); + FeedSW_watchdog(); + } + --nrScans; +#ifdef ESP8266 +#if FEATURE_ESP8266_DIRECT_WIFI_SCAN + { + static bool FIRST_SCAN = true; + + struct scan_config config; + memset(&config, 0, sizeof(config)); + config.ssid = nullptr; + config.bssid = nullptr; + config.channel = channel; + config.show_hidden = show_hidden ? 1 : 0;; + config.scan_type = WIFI_SCAN_TYPE_ACTIVE; + if (FIRST_SCAN) { + config.scan_time.active.min = 100; + config.scan_time.active.max = 200; + } else { + config.scan_time.active.min = 400; + config.scan_time.active.max = 500; + } + FIRST_SCAN = false; + wifi_station_scan(&config, &onWiFiScanDone); + if (!async) { + // will resume when SYSTEM_EVENT_SCAN_DONE event is fired + do { + delay(0); + } while (!WiFiEventData.processedScanDone); + } + + } +#else + WiFi.scanNetworks(async, show_hidden, channel); +#endif +#endif +#ifdef ESP32 + const bool passive = Settings.PassiveWiFiScan(); + const uint32_t max_ms_per_chan = 120; + WiFi.setScanTimeout(14 * max_ms_per_chan * 2); + WiFi.scanNetworks(async, show_hidden, passive, max_ms_per_chan /*, channel */); +#endif + if (!async) { + FeedSW_watchdog(); + processScanDone(); + } + } +#if FEATURE_TIMING_STATS + if (async) { + STOP_TIMER(WIFI_SCAN_ASYNC); + } else { + STOP_TIMER(WIFI_SCAN_SYNC); + } +#endif + +#ifdef ESP32 +#if ESP_IDF_VERSION_MAJOR<5 + RTC.clearLastWiFi(); + if (WiFiConnected()) { + # ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_INFO, F("WiFi : Disconnect after scan")); + #endif + + const bool needReconnect = WiFiEventData.wifiConnectAttemptNeeded; + WifiDisconnect(); + WiFiEventData.wifiConnectAttemptNeeded = needReconnect; + } +#endif +#endif +} + +// ******************************************************************************** +// Scan all Wifi Access Points +// ******************************************************************************** +void WiFiScan_log_to_serial() +{ + // Direct Serial is allowed here, since this function will only be called from serial input. + serialPrintln(F("WIFI : SSID Scan start")); + if (WiFi_AP_Candidates.scanComplete() <= 0) { + WiFiMode_t cur_wifimode = WiFi.getMode(); + WifiScan(false); + setWifiMode(cur_wifimode); + } + + const int8_t scanCompleteStatus = WiFi_AP_Candidates.scanComplete(); + if (scanCompleteStatus <= 0) { + serialPrintln(F("WIFI : No networks found")); + } + else + { + serialPrint(F("WIFI : ")); + serialPrint(String(scanCompleteStatus)); + serialPrintln(F(" networks found")); + + int i = 0; + + for (auto it = WiFi_AP_Candidates.scanned_begin(); it != WiFi_AP_Candidates.scanned_end(); ++it) + { + ++i; + // Print SSID and RSSI for each network found + serialPrint(F("WIFI : ")); + serialPrint(String(i)); + serialPrint(": "); + serialPrintln(it->toString()); + delay(10); + } + } + serialPrintln(""); +} + +// ******************************************************************************** +// Manage Wifi Modes +// ******************************************************************************** +void setSTA(bool enable) { + switch (WiFi.getMode()) { + case WIFI_OFF: + + if (enable) { setWifiMode(WIFI_STA); } + break; + case WIFI_STA: + + if (!enable) { setWifiMode(WIFI_OFF); } + break; + case WIFI_AP: + + if (enable) { setWifiMode(WIFI_AP_STA); } + break; + case WIFI_AP_STA: + + if (!enable) { setWifiMode(WIFI_AP); } + break; + default: + break; + } +} + +void setAP(bool enable) { + WiFiMode_t wifimode = WiFi.getMode(); + + #ifdef USES_ESPEASY_NOW + if (!enable && use_EspEasy_now) { + ESPEasy_now_handler.end(); + } + #endif + + switch (wifimode) { + case WIFI_OFF: + + if (enable) { + setWifiMode(WIFI_AP); + } + break; + case WIFI_STA: + + if (enable) { setWifiMode(WIFI_AP_STA); } + break; + case WIFI_AP: + + if (!enable) { setWifiMode(WIFI_OFF); } + break; + case WIFI_AP_STA: + + if (!enable) { setWifiMode(WIFI_STA); } + break; + default: + break; + } +} + +// Only internal scope +void setAPinternal(bool enable) +{ + if (enable) { + // create and store unique AP SSID/PW to prevent ESP from starting AP mode with default SSID and No password! + // setup ssid for AP Mode when needed + String softAPSSID = NetworkCreateRFCCompliantHostname(); + String pwd = SecuritySettings.WifiAPKey; + IPAddress subnet(DEFAULT_AP_SUBNET); + + if (!WiFi.softAPConfig(apIP, apIP, subnet)) { + addLog(LOG_LEVEL_ERROR, strformat( + ("WIFI : [AP] softAPConfig failed! IP: %s, GW: %s, SN: %s"), + apIP.toString().c_str(), + apIP.toString().c_str(), + subnet.toString().c_str() + ) + ); + } + + int channel = 1; + if (WifiIsSTA(WiFi.getMode()) && WiFiConnected()) { + channel = WiFi.channel(); + } else { + #ifdef USES_ESPEASY_NOW + if (Settings.UseESPEasyNow()) { + channel = Nodes.getESPEasyNOW_channel(); + } + #endif + } + + if (WiFi.softAP(softAPSSID.c_str(), pwd.c_str(), channel)) { + eventQueue.add(F("WiFi#APmodeEnabled")); + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLogMove(LOG_LEVEL_INFO, strformat( + F("WIFI : AP Mode enabled. SSID: %s IP: %s ch: %d"), + softAPSSID.c_str(), + formatIP(WiFi.softAPIP()).c_str(), + channel)); + } + } else { + if (loglevelActiveFor(LOG_LEVEL_ERROR)) { + addLogMove(LOG_LEVEL_ERROR, strformat( + F("WIFI : Error while starting AP Mode with SSID: %s IP: %s"), + softAPSSID.c_str(), + formatIP(apIP).c_str())); + } + } + #ifdef ESP32 + + #else // ifdef ESP32 + + if (wifi_softap_dhcps_status() != DHCP_STARTED) { + if (!wifi_softap_dhcps_start()) { + addLog(LOG_LEVEL_ERROR, F("WIFI : [AP] wifi_softap_dhcps_start failed!")); + } + } + #endif // ifdef ESP32 + WiFiEventData.timerAPoff.setMillisFromNow(WIFI_AP_OFF_TIMER_DURATION); + } else { + #if FEATURE_DNS_SERVER + if (dnsServerActive) { + dnsServerActive = false; + dnsServer.stop(); + } + #endif // if FEATURE_DNS_SERVER + } +} + +const __FlashStringHelper * getWifiModeString(WiFiMode_t wifimode) +{ + switch (wifimode) { + case WIFI_OFF: return F("OFF"); + case WIFI_STA: return F("STA"); + case WIFI_AP: return F("AP"); + case WIFI_AP_STA: return F("AP+STA"); + default: + break; + } + return F("Unknown"); +} + +void setWifiMode(WiFiMode_t new_mode) { + const WiFiMode_t cur_mode = WiFi.getMode(); + static WiFiMode_t processing_wifi_mode = cur_mode; + if (cur_mode == new_mode) { + return; + } + if (processing_wifi_mode == new_mode) { + // Prevent loops + return; + } + processing_wifi_mode = new_mode; + + if (cur_mode == WIFI_OFF) { + #if defined(ESP32) + // Needs to be set while WiFi is off + WiFi.hostname(NetworkCreateRFCCompliantHostname()); + #endif + WiFiEventData.markWiFiTurnOn(); + } + if (new_mode != WIFI_OFF) { + #ifdef ESP8266 + // See: https://github.com/esp8266/Arduino/issues/6172#issuecomment-500457407 + WiFi.forceSleepWake(); // Make sure WiFi is really active. + #endif + delay(100); + } else { + WifiDisconnect(); +// delay(100); + processDisconnect(); + WiFiEventData.clear_processed_flags(); + } + + addLog(LOG_LEVEL_INFO, concat(F("WIFI : Set WiFi to "), getWifiModeString(new_mode))); + + int retry = 2; + while (!WiFi.mode(new_mode) && retry > 0) { + #ifndef BUILD_MINIMAL_OTA + addLog(LOG_LEVEL_INFO, F("WIFI : Cannot set mode!!!!!")); + #endif + delay(100); + --retry; + } + retry = 2; + while (WiFi.getMode() != new_mode && retry > 0) { + #ifndef BUILD_MINIMAL_OTA + addLog(LOG_LEVEL_INFO, F("WIFI : mode not yet set")); + #endif + delay(100); + --retry; + } + + + if (new_mode == WIFI_OFF) { + WiFiEventData.markWiFiTurnOn(); + #if defined(ESP32) + // Needs to be set while WiFi is off + WiFi.hostname(NetworkCreateRFCCompliantHostname()); + #endif + delay(100); + #if defined(ESP32) + esp_wifi_set_ps(WIFI_PS_NONE); +// esp_wifi_set_ps(WIFI_PS_MAX_MODEM); + #endif + #ifdef ESP8266 + WiFi.forceSleepBegin(); + #endif // ifdef ESP8266 + delay(1); + } else { + #ifdef ESP32 + if (cur_mode == WIFI_OFF) { + registerWiFiEventHandler(); + } + #endif + // Only set power mode when AP is not enabled + // When AP is enabled, the sleep mode is already set to WIFI_NONE_SLEEP + if (!WifiIsAP(new_mode)) { + if (Settings.WifiNoneSleep()) { + #ifdef ESP8266 + WiFi.setSleepMode(WIFI_NONE_SLEEP); + #endif + #ifdef ESP32 + WiFi.setSleep(WIFI_PS_NONE); + #endif + } else if (Settings.EcoPowerMode()) { + // Allow light sleep during idle times + #ifdef ESP8266 + WiFi.setSleepMode(WIFI_LIGHT_SLEEP); + #endif + #ifdef ESP32 + // Maximum modem power saving. + // In this mode, interval to receive beacons is determined by the listen_interval parameter in wifi_sta_config_t + // FIXME TD-er: Must test if this is desired behavior in ESP32. + WiFi.setSleep(WIFI_PS_MAX_MODEM); + #endif + } else { + // Default + #ifdef ESP8266 + WiFi.setSleepMode(WIFI_MODEM_SLEEP); + #endif + #ifdef ESP32 + // Minimum modem power saving. + // In this mode, station wakes up to receive beacon every DTIM period + WiFi.setSleep(WIFI_PS_MIN_MODEM); + #endif + } + } +#if FEATURE_SET_WIFI_TX_PWR + SetWiFiTXpower(); +#endif + if (WifiIsSTA(new_mode)) { +// WiFi.setAutoConnect(Settings.SDK_WiFi_autoreconnect()); + WiFi.setAutoReconnect(Settings.SDK_WiFi_autoreconnect()); + } + delay(100); // Must allow for some time to init. + } + const bool new_mode_AP_enabled = WifiIsAP(new_mode); + + if (WifiIsAP(cur_mode) && !new_mode_AP_enabled) { + eventQueue.add(F("WiFi#APmodeDisabled")); + } + + if (WifiIsAP(cur_mode) != new_mode_AP_enabled) { + // Mode has changed + if (new_mode_AP_enabled) { + // Check if we should start internal AP + if (!WifiIsSTA(new_mode)) { + bool mustStartInternalAP = !Settings.DoNotStartAP(); + if (mustStartInternalAP && (WiFiEventData.wifiConnectInProgress || WiFiConnected())) { + mustStartInternalAP = false; + } + if (mustStartInternalAP) { + setAPinternal(new_mode_AP_enabled); + } + } + } else { + setAPinternal(new_mode_AP_enabled); + } + } + #if FEATURE_MDNS + #ifdef ESP8266 + // notifyAPChange() is not present in the ESP32 MDNSResponder + MDNS.notifyAPChange(); + #endif + #endif +} + +bool WifiIsAP(WiFiMode_t wifimode) +{ + #if defined(ESP32) + return (wifimode == WIFI_MODE_AP) || (wifimode == WIFI_MODE_APSTA); + #else // if defined(ESP32) + return (wifimode == WIFI_AP) || (wifimode == WIFI_AP_STA); + #endif // if defined(ESP32) +} + +bool WifiIsSTA(WiFiMode_t wifimode) +{ + #if defined(ESP32) + return (wifimode & WIFI_MODE_STA) != 0; + #else // if defined(ESP32) + return (wifimode & WIFI_STA) != 0; + #endif // if defined(ESP32) +} + +bool WiFiUseStaticIP() { + return Settings.IP[0] != 0 && Settings.IP[0] != 255; +} + +bool wifiAPmodeActivelyUsed() +{ + if (!WiFiEventData.processedDisconnectAPmode) return true; + #ifdef USES_ESPEASY_NOW + if (isESPEasy_now_only() && + last_network_medium_set_moment.timeoutReached(600 * 1000)) + { + // Only allow the ESPEasy_NOW_only mode for 10 minutes + setNetworkMedium(Settings.NetworkMedium); + return false; + } else if (ESPEasy_now_handler.active()) { + return true; + } + #endif + + if (!WifiIsAP(WiFi.getMode()) || (!WiFiEventData.timerAPoff.isSet())) { + // AP not active or soon to be disabled in processDisableAPmode() + return false; + } + if (WiFi.softAPgetStationNum() != 0) return true; + return !WiFiEventData.timerAPoff.timeReached(); + + // FIXME TD-er: is effectively checking for AP active enough or must really check for connected clients to prevent automatic wifi + // reconnect? +} + +void setConnectionSpeed() { + #ifdef ESP8266 + // ESP8266 only supports 802.11g mode when running in STA+AP + const bool forcedByAPmode = WifiIsAP(WiFi.getMode()); + WiFiPhyMode_t phyMode = (Settings.ForceWiFi_bg_mode() || forcedByAPmode) ? WIFI_PHY_MODE_11G : WIFI_PHY_MODE_11N; + if (!forcedByAPmode) { + const WiFi_AP_Candidate candidate = WiFi_AP_Candidates.getCurrent(); + if (candidate.phy_known() && (candidate.bits.phy_11g != candidate.bits.phy_11n)) { + if ((WIFI_PHY_MODE_11G == phyMode) && !candidate.bits.phy_11g) { + phyMode = WIFI_PHY_MODE_11N; + #ifndef BUILD_MINIMAL_OTA + addLog(LOG_LEVEL_INFO, F("WIFI : AP is set to 802.11n only")); + #endif + } else if ((WIFI_PHY_MODE_11N == phyMode) && !candidate.bits.phy_11n) { + phyMode = WIFI_PHY_MODE_11G; + #ifndef BUILD_MINIMAL_OTA + addLog(LOG_LEVEL_INFO, F("WIFI : AP is set to 802.11g only")); + #endif + } + } else { + bool useAlternate = WiFiEventData.connectionFailures > 10; + if (useAlternate) { + phyMode = (WIFI_PHY_MODE_11G == phyMode) ? WIFI_PHY_MODE_11N : WIFI_PHY_MODE_11G; + } + } + } else { + // No need to perform a next attempt. + WiFi_AP_Candidates.markAttempt(); + } + + if (WiFi.getPhyMode() == phyMode) { + return; + } + #ifndef BUILD_NO_DEBUG + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + String log = concat(F("WIFI : Set to 802.11"), (WIFI_PHY_MODE_11G == phyMode) ? 'g' : 'n'); + if (forcedByAPmode) { + log += (F(" (AP+STA mode)")); + } + if (Settings.ForceWiFi_bg_mode()) { + log += F(" Force B/G mode"); + } + addLogMove(LOG_LEVEL_INFO, log); + } + #endif + + WiFi.setPhyMode(phyMode); + #endif // ifdef ESP8266 + + // Does not (yet) work, so commented out. + #ifdef ESP32 + + // HT20 = 20 MHz channel width. + // HT40 = 40 MHz channel width. + // In theory, HT40 can offer upto 150 Mbps connection speed. + // However since HT40 is using nearly all channels on 2.4 GHz WiFi, + // Thus you are more likely to experience disturbances. + // The response speed and stability is better at HT20 for ESP units. + esp_wifi_set_bandwidth(WIFI_IF_STA, WIFI_BW_HT20); + + uint8_t protocol = WIFI_PROTOCOL_11B | WIFI_PROTOCOL_11G; // Default to BG + + if (!Settings.ForceWiFi_bg_mode() || (WiFiEventData.connectionFailures > 10)) { + // Set to use BGN + protocol |= WIFI_PROTOCOL_11N; + #ifdef ESP32C6 + protocol |= WIFI_PROTOCOL_11AX; + #endif + } + + const WiFi_AP_Candidate candidate = WiFi_AP_Candidates.getCurrent(); + if (candidate.phy_known()) { + // Check to see if the access point is set to "N-only" + if ((protocol & WIFI_PROTOCOL_11N) == 0) { + if (!candidate.bits.phy_11b && !candidate.bits.phy_11g && candidate.bits.phy_11n) { + if (candidate.bits.phy_11n) { + // Set to use BGN + protocol |= WIFI_PROTOCOL_11N; + addLog(LOG_LEVEL_INFO, F("WIFI : AP is set to 802.11n only")); + } +#ifdef ESP32C6 + if (candidate.bits.phy_11ax) { + // Set to use WiFi6 + protocol |= WIFI_PROTOCOL_11AX; + addLog(LOG_LEVEL_INFO, F("WIFI : AP is set to 802.11ax")); + } +#endif + } + } + } + + + if (WifiIsSTA(WiFi.getMode())) { + // Set to use "Long GI" making it more resilliant to reflections + // See: https://www.tp-link.com/us/configuration-guides/q_a_basic_wireless_concepts/?configurationId=2958#_idTextAnchor038 + esp_wifi_config_80211_tx_rate(WIFI_IF_STA, WIFI_PHY_RATE_MCS3_LGI); + esp_wifi_set_protocol(WIFI_IF_STA, protocol); + } + + if (WifiIsAP(WiFi.getMode())) { + esp_wifi_set_protocol(WIFI_IF_AP, protocol); + } + #endif // ifdef ESP32 + #ifdef ESP8266 + SetWiFiTXpower(); + #endif +} + +void setupStaticIPconfig() { + setUseStaticIP(WiFiUseStaticIP()); + + if (!WiFiUseStaticIP()) { return; } + + const IPAddress ip (Settings.IP); + const IPAddress gw (Settings.Gateway); + const IPAddress subnet (Settings.Subnet); + const IPAddress dns (Settings.DNS); + + WiFiEventData.dns0_cache = dns; + + WiFi.config(ip, gw, subnet, dns); + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLogMove(LOG_LEVEL_INFO, strformat( + F("IP : Static IP : %s GW: %s SN: %s DNS: %s"), + formatIP(ip).c_str(), + formatIP(gw).c_str(), + formatIP(subnet).c_str(), + getValue(LabelType::DNS).c_str())); + } +} + +// ******************************************************************************** +// Formatting WiFi related strings +// ******************************************************************************** +String formatScanResult(int i, const String& separator) { + int32_t rssi = 0; + + return formatScanResult(i, separator, rssi); +} + +String formatScanResult(int i, const String& separator, int32_t& rssi) { + WiFi_AP_Candidate tmp(i); + rssi = tmp.rssi; + return tmp.toString(separator); +} + + +void logConnectionStatus() { + static unsigned long lastLog = 0; + if (lastLog != 0 && timePassedSince(lastLog) < 1000) { + return; + } + lastLog = millis(); +#ifndef BUILD_NO_DEBUG + #ifdef ESP8266 + const uint8_t arduino_corelib_wifistatus = WiFi.status(); + const uint8_t sdk_wifistatus = wifi_station_get_connect_status(); + + if ((arduino_corelib_wifistatus == WL_CONNECTED) != (sdk_wifistatus == STATION_GOT_IP)) { + if (loglevelActiveFor(LOG_LEVEL_ERROR)) { + String log = F("WiFi : SDK station status differs from Arduino status. SDK-status: "); + log += SDKwifiStatusToString(sdk_wifistatus); + log += F(" Arduino status: "); + log += ArduinoWifiStatusToString(arduino_corelib_wifistatus); + addLogMove(LOG_LEVEL_ERROR, log); + } + } + #endif + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLogMove(LOG_LEVEL_INFO, strformat( + F("WIFI : Arduino wifi status: %s ESPeasy internal wifi status: %s"), + ArduinoWifiStatusToString(WiFi.status()).c_str(), + WiFiEventData.ESPeasyWifiStatusToString().c_str())); + } +/* + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + String log; + + switch (WiFi.status()) { + case WL_NO_SSID_AVAIL: { + log = F("WIFI : No SSID found matching: "); + break; + } + case WL_CONNECT_FAILED: { + log = F("WIFI : Connection failed to: "); + break; + } + case WL_DISCONNECTED: { + log = F("WIFI : WiFi.status() = WL_DISCONNECTED SSID: "); + break; + } + case WL_IDLE_STATUS: { + log = F("WIFI : Connection in IDLE state: "); + break; + } + case WL_CONNECTED: { + break; + } + default: + break; + } + + if (log.length() > 0) { + const char *ssid = getLastWiFiSettingsSSID(); + log += ssid; + addLog(LOG_LEVEL_INFO, log); + } + } + */ +#endif // ifndef BUILD_NO_DEBUG +} diff --git a/src/src/ESPEasyCore/ESPEasyWifi.h b/src/src/ESPEasyCore/ESPEasyWifi.h index 9b680a4fb9..bbd04e9e7c 100644 --- a/src/src/ESPEasyCore/ESPEasyWifi.h +++ b/src/src/ESPEasyCore/ESPEasyWifi.h @@ -1,157 +1,158 @@ -#ifndef ESPEASY_WIFI_H -#define ESPEASY_WIFI_H - -#include "../../ESPEasy_common.h" - -#if defined(ESP8266) - # include -#endif // if defined(ESP8266) -#if defined(ESP32) - # include -#endif // if defined(ESP32) - -#include "../DataTypes/WiFiConnectionProtocol.h" -#include "../DataStructs/WiFi_AP_Candidate.h" - -#include "../Helpers/LongTermTimer.h" - -#define WIFI_RECONNECT_WAIT 30000 // in milliSeconds -#define WIFI_AP_OFF_TIMER_DURATION 300000 // in milliSeconds -#if FEATURE_CUSTOM_PROVISIONING -#define WIFI_CONNECTION_CONSIDERED_STABLE 60000 // in milliSeconds -#else -#define WIFI_CONNECTION_CONSIDERED_STABLE 60000 // in milliSeconds -#endif -#define WIFI_ALLOW_AP_AFTERBOOT_PERIOD 5 // in minutes -#define WIFI_SCAN_INTERVAL_AP_USED 125000 // in milliSeconds -#define WIFI_SCAN_INTERVAL_MINIMAL 60000 // in milliSeconds - - -#ifdef ESPEASY_WIFI_CLEANUP_WORK_IN_PROGRESS - -enum class WiFiState_e { - // WiFi radio off - OFF, - // Only running in AP mode - AP_only, - // WiFi was in some kind of error state, waiting period - ErrorRecovery, - // STA mode + scanning - STA_Scanning, - // STA+AP mode + scanning, - // needs some careful handling to prevent disconnecting the connected stations - STA_AP_Scanning, - // Connecting to an AP - STA_Connecting, - // Reconnecting to an AP - // May need to handle some specific disconnect reasons differently from connecting for the first time. - STA_Reconnecting, - // Connected to an AP - STA_Connected -}; - - -class ESPEasyWiFi_t { -public: - - // Start the process of connecting or start AP, depending on the existing configuration. - bool begin(); - - // Terminate WiFi activity - void end(); - - // Process the state machine for managing WiFi connection - void loop(); - - WiFiState_e getState() const { return _state; } - - // Get the IP-address in this order: - // - STA interface if connected, - // - AP interface if active - // - 0.0.0.0 if neither connected nor active. - IPAddress getIP() const; - - void disconnect(); - - - - -private: - - // Handle timeouts + start of AP mode - void checkConnectProgress(); - - // Check to see if we already have some AP to connect to. - void checkScanningProgress(); - - void startScanning(); - - bool connectSTA(); - - - WiFi_AP_Candidate _active_sta; - WiFi_AP_Candidate _AP_conf; - - String _last_ssid; - MAC_address _last_bssid; - uint8_t _last_channel = 0; - WiFiState_e _state = WiFiState_e::OFF; - - LongTermTimer _last_state_change; - LongTermTimer _last_seen_connected; -}; - - -#endif // ESPEASY_WIFI_CLEANUP_WORK_IN_PROGRESS - -bool WiFiConnected(); -void WiFiConnectRelaxed(); -void AttemptWiFiConnect(); -bool prepareWiFi(); -bool checkAndResetWiFi(); -void resetWiFi(); -void initWiFi(); - -#ifdef ESP32 -void removeWiFiEventHandler(); -void registerWiFiEventHandler(); -#endif - -#if FEATURE_SET_WIFI_TX_PWR -void SetWiFiTXpower(); -void SetWiFiTXpower(float dBm); // 0-20.5 -void SetWiFiTXpower(float dBm, float rssi); -#endif -float GetRSSIthreshold(float& maxTXpwr); -// Return some quality based on RSSI. -// <-97 => 0 , >-50 => 10 -// -97 ... -50 => 1 ... 9 -int GetRSSI_quality(); -WiFiConnectionProtocol getConnectionProtocol(); -#ifdef ESP32 -// TSF time is 64-bit timer in usec, sent by the AP along with other packets. -// On tested access points, this seems to be the uptime in usec. -// Could be used among nodes connected to the same AP to increase time sync accuracy. -int64_t WiFi_get_TSF_time(); -#endif -void WifiDisconnect(); -bool WiFiScanAllowed(); -void WifiScan(bool async, uint8_t channel = 0); -void WiFiScan_log_to_serial(); -void setSTA(bool enable); -void setAP(bool enable); -const __FlashStringHelper * getWifiModeString(WiFiMode_t wifimode); -void setWifiMode(WiFiMode_t wifimode); -bool WifiIsAP(WiFiMode_t wifimode); -bool WifiIsSTA(WiFiMode_t wifimode); -bool WiFiUseStaticIP(); -bool wifiAPmodeActivelyUsed(); -void setConnectionSpeed(); -void setupStaticIPconfig(); -String formatScanResult(int i, const String& separator); -String formatScanResult(int i, const String& separator, int32_t& rssi); - -void logConnectionStatus(); - - +#ifndef ESPEASY_WIFI_H +#define ESPEASY_WIFI_H + +#include "../../ESPEasy_common.h" + +#if defined(ESP8266) + # include +#endif // if defined(ESP8266) +#if defined(ESP32) + # include +#endif // if defined(ESP32) + +#include "../DataTypes/WiFiConnectionProtocol.h" +#include "../DataStructs/WiFi_AP_Candidate.h" + +#include "../Helpers/LongTermTimer.h" + +#define WIFI_RECONNECT_WAIT 30000 // in milliSeconds +#define WIFI_AP_OFF_TIMER_EXTENSION 60000 // in milliSeconds +#define WIFI_AP_OFF_TIMER_DURATION 300000 // in milliSeconds +#if FEATURE_CUSTOM_PROVISIONING +#define WIFI_CONNECTION_CONSIDERED_STABLE 60000 // in milliSeconds +#else +#define WIFI_CONNECTION_CONSIDERED_STABLE 60000 // in milliSeconds +#endif +#define WIFI_ALLOW_AP_AFTERBOOT_PERIOD 5 // in minutes +#define WIFI_SCAN_INTERVAL_AP_USED 125000 // in milliSeconds +#define WIFI_SCAN_INTERVAL_MINIMAL 60000 // in milliSeconds + + +#ifdef ESPEASY_WIFI_CLEANUP_WORK_IN_PROGRESS + +enum class WiFiState_e { + // WiFi radio off + OFF, + // Only running in AP mode + AP_only, + // WiFi was in some kind of error state, waiting period + ErrorRecovery, + // STA mode + scanning + STA_Scanning, + // STA+AP mode + scanning, + // needs some careful handling to prevent disconnecting the connected stations + STA_AP_Scanning, + // Connecting to an AP + STA_Connecting, + // Reconnecting to an AP + // May need to handle some specific disconnect reasons differently from connecting for the first time. + STA_Reconnecting, + // Connected to an AP + STA_Connected +}; + + +class ESPEasyWiFi_t { +public: + + // Start the process of connecting or start AP, depending on the existing configuration. + bool begin(); + + // Terminate WiFi activity + void end(); + + // Process the state machine for managing WiFi connection + void loop(); + + WiFiState_e getState() const { return _state; } + + // Get the IP-address in this order: + // - STA interface if connected, + // - AP interface if active + // - 0.0.0.0 if neither connected nor active. + IPAddress getIP() const; + + void disconnect(); + + + + +private: + + // Handle timeouts + start of AP mode + void checkConnectProgress(); + + // Check to see if we already have some AP to connect to. + void checkScanningProgress(); + + void startScanning(); + + bool connectSTA(); + + + WiFi_AP_Candidate _active_sta; + WiFi_AP_Candidate _AP_conf; + + String _last_ssid; + MAC_address _last_bssid; + uint8_t _last_channel = 0; + WiFiState_e _state = WiFiState_e::OFF; + + LongTermTimer _last_state_change; + LongTermTimer _last_seen_connected; +}; + + +#endif // ESPEASY_WIFI_CLEANUP_WORK_IN_PROGRESS + +bool WiFiConnected(); +void WiFiConnectRelaxed(); +void AttemptWiFiConnect(); +bool prepareWiFi(); +bool checkAndResetWiFi(); +void resetWiFi(); +void initWiFi(); + +#ifdef ESP32 +void removeWiFiEventHandler(); +void registerWiFiEventHandler(); +#endif + +#if FEATURE_SET_WIFI_TX_PWR +void SetWiFiTXpower(); +void SetWiFiTXpower(float dBm); // 0-20.5 +void SetWiFiTXpower(float dBm, float rssi); +#endif +float GetRSSIthreshold(float& maxTXpwr); +// Return some quality based on RSSI. +// <-97 => 0 , >-50 => 10 +// -97 ... -50 => 1 ... 9 +int GetRSSI_quality(); +WiFiConnectionProtocol getConnectionProtocol(); +#ifdef ESP32 +// TSF time is 64-bit timer in usec, sent by the AP along with other packets. +// On tested access points, this seems to be the uptime in usec. +// Could be used among nodes connected to the same AP to increase time sync accuracy. +int64_t WiFi_get_TSF_time(); +#endif +void WifiDisconnect(); +bool WiFiScanAllowed(); +void WifiScan(bool async, uint8_t channel = 0); +void WiFiScan_log_to_serial(); +void setSTA(bool enable); +void setAP(bool enable); +const __FlashStringHelper * getWifiModeString(WiFiMode_t wifimode); +void setWifiMode(WiFiMode_t wifimode); +bool WifiIsAP(WiFiMode_t wifimode); +bool WifiIsSTA(WiFiMode_t wifimode); +bool WiFiUseStaticIP(); +bool wifiAPmodeActivelyUsed(); +void setConnectionSpeed(); +void setupStaticIPconfig(); +String formatScanResult(int i, const String& separator); +String formatScanResult(int i, const String& separator, int32_t& rssi); + +void logConnectionStatus(); + + #endif // ESPEASY_WIFI_H \ No newline at end of file diff --git a/src/src/ESPEasyCore/ESPEasyWifi_ProcessEvent.cpp b/src/src/ESPEasyCore/ESPEasyWifi_ProcessEvent.cpp index ba47615978..f33414cbb7 100644 --- a/src/src/ESPEasyCore/ESPEasyWifi_ProcessEvent.cpp +++ b/src/src/ESPEasyCore/ESPEasyWifi_ProcessEvent.cpp @@ -1,611 +1,695 @@ -#include "../ESPEasyCore/ESPEasyWifi_ProcessEvent.h" - -#include "../../ESPEasy-Globals.h" - -#if FEATURE_ETHERNET -#include "../ESPEasyCore/ESPEasyEth_ProcessEvent.h" -#endif -#include "../ESPEasyCore/ESPEasyNetwork.h" -#include "../ESPEasyCore/ESPEasyWifi.h" - -#include "../Globals/ESPEasyWiFiEvent.h" -#include "../Globals/ESPEasy_Scheduler.h" -#include "../Globals/ESPEasy_time.h" -#include "../Globals/EventQueue.h" -#include "../Globals/MQTT.h" -#include "../Globals/NetworkState.h" -#include "../Globals/RTC.h" -#include "../Globals/SecuritySettings.h" -#include "../Globals/Services.h" -#include "../Globals/Settings.h" -#include "../Globals/WiFi_AP_Candidates.h" - -#include "../Helpers/Convert.h" -#include "../Helpers/ESPEasyRTC.h" -#include "../Helpers/ESPEasy_Storage.h" -#include "../Helpers/Network.h" -#include "../Helpers/Networking.h" -#include "../Helpers/PeriodicalActions.h" -#include "../Helpers/StringConverter.h" -#include "../Helpers/StringGenerator_WiFi.h" -#include "../Helpers/StringProvider.h" - -// #include "../ESPEasyCore/ESPEasyEth.h" -// #include "../ESPEasyCore/ESPEasyWiFiEvent.h" -// #include "../ESPEasyCore/ESPEasy_Log.h" -// #include "../Helpers/ESPEasy_time_calc.h" -// #include "../Helpers/Misc.h" -// #include "../Helpers/Scheduler.h" - -#include "../WebServer/ESPEasy_WebServer.h" - - -// ******************************************************************************** -// Called from the loop() to make sure events are processed as soon as possible. -// These functions are called from Setup() or Loop() and thus may call delay() or yield() -// ******************************************************************************** -void handle_unprocessedNetworkEvents() -{ -#if FEATURE_ETHERNET - handle_unprocessedEthEvents(); -#endif - - if (active_network_medium == NetworkMedium_t::WIFI) { - const bool should_be_initialized = (WiFiEventData.WiFiGotIP() && WiFiEventData.WiFiConnected()) || NetworkConnected(); - if (WiFiEventData.WiFiServicesInitialized() != should_be_initialized) - { - if (!WiFiEventData.WiFiServicesInitialized()) { - WiFiEventData.processedDHCPTimeout = true; // FIXME TD-er: Find out when this happens (happens on ESP32 sometimes) - if (WiFiConnected()) { - if (!WiFiEventData.WiFiGotIP()) { - # ifndef BUILD_NO_DEBUG - addLog(LOG_LEVEL_DEBUG, F("WiFi : Missed gotIP event")); - #endif - WiFiEventData.processedGotIP = false; - processGotIP(); - } - if (!WiFiEventData.WiFiConnected()) { - # ifndef BUILD_NO_DEBUG - addLog(LOG_LEVEL_DEBUG, F("WiFi : Missed connected event")); - #endif - WiFiEventData.processedConnect = false; - processConnect(); - } - // Apparently we are connected, so no need to process any late disconnect event - WiFiEventData.processedDisconnect = true; - } - WiFiEventData.setWiFiServicesInitialized(); -//#ifdef ESP32 - setWebserverRunning(false); - delay(1); - setWebserverRunning(true); - delay(1); -/* -#else - CheckRunningServices(); -#endif -*/ - } - } - } - - if (WiFiEventData.unprocessedWifiEvents()) { - // Process disconnect events before connect events. - if (!WiFiEventData.processedDisconnect) { - #ifndef BUILD_NO_DEBUG - addLog(LOG_LEVEL_DEBUG, F("WIFI : Entering processDisconnect()")); - #endif // ifndef BUILD_NO_DEBUG - processDisconnect(); - } - } - - if (active_network_medium == NetworkMedium_t::WIFI) { - if ((!WiFiEventData.WiFiServicesInitialized()) || WiFiEventData.unprocessedWifiEvents() || WiFiEventData.wifiConnectAttemptNeeded) { - // WiFi connection is not yet available, so introduce some extra delays to - // help the background tasks managing wifi connections - delay(0); - - if (!WiFiEventData.processedConnect) { - #ifndef BUILD_NO_DEBUG - addLog(LOG_LEVEL_DEBUG, F("WIFI : Entering processConnect()")); - #endif // ifndef BUILD_NO_DEBUG - processConnect(); - } - - if (!WiFiEventData.processedGotIP) { - #ifndef BUILD_NO_DEBUG - addLog(LOG_LEVEL_DEBUG, F("WIFI : Entering processGotIP()")); - #endif // ifndef BUILD_NO_DEBUG - processGotIP(); - } - - if (!WiFiEventData.processedDHCPTimeout) { - #ifndef BUILD_NO_DEBUG - addLog(LOG_LEVEL_INFO, F("WIFI : DHCP timeout, Calling disconnect()")); - #endif // ifndef BUILD_NO_DEBUG - WiFiEventData.processedDHCPTimeout = true; - WifiDisconnect(); - } - - if (WiFi.status() == WL_DISCONNECTED && WiFiEventData.wifiConnectInProgress) { - if (WiFiEventData.last_wifi_connect_attempt_moment.isSet() && - WiFiEventData.last_wifi_connect_attempt_moment.timeoutReached(DEFAULT_WIFI_CONNECTION_TIMEOUT)) { - logConnectionStatus(); - resetWiFi(); - } - if (!WiFiEventData.last_wifi_connect_attempt_moment.isSet()) { - WiFiEventData.wifiConnectInProgress = false; - } - delay(10); - } - - if (!WiFiEventData.wifiConnectInProgress) { - WiFiEventData.wifiConnectAttemptNeeded = true; - NetworkConnectRelaxed(); - } - } - - - if (WiFiEventData.WiFiDisconnected()) { - #ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - static LongTermTimer lastDisconnectMoment_log; - static uint8_t lastWiFiStatus_log = 0; - uint8_t cur_wifi_status = WiFi.status(); - if (WiFiEventData.lastDisconnectMoment.get() != lastDisconnectMoment_log.get() || - lastWiFiStatus_log != cur_wifi_status) { - lastDisconnectMoment_log.set(WiFiEventData.lastDisconnectMoment.get()); - lastWiFiStatus_log = cur_wifi_status; - String wifilog = F("WIFI : Disconnected: WiFi.status() = "); - wifilog += WiFiEventData.ESPeasyWifiStatusToString(); - wifilog += F(" RSSI: "); - wifilog += String(WiFi.RSSI()); - wifilog += F(" status: "); - #ifdef ESP8266 - station_status_t status = wifi_station_get_connect_status(); - wifilog += SDKwifiStatusToString(status); - #endif - #ifdef ESP32 - wifilog += ArduinoWifiStatusToString(WiFi.status()); - #endif - addLogMove(LOG_LEVEL_DEBUG, wifilog); - } - } - #endif // ifndef BUILD_NO_DEBUG - - // While connecting to WiFi make sure the device has ample time to do so - delay(10); - } - - if (!WiFiEventData.processedDisconnectAPmode) { processDisconnectAPmode(); } - - if (!WiFiEventData.processedConnectAPmode) { processConnectAPmode(); } - - if (WiFiEventData.timerAPoff.isSet()) { processDisableAPmode(); } - - if (!WiFiEventData.processedScanDone) { processScanDone(); } - - if (WiFiEventData.wifi_connect_attempt > 0) { - // We only want to clear this counter if the connection is currently stable. - if (WiFiEventData.WiFiServicesInitialized()) { - if (WiFiEventData.lastConnectMoment.isSet() && WiFiEventData.lastConnectMoment.timeoutReached(WIFI_CONNECTION_CONSIDERED_STABLE)) { - // Connection considered stable - WiFiEventData.wifi_connect_attempt = 0; - WiFiEventData.wifi_considered_stable = true; - WiFi_AP_Candidates.markCurrentConnectionStable(); - - if (WiFi.getAutoReconnect() != Settings.SDK_WiFi_autoreconnect()) { - WiFi.setAutoReconnect(Settings.SDK_WiFi_autoreconnect()); - delay(1); - } - } else { - if (WiFi.getAutoReconnect()) { - WiFi.setAutoReconnect(false); - delay(1); - } - } - } - } - } -#if FEATURE_ETHERNET - check_Eth_DNS_valid(); -#endif // if FEATURE_ETHERNET - -#if FEATURE_ESPEASY_P2P - updateUDPport(false); -#endif -} - -// ******************************************************************************** -// Functions to process the data gathered from the events. -// These functions are called from Setup() or Loop() and thus may call delay() or yield() -// ******************************************************************************** -void processDisconnect() { - if (WiFiEventData.processedDisconnect) { return; } - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = strformat( - F("WIFI : Disconnected! Reason: '%s'"), - getLastDisconnectReason().c_str()); - - if (WiFiEventData.lastConnectedDuration_us > 0) { - log += concat( - F(" Connected for "), - format_msec_duration(WiFiEventData.lastConnectedDuration_us / 1000ll)); - } - addLogMove(LOG_LEVEL_INFO, log); - } - logConnectionStatus(); - - if (WiFiEventData.processingDisconnect.isSet()) { - if (WiFiEventData.processingDisconnect.millisPassedSince() > 5000 || WiFiEventData.processedDisconnect) { - WiFiEventData.processedDisconnect = true; - WiFiEventData.processingDisconnect.clear(); - } - } - - - if (WiFiEventData.processedDisconnect || - WiFiEventData.processingDisconnect.isSet()) { return; } - WiFiEventData.processingDisconnect.setNow(); - WiFiEventData.setWiFiDisconnected(); - WiFiEventData.wifiConnectAttemptNeeded = true; - delay(100); // FIXME TD-er: See https://github.com/letscontrolit/ESPEasy/issues/1987#issuecomment-451644424 - - if (Settings.UseRules) { - eventQueue.add(F("WiFi#Disconnected")); - } - - - // FIXME TD-er: With AutoReconnect enabled, WiFi must be reset or else we completely loose track of the actual WiFi state - bool mustRestartWiFi = Settings.WiFiRestart_connection_lost() || WiFi.getAutoReconnect(); - if (WiFiEventData.lastConnectedDuration_us > 0 && (WiFiEventData.lastConnectedDuration_us / 1000) < 5000) { - if (!WiFi_AP_Candidates.getBestCandidate().usable()) -// addLog(LOG_LEVEL_INFO, F("WIFI : !getBestCandidate().usable() => mustRestartWiFi = true")); - - mustRestartWiFi = true; - } - - if (WiFi.status() == WL_IDLE_STATUS) { -// addLog(LOG_LEVEL_INFO, F("WIFI : WiFi.status() == WL_IDLE_STATUS => mustRestartWiFi = true")); - mustRestartWiFi = true; - } - - - #ifdef USES_ESPEASY_NOW - if (use_EspEasy_now) { -// mustRestartWiFi = true; - } - #endif - //WifiDisconnect(); // Needed or else node may not reconnect reliably. - - if (mustRestartWiFi) { - WiFiEventData.processedDisconnect = true; - resetWiFi(); -// WifiScan(false); -// delay(100); -// setWifiMode(WIFI_OFF); -// initWiFi(); -// delay(100); - } -// delay(500); - logConnectionStatus(); - WiFiEventData.processedDisconnect = true; - WiFiEventData.processingDisconnect.clear(); -} - -void processConnect() { - if (WiFiEventData.processedConnect) { return; } - //delay(100); // FIXME TD-er: See https://github.com/letscontrolit/ESPEasy/issues/1987#issuecomment-451644424 - if (checkAndResetWiFi()) { - return; - } - WiFiEventData.processedConnect = true; - if (WiFi.status() == WL_DISCONNECTED) { - // Apparently not really connected - return; - } - - WiFiEventData.setWiFiConnected(); - ++WiFiEventData.wifi_reconnects; - - if (WiFi_AP_Candidates.getCurrent().bits.isEmergencyFallback) { - #ifdef CUSTOM_EMERGENCY_FALLBACK_RESET_CREDENTIALS - const bool mustResetCredentials = CUSTOM_EMERGENCY_FALLBACK_RESET_CREDENTIALS; - #else - const bool mustResetCredentials = false; - #endif - #ifdef CUSTOM_EMERGENCY_FALLBACK_START_AP - const bool mustStartAP = CUSTOM_EMERGENCY_FALLBACK_START_AP; - #else - const bool mustStartAP = false; - #endif - if (mustStartAP) { - int allowedUptimeMinutes = 10; - #ifdef CUSTOM_EMERGENCY_FALLBACK_ALLOW_MINUTES_UPTIME - allowedUptimeMinutes = CUSTOM_EMERGENCY_FALLBACK_ALLOW_MINUTES_UPTIME; - #endif - if (getUptimeMinutes() < allowedUptimeMinutes) { - WiFiEventData.timerAPstart.setNow(); - } - } - if (mustResetCredentials && !WiFiEventData.performedClearWiFiCredentials) { - WiFiEventData.performedClearWiFiCredentials = true; - SecuritySettings.clearWiFiCredentials(); - SaveSecuritySettings(); - WiFiEventData.markDisconnect(WIFI_DISCONNECT_REASON_AUTH_EXPIRE); - WiFi_AP_Candidates.force_reload(); - } - } - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - const LongTermTimer::Duration connect_duration = WiFiEventData.last_wifi_connect_attempt_moment.timeDiff(WiFiEventData.lastConnectMoment); - String log = strformat( - F("WIFI : Connected! AP: %s (%s) Ch: %d"), - WiFi.SSID().c_str(), - WiFi.BSSIDstr().c_str(), - RTC.lastWiFiChannel); - - if ((connect_duration > 0ll) && (connect_duration < 30000000ll)) { - // Just log times when they make sense. - log += strformat( - F(" Duration: %d ms"), - static_cast(connect_duration / 1000)); - } - addLogMove(LOG_LEVEL_INFO, log); - } - -// WiFiEventData.last_wifi_connect_attempt_moment.clear(); - - if (Settings.UseRules) { - if (WiFiEventData.bssid_changed) { - eventQueue.add(F("WiFi#ChangedAccesspoint")); - } - - if (WiFiEventData.channel_changed) { - eventQueue.add(F("WiFi#ChangedWiFichannel")); - } - } - - if (useStaticIP()) { - WiFiEventData.markGotIP(); // in static IP config the got IP event is never fired. - } - saveToRTC(); - - logConnectionStatus(); -} - -void processGotIP() { - if (WiFiEventData.processedGotIP) { - return; - } - if (checkAndResetWiFi()) { - return; - } - - IPAddress ip = NetworkLocalIP(); - - if (!useStaticIP()) { - #ifdef ESP8266 - if (!ip.isSet()) { - #else - if (ip[0] == 0 && ip[1] == 0 && ip[2] == 0 && ip[3] == 0) { - #endif - return; - } - } - const IPAddress gw = WiFi.gatewayIP(); - const IPAddress subnet = WiFi.subnetMask(); - const LongTermTimer::Duration dhcp_duration = WiFiEventData.lastConnectMoment.timeDiff(WiFiEventData.lastGetIPmoment); - WiFiEventData.dns0_cache = WiFi.dnsIP(0); - WiFiEventData.dns1_cache = WiFi.dnsIP(1); - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = strformat( - F("WIFI : %s (%s) GW: %s SN: %s DNS: %s"), - concat(useStaticIP() ? F("Static IP: ") : F("DHCP IP: "), formatIP(ip)).c_str(), - NetworkGetHostname().c_str(), - formatIP(gw).c_str(), - formatIP(subnet).c_str(), - getValue(LabelType::DNS).c_str()); - - if ((dhcp_duration > 0ll) && (dhcp_duration < 30000000ll)) { - // Just log times when they make sense. - log += strformat(F(" duration: %d ms"), static_cast(dhcp_duration / 1000)); - } - addLogMove(LOG_LEVEL_INFO, log); - } - - // Might not work in core 2.5.0 - // See https://github.com/esp8266/Arduino/issues/5839 - if ((Settings.IP_Octet != 0) && (Settings.IP_Octet != 255)) - { - ip[3] = Settings.IP_Octet; - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - addLogMove(LOG_LEVEL_INFO, concat(F("IP : Fixed IP octet:"), formatIP(ip))); - } - WiFi.config(ip, gw, subnet, WiFiEventData.dns0_cache, WiFiEventData.dns1_cache); - } - -#if FEATURE_MQTT - mqtt_reconnect_count = 0; - MQTTclient_should_reconnect = true; - timermqtt_interval = 100; - Scheduler.setIntervalTimer(SchedulerIntervalTimer_e::TIMER_MQTT); - scheduleNextMQTTdelayQueue(); -#endif // if FEATURE_MQTT - Scheduler.sendGratuitousARP_now(); - - if (Settings.UseRules) - { - eventQueue.add(F("WiFi#Connected")); - } - statusLED(true); - - // WiFi.scanDelete(); - - if (WiFiEventData.wifiSetup) { - // Wifi setup was active, Apparently these settings work. - WiFiEventData.wifiSetup = false; - SaveSecuritySettings(); - } - - if ((WiFiEventData.WiFiConnected() || WiFi.isConnected()) && hasIPaddr()) { - WiFiEventData.setWiFiGotIP(); - } - #if FEATURE_ESPEASY_P2P - refreshNodeList(); - #endif - logConnectionStatus(); -} - -#if FEATURE_USE_IPV6 -void processGotIPv6() { - if (!WiFiEventData.processedGotIP6) { - WiFiEventData.processedGotIP6 = true; - if (loglevelActiveFor(LOG_LEVEL_INFO)) - addLog(LOG_LEVEL_INFO, String(F("WIFI : STA got IP6 ")) + WiFiEventData.unprocessed_IP6.toString(true)); -#if FEATURE_ESPEASY_P2P -// updateUDPport(true); -#endif - } -} -#endif - -// A client disconnected from the AP on this node. -void processDisconnectAPmode() { - if (WiFiEventData.processedDisconnectAPmode) { return; } - WiFiEventData.processedDisconnectAPmode = true; - -#ifndef BUILD_NO_DEBUG - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - const int nrStationsConnected = WiFi.softAPgetStationNum(); - String log = F("AP Mode: Client disconnected: "); - log += WiFiEventData.lastMacDisconnectedAPmode.toString(); - log += F(" Connected devices: "); - log += nrStationsConnected; - addLogMove(LOG_LEVEL_INFO, log); - } -#endif -} - -// Client connects to AP on this node -void processConnectAPmode() { - if (WiFiEventData.processedConnectAPmode) { return; } - WiFiEventData.processedConnectAPmode = true; - // Extend timer to switch off AP. - WiFiEventData.timerAPoff.setMillisFromNow(WIFI_AP_OFF_TIMER_DURATION); -#ifndef BUILD_NO_DEBUG - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("AP Mode: Client connected: "); - log += WiFiEventData.lastMacConnectedAPmode.toString(); - log += F(" Connected devices: "); - log += WiFi.softAPgetStationNum(); - addLogMove(LOG_LEVEL_INFO, log); - } -#endif - - #if FEATURE_DNS_SERVER - // Start DNS, only used if the ESP has no valid WiFi config - // It will reply with it's own address on all DNS requests - // (captive portal concept) - if (!dnsServerActive) { - dnsServerActive = true; - dnsServer.start(DNS_PORT, "*", apIP); - } - #endif // if FEATURE_DNS_SERVER -} - -// Switch of AP mode when timeout reached and no client connected anymore. -void processDisableAPmode() { - if (!WiFiEventData.timerAPoff.isSet()) { return; } - - if (!WifiIsAP(WiFi.getMode())) { - return; - } - // disable AP after timeout and no clients connected. - if (WiFiEventData.timerAPoff.timeReached() && (WiFi.softAPgetStationNum() == 0)) { - setAP(false); - } - - if (!WifiIsAP(WiFi.getMode())) { - WiFiEventData.timerAPoff.clear(); - if (WiFiEventData.wifiConnectAttemptNeeded) { - // Force a reconnect cycle - WifiDisconnect(); - } - } -} - -void processScanDone() { - WiFi_AP_Candidates.load_knownCredentials(); - if (WiFiEventData.processedScanDone) { return; } - - - - // Better act on the scan done event, as it may get triggered for normal wifi begin calls. - int8_t scanCompleteStatus = WiFi.scanComplete(); - switch (scanCompleteStatus) { - case 0: // Nothing (yet) found - if (WiFiEventData.lastGetScanMoment.timeoutReached(5000)) { - WiFi.scanDelete(); - WiFiEventData.processedScanDone = true; - } - return; - case -1: // WIFI_SCAN_RUNNING - // FIXME TD-er: Set timeout... - if (WiFiEventData.lastGetScanMoment.timeoutReached(5000)) { - # ifndef BUILD_NO_DEBUG - addLog(LOG_LEVEL_ERROR, F("WiFi : Scan Running Timeout")); - #endif - WiFi.scanDelete(); - WiFiEventData.processedScanDone = true; - } - return; - case -2: // WIFI_SCAN_FAILED - addLog(LOG_LEVEL_ERROR, F("WiFi : Scan failed")); - WiFi.scanDelete(); - WiFiEventData.processedScanDone = true; - return; - } - - WiFiEventData.lastGetScanMoment.setNow(); - WiFiEventData.processedScanDone = true; -# ifndef BUILD_NO_DEBUG - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - addLogMove(LOG_LEVEL_INFO, concat(F("WiFi : Scan finished, found: "), scanCompleteStatus)); - } -#endif - -#if !FEATURE_ESP8266_DIRECT_WIFI_SCAN - WiFi_AP_Candidates.process_WiFiscan(scanCompleteStatus); -#endif - WiFi_AP_Candidates.load_knownCredentials(); - - if (WiFi_AP_Candidates.addedKnownCandidate() && !NetworkConnected()) { - if (!WiFiEventData.wifiConnectInProgress) { - WiFiEventData.wifiConnectAttemptNeeded = true; - # ifndef BUILD_NO_DEBUG - if (WiFi_AP_Candidates.addedKnownCandidate()) { - addLog(LOG_LEVEL_INFO, F("WiFi : Added known candidate, try to connect")); - } - #endif -#ifdef ESP32 -// setSTA(false); -#endif - NetworkConnectRelaxed(); -#ifdef USES_ESPEASY_NOW - temp_disable_EspEasy_now_timer = millis() + 20000; -#endif - } - } else if (!WiFiEventData.wifiConnectInProgress && !NetworkConnected()) { - WiFiEventData.timerAPstart.setNow(); - } - -} - - - - +#include "../ESPEasyCore/ESPEasyWifi_ProcessEvent.h" + +#include "../../ESPEasy-Globals.h" + +#include "../DataStructs/NodesHandler.h" + +#if FEATURE_ETHERNET +#include "../ESPEasyCore/ESPEasyEth_ProcessEvent.h" +#endif +#include "../ESPEasyCore/ESPEasy_Log.h" +#include "../ESPEasyCore/ESPEasyNetwork.h" +#include "../ESPEasyCore/ESPEasyWifi.h" + +#include "../Globals/ESPEasyWiFiEvent.h" +#include "../Globals/ESPEasy_Scheduler.h" +#ifdef USES_ESPEASY_NOW +#include "../Globals/ESPEasy_now_handler.h" +#include "../Globals/ESPEasy_now_state.h" +#endif +#include "../Globals/ESPEasy_time.h" +#include "../Globals/EventQueue.h" +#include "../Globals/MQTT.h" +#include "../Globals/NetworkState.h" +#include "../Globals/Nodes.h" +#include "../Globals/RTC.h" +#include "../Globals/SecuritySettings.h" +#include "../Globals/Services.h" +#include "../Globals/Settings.h" +#include "../Globals/WiFi_AP_Candidates.h" + +#include "../Helpers/Convert.h" +#include "../Helpers/ESPEasyRTC.h" +#include "../Helpers/ESPEasy_Storage.h" +#include "../Helpers/Network.h" +#include "../Helpers/Networking.h" +#include "../Helpers/PeriodicalActions.h" +#include "../Helpers/StringConverter.h" +#include "../Helpers/StringGenerator_WiFi.h" +#include "../Helpers/StringProvider.h" + +// #include "../ESPEasyCore/ESPEasyEth.h" +// #include "../ESPEasyCore/ESPEasyWiFiEvent.h" +// #include "../ESPEasyCore/ESPEasy_Log.h" +// #include "../Helpers/ESPEasy_time_calc.h" +// #include "../Helpers/Misc.h" +// #include "../Helpers/Scheduler.h" + +#include "../WebServer/ESPEasy_WebServer.h" + + + +// ******************************************************************************** +// Called from the loop() to make sure events are processed as soon as possible. +// These functions are called from Setup() or Loop() and thus may call delay() or yield() +// ******************************************************************************** +void handle_unprocessedNetworkEvents() +{ +#if FEATURE_ETHERNET + handle_unprocessedEthEvents(); +#endif + + if (active_network_medium == NetworkMedium_t::WIFI) { + const bool should_be_initialized = (WiFiEventData.WiFiGotIP() && WiFiEventData.WiFiConnected()) || NetworkConnected(); + if (WiFiEventData.WiFiServicesInitialized() != should_be_initialized) + { + if (!WiFiEventData.WiFiServicesInitialized()) { + WiFiEventData.processedDHCPTimeout = true; // FIXME TD-er: Find out when this happens (happens on ESP32 sometimes) + if (WiFiConnected()) { + if (!WiFiEventData.WiFiGotIP()) { + # ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_DEBUG, F("WiFi : Missed gotIP event")); + #endif + WiFiEventData.processedGotIP = false; + processGotIP(); + } + if (!WiFiEventData.WiFiConnected()) { + # ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_DEBUG, F("WiFi : Missed connected event")); + #endif + WiFiEventData.processedConnect = false; + processConnect(); + } + // Apparently we are connected, so no need to process any late disconnect event + WiFiEventData.processedDisconnect = true; + } + WiFiEventData.setWiFiServicesInitialized(); +//#ifdef ESP32 + setWebserverRunning(false); + delay(1); + setWebserverRunning(true); + delay(1); +/* +#else + CheckRunningServices(); +#endif +*/ + } + } + } + + if (WiFiEventData.unprocessedWifiEvents()) { + // Process disconnect events before connect events. + if (!WiFiEventData.processedDisconnect) { + #ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_DEBUG, F("WIFI : Entering processDisconnect()")); + #endif // ifndef BUILD_NO_DEBUG + processDisconnect(); + } + } + + if (active_network_medium == NetworkMedium_t::WIFI) { + if ((!WiFiEventData.WiFiServicesInitialized()) || WiFiEventData.unprocessedWifiEvents() || WiFiEventData.wifiConnectAttemptNeeded) { + // WiFi connection is not yet available, so introduce some extra delays to + // help the background tasks managing wifi connections + delay(0); + + if (!WiFiEventData.processedConnect) { + #ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_DEBUG, F("WIFI : Entering processConnect()")); + #endif // ifndef BUILD_NO_DEBUG + processConnect(); + } + + if (!WiFiEventData.processedGotIP) { + #ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_DEBUG, F("WIFI : Entering processGotIP()")); + #endif // ifndef BUILD_NO_DEBUG + processGotIP(); + } + + if (!WiFiEventData.processedDHCPTimeout) { + #ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_INFO, F("WIFI : DHCP timeout, Calling disconnect()")); + #endif // ifndef BUILD_NO_DEBUG + WiFiEventData.processedDHCPTimeout = true; + WifiDisconnect(); + } + + if (WiFi.status() == WL_DISCONNECTED && WiFiEventData.wifiConnectInProgress) { + if (WiFiEventData.last_wifi_connect_attempt_moment.isSet() && + WiFiEventData.last_wifi_connect_attempt_moment.timeoutReached(DEFAULT_WIFI_CONNECTION_TIMEOUT)) { + logConnectionStatus(); + resetWiFi(); + } + if (!WiFiEventData.last_wifi_connect_attempt_moment.isSet()) { + WiFiEventData.wifiConnectInProgress = false; + } + delay(10); + } + + if (!WiFiEventData.wifiConnectInProgress) { + WiFiEventData.wifiConnectAttemptNeeded = true; + NetworkConnectRelaxed(); + } + } + + + if (WiFiEventData.WiFiDisconnected()) { + #ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + static LongTermTimer lastDisconnectMoment_log; + static uint8_t lastWiFiStatus_log = 0; + uint8_t cur_wifi_status = WiFi.status(); + if (WiFiEventData.lastDisconnectMoment.get() != lastDisconnectMoment_log.get() || + lastWiFiStatus_log != cur_wifi_status) { + lastDisconnectMoment_log.set(WiFiEventData.lastDisconnectMoment.get()); + lastWiFiStatus_log = cur_wifi_status; + String wifilog = F("WIFI : Disconnected: WiFi.status() = "); + wifilog += WiFiEventData.ESPeasyWifiStatusToString(); + wifilog += F(" RSSI: "); + wifilog += String(WiFi.RSSI()); + wifilog += F(" status: "); + #ifdef ESP8266 + station_status_t status = wifi_station_get_connect_status(); + wifilog += SDKwifiStatusToString(status); + #endif + #ifdef ESP32 + wifilog += ArduinoWifiStatusToString(WiFi.status()); + #endif + addLogMove(LOG_LEVEL_DEBUG, wifilog); + } + } + #endif // ifndef BUILD_NO_DEBUG + + // While connecting to WiFi make sure the device has ample time to do so + delay(10); + } + + if (!WiFiEventData.processedDisconnectAPmode) { processDisconnectAPmode(); } + + if (!WiFiEventData.processedConnectAPmode) { processConnectAPmode(); } + +#ifdef USES_ESPEASY_NOW + if (!WiFiEventData.processedProbeRequestAPmode) { processProbeRequestAPmode(); } +#endif + + if (WiFiEventData.timerAPoff.isSet()) { processDisableAPmode(); } + + if (!WiFiEventData.processedScanDone) { processScanDone(); } + + if (WiFiEventData.wifi_connect_attempt > 0) { + // We only want to clear this counter if the connection is currently stable. + if (WiFiEventData.WiFiServicesInitialized()) { + if (WiFiEventData.lastConnectMoment.isSet() && WiFiEventData.lastConnectMoment.timeoutReached(WIFI_CONNECTION_CONSIDERED_STABLE)) { + // Connection considered stable + WiFiEventData.wifi_connect_attempt = 0; + WiFiEventData.wifi_considered_stable = true; + WiFi_AP_Candidates.markCurrentConnectionStable(); + + if (WiFi.getAutoReconnect() != Settings.SDK_WiFi_autoreconnect()) { + WiFi.setAutoReconnect(Settings.SDK_WiFi_autoreconnect()); + delay(1); + } + } else { + if (WiFi.getAutoReconnect()) { + WiFi.setAutoReconnect(false); + delay(1); + } + } + } + } + } +#if FEATURE_ETHERNET + check_Eth_DNS_valid(); +#endif // if FEATURE_ETHERNET + +#if FEATURE_ESPEASY_P2P + updateUDPport(false); +#endif +} + +// ******************************************************************************** +// Functions to process the data gathered from the events. +// These functions are called from Setup() or Loop() and thus may call delay() or yield() +// ******************************************************************************** +void processDisconnect() { + if (WiFiEventData.processedDisconnect) { return; } + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + String log = strformat( + F("WIFI : Disconnected! Reason: '%s'"), + getLastDisconnectReason().c_str()); + + if (WiFiEventData.lastConnectedDuration_us > 0) { + log += concat( + F(" Connected for "), + format_msec_duration(WiFiEventData.lastConnectedDuration_us / 1000ll)); + } + addLogMove(LOG_LEVEL_INFO, log); + } + logConnectionStatus(); + + if (WiFiEventData.processingDisconnect.isSet()) { + if (WiFiEventData.processingDisconnect.millisPassedSince() > 5000 || WiFiEventData.processedDisconnect) { + WiFiEventData.processedDisconnect = true; + WiFiEventData.processingDisconnect.clear(); + } + } + + if (WiFiEventData.processedDisconnect || + WiFiEventData.processingDisconnect.isSet()) { return; } + + if (Settings.UseRules) { + eventQueue.add(F("WiFi#Disconnected")); + } + + #ifdef ESP8266 + bool mustRestartWiFi = Settings.WiFiRestart_connection_lost(); + #else + // FIXME TD-er: With AutoReconnect enabled, WiFi must be reset or else we completely loose track of the actual WiFi state + bool mustRestartWiFi = Settings.WiFiRestart_connection_lost() || WiFi.getAutoReconnect(); + #endif + if (WiFiEventData.lastConnectedDuration_us > 0 && (WiFiEventData.lastConnectedDuration_us / 1000) < 5000) { + if (!WiFi_AP_Candidates.getBestCandidate().usable()) { +// addLog(LOG_LEVEL_INFO, F("WIFI : !getBestCandidate().usable() => mustRestartWiFi = true")); + + mustRestartWiFi = true; + } + } + + if (WiFi.status() == WL_IDLE_STATUS) { +// addLog(LOG_LEVEL_INFO, F("WIFI : WiFi.status() == WL_IDLE_STATUS => mustRestartWiFi = true")); + mustRestartWiFi = true; + } + + + #ifdef USES_ESPEASY_NOW + if (use_EspEasy_now) { +// mustRestartWiFi = true; + } + #endif + //WifiDisconnect(); // Needed or else node may not reconnect reliably. + + if (mustRestartWiFi) { + WiFiEventData.processedDisconnect = true; + resetWiFi(); +// WifiScan(false); +// delay(100); +// setWifiMode(WIFI_OFF); +// initWiFi(); +// delay(100); + } +// delay(500); + logConnectionStatus(); + WiFiEventData.processingDisconnect.clear(); + WiFiEventData.wifiConnectAttemptNeeded = true; +} + +void processConnect() { + if (WiFiEventData.processedConnect) { return; } + //delay(100); // FIXME TD-er: See https://github.com/letscontrolit/ESPEasy/issues/1987#issuecomment-451644424 + if (checkAndResetWiFi()) { + return; + } + WiFiEventData.processedConnect = true; + if (WiFi.status() == WL_DISCONNECTED) { + // Apparently not really connected + return; + } + + WiFiEventData.setWiFiConnected(); + ++WiFiEventData.wifi_reconnects; + + if (WiFi_AP_Candidates.getCurrent().bits.isEmergencyFallback) { + #ifdef CUSTOM_EMERGENCY_FALLBACK_RESET_CREDENTIALS + const bool mustResetCredentials = CUSTOM_EMERGENCY_FALLBACK_RESET_CREDENTIALS; + #else + const bool mustResetCredentials = false; + #endif + #ifdef CUSTOM_EMERGENCY_FALLBACK_START_AP + const bool mustStartAP = CUSTOM_EMERGENCY_FALLBACK_START_AP; + #else + const bool mustStartAP = false; + #endif + if (mustStartAP) { + int allowedUptimeMinutes = 10; + #ifdef CUSTOM_EMERGENCY_FALLBACK_ALLOW_MINUTES_UPTIME + allowedUptimeMinutes = CUSTOM_EMERGENCY_FALLBACK_ALLOW_MINUTES_UPTIME; + #endif + if (getUptimeMinutes() < allowedUptimeMinutes) { + WiFiEventData.timerAPstart.setNow(); + } + } + if (mustResetCredentials && !WiFiEventData.performedClearWiFiCredentials) { + WiFiEventData.performedClearWiFiCredentials = true; + SecuritySettings.clearWiFiCredentials(); + SaveSecuritySettings(); + WiFiEventData.markDisconnect(WIFI_DISCONNECT_REASON_AUTH_EXPIRE); + WiFi_AP_Candidates.force_reload(); + } + } + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + const LongTermTimer::Duration connect_duration = WiFiEventData.last_wifi_connect_attempt_moment.timeDiff(WiFiEventData.lastConnectMoment); + String log = strformat( + F("WIFI : Connected! AP: %s (%s) Ch: %d"), + WiFi.SSID().c_str(), + WiFi.BSSIDstr().c_str(), + RTC.lastWiFiChannel); + + if ((connect_duration > 0ll) && (connect_duration < 30000000ll)) { + // Just log times when they make sense. + log += strformat( + F(" Duration: %d ms"), + static_cast(connect_duration / 1000)); + } + addLogMove(LOG_LEVEL_INFO, log); + } + +// WiFiEventData.last_wifi_connect_attempt_moment.clear(); + + if (Settings.UseRules) { + if (WiFiEventData.bssid_changed) { + eventQueue.add(F("WiFi#ChangedAccesspoint")); + } + + if (WiFiEventData.channel_changed) { + eventQueue.add(F("WiFi#ChangedWiFichannel")); + } + } + + if (useStaticIP()) { + WiFiEventData.markGotIP(); // in static IP config the got IP event is never fired. + } + saveToRTC(); + + logConnectionStatus(); +} + +void processGotIP() { + if (WiFiEventData.processedGotIP) { + return; + } + if (checkAndResetWiFi()) { + return; + } + + IPAddress ip = NetworkLocalIP(); + + if (!useStaticIP()) { + #ifdef ESP8266 + if (!ip.isSet()) { + #else + if (ip[0] == 0 && ip[1] == 0 && ip[2] == 0 && ip[3] == 0) { + #endif + return; + } + } + const IPAddress gw = WiFi.gatewayIP(); + const IPAddress subnet = WiFi.subnetMask(); + const LongTermTimer::Duration dhcp_duration = WiFiEventData.lastConnectMoment.timeDiff(WiFiEventData.lastGetIPmoment); + WiFiEventData.dns0_cache = WiFi.dnsIP(0); + WiFiEventData.dns1_cache = WiFi.dnsIP(1); + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + String log = strformat( + F("WIFI : %s (%s) GW: %s SN: %s DNS: %s"), + concat(useStaticIP() ? F("Static IP: ") : F("DHCP IP: "), formatIP(ip)).c_str(), + NetworkGetHostname().c_str(), + formatIP(gw).c_str(), + formatIP(subnet).c_str(), + getValue(LabelType::DNS).c_str()); + + if ((dhcp_duration > 0ll) && (dhcp_duration < 30000000ll)) { + // Just log times when they make sense. + log += strformat(F(" duration: %d ms"), static_cast(dhcp_duration / 1000)); + } + addLogMove(LOG_LEVEL_INFO, log); + } + + // Might not work in core 2.5.0 + // See https://github.com/esp8266/Arduino/issues/5839 + if ((Settings.IP_Octet != 0) && (Settings.IP_Octet != 255)) + { + ip[3] = Settings.IP_Octet; + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLogMove(LOG_LEVEL_INFO, concat(F("IP : Fixed IP octet:"), formatIP(ip))); + } + WiFi.config(ip, gw, subnet, WiFiEventData.dns0_cache, WiFiEventData.dns1_cache); + } + +#if FEATURE_MQTT + mqtt_reconnect_count = 0; + MQTTclient_should_reconnect = true; + timermqtt_interval = 100; + Scheduler.setIntervalTimer(SchedulerIntervalTimer_e::TIMER_MQTT); + scheduleNextMQTTdelayQueue(); +#endif // if FEATURE_MQTT + Scheduler.sendGratuitousARP_now(); + + if (Settings.UseRules) + { + eventQueue.add(F("WiFi#Connected")); + } + statusLED(true); + + // WiFi.scanDelete(); + + if (WiFiEventData.wifiSetup) { + // Wifi setup was active, Apparently these settings work. + WiFiEventData.wifiSetup = false; + SaveSecuritySettings(); + } + + if ((WiFiEventData.WiFiConnected() || WiFi.isConnected()) && hasIPaddr()) { + WiFiEventData.setWiFiGotIP(); + } + #if FEATURE_ESPEASY_P2P + refreshNodeList(); + #endif + logConnectionStatus(); +} + +#if FEATURE_USE_IPV6 +void processGotIPv6() { + if (!WiFiEventData.processedGotIP6) { + WiFiEventData.processedGotIP6 = true; + if (loglevelActiveFor(LOG_LEVEL_INFO)) + addLog(LOG_LEVEL_INFO, String(F("WIFI : STA got IP6 ")) + WiFiEventData.unprocessed_IP6.toString(true)); +#if FEATURE_ESPEASY_P2P +// updateUDPport(true); +#endif + } +} +#endif + +// A client disconnected from the AP on this node. +void processDisconnectAPmode() { + if (WiFiEventData.processedDisconnectAPmode) { return; } + WiFiEventData.processedDisconnectAPmode = true; + +#ifndef BUILD_NO_DEBUG + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + const int nrStationsConnected = WiFi.softAPgetStationNum(); + String log = F("AP Mode: Client disconnected: "); + log += WiFiEventData.lastMacDisconnectedAPmode.toString(); + log += F(" Connected devices: "); + log += nrStationsConnected; + if (nrStationsConnected == 0) { + log += F(" AP turn off timer: "); + log += WIFI_AP_OFF_TIMER_EXTENSION/1000; + log += 's'; + } + addLogMove(LOG_LEVEL_INFO, log); + } +#endif +} + +#ifdef USES_ESPEASY_NOW +void processProbeRequestAPmode() { + if (WiFiEventData.processedProbeRequestAPmode) { return; } + + const MAC_address mac(APModeProbeRequestReceived_list.front().mac); + const int rssi = APModeProbeRequestReceived_list.front().rssi; + + Nodes.setRSSI(mac, rssi); + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + String log = F("AP Mode: Probe Request: "); + log += mac.toString(); + log += F(" ("); + log += rssi; + log += F(" dBm)"); + addLog(LOG_LEVEL_INFO, log); + } + + +// static MAC_address last_probed_mac; +// if (last_probed_mac != mac) { +// last_probed_mac = mac; + + // FIXME TD-er: Must create an answer for ESPEasy-NOW node discovery + #ifdef USES_ESPEASY_NOW + if (Settings.UseESPEasyNow()) { + ESPEasy_now_handler.sendDiscoveryAnnounce( + mac, Nodes.getESPEasyNOW_channel()); + } + #endif +// } + + APModeProbeRequestReceived_list.pop_front(); + WiFiEventData.processedProbeRequestAPmode = APModeProbeRequestReceived_list.size() == 0; +} +#endif + +// Client connects to AP on this node +void processConnectAPmode() { + if (WiFiEventData.processedConnectAPmode) { return; } + WiFiEventData.processedConnectAPmode = true; + // Extend timer to switch off AP. + WiFiEventData.timerAPoff.setMillisFromNow(WIFI_AP_OFF_TIMER_DURATION); +#ifndef BUILD_NO_DEBUG + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + String log = F("AP Mode: Client connected: "); + log += WiFiEventData.lastMacConnectedAPmode.toString(); + log += F(" Connected devices: "); + log += WiFi.softAPgetStationNum(); + addLogMove(LOG_LEVEL_INFO, log); + } +#endif + + #if FEATURE_DNS_SERVER + // Start DNS, only used if the ESP has no valid WiFi config + // It will reply with it's own address on all DNS requests + // (captive portal concept) + if (!dnsServerActive) { + dnsServerActive = true; + dnsServer.start(DNS_PORT, "*", apIP); + } + #endif // if FEATURE_DNS_SERVER +} + +// Switch of AP mode when timeout reached and no client connected anymore. +void processDisableAPmode() { + if (!WiFiEventData.timerAPoff.isSet()) { return; } + + if (!WifiIsAP(WiFi.getMode())) { + return; + } + // disable AP after timeout and no clients connected. + if (WiFiEventData.timerAPoff.timeReached() && (WiFi.softAPgetStationNum() == 0)) { + setAP(false); + } + + if (!WifiIsAP(WiFi.getMode())) { + WiFiEventData.timerAPoff.clear(); + if (WiFiEventData.wifiConnectAttemptNeeded) { + // Force a reconnect cycle + WifiDisconnect(); + } + } +} + +void processScanDone() { + WiFi_AP_Candidates.load_knownCredentials(); + if (WiFiEventData.processedScanDone) { return; } + + + + // Better act on the scan done event, as it may get triggered for normal wifi begin calls. + int8_t scanCompleteStatus = WiFi.scanComplete(); + switch (scanCompleteStatus) { + case 0: // Nothing (yet) found + if (WiFiEventData.lastGetScanMoment.timeoutReached(5000)) { + WiFi.scanDelete(); + WiFiEventData.processedScanDone = true; + } + return; + case -1: // WIFI_SCAN_RUNNING + // FIXME TD-er: Set timeout... + if (WiFiEventData.lastGetScanMoment.timeoutReached(5000)) { + # ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_ERROR, F("WiFi : Scan Running Timeout")); + #endif + WiFi.scanDelete(); + WiFiEventData.processedScanDone = true; + } + return; + case -2: // WIFI_SCAN_FAILED + addLog(LOG_LEVEL_ERROR, F("WiFi : Scan failed")); + WiFi.scanDelete(); + WiFiEventData.processedScanDone = true; + return; + } + + WiFiEventData.lastGetScanMoment.setNow(); + WiFiEventData.processedScanDone = true; +# ifndef BUILD_NO_DEBUG + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLogMove(LOG_LEVEL_INFO, concat(F("WiFi : Scan finished, found: "), scanCompleteStatus)); + } +#endif + +#if !FEATURE_ESP8266_DIRECT_WIFI_SCAN + WiFi_AP_Candidates.process_WiFiscan(scanCompleteStatus); +#endif + WiFi_AP_Candidates.load_knownCredentials(); + + if (WiFi_AP_Candidates.addedKnownCandidate() && !NetworkConnected()) { + if (!WiFiEventData.wifiConnectInProgress) { + WiFiEventData.wifiConnectAttemptNeeded = true; + # ifndef BUILD_NO_DEBUG + if (WiFi_AP_Candidates.addedKnownCandidate()) { + addLog(LOG_LEVEL_INFO, F("WiFi : Added known candidate, try to connect")); + } + #endif +#ifdef ESP32 +// setSTA(false); +#endif + NetworkConnectRelaxed(); +#ifdef USES_ESPEASY_NOW + temp_disable_EspEasy_now_timer = millis() + 20000; +#endif + } + } else if (!WiFiEventData.wifiConnectInProgress && !NetworkConnected()) { + WiFiEventData.timerAPstart.setNow(); + } + + #ifdef USES_ESPEASY_NOW + if (Settings.UseESPEasyNow()) { + ESPEasy_now_handler.addPeerFromWiFiScan(); + if (!NetworkConnected()) { + if (WiFi_AP_Candidates.addedKnownCandidate()) { + WiFi_AP_Candidates.force_reload(); + if (!WiFiEventData.wifiConnectInProgress) { + // if (isESPEasy_now_only() || !ESPEasy_now_handler.active()) { + addLog(LOG_LEVEL_INFO, F("WiFi : Added known candidate, try to connect (mesh)")); + + WifiDisconnect(); + setAP(false); + ESPEasy_now_handler.end(); + + // Disable ESPEasy_now for 20 seconds to give opportunity to connect to WiFi. + WiFiEventData.wifiConnectAttemptNeeded = true; + temp_disable_EspEasy_now_timer = millis() + 20000; +// setSTA(false); + setNetworkMedium(Settings.NetworkMedium); + NetworkConnectRelaxed(); +// } + } + } else { + temp_disable_EspEasy_now_timer = 0; + setNetworkMedium(NetworkMedium_t::ESPEasyNOW_only); + } + } + } + #endif + +} + + + + diff --git a/src/src/ESPEasyCore/ESPEasyWifi_ProcessEvent.h b/src/src/ESPEasyCore/ESPEasyWifi_ProcessEvent.h index 826bda61d8..afd9bb4ee6 100644 --- a/src/src/ESPEasyCore/ESPEasyWifi_ProcessEvent.h +++ b/src/src/ESPEasyCore/ESPEasyWifi_ProcessEvent.h @@ -1,18 +1,21 @@ -#ifndef ESPEASYCORE_ESPEASYWIFI_PROCESSEVENT_H -#define ESPEASYCORE_ESPEASYWIFI_PROCESSEVENT_H - -#include "../../ESPEasy_common.h" - -void handle_unprocessedNetworkEvents(); -void processDisconnect(); -void processConnect(); -void processGotIP(); -#if FEATURE_USE_IPV6 -void processGotIPv6(); -#endif -void processDisconnectAPmode(); -void processConnectAPmode(); -void processDisableAPmode(); -void processScanDone(); - -#endif // ifndef ESPEASYCORE_ESPEASYWIFI_PROCESSEVENT_H +#ifndef ESPEASYCORE_ESPEASYWIFI_PROCESSEVENT_H +#define ESPEASYCORE_ESPEASYWIFI_PROCESSEVENT_H + +#include "../../ESPEasy_common.h" + +void handle_unprocessedNetworkEvents(); +void processDisconnect(); +void processConnect(); +void processGotIP(); +#if FEATURE_USE_IPV6 +void processGotIPv6(); +#endif +void processDisconnectAPmode(); +#ifdef USES_ESPEASY_NOW +void processProbeRequestAPmode(); +#endif +void processConnectAPmode(); +void processDisableAPmode(); +void processScanDone(); + +#endif // ifndef ESPEASYCORE_ESPEASYWIFI_PROCESSEVENT_H diff --git a/src/src/ESPEasyCore/ESPEasy_setup.cpp b/src/src/ESPEasyCore/ESPEasy_setup.cpp index c0fd3a8d0d..611186d7a7 100644 --- a/src/src/ESPEasyCore/ESPEasy_setup.cpp +++ b/src/src/ESPEasyCore/ESPEasy_setup.cpp @@ -1,701 +1,712 @@ -#include "../ESPEasyCore/ESPEasy_setup.h" - -#include "../../ESPEasy_fdwdecl.h" // Needed for PluginInit() and CPluginInit() - -#include "../../ESPEasy-Globals.h" -#include "../../_Plugin_Helper.h" -#include "../Commands/InternalCommands_decoder.h" -#include "../CustomBuild/CompiletimeDefines.h" -#include "../ESPEasyCore/ESPEasyGPIO.h" -#include "../ESPEasyCore/ESPEasyNetwork.h" -#include "../ESPEasyCore/ESPEasyRules.h" -#include "../ESPEasyCore/ESPEasyWifi.h" -#include "../ESPEasyCore/ESPEasyWifi_ProcessEvent.h" -#include "../ESPEasyCore/Serial.h" -#include "../Globals/Cache.h" -#include "../Globals/ESPEasy_Console.h" -#include "../Globals/ESPEasyWiFiEvent.h" -#include "../Globals/ESPEasy_time.h" -#include "../Globals/NetworkState.h" -#include "../Globals/RTC.h" -#include "../Globals/Statistics.h" -#include "../Globals/WiFi_AP_Candidates.h" -#include "../Helpers/_CPlugin_init.h" -#include "../Helpers/_NPlugin_init.h" -#include "../Helpers/_Plugin_init.h" -#include "../Helpers/DeepSleep.h" -#include "../Helpers/ESPEasyRTC.h" -#include "../Helpers/ESPEasy_FactoryDefault.h" -#include "../Helpers/ESPEasy_Storage.h" -#include "../Helpers/ESPEasy_checks.h" -#include "../Helpers/Hardware_device_info.h" -#include "../Helpers/Memory.h" -#include "../Helpers/Misc.h" -#include "../Helpers/StringGenerator_System.h" -#include "../WebServer/ESPEasy_WebServer.h" - - -#ifdef USE_RTOS_MULTITASKING -# include "../Helpers/Networking.h" -# include "../Helpers/PeriodicalActions.h" -#endif // ifdef USE_RTOS_MULTITASKING - -#if FEATURE_ARDUINO_OTA -# include "../Helpers/OTA.h" -#endif // if FEATURE_ARDUINO_OTA - -#ifdef ESP32 - -# if ESP_IDF_VERSION_MAJOR < 5 -# include -# include -# include -# include -# else // if ESP_IDF_VERSION_MAJOR < 5 -# include -# include -# include -# endif // if ESP_IDF_VERSION_MAJOR < 5 - -# if CONFIG_IDF_TARGET_ESP32 -# if ESP_IDF_VERSION_MAJOR < 5 -# include "hal/efuse_ll.h" -# include "hal/efuse_hal.h" -# else // if ESP_IDF_VERSION_MAJOR < 5 -# include -# include - -# if ESP_IDF_VERSION > ESP_IDF_VERSION_VAL(5, 2, 0) - -// IDF5.3 fix esp_gpio_reserve used in init PSRAM. -# include "esp_private/esp_gpio_reserve.h" -# endif // if ESP_IDF_VERSION > ESP_IDF_VERSION_VAL(5, 2, 0) -# endif // if ESP_IDF_VERSION_MAJOR < 5 -# endif // if CONFIG_IDF_TARGET_ESP32 - -#endif // ifdef ESP32 - - -#ifdef USE_RTOS_MULTITASKING - -void RTOS_TaskServers(void *parameter) -{ - while (true) { - delay(100); - web_server.handleClient(); - # if FEATURE_ESPEASY_P2P - checkUDP(); - # endif // if FEATURE_ESPEASY_P2P - } -} - -void RTOS_TaskSerial(void *parameter) -{ - while (true) { - delay(100); - serial(); - } -} - -void RTOS_Task10ps(void *parameter) -{ - while (true) { - delay(100); - run10TimesPerSecond(); - } -} - -void RTOS_HandleSchedule(void *parameter) -{ - while (true) { - Scheduler.handle_schedule(); - } -} - -#endif // ifdef USE_RTOS_MULTITASKING - -/*********************************************************************************************\ -* ISR call back function for handling the watchdog. -\*********************************************************************************************/ -void sw_watchdog_callback(void *arg) -{ - yield(); // feed the WD - ++sw_watchdog_callback_count; -} - -/*********************************************************************************************\ -* SETUP -\*********************************************************************************************/ -void ESPEasy_setup() -{ -#if defined(ESP8266_DISABLE_EXTRA4K) || defined(USE_SECOND_HEAP) - - // disable_extra4k_at_link_time(); -#endif // if defined(ESP8266_DISABLE_EXTRA4K) || defined(USE_SECOND_HEAP) -#ifdef PHASE_LOCKED_WAVEFORM - enablePhaseLockedWaveform(); -#endif // ifdef PHASE_LOCKED_WAVEFORM -#ifdef USE_SECOND_HEAP - HeapSelectDram ephemeral; -#endif // ifdef USE_SECOND_HEAP -#ifdef ESP32 -# ifdef DISABLE_ESP32_BROWNOUT - DisableBrownout(); // Workaround possible weak LDO resulting in brownout detection during Wifi connection -# endif // DISABLE_ESP32_BROWNOUT - -# ifdef BOARD_HAS_PSRAM - psramInit(); -# endif // ifdef BOARD_HAS_PSRAM - -# if CONFIG_IDF_TARGET_ESP32 - - // restore GPIO16/17 if no PSRAM is found - if (!FoundPSRAM()) { - // test if the CPU is not pico - # if ESP_IDF_VERSION_MAJOR < 5 - uint32_t chip_ver = REG_GET_FIELD(EFUSE_BLK0_RDATA3_REG, EFUSE_RD_CHIP_VER_PKG); - uint32_t pkg_version = chip_ver & 0x7; - # else // if ESP_IDF_VERSION_MAJOR < 5 - uint32_t pkg_version = bootloader_common_get_chip_ver_pkg(); - uint32_t chip_ver = REG_GET_FIELD(EFUSE_BLK0_RDATA3_REG, EFUSE_RD_CHIP_PACKAGE); - # endif // if ESP_IDF_VERSION_MAJOR < 5 - - if (pkg_version <= 7) { // D0WD, S0WD, D2WD -#ifdef CORE32SOLO1 - gpio_num_t PSRAM_CLK = GPIO_NUM_NC; //GPIO_NUM_17; - gpio_num_t PSRAM_CS = GPIO_NUM_NC; //GPIO_NUM_16; -#else - gpio_num_t PSRAM_CLK = static_cast(CONFIG_D0WD_PSRAM_CLK_IO); - gpio_num_t PSRAM_CS = static_cast(CONFIG_D0WD_PSRAM_CS_IO); - - switch (pkg_version) - { - case EFUSE_RD_CHIP_VER_PKG_ESP32D2WDQ5: - PSRAM_CLK = static_cast(CONFIG_D2WD_PSRAM_CLK_IO); - PSRAM_CS = static_cast(CONFIG_D2WD_PSRAM_CS_IO); - break; - case EFUSE_RD_CHIP_VER_PKG_ESP32PICOD4: - case EFUSE_RD_CHIP_VER_PKG_ESP32PICOV302: - PSRAM_CLK = static_cast(GPIO_NUM_NC); - PSRAM_CS = static_cast(CONFIG_PICO_PSRAM_CS_IO); - break; - } -#endif -# if ESP_IDF_VERSION > ESP_IDF_VERSION_VAL(5, 2, 0) - - // Thanks Theo Arends from Tasmota - if (PSRAM_CLK != GPIO_NUM_NC) { - esp_gpio_revoke(BIT64(PSRAM_CLK) | BIT64(PSRAM_CS)); - } else { - if (PSRAM_CS != GPIO_NUM_NC) { - esp_gpio_revoke(BIT64(PSRAM_CS)); - } - } -# endif // if ESP_IDF_VERSION > ESP_IDF_VERSION_VAL(5, 2, 0) - - if (PSRAM_CLK != GPIO_NUM_NC) { - gpio_reset_pin(PSRAM_CLK); - } - if (PSRAM_CS != GPIO_NUM_NC) { - gpio_reset_pin(PSRAM_CS); - } - } - } -# endif // if CONFIG_IDF_TARGET_ESP32 - initADC(); -#endif // ESP32 -#ifndef BUILD_NO_RAM_TRACKER - lowestFreeStack = getFreeStackWatermark(); - lowestRAM = FreeMem(); -#endif // ifndef BUILD_NO_RAM_TRACKER - - /* - #ifdef ESP32 - { - ESPEasy_NVS_Helper preferences; - ResetFactoryDefaultPreference.init(preferences); - } - #endif - */ -#ifndef BUILD_NO_DEBUG - - // checkAll_internalCommands(); -#endif // ifndef BUILD_NO_DEBUG - - PluginSetup(); - CPluginSetup(); - - initWiFi(); - WiFiEventData.clearAll(); - -#ifndef BUILD_MINIMAL_OTA - run_compiletime_checks(); -#endif // ifndef BUILD_MINIMAL_OTA -#ifdef ESP8266 - - // ets_isr_attach(8, sw_watchdog_callback, nullptr); // Set a callback for feeding the watchdog. -#endif // ifdef ESP8266 - - - // Read ADC at boot, before WiFi tries to connect. - // see https://github.com/letscontrolit/ESPEasy/issues/2646 -#if FEATURE_ADC_VCC - vcc = ESP.getVcc() / 1000.0f; -#endif // if FEATURE_ADC_VCC -#ifdef ESP8266 - espeasy_analogRead(A0); -#endif // ifdef ESP8266 - - initAnalogWrite(); - - resetPluginTaskData(); - - #ifndef BUILD_NO_RAM_TRACKER - checkRAM(F("setup")); - #endif // ifndef BUILD_NO_RAM_TRACKER - ESPEasy_Console.begin(115200); - - // serialPrint("\n\n\nBOOOTTT\n\n\n"); - - initLog(); - #ifndef BUILD_NO_RAM_TRACKER - logMemUsageAfter(F("initLog()")); - #endif // ifndef BUILD_NO_RAM_TRACKER - #ifdef BOARD_HAS_PSRAM - - if (FoundPSRAM()) { - if (UsePSRAM()) { - addLog(LOG_LEVEL_INFO, F("Using PSRAM")); - } else { - addLog(LOG_LEVEL_ERROR, F("PSRAM found, unable to use")); - } - } - #endif // ifdef BOARD_HAS_PSRAM - - if (SpiffsSectors() < 32) - { - serialPrintln(F("\nNo (or too small) FS area..\nSystem Halted\nPlease reflash with 128k FS minimum!")); - - while (true) { - delay(1); - } - } - - emergencyReset(); - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("\n\n\rINIT : Booting version: "); - log += getValue(LabelType::BINARY_FILENAME); - log += F(", ("); - log += get_build_origin(); - log += F(") "); - log += getValue(LabelType::GIT_BUILD); - log += F(" ("); - log += getSystemLibraryString(); - log += ')'; - addLogMove(LOG_LEVEL_INFO, log); - log = F("INIT : Free RAM:"); - log += FreeMem(); - addLogMove(LOG_LEVEL_INFO, log); - } - - readBootCause(); - - { - String log; - - if (readFromRTC()) - { - RTC.bootFailedCount++; - RTC.bootCounter++; - lastMixedSchedulerId_beforereboot.mixed_id = RTC.lastMixedSchedulerId; - readUserVarFromRTC(); - log = concat(F("INIT : "), getLastBootCauseString()) + - concat(F(" #"), RTC.bootCounter); - - #ifndef BUILD_NO_DEBUG - log += F(" Last Action before Reboot: "); - log += ESPEasy_Scheduler::decodeSchedulerId(lastMixedSchedulerId_beforereboot); - log += F(" Last systime: "); - log += RTC.lastSysTime; - #endif // ifndef BUILD_NO_DEBUG - } - - // cold boot (RTC memory empty) - else - { - initRTC(); - - // cold boot situation - if (lastBootCause == BOOT_CAUSE_MANUAL_REBOOT) { // only set this if not set earlier during boot stage. - lastBootCause = BOOT_CAUSE_COLD_BOOT; - } - log = F("INIT : Cold Boot"); - } - - log += concat(F(" - Restart Reason: "), getResetReasonString()); - - RTC.deepSleepState = 0; - saveToRTC(); - - addLogMove(LOG_LEVEL_INFO, log); - } - #ifndef BUILD_NO_RAM_TRACKER - logMemUsageAfter(F("RTC init")); - #endif // ifndef BUILD_NO_RAM_TRACKER - - fileSystemCheck(); - #ifndef BUILD_NO_RAM_TRACKER - logMemUsageAfter(F("fileSystemCheck()")); - #endif // ifndef BUILD_NO_RAM_TRACKER - - // progMemMD5check(); - LoadSettings(); -#if FEATURE_DEFINE_SERIAL_CONSOLE_PORT - ESPEasy_Console.reInit(); -#endif // if FEATURE_DEFINE_SERIAL_CONSOLE_PORT - - #ifndef BUILD_NO_RAM_TRACKER - logMemUsageAfter(F("LoadSettings()")); - #endif // ifndef BUILD_NO_RAM_TRACKER - - addLog(LOG_LEVEL_INFO, concat(F("CPU Frequency: "), ESP.getCpuFreqMHz())); - - -#ifdef ESP32 -#ifndef CORE32SOLO1 - - // Configure dynamic frequency scaling: - // maximum and minimum frequencies are set in sdkconfig, - // automatic light sleep is enabled if tickless idle support is enabled. - ESP_PM_CONFIG_T pm_config = { - .max_freq_mhz = getCPU_MaxFreqMHz(), - .min_freq_mhz = Settings.EcoPowerMode() ? getCPU_MinFreqMHz() : getCPU_MaxFreqMHz(), -# if CONFIG_FREERTOS_USE_TICKLESS_IDLE - .light_sleep_enable = Settings.EcoPowerMode() -# else - .light_sleep_enable = false -# endif // if CONFIG_FREERTOS_USE_TICKLESS_IDLE - }; - esp_pm_configure(&pm_config); -#endif -#endif // ifdef ESP32 - - - #ifndef BUILD_NO_RAM_TRACKER - checkRAM(F("hardwareInit")); - #endif // ifndef BUILD_NO_RAM_TRACKER - hardwareInit(); - #ifndef BUILD_NO_RAM_TRACKER - logMemUsageAfter(F("hardwareInit()")); - #endif // ifndef BUILD_NO_RAM_TRACKER - - node_time.restoreFromRTC(); - - Settings.UseRTOSMultitasking = false; // For now, disable it, we experience heap corruption. - - if ((RTC.bootFailedCount > 10) && (RTC.bootCounter > 10)) { - uint8_t toDisable = RTC.bootFailedCount - 10; - toDisable = disablePlugin(toDisable); - - if (toDisable != 0) { - toDisable = disableController(toDisable); - } - #if FEATURE_NOTIFIER - - if (toDisable != 0) { - toDisable = disableNotification(toDisable); - } - #endif // if FEATURE_NOTIFIER - - if (toDisable != 0) { - toDisable = disableRules(toDisable); - } - - if (toDisable != 0) { - toDisable = disableAllPlugins(toDisable); - } - - if (toDisable != 0) { - toDisable = disableAllControllers(toDisable); - } -#if FEATURE_NOTIFIER - - if (toDisable != 0) { - toDisable = disableAllNotifications(toDisable); - } -#endif // if FEATURE_NOTIFIER - } - #if FEATURE_ETHERNET - - // This ensures, that changing WIFI OR ETHERNET MODE happens properly only after reboot. Changing without reboot would not be a good idea. - // This only works after LoadSettings(); - // Do not call setNetworkMedium here as that may try to clean up settings. - active_network_medium = Settings.NetworkMedium; - #else // if FEATURE_ETHERNET - - if (Settings.NetworkMedium == NetworkMedium_t::Ethernet) { - Settings.NetworkMedium = NetworkMedium_t::WIFI; - } - #endif // if FEATURE_ETHERNET - - setNetworkMedium(Settings.NetworkMedium); - - bool initWiFi = active_network_medium == NetworkMedium_t::WIFI; - - // FIXME TD-er: Must add another check for 'delayed start WiFi' for poorly designed ESP8266 nodes. - - - if (initWiFi) { -#ifdef ESP32 - // FIXME TD-er: Disabled for now, as this may not return and thus block the ESP forever. - // See: https://github.com/espressif/esp-idf/issues/15862 - //check_and_update_WiFi_Calibration(); -#endif - - WiFi_AP_Candidates.clearCache(); - WiFi_AP_Candidates.load_knownCredentials(); - setSTA(true); - - if (!WiFi_AP_Candidates.hasCandidates()) { - WiFiEventData.wifiSetup = true; - RTC.clearLastWiFi(); // Must scan all channels - // Wait until scan has finished to make sure as many as possible are found - // We're still in the setup phase, so nothing else is taking resources of the ESP. - WifiScan(false); - WiFiEventData.lastScanMoment.clear(); - } - - // Always perform WiFi scan - // It appears reconnecting from RTC may take just as long to be able to send first packet as performing a scan first and then connect. - // Perhaps the WiFi radio needs some time to stabilize first? - if (!WiFi_AP_Candidates.hasCandidates()) { - WifiScan(false, RTC.lastWiFiChannel); - } - WiFi_AP_Candidates.clearCache(); - processScanDone(); - WiFi_AP_Candidates.load_knownCredentials(); - - if (!WiFi_AP_Candidates.hasCandidates()) { - #ifndef BUILD_MINIMAL_OTA - addLog(LOG_LEVEL_INFO, F("Setup: Scan all channels")); - #endif - WifiScan(false); - } - - // setWifiMode(WIFI_OFF); - } - #ifndef BUILD_NO_RAM_TRACKER - logMemUsageAfter(F("WifiScan()")); - #endif // ifndef BUILD_NO_RAM_TRACKER - - - // setWifiMode(WIFI_STA); - checkRuleSets(); - #ifndef BUILD_NO_RAM_TRACKER - logMemUsageAfter(F("checkRuleSets()")); - #endif // ifndef BUILD_NO_RAM_TRACKER - - - // if different version, eeprom settings structure has changed. Full Reset needed - // on a fresh ESP module eeprom values are set to 255. Version results into -1 (signed int) - if ((Settings.Version != VERSION) || (Settings.PID != ESP_PROJECT_PID)) - { - // Direct Serial is allowed here, since this is only an emergency task. - serialPrint(F("\nPID:")); - serialPrintln(String(Settings.PID)); - serialPrint(F("Version:")); - serialPrintln(String(Settings.Version)); - serialPrintln(F("INIT : Incorrect PID or version!")); - delay(1000); - ResetFactory(); - } - - initSerial(); - #ifndef BUILD_NO_RAM_TRACKER - logMemUsageAfter(F("initSerial()")); - #endif // ifndef BUILD_NO_RAM_TRACKER - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - addLogMove(LOG_LEVEL_INFO, concat(F("INIT : Free RAM:"), FreeMem())); - } - -#ifndef BUILD_NO_DEBUG - - if (Settings.UseSerial && (Settings.SerialLogLevel >= LOG_LEVEL_DEBUG_MORE)) { - ESPEasy_Console.setDebugOutput(true); - } -#endif // ifndef BUILD_NO_DEBUG - - timermqtt_interval = 100; // Interval for checking MQTT - timerAwakeFromDeepSleep = millis(); - CPluginInit(); - #ifndef BUILD_NO_RAM_TRACKER - logMemUsageAfter(F("CPluginInit()")); - #endif // ifndef BUILD_NO_RAM_TRACKER - #if FEATURE_NOTIFIER - NPluginInit(); - # ifndef BUILD_NO_RAM_TRACKER - logMemUsageAfter(F("NPluginInit()")); - # endif - #endif // if FEATURE_NOTIFIER - - PluginInit(); - - initSerial(); // Plugins may have altered serial, so re-init serial - - #ifndef BUILD_NO_RAM_TRACKER - logMemUsageAfter(F("PluginInit()")); - #endif - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log; - if (reserve_special(log, 80)) { - log += concat(F("INFO : Plugins: "), getDeviceCount() + 1); - log += ' '; - log += getPluginDescriptionString(); - log += F(" ("); - log += getSystemLibraryString(); - log += ')'; - addLogMove(LOG_LEVEL_INFO, log); - } - } - - /* - if ((getDeviceCount() + 1) >= PLUGIN_MAX) { - addLog(LOG_LEVEL_ERROR, concat(F("Programming error! - Increase PLUGIN_MAX ("), getDeviceCount()) + ')'); - } - */ - - clearAllCaches(); - #ifndef BUILD_NO_RAM_TRACKER - logMemUsageAfter(F("clearAllCaches()")); - #endif // ifndef BUILD_NO_RAM_TRACKER - - if (Settings.UseRules && isDeepSleepEnabled()) - { - String event = F("System#NoSleep="); - event += Settings.deepSleep_wakeTime; - rulesProcessing(event); // TD-er: Process events in the setup() now. - } - - if (Settings.UseRules) - { - String event = F("System#Wake"); - rulesProcessing(event); // TD-er: Process events in the setup() now. - } - #ifdef ESP32 - - if (Settings.UseRules) - { - const uint32_t gpio_strap = GPIO_REG_READ(GPIO_STRAP_REG); - - // BOOT_MODE_GET(); - - // Event values: - // ESP32 : GPIO-5, GPIO-15, GPIO-4, GPIO-2, GPIO-0, GPIO-12 - // ESP32-C3: bit 0: GPIO2, bit 2: GPIO8, bit 3: GPIO9 - // ESP32-S2: Unclear what bits represent which strapping state. - // ESP32-S3: bit5 ~ bit2 correspond to strapping pins GPIO3, GPIO45, GPIO0, and GPIO46 respectively. - String event = F("System#BootMode="); - event += bitRead(gpio_strap, 0); - event += ','; - event += bitRead(gpio_strap, 1); - event += ','; - event += bitRead(gpio_strap, 2); - event += ','; - event += bitRead(gpio_strap, 3); - event += ','; - event += bitRead(gpio_strap, 4); - event += ','; - event += bitRead(gpio_strap, 5); - rulesProcessing(event); - } - #endif // ifdef ESP32 - - #if FEATURE_ETHERNET - - if (Settings.ETH_Pin_power_rst != -1) { - GPIO_Write(PLUGIN_GPIO, Settings.ETH_Pin_power_rst, 1); - } - - #endif // if FEATURE_ETHERNET - - NetworkConnectRelaxed(); - #ifndef BUILD_NO_RAM_TRACKER - logMemUsageAfter(F("NetworkConnectRelaxed()")); - #endif // ifndef BUILD_NO_RAM_TRACKER - - setWebserverRunning(true); - #ifndef BUILD_NO_RAM_TRACKER - logMemUsageAfter(F("setWebserverRunning()")); - #endif // ifndef BUILD_NO_RAM_TRACKER - - - #if FEATURE_REPORTING - ReportStatus(); - #endif // if FEATURE_REPORTING - - #if FEATURE_ARDUINO_OTA - ArduinoOTAInit(); - # ifndef BUILD_NO_RAM_TRACKER - logMemUsageAfter(F("ArduinoOTAInit()")); - # endif - #endif // if FEATURE_ARDUINO_OTA - - if (node_time.systemTimePresent()) { - node_time.initTime(); - #ifndef BUILD_NO_RAM_TRACKER - logMemUsageAfter(F("node_time.initTime()")); - #endif // ifndef BUILD_NO_RAM_TRACKER - } - - if (Settings.UseRules) - { - String event = F("System#Boot"); - rulesProcessing(event); // TD-er: Process events in the setup() now. - #ifndef BUILD_NO_RAM_TRACKER - logMemUsageAfter(F("rulesProcessing(System#Boot)")); - #endif // ifndef BUILD_NO_RAM_TRACKER - } - - writeDefaultCSS(); - #ifndef BUILD_NO_RAM_TRACKER - logMemUsageAfter(F("writeDefaultCSS()")); - #endif // ifndef BUILD_NO_RAM_TRACKER - - - #ifdef USE_RTOS_MULTITASKING - UseRTOSMultitasking = Settings.UseRTOSMultitasking; - - if (UseRTOSMultitasking) { - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - addLog(LOG_LEVEL_INFO, F("RTOS : Launching tasks")); - } - xTaskCreatePinnedToCore(RTOS_TaskServers, "RTOS_TaskServers", 16384, nullptr, 1, nullptr, 1); - xTaskCreatePinnedToCore(RTOS_TaskSerial, "RTOS_TaskSerial", 8192, nullptr, 1, nullptr, 1); - xTaskCreatePinnedToCore(RTOS_Task10ps, "RTOS_Task10ps", 8192, nullptr, 1, nullptr, 1); - xTaskCreatePinnedToCore( - RTOS_HandleSchedule, /* Function to implement the task */ - "RTOS_HandleSchedule", /* Name of the task */ - 16384, /* Stack size in words */ - nullptr, /* Task input parameter */ - 1, /* Priority of the task */ - nullptr, /* Task handle. */ - 1); /* Core where the task should run */ - } - #endif // ifdef USE_RTOS_MULTITASKING - - // Start the interval timers at N msec from now. - // Make sure to start them at some time after eachother, - // since they will keep running at the same interval. - Scheduler.setIntervalTimerOverride(SchedulerIntervalTimer_e::TIMER_20MSEC, 5); // timer for periodic actions 50 x per/sec - Scheduler.setIntervalTimerOverride(SchedulerIntervalTimer_e::TIMER_100MSEC, 66); // timer for periodic actions 10 x per/sec - Scheduler.setIntervalTimerOverride(SchedulerIntervalTimer_e::TIMER_1SEC, 777); // timer for periodic actions once per/sec - Scheduler.setIntervalTimerOverride(SchedulerIntervalTimer_e::TIMER_30SEC, 1333); // timer for watchdog once per 30 sec - Scheduler.setIntervalTimerOverride(SchedulerIntervalTimer_e::TIMER_MQTT, 88); // timer for interaction with MQTT - Scheduler.setIntervalTimerOverride(SchedulerIntervalTimer_e::TIMER_STATISTICS, 2222); - #ifndef BUILD_NO_RAM_TRACKER - logMemUsageAfter(F("Scheduler.setIntervalTimerOverride")); - #endif // ifndef BUILD_NO_RAM_TRACKER -} +#include "../ESPEasyCore/ESPEasy_setup.h" + +#include "../../ESPEasy-Globals.h" +#include "../../_Plugin_Helper.h" +#include "../Commands/InternalCommands_decoder.h" +#include "../CustomBuild/CompiletimeDefines.h" +#include "../ESPEasyCore/ESPEasyGPIO.h" +#include "../ESPEasyCore/ESPEasyNetwork.h" +#include "../ESPEasyCore/ESPEasyRules.h" +#include "../ESPEasyCore/ESPEasyWifi.h" +#include "../ESPEasyCore/ESPEasyWifi_ProcessEvent.h" +#include "../ESPEasyCore/Serial.h" +#include "../Globals/Cache.h" +#include "../Globals/ESPEasy_Console.h" +#include "../Globals/ESPEasyWiFiEvent.h" +#include "../Globals/ESPEasy_time.h" +#include "../Globals/NetworkState.h" +#include "../Globals/RTC.h" +#include "../Globals/Statistics.h" +#include "../Globals/WiFi_AP_Candidates.h" +#include "../Helpers/_CPlugin_init.h" +#include "../Helpers/_NPlugin_init.h" +#include "../Helpers/_Plugin_init.h" +#include "../Helpers/DeepSleep.h" +#include "../Helpers/ESPEasyRTC.h" +#include "../Helpers/ESPEasy_FactoryDefault.h" +#include "../Helpers/ESPEasy_Storage.h" +#include "../Helpers/ESPEasy_checks.h" +#include "../Helpers/Hardware_device_info.h" +#include "../Helpers/Memory.h" +#include "../Helpers/Misc.h" +#include "../Helpers/StringGenerator_System.h" +#include "../WebServer/ESPEasy_WebServer.h" + + +#ifdef USE_RTOS_MULTITASKING +# include "../Helpers/Networking.h" +# include "../Helpers/PeriodicalActions.h" +#endif // ifdef USE_RTOS_MULTITASKING + +#if FEATURE_ARDUINO_OTA +# include "../Helpers/OTA.h" +#endif // if FEATURE_ARDUINO_OTA + +#ifdef ESP32 + +# if ESP_IDF_VERSION_MAJOR < 5 +# include +# include +# include +# include +# else // if ESP_IDF_VERSION_MAJOR < 5 +# include +# include +# include +# endif // if ESP_IDF_VERSION_MAJOR < 5 + +# if CONFIG_IDF_TARGET_ESP32 +# if ESP_IDF_VERSION_MAJOR < 5 +# include "hal/efuse_ll.h" +# include "hal/efuse_hal.h" +# else // if ESP_IDF_VERSION_MAJOR < 5 +# include +# include + +# if ESP_IDF_VERSION > ESP_IDF_VERSION_VAL(5, 2, 0) + +// IDF5.3 fix esp_gpio_reserve used in init PSRAM. +# include "esp_private/esp_gpio_reserve.h" +# endif // if ESP_IDF_VERSION > ESP_IDF_VERSION_VAL(5, 2, 0) +# endif // if ESP_IDF_VERSION_MAJOR < 5 +# endif // if CONFIG_IDF_TARGET_ESP32 + +#endif // ifdef ESP32 + + +#ifdef USE_RTOS_MULTITASKING + +void RTOS_TaskServers(void *parameter) +{ + while (true) { + delay(100); + web_server.handleClient(); + # if FEATURE_ESPEASY_P2P + checkUDP(); + # endif // if FEATURE_ESPEASY_P2P + } +} + +void RTOS_TaskSerial(void *parameter) +{ + while (true) { + delay(100); + serial(); + } +} + +void RTOS_Task10ps(void *parameter) +{ + while (true) { + delay(100); + run10TimesPerSecond(); + } +} + +void RTOS_HandleSchedule(void *parameter) +{ + while (true) { + Scheduler.handle_schedule(); + } +} + +#endif // ifdef USE_RTOS_MULTITASKING + +/*********************************************************************************************\ +* ISR call back function for handling the watchdog. +\*********************************************************************************************/ +void sw_watchdog_callback(void *arg) +{ + yield(); // feed the WD + ++sw_watchdog_callback_count; +} + +/*********************************************************************************************\ +* SETUP +\*********************************************************************************************/ +void ESPEasy_setup() +{ +#if defined(ESP8266_DISABLE_EXTRA4K) || defined(USE_SECOND_HEAP) + + // disable_extra4k_at_link_time(); +#endif // if defined(ESP8266_DISABLE_EXTRA4K) || defined(USE_SECOND_HEAP) +#ifdef PHASE_LOCKED_WAVEFORM + enablePhaseLockedWaveform(); +#endif // ifdef PHASE_LOCKED_WAVEFORM +#ifdef USE_SECOND_HEAP + HeapSelectDram ephemeral; +#endif // ifdef USE_SECOND_HEAP +#ifdef ESP32 +# ifdef DISABLE_ESP32_BROWNOUT + DisableBrownout(); // Workaround possible weak LDO resulting in brownout detection during Wifi connection +# endif // DISABLE_ESP32_BROWNOUT + +# ifdef BOARD_HAS_PSRAM + psramInit(); +# endif // ifdef BOARD_HAS_PSRAM + +# if CONFIG_IDF_TARGET_ESP32 + + // restore GPIO16/17 if no PSRAM is found + if (!FoundPSRAM()) { + // test if the CPU is not pico + # if ESP_IDF_VERSION_MAJOR < 5 + uint32_t chip_ver = REG_GET_FIELD(EFUSE_BLK0_RDATA3_REG, EFUSE_RD_CHIP_VER_PKG); + uint32_t pkg_version = chip_ver & 0x7; + # else // if ESP_IDF_VERSION_MAJOR < 5 + uint32_t pkg_version = bootloader_common_get_chip_ver_pkg(); + uint32_t chip_ver = REG_GET_FIELD(EFUSE_BLK0_RDATA3_REG, EFUSE_RD_CHIP_PACKAGE); + # endif // if ESP_IDF_VERSION_MAJOR < 5 + + if (pkg_version <= 7) { // D0WD, S0WD, D2WD +#ifdef CORE32SOLO1 + gpio_num_t PSRAM_CLK = GPIO_NUM_NC; //GPIO_NUM_17; + gpio_num_t PSRAM_CS = GPIO_NUM_NC; //GPIO_NUM_16; +#else + gpio_num_t PSRAM_CLK = static_cast(CONFIG_D0WD_PSRAM_CLK_IO); + gpio_num_t PSRAM_CS = static_cast(CONFIG_D0WD_PSRAM_CS_IO); + + switch (pkg_version) + { + case EFUSE_RD_CHIP_VER_PKG_ESP32D2WDQ5: + PSRAM_CLK = static_cast(CONFIG_D2WD_PSRAM_CLK_IO); + PSRAM_CS = static_cast(CONFIG_D2WD_PSRAM_CS_IO); + break; + case EFUSE_RD_CHIP_VER_PKG_ESP32PICOD4: + case EFUSE_RD_CHIP_VER_PKG_ESP32PICOV302: + PSRAM_CLK = static_cast(GPIO_NUM_NC); + PSRAM_CS = static_cast(CONFIG_PICO_PSRAM_CS_IO); + break; + } +#endif +# if ESP_IDF_VERSION > ESP_IDF_VERSION_VAL(5, 2, 0) + + // Thanks Theo Arends from Tasmota + if (PSRAM_CLK != GPIO_NUM_NC) { + esp_gpio_revoke(BIT64(PSRAM_CLK) | BIT64(PSRAM_CS)); + } else { + if (PSRAM_CS != GPIO_NUM_NC) { + esp_gpio_revoke(BIT64(PSRAM_CS)); + } + } +# endif // if ESP_IDF_VERSION > ESP_IDF_VERSION_VAL(5, 2, 0) + + if (PSRAM_CLK != GPIO_NUM_NC) { + gpio_reset_pin(PSRAM_CLK); + } + if (PSRAM_CS != GPIO_NUM_NC) { + gpio_reset_pin(PSRAM_CS); + } + } + } +# endif // if CONFIG_IDF_TARGET_ESP32 + initADC(); +#endif // ESP32 +#ifndef BUILD_NO_RAM_TRACKER + lowestFreeStack = getFreeStackWatermark(); + lowestRAM = FreeMem(); +#endif // ifndef BUILD_NO_RAM_TRACKER + + /* + #ifdef ESP32 + { + ESPEasy_NVS_Helper preferences; + ResetFactoryDefaultPreference.init(preferences); + } + #endif + */ +#ifndef BUILD_NO_DEBUG + + // checkAll_internalCommands(); +#endif // ifndef BUILD_NO_DEBUG + + PluginSetup(); + CPluginSetup(); + + initWiFi(); + WiFiEventData.clearAll(); + +#ifndef BUILD_MINIMAL_OTA + run_compiletime_checks(); +#endif // ifndef BUILD_MINIMAL_OTA +#ifdef ESP8266 + + // ets_isr_attach(8, sw_watchdog_callback, nullptr); // Set a callback for feeding the watchdog. +#endif // ifdef ESP8266 + + + // Read ADC at boot, before WiFi tries to connect. + // see https://github.com/letscontrolit/ESPEasy/issues/2646 +#if FEATURE_ADC_VCC + vcc = ESP.getVcc() / 1000.0f; +#endif // if FEATURE_ADC_VCC +#ifdef ESP8266 + espeasy_analogRead(A0); +#endif // ifdef ESP8266 + + initAnalogWrite(); + + resetPluginTaskData(); + + #ifndef BUILD_NO_RAM_TRACKER + checkRAM(F("setup")); + #endif // ifndef BUILD_NO_RAM_TRACKER + ESPEasy_Console.begin(115200); + + // serialPrint("\n\n\nBOOOTTT\n\n\n"); + + initLog(); + #ifndef BUILD_NO_RAM_TRACKER + logMemUsageAfter(F("initLog()")); + #endif // ifndef BUILD_NO_RAM_TRACKER + #ifdef BOARD_HAS_PSRAM + + if (FoundPSRAM()) { + if (UsePSRAM()) { + addLog(LOG_LEVEL_INFO, F("Using PSRAM")); + } else { + addLog(LOG_LEVEL_ERROR, F("PSRAM found, unable to use")); + } + } + #endif // ifdef BOARD_HAS_PSRAM + + if (SpiffsSectors() < 32) + { + serialPrintln(F("\nNo (or too small) FS area..\nSystem Halted\nPlease reflash with 128k FS minimum!")); + + while (true) { + delay(1); + } + } + + emergencyReset(); + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + String log = F("\n\n\rINIT : Booting version: "); + log += getValue(LabelType::BINARY_FILENAME); + log += F(", ("); + log += get_build_origin(); + log += F(") "); + log += getValue(LabelType::GIT_BUILD); + log += F(" ("); + log += getSystemLibraryString(); + log += ')'; + addLogMove(LOG_LEVEL_INFO, log); + log = F("INIT : Free RAM:"); + log += FreeMem(); + addLogMove(LOG_LEVEL_INFO, log); + } + + readBootCause(); + + { + String log; + + if (readFromRTC()) + { + RTC.bootFailedCount++; + RTC.bootCounter++; + lastMixedSchedulerId_beforereboot.mixed_id = RTC.lastMixedSchedulerId; + readUserVarFromRTC(); + log = concat(F("INIT : "), getLastBootCauseString()) + + concat(F(" #"), RTC.bootCounter); + + #ifndef BUILD_NO_DEBUG + log += F(" Last Action before Reboot: "); + log += ESPEasy_Scheduler::decodeSchedulerId(lastMixedSchedulerId_beforereboot); + log += F(" Last systime: "); + log += RTC.lastSysTime; + #endif // ifndef BUILD_NO_DEBUG + } + + // cold boot (RTC memory empty) + else + { + initRTC(); + + // cold boot situation + if (lastBootCause == BOOT_CAUSE_MANUAL_REBOOT) { // only set this if not set earlier during boot stage. + lastBootCause = BOOT_CAUSE_COLD_BOOT; + } + log = F("INIT : Cold Boot"); + } + + log += concat(F(" - Restart Reason: "), getResetReasonString()); + + RTC.deepSleepState = 0; + saveToRTC(); + + addLogMove(LOG_LEVEL_INFO, log); + } + #ifndef BUILD_NO_RAM_TRACKER + logMemUsageAfter(F("RTC init")); + #endif // ifndef BUILD_NO_RAM_TRACKER + + fileSystemCheck(); + #ifndef BUILD_NO_RAM_TRACKER + logMemUsageAfter(F("fileSystemCheck()")); + #endif // ifndef BUILD_NO_RAM_TRACKER + + // progMemMD5check(); + LoadSettings(); +#if FEATURE_DEFINE_SERIAL_CONSOLE_PORT + ESPEasy_Console.reInit(); +#endif // if FEATURE_DEFINE_SERIAL_CONSOLE_PORT + + #ifndef BUILD_NO_RAM_TRACKER + logMemUsageAfter(F("LoadSettings()")); + #endif // ifndef BUILD_NO_RAM_TRACKER + + addLog(LOG_LEVEL_INFO, concat(F("CPU Frequency: "), ESP.getCpuFreqMHz())); + + +#ifdef ESP32 +#ifndef CORE32SOLO1 + + // Configure dynamic frequency scaling: + // maximum and minimum frequencies are set in sdkconfig, + // automatic light sleep is enabled if tickless idle support is enabled. + ESP_PM_CONFIG_T pm_config = { + .max_freq_mhz = getCPU_MaxFreqMHz(), + .min_freq_mhz = Settings.EcoPowerMode() ? getCPU_MinFreqMHz() : getCPU_MaxFreqMHz(), +# if CONFIG_FREERTOS_USE_TICKLESS_IDLE + .light_sleep_enable = Settings.EcoPowerMode() +# else + .light_sleep_enable = false +# endif // if CONFIG_FREERTOS_USE_TICKLESS_IDLE + }; + esp_pm_configure(&pm_config); +#endif +#endif // ifdef ESP32 + + + #ifndef BUILD_NO_RAM_TRACKER + checkRAM(F("hardwareInit")); + #endif // ifndef BUILD_NO_RAM_TRACKER + hardwareInit(); + #ifndef BUILD_NO_RAM_TRACKER + logMemUsageAfter(F("hardwareInit()")); + #endif // ifndef BUILD_NO_RAM_TRACKER + + node_time.restoreFromRTC(); + + Settings.UseRTOSMultitasking = false; // For now, disable it, we experience heap corruption. + + if ((RTC.bootFailedCount > 10) && (RTC.bootCounter > 10)) { + uint8_t toDisable = RTC.bootFailedCount - 10; + toDisable = disablePlugin(toDisable); + + if (toDisable != 0) { + toDisable = disableController(toDisable); + } + #if FEATURE_NOTIFIER + + if (toDisable != 0) { + toDisable = disableNotification(toDisable); + } + #endif // if FEATURE_NOTIFIER + + if (toDisable != 0) { + toDisable = disableRules(toDisable); + } + + if (toDisable != 0) { + toDisable = disableAllPlugins(toDisable); + } + + if (toDisable != 0) { + toDisable = disableAllControllers(toDisable); + } +#if FEATURE_NOTIFIER + + if (toDisable != 0) { + toDisable = disableAllNotifications(toDisable); + } +#endif // if FEATURE_NOTIFIER + } + #if FEATURE_ETHERNET + + // This ensures, that changing WIFI OR ETHERNET MODE happens properly only after reboot. Changing without reboot would not be a good idea. + // This only works after LoadSettings(); + // Do not call setNetworkMedium here as that may try to clean up settings. + active_network_medium = Settings.NetworkMedium; + #else // if FEATURE_ETHERNET + + if (Settings.NetworkMedium == NetworkMedium_t::Ethernet) { + Settings.NetworkMedium = NetworkMedium_t::WIFI; + } + #endif // if FEATURE_ETHERNET + + setNetworkMedium(Settings.NetworkMedium); + + bool initWiFi = active_network_medium == NetworkMedium_t::WIFI; + + // FIXME TD-er: Must add another check for 'delayed start WiFi' for poorly designed ESP8266 nodes. + + + if (initWiFi) { +#ifdef ESP32 + // FIXME TD-er: Disabled for now, as this may not return and thus block the ESP forever. + // See: https://github.com/espressif/esp-idf/issues/15862 + //check_and_update_WiFi_Calibration(); +#endif + + WiFi_AP_Candidates.clearCache(); + WiFi_AP_Candidates.load_knownCredentials(); + setSTA(true); + + if (!WiFi_AP_Candidates.hasCandidates()) { + WiFiEventData.wifiSetup = true; + RTC.clearLastWiFi(); // Must scan all channels + // Wait until scan has finished to make sure as many as possible are found + // We're still in the setup phase, so nothing else is taking resources of the ESP. + WifiScan(false); + WiFiEventData.lastScanMoment.clear(); + } + + // Always perform WiFi scan + // It appears reconnecting from RTC may take just as long to be able to send first packet as performing a scan first and then connect. + // Perhaps the WiFi radio needs some time to stabilize first? + if (!WiFi_AP_Candidates.hasCandidates()) { + WifiScan(false, RTC.lastWiFiChannel); + } + WiFi_AP_Candidates.clearCache(); + processScanDone(); + WiFi_AP_Candidates.load_knownCredentials(); + + if (!WiFi_AP_Candidates.hasCandidates()) { + #ifndef BUILD_MINIMAL_OTA + addLog(LOG_LEVEL_INFO, F("Setup: Scan all channels")); + #endif + WifiScan(false); + } + + // setWifiMode(WIFI_OFF); + } + #ifndef BUILD_NO_RAM_TRACKER + logMemUsageAfter(F("WifiScan()")); + #endif // ifndef BUILD_NO_RAM_TRACKER + + + // setWifiMode(WIFI_STA); + checkRuleSets(); + #ifndef BUILD_NO_RAM_TRACKER + logMemUsageAfter(F("checkRuleSets()")); + #endif // ifndef BUILD_NO_RAM_TRACKER + + + // if different version, eeprom settings structure has changed. Full Reset needed + // on a fresh ESP module eeprom values are set to 255. Version results into -1 (signed int) + if ((Settings.Version != VERSION) || (Settings.PID != ESP_PROJECT_PID)) + { + // Direct Serial is allowed here, since this is only an emergency task. + serialPrint(F("\nPID:")); + serialPrintln(String(Settings.PID)); + serialPrint(F("Version:")); + serialPrintln(String(Settings.Version)); + serialPrintln(F("INIT : Incorrect PID or version!")); + delay(1000); + ResetFactory(); + } + + initSerial(); + #ifndef BUILD_NO_RAM_TRACKER + logMemUsageAfter(F("initSerial()")); + #endif // ifndef BUILD_NO_RAM_TRACKER + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLogMove(LOG_LEVEL_INFO, concat(F("INIT : Free RAM:"), FreeMem())); + } + +#ifndef BUILD_NO_DEBUG + + if (Settings.UseSerial && (Settings.SerialLogLevel >= LOG_LEVEL_DEBUG_MORE)) { + ESPEasy_Console.setDebugOutput(true); + } +#endif // ifndef BUILD_NO_DEBUG + + timermqtt_interval = 100; // Interval for checking MQTT + timerAwakeFromDeepSleep = millis(); + CPluginInit(); + #ifndef BUILD_NO_RAM_TRACKER + logMemUsageAfter(F("CPluginInit()")); + #endif // ifndef BUILD_NO_RAM_TRACKER + #if FEATURE_NOTIFIER + NPluginInit(); + # ifndef BUILD_NO_RAM_TRACKER + logMemUsageAfter(F("NPluginInit()")); + # endif + #endif // if FEATURE_NOTIFIER + + PluginInit(); + + initSerial(); // Plugins may have altered serial, so re-init serial + + #ifndef BUILD_NO_RAM_TRACKER + logMemUsageAfter(F("PluginInit()")); + #endif + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + String log; + if (reserve_special(log, 80)) { + log += concat(F("INFO : Plugins: "), getDeviceCount() + 1); + log += ' '; + log += getPluginDescriptionString(); + log += F(" ("); + log += getSystemLibraryString(); + log += ')'; + addLogMove(LOG_LEVEL_INFO, log); + } + } + + /* + if ((getDeviceCount() + 1) >= PLUGIN_MAX) { + addLog(LOG_LEVEL_ERROR, concat(F("Programming error! - Increase PLUGIN_MAX ("), getDeviceCount()) + ')'); + } + */ + + clearAllCaches(); + #ifndef BUILD_NO_RAM_TRACKER + logMemUsageAfter(F("clearAllCaches()")); + #endif // ifndef BUILD_NO_RAM_TRACKER + + #ifdef USES_ESPEASY_NOW + if (isESPEasy_now_only() || Settings.UseESPEasyNow()) { + RTC.lastWiFiSettingsIndex = 0; // Force to load the first settings. + RTC.lastWiFiChannel = 0; // Force slow connect + } +#endif + + #ifdef USES_ESPEASY_NOW + // Disable ESPEasy_now for 20 seconds to give opportunity to connect to WiFi. + temp_disable_EspEasy_now_timer = millis() + 20000; + #endif + + + if (Settings.UseRules && isDeepSleepEnabled()) + { + String event = F("System#NoSleep="); + event += Settings.deepSleep_wakeTime; + rulesProcessing(event); // TD-er: Process events in the setup() now. + } + + if (Settings.UseRules) + { + String event = F("System#Wake"); + rulesProcessing(event); // TD-er: Process events in the setup() now. + } + #ifdef ESP32 + + if (Settings.UseRules) + { + const uint32_t gpio_strap = GPIO_REG_READ(GPIO_STRAP_REG); + + // BOOT_MODE_GET(); + + // Event values: + // ESP32 : GPIO-5, GPIO-15, GPIO-4, GPIO-2, GPIO-0, GPIO-12 + // ESP32-C3: bit 0: GPIO2, bit 2: GPIO8, bit 3: GPIO9 + // ESP32-S2: Unclear what bits represent which strapping state. + // ESP32-S3: bit5 ~ bit2 correspond to strapping pins GPIO3, GPIO45, GPIO0, and GPIO46 respectively. + String event = F("System#BootMode="); + event += bitRead(gpio_strap, 0); + event += ','; + event += bitRead(gpio_strap, 1); + event += ','; + event += bitRead(gpio_strap, 2); + event += ','; + event += bitRead(gpio_strap, 3); + event += ','; + event += bitRead(gpio_strap, 4); + event += ','; + event += bitRead(gpio_strap, 5); + rulesProcessing(event); + } + #endif // ifdef ESP32 + + #if FEATURE_ETHERNET + + if (Settings.ETH_Pin_power_rst != -1) { + GPIO_Write(PLUGIN_GPIO, Settings.ETH_Pin_power_rst, 1); + } + + #endif // if FEATURE_ETHERNET + + NetworkConnectRelaxed(); + #ifndef BUILD_NO_RAM_TRACKER + logMemUsageAfter(F("NetworkConnectRelaxed()")); + #endif // ifndef BUILD_NO_RAM_TRACKER + + setWebserverRunning(true); + #ifndef BUILD_NO_RAM_TRACKER + logMemUsageAfter(F("setWebserverRunning()")); + #endif // ifndef BUILD_NO_RAM_TRACKER + + + #if FEATURE_REPORTING + ReportStatus(); + #endif // if FEATURE_REPORTING + + #if FEATURE_ARDUINO_OTA + ArduinoOTAInit(); + # ifndef BUILD_NO_RAM_TRACKER + logMemUsageAfter(F("ArduinoOTAInit()")); + # endif + #endif // if FEATURE_ARDUINO_OTA + + if (node_time.systemTimePresent()) { + node_time.initTime(); + #ifndef BUILD_NO_RAM_TRACKER + logMemUsageAfter(F("node_time.initTime()")); + #endif // ifndef BUILD_NO_RAM_TRACKER + } + + if (Settings.UseRules) + { + String event = F("System#Boot"); + rulesProcessing(event); // TD-er: Process events in the setup() now. + #ifndef BUILD_NO_RAM_TRACKER + logMemUsageAfter(F("rulesProcessing(System#Boot)")); + #endif // ifndef BUILD_NO_RAM_TRACKER + } + + writeDefaultCSS(); + #ifndef BUILD_NO_RAM_TRACKER + logMemUsageAfter(F("writeDefaultCSS()")); + #endif // ifndef BUILD_NO_RAM_TRACKER + + + #ifdef USE_RTOS_MULTITASKING + UseRTOSMultitasking = Settings.UseRTOSMultitasking; + + if (UseRTOSMultitasking) { + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLog(LOG_LEVEL_INFO, F("RTOS : Launching tasks")); + } + xTaskCreatePinnedToCore(RTOS_TaskServers, "RTOS_TaskServers", 16384, nullptr, 1, nullptr, 1); + xTaskCreatePinnedToCore(RTOS_TaskSerial, "RTOS_TaskSerial", 8192, nullptr, 1, nullptr, 1); + xTaskCreatePinnedToCore(RTOS_Task10ps, "RTOS_Task10ps", 8192, nullptr, 1, nullptr, 1); + xTaskCreatePinnedToCore( + RTOS_HandleSchedule, /* Function to implement the task */ + "RTOS_HandleSchedule", /* Name of the task */ + 16384, /* Stack size in words */ + nullptr, /* Task input parameter */ + 1, /* Priority of the task */ + nullptr, /* Task handle. */ + 1); /* Core where the task should run */ + } + #endif // ifdef USE_RTOS_MULTITASKING + + // Start the interval timers at N msec from now. + // Make sure to start them at some time after eachother, + // since they will keep running at the same interval. + Scheduler.setIntervalTimerOverride(SchedulerIntervalTimer_e::TIMER_20MSEC, 5); // timer for periodic actions 50 x per/sec + Scheduler.setIntervalTimerOverride(SchedulerIntervalTimer_e::TIMER_100MSEC, 66); // timer for periodic actions 10 x per/sec + Scheduler.setIntervalTimerOverride(SchedulerIntervalTimer_e::TIMER_1SEC, 777); // timer for periodic actions once per/sec + Scheduler.setIntervalTimerOverride(SchedulerIntervalTimer_e::TIMER_30SEC, 1333); // timer for watchdog once per 30 sec + Scheduler.setIntervalTimerOverride(SchedulerIntervalTimer_e::TIMER_MQTT, 88); // timer for interaction with MQTT + Scheduler.setIntervalTimerOverride(SchedulerIntervalTimer_e::TIMER_STATISTICS, 2222); + #ifndef BUILD_NO_RAM_TRACKER + logMemUsageAfter(F("Scheduler.setIntervalTimerOverride")); + #endif // ifndef BUILD_NO_RAM_TRACKER +} diff --git a/src/src/Globals/CPlugins.cpp b/src/src/Globals/CPlugins.cpp index 7ec9a0ac76..69ba220660 100644 --- a/src/src/Globals/CPlugins.cpp +++ b/src/src/Globals/CPlugins.cpp @@ -1,234 +1,236 @@ -#include "../Globals/CPlugins.h" - -#include "../../_Plugin_Helper.h" -#include "../DataStructs/ESPEasy_EventStruct.h" -#include "../DataStructs/TimingStats.h" -#include "../DataTypes/ESPEasy_plugin_functions.h" -#include "../ESPEasyCore/ESPEasy_Log.h" -#if FEATURE_MQTT_DISCOVER -# include "../Globals/MQTT.h" -#endif // if FEATURE_MQTT_DISCOVER -#include "../Globals/Settings.h" -#include "../Helpers/_CPlugin_init.h" - - -/********************************************************************************************\ - Call CPlugin functions - \*********************************************************************************************/ -bool CPluginCall(CPlugin::Function Function, struct EventStruct *event) { - #ifdef USE_SECOND_HEAP - HeapSelectDram ephemeral; - #endif // ifdef USE_SECOND_HEAP - - String dummy; - - return CPluginCall(Function, event, dummy); -} - -bool CPluginCall(CPlugin::Function Function, struct EventStruct *event, String& str) -{ - #ifdef USE_SECOND_HEAP - HeapSelectDram ephemeral; - #endif // ifdef USE_SECOND_HEAP - - struct EventStruct TempEvent; - - if (event == 0) { - event = &TempEvent; - } - - switch (Function) - { - case CPlugin::Function::CPLUGIN_PROTOCOL_ADD: - // only called from CPluginSetup() directly using protocolIndex - break; - - // calls to all active controllers - case CPlugin::Function::CPLUGIN_INIT_ALL: - case CPlugin::Function::CPLUGIN_UDP_IN: - case CPlugin::Function::CPLUGIN_INTERVAL: // calls to send stats information - case CPlugin::Function::CPLUGIN_GOT_CONNECTED: // calls to send autodetect information - case CPlugin::Function::CPLUGIN_GOT_INVALID: // calls to mark unit as invalid - case CPlugin::Function::CPLUGIN_FLUSH: - case CPlugin::Function::CPLUGIN_TEN_PER_SECOND: - case CPlugin::Function::CPLUGIN_FIFTY_PER_SECOND: - case CPlugin::Function::CPLUGIN_WRITE: - { - const bool success = Function != CPlugin::Function::CPLUGIN_WRITE; - - if (Function == CPlugin::Function::CPLUGIN_INIT_ALL) { - Function = CPlugin::Function::CPLUGIN_INIT; - } - - for (controllerIndex_t x = 0; x < CONTROLLER_MAX; x++) { - if ((Settings.Protocol[x] != 0) && Settings.ControllerEnabled[x]) { - event->ControllerIndex = x; - String command; - - if (Function == CPlugin::Function::CPLUGIN_WRITE) { - command = str; - } - - if (CPluginCall( - getProtocolIndex_from_ControllerIndex(x), - Function, - event, - command)) { - if (Function == CPlugin::Function::CPLUGIN_WRITE) { - // Need to stop when write call was handled - return true; - } - } - } - } - return success; - } - - // calls to specific controller - case CPlugin::Function::CPLUGIN_INIT: - case CPlugin::Function::CPLUGIN_EXIT: - case CPlugin::Function::CPLUGIN_PROTOCOL_TEMPLATE: - case CPlugin::Function::CPLUGIN_PROTOCOL_SEND: - case CPlugin::Function::CPLUGIN_PROTOCOL_RECV: - case CPlugin::Function::CPLUGIN_GET_DEVICENAME: - case CPlugin::Function::CPLUGIN_WEBFORM_LOAD: - case CPlugin::Function::CPLUGIN_WEBFORM_SAVE: - case CPlugin::Function::CPLUGIN_GET_PROTOCOL_DISPLAY_NAME: - case CPlugin::Function::CPLUGIN_TASK_CHANGE_NOTIFICATION: - case CPlugin::Function::CPLUGIN_WEBFORM_SHOW_HOST_CONFIG: - { - const controllerIndex_t controllerindex = event->ControllerIndex; - bool success = false; - - if (validControllerIndex(controllerindex)) { - if (Settings.ControllerEnabled[controllerindex] && supportedCPluginID(Settings.Protocol[controllerindex])) - { - if (Function == CPlugin::Function::CPLUGIN_PROTOCOL_SEND) { - checkDeviceVTypeForTask(event); - } - success = CPluginCall( - getProtocolIndex_from_ControllerIndex(controllerindex), - Function, - event, - str); - } - #ifdef ESP32 - - if (Function == CPlugin::Function::CPLUGIN_EXIT) { - Cache.clearControllerSettings(controllerindex); - } - #endif // ifdef ESP32 - #if FEATURE_MQTT_DISCOVER - - if (Function == CPlugin::Function::CPLUGIN_EXIT) { - if (mqttDiscoveryController == controllerindex) { - mqttDiscoveryController = INVALID_CONTROLLER_INDEX; // Reset - mqttDiscoverOnlyTask = INVALID_TASK_INDEX; - } - } - #endif // if FEATURE_MQTT_DISCOVER - } - return success; - } - - case CPlugin::Function::CPLUGIN_ACKNOWLEDGE: // calls to send acknowledge back to controller - - for (controllerIndex_t x = 0; x < CONTROLLER_MAX; x++) { - if (Settings.ControllerEnabled[x] && supportedCPluginID(Settings.Protocol[x])) { - CPluginCall( - getProtocolIndex_from_ControllerIndex(x), - Function, - event, - str); - } - } - return true; - case CPlugin::Function::CPLUGIN_CONNECT_SUCCESS: - case CPlugin::Function::CPLUGIN_CONNECT_FAIL: - break; - } - - return false; -} - -// Check if there is any controller enabled. -bool anyControllerEnabled() { - for (controllerIndex_t x = 0; x < CONTROLLER_MAX; x++) { - if (Settings.ControllerEnabled[x] && supportedCPluginID(Settings.Protocol[x])) { - return true; - } - } - return false; -} - -// Find first enabled controller index with this protocol -controllerIndex_t findFirstEnabledControllerWithId(cpluginID_t cpluginid) { - if (supportedCPluginID(cpluginid)) { - for (controllerIndex_t i = 0; i < CONTROLLER_MAX; i++) { - if ((Settings.Protocol[i] == cpluginid) && Settings.ControllerEnabled[i]) { - return i; - } - } - } - return INVALID_CONTROLLER_INDEX; -} - -bool validProtocolIndex(protocolIndex_t index) -{ - return validProtocolIndex_init(index); -} - -/* - bool validControllerIndex(controllerIndex_t index) - { - return index < CONTROLLER_MAX; - } - */ -bool validCPluginID(cpluginID_t cpluginID) -{ - return getProtocolIndex_from_CPluginID_(cpluginID) != INVALID_PROTOCOL_INDEX; -} - -bool supportedCPluginID(cpluginID_t cpluginID) -{ - return validProtocolIndex(getProtocolIndex_from_CPluginID_(cpluginID)); -} - -protocolIndex_t getProtocolIndex_from_ControllerIndex(controllerIndex_t index) { - if (validControllerIndex(index)) { - return getProtocolIndex_from_CPluginID_(Settings.Protocol[index]); - } - return INVALID_PROTOCOL_INDEX; -} - -protocolIndex_t getProtocolIndex_from_CPluginID(cpluginID_t cpluginID) { - return getProtocolIndex_from_CPluginID_(cpluginID); -} - -cpluginID_t getCPluginID_from_ProtocolIndex(protocolIndex_t index) { - return getCPluginID_from_ProtocolIndex_(index); -} - -cpluginID_t getCPluginID_from_ControllerIndex(controllerIndex_t index) { - const protocolIndex_t protocolIndex = getProtocolIndex_from_ControllerIndex(index); - - return getCPluginID_from_ProtocolIndex(protocolIndex); -} - -String getCPluginNameFromProtocolIndex(protocolIndex_t ProtocolIndex) { - String controllerName; - - if (validProtocolIndex(ProtocolIndex)) { - CPluginCall(ProtocolIndex, CPlugin::Function::CPLUGIN_GET_DEVICENAME, nullptr, controllerName); - } - return controllerName; -} - -String getCPluginNameFromCPluginID(cpluginID_t cpluginID) { - protocolIndex_t protocolIndex = getProtocolIndex_from_CPluginID_(cpluginID); - - if (!validProtocolIndex(protocolIndex)) { - return strformat(F("CPlugin %d not included in build"), cpluginID); - } - return getCPluginNameFromProtocolIndex(protocolIndex); -} +#include "../Globals/CPlugins.h" + +#include "../../_Plugin_Helper.h" +#include "../DataStructs/ESPEasy_EventStruct.h" +#include "../DataStructs/TimingStats.h" +#include "../DataTypes/ESPEasy_plugin_functions.h" +#include "../ESPEasyCore/ESPEasy_Log.h" +#if FEATURE_MQTT_DISCOVER +# include "../Globals/MQTT.h" +#endif // if FEATURE_MQTT_DISCOVER +#include "../Globals/Settings.h" +#include "../Helpers/_CPlugin_init.h" + + +/********************************************************************************************\ + Call CPlugin functions + \*********************************************************************************************/ +bool CPluginCall(CPlugin::Function Function, struct EventStruct *event) { + #ifdef USE_SECOND_HEAP + HeapSelectDram ephemeral; + #endif // ifdef USE_SECOND_HEAP + + String dummy; + + return CPluginCall(Function, event, dummy); +} + +bool CPluginCall(CPlugin::Function Function, struct EventStruct *event, String& str) +{ + #ifdef USE_SECOND_HEAP + HeapSelectDram ephemeral; + #endif // ifdef USE_SECOND_HEAP + + struct EventStruct TempEvent; + + if (event == 0) { + event = &TempEvent; + } + + switch (Function) + { + case CPlugin::Function::CPLUGIN_PROTOCOL_ADD: + // only called from CPluginSetup() directly using protocolIndex + break; + + // calls to all active controllers + case CPlugin::Function::CPLUGIN_INIT_ALL: + case CPlugin::Function::CPLUGIN_UDP_IN: + case CPlugin::Function::CPLUGIN_INTERVAL: // calls to send stats information + case CPlugin::Function::CPLUGIN_GOT_CONNECTED: // calls to send autodetect information + case CPlugin::Function::CPLUGIN_GOT_INVALID: // calls to mark unit as invalid + case CPlugin::Function::CPLUGIN_FLUSH: + case CPlugin::Function::CPLUGIN_TEN_PER_SECOND: + case CPlugin::Function::CPLUGIN_FIFTY_PER_SECOND: + case CPlugin::Function::CPLUGIN_WRITE: + { + // Only CPLUGIN_WRITE might return a false + const bool success = Function != CPlugin::Function::CPLUGIN_WRITE; + + if (Function == CPlugin::Function::CPLUGIN_INIT_ALL) { + Function = CPlugin::Function::CPLUGIN_INIT; + } + + for (controllerIndex_t x = 0; x < CONTROLLER_MAX; x++) { + if ((Settings.Protocol[x] != 0) && Settings.ControllerEnabled[x]) { + event->ControllerIndex = x; + String command; + + if (Function == CPlugin::Function::CPLUGIN_WRITE) { + command = str; + } + + if (CPluginCall( + getProtocolIndex_from_ControllerIndex(x), + Function, + event, + command)) { + if (Function == CPlugin::Function::CPLUGIN_WRITE) { + // Need to stop when write call was handled + return true; + } + } + } + } + return success; + } + + // calls to specific controller + case CPlugin::Function::CPLUGIN_INIT: + case CPlugin::Function::CPLUGIN_EXIT: + case CPlugin::Function::CPLUGIN_PROTOCOL_TEMPLATE: + case CPlugin::Function::CPLUGIN_PROTOCOL_SEND: + case CPlugin::Function::CPLUGIN_PROTOCOL_RECV: + case CPlugin::Function::CPLUGIN_GET_DEVICENAME: + case CPlugin::Function::CPLUGIN_WEBFORM_LOAD: + case CPlugin::Function::CPLUGIN_WEBFORM_SAVE: + case CPlugin::Function::CPLUGIN_GET_PROTOCOL_DISPLAY_NAME: + case CPlugin::Function::CPLUGIN_TASK_CHANGE_NOTIFICATION: + case CPlugin::Function::CPLUGIN_WEBFORM_SHOW_HOST_CONFIG: + { + const controllerIndex_t controllerindex = event->ControllerIndex; + bool success = false; + + if (validControllerIndex(controllerindex)) { + if (Settings.ControllerEnabled[controllerindex] && supportedCPluginID(Settings.Protocol[controllerindex])) + { + if (Function == CPlugin::Function::CPLUGIN_PROTOCOL_SEND) { + checkDeviceVTypeForTask(event); + } + success = CPluginCall( + getProtocolIndex_from_ControllerIndex(controllerindex), + Function, + event, + str); + } + #ifdef ESP32 + + if (Function == CPlugin::Function::CPLUGIN_EXIT) { + Cache.clearControllerSettings(controllerindex); + } + #endif // ifdef ESP32 + #if FEATURE_MQTT_DISCOVER + + if (Function == CPlugin::Function::CPLUGIN_EXIT) { + if (mqttDiscoveryController == controllerindex) { + mqttDiscoveryController = INVALID_CONTROLLER_INDEX; // Reset + mqttDiscoverOnlyTask = INVALID_TASK_INDEX; + } + } + #endif // if FEATURE_MQTT_DISCOVER + } + return success; + } + + case CPlugin::Function::CPLUGIN_ACKNOWLEDGE: // calls to send acknowledge back to controller + + for (controllerIndex_t x = 0; x < CONTROLLER_MAX; x++) { + if (Settings.ControllerEnabled[x] && supportedCPluginID(Settings.Protocol[x])) { + CPluginCall( + getProtocolIndex_from_ControllerIndex(x), + Function, + event, + str); + } + } + return true; + case CPlugin::Function::CPLUGIN_CONNECT_SUCCESS: + case CPlugin::Function::CPLUGIN_CONNECT_FAIL: + break; + } + + return false; +} + +// Check if there is any controller enabled. +bool anyControllerEnabled() { + for (controllerIndex_t x = 0; x < CONTROLLER_MAX; x++) { + if (Settings.ControllerEnabled[x] && supportedCPluginID(Settings.Protocol[x])) { + return true; + } + } + return false; +} + +// Find first enabled controller index with this protocol +controllerIndex_t findFirstEnabledControllerWithId(cpluginID_t cpluginid) { + if (supportedCPluginID(cpluginid)) { + for (controllerIndex_t i = 0; i < CONTROLLER_MAX; i++) { + if ((Settings.Protocol[i] == cpluginid) && Settings.ControllerEnabled[i]) { + return i; + } + } + } + return INVALID_CONTROLLER_INDEX; +} + +bool validProtocolIndex(protocolIndex_t index) +{ + return validProtocolIndex_init(index); +} + +/* + bool validControllerIndex(controllerIndex_t index) + { + return index < CONTROLLER_MAX; + } + */ +bool validCPluginID(cpluginID_t cpluginID) +{ + return getProtocolIndex_from_CPluginID_(cpluginID) != INVALID_PROTOCOL_INDEX; +} + +bool supportedCPluginID(cpluginID_t cpluginID) +{ + return validProtocolIndex(getProtocolIndex_from_CPluginID_(cpluginID)); +} + +protocolIndex_t getProtocolIndex_from_ControllerIndex(controllerIndex_t index) { + if (validControllerIndex(index)) { + return getProtocolIndex_from_CPluginID_(Settings.Protocol[index]); + } + return INVALID_PROTOCOL_INDEX; +} + +protocolIndex_t getProtocolIndex_from_CPluginID(cpluginID_t cpluginID) { + return getProtocolIndex_from_CPluginID_(cpluginID); +} + +cpluginID_t getCPluginID_from_ProtocolIndex(protocolIndex_t index) { + return getCPluginID_from_ProtocolIndex_(index); +} + +cpluginID_t getCPluginID_from_ControllerIndex(controllerIndex_t index) { + const protocolIndex_t protocolIndex = getProtocolIndex_from_ControllerIndex(index); + + return getCPluginID_from_ProtocolIndex(protocolIndex); +} + + +String getCPluginNameFromProtocolIndex(protocolIndex_t ProtocolIndex) { + String controllerName; + + if (validProtocolIndex(ProtocolIndex)) { + CPluginCall(ProtocolIndex, CPlugin::Function::CPLUGIN_GET_DEVICENAME, nullptr, controllerName); + } + return controllerName; +} + +String getCPluginNameFromCPluginID(cpluginID_t cpluginID) { + protocolIndex_t protocolIndex = getProtocolIndex_from_CPluginID_(cpluginID); + + if (!validProtocolIndex(protocolIndex)) { + return strformat(F("CPlugin %d not included in build"), cpluginID); + } + return getCPluginNameFromProtocolIndex(protocolIndex); +} diff --git a/src/src/Globals/CPlugins.h b/src/src/Globals/CPlugins.h index 31e9ab3ae1..5e86e47d18 100644 --- a/src/src/Globals/CPlugins.h +++ b/src/src/Globals/CPlugins.h @@ -1,69 +1,77 @@ -#ifndef GLOBALS_CPLUGIN_H -#define GLOBALS_CPLUGIN_H - -#include "../../ESPEasy_common.h" - -#include "../CustomBuild/ESPEasyLimits.h" -#include "../DataStructs/ControllerSettingsStruct.h" -#include "../DataTypes/ESPEasy_plugin_functions.h" -#include "../DataTypes/CPluginID.h" -#include "../DataTypes/ControllerIndex.h" -#include "../DataTypes/ProtocolIndex.h" - - -/********************************************************************************************\ - Structures to address the Cplugins (controllers) and their configurations. - - A build of ESPeasy may not have all Cplugins included. - So there has to be some administration to keep track of what Cplugin is present - and how to address it. - - We have: - - CPlugin, like _C001.ino. - - Controller -> A selected instance of a CPlugin (analog to "task" for plugins, shown in the Controllers tab in the web interface) - - Protocol -> A CPlugin included in the build. - - We have the following one-to-one relations: - - CPlugin_id_to_ProtocolIndex - Map from CPlugin ID to Protocol Index. - - ProtocolIndex_to_CPlugin_id - Vector from ProtocolIndex to CPlugin ID. - - CPlugin_ptr - Array of function pointers to call Cplugins. - - Protocol - Vector of ProtocolStruct containing Cplugin specific information. - \*********************************************************************************************/ - - -bool CPluginCall(CPlugin::Function Function, - struct EventStruct *event); -bool CPluginCall(CPlugin::Function Function, - struct EventStruct *event, - String & str); -bool CPluginCall(protocolIndex_t protocolIndex, - CPlugin::Function Function, - struct EventStruct *event, - String & str); - -bool anyControllerEnabled(); -controllerIndex_t findFirstEnabledControllerWithId(cpluginID_t cpluginid); - -bool validProtocolIndex(protocolIndex_t index); - - -// bool validControllerIndex(controllerIndex_t index); -#define validControllerIndex(C_X) ((C_X) < CONTROLLER_MAX) - -// Check whether CPlugin is included in build. -bool validCPluginID(cpluginID_t cpluginID); - -// Check if cplugin is included in build. -// N.B. Invalid cplugin is also not considered supported. -// This is essentially (validCPluginID && validProtocolIndex) -bool supportedCPluginID(cpluginID_t cpluginID); -protocolIndex_t getProtocolIndex_from_ControllerIndex(controllerIndex_t index); -protocolIndex_t getProtocolIndex_from_CPluginID(cpluginID_t cpluginID); -cpluginID_t getCPluginID_from_ProtocolIndex(protocolIndex_t index); -cpluginID_t getCPluginID_from_ControllerIndex(controllerIndex_t index); - -String getCPluginNameFromProtocolIndex(protocolIndex_t ProtocolIndex); -String getCPluginNameFromCPluginID(cpluginID_t cpluginID); - - -#endif // GLOBALS_CPLUGIN_H +#ifndef GLOBALS_CPLUGIN_H +#define GLOBALS_CPLUGIN_H + +#include "../../ESPEasy_common.h" + +#include "../CustomBuild/ESPEasyLimits.h" +#include "../DataStructs/ControllerSettingsStruct.h" +#include "../DataTypes/ESPEasy_plugin_functions.h" +#include "../DataTypes/CPluginID.h" +#include "../DataTypes/ControllerIndex.h" +#include "../DataTypes/ProtocolIndex.h" + + +/********************************************************************************************\ + Structures to address the Cplugins (controllers) and their configurations. + + A build of ESPeasy may not have all Cplugins included. + So there has to be some administration to keep track of what Cplugin is present + and how to address it. + + We have: + - CPlugin, like _C001.ino. + - Controller -> A selected instance of a CPlugin (analog to "task" for plugins, shown in the Controllers tab in the web interface) + - Protocol -> A CPlugin included in the build. + + Addressing these: + - cpluginID_t -> Unique ID of a Cplugin, like '1' for _C001.ino. '0' is the invalid cpluginID_t + - protocolIndex_t -> Counter for the included CPlugins in the build. + Highest used protocolIndex_t will thus always be at most the highest CPluginID. + + We have the following one-to-one relations: + - CPlugin_id_to_ProtocolIndex - Vector from CPlugin ID to Protocol Index. + - ProtocolIndex_to_CPlugin_id - Vector from ProtocolIndex to CPlugin ID. + - CPlugin_ptr - Array of function pointers to call Cplugins. + - Protocol - Vector of ProtocolStruct containing Cplugin specific information. + + Since we only have a limited nr of CPlugins, and most builds will include almost all of them, + thus: CPlugin_id_to_ProtocolIndex is a vector and not a std::map as with the Plugins. + \*********************************************************************************************/ + + +bool CPluginCall(CPlugin::Function Function, + struct EventStruct *event); +bool CPluginCall(CPlugin::Function Function, + struct EventStruct *event, + String & str); +bool CPluginCall(protocolIndex_t protocolIndex, + CPlugin::Function Function, + struct EventStruct *event, + String & str); + +bool anyControllerEnabled(); +controllerIndex_t findFirstEnabledControllerWithId(cpluginID_t cpluginid); + +bool validProtocolIndex(protocolIndex_t index); + + +// bool validControllerIndex(controllerIndex_t index); +#define validControllerIndex(C_X) ((C_X) < CONTROLLER_MAX) + +// Check whether CPlugin is included in build. +bool validCPluginID(cpluginID_t cpluginID); + +// Check if cplugin is included in build. +// N.B. Invalid cplugin is also not considered supported. +// This is essentially (validCPluginID && validProtocolIndex) +bool supportedCPluginID(cpluginID_t cpluginID); +protocolIndex_t getProtocolIndex_from_ControllerIndex(controllerIndex_t index); +protocolIndex_t getProtocolIndex_from_CPluginID(cpluginID_t cpluginID); +cpluginID_t getCPluginID_from_ProtocolIndex(protocolIndex_t index); +cpluginID_t getCPluginID_from_ControllerIndex(controllerIndex_t index); + +String getCPluginNameFromProtocolIndex(protocolIndex_t ProtocolIndex); +String getCPluginNameFromCPluginID(cpluginID_t cpluginID); + + +#endif // GLOBALS_CPLUGIN_H diff --git a/src/src/Globals/ESPEasyWiFiEvent.cpp b/src/src/Globals/ESPEasyWiFiEvent.cpp index 9ec0c0a46b..05fd1c1d46 100644 --- a/src/src/Globals/ESPEasyWiFiEvent.cpp +++ b/src/src/Globals/ESPEasyWiFiEvent.cpp @@ -1,18 +1,31 @@ -#include "../Globals/ESPEasyWiFiEvent.h" - -#include "../../ESPEasy_common.h" - - -#ifdef ESP8266 -WiFiEventHandler stationConnectedHandler; -WiFiEventHandler stationDisconnectedHandler; -WiFiEventHandler stationGotIpHandler; -WiFiEventHandler stationModeDHCPTimeoutHandler; -WiFiEventHandler stationModeAuthModeChangeHandler; -WiFiEventHandler APModeStationConnectedHandler; -WiFiEventHandler APModeStationDisconnectedHandler; -#endif // ifdef ESP8266 - -WiFiEventData_t WiFiEventData; - - +#include "../Globals/ESPEasyWiFiEvent.h" + +#include "../../ESPEasy_common.h" + + +#ifdef ESP8266 +WiFiEventHandler stationConnectedHandler; +WiFiEventHandler stationDisconnectedHandler; +WiFiEventHandler stationGotIpHandler; +WiFiEventHandler stationModeDHCPTimeoutHandler; +WiFiEventHandler stationModeAuthModeChangeHandler; +WiFiEventHandler APModeStationConnectedHandler; +WiFiEventHandler APModeStationDisconnectedHandler; +#ifdef USES_ESPEASY_NOW +WiFiEventHandler APModeProbeRequestReceivedHandler; + +std::list APModeProbeRequestReceived_list; +#endif +#endif // ifdef ESP8266 + +#ifdef ESP32 +#ifdef USES_ESPEASY_NOW +# if ESP_IDF_VERSION_MAJOR>=5 +std::list APModeProbeRequestReceived_list; +#else +std::list APModeProbeRequestReceived_list; +#endif +#endif +#endif + +WiFiEventData_t WiFiEventData; diff --git a/src/src/Globals/ESPEasyWiFiEvent.h b/src/src/Globals/ESPEasyWiFiEvent.h index 068e3f7736..dfdd442624 100644 --- a/src/src/Globals/ESPEasyWiFiEvent.h +++ b/src/src/Globals/ESPEasyWiFiEvent.h @@ -1,39 +1,55 @@ -#ifndef GLOBALS_ESPEASYWIFIEVENT_H -#define GLOBALS_ESPEASYWIFIEVENT_H - - -#include "../../ESPEasy_common.h" - -#include "../DataStructs/WiFiEventData.h" - - -#include -#include - - -#ifdef ESP32 -# include -# include -# include - -#endif // ifdef ESP32 - -#ifdef ESP8266 -# include -# include -class IPAddress; - -extern WiFiEventHandler stationConnectedHandler; -extern WiFiEventHandler stationDisconnectedHandler; -extern WiFiEventHandler stationGotIpHandler; -extern WiFiEventHandler stationModeDHCPTimeoutHandler; -extern WiFiEventHandler stationModeAuthModeChangeHandler; -extern WiFiEventHandler APModeStationConnectedHandler; -extern WiFiEventHandler APModeStationDisconnectedHandler; -#endif // ifdef ESP8266 - - -extern WiFiEventData_t WiFiEventData; - - -#endif // GLOBALS_ESPEASYWIFIEVENT_H +#ifndef GLOBALS_ESPEASYWIFIEVENT_H +#define GLOBALS_ESPEASYWIFIEVENT_H + +#include "../../ESPEasy_common.h" + +#include "../DataStructs/MAC_address.h" +#if FEATURE_ETHERNET +#include "../DataStructs/EthernetEventData.h" +#endif +#include "../DataStructs/WiFiEventData.h" + + +#include +#include +#include + +#ifdef ESP32 +# include +# include +# include + +#endif // ifdef ESP32 + +#ifdef ESP8266 +# include +# include +class IPAddress; + +extern WiFiEventHandler stationConnectedHandler; +extern WiFiEventHandler stationDisconnectedHandler; +extern WiFiEventHandler stationGotIpHandler; +extern WiFiEventHandler stationModeDHCPTimeoutHandler; +extern WiFiEventHandler stationModeAuthModeChangeHandler; +extern WiFiEventHandler APModeStationConnectedHandler; +extern WiFiEventHandler APModeStationDisconnectedHandler; +#ifdef USES_ESPEASY_NOW +extern WiFiEventHandler APModeProbeRequestReceivedHandler; +extern std::list APModeProbeRequestReceived_list; +#endif +#endif // ifdef ESP8266 + +#ifdef ESP32 +#ifdef USES_ESPEASY_NOW +# if ESP_IDF_VERSION_MAJOR>=5 +extern std::list APModeProbeRequestReceived_list; +#else +extern std::list APModeProbeRequestReceived_list; +#endif +#endif +#endif + +extern WiFiEventData_t WiFiEventData; + + +#endif // GLOBALS_ESPEASYWIFIEVENT_H diff --git a/src/src/Globals/ESPEasy_now_handler.cpp b/src/src/Globals/ESPEasy_now_handler.cpp new file mode 100644 index 0000000000..5dcc7abbc3 --- /dev/null +++ b/src/src/Globals/ESPEasy_now_handler.cpp @@ -0,0 +1,7 @@ +#include "../Globals/ESPEasy_now_handler.h" + +#ifdef USES_ESPEASY_NOW + +ESPEasy_now_handler_t ESPEasy_now_handler; + +#endif \ No newline at end of file diff --git a/src/src/Globals/ESPEasy_now_handler.h b/src/src/Globals/ESPEasy_now_handler.h new file mode 100644 index 0000000000..f6cb06a73d --- /dev/null +++ b/src/src/Globals/ESPEasy_now_handler.h @@ -0,0 +1,14 @@ +#ifndef GLOBALS_ESPEASY_NOW_HANDLER_H +#define GLOBALS_ESPEASY_NOW_HANDLER_H + +#include "../../ESPEasy_common.h" + +#ifdef USES_ESPEASY_NOW + +#include "../Helpers/ESPEasy_now_handler.h" + +extern ESPEasy_now_handler_t ESPEasy_now_handler; + +#endif // ifdef USES_ESPEASY_NOW + +#endif // ifndef GLOBALS_ESPEASY_NOW_HANDLER_H diff --git a/src/src/Globals/ESPEasy_now_peermanager.cpp b/src/src/Globals/ESPEasy_now_peermanager.cpp new file mode 100644 index 0000000000..47d232a9a0 --- /dev/null +++ b/src/src/Globals/ESPEasy_now_peermanager.cpp @@ -0,0 +1,7 @@ +#include "../Globals/ESPEasy_now_peermanager.h" + +#ifdef USES_ESPEASY_NOW + +ESPEasy_now_peermanager_t ESPEasy_now_peermanager; + +#endif \ No newline at end of file diff --git a/src/src/Globals/ESPEasy_now_peermanager.h b/src/src/Globals/ESPEasy_now_peermanager.h new file mode 100644 index 0000000000..6f0452f16b --- /dev/null +++ b/src/src/Globals/ESPEasy_now_peermanager.h @@ -0,0 +1,15 @@ +#ifndef GLOBALS_ESPEASY_NOW_PEERMANAGER_H +#define GLOBALS_ESPEASY_NOW_PEERMANAGER_H + +#include "../../ESPEasy_common.h" + +#ifdef USES_ESPEASY_NOW + +# include "../Helpers/ESPEasy_now_peermanager.h" + +extern ESPEasy_now_peermanager_t ESPEasy_now_peermanager; + + +#endif // ifdef USES_ESPEASY_NOW + +#endif // GLOBALS_ESPEASY_NOW_PEERMANAGER_H diff --git a/src/src/Globals/ESPEasy_now_state.cpp b/src/src/Globals/ESPEasy_now_state.cpp new file mode 100644 index 0000000000..fbe7b9779c --- /dev/null +++ b/src/src/Globals/ESPEasy_now_state.cpp @@ -0,0 +1,11 @@ +#include "../Globals/ESPEasy_now_state.h" + + + +#ifdef USES_ESPEASY_NOW + +bool use_EspEasy_now = false; +bool plugin_EspEasy_now_active = false; +unsigned long temp_disable_EspEasy_now_timer = 0; + +#endif // ifdef USES_ESPEASY_NOW diff --git a/src/src/Globals/ESPEasy_now_state.h b/src/src/Globals/ESPEasy_now_state.h new file mode 100644 index 0000000000..b1c8f31e98 --- /dev/null +++ b/src/src/Globals/ESPEasy_now_state.h @@ -0,0 +1,17 @@ +#ifndef GLOBALS_ESPEASY_NOW_STATE_H +#define GLOBALS_ESPEASY_NOW_STATE_H + +#include "../../ESPEasy_common.h" + +#ifdef USES_ESPEASY_NOW + +# include + +extern bool use_EspEasy_now; +extern bool plugin_EspEasy_now_active; +extern unsigned long temp_disable_EspEasy_now_timer; + + +#endif // ifdef USES_ESPEASY_NOW + +#endif // GLOBALS_ESPEASY_NOW_STATE_H diff --git a/src/src/Globals/NetworkState.cpp b/src/src/Globals/NetworkState.cpp index e5c286c858..920c3b2e79 100644 --- a/src/src/Globals/NetworkState.cpp +++ b/src/src/Globals/NetworkState.cpp @@ -1,28 +1,29 @@ -#include "../Globals/NetworkState.h" - -#include "../../ESPEasy_common.h" - - -// Ethernet Connection status -NetworkMedium_t active_network_medium = NetworkMedium_t::NotSet; - -bool webserverRunning(false); -bool webserver_init(false); - -#if FEATURE_MDNS -bool mDNS_init(false); -#endif - - -// NTP status -bool statusNTPInitialized = false; - - -// Setup DNS, only used if the ESP has no valid WiFi config -const uint8_t DNS_PORT = 53; -IPAddress apIP(DEFAULT_AP_IP); - - - -// udp protocol stuff (syslog, global sync, node info list, ntp time) -WiFiUDP portUDP; +#include "../Globals/NetworkState.h" + +#include "../../ESPEasy_common.h" + + +// Ethernet Connection status +NetworkMedium_t active_network_medium = NetworkMedium_t::NotSet; +LongTermTimer last_network_medium_set_moment; + +bool webserverRunning(false); +bool webserver_init(false); + +#if FEATURE_MDNS +bool mDNS_init(false); +#endif + + +// NTP status +bool statusNTPInitialized = false; + + +// Setup DNS, only used if the ESP has no valid WiFi config +const uint8_t DNS_PORT = 53; +IPAddress apIP(DEFAULT_AP_IP); + + + +// udp protocol stuff (syslog, global sync, node info list, ntp time) +WiFiUDP portUDP; diff --git a/src/src/Globals/NetworkState.h b/src/src/Globals/NetworkState.h index b7664cb843..505d8953e6 100644 --- a/src/src/Globals/NetworkState.h +++ b/src/src/Globals/NetworkState.h @@ -1,34 +1,37 @@ -#ifndef GLOBALS_NETWORKSTATE_H -#define GLOBALS_NETWORKSTATE_H - -#include "../../ESPEasy_common.h" - -#include -#include - -#include "../DataTypes/ESPEasy_plugin_functions.h" -#include "../DataTypes/NetworkMedium.h" - -// Ethernet Connectiopn status -extern NetworkMedium_t active_network_medium; - -extern bool webserverRunning; -extern bool webserver_init; -#if FEATURE_MDNS -extern bool mDNS_init; -#endif - - -// NTP status -extern bool statusNTPInitialized; - - -// Setup DNS, only used if the ESP has no valid WiFi config -extern const uint8_t DNS_PORT; -extern IPAddress apIP; - -// udp protocol stuff (syslog, global sync, node info list, ntp time) -extern WiFiUDP portUDP; - - -#endif // GLOBALS_NETWORKSTATE_H +#ifndef GLOBALS_NETWORKSTATE_H +#define GLOBALS_NETWORKSTATE_H + +#include "../../ESPEasy_common.h" + +#include +#include + +#include "../DataTypes/ESPEasy_plugin_functions.h" +#include "../DataTypes/NetworkMedium.h" + +#include "../Helpers/LongTermTimer.h" + +// Ethernet Connectiopn status +extern NetworkMedium_t active_network_medium; +extern LongTermTimer last_network_medium_set_moment;; + +extern bool webserverRunning; +extern bool webserver_init; +#if FEATURE_MDNS +extern bool mDNS_init; +#endif + + +// NTP status +extern bool statusNTPInitialized; + + +// Setup DNS, only used if the ESP has no valid WiFi config +extern const uint8_t DNS_PORT; +extern IPAddress apIP; + +// udp protocol stuff (syslog, global sync, node info list, ntp time) +extern WiFiUDP portUDP; + + +#endif // GLOBALS_NETWORKSTATE_H diff --git a/src/src/Globals/Nodes.cpp b/src/src/Globals/Nodes.cpp index 26e983f062..0d9279d6df 100644 --- a/src/src/Globals/Nodes.cpp +++ b/src/src/Globals/Nodes.cpp @@ -1,7 +1,7 @@ -#include "../Globals/Nodes.h" - -#if FEATURE_ESPEASY_P2P - -NodesHandler Nodes; - -#endif \ No newline at end of file +#include "../Globals/Nodes.h" + +#if FEATURE_ESPEASY_P2P + +NodesHandler Nodes; + +#endif diff --git a/src/src/Globals/SendData_DuplicateChecker.cpp b/src/src/Globals/SendData_DuplicateChecker.cpp new file mode 100644 index 0000000000..acfc7298d9 --- /dev/null +++ b/src/src/Globals/SendData_DuplicateChecker.cpp @@ -0,0 +1,3 @@ +#include "../Globals/SendData_DuplicateChecker.h" + +SendData_DuplicateChecker_struct SendData_DuplicateChecker; \ No newline at end of file diff --git a/src/src/Globals/SendData_DuplicateChecker.h b/src/src/Globals/SendData_DuplicateChecker.h new file mode 100644 index 0000000000..fb770079a2 --- /dev/null +++ b/src/src/Globals/SendData_DuplicateChecker.h @@ -0,0 +1,8 @@ +#ifndef GLOBALS_SENDDATA_DUPLICATECHECKER_H +#define GLOBALS_SENDDATA_DUPLICATECHECKER_H + +#include "../DataStructs/SendData_DuplicateChecker_struct.h" + +extern SendData_DuplicateChecker_struct SendData_DuplicateChecker; + +#endif // GLOBALS_SENDDATA_DUPLICATECHECKER_H \ No newline at end of file diff --git a/src/src/Helpers/C019_ESPEasyNow_helper.cpp b/src/src/Helpers/C019_ESPEasyNow_helper.cpp new file mode 100644 index 0000000000..563eaf4771 --- /dev/null +++ b/src/src/Helpers/C019_ESPEasyNow_helper.cpp @@ -0,0 +1,49 @@ +#include "../Helpers/C019_ESPEasyNow_helper.h" + +#ifdef USES_ESPEASY_NOW +#include "../DataStructs/ESPEasy_EventStruct.h" +#include "../ESPEasyCore/ESPEasy_Log.h" +#include "../Globals/CPlugins.h" + + +bool C019_ESPEasyNow_helper::process_receive(struct EventStruct *event) { + controllerIndex_t ControllerID = findFirstEnabledControllerWithId(19 /* CPLUGIN_ID_019 */ ); + + if (!validControllerIndex(ControllerID)) { + // Controller is not enabled. + return false; + } + + if ((event->Data == nullptr) || (event->Par1 != sizeof(ESPEasy_Now_p2p_data))) { + return false; + } + ESPEasy_Now_p2p_data *data = reinterpret_cast(event->Data); + + if (!data->validate()) { + // return false; + } + + String log = F("ESPEasy_Now PluginData: "); + + log += data->plugin_id; + log += F(" sourceUnit: "); + log += data->sourceUnit; + addLog(LOG_LEVEL_INFO, log); + + switch (data->dataType) { + case ESPEasy_Now_p2p_data_type::PluginData: + { + C019_ESPEasyNow_helper::process_received_PluginData(*data); + break; + } + default: + break; + } + + return true; +} + +void C019_ESPEasyNow_helper::process_received_PluginData(const ESPEasy_Now_p2p_data& data) { + +} +#endif \ No newline at end of file diff --git a/src/src/Helpers/C019_ESPEasyNow_helper.h b/src/src/Helpers/C019_ESPEasyNow_helper.h new file mode 100644 index 0000000000..8cfc0463b1 --- /dev/null +++ b/src/src/Helpers/C019_ESPEasyNow_helper.h @@ -0,0 +1,18 @@ +#ifndef HELPERS_CONTROLLER_C019_ESPEASYNOW_HELPER_H +#define HELPERS_CONTROLLER_C019_ESPEASYNOW_HELPER_H + +#include "../../ESPEasy_common.h" + +#ifdef USES_ESPEASY_NOW + +#include "../DataStructs/ESPEasy_Now_p2p_data.h" + +struct C019_ESPEasyNow_helper { + static bool process_receive(struct EventStruct *event); + + static void process_received_PluginData(const ESPEasy_Now_p2p_data& data); +}; + +#endif + +#endif // HELPERS_CONTROLLER_C019_ESPEASYNOW_HELPER_H diff --git a/src/src/Helpers/ESPEasy_FactoryDefault.cpp b/src/src/Helpers/ESPEasy_FactoryDefault.cpp index 77a98564da..ab4d687b5e 100644 --- a/src/src/Helpers/ESPEasy_FactoryDefault.cpp +++ b/src/src/Helpers/ESPEasy_FactoryDefault.cpp @@ -1,498 +1,499 @@ -#include "../Helpers/ESPEasy_FactoryDefault.h" - -#include "../../ESPEasy_common.h" -#include "../../_Plugin_Helper.h" - -#include "../CustomBuild/CompiletimeDefines.h" -#include "../CustomBuild/StorageLayout.h" - -#include "../DataStructs/ControllerSettingsStruct.h" -#include "../DataStructs/FactoryDefaultPref.h" -#include "../DataStructs/GpioFactorySettingsStruct.h" - -#include "../ESPEasyCore/ESPEasy_backgroundtasks.h" -#include "../ESPEasyCore/ESPEasyWifi.h" -#include "../ESPEasyCore/Serial.h" - -#include "../Globals/ESPEasyWiFiEvent.h" -#include "../Globals/RTC.h" -#include "../Globals/ResetFactoryDefaultPref.h" -#include "../Globals/SecuritySettings.h" - -#include "../Helpers/_CPlugin_Helper.h" -#include "../Helpers/ESPEasyRTC.h" -#include "../Helpers/FS_Helper.h" -#include "../Helpers/Misc.h" - -#ifdef ESP32 - -// Store in NVS partition -# include "../Helpers/ESPEasy_NVS_Helper.h" - - -// Max. 15 char namespace for ESPEasy Factory Default settings -# define FACTORY_DEFAULT_NVS_NAMESPACE "ESPEasyFacDef" - -# include "../Helpers/StringConverter.h" -# include "../DataStructs/FactoryDefaultPref.h" -# include "../DataStructs/FactoryDefault_UnitName_NVS.h" -# include "../DataStructs/FactoryDefault_WiFi_NVS.h" -# include "../DataStructs/FactoryDefault_Network_NVS.h" -# include "../DataStructs/FactoryDefault_LogConsoleSettings_NVS.h" -# if FEATURE_ALTERNATIVE_CDN_URL -# include "../DataStructs/FactoryDefault_CDN_customurl_NVS.h" -# endif // if FEATURE_ALTERNATIVE_CDN_URL - - -#endif // ifdef ESP32 - - -/********************************************************************************************\ - Reset all settings to factory defaults - \*********************************************************************************************/ -void ResetFactory(bool formatFS) -{ - bool mustApplySafebootDefaults = false; - - #ifdef ESP32 - ESPEasy_NVS_Helper preferences; - - if (!ResetFactoryDefaultPreference.init(preferences)) { - mustApplySafebootDefaults = true; - } - #endif // ifdef ESP32 - #ifdef ESP8266 // Apply defaults on clean setup - mustApplySafebootDefaults = 0 == Settings.ResetFactoryDefaultPreference; - #endif // ifdef ESP8266 - - if (ResetFactoryDefaultPreference.getPreference() == 0) - { -#if FEATURE_CUSTOM_PROVISIONING - ResetFactoryDefaultPreference.setDeviceModel(static_cast(DEFAULT_FACTORY_DEFAULT_DEVICE_MODEL)); - #if DEFAULT_PROVISIONING_FETCH_RULES1 - ResetFactoryDefaultPreference.fetchRulesTXT(0, true); - #endif - #if DEFAULT_PROVISIONING_FETCH_RULES2 - ResetFactoryDefaultPreference.fetchRulesTXT(1, true); - #endif - #if DEFAULT_PROVISIONING_FETCH_RULES3 - ResetFactoryDefaultPreference.fetchRulesTXT(2, true); - #endif - #if DEFAULT_PROVISIONING_FETCH_RULES4 - ResetFactoryDefaultPreference.fetchRulesTXT(3, true); - #endif - #if DEFAULT_PROVISIONING_FETCH_NOTIFICATIONS - ResetFactoryDefaultPreference.fetchNotificationDat(true); - #endif - #if DEFAULT_PROVISIONING_FETCH_SECURITY - ResetFactoryDefaultPreference.fetchSecurityDat(true); - #endif - #if DEFAULT_PROVISIONING_FETCH_CONFIG - ResetFactoryDefaultPreference.fetchConfigDat(true); - #endif - #if DEFAULT_PROVISIONING_FETCH_PROVISIONING - ResetFactoryDefaultPreference.fetchProvisioningDat(true); - #endif - #if DEFAULT_PROVISIONING_SAVE_URL - ResetFactoryDefaultPreference.saveURL(true); - #endif - #if DEFAULT_PROVISIONING_SAVE_CREDENTIALS - ResetFactoryDefaultPreference.storeCredentials(true); - #endif - #endif - #if DEFAULT_FACTORY_RESET_KEEP_UNIT_NAME - ResetFactoryDefaultPreference.keepUnitName(true); - #endif - #if DEFAULT_FACTORY_RESET_KEEP_WIFI - ResetFactoryDefaultPreference.keepWiFi(true); - #endif - #if DEFAULT_FACTORY_RESET_KEEP_NETWORK - ResetFactoryDefaultPreference.keepNetwork(true); - #endif - #if DEFAULT_FACTORY_RESET_KEEP_NTP_DST - ResetFactoryDefaultPreference.keepNTP(true); - #endif - #if DEFAULT_FACTORY_RESET_KEEP_CONSOLE_LOG - ResetFactoryDefaultPreference.keepLogConsoleSettings(true); - #endif -#ifdef PLUGIN_BUILD_SAFEBOOT - mustApplySafebootDefaults = true; - ResetFactoryDefaultPreference.keepCustomCdnUrl(true); - - Settings.UseLastWiFiFromRTC(true); -#endif // ifdef PLUGIN_BUILD_SAFEBOOT - } - - - const GpioFactorySettingsStruct gpio_settings(ResetFactoryDefaultPreference.getDeviceModel()); - #ifndef BUILD_NO_RAM_TRACKER - checkRAM(F("ResetFactory")); - #endif // ifndef BUILD_NO_RAM_TRACKER - - // Direct Serial is allowed here, since this is only an emergency task. - serialPrint(F("RESET: Resetting factory defaults... using ")); - serialPrint(getDeviceModelString(ResetFactoryDefaultPreference.getDeviceModel())); - serialPrintln(F(" settings")); - process_serialWriteBuffer(); - delay(1000); - - if (readFromRTC()) - { - serialPrint(F("RESET: Warm boot, reset count: ")); - serialPrintln(String(RTC.factoryResetCounter)); - - if (RTC.factoryResetCounter >= 3) - { - serialPrintln(F("RESET: Too many resets, protecting your flash memory (powercycle to solve this)")); - return; - } - } - else - { - serialPrintln(F("RESET: Cold boot")); - initRTC(); - - // TODO TD-er: Store set device model in RTC. - } - - RTC.flashCounter = 0; // reset flashcounter, since we're already counting the number of factory-resets. we dont want to hit a flash-count - // limit during reset. - RTC.factoryResetCounter++; - saveToRTC(); - - if (formatFS) { - // always format on factory reset, in case of corrupt FS - ESPEASY_FS.end(); - serialPrintln(F("RESET: formatting...")); - FS_format(); - serialPrintln(F("RESET: formatting done...")); - process_serialWriteBuffer(); - - if (!ESPEASY_FS.begin()) - { - serialPrintln(F("RESET: FORMAT FS FAILED!")); - return; - } - } - -#if FEATURE_CUSTOM_PROVISIONING - { - MakeProvisioningSettings(ProvisioningSettings); - - if (ProvisioningSettings.get()) { - ProvisioningSettings->setUser(F(DEFAULT_PROVISIONING_USER)); - ProvisioningSettings->setPass(F(DEFAULT_PROVISIONING_PASS)); - ProvisioningSettings->setUrl(F(DEFAULT_PROVISIONING_URL)); - ProvisioningSettings->ResetFactoryDefaultPreference = ResetFactoryDefaultPreference.getPreference(); - saveProvisioningSettings(*ProvisioningSettings); - } - } -#endif // if FEATURE_CUSTOM_PROVISIONING - - // pad files with extra zeros for future extensions - InitFile(SettingsType::SettingsFileEnum::FILE_CONFIG_type); - InitFile(SettingsType::SettingsFileEnum::FILE_SECURITY_type); - #if FEATURE_NOTIFIER - InitFile(SettingsType::SettingsFileEnum::FILE_NOTIFICATION_type); - #endif // if FEATURE_NOTIFIER - - InitFile(getRulesFileName(0), 0); - - Settings.clearMisc(); - - if (!ResetFactoryDefaultPreference.keepNTP() || mustApplySafebootDefaults) { - Settings.clearTimeSettings(); - Settings.UseNTP(DEFAULT_USE_NTP); - strcpy_P(Settings.NTPHost, PSTR(DEFAULT_NTP_HOST)); - Settings.TimeZone = DEFAULT_TIME_ZONE; - Settings.DST = DEFAULT_USE_DST; - } - - if (!ResetFactoryDefaultPreference.keepNetwork() || mustApplySafebootDefaults) { - Settings.clearNetworkSettings(); - - // TD-er Reset access control - str2ip(F(DEFAULT_IPRANGE_LOW), SecuritySettings.AllowedIPrangeLow); - str2ip(F(DEFAULT_IPRANGE_HIGH), SecuritySettings.AllowedIPrangeHigh); - SecuritySettings.IPblockLevel = DEFAULT_IP_BLOCK_LEVEL; - - #if DEFAULT_USE_STATIC_IP - str2ip((char *)DEFAULT_IP, Settings.IP); - str2ip((char *)DEFAULT_DNS, Settings.DNS); - str2ip((char *)DEFAULT_GW, Settings.Gateway); - str2ip((char *)DEFAULT_SUBNET, Settings.Subnet); - #endif // if DEFAULT_USE_STATIC_IP - Settings.IncludeHiddenSSID(DEFAULT_WIFI_INCLUDE_HIDDEN_SSID); - } - - Settings.clearNotifications(); - Settings.clearControllers(); - Settings.clearTasks(); - - if (!ResetFactoryDefaultPreference.keepLogConsoleSettings() || mustApplySafebootDefaults) { - Settings.clearLogSettings(); - str2ip((char *)DEFAULT_SYSLOG_IP, Settings.Syslog_IP); - - setLogLevelFor(LOG_TO_SYSLOG, DEFAULT_SYSLOG_LEVEL); - setLogLevelFor(LOG_TO_SERIAL, DEFAULT_SERIAL_LOG_LEVEL); - setLogLevelFor(LOG_TO_WEBLOG, DEFAULT_WEB_LOG_LEVEL); - setLogLevelFor(LOG_TO_SDCARD, DEFAULT_SD_LOG_LEVEL); - Settings.SyslogFacility = DEFAULT_SYSLOG_FACILITY; - Settings.SyslogPort = DEFAULT_SYSLOG_PORT; - Settings.UseValueLogger = DEFAULT_USE_SD_LOG; - - // FIXME TD-er: Must also keep console settings. - Settings.console_serial_port = DEFAULT_CONSOLE_PORT; - Settings.console_serial_rxpin = DEFAULT_CONSOLE_PORT_RXPIN; - Settings.console_serial_txpin = DEFAULT_CONSOLE_PORT_TXPIN; - Settings.console_serial0_fallback = DEFAULT_CONSOLE_SER0_FALLBACK; - Settings.UseSerial = DEFAULT_USE_SERIAL; - Settings.BaudRate = DEFAULT_SERIAL_BAUD; - } - - if (!ResetFactoryDefaultPreference.keepUnitName() || mustApplySafebootDefaults) { - Settings.clearUnitNameSettings(); - Settings.Unit = UNIT; - strcpy_P(Settings.Name, PSTR(DEFAULT_NAME)); - Settings.UDPPort = DEFAULT_SYNC_UDP_PORT; - } - - if (!ResetFactoryDefaultPreference.keepWiFi() || mustApplySafebootDefaults) { - strcpy_P(SecuritySettings.WifiSSID, PSTR(DEFAULT_SSID)); - strcpy_P(SecuritySettings.WifiKey, PSTR(DEFAULT_KEY)); - strcpy_P(SecuritySettings.WifiSSID2, PSTR(DEFAULT_SSID2)); - strcpy_P(SecuritySettings.WifiKey2, PSTR(DEFAULT_KEY2)); - strcpy_P(SecuritySettings.WifiAPKey, PSTR(DEFAULT_AP_KEY)); - } - strcpy_P(SecuritySettings.Password, PSTR(DEFAULT_ADMIN_PASS)); - - Settings.ResetFactoryDefaultPreference = ResetFactoryDefaultPreference.getPreference(); - - // now we set all parameters that need to be non-zero as default value - - - Settings.PID = ESP_PROJECT_PID; - Settings.Version = VERSION; - Settings.Build = get_build_nr(); - - // Settings.IP_Octet = DEFAULT_IP_OCTET; - // Settings.Delay = DEFAULT_DELAY; - Settings.Pin_i2c_sda = gpio_settings.i2c_sda; - Settings.Pin_i2c_scl = gpio_settings.i2c_scl; - Settings.Pin_status_led = gpio_settings.status_led; - - // Settings.Pin_status_led_Inversed = DEFAULT_PIN_STATUS_LED_INVERSED; - Settings.Pin_sd_cs = -1; - Settings.Pin_Reset = DEFAULT_PIN_RESET_BUTTON; - Settings.Protocol[0] = DEFAULT_PROTOCOL; - - // Settings.deepSleep_wakeTime = 0; // Sleep disabled - // Settings.CustomCSS = false; - // Settings.InitSPI = DEFAULT_SPI; - - // advanced Settings - // Settings.UseRules = DEFAULT_USE_RULES; - Settings.ControllerEnabled[0] = DEFAULT_CONTROLLER_ENABLED; - Settings.MQTTRetainFlag_unused = DEFAULT_MQTT_RETAIN; - Settings.MessageDelay_unused = DEFAULT_MQTT_DELAY; - Settings.MQTTUseUnitNameAsClientId_unused = DEFAULT_MQTT_USE_UNITNAME_AS_CLIENTID; - - // allow to set default latitude and longitude - #ifdef DEFAULT_LATITUDE - Settings.Latitude = DEFAULT_LATITUDE; - #endif // ifdef DEFAULT_LATITUDE - #ifdef DEFAULT_LONGITUDE - Settings.Longitude = DEFAULT_LONGITUDE; - #endif // ifdef DEFAULT_LONGITUDE - -#ifdef ESP32 - - // Ethernet related settings are never used on ESP8266 - Settings.ETH_Phy_Addr = gpio_settings.eth_phyaddr; - Settings.ETH_Pin_mdc_cs = gpio_settings.eth_mdc; - Settings.ETH_Pin_mdio_irq = gpio_settings.eth_mdio; - Settings.ETH_Pin_power_rst = gpio_settings.eth_power; - Settings.ETH_Phy_Type = gpio_settings.eth_phytype; - Settings.ETH_Clock_Mode = gpio_settings.eth_clock_mode; -#endif // ifdef ESP32 - Settings.NetworkMedium = gpio_settings.network_medium; - - /* - Settings.GlobalSync = DEFAULT_USE_GLOBAL_SYNC; - - Settings.IP_Octet = DEFAULT_IP_OCTET; - Settings.WDI2CAddress = DEFAULT_WD_IC2_ADDRESS; - Settings.UseSSDP = DEFAULT_USE_SSDP; - Settings.ConnectionFailuresThreshold = DEFAULT_CON_FAIL_THRES; - Settings.WireClockStretchLimit = DEFAULT_I2C_CLOCK_LIMIT; - */ - - // Settings.I2C_clockSpeed = DEFAULT_I2C_CLOCK_SPEED; - - Settings.JSONBoolWithoutQuotes(DEFAULT_JSON_BOOL_WITHOUT_QUOTES); - Settings.EnableTimingStats(DEFAULT_ENABLE_TIMING_STATS); - -#ifdef PLUGIN_DESCR - strcpy_P(Settings.Name, PSTR(PLUGIN_DESCR)); -#endif // ifdef PLUGIN_DESCR - -#ifndef LIMIT_BUILD_SIZE - addPredefinedPlugins(gpio_settings); - addPredefinedRules(gpio_settings); -#endif // ifndef LIMIT_BUILD_SIZE - -#if DEFAULT_CONTROLLER - { - // Place in a scope to have its memory freed ASAP - MakeControllerSettings(ControllerSettings); // -V522 - - if (AllocatedControllerSettings()) { - safe_strncpy(ControllerSettings->Subscribe, F(DEFAULT_SUB), sizeof(ControllerSettings->Subscribe)); - safe_strncpy(ControllerSettings->Publish, F(DEFAULT_PUB), sizeof(ControllerSettings->Publish)); - safe_strncpy(ControllerSettings->MQTTLwtTopic, F(DEFAULT_MQTT_LWT_TOPIC), sizeof(ControllerSettings->MQTTLwtTopic)); - safe_strncpy(ControllerSettings->LWTMessageConnect, F(DEFAULT_MQTT_LWT_CONNECT_MESSAGE), - sizeof(ControllerSettings->LWTMessageConnect)); - safe_strncpy(ControllerSettings->LWTMessageDisconnect, F(DEFAULT_MQTT_LWT_DISCONNECT_MESSAGE), - sizeof(ControllerSettings->LWTMessageDisconnect)); - str2ip((char *)DEFAULT_SERVER, ControllerSettings->IP); - ControllerSettings->setHostname(F(DEFAULT_SERVER_HOST)); - ControllerSettings->UseDNS = DEFAULT_SERVER_USEDNS; - ControllerSettings->useExtendedCredentials(DEFAULT_USE_EXTD_CONTROLLER_CREDENTIALS); - ControllerSettings->Port = DEFAULT_PORT; - ControllerSettings->ClientTimeout = DEFAULT_CONTROLLER_TIMEOUT; - setControllerUser(0, *ControllerSettings, F(DEFAULT_CONTROLLER_USER)); - setControllerPass(0, *ControllerSettings, F(DEFAULT_CONTROLLER_PASS)); - - SaveControllerSettings(0, *ControllerSettings); - } - } -#endif // if DEFAULT_CONTROLLER - -#ifdef ESP32 - if (!mustApplySafebootDefaults) - { - Settings.ResetFactoryDefaultPreference = ResetFactoryDefaultPreference.getPreference(); - - if (ResetFactoryDefaultPreference.keepUnitName()) - { - FactoryDefault_UnitName_NVS unitNameNVS{}; - unitNameNVS.applyToSettings_from_NVS(preferences); - } - - if (ResetFactoryDefaultPreference.keepWiFi()) - { - FactoryDefault_WiFi_NVS wifiNVS{}; - wifiNVS.applyToSettings_from_NVS(preferences); - } - - if (ResetFactoryDefaultPreference.keepNetwork()) - { - // Restore Network IP settings - FactoryDefault_Network_NVS network_nvs; - network_nvs.applyToSettings_from_NVS(preferences); - } - - if (ResetFactoryDefaultPreference.keepLogConsoleSettings()) - { - // Restore Log and Console settings - FactoryDefault_LogConsoleSettings_NVS log_console_nvs; - log_console_nvs.applyToSettings_from_NVS(preferences); - } - -# if FEATURE_ALTERNATIVE_CDN_URL - - if (ResetFactoryDefaultPreference.keepCustomCdnUrl()) { - FactoryDefault_CDN_customurl_NVS::applyToSettings_from_NVS(preferences); - } -# endif // if FEATURE_ALTERNATIVE_CDN_URL - preferences.end(); - } -#endif // ifdef ESP32 - - const bool forFactoryReset = true; - SaveSettings(forFactoryReset); - #ifndef BUILD_NO_RAM_TRACKER - checkRAM(F("ResetFactory2")); - #endif // ifndef BUILD_NO_RAM_TRACKER - serialPrintln(F("RESET: Successful, rebooting. (you might need to press the reset button if you've just flashed the firmware)")); - - // NOTE: this is a known ESP8266 bug, not our fault. :) - delay(1000); - WiFi.persistent(true); // use SDK storage of SSID/WPA parameters - WiFiEventData.intent_to_reboot = true; - WifiDisconnect(); // this will store empty ssid/wpa into sdk storage - WiFi.persistent(false); // Do not use SDK storage of SSID/WPA parameters - reboot(IntendedRebootReason_e::ResetFactory); -} - -/*********************************************************************************************\ - Collect the stored preference for factory default -\*********************************************************************************************/ -void applyFactoryDefaultPref() { - // TODO TD-er: Store it in more places to make it more persistent - Settings.ResetFactoryDefaultPreference = ResetFactoryDefaultPreference.getPreference(); - -#ifdef ESP32 - ESPEasy_NVS_Helper preferences; - preferences.begin(F(FACTORY_DEFAULT_NVS_NAMESPACE)); - ResetFactoryDefaultPreference.to_NVS(preferences); - { - FactoryDefault_UnitName_NVS unitNameNVS{}; - - if (ResetFactoryDefaultPreference.keepUnitName()) - { - // Store Unit nr and hostname - unitNameNVS.fromSettings_to_NVS(preferences); - } else { - unitNameNVS.clear_from_NVS(preferences); - } - } - { - FactoryDefault_WiFi_NVS wifiNVS{}; - - if (ResetFactoryDefaultPreference.keepWiFi()) - { - // Store WiFi credentials - wifiNVS.fromSettings_to_NVS(preferences); - } else { - wifiNVS.clear_from_NVS(preferences); - } - } - { - FactoryDefault_Network_NVS network_nvs{}; - - if (ResetFactoryDefaultPreference.keepNetwork()) - { - // Store Network IP settings - network_nvs.fromSettings_to_NVS(preferences); - } else { - network_nvs.clear_from_NVS(preferences); - } - } - { - FactoryDefault_LogConsoleSettings_NVS log_console_nvs{}; - - if (ResetFactoryDefaultPreference.keepLogConsoleSettings()) - { - // Store Log and Console settings - log_console_nvs.fromSettings_to_NVS(preferences); - } else { - log_console_nvs.clear_from_NVS(preferences); - } - } -# if FEATURE_ALTERNATIVE_CDN_URL - { - if (ResetFactoryDefaultPreference.keepCustomCdnUrl()) - { - // Store custom CDN - FactoryDefault_CDN_customurl_NVS::fromSettings_to_NVS(preferences); - } else { - FactoryDefault_CDN_customurl_NVS::clear_from_NVS(preferences); - } - } -# endif // if FEATURE_ALTERNATIVE_CDN_URL - - - preferences.end(); -#endif // ifdef ESP32 -} +#include "../Helpers/ESPEasy_FactoryDefault.h" + +#include "../../ESPEasy_common.h" +#include "../../_Plugin_Helper.h" + +#include "../CustomBuild/CompiletimeDefines.h" +#include "../CustomBuild/StorageLayout.h" + +#include "../DataStructs/ControllerSettingsStruct.h" +#include "../DataStructs/FactoryDefaultPref.h" +#include "../DataStructs/GpioFactorySettingsStruct.h" + +#include "../ESPEasyCore/ESPEasy_backgroundtasks.h" +#include "../ESPEasyCore/ESPEasyWifi.h" +#include "../ESPEasyCore/Serial.h" + +#include "../Globals/ESPEasyWiFiEvent.h" +#include "../Globals/RTC.h" +#include "../Globals/ResetFactoryDefaultPref.h" +#include "../Globals/SecuritySettings.h" + +#include "../Helpers/_CPlugin_Helper.h" +#include "../Helpers/ESPEasyRTC.h" +#include "../Helpers/FS_Helper.h" +#include "../Helpers/Misc.h" + +#ifdef ESP32 + +// Store in NVS partition +# include "../Helpers/ESPEasy_NVS_Helper.h" + + +// Max. 15 char namespace for ESPEasy Factory Default settings +# define FACTORY_DEFAULT_NVS_NAMESPACE "ESPEasyFacDef" + +# include "../Helpers/StringConverter.h" +# include "../DataStructs/FactoryDefaultPref.h" +# include "../DataStructs/FactoryDefault_UnitName_NVS.h" +# include "../DataStructs/FactoryDefault_WiFi_NVS.h" +# include "../DataStructs/FactoryDefault_Network_NVS.h" +# include "../DataStructs/FactoryDefault_LogConsoleSettings_NVS.h" +# if FEATURE_ALTERNATIVE_CDN_URL +# include "../DataStructs/FactoryDefault_CDN_customurl_NVS.h" +# endif // if FEATURE_ALTERNATIVE_CDN_URL + + +#endif // ifdef ESP32 + + +/********************************************************************************************\ + Reset all settings to factory defaults + \*********************************************************************************************/ +void ResetFactory(bool formatFS) +{ + bool mustApplySafebootDefaults = false; + + #ifdef ESP32 + ESPEasy_NVS_Helper preferences; + + if (!ResetFactoryDefaultPreference.init(preferences)) { + mustApplySafebootDefaults = true; + } + #endif // ifdef ESP32 + #ifdef ESP8266 // Apply defaults on clean setup + mustApplySafebootDefaults = 0 == Settings.ResetFactoryDefaultPreference; + #endif // ifdef ESP8266 + + if (ResetFactoryDefaultPreference.getPreference() == 0) + { +#if FEATURE_CUSTOM_PROVISIONING + ResetFactoryDefaultPreference.setDeviceModel(static_cast(DEFAULT_FACTORY_DEFAULT_DEVICE_MODEL)); + #if DEFAULT_PROVISIONING_FETCH_RULES1 + ResetFactoryDefaultPreference.fetchRulesTXT(0, true); + #endif + #if DEFAULT_PROVISIONING_FETCH_RULES2 + ResetFactoryDefaultPreference.fetchRulesTXT(1, true); + #endif + #if DEFAULT_PROVISIONING_FETCH_RULES3 + ResetFactoryDefaultPreference.fetchRulesTXT(2, true); + #endif + #if DEFAULT_PROVISIONING_FETCH_RULES4 + ResetFactoryDefaultPreference.fetchRulesTXT(3, true); + #endif + #if DEFAULT_PROVISIONING_FETCH_NOTIFICATIONS + ResetFactoryDefaultPreference.fetchNotificationDat(true); + #endif + #if DEFAULT_PROVISIONING_FETCH_SECURITY + ResetFactoryDefaultPreference.fetchSecurityDat(true); + #endif + #if DEFAULT_PROVISIONING_FETCH_CONFIG + ResetFactoryDefaultPreference.fetchConfigDat(true); + #endif + #if DEFAULT_PROVISIONING_FETCH_PROVISIONING + ResetFactoryDefaultPreference.fetchProvisioningDat(true); + #endif + #if DEFAULT_PROVISIONING_SAVE_URL + ResetFactoryDefaultPreference.saveURL(true); + #endif + #if DEFAULT_PROVISIONING_SAVE_CREDENTIALS + ResetFactoryDefaultPreference.storeCredentials(true); + #endif + #endif + #if DEFAULT_FACTORY_RESET_KEEP_UNIT_NAME + ResetFactoryDefaultPreference.keepUnitName(true); + #endif + #if DEFAULT_FACTORY_RESET_KEEP_WIFI + ResetFactoryDefaultPreference.keepWiFi(true); + #endif + #if DEFAULT_FACTORY_RESET_KEEP_NETWORK + ResetFactoryDefaultPreference.keepNetwork(true); + #endif + #if DEFAULT_FACTORY_RESET_KEEP_NTP_DST + ResetFactoryDefaultPreference.keepNTP(true); + #endif + #if DEFAULT_FACTORY_RESET_KEEP_CONSOLE_LOG + ResetFactoryDefaultPreference.keepLogConsoleSettings(true); + #endif +#ifdef PLUGIN_BUILD_SAFEBOOT + mustApplySafebootDefaults = true; + ResetFactoryDefaultPreference.keepCustomCdnUrl(true); + + Settings.UseLastWiFiFromRTC(true); +#endif // ifdef PLUGIN_BUILD_SAFEBOOT + } + + + const GpioFactorySettingsStruct gpio_settings(ResetFactoryDefaultPreference.getDeviceModel()); + #ifndef BUILD_NO_RAM_TRACKER + checkRAM(F("ResetFactory")); + #endif // ifndef BUILD_NO_RAM_TRACKER + + // Direct Serial is allowed here, since this is only an emergency task. + serialPrint(F("RESET: Resetting factory defaults... using ")); + serialPrint(getDeviceModelString(ResetFactoryDefaultPreference.getDeviceModel())); + serialPrintln(F(" settings")); + process_serialWriteBuffer(); + delay(1000); + + if (readFromRTC()) + { + serialPrint(F("RESET: Warm boot, reset count: ")); + serialPrintln(String(RTC.factoryResetCounter)); + + if (RTC.factoryResetCounter >= 3) + { + serialPrintln(F("RESET: Too many resets, protecting your flash memory (powercycle to solve this)")); + return; + } + } + else + { + serialPrintln(F("RESET: Cold boot")); + initRTC(); + + // TODO TD-er: Store set device model in RTC. + } + + RTC.flashCounter = 0; // reset flashcounter, since we're already counting the number of factory-resets. we dont want to hit a flash-count + // limit during reset. + RTC.factoryResetCounter++; + saveToRTC(); + + if (formatFS) { + // always format on factory reset, in case of corrupt FS + ESPEASY_FS.end(); + serialPrintln(F("RESET: formatting...")); + FS_format(); + serialPrintln(F("RESET: formatting done...")); + process_serialWriteBuffer(); + + if (!ESPEASY_FS.begin()) + { + serialPrintln(F("RESET: FORMAT FS FAILED!")); + return; + } + } + +#if FEATURE_CUSTOM_PROVISIONING + { + MakeProvisioningSettings(ProvisioningSettings); + + if (ProvisioningSettings.get()) { + ProvisioningSettings->setUser(F(DEFAULT_PROVISIONING_USER)); + ProvisioningSettings->setPass(F(DEFAULT_PROVISIONING_PASS)); + ProvisioningSettings->setUrl(F(DEFAULT_PROVISIONING_URL)); + ProvisioningSettings->ResetFactoryDefaultPreference = ResetFactoryDefaultPreference.getPreference(); + saveProvisioningSettings(*ProvisioningSettings); + } + } +#endif // if FEATURE_CUSTOM_PROVISIONING + + // pad files with extra zeros for future extensions + InitFile(SettingsType::SettingsFileEnum::FILE_CONFIG_type); + InitFile(SettingsType::SettingsFileEnum::FILE_SECURITY_type); + #if FEATURE_NOTIFIER + InitFile(SettingsType::SettingsFileEnum::FILE_NOTIFICATION_type); + #endif // if FEATURE_NOTIFIER + + InitFile(getRulesFileName(0), 0); + + Settings.clearMisc(); + + if (!ResetFactoryDefaultPreference.keepNTP() || mustApplySafebootDefaults) { + Settings.clearTimeSettings(); + Settings.UseNTP(DEFAULT_USE_NTP); + strcpy_P(Settings.NTPHost, PSTR(DEFAULT_NTP_HOST)); + Settings.TimeZone = DEFAULT_TIME_ZONE; + Settings.DST = DEFAULT_USE_DST; + } + + if (!ResetFactoryDefaultPreference.keepNetwork() || mustApplySafebootDefaults) { + Settings.clearNetworkSettings(); + + // TD-er Reset access control + str2ip(F(DEFAULT_IPRANGE_LOW), SecuritySettings.AllowedIPrangeLow); + str2ip(F(DEFAULT_IPRANGE_HIGH), SecuritySettings.AllowedIPrangeHigh); + SecuritySettings.IPblockLevel = DEFAULT_IP_BLOCK_LEVEL; + + #if DEFAULT_USE_STATIC_IP + str2ip((char *)DEFAULT_IP, Settings.IP); + str2ip((char *)DEFAULT_DNS, Settings.DNS); + str2ip((char *)DEFAULT_GW, Settings.Gateway); + str2ip((char *)DEFAULT_SUBNET, Settings.Subnet); + #endif // if DEFAULT_USE_STATIC_IP + Settings.IncludeHiddenSSID(DEFAULT_WIFI_INCLUDE_HIDDEN_SSID); + } + + Settings.clearNotifications(); + Settings.clearControllers(); + Settings.clearTasks(); + + if (!ResetFactoryDefaultPreference.keepLogConsoleSettings() || mustApplySafebootDefaults) { + Settings.clearLogSettings(); + str2ip((char *)DEFAULT_SYSLOG_IP, Settings.Syslog_IP); + + setLogLevelFor(LOG_TO_SYSLOG, DEFAULT_SYSLOG_LEVEL); + setLogLevelFor(LOG_TO_SERIAL, DEFAULT_SERIAL_LOG_LEVEL); + setLogLevelFor(LOG_TO_WEBLOG, DEFAULT_WEB_LOG_LEVEL); + setLogLevelFor(LOG_TO_SDCARD, DEFAULT_SD_LOG_LEVEL); + Settings.SyslogFacility = DEFAULT_SYSLOG_FACILITY; + Settings.SyslogPort = DEFAULT_SYSLOG_PORT; + Settings.UseValueLogger = DEFAULT_USE_SD_LOG; + + // FIXME TD-er: Must also keep console settings. + Settings.console_serial_port = DEFAULT_CONSOLE_PORT; + Settings.console_serial_rxpin = DEFAULT_CONSOLE_PORT_RXPIN; + Settings.console_serial_txpin = DEFAULT_CONSOLE_PORT_TXPIN; + Settings.console_serial0_fallback = DEFAULT_CONSOLE_SER0_FALLBACK; + Settings.UseSerial = DEFAULT_USE_SERIAL; + Settings.BaudRate = DEFAULT_SERIAL_BAUD; + } + + if (!ResetFactoryDefaultPreference.keepUnitName() || mustApplySafebootDefaults) { + Settings.clearUnitNameSettings(); + Settings.Unit = UNIT; + strcpy_P(Settings.Name, PSTR(DEFAULT_NAME)); + Settings.UDPPort = DEFAULT_SYNC_UDP_PORT; + } + + if (!ResetFactoryDefaultPreference.keepWiFi() || mustApplySafebootDefaults) { + strcpy_P(SecuritySettings.WifiSSID, PSTR(DEFAULT_SSID)); + strcpy_P(SecuritySettings.WifiKey, PSTR(DEFAULT_KEY)); + strcpy_P(SecuritySettings.WifiSSID2, PSTR(DEFAULT_SSID2)); + strcpy_P(SecuritySettings.WifiKey2, PSTR(DEFAULT_KEY2)); + strcpy_P(SecuritySettings.WifiAPKey, PSTR(DEFAULT_AP_KEY)); + } + strcpy_P(SecuritySettings.Password, PSTR(DEFAULT_ADMIN_PASS)); + + Settings.ResetFactoryDefaultPreference = ResetFactoryDefaultPreference.getPreference(); + + // now we set all parameters that need to be non-zero as default value + + + Settings.PID = ESP_PROJECT_PID; + Settings.Version = VERSION; + Settings.Build = get_build_nr(); + + // Settings.IP_Octet = DEFAULT_IP_OCTET; + // Settings.Delay = DEFAULT_DELAY; + Settings.Pin_i2c_sda = gpio_settings.i2c_sda; + Settings.Pin_i2c_scl = gpio_settings.i2c_scl; + Settings.Pin_status_led = gpio_settings.status_led; + + // Settings.Pin_status_led_Inversed = DEFAULT_PIN_STATUS_LED_INVERSED; + Settings.Pin_sd_cs = -1; + Settings.Pin_Reset = DEFAULT_PIN_RESET_BUTTON; + Settings.Protocol[0] = DEFAULT_PROTOCOL; + + // Settings.deepSleep_wakeTime = 0; // Sleep disabled + // Settings.CustomCSS = false; + // Settings.InitSPI = DEFAULT_SPI; + + // advanced Settings + // Settings.UseRules = DEFAULT_USE_RULES; + Settings.ControllerEnabled[0] = DEFAULT_CONTROLLER_ENABLED; + Settings.MQTTRetainFlag_unused = DEFAULT_MQTT_RETAIN; + Settings.MessageDelay_unused = DEFAULT_MQTT_DELAY; + Settings.MQTTUseUnitNameAsClientId_unused = DEFAULT_MQTT_USE_UNITNAME_AS_CLIENTID; + + // allow to set default latitude and longitude + #ifdef DEFAULT_LATITUDE + Settings.Latitude = DEFAULT_LATITUDE; + #endif // ifdef DEFAULT_LATITUDE + #ifdef DEFAULT_LONGITUDE + Settings.Longitude = DEFAULT_LONGITUDE; + #endif // ifdef DEFAULT_LONGITUDE + +#ifdef ESP32 + + // Ethernet related settings are never used on ESP8266 + Settings.ETH_Phy_Addr = gpio_settings.eth_phyaddr; + Settings.ETH_Pin_mdc_cs = gpio_settings.eth_mdc; + Settings.ETH_Pin_mdio_irq = gpio_settings.eth_mdio; + Settings.ETH_Pin_power_rst = gpio_settings.eth_power; + Settings.ETH_Phy_Type = gpio_settings.eth_phytype; + Settings.ETH_Clock_Mode = gpio_settings.eth_clock_mode; +#endif // ifdef ESP32 + Settings.NetworkMedium = gpio_settings.network_medium; + + /* + Settings.GlobalSync = DEFAULT_USE_GLOBAL_SYNC; + + Settings.IP_Octet = DEFAULT_IP_OCTET; + Settings.WDI2CAddress = DEFAULT_WD_IC2_ADDRESS; + Settings.UseSSDP = DEFAULT_USE_SSDP; + Settings.ConnectionFailuresThreshold = DEFAULT_CON_FAIL_THRES; + Settings.WireClockStretchLimit = DEFAULT_I2C_CLOCK_LIMIT; + */ + + // Settings.I2C_clockSpeed = DEFAULT_I2C_CLOCK_SPEED; + + Settings.JSONBoolWithoutQuotes(DEFAULT_JSON_BOOL_WITHOUT_QUOTES); + Settings.EnableTimingStats(DEFAULT_ENABLE_TIMING_STATS); + +#ifdef PLUGIN_DESCR + strcpy_P(Settings.Name, PSTR(PLUGIN_DESCR)); +#endif // ifdef PLUGIN_DESCR + +#ifndef LIMIT_BUILD_SIZE + addPredefinedPlugins(gpio_settings); + addPredefinedRules(gpio_settings); +#endif // ifndef LIMIT_BUILD_SIZE + +#if DEFAULT_CONTROLLER + { + // Place in a scope to have its memory freed ASAP + MakeControllerSettings(ControllerSettings); // -V522 + + if (AllocatedControllerSettings()) { + safe_strncpy(ControllerSettings->Subscribe, F(DEFAULT_SUB), sizeof(ControllerSettings->Subscribe)); + safe_strncpy(ControllerSettings->Publish, F(DEFAULT_PUB), sizeof(ControllerSettings->Publish)); + safe_strncpy(ControllerSettings->MQTTLwtTopic, F(DEFAULT_MQTT_LWT_TOPIC), sizeof(ControllerSettings->MQTTLwtTopic)); + safe_strncpy(ControllerSettings->LWTMessageConnect, F(DEFAULT_MQTT_LWT_CONNECT_MESSAGE), + sizeof(ControllerSettings->LWTMessageConnect)); + safe_strncpy(ControllerSettings->LWTMessageDisconnect, F(DEFAULT_MQTT_LWT_DISCONNECT_MESSAGE), + sizeof(ControllerSettings->LWTMessageDisconnect)); + str2ip((char *)DEFAULT_SERVER, ControllerSettings->IP); + ControllerSettings->setHostname(F(DEFAULT_SERVER_HOST)); + ControllerSettings->UseDNS = DEFAULT_SERVER_USEDNS; + ControllerSettings->useExtendedCredentials(DEFAULT_USE_EXTD_CONTROLLER_CREDENTIALS); + ControllerSettings->Port = DEFAULT_PORT; + ControllerSettings->ClientTimeout = DEFAULT_CONTROLLER_TIMEOUT; + ControllerSettings->enableESPEasyNowFallback(DEFAULT_CONTROLLER_FALLBACK_MESH); + setControllerUser(0, *ControllerSettings, F(DEFAULT_CONTROLLER_USER)); + setControllerPass(0, *ControllerSettings, F(DEFAULT_CONTROLLER_PASS)); + + SaveControllerSettings(0, *ControllerSettings); + } + } +#endif // if DEFAULT_CONTROLLER + +#ifdef ESP32 + if (!mustApplySafebootDefaults) + { + Settings.ResetFactoryDefaultPreference = ResetFactoryDefaultPreference.getPreference(); + + if (ResetFactoryDefaultPreference.keepUnitName()) + { + FactoryDefault_UnitName_NVS unitNameNVS{}; + unitNameNVS.applyToSettings_from_NVS(preferences); + } + + if (ResetFactoryDefaultPreference.keepWiFi()) + { + FactoryDefault_WiFi_NVS wifiNVS{}; + wifiNVS.applyToSettings_from_NVS(preferences); + } + + if (ResetFactoryDefaultPreference.keepNetwork()) + { + // Restore Network IP settings + FactoryDefault_Network_NVS network_nvs; + network_nvs.applyToSettings_from_NVS(preferences); + } + + if (ResetFactoryDefaultPreference.keepLogConsoleSettings()) + { + // Restore Log and Console settings + FactoryDefault_LogConsoleSettings_NVS log_console_nvs; + log_console_nvs.applyToSettings_from_NVS(preferences); + } + +# if FEATURE_ALTERNATIVE_CDN_URL + + if (ResetFactoryDefaultPreference.keepCustomCdnUrl()) { + FactoryDefault_CDN_customurl_NVS::applyToSettings_from_NVS(preferences); + } +# endif // if FEATURE_ALTERNATIVE_CDN_URL + preferences.end(); + } +#endif // ifdef ESP32 + + const bool forFactoryReset = true; + SaveSettings(forFactoryReset); + #ifndef BUILD_NO_RAM_TRACKER + checkRAM(F("ResetFactory2")); + #endif // ifndef BUILD_NO_RAM_TRACKER + serialPrintln(F("RESET: Successful, rebooting. (you might need to press the reset button if you've just flashed the firmware)")); + + // NOTE: this is a known ESP8266 bug, not our fault. :) + delay(1000); + WiFi.persistent(true); // use SDK storage of SSID/WPA parameters + WiFiEventData.intent_to_reboot = true; + WifiDisconnect(); // this will store empty ssid/wpa into sdk storage + WiFi.persistent(false); // Do not use SDK storage of SSID/WPA parameters + reboot(IntendedRebootReason_e::ResetFactory); +} + +/*********************************************************************************************\ + Collect the stored preference for factory default +\*********************************************************************************************/ +void applyFactoryDefaultPref() { + // TODO TD-er: Store it in more places to make it more persistent + Settings.ResetFactoryDefaultPreference = ResetFactoryDefaultPreference.getPreference(); + +#ifdef ESP32 + ESPEasy_NVS_Helper preferences; + preferences.begin(F(FACTORY_DEFAULT_NVS_NAMESPACE)); + ResetFactoryDefaultPreference.to_NVS(preferences); + { + FactoryDefault_UnitName_NVS unitNameNVS{}; + + if (ResetFactoryDefaultPreference.keepUnitName()) + { + // Store Unit nr and hostname + unitNameNVS.fromSettings_to_NVS(preferences); + } else { + unitNameNVS.clear_from_NVS(preferences); + } + } + { + FactoryDefault_WiFi_NVS wifiNVS{}; + + if (ResetFactoryDefaultPreference.keepWiFi()) + { + // Store WiFi credentials + wifiNVS.fromSettings_to_NVS(preferences); + } else { + wifiNVS.clear_from_NVS(preferences); + } + } + { + FactoryDefault_Network_NVS network_nvs{}; + + if (ResetFactoryDefaultPreference.keepNetwork()) + { + // Store Network IP settings + network_nvs.fromSettings_to_NVS(preferences); + } else { + network_nvs.clear_from_NVS(preferences); + } + } + { + FactoryDefault_LogConsoleSettings_NVS log_console_nvs{}; + + if (ResetFactoryDefaultPreference.keepLogConsoleSettings()) + { + // Store Log and Console settings + log_console_nvs.fromSettings_to_NVS(preferences); + } else { + log_console_nvs.clear_from_NVS(preferences); + } + } +# if FEATURE_ALTERNATIVE_CDN_URL + { + if (ResetFactoryDefaultPreference.keepCustomCdnUrl()) + { + // Store custom CDN + FactoryDefault_CDN_customurl_NVS::fromSettings_to_NVS(preferences); + } else { + FactoryDefault_CDN_customurl_NVS::clear_from_NVS(preferences); + } + } +# endif // if FEATURE_ALTERNATIVE_CDN_URL + + + preferences.end(); +#endif // ifdef ESP32 +} diff --git a/src/src/Helpers/ESPEasy_checks.cpp b/src/src/Helpers/ESPEasy_checks.cpp index f2ab659359..f6357ee5b0 100644 --- a/src/src/Helpers/ESPEasy_checks.cpp +++ b/src/src/Helpers/ESPEasy_checks.cpp @@ -81,7 +81,7 @@ void check_max_size() { void run_compiletime_checks() { #ifndef LIMIT_BUILD_SIZE check_size(); - check_size(); + check_size(); check_max_size(); #ifdef ESP32 constexpr unsigned int SettingsStructSize = (340 + 84 * TASKS_MAX); @@ -109,7 +109,6 @@ void run_compiletime_checks() { check_size(); // Is not stored #endif - // LogStruct is mainly dependent on the number of lines. // Has to be round up to multiple of 4. #if ESP_IDF_VERSION_MAJOR > 3 diff --git a/src/src/Helpers/ESPEasy_now_handler.cpp b/src/src/Helpers/ESPEasy_now_handler.cpp new file mode 100644 index 0000000000..eb8c29b752 --- /dev/null +++ b/src/src/Helpers/ESPEasy_now_handler.cpp @@ -0,0 +1,1542 @@ +#include "../Helpers/ESPEasy_now_handler.h" + +#ifdef USES_ESPEASY_NOW + +# include "../Helpers/_CPlugin_Helper.h" + + +# include "../ControllerQueue/MQTT_queue_element.h" +# include "../DataStructs/ESPEasy_Now_DuplicateCheck.h" +# include "../DataStructs/ESPEasy_Now_MQTT_queue_check_packet.h" +# include "../DataStructs/ESPEasy_now_hdr.h" +# include "../DataStructs/ESPEasy_now_merger.h" +# include "../DataStructs/ESPEasy_Now_packet.h" +# include "../DataStructs/ESPEasy_Now_p2p_data.h" +# include "../DataStructs/ESPEasy_now_splitter.h" +# include "../DataStructs/ESPEasy_now_traceroute.h" +# include "../DataStructs/ESPEasy_Now_NTP_query.h" +# include "../DataStructs/MessageRouteInfo.h" +# include "../DataStructs/MAC_address.h" +# include "../DataStructs/NodeStruct.h" +# include "../DataStructs/TimingStats.h" +# include "../DataStructs/WiFi_AP_Candidate.h" +# include "../ESPEasyCore/Controller.h" +# include "../ESPEasyCore/ESPEasyWifi.h" +# include "../ESPEasyCore/ESPEasy_Log.h" +# include "../Globals/ESPEasy_now_peermanager.h" +# include "../Globals/ESPEasyWiFiEvent.h" +# include "../Globals/ESPEasy_time.h" +# include "../Globals/MQTT.h" +# include "../Globals/Nodes.h" +# include "../Globals/RTC.h" +# include "../Globals/SecuritySettings.h" +# include "../Globals/SendData_DuplicateChecker.h" +# include "../Globals/Settings.h" +# include "../Globals/WiFi_AP_Candidates.h" +# include "../Helpers/CRC_functions.h" +# include "../Helpers/ESPEasy_Storage.h" +# include "../Helpers/ESPEasy_time_calc.h" +# include "../Helpers/ESPEasyMutex.h" +# include "../Helpers/PeriodicalActions.h" + + +# include + + +# define ESPEASY_NOW_ACTIVITY_TIMEOUT 125000 // 2 minutes + 5 sec +# define ESPEASY_NOW_SINCE_LAST_BROADCAST 65000 // 1 minute + 5 sec to start sending a node directly +# define ESPEASY_NOW_CHANNEL_SEARCH_TIMEOUT 5000 // Time to wait for announcement packets on a single channel + +# define ESPEASY_NOW_MAX_CHANNEL 13 + +# define ESPEASY_NOW_TMP_SSID "ESPEASY_NOW" +# define ESPEASY_NOW_TMP_PASSPHRASE "random_passphrase" + +static uint64_t ICACHE_FLASH_ATTR mac_to_key(const uint8_t *mac, ESPEasy_now_hdr::message_t messageType, uint8_t message_count) +{ + uint64_t key = message_count; + + key = key << 8; + key += static_cast(messageType); + + for (uint8_t i = 0; i < 6; ++i) { + key = key << 8; + key += mac[i]; + } + return key; +} + +ESPEasy_Mutex ESPEasy_now_in_queue_mutex; +std::map ESPEasy_now_in_queue; + + +void ICACHE_FLASH_ATTR ESPEasy_now_onReceive(const uint8_t mac[6], const uint8_t *buf, size_t count, void *cbarg) { + START_TIMER; + const size_t payload_length = count - sizeof(ESPEasy_now_hdr); + + if ((count < sizeof(ESPEasy_now_hdr)) || (payload_length > ESPEasy_Now_packet::getMaxPayloadSize())) { + STOP_TIMER(INVALID_ESPEASY_NOW_LOOP); + return; // Too small + } + + ESPEasy_now_hdr header(buf); + + if (header.header_version != ESPEASY_NOW_HEADER_VERSION) { + STOP_TIMER(INVALID_ESPEASY_NOW_LOOP); + return; + } + + const uint8_t *payload = buf + sizeof(ESPEasy_now_hdr); + const uint16_t checksum = calc_CRC16(reinterpret_cast(payload), payload_length); + + if (header.checksum != checksum) { + STOP_TIMER(INVALID_ESPEASY_NOW_LOOP); + return; + } + const uint64_t key = mac_to_key(mac, header.message_type, header.message_count); + + { + ESPEasy_now_in_queue_mutex.lock(); + ESPEasy_now_in_queue[key].addPacket(header.packet_nr, mac, buf, count); + ESPEasy_now_in_queue_mutex.unlock(); + } + STOP_TIMER(RECEIVE_ESPEASY_NOW_LOOP); +} + +ESPEasy_now_handler_t::ESPEasy_now_handler_t() +{ + _best_NTP_candidate = new ESPEasy_Now_NTP_query(); +} + +ESPEasy_now_handler_t::~ESPEasy_now_handler_t() +{ + if (_best_NTP_candidate != nullptr) { + delete _best_NTP_candidate; + _best_NTP_candidate = nullptr; + } +} + +void ESPEasy_now_handler_t::loadConfig(struct EventStruct *event) +{ + customConfig.reset(new (std::nothrow) C019_ConfigStruct); + + if (customConfig) { + customConfig->init(event); + } +} + +bool ESPEasy_now_handler_t::begin() +{ + if (!Settings.UseESPEasyNow()) { return false; } + + // Check to see if we have an ESPEasy_NOW controller defined. + // Call CPLUGIN_INIT on it to load the settings. + const controllerIndex_t ESPEasy_NOW_controller_idx = get_ESPEasy_NOW_controller_index(); + + if (validControllerIndex(ESPEasy_NOW_controller_idx) && Settings.ControllerEnabled[ESPEasy_NOW_controller_idx]) { + struct EventStruct tmpEvent; + tmpEvent.ControllerIndex = ESPEasy_NOW_controller_idx; + + if (!CPluginCall(CPlugin::Function::CPLUGIN_INIT, &tmpEvent)) { return false; } + } + return do_begin(); +} + +bool ESPEasy_now_handler_t::do_begin() +{ + if (!Settings.UseESPEasyNow()) { return false; } + + if (use_EspEasy_now) { return true; } + + if ((WiFi.scanComplete() == WIFI_SCAN_RUNNING) || !WiFiEventData.processedScanDone) { return false; } + + if (WiFiEventData.wifiConnectInProgress) { + /* + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + String log; + if (log.reserve(64)) { + log = F(ESPEASY_NOW_NAME); + log += F(": wifiConnectInProgress"); + addLog(LOG_LEVEL_INFO, log); + } + } + */ + return false; + } + + if (temp_disable_EspEasy_now_timer != 0) { + if (!timeOutReached(temp_disable_EspEasy_now_timer)) { + return false; + } else { + temp_disable_EspEasy_now_timer = 0; + } + } + + _usedWiFiChannel = Nodes.getESPEasyNOW_channel(); + + if (_usedWiFiChannel == 0) { + _scanChannelsMode = true; + ++_lastScannedChannel; + + if (_lastScannedChannel > ESPEASY_NOW_MAX_CHANNEL) { + _lastScannedChannel = 1; + } + _usedWiFiChannel = _lastScannedChannel; + } else { + _scanChannelsMode = false; + } + + _last_traceroute_sent = 0; + _last_traceroute_received = 0; + _last_used = millis(); + _last_started = _last_used; + _controllerIndex = INVALID_CONTROLLER_INDEX; + + if (isESPEasy_now_only()) { + setConnectionSpeed(); + } + + const String ssid = F(ESPEASY_NOW_TMP_SSID); + const String passphrase = F(ESPEASY_NOW_TMP_PASSPHRASE); + + setAP(true); + # ifdef ESP32 + // We're running a dummy AP, so don't run DHCP server. + # if ESP_IDF_VERSION_MAJOR < 5 + tcpip_adapter_dhcps_stop(TCPIP_ADAPTER_IF_AP); + #endif + # endif // ifdef ESP32 + + // Make sure AP will not be turned off. + WiFiEventData.timerAPoff.clear(); + WiFiEventData.timerAPstart.clear(); + + + int ssid_hidden = 1; + int max_connection = 6; + WiFi.softAP(ssid.c_str(), passphrase.c_str(), _usedWiFiChannel, ssid_hidden, max_connection); + + // WiFi.softAPdisconnect(false); + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLogMove(LOG_LEVEL_INFO, concat(F(ESPEASY_NOW_NAME ": begin on channel "), _usedWiFiChannel)); + } + + if (!WifiEspNow.begin()) { + addLog(LOG_LEVEL_ERROR, F(ESPEASY_NOW_NAME ": Failed to initialize ESPEasy-NOW")); + return false; + } + ESPEasy_now_peermanager.removeAllPeers(); + ESPEasy_now_peermanager.addKnownPeers(); + + WifiEspNow.onReceive(ESPEasy_now_onReceive, nullptr); + + use_EspEasy_now = true; + addLog(LOG_LEVEL_INFO, F(ESPEASY_NOW_NAME " enabled")); + + if (_scanChannelsMode) { + WiFiEventData.lastScanMoment.clear(); + } + + if (_scanChannelsMode) { + WifiScan(false, _usedWiFiChannel); + + // Send discovery announce to all found hidden APs using the current channel + for (auto it = WiFi_AP_Candidates.scanned_begin(); it != WiFi_AP_Candidates.scanned_end(); ++it) { + if (it->bits.isHidden && (it->channel == _usedWiFiChannel)) { + const MAC_address mac(it->bssid); + sendDiscoveryAnnounce(mac, _usedWiFiChannel); + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLogMove(LOG_LEVEL_INFO, concat(F(ESPEASY_NOW_NAME ": Send discovery to "), mac.toString())); + } + } + } + } else { + sendDiscoveryAnnounce(); + } + + return true; +} + +void ESPEasy_now_handler_t::end() +{ + _controllerIndex = INVALID_CONTROLLER_INDEX; + _usedWiFiChannel = 0; + use_EspEasy_now = false; + + if (!Settings.UseLastWiFiFromRTC()) { + RTC.clearLastWiFi(); // Force a WiFi scan + } + + if (_last_used != 0) { + // Only call WifiEspNow.end() if it was started. + WifiEspNow.end(); + _last_used = 0; + _last_started = 0; + } + { + ESPEasy_now_in_queue_mutex.lock(); + ESPEasy_now_in_queue.clear(); + ESPEasy_now_in_queue_mutex.unlock(); + } + addLog(LOG_LEVEL_INFO, F(ESPEASY_NOW_NAME " disabled")); +} + +bool ESPEasy_now_handler_t::loop() +{ + loop_check_ESPEasyNOW_run_state(); + + if (!use_EspEasy_now) { return false; } + bool somethingProcessed = loop_process_ESPEasyNOW_in_queue(); + + loop_process_ESPEasyNOW_send_queue(); + + if (_send_failed_count > 30 /*|| !active()*/) { + _send_failed_count = 0; + + // FIXME TD-er: Must check/mark so this becomes true: isESPEasy_now_only() + + // Start scanning the next channel to see if we may end up with a new found node + // WifiScan(false, false); + // addPeerFromWiFiScan(); + // _last_used = millis(); + end(); + begin(); + } + return somethingProcessed; +} + +controllerIndex_t ESPEasy_now_handler_t::get_ESPEasy_NOW_controller_index() +{ + for (controllerIndex_t x = 0; x < CONTROLLER_MAX; x++) { + if (Settings.Protocol[x] == 19) { + return x; + } + } + return INVALID_CONTROLLER_INDEX; +} + +void ESPEasy_now_handler_t::loop_check_ESPEasyNOW_run_state() +{ + const uint8_t ESPEasyNOW_channel = Nodes.getESPEasyNOW_channel(); + const bool channelChanged = _usedWiFiChannel != ESPEasyNOW_channel; + + if (!WifiIsAP(WiFi.getMode()) || (channelChanged && !Nodes.isEndpoint())) { + // AP mode may be turned off externally, or WiFi channel may have changed. + // If so restart ESPEasy-now handler + if (use_EspEasy_now) { + if (ESPEasyNOW_channel == 0) { + // We need to give it some time to receive announcement messages and maybe even a traceroute + if (timePassedSince(_last_started) > ESPEASY_NOW_CHANNEL_SEARCH_TIMEOUT) { + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLog(LOG_LEVEL_INFO, + concat(F(ESPEASY_NOW_NAME ": No peers with distance set on channel "), static_cast(_usedWiFiChannel))); + } + + end(); + } + } else { + if (channelChanged) { + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLog(LOG_LEVEL_INFO, concat(F(ESPEASY_NOW_NAME ": Move to channel "), static_cast(ESPEasyNOW_channel))); + } + } + + end(); + } + } + } + + if (use_EspEasy_now) { + if (!Nodes.isEndpoint()) { + const bool traceroute_received_timeout = _last_traceroute_received != 0 && + (timePassedSince(_last_traceroute_received) > ESPEASY_NOW_ACTIVITY_TIMEOUT + 30000); + const bool traceroute_sent_timeout = _last_traceroute_sent != 0 && + (timePassedSince(_last_traceroute_sent) > ESPEASY_NOW_ACTIVITY_TIMEOUT); + const bool first_traceroute_receive_timeout = _last_traceroute_received == 0 && + (timePassedSince(_last_started) > ESPEASY_NOW_ACTIVITY_TIMEOUT + 30000); + + if (traceroute_received_timeout || traceroute_sent_timeout || first_traceroute_receive_timeout) { + addLog(LOG_LEVEL_INFO, F(ESPEASY_NOW_NAME ": Inactive due to not receiving trace routes")); + end(); + temp_disable_EspEasy_now_timer = millis() + 10000; + WifiScan(true, Nodes.getESPEasyNOW_channel()); + return; + } + } + } + + + if (Settings.UseESPEasyNow() != use_EspEasy_now) { + if (!use_EspEasy_now) { + begin(); + } else { + end(); + } + } +} + +bool ESPEasy_now_handler_t::loop_process_ESPEasyNOW_in_queue() +{ + bool somethingProcessed = false; + + if (!ESPEasy_now_in_queue.empty()) { + unsigned long timeout = millis() + 20; + + for (auto it = ESPEasy_now_in_queue.begin(); !timeOutReached(timeout) && it != ESPEasy_now_in_queue.end();) { + const bool expired = it->second.expired(); + bool removeMessage = true; + START_TIMER; + + bool valid = it->second.valid(); + + if (!valid || !it->second.messageComplete()) { + if (!valid || expired) { + if (expired) { + STOP_TIMER(EXPIRED_ESPEASY_NOW_LOOP); + } else { + STOP_TIMER(INVALID_ESPEASY_NOW_LOOP); + } + + if (loglevelActiveFor(LOG_LEVEL_ERROR)) { + String log; + + if (log.reserve(85)) { + log = F(ESPEASY_NOW_NAME); + log += F(": "); + log += it->second.getLogString(); + + if (expired) { + log += F(" Expired!"); + } else { + log += F(" Invalid!"); + } + addLog(LOG_LEVEL_ERROR, log); + } + } + } else { + if (!expired) { + removeMessage = false; + } + } + } else if (!expired) { + // Process it + + // FIXME TD-er: removeMessage has not been changed since construction, is this correct? + bool mustKeep = !removeMessage; + somethingProcessed = processMessage(it->second, mustKeep); + removeMessage = !mustKeep; + STOP_TIMER(HANDLE_ESPEASY_NOW_LOOP); + } + + if (removeMessage) { + ESPEasy_now_in_queue_mutex.lock(); + it = ESPEasy_now_in_queue.erase(it); + ESPEasy_now_in_queue_mutex.unlock(); + + /* + // FIXME TD-er: For now only process one item and then wait for the next loop. + if (somethingProcessed) { + return true; + } + */ + } else { + ++it; + } + } + } + return somethingProcessed; +} + +void ESPEasy_now_handler_t::loop_process_ESPEasyNOW_send_queue() +{ + // Try to pace the broadcasts of traceroutes and sending MQTT checks + // Only process one every 100 msec. + static unsigned long last_queue_processed = 0; + + if (timePassedSince(last_queue_processed) > 100) { + if (!Nodes.ESPEasy_now_traceroute_queue.empty()) { + Nodes.ESPEasy_now_traceroute_queue.sort(); + const ESPEasy_now_traceroute_struct route = Nodes.ESPEasy_now_traceroute_queue.front(); + sendTraceRoute(route); + _last_traceroute_received = millis(); + last_queue_processed = millis(); + + // Remove possible duplicate routes and keep the best 2 + size_t nrRoutes = 0; + + for (auto it = Nodes.ESPEasy_now_traceroute_queue.begin(); it != Nodes.ESPEasy_now_traceroute_queue.end();) { + addLog(LOG_LEVEL_INFO, concat(F("TraceRoutes: "), it->toString())); + + if (route.sameRoute(*it) || (nrRoutes >= 5)) { + it = Nodes.ESPEasy_now_traceroute_queue.erase(it); + } else { + ++nrRoutes; + ++it; + } + } + } else if (!Nodes.ESPEasy_now_MQTT_check_queue.empty()) { + while (!Nodes.ESPEasy_now_MQTT_check_queue.empty()) { + const MAC_address mac = Nodes.ESPEasy_now_MQTT_check_queue.front(); + const NodeStruct *node = Nodes.getNodeByMac(mac); + + if (node != nullptr) { + const uint8_t channel = node->channel; + sendMQTTCheckControllerQueue(mac, channel); + last_queue_processed = millis(); + } + + // Remove duplicate entries in the list. + for (auto it = Nodes.ESPEasy_now_MQTT_check_queue.begin(); it != Nodes.ESPEasy_now_MQTT_check_queue.end();) { + if (*it == mac) { + it = Nodes.ESPEasy_now_MQTT_check_queue.erase(it); + } else { + ++it; + } + } + } + } + } +} + +bool ESPEasy_now_handler_t::active() const +{ + if (!use_EspEasy_now) { + return false; + } + + if (_last_started == 0) { + return false; + } + + if (timePassedSince(_last_started) < ESPEASY_NOW_SINCE_LAST_BROADCAST) { + // Give the unit some time to find other nodes. + return true; + } + + /* + if (Nodes.lastTimeValidDistanceExpired()) { + return false; + } + */ + const bool traceroute_received_timeout = _last_traceroute_received != 0 && + (timePassedSince(_last_traceroute_received) > ESPEASY_NOW_ACTIVITY_TIMEOUT + 30000); + const bool traceroute_sent_timeout = _last_traceroute_sent != 0 && + (timePassedSince(_last_traceroute_sent) > ESPEASY_NOW_ACTIVITY_TIMEOUT); + const bool first_traceroute_receive_timeout = _last_traceroute_received == 0 && + (timePassedSince(_last_started) > ESPEASY_NOW_ACTIVITY_TIMEOUT + 30000); + + if (traceroute_received_timeout || traceroute_sent_timeout || first_traceroute_receive_timeout) { + addLog(LOG_LEVEL_INFO, F(ESPEASY_NOW_NAME ": Inactive due to not receiving trace routes")); + + return false; + } + + return timePassedSince(_last_used) < ESPEASY_NOW_ACTIVITY_TIMEOUT; +} + +MAC_address ESPEasy_now_handler_t::getActiveESPEasyNOW_MAC() const +{ + if (use_EspEasy_now) { + // FIXME TD-er: Must not check active mode, but rather the intended mode. + if (WifiIsAP(WiFi.getMode())) { + return WifiSoftAPmacAddress(); + } else { + return WifiSTAmacAddress(); + } + } + MAC_address this_mac; + + return this_mac; +} + +void ESPEasy_now_handler_t::addPeerFromWiFiScan() +{ + const int8_t scanCompleteStatus = WiFi.scanComplete(); + + for (int8_t i = 0; i < scanCompleteStatus; ++i) { + addPeerFromWiFiScan(i); + } +} + +void ESPEasy_now_handler_t::addPeerFromWiFiScan(const WiFi_AP_Candidate& peer) +{ + MAC_address peer_mac(peer.bssid); + auto nodeInfo = Nodes.getNodeByMac(peer_mac); + + if (nodeInfo != nullptr) { + Nodes.setRSSI(peer_mac, peer.rssi); + + // Sometimes a scan on one channel may see a node on another channel + // So don't set the channel of known node based on the WiFi scan + // nodeInfo->channel = WiFi.channel(scanIndex); + } else { + NodeStruct tmpNodeInfo; + tmpNodeInfo.setRSSI(peer.rssi); + tmpNodeInfo.channel = peer.channel; + peer_mac.get(tmpNodeInfo.ap_mac); + + if (tmpNodeInfo.markedAsPriorityPeer()) { + // FIXME TD-er: For now we assume the other node uses AP for ESPEasy-NOW + tmpNodeInfo.setESPEasyNow_mac(peer_mac); + + if (Nodes.addNode(tmpNodeInfo)) { + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + String log = concat(F(ESPEASY_NOW_NAME ": Found node via WiFi scan: "), peer_mac.toString()); + log += ' '; + log += tmpNodeInfo.getRSSI(); + log += concat(F(" dBm ch: "), static_cast(tmpNodeInfo.channel)); + addLog(LOG_LEVEL_INFO, log); + } + + // Must trigger a discovery request from the node. + sendDiscoveryAnnounce(peer_mac, peer.channel); + } + } + } +} + +bool ESPEasy_now_handler_t::processMessage(const ESPEasy_now_merger& message, bool& mustKeep) +{ + bool handled = false; + bool considerActive = false; + + mustKeep = false; + + { + // Check if message is sent by this node + MAC_address receivedMAC; + message.getMac(receivedMAC.mac); + + if ((WifiSoftAPmacAddress() == receivedMAC) || + (WifiSTAmacAddress() == receivedMAC)) { return handled; } + } + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLog(LOG_LEVEL_INFO, concat(F(ESPEASY_NOW_NAME ": received "), message.getLogString())); + } + + switch (message.getMessageType()) + { + case ESPEasy_now_hdr::message_t::NotSet: + case ESPEasy_now_hdr::message_t::ChecksumError: + break; + case ESPEasy_now_hdr::message_t::Acknowledgement: + break; + case ESPEasy_now_hdr::message_t::Announcement: + handled = handle_DiscoveryAnnounce(message, mustKeep); + break; + case ESPEasy_now_hdr::message_t::NTP_Query: + handled = handle_NTPquery(message, mustKeep); + break; + case ESPEasy_now_hdr::message_t::MQTTControllerMessage: + handled = handle_MQTTControllerMessage(message, mustKeep); + considerActive = true; + break; + case ESPEasy_now_hdr::message_t::MQTTCheckControllerQueue: + handled = handle_MQTTCheckControllerQueue(message, mustKeep); + considerActive = true; + break; + case ESPEasy_now_hdr::message_t::SendData_DuplicateCheck: + handled = handle_SendData_DuplicateCheck(message, mustKeep); + break; + case ESPEasy_now_hdr::message_t::P2P_data: + handled = handle_ESPEasyNow_p2p(message, mustKeep); + considerActive = true; + break; + case ESPEasy_now_hdr::message_t::TraceRoute: + handled = handle_TraceRoute(message, mustKeep); + considerActive = true; + break; + } + + if (handled && considerActive) { + _last_used = millis(); + } + + return handled; +} + +// ************************************************************* +// * Discovery Announcement +// ************************************************************* + +void ESPEasy_now_handler_t::sendDiscoveryAnnounce(int channel) +{ + sendDiscoveryAnnounce(ESPEasy_now_peermanager.getBroadcastMAC(), channel); +} + +void ESPEasy_now_handler_t::sendDiscoveryAnnounce(const MAC_address& mac, int channel) +{ + const NodeStruct *thisNode = Nodes.getThisNode(); + + if (thisNode == nullptr) { + // Should not happen + addLog(LOG_LEVEL_ERROR, F("Cannot send DiscoveryAnnounce, No ThisNode")); + return; + } + + // Append traceroute (if available) + const ESPEasy_now_traceroute_struct *thisTraceRoute = Nodes.getDiscoveryRoute(thisNode->unit); + + size_t len = sizeof(NodeStruct) + 1; // Append length indicator for traceroute + uint8_t traceroute_size = 0; + const uint8_t *traceroute_data = nullptr; + + if (thisTraceRoute != nullptr) { + traceroute_data = thisTraceRoute->getData(traceroute_size); + len += traceroute_size; + } + + ESPEasy_now_splitter msg(ESPEasy_now_hdr::message_t::Announcement, len); + + if (sizeof(NodeStruct) != msg.addBinaryData(reinterpret_cast(thisNode), sizeof(NodeStruct))) { + return; + } + + if (sizeof(uint8_t) != msg.addBinaryData(reinterpret_cast(&traceroute_size), sizeof(uint8_t))) { + return; + } + + if (traceroute_data != nullptr) { + if (traceroute_size != msg.addBinaryData(traceroute_data, traceroute_size)) { + return; + } + } + const unsigned long start = millis(); + + if (channel < 0) { + // Send to all channels + // FIXME TD-er: Not sure whether we can send to channels > 11 in all countries. + for (int ch = 1; ch < ESPEASY_NOW_MAX_CHANNEL; ++ch) { + msg.send(mac, ch); + delay(0); + } + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + String log = concat(F(ESPEASY_NOW_NAME ": Sent discovery to all channels in "), timePassedSince(start)); + log += F(" ms"); + addLogMove(LOG_LEVEL_INFO, log); + } + } else { + if (mac.all_one()) { + size_t count = 0; + size_t sent_count = 0; + + for (auto it = Nodes.begin(); it != Nodes.end(); ++it) { + ++count; + + // if (it->second.getAge() > ESPEASY_NOW_SINCE_LAST_BROADCAST) { + + if (msg.send(it->second.ESPEasy_Now_MAC(), channel)) { + ++sent_count; + } + + // } + } + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + String log = concat(F(ESPEASY_NOW_NAME ": Sent discovery to "), sent_count); + log += '/'; + log += count; + log += concat(F(" Nodes in "), timePassedSince(start)); + log += F(" ms"); + addLogMove(LOG_LEVEL_INFO, log); + } + } + + const NodeStruct *node = Nodes.getNodeByMac(mac); + + if (node != nullptr) { + const MAC_address espeasynow_mac = node->ESPEasy_Now_MAC(); + + if (espeasynow_mac != mac) { + msg.send(espeasynow_mac, channel); + } + } + msg.send(mac, channel); + } + + // WifiScan(true, channel); +} + +bool ESPEasy_now_handler_t::handle_DiscoveryAnnounce(const ESPEasy_now_merger& message, bool& mustKeep) +{ + mustKeep = false; + NodeStruct received; + ESPEasy_now_traceroute_struct traceRoute; + + const uint8_t cur_distance = Nodes.getDistance(); + + // Discovery messages have a single binary blob, starting at 0 + size_t payload_pos = 0; + + message.getBinaryData(reinterpret_cast(&received), sizeof(NodeStruct), payload_pos); + + if (!received.validate()) { return false; } + + MAC_address mac; + + message.getMac(mac); + + // Check to see if we process a node we've sent ourselves. + if (received.isThisNode()) { + return false; + } + + if (!received.setESPEasyNow_mac(mac)) { + if (loglevelActiveFor(LOG_LEVEL_ERROR)) { + addLog(LOG_LEVEL_ERROR, concat(F(ESPEASY_NOW_NAME ": Received discovery message from MAC not stated in message: "), mac.toString())); + } + return false; + } + + uint8_t traceroute_size = 0; + bool isNewNode = false; + bool nodeAdded = false; + + if (message.getBinaryData(reinterpret_cast(&traceroute_size), sizeof(uint8_t), payload_pos) != 0) { + if (traceroute_size != 0) { + ESPEasy_now_traceroute_struct traceroute(traceroute_size); + + if (message.getBinaryData(traceroute.get(), traceroute_size, payload_pos) == traceroute_size) { + isNewNode = Nodes.addNode(received, traceroute); + nodeAdded = true; + } + } + } + + if (!nodeAdded) { + isNewNode = Nodes.addNode(received, ESPEasy_now_traceroute_struct()); + nodeAdded = true; + } + + // Test to see if the discovery announce could be a good candidate for next NTP query. + _best_NTP_candidate->find_best_NTP( + mac, + static_cast(received.timeSource), + received.lastUpdated); + + /* + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + String log; + log.reserve(128); + log = F(ESPEASY_NOW_NAME ": discovery: "); + log += message.getLogString(); + log += '\n'; + log += received.getSummary(); + addLog(LOG_LEVEL_INFO, log); + } + */ + + const NodeStruct *preferred = Nodes.getPreferredNode(); + + if (preferred != nullptr) { + if (preferred->unit == received.unit) { + Nodes.updateThisNode(); + } + } + + if ((received.distance == 255) && (Nodes.getDistance() < 255)) { + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLog(LOG_LEVEL_INFO, concat(F(ESPEASY_NOW_NAME ": Send announce back to unit: "), static_cast(received.unit))); + } + + sendDiscoveryAnnounce(received.ESPEasy_Now_MAC(), received.channel); + { + if (Nodes.isEndpoint()) { + ESPEasy_now_traceroute_struct thisTraceRoute; + thisTraceRoute.addUnit(Settings.Unit); + + // Since we're the end node, claim highest success rate + thisTraceRoute.setSuccessRate_last_node(Settings.Unit, 255); + sendTraceRoute(received.ESPEasy_Now_MAC(), thisTraceRoute, received.channel); + } + } + } + + const uint8_t new_distance = Nodes.getDistance(); + + if ((new_distance != cur_distance) || isNewNode) { + if (new_distance == 0) { + // sendDiscoveryAnnounce(-1); // Send to all channels + sendDiscoveryAnnounce(); // For now disable sending the announce to all channels. + } else { + sendDiscoveryAnnounce(); + } + } + + return true; +} + +// ************************************************************* +// * Trace Route +// ************************************************************* +void ESPEasy_now_handler_t::sendTraceRoute() +{ + if (Nodes.isEndpoint()) { + ESPEasy_now_traceroute_struct thisTraceRoute; + thisTraceRoute.addUnit(Settings.Unit); + + // Since we're the end node, claim highest success rate + thisTraceRoute.setSuccessRate_last_node(Settings.Unit, 255); + + int channel = Nodes.getESPEasyNOW_channel(); + + if (channel == 0) { + channel = WiFi.channel(); + } + + sendTraceRoute(thisTraceRoute, channel); + _last_traceroute_sent = millis(); + } +} + +void ESPEasy_now_handler_t::sendTraceRoute(const ESPEasy_now_traceroute_struct& traceRoute, int channel) +{ + for (auto it = Nodes.begin(); it != Nodes.end(); ++it) { + // if (it->second.getAge() > ESPEASY_NOW_SINCE_LAST_BROADCAST) { + const int peer_channel = it->second.channel == 0 ? channel : it->second.channel; + sendTraceRoute(it->second.ESPEasy_Now_MAC(), traceRoute, peer_channel); + + // } + } + + sendTraceRoute(ESPEasy_now_peermanager.getBroadcastMAC(), traceRoute, Nodes.getESPEasyNOW_channel()); +} + +void ESPEasy_now_handler_t::sendTraceRoute(const MAC_address& mac, const ESPEasy_now_traceroute_struct& traceRoute, int channel) +{ + size_t len = 1; + uint8_t traceroute_size = 0; + const uint8_t *traceroute_data = traceRoute.getData(traceroute_size); + + len += traceroute_size; + + ESPEasy_now_splitter msg(ESPEasy_now_hdr::message_t::TraceRoute, len); + + if (sizeof(uint8_t) != msg.addBinaryData(reinterpret_cast(&traceroute_size), sizeof(uint8_t))) { + addLog(LOG_LEVEL_ERROR, F(ESPEASY_NOW_NAME ": sendTraceRoute error adding route size")); + return; + } + + if (traceroute_size != msg.addBinaryData(traceroute_data, traceroute_size)) { + addLog(LOG_LEVEL_ERROR, F(ESPEASY_NOW_NAME ": sendTraceRoute error adding trace route")); + return; + } + + if (mac.all_zero()) { return; } + + if (channel < 0) { + // Send to all channels + + const unsigned long start = millis(); + + // FIXME TD-er: Not sure whether we can send to channels > 11 in all countries. + for (int ch = 1; ch < ESPEASY_NOW_MAX_CHANNEL; ++ch) { + msg.send(mac, ch); + delay(0); + } + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + String log = concat(F(ESPEASY_NOW_NAME ": Sent Traceroute to all channels in "), timePassedSince(start)); + log += F(" ms"); + addLogMove(LOG_LEVEL_INFO, log); + } + } else { + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + String log = concat(F(ESPEASY_NOW_NAME ": sendTraceRoute "), traceRoute.toString()); + log += concat(F(" ch: "), channel); + log += concat(F(" to: "), mac.toString()); + addLogMove(LOG_LEVEL_INFO, log); + } + + msg.send(mac, channel); + } +} + +bool ESPEasy_now_handler_t::handle_TraceRoute(const ESPEasy_now_merger& message, bool& mustKeep) +{ + mustKeep = false; + size_t payload_pos = 0; + uint8_t traceroute_size = 0; + + if (message.getBinaryData(reinterpret_cast(&traceroute_size), sizeof(uint8_t), payload_pos) != 0) { + if (traceroute_size != 0) { + ESPEasy_now_traceroute_struct traceroute(traceroute_size); + + if (message.getBinaryData(traceroute.get(), traceroute_size, payload_pos) == traceroute_size) { + if (traceroute.getDistance() < 255) { + if (Nodes.getDistance() == 0) { + Nodes.ESPEasy_now_traceroute_queue.clear(); + Nodes.ESPEasy_now_MQTT_check_queue.clear(); + } else { + const uint8_t thisunit = Settings.Unit; + + if (!traceroute.unitInTraceRoute(thisunit)) { + MAC_address mac; + message.getMac(mac); + Nodes.setTraceRoute(mac, traceroute); + + if ((thisunit != 0) && (thisunit != 255) && !Nodes.isEndpoint()) { + // Do not forward the trace route if we're an endpoint. + uint8_t successrate; + const uint8_t lastUnit = traceroute.getUnit(traceroute.getDistance(), successrate); + successrate = Nodes.getSuccessRate(lastUnit); + traceroute.addUnit(thisunit); + traceroute.setSuccessRate_last_node(thisunit, successrate); + Nodes.ESPEasy_now_traceroute_queue.push_back(traceroute); + + // Send MQTT queue check to the node we received the traceroute from + // It may be a viable path to send MQTT to, so stay informed of its MQTT queue state + Nodes.ESPEasy_now_MQTT_check_queue.push_back(mac); + } + } + } + } + } + } + } + return true; +} + +// ************************************************************* +// * NTP Query +// ************************************************************* + +void ESPEasy_now_handler_t::sendNTPquery() +{ + if (!_best_NTP_candidate->hasLowerWander()) { return; } + MAC_address mac; + + if (!_best_NTP_candidate->getMac(mac)) { return; } + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLog(LOG_LEVEL_INFO, concat(F(ESPEASY_NOW_NAME ": Send NTP query to: "), mac.toString())); + } + + _best_NTP_candidate->markSendTime(); + ESPEasy_Now_NTP_query query; + const size_t len = sizeof(ESPEasy_Now_NTP_query); + ESPEasy_now_splitter msg(ESPEasy_now_hdr::message_t::NTP_Query, len); + + if (len == msg.addBinaryData(reinterpret_cast(&query), len)) { + msg.send(mac.mac); + } +} + +void ESPEasy_now_handler_t::sendNTPbroadcast() +{ + ESPEasy_Now_NTP_query query; + + query.createBroadcastNTP(); + const size_t len = sizeof(ESPEasy_Now_NTP_query); + ESPEasy_now_splitter msg(ESPEasy_now_hdr::message_t::NTP_Query, len); + + if (len == msg.addBinaryData(reinterpret_cast(&query), len)) { + msg.send(query._mac); + } +} + +bool ESPEasy_now_handler_t::handle_NTPquery(const ESPEasy_now_merger& message, bool& mustKeep) +{ + mustKeep = false; + ESPEasy_Now_NTP_query query; + + // NTP query messages have a single binary blob, starting at 0 + size_t payload_pos = 0; + + message.getBinaryData(reinterpret_cast(&query), sizeof(ESPEasy_Now_NTP_query), payload_pos); + + if (query._timeSource == timeSource_t::No_time_source) { + // Received a query, must generate an answer for it. + + // first fetch the MAC address to reply to + if (!message.getMac(query._mac)) { return false; } + + // Fill the reply + query.createReply(message.getFirstPacketTimestamp()); + + const size_t len = sizeof(ESPEasy_Now_NTP_query); + ESPEasy_now_splitter msg(ESPEasy_now_hdr::message_t::NTP_Query, len); + + if (len == msg.addBinaryData(reinterpret_cast(&query), len)) { + msg.send(query._mac); + return true; + } + return false; + } + + // Received a reply on our own query + return _best_NTP_candidate->processReply(query, message.getFirstPacketTimestamp()); +} + +// ************************************************************* +// * MQTT controller forwarder +// ************************************************************* + +bool ESPEasy_now_handler_t::sendToMQTT( + controllerIndex_t controllerIndex, + const String & topic, + const String & payload, + const MessageRouteInfo_t *messageRouteInfo) +{ + if (!use_EspEasy_now) { return false; } + + const uint8_t distance = Nodes.getDistance(); + + if (((distance == 0) && MQTTclient_connected) || (distance == 255)) { + // No need to send via ESPEasy_Now. + // We're either connected ((distance == 0) && MQTTclient_connected) + // or have no neighbor that can forward the message (distance == 255) + return false; + } + + if (validControllerIndex(controllerIndex)) { + load_ControllerSettingsCache(controllerIndex); + } + + bool processed = false; + + if (_enableESPEasyNowFallback /*&& !WiFiConnected(10) */) { + // Must make sure we don't forward it to the unit we received the message from. + const uint8_t unit_nr = (messageRouteInfo == nullptr) ? 0 : messageRouteInfo->getLastUnitNotMatching(Settings.Unit); + const NodeStruct *preferred = Nodes.getPreferredNode_notMatching(unit_nr); + + if (preferred != nullptr /* && Nodes.getDistance() > preferred->distance */) { + if ((messageRouteInfo != nullptr) && (messageRouteInfo->countUnitInTrace(preferred->unit) > 2)) { + // Preferred node appears in trace quite often, reduce success rate of this node. + Nodes.updateSuccessRate(preferred->unit, false); + } + MAC_address mac = preferred->ESPEasy_Now_MAC(); + + switch (Nodes.getMQTTQueueState(preferred->unit)) { + case ESPEasy_Now_MQTT_QueueCheckState::Enum::Unset: + case ESPEasy_Now_MQTT_QueueCheckState::Enum::Full: + // We need this one now, so add as first + Nodes.ESPEasy_now_MQTT_check_queue.push_front(mac); + Nodes.updateMQTT_checkQueue(); + return false; + case ESPEasy_Now_MQTT_QueueCheckState::Enum::Empty: + break; + } + + + // each string has null termination + const size_t topic_length = topic.length() + 1; + const size_t payload_length = payload.length() + 1; + size_t routeInfo_length = 0; + + MessageRouteInfo_t::uint8_t_vector routeInfo; + + // Check if the intended recipient supports routeInfo + if ((messageRouteInfo != nullptr) && (preferred->build > 20113)) { + // FIXME TD-er: Disabled for now as forwarding messages does not work well right now + // routeInfo = messageRouteInfo->serialize(); + // routeInfo_length = routeInfo.size(); + } + + // Todo: Add cpluginID_t cpluginID; to the message + size_t len = topic_length + payload_length + routeInfo_length; + + ESPEasy_now_splitter msg(ESPEasy_now_hdr::message_t::MQTTControllerMessage, len); + + if (topic_length != msg.addString(topic)) { + return false; + } + + if (payload_length != msg.addString(payload)) { + return false; + } + + if ((routeInfo_length != 0) && (routeInfo_length != msg.addBinaryData(&(routeInfo[0]), routeInfo_length))) { + // return false; + } + + WifiEspNowSendStatus sendStatus = msg.send(mac, _ClientTimeout, preferred->channel); + + switch (sendStatus) { + case WifiEspNowSendStatus::OK: + { + processed = true; + break; + } + case WifiEspNowSendStatus::NONE: + case WifiEspNowSendStatus::FAIL: + { + Nodes.setMQTTQueueState(preferred->unit, ESPEasy_Now_MQTT_QueueCheckState::Enum::Unset); + Nodes.ESPEasy_now_MQTT_check_queue.push_back(mac); + ++_send_failed_count; + break; + } + default: break; + } + } + } + return processed; +} + +bool ESPEasy_now_handler_t::handle_MQTTControllerMessage(const ESPEasy_now_merger& message, bool& mustKeep) +{ + # if FEATURE_MQTT + + // FIXME TD-er: Quick hack to just echo all data to the first enabled MQTT controller + controllerIndex_t controllerIndex = get_ESPEasy_NOW_controller_index(); + + if (!validControllerIndex(controllerIndex)) { + controllerIndex = firstEnabledMQTT_ControllerIndex(); + } + + + if (validControllerIndex(controllerIndex)) { + load_ControllerSettingsCache(controllerIndex); + MAC_address mac; + + bool success = false; + + if (message.getMac(mac)) { + MessageRouteInfo_t MessageRouteInfo; + const NodeStruct *node = Nodes.getNodeByMac(mac); + + if (node != nullptr) { + if (message.getMessageCount(MessageRouteInfo.count)) { + MessageRouteInfo.unit = node->unit; + } + } + + taskIndex_t filterTaskCallback = INVALID_TASK_INDEX; + + if (customConfig) { + if (customConfig->filterMQTT_forward) { + String topic; + size_t pos = 0; + message.getString(topic, pos); + filterTaskCallback = customConfig->matchTopic(topic); + } + } + + success = MQTTpublish( + controllerIndex, + filterTaskCallback, + message, + MessageRouteInfo, + _mqtt_retainFlag, + validTaskIndex(filterTaskCallback)); + + if (!success) { + mustKeep = false; + return success; + } + + ESPEasy_Now_MQTT_queue_check_packet query; + const bool queue_full = MQTT_queueFull(controllerIndex); + query.setState(queue_full); + + if (loglevelActiveFor(LOG_LEVEL_INFO) && queue_full) { + addLog(LOG_LEVEL_INFO, F(ESPEASY_NOW_NAME ": After MQTT message received: Full")); + } + sendMQTTCheckControllerQueue(mac, 0, query.state); + } + + mustKeep = !success; + return success; + } + + # endif // if FEATURE_MQTT + mustKeep = false; + return false; +} + +// ************************************************************* +// * Check MQTT queue state of preferred node +// ************************************************************* +bool ESPEasy_now_handler_t::sendMQTTCheckControllerQueue(controllerIndex_t controllerIndex) +{ + if (!use_EspEasy_now) { return false; } + + const uint8_t distance = Nodes.getDistance(); + + if ((distance == 0) || (distance == 255)) { + // No need to send via ESPEasy_Now. + // We're either connected (distance == 0) + // or have no neighbor that can forward the message (distance == 255) + return false; + } + const NodeStruct *preferred = Nodes.getPreferredNode(); + + if ((preferred != nullptr) && (Nodes.getDistance() > preferred->distance)) { + return sendMQTTCheckControllerQueue(preferred->ESPEasy_Now_MAC(), preferred->channel); + } + return false; +} + +bool ESPEasy_now_handler_t::sendMQTTCheckControllerQueue(const MAC_address & mac, + int channel, + ESPEasy_Now_MQTT_QueueCheckState::Enum state) { + ESPEasy_Now_MQTT_queue_check_packet query; + + query.state = state; + const size_t len = sizeof(ESPEasy_Now_MQTT_queue_check_packet); + ESPEasy_now_splitter msg(ESPEasy_now_hdr::message_t::MQTTCheckControllerQueue, len); + + if (len != msg.addBinaryData(reinterpret_cast(&query), len)) { + return false; + } + size_t timeout = 10; + WifiEspNowSendStatus sendStatus = msg.send(mac, timeout, channel); + + switch (sendStatus) { + case WifiEspNowSendStatus::OK: + { + return true; + } + case WifiEspNowSendStatus::NONE: + case WifiEspNowSendStatus::FAIL: + { + break; + } + default: break; + } + return false; +} + +bool ESPEasy_now_handler_t::handle_MQTTCheckControllerQueue(const ESPEasy_now_merger& message, bool& mustKeep) +{ + mustKeep = false; + # if FEATURE_MQTT + + ESPEasy_Now_MQTT_queue_check_packet query; + size_t payload_pos = 0; + message.getBinaryData(reinterpret_cast(&query), sizeof(ESPEasy_Now_MQTT_queue_check_packet), payload_pos); + + controllerIndex_t controllerIndex = firstEnabledMQTT_ControllerIndex(); + + if (validControllerIndex(controllerIndex)) { + if (query.isSet()) { + // Got an answer from our query + MAC_address mac; + + if (message.getMac(mac)) { + Nodes.setMQTTQueueState(mac, query.state); + # ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG_MORE)) { + addLog(LOG_LEVEL_DEBUG_MORE, concat(F(ESPEASY_NOW_NAME ": Received Queue state: "), query.isFull() ? F("Full") : F("not Full"))); + } + # endif // ifndef BUILD_NO_DEBUG + } + return true; + } else { + MAC_address mac; + + if (message.getMac(mac)) { + // We have to give our own queue state and reply + query.setState(MQTT_queueFull(controllerIndex)); + + // addLog(LOG_LEVEL_INFO, F(ESPEASY_NOW_NAME ": reply to queue state query")); + const size_t len = sizeof(ESPEasy_Now_MQTT_queue_check_packet); + ESPEasy_now_splitter msg(ESPEasy_now_hdr::message_t::MQTTCheckControllerQueue, len); + + if (len == msg.addBinaryData(reinterpret_cast(&query), len)) { + msg.send(mac); + return true; + } + } + } + } + + # endif // if FEATURE_MQTT + return false; +} + +// ************************************************************* +// * Controller Message Duplicate Check +// ************************************************************* + +void ESPEasy_now_handler_t::sendSendData_DuplicateCheck(uint32_t key, + ESPEasy_Now_DuplicateCheck::message_t message_type, + const MAC_address & mac) +{ + ESPEasy_Now_DuplicateCheck check(key, message_type); + const size_t len = sizeof(ESPEasy_Now_DuplicateCheck); + ESPEasy_now_splitter msg(ESPEasy_now_hdr::message_t::SendData_DuplicateCheck, len); + + if (len != msg.addBinaryData(reinterpret_cast(&check), len)) { + return; + } + + switch (message_type) { + case ESPEasy_Now_DuplicateCheck::message_t::KeyToCheck: + msg.sendToBroadcast(); + break; + case ESPEasy_Now_DuplicateCheck::message_t::AlreadyProcessed: + msg.send(mac); + break; + } + +# ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + String log; + log = F("ESPEasy_now dup check: "); + + switch (message_type) { + case ESPEasy_Now_DuplicateCheck::message_t::KeyToCheck: + log += F("Broadcast check key "); + log += key; + break; + case ESPEasy_Now_DuplicateCheck::message_t::AlreadyProcessed: + log += F("Processed key "); + log += key; + log += ' '; + log += mac.toString(); + break; + } + addLog(LOG_LEVEL_DEBUG, log); + } +# endif // ifndef BUILD_NO_DEBUG +} + +bool ESPEasy_now_handler_t::handle_SendData_DuplicateCheck(const ESPEasy_now_merger& message, bool& mustKeep) +{ + mustKeep = false; + ESPEasy_Now_DuplicateCheck check; + size_t payload_pos = 0; + + message.getBinaryData(reinterpret_cast(&check), sizeof(ESPEasy_Now_DuplicateCheck), payload_pos); + + switch (check._type) { + case ESPEasy_Now_DuplicateCheck::message_t::KeyToCheck: + + // This is a query from another node. + // Check if it has already been processed by some node. + if (SendData_DuplicateChecker.historicKey(check._key)) { + // Must reply back to that node we already have seen it + MAC_address mac; + + if (message.getMac(mac)) { + sendSendData_DuplicateCheck(check._key, + ESPEasy_Now_DuplicateCheck::message_t::AlreadyProcessed, + mac); + } + } + return true; + + case ESPEasy_Now_DuplicateCheck::message_t::AlreadyProcessed: + + // This is a rejection indicating some other node already has the data processed + SendData_DuplicateChecker.remove(check._key); + + return true; + } + return false; +} + +void ESPEasy_now_handler_t::load_ControllerSettingsCache(controllerIndex_t controllerIndex) +{ + if (validControllerIndex(controllerIndex) && (controllerIndex != _controllerIndex)) + { + // Place the ControllerSettings in a scope to free the memory as soon as we got all relevant information. + MakeControllerSettings(ControllerSettings); + + if (AllocatedControllerSettings()) { + LoadControllerSettings(controllerIndex, *ControllerSettings); + _enableESPEasyNowFallback = ControllerSettings->enableESPEasyNowFallback(); + + if ((Settings.Protocol[controllerIndex] == 19) && customConfig) { + _enableESPEasyNowFallback = customConfig->forwardMQTT; + } + _ClientTimeout = ControllerSettings->ClientTimeout; + _mqtt_retainFlag = ControllerSettings->mqtt_retainFlag(); + _controllerIndex = controllerIndex; + } + } +} + +// ************************************************************* +// * ESPEasyNow p2p +// ************************************************************* +bool ESPEasy_now_handler_t::sendESPEasyNow_p2p(controllerIndex_t controllerIndex, const MAC_address& mac, const ESPEasy_Now_p2p_data& data) { + if (!use_EspEasy_now) { return false; } + + ESPEasy_now_splitter msg(ESPEasy_now_hdr::message_t::P2P_data, data.getTotalSize()); + + // Add the first part of the data object, without the data array. + if (data.dataOffset != msg.addBinaryData(reinterpret_cast(&data), data.dataOffset)) { + return false; + } + + // Fetch the data array information, will also update size. + size_t size = 0; + const uint8_t *data_ptr = data.getBinaryData(0, size); + + if (size != msg.addBinaryData(data_ptr, size)) { + return false; + } + + return msg.send(mac); +} + +bool ESPEasy_now_handler_t::handle_ESPEasyNow_p2p(const ESPEasy_now_merger& message, bool& mustKeep) { + mustKeep = false; + controllerIndex_t controller_index = findFirstEnabledControllerWithId(19); // CPLUGIN_ID_019 + + if (!validControllerIndex(controller_index)) { + addLog(LOG_LEVEL_ERROR, F("Controller C019 not enabled")); + return false; + } + + ESPEasy_Now_p2p_data data; + size_t payload_pos = 0; + size_t payload_size = message.getPayloadSize(); + size_t headerSize = data.dataOffset; + + if (headerSize > payload_size) { + // This can only happen when the receiving end has a larger ESPEasy_Now_p2p_data struct + headerSize = payload_size; + } + message.getBinaryData(reinterpret_cast(&data), headerSize, payload_pos); + + // dataOffset may have changed to match the offset used by the sender. + payload_pos = data.dataOffset; + + size_t binaryData_size = payload_size - headerSize; + uint8_t *binaryData_ptr = data.prepareBinaryData(binaryData_size); + + if (binaryData_ptr == nullptr) { + addLog(LOG_LEVEL_ERROR, F("handle_ESPEasyNow_p2p: Cannot allocate data")); + + // Cannot allocate memory to process message, so return true to make sure it gets deleted. + return true; + } + + size_t received_size = message.getBinaryData(binaryData_ptr, binaryData_size, payload_pos); + + if (received_size != binaryData_size) { + // Did not receive all data + String log = F("handle_ESPEasyNow_p2p: Did not receive all data "); + log += received_size; + log += '/'; + log += binaryData_size; + log += F(" dataSize: "); + log += data.dataSize; + log += F(" payload_pos: "); + log += data.dataOffset; + addLog(LOG_LEVEL_ERROR, log); + + // return false; + } + + // Call C019 controller with event containing this data object as a pointer. + EventStruct event; + + event.ControllerIndex = controller_index; + event.Par1 = sizeof(ESPEasy_Now_p2p_data); + event.Data = reinterpret_cast(&data); + CPluginCall(CPlugin::Function::CPLUGIN_PROTOCOL_RECV, &event); + + return true; +} + +#endif // ifdef USES_ESPEASY_NOW diff --git a/src/src/Helpers/ESPEasy_now_handler.h b/src/src/Helpers/ESPEasy_now_handler.h new file mode 100644 index 0000000000..fc4a6b416f --- /dev/null +++ b/src/src/Helpers/ESPEasy_now_handler.h @@ -0,0 +1,155 @@ +#ifndef HELPERS_ESPEASY_NOW_HANDLER_H +#define HELPERS_ESPEASY_NOW_HANDLER_H + +#include "../../ESPEasy_common.h" + +#ifdef USES_ESPEASY_NOW + +# include "../Controller_config/C019_config.h" + +# include "../DataStructs/ESPEasy_Now_DuplicateCheck.h" +# include "../DataStructs/ESPEasy_Now_NTP_query.h" +# include "../DataStructs/ESPEasy_Now_p2p_data.h" +# include "../DataStructs/ESPEasy_now_merger.h" +# include "../DataStructs/ESPEasy_now_traceroute.h" +# include "../DataStructs/MAC_address.h" +# include "../DataStructs/MessageRouteInfo.h" +# include "../DataStructs/WiFi_AP_Candidate.h" +# include "../DataTypes/ControllerIndex.h" +# include "../DataTypes/ESPEasy_Now_MQTT_queue_check_state.h" +# include "../Globals/ESPEasy_now_state.h" + +class ESPEasy_now_handler_t { +public: + + ESPEasy_now_handler_t(); + + ~ESPEasy_now_handler_t(); + + void setConfig(const C019_ConfigStruct& config); + + void loadConfig(struct EventStruct *event); + + bool begin(); + + bool do_begin(); + + void end(); + + bool loop(); + +private: + + static controllerIndex_t get_ESPEasy_NOW_controller_index(); + + void loop_check_ESPEasyNOW_run_state(); + + bool loop_process_ESPEasyNOW_in_queue(); + + void loop_process_ESPEasyNOW_send_queue(); + +public: + + bool active() const; + + MAC_address getActiveESPEasyNOW_MAC() const; + + void addPeerFromWiFiScan(); + void addPeerFromWiFiScan(const WiFi_AP_Candidate& peer); + +private: + + bool processMessage(const ESPEasy_now_merger& message, + bool & mustKeep); + +public: + + // Send out the discovery announcement via broadcast. + // This may be picked up by others + void sendDiscoveryAnnounce(int channel = 0); + void sendDiscoveryAnnounce(const MAC_address& mac, + int channel = 0); + + // Send trace route as 'connected' node + void sendTraceRoute(); + + void sendTraceRoute(const ESPEasy_now_traceroute_struct& traceRoute, + int channel = 0); + void sendTraceRoute(const MAC_address & mac, + const ESPEasy_now_traceroute_struct& traceRoute, + int channel = 0); + + void sendNTPquery(); + + void sendNTPbroadcast(); + + bool sendToMQTT(controllerIndex_t controllerIndex, + const String & topic, + const String & payload, + const MessageRouteInfo_t *messageRouteInfo); + + bool sendMQTTCheckControllerQueue(controllerIndex_t controllerIndex); + bool sendMQTTCheckControllerQueue(const MAC_address & mac, + int channel, + ESPEasy_Now_MQTT_QueueCheckState::Enum state = ESPEasy_Now_MQTT_QueueCheckState::Enum::Unset); + + void sendSendData_DuplicateCheck(uint32_t key, + ESPEasy_Now_DuplicateCheck::message_t message_type, + const MAC_address & mac); + + bool sendESPEasyNow_p2p(controllerIndex_t controllerIndex, + const MAC_address & mac, + const ESPEasy_Now_p2p_data& data); + +private: + + bool handle_DiscoveryAnnounce(const ESPEasy_now_merger& message, + bool & mustKeep); + + bool handle_TraceRoute(const ESPEasy_now_merger& message, + bool & mustKeep); + + bool handle_NTPquery(const ESPEasy_now_merger& message, + bool & mustKeep); + + bool handle_MQTTControllerMessage(const ESPEasy_now_merger& message, + bool & mustKeep); + + bool handle_MQTTCheckControllerQueue(const ESPEasy_now_merger& message, + bool & mustKeep); + + bool handle_SendData_DuplicateCheck(const ESPEasy_now_merger& message, + bool & mustKeep); + + bool handle_ESPEasyNow_p2p(const ESPEasy_now_merger& message, + bool & mustKeep); + + void load_ControllerSettingsCache(controllerIndex_t controllerIndex); + + ESPEasy_Now_NTP_query *_best_NTP_candidate = nullptr; + + unsigned long _last_used = 0; + unsigned long _last_started = 0; + + // Trace route is only sent by 'connected' nodes (distance = 0) + unsigned long _last_traceroute_sent = 0; + + // Trace route will be received only when at least one node in the connected mesh has distance = 0 + unsigned long _last_traceroute_received = 0; + + uint8_t _send_failed_count = 0; + + unsigned int _ClientTimeout = 0; + uint8_t _usedWiFiChannel = 0; + uint8_t _lastScannedChannel = 0; + controllerIndex_t _controllerIndex = INVALID_CONTROLLER_INDEX; + C019_ConfigStruct_ptr customConfig; + bool _scanChannelsMode = true; + bool _enableESPEasyNowFallback = false; + bool _mqtt_retainFlag = false; +}; + + +#endif // ifdef USES_ESPEASY_NOW + +#endif // HELPERS_ESPEASY_NOW_HANDLER_H diff --git a/src/src/Helpers/ESPEasy_now_peermanager.cpp b/src/src/Helpers/ESPEasy_now_peermanager.cpp new file mode 100644 index 0000000000..83b18379d9 --- /dev/null +++ b/src/src/Helpers/ESPEasy_now_peermanager.cpp @@ -0,0 +1,122 @@ +#include "../Helpers/ESPEasy_now_peermanager.h" + +#ifdef USES_ESPEASY_NOW + +# include "../DataStructs/NodeStruct.h" +# include "../ESPEasyCore/ESPEasy_Log.h" +# include "../ESPEasyCore/ESPEasyNetwork.h" +# include "../ESPEasyCore/ESPEasyWifi.h" +# include "../Globals/Nodes.h" +# include "../Globals/SecuritySettings.h" + +# include "../Helpers/StringConverter.h" + + +ESPEasy_now_peermanager_t::ESPEasy_now_peermanager_t() { + removeAllPeers(); +} + +MAC_address ESPEasy_now_peermanager_t::getBroadcastMAC() +{ + static MAC_address ESPEasy_now_broadcast_MAC; + + if (!isBroadcastMAC(ESPEasy_now_broadcast_MAC)) { + for (int i = 0; i < 6; ++i) { + ESPEasy_now_broadcast_MAC.mac[i] = 0xFF; + } + } + return ESPEasy_now_broadcast_MAC; +} + +bool ESPEasy_now_peermanager_t::isBroadcastMAC(const MAC_address& mac) +{ + return mac.all_one(); +} + +bool ESPEasy_now_peermanager_t::addPeer(const MAC_address& mac, int channel, const uint8_t key[WIFIESPNOW_KEYLEN]) +{ + if (!use_EspEasy_now || mac.all_zero()) { + return false; + } + { + // Don't add yourself as a peer + if (WifiSTAmacAddress() == mac || WifiSoftAPmacAddress() == mac) return false; + } + + bool res = true; + + if (!isBroadcastMAC(mac)) { + const NodeStruct *nodeInfo = Nodes.getNodeByMac(mac); + + if (nodeInfo != nullptr) { + if (channel == 0) { + channel = nodeInfo->channel; + } + } + + if (!WifiEspNow.hasPeer(mac.mac)) { + res = WifiEspNow.addPeer(mac.mac, channel); + + if (!res && (activePeers.size() != 0)) { + WifiEspNow.removePeer(activePeers.front().mac); + activePeers.pop_front(); + res = WifiEspNow.addPeer(mac.mac, channel, key); + } + + if (res) { + activePeers.push_back(mac); + } else { + if (loglevelActiveFor(LOG_LEVEL_ERROR)) { + addLog(LOG_LEVEL_ERROR, concat(F(ESPEASY_NOW_NAME ": Failed to add peer "), MAC_address(mac).toString())); + } + } + } else { + // Peer exists, but add it anyway to make sure the channel is updated. + WifiEspNow.addPeer(mac.mac, channel); + + // Move the MAC address to the back of the list as it is actively used + if (activePeers.back() != mac) { + auto it = activePeers.begin(); + bool found = false; + + while (it != activePeers.end() && !found) { + if (*it == mac) { + found = true; + activePeers.erase(it); + } + ++it; + } + activePeers.push_back(mac); + } + } + } + return res; +} + +void ESPEasy_now_peermanager_t::removeAllPeers() { + const int MAX_PEERS = 20; + WifiEspNowPeerInfo oldPeers[MAX_PEERS]; + int nOldPeers = std::min(WifiEspNow.listPeers(oldPeers, MAX_PEERS), MAX_PEERS); + + for (int i = 0; i < nOldPeers; ++i) { + WifiEspNow.removePeer(oldPeers[i].mac); + } + activePeers.clear(); +} + +void ESPEasy_now_peermanager_t::addKnownPeers() +{ + for (uint8_t peer = 0; peer < ESPEASY_NOW_PEER_MAX; ++peer) { + if (SecuritySettings.peerMacSet(peer)) { + addPeer(SecuritySettings.EspEasyNowPeerMAC[peer], 0); + } + } + + for (auto it = Nodes.begin(); it != Nodes.end(); ++it) { + if (it->second.ESPEasyNowPeer) { + addPeer(it->second.ESPEasy_Now_MAC(), it->second.channel); + } + } +} + +#endif // ifdef USES_ESPEASY_NOW diff --git a/src/src/Helpers/ESPEasy_now_peermanager.h b/src/src/Helpers/ESPEasy_now_peermanager.h new file mode 100644 index 0000000000..0bc148ad21 --- /dev/null +++ b/src/src/Helpers/ESPEasy_now_peermanager.h @@ -0,0 +1,40 @@ +#ifndef HELPERS_ESPEASY_NOW_PEERMANAGER_H +#define HELPERS_ESPEASY_NOW_PEERMANAGER_H + +#include "../../ESPEasy_common.h" +#ifdef USES_ESPEASY_NOW + +# include "../DataStructs/MAC_address.h" +# include "../Globals/ESPEasy_now_state.h" + +# include + +struct ESPEasy_now_peermanager_t { + ESPEasy_now_peermanager_t(); + + // Return FF:FF:FF:FF:FF:FF + static MAC_address getBroadcastMAC(); + + static bool isBroadcastMAC(const MAC_address& mac); + + // ESP-now does send broadcast packets to known peers (and those that still may receive it) + // This makes broadcast packets much more reliable when the intended recipient is a known peer + // However we only have a limited number of peer slots (depending on whether or not an encryption key is used), + // so we must keep track of 'popular' nodes to keep those active as peer + bool addPeer(const MAC_address& mac, + int channel, + const uint8_t key[WIFIESPNOW_KEYLEN] = nullptr); + + void removeAllPeers(); + + // Add preferred peers from the settings and known nodes. + // Nodes will be added as last, as those are likely to be more up-to-date. + void addKnownPeers(); + +private: + + // Keep track of active peers so we can remove the oldest when the max. nr. of peers is reached. + std::list activePeers; +}; +#endif // ifdef USES_ESPEASY_NOW +#endif // ifndef HELPERS_ESPEASY_NOW_PEERMANAGER_H diff --git a/src/src/Helpers/ESPEasy_time.h b/src/src/Helpers/ESPEasy_time.h index d10f219ec7..48bd916723 100644 --- a/src/src/Helpers/ESPEasy_time.h +++ b/src/src/Helpers/ESPEasy_time.h @@ -91,6 +91,8 @@ class ESPEasy_time { void applyTimeZone(); + timeSource_t getTimeSource() const { return _timeSource; } + /********************************************************************************************\ Date/Time string formatters \*********************************************************************************************/ @@ -226,9 +228,6 @@ class ESPEasy_time { public: - timeSource_t getTimeSource() const { return _timeSource; } - - struct tm local_tm; // local time uint32_t syncInterval = 3600; // time sync will be attempted after this many seconds diff --git a/src/src/Helpers/Hardware.cpp b/src/src/Helpers/Hardware.cpp index b8f41a32ed..d46f4110b4 100644 --- a/src/src/Helpers/Hardware.cpp +++ b/src/src/Helpers/Hardware.cpp @@ -1,1043 +1,1044 @@ -#include "../Helpers/Hardware.h" - -#include "../Commands/GPIO.h" -#include "../CustomBuild/ESPEasyLimits.h" -#include "../DataTypes/SPI_options.h" -#include "../ESPEasyCore/ESPEasyGPIO.h" -#include "../ESPEasyCore/ESPEasy_Log.h" - -#include "../Globals/Device.h" -#include "../Globals/ESPEasyWiFiEvent.h" -#include "../Globals/ExtraTaskSettings.h" -#include "../Globals/Settings.h" -#include "../Globals/Statistics.h" -#include "../Globals/GlobalMapPortStatus.h" - -#include "../Helpers/ESPEasy_FactoryDefault.h" -#include "../Helpers/ESPEasy_Storage.h" -#include "../Helpers/FS_Helper.h" -#include "../Helpers/Hardware_device_info.h" -#include "../Helpers/Hardware_GPIO.h" -#include "../Helpers/Hardware_I2C.h" -#include "../Helpers/I2C_access.h" -#include "../Helpers/Misc.h" -#include "../Helpers/PortStatus.h" -#include "../Helpers/StringConverter.h" - -#include "../_Plugin_Helper.h" - - -#if defined(ESP8266) - # include -#endif // if defined(ESP8266) -#if defined(ESP32) - # include -#endif // if defined(ESP32) - -// #include "../../ESPEasy-Globals.h" - -#ifdef ESP32 - # include - # include - # include - # include - # include - - # if ESP_IDF_VERSION_MAJOR == 4 - # if CONFIG_IDF_TARGET_ESP32S3 // ESP32-S3 - # include - # include - # include - # elif CONFIG_IDF_TARGET_ESP32S2 // ESP32-S2 - # include - # include - # include - # elif CONFIG_IDF_TARGET_ESP32C3 // ESP32-C3 - # include - # include - # elif CONFIG_IDF_TARGET_ESP32 // ESP32/PICO-D4 - # include - # include - # include - # else // if CONFIG_IDF_TARGET_ESP32S3 - # error Target CONFIG_IDF_TARGET is not supported - # endif // if CONFIG_IDF_TARGET_ESP32S3 - # else // ESP32 IDF 5.x and later - # include - # include - # include - # endif // if ESP_IDF_VERSION_MAJOR == 4 - -#if CONFIG_IDF_TARGET_ESP32 // ESP32/PICO-D4 - # if ESP_IDF_VERSION_MAJOR < 5 - # define HAS_HALL_EFFECT_SENSOR 1 - # else // if ESP_IDF_VERSION_MAJOR < 5 - -// Support for Hall Effect sensor was removed in ESP_IDF 5.x - # define HAS_HALL_EFFECT_SENSOR 0 - # endif // if ESP_IDF_VERSION_MAJOR < 5 -# else - # define HAS_HALL_EFFECT_SENSOR 0 -# endif - - -# if ESP_IDF_VERSION_MAJOR >= 5 - -# include -# include -# include -# include - -// #include - -# endif // if ESP_IDF_VERSION_MAJOR >= 5 - -# include "../Helpers/Hardware_ADC_cali.h" - -#if FEATURE_ETHERNET -#include -#endif - -#endif // ifdef ESP32 - - -#if FEATURE_SD -# include -#endif // if FEATURE_SD - - -#include - - -# define GPIO_PLUGIN_ID 1 - -/********************************************************************************************\ - * Initialize specific hardware settings (only global ones, others are set through devices) - \*********************************************************************************************/ -void hardwareInit() -{ - // set GPIO pins state if not set to default - bool hasPullUp, hasPullDown; - - for (int gpio = 0; gpio <= MAX_GPIO; ++gpio) { - const bool serialPinConflict = isSerialConsolePin(gpio); - - if (!serialPinConflict) { - const uint32_t key = createKey(PLUGIN_GPIO, gpio); - #ifdef ESP32 - checkAndClearPWM(key); - #endif // ifdef ESP32 - - if (getGpioPullResistor(gpio, hasPullUp, hasPullDown)) { - PinBootState bootState = Settings.getPinBootState(gpio); - #if FEATURE_ETHERNET -/* - if (Settings.ETH_Pin_power_rst == gpio) - { - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("ETH : Reset ETH module on pin "); - log += Settings.ETH_Pin_power_rst; - addLog(LOG_LEVEL_INFO, log); - } - bootState = PinBootState::Output_low; - } - */ - #endif // if FEATURE_ETHERNET - - #ifdef ESP32 - if (bootState != PinBootState::Default_state) { - gpio_reset_pin(static_cast(gpio)); - } - #endif - - switch (bootState) - { - case PinBootState::Default_state: - // At startup, pins are configured as INPUT - break; - case PinBootState::Output_low: - createAndSetPortStatus_Mode_State(key, PIN_MODE_OUTPUT, 0); - GPIO_Write(PLUGIN_GPIO, gpio, LOW, PIN_MODE_OUTPUT); - - // setPinState(1, gpio, PIN_MODE_OUTPUT, LOW); - break; - case PinBootState::Output_high: - createAndSetPortStatus_Mode_State(key, PIN_MODE_OUTPUT, 0); - GPIO_Write(PLUGIN_GPIO, gpio, HIGH, PIN_MODE_OUTPUT); - - // setPinState(1, gpio, PIN_MODE_OUTPUT, HIGH); - break; - case PinBootState::Input_pullup: - - if (hasPullUp) { - createAndSetPortStatus_Mode_State(key, PIN_MODE_INPUT_PULLUP, 0); - pinMode(gpio, INPUT_PULLUP); - } - break; - case PinBootState::Input_pulldown: - - if (hasPullDown) { - createAndSetPortStatus_Mode_State(key, PIN_MODE_INPUT_PULLDOWN, 0); - - #ifdef ESP8266 - - if (gpio == 16) { - pinMode(gpio, INPUT_PULLDOWN_16); - } - #endif // ifdef ESP8266 - #ifdef ESP32 - pinMode(gpio, INPUT_PULLDOWN); - #endif // ifdef ESP32 - } - break; - case PinBootState::Input: - createAndSetPortStatus_Mode_State(key, PIN_MODE_INPUT, 0); - pinMode(gpio, INPUT); - break; - } - } - } - } - - if (getGpioPullResistor(Settings.Pin_Reset, hasPullUp, hasPullDown)) { - if (hasPullUp) { - pinMode(Settings.Pin_Reset, INPUT_PULLUP); - } - } - - initI2C(); - - #if FEATURE_PLUGIN_PRIORITY - String dummy; - PluginCall(PLUGIN_PRIORITY_INIT_ALL, nullptr, dummy); - #endif // if FEATURE_PLUGIN_PRIORITY - - bool tryInitSPI = true; -#if FEATURE_ETHERNET - if ((Settings.NetworkMedium == NetworkMedium_t::Ethernet) && - isValid(Settings.ETH_Phy_Type) && - isSPI_EthernetType(Settings.ETH_Phy_Type)) - { -#if !ETH_SPI_SUPPORTS_CUSTOM - tryInitSPI = false; -#endif - } -#endif - - - // SPI Init - bool SPI_initialized = false; - if (tryInitSPI && Settings.isSPI_valid()) - { - SPI.setHwCs(false); - - // MFD: for ESP32 enable the SPI on HSPI as the default is VSPI - #ifdef ESP32 - - const SPI_Options_e SPI_selection = static_cast(Settings.InitSPI); - int8_t spi_gpios[3] = {}; - - if (Settings.getSPI_pins(spi_gpios)) { - if (SPI_selection == SPI_Options_e::Vspi_Fspi) { - SPI.begin(); // Default SPI bus - } else { - SPI.begin(spi_gpios[0], spi_gpios[1], spi_gpios[2]); - } - SPI_initialized = true; - } - #else // ifdef ESP32 - SPI.begin(); - SPI_initialized = true; - #endif // ifdef ESP32 - } - - if (SPI_initialized) - { - addLog(LOG_LEVEL_INFO, F("INIT : SPI Init (without CS)")); - #if FEATURE_SD - - if (Settings.Pin_sd_cs >= 0) - { - if (SD.begin(Settings.Pin_sd_cs)) - { - addLog(LOG_LEVEL_INFO, F("SD : Init OK")); - } - else - { - SD.end(); - addLog(LOG_LEVEL_ERROR, F("SD : Init failed")); - } - } -#endif // if FEATURE_SD - } else { - addLog(LOG_LEVEL_INFO, F("INIT : SPI not enabled")); - } -} - - -void checkResetFactoryPin() { - static uint8_t factoryResetCounter = 0; - - if (Settings.Pin_Reset == -1) { - return; - } - - if (digitalRead(Settings.Pin_Reset) == 0) { // active low reset pin - factoryResetCounter++; // just count every second - } - else - { // reset pin released - if (factoryResetCounter > 9) { - // factory reset and reboot - ResetFactory(); - } - - if (factoryResetCounter > 3) { - // normal reboot - reboot(IntendedRebootReason_e::ResetFactoryPinActive); - } - factoryResetCounter = 0; // count was < 3, reset counter - } -} - -#ifdef ESP8266 -int lastADCvalue = 0; - -int espeasy_analogRead(int pin) { - if (!WiFiEventData.wifiConnectInProgress) { - #if FEATURE_ADC_VCC - lastADCvalue = ESP.getVcc(); - #else - lastADCvalue = analogRead(A0); - #endif // if FEATURE_ADC_VCC - } - return lastADCvalue; -} - -#endif // ifdef ESP8266 - -#ifdef ESP32 - -// ESP32 ADC calibration datatypes. - - -// FIXME TD-er: For now keep a local array of the adc calibration -#if ESP_IDF_VERSION_MAJOR < 5 -Hardware_ADC_cali_t ESP32_ADC_cali[ADC_ATTEN_MAX]{}; -#else -Hardware_ADC_cali_t ESP32_ADC_cali[ADC_ATTENDB_MAX]{}; -#endif - - -void initADC() { - for (size_t atten = 0; atten < NR_ELEMENTS(ESP32_ADC_cali); ++atten) { - if (!ESP32_ADC_cali[atten].initialized()) { - // FIXME TD-er: For now fake some pin which is connected to ADC1 - #ifdef ESP32_CLASSIC - const int pin = 36; - #else - const int pin = 1; - #endif - ESP32_ADC_cali[atten].init(pin, static_cast(atten)); - } - } -} - -float applyADCFactoryCalibration(float raw_value, adc_atten_t attenuation) -{ - if (attenuation < NR_ELEMENTS(ESP32_ADC_cali)) { - return ESP32_ADC_cali[attenuation].applyFactoryCalibration(raw_value); - } - return raw_value; -} - -bool hasADC_factory_calibration() { - return ESP32_ADC_cali[0].useFactoryCalibration(); -} - -const __FlashStringHelper* getADC_factory_calibration_type() -{ - return ESP32_ADC_cali[0].getADC_factory_calibration_type(); -} - -float getADC_factory_calibrated_min(adc_atten_t attenuation) -{ - if (attenuation < NR_ELEMENTS(ESP32_ADC_cali)) { - return ESP32_ADC_cali[attenuation].getMinOut(); - } - return 0.0f; -} - -float getADC_factory_calibrated_max(adc_atten_t attenuation) -{ - if (attenuation < NR_ELEMENTS(ESP32_ADC_cali)) { - return ESP32_ADC_cali[attenuation].getMaxOut(); - } - return MAX_ADC_VALUE; -} - -int getADC_num_for_gpio(int pin) { - int ch; - - return getADC_num_for_gpio(pin, ch); -} - -int getADC_num_for_gpio(int pin, int& channel) -{ - int adc, t; - - if (getADC_gpio_info(pin, adc, channel, t)) { - return adc; - } - return -1; -} - -int espeasy_analogRead(int pin, bool readAsTouch) { - int value = 0; - int adc, ch, t; - - if (getADC_gpio_info(pin, adc, ch, t)) { - bool canread = false; - - switch (adc) { - case 0: - # if HAS_HALL_EFFECT_SENSOR - value = hallRead(); - # endif // if HAS_HALL_EFFECT_SENSOR - break; - case 1: - canread = true; - break; - case 2: -#if ESP_IDF_VERSION_MAJOR < 5 - if (WiFi.getMode() == WIFI_OFF) { - // See: - // https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/adc.html#configuration-and-reading-adc - // ADC2 is shared with WiFi, so don't read ADC2 when WiFi is on. - canread = true; - } -#else - canread = true; -#endif - break; - } - - if (canread) { - if (readAsTouch && (t >= 0)) { - #if defined(SOC_TOUCH_SENSOR_SUPPORTED) && SOC_TOUCH_SENSOR_SUPPORTED - value = touchRead(pin); - # endif - } else { - value = analogRead(pin); - } - } - } - return value; -} - - -int getCPU_MaxFreqMHz() -{ -#if CONFIG_IDF_TARGET_ESP32 - return static_cast(efuse_hal_get_rated_freq_mhz()); -#elif CONFIG_IDF_TARGET_ESP32C2 - return 120; -#elif CONFIG_IDF_TARGET_ESP32C3 - return 160; -#elif CONFIG_IDF_TARGET_ESP32C6 - return 160; -#elif CONFIG_IDF_TARGET_ESP32H2 - //IDF-6570 - return 96; -#elif CONFIG_IDF_TARGET_ESP32P4 - return 400; -#elif CONFIG_IDF_TARGET_ESP32S2 - return 240; -#elif CONFIG_IDF_TARGET_ESP32S3 - return 240; - -# else - # error Target CONFIG_IDF_TARGET is not supported - return 160; -# endif -} - -int getCPU_MinFreqMHz() -{ - // TODO TD-er: May differ on some ESPs and also some allow less but only without WiFi - return 80; -} - - -#endif // ifdef ESP32 - - - -/*********************************************************************************************\ -* High entropy hardware random generator -* Thanks to DigitalAlchemist -\*********************************************************************************************/ - -#if ESP_IDF_VERSION_MAJOR >= 5 -#include -#endif - -uint32_t HwRandom() { -#if ESP_IDF_VERSION_MAJOR >= 5 - // See for more info on the HW RNG: - // https://docs.espressif.com/projects/esp-idf/en/latest/esp32s2/api-reference/system/random.html - return esp_random(); -#else - -// Based on code from https://raw.githubusercontent.com/espressif/esp-idf/master/components/esp32/hw_random.c -// https://github.com/arendst/Tasmota/blob/1e6b78a957be538cf494f0e2dc49060d1cb0fe8b/tasmota/support_esp.ino#L805 -#if ESP8266 - - // https://web.archive.org/web/20160922031242/http://esp8266-re.foogod.com/wiki/Random_Number_Generator - # define _RAND_ADDR 0x3FF20E44UL -#endif // ESP8266 -#ifdef ESP32 - # define _RAND_ADDR 0x3FF75144UL -#endif // ESP32 - static uint32_t last_ccount = 0; - uint32_t ccount; - uint32_t result = 0; - - do { - ccount = ESP.getCycleCount(); - result ^= *(volatile uint32_t *)_RAND_ADDR; // -V566 - } while (ccount - last_ccount < 64); - last_ccount = ccount; - return result ^ *(volatile uint32_t *)_RAND_ADDR; // -V566 -#undef _RAND_ADDR -#endif -} - -long HwRandom(long howbig) { - if(howbig == 0) { - return 0; - } - return HwRandom() % howbig; -} - -long HwRandom(long howsmall, long howbig) { - if(howsmall >= howbig) { - return howsmall; - } - long diff = howbig - howsmall; - return HwRandom(diff) + howsmall; -} - -ESPEASY_RULES_FLOAT_TYPE HwRandom_f( - ESPEASY_RULES_FLOAT_TYPE howsmall, - ESPEASY_RULES_FLOAT_TYPE howbig) -{ - if (approximatelyEqual(howsmall, howbig)) { - return howsmall; - } - return mapADCtoFloat( - HwRandom(), - 0, std::numeric_limits::max(), - howsmall, howbig); -} - - -#ifdef ESP8266 -void readBootCause() { - lastBootCause = BOOT_CAUSE_MANUAL_REBOOT; - const rst_info *resetInfo = ESP.getResetInfoPtr(); - - if (resetInfo != nullptr) { - switch (resetInfo->reason) { - // normal startup by power on - case REASON_DEFAULT_RST: lastBootCause = BOOT_CAUSE_COLD_BOOT; break; - - // hardware watch dog reset - case REASON_WDT_RST: lastBootCause = BOOT_CAUSE_EXT_WD; break; - - // exception reset, GPIO status won’t change - case REASON_EXCEPTION_RST: lastBootCause = BOOT_CAUSE_EXCEPTION; break; - - // software watch dog reset, GPIO status won’t change - case REASON_SOFT_WDT_RST: lastBootCause = BOOT_CAUSE_SW_WATCHDOG; break; - - // software restart ,system_restart , GPIO status won’t change - case REASON_SOFT_RESTART: lastBootCause = BOOT_CAUSE_SOFT_RESTART; break; - - // wake up from deep-sleep - case REASON_DEEP_SLEEP_AWAKE: lastBootCause = BOOT_CAUSE_DEEP_SLEEP; break; - - // external system reset - case REASON_EXT_SYS_RST: lastBootCause = BOOT_CAUSE_MANUAL_REBOOT; break; - default: - break; - } - } -} - -#endif // ifdef ESP8266 - -#ifdef ESP32 -void readBootCause() { - lastBootCause = BOOT_CAUSE_MANUAL_REBOOT; - - #ifdef ESP32S2 - - switch (rtc_get_reset_reason(0)) { - case NO_MEAN : break; - case POWERON_RESET : lastBootCause = BOOT_CAUSE_MANUAL_REBOOT; break; /**<1, Vbat power on reset*/ - case RTC_SW_SYS_RESET : lastBootCause = BOOT_CAUSE_SOFT_RESTART; break; /**<3, Software reset digital core*/ - case DEEPSLEEP_RESET : lastBootCause = BOOT_CAUSE_DEEP_SLEEP; break; /**<5, Deep Sleep reset digital core*/ - case TG0WDT_SYS_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<7, Timer Group0 Watch dog reset digital core*/ - case TG1WDT_SYS_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<8, Timer Group1 Watch dog reset digital core*/ - case RTCWDT_SYS_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<9, RTC Watch dog Reset digital core*/ - case INTRUSION_RESET : lastBootCause = BOOT_CAUSE_SOFT_RESTART; break; /**<10, Instrusion tested to reset CPU*/ - case TG0WDT_CPU_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<11, Time Group0 reset CPU*/ - case RTC_SW_CPU_RESET : lastBootCause = BOOT_CAUSE_SOFT_RESTART; break; /**<12, Software reset CPU*/ - case RTCWDT_CPU_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<13, RTC Watch dog Reset CPU*/ - case RTCWDT_BROWN_OUT_RESET : lastBootCause = BOOT_CAUSE_POWER_UNSTABLE; break; /**<15, Reset when the vdd voltage is not stable*/ - case RTCWDT_RTC_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<16, RTC Watch dog reset digital core and rtc module*/ - case TG1WDT_CPU_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<17, Time Group1 reset CPU*/ - case SUPER_WDT_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<18, super watchdog reset digital core and rtc module*/ - case GLITCH_RTC_RESET : lastBootCause = BOOT_CAUSE_POWER_UNSTABLE; break; /**<19, glitch reset digital core and rtc module*/ - case EFUSE_RESET : lastBootCause = BOOT_CAUSE_MANUAL_REBOOT; break; /**<20, efuse reset digital core*/ - } - - - -#elif defined(ESP32S3) - switch (rtc_get_reset_reason(0)) { - case NO_MEAN : break; - case POWERON_RESET : lastBootCause = BOOT_CAUSE_MANUAL_REBOOT; break; /**<1, Vbat power on reset*/ - case RTC_SW_SYS_RESET : lastBootCause = BOOT_CAUSE_SOFT_RESTART; break; /**<3, Software reset digital core*/ - case DEEPSLEEP_RESET : lastBootCause = BOOT_CAUSE_DEEP_SLEEP; break; /**<5, Deep Sleep reset digital core*/ - case TG0WDT_SYS_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<7, Timer Group0 Watch dog reset digital core*/ - case TG1WDT_SYS_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<8, Timer Group1 Watch dog reset digital core*/ - case RTCWDT_SYS_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<9, RTC Watch dog Reset digital core*/ - case INTRUSION_RESET : lastBootCause = BOOT_CAUSE_SOFT_RESTART; break; /**<10, Instrusion tested to reset CPU*/ - case TG0WDT_CPU_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<11, Time Group0 reset CPU*/ - case RTC_SW_CPU_RESET : lastBootCause = BOOT_CAUSE_SOFT_RESTART; break; /**<12, Software reset CPU*/ - case RTCWDT_CPU_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<13, RTC Watch dog Reset CPU*/ - case RTCWDT_BROWN_OUT_RESET : lastBootCause = BOOT_CAUSE_POWER_UNSTABLE; break; /**<15, Reset when the vdd voltage is not stable*/ - case RTCWDT_RTC_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<16, RTC Watch dog reset digital core and rtc module*/ - case TG1WDT_CPU_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<17, Time Group1 reset CPU*/ - case SUPER_WDT_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<18, super watchdog reset digital core and rtc module*/ - case GLITCH_RTC_RESET : lastBootCause = BOOT_CAUSE_POWER_UNSTABLE; break; /**<19, glitch reset digital core and rtc module*/ - case EFUSE_RESET : lastBootCause = BOOT_CAUSE_MANUAL_REBOOT; break; /**<20, efuse reset digital core*/ - case USB_UART_CHIP_RESET : lastBootCause = BOOT_CAUSE_MANUAL_REBOOT; break; /**<21, usb uart reset digital core */ - case USB_JTAG_CHIP_RESET : lastBootCause = BOOT_CAUSE_MANUAL_REBOOT; break; /**<22, usb jtag reset digital core */ - case POWER_GLITCH_RESET : lastBootCause = BOOT_CAUSE_POWER_UNSTABLE; break; /**<23, power glitch reset digital core and rtc module*/ - } - - -#elif defined(ESP32C2) - switch (rtc_get_reset_reason(0)) { - case NO_MEAN : break; - case POWERON_RESET : lastBootCause = BOOT_CAUSE_MANUAL_REBOOT; break; /**<1, Vbat power on reset*/ - case RTC_SW_SYS_RESET : lastBootCause = BOOT_CAUSE_SOFT_RESTART; break; /**<3, Software reset digital core*/ - case DEEPSLEEP_RESET : lastBootCause = BOOT_CAUSE_DEEP_SLEEP; break; /**<3, Deep Sleep reset digital core*/ - case TG0WDT_SYS_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<7, Timer Group0 Watch dog reset digital core*/ - case RTCWDT_SYS_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<9, RTC Watch dog Reset digital core*/ - case INTRUSION_RESET : lastBootCause = BOOT_CAUSE_SOFT_RESTART; break; /**<10, Instrusion tested to reset CPU*/ - case TG0WDT_CPU_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<11, Time Group0 reset CPU*/ - case RTC_SW_CPU_RESET : lastBootCause = BOOT_CAUSE_SOFT_RESTART; break; /**<12, Software reset CPU*/ - case RTCWDT_CPU_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<13, RTC Watch dog Reset CPU*/ - case RTCWDT_BROWN_OUT_RESET : lastBootCause = BOOT_CAUSE_POWER_UNSTABLE; break; /**<15, Reset when the vdd voltage is not stable*/ - case RTCWDT_RTC_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<16, RTC Watch dog reset digital core and rtc module*/ - case SUPER_WDT_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<11, super watchdog reset digital core and rtc module*/ - case GLITCH_RTC_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<19, glitch reset digital core and rtc module*/ - case EFUSE_RESET : lastBootCause = BOOT_CAUSE_POWER_UNSTABLE; break; /**<20, efuse reset digital core*/ - case JTAG_RESET : lastBootCause = BOOT_CAUSE_MANUAL_REBOOT; break; /**<24, jtag reset CPU*/ - } - - -#elif defined(ESP32C3) - switch (rtc_get_reset_reason(0)) { - case NO_MEAN : break; - case POWERON_RESET : lastBootCause = BOOT_CAUSE_MANUAL_REBOOT; break; /**<1, Vbat power on reset*/ - case RTC_SW_SYS_RESET : lastBootCause = BOOT_CAUSE_SOFT_RESTART; break; /**<3, Software reset digital core*/ - case DEEPSLEEP_RESET : lastBootCause = BOOT_CAUSE_DEEP_SLEEP; break; /**<5, Deep Sleep reset digital core*/ - case TG0WDT_SYS_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<7, Timer Group0 Watch dog reset digital core*/ - case TG1WDT_SYS_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<8, Timer Group1 Watch dog reset digital core*/ - case RTCWDT_SYS_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<9, RTC Watch dog Reset digital core*/ - case INTRUSION_RESET : lastBootCause = BOOT_CAUSE_SOFT_RESTART; break; /**<10, Instrusion tested to reset CPU*/ - case TG0WDT_CPU_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<11, Time Group0 reset CPU*/ - case RTC_SW_CPU_RESET : lastBootCause = BOOT_CAUSE_SOFT_RESTART; break; /**<12, Software reset CPU*/ - case RTCWDT_CPU_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<13, RTC Watch dog Reset CPU*/ - case RTCWDT_BROWN_OUT_RESET : lastBootCause = BOOT_CAUSE_POWER_UNSTABLE; break; /**<15, Reset when the vdd voltage is not stable*/ - case RTCWDT_RTC_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<16, RTC Watch dog reset digital core and rtc module*/ - case TG1WDT_CPU_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<17, Time Group1 reset CPU*/ - case SUPER_WDT_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<18, super watchdog reset digital core and rtc module*/ - case GLITCH_RTC_RESET : lastBootCause = BOOT_CAUSE_POWER_UNSTABLE; break; /**<19, glitch reset digital core and rtc module*/ - case EFUSE_RESET : lastBootCause = BOOT_CAUSE_MANUAL_REBOOT; break; /**<20, efuse reset digital core*/ - case USB_UART_CHIP_RESET : lastBootCause = BOOT_CAUSE_MANUAL_REBOOT; break; /**<21, usb uart reset digital core */ - case USB_JTAG_CHIP_RESET : lastBootCause = BOOT_CAUSE_MANUAL_REBOOT; break; /**<22, usb jtag reset digital core */ - case POWER_GLITCH_RESET : lastBootCause = BOOT_CAUSE_POWER_UNSTABLE; break; /**<23, power glitch reset digital core and rtc module*/ - } - -#elif defined(ESP32C6) - switch (rtc_get_reset_reason(0)) { - case NO_MEAN : break; - case POWERON_RESET : lastBootCause = BOOT_CAUSE_MANUAL_REBOOT; break; /**<1, Vbat power on reset*/ - case RTC_SW_SYS_RESET : lastBootCause = BOOT_CAUSE_SOFT_RESTART; break; /**<3, Software reset digital core*/ - case DEEPSLEEP_RESET : lastBootCause = BOOT_CAUSE_DEEP_SLEEP; break; /**<5, Deep Sleep reset digital core*/ - case SDIO_RESET : lastBootCause = BOOT_CAUSE_MANUAL_REBOOT; break; /**<6, Reset by SLC module, reset digital core (hp system)*/ - case TG0WDT_SYS_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<7, Timer Group0 Watch dog reset digital core*/ - case TG1WDT_SYS_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<8, Timer Group1 Watch dog reset digital core*/ - case RTCWDT_SYS_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<9, RTC Watch dog Reset digital core*/ - case TG0WDT_CPU_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<11, Time Group0 reset CPU*/ - case RTC_SW_CPU_RESET : lastBootCause = BOOT_CAUSE_SOFT_RESTART; break; /**<12, Software reset CPU*/ - case RTCWDT_CPU_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<13, RTC Watch dog Reset CPU*/ - case RTCWDT_BROWN_OUT_RESET : lastBootCause = BOOT_CAUSE_POWER_UNSTABLE; break; /**<15, Reset when the vdd voltage is not stable*/ - case RTCWDT_RTC_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<16, RTC Watch dog reset digital core and rtc module*/ - case TG1WDT_CPU_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<17, Time Group1 reset CPU*/ - case SUPER_WDT_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<18, super watchdog reset digital core and rtc module*/ - case EFUSE_RESET : lastBootCause = BOOT_CAUSE_MANUAL_REBOOT; break; /**<20, efuse reset digital core*/ - case USB_UART_CHIP_RESET : lastBootCause = BOOT_CAUSE_MANUAL_REBOOT; break; /**<21, usb uart reset digital core */ - case USB_JTAG_CHIP_RESET : lastBootCause = BOOT_CAUSE_MANUAL_REBOOT; break; /**<22, usb jtag reset digital core */ - case JTAG_RESET : lastBootCause = BOOT_CAUSE_MANUAL_REBOOT; break; /**<24, jtag reset CPU*/ - } - -# elif defined(ESP32_CLASSIC) - switch (rtc_get_reset_reason(0)) { - case NO_MEAN : break; - case POWERON_RESET : lastBootCause = BOOT_CAUSE_MANUAL_REBOOT; break; /**<1, Vbat power on reset*/ - case SW_RESET : lastBootCause = BOOT_CAUSE_SOFT_RESTART; break; /**<3, Software reset digital core*/ - case OWDT_RESET : lastBootCause = BOOT_CAUSE_SW_WATCHDOG; break; /**<4, Legacy watch dog reset digital core*/ - case DEEPSLEEP_RESET : lastBootCause = BOOT_CAUSE_DEEP_SLEEP; break; /**<3, Deep Sleep reset digital core*/ - case SDIO_RESET : lastBootCause = BOOT_CAUSE_MANUAL_REBOOT; break; /**<6, Reset by SLC module, reset digital core*/ - case TG0WDT_SYS_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<7, Timer Group0 Watch dog reset digital core*/ - case TG1WDT_SYS_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<8, Timer Group1 Watch dog reset digital core*/ - case RTCWDT_SYS_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<9, RTC Watch dog Reset digital core*/ - case INTRUSION_RESET : lastBootCause = BOOT_CAUSE_SOFT_RESTART; break; /**<10, Instrusion tested to reset CPU*/ - case TGWDT_CPU_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<11, Time Group reset CPU*/ - case SW_CPU_RESET : lastBootCause = BOOT_CAUSE_SOFT_RESTART; break; /**<12, Software reset CPU*/ - case RTCWDT_CPU_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<13, RTC Watch dog Reset CPU*/ - case EXT_CPU_RESET : lastBootCause = BOOT_CAUSE_MANUAL_REBOOT; break; /**<14, for APP CPU, reseted by PRO CPU*/ - case RTCWDT_BROWN_OUT_RESET : lastBootCause = BOOT_CAUSE_POWER_UNSTABLE; break; /**<15, Reset when the vdd voltage is not stable*/ - case RTCWDT_RTC_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<16, RTC Watch dog reset digital core and rtc module*/ - } - - # else - - static_assert(false, "Implement processor architecture"); - - #endif -} - -#endif // ifdef ESP32 - - -/********************************************************************************************\ - Hardware specific configurations - \*********************************************************************************************/ -const __FlashStringHelper* getDeviceModelBrandString(DeviceModel model) { - switch (model) { - case DeviceModel::DeviceModel_Sonoff_Basic: - case DeviceModel::DeviceModel_Sonoff_TH1x: - case DeviceModel::DeviceModel_Sonoff_S2x: - case DeviceModel::DeviceModel_Sonoff_TouchT1: - case DeviceModel::DeviceModel_Sonoff_TouchT2: - case DeviceModel::DeviceModel_Sonoff_TouchT3: - case DeviceModel::DeviceModel_Sonoff_4ch: - case DeviceModel::DeviceModel_Sonoff_POW: - case DeviceModel::DeviceModel_Sonoff_POWr2: return F("Sonoff"); - case DeviceModel::DeviceModel_Shelly1: - case DeviceModel::DeviceModel_ShellyPLUG_S: return F("Shelly"); -# if CONFIG_ETH_USE_ESP32_EMAC - case DeviceModel::DeviceModel_Olimex_ESP32_PoE: - case DeviceModel::DeviceModel_Olimex_ESP32_EVB: - case DeviceModel::DeviceModel_Olimex_ESP32_GATEWAY: - #ifdef ESP32_CLASSIC - return F("Olimex"); - #endif // ifdef ESP32_CLASSIC - case DeviceModel::DeviceModel_wESP32: - #ifdef ESP32_CLASSIC - return F("wESP32"); - #endif // ifdef ESP32_CLASSIC - case DeviceModel::DeviceModel_WT32_ETH01: - #ifdef ESP32_CLASSIC - return F("WT32-ETH01"); - #endif // ifdef ESP32_CLASSIC -#endif - case DeviceModel::DeviceModel_default: - case DeviceModel::DeviceModel_MAX: break; - - // Do not use default: as this allows the compiler to detect any missing cases. - } - return F(""); -} - -const __FlashStringHelper* getDeviceModelTypeString(DeviceModel model) -{ - switch (model) { -#if defined(ESP8266) && !defined(LIMIT_BUILD_SIZE) - case DeviceModel::DeviceModel_Sonoff_Basic: return F(" Basic"); - case DeviceModel::DeviceModel_Sonoff_TH1x: return F(" TH1x"); - case DeviceModel::DeviceModel_Sonoff_S2x: return F(" S2x"); - case DeviceModel::DeviceModel_Sonoff_TouchT1: return F(" TouchT1"); - case DeviceModel::DeviceModel_Sonoff_TouchT2: return F(" TouchT2"); - case DeviceModel::DeviceModel_Sonoff_TouchT3: return F(" TouchT3"); - case DeviceModel::DeviceModel_Sonoff_4ch: return F(" 4ch"); - case DeviceModel::DeviceModel_Sonoff_POW: return F(" POW"); - case DeviceModel::DeviceModel_Sonoff_POWr2: return F(" POW-r2"); - case DeviceModel::DeviceModel_Shelly1: return F("1"); - case DeviceModel::DeviceModel_ShellyPLUG_S: return F(" PLUG S"); -#else // if defined(ESP8266) && !defined(LIMIT_BUILD_SIZE) - case DeviceModel::DeviceModel_Sonoff_Basic: - case DeviceModel::DeviceModel_Sonoff_TH1x: - case DeviceModel::DeviceModel_Sonoff_S2x: - case DeviceModel::DeviceModel_Sonoff_TouchT1: - case DeviceModel::DeviceModel_Sonoff_TouchT2: - case DeviceModel::DeviceModel_Sonoff_TouchT3: - case DeviceModel::DeviceModel_Sonoff_4ch: - case DeviceModel::DeviceModel_Sonoff_POW: - case DeviceModel::DeviceModel_Sonoff_POWr2: - case DeviceModel::DeviceModel_Shelly1: - case DeviceModel::DeviceModel_ShellyPLUG_S: - return F("default"); -#endif // if defined(ESP8266) && !defined(LIMIT_BUILD_SIZE) -#if CONFIG_ETH_USE_ESP32_EMAC - case DeviceModel::DeviceModel_Olimex_ESP32_PoE: return F(" ESP32-PoE"); - case DeviceModel::DeviceModel_Olimex_ESP32_EVB: return F(" ESP32-EVB"); - case DeviceModel::DeviceModel_Olimex_ESP32_GATEWAY: return F(" ESP32-GATEWAY"); - case DeviceModel::DeviceModel_wESP32: break; - case DeviceModel::DeviceModel_WT32_ETH01: return F(" add-on"); -#endif // if CONFIG_ETH_USE_ESP32_EMAC - - case DeviceModel::DeviceModel_default: - case DeviceModel::DeviceModel_MAX: return F("default"); - - // Do not use default: as this allows the compiler to detect any missing cases. - } - return F(""); -} - -String getDeviceModelString(DeviceModel model) { - return concat( - getDeviceModelBrandString(model), - getDeviceModelTypeString(model)); -} - -bool modelMatchingFlashSize(DeviceModel model) { -#if defined(ESP8266) || (defined(ESP32_CLASSIC) && FEATURE_ETHERNET) - const uint32_t size_MB = getFlashRealSizeInBytes() >> 20; -#endif // if defined(ESP8266) || (defined(ESP32_CLASSIC) && FEATURE_ETHERNET) - - // TD-er: This also checks for ESP8266/ESP8285/ESP32_CLASSIC - switch (model) { - case DeviceModel::DeviceModel_Sonoff_Basic: - case DeviceModel::DeviceModel_Sonoff_TH1x: - case DeviceModel::DeviceModel_Sonoff_S2x: - case DeviceModel::DeviceModel_Sonoff_TouchT1: - case DeviceModel::DeviceModel_Sonoff_TouchT2: - case DeviceModel::DeviceModel_Sonoff_TouchT3: - case DeviceModel::DeviceModel_Sonoff_4ch: -#ifdef ESP8266 - return size_MB == 1; -#else // ifdef ESP8266 - return false; -#endif // ifdef ESP8266 - - case DeviceModel::DeviceModel_Sonoff_POW: - case DeviceModel::DeviceModel_Sonoff_POWr2: -#ifdef ESP8266 - return size_MB == 4; -#else // ifdef ESP8266 - return false; -#endif // ifdef ESP8266 - - case DeviceModel::DeviceModel_Shelly1: - case DeviceModel::DeviceModel_ShellyPLUG_S: -#ifdef ESP8266 - return size_MB == 2; -#else // ifdef ESP8266 - return false; -#endif // ifdef ESP8266 - - // These Olimex boards all have Ethernet -#if CONFIG_ETH_USE_ESP32_EMAC - case DeviceModel::DeviceModel_Olimex_ESP32_PoE: - case DeviceModel::DeviceModel_Olimex_ESP32_EVB: - case DeviceModel::DeviceModel_Olimex_ESP32_GATEWAY: - case DeviceModel::DeviceModel_wESP32: - case DeviceModel::DeviceModel_WT32_ETH01: -# if defined(ESP32_CLASSIC) && FEATURE_ETHERNET - return size_MB == 4; -# else // if defined(ESP32_CLASSIC) && FEATURE_ETHERNET - return false; -# endif // if defined(ESP32_CLASSIC) && FEATURE_ETHERNET -#endif // if CONFIG_ETH_USE_ESP32_EMAC - case DeviceModel::DeviceModel_default: - case DeviceModel::DeviceModel_MAX: - return true; - - // Do not use default: as this allows the compiler to detect any missing cases. - } - return true; -} - -void setFactoryDefault(DeviceModel model) { - ResetFactoryDefaultPreference.setDeviceModel(model); -} - -/********************************************************************************************\ - Add pre defined plugins and rules. - \*********************************************************************************************/ -void addSwitchPlugin(taskIndex_t taskIndex, int gpio, const String& name, bool activeLow) { - setTaskDevice_to_TaskIndex(PLUGIN_GPIO, taskIndex); - const int pins[] = { gpio, -1, -1 }; - - setBasicTaskValues( - taskIndex, - 0, // taskdevicetimer - true, // enabled - name, // name - pins); - Settings.TaskDevicePin1PullUp[taskIndex] = true; - - if (activeLow) { - Settings.TaskDevicePluginConfig[taskIndex][2] = 1; // SWITCH_TYPE_PUSH_ACTIVE_LOW; - } - Settings.TaskDevicePluginConfig[taskIndex][3] = 1; // "Send Boot state" checked. -} - -void addPredefinedPlugins(const GpioFactorySettingsStruct& gpio_settings) { - taskIndex_t taskIndex = 0; - - for (int i = 0; i < 4; ++i) { - if (gpio_settings.button[i] >= 0) { - String label = F("Button"); - label += (i + 1); - addSwitchPlugin(taskIndex, gpio_settings.button[i], label, true); - ++taskIndex; - } - - if (gpio_settings.relais[i] >= 0) { - String label = F("Relay"); - label += (i + 1); - addSwitchPlugin(taskIndex, gpio_settings.relais[i], label, false); - ++taskIndex; - } - } -} - -void addButtonRelayRule(uint8_t buttonNumber, int relay_gpio) { - Settings.UseRules = true; - String fileName; - - #if defined(ESP32) - fileName += '/'; - #endif // if defined(ESP32) - fileName += F("rules1.txt"); - String rule = F("on ButtonBNR#state do\n if [RelayBNR#state]=0\n gpio,GNR,1\n else\n gpio,GNR,0\n endif\nendon\n"); - rule.replace(F("BNR"), String(buttonNumber)); - rule.replace(F("GNR"), String(relay_gpio)); - String result = appendLineToFile(fileName, rule); - - if (result.length() > 0) { - addLogMove(LOG_LEVEL_ERROR, result); - } -} - -void addPredefinedRules(const GpioFactorySettingsStruct& gpio_settings) { - for (int i = 0; i < 4; ++i) { - if ((gpio_settings.button[i] >= 0) && (gpio_settings.relais[i] >= 0)) { - addButtonRelayRule((i + 1), gpio_settings.relais[i]); - } - } -} - -// ******************************************************************************** -// change of device: cleanup old device and reset default settings -// ******************************************************************************** -void setTaskDevice_to_TaskIndex(pluginID_t taskdevicenumber, taskIndex_t taskIndex) { - struct EventStruct TempEvent(taskIndex); - String dummy; - - // let the plugin do its cleanup by calling PLUGIN_EXIT with this TaskIndex - PluginCall(PLUGIN_EXIT, &TempEvent, dummy); - taskClear(taskIndex, false); // clear settings, but do not save - ClearCustomTaskSettings(taskIndex); - - Settings.TaskDeviceNumber[taskIndex] = taskdevicenumber.value; - - // Settings.getPluginID_for_task(taskIndex) = taskdevicenumber; - - if (validPluginID_fullcheck(taskdevicenumber)) // set default values if a new device has been selected - { - // FIXME TD-er: Must check if this is working (e.g. need to set nr. decimals?) - ExtraTaskSettings.clear(); - ExtraTaskSettings.TaskIndex = taskIndex; - - // NOTE: do not enable task by default. allow user to enter sensible valus first and let him enable it when ready. - PluginCall(PLUGIN_SET_DEFAULTS, &TempEvent, dummy); - PluginCall(PLUGIN_GET_DEVICEVALUENAMES, &TempEvent, dummy); // the plugin should populate ExtraTaskSettings with its default values. - - #if FEATURE_MQTT_DISCOVER && FEATURE_CUSTOM_TASKVAR_VTYPE && FEATURE_TASKVALUE_UNIT_OF_MEASURE - // Fill in standard Unit of measurement and Value Type, if possible - const deviceIndex_t DeviceIndex = getDeviceIndex_from_TaskIndex(taskIndex); - std::vector discoveryItems; - MQTT_DiscoveryGetDeviceVType(taskIndex, discoveryItems, getValueCountForTask(taskIndex), dummy); - - for (uint8_t varNr = 0; varNr < VARS_PER_TASK; ++varNr) { - // Match varNr with the DiscoveryItems to find the Sensor_VType for the value - for (uint8_t j = 0; j < discoveryItems.size(); ++j) { - for (uint8_t k = 0; k < discoveryItems[j].valueCount; ++k) { // Can have multiple values for 1 VType - if (varNr == discoveryItems[j].varIndex + k) { - const String uom = getValueType2DefaultHAUoM(discoveryItems[j].VType); - - if (!uom.isEmpty()) { - const int uomIdx = getUnitOfMeasureIndex(uom); - - if (uomIdx > 0) { - ExtraTaskSettings.setTaskVarUnitOfMeasure(varNr, uomIdx); - } - } - - if (Device[DeviceIndex].CustomVTypeVar) { - ExtraTaskSettings.setTaskVarCustomVType(varNr, static_cast(discoveryItems[j].VType)); - } - } - } - } - } - - #endif // if FEATURE_MQTT_DISCOVER && FEATURE_CUSTOM_TASKVAR_VTYPE && FEATURE_TASKVALUE_UNIT_OF_MEASURE - } else { - // New task is empty task, thus save config now. - taskClear(taskIndex, true); // clear settings, and save - } -} - -// ******************************************************************************** -// Initialize task with some default values applicable for almost all tasks -// ******************************************************************************** -void setBasicTaskValues(taskIndex_t taskIndex, unsigned long taskdevicetimer, - bool enabled, const String& name, const int pins[3]) { - if (!validTaskIndex(taskIndex)) { return; } - const deviceIndex_t DeviceIndex = getDeviceIndex_from_TaskIndex(taskIndex); - - if (!validDeviceIndex(DeviceIndex)) { return; } - - LoadTaskSettings(taskIndex); // Make sure ExtraTaskSettings are up-to-date - - if (taskdevicetimer > 0) { - Settings.TaskDeviceTimer[taskIndex] = taskdevicetimer; - } else { - if (!Device[DeviceIndex].TimerOptional) { // Set default delay, unless it's optional... - Settings.TaskDeviceTimer[taskIndex] = Settings.Delay; - } - else { - Settings.TaskDeviceTimer[taskIndex] = 0; - } - } - Settings.TaskDeviceEnabled[taskIndex] = enabled; - //Settings.TaskDeviceEnabled[taskIndex].enabled = enabled; - safe_strncpy(ExtraTaskSettings.TaskDeviceName, name.c_str(), sizeof(ExtraTaskSettings.TaskDeviceName)); - - // FIXME TD-er: Check for valid GPIO pin (and -1 for "not set") - Settings.TaskDevicePin1[taskIndex] = pins[0]; - Settings.TaskDevicePin2[taskIndex] = pins[1]; - Settings.TaskDevicePin3[taskIndex] = pins[2]; -} +#include "../Helpers/Hardware.h" + +#include "../Commands/GPIO.h" +#include "../CustomBuild/ESPEasyLimits.h" +#include "../DataTypes/SPI_options.h" +#include "../ESPEasyCore/ESPEasyGPIO.h" +#include "../ESPEasyCore/ESPEasy_Log.h" +#include "../ESPEasyCore/ESPEasyEth.h" + +#include "../Globals/Device.h" +#include "../Globals/ESPEasyWiFiEvent.h" +#include "../Globals/ExtraTaskSettings.h" +#include "../Globals/Settings.h" +#include "../Globals/Statistics.h" +#include "../Globals/GlobalMapPortStatus.h" + +#include "../Helpers/ESPEasy_FactoryDefault.h" +#include "../Helpers/ESPEasy_Storage.h" +#include "../Helpers/FS_Helper.h" +#include "../Helpers/Hardware_device_info.h" +#include "../Helpers/Hardware_GPIO.h" +#include "../Helpers/Hardware_I2C.h" +#include "../Helpers/I2C_access.h" +#include "../Helpers/Misc.h" +#include "../Helpers/PortStatus.h" +#include "../Helpers/StringConverter.h" + +#include "../_Plugin_Helper.h" + + +#if defined(ESP8266) + # include +#endif // if defined(ESP8266) +#if defined(ESP32) + # include +#endif // if defined(ESP32) + +// #include "../../ESPEasy-Globals.h" + +#ifdef ESP32 + # include + # include + # include + # include + # include + + # if ESP_IDF_VERSION_MAJOR == 4 + # if CONFIG_IDF_TARGET_ESP32S3 // ESP32-S3 + # include + # include + # include + # elif CONFIG_IDF_TARGET_ESP32S2 // ESP32-S2 + # include + # include + # include + # elif CONFIG_IDF_TARGET_ESP32C3 // ESP32-C3 + # include + # include + # elif CONFIG_IDF_TARGET_ESP32 // ESP32/PICO-D4 + # include + # include + # include + # else // if CONFIG_IDF_TARGET_ESP32S3 + # error Target CONFIG_IDF_TARGET is not supported + # endif // if CONFIG_IDF_TARGET_ESP32S3 + # else // ESP32 IDF 5.x and later + # include + # include + # include + # endif // if ESP_IDF_VERSION_MAJOR == 4 + +#if CONFIG_IDF_TARGET_ESP32 // ESP32/PICO-D4 + # if ESP_IDF_VERSION_MAJOR < 5 + # define HAS_HALL_EFFECT_SENSOR 1 + # else // if ESP_IDF_VERSION_MAJOR < 5 + +// Support for Hall Effect sensor was removed in ESP_IDF 5.x + # define HAS_HALL_EFFECT_SENSOR 0 + # endif // if ESP_IDF_VERSION_MAJOR < 5 +# else + # define HAS_HALL_EFFECT_SENSOR 0 +# endif + + +# if ESP_IDF_VERSION_MAJOR >= 5 + +# include +# include +# include +# include + +// #include + +# endif // if ESP_IDF_VERSION_MAJOR >= 5 + +# include "../Helpers/Hardware_ADC_cali.h" + +#if FEATURE_ETHERNET +#include +#endif + +#endif // ifdef ESP32 + + +#if FEATURE_SD +# include +#endif // if FEATURE_SD + + +#include + + +# define GPIO_PLUGIN_ID 1 + +/********************************************************************************************\ + * Initialize specific hardware settings (only global ones, others are set through devices) + \*********************************************************************************************/ +void hardwareInit() +{ + // set GPIO pins state if not set to default + bool hasPullUp, hasPullDown; + + for (int gpio = 0; gpio <= MAX_GPIO; ++gpio) { + const bool serialPinConflict = isSerialConsolePin(gpio); + + if (!serialPinConflict) { + const uint32_t key = createKey(PLUGIN_GPIO, gpio); + #ifdef ESP32 + checkAndClearPWM(key); + #endif // ifdef ESP32 + + if (getGpioPullResistor(gpio, hasPullUp, hasPullDown)) { + PinBootState bootState = Settings.getPinBootState(gpio); + #if FEATURE_ETHERNET +/* + if (Settings.ETH_Pin_power_rst == gpio) + { + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + String log = F("ETH : Reset ETH module on pin "); + log += Settings.ETH_Pin_power_rst; + addLog(LOG_LEVEL_INFO, log); + } + bootState = PinBootState::Output_low; + } + */ + #endif // if FEATURE_ETHERNET + + #ifdef ESP32 + if (bootState != PinBootState::Default_state) { + gpio_reset_pin(static_cast(gpio)); + } + #endif + + switch (bootState) + { + case PinBootState::Default_state: + // At startup, pins are configured as INPUT + break; + case PinBootState::Output_low: + createAndSetPortStatus_Mode_State(key, PIN_MODE_OUTPUT, 0); + GPIO_Write(PLUGIN_GPIO, gpio, LOW, PIN_MODE_OUTPUT); + + // setPinState(1, gpio, PIN_MODE_OUTPUT, LOW); + break; + case PinBootState::Output_high: + createAndSetPortStatus_Mode_State(key, PIN_MODE_OUTPUT, 0); + GPIO_Write(PLUGIN_GPIO, gpio, HIGH, PIN_MODE_OUTPUT); + + // setPinState(1, gpio, PIN_MODE_OUTPUT, HIGH); + break; + case PinBootState::Input_pullup: + + if (hasPullUp) { + createAndSetPortStatus_Mode_State(key, PIN_MODE_INPUT_PULLUP, 0); + pinMode(gpio, INPUT_PULLUP); + } + break; + case PinBootState::Input_pulldown: + + if (hasPullDown) { + createAndSetPortStatus_Mode_State(key, PIN_MODE_INPUT_PULLDOWN, 0); + + #ifdef ESP8266 + + if (gpio == 16) { + pinMode(gpio, INPUT_PULLDOWN_16); + } + #endif // ifdef ESP8266 + #ifdef ESP32 + pinMode(gpio, INPUT_PULLDOWN); + #endif // ifdef ESP32 + } + break; + case PinBootState::Input: + createAndSetPortStatus_Mode_State(key, PIN_MODE_INPUT, 0); + pinMode(gpio, INPUT); + break; + } + } + } + } + + if (getGpioPullResistor(Settings.Pin_Reset, hasPullUp, hasPullDown)) { + if (hasPullUp) { + pinMode(Settings.Pin_Reset, INPUT_PULLUP); + } + } + + initI2C(); + + #if FEATURE_PLUGIN_PRIORITY + String dummy; + PluginCall(PLUGIN_PRIORITY_INIT_ALL, nullptr, dummy); + #endif // if FEATURE_PLUGIN_PRIORITY + + bool tryInitSPI = true; +#if FEATURE_ETHERNET + if ((Settings.NetworkMedium == NetworkMedium_t::Ethernet) && + isValid(Settings.ETH_Phy_Type) && + isSPI_EthernetType(Settings.ETH_Phy_Type)) + { +#if !ETH_SPI_SUPPORTS_CUSTOM + tryInitSPI = false; +#endif + } +#endif + + + // SPI Init + bool SPI_initialized = false; + if (tryInitSPI && Settings.isSPI_valid()) + { + SPI.setHwCs(false); + + // MFD: for ESP32 enable the SPI on HSPI as the default is VSPI + #ifdef ESP32 + + const SPI_Options_e SPI_selection = static_cast(Settings.InitSPI); + int8_t spi_gpios[3] = {}; + + if (Settings.getSPI_pins(spi_gpios)) { + if (SPI_selection == SPI_Options_e::Vspi_Fspi) { + SPI.begin(); // Default SPI bus + } else { + SPI.begin(spi_gpios[0], spi_gpios[1], spi_gpios[2]); + } + SPI_initialized = true; + } + #else // ifdef ESP32 + SPI.begin(); + SPI_initialized = true; + #endif // ifdef ESP32 + } + + if (SPI_initialized) + { + addLog(LOG_LEVEL_INFO, F("INIT : SPI Init (without CS)")); + #if FEATURE_SD + + if (Settings.Pin_sd_cs >= 0) + { + if (SD.begin(Settings.Pin_sd_cs)) + { + addLog(LOG_LEVEL_INFO, F("SD : Init OK")); + } + else + { + SD.end(); + addLog(LOG_LEVEL_ERROR, F("SD : Init failed")); + } + } +#endif // if FEATURE_SD + } else { + addLog(LOG_LEVEL_INFO, F("INIT : SPI not enabled")); + } +} + + +void checkResetFactoryPin() { + static uint8_t factoryResetCounter = 0; + + if (Settings.Pin_Reset == -1) { + return; + } + + if (digitalRead(Settings.Pin_Reset) == 0) { // active low reset pin + factoryResetCounter++; // just count every second + } + else + { // reset pin released + if (factoryResetCounter > 9) { + // factory reset and reboot + ResetFactory(); + } + + if (factoryResetCounter > 3) { + // normal reboot + reboot(IntendedRebootReason_e::ResetFactoryPinActive); + } + factoryResetCounter = 0; // count was < 3, reset counter + } +} + +#ifdef ESP8266 +int lastADCvalue = 0; + +int espeasy_analogRead(int pin) { + if (!WiFiEventData.wifiConnectInProgress) { + #if FEATURE_ADC_VCC + lastADCvalue = ESP.getVcc(); + #else + lastADCvalue = analogRead(A0); + #endif // if FEATURE_ADC_VCC + } + return lastADCvalue; +} + +#endif // ifdef ESP8266 + +#ifdef ESP32 + +// ESP32 ADC calibration datatypes. + + +// FIXME TD-er: For now keep a local array of the adc calibration +#if ESP_IDF_VERSION_MAJOR < 5 +Hardware_ADC_cali_t ESP32_ADC_cali[ADC_ATTEN_MAX]{}; +#else +Hardware_ADC_cali_t ESP32_ADC_cali[ADC_ATTENDB_MAX]{}; +#endif + + +void initADC() { + for (size_t atten = 0; atten < NR_ELEMENTS(ESP32_ADC_cali); ++atten) { + if (!ESP32_ADC_cali[atten].initialized()) { + // FIXME TD-er: For now fake some pin which is connected to ADC1 + #ifdef ESP32_CLASSIC + const int pin = 36; + #else + const int pin = 1; + #endif + ESP32_ADC_cali[atten].init(pin, static_cast(atten)); + } + } +} + +float applyADCFactoryCalibration(float raw_value, adc_atten_t attenuation) +{ + if (attenuation < NR_ELEMENTS(ESP32_ADC_cali)) { + return ESP32_ADC_cali[attenuation].applyFactoryCalibration(raw_value); + } + return raw_value; +} + +bool hasADC_factory_calibration() { + return ESP32_ADC_cali[0].useFactoryCalibration(); +} + +const __FlashStringHelper* getADC_factory_calibration_type() +{ + return ESP32_ADC_cali[0].getADC_factory_calibration_type(); +} + +float getADC_factory_calibrated_min(adc_atten_t attenuation) +{ + if (attenuation < NR_ELEMENTS(ESP32_ADC_cali)) { + return ESP32_ADC_cali[attenuation].getMinOut(); + } + return 0.0f; +} + +float getADC_factory_calibrated_max(adc_atten_t attenuation) +{ + if (attenuation < NR_ELEMENTS(ESP32_ADC_cali)) { + return ESP32_ADC_cali[attenuation].getMaxOut(); + } + return MAX_ADC_VALUE; +} + +int getADC_num_for_gpio(int pin) { + int ch; + + return getADC_num_for_gpio(pin, ch); +} + +int getADC_num_for_gpio(int pin, int& channel) +{ + int adc, t; + + if (getADC_gpio_info(pin, adc, channel, t)) { + return adc; + } + return -1; +} + +int espeasy_analogRead(int pin, bool readAsTouch) { + int value = 0; + int adc, ch, t; + + if (getADC_gpio_info(pin, adc, ch, t)) { + bool canread = false; + + switch (adc) { + case 0: + # if HAS_HALL_EFFECT_SENSOR + value = hallRead(); + # endif // if HAS_HALL_EFFECT_SENSOR + break; + case 1: + canread = true; + break; + case 2: +#if ESP_IDF_VERSION_MAJOR < 5 + if (WiFi.getMode() == WIFI_OFF) { + // See: + // https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/adc.html#configuration-and-reading-adc + // ADC2 is shared with WiFi, so don't read ADC2 when WiFi is on. + canread = true; + } +#else + canread = true; +#endif + break; + } + + if (canread) { + if (readAsTouch && (t >= 0)) { + #if defined(SOC_TOUCH_SENSOR_SUPPORTED) && SOC_TOUCH_SENSOR_SUPPORTED + value = touchRead(pin); + # endif + } else { + value = analogRead(pin); + } + } + } + return value; +} + + +int getCPU_MaxFreqMHz() +{ +#if CONFIG_IDF_TARGET_ESP32 + return static_cast(efuse_hal_get_rated_freq_mhz()); +#elif CONFIG_IDF_TARGET_ESP32C2 + return 120; +#elif CONFIG_IDF_TARGET_ESP32C3 + return 160; +#elif CONFIG_IDF_TARGET_ESP32C6 + return 160; +#elif CONFIG_IDF_TARGET_ESP32H2 + //IDF-6570 + return 96; +#elif CONFIG_IDF_TARGET_ESP32P4 + return 400; +#elif CONFIG_IDF_TARGET_ESP32S2 + return 240; +#elif CONFIG_IDF_TARGET_ESP32S3 + return 240; + +# else + # error Target CONFIG_IDF_TARGET is not supported + return 160; +# endif +} + +int getCPU_MinFreqMHz() +{ + // TODO TD-er: May differ on some ESPs and also some allow less but only without WiFi + return 80; +} + + +#endif // ifdef ESP32 + + + +/*********************************************************************************************\ +* High entropy hardware random generator +* Thanks to DigitalAlchemist +\*********************************************************************************************/ + +#if ESP_IDF_VERSION_MAJOR >= 5 +#include +#endif + +uint32_t HwRandom() { +#if ESP_IDF_VERSION_MAJOR >= 5 + // See for more info on the HW RNG: + // https://docs.espressif.com/projects/esp-idf/en/latest/esp32s2/api-reference/system/random.html + return esp_random(); +#else + +// Based on code from https://raw.githubusercontent.com/espressif/esp-idf/master/components/esp32/hw_random.c +// https://github.com/arendst/Tasmota/blob/1e6b78a957be538cf494f0e2dc49060d1cb0fe8b/tasmota/support_esp.ino#L805 +#if ESP8266 + + // https://web.archive.org/web/20160922031242/http://esp8266-re.foogod.com/wiki/Random_Number_Generator + # define _RAND_ADDR 0x3FF20E44UL +#endif // ESP8266 +#ifdef ESP32 + # define _RAND_ADDR 0x3FF75144UL +#endif // ESP32 + static uint32_t last_ccount = 0; + uint32_t ccount; + uint32_t result = 0; + + do { + ccount = ESP.getCycleCount(); + result ^= *(volatile uint32_t *)_RAND_ADDR; // -V566 + } while (ccount - last_ccount < 64); + last_ccount = ccount; + return result ^ *(volatile uint32_t *)_RAND_ADDR; // -V566 +#undef _RAND_ADDR +#endif +} + +long HwRandom(long howbig) { + if(howbig == 0) { + return 0; + } + return HwRandom() % howbig; +} + +long HwRandom(long howsmall, long howbig) { + if(howsmall >= howbig) { + return howsmall; + } + long diff = howbig - howsmall; + return HwRandom(diff) + howsmall; +} + +ESPEASY_RULES_FLOAT_TYPE HwRandom_f( + ESPEASY_RULES_FLOAT_TYPE howsmall, + ESPEASY_RULES_FLOAT_TYPE howbig) +{ + if (approximatelyEqual(howsmall, howbig)) { + return howsmall; + } + return mapADCtoFloat( + HwRandom(), + 0, std::numeric_limits::max(), + howsmall, howbig); +} + + +#ifdef ESP8266 +void readBootCause() { + lastBootCause = BOOT_CAUSE_MANUAL_REBOOT; + const rst_info *resetInfo = ESP.getResetInfoPtr(); + + if (resetInfo != nullptr) { + switch (resetInfo->reason) { + // normal startup by power on + case REASON_DEFAULT_RST: lastBootCause = BOOT_CAUSE_COLD_BOOT; break; + + // hardware watch dog reset + case REASON_WDT_RST: lastBootCause = BOOT_CAUSE_EXT_WD; break; + + // exception reset, GPIO status won’t change + case REASON_EXCEPTION_RST: lastBootCause = BOOT_CAUSE_EXCEPTION; break; + + // software watch dog reset, GPIO status won’t change + case REASON_SOFT_WDT_RST: lastBootCause = BOOT_CAUSE_SW_WATCHDOG; break; + + // software restart ,system_restart , GPIO status won’t change + case REASON_SOFT_RESTART: lastBootCause = BOOT_CAUSE_SOFT_RESTART; break; + + // wake up from deep-sleep + case REASON_DEEP_SLEEP_AWAKE: lastBootCause = BOOT_CAUSE_DEEP_SLEEP; break; + + // external system reset + case REASON_EXT_SYS_RST: lastBootCause = BOOT_CAUSE_MANUAL_REBOOT; break; + default: + break; + } + } +} + +#endif // ifdef ESP8266 + +#ifdef ESP32 +void readBootCause() { + lastBootCause = BOOT_CAUSE_MANUAL_REBOOT; + + #ifdef ESP32S2 + + switch (rtc_get_reset_reason(0)) { + case NO_MEAN : break; + case POWERON_RESET : lastBootCause = BOOT_CAUSE_MANUAL_REBOOT; break; /**<1, Vbat power on reset*/ + case RTC_SW_SYS_RESET : lastBootCause = BOOT_CAUSE_SOFT_RESTART; break; /**<3, Software reset digital core*/ + case DEEPSLEEP_RESET : lastBootCause = BOOT_CAUSE_DEEP_SLEEP; break; /**<5, Deep Sleep reset digital core*/ + case TG0WDT_SYS_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<7, Timer Group0 Watch dog reset digital core*/ + case TG1WDT_SYS_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<8, Timer Group1 Watch dog reset digital core*/ + case RTCWDT_SYS_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<9, RTC Watch dog Reset digital core*/ + case INTRUSION_RESET : lastBootCause = BOOT_CAUSE_SOFT_RESTART; break; /**<10, Instrusion tested to reset CPU*/ + case TG0WDT_CPU_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<11, Time Group0 reset CPU*/ + case RTC_SW_CPU_RESET : lastBootCause = BOOT_CAUSE_SOFT_RESTART; break; /**<12, Software reset CPU*/ + case RTCWDT_CPU_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<13, RTC Watch dog Reset CPU*/ + case RTCWDT_BROWN_OUT_RESET : lastBootCause = BOOT_CAUSE_POWER_UNSTABLE; break; /**<15, Reset when the vdd voltage is not stable*/ + case RTCWDT_RTC_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<16, RTC Watch dog reset digital core and rtc module*/ + case TG1WDT_CPU_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<17, Time Group1 reset CPU*/ + case SUPER_WDT_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<18, super watchdog reset digital core and rtc module*/ + case GLITCH_RTC_RESET : lastBootCause = BOOT_CAUSE_POWER_UNSTABLE; break; /**<19, glitch reset digital core and rtc module*/ + case EFUSE_RESET : lastBootCause = BOOT_CAUSE_MANUAL_REBOOT; break; /**<20, efuse reset digital core*/ + } + + + +#elif defined(ESP32S3) + switch (rtc_get_reset_reason(0)) { + case NO_MEAN : break; + case POWERON_RESET : lastBootCause = BOOT_CAUSE_MANUAL_REBOOT; break; /**<1, Vbat power on reset*/ + case RTC_SW_SYS_RESET : lastBootCause = BOOT_CAUSE_SOFT_RESTART; break; /**<3, Software reset digital core*/ + case DEEPSLEEP_RESET : lastBootCause = BOOT_CAUSE_DEEP_SLEEP; break; /**<5, Deep Sleep reset digital core*/ + case TG0WDT_SYS_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<7, Timer Group0 Watch dog reset digital core*/ + case TG1WDT_SYS_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<8, Timer Group1 Watch dog reset digital core*/ + case RTCWDT_SYS_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<9, RTC Watch dog Reset digital core*/ + case INTRUSION_RESET : lastBootCause = BOOT_CAUSE_SOFT_RESTART; break; /**<10, Instrusion tested to reset CPU*/ + case TG0WDT_CPU_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<11, Time Group0 reset CPU*/ + case RTC_SW_CPU_RESET : lastBootCause = BOOT_CAUSE_SOFT_RESTART; break; /**<12, Software reset CPU*/ + case RTCWDT_CPU_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<13, RTC Watch dog Reset CPU*/ + case RTCWDT_BROWN_OUT_RESET : lastBootCause = BOOT_CAUSE_POWER_UNSTABLE; break; /**<15, Reset when the vdd voltage is not stable*/ + case RTCWDT_RTC_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<16, RTC Watch dog reset digital core and rtc module*/ + case TG1WDT_CPU_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<17, Time Group1 reset CPU*/ + case SUPER_WDT_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<18, super watchdog reset digital core and rtc module*/ + case GLITCH_RTC_RESET : lastBootCause = BOOT_CAUSE_POWER_UNSTABLE; break; /**<19, glitch reset digital core and rtc module*/ + case EFUSE_RESET : lastBootCause = BOOT_CAUSE_MANUAL_REBOOT; break; /**<20, efuse reset digital core*/ + case USB_UART_CHIP_RESET : lastBootCause = BOOT_CAUSE_MANUAL_REBOOT; break; /**<21, usb uart reset digital core */ + case USB_JTAG_CHIP_RESET : lastBootCause = BOOT_CAUSE_MANUAL_REBOOT; break; /**<22, usb jtag reset digital core */ + case POWER_GLITCH_RESET : lastBootCause = BOOT_CAUSE_POWER_UNSTABLE; break; /**<23, power glitch reset digital core and rtc module*/ + } + + +#elif defined(ESP32C2) + switch (rtc_get_reset_reason(0)) { + case NO_MEAN : break; + case POWERON_RESET : lastBootCause = BOOT_CAUSE_MANUAL_REBOOT; break; /**<1, Vbat power on reset*/ + case RTC_SW_SYS_RESET : lastBootCause = BOOT_CAUSE_SOFT_RESTART; break; /**<3, Software reset digital core*/ + case DEEPSLEEP_RESET : lastBootCause = BOOT_CAUSE_DEEP_SLEEP; break; /**<3, Deep Sleep reset digital core*/ + case TG0WDT_SYS_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<7, Timer Group0 Watch dog reset digital core*/ + case RTCWDT_SYS_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<9, RTC Watch dog Reset digital core*/ + case INTRUSION_RESET : lastBootCause = BOOT_CAUSE_SOFT_RESTART; break; /**<10, Instrusion tested to reset CPU*/ + case TG0WDT_CPU_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<11, Time Group0 reset CPU*/ + case RTC_SW_CPU_RESET : lastBootCause = BOOT_CAUSE_SOFT_RESTART; break; /**<12, Software reset CPU*/ + case RTCWDT_CPU_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<13, RTC Watch dog Reset CPU*/ + case RTCWDT_BROWN_OUT_RESET : lastBootCause = BOOT_CAUSE_POWER_UNSTABLE; break; /**<15, Reset when the vdd voltage is not stable*/ + case RTCWDT_RTC_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<16, RTC Watch dog reset digital core and rtc module*/ + case SUPER_WDT_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<11, super watchdog reset digital core and rtc module*/ + case GLITCH_RTC_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<19, glitch reset digital core and rtc module*/ + case EFUSE_RESET : lastBootCause = BOOT_CAUSE_POWER_UNSTABLE; break; /**<20, efuse reset digital core*/ + case JTAG_RESET : lastBootCause = BOOT_CAUSE_MANUAL_REBOOT; break; /**<24, jtag reset CPU*/ + } + + +#elif defined(ESP32C3) + switch (rtc_get_reset_reason(0)) { + case NO_MEAN : break; + case POWERON_RESET : lastBootCause = BOOT_CAUSE_MANUAL_REBOOT; break; /**<1, Vbat power on reset*/ + case RTC_SW_SYS_RESET : lastBootCause = BOOT_CAUSE_SOFT_RESTART; break; /**<3, Software reset digital core*/ + case DEEPSLEEP_RESET : lastBootCause = BOOT_CAUSE_DEEP_SLEEP; break; /**<5, Deep Sleep reset digital core*/ + case TG0WDT_SYS_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<7, Timer Group0 Watch dog reset digital core*/ + case TG1WDT_SYS_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<8, Timer Group1 Watch dog reset digital core*/ + case RTCWDT_SYS_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<9, RTC Watch dog Reset digital core*/ + case INTRUSION_RESET : lastBootCause = BOOT_CAUSE_SOFT_RESTART; break; /**<10, Instrusion tested to reset CPU*/ + case TG0WDT_CPU_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<11, Time Group0 reset CPU*/ + case RTC_SW_CPU_RESET : lastBootCause = BOOT_CAUSE_SOFT_RESTART; break; /**<12, Software reset CPU*/ + case RTCWDT_CPU_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<13, RTC Watch dog Reset CPU*/ + case RTCWDT_BROWN_OUT_RESET : lastBootCause = BOOT_CAUSE_POWER_UNSTABLE; break; /**<15, Reset when the vdd voltage is not stable*/ + case RTCWDT_RTC_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<16, RTC Watch dog reset digital core and rtc module*/ + case TG1WDT_CPU_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<17, Time Group1 reset CPU*/ + case SUPER_WDT_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<18, super watchdog reset digital core and rtc module*/ + case GLITCH_RTC_RESET : lastBootCause = BOOT_CAUSE_POWER_UNSTABLE; break; /**<19, glitch reset digital core and rtc module*/ + case EFUSE_RESET : lastBootCause = BOOT_CAUSE_MANUAL_REBOOT; break; /**<20, efuse reset digital core*/ + case USB_UART_CHIP_RESET : lastBootCause = BOOT_CAUSE_MANUAL_REBOOT; break; /**<21, usb uart reset digital core */ + case USB_JTAG_CHIP_RESET : lastBootCause = BOOT_CAUSE_MANUAL_REBOOT; break; /**<22, usb jtag reset digital core */ + case POWER_GLITCH_RESET : lastBootCause = BOOT_CAUSE_POWER_UNSTABLE; break; /**<23, power glitch reset digital core and rtc module*/ + } + +#elif defined(ESP32C6) + switch (rtc_get_reset_reason(0)) { + case NO_MEAN : break; + case POWERON_RESET : lastBootCause = BOOT_CAUSE_MANUAL_REBOOT; break; /**<1, Vbat power on reset*/ + case RTC_SW_SYS_RESET : lastBootCause = BOOT_CAUSE_SOFT_RESTART; break; /**<3, Software reset digital core*/ + case DEEPSLEEP_RESET : lastBootCause = BOOT_CAUSE_DEEP_SLEEP; break; /**<5, Deep Sleep reset digital core*/ + case SDIO_RESET : lastBootCause = BOOT_CAUSE_MANUAL_REBOOT; break; /**<6, Reset by SLC module, reset digital core (hp system)*/ + case TG0WDT_SYS_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<7, Timer Group0 Watch dog reset digital core*/ + case TG1WDT_SYS_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<8, Timer Group1 Watch dog reset digital core*/ + case RTCWDT_SYS_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<9, RTC Watch dog Reset digital core*/ + case TG0WDT_CPU_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<11, Time Group0 reset CPU*/ + case RTC_SW_CPU_RESET : lastBootCause = BOOT_CAUSE_SOFT_RESTART; break; /**<12, Software reset CPU*/ + case RTCWDT_CPU_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<13, RTC Watch dog Reset CPU*/ + case RTCWDT_BROWN_OUT_RESET : lastBootCause = BOOT_CAUSE_POWER_UNSTABLE; break; /**<15, Reset when the vdd voltage is not stable*/ + case RTCWDT_RTC_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<16, RTC Watch dog reset digital core and rtc module*/ + case TG1WDT_CPU_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<17, Time Group1 reset CPU*/ + case SUPER_WDT_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<18, super watchdog reset digital core and rtc module*/ + case EFUSE_RESET : lastBootCause = BOOT_CAUSE_MANUAL_REBOOT; break; /**<20, efuse reset digital core*/ + case USB_UART_CHIP_RESET : lastBootCause = BOOT_CAUSE_MANUAL_REBOOT; break; /**<21, usb uart reset digital core */ + case USB_JTAG_CHIP_RESET : lastBootCause = BOOT_CAUSE_MANUAL_REBOOT; break; /**<22, usb jtag reset digital core */ + case JTAG_RESET : lastBootCause = BOOT_CAUSE_MANUAL_REBOOT; break; /**<24, jtag reset CPU*/ + } + +# elif defined(ESP32_CLASSIC) + switch (rtc_get_reset_reason(0)) { + case NO_MEAN : break; + case POWERON_RESET : lastBootCause = BOOT_CAUSE_MANUAL_REBOOT; break; /**<1, Vbat power on reset*/ + case SW_RESET : lastBootCause = BOOT_CAUSE_SOFT_RESTART; break; /**<3, Software reset digital core*/ + case OWDT_RESET : lastBootCause = BOOT_CAUSE_SW_WATCHDOG; break; /**<4, Legacy watch dog reset digital core*/ + case DEEPSLEEP_RESET : lastBootCause = BOOT_CAUSE_DEEP_SLEEP; break; /**<3, Deep Sleep reset digital core*/ + case SDIO_RESET : lastBootCause = BOOT_CAUSE_MANUAL_REBOOT; break; /**<6, Reset by SLC module, reset digital core*/ + case TG0WDT_SYS_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<7, Timer Group0 Watch dog reset digital core*/ + case TG1WDT_SYS_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<8, Timer Group1 Watch dog reset digital core*/ + case RTCWDT_SYS_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<9, RTC Watch dog Reset digital core*/ + case INTRUSION_RESET : lastBootCause = BOOT_CAUSE_SOFT_RESTART; break; /**<10, Instrusion tested to reset CPU*/ + case TGWDT_CPU_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<11, Time Group reset CPU*/ + case SW_CPU_RESET : lastBootCause = BOOT_CAUSE_SOFT_RESTART; break; /**<12, Software reset CPU*/ + case RTCWDT_CPU_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<13, RTC Watch dog Reset CPU*/ + case EXT_CPU_RESET : lastBootCause = BOOT_CAUSE_MANUAL_REBOOT; break; /**<14, for APP CPU, reseted by PRO CPU*/ + case RTCWDT_BROWN_OUT_RESET : lastBootCause = BOOT_CAUSE_POWER_UNSTABLE; break; /**<15, Reset when the vdd voltage is not stable*/ + case RTCWDT_RTC_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<16, RTC Watch dog reset digital core and rtc module*/ + } + + # else + + static_assert(false, "Implement processor architecture"); + + #endif +} + +#endif // ifdef ESP32 + + +/********************************************************************************************\ + Hardware specific configurations + \*********************************************************************************************/ +const __FlashStringHelper* getDeviceModelBrandString(DeviceModel model) { + switch (model) { + case DeviceModel::DeviceModel_Sonoff_Basic: + case DeviceModel::DeviceModel_Sonoff_TH1x: + case DeviceModel::DeviceModel_Sonoff_S2x: + case DeviceModel::DeviceModel_Sonoff_TouchT1: + case DeviceModel::DeviceModel_Sonoff_TouchT2: + case DeviceModel::DeviceModel_Sonoff_TouchT3: + case DeviceModel::DeviceModel_Sonoff_4ch: + case DeviceModel::DeviceModel_Sonoff_POW: + case DeviceModel::DeviceModel_Sonoff_POWr2: return F("Sonoff"); + case DeviceModel::DeviceModel_Shelly1: + case DeviceModel::DeviceModel_ShellyPLUG_S: return F("Shelly"); +# if CONFIG_ETH_USE_ESP32_EMAC + case DeviceModel::DeviceModel_Olimex_ESP32_PoE: + case DeviceModel::DeviceModel_Olimex_ESP32_EVB: + case DeviceModel::DeviceModel_Olimex_ESP32_GATEWAY: + #ifdef ESP32_CLASSIC + return F("Olimex"); + #endif // ifdef ESP32_CLASSIC + case DeviceModel::DeviceModel_wESP32: + #ifdef ESP32_CLASSIC + return F("wESP32"); + #endif // ifdef ESP32_CLASSIC + case DeviceModel::DeviceModel_WT32_ETH01: + #ifdef ESP32_CLASSIC + return F("WT32-ETH01"); + #endif // ifdef ESP32_CLASSIC +#endif + case DeviceModel::DeviceModel_default: + case DeviceModel::DeviceModel_MAX: break; + + // Do not use default: as this allows the compiler to detect any missing cases. + } + return F(""); +} + +const __FlashStringHelper* getDeviceModelTypeString(DeviceModel model) +{ + switch (model) { +#if defined(ESP8266) && !defined(LIMIT_BUILD_SIZE) + case DeviceModel::DeviceModel_Sonoff_Basic: return F(" Basic"); + case DeviceModel::DeviceModel_Sonoff_TH1x: return F(" TH1x"); + case DeviceModel::DeviceModel_Sonoff_S2x: return F(" S2x"); + case DeviceModel::DeviceModel_Sonoff_TouchT1: return F(" TouchT1"); + case DeviceModel::DeviceModel_Sonoff_TouchT2: return F(" TouchT2"); + case DeviceModel::DeviceModel_Sonoff_TouchT3: return F(" TouchT3"); + case DeviceModel::DeviceModel_Sonoff_4ch: return F(" 4ch"); + case DeviceModel::DeviceModel_Sonoff_POW: return F(" POW"); + case DeviceModel::DeviceModel_Sonoff_POWr2: return F(" POW-r2"); + case DeviceModel::DeviceModel_Shelly1: return F("1"); + case DeviceModel::DeviceModel_ShellyPLUG_S: return F(" PLUG S"); +#else // if defined(ESP8266) && !defined(LIMIT_BUILD_SIZE) + case DeviceModel::DeviceModel_Sonoff_Basic: + case DeviceModel::DeviceModel_Sonoff_TH1x: + case DeviceModel::DeviceModel_Sonoff_S2x: + case DeviceModel::DeviceModel_Sonoff_TouchT1: + case DeviceModel::DeviceModel_Sonoff_TouchT2: + case DeviceModel::DeviceModel_Sonoff_TouchT3: + case DeviceModel::DeviceModel_Sonoff_4ch: + case DeviceModel::DeviceModel_Sonoff_POW: + case DeviceModel::DeviceModel_Sonoff_POWr2: + case DeviceModel::DeviceModel_Shelly1: + case DeviceModel::DeviceModel_ShellyPLUG_S: + return F("default"); +#endif // if defined(ESP8266) && !defined(LIMIT_BUILD_SIZE) +#if CONFIG_ETH_USE_ESP32_EMAC + case DeviceModel::DeviceModel_Olimex_ESP32_PoE: return F(" ESP32-PoE"); + case DeviceModel::DeviceModel_Olimex_ESP32_EVB: return F(" ESP32-EVB"); + case DeviceModel::DeviceModel_Olimex_ESP32_GATEWAY: return F(" ESP32-GATEWAY"); + case DeviceModel::DeviceModel_wESP32: break; + case DeviceModel::DeviceModel_WT32_ETH01: return F(" add-on"); +#endif // if CONFIG_ETH_USE_ESP32_EMAC + + case DeviceModel::DeviceModel_default: + case DeviceModel::DeviceModel_MAX: return F("default"); + + // Do not use default: as this allows the compiler to detect any missing cases. + } + return F(""); +} + +String getDeviceModelString(DeviceModel model) { + return concat( + getDeviceModelBrandString(model), + getDeviceModelTypeString(model)); +} + +bool modelMatchingFlashSize(DeviceModel model) { +#if defined(ESP8266) || (defined(ESP32_CLASSIC) && FEATURE_ETHERNET) + const uint32_t size_MB = getFlashRealSizeInBytes() >> 20; +#endif // if defined(ESP8266) || (defined(ESP32_CLASSIC) && FEATURE_ETHERNET) + + // TD-er: This also checks for ESP8266/ESP8285/ESP32_CLASSIC + switch (model) { + case DeviceModel::DeviceModel_Sonoff_Basic: + case DeviceModel::DeviceModel_Sonoff_TH1x: + case DeviceModel::DeviceModel_Sonoff_S2x: + case DeviceModel::DeviceModel_Sonoff_TouchT1: + case DeviceModel::DeviceModel_Sonoff_TouchT2: + case DeviceModel::DeviceModel_Sonoff_TouchT3: + case DeviceModel::DeviceModel_Sonoff_4ch: +#ifdef ESP8266 + return size_MB == 1; +#else // ifdef ESP8266 + return false; +#endif // ifdef ESP8266 + + case DeviceModel::DeviceModel_Sonoff_POW: + case DeviceModel::DeviceModel_Sonoff_POWr2: +#ifdef ESP8266 + return size_MB == 4; +#else // ifdef ESP8266 + return false; +#endif // ifdef ESP8266 + + case DeviceModel::DeviceModel_Shelly1: + case DeviceModel::DeviceModel_ShellyPLUG_S: +#ifdef ESP8266 + return size_MB == 2; +#else // ifdef ESP8266 + return false; +#endif // ifdef ESP8266 + + // These Olimex boards all have Ethernet +#if CONFIG_ETH_USE_ESP32_EMAC + case DeviceModel::DeviceModel_Olimex_ESP32_PoE: + case DeviceModel::DeviceModel_Olimex_ESP32_EVB: + case DeviceModel::DeviceModel_Olimex_ESP32_GATEWAY: + case DeviceModel::DeviceModel_wESP32: + case DeviceModel::DeviceModel_WT32_ETH01: +# if defined(ESP32_CLASSIC) && FEATURE_ETHERNET + return size_MB == 4; +# else // if defined(ESP32_CLASSIC) && FEATURE_ETHERNET + return false; +# endif // if defined(ESP32_CLASSIC) && FEATURE_ETHERNET +#endif // if CONFIG_ETH_USE_ESP32_EMAC + case DeviceModel::DeviceModel_default: + case DeviceModel::DeviceModel_MAX: + return true; + + // Do not use default: as this allows the compiler to detect any missing cases. + } + return true; +} + +void setFactoryDefault(DeviceModel model) { + ResetFactoryDefaultPreference.setDeviceModel(model); +} + +/********************************************************************************************\ + Add pre defined plugins and rules. + \*********************************************************************************************/ +void addSwitchPlugin(taskIndex_t taskIndex, int gpio, const String& name, bool activeLow) { + setTaskDevice_to_TaskIndex(PLUGIN_GPIO, taskIndex); + const int pins[] = { gpio, -1, -1 }; + + setBasicTaskValues( + taskIndex, + 0, // taskdevicetimer + true, // enabled + name, // name + pins); + Settings.TaskDevicePin1PullUp[taskIndex] = true; + + if (activeLow) { + Settings.TaskDevicePluginConfig[taskIndex][2] = 1; // SWITCH_TYPE_PUSH_ACTIVE_LOW; + } + Settings.TaskDevicePluginConfig[taskIndex][3] = 1; // "Send Boot state" checked. +} + +void addPredefinedPlugins(const GpioFactorySettingsStruct& gpio_settings) { + taskIndex_t taskIndex = 0; + + for (int i = 0; i < 4; ++i) { + if (gpio_settings.button[i] >= 0) { + String label = F("Button"); + label += (i + 1); + addSwitchPlugin(taskIndex, gpio_settings.button[i], label, true); + ++taskIndex; + } + + if (gpio_settings.relais[i] >= 0) { + String label = F("Relay"); + label += (i + 1); + addSwitchPlugin(taskIndex, gpio_settings.relais[i], label, false); + ++taskIndex; + } + } +} + +void addButtonRelayRule(uint8_t buttonNumber, int relay_gpio) { + Settings.UseRules = true; + String fileName; + + #if defined(ESP32) + fileName += '/'; + #endif // if defined(ESP32) + fileName += F("rules1.txt"); + String rule = F("on ButtonBNR#state do\n if [RelayBNR#state]=0\n gpio,GNR,1\n else\n gpio,GNR,0\n endif\nendon\n"); + rule.replace(F("BNR"), String(buttonNumber)); + rule.replace(F("GNR"), String(relay_gpio)); + String result = appendLineToFile(fileName, rule); + + if (result.length() > 0) { + addLogMove(LOG_LEVEL_ERROR, result); + } +} + +void addPredefinedRules(const GpioFactorySettingsStruct& gpio_settings) { + for (int i = 0; i < 4; ++i) { + if ((gpio_settings.button[i] >= 0) && (gpio_settings.relais[i] >= 0)) { + addButtonRelayRule((i + 1), gpio_settings.relais[i]); + } + } +} + +// ******************************************************************************** +// change of device: cleanup old device and reset default settings +// ******************************************************************************** +void setTaskDevice_to_TaskIndex(pluginID_t taskdevicenumber, taskIndex_t taskIndex) { + struct EventStruct TempEvent(taskIndex); + String dummy; + + // let the plugin do its cleanup by calling PLUGIN_EXIT with this TaskIndex + PluginCall(PLUGIN_EXIT, &TempEvent, dummy); + taskClear(taskIndex, false); // clear settings, but do not save + ClearCustomTaskSettings(taskIndex); + + Settings.TaskDeviceNumber[taskIndex] = taskdevicenumber.value; + + // Settings.getPluginID_for_task(taskIndex) = taskdevicenumber; + + if (validPluginID_fullcheck(taskdevicenumber)) // set default values if a new device has been selected + { + // FIXME TD-er: Must check if this is working (e.g. need to set nr. decimals?) + ExtraTaskSettings.clear(); + ExtraTaskSettings.TaskIndex = taskIndex; + + // NOTE: do not enable task by default. allow user to enter sensible valus first and let him enable it when ready. + PluginCall(PLUGIN_SET_DEFAULTS, &TempEvent, dummy); + PluginCall(PLUGIN_GET_DEVICEVALUENAMES, &TempEvent, dummy); // the plugin should populate ExtraTaskSettings with its default values. + + #if FEATURE_MQTT_DISCOVER && FEATURE_CUSTOM_TASKVAR_VTYPE && FEATURE_TASKVALUE_UNIT_OF_MEASURE + // Fill in standard Unit of measurement and Value Type, if possible + const deviceIndex_t DeviceIndex = getDeviceIndex_from_TaskIndex(taskIndex); + std::vector discoveryItems; + MQTT_DiscoveryGetDeviceVType(taskIndex, discoveryItems, getValueCountForTask(taskIndex), dummy); + + for (uint8_t varNr = 0; varNr < VARS_PER_TASK; ++varNr) { + // Match varNr with the DiscoveryItems to find the Sensor_VType for the value + for (uint8_t j = 0; j < discoveryItems.size(); ++j) { + for (uint8_t k = 0; k < discoveryItems[j].valueCount; ++k) { // Can have multiple values for 1 VType + if (varNr == discoveryItems[j].varIndex + k) { + const String uom = getValueType2DefaultHAUoM(discoveryItems[j].VType); + + if (!uom.isEmpty()) { + const int uomIdx = getUnitOfMeasureIndex(uom); + + if (uomIdx > 0) { + ExtraTaskSettings.setTaskVarUnitOfMeasure(varNr, uomIdx); + } + } + + if (Device[DeviceIndex].CustomVTypeVar) { + ExtraTaskSettings.setTaskVarCustomVType(varNr, static_cast(discoveryItems[j].VType)); + } + } + } + } + } + + #endif // if FEATURE_MQTT_DISCOVER && FEATURE_CUSTOM_TASKVAR_VTYPE && FEATURE_TASKVALUE_UNIT_OF_MEASURE + } else { + // New task is empty task, thus save config now. + taskClear(taskIndex, true); // clear settings, and save + } +} + +// ******************************************************************************** +// Initialize task with some default values applicable for almost all tasks +// ******************************************************************************** +void setBasicTaskValues(taskIndex_t taskIndex, unsigned long taskdevicetimer, + bool enabled, const String& name, const int pins[3]) { + if (!validTaskIndex(taskIndex)) { return; } + const deviceIndex_t DeviceIndex = getDeviceIndex_from_TaskIndex(taskIndex); + + if (!validDeviceIndex(DeviceIndex)) { return; } + + LoadTaskSettings(taskIndex); // Make sure ExtraTaskSettings are up-to-date + + if (taskdevicetimer > 0) { + Settings.TaskDeviceTimer[taskIndex] = taskdevicetimer; + } else { + if (!Device[DeviceIndex].TimerOptional) { // Set default delay, unless it's optional... + Settings.TaskDeviceTimer[taskIndex] = Settings.Delay; + } + else { + Settings.TaskDeviceTimer[taskIndex] = 0; + } + } + Settings.TaskDeviceEnabled[taskIndex] = enabled; + //Settings.TaskDeviceEnabled[taskIndex].enabled = enabled; + safe_strncpy(ExtraTaskSettings.TaskDeviceName, name.c_str(), sizeof(ExtraTaskSettings.TaskDeviceName)); + + // FIXME TD-er: Check for valid GPIO pin (and -1 for "not set") + Settings.TaskDevicePin1[taskIndex] = pins[0]; + Settings.TaskDevicePin2[taskIndex] = pins[1]; + Settings.TaskDevicePin3[taskIndex] = pins[2]; +} diff --git a/src/src/Helpers/Networking.cpp b/src/src/Helpers/Networking.cpp index c91a3aaefc..f794fd9204 100644 --- a/src/src/Helpers/Networking.cpp +++ b/src/src/Helpers/Networking.cpp @@ -1,2038 +1,2036 @@ -#include "../Helpers/Networking.h" - -#include "../Commands/ExecuteCommand.h" -#include "../CustomBuild/CompiletimeDefines.h" -#include "../DataStructs/NodeStruct.h" -#include "../DataStructs/TimingStats.h" -#include "../DataTypes/EventValueSource.h" -#include "../ESPEasyCore/ESPEasy_Log.h" -#include "../ESPEasyCore/ESPEasy_backgroundtasks.h" -#include "../ESPEasyCore/ESPEasyEth.h" -#include "../ESPEasyCore/ESPEasyNetwork.h" -#include "../ESPEasyCore/ESPEasyWifi.h" -#include "../ESPEasyCore/Serial.h" -#include "../Globals/ESPEasyEthEvent.h" -#include "../Globals/ESPEasyWiFiEvent.h" -#include "../Globals/ESPEasy_Scheduler.h" - -#ifdef USES_ESPEASY_NOW -#include "../Globals/ESPEasy_now_handler.h" -#endif - -#include "../Globals/EventQueue.h" -#include "../Globals/NetworkState.h" -#include "../Globals/Nodes.h" -#include "../Globals/ResetFactoryDefaultPref.h" -#include "../Globals/Settings.h" -#include "../Helpers/ESPEasy_Storage.h" -#include "../Helpers/ESPEasy_time_calc.h" -#include "../Helpers/Hardware.h" -#include "../Helpers/Misc.h" -#include "../Helpers/Network.h" -#include "../Helpers/Numerical.h" -#include "../Helpers/StringConverter.h" -#include "../Helpers/StringProvider.h" - -#if RESPONSE_PARSER_SUPPORT -#include "../Helpers/HTTPResponseParser.h" -#endif - -#include "../../ESPEasy-Globals.h" - -#include -#include -#include // for getDigestAuth - -#include - -#include - - -// Generic Networking routines - -// Syslog -// UDP system messaging -// SSDP -// #if LWIP_VERSION_MAJOR == 2 -#define IPADDR2STR(addr) (uint8_t)((uint32_t)addr & 0xFF), (uint8_t)(((uint32_t)addr >> 8) & 0xFF), \ - (uint8_t)(((uint32_t)addr >> 16) & 0xFF), (uint8_t)(((uint32_t)addr >> 24) & 0xFF) - -// #endif - -#include - -#ifdef ESP8266 -#include -#include -#include -#include -#endif - -#ifdef SUPPORT_ARP -# include - -# ifdef ESP32 -# include -# include - -void _etharp_gratuitous_func(struct netif *netif) { - etharp_gratuitous(netif); -} - -void etharp_gratuitous_r(struct netif *netif) { - tcpip_callback_with_block((tcpip_callback_fn)_etharp_gratuitous_func, netif, 0); -} - -# endif // ifdef ESP32 - -#endif // ifdef SUPPORT_ARP - -#if FEATURE_DOWNLOAD -# ifdef ESP8266 -# include -# endif // ifdef ESP8266 -# ifdef ESP32 -# include -# include -# endif // ifdef ESP32 -#endif // if FEATURE_DOWNLOAD - -#include - -/*********************************************************************************************\ - Syslog client -\*********************************************************************************************/ -void sendSyslog(uint8_t logLevel, const String& message) -{ - if ((Settings.Syslog_IP[0] != 0) && NetworkConnected()) - { - IPAddress broadcastIP(Settings.Syslog_IP[0], Settings.Syslog_IP[1], Settings.Syslog_IP[2], Settings.Syslog_IP[3]); - - FeedSW_watchdog(); - - if (portUDP.beginPacket(broadcastIP, Settings.SyslogPort) == 0) { - // problem resolving the hostname or port - return; - } - unsigned int prio = Settings.SyslogFacility * 8; - - if (logLevel == LOG_LEVEL_ERROR) { - prio += 3; // syslog error - } - else if (logLevel == LOG_LEVEL_INFO) { - prio += 5; // syslog notice - } - else { - prio += 7; - } - - // An RFC3164 compliant message must be formated like : "[TimeStamp ]Hostname TaskName: Message" - - // Using Settings.Name as the Hostname (Hostname must NOT content space) - { - String header; - header += '<'; - header += prio; - header += '>'; - header += NetworkCreateRFCCompliantHostname(true); - header += F(" EspEasy: "); - header.trim(); - header.replace(' ', '_'); - - #ifdef ESP8266 - portUDP.write(header.c_str(), header.length()); - #endif // ifdef ESP8266 - #ifdef ESP32 - portUDP.write(reinterpret_cast(header.c_str()), header.length()); - #endif // ifdef ESP32 - } - - #ifdef ESP8266 - portUDP.write(message.c_str(), message.length()); - #endif // ifdef ESP8266 - #ifdef ESP32 - portUDP.write(reinterpret_cast(message.c_str()), message.length()); - #endif // ifdef ESP32 - - portUDP.endPacket(); - FeedSW_watchdog(); - delay(0); - } -} - -#if FEATURE_ESPEASY_P2P - -/*********************************************************************************************\ - Send event using UDP message -\*********************************************************************************************/ -void SendUDPCommand(uint8_t destUnit, const char *data, uint8_t dataLength) -{ - if (!NetworkConnected(10)) { - return; - } - - if (destUnit != 0) - { - sendUDP(destUnit, (const uint8_t *)data, dataLength); - delay(10); - } else { - for (auto it = Nodes.begin(); it != Nodes.end(); ++it) { - if (it->first != Settings.Unit) { - sendUDP(it->first, (const uint8_t *)data, dataLength); - delay(10); - } - } - } - delay(50); -} - -/*********************************************************************************************\ - Send UDP message to specific unit (unit 255=broadcast) -\*********************************************************************************************/ -void sendUDP(uint8_t unit, const uint8_t *data, uint8_t size) -{ - if (!NetworkConnected(10)) { - return; - } - - IPAddress remoteNodeIP = getIPAddressForUnit(unit); - - if (remoteNodeIP[0] == 0) { - return; - } - -# ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG_MORE)) { - addLogMove(LOG_LEVEL_DEBUG_MORE, strformat( - F("UDP : Send UDP message to %d (%s)"), - unit, - remoteNodeIP.toString().c_str() - )); - } -# endif // ifndef BUILD_NO_DEBUG - - statusLED(true); - FeedSW_watchdog(); - portUDP.beginPacket(remoteNodeIP, Settings.UDPPort); - portUDP.write(data, size); - portUDP.endPacket(); - FeedSW_watchdog(); - delay(0); -} - -/*********************************************************************************************\ - Update UDP port (ESPEasy propiertary protocol) -\*********************************************************************************************/ -void updateUDPport(bool force) -{ - static uint16_t lastUsedUDPPort = 0; - - if (!force && Settings.UDPPort == lastUsedUDPPort) { - return; - } - - if (lastUsedUDPPort != 0) { - portUDP.stop(); - lastUsedUDPPort = 0; - } - - if (!NetworkConnected()) { - return; - } - - if (Settings.UDPPort != 0) { - if (portUDP.begin(Settings.UDPPort) == 0) { - if (loglevelActiveFor(LOG_LEVEL_ERROR)) { - addLogMove(LOG_LEVEL_ERROR, concat(F("UDP : Cannot bind to ESPEasy p2p UDP port "), Settings.UDPPort)); - } - } else { - lastUsedUDPPort = Settings.UDPPort; - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - addLogMove(LOG_LEVEL_INFO, concat(F("UDP : Start listening on port "), Settings.UDPPort)); - } - } - } -} - -/*********************************************************************************************\ - Check UDP messages (ESPEasy propiertary protocol) -\*********************************************************************************************/ -boolean runningUPDCheck = false; -void checkUDP() -{ - if (!NetworkConnected()) - return; - if (Settings.UDPPort == 0) { - return; - } - - if (runningUPDCheck) { - return; - } - START_TIMER - - runningUPDCheck = true; - - // UDP events - int packetSize = portUDP.parsePacket(); - - if (packetSize > 0 /*&& portUDP.remotePort() == Settings.UDPPort*/) - { - statusLED(true); - - IPAddress remoteIP = portUDP.remoteIP(); - - if (portUDP.remotePort() == 123) - { - // unexpected NTP reply, drop for now... - while (portUDP.available()) { - // Do not call portUDP.flush() as that's meant to sending the packet (on ESP8266) - portUDP.read(); - } - - runningUPDCheck = false; - return; - } - - // UDP_PACKETSIZE_MAX should be as small as possible but still enough to hold all - // data for PLUGIN_UDP_IN or CPLUGIN_UDP_IN calls - // This node may also receive other UDP packets which may be quite large - // and then crash due to memory allocation failures - if ((packetSize >= 2) && (packetSize < UDP_PACKETSIZE_MAX)) { - // Allocate buffer to process packet. - // Resize it to be 1 byte larger so we can 0-terminate it - // in case it is some plain text string - std::vector packetBuffer; - packetBuffer.resize(packetSize + 1); - - if (packetBuffer.size() >= static_cast(packetSize)) { - memset(&packetBuffer[0], 0, packetSize + 1); - int len = portUDP.read(&packetBuffer[0], packetSize); - - if (len >= 2) { - if (static_cast(packetBuffer[0]) != 255) - { - packetBuffer[len] = 0; - # ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - addLogMove(LOG_LEVEL_DEBUG, - strformat(F("UDP : %s Command: %s"), - formatIP(remoteIP, true).c_str(), - wrapWithQuotesIfContainsParameterSeparatorChar(String(&packetBuffer[0])).c_str() - )); - } - #endif - ExecuteCommand_all({EventValueSource::Enum::VALUE_SOURCE_SYSTEM, &packetBuffer[0]}, true); - } - else - { - // binary data! - switch (packetBuffer[1]) - { - case 1: // sysinfo message - { - if (len < 13) { - break; - } - int copy_length = sizeof(NodeStruct); - // Older versions sent 80 bytes, regardless of the size of NodeStruct - // Make sure the extra data received is ignored as it was also not initialized - if (len == 80) { - copy_length = 56; - } - - if (copy_length > (len - 2)) { - copy_length = (len - 2); - } - NodeStruct received; - memcpy(&received, &packetBuffer[2], copy_length); - - if (received.validate(remoteIP)) { - Nodes.addNode(received); // Create a new element when not present - -# ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG_MORE)) { - addLogMove(LOG_LEVEL_DEBUG_MORE, - strformat(F("UDP : %s (%d) %s,%s,%d"), - formatIP(remoteIP).c_str(), - received.unit, - received.STA_MAC().toString().c_str(), - formatIP(received.IP(), true).c_str(), - received.unit)); - } - -#endif // ifndef BUILD_NO_DEBUG - } - break; - } - - default: - { - struct EventStruct TempEvent; - TempEvent.Data = reinterpret_cast(&packetBuffer[0]); - TempEvent.Par1 = remoteIP[3]; - TempEvent.Par2 = len; - String dummy; - // TD-er: Disabled the PLUGIN_UDP_IN call as we don't have any plugin using this. - //PluginCall(PLUGIN_UDP_IN, &TempEvent, dummy); - CPluginCall(CPlugin::Function::CPLUGIN_UDP_IN, &TempEvent); - break; - } - } - } - } - } - } - } - - // Flush any remaining content of the packet. - while (portUDP.available()) { - // Do not call portUDP.flush() as that's meant to sending the packet (on ESP8266) - portUDP.read(); - } - runningUPDCheck = false; - STOP_TIMER(CHECK_UDP); -} - -/*********************************************************************************************\ - Get formatted IP address for unit - formatcodes: 0 = default toString(), 1 = empty string when invalid, 2 = 0 when invalid -\*********************************************************************************************/ -String formatUnitToIPAddress(uint8_t unit, uint8_t formatCode) { - IPAddress unitIPAddress = getIPAddressForUnit(unit); - - if (unitIPAddress[0] == 0) { // Invalid? - switch (formatCode) { - case 1: // Return empty string - { - return EMPTY_STRING; - } - case 2: // Return "0" - { - return String('0'); - } - } - } - return formatIP(unitIPAddress); -} - -/*********************************************************************************************\ - Get IP address for unit -\*********************************************************************************************/ -IPAddress getIPAddressForUnit(uint8_t unit) { - if (unit == 255) { - const IPAddress ip(255, 255, 255, 255); - return ip; - } - auto it = Nodes.find(unit); - - if (it == Nodes.end() || it->second.ip[0] == 0) { - IPAddress ip; - return ip; - } -#if FEATURE_USE_IPV6 -/* - // FIXME TD-er: for now do not try to send to IPv6 - if (it->second.hasIPv6_mac_based_link_local) { - return it->second.IPv6_link_local(); - } - if (it->second.hasIPv6_mac_based_link_global) { - return it->second.IPv6_global(); - } -*/ -#endif - return it->second.IP(); -} - - -String getNameForUnit(uint8_t unit) { - auto it = Nodes.find(unit); - - if (it == Nodes.end() || it->second.getNodeName().isEmpty()) { - return EMPTY_STRING; - } - return it->second.getNodeName(); -} - -long getAgeForUnit(uint8_t unit) { - auto it = Nodes.find(unit); - - if (it == Nodes.end()) { - return -1000; // milliseconds, negative == unknown - } - return static_cast(it->second.getAge()); -} - -uint16_t getBuildnrForUnit(uint8_t unit) { - auto it = Nodes.find(unit); - - if (it == Nodes.end() || it->second.build == 0) { - return 0; - } - return it->second.build; -} - -float getLoadForUnit(uint8_t unit) { - auto it = Nodes.find(unit); - - if (it == Nodes.end()) { - return 0.0f; - } - return it->second.getLoad(); -} - -uint8_t getTypeForUnit(uint8_t unit) { - auto it = Nodes.find(unit); - - if (it == Nodes.end()) { - return 0; - } - return it->second.nodeType; -} - -const __FlashStringHelper* getTypeStringForUnit(uint8_t unit) { - auto it = Nodes.find(unit); - - if (it == Nodes.end()) { - return F(""); - } - return it->second.getNodeTypeDisplayString(); -} - -/*********************************************************************************************\ - Refresh aging for remote units, drop if too old... -\*********************************************************************************************/ -void refreshNodeList() -{ - unsigned long max_age; - const unsigned long max_age_allowed = 10 * 60 * 1000; // 10 minutes - - Nodes.refreshNodeList(max_age_allowed, max_age); - - #ifdef USES_ESPEASY_NOW - #ifdef ESP8266 - // FIXME TD-er: Do not perform regular scans on ESP32 as long as we cannot scan per channel - if (!Nodes.isEndpoint()) { - WifiScan(true, Nodes.getESPEasyNOW_channel()); - } - #endif - #endif - - if (max_age > (0.75 * max_age_allowed)) { - Scheduler.sendGratuitousARP_now(); - } - sendSysInfoUDP(1); - #ifdef USES_ESPEASY_NOW - if (Nodes.recentlyBecameDistanceZero()) { - // Send to all channels - ESPEasy_now_handler.sendDiscoveryAnnounce(-1); - } else { - ESPEasy_now_handler.sendDiscoveryAnnounce(); - } - ESPEasy_now_handler.sendNTPquery(); - ESPEasy_now_handler.sendTraceRoute(); - #endif // ifdef USES_ESPEASY_NOW -} - -/*********************************************************************************************\ - Broadcast system info to other nodes. (to update node lists) -\*********************************************************************************************/ -void sendSysInfoUDP(uint8_t repeats) -{ - if ((Settings.UDPPort == 0) || !NetworkConnected(10)) { - return; - } - - // 1 byte 'binary token 255' - // 1 byte id '1' - // NodeStruct object (packed data struct) - - // send my info to the world... -# ifndef BUILD_NO_DEBUG - addLog(LOG_LEVEL_DEBUG_MORE, F("UDP : Send Sysinfo message")); -# endif // ifndef BUILD_NO_DEBUG - - const NodeStruct *thisNode = Nodes.getThisNode(); - - if (thisNode == nullptr) { - // Should not happen - return; - } - - // Prepare UDP packet to send - constexpr size_t data_size = sizeof(NodeStruct) + 2; - uint8_t data[data_size] = {0}; - data[0] = 255; - data[1] = 1; - memcpy(&data[2], thisNode, sizeof(NodeStruct)); - - for (uint8_t counter = 0; counter < repeats; counter++) - { - statusLED(true); - - IPAddress broadcastIP(255, 255, 255, 255); - FeedSW_watchdog(); - portUDP.beginPacket(broadcastIP, Settings.UDPPort); - portUDP.write(data, data_size); - portUDP.endPacket(); - - if (counter < (repeats - 1)) { - // FIXME TD-er: Must use scheduler to send out messages, not using delay - delay(100); - } - } -} - -#endif // FEATURE_ESPEASY_P2P - -#if defined(ESP8266) - -# if FEATURE_SSDP - -/********************************************************************************************\ - Respond to HTTP XML requests for SSDP information - \*********************************************************************************************/ -void SSDP_schema() { - if (!NetworkConnected(10)) { - return; - } - - const IPAddress ip = NetworkLocalIP(); - const uint32_t chipId = ESP.getChipId(); - char uuid[64]; - - sprintf_P(uuid, PSTR("38323636-4558-4dda-9188-cda0e6%02x%02x%02x"), - (uint16_t)((chipId >> 16) & 0xff), - (uint16_t)((chipId >> 8) & 0xff), - (uint16_t)chipId & 0xff); - - web_server.client().print(F( - "HTTP/1.1 200 OK\r\n" - "Content-Type: text/xml\r\n" - "Connection: close\r\n" - "Access-Control-Allow-Origin: *\r\n" - "\r\n" - "" - "" - "" - "1" - "0" - "" - "http://")); - - web_server.client().print(formatIP(ip)); - web_server.client().print(F(":80/" - "" - "urn:schemas-upnp-org:device:BinaryLight:1" - "")); - web_server.client().print(Settings.getName()); - web_server.client().print(F("" - "/" - "")); - web_server.client().print(String(ESP.getChipId())); - web_server.client().print(F("" - "ESP Easy" - "")); - web_server.client().print(getValue(LabelType::GIT_BUILD)); - web_server.client().print(F("" - "http://www.letscontrolit.com" - "http://www.letscontrolit.com" - "http://www.letscontrolit.com" - "uuid:")); - web_server.client().print(String(uuid)); - web_server.client().print(F("" - "\r\n" - "\r\n")); -} - -/********************************************************************************************\ - Global SSDP stuff - \*********************************************************************************************/ - -UdpContext *_server; - -IPAddress _respondToAddr; -uint16_t _respondToPort; - -bool _pending; -unsigned short _delay; -unsigned long _process_time; -unsigned long _notify_time; - -# define SSDP_INTERVAL 1200 -# define SSDP_PORT 1900 -# define SSDP_METHOD_SIZE 10 -# define SSDP_URI_SIZE 2 -# define SSDP_BUFFER_SIZE 64 -# define SSDP_MULTICAST_TTL 2 - -static const IPAddress SSDP_MULTICAST_ADDR(239, 255, 255, 250); - - -/********************************************************************************************\ - Launch SSDP listener and send initial notify - \*********************************************************************************************/ -bool SSDP_begin() { - _pending = false; - - if (_server != nullptr) { - _server->unref(); - - // FIXME TD-er: Shouldn't this also call delete _server ? - - _server = nullptr; - } - - constexpr unsigned size = sizeof(UdpContext); - void *ptr = special_calloc(1, size); - - if (ptr != nullptr) { - _server = new (ptr) UdpContext; - } - - if (_server == nullptr) { - return false; - } - _server->ref(); - - ip_addr_t ifaddr; - - ifaddr.addr = NetworkLocalIP(); - ip_addr_t multicast_addr; - - multicast_addr.addr = (uint32_t)SSDP_MULTICAST_ADDR; - - if (igmp_joingroup(&ifaddr, &multicast_addr) != ERR_OK) { - return false; - } - -# ifdef CORE_POST_2_5_0 - - // Core 2.5.0 changed the signature of some UdpContext function. - if (!_server->listen(IP_ADDR_ANY, SSDP_PORT)) { - return false; - } - - _server->setMulticastInterface(&ifaddr); - _server->setMulticastTTL(SSDP_MULTICAST_TTL); - _server->onRx(&SSDP_update); - - if (!_server->connect(&multicast_addr, SSDP_PORT)) { - return false; - } -# else // ifdef CORE_POST_2_5_0 - - if (!_server->listen(*IP_ADDR_ANY, SSDP_PORT)) { - return false; - } - - _server->setMulticastInterface(ifaddr); - _server->setMulticastTTL(SSDP_MULTICAST_TTL); - _server->onRx(&SSDP_update); - - if (!_server->connect(multicast_addr, SSDP_PORT)) { - return false; - } -# endif // ifdef CORE_POST_2_5_0 - - SSDP_update(); - - return true; -} - -/********************************************************************************************\ - Send SSDP messages (notify & responses) - \*********************************************************************************************/ -void SSDP_send(uint8_t method) { - uint32_t ip = NetworkLocalIP(); - - // FIXME TD-er: Why create String objects of these flashstrings? - String _ssdp_response_template = F( - "HTTP/1.1 200 OK\r\n" - "EXT:\r\n" - "ST: upnp:rootdevice\r\n"); - - String _ssdp_notify_template = F( - "NOTIFY * HTTP/1.1\r\n" - "HOST: 239.255.255.250:1900\r\n" - "NT: upnp:rootdevice\r\n" - "NTS: ssdp:alive\r\n"); - - String _ssdp_packet_template = F( - "%s" // _ssdp_response_template / _ssdp_notify_template - "CACHE-CONTROL: max-age=%u\r\n" // SSDP_INTERVAL - "SERVER: Arduino/1.0 UPNP/1.1 ESPEasy/%u\r\n" // _modelNumber - "USN: uuid:%s\r\n" // _uuid - "LOCATION: http://%u.%u.%u.%u:80/ssdp.xml\r\n" // NetworkLocalIP(), - "\r\n"); - { - char uuid[64] = { 0 }; - uint32_t chipId = ESP.getChipId(); - sprintf_P(uuid, PSTR("38323636-4558-4dda-9188-cda0e6%02x%02x%02x"), - (uint16_t)((chipId >> 16) & 0xff), - (uint16_t)((chipId >> 8) & 0xff), - (uint16_t)chipId & 0xff); - - char *buffer = nullptr; - # ifdef USE_SECOND_HEAP - { - HeapSelectIram ephemeral; - buffer = new (std::nothrow) char[1460](); - } - # endif // ifdef USE_SECOND_HEAP - if (buffer == nullptr) { - buffer = new (std::nothrow) char[1460](); - } - - if (buffer == nullptr) { return; } - int len = snprintf(buffer, 1460, - _ssdp_packet_template.c_str(), - (method == 0) ? _ssdp_response_template.c_str() : _ssdp_notify_template.c_str(), - SSDP_INTERVAL, - Settings.Build, - uuid, - IPADDR2STR(&ip) - ); - - _server->append(buffer, len); - delete[] buffer; - } - - ip_addr_t remoteAddr; - uint16_t remotePort; - - if (method == 0) { - remoteAddr.addr = _respondToAddr; - remotePort = _respondToPort; - } else { - remoteAddr.addr = SSDP_MULTICAST_ADDR; - remotePort = SSDP_PORT; - } - _server->send(&remoteAddr, remotePort); - statusLED(true); -} - -/********************************************************************************************\ - SSDP message processing - \*********************************************************************************************/ -void SSDP_update() { - if (!_pending && _server->next()) { - ssdp_method_t method = NONE; - - _respondToAddr = _server->getRemoteAddress(); - _respondToPort = _server->getRemotePort(); - - typedef enum { METHOD, URI, PROTO, KEY, VALUE, ABORT } states; - states state = METHOD; - - typedef enum { START, MAN, ST, MX } headers; - headers header = START; - - uint8_t cursor = 0; - uint8_t cr = 0; - - char buffer[SSDP_BUFFER_SIZE] = { 0 }; - - while (_server->getSize() > 0) { - char c = _server->read(); - - (c == '\r' || c == '\n') ? cr++ : cr = 0; - - switch (state) { - case METHOD: - - if (c == ' ') { - if (strcmp_P(buffer, PSTR("M-SEARCH")) == 0) { method = SEARCH; } - else if (strcmp_P(buffer, PSTR("NOTIFY")) == 0) { method = NOTIFY; } - - if (method == NONE) { state = ABORT; } - else { state = URI; } - cursor = 0; - } else if (cursor < SSDP_METHOD_SIZE - 1) { - buffer[cursor++] = c; - buffer[cursor] = '\0'; - } - break; - case URI: - - if (c == ' ') { - if (strcmp(buffer, "*")) { state = ABORT; } - else { state = PROTO; } - cursor = 0; - } else if (cursor < SSDP_URI_SIZE - 1) { - buffer[cursor++] = c; - buffer[cursor] = '\0'; - } - break; - case PROTO: - - if (cr == 2) { - state = KEY; - cursor = 0; - } - break; - case KEY: - - if (cr == 4) { - _pending = true; - _process_time = millis(); - } - else if (c == ' ') { - cursor = 0; - state = VALUE; - } - else if ((c != '\r') && (c != '\n') && (c != ':') && (cursor < SSDP_BUFFER_SIZE - 1)) { - buffer[cursor++] = c; - buffer[cursor] = '\0'; - } - break; - case VALUE: - - if (cr == 2) { - switch (header) { - case START: - break; - case MAN: - break; - case ST: - - if (strcmp_P(buffer, PSTR("ssdp:all"))) { - state = ABORT; - } - - // if the search type matches our type, we should respond instead of ABORT - if (strcmp_P(buffer, PSTR("urn:schemas-upnp-org:device:BinaryLight:1")) == 0) { - _pending = true; - _process_time = millis(); - state = KEY; - } - break; - case MX: - _delay = HwRandom(0, atoi(buffer)) * 1000L; - break; - } - - if (state != ABORT) { - state = KEY; - header = START; - cursor = 0; - } - } else if ((c != '\r') && (c != '\n')) { - if (header == START) { - if (strncmp(buffer, "MA", 2) == 0) { header = MAN; } - else if (strcmp(buffer, "ST") == 0) { header = ST; } - else if (strcmp(buffer, "MX") == 0) { header = MX; } - } - - if (cursor < SSDP_BUFFER_SIZE - 1) { - buffer[cursor++] = c; - buffer[cursor] = '\0'; - } - } - break; - case ABORT: - _pending = false; _delay = 0; - break; - } - } - } - - if (_pending && timeOutReached(_process_time + _delay)) { - _pending = false; _delay = 0; - SSDP_send(NONE); - } else if ((_notify_time == 0) || timeOutReached(_notify_time + (SSDP_INTERVAL * 1000L))) { - _notify_time = millis(); - SSDP_send(NOTIFY); - } - - if (_pending) { - while (_server->next()) { - _server->flush(); - } - } -} - -# endif // if FEATURE_SSDP -#endif // if defined(ESP8266) - - -// ******************************************************************************** -// Return subnet range of WiFi. -// ******************************************************************************** -bool getSubnetRange(IPAddress& low, IPAddress& high) -{ - if (!WiFiEventData.WiFiGotIP()) { - return false; - } - - const IPAddress ip = NetworkLocalIP(); - const IPAddress subnet = NetworkSubnetMask(); - - low = ip; - high = ip; - - // Compute subnet range. - for (uint8_t i = 0; i < 4; ++i) { - if (subnet[i] != 255) { - low[i] = low[i] & subnet[i]; - high[i] = high[i] | ~subnet[i]; - } - } - return true; -} - -// ******************************************************************************** -// Functions to test and handle network/client connectivity. -// ******************************************************************************** - -#ifdef CORE_POST_2_5_0 -# include -#endif // ifdef CORE_POST_2_5_0 - - -bool hasIPaddr() { - if (useStaticIP()) { return true; } - -#ifdef CORE_POST_2_5_0 - bool configured = false; - - for (auto addr : addrList) { - if ((configured = (!addr.isLocal() && (addr.ifnumber() == STATION_IF)))) { - /* - ESPEASY_SERIAL_CONSOLE_PORT.printf("STA: IF='%s' hostname='%s' addr= %s\n", - addr.ifname().c_str(), - addr.ifhostname(), - addr.toString().c_str()); - */ - break; - } - } - return configured; -#else // ifdef CORE_POST_2_5_0 - return WiFi.isConnected(); -#endif // ifdef CORE_POST_2_5_0 -} - -bool useStaticIP() { - #if FEATURE_ETHERNET - if (active_network_medium == NetworkMedium_t::Ethernet) { - return ethUseStaticIP(); - } - #endif - return WiFiUseStaticIP(); -} - -// Check connection. Maximum timeout 500 msec. -bool NetworkConnected(uint32_t timeout_ms) { - if (NetworkConnected()) return true; - -#ifdef USES_ESPEASY_NOW - if (isESPEasy_now_only()) { - return false; - } -#endif - - if (timeout_ms > 500) { - timeout_ms = 500; - } - - uint32_t timer = millis() + timeout_ms; - uint32_t min_delay = timeout_ms / 20; - - if (min_delay < 10) { - delay(0); // Allow at least once time for backgroundtasks - min_delay = 10; - } - - // Apparently something needs network, perform check to see if it is ready now. - while (!NetworkConnected()) { - if (timeOutReached(timer)) { - return false; - } - delay(min_delay); // Allow the backgroundtasks to continue procesing. - } - return true; -} - -bool hostReachable(const IPAddress& ip) { - if (!NetworkConnected()) { return false; } - - return true; // Disabled ping as requested here: - // https://github.com/letscontrolit/ESPEasy/issues/1494#issuecomment-397872538 - - /* - // Only do 1 ping at a time to return early - uint8_t retry = 3; - while (retry > 0) { - #if defined(ESP8266) - if (Ping.ping(ip, 1)) return true; - #endif - #if defined(ESP32) - if (ping_start(ip, 4, 0, 0, 5)) return true; - #endif - delay(50); - --retry; - } - if (loglevelActiveFor(LOG_LEVEL_ERROR)) { - String log = F("Host unreachable: "); - log += formatIP(ip); - addLog(LOG_LEVEL_ERROR, log); - } - if (ip[1] == 0 && ip[2] == 0 && ip[3] == 0) { - // Work-around to fix connected but not able to communicate. - addLog(LOG_LEVEL_ERROR, F("WiFi : Detected strange behavior, reconnect wifi.")); - WifiDisconnect(); - } - logConnectionStatus(); - return false; - */ -} - -#if FEATURE_HTTP_CLIENT -bool connectClient(WiFiClient& client, const char *hostname, uint16_t port, uint32_t timeout_ms) { - IPAddress ip; - - if (resolveHostByName(hostname, ip, timeout_ms)) { - return connectClient(client, ip, port, timeout_ms); - } - return false; -} - -bool connectClient(WiFiClient& client, IPAddress ip, uint16_t port, uint32_t timeout_ms) -{ - START_TIMER; - - if (!NetworkConnected()) { - client.stop(); - return false; - } -#ifndef BUILD_NO_DEBUG - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - addLog(LOG_LEVEL_DEBUG, strformat( - F("connectClient: '%s' port: %u"), - ip.toString().c_str(), - port)); - } -#endif - - // In case of domain name resolution error result can be negative. - // https://github.com/esp8266/Arduino/blob/18f643c7e2d6a0da9d26ff2b14c94e6536ab78c1/libraries/Ethernet/src/Dns.cpp#L44 - // Thus must match the result with 1. - bool connected = (client.connect(ip, port) == 1); - - delay(0); - - if (!connected) { -#ifndef BUILD_NO_DEBUG - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - addLog(LOG_LEVEL_ERROR, strformat( - F("connectClient: connect failed to '%s' port: %u"), - ip.toString().c_str(), - port)); - } -#endif - - Scheduler.sendGratuitousARP_now(); - client.stop(); // Make sure to start over without some stale connection - } - STOP_TIMER(CONNECT_CLIENT_STATS); -#if defined(ESP32) || defined(ARDUINO_ESP8266_RELEASE_2_3_0) || defined(ARDUINO_ESP8266_RELEASE_2_4_0) -#else - - if (connected) { - client.keepAlive(); // Use default keep alive values - } -#endif // if defined(ESP32) || defined(ARDUINO_ESP8266_RELEASE_2_3_0) || defined(ARDUINO_ESP8266_RELEASE_2_4_0) - return connected; -} -#endif // FEATURE_HTTP_CLIENT - -void scrubDNS() { - #if FEATURE_ETHERNET - if (active_network_medium == NetworkMedium_t::Ethernet) { - if (EthEventData.EthServicesInitialized()) { - setDNS(0, EthEventData.dns0_cache); - setDNS(1, EthEventData.dns1_cache); - } - return; - } - #endif - if (WiFiEventData.WiFiServicesInitialized()) { - setDNS(0, WiFiEventData.dns0_cache); - setDNS(1, WiFiEventData.dns1_cache); - } -} - -bool valid_DNS_address(const IPAddress& dns) { - return (/*dns.v4() != (uint32_t)0x00000000 && */ - dns != IPAddress((uint32_t)0xFD000000) && -#ifdef ESP32 - // Bug where IPv6 global prefix is set as DNS - // Global IPv6 prefixes currently start with 2xxx:: - (dns[0] & 0xF0) != 0x20 && -#endif - dns != INADDR_NONE); -} - -bool setDNS(int index, const IPAddress& dns) { - if (index >= 2) return false; - #ifdef ESP8266 - if(dns.isSet() && dns != WiFi.dnsIP(index)) { - dns_setserver(index, dns); - #ifndef BUILD_NO_DEBUG - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - addLogMove(LOG_LEVEL_INFO, concat(F("IP : Set DNS: "), formatIP(dns))); - } - #endif - return true; - } - #endif - #ifdef ESP32 - ip_addr_t d; - d.type = IPADDR_TYPE_V4; - - if (valid_DNS_address(dns) || dns == INADDR_NONE) { - // Set DNS0-Server - d.u_addr.ip4.addr = static_cast(dns); - const ip_addr_t* cur_dns = dns_getserver(index); - if (cur_dns != nullptr && cur_dns->u_addr.ip4.addr == d.u_addr.ip4.addr) { - // Still the same as before - return false; - } - dns_setserver(index, &d); - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - addLogMove(LOG_LEVEL_INFO, concat(F("IP : Set DNS: "), formatIP(dns))); - } - return true; - } - #endif - return false; -} - -bool resolveHostByName(const char *aHostname, IPAddress& aResult, uint32_t timeout_ms) { - START_TIMER; - - if (!NetworkConnected()) { - return false; - } - - FeedSW_watchdog(); - - // FIXME TD-er: Must try to restore DNS server entries. - scrubDNS(); - -#if defined(ARDUINO_ESP8266_RELEASE_2_3_0) || defined(ESP32) - bool resolvedIP = WiFi.hostByName(aHostname, aResult) == 1; -#else // if defined(ARDUINO_ESP8266_RELEASE_2_3_0) || defined(ESP32) - bool resolvedIP = WiFi.hostByName(aHostname, aResult, timeout_ms) == 1; -#endif // if defined(ARDUINO_ESP8266_RELEASE_2_3_0) || defined(ESP32) - delay(0); - FeedSW_watchdog(); - - if (!resolvedIP) { - Scheduler.sendGratuitousARP_now(); - } - STOP_TIMER(HOST_BY_NAME_STATS); - return resolvedIP; -} - -bool hostReachable(const String& hostname) { - IPAddress remote_addr; - - if (resolveHostByName(hostname.c_str(), remote_addr)) { - return hostReachable(remote_addr); - } - - if (loglevelActiveFor(LOG_LEVEL_ERROR)) { - addLogMove(LOG_LEVEL_ERROR, concat(F("Hostname cannot be resolved: "), hostname)); - } - return false; -} - -// Create a random port for the UDP connection. -// Return true when successful. -bool beginWiFiUDP_randomPort(WiFiUDP& udp) { - if (!NetworkConnected()) { - return false; - } - unsigned int attempts = 3; - - while (attempts > 0) { - --attempts; - long port = HwRandom(1025, 65535); - - if (udp.begin(port) != 0) { - return true; - } - } - return false; -} - -void sendGratuitousARP() { - if (!NetworkConnected()) { - return; - } -#ifdef SUPPORT_ARP - - // See https://github.com/letscontrolit/ESPEasy/issues/2374 - START_TIMER; - netif *n = netif_list; - - while (n) { - if ((n->hwaddr_len == ETH_HWADDR_LEN) && - (n->flags & NETIF_FLAG_ETHARP) && - ((n->flags & NETIF_FLAG_LINK_UP) && (n->flags & NETIF_FLAG_UP))) { - # ifdef ESP32 - etharp_gratuitous_r(n); - # else // ifdef ESP32 - etharp_gratuitous(n); - # endif // ifdef ESP32 - } - n = n->next; - } - STOP_TIMER(GRAT_ARP_STATS); -#endif // ifdef SUPPORT_ARP -} - -bool splitHostPortString(const String& hostPortString, String& host, uint16_t& port) { - port = 80; // Some default - int index_colon = hostPortString.indexOf(':'); - - if (index_colon >= 0) { - int32_t port_tmp; - - if (!validIntFromString(hostPortString.substring(index_colon + 1), port_tmp)) { - return false; - } - - if ((port_tmp < 0) || (port_tmp > 65535)) { return false; } - port = port_tmp; - host = hostPortString.substring(0, index_colon); - } else { - // No port nr defined. - host = hostPortString; - } - return true; -} - -bool splitUserPass_HostPortString(const String& hostPortString, String& user, String& pass, String& host, uint16_t& port) -{ - const int pos_at = hostPortString.indexOf('@'); - - if (pos_at != -1) { - user = hostPortString.substring(0, pos_at); - const int pos_colon = user.indexOf(':'); - - if (pos_colon != -1) { - pass = user.substring(pos_colon + 1); - user = user.substring(0, pos_colon); - } - return splitHostPortString(hostPortString.substring(pos_at + 1), host, port); - } - return splitHostPortString(hostPortString, host, port); -} - -// Split a full URL like "http://hostname:port/path/file.htm" -// Return value is everything after the hostname:port section (including /) -String splitURL(const String& fullURL, String& user, String& pass, String& host, uint16_t& port, String& file) { - int starthost = fullURL.indexOf(F("://")); - - if (starthost == -1) { - starthost = 0; - } else { - starthost += 3; - } - const int endhost = fullURL.indexOf('/', starthost); - splitUserPass_HostPortString(fullURL.substring(starthost, endhost), user, pass, host, port); - - if (endhost == -1) { - return EMPTY_STRING; - } - - int startfile = fullURL.lastIndexOf('/'); - - if (startfile >= 0) { - file = fullURL.substring(startfile); - } - return fullURL.substring(endhost); -} - -String get_user_agent_string() { - static unsigned int agent_size = 20; - String userAgent; - - userAgent.reserve(agent_size); - userAgent += F("ESP Easy/"); - userAgent += get_build_nr(); - userAgent += '/'; - userAgent += get_build_date(); - userAgent += ' '; - userAgent += get_build_time(); - agent_size = userAgent.length(); - return userAgent; -} - -bool splitHeaders(int& strpos, const String& multiHeaders, String& name, String& value) { - if (strpos < 0) { - return false; - } - int colonPos = multiHeaders.indexOf(':', strpos); - - if (colonPos < 0) { - return false; - } - name = multiHeaders.substring(strpos, colonPos); - int valueEndPos = multiHeaders.indexOf('\n', colonPos + 1); - - if (valueEndPos < 0) { - value = multiHeaders.substring(colonPos + 1); - strpos = -1; - } else { - value = multiHeaders.substring(colonPos + 1, valueEndPos); - strpos = valueEndPos + 1; - } - value.replace('\r', ' '); - value.trim(); - return true; -} - -String extractParam(const String& authReq, const String& param, const char delimit) { - int _begin = authReq.indexOf(param); - - if (_begin == -1) { return EMPTY_STRING; } - return authReq.substring(_begin + param.length(), authReq.indexOf(delimit, _begin + param.length())); -} - -#if FEATURE_HTTP_CLIENT -String getCNonce(const int len) { - static const char alphanum[] = "0123456789" - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz"; - String s; - - for (int i = 0; i < len; ++i) { - // FIXME TD-er: Is this "-1" correct? The mod operator makes sure we never reach the sizeof index - s += alphanum[rand() % (sizeof(alphanum) - 1)]; - } - - return s; -} - -String getDigestAuth(const String& authReq, - const String& username, - const String& password, - const String& method, - const String& uri, - unsigned int counter) { - // extracting required parameters for RFC 2069 simpler Digest - const String realm = extractParam(authReq, F("realm=\""), '"'); - const String nonce = extractParam(authReq, F("nonce=\""), '"'); - const String cNonce = getCNonce(8); - - char nc[9]; - - snprintf(nc, sizeof(nc), "%08x", counter); - - // parameters for the RFC 2617 newer Digest - MD5Builder md5; - - md5.begin(); - md5.add(username + ':' + realm + ':' + password); // md5 of the user:realm:user - md5.calculate(); - const String h1 = md5.toString(); - - md5.begin(); - md5.add(method + ':' + uri); - md5.calculate(); - const String h2 = md5.toString(); - - md5.begin(); - md5.add(h1 + ':' + nonce + ':' + String(nc) + ':' + cNonce + F(":auth:") + h2); - md5.calculate(); - - // return authorization - return strformat( - F("Digest username=\"%s\"" - ", realm=\"%s\"" - ", nonce=\"%s\"" - ", uri=\"%s\"" - ", algorithm=\"MD5\", qop=auth, nc=%s, cnonce=\"%s\"" - ", response=\"%s\""), - username.c_str(), - realm.c_str(), - nonce.c_str(), - uri.c_str(), - nc, - cNonce.c_str(), - md5.toString().c_str()); // response -} - -#ifndef BUILD_NO_DEBUG -void log_http_result(const HTTPClient& http, - const String & logIdentifier, - const String & host, - const String & HttpMethod, - int httpCode, - const String & response) -{ - uint8_t loglevel = LOG_LEVEL_ERROR; - bool success = false; - - // HTTP codes: - // 1xx Informational response - // 2xx Success - if ((httpCode >= 100) && (httpCode < 300)) { - loglevel = LOG_LEVEL_INFO; - success = true; - } - - if (loglevelActiveFor(loglevel)) { - String log = strformat(F("HTTP : %s %s %s"), - logIdentifier.c_str(), host.c_str(), HttpMethod.c_str()); - - if (!success) { - log += F("failed "); - } - log += concat(F("HTTP code: "), httpCode); - - if (!success) { - log += ' '; - log += http.errorToString(httpCode); - } - - if (response.length() > 0) { - log += concat(F(" Received reply: "), response.substring(0, 100)); // Returned string may be huge, so only log the first part. - } - addLogMove(loglevel, log); - } -} -#endif - -int http_authenticate(const String& logIdentifier, - WiFiClient & client, - HTTPClient & http, - uint16_t timeout, - const String& user, - const String& pass, - const String& host, - uint16_t port, - const String& uri, - const String& HttpMethod, - const String& header, - const String& postStr, - bool must_check_reply) -{ - if (!uri.startsWith(F("/"))) { - return http_authenticate( - logIdentifier, - client, - http, - timeout, - user, - pass, - host, - port, - concat(F("/"), uri), - HttpMethod, - header, - postStr, - must_check_reply); - } - int httpCode = 0; - const bool hasCredentials = !user.isEmpty() && !pass.isEmpty(); - - if (hasCredentials) { - must_check_reply = true; - http.setAuthorization(user.c_str(), pass.c_str()); - } else { - http.setAuthorization(""); // Clear Basic authorization -#ifdef ESP32 - http.setAuthorizationType(""); // Default type is "Basic" -#endif - } - http.setTimeout(timeout); - http.setUserAgent(get_user_agent_string()); - - if (Settings.SendToHTTP_follow_redirects()) { - http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); - http.setRedirectLimit(2); - } - - #ifdef MUSTFIX_CLIENT_TIMEOUT_IN_SECONDS - - // See: https://github.com/espressif/arduino-esp32/pull/6676 - client.setTimeout((timeout + 500) / 1000); // in seconds!!!! - Client *pClient = &client; - pClient->setTimeout(timeout); - #else // ifdef MUSTFIX_CLIENT_TIMEOUT_IN_SECONDS - client.setTimeout(timeout); // in msec as it should be! - #endif // ifdef MUSTFIX_CLIENT_TIMEOUT_IN_SECONDS - - // Add request header as fall back. - // When adding another "accept" header, it may be interpreted as: - // "if you have XXX, send it; or failing that, just give me what you've got." - http.addHeader(F("Accept"), F("*/*;q=0.1")); - - // Add client IP - http.addHeader(F("X-Forwarded-For"), formatIP(NetworkLocalIP())); - - delay(0); - scrubDNS(); -#if defined(CORE_POST_2_6_0) || defined(ESP32) - http.begin(client, host, port, uri, false); // HTTP -#else // if defined(CORE_POST_2_6_0) || defined(ESP32) - http.begin(client, host, port, uri); -#endif // if defined(CORE_POST_2_6_0) || defined(ESP32) - - const char *keys[] = { "WWW-Authenticate" }; - http.collectHeaders(keys, 1); - - { - int headerpos = 0; - String name, value; - - while (splitHeaders(headerpos, header, name, value)) { - // Disabled the check to exclude "Authorization", due to: - // https://github.com/letscontrolit/ESPEasy/issues/4364 - // Check was added for: https://github.com/letscontrolit/ESPEasy/issues/4355 - // However, I doubt this was the actual bug. More likely the supplied credential strings were not entirely empty for whatever reason. - // - // Work-around to not add Authorization header since the HTTPClient code - // only ignores this when base64Authorication is set. - -// if (!name.equalsIgnoreCase(F("Authorization"))) { - http.addHeader(name, value); -// } - } - } - - // start connection and send HTTP header (and body) - if (equals(HttpMethod, F("HEAD")) || equals(HttpMethod, F("GET"))) { - httpCode = http.sendRequest(HttpMethod.c_str()); - } else { - httpCode = http.sendRequest(HttpMethod.c_str(), postStr); - } - - // Check to see if we need to try digest auth - if ((httpCode == 401) && must_check_reply) { - const String authReq = http.header(String(F("WWW-Authenticate")).c_str()); - - if (authReq.indexOf(F("Digest")) != -1) { - // Use Digest authorization - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - addLogMove(LOG_LEVEL_INFO, concat(F("HTTP : Start Digest Authorization for "), host)); - } - - http.setAuthorization(""); // Clear Basic authorization -#ifdef ESP32 - http.setAuthorizationType(""); // Default type is "Basic" and "Digest" is already part of the string generated by getDigestAuth() -#endif - const String authorization = getDigestAuth(authReq, user, pass, F("GET"), uri, 1); - - http.end(); -#if defined(CORE_POST_2_6_0) || defined(ESP32) - http.begin(client, host, port, uri, false); // HTTP, not HTTPS -#else // if defined(CORE_POST_2_6_0) || defined(ESP32) - http.begin(client, host, port, uri); -#endif // if defined(CORE_POST_2_6_0) || defined(ESP32) - - http.addHeader(F("Authorization"), authorization); - - // start connection and send HTTP header (and body) - if (equals(HttpMethod, F("HEAD")) || equals(HttpMethod, F("GET"))) { - httpCode = http.sendRequest(HttpMethod.c_str()); - } else { - httpCode = http.sendRequest(HttpMethod.c_str(), postStr); - } - } - } - - if (!must_check_reply) { - // There are services which do not send an ack. - // So if the return code matches a read timeout, we change it into HTTP code 200 - if (httpCode == HTTPC_ERROR_READ_TIMEOUT) { - httpCode = 200; - } - } - - if (Settings.UseRules) { - // Generate event with the HTTP return code - // e.g. http#hostname=401 - eventQueue.addMove(strformat(F("http#%s=%d"), host.c_str(), httpCode)); - -// ----This way to the custom response parser---------------- -#if RESPONSE_PARSER_SUPPORT - eventFromResponse(host, httpCode, uri, http); -#endif - } -// ----------------------------------------------------------- - -#ifndef BUILD_NO_DEBUG - log_http_result(http, logIdentifier, host + ':' + port, HttpMethod, httpCode, EMPTY_STRING); -#endif - return httpCode; -} - -String send_via_http(const String& logIdentifier, - uint16_t timeout, - const String& user, - const String& pass, - const String& host, - uint16_t port, - const String& uri, - const String& HttpMethod, - const String& header, - const String& postStr, - int & httpCode, - bool must_check_reply) { - WiFiClient client; - HTTPClient http; - http.setReuse(false); - - httpCode = http_authenticate( - logIdentifier, - client, - http, - timeout, - user, - pass, - host, - port, - uri, - HttpMethod, - header, - postStr, - must_check_reply); - - String response; - - if ((httpCode > 0) && must_check_reply) { - response = http.getString(); -#ifndef BUILD_NO_DEBUG - if (!response.isEmpty()) { - log_http_result(http, logIdentifier, host, HttpMethod, httpCode, response); - } -#endif - } - http.end(); - // http.end() does not call client.stop() if it is no longer connected. - // However the client may still keep its internal state which may prevent - // future connections to the same host until there has been a connection to another host inbetween. - client.stop(); - return response; -} -#endif // FEATURE_HTTP_CLIENT - -#if FEATURE_DOWNLOAD - -// FIXME TD-er: Must set the timeout somewhere -# ifndef DOWNLOAD_FILE_TIMEOUT - # define DOWNLOAD_FILE_TIMEOUT 2000 -# endif // ifndef DOWNLOAD_FILE_TIMEOUT - -// Download a file from a given URL and save to a local file named "file_save" -// If the URL ends with a /, the file part will be assumed the same as file_save. -// If file_save is empty, the file part from the URL will be used as local file name. -// Return true when successful. -bool downloadFile(const String& url, String file_save) { - String error; - - return downloadFile(url, file_save, EMPTY_STRING, EMPTY_STRING, error); -} - -// User and Pass may be updated if they occur in the hostname part. -// Thus have to be copied instead of const reference. -bool start_downloadFile(WiFiClient & client, - HTTPClient & http, - const String& url, - String & file_save, - String user, - String pass, - String & error) { - String host, file; - uint16_t port; - String uri = splitURL(url, user, pass, host, port, file); - - if (file_save.isEmpty()) { - file_save = file; - } else if ((file.isEmpty()) && uri.endsWith("/")) { - // file = file_save; - uri += file_save; - } -# ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - addLogMove(LOG_LEVEL_DEBUG, strformat(F("downloadFile: URL: %s decoded: %s:%d%s"), - url.c_str(), host.c_str(), port, uri.c_str())); - } -# endif // ifndef BUILD_NO_DEBUG - - if (file_save.isEmpty()) { - error = F("Empty filename"); - addLog(LOG_LEVEL_ERROR, error); - return false; - } - - const int httpCode = http_authenticate( - F("DownloadFile"), - client, - http, - DOWNLOAD_FILE_TIMEOUT, - user, - pass, - host, - port, - uri, - F("GET"), - EMPTY_STRING, // header - EMPTY_STRING, // postStr - true // must_check_reply - ); - - if (httpCode != HTTP_CODE_OK) { - error = strformat(F("HTTP code: %d %s"), httpCode, url.c_str()); - - addLog(LOG_LEVEL_ERROR, error); - http.end(); - client.stop(); - return false; - } - return true; -} - -bool downloadFile(const String& url, String file_save, const String& user, const String& pass, String& error) { - WiFiClient client; - HTTPClient http; - http.setReuse(false); - - if (!start_downloadFile(client, http, url, file_save, user, pass, error)) { - return false; - } - - if (fileExists(file_save)) { - error = concat(F("File exists: "), file_save); - addLog(LOG_LEVEL_ERROR, error); - http.end(); - client.stop(); - return false; - } - - long len = http.getSize(); - fs::File f = tryOpenFile(file_save, "w"); - - if (f) { - const size_t downloadBuffSize = 256; - uint8_t buff[downloadBuffSize]; - size_t bytesWritten = 0; - unsigned long timeout = millis() + DOWNLOAD_FILE_TIMEOUT; - - // get tcp stream - WiFiClient *stream = &client; - - // read all data from server - while (http.connected() && (len > 0 || len == -1)) { - // read up to downloadBuffSize at a time. - size_t bytes_to_read = downloadBuffSize; - - if ((len > 0) && (len < static_cast(bytes_to_read))) { - bytes_to_read = len; - } - const size_t c = stream->readBytes(buff, bytes_to_read); - - if (c > 0) { - timeout = millis() + DOWNLOAD_FILE_TIMEOUT; - - if (f.write(buff, c) != c) { - error = strformat(F("Error saving file: %s %d Bytes written"), file_save.c_str(), bytesWritten); - addLog(LOG_LEVEL_ERROR, error); - http.end(); - client.stop(); - return false; - } - bytesWritten += c; - - if (len > 0) { len -= c; } - } - - if (timeOutReached(timeout)) { - error = concat(F("Timeout: "), file_save); - addLog(LOG_LEVEL_ERROR, error); - delay(0); - http.end(); - client.stop(); - return false; - } - delay(0); - } - f.close(); - http.end(); - client.stop(); - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - addLog(LOG_LEVEL_INFO, strformat(F("downloadFile: %s Success"), file_save.c_str())); - } - return true; - } - http.end(); - client.stop(); - error = concat(F("Failed to open file for writing: "), file_save); - addLog(LOG_LEVEL_ERROR, error); - return false; -} - -bool downloadFirmware(String filename, String& error) -{ - String baseurl, user, pass; -# if FEATURE_CUSTOM_PROVISIONING - MakeProvisioningSettings(ProvisioningSettings); - - if (ProvisioningSettings.get()) { - loadProvisioningSettings(*ProvisioningSettings); - if (!ProvisioningSettings->allowedFlags.allowFetchFirmware) { - error = F("Not Allowed"); - return false; - } - baseurl = ProvisioningSettings->url; - user = ProvisioningSettings->user; - pass = ProvisioningSettings->pass; - } -# endif // if FEATURE_CUSTOM_PROVISIONING - - const String fullUrl = joinUrlFilename(baseurl, filename); - - return downloadFirmware(fullUrl, filename, user, pass, error); -} - -bool downloadFirmware(const String& url, String& file_save, String& user, String& pass, String& error) -{ - WiFiClient client; - HTTPClient http; - error.clear(); - - if (!start_downloadFile(client, http, url, file_save, user, pass, error)) { - return false; - } - - int len = http.getSize(); - - if (Update.begin(len, U_FLASH, Settings.Pin_status_led, Settings.Pin_status_led_Inversed ? LOW : HIGH)) { - const size_t downloadBuffSize = 256; - uint8_t buff[downloadBuffSize]; - size_t bytesWritten = 0; - unsigned long timeout = millis() + DOWNLOAD_FILE_TIMEOUT; - - // get tcp stream - WiFiClient *stream = &client; - - while (error.isEmpty() && http.connected() && (len > 0 || len == -1)) { - // read up to downloadBuffSize at a time. - size_t bytes_to_read = downloadBuffSize; - - if ((len > 0) && (len < static_cast(bytes_to_read))) { - bytes_to_read = len; - } - const size_t c = stream->readBytes(buff, bytes_to_read); - - if (c > 0) { - timeout = millis() + DOWNLOAD_FILE_TIMEOUT; - - if (Update.write(buff, c) != c) { - error = strformat(F("Error saving firmware update: %s %d Bytes written"), - file_save.c_str(), bytesWritten); - break; - } - bytesWritten += c; - - if (len > 0) { len -= c; } - } - - if (timeOutReached(timeout)) { - error = concat(F("Timeout: "), file_save); - break; - } - - if (!UseRTOSMultitasking) { - // On ESP32 the schedule is executed on the 2nd core. - Scheduler.handle_schedule(); - } - backgroundtasks(); - } - } - http.end(); - client.stop(); - - if (error.isEmpty() && loglevelActiveFor(LOG_LEVEL_INFO)) { - addLog(LOG_LEVEL_INFO, strformat(F("downloadFile: %s Success"), file_save.c_str())); - } - - uint8_t errorcode = 0; - if (!Update.end()) { - errorcode = Update.getError(); -#ifdef ESP32 - const __FlashStringHelper * err_fstr = F("Unknown"); - switch (errorcode) { - case UPDATE_ERROR_OK: err_fstr = F("OK"); break; - case UPDATE_ERROR_WRITE: err_fstr = F("WRITE"); break; - case UPDATE_ERROR_ERASE: err_fstr = F("ERASE"); break; - case UPDATE_ERROR_READ: err_fstr = F("READ"); break; - case UPDATE_ERROR_SPACE: err_fstr = F("SPACE"); break; - case UPDATE_ERROR_SIZE: err_fstr = F("SIZE"); break; - case UPDATE_ERROR_STREAM: err_fstr = F("STREAM"); break; - case UPDATE_ERROR_MD5: err_fstr = F("MD5"); break; - case UPDATE_ERROR_MAGIC_BYTE: err_fstr = F("MAGIC_BYTE"); break; - case UPDATE_ERROR_ACTIVATE: err_fstr = F("ACTIVATE"); break; - case UPDATE_ERROR_NO_PARTITION: err_fstr = F("NO_PARTITION"); break; - case UPDATE_ERROR_BAD_ARGUMENT: err_fstr = F("BAD_ARGUMENT"); break; - case UPDATE_ERROR_ABORT: err_fstr = F("ABORT"); break; - } - error += concat(F(" Error: "), err_fstr); -#else - error += concat(F(" Error: "), errorcode); -#endif - } else { - if (Settings.UseRules) { - eventQueue.addMove(concat(F("ProvisionFirmware#success="), file_save)); - } - return true; - } - - backgroundtasks(); - if (loglevelActiveFor(LOG_LEVEL_ERROR)) { - addLog(LOG_LEVEL_ERROR, concat(F("Failed update firmware: "), error)); - } - - if (Settings.UseRules) { - eventQueue.addMove(concat(F("ProvisionFirmware#failed="), file_save)); - } - return false; -} - -String joinUrlFilename(const String& url, String& filename) -{ - String fullUrl; - - fullUrl.reserve(url.length() + filename.length() + 1); // May need to add an extra slash - fullUrl = url; - fullUrl = parseTemplate(fullUrl, true); // URL encode - - // URLEncode may also encode the '/' into "%2f" - // FIXME TD-er: Can this really occur? - fullUrl.replace(F("%2f"), F("/")); - - while (filename.startsWith(F("/"))) { - filename = filename.substring(1); - } - - if (!fullUrl.endsWith(F("/"))) { - fullUrl += F("/"); - } - fullUrl += filename; - return fullUrl; -} - -#endif // if FEATURE_DOWNLOAD - +#include "../Helpers/Networking.h" + +#include "../Commands/ExecuteCommand.h" +#include "../CustomBuild/CompiletimeDefines.h" +#include "../DataStructs/NodeStruct.h" +#include "../DataStructs/TimingStats.h" +#include "../DataTypes/EventValueSource.h" +#include "../ESPEasyCore/ESPEasy_Log.h" +#include "../ESPEasyCore/ESPEasy_backgroundtasks.h" +#include "../ESPEasyCore/ESPEasyEth.h" +#include "../ESPEasyCore/ESPEasyNetwork.h" +#include "../ESPEasyCore/ESPEasyWifi.h" +#include "../ESPEasyCore/Serial.h" +#include "../Globals/ESPEasyEthEvent.h" +#include "../Globals/ESPEasyWiFiEvent.h" +#include "../Globals/ESPEasy_Scheduler.h" + +#ifdef USES_ESPEASY_NOW +#include "../Globals/ESPEasy_now_handler.h" +#endif + +#include "../Globals/EventQueue.h" +#include "../Globals/NetworkState.h" +#include "../Globals/Nodes.h" +#include "../Globals/ResetFactoryDefaultPref.h" +#include "../Globals/Settings.h" +#include "../Helpers/ESPEasy_Storage.h" +#include "../Helpers/ESPEasy_time_calc.h" +#include "../Helpers/Hardware.h" +#include "../Helpers/Misc.h" +#include "../Helpers/Network.h" +#include "../Helpers/Numerical.h" +#include "../Helpers/StringConverter.h" +#include "../Helpers/StringProvider.h" + +#if RESPONSE_PARSER_SUPPORT +#include "../Helpers/HTTPResponseParser.h" +#endif + +#include "../../ESPEasy-Globals.h" + +#include +#include +#include // for getDigestAuth + +#include + +#include + + +// Generic Networking routines + +// Syslog +// UDP system messaging +// SSDP +// #if LWIP_VERSION_MAJOR == 2 +#define IPADDR2STR(addr) (uint8_t)((uint32_t)addr & 0xFF), (uint8_t)(((uint32_t)addr >> 8) & 0xFF), \ + (uint8_t)(((uint32_t)addr >> 16) & 0xFF), (uint8_t)(((uint32_t)addr >> 24) & 0xFF) + +// #endif + +#include + +#ifdef ESP8266 +#include +#include +#include +#include +#endif + +#ifdef SUPPORT_ARP +# include + +# ifdef ESP32 +# include +# include + +void _etharp_gratuitous_func(struct netif *netif) { + etharp_gratuitous(netif); +} + +void etharp_gratuitous_r(struct netif *netif) { + tcpip_callback_with_block((tcpip_callback_fn)_etharp_gratuitous_func, netif, 0); +} + +# endif // ifdef ESP32 + +#endif // ifdef SUPPORT_ARP + +#if FEATURE_DOWNLOAD +# ifdef ESP8266 +# include +# endif // ifdef ESP8266 +# ifdef ESP32 +# include +# include +# endif // ifdef ESP32 +#endif // if FEATURE_DOWNLOAD + +#include + +/*********************************************************************************************\ + Syslog client +\*********************************************************************************************/ +void sendSyslog(uint8_t logLevel, const String& message) +{ + if ((Settings.Syslog_IP[0] != 0) && NetworkConnected()) + { + IPAddress broadcastIP(Settings.Syslog_IP[0], Settings.Syslog_IP[1], Settings.Syslog_IP[2], Settings.Syslog_IP[3]); + + FeedSW_watchdog(); + + if (portUDP.beginPacket(broadcastIP, Settings.SyslogPort) == 0) { + // problem resolving the hostname or port + return; + } + unsigned int prio = Settings.SyslogFacility * 8; + + if (logLevel == LOG_LEVEL_ERROR) { + prio += 3; // syslog error + } + else if (logLevel == LOG_LEVEL_INFO) { + prio += 5; // syslog notice + } + else { + prio += 7; + } + + // An RFC3164 compliant message must be formated like : "[TimeStamp ]Hostname TaskName: Message" + + // Using Settings.Name as the Hostname (Hostname must NOT content space) + { + String header; + header += '<'; + header += prio; + header += '>'; + header += NetworkCreateRFCCompliantHostname(true); + header += F(" EspEasy: "); + header.trim(); + header.replace(' ', '_'); + + #ifdef ESP8266 + portUDP.write(header.c_str(), header.length()); + #endif // ifdef ESP8266 + #ifdef ESP32 + portUDP.write(reinterpret_cast(header.c_str()), header.length()); + #endif // ifdef ESP32 + } + + #ifdef ESP8266 + portUDP.write(message.c_str(), message.length()); + #endif // ifdef ESP8266 + #ifdef ESP32 + portUDP.write(reinterpret_cast(message.c_str()), message.length()); + #endif // ifdef ESP32 + + portUDP.endPacket(); + FeedSW_watchdog(); + delay(0); + } +} + +#if FEATURE_ESPEASY_P2P + +/*********************************************************************************************\ + Send event using UDP message +\*********************************************************************************************/ +void SendUDPCommand(uint8_t destUnit, const char *data, uint8_t dataLength) +{ + if (!NetworkConnected(10)) { + return; + } + + if (destUnit != 0) + { + sendUDP(destUnit, (const uint8_t *)data, dataLength); + delay(10); + } else { + for (auto it = Nodes.begin(); it != Nodes.end(); ++it) { + if (it->first != Settings.Unit) { + sendUDP(it->first, (const uint8_t *)data, dataLength); + delay(10); + } + } + } + delay(50); +} + +/*********************************************************************************************\ + Send UDP message to specific unit (unit 255=broadcast) +\*********************************************************************************************/ +void sendUDP(uint8_t unit, const uint8_t *data, uint8_t size) +{ + if (!NetworkConnected(10)) { + return; + } + + IPAddress remoteNodeIP = getIPAddressForUnit(unit); + + if (remoteNodeIP[0] == 0) { + return; + } + +# ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG_MORE)) { + addLogMove(LOG_LEVEL_DEBUG_MORE, strformat( + F("UDP : Send UDP message to %d (%s)"), + unit, + remoteNodeIP.toString().c_str() + )); + } +# endif // ifndef BUILD_NO_DEBUG + + statusLED(true); + FeedSW_watchdog(); + portUDP.beginPacket(remoteNodeIP, Settings.UDPPort); + portUDP.write(data, size); + portUDP.endPacket(); + FeedSW_watchdog(); + delay(0); +} + +/*********************************************************************************************\ + Update UDP port (ESPEasy propiertary protocol) +\*********************************************************************************************/ +void updateUDPport(bool force) +{ + static uint16_t lastUsedUDPPort = 0; + + if (!force && Settings.UDPPort == lastUsedUDPPort) { + return; + } + + if (lastUsedUDPPort != 0) { + portUDP.stop(); + lastUsedUDPPort = 0; + } + + if (!NetworkConnected()) { + return; + } + + if (Settings.UDPPort != 0) { + if (portUDP.begin(Settings.UDPPort) == 0) { + if (loglevelActiveFor(LOG_LEVEL_ERROR)) { + addLogMove(LOG_LEVEL_ERROR, concat(F("UDP : Cannot bind to ESPEasy p2p UDP port "), Settings.UDPPort)); + } + } else { + lastUsedUDPPort = Settings.UDPPort; + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLogMove(LOG_LEVEL_INFO, concat(F("UDP : Start listening on port "), Settings.UDPPort)); + } + } + } +} + +/*********************************************************************************************\ + Check UDP messages (ESPEasy propiertary protocol) +\*********************************************************************************************/ +boolean runningUPDCheck = false; +void checkUDP() +{ + if (!NetworkConnected()) + return; + if (Settings.UDPPort == 0) { + return; + } + + if (runningUPDCheck) { + return; + } + START_TIMER + + runningUPDCheck = true; + + // UDP events + int packetSize = portUDP.parsePacket(); + + if (packetSize > 0 /*&& portUDP.remotePort() == Settings.UDPPort*/) + { + statusLED(true); + + IPAddress remoteIP = portUDP.remoteIP(); + + if (portUDP.remotePort() == 123) + { + // unexpected NTP reply, drop for now... + while (portUDP.available()) { + // Do not call portUDP.flush() as that's meant to sending the packet (on ESP8266) + portUDP.read(); + } + + runningUPDCheck = false; + return; + } + + // UDP_PACKETSIZE_MAX should be as small as possible but still enough to hold all + // data for PLUGIN_UDP_IN or CPLUGIN_UDP_IN calls + // This node may also receive other UDP packets which may be quite large + // and then crash due to memory allocation failures + if ((packetSize >= 2) && (packetSize < UDP_PACKETSIZE_MAX)) { + // Allocate buffer to process packet. + // Resize it to be 1 byte larger so we can 0-terminate it + // in case it is some plain text string + std::vector packetBuffer; + packetBuffer.resize(packetSize + 1); + + if (packetBuffer.size() >= static_cast(packetSize)) { + memset(&packetBuffer[0], 0, packetSize + 1); + int len = portUDP.read(&packetBuffer[0], packetSize); + + if (len >= 2) { + if (static_cast(packetBuffer[0]) != 255) + { + packetBuffer[len] = 0; + # ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + addLogMove(LOG_LEVEL_DEBUG, + strformat(F("UDP : %s Command: %s"), + formatIP(remoteIP, true).c_str(), + wrapWithQuotesIfContainsParameterSeparatorChar(String(&packetBuffer[0])).c_str() + )); + } + #endif + ExecuteCommand_all({EventValueSource::Enum::VALUE_SOURCE_SYSTEM, &packetBuffer[0]}, true); + } + else + { + // binary data! + switch (packetBuffer[1]) + { + case 1: // sysinfo message + { + if (len < 13) { + break; + } + int copy_length = sizeof(NodeStruct); + // Older versions sent 80 bytes, regardless of the size of NodeStruct + // Make sure the extra data received is ignored as it was also not initialized + if (len == 80) { + copy_length = 56; + } + + if (copy_length > (len - 2)) { + copy_length = (len - 2); + } + NodeStruct received; + memcpy(&received, &packetBuffer[2], copy_length); + + if (received.validate(remoteIP)) { + Nodes.addNode(received); // Create a new element when not present + +# ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG_MORE)) { + addLogMove(LOG_LEVEL_DEBUG_MORE, + strformat(F("UDP : %s (%d) %s,%s,%d"), + formatIP(remoteIP).c_str(), + received.unit, + received.STA_MAC().toString().c_str(), + formatIP(received.IP(), true).c_str(), + received.unit)); + } + +#endif // ifndef BUILD_NO_DEBUG + } + break; + } + + default: + { + struct EventStruct TempEvent; + TempEvent.Data = reinterpret_cast(&packetBuffer[0]); + TempEvent.Par1 = remoteIP[3]; + TempEvent.Par2 = len; + String dummy; + // TD-er: Disabled the PLUGIN_UDP_IN call as we don't have any plugin using this. + //PluginCall(PLUGIN_UDP_IN, &TempEvent, dummy); + CPluginCall(CPlugin::Function::CPLUGIN_UDP_IN, &TempEvent); + break; + } + } + } + } + } + } + } + + // Flush any remaining content of the packet. + while (portUDP.available()) { + // Do not call portUDP.flush() as that's meant to sending the packet (on ESP8266) + portUDP.read(); + } + runningUPDCheck = false; + STOP_TIMER(CHECK_UDP); +} + +/*********************************************************************************************\ + Get formatted IP address for unit + formatcodes: 0 = default toString(), 1 = empty string when invalid, 2 = 0 when invalid +\*********************************************************************************************/ +String formatUnitToIPAddress(uint8_t unit, uint8_t formatCode) { + IPAddress unitIPAddress = getIPAddressForUnit(unit); + + if (unitIPAddress[0] == 0) { // Invalid? + switch (formatCode) { + case 1: // Return empty string + { + return EMPTY_STRING; + } + case 2: // Return "0" + { + return String('0'); + } + } + } + return formatIP(unitIPAddress); +} + +/*********************************************************************************************\ + Get IP address for unit +\*********************************************************************************************/ +IPAddress getIPAddressForUnit(uint8_t unit) { + if (unit == 255) { + const IPAddress ip(255, 255, 255, 255); + return ip; + } + auto it = Nodes.find(unit); + + if (it == Nodes.end() || it->second.ip[0] == 0) { + IPAddress ip; + return ip; + } +#if FEATURE_USE_IPV6 +/* + // FIXME TD-er: for now do not try to send to IPv6 + if (it->second.hasIPv6_mac_based_link_local) { + return it->second.IPv6_link_local(); + } + if (it->second.hasIPv6_mac_based_link_global) { + return it->second.IPv6_global(); + } +*/ +#endif + return it->second.IP(); +} + + +String getNameForUnit(uint8_t unit) { + auto it = Nodes.find(unit); + + if (it == Nodes.end() || it->second.getNodeName().isEmpty()) { + return EMPTY_STRING; + } + return it->second.getNodeName(); +} + +long getAgeForUnit(uint8_t unit) { + auto it = Nodes.find(unit); + + if (it == Nodes.end()) { + return -1000; // milliseconds, negative == unknown + } + return static_cast(it->second.getAge()); +} + +uint16_t getBuildnrForUnit(uint8_t unit) { + auto it = Nodes.find(unit); + + if (it == Nodes.end() || it->second.build == 0) { + return 0; + } + return it->second.build; +} + +float getLoadForUnit(uint8_t unit) { + auto it = Nodes.find(unit); + + if (it == Nodes.end()) { + return 0.0f; + } + return it->second.getLoad(); +} + +uint8_t getTypeForUnit(uint8_t unit) { + auto it = Nodes.find(unit); + + if (it == Nodes.end()) { + return 0; + } + return it->second.nodeType; +} + +const __FlashStringHelper* getTypeStringForUnit(uint8_t unit) { + auto it = Nodes.find(unit); + + if (it == Nodes.end()) { + return F(""); + } + return it->second.getNodeTypeDisplayString(); +} + +/*********************************************************************************************\ + Refresh aging for remote units, drop if too old... +\*********************************************************************************************/ +void refreshNodeList() +{ + unsigned long max_age; + const unsigned long max_age_allowed = 10 * 60 * 1000; // 10 minutes + + Nodes.refreshNodeList(max_age_allowed, max_age); + + #ifdef USES_ESPEASY_NOW + // FIXME TD-er: Do not perform regular scans on ESP32 as long as we cannot scan per channel + if (!Nodes.isEndpoint()) { + WifiScan(true, Nodes.getESPEasyNOW_channel()); + } + #endif + + if (max_age > (0.75 * max_age_allowed)) { + Scheduler.sendGratuitousARP_now(); + } + sendSysInfoUDP(1); + #ifdef USES_ESPEASY_NOW + if (Nodes.recentlyBecameDistanceZero()) { + // Send to all channels + ESPEasy_now_handler.sendDiscoveryAnnounce(-1); + } else { + ESPEasy_now_handler.sendDiscoveryAnnounce(Nodes.getESPEasyNOW_channel()); + } + ESPEasy_now_handler.sendNTPquery(); + ESPEasy_now_handler.sendTraceRoute(); + #endif // ifdef USES_ESPEASY_NOW +} + +/*********************************************************************************************\ + Broadcast system info to other nodes. (to update node lists) +\*********************************************************************************************/ +void sendSysInfoUDP(uint8_t repeats) +{ + if ((Settings.UDPPort == 0) || !NetworkConnected(10)) { + return; + } + + // 1 byte 'binary token 255' + // 1 byte id '1' + // NodeStruct object (packed data struct) + + // send my info to the world... +# ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_DEBUG_MORE, F("UDP : Send Sysinfo message")); +# endif // ifndef BUILD_NO_DEBUG + + const NodeStruct *thisNode = Nodes.getThisNode(); + + if (thisNode == nullptr) { + // Should not happen + return; + } + + // Prepare UDP packet to send + constexpr size_t data_size = sizeof(NodeStruct) + 2; + uint8_t data[data_size] = {0}; + data[0] = 255; + data[1] = 1; + memcpy(&data[2], thisNode, sizeof(NodeStruct)); + + for (uint8_t counter = 0; counter < repeats; counter++) + { + statusLED(true); + + IPAddress broadcastIP(255, 255, 255, 255); + FeedSW_watchdog(); + portUDP.beginPacket(broadcastIP, Settings.UDPPort); + portUDP.write(data, data_size); + portUDP.endPacket(); + + if (counter < (repeats - 1)) { + // FIXME TD-er: Must use scheduler to send out messages, not using delay + delay(100); + } + } +} + +#endif // FEATURE_ESPEASY_P2P + +#if defined(ESP8266) + +# if FEATURE_SSDP + +/********************************************************************************************\ + Respond to HTTP XML requests for SSDP information + \*********************************************************************************************/ +void SSDP_schema() { + if (!NetworkConnected(10)) { + return; + } + + const IPAddress ip = NetworkLocalIP(); + const uint32_t chipId = ESP.getChipId(); + char uuid[64]; + + sprintf_P(uuid, PSTR("38323636-4558-4dda-9188-cda0e6%02x%02x%02x"), + (uint16_t)((chipId >> 16) & 0xff), + (uint16_t)((chipId >> 8) & 0xff), + (uint16_t)chipId & 0xff); + + web_server.client().print(F( + "HTTP/1.1 200 OK\r\n" + "Content-Type: text/xml\r\n" + "Connection: close\r\n" + "Access-Control-Allow-Origin: *\r\n" + "\r\n" + "" + "" + "" + "1" + "0" + "" + "http://")); + + web_server.client().print(formatIP(ip)); + web_server.client().print(F(":80/" + "" + "urn:schemas-upnp-org:device:BinaryLight:1" + "")); + web_server.client().print(Settings.getName()); + web_server.client().print(F("" + "/" + "")); + web_server.client().print(String(ESP.getChipId())); + web_server.client().print(F("" + "ESP Easy" + "")); + web_server.client().print(getValue(LabelType::GIT_BUILD)); + web_server.client().print(F("" + "http://www.letscontrolit.com" + "http://www.letscontrolit.com" + "http://www.letscontrolit.com" + "uuid:")); + web_server.client().print(String(uuid)); + web_server.client().print(F("" + "\r\n" + "\r\n")); +} + +/********************************************************************************************\ + Global SSDP stuff + \*********************************************************************************************/ + +UdpContext *_server; + +IPAddress _respondToAddr; +uint16_t _respondToPort; + +bool _pending; +unsigned short _delay; +unsigned long _process_time; +unsigned long _notify_time; + +# define SSDP_INTERVAL 1200 +# define SSDP_PORT 1900 +# define SSDP_METHOD_SIZE 10 +# define SSDP_URI_SIZE 2 +# define SSDP_BUFFER_SIZE 64 +# define SSDP_MULTICAST_TTL 2 + +static const IPAddress SSDP_MULTICAST_ADDR(239, 255, 255, 250); + + +/********************************************************************************************\ + Launch SSDP listener and send initial notify + \*********************************************************************************************/ +bool SSDP_begin() { + _pending = false; + + if (_server != nullptr) { + _server->unref(); + + // FIXME TD-er: Shouldn't this also call delete _server ? + + _server = nullptr; + } + + constexpr unsigned size = sizeof(UdpContext); + void *ptr = special_calloc(1, size); + + if (ptr != nullptr) { + _server = new (ptr) UdpContext; + } + + if (_server == nullptr) { + return false; + } + _server->ref(); + + ip_addr_t ifaddr; + + ifaddr.addr = NetworkLocalIP(); + ip_addr_t multicast_addr; + + multicast_addr.addr = (uint32_t)SSDP_MULTICAST_ADDR; + + if (igmp_joingroup(&ifaddr, &multicast_addr) != ERR_OK) { + return false; + } + +# ifdef CORE_POST_2_5_0 + + // Core 2.5.0 changed the signature of some UdpContext function. + if (!_server->listen(IP_ADDR_ANY, SSDP_PORT)) { + return false; + } + + _server->setMulticastInterface(&ifaddr); + _server->setMulticastTTL(SSDP_MULTICAST_TTL); + _server->onRx(&SSDP_update); + + if (!_server->connect(&multicast_addr, SSDP_PORT)) { + return false; + } +# else // ifdef CORE_POST_2_5_0 + + if (!_server->listen(*IP_ADDR_ANY, SSDP_PORT)) { + return false; + } + + _server->setMulticastInterface(ifaddr); + _server->setMulticastTTL(SSDP_MULTICAST_TTL); + _server->onRx(&SSDP_update); + + if (!_server->connect(multicast_addr, SSDP_PORT)) { + return false; + } +# endif // ifdef CORE_POST_2_5_0 + + SSDP_update(); + + return true; +} + +/********************************************************************************************\ + Send SSDP messages (notify & responses) + \*********************************************************************************************/ +void SSDP_send(uint8_t method) { + uint32_t ip = NetworkLocalIP(); + + // FIXME TD-er: Why create String objects of these flashstrings? + String _ssdp_response_template = F( + "HTTP/1.1 200 OK\r\n" + "EXT:\r\n" + "ST: upnp:rootdevice\r\n"); + + String _ssdp_notify_template = F( + "NOTIFY * HTTP/1.1\r\n" + "HOST: 239.255.255.250:1900\r\n" + "NT: upnp:rootdevice\r\n" + "NTS: ssdp:alive\r\n"); + + String _ssdp_packet_template = F( + "%s" // _ssdp_response_template / _ssdp_notify_template + "CACHE-CONTROL: max-age=%u\r\n" // SSDP_INTERVAL + "SERVER: Arduino/1.0 UPNP/1.1 ESPEasy/%u\r\n" // _modelNumber + "USN: uuid:%s\r\n" // _uuid + "LOCATION: http://%u.%u.%u.%u:80/ssdp.xml\r\n" // NetworkLocalIP(), + "\r\n"); + { + char uuid[64] = { 0 }; + uint32_t chipId = ESP.getChipId(); + sprintf_P(uuid, PSTR("38323636-4558-4dda-9188-cda0e6%02x%02x%02x"), + (uint16_t)((chipId >> 16) & 0xff), + (uint16_t)((chipId >> 8) & 0xff), + (uint16_t)chipId & 0xff); + + char *buffer = nullptr; + # ifdef USE_SECOND_HEAP + { + HeapSelectIram ephemeral; + buffer = new (std::nothrow) char[1460](); + } + # endif // ifdef USE_SECOND_HEAP + if (buffer == nullptr) { + buffer = new (std::nothrow) char[1460](); + } + + if (buffer == nullptr) { return; } + int len = snprintf(buffer, 1460, + _ssdp_packet_template.c_str(), + (method == 0) ? _ssdp_response_template.c_str() : _ssdp_notify_template.c_str(), + SSDP_INTERVAL, + Settings.Build, + uuid, + IPADDR2STR(&ip) + ); + + _server->append(buffer, len); + delete[] buffer; + } + + ip_addr_t remoteAddr; + uint16_t remotePort; + + if (method == 0) { + remoteAddr.addr = _respondToAddr; + remotePort = _respondToPort; + } else { + remoteAddr.addr = SSDP_MULTICAST_ADDR; + remotePort = SSDP_PORT; + } + _server->send(&remoteAddr, remotePort); + statusLED(true); +} + +/********************************************************************************************\ + SSDP message processing + \*********************************************************************************************/ +void SSDP_update() { + if (!_pending && _server->next()) { + ssdp_method_t method = NONE; + + _respondToAddr = _server->getRemoteAddress(); + _respondToPort = _server->getRemotePort(); + + typedef enum { METHOD, URI, PROTO, KEY, VALUE, ABORT } states; + states state = METHOD; + + typedef enum { START, MAN, ST, MX } headers; + headers header = START; + + uint8_t cursor = 0; + uint8_t cr = 0; + + char buffer[SSDP_BUFFER_SIZE] = { 0 }; + + while (_server->getSize() > 0) { + char c = _server->read(); + + (c == '\r' || c == '\n') ? cr++ : cr = 0; + + switch (state) { + case METHOD: + + if (c == ' ') { + if (strcmp_P(buffer, PSTR("M-SEARCH")) == 0) { method = SEARCH; } + else if (strcmp_P(buffer, PSTR("NOTIFY")) == 0) { method = NOTIFY; } + + if (method == NONE) { state = ABORT; } + else { state = URI; } + cursor = 0; + } else if (cursor < SSDP_METHOD_SIZE - 1) { + buffer[cursor++] = c; + buffer[cursor] = '\0'; + } + break; + case URI: + + if (c == ' ') { + if (strcmp(buffer, "*")) { state = ABORT; } + else { state = PROTO; } + cursor = 0; + } else if (cursor < SSDP_URI_SIZE - 1) { + buffer[cursor++] = c; + buffer[cursor] = '\0'; + } + break; + case PROTO: + + if (cr == 2) { + state = KEY; + cursor = 0; + } + break; + case KEY: + + if (cr == 4) { + _pending = true; + _process_time = millis(); + } + else if (c == ' ') { + cursor = 0; + state = VALUE; + } + else if ((c != '\r') && (c != '\n') && (c != ':') && (cursor < SSDP_BUFFER_SIZE - 1)) { + buffer[cursor++] = c; + buffer[cursor] = '\0'; + } + break; + case VALUE: + + if (cr == 2) { + switch (header) { + case START: + break; + case MAN: + break; + case ST: + + if (strcmp_P(buffer, PSTR("ssdp:all"))) { + state = ABORT; + } + + // if the search type matches our type, we should respond instead of ABORT + if (strcmp_P(buffer, PSTR("urn:schemas-upnp-org:device:BinaryLight:1")) == 0) { + _pending = true; + _process_time = millis(); + state = KEY; + } + break; + case MX: + _delay = HwRandom(0, atoi(buffer)) * 1000L; + break; + } + + if (state != ABORT) { + state = KEY; + header = START; + cursor = 0; + } + } else if ((c != '\r') && (c != '\n')) { + if (header == START) { + if (strncmp(buffer, "MA", 2) == 0) { header = MAN; } + else if (strcmp(buffer, "ST") == 0) { header = ST; } + else if (strcmp(buffer, "MX") == 0) { header = MX; } + } + + if (cursor < SSDP_BUFFER_SIZE - 1) { + buffer[cursor++] = c; + buffer[cursor] = '\0'; + } + } + break; + case ABORT: + _pending = false; _delay = 0; + break; + } + } + } + + if (_pending && timeOutReached(_process_time + _delay)) { + _pending = false; _delay = 0; + SSDP_send(NONE); + } else if ((_notify_time == 0) || timeOutReached(_notify_time + (SSDP_INTERVAL * 1000L))) { + _notify_time = millis(); + SSDP_send(NOTIFY); + } + + if (_pending) { + while (_server->next()) { + _server->flush(); + } + } +} + +# endif // if FEATURE_SSDP +#endif // if defined(ESP8266) + + +// ******************************************************************************** +// Return subnet range of WiFi. +// ******************************************************************************** +bool getSubnetRange(IPAddress& low, IPAddress& high) +{ + if (!WiFiEventData.WiFiGotIP()) { + return false; + } + + const IPAddress ip = NetworkLocalIP(); + const IPAddress subnet = NetworkSubnetMask(); + + low = ip; + high = ip; + + // Compute subnet range. + for (uint8_t i = 0; i < 4; ++i) { + if (subnet[i] != 255) { + low[i] = low[i] & subnet[i]; + high[i] = high[i] | ~subnet[i]; + } + } + return true; +} + +// ******************************************************************************** +// Functions to test and handle network/client connectivity. +// ******************************************************************************** + +#ifdef CORE_POST_2_5_0 +# include +#endif // ifdef CORE_POST_2_5_0 + + +bool hasIPaddr() { + if (useStaticIP()) { return true; } + +#ifdef CORE_POST_2_5_0 + bool configured = false; + + for (auto addr : addrList) { + if ((configured = (!addr.isLocal() && (addr.ifnumber() == STATION_IF)))) { + /* + ESPEASY_SERIAL_CONSOLE_PORT.printf("STA: IF='%s' hostname='%s' addr= %s\n", + addr.ifname().c_str(), + addr.ifhostname(), + addr.toString().c_str()); + */ + break; + } + } + return configured; +#else // ifdef CORE_POST_2_5_0 + return WiFi.isConnected(); +#endif // ifdef CORE_POST_2_5_0 +} + +bool useStaticIP() { + #if FEATURE_ETHERNET + if (active_network_medium == NetworkMedium_t::Ethernet) { + return ethUseStaticIP(); + } + #endif + return WiFiUseStaticIP(); +} + +// Check connection. Maximum timeout 500 msec. +bool NetworkConnected(uint32_t timeout_ms) { + if (NetworkConnected()) return true; + +#ifdef USES_ESPEASY_NOW + if (isESPEasy_now_only()) { + return false; + } +#endif + + if (timeout_ms > 500) { + timeout_ms = 500; + } + + uint32_t timer = millis() + timeout_ms; + uint32_t min_delay = timeout_ms / 20; + + if (min_delay < 10) { + delay(0); // Allow at least once time for backgroundtasks + min_delay = 10; + } + + // Apparently something needs network, perform check to see if it is ready now. + while (!NetworkConnected()) { + if (timeOutReached(timer)) { + return false; + } + delay(min_delay); // Allow the backgroundtasks to continue procesing. + } + return true; +} + +bool hostReachable(const IPAddress& ip) { + if (!NetworkConnected()) { return false; } + + return true; // Disabled ping as requested here: + // https://github.com/letscontrolit/ESPEasy/issues/1494#issuecomment-397872538 + + /* + // Only do 1 ping at a time to return early + uint8_t retry = 3; + while (retry > 0) { + #if defined(ESP8266) + if (Ping.ping(ip, 1)) return true; + #endif + #if defined(ESP32) + if (ping_start(ip, 4, 0, 0, 5)) return true; + #endif + delay(50); + --retry; + } + if (loglevelActiveFor(LOG_LEVEL_ERROR)) { + String log = F("Host unreachable: "); + log += formatIP(ip); + addLog(LOG_LEVEL_ERROR, log); + } + if (ip[1] == 0 && ip[2] == 0 && ip[3] == 0) { + // Work-around to fix connected but not able to communicate. + addLog(LOG_LEVEL_ERROR, F("WiFi : Detected strange behavior, reconnect wifi.")); + WifiDisconnect(); + } + logConnectionStatus(); + return false; + */ +} + +#if FEATURE_HTTP_CLIENT +bool connectClient(WiFiClient& client, const char *hostname, uint16_t port, uint32_t timeout_ms) { + IPAddress ip; + + if (resolveHostByName(hostname, ip, timeout_ms)) { + return connectClient(client, ip, port, timeout_ms); + } + return false; +} + +bool connectClient(WiFiClient& client, IPAddress ip, uint16_t port, uint32_t timeout_ms) +{ + START_TIMER; + + if (!NetworkConnected()) { + client.stop(); + return false; + } +#ifndef BUILD_NO_DEBUG + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + addLog(LOG_LEVEL_DEBUG, strformat( + F("connectClient: '%s' port: %u"), + ip.toString().c_str(), + port)); + } +#endif + + // In case of domain name resolution error result can be negative. + // https://github.com/esp8266/Arduino/blob/18f643c7e2d6a0da9d26ff2b14c94e6536ab78c1/libraries/Ethernet/src/Dns.cpp#L44 + // Thus must match the result with 1. + bool connected = (client.connect(ip, port) == 1); + + delay(0); + + if (!connected) { +#ifndef BUILD_NO_DEBUG + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + addLog(LOG_LEVEL_ERROR, strformat( + F("connectClient: connect failed to '%s' port: %u"), + ip.toString().c_str(), + port)); + } +#endif + + Scheduler.sendGratuitousARP_now(); + client.stop(); // Make sure to start over without some stale connection + } + STOP_TIMER(CONNECT_CLIENT_STATS); +#if defined(ESP32) || defined(ARDUINO_ESP8266_RELEASE_2_3_0) || defined(ARDUINO_ESP8266_RELEASE_2_4_0) +#else + + if (connected) { + client.keepAlive(); // Use default keep alive values + } +#endif // if defined(ESP32) || defined(ARDUINO_ESP8266_RELEASE_2_3_0) || defined(ARDUINO_ESP8266_RELEASE_2_4_0) + return connected; +} +#endif // FEATURE_HTTP_CLIENT + +void scrubDNS() { + #if FEATURE_ETHERNET + if (active_network_medium == NetworkMedium_t::Ethernet) { + if (EthEventData.EthServicesInitialized()) { + setDNS(0, EthEventData.dns0_cache); + setDNS(1, EthEventData.dns1_cache); + } + return; + } + #endif + if (WiFiEventData.WiFiServicesInitialized()) { + setDNS(0, WiFiEventData.dns0_cache); + setDNS(1, WiFiEventData.dns1_cache); + } +} + +bool valid_DNS_address(const IPAddress& dns) { + return (/*dns.v4() != (uint32_t)0x00000000 && */ + dns != IPAddress((uint32_t)0xFD000000) && +#ifdef ESP32 + // Bug where IPv6 global prefix is set as DNS + // Global IPv6 prefixes currently start with 2xxx:: + (dns[0] & 0xF0) != 0x20 && +#endif + dns != INADDR_NONE); +} + +bool setDNS(int index, const IPAddress& dns) { + if (index >= 2) return false; + #ifdef ESP8266 + if(dns.isSet() && dns != WiFi.dnsIP(index)) { + dns_setserver(index, dns); + #ifndef BUILD_NO_DEBUG + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLogMove(LOG_LEVEL_INFO, concat(F("IP : Set DNS: "), formatIP(dns))); + } + #endif + return true; + } + #endif + #ifdef ESP32 + ip_addr_t d; + d.type = IPADDR_TYPE_V4; + + if (valid_DNS_address(dns) || dns == INADDR_NONE) { + // Set DNS0-Server + d.u_addr.ip4.addr = static_cast(dns); + const ip_addr_t* cur_dns = dns_getserver(index); + if (cur_dns != nullptr && cur_dns->u_addr.ip4.addr == d.u_addr.ip4.addr) { + // Still the same as before + return false; + } + dns_setserver(index, &d); + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLogMove(LOG_LEVEL_INFO, concat(F("IP : Set DNS: "), formatIP(dns))); + } + return true; + } + #endif + return false; +} + +bool resolveHostByName(const char *aHostname, IPAddress& aResult, uint32_t timeout_ms) { + START_TIMER; + + if (!NetworkConnected()) { + return false; + } + + FeedSW_watchdog(); + + // FIXME TD-er: Must try to restore DNS server entries. + scrubDNS(); + +#if defined(ARDUINO_ESP8266_RELEASE_2_3_0) || defined(ESP32) + bool resolvedIP = WiFi.hostByName(aHostname, aResult) == 1; +#else // if defined(ARDUINO_ESP8266_RELEASE_2_3_0) || defined(ESP32) + bool resolvedIP = WiFi.hostByName(aHostname, aResult, timeout_ms) == 1; +#endif // if defined(ARDUINO_ESP8266_RELEASE_2_3_0) || defined(ESP32) + delay(0); + FeedSW_watchdog(); + + if (!resolvedIP) { + Scheduler.sendGratuitousARP_now(); + } + STOP_TIMER(HOST_BY_NAME_STATS); + return resolvedIP; +} + +bool hostReachable(const String& hostname) { + IPAddress remote_addr; + + if (resolveHostByName(hostname.c_str(), remote_addr)) { + return hostReachable(remote_addr); + } + + if (loglevelActiveFor(LOG_LEVEL_ERROR)) { + addLogMove(LOG_LEVEL_ERROR, concat(F("Hostname cannot be resolved: "), hostname)); + } + return false; +} + +// Create a random port for the UDP connection. +// Return true when successful. +bool beginWiFiUDP_randomPort(WiFiUDP& udp) { + if (!NetworkConnected()) { + return false; + } + unsigned int attempts = 3; + + while (attempts > 0) { + --attempts; + long port = HwRandom(1025, 65535); + + if (udp.begin(port) != 0) { + return true; + } + } + return false; +} + +void sendGratuitousARP() { + if (!NetworkConnected()) { + return; + } +#ifdef SUPPORT_ARP + + // See https://github.com/letscontrolit/ESPEasy/issues/2374 + START_TIMER; + netif *n = netif_list; + + while (n) { + if ((n->hwaddr_len == ETH_HWADDR_LEN) && + (n->flags & NETIF_FLAG_ETHARP) && + ((n->flags & NETIF_FLAG_LINK_UP) && (n->flags & NETIF_FLAG_UP))) { + # ifdef ESP32 + etharp_gratuitous_r(n); + # else // ifdef ESP32 + etharp_gratuitous(n); + # endif // ifdef ESP32 + } + n = n->next; + } + STOP_TIMER(GRAT_ARP_STATS); +#endif // ifdef SUPPORT_ARP +} + +bool splitHostPortString(const String& hostPortString, String& host, uint16_t& port) { + port = 80; // Some default + int index_colon = hostPortString.indexOf(':'); + + if (index_colon >= 0) { + int32_t port_tmp; + + if (!validIntFromString(hostPortString.substring(index_colon + 1), port_tmp)) { + return false; + } + + if ((port_tmp < 0) || (port_tmp > 65535)) { return false; } + port = port_tmp; + host = hostPortString.substring(0, index_colon); + } else { + // No port nr defined. + host = hostPortString; + } + return true; +} + +bool splitUserPass_HostPortString(const String& hostPortString, String& user, String& pass, String& host, uint16_t& port) +{ + const int pos_at = hostPortString.indexOf('@'); + + if (pos_at != -1) { + user = hostPortString.substring(0, pos_at); + const int pos_colon = user.indexOf(':'); + + if (pos_colon != -1) { + pass = user.substring(pos_colon + 1); + user = user.substring(0, pos_colon); + } + return splitHostPortString(hostPortString.substring(pos_at + 1), host, port); + } + return splitHostPortString(hostPortString, host, port); +} + +// Split a full URL like "http://hostname:port/path/file.htm" +// Return value is everything after the hostname:port section (including /) +String splitURL(const String& fullURL, String& user, String& pass, String& host, uint16_t& port, String& file) { + int starthost = fullURL.indexOf(F("://")); + + if (starthost == -1) { + starthost = 0; + } else { + starthost += 3; + } + const int endhost = fullURL.indexOf('/', starthost); + splitUserPass_HostPortString(fullURL.substring(starthost, endhost), user, pass, host, port); + + if (endhost == -1) { + return EMPTY_STRING; + } + + int startfile = fullURL.lastIndexOf('/'); + + if (startfile >= 0) { + file = fullURL.substring(startfile); + } + return fullURL.substring(endhost); +} + +String get_user_agent_string() { + static unsigned int agent_size = 20; + String userAgent; + + userAgent.reserve(agent_size); + userAgent += F("ESP Easy/"); + userAgent += get_build_nr(); + userAgent += '/'; + userAgent += get_build_date(); + userAgent += ' '; + userAgent += get_build_time(); + agent_size = userAgent.length(); + return userAgent; +} + +bool splitHeaders(int& strpos, const String& multiHeaders, String& name, String& value) { + if (strpos < 0) { + return false; + } + int colonPos = multiHeaders.indexOf(':', strpos); + + if (colonPos < 0) { + return false; + } + name = multiHeaders.substring(strpos, colonPos); + int valueEndPos = multiHeaders.indexOf('\n', colonPos + 1); + + if (valueEndPos < 0) { + value = multiHeaders.substring(colonPos + 1); + strpos = -1; + } else { + value = multiHeaders.substring(colonPos + 1, valueEndPos); + strpos = valueEndPos + 1; + } + value.replace('\r', ' '); + value.trim(); + return true; +} + +String extractParam(const String& authReq, const String& param, const char delimit) { + int _begin = authReq.indexOf(param); + + if (_begin == -1) { return EMPTY_STRING; } + return authReq.substring(_begin + param.length(), authReq.indexOf(delimit, _begin + param.length())); +} + +#if FEATURE_HTTP_CLIENT +String getCNonce(const int len) { + static const char alphanum[] = "0123456789" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz"; + String s; + + for (int i = 0; i < len; ++i) { + // FIXME TD-er: Is this "-1" correct? The mod operator makes sure we never reach the sizeof index + s += alphanum[rand() % (sizeof(alphanum) - 1)]; + } + + return s; +} + +String getDigestAuth(const String& authReq, + const String& username, + const String& password, + const String& method, + const String& uri, + unsigned int counter) { + // extracting required parameters for RFC 2069 simpler Digest + const String realm = extractParam(authReq, F("realm=\""), '"'); + const String nonce = extractParam(authReq, F("nonce=\""), '"'); + const String cNonce = getCNonce(8); + + char nc[9]; + + snprintf(nc, sizeof(nc), "%08x", counter); + + // parameters for the RFC 2617 newer Digest + MD5Builder md5; + + md5.begin(); + md5.add(username + ':' + realm + ':' + password); // md5 of the user:realm:user + md5.calculate(); + const String h1 = md5.toString(); + + md5.begin(); + md5.add(method + ':' + uri); + md5.calculate(); + const String h2 = md5.toString(); + + md5.begin(); + md5.add(h1 + ':' + nonce + ':' + String(nc) + ':' + cNonce + F(":auth:") + h2); + md5.calculate(); + + // return authorization + return strformat( + F("Digest username=\"%s\"" + ", realm=\"%s\"" + ", nonce=\"%s\"" + ", uri=\"%s\"" + ", algorithm=\"MD5\", qop=auth, nc=%s, cnonce=\"%s\"" + ", response=\"%s\""), + username.c_str(), + realm.c_str(), + nonce.c_str(), + uri.c_str(), + nc, + cNonce.c_str(), + md5.toString().c_str()); // response +} + +#ifndef BUILD_NO_DEBUG +void log_http_result(const HTTPClient& http, + const String & logIdentifier, + const String & host, + const String & HttpMethod, + int httpCode, + const String & response) +{ + uint8_t loglevel = LOG_LEVEL_ERROR; + bool success = false; + + // HTTP codes: + // 1xx Informational response + // 2xx Success + if ((httpCode >= 100) && (httpCode < 300)) { + loglevel = LOG_LEVEL_INFO; + success = true; + } + + if (loglevelActiveFor(loglevel)) { + String log = strformat(F("HTTP : %s %s %s"), + logIdentifier.c_str(), host.c_str(), HttpMethod.c_str()); + + if (!success) { + log += F("failed "); + } + log += concat(F("HTTP code: "), httpCode); + + if (!success) { + log += ' '; + log += http.errorToString(httpCode); + } + + if (response.length() > 0) { + log += concat(F(" Received reply: "), response.substring(0, 100)); // Returned string may be huge, so only log the first part. + } + addLogMove(loglevel, log); + } +} +#endif + +int http_authenticate(const String& logIdentifier, + WiFiClient & client, + HTTPClient & http, + uint16_t timeout, + const String& user, + const String& pass, + const String& host, + uint16_t port, + const String& uri, + const String& HttpMethod, + const String& header, + const String& postStr, + bool must_check_reply) +{ + if (!uri.startsWith(F("/"))) { + return http_authenticate( + logIdentifier, + client, + http, + timeout, + user, + pass, + host, + port, + concat(F("/"), uri), + HttpMethod, + header, + postStr, + must_check_reply); + } + int httpCode = 0; + const bool hasCredentials = !user.isEmpty() && !pass.isEmpty(); + + if (hasCredentials) { + must_check_reply = true; + http.setAuthorization(user.c_str(), pass.c_str()); + } else { + http.setAuthorization(""); // Clear Basic authorization +#ifdef ESP32 + http.setAuthorizationType(""); // Default type is "Basic" +#endif + } + http.setTimeout(timeout); + http.setUserAgent(get_user_agent_string()); + + if (Settings.SendToHTTP_follow_redirects()) { + http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); + http.setRedirectLimit(2); + } + + #ifdef MUSTFIX_CLIENT_TIMEOUT_IN_SECONDS + + // See: https://github.com/espressif/arduino-esp32/pull/6676 + client.setTimeout((timeout + 500) / 1000); // in seconds!!!! + Client *pClient = &client; + pClient->setTimeout(timeout); + #else // ifdef MUSTFIX_CLIENT_TIMEOUT_IN_SECONDS + client.setTimeout(timeout); // in msec as it should be! + #endif // ifdef MUSTFIX_CLIENT_TIMEOUT_IN_SECONDS + + // Add request header as fall back. + // When adding another "accept" header, it may be interpreted as: + // "if you have XXX, send it; or failing that, just give me what you've got." + http.addHeader(F("Accept"), F("*/*;q=0.1")); + + // Add client IP + http.addHeader(F("X-Forwarded-For"), formatIP(NetworkLocalIP())); + + delay(0); + scrubDNS(); +#if defined(CORE_POST_2_6_0) || defined(ESP32) + http.begin(client, host, port, uri, false); // HTTP +#else // if defined(CORE_POST_2_6_0) || defined(ESP32) + http.begin(client, host, port, uri); +#endif // if defined(CORE_POST_2_6_0) || defined(ESP32) + + const char *keys[] = { "WWW-Authenticate" }; + http.collectHeaders(keys, 1); + + { + int headerpos = 0; + String name, value; + + while (splitHeaders(headerpos, header, name, value)) { + // Disabled the check to exclude "Authorization", due to: + // https://github.com/letscontrolit/ESPEasy/issues/4364 + // Check was added for: https://github.com/letscontrolit/ESPEasy/issues/4355 + // However, I doubt this was the actual bug. More likely the supplied credential strings were not entirely empty for whatever reason. + // + // Work-around to not add Authorization header since the HTTPClient code + // only ignores this when base64Authorication is set. + +// if (!name.equalsIgnoreCase(F("Authorization"))) { + http.addHeader(name, value); +// } + } + } + + // start connection and send HTTP header (and body) + if (equals(HttpMethod, F("HEAD")) || equals(HttpMethod, F("GET"))) { + httpCode = http.sendRequest(HttpMethod.c_str()); + } else { + httpCode = http.sendRequest(HttpMethod.c_str(), postStr); + } + + // Check to see if we need to try digest auth + if ((httpCode == 401) && must_check_reply) { + const String authReq = http.header(String(F("WWW-Authenticate")).c_str()); + + if (authReq.indexOf(F("Digest")) != -1) { + // Use Digest authorization + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLogMove(LOG_LEVEL_INFO, concat(F("HTTP : Start Digest Authorization for "), host)); + } + + http.setAuthorization(""); // Clear Basic authorization +#ifdef ESP32 + http.setAuthorizationType(""); // Default type is "Basic" and "Digest" is already part of the string generated by getDigestAuth() +#endif + const String authorization = getDigestAuth(authReq, user, pass, F("GET"), uri, 1); + + http.end(); +#if defined(CORE_POST_2_6_0) || defined(ESP32) + http.begin(client, host, port, uri, false); // HTTP, not HTTPS +#else // if defined(CORE_POST_2_6_0) || defined(ESP32) + http.begin(client, host, port, uri); +#endif // if defined(CORE_POST_2_6_0) || defined(ESP32) + + http.addHeader(F("Authorization"), authorization); + + // start connection and send HTTP header (and body) + if (equals(HttpMethod, F("HEAD")) || equals(HttpMethod, F("GET"))) { + httpCode = http.sendRequest(HttpMethod.c_str()); + } else { + httpCode = http.sendRequest(HttpMethod.c_str(), postStr); + } + } + } + + if (!must_check_reply) { + // There are services which do not send an ack. + // So if the return code matches a read timeout, we change it into HTTP code 200 + if (httpCode == HTTPC_ERROR_READ_TIMEOUT) { + httpCode = 200; + } + } + + if (Settings.UseRules) { + // Generate event with the HTTP return code + // e.g. http#hostname=401 + eventQueue.addMove(strformat(F("http#%s=%d"), host.c_str(), httpCode)); + +// ----This way to the custom response parser---------------- +#if RESPONSE_PARSER_SUPPORT + eventFromResponse(host, httpCode, uri, http); +#endif + } +// ----------------------------------------------------------- + +#ifndef BUILD_NO_DEBUG + log_http_result(http, logIdentifier, host + ':' + port, HttpMethod, httpCode, EMPTY_STRING); +#endif + return httpCode; +} + +String send_via_http(const String& logIdentifier, + uint16_t timeout, + const String& user, + const String& pass, + const String& host, + uint16_t port, + const String& uri, + const String& HttpMethod, + const String& header, + const String& postStr, + int & httpCode, + bool must_check_reply) { + WiFiClient client; + HTTPClient http; + http.setReuse(false); + + httpCode = http_authenticate( + logIdentifier, + client, + http, + timeout, + user, + pass, + host, + port, + uri, + HttpMethod, + header, + postStr, + must_check_reply); + + String response; + + if ((httpCode > 0) && must_check_reply) { + response = http.getString(); +#ifndef BUILD_NO_DEBUG + if (!response.isEmpty()) { + log_http_result(http, logIdentifier, host, HttpMethod, httpCode, response); + } +#endif + } + http.end(); + // http.end() does not call client.stop() if it is no longer connected. + // However the client may still keep its internal state which may prevent + // future connections to the same host until there has been a connection to another host inbetween. + client.stop(); + return response; +} +#endif // FEATURE_HTTP_CLIENT + +#if FEATURE_DOWNLOAD + +// FIXME TD-er: Must set the timeout somewhere +# ifndef DOWNLOAD_FILE_TIMEOUT + # define DOWNLOAD_FILE_TIMEOUT 2000 +# endif // ifndef DOWNLOAD_FILE_TIMEOUT + +// Download a file from a given URL and save to a local file named "file_save" +// If the URL ends with a /, the file part will be assumed the same as file_save. +// If file_save is empty, the file part from the URL will be used as local file name. +// Return true when successful. +bool downloadFile(const String& url, String file_save) { + String error; + + return downloadFile(url, file_save, EMPTY_STRING, EMPTY_STRING, error); +} + +// User and Pass may be updated if they occur in the hostname part. +// Thus have to be copied instead of const reference. +bool start_downloadFile(WiFiClient & client, + HTTPClient & http, + const String& url, + String & file_save, + String user, + String pass, + String & error) { + String host, file; + uint16_t port; + String uri = splitURL(url, user, pass, host, port, file); + + if (file_save.isEmpty()) { + file_save = file; + } else if ((file.isEmpty()) && uri.endsWith("/")) { + // file = file_save; + uri += file_save; + } +# ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + addLogMove(LOG_LEVEL_DEBUG, strformat(F("downloadFile: URL: %s decoded: %s:%d%s"), + url.c_str(), host.c_str(), port, uri.c_str())); + } +# endif // ifndef BUILD_NO_DEBUG + + if (file_save.isEmpty()) { + error = F("Empty filename"); + addLog(LOG_LEVEL_ERROR, error); + return false; + } + + const int httpCode = http_authenticate( + F("DownloadFile"), + client, + http, + DOWNLOAD_FILE_TIMEOUT, + user, + pass, + host, + port, + uri, + F("GET"), + EMPTY_STRING, // header + EMPTY_STRING, // postStr + true // must_check_reply + ); + + if (httpCode != HTTP_CODE_OK) { + error = strformat(F("HTTP code: %d %s"), httpCode, url.c_str()); + + addLog(LOG_LEVEL_ERROR, error); + http.end(); + client.stop(); + return false; + } + return true; +} + +bool downloadFile(const String& url, String file_save, const String& user, const String& pass, String& error) { + WiFiClient client; + HTTPClient http; + http.setReuse(false); + + if (!start_downloadFile(client, http, url, file_save, user, pass, error)) { + return false; + } + + if (fileExists(file_save)) { + error = concat(F("File exists: "), file_save); + addLog(LOG_LEVEL_ERROR, error); + http.end(); + client.stop(); + return false; + } + + long len = http.getSize(); + fs::File f = tryOpenFile(file_save, "w"); + + if (f) { + const size_t downloadBuffSize = 256; + uint8_t buff[downloadBuffSize]; + size_t bytesWritten = 0; + unsigned long timeout = millis() + DOWNLOAD_FILE_TIMEOUT; + + // get tcp stream + WiFiClient *stream = &client; + + // read all data from server + while (http.connected() && (len > 0 || len == -1)) { + // read up to downloadBuffSize at a time. + size_t bytes_to_read = downloadBuffSize; + + if ((len > 0) && (len < static_cast(bytes_to_read))) { + bytes_to_read = len; + } + const size_t c = stream->readBytes(buff, bytes_to_read); + + if (c > 0) { + timeout = millis() + DOWNLOAD_FILE_TIMEOUT; + + if (f.write(buff, c) != c) { + error = strformat(F("Error saving file: %s %d Bytes written"), file_save.c_str(), bytesWritten); + addLog(LOG_LEVEL_ERROR, error); + http.end(); + client.stop(); + return false; + } + bytesWritten += c; + + if (len > 0) { len -= c; } + } + + if (timeOutReached(timeout)) { + error = concat(F("Timeout: "), file_save); + addLog(LOG_LEVEL_ERROR, error); + delay(0); + http.end(); + client.stop(); + return false; + } + delay(0); + } + f.close(); + http.end(); + client.stop(); + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLog(LOG_LEVEL_INFO, strformat(F("downloadFile: %s Success"), file_save.c_str())); + } + return true; + } + http.end(); + client.stop(); + error = concat(F("Failed to open file for writing: "), file_save); + addLog(LOG_LEVEL_ERROR, error); + return false; +} + +bool downloadFirmware(String filename, String& error) +{ + String baseurl, user, pass; +# if FEATURE_CUSTOM_PROVISIONING + MakeProvisioningSettings(ProvisioningSettings); + + if (ProvisioningSettings.get()) { + loadProvisioningSettings(*ProvisioningSettings); + if (!ProvisioningSettings->allowedFlags.allowFetchFirmware) { + error = F("Not Allowed"); + return false; + } + baseurl = ProvisioningSettings->url; + user = ProvisioningSettings->user; + pass = ProvisioningSettings->pass; + } +# endif // if FEATURE_CUSTOM_PROVISIONING + + const String fullUrl = joinUrlFilename(baseurl, filename); + + return downloadFirmware(fullUrl, filename, user, pass, error); +} + +bool downloadFirmware(const String& url, String& file_save, String& user, String& pass, String& error) +{ + WiFiClient client; + HTTPClient http; + error.clear(); + + if (!start_downloadFile(client, http, url, file_save, user, pass, error)) { + return false; + } + + int len = http.getSize(); + + if (Update.begin(len, U_FLASH, Settings.Pin_status_led, Settings.Pin_status_led_Inversed ? LOW : HIGH)) { + const size_t downloadBuffSize = 256; + uint8_t buff[downloadBuffSize]; + size_t bytesWritten = 0; + unsigned long timeout = millis() + DOWNLOAD_FILE_TIMEOUT; + + // get tcp stream + WiFiClient *stream = &client; + + while (error.isEmpty() && http.connected() && (len > 0 || len == -1)) { + // read up to downloadBuffSize at a time. + size_t bytes_to_read = downloadBuffSize; + + if ((len > 0) && (len < static_cast(bytes_to_read))) { + bytes_to_read = len; + } + const size_t c = stream->readBytes(buff, bytes_to_read); + + if (c > 0) { + timeout = millis() + DOWNLOAD_FILE_TIMEOUT; + + if (Update.write(buff, c) != c) { + error = strformat(F("Error saving firmware update: %s %d Bytes written"), + file_save.c_str(), bytesWritten); + break; + } + bytesWritten += c; + + if (len > 0) { len -= c; } + } + + if (timeOutReached(timeout)) { + error = concat(F("Timeout: "), file_save); + break; + } + + if (!UseRTOSMultitasking) { + // On ESP32 the schedule is executed on the 2nd core. + Scheduler.handle_schedule(); + } + backgroundtasks(); + } + } + http.end(); + client.stop(); + + if (error.isEmpty() && loglevelActiveFor(LOG_LEVEL_INFO)) { + addLog(LOG_LEVEL_INFO, strformat(F("downloadFile: %s Success"), file_save.c_str())); + } + + uint8_t errorcode = 0; + if (!Update.end()) { + errorcode = Update.getError(); +#ifdef ESP32 + const __FlashStringHelper * err_fstr = F("Unknown"); + switch (errorcode) { + case UPDATE_ERROR_OK: err_fstr = F("OK"); break; + case UPDATE_ERROR_WRITE: err_fstr = F("WRITE"); break; + case UPDATE_ERROR_ERASE: err_fstr = F("ERASE"); break; + case UPDATE_ERROR_READ: err_fstr = F("READ"); break; + case UPDATE_ERROR_SPACE: err_fstr = F("SPACE"); break; + case UPDATE_ERROR_SIZE: err_fstr = F("SIZE"); break; + case UPDATE_ERROR_STREAM: err_fstr = F("STREAM"); break; + case UPDATE_ERROR_MD5: err_fstr = F("MD5"); break; + case UPDATE_ERROR_MAGIC_BYTE: err_fstr = F("MAGIC_BYTE"); break; + case UPDATE_ERROR_ACTIVATE: err_fstr = F("ACTIVATE"); break; + case UPDATE_ERROR_NO_PARTITION: err_fstr = F("NO_PARTITION"); break; + case UPDATE_ERROR_BAD_ARGUMENT: err_fstr = F("BAD_ARGUMENT"); break; + case UPDATE_ERROR_ABORT: err_fstr = F("ABORT"); break; + } + error += concat(F(" Error: "), err_fstr); +#else + error += concat(F(" Error: "), errorcode); +#endif + } else { + if (Settings.UseRules) { + eventQueue.addMove(concat(F("ProvisionFirmware#success="), file_save)); + } + return true; + } + + backgroundtasks(); + if (loglevelActiveFor(LOG_LEVEL_ERROR)) { + addLog(LOG_LEVEL_ERROR, concat(F("Failed update firmware: "), error)); + } + + if (Settings.UseRules) { + eventQueue.addMove(concat(F("ProvisionFirmware#failed="), file_save)); + } + return false; +} + +String joinUrlFilename(const String& url, String& filename) +{ + String fullUrl; + + fullUrl.reserve(url.length() + filename.length() + 1); // May need to add an extra slash + fullUrl = url; + fullUrl = parseTemplate(fullUrl, true); // URL encode + + // URLEncode may also encode the '/' into "%2f" + // FIXME TD-er: Can this really occur? + fullUrl.replace(F("%2f"), F("/")); + + while (filename.startsWith(F("/"))) { + filename = filename.substring(1); + } + + if (!fullUrl.endsWith(F("/"))) { + fullUrl += F("/"); + } + fullUrl += filename; + return fullUrl; +} + +#endif // if FEATURE_DOWNLOAD + diff --git a/src/src/Helpers/PeriodicalActions.cpp b/src/src/Helpers/PeriodicalActions.cpp index 8c0f3bc22c..e9151df913 100644 --- a/src/src/Helpers/PeriodicalActions.cpp +++ b/src/src/Helpers/PeriodicalActions.cpp @@ -1,507 +1,604 @@ -#include "../Helpers/PeriodicalActions.h" - - -#include "../../ESPEasy-Globals.h" - -#include "../ControllerQueue/DelayQueueElements.h" -#include "../ControllerQueue/MQTT_queue_element.h" -#include "../DataStructs/TimingStats.h" -#include "../DataTypes/ESPEasy_plugin_functions.h" -#include "../ESPEasyCore/Controller.h" -#include "../ESPEasyCore/ESPEasyGPIO.h" -#include "../ESPEasyCore/ESPEasy_Log.h" -#include "../ESPEasyCore/ESPEasyNetwork.h" -#include "../ESPEasyCore/ESPEasyWifi.h" -#include "../ESPEasyCore/ESPEasyRules.h" -#include "../ESPEasyCore/Serial.h" -#include "../Globals/ESPEasyWiFiEvent.h" -#if FEATURE_ETHERNET -#include "../Globals/ESPEasyEthEvent.h" -#endif -#include "../Globals/ESPEasy_Scheduler.h" -#include "../Globals/ESPEasy_time.h" -#include "../Globals/EventQueue.h" -#include "../Globals/MainLoopCommand.h" -#include "../Globals/MQTT.h" -#include "../Globals/NetworkState.h" -#include "../Globals/RTC.h" -#include "../Globals/Services.h" -#include "../Globals/Settings.h" -#include "../Globals/Statistics.h" -#include "../Globals/WiFi_AP_Candidates.h" -#include "../Helpers/ESPEasyRTC.h" -#include "../Helpers/FS_Helper.h" -#include "../Helpers/Hardware_temperature_sensor.h" -#include "../Helpers/Memory.h" -#include "../Helpers/Misc.h" -#include "../Helpers/Networking.h" -#include "../Helpers/StringGenerator_System.h" -#include "../Helpers/StringGenerator_WiFi.h" -#include "../Helpers/StringProvider.h" - -#ifdef USES_C015 -#include "../../ESPEasy_fdwdecl.h" -#endif - - - -#define PLUGIN_ID_MQTT_IMPORT 37 - - -/*********************************************************************************************\ - * Tasks that run 50 times per second -\*********************************************************************************************/ - -void run50TimesPerSecond() { - String dummy; - { - START_TIMER; - PluginCall(PLUGIN_FIFTY_PER_SECOND, 0, dummy); - STOP_TIMER(PLUGIN_CALL_50PS); - } - { - START_TIMER; - CPluginCall(CPlugin::Function::CPLUGIN_FIFTY_PER_SECOND, 0, dummy); - STOP_TIMER(CPLUGIN_CALL_50PS); - } - processNextEvent(); -} - -/*********************************************************************************************\ - * Tasks that run 10 times per second -\*********************************************************************************************/ -void run10TimesPerSecond() { - String dummy; - //@giig19767g: WARNING: Monitor10xSec must run before PLUGIN_TEN_PER_SECOND - { - START_TIMER; - GPIO_Monitor10xSec(); - STOP_TIMER(PLUGIN_CALL_10PSU); - } - { - START_TIMER; - PluginCall(PLUGIN_TEN_PER_SECOND, 0, dummy); - STOP_TIMER(PLUGIN_CALL_10PS); - } - { - START_TIMER; -// PluginCall(PLUGIN_UNCONDITIONAL_POLL, 0, dummyString); - PluginCall(PLUGIN_MONITOR, 0, dummy); - STOP_TIMER(PLUGIN_CALL_10PSU); - } - { - START_TIMER; - CPluginCall(CPlugin::Function::CPLUGIN_TEN_PER_SECOND, 0, dummy); - STOP_TIMER(CPLUGIN_CALL_10PS); - } - - #ifdef USES_C015 - if (NetworkConnected()) { - Blynk_Run_c015(); - } - #endif - if (!UseRTOSMultitasking) { - START_TIMER - web_server.handleClient(); - STOP_TIMER(WEBSERVER_HANDLE_CLIENT); - } -} - - -/*********************************************************************************************\ - * Tasks each second -\*********************************************************************************************/ -void runOncePerSecond() -{ - START_TIMER; - updateLogLevelCache(); - dailyResetCounter++; - if (dailyResetCounter > 86400) // 1 day elapsed... //86400 - { - RTC.flashDayCounter=0; - saveToRTC(); - dailyResetCounter=0; - #ifndef BUILD_MINIMAL_OTA - addLog(LOG_LEVEL_INFO, F("SYS : Reset 24h counters")); - #endif - } - - if (Settings.ConnectionFailuresThreshold) - if (WiFiEventData.connectionFailures > Settings.ConnectionFailuresThreshold) - delayedReboot(60, IntendedRebootReason_e::DelayedReboot); - - if (cmd_within_mainloop != 0) - { - switch (cmd_within_mainloop) - { - case CMD_WIFI_DISCONNECT: - { - WifiDisconnect(); - break; - } - case CMD_REBOOT: - { - reboot(IntendedRebootReason_e::CommandReboot); - break; - } - } - cmd_within_mainloop = 0; - } - // clock events - if (node_time.reportNewMinute()) { - String dummy; - PluginCall(PLUGIN_CLOCK_IN, 0, dummy); - if (Settings.UseRules) - { - // FIXME TD-er: What to do when the system time is not (yet) present? - if (node_time.systemTimePresent()) { - // TD-er: Do not add to the eventQueue, but execute right now. - const String event = strformat( - F("Clock#Time=%s,%s"), - node_time.weekday_str().c_str(), - node_time.getTimeString(':', false).c_str()); - rulesProcessing(event); - } - } - } - -// unsigned long start = micros(); - String dummy; - PluginCall(PLUGIN_ONCE_A_SECOND, 0, dummy); -// unsigned long elapsed = micros() - start; - - - // I2C Watchdog feed - if (Settings.WDI2CAddress != 0) - { - #if FEATURE_I2C_MULTIPLE - I2CSelectHighClockSpeed(Settings.getI2CInterfaceWDT()); // Select bus - #endif // if FEATURE_I2C_MULTIPLE - I2C_write8(Settings.WDI2CAddress, 0xA5); - } - - #if FEATURE_MDNS - #ifdef ESP8266 - // Allow MDNS processing - if (NetworkConnected()) { - MDNS.announce(); - } - #endif - #endif // if FEATURE_MDNS - - #if FEATURE_INTERNAL_TEMPERATURE && defined(ESP32_CLASSIC) - getInternalTemperature(); // Just read the value every second to hopefully get a valid next reading on original ESP32 - #endif // if FEATURE_INTERNAL_TEMPERATURE && defined(ESP32_CLASSIC) - - checkResetFactoryPin(); - STOP_TIMER(PLUGIN_CALL_1PS); -} - -/*********************************************************************************************\ - * Tasks each 30 seconds -\*********************************************************************************************/ -void runEach30Seconds() -{ - #ifndef BUILD_NO_RAM_TRACKER - checkRAMtoLog(); - #endif - wdcounter++; - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = strformat( - F("WD : Uptime %d ConnectFailures %u FreeMem %u"), - getUptimeMinutes(), - WiFiEventData.connectionFailures, - FreeMem()); - bool logWiFiStatus = true; - #if FEATURE_ETHERNET - if(active_network_medium == NetworkMedium_t::Ethernet) { - logWiFiStatus = false; - log += F( " EthSpeedState "); - log += getValue(LabelType::ETH_SPEED_STATE); - log += F(" ETH status: "); - log += EthEventData.ESPEasyEthStatusToString(); - } - #endif // if FEATURE_ETHERNET - if (logWiFiStatus) { - log += strformat( - F(" WiFiStatus: %s ESPeasy internal wifi status: %s"), - ArduinoWifiStatusToString(WiFi.status()).c_str(), - WiFiEventData.ESPeasyWifiStatusToString().c_str()); - } -// log += F(" ListenInterval "); -// log += WiFi.getListenInterval(); - addLogMove(LOG_LEVEL_INFO, log); -#if FEATURE_DEFINE_SERIAL_CONSOLE_PORT -// addLogMove(LOG_LEVEL_INFO, ESPEASY_SERIAL_CONSOLE_PORT.getLogString()); -#endif - } - WiFi_AP_Candidates.purge_expired(); - #if FEATURE_ESPEASY_P2P - sendSysInfoUDP(1); - refreshNodeList(); - #endif - - // sending $stats to homie controller - CPluginCall(CPlugin::Function::CPLUGIN_INTERVAL, 0); - - #if defined(ESP8266) - #if FEATURE_SSDP - if (Settings.UseSSDP) - SSDP_update(); - - #endif // if FEATURE_SSDP - #endif -#if FEATURE_ADC_VCC - if (!WiFiEventData.wifiConnectInProgress) { - vcc = ESP.getVcc() / 1000.0f; - } -#endif - - #if FEATURE_REPORTING - ReportStatus(); - #endif // if FEATURE_REPORTING - -} - -#if FEATURE_MQTT - - -void scheduleNextMQTTdelayQueue() { - if (MQTTDelayHandler != nullptr) { - Scheduler.scheduleNextDelayQueue(SchedulerIntervalTimer_e::TIMER_MQTT_DELAY_QUEUE, MQTTDelayHandler->getNextScheduleTime()); - } -} - -void schedule_all_MQTTimport_tasks() { - controllerIndex_t ControllerIndex = firstEnabledMQTT_ControllerIndex(); - - if (!validControllerIndex(ControllerIndex)) { return; } - - constexpr pluginID_t PLUGIN_MQTT_IMPORT(PLUGIN_ID_MQTT_IMPORT); - - deviceIndex_t DeviceIndex = getDeviceIndex(PLUGIN_MQTT_IMPORT); // Check if P037_MQTTimport is present in the build - if (validDeviceIndex(DeviceIndex)) { - for (taskIndex_t task = 0; task < TASKS_MAX; task++) { - if ((Settings.getPluginID_for_task(task) == PLUGIN_MQTT_IMPORT) && - (Settings.TaskDeviceEnabled[task])) { - // Schedule a call to each enabled MQTT import plugin to notify the broker connection state - EventStruct event(task); - event.Par1 = MQTTclient_connected ? 1 : 0; - Scheduler.schedule_plugin_task_event_timer(DeviceIndex, PLUGIN_MQTT_CONNECTION_STATE, std::move(event)); - } - } - } -} - -void processMQTTdelayQueue() { - if (MQTTDelayHandler == nullptr) { - return; - } - runPeriodicalMQTT(); // Update MQTT connected state. - if (!MQTTclient_connected) { - scheduleNextMQTTdelayQueue(); - return; - } - - START_TIMER; - MQTT_queue_element *element(static_cast(MQTTDelayHandler->getNext())); - - if (element == nullptr) { return; } - - bool handled = false; - - if (element->_call_PLUGIN_PROCESS_CONTROLLER_DATA) { - struct EventStruct TempEvent(element->_taskIndex); - String dummy; - - // FIXME TD-er: Do we need anything from the element in the event? -// TempEvent.String1 = element->_topic; -// TempEvent.String2 = element->_payload; - if (PluginCall(PLUGIN_PROCESS_CONTROLLER_DATA, &TempEvent, dummy)) { - handled = true; - MQTTDelayHandler->markProcessed(true); - } else { - MQTTDelayHandler->markProcessed(false); - } - } else - if (!handled) { - if (MQTTclient.publish(element->_topic.c_str(), element->_payload.c_str(), element->_retained)) { - if (WiFiEventData.connectionFailures > 0) { - --WiFiEventData.connectionFailures; - } - MQTTDelayHandler->markProcessed(true); - } else { - MQTTDelayHandler->markProcessed(false); -#ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - String log = F("MQTT : process MQTT queue not published, "); - log += MQTTDelayHandler->sendQueue.size(); - log += F(" items left in queue"); - addLogMove(LOG_LEVEL_DEBUG, log); - } -#endif // ifndef BUILD_NO_DEBUG - } - } - Scheduler.setIntervalTimerOverride(SchedulerIntervalTimer_e::TIMER_MQTT, 10); // Make sure the MQTT is being processed as soon as possible. - scheduleNextMQTTdelayQueue(); - STOP_TIMER(MQTT_DELAY_QUEUE); -} - -void updateMQTTclient_connected() { - if (MQTTclient_connected != MQTTclient.connected()) { - MQTTclient_connected = !MQTTclient_connected; - if (!MQTTclient_connected) { - if (loglevelActiveFor(LOG_LEVEL_ERROR)) { - String connectionError = F("MQTT : Connection lost, state: "); - connectionError += getMQTT_state(); - addLogMove(LOG_LEVEL_ERROR, connectionError); - } - MQTTclient_must_send_LWT_connected = false; - } else { - // Now schedule all tasks using the MQTT controller. - schedule_all_MQTTimport_tasks(); - } - if (Settings.UseRules) { - if (MQTTclient_connected) { - eventQueue.add(F("MQTT#Connected")); - } else { - eventQueue.add(F("MQTT#Disconnected")); - } - } - } - if (!MQTTclient_connected) { - // As suggested here: https://github.com/letscontrolit/ESPEasy/issues/1356 - if (timermqtt_interval < 30000) { - timermqtt_interval += 500; - } - } else { - timermqtt_interval = 100; - } - Scheduler.setIntervalTimer(SchedulerIntervalTimer_e::TIMER_MQTT); - scheduleNextMQTTdelayQueue(); -} - -void runPeriodicalMQTT() { - // MQTT_KEEPALIVE = 15 seconds. - if (!NetworkConnected(10)) { - updateMQTTclient_connected(); - return; - } - //dont do this in backgroundtasks(), otherwise causes crashes. (https://github.com/letscontrolit/ESPEasy/issues/683) - controllerIndex_t enabledMqttController = firstEnabledMQTT_ControllerIndex(); - if (validControllerIndex(enabledMqttController)) { - if (!MQTTclient.loop()) { - updateMQTTclient_connected(); - if (MQTTCheck(enabledMqttController)) { - updateMQTTclient_connected(); - } - } - } else { - if (MQTTclient.connected()) { - MQTTclient.disconnect(); - updateMQTTclient_connected(); - } - } -} - - -#endif //if FEATURE_MQTT - - - -void logTimerStatistics() { -# ifndef BUILD_NO_DEBUG - const uint8_t loglevel = LOG_LEVEL_DEBUG; -#else - const uint8_t loglevel = LOG_LEVEL_NONE; -#endif - updateLoopStats_30sec(loglevel); -#ifndef BUILD_NO_DEBUG -// logStatistics(loglevel, true); - if (loglevelActiveFor(loglevel)) { - String queueLog = F("Scheduler stats: (called/tasks/max_length/idle%) "); - queueLog += Scheduler.getQueueStats(); - addLogMove(loglevel, queueLog); - } -#endif -} - -void updateLoopStats_30sec(uint8_t loglevel) { - loopCounterLast = loopCounter; - loopCounter = 0; - if (loopCounterLast > loopCounterMax) - loopCounterMax = loopCounterLast; - - Scheduler.updateIdleTimeStats(); - -#ifndef BUILD_NO_DEBUG - if (loglevelActiveFor(loglevel)) { - String log = F("LoopStats: shortestLoop: "); - log += shortestLoop; - log += F(" longestLoop: "); - log += longestLoop; - log += F(" avgLoopDuration: "); - log += loop_usec_duration_total / loopCounter_full; - log += F(" loopCounterMax: "); - log += loopCounterMax; - log += F(" loopCounterLast: "); - log += loopCounterLast; - addLogMove(loglevel, log); - } -#endif - loop_usec_duration_total = 0; - loopCounter_full = 1; -} - - -/********************************************************************************************\ - Clean up all before going to sleep or reboot. - \*********************************************************************************************/ -void flushAndDisconnectAllClients() { - if (anyControllerEnabled()) { -#if FEATURE_MQTT - bool mqttControllerEnabled = validControllerIndex(firstEnabledMQTT_ControllerIndex()); -#endif //if FEATURE_MQTT - unsigned long timer = millis() + 1000; - while (!timeOutReached(timer)) { - // call to all controllers (delay queue) to flush all data. - CPluginCall(CPlugin::Function::CPLUGIN_FLUSH, 0); -#if FEATURE_MQTT - if (mqttControllerEnabled && MQTTclient.connected()) { - MQTTclient.loop(); - } -#endif //if FEATURE_MQTT - } -#if FEATURE_MQTT - if (mqttControllerEnabled && MQTTclient.connected()) { - MQTTclient.disconnect(); - updateMQTTclient_connected(); - } -#endif //if FEATURE_MQTT - saveToRTC(); - delay(100); // Flush anything in the network buffers. - } - process_serialWriteBuffer(); -} - - -void prepareShutdown(IntendedRebootReason_e reason) -{ - WiFiEventData.intent_to_reboot = true; -#if FEATURE_MQTT - runPeriodicalMQTT(); // Flush outstanding MQTT messages -#endif // if FEATURE_MQTT - process_serialWriteBuffer(); - flushAndDisconnectAllClients(); - saveUserVarToRTC(); - setWifiMode(WIFI_OFF); - ESPEASY_FS.end(); - process_serialWriteBuffer(); - delay(100); // give the node time to flush all before reboot or sleep - node_time.now_(); - Scheduler.markIntendedReboot(reason); - saveToRTC(); -} - - +#include "../Helpers/PeriodicalActions.h" + + +#include "../../ESPEasy-Globals.h" + +#include "../ControllerQueue/DelayQueueElements.h" +#include "../ControllerQueue/MQTT_queue_element.h" +#include "../DataStructs/TimingStats.h" +#include "../DataTypes/ESPEasy_plugin_functions.h" +#include "../ESPEasyCore/Controller.h" +#include "../ESPEasyCore/ESPEasyEth.h" +#include "../ESPEasyCore/ESPEasyGPIO.h" +#include "../ESPEasyCore/ESPEasy_Log.h" +#include "../ESPEasyCore/ESPEasyNetwork.h" +#include "../ESPEasyCore/ESPEasyWifi.h" +#include "../ESPEasyCore/ESPEasyRules.h" +#include "../ESPEasyCore/Serial.h" +#include "../Globals/ESPEasyWiFiEvent.h" +#if FEATURE_ETHERNET +#include "../Globals/ESPEasyEthEvent.h" +#endif +#include "../Globals/ESPEasy_Scheduler.h" +#include "../Globals/ESPEasy_time.h" +#include "../Globals/EventQueue.h" +#include "../Globals/MainLoopCommand.h" +#include "../Globals/MQTT.h" +#include "../Globals/NetworkState.h" +#include "../Globals/RTC.h" +#include "../Globals/Services.h" +#include "../Globals/Settings.h" +#include "../Globals/Statistics.h" +#ifdef USES_ESPEASY_NOW +#include "../Globals/ESPEasy_now_handler.h" +#include "../Globals/SendData_DuplicateChecker.h" +#endif +#include "../Globals/WiFi_AP_Candidates.h" +#include "../Helpers/ESPEasyRTC.h" +#include "../Helpers/FS_Helper.h" +#include "../Helpers/Hardware_temperature_sensor.h" +#include "../Helpers/Memory.h" +#include "../Helpers/Misc.h" +#include "../Helpers/Network.h" +#include "../Helpers/Networking.h" +#include "../Helpers/StringGenerator_System.h" +#include "../Helpers/StringGenerator_WiFi.h" +#include "../Helpers/StringProvider.h" + +#ifdef USES_C015 +#include "../../ESPEasy_fdwdecl.h" +#endif + + + +#define PLUGIN_ID_MQTT_IMPORT 37 + + +/*********************************************************************************************\ + * Tasks that run 50 times per second +\*********************************************************************************************/ + +void run50TimesPerSecond() { + String dummy; + { + START_TIMER; + PluginCall(PLUGIN_FIFTY_PER_SECOND, 0, dummy); + STOP_TIMER(PLUGIN_CALL_50PS); + } + { + START_TIMER; + CPluginCall(CPlugin::Function::CPLUGIN_FIFTY_PER_SECOND, 0, dummy); + STOP_TIMER(CPLUGIN_CALL_50PS); + } + #ifdef USES_ESPEASY_NOW + { + if (ESPEasy_now_handler.loop()) { + // FIXME TD-er: Must check if enabled, etc. + } + START_TIMER; + SendData_DuplicateChecker.loop(); + STOP_TIMER(ESPEASY_NOW_DEDUP_LOOP); + } + #endif + + processNextEvent(); +} + +/*********************************************************************************************\ + * Tasks that run 10 times per second +\*********************************************************************************************/ +void run10TimesPerSecond() { + String dummy; + //@giig19767g: WARNING: Monitor10xSec must run before PLUGIN_TEN_PER_SECOND + { + START_TIMER; + GPIO_Monitor10xSec(); + STOP_TIMER(PLUGIN_CALL_10PSU); + } + { + START_TIMER; + PluginCall(PLUGIN_TEN_PER_SECOND, 0, dummy); + STOP_TIMER(PLUGIN_CALL_10PS); + } + { + START_TIMER; +// PluginCall(PLUGIN_UNCONDITIONAL_POLL, 0, dummyString); + PluginCall(PLUGIN_MONITOR, 0, dummy); + STOP_TIMER(PLUGIN_CALL_10PSU); + } + { + START_TIMER; + CPluginCall(CPlugin::Function::CPLUGIN_TEN_PER_SECOND, 0, dummy); + STOP_TIMER(CPLUGIN_CALL_10PS); + } + + #ifdef USES_C015 + if (NetworkConnected()) { + Blynk_Run_c015(); + } + #endif + if (!UseRTOSMultitasking) { + START_TIMER + web_server.handleClient(); + STOP_TIMER(WEBSERVER_HANDLE_CLIENT); + } +} + + +/*********************************************************************************************\ + * Tasks each second +\*********************************************************************************************/ +void runOncePerSecond() +{ + START_TIMER; + updateLogLevelCache(); + dailyResetCounter++; + if (dailyResetCounter > 86400) // 1 day elapsed... //86400 + { + RTC.flashDayCounter=0; + saveToRTC(); + dailyResetCounter=0; + #ifndef BUILD_MINIMAL_OTA + addLog(LOG_LEVEL_INFO, F("SYS : Reset 24h counters")); + #endif + } + + if (Settings.ConnectionFailuresThreshold) + if (WiFiEventData.connectionFailures > Settings.ConnectionFailuresThreshold) + delayedReboot(60, IntendedRebootReason_e::DelayedReboot); + + if (cmd_within_mainloop != 0) + { + switch (cmd_within_mainloop) + { + case CMD_WIFI_DISCONNECT: + { + WifiDisconnect(); + break; + } + case CMD_REBOOT: + { + reboot(IntendedRebootReason_e::CommandReboot); + break; + } + } + cmd_within_mainloop = 0; + } + // clock events + if (node_time.reportNewMinute()) { + String dummy; + PluginCall(PLUGIN_CLOCK_IN, 0, dummy); + if (Settings.UseRules) + { + // FIXME TD-er: What to do when the system time is not (yet) present? + if (node_time.systemTimePresent()) { + // TD-er: Do not add to the eventQueue, but execute right now. + const String event = strformat( + F("Clock#Time=%s,%s"), + node_time.weekday_str().c_str(), + node_time.getTimeString(':', false).c_str()); + rulesProcessing(event); + } + } + } + +// unsigned long start = micros(); + String dummy; + PluginCall(PLUGIN_ONCE_A_SECOND, 0, dummy); +// unsigned long elapsed = micros() - start; + + + // I2C Watchdog feed + if (Settings.WDI2CAddress != 0) + { + #if FEATURE_I2C_MULTIPLE + I2CSelectHighClockSpeed(Settings.getI2CInterfaceWDT()); // Select bus + #endif // if FEATURE_I2C_MULTIPLE + I2C_write8(Settings.WDI2CAddress, 0xA5); + } + + #if FEATURE_MDNS + #ifdef ESP8266 + // Allow MDNS processing + if (NetworkConnected()) { + MDNS.announce(); + } + #endif + #endif // if FEATURE_MDNS + + #if FEATURE_INTERNAL_TEMPERATURE && defined(ESP32_CLASSIC) + getInternalTemperature(); // Just read the value every second to hopefully get a valid next reading on original ESP32 + #endif // if FEATURE_INTERNAL_TEMPERATURE && defined(ESP32_CLASSIC) + + checkResetFactoryPin(); + STOP_TIMER(PLUGIN_CALL_1PS); +} + +/*********************************************************************************************\ + * Tasks each 30 seconds +\*********************************************************************************************/ +void runEach30Seconds() +{ + #ifndef BUILD_NO_RAM_TRACKER + checkRAMtoLog(); + #endif + wdcounter++; + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + String log = strformat( + F("WD : Uptime %d ConnectFailures %u FreeMem %u"), + getUptimeMinutes(), + WiFiEventData.connectionFailures, + FreeMem()); + bool logWiFiStatus = true; + #if FEATURE_ETHERNET + if(active_network_medium == NetworkMedium_t::Ethernet) { + logWiFiStatus = false; + log += F( " EthSpeedState "); + log += getValue(LabelType::ETH_SPEED_STATE); + log += F(" ETH status: "); + log += EthEventData.ESPEasyEthStatusToString(); + } + #endif // if FEATURE_ETHERNET + if (logWiFiStatus) { + log += strformat( + F(" WiFiStatus: %s ESPeasy internal wifi status: %s"), + ArduinoWifiStatusToString(WiFi.status()).c_str(), + WiFiEventData.ESPeasyWifiStatusToString().c_str()); + } +// log += F(" ListenInterval "); +// log += WiFi.getListenInterval(); + addLogMove(LOG_LEVEL_INFO, log); +#if FEATURE_DEFINE_SERIAL_CONSOLE_PORT +// addLogMove(LOG_LEVEL_INFO, ESPEASY_SERIAL_CONSOLE_PORT.getLogString()); +#endif + } + WiFi_AP_Candidates.purge_expired(); + #if FEATURE_ESPEASY_P2P + sendSysInfoUDP(1); + refreshNodeList(); + #endif + + // sending $stats to homie controller + CPluginCall(CPlugin::Function::CPLUGIN_INTERVAL, 0); + + #if defined(ESP8266) + #if FEATURE_SSDP + if (Settings.UseSSDP) + SSDP_update(); + + #endif // if FEATURE_SSDP + #endif +#if FEATURE_ADC_VCC + if (!WiFiEventData.wifiConnectInProgress) { + vcc = ESP.getVcc() / 1000.0f; + } +#endif + + #if FEATURE_REPORTING + ReportStatus(); + #endif // if FEATURE_REPORTING + +} + +#if FEATURE_MQTT + + +void scheduleNextMQTTdelayQueue() { + if (MQTTDelayHandler != nullptr) { + unsigned long nextScheduled(MQTTDelayHandler->getNextScheduleTime()); + #ifdef USES_ESPEASY_NOW + if (!MQTTclient_connected) { + // Sending via the mesh may be retried at shorter intervals + if (timePassedSince(nextScheduled) < -5) { + nextScheduled = millis() + 5; + } + } + #endif + Scheduler.scheduleNextDelayQueue(SchedulerIntervalTimer_e::TIMER_MQTT_DELAY_QUEUE, nextScheduled); + } +} + +void schedule_all_MQTTimport_tasks() { + controllerIndex_t ControllerIndex = firstEnabledMQTT_ControllerIndex(); + + if (!validControllerIndex(ControllerIndex)) { return; } + + constexpr pluginID_t PLUGIN_MQTT_IMPORT(PLUGIN_ID_MQTT_IMPORT); + + deviceIndex_t DeviceIndex = getDeviceIndex(PLUGIN_MQTT_IMPORT); // Check if P037_MQTTimport is present in the build + if (validDeviceIndex(DeviceIndex)) { + for (taskIndex_t task = 0; task < TASKS_MAX; task++) { + if ((Settings.getPluginID_for_task(task) == PLUGIN_MQTT_IMPORT) && + (Settings.TaskDeviceEnabled[task])) { + // Schedule a call to each enabled MQTT import plugin to notify the broker connection state + EventStruct event(task); + event.Par1 = MQTTclient_connected ? 1 : 0; + Scheduler.schedule_plugin_task_event_timer(DeviceIndex, PLUGIN_MQTT_CONNECTION_STATE, std::move(event)); + } + } + } +} + + +void processMQTTdelayQueue() { + if (MQTTDelayHandler == nullptr) { + return; + } + runPeriodicalMQTT(); // Update MQTT connected state. + #ifndef USES_ESPEASY_NOW + // When using ESPEasy_NOW we may still send MQTT messages even when we're not connected. + // For all other situations no need to continue. + if (!MQTTclient_connected) { + scheduleNextMQTTdelayQueue(); + return; + } + #endif + + START_TIMER; + MQTT_queue_element *element(static_cast(MQTTDelayHandler->getNext())); + + if (element == nullptr) { return; } + + bool processed = false; + + if (element->_call_PLUGIN_PROCESS_CONTROLLER_DATA) { + struct EventStruct TempEvent(element->_taskIndex); + String dummy; + + if (PluginCall(PLUGIN_PROCESS_CONTROLLER_DATA, &TempEvent, dummy)) { + processed = true; + } + } + if (!processed) { +#ifdef USES_ESPEASY_NOW + MessageRouteInfo_t messageRouteInfo; + if (element->getMessageRouteInfo() != nullptr) { + messageRouteInfo = *(element->getMessageRouteInfo()); + } + messageRouteInfo.appendUnit(Settings.Unit); + + if (element->_topic.startsWith(F("traceroute/")) || element->_topic.indexOf(F("/traceroute/")) != -1) { + // Special debug feature for ESPEasy-NOW to perform a traceroute of packets. + // The message is prepended by each unit number handling the message. + const String replacement = getValue(LabelType::UNIT_NR); + String message; + message.reserve(replacement.length() + 1 + element->_payload.length()); + message = replacement; + message += ';'; + message += element->_payload; + + processed = processMQTT_message(element->_controller_idx, element->_topic, message, element->_retained, &messageRouteInfo); + } else { + processed = processMQTT_message(element->_controller_idx, element->_topic, element->_payload, element->_retained, &messageRouteInfo); + } +#else + processed = processMQTT_message(element->_controller_idx, element->_topic, element->_payload, element->_retained); +#endif + } + MQTTDelayHandler->markProcessed(processed); + if (processed) { + statusLED(true); + } else { +#ifndef BUILD_NO_DEBUG + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + String log = F("MQTT : process MQTT queue not published, "); + log += MQTTDelayHandler->sendQueue.size(); + log += F(" items left in queue"); + addLogMove(LOG_LEVEL_DEBUG, log); + } +#endif // ifndef BUILD_NO_DEBUG + } + + + Scheduler.setIntervalTimerOverride(SchedulerIntervalTimer_e::TIMER_MQTT, 1); // Make sure the MQTT is being processed as soon as possible. + scheduleNextMQTTdelayQueue(); + STOP_TIMER(MQTT_DELAY_QUEUE); +} + + +bool processMQTT_message(controllerIndex_t controllerIndex, + const String & topic, + const String & payload, + bool retained +#ifdef USES_ESPEASY_NOW + , const MessageRouteInfo_t* messageRouteInfo +#endif + ) +{ + START_TIMER; + bool processed = false; + + #ifdef USES_ESPEASY_NOW + if (!MQTTclient_connected) { + processed = ESPEasy_now_handler.sendToMQTT(controllerIndex, topic, payload, messageRouteInfo); + } + #endif + + if (!processed) { + if (MQTTclient.publish(topic.c_str(), payload.c_str(), retained)) { + // FIXME TD-er: Must check if connected via WiFi or Ethernet + if (WiFiEventData.connectionFailures > 0) { + --WiFiEventData.connectionFailures; + } +#ifndef BUILD_NO_DEBUG +#ifdef USES_ESPEASY_NOW + if (loglevelActiveFor(LOG_LEVEL_DEBUG) && messageRouteInfo != nullptr) { + addLogMove(LOG_LEVEL_DEBUG, concat(F("MQTT : published from mesh: "), messageRouteInfo->toString())); + } +#endif +#endif // ifndef BUILD_NO_DEBUG + processed = true; + } + } + Scheduler.setIntervalTimerOverride(SchedulerIntervalTimer_e::TIMER_MQTT, 10); // Make sure the MQTT is being processed as soon as possible. + scheduleNextMQTTdelayQueue(); + STOP_TIMER(MQTT_DELAY_QUEUE); + return processed; +} + +void updateMQTTclient_connected() { + if (MQTTclient_connected != MQTTclient.connected()) { + MQTTclient_connected = !MQTTclient_connected; + if (!MQTTclient_connected) { + if (loglevelActiveFor(LOG_LEVEL_ERROR)) { + String connectionError = F("MQTT : Connection lost, state: "); + connectionError += getMQTT_state(); + addLogMove(LOG_LEVEL_ERROR, connectionError); + } + MQTTclient_must_send_LWT_connected = false; + } else { + // Now schedule all tasks using the MQTT controller. + schedule_all_MQTTimport_tasks(); + } + if (Settings.UseRules) { + if (MQTTclient_connected) { + eventQueue.add(F("MQTT#Connected")); + } else { + eventQueue.add(F("MQTT#Disconnected")); + } + } + } + if (!MQTTclient_connected) { + // As suggested here: https://github.com/letscontrolit/ESPEasy/issues/1356 + if (timermqtt_interval < 30000) { + timermqtt_interval += 500; + } + } else { + timermqtt_interval = 100; + } + Scheduler.setIntervalTimer(SchedulerIntervalTimer_e::TIMER_MQTT); + scheduleNextMQTTdelayQueue(); +} + +void runPeriodicalMQTT() { + // MQTT_KEEPALIVE = 15 seconds. + if (!NetworkConnected(10)) { + updateMQTTclient_connected(); + return; + } + //dont do this in backgroundtasks(), otherwise causes crashes. (https://github.com/letscontrolit/ESPEasy/issues/683) + controllerIndex_t enabledMqttController = firstEnabledMQTT_ControllerIndex(); + if (validControllerIndex(enabledMqttController)) { + if (!MQTTclient.loop()) { + updateMQTTclient_connected(); + if (MQTTCheck(enabledMqttController)) { + updateMQTTclient_connected(); + } + } + } else { + if (MQTTclient.connected()) { + MQTTclient.disconnect(); + updateMQTTclient_connected(); + } + } +} + + +#endif //if FEATURE_MQTT + + + +void logTimerStatistics() { + static bool firstRun = true; + if (firstRun) { + Scheduler.setIntervalTimerOverride(SchedulerIntervalTimer_e::TIMER_STATISTICS, 1000); + firstRun = false; + } + +# ifndef BUILD_NO_DEBUG + const uint8_t loglevel = LOG_LEVEL_DEBUG; +#else + const uint8_t loglevel = LOG_LEVEL_NONE; +#endif + updateLoopStats_30sec(loglevel); +#ifndef BUILD_NO_DEBUG +// logStatistics(loglevel, true); + if (loglevelActiveFor(loglevel)) { + String queueLog = F("Scheduler stats: (called/tasks/max_length/idle%) "); + queueLog += Scheduler.getQueueStats(); + addLogMove(loglevel, queueLog); + } +#endif +} + +void updateLoopStats_30sec(uint8_t loglevel) { + loopCounterLast = loopCounter; + loopCounter = 0; + if (loopCounterLast > loopCounterMax) + loopCounterMax = loopCounterLast; + + Scheduler.updateIdleTimeStats(); + +#ifndef BUILD_NO_DEBUG + if (loglevelActiveFor(loglevel)) { + String log = F("LoopStats: shortestLoop: "); + log += shortestLoop; + log += F(" longestLoop: "); + log += longestLoop; + log += F(" avgLoopDuration: "); + log += loop_usec_duration_total / loopCounter_full; + log += F(" loopCounterMax: "); + log += loopCounterMax; + log += F(" loopCounterLast: "); + log += loopCounterLast; + addLogMove(loglevel, log); + } +#endif + loop_usec_duration_total = 0; + loopCounter_full = 1; +} + + +/********************************************************************************************\ + Clean up all before going to sleep or reboot. + \*********************************************************************************************/ +void flushAndDisconnectAllClients() { + if (anyControllerEnabled()) { +#if FEATURE_MQTT + bool mqttControllerEnabled = validControllerIndex(firstEnabledMQTT_ControllerIndex()); +#endif //if FEATURE_MQTT + unsigned long timer = millis() + 1000; + while (!timeOutReached(timer)) { + // call to all controllers (delay queue) to flush all data. + CPluginCall(CPlugin::Function::CPLUGIN_FLUSH, 0); +#if FEATURE_MQTT + if (mqttControllerEnabled && MQTTclient.connected()) { + MQTTclient.loop(); + } +#endif //if FEATURE_MQTT + } +#if FEATURE_MQTT + if (mqttControllerEnabled && MQTTclient.connected()) { + MQTTclient.disconnect(); + updateMQTTclient_connected(); + } +#endif //if FEATURE_MQTT + saveToRTC(); + delay(100); // Flush anything in the network buffers. + } + process_serialWriteBuffer(); +} + + +void prepareShutdown(IntendedRebootReason_e reason) +{ + WiFiEventData.intent_to_reboot = true; +#if FEATURE_MQTT + runPeriodicalMQTT(); // Flush outstanding MQTT messages +#endif // if FEATURE_MQTT + process_serialWriteBuffer(); + flushAndDisconnectAllClients(); + saveUserVarToRTC(); + setWifiMode(WIFI_OFF); + #if FEATURE_ETHERNET + ethPower(false); + #endif + ESPEASY_FS.end(); + process_serialWriteBuffer(); + delay(100); // give the node time to flush all before reboot or sleep + node_time.now_(); + Scheduler.markIntendedReboot(reason); + saveToRTC(); +} + + diff --git a/src/src/Helpers/PeriodicalActions.h b/src/src/Helpers/PeriodicalActions.h index 01031442b2..63061d5632 100644 --- a/src/src/Helpers/PeriodicalActions.h +++ b/src/src/Helpers/PeriodicalActions.h @@ -1,56 +1,66 @@ -#ifndef HELPERS_PERIODICALACTIONS_H -#define HELPERS_PERIODICALACTIONS_H - -#include "../../ESPEasy_common.h" - -#include "../Globals/CPlugins.h" -#include "../Helpers/Scheduler.h" - -/*********************************************************************************************\ - * Tasks that run 50 times per second -\*********************************************************************************************/ - -void run50TimesPerSecond(); - -/*********************************************************************************************\ - * Tasks that run 10 times per second -\*********************************************************************************************/ -void run10TimesPerSecond(); - - -/*********************************************************************************************\ - * Tasks each second -\*********************************************************************************************/ -void runOncePerSecond(); - -/*********************************************************************************************\ - * Tasks each 30 seconds -\*********************************************************************************************/ -void runEach30Seconds(); - -#if FEATURE_MQTT - -void scheduleNextMQTTdelayQueue(); -void schedule_all_MQTTimport_tasks(); - -void processMQTTdelayQueue(); - -void updateMQTTclient_connected(); - -void runPeriodicalMQTT(); - -#endif //if FEATURE_MQTT - - -void logTimerStatistics(); - -void updateLoopStats_30sec(uint8_t loglevel); - -/********************************************************************************************\ - Clean up all before going to sleep or reboot. - \*********************************************************************************************/ -void prepareShutdown(IntendedRebootReason_e reason); - - - +#ifndef HELPERS_PERIODICALACTIONS_H +#define HELPERS_PERIODICALACTIONS_H + +#include "../../ESPEasy_common.h" + +#include "../DataStructs/MessageRouteInfo.h" +#include "../Globals/CPlugins.h" +#include "../Helpers/Scheduler.h" + +/*********************************************************************************************\ + * Tasks that run 50 times per second +\*********************************************************************************************/ + +void run50TimesPerSecond(); + +/*********************************************************************************************\ + * Tasks that run 10 times per second +\*********************************************************************************************/ +void run10TimesPerSecond(); + + +/*********************************************************************************************\ + * Tasks each second +\*********************************************************************************************/ +void runOncePerSecond(); + +/*********************************************************************************************\ + * Tasks each 30 seconds +\*********************************************************************************************/ +void runEach30Seconds(); + +#if FEATURE_MQTT + +void scheduleNextMQTTdelayQueue(); +void schedule_all_MQTTimport_tasks(); + +void processMQTTdelayQueue(); + +bool processMQTT_message(controllerIndex_t controllerIndex, + const String & topic, + const String & payload, + bool retained +#ifdef USES_ESPEASY_NOW + , const MessageRouteInfo_t* messageRouteInfo +#endif + ); + +void updateMQTTclient_connected(); + +void runPeriodicalMQTT(); + +#endif //if FEATURE_MQTT + + +void logTimerStatistics(); + +void updateLoopStats_30sec(uint8_t loglevel); + +/********************************************************************************************\ + Clean up all before going to sleep or reboot. + \*********************************************************************************************/ +void prepareShutdown(IntendedRebootReason_e reason); + + + #endif // HELPERS_PERIODICALACTIONS_H \ No newline at end of file diff --git a/src/src/Helpers/Scheduler.cpp b/src/src/Helpers/Scheduler.cpp index 98ecf4b25d..176e2b9a07 100644 --- a/src/src/Helpers/Scheduler.cpp +++ b/src/src/Helpers/Scheduler.cpp @@ -1,112 +1,113 @@ -#include "../Helpers/Scheduler.h" - -#include "../../ESPEasy-Globals.h" -#include "../../_Plugin_Helper.h" - -#include "../DataStructs/Scheduler_IntendedRebootTimerID.h" -#include "../DataStructs/TimingStats.h" - -#include "../ESPEasyCore/ESPEasyRules.h" - -#include "../Globals/RTC.h" - -#include "../Helpers/ESPEasyRTC.h" - - -void ESPEasy_Scheduler::markIntendedReboot(IntendedRebootReason_e reason) { - const IntendedRebootTimerID id(reason); - - RTC.lastMixedSchedulerId = id.mixed_id; - saveToRTC(); -} - -/*********************************************************************************************\ -* Generic Timer functions. -\*********************************************************************************************/ -void ESPEasy_Scheduler::setNewTimerAt(SchedulerTimerID id, unsigned long timer) { - START_TIMER; - msecTimerHandler.registerAt(id.mixed_id, timer); - STOP_TIMER(SET_NEW_TIMER); -} - -/*********************************************************************************************\ -* Handle scheduled timers. -\*********************************************************************************************/ -void ESPEasy_Scheduler::handle_schedule() { - START_TIMER - unsigned long timer = 0; - unsigned long mixed_id = 0; - - if (timePassedSince(last_system_event_run) < 500) { - // Make sure system event queue will be looked at every now and then. - mixed_id = msecTimerHandler.getNextId(timer); - } - - if (RTC.lastMixedSchedulerId != mixed_id) { - RTC.lastMixedSchedulerId = mixed_id; - saveToRTC(); - } - - if (mixed_id == 0) { - // No id ready to run right now. - // Events are not that important to run immediately. - // Make sure normal scheduled jobs run at higher priority. - // backgroundtasks(); - process_system_event_queue(); - - // System events may have added one or more rule events, try to process those - processNextEvent(); - last_system_event_run = millis(); - STOP_TIMER(HANDLE_SCHEDULER_IDLE); - return; - } - - const SchedulerTimerID timerID(mixed_id); - - delay(0); // See: https://github.com/letscontrolit/ESPEasy/issues/1818#issuecomment-425351328 - - switch (timerID.getTimerType()) { - case SchedulerTimerType_e::ConstIntervalTimer: - process_interval_timer(timerID, timer); - break; - case SchedulerTimerType_e::PLUGIN_TASKTIMER_IN_e: - process_plugin_task_timer(timerID); - break; - case SchedulerTimerType_e::PLUGIN_DEVICETIMER_IN_e: - process_plugin_timer(timerID); - break; - case SchedulerTimerType_e::RulesTimer: - process_rules_timer(timerID, timer); - break; - case SchedulerTimerType_e::TaskDeviceTimer: - process_task_device_timer(timerID, timer); - break; - case SchedulerTimerType_e::GPIO_timer: - process_gpio_timer(timerID, timer); - break; - - case SchedulerTimerType_e::SystemEventQueue: - case SchedulerTimerType_e::IntendedReboot: - // TD-er: Not really something that needs to be processed here. - // - SystemEventQueue has its own ScheduledEventQueue which isn't time based. - // - IntendedReboot is just used to mark the intended reboot reason in RTC. - break; - } - STOP_TIMER(HANDLE_SCHEDULER_TASK); -} - -String ESPEasy_Scheduler::getQueueStats() { - return msecTimerHandler.getQueueStats(); -} - -void ESPEasy_Scheduler::updateIdleTimeStats() { - msecTimerHandler.updateIdleTimeStats(); -} - -float ESPEasy_Scheduler::getIdleTimePct() const { - return msecTimerHandler.getIdleTimePct(); -} - -void ESPEasy_Scheduler::setEcoMode(bool enabled) { - msecTimerHandler.setEcoMode(enabled); -} +#include "../Helpers/Scheduler.h" + +#include "../../ESPEasy-Globals.h" +#include "../../_Plugin_Helper.h" + +#include "../DataStructs/Scheduler_IntendedRebootTimerID.h" +#include "../DataStructs/TimingStats.h" + +#include "../ESPEasyCore/ESPEasyRules.h" + +#include "../Globals/RTC.h" + +#include "../Helpers/ESPEasyRTC.h" + + +void ESPEasy_Scheduler::markIntendedReboot(IntendedRebootReason_e reason) { + const IntendedRebootTimerID id(reason); + + RTC.lastMixedSchedulerId = id.mixed_id; + saveToRTC(); +} + +/*********************************************************************************************\ +* Generic Timer functions. +\*********************************************************************************************/ +void ESPEasy_Scheduler::setNewTimerAt(SchedulerTimerID id, unsigned long timer) { + START_TIMER; + msecTimerHandler.registerAt(id.mixed_id, timer); + STOP_TIMER(SET_NEW_TIMER); +} + +/*********************************************************************************************\ +* Handle scheduled timers. +\*********************************************************************************************/ +void ESPEasy_Scheduler::handle_schedule() { + START_TIMER + unsigned long timer = 0; + unsigned long mixed_id = 0; + + if (timePassedSince(last_system_event_run) < 500) { + // Make sure system event queue will be looked at every now and then. + mixed_id = msecTimerHandler.getNextId(timer); + } + + if (RTC.lastMixedSchedulerId != mixed_id) { + RTC.lastMixedSchedulerId = mixed_id; + saveToRTC(); + } + + if (mixed_id == 0) { + // No id ready to run right now. + // Events are not that important to run immediately. + // Make sure normal scheduled jobs run at higher priority. + // backgroundtasks(); + process_system_event_queue(); + + // System events may have added one or more rule events, try to process those + processNextEvent(); + last_system_event_run = millis(); + STOP_TIMER(HANDLE_SCHEDULER_IDLE); + return; + } + + const SchedulerTimerID timerID(mixed_id); + + delay(0); // See: https://github.com/letscontrolit/ESPEasy/issues/1818#issuecomment-425351328 + + switch (timerID.getTimerType()) { + case SchedulerTimerType_e::ConstIntervalTimer: + process_interval_timer(timerID, timer); + break; + case SchedulerTimerType_e::PLUGIN_TASKTIMER_IN_e: + process_plugin_task_timer(timerID); + break; + case SchedulerTimerType_e::PLUGIN_DEVICETIMER_IN_e: + process_plugin_timer(timerID); + break; + case SchedulerTimerType_e::RulesTimer: + process_rules_timer(timerID, timer); + break; + case SchedulerTimerType_e::TaskDeviceTimer: + process_task_device_timer(timerID, timer); + break; + case SchedulerTimerType_e::GPIO_timer: + process_gpio_timer(timerID, timer); + break; + + case SchedulerTimerType_e::SystemEventQueue: + case SchedulerTimerType_e::IntendedReboot: + // TD-er: Not really something that needs to be processed here. + // - SystemEventQueue has its own ScheduledEventQueue which isn't time based. + // - IntendedReboot is just used to mark the intended reboot reason in RTC. + break; + } + STOP_TIMER(HANDLE_SCHEDULER_TASK); +} + + +String ESPEasy_Scheduler::getQueueStats() { + return msecTimerHandler.getQueueStats(); +} + +void ESPEasy_Scheduler::updateIdleTimeStats() { + msecTimerHandler.updateIdleTimeStats(); +} + +float ESPEasy_Scheduler::getIdleTimePct() const { + return msecTimerHandler.getIdleTimePct(); +} + +void ESPEasy_Scheduler::setEcoMode(bool enabled) { + msecTimerHandler.setEcoMode(enabled); +} diff --git a/src/src/Helpers/Scheduler_IntervalTimer.cpp b/src/src/Helpers/Scheduler_IntervalTimer.cpp index 345d3decd5..ae3d3d9cfc 100644 --- a/src/src/Helpers/Scheduler_IntervalTimer.cpp +++ b/src/src/Helpers/Scheduler_IntervalTimer.cpp @@ -1,342 +1,340 @@ -#include "../Helpers/Scheduler.h" - - -#include "../../ESPEasy-Globals.h" - -#include "../ControllerQueue/DelayQueueElements.h" - -#include "../DataStructs/Scheduler_ConstIntervalTimerID.h" - -#include "../Globals/Settings.h" - -#include "../Helpers/ESPEasy_time_calc.h" -#include "../Helpers/Networking.h" -#include "../Helpers/PeriodicalActions.h" - - -/*********************************************************************************************\ -* Interval Timer -* These timers set a new scheduled timer, based on the old value. -* This will make their interval as constant as possible. -\*********************************************************************************************/ - -// Interval where it is more important to actually run the scheduled job, instead of keeping the time drift to a minimum. -// For example running the PLUGIN_FIFTY_PER_SECOND calls probably need to run as fast as possible as they need to fetch data before a buffer -// overflow happens. -// For those it is more important to actually run it than keeping pace. -void ESPEasy_Scheduler::setNextTimeInterval(unsigned long& timer, const unsigned long step) { - timer += step; - const long passed = timePassedSince(timer); - - if (passed < 0) { - // Event has not yet happened, which is fine. - return; - } - - if (static_cast(passed) > step) { - // No need to keep running behind, start again. - timer = millis() + step; - return; - } - - // Try to get in sync again. - timer = millis() + (step - passed); -} - -// More strict interval where no time drift is more important than missing a scheduled interval. -// For example timing for repeating longPulse where 2 scheduled intervals need to be at constant 'distance' from each other. -void ESPEasy_Scheduler::setNextStrictTimeInterval(unsigned long & timer, - const unsigned long step) { - timer += step; - const long passed = timePassedSince(timer); - - if (passed <= 0) { - // Event has not yet happened, which is fine. - return; - } - - // Try to get in sync again. - const unsigned long stepsMissed = static_cast(passed) / step; - - timer += (stepsMissed + 1) * step; -} - -void ESPEasy_Scheduler::setIntervalTimer(SchedulerIntervalTimer_e intervalTimer) { - setIntervalTimer(intervalTimer, millis()); -} - -void ESPEasy_Scheduler::setIntervalTimerAt(SchedulerIntervalTimer_e intervalTimer, unsigned long newtimer) { - const ConstIntervalTimerID timerID(intervalTimer); - - setNewTimerAt(timerID, newtimer); -} - -void ESPEasy_Scheduler::setIntervalTimerOverride(SchedulerIntervalTimer_e intervalTimer, unsigned long msecFromNow) { - unsigned long timer = millis(); - - setNextTimeInterval(timer, msecFromNow); - const ConstIntervalTimerID timerID(intervalTimer); - - setNewTimerAt(timerID, timer); -} - -void ESPEasy_Scheduler::scheduleNextDelayQueue(SchedulerIntervalTimer_e intervalTimer, unsigned long nextTime) { - if (nextTime != 0) { - // Schedule for next process run. - setIntervalTimerAt(intervalTimer, nextTime); - } -} - -void ESPEasy_Scheduler::setIntervalTimer(SchedulerIntervalTimer_e intervalTimer, unsigned long lasttimer) { - // Set the initial timers for the regular runs - unsigned long interval = 0; - - switch (intervalTimer) { - case SchedulerIntervalTimer_e::TIMER_20MSEC: interval = 20; break; - case SchedulerIntervalTimer_e::TIMER_100MSEC: interval = 100; break; - case SchedulerIntervalTimer_e::TIMER_1SEC: interval = 1000; break; - case SchedulerIntervalTimer_e::TIMER_30SEC: - case SchedulerIntervalTimer_e::TIMER_STATISTICS: interval = 30000; break; - case SchedulerIntervalTimer_e::TIMER_MQTT: interval = timermqtt_interval; break; - case SchedulerIntervalTimer_e::TIMER_GRATUITOUS_ARP: interval = timer_gratuitous_arp_interval; break; - - // Fall-through for all DelayQueue, which are just the fall-back timers. - // The timers for all delay queues will be set according to their own settings as long as there is something to process. - case SchedulerIntervalTimer_e::TIMER_MQTT_DELAY_QUEUE: - case SchedulerIntervalTimer_e::TIMER_C001_DELAY_QUEUE: - case SchedulerIntervalTimer_e::TIMER_C002_DELAY_QUEUE: - case SchedulerIntervalTimer_e::TIMER_C003_DELAY_QUEUE: - case SchedulerIntervalTimer_e::TIMER_C004_DELAY_QUEUE: - case SchedulerIntervalTimer_e::TIMER_C005_DELAY_QUEUE: - case SchedulerIntervalTimer_e::TIMER_C006_DELAY_QUEUE: - case SchedulerIntervalTimer_e::TIMER_C007_DELAY_QUEUE: - case SchedulerIntervalTimer_e::TIMER_C008_DELAY_QUEUE: - case SchedulerIntervalTimer_e::TIMER_C009_DELAY_QUEUE: - case SchedulerIntervalTimer_e::TIMER_C010_DELAY_QUEUE: - case SchedulerIntervalTimer_e::TIMER_C011_DELAY_QUEUE: - case SchedulerIntervalTimer_e::TIMER_C012_DELAY_QUEUE: - case SchedulerIntervalTimer_e::TIMER_C013_DELAY_QUEUE: - case SchedulerIntervalTimer_e::TIMER_C014_DELAY_QUEUE: - case SchedulerIntervalTimer_e::TIMER_C015_DELAY_QUEUE: - case SchedulerIntervalTimer_e::TIMER_C016_DELAY_QUEUE: - case SchedulerIntervalTimer_e::TIMER_C017_DELAY_QUEUE: - case SchedulerIntervalTimer_e::TIMER_C018_DELAY_QUEUE: - case SchedulerIntervalTimer_e::TIMER_C019_DELAY_QUEUE: - case SchedulerIntervalTimer_e::TIMER_C020_DELAY_QUEUE: - case SchedulerIntervalTimer_e::TIMER_C021_DELAY_QUEUE: - case SchedulerIntervalTimer_e::TIMER_C022_DELAY_QUEUE: - case SchedulerIntervalTimer_e::TIMER_C023_DELAY_QUEUE: - case SchedulerIntervalTimer_e::TIMER_C024_DELAY_QUEUE: - case SchedulerIntervalTimer_e::TIMER_C025_DELAY_QUEUE: - // When extending this, search for EXTEND_CONTROLLER_IDS - // in the code to find all places that need to be updated too. - interval = 1000; break; - } - unsigned long timer = lasttimer; - - setNextTimeInterval(timer, interval); - const ConstIntervalTimerID timerID(intervalTimer); - - setNewTimerAt(timerID, timer); -} - -void ESPEasy_Scheduler::sendGratuitousARP_now() { - sendGratuitousARP(); - - if (Settings.gratuitousARP()) { - timer_gratuitous_arp_interval = 100; - setIntervalTimer(SchedulerIntervalTimer_e::TIMER_GRATUITOUS_ARP); - } -} - -void ESPEasy_Scheduler::process_interval_timer(SchedulerTimerID timerID, unsigned long lasttimer) { - // Set the interval timer now, it may be altered by the commands below. - // This is the default next-run-time. - - const ConstIntervalTimerID *tmp = reinterpret_cast(&timerID); - const SchedulerIntervalTimer_e intervalTimer = tmp->getIntervalTimer(); - - setIntervalTimer(intervalTimer, lasttimer); - - switch (intervalTimer) { - case SchedulerIntervalTimer_e::TIMER_20MSEC: run50TimesPerSecond(); break; - case SchedulerIntervalTimer_e::TIMER_100MSEC: - - if (!UseRTOSMultitasking) { - run10TimesPerSecond(); - } - break; - case SchedulerIntervalTimer_e::TIMER_1SEC: runOncePerSecond(); break; - case SchedulerIntervalTimer_e::TIMER_30SEC: runEach30Seconds(); break; - case SchedulerIntervalTimer_e::TIMER_MQTT: -#if FEATURE_MQTT - runPeriodicalMQTT(); -#endif // if FEATURE_MQTT - break; - case SchedulerIntervalTimer_e::TIMER_STATISTICS: logTimerStatistics(); break; - case SchedulerIntervalTimer_e::TIMER_GRATUITOUS_ARP: - - // Slowly increase the interval timer. - timer_gratuitous_arp_interval = 2 * timer_gratuitous_arp_interval; - - if (timer_gratuitous_arp_interval > TIMER_GRATUITOUS_ARP_MAX) { - timer_gratuitous_arp_interval = TIMER_GRATUITOUS_ARP_MAX; - } - - if (Settings.gratuitousARP()) { - sendGratuitousARP(); - } - break; - case SchedulerIntervalTimer_e::TIMER_MQTT_DELAY_QUEUE: - case SchedulerIntervalTimer_e::TIMER_C002_DELAY_QUEUE: - case SchedulerIntervalTimer_e::TIMER_C005_DELAY_QUEUE: - case SchedulerIntervalTimer_e::TIMER_C006_DELAY_QUEUE: -#if FEATURE_MQTT - processMQTTdelayQueue(); -#endif // if FEATURE_MQTT - break; - case SchedulerIntervalTimer_e::TIMER_C001_DELAY_QUEUE: - #ifdef USES_C001 - process_c001_delay_queue(); - #endif // ifdef USES_C001 - break; - case SchedulerIntervalTimer_e::TIMER_C003_DELAY_QUEUE: - #ifdef USES_C003 - process_c003_delay_queue(); - #endif // ifdef USES_C003 - break; - case SchedulerIntervalTimer_e::TIMER_C004_DELAY_QUEUE: - #ifdef USES_C004 - process_c004_delay_queue(); - #endif // ifdef USES_C004 - break; - case SchedulerIntervalTimer_e::TIMER_C007_DELAY_QUEUE: - #ifdef USES_C007 - process_c007_delay_queue(); - #endif // ifdef USES_C007 - break; - case SchedulerIntervalTimer_e::TIMER_C008_DELAY_QUEUE: - #ifdef USES_C008 - process_c008_delay_queue(); - #endif // ifdef USES_C008 - break; - case SchedulerIntervalTimer_e::TIMER_C009_DELAY_QUEUE: - #ifdef USES_C009 - process_c009_delay_queue(); - #endif // ifdef USES_C009 - break; - case SchedulerIntervalTimer_e::TIMER_C010_DELAY_QUEUE: - #ifdef USES_C010 - process_c010_delay_queue(); - #endif // ifdef USES_C010 - break; - case SchedulerIntervalTimer_e::TIMER_C011_DELAY_QUEUE: - #ifdef USES_C011 - process_c011_delay_queue(); - #endif // ifdef USES_C011 - break; - case SchedulerIntervalTimer_e::TIMER_C012_DELAY_QUEUE: - #ifdef USES_C012 - process_c012_delay_queue(); - #endif // ifdef USES_C012 - break; - - case SchedulerIntervalTimer_e::TIMER_C013_DELAY_QUEUE: - /* - #ifdef USES_C013 - process_c013_delay_queue(); - #endif - */ - break; - - case SchedulerIntervalTimer_e::TIMER_C014_DELAY_QUEUE: - /* - #ifdef USES_C014 - process_c014_delay_queue(); - #endif - */ - break; - - case SchedulerIntervalTimer_e::TIMER_C015_DELAY_QUEUE: - #ifdef USES_C015 - process_c015_delay_queue(); - #endif // ifdef USES_C015 - break; - case SchedulerIntervalTimer_e::TIMER_C016_DELAY_QUEUE: - #ifdef USES_C016 - process_c016_delay_queue(); - #endif // ifdef USES_C016 - break; - - case SchedulerIntervalTimer_e::TIMER_C017_DELAY_QUEUE: - #ifdef USES_C017 - process_c017_delay_queue(); - #endif // ifdef USES_C017 - break; - - case SchedulerIntervalTimer_e::TIMER_C018_DELAY_QUEUE: - #ifdef USES_C018 - process_c018_delay_queue(); - #endif // ifdef USES_C018 - break; - - case SchedulerIntervalTimer_e::TIMER_C019_DELAY_QUEUE: - /* - #ifdef USES_C019 - process_c019_delay_queue(); - #endif - */ - break; - - case SchedulerIntervalTimer_e::TIMER_C020_DELAY_QUEUE: - /* - #ifdef USES_C020 - process_c020_delay_queue(); - #endif - */ - break; - - case SchedulerIntervalTimer_e::TIMER_C021_DELAY_QUEUE: - /* - #ifdef USES_C021 - process_c021_delay_queue(); - #endif - */ - break; - - case SchedulerIntervalTimer_e::TIMER_C022_DELAY_QUEUE: - /* - #ifdef USES_C022 - process_c022_delay_queue(); - #endif - */ - break; - - case SchedulerIntervalTimer_e::TIMER_C023_DELAY_QUEUE: - /* - #ifdef USES_C023 - process_c023_delay_queue(); - #endif - */ - break; - - case SchedulerIntervalTimer_e::TIMER_C024_DELAY_QUEUE: - /* - #ifdef USES_C024 - process_c024_delay_queue(); - #endif - */ - break; - - case SchedulerIntervalTimer_e::TIMER_C025_DELAY_QUEUE: - /* - #ifdef USES_C025 - process_c025_delay_queue(); - #endif - */ - break; - - // When extending this, search for EXTEND_CONTROLLER_IDS - // in the code to find all places that need to be updated too. - } -} +#include "../Helpers/Scheduler.h" + + +#include "../../ESPEasy-Globals.h" + +#include "../ControllerQueue/DelayQueueElements.h" + +#include "../DataStructs/Scheduler_ConstIntervalTimerID.h" + +#include "../Globals/Settings.h" + +#include "../Helpers/ESPEasy_time_calc.h" +#include "../Helpers/Networking.h" +#include "../Helpers/PeriodicalActions.h" + + +/*********************************************************************************************\ +* Interval Timer +* These timers set a new scheduled timer, based on the old value. +* This will make their interval as constant as possible. +\*********************************************************************************************/ + +// Interval where it is more important to actually run the scheduled job, instead of keeping the time drift to a minimum. +// For example running the PLUGIN_FIFTY_PER_SECOND calls probably need to run as fast as possible as they need to fetch data before a buffer +// overflow happens. +// For those it is more important to actually run it than keeping pace. +void ESPEasy_Scheduler::setNextTimeInterval(unsigned long& timer, const unsigned long step) { + timer += step; + const long passed = timePassedSince(timer); + + if (passed < 0) { + // Event has not yet happened, which is fine. + return; + } + + if (static_cast(passed) > step) { + // No need to keep running behind, start again. + timer = millis() + step; + return; + } + + // Try to get in sync again. + timer = millis() + (step - passed); +} + +// More strict interval where no time drift is more important than missing a scheduled interval. +// For example timing for repeating longPulse where 2 scheduled intervals need to be at constant 'distance' from each other. +void ESPEasy_Scheduler::setNextStrictTimeInterval(unsigned long & timer, + const unsigned long step) { + timer += step; + const long passed = timePassedSince(timer); + + if (passed <= 0) { + // Event has not yet happened, which is fine. + return; + } + + // Try to get in sync again. + const unsigned long stepsMissed = static_cast(passed) / step; + + timer += (stepsMissed + 1) * step; +} + +void ESPEasy_Scheduler::setIntervalTimer(SchedulerIntervalTimer_e intervalTimer) { + setIntervalTimer(intervalTimer, millis()); +} + +void ESPEasy_Scheduler::setIntervalTimerAt(SchedulerIntervalTimer_e intervalTimer, unsigned long newtimer) { + const ConstIntervalTimerID timerID(intervalTimer); + + setNewTimerAt(timerID, newtimer); +} + +void ESPEasy_Scheduler::setIntervalTimerOverride(SchedulerIntervalTimer_e intervalTimer, unsigned long msecFromNow) { + unsigned long timer = millis(); + + setNextTimeInterval(timer, msecFromNow); + const ConstIntervalTimerID timerID(intervalTimer); + + setNewTimerAt(timerID, timer); +} + +void ESPEasy_Scheduler::scheduleNextDelayQueue(SchedulerIntervalTimer_e intervalTimer, unsigned long nextTime) { + if (nextTime != 0) { + // Schedule for next process run. + setIntervalTimerAt(intervalTimer, nextTime); + } +} + +void ESPEasy_Scheduler::setIntervalTimer(SchedulerIntervalTimer_e intervalTimer, unsigned long lasttimer) { + // Set the initial timers for the regular runs + unsigned long interval = 0; + + switch (intervalTimer) { + case SchedulerIntervalTimer_e::TIMER_20MSEC: interval = 20; break; + case SchedulerIntervalTimer_e::TIMER_100MSEC: interval = 100; break; + case SchedulerIntervalTimer_e::TIMER_1SEC: interval = 1000; break; + case SchedulerIntervalTimer_e::TIMER_30SEC: + case SchedulerIntervalTimer_e::TIMER_STATISTICS: interval = 30000; break; + case SchedulerIntervalTimer_e::TIMER_MQTT: interval = timermqtt_interval; break; + case SchedulerIntervalTimer_e::TIMER_GRATUITOUS_ARP: interval = timer_gratuitous_arp_interval; break; + + // Fall-through for all DelayQueue, which are just the fall-back timers. + // The timers for all delay queues will be set according to their own settings as long as there is something to process. + case SchedulerIntervalTimer_e::TIMER_MQTT_DELAY_QUEUE: + case SchedulerIntervalTimer_e::TIMER_C001_DELAY_QUEUE: + case SchedulerIntervalTimer_e::TIMER_C002_DELAY_QUEUE: + case SchedulerIntervalTimer_e::TIMER_C003_DELAY_QUEUE: + case SchedulerIntervalTimer_e::TIMER_C004_DELAY_QUEUE: + case SchedulerIntervalTimer_e::TIMER_C005_DELAY_QUEUE: + case SchedulerIntervalTimer_e::TIMER_C006_DELAY_QUEUE: + case SchedulerIntervalTimer_e::TIMER_C007_DELAY_QUEUE: + case SchedulerIntervalTimer_e::TIMER_C008_DELAY_QUEUE: + case SchedulerIntervalTimer_e::TIMER_C009_DELAY_QUEUE: + case SchedulerIntervalTimer_e::TIMER_C010_DELAY_QUEUE: + case SchedulerIntervalTimer_e::TIMER_C011_DELAY_QUEUE: + case SchedulerIntervalTimer_e::TIMER_C012_DELAY_QUEUE: + case SchedulerIntervalTimer_e::TIMER_C013_DELAY_QUEUE: + case SchedulerIntervalTimer_e::TIMER_C014_DELAY_QUEUE: + case SchedulerIntervalTimer_e::TIMER_C015_DELAY_QUEUE: + case SchedulerIntervalTimer_e::TIMER_C016_DELAY_QUEUE: + case SchedulerIntervalTimer_e::TIMER_C017_DELAY_QUEUE: + case SchedulerIntervalTimer_e::TIMER_C018_DELAY_QUEUE: + case SchedulerIntervalTimer_e::TIMER_C019_DELAY_QUEUE: + case SchedulerIntervalTimer_e::TIMER_C020_DELAY_QUEUE: + case SchedulerIntervalTimer_e::TIMER_C021_DELAY_QUEUE: + case SchedulerIntervalTimer_e::TIMER_C022_DELAY_QUEUE: + case SchedulerIntervalTimer_e::TIMER_C023_DELAY_QUEUE: + case SchedulerIntervalTimer_e::TIMER_C024_DELAY_QUEUE: + case SchedulerIntervalTimer_e::TIMER_C025_DELAY_QUEUE: + // When extending this, search for EXTEND_CONTROLLER_IDS + // in the code to find all places that need to be updated too. + interval = 1000; break; + } + unsigned long timer = lasttimer; + + setNextTimeInterval(timer, interval); + const ConstIntervalTimerID timerID(intervalTimer); + + setNewTimerAt(timerID, timer); +} + +void ESPEasy_Scheduler::sendGratuitousARP_now() { + sendGratuitousARP(); + + if (Settings.gratuitousARP()) { + timer_gratuitous_arp_interval = 100; + setIntervalTimer(SchedulerIntervalTimer_e::TIMER_GRATUITOUS_ARP); + } +} + +void ESPEasy_Scheduler::process_interval_timer(SchedulerTimerID timerID, unsigned long lasttimer) { + // Set the interval timer now, it may be altered by the commands below. + // This is the default next-run-time. + + const ConstIntervalTimerID *tmp = reinterpret_cast(&timerID); + const SchedulerIntervalTimer_e intervalTimer = tmp->getIntervalTimer(); + + setIntervalTimer(intervalTimer, lasttimer); + + switch (intervalTimer) { + case SchedulerIntervalTimer_e::TIMER_20MSEC: run50TimesPerSecond(); break; + case SchedulerIntervalTimer_e::TIMER_100MSEC: + + if (!UseRTOSMultitasking) { + run10TimesPerSecond(); + } + break; + case SchedulerIntervalTimer_e::TIMER_1SEC: runOncePerSecond(); break; + case SchedulerIntervalTimer_e::TIMER_30SEC: runEach30Seconds(); break; + case SchedulerIntervalTimer_e::TIMER_MQTT: +#if FEATURE_MQTT + runPeriodicalMQTT(); +#endif // if FEATURE_MQTT + break; + case SchedulerIntervalTimer_e::TIMER_STATISTICS: logTimerStatistics(); break; + case SchedulerIntervalTimer_e::TIMER_GRATUITOUS_ARP: + + // Slowly increase the interval timer. + timer_gratuitous_arp_interval = 2 * timer_gratuitous_arp_interval; + + if (timer_gratuitous_arp_interval > TIMER_GRATUITOUS_ARP_MAX) { + timer_gratuitous_arp_interval = TIMER_GRATUITOUS_ARP_MAX; + } + + if (Settings.gratuitousARP()) { + sendGratuitousARP(); + } + break; + case SchedulerIntervalTimer_e::TIMER_MQTT_DELAY_QUEUE: + case SchedulerIntervalTimer_e::TIMER_C002_DELAY_QUEUE: + case SchedulerIntervalTimer_e::TIMER_C005_DELAY_QUEUE: + case SchedulerIntervalTimer_e::TIMER_C006_DELAY_QUEUE: +#if FEATURE_MQTT + processMQTTdelayQueue(); +#endif // if FEATURE_MQTT + break; + case SchedulerIntervalTimer_e::TIMER_C001_DELAY_QUEUE: + #ifdef USES_C001 + process_c001_delay_queue(); + #endif // ifdef USES_C001 + break; + case SchedulerIntervalTimer_e::TIMER_C003_DELAY_QUEUE: + #ifdef USES_C003 + process_c003_delay_queue(); + #endif // ifdef USES_C003 + break; + case SchedulerIntervalTimer_e::TIMER_C004_DELAY_QUEUE: + #ifdef USES_C004 + process_c004_delay_queue(); + #endif // ifdef USES_C004 + break; + case SchedulerIntervalTimer_e::TIMER_C007_DELAY_QUEUE: + #ifdef USES_C007 + process_c007_delay_queue(); + #endif // ifdef USES_C007 + break; + case SchedulerIntervalTimer_e::TIMER_C008_DELAY_QUEUE: + #ifdef USES_C008 + process_c008_delay_queue(); + #endif // ifdef USES_C008 + break; + case SchedulerIntervalTimer_e::TIMER_C009_DELAY_QUEUE: + #ifdef USES_C009 + process_c009_delay_queue(); + #endif // ifdef USES_C009 + break; + case SchedulerIntervalTimer_e::TIMER_C010_DELAY_QUEUE: + #ifdef USES_C010 + process_c010_delay_queue(); + #endif // ifdef USES_C010 + break; + case SchedulerIntervalTimer_e::TIMER_C011_DELAY_QUEUE: + #ifdef USES_C011 + process_c011_delay_queue(); + #endif // ifdef USES_C011 + break; + case SchedulerIntervalTimer_e::TIMER_C012_DELAY_QUEUE: + #ifdef USES_C012 + process_c012_delay_queue(); + #endif // ifdef USES_C012 + break; + + case SchedulerIntervalTimer_e::TIMER_C013_DELAY_QUEUE: + /* + #ifdef USES_C013 + process_c013_delay_queue(); + #endif + */ + break; + + case SchedulerIntervalTimer_e::TIMER_C014_DELAY_QUEUE: + /* + #ifdef USES_C014 + process_c014_delay_queue(); + #endif + */ + break; + + case SchedulerIntervalTimer_e::TIMER_C015_DELAY_QUEUE: + #ifdef USES_C015 + process_c015_delay_queue(); + #endif // ifdef USES_C015 + break; + case SchedulerIntervalTimer_e::TIMER_C016_DELAY_QUEUE: + #ifdef USES_C016 + process_c016_delay_queue(); + #endif // ifdef USES_C016 + break; + + case SchedulerIntervalTimer_e::TIMER_C017_DELAY_QUEUE: + #ifdef USES_C017 + process_c017_delay_queue(); + #endif // ifdef USES_C017 + break; + + case SchedulerIntervalTimer_e::TIMER_C018_DELAY_QUEUE: + #ifdef USES_C018 + process_c018_delay_queue(); + #endif // ifdef USES_C018 + break; + + case SchedulerIntervalTimer_e::TIMER_C019_DELAY_QUEUE: + #ifdef USES_C019 + process_c019_delay_queue(); + #endif + break; + + case SchedulerIntervalTimer_e::TIMER_C020_DELAY_QUEUE: + /* + #ifdef USES_C020 + process_c020_delay_queue(); + #endif + */ + break; + + case SchedulerIntervalTimer_e::TIMER_C021_DELAY_QUEUE: + /* + #ifdef USES_C021 + process_c021_delay_queue(); + #endif + */ + break; + + case SchedulerIntervalTimer_e::TIMER_C022_DELAY_QUEUE: + /* + #ifdef USES_C022 + process_c022_delay_queue(); + #endif + */ + break; + + case SchedulerIntervalTimer_e::TIMER_C023_DELAY_QUEUE: + /* + #ifdef USES_C023 + process_c023_delay_queue(); + #endif + */ + break; + + case SchedulerIntervalTimer_e::TIMER_C024_DELAY_QUEUE: + /* + #ifdef USES_C024 + process_c024_delay_queue(); + #endif + */ + break; + + case SchedulerIntervalTimer_e::TIMER_C025_DELAY_QUEUE: + /* + #ifdef USES_C025 + process_c025_delay_queue(); + #endif + */ + break; + + // When extending this, search for EXTEND_CONTROLLER_IDS + // in the code to find all places that need to be updated too. + } +} diff --git a/src/src/Helpers/StringProvider.cpp b/src/src/Helpers/StringProvider.cpp index c9f6bdecae..af0c01b7d3 100644 --- a/src/src/Helpers/StringProvider.cpp +++ b/src/src/Helpers/StringProvider.cpp @@ -1,843 +1,871 @@ -#include "../Helpers/StringProvider.h" - -#if FEATURE_ETHERNET -# include -#endif // if FEATURE_ETHERNET - -#include "../../ESPEasy-Globals.h" - -#include "../CustomBuild/CompiletimeDefines.h" - -#include "../ESPEasyCore/ESPEasyNetwork.h" -#include "../ESPEasyCore/ESPEasyWifi.h" -#if FEATURE_ETHERNET -#include "../ESPEasyCore/ESPEasyEth.h" -#endif - -#include "../Globals/Device.h" -#include "../Globals/ESPEasy_Console.h" -#include "../Globals/ESPEasy_Scheduler.h" -#include "../Globals/ESPEasy_time.h" -#include "../Globals/ESPEasyWiFiEvent.h" - -#if FEATURE_ETHERNET -#include "../Globals/ESPEasyEthEvent.h" -#endif - -#include "../Globals/NetworkState.h" -#include "../Globals/SecuritySettings.h" -#include "../Globals/Settings.h" -#include "../Globals/WiFi_AP_Candidates.h" - -#include "../Helpers/Convert.h" -#include "../Helpers/ESPEasy_Storage.h" -#include "../Helpers/Hardware_device_info.h" -#include "../Helpers/Hardware_temperature_sensor.h" -#include "../Helpers/Memory.h" -#include "../Helpers/Misc.h" -#include "../Helpers/Networking.h" -#include "../Helpers/Scheduler.h" -#include "../Helpers/StringConverter.h" -#include "../Helpers/StringGenerator_System.h" -#include "../Helpers/StringGenerator_WiFi.h" - -#include "../WebServer/JSON.h" -#include "../WebServer/AccessControl.h" - -#ifdef ESP32 -#include -#endif - -String getInternalLabel(LabelType::Enum label, char replaceSpace) { - return to_internal_string(getLabel(label), replaceSpace); -} - -const __FlashStringHelper * getLabel(LabelType::Enum label) { - switch (label) - { - case LabelType::UNIT_NR: return F("Unit Number"); - #if FEATURE_ZEROFILLED_UNITNUMBER - case LabelType::UNIT_NR_0: return F("Unit Number 0-filled"); - #endif // FEATURE_ZEROFILLED_UNITNUMBER - case LabelType::UNIT_NAME: return F("Unit Name"); - case LabelType::HOST_NAME: return F("Hostname"); - - case LabelType::LOCAL_TIME: return F("Local Time"); - case LabelType::TIME_SOURCE: return F("Time Source"); - case LabelType::TIME_WANDER: return F("Time Wander"); - #if FEATURE_EXT_RTC - case LabelType::EXT_RTC_UTC_TIME: return F("UTC time stored in RTC chip"); - #endif - case LabelType::UPTIME: return F("Uptime"); - case LabelType::LOAD_PCT: return F("Load"); - case LabelType::LOOP_COUNT: return F("Load LC"); - case LabelType::CPU_ECO_MODE: return F("CPU Eco Mode"); -#if FEATURE_SET_WIFI_TX_PWR - case LabelType::WIFI_TX_MAX_PWR: return F("Max WiFi TX Power"); - case LabelType::WIFI_CUR_TX_PWR: return F("Current WiFi TX Power"); - case LabelType::WIFI_SENS_MARGIN: return F("WiFi Sensitivity Margin"); - case LabelType::WIFI_SEND_AT_MAX_TX_PWR:return F("Send With Max TX Power"); -#endif - case LabelType::WIFI_NR_EXTRA_SCANS: return F("Extra WiFi scan loops"); - case LabelType::WIFI_USE_LAST_CONN_FROM_RTC: return F("Use Last Connected AP from RTC"); - - case LabelType::FREE_MEM: return F("Free RAM"); - case LabelType::FREE_STACK: return F("Free Stack"); -#ifdef USE_SECOND_HEAP - case LabelType::FREE_HEAP_IRAM: return F("Free 2nd Heap"); -#endif - -#if defined(CORE_POST_2_5_0) || defined(ESP32) - #ifndef LIMIT_BUILD_SIZE - case LabelType::HEAP_MAX_FREE_BLOCK: return F("Heap Max Free Block"); - #endif -#endif // if defined(CORE_POST_2_5_0) || defined(ESP32) -#if defined(CORE_POST_2_5_0) - #ifndef LIMIT_BUILD_SIZE - case LabelType::HEAP_FRAGMENTATION: return F("Heap Fragmentation"); - #endif -#endif // if defined(CORE_POST_2_5_0) - -#ifdef ESP32 - case LabelType::HEAP_SIZE: return F("Heap Size"); - case LabelType::HEAP_MIN_FREE: return F("Heap Min Free"); - #ifdef BOARD_HAS_PSRAM - case LabelType::PSRAM_SIZE: return F("PSRAM Size"); - case LabelType::PSRAM_FREE: return F("PSRAM Free"); - case LabelType::PSRAM_MIN_FREE: return F("PSRAM Min Free"); - case LabelType::PSRAM_MAX_FREE_BLOCK: return F("PSRAM Max Free Block"); - #endif // BOARD_HAS_PSRAM -#endif // ifdef ESP32 - - case LabelType::JSON_BOOL_QUOTES: return F("JSON bool output without quotes"); -#if FEATURE_TIMING_STATS - case LabelType::ENABLE_TIMING_STATISTICS: return F("Collect Timing Statistics"); -#endif - case LabelType::ENABLE_RULES_CACHING: return F("Enable Rules Cache"); - case LabelType::ENABLE_SERIAL_PORT_CONSOLE: return F("Enable Serial Port Console"); - case LabelType::CONSOLE_SERIAL_PORT: return F("Console Serial Port"); -#if USES_ESPEASY_CONSOLE_FALLBACK_PORT - case LabelType::CONSOLE_FALLBACK_TO_SERIAL0: return F("Fallback to Serial 0"); - case LabelType::CONSOLE_FALLBACK_PORT: return F("Console Fallback Port"); -#endif - -// case LabelType::ENABLE_RULES_EVENT_REORDER: return F("Optimize Rules Cache Event Order"); // TD-er: Disabled for now - case LabelType::TASKVALUESET_ALL_PLUGINS: return F("Allow TaskValueSet on all plugins"); - case LabelType::ALLOW_OTA_UNLIMITED: return F("Allow OTA without size-check"); -#if FEATURE_CLEAR_I2C_STUCK - case LabelType::ENABLE_CLEAR_HUNG_I2C_BUS: return F("Try clear I2C bus when stuck"); -#endif - #if FEATURE_I2C_DEVICE_CHECK - case LabelType::ENABLE_I2C_DEVICE_CHECK: return F("Check I2C devices when enabled"); - #endif // if FEATURE_I2C_DEVICE_CHECK -#ifndef BUILD_NO_RAM_TRACKER - case LabelType::ENABLE_RAM_TRACKING: return F("Enable RAM Tracker"); -#endif -#if FEATURE_AUTO_DARK_MODE - case LabelType::ENABLE_AUTO_DARK_MODE: return F("Web light/dark mode"); -#endif // FEATURE_AUTO_DARK_MODE -#if FEATURE_RULES_EASY_COLOR_CODE - case LabelType::DISABLE_RULES_AUTOCOMPLETE: return F("Disable Rules auto-completion"); -#endif // if FEATURE_RULES_EASY_COLOR_CODE -#if FEATURE_TARSTREAM_SUPPORT - case LabelType::DISABLE_SAVE_CONFIG_AS_TAR: return F("Disable Save Config as .tar"); -#endif // if FEATURE_TARSTREAM_SUPPORT - #if FEATURE_TASKVALUE_UNIT_OF_MEASURE - case LabelType::SHOW_UOM_ON_DEVICES_PAGE: return F("Show Unit of Measure"); - #endif // if FEATURE_TASKVALUE_UNIT_OF_MEASURE - - case LabelType::BOOT_TYPE: return F("Last Boot Cause"); - case LabelType::BOOT_COUNT: return F("Boot Count"); - case LabelType::DEEP_SLEEP_ALTERNATIVE_CALL: return F("Deep Sleep Alternative"); - case LabelType::RESET_REASON: return F("Reset Reason"); - case LabelType::LAST_TASK_BEFORE_REBOOT: return F("Last Action before Reboot"); - case LabelType::SW_WD_COUNT: return F("SW WD count"); - - - case LabelType::WIFI_CONNECTION: return F("WiFi Connection"); - case LabelType::WIFI_RSSI: return F("RSSI"); - case LabelType::IP_CONFIG: return F("IP Config"); -#if FEATURE_USE_IPV6 - case LabelType::IP6_LOCAL: return F("IPv6 link local"); - case LabelType::IP6_GLOBAL: return F("IPv6 global"); -// case LabelType::IP6_ALL_ADDRESSES: return F("IPv6 all addresses"); -#endif - case LabelType::IP_CONFIG_STATIC: return F("Static"); - case LabelType::IP_CONFIG_DYNAMIC: return F("DHCP"); - case LabelType::IP_ADDRESS: return F("IP Address"); - case LabelType::IP_SUBNET: return F("IP Subnet"); - case LabelType::IP_ADDRESS_SUBNET: return F("IP / Subnet"); - case LabelType::GATEWAY: return F("Gateway"); - case LabelType::CLIENT_IP: return F("Client IP"); - #if FEATURE_MDNS - case LabelType::M_DNS: return F("mDNS"); - #endif // if FEATURE_MDNS - case LabelType::DNS: return F("DNS"); - case LabelType::DNS_1: return F("DNS 1"); - case LabelType::DNS_2: return F("DNS 2"); - case LabelType::ALLOWED_IP_RANGE: return F("Allowed IP Range"); - case LabelType::STA_MAC: return F("STA MAC"); - case LabelType::AP_MAC: return F("AP MAC"); - case LabelType::SSID: return F("SSID"); - case LabelType::BSSID: return F("BSSID"); - case LabelType::CHANNEL: return F("Channel"); - case LabelType::ENCRYPTION_TYPE_STA: return F("Encryption Type"); - case LabelType::CONNECTED: return F("Connected"); - case LabelType::CONNECTED_MSEC: return F("Connected msec"); - case LabelType::LAST_DISCONNECT_REASON: return F("Last Disconnect Reason"); - case LabelType::LAST_DISC_REASON_STR: return F("Last Disconnect Reason str"); - case LabelType::NUMBER_RECONNECTS: return F("Number Reconnects"); - case LabelType::WIFI_STORED_SSID1: return F("Configured SSID1"); - case LabelType::WIFI_STORED_SSID2: return F("Configured SSID2"); - - - case LabelType::FORCE_WIFI_BG: return F("Force WiFi B/G"); - case LabelType::RESTART_WIFI_LOST_CONN: return F("Restart WiFi Lost Conn"); - case LabelType::FORCE_WIFI_NOSLEEP: return F("Force WiFi No Sleep"); - case LabelType::PERIODICAL_GRAT_ARP: return F("Periodical send Gratuitous ARP"); - case LabelType::CONNECTION_FAIL_THRESH: return F("Connection Failure Threshold"); -#ifndef ESP32 - case LabelType::WAIT_WIFI_CONNECT: return F("Extra Wait WiFi Connect"); -#endif - case LabelType::CONNECT_HIDDEN_SSID: return F("Include Hidden SSID"); -#ifdef ESP32 - case LabelType::WIFI_PASSIVE_SCAN: return F("Passive WiFi Scan"); -#endif - case LabelType::HIDDEN_SSID_SLOW_CONNECT: return F("Hidden SSID Slow Connect"); - case LabelType::SDK_WIFI_AUTORECONNECT: return F("Enable SDK WiFi Auto Reconnect"); -#if FEATURE_USE_IPV6 - case LabelType::ENABLE_IPV6: return F("Enable IPv6"); -#endif - - - case LabelType::BUILD_DESC: return F("Build"); - case LabelType::GIT_BUILD: return F("Git Build"); - case LabelType::SYSTEM_LIBRARIES: return F("System Libraries"); -#ifdef ESP32 - case LabelType::ESP_IDF_SDK_VERSION: return F("ESP-IDF Version"); -#endif - case LabelType::PLUGIN_COUNT: return F("Plugin Count"); - case LabelType::PLUGIN_DESCRIPTION: return F("Plugin Description"); - case LabelType::BUILD_TIME: return F("Build Time"); - case LabelType::BINARY_FILENAME: return F("Binary Filename"); - case LabelType::BUILD_PLATFORM: return F("Build Platform"); - case LabelType::GIT_HEAD: return F("Git HEAD"); - #ifdef CONFIGURATION_CODE - case LabelType::CONFIGURATION_CODE_LBL: return F("Configuration code"); - #endif // ifdef CONFIGURATION_CODE - - case LabelType::I2C_BUS_STATE: return F("I2C Bus State"); - case LabelType::I2C_BUS_CLEARED_COUNT: return F("I2C bus cleared count"); - - case LabelType::SYSLOG_LOG_LEVEL: return F("Syslog Log Level"); - case LabelType::SERIAL_LOG_LEVEL: return F("Serial Log Level"); - case LabelType::WEB_LOG_LEVEL: return F("Web Log Level"); - #if FEATURE_SD - case LabelType::SD_LOG_LEVEL: return F("SD Log Level"); - #endif // if FEATURE_SD - - case LabelType::ESP_CHIP_ID: return F("ESP Chip ID"); - case LabelType::ESP_CHIP_FREQ: return F("ESP Chip Frequency"); -#ifdef ESP32 - case LabelType::ESP_CHIP_XTAL_FREQ: return F("ESP Crystal Frequency"); - case LabelType::ESP_CHIP_APB_FREQ: return F("ESP APB Frequency"); -#endif - case LabelType::ESP_CHIP_MODEL: return F("ESP Chip Model"); - case LabelType::ESP_CHIP_REVISION: return F("ESP Chip Revision"); - case LabelType::ESP_CHIP_CORES: return F("ESP Chip Cores"); - - case LabelType::BOARD_NAME: return F("ESP Board Name"); - - case LabelType::FLASH_CHIP_ID: return F("Flash Chip ID"); - case LabelType::FLASH_CHIP_VENDOR: return F("Flash Chip Vendor"); - case LabelType::FLASH_CHIP_MODEL: return F("Flash Chip Model"); - case LabelType::FLASH_CHIP_REAL_SIZE: return F("Flash Chip Real Size"); - case LabelType::FLASH_CHIP_SPEED: return F("Flash Chip Speed"); - case LabelType::FLASH_IDE_SIZE: return F("Flash IDE Size"); - case LabelType::FLASH_IDE_SPEED: return F("Flash IDE Speed"); - case LabelType::FLASH_IDE_MODE: return F("Flash IDE Mode"); - case LabelType::FLASH_WRITE_COUNT: return F("Flash Writes"); - case LabelType::SKETCH_SIZE: return F("Sketch Size"); - case LabelType::SKETCH_FREE: return F("Sketch Free"); - #ifdef USE_LITTLEFS - case LabelType::FS_SIZE: return F("Little FS Size"); - case LabelType::FS_FREE: return F("Little FS Free"); - #else // ifdef USE_LITTLEFS - case LabelType::FS_SIZE: return F("SPIFFS Size"); - case LabelType::FS_FREE: return F("SPIFFS Free"); - #endif // ifdef USE_LITTLEFS - case LabelType::MAX_OTA_SKETCH_SIZE: return F("Max. OTA Sketch Size"); - case LabelType::OTA_2STEP: return F("OTA 2-step Needed"); - case LabelType::OTA_POSSIBLE: return F("OTA possible"); - #if FEATURE_INTERNAL_TEMPERATURE - case LabelType::INTERNAL_TEMPERATURE: return F("Internal Temperature"); - #endif // if FEATURE_INTERNAL_TEMPERATURE -#if FEATURE_ETHERNET - case LabelType::ETH_IP_ADDRESS: return F("Eth IP Address"); - case LabelType::ETH_IP_SUBNET: return F("Eth IP Subnet"); - case LabelType::ETH_IP_ADDRESS_SUBNET: return F("Eth IP / Subnet"); - case LabelType::ETH_IP_GATEWAY: return F("Eth Gateway"); - case LabelType::ETH_IP_DNS: return F("Eth DNS"); -#if FEATURE_USE_IPV6 - case LabelType::ETH_IP6_LOCAL: return F("Eth IPv6 link local"); -#endif - case LabelType::ETH_MAC: return F("Eth MAC"); - case LabelType::ETH_DUPLEX: return F("Eth Mode"); - case LabelType::ETH_SPEED: return F("Eth Speed"); - case LabelType::ETH_STATE: return F("Eth State"); - case LabelType::ETH_SPEED_STATE: return F("Eth Speed State"); - case LabelType::ETH_CONNECTED: return F("Eth connected"); - case LabelType::ETH_CHIP: return F("Eth chip"); -#endif // if FEATURE_ETHERNET -# if FEATURE_ETHERNET || defined(USES_ESPEASY_NOW) - case LabelType::ETH_WIFI_MODE: return F("Network Type"); -#endif - case LabelType::SUNRISE: return F("Sunrise"); - case LabelType::SUNSET: return F("Sunset"); - case LabelType::SUNRISE_S: return F("Sunrise sec."); - case LabelType::SUNSET_S: return F("Sunset sec."); - case LabelType::SUNRISE_M: return F("Sunrise min."); - case LabelType::SUNSET_M: return F("Sunset min."); - case LabelType::ISNTP: return F("Use NTP"); - case LabelType::UPTIME_MS: return F("Uptime (ms)"); - case LabelType::TIMEZONE_OFFSET: return F("Timezone Offset"); - case LabelType::LATITUDE: return F("Latitude"); - case LabelType::LONGITUDE: return F("Longitude"); - - case LabelType::MAX_LABEL: - break; - - } - return F("MissingString"); -} - -String getValue(LabelType::Enum label) { - int retval = INT_MAX; - switch (label) - { - case LabelType::UNIT_NR: retval = Settings.Unit; break; - #if FEATURE_ZEROFILLED_UNITNUMBER - case LabelType::UNIT_NR_0: - { - // Fixed 3-digit unitnumber - return formatIntLeadingZeroes(Settings.Unit, 3); - } - #endif // FEATURE_ZEROFILLED_UNITNUMBER - case LabelType::UNIT_NAME: return Settings.getName(); // Only return the set name, no appended unit. - case LabelType::HOST_NAME: return NetworkGetHostname(); - - - case LabelType::LOCAL_TIME: return node_time.getDateTimeString('-', ':', ' '); - case LabelType::TIME_SOURCE: - { - String timeSource_str = toString(node_time.getTimeSource()); - if (((node_time.getTimeSource() == timeSource_t::ESPEASY_p2p_UDP) || - (node_time.getTimeSource() == timeSource_t::ESP_now_peer)) && - (node_time.timeSource_p2p_unit != 0)) - { - return strformat(F("%s (%u)"), timeSource_str.c_str(), node_time.timeSource_p2p_unit); - } - return timeSource_str; - } - case LabelType::TIME_WANDER: return String(node_time.timeWander, 3); - #if FEATURE_EXT_RTC - case LabelType::EXT_RTC_UTC_TIME: - { - if (Settings.ExtTimeSource() != ExtTimeSource_e::None) { - // Try to read the stored time in the ext. time source to allow to check if it is working properly. - uint32_t unixtime; - if (node_time.ExtRTC_get(unixtime)) { - struct tm RTC_time; - breakTime(unixtime, RTC_time); - return formatDateTimeString(RTC_time); - } else { - return F("Not Set"); - } - } - return String('-'); - } - #endif - case LabelType::UPTIME: retval = getUptimeMinutes(); break; - case LabelType::LOAD_PCT: return toString(getCPUload(), 2); - case LabelType::LOOP_COUNT: retval = getLoopCountPerSec(); break; - case LabelType::CPU_ECO_MODE: return jsonBool(Settings.EcoPowerMode()); -#if FEATURE_SET_WIFI_TX_PWR - case LabelType::WIFI_TX_MAX_PWR: return toString(Settings.getWiFi_TX_power(), 2); - case LabelType::WIFI_CUR_TX_PWR: return toString(WiFiEventData.wifi_TX_pwr, 2); - case LabelType::WIFI_SENS_MARGIN: retval = Settings.WiFi_sensitivity_margin; break; - case LabelType::WIFI_SEND_AT_MAX_TX_PWR:return jsonBool(Settings.UseMaxTXpowerForSending()); -#endif - case LabelType::WIFI_NR_EXTRA_SCANS: retval = Settings.NumberExtraWiFiScans; break; - case LabelType::WIFI_USE_LAST_CONN_FROM_RTC: return jsonBool(Settings.UseLastWiFiFromRTC()); - - case LabelType::FREE_MEM: retval = FreeMem(); break; - case LabelType::FREE_STACK: retval = getCurrentFreeStack(); break; - -#ifdef USE_SECOND_HEAP - case LabelType::FREE_HEAP_IRAM: retval = FreeMem2ndHeap(); break; -#endif - -#if defined(CORE_POST_2_5_0) - #ifndef LIMIT_BUILD_SIZE - case LabelType::HEAP_MAX_FREE_BLOCK: retval = ESP.getMaxFreeBlockSize(); break; - #endif -#endif // if defined(CORE_POST_2_5_0) -#if defined(ESP32) - #ifndef LIMIT_BUILD_SIZE - case LabelType::HEAP_MAX_FREE_BLOCK: retval = ESP.getMaxAllocHeap(); break; - #endif -#endif // if defined(ESP32) -#if defined(CORE_POST_2_5_0) - #ifndef LIMIT_BUILD_SIZE - case LabelType::HEAP_FRAGMENTATION: retval = ESP.getHeapFragmentation(); break; - #endif -#endif // if defined(CORE_POST_2_5_0) -#ifdef ESP32 - case LabelType::HEAP_SIZE: retval = ESP.getHeapSize(); break; - case LabelType::HEAP_MIN_FREE: retval = ESP.getMinFreeHeap(); break; - #ifdef BOARD_HAS_PSRAM - case LabelType::PSRAM_SIZE: retval = UsePSRAM() ? ESP.getPsramSize() : 0; break; - case LabelType::PSRAM_FREE: retval = UsePSRAM() ? ESP.getFreePsram() : 0; break; - case LabelType::PSRAM_MIN_FREE: retval = UsePSRAM() ? ESP.getMinFreePsram() : 0; break; - case LabelType::PSRAM_MAX_FREE_BLOCK: retval = UsePSRAM() ? ESP.getMaxAllocPsram() : 0; break; - #endif // BOARD_HAS_PSRAM -#endif // ifdef ESP32 - - - case LabelType::JSON_BOOL_QUOTES: return jsonBool(Settings.JSONBoolWithoutQuotes()); -#if FEATURE_TIMING_STATS - case LabelType::ENABLE_TIMING_STATISTICS: return jsonBool(Settings.EnableTimingStats()); -#endif - case LabelType::ENABLE_RULES_CACHING: return jsonBool(Settings.EnableRulesCaching()); - case LabelType::ENABLE_SERIAL_PORT_CONSOLE: return jsonBool(Settings.UseSerial); - case LabelType::CONSOLE_SERIAL_PORT: return ESPEasy_Console.getPortDescription(); - -#if USES_ESPEASY_CONSOLE_FALLBACK_PORT - case LabelType::CONSOLE_FALLBACK_TO_SERIAL0: return jsonBool(Settings.console_serial0_fallback); - case LabelType::CONSOLE_FALLBACK_PORT: return ESPEasy_Console.getFallbackPortDescription(); -#endif - -// case LabelType::ENABLE_RULES_EVENT_REORDER: return jsonBool(Settings.EnableRulesEventReorder()); // TD-er: Disabled for now - case LabelType::TASKVALUESET_ALL_PLUGINS: return jsonBool(Settings.AllowTaskValueSetAllPlugins()); - case LabelType::ALLOW_OTA_UNLIMITED: return jsonBool(Settings.AllowOTAUnlimited()); -#if FEATURE_CLEAR_I2C_STUCK - case LabelType::ENABLE_CLEAR_HUNG_I2C_BUS: return jsonBool(Settings.EnableClearHangingI2Cbus()); -#endif -#if FEATURE_I2C_DEVICE_CHECK - case LabelType::ENABLE_I2C_DEVICE_CHECK: return jsonBool(Settings.CheckI2Cdevice()); -#endif // if FEATURE_I2C_DEVICE_CHECK -#ifndef BUILD_NO_RAM_TRACKER - case LabelType::ENABLE_RAM_TRACKING: return jsonBool(Settings.EnableRAMTracking()); -#endif -#if FEATURE_AUTO_DARK_MODE - case LabelType::ENABLE_AUTO_DARK_MODE: return toString(Settings.getCssMode()); -#endif // FEATURE_AUTO_DARK_MODE -#if FEATURE_RULES_EASY_COLOR_CODE - case LabelType::DISABLE_RULES_AUTOCOMPLETE: return jsonBool(Settings.DisableRulesCodeCompletion()); -#endif // if FEATURE_RULES_EASY_COLOR_CODE -#if FEATURE_TARSTREAM_SUPPORT - case LabelType::DISABLE_SAVE_CONFIG_AS_TAR: return jsonBool(Settings.DisableSaveConfigAsTar()); -#endif // if FEATURE_TARSTREAM_SUPPORT - #if FEATURE_TASKVALUE_UNIT_OF_MEASURE - case LabelType::SHOW_UOM_ON_DEVICES_PAGE: return jsonBool(Settings.ShowUnitOfMeasureOnDevicesPage()); - #endif // if FEATURE_TASKVALUE_UNIT_OF_MEASURE - - case LabelType::BOOT_TYPE: return getLastBootCauseString(); - case LabelType::BOOT_COUNT: break; - case LabelType::DEEP_SLEEP_ALTERNATIVE_CALL: return jsonBool(Settings.UseAlternativeDeepSleep()); - case LabelType::RESET_REASON: return getResetReasonString(); - case LabelType::LAST_TASK_BEFORE_REBOOT: return ESPEasy_Scheduler::decodeSchedulerId(lastMixedSchedulerId_beforereboot); - case LabelType::SW_WD_COUNT: retval = sw_watchdog_callback_count; break; - - case LabelType::WIFI_CONNECTION: break; - case LabelType::WIFI_RSSI: retval = WiFi.RSSI(); break; - case LabelType::IP_CONFIG: return useStaticIP() - ? getLabel(LabelType::IP_CONFIG_STATIC) - : getLabel(LabelType::IP_CONFIG_DYNAMIC); - case LabelType::IP_CONFIG_STATIC: break; - case LabelType::IP_CONFIG_DYNAMIC: break; - case LabelType::IP_ADDRESS: return formatIP(NetworkLocalIP()); - case LabelType::IP_SUBNET: return formatIP(NetworkSubnetMask()); - case LabelType::IP_ADDRESS_SUBNET: return strformat(F("%s / %s"), getValue(LabelType::IP_ADDRESS).c_str(), getValue(LabelType::IP_SUBNET).c_str()); - case LabelType::GATEWAY: return formatIP(NetworkGatewayIP()); -#if FEATURE_USE_IPV6 - case LabelType::IP6_LOCAL: return formatIP(NetworkLocalIP6(), true); - case LabelType::IP6_GLOBAL: return formatIP(NetworkGlobalIP6()); -#if FEATURE_ETHERNET - case LabelType::ETH_IP6_LOCAL: return formatIP(NetworkLocalIP6(), true); -#endif -/* - case LabelType::IP6_ALL_ADDRESSES: - { - IP6Addresses_t addresses = NetworkAllIPv6(); - String res; - for (auto it = addresses.begin(); it != addresses.end(); ++it) - { - if (!res.isEmpty()) { - res += F("
"); - } - res += it->toString(); - } - return res; - } -*/ -#endif - case LabelType::CLIENT_IP: return formatIP(web_server.client().remoteIP(), true); - #if FEATURE_INTERNAL_TEMPERATURE - case LabelType::INTERNAL_TEMPERATURE: return toString(getInternalTemperature()); - #endif // if FEATURE_INTERNAL_TEMPERATURE - - #if FEATURE_MDNS - case LabelType::M_DNS: return NetworkGetHostname() + F(".local"); - #endif // if FEATURE_MDNS - case LabelType::DNS: return strformat(F("%s / %s"), getValue(LabelType::DNS_1).c_str(), getValue(LabelType::DNS_2).c_str()); - case LabelType::DNS_1: return formatIP(NetworkDnsIP(0)); - case LabelType::DNS_2: return formatIP(NetworkDnsIP(1)); - case LabelType::ALLOWED_IP_RANGE: return describeAllowedIPrange(); - case LabelType::STA_MAC: return WifiSTAmacAddress().toString(); - case LabelType::AP_MAC: return WifiSoftAPmacAddress().toString(); - case LabelType::SSID: return WiFi.SSID(); - case LabelType::BSSID: return WiFi.BSSIDstr(); - case LabelType::CHANNEL: retval = WiFi.channel(); break; - case LabelType::ENCRYPTION_TYPE_STA: return // WiFi_AP_Candidates.getCurrent().encryption_type(); - WiFi_encryptionType(WiFiEventData.auth_mode); - case LabelType::CONNECTED: - #if FEATURE_ETHERNET - if(active_network_medium == NetworkMedium_t::Ethernet) { - return format_msec_duration(EthEventData.lastConnectMoment.millisPassedSince()); - } - #endif // if FEATURE_ETHERNET - return format_msec_duration(WiFiEventData.lastConnectMoment.millisPassedSince()); - - // Use only the nr of seconds to fit it in an int32, plus append '000' to have msec format again. - case LabelType::CONNECTED_MSEC: - #if FEATURE_ETHERNET - if(active_network_medium == NetworkMedium_t::Ethernet) { - return String(static_cast(EthEventData.lastConnectMoment.millisPassedSince() / 1000ll)) + F("000"); - } - #endif // if FEATURE_ETHERNET - return String(static_cast(WiFiEventData.lastConnectMoment.millisPassedSince() / 1000ll)) + F("000"); - case LabelType::LAST_DISCONNECT_REASON: return String(WiFiEventData.lastDisconnectReason); - case LabelType::LAST_DISC_REASON_STR: return getLastDisconnectReason(); - case LabelType::NUMBER_RECONNECTS: retval = WiFiEventData.wifi_reconnects; break; - case LabelType::WIFI_STORED_SSID1: return String(SecuritySettings.WifiSSID); - case LabelType::WIFI_STORED_SSID2: return String(SecuritySettings.WifiSSID2); - - - case LabelType::FORCE_WIFI_BG: return jsonBool(Settings.ForceWiFi_bg_mode()); - case LabelType::RESTART_WIFI_LOST_CONN: return jsonBool(Settings.WiFiRestart_connection_lost()); - case LabelType::FORCE_WIFI_NOSLEEP: return jsonBool(Settings.WifiNoneSleep()); - case LabelType::PERIODICAL_GRAT_ARP: return jsonBool(Settings.gratuitousARP()); - case LabelType::CONNECTION_FAIL_THRESH: retval = Settings.ConnectionFailuresThreshold; break; -#ifndef ESP32 - case LabelType::WAIT_WIFI_CONNECT: return jsonBool(Settings.WaitWiFiConnect()); -#endif - case LabelType::CONNECT_HIDDEN_SSID: return jsonBool(Settings.IncludeHiddenSSID()); -#ifdef ESP32 - case LabelType::WIFI_PASSIVE_SCAN: return jsonBool(Settings.PassiveWiFiScan()); -#endif - case LabelType::HIDDEN_SSID_SLOW_CONNECT: return jsonBool(Settings.HiddenSSID_SlowConnectPerBSSID()); - case LabelType::SDK_WIFI_AUTORECONNECT: return jsonBool(Settings.SDK_WiFi_autoreconnect()); -#if FEATURE_USE_IPV6 - case LabelType::ENABLE_IPV6: return jsonBool(Settings.EnableIPv6()); -#endif - - - case LabelType::BUILD_DESC: return getSystemBuildString(); - case LabelType::GIT_BUILD: - { - const String res(F(BUILD_GIT)); - - if (!res.isEmpty()) { return res; } - return get_git_head(); - } - case LabelType::SYSTEM_LIBRARIES: return getSystemLibraryString(); -#ifdef ESP32 - case LabelType::ESP_IDF_SDK_VERSION: return strformat( - F("%d.%d.%d"), - ESP_IDF_VERSION_MAJOR, - ESP_IDF_VERSION_MINOR, - ESP_IDF_VERSION_PATCH); -#endif - - case LabelType::PLUGIN_COUNT: retval = getDeviceCount() + 1; break; - case LabelType::PLUGIN_DESCRIPTION: return getPluginDescriptionString(); - case LabelType::BUILD_TIME: return String(get_build_date()) + ' ' + get_build_time(); - case LabelType::BINARY_FILENAME: return get_binary_filename(); - case LabelType::BUILD_PLATFORM: return get_build_platform(); - case LabelType::GIT_HEAD: return get_git_head(); - #ifdef CONFIGURATION_CODE - case LabelType::CONFIGURATION_CODE_LBL: return getConfigurationCode(); - #endif // ifdef CONFIGURATION_CODE - case LabelType::I2C_BUS_STATE: return toString(I2C_state); - case LabelType::I2C_BUS_CLEARED_COUNT: retval = I2C_bus_cleared_count; break; - case LabelType::SYSLOG_LOG_LEVEL: return getLogLevelDisplayString(Settings.SyslogLevel); - case LabelType::SERIAL_LOG_LEVEL: return getLogLevelDisplayString(getSerialLogLevel()); - case LabelType::WEB_LOG_LEVEL: return getLogLevelDisplayString(getWebLogLevel()); - #if FEATURE_SD - case LabelType::SD_LOG_LEVEL: return getLogLevelDisplayString(Settings.SDLogLevel); - #endif // if FEATURE_SD - - case LabelType::ESP_CHIP_ID: return formatToHex(getChipId(), 6); - case LabelType::ESP_CHIP_FREQ: retval = ESP.getCpuFreqMHz(); break; -#ifdef ESP32 - case LabelType::ESP_CHIP_XTAL_FREQ: retval = getXtalFrequencyMHz(); break; - case LabelType::ESP_CHIP_APB_FREQ: retval = rtc_clk_apb_freq_get() / 1000000; break; - //getApbFrequency() / 1000000; break; -#endif - case LabelType::ESP_CHIP_MODEL: return getChipModel(); - case LabelType::ESP_CHIP_REVISION: return getChipRevision(); - case LabelType::ESP_CHIP_CORES: retval = getChipCores(); break; - case LabelType::BOARD_NAME: return get_board_name(); - case LabelType::FLASH_CHIP_ID: return formatToHex(getFlashChipId(), 6); - case LabelType::FLASH_CHIP_VENDOR: return formatToHex(getFlashChipId() & 0xFF, 2); - case LabelType::FLASH_CHIP_MODEL: - { - const uint32_t flashChipId = getFlashChipId(); - const uint32_t flashDevice = (flashChipId & 0xFF00) | ((flashChipId >> 16) & 0xFF); - return formatToHex(flashDevice, 4); - } - case LabelType::FLASH_CHIP_REAL_SIZE: retval = getFlashRealSizeInBytes(); break; - case LabelType::FLASH_CHIP_SPEED: retval = getFlashChipSpeed() / 1000000; break; - case LabelType::FLASH_IDE_SIZE: break; - case LabelType::FLASH_IDE_SPEED: break; - case LabelType::FLASH_IDE_MODE: return getFlashChipMode(); - case LabelType::FLASH_WRITE_COUNT: break; - case LabelType::SKETCH_SIZE: break; - case LabelType::SKETCH_FREE: break; - case LabelType::FS_SIZE: retval = SpiffsTotalBytes(); break; - case LabelType::FS_FREE: retval = SpiffsFreeSpace(); break; - case LabelType::MAX_OTA_SKETCH_SIZE: break; - case LabelType::OTA_2STEP: break; - case LabelType::OTA_POSSIBLE: break; -#if FEATURE_ETHERNET - case LabelType::ETH_IP_ADDRESS: return formatIP(NetworkLocalIP()); - case LabelType::ETH_IP_SUBNET: return formatIP(NetworkSubnetMask()); - case LabelType::ETH_IP_ADDRESS_SUBNET: return strformat( - F("%s / %s"), - getValue(LabelType::ETH_IP_ADDRESS).c_str(), - getValue(LabelType::ETH_IP_SUBNET).c_str()); - case LabelType::ETH_IP_GATEWAY: return formatIP(NetworkGatewayIP()); - case LabelType::ETH_IP_DNS: return formatIP(NetworkDnsIP(0)); - case LabelType::ETH_MAC: return NetworkMacAddress().toString(); - case LabelType::ETH_DUPLEX: return EthLinkUp() ? (EthFullDuplex() ? F("Full Duplex") : F("Half Duplex")) : F("Link Down"); - case LabelType::ETH_SPEED: return EthLinkUp() ? getEthSpeed() : F("Link Down"); - case LabelType::ETH_STATE: return EthLinkUp() ? F("Link Up") : F("Link Down"); - case LabelType::ETH_SPEED_STATE: return EthLinkUp() ? getEthLinkSpeedState() : F("Link Down"); - case LabelType::ETH_CONNECTED: return ETHConnected() ? F("CONNECTED") : F("DISCONNECTED"); // 0=disconnected, 1=connected - case LabelType::ETH_CHIP: return toString(Settings.ETH_Phy_Type); -#endif // if FEATURE_ETHERNET -# if FEATURE_ETHERNET || defined(USES_ESPEASY_NOW) - case LabelType::ETH_WIFI_MODE: return toString(active_network_medium); -#endif - case LabelType::SUNRISE: return node_time.getSunriseTimeString(':'); - case LabelType::SUNSET: return node_time.getSunsetTimeString(':'); - case LabelType::SUNRISE_S: retval = node_time.sunRise.tm_hour * 3600 + node_time.sunRise.tm_min * 60 + node_time.sunRise.tm_sec; break; - case LabelType::SUNSET_S: retval = node_time.sunSet.tm_hour * 3600 + node_time.sunSet.tm_min * 60 + node_time.sunSet.tm_sec; break; - case LabelType::SUNRISE_M: retval = node_time.sunRise.tm_hour * 60 + node_time.sunRise.tm_min; break; - case LabelType::SUNSET_M: retval = node_time.sunSet.tm_hour * 60 + node_time.sunSet.tm_min; break; - case LabelType::ISNTP: return jsonBool(Settings.UseNTP()); - case LabelType::UPTIME_MS: return ull2String(getMicros64() / 1000); - case LabelType::TIMEZONE_OFFSET: retval = Settings.TimeZone; break; - case LabelType::LATITUDE: return toString(Settings.Latitude, 6); - case LabelType::LONGITUDE: return toString(Settings.Longitude, 6); - - case LabelType::MAX_LABEL: - break; - } - if (retval != INT_MAX) return String(retval); - return F("MissingString"); -} - -#if FEATURE_ETHERNET -String getEthSpeed() { - return strformat(F("%d [Mbps]"), EthLinkSpeed()); -} - -String getEthLinkSpeedState() { - if (EthLinkUp()) { - return strformat(F("%s %s %s"), - getValue(LabelType::ETH_STATE).c_str(), - getValue(LabelType::ETH_DUPLEX).c_str(), - getEthSpeed().c_str()); - } - return getValue(LabelType::ETH_STATE); -} - -#endif // if FEATURE_ETHERNET - -String getExtendedValue(LabelType::Enum label) { - switch (label) - { - case LabelType::UPTIME: - { - return minutesToDayHourMinute(getUptimeMinutes()); - } - - default: - break; - } - return EMPTY_STRING; -} - -String getFormNote(LabelType::Enum label) -{ - // Keep flash string till the end of the function, to reduce build size - // Otherwise lots of calls to String() constructor are included. - const __FlashStringHelper *flash_str = F(""); - - switch (label) { -#ifndef MINIMAL_OTA - case LabelType::CONNECT_HIDDEN_SSID: - flash_str = F("Must be checked to connect to a hidden SSID"); - break; -#ifdef ESP32 - case LabelType::WIFI_PASSIVE_SCAN: - flash_str = F("Passive scan listens for WiFi beacons, Active scan probes for AP. Passive scan is typically faster."); - break; -#endif // ifdef ESP32 - case LabelType::HIDDEN_SSID_SLOW_CONNECT: - flash_str = F("Required for some AP brands like Mikrotik to connect to hidden SSID"); - break; -#if FEATURE_USE_IPV6 - case LabelType::ENABLE_IPV6: - flash_str = F("Toggling IPv6 requires reboot"); - break; -#endif // if FEATURE_USE_IPV6 -#ifndef NO_HTTP_UPDATER - case LabelType::ALLOW_OTA_UNLIMITED: - flash_str = F("When enabled, OTA updating can overwrite the filesystem and settings!
Requires reboot to activate"); - break; -#endif // ifndef NO_HTTP_UPDATER -#if FEATURE_RULES_EASY_COLOR_CODE - case LabelType::DISABLE_RULES_AUTOCOMPLETE: - flash_str = F("Also disables Rules syntax highlighting!"); - break; -#endif // if FEATURE_RULES_EASY_COLOR_CODE - - case LabelType::FORCE_WIFI_NOSLEEP: - flash_str = F("Change WiFi sleep settings requires reboot to activate"); - break; - - case LabelType::CPU_ECO_MODE: - flash_str = F("Node may miss receiving packets with Eco mode enabled"); - break; - - case LabelType::WIFI_NR_EXTRA_SCANS: - flash_str = F("Number of extra times to scan all channels to have higher chance of finding the desired AP"); - break; -#ifndef ESP32 - case LabelType::WAIT_WIFI_CONNECT: - flash_str = F("Wait for 1000 msec right after connecting to WiFi.
May improve success on some APs like Fritz!Box"); - break; -#endif - -#endif - -#if FEATURE_SET_WIFI_TX_PWR - case LabelType::WIFI_TX_MAX_PWR: - case LabelType::WIFI_SENS_MARGIN: - { - float maxTXpwr; - float sensitivity = GetRSSIthreshold(maxTXpwr); - if (LabelType::WIFI_TX_MAX_PWR == label) { - return strformat( - F("Current max: %.2f dBm"), maxTXpwr); - } - return strformat( - F("Adjust TX power to target the AP with (sensitivity + margin) dBm signal strength. Current sensitivity: %.2f dBm"), - sensitivity); - } -#endif // if FEATURE_SET_WIFI_TX_PWR - - default: - return EMPTY_STRING; - } - - return flash_str; -} - - -String getFormUnit(LabelType::Enum label) -{ - const __FlashStringHelper *flash_str = F(""); - - switch (label) { -#if FEATURE_SET_WIFI_TX_PWR - case LabelType::WIFI_TX_MAX_PWR: - case LabelType::WIFI_CUR_TX_PWR: - case LabelType::WIFI_RSSI: - flash_str = F("dBm"); - break; - case LabelType::WIFI_SENS_MARGIN: - flash_str = F("dB"); - break; -#endif - case LabelType::TIME_WANDER: - flash_str = F("ppm"); - break; -#ifdef ESP32 - case LabelType::HEAP_SIZE: - case LabelType::HEAP_MIN_FREE: - #ifdef BOARD_HAS_PSRAM - case LabelType::PSRAM_SIZE: - case LabelType::PSRAM_FREE: - case LabelType::PSRAM_MIN_FREE: - case LabelType::PSRAM_MAX_FREE_BLOCK: - #endif // BOARD_HAS_PSRAM -#endif // ifdef ESP32 - case LabelType::FREE_MEM: - case LabelType::FREE_STACK: -#ifdef USE_SECOND_HEAP - case LabelType::FREE_HEAP_IRAM: -#endif -#if defined(CORE_POST_2_5_0) || defined(ESP32) - #ifndef LIMIT_BUILD_SIZE - case LabelType::HEAP_MAX_FREE_BLOCK: - #endif -#endif // if defined(CORE_POST_2_5_0) || defined(ESP32) - - flash_str = F("byte"); - break; - case LabelType::FLASH_CHIP_REAL_SIZE: - case LabelType::FLASH_IDE_SIZE: - flash_str = F("kB"); - break; -/* - case LabelType::UPTIME: - flash_str = F("min"); - break; -*/ - case LabelType::LOAD_PCT: -#if defined(CORE_POST_2_5_0) - #ifndef LIMIT_BUILD_SIZE - case LabelType::HEAP_FRAGMENTATION: - #endif -#endif // if defined(CORE_POST_2_5_0) - - flash_str = F("%"); - break; - - case LabelType::ESP_CHIP_FREQ: -#ifdef ESP32 - case LabelType::ESP_CHIP_XTAL_FREQ: - case LabelType::ESP_CHIP_APB_FREQ: -#endif - case LabelType::FLASH_CHIP_SPEED: - case LabelType::FLASH_IDE_SPEED: - flash_str = F("MHz"); - break; -#if FEATURE_INTERNAL_TEMPERATURE - case LabelType::INTERNAL_TEMPERATURE: - flash_str = F("°C"); - break; -#endif // if FEATURE_INTERNAL_TEMPERATURE - - - - default: - return EMPTY_STRING; - } - - return flash_str; +#include "../Helpers/StringProvider.h" + +#if FEATURE_ETHERNET +# include +#endif // if FEATURE_ETHERNET + +#include "../../ESPEasy-Globals.h" +#include "../../ESPEasy_common.h" + +#include "../CustomBuild/CompiletimeDefines.h" + +#include "../ESPEasyCore/ESPEasyNetwork.h" +#include "../ESPEasyCore/ESPEasyWifi.h" +#if FEATURE_ETHERNET +#include "../ESPEasyCore/ESPEasyEth.h" +#endif + +#include "../Globals/Device.h" +#include "../Globals/ESPEasy_Console.h" +#include "../Globals/ESPEasy_Scheduler.h" +#include "../Globals/ESPEasy_time.h" +#include "../Globals/ESPEasy_now_state.h" +#include "../Globals/ESPEasyWiFiEvent.h" + +#if FEATURE_ETHERNET +#include "../Globals/ESPEasyEthEvent.h" +#endif + +#include "../Globals/NetworkState.h" + +#if FEATURE_ESPEASY_P2P +#include "../Globals/Nodes.h" +#endif + +#include "../Globals/SecuritySettings.h" +#include "../Globals/Settings.h" +#include "../Globals/WiFi_AP_Candidates.h" + +#include "../Helpers/Convert.h" +#include "../Helpers/ESPEasy_Storage.h" +#include "../Helpers/Hardware_device_info.h" +#include "../Helpers/Hardware_temperature_sensor.h" +#include "../Helpers/Memory.h" +#include "../Helpers/Misc.h" +#include "../Helpers/Networking.h" +#include "../Helpers/Scheduler.h" +#include "../Helpers/StringConverter.h" +#include "../Helpers/StringGenerator_System.h" +#include "../Helpers/StringGenerator_WiFi.h" + +#include "../WebServer/JSON.h" +#include "../WebServer/AccessControl.h" + +#ifdef ESP32 +#include +#endif + +String getInternalLabel(LabelType::Enum label, char replaceSpace) { + return to_internal_string(getLabel(label), replaceSpace); +} + +const __FlashStringHelper * getLabel(LabelType::Enum label) { + switch (label) + { + case LabelType::UNIT_NR: return F("Unit Number"); + #if FEATURE_ZEROFILLED_UNITNUMBER + case LabelType::UNIT_NR_0: return F("Unit Number 0-filled"); + #endif // FEATURE_ZEROFILLED_UNITNUMBER + case LabelType::UNIT_NAME: return F("Unit Name"); + case LabelType::HOST_NAME: return F("Hostname"); + + case LabelType::LOCAL_TIME: return F("Local Time"); + case LabelType::TIME_SOURCE: return F("Time Source"); + case LabelType::TIME_WANDER: return F("Time Wander"); + #if FEATURE_EXT_RTC + case LabelType::EXT_RTC_UTC_TIME: return F("UTC time stored in RTC chip"); + #endif + case LabelType::UPTIME: return F("Uptime"); + case LabelType::LOAD_PCT: return F("Load"); + case LabelType::LOOP_COUNT: return F("Load LC"); + case LabelType::CPU_ECO_MODE: return F("CPU Eco Mode"); +#if FEATURE_SET_WIFI_TX_PWR + case LabelType::WIFI_TX_MAX_PWR: return F("Max WiFi TX Power"); + case LabelType::WIFI_CUR_TX_PWR: return F("Current WiFi TX Power"); + case LabelType::WIFI_SENS_MARGIN: return F("WiFi Sensitivity Margin"); + case LabelType::WIFI_SEND_AT_MAX_TX_PWR:return F("Send With Max TX Power"); +#endif + case LabelType::WIFI_NR_EXTRA_SCANS: return F("Extra WiFi scan loops"); + case LabelType::WIFI_USE_LAST_CONN_FROM_RTC: return F("Use Last Connected AP from RTC"); + + case LabelType::FREE_MEM: return F("Free RAM"); + case LabelType::FREE_STACK: return F("Free Stack"); +#ifdef USE_SECOND_HEAP + case LabelType::FREE_HEAP_IRAM: return F("Free 2nd Heap"); +#endif + +#if defined(CORE_POST_2_5_0) || defined(ESP32) + #ifndef LIMIT_BUILD_SIZE + case LabelType::HEAP_MAX_FREE_BLOCK: return F("Heap Max Free Block"); + #endif +#endif // if defined(CORE_POST_2_5_0) || defined(ESP32) +#if defined(CORE_POST_2_5_0) + #ifndef LIMIT_BUILD_SIZE + case LabelType::HEAP_FRAGMENTATION: return F("Heap Fragmentation"); + #endif +#endif // if defined(CORE_POST_2_5_0) + +#ifdef ESP32 + case LabelType::HEAP_SIZE: return F("Heap Size"); + case LabelType::HEAP_MIN_FREE: return F("Heap Min Free"); + #ifdef BOARD_HAS_PSRAM + case LabelType::PSRAM_SIZE: return F("PSRAM Size"); + case LabelType::PSRAM_FREE: return F("PSRAM Free"); + case LabelType::PSRAM_MIN_FREE: return F("PSRAM Min Free"); + case LabelType::PSRAM_MAX_FREE_BLOCK: return F("PSRAM Max Free Block"); + #endif // BOARD_HAS_PSRAM +#endif // ifdef ESP32 + + case LabelType::JSON_BOOL_QUOTES: return F("JSON bool output without quotes"); +#if FEATURE_TIMING_STATS + case LabelType::ENABLE_TIMING_STATISTICS: return F("Collect Timing Statistics"); +#endif + case LabelType::ENABLE_RULES_CACHING: return F("Enable Rules Cache"); + case LabelType::ENABLE_SERIAL_PORT_CONSOLE: return F("Enable Serial Port Console"); + case LabelType::CONSOLE_SERIAL_PORT: return F("Console Serial Port"); +#if USES_ESPEASY_CONSOLE_FALLBACK_PORT + case LabelType::CONSOLE_FALLBACK_TO_SERIAL0: return F("Fallback to Serial 0"); + case LabelType::CONSOLE_FALLBACK_PORT: return F("Console Fallback Port"); +#endif + +// case LabelType::ENABLE_RULES_EVENT_REORDER: return F("Optimize Rules Cache Event Order"); // TD-er: Disabled for now + case LabelType::TASKVALUESET_ALL_PLUGINS: return F("Allow TaskValueSet on all plugins"); + case LabelType::ALLOW_OTA_UNLIMITED: return F("Allow OTA without size-check"); +#if FEATURE_CLEAR_I2C_STUCK + case LabelType::ENABLE_CLEAR_HUNG_I2C_BUS: return F("Try clear I2C bus when stuck"); +#endif + #if FEATURE_I2C_DEVICE_CHECK + case LabelType::ENABLE_I2C_DEVICE_CHECK: return F("Check I2C devices when enabled"); + #endif // if FEATURE_I2C_DEVICE_CHECK +#ifndef BUILD_NO_RAM_TRACKER + case LabelType::ENABLE_RAM_TRACKING: return F("Enable RAM Tracker"); +#endif +#if FEATURE_AUTO_DARK_MODE + case LabelType::ENABLE_AUTO_DARK_MODE: return F("Web light/dark mode"); +#endif // FEATURE_AUTO_DARK_MODE +#if FEATURE_RULES_EASY_COLOR_CODE + case LabelType::DISABLE_RULES_AUTOCOMPLETE: return F("Disable Rules auto-completion"); +#endif // if FEATURE_RULES_EASY_COLOR_CODE +#if FEATURE_TARSTREAM_SUPPORT + case LabelType::DISABLE_SAVE_CONFIG_AS_TAR: return F("Disable Save Config as .tar"); +#endif // if FEATURE_TARSTREAM_SUPPORT + #if FEATURE_TASKVALUE_UNIT_OF_MEASURE + case LabelType::SHOW_UOM_ON_DEVICES_PAGE: return F("Show Unit of Measure"); + #endif // if FEATURE_TASKVALUE_UNIT_OF_MEASURE + + case LabelType::BOOT_TYPE: return F("Last Boot Cause"); + case LabelType::BOOT_COUNT: return F("Boot Count"); + case LabelType::DEEP_SLEEP_ALTERNATIVE_CALL: return F("Deep Sleep Alternative"); + case LabelType::RESET_REASON: return F("Reset Reason"); + case LabelType::LAST_TASK_BEFORE_REBOOT: return F("Last Action before Reboot"); + case LabelType::SW_WD_COUNT: return F("SW WD count"); + + + case LabelType::WIFI_CONNECTION: return F("WiFi Connection"); + case LabelType::WIFI_RSSI: return F("RSSI"); + case LabelType::IP_CONFIG: return F("IP Config"); +#if FEATURE_USE_IPV6 + case LabelType::IP6_LOCAL: return F("IPv6 link local"); + case LabelType::IP6_GLOBAL: return F("IPv6 global"); +// case LabelType::IP6_ALL_ADDRESSES: return F("IPv6 all addresses"); +#endif + case LabelType::IP_CONFIG_STATIC: return F("Static"); + case LabelType::IP_CONFIG_DYNAMIC: return F("DHCP"); + case LabelType::IP_ADDRESS: return F("IP Address"); + case LabelType::IP_SUBNET: return F("IP Subnet"); + case LabelType::IP_ADDRESS_SUBNET: return F("IP / Subnet"); + case LabelType::GATEWAY: return F("Gateway"); + case LabelType::CLIENT_IP: return F("Client IP"); + #if FEATURE_MDNS + case LabelType::M_DNS: return F("mDNS"); + #endif // if FEATURE_MDNS + case LabelType::DNS: return F("DNS"); + case LabelType::DNS_1: return F("DNS 1"); + case LabelType::DNS_2: return F("DNS 2"); + case LabelType::ALLOWED_IP_RANGE: return F("Allowed IP Range"); + case LabelType::STA_MAC: return F("STA MAC"); + case LabelType::AP_MAC: return F("AP MAC"); + case LabelType::SSID: return F("SSID"); + case LabelType::BSSID: return F("BSSID"); + case LabelType::CHANNEL: return F("Channel"); + case LabelType::ENCRYPTION_TYPE_STA: return F("Encryption Type"); + case LabelType::CONNECTED: return F("Connected"); + case LabelType::CONNECTED_MSEC: return F("Connected msec"); + case LabelType::LAST_DISCONNECT_REASON: return F("Last Disconnect Reason"); + case LabelType::LAST_DISC_REASON_STR: return F("Last Disconnect Reason str"); + case LabelType::NUMBER_RECONNECTS: return F("Number Reconnects"); + case LabelType::WIFI_STORED_SSID1: return F("Configured SSID1"); + case LabelType::WIFI_STORED_SSID2: return F("Configured SSID2"); + + + case LabelType::FORCE_WIFI_BG: return F("Force WiFi B/G"); + case LabelType::RESTART_WIFI_LOST_CONN: return F("Restart WiFi Lost Conn"); + case LabelType::FORCE_WIFI_NOSLEEP: return F("Force WiFi No Sleep"); + case LabelType::PERIODICAL_GRAT_ARP: return F("Periodical send Gratuitous ARP"); + case LabelType::CONNECTION_FAIL_THRESH: return F("Connection Failure Threshold"); +#ifndef ESP32 + case LabelType::WAIT_WIFI_CONNECT: return F("Extra Wait WiFi Connect"); +#endif + case LabelType::CONNECT_HIDDEN_SSID: return F("Include Hidden SSID"); +#ifdef ESP32 + case LabelType::WIFI_PASSIVE_SCAN: return F("Passive WiFi Scan"); +#endif + case LabelType::HIDDEN_SSID_SLOW_CONNECT: return F("Hidden SSID Slow Connect"); + case LabelType::SDK_WIFI_AUTORECONNECT: return F("Enable SDK WiFi Auto Reconnect"); +#if FEATURE_USE_IPV6 + case LabelType::ENABLE_IPV6: return F("Enable IPv6"); +#endif + + + case LabelType::BUILD_DESC: return F("Build"); + case LabelType::GIT_BUILD: return F("Git Build"); + case LabelType::SYSTEM_LIBRARIES: return F("System Libraries"); +#ifdef ESP32 + case LabelType::ESP_IDF_SDK_VERSION: return F("ESP-IDF Version"); +#endif + case LabelType::PLUGIN_COUNT: return F("Plugin Count"); + case LabelType::PLUGIN_DESCRIPTION: return F("Plugin Description"); + case LabelType::BUILD_TIME: return F("Build Time"); + case LabelType::BINARY_FILENAME: return F("Binary Filename"); + case LabelType::BUILD_PLATFORM: return F("Build Platform"); + case LabelType::GIT_HEAD: return F("Git HEAD"); + #ifdef CONFIGURATION_CODE + case LabelType::CONFIGURATION_CODE_LBL: return F("Configuration code"); + #endif // ifdef CONFIGURATION_CODE + + case LabelType::I2C_BUS_STATE: return F("I2C Bus State"); + case LabelType::I2C_BUS_CLEARED_COUNT: return F("I2C bus cleared count"); + + case LabelType::SYSLOG_LOG_LEVEL: return F("Syslog Log Level"); + case LabelType::SERIAL_LOG_LEVEL: return F("Serial Log Level"); + case LabelType::WEB_LOG_LEVEL: return F("Web Log Level"); + #if FEATURE_SD + case LabelType::SD_LOG_LEVEL: return F("SD Log Level"); + #endif // if FEATURE_SD + + case LabelType::ESP_CHIP_ID: return F("ESP Chip ID"); + case LabelType::ESP_CHIP_FREQ: return F("ESP Chip Frequency"); +#ifdef ESP32 + case LabelType::ESP_CHIP_XTAL_FREQ: return F("ESP Crystal Frequency"); + case LabelType::ESP_CHIP_APB_FREQ: return F("ESP APB Frequency"); +#endif + case LabelType::ESP_CHIP_MODEL: return F("ESP Chip Model"); + case LabelType::ESP_CHIP_REVISION: return F("ESP Chip Revision"); + case LabelType::ESP_CHIP_CORES: return F("ESP Chip Cores"); + + case LabelType::BOARD_NAME: return F("ESP Board Name"); + + case LabelType::FLASH_CHIP_ID: return F("Flash Chip ID"); + case LabelType::FLASH_CHIP_VENDOR: return F("Flash Chip Vendor"); + case LabelType::FLASH_CHIP_MODEL: return F("Flash Chip Model"); + case LabelType::FLASH_CHIP_REAL_SIZE: return F("Flash Chip Real Size"); + case LabelType::FLASH_CHIP_SPEED: return F("Flash Chip Speed"); + case LabelType::FLASH_IDE_SIZE: return F("Flash IDE Size"); + case LabelType::FLASH_IDE_SPEED: return F("Flash IDE Speed"); + case LabelType::FLASH_IDE_MODE: return F("Flash IDE Mode"); + case LabelType::FLASH_WRITE_COUNT: return F("Flash Writes"); + case LabelType::SKETCH_SIZE: return F("Sketch Size"); + case LabelType::SKETCH_FREE: return F("Sketch Free"); + #ifdef USE_LITTLEFS + case LabelType::FS_SIZE: return F("Little FS Size"); + case LabelType::FS_FREE: return F("Little FS Free"); + #else // ifdef USE_LITTLEFS + case LabelType::FS_SIZE: return F("SPIFFS Size"); + case LabelType::FS_FREE: return F("SPIFFS Free"); + #endif // ifdef USE_LITTLEFS + case LabelType::MAX_OTA_SKETCH_SIZE: return F("Max. OTA Sketch Size"); + case LabelType::OTA_2STEP: return F("OTA 2-step Needed"); + case LabelType::OTA_POSSIBLE: return F("OTA possible"); + +#ifdef USES_ESPEASY_NOW + case LabelType::ESPEASY_NOW_ENABLED: return F(ESPEASY_NOW_NAME " Enabled"); + case LabelType::ESPEASY_NOW_CHANNEL: return F(ESPEASY_NOW_NAME " Channel"); + case LabelType::ESPEASY_NOW_MQTT: return F(ESPEASY_NOW_NAME " Route to MQTT broker"); + case LabelType::ESPEASY_NOW_DISTANCE: return F(ESPEASY_NOW_NAME " Distance"); + case LabelType::TEMP_DISABLE_ESPEASY_NOW: return F(ESPEASY_NOW_NAME " Temporary disabled"); + case LabelType::ESPEASY_NOW_FORCED_CHANNEL: return F(ESPEASY_NOW_NAME " Forced Channel"); +#endif + +#if FEATURE_INTERNAL_TEMPERATURE + case LabelType::INTERNAL_TEMPERATURE: return F("Internal Temperature"); +#endif // if FEATURE_INTERNAL_TEMPERATURE +#if FEATURE_ETHERNET + case LabelType::ETH_IP_ADDRESS: return F("Eth IP Address"); + case LabelType::ETH_IP_SUBNET: return F("Eth IP Subnet"); + case LabelType::ETH_IP_ADDRESS_SUBNET: return F("Eth IP / Subnet"); + case LabelType::ETH_IP_GATEWAY: return F("Eth Gateway"); + case LabelType::ETH_IP_DNS: return F("Eth DNS"); +#if FEATURE_USE_IPV6 + case LabelType::ETH_IP6_LOCAL: return F("Eth IPv6 link local"); +#endif + case LabelType::ETH_MAC: return F("Eth MAC"); + case LabelType::ETH_DUPLEX: return F("Eth Mode"); + case LabelType::ETH_SPEED: return F("Eth Speed"); + case LabelType::ETH_STATE: return F("Eth State"); + case LabelType::ETH_SPEED_STATE: return F("Eth Speed State"); + case LabelType::ETH_CONNECTED: return F("Eth connected"); + case LabelType::ETH_CHIP: return F("Eth chip"); +#endif // if FEATURE_ETHERNET +# if FEATURE_ETHERNET || defined(USES_ESPEASY_NOW) + case LabelType::ETH_WIFI_MODE: return F("Network Type"); +#endif + case LabelType::SUNRISE: return F("Sunrise"); + case LabelType::SUNSET: return F("Sunset"); + case LabelType::SUNRISE_S: return F("Sunrise sec."); + case LabelType::SUNSET_S: return F("Sunset sec."); + case LabelType::SUNRISE_M: return F("Sunrise min."); + case LabelType::SUNSET_M: return F("Sunset min."); + case LabelType::ISNTP: return F("Use NTP"); + case LabelType::UPTIME_MS: return F("Uptime (ms)"); + case LabelType::TIMEZONE_OFFSET: return F("Timezone Offset"); + case LabelType::LATITUDE: return F("Latitude"); + case LabelType::LONGITUDE: return F("Longitude"); + + case LabelType::MAX_LABEL: + break; + + } + return F("MissingString"); +} + +String getValue(LabelType::Enum label) { + int retval = INT_MAX; + switch (label) + { + case LabelType::UNIT_NR: retval = Settings.Unit; break; + #if FEATURE_ZEROFILLED_UNITNUMBER + case LabelType::UNIT_NR_0: + { + // Fixed 3-digit unitnumber + return formatIntLeadingZeroes(Settings.Unit, 3); + } + #endif // FEATURE_ZEROFILLED_UNITNUMBER + case LabelType::UNIT_NAME: return Settings.getName(); // Only return the set name, no appended unit. + case LabelType::HOST_NAME: return NetworkGetHostname(); + + + case LabelType::LOCAL_TIME: return node_time.getDateTimeString('-', ':', ' '); + case LabelType::TIME_SOURCE: + { + String timeSource_str = toString(node_time.getTimeSource()); + if (((node_time.getTimeSource() == timeSource_t::ESPEASY_p2p_UDP) || + (node_time.getTimeSource() == timeSource_t::ESP_now_peer)) && + (node_time.timeSource_p2p_unit != 0)) + { + return strformat(F("%s (%u)"), timeSource_str.c_str(), node_time.timeSource_p2p_unit); + } + return timeSource_str; + } + case LabelType::TIME_WANDER: return String(node_time.timeWander, 3); + #if FEATURE_EXT_RTC + case LabelType::EXT_RTC_UTC_TIME: + { + if (Settings.ExtTimeSource() != ExtTimeSource_e::None) { + // Try to read the stored time in the ext. time source to allow to check if it is working properly. + uint32_t unixtime; + if (node_time.ExtRTC_get(unixtime)) { + struct tm RTC_time; + breakTime(unixtime, RTC_time); + return formatDateTimeString(RTC_time); + } else { + return F("Not Set"); + } + } + return String('-'); + } + #endif + case LabelType::UPTIME: retval = getUptimeMinutes(); break; + case LabelType::LOAD_PCT: return toString(getCPUload(), 2); + case LabelType::LOOP_COUNT: retval = getLoopCountPerSec(); break; + case LabelType::CPU_ECO_MODE: return jsonBool(Settings.EcoPowerMode()); +#if FEATURE_SET_WIFI_TX_PWR + case LabelType::WIFI_TX_MAX_PWR: return toString(Settings.getWiFi_TX_power(), 2); + case LabelType::WIFI_CUR_TX_PWR: return toString(WiFiEventData.wifi_TX_pwr, 2); + case LabelType::WIFI_SENS_MARGIN: retval = Settings.WiFi_sensitivity_margin; break; + case LabelType::WIFI_SEND_AT_MAX_TX_PWR:return jsonBool(Settings.UseMaxTXpowerForSending()); +#endif + case LabelType::WIFI_NR_EXTRA_SCANS: retval = Settings.NumberExtraWiFiScans; break; + case LabelType::WIFI_USE_LAST_CONN_FROM_RTC: return jsonBool(Settings.UseLastWiFiFromRTC()); + + case LabelType::FREE_MEM: retval = FreeMem(); break; + case LabelType::FREE_STACK: retval = getCurrentFreeStack(); break; + +#ifdef USE_SECOND_HEAP + case LabelType::FREE_HEAP_IRAM: retval = FreeMem2ndHeap(); break; +#endif + +#if defined(CORE_POST_2_5_0) + #ifndef LIMIT_BUILD_SIZE + case LabelType::HEAP_MAX_FREE_BLOCK: retval = ESP.getMaxFreeBlockSize(); break; + #endif +#endif // if defined(CORE_POST_2_5_0) +#if defined(ESP32) + #ifndef LIMIT_BUILD_SIZE + case LabelType::HEAP_MAX_FREE_BLOCK: retval = ESP.getMaxAllocHeap(); break; + #endif +#endif // if defined(ESP32) +#if defined(CORE_POST_2_5_0) + #ifndef LIMIT_BUILD_SIZE + case LabelType::HEAP_FRAGMENTATION: retval = ESP.getHeapFragmentation(); break; + #endif +#endif // if defined(CORE_POST_2_5_0) +#ifdef ESP32 + case LabelType::HEAP_SIZE: retval = ESP.getHeapSize(); break; + case LabelType::HEAP_MIN_FREE: retval = ESP.getMinFreeHeap(); break; + #ifdef BOARD_HAS_PSRAM + case LabelType::PSRAM_SIZE: retval = UsePSRAM() ? ESP.getPsramSize() : 0; break; + case LabelType::PSRAM_FREE: retval = UsePSRAM() ? ESP.getFreePsram() : 0; break; + case LabelType::PSRAM_MIN_FREE: retval = UsePSRAM() ? ESP.getMinFreePsram() : 0; break; + case LabelType::PSRAM_MAX_FREE_BLOCK: retval = UsePSRAM() ? ESP.getMaxAllocPsram() : 0; break; + #endif // BOARD_HAS_PSRAM +#endif // ifdef ESP32 + + + case LabelType::JSON_BOOL_QUOTES: return jsonBool(Settings.JSONBoolWithoutQuotes()); +#if FEATURE_TIMING_STATS + case LabelType::ENABLE_TIMING_STATISTICS: return jsonBool(Settings.EnableTimingStats()); +#endif + case LabelType::ENABLE_RULES_CACHING: return jsonBool(Settings.EnableRulesCaching()); + case LabelType::ENABLE_SERIAL_PORT_CONSOLE: return jsonBool(Settings.UseSerial); + case LabelType::CONSOLE_SERIAL_PORT: return ESPEasy_Console.getPortDescription(); + +#if USES_ESPEASY_CONSOLE_FALLBACK_PORT + case LabelType::CONSOLE_FALLBACK_TO_SERIAL0: return jsonBool(Settings.console_serial0_fallback); + case LabelType::CONSOLE_FALLBACK_PORT: return ESPEasy_Console.getFallbackPortDescription(); +#endif + +// case LabelType::ENABLE_RULES_EVENT_REORDER: return jsonBool(Settings.EnableRulesEventReorder()); // TD-er: Disabled for now + case LabelType::TASKVALUESET_ALL_PLUGINS: return jsonBool(Settings.AllowTaskValueSetAllPlugins()); + case LabelType::ALLOW_OTA_UNLIMITED: return jsonBool(Settings.AllowOTAUnlimited()); +#if FEATURE_CLEAR_I2C_STUCK + case LabelType::ENABLE_CLEAR_HUNG_I2C_BUS: return jsonBool(Settings.EnableClearHangingI2Cbus()); +#endif +#if FEATURE_I2C_DEVICE_CHECK + case LabelType::ENABLE_I2C_DEVICE_CHECK: return jsonBool(Settings.CheckI2Cdevice()); +#endif // if FEATURE_I2C_DEVICE_CHECK +#ifndef BUILD_NO_RAM_TRACKER + case LabelType::ENABLE_RAM_TRACKING: return jsonBool(Settings.EnableRAMTracking()); +#endif +#if FEATURE_AUTO_DARK_MODE + case LabelType::ENABLE_AUTO_DARK_MODE: return toString(Settings.getCssMode()); +#endif // FEATURE_AUTO_DARK_MODE +#if FEATURE_RULES_EASY_COLOR_CODE + case LabelType::DISABLE_RULES_AUTOCOMPLETE: return jsonBool(Settings.DisableRulesCodeCompletion()); +#endif // if FEATURE_RULES_EASY_COLOR_CODE +#if FEATURE_TARSTREAM_SUPPORT + case LabelType::DISABLE_SAVE_CONFIG_AS_TAR: return jsonBool(Settings.DisableSaveConfigAsTar()); +#endif // if FEATURE_TARSTREAM_SUPPORT + #if FEATURE_TASKVALUE_UNIT_OF_MEASURE + case LabelType::SHOW_UOM_ON_DEVICES_PAGE: return jsonBool(Settings.ShowUnitOfMeasureOnDevicesPage()); + #endif // if FEATURE_TASKVALUE_UNIT_OF_MEASURE + + case LabelType::BOOT_TYPE: return getLastBootCauseString(); + case LabelType::BOOT_COUNT: break; + case LabelType::DEEP_SLEEP_ALTERNATIVE_CALL: return jsonBool(Settings.UseAlternativeDeepSleep()); + case LabelType::RESET_REASON: return getResetReasonString(); + case LabelType::LAST_TASK_BEFORE_REBOOT: return ESPEasy_Scheduler::decodeSchedulerId(lastMixedSchedulerId_beforereboot); + case LabelType::SW_WD_COUNT: retval = sw_watchdog_callback_count; break; + + case LabelType::WIFI_CONNECTION: break; + case LabelType::WIFI_RSSI: retval = WiFi.RSSI(); break; + case LabelType::IP_CONFIG: return useStaticIP() + ? getLabel(LabelType::IP_CONFIG_STATIC) + : getLabel(LabelType::IP_CONFIG_DYNAMIC); + case LabelType::IP_CONFIG_STATIC: break; + case LabelType::IP_CONFIG_DYNAMIC: break; + case LabelType::IP_ADDRESS: return formatIP(NetworkLocalIP()); + case LabelType::IP_SUBNET: return formatIP(NetworkSubnetMask()); + case LabelType::IP_ADDRESS_SUBNET: return strformat(F("%s / %s"), getValue(LabelType::IP_ADDRESS).c_str(), getValue(LabelType::IP_SUBNET).c_str()); + case LabelType::GATEWAY: return formatIP(NetworkGatewayIP()); +#if FEATURE_USE_IPV6 + case LabelType::IP6_LOCAL: return formatIP(NetworkLocalIP6(), true); + case LabelType::IP6_GLOBAL: return formatIP(NetworkGlobalIP6()); +#if FEATURE_ETHERNET + case LabelType::ETH_IP6_LOCAL: return formatIP(NetworkLocalIP6(), true); +#endif +/* + case LabelType::IP6_ALL_ADDRESSES: + { + IP6Addresses_t addresses = NetworkAllIPv6(); + String res; + for (auto it = addresses.begin(); it != addresses.end(); ++it) + { + if (!res.isEmpty()) { + res += F("
"); + } + res += it->toString(); + } + return res; + } +*/ +#endif + case LabelType::CLIENT_IP: return formatIP(web_server.client().remoteIP(), true); + #if FEATURE_INTERNAL_TEMPERATURE + case LabelType::INTERNAL_TEMPERATURE: return toString(getInternalTemperature()); + #endif // if FEATURE_INTERNAL_TEMPERATURE + + #if FEATURE_MDNS + case LabelType::M_DNS: return NetworkGetHostname() + F(".local"); + #endif // if FEATURE_MDNS + case LabelType::DNS: return strformat(F("%s / %s"), getValue(LabelType::DNS_1).c_str(), getValue(LabelType::DNS_2).c_str()); + case LabelType::DNS_1: return formatIP(NetworkDnsIP(0)); + case LabelType::DNS_2: return formatIP(NetworkDnsIP(1)); + case LabelType::ALLOWED_IP_RANGE: return describeAllowedIPrange(); + case LabelType::STA_MAC: return WifiSTAmacAddress().toString(); + case LabelType::AP_MAC: return WifiSoftAPmacAddress().toString(); + case LabelType::SSID: return WiFi.SSID(); + case LabelType::BSSID: return WiFi.BSSIDstr(); + case LabelType::CHANNEL: retval = WiFi.channel(); break; + case LabelType::ENCRYPTION_TYPE_STA: return // WiFi_AP_Candidates.getCurrent().encryption_type(); + WiFi_encryptionType(WiFiEventData.auth_mode); + case LabelType::CONNECTED: + #if FEATURE_ETHERNET + if(active_network_medium == NetworkMedium_t::Ethernet) { + return format_msec_duration(EthEventData.lastConnectMoment.millisPassedSince()); + } + #endif // if FEATURE_ETHERNET + return format_msec_duration(WiFiEventData.lastConnectMoment.millisPassedSince()); + + // Use only the nr of seconds to fit it in an int32, plus append '000' to have msec format again. + case LabelType::CONNECTED_MSEC: + #if FEATURE_ETHERNET + if(active_network_medium == NetworkMedium_t::Ethernet) { + return String(static_cast(EthEventData.lastConnectMoment.millisPassedSince() / 1000ll)) + F("000"); + } + #endif // if FEATURE_ETHERNET + return String(static_cast(WiFiEventData.lastConnectMoment.millisPassedSince() / 1000ll)) + F("000"); + case LabelType::LAST_DISCONNECT_REASON: return String(WiFiEventData.lastDisconnectReason); + case LabelType::LAST_DISC_REASON_STR: return getLastDisconnectReason(); + case LabelType::NUMBER_RECONNECTS: retval = WiFiEventData.wifi_reconnects; break; + case LabelType::WIFI_STORED_SSID1: return String(SecuritySettings.WifiSSID); + case LabelType::WIFI_STORED_SSID2: return String(SecuritySettings.WifiSSID2); + + + case LabelType::FORCE_WIFI_BG: return jsonBool(Settings.ForceWiFi_bg_mode()); + case LabelType::RESTART_WIFI_LOST_CONN: return jsonBool(Settings.WiFiRestart_connection_lost()); + case LabelType::FORCE_WIFI_NOSLEEP: return jsonBool(Settings.WifiNoneSleep()); + case LabelType::PERIODICAL_GRAT_ARP: return jsonBool(Settings.gratuitousARP()); + case LabelType::CONNECTION_FAIL_THRESH: retval = Settings.ConnectionFailuresThreshold; break; +#ifndef ESP32 + case LabelType::WAIT_WIFI_CONNECT: return jsonBool(Settings.WaitWiFiConnect()); +#endif + case LabelType::CONNECT_HIDDEN_SSID: return jsonBool(Settings.IncludeHiddenSSID()); +#ifdef ESP32 + case LabelType::WIFI_PASSIVE_SCAN: return jsonBool(Settings.PassiveWiFiScan()); +#endif + case LabelType::HIDDEN_SSID_SLOW_CONNECT: return jsonBool(Settings.HiddenSSID_SlowConnectPerBSSID()); + case LabelType::SDK_WIFI_AUTORECONNECT: return jsonBool(Settings.SDK_WiFi_autoreconnect()); +#if FEATURE_USE_IPV6 + case LabelType::ENABLE_IPV6: return jsonBool(Settings.EnableIPv6()); +#endif + + + case LabelType::BUILD_DESC: return getSystemBuildString(); + case LabelType::GIT_BUILD: + { + const String res(F(BUILD_GIT)); + + if (!res.isEmpty()) { return res; } + return get_git_head(); + } + case LabelType::SYSTEM_LIBRARIES: return getSystemLibraryString(); +#ifdef ESP32 + case LabelType::ESP_IDF_SDK_VERSION: return strformat( + F("%d.%d.%d"), + ESP_IDF_VERSION_MAJOR, + ESP_IDF_VERSION_MINOR, + ESP_IDF_VERSION_PATCH); +#endif + + case LabelType::PLUGIN_COUNT: retval = getDeviceCount() + 1; break; + case LabelType::PLUGIN_DESCRIPTION: return getPluginDescriptionString(); + case LabelType::BUILD_TIME: return String(get_build_date()) + ' ' + get_build_time(); + case LabelType::BINARY_FILENAME: return get_binary_filename(); + case LabelType::BUILD_PLATFORM: return get_build_platform(); + case LabelType::GIT_HEAD: return get_git_head(); + #ifdef CONFIGURATION_CODE + case LabelType::CONFIGURATION_CODE_LBL: return getConfigurationCode(); + #endif // ifdef CONFIGURATION_CODE + case LabelType::I2C_BUS_STATE: return toString(I2C_state); + case LabelType::I2C_BUS_CLEARED_COUNT: retval = I2C_bus_cleared_count; break; + case LabelType::SYSLOG_LOG_LEVEL: return getLogLevelDisplayString(Settings.SyslogLevel); + case LabelType::SERIAL_LOG_LEVEL: return getLogLevelDisplayString(getSerialLogLevel()); + case LabelType::WEB_LOG_LEVEL: return getLogLevelDisplayString(getWebLogLevel()); + #if FEATURE_SD + case LabelType::SD_LOG_LEVEL: return getLogLevelDisplayString(Settings.SDLogLevel); + #endif // if FEATURE_SD + + case LabelType::ESP_CHIP_ID: return formatToHex(getChipId(), 6); + case LabelType::ESP_CHIP_FREQ: retval = ESP.getCpuFreqMHz(); break; +#ifdef ESP32 + case LabelType::ESP_CHIP_XTAL_FREQ: retval = getXtalFrequencyMHz(); break; + case LabelType::ESP_CHIP_APB_FREQ: retval = rtc_clk_apb_freq_get() / 1000000; break; + //getApbFrequency() / 1000000; break; +#endif + case LabelType::ESP_CHIP_MODEL: return getChipModel(); + case LabelType::ESP_CHIP_REVISION: return getChipRevision(); + case LabelType::ESP_CHIP_CORES: retval = getChipCores(); break; + case LabelType::BOARD_NAME: return get_board_name(); + case LabelType::FLASH_CHIP_ID: return formatToHex(getFlashChipId(), 6); + case LabelType::FLASH_CHIP_VENDOR: return formatToHex(getFlashChipId() & 0xFF, 2); + case LabelType::FLASH_CHIP_MODEL: + { + const uint32_t flashChipId = getFlashChipId(); + const uint32_t flashDevice = (flashChipId & 0xFF00) | ((flashChipId >> 16) & 0xFF); + return formatToHex(flashDevice, 4); + } + case LabelType::FLASH_CHIP_REAL_SIZE: retval = getFlashRealSizeInBytes(); break; + case LabelType::FLASH_CHIP_SPEED: retval = getFlashChipSpeed() / 1000000; break; + case LabelType::FLASH_IDE_SIZE: break; + case LabelType::FLASH_IDE_SPEED: break; + case LabelType::FLASH_IDE_MODE: return getFlashChipMode(); + case LabelType::FLASH_WRITE_COUNT: break; + case LabelType::SKETCH_SIZE: break; + case LabelType::SKETCH_FREE: break; + case LabelType::FS_SIZE: retval = SpiffsTotalBytes(); break; + case LabelType::FS_FREE: retval = SpiffsFreeSpace(); break; + case LabelType::MAX_OTA_SKETCH_SIZE: break; + case LabelType::OTA_2STEP: break; + case LabelType::OTA_POSSIBLE: break; + +#ifdef USES_ESPEASY_NOW + case LabelType::ESPEASY_NOW_ENABLED: return jsonBool(Settings.UseESPEasyNow()); + case LabelType::TEMP_DISABLE_ESPEASY_NOW: return jsonBool(temp_disable_EspEasy_now_timer != 0); + case LabelType::ESPEASY_NOW_FORCED_CHANNEL: return String(Settings.ForceESPEasyNOWchannel); + case LabelType::ESPEASY_NOW_CHANNEL: return String(Nodes.getESPEasyNOW_channel()); // FIXME TD-er: Must send intended channel and what to do when mesh is off? + case LabelType::ESPEASY_NOW_MQTT: return jsonBool(Nodes.getDistance() < 255); // FIXME TD-er: update this when definition of "distance" no longer reflects presence of connected MQTT broker + case LabelType::ESPEASY_NOW_DISTANCE: return String(Nodes.getDistance()); +#endif + + +#if FEATURE_ETHERNET + case LabelType::ETH_IP_ADDRESS: return formatIP(NetworkLocalIP()); + case LabelType::ETH_IP_SUBNET: return formatIP(NetworkSubnetMask()); + case LabelType::ETH_IP_ADDRESS_SUBNET: return strformat( + F("%s / %s"), + getValue(LabelType::ETH_IP_ADDRESS).c_str(), + getValue(LabelType::ETH_IP_SUBNET).c_str()); + case LabelType::ETH_IP_GATEWAY: return formatIP(NetworkGatewayIP()); + case LabelType::ETH_IP_DNS: return formatIP(NetworkDnsIP(0)); + case LabelType::ETH_MAC: return NetworkMacAddress().toString(); + case LabelType::ETH_DUPLEX: return EthLinkUp() ? (EthFullDuplex() ? F("Full Duplex") : F("Half Duplex")) : F("Link Down"); + case LabelType::ETH_SPEED: return EthLinkUp() ? getEthSpeed() : F("Link Down"); + case LabelType::ETH_STATE: return EthLinkUp() ? F("Link Up") : F("Link Down"); + case LabelType::ETH_SPEED_STATE: return EthLinkUp() ? getEthLinkSpeedState() : F("Link Down"); + case LabelType::ETH_CONNECTED: return ETHConnected() ? F("CONNECTED") : F("DISCONNECTED"); // 0=disconnected, 1=connected + case LabelType::ETH_CHIP: return toString(Settings.ETH_Phy_Type); +#endif // if FEATURE_ETHERNET +# if FEATURE_ETHERNET || defined(USES_ESPEASY_NOW) + case LabelType::ETH_WIFI_MODE: return toString(active_network_medium); +#endif + case LabelType::SUNRISE: return node_time.getSunriseTimeString(':'); + case LabelType::SUNSET: return node_time.getSunsetTimeString(':'); + case LabelType::SUNRISE_S: retval = node_time.sunRise.tm_hour * 3600 + node_time.sunRise.tm_min * 60 + node_time.sunRise.tm_sec; break; + case LabelType::SUNSET_S: retval = node_time.sunSet.tm_hour * 3600 + node_time.sunSet.tm_min * 60 + node_time.sunSet.tm_sec; break; + case LabelType::SUNRISE_M: retval = node_time.sunRise.tm_hour * 60 + node_time.sunRise.tm_min; break; + case LabelType::SUNSET_M: retval = node_time.sunSet.tm_hour * 60 + node_time.sunSet.tm_min; break; + case LabelType::ISNTP: return jsonBool(Settings.UseNTP()); + case LabelType::UPTIME_MS: return ull2String(getMicros64() / 1000); + case LabelType::TIMEZONE_OFFSET: retval = Settings.TimeZone; break; + case LabelType::LATITUDE: return toString(Settings.Latitude, 6); + case LabelType::LONGITUDE: return toString(Settings.Longitude, 6); + + case LabelType::MAX_LABEL: + break; + } + if (retval != INT_MAX) return String(retval); + return F("MissingString"); +} + +#if FEATURE_ETHERNET +String getEthSpeed() { + return strformat(F("%d [Mbps]"), EthLinkSpeed()); +} + +String getEthLinkSpeedState() { + if (EthLinkUp()) { + return strformat(F("%s %s %s"), + getValue(LabelType::ETH_STATE).c_str(), + getValue(LabelType::ETH_DUPLEX).c_str(), + getEthSpeed().c_str()); + } + return getValue(LabelType::ETH_STATE); +} + +#endif // if FEATURE_ETHERNET + +String getExtendedValue(LabelType::Enum label) { + switch (label) + { + case LabelType::UPTIME: + { + return minutesToDayHourMinute(getUptimeMinutes()); + } + + default: + break; + } + return EMPTY_STRING; +} + +String getFormNote(LabelType::Enum label) +{ + // Keep flash string till the end of the function, to reduce build size + // Otherwise lots of calls to String() constructor are included. + const __FlashStringHelper *flash_str = F(""); + + switch (label) { +#ifndef MINIMAL_OTA + case LabelType::CONNECT_HIDDEN_SSID: + flash_str = F("Must be checked to connect to a hidden SSID"); + break; +#ifdef ESP32 + case LabelType::WIFI_PASSIVE_SCAN: + flash_str = F("Passive scan listens for WiFi beacons, Active scan probes for AP. Passive scan is typically faster."); + break; +#endif // ifdef ESP32 + case LabelType::HIDDEN_SSID_SLOW_CONNECT: + flash_str = F("Required for some AP brands like Mikrotik to connect to hidden SSID"); + break; +#if FEATURE_USE_IPV6 + case LabelType::ENABLE_IPV6: + flash_str = F("Toggling IPv6 requires reboot"); + break; +#endif // if FEATURE_USE_IPV6 +#ifndef NO_HTTP_UPDATER + case LabelType::ALLOW_OTA_UNLIMITED: + flash_str = F("When enabled, OTA updating can overwrite the filesystem and settings!
Requires reboot to activate"); + break; +#endif // ifndef NO_HTTP_UPDATER +#if FEATURE_RULES_EASY_COLOR_CODE + case LabelType::DISABLE_RULES_AUTOCOMPLETE: + flash_str = F("Also disables Rules syntax highlighting!"); + break; +#endif // if FEATURE_RULES_EASY_COLOR_CODE + + case LabelType::FORCE_WIFI_NOSLEEP: + flash_str = F("Change WiFi sleep settings requires reboot to activate"); + break; + + case LabelType::CPU_ECO_MODE: + flash_str = F("Node may miss receiving packets with Eco mode enabled"); + break; + + case LabelType::WIFI_NR_EXTRA_SCANS: + flash_str = F("Number of extra times to scan all channels to have higher chance of finding the desired AP"); + break; +#ifndef ESP32 + case LabelType::WAIT_WIFI_CONNECT: + flash_str = F("Wait for 1000 msec right after connecting to WiFi.
May improve success on some APs like Fritz!Box"); + break; +#endif + +#endif + +#if FEATURE_SET_WIFI_TX_PWR + case LabelType::WIFI_TX_MAX_PWR: + case LabelType::WIFI_SENS_MARGIN: + { + float maxTXpwr; + float sensitivity = GetRSSIthreshold(maxTXpwr); + if (LabelType::WIFI_TX_MAX_PWR == label) { + return strformat( + F("Current max: %.2f dBm"), maxTXpwr); + } + return strformat( + F("Adjust TX power to target the AP with (sensitivity + margin) dBm signal strength. Current sensitivity: %.2f dBm"), + sensitivity); + } +#endif // if FEATURE_SET_WIFI_TX_PWR + + default: + return EMPTY_STRING; + } + + return flash_str; +} + + +String getFormUnit(LabelType::Enum label) +{ + const __FlashStringHelper *flash_str = F(""); + + switch (label) { +#if FEATURE_SET_WIFI_TX_PWR + case LabelType::WIFI_TX_MAX_PWR: + case LabelType::WIFI_CUR_TX_PWR: + case LabelType::WIFI_RSSI: + flash_str = F("dBm"); + break; + case LabelType::WIFI_SENS_MARGIN: + flash_str = F("dB"); + break; +#endif + case LabelType::TIME_WANDER: + flash_str = F("ppm"); + break; +#ifdef ESP32 + case LabelType::HEAP_SIZE: + case LabelType::HEAP_MIN_FREE: + #ifdef BOARD_HAS_PSRAM + case LabelType::PSRAM_SIZE: + case LabelType::PSRAM_FREE: + case LabelType::PSRAM_MIN_FREE: + case LabelType::PSRAM_MAX_FREE_BLOCK: + #endif // BOARD_HAS_PSRAM +#endif // ifdef ESP32 + case LabelType::FREE_MEM: + case LabelType::FREE_STACK: +#ifdef USE_SECOND_HEAP + case LabelType::FREE_HEAP_IRAM: +#endif +#if defined(CORE_POST_2_5_0) || defined(ESP32) + #ifndef LIMIT_BUILD_SIZE + case LabelType::HEAP_MAX_FREE_BLOCK: + #endif +#endif // if defined(CORE_POST_2_5_0) || defined(ESP32) + + flash_str = F("byte"); + break; + case LabelType::FLASH_CHIP_REAL_SIZE: + case LabelType::FLASH_IDE_SIZE: + flash_str = F("kB"); + break; +/* + case LabelType::UPTIME: + flash_str = F("min"); + break; +*/ + case LabelType::LOAD_PCT: +#if defined(CORE_POST_2_5_0) + #ifndef LIMIT_BUILD_SIZE + case LabelType::HEAP_FRAGMENTATION: + #endif +#endif // if defined(CORE_POST_2_5_0) + + flash_str = F("%"); + break; + + case LabelType::ESP_CHIP_FREQ: +#ifdef ESP32 + case LabelType::ESP_CHIP_XTAL_FREQ: + case LabelType::ESP_CHIP_APB_FREQ: +#endif + case LabelType::FLASH_CHIP_SPEED: + case LabelType::FLASH_IDE_SPEED: + flash_str = F("MHz"); + break; +#if FEATURE_INTERNAL_TEMPERATURE + case LabelType::INTERNAL_TEMPERATURE: + flash_str = F("°C"); + break; +#endif // if FEATURE_INTERNAL_TEMPERATURE + + + + default: + return EMPTY_STRING; + } + + return flash_str; } \ No newline at end of file diff --git a/src/src/Helpers/StringProvider.h b/src/src/Helpers/StringProvider.h index f15d3bc819..af2ebbb9c7 100644 --- a/src/src/Helpers/StringProvider.h +++ b/src/src/Helpers/StringProvider.h @@ -1,271 +1,282 @@ -#ifndef STRING_PROVIDER_TYPES_H -#define STRING_PROVIDER_TYPES_H - -#include "../../ESPEasy_common.h" - -struct LabelType { - enum Enum : uint8_t { - UNIT_NR, - #if FEATURE_ZEROFILLED_UNITNUMBER - UNIT_NR_0, - #endif // FEATURE_ZEROFILLED_UNITNUMBER - UNIT_NAME, - HOST_NAME, - - LOCAL_TIME, - TIME_SOURCE, - TIME_WANDER, - #if FEATURE_EXT_RTC - EXT_RTC_UTC_TIME, - #endif - UPTIME, - LOAD_PCT, // 15.10 - LOOP_COUNT, // 400 - CPU_ECO_MODE, // true -#if FEATURE_SET_WIFI_TX_PWR - WIFI_TX_MAX_PWR, // Unit: 0.25 dBm, 0 = use default (do not set) - WIFI_CUR_TX_PWR, // Unit dBm of current WiFi TX power. - WIFI_SENS_MARGIN, // Margin in dB on top of sensitivity - WIFI_SEND_AT_MAX_TX_PWR, -#endif - WIFI_NR_EXTRA_SCANS, - WIFI_USE_LAST_CONN_FROM_RTC, - - FREE_MEM, // 9876 - FREE_STACK, // 3456 -#ifdef USE_SECOND_HEAP - FREE_HEAP_IRAM, -#endif -#if defined(CORE_POST_2_5_0) || defined(ESP32) - #ifndef LIMIT_BUILD_SIZE - HEAP_MAX_FREE_BLOCK, // 7654 - #endif -#endif // if defined(CORE_POST_2_5_0) || defined(ESP32) -#if defined(CORE_POST_2_5_0) - #ifndef LIMIT_BUILD_SIZE - HEAP_FRAGMENTATION, // 12 - #endif -#endif // if defined(CORE_POST_2_5_0) - -#ifdef ESP32 - HEAP_SIZE, - HEAP_MIN_FREE, - #ifdef BOARD_HAS_PSRAM - PSRAM_SIZE, - PSRAM_FREE, - PSRAM_MIN_FREE, - PSRAM_MAX_FREE_BLOCK, - #endif // BOARD_HAS_PSRAM -#endif // ifdef ESP32 - - JSON_BOOL_QUOTES, -#if FEATURE_TIMING_STATS - ENABLE_TIMING_STATISTICS, -#endif - ENABLE_RULES_CACHING, - ENABLE_SERIAL_PORT_CONSOLE, - CONSOLE_SERIAL_PORT, -#if USES_ESPEASY_CONSOLE_FALLBACK_PORT - CONSOLE_FALLBACK_TO_SERIAL0, - CONSOLE_FALLBACK_PORT, -#endif -// ENABLE_RULES_EVENT_REORDER, // TD-er: Disabled for now - TASKVALUESET_ALL_PLUGINS, - ALLOW_OTA_UNLIMITED, -#if FEATURE_CLEAR_I2C_STUCK - ENABLE_CLEAR_HUNG_I2C_BUS, -#endif - #if FEATURE_I2C_DEVICE_CHECK - ENABLE_I2C_DEVICE_CHECK, - #endif // if FEATURE_I2C_DEVICE_CHECK -#ifndef BUILD_NO_RAM_TRACKER - ENABLE_RAM_TRACKING, -#endif -#if FEATURE_AUTO_DARK_MODE - ENABLE_AUTO_DARK_MODE, -#endif -#if FEATURE_RULES_EASY_COLOR_CODE - DISABLE_RULES_AUTOCOMPLETE, -#endif // if FEATURE_RULES_EASY_COLOR_CODE -#if FEATURE_TARSTREAM_SUPPORT - DISABLE_SAVE_CONFIG_AS_TAR, -#endif // if FEATURE_TARSTREAM_SUPPORT - #if FEATURE_TASKVALUE_UNIT_OF_MEASURE - SHOW_UOM_ON_DEVICES_PAGE, - #endif // if FEATURE_TASKVALUE_UNIT_OF_MEASURE - - BOOT_TYPE, // Cold boot - BOOT_COUNT, // 0 - RESET_REASON, // Software/System restart - DEEP_SLEEP_ALTERNATIVE_CALL, - LAST_TASK_BEFORE_REBOOT, // Last scheduled task. - SW_WD_COUNT, - - WIFI_CONNECTION, // 802.11G - WIFI_RSSI, // -67 - IP_CONFIG, // DHCP - IP_CONFIG_STATIC, - IP_CONFIG_DYNAMIC, - IP_ADDRESS, // 192.168.1.123 - IP_SUBNET, // 255.255.255.0 - IP_ADDRESS_SUBNET, // 192.168.1.123 / 255.255.255.0 - GATEWAY, // 192.168.1.1 -#if FEATURE_USE_IPV6 - IP6_LOCAL, - IP6_GLOBAL, -// IP6_ALL_ADDRESSES, -// IP6_ADDRESS_CDIR, -// IP6_GATEWAY, -#endif - CLIENT_IP, // 192.168.1.67 - #if FEATURE_MDNS - M_DNS, // breadboard.local - #endif // if FEATURE_MDNS - DNS, // 192.168.1.1 / (IP unset) - DNS_1, - DNS_2, - ALLOWED_IP_RANGE, // 192.168.1.0 - 192.168.1.255 - STA_MAC, // EC:FA:BC:0E:AE:5B - AP_MAC, // EE:FA:BC:0E:AE:5B - SSID, // mynetwork - BSSID, - CHANNEL, // 1 - ENCRYPTION_TYPE_STA, // WPA2 - CONNECTED, // 1h16m - CONNECTED_MSEC, // 1h16m - LAST_DISCONNECT_REASON, // 200 - LAST_DISC_REASON_STR, // Beacon timeout - NUMBER_RECONNECTS, // 5 - WIFI_STORED_SSID1, - WIFI_STORED_SSID2, - - FORCE_WIFI_BG, - RESTART_WIFI_LOST_CONN, - FORCE_WIFI_NOSLEEP, - PERIODICAL_GRAT_ARP, - CONNECTION_FAIL_THRESH, -#ifndef ESP32 - WAIT_WIFI_CONNECT, -#endif - HIDDEN_SSID_SLOW_CONNECT, - CONNECT_HIDDEN_SSID, -#ifdef ESP32 - WIFI_PASSIVE_SCAN, -#endif - SDK_WIFI_AUTORECONNECT, -#if FEATURE_USE_IPV6 - ENABLE_IPV6, -#endif - - BUILD_DESC, - GIT_BUILD, - SYSTEM_LIBRARIES, -#ifdef ESP32 - ESP_IDF_SDK_VERSION, -#endif - PLUGIN_COUNT, - PLUGIN_DESCRIPTION, - BUILD_TIME, - BINARY_FILENAME, - BUILD_PLATFORM, - GIT_HEAD, - #ifdef CONFIGURATION_CODE - CONFIGURATION_CODE_LBL, - #endif // ifdef CONFIGURATION_CODE - - - I2C_BUS_STATE, - I2C_BUS_CLEARED_COUNT, - - SYSLOG_LOG_LEVEL, - SERIAL_LOG_LEVEL, - WEB_LOG_LEVEL, -#if FEATURE_SD - SD_LOG_LEVEL, -#endif // if FEATURE_SD - - ESP_CHIP_ID, - ESP_CHIP_FREQ, -#ifdef ESP32 - ESP_CHIP_XTAL_FREQ, - ESP_CHIP_APB_FREQ, -#endif - ESP_CHIP_MODEL, - ESP_CHIP_REVISION, - ESP_CHIP_CORES, - BOARD_NAME, - - FLASH_CHIP_ID, - FLASH_CHIP_VENDOR, - FLASH_CHIP_MODEL, - FLASH_CHIP_REAL_SIZE, - FLASH_CHIP_SPEED, - FLASH_IDE_SIZE, - FLASH_IDE_SPEED, - FLASH_IDE_MODE, - FLASH_WRITE_COUNT, - SKETCH_SIZE, - SKETCH_FREE, - FS_SIZE, - FS_FREE, - MAX_OTA_SKETCH_SIZE, - OTA_2STEP, - OTA_POSSIBLE, - #if FEATURE_INTERNAL_TEMPERATURE - INTERNAL_TEMPERATURE, - #endif // if FEATURE_INTERNAL_TEMPERATURE -#if FEATURE_ETHERNET - ETH_IP_ADDRESS, - ETH_IP_SUBNET, - ETH_IP_ADDRESS_SUBNET, - ETH_IP_GATEWAY, - ETH_IP_DNS, -#if FEATURE_USE_IPV6 - ETH_IP6_LOCAL, -#endif - ETH_MAC, - ETH_DUPLEX, - ETH_SPEED, - ETH_STATE, - ETH_SPEED_STATE, - ETH_CONNECTED, - ETH_CHIP, -#endif // if FEATURE_ETHERNET -# if FEATURE_ETHERNET || defined(USES_ESPEASY_NOW) - ETH_WIFI_MODE, -#endif - SUNRISE, - SUNSET, - ISNTP, - UPTIME_MS, - TIMEZONE_OFFSET, - LATITUDE, - LONGITUDE, - SUNRISE_S, - SUNSET_S, - SUNRISE_M, - SUNSET_M, - - - MAX_LABEL // Keep as last - }; -}; - - -#if FEATURE_ETHERNET -String getEthSpeed(); - -String getEthLinkSpeedState(); -#endif // if FEATURE_ETHERNET - -String getInternalLabel(LabelType::Enum label, - char replaceSpace = '_'); -const __FlashStringHelper * getLabel(LabelType::Enum label); -String getValue(LabelType::Enum label); -String getExtendedValue(LabelType::Enum label); - -String getFormNote(LabelType::Enum label); -String getFormUnit(LabelType::Enum label); - - -#endif // STRING_PROVIDER_TYPES_H +#ifndef STRING_PROVIDER_TYPES_H +#define STRING_PROVIDER_TYPES_H + +#include "../../ESPEasy_common.h" + +struct LabelType { + enum Enum : uint8_t { + UNIT_NR, + #if FEATURE_ZEROFILLED_UNITNUMBER + UNIT_NR_0, + #endif // FEATURE_ZEROFILLED_UNITNUMBER + UNIT_NAME, + HOST_NAME, + + LOCAL_TIME, + TIME_SOURCE, + TIME_WANDER, + #if FEATURE_EXT_RTC + EXT_RTC_UTC_TIME, + #endif + UPTIME, + LOAD_PCT, // 15.10 + LOOP_COUNT, // 400 + CPU_ECO_MODE, // true +#if FEATURE_SET_WIFI_TX_PWR + WIFI_TX_MAX_PWR, // Unit: 0.25 dBm, 0 = use default (do not set) + WIFI_CUR_TX_PWR, // Unit dBm of current WiFi TX power. + WIFI_SENS_MARGIN, // Margin in dB on top of sensitivity + WIFI_SEND_AT_MAX_TX_PWR, +#endif + WIFI_NR_EXTRA_SCANS, + WIFI_USE_LAST_CONN_FROM_RTC, + + FREE_MEM, // 9876 + FREE_STACK, // 3456 +#ifdef USE_SECOND_HEAP + FREE_HEAP_IRAM, +#endif +#if defined(CORE_POST_2_5_0) || defined(ESP32) + #ifndef LIMIT_BUILD_SIZE + HEAP_MAX_FREE_BLOCK, // 7654 + #endif +#endif // if defined(CORE_POST_2_5_0) || defined(ESP32) +#if defined(CORE_POST_2_5_0) + #ifndef LIMIT_BUILD_SIZE + HEAP_FRAGMENTATION, // 12 + #endif +#endif // if defined(CORE_POST_2_5_0) + +#ifdef ESP32 + HEAP_SIZE, + HEAP_MIN_FREE, + #ifdef BOARD_HAS_PSRAM + PSRAM_SIZE, + PSRAM_FREE, + PSRAM_MIN_FREE, + PSRAM_MAX_FREE_BLOCK, + #endif // BOARD_HAS_PSRAM +#endif // ifdef ESP32 + + JSON_BOOL_QUOTES, +#if FEATURE_TIMING_STATS + ENABLE_TIMING_STATISTICS, +#endif + ENABLE_RULES_CACHING, + ENABLE_SERIAL_PORT_CONSOLE, + CONSOLE_SERIAL_PORT, +#if USES_ESPEASY_CONSOLE_FALLBACK_PORT + CONSOLE_FALLBACK_TO_SERIAL0, + CONSOLE_FALLBACK_PORT, +#endif +// ENABLE_RULES_EVENT_REORDER, // TD-er: Disabled for now + TASKVALUESET_ALL_PLUGINS, + ALLOW_OTA_UNLIMITED, +#if FEATURE_CLEAR_I2C_STUCK + ENABLE_CLEAR_HUNG_I2C_BUS, +#endif + #if FEATURE_I2C_DEVICE_CHECK + ENABLE_I2C_DEVICE_CHECK, + #endif // if FEATURE_I2C_DEVICE_CHECK +#ifndef BUILD_NO_RAM_TRACKER + ENABLE_RAM_TRACKING, +#endif +#if FEATURE_AUTO_DARK_MODE + ENABLE_AUTO_DARK_MODE, +#endif +#if FEATURE_RULES_EASY_COLOR_CODE + DISABLE_RULES_AUTOCOMPLETE, +#endif // if FEATURE_RULES_EASY_COLOR_CODE +#if FEATURE_TARSTREAM_SUPPORT + DISABLE_SAVE_CONFIG_AS_TAR, +#endif // if FEATURE_TARSTREAM_SUPPORT + #if FEATURE_TASKVALUE_UNIT_OF_MEASURE + SHOW_UOM_ON_DEVICES_PAGE, + #endif // if FEATURE_TASKVALUE_UNIT_OF_MEASURE + + BOOT_TYPE, // Cold boot + BOOT_COUNT, // 0 + RESET_REASON, // Software/System restart + DEEP_SLEEP_ALTERNATIVE_CALL, + LAST_TASK_BEFORE_REBOOT, // Last scheduled task. + SW_WD_COUNT, + + WIFI_CONNECTION, // 802.11G + WIFI_RSSI, // -67 + IP_CONFIG, // DHCP + IP_CONFIG_STATIC, + IP_CONFIG_DYNAMIC, + IP_ADDRESS, // 192.168.1.123 + IP_SUBNET, // 255.255.255.0 + IP_ADDRESS_SUBNET, // 192.168.1.123 / 255.255.255.0 + GATEWAY, // 192.168.1.1 +#if FEATURE_USE_IPV6 + IP6_LOCAL, + IP6_GLOBAL, +// IP6_ALL_ADDRESSES, +// IP6_ADDRESS_CDIR, +// IP6_GATEWAY, +#endif + CLIENT_IP, // 192.168.1.67 + #if FEATURE_MDNS + M_DNS, // breadboard.local + #endif // if FEATURE_MDNS + DNS, // 192.168.1.1 / (IP unset) + DNS_1, + DNS_2, + ALLOWED_IP_RANGE, // 192.168.1.0 - 192.168.1.255 + STA_MAC, // EC:FA:BC:0E:AE:5B + AP_MAC, // EE:FA:BC:0E:AE:5B + SSID, // mynetwork + BSSID, + CHANNEL, // 1 + ENCRYPTION_TYPE_STA, // WPA2 + CONNECTED, // 1h16m + CONNECTED_MSEC, // 1h16m + LAST_DISCONNECT_REASON, // 200 + LAST_DISC_REASON_STR, // Beacon timeout + NUMBER_RECONNECTS, // 5 + WIFI_STORED_SSID1, + WIFI_STORED_SSID2, + + FORCE_WIFI_BG, + RESTART_WIFI_LOST_CONN, + FORCE_WIFI_NOSLEEP, + PERIODICAL_GRAT_ARP, + CONNECTION_FAIL_THRESH, +#ifndef ESP32 + WAIT_WIFI_CONNECT, +#endif + HIDDEN_SSID_SLOW_CONNECT, + CONNECT_HIDDEN_SSID, +#ifdef ESP32 + WIFI_PASSIVE_SCAN, +#endif + SDK_WIFI_AUTORECONNECT, +#if FEATURE_USE_IPV6 + ENABLE_IPV6, +#endif + + BUILD_DESC, + GIT_BUILD, + SYSTEM_LIBRARIES, +#ifdef ESP32 + ESP_IDF_SDK_VERSION, +#endif + PLUGIN_COUNT, + PLUGIN_DESCRIPTION, + BUILD_TIME, + BINARY_FILENAME, + BUILD_PLATFORM, + GIT_HEAD, + #ifdef CONFIGURATION_CODE + CONFIGURATION_CODE_LBL, + #endif // ifdef CONFIGURATION_CODE + + + I2C_BUS_STATE, + I2C_BUS_CLEARED_COUNT, + + SYSLOG_LOG_LEVEL, + SERIAL_LOG_LEVEL, + WEB_LOG_LEVEL, +#if FEATURE_SD + SD_LOG_LEVEL, +#endif // if FEATURE_SD + + ESP_CHIP_ID, + ESP_CHIP_FREQ, +#ifdef ESP32 + ESP_CHIP_XTAL_FREQ, + ESP_CHIP_APB_FREQ, +#endif + ESP_CHIP_MODEL, + ESP_CHIP_REVISION, + ESP_CHIP_CORES, + BOARD_NAME, + + FLASH_CHIP_ID, + FLASH_CHIP_VENDOR, + FLASH_CHIP_MODEL, + FLASH_CHIP_REAL_SIZE, + FLASH_CHIP_SPEED, + FLASH_IDE_SIZE, + FLASH_IDE_SPEED, + FLASH_IDE_MODE, + FLASH_WRITE_COUNT, + SKETCH_SIZE, + SKETCH_FREE, + FS_SIZE, + FS_FREE, + MAX_OTA_SKETCH_SIZE, + OTA_2STEP, + OTA_POSSIBLE, + +#ifdef USES_ESPEASY_NOW + ESPEASY_NOW_ENABLED, + TEMP_DISABLE_ESPEASY_NOW, + ESPEASY_NOW_FORCED_CHANNEL, + ESPEASY_NOW_CHANNEL, + ESPEASY_NOW_MQTT, + ESPEASY_NOW_DISTANCE, +#endif + +#if FEATURE_INTERNAL_TEMPERATURE + INTERNAL_TEMPERATURE, +#endif // if FEATURE_INTERNAL_TEMPERATURE + +#if FEATURE_ETHERNET + ETH_IP_ADDRESS, + ETH_IP_SUBNET, + ETH_IP_ADDRESS_SUBNET, + ETH_IP_GATEWAY, + ETH_IP_DNS, +#if FEATURE_USE_IPV6 + ETH_IP6_LOCAL, +#endif + ETH_MAC, + ETH_DUPLEX, + ETH_SPEED, + ETH_STATE, + ETH_SPEED_STATE, + ETH_CONNECTED, + ETH_CHIP, +#endif // if FEATURE_ETHERNET +# if FEATURE_ETHERNET || defined(USES_ESPEASY_NOW) + ETH_WIFI_MODE, +#endif + SUNRISE, + SUNSET, + ISNTP, + UPTIME_MS, + TIMEZONE_OFFSET, + LATITUDE, + LONGITUDE, + SUNRISE_S, + SUNSET_S, + SUNRISE_M, + SUNSET_M, + + + MAX_LABEL // Keep as last + }; +}; + + +#if FEATURE_ETHERNET +String getEthSpeed(); + +String getEthLinkSpeedState(); +#endif // if FEATURE_ETHERNET + +String getInternalLabel(LabelType::Enum label, + char replaceSpace = '_'); +const __FlashStringHelper * getLabel(LabelType::Enum label); +String getValue(LabelType::Enum label); +String getExtendedValue(LabelType::Enum label); + +String getFormNote(LabelType::Enum label); +String getFormUnit(LabelType::Enum label); + + +#endif // STRING_PROVIDER_TYPES_H diff --git a/src/src/Helpers/SystemVariables.cpp b/src/src/Helpers/SystemVariables.cpp index 4c33879106..8ac2788992 100644 --- a/src/src/Helpers/SystemVariables.cpp +++ b/src/src/Helpers/SystemVariables.cpp @@ -1,682 +1,699 @@ -#include "../Helpers/SystemVariables.h" - - -#include "../../ESPEasy_common.h" - -#include "../../ESPEasy-Globals.h" - -#include "../CustomBuild/CompiletimeDefines.h" - -#include "../DataStructs/TimingStats.h" - -#include "../ESPEasyCore/ESPEasy_Log.h" -#include "../ESPEasyCore/ESPEasyNetwork.h" - -#include "../Globals/CRCValues.h" -#include "../Globals/ESPEasy_time.h" -#include "../Globals/ESPEasyWiFiEvent.h" -#if FEATURE_MQTT -# include "../Globals/MQTT.h" -#endif // if FEATURE_MQTT -#include "../Globals/NetworkState.h" -#include "../Globals/RulesCalculate.h" -#include "../Globals/RuntimeData.h" -#include "../Globals/Settings.h" -#include "../Globals/Statistics.h" - -#include "../Helpers/Convert.h" -#include "../Helpers/Hardware_device_info.h" -#include "../Helpers/Misc.h" -#include "../Helpers/Numerical.h" -#include "../Helpers/StringConverter.h" -#include "../Helpers/StringProvider.h" - - -#if defined(ESP8266) - # include -#endif // if defined(ESP8266) -#if defined(ESP32) - # include -#endif // if defined(ESP32) - - -String getReplacementString(const String& format, const String& s) { - int startpos = s.indexOf(format); - int endpos = s.indexOf('%', startpos + 1); - if (endpos == -1) { - addLog(LOG_LEVEL_ERROR, concat(F("SunTime syntax error: "), format)); - return format; - } - String R = s.substring(startpos, endpos + 1); - - -#ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - addLogMove(LOG_LEVEL_DEBUG, strformat( - F("ReplacementString SunTime: %s offset: %d"), - R.c_str(), - ESPEasy_time::getSecOffset(R))); - } -#endif // ifndef BUILD_NO_DEBUG - return R; -} - -void replSunRiseTimeString(const String& format, String& s, boolean useURLencode) { - const String R(getReplacementString(format, s)); - - repl(R, node_time.getSunriseTimeString(':', ESPEasy_time::getSecOffset(R)), s, useURLencode); -} - -void replSunSetTimeString(const String& format, String& s, boolean useURLencode) { - const String R(getReplacementString(format, s)); - - repl(R, node_time.getSunsetTimeString(':', ESPEasy_time::getSecOffset(R)), s, useURLencode); -} - -String timeReplacement_leadZero(int value) -{ - char valueString[5] = { 0 }; - - sprintf_P(valueString, PSTR("%02d"), value); - return valueString; -} - -// FIXME TD-er: Try to match these with StringProvider::getValue -LabelType::Enum SystemVariables2LabelType(SystemVariables::Enum enumval) { - LabelType::Enum label = LabelType::MAX_LABEL; - - switch (enumval) - { - case SystemVariables::IP: label = LabelType::IP_ADDRESS; break; -#if FEATURE_USE_IPV6 - case SystemVariables::IP6_LOCAL: label = LabelType::IP6_LOCAL; break; -#endif - case SystemVariables::SUBNET: label = LabelType::IP_SUBNET; break; - case SystemVariables::DNS: label = LabelType::DNS; break; - case SystemVariables::DNS_1: label = LabelType::DNS_1; break; - case SystemVariables::DNS_2: label = LabelType::DNS_2; break; - case SystemVariables::GATEWAY: label = LabelType::GATEWAY; break; - case SystemVariables::CLIENTIP: label = LabelType::CLIENT_IP; break; - #if FEATURE_INTERNAL_TEMPERATURE - case SystemVariables::INTERNAL_TEMPERATURE: label = LabelType::INTERNAL_TEMPERATURE; break; - #endif // if FEATURE_INTERNAL_TEMPERATURE - - #if FEATURE_ETHERNET - - case SystemVariables::ETHWIFIMODE: label = LabelType::ETH_WIFI_MODE; break; // 0=WIFI, 1=ETH - case SystemVariables::ETHCONNECTED: label = LabelType::ETH_CONNECTED; break; // 0=disconnected, 1=connected - case SystemVariables::ETHDUPLEX: label = LabelType::ETH_DUPLEX; break; - case SystemVariables::ETHSPEED: label = LabelType::ETH_SPEED; break; - case SystemVariables::ETHSTATE: label = LabelType::ETH_STATE; break; - case SystemVariables::ETHSPEEDSTATE: label = LabelType::ETH_SPEED_STATE; break; - #endif // if FEATURE_ETHERNET - case SystemVariables::LCLTIME: label = LabelType::LOCAL_TIME; break; - case SystemVariables::MAC: label = LabelType::STA_MAC; break; - case SystemVariables::RSSI: label = LabelType::WIFI_RSSI; break; - case SystemVariables::SUNRISE_S: label = LabelType::SUNRISE_S; break; - case SystemVariables::SUNSET_S: label = LabelType::SUNSET_S; break; - case SystemVariables::SUNRISE_M: label = LabelType::SUNRISE_M; break; - case SystemVariables::SUNSET_M: label = LabelType::SUNSET_M; break; - case SystemVariables::SYSBUILD_DESCR: label = LabelType::BUILD_DESC; break; - case SystemVariables::SYSBUILD_FILENAME: label = LabelType::BINARY_FILENAME; break; - case SystemVariables::SYSBUILD_GIT: label = LabelType::GIT_BUILD; break; - case SystemVariables::SYSSTACK: label = LabelType::FREE_STACK; break; - case SystemVariables::UNIT_sysvar: label = LabelType::UNIT_NR; break; - #if FEATURE_ZEROFILLED_UNITNUMBER - case SystemVariables::UNIT_0_sysvar: label = LabelType::UNIT_NR_0; break; - #endif // FEATURE_ZEROFILLED_UNITNUMBER - case SystemVariables::FLASH_FREQ: label = LabelType::FLASH_CHIP_SPEED; break; - case SystemVariables::FLASH_SIZE: label = LabelType::FLASH_CHIP_REAL_SIZE; break; - case SystemVariables::FLASH_CHIP_VENDOR: label = LabelType::FLASH_CHIP_VENDOR; break; - case SystemVariables::FLASH_CHIP_MODEL: label = LabelType::FLASH_CHIP_MODEL; break; - case SystemVariables::FS_SIZE: label = LabelType::FS_SIZE; break; - case SystemVariables::FS_FREE: label = LabelType::FS_FREE; break; - - case SystemVariables::ESP_CHIP_ID: label = LabelType::ESP_CHIP_ID; break; - case SystemVariables::ESP_CHIP_FREQ: label = LabelType::ESP_CHIP_FREQ; break; - case SystemVariables::ESP_CHIP_MODEL: label = LabelType::ESP_CHIP_MODEL; break; - case SystemVariables::ESP_CHIP_REVISION: label = LabelType::ESP_CHIP_REVISION; break; - case SystemVariables::ESP_CHIP_CORES: label = LabelType::ESP_CHIP_CORES; break; - case SystemVariables::BOARD_NAME: label = LabelType::BOARD_NAME; break; - - default: - // No matching LabelType yet. - break; - } - return label; -} - -String SystemVariables::getSystemVariable(SystemVariables::Enum enumval) { - const LabelType::Enum label = SystemVariables2LabelType(enumval); - - if (LabelType::MAX_LABEL != label) { - return getValue(label); - } - constexpr int INT_NOT_SET = std::numeric_limits::min(); - - int intvalue = INT_NOT_SET; - - switch (enumval) - { - case BOOT_CAUSE: intvalue = lastBootCause; break; // Integer value to be used in rules - case BSSID: return (WiFiEventData.WiFiDisconnected()) ? MAC_address().toString() : WiFi.BSSIDstr(); - case CR: return String('\r'); - case IP4: intvalue = static_cast(NetworkLocalIP()[3]); break; // 4th IP octet - case ISVAR_DOUBLE: intvalue = - #if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE - 1; - #else - 0; - #endif - break; - case ISLIMITED_BUILD: intvalue = - #ifdef LIMIT_BUILD_SIZE - 1; - #else - 0; - #endif - break; - case ISMQTT: intvalue = - #if FEATURE_MQTT - MQTTclient_connected ? 1 : - #endif // if FEATURE_MQTT - 0; break; - - case ISMQTTIMP: intvalue = - #ifdef USES_P037 - P037_MQTTImport_connected ? 1 : - #endif // ifdef USES_P037 - 0; break; - - case ISNTP: intvalue = statusNTPInitialized ? 1 : 0; break; - case ISWIFI: intvalue = WiFiEventData.wifiStatus; break; // 0=disconnected, 1=connected, 2=got ip, 4=services - // initialized - case LCLTIME_AM: return node_time.getDateTimeString_ampm('-', ':', ' '); - case LF: return String('\n'); - case MAC_INT: intvalue = getChipId(); break; // Last 24 bit of MAC address as integer, to be used in rules. - case SPACE: return String(' '); - case SSID: return (WiFiEventData.WiFiDisconnected()) ? String(F("--")) : WiFi.SSID(); - case SYSBUILD_DATE: return get_build_date(); - case SYSBUILD_TIME: return get_build_time(); - case SYSDAY: intvalue = node_time.day(); break; - case SYSDAY_0: return timeReplacement_leadZero(node_time.day()); - case SYSHEAP: intvalue = ESP.getFreeHeap(); break; - case SYSHOUR: intvalue = node_time.hour(); break; - case SYSHOUR_0: return timeReplacement_leadZero(node_time.hour()); - case SYSLOAD: return String(getCPUload(), 2); - case SYSMIN: intvalue = node_time.minute(); break; - case SYSMIN_0: return timeReplacement_leadZero(node_time.minute()); - case SYSMONTH: intvalue = node_time.month(); break; - case SYSMONTH_S: return node_time.month_str(); - case SYSNAME: return Settings.getHostname(); - case SYSSEC: intvalue = node_time.second(); break; - case SYSSEC_0: return timeReplacement_leadZero(node_time.second()); - case SYSSEC_D: intvalue = ((node_time.hour() * 60) + node_time.minute()) * 60 + node_time.second(); break; - case SYSTIME: return node_time.getTimeString(':'); - case SYSTIME_AM: return node_time.getTimeString_ampm(':'); - case SYSTIME_AM_0: return node_time.getTimeString_ampm(':', true, '0'); - case SYSTIME_AM_SP: return node_time.getTimeString_ampm(':', true, ' '); - case SYSTM_HM: return node_time.getTimeString(':', false); - case SYSTM_HM_0: return node_time.getTimeString(':', false, '0'); - case SYSTM_HM_SP: return node_time.getTimeString(':', false, ' '); - case SYSTM_HM_AM: return node_time.getTimeString_ampm(':', false); - case SYSTM_HM_AM_0: return node_time.getTimeString_ampm(':', false, '0'); - case SYSTM_HM_AM_SP: return node_time.getTimeString_ampm(':', false, ' '); - case SYSTZOFFSET: return node_time.getTimeZoneOffsetString(); - #ifndef LIMIT_BUILD_SIZE - case SYSTZOFFSET_S: intvalue = static_cast(static_cast(node_time.getLocalUnixTime()) - static_cast(node_time.getUnixTime())); break; - #endif // ifndef LIMIT_BUILD_SIZE - case SYSWEEKDAY: intvalue = node_time.weekday(); break; - case SYSWEEKDAY_S: return node_time.weekday_str(); - case SYSYEAR_0: - case SYSYEAR: intvalue = node_time.year(); break; - case SYSYEARS: return timeReplacement_leadZero(node_time.year() % 100); - case SYSMONTH_0: return timeReplacement_leadZero(node_time.month()); - case S_CR: return F("\\r"); - case S_LF: return F("\\n"); - case UNIXDAY: intvalue = node_time.getUnixTime() / 86400; break; - case UNIXDAY_SEC: intvalue = node_time.getUnixTime() % 86400; break; - case UNIXTIME: return String(node_time.getUnixTime()); - #ifndef LIMIT_BUILD_SIZE - case UNIXTIME_LCL: return String(node_time.getLocalUnixTime()); - #endif // ifndef LIMIT_BUILD_SIZE - case UPTIME: intvalue = getUptimeMinutes(); break; - case UPTIME_MS: return ull2String(getMicros64() / 1000); - #if FEATURE_ADC_VCC - case VCC: return String(vcc); - #else // if FEATURE_ADC_VCC - case VCC: intvalue = -1; break; - #endif // if FEATURE_ADC_VCC - case WI_CH: intvalue = (WiFiEventData.WiFiDisconnected()) ? 0 : WiFi.channel(); break; - - default: - // Already handled above. - return EMPTY_STRING; - } - - if (intvalue != INT_NOT_SET) { - return String(intvalue); - } - - return EMPTY_STRING; -} - -/* -#define SMART_REPL_T(T, S) \ - while (s.indexOf(T) != -1) { (S((T), s, useURLencode)); } -*/ - -#define SMART_REPL_T(T, S) \ - const String T_str(T); int __pos__ = s.indexOf(T_str); \ - while (__pos__ != -1) { (S((T_str), s, useURLencode)); __pos__ = s.indexOf(T_str, __pos__ + 1);} - -// Parse %vN% to replace ESPEasy variables -bool parse_pct_v_num_pct(String& s, boolean useURLencode, int start_pos) -{ - const String key_prefix = F("%v"); - int v_index = s.indexOf(key_prefix, start_pos); - - bool somethingReplaced = false; - - while ((v_index != -1)) { - // Exclude "%valname% or %value%" - // FIXME TD-er: Must find a more elegant way to fix this - if (!isalpha(s.charAt(v_index + 2))) { - // Check for: - // - Calculations indicated with leading '=' - // - nested indirections like %v%v1%% or %v%v_bla%% or %v_%v_2%% - const bool isv_ = s.charAt(v_index + 2) == '_'; - if ((s.charAt(v_index + 2) == '=') || - (s.charAt(v_index + 2) == '%' && s.charAt(v_index + 3) == 'v') || - (isv_ && (s.charAt(v_index + 3) == '%' && s.charAt(v_index + 4) == 'v'))) { - // FIXME TD-er: This may lead to stack overflow if we do an awful lot of nested user variables - if (parse_pct_v_num_pct(s, useURLencode, v_index + 2 + (isv_ ? 1 : 0))) { - somethingReplaced = true; - } - } - - // variable index may contain a calculation - // Calculations are enforced by a leading '=' - // like: %v=1+%v2%% - String value; - const int pos_closing_pct = s.indexOf('%', v_index + 1); - const String arg = s.substring(v_index + 2 + (isv_ ? 1 : 0), pos_closing_pct); - String valArg(arg); - constexpr int64_t errorvalue = -1; - const int64_t i = CalculateParam(arg, errorvalue); - // addLog(LOG_LEVEL_INFO, strformat(F("s: '%s', calc parse: %s => %d"), s.c_str(), arg.c_str(), i)); - if (i != errorvalue) { // We're calculating a numeric index like %v=1+%v2%%, so have to use the result for the value - valArg = ll2String(i); - } - - // Need to replace the entire arg - const String key = strformat(F("%%%s%s%%"), FsP(isv_ ? F("v_") : F("v")), arg.c_str()); - // addLog(LOG_LEVEL_INFO, strformat(F("parsed, key: %s (valArg: %s)"), key.c_str(), valArg.c_str())); - - if (s.indexOf(key) != -1) { - const bool trimTrailingZeros = true; - const ESPEASY_RULES_FLOAT_TYPE floatvalue = getCustomFloatVar(valArg); - const unsigned char nr_decimals = maxNrDecimals_fpType(floatvalue); - #if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE - value = doubleToString(floatvalue, nr_decimals, trimTrailingZeros); - #else // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE - value = floatToString(floatvalue, nr_decimals, trimTrailingZeros); - #endif // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE - - if (repl(key, value, s, useURLencode)) { - somethingReplaced = true; - } - } - } - v_index = s.indexOf(key_prefix, v_index + 1); // Find next occurance - //addLog(LOG_LEVEL_INFO, strformat(F("parse: %s"), s.c_str())); - } - return somethingReplaced; -} - -void SystemVariables::parseSystemVariables(String& s, boolean useURLencode) -{ - START_TIMER - - if (s.indexOf('%') == -1) { - STOP_TIMER(PARSE_SYSVAR_NOCHANGE); - return; - } - - bool somethingReplaced = false; - - // Parse ESPEasy user variables first as they might be combined - // as arument or index for other variables - parse_pct_v_num_pct(s, useURLencode, 0); - - do { - int last_percent_pos = -1; - somethingReplaced = false; - SystemVariables::Enum enumval = static_cast(0); - do { - enumval = SystemVariables::nextReplacementEnum(s, enumval, last_percent_pos); - - switch (enumval) - { - case SUNRISE: { - SMART_REPL_T(SystemVariables::toString(enumval), replSunRiseTimeString); - somethingReplaced = true; - break; - } - case SUNSET: { - SMART_REPL_T(SystemVariables::toString(enumval), replSunSetTimeString); - somethingReplaced = true; - break; - } - case VARIABLE: - { - // Should not be present anymore, but just in case... - if (parse_pct_v_num_pct(s, useURLencode, 0)) - somethingReplaced = true; - - break; - } - case UNKNOWN: - - // Do not replace - break; - default: - { - const String sysvar_str(SystemVariables::toString(enumval)); - if (s.indexOf(sysvar_str) != -1) { - if (repl( - sysvar_str, - getSystemVariable(enumval), - s, - useURLencode)) - somethingReplaced = true; - } - break; - } - } - } - while (enumval != SystemVariables::Enum::UNKNOWN); - } - while (somethingReplaced); - - STOP_TIMER(PARSE_SYSVAR); -} - -#undef SMART_REPL_T - - -SystemVariables::Enum SystemVariables::nextReplacementEnum(const String& str, SystemVariables::Enum last_tested, int& last_percent_pos) -{ - SystemVariables::Enum nextTested; - int percent_pos = last_percent_pos; - - do { - // Find first position in string which might be a good candidate to look for a system variable. - // Look for "%N" where 'N' is the first letter of a variable name we support. - percent_pos = str.indexOf('%', percent_pos + 1); - - if (percent_pos == -1) { - return Enum::UNKNOWN; - } - - nextTested = SystemVariables::startIndex_beginWith(str.c_str() + percent_pos + 1); - } while (Enum::UNKNOWN == nextTested); - - if (last_percent_pos < percent_pos) { - last_percent_pos = percent_pos; - last_tested = nextTested; - } - - if (last_tested > nextTested) { - // Iterate over the possible system variables - nextTested = static_cast(last_tested + 1); - const char firstChar_nextTested = static_cast(pgm_read_byte(SystemVariables::toFlashString(nextTested))); - const char firstChar_expected = str[percent_pos + 1]; - - if (firstChar_nextTested != firstChar_expected) { - nextTested = Enum::UNKNOWN; - } - } - - if (nextTested >= Enum::UNKNOWN) { - // We have tested all possible system variables - // Skip unsupported ones or maybe it is just a single percentage symbol in a string. - percent_pos = str.indexOf('%', percent_pos + 1); - - if (percent_pos == -1) { - return Enum::UNKNOWN; - } - last_percent_pos = percent_pos; - return SystemVariables::startIndex_beginWith(str.c_str() + percent_pos + 1); - } - - const __FlashStringHelper *fstr_sysvar = SystemVariables::toFlashString(nextTested); - String str_prefix = strformat(F("%%%c"), static_cast(pgm_read_byte(fstr_sysvar))); - bool str_prefix_exists = str.indexOf(str_prefix) != -1; - - for (int i = nextTested; i < Enum::UNKNOWN; ++i) { - SystemVariables::Enum enumval = static_cast(i); - fstr_sysvar = SystemVariables::toFlashString(enumval); - const String new_str_prefix = strformat(F("%%%c"), static_cast(pgm_read_byte(fstr_sysvar))); - - if ((str_prefix == new_str_prefix) && !str_prefix_exists) { - // Just continue - } else { - str_prefix = new_str_prefix; - str_prefix_exists = str.indexOf(str_prefix) != -1; - - if (str_prefix_exists) { - if (str.indexOf(SystemVariables::toString(enumval)) != -1) { - return enumval; - } - } - } - } - - return Enum::UNKNOWN; -} - -String SystemVariables::toString(Enum enumval) -{ - if ((enumval == Enum::SUNRISE) || (enumval == Enum::SUNSET) || enumval == Enum::VARIABLE) { - // These need variables, so only prepend a %, not wrap. - return String('%') + SystemVariables::toFlashString(enumval); - } - - return wrap_String(SystemVariables::toFlashString(enumval), '%'); -} - -SystemVariables::Enum SystemVariables::startIndex_beginWith(const char* beginchar) -{ - if (beginchar == nullptr || *beginchar == '\0') - { - return Enum::UNKNOWN; - } - - const char ch = tolower(*beginchar); -#ifndef LIMIT_BUILD_SIZE - // Speedup search for some variables starting with "%sys" - // Not needed if build size really matters - if (ch == 's') { - // Lots of system variables start with "%sys" - const char sys_str[] = {'s', 'y', 's'}; - bool found = true; - const char* cur = beginchar; - for (size_t i = 0; found && i < NR_ELEMENTS(sys_str); ++i, ++cur) { - if (tolower(*cur) != sys_str[i]) { - found = false; - } - } - if (found) { - const char next_ch = tolower(*cur); - if (next_ch != '\0') { - switch (next_ch) { - case 'b': return Enum::SYSBUILD_DATE; - case 'd': return Enum::SYSDAY; - case 'h': return Enum::SYSHEAP; - case 'l': return Enum::SYSLOAD; - case 'm': return Enum::SYSMIN; - case 'n': return Enum::SYSNAME; - case 's': return Enum::SYSSEC; - case 't': return Enum::SYSTIME; - case 'w': return Enum::SYSWEEKDAY; - case 'y': return Enum::SYSYEAR; - } - } - } - } -#endif - switch (ch) - { - case 'b': return Enum::BOARD_NAME; - case 'c': return Enum::CLIENTIP; - case 'd': return Enum::DNS; -#if FEATURE_ETHERNET - case 'e': return Enum::ETHCONNECTED; -#endif // if FEATURE_ETHERNET - case 'f': return Enum::FLASH_CHIP_MODEL; - case 'g': return Enum::GATEWAY; -#if FEATURE_INTERNAL_TEMPERATURE - case 'i': return Enum::INTERNAL_TEMPERATURE; -#else // if FEATURE_INTERNAL_TEMPERATURE - case 'i': return Enum::IP4; -#endif // if FEATURE_INTERNAL_TEMPERATURE - case 'l': return Enum::LCLTIME; - case 'm': return Enum::SUNRISE_M; - case 'n': return Enum::S_LF; - case 'r': return Enum::S_CR; - case 's': return Enum::SPACE; - case 'u': return Enum::UNIT_sysvar; - // case 'v': return Enum::VARIABLE; // Can not be the first 'v' variable, as the name is only 1 character long - case 'v': return Enum::VCC; - case 'w': return Enum::WI_CH; - } - - return Enum::UNKNOWN; -} - -const __FlashStringHelper * SystemVariables::toFlashString(SystemVariables::Enum enumval) -{ - switch (enumval) { - case Enum::BOARD_NAME: return F("board_name"); - case Enum::BOOT_CAUSE: return F("bootcause"); - case Enum::BSSID: return F("bssid"); - case Enum::CLIENTIP: return F("clientip"); - case Enum::CR: return F("CR"); - case Enum::ESP_CHIP_CORES: return F("cpu_cores"); - case Enum::ESP_CHIP_FREQ: return F("cpu_freq"); - case Enum::ESP_CHIP_ID: return F("cpu_id"); - case Enum::ESP_CHIP_MODEL: return F("cpu_model"); - case Enum::ESP_CHIP_REVISION: return F("cpu_rev"); - case Enum::DNS: return F("dns"); - case Enum::DNS_1: return F("dns1"); - case Enum::DNS_2: return F("dns2"); -#if FEATURE_ETHERNET - case Enum::ETHCONNECTED: return F("ethconnected"); - case Enum::ETHDUPLEX: return F("ethduplex"); - case Enum::ETHSPEED: return F("ethspeed"); - case Enum::ETHSPEEDSTATE: return F("ethspeedstate"); - case Enum::ETHSTATE: return F("ethstate"); - case Enum::ETHWIFIMODE: return F("ethwifimode"); -#endif // if FEATURE_ETHERNET - - case Enum::FLASH_CHIP_MODEL: return F("flash_chip_model"); - case Enum::FLASH_CHIP_VENDOR: return F("flash_chip_vendor"); - case Enum::FLASH_FREQ: return F("flash_freq"); - case Enum::FLASH_SIZE: return F("flash_size"); - case Enum::FS_FREE: return F("fs_free"); - case Enum::FS_SIZE: return F("fs_size"); - case Enum::GATEWAY: return F("gateway"); -#if FEATURE_INTERNAL_TEMPERATURE - case Enum::INTERNAL_TEMPERATURE: return F("inttemp"); -#endif // if FEATURE_INTERNAL_TEMPERATURE - - case Enum::IP4: return F("ip4"); - case Enum::IP: return F("ip"); -#if FEATURE_USE_IPV6 - case Enum::IP6_LOCAL: return F("ipv6local"); -#endif - case Enum::ISVAR_DOUBLE: return F("isvar_double"); - case Enum::ISLIMITED_BUILD: return F("islimited_build"); - case Enum::ISMQTT: return F("ismqtt"); - case Enum::ISMQTTIMP: return F("ismqttimp"); - case Enum::ISNTP: return F("isntp"); - case Enum::ISWIFI: return F("iswifi"); - case Enum::LCLTIME: return F("lcltime"); - case Enum::LCLTIME_AM: return F("lcltime_am"); - case Enum::LF: return F("LF"); - case Enum::SUNRISE_M: return F("m_sunrise"); - case Enum::SUNSET_M: return F("m_sunset"); - case Enum::MAC: return F("mac"); - case Enum::MAC_INT: return F("mac_int"); - case Enum::S_LF: return F("N"); - case Enum::S_CR: return F("R"); - case Enum::RSSI: return F("rssi"); - case Enum::SPACE: return F("SP"); - case Enum::SSID: return F("ssid"); - case Enum::SUBNET: return F("subnet"); - case Enum::SUNRISE: return F("sunrise"); - case Enum::SUNRISE_S: return F("s_sunrise"); - case Enum::SUNSET: return F("sunset"); - case Enum::SUNSET_S: return F("s_sunset"); - case Enum::SYSBUILD_DATE: return F("sysbuild_date"); - case Enum::SYSBUILD_DESCR: return F("sysbuild_desc"); - case Enum::SYSBUILD_FILENAME: return F("sysbuild_filename"); - case Enum::SYSBUILD_GIT: return F("sysbuild_git"); - case Enum::SYSBUILD_TIME: return F("sysbuild_time"); - case Enum::SYSDAY: return F("sysday"); - case Enum::SYSDAY_0: return F("sysday_0"); - case Enum::SYSHEAP: return F("sysheap"); - case Enum::SYSHOUR: return F("syshour"); - case Enum::SYSHOUR_0: return F("syshour_0"); - case Enum::SYSLOAD: return F("sysload"); - case Enum::SYSMIN: return F("sysmin"); - case Enum::SYSMIN_0: return F("sysmin_0"); - case Enum::SYSMONTH: return F("sysmonth"); - case Enum::SYSMONTH_S: return F("sysmonth_s"); - case Enum::SYSMONTH_0: return F("sysmonth_0"); - case Enum::SYSNAME: return F("sysname"); - case Enum::SYSSEC: return F("syssec"); - case Enum::SYSSEC_0: return F("syssec_0"); - case Enum::SYSSEC_D: return F("syssec_d"); - case Enum::SYSSTACK: return F("sysstack"); - case Enum::SYSTIME: return F("systime"); - case Enum::SYSTIME_AM: return F("systime_am"); - case Enum::SYSTIME_AM_0: return F("systime_am_0"); - case Enum::SYSTIME_AM_SP: return F("systime_am_sp"); - case Enum::SYSTM_HM: return F("systm_hm"); - case Enum::SYSTM_HM_0: return F("systm_hm_0"); - case Enum::SYSTM_HM_AM: return F("systm_hm_am"); - case Enum::SYSTM_HM_AM_0: return F("systm_hm_am_0"); - case Enum::SYSTM_HM_AM_SP: return F("systm_hm_am_sp"); - case Enum::SYSTM_HM_SP: return F("systm_hm_sp"); - case Enum::SYSTZOFFSET: return F("systzoffset"); - #ifndef LIMIT_BUILD_SIZE - case Enum::SYSTZOFFSET_S: return F("systzoffset_s"); - #endif // ifndef LIMIT_BUILD_SIZE - case Enum::SYSWEEKDAY: return F("sysweekday"); - case Enum::SYSWEEKDAY_S: return F("sysweekday_s"); - case Enum::SYSYEAR: return F("sysyear"); - case Enum::SYSYEARS: return F("sysyears"); - case Enum::SYSYEAR_0: return F("sysyear_0"); - case Enum::UNIT_sysvar: return F("unit"); -#if FEATURE_ZEROFILLED_UNITNUMBER - case Enum::UNIT_0_sysvar: return F("unit_0"); -#endif // FEATURE_ZEROFILLED_UNITNUMBER - case Enum::UNIXDAY: return F("unixday"); - case Enum::UNIXDAY_SEC: return F("unixday_sec"); - case Enum::UNIXTIME: return F("unixtime"); - #ifndef LIMIT_BUILD_SIZE - case Enum::UNIXTIME_LCL: return F("unixtime_lcl"); - #endif // ifndef LIMIT_BUILD_SIZE - case Enum::UPTIME: return F("uptime"); - case Enum::UPTIME_MS: return F("uptime_ms"); - case Enum::VCC: return F("vcc"); - case Enum::VARIABLE: return F("v"); // Can not be the first 'v' variable, as the name is only 1 character long - case Enum::WI_CH: return F("wi_ch"); - - case Enum::UNKNOWN: break; - } - return F("Unknown"); -} +#include "../Helpers/SystemVariables.h" + + +#include "../../ESPEasy_common.h" + +#include "../../ESPEasy-Globals.h" + +#include "../CustomBuild/CompiletimeDefines.h" + +#include "../DataStructs/TimingStats.h" + +#include "../ESPEasyCore/ESPEasy_Log.h" +#include "../ESPEasyCore/ESPEasyNetwork.h" + +#include "../Globals/CRCValues.h" +#include "../Globals/ESPEasy_time.h" +#include "../Globals/ESPEasyWiFiEvent.h" +#if FEATURE_MQTT +# include "../Globals/MQTT.h" +#endif // if FEATURE_MQTT +#include "../Globals/NetworkState.h" +#include "../Globals/RulesCalculate.h" +#include "../Globals/RuntimeData.h" +#include "../Globals/Settings.h" +#include "../Globals/Statistics.h" + +#include "../Helpers/Convert.h" +#include "../Helpers/Hardware_device_info.h" +#include "../Helpers/Misc.h" +#include "../Helpers/Numerical.h" +#include "../Helpers/StringConverter.h" +#include "../Helpers/StringProvider.h" + + +#if defined(ESP8266) + # include +#endif // if defined(ESP8266) +#if defined(ESP32) + # include +#endif // if defined(ESP32) + + +String getReplacementString(const String& format, const String& s) { + int startpos = s.indexOf(format); + int endpos = s.indexOf('%', startpos + 1); + if (endpos == -1) { + addLog(LOG_LEVEL_ERROR, concat(F("SunTime syntax error: "), format)); + return format; + } + String R = s.substring(startpos, endpos + 1); + + +#ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + addLogMove(LOG_LEVEL_DEBUG, strformat( + F("ReplacementString SunTime: %s offset: %d"), + R.c_str(), + ESPEasy_time::getSecOffset(R))); + } +#endif // ifndef BUILD_NO_DEBUG + return R; +} + +void replSunRiseTimeString(const String& format, String& s, boolean useURLencode) { + const String R(getReplacementString(format, s)); + + repl(R, node_time.getSunriseTimeString(':', ESPEasy_time::getSecOffset(R)), s, useURLencode); +} + +void replSunSetTimeString(const String& format, String& s, boolean useURLencode) { + const String R(getReplacementString(format, s)); + + repl(R, node_time.getSunsetTimeString(':', ESPEasy_time::getSecOffset(R)), s, useURLencode); +} + +String timeReplacement_leadZero(int value) +{ + char valueString[5] = { 0 }; + + sprintf_P(valueString, PSTR("%02d"), value); + return valueString; +} + +// FIXME TD-er: Try to match these with StringProvider::getValue +LabelType::Enum SystemVariables2LabelType(SystemVariables::Enum enumval) { + LabelType::Enum label = LabelType::MAX_LABEL; + + switch (enumval) + { + case SystemVariables::IP: label = LabelType::IP_ADDRESS; break; +#if FEATURE_USE_IPV6 + case SystemVariables::IP6_LOCAL: label = LabelType::IP6_LOCAL; break; +#endif + case SystemVariables::SUBNET: label = LabelType::IP_SUBNET; break; + case SystemVariables::DNS: label = LabelType::DNS; break; + case SystemVariables::DNS_1: label = LabelType::DNS_1; break; + case SystemVariables::DNS_2: label = LabelType::DNS_2; break; + case SystemVariables::GATEWAY: label = LabelType::GATEWAY; break; + case SystemVariables::CLIENTIP: label = LabelType::CLIENT_IP; break; + #if FEATURE_INTERNAL_TEMPERATURE + case SystemVariables::INTERNAL_TEMPERATURE: label = LabelType::INTERNAL_TEMPERATURE; break; + #endif // if FEATURE_INTERNAL_TEMPERATURE + + #ifdef USES_ESPEASY_NOW + case SystemVariables::ESPEASY_NOW_ENABLED: label = LabelType::ESPEASY_NOW_ENABLED; break; + case SystemVariables::ESPEASY_NOW_CHANNEL: label = LabelType::ESPEASY_NOW_CHANNEL; break; + case SystemVariables::ESPEASY_NOW_MQTT: label = LabelType::ESPEASY_NOW_MQTT; break; + case SystemVariables::ESPEASY_NOW_DISTANCE: label = LabelType::ESPEASY_NOW_DISTANCE; break; + case SystemVariables::ESPEASY_NOW_FORCED_CHANNEL: label = LabelType::ESPEASY_NOW_FORCED_CHANNEL; break; + #endif + + #if FEATURE_ETHERNET + case SystemVariables::ETHWIFIMODE: label = LabelType::ETH_WIFI_MODE; break; // 0=WIFI, 1=ETH + case SystemVariables::ETHCONNECTED: label = LabelType::ETH_CONNECTED; break; // 0=disconnected, 1=connected + case SystemVariables::ETHDUPLEX: label = LabelType::ETH_DUPLEX; break; + case SystemVariables::ETHSPEED: label = LabelType::ETH_SPEED; break; + case SystemVariables::ETHSTATE: label = LabelType::ETH_STATE; break; + case SystemVariables::ETHSPEEDSTATE: label = LabelType::ETH_SPEED_STATE; break; + #endif // if FEATURE_ETHERNET + + case SystemVariables::LCLTIME: label = LabelType::LOCAL_TIME; break; + case SystemVariables::MAC: label = LabelType::STA_MAC; break; + case SystemVariables::RSSI: label = LabelType::WIFI_RSSI; break; + case SystemVariables::SUNRISE_S: label = LabelType::SUNRISE_S; break; + case SystemVariables::SUNSET_S: label = LabelType::SUNSET_S; break; + case SystemVariables::SUNRISE_M: label = LabelType::SUNRISE_M; break; + case SystemVariables::SUNSET_M: label = LabelType::SUNSET_M; break; + case SystemVariables::SYSBUILD_DESCR: label = LabelType::BUILD_DESC; break; + case SystemVariables::SYSBUILD_FILENAME: label = LabelType::BINARY_FILENAME; break; + case SystemVariables::SYSBUILD_GIT: label = LabelType::GIT_BUILD; break; + case SystemVariables::SYSSTACK: label = LabelType::FREE_STACK; break; + case SystemVariables::UNIT_sysvar: label = LabelType::UNIT_NR; break; + #if FEATURE_ZEROFILLED_UNITNUMBER + case SystemVariables::UNIT_0_sysvar: label = LabelType::UNIT_NR_0; break; + #endif // FEATURE_ZEROFILLED_UNITNUMBER + case SystemVariables::FLASH_FREQ: label = LabelType::FLASH_CHIP_SPEED; break; + case SystemVariables::FLASH_SIZE: label = LabelType::FLASH_CHIP_REAL_SIZE; break; + case SystemVariables::FLASH_CHIP_VENDOR: label = LabelType::FLASH_CHIP_VENDOR; break; + case SystemVariables::FLASH_CHIP_MODEL: label = LabelType::FLASH_CHIP_MODEL; break; + case SystemVariables::FS_SIZE: label = LabelType::FS_SIZE; break; + case SystemVariables::FS_FREE: label = LabelType::FS_FREE; break; + + case SystemVariables::ESP_CHIP_ID: label = LabelType::ESP_CHIP_ID; break; + case SystemVariables::ESP_CHIP_FREQ: label = LabelType::ESP_CHIP_FREQ; break; + case SystemVariables::ESP_CHIP_MODEL: label = LabelType::ESP_CHIP_MODEL; break; + case SystemVariables::ESP_CHIP_REVISION: label = LabelType::ESP_CHIP_REVISION; break; + case SystemVariables::ESP_CHIP_CORES: label = LabelType::ESP_CHIP_CORES; break; + case SystemVariables::BOARD_NAME: label = LabelType::BOARD_NAME; break; + + default: + // No matching LabelType yet. + break; + } + return label; +} + +String SystemVariables::getSystemVariable(SystemVariables::Enum enumval) { + const LabelType::Enum label = SystemVariables2LabelType(enumval); + + if (LabelType::MAX_LABEL != label) { + return getValue(label); + } + constexpr int INT_NOT_SET = std::numeric_limits::min(); + + int intvalue = INT_NOT_SET; + + switch (enumval) + { + case BOOT_CAUSE: intvalue = lastBootCause; break; // Integer value to be used in rules + case BSSID: return (WiFiEventData.WiFiDisconnected()) ? MAC_address().toString() : WiFi.BSSIDstr(); + case CR: return String('\r'); + case IP4: intvalue = static_cast(NetworkLocalIP()[3]); break; // 4th IP octet + case ISVAR_DOUBLE: intvalue = + #if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + 1; + #else + 0; + #endif + break; + case ISLIMITED_BUILD: intvalue = + #ifdef LIMIT_BUILD_SIZE + 1; + #else + 0; + #endif + break; + case ISMQTT: intvalue = + #if FEATURE_MQTT + MQTTclient_connected ? 1 : + #endif // if FEATURE_MQTT + 0; break; + + case ISMQTTIMP: intvalue = + #ifdef USES_P037 + P037_MQTTImport_connected ? 1 : + #endif // ifdef USES_P037 + 0; break; + + case ISNTP: intvalue = statusNTPInitialized ? 1 : 0; break; + case ISWIFI: intvalue = WiFiEventData.wifiStatus; break; // 0=disconnected, 1=connected, 2=got ip, 4=services + // initialized + case LCLTIME_AM: return node_time.getDateTimeString_ampm('-', ':', ' '); + case LF: return String('\n'); + case MAC_INT: intvalue = getChipId(); break; // Last 24 bit of MAC address as integer, to be used in rules. + case SPACE: return String(' '); + case SSID: return (WiFiEventData.WiFiDisconnected()) ? String(F("--")) : WiFi.SSID(); + case SYSBUILD_DATE: return get_build_date(); + case SYSBUILD_TIME: return get_build_time(); + case SYSDAY: intvalue = node_time.day(); break; + case SYSDAY_0: return timeReplacement_leadZero(node_time.day()); + case SYSHEAP: intvalue = ESP.getFreeHeap(); break; + case SYSHOUR: intvalue = node_time.hour(); break; + case SYSHOUR_0: return timeReplacement_leadZero(node_time.hour()); + case SYSLOAD: return String(getCPUload(), 2); + case SYSMIN: intvalue = node_time.minute(); break; + case SYSMIN_0: return timeReplacement_leadZero(node_time.minute()); + case SYSMONTH: intvalue = node_time.month(); break; + case SYSMONTH_S: return node_time.month_str(); + case SYSNAME: return Settings.getHostname(); + case SYSSEC: intvalue = node_time.second(); break; + case SYSSEC_0: return timeReplacement_leadZero(node_time.second()); + case SYSSEC_D: intvalue = ((node_time.hour() * 60) + node_time.minute()) * 60 + node_time.second(); break; + case SYSTIME: return node_time.getTimeString(':'); + case SYSTIME_AM: return node_time.getTimeString_ampm(':'); + case SYSTIME_AM_0: return node_time.getTimeString_ampm(':', true, '0'); + case SYSTIME_AM_SP: return node_time.getTimeString_ampm(':', true, ' '); + case SYSTM_HM: return node_time.getTimeString(':', false); + case SYSTM_HM_0: return node_time.getTimeString(':', false, '0'); + case SYSTM_HM_SP: return node_time.getTimeString(':', false, ' '); + case SYSTM_HM_AM: return node_time.getTimeString_ampm(':', false); + case SYSTM_HM_AM_0: return node_time.getTimeString_ampm(':', false, '0'); + case SYSTM_HM_AM_SP: return node_time.getTimeString_ampm(':', false, ' '); + case SYSTZOFFSET: return node_time.getTimeZoneOffsetString(); + #ifndef LIMIT_BUILD_SIZE + case SYSTZOFFSET_S: intvalue = static_cast(static_cast(node_time.getLocalUnixTime()) - static_cast(node_time.getUnixTime())); break; + #endif // ifndef LIMIT_BUILD_SIZE + case SYSWEEKDAY: intvalue = node_time.weekday(); break; + case SYSWEEKDAY_S: return node_time.weekday_str(); + case SYSYEAR_0: + case SYSYEAR: intvalue = node_time.year(); break; + case SYSYEARS: return timeReplacement_leadZero(node_time.year() % 100); + case SYSMONTH_0: return timeReplacement_leadZero(node_time.month()); + case S_CR: return F("\\r"); + case S_LF: return F("\\n"); + case UNIXDAY: intvalue = node_time.getUnixTime() / 86400; break; + case UNIXDAY_SEC: intvalue = node_time.getUnixTime() % 86400; break; + case UNIXTIME: return String(node_time.getUnixTime()); + #ifndef LIMIT_BUILD_SIZE + case UNIXTIME_LCL: return String(node_time.getLocalUnixTime()); + #endif // ifndef LIMIT_BUILD_SIZE + case UPTIME: intvalue = getUptimeMinutes(); break; + case UPTIME_MS: return ull2String(getMicros64() / 1000); + #if FEATURE_ADC_VCC + case VCC: return String(vcc); + #else // if FEATURE_ADC_VCC + case VCC: intvalue = -1; break; + #endif // if FEATURE_ADC_VCC + case WI_CH: intvalue = (WiFiEventData.WiFiDisconnected()) ? 0 : WiFi.channel(); break; + + default: + // Already handled above. + return EMPTY_STRING; + } + + if (intvalue != INT_NOT_SET) { + return String(intvalue); + } + + return EMPTY_STRING; +} + +/* +#define SMART_REPL_T(T, S) \ + while (s.indexOf(T) != -1) { (S((T), s, useURLencode)); } +*/ + +#define SMART_REPL_T(T, S) \ + const String T_str(T); int __pos__ = s.indexOf(T_str); \ + while (__pos__ != -1) { (S((T_str), s, useURLencode)); __pos__ = s.indexOf(T_str, __pos__ + 1);} + +// Parse %vN% to replace ESPEasy variables +bool parse_pct_v_num_pct(String& s, boolean useURLencode, int start_pos) +{ + const String key_prefix = F("%v"); + int v_index = s.indexOf(key_prefix, start_pos); + + bool somethingReplaced = false; + + while ((v_index != -1)) { + // Exclude "%valname% or %value%" + // FIXME TD-er: Must find a more elegant way to fix this + if (!isalpha(s.charAt(v_index + 2))) { + // Check for: + // - Calculations indicated with leading '=' + // - nested indirections like %v%v1%% or %v%v_bla%% or %v_%v_2%% + const bool isv_ = s.charAt(v_index + 2) == '_'; + if ((s.charAt(v_index + 2) == '=') || + (s.charAt(v_index + 2) == '%' && s.charAt(v_index + 3) == 'v') || + (isv_ && (s.charAt(v_index + 3) == '%' && s.charAt(v_index + 4) == 'v'))) { + // FIXME TD-er: This may lead to stack overflow if we do an awful lot of nested user variables + if (parse_pct_v_num_pct(s, useURLencode, v_index + 2 + (isv_ ? 1 : 0))) { + somethingReplaced = true; + } + } + + // variable index may contain a calculation + // Calculations are enforced by a leading '=' + // like: %v=1+%v2%% + String value; + const int pos_closing_pct = s.indexOf('%', v_index + 1); + const String arg = s.substring(v_index + 2 + (isv_ ? 1 : 0), pos_closing_pct); + String valArg(arg); + constexpr int64_t errorvalue = -1; + const int64_t i = CalculateParam(arg, errorvalue); + // addLog(LOG_LEVEL_INFO, strformat(F("s: '%s', calc parse: %s => %d"), s.c_str(), arg.c_str(), i)); + if (i != errorvalue) { // We're calculating a numeric index like %v=1+%v2%%, so have to use the result for the value + valArg = ll2String(i); + } + + // Need to replace the entire arg + const String key = strformat(F("%%%s%s%%"), FsP(isv_ ? F("v_") : F("v")), arg.c_str()); + // addLog(LOG_LEVEL_INFO, strformat(F("parsed, key: %s (valArg: %s)"), key.c_str(), valArg.c_str())); + + if (s.indexOf(key) != -1) { + const bool trimTrailingZeros = true; + const ESPEASY_RULES_FLOAT_TYPE floatvalue = getCustomFloatVar(valArg); + const unsigned char nr_decimals = maxNrDecimals_fpType(floatvalue); + #if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + value = doubleToString(floatvalue, nr_decimals, trimTrailingZeros); + #else // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + value = floatToString(floatvalue, nr_decimals, trimTrailingZeros); + #endif // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + + if (repl(key, value, s, useURLencode)) { + somethingReplaced = true; + } + } + } + v_index = s.indexOf(key_prefix, v_index + 1); // Find next occurance + //addLog(LOG_LEVEL_INFO, strformat(F("parse: %s"), s.c_str())); + } + return somethingReplaced; +} + +void SystemVariables::parseSystemVariables(String& s, boolean useURLencode) +{ + START_TIMER + + if (s.indexOf('%') == -1) { + STOP_TIMER(PARSE_SYSVAR_NOCHANGE); + return; + } + + bool somethingReplaced = false; + + // Parse ESPEasy user variables first as they might be combined + // as arument or index for other variables + parse_pct_v_num_pct(s, useURLencode, 0); + + do { + int last_percent_pos = -1; + somethingReplaced = false; + SystemVariables::Enum enumval = static_cast(0); + do { + enumval = SystemVariables::nextReplacementEnum(s, enumval, last_percent_pos); + + switch (enumval) + { + case SUNRISE: { + SMART_REPL_T(SystemVariables::toString(enumval), replSunRiseTimeString); + somethingReplaced = true; + break; + } + case SUNSET: { + SMART_REPL_T(SystemVariables::toString(enumval), replSunSetTimeString); + somethingReplaced = true; + break; + } + case VARIABLE: + { + // Should not be present anymore, but just in case... + if (parse_pct_v_num_pct(s, useURLencode, 0)) + somethingReplaced = true; + + break; + } + case UNKNOWN: + + // Do not replace + break; + default: + { + const String sysvar_str(SystemVariables::toString(enumval)); + if (s.indexOf(sysvar_str) != -1) { + if (repl( + sysvar_str, + getSystemVariable(enumval), + s, + useURLencode)) + somethingReplaced = true; + } + break; + } + } + } + while (enumval != SystemVariables::Enum::UNKNOWN); + } + while (somethingReplaced); + + STOP_TIMER(PARSE_SYSVAR); +} + +#undef SMART_REPL_T + + +SystemVariables::Enum SystemVariables::nextReplacementEnum(const String& str, SystemVariables::Enum last_tested, int& last_percent_pos) +{ + SystemVariables::Enum nextTested; + int percent_pos = last_percent_pos; + + do { + // Find first position in string which might be a good candidate to look for a system variable. + // Look for "%N" where 'N' is the first letter of a variable name we support. + percent_pos = str.indexOf('%', percent_pos + 1); + + if (percent_pos == -1) { + return Enum::UNKNOWN; + } + + nextTested = SystemVariables::startIndex_beginWith(str.c_str() + percent_pos + 1); + } while (Enum::UNKNOWN == nextTested); + + if (last_percent_pos < percent_pos) { + last_percent_pos = percent_pos; + last_tested = nextTested; + } + + if (last_tested > nextTested) { + // Iterate over the possible system variables + nextTested = static_cast(last_tested + 1); + const char firstChar_nextTested = static_cast(pgm_read_byte(SystemVariables::toFlashString(nextTested))); + const char firstChar_expected = str[percent_pos + 1]; + + if (firstChar_nextTested != firstChar_expected) { + nextTested = Enum::UNKNOWN; + } + } + + if (nextTested >= Enum::UNKNOWN) { + // We have tested all possible system variables + // Skip unsupported ones or maybe it is just a single percentage symbol in a string. + percent_pos = str.indexOf('%', percent_pos + 1); + + if (percent_pos == -1) { + return Enum::UNKNOWN; + } + last_percent_pos = percent_pos; + return SystemVariables::startIndex_beginWith(str.c_str() + percent_pos + 1); + } + + const __FlashStringHelper *fstr_sysvar = SystemVariables::toFlashString(nextTested); + String str_prefix = strformat(F("%%%c"), static_cast(pgm_read_byte(fstr_sysvar))); + bool str_prefix_exists = str.indexOf(str_prefix) != -1; + + for (int i = nextTested; i < Enum::UNKNOWN; ++i) { + SystemVariables::Enum enumval = static_cast(i); + fstr_sysvar = SystemVariables::toFlashString(enumval); + const String new_str_prefix = strformat(F("%%%c"), static_cast(pgm_read_byte(fstr_sysvar))); + + if ((str_prefix == new_str_prefix) && !str_prefix_exists) { + // Just continue + } else { + str_prefix = new_str_prefix; + str_prefix_exists = str.indexOf(str_prefix) != -1; + + if (str_prefix_exists) { + if (str.indexOf(SystemVariables::toString(enumval)) != -1) { + return enumval; + } + } + } + } + + return Enum::UNKNOWN; +} + +String SystemVariables::toString(Enum enumval) +{ + if ((enumval == Enum::SUNRISE) || (enumval == Enum::SUNSET) || enumval == Enum::VARIABLE) { + // These need variables, so only prepend a %, not wrap. + return String('%') + SystemVariables::toFlashString(enumval); + } + + return wrap_String(SystemVariables::toFlashString(enumval), '%'); +} + +SystemVariables::Enum SystemVariables::startIndex_beginWith(const char* beginchar) +{ + if (beginchar == nullptr || *beginchar == '\0') + { + return Enum::UNKNOWN; + } + + const char ch = tolower(*beginchar); +#ifndef LIMIT_BUILD_SIZE + // Speedup search for some variables starting with "%sys" + // Not needed if build size really matters + if (ch == 's') { + // Lots of system variables start with "%sys" + const char sys_str[] = {'s', 'y', 's'}; + bool found = true; + const char* cur = beginchar; + for (size_t i = 0; found && i < NR_ELEMENTS(sys_str); ++i, ++cur) { + if (tolower(*cur) != sys_str[i]) { + found = false; + } + } + if (found) { + const char next_ch = tolower(*cur); + if (next_ch != '\0') { + switch (next_ch) { + case 'b': return Enum::SYSBUILD_DATE; + case 'd': return Enum::SYSDAY; + case 'h': return Enum::SYSHEAP; + case 'l': return Enum::SYSLOAD; + case 'm': return Enum::SYSMIN; + case 'n': return Enum::SYSNAME; + case 's': return Enum::SYSSEC; + case 't': return Enum::SYSTIME; + case 'w': return Enum::SYSWEEKDAY; + case 'y': return Enum::SYSYEAR; + } + } + } + } +#endif + switch (ch) + { + case 'b': return Enum::BOARD_NAME; + case 'c': return Enum::CLIENTIP; + case 'd': return Enum::DNS; +#if FEATURE_ETHERNET + case 'e': return Enum::ETHCONNECTED; +#endif // if FEATURE_ETHERNET + case 'f': return Enum::FLASH_CHIP_MODEL; + case 'g': return Enum::GATEWAY; +#if FEATURE_INTERNAL_TEMPERATURE + case 'i': return Enum::INTERNAL_TEMPERATURE; +#else // if FEATURE_INTERNAL_TEMPERATURE + case 'i': return Enum::IP4; +#endif // if FEATURE_INTERNAL_TEMPERATURE + case 'l': return Enum::LCLTIME; + case 'm': return Enum::SUNRISE_M; + case 'n': return Enum::S_LF; + case 'r': return Enum::S_CR; + case 's': return Enum::SPACE; + case 'u': return Enum::UNIT_sysvar; + // case 'v': return Enum::VARIABLE; // Can not be the first 'v' variable, as the name is only 1 character long + case 'v': return Enum::VCC; + case 'w': return Enum::WI_CH; + } + + return Enum::UNKNOWN; +} + +const __FlashStringHelper * SystemVariables::toFlashString(SystemVariables::Enum enumval) +{ + switch (enumval) { + case Enum::BOARD_NAME: return F("board_name"); + case Enum::BOOT_CAUSE: return F("bootcause"); + case Enum::BSSID: return F("bssid"); + case Enum::CLIENTIP: return F("clientip"); + case Enum::CR: return F("CR"); + case Enum::ESP_CHIP_CORES: return F("cpu_cores"); + case Enum::ESP_CHIP_FREQ: return F("cpu_freq"); + case Enum::ESP_CHIP_ID: return F("cpu_id"); + case Enum::ESP_CHIP_MODEL: return F("cpu_model"); + case Enum::ESP_CHIP_REVISION: return F("cpu_rev"); + case Enum::DNS: return F("dns"); + case Enum::DNS_1: return F("dns1"); + case Enum::DNS_2: return F("dns2"); +#if FEATURE_ETHERNET + case Enum::ETHCONNECTED: return F("ethconnected"); + case Enum::ETHDUPLEX: return F("ethduplex"); + case Enum::ETHSPEED: return F("ethspeed"); + case Enum::ETHSPEEDSTATE: return F("ethspeedstate"); + case Enum::ETHSTATE: return F("ethstate"); + case Enum::ETHWIFIMODE: return F("ethwifimode"); +#endif // if FEATURE_ETHERNET + + case Enum::FLASH_CHIP_MODEL: return F("flash_chip_model"); + case Enum::FLASH_CHIP_VENDOR: return F("flash_chip_vendor"); + case Enum::FLASH_FREQ: return F("flash_freq"); + case Enum::FLASH_SIZE: return F("flash_size"); + case Enum::FS_FREE: return F("fs_free"); + case Enum::FS_SIZE: return F("fs_size"); + case Enum::GATEWAY: return F("gateway"); +#if FEATURE_INTERNAL_TEMPERATURE + case Enum::INTERNAL_TEMPERATURE: return F("inttemp"); +#endif // if FEATURE_INTERNAL_TEMPERATURE + + case Enum::IP4: return F("ip4"); + case Enum::IP: return F("ip"); +#if FEATURE_USE_IPV6 + case Enum::IP6_LOCAL: return F("ipv6local"); +#endif + case Enum::ISVAR_DOUBLE: return F("isvar_double"); + case Enum::ISLIMITED_BUILD: return F("islimited_build"); + case Enum::ISMQTT: return F("ismqtt"); + case Enum::ISMQTTIMP: return F("ismqttimp"); + case Enum::ISNTP: return F("isntp"); + case Enum::ISWIFI: return F("iswifi"); + +#ifdef USES_ESPEASY_NOW + case Enum::ESPEASY_NOW_ENABLED: return F("mesh_enabled"); + case Enum::ESPEASY_NOW_CHANNEL: return F("mesh_ch"); + case Enum::ESPEASY_NOW_MQTT: return F("mesh_mqtt"); + case Enum::ESPEASY_NOW_DISTANCE: return F("mesh_dist"); + case Enum::ESPEASY_NOW_FORCED_CHANNEL: return F("mesh_forced_ch"); +#endif + + case Enum::LCLTIME: return F("lcltime"); + case Enum::LCLTIME_AM: return F("lcltime_am"); + case Enum::LF: return F("LF"); + case Enum::SUNRISE_M: return F("m_sunrise"); + case Enum::SUNSET_M: return F("m_sunset"); + case Enum::MAC: return F("mac"); + case Enum::MAC_INT: return F("mac_int"); + case Enum::S_LF: return F("N"); + case Enum::S_CR: return F("R"); + case Enum::RSSI: return F("rssi"); + case Enum::SPACE: return F("SP"); + case Enum::SSID: return F("ssid"); + case Enum::SUBNET: return F("subnet"); + case Enum::SUNRISE: return F("sunrise"); + case Enum::SUNRISE_S: return F("s_sunrise"); + case Enum::SUNSET: return F("sunset"); + case Enum::SUNSET_S: return F("s_sunset"); + case Enum::SYSBUILD_DATE: return F("sysbuild_date"); + case Enum::SYSBUILD_DESCR: return F("sysbuild_desc"); + case Enum::SYSBUILD_FILENAME: return F("sysbuild_filename"); + case Enum::SYSBUILD_GIT: return F("sysbuild_git"); + case Enum::SYSBUILD_TIME: return F("sysbuild_time"); + case Enum::SYSDAY: return F("sysday"); + case Enum::SYSDAY_0: return F("sysday_0"); + case Enum::SYSHEAP: return F("sysheap"); + case Enum::SYSHOUR: return F("syshour"); + case Enum::SYSHOUR_0: return F("syshour_0"); + case Enum::SYSLOAD: return F("sysload"); + case Enum::SYSMIN: return F("sysmin"); + case Enum::SYSMIN_0: return F("sysmin_0"); + case Enum::SYSMONTH: return F("sysmonth"); + case Enum::SYSMONTH_S: return F("sysmonth_s"); + case Enum::SYSMONTH_0: return F("sysmonth_0"); + case Enum::SYSNAME: return F("sysname"); + case Enum::SYSSEC: return F("syssec"); + case Enum::SYSSEC_0: return F("syssec_0"); + case Enum::SYSSEC_D: return F("syssec_d"); + case Enum::SYSSTACK: return F("sysstack"); + case Enum::SYSTIME: return F("systime"); + case Enum::SYSTIME_AM: return F("systime_am"); + case Enum::SYSTIME_AM_0: return F("systime_am_0"); + case Enum::SYSTIME_AM_SP: return F("systime_am_sp"); + case Enum::SYSTM_HM: return F("systm_hm"); + case Enum::SYSTM_HM_0: return F("systm_hm_0"); + case Enum::SYSTM_HM_AM: return F("systm_hm_am"); + case Enum::SYSTM_HM_AM_0: return F("systm_hm_am_0"); + case Enum::SYSTM_HM_AM_SP: return F("systm_hm_am_sp"); + case Enum::SYSTM_HM_SP: return F("systm_hm_sp"); + case Enum::SYSTZOFFSET: return F("systzoffset"); + #ifndef LIMIT_BUILD_SIZE + case Enum::SYSTZOFFSET_S: return F("systzoffset_s"); + #endif // ifndef LIMIT_BUILD_SIZE + case Enum::SYSWEEKDAY: return F("sysweekday"); + case Enum::SYSWEEKDAY_S: return F("sysweekday_s"); + case Enum::SYSYEAR: return F("sysyear"); + case Enum::SYSYEARS: return F("sysyears"); + case Enum::SYSYEAR_0: return F("sysyear_0"); + case Enum::UNIT_sysvar: return F("unit"); +#if FEATURE_ZEROFILLED_UNITNUMBER + case Enum::UNIT_0_sysvar: return F("unit_0"); +#endif // FEATURE_ZEROFILLED_UNITNUMBER + case Enum::UNIXDAY: return F("unixday"); + case Enum::UNIXDAY_SEC: return F("unixday_sec"); + case Enum::UNIXTIME: return F("unixtime"); + #ifndef LIMIT_BUILD_SIZE + case Enum::UNIXTIME_LCL: return F("unixtime_lcl"); + #endif // ifndef LIMIT_BUILD_SIZE + case Enum::UPTIME: return F("uptime"); + case Enum::UPTIME_MS: return F("uptime_ms"); + case Enum::VCC: return F("vcc"); + case Enum::VARIABLE: return F("v"); // Can not be the first 'v' variable, as the name is only 1 character long + case Enum::WI_CH: return F("wi_ch"); + + case Enum::UNKNOWN: break; + } + return F("Unknown"); +} diff --git a/src/src/Helpers/SystemVariables.h b/src/src/Helpers/SystemVariables.h index 0b1c0df50d..e0c4f677a5 100644 --- a/src/src/Helpers/SystemVariables.h +++ b/src/src/Helpers/SystemVariables.h @@ -1,151 +1,159 @@ -#ifndef HELPERS_SYSTEMVARIABLES_H -#define HELPERS_SYSTEMVARIABLES_H - -#include "../../ESPEasy_common.h" - -class SystemVariables { -public: - - enum Enum : uint8_t { - // For optmization, keep enums sorted alfabetically by their flash string - BOARD_NAME, - BOOT_CAUSE, - BSSID, - CLIENTIP, - CR, - DNS, - DNS_1, - DNS_2, - ESP_CHIP_CORES, - ESP_CHIP_FREQ, - ESP_CHIP_ID, - ESP_CHIP_MODEL, - ESP_CHIP_REVISION, -#if FEATURE_ETHERNET - ETHCONNECTED, - ETHDUPLEX, - ETHSPEED, - ETHSPEEDSTATE, - ETHSTATE, - ETHWIFIMODE, -#endif // if FEATURE_ETHERNET - - FLASH_CHIP_MODEL, - FLASH_CHIP_VENDOR, - FLASH_FREQ, - FLASH_SIZE, - FS_FREE, - FS_SIZE, - GATEWAY, -#if FEATURE_INTERNAL_TEMPERATURE - INTERNAL_TEMPERATURE, -#endif // if FEATURE_INTERNAL_TEMPERATURE - - IP4, - IP, -#if FEATURE_USE_IPV6 - IP6_LOCAL, -#endif - ISVAR_DOUBLE, - ISLIMITED_BUILD, - ISMQTT, - ISMQTTIMP, - ISNTP, - ISWIFI, - LCLTIME, - LCLTIME_AM, - LF, - SUNRISE_M, - SUNSET_M, - MAC, - MAC_INT, - S_LF, - S_CR, - RSSI, - SPACE, - SSID, - SUBNET, - SUNRISE, - SUNRISE_S, - SUNSET, - SUNSET_S, - SYSBUILD_DATE, - SYSBUILD_DESCR, - SYSBUILD_FILENAME, - SYSBUILD_GIT, - SYSBUILD_TIME, - SYSDAY, - SYSDAY_0, - SYSHEAP, - SYSHOUR, - SYSHOUR_0, - SYSLOAD, - SYSMIN, - SYSMIN_0, - SYSMONTH, - SYSMONTH_S, - SYSMONTH_0, - SYSNAME, - SYSSEC, - SYSSEC_0, - SYSSEC_D, - SYSSTACK, - SYSTIME, - SYSTIME_AM, - SYSTIME_AM_0, - SYSTIME_AM_SP, - SYSTM_HM, - SYSTM_HM_0, - SYSTM_HM_AM, - SYSTM_HM_AM_0, - SYSTM_HM_AM_SP, - SYSTM_HM_SP, - SYSTZOFFSET, - #ifndef LIMIT_BUILD_SIZE - SYSTZOFFSET_S, - #endif // ifndef LIMIT_BUILD_SIZE - SYSWEEKDAY, - SYSWEEKDAY_S, - SYSYEAR, - SYSYEARS, - SYSYEAR_0, - UNIT_sysvar, -#if FEATURE_ZEROFILLED_UNITNUMBER - UNIT_0_sysvar, -#endif // FEATURE_ZEROFILLED_UNITNUMBER - UNIXDAY, - UNIXDAY_SEC, - UNIXTIME, - #ifndef LIMIT_BUILD_SIZE - UNIXTIME_LCL, - #endif // ifndef LIMIT_BUILD_SIZE - UPTIME, - UPTIME_MS, - VCC, - VARIABLE, // Can not be the first 'v' variable, as the name is only 1 character long - WI_CH, - - - // Keep UNKNOWN as last - UNKNOWN - }; - - // Find the next thing to replace. - // Return UNKNOWN when nothing needs to be replaced. - static SystemVariables::Enum nextReplacementEnum(const String & str, - SystemVariables::Enum last_tested, - int & last_percent_pos); - - static String toString(SystemVariables::Enum enumval); - - static SystemVariables::Enum startIndex_beginWith(const char* beginchar); - static const __FlashStringHelper* toFlashString(SystemVariables::Enum enumval); - - static String getSystemVariable(SystemVariables::Enum enumval); - - static void parseSystemVariables(String& s, - boolean useURLencode); -}; - - -#endif // HELPERS_SYSTEMVARIABLES_H +#ifndef HELPERS_SYSTEMVARIABLES_H +#define HELPERS_SYSTEMVARIABLES_H + +#include "../../ESPEasy_common.h" + +class SystemVariables { +public: + + enum Enum : uint8_t { + // For optmization, keep enums sorted alfabetically by their flash string + BOARD_NAME, + BOOT_CAUSE, + BSSID, + CLIENTIP, + CR, + DNS, + DNS_1, + DNS_2, + ESP_CHIP_CORES, + ESP_CHIP_FREQ, + ESP_CHIP_ID, + ESP_CHIP_MODEL, + ESP_CHIP_REVISION, + +#ifdef USES_ESPEASY_NOW + ESPEASY_NOW_ENABLED, + ESPEASY_NOW_FORCED_CHANNEL, + ESPEASY_NOW_CHANNEL, + ESPEASY_NOW_MQTT, + ESPEASY_NOW_DISTANCE, +#endif + +#if FEATURE_ETHERNET + ETHCONNECTED, + ETHDUPLEX, + ETHSPEED, + ETHSPEEDSTATE, + ETHSTATE, + ETHWIFIMODE, +#endif // if FEATURE_ETHERNET + + FLASH_CHIP_MODEL, + FLASH_CHIP_VENDOR, + FLASH_FREQ, + FLASH_SIZE, + FS_FREE, + FS_SIZE, + GATEWAY, +#if FEATURE_INTERNAL_TEMPERATURE + INTERNAL_TEMPERATURE, +#endif // if FEATURE_INTERNAL_TEMPERATURE + + IP4, + IP, +#if FEATURE_USE_IPV6 + IP6_LOCAL, +#endif + ISVAR_DOUBLE, + ISLIMITED_BUILD, + ISMQTT, + ISMQTTIMP, + ISNTP, + ISWIFI, + LCLTIME, + LCLTIME_AM, + LF, + SUNRISE_M, + SUNSET_M, + MAC, + MAC_INT, + S_LF, + S_CR, + RSSI, + SPACE, + SSID, + SUBNET, + SUNRISE, + SUNRISE_S, + SUNSET, + SUNSET_S, + SYSBUILD_DATE, + SYSBUILD_DESCR, + SYSBUILD_FILENAME, + SYSBUILD_GIT, + SYSBUILD_TIME, + SYSDAY, + SYSDAY_0, + SYSHEAP, + SYSHOUR, + SYSHOUR_0, + SYSLOAD, + SYSMIN, + SYSMIN_0, + SYSMONTH, + SYSMONTH_S, + SYSMONTH_0, + SYSNAME, + SYSSEC, + SYSSEC_0, + SYSSEC_D, + SYSSTACK, + SYSTIME, + SYSTIME_AM, + SYSTIME_AM_0, + SYSTIME_AM_SP, + SYSTM_HM, + SYSTM_HM_0, + SYSTM_HM_AM, + SYSTM_HM_AM_0, + SYSTM_HM_AM_SP, + SYSTM_HM_SP, + SYSTZOFFSET, + #ifndef LIMIT_BUILD_SIZE + SYSTZOFFSET_S, + #endif // ifndef LIMIT_BUILD_SIZE + SYSWEEKDAY, + SYSWEEKDAY_S, + SYSYEAR, + SYSYEARS, + SYSYEAR_0, + UNIT_sysvar, +#if FEATURE_ZEROFILLED_UNITNUMBER + UNIT_0_sysvar, +#endif // FEATURE_ZEROFILLED_UNITNUMBER + UNIXDAY, + UNIXDAY_SEC, + UNIXTIME, + #ifndef LIMIT_BUILD_SIZE + UNIXTIME_LCL, + #endif // ifndef LIMIT_BUILD_SIZE + UPTIME, + UPTIME_MS, + VCC, + VARIABLE, // Can not be the first 'v' variable, as the name is only 1 character long + WI_CH, + + // Keep UNKNOWN as last + UNKNOWN + }; + + // Find the next thing to replace. + // Return UNKNOWN when nothing needs to be replaced. + static SystemVariables::Enum nextReplacementEnum(const String & str, + SystemVariables::Enum last_tested, + int & last_percent_pos); + + static String toString(SystemVariables::Enum enumval); + + static SystemVariables::Enum startIndex_beginWith(const char* beginchar); + static const __FlashStringHelper* toFlashString(SystemVariables::Enum enumval); + + static String getSystemVariable(SystemVariables::Enum enumval); + + static void parseSystemVariables(String& s, + boolean useURLencode); +}; + + +#endif // HELPERS_SYSTEMVARIABLES_H diff --git a/src/src/Helpers/WiFi_AP_CandidatesList.cpp b/src/src/Helpers/WiFi_AP_CandidatesList.cpp index d30d4039b5..e2caf06074 100644 --- a/src/src/Helpers/WiFi_AP_CandidatesList.cpp +++ b/src/src/Helpers/WiFi_AP_CandidatesList.cpp @@ -1,557 +1,560 @@ -#include "../Helpers/WiFi_AP_CandidatesList.h" - -#ifdef ESP32 -#include "../DataStructs/WiFi_AP_Candidates_NVS.h" -#endif - -#include "../ESPEasyCore/ESPEasy_Log.h" -#include "../Globals/ESPEasyWiFiEvent.h" -#include "../Globals/RTC.h" -#include "../Globals/SecuritySettings.h" -#include "../Globals/Settings.h" -#include "../Helpers/Misc.h" -#include "../Helpers/StringConverter.h" - -#if defined(ESP8266) - # include -#endif // if defined(ESP8266) -#if defined(ESP32) - # include -#endif // if defined(ESP32) - -#define WIFI_CUSTOM_DEPLOYMENT_KEY_INDEX 3 -#define WIFI_CUSTOM_SUPPORT_KEY_INDEX 4 -#define WIFI_CREDENTIALS_FALLBACK_SSID_INDEX 5 - -WiFi_AP_CandidatesList::WiFi_AP_CandidatesList() { - known.clear(); - candidates.clear(); - known_it = known.begin(); -} - -WiFi_AP_CandidatesList::~WiFi_AP_CandidatesList() { - candidates.clear(); - known.clear(); - scanned.clear(); - scanned_new.clear(); -} - -void WiFi_AP_CandidatesList::load_knownCredentials() { - if (!_mustLoadCredentials && !known.empty()) { return; } - _mustLoadCredentials = false; - known.clear(); - candidates.clear(); -// attemptsLeft = 1; - _addedKnownCandidate = false; -// addFromRTC(); - - { - // Add the known SSIDs - String ssid; - uint8_t index = 1; // Index 0 is the "unset" value - - bool done = false; - - while (!done) { - if (get_SSID(index, ssid)) { - // Make sure emplace_back is not done on the 2nd heap - # ifdef USE_SECOND_HEAP - HeapSelectDram ephemeral; - # endif // ifdef USE_SECOND_HEAP - - known.emplace_back(index, ssid); - if (SettingsIndexMatchCustomCredentials(index)) { - if (SettingsIndexMatchEmergencyFallback(index)) { - known.back().bits.isEmergencyFallback = true; - } else { - known.back().bits.lowPriority = true; - } - } - ++index; - } else { - if (SettingsIndexMatchCustomCredentials(index)) { - ++index; - } else { - done = true; - } - } - } - } - loadCandidatesFromScanned(); - addFromRTC(); -} - -void WiFi_AP_CandidatesList::clearCache() { - _mustLoadCredentials = true; - known.clear(); - known_it = known.begin(); -} - - -void WiFi_AP_CandidatesList::force_reload() { - clearCache(); - RTC.clearLastWiFi(); // Invalidate the RTC WiFi data. - candidates.clear(); - loadCandidatesFromScanned(); -} - -void WiFi_AP_CandidatesList::begin_sync_scan() { - candidates.clear(); - _addedKnownCandidate = false; -} - -void WiFi_AP_CandidatesList::purge_expired() { - for (auto it = scanned.begin(); it != scanned.end(); ) { - if (it->expired()) { - it = scanned.erase(it); - } else { - ++it; - } - } -} - -#if !FEATURE_ESP8266_DIRECT_WIFI_SCAN -void WiFi_AP_CandidatesList::process_WiFiscan(uint8_t scancount) { - // Append or update found APs from scan. - for (uint8_t i = 0; i < scancount; ++i) { - const WiFi_AP_Candidate tmp(i); - - scanned_new.push_back(tmp); - } - - after_process_WiFiscan(); -} -#endif - -#ifdef ESP8266 -#if FEATURE_ESP8266_DIRECT_WIFI_SCAN -void WiFi_AP_CandidatesList::process_WiFiscan(const bss_info& ap) { - WiFi_AP_Candidate tmp(ap); - scanned_new.push_back(tmp); -} -#endif -#endif - -void WiFi_AP_CandidatesList::after_process_WiFiscan() { - scanned_new.sort(); - scanned_new.unique(); - _mustLoadCredentials = true; - load_knownCredentials(); - WiFi.scanDelete(); - attemptsLeft = 1; -} - -bool WiFi_AP_CandidatesList::getNext(bool scanAllowed) { - load_knownCredentials(); - - if (candidates.empty()) { - if (scanAllowed) { - return false; - } - loadCandidatesFromScanned(); - attemptsLeft = 1; - if (candidates.empty()) { return false; } - } - - currentCandidate = candidates.front(); - bool mustPop = true; - - if (currentCandidate.bits.isHidden) { - // Iterate over the known credentials to try them all - // Hidden SSID stations do not broadcast their SSID, so we must fill it in ourselves. - if (known_it != known.end()) { - currentCandidate.ssid = known_it->ssid; - currentCandidate.index = known_it->index; - ++known_it; - } - - if (known_it != known.end()) { - mustPop = false; - } - } - - if (mustPop) { - if (attemptsLeft == 0) { - if (currentCandidate.bits.isHidden) { - // We tried to connect to hidden SSIDs in 1 run, so pop all hidden candidates. - for (auto cand_it = candidates.begin(); cand_it != candidates.end() && cand_it->bits.isHidden; ) { - cand_it = candidates.erase(cand_it); - } - } else { - if (!candidates.empty()) { - candidates.pop_front(); - } - } - - known_it = known.begin(); - attemptsLeft = 1; - } else { - markAttempt(); - } - } - return currentCandidate.usable(); -} - -const WiFi_AP_Candidate& WiFi_AP_CandidatesList::getCurrent() const { - return currentCandidate; -} - -void WiFi_AP_CandidatesList::markAttempt() { - if (attemptsLeft > 0) attemptsLeft--; -} - -WiFi_AP_Candidate WiFi_AP_CandidatesList::getBestCandidate() const { - for (auto it = candidates.begin(); it != candidates.end(); ++it) { - if (it->rssi < -1) { return *it; } - } - return WiFi_AP_Candidate(); -} - -bool WiFi_AP_CandidatesList::hasCandidateCredentials() { - load_knownCredentials(); - return !known.empty(); -} - -bool WiFi_AP_CandidatesList::hasCandidates() const { - return !candidates.empty(); -} - -void WiFi_AP_CandidatesList::markCurrentConnectionStable() { - clearCache(); - if (currentCandidate.enc_type == 0) { - bool matchfound = false; - for (auto it = candidates.begin(); !matchfound && it != candidates.end(); ++it) { - if (currentCandidate == *it) { - // We may have gotten the enc_type of the active used candidate - // Make sure to store the enc type before clearing the candidates list - currentCandidate.enc_type = it->enc_type; - matchfound = true; - } - } - } - if (currentCandidate.usable()) { - // Store in RTC - RTC.lastWiFiChannel = currentCandidate.channel; - currentCandidate.bssid.get(RTC.lastBSSID); - RTC.lastWiFiSettingsIndex = currentCandidate.index; -#ifdef ESP32 - if (Settings.UseLastWiFiFromRTC()) - WiFi_AP_Candidates_NVS::currentConnection_to_NVS(currentCandidate); - else - WiFi_AP_Candidates_NVS::clear_from_NVS(); -#endif - } - - candidates.clear(); - _addedKnownCandidate = false; - addFromRTC(); // Store the current one from RTC as the first candidate for a reconnect. -} - -int8_t WiFi_AP_CandidatesList::scanComplete() const { - const int8_t scanCompleteStatus = WiFi.scanComplete(); - if (scanCompleteStatus == -1) { - // Still scanning - return scanCompleteStatus; - } - - size_t found = 0; - for (auto scan = scanned.begin(); scan != scanned.end(); ++scan) { - if (!scan->expired()) { - ++found; - } - } - for (auto scan = scanned_new.begin(); scan != scanned_new.end(); ++scan) { - if (!scan->expired()) { - ++found; - } - } - if (found == 0) { - if (scanCompleteStatus == -2) { - // Not triggered - return scanCompleteStatus; - } - } - return found; -} - -bool WiFi_AP_CandidatesList::SettingsIndexMatchCustomCredentials(uint8_t index) -{ - return (WIFI_CUSTOM_DEPLOYMENT_KEY_INDEX == index || - WIFI_CUSTOM_SUPPORT_KEY_INDEX == index || - SettingsIndexMatchEmergencyFallback(index)); -} - -bool WiFi_AP_CandidatesList::SettingsIndexMatchEmergencyFallback(uint8_t index) -{ - return (WIFI_CREDENTIALS_FALLBACK_SSID_INDEX == index); -} - - -void WiFi_AP_CandidatesList::loadCandidatesFromScanned() { - // Make sure list operations are not done on the 2nd heap - # ifdef USE_SECOND_HEAP - HeapSelectDram ephemeral; - # endif // ifdef USE_SECOND_HEAP - - if (scanned_new.size() > 0) { - // We have new scans to process. - purge_expired(); - for (auto scan = scanned_new.begin(); scan != scanned_new.end();) { - #ifndef BUILD_NO_DEBUG - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - addLogMove(LOG_LEVEL_DEBUG, concat(F("WiFi : Scan result: "), scan->toString())); - } - #endif // ifndef BUILD_NO_DEBUG - - // Check to see if it is already present, if so, remove existing one. - for (auto tmp = scanned.begin(); tmp != scanned.end();) { - if (*tmp == *scan) { - tmp = scanned.erase(tmp); - } else { - ++tmp; - } - } - - // We copy instead of move, to make sure it is stored on the 2nd heap. - scanned.push_back(*scan); - scan = scanned_new.erase(scan); - } - scanned.sort(); - scanned.unique(); - } - - if (candidates.size() > 1) { - // Do not mess with the current candidates order if > 1 present - return; - } - // Purge unusable from known list. - for (auto it = known.begin(); it != known.end();) { - if (it->usable()) { - ++it; - } else { - it = known.erase(it); - } - } - known.sort(); - known.unique(); - known_it = known.begin(); - - for (auto scan = scanned.begin(); scan != scanned.end();) { - if (scan->expired()) { - scan = scanned.erase(scan); - } else { - if (scan->bits.isHidden) { - if (Settings.IncludeHiddenSSID()) { - if (SecuritySettings.hasWiFiCredentials()) { - candidates.push_back(*scan); - } - } - } else if (scan->ssid.length() > 0) { - for (auto kn_it = known.begin(); kn_it != known.end(); ++kn_it) { - if (scan->ssid.equals(kn_it->ssid)) { - WiFi_AP_Candidate tmp = *scan; - tmp.index = kn_it->index; - tmp.bits.lowPriority = kn_it->bits.lowPriority; - tmp.bits.isEmergencyFallback = kn_it->bits.isEmergencyFallback; - - if (tmp.usable()) { - candidates.push_back(tmp); - _addedKnownCandidate = true; - - // Check all knowns as we may have several AP's with the same SSID and different passwords. - } - } - } - } - ++scan; - } - } - # ifndef BUILD_NO_DEBUG - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - const WiFi_AP_Candidate bestCandidate = getBestCandidate(); - if (bestCandidate.usable()) { - addLogMove(LOG_LEVEL_INFO, concat(F("WiFi : Best AP candidate: "), bestCandidate.toString())); - } - } - #endif - candidates.sort(); - candidates.unique(); - addFromRTC(); - purge_unusable(); -} - -void WiFi_AP_CandidatesList::addFromRTC() { - if (!Settings.UseLastWiFiFromRTC()) return; - if (!RTC.lastWiFi_set()) { - #ifdef ESP32 - // Try to load from NVS and store in RTC - WiFi_AP_Candidate fromNVS; - if (WiFi_AP_Candidates_NVS::loadCandidate_from_NVS(fromNVS)) { - RTC.lastWiFiChannel = currentCandidate.channel; - currentCandidate.bssid.get(RTC.lastBSSID); - RTC.lastWiFiSettingsIndex = currentCandidate.index; - } else { - return; - } - #else - return; - #endif - } - - if (SettingsIndexMatchCustomCredentials(RTC.lastWiFiSettingsIndex)) - { - return; - } - - String ssid; - - if (!get_SSID(RTC.lastWiFiSettingsIndex, ssid)) { - return; - } - - WiFi_AP_Candidate fromRTC(RTC.lastWiFiSettingsIndex, ssid); - fromRTC.bssid = RTC.lastBSSID; - fromRTC.channel = RTC.lastWiFiChannel; - - if (!fromRTC.usable()) { - return; - } - - if (candidates.size() > 0 && candidates.front().ssid.equals(fromRTC.ssid)) { - // Front candidate was already from RTC. - candidates.pop_front(); - } - - // See if we may have a better candidate for the current network, with a significant better RSSI. - auto bestMatch = candidates.end(); - auto lastUsed = bestMatch; - for (auto it = candidates.begin(); lastUsed == candidates.end() && it != candidates.end(); ++it) { - if (it->usable() && it->ssid.equals(fromRTC.ssid)) { - const bool foundLastUsed = fromRTC.bssid_match(it->bssid); - if (foundLastUsed) { - lastUsed = it; - } else if (bestMatch == candidates.end()) { - bestMatch = it; - } - } - } - bool matchAdded = false; - if (bestMatch != candidates.end()) { - // Found a best match, possibly better than the last used. - if (lastUsed == candidates.end() || (bestMatch->rssi > (lastUsed->rssi + 10))) { - // Last used was not found or - // Other candidate has significant better RSSI - matchAdded = true; - candidates.push_front(*bestMatch); - } - } else if (lastUsed != candidates.end()) { - matchAdded = true; - candidates.push_front(*lastUsed); - } - if (!matchAdded) { - candidates.push_front(fromRTC); - // This is not taken from a scan, so no idea of the used encryption. - // Try to find a matching BSSID to get the encryption. - for (auto it = candidates.begin(); it != candidates.end(); ++it) { - if ((it->rssi != -1) && candidates.front() == *it) { - candidates.front().enc_type = it->enc_type; - return; - } - } - } - - candidates.front().rssi = -1; // Set to best possible RSSI so it is tried first. - - if (!candidates.front().usable() || !candidates.front().allowQuickConnect()) { - candidates.pop_front(); - return; - } - - if (currentCandidate == candidates.front()) { - candidates.front().enc_type = currentCandidate.enc_type; - } -} - -void WiFi_AP_CandidatesList::purge_unusable() { - for (auto it = candidates.begin(); it != candidates.end();) { - if (it->usable()) { - ++it; - } else { - it = candidates.erase(it); - } - } - if (candidates.size() > 1) { - candidates.sort(); - candidates.unique(); - } -} - -bool WiFi_AP_CandidatesList::get_SSID_key(uint8_t index, String& ssid, String& key) { - switch (index) { - case 1: - ssid = SecuritySettings.WifiSSID; - key = SecuritySettings.WifiKey; - break; - case 2: - ssid = SecuritySettings.WifiSSID2; - key = SecuritySettings.WifiKey2; - break; - case WIFI_CUSTOM_DEPLOYMENT_KEY_INDEX: - #if !defined(CUSTOM_DEPLOYMENT_SSID) || !defined(CUSTOM_DEPLOYMENT_KEY) - return false; - #else - ssid = F(CUSTOM_DEPLOYMENT_SSID); - key = F(CUSTOM_DEPLOYMENT_KEY); - #endif - break; - case WIFI_CUSTOM_SUPPORT_KEY_INDEX: - #if !defined(CUSTOM_SUPPORT_SSID) || !defined(CUSTOM_SUPPORT_KEY) - return false; - #else - ssid = F(CUSTOM_SUPPORT_SSID); - key = F(CUSTOM_SUPPORT_KEY); - #endif - break; - case WIFI_CREDENTIALS_FALLBACK_SSID_INDEX: - { - #if !defined(CUSTOM_EMERGENCY_FALLBACK_SSID) || !defined(CUSTOM_EMERGENCY_FALLBACK_KEY) - return false; - #else - int allowedUptimeMinutes = 10; - #ifdef CUSTOM_EMERGENCY_FALLBACK_ALLOW_MINUTES_UPTIME - allowedUptimeMinutes = CUSTOM_EMERGENCY_FALLBACK_ALLOW_MINUTES_UPTIME; - #endif - if (getUptimeMinutes() < allowedUptimeMinutes && SecuritySettings.hasWiFiCredentials()) { - ssid = F(CUSTOM_EMERGENCY_FALLBACK_SSID); - key = F(CUSTOM_EMERGENCY_FALLBACK_KEY); - } else { - return false; - } - #endif - break; - } - default: - return false; - } - - // TODO TD-er: Read other credentials from extra file. - - - - // Spaces are allowed in both SSID and pass phrase, so make sure to not trim the ssid and key. - return true; -} - -bool WiFi_AP_CandidatesList::get_SSID(uint8_t index, String& ssid) -{ - String key; - return get_SSID_key(index, ssid, key); -} - -String WiFi_AP_CandidatesList::get_key(uint8_t index) -{ - String ssid, key; - if (get_SSID_key(index, ssid, key)) - return key; - return EMPTY_STRING; +#include "../Helpers/WiFi_AP_CandidatesList.h" + +#ifdef ESP32 +#include "../DataStructs/WiFi_AP_Candidates_NVS.h" +#endif + +#include "../ESPEasyCore/ESPEasy_Log.h" +#include "../Globals/ESPEasyWiFiEvent.h" +#include "../Globals/RTC.h" +#include "../Globals/SecuritySettings.h" +#include "../Globals/Settings.h" +#include "../Helpers/Misc.h" +#include "../Helpers/StringConverter.h" + +#if defined(ESP8266) + # include +#endif // if defined(ESP8266) +#if defined(ESP32) + # include +#endif // if defined(ESP32) + +#define WIFI_CUSTOM_DEPLOYMENT_KEY_INDEX 3 +#define WIFI_CUSTOM_SUPPORT_KEY_INDEX 4 +#define WIFI_CREDENTIALS_FALLBACK_SSID_INDEX 5 + + +#ifdef USES_ESPEASY_NOW +#define ESPEASY_NOW_TMP_SSID "ESPEASY_NOW" +#define ESPEASY_NOW_TMP_PASSPHRASE "random_passphrase" +#endif + +WiFi_AP_CandidatesList::WiFi_AP_CandidatesList() { + known.clear(); + candidates.clear(); + known_it = known.begin(); +} + +WiFi_AP_CandidatesList::~WiFi_AP_CandidatesList() { + candidates.clear(); + known.clear(); + scanned.clear(); + scanned_new.clear(); +} + +void WiFi_AP_CandidatesList::load_knownCredentials() { + if (!_mustLoadCredentials && !known.empty()) { return; } + _mustLoadCredentials = false; + known.clear(); + candidates.clear(); +// attemptsLeft = 1; + _addedKnownCandidate = false; +// addFromRTC(); + + { + // Add the known SSIDs + String ssid; + uint8_t index = 1; // Index 0 is the "unset" value + + bool done = false; + + while (!done) { + if (get_SSID(index, ssid)) { + // Make sure emplace_back is not done on the 2nd heap + # ifdef USE_SECOND_HEAP + HeapSelectDram ephemeral; + # endif // ifdef USE_SECOND_HEAP + + known.emplace_back(index, ssid); + if (SettingsIndexMatchCustomCredentials(index)) { + if (SettingsIndexMatchEmergencyFallback(index)) { + known.back().bits.isEmergencyFallback = true; + } else { + known.back().bits.lowPriority = true; + } + } + ++index; + } else { + if (SettingsIndexMatchCustomCredentials(index)) { + ++index; + } else { + done = true; + } + } + } + } + loadCandidatesFromScanned(); + addFromRTC(); +} + +void WiFi_AP_CandidatesList::clearCache() { + _mustLoadCredentials = true; + known.clear(); + known_it = known.begin(); +} + + +void WiFi_AP_CandidatesList::force_reload() { + clearCache(); + RTC.clearLastWiFi(); // Invalidate the RTC WiFi data. + candidates.clear(); + loadCandidatesFromScanned(); +} + +void WiFi_AP_CandidatesList::begin_sync_scan() { + candidates.clear(); + _addedKnownCandidate = false; +} + +void WiFi_AP_CandidatesList::purge_expired() { + for (auto it = scanned.begin(); it != scanned.end(); ) { + if (it->expired()) { + it = scanned.erase(it); + } else { + ++it; + } + } +} + +#if !FEATURE_ESP8266_DIRECT_WIFI_SCAN +void WiFi_AP_CandidatesList::process_WiFiscan(uint8_t scancount) { + // Append or update found APs from scan. + for (uint8_t i = 0; i < scancount; ++i) { + const WiFi_AP_Candidate tmp(i); + + scanned_new.push_back(tmp); + } + + after_process_WiFiscan(); +} +#endif + +#ifdef ESP8266 +#if FEATURE_ESP8266_DIRECT_WIFI_SCAN +void WiFi_AP_CandidatesList::process_WiFiscan(const bss_info& ap) { + WiFi_AP_Candidate tmp(ap); + scanned_new.push_back(tmp); +} +#endif +#endif + +void WiFi_AP_CandidatesList::after_process_WiFiscan() { + scanned_new.sort(); + scanned_new.unique(); + _mustLoadCredentials = true; + load_knownCredentials(); + WiFi.scanDelete(); + attemptsLeft = 1; +} + +bool WiFi_AP_CandidatesList::getNext(bool scanAllowed) { + load_knownCredentials(); + + if (candidates.empty()) { + if (scanAllowed) { + return false; + } + loadCandidatesFromScanned(); + attemptsLeft = 1; + if (candidates.empty()) { return false; } + } + + currentCandidate = candidates.front(); + bool mustPop = true; + + if (currentCandidate.bits.isHidden) { + // Iterate over the known credentials to try them all + // Hidden SSID stations do not broadcast their SSID, so we must fill it in ourselves. + if (known_it != known.end()) { + currentCandidate.ssid = known_it->ssid; + currentCandidate.index = known_it->index; + ++known_it; + } + + if (known_it != known.end()) { + mustPop = false; + } + } + + if (mustPop) { + if (attemptsLeft == 0) { + if (currentCandidate.bits.isHidden) { + // We tried to connect to hidden SSIDs in 1 run, so pop all hidden candidates. + for (auto cand_it = candidates.begin(); cand_it != candidates.end() && cand_it->bits.isHidden; ) { + cand_it = candidates.erase(cand_it); + } + } else { + if (!candidates.empty()) { + candidates.pop_front(); + } + } + + known_it = known.begin(); + attemptsLeft = 1; + } else { + markAttempt(); + } + } + return currentCandidate.usable(); +} + +const WiFi_AP_Candidate& WiFi_AP_CandidatesList::getCurrent() const { + return currentCandidate; +} + +void WiFi_AP_CandidatesList::markAttempt() { + if (attemptsLeft > 0) attemptsLeft--; +} + +WiFi_AP_Candidate WiFi_AP_CandidatesList::getBestCandidate() const { + for (auto it = candidates.begin(); it != candidates.end(); ++it) { + if (it->rssi < -1) { return *it; } + } + return WiFi_AP_Candidate(); +} + +bool WiFi_AP_CandidatesList::hasCandidateCredentials() { + load_knownCredentials(); + return !known.empty(); +} + +bool WiFi_AP_CandidatesList::hasCandidates() const { + return !candidates.empty(); +} + +void WiFi_AP_CandidatesList::markCurrentConnectionStable() { + clearCache(); + if (currentCandidate.enc_type == 0) { + bool matchfound = false; + for (auto it = candidates.begin(); !matchfound && it != candidates.end(); ++it) { + if (currentCandidate == *it) { + // We may have gotten the enc_type of the active used candidate + // Make sure to store the enc type before clearing the candidates list + currentCandidate.enc_type = it->enc_type; + matchfound = true; + } + } + } + if (currentCandidate.usable()) { + // Store in RTC + RTC.lastWiFiChannel = currentCandidate.channel; + currentCandidate.bssid.get(RTC.lastBSSID); + RTC.lastWiFiSettingsIndex = currentCandidate.index; +#ifdef ESP32 + if (Settings.UseLastWiFiFromRTC()) + WiFi_AP_Candidates_NVS::currentConnection_to_NVS(currentCandidate); + else + WiFi_AP_Candidates_NVS::clear_from_NVS(); +#endif + } + + candidates.clear(); + _addedKnownCandidate = false; + addFromRTC(); // Store the current one from RTC as the first candidate for a reconnect. +} + +int8_t WiFi_AP_CandidatesList::scanComplete() const { + const int8_t scanCompleteStatus = WiFi.scanComplete(); + if (scanCompleteStatus == -1) { + // Still scanning + return scanCompleteStatus; + } + + size_t found = 0; + for (auto scan = scanned.begin(); scan != scanned.end(); ++scan) { + if (!scan->expired()) { + ++found; + } + } + for (auto scan = scanned_new.begin(); scan != scanned_new.end(); ++scan) { + if (!scan->expired()) { + ++found; + } + } + if (found == 0) { + if (scanCompleteStatus == -2) { + // Not triggered + return scanCompleteStatus; + } + } + return found; +} + +bool WiFi_AP_CandidatesList::SettingsIndexMatchCustomCredentials(uint8_t index) +{ + return (WIFI_CUSTOM_DEPLOYMENT_KEY_INDEX == index || + WIFI_CUSTOM_SUPPORT_KEY_INDEX == index || + SettingsIndexMatchEmergencyFallback(index)); +} + +bool WiFi_AP_CandidatesList::SettingsIndexMatchEmergencyFallback(uint8_t index) +{ + return (WIFI_CREDENTIALS_FALLBACK_SSID_INDEX == index); +} + +void WiFi_AP_CandidatesList::loadCandidatesFromScanned() { + // Make sure list operations are not done on the 2nd heap + # ifdef USE_SECOND_HEAP + HeapSelectDram ephemeral; + # endif // ifdef USE_SECOND_HEAP + + if (scanned_new.size() > 0) { + // We have new scans to process. + purge_expired(); + for (auto scan = scanned_new.begin(); scan != scanned_new.end();) { + #ifndef BUILD_NO_DEBUG + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + addLogMove(LOG_LEVEL_DEBUG, concat(F("WiFi : Scan result: "), scan->toString())); + } + #endif // ifndef BUILD_NO_DEBUG + + // Check to see if it is already present, if so, remove existing one. + for (auto tmp = scanned.begin(); tmp != scanned.end();) { + if (*tmp == *scan) { + tmp = scanned.erase(tmp); + } else { + ++tmp; + } + } + + // We copy instead of move, to make sure it is stored on the 2nd heap. + scanned.push_back(*scan); + scan = scanned_new.erase(scan); + } + scanned.sort(); + scanned.unique(); + } + + if (candidates.size() > 1) { + // Do not mess with the current candidates order if > 1 present + return; + } + // Purge unusable from known list. + for (auto it = known.begin(); it != known.end();) { + if (it->usable()) { + ++it; + } else { + it = known.erase(it); + } + } + known.sort(); + known.unique(); + known_it = known.begin(); + + for (auto scan = scanned.begin(); scan != scanned.end();) { + if (scan->expired()) { + scan = scanned.erase(scan); + } else { + if (scan->bits.isHidden) { + if (Settings.IncludeHiddenSSID()) { + if (SecuritySettings.hasWiFiCredentials()) { + candidates.push_back(*scan); + } + } + } else if (scan->ssid.length() > 0) { + for (auto kn_it = known.begin(); kn_it != known.end(); ++kn_it) { + if (scan->ssid.equals(kn_it->ssid)) { + WiFi_AP_Candidate tmp = *scan; + tmp.index = kn_it->index; + tmp.bits.lowPriority = kn_it->bits.lowPriority; + tmp.bits.isEmergencyFallback = kn_it->bits.isEmergencyFallback; + + if (tmp.usable()) { + candidates.push_back(tmp); + _addedKnownCandidate = true; + + // Check all knowns as we may have several AP's with the same SSID and different passwords. + } + } + } + } + ++scan; + } + } + # ifndef BUILD_NO_DEBUG + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + const WiFi_AP_Candidate bestCandidate = getBestCandidate(); + if (bestCandidate.usable()) { + addLogMove(LOG_LEVEL_INFO, concat(F("WiFi : Best AP candidate: "), bestCandidate.toString())); + } + } + #endif + candidates.sort(); + candidates.unique(); + addFromRTC(); + purge_unusable(); +} + +void WiFi_AP_CandidatesList::addFromRTC() { + if (!Settings.UseLastWiFiFromRTC()) return; + if (!RTC.lastWiFi_set()) { + #ifdef ESP32 + // Try to load from NVS and store in RTC + WiFi_AP_Candidate fromNVS; + if (WiFi_AP_Candidates_NVS::loadCandidate_from_NVS(fromNVS)) { + RTC.lastWiFiChannel = currentCandidate.channel; + currentCandidate.bssid.get(RTC.lastBSSID); + RTC.lastWiFiSettingsIndex = currentCandidate.index; + } else { + return; + } + #else + return; + #endif + } + + if (SettingsIndexMatchCustomCredentials(RTC.lastWiFiSettingsIndex)) + { + return; + } + + String ssid; + + if (!get_SSID(RTC.lastWiFiSettingsIndex, ssid)) { + return; + } + + WiFi_AP_Candidate fromRTC(RTC.lastWiFiSettingsIndex, ssid); + fromRTC.bssid = RTC.lastBSSID; + fromRTC.channel = RTC.lastWiFiChannel; + + if (!fromRTC.usable()) { + return; + } + + if (candidates.size() > 0 && candidates.front().ssid.equals(fromRTC.ssid)) { + // Front candidate was already from RTC. + candidates.pop_front(); + } + + // See if we may have a better candidate for the current network, with a significant better RSSI. + auto bestMatch = candidates.end(); + auto lastUsed = bestMatch; + for (auto it = candidates.begin(); lastUsed == candidates.end() && it != candidates.end(); ++it) { + if (it->usable() && it->ssid.equals(fromRTC.ssid)) { + const bool foundLastUsed = fromRTC.bssid_match(it->bssid); + if (foundLastUsed) { + lastUsed = it; + } else if (bestMatch == candidates.end()) { + bestMatch = it; + } + } + } + bool matchAdded = false; + if (bestMatch != candidates.end()) { + // Found a best match, possibly better than the last used. + if (lastUsed == candidates.end() || (bestMatch->rssi > (lastUsed->rssi + 10))) { + // Last used was not found or + // Other candidate has significant better RSSI + matchAdded = true; + candidates.push_front(*bestMatch); + } + } else if (lastUsed != candidates.end()) { + matchAdded = true; + candidates.push_front(*lastUsed); + } + if (!matchAdded) { + candidates.push_front(fromRTC); + // This is not taken from a scan, so no idea of the used encryption. + // Try to find a matching BSSID to get the encryption. + for (auto it = candidates.begin(); it != candidates.end(); ++it) { + if ((it->rssi != -1) && candidates.front() == *it) { + candidates.front().enc_type = it->enc_type; + return; + } + } + } + + candidates.front().rssi = -1; // Set to best possible RSSI so it is tried first. + + if (!candidates.front().usable() || !candidates.front().allowQuickConnect()) { + candidates.pop_front(); + return; + } + + if (currentCandidate == candidates.front()) { + candidates.front().enc_type = currentCandidate.enc_type; + } +} + +void WiFi_AP_CandidatesList::purge_unusable() { + for (auto it = candidates.begin(); it != candidates.end();) { + if (it->usable()) { + ++it; + } else { + it = candidates.erase(it); + } + } + if (candidates.size() > 1) { + candidates.sort(); + candidates.unique(); + } +} + +bool WiFi_AP_CandidatesList::get_SSID_key(uint8_t index, String& ssid, String& key) { + switch (index) { + case 1: + ssid = SecuritySettings.WifiSSID; + key = SecuritySettings.WifiKey; + break; + case 2: + ssid = SecuritySettings.WifiSSID2; + key = SecuritySettings.WifiKey2; + break; + case WIFI_CUSTOM_DEPLOYMENT_KEY_INDEX: + #if !defined(CUSTOM_DEPLOYMENT_SSID) || !defined(CUSTOM_DEPLOYMENT_KEY) + return false; + #else + ssid = F(CUSTOM_DEPLOYMENT_SSID); + key = F(CUSTOM_DEPLOYMENT_KEY); + #endif + break; + case WIFI_CUSTOM_SUPPORT_KEY_INDEX: + #if !defined(CUSTOM_SUPPORT_SSID) || !defined(CUSTOM_SUPPORT_KEY) + return false; + #else + ssid = F(CUSTOM_SUPPORT_SSID); + key = F(CUSTOM_SUPPORT_KEY); + #endif + break; + case WIFI_CREDENTIALS_FALLBACK_SSID_INDEX: + { + #if !defined(CUSTOM_EMERGENCY_FALLBACK_SSID) || !defined(CUSTOM_EMERGENCY_FALLBACK_KEY) + return false; + #else + int allowedUptimeMinutes = 10; + #ifdef CUSTOM_EMERGENCY_FALLBACK_ALLOW_MINUTES_UPTIME + allowedUptimeMinutes = CUSTOM_EMERGENCY_FALLBACK_ALLOW_MINUTES_UPTIME; + #endif + if (getUptimeMinutes() < allowedUptimeMinutes && SecuritySettings.hasWiFiCredentials()) { + ssid = F(CUSTOM_EMERGENCY_FALLBACK_SSID); + key = F(CUSTOM_EMERGENCY_FALLBACK_KEY); + } else { + return false; + } + #endif + break; + } + default: + return false; + } + + // TODO TD-er: Read other credentials from extra file. + + // Spaces are allowed in both SSID and pass phrase, so make sure to not trim the ssid and key. + return true; +} + +bool WiFi_AP_CandidatesList::get_SSID(uint8_t index, String& ssid) +{ + String key; + return get_SSID_key(index, ssid, key); +} + +String WiFi_AP_CandidatesList::get_key(uint8_t index) +{ + String ssid, key; + if (get_SSID_key(index, ssid, key)) + return key; + return EMPTY_STRING; } \ No newline at end of file diff --git a/src/src/Helpers/_CPlugin_Helper_webform.cpp b/src/src/Helpers/_CPlugin_Helper_webform.cpp index 249456a1b1..bcf31cbc0b 100644 --- a/src/src/Helpers/_CPlugin_Helper_webform.cpp +++ b/src/src/Helpers/_CPlugin_Helper_webform.cpp @@ -40,6 +40,9 @@ const __FlashStringHelper* toString(ControllerSettingsStruct::VarType parameterI case ControllerSettingsStruct::CONTROLLER_ALLOW_EXPIRE: return F("Allow Expire"); case ControllerSettingsStruct::CONTROLLER_DEDUPLICATE: return F("De-duplicate"); case ControllerSettingsStruct::CONTROLLER_USE_LOCAL_SYSTEM_TIME: return F("Use Local System Time"); +#ifdef USES_ESPEASY_NOW + case ControllerSettingsStruct::CONTROLLER_ENABLE_ESPEASY_NOW_FALLBACK: return F("Enable " ESPEASY_NOW_NAME " Fallback"); +#endif case ControllerSettingsStruct::CONTROLLER_CHECK_REPLY: return F("Check Reply"); @@ -412,6 +415,11 @@ void addControllerParameterForm(const ControllerSettingsStruct & ControllerSett case ControllerSettingsStruct::CONTROLLER_SAMPLE_SET_INITIATOR: addTaskSelectBox(displayName, internalName, ControllerSettings.SampleSetInitiator); break; +#ifdef USES_ESPEASY_NOW + case ControllerSettingsStruct::CONTROLLER_ENABLE_ESPEASY_NOW_FALLBACK: + addFormCheckBox(displayName, internalName, ControllerSettings.enableESPEasyNowFallback()); + break; +#endif case ControllerSettingsStruct::CONTROLLER_ENABLED: addFormCheckBox(displayName, internalName, Settings.ControllerEnabled[controllerindex]); break; @@ -602,6 +610,11 @@ void saveControllerParameterForm(ControllerSettingsStruct & ControllerSet case ControllerSettingsStruct::CONTROLLER_SAMPLE_SET_INITIATOR: ControllerSettings.SampleSetInitiator = getFormItemInt(internalName, ControllerSettings.SampleSetInitiator); break; +#ifdef USES_ESPEASY_NOW + case ControllerSettingsStruct::CONTROLLER_ENABLE_ESPEASY_NOW_FALLBACK: + ControllerSettings.enableESPEasyNowFallback(isFormItemChecked(internalName)); + break; +#endif case ControllerSettingsStruct::CONTROLLER_ENABLED: Settings.ControllerEnabled[controllerindex] = isFormItemChecked(internalName); break; diff --git a/src/src/PluginStructs/P094_Filter.cpp b/src/src/PluginStructs/P094_Filter.cpp index f922ce81cf..ec33cdce4b 100644 --- a/src/src/PluginStructs/P094_Filter.cpp +++ b/src/src/PluginStructs/P094_Filter.cpp @@ -1,461 +1,461 @@ -#include "../PluginStructs/P094_Filter.h" - -#ifdef USES_P094 - - -# include "../DataStructs/mBusPacket.h" - -# include "../Globals/ESPEasy_time.h" -# include "../Globals/TimeZone.h" -# include "../Helpers/ESPEasy_Storage.h" -# include "../Helpers/StringConverter.h" - - -// *INDENT-OFF* -# define P094_FILTER_WEBARG_LABEL(x) getPluginCustomArgName((x * 10) + 10) -# define P094_FILTER_WEBARG_MANUFACTURER(x) getPluginCustomArgName((x * 10) + 11) -# define P094_FILTER_WEBARG_METERTYPE(x) getPluginCustomArgName((x * 10) + 12) -# define P094_FILTER_WEBARG_SERIAL(x) getPluginCustomArgName((x * 10) + 13) -# define P094_FILTER_WEBARG_FILTER_WINDOW(x) getPluginCustomArgName((x * 10) + 14) -// *INDENT-ON* - -const char P094_Filter_Window_names[] PROGMEM = "none|all|1m|5m|15m|1h|day|month|once"; - -P094_Filter_Window get_FilterWindow(const String& str) -{ - char tmp[10]{}; - const int command_i = GetCommandCode(tmp, sizeof(tmp), str.c_str(), P094_Filter_Window_names); - - if (command_i == -1) { - // No match found - return P094_Filter_Window::None; - } - return static_cast(command_i); -} - -String Filter_WindowToString(P094_Filter_Window filterWindow) -{ - char tmp[10]{}; - String res(GetTextIndexed(tmp, sizeof(tmp), static_cast(filterWindow), P094_Filter_Window_names)); - - return res; -} - -P094_filter::P094_filter() { - _filter._manufacturer = mBus_packet_wildcard_manufacturer; - _filter._meterType = mBus_packet_wildcard_metertype; - _filter._serialNr = mBus_packet_wildcard_serial; - _filter._filterWindow = static_cast(P094_Filter_Window::None); -} - -void P094_filter::fromString(String str) -{ - // Set everything to wildcards - _filter._manufacturer = mBus_packet_wildcard_manufacturer; - _filter._meterType = mBus_packet_wildcard_metertype; - _filter._serialNr = mBus_packet_wildcard_serial; - _filter._filterWindow = static_cast(P094_Filter_Window::None); - - const int semicolonPos = str.indexOf(';'); - - if (semicolonPos != -1) { - _filter._filterWindow = static_cast(get_FilterWindow(str.substring(semicolonPos + 1))); - str = str.substring(0, semicolonPos); - } - - for (size_t i = 0; i < 3; ++i) { - String tmp; - - if (GetArgv(str.c_str(), tmp, (i + 1), '.')) { - if (!(tmp.isEmpty() || tmp.startsWith(F("*")))) { - if (i != 0) { - // Make sure the numerical values are parsed as HEX - if (!tmp.startsWith(F("0x")) && !tmp.startsWith(F("0X"))) { - tmp = concat(F("0x"), tmp); - } - } - - switch (i) { - case 0: // Manufacturer - _filter._manufacturer = mBusPacket_header_t::encodeManufacturerID(tmp); - break; - case 1: // Meter type - { - int32_t metertype = mBus_packet_wildcard_metertype; - - if (validIntFromString(tmp, metertype)) { - _filter._meterType = metertype; - } - break; - } - case 2: // Serial - { - int32_t serial = mBus_packet_wildcard_serial; - - if (validIntFromString(tmp, serial)) { - _filter._serialNr = serial; - } - break; - } - } - } - } - } -} - -String P094_filter::toString() const -{ - String res; - - res += getManufacturer(); - res += '.'; - - res += getMeterType(); - res += '.'; - - res += getSerial(); - res += ';'; - - res += Filter_WindowToString(getFilterWindow()); - - return res; -} - -const uint8_t * P094_filter::toBinary(size_t& size) const -{ - size = getBinarySize(); - return (uint8_t *)this; -} - -size_t P094_filter::fromBinary(const uint8_t *data) -{ - memcpy(this, data, getBinarySize()); - return getBinarySize(); -} - -bool P094_filter::isValid() const -{ - if ((_filter._manufacturer == 0) && - (_filter._meterType == 0) && - (_filter._serialNr == 0) && - (getFilterWindow() == P094_Filter_Window::None)) { - return false; - } - return - !isWildcardManufacturer() || - !isWildcardMeterType() || - !isWildcardSerial() || - getFilterWindow() != P094_Filter_Window::None; -} - -bool P094_filter::operator<(const P094_filter& rhs) const -{ - if (isValid() != rhs.isValid()) { - return isValid(); - } -/* - // Disable sorting, only sort by having valid filters at top. - if (isWildcardManufacturer() != rhs.isWildcardManufacturer()) { - return rhs.isWildcardManufacturer(); - } - - if (isWildcardMeterType() != rhs.isWildcardMeterType()) { - return rhs.isWildcardMeterType(); - } - - if (isWildcardSerial() != rhs.isWildcardSerial()) { - return rhs.isWildcardSerial(); - } - - if (!isWildcardManufacturer() && (_filter._manufacturer != rhs._filter._manufacturer)) { - return _filter._manufacturer < rhs._filter._manufacturer; - } - - if (!isWildcardMeterType() && (_filter._meterType != rhs._filter._meterType)) { - return _filter._meterType < rhs._filter._meterType; - } - - if (!isWildcardSerial() && (_filter._serialNr != rhs._filter._serialNr)) { - return _filter._serialNr < rhs._filter._serialNr; - } -*/ - return false; -} - -bool P094_filter::operator==(const P094_filter& rhs) const -{ - return equals(*this, rhs); -} - -bool P094_filter::operator!=(const P094_filter& rhs) const -{ - return !equals(*this, rhs); -} - -bool P094_filter::equals(const P094_filter& lhs, const P094_filter& rhs) -{ - if (!lhs.isValid() && !rhs.isValid()) { return true; } - - return lhs.toString() == rhs.toString(); -} - -size_t P094_filter::getBinarySize() -{ - // Only store the filter - constexpr size_t P094_filter_size = sizeof(_filter); - - return P094_filter_size; -} - -bool P094_filter::matches(const mBusPacket_header_t& other) const -{ - if (!isWildcardManufacturer()) { - if (_filter._manufacturer != other._manufacturer) { return false; } - } - - if (!isWildcardMeterType()) { - if (_filter._meterType != other._meterType) { return false; } - } - - if (!isWildcardSerial()) { - if (_filter._serialNr != other._serialNr) { return false; } - } - - return true; -} - -unsigned long P094_filter::computeUnixTimeExpiration() const -{ - // Match the interval window. - const P094_Filter_Window filterWindow = getFilterWindow(); - - if ((filterWindow == P094_Filter_Window::None) || - (filterWindow == P094_Filter_Window::Once)) { - // Return date infinitely far in the future - return 0xFFFFFFFF; - } - - if (filterWindow == P094_Filter_Window::All) { - // Return timestamp in the past - return 0; - } - - // Using UnixTime - const unsigned long currentTime = node_time.getUnixTime(); - unsigned long window_max = currentTime; - - if ((filterWindow == P094_Filter_Window::One_hour) || - (filterWindow == P094_Filter_Window::Day) || - (filterWindow == P094_Filter_Window::Month)) - { - // Create time struct in local time. - struct tm tm_max; - breakTime(time_zone.toLocal(currentTime), tm_max); - tm_max.tm_sec = 59; - tm_max.tm_min = 59; - - if (filterWindow == P094_Filter_Window::Day) { - // Using local time, thus incl. timezone and DST. - if (tm_max.tm_hour < 23) { - // Either: - // - between 00:00 and 12:00 => Max: 11:59:59 - // - between 12:00 and 23:00 => Max: 22:59:59 - - tm_max.tm_hour = (tm_max.tm_hour < 12) ? 11 : 22; - } else { - // between 23:00 and 00:00 => Max: 23:59:59 - tm_max.tm_hour = 23; - } - } else if (filterWindow == P094_Filter_Window::Month) { - // First set minute to midnight of today => Max: 23:59:59 - tm_max.tm_hour = 23; - - if (tm_max.tm_mday < 15) { - // - between 1st of month 00:00:00 and 15th of month 00:00:00 - tm_max.tm_mday = 14; - } else { - // Check if this is the last day of the month. - // Add 24h to the time and see if it is still the same month. - const uint8_t maxMonthDay = getMonthDays(tm_max); - - if (tm_max.tm_mday < maxMonthDay) { - // - between 15th of month 00:00:00 and last of month 00:00:00 - // So we must subtract one day. - tm_max.tm_mday = maxMonthDay - 1; - } else { - // - between last of month 00:00:00 and 1st of next month 00:00:00 - // Thus do not change the date as it is already at the last day of the month - } - } - } - - // Convert from local time. - window_max = time_zone.fromLocal(makeTime(tm_max)); - } else { - switch (filterWindow) { - case P094_Filter_Window::One_minute: - window_max = currentTime - (currentTime % (1 * 60)) + (1 * 60 - 1); - break; - case P094_Filter_Window::Five_minutes: - window_max = currentTime - (currentTime % (5 * 60)) + (5 * 60 - 1); - break; - case P094_Filter_Window::Fifteen_minutes: - window_max = currentTime - (currentTime % (15 * 60)) + (15 * 60 - 1); - break; - - default: - break; - } - } - return window_max; -} - -void P094_filter::WebformLoad(uint8_t filterIndex) const -{ - addRowLabel_tr_id( - concat(F("Filter "), static_cast(filterIndex + 1)), - P094_FILTER_WEBARG_LABEL(filterIndex)); - - // Manufacturer - addTextBox( - P094_FILTER_WEBARG_MANUFACTURER(filterIndex), - getManufacturer(), - 3, false, false, EMPTY_STRING, F("widenumber") -# if FEATURE_TOOLTIPS - , F("Manufacturer") -# endif // if FEATURE_TOOLTIPS - ); - - // Meter Type - addTextBox( - P094_FILTER_WEBARG_METERTYPE(filterIndex), - getMeterType(), - 4, false, false, EMPTY_STRING, F("widenumber") -# if FEATURE_TOOLTIPS - , F("Meter Type (HEX)") -# endif // if FEATURE_TOOLTIPS - ); - - // Serial nr - addTextBox( - P094_FILTER_WEBARG_SERIAL(filterIndex), - getSerial(), - 10, false, false, EMPTY_STRING, F("widenumber") -# if FEATURE_TOOLTIPS - , F("Serial (HEX)") -# endif // if FEATURE_TOOLTIPS - ); - - { - // Filter Window - const int optionValues[] = { - static_cast(P094_Filter_Window::All), - static_cast(P094_Filter_Window::One_minute), - static_cast(P094_Filter_Window::Five_minutes), - static_cast(P094_Filter_Window::Fifteen_minutes), - static_cast(P094_Filter_Window::One_hour), - static_cast(P094_Filter_Window::Day), - static_cast(P094_Filter_Window::Month), - static_cast(P094_Filter_Window::Once), - static_cast(P094_Filter_Window::None) - }; - - constexpr size_t nrOptions = sizeof(optionValues) / sizeof(optionValues[0]); - - String options[nrOptions]; - - for (size_t i = 0; i < nrOptions; ++i) { - const P094_Filter_Window filterWindow = static_cast(optionValues[i]); - options[i] = Filter_WindowToString(filterWindow); - } - FormSelectorOptions selector( - nrOptions, - options, - optionValues); - selector.classname = F("widenumber"); - # if FEATURE_TOOLTIPS - selector.tooltip = F("Filter Window"); - # endif - selector.addSelector( - P094_FILTER_WEBARG_FILTER_WINDOW(filterIndex), - _filter._filterWindow); - } -} - -String P094_WebformSave_GetWebArg(const String& id) { - String webarg_str = webArg(id); - - if (webarg_str.isEmpty()) { - webarg_str = '*'; - } - return webarg_str; -} - -bool P094_filter::WebformSave(uint8_t filterIndex) -{ - String filterString; - - // Manufacturer - filterString += P094_WebformSave_GetWebArg(P094_FILTER_WEBARG_MANUFACTURER(filterIndex)); - filterString += '.'; - - // Meter Type - filterString += P094_WebformSave_GetWebArg(P094_FILTER_WEBARG_METERTYPE(filterIndex)); - filterString += '.'; - - // Serial nr - filterString += P094_WebformSave_GetWebArg(P094_FILTER_WEBARG_SERIAL(filterIndex)); - - fromString(filterString); - - // Filter Window - _filter._filterWindow = getFormItemInt( - P094_FILTER_WEBARG_FILTER_WINDOW(filterIndex), - 0); - - return isValid(); -} - -String P094_filter::getManufacturer() const -{ - String manufacturer; - - if (isWildcardManufacturer()) { - manufacturer = '*'; - } else { - manufacturer = mBusPacket_header_t::decodeManufacturerID(_filter._manufacturer); - } - return manufacturer; -} - -String P094_filter::getMeterType() const -{ - String metertype; - - if (isWildcardMeterType()) { - metertype = '*'; - } else { - metertype = formatToHex_no_prefix(_filter._meterType, 2); - } - return metertype; -} - -String P094_filter::getSerial() const -{ - String serial; - - if (isWildcardSerial()) { - serial = '*'; - } else { - serial = formatToHex_no_prefix(_filter._serialNr, 8); - } - return serial; -} - -P094_Filter_Window P094_filter::getFilterWindow() const -{ - return static_cast(_filter._filterWindow); -} - - -#endif // ifdef USES_P094 \ No newline at end of file +#include "../PluginStructs/P094_Filter.h" + +#ifdef USES_P094 + + +# include "../DataStructs/mBusPacket.h" + +# include "../Globals/ESPEasy_time.h" +# include "../Globals/TimeZone.h" +# include "../Helpers/ESPEasy_Storage.h" +# include "../Helpers/StringConverter.h" + + +// *INDENT-OFF* +# define P094_FILTER_WEBARG_LABEL(x) getPluginCustomArgName((x * 10) + 10) +# define P094_FILTER_WEBARG_MANUFACTURER(x) getPluginCustomArgName((x * 10) + 11) +# define P094_FILTER_WEBARG_METERTYPE(x) getPluginCustomArgName((x * 10) + 12) +# define P094_FILTER_WEBARG_SERIAL(x) getPluginCustomArgName((x * 10) + 13) +# define P094_FILTER_WEBARG_FILTER_WINDOW(x) getPluginCustomArgName((x * 10) + 14) +// *INDENT-ON* + +const char P094_Filter_Window_names[] PROGMEM = "none|all|1m|5m|15m|1h|day|month|once"; + +P094_Filter_Window get_FilterWindow(const String& str) +{ + char tmp[10]{}; + const int command_i = GetCommandCode(tmp, sizeof(tmp), str.c_str(), P094_Filter_Window_names); + + if (command_i == -1) { + // No match found + return P094_Filter_Window::None; + } + return static_cast(command_i); +} + +String Filter_WindowToString(P094_Filter_Window filterWindow) +{ + char tmp[10]{}; + String res(GetTextIndexed(tmp, sizeof(tmp), static_cast(filterWindow), P094_Filter_Window_names)); + + return res; +} + +P094_filter::P094_filter() { + _filter._manufacturer = mBus_packet_wildcard_manufacturer; + _filter._meterType = mBus_packet_wildcard_metertype; + _filter._serialNr = mBus_packet_wildcard_serial; + _filter._filterWindow = static_cast(P094_Filter_Window::None); +} + +void P094_filter::fromString(String str) +{ + // Set everything to wildcards + _filter._manufacturer = mBus_packet_wildcard_manufacturer; + _filter._meterType = mBus_packet_wildcard_metertype; + _filter._serialNr = mBus_packet_wildcard_serial; + _filter._filterWindow = static_cast(P094_Filter_Window::None); + + const int semicolonPos = str.indexOf(';'); + + if (semicolonPos != -1) { + _filter._filterWindow = static_cast(get_FilterWindow(str.substring(semicolonPos + 1))); + str = str.substring(0, semicolonPos); + } + + for (size_t i = 0; i < 3; ++i) { + String tmp; + + if (GetArgv(str.c_str(), tmp, (i + 1), '.')) { + if (!(tmp.isEmpty() || tmp.startsWith(F("*")))) { + if (i != 0) { + // Make sure the numerical values are parsed as HEX + if (!tmp.startsWith(F("0x")) && !tmp.startsWith(F("0X"))) { + tmp = concat(F("0x"), tmp); + } + } + + switch (i) { + case 0: // Manufacturer + _filter._manufacturer = mBusPacket_header_t::encodeManufacturerID(tmp); + break; + case 1: // Meter type + { + int32_t metertype = mBus_packet_wildcard_metertype; + + if (validIntFromString(tmp, metertype)) { + _filter._meterType = metertype; + } + break; + } + case 2: // Serial + { + int32_t serial = mBus_packet_wildcard_serial; + + if (validIntFromString(tmp, serial)) { + _filter._serialNr = serial; + } + break; + } + } + } + } + } +} + +String P094_filter::toString() const +{ + String res; + + res += getManufacturer(); + res += '.'; + + res += getMeterType(); + res += '.'; + + res += getSerial(); + res += ';'; + + res += Filter_WindowToString(getFilterWindow()); + + return res; +} + +const uint8_t * P094_filter::toBinary(size_t& size) const +{ + size = getBinarySize(); + return (uint8_t *)this; +} + +size_t P094_filter::fromBinary(const uint8_t *data) +{ + memcpy(this, data, getBinarySize()); + return getBinarySize(); +} + +bool P094_filter::isValid() const +{ + if ((_filter._manufacturer == 0) && + (_filter._meterType == 0) && + (_filter._serialNr == 0) && + (getFilterWindow() == P094_Filter_Window::None)) { + return false; + } + return + !isWildcardManufacturer() || + !isWildcardMeterType() || + !isWildcardSerial() || + getFilterWindow() != P094_Filter_Window::None; +} + +bool P094_filter::operator<(const P094_filter& rhs) const +{ + if (isValid() != rhs.isValid()) { + return isValid(); + } +/* + // Disable sorting, only sort by having valid filters at top. + if (isWildcardManufacturer() != rhs.isWildcardManufacturer()) { + return rhs.isWildcardManufacturer(); + } + + if (isWildcardMeterType() != rhs.isWildcardMeterType()) { + return rhs.isWildcardMeterType(); + } + + if (isWildcardSerial() != rhs.isWildcardSerial()) { + return rhs.isWildcardSerial(); + } + + if (!isWildcardManufacturer() && (_filter._manufacturer != rhs._filter._manufacturer)) { + return _filter._manufacturer < rhs._filter._manufacturer; + } + + if (!isWildcardMeterType() && (_filter._meterType != rhs._filter._meterType)) { + return _filter._meterType < rhs._filter._meterType; + } + + if (!isWildcardSerial() && (_filter._serialNr != rhs._filter._serialNr)) { + return _filter._serialNr < rhs._filter._serialNr; + } +*/ + return false; +} + +bool P094_filter::operator==(const P094_filter& rhs) const +{ + return equals(*this, rhs); +} + +bool P094_filter::operator!=(const P094_filter& rhs) const +{ + return !equals(*this, rhs); +} + +bool P094_filter::equals(const P094_filter& lhs, const P094_filter& rhs) +{ + if (!lhs.isValid() && !rhs.isValid()) { return true; } + + return lhs.toString() == rhs.toString(); +} + +size_t P094_filter::getBinarySize() +{ + // Only store the filter + constexpr size_t P094_filter_size = sizeof(_filter); + + return P094_filter_size; +} + +bool P094_filter::matches(const mBusPacket_header_t& other) const +{ + if (!isWildcardManufacturer()) { + if (_filter._manufacturer != other._manufacturer) { return false; } + } + + if (!isWildcardMeterType()) { + if (_filter._meterType != other._meterType) { return false; } + } + + if (!isWildcardSerial()) { + if (_filter._serialNr != other._serialNr) { return false; } + } + + return true; +} + +unsigned long P094_filter::computeUnixTimeExpiration() const +{ + // Match the interval window. + const P094_Filter_Window filterWindow = getFilterWindow(); + + if ((filterWindow == P094_Filter_Window::None) || + (filterWindow == P094_Filter_Window::Once)) { + // Return date infinitely far in the future + return 0xFFFFFFFF; + } + + if (filterWindow == P094_Filter_Window::All) { + // Return timestamp in the past + return 0; + } + + // Using UnixTime + const unsigned long currentTime = node_time.getUnixTime(); + unsigned long window_max = currentTime; + + if ((filterWindow == P094_Filter_Window::One_hour) || + (filterWindow == P094_Filter_Window::Day) || + (filterWindow == P094_Filter_Window::Month)) + { + // Create time struct in local time. + struct tm tm_max; + breakTime(time_zone.toLocal(currentTime), tm_max); + tm_max.tm_sec = 59; + tm_max.tm_min = 59; + + if (filterWindow == P094_Filter_Window::Day) { + // Using local time, thus incl. timezone and DST. + if (tm_max.tm_hour < 23) { + // Either: + // - between 00:00 and 12:00 => Max: 11:59:59 + // - between 12:00 and 23:00 => Max: 22:59:59 + + tm_max.tm_hour = (tm_max.tm_hour < 12) ? 11 : 22; + } else { + // between 23:00 and 00:00 => Max: 23:59:59 + tm_max.tm_hour = 23; + } + } else if (filterWindow == P094_Filter_Window::Month) { + // First set minute to midnight of today => Max: 23:59:59 + tm_max.tm_hour = 23; + + if (tm_max.tm_mday < 15) { + // - between 1st of month 00:00:00 and 15th of month 00:00:00 + tm_max.tm_mday = 14; + } else { + // Check if this is the last day of the month. + // Add 24h to the time and see if it is still the same month. + const uint8_t maxMonthDay = getMonthDays(tm_max); + + if (tm_max.tm_mday < maxMonthDay) { + // - between 15th of month 00:00:00 and last of month 00:00:00 + // So we must subtract one day. + tm_max.tm_mday = maxMonthDay - 1; + } else { + // - between last of month 00:00:00 and 1st of next month 00:00:00 + // Thus do not change the date as it is already at the last day of the month + } + } + } + + // Convert from local time. + window_max = time_zone.fromLocal(makeTime(tm_max)); + } else { + switch (filterWindow) { + case P094_Filter_Window::One_minute: + window_max = currentTime - (currentTime % (1 * 60)) + (1 * 60 - 1); + break; + case P094_Filter_Window::Five_minutes: + window_max = currentTime - (currentTime % (5 * 60)) + (5 * 60 - 1); + break; + case P094_Filter_Window::Fifteen_minutes: + window_max = currentTime - (currentTime % (15 * 60)) + (15 * 60 - 1); + break; + + default: + break; + } + } + return window_max; +} + +void P094_filter::WebformLoad(uint8_t filterIndex) const +{ + addRowLabel_tr_id( + concat(F("Filter "), static_cast(filterIndex + 1)), + P094_FILTER_WEBARG_LABEL(filterIndex)); + + // Manufacturer + addTextBox( + P094_FILTER_WEBARG_MANUFACTURER(filterIndex), + getManufacturer(), + 3, false, false, EMPTY_STRING, F("widenumber") +# if FEATURE_TOOLTIPS + , F("Manufacturer") +# endif // if FEATURE_TOOLTIPS + ); + + // Meter Type + addTextBox( + P094_FILTER_WEBARG_METERTYPE(filterIndex), + getMeterType(), + 4, false, false, EMPTY_STRING, F("widenumber") +# if FEATURE_TOOLTIPS + , F("Meter Type (HEX)") +# endif // if FEATURE_TOOLTIPS + ); + + // Serial nr + addTextBox( + P094_FILTER_WEBARG_SERIAL(filterIndex), + getSerial(), + 10, false, false, EMPTY_STRING, F("widenumber") +# if FEATURE_TOOLTIPS + , F("Serial (HEX)") +# endif // if FEATURE_TOOLTIPS + ); + + { + // Filter Window + const int optionValues[] = { + static_cast(P094_Filter_Window::All), + static_cast(P094_Filter_Window::One_minute), + static_cast(P094_Filter_Window::Five_minutes), + static_cast(P094_Filter_Window::Fifteen_minutes), + static_cast(P094_Filter_Window::One_hour), + static_cast(P094_Filter_Window::Day), + static_cast(P094_Filter_Window::Month), + static_cast(P094_Filter_Window::Once), + static_cast(P094_Filter_Window::None) + }; + + constexpr size_t nrOptions = sizeof(optionValues) / sizeof(optionValues[0]); + + String options[nrOptions]; + + for (size_t i = 0; i < nrOptions; ++i) { + const P094_Filter_Window filterWindow = static_cast(optionValues[i]); + options[i] = Filter_WindowToString(filterWindow); + } + FormSelectorOptions selector( + nrOptions, + options, + optionValues); + selector.classname = F("widenumber"); + # if FEATURE_TOOLTIPS + selector.tooltip = F("Filter Window"); + # endif + selector.addSelector( + P094_FILTER_WEBARG_FILTER_WINDOW(filterIndex), + _filter._filterWindow); + } +} + +String P094_WebformSave_GetWebArg(const String& id) { + String webarg_str = webArg(id); + + if (webarg_str.isEmpty()) { + webarg_str = '*'; + } + return webarg_str; +} + +bool P094_filter::WebformSave(uint8_t filterIndex) +{ + String filterString; + + // Manufacturer + filterString += P094_WebformSave_GetWebArg(P094_FILTER_WEBARG_MANUFACTURER(filterIndex)); + filterString += '.'; + + // Meter Type + filterString += P094_WebformSave_GetWebArg(P094_FILTER_WEBARG_METERTYPE(filterIndex)); + filterString += '.'; + + // Serial nr + filterString += P094_WebformSave_GetWebArg(P094_FILTER_WEBARG_SERIAL(filterIndex)); + + fromString(filterString); + + // Filter Window + _filter._filterWindow = getFormItemInt( + P094_FILTER_WEBARG_FILTER_WINDOW(filterIndex), + 0); + + return isValid(); +} + +String P094_filter::getManufacturer() const +{ + String manufacturer; + + if (isWildcardManufacturer()) { + manufacturer = '*'; + } else { + manufacturer = mBusPacket_header_t::decodeManufacturerID(_filter._manufacturer); + } + return manufacturer; +} + +String P094_filter::getMeterType() const +{ + String metertype; + + if (isWildcardMeterType()) { + metertype = '*'; + } else { + metertype = formatToHex_no_prefix(_filter._meterType, 2); + } + return metertype; +} + +String P094_filter::getSerial() const +{ + String serial; + + if (isWildcardSerial()) { + serial = '*'; + } else { + serial = formatToHex_no_prefix(_filter._serialNr, 8); + } + return serial; +} + +P094_Filter_Window P094_filter::getFilterWindow() const +{ + return static_cast(_filter._filterWindow); +} + + +#endif // ifdef USES_P094 diff --git a/src/src/PluginStructs/P094_Filter.h b/src/src/PluginStructs/P094_Filter.h index 683923da00..0972aace22 100644 --- a/src/src/PluginStructs/P094_Filter.h +++ b/src/src/PluginStructs/P094_Filter.h @@ -1,95 +1,95 @@ -#ifndef PLUGINSTRUCTS_P094_FILTER_H -#define PLUGINSTRUCTS_P094_FILTER_H - -#include "../../_Plugin_Helper.h" -#ifdef USES_P094 - -# include "../DataStructs/mBusPacket.h" - -// Is stored, so do not change the int values. -enum class P094_Filter_Window : uint8_t { - None = 0, // no messages pass the filter - All = 1, // Realtime, every message passes the filter - One_minute = 2, // a message passes the filter every 1 minutes, aligned to time (00:00:00, 00:01:00, ...) - Five_minutes = 3, // a message passes the filter every 5 minutes, aligned to time (00:00:00, 00:05:00, ...) - Fifteen_minutes = 4, // a message passes the filter every 15 minutes, aligned to time - One_hour = 5, // a message passes the filter every hour, aligned to time - Day = 6, // a message passes the filter once every day - // - between 00:00 and 12:00, - // - between 12:00 and 23:00 and - // - between 23:00 and 00:00 - Month = 7, // a message passes the filter - // - between 1st of month 00:00:00 and 15th of month 00:00:00 - // - between 15th of month 00:00:00 and last of month 00:00:00 - // - between last of month 00:00:00 and 1st of next month 00:00:00 - Once = 8 // only one message passes the filter until next reboot -}; - - -// Examples for a filter definition list -// EBZ.02.12345678;all -// *.02.*;15m -// TCH.44.*;Once -// *.*.*;5m - -struct P094_filter { - P094_filter(); - - void fromString(String str); - String toString() const; - - const uint8_t* toBinary(size_t& size) const; - size_t fromBinary(const uint8_t *data); - - // Is valid when it doesn't match: *.*.*;none - bool isValid() const; - - bool operator<(const P094_filter& rhs) const; - bool operator==(const P094_filter& rhs) const; - bool operator!=(const P094_filter& rhs) const; - - static bool equals(const P094_filter& lhs, const P094_filter& rhs); - - static size_t getBinarySize(); - - - // Check to see if the manufacturer, metertype and serial matches. - bool matches(const mBusPacket_header_t& other) const; - - // Compute expiration UnixTime - unsigned long computeUnixTimeExpiration() const; - - void WebformLoad(uint8_t filterIndex) const; - bool WebformSave(uint8_t filterIndex); - - bool isWildcardManufacturer() const { - return _filter._manufacturer == mBus_packet_wildcard_manufacturer; - } - - bool isWildcardMeterType() const { - return _filter._meterType == mBus_packet_wildcard_metertype; - } - - bool isWildcardSerial() const { - return _filter._serialNr == mBus_packet_wildcard_serial; - } - - String getManufacturer() const; - String getMeterType() const; - String getSerial() const; - P094_Filter_Window getFilterWindow() const; - - // Keep this order of members as this is how it will be stored. - struct { - uint64_t _serialNr : 32; - uint64_t _manufacturer : 16; - uint64_t _meterType : 8; - - // Use for filtering - uint64_t _filterWindow : 8; - } _filter; -}; - -#endif // ifdef USES_P094 - -#endif // ifndef PLUGINSTRUCTS_P094_FILTER_H \ No newline at end of file +#ifndef PLUGINSTRUCTS_P094_FILTER_H +#define PLUGINSTRUCTS_P094_FILTER_H + +#include "../../_Plugin_Helper.h" +#ifdef USES_P094 + +# include "../DataStructs/mBusPacket.h" + +// Is stored, so do not change the int values. +enum class P094_Filter_Window : uint8_t { + None = 0, // no messages pass the filter + All = 1, // Realtime, every message passes the filter + One_minute = 2, // a message passes the filter every 1 minutes, aligned to time (00:00:00, 00:01:00, ...) + Five_minutes = 3, // a message passes the filter every 5 minutes, aligned to time (00:00:00, 00:05:00, ...) + Fifteen_minutes = 4, // a message passes the filter every 15 minutes, aligned to time + One_hour = 5, // a message passes the filter every hour, aligned to time + Day = 6, // a message passes the filter once every day + // - between 00:00 and 12:00, + // - between 12:00 and 23:00 and + // - between 23:00 and 00:00 + Month = 7, // a message passes the filter + // - between 1st of month 00:00:00 and 15th of month 00:00:00 + // - between 15th of month 00:00:00 and last of month 00:00:00 + // - between last of month 00:00:00 and 1st of next month 00:00:00 + Once = 8 // only one message passes the filter until next reboot +}; + + +// Examples for a filter definition list +// EBZ.02.12345678;all +// *.02.*;15m +// TCH.44.*;Once +// *.*.*;5m + +struct P094_filter { + P094_filter(); + + void fromString(String str); + String toString() const; + + const uint8_t* toBinary(size_t& size) const; + size_t fromBinary(const uint8_t *data); + + // Is valid when it doesn't match: *.*.*;none + bool isValid() const; + + bool operator<(const P094_filter& rhs) const; + bool operator==(const P094_filter& rhs) const; + bool operator!=(const P094_filter& rhs) const; + + static bool equals(const P094_filter& lhs, const P094_filter& rhs); + + static size_t getBinarySize(); + + + // Check to see if the manufacturer, metertype and serial matches. + bool matches(const mBusPacket_header_t& other) const; + + // Compute expiration UnixTime + unsigned long computeUnixTimeExpiration() const; + + void WebformLoad(uint8_t filterIndex) const; + bool WebformSave(uint8_t filterIndex); + + bool isWildcardManufacturer() const { + return _filter._manufacturer == mBus_packet_wildcard_manufacturer; + } + + bool isWildcardMeterType() const { + return _filter._meterType == mBus_packet_wildcard_metertype; + } + + bool isWildcardSerial() const { + return _filter._serialNr == mBus_packet_wildcard_serial; + } + + String getManufacturer() const; + String getMeterType() const; + String getSerial() const; + P094_Filter_Window getFilterWindow() const; + + // Keep this order of members as this is how it will be stored. + struct { + uint64_t _serialNr : 32; + uint64_t _manufacturer : 16; + uint64_t _meterType : 8; + + // Use for filtering + uint64_t _filterWindow : 8; + } _filter; +}; + +#endif // ifdef USES_P094 + +#endif // ifndef PLUGINSTRUCTS_P094_FILTER_H diff --git a/src/src/PluginStructs/P094_data_struct.cpp b/src/src/PluginStructs/P094_data_struct.cpp index f0695bd374..787083769d 100644 --- a/src/src/PluginStructs/P094_data_struct.cpp +++ b/src/src/PluginStructs/P094_data_struct.cpp @@ -1,853 +1,853 @@ -#include "../PluginStructs/P094_data_struct.h" - -#ifdef USES_P094 - -// Needed also here for PlatformIO's library finder as the .h file -// is in a directory which is excluded in the src_filter -# include - -# include - -#include - -# include "../DataStructs/mBusPacket.h" -# include "../Globals/MQTT.h" - -// # include "../Globals/ESPEasy_time.h" -// # include "../Globals/TimeZone.h" -// # include "../Helpers/ESPEasy_Storage.h" -// # include "../Helpers/StringConverter.h" - - -P094_data_struct::P094_data_struct() : easySerial(nullptr) {} - -P094_data_struct::~P094_data_struct() { - if (easySerial != nullptr) { - delete easySerial; - easySerial = nullptr; - } -} - -void P094_data_struct::reset() { - if (easySerial != nullptr) { - delete easySerial; - easySerial = nullptr; - } -} - -bool P094_data_struct::init(ESPEasySerialPort port, - const int16_t serial_rx, - const int16_t serial_tx, - unsigned long baudrate) { - if ((serial_rx < 0) && (serial_tx < 0)) { - return false; - } - reset(); - easySerial = new (std::nothrow) ESPeasySerial(port, serial_rx, serial_tx); - - if (easySerial == nullptr) { - return false; - } - easySerial->begin(baudrate); - return true; -} - -void P094_data_struct::setFlags(unsigned long filterOffWindowTime_ms, - bool intervalFilterEnabled, - bool mute, - bool collectStats) -{ - filterOffWindowTime = filterOffWindowTime_ms; - interval_filter.enabled = intervalFilterEnabled; - collect_stats = collectStats; - mute_messages = mute; -} - - -void P094_data_struct::loadFilters(struct EventStruct *event, uint8_t nrFilters) -{ - int offset_in_block = 0; - - - const size_t chunkSize = P094_filter::getBinarySize(); - const size_t maxNrFilters = 1024u / chunkSize; - - if (nrFilters > maxNrFilters) { nrFilters = maxNrFilters; } - - _filters.clear(); - - size_t nrChunks = 8; - - if (nrFilters < nrChunks) { - nrChunks = nrFilters; - } - - const size_t bufferSize = nrChunks * chunkSize; - - while (nrFilters > 0) { - uint8_t buffer[bufferSize]; - ZERO_FILL(buffer); - - LoadCustomTaskSettings(event->TaskIndex, buffer, bufferSize, offset_in_block); - offset_in_block += bufferSize; - - uint8_t *readPos = buffer; - - for (size_t i = 0; i < nrChunks && nrFilters > 0; ++i) { - P094_filter filter; - filter.fromBinary(readPos); - - if (filter.isValid()) { - _filters.push_back(filter); - } - - --nrFilters; - readPos += chunkSize; - } - } -} - -String P094_data_struct::saveFilters(struct EventStruct *event) const -{ - int offset_in_block = 0; - - String res; - const size_t nrFilters = _filters.size(); - size_t currentFilter = 0; - size_t actualNrFilters = 0; - const size_t chunkSize = P094_filter::getBinarySize(); - const size_t nrChunks = 8; - #ifdef ESP32 - const size_t bufferSize = 1024; - #else - const size_t bufferSize = 256; - #endif - - std::vector buffer; - buffer.resize(bufferSize); - - - while ((offset_in_block + bufferSize) <= 1024 && res.isEmpty()) { - for (auto it = buffer.begin(); it != buffer.end(); ++it) { - *it = 0; - } - - uint8_t *writePos = &buffer[0]; - size_t writeSize = 0; - - while (writeSize < bufferSize && currentFilter < nrFilters) { - if (_filters[currentFilter].isValid()) { - size_t size{}; - const uint8_t *binaryData = _filters[currentFilter].toBinary(size); - memcpy(writePos, binaryData, size); - writePos += size; - writeSize += size; - ++actualNrFilters; - } - ++currentFilter; - } - res = SaveCustomTaskSettings(event->TaskIndex, &buffer[0], bufferSize, offset_in_block); - offset_in_block += bufferSize; - } - if (P094_NR_FILTERS < actualNrFilters) { - P094_NR_FILTERS = actualNrFilters; - } - return res; -} - -void P094_data_struct::clearFilters() -{ - _filters.clear(); -} - -bool P094_data_struct::addFilter(struct EventStruct *event, const String& filter) -{ - P094_filter f; - - f.fromString(filter); - - if (!f.isValid()) { - return false; - } - - if (isDuplicate(f)) { - if (loglevelActiveFor(LOG_LEVEL_ERROR)) { - addLogMove(LOG_LEVEL_ERROR, concat(F("CUL Reader : Duplicate filter found: "), f.toString())); - } - - return false; - } - - _filters.push_back(f); - - // No sorting as this may have unexpected side-effects - // std::sort(_filters.begin(), _filters.end()); - - if (P094_NR_FILTERS < _filters.size()) { - P094_NR_FILTERS = _filters.size(); - } - return true; -} - -String P094_data_struct::getFiltersMD5() const -{ - if (mute_messages) { - return F("blockall"); - } - - MD5Builder md5; - uint8_t checksum[16]{}; - md5.begin(); - - uint8_t nrFiltersAdded = 0; - const char separator[] = {'|', 0}; - for (auto it = _filters.begin(); it != _filters.end(); ++it) { - if (it->isValid()) { - if (nrFiltersAdded != 0) { - md5.add(separator); - } - md5.add(it->toString().c_str()); - ++nrFiltersAdded; - } - } - - if (nrFiltersAdded == 0) { - // No filters, thus all messages will just pass - return F("pass"); - } - - md5.calculate(); - md5.getBytes(checksum); - - return formatToHex_array(checksum, sizeof(checksum)); -} - -void P094_data_struct::WebformLoadFilters(uint8_t nrFilters) const -{ - if (nrFilters > 0) { - addFormNote(F("Filter Fields: Manufacturer, Meter Type, Serial, Filter Window")); - } - - for (uint8_t filterLine = 0; filterLine < nrFilters; ++filterLine) - { - if (filterLine < _filters.size()) { - _filters[filterLine].WebformLoad(filterLine); - } else { - P094_filter dummy; - dummy.WebformLoad(filterLine); - } - } -} - -void P094_data_struct::WebformSaveFilters(struct EventStruct *event, uint8_t nrFilters) -{ - _filters.clear(); - - for (uint8_t filterLine = 0; filterLine < nrFilters; ++filterLine) - { - P094_filter dummy; - - if (dummy.WebformSave(filterLine)) { - // Filter with filled in values, worth storing - if (!isDuplicate(dummy)) { - _filters.push_back(dummy); - } - } - } - addHtmlError(saveFilters(event)); -} - -bool P094_data_struct::isInitialized() const { - return easySerial != nullptr; -} - -void P094_data_struct::sendString(const String& data) { - if (isInitialized()) { - if (data.length() > 0) { - setDisableFilterWindowTimer(); - easySerial->write(data.c_str()); - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - addLogMove(LOG_LEVEL_INFO, concat(F("Proxy: Sending: "), data)); - } - } - } -} - -# if P094_DEBUG_OPTIONS - -const __FlashStringHelper* getDebugSentences(int& count) { - // *INDENT-OFF* - switch (count) { - case 1: return F("b3C449344369291352337D55472593107009344230A920000200C0538ECE32625004C0527262500426CBF2CCC0805BDF032262500C2086CDF21326CFFFF046D26BB1103DA22B4E093E2"); break; //QDS.0A.00073159"); break; //QDS.37.35919236 - case 2: return F("b9644A732260729700A0AB8487A4E10002002747D00046D030AC1270CB02E0600000000446D3B17BF2C4C0600000083410084016D3B17DE268C010600000000CC190B0106000000008C020600000000CC0206B190000000008C030600000000CC030600008B9400008C040600000000CC04060000000099348C050600000000CC0506000000008C0673DA0600000000CC0606000000008C070600E0F80000003C22030402000F841001245C84E7"); break; //LUG.0A.70290726 - case 3: return F("bCE44A8153132000801022ADC7F6900C005098D2F2D70E24F3E43458739FD572B2DB7CB22EA563C57F3017308E093A4CBC662DF70F000A2E2B18215FC7098DBC7DC8A2ABAD8202F700C5A7D8B0FC89094823FC6B54565730369E73039146898536381B5DE8B8F3A5377A807EB30383ACC0176176C6C18265932082844F0B5A3F69B0A66FD0E35FAED9A53B825E073FC1E67193A727C97BC1025229C87421FA0381443A5F2F2897AE44D383FE125614F08BABEC6B46DF0FFCB910DAD1B3CD53B44AA83726492D845F840A2D20B738E9FB212D5C74FF91FD2796A22D669CBF0B0FEC1BAFA171A65FFB165B2E9"); break; //EMH.02.08003231 - case 4: return F("b5344E2306291001500030F388C30A7900F002C2583AE010032E1E493C32BEF51CDA37A430030071027A19EE14B0BBCAD656D0783516CCB7CFBC6AAFAECDCAD70020FE3DA54FCBC8EC2AED88DFD0972C55CF9336E1683574ABADBD046BB53623F8013"); break; //LGB.03.15009162 - case 5: return F("b5344A732806139690404B70A8C2063900F002C25923338000C8BC361CE2EE050FD3B7A6340300710DEC49523134391877289A80A53A505655A833F754F221E619D08FB4DB5AD773EAB16B545B306C69D1493CD851012BBF4624A5DDA556AF07E83E5"); break; //LUG.04.69396180 - case 6: return F("b9644A732460335700A043C1F7ACD0000200274DE02046D030D94250C804C0623000000446D3B177F2C4C060000005D610084016D3B179E248C010600000000CCB81D0106000000008C020600000000CC0206B190000000008C030600000000CC030600008B9400008C040600000000CC04060000000099348C050600000000CC0506000000008C0673DA0600000000CC0606000000008C070600E0F80000003C22000000000F0010004FF980F9"); break; //LUG.04.70350346 - case 7: return F("b13440000000000DA00DAA8CF7101FD0C3A02FD171101CB938032"); break; //@@@.DA.DA000000 - case 8: return F("b63445A146699750001026CE68C20D7900F002C25D7CE0C000B7C13179B38522166CE7AD700400710CDEF8D2A82F77DD15E367871F1E04261AAFAC430C2B55C1DED4A3148306D4C296CF10D72C9E79310A47DD73FDBFDF2CEA6490B6CA12A30EE5D64621A90B5E71F75D50D24C87B10E2ADDF802E"); break; //EBZ.02.00759966 - case 9: return F("b5E44496A3680003888049D2D7A1D0050053FBA7B54810C548AC112ECFC76CE753AF07A625248C05827C843371AB5DC6C6C8D5D457E845B4B67FB4CEFF06720EA7A9112BFD0A96BC7E97D49FB9BBD59155D109433F0C4823DEA7A13E5281C00E4945F5B05D7518CE085EC8BFE738122"); break; //ZRI.04.38008036 - case 10 : return F("b1B44A5110301808238379BA77241022436931581038A88000002A7184A0AD900E327803A"); break; //ELS.03.36240241"); break; //DME.37.82800103 - case 11 : return F("b3E44B405399098502E0439DC7A0F70300530C7144B5962760A55DE8BA4E49C37676B0CD702698B5FBCE59E35E33D33F736AE13FB1C31DD43ADDC3FE7FF8E1EDC01D749974884BBF96580FE"); break; //AMT.04.50989039 - case 12 : return F("b5B4479169014216130377E5B8C2034900F002C256448000029E1A134984E32773DF97289000047791611023400302CBD0710FF4380B4AE49A140E94F319BE97049FDA8DDC96A8DC437F3BFB02ADC86082E9507934C7ED4FC6F4F678D613F25C09A1DDE927D817F5A824A8012"); break; //ESY.02.47000089"); break; //ESY.37.61211490 - case 13 : return F("b7B445A1415200000023724958C20A0900F002C25B4F60800C0E1D417100AD1BB6EDC72324371005A140102A00050F9B10710112A08DB9869998DE2C0D8614F76213F8682D10A8EF2951413C839461E8E3139EA62193E02B1584E6EC8EDB082AB70C6504F1ADF9E6ABD270E96FE8745AEB93C454FC3C9EAA2D5FCD6679E8A38A3E0818D4B6652993CEE5F8E514867801D"); break; //EBZ.02.00714332"); break; //EBZ.37.00002015 - case 14 : return F("b49449344100549253508227F780DFF5F3500824600007E0007B06EFF2BE9FF000000007F2C000000009E24000000DAC000008000800080008000800080000000E9F10000000000000000002F046D010E8625249880E6"); break; //QDS.08.25490510 - case 15 : return F("b9644A732370335700A045D187A440000200274DD02046D071189260C33FD0638010000446D3B177F2C4C06000000DFDD0084016D3B179F258C010641000000CC0DBA0106000000008C020600000000CC0206B190000000008C030600000000CC030600008B9400008C040600000000CC04060000000099348C050600000000CC0506000000008C0673DA0600000000CC0606000000008C070600E0F80000003C22000000000F0010004FF980E0"); break; //LUG.04.70350337 - case 16 : return F("b4806AC1956030015020363917256030015AC190203D8000000466D00AC9200118625000D78113131363533303030619C3531343530303030308940FD1A014C933B263A494700004C130546000001FD67030D9A8057"); break; //FML.03.15000356"); break; //FML.03.15000356 - case 17 : return F("b9E44A815242292070102881F7F4B0098050B0E989C939C3479F8904137236FE582B853DCACDD8DB48A717A6B42935CB977102079E6397B07AAD2A648E5B65E44D97F9A020B2BFAE433FA37FCB2A2C32711A5986B301D2F6E4A424C1144D808CE9D592C316B117C572689AC6C1322CC05E81F590CAAF457390F6B39DACC946FA314F8E8A34268157AC4338781C3EF5807F9221394DD1FAB5165E1261614B8B85758851295334DF52D9A4DCE2E1E17A555A21007D2DA802B"); break; //EMH.02.07922224 - case 18 : return F("b2E44B05C99010100021B65BE7AC30000002F2F0A6605020AFB1A33041AA002FD971D00002F2F2F2F2F2F2F2F2F2FDF772F2F2F2F2F25EE8008"); break; //WEP.1B.00010199 - case 19 : return F("b2E44B05C75000000041B8EF87A510000002F2F0A6661010AFB1A8905449802FD971D00002F2F2F2F2F2F2F2F2F2FDF772F2F2F2F2F25EE80F8"); break; //WEP.1B.00000075 - case 20 : return F("b1E44C418EA76010001034ECB7A820010A5597AB1CCF2014088590E64BCAB21DC1CC068DAA08035"); break; //FFD.03.000176EA - case 21 : return F("b4E442423245450514A07545F7AC6004005BDB5EDF3750DF41725EE867C3E39750E20B9F5FB092089B6A5DC7AA586101778BCD5EAD4995B102AC639F0FB4D12403EFE3554F72CAE4F5D348CC374F571CCE4A98634318027AAF3E7DD8600"); break; //HYD.07.51505424 - case 22 : return F("b4344A511031008667607CFF48C0031900F002C2531D311006D980E9CC8D28524C6417AE570210710CFF2DB65BFE77C51602690DAEB954A5455A2ABED621B74AC56A79D81390EBFCADC9D3D34F5928DDC"); break; //DME.07.66081003 - case 23 : return F("b4344A511031008667607CFF48C0031900F002C2532D31100BC55A548A0082663A6FC7AE670210710C7B178D748F610E2CB16DE82C823EF83334EF1A0C383FB42DF7BED1846323211FC25DCC1EBF085DE"); break; //DME.07.66081003 - case 24 : return F("b4344A511031008667607CFF48C0031900F002C2533D311004E0C2E86D68C08F425747AE77021071013F9284E0CB5896FFB27D33882716D09F7EC8175FD04516AAE9122E584095D6441E14E5B4ED189DD"); break; //DME.07.66081003 - case 25 : return F("b6E44A511825169584004B8737AB500600554B11A7F9F7DDCAC35695A3191EA83FD79D1876F378419D5AB37CA1BD857243492B6379B258A4831015F9F0D4B7098218D7E7C44421422FCABC0770F0E67C16EFA13ABEE798D58062CDB0F06AA312592C085F046D29B64F7031FE17CC7EC8B44D61FB5E2F37301ACA7AAC025666C802C"); break; //DME.04.58695182 - case 26 : return F("b4944C51402203571000451A77A090001202F2F046D2E299926040687ED7214000001FD17000413F6670400043B009378000000042B00000000025B1900025F1986B90002610A0003FD0C05000002FD0B3011F52BA393"); break; //EFE.04.71352002 - case 27 : return F("b2844C5146427807103073D877234626016C5140007D72000202F2F04DD1C6D2F3498260413B96E010001FD1700066080E7"); break; //EFE.07.16606234"); break; //EFE.07.71802764 - case 28 : return F("bA644C514960080900307DABF7296008090C5140007ED0000202F2F426E8D6C7E2944133C08000001FD1700840113039188130000C40113D611000084021324108D5A0000C40213A70F00008403132C0F00003550C403137B0E0000840413C0080000C404749913650800008405133C080000C40513AD831B07000084061380050000C406138401006999008407133A000000C407133A00000084286B081300000000046D332A8F260413A7137132000003FD0C02032102FD0B01114D8480FA"); break; //EFE.07.90800096"); break; //EFE.07.90800096 - case 29 : return F("b5E442515695800000C1A452E7A590050252502B45C2E823EF856FABFC4775E28D2904492FB74BA65A843BA7E63BC698D83279AE2BE04B957699517818F7B33F8E58001A7C9CE0E73C5769487247954D0A000E811754BAB1CED0E61567EB1FE504B091E0C7DCF4632E3FAC31618804D"); break; //EIE.1A.00005869 - case 30 : return F("b4806AC1956030015020363917256030015AC19020323000000466D0009780004872B000D7811313136353330303028633531343530303030308940FD1A014C933B263A494700004C130546000001FD67030D9A8030"); break; //FML.03.15000356"); break; //FML.03.15000356 - case 31 : return F("bY5044972608062002001A7AC90340A517F5A8F83D78281AEFF06FDBDEDEF842336FA663F292D6EEACB0F54CA02FB47C8F587862B352ED30166FEE61753998230C38444F845C9FCC364D3C614095F92D14A28010"); break; //ITW.1A.02200608 - case 32 : return F("bY5E449726900634160004728499181897A60030BA0040A53FB81E378B33F9E23FEDF6A0B1B381F4972C2842041CC7EC8D74D675CE56222039CE82385073750F2B695CBEC9B0E8A48EC3F09B74C26E4194A06B7974203DDD0C7976874216E0D282DE"); break; //ITW.04.16340690"); break; //ITW.30.18189984 - case 33 : return F("bY50449726141331160007728499181897A600307E0030A5500A4109842EF76DD4A2DEDF6722CCB4D0746C8505086D91ED34B41AFD24FED0111715A21E549191B1529EE2AE8229E50E7900000000000070208AD8"); break; //ITW.07.16311314"); break; //ITW.30.18189984 - case 34 : return F("bY5044972625353893071A7A0B0040A5B12EC2E009C467CAD2AF0A38712684FE764C6181D2969A7F0F7B076CB9719FEB21C32E48161EEA5A1E6E92F354FED894994C368F17E6F68E2E6AA1D7C289E3FD7A908015"); break; //ITW.1A.93383525 - case 35 : return F("b2E449726606670194107A6AB8CB0D47ABF0000A00413E84B070004FD3442178001C00004933C00000000333B00000A2D00325A0000CD2E801C"); break; //ITW.07.19706660 - case 36 : return F("bY50449726141331160007728499181897A600307E0030A5500A4109842EF76DD4A2DEDF6722CCB4D0746C8505086D91ED34B41AFD24FED0111715A21E549191B1529EE2AE8229E50E7900000000000070208AD8"); break; //ITW.07.16311314"); break; //ITW.30.18189984 - case 37 : return F("b314493447236379635085D337A040000200B6E3103004B6E070300420D0E6C7F2CCB086E310300C2086C9E24326C1422FFFF046D240E86250E4E80E2"); break; //QDS.08.96373672 - case 38 : return F("b494493447236379635083FD9780DFF5F350082030000F00007B06EFFC6F5FF310300007F2C070300009E24310300E13000008000800000000000001F007300A145C9006BFF64003D000C002F046D200E86255AA081DF"); break; //QDS.08.96373672 - case 39 : return F("b494493447153871216062D53780DFF5F350082DA00007F0007C113FF8331FF969799997F2C279899999F2596979988C999000000000000FFFF000000000000008B35000000FFFFFEFF00002F046D25068C2608D2803F"); break; //QDS.06.12875371 - case 40 : return F("b344493447153871216069A667A8000082004ED3926096C2701FD0C110157046D17138F2602FD3CC2010DFF5F0C005FC50861FF000006130701FFFC138B803F"); break; //QDS.06.12875371 - case 41 : return F("b3D44934417196746221AD6FF7AA210000081027C034955230182026CAC3BB62181037C034C41230082036CFFFF0238E2FD171000326CFFFF046D0311B62102FDFE8FAC7E19004F33802D"); break; //QDS.1A.46671917 - case 42 : return F("b3744934417196746221A40247AA310002081027C034955230182026C5AE5B62181037C034C41230082036CFFFF0238E2FD171000326CFFFF046D0311B621AD04802B"); break; //QDS.1A.46671917 - case 43 : return F("b39449344775149131706E1D57A680000200C13750000004C1300000010FC00426CFFFFCC081358000000C2086C9F61212502BB560000326CFFFF046D070B8A268D5880FA"); break; //QDS.06.13495177 - case 44 : return F("b4944934477514913170662C2780DFF5F3500827B0000810007C113FF5A69FF75000000FFFF000000009F255800004659000080008000800080008000800080009B248001000000000004002F046D1F0D8A266CB280FA"); break; //QDS.06.13495177 - case 45 : return F("b3744934484145047231AAE907A9600002081027C034955230082026CD1DAFFFF81037C034C41230082036CFFFF02DDAAFD170000326CFFFF046D0503AD21AB3580D9"); break; //QDS.1A.47501484 - case 46 : return F("b39449344843350131707BA4A7AF00000200C13000100004C13000000C11300426CFFFFCC081361000000C2086C9F6DF62502BB560000326CFFFF046D370A8A26486681E6"); break; //QDS.07.13503384 - case 47 : return F("b49449344843350131707395D780DFF5F350082000000EE0007C113FF4BC0FF00010000FFFF000000009F256100009FB5000080008000800080008000800080009B248001000000000005002F046D020D8A263BD880E7"); break; //QDS.07.13503384 - case 48 : return F("b3C4493440989323533372A78728903252493443307C50000200C133698392007004C1331430400426C7F2CCC081319DD26200700C2086C9F25326CFFFF046D058B67079026F78582DC"); break; //QDS.07.24250389"); break; //QDS.37.35328909 - case 49 : return F("b2C449344098932353337D4E7728903252493443307C000082004ED39ACEB2C06702501FD0C10046D1C06902602FD705D3CC20189FB81DA"); break; //QDS.07.24250389"); break; //QDS.37.35328909 - case 50 : return F("b5344934409893235333714F478077989032524934433070DFF5F3500D11E82CE0000100007C113FFFF362007007FCFC92C314304009F252620070020029102810C8E02730241023902940287023F0254029062770227012F046D14108D26710885D5"); break; //QDS.37.35328909 - case 51 : return F("b1F44685034025063591B28F57AE100000001FD17000265EF070F6A18DFC253E0F351D0900E8180F9"); break; //TCH.1B.63500234 - case 52 : return F("b294468501904601473F0AE14A0009F27EDA5B130CA280080770001045667006BA1007CB2008DC3009ED4000FE50096BA80EB"); break; //TCH.F0.14600419 - case 53 : return F("b294468508220285376F0F19BA0009F27A1280028A128000033000006BC4C006BA1007CB2008DC3009ED4000FE50096BA80DC"); break; //TCH.F0.53282082 - case 54 : return F("b50446850920420745937163972612600006850FE036B0000200415CC4CB0551F0084041552FC1C0082046C7C228D92D804951F1E72FE000000006F1BF114A212D1B377124C1EA62E83430E5006511443943F9B47F52901FD17002F1F2F800E"); break; //TCH.03.00002661"); break; //TCH.37.74200492 - case 55 : return F("b3644685005012040714351C0A004372900000060DA4E029B5FBAC4123A84170DBBFBF13D06000000000000000000AE3800000000000000000000000000FFFF94A6"); break; //TCH.43.40200105 - case 56 : return F("b3644685005012040714351C0A0009F298F560290E10101A40000FFF719F04E99E7B2F9E2E512000000000000000042B000000000000000000000000000FFFF95A6"); break; //TCH.43.40200105 - case 57 : return F("b8E44A81524229207010276807F680080056E68ED762185BEDAB68F79E2A8BF6562ED1C21105B74E82AA9D1B5E79311AB713DC2F86C785D18F6E470067B86284E8A9D1AF9F36330FE18968F826F8D1B21E99F1C08D45FB9CDBF04B37136E2FC1CED74FF3186482FA3058F6F1BD712BBE20098CCB0C72D51F6013CBB974678706CF2F4D6D0D07A793FE1EC8EAF701AD2BEE5922E69F72C356AC1B009A0E0646B0413DD45802B"); break; //EMH.02.07922224 - case 58 : return F("b374465B2118222001604DDD67A26140000046D1107152A01FD0C06326E796CFFFF0DFF5F0C00083D300001061308131C0BFFFC02FD1700140C7896145559D9FF8032"); break; //LSE.04.00228211 - case 59 : return F("b4BC465B251A16000F193DC2CA083110049244E01394465324604401311FC17067A4F0000000C13714800004C1310BC7A000000426C7F2C02BB560000326CFFFF420A046D2009912682046C9F258C04136643DF650000FFFF80DE"); break; //LSE.00.00000000 - case 60 : return F("b4BC465B251A18000F1950519A08311009E2423003944653252044013781E17067A500000000C13063401004C1310623C000000426C7F2C02BB560000326CFFFF420A046D330E912682046C9F258C04138720452E01000B2780DD"); break; //LSE.00.00000000 - case 61 : return F("b43C465B251A12001F0DDDD95A08310008A2460013144653221587296186735087A4F0000000B6E9102004B6E000041BB00426CFFFF326CFFFF046D2108912682BC3E046C9F258B046E9102000B2A80DD"); break; //LSE.00.00000000 - case 62 : return F("b25C465B251A0000AF1033454A2653205834710290E862400005F3573BB1B9A276422A2271D001D0004C9253580E4"); break; //LSE.00.00000000 - case 63 : return F("b4344A511031008667607CFF48C0031900F002C2531D311006D980E9CC8D28524C6417AE570210710CFF2DB65BFE77C51602690DAEB954A5455A2ABED621B74AC56A79D81390EBFCADC9D3D34F5928DDC"); break; //DME.07.66081003 - case 64 : return F("b4344A511031008667607CFF48C0031900F002C2532D31100BC55A548A0082663A6FC7AE670210710C7B178D748F610E2CB16DE82C823EF83334EF1A0C383FB42DF7BED1846323211FC25DCC1EBF085DE"); break; //DME.07.66081003 - case 65 : return F("b4344A511031008667607CFF48C0031900F002C2533D311004E0C2E86D68C08F425747AE77021071013F9284E0CB5896FFB27D33882716D09F7EC8175FD04516AAE9122E584095D6441E14E5B4ED189DD"); break; //DME.07.66081003 - case 66 : return F("b6E44A511825169584004B8737AB500600554B11A7F9F7DDCAC35695A3191EA83FD79D1876F378419D5AB37CA1BD857243492B6379B258A4831015F9F0D4B7098218D7E7C44421422FCABC0770F0E67C16EFA13ABEE798D58062CDB0F06AA312592C085F046D29B64F7031FE17CC7EC8B44D61FB5E2F37301ACA7AAC025666C802C"); break; //DME.04.58695182 - case 67 : return F("bYB04424341931031950067A800000002F2F0413F1570000046D092EC52504FD17004000000E780000000000004413184B0000426CBF2C840113F057000082016CDE24D3013B4E0400C4016D3128692C8104FD280182046CDE24840413F0570000C40413FD560000840513D1530000C40513CC4F0000840613184B0000C40678D313EA47000084071323430000C40713153F0000840813FD3B0000C4081393380000840913A3340000C4091311310000271081D7"); break; //MAD.06.19033119 - case 68 : return F("b4E44A5113748906370076F917A570040053A66831E6B6C8F06FB2C7CC1F60A673B63ACBF2F6A8D3347D34DE4BBDD4677E5DEC850D5CBBDF5B0AA7095B415EFCD93A124AC3B884FAF226845C439DBF3A2EB486CBE7D8D845383E3E480ED"); break; //DME.07.63904837 - case 69 : return F("b5344A511292535727507858F8C000E900F002C25823E090045AF7EFEDB43FBE691577A82003107105692A21A8C0C7531EEEAC3AA631D6BD790CA1B271C7E3C8860A189DA37AE30E89AB63AD316663EB4AD472596156E592602F8A016E51A5E0D8753"); break; //DME.07.72352529 - case 70 : return F("b5344A5117157367276075A048C00B0900F002C25218501000C373A89C912675F09407A2100310710DEF25F58D772CBD51AD6D2B487ED868B6B086976F51AE52E65D877F0310E9DCA942F4E7F94196DD2821329E662FD4E4C9F20A1A8829C54328EEC"); break; //DME.07.72365771 - case 71 : return F("b2E44A511880110827B0780AF7A4573200592F4DF44D5F258DF39AB376DD4A5BC338BBB2493257A98DC52C5E6299029DC79C01ED377C7F48028"); break; //DME.07.82100188 - case 72 : return F("b3644E61E95413900010E89957212517615E61E3C074230206593240B570397DF6230FBC015B276D63CD694FA79B17D1436E0E9FA1003C0763761BD8FBD67B73A8D"); break; //GWF.07.15765112"); break; //GWF.0E.00394195 - case 73 : return F("b9644FA12237704190007308E7AA8000020046D1E2B862B0413D1EA01F9CA0002FD17000001FD481D426C7F2C4413455C9E8A000084011370E50100C40113A0C200F70100840213EA9E0100C4021349870100735F8403130A700100C40313595301008404C386139F2B0100C4041345FA0000840513BABE2BCD0000C40513DEAA00008406139E8A00A1F700C40613D966000084071390400000C4F6040713E11C00008408136200000087F382DE"); break; //DWZ.07.19047723 - case 74 : return F("bA944FA1283211320020766737A6B009025ADD27B2058B2ADAAE5B5D2C8B11BD295CE460BD1ECA50B533A58FF19F7F9DEC291893DA502B763E7C80CEF440B899C92DE87D5548838FC2C9FF3873927441B0B17222FB14545D2E5A0CF81216074B3DF0911CB99BD20A558F2D873A9A649092F3C7B01BA2BEBCC26A58C0FD99ABC2E5E0E5AFC241A27B8C0863C571C3D586B7E2A06044B792CFE021C21BB1B71BCEE4D5D84E75C3FC0624DF07C8C8832C82140086903FD0C08000002FD0B011116A380EF"); break; //DWZ.07.20132183 - case 75 : return F("b3944934419504913170628617A660000200C13480900004C130000007BBF00426CFFFFCC081315020000C2086C9FC6D42A02BB560000326CFFFF046D1316952B82B084D3"); break; //QDS.06.13495019 - case 76 : return F("b2E446850915944496262C568A0009F276D03B017BC0000060A0A0809A71C0908090706070A0A09090A0B0B080708CB640A060A07071EA586E2"); break; //TCH.62.49445991 - case 77 : return F("b2E446850573060566562ADA6A0007E293200C0191400001400000000CC37000000000000000001010101010101023FB20101010001328686E1"); break; //TCH.62.56603057 - case 78 : return F("b2E4468505162250070628CD3A0009F277D00600A0B00000001010101002A010302010101020102010101010101018F6C0001010102AF7786F8"); break; //TCH.62.00256251 - case 79 : return F("b2F446850302976627462A01BA2064D280000600A240004000307070747EA0706060000000000000000000000000077A2000000000000FFFF80F6"); break; //TCH.62.62762930 - case 80 : return F("b2F446850370321809562C217A2069F270C00A00E0700000001000001B0E800000100010100000101000100000100B1AD000101010001328680F9"); break; //TCH.62.80210337 - case 81 : return F("b2D44653277993613170784017ABA0000000C13196100004C131100003AE500426C7F2C02BB560000326CFFFF046D2ACD2E068527CCD580DF"); break; //LSE.07.13369977 - case 82 : return F("bY24442D2C394864681D168D20AE91BF1622CC255857FE7B524DD67C4944CD428FBE5E0DFAB98021"); break; //KAM.16.68644839 - case 83 : return F("b2D446532490340131706417B7AB20000000C13814000004C13100000A12600426C7F2C02BB560000326CFFFF046D2ACD3B168327AC23800F"); break; //LSE.06.13400349 - case 84 : return F("b3644E61E08106100020E7F627236090021AE4C01071B0020A56EE84D172DF9648D9C4409131B88D3FD85BFD022BCB54971CACB714D617E5563719B95E9B2059A59"); break; //SEN.07.21000936"); break; //GWF.0E.00611008 - case 85 : return F("b3E44FA1287530019011691D07AC6002025D541F7DE1D910813127884F7DF4D80BF600A4323BD730B4639E4E0EA8B86129BDE9DD71D0F800C000109002487721866530201310101B10187CE"); break; //DWZ.16.19005387 - case 86 : return F("b9644FA1261281221000689447AB9000020046D302BAF2704136300009A0E0002FD17000001FD481C426C000044130E9B0000000084011300000000C4011300002A2D000084021300000000C402130000000098F984031300000000C40313000000008404B0D51300000000C404130000000084051300A00E000000C4051300000000840613000000B5CF00C406130000000084071300000000C4E74A071300000000840813000000009995812A"); break; //DWZ.06.21122861 - case 87 : return F("b3E44FA12336300190106617D7A81002025F1114AE0F19A04778065DD02E84809E9F7163157F05FB2506D26A3835904CED370FBCA9E0F800C00010900E10E03000A70207F3101013FFE80E3"); break; //DWZ.06.19006333 - case 88 : return F("b4C44B40968440303170787F77A3F0000000C1305000000046D1A2EAAA19F250F8F00010000000000000000000000D5BD00000000000000000000000000000000FFFF00000000000000000000000000000000FFFF000000FFFF80EE"); break; //BMT.07.03034468 - case 89 : return F("bY29442D2C394864681D168D20B3B0BF162236090AB83DFCC84216131495B5A2DF59242760EDECA043AF20801F"); break; //KAM.16.68644839 - case 90 : return F("b2844C51473278071030605C97237253816C5140006612000202F2F0464E66D243498260413070B010001FD1700146B80DD"); break; //EFE.06.16382537"); break; //EFE.06.71802773 - case 91 : return F("b39449344715387121606AE447AD40000200C13969799994C132798998CC999426C7F2CCC081396979999C2086C9F18DC2502BB560000326CFFFF046D2E0D8D261542803D"); break; //QDS.06.12875371 - case 92 : return F("b39449344724733131706336E7A270000200C13330600004C13100000218B00426C7F2CCC081382010000C2086C9FF2142A02BB560000326CFFFF046D1116952BF4D081DC"); break; //QDS.06.13334772 - case 93 : return F("b3944934451926513180637967A7D0000200C13110000004C13000000B0B100426CFFFFCC081311000000C2086C9F52D52A02BB560000326CFFFF046D3A0B862B28D08027"); break; //QDS.06.13659251 - case 94 : return F("bY5444A85C3281262703077AA90040254A1AED9189683FF741015BCF9C9FD17914758544A14121969793DAA718C7C091F9E26BF16197828BD514A4E66C5460849605A64ACFBD3D3167332F6AF040711E426CBF237DD6802A"); break; //WEH.07.27268132 - case 95 : return F("b394493444993671216075B197AD00000200C13850441004C13286138A3F900426CBF2CCC081345574000C2086CDF284D2302BB560000326CFFFF046D330BD62497B485F8"); break; //QDS.07.12679349 - case 96 : return F("b39449344782049131707F5A37A480000200C13121902004C13000000D42000426CFFFFCC081353750100C2086C9F077F2A02BB560000326CFFFF046D0716952B9ADF80EA"); break; //QDS.07.13492078 - case 97 : return F("b3944934462526513180768437A880000200C13060400004C13000000950300426CFFFFCC081398000000C2086C9F0D7D2A02BB560000326CFFFF046D320B862BCC358012"); break; //QDS.07.13655262 - case 98 : return F("b3C4493440989323533372A78728903252493443307C50000200C133698392007004C1331430400426C7F2CCC081319DD26200700C2086C9F25326CFFFF046D058B67079026F78582DC"); break; //QDS.07.24250389"); break; //QDS.37.35328909 - case 99 : return F("b6644496A721102551437F5C77224831715496A000726005005827161FD1AB240AE86C2A6D7F691E4C8531E4530AE4FC8A294BE87862FDDDCE843D7679005C55E082A744BC18EA87FF12298AE4258EE9D89B1F9511318B8D7152464FB11007F5CEB827893513C954A6E9735185FE268124771D567A080E8"); break; //ZRI.07.15178324"); break; //ZRI.37.55021172 - case 100 : return F("bY50449726041331160007728499181897A60030E10030A59E74DF73596296EE08CF3F1BA88B47A3A264C30177EC921B750B03B99F992A0EEB064560EFAD6C5EC7CCA1009D72C63B0E79000000000000697E801E"); break; //ITW.07.16311304"); break; //ITW.30.18189984 - case 101 : return F("b2F44090773754205100794947ADD100000046D070DBB2404130F000029E0000259F0D834FD17010000000424132B5729820001FD7462313A802A"); break; //AXI.07.05427573 - case 102 : return F("bAE44EE4D449858203C07C8B87A0100A0257AB76440C2E16196857D0FAC3205AFB9D036CD7885C1F60C61095F2300D08DF56026E74FFB2F876BD89D27F65B3EA3729F53B26A48F2A5684EDD67A16177F8DD127C2CD8FE1C42CD5035E7EE110515C7369DB07AA59D5954E077C30AA29423D12429C77F6A7DEF679B3A90D7FD075AB7ED262466ECB4FDB66C609FC5DCBEA89FBCEB7BAA4EB8C312CF1A242B39A696A25E81CF0B5994B25CBDC06749D29F5B0F9E9F98476FFF9ED428840C0082459878C8FCEBA90EC380F7"); break; //SON.07.20589844 - case 103 : return F("b4344010648020780011656A78C001D900F002C2561675D61AD80F90FDEC404B8CAB47A80032007100A387268EF9F9CC8106E5D7FEE6E9D41D7E8780E2B18C2F71743F96AC442C6191D9B8E8E2AEE85F1"); break; //APA.16.80070248 - case 104 : return F("b2E44685017467703627254FFA0009F299A23A02C3A000008060605050B45060504020407060412140C0F0F100E111BFE070B0A0908C0EC81DC"); break; //TCH.72.03774617 - case 105 : return F("b2E446850812896626572C47AA0009F290D1620272200060001018DE4606A430F1101000701010302010403020403DC700002050302E7F581EE"); break; //TCH.72.62962881 - case 106 : return F("b2E44685007457014707281B0A0009F270400600A0100000000000001A1A60000000000010000000000000000010086A10000000000FFFF8007"); break; //TCH.72.14704507 - case 107 : return F("b2F4468509851603374725693A2069F27CE00600A600000000409090A8C140B090C0C0F0E070F0C0D0F0E0E0B0F0A640F09090D0E1009932C80FB"); break; //TCH.72.33605198 - case 108 : return F("b2F446850396450729572A1CAA2069F27FC09800D3E0200000238383EF87E353F3A3B3A2C2E1E2B3830111E2A181FA0AB2432273805208FCC86D2"); break; //TCH.72.72506439 - case 109 : return F("b76447916987435614037E4C2729646866079161102FC0060058CA7D7567090FFC84CBDE4FE2996BF8080E6A2C41D6DF46D8921894C814CC0BB36C550312F03A2734512ED7A69349DE3C349CFC079B62A3AD93BAA84A087C739785EFC9A415F6AF0662B05401FD0C4C9D4A676DA7FC1F6C592823EA6B6DFCAA3DDB2E4D5B6E87C5EA1B92E903D62"); break; //ESY.02.60864696"); break; //ESY.37.61357498 - case 110 : return F("bCE44A8154457060801022C397FDE00C005F4FEE3A225126DE3E8344CEACB8502C615B4F7EE9D9CA99F2E3AB5AEA9B3417ABD2B8DF835C0A8C31510DC184ACC8E261E24B717F51C01887D9B39D57965A4004CB68B65206E173F7374489CCBBAB63D4F4B33B488DE06DD33C93EB719BDD805E331238755D22C7E94ECD3636C1CCD965CF4E6DC37123EA8D95771BBCD12431AD12B646EBD21FCB77BD2D100C9CCCE7546268B3AF080DEC8F6B61BCC209013295A74A2730D9CB11E52056D0C0879EA29A2CE24210EAF58587855A790FF343CF82914E0164ED08C70FD54DB800BD9B1C0F2431883324E43A1D293"); break; //EMH.02.08065744 - case 111 : return F("bCE44A8153132000801022ADC7F9000C00546CF1AA7C3268079B9F3A0D8D2FA9AE941DDCDA1D0D8CFA6358FB9FDAD0AF38BB344D954ADD742E77428CA48DD918F361E23D5805BA6BB4CD2470016F66B92CF5D2AF69C7751F1FD2D887A10F26865E6AD95F58754980416935DA7A6C669823CDA9A3CF4D0EBE233D7F706DA6ECFD8209C8F140A7666B9AB2D9ED8C0D5AE04C3600B0899207B54AA98FABF9D14F773F41A53378E9A5678516B95EDEDED334821972EC196927F794E72725C78300FE65155E4E49B43A329EE44AD6FD90070548499CCFFD9FB71ACB4183B4B14DEF7D8B99BF02B754E651850960D80F6"); break; //EMH.02.08003231 - case 112 : return F("b7B445A1415200000023724958C2049900F002C259D7515009EC6163C4A7CB4E8935772324371005A140102490050646D0710FDE80DD548AA00BBCFEB3152BF11CDD91D4D31F1C73144BBB17696622104EB91D19A32FF7CA939126767495134EE598AF96AC9F0081A83B4D7EAF7A497981C15604C85E4E3B9FBA393BDAD8E0DED15EED4BA887D9970D17797849FE68045"); break; //EBZ.02.00714332"); break; //EBZ.37.00002015 - case 113 : return F("b5B445A14152000000237E4CE8C2048900F002C259C751500003AD02FC41ACF50E92F72324371005A140102480030E8FC8710B2BC8D30DC227AD803ED24F3B6FCC6E5131442E65A7F5CC4589D51AE2FB2690E9457810BDAAAC81988A3E5E81E6CDA24BE256218D907511C8044"); break; //EBZ.02.00714332"); break; //EBZ.37.00002015 - case 114 : return F("b1E44C418557901000102E4437ADB00108561D52810CD237CA87FB7C5A2F62A65C25C457BF78028"); break; //FFD.02.00017955 - case 115 : return F("bY4344471325798761013672193170604713010210000000000004788F419E030DFD110E39313133303730363030475A4431060200000000000006823C0000000000004E88"); break; //DZG.02.60703119"); break; //DZG.36.61877925 - case 116 : return F("b7B447916419806613037A83A8C204C900F002C25B4AA0000CC2128E94C8F3D946F737210552961791611024C00500ACF0710FCFB475BDA1FD49D52FF6FB45E833D5D97609373AB3EB562E01CE23D1389FFEC7EE41D4E7B20D35D8B80581C289834F4E3C542C09B0C37331332A321B79DAF7F60CDB028452AE3A26291EFD25C87DDC9EACF462C7B441510340F755C800B"); break; //ESY.02.61295510"); break; //ESY.37.61069841 - case 117 : return F("b6B44791641980661303756A58C204B900F002C25B3AA0000ABEB03EC47E933C072B97210552961791611024B00400536871036D216BF2B74E11F1952539F406B7D4DB7721B1154E21C36F0F55BD4B5B5FF9977572BCD27D93A909347A0D84AE9E804C361E9E6D98F9BE296F76E3A5100357CDD3FA09AABE44103EB508044"); break; //ESY.02.61295510"); break; //ESY.37.61069841 - case 118 : return F("b73447916856565601002F8DC8C0037900F012C25809A62002B5C02299AC829AD82267A370050471010A9AE87B893A51E226DD3203AD775D84E2B2E04D96F85DA0CD258EBC10912241ACAEB1406433A637F6C70811B7FC99565C388A0365A9B57F94E0380317F5CA1052D6FE55C7BE3118A4D1CD05AABC646F5D288B129D7934E68CF030A"); break; //ESY.02.60656585 - case 119 : return F("b7B447916419806613037A83A8C20EC900F002C259EB10000F76F8B1B2D9E402B9498721055296179161102EC00508B8E0710072DE1601CB75C3C9C30E9E037024B1D516151D3724CCF1654B2AC7707B5B41F2BBE3F2C19E9467A0740AB6A4F7F03F5E764FCCECC689EEA66BB4A9A6F25FDDCE8A60E9D998A43936B8A3B2EB57A8F667B476D1ED1CD2381D0ABF6118033"); break; //ESY.02.61295510"); break; //ESY.37.61069841 - case 120 : return F("b6344FA30883161560002C10A8C2042900F002C2549030000BC2C8A72651D570AEB967AA7004007103CB056B91B8007B8882351411FD19256641A220725C724891287445B85FDFDA2DB638B542BB7FC7209833DE16EDCBB90FA686C10076EED63BD40AB0EE3EBCDFDF40032F5C0DCD66322BE804A"); break; //LGZ.02.56613188 - case 121 : return F("b6B445A14043300000237C1FB8C200E900F002C250E000000F96A649A04D7DC457E8972807759005A1401020E0040A6D20710A07B663F2ED91A0DEA4515C0C1D4A296569A7C4DC27AE724F38CF0E518949AFAA9C3AA8703638F5305E2A333941756C4F5BF4703A411B7FE92F58D7F2724E31E10DCDCBF2C7EE18CD4738043"); break; //EBZ.02.00597780"); break; //EBZ.37.00003304 - case 122 : return F("b5B445A14043300000237FF3F8C2001900F002C2501000000574262230ACE2FB2FB5072807759005A140102010030303C8710DD05806D5E2EFB0ACD9DA1964A191D7BADA8E1A508EE7CFA86336B14B1C756AA1E20FA5CE029D2CE99CC84BFA0CAFD723A5D0BC20E6FECC585FF"); break; //EBZ.02.00597780"); break; //EBZ.37.00003304 - case 123 : return F("b4E447916110000600702D8607A99004005860F4BADF0716289AEE56290239E4549E908B33CF4C280DEDD3382DF293865824ADFA25EBDDDF28DB046A59DA2A4C2DB62CF177F2E77EF3E62D6A67DEA6BBD01BEA1DFB9ED3DC8743E2E"); break; //ESY.02.60000011 - case 124 : return F("bD344A815394643100002D3DD8C2003900F002C25741E0000A17997475A31870EDCF27A0300B007101E6BCA68B34F4603824A04FC0B87A398438300B31366E3B94F45C0EEBFF210719F87A8382509BC329B59A4031ECAC4CA734374B06825329A3665EAA2C626DA096C8E50002E95443D540F92C90EE305931E8E066BB8A76AA2DDE94042B90B94E9D3174612DBF8755145BBA760A85B7C84DDF075950FFDE6EEB4EF76A664F4A474CE050E12021434806BB9C4CDDA5CA8F13DDE8010C5DB0EAD57FD5E6DF042A1C0AE83DA23EB2B8C3444A11D8A6F6274072E516F42DA449DC460D14C1D9E12F95CAE43112976493AD493F0"); break; //EMH.02.10434639 - case 125 : return F("b8E44A815262292070102C8F57FED00800592BC9808524F745DE09733D6B862F1FEF9AC816CCD59B730C0BC8D173E7B1B187735C505768A284780C3B7E53CCF252EC84F44DF2A8949DB12139FD80D000930321F57E73C8F22B44AB57C03F93C35B02B853E45A0960F0FCD326F4760CA76945EADF6F294356AC8308DE284EEBCECB57EDF9BB63BCC5D424A764348B2D40E86C812AEA58CA14B652DD3853BEF5BDF56A27F8031"); break; //EMH.02.07922226 - case 126 : return F("b5344A81594176710020267788C20D7900F002C256A000000E4D7497D043A0EE5B2A47AD7003007102F390C301AD7CB52A6B8633CA25AA26BDE4FDF94F3230E43CCBF66EEC0D4C0C7A6E66310DC6376768FE891C0CA84DD365D3FBF23690BB55E812B"); break; //EMH.02.10671794 - case 127 : return F("b2644AC482711000050378A347201271100AC4850021B0000002F2F0C01C103895936002F2F2F2F2F2F2F2F84F48029"); break; //REL.02.00112701"); break; //REL.37.00001127 - case 128 : return F("b7C44361C120001000002CA9C8C203F7A3F00000004050000000004FBCBA782750000000004FB82F53C000000000412B22A0000000004FB140000000004FB943C59F00000000004FDD9FC010000000004FDD954D8FC020000000004FDD9FC030000000004CF6EFDC8FC012909000004FDC8FC02F10400CDDC0004FDC8FC03F104000002FB2EF40101DC37FD1700DE8C80FE"); break; //GAV.02.00010012 - case 129 : return F("b3244361C373601000102603A8C20457A4500000004050900000004FBDA3482750800000004FB82F53C0000000004C3AE2A2415000001FD17001EB2801B"); break; //GAV.02.00013637 - case 130 : return F("bY394447135523636001027A830000000478232D9D030DFD110E35353332333630363030475A44310602ED8B0000000006823C00000000000004E4802B"); break; //DZG.02.60632355 - case 131 : return F("b314493447236379635085D337A040000200B6E3103004B6E070300420D0E6C7F2CCB086E310300C2086C9E24326C1422FFFF046D240E86250E4E80E2"); break; //QDS.08.96373672 - case 132 : return F("b494493447236379635083FD9780DFF5F350082030000F00007B06EFFC6F5FF310300007F2C070300009E24310300E13000008000800000000000001F007300A145C9006BFF64003D000C002F046D200E86255AA081DF"); break; //QDS.08.96373672 - case 133 : return F("b494493447153871216062D53780DFF5F350082DA00007F0007C113FF8331FF969799997F2C279899999F2596979988C999000000000000FFFF000000000000008B35000000FFFFFEFF00002F046D25068C2608D2803F"); break; //QDS.06.12875371 - case 134 : return F("b344493447153871216069A667A8000082004ED3926096C2701FD0C110157046D17138F2602FD3CC2010DFF5F0C005FC50861FF000006130701FFFC138B803F"); break; //QDS.06.12875371 - case 135 : return F("b3D44934417196746221AD6FF7AA210000081027C034955230182026CAC3BB62181037C034C41230082036CFFFF0238E2FD171000326CFFFF046D0311B62102FDFE8FAC7E19004F33802D"); break; //QDS.1A.46671917 - case 136 : return F("b3744934417196746221A40247AA310002081027C034955230182026C5AE5B62181037C034C41230082036CFFFF0238E2FD171000326CFFFF046D0311B621AD04802B"); break; //QDS.1A.46671917 - case 137 : return F("b39449344775149131706E1D57A680000200C13750000004C1300000010FC00426CFFFFCC081358000000C2086C9F61212502BB560000326CFFFF046D070B8A268D5880FA"); break; //QDS.06.13495177 - case 138 : return F("b4944934477514913170662C2780DFF5F3500827B0000810007C113FF5A69FF75000000FFFF000000009F255800004659000080008000800080008000800080009B248001000000000004002F046D1F0D8A266CB280FA"); break; //QDS.06.13495177 - case 139 : return F("b3744934484145047231AAE907A9600002081027C034955230082026CD1DAFFFF81037C034C41230082036CFFFF02DDAAFD170000326CFFFF046D0503AD21AB3580D9"); break; //QDS.1A.47501484 - case 140 : return F("b39449344843350131707BA4A7AF00000200C13000100004C13000000C11300426CFFFFCC081361000000C2086C9F6DF62502BB560000326CFFFF046D370A8A26486681E6"); break; //QDS.07.13503384 - case 141 : return F("b49449344843350131707395D780DFF5F350082000000EE0007C113FF4BC0FF00010000FFFF000000009F256100009FB5000080008000800080008000800080009B248001000000000005002F046D020D8A263BD880E7"); break; //QDS.07.13503384 - case 142 : return F("b3C4493440989323533372A78728903252493443307C50000200C133698392007004C1331430400426C7F2CCC081319DD26200700C2086C9F25326CFFFF046D058B67079026F78582DC"); break; //QDS.07.24250389"); break; //QDS.37.35328909 - case 143 : return F("b2C449344098932353337D4E7728903252493443307C000082004ED39ACEB2C06702501FD0C10046D1C06902602FD705D3CC20189FB81DA"); break; //QDS.07.24250389"); break; //QDS.37.35328909 - case 144 : return F("b5344934409893235333714F478077989032524934433070DFF5F3500D11E82CE0000100007C113FFFF362007007FCFC92C314304009F252620070020029102810C8E02730241023902940287023F0254029062770227012F046D14108D26710885D5"); break; //QDS.37.35328909 - case 145 : return F("b624468501509651494085A758C002A900F002C25DECE080028421E778E39B0665A707ADE0030071080A1255F4529D6628D1BD017B641EF0A8046B680C77DAB285B3C2A943522663000821E48556555C93D048F5DF327EFA4BECCA1914A1C0F9EFCE6532756F5E9BF68B0E01E8802D91270"); break; //TCH.08.14650915 - case 146 : return F("b31449344401892903408DFBB7A9B0000200B6E4800004B6E14010042A7D46CBF2CCB086E480000C2086CDF23326CB9BCFFFF046D0E0BD624CF16800B"); break; //QDS.08.90921840 - case 147 : return F("b314493447236379635085D337A040000200B6E3103004B6E070300420D0E6C7F2CCB086E310300C2086C9E24326C1422FFFF046D240E86250E4E80E2"); break; //QDS.08.96373672 - case 148 : return F("b3444EE4D774933221608D1BB7AF7000000046D280DBB24036EEB000059A9426CE1F7436E00000002FF2C000002598620C80B0265F10802FD66A000779E80EA"); break; //SON.08.22334977 - case 149 : return F("b2E44B00971280018540843227A0E0000202F2F036E0000000F1000016F1B8563822A0000E70A812A000000000000AF010000000000FFFF81DD"); break; //BMP.08.18002871 - case 150 : return F("b2E44B00971280018540843227A0E0000002F2F0F0000000000000000718D00000000000000000000000000000000FFFF0B2A00002FF97381DE"); break; //BMP.08.18002871 - case 151 : return F("b26446532392238253508D6A07AAF0000000B6E0000004B6E000000422F866C7F2C326CFFFF046D2D0A83279B6780E8"); break; //LSE.08.25382239 - case 152 : return F("b62446850189272149408C4E28C00CF900F002C2564B20800A81E8BCCCA14532696097A6400300710F61F531C03426B9317EAD69912E862AC2017148D8C179D50E133470B7A04CD063D00E6FB1C522DDDC80A3300335E26FE16F9D339A61C0F41BC1ACF4A0A8A29310674536C344CA64D7D"); break; //TCH.08.14729218 - case 153 : return F("b434468501892721494083F2A8C20CD900F002C255FB20800903D2653461ABE7080E57A5F002007103032214C1915B7A16175B00F90B8EB889A4C280207D9C74F5A4088C584D951BDB20851C3DF9E"); break; //TCH.08.14729218 - case 154 : return F("bY2F44C5145935816213087A080000202F2F046D0F2EBB24036E000000426C9F25436E000000317F00346D00200000F1458737"); break; //EFE.08.62813559 - case 155 : return F("b3E44F536861202000108F54A7A9B0020250A86DF3204D8B7DAFBD62C57159092FFCBB77F612E656C59AD065C96CC24B353B024A6930F800C00010900E10E03005DC1207F1B05074A5380F2"); break; //MWU.08.00021286 - case 156 : return F("b2E446850844616516180E459A0015C284604600A7200003900341D9A5C477E9ABC873D05000000000000000000019BB4000406456E897887DD"); break; //TCH.80.51164684 - case 157 : return F("b2E4468506974806164805122A001DE26EB02900D570200000000040D21820D4E301126353030334B44170B0A01004EE40000000000FFFF83D9"); break; //TCH.80.61807469 - case 158 : return F("b324468500441169269802F7CA0119F272F01600A3B00C8082F09000007730000000500000220140B1807030000007111000000000000000001C29A85D5"); break; //TCH.80.92164104 - case 159 : return F("b33446850710351129480ABE9A20F9F270000D00E03000128090B0900DA3600000000000000000000000100020000274800000000000000000000FFFF80E3"); break; //TCH.80.12510371 - case 160 : return F("b4944C51402203571000451A77A090001202F2F046D2E299926040687ED7214000001FD17000413F6670400043B009378000000042B00000000025B1900025F1986B90002610A0003FD0C05000002FD0B3011F52BA393"); break; //EFE.04.71352002 - case 161 : return F("b2844C5146427807103073D877234626016C5140007D72000202F2F04DD1C6D2F3498260413B96E010001FD1700066080E7"); break; //EFE.07.16606234"); break; //EFE.07.71802764 - case 162 : return F("bA644C514960080900307DABF7296008090C5140007ED0000202F2F426E8D6C7E2944133C08000001FD1700840113039188130000C40113D611000084021324108D5A0000C40213A70F00008403132C0F00003550C403137B0E0000840413C0080000C404749913650800008405133C080000C40513AD831B07000084061380050000C406138401006999008407133A000000C407133A00000084286B081300000000046D332A8F260413A7137132000003FD0C02032102FD0B01114D8480FA"); break; //EFE.07.90800096"); break; //EFE.07.90800096 - case 163 : return F("b4806AC1956030015020363917256030015AC19020323000000466D0009780004872B000D7811313136353330303028633531343530303030308940FD1A014C933B263A494700004C130546000001FD67030D9A8030"); break; //FML.03.15000356"); break; //FML.03.15000356 - case 164 : return F("b3644A511621280223837CE5E7241022436931581038C002005A8F458578136DF07A14CE37F82BA702DF936647F231F18C961CF7A6CE31CFAACE57153A8888B"); break; //ELS.03.36240241"); break; //DME.37.22801262 - case 165 : return F("b1F44685034025063591B28F57AE100000001FD17000265EF070F6A18DFC253E0F351D0900E8180F9"); break; //TCH.1B.63500234 - case 166 : return F("b294468501904601473F0AE14A0009F27EDA5B130CA280080770001045667006BA1007CB2008DC3009ED4000FE50096BA80EB"); break; //TCH.F0.14600419 - case 167 : return F("b294468508220285376F0F19BA0009F27A1280028A128000033000006BC4C006BA1007CB2008DC3009ED4000FE50096BA80DC"); break; //TCH.F0.53282082 - case 168 : return F("b50446850920420745937163972612600006850FE036B0000200415CC4CB0551F0084041552FC1C0082046C7C228D92D804951F1E72FE000000006F1BF114A212D1B377124C1EA62E83430E5006511443943F9B47F52901FD17002F1F2F800E"); break; //TCH.03.00002661"); break; //TCH.37.74200492 - case 169 : return F("b3644685005012040714351C0A004372900000060DA4E029B5FBAC4123A84170DBBFBF13D06000000000000000000AE3800000000000000000000000000FFFF94A6"); break; //TCH.43.40200105 - case 170 : return F("b3644685005012040714351C0A0009F298F560290E10101A40000FFF719F04E99E7B2F9E2E512000000000000000042B000000000000000000000000000FFFF95A6"); break; //TCH.43.40200105 - case 171 : return F("b5E44496A973430000D1AFCF97AF7CB50057AB6BD92ED8A65D4DB8A19DDA1B6D3CD164A0F600C93485BCFC48263B255C4FC57033B6114A4DD1590F6E3B22855F7161BBFB100973B49CDB3593DEC5164ECF04F1CAB0311E89B873ECECCD7FBF0D60D125734B551865D12F7D012748025"); break; //ZRI.1A.00303497 - case 172 : return F("b4E449344492512971437674F72090328039344141A7510002081027CB4ED034955230F82026C8927C1027C0354466C2C230FC4026D2C0E892781037C034C412307BC3282036C892702FD171000326CFFFF048C536D2809BA22ECC883DC"); break; //QDS.1A.03280309"); break; //QDS.37.97122549 - case 173 : return F("b4E449344505427971537894372380266079344151A6203002081027C8B4A034955230282026CFFFFC1027C035446B3DE2302C4026D1107632681037C034C41234AE90082036CFFFF02FD170008326CFFFF0469C16D000BB822FEF28024"); break; //QDS.1A.07660238"); break; //QDS.37.97275450 - case 174 : return F("b3E44934435188745211A19F67801FD080D81027C034955230182026CF8CB932381037C034C41230082036CFFFF0359F5FD17300010326CFFFF046D060A8428027576FDAC7E5F0192628012"); break; //QDS.1A.45871835 - case 175 : return F("b3E44934455319745221A29607801FD088A81027C034955230F82026CB7439C2381037C034C41230082036CFFFF037174FD17500010326CFFFF046D260B862B02842AFDAC7E8200271080FB"); break; //QDS.1A.45973155 - case 176 : return F("b3E44934404155047231ABDD57801FD084C81027C034955230082026CCC02FFFF81037C034C41230082036CFFFF03E0CFFD17000000326CFFFF046D080DAC2102EA18FDAC7E11005C3E80E0"); break; //QDS.1A.47501504 - case 177 : return F("bY60442515485001000C1A7A23005025568AED71E43AF834900BEC738E08C4FA2637B8915FB401FD6296F19C3AEECEEBC3164B967CD5445E6AAFE90F416314191CB1839210B7CD2EFE168911FD465DAB56CCDA9C82862B90F29353AB57532B49E67E"); break; //EIE.1A.00015048 - case 178 : return F("bY75442515639716000C1A8C208A900F002C25A7000000DDCE55D203E089D07AA800500710760097C0A7F24E9681882D62EFF802EC33146C9B3828FD5B2026432F40E7098DA78C4538579DDE260B2DCCE933093DA312A2C499D4473F150422121279632724B2FCB44A2110D2A2DA87B8C084512CDD698F"); break; //EIE.1A.00169763 - case 179 : return F("b5E442515954400000C1AA17A7A74005025F6B841CED5F1892796E8217F31F08E864EF5C0BBBDFE640AE3711C34C4B5B8AE3B821F1F0F3FF81C8259CBDABD6D05A0A751305C3399E9450DE8E86BE0BDB2D7AFA79BB10179B7EB1F37983CB8C7F10746888DFA54B4CD95AB8C78018025"); break; //EIE.1A.00004495 - case 180 : return F("b5E442515695800000C1A452E7AF200502547F361FEDBED8040998A4AF106CE368D0D469CFE69DD1983E5D5A84078AB4F4F2AFD97FD57668A660038C14F4B79CC3CAB703C3C4ADB6B19AE027E25C4E157B23E9A4D5A7CF24F4962BD29591A73F3E3BD13C9F26D15826364C2DFB28051"); break; //EIE.1A.00005869 - case 181 : return F("b37449344981002451F1AF6837AC418002081027C034955230082026C7CB5FFFF81037C034C41230282036C6D2A027047FD178400326C962A046D080FA623CD2B8AD1"); break; //QDS.1A.45021098 - case 182 : return F("bY5844972642631092001A727299291897A60030B21340A5C84E203CFF62E039C2A1F61CF679A05B7DA7F31E4F0AAA0F30EFFECF4AC176AC9E173C426799C618D50A4B285CBB9074CDF78FA733C7AEC4F66C45D760FDFEE2327E8016"); break; //ITW.1A.92106342"); break; //ITW.30.18299972 - case 183 : return F("b3E449344981002451F1A2CED7801FD08FB81027C034955230082026C61FAFFFF81037C034C41230282036C6D2A034D22FD17840018326C962A046D0A0EA523028B4BFDAC7EF0002E078AD5"); break; //QDS.1A.45021098 - case 184 : return F("bY5044972694376092001A7A3E1340A553D753D0583A78B2BC306DF80BA1DBF6FC88DAFCD83AF7D955EC6196B643A571494B99AE831F8894A4726042E23EA5C474411A585EEEEE8E84A15F54562C31168159804D"); break; //ITW.1A.92603794 - case 185 : return F("bY5044972625353893071A7AE40040A517031B1A58F312C641E1E45D19F6DD3EA9B61FC929D70F39747F6A8A3BA53D7ED25B2A86C741728467DADC3652D54F40660B397E72F60CE3434006A1D843B3248D9C8016"); break; //ITW.1A.93383525 - case 186 : return F("b1744242349075722754982E471F202000001FD0C6202FD173000CFB38EF8"); break; //HYD.49.22570749 - case 187 : return F("b1744242349075722754982E471F602000001FD0C6202FD17300044308CEF"); break; //HYD.49.22570749 - case 188 : return F("b1744242349075722754982E471FC02000001FD0C6202FD173000B52092F0"); break; //HYD.49.22570749 - case 189 : return F("b1744242349075722754982E4711402000001FD0C6202FD17300084489CF0"); break; //HYD.49.22570749 - case 190 : return F("b63442D2C272951803504DCB48C2064900F002C251664000038AA5E8D66325C253B6B7A6400400710EDD4746D6402CF31496EE7AE09E634270E5701ED9E5D16E7A5A22EBC15B0CB6AC0EF980F73E5AD3BF6E658AFC24F614AFBF844AD1EC8CA21C0FCF8FC2E9E64C0B28542EE8C7EA37B444A8036"); break; //KAM.04.80512927 - case 191 : return F("b5E44A7329022226704041EDE7A7500502599F38B5BB9B53F705A6B158D76D3C33F390AE5F6E48A051680C3B866F317F4DBC781E920051DB2619F768DA54EB632DA8746E483A5569A9D8C0E16905ED61857D1B9A07C6EB6AE501B22E0D55A7DD4AF67DB88D6DDCCA5B78E5B88528014"); break; //LUG.04.67222290 - case 192 : return F("b60446850090510825937364F8C0039900F002C2584340D0063667A2334810387B25C72989280612423FE048500307DA907106591F67C43C0B36FCA410346B6A06E7CAADE06D06CF2911ED2775E3297F20105876C245C9309EC93EA0F68B3F9FA466371C73B4A39B80FEBAD1F9C40758028"); break; //HYD.04.61809298"); break; //TCH.37.82100509 - case 193 : return F("b62446850189272149408C4E28C00CF900F002C2564B20800A81E8BCCCA14532696097A6400300710F61F531C03426B9317EAD69912E862AC2017148D8C179D50E133470B7A04CD063D00E6FB1C522DDDC80A3300335E26FE16F9D339A61C0F41BC1ACF4A0A8A29310674536C344CA64D7D"); break; //TCH.08.14729218 - case 194 : return F("b434468501892721494083F2A8C20CD900F002C255FB20800903D2653461ABE7080E57A5F002007103032214C1915B7A16175B00F90B8EB889A4C280207D9C74F5A4088C584D951BDB20851C3DF9E"); break; //TCH.08.14729218 - case 195 : return F("b48442423240756341200933E7AA0303A31A074C27AB1B79A4BF71E2818DAA788D064B94612F5AED2C06E10F1A022615C7EA6E4E97D999D450C33C358340DCB3D948F7F9B58E2B922ED1A580BA905B4E34388A184D5"); break; //HYD.00.34560724 - case 196 : return F("bY5044972670314082001A7A120040A5AE9E0AFE53E0E5216516C41E94CCEA4220BC25A8A5CCF38635E315900BA6BEB0CB5E343BF419524FFED59CD28D106314AC875F9812782700A8267FFCDB4A24251099804A"); break; //ITW.1A.82403170 - case 197 : return F("bY5044972670314082000A7A7A0040A5CE2F908D1B6C7FAB19CF06F0EE140BB44A84C7FF54BDA05D33D6EE45686D054984A4467283EDA6514E8094E361082D9555BD043A64D3F593BAE29C577984921E3CBC92D7"); break; //ITW.0A.82403170 - case 198 : return F("b3B44931536000000013721968C30AA900F002C25BBD101005A558A6E08987D124F027212334938931581036C0010657B0710B23E83B33BD1C8F5A3AB0DAFCEFB35C1D44718C68011"); break; //ELS.03.38493312"); break; //ELS.37.00000036 - case 199 : return F("b2E44B05C95720000021B556C7A500020059C8692AAFADBDBCAA36875B54901F2E32655B735A59AB899223306CD8402A02D189C816E86BB8061"); break; //WEP.1B.00007295 - case 200 : return F("b3B449526564412004237BD198C20E7900F002C25641C00002B7452AB08B6F3843AA6725644120095264203E700108D660710B78BE5BE2867BD38DCE24619A59D0A6BB9384E3A80E2"); break; //ITU.03.00124456"); break; //ITU.37.00124456 - case 201 : return F("b2E44A5111863054230037FB57A30002105DE7DCE381F74E06136FEB49A5B3D45B688341DDCF387CC0D6344DF5BF60078C7A596B1A0D3BE8020"); break; //DME.03.42056318 - case 202 : return F("b1B44A5110301808238379BA77241022436931581038A88000002A7184A0AD900E327803A"); break; //ELS.03.36240241"); break; //DME.37.82800103 - case 203 : return F("b2644AC488437000050379BF37201843700AC485003210000002F2F0CFB6E16038161002F2F2F2F2F2F2F2F2C138057"); break; //REL.03.00378401"); break; //REL.37.00003784 - case 204 : return F("b3644496A855900500537290F7285590000496A0103CC002025CF5138AEB21021CE8339724700D0AF89B3CDDAAE2BAD28479AC27ADBE1E3C3B849269E632772E54C"); break; //ZRI.03.00005985"); break; //ZRI.37.50005985 - case 205 : return F("b1B44A5110301808238379BA77241022436931581038F88000002A7183766D900E3278003"); break; //ELS.03.36240241"); break; //DME.37.82800103 - case 206 : return F("b3B449526404107004237CE7B8C2076900F002C25832C020029B8AE8C5A07F337A5EF72046318043041000375001016F10710C9B050BA32F8730DFCC9465A1C3385BA84D8190D801B"); break; //PIP.03.04186304"); break; //ITU.37.00074140 - case 207 : return F("b3B4493154809000001379A2E8C2028900F002C252907000090080FACAA513D7A7B7B729549120430413A03CB001054A107101672D7C39DD223F7A8D3C4FFF7E9CA294237B1AD83DE"); break; //PIP.03.04124995"); break; //ELS.37.00000948 - case 208 : return F("b1944C418637901000103C9CE7A690000A00414EE75620202FD08EAF2A1B38039"); break; //FFD.03.00017963 - case 209 : return F("b3B449526544412004237036C8C2065900F002C25943B00000CC9351967A937062362723991270492262203650810BBE30710483A983059AFE3C21836C683F23BA2B5A04EBAE38021"); break; //ITR.03.04279139"); break; //ITU.37.00124454 - case 210 : return F("b3B4493154909000001375BA68C20BD900F002C25BE0200004F4E34B29075C9986ACB722009210493150003B10010E2D407103C75563866FAC12EBCE6FC5829323547A3CC9A488057"); break; //ELS.03.04210920"); break; //ELS.37.00000949 - case 211 : return F("b3B44931558130000013727848C20DD900F002C25DE0C0000ADFB2D6B2E02C73267AF72011713419315250339001037AB0710A68555FAD7A29A1A875EE33FD26D13EAE33A7AF08035"); break; //ELS.03.41131701"); break; //ELS.37.00001358 - case 212 : return F("b3644A511870350133837198E7280409628931580038F002105F09DD43A485B949F2AB07122E6A6CB3E4BDF9055A351316D145C3EF8522BDF56D9D12495B281D5B0"); break; //ELS.03.28964080"); break; //DME.37.13500387 - case 213 : return F("b3644A511621280223837CE5E724102243693158103850020050075969BAC2F21DD6DEDFFDBDDCF9F0E9A7E066046DF920DF3BFE4DA1174DE1943F173A93C0E2A5B"); break; //ELS.03.36240241"); break; //DME.37.22801262 - case 214 : return F("b1B44A5110301808238379BA77241022436931581038F88000002A7183766D900E3278003"); break; //ELS.03.36240241"); break; //DME.37.82800103 - case 215 : return F("b2E44B05C10130000021B34137A490000002F2F0A6653020AFB1A5105E7D102FD971D00002F2F2F2F2F2F2F2F2F2FDF772F2F2F2F2F25EE8016"); break; //WEP.1B.00001310 - case 216 : return F("b2E44B05C77010000041B1E0B7AE60000002F2F0A6641020AFB1A8103251802FD971D00002F2F2F2F2F2F2F2F2F2FDF772F2F2F2F2F25EE80F9"); break; //WEP.1B.00000177 - case 217 : return F("b4E44333081400004032AF18B7A03004005E5AEF57BA739B9789BD9AAFAE16068AFF9FB85A70613B3800B85D28B409B5BDD9607BE2A1450EEE20137C663FB522F4E6B9E914E8E3795C577BFB32D6E0152C21EB0358C41DD6D9F76498020"); break; //LAS.2A.04004081 - case 218 : return F("b1E443330886102001E1B8E9A7A180000202F2F026584030778281E18ADDC240B0000AB01768017"); break; //LAS.1B.00026188 - case 219 : return F("b2E443330603903003C1B18AF7A77002025BB29E575E63C4AA6174A82CED44D5497FA0207F44D4518D8CFA53CD7024276A675D4E0621C0F80E5"); break; //LAS.1B.00033960 - case 220 : return F("b7044B40908794920101B6F8B7A320000000265490A42651F0A8201650BCEB9072265D70912655F0A62659905526576FB4F0A02FB1ABD0142FB1ABB018201FB1A36B6C60122FB1AB90112FB1ABC0162FB1AC24B1C0152FB1ACF01066D3B1D2EAA25000FFFBE95FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCA00FFFFFFFFFFFFFF7B5382DE"); break; //BMT.1B.20497908 - case 221 : return F("b60449615347810630C1BE5957A8C0000202F2F026505094265F908822DF20165880822658C081265680A62655808B1785265680A02FB1A880142FB1A95018201130DFB1AE00122FB1A660112FB1A100262FB491D1A660152FB1A1F0202FD1B60030DFD0F817605302E302E340F6C04"); break; //ELV.1B.63107834 - case 222 : return F("bY2944961565181468201B7A140000202F2F0265E20842659A0802FD1B30030DFD0F05302E302E340F5E46"); break; //ELV.1B.68141865 - case 223 : return F("b1644AF4C40020041011B788C7A0A0000000266F3000266F10026FE800F"); break; //SEO.1B.41000240 - case 224 : return F("b5E442515695800000C1A452E7A590050252502B45C2E823EF856FABFC4775E28D2904492FB74BA65A843BA7E63BC698D83279AE2BE04B957699517818F7B33F8E58001A7C9CE0E73C5769487247954D0A000E811754BAB1CED0E61567EB1FE504B091E0C7DCF4632E3FAC31618804D"); break; //EIE.1A.00005869 - case 225 : return F("b5344E2306291001500030F388C30A7900F002C2583AE010032E1E493C32BEF51CDA37A430030071027A19EE14B0BBCAD656D0783516CCB7CFBC6AAFAECDCAD70020FE3DA54FCBC8EC2AED88DFD0972C55CF9336E1683574ABADBD046BB53623F8013"); break; //LGB.03.15009162 - case 226 : return F("b5344A732806139690404B70A8C2063900F002C25923338000C8BC361CE2EE050FD3B7A6340300710DEC49523134391877289A80A53A505655A833F754F221E619D08FB4DB5AD773EAB16B545B306C69D1493CD851012BBF4624A5DDA556AF07E83E5"); break; //LUG.04.69396180 - case 227 : return F("b9644A732460335700A043C1F7ACD0000200274DE02046D030D94250C804C0623000000446D3B177F2C4C060000005D610084016D3B179E248C010600000000CCB81D0106000000008C020600000000CC0206B190000000008C030600000000CC030600008B9400008C040600000000CC04060000000099348C050600000000CC0506000000008C0673DA0600000000CC0606000000008C070600E0F80000003C22000000000F0010004FF980F9"); break; //LUG.04.70350346 - case 228 : return F("b13440000000000DA00DAA8CF7101FD0C3A02FD171101CB938032"); break; //@@@.DA.DA000000 - case 229 : return F("b63445A146699750001026CE68C20D7900F002C25D7CE0C000B7C13179B38522166CE7AD700400710CDEF8D2A82F77DD15E367871F1E04261AAFAC430C2B55C1DED4A3148306D4C296CF10D72C9E79310A47DD73FDBFDF2CEA6490B6CA12A30EE5D64621A90B5E71F75D50D24C87B10E2ADDF802E"); break; //EBZ.02.00759966 - case 230 : return F("b5E44496A3680003888049D2D7A1D0050053FBA7B54810C548AC112ECFC76CE753AF07A625248C05827C843371AB5DC6C6C8D5D457E845B4B67FB4CEFF06720EA7A9112BFD0A96BC7E97D49FB9BBD59155D109433F0C4823DEA7A13E5281C00E4945F5B05D7518CE085EC8BFE738122"); break; //ZRI.04.38008036 - case 231 : return F("b1B44A5110301808238379BA77241022436931581038A88000002A7184A0AD900E327803A"); break; //ELS.03.36240241"); break; //DME.37.82800103 - case 232 : return F("b3E44B405399098502E0439DC7A0F70300530C7144B5962760A55DE8BA4E49C37676B0CD702698B5FBCE59E35E33D33F736AE13FB1C31DD43ADDC3FE7FF8E1EDC01D749974884BBF96580FE"); break; //AMT.04.50989039 - case 233 : return F("b5B4479169014216130377E5B8C2034900F002C256448000029E1A134984E32773DF97289000047791611023400302CBD0710FF4380B4AE49A140E94F319BE97049FDA8DDC96A8DC437F3BFB02ADC86082E9507934C7ED4FC6F4F678D613F25C09A1DDE927D817F5A824A8012"); break; //ESY.02.47000089"); break; //ESY.37.61211490 - case 234 : return F("b7B445A1415200000023724958C20A0900F002C25B4F60800C0E1D417100AD1BB6EDC72324371005A140102A00050F9B10710112A08DB9869998DE2C0D8614F76213F8682D10A8EF2951413C839461E8E3139EA62193E02B1584E6EC8EDB082AB70C6504F1ADF9E6ABD270E96FE8745AEB93C454FC3C9EAA2D5FCD6679E8A38A3E0818D4B6652993CEE5F8E514867801D"); break; //EBZ.02.00714332"); break; //EBZ.37.00002015 - case 235 : return F("b49449344100549253508227F780DFF5F3500824600007E0007B06EFF2BE9FF000000007F2C000000009E24000000DAC000008000800080008000800080000000E9F10000000000000000002F046D010E8625249880E6"); break; //QDS.08.25490510 - case 236 : return F("b9644A732370335700A045D187A440000200274DD02046D071189260C33FD0638010000446D3B177F2C4C06000000DFDD0084016D3B179F258C010641000000CC0DBA0106000000008C020600000000CC0206B190000000008C030600000000CC030600008B9400008C040600000000CC04060000000099348C050600000000CC0506000000008C0673DA0600000000CC0606000000008C070600E0F80000003C22000000000F0010004FF980E0"); break; //LUG.04.70350337 - case 237 : return F("b4806AC1956030015020363917256030015AC190203D8000000466D00AC9200118625000D78113131363533303030619C3531343530303030308940FD1A014C933B263A494700004C130546000001FD67030D9A8057"); break; //FML.03.15000356"); break; //FML.03.15000356 - case 238 : return F("b9E44A815242292070102881F7F4B0098050B0E989C939C3479F8904137236FE582B853DCACDD8DB48A717A6B42935CB977102079E6397B07AAD2A648E5B65E44D97F9A020B2BFAE433FA37FCB2A2C32711A5986B301D2F6E4A424C1144D808CE9D592C316B117C572689AC6C1322CC05E81F590CAAF457390F6B39DACC946FA314F8E8A34268157AC4338781C3EF5807F9221394DD1FAB5165E1261614B8B85758851295334DF52D9A4DCE2E1E17A555A21007D2DA802B"); break; //EMH.02.07922224 - case 239 : return F("b2E44B05C99010100021B65BE7AC30000002F2F0A6605020AFB1A33041AA002FD971D00002F2F2F2F2F2F2F2F2F2FDF772F2F2F2F2F25EE8008"); break; //WEP.1B.00010199 - case 240 : return F("b2E44B05C75000000041B8EF87A510000002F2F0A6661010AFB1A8905449802FD971D00002F2F2F2F2F2F2F2F2F2FDF772F2F2F2F2F25EE80F8"); break; //WEP.1B.00000075 - case 241 : return F("b1E44C418EA76010001034ECB7A820010A5597AB1CCF2014088590E64BCAB21DC1CC068DAA08035"); break; //FFD.03.000176EA - case 242 : return F("b4E442423245450514A07545F7AC6004005BDB5EDF3750DF41725EE867C3E39750E20B9F5FB092089B6A5DC7AA586101778BCD5EAD4995B102AC639F0FB4D12403EFE3554F72CAE4F5D348CC374F571CCE4A98634318027AAF3E7DD8600"); break; //HYD.07.51505424 - case 243 : return F("b3C449344454392352337ABFC727972126793442304FF0000200C050321648206004C0530240500426CBF2CCC0805098875630600C2086CDF23326CFFFF046D177FEA11D3242E1F830C"); break; //QDS.04.67127279"); break; //QDS.37.35924345 - case 244 : return F("bY5E449726900634160004728499181897A60030D30040A5D20022C396367C6A868B05FE2E9F70B6878070839C62E69DDC79CB409EBCAD68E950A6958B9B92FE6D1B700065B8BD52CB3035B93653FFDA3C3EF3EF6BBB1B450C7976874216AD188011"); break; //ITW.04.16340690"); break; //ITW.30.18189984 - case 245 : return F("b3444A73228188269040439327A370000202F2F0974040970080C06348AF03500000C14040301000B2D6207000B3BDD4A2322000A5A05090A5E0506F29780E8"); break; //LUG.04.69821828 - case 246 : return F("b9644A732170135700A0476A67A2F00002002747501046D3315952B0C3CF20637000000446D3B177F2C4C06000000F4640084016D3B179F2A8C010636000000CCB85A0106330000008C020633000000CC02067E6A330000008C030633000000CC030600009A1900008C040600000000CC04060000000099348C050600000000CC0506000000008C0673DA0600000000CC0606000000008C070600E0F80000003C22890200000F001000C94A80E8"); break; //LUG.04.70350117 - case 247 : return F("b5E44496A044550108804533A7A19005005B14A145C0EEC7C6FC18A85582753BE2BD08B6F0F9320809C5F28436A3388BB09145DF69DA51ED00DCCFE7BA2D6F86936D17E654489390BB7511CC4DA27A576A9612B1243151C634899A74D3EBDB2CA578CA4444F5740D74D3EC766628013"); break; //ZRI.04.10504504 - case 248 : return F("bD6440186669601001637060C7215067071010660047400C0251CE72725D9364386C4BF8A15AF4C722115F38B66DDE9F4B800D05A14F870E74492B8F57CB2282388B864D71416FDE0A74CA84735B59D1806BBBF8483B6B14AB580F0D5257E8CC06889EBA80AFB032BCEECD849E7E6A4639B5649B0BF5CBB57BF0993AE16EF7D88D784ADD9CE09284880D14D1502967798901C9E24ED6A04A3B26AE580B5D2AEC46804DEC1CBEDCB1E0B3FE1F84679620FD59E297FE8E67374EE9C34CA5A7F45B4B3709C6BD4E0B546F706DDC571C6469CFA90602C0E339A054CDE1D0A0E071651FD49FEBD485D8E561BB36AF7BED2741D35BBC88051"); break; //APA.04.71700615"); break; //APA.37.00019666 - case 249 : return F("bA944C514381355920004A5FC7A3D0090257403AC17355186FF933BAF0D0820041595970BD168489A746DF337AB01D1DBBA3EF7D486D74CCCEE777E57BD12A15D22B89AB4B4266D84B9C5C1DDBC437CD64807AF0B2B232E3A3C1B4DA568C9F8BA39FDF4565C170ACEF1918655B45D82E8BAAD769B88FA917E82542CAD596AB2145433599FFDDD3FCE889F657A8AC07FE5CED3F184244475B59710CFDCA44D7C3AE00CC021FA6EE3F49B77939529B49279FC526803FD0C05010002FD0B01111E248025"); break; //EFE.04.92551338 - case 250 : return F("b6E44A511485268614004B97F7A86006005B8D6D972C8E06EFB5AEC0FC750DB9DFFE62BB59DA183A5709DFC5029CEB2E44FEAF475DD2F3CF6D90544F29372A5C027DACA08536B2457CDEDEB76E36B281F4A0FD4A203FE9F7DFD337BDEA15D02F53A21CB281A3EF01E15D779B3018A99D0E3EF2FF946A63F419E56644BBDF8FB8010"); break; //DME.04.61685248 - case 251 : return F("b3E44A51159351969A004761E7A030030050F0020FC830AABCA7CEE4EC8E44524D98802ED5CA2EF3C89774FF800730951E02569BF91C156331EC9C29730FE2F437128D7E574D29C4E808047"); break; //DME.04.69193559 - case 252 : return F("b7144685009022004593720EB8C00EE900F002C252CA40100F5CDA57367A4436D244E72791670692423FE040600407D7C07108660CA27A4DD53871B12F96FFEF0F84E0CA88A2201BD794859D35CCD051F34327D04EAAE8772675F8E7F28DE2E515FBC9AF3A40E72C078304158D453511F2CF39DBF98DBE1EAD84F0F2CAD975EDDCC4F"); break; //HYD.04.69701679"); break; //TCH.37.04200209 - case 253 : return F("bA944C514381355920004A5FC7A670090251913406458376E6F9CEB817ABA85D9F26E5867EBA222FDFF496F2C8A312E593C80C0604576D7B5269B4A25BD15B314C0598FE3395611400AA7ADB705EE2120686208A449BDC5EE828FF048F669516585611337839C0A40A308F4DE619997F29D2438CE9EC263574FC60D42197CFA7B47528574792BFFF54AFF9193CA678242653A97DEBCB5E5333687846A875A69C4B7371C762C03A2DB5AB738D92623907896AEBF03FD0C05010002FD0B0111C6E48020"); break; //EFE.04.92551338 - case 254 : return F("b5E44C51475051512010425417A1C004025EEA5F744D756D853BB606F5DBB33DD7B26896D7370BD9C50990E605CE03F8F16DEE8AA36EF2C87E3902F933DDD87E1D383B7C647E8D2236E5FF5E1BE6742D65511FE1629B5CD3F6F0F800C000109002F680300035E2001020A0901C98008"); break; //EFE.04.12150575 - case 255 : return F("b5344A5115158536941043C9D8C00CC900F002C25986B0110B13D74311F4D1ACF88797A7E7031071066DEA0D815DFAC31E07763666D4C46C2DEA856FCEC34F2B5CCF3750F72BD8F924544E73C51D7DBC11A1AE31049E5D0E121CE0053AEB8E018805B"); break; //DME.04.69535851 - case 256 : return F("bA944C514381355920004A5FC7A79009025E37121BAC0ACF1A216281AE7A85DA20DF90613A83417418BD9BA7B0F09C192713A65DECD3ECB2DFC5299683A5B2669EE70851493FC9944D7579F7FCD44CCC839E432D498E40EFDF1614BD9B7E6F1B4AD96CC16939C9D061434790765D3883275B80DE0508393A0F00C1B1590A66F94D920BD5E80125C610F7C0B0346D8D0CFE8DD4E8A14370077D90A7BCFA59345DB13D7134E80D5F2F1D50B3B043075126DF42C2A03FD0C05010002FD0B0111ABC78028"); break; //EFE.04.92551338 - case 257 : return F("b4E44EE4D557648251B048F777ABE004025D84601014EEA5EDCC08B007BC8BB1EBB266BADDEC127897EDA4296EEB8EB05A57DFCA5190F54B41C05B1BF0F5EF82B719CD27EA6242263DFB94F589F38843A1146916B872141977275BD8035"); break; //SON.04.25487655 - case 258 : return F("b2644AC488137000050371BF47201813700AC485000D90000002F2F0C32C60E569405002F2F2F2F2F2F2F2FB90E8059"); break; //REL.00.00378101"); break; //REL.37.00003781 - case 259 : return F("b2644AC488137000050371BF47202813700AC48500CE20000002F2F0C068D16635640002F2F2F2F2F2F2F2F72188058"); break; //REL.0C.00378102"); break; //REL.37.00003781 - case 260 : return F("b36446850790133512243C21BA1009F29793C0088230200007E0E4FFD476370544E2D33280000000000000000000CC8A2608214298C81CD45160105964E203B372E"); break; //TCH.43.51330179 - case 261 : return F("b3744685029069372274351B5A2129F27995400305C1200000007400B69792D4C914447BB7D4A2AC86CB37BFAFD59EEC7971B1F070D00000000002C0040004FD085CC"); break; //TCH.43.72930629 - case 262 : return F("b37446850715221622843FE7BA2129F29F827008800000080800D0000DE060000E00739EF48B3861639BC2044115A10CB0C6283102F1830C0080AD861D24C1E8F80E1"); break; //TCH.43.62215271 - case 263 : return F("b374468508475676739430ECDA2109F27793500287F15000000D34D95907D550ADB32A24C9AAAA9922ED6A76075D51C1F7937DB8D4B95F20E4C63EDC5997041E280FA"); break; //TCH.43.67677584 - case 264 : return F("b36446850442620514543A443A1009F279E180038CC0400803680000286DB08F0C00727FCF0C82F92E8128D31165D722873C60C1568910103062C700107EF3F912E"); break; //TCH.43.51202644 - case 265 : return F("b3744685060478860574395F9A20D9F29820500A00000000100060000E34A00000000000000000000000000000000FFFF0000000000000000000000000000FFFF80D4"); break; //TCH.43.60884760 - case 266 : return F("b3644685005012040714351C0A0009F298F560290E10101A40000FFF719F04E99E7B2F9E2E512000000000000000042B000000000000000000000000000FFFF95A6"); break; //TCH.43.40200105 - case 267 : return F("b364468504200545145444FE0A1009F29CA6900B0F00D008023001A68BB4748D28D4F7685755095534E09E187ADE5A7B903D03027041000000100008002CF41D570"); break; //TCH.44.51540042 - case 268 : return F("b37446850341929625744B280A20D9F27C9350058395A00000169848B3EDF2ECEF8A17F008A875B80FCB947E0914351334E69E4A14D72A8E296375279E286AB9B8008"); break; //TCH.44.62291934 - case 269 : return F("b36446850060670527144C1AAA0009F29C41F0190DE0F0081A5A1BF2595D38ABA9F512E89A3471D67F8F0C6206308383B6288298BD0A3D68424A228A9907F8B34B2"); break; //TCH.44.52700606 - case 270 : return F("bY5044972608062002001A7AC90340A517F5A8F83D78281AEFF06FDBDEDEF842336FA663F292D6EEACB0F54CA02FB47C8F587862B352ED30166FEE61753998230C38444F845C9FCC364D3C614095F92D14A28010"); break; //ITW.1A.02200608 - case 271 : return F("bY5E449726900634160004728499181897A60030BA0040A53FB81E378B33F9E23FEDF6A0B1B381F4972C2842041CC7EC8D74D675CE56222039CE82385073750F2B695CBEC9B0E8A48EC3F09B74C26E4194A06B7974203DDD0C7976874216E0D282DE"); break; //ITW.04.16340690"); break; //ITW.30.18189984 - case 272 : return F("bY50449726141331160007728499181897A600307E0030A5500A4109842EF76DD4A2DEDF6722CCB4D0746C8505086D91ED34B41AFD24FED0111715A21E549191B1529EE2AE8229E50E7900000000000070208AD8"); break; //ITW.07.16311314"); break; //ITW.30.18189984 - case 273 : return F("bY5044972625353893071A7A0B0040A5B12EC2E009C467CAD2AF0A38712684FE764C6181D2969A7F0F7B076CB9719FEB21C32E48161EEA5A1E6E92F354FED894994C368F17E6F68E2E6AA1D7C289E3FD7A908015"); break; //ITW.1A.93383525 - case 274 : return F("b2E449726606670194107A6AB8CB0D47ABF0000A00413E84B070004FD3442178001C00004933C00000000333B00000A2D00325A0000CD2E801C"); break; //ITW.07.19706660 - case 275 : return F("bY50449726141331160007728499181897A600307E0030A5500A4109842EF76DD4A2DEDF6722CCB4D0746C8505086D91ED34B41AFD24FED0111715A21E549191B1529EE2AE8229E50E7900000000000070208AD8"); break; //ITW.07.16311314"); break; //ITW.30.18189984 - - } - // *INDENT-ON* - count = 0; - return F(""); -} - -# endif // if P094_DEBUG_OPTIONS - -bool P094_data_struct::loop() { - if (!isInitialized()) { - return false; - } - bool fullSentenceReceived = false; - - if (easySerial != nullptr) { - int available = easySerial->available(); - - unsigned long timeout = millis() + 10; - - while (available > 0 && !fullSentenceReceived) { - // Look for end marker - char c = easySerial->read(); - --available; - - if (available == 0) { - if (!timeOutReached(timeout)) { - available = easySerial->available(); - } - delay(0); - } - - switch (c) { - case 13: - { - const size_t length = sentence_part.length(); - bool valid = length > 0; - - for (size_t i = 0; i < length && valid; ++i) { - if ((sentence_part[i] > 127) || (sentence_part[i] < 32)) { - free_string(sentence_part); - ++sentences_received_error; - valid = false; - } - } - - if (valid) { - fullSentenceReceived = true; - } - break; - } - case 10: - - // Ignore LF - break; - default: - - if ((c >= 32) && (c < 127)) { - sentence_part += c; - } else { - current_sentence_errored = true; - } - break; - } - - if (max_length_reached()) { fullSentenceReceived = true; } - } - } - - if (fullSentenceReceived) { - ++sentences_received; - length_last_received = sentence_part.length(); - } -# if P094_DEBUG_OPTIONS - else { - if (debug_generate_CUL_data && (sentence_part.length() == 0)) { - static uint32_t last_test_sentence = 0; - static int count = 0; - - if (timePassedSince(last_test_sentence) > 1000) { - count++; - - // sentence_part = F("b2644AC48585300005037FAB97201585300AC485003150000202F2F0C0AF314213993002F2F2F2F2F2F2F2FAFCA8046"); - sentence_part = getDebugSentences(count); - fullSentenceReceived = true; - last_test_sentence = millis(); - } - } - } -# endif // if P094_DEBUG_OPTIONS - return fullSentenceReceived; -} - -const String& P094_data_struct::peekSentence() const { - return sentence_part; -} - -void P094_data_struct::getSentence(String& string, bool appendSysTime) { - string = std::move(sentence_part); - - if (appendSysTime) { - // Unix timestamp = 10 decimals + separator - if (string.reserve(sentence_part.length() + 11)) { - string += ';'; - string += node_time.getUnixTime(); - } - } - sentence_part.reserve(string.length()); -} - -void P094_data_struct::getSentencesReceived(uint32_t& succes, uint32_t& error, uint32_t& length_last) const { - succes = sentences_received; - error = sentences_received_error; - length_last = length_last_received; -} - -void P094_data_struct::setMaxLength(uint16_t maxlenght) { - max_length = maxlenght; -} - -uint32_t P094_data_struct::getFilterOffWindowTime() const { - return filterOffWindowTime; -} - -void P094_data_struct::setDisableFilterWindowTimer() { - if (getFilterOffWindowTime() == 0) { - disable_filter_window = 0; - } - else { - disable_filter_window = millis() + getFilterOffWindowTime(); - } -} - -bool P094_data_struct::disableFilterWindowActive() const { - if (disable_filter_window != 0) { - if (!timeOutReached(disable_filter_window)) { - // We're still in the window where filtering is disabled - return true; - } - } - return false; -} - -bool P094_data_struct::parsePacket(const String& received, mBusPacket_t& packet) { - const size_t strlength = received.length(); - - if (strlength == 0) { - return false; - } - - const char firstChar = received[0]; - - if ((firstChar == 'b')) { - // Received a data packet in CUL format. - if (strlength < 21) { - return false; - } - - // Decoded packet - if (!packet.parse(received)) { return false; } - - const mBusPacket_header_t *header = packet.getDeviceHeader(); - - if (header == nullptr) { - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - addLogMove(LOG_LEVEL_INFO, concat(F("CUL Filter: NO Header "), packet.toString())); - } - - return false; - } - - if (mute_messages) { - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - addLogMove(LOG_LEVEL_INFO, concat(F("CUL Filter: Muted "), packet.toString())); - } - - return false; // Mute all messages - } - - if (!interval_filter.enabled || (_filters.size() == 0)) { - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - addLogMove(LOG_LEVEL_INFO, concat(F("CUL Filter: NO Filter "), packet.toString())); - } - - return true; // No filtering - } - - for (unsigned int f = 0; f < _filters.size(); ++f) { - if (_filters[f].matches(*header)) { - const bool res = interval_filter.filter(packet, _filters[f]); - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - addLogMove(LOG_LEVEL_INFO, concat(F("CUL Filter: Match "), _filters[f].toString())); - addLogMove(LOG_LEVEL_INFO, concat(res ? F("CUL Filter: Pass ") : F("CUL Filter: Reject "), header->toString())); - } - - return res; - } - } - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - addLogMove(LOG_LEVEL_INFO, concat(F("CUL Filter: NO Match "), header->toString())); - } - - // No matching filter, so consider fall-through filter to be: - // *.*.*;none - return false; - } else { - switch (firstChar) { - case 'C': // CMODE - case 'S': // SMODE - case 'T': // TMODE - case 'O': // OFF - case 'V': // Version info - - // FIXME TD-er: Must test the result of the other possible answers. - return true; - } - } - - return false; -} - -void P094_data_struct::interval_filter_purgeExpired() { - interval_filter.purgeExpired(); -} - -void P094_data_struct::html_show_interval_filter_stats() const -{ - if (interval_filter._mBusFilterMap.empty()) { return; } - - addRowLabel(F("Interval Filter Entries")); - addHtmlInt(interval_filter._mBusFilterMap.size()); - - addFormNote(F("Non expired W-MBus device filters")); -} - -bool P094_data_struct::collect_stats_add(const mBusPacket_t& packet, const String& source) { - if (collect_stats) { - return mBus_stats[firstStatsIndexActive ? 0 : 1].add(packet, source); - } - return false; -} - -void P094_data_struct::prepare_dump_stats() { - firstStatsIndexActive = !firstStatsIndexActive; -} - -bool P094_data_struct::dump_next_stats(String& str) { - const uint8_t dumpStatsIndex = firstStatsIndexActive ? 1 : 0; - - if (mBus_stats[dumpStatsIndex]._mBusStatsMap.empty()) { return false; } - - str = concat(F("stats;"), mBus_stats[dumpStatsIndex].getFront()); - - return true; -} - -void P094_data_struct::html_show_mBus_stats() const -{ - const uint8_t dumpStatsIndex = firstStatsIndexActive ? 0 : 1; - - if (mBus_stats[dumpStatsIndex]._mBusStatsMap.empty()) { return; } - - addRowLabel(F("W-MBus Devices")); - addHtmlInt(mBus_stats[dumpStatsIndex]._mBusStatsMap.size()); - - addFormNote(F("Devices received since last culreader,dumpstats")); - - mBus_stats[dumpStatsIndex].toHtml(); -} - -bool P094_data_struct::max_length_reached() const { - if (max_length == 0) { return false; } - return sentence_part.length() >= max_length; -} - -bool P094_data_struct::isDuplicate(const P094_filter& other) const -{ - const String f_str = other.toString(); - - for (auto it = _filters.begin(); it != _filters.end(); ++it) { - if (f_str.equals(it->toString())) { - return true; - } - } - return false; -} - -# if P094_DEBUG_OPTIONS -uint32_t P094_data_struct::getDebugCounter() { - return debug_counter++; -} - -# endif // if P094_DEBUG_OPTIONS - -#endif // USES_P094 \ No newline at end of file +#include "../PluginStructs/P094_data_struct.h" + +#ifdef USES_P094 + +// Needed also here for PlatformIO's library finder as the .h file +// is in a directory which is excluded in the src_filter +# include + +# include + +#include + +# include "../DataStructs/mBusPacket.h" +# include "../Globals/MQTT.h" + +// # include "../Globals/ESPEasy_time.h" +// # include "../Globals/TimeZone.h" +// # include "../Helpers/ESPEasy_Storage.h" +// # include "../Helpers/StringConverter.h" + + +P094_data_struct::P094_data_struct() : easySerial(nullptr) {} + +P094_data_struct::~P094_data_struct() { + if (easySerial != nullptr) { + delete easySerial; + easySerial = nullptr; + } +} + +void P094_data_struct::reset() { + if (easySerial != nullptr) { + delete easySerial; + easySerial = nullptr; + } +} + +bool P094_data_struct::init(ESPEasySerialPort port, + const int16_t serial_rx, + const int16_t serial_tx, + unsigned long baudrate) { + if ((serial_rx < 0) && (serial_tx < 0)) { + return false; + } + reset(); + easySerial = new (std::nothrow) ESPeasySerial(port, serial_rx, serial_tx); + + if (easySerial == nullptr) { + return false; + } + easySerial->begin(baudrate); + return true; +} + +void P094_data_struct::setFlags(unsigned long filterOffWindowTime_ms, + bool intervalFilterEnabled, + bool mute, + bool collectStats) +{ + filterOffWindowTime = filterOffWindowTime_ms; + interval_filter.enabled = intervalFilterEnabled; + collect_stats = collectStats; + mute_messages = mute; +} + + +void P094_data_struct::loadFilters(struct EventStruct *event, uint8_t nrFilters) +{ + int offset_in_block = 0; + + + const size_t chunkSize = P094_filter::getBinarySize(); + const size_t maxNrFilters = 1024u / chunkSize; + + if (nrFilters > maxNrFilters) { nrFilters = maxNrFilters; } + + _filters.clear(); + + size_t nrChunks = 8; + + if (nrFilters < nrChunks) { + nrChunks = nrFilters; + } + + const size_t bufferSize = nrChunks * chunkSize; + + while (nrFilters > 0) { + uint8_t buffer[bufferSize]; + ZERO_FILL(buffer); + + LoadCustomTaskSettings(event->TaskIndex, buffer, bufferSize, offset_in_block); + offset_in_block += bufferSize; + + uint8_t *readPos = buffer; + + for (size_t i = 0; i < nrChunks && nrFilters > 0; ++i) { + P094_filter filter; + filter.fromBinary(readPos); + + if (filter.isValid()) { + _filters.push_back(filter); + } + + --nrFilters; + readPos += chunkSize; + } + } +} + +String P094_data_struct::saveFilters(struct EventStruct *event) const +{ + int offset_in_block = 0; + + String res; + const size_t nrFilters = _filters.size(); + size_t currentFilter = 0; + size_t actualNrFilters = 0; + const size_t chunkSize = P094_filter::getBinarySize(); + const size_t nrChunks = 8; + #ifdef ESP32 + const size_t bufferSize = 1024; + #else + const size_t bufferSize = 256; + #endif + + std::vector buffer; + buffer.resize(bufferSize); + + + while ((offset_in_block + bufferSize) <= 1024 && res.isEmpty()) { + for (auto it = buffer.begin(); it != buffer.end(); ++it) { + *it = 0; + } + + uint8_t *writePos = &buffer[0]; + size_t writeSize = 0; + + while (writeSize < bufferSize && currentFilter < nrFilters) { + if (_filters[currentFilter].isValid()) { + size_t size{}; + const uint8_t *binaryData = _filters[currentFilter].toBinary(size); + memcpy(writePos, binaryData, size); + writePos += size; + writeSize += size; + ++actualNrFilters; + } + ++currentFilter; + } + res = SaveCustomTaskSettings(event->TaskIndex, &buffer[0], bufferSize, offset_in_block); + offset_in_block += bufferSize; + } + if (P094_NR_FILTERS < actualNrFilters) { + P094_NR_FILTERS = actualNrFilters; + } + return res; +} + +void P094_data_struct::clearFilters() +{ + _filters.clear(); +} + +bool P094_data_struct::addFilter(struct EventStruct *event, const String& filter) +{ + P094_filter f; + + f.fromString(filter); + + if (!f.isValid()) { + return false; + } + + if (isDuplicate(f)) { + if (loglevelActiveFor(LOG_LEVEL_ERROR)) { + addLogMove(LOG_LEVEL_ERROR, concat(F("CUL Reader : Duplicate filter found: "), f.toString())); + } + + return false; + } + + _filters.push_back(f); + + // No sorting as this may have unexpected side-effects + // std::sort(_filters.begin(), _filters.end()); + + if (P094_NR_FILTERS < _filters.size()) { + P094_NR_FILTERS = _filters.size(); + } + return true; +} + +String P094_data_struct::getFiltersMD5() const +{ + if (mute_messages) { + return F("blockall"); + } + + MD5Builder md5; + uint8_t checksum[16]{}; + md5.begin(); + + uint8_t nrFiltersAdded = 0; + const char separator[] = {'|', 0}; + for (auto it = _filters.begin(); it != _filters.end(); ++it) { + if (it->isValid()) { + if (nrFiltersAdded != 0) { + md5.add(separator); + } + md5.add(it->toString().c_str()); + ++nrFiltersAdded; + } + } + + if (nrFiltersAdded == 0) { + // No filters, thus all messages will just pass + return F("pass"); + } + + md5.calculate(); + md5.getBytes(checksum); + + return formatToHex_array(checksum, sizeof(checksum)); +} + +void P094_data_struct::WebformLoadFilters(uint8_t nrFilters) const +{ + if (nrFilters > 0) { + addFormNote(F("Filter Fields: Manufacturer, Meter Type, Serial, Filter Window")); + } + + for (uint8_t filterLine = 0; filterLine < nrFilters; ++filterLine) + { + if (filterLine < _filters.size()) { + _filters[filterLine].WebformLoad(filterLine); + } else { + P094_filter dummy; + dummy.WebformLoad(filterLine); + } + } +} + +void P094_data_struct::WebformSaveFilters(struct EventStruct *event, uint8_t nrFilters) +{ + _filters.clear(); + + for (uint8_t filterLine = 0; filterLine < nrFilters; ++filterLine) + { + P094_filter dummy; + + if (dummy.WebformSave(filterLine)) { + // Filter with filled in values, worth storing + if (!isDuplicate(dummy)) { + _filters.push_back(dummy); + } + } + } + addHtmlError(saveFilters(event)); +} + +bool P094_data_struct::isInitialized() const { + return easySerial != nullptr; +} + +void P094_data_struct::sendString(const String& data) { + if (isInitialized()) { + if (data.length() > 0) { + setDisableFilterWindowTimer(); + easySerial->write(data.c_str()); + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLogMove(LOG_LEVEL_INFO, concat(F("Proxy: Sending: "), data)); + } + } + } +} + +# if P094_DEBUG_OPTIONS + +const __FlashStringHelper* getDebugSentences(int& count) { + // *INDENT-OFF* + switch (count) { + case 1: return F("b3C449344369291352337D55472593107009344230A920000200C0538ECE32625004C0527262500426CBF2CCC0805BDF032262500C2086CDF21326CFFFF046D26BB1103DA22B4E093E2"); break; //QDS.0A.00073159"); break; //QDS.37.35919236 + case 2: return F("b9644A732260729700A0AB8487A4E10002002747D00046D030AC1270CB02E0600000000446D3B17BF2C4C0600000083410084016D3B17DE268C010600000000CC190B0106000000008C020600000000CC0206B190000000008C030600000000CC030600008B9400008C040600000000CC04060000000099348C050600000000CC0506000000008C0673DA0600000000CC0606000000008C070600E0F80000003C22030402000F841001245C84E7"); break; //LUG.0A.70290726 + case 3: return F("bCE44A8153132000801022ADC7F6900C005098D2F2D70E24F3E43458739FD572B2DB7CB22EA563C57F3017308E093A4CBC662DF70F000A2E2B18215FC7098DBC7DC8A2ABAD8202F700C5A7D8B0FC89094823FC6B54565730369E73039146898536381B5DE8B8F3A5377A807EB30383ACC0176176C6C18265932082844F0B5A3F69B0A66FD0E35FAED9A53B825E073FC1E67193A727C97BC1025229C87421FA0381443A5F2F2897AE44D383FE125614F08BABEC6B46DF0FFCB910DAD1B3CD53B44AA83726492D845F840A2D20B738E9FB212D5C74FF91FD2796A22D669CBF0B0FEC1BAFA171A65FFB165B2E9"); break; //EMH.02.08003231 + case 4: return F("b5344E2306291001500030F388C30A7900F002C2583AE010032E1E493C32BEF51CDA37A430030071027A19EE14B0BBCAD656D0783516CCB7CFBC6AAFAECDCAD70020FE3DA54FCBC8EC2AED88DFD0972C55CF9336E1683574ABADBD046BB53623F8013"); break; //LGB.03.15009162 + case 5: return F("b5344A732806139690404B70A8C2063900F002C25923338000C8BC361CE2EE050FD3B7A6340300710DEC49523134391877289A80A53A505655A833F754F221E619D08FB4DB5AD773EAB16B545B306C69D1493CD851012BBF4624A5DDA556AF07E83E5"); break; //LUG.04.69396180 + case 6: return F("b9644A732460335700A043C1F7ACD0000200274DE02046D030D94250C804C0623000000446D3B177F2C4C060000005D610084016D3B179E248C010600000000CCB81D0106000000008C020600000000CC0206B190000000008C030600000000CC030600008B9400008C040600000000CC04060000000099348C050600000000CC0506000000008C0673DA0600000000CC0606000000008C070600E0F80000003C22000000000F0010004FF980F9"); break; //LUG.04.70350346 + case 7: return F("b13440000000000DA00DAA8CF7101FD0C3A02FD171101CB938032"); break; //@@@.DA.DA000000 + case 8: return F("b63445A146699750001026CE68C20D7900F002C25D7CE0C000B7C13179B38522166CE7AD700400710CDEF8D2A82F77DD15E367871F1E04261AAFAC430C2B55C1DED4A3148306D4C296CF10D72C9E79310A47DD73FDBFDF2CEA6490B6CA12A30EE5D64621A90B5E71F75D50D24C87B10E2ADDF802E"); break; //EBZ.02.00759966 + case 9: return F("b5E44496A3680003888049D2D7A1D0050053FBA7B54810C548AC112ECFC76CE753AF07A625248C05827C843371AB5DC6C6C8D5D457E845B4B67FB4CEFF06720EA7A9112BFD0A96BC7E97D49FB9BBD59155D109433F0C4823DEA7A13E5281C00E4945F5B05D7518CE085EC8BFE738122"); break; //ZRI.04.38008036 + case 10 : return F("b1B44A5110301808238379BA77241022436931581038A88000002A7184A0AD900E327803A"); break; //ELS.03.36240241"); break; //DME.37.82800103 + case 11 : return F("b3E44B405399098502E0439DC7A0F70300530C7144B5962760A55DE8BA4E49C37676B0CD702698B5FBCE59E35E33D33F736AE13FB1C31DD43ADDC3FE7FF8E1EDC01D749974884BBF96580FE"); break; //AMT.04.50989039 + case 12 : return F("b5B4479169014216130377E5B8C2034900F002C256448000029E1A134984E32773DF97289000047791611023400302CBD0710FF4380B4AE49A140E94F319BE97049FDA8DDC96A8DC437F3BFB02ADC86082E9507934C7ED4FC6F4F678D613F25C09A1DDE927D817F5A824A8012"); break; //ESY.02.47000089"); break; //ESY.37.61211490 + case 13 : return F("b7B445A1415200000023724958C20A0900F002C25B4F60800C0E1D417100AD1BB6EDC72324371005A140102A00050F9B10710112A08DB9869998DE2C0D8614F76213F8682D10A8EF2951413C839461E8E3139EA62193E02B1584E6EC8EDB082AB70C6504F1ADF9E6ABD270E96FE8745AEB93C454FC3C9EAA2D5FCD6679E8A38A3E0818D4B6652993CEE5F8E514867801D"); break; //EBZ.02.00714332"); break; //EBZ.37.00002015 + case 14 : return F("b49449344100549253508227F780DFF5F3500824600007E0007B06EFF2BE9FF000000007F2C000000009E24000000DAC000008000800080008000800080000000E9F10000000000000000002F046D010E8625249880E6"); break; //QDS.08.25490510 + case 15 : return F("b9644A732370335700A045D187A440000200274DD02046D071189260C33FD0638010000446D3B177F2C4C06000000DFDD0084016D3B179F258C010641000000CC0DBA0106000000008C020600000000CC0206B190000000008C030600000000CC030600008B9400008C040600000000CC04060000000099348C050600000000CC0506000000008C0673DA0600000000CC0606000000008C070600E0F80000003C22000000000F0010004FF980E0"); break; //LUG.04.70350337 + case 16 : return F("b4806AC1956030015020363917256030015AC190203D8000000466D00AC9200118625000D78113131363533303030619C3531343530303030308940FD1A014C933B263A494700004C130546000001FD67030D9A8057"); break; //FML.03.15000356"); break; //FML.03.15000356 + case 17 : return F("b9E44A815242292070102881F7F4B0098050B0E989C939C3479F8904137236FE582B853DCACDD8DB48A717A6B42935CB977102079E6397B07AAD2A648E5B65E44D97F9A020B2BFAE433FA37FCB2A2C32711A5986B301D2F6E4A424C1144D808CE9D592C316B117C572689AC6C1322CC05E81F590CAAF457390F6B39DACC946FA314F8E8A34268157AC4338781C3EF5807F9221394DD1FAB5165E1261614B8B85758851295334DF52D9A4DCE2E1E17A555A21007D2DA802B"); break; //EMH.02.07922224 + case 18 : return F("b2E44B05C99010100021B65BE7AC30000002F2F0A6605020AFB1A33041AA002FD971D00002F2F2F2F2F2F2F2F2F2FDF772F2F2F2F2F25EE8008"); break; //WEP.1B.00010199 + case 19 : return F("b2E44B05C75000000041B8EF87A510000002F2F0A6661010AFB1A8905449802FD971D00002F2F2F2F2F2F2F2F2F2FDF772F2F2F2F2F25EE80F8"); break; //WEP.1B.00000075 + case 20 : return F("b1E44C418EA76010001034ECB7A820010A5597AB1CCF2014088590E64BCAB21DC1CC068DAA08035"); break; //FFD.03.000176EA + case 21 : return F("b4E442423245450514A07545F7AC6004005BDB5EDF3750DF41725EE867C3E39750E20B9F5FB092089B6A5DC7AA586101778BCD5EAD4995B102AC639F0FB4D12403EFE3554F72CAE4F5D348CC374F571CCE4A98634318027AAF3E7DD8600"); break; //HYD.07.51505424 + case 22 : return F("b4344A511031008667607CFF48C0031900F002C2531D311006D980E9CC8D28524C6417AE570210710CFF2DB65BFE77C51602690DAEB954A5455A2ABED621B74AC56A79D81390EBFCADC9D3D34F5928DDC"); break; //DME.07.66081003 + case 23 : return F("b4344A511031008667607CFF48C0031900F002C2532D31100BC55A548A0082663A6FC7AE670210710C7B178D748F610E2CB16DE82C823EF83334EF1A0C383FB42DF7BED1846323211FC25DCC1EBF085DE"); break; //DME.07.66081003 + case 24 : return F("b4344A511031008667607CFF48C0031900F002C2533D311004E0C2E86D68C08F425747AE77021071013F9284E0CB5896FFB27D33882716D09F7EC8175FD04516AAE9122E584095D6441E14E5B4ED189DD"); break; //DME.07.66081003 + case 25 : return F("b6E44A511825169584004B8737AB500600554B11A7F9F7DDCAC35695A3191EA83FD79D1876F378419D5AB37CA1BD857243492B6379B258A4831015F9F0D4B7098218D7E7C44421422FCABC0770F0E67C16EFA13ABEE798D58062CDB0F06AA312592C085F046D29B64F7031FE17CC7EC8B44D61FB5E2F37301ACA7AAC025666C802C"); break; //DME.04.58695182 + case 26 : return F("b4944C51402203571000451A77A090001202F2F046D2E299926040687ED7214000001FD17000413F6670400043B009378000000042B00000000025B1900025F1986B90002610A0003FD0C05000002FD0B3011F52BA393"); break; //EFE.04.71352002 + case 27 : return F("b2844C5146427807103073D877234626016C5140007D72000202F2F04DD1C6D2F3498260413B96E010001FD1700066080E7"); break; //EFE.07.16606234"); break; //EFE.07.71802764 + case 28 : return F("bA644C514960080900307DABF7296008090C5140007ED0000202F2F426E8D6C7E2944133C08000001FD1700840113039188130000C40113D611000084021324108D5A0000C40213A70F00008403132C0F00003550C403137B0E0000840413C0080000C404749913650800008405133C080000C40513AD831B07000084061380050000C406138401006999008407133A000000C407133A00000084286B081300000000046D332A8F260413A7137132000003FD0C02032102FD0B01114D8480FA"); break; //EFE.07.90800096"); break; //EFE.07.90800096 + case 29 : return F("b5E442515695800000C1A452E7A590050252502B45C2E823EF856FABFC4775E28D2904492FB74BA65A843BA7E63BC698D83279AE2BE04B957699517818F7B33F8E58001A7C9CE0E73C5769487247954D0A000E811754BAB1CED0E61567EB1FE504B091E0C7DCF4632E3FAC31618804D"); break; //EIE.1A.00005869 + case 30 : return F("b4806AC1956030015020363917256030015AC19020323000000466D0009780004872B000D7811313136353330303028633531343530303030308940FD1A014C933B263A494700004C130546000001FD67030D9A8030"); break; //FML.03.15000356"); break; //FML.03.15000356 + case 31 : return F("bY5044972608062002001A7AC90340A517F5A8F83D78281AEFF06FDBDEDEF842336FA663F292D6EEACB0F54CA02FB47C8F587862B352ED30166FEE61753998230C38444F845C9FCC364D3C614095F92D14A28010"); break; //ITW.1A.02200608 + case 32 : return F("bY5E449726900634160004728499181897A60030BA0040A53FB81E378B33F9E23FEDF6A0B1B381F4972C2842041CC7EC8D74D675CE56222039CE82385073750F2B695CBEC9B0E8A48EC3F09B74C26E4194A06B7974203DDD0C7976874216E0D282DE"); break; //ITW.04.16340690"); break; //ITW.30.18189984 + case 33 : return F("bY50449726141331160007728499181897A600307E0030A5500A4109842EF76DD4A2DEDF6722CCB4D0746C8505086D91ED34B41AFD24FED0111715A21E549191B1529EE2AE8229E50E7900000000000070208AD8"); break; //ITW.07.16311314"); break; //ITW.30.18189984 + case 34 : return F("bY5044972625353893071A7A0B0040A5B12EC2E009C467CAD2AF0A38712684FE764C6181D2969A7F0F7B076CB9719FEB21C32E48161EEA5A1E6E92F354FED894994C368F17E6F68E2E6AA1D7C289E3FD7A908015"); break; //ITW.1A.93383525 + case 35 : return F("b2E449726606670194107A6AB8CB0D47ABF0000A00413E84B070004FD3442178001C00004933C00000000333B00000A2D00325A0000CD2E801C"); break; //ITW.07.19706660 + case 36 : return F("bY50449726141331160007728499181897A600307E0030A5500A4109842EF76DD4A2DEDF6722CCB4D0746C8505086D91ED34B41AFD24FED0111715A21E549191B1529EE2AE8229E50E7900000000000070208AD8"); break; //ITW.07.16311314"); break; //ITW.30.18189984 + case 37 : return F("b314493447236379635085D337A040000200B6E3103004B6E070300420D0E6C7F2CCB086E310300C2086C9E24326C1422FFFF046D240E86250E4E80E2"); break; //QDS.08.96373672 + case 38 : return F("b494493447236379635083FD9780DFF5F350082030000F00007B06EFFC6F5FF310300007F2C070300009E24310300E13000008000800000000000001F007300A145C9006BFF64003D000C002F046D200E86255AA081DF"); break; //QDS.08.96373672 + case 39 : return F("b494493447153871216062D53780DFF5F350082DA00007F0007C113FF8331FF969799997F2C279899999F2596979988C999000000000000FFFF000000000000008B35000000FFFFFEFF00002F046D25068C2608D2803F"); break; //QDS.06.12875371 + case 40 : return F("b344493447153871216069A667A8000082004ED3926096C2701FD0C110157046D17138F2602FD3CC2010DFF5F0C005FC50861FF000006130701FFFC138B803F"); break; //QDS.06.12875371 + case 41 : return F("b3D44934417196746221AD6FF7AA210000081027C034955230182026CAC3BB62181037C034C41230082036CFFFF0238E2FD171000326CFFFF046D0311B62102FDFE8FAC7E19004F33802D"); break; //QDS.1A.46671917 + case 42 : return F("b3744934417196746221A40247AA310002081027C034955230182026C5AE5B62181037C034C41230082036CFFFF0238E2FD171000326CFFFF046D0311B621AD04802B"); break; //QDS.1A.46671917 + case 43 : return F("b39449344775149131706E1D57A680000200C13750000004C1300000010FC00426CFFFFCC081358000000C2086C9F61212502BB560000326CFFFF046D070B8A268D5880FA"); break; //QDS.06.13495177 + case 44 : return F("b4944934477514913170662C2780DFF5F3500827B0000810007C113FF5A69FF75000000FFFF000000009F255800004659000080008000800080008000800080009B248001000000000004002F046D1F0D8A266CB280FA"); break; //QDS.06.13495177 + case 45 : return F("b3744934484145047231AAE907A9600002081027C034955230082026CD1DAFFFF81037C034C41230082036CFFFF02DDAAFD170000326CFFFF046D0503AD21AB3580D9"); break; //QDS.1A.47501484 + case 46 : return F("b39449344843350131707BA4A7AF00000200C13000100004C13000000C11300426CFFFFCC081361000000C2086C9F6DF62502BB560000326CFFFF046D370A8A26486681E6"); break; //QDS.07.13503384 + case 47 : return F("b49449344843350131707395D780DFF5F350082000000EE0007C113FF4BC0FF00010000FFFF000000009F256100009FB5000080008000800080008000800080009B248001000000000005002F046D020D8A263BD880E7"); break; //QDS.07.13503384 + case 48 : return F("b3C4493440989323533372A78728903252493443307C50000200C133698392007004C1331430400426C7F2CCC081319DD26200700C2086C9F25326CFFFF046D058B67079026F78582DC"); break; //QDS.07.24250389"); break; //QDS.37.35328909 + case 49 : return F("b2C449344098932353337D4E7728903252493443307C000082004ED39ACEB2C06702501FD0C10046D1C06902602FD705D3CC20189FB81DA"); break; //QDS.07.24250389"); break; //QDS.37.35328909 + case 50 : return F("b5344934409893235333714F478077989032524934433070DFF5F3500D11E82CE0000100007C113FFFF362007007FCFC92C314304009F252620070020029102810C8E02730241023902940287023F0254029062770227012F046D14108D26710885D5"); break; //QDS.37.35328909 + case 51 : return F("b1F44685034025063591B28F57AE100000001FD17000265EF070F6A18DFC253E0F351D0900E8180F9"); break; //TCH.1B.63500234 + case 52 : return F("b294468501904601473F0AE14A0009F27EDA5B130CA280080770001045667006BA1007CB2008DC3009ED4000FE50096BA80EB"); break; //TCH.F0.14600419 + case 53 : return F("b294468508220285376F0F19BA0009F27A1280028A128000033000006BC4C006BA1007CB2008DC3009ED4000FE50096BA80DC"); break; //TCH.F0.53282082 + case 54 : return F("b50446850920420745937163972612600006850FE036B0000200415CC4CB0551F0084041552FC1C0082046C7C228D92D804951F1E72FE000000006F1BF114A212D1B377124C1EA62E83430E5006511443943F9B47F52901FD17002F1F2F800E"); break; //TCH.03.00002661"); break; //TCH.37.74200492 + case 55 : return F("b3644685005012040714351C0A004372900000060DA4E029B5FBAC4123A84170DBBFBF13D06000000000000000000AE3800000000000000000000000000FFFF94A6"); break; //TCH.43.40200105 + case 56 : return F("b3644685005012040714351C0A0009F298F560290E10101A40000FFF719F04E99E7B2F9E2E512000000000000000042B000000000000000000000000000FFFF95A6"); break; //TCH.43.40200105 + case 57 : return F("b8E44A81524229207010276807F680080056E68ED762185BEDAB68F79E2A8BF6562ED1C21105B74E82AA9D1B5E79311AB713DC2F86C785D18F6E470067B86284E8A9D1AF9F36330FE18968F826F8D1B21E99F1C08D45FB9CDBF04B37136E2FC1CED74FF3186482FA3058F6F1BD712BBE20098CCB0C72D51F6013CBB974678706CF2F4D6D0D07A793FE1EC8EAF701AD2BEE5922E69F72C356AC1B009A0E0646B0413DD45802B"); break; //EMH.02.07922224 + case 58 : return F("b374465B2118222001604DDD67A26140000046D1107152A01FD0C06326E796CFFFF0DFF5F0C00083D300001061308131C0BFFFC02FD1700140C7896145559D9FF8032"); break; //LSE.04.00228211 + case 59 : return F("b4BC465B251A16000F193DC2CA083110049244E01394465324604401311FC17067A4F0000000C13714800004C1310BC7A000000426C7F2C02BB560000326CFFFF420A046D2009912682046C9F258C04136643DF650000FFFF80DE"); break; //LSE.00.00000000 + case 60 : return F("b4BC465B251A18000F1950519A08311009E2423003944653252044013781E17067A500000000C13063401004C1310623C000000426C7F2C02BB560000326CFFFF420A046D330E912682046C9F258C04138720452E01000B2780DD"); break; //LSE.00.00000000 + case 61 : return F("b43C465B251A12001F0DDDD95A08310008A2460013144653221587296186735087A4F0000000B6E9102004B6E000041BB00426CFFFF326CFFFF046D2108912682BC3E046C9F258B046E9102000B2A80DD"); break; //LSE.00.00000000 + case 62 : return F("b25C465B251A0000AF1033454A2653205834710290E862400005F3573BB1B9A276422A2271D001D0004C9253580E4"); break; //LSE.00.00000000 + case 63 : return F("b4344A511031008667607CFF48C0031900F002C2531D311006D980E9CC8D28524C6417AE570210710CFF2DB65BFE77C51602690DAEB954A5455A2ABED621B74AC56A79D81390EBFCADC9D3D34F5928DDC"); break; //DME.07.66081003 + case 64 : return F("b4344A511031008667607CFF48C0031900F002C2532D31100BC55A548A0082663A6FC7AE670210710C7B178D748F610E2CB16DE82C823EF83334EF1A0C383FB42DF7BED1846323211FC25DCC1EBF085DE"); break; //DME.07.66081003 + case 65 : return F("b4344A511031008667607CFF48C0031900F002C2533D311004E0C2E86D68C08F425747AE77021071013F9284E0CB5896FFB27D33882716D09F7EC8175FD04516AAE9122E584095D6441E14E5B4ED189DD"); break; //DME.07.66081003 + case 66 : return F("b6E44A511825169584004B8737AB500600554B11A7F9F7DDCAC35695A3191EA83FD79D1876F378419D5AB37CA1BD857243492B6379B258A4831015F9F0D4B7098218D7E7C44421422FCABC0770F0E67C16EFA13ABEE798D58062CDB0F06AA312592C085F046D29B64F7031FE17CC7EC8B44D61FB5E2F37301ACA7AAC025666C802C"); break; //DME.04.58695182 + case 67 : return F("bYB04424341931031950067A800000002F2F0413F1570000046D092EC52504FD17004000000E780000000000004413184B0000426CBF2C840113F057000082016CDE24D3013B4E0400C4016D3128692C8104FD280182046CDE24840413F0570000C40413FD560000840513D1530000C40513CC4F0000840613184B0000C40678D313EA47000084071323430000C40713153F0000840813FD3B0000C4081393380000840913A3340000C4091311310000271081D7"); break; //MAD.06.19033119 + case 68 : return F("b4E44A5113748906370076F917A570040053A66831E6B6C8F06FB2C7CC1F60A673B63ACBF2F6A8D3347D34DE4BBDD4677E5DEC850D5CBBDF5B0AA7095B415EFCD93A124AC3B884FAF226845C439DBF3A2EB486CBE7D8D845383E3E480ED"); break; //DME.07.63904837 + case 69 : return F("b5344A511292535727507858F8C000E900F002C25823E090045AF7EFEDB43FBE691577A82003107105692A21A8C0C7531EEEAC3AA631D6BD790CA1B271C7E3C8860A189DA37AE30E89AB63AD316663EB4AD472596156E592602F8A016E51A5E0D8753"); break; //DME.07.72352529 + case 70 : return F("b5344A5117157367276075A048C00B0900F002C25218501000C373A89C912675F09407A2100310710DEF25F58D772CBD51AD6D2B487ED868B6B086976F51AE52E65D877F0310E9DCA942F4E7F94196DD2821329E662FD4E4C9F20A1A8829C54328EEC"); break; //DME.07.72365771 + case 71 : return F("b2E44A511880110827B0780AF7A4573200592F4DF44D5F258DF39AB376DD4A5BC338BBB2493257A98DC52C5E6299029DC79C01ED377C7F48028"); break; //DME.07.82100188 + case 72 : return F("b3644E61E95413900010E89957212517615E61E3C074230206593240B570397DF6230FBC015B276D63CD694FA79B17D1436E0E9FA1003C0763761BD8FBD67B73A8D"); break; //GWF.07.15765112"); break; //GWF.0E.00394195 + case 73 : return F("b9644FA12237704190007308E7AA8000020046D1E2B862B0413D1EA01F9CA0002FD17000001FD481D426C7F2C4413455C9E8A000084011370E50100C40113A0C200F70100840213EA9E0100C4021349870100735F8403130A700100C40313595301008404C386139F2B0100C4041345FA0000840513BABE2BCD0000C40513DEAA00008406139E8A00A1F700C40613D966000084071390400000C4F6040713E11C00008408136200000087F382DE"); break; //DWZ.07.19047723 + case 74 : return F("bA944FA1283211320020766737A6B009025ADD27B2058B2ADAAE5B5D2C8B11BD295CE460BD1ECA50B533A58FF19F7F9DEC291893DA502B763E7C80CEF440B899C92DE87D5548838FC2C9FF3873927441B0B17222FB14545D2E5A0CF81216074B3DF0911CB99BD20A558F2D873A9A649092F3C7B01BA2BEBCC26A58C0FD99ABC2E5E0E5AFC241A27B8C0863C571C3D586B7E2A06044B792CFE021C21BB1B71BCEE4D5D84E75C3FC0624DF07C8C8832C82140086903FD0C08000002FD0B011116A380EF"); break; //DWZ.07.20132183 + case 75 : return F("b3944934419504913170628617A660000200C13480900004C130000007BBF00426CFFFFCC081315020000C2086C9FC6D42A02BB560000326CFFFF046D1316952B82B084D3"); break; //QDS.06.13495019 + case 76 : return F("b2E446850915944496262C568A0009F276D03B017BC0000060A0A0809A71C0908090706070A0A09090A0B0B080708CB640A060A07071EA586E2"); break; //TCH.62.49445991 + case 77 : return F("b2E446850573060566562ADA6A0007E293200C0191400001400000000CC37000000000000000001010101010101023FB20101010001328686E1"); break; //TCH.62.56603057 + case 78 : return F("b2E4468505162250070628CD3A0009F277D00600A0B00000001010101002A010302010101020102010101010101018F6C0001010102AF7786F8"); break; //TCH.62.00256251 + case 79 : return F("b2F446850302976627462A01BA2064D280000600A240004000307070747EA0706060000000000000000000000000077A2000000000000FFFF80F6"); break; //TCH.62.62762930 + case 80 : return F("b2F446850370321809562C217A2069F270C00A00E0700000001000001B0E800000100010100000101000100000100B1AD000101010001328680F9"); break; //TCH.62.80210337 + case 81 : return F("b2D44653277993613170784017ABA0000000C13196100004C131100003AE500426C7F2C02BB560000326CFFFF046D2ACD2E068527CCD580DF"); break; //LSE.07.13369977 + case 82 : return F("bY24442D2C394864681D168D20AE91BF1622CC255857FE7B524DD67C4944CD428FBE5E0DFAB98021"); break; //KAM.16.68644839 + case 83 : return F("b2D446532490340131706417B7AB20000000C13814000004C13100000A12600426C7F2C02BB560000326CFFFF046D2ACD3B168327AC23800F"); break; //LSE.06.13400349 + case 84 : return F("b3644E61E08106100020E7F627236090021AE4C01071B0020A56EE84D172DF9648D9C4409131B88D3FD85BFD022BCB54971CACB714D617E5563719B95E9B2059A59"); break; //SEN.07.21000936"); break; //GWF.0E.00611008 + case 85 : return F("b3E44FA1287530019011691D07AC6002025D541F7DE1D910813127884F7DF4D80BF600A4323BD730B4639E4E0EA8B86129BDE9DD71D0F800C000109002487721866530201310101B10187CE"); break; //DWZ.16.19005387 + case 86 : return F("b9644FA1261281221000689447AB9000020046D302BAF2704136300009A0E0002FD17000001FD481C426C000044130E9B0000000084011300000000C4011300002A2D000084021300000000C402130000000098F984031300000000C40313000000008404B0D51300000000C404130000000084051300A00E000000C4051300000000840613000000B5CF00C406130000000084071300000000C4E74A071300000000840813000000009995812A"); break; //DWZ.06.21122861 + case 87 : return F("b3E44FA12336300190106617D7A81002025F1114AE0F19A04778065DD02E84809E9F7163157F05FB2506D26A3835904CED370FBCA9E0F800C00010900E10E03000A70207F3101013FFE80E3"); break; //DWZ.06.19006333 + case 88 : return F("b4C44B40968440303170787F77A3F0000000C1305000000046D1A2EAAA19F250F8F00010000000000000000000000D5BD00000000000000000000000000000000FFFF00000000000000000000000000000000FFFF000000FFFF80EE"); break; //BMT.07.03034468 + case 89 : return F("bY29442D2C394864681D168D20B3B0BF162236090AB83DFCC84216131495B5A2DF59242760EDECA043AF20801F"); break; //KAM.16.68644839 + case 90 : return F("b2844C51473278071030605C97237253816C5140006612000202F2F0464E66D243498260413070B010001FD1700146B80DD"); break; //EFE.06.16382537"); break; //EFE.06.71802773 + case 91 : return F("b39449344715387121606AE447AD40000200C13969799994C132798998CC999426C7F2CCC081396979999C2086C9F18DC2502BB560000326CFFFF046D2E0D8D261542803D"); break; //QDS.06.12875371 + case 92 : return F("b39449344724733131706336E7A270000200C13330600004C13100000218B00426C7F2CCC081382010000C2086C9FF2142A02BB560000326CFFFF046D1116952BF4D081DC"); break; //QDS.06.13334772 + case 93 : return F("b3944934451926513180637967A7D0000200C13110000004C13000000B0B100426CFFFFCC081311000000C2086C9F52D52A02BB560000326CFFFF046D3A0B862B28D08027"); break; //QDS.06.13659251 + case 94 : return F("bY5444A85C3281262703077AA90040254A1AED9189683FF741015BCF9C9FD17914758544A14121969793DAA718C7C091F9E26BF16197828BD514A4E66C5460849605A64ACFBD3D3167332F6AF040711E426CBF237DD6802A"); break; //WEH.07.27268132 + case 95 : return F("b394493444993671216075B197AD00000200C13850441004C13286138A3F900426CBF2CCC081345574000C2086CDF284D2302BB560000326CFFFF046D330BD62497B485F8"); break; //QDS.07.12679349 + case 96 : return F("b39449344782049131707F5A37A480000200C13121902004C13000000D42000426CFFFFCC081353750100C2086C9F077F2A02BB560000326CFFFF046D0716952B9ADF80EA"); break; //QDS.07.13492078 + case 97 : return F("b3944934462526513180768437A880000200C13060400004C13000000950300426CFFFFCC081398000000C2086C9F0D7D2A02BB560000326CFFFF046D320B862BCC358012"); break; //QDS.07.13655262 + case 98 : return F("b3C4493440989323533372A78728903252493443307C50000200C133698392007004C1331430400426C7F2CCC081319DD26200700C2086C9F25326CFFFF046D058B67079026F78582DC"); break; //QDS.07.24250389"); break; //QDS.37.35328909 + case 99 : return F("b6644496A721102551437F5C77224831715496A000726005005827161FD1AB240AE86C2A6D7F691E4C8531E4530AE4FC8A294BE87862FDDDCE843D7679005C55E082A744BC18EA87FF12298AE4258EE9D89B1F9511318B8D7152464FB11007F5CEB827893513C954A6E9735185FE268124771D567A080E8"); break; //ZRI.07.15178324"); break; //ZRI.37.55021172 + case 100 : return F("bY50449726041331160007728499181897A60030E10030A59E74DF73596296EE08CF3F1BA88B47A3A264C30177EC921B750B03B99F992A0EEB064560EFAD6C5EC7CCA1009D72C63B0E79000000000000697E801E"); break; //ITW.07.16311304"); break; //ITW.30.18189984 + case 101 : return F("b2F44090773754205100794947ADD100000046D070DBB2404130F000029E0000259F0D834FD17010000000424132B5729820001FD7462313A802A"); break; //AXI.07.05427573 + case 102 : return F("bAE44EE4D449858203C07C8B87A0100A0257AB76440C2E16196857D0FAC3205AFB9D036CD7885C1F60C61095F2300D08DF56026E74FFB2F876BD89D27F65B3EA3729F53B26A48F2A5684EDD67A16177F8DD127C2CD8FE1C42CD5035E7EE110515C7369DB07AA59D5954E077C30AA29423D12429C77F6A7DEF679B3A90D7FD075AB7ED262466ECB4FDB66C609FC5DCBEA89FBCEB7BAA4EB8C312CF1A242B39A696A25E81CF0B5994B25CBDC06749D29F5B0F9E9F98476FFF9ED428840C0082459878C8FCEBA90EC380F7"); break; //SON.07.20589844 + case 103 : return F("b4344010648020780011656A78C001D900F002C2561675D61AD80F90FDEC404B8CAB47A80032007100A387268EF9F9CC8106E5D7FEE6E9D41D7E8780E2B18C2F71743F96AC442C6191D9B8E8E2AEE85F1"); break; //APA.16.80070248 + case 104 : return F("b2E44685017467703627254FFA0009F299A23A02C3A000008060605050B45060504020407060412140C0F0F100E111BFE070B0A0908C0EC81DC"); break; //TCH.72.03774617 + case 105 : return F("b2E446850812896626572C47AA0009F290D1620272200060001018DE4606A430F1101000701010302010403020403DC700002050302E7F581EE"); break; //TCH.72.62962881 + case 106 : return F("b2E44685007457014707281B0A0009F270400600A0100000000000001A1A60000000000010000000000000000010086A10000000000FFFF8007"); break; //TCH.72.14704507 + case 107 : return F("b2F4468509851603374725693A2069F27CE00600A600000000409090A8C140B090C0C0F0E070F0C0D0F0E0E0B0F0A640F09090D0E1009932C80FB"); break; //TCH.72.33605198 + case 108 : return F("b2F446850396450729572A1CAA2069F27FC09800D3E0200000238383EF87E353F3A3B3A2C2E1E2B3830111E2A181FA0AB2432273805208FCC86D2"); break; //TCH.72.72506439 + case 109 : return F("b76447916987435614037E4C2729646866079161102FC0060058CA7D7567090FFC84CBDE4FE2996BF8080E6A2C41D6DF46D8921894C814CC0BB36C550312F03A2734512ED7A69349DE3C349CFC079B62A3AD93BAA84A087C739785EFC9A415F6AF0662B05401FD0C4C9D4A676DA7FC1F6C592823EA6B6DFCAA3DDB2E4D5B6E87C5EA1B92E903D62"); break; //ESY.02.60864696"); break; //ESY.37.61357498 + case 110 : return F("bCE44A8154457060801022C397FDE00C005F4FEE3A225126DE3E8344CEACB8502C615B4F7EE9D9CA99F2E3AB5AEA9B3417ABD2B8DF835C0A8C31510DC184ACC8E261E24B717F51C01887D9B39D57965A4004CB68B65206E173F7374489CCBBAB63D4F4B33B488DE06DD33C93EB719BDD805E331238755D22C7E94ECD3636C1CCD965CF4E6DC37123EA8D95771BBCD12431AD12B646EBD21FCB77BD2D100C9CCCE7546268B3AF080DEC8F6B61BCC209013295A74A2730D9CB11E52056D0C0879EA29A2CE24210EAF58587855A790FF343CF82914E0164ED08C70FD54DB800BD9B1C0F2431883324E43A1D293"); break; //EMH.02.08065744 + case 111 : return F("bCE44A8153132000801022ADC7F9000C00546CF1AA7C3268079B9F3A0D8D2FA9AE941DDCDA1D0D8CFA6358FB9FDAD0AF38BB344D954ADD742E77428CA48DD918F361E23D5805BA6BB4CD2470016F66B92CF5D2AF69C7751F1FD2D887A10F26865E6AD95F58754980416935DA7A6C669823CDA9A3CF4D0EBE233D7F706DA6ECFD8209C8F140A7666B9AB2D9ED8C0D5AE04C3600B0899207B54AA98FABF9D14F773F41A53378E9A5678516B95EDEDED334821972EC196927F794E72725C78300FE65155E4E49B43A329EE44AD6FD90070548499CCFFD9FB71ACB4183B4B14DEF7D8B99BF02B754E651850960D80F6"); break; //EMH.02.08003231 + case 112 : return F("b7B445A1415200000023724958C2049900F002C259D7515009EC6163C4A7CB4E8935772324371005A140102490050646D0710FDE80DD548AA00BBCFEB3152BF11CDD91D4D31F1C73144BBB17696622104EB91D19A32FF7CA939126767495134EE598AF96AC9F0081A83B4D7EAF7A497981C15604C85E4E3B9FBA393BDAD8E0DED15EED4BA887D9970D17797849FE68045"); break; //EBZ.02.00714332"); break; //EBZ.37.00002015 + case 113 : return F("b5B445A14152000000237E4CE8C2048900F002C259C751500003AD02FC41ACF50E92F72324371005A140102480030E8FC8710B2BC8D30DC227AD803ED24F3B6FCC6E5131442E65A7F5CC4589D51AE2FB2690E9457810BDAAAC81988A3E5E81E6CDA24BE256218D907511C8044"); break; //EBZ.02.00714332"); break; //EBZ.37.00002015 + case 114 : return F("b1E44C418557901000102E4437ADB00108561D52810CD237CA87FB7C5A2F62A65C25C457BF78028"); break; //FFD.02.00017955 + case 115 : return F("bY4344471325798761013672193170604713010210000000000004788F419E030DFD110E39313133303730363030475A4431060200000000000006823C0000000000004E88"); break; //DZG.02.60703119"); break; //DZG.36.61877925 + case 116 : return F("b7B447916419806613037A83A8C204C900F002C25B4AA0000CC2128E94C8F3D946F737210552961791611024C00500ACF0710FCFB475BDA1FD49D52FF6FB45E833D5D97609373AB3EB562E01CE23D1389FFEC7EE41D4E7B20D35D8B80581C289834F4E3C542C09B0C37331332A321B79DAF7F60CDB028452AE3A26291EFD25C87DDC9EACF462C7B441510340F755C800B"); break; //ESY.02.61295510"); break; //ESY.37.61069841 + case 117 : return F("b6B44791641980661303756A58C204B900F002C25B3AA0000ABEB03EC47E933C072B97210552961791611024B00400536871036D216BF2B74E11F1952539F406B7D4DB7721B1154E21C36F0F55BD4B5B5FF9977572BCD27D93A909347A0D84AE9E804C361E9E6D98F9BE296F76E3A5100357CDD3FA09AABE44103EB508044"); break; //ESY.02.61295510"); break; //ESY.37.61069841 + case 118 : return F("b73447916856565601002F8DC8C0037900F012C25809A62002B5C02299AC829AD82267A370050471010A9AE87B893A51E226DD3203AD775D84E2B2E04D96F85DA0CD258EBC10912241ACAEB1406433A637F6C70811B7FC99565C388A0365A9B57F94E0380317F5CA1052D6FE55C7BE3118A4D1CD05AABC646F5D288B129D7934E68CF030A"); break; //ESY.02.60656585 + case 119 : return F("b7B447916419806613037A83A8C20EC900F002C259EB10000F76F8B1B2D9E402B9498721055296179161102EC00508B8E0710072DE1601CB75C3C9C30E9E037024B1D516151D3724CCF1654B2AC7707B5B41F2BBE3F2C19E9467A0740AB6A4F7F03F5E764FCCECC689EEA66BB4A9A6F25FDDCE8A60E9D998A43936B8A3B2EB57A8F667B476D1ED1CD2381D0ABF6118033"); break; //ESY.02.61295510"); break; //ESY.37.61069841 + case 120 : return F("b6344FA30883161560002C10A8C2042900F002C2549030000BC2C8A72651D570AEB967AA7004007103CB056B91B8007B8882351411FD19256641A220725C724891287445B85FDFDA2DB638B542BB7FC7209833DE16EDCBB90FA686C10076EED63BD40AB0EE3EBCDFDF40032F5C0DCD66322BE804A"); break; //LGZ.02.56613188 + case 121 : return F("b6B445A14043300000237C1FB8C200E900F002C250E000000F96A649A04D7DC457E8972807759005A1401020E0040A6D20710A07B663F2ED91A0DEA4515C0C1D4A296569A7C4DC27AE724F38CF0E518949AFAA9C3AA8703638F5305E2A333941756C4F5BF4703A411B7FE92F58D7F2724E31E10DCDCBF2C7EE18CD4738043"); break; //EBZ.02.00597780"); break; //EBZ.37.00003304 + case 122 : return F("b5B445A14043300000237FF3F8C2001900F002C2501000000574262230ACE2FB2FB5072807759005A140102010030303C8710DD05806D5E2EFB0ACD9DA1964A191D7BADA8E1A508EE7CFA86336B14B1C756AA1E20FA5CE029D2CE99CC84BFA0CAFD723A5D0BC20E6FECC585FF"); break; //EBZ.02.00597780"); break; //EBZ.37.00003304 + case 123 : return F("b4E447916110000600702D8607A99004005860F4BADF0716289AEE56290239E4549E908B33CF4C280DEDD3382DF293865824ADFA25EBDDDF28DB046A59DA2A4C2DB62CF177F2E77EF3E62D6A67DEA6BBD01BEA1DFB9ED3DC8743E2E"); break; //ESY.02.60000011 + case 124 : return F("bD344A815394643100002D3DD8C2003900F002C25741E0000A17997475A31870EDCF27A0300B007101E6BCA68B34F4603824A04FC0B87A398438300B31366E3B94F45C0EEBFF210719F87A8382509BC329B59A4031ECAC4CA734374B06825329A3665EAA2C626DA096C8E50002E95443D540F92C90EE305931E8E066BB8A76AA2DDE94042B90B94E9D3174612DBF8755145BBA760A85B7C84DDF075950FFDE6EEB4EF76A664F4A474CE050E12021434806BB9C4CDDA5CA8F13DDE8010C5DB0EAD57FD5E6DF042A1C0AE83DA23EB2B8C3444A11D8A6F6274072E516F42DA449DC460D14C1D9E12F95CAE43112976493AD493F0"); break; //EMH.02.10434639 + case 125 : return F("b8E44A815262292070102C8F57FED00800592BC9808524F745DE09733D6B862F1FEF9AC816CCD59B730C0BC8D173E7B1B187735C505768A284780C3B7E53CCF252EC84F44DF2A8949DB12139FD80D000930321F57E73C8F22B44AB57C03F93C35B02B853E45A0960F0FCD326F4760CA76945EADF6F294356AC8308DE284EEBCECB57EDF9BB63BCC5D424A764348B2D40E86C812AEA58CA14B652DD3853BEF5BDF56A27F8031"); break; //EMH.02.07922226 + case 126 : return F("b5344A81594176710020267788C20D7900F002C256A000000E4D7497D043A0EE5B2A47AD7003007102F390C301AD7CB52A6B8633CA25AA26BDE4FDF94F3230E43CCBF66EEC0D4C0C7A6E66310DC6376768FE891C0CA84DD365D3FBF23690BB55E812B"); break; //EMH.02.10671794 + case 127 : return F("b2644AC482711000050378A347201271100AC4850021B0000002F2F0C01C103895936002F2F2F2F2F2F2F2F84F48029"); break; //REL.02.00112701"); break; //REL.37.00001127 + case 128 : return F("b7C44361C120001000002CA9C8C203F7A3F00000004050000000004FBCBA782750000000004FB82F53C000000000412B22A0000000004FB140000000004FB943C59F00000000004FDD9FC010000000004FDD954D8FC020000000004FDD9FC030000000004CF6EFDC8FC012909000004FDC8FC02F10400CDDC0004FDC8FC03F104000002FB2EF40101DC37FD1700DE8C80FE"); break; //GAV.02.00010012 + case 129 : return F("b3244361C373601000102603A8C20457A4500000004050900000004FBDA3482750800000004FB82F53C0000000004C3AE2A2415000001FD17001EB2801B"); break; //GAV.02.00013637 + case 130 : return F("bY394447135523636001027A830000000478232D9D030DFD110E35353332333630363030475A44310602ED8B0000000006823C00000000000004E4802B"); break; //DZG.02.60632355 + case 131 : return F("b314493447236379635085D337A040000200B6E3103004B6E070300420D0E6C7F2CCB086E310300C2086C9E24326C1422FFFF046D240E86250E4E80E2"); break; //QDS.08.96373672 + case 132 : return F("b494493447236379635083FD9780DFF5F350082030000F00007B06EFFC6F5FF310300007F2C070300009E24310300E13000008000800000000000001F007300A145C9006BFF64003D000C002F046D200E86255AA081DF"); break; //QDS.08.96373672 + case 133 : return F("b494493447153871216062D53780DFF5F350082DA00007F0007C113FF8331FF969799997F2C279899999F2596979988C999000000000000FFFF000000000000008B35000000FFFFFEFF00002F046D25068C2608D2803F"); break; //QDS.06.12875371 + case 134 : return F("b344493447153871216069A667A8000082004ED3926096C2701FD0C110157046D17138F2602FD3CC2010DFF5F0C005FC50861FF000006130701FFFC138B803F"); break; //QDS.06.12875371 + case 135 : return F("b3D44934417196746221AD6FF7AA210000081027C034955230182026CAC3BB62181037C034C41230082036CFFFF0238E2FD171000326CFFFF046D0311B62102FDFE8FAC7E19004F33802D"); break; //QDS.1A.46671917 + case 136 : return F("b3744934417196746221A40247AA310002081027C034955230182026C5AE5B62181037C034C41230082036CFFFF0238E2FD171000326CFFFF046D0311B621AD04802B"); break; //QDS.1A.46671917 + case 137 : return F("b39449344775149131706E1D57A680000200C13750000004C1300000010FC00426CFFFFCC081358000000C2086C9F61212502BB560000326CFFFF046D070B8A268D5880FA"); break; //QDS.06.13495177 + case 138 : return F("b4944934477514913170662C2780DFF5F3500827B0000810007C113FF5A69FF75000000FFFF000000009F255800004659000080008000800080008000800080009B248001000000000004002F046D1F0D8A266CB280FA"); break; //QDS.06.13495177 + case 139 : return F("b3744934484145047231AAE907A9600002081027C034955230082026CD1DAFFFF81037C034C41230082036CFFFF02DDAAFD170000326CFFFF046D0503AD21AB3580D9"); break; //QDS.1A.47501484 + case 140 : return F("b39449344843350131707BA4A7AF00000200C13000100004C13000000C11300426CFFFFCC081361000000C2086C9F6DF62502BB560000326CFFFF046D370A8A26486681E6"); break; //QDS.07.13503384 + case 141 : return F("b49449344843350131707395D780DFF5F350082000000EE0007C113FF4BC0FF00010000FFFF000000009F256100009FB5000080008000800080008000800080009B248001000000000005002F046D020D8A263BD880E7"); break; //QDS.07.13503384 + case 142 : return F("b3C4493440989323533372A78728903252493443307C50000200C133698392007004C1331430400426C7F2CCC081319DD26200700C2086C9F25326CFFFF046D058B67079026F78582DC"); break; //QDS.07.24250389"); break; //QDS.37.35328909 + case 143 : return F("b2C449344098932353337D4E7728903252493443307C000082004ED39ACEB2C06702501FD0C10046D1C06902602FD705D3CC20189FB81DA"); break; //QDS.07.24250389"); break; //QDS.37.35328909 + case 144 : return F("b5344934409893235333714F478077989032524934433070DFF5F3500D11E82CE0000100007C113FFFF362007007FCFC92C314304009F252620070020029102810C8E02730241023902940287023F0254029062770227012F046D14108D26710885D5"); break; //QDS.37.35328909 + case 145 : return F("b624468501509651494085A758C002A900F002C25DECE080028421E778E39B0665A707ADE0030071080A1255F4529D6628D1BD017B641EF0A8046B680C77DAB285B3C2A943522663000821E48556555C93D048F5DF327EFA4BECCA1914A1C0F9EFCE6532756F5E9BF68B0E01E8802D91270"); break; //TCH.08.14650915 + case 146 : return F("b31449344401892903408DFBB7A9B0000200B6E4800004B6E14010042A7D46CBF2CCB086E480000C2086CDF23326CB9BCFFFF046D0E0BD624CF16800B"); break; //QDS.08.90921840 + case 147 : return F("b314493447236379635085D337A040000200B6E3103004B6E070300420D0E6C7F2CCB086E310300C2086C9E24326C1422FFFF046D240E86250E4E80E2"); break; //QDS.08.96373672 + case 148 : return F("b3444EE4D774933221608D1BB7AF7000000046D280DBB24036EEB000059A9426CE1F7436E00000002FF2C000002598620C80B0265F10802FD66A000779E80EA"); break; //SON.08.22334977 + case 149 : return F("b2E44B00971280018540843227A0E0000202F2F036E0000000F1000016F1B8563822A0000E70A812A000000000000AF010000000000FFFF81DD"); break; //BMP.08.18002871 + case 150 : return F("b2E44B00971280018540843227A0E0000002F2F0F0000000000000000718D00000000000000000000000000000000FFFF0B2A00002FF97381DE"); break; //BMP.08.18002871 + case 151 : return F("b26446532392238253508D6A07AAF0000000B6E0000004B6E000000422F866C7F2C326CFFFF046D2D0A83279B6780E8"); break; //LSE.08.25382239 + case 152 : return F("b62446850189272149408C4E28C00CF900F002C2564B20800A81E8BCCCA14532696097A6400300710F61F531C03426B9317EAD69912E862AC2017148D8C179D50E133470B7A04CD063D00E6FB1C522DDDC80A3300335E26FE16F9D339A61C0F41BC1ACF4A0A8A29310674536C344CA64D7D"); break; //TCH.08.14729218 + case 153 : return F("b434468501892721494083F2A8C20CD900F002C255FB20800903D2653461ABE7080E57A5F002007103032214C1915B7A16175B00F90B8EB889A4C280207D9C74F5A4088C584D951BDB20851C3DF9E"); break; //TCH.08.14729218 + case 154 : return F("bY2F44C5145935816213087A080000202F2F046D0F2EBB24036E000000426C9F25436E000000317F00346D00200000F1458737"); break; //EFE.08.62813559 + case 155 : return F("b3E44F536861202000108F54A7A9B0020250A86DF3204D8B7DAFBD62C57159092FFCBB77F612E656C59AD065C96CC24B353B024A6930F800C00010900E10E03005DC1207F1B05074A5380F2"); break; //MWU.08.00021286 + case 156 : return F("b2E446850844616516180E459A0015C284604600A7200003900341D9A5C477E9ABC873D05000000000000000000019BB4000406456E897887DD"); break; //TCH.80.51164684 + case 157 : return F("b2E4468506974806164805122A001DE26EB02900D570200000000040D21820D4E301126353030334B44170B0A01004EE40000000000FFFF83D9"); break; //TCH.80.61807469 + case 158 : return F("b324468500441169269802F7CA0119F272F01600A3B00C8082F09000007730000000500000220140B1807030000007111000000000000000001C29A85D5"); break; //TCH.80.92164104 + case 159 : return F("b33446850710351129480ABE9A20F9F270000D00E03000128090B0900DA3600000000000000000000000100020000274800000000000000000000FFFF80E3"); break; //TCH.80.12510371 + case 160 : return F("b4944C51402203571000451A77A090001202F2F046D2E299926040687ED7214000001FD17000413F6670400043B009378000000042B00000000025B1900025F1986B90002610A0003FD0C05000002FD0B3011F52BA393"); break; //EFE.04.71352002 + case 161 : return F("b2844C5146427807103073D877234626016C5140007D72000202F2F04DD1C6D2F3498260413B96E010001FD1700066080E7"); break; //EFE.07.16606234"); break; //EFE.07.71802764 + case 162 : return F("bA644C514960080900307DABF7296008090C5140007ED0000202F2F426E8D6C7E2944133C08000001FD1700840113039188130000C40113D611000084021324108D5A0000C40213A70F00008403132C0F00003550C403137B0E0000840413C0080000C404749913650800008405133C080000C40513AD831B07000084061380050000C406138401006999008407133A000000C407133A00000084286B081300000000046D332A8F260413A7137132000003FD0C02032102FD0B01114D8480FA"); break; //EFE.07.90800096"); break; //EFE.07.90800096 + case 163 : return F("b4806AC1956030015020363917256030015AC19020323000000466D0009780004872B000D7811313136353330303028633531343530303030308940FD1A014C933B263A494700004C130546000001FD67030D9A8030"); break; //FML.03.15000356"); break; //FML.03.15000356 + case 164 : return F("b3644A511621280223837CE5E7241022436931581038C002005A8F458578136DF07A14CE37F82BA702DF936647F231F18C961CF7A6CE31CFAACE57153A8888B"); break; //ELS.03.36240241"); break; //DME.37.22801262 + case 165 : return F("b1F44685034025063591B28F57AE100000001FD17000265EF070F6A18DFC253E0F351D0900E8180F9"); break; //TCH.1B.63500234 + case 166 : return F("b294468501904601473F0AE14A0009F27EDA5B130CA280080770001045667006BA1007CB2008DC3009ED4000FE50096BA80EB"); break; //TCH.F0.14600419 + case 167 : return F("b294468508220285376F0F19BA0009F27A1280028A128000033000006BC4C006BA1007CB2008DC3009ED4000FE50096BA80DC"); break; //TCH.F0.53282082 + case 168 : return F("b50446850920420745937163972612600006850FE036B0000200415CC4CB0551F0084041552FC1C0082046C7C228D92D804951F1E72FE000000006F1BF114A212D1B377124C1EA62E83430E5006511443943F9B47F52901FD17002F1F2F800E"); break; //TCH.03.00002661"); break; //TCH.37.74200492 + case 169 : return F("b3644685005012040714351C0A004372900000060DA4E029B5FBAC4123A84170DBBFBF13D06000000000000000000AE3800000000000000000000000000FFFF94A6"); break; //TCH.43.40200105 + case 170 : return F("b3644685005012040714351C0A0009F298F560290E10101A40000FFF719F04E99E7B2F9E2E512000000000000000042B000000000000000000000000000FFFF95A6"); break; //TCH.43.40200105 + case 171 : return F("b5E44496A973430000D1AFCF97AF7CB50057AB6BD92ED8A65D4DB8A19DDA1B6D3CD164A0F600C93485BCFC48263B255C4FC57033B6114A4DD1590F6E3B22855F7161BBFB100973B49CDB3593DEC5164ECF04F1CAB0311E89B873ECECCD7FBF0D60D125734B551865D12F7D012748025"); break; //ZRI.1A.00303497 + case 172 : return F("b4E449344492512971437674F72090328039344141A7510002081027CB4ED034955230F82026C8927C1027C0354466C2C230FC4026D2C0E892781037C034C412307BC3282036C892702FD171000326CFFFF048C536D2809BA22ECC883DC"); break; //QDS.1A.03280309"); break; //QDS.37.97122549 + case 173 : return F("b4E449344505427971537894372380266079344151A6203002081027C8B4A034955230282026CFFFFC1027C035446B3DE2302C4026D1107632681037C034C41234AE90082036CFFFF02FD170008326CFFFF0469C16D000BB822FEF28024"); break; //QDS.1A.07660238"); break; //QDS.37.97275450 + case 174 : return F("b3E44934435188745211A19F67801FD080D81027C034955230182026CF8CB932381037C034C41230082036CFFFF0359F5FD17300010326CFFFF046D060A8428027576FDAC7E5F0192628012"); break; //QDS.1A.45871835 + case 175 : return F("b3E44934455319745221A29607801FD088A81027C034955230F82026CB7439C2381037C034C41230082036CFFFF037174FD17500010326CFFFF046D260B862B02842AFDAC7E8200271080FB"); break; //QDS.1A.45973155 + case 176 : return F("b3E44934404155047231ABDD57801FD084C81027C034955230082026CCC02FFFF81037C034C41230082036CFFFF03E0CFFD17000000326CFFFF046D080DAC2102EA18FDAC7E11005C3E80E0"); break; //QDS.1A.47501504 + case 177 : return F("bY60442515485001000C1A7A23005025568AED71E43AF834900BEC738E08C4FA2637B8915FB401FD6296F19C3AEECEEBC3164B967CD5445E6AAFE90F416314191CB1839210B7CD2EFE168911FD465DAB56CCDA9C82862B90F29353AB57532B49E67E"); break; //EIE.1A.00015048 + case 178 : return F("bY75442515639716000C1A8C208A900F002C25A7000000DDCE55D203E089D07AA800500710760097C0A7F24E9681882D62EFF802EC33146C9B3828FD5B2026432F40E7098DA78C4538579DDE260B2DCCE933093DA312A2C499D4473F150422121279632724B2FCB44A2110D2A2DA87B8C084512CDD698F"); break; //EIE.1A.00169763 + case 179 : return F("b5E442515954400000C1AA17A7A74005025F6B841CED5F1892796E8217F31F08E864EF5C0BBBDFE640AE3711C34C4B5B8AE3B821F1F0F3FF81C8259CBDABD6D05A0A751305C3399E9450DE8E86BE0BDB2D7AFA79BB10179B7EB1F37983CB8C7F10746888DFA54B4CD95AB8C78018025"); break; //EIE.1A.00004495 + case 180 : return F("b5E442515695800000C1A452E7AF200502547F361FEDBED8040998A4AF106CE368D0D469CFE69DD1983E5D5A84078AB4F4F2AFD97FD57668A660038C14F4B79CC3CAB703C3C4ADB6B19AE027E25C4E157B23E9A4D5A7CF24F4962BD29591A73F3E3BD13C9F26D15826364C2DFB28051"); break; //EIE.1A.00005869 + case 181 : return F("b37449344981002451F1AF6837AC418002081027C034955230082026C7CB5FFFF81037C034C41230282036C6D2A027047FD178400326C962A046D080FA623CD2B8AD1"); break; //QDS.1A.45021098 + case 182 : return F("bY5844972642631092001A727299291897A60030B21340A5C84E203CFF62E039C2A1F61CF679A05B7DA7F31E4F0AAA0F30EFFECF4AC176AC9E173C426799C618D50A4B285CBB9074CDF78FA733C7AEC4F66C45D760FDFEE2327E8016"); break; //ITW.1A.92106342"); break; //ITW.30.18299972 + case 183 : return F("b3E449344981002451F1A2CED7801FD08FB81027C034955230082026C61FAFFFF81037C034C41230282036C6D2A034D22FD17840018326C962A046D0A0EA523028B4BFDAC7EF0002E078AD5"); break; //QDS.1A.45021098 + case 184 : return F("bY5044972694376092001A7A3E1340A553D753D0583A78B2BC306DF80BA1DBF6FC88DAFCD83AF7D955EC6196B643A571494B99AE831F8894A4726042E23EA5C474411A585EEEEE8E84A15F54562C31168159804D"); break; //ITW.1A.92603794 + case 185 : return F("bY5044972625353893071A7AE40040A517031B1A58F312C641E1E45D19F6DD3EA9B61FC929D70F39747F6A8A3BA53D7ED25B2A86C741728467DADC3652D54F40660B397E72F60CE3434006A1D843B3248D9C8016"); break; //ITW.1A.93383525 + case 186 : return F("b1744242349075722754982E471F202000001FD0C6202FD173000CFB38EF8"); break; //HYD.49.22570749 + case 187 : return F("b1744242349075722754982E471F602000001FD0C6202FD17300044308CEF"); break; //HYD.49.22570749 + case 188 : return F("b1744242349075722754982E471FC02000001FD0C6202FD173000B52092F0"); break; //HYD.49.22570749 + case 189 : return F("b1744242349075722754982E4711402000001FD0C6202FD17300084489CF0"); break; //HYD.49.22570749 + case 190 : return F("b63442D2C272951803504DCB48C2064900F002C251664000038AA5E8D66325C253B6B7A6400400710EDD4746D6402CF31496EE7AE09E634270E5701ED9E5D16E7A5A22EBC15B0CB6AC0EF980F73E5AD3BF6E658AFC24F614AFBF844AD1EC8CA21C0FCF8FC2E9E64C0B28542EE8C7EA37B444A8036"); break; //KAM.04.80512927 + case 191 : return F("b5E44A7329022226704041EDE7A7500502599F38B5BB9B53F705A6B158D76D3C33F390AE5F6E48A051680C3B866F317F4DBC781E920051DB2619F768DA54EB632DA8746E483A5569A9D8C0E16905ED61857D1B9A07C6EB6AE501B22E0D55A7DD4AF67DB88D6DDCCA5B78E5B88528014"); break; //LUG.04.67222290 + case 192 : return F("b60446850090510825937364F8C0039900F002C2584340D0063667A2334810387B25C72989280612423FE048500307DA907106591F67C43C0B36FCA410346B6A06E7CAADE06D06CF2911ED2775E3297F20105876C245C9309EC93EA0F68B3F9FA466371C73B4A39B80FEBAD1F9C40758028"); break; //HYD.04.61809298"); break; //TCH.37.82100509 + case 193 : return F("b62446850189272149408C4E28C00CF900F002C2564B20800A81E8BCCCA14532696097A6400300710F61F531C03426B9317EAD69912E862AC2017148D8C179D50E133470B7A04CD063D00E6FB1C522DDDC80A3300335E26FE16F9D339A61C0F41BC1ACF4A0A8A29310674536C344CA64D7D"); break; //TCH.08.14729218 + case 194 : return F("b434468501892721494083F2A8C20CD900F002C255FB20800903D2653461ABE7080E57A5F002007103032214C1915B7A16175B00F90B8EB889A4C280207D9C74F5A4088C584D951BDB20851C3DF9E"); break; //TCH.08.14729218 + case 195 : return F("b48442423240756341200933E7AA0303A31A074C27AB1B79A4BF71E2818DAA788D064B94612F5AED2C06E10F1A022615C7EA6E4E97D999D450C33C358340DCB3D948F7F9B58E2B922ED1A580BA905B4E34388A184D5"); break; //HYD.00.34560724 + case 196 : return F("bY5044972670314082001A7A120040A5AE9E0AFE53E0E5216516C41E94CCEA4220BC25A8A5CCF38635E315900BA6BEB0CB5E343BF419524FFED59CD28D106314AC875F9812782700A8267FFCDB4A24251099804A"); break; //ITW.1A.82403170 + case 197 : return F("bY5044972670314082000A7A7A0040A5CE2F908D1B6C7FAB19CF06F0EE140BB44A84C7FF54BDA05D33D6EE45686D054984A4467283EDA6514E8094E361082D9555BD043A64D3F593BAE29C577984921E3CBC92D7"); break; //ITW.0A.82403170 + case 198 : return F("b3B44931536000000013721968C30AA900F002C25BBD101005A558A6E08987D124F027212334938931581036C0010657B0710B23E83B33BD1C8F5A3AB0DAFCEFB35C1D44718C68011"); break; //ELS.03.38493312"); break; //ELS.37.00000036 + case 199 : return F("b2E44B05C95720000021B556C7A500020059C8692AAFADBDBCAA36875B54901F2E32655B735A59AB899223306CD8402A02D189C816E86BB8061"); break; //WEP.1B.00007295 + case 200 : return F("b3B449526564412004237BD198C20E7900F002C25641C00002B7452AB08B6F3843AA6725644120095264203E700108D660710B78BE5BE2867BD38DCE24619A59D0A6BB9384E3A80E2"); break; //ITU.03.00124456"); break; //ITU.37.00124456 + case 201 : return F("b2E44A5111863054230037FB57A30002105DE7DCE381F74E06136FEB49A5B3D45B688341DDCF387CC0D6344DF5BF60078C7A596B1A0D3BE8020"); break; //DME.03.42056318 + case 202 : return F("b1B44A5110301808238379BA77241022436931581038A88000002A7184A0AD900E327803A"); break; //ELS.03.36240241"); break; //DME.37.82800103 + case 203 : return F("b2644AC488437000050379BF37201843700AC485003210000002F2F0CFB6E16038161002F2F2F2F2F2F2F2F2C138057"); break; //REL.03.00378401"); break; //REL.37.00003784 + case 204 : return F("b3644496A855900500537290F7285590000496A0103CC002025CF5138AEB21021CE8339724700D0AF89B3CDDAAE2BAD28479AC27ADBE1E3C3B849269E632772E54C"); break; //ZRI.03.00005985"); break; //ZRI.37.50005985 + case 205 : return F("b1B44A5110301808238379BA77241022436931581038F88000002A7183766D900E3278003"); break; //ELS.03.36240241"); break; //DME.37.82800103 + case 206 : return F("b3B449526404107004237CE7B8C2076900F002C25832C020029B8AE8C5A07F337A5EF72046318043041000375001016F10710C9B050BA32F8730DFCC9465A1C3385BA84D8190D801B"); break; //PIP.03.04186304"); break; //ITU.37.00074140 + case 207 : return F("b3B4493154809000001379A2E8C2028900F002C252907000090080FACAA513D7A7B7B729549120430413A03CB001054A107101672D7C39DD223F7A8D3C4FFF7E9CA294237B1AD83DE"); break; //PIP.03.04124995"); break; //ELS.37.00000948 + case 208 : return F("b1944C418637901000103C9CE7A690000A00414EE75620202FD08EAF2A1B38039"); break; //FFD.03.00017963 + case 209 : return F("b3B449526544412004237036C8C2065900F002C25943B00000CC9351967A937062362723991270492262203650810BBE30710483A983059AFE3C21836C683F23BA2B5A04EBAE38021"); break; //ITR.03.04279139"); break; //ITU.37.00124454 + case 210 : return F("b3B4493154909000001375BA68C20BD900F002C25BE0200004F4E34B29075C9986ACB722009210493150003B10010E2D407103C75563866FAC12EBCE6FC5829323547A3CC9A488057"); break; //ELS.03.04210920"); break; //ELS.37.00000949 + case 211 : return F("b3B44931558130000013727848C20DD900F002C25DE0C0000ADFB2D6B2E02C73267AF72011713419315250339001037AB0710A68555FAD7A29A1A875EE33FD26D13EAE33A7AF08035"); break; //ELS.03.41131701"); break; //ELS.37.00001358 + case 212 : return F("b3644A511870350133837198E7280409628931580038F002105F09DD43A485B949F2AB07122E6A6CB3E4BDF9055A351316D145C3EF8522BDF56D9D12495B281D5B0"); break; //ELS.03.28964080"); break; //DME.37.13500387 + case 213 : return F("b3644A511621280223837CE5E724102243693158103850020050075969BAC2F21DD6DEDFFDBDDCF9F0E9A7E066046DF920DF3BFE4DA1174DE1943F173A93C0E2A5B"); break; //ELS.03.36240241"); break; //DME.37.22801262 + case 214 : return F("b1B44A5110301808238379BA77241022436931581038F88000002A7183766D900E3278003"); break; //ELS.03.36240241"); break; //DME.37.82800103 + case 215 : return F("b2E44B05C10130000021B34137A490000002F2F0A6653020AFB1A5105E7D102FD971D00002F2F2F2F2F2F2F2F2F2FDF772F2F2F2F2F25EE8016"); break; //WEP.1B.00001310 + case 216 : return F("b2E44B05C77010000041B1E0B7AE60000002F2F0A6641020AFB1A8103251802FD971D00002F2F2F2F2F2F2F2F2F2FDF772F2F2F2F2F25EE80F9"); break; //WEP.1B.00000177 + case 217 : return F("b4E44333081400004032AF18B7A03004005E5AEF57BA739B9789BD9AAFAE16068AFF9FB85A70613B3800B85D28B409B5BDD9607BE2A1450EEE20137C663FB522F4E6B9E914E8E3795C577BFB32D6E0152C21EB0358C41DD6D9F76498020"); break; //LAS.2A.04004081 + case 218 : return F("b1E443330886102001E1B8E9A7A180000202F2F026584030778281E18ADDC240B0000AB01768017"); break; //LAS.1B.00026188 + case 219 : return F("b2E443330603903003C1B18AF7A77002025BB29E575E63C4AA6174A82CED44D5497FA0207F44D4518D8CFA53CD7024276A675D4E0621C0F80E5"); break; //LAS.1B.00033960 + case 220 : return F("b7044B40908794920101B6F8B7A320000000265490A42651F0A8201650BCEB9072265D70912655F0A62659905526576FB4F0A02FB1ABD0142FB1ABB018201FB1A36B6C60122FB1AB90112FB1ABC0162FB1AC24B1C0152FB1ACF01066D3B1D2EAA25000FFFBE95FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCA00FFFFFFFFFFFFFF7B5382DE"); break; //BMT.1B.20497908 + case 221 : return F("b60449615347810630C1BE5957A8C0000202F2F026505094265F908822DF20165880822658C081265680A62655808B1785265680A02FB1A880142FB1A95018201130DFB1AE00122FB1A660112FB1A100262FB491D1A660152FB1A1F0202FD1B60030DFD0F817605302E302E340F6C04"); break; //ELV.1B.63107834 + case 222 : return F("bY2944961565181468201B7A140000202F2F0265E20842659A0802FD1B30030DFD0F05302E302E340F5E46"); break; //ELV.1B.68141865 + case 223 : return F("b1644AF4C40020041011B788C7A0A0000000266F3000266F10026FE800F"); break; //SEO.1B.41000240 + case 224 : return F("b5E442515695800000C1A452E7A590050252502B45C2E823EF856FABFC4775E28D2904492FB74BA65A843BA7E63BC698D83279AE2BE04B957699517818F7B33F8E58001A7C9CE0E73C5769487247954D0A000E811754BAB1CED0E61567EB1FE504B091E0C7DCF4632E3FAC31618804D"); break; //EIE.1A.00005869 + case 225 : return F("b5344E2306291001500030F388C30A7900F002C2583AE010032E1E493C32BEF51CDA37A430030071027A19EE14B0BBCAD656D0783516CCB7CFBC6AAFAECDCAD70020FE3DA54FCBC8EC2AED88DFD0972C55CF9336E1683574ABADBD046BB53623F8013"); break; //LGB.03.15009162 + case 226 : return F("b5344A732806139690404B70A8C2063900F002C25923338000C8BC361CE2EE050FD3B7A6340300710DEC49523134391877289A80A53A505655A833F754F221E619D08FB4DB5AD773EAB16B545B306C69D1493CD851012BBF4624A5DDA556AF07E83E5"); break; //LUG.04.69396180 + case 227 : return F("b9644A732460335700A043C1F7ACD0000200274DE02046D030D94250C804C0623000000446D3B177F2C4C060000005D610084016D3B179E248C010600000000CCB81D0106000000008C020600000000CC0206B190000000008C030600000000CC030600008B9400008C040600000000CC04060000000099348C050600000000CC0506000000008C0673DA0600000000CC0606000000008C070600E0F80000003C22000000000F0010004FF980F9"); break; //LUG.04.70350346 + case 228 : return F("b13440000000000DA00DAA8CF7101FD0C3A02FD171101CB938032"); break; //@@@.DA.DA000000 + case 229 : return F("b63445A146699750001026CE68C20D7900F002C25D7CE0C000B7C13179B38522166CE7AD700400710CDEF8D2A82F77DD15E367871F1E04261AAFAC430C2B55C1DED4A3148306D4C296CF10D72C9E79310A47DD73FDBFDF2CEA6490B6CA12A30EE5D64621A90B5E71F75D50D24C87B10E2ADDF802E"); break; //EBZ.02.00759966 + case 230 : return F("b5E44496A3680003888049D2D7A1D0050053FBA7B54810C548AC112ECFC76CE753AF07A625248C05827C843371AB5DC6C6C8D5D457E845B4B67FB4CEFF06720EA7A9112BFD0A96BC7E97D49FB9BBD59155D109433F0C4823DEA7A13E5281C00E4945F5B05D7518CE085EC8BFE738122"); break; //ZRI.04.38008036 + case 231 : return F("b1B44A5110301808238379BA77241022436931581038A88000002A7184A0AD900E327803A"); break; //ELS.03.36240241"); break; //DME.37.82800103 + case 232 : return F("b3E44B405399098502E0439DC7A0F70300530C7144B5962760A55DE8BA4E49C37676B0CD702698B5FBCE59E35E33D33F736AE13FB1C31DD43ADDC3FE7FF8E1EDC01D749974884BBF96580FE"); break; //AMT.04.50989039 + case 233 : return F("b5B4479169014216130377E5B8C2034900F002C256448000029E1A134984E32773DF97289000047791611023400302CBD0710FF4380B4AE49A140E94F319BE97049FDA8DDC96A8DC437F3BFB02ADC86082E9507934C7ED4FC6F4F678D613F25C09A1DDE927D817F5A824A8012"); break; //ESY.02.47000089"); break; //ESY.37.61211490 + case 234 : return F("b7B445A1415200000023724958C20A0900F002C25B4F60800C0E1D417100AD1BB6EDC72324371005A140102A00050F9B10710112A08DB9869998DE2C0D8614F76213F8682D10A8EF2951413C839461E8E3139EA62193E02B1584E6EC8EDB082AB70C6504F1ADF9E6ABD270E96FE8745AEB93C454FC3C9EAA2D5FCD6679E8A38A3E0818D4B6652993CEE5F8E514867801D"); break; //EBZ.02.00714332"); break; //EBZ.37.00002015 + case 235 : return F("b49449344100549253508227F780DFF5F3500824600007E0007B06EFF2BE9FF000000007F2C000000009E24000000DAC000008000800080008000800080000000E9F10000000000000000002F046D010E8625249880E6"); break; //QDS.08.25490510 + case 236 : return F("b9644A732370335700A045D187A440000200274DD02046D071189260C33FD0638010000446D3B177F2C4C06000000DFDD0084016D3B179F258C010641000000CC0DBA0106000000008C020600000000CC0206B190000000008C030600000000CC030600008B9400008C040600000000CC04060000000099348C050600000000CC0506000000008C0673DA0600000000CC0606000000008C070600E0F80000003C22000000000F0010004FF980E0"); break; //LUG.04.70350337 + case 237 : return F("b4806AC1956030015020363917256030015AC190203D8000000466D00AC9200118625000D78113131363533303030619C3531343530303030308940FD1A014C933B263A494700004C130546000001FD67030D9A8057"); break; //FML.03.15000356"); break; //FML.03.15000356 + case 238 : return F("b9E44A815242292070102881F7F4B0098050B0E989C939C3479F8904137236FE582B853DCACDD8DB48A717A6B42935CB977102079E6397B07AAD2A648E5B65E44D97F9A020B2BFAE433FA37FCB2A2C32711A5986B301D2F6E4A424C1144D808CE9D592C316B117C572689AC6C1322CC05E81F590CAAF457390F6B39DACC946FA314F8E8A34268157AC4338781C3EF5807F9221394DD1FAB5165E1261614B8B85758851295334DF52D9A4DCE2E1E17A555A21007D2DA802B"); break; //EMH.02.07922224 + case 239 : return F("b2E44B05C99010100021B65BE7AC30000002F2F0A6605020AFB1A33041AA002FD971D00002F2F2F2F2F2F2F2F2F2FDF772F2F2F2F2F25EE8008"); break; //WEP.1B.00010199 + case 240 : return F("b2E44B05C75000000041B8EF87A510000002F2F0A6661010AFB1A8905449802FD971D00002F2F2F2F2F2F2F2F2F2FDF772F2F2F2F2F25EE80F8"); break; //WEP.1B.00000075 + case 241 : return F("b1E44C418EA76010001034ECB7A820010A5597AB1CCF2014088590E64BCAB21DC1CC068DAA08035"); break; //FFD.03.000176EA + case 242 : return F("b4E442423245450514A07545F7AC6004005BDB5EDF3750DF41725EE867C3E39750E20B9F5FB092089B6A5DC7AA586101778BCD5EAD4995B102AC639F0FB4D12403EFE3554F72CAE4F5D348CC374F571CCE4A98634318027AAF3E7DD8600"); break; //HYD.07.51505424 + case 243 : return F("b3C449344454392352337ABFC727972126793442304FF0000200C050321648206004C0530240500426CBF2CCC0805098875630600C2086CDF23326CFFFF046D177FEA11D3242E1F830C"); break; //QDS.04.67127279"); break; //QDS.37.35924345 + case 244 : return F("bY5E449726900634160004728499181897A60030D30040A5D20022C396367C6A868B05FE2E9F70B6878070839C62E69DDC79CB409EBCAD68E950A6958B9B92FE6D1B700065B8BD52CB3035B93653FFDA3C3EF3EF6BBB1B450C7976874216AD188011"); break; //ITW.04.16340690"); break; //ITW.30.18189984 + case 245 : return F("b3444A73228188269040439327A370000202F2F0974040970080C06348AF03500000C14040301000B2D6207000B3BDD4A2322000A5A05090A5E0506F29780E8"); break; //LUG.04.69821828 + case 246 : return F("b9644A732170135700A0476A67A2F00002002747501046D3315952B0C3CF20637000000446D3B177F2C4C06000000F4640084016D3B179F2A8C010636000000CCB85A0106330000008C020633000000CC02067E6A330000008C030633000000CC030600009A1900008C040600000000CC04060000000099348C050600000000CC0506000000008C0673DA0600000000CC0606000000008C070600E0F80000003C22890200000F001000C94A80E8"); break; //LUG.04.70350117 + case 247 : return F("b5E44496A044550108804533A7A19005005B14A145C0EEC7C6FC18A85582753BE2BD08B6F0F9320809C5F28436A3388BB09145DF69DA51ED00DCCFE7BA2D6F86936D17E654489390BB7511CC4DA27A576A9612B1243151C634899A74D3EBDB2CA578CA4444F5740D74D3EC766628013"); break; //ZRI.04.10504504 + case 248 : return F("bD6440186669601001637060C7215067071010660047400C0251CE72725D9364386C4BF8A15AF4C722115F38B66DDE9F4B800D05A14F870E74492B8F57CB2282388B864D71416FDE0A74CA84735B59D1806BBBF8483B6B14AB580F0D5257E8CC06889EBA80AFB032BCEECD849E7E6A4639B5649B0BF5CBB57BF0993AE16EF7D88D784ADD9CE09284880D14D1502967798901C9E24ED6A04A3B26AE580B5D2AEC46804DEC1CBEDCB1E0B3FE1F84679620FD59E297FE8E67374EE9C34CA5A7F45B4B3709C6BD4E0B546F706DDC571C6469CFA90602C0E339A054CDE1D0A0E071651FD49FEBD485D8E561BB36AF7BED2741D35BBC88051"); break; //APA.04.71700615"); break; //APA.37.00019666 + case 249 : return F("bA944C514381355920004A5FC7A3D0090257403AC17355186FF933BAF0D0820041595970BD168489A746DF337AB01D1DBBA3EF7D486D74CCCEE777E57BD12A15D22B89AB4B4266D84B9C5C1DDBC437CD64807AF0B2B232E3A3C1B4DA568C9F8BA39FDF4565C170ACEF1918655B45D82E8BAAD769B88FA917E82542CAD596AB2145433599FFDDD3FCE889F657A8AC07FE5CED3F184244475B59710CFDCA44D7C3AE00CC021FA6EE3F49B77939529B49279FC526803FD0C05010002FD0B01111E248025"); break; //EFE.04.92551338 + case 250 : return F("b6E44A511485268614004B97F7A86006005B8D6D972C8E06EFB5AEC0FC750DB9DFFE62BB59DA183A5709DFC5029CEB2E44FEAF475DD2F3CF6D90544F29372A5C027DACA08536B2457CDEDEB76E36B281F4A0FD4A203FE9F7DFD337BDEA15D02F53A21CB281A3EF01E15D779B3018A99D0E3EF2FF946A63F419E56644BBDF8FB8010"); break; //DME.04.61685248 + case 251 : return F("b3E44A51159351969A004761E7A030030050F0020FC830AABCA7CEE4EC8E44524D98802ED5CA2EF3C89774FF800730951E02569BF91C156331EC9C29730FE2F437128D7E574D29C4E808047"); break; //DME.04.69193559 + case 252 : return F("b7144685009022004593720EB8C00EE900F002C252CA40100F5CDA57367A4436D244E72791670692423FE040600407D7C07108660CA27A4DD53871B12F96FFEF0F84E0CA88A2201BD794859D35CCD051F34327D04EAAE8772675F8E7F28DE2E515FBC9AF3A40E72C078304158D453511F2CF39DBF98DBE1EAD84F0F2CAD975EDDCC4F"); break; //HYD.04.69701679"); break; //TCH.37.04200209 + case 253 : return F("bA944C514381355920004A5FC7A670090251913406458376E6F9CEB817ABA85D9F26E5867EBA222FDFF496F2C8A312E593C80C0604576D7B5269B4A25BD15B314C0598FE3395611400AA7ADB705EE2120686208A449BDC5EE828FF048F669516585611337839C0A40A308F4DE619997F29D2438CE9EC263574FC60D42197CFA7B47528574792BFFF54AFF9193CA678242653A97DEBCB5E5333687846A875A69C4B7371C762C03A2DB5AB738D92623907896AEBF03FD0C05010002FD0B0111C6E48020"); break; //EFE.04.92551338 + case 254 : return F("b5E44C51475051512010425417A1C004025EEA5F744D756D853BB606F5DBB33DD7B26896D7370BD9C50990E605CE03F8F16DEE8AA36EF2C87E3902F933DDD87E1D383B7C647E8D2236E5FF5E1BE6742D65511FE1629B5CD3F6F0F800C000109002F680300035E2001020A0901C98008"); break; //EFE.04.12150575 + case 255 : return F("b5344A5115158536941043C9D8C00CC900F002C25986B0110B13D74311F4D1ACF88797A7E7031071066DEA0D815DFAC31E07763666D4C46C2DEA856FCEC34F2B5CCF3750F72BD8F924544E73C51D7DBC11A1AE31049E5D0E121CE0053AEB8E018805B"); break; //DME.04.69535851 + case 256 : return F("bA944C514381355920004A5FC7A79009025E37121BAC0ACF1A216281AE7A85DA20DF90613A83417418BD9BA7B0F09C192713A65DECD3ECB2DFC5299683A5B2669EE70851493FC9944D7579F7FCD44CCC839E432D498E40EFDF1614BD9B7E6F1B4AD96CC16939C9D061434790765D3883275B80DE0508393A0F00C1B1590A66F94D920BD5E80125C610F7C0B0346D8D0CFE8DD4E8A14370077D90A7BCFA59345DB13D7134E80D5F2F1D50B3B043075126DF42C2A03FD0C05010002FD0B0111ABC78028"); break; //EFE.04.92551338 + case 257 : return F("b4E44EE4D557648251B048F777ABE004025D84601014EEA5EDCC08B007BC8BB1EBB266BADDEC127897EDA4296EEB8EB05A57DFCA5190F54B41C05B1BF0F5EF82B719CD27EA6242263DFB94F589F38843A1146916B872141977275BD8035"); break; //SON.04.25487655 + case 258 : return F("b2644AC488137000050371BF47201813700AC485000D90000002F2F0C32C60E569405002F2F2F2F2F2F2F2FB90E8059"); break; //REL.00.00378101"); break; //REL.37.00003781 + case 259 : return F("b2644AC488137000050371BF47202813700AC48500CE20000002F2F0C068D16635640002F2F2F2F2F2F2F2F72188058"); break; //REL.0C.00378102"); break; //REL.37.00003781 + case 260 : return F("b36446850790133512243C21BA1009F29793C0088230200007E0E4FFD476370544E2D33280000000000000000000CC8A2608214298C81CD45160105964E203B372E"); break; //TCH.43.51330179 + case 261 : return F("b3744685029069372274351B5A2129F27995400305C1200000007400B69792D4C914447BB7D4A2AC86CB37BFAFD59EEC7971B1F070D00000000002C0040004FD085CC"); break; //TCH.43.72930629 + case 262 : return F("b37446850715221622843FE7BA2129F29F827008800000080800D0000DE060000E00739EF48B3861639BC2044115A10CB0C6283102F1830C0080AD861D24C1E8F80E1"); break; //TCH.43.62215271 + case 263 : return F("b374468508475676739430ECDA2109F27793500287F15000000D34D95907D550ADB32A24C9AAAA9922ED6A76075D51C1F7937DB8D4B95F20E4C63EDC5997041E280FA"); break; //TCH.43.67677584 + case 264 : return F("b36446850442620514543A443A1009F279E180038CC0400803680000286DB08F0C00727FCF0C82F92E8128D31165D722873C60C1568910103062C700107EF3F912E"); break; //TCH.43.51202644 + case 265 : return F("b3744685060478860574395F9A20D9F29820500A00000000100060000E34A00000000000000000000000000000000FFFF0000000000000000000000000000FFFF80D4"); break; //TCH.43.60884760 + case 266 : return F("b3644685005012040714351C0A0009F298F560290E10101A40000FFF719F04E99E7B2F9E2E512000000000000000042B000000000000000000000000000FFFF95A6"); break; //TCH.43.40200105 + case 267 : return F("b364468504200545145444FE0A1009F29CA6900B0F00D008023001A68BB4748D28D4F7685755095534E09E187ADE5A7B903D03027041000000100008002CF41D570"); break; //TCH.44.51540042 + case 268 : return F("b37446850341929625744B280A20D9F27C9350058395A00000169848B3EDF2ECEF8A17F008A875B80FCB947E0914351334E69E4A14D72A8E296375279E286AB9B8008"); break; //TCH.44.62291934 + case 269 : return F("b36446850060670527144C1AAA0009F29C41F0190DE0F0081A5A1BF2595D38ABA9F512E89A3471D67F8F0C6206308383B6288298BD0A3D68424A228A9907F8B34B2"); break; //TCH.44.52700606 + case 270 : return F("bY5044972608062002001A7AC90340A517F5A8F83D78281AEFF06FDBDEDEF842336FA663F292D6EEACB0F54CA02FB47C8F587862B352ED30166FEE61753998230C38444F845C9FCC364D3C614095F92D14A28010"); break; //ITW.1A.02200608 + case 271 : return F("bY5E449726900634160004728499181897A60030BA0040A53FB81E378B33F9E23FEDF6A0B1B381F4972C2842041CC7EC8D74D675CE56222039CE82385073750F2B695CBEC9B0E8A48EC3F09B74C26E4194A06B7974203DDD0C7976874216E0D282DE"); break; //ITW.04.16340690"); break; //ITW.30.18189984 + case 272 : return F("bY50449726141331160007728499181897A600307E0030A5500A4109842EF76DD4A2DEDF6722CCB4D0746C8505086D91ED34B41AFD24FED0111715A21E549191B1529EE2AE8229E50E7900000000000070208AD8"); break; //ITW.07.16311314"); break; //ITW.30.18189984 + case 273 : return F("bY5044972625353893071A7A0B0040A5B12EC2E009C467CAD2AF0A38712684FE764C6181D2969A7F0F7B076CB9719FEB21C32E48161EEA5A1E6E92F354FED894994C368F17E6F68E2E6AA1D7C289E3FD7A908015"); break; //ITW.1A.93383525 + case 274 : return F("b2E449726606670194107A6AB8CB0D47ABF0000A00413E84B070004FD3442178001C00004933C00000000333B00000A2D00325A0000CD2E801C"); break; //ITW.07.19706660 + case 275 : return F("bY50449726141331160007728499181897A600307E0030A5500A4109842EF76DD4A2DEDF6722CCB4D0746C8505086D91ED34B41AFD24FED0111715A21E549191B1529EE2AE8229E50E7900000000000070208AD8"); break; //ITW.07.16311314"); break; //ITW.30.18189984 + + } + // *INDENT-ON* + count = 0; + return F(""); +} + +# endif // if P094_DEBUG_OPTIONS + +bool P094_data_struct::loop() { + if (!isInitialized()) { + return false; + } + bool fullSentenceReceived = false; + + if (easySerial != nullptr) { + int available = easySerial->available(); + + unsigned long timeout = millis() + 10; + + while (available > 0 && !fullSentenceReceived) { + // Look for end marker + char c = easySerial->read(); + --available; + + if (available == 0) { + if (!timeOutReached(timeout)) { + available = easySerial->available(); + } + delay(0); + } + + switch (c) { + case 13: + { + const size_t length = sentence_part.length(); + bool valid = length > 0; + + for (size_t i = 0; i < length && valid; ++i) { + if ((sentence_part[i] > 127) || (sentence_part[i] < 32)) { + free_string(sentence_part); + ++sentences_received_error; + valid = false; + } + } + + if (valid) { + fullSentenceReceived = true; + } + break; + } + case 10: + + // Ignore LF + break; + default: + + if ((c >= 32) && (c < 127)) { + sentence_part += c; + } else { + current_sentence_errored = true; + } + break; + } + + if (max_length_reached()) { fullSentenceReceived = true; } + } + } + + if (fullSentenceReceived) { + ++sentences_received; + length_last_received = sentence_part.length(); + } +# if P094_DEBUG_OPTIONS + else { + if (debug_generate_CUL_data && (sentence_part.length() == 0)) { + static uint32_t last_test_sentence = 0; + static int count = 0; + + if (timePassedSince(last_test_sentence) > 1000) { + count++; + + // sentence_part = F("b2644AC48585300005037FAB97201585300AC485003150000202F2F0C0AF314213993002F2F2F2F2F2F2F2FAFCA8046"); + sentence_part = getDebugSentences(count); + fullSentenceReceived = true; + last_test_sentence = millis(); + } + } + } +# endif // if P094_DEBUG_OPTIONS + return fullSentenceReceived; +} + +const String& P094_data_struct::peekSentence() const { + return sentence_part; +} + +void P094_data_struct::getSentence(String& string, bool appendSysTime) { + string = std::move(sentence_part); + + if (appendSysTime) { + // Unix timestamp = 10 decimals + separator + if (string.reserve(sentence_part.length() + 11)) { + string += ';'; + string += node_time.getUnixTime(); + } + } + sentence_part.reserve(string.length()); +} + +void P094_data_struct::getSentencesReceived(uint32_t& succes, uint32_t& error, uint32_t& length_last) const { + succes = sentences_received; + error = sentences_received_error; + length_last = length_last_received; +} + +void P094_data_struct::setMaxLength(uint16_t maxlenght) { + max_length = maxlenght; +} + +uint32_t P094_data_struct::getFilterOffWindowTime() const { + return filterOffWindowTime; +} + +void P094_data_struct::setDisableFilterWindowTimer() { + if (getFilterOffWindowTime() == 0) { + disable_filter_window = 0; + } + else { + disable_filter_window = millis() + getFilterOffWindowTime(); + } +} + +bool P094_data_struct::disableFilterWindowActive() const { + if (disable_filter_window != 0) { + if (!timeOutReached(disable_filter_window)) { + // We're still in the window where filtering is disabled + return true; + } + } + return false; +} + +bool P094_data_struct::parsePacket(const String& received, mBusPacket_t& packet) { + const size_t strlength = received.length(); + + if (strlength == 0) { + return false; + } + + const char firstChar = received[0]; + + if ((firstChar == 'b')) { + // Received a data packet in CUL format. + if (strlength < 21) { + return false; + } + + // Decoded packet + if (!packet.parse(received)) { return false; } + + const mBusPacket_header_t *header = packet.getDeviceHeader(); + + if (header == nullptr) { + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLogMove(LOG_LEVEL_INFO, concat(F("CUL Filter: NO Header "), packet.toString())); + } + + return false; + } + + if (mute_messages) { + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLogMove(LOG_LEVEL_INFO, concat(F("CUL Filter: Muted "), packet.toString())); + } + + return false; // Mute all messages + } + + if (!interval_filter.enabled || (_filters.size() == 0)) { + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLogMove(LOG_LEVEL_INFO, concat(F("CUL Filter: NO Filter "), packet.toString())); + } + + return true; // No filtering + } + + for (unsigned int f = 0; f < _filters.size(); ++f) { + if (_filters[f].matches(*header)) { + const bool res = interval_filter.filter(packet, _filters[f]); + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLogMove(LOG_LEVEL_INFO, concat(F("CUL Filter: Match "), _filters[f].toString())); + addLogMove(LOG_LEVEL_INFO, concat(res ? F("CUL Filter: Pass ") : F("CUL Filter: Reject "), header->toString())); + } + + return res; + } + } + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLogMove(LOG_LEVEL_INFO, concat(F("CUL Filter: NO Match "), header->toString())); + } + + // No matching filter, so consider fall-through filter to be: + // *.*.*;none + return false; + } else { + switch (firstChar) { + case 'C': // CMODE + case 'S': // SMODE + case 'T': // TMODE + case 'O': // OFF + case 'V': // Version info + + // FIXME TD-er: Must test the result of the other possible answers. + return true; + } + } + + return false; +} + +void P094_data_struct::interval_filter_purgeExpired() { + interval_filter.purgeExpired(); +} + +void P094_data_struct::html_show_interval_filter_stats() const +{ + if (interval_filter._mBusFilterMap.empty()) { return; } + + addRowLabel(F("Interval Filter Entries")); + addHtmlInt(interval_filter._mBusFilterMap.size()); + + addFormNote(F("Non expired W-MBus device filters")); +} + +bool P094_data_struct::collect_stats_add(const mBusPacket_t& packet, const String& source) { + if (collect_stats) { + return mBus_stats[firstStatsIndexActive ? 0 : 1].add(packet, source); + } + return false; +} + +void P094_data_struct::prepare_dump_stats() { + firstStatsIndexActive = !firstStatsIndexActive; +} + +bool P094_data_struct::dump_next_stats(String& str) { + const uint8_t dumpStatsIndex = firstStatsIndexActive ? 1 : 0; + + if (mBus_stats[dumpStatsIndex]._mBusStatsMap.empty()) { return false; } + + str = concat(F("stats;"), mBus_stats[dumpStatsIndex].getFront()); + + return true; +} + +void P094_data_struct::html_show_mBus_stats() const +{ + const uint8_t dumpStatsIndex = firstStatsIndexActive ? 0 : 1; + + if (mBus_stats[dumpStatsIndex]._mBusStatsMap.empty()) { return; } + + addRowLabel(F("W-MBus Devices")); + addHtmlInt(mBus_stats[dumpStatsIndex]._mBusStatsMap.size()); + + addFormNote(F("Devices received since last culreader,dumpstats")); + + mBus_stats[dumpStatsIndex].toHtml(); +} + +bool P094_data_struct::max_length_reached() const { + if (max_length == 0) { return false; } + return sentence_part.length() >= max_length; +} + +bool P094_data_struct::isDuplicate(const P094_filter& other) const +{ + const String f_str = other.toString(); + + for (auto it = _filters.begin(); it != _filters.end(); ++it) { + if (f_str.equals(it->toString())) { + return true; + } + } + return false; +} + +# if P094_DEBUG_OPTIONS +uint32_t P094_data_struct::getDebugCounter() { + return debug_counter++; +} + +# endif // if P094_DEBUG_OPTIONS + +#endif // USES_P094 diff --git a/src/src/PluginStructs/P094_data_struct.h b/src/src/PluginStructs/P094_data_struct.h index 872038631a..ff02bbff69 100644 --- a/src/src/PluginStructs/P094_data_struct.h +++ b/src/src/PluginStructs/P094_data_struct.h @@ -1,194 +1,193 @@ -#ifndef PLUGINSTRUCTS_P094_DATA_STRUCT_H -#define PLUGINSTRUCTS_P094_DATA_STRUCT_H - -#include "../../_Plugin_Helper.h" -#ifdef USES_P094 - -# include "../Helpers/CUL_interval_filter.h" -# include "../Helpers/CUL_stats.h" - -# include "../PluginStructs/P094_Filter.h" - -# include -# include - -# ifndef P094_DEBUG_OPTIONS -# define P094_DEBUG_OPTIONS 0 -# endif // ifndef P094_DEBUG_OPTIONS - - -# define P094_BAUDRATE PCONFIG_LONG(0) -# define P094_BAUDRATE_LABEL PCONFIG_LABEL(0) - -# define P094_DEBUG_SENTENCE_LENGTH PCONFIG_LONG(1) -# define P094_DEBUG_SENTENCE_LABEL PCONFIG_LABEL(1) - -# define P094_DISABLE_WINDOW_TIME_MS PCONFIG_LONG(2) - -# define P094_GET_APPEND_RECEIVE_SYSTIME bitRead(PCONFIG(0), 0) -# define P094_SET_APPEND_RECEIVE_SYSTIME(X) bitWrite(PCONFIG(0), 0, X) - -# if P094_DEBUG_OPTIONS -# define P094_GET_GENERATE_DEBUG_CUL_DATA bitRead(PCONFIG(0), 1) -# define P094_SET_GENERATE_DEBUG_CUL_DATA(X) bitWrite(PCONFIG(0), 1, X) -# endif // if P094_DEBUG_OPTIONS - -# define P094_GET_INTERVAL_FILTER bitRead(PCONFIG(0), 2) -# define P094_SET_INTERVAL_FILTER(X) bitWrite(PCONFIG(0), 2, X) - -# define P094_GET_COLLECT_STATS bitRead(PCONFIG(0), 3) -# define P094_SET_COLLECT_STATS(X) bitWrite(PCONFIG(0), 3, X) - -# define P094_GET_MUTE_MESSAGES bitRead(PCONFIG(0), 4) -# define P094_SET_MUTE_MESSAGES(X) bitWrite(PCONFIG(0), 4, X) - -# define P094_NR_FILTERS PCONFIG(1) - -# ifdef ESP8266 -# define P094_MAX_NR_FILTERS 25 -# endif // ifdef ESP8266 -# ifdef ESP32 -# define P094_MAX_NR_FILTERS 100 -# endif // ifdef ESP32 - - -# ifdef ESP8266 -# define P094_MAX_MSG_LENGTH 550 -# endif // ifdef ESP8266 -# ifdef ESP32 -# define P094_MAX_MSG_LENGTH 1024 -# endif // ifdef ESP32 - - -# define P094_DEFAULT_BAUDRATE 38400 - - -struct P094_data_struct : public PluginTaskData_base { -public: - - P094_data_struct(); - - virtual ~P094_data_struct(); - - void reset(); - - bool init(ESPEasySerialPort port, - const int16_t serial_rx, - const int16_t serial_tx, - unsigned long baudrate); - - void setFlags(unsigned long filterOffWindowTime_ms, - bool intervalFilterEnabled, - bool mute, - bool collectStats); - - - void loadFilters(struct EventStruct *event, - uint8_t nrFilters); - - String saveFilters(struct EventStruct *event) const; - - - void clearFilters(); - - bool addFilter(struct EventStruct *event, const String& filter); - - String getFiltersMD5() const; - - void WebformLoadFilters(uint8_t nrFilters) const; - - void WebformSaveFilters(struct EventStruct *event, - uint8_t nrFilters); - - bool isInitialized() const; - - void sendString(const String& data); - - bool loop(); - - const String& peekSentence() const; - - void getSentence(String& string, - bool appendSysTime); - - void getSentencesReceived(uint32_t& succes, - uint32_t& error, - uint32_t& length_last) const; - - void setMaxLength(uint16_t maxlenght); - - void setLine(uint8_t varNr, - const String& line); - - uint32_t getFilterOffWindowTime() const; - - bool filterUsed(uint8_t lineNr) const; - - void setDisableFilterWindowTimer(); - - bool disableFilterWindowActive() const; - - bool parsePacket(const String& received, - mBusPacket_t& packet); - - -# if P094_DEBUG_OPTIONS - - // Get (and increment) debug counter - uint32_t getDebugCounter(); - - void setGenerate_DebugCulData(bool value) { - debug_generate_CUL_data = value; - } - -# endif // if P094_DEBUG_OPTIONS - - void interval_filter_purgeExpired(); - - void html_show_interval_filter_stats() const; - - - bool collect_stats_add(const mBusPacket_t& packet, const String& source); - void prepare_dump_stats(); - bool dump_next_stats(String& str); - - void html_show_mBus_stats() const; - -private: - - bool max_length_reached() const; - - bool isDuplicate(const P094_filter& other) const; - - std::vector_filters; - - ESPeasySerial *easySerial = nullptr; - String sentence_part; - uint16_t max_length = P094_MAX_MSG_LENGTH; - uint16_t nrFilters{}; - unsigned long filterOffWindowTime = 0; - uint32_t sentences_received = 0; - uint32_t sentences_received_error = 0; - bool current_sentence_errored = false; - uint32_t length_last_received = 0; - unsigned long disable_filter_window = 0; - - # if P094_DEBUG_OPTIONS - uint32_t debug_counter = 0; - bool debug_generate_CUL_data = false; - # endif // if P094_DEBUG_OPTIONS - bool collect_stats = false; - bool mute_messages = false; - - bool firstStatsIndexActive = false; - - CUL_interval_filter interval_filter; - - // Alternating stats, one being flushed, the other used to collect new stats - CUL_Stats mBus_stats[2]; -}; - - -#endif // USES_P094 - -#endif // PLUGINSTRUCTS_P094_DATA_STRUCT_H +#ifndef PLUGINSTRUCTS_P094_DATA_STRUCT_H +#define PLUGINSTRUCTS_P094_DATA_STRUCT_H + +#include "../../_Plugin_Helper.h" +#ifdef USES_P094 + +# include "../Helpers/CUL_interval_filter.h" +# include "../Helpers/CUL_stats.h" + +# include "../PluginStructs/P094_Filter.h" + +# include +# include + +# ifndef P094_DEBUG_OPTIONS +# define P094_DEBUG_OPTIONS 0 +# endif // ifndef P094_DEBUG_OPTIONS + + +# define P094_BAUDRATE PCONFIG_LONG(0) +# define P094_BAUDRATE_LABEL PCONFIG_LABEL(0) + +# define P094_DEBUG_SENTENCE_LENGTH PCONFIG_LONG(1) +# define P094_DEBUG_SENTENCE_LABEL PCONFIG_LABEL(1) + +# define P094_DISABLE_WINDOW_TIME_MS PCONFIG_LONG(2) + +# define P094_GET_APPEND_RECEIVE_SYSTIME bitRead(PCONFIG(0), 0) +# define P094_SET_APPEND_RECEIVE_SYSTIME(X) bitWrite(PCONFIG(0), 0, X) + +# if P094_DEBUG_OPTIONS +# define P094_GET_GENERATE_DEBUG_CUL_DATA bitRead(PCONFIG(0), 1) +# define P094_SET_GENERATE_DEBUG_CUL_DATA(X) bitWrite(PCONFIG(0), 1, X) +# endif // if P094_DEBUG_OPTIONS + +# define P094_GET_INTERVAL_FILTER bitRead(PCONFIG(0), 2) +# define P094_SET_INTERVAL_FILTER(X) bitWrite(PCONFIG(0), 2, X) + +# define P094_GET_COLLECT_STATS bitRead(PCONFIG(0), 3) +# define P094_SET_COLLECT_STATS(X) bitWrite(PCONFIG(0), 3, X) + +# define P094_GET_MUTE_MESSAGES bitRead(PCONFIG(0), 4) +# define P094_SET_MUTE_MESSAGES(X) bitWrite(PCONFIG(0), 4, X) + +# define P094_NR_FILTERS PCONFIG(1) + +# ifdef ESP8266 +# define P094_MAX_NR_FILTERS 25 +# endif // ifdef ESP8266 +# ifdef ESP32 +# define P094_MAX_NR_FILTERS 100 +# endif // ifdef ESP32 + + +# ifdef ESP8266 +# define P094_MAX_MSG_LENGTH 550 +# endif // ifdef ESP8266 +# ifdef ESP32 +# define P094_MAX_MSG_LENGTH 1024 +# endif // ifdef ESP32 + + +# define P094_DEFAULT_BAUDRATE 38400 + + +struct P094_data_struct : public PluginTaskData_base { +public: + + P094_data_struct(); + + virtual ~P094_data_struct(); + + void reset(); + + bool init(ESPEasySerialPort port, + const int16_t serial_rx, + const int16_t serial_tx, + unsigned long baudrate); + + void setFlags(unsigned long filterOffWindowTime_ms, + bool intervalFilterEnabled, + bool mute, + bool collectStats); + + + void loadFilters(struct EventStruct *event, + uint8_t nrFilters); + + String saveFilters(struct EventStruct *event) const; + + + void clearFilters(); + + bool addFilter(struct EventStruct *event, const String& filter); + + String getFiltersMD5() const; + + void WebformLoadFilters(uint8_t nrFilters) const; + + void WebformSaveFilters(struct EventStruct *event, + uint8_t nrFilters); + + bool isInitialized() const; + + void sendString(const String& data); + + bool loop(); + + const String& peekSentence() const; + + void getSentence(String& string, + bool appendSysTime); + + void getSentencesReceived(uint32_t& succes, + uint32_t& error, + uint32_t& length_last) const; + + void setMaxLength(uint16_t maxlenght); + + void setLine(uint8_t varNr, + const String& line); + + uint32_t getFilterOffWindowTime() const; + + bool filterUsed(uint8_t lineNr) const; + + void setDisableFilterWindowTimer(); + + bool disableFilterWindowActive() const; + + bool parsePacket(const String& received, + mBusPacket_t& packet); + +# if P094_DEBUG_OPTIONS + + // Get (and increment) debug counter + uint32_t getDebugCounter(); + + void setGenerate_DebugCulData(bool value) { + debug_generate_CUL_data = value; + } + +# endif // if P094_DEBUG_OPTIONS + + void interval_filter_purgeExpired(); + + void html_show_interval_filter_stats() const; + + + bool collect_stats_add(const mBusPacket_t& packet, const String& source); + void prepare_dump_stats(); + bool dump_next_stats(String& str); + + void html_show_mBus_stats() const; + +private: + + bool max_length_reached() const; + + bool isDuplicate(const P094_filter& other) const; + + std::vector_filters; + + ESPeasySerial *easySerial = nullptr; + String sentence_part; + uint16_t max_length = P094_MAX_MSG_LENGTH; + uint16_t nrFilters{}; + unsigned long filterOffWindowTime = 0; + uint32_t sentences_received = 0; + uint32_t sentences_received_error = 0; + bool current_sentence_errored = false; + uint32_t length_last_received = 0; + unsigned long disable_filter_window = 0; + + # if P094_DEBUG_OPTIONS + uint32_t debug_counter = 0; + bool debug_generate_CUL_data = false; + # endif // if P094_DEBUG_OPTIONS + bool collect_stats = false; + bool mute_messages = false; + + bool firstStatsIndexActive = false; + + CUL_interval_filter interval_filter; + + // Alternating stats, one being flushed, the other used to collect new stats + CUL_Stats mBus_stats[2]; +}; + + +#endif // USES_P094 + +#endif // PLUGINSTRUCTS_P094_DATA_STRUCT_H diff --git a/src/src/WebServer/AdvancedConfigPage.cpp b/src/src/WebServer/AdvancedConfigPage.cpp index 4e22f58526..0d1374075b 100644 --- a/src/src/WebServer/AdvancedConfigPage.cpp +++ b/src/src/WebServer/AdvancedConfigPage.cpp @@ -1,539 +1,555 @@ -#include "../WebServer/AdvancedConfigPage.h" - -#ifdef WEBSERVER_ADVANCED - -#include "../WebServer/HTML_wrappers.h" -#include "../WebServer/Markup.h" -#include "../WebServer/Markup_Buttons.h" -#include "../WebServer/Markup_Forms.h" -#include "../WebServer/ESPEasy_WebServer.h" - -#include "../ESPEasyCore/ESPEasyWifi.h" - -#include "../Globals/ESPEasy_time.h" -#include "../Globals/Settings.h" -#include "../Globals/TimeZone.h" - -#include "../Helpers/_Plugin_Helper_serial.h" -#include "../Helpers/ESPEasy_Storage.h" -#include "../Helpers/ESPEasy_time.h" -#include "../Helpers/Hardware_defines.h" -#include "../Helpers/StringConverter.h" - -#if FEATURE_I2C_MULTIPLE -#include "../Helpers/I2C_access.h" -#endif - -void setLogLevelFor(uint8_t destination, LabelType::Enum label) { - setLogLevelFor(destination, getFormItemInt(getInternalLabel(label))); -} - -// ******************************************************************************** -// Web Interface config page -// ******************************************************************************** -void handle_advanced() { - #ifndef BUILD_NO_RAM_TRACKER - checkRAM(F("handle_advanced")); - #endif - - if (!isLoggedIn()) { return; } - navMenuIndex = MENU_INDEX_TOOLS; - TXBuffer.startStream(); - sendHeadandTail_stdtemplate(_HEAD); - - if (!webArg(F("edit")).isEmpty()) - { -// Settings.MessageDelay_unused = getFormItemInt(F("messagedelay")); - Settings.IP_Octet = webArg(F("ip")).toInt(); - strncpy_webserver_arg(Settings.NTPHost, F("ntphost")); - Settings.TimeZone = getFormItemInt(F("timezone")); - TimeChangeRule dst_start(getFormItemInt(F("dststartweek")), getFormItemInt(F("dststartdow")), getFormItemInt(F("dststartmonth")), getFormItemInt(F("dststarthour")), Settings.TimeZone); - - if (dst_start.isValid()) { Settings.DST_Start = dst_start.toFlashStoredValue(); } - TimeChangeRule dst_end(getFormItemInt(F("dstendweek")), getFormItemInt(F("dstenddow")), getFormItemInt(F("dstendmonth")), getFormItemInt(F("dstendhour")), Settings.TimeZone); - - if (dst_end.isValid()) { Settings.DST_End = dst_end.toFlashStoredValue(); } - webArg2ip(F("syslogip"), Settings.Syslog_IP); - Settings.WebserverPort = getFormItemInt(F("webport")); - Settings.UDPPort = getFormItemInt(F("udpport")); - - Settings.SyslogFacility = getFormItemInt(F("syslogfacility")); - Settings.SyslogPort = getFormItemInt(F("syslogport")); - Settings.UseSerial = isFormItemChecked(LabelType::ENABLE_SERIAL_PORT_CONSOLE); - -#if FEATURE_DEFINE_SERIAL_CONSOLE_PORT - Settings.console_serial_rxpin = getFormItemInt(F("taskdevicepin1"), Settings.console_serial_rxpin); - Settings.console_serial_txpin = getFormItemInt(F("taskdevicepin2"), Settings.console_serial_txpin); - - serialHelper_webformSave( - Settings.console_serial_port, - Settings.console_serial_rxpin, - Settings.console_serial_txpin); -#if USES_ESPEASY_CONSOLE_FALLBACK_PORT - Settings.console_serial0_fallback = isFormItemChecked(LabelType::CONSOLE_FALLBACK_TO_SERIAL0); -#endif - -#endif - setLogLevelFor(LOG_TO_SYSLOG, LabelType::SYSLOG_LOG_LEVEL); - setLogLevelFor(LOG_TO_SERIAL, LabelType::SERIAL_LOG_LEVEL); - setLogLevelFor(LOG_TO_WEBLOG, LabelType::WEB_LOG_LEVEL); -#if FEATURE_SD - setLogLevelFor(LOG_TO_SDCARD, LabelType::SD_LOG_LEVEL); -#endif // if FEATURE_SD - Settings.UseValueLogger = isFormItemChecked(F("valuelogger")); - Settings.BaudRate = getFormItemInt(F("baudrate")); - Settings.UseNTP(isFormItemChecked(F("usentp"))); - Settings.ExtTimeSource( - static_cast(getFormItemInt(F("exttimesource"))) - ); - #if FEATURE_I2C_MULTIPLE - if (getI2CBusCount() > 1) { - set3BitToUL(Settings.I2C_peripheral_bus, I2C_PERIPHERAL_BUS_CLOCK, getFormItemInt(F("pi2cbusrtc"))); - set3BitToUL(Settings.I2C_peripheral_bus, I2C_PERIPHERAL_BUS_WDT, getFormItemInt(F("pi2cbuswdt"))); - } - #endif // if FEATURE_I2C_MULTIPLE - Settings.DST = isFormItemChecked(F("dst")); - Settings.WDI2CAddress = getFormItemInt(F("wdi2caddress")); - #if FEATURE_SSDP - Settings.UseSSDP = isFormItemChecked(F("usessdp")); - #endif // if FEATURE_SSDP - Settings.UseRules = isFormItemChecked(F("userules")); - Settings.ConnectionFailuresThreshold = getFormItemInt(LabelType::CONNECTION_FAIL_THRESH); - Settings.ArduinoOTAEnable = isFormItemChecked(F("arduinootaenable")); - Settings.UseRTOSMultitasking = isFormItemChecked(F("usertosmultitasking")); - - // MQTT settings now moved to the controller settings. -// Settings.MQTTRetainFlag_unused = isFormItemChecked(F("mqttretainflag")); -// Settings.MQTTUseUnitNameAsClientId = isFormItemChecked(F("mqttuseunitnameasclientid")); -// Settings.uniqueMQTTclientIdReconnect(isFormItemChecked(F("uniquemqttclientidreconnect"))); - Settings.Latitude = getFormItemFloat(F("latitude")); - Settings.Longitude = getFormItemFloat(F("longitude")); - #ifdef WEBSERVER_NEW_RULES - Settings.OldRulesEngine(isFormItemChecked(F("oldrulesengine"))); - #endif // WEBSERVER_NEW_RULES - Settings.TolerantLastArgParse(isFormItemChecked(F("tolerantargparse"))); - Settings.SendToHttp_ack(isFormItemChecked(F("sendtohttp_ack"))); - Settings.SendToHTTP_follow_redirects(isFormItemChecked(F("sendtohttp_redir"))); - Settings.ForceWiFi_bg_mode(isFormItemChecked(LabelType::FORCE_WIFI_BG)); - Settings.WiFiRestart_connection_lost(isFormItemChecked(LabelType::RESTART_WIFI_LOST_CONN)); - Settings.EcoPowerMode(isFormItemChecked(LabelType::CPU_ECO_MODE)); - Settings.WifiNoneSleep(isFormItemChecked(LabelType::FORCE_WIFI_NOSLEEP)); -#ifdef SUPPORT_ARP - Settings.gratuitousARP(isFormItemChecked(LabelType::PERIODICAL_GRAT_ARP)); -#endif // ifdef SUPPORT_ARP -#if FEATURE_SET_WIFI_TX_PWR - Settings.setWiFi_TX_power(getFormItemFloat(LabelType::WIFI_TX_MAX_PWR)); - Settings.WiFi_sensitivity_margin = getFormItemInt(LabelType::WIFI_SENS_MARGIN); - Settings.UseMaxTXpowerForSending(isFormItemChecked(LabelType::WIFI_SEND_AT_MAX_TX_PWR)); -#endif - Settings.NumberExtraWiFiScans = getFormItemInt(LabelType::WIFI_NR_EXTRA_SCANS); - Settings.UseLastWiFiFromRTC(isFormItemChecked(LabelType::WIFI_USE_LAST_CONN_FROM_RTC)); - Settings.JSONBoolWithoutQuotes(isFormItemChecked(LabelType::JSON_BOOL_QUOTES)); -#if FEATURE_TIMING_STATS - Settings.EnableTimingStats(isFormItemChecked(LabelType::ENABLE_TIMING_STATISTICS)); -#endif - Settings.AllowTaskValueSetAllPlugins(isFormItemChecked(LabelType::TASKVALUESET_ALL_PLUGINS)); -#if FEATURE_CLEAR_I2C_STUCK - Settings.EnableClearHangingI2Cbus(isFormItemChecked(LabelType::ENABLE_CLEAR_HUNG_I2C_BUS)); -#endif - #if FEATURE_I2C_DEVICE_CHECK - Settings.CheckI2Cdevice(isFormItemChecked(LabelType::ENABLE_I2C_DEVICE_CHECK)); - #endif // if FEATURE_I2C_DEVICE_CHECK -#ifndef ESP32 - Settings.WaitWiFiConnect(isFormItemChecked(LabelType::WAIT_WIFI_CONNECT)); -#endif - Settings.HiddenSSID_SlowConnectPerBSSID(isFormItemChecked(LabelType::HIDDEN_SSID_SLOW_CONNECT)); - Settings.SDK_WiFi_autoreconnect(isFormItemChecked(LabelType::SDK_WIFI_AUTORECONNECT)); -#ifdef ESP32 - Settings.PassiveWiFiScan(isFormItemChecked(LabelType::WIFI_PASSIVE_SCAN)); -#endif -#if FEATURE_USE_IPV6 - Settings.EnableIPv6(isFormItemChecked(LabelType::ENABLE_IPV6)); -#endif - - - -#ifndef BUILD_NO_RAM_TRACKER - Settings.EnableRAMTracking(isFormItemChecked(LabelType::ENABLE_RAM_TRACKING)); -#endif - - #ifdef ESP8266 - Settings.UseAlternativeDeepSleep(isFormItemChecked(LabelType::DEEP_SLEEP_ALTERNATIVE_CALL)); - #endif - - Settings.EnableRulesCaching(isFormItemChecked(LabelType::ENABLE_RULES_CACHING)); -// Settings.EnableRulesEventReorder(isFormItemChecked(LabelType::ENABLE_RULES_EVENT_REORDER)); // TD-er: Disabled for now - -#ifndef NO_HTTP_UPDATER - Settings.AllowOTAUnlimited(isFormItemChecked(LabelType::ALLOW_OTA_UNLIMITED)); -#endif // NO_HTTP_UPDATER -#if FEATURE_AUTO_DARK_MODE - Settings.setCssMode(getFormItemInt(getInternalLabel(LabelType::ENABLE_AUTO_DARK_MODE))); -#endif // FEATURE_AUTO_DARK_MODE -#if FEATURE_RULES_EASY_COLOR_CODE - Settings.DisableRulesCodeCompletion(isFormItemChecked(LabelType::DISABLE_RULES_AUTOCOMPLETE)); -#endif // if FEATURE_RULES_EASY_COLOR_CODE -#if FEATURE_TARSTREAM_SUPPORT - Settings.DisableSaveConfigAsTar(isFormItemChecked(LabelType::DISABLE_SAVE_CONFIG_AS_TAR)); -#endif // if FEATURE_TARSTREAM_SUPPORT - #if FEATURE_TASKVALUE_UNIT_OF_MEASURE - Settings.ShowUnitOfMeasureOnDevicesPage(isFormItemChecked(LabelType::SHOW_UOM_ON_DEVICES_PAGE)); - #endif // if FEATURE_TASKVALUE_UNIT_OF_MEASURE - - addHtmlError(SaveSettings()); - - if (node_time.systemTimePresent()) { - node_time.initTime(); - } - } - - addHtml(F("
")); - html_table_class_normal(); - - addFormHeader(F("Advanced Settings"), F("RTDTools/Tools.html#advanced")); - - addFormSubHeader(F("Rules Settings")); - - addFormCheckBox(F("Rules"), F("userules"), Settings.UseRules); - #ifdef WEBSERVER_NEW_RULES - addFormCheckBox(F("Old Engine"), F("oldrulesengine"), Settings.OldRulesEngine()); - #endif // WEBSERVER_NEW_RULES - addFormCheckBox(LabelType::ENABLE_RULES_CACHING, Settings.EnableRulesCaching()); -// addFormCheckBox(LabelType::ENABLE_RULES_EVENT_REORDER, Settings.EnableRulesEventReorder()); // TD-er: Disabled for now - - addFormCheckBox(F("Tolerant last parameter"), F("tolerantargparse"), Settings.TolerantLastArgParse()); - addFormNote(F("Perform less strict parsing on last argument of some commands (e.g. publish and sendToHttp)")); - addFormCheckBox(F("SendToHTTP wait for ack"), F("sendtohttp_ack"), Settings.SendToHttp_ack()); - addFormCheckBox(F("SendToHTTP Follow Redirects"), F("sendtohttp_redir"), Settings.SendToHTTP_follow_redirects()); - - /* - // MQTT settings now moved to the controller settings. - addFormSubHeader(F("Controller Settings")); - - addFormNumericBox(F("Message Interval"), F("messagedelay"), Settings.MessageDelay_unused, 0, INT_MAX); - addUnit(F("ms")); - - addFormCheckBox(F("MQTT Retain Msg"), F("mqttretainflag"), Settings.MQTTRetainFlag_unused); - addFormCheckBox(F("MQTT use unit name as ClientId"), F("mqttuseunitnameasclientid"), Settings.MQTTUseUnitNameAsClientId); - addFormCheckBox(F("MQTT change ClientId at reconnect"), F("uniquemqttclientidreconnect"), Settings.uniqueMQTTclientIdReconnect_unused()); -*/ - - addFormSubHeader(F("Time Source")); - - addFormCheckBox(F("Use NTP"), F("usentp"), Settings.UseNTP()); - addFormTextBox(F("NTP Hostname"), F("ntphost"), Settings.NTPHost, 63); - #if FEATURE_EXT_RTC - addFormExtTimeSourceSelect(F("External Time Source"), F("exttimesource"), Settings.ExtTimeSource()); - if (Settings.ExtTimeSource() != ExtTimeSource_e::None) { - addFormNote(concat(getLabel(LabelType::EXT_RTC_UTC_TIME), F(": ")) + getValue(LabelType::EXT_RTC_UTC_TIME)); - } - #if FEATURE_I2C_MULTIPLE - { - const uint8_t i2cBus = Settings.getI2CInterfaceRTC(); - I2CInterfaceSelector(F("Ext. Time Source I2C Bus"), - F("pi2cbusrtc"), - i2cBus, - false); - } - #endif // if FEATURE_I2C_MULTIPLE - #endif - - addFormSubHeader(F("DST Settings")); - addFormDstSelect(true, Settings.DST_Start); - addFormDstSelect(false, Settings.DST_End); - addFormCheckBox(F("DST"), F("dst"), Settings.DST); - - addFormSubHeader(F("Location Settings")); - addFormNumericBox(F("Timezone Offset (UTC +)"), F("timezone"), Settings.TimeZone, -720, 840); // UTC-12H ... UTC+14h - addUnit(F("minutes")); - addFormFloatNumberBox(F("Latitude"), F("latitude"), Settings.Latitude, -90.0f, 90.0f); - addUnit(F("°")); - addFormFloatNumberBox(F("Longitude"), F("longitude"), Settings.Longitude, -180.0f, 180.0f); - addUnit(F("°")); - addFormNote(F("Longitude and Latitude are used to compute sunrise and sunset")); - - addFormSubHeader(F("Log Settings")); - - addFormIPBox(F("Syslog IP"), F("syslogip"), Settings.Syslog_IP); - addFormNumericBox(F("Syslog UDP port"), F("syslogport"), Settings.SyslogPort, 0, 65535); - - addFormLogLevelSelect(LabelType::SYSLOG_LOG_LEVEL, Settings.SyslogLevel); - addFormLogFacilitySelect(F("Syslog Facility"), F("syslogfacility"), Settings.SyslogFacility); - addFormLogLevelSelect(LabelType::SERIAL_LOG_LEVEL, Settings.SerialLogLevel); - addFormLogLevelSelect(LabelType::WEB_LOG_LEVEL, Settings.WebLogLevel); - -#if FEATURE_SD - addFormLogLevelSelect(LabelType::SD_LOG_LEVEL, Settings.SDLogLevel); - - addFormCheckBox(F("SD Card Value Logger"), F("valuelogger"), Settings.UseValueLogger); -#endif // if FEATURE_SD - - - addFormSubHeader(F("Serial Console Settings")); - addFormCheckBox(LabelType::ENABLE_SERIAL_PORT_CONSOLE, Settings.UseSerial); - addFormNumericBox(F("Baud Rate"), F("baudrate"), Settings.BaudRate, 0, 1000000); - -#if FEATURE_DEFINE_SERIAL_CONSOLE_PORT - serialHelper_webformLoad( - static_cast(Settings.console_serial_port), - Settings.console_serial_rxpin, - Settings.console_serial_txpin, - true); - - // Show serial port selection - addFormPinSelect( - PinSelectPurpose::Serial_input, - formatGpioName_serialRX(false), - F("taskdevicepin1"), - Settings.console_serial_rxpin); - addFormPinSelect( - PinSelectPurpose::Serial_output, - formatGpioName_serialTX(false), - F("taskdevicepin2"), - Settings.console_serial_txpin); - - html_add_script(F("document.getElementById('serPort').onchange();"), false); -#if USES_ESPEASY_CONSOLE_FALLBACK_PORT - addFormCheckBox(LabelType::CONSOLE_FALLBACK_TO_SERIAL0, Settings.console_serial0_fallback); -#endif - -#endif - - - addFormSubHeader(F("Inter-ESPEasy Network")); - if (Settings.UDPPort != 8266 ) addFormNote(F("Preferred P2P port is 8266")); - addFormNumericBox(F("ESPEasy p2p UDP port"), F("udpport"), Settings.UDPPort, 0, 65535); - - // TODO sort settings in groups or move to other pages/groups - addFormSubHeader(F("Special and Experimental Settings")); - - addFormNumericBox(F("Webserver port"), F("webport"), Settings.WebserverPort, 0, 65535); - addFormNote(F("Requires reboot to activate")); - - addFormNumericBox(F("Fixed IP Octet"), F("ip"), Settings.IP_Octet, 0, 255); - - addFormNumericBox(F("WD I2C Address"), F("wdi2caddress"), Settings.WDI2CAddress, 0, 127); - addHtml(F(" (decimal)")); - #if FEATURE_I2C_MULTIPLE - { - const uint8_t i2cBus = Settings.getI2CInterfaceWDT(); - I2CInterfaceSelector(F("WD I2C Bus"), - F("pi2cbuswdt"), - i2cBus, - false); - } - #endif // if FEATURE_I2C_MULTIPLE - - // TODO: Remove this code - addRowLabel(F("I2C ClockStretchLimit")); - addUnit(F("Moved to Hardware page")); - - #if FEATURE_ARDUINO_OTA - addFormCheckBox(F("Enable Arduino OTA"), F("arduinootaenable"), Settings.ArduinoOTAEnable); - #endif // if FEATURE_ARDUINO_OTA - #if defined(ESP32) - addFormCheckBox_disabled(F("Enable RTOS Multitasking"), F("usertosmultitasking"), Settings.UseRTOSMultitasking); - #endif // if defined(ESP32) - - addFormCheckBox(LabelType::JSON_BOOL_QUOTES, Settings.JSONBoolWithoutQuotes()); -#if FEATURE_TIMING_STATS - addFormCheckBox(LabelType::ENABLE_TIMING_STATISTICS, Settings.EnableTimingStats()); -#endif // if FEATURE_TIMING_STATS -#ifndef BUILD_NO_RAM_TRACKER - addFormCheckBox(LabelType::ENABLE_RAM_TRACKING, Settings.EnableRAMTracking()); -#endif - - addFormCheckBox(LabelType::TASKVALUESET_ALL_PLUGINS, Settings.AllowTaskValueSetAllPlugins()); -#if FEATURE_CLEAR_I2C_STUCK - addFormCheckBox(LabelType::ENABLE_CLEAR_HUNG_I2C_BUS, Settings.EnableClearHangingI2Cbus()); -#endif - #if FEATURE_I2C_DEVICE_CHECK - addFormCheckBox(LabelType::ENABLE_I2C_DEVICE_CHECK, Settings.CheckI2Cdevice()); - #endif // if FEATURE_I2C_DEVICE_CHECK - - #if FEATURE_TASKVALUE_UNIT_OF_MEASURE - addFormCheckBox(LabelType::SHOW_UOM_ON_DEVICES_PAGE, Settings.ShowUnitOfMeasureOnDevicesPage()); - #endif // if FEATURE_TASKVALUE_UNIT_OF_MEASURE - - # ifndef NO_HTTP_UPDATER - addFormCheckBox(LabelType::ALLOW_OTA_UNLIMITED, Settings.AllowOTAUnlimited()); - # endif // ifndef NO_HTTP_UPDATER - #if FEATURE_AUTO_DARK_MODE - const __FlashStringHelper * cssModeNames[] = { - F("Auto"), - F("Light"), - F("Dark"), - }; - //const int cssModeOptions[] = { 0, 1, 2}; - constexpr int nrCssModeOptions = NR_ELEMENTS(cssModeNames); - const FormSelectorOptions selector( - nrCssModeOptions, - cssModeNames/*, - cssModeOptions*/); - selector.addFormSelector( - getLabel(LabelType::ENABLE_AUTO_DARK_MODE), - getInternalLabel(LabelType::ENABLE_AUTO_DARK_MODE), - Settings.getCssMode()); - #endif // FEATURE_AUTO_DARK_MODE - - #if FEATURE_RULES_EASY_COLOR_CODE - addFormCheckBox(LabelType::DISABLE_RULES_AUTOCOMPLETE, Settings.DisableRulesCodeCompletion()); - #endif // if FEATURE_RULES_EASY_COLOR_CODE - #if FEATURE_TARSTREAM_SUPPORT - addFormCheckBox(LabelType::DISABLE_SAVE_CONFIG_AS_TAR, Settings.DisableSaveConfigAsTar()); - #endif // if FEATURE_TARSTREAM_SUPPORT - - #ifdef ESP8266 - addFormCheckBox(LabelType::DEEP_SLEEP_ALTERNATIVE_CALL, Settings.UseAlternativeDeepSleep()); - #endif - - - #if FEATURE_SSDP - addFormCheckBox_disabled(F("Use SSDP"), F("usessdp"), Settings.UseSSDP); - #endif // if FEATURE_SSDP - - addFormNumericBox(LabelType::CONNECTION_FAIL_THRESH, Settings.ConnectionFailuresThreshold, 0, 100); - addFormCheckBox(LabelType::FORCE_WIFI_BG, Settings.ForceWiFi_bg_mode()); - - addFormCheckBox(LabelType::RESTART_WIFI_LOST_CONN, Settings.WiFiRestart_connection_lost()); - addFormCheckBox(LabelType::FORCE_WIFI_NOSLEEP, Settings.WifiNoneSleep()); -#ifdef SUPPORT_ARP - addFormCheckBox(LabelType::PERIODICAL_GRAT_ARP, Settings.gratuitousARP()); -#endif // ifdef SUPPORT_ARP - addFormCheckBox(LabelType::CPU_ECO_MODE, Settings.EcoPowerMode()); -#if FEATURE_SET_WIFI_TX_PWR - addFormFloatNumberBox(LabelType::WIFI_TX_MAX_PWR, Settings.getWiFi_TX_power(), 0.0f, MAX_TX_PWR_DBM_11b, 2, 0.25f); - addFormNumericBox(LabelType::WIFI_SENS_MARGIN, Settings.WiFi_sensitivity_margin, -20, 30); - addFormCheckBox(LabelType::WIFI_SEND_AT_MAX_TX_PWR, Settings.UseMaxTXpowerForSending()); -#endif - { - addFormNumericBox(LabelType::WIFI_NR_EXTRA_SCANS, Settings.NumberExtraWiFiScans, 0, 5); - } - addFormCheckBox(LabelType::WIFI_USE_LAST_CONN_FROM_RTC, Settings.UseLastWiFiFromRTC()); - -#ifndef ESP32 - addFormCheckBox(LabelType::WAIT_WIFI_CONNECT, Settings.WaitWiFiConnect()); -#endif - addFormCheckBox(LabelType::SDK_WIFI_AUTORECONNECT, Settings.SDK_WiFi_autoreconnect()); - addFormCheckBox(LabelType::HIDDEN_SSID_SLOW_CONNECT, Settings.HiddenSSID_SlowConnectPerBSSID()); -#ifdef ESP32 - addFormCheckBox(LabelType::WIFI_PASSIVE_SCAN, Settings.PassiveWiFiScan()); -#endif -#if FEATURE_USE_IPV6 - addFormCheckBox(LabelType::ENABLE_IPV6, Settings.EnableIPv6()); -#endif - - - - addFormSeparator(2); - - html_TR_TD(); - html_TD(); - addSubmitButton(); - addHtml(F("")); - html_end_table(); - html_end_form(); - sendHeadandTail_stdtemplate(_TAIL); - TXBuffer.endStream(); -} - -void addFormDstSelect(bool isStart, uint16_t choice) { - uint16_t tmpstart(choice); - uint16_t tmpend(choice); - - if (!TimeChangeRule(choice, 0).isValid()) { - time_zone.getDefaultDst_flash_values(tmpstart, tmpend); - } - TimeChangeRule rule(isStart ? tmpstart : tmpend, 0); - { - const __FlashStringHelper * week[] = { F("Last"), F("1st"), F("2nd"), F("3rd"), F("4th") }; - constexpr int weekValues[] = { 0, 1, 2, 3, 4 }; - addRowLabel(concat( - isStart ? F("Start") : F("End"), - F(" (week, dow, month)"))); - - const FormSelectorOptions selector(NR_ELEMENTS(weekValues), week, weekValues); - selector.addSelector( - isStart ? F("dststartweek") : F("dstendweek"), - rule.week); - } - html_BR(); - { - const __FlashStringHelper * dow[] = { F("Sun"), F("Mon"), F("Tue"), F("Wed"), F("Thu"), F("Fri"), F("Sat") }; - constexpr int dowValues[] = { 1, 2, 3, 4, 5, 6, 7 }; - - const FormSelectorOptions selector(NR_ELEMENTS(dowValues), dow, dowValues); - selector.addSelector( - isStart ? F("dststartdow") : F("dstenddow"), - rule.dow); - } - html_BR(); - { - const __FlashStringHelper * month[] = { F("Jan"), F("Feb"), F("Mar"), F("Apr"), F("May"), F("Jun"), F("Jul"), F("Aug"), F("Sep"), F("Oct"), F("Nov"), F( - "Dec") }; - constexpr int monthValues[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 }; - - const FormSelectorOptions selector(NR_ELEMENTS(monthValues), month, monthValues); - selector.addSelector( - isStart ? F("dststartmonth") : F("dstendmonth"), - rule.month); - } - - addFormNumericBox( - isStart ? F("Start (localtime, e.g. 2h→3h)") : F("End (localtime, e.g. 3h→2h)"), - isStart ? F("dststarthour") : F("dstendhour"), - rule.hour, 0, 23); - addUnit(isStart ? F("hour ↷") : F("hour ↶")); -} - -void addFormExtTimeSourceSelect(const __FlashStringHelper * label, const __FlashStringHelper * id, ExtTimeSource_e choice) -{ - addRowLabel(label); - const __FlashStringHelper * options[] = - { F("None"), F("DS1307"), F("DS3231"), F("PCF8523"), F("PCF8563")}; - constexpr int optionValues[] = { - static_cast(ExtTimeSource_e::None), - static_cast(ExtTimeSource_e::DS1307), - static_cast(ExtTimeSource_e::DS3231), - static_cast(ExtTimeSource_e::PCF8523), - static_cast(ExtTimeSource_e::PCF8563) - }; - - const FormSelectorOptions selector(NR_ELEMENTS(optionValues), options, optionValues); - selector.addSelector(id, static_cast(choice)); -} - - -void addFormLogLevelSelect(LabelType::Enum label, int choice) -{ - #ifdef BUILD_NO_DEBUG - if (choice > LOG_LEVEL_INFO) choice = LOG_LEVEL_INFO; - #endif - - addRowLabel(getLabel(label)); - const __FlashStringHelper * options[LOG_LEVEL_NRELEMENTS + 1]; - int optionValues[LOG_LEVEL_NRELEMENTS + 1] = { 0 }; - - options[0] = getLogLevelDisplayString(0); - - for (int i = 0; i < LOG_LEVEL_NRELEMENTS; ++i) { - options[i + 1] = getLogLevelDisplayStringFromIndex(i, optionValues[i + 1]); - } - const FormSelectorOptions selector(LOG_LEVEL_NRELEMENTS + 1, options, optionValues); - selector.addSelector(getInternalLabel(label), choice); - -} - -void addFormLogFacilitySelect(const __FlashStringHelper * label, const __FlashStringHelper * id, int choice) -{ - addRowLabel(label); - const __FlashStringHelper * options[] = - { F("Kernel"), F("User"), F("Daemon"), F("Message"), F("Local0"), F("Local1"), - F("Local2"), F("Local3"), F("Local4"), F("Local5"), F("Local6"), F("Local7") }; - const int optionValues[] = { 0, 1, 3, 5, 16, 17, 18, 19, 20, 21, 22, 23 }; - - const FormSelectorOptions selector(NR_ELEMENTS(options), options, optionValues); - selector.addSelector(id, choice); -} - -#endif // ifdef WEBSERVER_ADVANCED +#include "../WebServer/AdvancedConfigPage.h" + +#ifdef WEBSERVER_ADVANCED + +#include "../WebServer/HTML_wrappers.h" +#include "../WebServer/Markup.h" +#include "../WebServer/Markup_Buttons.h" +#include "../WebServer/Markup_Forms.h" +#include "../WebServer/ESPEasy_WebServer.h" + +#include "../ESPEasyCore/ESPEasyWifi.h" + +#include "../Globals/ESPEasy_time.h" +#include "../Globals/Settings.h" +#include "../Globals/TimeZone.h" + +#include "../Helpers/_Plugin_Helper_serial.h" +#include "../Helpers/ESPEasy_Storage.h" +#include "../Helpers/ESPEasy_time.h" +#include "../Helpers/Hardware_defines.h" +#include "../Helpers/StringConverter.h" + +#if FEATURE_I2C_MULTIPLE +#include "../Helpers/I2C_access.h" +#endif + +void setLogLevelFor(uint8_t destination, LabelType::Enum label) { + setLogLevelFor(destination, getFormItemInt(getInternalLabel(label))); +} + +// ******************************************************************************** +// Web Interface config page +// ******************************************************************************** +void handle_advanced() { + #ifndef BUILD_NO_RAM_TRACKER + checkRAM(F("handle_advanced")); + #endif + + if (!isLoggedIn()) { return; } + navMenuIndex = MENU_INDEX_TOOLS; + TXBuffer.startStream(); + sendHeadandTail_stdtemplate(_HEAD); + + if (!webArg(F("edit")).isEmpty()) + { +// Settings.MessageDelay_unused = getFormItemInt(F("messagedelay")); + Settings.IP_Octet = webArg(F("ip")).toInt(); + strncpy_webserver_arg(Settings.NTPHost, F("ntphost")); + Settings.TimeZone = getFormItemInt(F("timezone")); + TimeChangeRule dst_start(getFormItemInt(F("dststartweek")), getFormItemInt(F("dststartdow")), getFormItemInt(F("dststartmonth")), getFormItemInt(F("dststarthour")), Settings.TimeZone); + + if (dst_start.isValid()) { Settings.DST_Start = dst_start.toFlashStoredValue(); } + TimeChangeRule dst_end(getFormItemInt(F("dstendweek")), getFormItemInt(F("dstenddow")), getFormItemInt(F("dstendmonth")), getFormItemInt(F("dstendhour")), Settings.TimeZone); + + if (dst_end.isValid()) { Settings.DST_End = dst_end.toFlashStoredValue(); } + webArg2ip(F("syslogip"), Settings.Syslog_IP); + Settings.WebserverPort = getFormItemInt(F("webport")); + Settings.UDPPort = getFormItemInt(F("udpport")); + + Settings.SyslogFacility = getFormItemInt(F("syslogfacility")); + Settings.SyslogPort = getFormItemInt(F("syslogport")); + Settings.UseSerial = isFormItemChecked(LabelType::ENABLE_SERIAL_PORT_CONSOLE); + +#if FEATURE_DEFINE_SERIAL_CONSOLE_PORT + Settings.console_serial_rxpin = getFormItemInt(F("taskdevicepin1"), Settings.console_serial_rxpin); + Settings.console_serial_txpin = getFormItemInt(F("taskdevicepin2"), Settings.console_serial_txpin); + + serialHelper_webformSave( + Settings.console_serial_port, + Settings.console_serial_rxpin, + Settings.console_serial_txpin); +#if USES_ESPEASY_CONSOLE_FALLBACK_PORT + Settings.console_serial0_fallback = isFormItemChecked(LabelType::CONSOLE_FALLBACK_TO_SERIAL0); +#endif + +#endif + setLogLevelFor(LOG_TO_SYSLOG, LabelType::SYSLOG_LOG_LEVEL); + setLogLevelFor(LOG_TO_SERIAL, LabelType::SERIAL_LOG_LEVEL); + setLogLevelFor(LOG_TO_WEBLOG, LabelType::WEB_LOG_LEVEL); +#if FEATURE_SD + setLogLevelFor(LOG_TO_SDCARD, LabelType::SD_LOG_LEVEL); +#endif // if FEATURE_SD + Settings.UseValueLogger = isFormItemChecked(F("valuelogger")); + Settings.BaudRate = getFormItemInt(F("baudrate")); + Settings.UseNTP(isFormItemChecked(F("usentp"))); + Settings.ExtTimeSource( + static_cast(getFormItemInt(F("exttimesource"))) + ); + #if FEATURE_I2C_MULTIPLE + if (getI2CBusCount() > 1) { + set3BitToUL(Settings.I2C_peripheral_bus, I2C_PERIPHERAL_BUS_CLOCK, getFormItemInt(F("pi2cbusrtc"))); + set3BitToUL(Settings.I2C_peripheral_bus, I2C_PERIPHERAL_BUS_WDT, getFormItemInt(F("pi2cbuswdt"))); + } + #endif // if FEATURE_I2C_MULTIPLE + Settings.DST = isFormItemChecked(F("dst")); + Settings.WDI2CAddress = getFormItemInt(F("wdi2caddress")); + #if FEATURE_SSDP + Settings.UseSSDP = isFormItemChecked(F("usessdp")); + #endif // if FEATURE_SSDP + Settings.UseRules = isFormItemChecked(F("userules")); + Settings.ConnectionFailuresThreshold = getFormItemInt(LabelType::CONNECTION_FAIL_THRESH); + Settings.ArduinoOTAEnable = isFormItemChecked(F("arduinootaenable")); + Settings.UseRTOSMultitasking = isFormItemChecked(F("usertosmultitasking")); + + // MQTT settings now moved to the controller settings. +// Settings.MQTTRetainFlag_unused = isFormItemChecked(F("mqttretainflag")); +// Settings.MQTTUseUnitNameAsClientId = isFormItemChecked(F("mqttuseunitnameasclientid")); +// Settings.uniqueMQTTclientIdReconnect(isFormItemChecked(F("uniquemqttclientidreconnect"))); + Settings.Latitude = getFormItemFloat(F("latitude")); + Settings.Longitude = getFormItemFloat(F("longitude")); + #ifdef WEBSERVER_NEW_RULES + Settings.OldRulesEngine(isFormItemChecked(F("oldrulesengine"))); + #endif // WEBSERVER_NEW_RULES + Settings.TolerantLastArgParse(isFormItemChecked(F("tolerantargparse"))); + Settings.SendToHttp_ack(isFormItemChecked(F("sendtohttp_ack"))); + Settings.SendToHTTP_follow_redirects(isFormItemChecked(F("sendtohttp_redir"))); + Settings.ForceWiFi_bg_mode(isFormItemChecked(LabelType::FORCE_WIFI_BG)); + Settings.WiFiRestart_connection_lost(isFormItemChecked(LabelType::RESTART_WIFI_LOST_CONN)); + Settings.EcoPowerMode(isFormItemChecked(LabelType::CPU_ECO_MODE)); + Settings.WifiNoneSleep(isFormItemChecked(LabelType::FORCE_WIFI_NOSLEEP)); +#ifdef SUPPORT_ARP + Settings.gratuitousARP(isFormItemChecked(LabelType::PERIODICAL_GRAT_ARP)); +#endif // ifdef SUPPORT_ARP +#if FEATURE_SET_WIFI_TX_PWR + Settings.setWiFi_TX_power(getFormItemFloat(LabelType::WIFI_TX_MAX_PWR)); + Settings.WiFi_sensitivity_margin = getFormItemInt(LabelType::WIFI_SENS_MARGIN); + Settings.UseMaxTXpowerForSending(isFormItemChecked(LabelType::WIFI_SEND_AT_MAX_TX_PWR)); +#endif + Settings.NumberExtraWiFiScans = getFormItemInt(LabelType::WIFI_NR_EXTRA_SCANS); + Settings.UseLastWiFiFromRTC(isFormItemChecked(LabelType::WIFI_USE_LAST_CONN_FROM_RTC)); + Settings.JSONBoolWithoutQuotes(isFormItemChecked(LabelType::JSON_BOOL_QUOTES)); +#if FEATURE_TIMING_STATS + Settings.EnableTimingStats(isFormItemChecked(LabelType::ENABLE_TIMING_STATISTICS)); +#endif + Settings.AllowTaskValueSetAllPlugins(isFormItemChecked(LabelType::TASKVALUESET_ALL_PLUGINS)); +#if FEATURE_CLEAR_I2C_STUCK + Settings.EnableClearHangingI2Cbus(isFormItemChecked(LabelType::ENABLE_CLEAR_HUNG_I2C_BUS)); +#endif + #if FEATURE_I2C_DEVICE_CHECK + Settings.CheckI2Cdevice(isFormItemChecked(LabelType::ENABLE_I2C_DEVICE_CHECK)); + #endif // if FEATURE_I2C_DEVICE_CHECK +#ifndef ESP32 + Settings.WaitWiFiConnect(isFormItemChecked(LabelType::WAIT_WIFI_CONNECT)); +#endif + Settings.HiddenSSID_SlowConnectPerBSSID(isFormItemChecked(LabelType::HIDDEN_SSID_SLOW_CONNECT)); + Settings.SDK_WiFi_autoreconnect(isFormItemChecked(LabelType::SDK_WIFI_AUTORECONNECT)); +#ifdef ESP32 + Settings.PassiveWiFiScan(isFormItemChecked(LabelType::WIFI_PASSIVE_SCAN)); +#endif +#if FEATURE_USE_IPV6 + Settings.EnableIPv6(isFormItemChecked(LabelType::ENABLE_IPV6)); +#endif + + + +#ifndef BUILD_NO_RAM_TRACKER + Settings.EnableRAMTracking(isFormItemChecked(LabelType::ENABLE_RAM_TRACKING)); +#endif + + #ifdef ESP8266 + Settings.UseAlternativeDeepSleep(isFormItemChecked(LabelType::DEEP_SLEEP_ALTERNATIVE_CALL)); + #endif + +#ifdef USES_ESPEASY_NOW + Settings.UseESPEasyNow(isFormItemChecked(getInternalLabel(LabelType::ESPEASY_NOW_ENABLED))); + Settings.ForceESPEasyNOWchannel = getFormItemInt(getInternalLabel(LabelType::ESPEASY_NOW_FORCED_CHANNEL)); +#endif + + Settings.EnableRulesCaching(isFormItemChecked(LabelType::ENABLE_RULES_CACHING)); +// Settings.EnableRulesEventReorder(isFormItemChecked(LabelType::ENABLE_RULES_EVENT_REORDER)); // TD-er: Disabled for now + +#ifndef NO_HTTP_UPDATER + Settings.AllowOTAUnlimited(isFormItemChecked(LabelType::ALLOW_OTA_UNLIMITED)); +#endif // NO_HTTP_UPDATER +#if FEATURE_AUTO_DARK_MODE + Settings.setCssMode(getFormItemInt(getInternalLabel(LabelType::ENABLE_AUTO_DARK_MODE))); +#endif // FEATURE_AUTO_DARK_MODE +#if FEATURE_RULES_EASY_COLOR_CODE + Settings.DisableRulesCodeCompletion(isFormItemChecked(LabelType::DISABLE_RULES_AUTOCOMPLETE)); +#endif // if FEATURE_RULES_EASY_COLOR_CODE +#if FEATURE_TARSTREAM_SUPPORT + Settings.DisableSaveConfigAsTar(isFormItemChecked(LabelType::DISABLE_SAVE_CONFIG_AS_TAR)); +#endif // if FEATURE_TARSTREAM_SUPPORT + #if FEATURE_TASKVALUE_UNIT_OF_MEASURE + Settings.ShowUnitOfMeasureOnDevicesPage(isFormItemChecked(LabelType::SHOW_UOM_ON_DEVICES_PAGE)); + #endif // if FEATURE_TASKVALUE_UNIT_OF_MEASURE + + addHtmlError(SaveSettings()); + + if (node_time.systemTimePresent()) { + node_time.initTime(); + } + } + + addHtml(F("")); + html_table_class_normal(); + + addFormHeader(F("Advanced Settings"), F("RTDTools/Tools.html#advanced")); + + addFormSubHeader(F("Rules Settings")); + + addFormCheckBox(F("Rules"), F("userules"), Settings.UseRules); + #ifdef WEBSERVER_NEW_RULES + addFormCheckBox(F("Old Engine"), F("oldrulesengine"), Settings.OldRulesEngine()); + #endif // WEBSERVER_NEW_RULES + addFormCheckBox(LabelType::ENABLE_RULES_CACHING, Settings.EnableRulesCaching()); +// addFormCheckBox(LabelType::ENABLE_RULES_EVENT_REORDER, Settings.EnableRulesEventReorder()); // TD-er: Disabled for now + + addFormCheckBox(F("Tolerant last parameter"), F("tolerantargparse"), Settings.TolerantLastArgParse()); + addFormNote(F("Perform less strict parsing on last argument of some commands (e.g. publish and sendToHttp)")); + addFormCheckBox(F("SendToHTTP wait for ack"), F("sendtohttp_ack"), Settings.SendToHttp_ack()); + addFormCheckBox(F("SendToHTTP Follow Redirects"), F("sendtohttp_redir"), Settings.SendToHTTP_follow_redirects()); + + /* + // MQTT settings now moved to the controller settings. + addFormSubHeader(F("Controller Settings")); + + addFormNumericBox(F("Message Interval"), F("messagedelay"), Settings.MessageDelay_unused, 0, INT_MAX); + addUnit(F("ms")); + + addFormCheckBox(F("MQTT Retain Msg"), F("mqttretainflag"), Settings.MQTTRetainFlag_unused); + addFormCheckBox(F("MQTT use unit name as ClientId"), F("mqttuseunitnameasclientid"), Settings.MQTTUseUnitNameAsClientId); + addFormCheckBox(F("MQTT change ClientId at reconnect"), F("uniquemqttclientidreconnect"), Settings.uniqueMQTTclientIdReconnect_unused()); +*/ + + addFormSubHeader(F("Time Source")); + + addFormCheckBox(F("Use NTP"), F("usentp"), Settings.UseNTP()); + addFormTextBox(F("NTP Hostname"), F("ntphost"), Settings.NTPHost, 63); + #if FEATURE_EXT_RTC + addFormExtTimeSourceSelect(F("External Time Source"), F("exttimesource"), Settings.ExtTimeSource()); + if (Settings.ExtTimeSource() != ExtTimeSource_e::None) { + addFormNote(concat(getLabel(LabelType::EXT_RTC_UTC_TIME), F(": ")) + getValue(LabelType::EXT_RTC_UTC_TIME)); + } + #if FEATURE_I2C_MULTIPLE + { + const uint8_t i2cBus = Settings.getI2CInterfaceRTC(); + I2CInterfaceSelector(F("Ext. Time Source I2C Bus"), + F("pi2cbusrtc"), + i2cBus, + false); + } + #endif // if FEATURE_I2C_MULTIPLE + #endif + + addFormSubHeader(F("DST Settings")); + addFormDstSelect(true, Settings.DST_Start); + addFormDstSelect(false, Settings.DST_End); + addFormCheckBox(F("DST"), F("dst"), Settings.DST); + + addFormSubHeader(F("Location Settings")); + addFormNumericBox(F("Timezone Offset (UTC +)"), F("timezone"), Settings.TimeZone, -720, 840); // UTC-12H ... UTC+14h + addUnit(F("minutes")); + addFormFloatNumberBox(F("Latitude"), F("latitude"), Settings.Latitude, -90.0f, 90.0f); + addUnit(F("°")); + addFormFloatNumberBox(F("Longitude"), F("longitude"), Settings.Longitude, -180.0f, 180.0f); + addUnit(F("°")); + addFormNote(F("Longitude and Latitude are used to compute sunrise and sunset")); + + addFormSubHeader(F("Log Settings")); + + addFormIPBox(F("Syslog IP"), F("syslogip"), Settings.Syslog_IP); + addFormNumericBox(F("Syslog UDP port"), F("syslogport"), Settings.SyslogPort, 0, 65535); + + addFormLogLevelSelect(LabelType::SYSLOG_LOG_LEVEL, Settings.SyslogLevel); + addFormLogFacilitySelect(F("Syslog Facility"), F("syslogfacility"), Settings.SyslogFacility); + addFormLogLevelSelect(LabelType::SERIAL_LOG_LEVEL, Settings.SerialLogLevel); + addFormLogLevelSelect(LabelType::WEB_LOG_LEVEL, Settings.WebLogLevel); + +#if FEATURE_SD + addFormLogLevelSelect(LabelType::SD_LOG_LEVEL, Settings.SDLogLevel); + + addFormCheckBox(F("SD Card Value Logger"), F("valuelogger"), Settings.UseValueLogger); +#endif // if FEATURE_SD + + + addFormSubHeader(F("Serial Console Settings")); + addFormCheckBox(LabelType::ENABLE_SERIAL_PORT_CONSOLE, Settings.UseSerial); + addFormNumericBox(F("Baud Rate"), F("baudrate"), Settings.BaudRate, 0, 1000000); + +#if FEATURE_DEFINE_SERIAL_CONSOLE_PORT + serialHelper_webformLoad( + static_cast(Settings.console_serial_port), + Settings.console_serial_rxpin, + Settings.console_serial_txpin, + true); + + // Show serial port selection + addFormPinSelect( + PinSelectPurpose::Serial_input, + formatGpioName_serialRX(false), + F("taskdevicepin1"), + Settings.console_serial_rxpin); + addFormPinSelect( + PinSelectPurpose::Serial_output, + formatGpioName_serialTX(false), + F("taskdevicepin2"), + Settings.console_serial_txpin); + + html_add_script(F("document.getElementById('serPort').onchange();"), false); +#if USES_ESPEASY_CONSOLE_FALLBACK_PORT + addFormCheckBox(LabelType::CONSOLE_FALLBACK_TO_SERIAL0, Settings.console_serial0_fallback); +#endif + +#endif + + + addFormSubHeader(F("Inter-ESPEasy Network")); + if (Settings.UDPPort != 8266 ) addFormNote(F("Preferred P2P port is 8266")); + addFormNumericBox(F("ESPEasy p2p UDP port"), F("udpport"), Settings.UDPPort, 0, 65535); + + // TODO sort settings in groups or move to other pages/groups + addFormSubHeader(F("Special and Experimental Settings")); + + addFormNumericBox(F("Webserver port"), F("webport"), Settings.WebserverPort, 0, 65535); + addFormNote(F("Requires reboot to activate")); + + addFormNumericBox(F("Fixed IP Octet"), F("ip"), Settings.IP_Octet, 0, 255); + + addFormNumericBox(F("WD I2C Address"), F("wdi2caddress"), Settings.WDI2CAddress, 0, 127); + addHtml(F(" (decimal)")); + #if FEATURE_I2C_MULTIPLE + { + const uint8_t i2cBus = Settings.getI2CInterfaceWDT(); + I2CInterfaceSelector(F("WD I2C Bus"), + F("pi2cbuswdt"), + i2cBus, + false); + } + #endif // if FEATURE_I2C_MULTIPLE + + // TODO: Remove this code + addRowLabel(F("I2C ClockStretchLimit")); + addUnit(F("Moved to Hardware page")); + + #if FEATURE_ARDUINO_OTA + addFormCheckBox(F("Enable Arduino OTA"), F("arduinootaenable"), Settings.ArduinoOTAEnable); + #endif // if FEATURE_ARDUINO_OTA + #if defined(ESP32) + addFormCheckBox_disabled(F("Enable RTOS Multitasking"), F("usertosmultitasking"), Settings.UseRTOSMultitasking); + #endif // if defined(ESP32) + + addFormCheckBox(LabelType::JSON_BOOL_QUOTES, Settings.JSONBoolWithoutQuotes()); +#if FEATURE_TIMING_STATS + addFormCheckBox(LabelType::ENABLE_TIMING_STATISTICS, Settings.EnableTimingStats()); +#endif // if FEATURE_TIMING_STATS +#ifndef BUILD_NO_RAM_TRACKER + addFormCheckBox(LabelType::ENABLE_RAM_TRACKING, Settings.EnableRAMTracking()); +#endif + + addFormCheckBox(LabelType::TASKVALUESET_ALL_PLUGINS, Settings.AllowTaskValueSetAllPlugins()); +#if FEATURE_CLEAR_I2C_STUCK + addFormCheckBox(LabelType::ENABLE_CLEAR_HUNG_I2C_BUS, Settings.EnableClearHangingI2Cbus()); +#endif + #if FEATURE_I2C_DEVICE_CHECK + addFormCheckBox(LabelType::ENABLE_I2C_DEVICE_CHECK, Settings.CheckI2Cdevice()); + #endif // if FEATURE_I2C_DEVICE_CHECK + + #if FEATURE_TASKVALUE_UNIT_OF_MEASURE + addFormCheckBox(LabelType::SHOW_UOM_ON_DEVICES_PAGE, Settings.ShowUnitOfMeasureOnDevicesPage()); + #endif // if FEATURE_TASKVALUE_UNIT_OF_MEASURE + + # ifndef NO_HTTP_UPDATER + addFormCheckBox(LabelType::ALLOW_OTA_UNLIMITED, Settings.AllowOTAUnlimited()); + # endif // ifndef NO_HTTP_UPDATER + #if FEATURE_AUTO_DARK_MODE + const __FlashStringHelper * cssModeNames[] = { + F("Auto"), + F("Light"), + F("Dark"), + }; + //const int cssModeOptions[] = { 0, 1, 2}; + constexpr int nrCssModeOptions = NR_ELEMENTS(cssModeNames); + const FormSelectorOptions selector( + nrCssModeOptions, + cssModeNames/*, + cssModeOptions*/); + selector.addFormSelector( + getLabel(LabelType::ENABLE_AUTO_DARK_MODE), + getInternalLabel(LabelType::ENABLE_AUTO_DARK_MODE), + Settings.getCssMode()); + #endif // FEATURE_AUTO_DARK_MODE + + #if FEATURE_RULES_EASY_COLOR_CODE + addFormCheckBox(LabelType::DISABLE_RULES_AUTOCOMPLETE, Settings.DisableRulesCodeCompletion()); + #endif // if FEATURE_RULES_EASY_COLOR_CODE + #if FEATURE_TARSTREAM_SUPPORT + addFormCheckBox(LabelType::DISABLE_SAVE_CONFIG_AS_TAR, Settings.DisableSaveConfigAsTar()); + #endif // if FEATURE_TARSTREAM_SUPPORT + + #ifdef ESP8266 + addFormCheckBox(LabelType::DEEP_SLEEP_ALTERNATIVE_CALL, Settings.UseAlternativeDeepSleep()); + #endif + + + #if FEATURE_SSDP + addFormCheckBox_disabled(F("Use SSDP"), F("usessdp"), Settings.UseSSDP); + #endif // if FEATURE_SSDP + + addFormNumericBox(LabelType::CONNECTION_FAIL_THRESH, Settings.ConnectionFailuresThreshold, 0, 100); + addFormCheckBox(LabelType::FORCE_WIFI_BG, Settings.ForceWiFi_bg_mode()); + + addFormCheckBox(LabelType::RESTART_WIFI_LOST_CONN, Settings.WiFiRestart_connection_lost()); + addFormCheckBox(LabelType::FORCE_WIFI_NOSLEEP, Settings.WifiNoneSleep()); +#ifdef SUPPORT_ARP + addFormCheckBox(LabelType::PERIODICAL_GRAT_ARP, Settings.gratuitousARP()); +#endif // ifdef SUPPORT_ARP + addFormCheckBox(LabelType::CPU_ECO_MODE, Settings.EcoPowerMode()); +#if FEATURE_SET_WIFI_TX_PWR + addFormFloatNumberBox(LabelType::WIFI_TX_MAX_PWR, Settings.getWiFi_TX_power(), 0.0f, MAX_TX_PWR_DBM_11b, 2, 0.25f); + addFormNumericBox(LabelType::WIFI_SENS_MARGIN, Settings.WiFi_sensitivity_margin, -20, 30); + addFormCheckBox(LabelType::WIFI_SEND_AT_MAX_TX_PWR, Settings.UseMaxTXpowerForSending()); +#endif + { + addFormNumericBox(LabelType::WIFI_NR_EXTRA_SCANS, Settings.NumberExtraWiFiScans, 0, 5); + } + addFormCheckBox(LabelType::WIFI_USE_LAST_CONN_FROM_RTC, Settings.UseLastWiFiFromRTC()); + +#ifndef ESP32 + addFormCheckBox(LabelType::WAIT_WIFI_CONNECT, Settings.WaitWiFiConnect()); +#endif + addFormCheckBox(LabelType::SDK_WIFI_AUTORECONNECT, Settings.SDK_WiFi_autoreconnect()); + addFormCheckBox(LabelType::HIDDEN_SSID_SLOW_CONNECT, Settings.HiddenSSID_SlowConnectPerBSSID()); +#ifdef ESP32 + addFormCheckBox(LabelType::WIFI_PASSIVE_SCAN, Settings.PassiveWiFiScan()); +#endif +#if FEATURE_USE_IPV6 + addFormCheckBox(LabelType::ENABLE_IPV6, Settings.EnableIPv6()); +#endif + + + +#ifdef USES_ESPEASY_NOW + addFormCheckBox(LabelType::ESPEASY_NOW_ENABLED, Settings.UseESPEasyNow()); + { + addFormNumericBox(LabelType::ESPEASY_NOW_FORCED_CHANNEL, Settings.ForceESPEasyNOWchannel, 0, 14); + addFormNote(F("Force channel to use for " + ESPEASY_NOW_NAME + "-only mode (0 = use any channel)")); + } + +#endif + + addFormSeparator(2); + + html_TR_TD(); + html_TD(); + addSubmitButton(); + addHtml(F("")); + html_end_table(); + html_end_form(); + sendHeadandTail_stdtemplate(_TAIL); + TXBuffer.endStream(); +} + +void addFormDstSelect(bool isStart, uint16_t choice) { + uint16_t tmpstart(choice); + uint16_t tmpend(choice); + + if (!TimeChangeRule(choice, 0).isValid()) { + time_zone.getDefaultDst_flash_values(tmpstart, tmpend); + } + TimeChangeRule rule(isStart ? tmpstart : tmpend, 0); + { + const __FlashStringHelper * week[] = { F("Last"), F("1st"), F("2nd"), F("3rd"), F("4th") }; + constexpr int weekValues[] = { 0, 1, 2, 3, 4 }; + addRowLabel(concat( + isStart ? F("Start") : F("End"), + F(" (week, dow, month)"))); + + const FormSelectorOptions selector(NR_ELEMENTS(weekValues), week, weekValues); + selector.addSelector( + isStart ? F("dststartweek") : F("dstendweek"), + rule.week); + } + html_BR(); + { + const __FlashStringHelper * dow[] = { F("Sun"), F("Mon"), F("Tue"), F("Wed"), F("Thu"), F("Fri"), F("Sat") }; + constexpr int dowValues[] = { 1, 2, 3, 4, 5, 6, 7 }; + + const FormSelectorOptions selector(NR_ELEMENTS(dowValues), dow, dowValues); + selector.addSelector( + isStart ? F("dststartdow") : F("dstenddow"), + rule.dow); + } + html_BR(); + { + const __FlashStringHelper * month[] = { F("Jan"), F("Feb"), F("Mar"), F("Apr"), F("May"), F("Jun"), F("Jul"), F("Aug"), F("Sep"), F("Oct"), F("Nov"), F( + "Dec") }; + constexpr int monthValues[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 }; + + const FormSelectorOptions selector(NR_ELEMENTS(monthValues), month, monthValues); + selector.addSelector( + isStart ? F("dststartmonth") : F("dstendmonth"), + rule.month); + } + + addFormNumericBox( + isStart ? F("Start (localtime, e.g. 2h→3h)") : F("End (localtime, e.g. 3h→2h)"), + isStart ? F("dststarthour") : F("dstendhour"), + rule.hour, 0, 23); + addUnit(isStart ? F("hour ↷") : F("hour ↶")); +} + +void addFormExtTimeSourceSelect(const __FlashStringHelper * label, const __FlashStringHelper * id, ExtTimeSource_e choice) +{ + addRowLabel(label); + const __FlashStringHelper * options[] = + { F("None"), F("DS1307"), F("DS3231"), F("PCF8523"), F("PCF8563")}; + constexpr int optionValues[] = { + static_cast(ExtTimeSource_e::None), + static_cast(ExtTimeSource_e::DS1307), + static_cast(ExtTimeSource_e::DS3231), + static_cast(ExtTimeSource_e::PCF8523), + static_cast(ExtTimeSource_e::PCF8563) + }; + + const FormSelectorOptions selector(NR_ELEMENTS(optionValues), options, optionValues); + selector.addSelector(id, static_cast(choice)); +} + + +void addFormLogLevelSelect(LabelType::Enum label, int choice) +{ + #ifdef BUILD_NO_DEBUG + if (choice > LOG_LEVEL_INFO) choice = LOG_LEVEL_INFO; + #endif + + addRowLabel(getLabel(label)); + const __FlashStringHelper * options[LOG_LEVEL_NRELEMENTS + 1]; + int optionValues[LOG_LEVEL_NRELEMENTS + 1] = { 0 }; + + options[0] = getLogLevelDisplayString(0); + + for (int i = 0; i < LOG_LEVEL_NRELEMENTS; ++i) { + options[i + 1] = getLogLevelDisplayStringFromIndex(i, optionValues[i + 1]); + } + const FormSelectorOptions selector(LOG_LEVEL_NRELEMENTS + 1, options, optionValues); + selector.addSelector(getInternalLabel(label), choice); + +} + +void addFormLogFacilitySelect(const __FlashStringHelper * label, const __FlashStringHelper * id, int choice) +{ + addRowLabel(label); + const __FlashStringHelper * options[] = + { F("Kernel"), F("User"), F("Daemon"), F("Message"), F("Local0"), F("Local1"), + F("Local2"), F("Local3"), F("Local4"), F("Local5"), F("Local6"), F("Local7") }; + const int optionValues[] = { 0, 1, 3, 5, 16, 17, 18, 19, 20, 21, 22, 23 }; + + const FormSelectorOptions selector(NR_ELEMENTS(options), options, optionValues); + selector.addSelector(id, choice); +} + +#endif // ifdef WEBSERVER_ADVANCED diff --git a/src/src/WebServer/AdvancedConfigPage.h b/src/src/WebServer/AdvancedConfigPage.h index e1b26c23c4..421da1d2bb 100644 --- a/src/src/WebServer/AdvancedConfigPage.h +++ b/src/src/WebServer/AdvancedConfigPage.h @@ -1,30 +1,29 @@ -#ifndef WEBSERVER_WEBSERVER_ADVANCEDCONFIGPAGE_H -#define WEBSERVER_WEBSERVER_ADVANCEDCONFIGPAGE_H - -#include "../WebServer/common.h" - -#ifdef WEBSERVER_ADVANCED - -#include "../DataTypes/TimeSource.h" - - -// ******************************************************************************** -// Web Interface config page -// ******************************************************************************** -void handle_advanced(); - -void addFormDstSelect(bool isStart, uint16_t choice); - -void addFormExtTimeSourceSelect(const __FlashStringHelper * label, const __FlashStringHelper * id, ExtTimeSource_e choice); - -void addFormLogLevelSelect(LabelType::Enum label, int choice); - -void addFormLogFacilitySelect(const __FlashStringHelper * label, const __FlashStringHelper * id, int choice); - - -#endif // ifdef WEBSERVER_ADVANCED - - - - +#ifndef WEBSERVER_WEBSERVER_ADVANCEDCONFIGPAGE_H +#define WEBSERVER_WEBSERVER_ADVANCEDCONFIGPAGE_H + +#include "../WebServer/common.h" + +#ifdef WEBSERVER_ADVANCED + +#include "../DataTypes/TimeSource.h" + +// ******************************************************************************** +// Web Interface config page +// ******************************************************************************** +void handle_advanced(); + +void addFormDstSelect(bool isStart, uint16_t choice); + +void addFormExtTimeSourceSelect(const __FlashStringHelper * label, const __FlashStringHelper * id, ExtTimeSource_e choice); + +void addFormLogLevelSelect(LabelType::Enum label, int choice); + +void addFormLogFacilitySelect(const __FlashStringHelper * label, const __FlashStringHelper * id, int choice); + + +#endif // ifdef WEBSERVER_ADVANCED + + + + #endif \ No newline at end of file diff --git a/src/src/WebServer/ControllerPage.h b/src/src/WebServer/ControllerPage.h index bba1b881db..369e9e61e2 100644 --- a/src/src/WebServer/ControllerPage.h +++ b/src/src/WebServer/ControllerPage.h @@ -1,45 +1,44 @@ -#ifndef WEBSERVER_WEBSERVER_CONTROLLERPAGE_H -#define WEBSERVER_WEBSERVER_CONTROLLERPAGE_H - -#include "../WebServer/common.h" - -#ifdef WEBSERVER_CONTROLLERS - -#include "../DataStructs/ControllerSettingsStruct.h" - -#include "../Globals/CPlugins.h" - -// ******************************************************************************** -// Web Interface controller page -// ******************************************************************************** -void handle_controllers(); - -// ******************************************************************************** -// Selected controller has changed. -// Clear all Controller settings and load some defaults -// ******************************************************************************** -void handle_controllers_clearLoadDefaults(uint8_t controllerindex, ControllerSettingsStruct& ControllerSettings); - -// ******************************************************************************** -// Collect all submitted form data and store in the ControllerSettings -// ******************************************************************************** -void handle_controllers_CopySubmittedSettings(uint8_t controllerindex, ControllerSettingsStruct& ControllerSettings); - -void handle_controllers_CopySubmittedSettings_CPluginCall(uint8_t controllerindex); - -// ******************************************************************************** -// Show table with all selected controllers -// ******************************************************************************** -void handle_controllers_ShowAllControllersTable(); - -// ******************************************************************************** -// Show the controller settings page -// ******************************************************************************** -void handle_controllers_ControllerSettingsPage(controllerIndex_t controllerindex); - -#endif // ifdef WEBSERVER_CONTROLLERS - - - - +#ifndef WEBSERVER_WEBSERVER_CONTROLLERPAGE_H +#define WEBSERVER_WEBSERVER_CONTROLLERPAGE_H + +#include "../WebServer/common.h" + +#ifdef WEBSERVER_CONTROLLERS + +#include "../DataStructs/ControllerSettingsStruct.h" +#include "../Globals/CPlugins.h" + +// ******************************************************************************** +// Web Interface controller page +// ******************************************************************************** +void handle_controllers(); + +// ******************************************************************************** +// Selected controller has changed. +// Clear all Controller settings and load some defaults +// ******************************************************************************** +void handle_controllers_clearLoadDefaults(uint8_t controllerindex, ControllerSettingsStruct& ControllerSettings); + +// ******************************************************************************** +// Collect all submitted form data and store in the ControllerSettings +// ******************************************************************************** +void handle_controllers_CopySubmittedSettings(uint8_t controllerindex, ControllerSettingsStruct& ControllerSettings); + +void handle_controllers_CopySubmittedSettings_CPluginCall(uint8_t controllerindex); + +// ******************************************************************************** +// Show table with all selected controllers +// ******************************************************************************** +void handle_controllers_ShowAllControllersTable(); + +// ******************************************************************************** +// Show the controller settings page +// ******************************************************************************** +void handle_controllers_ControllerSettingsPage(controllerIndex_t controllerindex); + +#endif // ifdef WEBSERVER_CONTROLLERS + + + + #endif \ No newline at end of file diff --git a/src/src/WebServer/ESPEasy_WebServer.cpp b/src/src/WebServer/ESPEasy_WebServer.cpp index 5ba648ab3e..bc600abc82 100644 --- a/src/src/WebServer/ESPEasy_WebServer.cpp +++ b/src/src/WebServer/ESPEasy_WebServer.cpp @@ -1,1253 +1,1256 @@ -#include "../WebServer/ESPEasy_WebServer.h" - -#include "../WebServer/common.h" - -#include "../WebServer/404.h" -#include "../WebServer/AccessControl.h" -#include "../WebServer/AdvancedConfigPage.h" -#include "../WebServer/CacheControllerPages.h" -#include "../WebServer/ConfigPage.h" -#include "../WebServer/ControlPage.h" -#include "../WebServer/ControllerPage.h" -#include "../WebServer/CustomPage.h" -#include "../WebServer/DevicesPage.h" -#include "../WebServer/DownloadPage.h" -#include "../WebServer/FactoryResetPage.h" -#include "../WebServer/FileList.h" -#include "../WebServer/HTML_wrappers.h" -#include "../WebServer/HardwarePage.h" -#include "../WebServer/I2C_Scanner.h" -#include "../WebServer/JSON.h" -#include "../WebServer/LoadFromFS.h" -#include "../WebServer/Log.h" -#include "../WebServer/Markup.h" -#include "../WebServer/Markup_Buttons.h" -#include "../WebServer/Markup_Forms.h" -#include "../WebServer/NotificationPage.h" -#include "../WebServer/PinStates.h" -#include "../WebServer/RootPage.h" -#include "../WebServer/Rules.h" -#include "../WebServer/SettingsArchive.h" -#include "../WebServer/SetupPage.h" -#include "../WebServer/SysInfoPage.h" -#include "../WebServer/Metrics.h" -#include "../WebServer/SysVarPage.h" -#include "../WebServer/TimingStats.h" -#include "../WebServer/ToolsPage.h" -#include "../WebServer/UploadPage.h" -#include "../WebServer/WiFiScanner.h" - -#include "../WebServer/WebTemplateParser.h" - - -#include "../../ESPEasy-Globals.h" -#include "../../_Plugin_Helper.h" -#include "../../ESPEasy_common.h" - -#include "../CustomBuild/CompiletimeDefines.h" - -#include "../DataStructs/TimingStats.h" - -#include "../DataTypes/SettingsType.h" - -#include "../ESPEasyCore/ESPEasyNetwork.h" -#include "../ESPEasyCore/ESPEasyRules.h" -#include "../ESPEasyCore/ESPEasyWifi.h" - -#include "../Globals/CPlugins.h" -#include "../Globals/Device.h" -#include "../Globals/NetworkState.h" -#include "../Globals/SecuritySettings.h" -#include "../Globals/Settings.h" - -#include "../Helpers/ESPEasy_Storage.h" -#include "../Helpers/Hardware_device_info.h" -#include "../Helpers/Networking.h" -#include "../Helpers/OTA.h" -#include "../Helpers/StringConverter.h" - -#include "../Static/WebStaticData.h" - -#include - - -void safe_strncpy_webserver_arg(char *dest, const String& arg, size_t max_size) { - if (hasArg(arg)) { - safe_strncpy(dest, webArg(arg).c_str(), max_size); - } -} - -void safe_strncpy_webserver_arg(char *dest, const __FlashStringHelper *arg, size_t max_size) { - safe_strncpy_webserver_arg(dest, String(arg), max_size); -} - -void sendHeadandTail(const __FlashStringHelper *tmplName, bool Tail, bool rebooting) { - // This function is called twice per serving a web page. - // So it must keep track of the timer longer than the scope of this function. - // Therefore use a local static variable. - #if FEATURE_TIMING_STATS - static uint64_t statisticsTimerStart = 0; - - if (!Tail) { - statisticsTimerStart = getMicros64(); - } - #endif // if FEATURE_TIMING_STATS - { - const String fileName = concat(tmplName, F(".htm")); - fs::File f = tryOpenFile(fileName, "r"); - - WebTemplateParser templateParser(Tail, rebooting); - - if (f) { - bool success = true; - - while (f.available() && success) { - success = templateParser.process((char)f.read()); - } - f.close(); - } else { - getWebPageTemplateDefault(tmplName, templateParser); - } - #ifndef BUILD_NO_RAM_TRACKER - checkRAM(F("sendWebPage")); - #endif // ifndef BUILD_NO_RAM_TRACKER - - // web activity timer - lastWeb = millis(); - } - - if (shouldReboot) { - // we only add this here as a seperate chunk to prevent using too much memory at once - serve_JS(JSfiles_e::Reboot); - } - STOP_TIMER(HANDLE_SERVING_WEBPAGE); -} - -void sendHeadandTail_stdtemplate(bool Tail, bool rebooting) { - sendHeadandTail(F("TmplStd"), Tail, rebooting); - - if (!Tail) { - if (!clientIPinSubnet() && WifiIsAP(WiFi.getMode()) && (WiFi.softAPgetStationNum() > 0)) { - addHtmlError(F("Warning: Connected via AP")); - } - - #ifndef BUILD_NO_DEBUG - - /* - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - const int nrArgs = web_server.args(); - - if (nrArgs > 0) { - String log = F(" Webserver "); - log += nrArgs; - log += F(" Arguments"); - - if (nrArgs > 20) { - log += F(" (First 20)"); - } - log += ':'; - - for (int i = 0; i < nrArgs && i < 20; ++i) { - log += ' '; - log += i; - log += F(": '"); - log += web_server.argName(i); - log += F("' length: "); - log += webArg(i).length(); - } - addLogMove(LOG_LEVEL_INFO, log); - } - } - */ - #endif // ifndef BUILD_NO_DEBUG - } - - // We have sent a lot of data at once. - // try to flush it to the connected client to free up some RAM - // from pending transfers -// TXBuffer.flush(); -// delay(10); -} - -bool captivePortal() { - const IPAddress client_localIP = web_server.client().localIP(); - const bool fromAP = client_localIP == apIP; - const bool hasWiFiCredentials = SecuritySettings.hasWiFiCredentials(); - - if (hasWiFiCredentials || !fromAP) { - return false; - } - - if (!isIP(web_server.hostHeader()) && (web_server.hostHeader() != (NetworkGetHostname() + F(".local")))) { - String redirectURL = concat(F("http://"), formatIP(client_localIP)); - #ifdef WEBSERVER_SETUP - - if (fromAP && !hasWiFiCredentials) { - redirectURL += F("/setup"); - } - #endif // ifdef WEBSERVER_SETUP - sendHeader(F("Location"), redirectURL, true); - web_server.send(302, F("text/plain"), EMPTY_STRING); // Empty content inhibits Content-length header so we have to close the socket - // ourselves. - web_server.client().stop(); // Stop is needed because we sent no content length - return true; - } - return false; -} - -// ******************************************************************************** -// Web Interface init -// ******************************************************************************** -// #include "core_version.h" - - -void WebServerInit() -{ - if (webserver_init) { return; } - webserver_init = true; - - // Prepare webserver pages - #ifdef WEBSERVER_ROOT - web_server.on(F("/"), handle_root); - - // Entries for several captive portal URLs. - // Maybe not needed. Might be handled by notFound handler. - web_server.on(UriGlob("/generate_204*"), handle_root); // Android captive portal. Handle "/generate_204_"-like requests. - web_server.on(F("/fwlink"), handle_root); // Microsoft captive portal. - #endif // ifdef WEBSERVER_ROOT - #ifdef WEBSERVER_ADVANCED - web_server.on(F("/advanced"), handle_advanced); - # if defined(WEBSERVER_DOWNLOAD) && FEATURE_TARSTREAM_SUPPORT - web_server.on(F("/backup"), handle_full_backup); - web_server.on(F("/backupnup"), handle_full_backup_no_usr_pwd); - # endif // if defined(WEBSERVER_DOWNLOAD) && FEATURE_TARSTREAM_SUPPORT - #endif // ifdef WEBSERVER_ADVANCED - #ifdef WEBSERVER_CONFIG - web_server.on(F("/config"), handle_config); - #endif // ifdef WEBSERVER_CONFIG - #ifdef WEBSERVER_CONTROL - web_server.on(F("/control"), handle_control); - #endif // ifdef WEBSERVER_CONTROL - #ifdef WEBSERVER_CONTROLLERS - web_server.on(F("/controllers"), handle_controllers); - #endif // ifdef WEBSERVER_CONTROLLERS - #ifdef WEBSERVER_DEVICES - web_server.on(F("/devices"), handle_devices); - #endif // ifdef WEBSERVER_DEVICES - #ifdef WEBSERVER_DOWNLOAD - web_server.on(F("/download"), handle_download); - #endif // ifdef WEBSERVER_DOWNLOAD - -#ifdef USES_C016 - - web_server.on(F("/dumpcache"), handle_dumpcache); // C016 specific entrie - web_server.on(F("/cache_json"), handle_cache_json); // C016 specific entrie - web_server.on(F("/cache_csv"), handle_cache_csv); // C016 specific entrie -#endif // USES_C016 - - #ifdef WEBSERVER_FACTORY_RESET - web_server.on(F("/factoryreset"), handle_factoryreset); - #endif // ifdef WEBSERVER_FACTORY_RESET - #if FEATURE_SETTINGS_ARCHIVE - web_server.on(F("/settingsarchive"), handle_settingsarchive); - #endif // if FEATURE_SETTINGS_ARCHIVE - #ifdef WEBSERVER_FILELIST - web_server.on(F("/filelist"), handle_filelist); - #endif // ifdef WEBSERVER_FILELIST - #ifdef WEBSERVER_HARDWARE - web_server.on(F("/hardware"), handle_hardware); - #endif // ifdef WEBSERVER_HARDWARE - #ifdef WEBSERVER_I2C_SCANNER - web_server.on(F("/i2cscanner"), handle_i2cscanner); - #endif // ifdef WEBSERVER_I2C_SCANNER - web_server.on(F("/json"), handle_json); // Also part of WEBSERVER_NEW_UI - #ifdef WEBSERVER_CSVVAL - web_server.on(F("/csv"), handle_csvval); - #endif - web_server.on(F("/log"), handle_log); - web_server.on(F("/logjson"), handle_log_JSON); // Also part of WEBSERVER_NEW_UI -#if FEATURE_NOTIFIER - web_server.on(F("/notifications"), handle_notifications); -#endif // if FEATURE_NOTIFIER - #ifdef WEBSERVER_PINSTATES - web_server.on(F("/pinstates"), handle_pinstates); - #endif // ifdef WEBSERVER_PINSTATES - #ifdef WEBSERVER_RULES - web_server.on(F("/rules"), handle_rules_new); - web_server.on(F("/rules/"), Goto_Rules_Root); - # ifdef WEBSERVER_NEW_RULES - web_server.on(F("/rules/add"), []() - { - handle_rules_edit(web_server.uri(), true); - }); - web_server.on(F("/rules/backup"), handle_rules_backup); - web_server.on(F("/rules/delete"), handle_rules_delete); - # endif // WEBSERVER_NEW_RULES - #endif // WEBSERVER_RULES -#if FEATURE_SD - web_server.on(F("/SDfilelist"), handle_SDfilelist); -#endif // if FEATURE_SD -#ifdef WEBSERVER_SETUP - web_server.on(F("/setup"), handle_setup); -#endif // ifdef WEBSERVER_SETUP -#ifdef WEBSERVER_SYSINFO - web_server.on(F("/sysinfo"), handle_sysinfo); -#endif // ifdef WEBSERVER_SYSINFO -#ifdef WEBSERVER_METRICS - web_server.on(F("/metrics"), handle_metrics); -#endif // ifdef WEBSERVER_METRICS -#ifdef WEBSERVER_SYSVARS - web_server.on(F("/sysvars"), handle_sysvars); -#endif // WEBSERVER_SYSVARS -#ifdef WEBSERVER_TIMINGSTATS - web_server.on(F("/timingstats"), handle_timingstats); -#endif // WEBSERVER_TIMINGSTATS -#ifdef WEBSERVER_TOOLS - web_server.on(F("/tools"), handle_tools); -#endif // ifdef WEBSERVER_TOOLS -#ifdef WEBSERVER_UPLOAD - web_server.on(F("/upload"), HTTP_GET, handle_upload); - web_server.on(F("/upload"), HTTP_POST, handle_upload_post, handleFileUpload); -#endif // ifdef WEBSERVER_UPLOAD -#if FEATURE_SD - web_server.on(F("/uploadsd"), HTTP_GET, handle_upload); - web_server.on(F("/uploadsd"), HTTP_POST, handle_upload_post, handleSDFileUpload); -#endif // if FEATURE_SD -#ifdef WEBSERVER_WIFI_SCANNER - web_server.on(F("/wifiscanner"), handle_wifiscanner); -#endif // ifdef WEBSERVER_WIFI_SCANNER - -#ifdef WEBSERVER_NEW_UI - web_server.on(F("/buildinfo"), handle_buildinfo); // Also part of WEBSERVER_NEW_UI - web_server.on(F("/factoryreset_json"), handle_factoryreset_json); - web_server.on(F("/filelist_json"), handle_filelist_json); - web_server.on(F("/i2cscanner_json"), handle_i2cscanner_json); - # if FEATURE_ESPEASY_P2P - web_server.on(F("/node_list_json"), handle_nodes_list_json); - # endif // if FEATURE_ESPEASY_P2P - web_server.on(F("/pinstates_json"), handle_pinstates_json); - web_server.on(F("/timingstats_json"), handle_timingstats_json); - web_server.on(F("/upload_json"), HTTP_POST, handle_upload_json, handleFileUpload); - web_server.on(F("/wifiscanner_json"), handle_wifiscanner_json); -#endif // WEBSERVER_NEW_UI -#if SHOW_SYSINFO_JSON - web_server.on(F("/sysinfo_json"), handle_sysinfo_json); -#endif// SHOW_SYSINFO_JSON - - web_server.onNotFound(handleNotFound); - - // List of headers to be recorded - // "If-None-Match" is used to see whether we need to serve a static file, or simply can reply with a 304 (not modified) - const char *headerkeys[] = { "If-None-Match" }; - constexpr size_t headerkeyssize = NR_ELEMENTS(headerkeys); - web_server.collectHeaders(headerkeys, headerkeyssize); - #if defined(ESP8266) || defined(ESP32) - { - # ifndef NO_HTTP_UPDATER - uint32_t maxSketchSize; - bool use2step; - - // allow OTA to smaller version of ESPEasy/other firmware - if (Settings.AllowOTAUnlimited() || OTA_possible(maxSketchSize, use2step)) { - httpUpdater.setup(&web_server); - } - # endif // ifndef NO_HTTP_UPDATER - } - #endif // if defined(ESP8266) - - #if defined(ESP8266) - - # if FEATURE_SSDP - - if (Settings.UseSSDP) - { - web_server.on(F("/ssdp.xml"), HTTP_GET, []() { - # ifdef MUSTFIX_CLIENT_TIMEOUT_IN_SECONDS - - // See: https://github.com/espressif/arduino-esp32/pull/6676 - web_server.client().setTimeout((CONTROLLER_CLIENTTIMEOUT_DFLT + 500) / 1000); // in seconds!!!! - Client& pClient = web_server.client(); - pClient.setTimeout(CONTROLLER_CLIENTTIMEOUT_DFLT); - # else // ifdef MUSTFIX_CLIENT_TIMEOUT_IN_SECONDS - web_server.client().setTimeout(CONTROLLER_CLIENTTIMEOUT_DFLT); // in msec as it should be! - # endif // ifdef MUSTFIX_CLIENT_TIMEOUT_IN_SECONDS - - SSDP_schema(); - }); - SSDP_begin(); - } - # endif // if FEATURE_SSDP - #endif // if defined(ESP8266) -} - -void setWebserverRunning(bool state) { - if (webserverRunning == state) { - return; - } - - if (state) { - WebServerInit(); - web_server.begin(Settings.WebserverPort); - #ifndef BUILD_MINIMAL_OTA - addLog(LOG_LEVEL_INFO, F("Webserver: start")); - #endif - } else { - web_server.client().stop(); - web_server.stop(); - #ifndef BUILD_MINIMAL_OTA - addLog(LOG_LEVEL_INFO, F("Webserver: stop")); - #endif - } - webserverRunning = state; - CheckRunningServices(); // Uses webserverRunning state. -} - -void getWebPageTemplateDefault(const String& tmplName, WebTemplateParser& parser) -{ - const bool addJS = true; - const bool addMeta = true; - - /* - if (equals(tmplName, F("TmplAP"))) - { - - getWebPageTemplateDefaultHead(parser, addMeta, !addJS); - - if (!parser.isTail()) { - #ifndef WEBPAGE_TEMPLATE_AP_HEADER - parser.process(F("
" - "

Welcome to ESP Easy Mega AP

")); - #else - parser.process(F(WEBPAGE_TEMPLATE_AP_HEADER)); - #endif - - parser.process(F("
")); - } - getWebPageTemplateDefaultContentSection(parser); - getWebPageTemplateDefaultFooter(parser); - } - else - */ - if (equals(tmplName, F("TmplMsg"))) - { - getWebPageTemplateDefaultHead(parser, !addMeta, !addJS); - - if (!parser.isTail()) { - parser.process(F("")); - } - getWebPageTemplateDefaultHeader(parser, F("{{name}}"), false); - getWebPageTemplateDefaultContentSection(parser); - getWebPageTemplateDefaultFooter(parser); - } - else if (equals(tmplName, F("TmplDsh"))) - { - getWebPageTemplateDefaultHead(parser, !addMeta, addJS); - parser.process(F("" - "{{content}}" - "")); - } - else // all other template names e.g. TmplStd - { - getWebPageTemplateDefaultHead(parser, addMeta, addJS); - - if (!parser.isTail()) { - parser.process(F("")); - } - getWebPageTemplateDefaultHeader(parser, F("{{name}} {{logo}}"), true); - getWebPageTemplateDefaultContentSection(parser); - getWebPageTemplateDefaultFooter(parser); - } - - // addLog(LOG_LEVEL_INFO, concat(F("tmpl.length(): "), tmpl.length())); -} - -void getWebPageTemplateDefaultHead(WebTemplateParser& parser, bool addMeta, bool addJS) { - if (parser.isTail()) { return; } - parser.process(F("" - "" - "" - "" - "{{name}}")); - - if (addMeta) { parser.process(F("{{meta}}")); } - - if (addJS) { parser.process(F("{{js}}")); } - - parser.process(F("{{css}}" - "")); -} - -void getWebPageTemplateDefaultHeader(WebTemplateParser& parser, const __FlashStringHelper *title, bool addMenu) { - { - if (parser.isTail()) { return; } - #ifndef WEBPAGE_TEMPLATE_DEFAULT_HEADER - parser.process(F("

ESP Easy Mega: ")); - parser.process(title); - # if BUILD_IN_WEBHEADER - parser.process(F( - "
Build: " GITHUB_RELEASES_LINK_PREFIX "{{date}}" GITHUB_RELEASES_LINK_SUFFIX - "
")); - # endif // #if BUILD_IN_WEBHEADER - parser.process(F("


")); - #else // ifndef WEBPAGE_TEMPLATE_DEFAULT_HEADER - String tmp = F(WEBPAGE_TEMPLATE_DEFAULT_HEADER); - tmp.replace(F("{{title}}"), title); - parser.process(tmp); - #endif // ifndef WEBPAGE_TEMPLATE_DEFAULT_HEADER - } - - if (addMenu) { parser.process(F("{{menu}}")); } - parser.process(F("
")); -} - -void getWebPageTemplateDefaultContentSection(WebTemplateParser& parser) { - parser.process(F("
" - "" - "{{error}}" - "" - "{{content}}" - "
" - )); -} - -void getWebPageTemplateDefaultFooter(WebTemplateParser& parser) { - if (!parser.isTail()) { return; } - #ifndef WEBPAGE_TEMPLATE_DEFAULT_FOOTER - parser.process(F("
" - "
" - "
Powered by Let's Control It community" - # if BUILD_IN_WEBFOOTER - "
Build: " GITHUB_RELEASES_LINK_PREFIX "{{build}} {{date}}" GITHUB_RELEASES_LINK_SUFFIX "
" - # endif // #if BUILD_IN_WEBFOOTER - "
" - "
" - "" - )); -#else // ifndef WEBPAGE_TEMPLATE_DEFAULT_FOOTER - parser.process(F(WEBPAGE_TEMPLATE_DEFAULT_FOOTER)); -#endif // ifndef WEBPAGE_TEMPLATE_DEFAULT_FOOTER -} - -void writeDefaultCSS(void) -{ - return; // TODO - - /* - #ifndef WEBSERVER_USE_CDN_JS_CSS - - if (!fileExists(F("esp.css"))) - { - fs::File f = tryOpenFile(F("esp.css"), "w"); - - if (f) - { - String defaultCSS; - defaultCSS = PGMT(DATA_ESPEASY_DEFAULT_MIN_CSS); - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("CSS : Writing default CSS file to FS ("); - log += defaultCSS.length(); - log += F(" bytes)"); - addLog(LOG_LEVEL_INFO, log); - } - f.write((const unsigned char *)defaultCSS.c_str(), defaultCSS.length()); // note: content must be in RAM - a write of F("XXX") does - // not work - f.close(); - } - } - #endif - */ -} - -// ******************************************************************************** -// Functions to stream JSON directly to TXBuffer -// FIXME TD-er: replace stream_xxx_json_object* into this code. -// N.B. handling of numerical values differs (string vs. no string) -// ******************************************************************************** - -int8_t level = 0; -int8_t lastLevel = -1; - -void json_quote_name(const __FlashStringHelper *val) { - json_quote_name(String(val)); -} - -void json_quote_name(const String& val) { - if (lastLevel == level) { - addHtml(','); - } - - if (val.length() > 0) { - json_quote_val(val); - addHtml(':'); - } -} - -void json_quote_val(const String& val) { - addHtml('\"'); - addHtml(val); - addHtml('\"'); -} - -void json_open(bool arr) { - json_open(arr, EMPTY_STRING); -} - -void json_open(bool arr, const __FlashStringHelper *name) { - json_quote_name(name); - addHtml(arr ? '[' : '{'); - lastLevel = level; - level++; -} - -void json_open(bool arr, const String& name) { - json_quote_name(name); - addHtml(arr ? '[' : '{'); - lastLevel = level; - level++; -} - -void json_init() { - level = 0; - lastLevel = -1; -} - -void json_close() { - json_close(false); -} - -void json_close(bool arr) { - addHtml(arr ? ']' : '}'); - level--; - lastLevel = level; -} - -void json_number(const __FlashStringHelper *name, const String& value) -{ - json_prop(name, value); -} - -void json_number(const String& name, const String& value) { - json_prop(name, value); -} - -void json_prop(const __FlashStringHelper *name, const String& value) -{ - json_quote_name(name); - json_quote_val(value); - lastLevel = level; -} - -void json_prop(const String& name, const String& value) { - json_quote_name(name); - json_quote_val(value); - lastLevel = level; -} - -void json_prop(LabelType::Enum label) { - json_prop(getInternalLabel(label, '-'), getValue(label)); -} - -// ******************************************************************************** -// Add a task select dropdown list -// This allows to select a task index based on the existing tasks. -// ******************************************************************************** -void addTaskSelect(const String& name, taskIndex_t choice, const String& cssclass) -{ - String deviceName; - - addHtml(F("'); - - const uint8_t valueCount = getValueCountForTask(TaskIndex); - - for (uint8_t x = 0; x < valueCount; x++) - { - addHtml(F("")); - } -} - -// ******************************************************************************** -// Login state check -// ******************************************************************************** -bool isLoggedIn(bool mustProvideLogin) -{ - if (!clientIPallowed()) { return false; } - - if (SecuritySettings.Password[0] == 0) { return true; } - - if (!mustProvideLogin) { - return false; - } - - { - String www_username = F(DEFAULT_ADMIN_USERNAME); - - if (!web_server.authenticate(www_username.c_str(), SecuritySettings.Password)) - - // Basic Auth Method with Custom realm and Failure Response - // return server.requestAuthentication(BASIC_AUTH, www_realm, authFailResponse); - // Digest Auth Method with realm="Login Required" and empty Failure Response - // return server.requestAuthentication(DIGEST_AUTH); - // Digest Auth Method with Custom realm and empty Failure Response - // return server.requestAuthentication(DIGEST_AUTH, www_realm); - // Digest Auth Method with Custom realm and Failure Response - { -#ifdef CORE_PRE_2_5_0 - - // See https://github.com/esp8266/Arduino/issues/4717 - HTTPAuthMethod mode = BASIC_AUTH; -#else // ifdef CORE_PRE_2_5_0 - HTTPAuthMethod mode = DIGEST_AUTH; -#endif // ifdef CORE_PRE_2_5_0 - String message = F("Login Required (default user: "); - message += www_username; - message += ')'; - web_server.requestAuthentication(mode, message.c_str()); - - if (Settings.UseRules) - { - String event = F("Login#Failed"); - - // TD-er: Do not add to the eventQueue, but execute right now. - rulesProcessing(event); - } - - return false; - } - } - return true; -} - -String getControllerSymbol(uint8_t index) -{ - String ret = F("&#"); - - ret += 10102 + index; - ret += F(";"); - return ret; -} - -/* - String getValueSymbol(uint8_t index) - { - String ret = F("&#"); - ret += 10112 + index; - ret += ';'; - return ret; - } - */ -void addSVG_param(const char key, int value) -{ - addHtml(strformat(F(" %c=\"%d\""), key, value)); -} - -void addSVG_param(const char key, float value) -{ - addSVG_param(key, toString(value, 2)); -} - -void addSVG_param(const char key, const String& value) -{ - addHtml(strformat(F(" %c=\"%s\""), key, value.c_str())); -} - -void addSVG_param(const __FlashStringHelper *key, int value) { - addHtml(strformat(F(" %s=\"%d\""), key, value)); -} - -void addSVG_param(const __FlashStringHelper *key, float value) { - addSVG_param(key, toString(value, 2)); -} - -void addSVG_param(const __FlashStringHelper *key, const String& value) { - addHtml(strformat(F(" %s=\"%s\""), key, value.c_str())); -} - -void createSvgRect_noStroke(const __FlashStringHelper *classname, - unsigned int fillColor, - float xoffset, - float yoffset, - float width, - float height, - float rx, - float ry) { - createSvgRect(classname, fillColor, fillColor, xoffset, yoffset, width, height, 0, rx, ry); -} - -void createSvgRect(const String& classname, - unsigned int fillColor, - unsigned int strokeColor, - float xoffset, - float yoffset, - float width, - float height, - float strokeWidth, - float rx, - float ry) { - addHtml(F("")); -} - -void createSvgHorRectPath(unsigned int color, int xoffset, int yoffset, int size, int height, int range, float SVG_BAR_WIDTH) { - if (range == 0) { - range = 1; - } - float width = (SVG_BAR_WIDTH * size) / range; - - if (width < 2) { width = 2; } - addHtml(formatToHex(color, F("\n")); -} - -void createSvgTextElement(const String& text, float textXoffset, float textYoffset) { - addHtml(F("', '\n'); - - addHtml(F("'); - - addHtml(text); - addHtml(F("\n")); -} - -#define SVG_BAR_HEIGHT 16 -#define SVG_BAR_WIDTH 400 - -void write_SVG_image_header(int width, int height, bool useViewbox) { - addHtml(F("'); - addHtml(F("")); -} - -/* - void getESPeasyLogo(int width_pixels) { - write_SVG_image_header(width_pixels, width_pixels, true); - addHtml(F(""); - } - */ -void getWiFi_RSSI_icon(int rssi, int width_pixels) -{ - const int nbars_filled = (rssi + 100) / 8; - const int nbars = 5; - int white_between_bar = (static_cast(width_pixels) / nbars) * 0.2f; - - if (white_between_bar < 1) { white_between_bar = 1; } - const int barWidth = (width_pixels - (nbars - 1) * white_between_bar) / nbars; - const int svg_width_pixels = nbars * barWidth + (nbars - 1) * white_between_bar; - - write_SVG_image_header(svg_width_pixels, svg_width_pixels, true); - const float scale = 100.0f / svg_width_pixels; - const int bar_height_step = 100 / nbars; - - for (int i = 0; i < nbars; ++i) { - const unsigned int color = i < nbars_filled ? 0x07d : 0xBFa1a1a1; // Blue/Grey75% - const int barHeight = (i + 1) * bar_height_step; - createSvgRect_noStroke(i < nbars_filled ? F("bar_highlight") : F("bar_dimmed"), - color, - i * (barWidth + white_between_bar) * scale, - 100 - barHeight, - barWidth, - barHeight, - 0, - 0); - } - addHtml(F("\n")); -} - -#if FEATURE_CHART_STORAGE_LAYOUT -void getConfig_dat_file_layout() { - const int shiftY = 2; - float yOffset = shiftY; - - write_SVG_image_header(SVG_BAR_WIDTH + 250, SVG_BAR_HEIGHT + shiftY); - - int max_index, offset, max_size{}; - int struct_size = 0; - - // background - const uint32_t realSize = SettingsType::getFileSize(SettingsType::Enum::TaskSettings_Type); - - createSvgHorRectPath(0xcdcdcd, 0, yOffset, realSize, SVG_BAR_HEIGHT - 2, realSize, SVG_BAR_WIDTH); - - for (int st = 0; st < static_cast(SettingsType::Enum::SettingsType_MAX); ++st) { - SettingsType::Enum settingsType = static_cast(st); - - if (SettingsType::getSettingsFile(settingsType) == SettingsType::SettingsFileEnum::FILE_CONFIG_type) { - unsigned int color = SettingsType::getSVGcolor(settingsType); - - if (SettingsType::getSettingsParameters(settingsType, 0, max_index, offset, max_size, struct_size)) - { - for (int i = 0; i < max_index; ++i) { - if (SettingsType::getSettingsParameters(settingsType, i, offset, max_size)) { - // Struct position - createSvgHorRectPath(color, offset, yOffset, max_size, SVG_BAR_HEIGHT - 2, realSize, SVG_BAR_WIDTH); - } - } - } - } - } - - // Text labels - constexpr float textXoffset = SVG_BAR_WIDTH + 2; - float textYoffset = yOffset + 0.9f * SVG_BAR_HEIGHT; - - createSvgTextElement(SettingsType::getSettingsFileName(SettingsType::Enum::TaskSettings_Type), textXoffset, textYoffset); - addHtml(F("\n")); -} - -void getStorageTableSVG(SettingsType::Enum settingsType) { - uint32_t realSize = SettingsType::getFileSize(settingsType); - unsigned int color = SettingsType::getSVGcolor(settingsType); - const int shiftY = 2; - - int max_index, offset, max_size{}; - int struct_size = 0; - - if (!SettingsType::getSettingsParameters(settingsType, 0, max_index, offset, max_size, struct_size)) - { - return; - } - - if (max_index == 0) { return; } - - // One more to add bar indicating struct size vs. reserved space. - write_SVG_image_header(SVG_BAR_WIDTH + 250, (max_index + 1) * SVG_BAR_HEIGHT + shiftY); - float yOffset = shiftY; - - for (int i = 0; i < max_index; ++i) { - if (SettingsType::getSettingsParameters(settingsType, i, offset, max_size)) { - // background - createSvgHorRectPath(0xcdcdcd, 0, yOffset, realSize, SVG_BAR_HEIGHT - 2, realSize, SVG_BAR_WIDTH); - - // Struct position - createSvgHorRectPath(color, offset, yOffset, max_size, SVG_BAR_HEIGHT - 2, realSize, SVG_BAR_WIDTH); - - // Text labels - float textXoffset = SVG_BAR_WIDTH + 2; - float textYoffset = yOffset + 0.9f * SVG_BAR_HEIGHT; - createSvgTextElement(formatHumanReadable(offset, 1024), textXoffset, textYoffset); - textXoffset = SVG_BAR_WIDTH + 60; - createSvgTextElement(formatHumanReadable(max_size, 1024), textXoffset, textYoffset); - textXoffset = SVG_BAR_WIDTH + 130; - createSvgTextElement(String(i), textXoffset, textYoffset); - yOffset += SVG_BAR_HEIGHT; - } - } - - // usage - createSvgHorRectPath(0xcdcdcd, 0, yOffset, max_size, SVG_BAR_HEIGHT - 2, max_size, SVG_BAR_WIDTH); - - // Struct size (used part of the reserved space) - if (struct_size != 0) { - createSvgHorRectPath(color, 0, yOffset, struct_size, SVG_BAR_HEIGHT - 2, max_size, SVG_BAR_WIDTH); - } - - // Text labels - float textXoffset = SVG_BAR_WIDTH + 2; - float textYoffset = yOffset + 0.9f * SVG_BAR_HEIGHT; - - if (struct_size != 0) { - String text = strformat( - F("%s/%s per item"), - formatHumanReadable(struct_size, 1024).c_str(), - formatHumanReadable(max_size, 1024).c_str()); - createSvgTextElement(text, textXoffset, textYoffset); - } else { - createSvgTextElement(F("Variable size"), textXoffset, textYoffset); - } - addHtml(F("\n")); -} - -void drawPartitionChartSVG( - float yOffset, - uint32_t realSize, - uint32_t partitionAddress, - uint32_t partitionSize, - unsigned int partitionColor, - const String& label, - const String& name) -{ - createSvgHorRectPath(0xcdcdcd, 0, yOffset, realSize, SVG_BAR_HEIGHT - 2, realSize, SVG_BAR_WIDTH); - createSvgHorRectPath(partitionColor, partitionAddress, yOffset, partitionSize, SVG_BAR_HEIGHT - 2, realSize, SVG_BAR_WIDTH); - float textXoffset = SVG_BAR_WIDTH + 2; - const float textYoffset = yOffset + 0.9f * SVG_BAR_HEIGHT; - createSvgTextElement(formatHumanReadable(partitionSize, 1024), textXoffset, textYoffset); - textXoffset = SVG_BAR_WIDTH + 60; - createSvgTextElement(label, textXoffset, textYoffset); - textXoffset = SVG_BAR_WIDTH + 130; - createSvgTextElement(name, textXoffset, textYoffset); -} - -# ifdef ESP32 - -# include - - -void getPartitionTableSVG(uint8_t pType, unsigned int partitionColor) { - int nrPartitions = getPartionCount(pType); - - if (nrPartitions == 0) { return; } - const int shiftY = 2; - - uint32_t realSize = getFlashRealSizeInBytes(); - esp_partition_type_t partitionType = static_cast(pType); - const esp_partition_t *_mypart; - esp_partition_iterator_t _mypartiterator = esp_partition_find(partitionType, ESP_PARTITION_SUBTYPE_ANY, nullptr); - - write_SVG_image_header(SVG_BAR_WIDTH + 250, nrPartitions * SVG_BAR_HEIGHT + shiftY); - float yOffset = shiftY; - - if (_mypartiterator) { - do { - _mypart = esp_partition_get(_mypartiterator); - drawPartitionChartSVG( - yOffset, - realSize, - _mypart->address, - _mypart->size, - partitionColor, - _mypart->label, - getPartitionType(_mypart->type, _mypart->subtype)); - yOffset += SVG_BAR_HEIGHT; - } while ((_mypartiterator = esp_partition_next(_mypartiterator)) != nullptr); - } - addHtml(F("\n")); - esp_partition_iterator_release(_mypartiterator); -} - -# endif // ifdef ESP32 - -# ifdef ESP8266 -void getPartitionTableSVG() { - // sketch / OTA / FS / EEPROM / RFcal / wifi - const int nrPartitions = 6; - const int shiftY = 2; - - write_SVG_image_header(SVG_BAR_WIDTH + 250, nrPartitions * SVG_BAR_HEIGHT + shiftY); - float yOffset = shiftY; - - for (int i = 0; i < nrPartitions; ++i) { - const ESP8266_partition_type ptype = static_cast(i); - uint32_t partitionAddress = 0; - int32_t partitionSize = 0; - const int32_t partitionSector = getPartitionInfo(ptype, partitionAddress, partitionSize); - - const __FlashStringHelper *label = F(""); - String descr; - unsigned int partitionColor = 0xab56e6; - - switch (ptype) { - case ESP8266_partition_type::sketch: - label = F("sketch"); - partitionColor = 0xab56e6; - break; - case ESP8266_partition_type::ota: - label = F("ota"); - partitionColor = 0x5856e6; - break; - case ESP8266_partition_type::fs: - label = F("fs"); - partitionColor = 0xff7f00; - # ifdef USE_LITTLEFS - descr = F("LittleFS"); - # else // ifdef USE_LITTLEFS - descr = F("SPIFFS"); - # endif // ifdef USE_LITTLEFS - break; - case ESP8266_partition_type::eeprom: - label = F("eeprom"); - descr = concat(F("sector:"), partitionSector); - partitionColor = 0x7fff00; - break; - case ESP8266_partition_type::rf_cal: - label = F("RFcal"); - partitionColor = 0xff007f; - break; - case ESP8266_partition_type::wifi: - label = F("WiFi"); - partitionColor = 0xff00ff; - break; - } - - drawPartitionChartSVG( - yOffset, - getFlashRealSizeInBytes(), - partitionAddress, - partitionSize, - partitionColor, - label, - descr); - yOffset += SVG_BAR_HEIGHT; - - /* - String debuglog = concat(F("partition: "), (i+1)); - debuglog += concat(F(" FS_st: "), formatToHex((uint32_t)&_FS_start)); - debuglog += concat(F(" FS_end: "), formatToHex((uint32_t)&_FS_end)); - debuglog += concat(F(" EEPROM: "), formatToHex((uint32_t)&_EEPROM_start)); - debuglog += concat(F(" addr: "), formatToHex(partitionAddress, 8)); - debuglog += concat(F(" part.size: "), partitionSize); - debuglog += concat(F(" label: "), label); - addLog(LOG_LEVEL_INFO, debuglog); - */ - } - addHtml(F("\n")); -} - -# endif // ifdef ESP8266 -#endif // if FEATURE_CHART_STORAGE_LAYOUT - -bool webArg2ip(const __FlashStringHelper *arg, uint8_t *IP) { - return str2ip(webArg(arg), IP); -} +#include "../WebServer/ESPEasy_WebServer.h" + +#include "../WebServer/common.h" + +#include "../WebServer/404.h" +#include "../WebServer/AccessControl.h" +#include "../WebServer/AdvancedConfigPage.h" +#include "../WebServer/CacheControllerPages.h" +#include "../WebServer/ConfigPage.h" +#include "../WebServer/ControlPage.h" +#include "../WebServer/ControllerPage.h" +#include "../WebServer/CustomPage.h" +#include "../WebServer/DevicesPage.h" +#include "../WebServer/DownloadPage.h" +#include "../WebServer/FactoryResetPage.h" +#include "../WebServer/FileList.h" +#include "../WebServer/HTML_wrappers.h" +#include "../WebServer/HardwarePage.h" +#include "../WebServer/I2C_Scanner.h" +#include "../WebServer/JSON.h" +#include "../WebServer/LoadFromFS.h" +#include "../WebServer/Log.h" +#include "../WebServer/Markup.h" +#include "../WebServer/Markup_Buttons.h" +#include "../WebServer/Markup_Forms.h" +#include "../WebServer/NotificationPage.h" +#include "../WebServer/PinStates.h" +#include "../WebServer/RootPage.h" +#include "../WebServer/Rules.h" +#include "../WebServer/SettingsArchive.h" +#include "../WebServer/SetupPage.h" +#include "../WebServer/SysInfoPage.h" +#include "../WebServer/Metrics.h" +#include "../WebServer/SysVarPage.h" +#include "../WebServer/TimingStats.h" +#include "../WebServer/ToolsPage.h" +#include "../WebServer/UploadPage.h" +#include "../WebServer/WiFiScanner.h" + +#include "../WebServer/WebTemplateParser.h" + + +#include "../../ESPEasy-Globals.h" +#include "../../_Plugin_Helper.h" +#include "../../ESPEasy_common.h" + +#include "../CustomBuild/CompiletimeDefines.h" + +#include "../DataStructs/TimingStats.h" + +#include "../DataTypes/SettingsType.h" + +#include "../ESPEasyCore/ESPEasyNetwork.h" +#include "../ESPEasyCore/ESPEasyRules.h" +#include "../ESPEasyCore/ESPEasyWifi.h" + +#include "../Globals/CPlugins.h" +#include "../Globals/Device.h" +#include "../Globals/NetworkState.h" +#include "../Globals/SecuritySettings.h" +#include "../Globals/Settings.h" + +#include "../Helpers/ESPEasy_Storage.h" +#include "../Helpers/Hardware_device_info.h" +#include "../Helpers/Networking.h" +#include "../Helpers/OTA.h" +#include "../Helpers/StringConverter.h" + +#include "../Static/WebStaticData.h" + +#include + + +void safe_strncpy_webserver_arg(char *dest, const String& arg, size_t max_size) { + if (hasArg(arg)) { + safe_strncpy(dest, webArg(arg).c_str(), max_size); + } +} + +void safe_strncpy_webserver_arg(char *dest, const __FlashStringHelper *arg, size_t max_size) { + safe_strncpy_webserver_arg(dest, String(arg), max_size); +} + +void sendHeadandTail(const __FlashStringHelper *tmplName, bool Tail, bool rebooting) { + // This function is called twice per serving a web page. + // So it must keep track of the timer longer than the scope of this function. + // Therefore use a local static variable. + #if FEATURE_TIMING_STATS + static uint64_t statisticsTimerStart = 0; + + if (!Tail) { + statisticsTimerStart = getMicros64(); + } + #endif // if FEATURE_TIMING_STATS + { + const String fileName = concat(tmplName, F(".htm")); + fs::File f = tryOpenFile(fileName, "r"); + + WebTemplateParser templateParser(Tail, rebooting); + + if (f) { + bool success = true; + + while (f.available() && success) { + success = templateParser.process((char)f.read()); + } + f.close(); + } else { + getWebPageTemplateDefault(tmplName, templateParser); + } + #ifndef BUILD_NO_RAM_TRACKER + checkRAM(F("sendWebPage")); + #endif // ifndef BUILD_NO_RAM_TRACKER + + // web activity timer + lastWeb = millis(); + } + + if (shouldReboot) { + // we only add this here as a seperate chunk to prevent using too much memory at once + serve_JS(JSfiles_e::Reboot); + } + STOP_TIMER(HANDLE_SERVING_WEBPAGE); +} + +void sendHeadandTail_stdtemplate(bool Tail, bool rebooting) { + sendHeadandTail(F("TmplStd"), Tail, rebooting); + + if (!Tail) { + if (!clientIPinSubnet() && WifiIsAP(WiFi.getMode()) && (WiFi.softAPgetStationNum() > 0)) { + addHtmlError(F("Warning: Connected via AP")); + } + + #ifndef BUILD_NO_DEBUG + + /* + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + const int nrArgs = web_server.args(); + + if (nrArgs > 0) { + String log = F(" Webserver "); + log += nrArgs; + log += F(" Arguments"); + + if (nrArgs > 20) { + log += F(" (First 20)"); + } + log += ':'; + + for (int i = 0; i < nrArgs && i < 20; ++i) { + log += ' '; + log += i; + log += F(": '"); + log += web_server.argName(i); + log += F("' length: "); + log += webArg(i).length(); + } + addLogMove(LOG_LEVEL_INFO, log); + } + } + */ + #endif // ifndef BUILD_NO_DEBUG + } + + // We have sent a lot of data at once. + // try to flush it to the connected client to free up some RAM + // from pending transfers +// TXBuffer.flush(); +// delay(10); +} + +bool captivePortal() { + const IPAddress client_localIP = web_server.client().localIP(); + const bool fromAP = client_localIP == apIP; + const bool hasWiFiCredentials = SecuritySettings.hasWiFiCredentials(); + + if (hasWiFiCredentials || !fromAP) { + return false; + } + + if (!isIP(web_server.hostHeader()) && (web_server.hostHeader() != (NetworkGetHostname() + F(".local")))) { + String redirectURL = concat(F("http://"), formatIP(client_localIP)); + #ifdef WEBSERVER_SETUP + + if (fromAP && !hasWiFiCredentials) { + redirectURL += F("/setup"); + } + #endif // ifdef WEBSERVER_SETUP + sendHeader(F("Location"), redirectURL, true); + web_server.send(302, F("text/plain"), EMPTY_STRING); // Empty content inhibits Content-length header so we have to close the socket + // ourselves. + web_server.client().stop(); // Stop is needed because we sent no content length + return true; + } + return false; +} + +// ******************************************************************************** +// Web Interface init +// ******************************************************************************** +// #include "core_version.h" + + +void WebServerInit() +{ + if (webserver_init) { return; } + webserver_init = true; + + // Prepare webserver pages + + // FIXME TD-er: The added String() wrapper is needed for the latest ESP32 core lib. + // See: https://github.com/espressif/arduino-esp32/issues/4374 + #ifdef WEBSERVER_ROOT + web_server.on(F("/"), handle_root); + + // Entries for several captive portal URLs. + // Maybe not needed. Might be handled by notFound handler. + web_server.on(UriGlob("/generate_204*"), handle_root); // Android captive portal. Handle "/generate_204_"-like requests. + web_server.on(F("/fwlink"), handle_root); // Microsoft captive portal. + #endif // ifdef WEBSERVER_ROOT + #ifdef WEBSERVER_ADVANCED + web_server.on(F("/advanced"), handle_advanced); + # if defined(WEBSERVER_DOWNLOAD) && FEATURE_TARSTREAM_SUPPORT + web_server.on(F("/backup"), handle_full_backup); + web_server.on(F("/backupnup"), handle_full_backup_no_usr_pwd); + # endif // if defined(WEBSERVER_DOWNLOAD) && FEATURE_TARSTREAM_SUPPORT + #endif // ifdef WEBSERVER_ADVANCED + #ifdef WEBSERVER_CONFIG + web_server.on(F("/config"), handle_config); + #endif // ifdef WEBSERVER_CONFIG + #ifdef WEBSERVER_CONTROL + web_server.on(F("/control"), handle_control); + #endif // ifdef WEBSERVER_CONTROL + #ifdef WEBSERVER_CONTROLLERS + web_server.on(F("/controllers"), handle_controllers); + #endif // ifdef WEBSERVER_CONTROLLERS + #ifdef WEBSERVER_DEVICES + web_server.on(F("/devices"), handle_devices); + #endif // ifdef WEBSERVER_DEVICES + #ifdef WEBSERVER_DOWNLOAD + web_server.on(F("/download"), handle_download); + #endif // ifdef WEBSERVER_DOWNLOAD + +#ifdef USES_C016 + + web_server.on(F("/dumpcache"), handle_dumpcache); // C016 specific entrie + web_server.on(F("/cache_json"), handle_cache_json); // C016 specific entrie + web_server.on(F("/cache_csv"), handle_cache_csv); // C016 specific entrie +#endif // USES_C016 + + #ifdef WEBSERVER_FACTORY_RESET + web_server.on(F("/factoryreset"), handle_factoryreset); + #endif // ifdef WEBSERVER_FACTORY_RESET + #if FEATURE_SETTINGS_ARCHIVE + web_server.on(F("/settingsarchive"), handle_settingsarchive); + #endif // if FEATURE_SETTINGS_ARCHIVE + #ifdef WEBSERVER_FILELIST + web_server.on(F("/filelist"), handle_filelist); + #endif // ifdef WEBSERVER_FILELIST + #ifdef WEBSERVER_HARDWARE + web_server.on(F("/hardware"), handle_hardware); + #endif // ifdef WEBSERVER_HARDWARE + #ifdef WEBSERVER_I2C_SCANNER + web_server.on(F("/i2cscanner"), handle_i2cscanner); + #endif // ifdef WEBSERVER_I2C_SCANNER + web_server.on(F("/json"), handle_json); // Also part of WEBSERVER_NEW_UI + #ifdef WEBSERVER_CSVVAL + web_server.on(F("/csv"), handle_csvval); + #endif + web_server.on(F("/log"), handle_log); + web_server.on(F("/logjson"), handle_log_JSON); // Also part of WEBSERVER_NEW_UI +#if FEATURE_NOTIFIER + web_server.on(F("/notifications"), handle_notifications); +#endif // if FEATURE_NOTIFIER + #ifdef WEBSERVER_PINSTATES + web_server.on(F("/pinstates"), handle_pinstates); + #endif // ifdef WEBSERVER_PINSTATES + #ifdef WEBSERVER_RULES + web_server.on(F("/rules"), handle_rules_new); + web_server.on(F("/rules/"), Goto_Rules_Root); + # ifdef WEBSERVER_NEW_RULES + web_server.on(F("/rules/add"), []() + { + handle_rules_edit(web_server.uri(), true); + }); + web_server.on(F("/rules/backup"), handle_rules_backup); + web_server.on(F("/rules/delete"), handle_rules_delete); + # endif // WEBSERVER_NEW_RULES + #endif // WEBSERVER_RULES +#if FEATURE_SD + web_server.on(F("/SDfilelist"), handle_SDfilelist); +#endif // if FEATURE_SD +#ifdef WEBSERVER_SETUP + web_server.on(F("/setup"), handle_setup); +#endif // ifdef WEBSERVER_SETUP +#ifdef WEBSERVER_SYSINFO + web_server.on(F("/sysinfo"), handle_sysinfo); +#endif // ifdef WEBSERVER_SYSINFO +#ifdef WEBSERVER_METRICS + web_server.on(F("/metrics"), handle_metrics); +#endif // ifdef WEBSERVER_METRICS +#ifdef WEBSERVER_SYSVARS + web_server.on(F("/sysvars"), handle_sysvars); +#endif // WEBSERVER_SYSVARS +#ifdef WEBSERVER_TIMINGSTATS + web_server.on(F("/timingstats"), handle_timingstats); +#endif // WEBSERVER_TIMINGSTATS +#ifdef WEBSERVER_TOOLS + web_server.on(F("/tools"), handle_tools); +#endif // ifdef WEBSERVER_TOOLS +#ifdef WEBSERVER_UPLOAD + web_server.on(F("/upload"), HTTP_GET, handle_upload); + web_server.on(F("/upload"), HTTP_POST, handle_upload_post, handleFileUpload); +#endif // ifdef WEBSERVER_UPLOAD +#if FEATURE_SD + web_server.on(F("/uploadsd"), HTTP_GET, handle_upload); + web_server.on(F("/uploadsd"), HTTP_POST, handle_upload_post, handleSDFileUpload); +#endif // if FEATURE_SD +#ifdef WEBSERVER_WIFI_SCANNER + web_server.on(F("/wifiscanner"), handle_wifiscanner); +#endif // ifdef WEBSERVER_WIFI_SCANNER + +#ifdef WEBSERVER_NEW_UI + web_server.on(F("/buildinfo"), handle_buildinfo); // Also part of WEBSERVER_NEW_UI + web_server.on(F("/factoryreset_json"), handle_factoryreset_json); + web_server.on(F("/filelist_json"), handle_filelist_json); + web_server.on(F("/i2cscanner_json"), handle_i2cscanner_json); + # if FEATURE_ESPEASY_P2P + web_server.on(F("/node_list_json"), handle_nodes_list_json); + # endif // if FEATURE_ESPEASY_P2P + web_server.on(F("/pinstates_json"), handle_pinstates_json); + web_server.on(F("/timingstats_json"), handle_timingstats_json); + web_server.on(F("/upload_json"), HTTP_POST, handle_upload_json, handleFileUpload); + web_server.on(F("/wifiscanner_json"), handle_wifiscanner_json); +#endif // WEBSERVER_NEW_UI +#if SHOW_SYSINFO_JSON + web_server.on(F("/sysinfo_json"), handle_sysinfo_json); +#endif// SHOW_SYSINFO_JSON + + web_server.onNotFound(handleNotFound); + + // List of headers to be recorded + // "If-None-Match" is used to see whether we need to serve a static file, or simply can reply with a 304 (not modified) + const char *headerkeys[] = { "If-None-Match" }; + constexpr size_t headerkeyssize = NR_ELEMENTS(headerkeys); + web_server.collectHeaders(headerkeys, headerkeyssize); + #if defined(ESP8266) || defined(ESP32) + { + # ifndef NO_HTTP_UPDATER + uint32_t maxSketchSize; + bool use2step; + + // allow OTA to smaller version of ESPEasy/other firmware + if (Settings.AllowOTAUnlimited() || OTA_possible(maxSketchSize, use2step)) { + httpUpdater.setup(&web_server); + } + # endif // ifndef NO_HTTP_UPDATER + } + #endif // if defined(ESP8266) + + #if defined(ESP8266) + + # if FEATURE_SSDP + + if (Settings.UseSSDP) + { + web_server.on(F("/ssdp.xml"), HTTP_GET, []() { + # ifdef MUSTFIX_CLIENT_TIMEOUT_IN_SECONDS + + // See: https://github.com/espressif/arduino-esp32/pull/6676 + web_server.client().setTimeout((CONTROLLER_CLIENTTIMEOUT_DFLT + 500) / 1000); // in seconds!!!! + Client& pClient = web_server.client(); + pClient.setTimeout(CONTROLLER_CLIENTTIMEOUT_DFLT); + # else // ifdef MUSTFIX_CLIENT_TIMEOUT_IN_SECONDS + web_server.client().setTimeout(CONTROLLER_CLIENTTIMEOUT_DFLT); // in msec as it should be! + # endif // ifdef MUSTFIX_CLIENT_TIMEOUT_IN_SECONDS + + SSDP_schema(); + }); + SSDP_begin(); + } + # endif // if FEATURE_SSDP + #endif // if defined(ESP8266) +} + +void setWebserverRunning(bool state) { + if (webserverRunning == state) { + return; + } + + if (state) { + WebServerInit(); + web_server.begin(Settings.WebserverPort); + #ifndef BUILD_MINIMAL_OTA + addLog(LOG_LEVEL_INFO, F("Webserver: start")); + #endif + } else { + web_server.client().stop(); + web_server.stop(); + #ifndef BUILD_MINIMAL_OTA + addLog(LOG_LEVEL_INFO, F("Webserver: stop")); + #endif + } + webserverRunning = state; + CheckRunningServices(); // Uses webserverRunning state. +} + +void getWebPageTemplateDefault(const String& tmplName, WebTemplateParser& parser) +{ + const bool addJS = true; + const bool addMeta = true; + + /* + if (equals(tmplName, F("TmplAP"))) + { + + getWebPageTemplateDefaultHead(parser, addMeta, !addJS); + + if (!parser.isTail()) { + #ifndef WEBPAGE_TEMPLATE_AP_HEADER + parser.process(F("
" + "

Welcome to ESP Easy Mega AP

")); + #else + parser.process(F(WEBPAGE_TEMPLATE_AP_HEADER)); + #endif + + parser.process(F("
")); + } + getWebPageTemplateDefaultContentSection(parser); + getWebPageTemplateDefaultFooter(parser); + } + else + */ + if (equals(tmplName, F("TmplMsg"))) + { + getWebPageTemplateDefaultHead(parser, !addMeta, !addJS); + + if (!parser.isTail()) { + parser.process(F("")); + } + getWebPageTemplateDefaultHeader(parser, F("{{name}}"), false); + getWebPageTemplateDefaultContentSection(parser); + getWebPageTemplateDefaultFooter(parser); + } + else if (equals(tmplName, F("TmplDsh"))) + { + getWebPageTemplateDefaultHead(parser, !addMeta, addJS); + parser.process(F("" + "{{content}}" + "")); + } + else // all other template names e.g. TmplStd + { + getWebPageTemplateDefaultHead(parser, addMeta, addJS); + + if (!parser.isTail()) { + parser.process(F("")); + } + getWebPageTemplateDefaultHeader(parser, F("{{name}} {{logo}}"), true); + getWebPageTemplateDefaultContentSection(parser); + getWebPageTemplateDefaultFooter(parser); + } + + // addLog(LOG_LEVEL_INFO, concat(F("tmpl.length(): "), tmpl.length())); +} + +void getWebPageTemplateDefaultHead(WebTemplateParser& parser, bool addMeta, bool addJS) { + if (parser.isTail()) { return; } + parser.process(F("" + "" + "" + "" + "{{name}}")); + + if (addMeta) { parser.process(F("{{meta}}")); } + + if (addJS) { parser.process(F("{{js}}")); } + + parser.process(F("{{css}}" + "")); +} + +void getWebPageTemplateDefaultHeader(WebTemplateParser& parser, const __FlashStringHelper *title, bool addMenu) { + { + if (parser.isTail()) { return; } + #ifndef WEBPAGE_TEMPLATE_DEFAULT_HEADER + parser.process(F("

ESP Easy Mega: ")); + parser.process(title); + # if BUILD_IN_WEBHEADER + parser.process(F( + "
Build: " GITHUB_RELEASES_LINK_PREFIX "{{date}}" GITHUB_RELEASES_LINK_SUFFIX + "
")); + # endif // #if BUILD_IN_WEBHEADER + parser.process(F("


")); + #else // ifndef WEBPAGE_TEMPLATE_DEFAULT_HEADER + String tmp = F(WEBPAGE_TEMPLATE_DEFAULT_HEADER); + tmp.replace(F("{{title}}"), title); + parser.process(tmp); + #endif // ifndef WEBPAGE_TEMPLATE_DEFAULT_HEADER + } + + if (addMenu) { parser.process(F("{{menu}}")); } + parser.process(F("
")); +} + +void getWebPageTemplateDefaultContentSection(WebTemplateParser& parser) { + parser.process(F("
" + "" + "{{error}}" + "" + "{{content}}" + "
" + )); +} + +void getWebPageTemplateDefaultFooter(WebTemplateParser& parser) { + if (!parser.isTail()) { return; } + #ifndef WEBPAGE_TEMPLATE_DEFAULT_FOOTER + parser.process(F("
" + "
" + "
Powered by Let's Control It community" + # if BUILD_IN_WEBFOOTER + "
Build: " GITHUB_RELEASES_LINK_PREFIX "{{build}} {{date}}" GITHUB_RELEASES_LINK_SUFFIX "
" + # endif // #if BUILD_IN_WEBFOOTER + "
" + "
" + "" + )); +#else // ifndef WEBPAGE_TEMPLATE_DEFAULT_FOOTER + parser.process(F(WEBPAGE_TEMPLATE_DEFAULT_FOOTER)); +#endif // ifndef WEBPAGE_TEMPLATE_DEFAULT_FOOTER +} + +void writeDefaultCSS(void) +{ + return; // TODO + + /* + #ifndef WEBSERVER_USE_CDN_JS_CSS + + if (!fileExists(F("esp.css"))) + { + fs::File f = tryOpenFile(F("esp.css"), "w"); + + if (f) + { + String defaultCSS; + defaultCSS = PGMT(DATA_ESPEASY_DEFAULT_MIN_CSS); + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + String log = F("CSS : Writing default CSS file to FS ("); + log += defaultCSS.length(); + log += F(" bytes)"); + addLog(LOG_LEVEL_INFO, log); + } + f.write((const unsigned char *)defaultCSS.c_str(), defaultCSS.length()); // note: content must be in RAM - a write of F("XXX") does + // not work + f.close(); + } + } + #endif + */ +} + +// ******************************************************************************** +// Functions to stream JSON directly to TXBuffer +// FIXME TD-er: replace stream_xxx_json_object* into this code. +// N.B. handling of numerical values differs (string vs. no string) +// ******************************************************************************** + +int8_t level = 0; +int8_t lastLevel = -1; + +void json_quote_name(const __FlashStringHelper *val) { + json_quote_name(String(val)); +} + +void json_quote_name(const String& val) { + if (lastLevel == level) { + addHtml(','); + } + + if (val.length() > 0) { + json_quote_val(val); + addHtml(':'); + } +} + +void json_quote_val(const String& val) { + addHtml('\"'); + addHtml(val); + addHtml('\"'); +} + +void json_open(bool arr) { + json_open(arr, EMPTY_STRING); +} + +void json_open(bool arr, const __FlashStringHelper *name) { + json_quote_name(name); + addHtml(arr ? '[' : '{'); + lastLevel = level; + level++; +} + +void json_open(bool arr, const String& name) { + json_quote_name(name); + addHtml(arr ? '[' : '{'); + lastLevel = level; + level++; +} + +void json_init() { + level = 0; + lastLevel = -1; +} + +void json_close() { + json_close(false); +} + +void json_close(bool arr) { + addHtml(arr ? ']' : '}'); + level--; + lastLevel = level; +} + +void json_number(const __FlashStringHelper *name, const String& value) +{ + json_prop(name, value); +} + +void json_number(const String& name, const String& value) { + json_prop(name, value); +} + +void json_prop(const __FlashStringHelper *name, const String& value) +{ + json_quote_name(name); + json_quote_val(value); + lastLevel = level; +} + +void json_prop(const String& name, const String& value) { + json_quote_name(name); + json_quote_val(value); + lastLevel = level; +} + +void json_prop(LabelType::Enum label) { + json_prop(getInternalLabel(label, '-'), getValue(label)); +} + +// ******************************************************************************** +// Add a task select dropdown list +// This allows to select a task index based on the existing tasks. +// ******************************************************************************** +void addTaskSelect(const String& name, taskIndex_t choice, const String& cssclass) +{ + String deviceName; + + addHtml(F("'); + + const uint8_t valueCount = getValueCountForTask(TaskIndex); + + for (uint8_t x = 0; x < valueCount; x++) + { + addHtml(F("")); + } +} + +// ******************************************************************************** +// Login state check +// ******************************************************************************** +bool isLoggedIn(bool mustProvideLogin) +{ + if (!clientIPallowed()) { return false; } + + if (SecuritySettings.Password[0] == 0) { return true; } + + if (!mustProvideLogin) { + return false; + } + + { + String www_username = F(DEFAULT_ADMIN_USERNAME); + + if (!web_server.authenticate(www_username.c_str(), SecuritySettings.Password)) + + // Basic Auth Method with Custom realm and Failure Response + // return server.requestAuthentication(BASIC_AUTH, www_realm, authFailResponse); + // Digest Auth Method with realm="Login Required" and empty Failure Response + // return server.requestAuthentication(DIGEST_AUTH); + // Digest Auth Method with Custom realm and empty Failure Response + // return server.requestAuthentication(DIGEST_AUTH, www_realm); + // Digest Auth Method with Custom realm and Failure Response + { +#ifdef CORE_PRE_2_5_0 + + // See https://github.com/esp8266/Arduino/issues/4717 + HTTPAuthMethod mode = BASIC_AUTH; +#else // ifdef CORE_PRE_2_5_0 + HTTPAuthMethod mode = DIGEST_AUTH; +#endif // ifdef CORE_PRE_2_5_0 + String message = F("Login Required (default user: "); + message += www_username; + message += ')'; + web_server.requestAuthentication(mode, message.c_str()); + + if (Settings.UseRules) + { + String event = F("Login#Failed"); + + // TD-er: Do not add to the eventQueue, but execute right now. + rulesProcessing(event); + } + + return false; + } + } + return true; +} + +String getControllerSymbol(uint8_t index) +{ + String ret = F("&#"); + + ret += 10102 + index; + ret += F(";"); + return ret; +} + +/* + String getValueSymbol(uint8_t index) + { + String ret = F("&#"); + ret += 10112 + index; + ret += ';'; + return ret; + } + */ +void addSVG_param(const char key, int value) +{ + addHtml(strformat(F(" %c=\"%d\""), key, value)); +} + +void addSVG_param(const char key, float value) +{ + addSVG_param(key, toString(value, 2)); +} + +void addSVG_param(const char key, const String& value) +{ + addHtml(strformat(F(" %c=\"%s\""), key, value.c_str())); +} + +void addSVG_param(const __FlashStringHelper *key, int value) { + addHtml(strformat(F(" %s=\"%d\""), key, value)); +} + +void addSVG_param(const __FlashStringHelper *key, float value) { + addSVG_param(key, toString(value, 2)); +} + +void addSVG_param(const __FlashStringHelper *key, const String& value) { + addHtml(strformat(F(" %s=\"%s\""), key, value.c_str())); +} + +void createSvgRect_noStroke(const __FlashStringHelper *classname, + unsigned int fillColor, + float xoffset, + float yoffset, + float width, + float height, + float rx, + float ry) { + createSvgRect(classname, fillColor, fillColor, xoffset, yoffset, width, height, 0, rx, ry); +} + +void createSvgRect(const String& classname, + unsigned int fillColor, + unsigned int strokeColor, + float xoffset, + float yoffset, + float width, + float height, + float strokeWidth, + float rx, + float ry) { + addHtml(F("")); +} + +void createSvgHorRectPath(unsigned int color, int xoffset, int yoffset, int size, int height, int range, float SVG_BAR_WIDTH) { + if (range == 0) { + range = 1; + } + float width = (SVG_BAR_WIDTH * size) / range; + + if (width < 2) { width = 2; } + addHtml(formatToHex(color, F("\n")); +} + +void createSvgTextElement(const String& text, float textXoffset, float textYoffset) { + addHtml(F("', '\n'); + + addHtml(F("'); + + addHtml(text); + addHtml(F("\n")); +} + +#define SVG_BAR_HEIGHT 16 +#define SVG_BAR_WIDTH 400 + +void write_SVG_image_header(int width, int height, bool useViewbox) { + addHtml(F("'); + addHtml(F("")); +} + +/* + void getESPeasyLogo(int width_pixels) { + write_SVG_image_header(width_pixels, width_pixels, true); + addHtml(F(""); + } + */ +void getWiFi_RSSI_icon(int rssi, int width_pixels) +{ + const int nbars_filled = (rssi + 100) / 8; + const int nbars = 5; + int white_between_bar = (static_cast(width_pixels) / nbars) * 0.2f; + + if (white_between_bar < 1) { white_between_bar = 1; } + const int barWidth = (width_pixels - (nbars - 1) * white_between_bar) / nbars; + const int svg_width_pixels = nbars * barWidth + (nbars - 1) * white_between_bar; + + write_SVG_image_header(svg_width_pixels, svg_width_pixels, true); + const float scale = 100.0f / svg_width_pixels; + const int bar_height_step = 100 / nbars; + + for (int i = 0; i < nbars; ++i) { + const unsigned int color = i < nbars_filled ? 0x07d : 0xBFa1a1a1; // Blue/Grey75% + const int barHeight = (i + 1) * bar_height_step; + createSvgRect_noStroke(i < nbars_filled ? F("bar_highlight") : F("bar_dimmed"), + color, + i * (barWidth + white_between_bar) * scale, + 100 - barHeight, + barWidth, + barHeight, + 0, + 0); + } + addHtml(F("\n")); +} + +#if FEATURE_CHART_STORAGE_LAYOUT +void getConfig_dat_file_layout() { + const int shiftY = 2; + float yOffset = shiftY; + + write_SVG_image_header(SVG_BAR_WIDTH + 250, SVG_BAR_HEIGHT + shiftY); + + int max_index, offset, max_size{}; + int struct_size = 0; + + // background + const uint32_t realSize = SettingsType::getFileSize(SettingsType::Enum::TaskSettings_Type); + + createSvgHorRectPath(0xcdcdcd, 0, yOffset, realSize, SVG_BAR_HEIGHT - 2, realSize, SVG_BAR_WIDTH); + + for (int st = 0; st < static_cast(SettingsType::Enum::SettingsType_MAX); ++st) { + SettingsType::Enum settingsType = static_cast(st); + + if (SettingsType::getSettingsFile(settingsType) == SettingsType::SettingsFileEnum::FILE_CONFIG_type) { + unsigned int color = SettingsType::getSVGcolor(settingsType); + + if (SettingsType::getSettingsParameters(settingsType, 0, max_index, offset, max_size, struct_size)) + { + for (int i = 0; i < max_index; ++i) { + if (SettingsType::getSettingsParameters(settingsType, i, offset, max_size)) { + // Struct position + createSvgHorRectPath(color, offset, yOffset, max_size, SVG_BAR_HEIGHT - 2, realSize, SVG_BAR_WIDTH); + } + } + } + } + } + + // Text labels + constexpr float textXoffset = SVG_BAR_WIDTH + 2; + float textYoffset = yOffset + 0.9f * SVG_BAR_HEIGHT; + + createSvgTextElement(SettingsType::getSettingsFileName(SettingsType::Enum::TaskSettings_Type), textXoffset, textYoffset); + addHtml(F("\n")); +} + +void getStorageTableSVG(SettingsType::Enum settingsType) { + uint32_t realSize = SettingsType::getFileSize(settingsType); + unsigned int color = SettingsType::getSVGcolor(settingsType); + const int shiftY = 2; + + int max_index, offset, max_size{}; + int struct_size = 0; + + if (!SettingsType::getSettingsParameters(settingsType, 0, max_index, offset, max_size, struct_size)) + { + return; + } + + if (max_index == 0) { return; } + + // One more to add bar indicating struct size vs. reserved space. + write_SVG_image_header(SVG_BAR_WIDTH + 250, (max_index + 1) * SVG_BAR_HEIGHT + shiftY); + float yOffset = shiftY; + + for (int i = 0; i < max_index; ++i) { + if (SettingsType::getSettingsParameters(settingsType, i, offset, max_size)) { + // background + createSvgHorRectPath(0xcdcdcd, 0, yOffset, realSize, SVG_BAR_HEIGHT - 2, realSize, SVG_BAR_WIDTH); + + // Struct position + createSvgHorRectPath(color, offset, yOffset, max_size, SVG_BAR_HEIGHT - 2, realSize, SVG_BAR_WIDTH); + + // Text labels + float textXoffset = SVG_BAR_WIDTH + 2; + float textYoffset = yOffset + 0.9f * SVG_BAR_HEIGHT; + createSvgTextElement(formatHumanReadable(offset, 1024), textXoffset, textYoffset); + textXoffset = SVG_BAR_WIDTH + 60; + createSvgTextElement(formatHumanReadable(max_size, 1024), textXoffset, textYoffset); + textXoffset = SVG_BAR_WIDTH + 130; + createSvgTextElement(String(i), textXoffset, textYoffset); + yOffset += SVG_BAR_HEIGHT; + } + } + + // usage + createSvgHorRectPath(0xcdcdcd, 0, yOffset, max_size, SVG_BAR_HEIGHT - 2, max_size, SVG_BAR_WIDTH); + + // Struct size (used part of the reserved space) + if (struct_size != 0) { + createSvgHorRectPath(color, 0, yOffset, struct_size, SVG_BAR_HEIGHT - 2, max_size, SVG_BAR_WIDTH); + } + + // Text labels + float textXoffset = SVG_BAR_WIDTH + 2; + float textYoffset = yOffset + 0.9f * SVG_BAR_HEIGHT; + + if (struct_size != 0) { + String text = strformat( + F("%s/%s per item"), + formatHumanReadable(struct_size, 1024).c_str(), + formatHumanReadable(max_size, 1024).c_str()); + createSvgTextElement(text, textXoffset, textYoffset); + } else { + createSvgTextElement(F("Variable size"), textXoffset, textYoffset); + } + addHtml(F("\n")); +} + +void drawPartitionChartSVG( + float yOffset, + uint32_t realSize, + uint32_t partitionAddress, + uint32_t partitionSize, + unsigned int partitionColor, + const String& label, + const String& name) +{ + createSvgHorRectPath(0xcdcdcd, 0, yOffset, realSize, SVG_BAR_HEIGHT - 2, realSize, SVG_BAR_WIDTH); + createSvgHorRectPath(partitionColor, partitionAddress, yOffset, partitionSize, SVG_BAR_HEIGHT - 2, realSize, SVG_BAR_WIDTH); + float textXoffset = SVG_BAR_WIDTH + 2; + const float textYoffset = yOffset + 0.9f * SVG_BAR_HEIGHT; + createSvgTextElement(formatHumanReadable(partitionSize, 1024), textXoffset, textYoffset); + textXoffset = SVG_BAR_WIDTH + 60; + createSvgTextElement(label, textXoffset, textYoffset); + textXoffset = SVG_BAR_WIDTH + 130; + createSvgTextElement(name, textXoffset, textYoffset); +} + +# ifdef ESP32 + +# include + + +void getPartitionTableSVG(uint8_t pType, unsigned int partitionColor) { + int nrPartitions = getPartionCount(pType); + + if (nrPartitions == 0) { return; } + const int shiftY = 2; + + uint32_t realSize = getFlashRealSizeInBytes(); + esp_partition_type_t partitionType = static_cast(pType); + const esp_partition_t *_mypart; + esp_partition_iterator_t _mypartiterator = esp_partition_find(partitionType, ESP_PARTITION_SUBTYPE_ANY, nullptr); + + write_SVG_image_header(SVG_BAR_WIDTH + 250, nrPartitions * SVG_BAR_HEIGHT + shiftY); + float yOffset = shiftY; + + if (_mypartiterator) { + do { + _mypart = esp_partition_get(_mypartiterator); + drawPartitionChartSVG( + yOffset, + realSize, + _mypart->address, + _mypart->size, + partitionColor, + _mypart->label, + getPartitionType(_mypart->type, _mypart->subtype)); + yOffset += SVG_BAR_HEIGHT; + } while ((_mypartiterator = esp_partition_next(_mypartiterator)) != nullptr); + } + addHtml(F("\n")); + esp_partition_iterator_release(_mypartiterator); +} + +# endif // ifdef ESP32 + +# ifdef ESP8266 +void getPartitionTableSVG() { + // sketch / OTA / FS / EEPROM / RFcal / wifi + const int nrPartitions = 6; + const int shiftY = 2; + + write_SVG_image_header(SVG_BAR_WIDTH + 250, nrPartitions * SVG_BAR_HEIGHT + shiftY); + float yOffset = shiftY; + + for (int i = 0; i < nrPartitions; ++i) { + const ESP8266_partition_type ptype = static_cast(i); + uint32_t partitionAddress = 0; + int32_t partitionSize = 0; + const int32_t partitionSector = getPartitionInfo(ptype, partitionAddress, partitionSize); + + const __FlashStringHelper *label = F(""); + String descr; + unsigned int partitionColor = 0xab56e6; + + switch (ptype) { + case ESP8266_partition_type::sketch: + label = F("sketch"); + partitionColor = 0xab56e6; + break; + case ESP8266_partition_type::ota: + label = F("ota"); + partitionColor = 0x5856e6; + break; + case ESP8266_partition_type::fs: + label = F("fs"); + partitionColor = 0xff7f00; + # ifdef USE_LITTLEFS + descr = F("LittleFS"); + # else // ifdef USE_LITTLEFS + descr = F("SPIFFS"); + # endif // ifdef USE_LITTLEFS + break; + case ESP8266_partition_type::eeprom: + label = F("eeprom"); + descr = concat(F("sector:"), partitionSector); + partitionColor = 0x7fff00; + break; + case ESP8266_partition_type::rf_cal: + label = F("RFcal"); + partitionColor = 0xff007f; + break; + case ESP8266_partition_type::wifi: + label = F("WiFi"); + partitionColor = 0xff00ff; + break; + } + + drawPartitionChartSVG( + yOffset, + getFlashRealSizeInBytes(), + partitionAddress, + partitionSize, + partitionColor, + label, + descr); + yOffset += SVG_BAR_HEIGHT; + + /* + String debuglog = concat(F("partition: "), (i+1)); + debuglog += concat(F(" FS_st: "), formatToHex((uint32_t)&_FS_start)); + debuglog += concat(F(" FS_end: "), formatToHex((uint32_t)&_FS_end)); + debuglog += concat(F(" EEPROM: "), formatToHex((uint32_t)&_EEPROM_start)); + debuglog += concat(F(" addr: "), formatToHex(partitionAddress, 8)); + debuglog += concat(F(" part.size: "), partitionSize); + debuglog += concat(F(" label: "), label); + addLog(LOG_LEVEL_INFO, debuglog); + */ + } + addHtml(F("\n")); +} + +# endif // ifdef ESP8266 +#endif // if FEATURE_CHART_STORAGE_LAYOUT + +bool webArg2ip(const __FlashStringHelper *arg, uint8_t *IP) { + return str2ip(webArg(arg), IP); +} diff --git a/src/src/WebServer/JSON.cpp b/src/src/WebServer/JSON.cpp index 4fe5245aee..116df49a77 100644 --- a/src/src/WebServer/JSON.cpp +++ b/src/src/WebServer/JSON.cpp @@ -1,888 +1,891 @@ -#include "../WebServer/JSON.h" - -#include "../WebServer/ESPEasy_WebServer.h" -#include "../WebServer/JSON.h" -#include "../WebServer/Markup_Forms.h" - -#include "../CustomBuild/CompiletimeDefines.h" - -#include "../DataStructs/TimingStats.h" - -#include "../Globals/Cache.h" -#include "../Globals/Nodes.h" -#include "../Globals/Device.h" -#include "../Globals/Plugins.h" -#include "../Globals/NPlugins.h" - -#include "../Helpers/_Plugin_init.h" -#include "../Helpers/ESPEasyStatistics.h" -#include "../Helpers/ESPEasy_Storage.h" -#include "../Helpers/Numerical.h" -#include "../Helpers/StringConverter.h" -#include "../Helpers/StringProvider.h" -#include "../Helpers/StringGenerator_System.h" - -#include "../../_Plugin_Helper.h" -#include "../../ESPEasy-Globals.h" - -void stream_comma_newline() { - addHtml(',', '\n'); -} - - -// ******************************************************************************** -// Web Interface get CSV value from task -// ******************************************************************************** -#ifdef WEBSERVER_CSVVAL -void handle_csvval() -{ - TXBuffer.startJsonStream(); - const int printHeader = getFormItemInt(F("header"), 1); - bool printHeaderValid = true; - if (printHeader != 1 && printHeader != 0) - { - addHtml(F("ERROR: Header not valid!\n")); - printHeaderValid = false; - } - - const taskIndex_t taskNr = getFormItemInt(F("tasknr"), INVALID_TASK_INDEX); - const bool taskValid = validTaskIndex(taskNr); - if (!taskValid) - { - addHtml(F("ERROR: TaskNr not valid!\n")); - } - - const int INVALID_VALUE_NUM = INVALID_TASKVAR_INDEX + 1; - const taskVarIndex_t valNr = getFormItemInt(F("valnr"), INVALID_VALUE_NUM); - bool valueNumberValid = true; - if (valNr != INVALID_VALUE_NUM && !validTaskVarIndex(valNr)) - { - addHtml(F("ERROR: ValueId not valid!\n")); - valueNumberValid = false; - } - - if (taskValid && valueNumberValid && printHeaderValid) - { - const deviceIndex_t DeviceIndex = getDeviceIndex_from_TaskIndex(taskNr); - - if (validDeviceIndex(DeviceIndex)) - { - const uint8_t taskValCount = getValueCountForTask(taskNr); - - if (printHeader) - { - for (uint8_t x = 0; x < taskValCount; x++) - { - if (valNr == INVALID_VALUE_NUM || valNr == x) - { - addHtml(Cache.getTaskDeviceValueName(taskNr, x)); - if (x != taskValCount - 1) - { - addHtml(';'); - } - } - } - addHtml('\n'); - } - - struct EventStruct TempEvent(taskNr); - - for (uint8_t x = 0; x < taskValCount; x++) - { - if ((valNr == INVALID_VALUE_NUM) || (valNr == x)) - { - addHtml(formatUserVarNoCheck(&TempEvent, x)); - - if (x != taskValCount - 1) - { - addHtml(';'); - } - } - } - addHtml('\n'); - } - } - TXBuffer.endStream(); -} -#endif - -// ******************************************************************************** -// Web Interface JSON page (no password!) -// ******************************************************************************** -void handle_json() -{ - START_TIMER - const taskIndex_t taskNr = getFormItemInt(F("tasknr"), INVALID_TASK_INDEX); - const bool showSpecificTask = validTaskIndex(taskNr); - bool showSystem = true; - bool showWifi = true; - - #if FEATURE_ETHERNET - bool showEthernet = true; - #endif // if FEATURE_ETHERNET - bool showDataAcquisition = true; - bool showTaskDetails = true; - #if FEATURE_ESPEASY_P2P - bool showNodes = true; - #endif - #if FEATURE_PLUGIN_STATS - bool showPluginStats = getFormItemInt(F("showpluginstats"), 0) != 0; - #endif - - if (equals(webArg(F("view")), F("sensorupdate"))) { - showSystem = false; - showWifi = false; - #if FEATURE_ETHERNET - showEthernet = false; - #endif // if FEATURE_ETHERNET - showDataAcquisition = false; - showTaskDetails = false; - #if FEATURE_ESPEASY_P2P - showNodes = false; - #endif - #if FEATURE_PLUGIN_STATS - showPluginStats = hasArg(F("showpluginstats")); - #endif - } - - TXBuffer.startJsonStream(); - - if (!showSpecificTask) - { - addHtml('{'); - - if (showSystem) { - addHtml(F("\"System\":{\n")); - - if (wdcounter > 0) - { - stream_next_json_object_value(LabelType::LOAD_PCT); - stream_next_json_object_value(LabelType::LOOP_COUNT); - } - - static const LabelType::Enum labels[] PROGMEM = - { - LabelType::BUILD_DESC, - LabelType::GIT_BUILD, - LabelType::SYSTEM_LIBRARIES, -#ifdef ESP32 - LabelType::ESP_IDF_SDK_VERSION, -#endif - LabelType::PLUGIN_COUNT, - LabelType::PLUGIN_DESCRIPTION, - LabelType::BUILD_TIME, - LabelType::BINARY_FILENAME, - LabelType::LOCAL_TIME, - #if FEATURE_EXT_RTC - LabelType::EXT_RTC_UTC_TIME, - #endif - LabelType::TIME_SOURCE, - LabelType::TIME_WANDER, - LabelType::ISNTP, - LabelType::UNIT_NR, - LabelType::UNIT_NAME, - LabelType::UPTIME, - LabelType::UPTIME_MS, -#if FEATURE_INTERNAL_TEMPERATURE - LabelType::INTERNAL_TEMPERATURE, -#endif - LabelType::BOOT_TYPE, - LabelType::RESET_REASON, - LabelType::CPU_ECO_MODE, - - #if defined(CORE_POST_2_5_0) || defined(ESP32) - #ifndef LIMIT_BUILD_SIZE - LabelType::HEAP_MAX_FREE_BLOCK, // 7654 - #endif - #endif // if defined(CORE_POST_2_5_0) || defined(ESP32) - #if defined(CORE_POST_2_5_0) - #ifndef LIMIT_BUILD_SIZE - LabelType::HEAP_FRAGMENTATION, // 12 - #endif - #endif // if defined(CORE_POST_2_5_0) - LabelType::FREE_MEM, - #ifdef USE_SECOND_HEAP - LabelType::FREE_HEAP_IRAM, - #endif - LabelType::FREE_STACK, - - #ifdef ESP32 - LabelType::HEAP_SIZE, - LabelType::HEAP_MIN_FREE, - #ifdef BOARD_HAS_PSRAM - LabelType::PSRAM_SIZE, - LabelType::PSRAM_FREE, - LabelType::PSRAM_MIN_FREE, - LabelType::PSRAM_MAX_FREE_BLOCK, - #endif // BOARD_HAS_PSRAM - #endif // ifdef ESP32 - LabelType::ESP_CHIP_MODEL, - #ifdef ESP32 - LabelType::ESP_CHIP_REVISION, - #endif // ifdef ESP32 - LabelType::FLASH_CHIP_ID, - LabelType::FLASH_CHIP_VENDOR, - LabelType::FLASH_CHIP_MODEL, - LabelType::FLASH_CHIP_REAL_SIZE, - LabelType::FLASH_CHIP_SPEED, - LabelType::FLASH_IDE_MODE, - LabelType::FS_SIZE, - - LabelType::SUNRISE, - LabelType::SUNSET, - LabelType::TIMEZONE_OFFSET, - LabelType::LATITUDE, - LabelType::LONGITUDE, - LabelType::SYSLOG_LOG_LEVEL, - LabelType::SERIAL_LOG_LEVEL, - LabelType::WEB_LOG_LEVEL, - #if FEATURE_SD - LabelType::SD_LOG_LEVEL, - #endif // if FEATURE_SD - - - LabelType::MAX_LABEL - }; - - stream_json_object_values(labels); - stream_comma_newline(); - } - - if (showWifi) { - addHtml(F("\"WiFi\":{\n")); - static const LabelType::Enum labels[] PROGMEM = - { - LabelType::HOST_NAME, - #if FEATURE_MDNS - LabelType::M_DNS, - #endif // if FEATURE_MDNS - LabelType::IP_CONFIG, - LabelType::IP_ADDRESS, -#if FEATURE_USE_IPV6 - LabelType::IP6_LOCAL, - LabelType::IP6_GLOBAL, - LabelType::ENABLE_IPV6, -#endif - LabelType::IP_SUBNET, - LabelType::GATEWAY, - LabelType::STA_MAC, - LabelType::DNS_1, - LabelType::DNS_2, - LabelType::SSID, - LabelType::BSSID, - LabelType::CHANNEL, - LabelType::ENCRYPTION_TYPE_STA, - LabelType::CONNECTED_MSEC, - LabelType::LAST_DISCONNECT_REASON, - LabelType::LAST_DISC_REASON_STR, - LabelType::NUMBER_RECONNECTS, - LabelType::WIFI_STORED_SSID1, - LabelType::WIFI_STORED_SSID2, - LabelType::FORCE_WIFI_BG, - LabelType::RESTART_WIFI_LOST_CONN, - LabelType::FORCE_WIFI_NOSLEEP, -#ifdef SUPPORT_ARP - LabelType::PERIODICAL_GRAT_ARP, -#endif // ifdef SUPPORT_ARP -#ifdef USES_ESPEASY_NOW - LabelType::USE_ESPEASY_NOW, - LabelType::FORCE_ESPEASY_NOW_CHANNEL, -#endif - LabelType::CONNECTION_FAIL_THRESH, -#if FEATURE_SET_WIFI_TX_PWR - LabelType::WIFI_TX_MAX_PWR, - LabelType::WIFI_CUR_TX_PWR, - LabelType::WIFI_SENS_MARGIN, - LabelType::WIFI_SEND_AT_MAX_TX_PWR, -#endif - LabelType::WIFI_NR_EXTRA_SCANS, -#ifdef ESP32 - LabelType::WIFI_PASSIVE_SCAN, -#endif - LabelType::WIFI_USE_LAST_CONN_FROM_RTC, - LabelType::WIFI_RSSI, -#ifndef ESP32 - LabelType::WAIT_WIFI_CONNECT, -#endif - LabelType::HIDDEN_SSID_SLOW_CONNECT, - LabelType::CONNECT_HIDDEN_SSID, - LabelType::SDK_WIFI_AUTORECONNECT, - - LabelType::MAX_LABEL - }; - - stream_json_object_values(labels); - - // TODO: PKR: Add ETH Objects - stream_comma_newline(); - } - - #if FEATURE_ETHERNET - - if (showEthernet) { - addHtml(F("\"Ethernet\":{\n")); - static const LabelType::Enum labels[] PROGMEM = - { - LabelType::ETH_WIFI_MODE, - LabelType::ETH_CONNECTED, - LabelType::ETH_CHIP, - LabelType::ETH_DUPLEX, - LabelType::ETH_SPEED, - LabelType::ETH_STATE, - LabelType::ETH_SPEED_STATE, - - - LabelType::MAX_LABEL - }; - - stream_json_object_values(labels); - stream_comma_newline(); - } - #endif // if FEATURE_ETHERNET - - #if FEATURE_ESPEASY_P2P - if (showNodes) { - bool comma_between = false; - - for (auto it = Nodes.begin(); it != Nodes.end(); ++it) - { - if (it->second.ip[0] != 0) - { - if (comma_between) { - addHtml(','); - } else { - comma_between = true; - addHtml(F("\"nodes\":[\n")); // open json array if >0 nodes - } - - addHtml('{'); - stream_next_json_object_value(F("nr"), it->first); - stream_next_json_object_value(F("name"), - (it->first != Settings.Unit) ? it->second.getNodeName() : Settings.getName()); - - if (it->second.build) { - stream_next_json_object_value(F("build"), formatSystemBuildNr(it->second.build)); - } - - if (it->second.nodeType) { - stream_next_json_object_value(F("platform"), it->second.getNodeTypeDisplayString()); - } - const int8_t rssi = it->second.getRSSI(); - if (rssi < 0) { - stream_next_json_object_value(F("rssi"), rssi); - } - if (it->second.build >= 20107) { - stream_next_json_object_value(F("load"), toString(it->second.getLoad(), 2)); - if (it->second.webgui_portnumber != 80) { - stream_next_json_object_value(F("webport"), it->second.webgui_portnumber); - } - } - stream_next_json_object_value(F("ip"), formatIP(it->second.IP())); -#if FEATURE_USE_IPV6 - if (it->second.hasIPv6_mac_based_link_local) { - stream_next_json_object_value(F("ipv6local"), formatIP(it->second.IPv6_link_local(true), true)); - } - if (it->second.hasIPv6_mac_based_link_global) { - stream_next_json_object_value(F("ipv6global"), formatIP(it->second.IPv6_global())); - } -#endif - stream_last_json_object_value(F("age"), it->second.getAge()); - } // if node info exists - } // for loop - - if (comma_between) { - addHtml(F("],\n")); // close array if >0 nodes - } - } - #endif - } - - taskIndex_t firstTaskIndex = 0; - taskIndex_t lastTaskIndex = TASKS_MAX - 1; - - if (showSpecificTask) - { - firstTaskIndex = taskNr - 1; - lastTaskIndex = taskNr - 1; - } - taskIndex_t lastActiveTaskIndex = 0; - - for (taskIndex_t TaskIndex = firstTaskIndex; TaskIndex <= lastTaskIndex; TaskIndex++) { - if (validPluginID_fullcheck(Settings.getPluginID_for_task(TaskIndex))) { - lastActiveTaskIndex = TaskIndex; - } - } - - if (!showSpecificTask) { - addHtml(F("\"Sensors\":[\n")); - } - - // Keep track of the lowest reported TTL and use that as refresh interval. - unsigned long lowest_ttl_json = 60; - - for (taskIndex_t TaskIndex = firstTaskIndex; TaskIndex <= lastActiveTaskIndex && validTaskIndex(TaskIndex); TaskIndex++) - { - const deviceIndex_t DeviceIndex = getDeviceIndex_from_TaskIndex(TaskIndex); - - if (validDeviceIndex(DeviceIndex)) - { - const unsigned long taskInterval = Settings.TaskDeviceTimer[TaskIndex]; - //LoadTaskSettings(TaskIndex); - addHtml('{', '\n'); - - unsigned long ttl_json = 60; // Default value - - // For simplicity, do the optional values first. - const uint8_t valueCount = getValueCountForTask(TaskIndex); - - if (valueCount != 0) { - if (Settings.TaskDeviceEnabled[TaskIndex]) { - if (taskInterval == 0) { - ttl_json = 1; - } else { - ttl_json = taskInterval; - } - - if (ttl_json < lowest_ttl_json) { - lowest_ttl_json = ttl_json; - } - } - addHtml(F("\"TaskValues\": [\n")); - - struct EventStruct TempEvent(TaskIndex); - - for (uint8_t x = 0; x < valueCount; x++) - { - uint8_t nrDecimals = Cache.getTaskDeviceValueDecimals(TaskIndex, x); - String value = formatUserVarNoCheck(&TempEvent, x); - #if FEATURE_STRING_VARIABLES - bool hasPresentation; - const String presentation = formatUserVarForPresentation(&TempEvent, x, hasPresentation, value, DeviceIndex); - #endif // if FEATURE_STRING_VARIABLES - - if (mustConsiderAsJSONString(value)) { - // Flag as not to treat as a float - nrDecimals = 255; - } - #if FEATURE_TASKVALUE_UNIT_OF_MEASURE - String uom; - const uint8_t uomIndex = Cache.getTaskVarUnitOfMeasure(TaskIndex, x); - if (uomIndex != 0) { - uom = toUnitOfMeasureName(uomIndex); - } - #else // if FEATURE_TASKVALUE_UNIT_OF_MEASURE - const String uom; - #endif // if FEATURE_TASKVALUE_UNIT_OF_MEASURE - handle_json_stream_task_value_data(x + 1, - Cache.getTaskDeviceValueName(TaskIndex, x), - nrDecimals, - value, - #if FEATURE_STRING_VARIABLES - presentation, - #else // if FEATURE_STRING_VARIABLES - EMPTY_STRING, - #endif // if FEATURE_STRING_VARIABLES - uom, - x < (valueCount - 1)); - } - #if FEATURE_STRING_VARIABLES - if (Settings.ShowDerivedTaskValues(TaskIndex)) { - int varNr = VARS_PER_TASK; - String taskName = getTaskDeviceName(TaskIndex); - taskName.toLowerCase(); - String postfix; - const String search = getDerivedValueSearchAndPostfix(taskName, postfix); - - auto it = customStringVar.begin(); - while (it != customStringVar.end()) { - if (it->first.startsWith(search) && it->first.endsWith(postfix)) { - String valueName = it->first.substring(search.length(), it->first.indexOf('-')); - String uom; - String vType; - const String vname2 = getDerivedValueNameUomAndVType(taskName, valueName, uom, vType); - if (!vname2.isEmpty()) { - valueName = vname2; - } - if (!it->second.isEmpty()) { - String value(it->second); - stripEscapeCharacters(value); - value = parseTemplate(value); - ESPEASY_RULES_FLOAT_TYPE floatvalue{}; - uint8_t nrDecimals = 255; // FIXME Use the minimal number of decimals needed - bool hasPresentation; - const String presentation = formatUserVarForPresentation(&TempEvent, INVALID_TASKVAR_INDEX, hasPresentation, value, DeviceIndex, valueName); - - stream_comma_newline(); // Push out a comma and newline - handle_json_stream_task_value_data(varNr + 1, - valueName, - nrDecimals, - value, - presentation, - uom, - false); // No comma here - ++varNr; - } - } - else if (it->first.substring(0, search.length()).compareTo(search) > 0) { - break; - } - ++it; - } - } - #endif // if FEATURE_STRING_VARIABLES - addHtml(F("],\n")); - } - -#if FEATURE_PLUGIN_STATS && FEATURE_CHART_JS - if (showPluginStats && Device[DeviceIndex].PluginStats) { - PluginTaskData_base *taskData = getPluginTaskDataBaseClassOnly(TaskIndex); - if (taskData != nullptr && taskData->nrSamplesPresent() > 0) { - addHtml(F("\"PluginStats\":\n")); - taskData->plot_ChartJS(true); - stream_comma_newline(); - } - } -#endif - - - if (showSpecificTask) { - stream_next_json_object_value(F("TTL"), ttl_json * 1000); - #if FEATURE_TASKVALUE_UNIT_OF_MEASURE - stream_next_json_object_value(F("ShowUoM"), jsonBool(Settings.ShowUnitOfMeasureOnDevicesPage())); - #endif // if FEATURE_TASKVALUE_UNIT_OF_MEASURE - } - - if (showDataAcquisition) { - addHtml(F("\"DataAcquisition\": [\n")); - - for (controllerIndex_t x = 0; x < CONTROLLER_MAX; x++) - { - addHtml('{'); - stream_next_json_object_value(F("Controller"), x + 1); - stream_next_json_object_value(F("IDX"), Settings.TaskDeviceID[x][TaskIndex]); - stream_last_json_object_value(F("Enabled"), jsonBool(Settings.TaskDeviceSendData[x][TaskIndex])); - - if (x < (CONTROLLER_MAX - 1)) { - stream_comma_newline(); - } - } - addHtml(F("],\n")); - } - - if (showTaskDetails) { - stream_next_json_object_value(F("TaskInterval"), taskInterval); - stream_next_json_object_value(F("Type"), getPluginNameFromDeviceIndex(DeviceIndex)); - stream_next_json_object_value(F("TaskName"), getTaskDeviceName(TaskIndex)); - stream_next_json_object_value(F("TaskDeviceNumber"), Settings.getPluginID_for_task(TaskIndex).value); - for(int i = 0; i < 3; i++) { - if (Settings.TaskDevicePin[i][TaskIndex] >= 0) { - stream_next_json_object_value(concat(F("TaskDeviceGPIO"), i + 1) , static_cast(Settings.TaskDevicePin[i][TaskIndex])); - } - } - - #if FEATURE_I2CMULTIPLEXER - uint8_t i2cBus = 0; - #if FEATURE_I2C_MULTIPLE - i2cBus = Settings.getI2CInterface(TaskIndex); - #endif - if (Device[DeviceIndex].Type == DEVICE_TYPE_I2C && isI2CMultiplexerEnabled(i2cBus)) { - #if FEATURE_I2C_MULTIPLE - stream_next_json_object_value(F("I2C_Interface"), static_cast(i2cBus + 1)); - #endif - int8_t channel = Settings.I2C_Multiplexer_Channel[TaskIndex]; - if (bitRead(Settings.I2C_Flags[TaskIndex], I2C_FLAGS_MUX_MULTICHANNEL)) { - addHtml(F("\"I2CBus\" : [")); - uint8_t b = 0; - for (uint8_t c = 0; c < I2CMultiplexerMaxChannels(i2cBus); ++c) { - if (bitRead(channel, c)) { - if (b > 0) { stream_comma_newline(); } - b++; - addHtml(F("\"Multiplexer channel ")); - addHtmlInt(c); - addHtml('"'); - } - } - addHtml(F("],\n")); - } else { - if (channel == -1){ - stream_next_json_object_value(F("I2Cbus"), F("Standard I2C bus")); - } else { - String i2cChannel = concat(F("Multiplexer channel "), channel); - stream_next_json_object_value(F("I2Cbus"), i2cChannel); - } - } - } - #endif // if FEATURE_I2CMULTIPLEXER - } - stream_next_json_object_value(F("TaskEnabled"), - // jsonBool(Settings.TaskDeviceEnabled[TaskIndex].enabled)); - jsonBool(Settings.TaskDeviceEnabled[TaskIndex])); - - stream_last_json_object_value(F("TaskNumber"), TaskIndex + 1); - - if (TaskIndex != lastActiveTaskIndex) { - addHtml(','); - } - addHtml('\n'); - } - } - - if (!showSpecificTask) { - addHtml(F("],\n")); - #if FEATURE_TASKVALUE_UNIT_OF_MEASURE - stream_next_json_object_value(F("ShowUoM"), jsonBool(Settings.ShowUnitOfMeasureOnDevicesPage())); - #endif // if FEATURE_TASKVALUE_UNIT_OF_MEASURE - stream_last_json_object_value(F("TTL"), lowest_ttl_json * 1000); - } - - TXBuffer.endStream(); - STOP_TIMER(HANDLE_SERVING_WEBPAGE_JSON); -} - -void handle_json_stream_task_value_data(uint16_t valueNumber, - const String & valueName, - uint8_t nrDecimals, - const String & value, - const String & presentation, - const String & uom, - bool appendComma) { - addHtml('{'); - stream_next_json_object_value(F("ValueNumber"), valueNumber); - stream_next_json_object_value(F("Name"), valueName); - stream_next_json_object_value(F("NrDecimals"), nrDecimals); - #if FEATURE_STRING_VARIABLES - if (!presentation.isEmpty()) { - stream_next_json_object_value(F("Presentation"), presentation); - } - #endif // if FEATURE_STRING_VARIABLES - if (!uom.isEmpty()) { - stream_next_json_object_value(F("UoM"), uom); - } - stream_last_json_object_value(F("Value"), value); - - if (appendComma) { - stream_comma_newline(); - } -} -// ******************************************************************************** -// JSON formatted timing statistics -// ******************************************************************************** - -#ifdef WEBSERVER_NEW_UI -void handle_timingstats_json() { - TXBuffer.startJsonStream(); - json_init(); - json_open(); - # if FEATURE_TIMING_STATS - jsonStatistics(false); - # endif // if FEATURE_TIMING_STATS - json_close(); - TXBuffer.endStream(); -} - -#endif // WEBSERVER_NEW_UI - -#ifdef WEBSERVER_NEW_UI - -#if FEATURE_ESPEASY_P2P -void handle_nodes_list_json() { - if (!isLoggedIn()) { return; } - TXBuffer.startJsonStream(); - json_init(); - json_open(true); - - for (auto it = Nodes.begin(); it != Nodes.end(); ++it) - { - if (it->second.ip[0] != 0) - { - json_open(); - bool isThisUnit = it->first == Settings.Unit; - - if (isThisUnit) { - json_number(F("thisunit"), String(1)); - } - - json_number(F("first"), String(it->first)); - json_prop(F("name"), isThisUnit ? Settings.getName() : it->second.getNodeName()); - - if (it->second.build) { json_prop(F("build"), formatSystemBuildNr(it->second.build)); } - json_prop(F("type"), it->second.getNodeTypeDisplayString()); - json_prop(F("ip"), formatIP(it->second.ip)); - json_number(F("age"), String(it->second.getAge() / 1000)); // time in seconds - json_close(); - } - } - json_close(true); - TXBuffer.endStream(); -} -#endif - -void handle_buildinfo() { - if (!isLoggedIn()) { return; } - TXBuffer.startJsonStream(); - json_init(); - json_open(); - { - json_open(true, F("plugins")); - - for (deviceIndex_t x; x <= getDeviceCount(); x++) { - const pluginID_t pluginID = getPluginID_from_DeviceIndex(x); - if (validPluginID(pluginID)) { - json_open(); - json_number(F("id"), String(pluginID)); - json_prop(F("name"), getPluginNameFromDeviceIndex(x)); - json_close(); - } - } - json_close(true); - } - { - json_open(true, F("controllers")); - - for (protocolIndex_t x = 0; x < getHighestIncludedCPluginID(); x++) { - if (getCPluginID_from_ProtocolIndex(x) != INVALID_C_PLUGIN_ID) { - json_open(); - json_number(F("id"), String(x + 1)); - json_prop(F("name"), getCPluginNameFromProtocolIndex(x)); - json_close(); - } - } - json_close(true); - } -#if FEATURE_NOTIFIER - { - json_open(true, F("notifications")); - - for (uint8_t x = 0; x < NPLUGIN_MAX; x++) { - if (validNPluginID(NPlugin_id[x])) { - json_open(); - json_number(F("id"), String(x + 1)); - json_prop(F("name"), getNPluginNameFromNotifierIndex(x)); - json_close(); - } - } - json_close(true); - } -#endif - json_prop(LabelType::BUILD_DESC); - json_prop(LabelType::GIT_BUILD); - json_prop(LabelType::SYSTEM_LIBRARIES); -#ifdef ESP32 - json_prop(LabelType::ESP_IDF_SDK_VERSION); -#endif - json_prop(LabelType::PLUGIN_COUNT); - json_prop(LabelType::PLUGIN_DESCRIPTION); - json_close(); - TXBuffer.endStream(); -} - -#endif // WEBSERVER_NEW_UI - - -/*********************************************************************************************\ - Streaming versions directly to TXBuffer -\*********************************************************************************************/ -void stream_to_json_object_value(const __FlashStringHelper * object, const String& value) { - stream_to_json_object_value(String(object), value); -} - -void stream_to_json_object_value(const String& object, const String& value) { - addHtml(wrap_String(object, '"', '"')); - addHtml(':'); - addHtml(to_json_value(value)); -} - -void stream_to_json_object_value(const __FlashStringHelper * object, int value) { - stream_to_json_object_value(String(object), value); -} - -void stream_to_json_object_value(const String& object, int value) { - addHtml(wrap_String(object, '"', '"')); - addHtml(':'); - addHtmlInt(value); -} - -String jsonBool(bool value) { - return boolToString(value); -} - - -// Add JSON formatted data directly to the TXbuffer, including a trailing comma. -void stream_next_json_object_value(const __FlashStringHelper * object, const String& value) { - stream_to_json_object_value(object, value); - stream_comma_newline(); -} - -void stream_next_json_object_value(const __FlashStringHelper * object, String&& value) { - stream_to_json_object_value(object, value); - stream_comma_newline(); -} - -void stream_next_json_object_value(const String& object, const String& value) { - stream_to_json_object_value(object, value); - stream_comma_newline(); -} - -void stream_next_json_object_value(const __FlashStringHelper * object, int value) { - stream_to_json_object_value(object, value); - stream_comma_newline(); -} - -void stream_next_json_object_value(const String& object, int value) { - stream_to_json_object_value(object, value); - stream_comma_newline(); -} - -void stream_newline_close_brace() { - addHtml('\n', '}'); -} - - -// Add JSON formatted data directly to the TXbuffer, including a closing '}' -void stream_last_json_object_value(const __FlashStringHelper * object, const String& value) { - stream_to_json_object_value(object, value); - stream_newline_close_brace(); -} - -void stream_last_json_object_value(const __FlashStringHelper * object, String&& value) { - stream_to_json_object_value(object, value); - stream_newline_close_brace(); -} - -void stream_last_json_object_value(const String& object, const String& value) { - stream_to_json_object_value(object, value); - stream_newline_close_brace(); -} - -void stream_last_json_object_value(const __FlashStringHelper * object, int value) { - stream_to_json_object_value(object, value); - stream_newline_close_brace(); -} - -void stream_json_object_values(const LabelType::Enum labels[]) -{ - size_t i = 0; - LabelType::Enum cur = static_cast(pgm_read_byte(labels + i)); - - while (true) { - const LabelType::Enum next = static_cast(pgm_read_byte(labels + i + 1)); - const bool nextIsLast = next == LabelType::MAX_LABEL; - - if (nextIsLast) { - stream_last_json_object_value(cur); - return; - } else { - stream_next_json_object_value(cur); - } - ++i; - cur = next; - } -} - -void stream_next_json_object_value(LabelType::Enum label) { - stream_next_json_object_value(getLabel(label), getValue(label)); -} - -void stream_last_json_object_value(LabelType::Enum label) { - stream_last_json_object_value(getLabel(label), getValue(label)); +#include "../WebServer/JSON.h" + +#include "../WebServer/ESPEasy_WebServer.h" +#include "../WebServer/JSON.h" +#include "../WebServer/Markup_Forms.h" + +#include "../CustomBuild/CompiletimeDefines.h" + +#include "../DataStructs/TimingStats.h" + +#include "../Globals/Cache.h" +#include "../Globals/Nodes.h" +#include "../Globals/Device.h" +#include "../Globals/Plugins.h" +#include "../Globals/NPlugins.h" + +#include "../Helpers/_Plugin_init.h" +#include "../Helpers/ESPEasyStatistics.h" +#include "../Helpers/ESPEasy_Storage.h" +#include "../Helpers/Numerical.h" +#include "../Helpers/StringConverter.h" +#include "../Helpers/StringProvider.h" +#include "../Helpers/StringGenerator_System.h" + +#include "../../_Plugin_Helper.h" +#include "../../ESPEasy-Globals.h" + +void stream_comma_newline() { + addHtml(',', '\n'); +} + + +// ******************************************************************************** +// Web Interface get CSV value from task +// ******************************************************************************** +#ifdef WEBSERVER_CSVVAL +void handle_csvval() +{ + TXBuffer.startJsonStream(); + const int printHeader = getFormItemInt(F("header"), 1); + bool printHeaderValid = true; + if (printHeader != 1 && printHeader != 0) + { + addHtml(F("ERROR: Header not valid!\n")); + printHeaderValid = false; + } + + const taskIndex_t taskNr = getFormItemInt(F("tasknr"), INVALID_TASK_INDEX); + const bool taskValid = validTaskIndex(taskNr); + if (!taskValid) + { + addHtml(F("ERROR: TaskNr not valid!\n")); + } + + const int INVALID_VALUE_NUM = INVALID_TASKVAR_INDEX + 1; + const taskVarIndex_t valNr = getFormItemInt(F("valnr"), INVALID_VALUE_NUM); + bool valueNumberValid = true; + if (valNr != INVALID_VALUE_NUM && !validTaskVarIndex(valNr)) + { + addHtml(F("ERROR: ValueId not valid!\n")); + valueNumberValid = false; + } + + if (taskValid && valueNumberValid && printHeaderValid) + { + const deviceIndex_t DeviceIndex = getDeviceIndex_from_TaskIndex(taskNr); + + if (validDeviceIndex(DeviceIndex)) + { + const uint8_t taskValCount = getValueCountForTask(taskNr); + + if (printHeader) + { + for (uint8_t x = 0; x < taskValCount; x++) + { + if (valNr == INVALID_VALUE_NUM || valNr == x) + { + addHtml(Cache.getTaskDeviceValueName(taskNr, x)); + if (x != taskValCount - 1) + { + addHtml(';'); + } + } + } + addHtml('\n'); + } + + struct EventStruct TempEvent(taskNr); + + for (uint8_t x = 0; x < taskValCount; x++) + { + if ((valNr == INVALID_VALUE_NUM) || (valNr == x)) + { + addHtml(formatUserVarNoCheck(&TempEvent, x)); + + if (x != taskValCount - 1) + { + addHtml(';'); + } + } + } + addHtml('\n'); + } + } + TXBuffer.endStream(); +} +#endif + +// ******************************************************************************** +// Web Interface JSON page (no password!) +// ******************************************************************************** +void handle_json() +{ + START_TIMER + const taskIndex_t taskNr = getFormItemInt(F("tasknr"), INVALID_TASK_INDEX); + const bool showSpecificTask = validTaskIndex(taskNr); + bool showSystem = true; + bool showWifi = true; + + #if FEATURE_ETHERNET + bool showEthernet = true; + #endif // if FEATURE_ETHERNET + bool showDataAcquisition = true; + bool showTaskDetails = true; + #if FEATURE_ESPEASY_P2P + bool showNodes = true; + #endif + #if FEATURE_PLUGIN_STATS + bool showPluginStats = getFormItemInt(F("showpluginstats"), 0) != 0; + #endif + + if (equals(webArg(F("view")), F("sensorupdate"))) { + showSystem = false; + showWifi = false; + #if FEATURE_ETHERNET + showEthernet = false; + #endif // if FEATURE_ETHERNET + showDataAcquisition = false; + showTaskDetails = false; + #if FEATURE_ESPEASY_P2P + showNodes = false; + #endif + #if FEATURE_PLUGIN_STATS + showPluginStats = hasArg(F("showpluginstats")); + #endif + } + + TXBuffer.startJsonStream(); + + if (!showSpecificTask) + { + addHtml('{'); + + if (showSystem) { + addHtml(F("\"System\":{\n")); + + if (wdcounter > 0) + { + stream_next_json_object_value(LabelType::LOAD_PCT); + stream_next_json_object_value(LabelType::LOOP_COUNT); + } + + static const LabelType::Enum labels[] PROGMEM = + { + LabelType::BUILD_DESC, + LabelType::GIT_BUILD, + LabelType::SYSTEM_LIBRARIES, +#ifdef ESP32 + LabelType::ESP_IDF_SDK_VERSION, +#endif + LabelType::PLUGIN_COUNT, + LabelType::PLUGIN_DESCRIPTION, + LabelType::BUILD_TIME, + LabelType::BINARY_FILENAME, + LabelType::LOCAL_TIME, + #if FEATURE_EXT_RTC + LabelType::EXT_RTC_UTC_TIME, + #endif + LabelType::TIME_SOURCE, + LabelType::TIME_WANDER, + LabelType::ISNTP, + LabelType::UNIT_NR, + LabelType::UNIT_NAME, + LabelType::UPTIME, + LabelType::UPTIME_MS, +#if FEATURE_INTERNAL_TEMPERATURE + LabelType::INTERNAL_TEMPERATURE, +#endif + LabelType::BOOT_TYPE, + LabelType::RESET_REASON, + LabelType::CPU_ECO_MODE, + + #if defined(CORE_POST_2_5_0) || defined(ESP32) + #ifndef LIMIT_BUILD_SIZE + LabelType::HEAP_MAX_FREE_BLOCK, // 7654 + #endif + #endif // if defined(CORE_POST_2_5_0) || defined(ESP32) + #if defined(CORE_POST_2_5_0) + #ifndef LIMIT_BUILD_SIZE + LabelType::HEAP_FRAGMENTATION, // 12 + #endif + #endif // if defined(CORE_POST_2_5_0) + LabelType::FREE_MEM, + #ifdef USE_SECOND_HEAP + LabelType::FREE_HEAP_IRAM, + #endif + LabelType::FREE_STACK, + + #ifdef ESP32 + LabelType::HEAP_SIZE, + LabelType::HEAP_MIN_FREE, + #ifdef BOARD_HAS_PSRAM + LabelType::PSRAM_SIZE, + LabelType::PSRAM_FREE, + LabelType::PSRAM_MIN_FREE, + LabelType::PSRAM_MAX_FREE_BLOCK, + #endif // BOARD_HAS_PSRAM + #endif // ifdef ESP32 + LabelType::ESP_CHIP_MODEL, + #ifdef ESP32 + LabelType::ESP_CHIP_REVISION, + #endif // ifdef ESP32 + LabelType::FLASH_CHIP_ID, + LabelType::FLASH_CHIP_VENDOR, + LabelType::FLASH_CHIP_MODEL, + LabelType::FLASH_CHIP_REAL_SIZE, + LabelType::FLASH_CHIP_SPEED, + LabelType::FLASH_IDE_MODE, + LabelType::FS_SIZE, + + LabelType::SUNRISE, + LabelType::SUNSET, + LabelType::TIMEZONE_OFFSET, + LabelType::LATITUDE, + LabelType::LONGITUDE, + LabelType::SYSLOG_LOG_LEVEL, + LabelType::SERIAL_LOG_LEVEL, + LabelType::WEB_LOG_LEVEL, + #if FEATURE_SD + LabelType::SD_LOG_LEVEL, + #endif // if FEATURE_SD + + + LabelType::MAX_LABEL + }; + + stream_json_object_values(labels); + stream_comma_newline(); + } + + if (showWifi) { + addHtml(F("\"WiFi\":{\n")); + static const LabelType::Enum labels[] PROGMEM = + { + LabelType::HOST_NAME, + #if FEATURE_MDNS + LabelType::M_DNS, + #endif // if FEATURE_MDNS + LabelType::IP_CONFIG, + LabelType::IP_ADDRESS, +#if FEATURE_USE_IPV6 + LabelType::IP6_LOCAL, + LabelType::IP6_GLOBAL, + LabelType::ENABLE_IPV6, +#endif + LabelType::IP_SUBNET, + LabelType::GATEWAY, + LabelType::STA_MAC, + LabelType::DNS_1, + LabelType::DNS_2, + LabelType::SSID, + LabelType::BSSID, + LabelType::CHANNEL, + LabelType::ENCRYPTION_TYPE_STA, + LabelType::CONNECTED_MSEC, + LabelType::LAST_DISCONNECT_REASON, + LabelType::LAST_DISC_REASON_STR, + LabelType::NUMBER_RECONNECTS, + LabelType::WIFI_STORED_SSID1, + LabelType::WIFI_STORED_SSID2, + LabelType::FORCE_WIFI_BG, + LabelType::RESTART_WIFI_LOST_CONN, + LabelType::FORCE_WIFI_NOSLEEP, +#ifdef SUPPORT_ARP + LabelType::PERIODICAL_GRAT_ARP, +#endif // ifdef SUPPORT_ARP +#ifdef USES_ESPEASY_NOW + LabelType::ESPEASY_NOW_ENABLED, + LabelType::ESPEASY_NOW_CHANNEL, + LabelType::ESPEASY_NOW_FORCED_CHANNEL, + LabelType::ESPEASY_NOW_MQTT, + LabelType::ESPEASY_NOW_DISTANCE, +#endif + LabelType::CONNECTION_FAIL_THRESH, +#if FEATURE_SET_WIFI_TX_PWR + LabelType::WIFI_TX_MAX_PWR, + LabelType::WIFI_CUR_TX_PWR, + LabelType::WIFI_SENS_MARGIN, + LabelType::WIFI_SEND_AT_MAX_TX_PWR, +#endif + LabelType::WIFI_NR_EXTRA_SCANS, +#ifdef ESP32 + LabelType::WIFI_PASSIVE_SCAN, +#endif + LabelType::WIFI_USE_LAST_CONN_FROM_RTC, + LabelType::WIFI_RSSI, +#ifndef ESP32 + LabelType::WAIT_WIFI_CONNECT, +#endif + LabelType::HIDDEN_SSID_SLOW_CONNECT, + LabelType::CONNECT_HIDDEN_SSID, + LabelType::SDK_WIFI_AUTORECONNECT, + + LabelType::MAX_LABEL + }; + + stream_json_object_values(labels); + + // TODO: PKR: Add ETH Objects + stream_comma_newline(); + } + + #if FEATURE_ETHERNET + + if (showEthernet) { + addHtml(F("\"Ethernet\":{\n")); + static const LabelType::Enum labels[] PROGMEM = + { + LabelType::ETH_WIFI_MODE, + LabelType::ETH_CONNECTED, + LabelType::ETH_CHIP, + LabelType::ETH_DUPLEX, + LabelType::ETH_SPEED, + LabelType::ETH_STATE, + LabelType::ETH_SPEED_STATE, + + + LabelType::MAX_LABEL + }; + + stream_json_object_values(labels); + stream_comma_newline(); + } + #endif // if FEATURE_ETHERNET + + #if FEATURE_ESPEASY_P2P + if (showNodes) { + bool comma_between = false; + + for (auto it = Nodes.begin(); it != Nodes.end(); ++it) + { + if (it->second.ip[0] != 0) + { + if (comma_between) { + addHtml(','); + } else { + comma_between = true; + addHtml(F("\"nodes\":[\n")); // open json array if >0 nodes + } + + addHtml('{'); + stream_next_json_object_value(F("nr"), it->first); + stream_next_json_object_value(F("name"), + (it->first != Settings.Unit) ? it->second.getNodeName() : Settings.getName()); + + if (it->second.build) { + stream_next_json_object_value(F("build"), formatSystemBuildNr(it->second.build)); + } + + if (it->second.nodeType) { + stream_next_json_object_value(F("platform"), it->second.getNodeTypeDisplayString()); + } + const int8_t rssi = it->second.getRSSI(); + if (rssi < 0) { + stream_next_json_object_value(F("rssi"), rssi); + } + if (it->second.build >= 20107) { + stream_next_json_object_value(F("load"), toString(it->second.getLoad(), 2)); + if (it->second.webgui_portnumber != 80) { + stream_next_json_object_value(F("webport"), it->second.webgui_portnumber); + } + } + stream_next_json_object_value(F("ip"), formatIP(it->second.IP())); +#if FEATURE_USE_IPV6 + if (it->second.hasIPv6_mac_based_link_local) { + stream_next_json_object_value(F("ipv6local"), formatIP(it->second.IPv6_link_local(true), true)); + } + if (it->second.hasIPv6_mac_based_link_global) { + stream_next_json_object_value(F("ipv6global"), formatIP(it->second.IPv6_global())); + } +#endif + stream_last_json_object_value(F("age"), it->second.getAge()); + } // if node info exists + } // for loop + + if (comma_between) { + addHtml(F("],\n")); // close array if >0 nodes + } + } + #endif + } + + taskIndex_t firstTaskIndex = 0; + taskIndex_t lastTaskIndex = TASKS_MAX - 1; + + if (showSpecificTask) + { + firstTaskIndex = taskNr - 1; + lastTaskIndex = taskNr - 1; + } + taskIndex_t lastActiveTaskIndex = 0; + + for (taskIndex_t TaskIndex = firstTaskIndex; TaskIndex <= lastTaskIndex; TaskIndex++) { + if (validPluginID_fullcheck(Settings.getPluginID_for_task(TaskIndex))) { + lastActiveTaskIndex = TaskIndex; + } + } + + if (!showSpecificTask) { + addHtml(F("\"Sensors\":[\n")); + } + + // Keep track of the lowest reported TTL and use that as refresh interval. + unsigned long lowest_ttl_json = 60; + + for (taskIndex_t TaskIndex = firstTaskIndex; TaskIndex <= lastActiveTaskIndex && validTaskIndex(TaskIndex); TaskIndex++) + { + const deviceIndex_t DeviceIndex = getDeviceIndex_from_TaskIndex(TaskIndex); + + if (validDeviceIndex(DeviceIndex)) + { + const unsigned long taskInterval = Settings.TaskDeviceTimer[TaskIndex]; + //LoadTaskSettings(TaskIndex); + addHtml('{', '\n'); + + unsigned long ttl_json = 60; // Default value + + // For simplicity, do the optional values first. + const uint8_t valueCount = getValueCountForTask(TaskIndex); + + if (valueCount != 0) { + if (Settings.TaskDeviceEnabled[TaskIndex]) { + if (taskInterval == 0) { + ttl_json = 1; + } else { + ttl_json = taskInterval; + } + + if (ttl_json < lowest_ttl_json) { + lowest_ttl_json = ttl_json; + } + } + addHtml(F("\"TaskValues\": [\n")); + + struct EventStruct TempEvent(TaskIndex); + + for (uint8_t x = 0; x < valueCount; x++) + { + uint8_t nrDecimals = Cache.getTaskDeviceValueDecimals(TaskIndex, x); + String value = formatUserVarNoCheck(&TempEvent, x); + #if FEATURE_STRING_VARIABLES + bool hasPresentation; + const String presentation = formatUserVarForPresentation(&TempEvent, x, hasPresentation, value, DeviceIndex); + #endif // if FEATURE_STRING_VARIABLES + + if (mustConsiderAsJSONString(value)) { + // Flag as not to treat as a float + nrDecimals = 255; + } + #if FEATURE_TASKVALUE_UNIT_OF_MEASURE + String uom; + const uint8_t uomIndex = Cache.getTaskVarUnitOfMeasure(TaskIndex, x); + if (uomIndex != 0) { + uom = toUnitOfMeasureName(uomIndex); + } + #else // if FEATURE_TASKVALUE_UNIT_OF_MEASURE + const String uom; + #endif // if FEATURE_TASKVALUE_UNIT_OF_MEASURE + handle_json_stream_task_value_data(x + 1, + Cache.getTaskDeviceValueName(TaskIndex, x), + nrDecimals, + value, + #if FEATURE_STRING_VARIABLES + presentation, + #else // if FEATURE_STRING_VARIABLES + EMPTY_STRING, + #endif // if FEATURE_STRING_VARIABLES + uom, + x < (valueCount - 1)); + } + #if FEATURE_STRING_VARIABLES + if (Settings.ShowDerivedTaskValues(TaskIndex)) { + int varNr = VARS_PER_TASK; + String taskName = getTaskDeviceName(TaskIndex); + taskName.toLowerCase(); + String postfix; + const String search = getDerivedValueSearchAndPostfix(taskName, postfix); + + auto it = customStringVar.begin(); + while (it != customStringVar.end()) { + if (it->first.startsWith(search) && it->first.endsWith(postfix)) { + String valueName = it->first.substring(search.length(), it->first.indexOf('-')); + String uom; + String vType; + const String vname2 = getDerivedValueNameUomAndVType(taskName, valueName, uom, vType); + if (!vname2.isEmpty()) { + valueName = vname2; + } + if (!it->second.isEmpty()) { + String value(it->second); + stripEscapeCharacters(value); + value = parseTemplate(value); + ESPEASY_RULES_FLOAT_TYPE floatvalue{}; + uint8_t nrDecimals = 255; // FIXME Use the minimal number of decimals needed + bool hasPresentation; + const String presentation = formatUserVarForPresentation(&TempEvent, INVALID_TASKVAR_INDEX, hasPresentation, value, DeviceIndex, valueName); + + stream_comma_newline(); // Push out a comma and newline + handle_json_stream_task_value_data(varNr + 1, + valueName, + nrDecimals, + value, + presentation, + uom, + false); // No comma here + ++varNr; + } + } + else if (it->first.substring(0, search.length()).compareTo(search) > 0) { + break; + } + ++it; + } + } + #endif // if FEATURE_STRING_VARIABLES + addHtml(F("],\n")); + } + +#if FEATURE_PLUGIN_STATS && FEATURE_CHART_JS + if (showPluginStats && Device[DeviceIndex].PluginStats) { + PluginTaskData_base *taskData = getPluginTaskDataBaseClassOnly(TaskIndex); + if (taskData != nullptr && taskData->nrSamplesPresent() > 0) { + addHtml(F("\"PluginStats\":\n")); + taskData->plot_ChartJS(true); + stream_comma_newline(); + } + } +#endif + + + if (showSpecificTask) { + stream_next_json_object_value(F("TTL"), ttl_json * 1000); + #if FEATURE_TASKVALUE_UNIT_OF_MEASURE + stream_next_json_object_value(F("ShowUoM"), jsonBool(Settings.ShowUnitOfMeasureOnDevicesPage())); + #endif // if FEATURE_TASKVALUE_UNIT_OF_MEASURE + } + + if (showDataAcquisition) { + addHtml(F("\"DataAcquisition\": [\n")); + + for (controllerIndex_t x = 0; x < CONTROLLER_MAX; x++) + { + addHtml('{'); + stream_next_json_object_value(F("Controller"), x + 1); + stream_next_json_object_value(F("IDX"), Settings.TaskDeviceID[x][TaskIndex]); + stream_last_json_object_value(F("Enabled"), jsonBool(Settings.TaskDeviceSendData[x][TaskIndex])); + + if (x < (CONTROLLER_MAX - 1)) { + stream_comma_newline(); + } + } + addHtml(F("],\n")); + } + + if (showTaskDetails) { + stream_next_json_object_value(F("TaskInterval"), taskInterval); + stream_next_json_object_value(F("Type"), getPluginNameFromDeviceIndex(DeviceIndex)); + stream_next_json_object_value(F("TaskName"), getTaskDeviceName(TaskIndex)); + stream_next_json_object_value(F("TaskDeviceNumber"), Settings.getPluginID_for_task(TaskIndex).value); + for(int i = 0; i < 3; i++) { + if (Settings.TaskDevicePin[i][TaskIndex] >= 0) { + stream_next_json_object_value(concat(F("TaskDeviceGPIO"), i + 1) , static_cast(Settings.TaskDevicePin[i][TaskIndex])); + } + } + + #if FEATURE_I2CMULTIPLEXER + uint8_t i2cBus = 0; + #if FEATURE_I2C_MULTIPLE + i2cBus = Settings.getI2CInterface(TaskIndex); + #endif + if (Device[DeviceIndex].Type == DEVICE_TYPE_I2C && isI2CMultiplexerEnabled(i2cBus)) { + #if FEATURE_I2C_MULTIPLE + stream_next_json_object_value(F("I2C_Interface"), static_cast(i2cBus + 1)); + #endif + int8_t channel = Settings.I2C_Multiplexer_Channel[TaskIndex]; + if (bitRead(Settings.I2C_Flags[TaskIndex], I2C_FLAGS_MUX_MULTICHANNEL)) { + addHtml(F("\"I2CBus\" : [")); + uint8_t b = 0; + for (uint8_t c = 0; c < I2CMultiplexerMaxChannels(i2cBus); ++c) { + if (bitRead(channel, c)) { + if (b > 0) { stream_comma_newline(); } + b++; + addHtml(F("\"Multiplexer channel ")); + addHtmlInt(c); + addHtml('"'); + } + } + addHtml(F("],\n")); + } else { + if (channel == -1){ + stream_next_json_object_value(F("I2Cbus"), F("Standard I2C bus")); + } else { + String i2cChannel = concat(F("Multiplexer channel "), channel); + stream_next_json_object_value(F("I2Cbus"), i2cChannel); + } + } + } + #endif // if FEATURE_I2CMULTIPLEXER + } + stream_next_json_object_value(F("TaskEnabled"), + // jsonBool(Settings.TaskDeviceEnabled[TaskIndex].enabled)); + jsonBool(Settings.TaskDeviceEnabled[TaskIndex])); + + stream_last_json_object_value(F("TaskNumber"), TaskIndex + 1); + + if (TaskIndex != lastActiveTaskIndex) { + addHtml(','); + } + addHtml('\n'); + } + } + + if (!showSpecificTask) { + addHtml(F("],\n")); + #if FEATURE_TASKVALUE_UNIT_OF_MEASURE + stream_next_json_object_value(F("ShowUoM"), jsonBool(Settings.ShowUnitOfMeasureOnDevicesPage())); + #endif // if FEATURE_TASKVALUE_UNIT_OF_MEASURE + stream_last_json_object_value(F("TTL"), lowest_ttl_json * 1000); + } + + TXBuffer.endStream(); + STOP_TIMER(HANDLE_SERVING_WEBPAGE_JSON); +} + +void handle_json_stream_task_value_data(uint16_t valueNumber, + const String & valueName, + uint8_t nrDecimals, + const String & value, + const String & presentation, + const String & uom, + bool appendComma) { + addHtml('{'); + stream_next_json_object_value(F("ValueNumber"), valueNumber); + stream_next_json_object_value(F("Name"), valueName); + stream_next_json_object_value(F("NrDecimals"), nrDecimals); + #if FEATURE_STRING_VARIABLES + if (!presentation.isEmpty()) { + stream_next_json_object_value(F("Presentation"), presentation); + } + #endif // if FEATURE_STRING_VARIABLES + if (!uom.isEmpty()) { + stream_next_json_object_value(F("UoM"), uom); + } + stream_last_json_object_value(F("Value"), value); + + if (appendComma) { + stream_comma_newline(); + } +} +// ******************************************************************************** +// JSON formatted timing statistics +// ******************************************************************************** + +#ifdef WEBSERVER_NEW_UI +void handle_timingstats_json() { + TXBuffer.startJsonStream(); + json_init(); + json_open(); + # if FEATURE_TIMING_STATS + jsonStatistics(false); + # endif // if FEATURE_TIMING_STATS + json_close(); + TXBuffer.endStream(); +} + +#endif // WEBSERVER_NEW_UI + +#ifdef WEBSERVER_NEW_UI + +#if FEATURE_ESPEASY_P2P +void handle_nodes_list_json() { + if (!isLoggedIn()) { return; } + TXBuffer.startJsonStream(); + json_init(); + json_open(true); + + for (auto it = Nodes.begin(); it != Nodes.end(); ++it) + { + if (it->second.ip[0] != 0) + { + json_open(); + bool isThisUnit = it->first == Settings.Unit; + + if (isThisUnit) { + json_number(F("thisunit"), String(1)); + } + + json_number(F("first"), String(it->first)); + json_prop(F("name"), isThisUnit ? Settings.getName() : it->second.getNodeName()); + + if (it->second.build) { json_prop(F("build"), formatSystemBuildNr(it->second.build)); } + json_prop(F("type"), it->second.getNodeTypeDisplayString()); + json_prop(F("ip"), formatIP(it->second.ip)); + json_number(F("age"), String(it->second.getAge() / 1000)); // time in seconds + json_close(); + } + } + json_close(true); + TXBuffer.endStream(); +} +#endif + +void handle_buildinfo() { + if (!isLoggedIn()) { return; } + TXBuffer.startJsonStream(); + json_init(); + json_open(); + { + json_open(true, F("plugins")); + + for (deviceIndex_t x; x <= getDeviceCount(); x++) { + const pluginID_t pluginID = getPluginID_from_DeviceIndex(x); + if (validPluginID(pluginID)) { + json_open(); + json_number(F("id"), String(pluginID)); + json_prop(F("name"), getPluginNameFromDeviceIndex(x)); + json_close(); + } + } + json_close(true); + } + { + json_open(true, F("controllers")); + + for (protocolIndex_t x = 0; x < getHighestIncludedCPluginID(); x++) { + if (getCPluginID_from_ProtocolIndex(x) != INVALID_C_PLUGIN_ID) { + json_open(); + json_number(F("id"), String(x + 1)); + json_prop(F("name"), getCPluginNameFromProtocolIndex(x)); + json_close(); + } + } + json_close(true); + } +#if FEATURE_NOTIFIER + { + json_open(true, F("notifications")); + + for (uint8_t x = 0; x < NPLUGIN_MAX; x++) { + if (validNPluginID(NPlugin_id[x])) { + json_open(); + json_number(F("id"), String(x + 1)); + json_prop(F("name"), getNPluginNameFromNotifierIndex(x)); + json_close(); + } + } + json_close(true); + } +#endif + json_prop(LabelType::BUILD_DESC); + json_prop(LabelType::GIT_BUILD); + json_prop(LabelType::SYSTEM_LIBRARIES); +#ifdef ESP32 + json_prop(LabelType::ESP_IDF_SDK_VERSION); +#endif + json_prop(LabelType::PLUGIN_COUNT); + json_prop(LabelType::PLUGIN_DESCRIPTION); + json_close(); + TXBuffer.endStream(); +} + +#endif // WEBSERVER_NEW_UI + + +/*********************************************************************************************\ + Streaming versions directly to TXBuffer +\*********************************************************************************************/ +void stream_to_json_object_value(const __FlashStringHelper * object, const String& value) { + stream_to_json_object_value(String(object), value); +} + +void stream_to_json_object_value(const String& object, const String& value) { + addHtml(wrap_String(object, '"', '"')); + addHtml(':'); + addHtml(to_json_value(value)); +} + +void stream_to_json_object_value(const __FlashStringHelper * object, int value) { + stream_to_json_object_value(String(object), value); +} + +void stream_to_json_object_value(const String& object, int value) { + addHtml(wrap_String(object, '"', '"')); + addHtml(':'); + addHtmlInt(value); +} + +String jsonBool(bool value) { + return boolToString(value); +} + + +// Add JSON formatted data directly to the TXbuffer, including a trailing comma. +void stream_next_json_object_value(const __FlashStringHelper * object, const String& value) { + stream_to_json_object_value(object, value); + stream_comma_newline(); +} + +void stream_next_json_object_value(const __FlashStringHelper * object, String&& value) { + stream_to_json_object_value(object, value); + stream_comma_newline(); +} + +void stream_next_json_object_value(const String& object, const String& value) { + stream_to_json_object_value(object, value); + stream_comma_newline(); +} + +void stream_next_json_object_value(const __FlashStringHelper * object, int value) { + stream_to_json_object_value(object, value); + stream_comma_newline(); +} + +void stream_next_json_object_value(const String& object, int value) { + stream_to_json_object_value(object, value); + stream_comma_newline(); +} + +void stream_newline_close_brace() { + addHtml('\n', '}'); +} + + +// Add JSON formatted data directly to the TXbuffer, including a closing '}' +void stream_last_json_object_value(const __FlashStringHelper * object, const String& value) { + stream_to_json_object_value(object, value); + stream_newline_close_brace(); +} + +void stream_last_json_object_value(const __FlashStringHelper * object, String&& value) { + stream_to_json_object_value(object, value); + stream_newline_close_brace(); +} + +void stream_last_json_object_value(const String& object, const String& value) { + stream_to_json_object_value(object, value); + stream_newline_close_brace(); +} + +void stream_last_json_object_value(const __FlashStringHelper * object, int value) { + stream_to_json_object_value(object, value); + stream_newline_close_brace(); +} + +void stream_json_object_values(const LabelType::Enum labels[]) +{ + size_t i = 0; + LabelType::Enum cur = static_cast(pgm_read_byte(labels + i)); + + while (true) { + const LabelType::Enum next = static_cast(pgm_read_byte(labels + i + 1)); + const bool nextIsLast = next == LabelType::MAX_LABEL; + + if (nextIsLast) { + stream_last_json_object_value(cur); + return; + } else { + stream_next_json_object_value(cur); + } + ++i; + cur = next; + } +} + +void stream_next_json_object_value(LabelType::Enum label) { + stream_next_json_object_value(getLabel(label), getValue(label)); +} + +void stream_last_json_object_value(LabelType::Enum label) { + stream_last_json_object_value(getLabel(label), getValue(label)); } \ No newline at end of file diff --git a/src/src/WebServer/RootPage.cpp b/src/src/WebServer/RootPage.cpp index df0749f29f..0cb0192aa6 100644 --- a/src/src/WebServer/RootPage.cpp +++ b/src/src/WebServer/RootPage.cpp @@ -1,476 +1,476 @@ -#include "../WebServer/RootPage.h" - - -#ifdef WEBSERVER_ROOT - -# include "../WebServer/ESPEasy_WebServer.h" -# include "../WebServer/HTML_wrappers.h" -# include "../WebServer/LoadFromFS.h" -# include "../WebServer/Markup.h" -# include "../WebServer/Markup_Buttons.h" -# include "../WebServer/Markup_Forms.h" - -# include "../Commands/ExecuteCommand.h" -# include "../ESPEasyCore/ESPEasyNetwork.h" -# include "../Globals/ESPEasy_time.h" -# include "../Globals/ESPEasyWiFiEvent.h" -# include "../Globals/MainLoopCommand.h" -# include "../Globals/NetworkState.h" -# include "../Globals/Nodes.h" -# include "../Globals/Settings.h" -# include "../Globals/Statistics.h" -# include "../Helpers/ESPEasy_Storage.h" -# include "../Helpers/Memory.h" -# include "../Helpers/Misc.h" -# include "../Helpers/StringGenerator_System.h" -# include "../Helpers/WebServer_commandHelper.h" - - -# include "../../ESPEasy-Globals.h" - -# if FEATURE_MQTT -# include "../Globals/MQTT.h" -# include "../ESPEasyCore/Controller.h" // For finding enabled MQTT controller -# endif // if FEATURE_MQTT - - -# ifndef MAIN_PAGE_SHOW_BASIC_INFO_NOT_LOGGED_IN - # define MAIN_PAGE_SHOW_BASIC_INFO_NOT_LOGGED_IN false -# endif // ifndef MAIN_PAGE_SHOW_BASIC_INFO_NOT_LOGGED_IN - -// Define main page elements present -# ifndef MAIN_PAGE_SHOW_SYSINFO_BUTTON - # define MAIN_PAGE_SHOW_SYSINFO_BUTTON true -# endif // ifndef MAIN_PAGE_SHOW_SYSINFO_BUTTON - -# ifndef MAIN_PAGE_SHOW_WiFi_SETUP_BUTTON - # define MAIN_PAGE_SHOW_WiFi_SETUP_BUTTON false -# endif // ifndef MAIN_PAGE_SHOW_WiFi_SETUP_BUTTON - -# ifndef MAIN_PAGE_SHOW_NODE_LIST_BUILD - # define MAIN_PAGE_SHOW_NODE_LIST_BUILD true -# endif // ifndef MAIN_PAGE_SHOW_NODE_LIST_BUILD -# ifndef MAIN_PAGE_SHOW_NODE_LIST_TYPE - # define MAIN_PAGE_SHOW_NODE_LIST_TYPE true -# endif // ifndef MAIN_PAGE_SHOW_NODE_LIST_TYPE - - -// ******************************************************************************** -// Web Interface root page -// ******************************************************************************** -void handle_root() { - # ifdef USE_SECOND_HEAP - HeapSelectDram ephemeral; - # endif // ifdef USE_SECOND_HEAP - - # ifndef BUILD_NO_RAM_TRACKER - checkRAM(F("handle_root")); - # endif // ifndef BUILD_NO_RAM_TRACKER - - if (captivePortal()) { // If captive portal redirect instead of displaying the page. - return; - } - - // if Wifi setup, launch setup wizard if AP_DONT_FORCE_SETUP is not set. - if (WiFiEventData.wifiSetup && !Settings.ApDontForceSetup()) - { - web_server.send_P(200, (PGM_P)F("text/html"), (PGM_P)F("")); - return; - } - - if (!MAIN_PAGE_SHOW_BASIC_INFO_NOT_LOGGED_IN) { - if (!isLoggedIn()) { return; } - } - - const bool loggedIn = isLoggedIn(false); - - navMenuIndex = 0; - - // if index.htm exists on FS serve that one (first check if gziped version exists) - if (loadFromFS(F("/index.htm.gz"))) { return; } - - if (loadFromFS(F("/index.htm"))) { return; } - - TXBuffer.startStream(); - - boolean rebootCmd = false; - String sCommand = webArg(F("cmd")); - rebootCmd = strcasecmp_P(sCommand.c_str(), PSTR("reboot")) == 0; - sendHeadandTail_stdtemplate(_HEAD, rebootCmd); - - int freeMem = ESP.getFreeHeap(); - - // TODO: move this to handle_tools, from where it is actually called? - - // have to disconnect or reboot from within the main loop - // because the webconnection is still active at this point - // disconnect here could result into a crash/reboot... - if (strcasecmp_P(sCommand.c_str(), PSTR("wifidisconnect")) == 0) - { - #ifndef BUILD_MINIMAL_OTA - addLog(LOG_LEVEL_INFO, F("WIFI : Disconnecting...")); - #endif - cmd_within_mainloop = CMD_WIFI_DISCONNECT; - addHtml(F("OK")); - } else if (strcasecmp_P(sCommand.c_str(), PSTR("reboot")) == 0) - { - #ifndef BUILD_MINIMAL_OTA - addLog(LOG_LEVEL_INFO, F(" : Rebooting...")); - #endif - cmd_within_mainloop = CMD_REBOOT; - addHtml(F("OK")); - } else if (strcasecmp_P(sCommand.c_str(), PSTR("reset")) == 0) - { - if (loggedIn) { - #ifndef BUILD_MINIMAL_OTA - addLog(LOG_LEVEL_INFO, F(" : factory reset...")); - #endif - cmd_within_mainloop = CMD_REBOOT; - addHtml(F( - "OK. Please wait > 1 min and connect to Access point.

PW=configesp
URL=192.168.4.1")); - TXBuffer.endStream(); - ExecuteCommand_internal({EventValueSource::Enum::VALUE_SOURCE_HTTP, sCommand.c_str()}, true); - return; - } - } else { - if (loggedIn) { - handle_command_from_web(EventValueSource::Enum::VALUE_SOURCE_HTTP, sCommand); - printToWeb = false; - printToWebJSON = false; - } - - addHtml(F("")); - html_table_class_normal(); - addFormHeader(F("System Info")); - - addRowLabelValue(LabelType::UNIT_NR); - addRowLabelValue(LabelType::GIT_BUILD); - addRowLabel(LabelType::LOCAL_TIME); - - if (node_time.systemTimePresent()) - { - addHtml(getValue(LabelType::LOCAL_TIME)); - } - else { - addHtml(F("No system time source")); - } - addRowLabelValue(LabelType::TIME_SOURCE); - - addRowLabel(LabelType::UPTIME); - { - addHtml(getExtendedValue(LabelType::UPTIME)); - } - addRowLabel(LabelType::LOAD_PCT); - - if (wdcounter > 0) - { - addHtml(strformat( - F("%.2f [%%] (LC=%d)"), - getCPUload(), - getLoopCountPerSec())); - } - -#if FEATURE_INTERNAL_TEMPERATURE - addRowLabelValue(LabelType::INTERNAL_TEMPERATURE); -#endif - { - addRowLabel(LabelType::FREE_MEM); - addHtmlInt(freeMem); - addUnit(getFormUnit(LabelType::FREE_MEM)); -# ifndef BUILD_NO_RAM_TRACKER - addHtml(strformat( - F(" (%d - %s)"), - lowestRAM, - lowestRAMfunction.c_str())); -# endif // ifndef BUILD_NO_RAM_TRACKER - } - { -# ifdef USE_SECOND_HEAP - addRowLabelValue(LabelType::FREE_HEAP_IRAM); -# endif // ifdef USE_SECOND_HEAP - } - { - addRowLabel(LabelType::FREE_STACK); - addHtmlInt(getCurrentFreeStack()); - addUnit(getFormUnit(LabelType::FREE_STACK)); -# ifndef BUILD_NO_RAM_TRACKER - addHtml(strformat( - F(" (%d - %s)"), - lowestFreeStack, - lowestFreeStackfunction.c_str())); -# endif // ifndef BUILD_NO_RAM_TRACKER - } - - # if FEATURE_ETHERNET - addRowLabelValue(LabelType::ETH_WIFI_MODE); - # endif // if FEATURE_ETHERNET - - if (!WiFiEventData.WiFiDisconnected()) - { - addRowLabelValue(LabelType::IP_ADDRESS); -#if FEATURE_USE_IPV6 - if (Settings.EnableIPv6()) { - addRowLabelValue(LabelType::IP6_LOCAL); - // Do not show global IPv6 on the root page - } -#endif - addRowLabel(LabelType::WIFI_RSSI); - addHtml(strformat( - F("%d [dBm] (%s)"), - WiFi.RSSI(), - WiFi.SSID().c_str())); - } - - # if FEATURE_ETHERNET - - if (active_network_medium == NetworkMedium_t::Ethernet) { - addRowLabelValue(LabelType::ETH_SPEED_STATE); - addRowLabelValue(LabelType::ETH_IP_ADDRESS); -#if FEATURE_USE_IPV6 - if (Settings.EnableIPv6()) { - addRowLabelValue(LabelType::ETH_IP6_LOCAL); - // Do not show global IPv6 on the root page - } -#endif - } - # endif // if FEATURE_ETHERNET - - # if FEATURE_MDNS - { - addRowLabel(LabelType::M_DNS); - addHtml(F("")); - addHtml(url); - addHtml(F("")); - } - # endif // if FEATURE_MDNS - - # if FEATURE_MQTT - { - if (validControllerIndex(firstEnabledMQTT_ControllerIndex())) { - addRowLabel(F("MQTT Client Connected")); - addEnabled(MQTTclient_connected); - } - } - # endif // if FEATURE_MQTT - - - # if MAIN_PAGE_SHOW_SYSINFO_BUTTON - html_TR_TD(); - html_TD(); - addButton(F("sysinfo"), F("More info")); - # endif // if MAIN_PAGE_SHOW_SYSINFO_BUTTON - # if MAIN_PAGE_SHOW_WiFi_SETUP_BUTTON - html_TR_TD(); - html_TD(); - addButton(F("setup"), F("WiFi Setup")); - # endif // if MAIN_PAGE_SHOW_WiFi_SETUP_BUTTON - - if (loggedIn) { - if (printWebString.length() > 0) - { - html_BR(); - html_BR(); - addFormHeader(F("Command Argument")); - addRowLabel(F("Command")); - addHtml(sCommand); - - addHtml(F("Command Output
")); - free_string(printWebString); - } - } - html_end_table(); - - # if FEATURE_ESPEASY_P2P - html_BR(); - - if ((Settings.Unit == 0) && - (Settings.UDPPort != 0)) { addFormNote(F("Warning: Unit number is 0, please change it if you want to send data to other units.")); } - html_BR(); - html_table_class_multirow_noborder(); - html_TR(); - html_table_header(F("Node List")); - html_table_header(F("Name")); - - if (MAIN_PAGE_SHOW_NODE_LIST_BUILD) { - html_table_header(getLabel(LabelType::BUILD_DESC)); - } - - if (MAIN_PAGE_SHOW_NODE_LIST_TYPE) { - html_table_header(F("Type")); - } - html_table_header(F("IP"), 160); // Should fit "255.255.255.255" - html_table_header(F("Load")); - html_table_header(F("Age (s)")); - # ifdef USES_ESPEASY_NOW - - if (Settings.UseESPEasyNow()) { - html_table_header(F("Dist")); - html_table_header(F("Peer Info"), 160); - } - # endif // ifdef USES_ESPEASY_NOW - - for (auto it = Nodes.begin(); it != Nodes.end(); ++it) - { - if (it->second.valid()) - { - bool isThisUnit = it->first == Settings.Unit; - - if (isThisUnit) { - html_TR_TD_highlight(); - } - else { - html_TR_TD(); - } - - addHtml(F("Unit ")); - addHtmlInt(it->first); - html_TD(); - - if (isThisUnit) { - addHtml(Settings.getName()); - } - else { - addHtml(it->second.getNodeName()); - } - html_TD(); - - if (MAIN_PAGE_SHOW_NODE_LIST_BUILD) { - if (it->second.build) { - addHtml(formatSystemBuildNr(it->second.build)); - } - html_TD(); - } - - if (MAIN_PAGE_SHOW_NODE_LIST_TYPE) { - addHtml(it->second.getNodeTypeDisplayString()); - html_TD(); - } - - if (it->second.ip[0] != 0 -#if FEATURE_USE_IPV6 - || (Settings.EnableIPv6() && - (it->second.hasIPv6_mac_based_link_local || - it->second.hasIPv6_mac_based_link_global) - ) -#endif - ) - { - IPAddress ip = it->second.IP(); - const uint16_t port = it->second.webgui_portnumber; - -#if FEATURE_USE_IPV6 - bool isIPv6 = false; - if (Settings.EnableIPv6()) { - if (it->second.hasIPv6_mac_based_link_local) { - ip = it->second.IPv6_link_local(true); - isIPv6 = true; - } else if (it->second.hasIPv6_mac_based_link_global) { - ip = it->second.IPv6_global(); - isIPv6 = true; - } - } - if (it->second.hasIPv4 && it->second.hasIPv6()) { - // Add 2 buttons for IPv4 and IPv6 address - html_add_wide_button_prefix(); - addHtml(F("http://")); - addHtml(wrap_String(formatIP(ip), '[', ']')); - if ((port != 0) && (port != 80)) { - addHtml(':'); - addHtmlInt(port); - } - addHtml('\'', '>'); - addHtml(formatIP(ip)); - addHtml(F("")); - - // Now prepare 2nd button prefix - addHtml(F("
")); - html_add_wide_button_prefix(); - ip = it->second.IP(); - isIPv6 = false; - } else { - // Add single wide button - html_add_wide_button_prefix(); - } -#else - html_add_wide_button_prefix(); -#endif - addHtml(F("http://")); -#if FEATURE_USE_IPV6 - - if (isIPv6) { - addHtml(wrap_String(formatIP(ip), '[', ']')); - } else { - addHtml(formatIP(ip)); - } - #else - addHtml(formatIP(ip)); - #endif - - if ((port != 0) && (port != 80)) { - addHtml(':'); - addHtmlInt(port); - } - addHtml('\'', '>'); - addHtml(formatIP(ip)); - addHtml(F("")); - } - html_TD(); - const float load = it->second.getLoad(); - - if (load > 0.1) { - addHtmlFloat(load); - } - html_TD(); - addHtmlInt(static_cast(it->second.getAge() / 1000)); // time in seconds - # ifdef USES_ESPEASY_NOW - - if (Settings.UseESPEasyNow()) { - html_TD(); - - if (it->second.distance != 255) { - addHtmlInt(it->second.distance); - } - html_TD(); - - if (it->second.ESPEasyNowPeer) { - addHtml(F(ESPEASY_NOW_NAME)); - addHtml(' '); - addHtml(it->second.ESPEasy_Now_MAC().toString()); - addHtml(F(" (ch: ")); - addHtmlInt(it->second.channel); - int8_t rssi = it->second.getRSSI(); - - if (rssi < 0) { - addHtml(' '); - addHtmlInt(rssi); - } - addHtml(')'); - const ESPEasy_now_traceroute_struct *trace = Nodes.getDiscoveryRoute(it->second.unit); - - if (trace != nullptr) { - addHtml(' '); - addHtml(trace->toString()); - } - } - } - # endif // ifdef USES_ESPEASY_NOW - } - } - - html_end_table(); - # endif // if FEATURE_ESPEASY_P2P - html_end_form(); - - free_string(printWebString); - printToWeb = false; - sendHeadandTail_stdtemplate(_TAIL); - } - TXBuffer.endStream(); -} - -#endif // ifdef WEBSERVER_ROOT +#include "../WebServer/RootPage.h" + + +#ifdef WEBSERVER_ROOT + +# include "../WebServer/ESPEasy_WebServer.h" +# include "../WebServer/HTML_wrappers.h" +# include "../WebServer/LoadFromFS.h" +# include "../WebServer/Markup.h" +# include "../WebServer/Markup_Buttons.h" +# include "../WebServer/Markup_Forms.h" + +# include "../Commands/ExecuteCommand.h" +# include "../ESPEasyCore/ESPEasyNetwork.h" +# include "../Globals/ESPEasy_time.h" +# include "../Globals/ESPEasyWiFiEvent.h" +# include "../Globals/MainLoopCommand.h" +# include "../Globals/NetworkState.h" +# include "../Globals/Nodes.h" +# include "../Globals/Settings.h" +# include "../Globals/Statistics.h" +# include "../Helpers/ESPEasy_Storage.h" +# include "../Helpers/Memory.h" +# include "../Helpers/Misc.h" +# include "../Helpers/StringGenerator_System.h" +# include "../Helpers/WebServer_commandHelper.h" + + +# include "../../ESPEasy-Globals.h" + +# if FEATURE_MQTT +# include "../Globals/MQTT.h" +# include "../ESPEasyCore/Controller.h" // For finding enabled MQTT controller +# endif // if FEATURE_MQTT + + +# ifndef MAIN_PAGE_SHOW_BASIC_INFO_NOT_LOGGED_IN + # define MAIN_PAGE_SHOW_BASIC_INFO_NOT_LOGGED_IN false +# endif // ifndef MAIN_PAGE_SHOW_BASIC_INFO_NOT_LOGGED_IN + +// Define main page elements present +# ifndef MAIN_PAGE_SHOW_SYSINFO_BUTTON + # define MAIN_PAGE_SHOW_SYSINFO_BUTTON true +# endif // ifndef MAIN_PAGE_SHOW_SYSINFO_BUTTON + +# ifndef MAIN_PAGE_SHOW_WiFi_SETUP_BUTTON + # define MAIN_PAGE_SHOW_WiFi_SETUP_BUTTON false +# endif // ifndef MAIN_PAGE_SHOW_WiFi_SETUP_BUTTON + +# ifndef MAIN_PAGE_SHOW_NODE_LIST_BUILD + # define MAIN_PAGE_SHOW_NODE_LIST_BUILD true +# endif // ifndef MAIN_PAGE_SHOW_NODE_LIST_BUILD +# ifndef MAIN_PAGE_SHOW_NODE_LIST_TYPE + # define MAIN_PAGE_SHOW_NODE_LIST_TYPE true +# endif // ifndef MAIN_PAGE_SHOW_NODE_LIST_TYPE + + +// ******************************************************************************** +// Web Interface root page +// ******************************************************************************** +void handle_root() { + # ifdef USE_SECOND_HEAP + HeapSelectDram ephemeral; + # endif // ifdef USE_SECOND_HEAP + + # ifndef BUILD_NO_RAM_TRACKER + checkRAM(F("handle_root")); + # endif // ifndef BUILD_NO_RAM_TRACKER + + if (captivePortal()) { // If captive portal redirect instead of displaying the page. + return; + } + + // if Wifi setup, launch setup wizard if AP_DONT_FORCE_SETUP is not set. + if (WiFiEventData.wifiSetup && !Settings.ApDontForceSetup()) + { + web_server.send_P(200, (PGM_P)F("text/html"), (PGM_P)F("")); + return; + } + + if (!MAIN_PAGE_SHOW_BASIC_INFO_NOT_LOGGED_IN) { + if (!isLoggedIn()) { return; } + } + + const bool loggedIn = isLoggedIn(false); + + navMenuIndex = 0; + + // if index.htm exists on FS serve that one (first check if gziped version exists) + if (loadFromFS(F("/index.htm.gz"))) { return; } + + if (loadFromFS(F("/index.htm"))) { return; } + + TXBuffer.startStream(); + + boolean rebootCmd = false; + String sCommand = webArg(F("cmd")); + rebootCmd = strcasecmp_P(sCommand.c_str(), PSTR("reboot")) == 0; + sendHeadandTail_stdtemplate(_HEAD, rebootCmd); + + int freeMem = ESP.getFreeHeap(); + + // TODO: move this to handle_tools, from where it is actually called? + + // have to disconnect or reboot from within the main loop + // because the webconnection is still active at this point + // disconnect here could result into a crash/reboot... + if (strcasecmp_P(sCommand.c_str(), PSTR("wifidisconnect")) == 0) + { + #ifndef BUILD_MINIMAL_OTA + addLog(LOG_LEVEL_INFO, F("WIFI : Disconnecting...")); + #endif + cmd_within_mainloop = CMD_WIFI_DISCONNECT; + addHtml(F("OK")); + } else if (strcasecmp_P(sCommand.c_str(), PSTR("reboot")) == 0) + { + #ifndef BUILD_MINIMAL_OTA + addLog(LOG_LEVEL_INFO, F(" : Rebooting...")); + #endif + cmd_within_mainloop = CMD_REBOOT; + addHtml(F("OK")); + } else if (strcasecmp_P(sCommand.c_str(), PSTR("reset")) == 0) + { + if (loggedIn) { + #ifndef BUILD_MINIMAL_OTA + addLog(LOG_LEVEL_INFO, F(" : factory reset...")); + #endif + cmd_within_mainloop = CMD_REBOOT; + addHtml(F( + "OK. Please wait > 1 min and connect to Access point.

PW=configesp
URL=192.168.4.1")); + TXBuffer.endStream(); + ExecuteCommand_internal({EventValueSource::Enum::VALUE_SOURCE_HTTP, sCommand.c_str()}, true); + return; + } + } else { + if (loggedIn) { + handle_command_from_web(EventValueSource::Enum::VALUE_SOURCE_HTTP, sCommand); + printToWeb = false; + printToWebJSON = false; + } + + addHtml(F("")); + html_table_class_normal(); + addFormHeader(F("System Info")); + + addRowLabelValue(LabelType::UNIT_NR); + addRowLabelValue(LabelType::GIT_BUILD); + addRowLabel(LabelType::LOCAL_TIME); + + if (node_time.systemTimePresent()) + { + addHtml(getValue(LabelType::LOCAL_TIME)); + } + else { + addHtml(F("No system time source")); + } + addRowLabelValue(LabelType::TIME_SOURCE); + + addRowLabel(LabelType::UPTIME); + { + addHtml(getExtendedValue(LabelType::UPTIME)); + } + addRowLabel(LabelType::LOAD_PCT); + + if (wdcounter > 0) + { + addHtml(strformat( + F("%.2f [%%] (LC=%d)"), + getCPUload(), + getLoopCountPerSec())); + } + +#if FEATURE_INTERNAL_TEMPERATURE + addRowLabelValue(LabelType::INTERNAL_TEMPERATURE); +#endif + { + addRowLabel(LabelType::FREE_MEM); + addHtmlInt(freeMem); + addUnit(getFormUnit(LabelType::FREE_MEM)); +# ifndef BUILD_NO_RAM_TRACKER + addHtml(strformat( + F(" (%d - %s)"), + lowestRAM, + lowestRAMfunction.c_str())); +# endif // ifndef BUILD_NO_RAM_TRACKER + } + { +# ifdef USE_SECOND_HEAP + addRowLabelValue(LabelType::FREE_HEAP_IRAM); +# endif // ifdef USE_SECOND_HEAP + } + { + addRowLabel(LabelType::FREE_STACK); + addHtmlInt(getCurrentFreeStack()); + addUnit(getFormUnit(LabelType::FREE_STACK)); +# ifndef BUILD_NO_RAM_TRACKER + addHtml(strformat( + F(" (%d - %s)"), + lowestFreeStack, + lowestFreeStackfunction.c_str())); +# endif // ifndef BUILD_NO_RAM_TRACKER + } + + # if FEATURE_ETHERNET + addRowLabelValue(LabelType::ETH_WIFI_MODE); + # endif // if FEATURE_ETHERNET + + if (!WiFiEventData.WiFiDisconnected()) + { + addRowLabelValue(LabelType::IP_ADDRESS); +#if FEATURE_USE_IPV6 + if (Settings.EnableIPv6()) { + addRowLabelValue(LabelType::IP6_LOCAL); + // Do not show global IPv6 on the root page + } +#endif + addRowLabel(LabelType::WIFI_RSSI); + addHtml(strformat( + F("%d [dBm] (%s)"), + WiFi.RSSI(), + WiFi.SSID().c_str())); + } + + # if FEATURE_ETHERNET + + if (active_network_medium == NetworkMedium_t::Ethernet) { + addRowLabelValue(LabelType::ETH_SPEED_STATE); + addRowLabelValue(LabelType::ETH_IP_ADDRESS); +#if FEATURE_USE_IPV6 + if (Settings.EnableIPv6()) { + addRowLabelValue(LabelType::ETH_IP6_LOCAL); + // Do not show global IPv6 on the root page + } +#endif + } + # endif // if FEATURE_ETHERNET + + # if FEATURE_MDNS + { + addRowLabel(LabelType::M_DNS); + addHtml(F("")); + addHtml(url); + addHtml(F("")); + } + # endif // if FEATURE_MDNS + + # if FEATURE_MQTT + { + if (validControllerIndex(firstEnabledMQTT_ControllerIndex())) { + addRowLabel(F("MQTT Client Connected")); + addEnabled(MQTTclient_connected); + } + } + # endif // if FEATURE_MQTT + + + # if MAIN_PAGE_SHOW_SYSINFO_BUTTON + html_TR_TD(); + html_TD(); + addButton(F("sysinfo"), F("More info")); + # endif // if MAIN_PAGE_SHOW_SYSINFO_BUTTON + # if MAIN_PAGE_SHOW_WiFi_SETUP_BUTTON + html_TR_TD(); + html_TD(); + addButton(F("setup"), F("WiFi Setup")); + # endif // if MAIN_PAGE_SHOW_WiFi_SETUP_BUTTON + + if (loggedIn) { + if (printWebString.length() > 0) + { + html_BR(); + html_BR(); + addFormHeader(F("Command Argument")); + addRowLabel(F("Command")); + addHtml(sCommand); + + addHtml(F("Command Output
")); + free_string(printWebString); + } + } + html_end_table(); + + # if FEATURE_ESPEASY_P2P + html_BR(); + + if ((Settings.Unit == 0) && + (Settings.UDPPort != 0)) { addFormNote(F("Warning: Unit number is 0, please change it if you want to send data to other units.")); } + html_BR(); + html_table_class_multirow_noborder(); + html_TR(); + html_table_header(F("Node List")); + html_table_header(F("Name")); + + if (MAIN_PAGE_SHOW_NODE_LIST_BUILD) { + html_table_header(getLabel(LabelType::BUILD_DESC)); + } + + if (MAIN_PAGE_SHOW_NODE_LIST_TYPE) { + html_table_header(F("Type")); + } + html_table_header(F("IP"), 160); // Should fit "255.255.255.255" + html_table_header(F("Load")); + html_table_header(F("Age (s)")); + # ifdef USES_ESPEASY_NOW + + if (Settings.UseESPEasyNow()) { + html_table_header(F("Dist")); + html_table_header(F("Peer Info"), 160); + } + # endif // ifdef USES_ESPEASY_NOW + + for (auto it = Nodes.begin(); it != Nodes.end(); ++it) + { + if (it->second.valid()) + { + bool isThisUnit = it->first == Settings.Unit; + + if (isThisUnit) { + html_TR_TD_highlight(); + } + else { + html_TR_TD(); + } + + addHtml(F("Unit ")); + addHtmlInt(it->first); + html_TD(); + + if (isThisUnit) { + addHtml(Settings.getName()); + } + else { + addHtml(it->second.getNodeName()); + } + html_TD(); + + if (MAIN_PAGE_SHOW_NODE_LIST_BUILD) { + if (it->second.build) { + addHtml(formatSystemBuildNr(it->second.build)); + } + html_TD(); + } + + if (MAIN_PAGE_SHOW_NODE_LIST_TYPE) { + addHtml(it->second.getNodeTypeDisplayString()); + html_TD(); + } + + if (it->second.ip[0] != 0 +#if FEATURE_USE_IPV6 + || (Settings.EnableIPv6() && + (it->second.hasIPv6_mac_based_link_local || + it->second.hasIPv6_mac_based_link_global) + ) +#endif + ) + { + IPAddress ip = it->second.IP(); + const uint16_t port = it->second.webgui_portnumber; + +#if FEATURE_USE_IPV6 + bool isIPv6 = false; + if (Settings.EnableIPv6()) { + if (it->second.hasIPv6_mac_based_link_local) { + ip = it->second.IPv6_link_local(true); + isIPv6 = true; + } else if (it->second.hasIPv6_mac_based_link_global) { + ip = it->second.IPv6_global(); + isIPv6 = true; + } + } + if (it->second.hasIPv4 && it->second.hasIPv6()) { + // Add 2 buttons for IPv4 and IPv6 address + html_add_wide_button_prefix(); + addHtml(F("http://")); + addHtml(wrap_String(formatIP(ip), '[', ']')); + if ((port != 0) && (port != 80)) { + addHtml(':'); + addHtmlInt(port); + } + addHtml('\'', '>'); + addHtml(formatIP(ip)); + addHtml(F("")); + + // Now prepare 2nd button prefix + addHtml(F("
")); + html_add_wide_button_prefix(); + ip = it->second.IP(); + isIPv6 = false; + } else { + // Add single wide button + html_add_wide_button_prefix(); + } +#else + html_add_wide_button_prefix(); +#endif + addHtml(F("http://")); +#if FEATURE_USE_IPV6 + + if (isIPv6) { + addHtml(wrap_String(formatIP(ip), '[', ']')); + } else { + addHtml(formatIP(ip)); + } + #else + addHtml(formatIP(ip)); + #endif + + if ((port != 0) && (port != 80)) { + addHtml(':'); + addHtmlInt(port); + } + addHtml('\'', '>'); + addHtml(formatIP(ip)); + addHtml(F("")); + } + html_TD(); + const float load = it->second.getLoad(); + + if (load > 0.1) { + addHtmlFloat(load); + } + html_TD(); + addHtmlInt(static_cast(it->second.getAge() / 1000)); // time in seconds + # ifdef USES_ESPEASY_NOW + + if (Settings.UseESPEasyNow()) { + html_TD(); + + if (it->second.distance != 255) { + addHtmlInt(it->second.distance); + } + html_TD(); + + if (it->second.ESPEasyNowPeer) { + addHtml(F(ESPEASY_NOW_NAME)); + addHtml(' '); + addHtml(it->second.ESPEasy_Now_MAC().toString()); + addHtml(F(" (ch: ")); + addHtmlInt(it->second.channel); + int8_t rssi = it->second.getRSSI(); + + if (rssi < 0) { + addHtml(' '); + addHtmlInt(rssi); + } + addHtml(')'); + const ESPEasy_now_traceroute_struct *trace = Nodes.getDiscoveryRoute(it->second.unit); + + if (trace != nullptr) { + addHtml(' '); + addHtml(trace->toString()); + } + } + } + # endif // ifdef USES_ESPEASY_NOW + } + } + + html_end_table(); + # endif // if FEATURE_ESPEASY_P2P + html_end_form(); + + free_string(printWebString); + printToWeb = false; + sendHeadandTail_stdtemplate(_TAIL); + } + TXBuffer.endStream(); +} + +#endif // ifdef WEBSERVER_ROOT \ No newline at end of file diff --git a/src/src/WebServer/SysInfoPage.cpp b/src/src/WebServer/SysInfoPage.cpp index 0ffd9b76d3..2387ccf8e3 100644 --- a/src/src/WebServer/SysInfoPage.cpp +++ b/src/src/WebServer/SysInfoPage.cpp @@ -1,888 +1,904 @@ -#include "../WebServer/SysInfoPage.h" - -#if defined(WEBSERVER_SYSINFO) || SHOW_SYSINFO_JSON - -# include "../WebServer/AccessControl.h" -# include "../WebServer/ESPEasy_WebServer.h" -# include "../WebServer/HTML_wrappers.h" -# include "../WebServer/Markup.h" -# include "../WebServer/Markup_Buttons.h" - - -# include "../../ESPEasy-Globals.h" - -# include "../Commands/Diagnostic.h" - -# include "../CustomBuild/CompiletimeDefines.h" - -# include "../DataStructs/RTCStruct.h" - -# include "../ESPEasyCore/ESPEasyEth.h" -# include "../ESPEasyCore/ESPEasyNetwork.h" -# include "../ESPEasyCore/ESPEasyWifi.h" - -# include "../Globals/CRCValues.h" -# include "../Globals/ESPEasy_time.h" -# include "../Globals/ESPEasyWiFiEvent.h" -# include "../Globals/NetworkState.h" -# include "../Globals/RTC.h" -# include "../Globals/Settings.h" - -# include "../Helpers/Convert.h" -# include "../Helpers/ESPEasyStatistics.h" -# include "../Helpers/ESPEasy_Storage.h" -# include "../Helpers/Hardware_device_info.h" -# include "../Helpers/Memory.h" -# include "../Helpers/Misc.h" -# include "../Helpers/Networking.h" -# include "../Helpers/OTA.h" -# include "../Helpers/StringConverter.h" -# include "../Helpers/StringGenerator_GPIO.h" -# include "../Helpers/StringGenerator_System.h" -# include "../Helpers/StringProvider.h" - -# include "../Static/WebStaticData.h" - -# if FEATURE_MQTT -# include "../Globals/MQTT.h" -# include "../ESPEasyCore/Controller.h" // For finding enabled MQTT controller -# endif // if FEATURE_MQTT - - -#if FEATURE_INTERNAL_TEMPERATURE -#include "../Helpers/Hardware_temperature_sensor.h" -#endif - -# ifdef ESP32 -# include -# endif // ifdef ESP32 - - -# if SHOW_SYSINFO_JSON - -// ******************************************************************************** -// Web Interface sysinfo page -// ******************************************************************************** -void handle_sysinfo_json() { - # ifndef BUILD_NO_RAM_TRACKER - checkRAM(F("handle_sysinfo")); - # endif // ifndef BUILD_NO_RAM_TRACKER - - if (!isLoggedIn()) { return; } - TXBuffer.startJsonStream(); - json_init(); - json_open(); - json_open(false, F("general")); - json_number(F("unit"), String(Settings.Unit)); - json_prop(F("time"), node_time.getDateTimeString('-', ':', ' ')); - json_prop(F("uptime"), getExtendedValue(LabelType::UPTIME)); - json_number(F("cpu_load"), toString(getCPUload())); -#if FEATURE_INTERNAL_TEMPERATURE - json_number(F("cpu_temp"), toString(getInternalTemperature())); -#endif - json_number(F("loop_count"), String(getLoopCountPerSec())); - json_close(); - - int freeMem = ESP.getFreeHeap(); - json_open(false, F("mem")); - json_number(F("free"), String(freeMem)); - json_number(F("low_ram"), String( - # ifndef BUILD_NO_RAM_TRACKER - lowestRAM - # else // ifndef BUILD_NO_RAM_TRACKER - 0 - # endif // ifndef BUILD_NO_RAM_TRACKER - )); - json_prop(F("low_ram_fn"), String( - # ifndef BUILD_NO_RAM_TRACKER - lowestRAMfunction - # else // ifndef BUILD_NO_RAM_TRACKER - 0 - # endif // ifndef BUILD_NO_RAM_TRACKER - )); - json_number(F("stack"), String(getCurrentFreeStack())); - json_number(F("low_stack"), String( - # ifndef BUILD_NO_RAM_TRACKER - lowestFreeStack - # else // ifndef BUILD_NO_RAM_TRACKER - 0 - # endif // ifndef BUILD_NO_RAM_TRACKER - )); - json_prop(F("low_stack_fn"), String( - # ifndef BUILD_NO_RAM_TRACKER - lowestFreeStackfunction - # else // ifndef BUILD_NO_RAM_TRACKER - 0 - # endif // ifndef BUILD_NO_RAM_TRACKER - )); - json_close(); - - json_open(false, F("boot")); - json_prop(F("last_cause"), getLastBootCauseString()); - json_number(F("counter"), String(RTC.bootCounter)); - json_prop(F("reset_reason"), getResetReasonString()); - json_close(); - - json_open(false, F("wifi")); - json_prop(F("type"), toString(getConnectionProtocol())); - json_number(F("rssi"), String(WiFi.RSSI())); - json_prop(F("dhcp"), useStaticIP() ? getLabel(LabelType::IP_CONFIG_STATIC) : getLabel(LabelType::IP_CONFIG_DYNAMIC)); - json_prop(F("ip"), getValue(LabelType::IP_ADDRESS)); -#if FEATURE_USE_IPV6 - if (Settings.EnableIPv6()) { - json_prop(F("ip6_local"), getValue(LabelType::IP6_LOCAL)); - json_prop(F("ip6_global"), getValue(LabelType::IP6_GLOBAL)); - } -#endif - - json_prop(F("subnet"), getValue(LabelType::IP_SUBNET)); - json_prop(F("gw"), getValue(LabelType::GATEWAY)); - json_prop(F("dns1"), getValue(LabelType::DNS_1)); - json_prop(F("dns2"), getValue(LabelType::DNS_2)); - json_prop(F("allowed_range"), describeAllowedIPrange()); - json_prop(F("sta_mac"), getValue(LabelType::STA_MAC)); - json_prop(F("ap_mac"), getValue(LabelType::AP_MAC)); - json_prop(F("ssid"), getValue(LabelType::SSID)); - json_prop(F("bssid"), getValue(LabelType::BSSID)); - json_number(F("channel"), getValue(LabelType::CHANNEL)); - json_prop(F("encryption"), getValue(LabelType::ENCRYPTION_TYPE_STA)); - json_prop(F("connected"), getValue(LabelType::CONNECTED)); - json_prop(F("ldr"), getValue(LabelType::LAST_DISC_REASON_STR)); - json_number(F("reconnects"), getValue(LabelType::NUMBER_RECONNECTS)); - json_prop(F("ssid1"), getValue(LabelType::WIFI_STORED_SSID1)); - json_prop(F("ssid2"), getValue(LabelType::WIFI_STORED_SSID2)); - json_close(); - -# if FEATURE_ETHERNET - json_open(false, F("ethernet")); - json_prop(F("ethwifimode"), getValue(LabelType::ETH_WIFI_MODE)); - json_prop(F("ethconnected"), getValue(LabelType::ETH_CONNECTED)); - json_prop(F("ethchip"), getValue(LabelType::ETH_CHIP)); - json_prop(F("ethduplex"), getValue(LabelType::ETH_DUPLEX)); - json_prop(F("ethspeed"), getValue(LabelType::ETH_SPEED)); - json_prop(F("ethstate"), getValue(LabelType::ETH_STATE)); - json_prop(F("ethspeedstate"), getValue(LabelType::ETH_SPEED_STATE)); -#if FEATURE_USE_IPV6 - if (Settings.EnableIPv6()) - json_prop(F("ethipv6local"), getValue(LabelType::ETH_IP6_LOCAL)); -#endif - json_close(); -# endif // if FEATURE_ETHERNET - - json_open(false, F("firmware")); - json_prop(F("build"), getSystemBuildString()); - json_prop(F("notes"), F(BUILD_NOTES)); - json_prop(F("libraries"), getSystemLibraryString()); - json_prop(F("git_version"), getValue(LabelType::GIT_BUILD)); - json_prop(F("plugins"), getPluginDescriptionString()); - json_prop(F("md5"), String(CRCValues.compileTimeMD5[0], HEX)); - json_number(F("md5_check"), String(CRCValues.checkPassed())); - json_prop(F("build_time"), get_build_time()); - json_prop(F("filename"), getValue(LabelType::BINARY_FILENAME)); - json_prop(F("build_platform"), getValue(LabelType::BUILD_PLATFORM)); - json_prop(F("git_head"), getValue(LabelType::GIT_HEAD)); - #ifdef CONFIGURATION_CODE - json_prop(F("configuration_code"), getValue(LabelType::CONFIGURATION_CODE_LBL)); - #endif // ifdef CONFIGURATION_CODE - json_close(); - - json_open(false, F("esp")); - json_prop(F("chip_id"), getValue(LabelType::ESP_CHIP_ID)); - json_number(F("cpu"), getValue(LabelType::ESP_CHIP_FREQ)); -#ifdef ESP32 - json_number(F("xtal_freq"), getValue(LabelType::ESP_CHIP_XTAL_FREQ)); - json_number(F("abp_freq"), getValue(LabelType::ESP_CHIP_APB_FREQ)); -#endif - json_prop(F("board"), getValue(LabelType::BOARD_NAME)); - json_close(); - - json_open(false, F("storage")); - - // Set to HEX may be something like 0x1640E0. - // Where manufacturer is 0xE0 and device is 0x4016. - json_number(F("chip_id"), getValue(LabelType::FLASH_CHIP_ID)); - if (flashChipVendorPuya()) { - if (puyaSupport()) { - json_prop(F("vendor"), F("puya, supported")); - } else { - json_prop(F("vendor"), F("puya, error")); - } - } else { - json_prop(F("vendor"), getValue(LabelType::FLASH_CHIP_VENDOR)); - } - json_number(F("device"), getValue(LabelType::FLASH_CHIP_MODEL)); - json_number(F("real_size"), String(getFlashRealSizeInBytes() / 1024)); - json_number(F("ide_size"), String(ESP.getFlashChipSize() / 1024)); - - // Please check what is supported for the ESP32 - json_number(F("flash_speed"), getValue(LabelType::FLASH_CHIP_SPEED)); - - json_prop(F("mode"), getFlashChipMode()); - - json_number(F("writes"), String(RTC.flashDayCounter)); - json_number(F("flash_counter"), String(RTC.flashCounter)); - json_number(F("sketch_size"), String(getSketchSize() / 1024)); - json_number(F("sketch_free"), String(getFreeSketchSpace() / 1024)); - - json_number(F("spiffs_size"), String(SpiffsTotalBytes() / 1024)); - json_number(F("spiffs_free"), String(SpiffsFreeSpace() / 1024)); - json_close(); - json_close(); - - TXBuffer.endStream(); -} - -#endif // SHOW_SYSINFO_JSON - -#ifdef WEBSERVER_SYSINFO - -void handle_sysinfo() { - # ifndef BUILD_NO_RAM_TRACKER - checkRAM(F("handle_sysinfo")); - # endif // ifndef BUILD_NO_RAM_TRACKER - - if (!isLoggedIn()) { return; } - navMenuIndex = MENU_INDEX_TOOLS; - html_reset_copyTextCounter(); - TXBuffer.startStream(); - sendHeadandTail_stdtemplate(_HEAD); - - addHtml(printWebString); - addHtml(F("")); - - // the table header - html_table_class_normal(); - - - # ifdef WEBSERVER_GITHUB_COPY - - // Not using addFormHeader() to get the copy button on the same header line as 2nd column - html_TR(); - html_table_header(F("System Info"), 225); - addHtml(F("")); // Needed to get the copy button on the same header line. - addCopyButton(F("copyText"), F("\\n"), F("Copy info to clipboard")); - - TXBuffer.addFlashString((PGM_P)FPSTR(githublogo)); - serve_JS(JSfiles_e::GitHubClipboard); - - # else // ifdef WEBSERVER_GITHUB_COPY - addFormHeader(F("System Info")); - - # endif // ifdef WEBSERVER_GITHUB_COPY - - handle_sysinfo_basicInfo(); - -#ifndef WEBSERVER_SYSINFO_MINIMAL - handle_sysinfo_memory(); -#endif - - handle_sysinfo_Network(); - -# if FEATURE_ETHERNET - handle_sysinfo_Ethernet(); -# endif // if FEATURE_ETHERNET - -#ifndef WEBSERVER_SYSINFO_MINIMAL - handle_sysinfo_WiFiSettings(); -#endif - - handle_sysinfo_Firmware(); - -#ifndef WEBSERVER_SYSINFO_MINIMAL - handle_sysinfo_SystemStatus(); - - handle_sysinfo_NetworkServices(); - - handle_sysinfo_ESP_Board(); - - handle_sysinfo_Storage(); -#endif - - - html_end_table(); - html_end_form(); - sendHeadandTail_stdtemplate(_TAIL); - TXBuffer.endStream(); -} - -void handle_sysinfo_basicInfo() { - addRowLabelValue(LabelType::UNIT_NR); - - if (node_time.systemTimePresent()) - { - addRowLabelValue(LabelType::LOCAL_TIME); - #if FEATURE_EXT_RTC - if (Settings.ExtTimeSource() != ExtTimeSource_e::None) { - addRowLabelValue(LabelType::EXT_RTC_UTC_TIME); - } - #endif - addRowLabelValue(LabelType::TIME_SOURCE); - addRowLabelValue(LabelType::TIME_WANDER); - } - - addRowLabel(LabelType::UPTIME); - { - addHtml(getExtendedValue(LabelType::UPTIME)); - } - - addRowLabel(LabelType::LOAD_PCT); - - if (wdcounter > 0) - { - addHtml(strformat( - F("%.2f [%%] (LC=%d)"), - getCPUload(), - getLoopCountPerSec())); - } - - static const LabelType::Enum labels[] PROGMEM = - { -#if FEATURE_INTERNAL_TEMPERATURE - LabelType::INTERNAL_TEMPERATURE, -#endif - - LabelType::CPU_ECO_MODE, - - LabelType::RESET_REASON, - LabelType::LAST_TASK_BEFORE_REBOOT, - LabelType::SW_WD_COUNT, - LabelType::MAX_LABEL - }; - addRowLabelValues(labels); - - addRowLabel(F("Boot")); - { - addHtml(getLastBootCauseString()); - addHtml(strformat( - F(" (%d)"), static_cast(RTC.bootCounter))); - } -} - -#ifndef WEBSERVER_SYSINFO_MINIMAL -void handle_sysinfo_memory() { - addTableSeparator(F("Memory"), 2, 3); - -# ifdef ESP32 - addRowLabelValue(LabelType::HEAP_SIZE); - addRowLabelValue(LabelType::HEAP_MIN_FREE); -# endif // ifdef ESP32 - - int freeMem = ESP.getFreeHeap(); - addRowLabel(LabelType::FREE_MEM); - { - addHtmlInt(freeMem); - addUnit(getFormUnit(LabelType::FREE_MEM)); -# ifndef BUILD_NO_RAM_TRACKER - addHtml(F(" (")); - addHtmlInt(lowestRAM); - addHtml(F(" - ")); - addHtml(lowestRAMfunction); - addHtml(')'); -# endif // ifndef BUILD_NO_RAM_TRACKER - } -# if defined(CORE_POST_2_5_0) || defined(ESP32) - # ifndef LIMIT_BUILD_SIZE - addRowLabelValue(LabelType::HEAP_MAX_FREE_BLOCK); - # endif // ifndef LIMIT_BUILD_SIZE -# endif // if defined(CORE_POST_2_5_0) || defined(ESP32) -# if defined(CORE_POST_2_5_0) - # ifndef LIMIT_BUILD_SIZE - addRowLabelValue(LabelType::HEAP_FRAGMENTATION); - # endif // ifndef LIMIT_BUILD_SIZE - { - #ifdef USE_SECOND_HEAP - addRowLabelValue(LabelType::FREE_HEAP_IRAM); - #endif - } -# endif // if defined(CORE_POST_2_5_0) - - - addRowLabel(LabelType::FREE_STACK); - { - addHtmlInt(getCurrentFreeStack()); - addUnit(getFormUnit(LabelType::FREE_STACK)); -# ifndef BUILD_NO_RAM_TRACKER - addHtml(F(" (")); - addHtmlInt(lowestFreeStack); - addHtml(F(" - ")); - addHtml(lowestFreeStackfunction); - addHtml(')'); -# endif // ifndef BUILD_NO_RAM_TRACKER - } - -# if defined(ESP32) && defined(BOARD_HAS_PSRAM) - - addRowLabelValue(LabelType::PSRAM_SIZE); - if (UsePSRAM()) { - static const LabelType::Enum labels[] PROGMEM = - { - LabelType::PSRAM_FREE, - LabelType::PSRAM_MIN_FREE, - LabelType::PSRAM_MAX_FREE_BLOCK, - LabelType::MAX_LABEL - }; - addRowLabelValues(labels); - } -# endif // if defined(ESP32) && defined(BOARD_HAS_PSRAM) -} -#endif - -# if FEATURE_ETHERNET -void handle_sysinfo_Ethernet() { - if (active_network_medium == NetworkMedium_t::Ethernet) { - addTableSeparator(F("Ethernet"), 2, 3); - - static const LabelType::Enum labels[] PROGMEM = - { - LabelType::ETH_CHIP, - LabelType::ETH_STATE, - LabelType::ETH_SPEED, - LabelType::ETH_DUPLEX, - LabelType::ETH_MAC, -// LabelType::ETH_IP_ADDRESS_SUBNET, -// LabelType::ETH_IP_GATEWAY, -// LabelType::ETH_IP_DNS, - - LabelType::MAX_LABEL - }; - - addRowLabelValues(labels); - - } -} - -# endif // if FEATURE_ETHERNET - -void handle_sysinfo_Network() { - addTableSeparator(F("Network"), 2, 3); - - { - static const LabelType::Enum labels[] PROGMEM = - { -# if FEATURE_ETHERNET || defined(USES_ESPEASY_NOW) - LabelType::ETH_WIFI_MODE, -# endif - LabelType::IP_CONFIG, - LabelType::IP_ADDRESS_SUBNET, -#if FEATURE_USE_IPV6 - LabelType::IP6_LOCAL, - LabelType::IP6_GLOBAL, -// LabelType::IP6_ALL_ADDRESSES, -#endif - LabelType::GATEWAY, - LabelType::CLIENT_IP, - LabelType::DNS, - LabelType::ALLOWED_IP_RANGE, - LabelType::CONNECTED, - LabelType::NUMBER_RECONNECTS, - - LabelType::MAX_LABEL - }; - - addRowLabelValues(labels); - } - - addTableSeparator(F("WiFi"), 2, 3, F("Wifi")); - - const bool showWiFiConnectionInfo = !WiFiEventData.WiFiDisconnected(); - - - addRowLabel(LabelType::WIFI_CONNECTION); - if (showWiFiConnectionInfo) - { - addHtml(toString(getConnectionProtocol())); - addHtml(F(" (RSSI ")); - addHtmlInt(WiFi.RSSI()); - addHtml(F(" dBm)")); - } else addHtml('-'); - - addRowLabel(LabelType::SSID); - if (showWiFiConnectionInfo) - { - addHtml(WiFi.SSID()); - addHtml(F(" (")); - addHtml(WiFi.BSSIDstr()); - addHtml(')'); - } else addHtml('-'); - - #ifdef ESP32 - const int64_t tsf_time = WiFi_get_TSF_time(); - if (tsf_time > 0) { - addRowLabel(F("WiFi TSF time")); - // Split it while printing, so we're not loosing a lot of decimals in the float conversion - uint32_t tsf_usec{}; - addHtml(secondsToDayHourMinuteSecond(micros_to_sec_usec(tsf_time, tsf_usec))); - addHtml(strformat(F(".%06u"), tsf_usec)); - } - #endif - - addRowLabel(getLabel(LabelType::CHANNEL)); - if (showWiFiConnectionInfo) { - addHtml(getValue(LabelType::CHANNEL)); - } else addHtml('-'); - - addRowLabel(getLabel(LabelType::ENCRYPTION_TYPE_STA)); - if (showWiFiConnectionInfo) { - addHtml(getValue(LabelType::ENCRYPTION_TYPE_STA)); - } else addHtml('-'); - - if (active_network_medium == NetworkMedium_t::WIFI) - { - addRowLabel(LabelType::LAST_DISCONNECT_REASON); - addHtml(getValue(LabelType::LAST_DISC_REASON_STR)); - addRowLabelValue(LabelType::WIFI_STORED_SSID1); - addRowLabelValue(LabelType::WIFI_STORED_SSID2); - } - - addRowLabelValue(LabelType::STA_MAC); - addRowLabelValue(LabelType::AP_MAC); - html_TR(); -} - -#ifndef WEBSERVER_SYSINFO_MINIMAL -void handle_sysinfo_WiFiSettings() { - addTableSeparator(F("WiFi Settings"), 2, 3); - - static const LabelType::Enum labels[] PROGMEM = - { - LabelType::FORCE_WIFI_BG, - LabelType::RESTART_WIFI_LOST_CONN, - LabelType::FORCE_WIFI_NOSLEEP, -# ifdef SUPPORT_ARP - LabelType::PERIODICAL_GRAT_ARP, -# endif // ifdef SUPPORT_ARP - LabelType::CONNECTION_FAIL_THRESH, -#if FEATURE_SET_WIFI_TX_PWR - LabelType::WIFI_TX_MAX_PWR, - LabelType::WIFI_CUR_TX_PWR, - LabelType::WIFI_SENS_MARGIN, - LabelType::WIFI_SEND_AT_MAX_TX_PWR, -#endif - LabelType::WIFI_NR_EXTRA_SCANS, -#ifdef USES_ESPEASY_NOW - LabelType::USE_ESPEASY_NOW, - LabelType::FORCE_ESPEASY_NOW_CHANNEL, -#endif - LabelType::WIFI_USE_LAST_CONN_FROM_RTC, -#ifndef ESP32 - LabelType::WAIT_WIFI_CONNECT, -#endif -#ifdef ESP32 - LabelType::WIFI_PASSIVE_SCAN, -#endif - LabelType::HIDDEN_SSID_SLOW_CONNECT, - LabelType::CONNECT_HIDDEN_SSID, - LabelType::SDK_WIFI_AUTORECONNECT, -#if FEATURE_USE_IPV6 - LabelType::ENABLE_IPV6, -#endif - - LabelType::MAX_LABEL - }; - - addRowLabelValues(labels); -} -#endif - -void handle_sysinfo_Firmware() { - addTableSeparator(F("Firmware"), 2, 3); - - addRowLabelValue_copy(LabelType::BUILD_DESC); - addHtml(' '); - addHtml(F(BUILD_NOTES)); - - addRowLabelValue_copy(LabelType::SYSTEM_LIBRARIES); -#ifdef ESP32 - addRowLabelValue_copy(LabelType::ESP_IDF_SDK_VERSION); -#endif - addRowLabelValue_copy(LabelType::GIT_BUILD); - addRowLabelValue_copy(LabelType::PLUGIN_COUNT); - addHtml(' '); - addHtml(getPluginDescriptionString()); - - addRowLabel(F("Build Origin")); - addHtml(get_build_origin()); - addRowLabelValue_copy(LabelType::BUILD_TIME); - addRowLabelValue_copy(LabelType::BINARY_FILENAME); - addRowLabelValue_copy(LabelType::BUILD_PLATFORM); - addRowLabelValue_copy(LabelType::GIT_HEAD); - #ifdef CONFIGURATION_CODE - addRowLabelValue_copy(LabelType::CONFIGURATION_CODE_LBL); - #endif // ifdef CONFIGURATION_CODE -} - -#ifndef WEBSERVER_SYSINFO_MINIMAL -void handle_sysinfo_SystemStatus() { - addTableSeparator(F("System Status"), 2, 3); - - static const LabelType::Enum labels[] PROGMEM = - { - // Actual Loglevel - LabelType::SYSLOG_LOG_LEVEL, - LabelType::SERIAL_LOG_LEVEL, - LabelType::WEB_LOG_LEVEL, -# if FEATURE_SD - LabelType::SD_LOG_LEVEL, -# endif // if FEATURE_SD - - LabelType::ENABLE_SERIAL_PORT_CONSOLE, - LabelType::CONSOLE_SERIAL_PORT, -#if USES_ESPEASY_CONSOLE_FALLBACK_PORT - LabelType::CONSOLE_FALLBACK_TO_SERIAL0, - LabelType::CONSOLE_FALLBACK_PORT, -#endif - LabelType::MAX_LABEL - }; - - addRowLabelValues(labels); -#if FEATURE_CLEAR_I2C_STUCK - if (Settings.EnableClearHangingI2Cbus()) { - addRowLabelValue(LabelType::I2C_BUS_STATE); - addRowLabelValue(LabelType::I2C_BUS_CLEARED_COUNT); - } -#endif -} -#endif - -#ifndef WEBSERVER_SYSINFO_MINIMAL -void handle_sysinfo_NetworkServices() { - addTableSeparator(F("Network Services"), 2, 3); - - addRowLabel(F("Network Connected")); - addEnabled(NetworkConnected()); - - addRowLabel(F("NTP Initialized")); - addEnabled(statusNTPInitialized); - - #if FEATURE_MQTT - if (validControllerIndex(firstEnabledMQTT_ControllerIndex())) { - addRowLabel(F("MQTT Client Connected")); - addEnabled(MQTTclient_connected); - } - #endif -} -#endif - -#ifndef WEBSERVER_SYSINFO_MINIMAL -void handle_sysinfo_ESP_Board() { - addTableSeparator(F("ESP Board"), 2, 3); - - - addRowLabel(LabelType::ESP_CHIP_ID); - - const uint32_t chipID = getChipId(); - addHtml(strformat( - F("%d (%s)"), - chipID, - formatToHex(chipID, 6).c_str())); - - static const LabelType::Enum labels[] PROGMEM = - { - LabelType::ESP_CHIP_FREQ, -# ifdef ESP32 - LabelType::ESP_CHIP_XTAL_FREQ, - LabelType::ESP_CHIP_APB_FREQ, -# endif // ifdef ESP32 - - LabelType::ESP_CHIP_MODEL, -# if defined(ESP32) - LabelType::ESP_CHIP_REVISION, -# endif // if defined(ESP32) - LabelType::ESP_CHIP_CORES, - LabelType::BOARD_NAME, - LabelType::MAX_LABEL - }; - - addRowLabelValues(labels); - - -#if defined(ESP32) - addRowLabel(F("ESP Chip Features")); - addHtml(getChipFeaturesString()); -#endif -} - -# endif // ifndef WEBSERVER_SYSINFO_MINIMAL - -# ifndef WEBSERVER_SYSINFO_MINIMAL -void handle_sysinfo_Storage() { - addTableSeparator(F("Storage"), 2, 3); - - if (getFlashChipId() != 0) { - addRowLabel(LabelType::FLASH_CHIP_ID); - - - // Set to HEX may be something like 0x1640E0. - // Where manufacturer is 0xE0 and device is 0x4016. - addHtml(F("Vendor: ")); - addHtml(getValue(LabelType::FLASH_CHIP_VENDOR)); - - if (flashChipVendorPuya()) - { - addHtml(F(" (PUYA")); - - if (puyaSupport()) { - addHtml(F(", supported")); - } else { - addHtml(F(HTML_SYMBOL_WARNING)); - } - addHtml(')'); - } - addHtml(F(" Device: ")); - addHtml(getValue(LabelType::FLASH_CHIP_MODEL)); - #ifdef ESP32 - if (getChipFeatures().embeddedFlash) { - addHtml(F(" (Embedded)")); - } - #endif - } - const uint32_t realSize = getFlashRealSizeInBytes(); - const uint32_t ideSize = ESP.getFlashChipSize(); - - addRowLabel(LabelType::FLASH_CHIP_REAL_SIZE); - addHtmlInt(realSize / 1024); - addHtml(F(" [kB]")); - - addRowLabel(LabelType::FLASH_IDE_SIZE); - addHtmlInt(ideSize / 1024); - addHtml(F(" [kB]")); - - addRowLabel(LabelType::FLASH_CHIP_SPEED); - addHtmlInt(getFlashChipSpeed() / 1000000); - addHtml(F(" [MHz]")); - - // Please check what is supported for the ESP32 - addRowLabel(LabelType::FLASH_IDE_SPEED); - addHtmlInt(ESP.getFlashChipSpeed() / 1000000); - addHtml(F(" [MHz]")); - - addRowLabelValue(LabelType::FLASH_IDE_MODE); - - addRowLabel(LabelType::FLASH_WRITE_COUNT); - addHtml(strformat( - F("%d daily / %d boot"), - RTC.flashDayCounter, - static_cast(RTC.flashCounter))); - - { - uint32_t maxSketchSize; - bool use2step; - # if defined(ESP8266) - bool otaEnabled = - # endif // if defined(ESP8266) - OTA_possible(maxSketchSize, use2step); - - addRowLabel(LabelType::SKETCH_SIZE); - addHtml(strformat( - F("%d [kB] (%d kB not used)"), - getSketchSize() / 1024, - (maxSketchSize - getSketchSize()) / 1024)); - - addRowLabel(LabelType::MAX_OTA_SKETCH_SIZE); - addHtml(strformat( - F("%d [kB] (%d bytes)"), - maxSketchSize / 1024, - maxSketchSize)); - - # if defined(ESP8266) - addRowLabel(LabelType::OTA_POSSIBLE); - addHtml(boolToString(otaEnabled)); - - addRowLabel(LabelType::OTA_2STEP); - addHtml(boolToString(use2step)); - # endif // if defined(ESP8266) - } - - addRowLabel(LabelType::FS_SIZE); - addHtml(strformat( - F("%d [kB] (%d kB free)"), - SpiffsTotalBytes() / 1024, - SpiffsFreeSpace() / 1024)); - - # ifndef LIMIT_BUILD_SIZE - addRowLabel(F("Page size")); - addHtmlInt(SpiffsPagesize()); - addUnit(getFormUnit(LabelType::FREE_MEM)); // FIXME TD-er: Add labeltype - - addRowLabel(F("Block size")); - addHtmlInt(SpiffsBlocksize()); - addUnit(getFormUnit(LabelType::FREE_MEM)); // FIXME TD-er: Add labeltype - - addRowLabel(F("Number of blocks")); - addHtmlInt(SpiffsTotalBytes() / SpiffsBlocksize()); - - { - # if defined(ESP8266) - fs::FSInfo fs_info; - ESPEASY_FS.info(fs_info); - addRowLabel(F("Maximum open files")); - addHtmlInt(fs_info.maxOpenFiles); - - addRowLabel(F("Maximum path length")); - addHtmlInt(fs_info.maxPathLength); - - # endif // if defined(ESP8266) - } - # endif // ifndef LIMIT_BUILD_SIZE - -#if FEATURE_CHART_STORAGE_LAYOUT - - if (showSettingsFileLayout) { - addTableSeparator(F("Settings Files"), 2, 3); - html_TR_TD(); - addHtml(F("Layout Settings File")); - html_TD(); - getConfig_dat_file_layout(); - html_TR_TD(); - html_TD(); - addHtml(F("(offset / size per item / index)")); - - for (int st = 0; st < static_cast(SettingsType::Enum::SettingsType_MAX); ++st) { - const SettingsType::Enum settingsType = static_cast(st); - #if !FEATURE_NOTIFIER - if (settingsType == SettingsType::Enum::NotificationSettings_Type) { - continue; - } - #endif - html_TR_TD(); - addHtml(SettingsType::getSettingsTypeString(settingsType)); - html_BR(); - addHtml(SettingsType::getSettingsFileName(settingsType)); - html_TD(); - getStorageTableSVG(settingsType); - delay(1); - } - } - - # ifdef ESP32 - addTableSeparator(F("Partitions"), 2, 3, - F("https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-guides/partition-tables.html")); - - addRowLabel(F("Data Partition Table")); - - // TXBuffer += getPartitionTableHeader(F(" - "), F("
")); - // TXBuffer += getPartitionTable(ESP_PARTITION_TYPE_DATA, F(" - "), F("
")); - getPartitionTableSVG(ESP_PARTITION_TYPE_DATA, 0x5856e6); - - addRowLabel(F("App Partition Table")); - - // TXBuffer += getPartitionTableHeader(F(" - "), F("
")); - // TXBuffer += getPartitionTable(ESP_PARTITION_TYPE_APP , F(" - "), F("
")); - getPartitionTableSVG(ESP_PARTITION_TYPE_APP, 0xab56e6); - # endif // ifdef ESP32 - - #ifdef ESP8266 - addTableSeparator(F("Partitions"), 2, 3); - - addRowLabel(F("Partition Table")); - - getPartitionTableSVG(); - #endif -#endif -} -#endif - -#endif // ifdef WEBSERVER_SYSINFO - - -#endif +#include "../WebServer/SysInfoPage.h" + +#if defined(WEBSERVER_SYSINFO) || SHOW_SYSINFO_JSON + +# include "../WebServer/AccessControl.h" +# include "../WebServer/ESPEasy_WebServer.h" +# include "../WebServer/HTML_wrappers.h" +# include "../WebServer/Markup.h" +# include "../WebServer/Markup_Buttons.h" + + +# include "../../ESPEasy-Globals.h" + +# include "../Commands/Diagnostic.h" + +# include "../CustomBuild/CompiletimeDefines.h" + +# include "../DataStructs/RTCStruct.h" + +# include "../ESPEasyCore/ESPEasyEth.h" +# include "../ESPEasyCore/ESPEasyNetwork.h" +# include "../ESPEasyCore/ESPEasyWifi.h" + +# include "../Globals/CRCValues.h" +# include "../Globals/ESPEasy_time.h" +# include "../Globals/ESPEasyWiFiEvent.h" +# include "../Globals/NetworkState.h" +# include "../Globals/RTC.h" +# include "../Globals/Settings.h" + +# include "../Helpers/Convert.h" +# include "../Helpers/ESPEasyStatistics.h" +# include "../Helpers/ESPEasy_Storage.h" +# include "../Helpers/Hardware_device_info.h" +# include "../Helpers/Memory.h" +# include "../Helpers/Misc.h" +# include "../Helpers/Networking.h" +# include "../Helpers/OTA.h" +# include "../Helpers/StringConverter.h" +# include "../Helpers/StringGenerator_GPIO.h" +# include "../Helpers/StringGenerator_System.h" +# include "../Helpers/StringProvider.h" + +# include "../Static/WebStaticData.h" + +# if FEATURE_MQTT +# include "../Globals/MQTT.h" +# include "../ESPEasyCore/Controller.h" // For finding enabled MQTT controller +# endif // if FEATURE_MQTT + + +#if FEATURE_INTERNAL_TEMPERATURE +#include "../Helpers/Hardware_temperature_sensor.h" +#endif + +# ifdef ESP32 +# include +# endif // ifdef ESP32 + + +# if SHOW_SYSINFO_JSON + +// ******************************************************************************** +// Web Interface sysinfo page +// ******************************************************************************** +void handle_sysinfo_json() { + # ifndef BUILD_NO_RAM_TRACKER + checkRAM(F("handle_sysinfo")); + # endif // ifndef BUILD_NO_RAM_TRACKER + + if (!isLoggedIn()) { return; } + TXBuffer.startJsonStream(); + json_init(); + json_open(); + json_open(false, F("general")); + json_number(F("unit"), String(Settings.Unit)); + json_prop(F("time"), node_time.getDateTimeString('-', ':', ' ')); + json_prop(F("uptime"), getExtendedValue(LabelType::UPTIME)); + json_number(F("cpu_load"), toString(getCPUload())); +#if FEATURE_INTERNAL_TEMPERATURE + json_number(F("cpu_temp"), toString(getInternalTemperature())); +#endif + json_number(F("loop_count"), String(getLoopCountPerSec())); + json_close(); + + int freeMem = ESP.getFreeHeap(); + json_open(false, F("mem")); + json_number(F("free"), String(freeMem)); + json_number(F("low_ram"), String( + # ifndef BUILD_NO_RAM_TRACKER + lowestRAM + # else // ifndef BUILD_NO_RAM_TRACKER + 0 + # endif // ifndef BUILD_NO_RAM_TRACKER + )); + json_prop(F("low_ram_fn"), String( + # ifndef BUILD_NO_RAM_TRACKER + lowestRAMfunction + # else // ifndef BUILD_NO_RAM_TRACKER + 0 + # endif // ifndef BUILD_NO_RAM_TRACKER + )); + json_number(F("stack"), String(getCurrentFreeStack())); + json_number(F("low_stack"), String( + # ifndef BUILD_NO_RAM_TRACKER + lowestFreeStack + # else // ifndef BUILD_NO_RAM_TRACKER + 0 + # endif // ifndef BUILD_NO_RAM_TRACKER + )); + json_prop(F("low_stack_fn"), String( + # ifndef BUILD_NO_RAM_TRACKER + lowestFreeStackfunction + # else // ifndef BUILD_NO_RAM_TRACKER + 0 + # endif // ifndef BUILD_NO_RAM_TRACKER + )); + json_close(); + + json_open(false, F("boot")); + json_prop(F("last_cause"), getLastBootCauseString()); + json_number(F("counter"), String(RTC.bootCounter)); + json_prop(F("reset_reason"), getResetReasonString()); + json_close(); + + json_open(false, F("wifi")); + json_prop(F("type"), toString(getConnectionProtocol())); + json_number(F("rssi"), String(WiFi.RSSI())); + json_prop(F("dhcp"), useStaticIP() ? getLabel(LabelType::IP_CONFIG_STATIC) : getLabel(LabelType::IP_CONFIG_DYNAMIC)); + json_prop(F("ip"), getValue(LabelType::IP_ADDRESS)); +#if FEATURE_USE_IPV6 + if (Settings.EnableIPv6()) { + json_prop(F("ip6_local"), getValue(LabelType::IP6_LOCAL)); + json_prop(F("ip6_global"), getValue(LabelType::IP6_GLOBAL)); + } +#endif + + json_prop(F("subnet"), getValue(LabelType::IP_SUBNET)); + json_prop(F("gw"), getValue(LabelType::GATEWAY)); + json_prop(F("dns1"), getValue(LabelType::DNS_1)); + json_prop(F("dns2"), getValue(LabelType::DNS_2)); + json_prop(F("allowed_range"), describeAllowedIPrange()); + json_prop(F("sta_mac"), getValue(LabelType::STA_MAC)); + json_prop(F("ap_mac"), getValue(LabelType::AP_MAC)); + json_prop(F("ssid"), getValue(LabelType::SSID)); + json_prop(F("bssid"), getValue(LabelType::BSSID)); + json_number(F("channel"), getValue(LabelType::CHANNEL)); + json_prop(F("encryption"), getValue(LabelType::ENCRYPTION_TYPE_STA)); + json_prop(F("connected"), getValue(LabelType::CONNECTED)); + json_prop(F("ldr"), getValue(LabelType::LAST_DISC_REASON_STR)); + json_number(F("reconnects"), getValue(LabelType::NUMBER_RECONNECTS)); + json_prop(F("ssid1"), getValue(LabelType::WIFI_STORED_SSID1)); + json_prop(F("ssid2"), getValue(LabelType::WIFI_STORED_SSID2)); + json_close(); + +# if FEATURE_ETHERNET + json_open(false, F("ethernet")); + json_prop(F("ethwifimode"), getValue(LabelType::ETH_WIFI_MODE)); + json_prop(F("ethconnected"), getValue(LabelType::ETH_CONNECTED)); + json_prop(F("ethchip"), getValue(LabelType::ETH_CHIP)); + json_prop(F("ethduplex"), getValue(LabelType::ETH_DUPLEX)); + json_prop(F("ethspeed"), getValue(LabelType::ETH_SPEED)); + json_prop(F("ethstate"), getValue(LabelType::ETH_STATE)); + json_prop(F("ethspeedstate"), getValue(LabelType::ETH_SPEED_STATE)); +#if FEATURE_USE_IPV6 + if (Settings.EnableIPv6()) + json_prop(F("ethipv6local"), getValue(LabelType::ETH_IP6_LOCAL)); +#endif + json_close(); +# endif // if FEATURE_ETHERNET + + json_open(false, F("firmware")); + json_prop(F("build"), getSystemBuildString()); + json_prop(F("notes"), F(BUILD_NOTES)); + json_prop(F("libraries"), getSystemLibraryString()); + json_prop(F("git_version"), getValue(LabelType::GIT_BUILD)); + json_prop(F("plugins"), getPluginDescriptionString()); + json_prop(F("md5"), String(CRCValues.compileTimeMD5[0], HEX)); + json_number(F("md5_check"), String(CRCValues.checkPassed())); + json_prop(F("build_time"), get_build_time()); + json_prop(F("filename"), getValue(LabelType::BINARY_FILENAME)); + json_prop(F("build_platform"), getValue(LabelType::BUILD_PLATFORM)); + json_prop(F("git_head"), getValue(LabelType::GIT_HEAD)); + #ifdef CONFIGURATION_CODE + json_prop(F("configuration_code"), getValue(LabelType::CONFIGURATION_CODE_LBL)); + #endif // ifdef CONFIGURATION_CODE + json_close(); + + json_open(false, F("esp")); + json_prop(F("chip_id"), getValue(LabelType::ESP_CHIP_ID)); + json_number(F("cpu"), getValue(LabelType::ESP_CHIP_FREQ)); +#ifdef ESP32 + json_number(F("xtal_freq"), getValue(LabelType::ESP_CHIP_XTAL_FREQ)); + json_number(F("abp_freq"), getValue(LabelType::ESP_CHIP_APB_FREQ)); +#endif + json_prop(F("board"), getValue(LabelType::BOARD_NAME)); + json_close(); + + json_open(false, F("storage")); + + // Set to HEX may be something like 0x1640E0. + // Where manufacturer is 0xE0 and device is 0x4016. + json_number(F("chip_id"), getValue(LabelType::FLASH_CHIP_ID)); + if (flashChipVendorPuya()) { + if (puyaSupport()) { + json_prop(F("vendor"), F("puya, supported")); + } else { + json_prop(F("vendor"), F("puya, error")); + } + } else { + json_prop(F("vendor"), getValue(LabelType::FLASH_CHIP_VENDOR)); + } + json_number(F("device"), getValue(LabelType::FLASH_CHIP_MODEL)); + json_number(F("real_size"), String(getFlashRealSizeInBytes() / 1024)); + json_number(F("ide_size"), String(ESP.getFlashChipSize() / 1024)); + + // Please check what is supported for the ESP32 + json_number(F("flash_speed"), getValue(LabelType::FLASH_CHIP_SPEED)); + + json_prop(F("mode"), getFlashChipMode()); + + json_number(F("writes"), String(RTC.flashDayCounter)); + json_number(F("flash_counter"), String(RTC.flashCounter)); + json_number(F("sketch_size"), String(getSketchSize() / 1024)); + json_number(F("sketch_free"), String(getFreeSketchSpace() / 1024)); + + json_number(F("spiffs_size"), String(SpiffsTotalBytes() / 1024)); + json_number(F("spiffs_free"), String(SpiffsFreeSpace() / 1024)); + json_close(); + json_close(); + + TXBuffer.endStream(); +} + +#endif // SHOW_SYSINFO_JSON + +#ifdef WEBSERVER_SYSINFO + +void handle_sysinfo() { + # ifndef BUILD_NO_RAM_TRACKER + checkRAM(F("handle_sysinfo")); + # endif // ifndef BUILD_NO_RAM_TRACKER + + if (!isLoggedIn()) { return; } + navMenuIndex = MENU_INDEX_TOOLS; + html_reset_copyTextCounter(); + TXBuffer.startStream(); + sendHeadandTail_stdtemplate(_HEAD); + + addHtml(printWebString); + addHtml(F("")); + + // the table header + html_table_class_normal(); + + + # ifdef WEBSERVER_GITHUB_COPY + + // Not using addFormHeader() to get the copy button on the same header line as 2nd column + html_TR(); + html_table_header(F("System Info"), 225); + addHtml(F("")); // Needed to get the copy button on the same header line. + addCopyButton(F("copyText"), F("\\n"), F("Copy info to clipboard")); + + TXBuffer.addFlashString((PGM_P)FPSTR(githublogo)); + serve_JS(JSfiles_e::GitHubClipboard); + + # else // ifdef WEBSERVER_GITHUB_COPY + addFormHeader(F("System Info")); + + # endif // ifdef WEBSERVER_GITHUB_COPY + + handle_sysinfo_basicInfo(); + +#ifndef WEBSERVER_SYSINFO_MINIMAL + handle_sysinfo_memory(); +#endif + + handle_sysinfo_Network(); + +#ifdef USES_ESPEASY_NOW + handle_sysinfo_ESPEasyNow(); +#endif + +# if FEATURE_ETHERNET + handle_sysinfo_Ethernet(); +# endif // if FEATURE_ETHERNET + +#ifndef WEBSERVER_SYSINFO_MINIMAL + handle_sysinfo_WiFiSettings(); +#endif + + handle_sysinfo_Firmware(); + +#ifndef WEBSERVER_SYSINFO_MINIMAL + handle_sysinfo_SystemStatus(); + + handle_sysinfo_NetworkServices(); + + handle_sysinfo_ESP_Board(); + + handle_sysinfo_Storage(); +#endif + + + html_end_table(); + html_end_form(); + sendHeadandTail_stdtemplate(_TAIL); + TXBuffer.endStream(); +} + +void handle_sysinfo_basicInfo() { + addRowLabelValue(LabelType::UNIT_NR); + + if (node_time.systemTimePresent()) + { + addRowLabelValue(LabelType::LOCAL_TIME); + #if FEATURE_EXT_RTC + if (Settings.ExtTimeSource() != ExtTimeSource_e::None) { + addRowLabelValue(LabelType::EXT_RTC_UTC_TIME); + } + #endif + addRowLabelValue(LabelType::TIME_SOURCE); + addRowLabelValue(LabelType::TIME_WANDER); + } + + addRowLabel(LabelType::UPTIME); + { + addHtml(getExtendedValue(LabelType::UPTIME)); + } + + addRowLabel(LabelType::LOAD_PCT); + + if (wdcounter > 0) + { + addHtml(strformat( + F("%.2f [%%] (LC=%d)"), + getCPUload(), + getLoopCountPerSec())); + } + + static const LabelType::Enum labels[] PROGMEM = + { +#if FEATURE_INTERNAL_TEMPERATURE + LabelType::INTERNAL_TEMPERATURE, +#endif + + LabelType::CPU_ECO_MODE, + + LabelType::RESET_REASON, + LabelType::LAST_TASK_BEFORE_REBOOT, + LabelType::SW_WD_COUNT, + LabelType::MAX_LABEL + }; + addRowLabelValues(labels); + + addRowLabel(F("Boot")); + { + addHtml(getLastBootCauseString()); + addHtml(strformat( + F(" (%d)"), static_cast(RTC.bootCounter))); + } +} + +#ifndef WEBSERVER_SYSINFO_MINIMAL +void handle_sysinfo_memory() { + addTableSeparator(F("Memory"), 2, 3); + +# ifdef ESP32 + addRowLabelValue(LabelType::HEAP_SIZE); + addRowLabelValue(LabelType::HEAP_MIN_FREE); +# endif // ifdef ESP32 + + int freeMem = ESP.getFreeHeap(); + addRowLabel(LabelType::FREE_MEM); + { + addHtmlInt(freeMem); + addUnit(getFormUnit(LabelType::FREE_MEM)); +# ifndef BUILD_NO_RAM_TRACKER + addHtml(F(" (")); + addHtmlInt(lowestRAM); + addHtml(F(" - ")); + addHtml(lowestRAMfunction); + addHtml(')'); +# endif // ifndef BUILD_NO_RAM_TRACKER + } +# if defined(CORE_POST_2_5_0) || defined(ESP32) + # ifndef LIMIT_BUILD_SIZE + addRowLabelValue(LabelType::HEAP_MAX_FREE_BLOCK); + # endif // ifndef LIMIT_BUILD_SIZE +# endif // if defined(CORE_POST_2_5_0) || defined(ESP32) +# if defined(CORE_POST_2_5_0) + # ifndef LIMIT_BUILD_SIZE + addRowLabelValue(LabelType::HEAP_FRAGMENTATION); + # endif // ifndef LIMIT_BUILD_SIZE + { + #ifdef USE_SECOND_HEAP + addRowLabelValue(LabelType::FREE_HEAP_IRAM); + #endif + } +# endif // if defined(CORE_POST_2_5_0) + + + addRowLabel(LabelType::FREE_STACK); + { + addHtmlInt(getCurrentFreeStack()); + addUnit(getFormUnit(LabelType::FREE_STACK)); +# ifndef BUILD_NO_RAM_TRACKER + addHtml(F(" (")); + addHtmlInt(lowestFreeStack); + addHtml(F(" - ")); + addHtml(lowestFreeStackfunction); + addHtml(')'); +# endif // ifndef BUILD_NO_RAM_TRACKER + } + +# if defined(ESP32) && defined(BOARD_HAS_PSRAM) + + addRowLabelValue(LabelType::PSRAM_SIZE); + if (UsePSRAM()) { + static const LabelType::Enum labels[] PROGMEM = + { + LabelType::PSRAM_FREE, + LabelType::PSRAM_MIN_FREE, + LabelType::PSRAM_MAX_FREE_BLOCK, + LabelType::MAX_LABEL + }; + addRowLabelValues(labels); + } +# endif // if defined(ESP32) && defined(BOARD_HAS_PSRAM) +} +#endif + +#ifdef USES_ESPEASY_NOW +void handle_sysinfo_ESPEasyNow() { + addTableSeparator(F(ESPEASY_NOW_NAME), 2, 3); + addRowLabelValue(LabelType::ESPEASY_NOW_ENABLED); + addRowLabelValue(LabelType::ESPEASY_NOW_CHANNEL); + addRowLabelValue(LabelType::ESPEASY_NOW_FORCED_CHANNEL); + addRowLabelValue(LabelType::ESPEASY_NOW_MQTT); + addRowLabelValue(LabelType::ESPEASY_NOW_DISTANCE); +} +#endif + + +# if FEATURE_ETHERNET +void handle_sysinfo_Ethernet() { + if (active_network_medium == NetworkMedium_t::Ethernet) { + addTableSeparator(F("Ethernet"), 2, 3); + + static const LabelType::Enum labels[] PROGMEM = + { + LabelType::ETH_CHIP, + LabelType::ETH_STATE, + LabelType::ETH_SPEED, + LabelType::ETH_DUPLEX, + LabelType::ETH_MAC, +// LabelType::ETH_IP_ADDRESS_SUBNET, +// LabelType::ETH_IP_GATEWAY, +// LabelType::ETH_IP_DNS, + + LabelType::MAX_LABEL + }; + + addRowLabelValues(labels); + + } +} + +# endif // if FEATURE_ETHERNET + +void handle_sysinfo_Network() { + addTableSeparator(F("Network"), 2, 3); + + { + static const LabelType::Enum labels[] PROGMEM = + { +# if FEATURE_ETHERNET || defined(USES_ESPEASY_NOW) + LabelType::ETH_WIFI_MODE, +# endif + LabelType::IP_CONFIG, + LabelType::IP_ADDRESS_SUBNET, +#if FEATURE_USE_IPV6 + LabelType::IP6_LOCAL, + LabelType::IP6_GLOBAL, +// LabelType::IP6_ALL_ADDRESSES, +#endif + LabelType::GATEWAY, + LabelType::CLIENT_IP, + LabelType::DNS, + LabelType::ALLOWED_IP_RANGE, + LabelType::CONNECTED, + LabelType::NUMBER_RECONNECTS, + + LabelType::MAX_LABEL + }; + + addRowLabelValues(labels); + } + + addTableSeparator(F("WiFi"), 2, 3, F("Wifi")); + + const bool showWiFiConnectionInfo = !WiFiEventData.WiFiDisconnected(); + + + addRowLabel(LabelType::WIFI_CONNECTION); + if (showWiFiConnectionInfo) + { + addHtml(toString(getConnectionProtocol())); + addHtml(F(" (RSSI ")); + addHtmlInt(WiFi.RSSI()); + addHtml(F(" dBm)")); + } else addHtml('-'); + + addRowLabel(LabelType::SSID); + if (showWiFiConnectionInfo) + { + addHtml(WiFi.SSID()); + addHtml(F(" (")); + addHtml(WiFi.BSSIDstr()); + addHtml(')'); + } else addHtml('-'); + + #ifdef ESP32 + const int64_t tsf_time = WiFi_get_TSF_time(); + if (tsf_time > 0) { + addRowLabel(F("WiFi TSF time")); + // Split it while printing, so we're not loosing a lot of decimals in the float conversion + uint32_t tsf_usec{}; + addHtml(secondsToDayHourMinuteSecond(micros_to_sec_usec(tsf_time, tsf_usec))); + addHtml(strformat(F(".%06u"), tsf_usec)); + } + #endif + + addRowLabel(getLabel(LabelType::CHANNEL)); + if (showWiFiConnectionInfo) { + addHtml(getValue(LabelType::CHANNEL)); + } else addHtml('-'); + + addRowLabel(getLabel(LabelType::ENCRYPTION_TYPE_STA)); + if (showWiFiConnectionInfo) { + addHtml(getValue(LabelType::ENCRYPTION_TYPE_STA)); + } else addHtml('-'); + + if (active_network_medium == NetworkMedium_t::WIFI) + { + addRowLabel(LabelType::LAST_DISCONNECT_REASON); + addHtml(getValue(LabelType::LAST_DISC_REASON_STR)); + addRowLabelValue(LabelType::WIFI_STORED_SSID1); + addRowLabelValue(LabelType::WIFI_STORED_SSID2); + } + + addRowLabelValue(LabelType::STA_MAC); + addRowLabelValue(LabelType::AP_MAC); + html_TR(); +} + +#ifndef WEBSERVER_SYSINFO_MINIMAL +void handle_sysinfo_WiFiSettings() { + addTableSeparator(F("WiFi Settings"), 2, 3); + + static const LabelType::Enum labels[] PROGMEM = + { + LabelType::FORCE_WIFI_BG, + LabelType::RESTART_WIFI_LOST_CONN, + LabelType::FORCE_WIFI_NOSLEEP, +# ifdef SUPPORT_ARP + LabelType::PERIODICAL_GRAT_ARP, +# endif // ifdef SUPPORT_ARP + LabelType::CONNECTION_FAIL_THRESH, +#if FEATURE_SET_WIFI_TX_PWR + LabelType::WIFI_TX_MAX_PWR, + LabelType::WIFI_CUR_TX_PWR, + LabelType::WIFI_SENS_MARGIN, + LabelType::WIFI_SEND_AT_MAX_TX_PWR, +#endif + LabelType::WIFI_NR_EXTRA_SCANS, +#ifdef USES_ESPEASY_NOW + LabelType::ESPEASY_NOW_ENABLED, + LabelType::ESPEASY_NOW_FORCED_CHANNEL, +#endif + LabelType::WIFI_USE_LAST_CONN_FROM_RTC, +#ifndef ESP32 + LabelType::WAIT_WIFI_CONNECT, +#endif +#ifdef ESP32 + LabelType::WIFI_PASSIVE_SCAN, +#endif + LabelType::HIDDEN_SSID_SLOW_CONNECT, + LabelType::CONNECT_HIDDEN_SSID, + LabelType::SDK_WIFI_AUTORECONNECT, +#if FEATURE_USE_IPV6 + LabelType::ENABLE_IPV6, +#endif + + LabelType::MAX_LABEL + }; + + addRowLabelValues(labels); +} +#endif + +void handle_sysinfo_Firmware() { + addTableSeparator(F("Firmware"), 2, 3); + + addRowLabelValue_copy(LabelType::BUILD_DESC); + addHtml(' '); + addHtml(F(BUILD_NOTES)); + + addRowLabelValue_copy(LabelType::SYSTEM_LIBRARIES); +#ifdef ESP32 + addRowLabelValue_copy(LabelType::ESP_IDF_SDK_VERSION); +#endif + addRowLabelValue_copy(LabelType::GIT_BUILD); + addRowLabelValue_copy(LabelType::PLUGIN_COUNT); + addHtml(' '); + addHtml(getPluginDescriptionString()); + + addRowLabel(F("Build Origin")); + addHtml(get_build_origin()); + addRowLabelValue_copy(LabelType::BUILD_TIME); + addRowLabelValue_copy(LabelType::BINARY_FILENAME); + addRowLabelValue_copy(LabelType::BUILD_PLATFORM); + addRowLabelValue_copy(LabelType::GIT_HEAD); + #ifdef CONFIGURATION_CODE + addRowLabelValue_copy(LabelType::CONFIGURATION_CODE_LBL); + #endif // ifdef CONFIGURATION_CODE +} + +#ifndef WEBSERVER_SYSINFO_MINIMAL +void handle_sysinfo_SystemStatus() { + addTableSeparator(F("System Status"), 2, 3); + + static const LabelType::Enum labels[] PROGMEM = + { + // Actual Loglevel + LabelType::SYSLOG_LOG_LEVEL, + LabelType::SERIAL_LOG_LEVEL, + LabelType::WEB_LOG_LEVEL, +# if FEATURE_SD + LabelType::SD_LOG_LEVEL, +# endif // if FEATURE_SD + + LabelType::ENABLE_SERIAL_PORT_CONSOLE, + LabelType::CONSOLE_SERIAL_PORT, +#if USES_ESPEASY_CONSOLE_FALLBACK_PORT + LabelType::CONSOLE_FALLBACK_TO_SERIAL0, + LabelType::CONSOLE_FALLBACK_PORT, +#endif + LabelType::MAX_LABEL + }; + + addRowLabelValues(labels); +#if FEATURE_CLEAR_I2C_STUCK + if (Settings.EnableClearHangingI2Cbus()) { + addRowLabelValue(LabelType::I2C_BUS_STATE); + addRowLabelValue(LabelType::I2C_BUS_CLEARED_COUNT); + } +#endif +} +#endif + +#ifndef WEBSERVER_SYSINFO_MINIMAL +void handle_sysinfo_NetworkServices() { + addTableSeparator(F("Network Services"), 2, 3); + + addRowLabel(F("Network Connected")); + addEnabled(NetworkConnected()); + + addRowLabel(F("NTP Initialized")); + addEnabled(statusNTPInitialized); + + #if FEATURE_MQTT + if (validControllerIndex(firstEnabledMQTT_ControllerIndex())) { + addRowLabel(F("MQTT Client Connected")); + addEnabled(MQTTclient_connected); + } + #endif +} +#endif + +#ifndef WEBSERVER_SYSINFO_MINIMAL +void handle_sysinfo_ESP_Board() { + addTableSeparator(F("ESP Board"), 2, 3); + + + addRowLabel(LabelType::ESP_CHIP_ID); + + const uint32_t chipID = getChipId(); + addHtml(strformat( + F("%d (%s)"), + chipID, + formatToHex(chipID, 6).c_str())); + + static const LabelType::Enum labels[] PROGMEM = + { + LabelType::ESP_CHIP_FREQ, +# ifdef ESP32 + LabelType::ESP_CHIP_XTAL_FREQ, + LabelType::ESP_CHIP_APB_FREQ, +# endif // ifdef ESP32 + + LabelType::ESP_CHIP_MODEL, +# if defined(ESP32) + LabelType::ESP_CHIP_REVISION, +# endif // if defined(ESP32) + LabelType::ESP_CHIP_CORES, + LabelType::BOARD_NAME, + LabelType::MAX_LABEL + }; + + addRowLabelValues(labels); + + +#if defined(ESP32) + addRowLabel(F("ESP Chip Features")); + addHtml(getChipFeaturesString()); +#endif +} + +# endif // ifndef WEBSERVER_SYSINFO_MINIMAL + +# ifndef WEBSERVER_SYSINFO_MINIMAL +void handle_sysinfo_Storage() { + addTableSeparator(F("Storage"), 2, 3); + + if (getFlashChipId() != 0) { + addRowLabel(LabelType::FLASH_CHIP_ID); + + + // Set to HEX may be something like 0x1640E0. + // Where manufacturer is 0xE0 and device is 0x4016. + addHtml(F("Vendor: ")); + addHtml(getValue(LabelType::FLASH_CHIP_VENDOR)); + + if (flashChipVendorPuya()) + { + addHtml(F(" (PUYA")); + + if (puyaSupport()) { + addHtml(F(", supported")); + } else { + addHtml(F(HTML_SYMBOL_WARNING)); + } + addHtml(')'); + } + addHtml(F(" Device: ")); + addHtml(getValue(LabelType::FLASH_CHIP_MODEL)); + #ifdef ESP32 + if (getChipFeatures().embeddedFlash) { + addHtml(F(" (Embedded)")); + } + #endif + } + const uint32_t realSize = getFlashRealSizeInBytes(); + const uint32_t ideSize = ESP.getFlashChipSize(); + + addRowLabel(LabelType::FLASH_CHIP_REAL_SIZE); + addHtmlInt(realSize / 1024); + addHtml(F(" [kB]")); + + addRowLabel(LabelType::FLASH_IDE_SIZE); + addHtmlInt(ideSize / 1024); + addHtml(F(" [kB]")); + + addRowLabel(LabelType::FLASH_CHIP_SPEED); + addHtmlInt(getFlashChipSpeed() / 1000000); + addHtml(F(" [MHz]")); + + // Please check what is supported for the ESP32 + addRowLabel(LabelType::FLASH_IDE_SPEED); + addHtmlInt(ESP.getFlashChipSpeed() / 1000000); + addHtml(F(" [MHz]")); + + addRowLabelValue(LabelType::FLASH_IDE_MODE); + + addRowLabel(LabelType::FLASH_WRITE_COUNT); + addHtml(strformat( + F("%d daily / %d boot"), + RTC.flashDayCounter, + static_cast(RTC.flashCounter))); + + { + uint32_t maxSketchSize; + bool use2step; + # if defined(ESP8266) + bool otaEnabled = + # endif // if defined(ESP8266) + OTA_possible(maxSketchSize, use2step); + + addRowLabel(LabelType::SKETCH_SIZE); + addHtml(strformat( + F("%d [kB] (%d kB not used)"), + getSketchSize() / 1024, + (maxSketchSize - getSketchSize()) / 1024)); + + addRowLabel(LabelType::MAX_OTA_SKETCH_SIZE); + addHtml(strformat( + F("%d [kB] (%d bytes)"), + maxSketchSize / 1024, + maxSketchSize)); + + # if defined(ESP8266) + addRowLabel(LabelType::OTA_POSSIBLE); + addHtml(boolToString(otaEnabled)); + + addRowLabel(LabelType::OTA_2STEP); + addHtml(boolToString(use2step)); + # endif // if defined(ESP8266) + } + + addRowLabel(LabelType::FS_SIZE); + addHtml(strformat( + F("%d [kB] (%d kB free)"), + SpiffsTotalBytes() / 1024, + SpiffsFreeSpace() / 1024)); + + # ifndef LIMIT_BUILD_SIZE + addRowLabel(F("Page size")); + addHtmlInt(SpiffsPagesize()); + addUnit(getFormUnit(LabelType::FREE_MEM)); // FIXME TD-er: Add labeltype + + addRowLabel(F("Block size")); + addHtmlInt(SpiffsBlocksize()); + addUnit(getFormUnit(LabelType::FREE_MEM)); // FIXME TD-er: Add labeltype + + addRowLabel(F("Number of blocks")); + addHtmlInt(SpiffsTotalBytes() / SpiffsBlocksize()); + + { + # if defined(ESP8266) + fs::FSInfo fs_info; + ESPEASY_FS.info(fs_info); + addRowLabel(F("Maximum open files")); + addHtmlInt(fs_info.maxOpenFiles); + + addRowLabel(F("Maximum path length")); + addHtmlInt(fs_info.maxPathLength); + + # endif // if defined(ESP8266) + } + # endif // ifndef LIMIT_BUILD_SIZE + +#if FEATURE_CHART_STORAGE_LAYOUT + + if (showSettingsFileLayout) { + addTableSeparator(F("Settings Files"), 2, 3); + html_TR_TD(); + addHtml(F("Layout Settings File")); + html_TD(); + getConfig_dat_file_layout(); + html_TR_TD(); + html_TD(); + addHtml(F("(offset / size per item / index)")); + + for (int st = 0; st < static_cast(SettingsType::Enum::SettingsType_MAX); ++st) { + const SettingsType::Enum settingsType = static_cast(st); + #if !FEATURE_NOTIFIER + if (settingsType == SettingsType::Enum::NotificationSettings_Type) { + continue; + } + #endif + html_TR_TD(); + addHtml(SettingsType::getSettingsTypeString(settingsType)); + html_BR(); + addHtml(SettingsType::getSettingsFileName(settingsType)); + html_TD(); + getStorageTableSVG(settingsType); + delay(1); + } + } + + # ifdef ESP32 + addTableSeparator(F("Partitions"), 2, 3, + F("https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-guides/partition-tables.html")); + + addRowLabel(F("Data Partition Table")); + + // TXBuffer += getPartitionTableHeader(F(" - "), F("
")); + // TXBuffer += getPartitionTable(ESP_PARTITION_TYPE_DATA, F(" - "), F("
")); + getPartitionTableSVG(ESP_PARTITION_TYPE_DATA, 0x5856e6); + + addRowLabel(F("App Partition Table")); + + // TXBuffer += getPartitionTableHeader(F(" - "), F("
")); + // TXBuffer += getPartitionTable(ESP_PARTITION_TYPE_APP , F(" - "), F("
")); + getPartitionTableSVG(ESP_PARTITION_TYPE_APP, 0xab56e6); + # endif // ifdef ESP32 + + #ifdef ESP8266 + addTableSeparator(F("Partitions"), 2, 3); + + addRowLabel(F("Partition Table")); + + getPartitionTableSVG(); + #endif +#endif +} +#endif + +#endif // ifdef WEBSERVER_SYSINFO + + +#endif diff --git a/src/src/WebServer/SysInfoPage.h b/src/src/WebServer/SysInfoPage.h index 631f8e1d07..5d6dc857a4 100644 --- a/src/src/WebServer/SysInfoPage.h +++ b/src/src/WebServer/SysInfoPage.h @@ -1,52 +1,56 @@ -#ifndef WEBSERVER_WEBSERVER_SYSINFOPAGE_H -#define WEBSERVER_WEBSERVER_SYSINFOPAGE_H - -#include "../WebServer/common.h" - - -#if SHOW_SYSINFO_JSON - -// ******************************************************************************** -// Web Interface sysinfo page -// ******************************************************************************** -void handle_sysinfo_json(); - -#endif - -#ifdef WEBSERVER_SYSINFO - -void handle_sysinfo(); - -void handle_sysinfo_basicInfo(); - -#ifndef WEBSERVER_SYSINFO_MINIMAL -void handle_sysinfo_memory(); -#endif - -#if FEATURE_ETHERNET -void handle_sysinfo_Ethernet(); -#endif - -void handle_sysinfo_Network(); - -#ifndef WEBSERVER_SYSINFO_MINIMAL -void handle_sysinfo_WiFiSettings(); -#endif - -void handle_sysinfo_Firmware(); - -#ifndef WEBSERVER_SYSINFO_MINIMAL -void handle_sysinfo_SystemStatus(); - -void handle_sysinfo_NetworkServices(); - -void handle_sysinfo_ESP_Board(); - -void handle_sysinfo_Storage(); -#endif - -#endif // ifdef WEBSERVER_SYSINFO - - - +#ifndef WEBSERVER_WEBSERVER_SYSINFOPAGE_H +#define WEBSERVER_WEBSERVER_SYSINFOPAGE_H + +#include "../WebServer/common.h" + + +#if SHOW_SYSINFO_JSON + +// ******************************************************************************** +// Web Interface sysinfo page +// ******************************************************************************** +void handle_sysinfo_json(); + +#endif + +#ifdef WEBSERVER_SYSINFO + +void handle_sysinfo(); + +void handle_sysinfo_basicInfo(); + +#ifndef WEBSERVER_SYSINFO_MINIMAL +void handle_sysinfo_memory(); +#endif + +#if FEATURE_ETHERNET +void handle_sysinfo_Ethernet(); +#endif + +void handle_sysinfo_Network(); + +#ifndef WEBSERVER_SYSINFO_MINIMAL +void handle_sysinfo_WiFiSettings(); +#endif + +#ifdef USES_ESPEASY_NOW +void handle_sysinfo_ESPEasyNow(); +#endif + +void handle_sysinfo_Firmware(); + +#ifndef WEBSERVER_SYSINFO_MINIMAL +void handle_sysinfo_SystemStatus(); + +void handle_sysinfo_NetworkServices(); + +void handle_sysinfo_ESP_Board(); + +void handle_sysinfo_Storage(); +#endif + +#endif // ifdef WEBSERVER_SYSINFO + + + #endif \ No newline at end of file diff --git a/src/src/WebServer/SysVarPage.cpp b/src/src/WebServer/SysVarPage.cpp index cfd8ea75da..2f6d004b21 100644 --- a/src/src/WebServer/SysVarPage.cpp +++ b/src/src/WebServer/SysVarPage.cpp @@ -1,538 +1,556 @@ -#include "../WebServer/SysVarPage.h" - - -#ifdef WEBSERVER_SYSVARS - -# include "../WebServer/ESPEasy_WebServer.h" -# include "../WebServer/AccessControl.h" -# include "../WebServer/Markup.h" -# include "../WebServer/Markup_Forms.h" -# include "../WebServer/HTML_wrappers.h" - -# include "../Globals/RuntimeData.h" - -# include "../Helpers/Numerical.h" -# include "../Helpers/StringConverter.h" -# if FEATURE_STRING_VARIABLES -# include "../Helpers/StringParser.h" -# endif // if -# include "../Helpers/SystemVariables.h" - - -// ******************************************************************************** -// Web Interface sysvars showing all system vars and their value. -// ******************************************************************************** -void addSysVar_enum_html(SystemVariables::Enum enumval) { - addSysVar_html(SystemVariables::toString(enumval), false); -} - -void addSysVar_enum_html(const SystemVariables::Enum enumval[], size_t nrElements) { - for (size_t i = 0; i < nrElements; ++i) { - addSysVar_enum_html(enumval[i]); - } -} - -void handle_sysvars() { - # ifndef BUILD_NO_RAM_TRACKER - checkRAM(F("handle_sysvars")); - # endif // ifndef BUILD_NO_RAM_TRACKER - - if (!isLoggedIn()) { return; } - TXBuffer.startStream(); - sendHeadandTail_stdtemplate(_HEAD); - - html_BR(); - addHtml(F("

This page may load slow.
Do not load too often, since it may affect performance of the node.

")); - html_BR(); - - // the table header - html_table_class_normal(); - html_TR(); - html_table_header(F("System Variables")); - html_table_header(F("Normal")); - html_table_header(F("URL encoded"), F("RTDReference/SystemVariable.html"), 0); - - addTableSeparator(F("Custom Variables"), 3, 3); - - if (customFloatVar.empty()) { - html_TR_TD(); - addHtml(F("No variables set")); - html_TD(); - html_TD(); - } else { - uint32_t tmpVar{}; - - for (auto it = customFloatVar.begin(); it != customFloatVar.end(); ++it) { - const bool isv_ = !validUIntFromString(it->first, tmpVar); - addSysVar_html(strformat(F("%%%s%s%%"), FsP(isv_ ? F("v_") : F("v")), it->first.c_str()), false); - } - } - - # if FEATURE_STRING_VARIABLES - addTableSeparator(F("String Variables"), 3, 3); - - if (customStringVar.empty()) { - html_TR_TD(); - addHtml(F("No string variables set")); - html_TD(); - html_TD(); - } else { - for (auto it = customStringVar.begin(); it != customStringVar.end(); ++it) { - addSysVar_html(strformat(F("[str#%s]"), it->first.c_str()), false); - } - } - # endif // if FEATURE_STRING_VARIABLES - - addTableSeparator(F("Constants"), 3, 3); - { - const SystemVariables::Enum vars[] = { - SystemVariables::CR, - SystemVariables::LF, - SystemVariables::SPACE, - SystemVariables::S_CR, - SystemVariables::S_LF - }; - addSysVar_enum_html(vars, NR_ELEMENTS(vars)); - } - - addTableSeparator(F("Network"), 3, 3); - { - const SystemVariables::Enum vars[] = { - SystemVariables::MAC, - SystemVariables::MAC_INT, - SystemVariables::IP, - SystemVariables::IP4, - SystemVariables::SUBNET, - SystemVariables::GATEWAY, - SystemVariables::DNS, - SystemVariables::DNS_1, - SystemVariables::DNS_2, -# if FEATURE_USE_IPV6 - SystemVariables::IP6_LOCAL, -# endif // if FEATURE_USE_IPV6 - SystemVariables::RSSI, - SystemVariables::SSID, - SystemVariables::BSSID, - SystemVariables::WI_CH - }; - addSysVar_enum_html(vars, NR_ELEMENTS(vars)); - } - # if FEATURE_ETHERNET - addTableSeparator(F("Ethernet"), 3, 3); - { - const SystemVariables::Enum vars[] = { - SystemVariables::ETHWIFIMODE, - SystemVariables::ETHCONNECTED, - SystemVariables::ETHDUPLEX, - SystemVariables::ETHSPEED, - SystemVariables::ETHSTATE, - SystemVariables::ETHSPEEDSTATE, - }; - addSysVar_enum_html(vars, NR_ELEMENTS(vars)); - } - # endif // if FEATURE_ETHERNET - - addTableSeparator(F("System"), 3, 3); - { - const SystemVariables::Enum vars[] = { - SystemVariables::UNIT_sysvar, - # if FEATURE_ZEROFILLED_UNITNUMBER - SystemVariables::UNIT_0_sysvar, - # endif // FEATURE_ZEROFILLED_UNITNUMBER - SystemVariables::SYSLOAD, - SystemVariables::SYSHEAP, - SystemVariables::SYSSTACK, - SystemVariables::SYSNAME, -# if FEATURE_ADC_VCC - SystemVariables::VCC, -# endif // if FEATURE_ADC_VCC - # if FEATURE_INTERNAL_TEMPERATURE - SystemVariables::INTERNAL_TEMPERATURE, - # endif // if FEATURE_INTERNAL_TEMPERATURE - SystemVariables::BOOT_CAUSE, - SystemVariables::ISLIMITED_BUILD, - SystemVariables::ISVAR_DOUBLE, - }; - addSysVar_enum_html(vars, NR_ELEMENTS(vars)); - } - - addTableSeparator(F("Services Status"), 3, 3); - { - const SystemVariables::Enum vars[] = { - SystemVariables::ISWIFI, - SystemVariables::ISNTP, -# if FEATURE_MQTT - SystemVariables::ISMQTT, -# endif // if FEATURE_MQTT -# ifdef USES_P037 - SystemVariables::ISMQTTIMP, -# endif // USES_P037 - }; - addSysVar_enum_html(vars, NR_ELEMENTS(vars)); - } - - addTableSeparator(F("Time"), 3, 3); - { - const SystemVariables::Enum vars[] = { - SystemVariables::LCLTIME, - SystemVariables::LCLTIME_AM, - SystemVariables::SYSTM_HM, - SystemVariables::SYSTM_HM_0, - SystemVariables::SYSTM_HM_SP, - SystemVariables::SYSTM_HM_AM, - SystemVariables::SYSTM_HM_AM_0, - SystemVariables::SYSTM_HM_AM_SP, - SystemVariables::SYSTIME, - SystemVariables::SYSTIME_AM, - SystemVariables::SYSTIME_AM_0, - SystemVariables::SYSTIME_AM_SP, - SystemVariables::SYSBUILD_DATE, - SystemVariables::SYSBUILD_TIME, - SystemVariables::SYSBUILD_FILENAME, - SystemVariables::SYSBUILD_DESCR, - SystemVariables::SYSBUILD_GIT - }; - addSysVar_enum_html(vars, NR_ELEMENTS(vars)); - } - - - addTableSeparator(F("System Time"), 3, 3); - { - const SystemVariables::Enum vars[] = { - SystemVariables::UPTIME, - SystemVariables::UPTIME_MS, - SystemVariables::UNIXTIME, - # ifndef LIMIT_BUILD_SIZE - SystemVariables::UNIXTIME_LCL, - # endif // ifndef LIMIT_BUILD_SIZE - SystemVariables::UNIXDAY, - SystemVariables::UNIXDAY_SEC, - }; - addSysVar_enum_html(vars, NR_ELEMENTS(vars)); - } - { - const __FlashStringHelper *vars[] = { - F("%sysyear% // %sysyear_0%"), - F("%sysyears%"), - F("%sysmonth% // %sysmonth_0%") - }; - - for (unsigned int i = 0; i < NR_ELEMENTS(vars); ++i) { - addSysVar_html(vars[i]); - } - } - addSysVar_enum_html(SystemVariables::SYSMONTH_S); - { - const __FlashStringHelper *vars[] = { - F("%sysday% // %sysday_0%"), - F("%syshour% // %syshour_0%"), - F("%sysmin% // %sysmin_0%"), - F("%syssec% // %syssec_0%") - }; - - for (unsigned int i = 0; i < NR_ELEMENTS(vars); ++i) { - addSysVar_html(vars[i]); - } - } - - { - const SystemVariables::Enum vars[] = { - SystemVariables::SYSSEC_D, - SystemVariables::SYSWEEKDAY, - SystemVariables::SYSWEEKDAY_S, - SystemVariables::SYSTZOFFSET, - # ifndef LIMIT_BUILD_SIZE - SystemVariables::SYSTZOFFSET_S, - # endif // ifndef LIMIT_BUILD_SIZE - }; - addSysVar_enum_html(vars, NR_ELEMENTS(vars)); - } - - addTableSeparator(F("Sunrise/Sunset"), 3, 3); - { - const __FlashStringHelper *vars[] = { - F("%sunset%"), - F("%sunset-1h%"), - F("%sunrise%"), - F("%sunrise+10m%"), - F("%s_sunset%"), - F("%s_sunrise%"), - F("%m_sunset%"), - F("%m_sunrise%") - }; - - for (unsigned int i = 0; i < NR_ELEMENTS(vars); ++i) { - addSysVar_html(vars[i]); - } - } - - addTableSeparator(F("ESP Board"), 3, 3); - { - const SystemVariables::Enum vars[] = { - SystemVariables::ESP_CHIP_ID, - SystemVariables::ESP_CHIP_FREQ, - SystemVariables::ESP_CHIP_MODEL, - SystemVariables::ESP_CHIP_REVISION, - SystemVariables::ESP_CHIP_CORES, - SystemVariables::BOARD_NAME, - }; - addSysVar_enum_html(vars, NR_ELEMENTS(vars)); - } - - addTableSeparator(F("Storage"), 3, 3); - { - const SystemVariables::Enum vars[] = { - SystemVariables::FLASH_FREQ, - SystemVariables::FLASH_SIZE, - SystemVariables::FLASH_CHIP_VENDOR, - SystemVariables::FLASH_CHIP_MODEL, - SystemVariables::FS_SIZE, - SystemVariables::FS_FREE, - }; - addSysVar_enum_html(vars, NR_ELEMENTS(vars)); - } - -# ifndef BUILD_NO_SPECIAL_CHARACTERS_STRINGCONVERTER - { - addTableSeparator(F("Special Characters"), 3, 2); - const __FlashStringHelper *MathSymbols[] = { - // addTableSeparator(F("Degree"), 3, 3); - F("{D}"), - F("°"), - - // addTableSeparator(F("Angle quotes"), 3, 3); - F("{<<}"), - F("«"), - - // addFormSeparator(3); - F("{>>}"), - F("»"), - - // addTableSeparator(F("Greek letter Mu"), 3, 3); - F("{u}"), - F("µ"), - - // addTableSeparator(F("Currency"), 3, 3); - F("{E}"), - F("€"), - - // addFormSeparator(3); - F("{Y}"), - F("¥"), - - // addFormSeparator(3); - F("{P}"), - F("£"), - - // addFormSeparator(3); - F("{c}"), - F("¢"), - - // addTableSeparator(F("Math symbols"), 3, 3); - F("{^1}"), - F("¹"), - - // addFormSeparator(3); - F("{^2}"), - F("²"), - - // addFormSeparator(3); - F("{^3}"), - F("³"), - - // addFormSeparator(3); - F("{1_4}"), - F("¼"), - - // addFormSeparator(3); - F("{1_2}"), - F("½"), - - // addFormSeparator(3); - F("{3_4}"), - F("¾"), - - // addFormSeparator(3); - F("{+-}"), - F("±"), - - // addFormSeparator(3); - F("{x}"), - F("×"), - - // addFormSeparator(3); - F("{..}"), - F("÷"), - }; - - constexpr unsigned int nrGroups = NR_ELEMENTS(MathSymbols) >> 1; - - for (unsigned int i = 0; i < nrGroups; ++i) { - switch (i) { - case 0: addTableSeparator(F("Degree"), 3, 3); break; - case 1: addTableSeparator(F("Angle quotes"), 3, 3); break; - case 3: addTableSeparator(F("Greek letter Mu"), 3, 3); break; - case 4: addTableSeparator(F("Currency"), 3, 3); break; - case 8: addTableSeparator(F("Math symbols"), 3, 3); break; - default: addFormSeparator(3); break; - } - addSysVar_html_specialChar(MathSymbols[2 * i]); - addSysVar_html_specialChar(MathSymbols[(2 * i) + 1]); - } - } -# endif // ifndef BUILD_NO_SPECIAL_CHARACTERS_STRINGCONVERTER - { - addTableSeparator(F("Standard Conversions"), 3, 3); - - const __FlashStringHelper *StdConversions[] = { - F("Wind Dir.: %c_w_dir%(123.4)"), - F("{D}C to {D}F: %c_c2f%(20.4)"), - F("m/s to Bft: %c_ms2Bft%(5.1)"), - F("Dew point(T,H): %c_dew_th%(18.6,67)"), - F("Altitude(air,sea): %c_alt_pres_sea%(850,1000)"), - F("PressureElevation(air,alt): %c_sea_pres_alt%(850,1350.03)"), - - F(""), // addFormSeparator(3, - F("cm to imperial: %c_cm2imp%(190)"), - F("mm to imperial: %c_mm2imp%(1900)"), - - F(""), // addFormSeparator(3, - F("Mins to days: %c_m2day%(1900)"), - F("Mins to dh: %c_m2dh%(1900)"), - F("Mins to dhm: %c_m2dhm%(1900)"), - F("Mins to hcm: %c_m2hcm%(482)"), - F("Secs to dhms: %c_s2dhms%(100000)"), -# if FEATURE_STRING_VARIABLES - F("Timestamp to date/time: %c_ts2date%(%unixtime_lcl%)"), - F("Timestamp to date/time am/pm: %c_ts2date%(%unixtime_lcl%,1)"), - F("Timestamp to weekday: %c_ts2wday%(%unixtime_lcl%)"), - F("Timestamp to ISO date/time: %c_ts2isodate%(%unixtime_lcl%)"), - F("Timestamp to ISO date/time/offset: %c_ts2isodate%(%unixtime_lcl%,1)"), -# endif // if FEATURE_STRING_VARIABLES - - - F(""), // addFormSeparator(3, -# ifndef LIMIT_BUILD_SIZE - F("Random: %c_random%(0, 100)"), -# endif // ifndef LIMIT_BUILD_SIZE - F("To HEX: %c_2hex%(100000) or: %c_2hex%(100000,6)"), - -# if FEATURE_ESPEASY_P2P - F(""), // addFormSeparator(3, - F("Unit to IP: %c_u2ip%(%unit%, 2)"), - F("Unit to Name: %c_uname%(%unit%)"), - F("Unit to Age: %c_uage%(%unit%)"), - F("Unit to Build: %c_ubuild%(%unit%)"), - F("Unit to Build-string: %c_ubuildstr%(%unit%)"), - F("Unit to Load: %c_uload%(%unit%)"), - F("Unit to ESP-type: %c_utype%(%unit%)"), - F("Unit to ESP-type-string: %c_utypestr%(%unit%)"), -# endif // if FEATURE_ESPEASY_P2P -# if FEATURE_STRING_VARIABLES - F(""), // addFormSeparator(3, - F("Check if numeric value (test:'123'): %c_isnum%(test)"), - F("Format (testf:'Out $6.2f M$g'): %c_strf%(testf,123.45,2)"), -# endif // if FEATURE_STRING_VARIABLES - }; - -# if FEATURE_STRING_VARIABLES - const bool has_test = hasCustomStringVar(F("test")); - const bool has_testf = hasCustomStringVar(F("testf")); - - // Save current values - String test, testf; - - if (has_test) { - test = getCustomStringVar(F("test")); - } - - if (has_testf) { - testf = getCustomStringVar(F("testf")); - } - - setCustomStringVar(F("test"), F("123")); - setCustomStringVar(F("testf"), F("Out $6.2f M$g")); -# endif // if FEATURE_STRING_VARIABLES - constexpr uint16_t nrStdConv = NR_ELEMENTS(StdConversions); - - for (uint16_t i = 0; i < nrStdConv; ++i) { - const String s(StdConversions[i]); - - if (s.isEmpty()) { addFormSeparator(3); } - else { addSysVar_html(s); } - } -# if FEATURE_STRING_VARIABLES - - // Restore values - if (has_test) { - setCustomStringVar(F("test"), test); - } - else { - clearCustomStringVar(F("test")); - } - - if (has_testf) { - setCustomStringVar(F("testf"), testf); - } - else { - clearCustomStringVar(F("testf")); - } - -# endif // if FEATURE_STRING_VARIABLES - } - html_end_table(); - html_end_form(); - sendHeadandTail_stdtemplate(_TAIL); - TXBuffer.endStream(); -} - -void addSysVar_html(const __FlashStringHelper *input) { - addSysVar_html(String(input), false); -} - -void addSysVar_html_specialChar(const __FlashStringHelper *input) { - addSysVar_html(String(input), true); -} - -void addSysVar_html(const String& input) { - addSysVar_html(input, false); -} - -void addSysVar_html(String input, bool isSpecialChar) { - // Make deepcopy for replacement, so parameter is a copy, not a const reference - - html_TR_TD(); - addHtml(F("
")); // Make monospaced ( tag?)
-  addHtml(input);
-  addHtml(F("
")); - - if (isSpecialChar) { - parseSpecialCharacters(input, false); - html_TD(); - addHtml(input); - html_TD(); - addHtml(URLEncode(input)); - } else { - const String orig_input(input); - - for (int i = 0; i < 2; ++i) { - const bool URLencoded = i == 1; - - if (URLencoded) { - input = orig_input; - } - - parseSystemVariables(input, URLencoded); - parseStandardConversions(input, URLencoded); - # if FEATURE_STRING_VARIABLES - input = parseTemplate_padded(input, 0, URLencoded); - # endif // if FEATURE_STRING_VARIABLES - - html_TD(); - addHtml(input); - } - } - delay(0); -} - -#endif // WEBSERVER_SYSVARS +#include "../WebServer/SysVarPage.h" + + +#ifdef WEBSERVER_SYSVARS + +# include "../WebServer/ESPEasy_WebServer.h" +# include "../WebServer/AccessControl.h" +# include "../WebServer/Markup.h" +# include "../WebServer/Markup_Forms.h" +# include "../WebServer/HTML_wrappers.h" + +# include "../Globals/RuntimeData.h" + +# include "../Helpers/Numerical.h" +# include "../Helpers/StringConverter.h" +# if FEATURE_STRING_VARIABLES +# include "../Helpers/StringParser.h" +# endif // if +# include "../Helpers/SystemVariables.h" + + +// ******************************************************************************** +// Web Interface sysvars showing all system vars and their value. +// ******************************************************************************** +void addSysVar_enum_html(SystemVariables::Enum enumval) { + addSysVar_html(SystemVariables::toString(enumval), false); +} + +void addSysVar_enum_html(const SystemVariables::Enum enumval[], size_t nrElements) { + for (size_t i = 0; i < nrElements; ++i) { + addSysVar_enum_html(enumval[i]); + } +} + +void handle_sysvars() { + # ifndef BUILD_NO_RAM_TRACKER + checkRAM(F("handle_sysvars")); + # endif // ifndef BUILD_NO_RAM_TRACKER + + if (!isLoggedIn()) { return; } + TXBuffer.startStream(); + sendHeadandTail_stdtemplate(_HEAD); + + html_BR(); + addHtml(F("

This page may load slow.
Do not load too often, since it may affect performance of the node.

")); + html_BR(); + + // the table header + html_table_class_normal(); + html_TR(); + html_table_header(F("System Variables")); + html_table_header(F("Normal")); + html_table_header(F("URL encoded"), F("RTDReference/SystemVariable.html"), 0); + + addTableSeparator(F("Custom Variables"), 3, 3); + + if (customFloatVar.empty()) { + html_TR_TD(); + addHtml(F("No variables set")); + html_TD(); + html_TD(); + } else { + uint32_t tmpVar{}; + + for (auto it = customFloatVar.begin(); it != customFloatVar.end(); ++it) { + const bool isv_ = !validUIntFromString(it->first, tmpVar); + addSysVar_html(strformat(F("%%%s%s%%"), FsP(isv_ ? F("v_") : F("v")), it->first.c_str()), false); + } + } + + # if FEATURE_STRING_VARIABLES + addTableSeparator(F("String Variables"), 3, 3); + + if (customStringVar.empty()) { + html_TR_TD(); + addHtml(F("No string variables set")); + html_TD(); + html_TD(); + } else { + for (auto it = customStringVar.begin(); it != customStringVar.end(); ++it) { + addSysVar_html(strformat(F("[str#%s]"), it->first.c_str()), false); + } + } + # endif // if FEATURE_STRING_VARIABLES + + addTableSeparator(F("Constants"), 3, 3); + { + const SystemVariables::Enum vars[] = { + SystemVariables::CR, + SystemVariables::LF, + SystemVariables::SPACE, + SystemVariables::S_CR, + SystemVariables::S_LF + }; + addSysVar_enum_html(vars, NR_ELEMENTS(vars)); + } + + addTableSeparator(F("Network"), 3, 3); + { + const SystemVariables::Enum vars[] = { + SystemVariables::MAC, + SystemVariables::MAC_INT, + SystemVariables::IP, + SystemVariables::IP4, + SystemVariables::SUBNET, + SystemVariables::GATEWAY, + SystemVariables::DNS, + SystemVariables::DNS_1, + SystemVariables::DNS_2, +# if FEATURE_USE_IPV6 + SystemVariables::IP6_LOCAL, +# endif // if FEATURE_USE_IPV6 + SystemVariables::RSSI, + SystemVariables::SSID, + SystemVariables::BSSID, + SystemVariables::WI_CH + }; + addSysVar_enum_html(vars, NR_ELEMENTS(vars)); + } + +#ifdef USES_ESPEASY_NOW + addTableSeparator(F(ESPEASY_NOW_NAME), 3, 3); + { + const SystemVariables::Enum vars[] = { + + SystemVariables::ESPEASY_NOW_ENABLED, + SystemVariables::ESPEASY_NOW_CHANNEL, + SystemVariables::ESPEASY_NOW_FORCED_CHANNEL, + SystemVariables::ESPEASY_NOW_MQTT, + SystemVariables::ESPEASY_NOW_DISTANCE + }; + addSysVar_enum_html(vars, NR_ELEMENTS(vars)); + } + +#endif + + +# if FEATURE_ETHERNET + addTableSeparator(F("Ethernet"), 3, 3); + { + const SystemVariables::Enum vars[] = { + SystemVariables::ETHWIFIMODE, + SystemVariables::ETHCONNECTED, + SystemVariables::ETHDUPLEX, + SystemVariables::ETHSPEED, + SystemVariables::ETHSTATE, + SystemVariables::ETHSPEEDSTATE, + }; + addSysVar_enum_html(vars, NR_ELEMENTS(vars)); + } + # endif // if FEATURE_ETHERNET + + addTableSeparator(F("System"), 3, 3); + { + const SystemVariables::Enum vars[] = { + SystemVariables::UNIT_sysvar, + # if FEATURE_ZEROFILLED_UNITNUMBER + SystemVariables::UNIT_0_sysvar, + # endif // FEATURE_ZEROFILLED_UNITNUMBER + SystemVariables::SYSLOAD, + SystemVariables::SYSHEAP, + SystemVariables::SYSSTACK, + SystemVariables::SYSNAME, +# if FEATURE_ADC_VCC + SystemVariables::VCC, +# endif // if FEATURE_ADC_VCC + # if FEATURE_INTERNAL_TEMPERATURE + SystemVariables::INTERNAL_TEMPERATURE, + # endif // if FEATURE_INTERNAL_TEMPERATURE + SystemVariables::BOOT_CAUSE, + SystemVariables::ISLIMITED_BUILD, + SystemVariables::ISVAR_DOUBLE, + }; + addSysVar_enum_html(vars, NR_ELEMENTS(vars)); + } + + addTableSeparator(F("Services Status"), 3, 3); + { + const SystemVariables::Enum vars[] = { + SystemVariables::ISWIFI, + SystemVariables::ISNTP, +# if FEATURE_MQTT + SystemVariables::ISMQTT, +# endif // if FEATURE_MQTT +# ifdef USES_P037 + SystemVariables::ISMQTTIMP, +# endif // USES_P037 + }; + addSysVar_enum_html(vars, NR_ELEMENTS(vars)); + } + + addTableSeparator(F("Time"), 3, 3); + { + const SystemVariables::Enum vars[] = { + SystemVariables::LCLTIME, + SystemVariables::LCLTIME_AM, + SystemVariables::SYSTM_HM, + SystemVariables::SYSTM_HM_0, + SystemVariables::SYSTM_HM_SP, + SystemVariables::SYSTM_HM_AM, + SystemVariables::SYSTM_HM_AM_0, + SystemVariables::SYSTM_HM_AM_SP, + SystemVariables::SYSTIME, + SystemVariables::SYSTIME_AM, + SystemVariables::SYSTIME_AM_0, + SystemVariables::SYSTIME_AM_SP, + SystemVariables::SYSBUILD_DATE, + SystemVariables::SYSBUILD_TIME, + SystemVariables::SYSBUILD_FILENAME, + SystemVariables::SYSBUILD_DESCR, + SystemVariables::SYSBUILD_GIT + }; + addSysVar_enum_html(vars, NR_ELEMENTS(vars)); + } + + + addTableSeparator(F("System Time"), 3, 3); + { + const SystemVariables::Enum vars[] = { + SystemVariables::UPTIME, + SystemVariables::UPTIME_MS, + SystemVariables::UNIXTIME, + # ifndef LIMIT_BUILD_SIZE + SystemVariables::UNIXTIME_LCL, + # endif // ifndef LIMIT_BUILD_SIZE + SystemVariables::UNIXDAY, + SystemVariables::UNIXDAY_SEC, + }; + addSysVar_enum_html(vars, NR_ELEMENTS(vars)); + } + { + const __FlashStringHelper *vars[] = { + F("%sysyear% // %sysyear_0%"), + F("%sysyears%"), + F("%sysmonth% // %sysmonth_0%") + }; + + for (unsigned int i = 0; i < NR_ELEMENTS(vars); ++i) { + addSysVar_html(vars[i]); + } + } + addSysVar_enum_html(SystemVariables::SYSMONTH_S); + { + const __FlashStringHelper *vars[] = { + F("%sysday% // %sysday_0%"), + F("%syshour% // %syshour_0%"), + F("%sysmin% // %sysmin_0%"), + F("%syssec% // %syssec_0%") + }; + + for (unsigned int i = 0; i < NR_ELEMENTS(vars); ++i) { + addSysVar_html(vars[i]); + } + } + + { + const SystemVariables::Enum vars[] = { + SystemVariables::SYSSEC_D, + SystemVariables::SYSWEEKDAY, + SystemVariables::SYSWEEKDAY_S, + SystemVariables::SYSTZOFFSET, + # ifndef LIMIT_BUILD_SIZE + SystemVariables::SYSTZOFFSET_S, + # endif // ifndef LIMIT_BUILD_SIZE + }; + addSysVar_enum_html(vars, NR_ELEMENTS(vars)); + } + + addTableSeparator(F("Sunrise/Sunset"), 3, 3); + { + const __FlashStringHelper *vars[] = { + F("%sunset%"), + F("%sunset-1h%"), + F("%sunrise%"), + F("%sunrise+10m%"), + F("%s_sunset%"), + F("%s_sunrise%"), + F("%m_sunset%"), + F("%m_sunrise%") + }; + + for (unsigned int i = 0; i < NR_ELEMENTS(vars); ++i) { + addSysVar_html(vars[i]); + } + } + + addTableSeparator(F("ESP Board"), 3, 3); + { + const SystemVariables::Enum vars[] = { + SystemVariables::ESP_CHIP_ID, + SystemVariables::ESP_CHIP_FREQ, + SystemVariables::ESP_CHIP_MODEL, + SystemVariables::ESP_CHIP_REVISION, + SystemVariables::ESP_CHIP_CORES, + SystemVariables::BOARD_NAME, + }; + addSysVar_enum_html(vars, NR_ELEMENTS(vars)); + } + + addTableSeparator(F("Storage"), 3, 3); + { + const SystemVariables::Enum vars[] = { + SystemVariables::FLASH_FREQ, + SystemVariables::FLASH_SIZE, + SystemVariables::FLASH_CHIP_VENDOR, + SystemVariables::FLASH_CHIP_MODEL, + SystemVariables::FS_SIZE, + SystemVariables::FS_FREE, + }; + addSysVar_enum_html(vars, NR_ELEMENTS(vars)); + } + +# ifndef BUILD_NO_SPECIAL_CHARACTERS_STRINGCONVERTER + { + addTableSeparator(F("Special Characters"), 3, 2); + const __FlashStringHelper *MathSymbols[] = { + // addTableSeparator(F("Degree"), 3, 3); + F("{D}"), + F("°"), + + // addTableSeparator(F("Angle quotes"), 3, 3); + F("{<<}"), + F("«"), + + // addFormSeparator(3); + F("{>>}"), + F("»"), + + // addTableSeparator(F("Greek letter Mu"), 3, 3); + F("{u}"), + F("µ"), + + // addTableSeparator(F("Currency"), 3, 3); + F("{E}"), + F("€"), + + // addFormSeparator(3); + F("{Y}"), + F("¥"), + + // addFormSeparator(3); + F("{P}"), + F("£"), + + // addFormSeparator(3); + F("{c}"), + F("¢"), + + // addTableSeparator(F("Math symbols"), 3, 3); + F("{^1}"), + F("¹"), + + // addFormSeparator(3); + F("{^2}"), + F("²"), + + // addFormSeparator(3); + F("{^3}"), + F("³"), + + // addFormSeparator(3); + F("{1_4}"), + F("¼"), + + // addFormSeparator(3); + F("{1_2}"), + F("½"), + + // addFormSeparator(3); + F("{3_4}"), + F("¾"), + + // addFormSeparator(3); + F("{+-}"), + F("±"), + + // addFormSeparator(3); + F("{x}"), + F("×"), + + // addFormSeparator(3); + F("{..}"), + F("÷"), + }; + + constexpr unsigned int nrGroups = NR_ELEMENTS(MathSymbols) >> 1; + + for (unsigned int i = 0; i < nrGroups; ++i) { + switch (i) { + case 0: addTableSeparator(F("Degree"), 3, 3); break; + case 1: addTableSeparator(F("Angle quotes"), 3, 3); break; + case 3: addTableSeparator(F("Greek letter Mu"), 3, 3); break; + case 4: addTableSeparator(F("Currency"), 3, 3); break; + case 8: addTableSeparator(F("Math symbols"), 3, 3); break; + default: addFormSeparator(3); break; + } + addSysVar_html_specialChar(MathSymbols[2 * i]); + addSysVar_html_specialChar(MathSymbols[(2 * i) + 1]); + } + } +# endif // ifndef BUILD_NO_SPECIAL_CHARACTERS_STRINGCONVERTER + { + addTableSeparator(F("Standard Conversions"), 3, 3); + + const __FlashStringHelper *StdConversions[] = { + F("Wind Dir.: %c_w_dir%(123.4)"), + F("{D}C to {D}F: %c_c2f%(20.4)"), + F("m/s to Bft: %c_ms2Bft%(5.1)"), + F("Dew point(T,H): %c_dew_th%(18.6,67)"), + F("Altitude(air,sea): %c_alt_pres_sea%(850,1000)"), + F("PressureElevation(air,alt): %c_sea_pres_alt%(850,1350.03)"), + + F(""), // addFormSeparator(3, + F("cm to imperial: %c_cm2imp%(190)"), + F("mm to imperial: %c_mm2imp%(1900)"), + + F(""), // addFormSeparator(3, + F("Mins to days: %c_m2day%(1900)"), + F("Mins to dh: %c_m2dh%(1900)"), + F("Mins to dhm: %c_m2dhm%(1900)"), + F("Mins to hcm: %c_m2hcm%(482)"), + F("Secs to dhms: %c_s2dhms%(100000)"), +# if FEATURE_STRING_VARIABLES + F("Timestamp to date/time: %c_ts2date%(%unixtime_lcl%)"), + F("Timestamp to date/time am/pm: %c_ts2date%(%unixtime_lcl%,1)"), + F("Timestamp to weekday: %c_ts2wday%(%unixtime_lcl%)"), + F("Timestamp to ISO date/time: %c_ts2isodate%(%unixtime_lcl%)"), + F("Timestamp to ISO date/time/offset: %c_ts2isodate%(%unixtime_lcl%,1)"), +# endif // if FEATURE_STRING_VARIABLES + + + F(""), // addFormSeparator(3, +# ifndef LIMIT_BUILD_SIZE + F("Random: %c_random%(0, 100)"), +# endif // ifndef LIMIT_BUILD_SIZE + F("To HEX: %c_2hex%(100000) or: %c_2hex%(100000,6)"), + +# if FEATURE_ESPEASY_P2P + F(""), // addFormSeparator(3, + F("Unit to IP: %c_u2ip%(%unit%, 2)"), + F("Unit to Name: %c_uname%(%unit%)"), + F("Unit to Age: %c_uage%(%unit%)"), + F("Unit to Build: %c_ubuild%(%unit%)"), + F("Unit to Build-string: %c_ubuildstr%(%unit%)"), + F("Unit to Load: %c_uload%(%unit%)"), + F("Unit to ESP-type: %c_utype%(%unit%)"), + F("Unit to ESP-type-string: %c_utypestr%(%unit%)"), +# endif // if FEATURE_ESPEASY_P2P +# if FEATURE_STRING_VARIABLES + F(""), // addFormSeparator(3, + F("Check if numeric value (test:'123'): %c_isnum%(test)"), + F("Format (testf:'Out $6.2f M$g'): %c_strf%(testf,123.45,2)"), +# endif // if FEATURE_STRING_VARIABLES + }; + +# if FEATURE_STRING_VARIABLES + const bool has_test = hasCustomStringVar(F("test")); + const bool has_testf = hasCustomStringVar(F("testf")); + + // Save current values + String test, testf; + + if (has_test) { + test = getCustomStringVar(F("test")); + } + + if (has_testf) { + testf = getCustomStringVar(F("testf")); + } + + setCustomStringVar(F("test"), F("123")); + setCustomStringVar(F("testf"), F("Out $6.2f M$g")); +# endif // if FEATURE_STRING_VARIABLES + constexpr uint16_t nrStdConv = NR_ELEMENTS(StdConversions); + + for (uint16_t i = 0; i < nrStdConv; ++i) { + const String s(StdConversions[i]); + + if (s.isEmpty()) { addFormSeparator(3); } + else { addSysVar_html(s); } + } +# if FEATURE_STRING_VARIABLES + + // Restore values + if (has_test) { + setCustomStringVar(F("test"), test); + } + else { + clearCustomStringVar(F("test")); + } + + if (has_testf) { + setCustomStringVar(F("testf"), testf); + } + else { + clearCustomStringVar(F("testf")); + } + +# endif // if FEATURE_STRING_VARIABLES + } + html_end_table(); + html_end_form(); + sendHeadandTail_stdtemplate(_TAIL); + TXBuffer.endStream(); +} + +void addSysVar_html(const __FlashStringHelper *input) { + addSysVar_html(String(input), false); +} + +void addSysVar_html_specialChar(const __FlashStringHelper *input) { + addSysVar_html(String(input), true); +} + +void addSysVar_html(const String& input) { + addSysVar_html(input, false); +} + +void addSysVar_html(String input, bool isSpecialChar) { + // Make deepcopy for replacement, so parameter is a copy, not a const reference + + html_TR_TD(); + addHtml(F("
")); // Make monospaced ( tag?)
+  addHtml(input);
+  addHtml(F("
")); + + if (isSpecialChar) { + parseSpecialCharacters(input, false); + html_TD(); + addHtml(input); + html_TD(); + addHtml(URLEncode(input)); + } else { + const String orig_input(input); + + for (int i = 0; i < 2; ++i) { + const bool URLencoded = i == 1; + + if (URLencoded) { + input = orig_input; + } + + parseSystemVariables(input, URLencoded); + parseStandardConversions(input, URLencoded); + # if FEATURE_STRING_VARIABLES + input = parseTemplate_padded(input, 0, URLencoded); + # endif // if FEATURE_STRING_VARIABLES + + html_TD(); + addHtml(input); + } + } + delay(0); +} + +#endif // WEBSERVER_SYSVARS diff --git a/tools/pio/pre_custom_esp32.py b/tools/pio/pre_custom_esp32.py index 6c642b9b37..b076f1dd2d 100644 --- a/tools/pio/pre_custom_esp32.py +++ b/tools/pio/pre_custom_esp32.py @@ -48,9 +48,12 @@ "-DUSES_P100", # Pulse Counter - DS2423 # "-DUSES_P087", # Serial Proxy -# "-DUSES_P094", # CUL Reader + "-DUSES_P094", # CUL Reader + "-DP094_DEBUG_OPTIONS=1", # "-DUSES_P095", # TFT ILI9341 "-DUSES_P097", # Touch (ESP32) + "-DUSES_P098", # PWM motor + "-DUSES_P100", # Pulse Counter - DS2423 "-DUSES_P106", # BME680 "-DUSES_P107", # SI1145 UV index "-DUSES_P110", # VL53L0x @@ -59,14 +62,16 @@ "-DUSES_P146", # Cache Reader "-DUSES_P123", # AS3935 Lightning Detector +# "-DUSES_C015", # Blynk "-DUSES_C016", # Cache Controller "-DUSES_C018", # TTN/RN2483 -# "-DUSES_C015", # Blynk + "-DUSES_C019", # ESPEasy-NOW "-DUSES_N001", # E-mail "-DFEATURE_MQTT_TLS=1", "-DFEATURE_EMAIL_TLS=1", + "-DUSES_ESPEASY_NOW", "-DFEATURE_EXT_RTC=1", "-DFEATURE_SD=1", "-DFEATURE_I2CMULTIPLEXER=1", diff --git a/tools/pio/pre_custom_esp82xx.py b/tools/pio/pre_custom_esp82xx.py index 1d1768bd3e..6d0cfcfc71 100644 --- a/tools/pio/pre_custom_esp82xx.py +++ b/tools/pio/pre_custom_esp82xx.py @@ -47,23 +47,27 @@ # "-DUSES_P098", # PWM motor # "-DUSES_P100", # Pulse Counter - DS2423 # "-DUSES_P087", # Serial Proxy -# "-DUSES_P094", # CUL Reader + "-DUSES_P094", # CUL Reader # "-DUSES_P095", # TFT ILI9341 +# "-DUSES_P098", # PWM motor +# "-DUSES_P100", # Pulse Counter - DS2423 # "-DUSES_P106", # BME680 # "-DUSES_P107", # SI1145 UV index - "-DUSES_P146", # Cache Reader - "-DUSES_P169", # AS3935 Lightning Detector +# "-DUSES_P146", # Cache Reader +# "-DUSES_P169", # AS3935 Lightning Detector - "-DUSES_C016", # Cache Controller -# "-DUSES_C018", # TTN/RN2483 # "-DUSES_C015", # Blynk +# "-DUSES_C016", # Cache Controller +# "-DUSES_C018", # TTN/RN2483 + "-DUSES_C019", # ESPEasy-NOW "-DFEATURE_MQTT_TLS=0", "-DFEATURE_EMAIL_TLS=0", + "-DUSES_ESPEASY_NOW", # "-DFEATURE_MDNS=1", # "-DFEATURE_SD=1", - "-DFEATURE_EXT_RTC=1", +# "-DFEATURE_EXT_RTC=1", "-DFEATURE_I2CMULTIPLEXER=1", "-DFEATURE_TRIGONOMETRIC_FUNCTIONS_RULES=1", "-DFEATURE_CUSTOM_PROVISIONING=1", @@ -73,8 +77,8 @@ "-DEMBED_ESPEASY_DEFAULT_MIN_CSS", # "-DPLUGIN_STATS_NR_ELEMENTS=30", - "-DFEATURE_PLUGIN_STATS=1", - "-DFEATURE_CHART_JS=1", +# "-DFEATURE_PLUGIN_STATS=1", +# "-DFEATURE_CHART_JS=1", # "-DFEATURE_ADC_VCC=1",