/* MeshBackendBase Copyright (c) 2015 Julian Fell and 2019 Anders Löfgren. All rights reserved. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "MeshBackendBase.h" #include "TypeConversionFunctions.h" #include "MutexTracker.h" #include namespace { namespace TypeCast = MeshTypeConversionFunctions; MeshBackendBase *apController = nullptr; } std::shared_ptr MeshBackendBase::_scanMutex = std::make_shared(false); MeshBackendBase::MeshBackendBase(const requestHandlerType requestHandler, const responseHandlerType responseHandler, const networkFilterType networkFilter, const MeshBackendType classType) { setRequestHandler(requestHandler); setResponseHandler(responseHandler); setNetworkFilter(networkFilter); setClassType(classType); } MeshBackendBase::~MeshBackendBase() { deactivateControlledAP(); } void MeshBackendBase::setClassType(const MeshBackendType classType) { _classType = classType; } MeshBackendType MeshBackendBase::getClassType() const {return _classType;} void MeshBackendBase::setVerboseModeState(const bool enabled) { _conditionalPrinter.setVerboseModeState(enabled); } bool MeshBackendBase::verboseMode() const { return _conditionalPrinter.verboseMode(); } void MeshBackendBase::verboseModePrint(const String &stringToPrint, const bool newline) const { _conditionalPrinter.verboseModePrint(stringToPrint, newline); } void MeshBackendBase::setPrintWarnings(const bool printEnabled) { ConditionalPrinter::setPrintWarnings(printEnabled); } bool MeshBackendBase::printWarnings() {return ConditionalPrinter::printWarnings();} void MeshBackendBase::warningPrint(const String &stringToPrint, const bool newline) { ConditionalPrinter::warningPrint(stringToPrint, newline); } void MeshBackendBase::activateAP() { // Deactivate active AP to avoid two servers using the same port, which can lead to crashes. deactivateAP(); activateAPHook(); WiFi.mode(WIFI_AP_STA); apController = this; } void MeshBackendBase::activateAPHook() { WiFi.softAP( getSSID().c_str(), getMeshPassword().c_str(), getWiFiChannel(), getAPHidden() ); // Note that a maximum of 8 TCP/IP stations can be connected at a time to each AP, max 4 by default. } void MeshBackendBase::deactivateAP() { if(MeshBackendBase *currentAPController = MeshBackendBase::getAPController()) currentAPController->deactivateControlledAP(); } bool MeshBackendBase::deactivateControlledAP() { if(isAPController()) { deactivateAPHook(); WiFi.softAPdisconnect(); WiFi.mode(WIFI_STA); // Since there is no active AP controller now, make the apController variable point to nothing. apController = nullptr; return true; } return false; } void MeshBackendBase::deactivateAPHook() { } void MeshBackendBase::restartAP() { deactivateAP(); yield(); activateAP(); yield(); } MeshBackendBase *MeshBackendBase::getAPController() { return apController; } bool MeshBackendBase::isAPController() const { return (this == getAPController()); } void MeshBackendBase::setWiFiChannel(const uint8 newWiFiChannel) { wifi_country_t wifiCountry; wifi_get_country(&wifiCountry); // Note: Should return 0 on success and -1 on failure, but always seems to return 1. Possibly broken API. Channels 1 to 13 are the default limits. assert(wifiCountry.schan <= newWiFiChannel && newWiFiChannel <= wifiCountry.schan + wifiCountry.nchan - 1); _meshWiFiChannel = newWiFiChannel; // WiFi.channel() will change if this node connects to an AP with another channel, // so there is no guarantee we are using _meshWiFiChannel. // Also, we cannot change the WiFi channel while we are still connected to the other AP. if(WiFi.channel() != getWiFiChannel() && WiFi.status() != WL_CONNECTED) { // Apply changes to active AP. if(isAPController()) restartAP(); } } uint8 MeshBackendBase::getWiFiChannel() const { return _meshWiFiChannel; } void MeshBackendBase::setSSID(const String &newSSIDPrefix, const String &newSSIDRoot, const String &newSSIDSuffix) { if(!newSSIDPrefix.isEmpty()) _SSIDPrefix = newSSIDPrefix; if(!newSSIDRoot.isEmpty()) _SSIDRoot = newSSIDRoot; if(!newSSIDSuffix.isEmpty()) _SSIDSuffix = newSSIDSuffix; String newSSID = _SSIDPrefix + _SSIDRoot + _SSIDSuffix; assert(newSSID.length() <= 31); if(getSSID() != newSSID) { _SSID = newSSID; // Apply SSID changes to active AP. if(isAPController()) restartAP(); } } String MeshBackendBase::getSSID() const {return _SSID;} void MeshBackendBase::setSSIDPrefix(const String &newSSIDPrefix) { setSSID(newSSIDPrefix); } String MeshBackendBase::getSSIDPrefix() const {return _SSIDPrefix;} void MeshBackendBase::setSSIDRoot(const String &newSSIDRoot) { setSSID(emptyString, newSSIDRoot); } String MeshBackendBase::getSSIDRoot() const {return _SSIDRoot;} void MeshBackendBase::setSSIDSuffix(const String &newSSIDSuffix) { setSSID(emptyString, emptyString, newSSIDSuffix); } String MeshBackendBase::getSSIDSuffix() const {return _SSIDSuffix;} void MeshBackendBase::setMeshName(const String &newMeshName) { setSSIDPrefix(newMeshName); } String MeshBackendBase::getMeshName() const {return getSSIDPrefix();} void MeshBackendBase::setNodeID(const String &newNodeID) { setSSIDSuffix(newNodeID); } String MeshBackendBase::getNodeID() const {return getSSIDSuffix();} void MeshBackendBase::setMeshPassword(const String &newMeshPassword) { assert(8 <= newMeshPassword.length() && newMeshPassword.length() <= 64); // Limited by the ESP8266 API. assert(newMeshPassword.indexOf('"') == -1); // " is not allowed in passwords to allow for easier JSON parsing and predictable password length (no need for extra escape characters). _meshPassword = newMeshPassword; // Apply changes to active AP. if(isAPController()) restartAP(); } String MeshBackendBase::getMeshPassword() const {return _meshPassword;} void MeshBackendBase::setMessage(const String &newMessage) {_message = newMessage;} String MeshBackendBase::getMessage() const {return _message;} void MeshBackendBase::setRequestHandler(const MeshBackendBase::requestHandlerType requestHandler) {_requestHandler = requestHandler;} MeshBackendBase::requestHandlerType MeshBackendBase::getRequestHandler() const {return _requestHandler;} void MeshBackendBase::setResponseHandler(const MeshBackendBase::responseHandlerType responseHandler) {_responseHandler = responseHandler;} MeshBackendBase::responseHandlerType MeshBackendBase::getResponseHandler() const {return _responseHandler;} void MeshBackendBase::setNetworkFilter(const MeshBackendBase::networkFilterType networkFilter) {_networkFilter = networkFilter;} MeshBackendBase::networkFilterType MeshBackendBase::getNetworkFilter() const {return _networkFilter;} void MeshBackendBase::setTransmissionOutcomesUpdateHook(const MeshBackendBase::transmissionOutcomesUpdateHookType transmissionOutcomesUpdateHook) {_transmissionOutcomesUpdateHook = transmissionOutcomesUpdateHook;} MeshBackendBase::transmissionOutcomesUpdateHookType MeshBackendBase::getTransmissionOutcomesUpdateHook() const {return _transmissionOutcomesUpdateHook;} void MeshBackendBase::setScanHidden(const bool scanHidden) { _scanHidden = scanHidden; } bool MeshBackendBase::getScanHidden() const {return _scanHidden;} void MeshBackendBase::setAPHidden(const bool apHidden) { if(getAPHidden() != apHidden) { _apHidden = apHidden; // Apply changes to active AP. if(isAPController()) restartAP(); } } bool MeshBackendBase::getAPHidden() const {return _apHidden;} ConditionalPrinter *MeshBackendBase::getConditionalPrinter() {return &_conditionalPrinter;} const ConditionalPrinter *MeshBackendBase::getConditionalPrinterConst() const {return &_conditionalPrinter;} bool MeshBackendBase::latestTransmissionSuccessfulBase(const std::vector &latestTransmissionOutcomes) { if(latestTransmissionOutcomes.empty()) return false; for(const TransmissionOutcome &transmissionOutcome : latestTransmissionOutcomes) if(transmissionOutcome.transmissionStatus() != TransmissionStatusType::TRANSMISSION_COMPLETE) return false; return true; } void MeshBackendBase::scanForNetworks(const bool scanAllWiFiChannels) { MutexTracker mutexTracker(_scanMutex); if(!mutexTracker.mutexCaptured()) { assert(false && String(F("ERROR! Scan already in progress. Don't call scanForNetworks from callbacks as this may corrupt program state! Aborting."))); return; } verboseModePrint(F("Scanning... "), false); /* Scan for APs */ // If scanAllWiFiChannels is true, scanning will cause the WiFi radio to cycle through all WiFi channels. // This means existing WiFi connections are likely to break or work poorly if done frequently. int n = 0; if(scanAllWiFiChannels) { n = WiFi.scanNetworks(false, getScanHidden()); } else { // Scan function argument overview: scanNetworks(bool async = false, bool show_hidden = false, uint8 channel = 0, uint8* ssid = NULL) n = WiFi.scanNetworks(false, getScanHidden(), getWiFiChannel()); } getNetworkFilter()(n, *this); // Update the connectionQueue. } void MeshBackendBase::printAPInfo(const NetworkInfoBase &apNetworkInfo) { String mainNetworkIdentifier = apNetworkInfo.SSID(); if(mainNetworkIdentifier == NetworkInfoBase::defaultSSID) // If SSID not provided, use BSSID instead { mainNetworkIdentifier = TypeCast::macToString(apNetworkInfo.BSSID()); } verboseModePrint(String(F("AP acquired: ")) + mainNetworkIdentifier + String(F(", Ch:")) + String(apNetworkInfo.wifiChannel()) + ' ', false); if(apNetworkInfo.RSSI() != NetworkInfoBase::defaultRSSI) { verboseModePrint(String('(') + String(apNetworkInfo.RSSI()) + String(F("dBm) ")) + (apNetworkInfo.encryptionType() == ENC_TYPE_NONE ? String(F("open")) : emptyString), false); } verboseModePrint(F("... "), false); }