From fba4ac15f6df7af715b949c07e7856ca80551591 Mon Sep 17 00:00:00 2001 From: Anders Date: Fri, 5 Jun 2020 21:56:33 +0200 Subject: [PATCH] - Make most type definitions public instead of protected, to facilitate use in composition. - Call ResponseTransmittedHook after every response transmission attempt, instead of after every successful response transmission attempt. - Improve documentation. - Finalize README.md. - Update keywords.txt. --- libraries/ESP8266WiFiMesh/README.md | 357 ++++++++++++++++-- .../examples/HelloEspnow/HelloEspnow.ino | 14 +- .../examples/HelloMesh/HelloMesh.ino | 5 +- libraries/ESP8266WiFiMesh/keywords.txt | 214 ++++++++++- .../ESP8266WiFiMesh/src/EspnowMeshBackend.h | 26 +- .../ESP8266WiFiMesh/src/EspnowTransmitter.cpp | 14 +- .../ESP8266WiFiMesh/src/EspnowTransmitter.h | 4 +- .../ESP8266WiFiMesh/src/FloodingMesh.cpp | 19 +- libraries/ESP8266WiFiMesh/src/FloodingMesh.h | 15 +- .../ESP8266WiFiMesh/src/MeshBackendBase.h | 4 +- .../ESP8266WiFiMesh/src/TcpIpMeshBackend.cpp | 2 +- 11 files changed, 592 insertions(+), 82 deletions(-) diff --git a/libraries/ESP8266WiFiMesh/README.md b/libraries/ESP8266WiFiMesh/README.md index 16e86aa57..89ae92507 100644 --- a/libraries/ESP8266WiFiMesh/README.md +++ b/libraries/ESP8266WiFiMesh/README.md @@ -2,56 +2,73 @@ ## Contents 1. [Overview](#Overview) - * [How does it work?](#Work) -2. [Tell me more!](#More) - * [EspnowMeshBackend](#EspnowMeshBackendMore) - * [TcpIpMeshBackend](#TcpIpMeshBackendMore) - * [FloodingMesh](#FloodingMeshMore) +2. [How does it work?](#Work) +3. [The first step](#Start) +4. [TcpIpMeshBackend](#TcpIpMeshBackendMore) + * [Usage](#TcpIpMeshBackendUsage) + * [Note](#TcpIpMeshBackendNote) + * [General Information](#TcpIpMeshBackendGeneral) +5. [EspnowMeshBackend](#EspnowMeshBackendMore) + * [Usage](#EspnowMeshBackendUsage) + * [Note](#EspnowMeshBackendNote) + * [Callbacks](#EspnowMeshBackendCallbacks) + * [Encryption](#EspnowMeshBackendEncryption) + * [CCMP](#CCMP) + * [AEAD](#AEAD) +6. [FloodingMesh](#FloodingMeshMore) + * [Usage](#FloodingMeshUsage) + * [Note](#FloodingMeshNote) + * [Serialization and the internal state of a node](#FloodingMeshSerialization) +7. [FAQ](#FAQ) + * [My ESP8266 crashes on start-up when I use the library!](#FAQStartupCrash) + * [The node does not remember the SSID I assign to it!](#FAQSSIDAmnesia) + * [I want to control the WiFi mode myself.](#FAQModeControl) + * [I have a lot of interference from all the nodes that are close to each other. What can I do?](#FAQInterference) + * [How do I change the interval of the WiFi AP beacon broadcast?](#FAQBeaconInterval) + * [My ESP is ignoring the WiFi AP beacon broadcast interval settings you just told me about above! (a.k.a. How do I change the WiFi scan mode to passive?)](#FAQPassiveScan) ## Overview This is a library for creating a mesh network using the ESP8266. -The library has been tested and works with Arduino core for ESP8266 version 2.7.2 (with lwIP2). It may work with earlier and later core releases, but this has not been tested during development. +The library has been tested and works with Arduino Core for ESP8266 version 2.7.2 (with lwIP2). It may work with earlier and later core releases, but this has not been tested during development. **Note:** This mesh library has been extensively rewritten for core release 2.7.2. The old method signatures have been retained for compatibility purposes, but will be removed in core release 3.0.0. If you are still using these old method signatures please consider migrating to the new API shown in the `EspnowMeshBackend.h` or `TcpIpMeshBackend.h` source files. -### How does it work? +## How does it work? -The ESP8266 WiFi Mesh library is a cake, metaphorically speaking. At the bottom you have the general ESP8266 Arduino core WiFi functionality. On top of this two mesh backends have been created (`EspnowMeshBackend` and `TcpIpMeshBackend`), a yummy filling that completely covers the bottom. Then at the very top over the backends is the beautiful and delicious frosting: `FloodingMesh`. `FloodingMesh` is an actual mesh network implementation that uses the `EspnowMeshBackend`. +The ESP8266 WiFi Mesh library is a cake, metaphorically speaking. At the bottom you have the general ESP8266 Arduino Core WiFi functionality. On top of this two mesh backends have been created (`EspnowMeshBackend` and `TcpIpMeshBackend`), a yummy filling that completely covers the bottom. Then at the very top over the backends is the beautiful and delicious frosting: `FloodingMesh`. `FloodingMesh` is an actual mesh network implementation that uses the `EspnowMeshBackend`. -Eating the cake would typically be a process which involves all the layers, but it is completely possible to use both the EspnowMeshBackend and the TcpIpMeshBackend separately from FloodingMesh, perhaps to construct your own mesh network architecture or just to simplify the usage of TCP/IP or ESP-NOW. If you have made a nice mesh architecture with this library that you would like to share with the rest of the world, feel free to make a PR with it! +Eating the cake in its current form is a process which involves all the layers. However, if you prefer to be your own pastry chef it is easy to use both the `EspnowMeshBackend` and the `TcpIpMeshBackend` separately from `FloodingMesh`, perhaps to construct your own mesh network architecture or just to simplify the usage of TCP/IP or ESP-NOW. If you have made a nice mesh architecture with this library that you would like to share with the rest of the world, feel free to make a PR with it! -There is plenty of details to the operations of the library, but if you want to get started quickly you really only need to know this: In the example folder of the library there is a file called `HelloMesh.ino`. Upload it to a few ESP8266 and you have a working mesh network. Change the `useLED` variable to true if you have built-in LEDs on your ESP8266s to illustrate how the message is spread through the network. Change the `floodingMesh.broadcast` calls to modify what the mesh nodes are transmitting to each other. +In general ESP-NOW is faster than TCP/IP for small data payloads (up to a few kB). The data segment of a standard ESP-NOW transmission is 234 bytes, which takes around 2-4 ms to transmit. + +TCP/IP takes longer to connect (around 1000 ms), and an AP has to disconnect all connected stations in order to transfer data to another AP, but this backend has a much higher data transfer speed than ESP-NOW once connected (100x faster or so). + +## The first step + +There are plenty of details to the operations of the library, but if you want to get started quickly you really only need to know this: In the example folder of the library there is a file called `HelloMesh.ino`. Upload it to a few ESP8266 and you have a working mesh network. Change the `useLED` variable to `true` if you have built-in LEDs on your ESP8266s to illustrate how the message is spread through the network. Change the `floodingMesh.broadcast` calls to modify what the mesh nodes are transmitting to each other. Change the code of the `meshMessageHandler` to modify how mesh nodes react to received transmissions. Finally, three things are important to note: -1. This library uses the standard Arduino core for ESP8266 WiFi functions. Therefore, other code that also uses these WiFi functions (e.g. `WiFi.mode()`) may cause conflicts with the library, resulting in strange behaviour. See the FAQ for ideas on how to work around this. +1. This library uses the standard Arduino Core for ESP8266 WiFi functions. Therefore, other code that also uses these WiFi functions (e.g. `WiFi.mode()`) may cause conflicts with the library, resulting in strange behaviour. See "[I want to control the WiFi mode myself](#FAQModeControl)" in the FAQ for ideas on how to work around this. 2. Both the `EspnowMeshBackend` and the `TcpIpMeshBackend` can be used simultaneously on the same node. However, since there is only one WiFi radio on the ESP8266, only one backend at a time will be responsible for the settings of this radio (SSID, WiFi channel etc.). The backend in control is known as the `APController` in the library. Both backends can still send messages, regardless of who is `APController`. -3. The `MeshBackendBase`, `EspnowMeshBackend`, `TcpIpMeshBackend` and `FloodingMesh` source files are supposed to be the main front-ends of the library and are all extensively documented. If you wonder how something is working, chances are good that you will find an answer in the documentation of those files. +3. The `MeshBackendBase`, `EspnowMeshBackend`, `TcpIpMeshBackend` and `FloodingMesh` source files are meant to be the main front-ends of the library and are all extensively documented. If you wonder about how something is working, chances are good that you will find an answer in the documentation of those files. -## Tell me more! +## TcpIpMeshBackend -### EspnowMeshBackend - -#### Usage - -The basic operation of the ESP-NOW mesh backend is as follows: - -### TcpIpMeshBackend - -#### Usage +### Usage The basic operation of the TCP/IP mesh backend is as follows: -The `attemptTransmission` method of the TcpIpMeshBackend 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 TcpIpMeshBackend 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 TcpIpMeshBackend instance. The outcome from each transmission attempt can be found in the `latestTransmissionOutcomes` vector. +The `attemptTransmission` method of the TcpIpMeshBackend 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 the default behaviour is for a WiFi scan to be performed. The scan results are sent to the `networkFilter` callback function of the TcpIpMeshBackend 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 TcpIpMeshBackend instance. The outcome from each transmission attempt can be found in the `latestTransmissionOutcomes` vector. -The node receives messages from other TCP/IP nodes by calling the `acceptRequest` method of the TcpIpMeshBackend 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. Since received ESP-NOW messages are handled via a callback, there is no need to call `acceptRequest` to receive these. +The node receives messages from other TCP/IP nodes by calling the `acceptRequests` method of the TcpIpMeshBackend 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 HelloTcpIp example. The main functions to modify in the example are `manageRequest` (`requestHandler`), `manageResponse` (`responseHandler`), `networkFilter` and `exampleTransmissionOutcomesUpdateHook`. There is also much more information to be found in the source code comments. -#### Note +### 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. When using static IP, the following is good to keep in mind: @@ -63,7 +80,7 @@ For more details, see the included HelloTcpIp example. The main functions to mod Static IP is a global setting (for now), meaning that all TcpIpMeshBackend instances on the same ESP8266 share the same static IP settings. -* Scanning all WiFi channels (e.g. via the `attemptTransmission` method with the `scanAllWiFiChannels` argument set to true) will slow down scans considerably and make it more likely that existing WiFi connections will break during scans. +* Scanning all WiFi channels (e.g. via the `attemptTransmission` method with the `scanAllWiFiChannels` argument set to `true`) will slow down scans considerably and make it more likely that existing WiFi connections will break during scans. * If the ESP8266 has an active AP, that AP will switch WiFi channel to match that of any other AP the TcpIpMeshBackend of 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: @@ -78,18 +95,294 @@ For more details, see the included HelloTcpIp example. The main functions to mod * Also, remember to change the default mesh network WiFi password! -#### General Information +### General Information * 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 TCP/IP 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). -### FloodingMesh +## EspnowMeshBackend -The FloodingMesh exclusively uses the `EspnowMeshBackend`. The mesh network size is only limited by available MAC addresses, so the maximum is (2^48)/2 = 140 trillion give or take. However, the maximum throughput of the FloodingMesh is around 100 messages per second with 234 bytes per message, so using the maximum number of nodes is not recommended in most cases. +Unlike the TcpIpMeshBackend, the ESP-NOW backend uses pure callbacks even for message reception. This means that whenever `delay()` is called or the `loop()` function returns, the ESP-NOW backend will automatically check if an ESP-NOW message has been received and send it to the correct callback. There is no need to call `acceptRequests` as for the TcpIpMeshBackend. As a result of this, it is possible to receive an ingoing ESP-NOW transmission at the same time as an outgoing ESP-NOW transmission is in progress. This will likely be noted as a spike in the usual transmission time, the size of which will depend on the execution time of `requestHandler`/`responseHandler` (determined by transmission type). -As the name implies, FloodingMesh is a simple flooding mesh architecture, which means it stores no mesh network routing data in the nodes but only passes new messages on. It therefore has no RAM overhead for network size, which is important for the ESP8266 since available RAM is very limited. The downside is that there is a lot of network traffic for each sent message, so especially for dense networks a lot of interference will be created. Based on tests, a mesh with 30 nodes close together will work well (1-2 dropped messages of 1000). A mesh with around 160 nodes close together will not work at all (though this would probably be solved by spreading out the nodes more, so the interference is reduced). +Some ESP-NOW tasks cannot be securely handled via callbacks. To manage this there are `espnowDelay` and `performEspnowMaintenance` functions available which handle these tasks separately. Either of these methods should be called regularly when your node has some time over for handling background tasks. -#### Usage +### Usage -Change the `useLED` variable to true if you have built-in LEDs on your ESP8266s to illustrate how the message is spread through the network. Change the `floodingMesh.broadcast` calls to modify what the mesh nodes are transmitting to each other. \ No newline at end of file +There are two primary ways to send an ESP-NOW message: `broadcast` and `attemptTransmission`. + +If `broadcast` is used, the message is sent to all surrounding nodes in one transmission without any WiFi scan. When the surrounding nodes receive the broadcast they will send it to the `broadcastFilter` callback of the EspnowMeshBackend instance, and based on the return value of this callback either accept or reject the broadcast. The `broadcastFilter` callback is also responsible for removing any metadata from the broadcast. + +If `attemptTransmission` is used, a WiFi scan is by default performed before the transmission. The scan results are sent to the `networkFilter` callback function of the EspnowMeshBackend instance which adds the AP:s of interest to the `connectionQueue` vector. The message is then transmitted to the nodes in the `connectionQueue`. The outcome from each transmission attempt can be found in the `latestTransmissionOutcomes` vector. + +Regardless of whether `broadcast` or `attemptTransmission` is used, when a node receives a message (and it is accepted), the message is passed to the `requestHandler` callback of the EspnowMeshBackend instance. For each received message the return value of `requestHandler` is stored as a response in the `responsesToSend` waiting list. These stored responses will then be sent whenever `performEspnowMaintenance` (or `espnowDelay`) is called. + +When the response is received by the node that sent the request, the response message is forwarded to the `responseHandler` callback of the EspnowMeshBackend instance that sent the request. + +To be completely clear, requests are actually passed to the `broadcastFilter` and `requestHandler` callbacks belonging to the `EspnowRequestManager` of the node, but as long as there is only one EspnowMeshBackend instance on the node this will be the `EspnowRequestManager`. Also, since received ESP-NOW messages are handled via a callback, there is no need to call `acceptRequests` to receive messages, unlike with the TcpIpMeshBackend. + +The EspnowMeshBackend has a few different options for encrypting messages. This is described in greater detail in the [Encryption](#EspnowMeshBackendEncryption) section below. + +More information can be found in the source code comments and in the included HelloEspnow example. The main functions to modify in the example are `manageRequest` (`requestHandler`), `manageResponse` (`responseHandler`), `networkFilter` and `broadcastFilter`. + +### Note + +* `yield()` can cause crashes when using ESP-NOW, since the command requires code to be run in the CONT context. If you are having problems with this, use `delay()` instead. + +* This library uses the ESP8266 modules' MAC addresses to keep track of transmissions. So if you need to change the MAC addresses do so with care and preferably before any transmission is made. +Turning the AP off will make it impossible to send information to the node AP mac. However, it will still be possible to send the data to the station mac. +To do this, send over the station mac to the transmitting node and then manually add it to the `connectionQueue` whenever a transmission should be made to that node. + +* If the available heap goes under `criticalHeapLevel()` bytes (6000 bytes by default), the ESP-NOW backend will temporarily cease accepting new incoming ESP-NOW requests in an attempt to avoid running out of RAM. Warning messages about this will also be printed to the Serial Monitor, assuming `printWarnings()` is `true` (this is the default value). + +* During very heavy load the `performEspnowMaintenance` method may occasionally need to process requests for tens of milliseconds. Since this won't happen until the method is called, you can choose when this is done. Callbacks can be executed while the request processing is ongoing, but note that they should have a very fast execution time in this case. Also be sure to take into account the callback restrictions mentioned [below](EspnowMeshBackendCallbacks). + +* When `WiFi.mode(WIFI_STA)` is used, nodes are unable to receive ESP-NOW broadcast messages. All nodes can however still receive direct ESP-NOW messages to their STA mac. Nodes seem to continue transmitting successfully to the correct (broadcast) MAC regardless of WiFi mode, only message reception is affected. Different combinations of ESP-NOW roles do not seem to have any influence on the outcome. Stripping out all library code and only using the bare minimum required for a broadcast does not change the outcome. Thus, this issue seems to be unfixable until corrected by Espressif. + + During testing it seemed for a while as though some nodes were able to receive ESP-NOW broadcasts even when in STA mode. There was no obvious difference between the nodes for which this worked and those for which it did not, so what caused this is unknown. Possibly the issue could have been caused by something stored on the nodes, perhaps a different persistent WiFi config or something similar. It is of course also possible that there was an error made during testing, but the event is noted here as it could be an avenue for further investigation. + +* Although ESP-NOW responses will generally be sent in the order they were created, this is not guaranteed to be the case. For example, response order will be mixed up if some responses first fail to transmit while others transmit successfully. Use the `ResponseTransmittedHook`callback if this behaviour should be modified. + +### Callbacks + +For maximum performance and minimum RAM usage it is very important that your callbacks and hooks can be handled quickly (within a few milliseconds, preferably), as node performance can start to suffer quickly otherwise, particularly if transmission intensity is high. Be especially wary of long Serial prints, as these require a lot of time to complete. If transmission activity is very low, it is however possible to have callbacks which take a long time to complete. In these cases, even a callback execution time of multiple seconds can be acceptable. Of course, you would get problems with other parts of the Arduino Core framework (like watch dog timer resets) if you don't call `delay()` or `ESP.wdtFeed()` within that time. + +Certain methods of the EspnowMeshBackend (e.g. `attemptTransmission`, `broadcast`, `espnowDelay` and `performEspnowMaintenance`) should not be used within callbacks, since this can mess with the internal state of the backend. These methods are all using a `MutexTracker` component to enforce this requirement via asserts, so if your nodes are crashing for unknown reasons when using callbacks, make sure to check the Serial Monitor to see if there are any mutex error messages! + +One way to resolve such errors is to simply always call the sensitive methods from the `loop()` instead of from a callback, possibly just storing the received value for later inside the callback. [PolledTimeout](https://github.com/esp8266/Arduino/blob/master/cores/esp8266/PolledTimeout.h) can be helpful for time tracking in this case. + +If a callback with the sensitive methods is required, it has been reported that the methods in `TaskScheduler.h` of the [TaskScheduler library](https://github.com/arkhipenko/TaskScheduler) work well when scheduling tasks. It can in this role be used as a replacement of the [Ticker](https://arduino-esp8266.readthedocs.io/en/latest/libraries.html#ticker) functionality in the Arduino Core. + +The reason the callback limitations exist is that during a transmission the library will only get an ack from the receiver when `delay()` is used. Yet `delay()` also calls all other background tasks, including user callbacks, and these must thus be safe to execute during ongoing transmissions. + +### Encryption + +There are two separate methods for encrypting a message with the ESP-NOW backend. One method creates an encrypted connection between two nodes using the built-in CCMP encryption of the ESP8266. The other method simply uses software AEAD to encrypt and decrypt the messages sent. + +More in-depth information about the encryption methods of the framework can be found at the top of the EspnowMeshBackend.h and EspnowProtocolInterpreter.h files. + +A brief overview of the advantages of each method: + +AEAD + +* The AEAD encryption does not require any pairing, and is thus faster for single messages than establishing a new encrypted connection before transfer. + +* AEAD encryption also works with ESP-NOW broadcasts and supports an unlimited number of nodes, which is not true for encrypted connections. + +CCMP + +* Using AEAD will only encrypt the message content, not the transmission metadata. CCMP encryption covers both. + +* Encrypted ESP-NOW connections come with built in replay attack protection, which is not provided by the framework when using AEAD encryption. + +* Encrypted ESP-NOW connections also allow `EspnowProtocolInterpreter::aeadMetadataSize` extra message bytes per transmission. + +* Transmissions via encrypted connections are also slightly faster than via AEAD once a connection has been established. + +#### CCMP + +For encrypted connections (managed via such methods as `addEncryptedConnection`, `requestEncryptedConnection` and `requestEncryptedConnectionRemoval`), ESP-NOW [uses](https://www.espressif.com/sites/default/files/documentation/esp-now_user_guide_en.pdf) [CCMP encryption](https://en.wikipedia.org/wiki/CCMP_(cryptography)). +To handle some idiosyncrasies of ESP-NOW (like having no way in the application layer to know if received information is encrypted or not), a separate API layer has been built on top. +This API layer is provided in the hope that it will be useful, but has not been subject to any cryptographic validation (yet, feel free to have a go at it if you have the knowledge). +The goal of the API layer is to ensure that when an encrypted connection is established, the received encrypted messages will both be marked as encrypted and be trustworthy. + +Established encrypted connections can be either permanent or temporary. A permanent encrypted connection can only be removed by explicitly calling `removeEncryptedConnection` or `requestEncryptedConnectionRemoval`. A temporary encrypted connection will expire once the duration has passed, although this duration can be updated through the methods used for adding new encrypted connections. + +The maximum number of simultaneous encrypted connections is restricted by the ESP-NOW specifications and is `EspnowProtocolInterpreter::maxEncryptedConnections` (6 by default). If required, a stricter soft upper limit can be used for the number of encrypted connections a node can have when receiving encrypted connection requests, to ensure there is normally some margin to the hard maximum. This is handled via the`setEncryptedConnectionsSoftLimit` method. + +The internal state of an encrypted connection will be lost if the ESP8266 is restarted or loses power, meaning encrypted messages will no longer be received. There is however functionality available to serialize the state of an encrypted connection so it can be restored later. The HelloEspnow.ino example file shows how this is done. Of course, a stored state should only be used once, since the communication will otherwise be susceptible to replay attacks. See "[Serialization and the internal state of a node](#FloodingMeshSerialization)" in the FloodingMesh docs for more info. + +Some security considerations for CCMP encrypted connections are listed below. + +* Part of the separate API layer uses the internal hardware random number generator of the ESP8266 (via `ESP.random()`) to initialize the connection state. This may or may not have enough entropy for your security needs. +For an even more random (but slower) number generation, you may want to replace the use of plain `ESP.random()` with something else. + +* Since there is no way to know whether a received transmission is encrypted or not via the default ESP-NOW API, an attacker can send unencrypted ESP-NOW messages which pretend to be encrypted without this being detected by the application. To prevent such attacks from happening, this framework uses an extra 64 bit session key for all encrypted connections. A message is only accepted as encrypted if it has the correct session key. 64 bits are used mainly because the uint64_t datatype is the largest natively supported by the ESP8266 Arduino Core, and because each ESP-NOW transmission has a relatively small maximum capacity of 250 bytes. + +* The ESP-NOW CCMP encryption should according to the standard have replay attack protection built in, but there is no official documentation from Espressif about this. The 64 bit session key used for encrypted connections, as described above, will however also ensure replay protection. + +* The maximum rate at which a potential attacker can poll a session key (via unencrypted transmissions pretending to be encrypted transmissions) is around 0.3 keys per ms, but in practice this rate would render the node completely unresponsive and is thus easily detected. +Assuming the rate above is used that would mean that an attacker in one day could try 0.3 x 1000 x 60 x 60 x 24 = 25 920 000 keys, which is roughly 1/711 600 000 000 of the total (total is 2^(64) - 2^(32), the top 32 session key bits are all 0 when the transmission is unencrypted). + +* Should there be a need for even more security, the user could enhance the library with 128 bit (or more) session keys, or ensure CCMP encrypted messages are sent frequently since this will rehash the session key every time, or frequently remove and re-add the encrypted connections (which will cause the session keys to be randomized or set to the supplied values). + +#### Authenticated Encryption with Associated Data (AEAD) + +In addition to using encrypted ESP-NOW connections the framework can send automatically encrypted messages (using AEAD) over both encrypted and unencrypted connections. This message encryption is conditioned on the `useEncryptedMessages()` flag of the EspnowMeshBackend. Typically, activating the AEAD encryption would be done like so: +``` +espnowBackendInstance.setEspnowMessageEncryptionKey(F("ChangeThisKeySeed_TODO")); // The message encryption key should always be set manually. Otherwise a default key (all zeroes) is used. +espnowBackendInstance.setUseEncryptedMessages(true); +``` + +The AEAD protocol uses the ChaCha20 stream cipher with Poly1305 for message authentication. +More information about this encryption standard can be found here: https://tools.ietf.org/html/rfc7539 , https://tools.ietf.org/html/rfc8439 + +## FloodingMesh + +**Important:** As of now, the ESP8266 must have the AP active to receive mesh messages (either via AP mode (use only if CCMP encryption is not required) or AP+STA mode). Messages can however be transmitted even when the AP is turned off. This is limited by the Espressif binary in the ESP8266 Arduino Core and so cannot be corrected by the library code. + +The FloodingMesh exclusively uses the `EspnowMeshBackend`. The mesh network size is only limited by available MAC addresses, so the maximum is (2^48)/2 = 140 trillion give or take. However, the maximum throughput of the FloodingMesh is around 100 messages per second with 234 bytes per message, so using the maximum number of nodes is not recommended in most cases. Note that while ASCII characters require 1 byte each, non-ASCII characters usually require 2 bytes each. + +As the name implies, FloodingMesh is a simple flooding mesh architecture, which means it stores no mesh network routing data in the nodes but only passes new messages on to all surrounding nodes. It therefore has no RAM overhead for network size, which is important for the ESP8266 since available RAM is very limited. The downside is that there is a lot of network traffic for each sent message, and all nodes use the same WiFi channel, so especially for dense networks a lot of interference will be created. Based on tests, a mesh with 30 nodes close together (-44 dBm RSSI) will work well (1-2 dropped messages of 1000). A mesh with around 160 nodes close together will not work at all (though this would probably be solved by spreading out the nodes more, so the interference is reduced). + +### Usage + +There are two primary ways to send a message in FloodingMesh: `broadcast` and `encryptedBroadcast`. + +Messages sent via `encryptedBroadcast` use CCMP encryption. Messages sent via `broadcast` are by default unencrypted, but can optionally be encrypted with AEAD encryption. See the "[Encryption](#EspnowMeshBackendEncryption)" segment of the EspnowMeshBackend documentation for more information on the forms of encryption. + +The main advantage of `encryptedBroadcast` over `broadcast` is that replay attack protection comes built-in. However, `encryptedBroadcast` is currently slow and experimental so for now `broadcast` is the recommended method to use. This means that replay attacks must be handled separately in a manner suitable for your application (e.g. by adding a counter to your messages or just designing your application so repeated messages is not an issue). + +When `broadcast` is used, the message is sent to all surrounding nodes in one transmission without any WiFi scan. + +When a FloodingMesh node receives a message it will first check in its logs to see if the message ID has been received before. If the message ID is not found, the message will be passed to the `meshMessageHandler` of the FloodingMesh instance. + +If `meshMessageHandler` returns `false`, the message will not be propagated from the node. If `meshMessageHandler` returns `true`, the message (including any modifications made to it by the `meshMessageHandler`) will be stored in the `forwardingBacklog`. Messages stored in this way are automatically sent to all surrounding nodes via a new `broadcast` or `encryptedBroadcast` (same method as used for the received message) whenever `performMeshMaintenance()`, `performMeshInstanceMaintenance()` or `floodingMeshDelay` is called. + +For advanced users, the behaviour of FloodingMesh can easily be modified on the fly by changing the callbacks of the EspnowMeshBackend instance used by the FloodingMesh. The default behaviour can then be restored by calling the `restore` method for the respective callbacks. E.g. messages to forward in the FloodingMesh are by default stored in the `_defaultRequestHandler`, so call `floodingMeshInstance.getEspnowMeshBackend().setRequestHandler` with your own `requestHandler` function to modify this behaviour. + +More details can be found in the source code comments of both FloodingMesh and EspnowMeshBackend, as well as in the included HelloMesh example. The main function to modify in the example is `meshMessageHandler`. You can also change the `useLED` variable in the example to `true` if you have built-in LEDs on your ESP8266s to get visual feedback on how the message is spread through the mesh network. + +Note that there is no mesh recovery code in the HelloMesh example. It only selects one node (which is marked via the onboard LED if the `useLED` variable is `true`) and makes it continuously transmit. So if the selected node goes offline, no new transmissions will be made. One way to make the example mesh recover is to add a timeout to re-start the selection process if no message is received after a while. However, in practice you will probably want most or all nodes to broadcast their own messages, not just one selected node, so such a recovery timeout will not be useful in that context. + +**I want to know all the nodes in my FloodingMesh. What do I do?** + +To get a list of all nodes in the HelloMesh.ino example, you will have to make broadcast transmissions such as `floodingMesh.broadcast("Register MAC");` and then add code to register previously unknown `meshInstance.getOriginMac()` in the `meshMessageHandler`. + +**What's the best method to get the number of FloodingMesh nodes around me?** + +You could do a WiFi scan if you just want to see the nodes around you (if WiFi AP is enabled). Or you could make the nodes transmit and pick up the MACs with `meshInstance.getEspnowMeshBackend().getSenderMac()` in the `meshMessageHandler`. + +### Note + +Since FloodingMesh is based on EspnowMeshBackend, it shares all the limitations described for that backend above. In addition there are some more specific issues to keep in mind. + +* The network needs enough time to re-broadcast messages. In practice, if the mesh transmits more than 100 new messages per second (in total), there is a risk of running out of RAM since more messages will be received by the nodes than they can re-transmit. + +* A too low value for `messageLogSize` can result in a message storm since the number of "active" messages will be greater than the log size, resulting in messages that bounce around in the network without end. The message log stores all unique FloodingMesh message IDs seen by a node, with more recent ones replacing the older ones when `messageLogSize` is reached. This means that a node in a mesh network containing 2 nodes will have to send `messageLogSize + 1` transmissions to cause the message log of the other node to forget the first message, while a node in a mesh network containing 101 nodes will have to send 1 % as many messages (on average) to do the same. + + Use `FloodingMesh::setMessageLogSize` to adapt the log size to your needs. A larger log size will of course lead to a higher RAM usage. + +### Serialization and the internal state of a node + +The internal state of a node will be lost if it is restarted or loses power. There is however a method called `serializeMeshState()` available in FloodingMesh to serialize the state of a node so it can be restored later. Of course, a stored state should only be used once, since the communication will otherwise be susceptible to replay attacks. + +For the node state of FloodingMesh there are a few things to keep in mind. + +1. If you use the serialization functionality everything should just work. +2. If all nodes go to sleep without serializing, they will of course lose their memory but the network will be recreated and work as normal when the nodes wake up. +3. If only some nodes go to sleep without serializing the state, things get more complicated. The following is possible: + * If you use encryptedBroadcast, the nodes that wake up may silently ignore messages forever from the nodes they used to have an encrypted connection with. + * If you do not use encryptedBroadcasts the ESP-NOW backend will by default clear its message ID logs in 2.5 seconds (`logEntryLifetimeMs`) and FloodingMesh will have done the same after 100 new message IDs have been received (`messageLogSize`). Once the logs of both classes have been cleared, things will work as normal. Before that any new message the awoken node sends may have the same ID as an old message, and will then be silently ignored by the receiver. + +The messageID is always used together with the node MAC of the sender. For details on how the ID is generated, check out the `generateMessageID` methods. + +It is important to realize that there is no global message ID counter, only the local received message IDs for each node in the network. Automatic resynchronizing with this local value is currently only supported for encrypted connections, which exist exclusively between two nodes. For unencrypted connections, `addUnencryptedConnection` may be used manually for similar purposes. + +## FAQ + +### My ESP8266 crashes on start-up when I use the library! + +This could be caused by incorrect arguments to the constructors of the library. Usually you would get a Serial Monitor print of the error in question, but if the constructor is called before you call `Serial.begin(115200)` then there will be nothing to print to. The solution is first to check so that all constructor arguments are valid, e.g. that the mesh password has the correct length and does not contain any forbidden characters. If everything checks out you can try to move all the library contructors you use into the `setup()` function of your sketch, after the position where `Serial.begin(115200)` is called. That should give you a proper error message in the Serial Monitor, so you can locate the problem. + +### The node does not remember the SSID I assign to it! + +All example files use `WiFi.persistent(false)` in the `setup()` function, so if you switch the AP off and on again only by using `WiFi.mode()` without the framework methods (`activateAP`/`deactivateAP`), it is likely your last persisted SSID is used, not the one you set in the FloodingMesh/EspnowMeshBackend/TcpIpMeshBackend constructor. The solution is to always use the framework methods to turn the AP on and off, or to follow the instructions below for controlling WiFi mode. + +### I want to control the WiFi mode myself. + +By default the mesh library assumes it is the only code in charge of managing the WiFi. So it expects to be the middle man when the user wants to do something WiFi related. + +That being said, there are some relatively simple ways to go around this. Note that the steps below are not officially supported and may break in future library versions. + +The key to solving this is to note that the only methods of EspnowMeshBackend and FloodingMesh which interact with the WiFi mode is `begin()`, `activateAP()` and `deactivateAP()` (for TcpIpMeshBackend `attemptTransmission` should be added to this list). Let's take a look at the methods: + +``` +void EspnowMeshBackend::begin() +{ + if(!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. + + activateEspnow(); +} + +void MeshBackendBase::activateAP() +{ + // Deactivate active AP to avoid two servers using the same port, which can lead to crashes. + if(MeshBackendBase *currentAPController = MeshBackendBase::getAPController()) + currentAPController->deactivateAP(); + + activateAPHook(); + + WiFi.mode(WIFI_AP_STA); + + apController = this; +} + +void MeshBackendBase::activateAPHook() +{ + WiFi.softAP( getSSID().c_str(), getMeshPassword().c_str(), getWiFiChannel(), getAPHidden() ); // Note that a maximum of 8 TCP/IP stations can be connected at a time to each AP, max 4 by default. +} + +void MeshBackendBase::deactivateAP() +{ + if(isAPController()) + { + deactivateAPHook(); + + WiFi.softAPdisconnect(); + WiFi.mode(WIFI_STA); + + // Since there is no active AP controller now, make the apController variable point to nothing. + apController = nullptr; + } +} + +void MeshBackendBase::deactivateAPHook() +{ +} +``` + +As you can see, there is nothing in `activateAP` and `deactivateAP` that you cannot do yourself. You do not have to worry about`apController` since it is only used if the mesh library is actually managing an AP (i.e. if `activateAP()` has been called), and the rest is standard Arduino Core WiFi calls. All you have to do then is to call `begin()` once when your program starts and then take responsibility yourself for activating and deactivating an AP with the correct SSID. Essentially, you would create the following function: + +``` +void myActivateAP() +{ + WiFi.softAP( SSID, password, WiFiChannel ); // You can store these values in the mesh backend and call the respective getters, but then you also have to set the backend values whenever they change. + WiFi.mode(WIFI_AP_STA); // Can also be WiFi.mode(WIFI_AP) +} +``` + +Please note that having an AP active is required when receiving broadcasts with FloodingMesh and EspnowMeshBackend (transmitting broadcasts work even when the AP is off). Regular `attemptTransmission` will work with AP turned off if the recipient STA MAC is already known (then you can set WiFi mode to any mode you like, apart from `WIFI_OFF`). + +When an AP is required, AP+STA mode is used in the ESP-NOW backend to keep compatibility with the TCP/IP backend (both backends can be used at the same time). The reason AP+STA mode is used in the TCP/IP backend can be found in TcpIpMeshBackend.cpp : "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." +Also, AP+STA mode allows encrypted ESP-NOW connections to recover from failure in some cases. + +So in summary, you can solve this by calling `begin()` once and then only using the library methods that do not interact with the WiFi mode. As long as you manage your own AP. + +### I have a lot of interference from all the nodes that are close to each other. What can I do? + +In general, you can switch WiFi channel for some nodes (use only channel 1, 6 and 11 for optimal spread, remember that nodes on different WiFi channels cannot communicate directly with each other), try to improve signal quality, or try to reduce interference by reducing the amount of transmissions in the network. + +If using FloodingMesh you can try to experiment with reducing error rates by using the mesh method `void setBroadcastReceptionRedundancy(uint8_t redundancy);` (default 2) at the cost of more RAM. + + +With both FloodingMesh and the EspnowMeshBackend it is possible to use `floodingMesh.getEspnowMeshBackend().setBroadcastTransmissionRedundancy(uint8_t redundancy)` (default 1) to increase the chance of a message arriving at the cost of longer transmission times. + +For reducing the amount of transmissions in the network, that will either require you to optimize your transmission usage or reduce the amount of background protocol transmissions. The latter option is described in greater detail in the two answers below. + +### How do I change the interval of the WiFi AP beacon broadcast? + +Currently this requires hacking your Arduino Core source files. At [line 122](https://github.com/esp8266/Arduino/blob/8ee67ab2b53463466fd9f035eef2c542ad9a6775/libraries/ESP8266WiFi/src/ESP8266WiFiAP.cpp#L122) in `ESP8266WiFiAP.cpp` you will find the following line `conf.beacon_interval = 100;` (within the `softAp` method). You can change 100 to any value in the range [100, 60000] ms. If you are having problems with too many AP beacon broadcasts in a mesh network, increasing this value should help you with that. To prevent all nodes from beaconing at the same time, delay initial AP activation by a random value in the range [0, x] and then change `conf.beacon_interval` to x, for some large value x <= 60000 ms (same for all nodes). + +### My ESP is ignoring the WiFi AP beacon broadcast interval settings you just told me about above! (a.k.a. How do I change the WiFi scan mode to passive?) + +The default WiFi scan mode of the ESP8266 is active. This triggers a probe response by all AP:s that receives the probe request from the scan. So setting a different beacon interval time has little effect on the background transmission activity if a lot of active scans happen, since all nodes will start performing probe responses (at the same time) in response to the scans. + +However, we can change the scan mode so it is passive instead! That will avoid a flood of probe responses after every scan. The downside is that your scan will only detect the nodes that happen to beacon during the scan time. Since you may be able to use ESP-NOW broadcasts instead of AP beacons for node detection, this is perhaps not a problem if you just want to reduce background transmission activity as much as possible to reduce interference. + +Note though, that any device that uses active WiFi scans will trigger probe responses from the ESP8266, including smartphones and laptops. So even if you make all ESPs use passive scans, you can still end up with a lot of probe responses from the ESPs if they are close to other devices. The only way to fix this would be to disable the AP of the ESP8266, which of course will make it impossible to find the node via a WiFi scan, and also seems to make it impossible to receive ESP-NOW broadcasts (sending ESP-NOW broadcasts still work though, see the "[Note](#EspnowMeshBackendNote)" section of the EspnowMeshBackend documentation for more on this). + +To change the WiFi scan mode to passive, the following information is helpful: +1. A `scan_config` struct is found in `user_interface.h` (and the ESP8266 API documentation). We want to modify `scan_type`, but note that `scan_time` can also be set here if we want faster or slower scans. +2. In `ESP8266WiFiScan.cpp` one can find the following variable declaration: `struct scan_config config;` around line 87. Adding `config.scan_type = WIFI_SCAN_TYPE_PASSIVE;` after `memset(&config, 0, sizeof(config));` on line 88 will ensure passive scans are used. diff --git a/libraries/ESP8266WiFiMesh/examples/HelloEspnow/HelloEspnow.ino b/libraries/ESP8266WiFiMesh/examples/HelloEspnow/HelloEspnow.ino index 740aa50bd..3b90a6ef1 100644 --- a/libraries/ESP8266WiFiMesh/examples/HelloEspnow/HelloEspnow.ino +++ b/libraries/ESP8266WiFiMesh/examples/HelloEspnow/HelloEspnow.ino @@ -205,22 +205,24 @@ bool exampleTransmissionOutcomesUpdateHook(MeshBackendBase &meshInstance) { /** Once passed to the setResponseTransmittedHook method of the ESP-NOW backend, - this function will be called after each successful ESP-NOW response transmission, just before the response is removed from the waiting list. - If a particular response is not sent, there will be no function call for it. + this function will be called after each attempted ESP-NOW response transmission. + In case of a successful response transmission, this happens just before the response is removed from the waiting list. Only the hook of the EspnowMeshBackend instance that is getEspnowRequestManager() will be called. + @param transmissionSuccessful True if the response was transmitted successfully. False otherwise. @param response The sent response. @param recipientMac The MAC address the response was sent to. @param responseIndex The index of the response in the waiting list. @param meshInstance The EspnowMeshBackend instance that called the function. @return True if the response transmission process should continue with the next response in the waiting list. - False if the response transmission process should stop after removing the just sent response from the waiting list. + False if the response transmission process should stop once processing of the just sent response is complete. */ -bool exampleResponseTransmittedHook(const String &response, const uint8_t *recipientMac, uint32_t responseIndex, EspnowMeshBackend &meshInstance) { +bool exampleResponseTransmittedHook(bool transmissionSuccessful, const String &response, const uint8_t *recipientMac, uint32_t responseIndex, EspnowMeshBackend &meshInstance) { // Currently this is exactly the same as the default hook, but you can modify it to alter the behaviour of sendEspnowResponses. - (void)response; // This is useful to remove a "unused parameter" compiler warning. Does nothing else. + (void)transmissionSuccessful; // This is useful to remove a "unused parameter" compiler warning. Does nothing else. + (void)response; (void)recipientMac; (void)responseIndex; (void)meshInstance; @@ -255,7 +257,7 @@ void setup() { EspnowMeshBackend::setEspnowEncryptionKok(espnowEncryptionKok); espnowNode.setEspnowEncryptedConnectionKey(espnowEncryptedConnectionKey); - // Makes it possible to find the node through scans, and also makes it possible to recover from an encrypted connection where only the other node is encrypted. + // Makes it possible to find the node through scans, makes it possible to recover from an encrypted connection where only the other node is encrypted, and also makes it possible to receive broadcast transmissions. // Note that only one AP can be active at a time in total, and this will always be the one which was last activated. // Thus the AP is shared by all backends. espnowNode.activateAP(); diff --git a/libraries/ESP8266WiFiMesh/examples/HelloMesh/HelloMesh.ino b/libraries/ESP8266WiFiMesh/examples/HelloMesh/HelloMesh.ino index a6fb6bcd2..0fdc2d3c5 100644 --- a/libraries/ESP8266WiFiMesh/examples/HelloMesh/HelloMesh.ino +++ b/libraries/ESP8266WiFiMesh/examples/HelloMesh/HelloMesh.ino @@ -3,6 +3,9 @@ Once 28 seconds have passed, the node that has the highest AP MAC will start broadcasting benchmark messages, which will allow you to see how many messages are lost at the other nodes. If you have an onboard LED on your ESP8266 it is recommended that you change the useLED variable below to true. That way you will get instant confirmation of the mesh communication without checking the Serial Monitor. + + If you want to experiment with reducing error rates you can use the mesh method "void setBroadcastReceptionRedundancy(uint8_t redundancy);" (default 2) at the cost of more RAM. + Or "floodingMesh.getEspnowMeshBackend().setBroadcastTransmissionRedundancy(uint8_t redundancy)" (default 1) at the cost of longer transmission times. */ #define ESP8266WIFIMESH_DISABLE_COMPATIBILITY // Excludes redundant compatibility code. TODO: Should be used for new code until the compatibility code is removed with release 3.0.0 of the Arduino core. @@ -133,7 +136,7 @@ void setup() { Serial.println(F("Setting up mesh node...")); floodingMesh.begin(); - floodingMesh.activateAP(); + floodingMesh.activateAP(); // Required to receive messages uint8_t apMacArray[6] {0}; theOneMac = TypeCast::macToString(WiFi.softAPmacAddress(apMacArray)); diff --git a/libraries/ESP8266WiFiMesh/keywords.txt b/libraries/ESP8266WiFiMesh/keywords.txt index 3e1088a6b..db8d7cd3b 100644 --- a/libraries/ESP8266WiFiMesh/keywords.txt +++ b/libraries/ESP8266WiFiMesh/keywords.txt @@ -12,48 +12,96 @@ ESP8266WiFiMesh KEYWORD3 # Datatypes (KEYWORD1) ####################################### +MeshBackendBase KEYWORD1 +MeshBackendType KEYWORD1 +requestHandlerType KEYWORD1 +responseHandlerType KEYWORD1 +networkFilterType KEYWORD1 +transmissionOutcomesUpdateHookType KEYWORD1 + +TcpIpMeshBackend KEYWORD1 + +EspnowMeshBackend KEYWORD1 +broadcastFilterType KEYWORD1 +ConnectionType KEYWORD1 +EncryptedConnectionStatus KEYWORD1 +EncryptedConnectionRemovalOutcome KEYWORD1 +responseTransmittedHookType KEYWORD1 + +FloodingMesh KEYWORD1 +messageHandlerType KEYWORD1 + +TransmissionOutcome KEYWORD1 TransmissionStatusType KEYWORD1 +NetworkInfoBase KEYWORD1 +TcpIpNetworkInfo KEYWORD1 +EspnowNetworkInfo KEYWORD1 + ####################################### # Methods and Functions (KEYWORD2) ####################################### -connectionQueue KEYWORD2 -latestTransmissionOutcomes KEYWORD2 -latestTransmissionSuccessful KEYWORD2 +# MeshBackendBase begin KEYWORD2 activateAP KEYWORD2 deactivateAP KEYWORD2 +deactivateControlledAP KEYWORD2 restartAP KEYWORD2 getAPController KEYWORD2 isAPController KEYWORD2 setWiFiChannel KEYWORD2 getWiFiChannel KEYWORD2 +setSSID KEYWORD2 +getSSID KEYWORD2 +setSSIDPrefix KEYWORD2 +getSSIDPrefix KEYWORD2 +setSSIDRoot KEYWORD2 +getSSIDRoot KEYWORD2 +setSSIDSuffix KEYWORD2 +getSSIDSuffix KEYWORD2 setMeshName KEYWORD2 getMeshName KEYWORD2 setNodeID KEYWORD2 getNodeID KEYWORD2 -setSSID KEYWORD2 -getSSID KEYWORD2 +setMeshPassword KEYWORD2 +getMeshPassword KEYWORD2 setMessage KEYWORD2 getMessage KEYWORD2 attemptTransmission KEYWORD2 -acceptRequests KEYWORD2 -setStaticIP KEYWORD2 -getStaticIP KEYWORD2 -disableStaticIP->KEYWORD2 -uint64ToString KEYWORD2 -stringToUint64 KEYWORD2 setRequestHandler KEYWORD2 getRequestHandler KEYWORD2 setResponseHandler KEYWORD2 getResponseHandler KEYWORD2 setNetworkFilter KEYWORD2 getNetworkFilter KEYWORD2 +setTransmissionOutcomesUpdateHook KEYWORD2 +getTransmissionOutcomesUpdateHook KEYWORD2 setScanHidden KEYWORD2 getScanHidden KEYWORD2 setAPHidden KEYWORD2 getAPHidden KEYWORD2 +setVerboseModeState KEYWORD2 +verboseMode KEYWORD2 +verboseModePrint KEYWORD2 +setPrintWarnings KEYWORD2 +printWarnings KEYWORD2 +warningPrint KEYWORD2 +getClassType KEYWORD2 +printAPInfo KEYWORD2 + +# TcpIpMeshBackend +connectionQueue KEYWORD2 +constConnectionQueue KEYWORD2 +latestTransmissionOutcomes KEYWORD2 +latestTransmissionSuccessful KEYWORD2 +acceptRequests KEYWORD2 +getCurrentMessage KEYWORD2 +setStaticIP KEYWORD2 +getStaticIP KEYWORD2 +disableStaticIP->KEYWORD2 +setServerPort KEYWORD2 +getServerPort KEYWORD2 setMaxAPStations KEYWORD2 getMaxAPStations KEYWORD2 setConnectionAttemptTimeout KEYWORD2 @@ -63,10 +111,152 @@ getStationModeTimeout KEYWORD2 setAPModeTimeout KEYWORD2 getAPModeTimeout KEYWORD2 +# EspnowMeshBackend +espnowDelay KEYWORD2 +performEspnowMaintenance KEYWORD2 +criticalHeapLevel KEYWORD2 +setCriticalHeapLevelBuffer KEYWORD2 +criticalHeapLevelBuffer KEYWORD2 +deactivateEspnow KEYWORD2 +attemptAutoEncryptingTransmission KEYWORD2 +broadcast KEYWORD2 +setBroadcastTransmissionRedundancy KEYWORD2 +getBroadcastTransmissionRedundancy KEYWORD2 +setEspnowRequestManager KEYWORD2 +getEspnowRequestManager KEYWORD2 +isEspnowRequestManager KEYWORD2 +setLogEntryLifetimeMs KEYWORD2 +logEntryLifetimeMs KEYWORD2 +setBroadcastResponseTimeoutMs KEYWORD2 +broadcastResponseTimeoutMs KEYWORD2 +setEspnowEncryptedConnectionKey KEYWORD2 +getEspnowEncryptedConnectionKey KEYWORD2 +setEspnowEncryptionKok KEYWORD2 +getEspnowEncryptionKok KEYWORD2 +setEspnowHashKey KEYWORD2 +getEspnowHashKey KEYWORD2 +setUseEncryptedMessages KEYWORD2 +useEncryptedMessages KEYWORD2 +setEspnowMessageEncryptionKey KEYWORD2 +getEspnowMessageEncryptionKey KEYWORD2 +getMaxMessageBytesPerTransmission KEYWORD2 +setMaxTransmissionsPerMessage KEYWORD2 +getMaxTransmissionsPerMessage KEYWORD2 +getMaxMessageLength KEYWORD2 +staticVerboseMode KEYWORD2 +staticVerboseModePrint KEYWORD2 +getScheduledResponseMessage KEYWORD2 +getScheduledResponseRecipient KEYWORD2 +numberOfScheduledResponses KEYWORD2 +clearAllScheduledResponses KEYWORD2 +deleteScheduledResponsesByRecipient KEYWORD2 +setEspnowTransmissionTimeout KEYWORD2 +getEspnowTransmissionTimeout KEYWORD2 +setEspnowRetransmissionInterval KEYWORD2 +getEspnowRetransmissionInterval KEYWORD2 +setEncryptionRequestTimeout KEYWORD2 +getEncryptionRequestTimeout KEYWORD2 +setAutoEncryptionDuration KEYWORD2 +getAutoEncryptionDuration KEYWORD2 +setBroadcastFilter KEYWORD2 +getBroadcastFilter KEYWORD2 +setResponseTransmittedHook KEYWORD2 +getResponseTransmittedHook KEYWORD2 +getSenderMac KEYWORD2 +getSenderAPMac KEYWORD2 +receivedEncryptedTransmission KEYWORD2 +addUnencryptedConnection KEYWORD2 +addEncryptedConnection KEYWORD2 +addTemporaryEncryptedConnection KEYWORD2 +requestEncryptedConnection KEYWORD2 +requestTemporaryEncryptedConnection KEYWORD2 +requestFlexibleTemporaryEncryptedConnection KEYWORD2 +removeEncryptedConnection KEYWORD2 +requestEncryptedConnectionRemoval KEYWORD2 +setAcceptsUnverifiedRequests KEYWORD2 +acceptsUnverifiedRequests KEYWORD2 +setEncryptedConnectionsSoftLimit KEYWORD2 +encryptedConnectionsSoftLimit KEYWORD2 +numberOfEncryptedConnections KEYWORD2 +getEncryptedMac KEYWORD2 +serializeUnencryptedConnection KEYWORD2 +serializeEncryptedConnection KEYWORD2 +serializeEncryptedConnection KEYWORD2 +getConnectionInfo KEYWORD2 +getTransmissionFailRate KEYWORD2 +resetTransmissionFailRate KEYWORD2 + +# FloodingMesh +floodingMeshDelay KEYWORD2 +performMeshMaintenance KEYWORD2 +performMeshInstanceMaintenance KEYWORD2 +serializeMeshState KEYWORD2 +setBroadcastReceptionRedundancy KEYWORD2 +getBroadcastReceptionRedundancy KEYWORD2 +encryptedBroadcast KEYWORD2 +clearMessageLogs KEYWORD2 +clearForwardingBacklog KEYWORD2 +setMessageHandler KEYWORD2 +getMessageHandler KEYWORD2 +getOriginMac KEYWORD2 +setMessageLogSize KEYWORD2 +messageLogSize KEYWORD2 +maxUnencryptedMessageLength KEYWORD2 +maxEncryptedMessageLength KEYWORD2 +setMetadataDelimiter KEYWORD2 +metadataDelimiter KEYWORD2 +getEspnowMeshBackend KEYWORD2 +getEspnowMeshBackendConst KEYWORD2 +restoreDefaultRequestHandler KEYWORD2 +restoreDefaultResponseHandler KEYWORD2 +restoreDefaultNetworkFilter KEYWORD2 +restoreDefaultBroadcastFilter KEYWORD2 +restoreDefaultTransmissionOutcomesUpdateHook KEYWORD2 +restoreDefaultResponseTransmittedHook KEYWORD2 + +# NetworkInfoBase +setBSSID KEYWORD2 +getBSSID KEYWORD2 +setWifiChannel KEYWORD2 +wifiChannel KEYWORD2 +setEncryptionType KEYWORD2 +setRSSI KEYWORD2 +setIsHidden KEYWORD2 + +# TransmissionOutcome +setTransmissionStatus KEYWORD2 +transmissionStatus KEYWORD2 + +# TypeConversionFunctions +uint64ToString KEYWORD2 +stringToUint64 KEYWORD2 +uint8ArrayToHexString KEYWORD2 +hexStringToUint8Array KEYWORD2 +uint8ArrayToMultiString KEYWORD2 +bufferedUint8ArrayToMultiString KEYWORD2 +macToString KEYWORD2 +stringToMac KEYWORD2 +macToUint64 KEYWORD2 +uint64ToMac KEYWORD2 +uint64ToUint8Array KEYWORD2 +uint8ArrayToUint64 KEYWORD2 +meshBackendCast KEYWORD2 + +# UtilityFunctions +macEqual KEYWORD2 +randomUint64 KEYWORD2 +getMapValue KEYWORD2 + ####################################### # Constants (LITERAL1) ####################################### emptyIP LITERAL1 + NETWORK_INFO_DEFAULT_INT LITERAL1 -WIFI_MESH_EMPTY_STRING LITERAL1 +defaultBSSID LITERAL1 +defaultEncryptionType LITERAL1 +defaultIsHidden LITERAL1 +defaultSSID LITERAL1 +defaultWifiChannel LITERAL1 +defaultRSSI LITERAL1 diff --git a/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.h b/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.h index 20a3f9a5c..4519372f3 100644 --- a/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.h +++ b/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.h @@ -106,11 +106,9 @@ void espnowDelay(const uint32_t durationMs); class EspnowMeshBackend : public MeshBackendBase { -protected: +public: using broadcastFilterType = std::function; - -public: /** * ESP-NOW constructor method. Creates an ESP-NOW node, ready to be initialised. @@ -299,6 +297,8 @@ public: * This also means that the broadcaster can receive duplicate responses from the same node if transmission conditions are poor and an ack is lost. * A broadcast can never be sent encrypted. * + * Note that the node needs to have its AP active to be able to receive broadcasts. Nodes can send broadcasts even if their AP is off. + * * @param message The message to send to the other nodes. Unlike the attemptTransmission method, the message will not be stored in the class instance, since there is no certain way to change the message during an ongoing broadcast. */ void broadcast(const String &message); @@ -639,13 +639,13 @@ public: broadcastFilterType getBroadcastFilter() const; /** - * Set a function that should be called after each successful ESP-NOW response transmission, just before the response is removed from the waiting list. - * If a particular response is not sent, there will be no function call for it. + * Set a function that should be called after each attempted ESP-NOW response transmission. + * In case of a successful response transmission, the call happens just before the response is removed from the waiting list. * Only the hook of the EspnowMeshBackend instance that is getEspnowRequestManager() will be called. * * The hook should return a bool. * If this return value is true, the response transmission process will continue with the next response in the waiting list. - * If it is false, the response transmission process will stop after removing the just sent response from the waiting list. + * If it is false, the response transmission process will stop once processing of the just sent response is complete. * The default responseTransmittedHook always returns true. */ void setResponseTransmittedHook(const EspnowTransmitter::responseTransmittedHookType responseTransmittedHook); @@ -714,6 +714,8 @@ public: * * When called, the method will update an existing encrypted ESP-NOW connection with the current stored encrypted connection key. (in case it has changed since the connection was established) * + * The maximum number of simultaneous encrypted connections is restricted by the ESP-NOW API and is EspnowProtocolInterpreter::maxEncryptedConnections (6 by default). + * * @param peerStaMac The station MAC of the other node. * @param peerApMac The AP MAC of the other node. * @param peerSessionKey The session key of the other node. At least one of the leftmost 32 bits should be 1, since the key otherwise indicates the connection is unencrypted. @@ -730,6 +732,8 @@ public: * * When called, the method will update an existing encrypted ESP-NOW connection with the current stored encrypted connection key. (in case it has changed since the connection was established) * + * The maximum number of simultaneous encrypted connections is restricted by the ESP-NOW API and is EspnowProtocolInterpreter::maxEncryptedConnections (6 by default). + * * Note that the espnowEncryptedConnectionKey, espnowEncryptionKok, espnowHashKey and espnowMessageEncryptionKey are not serialized. * These will be set to the values of the EspnowMeshBackend instance that is adding the serialized encrypted connection. * @@ -747,6 +751,8 @@ public: * * When called, the method will update an existing encrypted ESP-NOW connection with the current stored encrypted connection key. (in case it has changed since the connection was established) * + * The maximum number of simultaneous encrypted connections is restricted by the ESP-NOW API and is EspnowProtocolInterpreter::maxEncryptedConnections (6 by default). + * * As with all these methods, changes will only take effect once the requester proves it has the ability to decrypt the session key. * * @param peerStaMac The station MAC of the other node. @@ -766,6 +772,8 @@ public: * * When called, the method will update an existing encrypted ESP-NOW connection with the current stored encrypted connection key. (in case it has changed since the connection was established) * + * The maximum number of simultaneous encrypted connections is restricted by the ESP-NOW API and is EspnowProtocolInterpreter::maxEncryptedConnections (6 by default). + * * Note that the espnowEncryptedConnectionKey, espnowEncryptionKok, espnowHashKey and espnowMessageEncryptionKey are not serialized. * These will be set to the values of the EspnowMeshBackend instance that is adding the serialized encrypted connection. * @@ -782,6 +790,8 @@ public: * If an encrypted connection to peerMac already exists, only connection duration is updated. All other settings are kept as is. Use removeEncryptedConnection/requestEncryptedConnectionRemoval first if encryption keys should be updated. * The method makes sure both nodes have an encrypted connection to each other that's permanent. * + * The maximum number of simultaneous encrypted connections is restricted by the ESP-NOW API and is EspnowProtocolInterpreter::maxEncryptedConnections (6 by default). + * * @param peerMac The MAC of the other node to which the request should be sent. * * @return EncryptedConnectionStatus::CONNECTION_ESTABLISHED if the permanent connection was created. EncryptedConnectionStatus::SOFT_LIMIT_CONNECTION_ESTABLISHED if only a temporary soft limit connection could be established (see the setEncryptedConnectionsSoftLimit method documentation for details). Otherwise another status code based on the outcome. @@ -793,6 +803,8 @@ public: * If a temporary encrypted connection to peerMac already exists, only connection duration is updated. All other settings are kept as is. Permanent connections are not modified. Use removeEncryptedConnection/requestEncryptedConnectionRemoval first if encryption keys should be updated. * The method makes sure both nodes have an encrypted connection to each other that's either permanent or has exactly the duration specified. * + * The maximum number of simultaneous encrypted connections is restricted by the ESP-NOW API and is EspnowProtocolInterpreter::maxEncryptedConnections (6 by default). + * * @param peerMac The MAC of the other node to which the request should be sent. * @param durationMs The desired duration of the connection. * @@ -805,6 +817,8 @@ public: * If a temporary encrypted connection to peerMac with a shorter duration already exists, connection duration is updated. All other settings are kept as is. Permanent connections are not modified. Use removeEncryptedConnection/requestEncryptedConnectionRemoval first if encryption keys should be updated. * The method makes sure both nodes have an encrypted connection to each other that's either permanent or has at least the duration specified. * + * The maximum number of simultaneous encrypted connections is restricted by the ESP-NOW API and is EspnowProtocolInterpreter::maxEncryptedConnections (6 by default). + * * Note that if a temporary encrypted connection already exists to a target node, this method will slightly extend the connection duration * depending on the time it takes to verify the connection to the node. * diff --git a/libraries/ESP8266WiFiMesh/src/EspnowTransmitter.cpp b/libraries/ESP8266WiFiMesh/src/EspnowTransmitter.cpp index 89c5110ef..3670844c8 100644 --- a/libraries/ESP8266WiFiMesh/src/EspnowTransmitter.cpp +++ b/libraries/ESP8266WiFiMesh/src/EspnowTransmitter.cpp @@ -162,15 +162,17 @@ void EspnowTransmitter::sendEspnowResponses(const ExpiringTimeTracker *estimated continue; } - bool hookOutcome = true; // Note that callbacks can be called during delay time, so it is possible to receive a transmission during espnowSendToNode // (which may add an element to the responsesToSend list). - if(espnowSendToNodeUnsynchronized(responseIterator->getMessage(), responseIterator->getRecipientMac(), 'A', responseIterator->getRequestID()) - == TransmissionStatusType::TRANSMISSION_COMPLETE) + bool transmissionSuccessful = espnowSendToNodeUnsynchronized(responseIterator->getMessage(), responseIterator->getRecipientMac(), 'A', responseIterator->getRequestID()) + == TransmissionStatusType::TRANSMISSION_COMPLETE; + + bool hookOutcome = true; + if(EspnowMeshBackend *currentEspnowRequestManager = EspnowMeshBackend::getEspnowRequestManager()) + hookOutcome = currentEspnowRequestManager->getResponseTransmittedHook()(transmissionSuccessful, responseIterator->getMessage(), responseIterator->getRecipientMac(), responseIndex, *currentEspnowRequestManager); + + if(transmissionSuccessful) { - if(EspnowMeshBackend *currentEspnowRequestManager = EspnowMeshBackend::getEspnowRequestManager()) - hookOutcome = currentEspnowRequestManager->getResponseTransmittedHook()(responseIterator->getMessage(), responseIterator->getRecipientMac(), responseIndex, *currentEspnowRequestManager); - responseIterator = EspnowDatabase::responsesToSend().erase(responseIterator); --responseIndex; } diff --git a/libraries/ESP8266WiFiMesh/src/EspnowTransmitter.h b/libraries/ESP8266WiFiMesh/src/EspnowTransmitter.h index d717c0b07..403bea302 100644 --- a/libraries/ESP8266WiFiMesh/src/EspnowTransmitter.h +++ b/libraries/ESP8266WiFiMesh/src/EspnowTransmitter.h @@ -31,7 +31,7 @@ class EspnowTransmitter public: - using responseTransmittedHookType = std::function; + using responseTransmittedHookType = std::function; EspnowTransmitter(ConditionalPrinter &conditionalPrinterInstance, EspnowDatabase &databaseInstance, EspnowConnectionManager &connectionManagerInstance); @@ -101,7 +101,7 @@ private: EspnowDatabase & _database; EspnowConnectionManager & _connectionManager; - responseTransmittedHookType _responseTransmittedHook = [](const String &, const uint8_t *, uint32_t, EspnowMeshBackend &){ return true; }; + responseTransmittedHookType _responseTransmittedHook = [](bool, const String &, const uint8_t *, uint32_t, EspnowMeshBackend &){ return true; }; uint8_t _broadcastTransmissionRedundancy = 1; }; diff --git a/libraries/ESP8266WiFiMesh/src/FloodingMesh.cpp b/libraries/ESP8266WiFiMesh/src/FloodingMesh.cpp index a1bb26d73..8f883c282 100644 --- a/libraries/ESP8266WiFiMesh/src/FloodingMesh.cpp +++ b/libraries/ESP8266WiFiMesh/src/FloodingMesh.cpp @@ -272,6 +272,9 @@ void FloodingMesh::setMetadataDelimiter(const char metadataDelimiter) assert(metadataDelimiter < '0' || '9' < metadataDelimiter); assert(metadataDelimiter < 'A' || 'F' < metadataDelimiter); assert(metadataDelimiter < 'a' || 'f' < metadataDelimiter); + + // Reserved for encryptedBroadcast for now + assert(metadataDelimiter != ','); _metadataDelimiter = metadataDelimiter; } @@ -370,8 +373,8 @@ void FloodingMesh::restoreDefaultTransmissionOutcomesUpdateHook() void FloodingMesh::restoreDefaultResponseTransmittedHook() { - getEspnowMeshBackend().setResponseTransmittedHook([this](const String &response, const uint8_t *recipientMac, uint32_t responseIndex, EspnowMeshBackend &meshInstance) - { return _defaultResponseTransmittedHook(response, recipientMac, responseIndex, meshInstance); }); + getEspnowMeshBackend().setResponseTransmittedHook([this](bool transmissionSuccessful, const String &response, const uint8_t *recipientMac, uint32_t responseIndex, EspnowMeshBackend &meshInstance) + { return _defaultResponseTransmittedHook(transmissionSuccessful, response, recipientMac, responseIndex, meshInstance); }); } /** @@ -543,21 +546,23 @@ bool FloodingMesh::_defaultTransmissionOutcomesUpdateHook(MeshBackendBase &meshI /** * Once passed to the setResponseTransmittedHook method of the ESP-NOW backend, - * this function will be called after each successful ESP-NOW response transmission, just before the response is removed from the waiting list. - * If a particular response is not sent, there will be no function call for it. + * this function will be called after each attempted ESP-NOW response transmission. + * In case of a successful response transmission, this happens just before the response is removed from the waiting list. * Only the hook of the EspnowMeshBackend instance that is getEspnowRequestManager() will be called. * + * @param transmissionSuccessful True if the response was transmitted successfully. False otherwise. * @param response The sent response. * @param recipientMac The MAC address the response was sent to. * @param responseIndex The index of the response in the waiting list. * @param meshInstance The EspnowMeshBackend instance that called the function. * * @return True if the response transmission process should continue with the next response in the waiting list. - * False if the response transmission process should stop after removing the just sent response from the waiting list. + * False if the response transmission process should stop after processing of the just sent response is complete. */ -bool FloodingMesh::_defaultResponseTransmittedHook(const String &response, const uint8_t *recipientMac, const uint32_t responseIndex, EspnowMeshBackend &meshInstance) +bool FloodingMesh::_defaultResponseTransmittedHook(bool transmissionSuccessful, const String &response, const uint8_t *recipientMac, const uint32_t responseIndex, EspnowMeshBackend &meshInstance) { - (void)response; // This is useful to remove a "unused parameter" compiler warning. Does nothing else. + (void)transmissionSuccessful; // This is useful to remove a "unused parameter" compiler warning. Does nothing else. + (void)response; (void)recipientMac; (void)responseIndex; (void)meshInstance; diff --git a/libraries/ESP8266WiFiMesh/src/FloodingMesh.h b/libraries/ESP8266WiFiMesh/src/FloodingMesh.h index a705cc7c6..b77a8e698 100644 --- a/libraries/ESP8266WiFiMesh/src/FloodingMesh.h +++ b/libraries/ESP8266WiFiMesh/src/FloodingMesh.h @@ -41,14 +41,11 @@ void floodingMeshDelay(const uint32_t durationMs); class FloodingMesh { - -protected: - - using messageHandlerType = std::function; - using messageQueueElementType = std::map::iterator; public: + using messageHandlerType = std::function; + /** * FloodingMesh constructor method. Creates a FloodingMesh node, ready to be initialised. * @@ -160,6 +157,8 @@ public: /** * Make an unencrypted broadcast to the entire mesh network. * + * activateAP() must have been called for nodes to be able to receive broadcasts. Nodes can however send broadcasts even if their AP is off. + * * It is recommended that there is at most one new message transmitted in the mesh every 10, 20, 30 ms for messages up to length maxUnencryptedMessageLength()*n, * where n is (roughly, depending on mesh name length) 1/4, 3/5 and 1 respectively. If transmissions are more frequent than this, message loss will increase. * @@ -179,6 +178,8 @@ public: /** * Make an encrypted broadcast to the entire mesh network. * + * activateAP() must have been called for encryptedBroadcast to work. + * * ########## WARNING! This an experimental feature. API may change at any time. Only use if you like it when things break. ########## * Will be very slow compared to unencrypted broadcasts. Probably works OK in a small mesh with a maximum of 2-3 new messages transmitted in the mesh every second. * Because of the throughput difference, mixing encypted and unencrypted broadcasts is not recommended if there are frequent mesh broadcasts (multiple per second), @@ -285,6 +286,8 @@ public: protected: + using messageQueueElementType = std::map::iterator; + static std::set availableFloodingMeshes; String generateMessageID(); @@ -323,7 +326,7 @@ private: void _defaultNetworkFilter(const int numberOfNetworks, MeshBackendBase &meshInstance); bool _defaultBroadcastFilter(String &firstTransmission, EspnowMeshBackend &meshInstance); bool _defaultTransmissionOutcomesUpdateHook(MeshBackendBase &meshInstance); - bool _defaultResponseTransmittedHook(const String &response, const uint8_t *recipientMac, const uint32_t responseIndex, EspnowMeshBackend &meshInstance); + bool _defaultResponseTransmittedHook(bool transmissionSuccessful, const String &response, const uint8_t *recipientMac, const uint32_t responseIndex, EspnowMeshBackend &meshInstance); uint8_t _originMac[6] = {0}; diff --git a/libraries/ESP8266WiFiMesh/src/MeshBackendBase.h b/libraries/ESP8266WiFiMesh/src/MeshBackendBase.h index cd1e8dc86..483deaf31 100644 --- a/libraries/ESP8266WiFiMesh/src/MeshBackendBase.h +++ b/libraries/ESP8266WiFiMesh/src/MeshBackendBase.h @@ -32,15 +32,13 @@ enum class MeshBackendType class MeshBackendBase { -protected: +public: using requestHandlerType = std::function ; using responseHandlerType = std::function; using networkFilterType = std::function; using transmissionOutcomesUpdateHookType = std::function; -public: - MeshBackendBase(const requestHandlerType requestHandler, const responseHandlerType responseHandler, const networkFilterType networkFilter, const MeshBackendType classType); virtual ~MeshBackendBase(); diff --git a/libraries/ESP8266WiFiMesh/src/TcpIpMeshBackend.cpp b/libraries/ESP8266WiFiMesh/src/TcpIpMeshBackend.cpp index 927beee00..6aebe46ba 100644 --- a/libraries/ESP8266WiFiMesh/src/TcpIpMeshBackend.cpp +++ b/libraries/ESP8266WiFiMesh/src/TcpIpMeshBackend.cpp @@ -280,7 +280,7 @@ TransmissionStatusType TcpIpMeshBackend::exchangeInfo(WiFiClient &currClient) TransmissionStatusType TcpIpMeshBackend::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. + // We cannot send data to the AP in AP_STA 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);