From d1f7a2a0b93dae28c2e54c14c61affb566650c68 Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Thu, 14 Aug 2025 22:47:03 +0200 Subject: [PATCH] [P089] Add support for ESP32 --- lib/ESPping/LICENSE | 201 ++++++++++ lib/ESPping/README.md | 41 ++ lib/ESPping/examples/test_ping/test_ping.ino | 61 +++ lib/ESPping/keywords.txt | 29 ++ lib/ESPping/library.properties | 9 + lib/ESPping/src/ESPping.cpp | 161 ++++++++ lib/ESPping/src/ESPping.h | 74 ++++ lib/ESPping/src/ping32.cpp | 384 +++++++++++++++++++ lib/ESPping/src/ping32.h | 37 ++ src/_P089_Ping.ino | 57 ++- src/src/CustomBuild/define_plugin_sets.h | 12 + src/src/Helpers/_Plugin_init.cpp | 8 - src/src/Helpers/_Plugin_init.h | 3 - src/src/PluginStructs/P089_data_struct.cpp | 110 +++++- src/src/PluginStructs/P089_data_struct.h | 70 +++- 15 files changed, 1229 insertions(+), 28 deletions(-) create mode 100644 lib/ESPping/LICENSE create mode 100644 lib/ESPping/README.md create mode 100644 lib/ESPping/examples/test_ping/test_ping.ino create mode 100644 lib/ESPping/keywords.txt create mode 100644 lib/ESPping/library.properties create mode 100644 lib/ESPping/src/ESPping.cpp create mode 100644 lib/ESPping/src/ESPping.h create mode 100644 lib/ESPping/src/ping32.cpp create mode 100644 lib/ESPping/src/ping32.h diff --git a/lib/ESPping/LICENSE b/lib/ESPping/LICENSE new file mode 100644 index 000000000..29f81d812 --- /dev/null +++ b/lib/ESPping/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/lib/ESPping/README.md b/lib/ESPping/README.md new file mode 100644 index 000000000..6b24c51d6 --- /dev/null +++ b/lib/ESPping/README.md @@ -0,0 +1,41 @@ +# ESPping +Let the ESP32/ESP8266 ping a remote machine. + +With this library an ESP32/ESP8266 can ping a remote machine and know if it's reachable. +It provide some basic measurements on ping messages (avg response time). + +thanks to Daniele Colanardi and Marian Craciunescu + +## Usage + +First, include the library in your sketch along with WiFi library: + +```Arduino +#include +``` + +Next, simply call the `Ping.ping()` function + +```Arduino +IPAddress ip (192, 168, 0, 1); // The remote ip to ping +bool ret = Ping.ping(ip); +``` + +`ret` will be true if the remote responded to pings, false if not reachable. +The library supports hostname too, just pass a string instead of the ip address: + +```Arduino +bool ret = Ping.ping("www.google.com"); +``` + +Additionally, the function accept a second integer parameter `count` that specify how many pings has to be sent: + +```Arduino +bool ret = Ping.ping(ip_or_host, 10); +``` + +After `Ping.ping()` has been called, the average response time (in milliseconds) can be retrieved with + +```Arduino +float avg_time_ms = Ping.averageTime(); +``` diff --git a/lib/ESPping/examples/test_ping/test_ping.ino b/lib/ESPping/examples/test_ping/test_ping.ino new file mode 100644 index 000000000..c471ed630 --- /dev/null +++ b/lib/ESPping/examples/test_ping/test_ping.ino @@ -0,0 +1,61 @@ +/****************************************************** + test Ping +******************************************************/ + +#include + +void setup() +{ + Serial.begin(115200); + delay(100); + + WiFi.begin("ssid","password"); + + // attente connexion + Serial.print("\nConnection"); + while (WiFi.status() != WL_CONNECTED) + { + delay(500); + Serial.print("."); + } + Serial.print("\nConnection OK, IP: "); + Serial.print(WiFi.localIP()); + Serial.print(" Gateway: "); + Serial.println(WiFi.gatewayIP()); + Serial.println("You can try to ping me ;-)"); + +} + +void loop() +{ + + // Ping IP + const IPAddress remote_ip(9,9,9,9); + Serial.print(remote_ip); + if (Ping.ping(remote_ip) > 0){ + Serial.printf(" response time : %d/%.2f/%d ms\n", Ping.minTime(), Ping.averageTime(), Ping.maxTime()); + } else { + Serial.println(" Error !"); + } + delay(1000); + + // Ping Host + const char* remote_host = "quad9.net"; + Serial.print(remote_host); + if (Ping.ping(remote_host) > 0){ + Serial.printf(" response time : %d/%.2f/%d ms\n", Ping.minTime(), Ping.averageTime(), Ping.maxTime()); + } else { + Serial.println(" Ping Error !"); + } + delay(1000); + + // Ping local IP + Serial.print(WiFi.gatewayIP()); + if (Ping.ping(WiFi.gatewayIP()) > 0){ + Serial.printf(" response time : %d/%.2f/%d ms\n", Ping.minTime(), Ping.averageTime(), Ping.maxTime()); + } else { + Serial.println(" Ping Error !"); + } + delay(1000); + +} diff --git a/lib/ESPping/keywords.txt b/lib/ESPping/keywords.txt new file mode 100644 index 000000000..08d3e0b21 --- /dev/null +++ b/lib/ESPping/keywords.txt @@ -0,0 +1,29 @@ +####################################### +# Syntax Coloring Map For ESPping +####################################### + +####################################### +# Library (KEYWORD3) +####################################### + +ESPping KEYWORD3 + +####################################### +# Class (KEYWORD1) +####################################### + +Ping KEYWORD1 + +####################################### +# Methods and Functions (KEYWORD2) +####################################### + +ping KEYWORD2 +minTime KEYWORD2 +averageTime KEYWORD2 +maxTime KEYWORD2 + +####################################### +# Constants (LITERAL1) +####################################### + diff --git a/lib/ESPping/library.properties b/lib/ESPping/library.properties new file mode 100644 index 000000000..d5b459d76 --- /dev/null +++ b/lib/ESPping/library.properties @@ -0,0 +1,9 @@ +name=ESPping +version=1.0.5 +author=dvarrel, Daniele Colanardi, Marian Craciunescu +maintainer=dvarrel +sentence=Let the ESP32/ESP8266 ping a remote machine. +paragraph=With this library an ESP32/ESP8266 can ping a remote machine and know if reachable. It provide some basic measurements on ping messages (avg response time). +category=Communication +url=https://github.com/dvarrel/ESPping.git +architectures=esp8266,esp32 diff --git a/lib/ESPping/src/ESPping.cpp b/lib/ESPping/src/ESPping.cpp new file mode 100644 index 000000000..2d151fd6e --- /dev/null +++ b/lib/ESPping/src/ESPping.cpp @@ -0,0 +1,161 @@ +/* + ESP8266Ping - Ping library for ESP8266 + Copyright (c) 2015 Daniele Colanardi. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#include "ESPping.h" +#include + +#ifdef ESP8266 +extern "C" void esp_schedule(); +extern "C" void esp_yield(); +#endif +#ifdef ESP32 +extern "C" void esp_schedule(void) {}; +extern "C" void esp_yield(void) {}; +#endif + +PingClass::PingClass() {} + +bool PingClass::ping(IPAddress dest, int16_t count) { + _expected_count = count; + _errors = 0; + _success = 0; + + _min_time = INT_MAX; + _avg_time = 0.0f; + _max_time = 0; + + + memset(&_options, 0, sizeof(struct ping_option)); + + // Repeat count (how many time send a ping message to destination) + _options.count = count; + // Time interval between two ping (seconds??) + _options.coarse_time = 1; + // Destination machine + _options.ip = dest; + + // Callbacks + _options.recv_function = reinterpret_cast(&PingClass::_ping_recv_cb); + _options.sent_function = NULL; //reinterpret_cast(&_ping_sent_cb); + + // Let's go! + if(ping_start(&_options)) { + #ifdef ESP8266 + //wait until finished + while((_success + _errors) < _expected_count){ + delay(1); + esp_yield(); + }; + #else + // Suspend till the process end + esp_yield(); + #endif + } + return (_success > 0); +} + +bool PingClass::ping(const char* host, int16_t count) { + IPAddress remote_addr; + + if (WiFi.hostByName(host, remote_addr)) + return ping(remote_addr, count); + + return false; +} + +float PingClass::averageTime() { + return _avg_time; +} + +float PingClass::minTime() { + return _min_time; +} + +float PingClass::maxTime() { + return _max_time; +} + +void PingClass::_ping_recv_cb(void *opt, void *resp) { + // Cast the parameters to get some usable info + ping_resp* ping_resp = reinterpret_cast(resp); + //ping_option* ping_opt = reinterpret_cast(opt); + + // Error or success? + #ifdef ESP8266 + if (ping_resp->ping_err == -1) + _errors++; + else { + _success++; + _avg_time += ping_resp->resp_time; + if(ping_resp->resp_time < _min_time) + _min_time = ping_resp->resp_time; + if(ping_resp->resp_time > _max_time) + _max_time = ping_resp->resp_time; + + } + #else + _errors = ping_resp->ping_err; + _success = _expected_count - _errors; + _avg_time = ping_resp->resp_time; + #endif + + // Some debug info + DEBUG_PING( + "DEBUG: ping reply" + "\ttotal_count = %d" + "\tresp_time = %d" + "\tseqno = %d" + "\ttimeout_count = %d" + "\tbytes = %d" + "\ttotal_bytes = %d" + "\ttotal_time = %d" + "\tping_err = %d \n", + (int)ping_resp->total_count, (int)ping_resp->resp_time, (int)ping_resp->seqno, + (int)ping_resp->timeout_count, (int)ping_resp->bytes, (int)ping_resp->total_bytes, + (int)ping_resp->total_time, (int)ping_resp->ping_err + ); + + // Is it time to end? + // Don't using seqno because it does not increase on error + #ifdef ESP8266 + if (_success + _errors == _expected_count ) { + _avg_time = _success > 0 ? _avg_time / _success : 0; + #else + if (ping_resp->total_count== _expected_count) { + + #endif + + DEBUG_PING("success %d errors %d\n", _success, _errors); + DEBUG_PING("Resp times min %d, avg %.2f, max %d ms\n", _min_time, _avg_time, _max_time); + + #ifdef ESP32 + // Done, return to main functiom + esp_schedule(); + #endif + } +} + +byte PingClass::_expected_count = 0; +byte PingClass::_errors = 0; +byte PingClass::_success = 0; +float PingClass::_avg_time = 0; +float PingClass::_min_time = std::numeric_limits::max(); +float PingClass::_max_time = 0; + +PingClass Ping; + diff --git a/lib/ESPping/src/ESPping.h b/lib/ESPping/src/ESPping.h new file mode 100644 index 000000000..22ff396e0 --- /dev/null +++ b/lib/ESPping/src/ESPping.h @@ -0,0 +1,74 @@ +/* + ESP32Ping - Ping library for ESP32 or ESP8266 + Copyright (c) 2018 Marian Craciunescu. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef ESPping_H +#define ESPping_H + +#include +#include +#ifdef ESP32 +#include +#include +extern "C" { + void esp_schedule(void); + void esp_yield(void); +} +#endif + +#ifdef ESP8266 +#include +extern "C" { + #include +} +#endif + +//#define ENABLE_DEBUG_PING 0 +#ifdef ENABLE_DEBUG_PING + #define DEBUG_PING(...) Serial.printf(__VA_ARGS__) +#else + #define DEBUG_PING(...) +#endif + +class PingClass { + public: + PingClass(); + + bool ping(IPAddress dest, int16_t count = 5); + bool ping(const char* host, int16_t count = 5); + + float averageTime(); + float minTime(); + float maxTime(); + + protected: + static void _ping_sent_cb(void *opt, void *pdata); + static void _ping_recv_cb(void *opt, void *pdata); + + IPAddress _dest; + ping_option _options; + + static byte _expected_count, _errors, _success; + static float _min_time, _max_time; + static float _avg_time; +}; + +extern PingClass Ping; + +#endif + diff --git a/lib/ESPping/src/ping32.cpp b/lib/ESPping/src/ping32.cpp new file mode 100644 index 000000000..2c7f05ab0 --- /dev/null +++ b/lib/ESPping/src/ping32.cpp @@ -0,0 +1,384 @@ +/* +* ESP32 Ping library +* +* All rights reserved. +* +* Permission to use, copy, modify, and distribute this software +* and its documentation for any purpose and without fee is hereby +* granted, provided that the above copyright notice appear in all +* copies and that both that the copyright notice and this +* permission notice and warranty disclaimer appear in supporting +* documentation, and that the name of the author not be used in +* advertising or publicity pertaining to distribution of the +* software without specific, written prior permission. +* +* The author disclaim all warranties with regard to this +* software, including all implied warranties of merchantability +* and fitness. In no event shall the author be liable for any +* special, indirect or consequential damages or any damages +* whatsoever resulting from loss of use, data or profits, whether +* in an action of contract, negligence or other tortious action, +* arising out of or in connection with the use or performance of +* this software. +* +* -------------------------------------------------------------------------------- +* Ping Library is based on the following source code: +* +* Lua RTOS, ping utility +* +* +* Author: Jaume Oliv� (jolive@iberoxarxa.com / jolive@whitecatboard.org) +* +* -------------------------------------------------------------------------------- +* +* Redistribution and use in source and binary forms, with or without modification, +* are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* 2. Redistributions in binary form must reproduce the above copyright notice, +* this list of conditions and the following disclaimer in the documentation +* and/or other materials provided with the distribution. +* 3. The name of the author may not be used to endorse or promote products +* derived from this software without specific prior written permission. +* +* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED +* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT +* SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT +* OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING +* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY +* OF SUCH DAMAGE. +* +* This file is part of the lwIP TCP/IP stack. +* +*/ +#ifdef ESP32 +#include + +#include +#include +#include +#include +#include +#include + +#include "ping32.h" + +#include "lwip/inet_chksum.h" +#include "lwip/ip.h" +#include "lwip/ip4.h" +#include "lwip/err.h" +#include "lwip/icmp.h" +#include "lwip/sockets.h" +#include "lwip/sys.h" +#include "lwip/netdb.h" +#include "lwip/dns.h" + +static uint16_t ping_seq_num; +static uint8_t stopped = 0; + +/* +* Statistics +*/ +static uint32_t transmitted = 0; +static uint32_t received = 0; +static float min_time = 0; +static float max_time = 0; +static float mean_time = 0; +static float last_mean_time = 0; +static float var_time = 0; + +#define PING_ID 0xAFAF + +#ifndef PING_DEFAULT_COUNT +#define PING_DEFAULT_COUNT 10 +#endif +#ifndef PING_DEFAULT_INTERVAL +#define PING_DEFAULT_INTERVAL 1 +#endif +#ifndef PING_DEFAULT_SIZE +#define PING_DEFAULT_SIZE 32 +#endif +#ifndef PING_DEFAULT_TIMEOUT +#define PING_DEFAULT_TIMEOUT 1 +#endif + +/* +* Helper functions +* +*/ +static void ping_prepare_echo(struct icmp_echo_hdr *iecho, uint16_t len) { + size_t i; + size_t data_len = len - sizeof(struct icmp_echo_hdr); + + ICMPH_TYPE_SET(iecho, ICMP_ECHO); + ICMPH_CODE_SET(iecho, 0); + iecho->chksum = 0; + iecho->id = PING_ID; + iecho->seqno = htons(++ping_seq_num); + + /* fill the additional data buffer with some data */ + for (i = 0; i < data_len; i++) { + ((char*)iecho)[sizeof(struct icmp_echo_hdr) + i] = (char)i; + } + + iecho->chksum = inet_chksum(iecho, len); +} + +static err_t ping_send(int s, ip4_addr_t *addr, int size) { + struct icmp_echo_hdr *iecho; + struct sockaddr_in to; + size_t ping_size = sizeof(struct icmp_echo_hdr) + size; + int err; + + iecho = (struct icmp_echo_hdr *)mem_malloc((mem_size_t)ping_size); + if (!iecho) { + mem_free(iecho); + return ERR_MEM; + } + + ping_prepare_echo(iecho, (uint16_t)ping_size); + + to.sin_len = sizeof(to); + to.sin_family = AF_INET; + inet_addr_from_ip4addr(&to.sin_addr, addr); + + if ((err = sendto(s, iecho, ping_size, 0, (struct sockaddr*)&to, sizeof(to)))) { + transmitted++; + } + mem_free(iecho); + return (err ? ERR_OK : ERR_VAL); +} + +static void ping_recv(int s) { + char buf[64]; + int fromlen, len; + struct sockaddr_in from; + struct ip_hdr *iphdr; + struct icmp_echo_hdr *iecho = NULL; + char ipa[16]; + struct timeval begin; + struct timeval end; + uint64_t micros_begin; + uint64_t micros_end; + float elapsed; + + // Register begin time + gettimeofday(&begin, NULL); + + // Send + while ((len = recvfrom(s, buf, sizeof(buf), 0, (struct sockaddr*)&from, (socklen_t*)&fromlen)) > 0) { + if (len >= (int)(sizeof(struct ip_hdr) + sizeof(struct icmp_echo_hdr))) { + // Register end time + gettimeofday(&end, NULL); + + /// Get from IP address + ip4_addr_t fromaddr; + inet_addr_to_ip4addr(&fromaddr, &from.sin_addr); + + strcpy(ipa, inet_ntoa(fromaddr)); + + // Get echo + iphdr = (struct ip_hdr *)buf; + iecho = (struct icmp_echo_hdr *)(buf + (IPH_HL(iphdr) * 4)); + + // Print .... + if ((iecho->id == PING_ID) && (iecho->seqno == htons(ping_seq_num))) { + received++; + + // Get elapsed time in milliseconds + micros_begin = begin.tv_sec * 1000000; + micros_begin += begin.tv_usec; + + micros_end = end.tv_sec * 1000000; + micros_end += end.tv_usec; + + elapsed = (float)(micros_end - micros_begin) / (float)1000.0; + + // Update statistics + // Mean and variance are computed in an incremental way + if (elapsed < min_time) { + min_time = elapsed; + } + + if (elapsed > max_time) { + max_time = elapsed; + } + + last_mean_time = mean_time; + mean_time = (((received - 1) * mean_time) + elapsed) / received; + + if (received > 1) { + var_time = var_time + ((elapsed - last_mean_time) * (elapsed - mean_time)); + } + + // Print ... + log_d("%d bytes from %s: icmp_seq=%d time=%.3f ms", len, ipa, + ntohs(iecho->seqno), elapsed + ); + + return; + } + else { + // TODO + } + } + } + + if (len < 0) { + log_d("Request timeout for icmp_seq %d", ping_seq_num); + } +} +/* +static void stop_action(int i) { + signal(i, SIG_DFL); + + stopped = 1; +} +*/ +/* +* Operation functions +* +*/ +void ping(const char *name, int16_t count, int interval, int size, int timeout) { + // Resolve name + hostent * target = gethostbyname(name); + IPAddress adr = *target->h_addr_list[0]; + if (target->h_length == 0) { + // TODO: error not found target????? + return; + } + ping_start(adr, count, interval, size, timeout); +} + +bool ping_start(struct ping_option *ping_o) { + + + return ping_start(ping_o->ip,ping_o->count,0,0,0,ping_o); + +} +bool ping_start(IPAddress adr, int16_t count=0, int interval=0, int size=0, int timeout=0, struct ping_option *ping_o) { +// driver_error_t *error; + struct sockaddr_in address; + ip4_addr_t ping_target; + int s; + // Get default values if argument are not provided + if (count == 0) { + count = PING_DEFAULT_COUNT; + } + + if (interval == 0) { + interval = PING_DEFAULT_INTERVAL; + } + + if (size == 0) { + size = PING_DEFAULT_SIZE; + } + + if (timeout == 0) { + timeout = PING_DEFAULT_TIMEOUT; + } + + // Create socket + if ((s = socket(AF_INET, SOCK_RAW, IP_PROTO_ICMP)) < 0) { + // TODO: error + return false; + } + + + address.sin_addr.s_addr = adr; + ping_target.addr = address.sin_addr.s_addr; + + // Setup socket + struct timeval tout; + + // Timeout + tout.tv_sec = timeout; + tout.tv_usec = 0; + + if (setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &tout, sizeof(tout)) < 0) { + closesocket(s); + // TODO: error + return false; + } + + stopped = 0; + transmitted = 0; + received = 0; + min_time = 1.E+9;// FLT_MAX; + max_time = 0.0; + mean_time = 0.0; + var_time = 0.0; + + // Register signal for stop ping + //signal(SIGINT, stop_action); + + // Begin ping ... + char ipa[16]; + + strcpy(ipa, inet_ntoa(ping_target)); + log_i("PING %s: %d data bytes", ipa, size); + + ping_seq_num = 0; + + unsigned long ping_started_time = millis(); + while ((ping_seq_num < count) && (!stopped)) { + if (ping_send(s, &ping_target, size) == ERR_OK) { + ping_recv(s); + } + if(ping_seq_num < count){ + delay( interval*1000L); + } + } + + closesocket(s); + + log_i("%d packets transmitted, %d packets received, %.1f%% packet loss", + transmitted, + received, + ((((float)transmitted - (float)received) / (float)transmitted) * 100.0) + ); + + + if (ping_o) { + ping_resp pingresp; + log_i("round-trip min/avg/max/stddev = %.3f/%.3f/%.3f/%.3f ms", min_time, mean_time, max_time, sqrt(var_time / received)); + pingresp.total_count = count; //Number of pings + pingresp.resp_time = mean_time; //Average time for the pings + pingresp.seqno = 0; //not relevant + pingresp.timeout_count = transmitted - received; //number of pings which failed + pingresp.bytes = size; //number of bytes received for 1 ping + pingresp.total_bytes = size * count; //number of bytes for all pings + pingresp.total_time = (millis() - ping_started_time) / 1000.0; //Time consumed for all pings; it takes into account also timeout pings + pingresp.ping_err = transmitted - received; //number of pings failed + // Call the callback function + ping_o->recv_function(ping_o, &pingresp); + } + + // Return true if at least one ping had a successfull "pong" + return (received > 0); +} + +bool ping_regist_recv(struct ping_option *ping_opt, ping_recv_function ping_recv) +{ + if (ping_opt == NULL) + return false; + + ping_opt->recv_function = ping_recv; + return true; +} + +bool ping_regist_sent(struct ping_option *ping_opt, ping_sent_function ping_sent) +{ + if (ping_opt == NULL) + return false; + + ping_opt->sent_function = ping_sent; + return true; +} + +#endif diff --git a/lib/ESPping/src/ping32.h b/lib/ESPping/src/ping32.h new file mode 100644 index 000000000..b0d5a3c34 --- /dev/null +++ b/lib/ESPping/src/ping32.h @@ -0,0 +1,37 @@ +#ifdef ESP32 + +#ifndef PING32_H +#define PING32_H + +#include +#include + +typedef void(*ping_recv_function)(void* arg, void *pdata); +typedef void(*ping_sent_function)(void* arg, void *pdata); + +struct ping_option { + int16_t count; + uint32_t ip; + uint32_t coarse_time; + ping_recv_function recv_function; + ping_sent_function sent_function; + void* reverse; +}; + +struct ping_resp { + uint32_t total_count; + float resp_time; + uint32_t seqno; + uint32_t timeout_count; + uint32_t bytes; + uint32_t total_bytes; + float total_time; + int8_t ping_err; +}; + +bool ping_start(struct ping_option *ping_opt); +void ping(const char *name, int16_t count, int interval, int size, int timeout); +bool ping_start(IPAddress adr, int16_t count, int interval, int size, int timeout, struct ping_option *ping_o = NULL); + +#endif +#endif // PING_H diff --git a/src/_P089_Ping.ino b/src/_P089_Ping.ino index ae7a4b577..580e890d1 100644 --- a/src/_P089_Ping.ino +++ b/src/_P089_Ping.ino @@ -1,5 +1,5 @@ #include "_Plugin_Helper.h" -#if defined(USES_P089) && defined(ESP8266) +#ifdef USES_P089 # include "src/PluginStructs/P089_data_struct.h" @@ -19,6 +19,8 @@ Maintainer: Denys Fedoryshchenko, denys AT nuclearcat.com */ /** Changelog: + * 2025-08-14 tonhuisman: Add code to plugin to use dvarrel/ESPping library that works for ESP32. + * ESP32 handles the ping in an independent RTOS task, to make it non-blocking. * 2025-01-12 tonhuisman: Add support for MQTT AutoDiscovery (not supported for Ping) * 2023-03-19 tonhuisman: Show hostname in GPIO column of Devices page * 2023-03-14 tonhuisman: Change command handling to not require the taskname as the second argument if no 3rd argument is given. @@ -30,6 +32,7 @@ # define PLUGIN_089 # define PLUGIN_NAME_089 "Communication - Ping" # define PLUGIN_VALUENAME1_089 "Fails" +# define PLUGIN_VALUENAME2_089 "Avg_ms" boolean Plugin_089(uint8_t function, struct EventStruct *event, String& string) @@ -60,14 +63,34 @@ boolean Plugin_089(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_GET_DEVICEVALUENAMES: { strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_089)); + # ifdef ESP32 + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_089)); + # endif // ifdef ESP32 break; } + # ifdef ESP32 + case PLUGIN_GET_DEVICEVALUECOUNT: + { + int vCount = P089_VALUE_COUNT; + + if (0 == vCount) { + vCount = 1; + } + event->Par1 = vCount; + success = true; + break; + } + # endif // ifdef ESP32 + # if FEATURE_MQTT_DISCOVER case PLUGIN_GET_DISCOVERY_VTYPES: { event->Par1 = static_cast(Sensor_VType::SENSOR_TYPE_NONE); // Not yet supported - success = true; + # ifdef ESP32 + event->Par2 = static_cast(Sensor_VType::SENSOR_TYPE_DURATION); + # endif // ifdef ESP32 + success = true; break; } # endif // if FEATURE_MQTT_DISCOVER @@ -92,12 +115,35 @@ boolean Plugin_089(uint8_t function, struct EventStruct *event, String& string) char hostname[PLUGIN_089_HOSTNAME_SIZE]{}; LoadCustomTaskSettings(event->TaskIndex, (uint8_t *)&hostname, PLUGIN_089_HOSTNAME_SIZE); addFormTextBox(F("Hostname"), F("host"), hostname, PLUGIN_089_HOSTNAME_SIZE - 2); + + # ifdef ESP32 + + if (P089_PING_COUNT < 1) { + P089_PING_COUNT = 5; + } + addFormNumericBox(F("Ping count"), F("pcount"), P089_PING_COUNT, 1, 100); + + const __FlashStringHelper*countOptions[] = { + F("Fails"), + F("Fails, Avg_ms"), + }; + const int countValues[] = { 1, 2 }; + FormSelectorOptions selector(2, countOptions, countValues); + selector.reloadonchange = true; + selector.addFormSelector(F("Available Values"), F("pvals"), P089_VALUE_COUNT); + # endif // ifdef ESP32 + success = true; break; } case PLUGIN_WEBFORM_SAVE: { + # ifdef ESP32 + P089_PING_COUNT = getFormItemInt(F("pcount"), 5); + P089_VALUE_COUNT = getFormItemInt(F("pvals"), 1); + # endif // ifdef ESP32 + char hostname[PLUGIN_089_HOSTNAME_SIZE]{}; // Reset "Fails" if settings updated @@ -112,6 +158,9 @@ boolean Plugin_089(uint8_t function, struct EventStruct *event, String& string) { success = initPluginTaskData(event->TaskIndex, new (std::nothrow) P089_data_struct()); UserVar.setFloat(event->TaskIndex, 0, 0); + # ifdef ESP32 + UserVar.setFloat(event->TaskIndex, 1, 0); + # endif // ifdef ESP32 break; } @@ -120,7 +169,7 @@ boolean Plugin_089(uint8_t function, struct EventStruct *event, String& string) P089_data_struct *P089_taskdata = static_cast(getPluginTaskData(event->TaskIndex)); - if (nullptr == P089_taskdata) { + if ((nullptr == P089_taskdata) || !P089_taskdata->isInitialized()) { break; } @@ -165,4 +214,4 @@ boolean Plugin_089(uint8_t function, struct EventStruct *event, String& string) return success; } -#endif // if defined(USES_P089) && defined(ESP8266) +#endif // ifdef USES_P089 diff --git a/src/src/CustomBuild/define_plugin_sets.h b/src/src/CustomBuild/define_plugin_sets.h index 9a6a2bc2f..5fc15eca6 100644 --- a/src/src/CustomBuild/define_plugin_sets.h +++ b/src/src/CustomBuild/define_plugin_sets.h @@ -1753,6 +1753,9 @@ To create/register a plugin, you have to : #ifndef USES_P085 #define USES_P085 // AcuDC24x #endif + #ifndef USES_P089 + #define USES_P089 // Ping + #endif #ifndef USES_P093 #define USES_P093 // Mitsubishi Heat Pump #endif @@ -1977,6 +1980,9 @@ To create/register a plugin, you have to : #ifndef USES_P083 #define USES_P083 // SGP30 #endif + #ifndef USES_P089 + #define USES_P089 // Ping + #endif #ifndef USES_P090 #define USES_P090 // CCS811 #endif @@ -2094,6 +2100,9 @@ To create/register a plugin, you have to : #ifndef USES_P070 #define USES_P070 // NeoPixel_Clock #endif + #ifndef USES_P089 + #define USES_P089 // Ping + #endif #ifndef USES_P128 #define USES_P128 // NeoPixelBusFX #endif @@ -2326,6 +2335,9 @@ To create/register a plugin, you have to : #ifndef USES_P088 #define USES_P088 // ToniA IR plugin #endif + #ifndef USES_P089 + #define USES_P089 // Ping + #endif #ifndef USES_P094 #define USES_P094 // CUL Reader #endif diff --git a/src/src/Helpers/_Plugin_init.cpp b/src/src/Helpers/_Plugin_init.cpp index 9365b8d79..874df65f9 100644 --- a/src/src/Helpers/_Plugin_init.cpp +++ b/src/src/Helpers/_Plugin_init.cpp @@ -371,11 +371,7 @@ constexpr /*pluginID_t*/ uint8_t DeviceIndex_to_Plugin_id[] PROGMEM = #endif // ifdef USES_P088 #ifdef USES_P089 - # ifdef ESP8266 - - // FIXME TD-er: Support Ping plugin for ESP32 89, - # endif // ifdef ESP8266 #endif // ifdef USES_P089 #ifdef USES_P090 @@ -1404,11 +1400,7 @@ constexpr const Plugin_ptr_t PROGMEM Plugin_ptr[] = #endif // ifdef USES_P088 #ifdef USES_P089 - # ifdef ESP8266 - - // FIXME TD-er: Support Ping plugin for ESP32 &Plugin_089, - # endif // ifdef ESP8266 #endif // ifdef USES_P089 #ifdef USES_P090 diff --git a/src/src/Helpers/_Plugin_init.h b/src/src/Helpers/_Plugin_init.h index 512c61038..79436fd04 100644 --- a/src/src/Helpers/_Plugin_init.h +++ b/src/src/Helpers/_Plugin_init.h @@ -390,10 +390,7 @@ void PluginInit(bool priorityOnly = false); #endif #ifdef USES_P089 - #ifdef ESP8266 - // FIXME TD-er: Support Ping plugin for ESP32 ADDPLUGIN_H(089) - #endif #endif #ifdef USES_P090 diff --git a/src/src/PluginStructs/P089_data_struct.cpp b/src/src/PluginStructs/P089_data_struct.cpp index 6da7f59ed..d36d67a90 100644 --- a/src/src/PluginStructs/P089_data_struct.cpp +++ b/src/src/PluginStructs/P089_data_struct.cpp @@ -1,13 +1,114 @@ #include "../PluginStructs/P089_data_struct.h" -#if defined(USES_P089) && defined(ESP8266) +#ifdef USES_P089 -#include "../Helpers/Networking.h" +# include "../Helpers/Networking.h" -#include "../Helpers/_Plugin_init.h" +# include "../Helpers/_Plugin_init.h" +# ifdef ESP32 +P089_data_struct::P089_data_struct() { + espPing = new (std::nothrow) PingClass(); + _ping_task_data.initialized = isInitialized(); +} +P089_data_struct::~P089_data_struct() { + if (_ping_task_data.taskHandle) { + vTaskDelete(_ping_task_data.taskHandle); + } + delete espPing; +} + +void P089_execute_ping_task(void *parameter) +{ + P089_ping_task_data*ping_task_data = static_cast(parameter); + + if ((ping_task_data->status == P089_status::Working) && (nullptr != ping_task_data->espPing)) { + // Blocking operation + ping_task_data->result = ping_task_data->espPing->ping(ping_task_data->pingIp, ping_task_data->count); + + // Results are in + ping_task_data->avgTime = ping_task_data->espPing->averageTime(); + ping_task_data->status = P089_status::Ready; + } + + ping_task_data->taskHandle = NULL; + vTaskDelete(ping_task_data->taskHandle); +} + +bool P089_data_struct::send_ping(struct EventStruct *event) { + /* This ping lost for sure */ + if (!isInitialized() || !NetworkConnected()) { + return true; + } + + if (!_ping_task_data.initialized) { + // addLog(LOG_LEVEL_ERROR, F("PING : Not initialized.")); + return true; // Not (yet?) initialized + } + + if (_ping_task_data.status == P089_status::Working) { + // addLog(LOG_LEVEL_INFO, F("PING : Still working.")); + return false; // Busy, but not a failure + } + + if (_ping_task_data.status == P089_status::Ready) { + if (_ping_task_data.result) { + addLog(LOG_LEVEL_INFO, strformat(F("PING : Successfully pinged %s (%s) count: %d avg: %.03f ms"), + _ping_task_data.hostname.c_str(), + _ping_task_data.pingIp.toString().c_str(), + _ping_task_data.count, + _ping_task_data.avgTime)); + } else { + addLog(LOG_LEVEL_ERROR, strformat(F("PING : Error pinging %s (%s)"), + _ping_task_data.hostname.c_str(), + _ping_task_data.pingIp.toString().c_str())); + } + UserVar.setFloat(event->TaskIndex, 1, _ping_task_data.avgTime); // Set always, even when not specifically enabled + + _ping_task_data.status = P089_status::Initial; // Ready for new work + return !_ping_task_data.result; // Inverted result to report back! + } + + IPAddress ip; + char hostname[PLUGIN_089_HOSTNAME_SIZE]{}; + + LoadCustomTaskSettings(event->TaskIndex, (uint8_t *)&hostname, PLUGIN_089_HOSTNAME_SIZE); + + /* This one lost as well, DNS dead? */ + if (!resolveHostByName(hostname, ip)) { + return true; + } + + int16_t pingCount = P089_PING_COUNT; + + if ((pingCount < 1) || (pingCount > 100)) { + pingCount = 5; + } + + _ping_task_data.status = P089_status::Working; + _ping_task_data.count = pingCount; + _ping_task_data.pingIp = ip; + _ping_task_data.hostname = hostname; // Bring all data to the task + _ping_task_data.espPing = espPing; + + xTaskCreatePinnedToCore( + P089_execute_ping_task, // Function that should be called + "PingClass.ping()", // Name of the task (for debugging) + 4000, // Stack size (bytes) + &_ping_task_data, // Parameter to pass + 1, // Task priority + &_ping_task_data.taskHandle, // Task handle + xPortGetCoreID() // Core you want to run the task on (0 or 1) + ); + + return false; // Started work, not a failure +} + +# endif // ifdef ESP32 + +# ifdef ESP8266 P089_data_struct::P089_data_struct() { destIPAddress.addr = 0; idseq = 0; @@ -159,4 +260,5 @@ uint8_t PingReceiver(void *origin, struct raw_pcb *pcb, struct pbuf *packetBuffe return 1; } -#endif // if defined(USES_P089) && defined(ESP8266) +# endif // ifdef ESP8266 +#endif // ifdef USES_P089 diff --git a/src/src/PluginStructs/P089_data_struct.h b/src/src/PluginStructs/P089_data_struct.h index 8e8cefa23..5947a1d1f 100644 --- a/src/src/PluginStructs/P089_data_struct.h +++ b/src/src/PluginStructs/P089_data_struct.h @@ -2,7 +2,7 @@ #define PLUGINSTRUCTS_P089_DATA_STRUCT_H #include "../../_Plugin_Helper.h" -#if defined(USES_P089) && defined(ESP8266) +#ifdef USES_P089 # define PLUGIN_ID_089 89 @@ -12,16 +12,64 @@ # define ICMP_PAYLOAD_LEN 32 +# ifdef ESP32 +# define P089_PING_COUNT PCONFIG(0) +# define P089_VALUE_COUNT PCONFIG(1) + +# include + +enum class P089_status : uint8_t { + Initial = 0, + Working = 1, + Ready = 2, +}; + +struct P089_ping_task_data { + bool initialized{}; + P089_status status = P089_status::Initial; + IPAddress pingIp; + String hostname; + bool result{}; + uint16_t count{}; + float avgTime{}; + PingClass *espPing = nullptr; + + // This is C-code, so not set to nullptr, but to NULL + TaskHandle_t taskHandle = NULL; +}; + +struct P089_data_struct : public PluginTaskData_base { +public: + + P089_data_struct(); + + virtual ~P089_data_struct(); + + bool isInitialized() const { + return nullptr != espPing; + } + + bool send_ping(struct EventStruct *event); + +private: + + PingClass *espPing = nullptr; + P089_ping_task_data _ping_task_data; +}; +# endif // ifdef ESP32 + +# ifdef ESP8266 + extern "C" { -# include -# include // needed for icmp packet definitions -# include // needed for inet_chksum() -# include // needed for sys_now() -# include +# include +# include // needed for icmp packet definitions +# include // needed for inet_chksum() +# include // needed for sys_now() +# include } -# include "src/ESPEasyCore/ESPEasyNetwork.h" +# include "src/ESPEasyCore/ESPEasyNetwork.h" struct P089_icmp_pcb { @@ -39,6 +87,10 @@ public: bool send_ping(struct EventStruct *event); + bool isInitialized() const { + return nullptr != P089_data; + } + struct P089_icmp_pcb *P089_data = nullptr; ip_addr_t destIPAddress; uint32_t idseq; @@ -50,6 +102,6 @@ uint8_t PingReceiver(void *origin, struct pbuf *packetBuffer, const ip_addr_t *addr); - -#endif // if defined(USES_P089) && defined(ESP8266) +# endif // ifdef ESP8266 +#endif // ifdef USES_P089 #endif // ifndef PLUGINSTRUCTS_P089_DATA_STRUCT_H