Add ENC28J60 Ethernet support with INetworkManager abstraction

- Create INetworkManager interface for network abstraction
- Add EthernetManager for ENC28J60 module using EthernetENC library
- Update WiFiManager to implement INetworkManager interface
- Update MQTTBridge to use INetworkManager* instead of WiFiManager&
- Add heltec_v3_ethernet PlatformIO environment
- Uses HSPI bus (GPIO 19/20/47/48) separate from LoRa SPI
This commit is contained in:
Ryan Malloy 2026-02-05 09:45:16 -07:00
parent 04667f5161
commit 7516c808a7
10 changed files with 635 additions and 57 deletions

View File

@ -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"'

199
src/EthernetManager.cpp Normal file
View File

@ -0,0 +1,199 @@
#ifdef WITH_ETHERNET
#include "EthernetManager.h"
#include <SPI.h>
#include <EthernetENC.h>
// 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

78
src/EthernetManager.h Normal file
View File

@ -0,0 +1,78 @@
#pragma once
#ifdef WITH_ETHERNET
#include <Arduino.h>
#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

51
src/INetworkManager.h Normal file
View File

@ -0,0 +1,51 @@
#pragma once
#if defined(WITH_MQTT) || defined(WITH_ETHERNET)
#include <Arduino.h>
#include <IPAddress.h>
// 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

View File

@ -1,4 +1,4 @@
#ifdef WITH_MQTT
#if defined(WITH_MQTT) || defined(WITH_ETHERNET)
#include "MQTTBridge.h"
#include <MeshCore.h>
@ -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

View File

@ -1,13 +1,14 @@
#pragma once
#ifdef WITH_MQTT
#if defined(WITH_MQTT) || defined(WITH_ETHERNET)
#include <Arduino.h>
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>
#include <Mesh.h>
#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

View File

@ -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

View File

@ -29,6 +29,11 @@
#include "WebConfig.h"
#endif
#ifdef WITH_ETHERNET
#include "EthernetManager.h"
#include "MQTTBridge.h"
#endif
#include <helpers/AdvertDataHelpers.h>
#include <helpers/ArduinoHelpers.h>
#include <helpers/ClientACL.h>
@ -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;

View File

@ -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

View File

@ -4,8 +4,9 @@
#include <Arduino.h>
#include <WiFi.h>
#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: