Files
project-vesper/vesper/src/Networking/Networking.cpp

457 lines
14 KiB
C++

#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");
}
}
}