mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2026-05-04 21:15:52 +00:00
use a state machine for cycling between Eth-Wifi-AP
This commit is contained in:
@@ -67,11 +67,15 @@ void Network::begin() {
|
|||||||
|
|
||||||
// Initialise WiFi - we only do this once, when the network service is started.
|
// Initialise WiFi - we only do this once, when the network service is started.
|
||||||
|
|
||||||
// We want the device to come up in opmode=0 (WIFI_OFF), which is not the default after a flash erase.
|
// WiFi.mode(WIFI_OFF); // we want the device to come up in opmode=0 (WIFI_OFF), which is not the default after a flash erase
|
||||||
// Persistence is true by default, so this WiFi.mode() call writes opmode=0 to NVS for future boots.
|
|
||||||
// WiFi.mode(WIFI_OFF);
|
|
||||||
|
|
||||||
// if Wifi is disabled, with no SSID, stop here
|
// pick the first usable phase based on what's actually configured on this device.
|
||||||
|
// done up-front so the early-return paths below still leave us in a sane phase
|
||||||
|
// (e.g. on a board with no SSID and no Ethernet PHY we want to land in AP without
|
||||||
|
// burning a 4-tick Ethernet timeout first)
|
||||||
|
phase_ = initialPhase();
|
||||||
|
|
||||||
|
// if Wifi is disabled, or with no SSID, don't initialise WiFi
|
||||||
if (ssid_.isEmpty()) {
|
if (ssid_.isEmpty()) {
|
||||||
WiFi.mode(WIFI_OFF);
|
WiFi.mode(WIFI_OFF);
|
||||||
return;
|
return;
|
||||||
@@ -99,20 +103,19 @@ void Network::begin() {
|
|||||||
WiFi.setSleep(false); // turn off sleep - WIFI_PS_NONE
|
WiFi.setSleep(false); // turn off sleep - WIFI_PS_NONE
|
||||||
}
|
}
|
||||||
|
|
||||||
wifi_connect_pending_ = false; // set before begin() so the event handlers can race-clear it safely
|
// set before begin() so the event handlers can race-clear it safely
|
||||||
|
wifi_connect_pending_ = false;
|
||||||
|
ethernet_connect_pending_ = false;
|
||||||
|
|
||||||
// scan settings give connect issues since arduino 2.0.14 and arduino 3.x.x with some wifi systems
|
// scan settings give connect issues since arduino 2.0.14 and arduino 3.x.x with some wifi systems
|
||||||
// WiFi.setScanMethod(WIFI_ALL_CHANNEL_SCAN); // default is FAST_SCAN
|
// WiFi.setScanMethod(WIFI_ALL_CHANNEL_SCAN); // default is FAST_SCAN
|
||||||
// WiFi.setSortMethod(WIFI_CONNECT_AP_BY_SIGNAL); // is default, no need to set
|
// WiFi.setSortMethod(WIFI_CONNECT_AP_BY_SIGNAL); // is default, no need to set
|
||||||
|
|
||||||
// arduino-esp32's WiFi.onEvent() simply appends to an internal callback list with no
|
// avoid duplicate registration, so register the lambdas only once across the lifetime of this Network instance
|
||||||
// de-duplication, so register the lambdas only once across the lifetime of this Network instance
|
|
||||||
if (!wifi_events_registered_) {
|
if (!wifi_events_registered_) {
|
||||||
wifi_events_registered_ = true;
|
wifi_events_registered_ = true;
|
||||||
|
|
||||||
// Defer Tx power setting until STA is actually started. Calling WiFi.setTxPower() before
|
// Defer Tx power setting until STA is actually started
|
||||||
// WIFI_EVENT_STA_START fires would fail with "Neither AP or STA has been started" because
|
|
||||||
// WiFi.STA.started() only flips after esp_wifi_start() raises the event asynchronously.
|
|
||||||
WiFi.onEvent(
|
WiFi.onEvent(
|
||||||
[this](WiFiEvent_t /*event*/, WiFiEventInfo_t /*info*/) {
|
[this](WiFiEvent_t /*event*/, WiFiEventInfo_t /*info*/) {
|
||||||
#ifdef BOARD_C3_MINI_V1
|
#ifdef BOARD_C3_MINI_V1
|
||||||
@@ -128,11 +131,6 @@ void Network::begin() {
|
|||||||
|
|
||||||
// capture the WIFI_REASON_* code on every STA disconnect event so check_connection() can
|
// capture the WIFI_REASON_* code on every STA disconnect event so check_connection() can
|
||||||
// log a meaningful reason when its periodic poll notices we're no longer associated.
|
// log a meaningful reason when its periodic poll notices we're no longer associated.
|
||||||
// Also release the connect-pending guard so the next loop tick can issue a fresh WiFi.begin().
|
|
||||||
// The first STA_DISCONNECTED after boot is suppressed because arduino-esp32 hard-codes a
|
|
||||||
// "retry once on first_connect" inside its own STA event handler (see STA.cpp), so a
|
|
||||||
// transient initial AUTH_FAIL / NO_AP_FOUND / etc. is automatically retried and almost
|
|
||||||
// always succeeds; logging it as a WARNING is misleading noise.
|
|
||||||
WiFi.onEvent(
|
WiFi.onEvent(
|
||||||
[this](WiFiEvent_t /*event*/, WiFiEventInfo_t info) {
|
[this](WiFiEvent_t /*event*/, WiFiEventInfo_t info) {
|
||||||
last_disconnect_reason_ = info.wifi_sta_disconnected.reason;
|
last_disconnect_reason_ = info.wifi_sta_disconnected.reason;
|
||||||
@@ -235,19 +233,35 @@ void Network::reconnect() {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
// reset network state
|
// reset network state
|
||||||
network_ip_ = 0;
|
network_ip_ = 0;
|
||||||
network_iface_ = NetIface::NONE;
|
network_iface_ = NetIface::NONE;
|
||||||
has_ipv6_ = false;
|
has_ipv6_ = false;
|
||||||
juststopped_ = false;
|
juststopped_ = false;
|
||||||
wifi_connect_pending_ = false;
|
wifi_connect_pending_ = false;
|
||||||
last_disconnect_reason_ = 0;
|
ethernet_connect_pending_ = false;
|
||||||
connect_retry_ = 0;
|
last_disconnect_reason_ = 0;
|
||||||
reconnect_count_ = 0;
|
connect_retry_ = 0;
|
||||||
|
reconnect_count_ = 0;
|
||||||
|
phase_ = NetPhase::ETHERNET; // begin() will refine this once settings are reloaded
|
||||||
|
|
||||||
// reload the network settings and apply them
|
// reload the network settings and apply them
|
||||||
begin();
|
begin();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// pick the first phase that has the hardware/config to even be attempted on this device.
|
||||||
|
// boards without an Ethernet PHY skip straight to WIFI; without a configured SSID, straight to AP.
|
||||||
|
NetPhase Network::initialPhase() const {
|
||||||
|
#ifndef EMSESP_STANDALONE
|
||||||
|
if (phy_type_ != PHY_type::PHY_TYPE_NONE) {
|
||||||
|
return NetPhase::ETHERNET;
|
||||||
|
}
|
||||||
|
if (!ssid_.isEmpty()) {
|
||||||
|
return NetPhase::WIFI;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return NetPhase::AP;
|
||||||
|
}
|
||||||
|
|
||||||
// network loop, looking for new and disconnecting networks
|
// network loop, looking for new and disconnecting networks
|
||||||
void Network::loop() {
|
void Network::loop() {
|
||||||
#ifndef EMSESP_STANDALONE
|
#ifndef EMSESP_STANDALONE
|
||||||
@@ -258,13 +272,12 @@ void Network::loop() {
|
|||||||
if (!lastConnectionAttempt_ || static_cast<uint32_t>(currentMillis - lastConnectionAttempt_) >= reconnectDelay) {
|
if (!lastConnectionAttempt_ || static_cast<uint32_t>(currentMillis - lastConnectionAttempt_) >= reconnectDelay) {
|
||||||
lastConnectionAttempt_ = currentMillis;
|
lastConnectionAttempt_ = currentMillis;
|
||||||
|
|
||||||
// manage network interfaces
|
// manage the network interfaces: Ethernet, WiFi and AP in that order
|
||||||
startEthernet(); // Ethernet
|
startEthernet(); // Ethernet
|
||||||
startWIFI(); // WiFi
|
startWIFI(); // WiFi
|
||||||
startAP(); // Captive Portal (AP)
|
startAP(); // Captive Portal (AP)
|
||||||
|
|
||||||
// already have a connection: verify it's still alive
|
// already have a connection: verify it's still alive
|
||||||
// or trigger if the WiFi handshaked failed on the WiFi.begin() call
|
|
||||||
if (network_ip_ != 0 || last_disconnect_reason_ != 0) {
|
if (network_ip_ != 0 || last_disconnect_reason_ != 0) {
|
||||||
checkConnection();
|
checkConnection();
|
||||||
}
|
}
|
||||||
@@ -306,15 +319,18 @@ void Network::checkConnection() {
|
|||||||
if (reason == 0) {
|
if (reason == 0) {
|
||||||
reason = WIFI_REASON_UNSPECIFIED; // event hasn't fired yet (or was cleared); avoid logging "0"
|
reason = WIFI_REASON_UNSPECIFIED; // event hasn't fired yet (or was cleared); avoid logging "0"
|
||||||
}
|
}
|
||||||
wifi_connect_pending_ = false;
|
|
||||||
LOG_INFO("WiFi connection lost (reason %u: %s)", reason, disconnectReason(reason));
|
LOG_INFO("WiFi connection lost (reason %u: %s)", reason, disconnectReason(reason));
|
||||||
|
wifi_connect_pending_ = false;
|
||||||
} else if (network_iface_ == NetIface::ETHERNET) {
|
} else if (network_iface_ == NetIface::ETHERNET) {
|
||||||
LOG_INFO("Ethernet connection lost");
|
LOG_INFO("Ethernet connection lost");
|
||||||
|
ethernet_connect_pending_ = false;
|
||||||
}
|
}
|
||||||
juststopped_ = true;
|
juststopped_ = true;
|
||||||
network_iface_ = NetIface::NONE;
|
network_iface_ = NetIface::NONE;
|
||||||
network_ip_ = 0;
|
network_ip_ = 0;
|
||||||
has_ipv6_ = false;
|
has_ipv6_ = false;
|
||||||
|
connect_retry_ = 0;
|
||||||
|
phase_ = initialPhase();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
@@ -322,12 +338,12 @@ void Network::checkConnection() {
|
|||||||
// set the WiFi TxPower based on the RSSI (signal strength), picking the lowest value
|
// set the WiFi TxPower based on the RSSI (signal strength), picking the lowest value
|
||||||
// code is based of RSSI (signal strength) and copied from Tasmota's WiFiSetTXpowerBasedOnRssi() which is copied ESPEasy's ESPEasyWifi.SetWiFiTXpower() function
|
// code is based of RSSI (signal strength) and copied from Tasmota's WiFiSetTXpowerBasedOnRssi() which is copied ESPEasy's ESPEasyWifi.SetWiFiTXpower() function
|
||||||
// Range ESP32 : 2dBm - 20dBm
|
// Range ESP32 : 2dBm - 20dBm
|
||||||
// 802.11b - wifi1
|
// 802.11b - wifi1
|
||||||
// 802.11a - wifi2
|
// 802.11a - wifi2
|
||||||
// 802.11g - wifi3
|
// 802.11g - wifi3
|
||||||
// 802.11n - wifi4
|
// 802.11n - wifi4
|
||||||
// 802.11ac - wifi5
|
// 802.11ac - wifi5
|
||||||
// 802.11ax - wifi6
|
// 802.11ax - wifi6
|
||||||
// tx_power is the Tx power to set, 0 for auto
|
// tx_power is the Tx power to set, 0 for auto
|
||||||
void Network::setWiFiPower(uint8_t tx_power) {
|
void Network::setWiFiPower(uint8_t tx_power) {
|
||||||
#ifndef EMSESP_STANDALONE
|
#ifndef EMSESP_STANDALONE
|
||||||
@@ -341,7 +357,7 @@ void Network::setWiFiPower(uint8_t tx_power) {
|
|||||||
int max_tx_pwr = MAX_TX_PWR_DBM_n; // assume wifi4
|
int max_tx_pwr = MAX_TX_PWR_DBM_n; // assume wifi4
|
||||||
int threshold = WIFI_SENSITIVITY_n + 120; // Margin in dBm * 10 on top of threshold
|
int threshold = WIFI_SENSITIVITY_n + 120; // Margin in dBm * 10 on top of threshold
|
||||||
|
|
||||||
// Assume AP sends with max set by ETSI standard.
|
// Assume AP sends with max set by ETSI standard
|
||||||
// 2.4 GHz: 100 mWatt (20 dBm)
|
// 2.4 GHz: 100 mWatt (20 dBm)
|
||||||
// US and some other countries allow 1000 mW (30 dBm)
|
// US and some other countries allow 1000 mW (30 dBm)
|
||||||
int rssi = WiFi.RSSI() * 10;
|
int rssi = WiFi.RSSI() * 10;
|
||||||
@@ -381,7 +397,7 @@ void Network::setWiFiPower(uint8_t tx_power) {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (!WiFi.setTxPower(p)) {
|
if (!WiFi.setTxPower(p)) {
|
||||||
emsesp::EMSESP::logger().warning("Failed to set WiFi Tx Power!!");
|
LOG_DEBUG("Failed to set WiFi Tx Power");
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
@@ -491,14 +507,19 @@ const char * Network::disconnectReason(uint8_t code) {
|
|||||||
// WiFi management
|
// WiFi management
|
||||||
void Network::startWIFI() {
|
void Network::startWIFI() {
|
||||||
#ifndef EMSESP_STANDALONE
|
#ifndef EMSESP_STANDALONE
|
||||||
// exit if WiFi is already connected, or if we have no SSID or another Wifi.begin() is already in progress
|
// only run during the WIFI phase; ETHERNET phase keeps WiFi quiet, AP phase has its own bring-up
|
||||||
if (WiFi.isConnected() || ssid_.isEmpty() || wifi_connect_pending_) {
|
if (phase_ != NetPhase::WIFI) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// exit if WiFi or Ethernet is already connected, or if we have no SSID or another Wifi.begin() is already in progress
|
||||||
|
if (WiFi.isConnected() || ssid_.isEmpty() || ethernet_connected() || wifi_connect_pending_) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
wifi_connect_pending_ = true;
|
wifi_connect_pending_ = true;
|
||||||
|
|
||||||
// LOG_DEBUG("WiFi connection with %s and %s", ssid_.c_str(), password_.c_str());
|
LOG_DEBUG("WiFi connection with %s and %s", ssid_.c_str(), password_.c_str());
|
||||||
|
|
||||||
// attempt to connect to the wifi network
|
// attempt to connect to the wifi network
|
||||||
// the event handlers handle error handling and retries
|
// the event handlers handle error handling and retries
|
||||||
@@ -513,12 +534,17 @@ void Network::startWIFI() {
|
|||||||
|
|
||||||
// Ethernet management
|
// Ethernet management
|
||||||
// Brings up the ETH driver / netif exactly once. After ETH.begin() returns true the driver
|
// Brings up the ETH driver / netif exactly once. After ETH.begin() returns true the driver
|
||||||
// continues to run autonomously (link negotiation, DHCP, etc); the loop must NOT call ETH.begin()
|
// continues to run autonomously (link negotiation, DHCP, etc)
|
||||||
// again on every iteration because that thrashes the netif and can prevent DHCP from completing
|
|
||||||
void Network::startEthernet() {
|
void Network::startEthernet() {
|
||||||
#if CONFIG_IDF_TARGET_ESP32
|
#if CONFIG_IDF_TARGET_ESP32
|
||||||
|
// only run during the ETHERNET phase; once we've given up on Ethernet, the driver is left
|
||||||
|
// running in case the link comes up later, but we no longer try to (re)start it here
|
||||||
|
if (phase_ != NetPhase::ETHERNET) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// already up and running, nothing to do
|
// already up and running, nothing to do
|
||||||
if (eth_started_) {
|
if (ethernet_connect_pending_ || ethernet_connected()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -529,6 +555,12 @@ void Network::startEthernet() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// disabled Ethernet for boards with only 4MB flash and no PSRAM
|
||||||
|
if (ESP.getFlashChipSize() < 4194304 && !ESP.getPsramSize()) { // 4MB
|
||||||
|
LOG_NOTICE("Ethernet disabled for boards with only 4MB flash");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// reset power and add a delay as ETH doesn't not always start up correctly after a warm boot
|
// reset power and add a delay as ETH doesn't not always start up correctly after a warm boot
|
||||||
if (eth_power_ != -1) {
|
if (eth_power_ != -1) {
|
||||||
pinMode(eth_power_, OUTPUT);
|
pinMode(eth_power_, OUTPUT);
|
||||||
@@ -537,11 +569,10 @@ void Network::startEthernet() {
|
|||||||
digitalWrite(eth_power_, HIGH);
|
digitalWrite(eth_power_, HIGH);
|
||||||
}
|
}
|
||||||
|
|
||||||
// call to ETH.begin(type, phy_addr, mdc, mdio, power, clock_mode)
|
// mdc = 23 = Pin# of the I²C clock signal for the Ethernet PHY
|
||||||
// mdc = 23 = Pin# of the I²C clock signal for the Ethernet PHY - hardcoded
|
// mdio = 18 = Pin# of the I²C IO signal for the Ethernet PHY
|
||||||
// mdio = 18 = Pin# of the I²C IO signal for the Ethernet PHY - hardcoded
|
// phy_addr = I²C-address of Ethernet PHY (0 or 1 for LAN8720, 31 for TLK110)
|
||||||
// phy_addr = eth_phy_addr_ = I²C-address of Ethernet PHY (0 or 1 for LAN8720, 31 for TLK110)
|
// power = Pin# of the enable signal for the external crystal oscillator (-1 to disable for internal APLL source)
|
||||||
// power = eth_power_ = Pin# of the enable signal for the external crystal oscillator (-1 to disable for internal APLL source)
|
|
||||||
// type = Type of the Ethernet PHY (LAN8720 or TLK110)
|
// type = Type of the Ethernet PHY (LAN8720 or TLK110)
|
||||||
// clock_mode =
|
// clock_mode =
|
||||||
// ETH_CLOCK_GPIO0_IN = 0 RMII clock input to GPIO0
|
// ETH_CLOCK_GPIO0_IN = 0 RMII clock input to GPIO0
|
||||||
@@ -552,15 +583,15 @@ void Network::startEthernet() {
|
|||||||
: (phy_type_ == PHY_type::PHY_TYPE_TLK110) ? ETH_PHY_TLK110
|
: (phy_type_ == PHY_type::PHY_TYPE_TLK110) ? ETH_PHY_TLK110
|
||||||
: ETH_PHY_RTL8201; // Type of the Ethernet PHY (LAN8720 or TLK110)
|
: ETH_PHY_RTL8201; // Type of the Ethernet PHY (LAN8720 or TLK110)
|
||||||
if (ETH.begin(type, eth_phy_addr_, 23, 18, eth_power_, (eth_clock_mode_t)eth_clock_mode_)) {
|
if (ETH.begin(type, eth_phy_addr_, 23, 18, eth_power_, (eth_clock_mode_t)eth_clock_mode_)) {
|
||||||
LOG_DEBUG("Ethernet started");
|
LOG_DEBUG("Ethernet module found - starting");
|
||||||
eth_started_ = true; // mark up; do not re-enter this block until reboot / explicit teardown
|
ethernet_connect_pending_ = true;
|
||||||
ETH.setHostname(hostname_.c_str()); // Push hostname to the ETH netif immediately after it's created
|
ETH.setHostname(hostname_.c_str()); // Push hostname to the ETH netif immediately after it's created
|
||||||
ETH.enableIPv6(true);
|
ETH.enableIPv6(true);
|
||||||
if (staticIPConfig_) {
|
if (staticIPConfig_) {
|
||||||
ETH.config(localIP_, gatewayIP_, subnetMask_, dnsIP1_, dnsIP2_);
|
ETH.config(localIP_, gatewayIP_, subnetMask_, dnsIP1_, dnsIP2_);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
LOG_ERROR("Failed to start Ethernet");
|
LOG_ERROR("Failed to start Ethernet module");
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
@@ -631,8 +662,6 @@ void Network::findNetworks() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// LOG_DEBUG("best_iface: %d, network_iface_: %d", best_iface, network_iface_);
|
|
||||||
|
|
||||||
// if we have a connection and it's a new one, set it up
|
// if we have a connection and it's a new one, set it up
|
||||||
if (best_iface != NetIface::NONE && best_iface != network_iface_) {
|
if (best_iface != NetIface::NONE && best_iface != network_iface_) {
|
||||||
network_ip_ = info.ip.addr;
|
network_ip_ = info.ip.addr;
|
||||||
@@ -640,6 +669,17 @@ void Network::findNetworks() {
|
|||||||
has_ipv6_ = info.has_ipv6;
|
has_ipv6_ = info.has_ipv6;
|
||||||
connect_retry_ = 0;
|
connect_retry_ = 0;
|
||||||
|
|
||||||
|
// sync the phase to the interface that actually came up. ETH can come up late
|
||||||
|
// (e.g. cable plugged in after we'd already moved on to WiFi/AP) and we want the
|
||||||
|
// next disconnect-driven retry cycle to start from the right place.
|
||||||
|
if (network_iface_ == NetIface::ETHERNET) {
|
||||||
|
phase_ = NetPhase::ETHERNET;
|
||||||
|
} else if (network_iface_ == NetIface::WIFI) {
|
||||||
|
phase_ = NetPhase::WIFI;
|
||||||
|
} else if (network_iface_ == NetIface::AP) {
|
||||||
|
phase_ = NetPhase::AP;
|
||||||
|
}
|
||||||
|
|
||||||
LOG_INFO("Network connected via %s (IP: " IPSTR ")",
|
LOG_INFO("Network connected via %s (IP: " IPSTR ")",
|
||||||
network_iface_ == NetIface::ETHERNET ? "Ethernet"
|
network_iface_ == NetIface::ETHERNET ? "Ethernet"
|
||||||
: network_iface_ == NetIface::WIFI ? "WiFi"
|
: network_iface_ == NetIface::WIFI ? "WiFi"
|
||||||
@@ -675,7 +715,31 @@ void Network::findNetworks() {
|
|||||||
network_iface_ = NetIface::NONE;
|
network_iface_ = NetIface::NONE;
|
||||||
has_ipv6_ = false;
|
has_ipv6_ = false;
|
||||||
connect_retry_++;
|
connect_retry_++;
|
||||||
LOG_DEBUG("No active network interfaces found yet (retry #%d)", connect_retry_);
|
LOG_DEBUG("Looking for network interfaces (retry #%d, phase=%s)",
|
||||||
|
connect_retry_,
|
||||||
|
phase_ == NetPhase::ETHERNET ? "Ethernet"
|
||||||
|
: phase_ == NetPhase::WIFI ? "WiFi"
|
||||||
|
: "AP");
|
||||||
|
|
||||||
|
// give up on this interface and try the next one. ETHERNET -> WIFI (or straight to AP if no SSID configured); WIFI -> AP.
|
||||||
|
if (connect_retry_ >= MAX_NETWORK_RECONNECTION_ATTEMPTS && phase_ != NetPhase::AP) {
|
||||||
|
if (phase_ == NetPhase::ETHERNET) {
|
||||||
|
if (ssid_.isEmpty()) {
|
||||||
|
LOG_WARNING("Ethernet failed to connect after %u attempts, falling back to AP", connect_retry_);
|
||||||
|
phase_ = NetPhase::AP;
|
||||||
|
} else {
|
||||||
|
LOG_WARNING("Ethernet failed to connect after %u attempts, switching to WiFi", connect_retry_);
|
||||||
|
phase_ = NetPhase::WIFI;
|
||||||
|
}
|
||||||
|
ethernet_connect_pending_ = false;
|
||||||
|
} else if (phase_ == NetPhase::WIFI) {
|
||||||
|
LOG_WARNING("WiFi failed to connect after %u attempts, falling back to AP", connect_retry_);
|
||||||
|
phase_ = NetPhase::AP;
|
||||||
|
wifi_connect_pending_ = false;
|
||||||
|
WiFi.disconnect(true);
|
||||||
|
}
|
||||||
|
connect_retry_ = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@@ -685,8 +749,8 @@ void Network::findNetworks() {
|
|||||||
// access point (soft-AP) and the captive portal
|
// access point (soft-AP) and the captive portal
|
||||||
void Network::startAP() {
|
void Network::startAP() {
|
||||||
#ifndef EMSESP_STANDALONE
|
#ifndef EMSESP_STANDALONE
|
||||||
// Only start AP as a fallback if the Network has failed
|
// only start AP as a fallback once both the Ethernet and WiFi phases have given up
|
||||||
if (connect_retry_ < MAX_NETWORK_RECONNECTION_ATTEMPTS) {
|
if (phase_ != NetPhase::AP) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,12 @@
|
|||||||
namespace emsesp {
|
namespace emsesp {
|
||||||
|
|
||||||
#define NETWORK_RECONNECTION_DELAY_SHORT 5000 // 5 seconds
|
#define NETWORK_RECONNECTION_DELAY_SHORT 5000 // 5 seconds
|
||||||
|
|
||||||
|
#ifndef EMSESP_DEBUG
|
||||||
#define NETWORK_RECONNECTION_DELAY_LONG 60000 // 1 minute
|
#define NETWORK_RECONNECTION_DELAY_LONG 60000 // 1 minute
|
||||||
|
#else
|
||||||
|
#define NETWORK_RECONNECTION_DELAY_LONG 10000 // 10 seconds - for debugging
|
||||||
|
#endif
|
||||||
|
|
||||||
#define MAX_NETWORK_RECONNECTION_ATTEMPTS 4 // maximum number of network reconnection attempts before going to AP fallback
|
#define MAX_NETWORK_RECONNECTION_ATTEMPTS 4 // maximum number of network reconnection attempts before going to AP fallback
|
||||||
|
|
||||||
@@ -75,15 +80,23 @@ namespace emsesp {
|
|||||||
|
|
||||||
// which physical interface we are currently using for the active network connection.
|
// which physical interface we are currently using for the active network connection.
|
||||||
// Mapped from the esp-netif description string returned by esp_netif_get_desc(): "sta" -> WIFI,
|
// Mapped from the esp-netif description string returned by esp_netif_get_desc(): "sta" -> WIFI,
|
||||||
// "ap" -> AP, "eth"/"eth1"/"eth2"/... (arduino-esp32 v3.x suffixes ETH netifs because it supports
|
// "ap" -> AP, "eth"/"eth1"/"eth2"/...
|
||||||
// multiple ETH instances) -> ETHERNET. Anything else stays as NONE.
|
|
||||||
enum class NetIface : uint8_t {
|
enum class NetIface : uint8_t {
|
||||||
NONE = 0,
|
NONE = 0, // 0
|
||||||
WIFI, // 1
|
WIFI, // 1
|
||||||
ETHERNET, // 2
|
ETHERNET, // 2
|
||||||
AP, // 3
|
AP, // 3
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Connection bring-up state machine. We try the "real" interfaces first (Ethernet, then WiFi)
|
||||||
|
// each with its own MAX_NETWORK_RECONNECTION_ATTEMPTS budget, before falling back to the soft-AP
|
||||||
|
// captive portal. Phase is advanced in find_networks() once retries are exhausted.
|
||||||
|
enum class NetPhase : uint8_t {
|
||||||
|
ETHERNET = 0,
|
||||||
|
WIFI = 1,
|
||||||
|
AP = 2,
|
||||||
|
};
|
||||||
|
|
||||||
class Network {
|
class Network {
|
||||||
public:
|
public:
|
||||||
void begin();
|
void begin();
|
||||||
@@ -173,6 +186,7 @@ class Network {
|
|||||||
void setWiFiPower(uint8_t tx_power);
|
void setWiFiPower(uint8_t tx_power);
|
||||||
const char * disconnectReason(uint8_t code);
|
const char * disconnectReason(uint8_t code);
|
||||||
void stopAP();
|
void stopAP();
|
||||||
|
NetPhase initialPhase() const;
|
||||||
|
|
||||||
#if defined(__clang__)
|
#if defined(__clang__)
|
||||||
#pragma clang diagnostic push
|
#pragma clang diagnostic push
|
||||||
@@ -185,14 +199,16 @@ class Network {
|
|||||||
NetIface network_iface_ = NetIface::NONE;
|
NetIface network_iface_ = NetIface::NONE;
|
||||||
bool has_ipv6_ = false;
|
bool has_ipv6_ = false;
|
||||||
bool juststopped_ = false;
|
bool juststopped_ = false;
|
||||||
bool eth_started_ = false; // true after ETH.begin() has succeeded once; prevents repeated re-init while DHCP is still running
|
|
||||||
volatile uint8_t last_disconnect_reason_ = 0;
|
volatile uint8_t last_disconnect_reason_ = 0;
|
||||||
uint16_t connect_retry_ = 0; // number of network re-connection attempts
|
uint16_t connect_retry_ = 0; // number of network re-connection attempts
|
||||||
|
|
||||||
volatile bool wifi_connect_pending_ = false;
|
volatile bool wifi_connect_pending_ = false;
|
||||||
|
volatile bool ethernet_connect_pending_ = false;
|
||||||
|
|
||||||
|
NetPhase phase_ = NetPhase::ETHERNET;
|
||||||
|
|
||||||
bool wifi_events_registered_ = false; // ensure WiFi.onEvent() handlers are registered only once across begin()/reconnect() cycles
|
bool wifi_events_registered_ = false; // ensure WiFi.onEvent() handlers are registered only once across begin()/reconnect() cycles
|
||||||
bool wifi_ever_connected_ = false; // set true once we've successfully obtained an IP; used to silence the harmless first-attempt disconnect emitted by arduino-esp32's built-in retry-once behaviour
|
bool wifi_ever_connected_ = false; // set true once we've successfully obtained an IP
|
||||||
|
|
||||||
// Network and AP settings
|
// Network and AP settings
|
||||||
bool enableMDNS_;
|
bool enableMDNS_;
|
||||||
|
|||||||
Reference in New Issue
Block a user