From 7d5997dad1054b5058eb70db5824f454eb5e7928 Mon Sep 17 00:00:00 2001 From: aerlon <39123666+aerlon@users.noreply.github.com> Date: Wed, 1 Aug 2018 04:46:20 +0200 Subject: [PATCH] Update of mesh network library. (#4718) * Make mesh network actually usable. Make mesh network use static IP during initial connection to speed up connection time. Add separate handlers for requests and responses. Add network password. Provide more detailed code example. Add optional verbose mode. Improve comments. Add readme file. * Fix compiler warnings. Fix code style of HelloMesh.ino to avoid upsetting Travis. * Remove stray spaces. * Make mesh network WiFi password settable via the ESP8266WiFiMesh constructor. Make use of static IP optional by moving static IP initialization code to setStaticIP method. Increase scanning interval from one to two seconds in the HelloMesh.ino example to increase chances of successful connections. Update comments. Update README.rst. * Increase specificity in the conditions of the waitForClientTransmission method (renamed from waitForClient) to avoid issues related to #4626 , #4728 and #4754 in the future. * Improve most parts of the library to achieve better performance and greatly increase flexibility. Changes: * Make WiFi-connection related variables static to allow for the use of multiple ESP8266WiFiMesh instances on a single node (useful e.g. when communicating with several different mesh networks). * Make it possible to choose AP port, which is helpful when using multiple ESP8266WiFiMesh AP:s on a single node. * Add user-customizable network filter. * Make activation of own AP optional for each mesh node. * Add ways to change mesh network name and node id for existing ESP8266WiFiMesh instances. * Add verboseModePrint method to clean up the code. * Add reactivation of static IP after successful data transfers to speed up re-connection attempts. * Add empty_IP constant which can be used to check if static IP is disabled for a ESP8266WiFiMesh instance. * Remove the WiFiClient _client class variable in ESP8266WiFiMesh since there is no need to save _client in the class instance. * Add transmission status as a return value from attemptTransmission. * Pass calling ESP8266WiFiMesh instance pointer to callback functions to allow for greater range of actions in callbacks. * Make transmission message a class variable to allow it to be stored in the class and accessed from callbacks. * Add getters for mesh name and node id to ESP8266WiFiMesh. * Add getter and setter for networkFilter to ESP8266WiFiMesh. * Increase range of available node_id:s by changing the type to String and adding functions to convert between String and uint64_t using a customizable radix between 2 and 36. * Make it possible to connect to several nodes during each attemptTransmission call. * Add static connection_queue and latest_transmission_outcomes vectors to the ESP8266WiFiMesh class, a NetworkInfo class and a TransmissionResult class to aid in bookkeeping when connecting to several AP:s during one attemptTransmission call. * Make wifi_channel and BSSID optional when connecting to an AP (though excluding them will slow down the connection process). * Add optional scan and static ip optimizations available in Arduino core for ESP8266 version 2.4.2. * Add functions to check lwIP version in order to enable WiFi optimizations only available with lwIP2. * Add concluding_disconnect, initial_disconnect and no_scan options to the attemptTransmission method. * Update documentation. * Improve README.rst formatting. * Further improve README.rst. * Even further improve README.rst. * Make source code comments Doxygen compatible. Improve README file and change its file format to .md. * Add temporary compatibility layer to ensure backwards compatibility with the old mesh network library API until the next major core release (2.5.0). * Polish documentation slightly. * Add scan_all_wifi_channels option to attemptTransmission method. * - Add getter and setter for the WiFi channel of a ESP8266WiFiMesh instance. - Separate methods for changing mesh name and node id from AP control methods. - Add methods getAPController and isAPController to better handle situations when multiple ESP8266WiFiMesh instances take turns to be in control of the AP. - Create separate UtilityMethods.cpp file for utility methods. - Improve code efficiency and robustness, e.g. by passing arguments by reference instead of by value for non-POD types and employing typedefs. - Update README.md. * Make the code more stylish. * Update README.md with the new ESP8266WiFiMesh constructor documentation. * Make attemptScan method in CompatibilityLayer use reference as argument. * Make it possible to use const String as argument to attemptScan. * - Make code use camelCase instead of snake_case. - Improve documentation. * Rename Uint64ToString to uint64ToString and StringToUint64 to stringToUint64, since they are methods. --- libraries/ESP8266WiFiMesh/README.md | 81 +++ .../examples/HelloMesh/HelloMesh.ino | 123 +++- libraries/ESP8266WiFiMesh/keywords.txt | 36 +- libraries/ESP8266WiFiMesh/library.properties | 4 +- .../src/CompatibilityLayer.cpp | 181 ++++++ .../ESP8266WiFiMesh/src/ESP8266WiFiMesh.cpp | 588 +++++++++++++++--- .../ESP8266WiFiMesh/src/ESP8266WiFiMesh.h | 304 +++++++-- libraries/ESP8266WiFiMesh/src/NetworkInfo.cpp | 77 +++ libraries/ESP8266WiFiMesh/src/NetworkInfo.h | 69 ++ .../src/TransmissionResult.cpp | 42 ++ .../ESP8266WiFiMesh/src/TransmissionResult.h | 57 ++ .../ESP8266WiFiMesh/src/UtilityMethods.cpp | 127 ++++ 12 files changed, 1534 insertions(+), 155 deletions(-) create mode 100644 libraries/ESP8266WiFiMesh/README.md create mode 100644 libraries/ESP8266WiFiMesh/src/CompatibilityLayer.cpp create mode 100644 libraries/ESP8266WiFiMesh/src/NetworkInfo.cpp create mode 100644 libraries/ESP8266WiFiMesh/src/NetworkInfo.h create mode 100644 libraries/ESP8266WiFiMesh/src/TransmissionResult.cpp create mode 100644 libraries/ESP8266WiFiMesh/src/TransmissionResult.h create mode 100644 libraries/ESP8266WiFiMesh/src/UtilityMethods.cpp diff --git a/libraries/ESP8266WiFiMesh/README.md b/libraries/ESP8266WiFiMesh/README.md new file mode 100644 index 0000000000..5876b0872a --- /dev/null +++ b/libraries/ESP8266WiFiMesh/README.md @@ -0,0 +1,81 @@ +ESP8266 WiFi Mesh +================= + +A library for turning your ESP8266 into a mesh network node. + +The library has been tested and works with Arduino core for ESP8266 version 2.3.0 (with default lwIP) and 2.4.2 or higher (with lwIP 1.4 and lwIP2). + +**Note:** This mesh library has been rewritten for core release 2.4.2. The old method signatures have been retained for compatibility purposes, but will be removed in core release 2.5.0. If you are still using these old method signatures please consider migrating to the new API shown in the `ESP8266WiFiMesh.h` source file. + +Usage +----- + +The basic operation of a mesh node is as follows: + +The `attemptTransmission` method of the ESP8266WiFiMesh instance is called with a message to send to other nodes in the mesh network. If the node is already connected to an AP, the message is sent only to that AP. Otherwise a WiFi scan is performed. The scan results are sent to the `networkFilter` callback function of the ESP8266WiFiMesh instance which adds the AP:s of interest to the `connectionQueue` vector. The message is then transmitted to the networks in the `connectionQueue`, and the response from each AP is sent to the `responseHandler` callback of the ESP8266WiFiMesh instance. The outcome from each transmission attempt can be found in the `latestTransmissionOutcomes` vector. + +The node receives messages from other nodes by calling the `acceptRequest` method of the ESP8266WiFiMesh instance. These received messages are passed to the `requestHandler` callback of the mesh instance. For each received message the return value of `requestHandler` is sent to the other node as a response to the message. + +For more details, see the included example. The main functions to modify in the example are `manageRequest` (`requestHandler`), `manageResponse` (`responseHandler`) and `networkFilter`. There is also more information to be found in the source code comments. An example is the ESP8266WiFiMesh constructor comment, which is shown below for reference: +``` +/** +* WiFiMesh Constructor method. Creates a WiFi Mesh Node, ready to be initialised. +* +* @param requestHandler The callback handler for dealing with received requests. Takes a string as an argument which +* is the request string received from another node and returns the string to send back. +* @param responseHandler The callback handler for dealing with received responses. Takes a string as an argument which +* is the response string received from another node. Returns a transmission status code as a transmission_status_t. +* @param networkFilter The callback handler for deciding which WiFi networks to connect to. +* @param meshPassword The WiFi password for the mesh network. +* @param meshName The name of the mesh network. Used as prefix for the node SSID and to find other network nodes in the example network filter function. +* @param nodeID The id for this mesh node. Used as suffix for the node SSID. If set to "", the id will default to ESP.getChipId(). +* @param verboseMode Determines if we should print the events occurring in the library to Serial. Off by default. +* @param meshWiFiChannel The WiFi channel used by the mesh network. Valid values are integers from 1 to 13. Defaults to 1. +* 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. +* In such a case, whenever the station of one ESP8266WiFiMesh instance connects to an AP, it will silently force the +* WiFi channel of any active AP on the ESP8266 to match that of the station. This will cause disconnects and possibly +* make it impossible for other stations to detect the APs whose WiFi channels have changed. +* @param serverPort The server port used by the AP of the ESP8266WiFiMesh instance. If multiple APs exist on a single ESP8266, each requires a separate server port. +* If two AP:s on the same ESP8266 are using the same server port, they will not be able to have both server instances active at the same time. +* This is managed automatically by the activateAP method. +* +*/ +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); +``` + +### Note + +* 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. + +* 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. + + If you are using a core version prior to 2.4.2 it is possible to disable the WiFi scan and static IP optimizations by commenting out the `ENABLE_STATIC_IP_OPTIMIZATION` and `ENABLE_WIFI_SCAN_OPTIMIZATION` defines in ESP8266WiFiMesh.h. Press Ctrl+K in the Arduino IDE while an example from the mesh library is opened, to open the library folder (or click "Show Sketch Folder" in the Sketch menu). ESP8266WiFiMesh.h can then be found at ESP8266WiFiMesh/src. Edit the file with any text editor. + +* The WiFi scan optimization mentioned above works by making WiFi scans only search through the same WiFi channel as the ESP8266WiFiMesh instance is using. If you would like to scan all WiFi channels instead, set the `scanAllWiFiChannels` argument of the `attemptTransmission` method to `true`. Note that scanning all WiFi channels will slow down scans considerably and make it more likely that existing WiFi connections will break during scans. Also note that if the ESP8266 has an active AP, that AP will switch WiFi channel to match that of any other AP the ESP8266 connects to (compare next bullet point). This can make it impossible for other nodes to detect the AP if they are scanning the wrong WiFi channel. To remedy this, force the AP back on the original channel by using the `restartAP` method of the current AP controller once the ESP8266 has disconnected from the other AP. This would typically be done like so: + + ``` + if(ESP8266WiFiMesh *apController = ESP8266WiFiMesh::getAPController()) // Make sure apController is not nullptr + apController->restartAP(); + ``` + +* It is possible to have several ESP8266WiFiMesh instances running on every ESP8266 (e.g. to communicate with different mesh networks). However, because the ESP8266 has one WiFi radio only one AP per ESP8266 can be active at a time. Also note that if the ESP8266WiFiMesh instances use different WiFi channels, active APs are forced to use the same WiFi channel as active stations, possibly causing AP disconnections. + +* While it is possible to connect to other nodes by only giving their SSID, e.g. `ESP8266WiFiMesh::connectionQueue.push_back(NetworkInfo("NodeSSID"));`, it is recommended that AP WiFi channel and AP BSSID are given as well, to minimize connection delay. + +* Also, remember to change the default mesh network WiFi password! + +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. + +* 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). + +* Scanning for networks (e.g. via the `attemptTransmission` method) without the WiFi scan optimizations for core version 2.4.2 mentioned above, causes the WiFi radio to cycle through all WiFi channels which means existing WiFi connections are likely to break or work poorly if done frequently. \ No newline at end of file diff --git a/libraries/ESP8266WiFiMesh/examples/HelloMesh/HelloMesh.ino b/libraries/ESP8266WiFiMesh/examples/HelloMesh/HelloMesh.ino index efbbeb7e92..f5347743ab 100644 --- a/libraries/ESP8266WiFiMesh/examples/HelloMesh/HelloMesh.ino +++ b/libraries/ESP8266WiFiMesh/examples/HelloMesh/HelloMesh.ino @@ -1,50 +1,131 @@ #include #include -unsigned int request_i = 0; -unsigned int response_i = 0; +String exampleMeshName("MeshNode_"); -String manageRequest(String request); +unsigned int requestNumber = 0; +unsigned int responseNumber = 0; + +String manageRequest(const String &request, ESP8266WiFiMesh &meshInstance); +transmission_status_t manageResponse(const String &response, ESP8266WiFiMesh &meshInstance); +void networkFilter(int numberOfNetworks, ESP8266WiFiMesh &meshInstance); /* Create the mesh node object */ -ESP8266WiFiMesh mesh_node = ESP8266WiFiMesh(ESP.getChipId(), manageRequest); +ESP8266WiFiMesh meshNode = ESP8266WiFiMesh(manageRequest, manageResponse, networkFilter, "ChangeThisWiFiPassword_TODO", exampleMeshName, "", true); /** - Callback for when other nodes send you data + Callback for when other nodes send you a request - @request The string received from another node in the mesh + @param request The request string received from another node in the mesh + @param meshInstance The ESP8266WiFiMesh instance that called the function. @returns The string to send back to the other node */ -String manageRequest(String request) { +String manageRequest(const String &request, ESP8266WiFiMesh &meshInstance) { /* Print out received message */ - Serial.print("received: "); + Serial.print("Request received: "); Serial.println(request); /* return a string to send back */ - char response[60]; - sprintf(response, "Hello world response #%d from Mesh_Node%d.", response_i++, ESP.getChipId()); - return response; + 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 + + @param response The response string received from another node in the mesh + @param meshInstance The ESP8266WiFiMesh instance that called the function. + @returns The status code resulting from the response, as an int +*/ +transmission_status_t manageResponse(const String &response, ESP8266WiFiMesh &meshInstance) { + transmission_status_t statusCode = TS_TRANSMISSION_COMPLETE; + + /* Print out received message */ + Serial.print("Request sent: "); + Serial.println(meshInstance.getMessage()); + Serial.print("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() + "."); + + // (void)meshInstance; // This is useful to remove a "unused parameter" compiler warning. Does nothing else. + return statusCode; } 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. + WiFi.persistent(false); + Serial.begin(115200); - delay(10); + delay(50); // Wait for Serial. + + //yield(); // Use this if you don't want to wait for Serial. 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("Setting up mesh node..."); /* Initialise the mesh node */ - mesh_node.begin(); + meshNode.begin(); + meshNode.activateAP(); // Each AP requires a separate server port. + meshNode.setStaticIP(IPAddress(192, 168, 4, 22)); // Activate static IP mode to speed up connection times. } +int32_t timeOfLastScan = -10000; void loop() { - /* Accept any incoming connections */ - mesh_node.acceptRequest(); - - /* Scan for other nodes and send them a message */ - char request[60]; - sprintf(request, "Hello world request #%d from Mesh_Node%d.", request_i++, ESP.getChipId()); - mesh_node.attemptScan(request); - delay(1000); + 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() + "."; + meshNode.attemptTransmission(request, false); + timeOfLastScan = millis(); + + if (ESP8266WiFiMesh::latestTransmissionOutcomes.empty()) { + Serial.println("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); + } else if (transmissionResult.transmissionStatus == TS_CONNECTION_FAILED) { + Serial.println("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(); + } else { + /* Accept any incoming connections */ + meshNode.acceptRequest(); + } } diff --git a/libraries/ESP8266WiFiMesh/keywords.txt b/libraries/ESP8266WiFiMesh/keywords.txt index 14657d805c..fdb0caf08e 100644 --- a/libraries/ESP8266WiFiMesh/keywords.txt +++ b/libraries/ESP8266WiFiMesh/keywords.txt @@ -13,11 +13,45 @@ ESP8266WiFiMesh KEYWORD3 ####################################### ESP8266WiFiMesh KEYWORD1 +NetworkInfo KEYWORD1 +TransmissionResult KEYWORD1 +transmission_status_t KEYWORD1 ####################################### # Methods and Functions (KEYWORD2) ####################################### +connectionQueue KEYWORD2 +latestTransmissionOutcomes KEYWORD2 begin KEYWORD2 -attemptScan KEYWORD2 +activateAP KEYWORD2 +deactivateAP KEYWORD2 +restartAP KEYWORD2 +getAPController KEYWORD2 +isAPController KEYWORD2 +getWiFiChannel KEYWORD2 +setWiFiChannel KEYWORD2 +getMeshName KEYWORD2 +setMeshName KEYWORD2 +getNodeID KEYWORD2 +setNodeID KEYWORD2 +setSSID KEYWORD2 +getMessage KEYWORD2 +setMessage KEYWORD2 +attemptTransmission KEYWORD2 acceptRequest KEYWORD2 +setStaticIP KEYWORD2 +getStaticIP KEYWORD2 +disableStaticIP->KEYWORD2 +uint64ToString KEYWORD2 +stringToUint64 KEYWORD2 +getNetworkFilter KEYWORD2 +setNetworkFilter KEYWORD2 + +####################################### +# Constants (LITERAL1) +####################################### + +emptyIP LITERAL1 +NETWORK_INFO_DEFAULT_INT LITERAL1 +WIFI_MESH_EMPTY_STRING LITERAL1 diff --git a/libraries/ESP8266WiFiMesh/library.properties b/libraries/ESP8266WiFiMesh/library.properties index dc6ba9a4da..5dc4dec11a 100644 --- a/libraries/ESP8266WiFiMesh/library.properties +++ b/libraries/ESP8266WiFiMesh/library.properties @@ -1,7 +1,7 @@ name=ESP8266WiFiMesh -version=1.0 +version=2.0 author=Julian Fell -maintainer= +maintainer=Anders Löfgren sentence=Mesh network library paragraph=The library sets up a Mesh Node which acts as a router, creating a Mesh Network with other nodes. category=Communication diff --git a/libraries/ESP8266WiFiMesh/src/CompatibilityLayer.cpp b/libraries/ESP8266WiFiMesh/src/CompatibilityLayer.cpp new file mode 100644 index 0000000000..fd926a9351 --- /dev/null +++ b/libraries/ESP8266WiFiMesh/src/CompatibilityLayer.cpp @@ -0,0 +1,181 @@ +/* + Old version of ESP8266WiFiMesh.cpp - Mesh network node + Sets up a Mesh Node which acts as a router, creating a Mesh Network with other nodes. All information + is passed in both directions, but it is up to the user what the data sent is and how it is dealt with. + + Copyright (c) 2015 Julian Fell. All rights reserved. + Updated 2018 by Anders Löfgren. + + 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 +*/ + + + + + + +/******************************************************************************************** +* NOTE! +* +* All method signatures in this file are deprecated and will be removed in core version 2.5.0. +* If you are still using these methods, please consider migrating to the new API shown in +* the ESP8266WiFiMesh.h source file. +* +* TODO: delete this file. +********************************************************************************************/ + + + + + +#include +#include +#include +#include + +#include "ESP8266WiFiMesh.h" + +#define SSID_PREFIX "Mesh_Node" +#define SERVER_IP_ADDR "192.168.4.1" +#define SERVER_PORT 4011 + +// DEPRECATED! +ESP8266WiFiMesh::ESP8266WiFiMesh(uint32_t chipID, ESP8266WiFiMesh::compatibilityLayerHandlerType handler) +: _server(SERVER_PORT) +{ + _chipID = chipID; + _SSID = String( String( SSID_PREFIX ) + String( _chipID ) ); + _ssidPrefix = String( SSID_PREFIX ); + _handler = handler; +} + +/** + * Wait for a WiFiClient to connect + * + * @returns: True if the client is ready, false otherwise. + * + */ +// DEPRECATED! +bool ESP8266WiFiMesh::waitForClient(WiFiClient &currClient, int maxWait) +{ + int wait = maxWait; + while(currClient.connected() && !currClient.available() && wait--) + delay(3); + + /* Return false if the client isn't ready to communicate */ + if (WiFi.status() == WL_DISCONNECTED || !currClient.connected()) + return false; + + return true; +} + +/** + * Send the supplied message then read back the other node's response + * and pass that to the user-supplied handler. + * + * @message The string to send to the node. + * @returns: True if the exchange was a succes, false otherwise. + * + */ +// DEPRECATED! +bool ESP8266WiFiMesh::exchangeInfo(const char *message, WiFiClient &currClient) +{ + currClient.println( message ); + + if (!waitForClient(currClient, 1000)) + return false; + + String response = currClient.readStringUntil('\r'); + currClient.readStringUntil('\n'); + + if (response.length() <= 2) + return false; + + /* Pass data to user callback */ + _handler(response); + return true; +} + +/** + * Connect to the AP at ssid, send them a message then disconnect. + * + * @targetSSID The name of the AP the other node has set up. + * @message The string to send to the node. + * + */ +// DEPRECATED! +void ESP8266WiFiMesh::connectToNode(const String &targetSSID, const char *message) +{ + WiFiClient currClient; + WiFi.begin( targetSSID.c_str() ); + + int wait = 1500; + while((WiFi.status() == WL_DISCONNECTED) && wait--) + delay(3); + + /* If the connection timed out */ + if (WiFi.status() != 3) + return; + + /* Connect to the node's server */ + if (!currClient.connect(SERVER_IP_ADDR, SERVER_PORT)) + return; + + if (!exchangeInfo(message, currClient)) + return; + + currClient.stop(); + WiFi.disconnect(); +} + +// DEPRECATED! +void ESP8266WiFiMesh::attemptScanKernel(const char *message) +{ + /* Scan for APs */ + int n = WiFi.scanNetworks(); + + for (int i = 0; i < n; ++i) { + String currentSSID = WiFi.SSID(i); + int index = currentSSID.indexOf( _ssidPrefix ); + uint32_t targetChipID = (currentSSID.substring(index + _ssidPrefix.length())).toInt(); + + /* Connect to any _suitable_ APs which contain _ssidPrefix */ + if (index >= 0 && (targetChipID < _chipID)) { + + WiFi.mode(WIFI_STA); + delay(100); + connectToNode(currentSSID, message); + WiFi.mode(WIFI_AP_STA); + delay(100); + } + } +} + +// DEPRECATED! +void ESP8266WiFiMesh::attemptScan(const String &message) +{ + attemptScanKernel(message.c_str()); +} + +// DEPRECATED! +void ESP8266WiFiMesh::attemptScan(char *message) +{ + attemptScanKernel(message); +} + +// DEPRECATED! +template +void ESP8266WiFiMesh::attemptScan(char (&message)[Size]) +{ + attemptScanKernel(message); +} \ No newline at end of file diff --git a/libraries/ESP8266WiFiMesh/src/ESP8266WiFiMesh.cpp b/libraries/ESP8266WiFiMesh/src/ESP8266WiFiMesh.cpp index 6d3e544d7c..b617be1f00 100644 --- a/libraries/ESP8266WiFiMesh/src/ESP8266WiFiMesh.cpp +++ b/libraries/ESP8266WiFiMesh/src/ESP8266WiFiMesh.cpp @@ -1,9 +1,9 @@ /* ESP8266WiFiMesh.cpp - Mesh network node - Sets up a Mesh Node which acts as a router, creating a Mesh Network with other nodes. All information - is passed in both directions, but it is up to the user what the data sent is and how it is dealt with. + Sets up a Mesh Node which acts as a router, creating a Mesh Network with other nodes. Copyright (c) 2015 Julian Fell. All rights reserved. + Updated 2018 by Anders Löfgren. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public @@ -18,151 +18,555 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ -#include #include #include #include +#include #include "ESP8266WiFiMesh.h" -#define SSID_PREFIX "Mesh_Node" -#define SERVER_IP_ADDR "192.168.4.1" -#define SERVER_PORT 4011 +#define SERVER_IP_ADDR "192.168.4.1" -ESP8266WiFiMesh::ESP8266WiFiMesh(uint32_t chip_id, std::function handler) -: _server(SERVER_PORT) +const IPAddress ESP8266WiFiMesh::emptyIP = IPAddress(); +const uint32_t ESP8266WiFiMesh::lwipVersion203Signature[3] {2,0,3}; + +String ESP8266WiFiMesh::lastSSID = ""; +bool ESP8266WiFiMesh::staticIPActivated = false; + +// IP needs to be at the same subnet as server gateway (192.168.4 in this case). Station gateway ip must match ip for server. +IPAddress ESP8266WiFiMesh::staticIP = emptyIP; +IPAddress ESP8266WiFiMesh::gateway = IPAddress(192,168,4,1); +IPAddress ESP8266WiFiMesh::subnetMask = IPAddress(255,255,255,0); +ESP8266WiFiMesh *ESP8266WiFiMesh::apController = nullptr; + +std::vector ESP8266WiFiMesh::connectionQueue = {}; +std::vector ESP8266WiFiMesh::latestTransmissionOutcomes = {}; + +ESP8266WiFiMesh::~ESP8266WiFiMesh() +{ + if(isAPController()) + { + apController = nullptr; + } +} + +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) + : _server(serverPort), _lwipVersion{0, 0, 0} +{ + storeLwipVersion(); + + updateNetworkNames(meshName, (nodeID != "" ? nodeID : uint64ToString(ESP.getChipId()))); + _requestHandler = requestHandler; + _responseHandler = responseHandler; + setWiFiChannel(meshWiFiChannel); + _serverPort = serverPort; + _meshPassword = meshPassword; + _verboseMode = verboseMode; + _networkFilter = networkFilter; +} + +void ESP8266WiFiMesh::updateNetworkNames(const String &newMeshName, const String &newNodeID) { - _chip_id = chip_id; - _ssid = String( String( SSID_PREFIX ) + String( _chip_id ) ); - _ssid_prefix = String( SSID_PREFIX ); - _handler = handler; + if(newMeshName != "") + _meshName = newMeshName; + if(newNodeID != "") + _nodeID = newNodeID; + + _SSID = _meshName + _nodeID; + + // Apply SSID changes to active AP. + if(isAPController()) + restartAP(); } void ESP8266WiFiMesh::begin() { - WiFi.mode(WIFI_AP_STA); - WiFi.softAP( _ssid.c_str() ); - _server.begin(); + //////////////////////////// TODO: REMOVE IN 2.5.0//////////////////////////// + if(_handler != NULL) + { + WiFi.mode(WIFI_AP_STA); + WiFi.softAP( _SSID.c_str() ); + _server.begin(); + } + else + { + //////////////////////////// TODO: REMOVE IN 2.5.0//////////////////////////// + WiFi.mode(WIFI_AP_STA); + + #ifdef ENABLE_STATIC_IP_OPTIMIZATION + if(atLeastLwipVersion(lwipVersion203Signature)) + { + verboseModePrint("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"); + } + #endif + } +} + +void ESP8266WiFiMesh::setStaticIP(const IPAddress &newIP) +{ + // Comment out the line below to remove static IP and use DHCP instead. + // DHCP makes WiFi connection happen slower, but there is no need to care about manually giving different IPs to the nodes and less need to worry about used IPs giving "Server unavailable" issues. + // Static IP has faster connection times (50 % of DHCP) and will make sending of data to a node that is already transmitting data happen more reliably. + // Note that after WiFi.config(staticIP, gateway, subnetMask) is used, static IP will always be active, even for new connections, unless WiFi.config(0u,0u,0u); is called. + WiFi.config(newIP, gateway, subnetMask); + staticIPActivated = true; + staticIP = newIP; +} + +IPAddress ESP8266WiFiMesh::getStaticIP() +{ + if(staticIPActivated) + return staticIP; + + return emptyIP; +} + +void ESP8266WiFiMesh::disableStaticIP() +{ + WiFi.config(0u,0u,0u); + yield(); + staticIPActivated = false; } +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 + _server.begin(); // Actually calls _server.stop()/_server.close() first. + + apController = this; +} + +void ESP8266WiFiMesh::deactivateAP() +{ + if(isAPController()) + { + _server.stop(); + WiFi.softAPdisconnect(); + + // Since there is no active AP controller now, make the apController variable point to nothing. + apController = nullptr; + } +} + +void ESP8266WiFiMesh::restartAP() +{ + deactivateAP(); + yield(); + activateAP(); + yield(); +} + +ESP8266WiFiMesh * ESP8266WiFiMesh::getAPController() +{ + return apController; +} + +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(); +} + +String ESP8266WiFiMesh::getMeshName() {return _meshName;} + +void ESP8266WiFiMesh::setMeshName(const String &newMeshName) +{ + updateNetworkNames(newMeshName); +} + +String ESP8266WiFiMesh::getNodeID() {return _nodeID;} + +void ESP8266WiFiMesh::setNodeID(const String &newNodeID) +{ + updateNetworkNames("", newNodeID); +} + +void ESP8266WiFiMesh::setSSID(const String &newMeshName, const String &newNodeID) +{ + updateNetworkNames(newMeshName, newNodeID); +} + +String ESP8266WiFiMesh::getMessage() {return _message;} +void ESP8266WiFiMesh::setMessage(const String &newMessage) {_message = newMessage;} + +ESP8266WiFiMesh::networkFilterType ESP8266WiFiMesh::getNetworkFilter() {return _networkFilter;} +void ESP8266WiFiMesh::setNetworkFilter(ESP8266WiFiMesh::networkFilterType networkFilter) {_networkFilter = networkFilter;} + /** - * Wait for a WiFiClient to connect + * Disconnect completely from a network. + */ +void ESP8266WiFiMesh::fullStop(WiFiClient &currClient) +{ + currClient.stop(); + yield(); + WiFi.disconnect(); + yield(); +} + +/** + * Wait for a WiFiClient to transmit * * @returns: True if the client is ready, false otherwise. * */ -bool ESP8266WiFiMesh::waitForClient(WiFiClient curr_client, int max_wait) +bool ESP8266WiFiMesh::waitForClientTransmission(WiFiClient &currClient, int maxWait) { - int wait = max_wait; - while(curr_client.connected() && !curr_client.available() && wait--) - delay(3); + int wait = maxWait; + while(currClient.connected() && !currClient.available() && wait--) + delay(3); - /* Return false if the client isn't ready to communicate */ - if (WiFi.status() == WL_DISCONNECTED || !curr_client.connected()) - return false; - - return true; + /* Return false if the client isn't ready to communicate */ + if (WiFi.status() == WL_DISCONNECTED && !currClient.available()) + { + verboseModePrint("Disconnected!"); + return false; + } + + return true; } /** - * Send the supplied message then read back the other node's response - * and pass that to the user-supplied handler. + * Send the mesh instance's current message then read back the other node's response + * and pass that to the user-supplied responseHandler. * - * @target_ssid The name of the AP the other node has set up. - * @message The string to send to the node. - * @returns: True if the exchange was a succes, false otherwise. + * @param currClient The client to which the message should be transmitted. + * @returns: A status code based on the outcome of the exchange. * */ -bool ESP8266WiFiMesh::exchangeInfo(String message, WiFiClient curr_client) +transmission_status_t ESP8266WiFiMesh::exchangeInfo(WiFiClient &currClient) { - curr_client.println( message.c_str() ); + verboseModePrint("Transmitting"); + + currClient.print(getMessage() + "\r"); + yield(); - if (!waitForClient(curr_client, 1000)) - return false; + if (!waitForClientTransmission(currClient, 1000)) + { + fullStop(currClient); + return TS_CONNECTION_FAILED; + } - String response = curr_client.readStringUntil('\r'); - curr_client.readStringUntil('\n'); + if (!currClient.available()) + { + verboseModePrint("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. + } - if (response.length() <= 2) - return false; + String response = currClient.readStringUntil('\r'); + yield(); + currClient.flush(); - /* Pass data to user callback */ - _handler(response); - return true; + /* Pass data to user callback */ + return _responseHandler(response, *this); } /** - * Connect to the AP at ssid, send them a message then disconnect. + * Handle data transfer process with a connected AP. * - * @target_ssid The name of the AP the other node has set up. - * @message The string to send to the node. - * + * @returns: A status code based on the outcome of the data transfer attempt. + */ +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). + WiFi.mode(WIFI_STA); + delay(1); + transmission_status_t transmissionOutcome = attemptDataTransferKernel(); + WiFi.mode(WIFI_AP_STA); + delay(1); + + return transmissionOutcome; +} + +/** + * Helper function that contains the core functionality for the data transfer process with a connected AP. + * + * @returns: A status code based on the outcome of the data transfer attempt. */ -void ESP8266WiFiMesh::connectToNode(String target_ssid, String message) +transmission_status_t ESP8266WiFiMesh::attemptDataTransferKernel() { - WiFiClient curr_client; - WiFi.begin( target_ssid.c_str() ); + WiFiClient currClient; + + /* Connect to the node's server */ + if (!currClient.connect(SERVER_IP_ADDR, _serverPort)) + { + fullStop(currClient); + verboseModePrint("Server unavailable"); + return TS_CONNECTION_FAILED; + } - int wait = 1500; - while((WiFi.status() == WL_DISCONNECTED) && wait--) - delay(3); + transmission_status_t transmissionOutcome = exchangeInfo(currClient); + if (transmissionOutcome <= 0) + { + verboseModePrint("Transmission failed during exchangeInfo."); + return transmissionOutcome; + } + + currClient.stop(); + yield(); - /* If the connection timed out */ - if (WiFi.status() != 3) - return; + return transmissionOutcome; +} + +void ESP8266WiFiMesh::initiateConnectionToAP(const String &targetSSID, int targetChannel, uint8_t *targetBSSID) +{ + if(targetChannel == NETWORK_INFO_DEFAULT_INT) + WiFi.begin( targetSSID.c_str(), _meshPassword.c_str() ); // Without giving channel and BSSID, connection time is longer. + else if(targetBSSID == NULL) + WiFi.begin( targetSSID.c_str(), _meshPassword.c_str(), targetChannel ); // Without giving channel and BSSID, connection time is longer. + else + WiFi.begin( targetSSID.c_str(), _meshPassword.c_str(), targetChannel, targetBSSID ); +} + +/** + * Connect to the AP at SSID and transmit the mesh instance's current message. + * + * @param targetSSID The name of the AP the other node has set up. + * @param targetChannel The WiFI channel of the AP the other node has set up. + * @param targetBSSID The mac address of the AP the other node has set up. + * @returns: A status code based on the outcome of the connection and data transfer process. + * + */ +transmission_status_t ESP8266WiFiMesh::connectToNode(const String &targetSSID, int targetChannel, uint8_t *targetBSSID) +{ + if(staticIPActivated && lastSSID != "" && lastSSID != targetSSID) // So we only do this once per connection, in case there is a performance impact. + { + #ifdef ENABLE_STATIC_IP_OPTIMIZATION + 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. + WiFi.mode(WIFI_OFF); + WiFi.mode(WIFI_AP_STA); + 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."); + } + #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."); + #endif + } + lastSSID = targetSSID; + + verboseModePrint("Connecting... ", false); + initiateConnectionToAP(targetSSID, targetChannel, targetBSSID); + + int connectionStartTime = millis(); + int attemptNumber = 1; - /* Connect to the node's server */ - if (!curr_client.connect(SERVER_IP_ADDR, SERVER_PORT)) - return; + int waitingTime = millis() - connectionStartTime; + while((WiFi.status() == WL_DISCONNECTED) && waitingTime <= 10000) + { + if(waitingTime > attemptNumber * 10000) // 10000 can be lowered if you want to limit the time allowed for each connection attempt. + { + verboseModePrint("... ", false); + WiFi.disconnect(); + yield(); + initiateConnectionToAP(targetSSID, targetChannel, targetBSSID); + attemptNumber++; + } + delay(1); + waitingTime = millis() - connectionStartTime; + } - if (!exchangeInfo(message, curr_client)) - return; + verboseModePrint(String(waitingTime)); + + /* If the connection timed out */ + if (WiFi.status() != WL_CONNECTED) + { + verboseModePrint("Timeout"); + return TS_CONNECTION_FAILED; + } - curr_client.stop(); - WiFi.disconnect(); + return attemptDataTransfer(); } -void ESP8266WiFiMesh::attemptScan(String message) +void ESP8266WiFiMesh::attemptTransmission(const String &message, bool concludingDisconnect, bool initialDisconnect, bool noScan, bool scanAllWiFiChannels) { - /* Scan for APs */ - int n = WiFi.scanNetworks(); + setMessage(message); + + if(initialDisconnect) + { + WiFi.disconnect(); + yield(); + } + + latestTransmissionOutcomes.clear(); + + if(WiFi.status() == WL_CONNECTED) + { + transmission_status_t transmissionResult = attemptDataTransfer(); + latestTransmissionOutcomes.push_back(TransmissionResult(connectionQueue.back(), transmissionResult)); + } + else + { + if(!noScan) + { + verboseModePrint("Scanning... ", false); + + /* Scan for APs */ + connectionQueue.clear(); - for (int i = 0; i < n; ++i) { - String current_ssid = WiFi.SSID(i); - int index = current_ssid.indexOf( _ssid_prefix ); - uint32_t target_chip_id = (current_ssid.substring(index + _ssid_prefix.length())).toInt(); + // If scanAllWiFiChannels is true or Arduino core for ESP8266 version < 2.4.2 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; + #ifdef ENABLE_WIFI_SCAN_OPTIMIZATION + if(scanAllWiFiChannels) + { + n = WiFi.scanNetworks(); + } + 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); + } + #else + n = WiFi.scanNetworks(); + #endif + + _networkFilter(n, *this); // Update the connectionQueue. + } + + for(NetworkInfo ¤tNetwork : connectionQueue) + { + WiFi.disconnect(); + yield(); - /* Connect to any _suitable_ APs which contain _ssid_prefix */ - if (index >= 0 && (target_chip_id < _chip_id)) { + String currentSSID = ""; + int currentWiFiChannel = NETWORK_INFO_DEFAULT_INT; + uint8_t *currentBSSID = NULL; - WiFi.mode(WIFI_STA); - delay(100); - connectToNode(current_ssid, message); - WiFi.mode(WIFI_AP_STA); - delay(100); - } - } + // If an SSID has been assigned, it is prioritized over an assigned networkIndex since the networkIndex is more likely to change. + if(currentNetwork.SSID != "") + { + currentSSID = currentNetwork.SSID; + currentWiFiChannel = currentNetwork.wifiChannel; + currentBSSID = currentNetwork.BSSID; + } + else // Use only networkIndex + { + currentSSID = WiFi.SSID(currentNetwork.networkIndex); + currentWiFiChannel = WiFi.channel(currentNetwork.networkIndex); + currentBSSID = WiFi.BSSID(currentNetwork.networkIndex); + } + + if(_verboseMode) // Avoid string generation if not required + { + verboseModePrint("AP acquired: " + currentSSID + ", 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("... ", false); + } + + transmission_status_t transmissionResult = connectToNode(currentSSID, currentWiFiChannel, currentBSSID); + + latestTransmissionOutcomes.push_back(TransmissionResult{.origin = currentNetwork, .transmissionStatus = transmissionResult}); + } + } + + if(WiFi.status() == WL_CONNECTED && staticIP != emptyIP && !staticIPActivated) + { + verboseModePrint("Reactivating static IP to allow for faster re-connects."); + setStaticIP(staticIP); + } + + // If we do not want to be connected at end of transmission, disconnect here so we can re-enable static IP first (above). + if(concludingDisconnect) + { + WiFi.disconnect(); + yield(); + } } void ESP8266WiFiMesh::acceptRequest() { - while (true) { - _client = _server.available(); - if (!_client) - break; + //////////////////////////// TODO: REMOVE IN 2.5.0//////////////////////////// + if(_handler != NULL) + { + while (true) { + _client = _server.available(); + if (!_client) + break; + + if (!waitForClient(_client, 1500)) { + continue; + } + + /* Read in request and pass it to the supplied handler */ + String request = _client.readStringUntil('\r'); + _client.readStringUntil('\n'); + + String response = _handler(request); + + /* Send the response back to the client */ + if (_client.connected()) + _client.println(response); + } + } + else + { + //////////////////////////// TODO: REMOVE IN 2.5.0//////////////////////////// + while (true) { + WiFiClient _client = _server.available(); + + if (!_client) + break; - if (!waitForClient(_client, 1500)) { - continue; - } + if (!waitForClientTransmission(_client, 1500) || !_client.available()) { + continue; + } - /* Read in request and pass it to the supplied handler */ - String request = _client.readStringUntil('\r'); - _client.readStringUntil('\n'); + /* Read in request and pass it to the supplied requestHandler */ + String request = _client.readStringUntil('\r'); + yield(); + _client.flush(); - String response = _handler(request); + String response = _requestHandler(request, *this); - /* Send the response back to the client */ - if (_client.connected()) - _client.println(response); - } + /* Send the response back to the client */ + if (_client.connected()) + { + verboseModePrint("Responding"); + _client.print(response + "\r"); + _client.flush(); + yield(); + } + } + } } diff --git a/libraries/ESP8266WiFiMesh/src/ESP8266WiFiMesh.h b/libraries/ESP8266WiFiMesh/src/ESP8266WiFiMesh.h index dd959731bb..0cbd4407b6 100644 --- a/libraries/ESP8266WiFiMesh/src/ESP8266WiFiMesh.h +++ b/libraries/ESP8266WiFiMesh/src/ESP8266WiFiMesh.h @@ -1,9 +1,9 @@ /* ESP8266WiFiMesh.h - Mesh network node - Sets up a Mesh Node which acts as a router, creating a Mesh Network with other nodes. All information - is passed in both directions, but it is up to the user what the data sent is and how it is dealt with. + Sets up a Mesh Node which acts as a router, creating a Mesh Network with other nodes. Copyright (c) 2015 Julian Fell. All rights reserved. + Updated 2018 by Anders Löfgren. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public @@ -24,52 +24,278 @@ #include #include #include +#include +#include "NetworkInfo.h" +#include "TransmissionResult.h" + +#define ENABLE_STATIC_IP_OPTIMIZATION // Requires Arduino core for ESP8266 version 2.4.2 or higher and lwIP2 (lwIP can be changed in "Tools" menu of Arduino IDE). +#define ENABLE_WIFI_SCAN_OPTIMIZATION // Requires Arduino core for ESP8266 version 2.4.2 or higher. Scan time should go from about 2100 ms to around 60 ms if channel 1 (standard) is used. + +const String WIFI_MESH_EMPTY_STRING = ""; class ESP8266WiFiMesh { private: - String _ssid; - String _ssid_prefix; - uint32_t _chip_id; + String _SSID; + String _meshName; + String _nodeID; + int _serverPort; + String _meshPassword; + uint8 _meshWiFiChannel; + bool _verboseMode; + WiFiServer _server; + uint32_t _lwipVersion[3]; + static const uint32_t lwipVersion203Signature[3]; + String _message = WIFI_MESH_EMPTY_STRING; + + static String lastSSID; + static bool staticIPActivated; + static IPAddress staticIP; + static IPAddress gateway; + static IPAddress subnetMask; + static ESP8266WiFiMesh *apController; - std::function _handler; - - WiFiServer _server; - WiFiClient _client; + typedef std::function requestHandlerType; + typedef std::function responseHandlerType; + typedef std::function networkFilterType; - void connectToNode(String target_ssid, String message); - bool exchangeInfo(String message, WiFiClient curr_client); - bool waitForClient(WiFiClient curr_client, int max_wait); + requestHandlerType _requestHandler; + responseHandlerType _responseHandler; + networkFilterType _networkFilter; + void updateNetworkNames(const String &newMeshName = WIFI_MESH_EMPTY_STRING, const String &newNodeID = WIFI_MESH_EMPTY_STRING); + void verboseModePrint(const String &stringToPrint, bool newline = true); + void fullStop(WiFiClient &currClient); + 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); + transmission_status_t attemptDataTransfer(); + transmission_status_t attemptDataTransferKernel(); + void storeLwipVersion(); + bool atLeastLwipVersion(const uint32_t minLwipVersion[3]); + + + + + //////////////////////////// TODO: REMOVE IN 2.5.0//////////////////////////// + + typedef std::function compatibilityLayerHandlerType; + + String _ssidPrefix; + uint32_t _chipID; + + compatibilityLayerHandlerType _handler = NULL; + + WiFiClient _client; + + void connectToNode(const String &targetSSID, const char *message); + bool exchangeInfo(const char *message, WiFiClient &currClient); + bool waitForClient(WiFiClient &currClient, int maxWait); + void attemptScanKernel(const char *message); + + //////////////////////////// TODO: REMOVE IN 2.5.0//////////////////////////// + + + public: - /** - * WiFiMesh Constructor method. Creates a WiFi Mesh Node, ready to be initialised. - * - * @chip_id A unique identifier number for the node. - * @handler The callback handler for dealing with received messages. Takes a string as an argument which - * is the string received from another node and returns the string to send back. - * - */ - ESP8266WiFiMesh(uint32_t chip_id, std::function handler); - - /** - * Initialises the node. - */ - void begin(); - - /** - * Scan for other nodes, and exchange the chosen message with any that are found. - * - * @message The message to send to all other nodes. - * - */ - void attemptScan(String message); - - /** - * If any clients are connected, accept their requests and call the hander function for each one. - */ - void acceptRequest(); + //////////////////////////// TODO: REMOVE IN 2.5.0//////////////////////////// + + /** + * WiFiMesh Constructor method. Creates a WiFi Mesh Node, ready to be initialised. + * + * @chipID A unique identifier number for the node. + * @handler The callback handler for dealing with received messages. Takes a string as an argument which + * is the string received from another node and returns the string to send back. + * + */ + ESP8266WiFiMesh(uint32_t chipID, compatibilityLayerHandlerType handler); + + /** + * Scan for other nodes, and exchange the chosen message with any that are found. + * + * @message The message to send to all other nodes. + * + */ + void attemptScan(const String &message); + void attemptScan(char *message); + + template + void attemptScan(char (&message)[Size]); + + //////////////////////////// TODO: REMOVE IN 2.5.0//////////////////////////// + + ~ESP8266WiFiMesh(); + + /** + * WiFiMesh Constructor method. Creates a WiFi Mesh Node, ready to be initialised. + * + * @param requestHandler The callback handler for dealing with received requests. Takes a string as an argument which + * is the request string received from another node and returns the string to send back. + * @param responseHandler The callback handler for dealing with received responses. Takes a string as an argument which + * is the response string received from another node. Returns a transmission status code as a transmission_status_t. + * @param networkFilter The callback handler for deciding which WiFi networks to connect to. + * @param meshPassword The WiFi password for the mesh network. + * @param meshName The name of the mesh network. Used as prefix for the node SSID and to find other network nodes in the example network filter function. + * @param nodeID The id for this mesh node. Used as suffix for the node SSID. If set to "", the id will default to ESP.getChipId(). + * @param verboseMode Determines if we should print the events occurring in the library to Serial. Off by default. + * @param meshWiFiChannel The WiFi channel used by the mesh network. Valid values are integers from 1 to 13. Defaults to 1. + * 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. + * In such a case, whenever the station of one ESP8266WiFiMesh instance connects to an AP, it will silently force the + * WiFi channel of any active AP on the ESP8266 to match that of the station. This will cause disconnects and possibly + * make it impossible for other stations to detect the APs whose WiFi channels have changed. + * @param serverPort The server port used by the AP of the ESP8266WiFiMesh instance. If multiple APs exist on a single ESP8266, each requires a separate server port. + * If two AP:s on the same ESP8266 are using the same server port, they will not be able to have both server instances active at the same time. + * This is managed automatically by the activateAP method. + * + */ + 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); + + /** + * A vector that contains the NetworkInfo for each WiFi network to connect to. + * The connectionQueue vector is cleared before each new scan and filled via the networkFilter callback function once the scan completes. + * WiFi connections will start with connectionQueue[0] and then incrementally proceed to higher vector positions. + * Note that old network indicies often are invalidated whenever a new WiFi network scan occurs. + */ + static std::vector connectionQueue; + + /** + * A vector with the TransmissionResult for each AP to which a transmission was attempted during the latest attemptTransmission call. + * The latestTransmissionOutcomes vector is cleared before each new transmission attempt. + * Connection attempts are indexed in the same order they were attempted. + * Note that old network indicies often are invalidated whenever a new WiFi network scan occurs. + */ + static std::vector latestTransmissionOutcomes; + + /** + * Initialises the node. + */ + void begin(); + + /** + * Each AP requires a separate server port. If two AP:s are using the same server port, they will not be able to have both server instances active at the same time. + * This is managed automatically by the activateAP method. + */ + void activateAP(); + void deactivateAP(); + void restartAP(); + + /** + * Get the ESP8266WiFiMesh instance currently in control of the ESP8266 AP. + * Note that the result will be nullptr when there is no active AP controller. + * If another instance takes control over the AP after the pointer is created, + * the created pointer will still point to the old AP instance. + * + * @returns A pointer to the ESP8266WiFiMesh instance currently in control of the ESP8266 AP, + * or nullptr if there is no active AP controller. + */ + static ESP8266WiFiMesh * getAPController(); + + /** + * Check if this ESP8266WiFiMesh instance is in control of the ESP8266 AP. + * + * @returns True if this ESP8266WiFiMesh instance is in control of the ESP8266 AP. False otherwise. + */ + 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. + * + * 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. + * In such a case, whenever the station of one ESP8266WiFiMesh instance connects to an AP, it will silently force the + * WiFi channel of any active AP on the ESP8266 to match that of the station. This will cause disconnects and possibly + * make it impossible for other stations to detect the APs whose WiFi channels have changed. + * + * @param newWiFiChannel The WiFi channel to change to. Valid values are integers from 1 to 13. + * + */ + void setWiFiChannel(uint8 newWiFiChannel); + + String getMeshName(); + + /** + * 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. + * + * @param newMeshName The mesh name to change to. + */ + void setMeshName(const String &newMeshName); + + String getNodeID(); + + /** + * 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. + * + * @param newNodeID The node id to change to. + */ + void setNodeID(const String &newNodeID); + + /** + * 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. + * + * @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(); + + /** + * Set the message that will be sent to other nodes when calling attemptTransmission. + * + * @param newMessage The message to send. + */ + void setMessage(const String &newMessage); + + /** + * If AP connection already exists, and the initialDisconnect argument is set to false, send message only to the already connected AP. + * Otherwise, scan for other networks, send the scan result to networkFilter and then transmit the message to the networks found in connectionQueue. + * + * @param message The message to send to other nodes. It will be stored in the class instance until replaced via attemptTransmission or setMessage. + * @param concludingDisconnect Disconnect from AP once transmission is complete. + * @param initialDisconnect Disconnect from any currently connected AP before attempting transmission. + * @param noScan Do not scan for new networks and do not call networkFilter function. Will only use the data already in connectionQueue for the transmission. + * @param scanAllWiFiChannels Scan all WiFi channels during a WiFi scan, instead of just the channel the ESP8266WiFiMesh instance is using. + * Scanning all WiFi channels takes about 2100 ms, compared to just 60 ms if only channel 1 (standard) is scanned. + * Note that if the ESP8266 has an active AP, that AP will switch WiFi channel to match that of any other AP the ESP8266 connects to. + * This can make it impossible for other nodes to detect the AP if they are scanning the wrong WiFi channel. + */ + void attemptTransmission(const String &message, bool concludingDisconnect = true, bool initialDisconnect = false, bool noScan = false, bool scanAllWiFiChannels = false); + + /** + * If any clients are connected, accept their requests and call the requestHandler function for each one. + */ + void acceptRequest(); + + /** + * Set a static IP address for the ESP8266 and activate use of static IP. + * The static IP needs to be at the same subnet as the server's gateway. + */ + void setStaticIP(const IPAddress &newIP); + + IPAddress getStaticIP(); + void disableStaticIP(); + + /** + * An empty IPAddress. Used as default when no IP is set. + */ + 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 setNetworkFilter(networkFilterType networkFilter); }; #endif diff --git a/libraries/ESP8266WiFiMesh/src/NetworkInfo.cpp b/libraries/ESP8266WiFiMesh/src/NetworkInfo.cpp new file mode 100644 index 0000000000..10300dd7bf --- /dev/null +++ b/libraries/ESP8266WiFiMesh/src/NetworkInfo.cpp @@ -0,0 +1,77 @@ +/* + * NetworkInfo + * 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 "NetworkInfo.h" + +void NetworkInfo::copyBSSID(uint8_t newBSSID[6]) +{ + if(newBSSID != NULL) + { + if(BSSID == NULL) + { + BSSID = _bssidArray; + } + + for(int i = 0; i < 6; i++) + { + BSSID[i] = newBSSID[i]; + } + } + else + { + BSSID = NULL; + } +} + +NetworkInfo::NetworkInfo(int newNetworkIndex, bool autofill) : networkIndex(newNetworkIndex) +{ + if(autofill) + { + SSID = WiFi.SSID(newNetworkIndex); + wifiChannel = WiFi.channel(newNetworkIndex); + copyBSSID(WiFi.BSSID(newNetworkIndex)); + } +} + +NetworkInfo::NetworkInfo(const String &newSSID, int newWiFiChannel, uint8_t newBSSID[6], int newNetworkIndex) : + SSID(newSSID), wifiChannel(newWiFiChannel), networkIndex(newNetworkIndex) +{ + copyBSSID(newBSSID); +} + +NetworkInfo::NetworkInfo(const NetworkInfo &other) : SSID(other.SSID), wifiChannel(other.wifiChannel), networkIndex(other.networkIndex) +{ + copyBSSID(other.BSSID); +} + +NetworkInfo & NetworkInfo::operator=(const NetworkInfo &other) +{ + SSID = other.SSID; + wifiChannel = other.wifiChannel; + copyBSSID(other.BSSID); + networkIndex = other.networkIndex; + return *this; +} + diff --git a/libraries/ESP8266WiFiMesh/src/NetworkInfo.h b/libraries/ESP8266WiFiMesh/src/NetworkInfo.h new file mode 100644 index 0000000000..82fcd90c04 --- /dev/null +++ b/libraries/ESP8266WiFiMesh/src/NetworkInfo.h @@ -0,0 +1,69 @@ +/* + * NetworkInfo + * 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 __NETWORKINFO_H__ +#define __NETWORKINFO_H__ + +#include + +const int NETWORK_INFO_DEFAULT_INT = -1; + +class NetworkInfo { + +private: + + uint8_t _bssidArray[6] {0}; + +public: + + String SSID = ""; + int wifiChannel = NETWORK_INFO_DEFAULT_INT; + uint8_t *BSSID = NULL; + int networkIndex = NETWORK_INFO_DEFAULT_INT; + + /** + * @param autofill Automatically fill in the rest of the network info using newNetworkIndex and the WiFi scan results. + */ + NetworkInfo(int newNetworkIndex, bool autofill = true); + + /** + * Without giving channel and BSSID, connection time is longer. + */ + NetworkInfo(const String &newSSID, int newWiFiChannel = NETWORK_INFO_DEFAULT_INT, uint8_t newBSSID[6] = NULL, int newNetworkIndex = NETWORK_INFO_DEFAULT_INT); + + NetworkInfo(const NetworkInfo &other); + + NetworkInfo & operator=(const NetworkInfo &other); + + // No need for explicit destructor with current class design + + /** + * Copy newBSSID into BSSID. + * Prefer this method for changing NetworkInfo BSSID, unless you actually want to change the BSSID pointer. + */ + void copyBSSID(uint8_t newBSSID[6]); +}; + +#endif diff --git a/libraries/ESP8266WiFiMesh/src/TransmissionResult.cpp b/libraries/ESP8266WiFiMesh/src/TransmissionResult.cpp new file mode 100644 index 0000000000..81f9312461 --- /dev/null +++ b/libraries/ESP8266WiFiMesh/src/TransmissionResult.cpp @@ -0,0 +1,42 @@ +/* + * TransmissionResult + * 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 "TransmissionResult.h" + +TransmissionResult::TransmissionResult(int newNetworkIndex, transmission_status_t newTransmissionStatus, bool autofill) : + NetworkInfo(newNetworkIndex, autofill), transmissionStatus(newTransmissionStatus) +{ } + +TransmissionResult::TransmissionResult(const String &newSSID, int newWiFiChannel, uint8_t newBSSID[6], transmission_status_t newTransmissionStatus) : + NetworkInfo(newSSID, newWiFiChannel, newBSSID), transmissionStatus(newTransmissionStatus) +{ } + +TransmissionResult::TransmissionResult(const String &newSSID, int newWiFiChannel, uint8_t newBSSID[6], int newNetworkIndex, transmission_status_t newTransmissionStatus) : + NetworkInfo(newSSID, newWiFiChannel, newBSSID, newNetworkIndex), transmissionStatus(newTransmissionStatus) +{ } + +TransmissionResult::TransmissionResult(const NetworkInfo& origin, transmission_status_t newTransmissionStatus) : + NetworkInfo(origin), transmissionStatus(newTransmissionStatus) +{ } diff --git a/libraries/ESP8266WiFiMesh/src/TransmissionResult.h b/libraries/ESP8266WiFiMesh/src/TransmissionResult.h new file mode 100644 index 0000000000..8cc4cc020b --- /dev/null +++ b/libraries/ESP8266WiFiMesh/src/TransmissionResult.h @@ -0,0 +1,57 @@ +/* + * TransmissionResult + * 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 __TRANSMISSIONRESULT_H__ +#define __TRANSMISSIONRESULT_H__ + +#include +#include "NetworkInfo.h" + +typedef enum +{ + TS_CONNECTION_FAILED = -1, + TS_TRANSMISSION_FAILED = 0, + TS_TRANSMISSION_COMPLETE = 1 +} transmission_status_t; + +class TransmissionResult : public NetworkInfo { + +public: + + transmission_status_t transmissionStatus; + + /** + * @param autofill Automatically fill in the rest of the network info using newNetworkIndex and the WiFi scan results. + */ + TransmissionResult(int newNetworkIndex, transmission_status_t newTransmissionStatus, bool autofill = true); + + TransmissionResult(const String &newSSID, int newWiFiChannel, uint8_t newBSSID[6], transmission_status_t newTransmissionStatus); + + TransmissionResult(const String &newSSID, int newWiFiChannel, uint8_t newBSSID[6], int newNetworkIndex, transmission_status_t newTransmissionStatus); + + TransmissionResult(const NetworkInfo& origin, transmission_status_t newTransmissionStatus); +}; + +#endif diff --git a/libraries/ESP8266WiFiMesh/src/UtilityMethods.cpp b/libraries/ESP8266WiFiMesh/src/UtilityMethods.cpp new file mode 100644 index 0000000000..ecc36ca72f --- /dev/null +++ b/libraries/ESP8266WiFiMesh/src/UtilityMethods.cpp @@ -0,0 +1,127 @@ +/* + * TransmissionResult + * 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 "ESP8266WiFiMesh.h" +#include + +void ESP8266WiFiMesh::verboseModePrint(const String &stringToPrint, bool newline) +{ + if(_verboseMode) + { + if(newline) + Serial.println(stringToPrint); + else + Serial.print(stringToPrint); + } +} + +/** + * 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. + */ +void ESP8266WiFiMesh::storeLwipVersion() +{ + // ESP.getFullVersion() looks something like: + // SDK:2.2.1(cfd48f3)/Core:win-2.5.0-dev/lwIP:2.0.3(STABLE-2_0_3_RELEASE/glue:arduino-2.4.1-10-g0c0d8c2)/BearSSL:94e9704 + String fullVersion = ESP.getFullVersion(); + + int i = fullVersion.indexOf("lwIP:") + 5; + char currentChar = fullVersion.charAt(i); + + for(int versionPart = 0; versionPart < 3; versionPart++) + { + while(!isdigit(currentChar)) + { + currentChar = fullVersion.charAt(++i); + } + while(isdigit(currentChar)) + { + _lwipVersion[versionPart] = 10 * _lwipVersion[versionPart] + (currentChar - '0'); // Left shift and add digit value, in base 10. + currentChar = fullVersion.charAt(++i); + } + } +} + +/** + * Check if the code is running on a version of lwIP that is at least minLwipVersion. + */ +bool ESP8266WiFiMesh::atLeastLwipVersion(const uint32_t minLwipVersion[3]) +{ + for(int versionPart = 0; versionPart < 3; versionPart++) + { + if(_lwipVersion[versionPart] > minLwipVersion[versionPart]) + return true; + else if(_lwipVersion[versionPart] < minLwipVersion[versionPart]) + return false; + } + + return true; +}