// 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 #include NetworkSettingsClass::NetworkSettingsClass() : apIp(192, 168, 4, 1) , apNetmask(255, 255, 255, 0) { dnsServer.reset(new DNSServer()); } void NetworkSettingsClass::init(Scheduler* scheduler) { 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(); scheduler->addTask(_loopTask); _loopTask.setCallback(std::bind(&NetworkSettingsClass::loop, this)); _loopTask.setIterations(TASK_FOREVER); _loopTask.enable(); } 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(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;