From b7ebe03ca8128e9ddf99b60775dfb804ee19aa66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Wed, 26 Oct 2022 11:09:59 +0200 Subject: [PATCH] API Server and DHCP Lease Management --- arch/esp32/esp32.ini | 3 +- arch/esp32/esp32s3.ini | 1 - platformio.ini | 1 + src/main.cpp | 1 + src/mesh/eth/ethClient.cpp | 92 ++++++++++++++++++++++++++++++++++- src/mesh/eth/ethServerAPI.cpp | 82 +++++++++++++++++++++++++++++++ src/mesh/eth/ethServerAPI.h | 58 ++++++++++++++++++++++ src/mqtt/MQTT.cpp | 6 +++ src/mqtt/MQTT.h | 2 + 9 files changed, 241 insertions(+), 5 deletions(-) create mode 100644 src/mesh/eth/ethServerAPI.cpp create mode 100644 src/mesh/eth/ethServerAPI.h diff --git a/arch/esp32/esp32.ini b/arch/esp32/esp32.ini index 1b57ffb8b..ba98a6b1f 100644 --- a/arch/esp32/esp32.ini +++ b/arch/esp32/esp32.ini @@ -33,8 +33,7 @@ lib_deps = ${environmental_base.lib_deps} https://github.com/meshtastic/esp32_https_server.git#657509856ce97e9dddeffb89a559f544faefd5cd h2zero/NimBLE-Arduino@^1.4.0 - arduino-libraries/NTPClient@^3.1.0 - https://github.com/lewisxhe/XPowersLib.git#84b7373faea3118b6c37954d52f98b8a337148d6 + https://github.com/lewisxhe/XPowersLib.git#84b7373faea3118b6c37954d52f98b8a337148d6 lib_ignore = segger_rtt diff --git a/arch/esp32/esp32s3.ini b/arch/esp32/esp32s3.ini index 286d9950e..f5338d9a9 100644 --- a/arch/esp32/esp32s3.ini +++ b/arch/esp32/esp32s3.ini @@ -33,7 +33,6 @@ lib_deps = ${environmental_base.lib_deps} https://github.com/meshtastic/esp32_https_server.git#657509856ce97e9dddeffb89a559f544faefd5cd h2zero/NimBLE-Arduino@^1.4.0 - arduino-libraries/NTPClient@^3.1.0 https://github.com/lewisxhe/XPowersLib.git#84b7373faea3118b6c37954d52f98b8a337148d6 lib_ignore = diff --git a/platformio.ini b/platformio.ini index 1f97cfb19..527bce0d0 100644 --- a/platformio.ini +++ b/platformio.ini @@ -79,6 +79,7 @@ build_src_filter = ${env.build_src_filter} - [networking_base] lib_deps = knolleary/PubSubClient@^2.8 + arduino-libraries/NTPClient@^3.1.0 meshtastic/json11@^1.0.2 ; Common libs for environmental measurements in telemetry module diff --git a/src/main.cpp b/src/main.cpp index a783f023b..d2505d816 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -43,6 +43,7 @@ #endif #if HAS_ETHERNET +#include "mesh/eth/ethServerAPI.h" #include "mqtt/MQTT.h" #endif diff --git a/src/mesh/eth/ethClient.cpp b/src/mesh/eth/ethClient.cpp index 86e4e56c9..1bd8ea96e 100644 --- a/src/mesh/eth/ethClient.cpp +++ b/src/mesh/eth/ethClient.cpp @@ -1,15 +1,98 @@ #include "mesh/eth/ethClient.h" #include "NodeDB.h" +#include "RTC.h" +#include "concurrency/Periodic.h" #include #include #include "target_specific.h" +#include "mesh/eth/ethServerAPI.h" +#include "mqtt/MQTT.h" + +#ifndef DISABLE_NTP +#include + +// NTP +EthernetUDP ntpUDP; + +NTPClient timeClient(ntpUDP, config.network.ntp_server); + +uint32_t ntp_renew = 0; +#endif + +// Stores our hostname +char ourHost[16]; + +bool ethStartupComplete = 0; + +using namespace concurrency; + +static Periodic *ethEvent; + +static int32_t reconnectETH() +{ + if (config.network.eth_enabled) { + Ethernet.maintain(); + if (!ethStartupComplete) { + // Start web server + DEBUG_MSG("... Starting network services\n"); + + // // start mdns + // if (!MDNS.begin("Meshtastic")) { + // DEBUG_MSG("Error setting up MDNS responder!\n"); + // } else { + // DEBUG_MSG("mDNS responder started\n"); + // DEBUG_MSG("mDNS Host: Meshtastic.local\n"); + // MDNS.addService("http", "tcp", 80); + // MDNS.addService("https", "tcp", 443); + // } + + #ifndef DISABLE_NTP + DEBUG_MSG("Starting NTP time client\n"); + timeClient.begin(); + timeClient.setUpdateInterval(60 * 60); // Update once an hour + #endif + + // initWebServer(); + initApiServer(); + + ethStartupComplete = true; + } + + // FIXME this is kinda yucky, instead we should just have an observable for 'wifireconnected' + if (mqtt && !mqtt->connected()) { + mqtt->reconnect(); + } + } + +#ifndef DISABLE_NTP + if (isEthernetAvailable() && (ntp_renew < millis())) { + DEBUG_MSG("Updating NTP time\n"); + if (timeClient.update()) { + DEBUG_MSG("NTP Request Success - Setting RTCQualityNTP if needed\n"); + + struct timeval tv; + tv.tv_sec = timeClient.getEpochTime(); + tv.tv_usec = 0; + + perhapsSetRTC(RTCQualityNTP, &tv); + + ntp_renew = millis() + 43200 * 1000; // every 12 hours + + } else { + DEBUG_MSG("NTP Update failed\n"); + } + } +#endif + + return 5000; // every 5 seconds +} // Startup Ethernet bool initEthernet() { - config.network.eth_enabled = true; - config.network.eth_mode = Config_NetworkConfig_EthMode_DHCP; + // config.network.eth_enabled = true; + // config.network.eth_mode = Config_NetworkConfig_EthMode_DHCP; if (config.network.eth_enabled) { @@ -26,6 +109,8 @@ bool initEthernet() int status = 0; + // createSSLCert(); + getMacAddr(mac); // FIXME use the BLE MAC for now... if (config.network.eth_mode == Config_NetworkConfig_EthMode_DHCP) { @@ -56,6 +141,9 @@ bool initEthernet() DEBUG_MSG("Gateway IP %u.%u.%u.%u\n",Ethernet.gatewayIP()[0], Ethernet.gatewayIP()[1], Ethernet.gatewayIP()[2], Ethernet.gatewayIP()[3]); DEBUG_MSG("DNS Server IP %u.%u.%u.%u\n",Ethernet.dnsServerIP()[0], Ethernet.dnsServerIP()[1], Ethernet.dnsServerIP()[2], Ethernet.dnsServerIP()[3]); } + + ethEvent = new Periodic("ethConnect", reconnectETH); + return true; } else { diff --git a/src/mesh/eth/ethServerAPI.cpp b/src/mesh/eth/ethServerAPI.cpp new file mode 100644 index 000000000..bb7dd927d --- /dev/null +++ b/src/mesh/eth/ethServerAPI.cpp @@ -0,0 +1,82 @@ +#include "ethServerAPI.h" +#include "configuration.h" +#include + +static ethServerPort *apiPort; + +void initApiServer(int port) +{ + // Start API server on port 4403 + if (!apiPort) { + apiPort = new ethServerPort(port); + DEBUG_MSG("API server listening on TCP port %d\n", port); + apiPort->init(); + } +} + +ethServerAPI::ethServerAPI(EthernetClient &_client) : StreamAPI(&client), client(_client) +{ + DEBUG_MSG("Incoming ethernet connection\n"); +} + +ethServerAPI::~ethServerAPI() +{ + client.stop(); + + // FIXME - delete this if the client dropps the connection! +} + +/// override close to also shutdown the TCP link +void ethServerAPI::close() +{ + client.stop(); // drop tcp connection + StreamAPI::close(); +} + +/// Check the current underlying physical link to see if the client is currently connected +bool ethServerAPI::checkIsConnected() +{ + return client.connected(); +} + +int32_t ethServerAPI::runOnce() +{ + if (client.connected()) { + return StreamAPI::runOnce(); + } else { + DEBUG_MSG("Client dropped connection, suspending API service\n"); + enabled = false; // we no longer need to run + return 0; + } +} + +/// If an api server is running, we try to spit out debug 'serial' characters there +void ethServerPort::debugOut(char c) +{ + if (apiPort && apiPort->openAPI) + apiPort->openAPI->debugOut(c); +} + + +ethServerPort::ethServerPort(int port) : EthernetServer(port), concurrency::OSThread("ApiServer") {} + +void ethServerPort::init() +{ + begin(); +} + +int32_t ethServerPort::runOnce() +{ + auto client = available(); + if (client) { + // Close any previous connection (see FIXME in header file) + if (openAPI) { + DEBUG_MSG("Force closing previous TCP connection\n"); + delete openAPI; + } + + openAPI = new ethServerAPI(client); + } + + return 100; // only check occasionally for incoming connections +} diff --git a/src/mesh/eth/ethServerAPI.h b/src/mesh/eth/ethServerAPI.h new file mode 100644 index 000000000..c92ab2f17 --- /dev/null +++ b/src/mesh/eth/ethServerAPI.h @@ -0,0 +1,58 @@ +#pragma once + +#include "StreamAPI.h" +#include + +/** + * Provides both debug printing and, if the client starts sending protobufs to us, switches to send/receive protobufs + * (and starts dropping debug printing - FIXME, eventually those prints should be encapsulated in protobufs). + */ +class ethServerAPI : public StreamAPI +{ + private: + EthernetClient client; + + public: + explicit ethServerAPI(EthernetClient &_client); + + virtual ~ethServerAPI(); + + /// override close to also shutdown the TCP link + virtual void close(); + + protected: + /// We override this method to prevent publishing EVENT_SERIAL_CONNECTED/DISCONNECTED for wifi links (we want the board to + /// stay in the POWERED state to prevent disabling wifi) + virtual void onConnectionChanged(bool connected) override {} + + virtual int32_t runOnce() override; // Check for dropped client connections + + /// Check the current underlying physical link to see if the client is currently connected + virtual bool checkIsConnected() override; +}; + +/** + * Listens for incoming connections and does accepts and creates instances of WiFiServerAPI as needed + */ +class ethServerPort : public EthernetServer, private concurrency::OSThread +{ + /** The currently open port + * + * FIXME: We currently only allow one open TCP connection at a time, because we depend on the loop() call in this class to + * delegate to the worker. Once coroutines are implemented we can relax this restriction. + */ + ethServerAPI *openAPI = NULL; + + public: + explicit ethServerPort(int port); + + void init(); + + /// If an api server is running, we try to spit out debug 'serial' characters there + static void debugOut(char c); + + protected: + int32_t runOnce() override; +}; + +void initApiServer(int port=4403); diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index 9d6c68225..e77e82e54 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -107,6 +107,11 @@ MQTT::MQTT() : concurrency::OSThread("mqtt"), pubSub(mqttClient) // preflightSleepObserver.observe(&preflightSleep); } +bool MQTT::connected() +{ + return pubSub.connected(); +} + void MQTT::reconnect() { if (wantsLink()) { @@ -197,6 +202,7 @@ bool MQTT::wantsLink() const #if HAS_ETHERNET return hasChannel && (Ethernet.linkStatus() == LinkON); #endif + return false; } int32_t MQTT::runOnce() diff --git a/src/mqtt/MQTT.h b/src/mqtt/MQTT.h index 4412e9526..c8381574c 100644 --- a/src/mqtt/MQTT.h +++ b/src/mqtt/MQTT.h @@ -48,6 +48,8 @@ class MQTT : private concurrency::OSThread /** Attempt to connect to server if necessary */ void reconnect(); + + bool connected(); protected: virtual int32_t runOnce() override;