453 lines
14 KiB
C++
453 lines
14 KiB
C++
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* Copyright (C) 2022 Thomas Basler and others
|
|
*/
|
|
#include "NetworkSettings.h"
|
|
#include "Configuration.h"
|
|
#include "MessageOutput.h"
|
|
#include "PinMapping.h"
|
|
#include "Utils.h"
|
|
#include "defaults.h"
|
|
#include <ESPmDNS.h>
|
|
#include <ETH.h>
|
|
|
|
NetworkSettingsClass::NetworkSettingsClass()
|
|
: apIp(192, 168, 4, 1)
|
|
, apNetmask(255, 255, 255, 0)
|
|
{
|
|
dnsServer.reset(new DNSServer());
|
|
}
|
|
|
|
void NetworkSettingsClass::init()
|
|
{
|
|
using std::placeholders::_1;
|
|
|
|
WiFi.setScanMethod(WIFI_ALL_CHANNEL_SCAN);
|
|
WiFi.setSortMethod(WIFI_CONNECT_AP_BY_SIGNAL);
|
|
|
|
WiFi.onEvent(std::bind(&NetworkSettingsClass::NetworkEvent, this, _1));
|
|
setupMode();
|
|
}
|
|
|
|
void NetworkSettingsClass::NetworkEvent(WiFiEvent_t event)
|
|
{
|
|
switch (event) {
|
|
case ARDUINO_EVENT_ETH_START:
|
|
MessageOutput.println("ETH start");
|
|
if (_networkMode == network_mode::Ethernet) {
|
|
raiseEvent(network_event::NETWORK_START);
|
|
}
|
|
break;
|
|
case ARDUINO_EVENT_ETH_STOP:
|
|
MessageOutput.println("ETH stop");
|
|
if (_networkMode == network_mode::Ethernet) {
|
|
raiseEvent(network_event::NETWORK_STOP);
|
|
}
|
|
break;
|
|
case ARDUINO_EVENT_ETH_CONNECTED:
|
|
MessageOutput.println("ETH connected");
|
|
_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.println("ETH disconnected");
|
|
_ethConnected = false;
|
|
if (_networkMode == network_mode::Ethernet) {
|
|
raiseEvent(network_event::NETWORK_DISCONNECTED);
|
|
}
|
|
break;
|
|
case ARDUINO_EVENT_WIFI_STA_CONNECTED:
|
|
MessageOutput.println("WiFi connected");
|
|
if (_networkMode == network_mode::WiFi) {
|
|
raiseEvent(network_event::NETWORK_CONNECTED);
|
|
}
|
|
break;
|
|
case ARDUINO_EVENT_WIFI_STA_DISCONNECTED:
|
|
MessageOutput.println("WiFi disconnected");
|
|
if (_networkMode == network_mode::WiFi) {
|
|
MessageOutput.println("Try reconnecting");
|
|
WiFi.reconnect();
|
|
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(NetworkEventCb cbEvent, network_event event)
|
|
{
|
|
if (!cbEvent) {
|
|
return pdFALSE;
|
|
}
|
|
NetworkEventCbList_t newEventHandler;
|
|
newEventHandler.cb = cbEvent;
|
|
newEventHandler.event = event;
|
|
_cbEventList.push_back(newEventHandler);
|
|
return true;
|
|
}
|
|
|
|
void NetworkSettingsClass::raiseEvent(network_event event)
|
|
{
|
|
for (uint32_t i = 0; i < _cbEventList.size(); i++) {
|
|
NetworkEventCbList_t entry = _cbEventList[i];
|
|
if (entry.cb) {
|
|
if (entry.event == event || entry.event == network_event::NETWORK_EVENT_MAX) {
|
|
entry.cb(event);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void NetworkSettingsClass::handleMDNS()
|
|
{
|
|
bool mdnsEnabled = Configuration.get().Mdns.Enabled;
|
|
|
|
if (lastMdnsEnabled == mdnsEnabled) {
|
|
return;
|
|
}
|
|
|
|
lastMdnsEnabled = mdnsEnabled;
|
|
|
|
MDNS.end();
|
|
|
|
if (!mdnsEnabled) {
|
|
return;
|
|
}
|
|
|
|
if (MDNS.begin(getHostname())) {
|
|
MessageOutput.print("MDNS responder starting...");
|
|
|
|
MDNS.addService("http", "tcp", 80);
|
|
MDNS.addService("opendtu", "tcp", 80);
|
|
MDNS.addServiceTxt("opendtu", "tcp", "git_hash", AUTO_GIT_HASH);
|
|
|
|
MessageOutput.println("done");
|
|
} else {
|
|
MessageOutput.println("Error setting up MDNS responder!");
|
|
}
|
|
}
|
|
|
|
void NetworkSettingsClass::setupMode()
|
|
{
|
|
if (adminEnabled) {
|
|
WiFi.mode(WIFI_AP_STA);
|
|
String ssidString = getApName();
|
|
WiFi.softAPConfig(apIp, apIp, apNetmask);
|
|
WiFi.softAP((const char*)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);
|
|
}
|
|
}
|
|
|
|
if (PinMapping.isValidEthConfig()) {
|
|
PinMapping_t& pin = PinMapping.get();
|
|
ETH.begin(pin.eth_phy_addr, pin.eth_power, pin.eth_mdc, pin.eth_mdio, pin.eth_type, pin.eth_clk_mode);
|
|
}
|
|
}
|
|
|
|
void NetworkSettingsClass::enableAdminMode()
|
|
{
|
|
adminEnabled = true;
|
|
adminTimeoutCounter = 0;
|
|
adminTimeoutCounterMax = Configuration.get().WiFi.ApTimeout * 60;
|
|
setupMode();
|
|
}
|
|
|
|
String NetworkSettingsClass::getApName()
|
|
{
|
|
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.println("Switch to Ethernet mode");
|
|
_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.println("Switch to WiFi mode");
|
|
_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: %d / %d\r\n", adminTimeoutCounter, adminTimeoutCounterMax);
|
|
}
|
|
}
|
|
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) {
|
|
adminEnabled = false;
|
|
MessageOutput.println("Admin mode disabled");
|
|
setupMode();
|
|
}
|
|
// 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 && !forceDisconnection) {
|
|
MessageOutput.print("Disable search for AP... ");
|
|
WiFi.mode(WIFI_AP);
|
|
MessageOutput.println("done");
|
|
connectRedoTimer = 0;
|
|
forceDisconnection = true;
|
|
}
|
|
if (connectRedoTimer > WIFI_RECONNECT_REDO_TIMEOUT && forceDisconnection) {
|
|
MessageOutput.print("Enable search for AP... ");
|
|
WiFi.mode(WIFI_AP_STA);
|
|
MessageOutput.println("done");
|
|
applyConfig();
|
|
connectTimeoutTimer = 0;
|
|
forceDisconnection = false;
|
|
}
|
|
}
|
|
}
|
|
if (dnsServerStatus) {
|
|
dnsServer->processNextRequest();
|
|
}
|
|
|
|
handleMDNS();
|
|
}
|
|
|
|
void NetworkSettingsClass::applyConfig()
|
|
{
|
|
setHostname();
|
|
if (!strcmp(Configuration.get().WiFi.Ssid, "")) {
|
|
return;
|
|
}
|
|
MessageOutput.print("Configuring WiFi STA using ");
|
|
if (strcmp(WiFi.SSID().c_str(), Configuration.get().WiFi.Ssid) || strcmp(WiFi.psk().c_str(), Configuration.get().WiFi.Password)) {
|
|
MessageOutput.print("new credentials... ");
|
|
WiFi.begin(
|
|
Configuration.get().WiFi.Ssid,
|
|
Configuration.get().WiFi.Password);
|
|
} else {
|
|
MessageOutput.print("existing credentials... ");
|
|
WiFi.begin();
|
|
}
|
|
MessageOutput.println("done");
|
|
setStaticIp();
|
|
}
|
|
|
|
void NetworkSettingsClass::setHostname()
|
|
{
|
|
MessageOutput.print("Setting Hostname... ");
|
|
if (_networkMode == network_mode::WiFi) {
|
|
if (WiFi.hostname(getHostname())) {
|
|
MessageOutput.println("done");
|
|
} else {
|
|
MessageOutput.println("failed");
|
|
}
|
|
|
|
// 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) {
|
|
if (ETH.setHostname(getHostname().c_str())) {
|
|
MessageOutput.println("done");
|
|
} else {
|
|
MessageOutput.println("failed");
|
|
}
|
|
}
|
|
}
|
|
|
|
void NetworkSettingsClass::setStaticIp()
|
|
{
|
|
if (_networkMode == network_mode::WiFi) {
|
|
if (Configuration.get().WiFi.Dhcp) {
|
|
MessageOutput.print("Configuring WiFi STA DHCP IP... ");
|
|
WiFi.config(INADDR_NONE, INADDR_NONE, INADDR_NONE);
|
|
MessageOutput.println("done");
|
|
} else {
|
|
MessageOutput.print("Configuring WiFi STA static IP... ");
|
|
WiFi.config(
|
|
IPAddress(Configuration.get().WiFi.Ip),
|
|
IPAddress(Configuration.get().WiFi.Gateway),
|
|
IPAddress(Configuration.get().WiFi.Netmask),
|
|
IPAddress(Configuration.get().WiFi.Dns1),
|
|
IPAddress(Configuration.get().WiFi.Dns2));
|
|
MessageOutput.println("done");
|
|
}
|
|
} else if (_networkMode == network_mode::Ethernet) {
|
|
if (Configuration.get().WiFi.Ssid) {
|
|
MessageOutput.print("Configuring Ethernet DHCP IP... ");
|
|
ETH.config(INADDR_NONE, INADDR_NONE, INADDR_NONE, INADDR_NONE);
|
|
MessageOutput.println("done");
|
|
} else {
|
|
MessageOutput.print("Configuring Ethernet static IP... ");
|
|
ETH.config(
|
|
IPAddress(Configuration.get().WiFi.Ip),
|
|
IPAddress(Configuration.get().WiFi.Gateway),
|
|
IPAddress(Configuration.get().WiFi.Netmask),
|
|
IPAddress(Configuration.get().WiFi.Dns1),
|
|
IPAddress(Configuration.get().WiFi.Dns2));
|
|
MessageOutput.println("done");
|
|
}
|
|
}
|
|
}
|
|
|
|
IPAddress NetworkSettingsClass::localIP()
|
|
{
|
|
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()
|
|
{
|
|
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()
|
|
{
|
|
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(uint8_t dns_no)
|
|
{
|
|
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()
|
|
{
|
|
switch (_networkMode) {
|
|
case network_mode::Ethernet:
|
|
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;
|
|
|
|
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()
|
|
{
|
|
return WiFi.localIP()[0] != 0 || ETH.localIP()[0] != 0;
|
|
}
|
|
|
|
network_mode NetworkSettingsClass::NetworkMode()
|
|
{
|
|
return _networkMode;
|
|
}
|
|
|
|
NetworkSettingsClass NetworkSettings;
|