Complete Rebuild, with Subsystems for each component. RTOS Tasks. (help by Claude)
This commit is contained in:
456
vesper/src/Networking/Networking.cpp
Normal file
456
vesper/src/Networking/Networking.cpp
Normal file
@@ -0,0 +1,456 @@
|
||||
#include "Networking.hpp"
|
||||
#include "../ConfigManager/ConfigManager.hpp"
|
||||
#include "../Logging/Logging.hpp"
|
||||
#include <WiFiManager.h>
|
||||
#include <SPI.h>
|
||||
|
||||
// Static instance for callbacks (with safety checks)
|
||||
Networking* Networking::_instance = nullptr;
|
||||
|
||||
Networking::Networking(ConfigManager& configManager)
|
||||
: _configManager(configManager)
|
||||
, _state(NetworkState::DISCONNECTED)
|
||||
, _activeConnection(ConnectionType::NONE)
|
||||
, _lastConnectionAttempt(0)
|
||||
, _bootStartTime(0)
|
||||
, _bootSequenceComplete(false)
|
||||
, _ethernetCableConnected(false)
|
||||
, _wifiManager(nullptr)
|
||||
, _reconnectionTimer(nullptr) {
|
||||
|
||||
// Safety check for multiple instances
|
||||
if (_instance != nullptr) {
|
||||
LOG_WARNING("Multiple Networking instances detected! Previous instance will be overridden.");
|
||||
}
|
||||
|
||||
_instance = this;
|
||||
_wifiManager = new WiFiManager();
|
||||
}
|
||||
|
||||
Networking::~Networking() {
|
||||
// Clear static instance safely
|
||||
if (_instance == this) {
|
||||
_instance = nullptr;
|
||||
}
|
||||
|
||||
// Cleanup timer
|
||||
if (_reconnectionTimer) {
|
||||
xTimerDelete(_reconnectionTimer, portMAX_DELAY);
|
||||
_reconnectionTimer = nullptr;
|
||||
}
|
||||
|
||||
// Cleanup WiFiManager
|
||||
if (_wifiManager) {
|
||||
delete _wifiManager;
|
||||
_wifiManager = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void Networking::begin() {
|
||||
LOG_INFO("Initializing Networking System");
|
||||
|
||||
_bootStartTime = millis();
|
||||
|
||||
// Create reconnection timer
|
||||
_reconnectionTimer = xTimerCreate("reconnectionTimer", pdMS_TO_TICKS(RECONNECTION_INTERVAL),
|
||||
pdTRUE, (void*)0, reconnectionTimerCallback);
|
||||
|
||||
// Setup network event handler
|
||||
WiFi.onEvent(networkEventHandler);
|
||||
|
||||
// Configure WiFiManager
|
||||
_wifiManager->setDebugOutput(false);
|
||||
_wifiManager->setConfigPortalTimeout(180); // 3 minutes
|
||||
|
||||
// Start Ethernet hardware
|
||||
auto& hwConfig = _configManager.getHardwareConfig();
|
||||
ETH.begin(hwConfig.ethPhyType, hwConfig.ethPhyAddr, hwConfig.ethPhyCs,
|
||||
hwConfig.ethPhyIrq, hwConfig.ethPhyRst, SPI);
|
||||
|
||||
// Start connection sequence
|
||||
LOG_INFO("Starting network connection sequence...");
|
||||
startEthernetConnection();
|
||||
}
|
||||
|
||||
void Networking::startEthernetConnection() {
|
||||
LOG_INFO("Attempting Ethernet connection...");
|
||||
setState(NetworkState::CONNECTING_ETHERNET);
|
||||
|
||||
// Check if Ethernet hardware initialization failed
|
||||
if (!ETH.linkUp()) {
|
||||
LOG_WARNING("Ethernet hardware not detected or failed to initialize");
|
||||
LOG_INFO("Falling back to WiFi immediately");
|
||||
startWiFiConnection();
|
||||
return;
|
||||
}
|
||||
|
||||
// Ethernet will auto-connect via events
|
||||
// Set timeout for Ethernet attempt (5 seconds)
|
||||
_lastConnectionAttempt = millis();
|
||||
|
||||
// Start reconnection timer to handle timeout
|
||||
xTimerStart(_reconnectionTimer, 0);
|
||||
}
|
||||
|
||||
void Networking::startWiFiConnection() {
|
||||
LOG_INFO("Attempting WiFi connection...");
|
||||
setState(NetworkState::CONNECTING_WIFI);
|
||||
|
||||
if (!hasValidWiFiCredentials()) {
|
||||
LOG_WARNING("No valid WiFi credentials found");
|
||||
if (shouldStartPortal()) {
|
||||
startWiFiPortal();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_INFO("Using WiFiManager saved credentials");
|
||||
|
||||
WiFi.mode(WIFI_STA);
|
||||
applyNetworkConfig(false); // false = WiFi config
|
||||
|
||||
// Let WiFiManager handle credentials (uses saved SSID/password)
|
||||
WiFi.begin();
|
||||
|
||||
_lastConnectionAttempt = millis();
|
||||
|
||||
// Start reconnection timer to handle timeout
|
||||
xTimerStart(_reconnectionTimer, 0);
|
||||
}
|
||||
|
||||
void Networking::startWiFiPortal() {
|
||||
LOG_INFO("Starting WiFi configuration portal...");
|
||||
setState(NetworkState::WIFI_PORTAL_MODE);
|
||||
|
||||
WiFi.mode(WIFI_AP_STA);
|
||||
|
||||
auto& netConfig = _configManager.getNetworkConfig();
|
||||
String apName = "Vesper-" + _configManager.getDeviceUID();
|
||||
|
||||
LOG_INFO("WiFi Portal: SSID='%s', Password='%s'", apName.c_str(), netConfig.apPass.c_str());
|
||||
|
||||
if (_wifiManager->autoConnect(apName.c_str(), netConfig.apPass.c_str())) {
|
||||
LOG_INFO("WiFi configured successfully via portal");
|
||||
onWiFiConnected();
|
||||
} else {
|
||||
LOG_ERROR("WiFi portal configuration failed");
|
||||
setState(NetworkState::DISCONNECTED);
|
||||
// Start reconnection timer to try again
|
||||
xTimerStart(_reconnectionTimer, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void Networking::handleReconnection() {
|
||||
if (_state == NetworkState::CONNECTED_ETHERNET || _state == NetworkState::CONNECTED_WIFI) {
|
||||
return; // Already connected
|
||||
}
|
||||
|
||||
LOG_DEBUG("Attempting reconnection...");
|
||||
|
||||
// Check for Ethernet timeout (fall back to WiFi)
|
||||
if (_state == NetworkState::CONNECTING_ETHERNET) {
|
||||
unsigned long now = millis();
|
||||
if (now - _lastConnectionAttempt > 5000) { // 5 second timeout
|
||||
LOG_INFO("Ethernet connection timeout - falling back to WiFi");
|
||||
startWiFiConnection();
|
||||
return;
|
||||
}
|
||||
return; // Still waiting for Ethernet
|
||||
}
|
||||
|
||||
// Check for WiFi timeout (try again)
|
||||
if (_state == NetworkState::CONNECTING_WIFI) {
|
||||
unsigned long now = millis();
|
||||
if (now - _lastConnectionAttempt > 10000) { // 10 second timeout
|
||||
LOG_INFO("WiFi connection timeout - retrying");
|
||||
startWiFiConnection(); // Retry WiFi
|
||||
}
|
||||
return; // Still waiting for WiFi
|
||||
}
|
||||
|
||||
// State is DISCONNECTED - decide what to try
|
||||
if (_ethernetCableConnected) {
|
||||
LOG_INFO("Ethernet cable detected - trying Ethernet");
|
||||
startEthernetConnection();
|
||||
} else {
|
||||
LOG_INFO("No Ethernet - trying WiFi");
|
||||
if (hasValidWiFiCredentials()) {
|
||||
startWiFiConnection();
|
||||
} else if (shouldStartPortal()) {
|
||||
startWiFiPortal();
|
||||
} else {
|
||||
LOG_WARNING("No WiFi credentials and boot sequence complete - waiting");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════════
|
||||
// HEALTH CHECK IMPLEMENTATION
|
||||
// ════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
bool Networking::isHealthy() const {
|
||||
// Check if we have any active connection
|
||||
if (_activeConnection == ConnectionType::NONE) {
|
||||
LOG_DEBUG("Networking: Unhealthy - No active connection");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check connection state
|
||||
if (_state != NetworkState::CONNECTED_ETHERNET && _state != NetworkState::CONNECTED_WIFI) {
|
||||
LOG_DEBUG("Networking: Unhealthy - Not in connected state");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check IP address validity
|
||||
String ip = getLocalIP();
|
||||
if (ip == "0.0.0.0" || ip.isEmpty()) {
|
||||
LOG_DEBUG("Networking: Unhealthy - Invalid IP address");
|
||||
return false;
|
||||
}
|
||||
|
||||
// For WiFi connections, check signal strength
|
||||
if (_activeConnection == ConnectionType::WIFI) {
|
||||
if (WiFi.status() != WL_CONNECTED) {
|
||||
LOG_DEBUG("Networking: Unhealthy - WiFi not connected");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check signal strength (RSSI should be better than -80 dBm)
|
||||
int32_t rssi = WiFi.RSSI();
|
||||
if (rssi < -80) {
|
||||
LOG_DEBUG("Networking: Unhealthy - Poor WiFi signal: %d dBm", rssi);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// For Ethernet connections, check link status
|
||||
if (_activeConnection == ConnectionType::ETHERNET) {
|
||||
if (!ETH.linkUp()) {
|
||||
LOG_DEBUG("Networking: Unhealthy - Ethernet link down");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Networking::setState(NetworkState newState) {
|
||||
if (_state != newState) {
|
||||
LOG_DEBUG("Network state: %d -> %d", (int)_state, (int)newState);
|
||||
_state = newState;
|
||||
}
|
||||
}
|
||||
|
||||
void Networking::setActiveConnection(ConnectionType type) {
|
||||
if (_activeConnection != type) {
|
||||
LOG_INFO("Active connection changed: %d -> %d", (int)_activeConnection, (int)type);
|
||||
_activeConnection = type;
|
||||
}
|
||||
}
|
||||
|
||||
void Networking::notifyConnectionChange(bool connected) {
|
||||
if (connected && _onNetworkConnected) {
|
||||
_onNetworkConnected();
|
||||
} else if (!connected && _onNetworkDisconnected) {
|
||||
_onNetworkDisconnected();
|
||||
}
|
||||
}
|
||||
|
||||
// Event handlers
|
||||
void Networking::onEthernetConnected() {
|
||||
LOG_INFO("Ethernet connected successfully");
|
||||
setState(NetworkState::CONNECTED_ETHERNET);
|
||||
setActiveConnection(ConnectionType::ETHERNET);
|
||||
|
||||
// Stop WiFi if it was running
|
||||
if (WiFi.getMode() != WIFI_OFF) {
|
||||
WiFi.disconnect(true);
|
||||
WiFi.mode(WIFI_OFF);
|
||||
}
|
||||
|
||||
// Stop reconnection timer
|
||||
xTimerStop(_reconnectionTimer, 0);
|
||||
|
||||
notifyConnectionChange(true);
|
||||
}
|
||||
|
||||
void Networking::onEthernetDisconnected() {
|
||||
LOG_WARNING("Ethernet disconnected");
|
||||
|
||||
if (_activeConnection == ConnectionType::ETHERNET) {
|
||||
setState(NetworkState::DISCONNECTED);
|
||||
setActiveConnection(ConnectionType::NONE);
|
||||
notifyConnectionChange(false);
|
||||
|
||||
// Start reconnection attempts
|
||||
xTimerStart(_reconnectionTimer, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void Networking::onWiFiConnected() {
|
||||
LOG_INFO("WiFi connected successfully - IP: %s", WiFi.localIP().toString().c_str());
|
||||
setState(NetworkState::CONNECTED_WIFI);
|
||||
setActiveConnection(ConnectionType::WIFI);
|
||||
|
||||
// Stop reconnection timer
|
||||
xTimerStop(_reconnectionTimer, 0);
|
||||
|
||||
// Mark boot sequence as complete
|
||||
_bootSequenceComplete = true;
|
||||
|
||||
notifyConnectionChange(true);
|
||||
}
|
||||
|
||||
void Networking::onWiFiDisconnected() {
|
||||
LOG_WARNING("WiFi disconnected");
|
||||
|
||||
if (_activeConnection == ConnectionType::WIFI) {
|
||||
setState(NetworkState::DISCONNECTED);
|
||||
setActiveConnection(ConnectionType::NONE);
|
||||
notifyConnectionChange(false);
|
||||
|
||||
// Start reconnection attempts
|
||||
xTimerStart(_reconnectionTimer, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void Networking::onEthernetCableChange(bool connected) {
|
||||
_ethernetCableConnected = connected;
|
||||
LOG_INFO("Ethernet cable %s", connected ? "connected" : "disconnected");
|
||||
|
||||
if (connected && _activeConnection != ConnectionType::ETHERNET) {
|
||||
// Cable connected and we're not using Ethernet - try to connect
|
||||
startEthernetConnection();
|
||||
}
|
||||
}
|
||||
|
||||
// Utility methods
|
||||
void Networking::applyNetworkConfig(bool ethernet) {
|
||||
auto& netConfig = _configManager.getNetworkConfig();
|
||||
|
||||
if (netConfig.useStaticIP) {
|
||||
LOG_INFO("Applying static IP configuration");
|
||||
if (ethernet) {
|
||||
ETH.config(netConfig.ip, netConfig.gateway, netConfig.subnet, netConfig.dns1, netConfig.dns2);
|
||||
} else {
|
||||
WiFi.config(netConfig.ip, netConfig.gateway, netConfig.subnet, netConfig.dns1, netConfig.dns2);
|
||||
}
|
||||
} else {
|
||||
LOG_INFO("Using DHCP configuration");
|
||||
}
|
||||
|
||||
if (ethernet) {
|
||||
ETH.setHostname(netConfig.hostname.c_str());
|
||||
} else {
|
||||
WiFi.setHostname(netConfig.hostname.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
bool Networking::hasValidWiFiCredentials() {
|
||||
// Check if WiFiManager has saved credentials
|
||||
return WiFi.SSID().length() > 0;
|
||||
}
|
||||
|
||||
bool Networking::shouldStartPortal() {
|
||||
// Only start portal during boot sequence and if we're truly disconnected
|
||||
return !_bootSequenceComplete &&
|
||||
(millis() - _bootStartTime < BOOT_TIMEOUT) &&
|
||||
_activeConnection == ConnectionType::NONE;
|
||||
}
|
||||
|
||||
// Status methods
|
||||
bool Networking::isConnected() const {
|
||||
return _activeConnection != ConnectionType::NONE;
|
||||
}
|
||||
|
||||
String Networking::getLocalIP() const {
|
||||
switch (_activeConnection) {
|
||||
case ConnectionType::ETHERNET:
|
||||
return ETH.localIP().toString();
|
||||
case ConnectionType::WIFI:
|
||||
return WiFi.localIP().toString();
|
||||
default:
|
||||
return "0.0.0.0";
|
||||
}
|
||||
}
|
||||
|
||||
void Networking::forceReconnect() {
|
||||
LOG_INFO("Forcing reconnection...");
|
||||
setState(NetworkState::RECONNECTING);
|
||||
setActiveConnection(ConnectionType::NONE);
|
||||
|
||||
// Disconnect everything
|
||||
if (WiFi.getMode() != WIFI_OFF) {
|
||||
WiFi.disconnect(true);
|
||||
WiFi.mode(WIFI_OFF);
|
||||
}
|
||||
|
||||
// Restart connection sequence
|
||||
delay(1000);
|
||||
startEthernetConnection();
|
||||
}
|
||||
|
||||
// Static callbacks
|
||||
void Networking::networkEventHandler(arduino_event_id_t event, arduino_event_info_t info) {
|
||||
if (!_instance) return;
|
||||
|
||||
LOG_DEBUG("Network event: %d", event);
|
||||
|
||||
switch (event) {
|
||||
case ARDUINO_EVENT_ETH_START:
|
||||
LOG_DEBUG("ETH Started");
|
||||
break;
|
||||
|
||||
case ARDUINO_EVENT_ETH_CONNECTED:
|
||||
LOG_DEBUG("ETH Cable Connected");
|
||||
_instance->onEthernetCableChange(true);
|
||||
break;
|
||||
|
||||
case ARDUINO_EVENT_ETH_GOT_IP:
|
||||
LOG_INFO("ETH Got IP: %s", ETH.localIP().toString().c_str());
|
||||
_instance->applyNetworkConfig(true);
|
||||
_instance->onEthernetConnected();
|
||||
break;
|
||||
|
||||
case ARDUINO_EVENT_ETH_DISCONNECTED:
|
||||
LOG_WARNING("ETH Cable Disconnected");
|
||||
_instance->onEthernetCableChange(false);
|
||||
_instance->onEthernetDisconnected();
|
||||
break;
|
||||
|
||||
case ARDUINO_EVENT_ETH_STOP:
|
||||
LOG_INFO("ETH Stopped");
|
||||
_instance->onEthernetDisconnected();
|
||||
break;
|
||||
|
||||
case ARDUINO_EVENT_WIFI_STA_GOT_IP:
|
||||
LOG_INFO("WiFi Got IP: %s", WiFi.localIP().toString().c_str());
|
||||
_instance->onWiFiConnected();
|
||||
break;
|
||||
|
||||
case ARDUINO_EVENT_WIFI_STA_DISCONNECTED:
|
||||
LOG_WARNING("WiFi Disconnected");
|
||||
_instance->onWiFiDisconnected();
|
||||
break;
|
||||
|
||||
case ARDUINO_EVENT_WIFI_STA_CONNECTED:
|
||||
LOG_DEBUG("WiFi STA Connected");
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Networking::reconnectionTimerCallback(TimerHandle_t xTimer) {
|
||||
if (_instance) {
|
||||
_instance->handleReconnection();
|
||||
|
||||
// Check if boot sequence should be marked complete
|
||||
if (!_instance->_bootSequenceComplete &&
|
||||
(millis() - _instance->_bootStartTime > BOOT_TIMEOUT)) {
|
||||
_instance->_bootSequenceComplete = true;
|
||||
LOG_INFO("Boot sequence timeout - no more portal attempts");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user