mirror of
https://github.com/tbnobody/OpenDTU.git
synced 2025-12-11 17:30:37 +01:00
Sometimes the ESP does not trigger the ARDUINO_EVENT_WIFI_STA_DISCONNECTED when a disconnect occours. In this case, no further reconnect attempt is done. To take care of this behavior, the wifi hardware is reset when no connect occours within 60 seconds..
525 lines
16 KiB
C++
525 lines
16 KiB
C++
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* Copyright (C) 2022-2024 Thomas Basler and others
|
|
*/
|
|
#include "NetworkSettings.h"
|
|
#include "Configuration.h"
|
|
#include "MessageOutput.h"
|
|
#include "PinMapping.h"
|
|
#include "Utils.h"
|
|
#include "__compiled_constants.h"
|
|
#include "defaults.h"
|
|
#include <ESPmDNS.h>
|
|
#include <ETH.h>
|
|
|
|
NetworkSettingsClass::NetworkSettingsClass()
|
|
: _loopTask(TASK_IMMEDIATE, TASK_FOREVER, std::bind(&NetworkSettingsClass::loop, this))
|
|
, _apIp(192, 168, 4, 1)
|
|
, _apNetmask(255, 255, 255, 0)
|
|
, _dnsServer(std::make_unique<DNSServer>())
|
|
{
|
|
}
|
|
|
|
void NetworkSettingsClass::init(Scheduler& scheduler)
|
|
{
|
|
using std::placeholders::_1;
|
|
using std::placeholders::_2;
|
|
|
|
WiFi.setScanMethod(WIFI_ALL_CHANNEL_SCAN);
|
|
WiFi.setSortMethod(WIFI_CONNECT_AP_BY_SIGNAL);
|
|
|
|
WiFi.disconnect(true, true);
|
|
|
|
WiFi.onEvent(std::bind(&NetworkSettingsClass::NetworkEvent, this, _1, _2));
|
|
|
|
if (PinMapping.isValidW5500Config()) {
|
|
const PinMapping_t& pin = PinMapping.get();
|
|
_w5500 = W5500::setup(pin.w5500_mosi, pin.w5500_miso, pin.w5500_sclk, pin.w5500_cs, pin.w5500_int, pin.w5500_rst);
|
|
if (_w5500)
|
|
MessageOutput.printf("W5500: Connection successful\r\n");
|
|
else
|
|
MessageOutput.printf("W5500: Connection error!!\r\n");
|
|
}
|
|
#if CONFIG_ETH_USE_ESP32_EMAC
|
|
else if (PinMapping.isValidEthConfig()) {
|
|
const PinMapping_t& pin = PinMapping.get();
|
|
#if ESP_ARDUINO_VERSION_MAJOR < 3
|
|
ETH.begin(pin.eth_phy_addr, pin.eth_power, pin.eth_mdc, pin.eth_mdio, pin.eth_type, pin.eth_clk_mode);
|
|
#else
|
|
ETH.begin(pin.eth_type, pin.eth_phy_addr, pin.eth_mdc, pin.eth_mdio, pin.eth_power, pin.eth_clk_mode);
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
setupMode();
|
|
|
|
scheduler.addTask(_loopTask);
|
|
_loopTask.enable();
|
|
}
|
|
|
|
void NetworkSettingsClass::NetworkEvent(const WiFiEvent_t event, WiFiEventInfo_t info)
|
|
{
|
|
switch (event) {
|
|
case ARDUINO_EVENT_ETH_START:
|
|
MessageOutput.printf("ETH start\r\n");
|
|
if (_networkMode == network_mode::Ethernet) {
|
|
raiseEvent(network_event::NETWORK_START);
|
|
}
|
|
break;
|
|
case ARDUINO_EVENT_ETH_STOP:
|
|
MessageOutput.printf("ETH stop\r\n");
|
|
if (_networkMode == network_mode::Ethernet) {
|
|
raiseEvent(network_event::NETWORK_STOP);
|
|
}
|
|
break;
|
|
case ARDUINO_EVENT_ETH_CONNECTED:
|
|
MessageOutput.printf("ETH connected\r\n");
|
|
_ethConnected = true;
|
|
raiseEvent(network_event::NETWORK_CONNECTED);
|
|
break;
|
|
case ARDUINO_EVENT_ETH_GOT_IP:
|
|
MessageOutput.printf("ETH got IP: %s\r\n", ETH.localIP().toString().c_str());
|
|
if (_networkMode == network_mode::Ethernet) {
|
|
raiseEvent(network_event::NETWORK_GOT_IP);
|
|
}
|
|
break;
|
|
case ARDUINO_EVENT_ETH_DISCONNECTED:
|
|
MessageOutput.printf("ETH disconnected\r\n");
|
|
_ethConnected = false;
|
|
if (_networkMode == network_mode::Ethernet) {
|
|
raiseEvent(network_event::NETWORK_DISCONNECTED);
|
|
}
|
|
break;
|
|
case ARDUINO_EVENT_WIFI_STA_CONNECTED:
|
|
MessageOutput.printf("WiFi connected\r\n");
|
|
if (_networkMode == network_mode::WiFi) {
|
|
raiseEvent(network_event::NETWORK_CONNECTED);
|
|
}
|
|
break;
|
|
case ARDUINO_EVENT_WIFI_STA_DISCONNECTED:
|
|
// Reason codes can be found here: https://github.com/espressif/esp-idf/blob/5454d37d496a8c58542eb450467471404c606501/components/esp_wifi/include/esp_wifi_types_generic.h#L79-L141
|
|
MessageOutput.printf("WiFi disconnected: %" PRIu8 "\r\n", info.wifi_sta_disconnected.reason);
|
|
if (_networkMode == network_mode::WiFi) {
|
|
MessageOutput.printf("Try reconnecting\r\n");
|
|
_lastReconnectAttempt = millis();
|
|
WiFi.disconnect(true, false);
|
|
WiFi.begin();
|
|
raiseEvent(network_event::NETWORK_DISCONNECTED);
|
|
}
|
|
break;
|
|
case ARDUINO_EVENT_WIFI_STA_GOT_IP:
|
|
MessageOutput.printf("WiFi got ip: %s\r\n", WiFi.localIP().toString().c_str());
|
|
if (_networkMode == network_mode::WiFi) {
|
|
raiseEvent(network_event::NETWORK_GOT_IP);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
bool NetworkSettingsClass::onEvent(DtuNetworkEventCb cbEvent, const network_event event)
|
|
{
|
|
if (!cbEvent) {
|
|
return pdFALSE;
|
|
}
|
|
DtuNetworkEventCbList_t newEventHandler;
|
|
newEventHandler.cb = cbEvent;
|
|
newEventHandler.event = event;
|
|
_cbEventList.push_back(newEventHandler);
|
|
return true;
|
|
}
|
|
|
|
void NetworkSettingsClass::raiseEvent(const network_event event)
|
|
{
|
|
for (auto& entry : _cbEventList) {
|
|
if (entry.cb) {
|
|
if (entry.event == event || entry.event == network_event::NETWORK_EVENT_MAX) {
|
|
entry.cb(event);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void NetworkSettingsClass::handleMDNS()
|
|
{
|
|
const bool mdnsEnabled = Configuration.get().Mdns.Enabled;
|
|
|
|
// Return if no state change
|
|
if (_lastMdnsEnabled == mdnsEnabled) {
|
|
return;
|
|
}
|
|
|
|
_lastMdnsEnabled = mdnsEnabled;
|
|
MDNS.end();
|
|
|
|
if (!mdnsEnabled) {
|
|
MessageOutput.printf("MDNS disabled\r\n");
|
|
return;
|
|
}
|
|
|
|
MessageOutput.printf("Starting MDNS responder...\r\n");
|
|
|
|
if (!MDNS.begin(getHostname())) {
|
|
MessageOutput.printf("Error setting up MDNS responder!\r\n");
|
|
return;
|
|
}
|
|
|
|
MDNS.addService("http", "tcp", 80);
|
|
MDNS.addService("opendtu", "tcp", 80);
|
|
MDNS.addServiceTxt("opendtu", "tcp", "git_hash", __COMPILED_GIT_HASH__);
|
|
|
|
MessageOutput.printf("MDNS started\r\n");
|
|
}
|
|
|
|
void NetworkSettingsClass::setupMode()
|
|
{
|
|
if (_adminEnabled) {
|
|
WiFi.mode(WIFI_AP_STA);
|
|
String ssidString = getApName();
|
|
WiFi.softAPConfig(_apIp, _apIp, _apNetmask);
|
|
WiFi.softAP(ssidString.c_str(), Configuration.get().Security.Password);
|
|
_dnsServer->setErrorReplyCode(DNSReplyCode::NoError);
|
|
_dnsServer->start(DNS_PORT, "*", WiFi.softAPIP());
|
|
_dnsServerStatus = true;
|
|
} else {
|
|
_dnsServerStatus = false;
|
|
_dnsServer->stop();
|
|
if (_networkMode == network_mode::WiFi) {
|
|
WiFi.mode(WIFI_STA);
|
|
} else {
|
|
WiFi.mode(WIFI_MODE_NULL);
|
|
}
|
|
}
|
|
}
|
|
|
|
void NetworkSettingsClass::enableAdminMode()
|
|
{
|
|
// This prevents a immediate "Disabling search for AP" when
|
|
// the network connection persists for a long time and the
|
|
// credentials gets changed.
|
|
_connectTimeoutTimer = 0;
|
|
_connectRedoTimer = 0;
|
|
|
|
_adminTimeoutCounter = 0;
|
|
_adminTimeoutCounterMax = Configuration.get().WiFi.ApTimeout * 60;
|
|
_adminEnabled = true;
|
|
setupMode();
|
|
}
|
|
|
|
void NetworkSettingsClass::disableAdminMode()
|
|
{
|
|
_adminEnabled = false;
|
|
MessageOutput.printf("Admin mode disabled\r\n");
|
|
setupMode();
|
|
}
|
|
|
|
bool NetworkSettingsClass::wifiConfigured() const
|
|
{
|
|
// Check if SSID is empty
|
|
return strcmp(Configuration.get().WiFi.Ssid, "");
|
|
}
|
|
|
|
String NetworkSettingsClass::getApName() const
|
|
{
|
|
return String(ACCESS_POINT_NAME + String(Utils::getChipId()));
|
|
}
|
|
|
|
void NetworkSettingsClass::loop()
|
|
{
|
|
if (_ethConnected) {
|
|
if (_networkMode != network_mode::Ethernet) {
|
|
// Do stuff when switching to Ethernet mode
|
|
MessageOutput.printf("Switch to Ethernet mode\r\n");
|
|
_networkMode = network_mode::Ethernet;
|
|
WiFi.mode(WIFI_MODE_NULL);
|
|
setStaticIp();
|
|
setHostname();
|
|
}
|
|
} else if (_networkMode != network_mode::WiFi) {
|
|
// Do stuff when switching to Ethernet mode
|
|
MessageOutput.printf("Switch to WiFi mode\r\n");
|
|
_networkMode = network_mode::WiFi;
|
|
enableAdminMode();
|
|
applyConfig();
|
|
}
|
|
|
|
if (millis() - _lastTimerCall > 1000) {
|
|
if (_adminEnabled && _adminTimeoutCounterMax > 0) {
|
|
_adminTimeoutCounter++;
|
|
if (_adminTimeoutCounter % 10 == 0) {
|
|
MessageOutput.printf("Admin AP remaining seconds: %" PRIu32 " / %" PRIu32 "\r\n", _adminTimeoutCounter, _adminTimeoutCounterMax);
|
|
}
|
|
}
|
|
if (_performConnection && !isConnected() && wifiConfigured() && millis() - _lastReconnectAttempt > 60000) {
|
|
MessageOutput.printf("Wifi reconnect watchdog triggered... Resetting Wifi hardware\r\n");
|
|
WiFi.disconnect(true, false);
|
|
WiFi.mode(WIFI_MODE_NULL);
|
|
if (_adminEnabled) {
|
|
// Call enableAdminMode to reset all the timeout values.
|
|
// Otherwise the search for AP gets disabled immediatly after wifi reset.
|
|
enableAdminMode();
|
|
}
|
|
applyConfig();
|
|
_lastReconnectAttempt = millis(); // Just in case if the reconnect method gets not triggered
|
|
}
|
|
_connectTimeoutTimer++;
|
|
_connectRedoTimer++;
|
|
_lastTimerCall = millis();
|
|
}
|
|
if (_adminEnabled) {
|
|
// Don't disable the admin mode when network is not available
|
|
if (!isConnected()) {
|
|
_adminTimeoutCounter = 0;
|
|
}
|
|
// If WiFi is connected to AP for more than adminTimeoutCounterMax
|
|
// seconds, disable the internal Access Point
|
|
if (_adminTimeoutCounter > _adminTimeoutCounterMax) {
|
|
disableAdminMode();
|
|
}
|
|
// It's nearly not possible to use the internal AP if the
|
|
// WiFi is searching for an AP. So disable searching afer
|
|
// WIFI_RECONNECT_TIMEOUT and repeat after WIFI_RECONNECT_REDO_TIMEOUT
|
|
if (isConnected()) {
|
|
_connectTimeoutTimer = 0;
|
|
_connectRedoTimer = 0;
|
|
} else {
|
|
if (_connectTimeoutTimer > WIFI_RECONNECT_TIMEOUT && _performConnection) {
|
|
MessageOutput.printf("Disabling search for AP...\r\n");
|
|
WiFi.mode(WIFI_AP);
|
|
_connectRedoTimer = 0;
|
|
_performConnection = false;
|
|
}
|
|
if (_connectRedoTimer > WIFI_RECONNECT_REDO_TIMEOUT && !_performConnection) {
|
|
MessageOutput.printf("Enable search for AP...\r\n");
|
|
WiFi.mode(WIFI_AP_STA);
|
|
applyConfig();
|
|
_connectTimeoutTimer = 0;
|
|
_performConnection = true;
|
|
}
|
|
}
|
|
}
|
|
if (_dnsServerStatus) {
|
|
_dnsServer->processNextRequest();
|
|
}
|
|
|
|
handleMDNS();
|
|
}
|
|
|
|
void NetworkSettingsClass::applyConfig()
|
|
{
|
|
setHostname();
|
|
|
|
const auto& config = Configuration.get().WiFi;
|
|
|
|
if (!wifiConfigured()) {
|
|
return;
|
|
}
|
|
|
|
const bool newCredentials = strcmp(WiFi.SSID().c_str(), config.Ssid) || strcmp(WiFi.psk().c_str(), config.Password);
|
|
|
|
MessageOutput.printf("Start configuring WiFi STA using %s credentials\r\n",
|
|
newCredentials ? "new" : "existing");
|
|
|
|
bool success = false;
|
|
if (newCredentials) {
|
|
success = WiFi.begin(
|
|
config.Ssid,
|
|
config.Password) != WL_CONNECT_FAILED;
|
|
} else {
|
|
success = WiFi.begin() != WL_CONNECT_FAILED;
|
|
}
|
|
|
|
MessageOutput.printf("Configuring WiFi %s\r\n", success ? "done" : "failed");
|
|
|
|
setStaticIp();
|
|
}
|
|
|
|
void NetworkSettingsClass::setHostname()
|
|
{
|
|
if (_networkMode == network_mode::Undefined) {
|
|
return;
|
|
}
|
|
|
|
const String hostname = getHostname();
|
|
bool success = false;
|
|
|
|
MessageOutput.printf("Start setting hostname...\r\n");
|
|
if (_networkMode == network_mode::WiFi) {
|
|
success = WiFi.hostname(hostname);
|
|
|
|
// Evil bad hack to get the hostname set up correctly
|
|
WiFi.mode(WIFI_MODE_APSTA);
|
|
WiFi.mode(WIFI_MODE_STA);
|
|
setupMode();
|
|
} else if (_networkMode == network_mode::Ethernet) {
|
|
success = ETH.setHostname(hostname.c_str());
|
|
}
|
|
|
|
MessageOutput.printf("Setting hostname %s\r\n", success ? "done" : "failed");
|
|
}
|
|
|
|
void NetworkSettingsClass::setStaticIp()
|
|
{
|
|
if (_networkMode == network_mode::Undefined) {
|
|
return;
|
|
}
|
|
|
|
const auto& config = Configuration.get().WiFi;
|
|
const char* mode = (_networkMode == network_mode::WiFi) ? "WiFi" : "Ethernet";
|
|
const char* ipType = config.Dhcp ? "DHCP" : "static";
|
|
|
|
MessageOutput.printf("Start configuring %s %s IP...\r\n", mode, ipType);
|
|
|
|
bool success = false;
|
|
if (_networkMode == network_mode::WiFi) {
|
|
if (config.Dhcp) {
|
|
success = WiFi.config(INADDR_NONE, INADDR_NONE, INADDR_NONE);
|
|
} else {
|
|
success = WiFi.config(
|
|
IPAddress(config.Ip),
|
|
IPAddress(config.Gateway),
|
|
IPAddress(config.Netmask),
|
|
IPAddress(config.Dns1),
|
|
IPAddress(config.Dns2));
|
|
}
|
|
} else if (_networkMode == network_mode::Ethernet) {
|
|
if (config.Dhcp) {
|
|
success = ETH.config(INADDR_NONE, INADDR_NONE, INADDR_NONE, INADDR_NONE);
|
|
} else {
|
|
success = ETH.config(
|
|
IPAddress(config.Ip),
|
|
IPAddress(config.Gateway),
|
|
IPAddress(config.Netmask),
|
|
IPAddress(config.Dns1),
|
|
IPAddress(config.Dns2));
|
|
}
|
|
}
|
|
|
|
MessageOutput.printf("Configure IP %s\r\n", success ? "done" : "failed");
|
|
}
|
|
|
|
IPAddress NetworkSettingsClass::localIP() const
|
|
{
|
|
switch (_networkMode) {
|
|
case network_mode::Ethernet:
|
|
return ETH.localIP();
|
|
break;
|
|
case network_mode::WiFi:
|
|
return WiFi.localIP();
|
|
break;
|
|
default:
|
|
return INADDR_NONE;
|
|
}
|
|
}
|
|
|
|
IPAddress NetworkSettingsClass::subnetMask() const
|
|
{
|
|
switch (_networkMode) {
|
|
case network_mode::Ethernet:
|
|
return ETH.subnetMask();
|
|
break;
|
|
case network_mode::WiFi:
|
|
return WiFi.subnetMask();
|
|
break;
|
|
default:
|
|
return IPAddress(255, 255, 255, 0);
|
|
}
|
|
}
|
|
|
|
IPAddress NetworkSettingsClass::gatewayIP() const
|
|
{
|
|
switch (_networkMode) {
|
|
case network_mode::Ethernet:
|
|
return ETH.gatewayIP();
|
|
break;
|
|
case network_mode::WiFi:
|
|
return WiFi.gatewayIP();
|
|
break;
|
|
default:
|
|
return INADDR_NONE;
|
|
}
|
|
}
|
|
|
|
IPAddress NetworkSettingsClass::dnsIP(const uint8_t dns_no) const
|
|
{
|
|
switch (_networkMode) {
|
|
case network_mode::Ethernet:
|
|
return ETH.dnsIP(dns_no);
|
|
break;
|
|
case network_mode::WiFi:
|
|
return WiFi.dnsIP(dns_no);
|
|
break;
|
|
default:
|
|
return INADDR_NONE;
|
|
}
|
|
}
|
|
|
|
String NetworkSettingsClass::macAddress() const
|
|
{
|
|
switch (_networkMode) {
|
|
case network_mode::Ethernet:
|
|
if (_w5500) {
|
|
return _w5500->macAddress();
|
|
}
|
|
return ETH.macAddress();
|
|
break;
|
|
case network_mode::WiFi:
|
|
return WiFi.macAddress();
|
|
break;
|
|
default:
|
|
return "";
|
|
}
|
|
}
|
|
|
|
String NetworkSettingsClass::getHostname()
|
|
{
|
|
const CONFIG_T& config = Configuration.get();
|
|
char preparedHostname[WIFI_MAX_HOSTNAME_STRLEN + 1];
|
|
char resultHostname[WIFI_MAX_HOSTNAME_STRLEN + 1];
|
|
uint8_t pos = 0;
|
|
|
|
const uint32_t chipId = Utils::getChipId();
|
|
snprintf(preparedHostname, WIFI_MAX_HOSTNAME_STRLEN + 1, config.WiFi.Hostname, chipId);
|
|
|
|
const char* pC = preparedHostname;
|
|
while (*pC && pos < WIFI_MAX_HOSTNAME_STRLEN) { // while !null and not over length
|
|
if (isalnum(*pC)) { // if the current char is alpha-numeric append it to the hostname
|
|
resultHostname[pos] = *pC;
|
|
pos++;
|
|
} else if (*pC == ' ' || *pC == '_' || *pC == '-' || *pC == '+' || *pC == '!' || *pC == '?' || *pC == '*') {
|
|
resultHostname[pos] = '-';
|
|
pos++;
|
|
}
|
|
// else do nothing - no leading hyphens and do not include hyphens for all other characters.
|
|
pC++;
|
|
}
|
|
|
|
resultHostname[pos] = '\0'; // terminate string
|
|
|
|
// last character must not be hyphen
|
|
while (pos > 0 && resultHostname[pos - 1] == '-') {
|
|
resultHostname[pos - 1] = '\0';
|
|
pos--;
|
|
}
|
|
|
|
// Fallback if no other rule applied
|
|
if (strlen(resultHostname) == 0) {
|
|
snprintf(resultHostname, WIFI_MAX_HOSTNAME_STRLEN + 1, APP_HOSTNAME, chipId);
|
|
}
|
|
|
|
return resultHostname;
|
|
}
|
|
|
|
bool NetworkSettingsClass::isConnected() const
|
|
{
|
|
return (WiFi.localIP()[0] != 0 && WiFi.isConnected() ) || ETH.localIP()[0] != 0;
|
|
}
|
|
|
|
network_mode NetworkSettingsClass::NetworkMode() const
|
|
{
|
|
return _networkMode;
|
|
}
|
|
|
|
NetworkSettingsClass NetworkSettings;
|