diff --git a/libraries/ESP8266WiFiMesh/README.md b/libraries/ESP8266WiFiMesh/README.md index 5876b0872a..8d955220ca 100644 --- a/libraries/ESP8266WiFiMesh/README.md +++ b/libraries/ESP8266WiFiMesh/README.md @@ -50,7 +50,7 @@ ESP8266WiFiMesh(requestHandlerType requestHandler, responseHandlerType responseH * This library can use static IP:s for the nodes to speed up connection times. To enable this, use the `setStaticIP` method after calling the `begin` method, as in the included example. Ensure that nodes connecting to the same AP have distinct static IP:s. Node IP:s need to be at the same subnet as the server gateway (192.168.4 for this library by default). It may also be worth noting that station gateway IP must match the IP for the server on the nodes, though this is the default setting for the library. - At the moment static IP is a global setting, meaning that all ESP8266WiFiMesh instances on a single ESP8266 share the same static IP settings. + At the moment static IP is a global setting, meaning that all ESP8266WiFiMesh instances on the same ESP8266 share the same static IP settings. * When Arduino core for ESP8266 version 2.4.2 or higher is used, there are optimizations available for WiFi scans and static IP use to reduce the time it takes for nodes to connect to each other. These optimizations are enabled by default. To take advantage of the static IP optimizations you also need to use lwIP2. The lwIP version can be changed in the Tools menu of Arduino IDE. @@ -74,7 +74,7 @@ General Information * This library uses the standard Arduino core for ESP8266 WiFi functions. Therefore, other code that also uses these WiFi functions may cause conflicts with the library, resulting in strange behaviour. -* A maximum of 5 stations can be connected at a time to each AP. +* By default, a maximum of 4 stations can be connected at a time to each AP. This can be changed to a value in the range 0 to 8 via the `setMaxAPStations` method. Once the max number has been reached, any other station that wants to connect will be forced to wait until an already connected station disconnects. The more stations that are connected, the more memory is required. * Unlike `WiFi.mode(WIFI_AP)`, the `WiFi.mode(WIFI_AP_STA)` which is used in this library allows nodes to stay connected to an AP they connect to while in STA mode, at the same time as they can receive connections from other stations. Nodes cannot send data to an AP while in STA_AP mode though, that requires STA mode. Switching to STA mode will sometimes disconnect stations connected to the node AP (though they can request a reconnect even while the previous AP node is in STA mode). diff --git a/libraries/ESP8266WiFiMesh/examples/HelloMesh/HelloMesh.ino b/libraries/ESP8266WiFiMesh/examples/HelloMesh/HelloMesh.ino index f5347743ab..cdd3e6d5c9 100644 --- a/libraries/ESP8266WiFiMesh/examples/HelloMesh/HelloMesh.ino +++ b/libraries/ESP8266WiFiMesh/examples/HelloMesh/HelloMesh.ino @@ -1,7 +1,21 @@ #include #include +#include +#include -String exampleMeshName("MeshNode_"); +/** + NOTE: Although we could define the strings below as normal String variables, + here we are using PROGMEM combined with the FPSTR() macro (and also just the F() macro further down in the file). + The reason is that this approach will place the strings in flash memory which will help save RAM during program execution. + Reading strings from flash will be slower than reading them from RAM, + but this will be a negligible difference when printing them to Serial. + + More on F(), FPSTR() and PROGMEM: + https://github.com/esp8266/Arduino/issues/1143 + https://arduino-esp8266.readthedocs.io/en/latest/PROGMEM.html +*/ +const char exampleMeshName[] PROGMEM = "MeshNode_"; +const char exampleWiFiPassword[] PROGMEM = "ChangeThisWiFiPassword_TODO"; unsigned int requestNumber = 0; unsigned int responseNumber = 0; @@ -11,7 +25,7 @@ transmission_status_t manageResponse(const String &response, ESP8266WiFiMesh &me void networkFilter(int numberOfNetworks, ESP8266WiFiMesh &meshInstance); /* Create the mesh node object */ -ESP8266WiFiMesh meshNode = ESP8266WiFiMesh(manageRequest, manageResponse, networkFilter, "ChangeThisWiFiPassword_TODO", exampleMeshName, "", true); +ESP8266WiFiMesh meshNode = ESP8266WiFiMesh(manageRequest, manageResponse, networkFilter, FPSTR(exampleWiFiPassword), FPSTR(exampleMeshName), "", true); /** Callback for when other nodes send you a request @@ -21,6 +35,11 @@ ESP8266WiFiMesh meshNode = ESP8266WiFiMesh(manageRequest, manageResponse, networ @returns The string to send back to the other node */ String manageRequest(const String &request, ESP8266WiFiMesh &meshInstance) { + // We do not store strings in flash (via F()) in this function. + // The reason is that the other node will be waiting for our response, + // so keeping the strings in RAM will give a (small) improvement in response time. + // Of course, it is advised to adjust this approach based on RAM requirements. + /* Print out received message */ Serial.print("Request received: "); Serial.println(request); @@ -29,28 +48,6 @@ String manageRequest(const String &request, ESP8266WiFiMesh &meshInstance) { return ("Hello world response #" + String(responseNumber++) + " from " + meshInstance.getMeshName() + meshInstance.getNodeID() + "."); } -/** - Callback used to decide which networks to connect to once a WiFi scan has been completed. - - @param numberOfNetworks The number of networks found in the WiFi scan. - @param meshInstance The ESP8266WiFiMesh instance that called the function. -*/ -void networkFilter(int numberOfNetworks, ESP8266WiFiMesh &meshInstance) { - for (int i = 0; i < numberOfNetworks; ++i) { - String currentSSID = WiFi.SSID(i); - int meshNameIndex = currentSSID.indexOf(meshInstance.getMeshName()); - - /* Connect to any _suitable_ APs which contain meshInstance.getMeshName() */ - if (meshNameIndex >= 0) { - uint64_t targetNodeID = ESP8266WiFiMesh::stringToUint64(currentSSID.substring(meshNameIndex + meshInstance.getMeshName().length())); - - if (targetNodeID < ESP8266WiFiMesh::stringToUint64(meshInstance.getNodeID())) { - ESP8266WiFiMesh::connectionQueue.push_back(NetworkInfo(i)); - } - } - } -} - /** Callback for when you get a response from other nodes @@ -62,18 +59,41 @@ transmission_status_t manageResponse(const String &response, ESP8266WiFiMesh &me transmission_status_t statusCode = TS_TRANSMISSION_COMPLETE; /* Print out received message */ - Serial.print("Request sent: "); + Serial.print(F("Request sent: ")); Serial.println(meshInstance.getMessage()); - Serial.print("Response received: "); + Serial.print(F("Response received: ")); Serial.println(response); // Our last request got a response, so time to create a new request. - meshInstance.setMessage("Hello world request #" + String(++requestNumber) + " from " + meshInstance.getMeshName() + meshInstance.getNodeID() + "."); + meshInstance.setMessage(String(F("Hello world request #")) + String(++requestNumber) + String(F(" from ")) + + meshInstance.getMeshName() + meshInstance.getNodeID() + String(F("."))); // (void)meshInstance; // This is useful to remove a "unused parameter" compiler warning. Does nothing else. return statusCode; } +/** + Callback used to decide which networks to connect to once a WiFi scan has been completed. + + @param numberOfNetworks The number of networks found in the WiFi scan. + @param meshInstance The ESP8266WiFiMesh instance that called the function. +*/ +void networkFilter(int numberOfNetworks, ESP8266WiFiMesh &meshInstance) { + for (int networkIndex = 0; networkIndex < numberOfNetworks; ++networkIndex) { + String currentSSID = WiFi.SSID(networkIndex); + int meshNameIndex = currentSSID.indexOf(meshInstance.getMeshName()); + + /* Connect to any _suitable_ APs which contain meshInstance.getMeshName() */ + if (meshNameIndex >= 0) { + uint64_t targetNodeID = stringToUint64(currentSSID.substring(meshNameIndex + meshInstance.getMeshName().length())); + + if (targetNodeID < stringToUint64(meshInstance.getNodeID())) { + ESP8266WiFiMesh::connectionQueue.push_back(NetworkInfo(networkIndex)); + } + } + } +} + void setup() { // Prevents the flash memory from being worn out, see: https://github.com/esp8266/Arduino/issues/1054 . // This will however delay node WiFi start-up by about 700 ms. The delay is 900 ms if we otherwise would have stored the WiFi network we want to connect to. @@ -84,15 +104,19 @@ void setup() { //yield(); // Use this if you don't want to wait for Serial. + // The WiFi.disconnect() ensures that the WiFi is working correctly. If this is not done before receiving WiFi connections, + // those WiFi connections will take a long time to make or sometimes will not work at all. + WiFi.disconnect(); + Serial.println(); Serial.println(); - Serial.println("Note that this library can use static IP:s for the nodes to speed up connection times.\n" - "Use the setStaticIP method as shown in this example to enable this.\n" - "Ensure that nodes connecting to the same AP have distinct static IP:s.\n" - "Also, remember to change the default mesh network password!\n\n"); + Serial.println(F("Note that this library can use static IP:s for the nodes to speed up connection times.\n" + "Use the setStaticIP method as shown in this example to enable this.\n" + "Ensure that nodes connecting to the same AP have distinct static IP:s.\n" + "Also, remember to change the default mesh network password!\n\n")); - Serial.println("Setting up mesh node..."); + Serial.println(F("Setting up mesh node...")); /* Initialise the mesh node */ meshNode.begin(); @@ -104,22 +128,29 @@ int32_t timeOfLastScan = -10000; void loop() { if (millis() - timeOfLastScan > 3000 // Give other nodes some time to connect between data transfers. || (WiFi.status() != WL_CONNECTED && millis() - timeOfLastScan > 2000)) { // Scan for networks with two second intervals when not already connected. - String request = "Hello world request #" + String(requestNumber) + " from " + meshNode.getMeshName() + meshNode.getNodeID() + "."; + String request = String(F("Hello world request #")) + String(requestNumber) + String(F(" from ")) + meshNode.getMeshName() + meshNode.getNodeID() + String(F(".")); meshNode.attemptTransmission(request, false); timeOfLastScan = millis(); + // One way to check how attemptTransmission worked out + if (ESP8266WiFiMesh::latestTransmissionSuccessful()) { + Serial.println(F("Transmission successful.")); + } + + // Another way to check how attemptTransmission worked out if (ESP8266WiFiMesh::latestTransmissionOutcomes.empty()) { - Serial.println("No mesh AP found."); + Serial.println(F("No mesh AP found.")); } else { for (TransmissionResult &transmissionResult : ESP8266WiFiMesh::latestTransmissionOutcomes) { if (transmissionResult.transmissionStatus == TS_TRANSMISSION_FAILED) { - Serial.println("Transmission failed to mesh AP " + transmissionResult.SSID); + Serial.println(String(F("Transmission failed to mesh AP ")) + transmissionResult.SSID); } else if (transmissionResult.transmissionStatus == TS_CONNECTION_FAILED) { - Serial.println("Connection failed to mesh AP " + transmissionResult.SSID); + Serial.println(String(F("Connection failed to mesh AP ")) + transmissionResult.SSID); } else if (transmissionResult.transmissionStatus == TS_TRANSMISSION_COMPLETE) { // No need to do anything, transmission was successful. } else { - Serial.println("Invalid transmission status for " + transmissionResult.SSID + "!"); + Serial.println(String(F("Invalid transmission status for ")) + transmissionResult.SSID + String(F("!"))); + assert(F("Invalid transmission status returned from responseHandler!") && false); } } } diff --git a/libraries/ESP8266WiFiMesh/keywords.txt b/libraries/ESP8266WiFiMesh/keywords.txt index fdb0caf08e..2ea9f96aad 100644 --- a/libraries/ESP8266WiFiMesh/keywords.txt +++ b/libraries/ESP8266WiFiMesh/keywords.txt @@ -23,21 +23,23 @@ transmission_status_t KEYWORD1 connectionQueue KEYWORD2 latestTransmissionOutcomes KEYWORD2 +latestTransmissionSuccessful KEYWORD2 begin KEYWORD2 activateAP KEYWORD2 deactivateAP KEYWORD2 restartAP KEYWORD2 getAPController KEYWORD2 isAPController KEYWORD2 -getWiFiChannel KEYWORD2 setWiFiChannel KEYWORD2 -getMeshName KEYWORD2 +getWiFiChannel KEYWORD2 setMeshName KEYWORD2 -getNodeID KEYWORD2 +getMeshName KEYWORD2 setNodeID KEYWORD2 +getNodeID KEYWORD2 setSSID KEYWORD2 -getMessage KEYWORD2 +getSSID KEYWORD2 setMessage KEYWORD2 +getMessage KEYWORD2 attemptTransmission KEYWORD2 acceptRequest KEYWORD2 setStaticIP KEYWORD2 @@ -45,8 +47,24 @@ getStaticIP KEYWORD2 disableStaticIP->KEYWORD2 uint64ToString KEYWORD2 stringToUint64 KEYWORD2 -getNetworkFilter KEYWORD2 +setRequestHandler KEYWORD2 +getRequestHandler KEYWORD2 +setResponseHandler KEYWORD2 +getResponseHandler KEYWORD2 setNetworkFilter KEYWORD2 +getNetworkFilter KEYWORD2 +setScanHidden KEYWORD2 +getScanHidden KEYWORD2 +setAPHidden KEYWORD2 +getAPHidden KEYWORD2 +setMaxAPStations KEYWORD2 +getMaxAPStations KEYWORD2 +setConnectionAttemptTimeout KEYWORD2 +getConnectionAttemptTimeout KEYWORD2 +setStationModeTimeout KEYWORD2 +getStationModeTimeout KEYWORD2 +setAPModeTimeout KEYWORD2 +getAPModeTimeout KEYWORD2 ####################################### # Constants (LITERAL1) diff --git a/libraries/ESP8266WiFiMesh/library.properties b/libraries/ESP8266WiFiMesh/library.properties index 70dff32590..ddfea96a58 100644 --- a/libraries/ESP8266WiFiMesh/library.properties +++ b/libraries/ESP8266WiFiMesh/library.properties @@ -1,5 +1,5 @@ name=ESP8266WiFiMesh -version=2.0 +version=2.1 author=Julian Fell maintainer=Anders Löfgren sentence=Mesh network library diff --git a/libraries/ESP8266WiFiMesh/src/ESP8266WiFiMesh.cpp b/libraries/ESP8266WiFiMesh/src/ESP8266WiFiMesh.cpp index b617be1f00..fead562e6a 100644 --- a/libraries/ESP8266WiFiMesh/src/ESP8266WiFiMesh.cpp +++ b/libraries/ESP8266WiFiMesh/src/ESP8266WiFiMesh.cpp @@ -24,6 +24,7 @@ #include #include "ESP8266WiFiMesh.h" +#include "TypeConversionFunctions.h" #define SERVER_IP_ADDR "192.168.4.1" @@ -44,15 +45,12 @@ std::vector ESP8266WiFiMesh::latestTransmissionOutcomes = {} ESP8266WiFiMesh::~ESP8266WiFiMesh() { - if(isAPController()) - { - apController = nullptr; - } + deactivateAP(); } ESP8266WiFiMesh::ESP8266WiFiMesh(ESP8266WiFiMesh::requestHandlerType requestHandler, ESP8266WiFiMesh::responseHandlerType responseHandler, ESP8266WiFiMesh::networkFilterType networkFilter, const String &meshPassword, const String &meshName, - const String &nodeID, bool verboseMode, uint8 meshWiFiChannel, int serverPort) + const String &nodeID, bool verboseMode, uint8 meshWiFiChannel, uint16_t serverPort) : _server(serverPort), _lwipVersion{0, 0, 0} { storeLwipVersion(); @@ -73,12 +71,17 @@ void ESP8266WiFiMesh::updateNetworkNames(const String &newMeshName, const String _meshName = newMeshName; if(newNodeID != "") _nodeID = newNodeID; - - _SSID = _meshName + _nodeID; - // Apply SSID changes to active AP. - if(isAPController()) - restartAP(); + String newSSID = _meshName + _nodeID; + + if(_SSID != newSSID) + { + _SSID = newSSID; + + // Apply SSID changes to active AP. + if(isAPController()) + restartAP(); + } } void ESP8266WiFiMesh::begin() @@ -93,16 +96,17 @@ void ESP8266WiFiMesh::begin() else { //////////////////////////// TODO: REMOVE IN 2.5.0//////////////////////////// - WiFi.mode(WIFI_AP_STA); + if(!ESP8266WiFiMesh::getAPController()) // If there is no active AP controller + WiFi.mode(WIFI_STA); // WIFI_AP_STA mode automatically sets up an AP, so we can't use that as default. #ifdef ENABLE_STATIC_IP_OPTIMIZATION if(atLeastLwipVersion(lwipVersion203Signature)) { - verboseModePrint("lwIP version is at least 2.0.3. Static ip optimizations enabled.\n"); + verboseModePrint(F("lwIP version is at least 2.0.3. Static ip optimizations enabled.\n")); } else { - verboseModePrint("lwIP version is less than 2.0.3. Static ip optimizations DISABLED.\n"); + verboseModePrint(F("lwIP version is less than 2.0.3. Static ip optimizations DISABLED.\n")); } #endif } @@ -139,8 +143,11 @@ void ESP8266WiFiMesh::activateAP() // Deactivate active AP to avoid two servers using the same port, which can lead to crashes. if(ESP8266WiFiMesh *currentAPController = ESP8266WiFiMesh::getAPController()) currentAPController->deactivateAP(); - - WiFi.softAP( _SSID.c_str(), _meshPassword.c_str(), _meshWiFiChannel ); // Note that a maximum of 5 stations can be connected at a time to each AP + + WiFi.softAP( _SSID.c_str(), _meshPassword.c_str(), _meshWiFiChannel, _apHidden, _maxAPStations ); // Note that a maximum of 8 stations can be connected at a time to each AP + WiFi.mode(WIFI_AP_STA); + + _server = WiFiServer(_serverPort); // Fixes an occasional crash bug that occurs when using the copy constructor to duplicate the AP controller. _server.begin(); // Actually calls _server.stop()/_server.close() first. apController = this; @@ -152,6 +159,7 @@ void ESP8266WiFiMesh::deactivateAP() { _server.stop(); WiFi.softAPdisconnect(); + WiFi.mode(WIFI_STA); // Since there is no active AP controller now, make the apController variable point to nothing. apController = nullptr; @@ -176,46 +184,130 @@ bool ESP8266WiFiMesh::isAPController() return (this == apController); } -uint8 ESP8266WiFiMesh::getWiFiChannel() -{ - return _meshWiFiChannel; -} - void ESP8266WiFiMesh::setWiFiChannel(uint8 newWiFiChannel) { assert(1 <= newWiFiChannel && newWiFiChannel <= 13); _meshWiFiChannel = newWiFiChannel; - // Apply changes to active AP. - if(isAPController()) - restartAP(); + // 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() != _meshWiFiChannel && WiFi.status() != WL_CONNECTED) + { + // Apply changes to active AP. + if(isAPController()) + restartAP(); + } } -String ESP8266WiFiMesh::getMeshName() {return _meshName;} +uint8 ESP8266WiFiMesh::getWiFiChannel() +{ + return _meshWiFiChannel; +} void ESP8266WiFiMesh::setMeshName(const String &newMeshName) { updateNetworkNames(newMeshName); } -String ESP8266WiFiMesh::getNodeID() {return _nodeID;} +String ESP8266WiFiMesh::getMeshName() {return _meshName;} void ESP8266WiFiMesh::setNodeID(const String &newNodeID) { updateNetworkNames("", newNodeID); } +String ESP8266WiFiMesh::getNodeID() {return _nodeID;} + void ESP8266WiFiMesh::setSSID(const String &newMeshName, const String &newNodeID) { updateNetworkNames(newMeshName, newNodeID); } -String ESP8266WiFiMesh::getMessage() {return _message;} +String ESP8266WiFiMesh::getSSID() {return _SSID;} + void ESP8266WiFiMesh::setMessage(const String &newMessage) {_message = newMessage;} +String ESP8266WiFiMesh::getMessage() {return _message;} + +void ESP8266WiFiMesh::setRequestHandler(ESP8266WiFiMesh::requestHandlerType requestHandler) {_requestHandler = requestHandler;} +ESP8266WiFiMesh::requestHandlerType ESP8266WiFiMesh::getRequestHandler() {return _requestHandler;} + +void ESP8266WiFiMesh::setResponseHandler(ESP8266WiFiMesh::responseHandlerType responseHandler) {_responseHandler = responseHandler;} +ESP8266WiFiMesh::responseHandlerType ESP8266WiFiMesh::getResponseHandler() {return _responseHandler;} -ESP8266WiFiMesh::networkFilterType ESP8266WiFiMesh::getNetworkFilter() {return _networkFilter;} void ESP8266WiFiMesh::setNetworkFilter(ESP8266WiFiMesh::networkFilterType networkFilter) {_networkFilter = networkFilter;} +ESP8266WiFiMesh::networkFilterType ESP8266WiFiMesh::getNetworkFilter() {return _networkFilter;} + +void ESP8266WiFiMesh::setScanHidden(bool scanHidden) +{ + _scanHidden = scanHidden; +} + +bool ESP8266WiFiMesh::getScanHidden() {return _scanHidden;} + +void ESP8266WiFiMesh::setAPHidden(bool apHidden) +{ + if(_apHidden != apHidden) + { + _apHidden = apHidden; + + // Apply changes to active AP. + if(isAPController()) + restartAP(); + } +} + +bool ESP8266WiFiMesh::getAPHidden() {return _apHidden;} + +void ESP8266WiFiMesh::setMaxAPStations(uint8_t maxAPStations) +{ + assert(maxAPStations <= 8); // Valid values are 0 to 8, but uint8_t is always at least 0. + + if(_maxAPStations != maxAPStations) + { + _maxAPStations = maxAPStations; + + // Apply changes to active AP. + if(isAPController()) + restartAP(); + } +} + +bool ESP8266WiFiMesh::getMaxAPStations() {return _maxAPStations;} + +void ESP8266WiFiMesh::setConnectionAttemptTimeout(int32_t connectionAttemptTimeoutMs) +{ + _connectionAttemptTimeoutMs = connectionAttemptTimeoutMs; +} + +int32_t ESP8266WiFiMesh::getConnectionAttemptTimeout() {return _connectionAttemptTimeoutMs;} + +void ESP8266WiFiMesh::setStationModeTimeout(int stationModeTimeoutMs) +{ + _stationModeTimeoutMs = stationModeTimeoutMs; +} + +int ESP8266WiFiMesh::getStationModeTimeout() {return _stationModeTimeoutMs;} + +void ESP8266WiFiMesh::setAPModeTimeout(uint32_t apModeTimeoutMs) +{ + _apModeTimeoutMs = apModeTimeoutMs; +} + +uint32_t ESP8266WiFiMesh::getAPModeTimeout() {return _apModeTimeoutMs;} + +bool ESP8266WiFiMesh::latestTransmissionSuccessful() +{ + if(ESP8266WiFiMesh::latestTransmissionOutcomes.empty()) + return false; + else + for(TransmissionResult &transmissionResult : ESP8266WiFiMesh::latestTransmissionOutcomes) + if(transmissionResult.transmissionStatus != TS_TRANSMISSION_COMPLETE) + return false; + + return true; +} /** * Disconnect completely from a network. @@ -234,16 +326,20 @@ void ESP8266WiFiMesh::fullStop(WiFiClient &currClient) * @returns: True if the client is ready, false otherwise. * */ -bool ESP8266WiFiMesh::waitForClientTransmission(WiFiClient &currClient, int maxWait) +bool ESP8266WiFiMesh::waitForClientTransmission(WiFiClient &currClient, uint32_t maxWait) { - int wait = maxWait; - while(currClient.connected() && !currClient.available() && wait--) - delay(3); + uint32_t connectionStartTime = millis(); + uint32_t waitingTime = millis() - connectionStartTime; + while(currClient.connected() && !currClient.available() && waitingTime < maxWait) + { + delay(1); + waitingTime = millis() - connectionStartTime; + } /* Return false if the client isn't ready to communicate */ if (WiFi.status() == WL_DISCONNECTED && !currClient.available()) { - verboseModePrint("Disconnected!"); + verboseModePrint(F("Disconnected!")); return false; } @@ -260,12 +356,12 @@ bool ESP8266WiFiMesh::waitForClientTransmission(WiFiClient &currClient, int maxW */ transmission_status_t ESP8266WiFiMesh::exchangeInfo(WiFiClient &currClient) { - verboseModePrint("Transmitting"); + verboseModePrint("Transmitting"); // Not storing strings in flash (via F()) to avoid performance impacts when using the string. currClient.print(getMessage() + "\r"); yield(); - if (!waitForClientTransmission(currClient, 1000)) + if (!waitForClientTransmission(currClient, _stationModeTimeoutMs)) { fullStop(currClient); return TS_CONNECTION_FAILED; @@ -273,7 +369,7 @@ transmission_status_t ESP8266WiFiMesh::exchangeInfo(WiFiClient &currClient) if (!currClient.available()) { - verboseModePrint("No response!"); + verboseModePrint(F("No response!")); return TS_TRANSMISSION_FAILED; // WiFi.status() != WL_DISCONNECTED so we do not want to use fullStop(currClient) here since that would force the node to scan for WiFi networks. } @@ -295,10 +391,11 @@ transmission_status_t ESP8266WiFiMesh::attemptDataTransfer() // Unlike WiFi.mode(WIFI_AP);, WiFi.mode(WIFI_AP_STA); allows us to stay connected to the AP we connected to in STA mode, at the same time as we can receive connections from other stations. // We cannot send data to the AP in STA_AP mode though, that requires STA mode. // Switching to STA mode will disconnect all stations connected to the node AP (though they can request a reconnect even while we are in STA mode). + WiFiMode_t storedWiFiMode = WiFi.getMode(); WiFi.mode(WIFI_STA); delay(1); transmission_status_t transmissionOutcome = attemptDataTransferKernel(); - WiFi.mode(WIFI_AP_STA); + WiFi.mode(storedWiFiMode); delay(1); return transmissionOutcome; @@ -312,19 +409,20 @@ transmission_status_t ESP8266WiFiMesh::attemptDataTransfer() transmission_status_t ESP8266WiFiMesh::attemptDataTransferKernel() { WiFiClient currClient; - + currClient.setTimeout(_stationModeTimeoutMs); + /* Connect to the node's server */ if (!currClient.connect(SERVER_IP_ADDR, _serverPort)) { fullStop(currClient); - verboseModePrint("Server unavailable"); + verboseModePrint(F("Server unavailable")); return TS_CONNECTION_FAILED; } transmission_status_t transmissionOutcome = exchangeInfo(currClient); if (transmissionOutcome <= 0) { - verboseModePrint("Transmission failed during exchangeInfo."); + verboseModePrint(F("Transmission failed during exchangeInfo.")); return transmissionOutcome; } @@ -361,36 +459,37 @@ transmission_status_t ESP8266WiFiMesh::connectToNode(const String &targetSSID, i if(atLeastLwipVersion(lwipVersion203Signature)) { // Can be used with Arduino core for ESP8266 version 2.4.2 or higher with lwIP2 enabled to keep static IP on even during network switches. + WiFiMode_t storedWiFiMode = WiFi.getMode(); WiFi.mode(WIFI_OFF); - WiFi.mode(WIFI_AP_STA); + WiFi.mode(storedWiFiMode); yield(); } else { // Disable static IP so that we can connect to other servers via DHCP (DHCP is slower but required for connecting to more than one server, it seems (possible bug?)). disableStaticIP(); - verboseModePrint("\nConnecting to a different network. Static IP deactivated to make this possible."); + verboseModePrint(F("\nConnecting to a different network. Static IP deactivated to make this possible.")); } #else // Disable static IP so that we can connect to other servers via DHCP (DHCP is slower but required for connecting to more than one server, it seems (possible bug?)). disableStaticIP(); - verboseModePrint("\nConnecting to a different network. Static IP deactivated to make this possible."); + verboseModePrint(F("\nConnecting to a different network. Static IP deactivated to make this possible.")); #endif } lastSSID = targetSSID; - verboseModePrint("Connecting... ", false); + verboseModePrint(F("Connecting... "), false); initiateConnectionToAP(targetSSID, targetChannel, targetBSSID); int connectionStartTime = millis(); int attemptNumber = 1; int waitingTime = millis() - connectionStartTime; - while((WiFi.status() == WL_DISCONNECTED) && waitingTime <= 10000) + while((WiFi.status() == WL_DISCONNECTED) && waitingTime <= _connectionAttemptTimeoutMs) { - if(waitingTime > attemptNumber * 10000) // 10000 can be lowered if you want to limit the time allowed for each connection attempt. + if(waitingTime > attemptNumber * _connectionAttemptTimeoutMs) // _connectionAttemptTimeoutMs can be replaced (lowered) if you want to limit the time allowed for each connection attempt. { - verboseModePrint("... ", false); + verboseModePrint(F("... "), false); WiFi.disconnect(); yield(); initiateConnectionToAP(targetSSID, targetChannel, targetBSSID); @@ -405,7 +504,7 @@ transmission_status_t ESP8266WiFiMesh::connectToNode(const String &targetSSID, i /* If the connection timed out */ if (WiFi.status() != WL_CONNECTED) { - verboseModePrint("Timeout"); + verboseModePrint(F("Timeout")); return TS_CONNECTION_FAILED; } @@ -433,7 +532,7 @@ void ESP8266WiFiMesh::attemptTransmission(const String &message, bool concluding { if(!noScan) { - verboseModePrint("Scanning... ", false); + verboseModePrint(F("Scanning... "), false); /* Scan for APs */ connectionQueue.clear(); @@ -444,15 +543,15 @@ void ESP8266WiFiMesh::attemptTransmission(const String &message, bool concluding #ifdef ENABLE_WIFI_SCAN_OPTIMIZATION if(scanAllWiFiChannels) { - n = WiFi.scanNetworks(); + n = WiFi.scanNetworks(false, _scanHidden); } else { // Scan function argument overview: scanNetworks(bool async = false, bool show_hidden = false, uint8 channel = 0, uint8* ssid = NULL) - n = WiFi.scanNetworks(false, false, _meshWiFiChannel); + n = WiFi.scanNetworks(false, _scanHidden, _meshWiFiChannel); } #else - n = WiFi.scanNetworks(); + n = WiFi.scanNetworks(false, _scanHidden); #endif _networkFilter(n, *this); // Update the connectionQueue. @@ -483,15 +582,15 @@ void ESP8266WiFiMesh::attemptTransmission(const String &message, bool concluding if(_verboseMode) // Avoid string generation if not required { - verboseModePrint("AP acquired: " + currentSSID + ", Ch:" + String(currentWiFiChannel) + " ", false); + verboseModePrint(String(F("AP acquired: ")) + currentSSID + String(F(", Ch:")) + String(currentWiFiChannel) + " ", false); if(currentNetwork.networkIndex != NETWORK_INFO_DEFAULT_INT) { - verboseModePrint("(" + String(WiFi.RSSI(currentNetwork.networkIndex)) + "dBm) " + - (WiFi.encryptionType(currentNetwork.networkIndex) == ENC_TYPE_NONE ? "open" : ""), false); + verboseModePrint("(" + String(WiFi.RSSI(currentNetwork.networkIndex)) + String(F("dBm) ")) + + (WiFi.encryptionType(currentNetwork.networkIndex) == ENC_TYPE_NONE ? String(F("open")) : ""), false); } - verboseModePrint("... ", false); + verboseModePrint(F("... "), false); } transmission_status_t transmissionResult = connectToNode(currentSSID, currentWiFiChannel, currentBSSID); @@ -502,7 +601,7 @@ void ESP8266WiFiMesh::attemptTransmission(const String &message, bool concluding if(WiFi.status() == WL_CONNECTED && staticIP != emptyIP && !staticIPActivated) { - verboseModePrint("Reactivating static IP to allow for faster re-connects."); + verboseModePrint(F("Reactivating static IP to allow for faster re-connects.")); setStaticIP(staticIP); } @@ -524,7 +623,7 @@ void ESP8266WiFiMesh::acceptRequest() if (!_client) break; - if (!waitForClient(_client, 1500)) { + if (!waitForClient(_client, _apModeTimeoutMs)) { continue; } @@ -548,7 +647,7 @@ void ESP8266WiFiMesh::acceptRequest() if (!_client) break; - if (!waitForClientTransmission(_client, 1500) || !_client.available()) { + if (!waitForClientTransmission(_client, _apModeTimeoutMs) || !_client.available()) { continue; } @@ -562,7 +661,7 @@ void ESP8266WiFiMesh::acceptRequest() /* Send the response back to the client */ if (_client.connected()) { - verboseModePrint("Responding"); + verboseModePrint("Responding"); // Not storing strings in flash (via F()) to avoid performance impacts when using the string. _client.print(response + "\r"); _client.flush(); yield(); diff --git a/libraries/ESP8266WiFiMesh/src/ESP8266WiFiMesh.h b/libraries/ESP8266WiFiMesh/src/ESP8266WiFiMesh.h index 0cbd4407b6..ceca8f0ff4 100644 --- a/libraries/ESP8266WiFiMesh/src/ESP8266WiFiMesh.h +++ b/libraries/ESP8266WiFiMesh/src/ESP8266WiFiMesh.h @@ -39,7 +39,7 @@ class ESP8266WiFiMesh { String _SSID; String _meshName; String _nodeID; - int _serverPort; + uint16_t _serverPort; String _meshPassword; uint8 _meshWiFiChannel; bool _verboseMode; @@ -47,6 +47,12 @@ class ESP8266WiFiMesh { uint32_t _lwipVersion[3]; static const uint32_t lwipVersion203Signature[3]; String _message = WIFI_MESH_EMPTY_STRING; + bool _scanHidden = false; + bool _apHidden = false; + uint8_t _maxAPStations = 4; + int32_t _connectionAttemptTimeoutMs = 10000; + int _stationModeTimeoutMs = 5000; // int is the type used in the Arduino core for this particular API, not uint32_t, which is why we use int here. + uint32_t _apModeTimeoutMs = 4500; static String lastSSID; static bool staticIPActivated; @@ -69,7 +75,7 @@ class ESP8266WiFiMesh { void initiateConnectionToAP(const String &targetSSID, int targetChannel = NETWORK_INFO_DEFAULT_INT, uint8_t *targetBSSID = NULL); transmission_status_t connectToNode(const String &targetSSID, int targetChannel = NETWORK_INFO_DEFAULT_INT, uint8_t *targetBSSID = NULL); transmission_status_t exchangeInfo(WiFiClient &currClient); - bool waitForClientTransmission(WiFiClient &currClient, int maxWait); + bool waitForClientTransmission(WiFiClient &currClient, uint32_t maxWait); transmission_status_t attemptDataTransfer(); transmission_status_t attemptDataTransferKernel(); void storeLwipVersion(); @@ -153,7 +159,7 @@ class ESP8266WiFiMesh { */ ESP8266WiFiMesh(requestHandlerType requestHandler, responseHandlerType responseHandler, networkFilterType networkFilter, const String &meshPassword, const String &meshName = "MeshNode_", const String &nodeID = WIFI_MESH_EMPTY_STRING, bool verboseMode = false, - uint8 meshWiFiChannel = 1, int serverPort = 4011); + uint8 meshWiFiChannel = 1, uint16_t serverPort = 4011); /** * A vector that contains the NetworkInfo for each WiFi network to connect to. @@ -171,6 +177,11 @@ class ESP8266WiFiMesh { */ static std::vector latestTransmissionOutcomes; + /** + * @returns True if latest transmission was successful (i.e. latestTransmissionOutcomes is not empty and all entries have transmissionStatus TS_TRANSMISSION_COMPLETE). False otherwise. + */ + static bool latestTransmissionSuccessful(); + /** * Initialises the node. */ @@ -202,11 +213,9 @@ class ESP8266WiFiMesh { */ bool isAPController(); - uint8 getWiFiChannel(); - /** * Change the WiFi channel used by this ESP8266WiFiMesh instance. - * Will also change the AP WiFi channel if this ESP8266WiFiMesh instance is the current AP controller. + * Will also change the WiFi channel for the active AP if this ESP8266WiFiMesh instance is the current AP controller and it is possible to change channel. * * WARNING: The ESP8266 has only one WiFi channel, and the the station/client mode is always prioritized for channel selection. * This can cause problems if several ESP8266WiFiMesh instances exist on the same ESP8266 and use different WiFi channels. @@ -218,44 +227,43 @@ class ESP8266WiFiMesh { * */ void setWiFiChannel(uint8 newWiFiChannel); - - String getMeshName(); + uint8 getWiFiChannel(); /** * Change the mesh name used by this ESP8266WiFiMesh instance. - * Will also change the AP mesh name (SSID prefix) if this ESP8266WiFiMesh instance is the current AP controller. + * Will also change the mesh name (SSID prefix) for the active AP if this ESP8266WiFiMesh instance is the current AP controller. * * @param newMeshName The mesh name to change to. */ void setMeshName(const String &newMeshName); - - String getNodeID(); - + String getMeshName(); + /** * Change the node id used by this ESP8266WiFiMesh instance. - * Will also change the AP node id (SSID suffix) if this ESP8266WiFiMesh instance is the current AP controller. + * Will also change the node id (SSID suffix) for the active AP if this ESP8266WiFiMesh instance is the current AP controller. * * @param newNodeID The node id to change to. */ void setNodeID(const String &newNodeID); - + String getNodeID(); + /** * Change the SSID (mesh name + node id) used by this ESP8266WiFiMesh instance. - * Will also change the AP SSID if this ESP8266WiFiMesh instance is the current AP controller. + * Will also change the SSID for the active AP if this ESP8266WiFiMesh instance is the current AP controller. * * @param newMeshName The mesh name to change to. Will be the SSID prefix. * @param newNodeID The node id to change to. Will be the SSID suffix. */ void setSSID(const String &newMeshName, const String &newNodeID); - - String getMessage(); - + String getSSID(); + /** * Set the message that will be sent to other nodes when calling attemptTransmission. * * @param newMessage The message to send. */ void setMessage(const String &newMessage); + String getMessage(); /** * If AP connection already exists, and the initialDisconnect argument is set to false, send message only to the already connected AP. @@ -282,7 +290,6 @@ class ESP8266WiFiMesh { * The static IP needs to be at the same subnet as the server's gateway. */ void setStaticIP(const IPAddress &newIP); - IPAddress getStaticIP(); void disableStaticIP(); @@ -291,11 +298,77 @@ class ESP8266WiFiMesh { */ static const IPAddress emptyIP; - static String uint64ToString(uint64_t number, byte base = 16); - static uint64_t stringToUint64(const String &string, byte base = 16); - - networkFilterType getNetworkFilter(); + void setRequestHandler(requestHandlerType requestHandler); + requestHandlerType getRequestHandler(); + + void setResponseHandler(responseHandlerType responseHandler); + responseHandlerType getResponseHandler(); + void setNetworkFilter(networkFilterType networkFilter); + networkFilterType getNetworkFilter(); + + /** + * Set whether scan results from this ESP8266WiFiMesh instance will include WiFi networks with hidden SSIDs. + * This is false by default. + * The SSID field of a found hidden network will be blank in the scan results. + * WiFi.isHidden(networkIndex) can be used to verify that a found network is hidden. + * + * @param scanHidden If true, WiFi networks with hidden SSIDs will be included in scan results. + */ + void setScanHidden(bool scanHidden); + bool getScanHidden(); + + /** + * Set whether the AP controlled by this ESP8266WiFiMesh instance will have a WiFi network with hidden SSID. + * This is false by default. + * Will also change the setting for the active AP if this ESP8266WiFiMesh instance is the current AP controller. + * + * @param apHidden If true, the WiFi network created will have a hidden SSID. + */ + void setAPHidden(bool apHidden); + bool getAPHidden(); + + /** + * Set the maximum number of stations that can simultaneously be connected to the AP controlled by this ESP8266WiFiMesh instance. + * This number is 4 by default. + * Once the max number has been reached, any other station that wants to connect will be forced to wait until an already connected station disconnects. + * The more stations that are connected, the more memory is required. + * Will also change the setting for the active AP if this ESP8266WiFiMesh instance is the current AP controller. + * + * @param maxAPStations The maximum number of simultaneous station connections allowed. Valid values are 0 to 8. + */ + void setMaxAPStations(uint8_t maxAPStations); + bool getMaxAPStations(); + + /** + * Set the timeout for each attempt to connect to another AP that occurs through the attemptTransmission method by this ESP8266WiFiMesh instance. + * The timeout is 10 000 ms by default. + * + * @param connectionAttemptTimeoutMs The timeout for each connection attempt, in milliseconds. + */ + void setConnectionAttemptTimeout(int32_t connectionAttemptTimeoutMs); + int32_t getConnectionAttemptTimeout(); + + /** + * Set the timeout to use for transmissions when this ESP8266WiFiMesh instance acts as a station (i.e. when connected to another AP). + * This will affect the timeout of the attemptTransmission method once a connection to an AP has been established. + * The timeout is 5 000 ms by default. + * + * @param stationModeTimeoutMs The timeout to use, in milliseconds. + */ + void setStationModeTimeout(int stationModeTimeoutMs); + int getStationModeTimeout(); + + /** + * Set the timeout to use for transmissions when this ESP8266WiFiMesh instance acts as an AP (i.e. when receiving connections from other stations). + * This will affect the timeout of the acceptRequest method. + * The timeout is 4 500 ms by default. + * Will also change the setting for the active AP if this ESP8266WiFiMesh instance is the current AP controller. + * + * @param apModeTimeoutMs The timeout to use, in milliseconds. + */ + void setAPModeTimeout(uint32_t apModeTimeoutMs); + uint32_t getAPModeTimeout(); }; #endif diff --git a/libraries/ESP8266WiFiMesh/src/TypeConversionFunctions.cpp b/libraries/ESP8266WiFiMesh/src/TypeConversionFunctions.cpp new file mode 100644 index 0000000000..be908b556e --- /dev/null +++ b/libraries/ESP8266WiFiMesh/src/TypeConversionFunctions.cpp @@ -0,0 +1,58 @@ +/* + * TypeConversionFunctions + * Copyright (C) 2018 Anders Löfgren + * + * License (MIT license): + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "TypeConversionFunctions.h" + +String uint64ToString(uint64_t number, byte base) +{ + assert(2 <= base && base <= 36); + + String result = ""; + + while(number > 0) + { + result = String((uint32_t)(number % base), base) + result; + number /= base; + } + + return (result == "" ? "0" : result); +} + +uint64_t stringToUint64(const String &string, byte base) +{ + assert(2 <= base && base <= 36); + + uint64_t result = 0; + + char currentCharacter[1]; + for(uint32_t i = 0; i < string.length(); i++) + { + result *= base; + currentCharacter[0] = string.charAt(i); + result += strtoul(currentCharacter, NULL, base); + } + + return result; +} diff --git a/libraries/ESP8266WiFiMesh/src/TypeConversionFunctions.h b/libraries/ESP8266WiFiMesh/src/TypeConversionFunctions.h new file mode 100644 index 0000000000..5d42e414cc --- /dev/null +++ b/libraries/ESP8266WiFiMesh/src/TypeConversionFunctions.h @@ -0,0 +1,50 @@ +/* + * TypeConversionFunctions + * Copyright (C) 2018 Anders Löfgren + * + * License (MIT license): + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef __TYPECONVERSIONFUNCTIONS_H__ +#define __TYPECONVERSIONFUNCTIONS_H__ + +#include +#include + +/** + * Note that using a base higher than 16 increases likelihood of randomly generating SSID strings containing controversial words. + * + * @param number The number to convert to a string with radix "base". + * @param base The radix to convert "number" into. Must be between 2 and 36. + * @returns A string of "number" encoded in radix "base". + */ +String uint64ToString(uint64_t number, byte base = 16); + +/** + * Note that using a base higher than 16 increases likelihood of randomly generating SSID strings containing controversial words. + * + * @param string The string to convert to uint64_t. String must use radix "base". + * @param base The radix of "string". Must be between 2 and 36. + * @returns A uint64_t of the string, using radix "base" during decoding. + */ +uint64_t stringToUint64(const String &string, byte base = 16); + +#endif diff --git a/libraries/ESP8266WiFiMesh/src/UtilityMethods.cpp b/libraries/ESP8266WiFiMesh/src/UtilityMethods.cpp index ecc36ca72f..795eacb638 100644 --- a/libraries/ESP8266WiFiMesh/src/UtilityMethods.cpp +++ b/libraries/ESP8266WiFiMesh/src/UtilityMethods.cpp @@ -23,8 +23,8 @@ * THE SOFTWARE. */ +#include "TypeConversionFunctions.h" #include "ESP8266WiFiMesh.h" -#include void ESP8266WiFiMesh::verboseModePrint(const String &stringToPrint, bool newline) { @@ -37,52 +37,6 @@ void ESP8266WiFiMesh::verboseModePrint(const String &stringToPrint, bool newline } } -/** - * Note that using a base higher than 16 increases likelihood of randomly generating SSID strings containing controversial words. - * - * @param number The number to convert to a string with radix "base". - * @param base The radix to convert "number" into. Must be between 2 and 36. - * @returns A string of "number" encoded in radix "base". - */ -String ESP8266WiFiMesh::uint64ToString(uint64_t number, byte base) -{ - assert(2 <= base && base <= 36); - - String result = ""; - - while(number > 0) - { - result = String((uint32_t)(number % base), base) + result; - number /= base; - } - - return (result == "" ? "0" : result); -} - -/** - * Note that using a base higher than 16 increases likelihood of randomly generating SSID strings containing controversial words. - * - * @param string The string to convert to uint64_t. String must use radix "base". - * @param base The radix of "string". Must be between 2 and 36. - * @returns A uint64_t of the string, using radix "base" during decoding. - */ -uint64_t ESP8266WiFiMesh::stringToUint64(const String &string, byte base) -{ - assert(2 <= base && base <= 36); - - uint64_t result = 0; - - char currentCharacter[1]; - for(uint32_t i = 0; i < string.length(); i++) - { - result *= base; - currentCharacter[0] = string.charAt(i); - result += strtoul(currentCharacter, NULL, base); - } - - return result; -} - /** * Calculate the current lwIP version number and store the numbers in the _lwipVersion array. * lwIP version can be changed in the "Tools" menu of Arduino IDE.