457 lines
14 KiB
C++
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");
|
|
}
|
|
}
|
|
}
|