diff --git a/platformio.ini b/platformio.ini index f67cfac..7a365ac 100644 --- a/platformio.ini +++ b/platformio.ini @@ -134,11 +134,44 @@ build_flags = ; Enable MQTT gateway feature -D WITH_MQTT=1 ; WiFi credentials (configure these or use web UI) - -D MQTT_WIFI_SSID='"YourNetworkSSID"' - -D MQTT_WIFI_PASS='"YourNetworkPassword"' + -D MQTT_WIFI_SSID='"tsunami"' + -D MQTT_WIFI_PASS='"2089916341"' ; MQTT broker settings (defaults, can be changed via web UI) - -D MQTT_BROKER='"meshqt.l.supported.systems"' - -D MQTT_PORT=443 + -D MQTT_BROKER='"192.168.1.38"' + -D MQTT_PORT=11883 + -D MQTT_USE_TLS=0 + -D MQTT_USER='""' + -D MQTT_PASS='""' + -D MQTT_TOPIC_PREFIX='"meshcore/repeater"' + +; ============================================================================= +; Ethernet Gateway Environment (ENC28J60) +; Uses wired Ethernet instead of WiFi for more reliable MQTT bridging +; Requires ENC28J60 module connected to GPIO19/20/47/48 +; ============================================================================= +[env:heltec_v3_ethernet] +extends = env:heltec_v3_repeater + +; Additional libraries for Ethernet + MQTT functionality +lib_deps = + ${env:heltec_v3_repeater.lib_deps} + knolleary/PubSubClient @ ^2.8 + bblanchon/ArduinoJson @ ^7.0 + jandrassy/EthernetENC @ ^2.0.4 + +build_flags = + ${env:heltec_v3_repeater.build_flags} + ; Enable Ethernet gateway feature (instead of WiFi) + -D WITH_ETHERNET=1 + ; ENC28J60 SPI pins (safe GPIOs that don't conflict with LoRa) + -D ETH_SPI_SCK=19 + -D ETH_SPI_MOSI=20 + -D ETH_SPI_MISO=47 + -D ETH_SPI_CS=48 + -D ETH_INT_PIN=-1 + ; MQTT broker settings (configure these for your network) + -D MQTT_BROKER='"192.168.1.38"' + -D MQTT_PORT=11883 -D MQTT_USER='""' -D MQTT_PASS='""' -D MQTT_TOPIC_PREFIX='"meshcore/repeater"' diff --git a/src/EthernetManager.cpp b/src/EthernetManager.cpp new file mode 100644 index 0000000..901de00 --- /dev/null +++ b/src/EthernetManager.cpp @@ -0,0 +1,199 @@ +#ifdef WITH_ETHERNET + +#include "EthernetManager.h" +#include +#include + +// Use HSPI for Ethernet (separate from LoRa which uses default SPI) +SPIClass ethSPI(HSPI); + +EthernetManager::EthernetManager() + : _state(NetworkState::DISCONNECTED), + _connect_start_time(0), + _last_connect_attempt(0), + _connected_since(0), + _initialized(false) { + memset(&_config, 0, sizeof(_config)); + memset(_mac, 0, sizeof(_mac)); +} + +void EthernetManager::generateMacFromChipId() { + // Generate a locally-administered MAC address from ESP32 chip ID + uint64_t chipid = ESP.getEfuseMac(); + _mac[0] = 0x02; // Locally administered, unicast + _mac[1] = (chipid >> 0) & 0xFF; + _mac[2] = (chipid >> 8) & 0xFF; + _mac[3] = (chipid >> 16) & 0xFF; + _mac[4] = (chipid >> 24) & 0xFF; + _mac[5] = (chipid >> 32) & 0xFF; +} + +void EthernetManager::begin(const EthernetConfig& config) { + _config = config; + _initialized = true; + _state = NetworkState::DISCONNECTED; + + // Use provided MAC or generate from chip ID + bool has_mac = false; + for (int i = 0; i < 6; i++) { + if (config.mac[i] != 0) { + has_mac = true; + break; + } + } + if (has_mac) { + memcpy(_mac, config.mac, 6); + } else { + generateMacFromChipId(); + } + + Serial.println("[ETH] Initializing ENC28J60..."); + Serial.printf("[ETH] Pins: SCK=%d MOSI=%d MISO=%d CS=%d\n", + _config.spi_sck, _config.spi_mosi, _config.spi_miso, + _config.spi_cs); + Serial.printf("[ETH] MAC: %02X:%02X:%02X:%02X:%02X:%02X\n", + _mac[0], _mac[1], _mac[2], _mac[3], _mac[4], _mac[5]); + + // Initialize SPI bus for ENC28J60 (HSPI, separate from LoRa) + ethSPI.begin(_config.spi_sck, _config.spi_miso, _config.spi_mosi, _config.spi_cs); + + // Initialize EthernetENC with our SPI instance + Ethernet.init(_config.spi_cs); + + attemptConnection(); +} + +void EthernetManager::attemptConnection() { + if (!_initialized) return; + + _state = NetworkState::CONNECTING; + _connect_start_time = millis(); + _last_connect_attempt = millis(); + + Serial.println("[ETH] Starting DHCP..."); + + if (_config.use_dhcp) { + // DHCP - blocking call with timeout + int result = Ethernet.begin(_mac, _config.connect_timeout_ms); + if (result == 0) { + Serial.println("[ETH] DHCP failed!"); + + // Check what went wrong + if (Ethernet.hardwareStatus() == EthernetNoHardware) { + Serial.println("[ETH] ENC28J60 not found! Check wiring."); + _state = NetworkState::ERROR; + } else if (Ethernet.linkStatus() == LinkOFF) { + Serial.println("[ETH] Cable not connected."); + _state = NetworkState::DISCONNECTED; + } else { + Serial.println("[ETH] DHCP timeout."); + _state = NetworkState::DISCONNECTED; + } + return; + } + } else { + // Static IP + IPAddress ip(_config.static_ip); + IPAddress gw(_config.gateway); + IPAddress sn(_config.subnet); + IPAddress dns1(_config.dns1); + + Ethernet.begin(_mac, ip, dns1, gw, sn); + + // Check hardware + if (Ethernet.hardwareStatus() == EthernetNoHardware) { + Serial.println("[ETH] ENC28J60 not found! Check wiring."); + _state = NetworkState::ERROR; + return; + } + } + + // Success! + _state = NetworkState::CONNECTED; + _connected_since = millis(); + + Serial.printf("[ETH] Connected! IP: %s\n", Ethernet.localIP().toString().c_str()); +} + +void EthernetManager::loop() { + if (!_initialized) return; + + switch (_state) { + case NetworkState::CONNECTING: + // Connection is handled synchronously in attemptConnection() + // This state shouldn't persist + if (millis() - _connect_start_time > _config.connect_timeout_ms) { + _state = NetworkState::DISCONNECTED; + _last_connect_attempt = millis(); + } + break; + + case NetworkState::CONNECTED: + // Maintain DHCP lease + Ethernet.maintain(); + + // Check if link is still up + if (Ethernet.linkStatus() == LinkOFF) { + Serial.println("[ETH] Link lost"); + _state = NetworkState::DISCONNECTED; + _connected_since = 0; + } + break; + + case NetworkState::DISCONNECTED: + // Try to reconnect after interval + if (millis() - _last_connect_attempt > _config.reconnect_interval_ms) { + attemptConnection(); + } + break; + + case NetworkState::ERROR: + // Try to recover after a longer delay + if (millis() - _last_connect_attempt > 30000) { + _state = NetworkState::DISCONNECTED; + } + break; + + default: + break; + } +} + +void EthernetManager::end() { + if (!_initialized) return; + + _state = NetworkState::DISCONNECTED; + _initialized = false; + _connected_since = 0; + Serial.println("[ETH] Stopped"); +} + +void EthernetManager::reconnect() { + Serial.println("[ETH] Reconnect requested"); + _state = NetworkState::DISCONNECTED; + _last_connect_attempt = 0; // Trigger immediate reconnect +} + +IPAddress EthernetManager::getLocalIP() const { + return Ethernet.localIP(); +} + +String EthernetManager::getMacAddress() const { + char buf[18]; + snprintf(buf, sizeof(buf), "%02X:%02X:%02X:%02X:%02X:%02X", + _mac[0], _mac[1], _mac[2], _mac[3], _mac[4], _mac[5]); + return String(buf); +} + +uint32_t EthernetManager::getConnectionUptime() const { + if (_state == NetworkState::CONNECTED && _connected_since > 0) { + return millis() - _connected_since; + } + return 0; +} + +bool EthernetManager::isLinkUp() const { + return Ethernet.linkStatus() == LinkON; +} + +#endif // WITH_ETHERNET diff --git a/src/EthernetManager.h b/src/EthernetManager.h new file mode 100644 index 0000000..1c1b8f8 --- /dev/null +++ b/src/EthernetManager.h @@ -0,0 +1,78 @@ +#pragma once + +#ifdef WITH_ETHERNET + +#include +#include "INetworkManager.h" + +// ENC28J60 SPI pin configuration for Heltec V3 +// Using available GPIOs that don't conflict with LoRa SPI +#ifndef ETH_SPI_SCK +#define ETH_SPI_SCK 19 // GPIO19 - safe for external use +#endif +#ifndef ETH_SPI_MOSI +#define ETH_SPI_MOSI 20 // GPIO20 - safe for external use +#endif +#ifndef ETH_SPI_MISO +#define ETH_SPI_MISO 47 // GPIO47 - safe for external use +#endif +#ifndef ETH_SPI_CS +#define ETH_SPI_CS 48 // GPIO48 - safe for external use +#endif + +// Configuration for Ethernet manager +struct EthernetConfig { + uint8_t spi_sck; + uint8_t spi_mosi; + uint8_t spi_miso; + uint8_t spi_cs; + uint32_t connect_timeout_ms; + uint32_t reconnect_interval_ms; + bool use_dhcp; // true = DHCP, false = static IP + // Static IP config (only used if use_dhcp = false) + uint32_t static_ip; + uint32_t gateway; + uint32_t subnet; + uint32_t dns1; + uint32_t dns2; + // MAC address (if all zeros, generate from ESP32 chip ID) + uint8_t mac[6]; +}; + +class EthernetManager : public INetworkManager { +public: + EthernetManager(); + + // Initialize Ethernet with configuration + void begin(const EthernetConfig& config); + + // INetworkManager interface + void loop() override; + void end() override; + NetworkState getState() const override { return _state; } + bool isConnected() const override { return _state == NetworkState::CONNECTED; } + IPAddress getLocalIP() const override; + String getMacAddress() const override; + int32_t getRSSI() const override { return 0; } // Wired = no RSSI + const char* getConnectionName() const override { return "ENC28J60"; } + void reconnect() override; + uint32_t getConnectionUptime() const override; + const char* getNetworkType() const override { return "Ethernet"; } + + // Ethernet-specific + bool isLinkUp() const; + +private: + EthernetConfig _config; + NetworkState _state; + unsigned long _connect_start_time; + unsigned long _last_connect_attempt; + unsigned long _connected_since; + bool _initialized; + uint8_t _mac[6]; + + void attemptConnection(); + void generateMacFromChipId(); +}; + +#endif // WITH_ETHERNET diff --git a/src/INetworkManager.h b/src/INetworkManager.h new file mode 100644 index 0000000..7fc6454 --- /dev/null +++ b/src/INetworkManager.h @@ -0,0 +1,51 @@ +#pragma once + +#if defined(WITH_MQTT) || defined(WITH_ETHERNET) + +#include +#include + +// Network connection states (generic) +enum class NetworkState { + DISCONNECTED, + CONNECTING, + CONNECTED, + AP_MODE, // WiFi only + ERROR +}; + +// Abstract network interface for MQTT bridge +// Implementations: WiFiManager, EthernetManager +class INetworkManager { +public: + virtual ~INetworkManager() = default; + + // Lifecycle + virtual void loop() = 0; + virtual void end() = 0; + + // Connection state + virtual NetworkState getState() const = 0; + virtual bool isConnected() const = 0; + + // Network info + virtual IPAddress getLocalIP() const = 0; + virtual String getMacAddress() const = 0; + + // Signal quality (returns 0 for wired connections) + virtual int32_t getRSSI() const { return 0; } + + // Connection identifier (SSID for WiFi, "Ethernet" for wired) + virtual const char* getConnectionName() const = 0; + + // Force reconnection + virtual void reconnect() = 0; + + // Uptime since connected (ms) + virtual uint32_t getConnectionUptime() const = 0; + + // Type identifier for UI/logging + virtual const char* getNetworkType() const = 0; +}; + +#endif // WITH_MQTT || WITH_ETHERNET diff --git a/src/MQTTBridge.cpp b/src/MQTTBridge.cpp index bd4b3e2..27c532d 100644 --- a/src/MQTTBridge.cpp +++ b/src/MQTTBridge.cpp @@ -1,4 +1,4 @@ -#ifdef WITH_MQTT +#if defined(WITH_MQTT) || defined(WITH_ETHERNET) #include "MQTTBridge.h" #include @@ -7,9 +7,9 @@ // Static instance for callback MQTTBridge* MQTTBridge::_instance = nullptr; -MQTTBridge::MQTTBridge(WiFiManager& wifi, mesh::PacketManager* mgr, mesh::RTCClock* rtc) - : _wifi(wifi), - _mqtt_client(_wifi_client), +MQTTBridge::MQTTBridge(INetworkManager* network, mesh::PacketManager* mgr, mesh::RTCClock* rtc) + : _network(network), + _mqtt_client(), _state(MQTTState::DISCONNECTED), _mgr(mgr), _rtc(rtc), @@ -41,8 +41,15 @@ void MQTTBridge::begin(const MQTTConfig& config, const uint8_t* self_pubkey) { setupTopics(); - // Configure TLS - skip cert verification (traffic still encrypted) - _wifi_client.setInsecure(); + // Configure client based on TLS setting + if (_config.use_tls) { + _wifi_client_secure.setInsecure(); // Skip cert verification + _mqtt_client.setClient(_wifi_client_secure); + Serial.printf("[MQTT] TLS enabled\n"); + } else { + _mqtt_client.setClient(_wifi_client); + Serial.printf("[MQTT] Plain TCP\n"); + } _mqtt_client.setServer(_config.broker, _config.port); _mqtt_client.setCallback(mqttCallback); @@ -52,10 +59,10 @@ void MQTTBridge::begin(const MQTTConfig& config, const uint8_t* self_pubkey) { _initialized = true; _state = MQTTState::DISCONNECTED; - Serial.printf("[MQTTS] Initialized: %s:%d (TLS), prefix=%s, gateway=%s\n", + Serial.printf("[MQTT] Initialized: %s:%d, prefix=%s, gateway=%s\n", _config.broker, _config.port, _config.topic_prefix, _gateway_id); - if (_config.enabled && _wifi.isConnected()) { + if (_config.enabled && _network->isConnected()) { attemptConnection(); } } @@ -63,7 +70,7 @@ void MQTTBridge::begin(const MQTTConfig& config, const uint8_t* self_pubkey) { void MQTTBridge::loop() { if (!_initialized || !_config.enabled) return; - if (!_wifi.isConnected()) { + if (!_network->isConnected()) { if (_state == MQTTState::CONNECTED) { _state = MQTTState::DISCONNECTED; Serial.println("[MQTTS] WiFi lost, disconnected"); @@ -122,7 +129,7 @@ void MQTTBridge::end() { } void MQTTBridge::attemptConnection() { - if (!_wifi.isConnected()) return; + if (!_network->isConnected()) return; _state = MQTTState::CONNECTING; _last_connect_attempt = millis(); @@ -206,8 +213,8 @@ void MQTTBridge::publishStatus() { JsonDocument doc; doc["status"] = "online"; doc["gateway_id"] = _gateway_id; - doc["ip"] = _wifi.getLocalIP().toString(); - doc["rssi"] = _wifi.getRSSI(); + doc["ip"] = _network->getLocalIP().toString(); + doc["rssi"] = _network->getRSSI(); doc["uptime_secs"] = (_connected_since > 0) ? (millis() - _connected_since) / 1000 : 0; doc["free_heap"] = ESP.getFreeHeap(); doc["timestamp"] = getTimestamp(); @@ -399,4 +406,4 @@ const char* MQTTBridge::getTimestamp() { return buf; } -#endif // WITH_MQTT +#endif // WITH_MQTT || WITH_ETHERNET diff --git a/src/MQTTBridge.h b/src/MQTTBridge.h index 874a5b6..f495447 100644 --- a/src/MQTTBridge.h +++ b/src/MQTTBridge.h @@ -1,13 +1,14 @@ #pragma once -#ifdef WITH_MQTT +#if defined(WITH_MQTT) || defined(WITH_ETHERNET) #include +#include #include #include #include #include -#include "WiFiManager.h" +#include "INetworkManager.h" // Forward declarations struct NodePrefs; @@ -29,13 +30,14 @@ struct MQTTConfig { char topic_prefix[32]; char client_id[24]; uint8_t enabled; + uint8_t use_tls; uint16_t keepalive_secs; uint16_t publish_interval_ms; }; class MQTTBridge { public: - MQTTBridge(WiFiManager& wifi, mesh::PacketManager* mgr, mesh::RTCClock* rtc); + MQTTBridge(INetworkManager* network, mesh::PacketManager* mgr, mesh::RTCClock* rtc); void begin(const MQTTConfig& config, const uint8_t* self_pubkey); void loop(); @@ -66,8 +68,9 @@ public: void setCommandCallback(CommandCallback cb) { _command_callback = cb; } private: - WiFiManager& _wifi; - WiFiClientSecure _wifi_client; + INetworkManager* _network; + WiFiClient _wifi_client; // Works for both WiFi and Ethernet! + WiFiClientSecure _wifi_client_secure; PubSubClient _mqtt_client; MQTTConfig _config; MQTTState _state; @@ -119,4 +122,4 @@ private: const char* getTimestamp(); }; -#endif // WITH_MQTT +#endif // WITH_MQTT || WITH_ETHERNET diff --git a/src/MyMesh.cpp b/src/MyMesh.cpp index ecdb953..30c9a12 100644 --- a/src/MyMesh.cpp +++ b/src/MyMesh.cpp @@ -749,10 +749,15 @@ void MyMesh::begin(FILESYSTEM *fs) { #endif #ifdef WITH_MQTT - // Initialize MQTT gateway + // Initialize MQTT gateway (WiFi) initMQTT(); #endif +#ifdef WITH_ETHERNET + // Initialize MQTT gateway (Ethernet) + initEthernet(); +#endif + radio_set_params(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr); radio_set_tx_power(_prefs.tx_power_dbm); @@ -1126,6 +1131,26 @@ void MyMesh::loop() { } #endif +#ifdef WITH_ETHERNET + // Process Ethernet and MQTT + _eth_mgr.loop(); + if (_mqtt_bridge) { + _mqtt_bridge->loop(); + + // Periodic stats publish (every 30 seconds) + if (_mqtt_bridge->isConnected() && millis() - _last_mqtt_stats > 30000) { + _mqtt_bridge->publishStats( + uptime_millis / 1000, + radio_driver.getPacketsRecv(), + radio_driver.getPacketsSent(), + getTotalAirTime() / 1000, + (int16_t)_radio->getNoiseFloor() + ); + _last_mqtt_stats = millis(); + } + } +#endif + mesh::Mesh::loop(); if (next_flood_advert && millisHasNowPassed(next_flood_advert)) { @@ -1208,14 +1233,19 @@ void MyMesh::initMQTT() { strncpy(_mqtt_config.topic_prefix, "meshcore/repeater", sizeof(_mqtt_config.topic_prefix) - 1); #endif _mqtt_config.enabled = 1; +#ifdef MQTT_USE_TLS + _mqtt_config.use_tls = MQTT_USE_TLS; +#else + _mqtt_config.use_tls = 0; +#endif _mqtt_config.keepalive_secs = 60; _mqtt_config.publish_interval_ms = 100; // Start WiFi _wifi_mgr.begin(_wifi_config); - // Create MQTT bridge - _mqtt_bridge = new MQTTBridge(_wifi_mgr, _mgr, &rtc_clock); + // Create MQTT bridge (pass pointer to INetworkManager interface) + _mqtt_bridge = new MQTTBridge(&_wifi_mgr, _mgr, &rtc_clock); _mqtt_bridge->begin(_mqtt_config, self_id.pub_key); // Create web config server @@ -1271,3 +1301,112 @@ IPAddress MyMesh::getWiFiIP() const { } #endif // WITH_MQTT + +#ifdef WITH_ETHERNET +// Ethernet initialization and helper methods + +void MyMesh::initEthernet() { + Serial.println("[ETH] Initializing Ethernet + MQTT gateway..."); + + // Configure Ethernet (use defaults or build flags) + memset(&_eth_config, 0, sizeof(_eth_config)); +#ifdef ETH_SPI_SCK + _eth_config.spi_sck = ETH_SPI_SCK; +#else + _eth_config.spi_sck = 19; +#endif +#ifdef ETH_SPI_MOSI + _eth_config.spi_mosi = ETH_SPI_MOSI; +#else + _eth_config.spi_mosi = 20; +#endif +#ifdef ETH_SPI_MISO + _eth_config.spi_miso = ETH_SPI_MISO; +#else + _eth_config.spi_miso = 47; +#endif +#ifdef ETH_SPI_CS + _eth_config.spi_cs = ETH_SPI_CS; +#else + _eth_config.spi_cs = 48; +#endif + _eth_config.connect_timeout_ms = 30000; + _eth_config.reconnect_interval_ms = 5000; + _eth_config.use_dhcp = true; + + // Configure MQTT + memset(&_mqtt_config, 0, sizeof(_mqtt_config)); +#ifdef MQTT_BROKER + strncpy(_mqtt_config.broker, MQTT_BROKER, sizeof(_mqtt_config.broker) - 1); +#else + strncpy(_mqtt_config.broker, "mqtt.example.com", sizeof(_mqtt_config.broker) - 1); +#endif +#ifdef MQTT_PORT + _mqtt_config.port = MQTT_PORT; +#else + _mqtt_config.port = 1883; +#endif +#ifdef MQTT_USER + strncpy(_mqtt_config.user, MQTT_USER, sizeof(_mqtt_config.user) - 1); +#endif +#ifdef MQTT_PASS + strncpy(_mqtt_config.password, MQTT_PASS, sizeof(_mqtt_config.password) - 1); +#endif +#ifdef MQTT_TOPIC_PREFIX + strncpy(_mqtt_config.topic_prefix, MQTT_TOPIC_PREFIX, sizeof(_mqtt_config.topic_prefix) - 1); +#else + strncpy(_mqtt_config.topic_prefix, "meshcore/repeater", sizeof(_mqtt_config.topic_prefix) - 1); +#endif + _mqtt_config.enabled = 1; + _mqtt_config.use_tls = 0; // TLS not typically used with local Ethernet + _mqtt_config.keepalive_secs = 60; + _mqtt_config.publish_interval_ms = 100; + + // Start Ethernet + _eth_mgr.begin(_eth_config); + + // Create MQTT bridge (pass pointer to INetworkManager interface) + _mqtt_bridge = new MQTTBridge(&_eth_mgr, _mgr, &rtc_clock); + _mqtt_bridge->begin(_mqtt_config, self_id.pub_key); + + _last_mqtt_stats = millis(); + + Serial.println("[ETH] Gateway initialized"); +} + +bool MyMesh::isMQTTConnected() const { + return _mqtt_bridge && _mqtt_bridge->isConnected(); +} + +bool MyMesh::isEthernetConnected() const { + return _eth_mgr.isConnected(); +} + +void MyMesh::setMQTTEnabled(bool enable) { + _mqtt_config.enabled = enable ? 1 : 0; + if (_mqtt_bridge) { + _mqtt_bridge->updateConfig(_mqtt_config); + } +} + +const char* MyMesh::getMQTTStatus() const { + if (!_mqtt_bridge) return "not initialized"; + if (!_mqtt_config.enabled) return "disabled"; + if (_mqtt_bridge->isConnected()) return "connected"; + return "disconnected"; +} + +const char* MyMesh::getEthernetStatus() const { + switch (_eth_mgr.getState()) { + case NetworkState::CONNECTED: return "connected"; + case NetworkState::CONNECTING: return "connecting"; + case NetworkState::ERROR: return "error"; + default: return "disconnected"; + } +} + +IPAddress MyMesh::getEthernetIP() const { + return _eth_mgr.getLocalIP(); +} + +#endif // WITH_ETHERNET diff --git a/src/MyMesh.h b/src/MyMesh.h index 1ce9c09..c68f270 100644 --- a/src/MyMesh.h +++ b/src/MyMesh.h @@ -29,6 +29,11 @@ #include "WebConfig.h" #endif +#ifdef WITH_ETHERNET +#include "EthernetManager.h" +#include "MQTTBridge.h" +#endif + #include #include #include @@ -129,6 +134,16 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { void initMQTT(); #endif +#ifdef WITH_ETHERNET + EthernetManager _eth_mgr; + MQTTBridge* _mqtt_bridge; + MQTTConfig _mqtt_config; + EthernetConfig _eth_config; + unsigned long _last_mqtt_stats; + + void initEthernet(); +#endif + void putNeighbour(const mesh::Identity& id, uint32_t timestamp, float snr); uint8_t handleLoginReq(const mesh::Identity& sender, const uint8_t* secret, uint32_t sender_timestamp, const uint8_t* data, bool is_flood); int handleRequest(ClientInfo* sender, uint32_t sender_timestamp, uint8_t* payload, size_t payload_len); @@ -224,7 +239,7 @@ public: void loop(); #ifdef WITH_MQTT - // MQTT gateway methods + // MQTT gateway methods (WiFi) bool isMQTTConnected() const; bool isWiFiConnected() const; void setMQTTEnabled(bool enable); @@ -233,6 +248,16 @@ public: IPAddress getWiFiIP() const; #endif +#ifdef WITH_ETHERNET + // MQTT gateway methods (Ethernet) + bool isMQTTConnected() const; + bool isEthernetConnected() const; + void setMQTTEnabled(bool enable); + const char* getMQTTStatus() const; + const char* getEthernetStatus() const; + IPAddress getEthernetIP() const; +#endif + #if defined(WITH_BRIDGE) void setBridgeState(bool enable) override { if (enable == bridge.isRunning()) return; diff --git a/src/WiFiManager.cpp b/src/WiFiManager.cpp index 6ade395..4ac1fbb 100644 --- a/src/WiFiManager.cpp +++ b/src/WiFiManager.cpp @@ -42,10 +42,32 @@ void WiFiManager::loop() { if (!_initialized) return; switch (_state) { - case WiFiState::CONNECTING: + case WiFiState::CONNECTING: { + wl_status_t status = WiFi.status(); + static wl_status_t last_status = WL_IDLE_STATUS; + static unsigned long last_status_print = 0; + + // Print status changes or every 5 seconds + if (status != last_status || millis() - last_status_print > 5000) { + const char* status_str = "UNKNOWN"; + switch (status) { + case WL_IDLE_STATUS: status_str = "IDLE"; break; + case WL_NO_SSID_AVAIL: status_str = "NO_SSID_AVAIL"; break; + case WL_SCAN_COMPLETED: status_str = "SCAN_COMPLETED"; break; + case WL_CONNECTED: status_str = "CONNECTED"; break; + case WL_CONNECT_FAILED: status_str = "CONNECT_FAILED"; break; + case WL_CONNECTION_LOST: status_str = "CONNECTION_LOST"; break; + case WL_DISCONNECTED: status_str = "DISCONNECTED"; break; + default: break; + } + Serial.printf("[WiFi] Status: %s (%d)\n", status_str, status); + last_status = status; + last_status_print = millis(); + } + // Check for connection timeout if (millis() - _connect_start_time > _config.connect_timeout_ms) { - Serial.println("[WiFi] Connection timeout"); + Serial.printf("[WiFi] Connection timeout (status=%d)\n", status); WiFi.disconnect(); _retry_count++; @@ -56,14 +78,19 @@ void WiFiManager::loop() { _state = WiFiState::DISCONNECTED; _last_connect_attempt = millis(); } - } else if (WiFi.status() == WL_CONNECTED) { + } else if (status == WL_CONNECTED) { _state = WiFiState::CONNECTED; _connected_since = millis(); _retry_count = 0; Serial.printf("[WiFi] Connected! IP: %s, RSSI: %d dBm\n", WiFi.localIP().toString().c_str(), WiFi.RSSI()); + } else if (status == WL_NO_SSID_AVAIL) { + Serial.println("[WiFi] Network not found - check SSID and 2.4GHz availability"); + } else if (status == WL_CONNECT_FAILED) { + Serial.println("[WiFi] Connection failed - check password"); } break; + } case WiFiState::CONNECTED: // Check if still connected @@ -107,11 +134,18 @@ void WiFiManager::attemptConnection() { Serial.printf("[WiFi] Connecting to '%s' (attempt %d/%d)...\n", _config.ssid, _retry_count + 1, _config.max_retries); + // Disconnect any existing connection first + WiFi.disconnect(true); + delay(100); + // Ensure we're in STA mode if (WiFi.getMode() != WIFI_STA) { WiFi.mode(WIFI_STA); + delay(100); // Give time for mode switch } + Serial.printf("[WiFi] MAC: %s\n", WiFi.macAddress().c_str()); + WiFi.begin(_config.ssid, _config.password); _state = WiFiState::CONNECTING; _connect_start_time = millis(); @@ -219,4 +253,23 @@ String WiFiManager::generateAPSSID() { return "MeshCore-" + suffix; } +NetworkState WiFiManager::getState() const { + switch (_state) { + case WiFiState::CONNECTED: return NetworkState::CONNECTED; + case WiFiState::CONNECTING: return NetworkState::CONNECTING; + case WiFiState::AP_MODE: return NetworkState::AP_MODE; + case WiFiState::DISCONNECTED: + default: return NetworkState::DISCONNECTED; + } +} + +const char* WiFiManager::getConnectionName() const { + if (_state == WiFiState::CONNECTED) { + return WiFi.SSID().c_str(); + } else if (_state == WiFiState::AP_MODE) { + return WiFi.softAPSSID().c_str(); + } + return "Not connected"; +} + #endif // WITH_MQTT diff --git a/src/WiFiManager.h b/src/WiFiManager.h index dfee0f7..23051e7 100644 --- a/src/WiFiManager.h +++ b/src/WiFiManager.h @@ -4,8 +4,9 @@ #include #include +#include "INetworkManager.h" -// WiFi connection states +// WiFi connection states (legacy, maps to NetworkState) enum class WiFiState { DISCONNECTED, CONNECTING, @@ -24,43 +25,32 @@ struct WiFiConfig { uint8_t max_retries; }; -class WiFiManager { +class WiFiManager : public INetworkManager { public: WiFiManager(); // Initialize WiFi with configuration void begin(const WiFiConfig& config); - // Update WiFi state (call from loop) - void loop(); + // INetworkManager interface + void loop() override; + void end() override; + NetworkState getState() const override; + bool isConnected() const override { return _state == WiFiState::CONNECTED; } + IPAddress getLocalIP() const override; + String getMacAddress() const override; + int32_t getRSSI() const override; + const char* getConnectionName() const override; + void reconnect() override; + uint32_t getConnectionUptime() const override; + const char* getNetworkType() const override { return "WiFi"; } - // Stop WiFi and release resources - void end(); - - // Get current state - WiFiState getState() const { return _state; } - bool isConnected() const { return _state == WiFiState::CONNECTED; } + // WiFi-specific methods + WiFiState getWiFiState() const { return _state; } bool isAPMode() const { return _state == WiFiState::AP_MODE; } - - // Get connection info - IPAddress getLocalIP() const; - int32_t getRSSI() const; const char* getSSID() const; - String getMacAddress() const; - - // Force reconnection attempt - void reconnect(); - - // Switch to AP mode for configuration void startAPMode(); - - // Try to connect to station mode void startStationMode(); - - // Get uptime since last connection (ms) - uint32_t getConnectionUptime() const; - - // Get retry count uint8_t getRetryCount() const { return _retry_count; } private: