mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2026-05-04 13:05:52 +00:00
18
interface/pnpm-lock.yaml
generated
18
interface/pnpm-lock.yaml
generated
@@ -1151,8 +1151,8 @@ packages:
|
||||
base64-js@1.5.1:
|
||||
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
|
||||
|
||||
baseline-browser-mapping@2.10.25:
|
||||
resolution: {integrity: sha512-QO/VHsXCQdnzADMfmkeOPvHdIAkoB7i0/rGjINPJEetLx75hNttVWGQ/jycHUDP9zZ9rupbm60WRxcwViB0MiA==}
|
||||
baseline-browser-mapping@2.10.27:
|
||||
resolution: {integrity: sha512-zEs/ufmZoUd7WftKpKyXaT6RFxpQ5Qm9xytKRHvJfxFV9DFJkZph9RvJ1LcOUi0Z1ZVijMte65JbILeV+8QQEA==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
hasBin: true
|
||||
|
||||
@@ -2993,9 +2993,9 @@ packages:
|
||||
resolution: {integrity: sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==}
|
||||
deprecated: 'Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility'
|
||||
|
||||
stack-trace@1.0.0-pre2:
|
||||
resolution: {integrity: sha512-2ztBJRek8IVofG9DBJqdy2N5kulaacX30Nz7xmkYF6ale9WBVmIy6mFBchvGX7Vx/MyjBhx+Rcxqrj+dbOnQ6A==}
|
||||
engines: {node: '>=16'}
|
||||
stack-trace@1.0.0:
|
||||
resolution: {integrity: sha512-H6D7134xi6qONvh7ZHKgviXf+rd3vhGBSvebPZCaUkd8zvQ+7PtDw6CljPTe4cXWNf2IKZGNqw6VJXSb9IgBpA==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
|
||||
strict-uri-encode@1.1.0:
|
||||
resolution: {integrity: sha512-R3f198pcvnB+5IpnBlRkphuE9n46WyVl8I39W/ZUTZLz4nqSP/oLYUrcnJrw462Ds8he4YKMov2efsTIw1BDGQ==}
|
||||
@@ -4279,7 +4279,7 @@ snapshots:
|
||||
|
||||
base64-js@1.5.1: {}
|
||||
|
||||
baseline-browser-mapping@2.10.25: {}
|
||||
baseline-browser-mapping@2.10.27: {}
|
||||
|
||||
bin-build@3.0.0:
|
||||
dependencies:
|
||||
@@ -4340,7 +4340,7 @@ snapshots:
|
||||
|
||||
browserslist@4.28.2:
|
||||
dependencies:
|
||||
baseline-browser-mapping: 2.10.25
|
||||
baseline-browser-mapping: 2.10.27
|
||||
caniuse-lite: 1.0.30001791
|
||||
electron-to-chromium: 1.5.349
|
||||
node-releases: 2.0.38
|
||||
@@ -6178,7 +6178,7 @@ snapshots:
|
||||
|
||||
stable@0.1.8: {}
|
||||
|
||||
stack-trace@1.0.0-pre2: {}
|
||||
stack-trace@1.0.0: {}
|
||||
|
||||
strict-uri-encode@1.1.0: {}
|
||||
|
||||
@@ -6420,7 +6420,7 @@ snapshots:
|
||||
node-html-parser: 6.1.13
|
||||
simple-code-frame: 1.3.0
|
||||
source-map: 0.7.6
|
||||
stack-trace: 1.0.0-pre2
|
||||
stack-trace: 1.0.0
|
||||
vite: 8.0.10(@types/node@25.6.0)(esbuild@0.27.4)(terser@5.46.2)
|
||||
|
||||
vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.4)(terser@5.46.2):
|
||||
|
||||
@@ -173,7 +173,7 @@ const NetworkSettings = () => {
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors || {}}
|
||||
name="ssid"
|
||||
label={'SSID (' + LL.NETWORK_BLANK_SSID() + ')'}
|
||||
label="SSID"
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={data.ssid}
|
||||
|
||||
@@ -261,7 +261,6 @@ const cz: Translation = {
|
||||
SCAN_AGAIN: 'Skenovat znovu',
|
||||
NETWORK_SCANNER: 'Síťový skener',
|
||||
NETWORK_NO_WIFI: 'Nenalezeny žádné WiFi sítě',
|
||||
NETWORK_BLANK_SSID: 'ponechte prázdné pro deaktivaci WiFi a povolení ETH',
|
||||
NETWORK_BLANK_BSSID: 'ponechte prázdné pokud použijete jen SSID',
|
||||
TX_POWER: 'Vysílací výkon',
|
||||
HOSTNAME: 'Název hostitele',
|
||||
|
||||
@@ -261,7 +261,6 @@ const de: Translation = {
|
||||
SCAN_AGAIN: 'Erneute Suche',
|
||||
NETWORK_SCANNER: 'Netzwerksuche',
|
||||
NETWORK_NO_WIFI: 'Keine WiFi-Netzwerke gefunden',
|
||||
NETWORK_BLANK_SSID: 'Freilassen, um WiFi zu deaktivieren und ETH zu aktivieren.',
|
||||
NETWORK_BLANK_BSSID: 'Freilassen, um nur SSID für die Verbindung zu nutzen.',
|
||||
TX_POWER: 'Tx Leistung',
|
||||
HOSTNAME: 'Hostname',
|
||||
|
||||
@@ -261,7 +261,6 @@ const en: Translation = {
|
||||
SCAN_AGAIN: 'Scan again',
|
||||
NETWORK_SCANNER: 'Network Scanner',
|
||||
NETWORK_NO_WIFI: 'No WiFi networks found',
|
||||
NETWORK_BLANK_SSID: 'leave blank to disable WiFi and enable ETH',
|
||||
NETWORK_BLANK_BSSID: 'leave blank to use only SSID',
|
||||
TX_POWER: 'Tx Power',
|
||||
HOSTNAME: 'Hostname',
|
||||
|
||||
@@ -261,7 +261,6 @@ const fr: Translation = {
|
||||
SCAN_AGAIN: 'Rescanner',
|
||||
NETWORK_SCANNER: 'Scan réseau',
|
||||
NETWORK_NO_WIFI: 'Pas de réseau WiFi trouvé',
|
||||
NETWORK_BLANK_SSID: 'laisser vide pour désactiver le WiFi',
|
||||
NETWORK_BLANK_BSSID: 'laisser vide pour utiliser uniquement le SSID',
|
||||
TX_POWER: 'Puissance Tx',
|
||||
HOSTNAME: "Nom d'hôte",
|
||||
|
||||
@@ -261,7 +261,6 @@ const it: Translation = {
|
||||
SCAN_AGAIN: 'Scansiona ancora',
|
||||
NETWORK_SCANNER: 'Scansione Rete',
|
||||
NETWORK_NO_WIFI: 'Nessuana rete WiFi trovata',
|
||||
NETWORK_BLANK_SSID: 'lasciare vuoto per disattivare WiFi',
|
||||
NETWORK_BLANK_BSSID: 'lasciare vuoto per usare solo SSID',
|
||||
TX_POWER: 'Potenza Tx',
|
||||
HOSTNAME: 'Nome ospite',
|
||||
|
||||
@@ -261,7 +261,6 @@ const nl: Translation = {
|
||||
SCAN_AGAIN: 'Opnieuw scannen',
|
||||
NETWORK_SCANNER: 'Netwerk Scannen',
|
||||
NETWORK_NO_WIFI: 'Geen WiFi netwerken gevonden',
|
||||
NETWORK_BLANK_SSID: 'laat leeg om WiFi uit te schakelen',
|
||||
NETWORK_BLANK_BSSID: 'laat leeg om alleen SSID te bebruiken',
|
||||
TX_POWER: 'Tx Vermogen',
|
||||
HOSTNAME: 'Hostnaam',
|
||||
|
||||
@@ -261,7 +261,6 @@ const no: Translation = {
|
||||
SCAN_AGAIN: 'Søk igjen',
|
||||
NETWORK_SCANNER: 'Nettverk Scanner',
|
||||
NETWORK_NO_WIFI: 'Ingen trådløse nett funnet',
|
||||
NETWORK_BLANK_SSID: 'la feltet være blankt for å deaktivisere trådløst nettverk',
|
||||
NETWORK_BLANK_BSSID: 'la feltet være blankt for å bruke kun SSID',
|
||||
TX_POWER: 'Tx Effekt',
|
||||
HOSTNAME: 'Hostname',
|
||||
|
||||
@@ -261,7 +261,6 @@ const pl: BaseTranslation = {
|
||||
SCAN_AGAIN: 'Skanuj ponownie',
|
||||
NETWORK_SCANNER: 'Skaner sieci WiFi',
|
||||
NETWORK_NO_WIFI: 'Brak sieci WiFi w zasięgu',
|
||||
NETWORK_BLANK_SSID: 'pozostaw puste aby wyłączyć WiFi i włączyć ETH',
|
||||
NETWORK_BLANK_BSSID: 'pozostaw puste aby używać tylko SSID',
|
||||
TX_POWER: 'Moc nadawania',
|
||||
HOSTNAME: 'Nazwa w sieci',
|
||||
|
||||
@@ -261,7 +261,6 @@ const sk: Translation = {
|
||||
SCAN_AGAIN: 'Skenovať znova',
|
||||
NETWORK_SCANNER: 'Sieťový skener',
|
||||
NETWORK_NO_WIFI: 'WiFi siete nenájdené',
|
||||
NETWORK_BLANK_SSID: 'nechajte prázdne, ak chcete zakázať WiFi a povoliť ETH',
|
||||
NETWORK_BLANK_BSSID: 'ponechajte prázdne, ak chcete používať iba SSID',
|
||||
TX_POWER: 'Tx výkon',
|
||||
HOSTNAME: 'Hostname',
|
||||
|
||||
@@ -261,7 +261,6 @@ const sv: Translation = {
|
||||
SCAN_AGAIN: 'Sök igen',
|
||||
NETWORK_SCANNER: 'Hittade nätverk',
|
||||
NETWORK_NO_WIFI: 'Inga WiFi-nätverk hittades',
|
||||
NETWORK_BLANK_SSID: 'lämna blankt för att inaktivera WiFi',
|
||||
NETWORK_BLANK_BSSID: 'lämna blankt för att bara använda SSID',
|
||||
TX_POWER: 'Tx effekt',
|
||||
HOSTNAME: 'Värdnamn',
|
||||
|
||||
@@ -261,7 +261,6 @@ const tr: Translation = {
|
||||
SCAN_AGAIN: 'Tekrar tara',
|
||||
NETWORK_SCANNER: 'Ağ Tarayıcısı',
|
||||
NETWORK_NO_WIFI: 'Hiçbir Kablosuz Ağ bulunamadı',
|
||||
NETWORK_BLANK_SSID: 'Kablosuz ağı devre dışı bırakmak için boş bırakın',
|
||||
NETWORK_BLANK_BSSID: 'sadece SSID kullanmak için boş bırakın',
|
||||
TX_POWER: 'Aktarım gücü',
|
||||
HOSTNAME: 'Ana Makine Adı',
|
||||
|
||||
@@ -72,12 +72,6 @@ StateUpdateResult NetworkSettings::update(JsonObject root, NetworkSettings & set
|
||||
settings.staticIPConfig = false;
|
||||
}
|
||||
|
||||
// see if we need to inform the user of a restart
|
||||
// if tx power, enableCORS, CORSOrigin, ssid changes, we need to restart
|
||||
if (tx_power != settings.tx_power || enableCORS != settings.enableCORS || CORSOrigin != settings.CORSOrigin
|
||||
|| (ssid != settings.ssid && settings.ssid.isEmpty())) {
|
||||
return StateUpdateResult::CHANGED_RESTART; // tell WebUI that a restart is needed
|
||||
}
|
||||
|
||||
return StateUpdateResult::CHANGED;
|
||||
// always best to do a restart after changing network settings
|
||||
return StateUpdateResult::CHANGED_RESTART;
|
||||
}
|
||||
@@ -51,7 +51,7 @@ void NetworkStatus::networkStatus(AsyncWebServerRequest * request) {
|
||||
root["ssid"] = WiFi.SSID();
|
||||
root["bssid"] = WiFi.BSSIDstr();
|
||||
root["channel"] = WiFi.channel();
|
||||
root["reconnect_count"] = emsesp::EMSESP::network_.getWifiReconnects();
|
||||
root["reconnect_count"] = emsesp::EMSESP::network_.getNetworkReconnects();
|
||||
root["subnet_mask"] = WiFi.subnetMask().toString();
|
||||
|
||||
if (WiFi.gatewayIP() != INADDR_NONE) {
|
||||
|
||||
@@ -20,14 +20,21 @@
|
||||
|
||||
#include "emsesp.h"
|
||||
|
||||
#ifndef NETWORK_FALLBACK_AP_SSID
|
||||
#define NETWORK_FALLBACK_AP_SSID "ems-esp"
|
||||
#endif
|
||||
#ifndef NETWORK_FALLBACK_AP_PASSWORD
|
||||
#define NETWORK_FALLBACK_AP_PASSWORD "ems-esp-neo"
|
||||
#endif
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
uuid::log::Logger Network::logger_{F_(network), uuid::log::Facility::KERN};
|
||||
|
||||
void Network::begin() {
|
||||
#ifndef EMSESP_STANDALONE
|
||||
// pull Network settings and store locally on stack
|
||||
EMSESP::esp32React.getNetworkSettingsService()->read([&](auto & settings) {
|
||||
// pull all settings and store locally
|
||||
EMSESP::esp32React.getNetworkSettingsService()->read([&](NetworkSettings & settings) {
|
||||
enableMDNS_ = settings.enableMDNS;
|
||||
staticIPConfig_ = settings.staticIPConfig;
|
||||
localIP_ = settings.localIP;
|
||||
@@ -65,37 +72,81 @@ void Network::begin() {
|
||||
ap_subnetMask_ = settings.subnetMask;
|
||||
});
|
||||
|
||||
// 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.
|
||||
// Persistence is true by default, so this WiFi.mode() call writes opmode=0 to NVS for future boots.
|
||||
WiFi.mode(WIFI_OFF);
|
||||
// set before begin() so the event handlers can race-clear it safely
|
||||
wifi_connect_pending_ = false;
|
||||
ethernet_connect_pending_ = false;
|
||||
|
||||
// From here on, mode changes stay in RAM only and don't touch NVS
|
||||
phase_ = initialPhase();
|
||||
|
||||
// Initialise WiFi once when the Network service starts
|
||||
WiFi.persistent(false);
|
||||
WiFi.setAutoReconnect(false);
|
||||
WiFi.mode(WIFI_STA);
|
||||
WiFi.disconnect(true); // wipe old settings in NVS
|
||||
WiFi.setHostname(hostname_.c_str()); // updates shared default_hostname buffer
|
||||
WiFi.enableSTA(true); // creates the STA netif
|
||||
WiFi.STA.setHostname(hostname_.c_str()); // pushes to esp_netif_set_hostname
|
||||
WiFi.enableIPv6(true);
|
||||
if (staticIPConfig_) {
|
||||
WiFi.config(localIP_, gatewayIP_, subnetMask_, dnsIP1_, dnsIP2_); // configure for static IP
|
||||
}
|
||||
|
||||
// www.esp32.com/viewtopic.php?t=12055
|
||||
if (bandwidth20_) {
|
||||
esp_wifi_set_bandwidth(static_cast<wifi_interface_t>(ESP_IF_WIFI_STA), WIFI_BW_HT20);
|
||||
} else {
|
||||
esp_wifi_set_bandwidth(static_cast<wifi_interface_t>(ESP_IF_WIFI_STA), WIFI_BW_HT40);
|
||||
}
|
||||
if (nosleep_) {
|
||||
WiFi.setSleep(false); // turn off sleep - WIFI_PS_NONE
|
||||
}
|
||||
|
||||
// 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.setSortMethod(WIFI_CONNECT_AP_BY_SIGNAL); // is default, no need to set
|
||||
|
||||
// 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.
|
||||
// Also release the connect-pending guard so the next loop tick can issue a fresh WiFi.begin()
|
||||
WiFi.onEvent(
|
||||
[this](WiFiEvent_t /*event*/, WiFiEventInfo_t info) {
|
||||
last_disconnect_reason_ = info.wifi_sta_disconnected.reason;
|
||||
wifi_connect_pending_ = false;
|
||||
},
|
||||
ARDUINO_EVENT_WIFI_STA_DISCONNECTED);
|
||||
// avoid duplicate registration, so register the lambdas only once across the lifetime of this Network instance
|
||||
if (!wifi_events_registered_) {
|
||||
wifi_events_registered_ = true;
|
||||
|
||||
// Defer Tx power setting until STA is actually started
|
||||
WiFi.onEvent(
|
||||
[this](WiFiEvent_t /*event*/, WiFiEventInfo_t /*info*/) {
|
||||
#ifdef BOARD_C3_MINI_V1
|
||||
// always hardcode Tx power for Wemos C3 Mini v1
|
||||
// v1 needs this value, see https://github.com/emsesp/EMS-ESP32/pull/620#discussion_r993173979
|
||||
// https://www.wemos.cc/en/latest/c3/c3_mini_1_0_0.html#about-wifi
|
||||
WiFi.setTxPower(WIFI_POWER_8_5dBm);
|
||||
#else
|
||||
setWiFiPower(tx_power_);
|
||||
#endif
|
||||
},
|
||||
ARDUINO_EVENT_WIFI_STA_START);
|
||||
|
||||
// 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.
|
||||
WiFi.onEvent(
|
||||
[this](WiFiEvent_t /*event*/, WiFiEventInfo_t info) {
|
||||
last_disconnect_reason_ = info.wifi_sta_disconnected.reason;
|
||||
wifi_connect_pending_ = false;
|
||||
if (wifi_ever_connected_) {
|
||||
LOG_WARNING("WiFi disconnected (reason: %s)", disconnectReason(last_disconnect_reason_));
|
||||
} else {
|
||||
LOG_WARNING("WiFi initial connect attempt failed (reason: %s)", disconnectReason(last_disconnect_reason_));
|
||||
}
|
||||
},
|
||||
ARDUINO_EVENT_WIFI_STA_DISCONNECTED);
|
||||
|
||||
// clear the saved reason and the connect-pending guard on a fresh STA association,
|
||||
WiFi.onEvent(
|
||||
[this](WiFiEvent_t /*event*/, WiFiEventInfo_t /*info*/) {
|
||||
last_disconnect_reason_ = 0;
|
||||
wifi_connect_pending_ = false;
|
||||
wifi_ever_connected_ = true;
|
||||
},
|
||||
ARDUINO_EVENT_WIFI_STA_GOT_IP);
|
||||
}
|
||||
|
||||
// clear the saved reason and the connect-pending guard on a fresh STA association
|
||||
WiFi.onEvent(
|
||||
[this](WiFiEvent_t /*event*/, WiFiEventInfo_t /*info*/) {
|
||||
last_disconnect_reason_ = 0;
|
||||
wifi_connect_pending_ = false;
|
||||
},
|
||||
ARDUINO_EVENT_WIFI_STA_GOT_IP);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -147,7 +198,7 @@ std::string Network::getMacAddress() const {
|
||||
}
|
||||
}
|
||||
|
||||
// get the number of stations connected to the AP
|
||||
// get the number of sessions connected to the AP
|
||||
uint8_t Network::getStationNum() const {
|
||||
#ifndef EMSESP_STANDALONE
|
||||
return network_iface_ == NetIface::AP ? WiFi.softAPgetStationNum() : 0;
|
||||
@@ -159,12 +210,13 @@ uint8_t Network::getStationNum() const {
|
||||
// disconnect all WiFi, Eth and AP
|
||||
// so we can starts searching again to reconnect
|
||||
void Network::reconnect() {
|
||||
LOG_DEBUG("Disconnecting all networks");
|
||||
LOG_DEBUG("Reconnecting all networks");
|
||||
|
||||
#ifndef EMSESP_STANDALONE
|
||||
// disconnect WiFi
|
||||
if (wifi_connected()) {
|
||||
WiFi.disconnect(true);
|
||||
WiFi.disconnect(true, true);
|
||||
WiFi.mode(WIFI_STA); // reset mode
|
||||
}
|
||||
|
||||
// disconnect AP
|
||||
@@ -174,17 +226,33 @@ void Network::reconnect() {
|
||||
#endif
|
||||
|
||||
// reset network state
|
||||
network_ip_ = 0;
|
||||
network_iface_ = NetIface::NONE;
|
||||
has_ipv6_ = false;
|
||||
juststopped_ = true;
|
||||
wifi_connect_pending_ = false;
|
||||
last_disconnect_reason_ = 0;
|
||||
network_ip_ = 0;
|
||||
network_iface_ = NetIface::NONE;
|
||||
has_ipv6_ = false;
|
||||
juststopped_ = false;
|
||||
wifi_connect_pending_ = false;
|
||||
ethernet_connect_pending_ = false;
|
||||
last_disconnect_reason_ = 0;
|
||||
connect_retry_ = 0;
|
||||
reconnect_count_ = 0;
|
||||
phase_ = NetPhase::ETHERNET; // begin() will refine this once settings are reloaded
|
||||
|
||||
// reload the network settings, as this could be called from the console
|
||||
// reload the network settings and apply them
|
||||
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 {
|
||||
if (phy_type_ != PHY_type::PHY_TYPE_NONE) {
|
||||
return NetPhase::ETHERNET;
|
||||
}
|
||||
if (!ssid_.isEmpty()) {
|
||||
return NetPhase::WIFI;
|
||||
}
|
||||
return NetPhase::AP;
|
||||
}
|
||||
|
||||
// network loop, looking for new and disconnecting networks
|
||||
void Network::loop() {
|
||||
#ifndef EMSESP_STANDALONE
|
||||
@@ -195,17 +263,17 @@ void Network::loop() {
|
||||
if (!lastConnectionAttempt_ || static_cast<uint32_t>(currentMillis - lastConnectionAttempt_) >= reconnectDelay) {
|
||||
lastConnectionAttempt_ = currentMillis;
|
||||
|
||||
// manage network interfaces
|
||||
startAP(); // Captive Portal (AP)
|
||||
startWIFI(); // WiFi
|
||||
// manage the network interfaces: Ethernet, WiFi and AP in that order
|
||||
startEthernet(); // Ethernet
|
||||
startWIFI(); // WiFi
|
||||
startAP(); // Captive Portal (AP)
|
||||
|
||||
// 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) {
|
||||
checkConnection();
|
||||
}
|
||||
findNetworks(); // detect new connections
|
||||
|
||||
findNetworks(); // detect any new network connections
|
||||
}
|
||||
|
||||
// process DNS requests for the captive portal while the soft-AP is up
|
||||
@@ -216,9 +284,12 @@ void Network::loop() {
|
||||
}
|
||||
|
||||
// Re-validate the currently active connection
|
||||
// if a netif is no longer up or has lost its IP (cable unplugged, AP gone, DHCP lease lost, ...) we drop our state so
|
||||
// find_networks() can pick up a new one
|
||||
// if a netif is no longer up or has lost its IP (cable unplugged, AP gone, DHCP lease lost, ...) we drop our state so find_networks() can pick up a new one
|
||||
void Network::checkConnection() {
|
||||
if (network_iface_ == NetIface::NONE) {
|
||||
return;
|
||||
}
|
||||
|
||||
#ifndef EMSESP_STANDALONE
|
||||
bool still_up = false;
|
||||
for (esp_netif_t * netif = esp_netif_next_unsafe(NULL); netif != NULL; netif = esp_netif_next_unsafe(netif)) {
|
||||
@@ -239,10 +310,17 @@ void Network::checkConnection() {
|
||||
reason = WIFI_REASON_UNSPECIFIED; // event hasn't fired yet (or was cleared); avoid logging "0"
|
||||
}
|
||||
LOG_INFO("WiFi connection lost (reason %u: %s)", reason, disconnectReason(reason));
|
||||
} else {
|
||||
wifi_connect_pending_ = false;
|
||||
} else if (network_iface_ == NetIface::ETHERNET) {
|
||||
LOG_INFO("Ethernet connection lost");
|
||||
ethernet_connect_pending_ = false;
|
||||
}
|
||||
reconnect();
|
||||
juststopped_ = true;
|
||||
network_iface_ = NetIface::NONE;
|
||||
network_ip_ = 0;
|
||||
has_ipv6_ = false;
|
||||
connect_retry_ = 0;
|
||||
phase_ = initialPhase();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -250,12 +328,12 @@ void Network::checkConnection() {
|
||||
// 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
|
||||
// Range ESP32 : 2dBm - 20dBm
|
||||
// 802.11b - wifi1
|
||||
// 802.11a - wifi2
|
||||
// 802.11g - wifi3
|
||||
// 802.11n - wifi4
|
||||
// 802.11ac - wifi5
|
||||
// 802.11ax - wifi6
|
||||
// 802.11b - wifi1
|
||||
// 802.11a - wifi2
|
||||
// 802.11g - wifi3
|
||||
// 802.11n - wifi4
|
||||
// 802.11ac - wifi5
|
||||
// 802.11ax - wifi6
|
||||
// tx_power is the Tx power to set, 0 for auto
|
||||
void Network::setWiFiPower(uint8_t tx_power) {
|
||||
#ifndef EMSESP_STANDALONE
|
||||
@@ -269,7 +347,7 @@ void Network::setWiFiPower(uint8_t tx_power) {
|
||||
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
|
||||
|
||||
// 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)
|
||||
// US and some other countries allow 1000 mW (30 dBm)
|
||||
int rssi = WiFi.RSSI() * 10;
|
||||
@@ -309,7 +387,7 @@ void Network::setWiFiPower(uint8_t tx_power) {
|
||||
#endif
|
||||
|
||||
if (!WiFi.setTxPower(p)) {
|
||||
emsesp::EMSESP::logger().warning("Failed to set WiFi Tx Power");
|
||||
LOG_DEBUG("Failed to set WiFi Tx Power");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -419,88 +497,59 @@ const char * Network::disconnectReason(uint8_t code) {
|
||||
// WiFi management
|
||||
void Network::startWIFI() {
|
||||
#ifndef EMSESP_STANDALONE
|
||||
// Abort if already connected, or if we have no SSID or another Wifi.begin() is already in progress
|
||||
if (WiFi.isConnected() || ssid_.length() == 0 || wifi_connect_pending_) {
|
||||
// only run during the WIFI phase; ETHERNET phase keeps WiFi quiet, AP phase has its own bring-up
|
||||
if (phase_ != NetPhase::WIFI) {
|
||||
return;
|
||||
}
|
||||
|
||||
WiFi.setHostname(hostname_.c_str()); // updates shared default_hostname buffer
|
||||
WiFi.enableSTA(true); // creates the STA netif
|
||||
WiFi.STA.setHostname(hostname_.c_str()); // pushes to esp_netif_set_hostname
|
||||
WiFi.enableIPv6(true);
|
||||
if (staticIPConfig_) {
|
||||
WiFi.config(localIP_, gatewayIP_, subnetMask_, dnsIP1_, dnsIP2_); // configure for static IP
|
||||
// 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;
|
||||
}
|
||||
|
||||
// www.esp32.com/viewtopic.php?t=12055
|
||||
if (bandwidth20_) {
|
||||
esp_wifi_set_bandwidth(static_cast<wifi_interface_t>(ESP_IF_WIFI_STA), WIFI_BW_HT20);
|
||||
} else {
|
||||
esp_wifi_set_bandwidth(static_cast<wifi_interface_t>(ESP_IF_WIFI_STA), WIFI_BW_HT40);
|
||||
}
|
||||
if (nosleep_) {
|
||||
WiFi.setSleep(false); // turn off sleep - WIFI_PS_NONE
|
||||
}
|
||||
wifi_connect_pending_ = true;
|
||||
|
||||
// attempt to connect to the network
|
||||
uint8_t bssid[6];
|
||||
wl_status_t status;
|
||||
wifi_connect_pending_ = true; // set before begin() so the event handlers can race-clear it safely
|
||||
LOG_DEBUG("WiFi connection with %s and %s", ssid_.c_str(), password_.c_str());
|
||||
|
||||
// attempt to connect to the wifi network
|
||||
// the event handlers handle error handling and retries
|
||||
uint8_t bssid[6];
|
||||
if (formatBSSID(bssid_, bssid)) {
|
||||
status = WiFi.begin(ssid_.c_str(), password_.c_str(), 0, bssid);
|
||||
WiFi.begin(ssid_.c_str(), password_.c_str(), 0, bssid);
|
||||
} else {
|
||||
status = WiFi.begin(ssid_.c_str(), password_.c_str());
|
||||
WiFi.begin(ssid_.c_str(), password_.c_str());
|
||||
}
|
||||
if (status == WL_CONNECT_FAILED) {
|
||||
wifi_connect_pending_ = false; // begin() didn't actually start anything, allow next tick to retry
|
||||
LOG_ERROR("WiFi connection failed (code %d)", status);
|
||||
}
|
||||
|
||||
#ifdef BOARD_C3_MINI_V1
|
||||
// always hardcode Tx power for Wemos CS Mini v1
|
||||
// v1 needs this value, see https://github.com/emsesp/EMS-ESP32/pull/620#discussion_r993173979
|
||||
// https://www.wemos.cc/en/latest/c3/c3_mini_1_0_0.html#about-wifi
|
||||
WiFi.setTxPower(WIFI_POWER_8_5dBm);
|
||||
#else
|
||||
setWiFiPower(tx_power_);
|
||||
#endif
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
// Ethernet management
|
||||
// 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()
|
||||
// again on every iteration because that thrashes the netif and can prevent DHCP from completing
|
||||
// continues to run autonomously (link negotiation, DHCP, etc)
|
||||
void Network::startEthernet() {
|
||||
#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
|
||||
if (eth_started_) {
|
||||
if (ethernet_connect_pending_ || ethernet_connected()) {
|
||||
return;
|
||||
}
|
||||
|
||||
#ifndef EMSESP_STANDALONE
|
||||
|
||||
// no ethernet present or wifi takes precedence
|
||||
if (phy_type_ == PHY_type::PHY_TYPE_NONE || (ssid_.length() > 0)) {
|
||||
// no ethernet present
|
||||
if (phy_type_ == PHY_type::PHY_TYPE_NONE) {
|
||||
return;
|
||||
}
|
||||
|
||||
// configure Ethernet
|
||||
int mdc = 23; // Pin# of the I²C clock signal for the Ethernet PHY - hardcoded
|
||||
int mdio = 18; // Pin# of the I²C IO signal for the Ethernet PHY - hardcoded
|
||||
uint8_t phy_addr = eth_phy_addr_; // I²C-address of Ethernet PHY (0 or 1 for LAN8720, 31 for TLK110)
|
||||
int8_t power = eth_power_; // Pin# of the enable signal for the external crystal oscillator (-1 to disable for internal APLL source)
|
||||
eth_phy_type_t type = (phy_type_ == PHY_type::PHY_TYPE_LAN8720) ? ETH_PHY_LAN8720
|
||||
: (phy_type_ == PHY_type::PHY_TYPE_TLK110) ? ETH_PHY_TLK110
|
||||
: ETH_PHY_RTL8201; // Type of the Ethernet PHY (LAN8720 or TLK110)
|
||||
// clock mode:
|
||||
// ETH_CLOCK_GPIO0_IN = 0 RMII clock input to GPIO0
|
||||
// ETH_CLOCK_GPIO0_OUT = 1 RMII clock output from GPIO0
|
||||
// ETH_CLOCK_GPIO16_OUT = 2 RMII clock output from GPIO16
|
||||
// ETH_CLOCK_GPIO17_OUT = 3 RMII clock output from GPIO17, for 50hz inverted clock
|
||||
auto clock_mode = (eth_clock_mode_t)eth_clock_mode_;
|
||||
// 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
|
||||
if (eth_power_ != -1) {
|
||||
@@ -510,15 +559,29 @@ void Network::startEthernet() {
|
||||
digitalWrite(eth_power_, HIGH);
|
||||
}
|
||||
|
||||
if (ETH.begin(type, phy_addr, mdc, mdio, power, clock_mode)) {
|
||||
eth_started_ = true; // mark up; do not re-enter this block until reboot / explicit teardown
|
||||
// mdc = 23 = Pin# of the I²C clock signal for the Ethernet PHY
|
||||
// mdio = 18 = Pin# of the I²C IO signal for the Ethernet PHY
|
||||
// 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)
|
||||
// type = Type of the Ethernet PHY (LAN8720 or TLK110)
|
||||
// clock_mode =
|
||||
// ETH_CLOCK_GPIO0_IN = 0 RMII clock input to GPIO0
|
||||
// ETH_CLOCK_GPIO0_OUT = 1 RMII clock output from GPIO0
|
||||
// ETH_CLOCK_GPIO16_OUT = 2 RMII clock output from GPIO16
|
||||
// ETH_CLOCK_GPIO17_OUT = 3 RMII clock output from GPIO17, for 50hz inverted clock
|
||||
eth_phy_type_t type = (phy_type_ == PHY_type::PHY_TYPE_LAN8720) ? ETH_PHY_LAN8720
|
||||
: (phy_type_ == PHY_type::PHY_TYPE_TLK110) ? ETH_PHY_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_)) {
|
||||
LOG_DEBUG("Ethernet module found - starting");
|
||||
ethernet_connect_pending_ = true;
|
||||
ETH.setHostname(hostname_.c_str()); // Push hostname to the ETH netif immediately after it's created
|
||||
ETH.enableIPv6(true);
|
||||
if (staticIPConfig_) {
|
||||
ETH.config(localIP_, gatewayIP_, subnetMask_, dnsIP1_, dnsIP2_);
|
||||
}
|
||||
} else {
|
||||
LOG_ERROR("Failed to start Ethernet");
|
||||
LOG_ERROR("Failed to start Ethernet module");
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
@@ -526,17 +589,9 @@ void Network::startEthernet() {
|
||||
|
||||
// check if the network is connected and set network_ip_ / network_iface_ / has_ipv6_
|
||||
// Iterates over every esp-netif that exists, prioritizing Ethernet > WiFi > AP
|
||||
bool Network::findNetworks() {
|
||||
void Network::findNetworks() {
|
||||
#ifndef EMSESP_STANDALONE
|
||||
|
||||
// exit if already have a connection, unless in AP mode
|
||||
// when in AP mode, it will always try and connect to the WiFi
|
||||
if (network_ip_ != 0 && !(WiFi.getMode() & WIFI_AP)) {
|
||||
// const esp_ip4_addr_t ip4 = {.addr = network_ip_};
|
||||
// LOG_DEBUG("Network already connected via IPv4: " IPSTR, IP2STR(&ip4));
|
||||
return true;
|
||||
}
|
||||
|
||||
struct NetInfo {
|
||||
esp_ip4_addr_t ip;
|
||||
esp_ip6_addr_t ip6;
|
||||
@@ -580,23 +635,31 @@ bool Network::findNetworks() {
|
||||
strlcpy(info.desc, desc, sizeof(info.desc));
|
||||
}
|
||||
info.has_ipv6 = (esp_netif_get_ip6_linklocal(netif, &info.ip6) == ESP_OK);
|
||||
best_iface = candidate;
|
||||
|
||||
best_iface = candidate;
|
||||
if (best_iface == NetIface::ETHERNET) {
|
||||
break; // top priority, can't be beaten by anything later in the list
|
||||
}
|
||||
}
|
||||
|
||||
auto previous_iface = NetIface::NONE;
|
||||
|
||||
// if we have a connection and it's a new one, set it up
|
||||
if (best_iface != NetIface::NONE && best_iface != previous_iface) {
|
||||
previous_iface = network_iface_; // save the previous interface for comparison next time
|
||||
if (best_iface != NetIface::NONE && best_iface != network_iface_) {
|
||||
network_ip_ = info.ip.addr;
|
||||
network_iface_ = iface_from_desc(info.desc); // "sta"/"ap"/"eth*"
|
||||
has_ipv6_ = info.has_ipv6;
|
||||
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 ")",
|
||||
network_iface_ == NetIface::ETHERNET ? "Ethernet"
|
||||
: network_iface_ == NetIface::WIFI ? "WiFi"
|
||||
@@ -612,8 +675,7 @@ bool Network::findNetworks() {
|
||||
// count the number of restarts (for Wifi and Eth)
|
||||
if (juststopped_) {
|
||||
juststopped_ = false;
|
||||
connectcount_++;
|
||||
LOG_DEBUG("Network re-connection count %d", connectcount_);
|
||||
reconnect_count_++;
|
||||
}
|
||||
|
||||
// start mDNS for any real network interface (skip the SoftAP since the captive portal handles its own DNS)
|
||||
@@ -624,44 +686,94 @@ bool Network::findNetworks() {
|
||||
// fetch the versions.json file from emsesp.org
|
||||
EMSESP::webStatusService.schedule_versions_refresh();
|
||||
|
||||
return true; // we have a network connection
|
||||
return;
|
||||
}
|
||||
|
||||
// fallback, reset network state if nothing found
|
||||
if (best_iface == NetIface::NONE) {
|
||||
network_ip_ = 0;
|
||||
network_iface_ = NetIface::NONE;
|
||||
has_ipv6_ = false;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// fallback
|
||||
network_ip_ = 0;
|
||||
network_iface_ = NetIface::NONE;
|
||||
has_ipv6_ = false;
|
||||
connect_retry_++;
|
||||
LOG_DEBUG("No active network interfaces found yet, re-connection count %d", connect_retry_);
|
||||
#endif
|
||||
return false; // no connection found yet
|
||||
return;
|
||||
}
|
||||
|
||||
// access point (soft-AP) and the captive portal
|
||||
void Network::startAP() {
|
||||
#ifndef EMSESP_STANDALONE
|
||||
// Only start AP as a fallback if the Network has failed
|
||||
if (connect_retry_ < MAX_NETWORK_RECONNECTION_ATTEMPTS) {
|
||||
// only start AP as a fallback once both the Ethernet and WiFi phases have given up
|
||||
if (phase_ != NetPhase::AP) {
|
||||
return;
|
||||
}
|
||||
|
||||
// don't start the soft-AP if it is disabled, or Ethernet has taken over or we have a real WiFi connection or it's already running
|
||||
if (ap_provisionMode_ == AP_MODE_NEVER || network_connected() || WiFi.getMode() & WIFI_AP) {
|
||||
// Don't start AP if wired/Wi-Fi STA is serving the network
|
||||
if (ap_provisionMode_ == AP_MODE_NEVER || network_connected()) {
|
||||
return;
|
||||
}
|
||||
|
||||
WiFi.softAPenableIPv6(); // force IPv6, same as for STA - fixes https://github.com/emsesp/EMS-ESP32/issues/1922
|
||||
WiFi.softAPConfig(ap_localIP_, ap_gatewayIP_, ap_subnetMask_);
|
||||
// Captive-portal DNS is already bound to the softAP interface
|
||||
if (ap_dnsServer_) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!WiFi.softAPConfig(ap_localIP_, ap_gatewayIP_, ap_subnetMask_)) {
|
||||
LOG_DEBUG("softAPConfig failed");
|
||||
return;
|
||||
}
|
||||
esp_wifi_set_bandwidth(static_cast<wifi_interface_t>(ESP_IF_WIFI_AP), WIFI_BW_HT20);
|
||||
WiFi.softAP(ap_ssid_.c_str(), ap_password_.c_str(), ap_channel_, ap_ssid_hidden_, ap_max_clients_);
|
||||
|
||||
// WiFi.softAPenableIPv6();
|
||||
|
||||
if (!WiFi.softAP(ap_ssid_.c_str(), ap_password_.c_str(), ap_channel_, ap_ssid_hidden_, ap_max_clients_)) {
|
||||
LOG_ERROR("softAP failed; check SSID/password in AP settings");
|
||||
WiFi.softAPdisconnect(true);
|
||||
return;
|
||||
}
|
||||
#if CONFIG_IDF_TARGET_ESP32C3
|
||||
WiFi.setTxPower(WIFI_POWER_8_5dBm); // https://www.wemos.cc/en/latest/c3/c3_mini_1_0_0.html#about-wifi
|
||||
#endif
|
||||
const IPAddress apIp = WiFi.softAPIP();
|
||||
if (static_cast<uint32_t>(apIp) == 0) {
|
||||
LOG_DEBUG("SoftAP has no IPv4 yet; skipping captive-portal DNS for now.");
|
||||
WiFi.softAPdisconnect(true);
|
||||
return;
|
||||
}
|
||||
LOG_INFO("Starting Access Point with captive portal on %u.%u.%u.%u", apIp[0], apIp[1], apIp[2], apIp[3]);
|
||||
|
||||
// start DNS server for Captive Portal
|
||||
ap_dnsServer_ = new DNSServer;
|
||||
if (!ap_dnsServer_) {
|
||||
LOG_DEBUG("Out of memory starting captive-portal DNSServer");
|
||||
WiFi.softAPdisconnect(true);
|
||||
return;
|
||||
}
|
||||
ap_dnsServer_->start(DNS_PORT, "*", apIp);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -35,12 +35,17 @@
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
#define NETWORK_RECONNECTION_DELAY_SHORT 3000 // 3 seconds
|
||||
#define NETWORK_RECONNECTION_DELAY_LONG 60000 // 60 seconds
|
||||
#define NETWORK_RECONNECTION_DELAY_SHORT 5000 // 5 seconds
|
||||
|
||||
#define MAX_NETWORK_RECONNECTION_ATTEMPTS 3 // maximum number of network reconnection attempts
|
||||
#ifndef EMSESP_DEBUG
|
||||
#define NETWORK_RECONNECTION_DELAY_LONG 60000 // 1 minute
|
||||
#else
|
||||
#define NETWORK_RECONNECTION_DELAY_LONG 10000 // 10 seconds - for debugging
|
||||
#endif
|
||||
|
||||
#define DNS_PORT 53
|
||||
#define MAX_NETWORK_RECONNECTION_ATTEMPTS 3 // maximum number of network reconnection attempts before going to AP fallback
|
||||
|
||||
#define DNS_PORT 53 // dns server port for captive portal
|
||||
|
||||
// copied from Tasmota
|
||||
#if CONFIG_IDF_TARGET_ESP32S2
|
||||
@@ -75,13 +80,18 @@ namespace emsesp {
|
||||
|
||||
// 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,
|
||||
// "ap" -> AP, "eth"/"eth1"/"eth2"/... (arduino-esp32 v3.x suffixes ETH netifs because it supports
|
||||
// multiple ETH instances) -> ETHERNET. Anything else stays as NONE.
|
||||
// "ap" -> AP, "eth"/"eth1"/"eth2"/...
|
||||
enum class NetIface : uint8_t {
|
||||
NONE = 0,
|
||||
WIFI,
|
||||
ETHERNET,
|
||||
AP,
|
||||
NONE = 0, // 0
|
||||
WIFI, // 1
|
||||
ETHERNET, // 2
|
||||
AP, // 3
|
||||
};
|
||||
|
||||
enum class NetPhase : uint8_t {
|
||||
ETHERNET = 0,
|
||||
WIFI = 1,
|
||||
AP = 2,
|
||||
};
|
||||
|
||||
class Network {
|
||||
@@ -89,10 +99,6 @@ class Network {
|
||||
void begin();
|
||||
void loop();
|
||||
|
||||
uint16_t getWifiReconnects() const {
|
||||
return connectcount_;
|
||||
}
|
||||
|
||||
uint32_t network_ip() const {
|
||||
return network_ip_;
|
||||
}
|
||||
@@ -121,8 +127,8 @@ class Network {
|
||||
return has_ipv6_ && (network_iface_ == NetIface::WIFI || network_iface_ == NetIface::ETHERNET);
|
||||
}
|
||||
|
||||
uint16_t getWifiReconnects() {
|
||||
return connectcount_;
|
||||
uint16_t getNetworkReconnects() {
|
||||
return reconnect_count_;
|
||||
}
|
||||
|
||||
std::string getLocalIP() const;
|
||||
@@ -149,10 +155,25 @@ class Network {
|
||||
return NetIface::NONE;
|
||||
}
|
||||
|
||||
static const char * network_iface_to_string(NetIface iface) {
|
||||
switch (iface) {
|
||||
case NetIface::WIFI:
|
||||
return "WiFi";
|
||||
case NetIface::ETHERNET:
|
||||
return "Ethernet";
|
||||
case NetIface::AP:
|
||||
return "AP";
|
||||
case NetIface::NONE:
|
||||
default:
|
||||
return "None";
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
private:
|
||||
static uuid::log::Logger logger_;
|
||||
|
||||
bool findNetworks();
|
||||
void findNetworks();
|
||||
void checkConnection();
|
||||
void startmDNS() const;
|
||||
bool formatBSSID(const String & bssid, uint8_t (&mac)[6]);
|
||||
@@ -162,6 +183,7 @@ class Network {
|
||||
void setWiFiPower(uint8_t tx_power);
|
||||
const char * disconnectReason(uint8_t code);
|
||||
void stopAP();
|
||||
NetPhase initialPhase() const;
|
||||
|
||||
#if defined(__clang__)
|
||||
#pragma clang diagnostic push
|
||||
@@ -169,15 +191,21 @@ class Network {
|
||||
#endif
|
||||
|
||||
unsigned long lastConnectionAttempt_ = 0;
|
||||
uint16_t connectcount_ = 0; // number of network reconnects
|
||||
uint16_t reconnect_count_ = 0; // number of network reconnects
|
||||
uint32_t network_ip_ = 0;
|
||||
NetIface network_iface_ = NetIface::NONE;
|
||||
bool has_ipv6_ = 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;
|
||||
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_ever_connected_ = false; // set true once we've successfully obtained an IP
|
||||
|
||||
// Network and AP settings
|
||||
bool enableMDNS_;
|
||||
@@ -216,7 +244,7 @@ class Network {
|
||||
|
||||
// for the captive portal in AP mode
|
||||
#ifndef EMSESP_STANDALONE
|
||||
DNSServer * ap_dnsServer_;
|
||||
DNSServer * ap_dnsServer_ = nullptr;
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
@@ -742,6 +742,8 @@ void System::button_OnClick(PButton & b) {
|
||||
}
|
||||
|
||||
// button double click
|
||||
// reconnect to AP by removing the SSID from the network settings
|
||||
// note: in v3.9 this is normal behaviour to fallback to AP if the Wifi or Ethernet connection fails
|
||||
void System::button_OnDblClick(PButton & b) {
|
||||
LOG_NOTICE("Button pressed - double click - wifi reconnect to AP");
|
||||
#ifndef EMSESP_STANDALONE
|
||||
@@ -998,7 +1000,7 @@ void System::heartbeat_json(JsonObject output) {
|
||||
int8_t rssi = WiFi.RSSI();
|
||||
output["rssi"] = rssi;
|
||||
output["wifistrength"] = wifi_quality(rssi);
|
||||
output["wifireconnects"] = EMSESP::network_.getWifiReconnects();
|
||||
output["wifireconnects"] = EMSESP::network_.getNetworkReconnects();
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1307,19 +1309,19 @@ void System::show_system(uuid::console::Shell & shell) {
|
||||
shell.println("Network:");
|
||||
switch (WiFi.status()) {
|
||||
case WL_IDLE_STATUS:
|
||||
shell.printfln(" Status: Idle");
|
||||
shell.printfln(" WiFi Status: Idle");
|
||||
break;
|
||||
|
||||
case WL_NO_SSID_AVAIL:
|
||||
shell.printfln(" Status: Network not found");
|
||||
shell.printfln(" WiFi Status: Network not found");
|
||||
break;
|
||||
|
||||
case WL_SCAN_COMPLETED:
|
||||
shell.printfln(" Status: Network scan complete");
|
||||
shell.printfln(" WiFi Status: Network scan complete");
|
||||
break;
|
||||
|
||||
case WL_CONNECTED:
|
||||
shell.printfln(" Status: WiFi connected");
|
||||
shell.printfln(" WiFi Status: Connected");
|
||||
shell.printfln(" SSID: %s", WiFi.SSID().c_str());
|
||||
shell.printfln(" BSSID: %s", WiFi.BSSIDstr().c_str());
|
||||
shell.printfln(" RSSI: %d dBm (%d %%)", WiFi.RSSI(), wifi_quality(WiFi.RSSI()));
|
||||
@@ -1367,8 +1369,13 @@ void System::show_system(uuid::console::Shell & shell) {
|
||||
shell.printfln(" IPv6 address: %s", uuid::printable_to_string(ETH.linkLocalIPv6()).c_str());
|
||||
}
|
||||
}
|
||||
shell.println();
|
||||
|
||||
// show AP is connected
|
||||
if (EMSESP::network_.ap_connected()) {
|
||||
shell.printfln(" AP Status: connected");
|
||||
}
|
||||
|
||||
shell.println();
|
||||
shell.println("Syslog:");
|
||||
if (!syslog_enabled_) {
|
||||
shell.printfln(" Syslog: disabled");
|
||||
@@ -2451,7 +2458,7 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output
|
||||
node["network"] = "WiFi";
|
||||
node["hostname"] = WiFi.getHostname();
|
||||
node["RSSI"] = WiFi.RSSI();
|
||||
node["WIFIReconnects"] = EMSESP::network_.getWifiReconnects();
|
||||
node["WIFIReconnects"] = EMSESP::network_.getNetworkReconnects();
|
||||
// node["MAC"] = WiFi.macAddress();
|
||||
// node["IPv4 address"] = uuid::printable_to_string(WiFi.localIP()) + "/" + uuid::printable_to_string(WiFi.subnetMask());
|
||||
// node["IPv4 gateway"] = uuid::printable_to_string(WiFi.gatewayIP());
|
||||
|
||||
@@ -359,7 +359,6 @@ StateUpdateResult WebSettings::update(JsonObject root, WebSettings & settings) {
|
||||
// either via the Web UI or via the Console
|
||||
void WebSettingsService::onUpdate() {
|
||||
// skip if we're restarting anyway
|
||||
|
||||
if (WebSettings::has_flags(WebSettings::ChangeFlags::RESTART)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -369,6 +369,18 @@ void WebStatusService::getVersions(JsonObject root) {
|
||||
#endif
|
||||
}
|
||||
|
||||
// schedule the next versions.json fetch a few seconds out so the network stack has time to settle
|
||||
// (DHCP completion, default-netif assignment and DNS server propagation through lwip)
|
||||
void WebStatusService::schedule_versions_refresh() {
|
||||
#ifndef EMSESP_STANDALONE
|
||||
uint32_t next = uuid::get_uptime() + VERSIONS_INITIAL_FETCH_DELAY_MS;
|
||||
if (next == 0) {
|
||||
next = 1; // 0 is the "idle" sentinel — never let the wrap land there
|
||||
}
|
||||
versions_next_fetch_ms_ = next;
|
||||
#endif
|
||||
}
|
||||
|
||||
// periodic refresh (1 hour) of the cached versions.json
|
||||
// runs on the main loop task, which has a much bigger stack than AsyncTCP needed for https
|
||||
void WebStatusService::loop() {
|
||||
@@ -378,8 +390,6 @@ void WebStatusService::loop() {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO handle a network re-connect to fetch the values again (set versions_next_fetch_ms_ to 1)
|
||||
|
||||
// 0 = idle, nothing scheduled
|
||||
if (versions_next_fetch_ms_ == 0) {
|
||||
return;
|
||||
@@ -419,7 +429,7 @@ bool WebStatusService::refresh_versions_cache() {
|
||||
int httpCode = http.GET();
|
||||
if (httpCode != HTTP_CODE_OK) {
|
||||
#if defined(EMSESP_DEBUG)
|
||||
EMSESP::logger().debug("versions.json: HTTP %d", httpCode);
|
||||
EMSESP::logger().debug("versions.json: HTTP error code %d", httpCode);
|
||||
#endif
|
||||
http.end();
|
||||
return false;
|
||||
@@ -453,7 +463,7 @@ bool WebStatusService::refresh_versions_cache() {
|
||||
|
||||
versions_cache_valid_ = true;
|
||||
#if defined(EMSESP_DEBUG)
|
||||
EMSESP::logger().debug("versions.json: refreshed (stable=%s dev=%s), current=%s",
|
||||
EMSESP::logger().debug("versions.json: fetched stable=%s, dev=%s, current=%s",
|
||||
versions_stable_.version.c_str(),
|
||||
versions_dev_.version.c_str(),
|
||||
current_version_s.c_str());
|
||||
|
||||
@@ -30,10 +30,11 @@ class WebStatusService {
|
||||
return versions_cache_valid_;
|
||||
}
|
||||
|
||||
// refresh the versions.json cache
|
||||
void schedule_versions_refresh() {
|
||||
versions_next_fetch_ms_ = 1;
|
||||
}
|
||||
// schedule a refresh of the versions.json cache. Defers the fetch by
|
||||
// VERSIONS_INITIAL_FETCH_DELAY_MS so the network stack (DHCP, default netif, DNS server)
|
||||
// has time to settle after the link first comes up — otherwise hostByName() can fail
|
||||
// immediately on boot with a noisy "DNS Failed ... error '-54'".
|
||||
void schedule_versions_refresh();
|
||||
|
||||
bool current_upgradeable() const; // true if a newer version is available
|
||||
|
||||
@@ -71,8 +72,9 @@ class WebStatusService {
|
||||
|
||||
bool refresh_versions_cache(); // does the actual HTTPS fetch + parse, returns true on success
|
||||
|
||||
static constexpr uint32_t VERSIONS_REFRESH_INTERVAL_MS = 60UL * 60UL * 1000UL; // 1 hour on success
|
||||
static constexpr uint32_t VERSIONS_RETRY_INTERVAL_MS = 5UL * 60UL * 1000UL; // 5 min after failure
|
||||
static constexpr uint32_t VERSIONS_REFRESH_INTERVAL_MS = 60UL * 60UL * 1000UL; // 1 hour on success
|
||||
static constexpr uint32_t VERSIONS_RETRY_INTERVAL_MS = 5UL * 60UL * 1000UL; // 5 min after failure
|
||||
static constexpr uint32_t VERSIONS_INITIAL_FETCH_DELAY_MS = 5UL * 1000UL; // 5 s after a link comes up
|
||||
};
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
Reference in New Issue
Block a user