diff --git a/libraries/ESP8266WiFiMesh/README.md b/libraries/ESP8266WiFiMesh/README.md
index 8d955220ca..4bfd019928 100644
--- a/libraries/ESP8266WiFiMesh/README.md
+++ b/libraries/ESP8266WiFiMesh/README.md
@@ -1,81 +1,398 @@
-ESP8266 WiFi Mesh
-=================
+# ESP8266 WiFi Mesh
-A library for turning your ESP8266 into a mesh network node.
+## Contents
+1. [Overview](#Overview)
+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)
+ * [My internet is slower when I connect the ESP8266 to my router!](#FAQSlowRouter)
-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.
+## Overview
-Usage
------
+This is a library for creating a mesh network using the ESP8266.
-The basic operation of a mesh node is as follows:
+The library has been tested and works with Arduino Core for ESP8266 version 3.0.0 (with lwIP2). It may work with earlier and later core releases, but this has not been tested during development.
-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.
+**Note:** This mesh library has been extensively rewritten for core release 3.0.0. The old method signatures have been retained for compatibility purposes, but will be removed in core release 3.0.X. 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.
-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.
+## How does it work?
-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);
-```
+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 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!
+
+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. However, 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 "[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 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.
+
+## TcpIpMeshBackend
-### Note
+### Usage
-* 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.
+The basic operation of the TCP/IP mesh backend is as follows:
- At the moment static IP is a global setting, meaning that all ESP8266WiFiMesh instances on the same ESP8266 share the same static IP settings.
+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.
-* 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.
+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.
- 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.
+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.
-* 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:
+### 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:
+
+ 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).
+
+ Station gateway IP must match the IP for the server on the nodes. This is the default setting for the library.
+
+ 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.
+
+* 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:
```
- if(ESP8266WiFiMesh *apController = ESP8266WiFiMesh::getAPController()) // Make sure apController is not nullptr
+ if(MeshBackendBase *apController = MeshBackendBase::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.
+* It is possible to have several TcpIpMeshBackend 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 TcpIpMeshBackend 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.
+* While it is possible to connect to other nodes by only giving their SSID, e.g. `TcpIpMeshBackend::connectionQueue().emplace_back("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.
+### 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 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).
+* 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).
+
+## EspnowMeshBackend
+
+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).
+
+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
+
+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.
+
+***
+
+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).
+
+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 message byte each, non-ASCII characters usually require 2 message bytes each.
+
+### 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 by 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 broadcast 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 IDs 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 `encryptedBroadcast` 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). The regular `attemptTransmission` method will transmit even to nodes that have their 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.
+
+### My internet is slower when I connect the ESP8266 to my router!
+There has been some reports about this happening when the ESP8266 is in AP+STA mode while connected to the router. The ESP8266 automatically switches to 802.11g in AP+STA mode, so if your router normally uses a faster WiFi standard such as 802.11n or 802.11ac the router may change mode of operation to 802.11g. Typically this would result in a maximum WiFi speed of around 30 Mbit/s.
+
+A possible workaround is to use only AP mode or STA mode (see "[I want to control the WiFi mode myself](#FAQModeControl)"), perhaps with an extra ESP8266 in one of these modes as a buffer between your ESP8266 mesh network and your router. Remember that the ESP8266 must have the AP active in order to receive ESP-NOW broadcast messages.
-* 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
+Another possible workaround is to try with a different router or router firmware.
\ No newline at end of file
diff --git a/libraries/ESP8266WiFiMesh/examples/HelloEspnow/HelloEspnow.ino b/libraries/ESP8266WiFiMesh/examples/HelloEspnow/HelloEspnow.ino
new file mode 100644
index 0000000000..3b90a6ef11
--- /dev/null
+++ b/libraries/ESP8266WiFiMesh/examples/HelloEspnow/HelloEspnow.ino
@@ -0,0 +1,454 @@
+#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.
+
+#include
+#include
+#include
+#include
+#include
+
+namespace TypeCast = MeshTypeConversionFunctions;
+
+/**
+ NOTE: Although we could define the strings below as normal String variables,
+ here we are using PROGMEM combined with the FPSTR() macro (and also just the F() macro further down in the file).
+ The reason is that this approach will place the strings in flash memory which will help save RAM during program execution.
+ Reading strings from flash will be slower than reading them from RAM,
+ but this will be a negligible difference when printing them to Serial.
+
+ More on F(), FPSTR() and PROGMEM:
+ https://github.com/esp8266/Arduino/issues/1143
+ https://arduino-esp8266.readthedocs.io/en/latest/PROGMEM.html
+*/
+constexpr char exampleMeshName[] PROGMEM = "MeshNode_"; // The name of the mesh network. Used as prefix for the node SSID and to find other network nodes in the example networkFilter and broadcastFilter functions below.
+constexpr char exampleWiFiPassword[] PROGMEM = "ChangeThisWiFiPassword_TODO"; // Note: " is an illegal character. The password has to be min 8 and max 64 characters long, otherwise an AP which uses it will not be found during scans.
+
+// A custom encryption key is required when using encrypted ESP-NOW transmissions. There is always a default Kok set, but it can be replaced if desired.
+// All ESP-NOW keys below must match in an encrypted connection pair for encrypted communication to be possible.
+// Note that it is also possible to use Strings as key seeds instead of arrays.
+uint8_t espnowEncryptedConnectionKey[16] = {0x33, 0x44, 0x33, 0x44, 0x33, 0x44, 0x33, 0x44, // This is the key for encrypting transmissions of encrypted connections.
+ 0x33, 0x44, 0x33, 0x44, 0x33, 0x44, 0x32, 0x11
+ };
+uint8_t espnowEncryptionKok[16] = {0x22, 0x44, 0x33, 0x44, 0x33, 0x44, 0x33, 0x44, // This is the key for encrypting the encrypted connection key.
+ 0x33, 0x44, 0x33, 0x44, 0x33, 0x44, 0x32, 0x33
+ };
+uint8_t espnowHashKey[16] = {0xEF, 0x44, 0x33, 0x0C, 0x33, 0x44, 0xFE, 0x44, // This is the secret key used for HMAC during encrypted connection requests.
+ 0x33, 0x44, 0x33, 0xB0, 0x33, 0x44, 0x32, 0xAD
+ };
+
+unsigned int requestNumber = 0;
+unsigned int responseNumber = 0;
+
+const char broadcastMetadataDelimiter = 23; // 23 = End-of-Transmission-Block (ETB) control character in ASCII
+
+String manageRequest(const String &request, MeshBackendBase &meshInstance);
+TransmissionStatusType manageResponse(const String &response, MeshBackendBase &meshInstance);
+void networkFilter(int numberOfNetworks, MeshBackendBase &meshInstance);
+bool broadcastFilter(String &firstTransmission, EspnowMeshBackend &meshInstance);
+
+/* Create the mesh node object */
+EspnowMeshBackend espnowNode = EspnowMeshBackend(manageRequest, manageResponse, networkFilter, broadcastFilter, FPSTR(exampleWiFiPassword), espnowEncryptedConnectionKey, espnowHashKey, FPSTR(exampleMeshName), TypeCast::uint64ToString(ESP.getChipId()), true);
+
+/**
+ Callback for when other nodes send you a request
+
+ @param request The request string received from another node in the mesh
+ @param meshInstance The MeshBackendBase instance that called the function.
+ @return The string to send back to the other node. For ESP-NOW, return an empy string ("") if no response should be sent.
+*/
+String manageRequest(const String &request, MeshBackendBase &meshInstance) {
+ // To get the actual class of the polymorphic meshInstance, do as follows (meshBackendCast replaces dynamic_cast since RTTI is disabled)
+ if (EspnowMeshBackend *espnowInstance = TypeCast::meshBackendCast(&meshInstance)) {
+ String transmissionEncrypted = espnowInstance->receivedEncryptedTransmission() ? F(", Encrypted transmission") : F(", Unencrypted transmission");
+ Serial.print(String(F("ESP-NOW (")) + espnowInstance->getSenderMac() + transmissionEncrypted + F("): "));
+ } else if (TcpIpMeshBackend *tcpIpInstance = TypeCast::meshBackendCast(&meshInstance)) {
+ (void)tcpIpInstance; // This is useful to remove a "unused parameter" compiler warning. Does nothing else.
+ Serial.print(F("TCP/IP: "));
+ } else {
+ Serial.print(F("UNKNOWN!: "));
+ }
+
+ /* Print out received message */
+ // Only show first 100 characters because printing a large String takes a lot of time, which is a bad thing for a callback function.
+ // If you need to print the whole String it is better to store it and print it in the loop() later.
+ // Note that request.substring will not work as expected if the String contains null values as data.
+ Serial.print(F("Request received: "));
+
+ if (request.charAt(0) == 0) {
+ Serial.println(request); // substring will not work for multiStrings.
+ } else {
+ Serial.println(request.substring(0, 100));
+ }
+
+ /* return a string to send back */
+ return (String(F("Hello world response #")) + String(responseNumber++) + F(" from ") + meshInstance.getMeshName() + meshInstance.getNodeID() + F(" with AP MAC ") + WiFi.softAPmacAddress() + String('.'));
+}
+
+/**
+ 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 MeshBackendBase instance that called the function.
+ @return The status code resulting from the response, as an int
+*/
+TransmissionStatusType manageResponse(const String &response, MeshBackendBase &meshInstance) {
+ TransmissionStatusType statusCode = TransmissionStatusType::TRANSMISSION_COMPLETE;
+
+ // To get the actual class of the polymorphic meshInstance, do as follows (meshBackendCast replaces dynamic_cast since RTTI is disabled)
+ if (EspnowMeshBackend *espnowInstance = TypeCast::meshBackendCast(&meshInstance)) {
+ String transmissionEncrypted = espnowInstance->receivedEncryptedTransmission() ? F(", Encrypted transmission") : F(", Unencrypted transmission");
+ Serial.print(String(F("ESP-NOW (")) + espnowInstance->getSenderMac() + transmissionEncrypted + F("): "));
+ } else if (TcpIpMeshBackend *tcpIpInstance = TypeCast::meshBackendCast(&meshInstance)) {
+ Serial.print(F("TCP/IP: "));
+
+ // Getting the sent message like this will work as long as ONLY(!) TCP/IP is used.
+ // With TCP/IP the response will follow immediately after the request, so the stored message will not have changed.
+ // With ESP-NOW there is no guarantee when or if a response will show up, it can happen before or after the stored message is changed.
+ // So for ESP-NOW, adding unique identifiers in the response and request is required to associate a response with a request.
+ Serial.print(F("Request sent: "));
+ Serial.println(tcpIpInstance->getCurrentMessage().substring(0, 100));
+ } else {
+ Serial.print(F("UNKNOWN!: "));
+ }
+
+ /* Print out received message */
+ // Only show first 100 characters because printing a large String takes a lot of time, which is a bad thing for a callback function.
+ // If you need to print the whole String it is better to store it and print it in the loop() later.
+ // Note that response.substring will not work as expected if the String contains null values as data.
+ Serial.print(F("Response received: "));
+ Serial.println(response.substring(0, 100));
+
+ return statusCode;
+}
+
+/**
+ Callback used to decide which networks to connect to once a WiFi scan has been completed.
+
+ @param numberOfNetworks The number of networks found in the WiFi scan.
+ @param meshInstance The MeshBackendBase instance that called the function.
+*/
+void networkFilter(int numberOfNetworks, MeshBackendBase &meshInstance) {
+ // Note that the network index of a given node may change whenever a new scan is done.
+ for (int networkIndex = 0; networkIndex < numberOfNetworks; ++networkIndex) {
+ String currentSSID = WiFi.SSID(networkIndex);
+ int meshNameIndex = currentSSID.indexOf(meshInstance.getMeshName());
+
+ /* Connect to any _suitable_ APs which contain meshInstance.getMeshName() */
+ if (meshNameIndex >= 0) {
+ uint64_t targetNodeID = TypeCast::stringToUint64(currentSSID.substring(meshNameIndex + meshInstance.getMeshName().length()));
+
+ if (targetNodeID < TypeCast::stringToUint64(meshInstance.getNodeID())) {
+ if (EspnowMeshBackend *espnowInstance = TypeCast::meshBackendCast(&meshInstance)) {
+ espnowInstance->connectionQueue().emplace_back(networkIndex);
+ } else if (TcpIpMeshBackend *tcpIpInstance = TypeCast::meshBackendCast(&meshInstance)) {
+ tcpIpInstance->connectionQueue().emplace_back(networkIndex);
+ } else {
+ Serial.println(F("Invalid mesh backend!"));
+ }
+ }
+ }
+ }
+}
+
+/**
+ Callback used to decide which broadcast messages to accept. Only called for the first transmission in each broadcast.
+ If true is returned from this callback, the first broadcast transmission is saved until the entire broadcast message has been received.
+ The complete broadcast message will then be sent to the requestHandler (manageRequest in this example).
+ If false is returned from this callback, the broadcast message is discarded.
+ Note that the BroadcastFilter may be called multiple times for messages that are discarded in this way, but is only called once for accepted messages.
+
+ @param firstTransmission The first transmission of the broadcast. Modifications to this String are passed on to the broadcast message.
+ @param meshInstance The EspnowMeshBackend instance that called the function.
+
+ @return True if the broadcast should be accepted. False otherwise.
+*/
+bool broadcastFilter(String &firstTransmission, EspnowMeshBackend &meshInstance) {
+ // This example broadcastFilter will accept a transmission if it contains the broadcastMetadataDelimiter
+ // and as metaData either no targetMeshName or a targetMeshName that matches the MeshName of meshInstance.
+
+ int32_t metadataEndIndex = firstTransmission.indexOf(broadcastMetadataDelimiter);
+
+ if (metadataEndIndex == -1) {
+ return false; // broadcastMetadataDelimiter not found
+ }
+
+ String targetMeshName = firstTransmission.substring(0, metadataEndIndex);
+
+ if (!targetMeshName.isEmpty() && meshInstance.getMeshName() != targetMeshName) {
+ return false; // Broadcast is for another mesh network
+ } else {
+ // Remove metadata from message and mark as accepted broadcast.
+ // Note that when you modify firstTransmission it is best to avoid using substring or other String methods that rely on null values for String length determination.
+ // Otherwise your broadcasts cannot include null values in the message bytes.
+ firstTransmission.remove(0, metadataEndIndex + 1);
+ return true;
+ }
+}
+
+/**
+ Once passed to the setTransmissionOutcomesUpdateHook method of the ESP-NOW backend,
+ this function will be called after each update of the latestTransmissionOutcomes vector during attemptTransmission.
+ (which happens after each individual transmission has finished)
+
+ Example use cases is modifying getMessage() between transmissions, or aborting attemptTransmission before all nodes in the connectionQueue have been contacted.
+
+ @param meshInstance The MeshBackendBase instance that called the function.
+
+ @return True if attemptTransmission should continue with the next entry in the connectionQueue. False if attemptTransmission should stop.
+*/
+bool exampleTransmissionOutcomesUpdateHook(MeshBackendBase &meshInstance) {
+ // Currently this is exactly the same as the default hook, but you can modify it to alter the behaviour of attemptTransmission.
+
+ (void)meshInstance; // This is useful to remove a "unused parameter" compiler warning. Does nothing else.
+
+ return true;
+}
+
+/**
+ Once passed to the setResponseTransmittedHook method of the ESP-NOW backend,
+ 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 once processing of the just sent response is complete.
+*/
+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)transmissionSuccessful; // This is useful to remove a "unused parameter" compiler warning. Does nothing else.
+ (void)response;
+ (void)recipientMac;
+ (void)responseIndex;
+ (void)meshInstance;
+
+ return true;
+}
+
+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);
+
+ Serial.println();
+ Serial.println();
+
+ Serial.println(F("Note that this library can use static IP:s for the nodes with the TCP/IP backend to speed up connection times.\n"
+ "Use the setStaticIP method 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 and ESP-NOW keys!\n\n"));
+
+ Serial.println(F("Setting up mesh node..."));
+
+ /* Initialise the mesh node */
+ espnowNode.begin();
+
+ // Note: This changes the Kok for all EspnowMeshBackend instances on this ESP8266.
+ // Encrypted connections added before the Kok change will retain their old Kok.
+ // Both Kok and encrypted connection key must match in an encrypted connection pair for encrypted communication to be possible.
+ // Otherwise the transmissions will never reach the recipient, even though acks are received by the sender.
+ EspnowMeshBackend::setEspnowEncryptionKok(espnowEncryptionKok);
+ espnowNode.setEspnowEncryptedConnectionKey(espnowEncryptedConnectionKey);
+
+ // 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();
+
+ // Storing our message in the EspnowMeshBackend instance is not required, but can be useful for organizing code, especially when using many EspnowMeshBackend instances.
+ // Note that calling the multi-recipient versions of espnowNode.attemptTransmission and espnowNode.attemptAutoEncryptingTransmission will replace the stored message with whatever message is transmitted.
+ // Also note that the maximum allowed number of ASCII characters in a ESP-NOW message is given by EspnowMeshBackend::getMaxMessageLength().
+ espnowNode.setMessage(String(F("Hello world request #")) + String(requestNumber) + F(" from ") + espnowNode.getMeshName() + espnowNode.getNodeID() + String('.'));
+
+ espnowNode.setTransmissionOutcomesUpdateHook(exampleTransmissionOutcomesUpdateHook);
+ espnowNode.setResponseTransmittedHook(exampleResponseTransmittedHook);
+
+ // In addition to using encrypted ESP-NOW connections the framework can also send automatically encrypted messages (AEAD) over both encrypted and unencrypted connections.
+ // Using AEAD will only encrypt the message content, not the transmission metadata.
+ // 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.
+ // Encrypted ESP-NOW connections do however come with built in replay attack protection, which is not provided by the framework when using AEAD encryption,
+ // and 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.
+ //
+ // Uncomment the lines below to use automatic AEAD encryption/decryption of messages sent/received.
+ // All nodes this node wishes to communicate with must then also use encrypted messages with the same getEspnowMessageEncryptionKey(), or messages will not be accepted.
+ // Note that using AEAD encrypted messages will reduce the number of message bytes that can be transmitted.
+ //espnowNode.setEspnowMessageEncryptionKey(F("ChangeThisKeySeed_TODO")); // The message encryption key should always be set manually. Otherwise a default key (all zeroes) is used.
+ //espnowNode.setUseEncryptedMessages(true);
+}
+
+int32_t timeOfLastScan = -10000;
+void loop() {
+ // The performEspnowMaintenance() method performs all the background operations for the EspnowMeshBackend.
+ // It is recommended to place it in the beginning of the loop(), unless there is a need to put it elsewhere.
+ // Among other things, the method cleans up old Espnow log entries (freeing up RAM) and sends the responses you provide to Espnow requests.
+ // Note that depending on the amount of responses to send and their length, this method can take tens or even hundreds of milliseconds to complete.
+ // More intense transmission activity and less frequent calls to performEspnowMaintenance will likely cause the method to take longer to complete, so plan accordingly.
+
+ //Should not be used inside responseHandler, requestHandler, networkFilter or broadcastFilter callbacks since performEspnowMaintenance() can alter the ESP-NOW state.
+ EspnowMeshBackend::performEspnowMaintenance();
+
+ if (millis() - timeOfLastScan > 10000) { // Give other nodes some time to connect between data transfers.
+ Serial.println(F("\nPerforming unencrypted ESP-NOW transmissions."));
+
+ uint32_t startTime = millis();
+ espnowNode.attemptTransmission(espnowNode.getMessage());
+ Serial.println(String(F("Scan and ")) + String(espnowNode.latestTransmissionOutcomes().size()) + F(" transmissions done in ") + String(millis() - startTime) + F(" ms."));
+
+ timeOfLastScan = millis();
+
+ // Wait for response. espnowDelay continuously calls performEspnowMaintenance() so we will respond to ESP-NOW request while waiting.
+ // Should not be used inside responseHandler, requestHandler, networkFilter or broadcastFilter callbacks since performEspnowMaintenance() can alter the ESP-NOW state.
+ espnowDelay(100);
+
+ // One way to check how attemptTransmission worked out
+ if (espnowNode.latestTransmissionSuccessful()) {
+ Serial.println(F("Transmission successful."));
+ }
+
+ // Another way to check how attemptTransmission worked out
+ if (espnowNode.latestTransmissionOutcomes().empty()) {
+ Serial.println(F("No mesh AP found."));
+ } else {
+ for (TransmissionOutcome &transmissionOutcome : espnowNode.latestTransmissionOutcomes()) {
+ if (transmissionOutcome.transmissionStatus() == TransmissionStatusType::TRANSMISSION_FAILED) {
+ Serial.println(String(F("Transmission failed to mesh AP ")) + transmissionOutcome.SSID());
+ } else if (transmissionOutcome.transmissionStatus() == TransmissionStatusType::CONNECTION_FAILED) {
+ Serial.println(String(F("Connection failed to mesh AP ")) + transmissionOutcome.SSID());
+ } else if (transmissionOutcome.transmissionStatus() == TransmissionStatusType::TRANSMISSION_COMPLETE) {
+ // No need to do anything, transmission was successful.
+ } else {
+ Serial.println(String(F("Invalid transmission status for ")) + transmissionOutcome.SSID() + String('!'));
+ assert(F("Invalid transmission status returned from responseHandler!") && false);
+ }
+ }
+
+ Serial.println(F("\nPerforming ESP-NOW broadcast."));
+
+ startTime = millis();
+
+ // Remove espnowNode.getMeshName() from the broadcastMetadata below to broadcast to all ESP-NOW nodes regardless of MeshName.
+ // Note that data that comes before broadcastMetadataDelimiter should not contain any broadcastMetadataDelimiter characters,
+ // otherwise the broadcastFilter function used in this example file will not work.
+ String broadcastMetadata = espnowNode.getMeshName() + String(broadcastMetadataDelimiter);
+ String broadcastMessage = String(F("Broadcast #")) + String(requestNumber) + F(" from ") + espnowNode.getMeshName() + espnowNode.getNodeID() + String('.');
+ espnowNode.broadcast(broadcastMetadata + broadcastMessage);
+ Serial.println(String(F("Broadcast to all mesh nodes done in ")) + String(millis() - startTime) + F(" ms."));
+
+ espnowDelay(100); // Wait for responses (broadcasts can receive an unlimited number of responses, other transmissions can only receive one response).
+
+ // If you have a data array containing null values it is possible to transmit the raw data by making the array into a multiString as shown below.
+ // You can use String::c_str() or String::begin() to retreive the data array later.
+ // Note that certain String methods such as String::substring use null values to determine String length, which means they will not work as normal with multiStrings.
+ uint8_t dataArray[] = {0, '\'', 0, '\'', ' ', '(', 'n', 'u', 'l', 'l', ')', ' ', 'v', 'a', 'l', 'u', 'e'};
+ String espnowMessage = TypeCast::uint8ArrayToMultiString(dataArray, sizeof dataArray) + F(" from ") + espnowNode.getMeshName() + espnowNode.getNodeID() + String('.');
+ Serial.println(String(F("\nTransmitting: ")) + espnowMessage);
+ espnowNode.attemptTransmission(espnowMessage, false);
+ espnowDelay(100); // Wait for response.
+
+ Serial.println(F("\nPerforming encrypted ESP-NOW transmissions."));
+
+ uint8_t targetBSSID[6] {0};
+
+ // We can create encrypted connections to individual nodes so that all ESP-NOW communication with the node will be encrypted.
+ if (espnowNode.constConnectionQueue()[0].getBSSID(targetBSSID) && espnowNode.requestEncryptedConnection(targetBSSID) == EncryptedConnectionStatus::CONNECTION_ESTABLISHED) {
+ // The WiFi scan will detect the AP MAC, but this will automatically be converted to the encrypted STA MAC by the framework.
+ String peerMac = TypeCast::macToString(targetBSSID);
+
+ Serial.println(String(F("Encrypted ESP-NOW connection with ")) + peerMac + F(" established!"));
+
+ // Making a transmission now will cause messages to targetBSSID to be encrypted.
+ String espnowMessage = String(F("This message is encrypted only when received by node ")) + peerMac;
+ Serial.println(String(F("\nTransmitting: ")) + espnowMessage);
+ espnowNode.attemptTransmission(espnowMessage, false);
+ espnowDelay(100); // Wait for response.
+
+ // A connection can be serialized and stored for later use.
+ // Note that this saves the current state only, so if encrypted communication between the nodes happen after this, the stored state is invalid.
+ String serializedEncryptedConnection = EspnowMeshBackend::serializeEncryptedConnection(targetBSSID);
+
+ Serial.println();
+ // We can remove an encrypted connection like so.
+ espnowNode.removeEncryptedConnection(targetBSSID);
+
+ // Note that the peer will still be encrypted, so although we can send unencrypted messages to the peer, we cannot read the encrypted responses it sends back.
+ espnowMessage = String(F("This message is no longer encrypted when received by node ")) + peerMac;
+ Serial.println(String(F("\nTransmitting: ")) + espnowMessage);
+ espnowNode.attemptTransmission(espnowMessage, false);
+ espnowDelay(100); // Wait for response.
+ Serial.println(F("Cannot read the encrypted response..."));
+
+ // Let's re-add our stored connection so we can communicate properly with targetBSSID again!
+ espnowNode.addEncryptedConnection(serializedEncryptedConnection);
+
+ espnowMessage = String(F("This message is once again encrypted when received by node ")) + peerMac;
+ Serial.println(String(F("\nTransmitting: ")) + espnowMessage);
+ espnowNode.attemptTransmission(espnowMessage, false);
+ espnowDelay(100); // Wait for response.
+
+ Serial.println();
+ // If we want to remove the encrypted connection on both nodes, we can do it like this.
+ EncryptedConnectionRemovalOutcome removalOutcome = espnowNode.requestEncryptedConnectionRemoval(targetBSSID);
+ if (removalOutcome == EncryptedConnectionRemovalOutcome::REMOVAL_SUCCEEDED) {
+ Serial.println(peerMac + F(" is no longer encrypted!"));
+
+ espnowMessage = String(F("This message is only received by node ")) + peerMac + F(". Transmitting in this way will not change the transmission state of the sender.");
+ Serial.println(String(F("Transmitting: ")) + espnowMessage);
+ espnowNode.attemptTransmission(espnowMessage, EspnowNetworkInfo(targetBSSID));
+ espnowDelay(100); // Wait for response.
+
+ Serial.println();
+
+ // Of course, we can also just create a temporary encrypted connection that will remove itself once its duration has passed.
+ if (espnowNode.requestTemporaryEncryptedConnection(targetBSSID, 1000) == EncryptedConnectionStatus::CONNECTION_ESTABLISHED) {
+ espnowDelay(42);
+ uint32_t remainingDuration = 0;
+ EspnowMeshBackend::getConnectionInfo(targetBSSID, &remainingDuration);
+
+ espnowMessage = String(F("Messages this node sends to ")) + peerMac + F(" will be encrypted for ") + String(remainingDuration) + F(" ms more.");
+ Serial.println(String(F("\nTransmitting: ")) + espnowMessage);
+ espnowNode.attemptTransmission(espnowMessage, false);
+
+ EspnowMeshBackend::getConnectionInfo(targetBSSID, &remainingDuration);
+ espnowDelay(remainingDuration + 100);
+
+ espnowMessage = String(F("Due to encrypted connection expiration, this message is no longer encrypted when received by node ")) + peerMac;
+ Serial.println(String(F("\nTransmitting: ")) + espnowMessage);
+ espnowNode.attemptTransmission(espnowMessage, false);
+ espnowDelay(100); // Wait for response.
+ }
+
+ // Or if we prefer we can just let the library automatically create brief encrypted connections which are long enough to transmit an encrypted message.
+ // Note that encrypted responses will not be received, unless there already was an encrypted connection established with the peer before attemptAutoEncryptingTransmission was called.
+ // This can be remedied via the requestPermanentConnections argument, though it must be noted that the maximum number of encrypted connections supported at a time is 6.
+ espnowMessage = F("This message is always encrypted, regardless of receiver.");
+ Serial.println(String(F("\nTransmitting: ")) + espnowMessage);
+ espnowNode.attemptAutoEncryptingTransmission(espnowMessage);
+ espnowDelay(100); // Wait for response.
+ } else {
+ Serial.println(String(F("Ooops! Encrypted connection removal failed. Status: ")) + String(static_cast(removalOutcome)));
+ }
+
+ // Finally, should you ever want to stop other parties from sending unencrypted messages to the node
+ // setAcceptsUnencryptedRequests(false);
+ // can be used for this. It applies to both encrypted connection requests and regular transmissions.
+
+ Serial.println(F("\n##############################################################################################"));
+ }
+
+ // Our last request was sent to all nodes found, so time to create a new request.
+ espnowNode.setMessage(String(F("Hello world request #")) + String(++requestNumber) + F(" from ")
+ + espnowNode.getMeshName() + espnowNode.getNodeID() + String('.'));
+ }
+
+ Serial.println();
+ }
+}
diff --git a/libraries/ESP8266WiFiMesh/examples/HelloMesh/HelloMesh.ino b/libraries/ESP8266WiFiMesh/examples/HelloMesh/HelloMesh.ino
index cdd3e6d5c9..3ae8567e16 100644
--- a/libraries/ESP8266WiFiMesh/examples/HelloMesh/HelloMesh.ino
+++ b/libraries/ESP8266WiFiMesh/examples/HelloMesh/HelloMesh.ino
@@ -1,7 +1,21 @@
+/**
+ This example makes every node broadcast their AP MAC to the rest of the network during the first 28 seconds, as long as the node thinks it has the highest AP MAC in the network.
+ 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.
+
#include
-#include
#include
#include
+#include
+
+namespace TypeCast = MeshTypeConversionFunctions;
/**
NOTE: Although we could define the strings below as normal String variables,
@@ -14,84 +28,95 @@
https://github.com/esp8266/Arduino/issues/1143
https://arduino-esp8266.readthedocs.io/en/latest/PROGMEM.html
*/
-const char exampleMeshName[] PROGMEM = "MeshNode_";
-const char exampleWiFiPassword[] PROGMEM = "ChangeThisWiFiPassword_TODO";
+constexpr char exampleMeshName[] PROGMEM = "MeshNode_"; // The name of the mesh network. Used as prefix for the node SSID and to find other network nodes in the example networkFilter and broadcastFilter functions below.
+constexpr char exampleWiFiPassword[] PROGMEM = "ChangeThisWiFiPassword_TODO"; // Note: " is an illegal character. The password has to be min 8 and max 64 characters long, otherwise an AP which uses it will not be found during scans.
-unsigned int requestNumber = 0;
-unsigned int responseNumber = 0;
+// A custom encryption key is required when using encrypted ESP-NOW transmissions. There is always a default Kok set, but it can be replaced if desired.
+// All ESP-NOW keys below must match in an encrypted connection pair for encrypted communication to be possible.
+// Note that it is also possible to use Strings as key seeds instead of arrays.
+uint8_t espnowEncryptedConnectionKey[16] = {0x33, 0x44, 0x33, 0x44, 0x33, 0x44, 0x33, 0x44, // This is the key for encrypting transmissions of encrypted connections.
+ 0x33, 0x44, 0x33, 0x44, 0x33, 0x44, 0x32, 0x11
+ };
+uint8_t espnowHashKey[16] = {0xEF, 0x44, 0x33, 0x0C, 0x33, 0x44, 0xFE, 0x44, // This is the secret key used for HMAC during encrypted connection requests.
+ 0x33, 0x44, 0x33, 0xB0, 0x33, 0x44, 0x32, 0xAD
+ };
-String manageRequest(const String &request, ESP8266WiFiMesh &meshInstance);
-transmission_status_t manageResponse(const String &response, ESP8266WiFiMesh &meshInstance);
-void networkFilter(int numberOfNetworks, ESP8266WiFiMesh &meshInstance);
+bool meshMessageHandler(String &message, FloodingMesh &meshInstance);
/* Create the mesh node object */
-ESP8266WiFiMesh meshNode = ESP8266WiFiMesh(manageRequest, manageResponse, networkFilter, FPSTR(exampleWiFiPassword), FPSTR(exampleMeshName), "", true);
+FloodingMesh floodingMesh = FloodingMesh(meshMessageHandler, FPSTR(exampleWiFiPassword), espnowEncryptedConnectionKey, espnowHashKey, FPSTR(exampleMeshName), TypeCast::uint64ToString(ESP.getChipId()), true);
-/**
- Callback for when other nodes send you a request
+bool theOne = true;
+String theOneMac;
- @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(const String &request, ESP8266WiFiMesh &meshInstance) {
- // We do not store strings in flash (via F()) in this function.
- // The reason is that the other node will be waiting for our response,
- // so keeping the strings in RAM will give a (small) improvement in response time.
- // Of course, it is advised to adjust this approach based on RAM requirements.
-
- /* Print out received message */
- Serial.print("Request received: ");
- Serial.println(request);
-
- /* return a string to send back */
- return ("Hello world response #" + String(responseNumber++) + " from " + meshInstance.getMeshName() + meshInstance.getNodeID() + ".");
-}
+bool useLED = false; // Change this to true if you wish the onboard LED to mark The One.
/**
- 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
+ Callback for when a message is received from the mesh network.
+
+ @param message The message String received from the mesh.
+ Modifications to this String are passed on when the message is forwarded from this node to other nodes.
+ However, the forwarded message will still use the same messageID.
+ Thus it will not be sent to nodes that have already received this messageID.
+ If you want to send a new message to the whole network, use a new broadcast from within the loop() instead.
+ @param meshInstance The FloodingMesh instance that received the message.
+ @return True if this node should forward the received message to other nodes. False otherwise.
*/
-transmission_status_t manageResponse(const String &response, ESP8266WiFiMesh &meshInstance) {
- transmission_status_t statusCode = TS_TRANSMISSION_COMPLETE;
+bool meshMessageHandler(String &message, FloodingMesh &meshInstance) {
+ int32_t delimiterIndex = message.indexOf(meshInstance.metadataDelimiter());
+ if (delimiterIndex == 0) {
+ Serial.print(String(F("Message received from STA MAC ")) + meshInstance.getEspnowMeshBackend().getSenderMac() + F(": "));
+ Serial.println(message.substring(2, 102));
+
+ String potentialMac = message.substring(2, 14);
+
+ if (potentialMac >= theOneMac) {
+ if (potentialMac > theOneMac) {
+ theOne = false;
+ theOneMac = potentialMac;
+ }
- /* Print out received message */
- Serial.print(F("Request sent: "));
- Serial.println(meshInstance.getMessage());
- Serial.print(F("Response received: "));
- Serial.println(response);
+ if (useLED && !theOne) {
+ bool ledState = message.charAt(1) == '1';
+ digitalWrite(LED_BUILTIN, ledState); // Turn LED on/off (LED_BUILTIN is active low)
+ }
- // Our last request got a response, so time to create a new request.
- meshInstance.setMessage(String(F("Hello world request #")) + String(++requestNumber) + String(F(" from "))
- + meshInstance.getMeshName() + meshInstance.getNodeID() + String(F(".")));
+ return true;
+ } else {
+ return false;
+ }
+ } else if (delimiterIndex > 0) {
+ if (meshInstance.getOriginMac() == theOneMac) {
+ uint32_t totalBroadcasts = strtoul(message.c_str(), nullptr, 0); // strtoul stops reading input when an invalid character is discovered.
- // (void)meshInstance; // This is useful to remove a "unused parameter" compiler warning. Does nothing else.
- return statusCode;
-}
+ // Static variables are only initialized once.
+ static uint32_t firstBroadcast = totalBroadcasts;
-/**
- Callback used to decide which networks to connect to once a WiFi scan has been completed.
+ if (totalBroadcasts - firstBroadcast >= 100) { // Wait a little to avoid start-up glitches
+ static uint32_t missedBroadcasts = 1; // Starting at one to compensate for initial -1 below.
+ static uint32_t previousTotalBroadcasts = totalBroadcasts;
+ static uint32_t totalReceivedBroadcasts = 0;
+ totalReceivedBroadcasts++;
- @param numberOfNetworks The number of networks found in the WiFi scan.
- @param meshInstance The ESP8266WiFiMesh instance that called the function.
-*/
-void networkFilter(int numberOfNetworks, ESP8266WiFiMesh &meshInstance) {
- for (int networkIndex = 0; networkIndex < numberOfNetworks; ++networkIndex) {
- String currentSSID = WiFi.SSID(networkIndex);
- int meshNameIndex = currentSSID.indexOf(meshInstance.getMeshName());
+ missedBroadcasts += totalBroadcasts - previousTotalBroadcasts - 1; // We expect an increment by 1.
+ previousTotalBroadcasts = totalBroadcasts;
- /* Connect to any _suitable_ APs which contain meshInstance.getMeshName() */
- if (meshNameIndex >= 0) {
- uint64_t targetNodeID = stringToUint64(currentSSID.substring(meshNameIndex + meshInstance.getMeshName().length()));
-
- if (targetNodeID < stringToUint64(meshInstance.getNodeID())) {
- ESP8266WiFiMesh::connectionQueue.push_back(NetworkInfo(networkIndex));
+ if (totalReceivedBroadcasts % 50 == 0) {
+ Serial.println(String(F("missed/total: ")) + String(missedBroadcasts) + '/' + String(totalReceivedBroadcasts));
+ }
+ if (totalReceivedBroadcasts % 500 == 0) {
+ Serial.println(String(F("Benchmark message: ")) + message.substring(0, 100));
+ }
}
}
+ } else {
+ // Only show first 100 characters because printing a large String takes a lot of time, which is a bad thing for a callback function.
+ // If you need to print the whole String it is better to store it and print it in the loop() later.
+ Serial.print(String(F("Message with origin ")) + meshInstance.getOriginMac() + F(" received: "));
+ Serial.println(message.substring(0, 100));
}
+
+ return true;
}
void setup() {
@@ -100,63 +125,73 @@ void setup() {
WiFi.persistent(false);
Serial.begin(115200);
- delay(50); // Wait for Serial.
-
- //yield(); // Use this if you don't want to wait for Serial.
-
- // The WiFi.disconnect() ensures that the WiFi is working correctly. If this is not done before receiving WiFi connections,
- // those WiFi connections will take a long time to make or sometimes will not work at all.
- WiFi.disconnect();
Serial.println();
Serial.println();
- Serial.println(F("Note that this library can use static IP:s for the nodes to speed up connection times.\n"
- "Use the setStaticIP method as shown in this example to enable this.\n"
- "Ensure that nodes connecting to the same AP have distinct static IP:s.\n"
- "Also, remember to change the default mesh network password!\n\n"));
+ Serial.println(F("If you have an onboard LED on your ESP8266 it is recommended that you change the useLED variable to true.\n"
+ "That way you will get instant confirmation of the mesh communication.\n"
+ "Also, remember to change the default mesh network password and ESP-NOW keys!\n"));
Serial.println(F("Setting up mesh node..."));
- /* Initialise the mesh node */
- 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.
+ floodingMesh.begin();
+ floodingMesh.activateAP(); // Required to receive messages
+
+ uint8_t apMacArray[6] {0};
+ theOneMac = TypeCast::macToString(WiFi.softAPmacAddress(apMacArray));
+
+ if (useLED) {
+ pinMode(LED_BUILTIN, OUTPUT); // Initialize the LED_BUILTIN pin as an output
+ digitalWrite(LED_BUILTIN, LOW); // Turn LED on (LED_BUILTIN is active low)
+ }
+
+ // Uncomment the lines below to use automatic AEAD encryption/decryption of messages sent/received via broadcast() and encryptedBroadcast().
+ // The main benefit of AEAD encryption is that it can be used with normal broadcasts (which are substantially faster than encryptedBroadcasts).
+ // The main drawbacks are that AEAD only encrypts the message data (not transmission metadata), transfers less data per message and lacks replay attack protection.
+ // When using AEAD, potential replay attacks must thus be handled manually.
+ //floodingMesh.getEspnowMeshBackend().setEspnowMessageEncryptionKey(F("ChangeThisKeySeed_TODO")); // The message encryption key should always be set manually. Otherwise a default key (all zeroes) is used.
+ //floodingMesh.getEspnowMeshBackend().setUseEncryptedMessages(true);
+
+ floodingMeshDelay(5000); // Give some time for user to start the nodes
}
-int32_t timeOfLastScan = -10000;
+int32_t timeOfLastProclamation = -10000;
void loop() {
- if (millis() - timeOfLastScan > 3000 // Give other nodes some time to connect between data transfers.
- || (WiFi.status() != WL_CONNECTED && millis() - timeOfLastScan > 2000)) { // Scan for networks with two second intervals when not already connected.
- String request = String(F("Hello world request #")) + String(requestNumber) + String(F(" from ")) + meshNode.getMeshName() + meshNode.getNodeID() + String(F("."));
- meshNode.attemptTransmission(request, false);
- timeOfLastScan = millis();
-
- // One way to check how attemptTransmission worked out
- if (ESP8266WiFiMesh::latestTransmissionSuccessful()) {
- Serial.println(F("Transmission successful."));
+ static bool ledState = 1;
+ static uint32_t benchmarkCount = 0;
+ static uint32_t loopStart = millis();
+
+ // The floodingMeshDelay() method performs all the background operations for the FloodingMesh (via FloodingMesh::performMeshMaintenance()).
+ // It is recommended to place one of these methods in the beginning of the loop(), unless there is a need to put them elsewhere.
+ // Among other things, the method cleans up old ESP-NOW log entries (freeing up RAM) and forwards received mesh messages.
+ // Note that depending on the amount of messages to forward and their length, this method can take tens or even hundreds of milliseconds to complete.
+ // More intense transmission activity and less frequent calls to performMeshMaintenance will likely cause the method to take longer to complete, so plan accordingly.
+ // The maintenance methods should not be used inside the meshMessageHandler callback, since they can alter the mesh node state. The framework will alert you during runtime if you make this mistake.
+ floodingMeshDelay(1);
+
+ // If you wish to transmit only to a single node, try using one of the following methods (requires the node to be within range and know the MAC of the recipient):
+ // Unencrypted: TransmissionStatusType floodingMesh.getEspnowMeshBackend().attemptTransmission(message, EspnowNetworkInfo(recipientMac));
+ // Encrypted (slow): floodingMesh.getEspnowMeshBackend().attemptAutoEncryptingTransmission(message, EspnowNetworkInfo(recipientMac));
+
+ if (theOne) {
+ if (millis() - timeOfLastProclamation > 10000) {
+ uint32_t startTime = millis();
+ ledState = ledState ^ bool(benchmarkCount); // Make other nodes' LEDs alternate between on and off once benchmarking begins.
+
+ // Note: The maximum length of an unencrypted broadcast message is given by floodingMesh.maxUnencryptedMessageLength(). It is around 670 bytes by default.
+ floodingMesh.broadcast(String(floodingMesh.metadataDelimiter()) + String(ledState) + theOneMac + F(" is The One."));
+ Serial.println(String(F("Proclamation broadcast done in ")) + String(millis() - startTime) + F(" ms."));
+
+ timeOfLastProclamation = millis();
+ floodingMeshDelay(20);
}
- // Another way to check how attemptTransmission worked out
- if (ESP8266WiFiMesh::latestTransmissionOutcomes.empty()) {
- Serial.println(F("No mesh AP found."));
- } else {
- for (TransmissionResult &transmissionResult : ESP8266WiFiMesh::latestTransmissionOutcomes) {
- if (transmissionResult.transmissionStatus == TS_TRANSMISSION_FAILED) {
- Serial.println(String(F("Transmission failed to mesh AP ")) + transmissionResult.SSID);
- } else if (transmissionResult.transmissionStatus == TS_CONNECTION_FAILED) {
- Serial.println(String(F("Connection failed to mesh AP ")) + transmissionResult.SSID);
- } else if (transmissionResult.transmissionStatus == TS_TRANSMISSION_COMPLETE) {
- // No need to do anything, transmission was successful.
- } else {
- Serial.println(String(F("Invalid transmission status for ")) + transmissionResult.SSID + String(F("!")));
- assert(F("Invalid transmission status returned from responseHandler!") && false);
- }
- }
+ if (millis() - loopStart > 23000) { // Start benchmarking the mesh once three proclamations have been made
+ uint32_t startTime = millis();
+ floodingMesh.broadcast(String(benchmarkCount++) + String(floodingMesh.metadataDelimiter()) + F(": Not a spoon in sight."));
+ Serial.println(String(F("Benchmark broadcast done in ")) + String(millis() - startTime) + F(" ms."));
+ floodingMeshDelay(20);
}
- Serial.println();
- } else {
- /* Accept any incoming connections */
- meshNode.acceptRequest();
}
}
diff --git a/libraries/ESP8266WiFiMesh/examples/HelloTcpIp/HelloTcpIp.ino b/libraries/ESP8266WiFiMesh/examples/HelloTcpIp/HelloTcpIp.ino
new file mode 100644
index 0000000000..661f2249cd
--- /dev/null
+++ b/libraries/ESP8266WiFiMesh/examples/HelloTcpIp/HelloTcpIp.ino
@@ -0,0 +1,223 @@
+#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.
+
+#include
+#include
+#include
+#include
+#include
+
+namespace TypeCast = MeshTypeConversionFunctions;
+
+/**
+ NOTE: Although we could define the strings below as normal String variables,
+ here we are using PROGMEM combined with the FPSTR() macro (and also just the F() macro further down in the file).
+ The reason is that this approach will place the strings in flash memory which will help save RAM during program execution.
+ Reading strings from flash will be slower than reading them from RAM,
+ but this will be a negligible difference when printing them to Serial.
+
+ More on F(), FPSTR() and PROGMEM:
+ https://github.com/esp8266/Arduino/issues/1143
+ https://arduino-esp8266.readthedocs.io/en/latest/PROGMEM.html
+*/
+constexpr char exampleMeshName[] PROGMEM = "MeshNode_";
+constexpr char exampleWiFiPassword[] PROGMEM = "ChangeThisWiFiPassword_TODO"; // Note: " is an illegal character. The password has to be min 8 and max 64 characters long, otherwise an AP which uses it will not be found during scans.
+
+unsigned int requestNumber = 0;
+unsigned int responseNumber = 0;
+
+String manageRequest(const String &request, MeshBackendBase &meshInstance);
+TransmissionStatusType manageResponse(const String &response, MeshBackendBase &meshInstance);
+void networkFilter(int numberOfNetworks, MeshBackendBase &meshInstance);
+
+/* Create the mesh node object */
+TcpIpMeshBackend tcpIpNode = TcpIpMeshBackend(manageRequest, manageResponse, networkFilter, FPSTR(exampleWiFiPassword), FPSTR(exampleMeshName), TypeCast::uint64ToString(ESP.getChipId()), true);
+
+/**
+ Callback for when other nodes send you a request
+
+ @param request The request string received from another node in the mesh
+ @param meshInstance The MeshBackendBase instance that called the function.
+ @return The string to send back to the other node. For ESP-NOW, return an empy string ("") if no response should be sent.
+*/
+String manageRequest(const String &request, MeshBackendBase &meshInstance) {
+ // To get the actual class of the polymorphic meshInstance, do as follows (meshBackendCast replaces dynamic_cast since RTTI is disabled)
+ if (EspnowMeshBackend *espnowInstance = TypeCast::meshBackendCast(&meshInstance)) {
+ String transmissionEncrypted = espnowInstance->receivedEncryptedTransmission() ? F(", Encrypted transmission") : F(", Unencrypted transmission");
+ Serial.print(String(F("ESP-NOW (")) + espnowInstance->getSenderMac() + transmissionEncrypted + F("): "));
+ } else if (TcpIpMeshBackend *tcpIpInstance = TypeCast::meshBackendCast(&meshInstance)) {
+ (void)tcpIpInstance; // This is useful to remove a "unused parameter" compiler warning. Does nothing else.
+ Serial.print(F("TCP/IP: "));
+ } else {
+ Serial.print(F("UNKNOWN!: "));
+ }
+
+ /* Print out received message */
+ // Only show first 100 characters because printing a large String takes a lot of time, which is a bad thing for a callback function.
+ // If you need to print the whole String it is better to store it and print it in the loop() later.
+ // Note that request.substring will not work as expected if the String contains null values as data.
+ Serial.print(F("Request received: "));
+ Serial.println(request.substring(0, 100));
+
+ /* return a string to send back */
+ return (String(F("Hello world response #")) + String(responseNumber++) + F(" from ") + meshInstance.getMeshName() + meshInstance.getNodeID() + F(" with AP MAC ") + WiFi.softAPmacAddress() + String('.'));
+}
+
+/**
+ 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 MeshBackendBase instance that called the function.
+ @return The status code resulting from the response, as an int
+*/
+TransmissionStatusType manageResponse(const String &response, MeshBackendBase &meshInstance) {
+ TransmissionStatusType statusCode = TransmissionStatusType::TRANSMISSION_COMPLETE;
+
+ // To get the actual class of the polymorphic meshInstance, do as follows (meshBackendCast replaces dynamic_cast since RTTI is disabled)
+ if (EspnowMeshBackend *espnowInstance = TypeCast::meshBackendCast(&meshInstance)) {
+ String transmissionEncrypted = espnowInstance->receivedEncryptedTransmission() ? F(", Encrypted transmission") : F(", Unencrypted transmission");
+ Serial.print(String(F("ESP-NOW (")) + espnowInstance->getSenderMac() + transmissionEncrypted + F("): "));
+ } else if (TcpIpMeshBackend *tcpIpInstance = TypeCast::meshBackendCast(&meshInstance)) {
+ Serial.print(F("TCP/IP: "));
+
+ // Getting the sent message like this will work as long as ONLY(!) TCP/IP is used.
+ // With TCP/IP the response will follow immediately after the request, so the stored message will not have changed.
+ // With ESP-NOW there is no guarantee when or if a response will show up, it can happen before or after the stored message is changed.
+ // So for ESP-NOW, adding unique identifiers in the response and request is required to associate a response with a request.
+ Serial.print(F("Request sent: "));
+ Serial.println(tcpIpInstance->getCurrentMessage().substring(0, 100));
+ } else {
+ Serial.print(F("UNKNOWN!: "));
+ }
+
+ /* Print out received message */
+ // Only show first 100 characters because printing a large String takes a lot of time, which is a bad thing for a callback function.
+ // If you need to print the whole String it is better to store it and print it in the loop() later.
+ // Note that response.substring will not work as expected if the String contains null values as data.
+ Serial.print(F("Response received: "));
+ Serial.println(response.substring(0, 100));
+
+ return statusCode;
+}
+
+/**
+ Callback used to decide which networks to connect to once a WiFi scan has been completed.
+
+ @param numberOfNetworks The number of networks found in the WiFi scan.
+ @param meshInstance The MeshBackendBase instance that called the function.
+*/
+void networkFilter(int numberOfNetworks, MeshBackendBase &meshInstance) {
+ // Note that the network index of a given node may change whenever a new scan is done.
+ for (int networkIndex = 0; networkIndex < numberOfNetworks; ++networkIndex) {
+ String currentSSID = WiFi.SSID(networkIndex);
+ int meshNameIndex = currentSSID.indexOf(meshInstance.getMeshName());
+
+ /* Connect to any _suitable_ APs which contain meshInstance.getMeshName() */
+ if (meshNameIndex >= 0) {
+ uint64_t targetNodeID = TypeCast::stringToUint64(currentSSID.substring(meshNameIndex + meshInstance.getMeshName().length()));
+
+ if (targetNodeID < TypeCast::stringToUint64(meshInstance.getNodeID())) {
+ if (EspnowMeshBackend *espnowInstance = TypeCast::meshBackendCast(&meshInstance)) {
+ espnowInstance->connectionQueue().emplace_back(networkIndex);
+ } else if (TcpIpMeshBackend *tcpIpInstance = TypeCast::meshBackendCast(&meshInstance)) {
+ tcpIpInstance->connectionQueue().emplace_back(networkIndex);
+ } else {
+ Serial.println(F("Invalid mesh backend!"));
+ }
+ }
+ }
+ }
+}
+
+/**
+ Once passed to the setTransmissionOutcomesUpdateHook method of the TCP/IP backend,
+ this function will be called after each update of the latestTransmissionOutcomes vector during attemptTransmission.
+ (which happens after each individual transmission has finished)
+
+ Example use cases is modifying getMessage() between transmissions, or aborting attemptTransmission before all nodes in the connectionQueue have been contacted.
+
+ @param meshInstance The MeshBackendBase instance that called the function.
+
+ @return True if attemptTransmission should continue with the next entry in the connectionQueue. False if attemptTransmission should stop.
+*/
+bool exampleTransmissionOutcomesUpdateHook(MeshBackendBase &meshInstance) {
+ // The default hook only returns true and does nothing else.
+
+ if (TcpIpMeshBackend *tcpIpInstance = TypeCast::meshBackendCast(&meshInstance)) {
+ if (tcpIpInstance->latestTransmissionOutcomes().back().transmissionStatus() == TransmissionStatusType::TRANSMISSION_COMPLETE) {
+ // Our last request got a response, so time to create a new request.
+ meshInstance.setMessage(String(F("Hello world request #")) + String(++requestNumber) + F(" from ")
+ + meshInstance.getMeshName() + meshInstance.getNodeID() + String('.'));
+ }
+ } else {
+ Serial.println(F("Invalid mesh backend!"));
+ }
+
+ return true;
+}
+
+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);
+
+ Serial.println();
+ Serial.println();
+
+ Serial.println(F("Note that this library can use static IP:s for the nodes to speed up connection times.\n"
+ "Use the setStaticIP method as shown in this example to enable this.\n"
+ "Ensure that nodes connecting to the same AP have distinct static IP:s.\n"
+ "Also, remember to change the default mesh network password!\n\n"));
+
+ Serial.println(F("Setting up mesh node..."));
+
+ /* Initialise the mesh node */
+ tcpIpNode.begin();
+ tcpIpNode.activateAP(); // Each AP requires a separate server port.
+ tcpIpNode.setStaticIP(IPAddress(192, 168, 4, 22)); // Activate static IP mode to speed up connection times.
+
+ // Storing our message in the TcpIpMeshBackend instance is not required, but can be useful for organizing code, especially when using many TcpIpMeshBackend instances.
+ // Note that calling the multi-recipient tcpIpNode.attemptTransmission will replace the stored message with whatever message is transmitted.
+ tcpIpNode.setMessage(String(F("Hello world request #")) + String(requestNumber) + F(" from ") + tcpIpNode.getMeshName() + tcpIpNode.getNodeID() + String('.'));
+
+ tcpIpNode.setTransmissionOutcomesUpdateHook(exampleTransmissionOutcomesUpdateHook);
+}
+
+int32_t timeOfLastScan = -10000;
+void loop() {
+ if (millis() - timeOfLastScan > 3000 // Give other nodes some time to connect between data transfers.
+ || (WiFi.status() != WL_CONNECTED && millis() - timeOfLastScan > 2000)) { // Scan for networks with two second intervals when not already connected.
+
+ // attemptTransmission(message, scan, scanAllWiFiChannels, concludingDisconnect, initialDisconnect = false)
+ tcpIpNode.attemptTransmission(tcpIpNode.getMessage(), true, false, false);
+ timeOfLastScan = millis();
+
+ // One way to check how attemptTransmission worked out
+ if (tcpIpNode.latestTransmissionSuccessful()) {
+ Serial.println(F("Transmission successful."));
+ }
+
+ // Another way to check how attemptTransmission worked out
+ if (tcpIpNode.latestTransmissionOutcomes().empty()) {
+ Serial.println(F("No mesh AP found."));
+ } else {
+ for (TransmissionOutcome &transmissionOutcome : tcpIpNode.latestTransmissionOutcomes()) {
+ if (transmissionOutcome.transmissionStatus() == TransmissionStatusType::TRANSMISSION_FAILED) {
+ Serial.println(String(F("Transmission failed to mesh AP ")) + transmissionOutcome.SSID());
+ } else if (transmissionOutcome.transmissionStatus() == TransmissionStatusType::CONNECTION_FAILED) {
+ Serial.println(String(F("Connection failed to mesh AP ")) + transmissionOutcome.SSID());
+ } else if (transmissionOutcome.transmissionStatus() == TransmissionStatusType::TRANSMISSION_COMPLETE) {
+ // No need to do anything, transmission was successful.
+ } else {
+ Serial.println(String(F("Invalid transmission status for ")) + transmissionOutcome.SSID() + String('!'));
+ assert(F("Invalid transmission status returned from responseHandler!") && false);
+ }
+ }
+ }
+ Serial.println();
+ } else {
+ /* Accept any incoming connections */
+ tcpIpNode.acceptRequests();
+ }
+}
diff --git a/libraries/ESP8266WiFiMesh/keywords.txt b/libraries/ESP8266WiFiMesh/keywords.txt
index 2ea9f96aad..db8d7cd3be 100644
--- a/libraries/ESP8266WiFiMesh/keywords.txt
+++ b/libraries/ESP8266WiFiMesh/keywords.txt
@@ -12,51 +12,96 @@ ESP8266WiFiMesh KEYWORD3
# Datatypes (KEYWORD1)
#######################################
-ESP8266WiFiMesh KEYWORD1
-NetworkInfo KEYWORD1
-TransmissionResult KEYWORD1
-transmission_status_t 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
-acceptRequest 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
@@ -66,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/library.properties b/libraries/ESP8266WiFiMesh/library.properties
index ddfea96a58..2ff47b0ead 100644
--- a/libraries/ESP8266WiFiMesh/library.properties
+++ b/libraries/ESP8266WiFiMesh/library.properties
@@ -1,6 +1,6 @@
name=ESP8266WiFiMesh
-version=2.1
-author=Julian Fell
+version=2.2
+author=Julian Fell, Anders Löfgren
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.
diff --git a/libraries/ESP8266WiFiMesh/src/CompatibilityLayer.cpp b/libraries/ESP8266WiFiMesh/src/CompatibilityLayer.cpp
index fd926a9351..0bb480acd0 100644
--- a/libraries/ESP8266WiFiMesh/src/CompatibilityLayer.cpp
+++ b/libraries/ESP8266WiFiMesh/src/CompatibilityLayer.cpp
@@ -27,9 +27,9 @@
/********************************************************************************************
* NOTE!
*
-* All method signatures in this file are deprecated and will be removed in core version 2.5.0.
+* All method signatures in this file are deprecated and will be removed in core version 3.0.0.
* If you are still using these methods, please consider migrating to the new API shown in
-* the ESP8266WiFiMesh.h source file.
+* the EspnowMeshBackend.h or TcpIpMeshBackend.h source files.
*
* TODO: delete this file.
********************************************************************************************/
diff --git a/libraries/ESP8266WiFiMesh/src/ConditionalPrinter.cpp b/libraries/ESP8266WiFiMesh/src/ConditionalPrinter.cpp
new file mode 100644
index 0000000000..15b08195b8
--- /dev/null
+++ b/libraries/ESP8266WiFiMesh/src/ConditionalPrinter.cpp
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2020 Anders Löfgren
+ *
+ * License (MIT license):
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "TypeConversionFunctions.h"
+#include "MeshBackendBase.h"
+#include "EspnowMeshBackend.h"
+
+namespace
+{
+ bool _staticVerboseMode = false;
+ bool _printWarnings = true;
+}
+
+void ConditionalPrinter::setVerboseModeState(const bool enabled) {_verboseMode = enabled;}
+bool ConditionalPrinter::verboseMode() const {return _verboseMode;}
+
+void ConditionalPrinter::verboseModePrint(const String &stringToPrint, const bool newline) const
+{
+ if(verboseMode())
+ {
+ if(newline)
+ Serial.println(stringToPrint);
+ else
+ Serial.print(stringToPrint);
+ }
+}
+
+void ConditionalPrinter::setStaticVerboseModeState(const bool enabled) {_staticVerboseMode = enabled;};
+bool ConditionalPrinter::staticVerboseMode() {return _staticVerboseMode;}
+
+void ConditionalPrinter::staticVerboseModePrint(const String &stringToPrint, const bool newline)
+{
+ if(staticVerboseMode())
+ {
+ if(newline)
+ Serial.println(stringToPrint);
+ else
+ Serial.print(stringToPrint);
+ }
+}
+
+void ConditionalPrinter::setPrintWarnings(const bool printEnabled) {_printWarnings = printEnabled;}
+bool ConditionalPrinter::printWarnings() {return _printWarnings;}
+
+void ConditionalPrinter::warningPrint(const String &stringToPrint, const bool newline)
+{
+ if(printWarnings())
+ {
+ if(newline)
+ Serial.println(stringToPrint);
+ else
+ Serial.print(stringToPrint);
+ }
+}
diff --git a/libraries/ESP8266WiFiMesh/src/ConditionalPrinter.h b/libraries/ESP8266WiFiMesh/src/ConditionalPrinter.h
new file mode 100644
index 0000000000..bf66c6652d
--- /dev/null
+++ b/libraries/ESP8266WiFiMesh/src/ConditionalPrinter.h
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2020 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 __CONDITIONALPRINTER_H__
+#define __CONDITIONALPRINTER_H__
+
+class ConditionalPrinter
+{
+
+public:
+
+ /**
+ * Set whether the normal events occurring in the library will be printed to Serial or not.
+ *
+ * @param enabled If true, library Serial prints are activated.
+ */
+ void setVerboseModeState(const bool enabled);
+ bool verboseMode() const;
+
+ /**
+ * Only print stringToPrint if verboseMode() returns true.
+ *
+ * @param stringToPrint String to print.
+ * @param newline If true, will end the print with a newline. True by default.
+ */
+ void verboseModePrint(const String &stringToPrint, const bool newline = true) const;
+
+ /**
+ * Same as verboseMode(), but used for printing from static functions.
+ *
+ * @param enabled If true, the normal events occurring in the library will be printed to Serial.
+ */
+ static void setStaticVerboseModeState(const bool enabled);
+ static bool staticVerboseMode();
+
+ /**
+ * Only print stringToPrint if staticVerboseMode() returns true.
+ *
+ * @param stringToPrint String to print.
+ * @param newline If true, will end the print with a newline. True by default.
+ */
+ static void staticVerboseModePrint(const String &stringToPrint, const bool newline = true);
+
+ /**
+ * Set whether the warnings occurring in the library will be printed to Serial or not. On by default.
+ *
+ * @param printEnabled If true, warning Serial prints from the library are activated.
+ */
+ static void setPrintWarnings(const bool printEnabled);
+ static bool printWarnings();
+
+ /**
+ * Only print stringToPrint if printWarnings() returns true.
+ *
+ * @param stringToPrint String to print.
+ * @param newline If true, will end the print with a newline. True by default.
+ */
+ static void warningPrint(const String &stringToPrint, const bool newline = true);
+
+private:
+
+ bool _verboseMode = false;
+
+};
+
+#endif
diff --git a/libraries/ESP8266WiFiMesh/src/ESP8266WiFiMesh.cpp b/libraries/ESP8266WiFiMesh/src/ESP8266WiFiMesh.cpp
index e35cac33b9..5775fa3009 100644
--- a/libraries/ESP8266WiFiMesh/src/ESP8266WiFiMesh.cpp
+++ b/libraries/ESP8266WiFiMesh/src/ESP8266WiFiMesh.cpp
@@ -18,6 +18,28 @@
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
+
+
+
+
+
+
+/********************************************************************************************
+* NOTE!
+*
+* This class is deprecated and will be removed in core version 3.0.0.
+* If you are still using this class, please consider migrating to the new API shown in
+* the EspnowMeshBackend.h or TcpIpMeshBackend.h source files.
+*
+* TODO: delete this file.
+********************************************************************************************/
+
+
+
+
+
+
+
#include
#include
#include
@@ -26,10 +48,11 @@
#include "ESP8266WiFiMesh.h"
#include "TypeConversionFunctions.h"
+namespace TypeCast = MeshTypeConversionFunctions;
+
#define SERVER_IP_ADDR "192.168.4.1"
const IPAddress ESP8266WiFiMesh::emptyIP = IPAddress();
-const uint32_t ESP8266WiFiMesh::lwipVersion203Signature[3] {2,0,3};
String ESP8266WiFiMesh::lastSSID;
bool ESP8266WiFiMesh::staticIPActivated = false;
@@ -51,11 +74,9 @@ ESP8266WiFiMesh::~ESP8266WiFiMesh()
ESP8266WiFiMesh::ESP8266WiFiMesh(ESP8266WiFiMesh::requestHandlerType requestHandler, ESP8266WiFiMesh::responseHandlerType responseHandler,
ESP8266WiFiMesh::networkFilterType networkFilter, const String &meshPassword, const String &meshName,
const String &nodeID, bool verboseMode, uint8 meshWiFiChannel, uint16_t serverPort)
- : _server(serverPort), _lwipVersion{0, 0, 0}
-{
- storeLwipVersion();
-
- updateNetworkNames(meshName, (!nodeID.isEmpty() ? nodeID : uint64ToString(ESP.getChipId())));
+ : _server(serverPort)
+{
+ updateNetworkNames(meshName, (!nodeID.isEmpty() ? nodeID : TypeCast::uint64ToString(ESP.getChipId())));
_requestHandler = requestHandler;
_responseHandler = responseHandler;
setWiFiChannel(meshWiFiChannel);
@@ -99,15 +120,10 @@ void ESP8266WiFiMesh::begin()
if(!ESP8266WiFiMesh::getAPController()) // If there is no active AP controller
WiFi.mode(WIFI_STA); // WIFI_AP_STA mode automatically sets up an AP, so we can't use that as default.
- #ifdef ENABLE_STATIC_IP_OPTIMIZATION
- if(atLeastLwipVersion(lwipVersion203Signature))
- {
- verboseModePrint(F("lwIP version is at least 2.0.3. Static ip optimizations enabled.\n"));
- }
- else
- {
- verboseModePrint(F("lwIP version is less than 2.0.3. Static ip optimizations DISABLED.\n"));
- }
+ #if LWIP_VERSION_MAJOR >= 2
+ verboseModePrint(F("lwIP version is at least 2. Static ip optimizations enabled.\n"));
+ #else
+ verboseModePrint(F("lwIP version is less than 2. Static ip optimizations DISABLED.\n"));
#endif
}
}
@@ -323,7 +339,7 @@ void ESP8266WiFiMesh::fullStop(WiFiClient &currClient)
/**
* Wait for a WiFiClient to transmit
*
- * @returns: True if the client is ready, false otherwise.
+ * @return: True if the client is ready, false otherwise.
*
*/
bool ESP8266WiFiMesh::waitForClientTransmission(WiFiClient &currClient, uint32_t maxWait)
@@ -351,7 +367,7 @@ bool ESP8266WiFiMesh::waitForClientTransmission(WiFiClient &currClient, uint32_t
* and pass that to the user-supplied responseHandler.
*
* @param currClient The client to which the message should be transmitted.
- * @returns: A status code based on the outcome of the exchange.
+ * @return: A status code based on the outcome of the exchange.
*
*/
transmission_status_t ESP8266WiFiMesh::exchangeInfo(WiFiClient &currClient)
@@ -384,7 +400,7 @@ transmission_status_t ESP8266WiFiMesh::exchangeInfo(WiFiClient &currClient)
/**
* Handle data transfer process with a connected AP.
*
- * @returns: A status code based on the outcome of the data transfer attempt.
+ * @return: A status code based on the outcome of the data transfer attempt.
*/
transmission_status_t ESP8266WiFiMesh::attemptDataTransfer()
{
@@ -404,7 +420,7 @@ transmission_status_t ESP8266WiFiMesh::attemptDataTransfer()
/**
* 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.
+ * @return: A status code based on the outcome of the data transfer attempt.
*/
transmission_status_t ESP8266WiFiMesh::attemptDataTransferKernel()
{
@@ -448,32 +464,25 @@ void ESP8266WiFiMesh::initiateConnectionToAP(const String &targetSSID, int targe
* @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.
+ * @return: 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.isEmpty() && 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.
- WiFiMode_t storedWiFiMode = WiFi.getMode();
- WiFi.mode(WIFI_OFF);
- WiFi.mode(storedWiFiMode);
- yield();
- }
- else
- {
- // Disable static IP so that we can connect to other servers via DHCP (DHCP is slower but required for connecting to more than one server, it seems (possible bug?)).
- disableStaticIP();
- verboseModePrint(F("\nConnecting to a different network. Static IP deactivated to make this possible."));
- }
+ #if LWIP_VERSION_MAJOR >= 2
+ // Can be used with Arduino core for ESP8266 version 2.4.2 or higher with lwIP2 enabled to keep static IP on even during network switches.
+ WiFiMode_t storedWiFiMode = WiFi.getMode();
+ WiFi.mode(WIFI_OFF);
+ WiFi.mode(storedWiFiMode);
+ yield();
+
#else
// Disable static IP so that we can connect to other servers via DHCP (DHCP is slower but required for connecting to more than one server, it seems (possible bug?)).
disableStaticIP();
verboseModePrint(F("\nConnecting to a different network. Static IP deactivated to make this possible."));
+
#endif
}
lastSSID = targetSSID;
@@ -537,10 +546,9 @@ void ESP8266WiFiMesh::attemptTransmission(const String &message, bool concluding
/* Scan for APs */
connectionQueue.clear();
- // 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.
+ // If scanAllWiFiChannels is true scanning will cause the WiFi radio to cycle through all WiFi channels.
// This means existing WiFi connections are likely to break or work poorly if done frequently.
int n = 0;
- #ifdef ENABLE_WIFI_SCAN_OPTIMIZATION
if(scanAllWiFiChannels)
{
n = WiFi.scanNetworks(false, _scanHidden);
@@ -550,9 +558,6 @@ void ESP8266WiFiMesh::attemptTransmission(const String &message, bool concluding
// Scan function argument overview: scanNetworks(bool async = false, bool show_hidden = false, uint8 channel = 0, uint8* ssid = NULL)
n = WiFi.scanNetworks(false, _scanHidden, _meshWiFiChannel);
}
- #else
- n = WiFi.scanNetworks(false, _scanHidden);
- #endif
_networkFilter(n, *this); // Update the connectionQueue.
}
@@ -650,12 +655,12 @@ void ESP8266WiFiMesh::acceptRequest()
if (!waitForClientTransmission(_client, _apModeTimeoutMs) || !_client.available()) {
continue;
}
-
+
/* Read in request and pass it to the supplied requestHandler */
String request = _client.readStringUntil('\r');
yield();
_client.flush();
-
+
String response = _requestHandler(request, *this);
/* Send the response back to the client */
@@ -669,3 +674,15 @@ void ESP8266WiFiMesh::acceptRequest()
}
}
}
+
+
+void ESP8266WiFiMesh::verboseModePrint(const String &stringToPrint, bool newline)
+{
+ if(_verboseMode)
+ {
+ if(newline)
+ Serial.println(stringToPrint);
+ else
+ Serial.print(stringToPrint);
+ }
+}
diff --git a/libraries/ESP8266WiFiMesh/src/ESP8266WiFiMesh.h b/libraries/ESP8266WiFiMesh/src/ESP8266WiFiMesh.h
index ceca8f0ff4..8647502bb3 100644
--- a/libraries/ESP8266WiFiMesh/src/ESP8266WiFiMesh.h
+++ b/libraries/ESP8266WiFiMesh/src/ESP8266WiFiMesh.h
@@ -18,6 +18,28 @@
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
+
+
+
+
+
+
+/********************************************************************************************
+* NOTE!
+*
+* This class is deprecated and will be removed in core version 3.0.0.
+* If you are still using this class, please consider migrating to the new API shown in
+* the EspnowMeshBackend.h or TcpIpMeshBackend.h source files.
+*
+* TODO: delete this file.
+********************************************************************************************/
+
+
+
+
+
+
+
#ifndef __WIFIMESH_H__
#define __WIFIMESH_H__
@@ -25,11 +47,9 @@
#include
#include
#include
-#include "NetworkInfo.h"
#include "TransmissionResult.h"
+#include "NetworkInfo.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 = "";
@@ -44,8 +64,6 @@ class ESP8266WiFiMesh {
uint8 _meshWiFiChannel;
bool _verboseMode;
WiFiServer _server;
- uint32_t _lwipVersion[3];
- static const uint32_t lwipVersion203Signature[3];
String _message = WIFI_MESH_EMPTY_STRING;
bool _scanHidden = false;
bool _apHidden = false;
@@ -56,6 +74,7 @@ class ESP8266WiFiMesh {
static String lastSSID;
static bool staticIPActivated;
+ bool useStaticIP;
static IPAddress staticIP;
static IPAddress gateway;
static IPAddress subnetMask;
@@ -78,8 +97,6 @@ class ESP8266WiFiMesh {
bool waitForClientTransmission(WiFiClient &currClient, uint32_t maxWait);
transmission_status_t attemptDataTransfer();
transmission_status_t attemptDataTransferKernel();
- void storeLwipVersion();
- bool atLeastLwipVersion(const uint32_t minLwipVersion[3]);
@@ -133,7 +150,7 @@ class ESP8266WiFiMesh {
//////////////////////////// TODO: REMOVE IN 2.5.0////////////////////////////
~ESP8266WiFiMesh();
-
+
/**
* WiFiMesh Constructor method. Creates a WiFi Mesh Node, ready to be initialised.
*
@@ -159,7 +176,7 @@ class ESP8266WiFiMesh {
*/
ESP8266WiFiMesh(requestHandlerType requestHandler, responseHandlerType responseHandler, networkFilterType networkFilter,
const String &meshPassword, const String &meshName = "MeshNode_", const String &nodeID = WIFI_MESH_EMPTY_STRING, bool verboseMode = false,
- uint8 meshWiFiChannel = 1, uint16_t serverPort = 4011);
+ uint8 meshWiFiChannel = 1, uint16_t serverPort = 4011) __attribute__((deprecated));
/**
* A vector that contains the NetworkInfo for each WiFi network to connect to.
@@ -178,7 +195,7 @@ class ESP8266WiFiMesh {
static std::vector latestTransmissionOutcomes;
/**
- * @returns True if latest transmission was successful (i.e. latestTransmissionOutcomes is not empty and all entries have transmissionStatus TS_TRANSMISSION_COMPLETE). False otherwise.
+ * @return True if latest transmission was successful (i.e. latestTransmissionOutcomes is not empty and all entries have transmissionStatus TS_TRANSMISSION_COMPLETE). False otherwise.
*/
static bool latestTransmissionSuccessful();
@@ -201,7 +218,7 @@ class ESP8266WiFiMesh {
* 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,
+ * @return 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();
@@ -209,7 +226,7 @@ class ESP8266WiFiMesh {
/**
* 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.
+ * @return True if this ESP8266WiFiMesh instance is in control of the ESP8266 AP. False otherwise.
*/
bool isAPController();
diff --git a/libraries/ESP8266WiFiMesh/src/EncryptedConnectionData.cpp b/libraries/ESP8266WiFiMesh/src/EncryptedConnectionData.cpp
new file mode 100644
index 0000000000..03a8a89fcd
--- /dev/null
+++ b/libraries/ESP8266WiFiMesh/src/EncryptedConnectionData.cpp
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2019 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 "EncryptedConnectionData.h"
+#include "UtilityFunctions.h"
+#include "TypeConversionFunctions.h"
+#include "JsonTranslator.h"
+#include "MeshCryptoInterface.h"
+#include "Serializer.h"
+
+namespace
+{
+ using EspnowProtocolInterpreter::hashKeyLength;
+ namespace TypeCast = MeshTypeConversionFunctions;
+}
+
+EncryptedConnectionData::EncryptedConnectionData(const uint8_t peerStaMac[6], const uint8_t peerApMac[6], const uint64_t peerSessionKey, const uint64_t ownSessionKey, const uint8_t hashKey[hashKeyLength])
+ : _peerSessionKey(peerSessionKey), _ownSessionKey(ownSessionKey)
+{
+ std::copy_n(peerStaMac, 6, _peerStaMac);
+ std::copy_n(peerApMac, 6, _peerApMac);
+ std::copy_n(hashKey, hashKeyLength, _hashKey);
+}
+
+EncryptedConnectionData::EncryptedConnectionData(const uint8_t peerStaMac[6], const uint8_t peerApMac[6], const uint64_t peerSessionKey, const uint64_t ownSessionKey, const uint32_t duration, const uint8_t hashKey[hashKeyLength])
+ : EncryptedConnectionData(peerStaMac, peerApMac, peerSessionKey, ownSessionKey, hashKey)
+{
+ setRemainingDuration(duration);
+}
+
+EncryptedConnectionData::EncryptedConnectionData(const EncryptedConnectionData &other)
+ : _peerSessionKey(other.getPeerSessionKey()), _ownSessionKey(other.getOwnSessionKey()),
+ _timeTracker(other.temporary() ? new ExpiringTimeTracker(*other.temporary()) : nullptr),
+ _desync(other.desync())
+{
+ other.getPeerStaMac(_peerStaMac);
+ other.getPeerApMac(_peerApMac);
+ other.getHashKey(_hashKey);
+}
+
+EncryptedConnectionData & EncryptedConnectionData::operator=(const EncryptedConnectionData &other)
+{
+ if(this != &other)
+ {
+ other.getPeerStaMac(_peerStaMac);
+ other.getPeerApMac(_peerApMac);
+ _peerSessionKey = other.getPeerSessionKey();
+ _ownSessionKey = other.getOwnSessionKey();
+ other.getHashKey(_hashKey);
+ _desync = other.desync();
+ _timeTracker = std::unique_ptr(other.temporary() ? new ExpiringTimeTracker(*other.temporary()) : nullptr);
+ }
+ return *this;
+}
+
+uint8_t *EncryptedConnectionData::getEncryptedPeerMac(uint8_t *resultArray) const
+{
+ return getPeerStaMac(resultArray);
+}
+
+uint8_t *EncryptedConnectionData::getUnencryptedPeerMac(uint8_t *resultArray) const
+{
+ return getPeerApMac(resultArray);
+}
+
+uint8_t *EncryptedConnectionData::getPeerStaMac(uint8_t *resultArray) const
+{
+ std::copy_n(_peerStaMac, 6, resultArray);
+ return resultArray;
+}
+
+uint8_t *EncryptedConnectionData::getPeerApMac(uint8_t *resultArray) const
+{
+ std::copy_n(_peerApMac, 6, resultArray);
+ return resultArray;
+}
+
+void EncryptedConnectionData::setPeerApMac(const uint8_t *peerApMac)
+{
+ std::copy_n(peerApMac, 6, _peerApMac);
+}
+
+bool EncryptedConnectionData::connectedTo(const uint8_t *peerMac) const
+{
+ if(MeshUtilityFunctions::macEqual(peerMac, _peerStaMac) || MeshUtilityFunctions::macEqual(peerMac, _peerApMac))
+ {
+ return true;
+ }
+
+ return false;
+}
+
+void EncryptedConnectionData::setHashKey(const uint8_t hashKey[hashKeyLength])
+{
+ assert(hashKey != nullptr);
+
+ std::copy_n(hashKey, hashKeyLength, _hashKey);
+}
+
+uint8_t *EncryptedConnectionData::getHashKey(uint8_t *resultArray) const
+{
+ std::copy_n(_hashKey, hashKeyLength, resultArray);
+ return resultArray;
+}
+
+void EncryptedConnectionData::setPeerSessionKey(const uint64_t sessionKey) { _peerSessionKey = sessionKey; }
+uint64_t EncryptedConnectionData::getPeerSessionKey() const { return _peerSessionKey; }
+
+void EncryptedConnectionData::setOwnSessionKey(const uint64_t sessionKey) { _ownSessionKey = sessionKey; }
+uint64_t EncryptedConnectionData::getOwnSessionKey() const { return _ownSessionKey; }
+
+uint64_t EncryptedConnectionData::incrementSessionKey(const uint64_t sessionKey, const uint8_t *hashKey, const uint8_t hashKeyLength)
+{
+ uint8_t inputArray[8] {0};
+ uint8_t hmacArray[experimental::crypto::SHA256::NATURAL_LENGTH] {0};
+ experimental::crypto::SHA256::hmac(TypeCast::uint64ToUint8Array(sessionKey, inputArray), 8, hashKey, hashKeyLength, hmacArray, experimental::crypto::SHA256::NATURAL_LENGTH);
+
+ /* HMAC truncation should be OK since hmac sha256 is a PRF and we are truncating to the leftmost (MSB) bits.
+ PRF: https://crypto.stackexchange.com/questions/26410/whats-the-gcm-sha-256-of-a-tls-protocol/26434#26434
+ Truncate to leftmost bits: https://tools.ietf.org/html/rfc2104#section-5 */
+ uint64_t newLeftmostBits = TypeCast::uint8ArrayToUint64(hmacArray) & EspnowProtocolInterpreter::uint64LeftmostBits;
+
+ if(newLeftmostBits == 0)
+ newLeftmostBits = ((uint64_t)ESP.random() | (1 << 31)) << 32; // We never want newLeftmostBits == 0 since that would indicate an unencrypted transmission.
+
+ uint64_t newRightmostBits = (uint32_t)(sessionKey + 1);
+
+ return newLeftmostBits | newRightmostBits;
+}
+
+void EncryptedConnectionData::incrementOwnSessionKey()
+{
+ setOwnSessionKey(incrementSessionKey(getOwnSessionKey(), _hashKey, EspnowProtocolInterpreter::hashKeyLength));
+}
+
+void EncryptedConnectionData::setDesync(const bool desync) { _desync = desync; }
+bool EncryptedConnectionData::desync() const { return _desync; }
+
+String EncryptedConnectionData::serialize() const
+{
+ return Serializer:: serializeEncryptedConnection((temporary() ? String(temporary()->remainingDuration()) : emptyString), String(desync()), TypeCast::uint64ToString(getOwnSessionKey()),
+ TypeCast::uint64ToString(getPeerSessionKey()), TypeCast::macToString(_peerStaMac), TypeCast::macToString(_peerApMac));
+}
+
+const ExpiringTimeTracker *EncryptedConnectionData::temporary() const
+{
+ return _timeTracker.get();
+}
+
+void EncryptedConnectionData::setRemainingDuration(const uint32_t remainingDuration)
+{
+ if(!_timeTracker)
+ {
+ _timeTracker = std::unique_ptr(new ExpiringTimeTracker(remainingDuration)); // TODO: Change to std::make_unique(remainingDuration); once compiler fully supports C++14
+ }
+ else
+ {
+ _timeTracker->setRemainingDuration(remainingDuration);
+ }
+}
+
+void EncryptedConnectionData::removeDuration()
+{
+ _timeTracker = nullptr;
+}
diff --git a/libraries/ESP8266WiFiMesh/src/EncryptedConnectionData.h b/libraries/ESP8266WiFiMesh/src/EncryptedConnectionData.h
new file mode 100644
index 0000000000..cb9ac95593
--- /dev/null
+++ b/libraries/ESP8266WiFiMesh/src/EncryptedConnectionData.h
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2019 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 __ESPNOWENCRYPTEDCONNECTIONDATA_H__
+#define __ESPNOWENCRYPTEDCONNECTIONDATA_H__
+
+#include "ExpiringTimeTracker.h"
+#include "EspnowProtocolInterpreter.h"
+#include
+#include
+
+class EncryptedConnectionData {
+
+public:
+
+ virtual ~EncryptedConnectionData() = default;
+
+ EncryptedConnectionData(const uint8_t peerStaMac[6], const uint8_t peerApMac[6], const uint64_t peerSessionKey, const uint64_t ownSessionKey,
+ const uint8_t hashKey[EspnowProtocolInterpreter::hashKeyLength]);
+ EncryptedConnectionData(const uint8_t peerStaMac[6], const uint8_t peerApMac[6], const uint64_t peerSessionKey, const uint64_t ownSessionKey,
+ const uint32_t duration, const uint8_t hashKey[EspnowProtocolInterpreter::hashKeyLength]);
+
+ EncryptedConnectionData(const EncryptedConnectionData &other);
+
+ EncryptedConnectionData & operator=(const EncryptedConnectionData &other);
+
+ /**
+ * @param resultArray An uint8_t array with at least size 6.
+ *
+ * @return The interface MAC used for communicating with the peer.
+ */
+ uint8_t *getEncryptedPeerMac(uint8_t *resultArray) const;
+ uint8_t *getUnencryptedPeerMac(uint8_t *resultArray) const;
+
+ // @param resultArray At least size 6.
+ uint8_t *getPeerStaMac(uint8_t *resultArray) const;
+ void setPeerStaMac(const uint8_t *peerStaMac) = delete; // A method for setPeerStaMac would sometimes require interacting with the ESP-NOW API to change encrypted connections, so it is not implemented.
+ uint8_t *getPeerApMac(uint8_t *resultArray) const;
+ void setPeerApMac(const uint8_t *peerApMac);
+
+ bool connectedTo(const uint8_t *peerMac) const;
+
+ void setHashKey(const uint8_t hashKey[EspnowProtocolInterpreter::hashKeyLength]);
+ // @param resultArray At least size hashKeyLength.
+ uint8_t *getHashKey(uint8_t *resultArray) const;
+
+ void setPeerSessionKey(const uint64_t sessionKey);
+ uint64_t getPeerSessionKey() const;
+ void setOwnSessionKey(const uint64_t sessionKey);
+ uint64_t getOwnSessionKey() const;
+
+ static uint64_t incrementSessionKey(const uint64_t sessionKey, const uint8_t *hashKey, const uint8_t hashKeyLength);
+ void incrementOwnSessionKey();
+
+ void setDesync(const bool desync);
+ bool desync() const;
+
+ // 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.
+ String serialize() const;
+
+ const ExpiringTimeTracker *temporary() const;
+ virtual void setRemainingDuration(const uint32_t remainingDuration);
+ virtual void removeDuration();
+
+private:
+
+ uint8_t _peerStaMac[6] {0};
+ uint8_t _peerApMac[6] {0};
+ uint64_t _peerSessionKey;
+ uint64_t _ownSessionKey;
+ std::unique_ptr _timeTracker = nullptr;
+ uint8_t _hashKey[EspnowProtocolInterpreter::hashKeyLength] {0};
+ bool _desync = false;
+};
+
+#endif
diff --git a/libraries/ESP8266WiFiMesh/src/EncryptedConnectionLog.cpp b/libraries/ESP8266WiFiMesh/src/EncryptedConnectionLog.cpp
new file mode 100644
index 0000000000..6b979575c4
--- /dev/null
+++ b/libraries/ESP8266WiFiMesh/src/EncryptedConnectionLog.cpp
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2019 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 "EncryptedConnectionLog.h"
+
+namespace
+{
+ using EspnowProtocolInterpreter::hashKeyLength;
+}
+
+EncryptedConnectionLog::EncryptedConnectionLog(const uint8_t peerStaMac[6], const uint8_t peerApMac[6], const uint64_t peerSessionKey, const uint64_t ownSessionKey, const uint8_t hashKey[hashKeyLength])
+ : EncryptedConnectionData(peerStaMac, peerApMac, peerSessionKey, ownSessionKey, hashKey)
+{ }
+
+EncryptedConnectionLog::EncryptedConnectionLog(const uint8_t peerStaMac[6], const uint8_t peerApMac[6], const uint64_t peerSessionKey, const uint64_t ownSessionKey, const uint32_t duration, const uint8_t hashKey[hashKeyLength])
+ : EncryptedConnectionData(peerStaMac, peerApMac, peerSessionKey, ownSessionKey, duration, hashKey)
+{ }
+
+std::unique_ptr EncryptedConnectionLog::_soonestExpiringConnectionTracker = nullptr;
+
+bool EncryptedConnectionLog::_newRemovalsScheduled = false;
+
+void EncryptedConnectionLog::setRemainingDuration(const uint32_t remainingDuration)
+{
+ EncryptedConnectionData::setRemainingDuration(remainingDuration);
+
+ setScheduledForRemoval(false);
+
+ updateSoonestExpiringConnectionTracker(remainingDuration);
+}
+
+void EncryptedConnectionLog::removeDuration()
+{
+ EncryptedConnectionData::removeDuration();
+ setScheduledForRemoval(false);
+}
+
+void EncryptedConnectionLog::scheduleForRemoval()
+{
+ // When we give the connection 0 remaining duration it will be removed during the next performEspnowMaintenance() call.
+ // Duration must be changed before setting the scheduledForRemoval flag to true, since the flag is otherwise cleared.
+ setRemainingDuration(0);
+ setScheduledForRemoval(true);
+}
+
+void EncryptedConnectionLog::setScheduledForRemoval(const bool scheduledForRemoval)
+{
+ _scheduledForRemoval = scheduledForRemoval;
+
+ if(scheduledForRemoval)
+ setNewRemovalsScheduled(true);
+}
+bool EncryptedConnectionLog::removalScheduled() const { return _scheduledForRemoval; }
+
+void EncryptedConnectionLog::setNewRemovalsScheduled(const bool newRemovalsScheduled) { _newRemovalsScheduled = newRemovalsScheduled; }
+bool EncryptedConnectionLog::newRemovalsScheduled( ) { return _newRemovalsScheduled; }
+
+const ExpiringTimeTracker *EncryptedConnectionLog::getSoonestExpiringConnectionTracker()
+{
+ return _soonestExpiringConnectionTracker.get();
+}
+
+void EncryptedConnectionLog::updateSoonestExpiringConnectionTracker(const uint32_t remainingDuration)
+{
+ if(!getSoonestExpiringConnectionTracker() || remainingDuration < getSoonestExpiringConnectionTracker()->remainingDuration())
+ {
+ _soonestExpiringConnectionTracker = std::unique_ptr(new ExpiringTimeTracker(remainingDuration)); // TODO: Change to std::make_unique(remainingDuration); once compiler fully supports C++14
+ }
+}
+
+void EncryptedConnectionLog::clearSoonestExpiringConnectionTracker()
+{
+ _soonestExpiringConnectionTracker = nullptr;
+}
diff --git a/libraries/ESP8266WiFiMesh/src/EncryptedConnectionLog.h b/libraries/ESP8266WiFiMesh/src/EncryptedConnectionLog.h
new file mode 100644
index 0000000000..91386c75f1
--- /dev/null
+++ b/libraries/ESP8266WiFiMesh/src/EncryptedConnectionLog.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2019 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 __ESPNOWENCRYPTEDCONNECTIONLOG_H__
+#define __ESPNOWENCRYPTEDCONNECTIONLOG_H__
+
+#include "EncryptedConnectionData.h"
+#include "EspnowProtocolInterpreter.h"
+
+class EncryptedConnectionLog : public EncryptedConnectionData {
+
+public:
+
+ EncryptedConnectionLog(const uint8_t peerStaMac[6], const uint8_t peerApMac[6], const uint64_t peerSessionKey, const uint64_t ownSessionKey,
+ const uint8_t hashKey[EspnowProtocolInterpreter::hashKeyLength]);
+ EncryptedConnectionLog(const uint8_t peerStaMac[6], const uint8_t peerApMac[6], const uint64_t peerSessionKey, const uint64_t ownSessionKey,
+ const uint32_t duration, const uint8_t hashKey[EspnowProtocolInterpreter::hashKeyLength]);
+
+ // Only guaranteed to expire at the latest when the soonestExpiringConnection does. Can expire before the soonestExpiringConnection since it is not updated on connection removal.
+ // Needs to be a copy to avoid invalidation during operations on temporaryEncryptedConnections.
+ static std::unique_ptr _soonestExpiringConnectionTracker;
+
+ // Only indicates if at least one removal was scheduled since the flag was last cleared, not if the removal is still scheduled to happen.
+ // Canceling a removal will not update the flag.
+ static bool _newRemovalsScheduled;
+
+ // Can be used to set a duration both for temporary and permanent encrypted connections (transforming the latter into a temporary connection in the process).
+ void setRemainingDuration(const uint32_t remainingDuration) override;
+ void removeDuration() override;
+
+ void scheduleForRemoval();
+ bool removalScheduled() const;
+
+ static void setNewRemovalsScheduled(const bool newRemovalsScheduled);
+ static bool newRemovalsScheduled();
+
+ static const ExpiringTimeTracker *getSoonestExpiringConnectionTracker();
+ static void updateSoonestExpiringConnectionTracker(const uint32_t remainingDuration);
+ static void clearSoonestExpiringConnectionTracker();
+
+private:
+
+ bool _scheduledForRemoval = false;
+ void setScheduledForRemoval(const bool scheduledForRemoval);
+};
+
+#endif
diff --git a/libraries/ESP8266WiFiMesh/src/EspnowConnectionManager.cpp b/libraries/ESP8266WiFiMesh/src/EspnowConnectionManager.cpp
new file mode 100644
index 0000000000..c0534d269c
--- /dev/null
+++ b/libraries/ESP8266WiFiMesh/src/EspnowConnectionManager.cpp
@@ -0,0 +1,585 @@
+/*
+ Copyright (C) 2020 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
+*/
+
+#include
+extern "C" {
+ #include
+}
+
+#include "EspnowConnectionManager.h"
+#include "JsonTranslator.h"
+#include "MeshCryptoInterface.h"
+#include "Serializer.h"
+#include "EspnowTransmitter.h"
+
+namespace
+{
+ using EspnowProtocolInterpreter::encryptedConnectionKeyLength;
+ using EspnowProtocolInterpreter::hashKeyLength;
+ using EspnowProtocolInterpreter::maxEncryptedConnections;
+
+ namespace TypeCast = MeshTypeConversionFunctions;
+
+ std::vector _encryptedConnections = {};
+
+ uint32_t _unsynchronizedMessageID = 0;
+
+ uint8_t _espnowEncryptionKok[encryptedConnectionKeyLength] = { 0 };
+ bool _espnowEncryptionKokSet = false;
+}
+
+EspnowConnectionManager::EspnowConnectionManager(ConditionalPrinter &conditionalPrinterInstance, EspnowDatabase &databaseInstance)
+ : _conditionalPrinter(conditionalPrinterInstance), _database(databaseInstance)
+{
+ // Reserve the maximum possible usage early on to prevent heap fragmentation later.
+ encryptedConnections().reserve(maxEncryptedConnections);
+}
+
+std::vector & EspnowConnectionManager::encryptedConnections() { return _encryptedConnections; }
+
+uint8_t EspnowConnectionManager::numberOfEncryptedConnections()
+{
+ return encryptedConnections().size();
+}
+
+ConnectionType EspnowConnectionManager::getConnectionInfo(uint8_t *peerMac, uint32_t *remainingDuration)
+{
+ EncryptedConnectionLog *encryptedConnection = nullptr;
+
+ if(peerMac)
+ encryptedConnection = getEncryptedConnection(peerMac);
+
+ return getConnectionInfoHelper(encryptedConnection, remainingDuration);
+}
+
+ConnectionType EspnowConnectionManager::getConnectionInfo(const uint32_t connectionIndex, uint32_t *remainingDuration, uint8_t *peerMac)
+{
+ EncryptedConnectionLog *encryptedConnection = nullptr;
+
+ if(connectionIndex < numberOfEncryptedConnections())
+ encryptedConnection = &encryptedConnections()[connectionIndex];
+
+ return getConnectionInfoHelper(encryptedConnection, remainingDuration, peerMac);
+}
+
+EspnowConnectionManager::connectionLogIterator EspnowConnectionManager::connectionLogEndIterator()
+{
+ return encryptedConnections().end();
+}
+
+void EspnowConnectionManager::setEspnowEncryptedConnectionKey(const uint8_t espnowEncryptedConnectionKey[encryptedConnectionKeyLength])
+{
+ assert(espnowEncryptedConnectionKey != nullptr);
+
+ for(int i = 0; i < encryptedConnectionKeyLength; ++i)
+ {
+ _espnowEncryptedConnectionKey[i] = espnowEncryptedConnectionKey[i];
+ }
+}
+
+void EspnowConnectionManager::setEspnowEncryptedConnectionKey(const String &espnowEncryptedConnectionKeySeed)
+{
+ MeshCryptoInterface::initializeKey(_espnowEncryptedConnectionKey, encryptedConnectionKeyLength, espnowEncryptedConnectionKeySeed);
+}
+
+const uint8_t *EspnowConnectionManager::getEspnowEncryptedConnectionKey() const
+{
+ return _espnowEncryptedConnectionKey;
+}
+
+uint8_t *EspnowConnectionManager::getEspnowEncryptedConnectionKey(uint8_t resultArray[encryptedConnectionKeyLength]) const
+{
+ std::copy_n(_espnowEncryptedConnectionKey, encryptedConnectionKeyLength, resultArray);
+ return resultArray;
+}
+
+bool EspnowConnectionManager::initializeEncryptionKok()
+{
+ // esp_now_set_kok returns 0 on success.
+ return !(_espnowEncryptionKokSet && esp_now_set_kok(_espnowEncryptionKok, encryptedConnectionKeyLength));
+}
+
+bool EspnowConnectionManager::setEspnowEncryptionKok(uint8_t espnowEncryptionKok[encryptedConnectionKeyLength])
+{
+ if(espnowEncryptionKok == nullptr || esp_now_set_kok(espnowEncryptionKok, encryptedConnectionKeyLength)) // esp_now_set_kok failed if not == 0
+ return false;
+
+ for(int i = 0; i < encryptedConnectionKeyLength; ++i)
+ {
+ _espnowEncryptionKok[i] = espnowEncryptionKok[i];
+ }
+
+ _espnowEncryptionKokSet = true;
+
+ return true;
+}
+
+bool EspnowConnectionManager::setEspnowEncryptionKok(const String &espnowEncryptionKokSeed)
+{
+ uint8_t espnowEncryptionKok[encryptedConnectionKeyLength] {};
+ MeshCryptoInterface::initializeKey(espnowEncryptionKok, encryptedConnectionKeyLength, espnowEncryptionKokSeed);
+
+ return setEspnowEncryptionKok(espnowEncryptionKok);
+}
+
+const uint8_t *EspnowConnectionManager::getEspnowEncryptionKok()
+{
+ if(_espnowEncryptionKokSet)
+ return _espnowEncryptionKok;
+ else
+ return nullptr;
+}
+
+void EspnowConnectionManager::setEspnowHashKey(const uint8_t espnowHashKey[hashKeyLength])
+{
+ assert(espnowHashKey != nullptr);
+
+ for(int i = 0; i < hashKeyLength; ++i)
+ {
+ _espnowHashKey[i] = espnowHashKey[i];
+ }
+}
+
+void EspnowConnectionManager::setEspnowHashKey(const String &espnowHashKeySeed)
+{
+ MeshCryptoInterface::initializeKey(_espnowHashKey, hashKeyLength, espnowHashKeySeed);
+}
+
+const uint8_t *EspnowConnectionManager::getEspnowHashKey() const
+{
+ return _espnowHashKey;
+}
+
+uint8_t *EspnowConnectionManager::getEncryptedMac(const uint8_t *peerMac, uint8_t *resultArray)
+{
+ if(EncryptedConnectionLog *encryptedConnection = getEncryptedConnection(peerMac))
+ {
+ return encryptedConnection->getEncryptedPeerMac(resultArray);
+ }
+
+ return nullptr;
+}
+
+EncryptedConnectionLog *EspnowConnectionManager::getEncryptedConnection(const uint8_t *peerMac)
+{
+ auto connectionIterator = getEncryptedConnectionIterator(peerMac, encryptedConnections());
+ if(connectionIterator != encryptedConnections().end())
+ {
+ return &(*connectionIterator);
+ }
+
+ return nullptr;
+}
+
+EncryptedConnectionLog *EspnowConnectionManager::getEncryptedConnection(const uint32_t connectionIndex)
+{
+ if(connectionIndex < numberOfEncryptedConnections())
+ return &encryptedConnections()[connectionIndex];
+
+ return nullptr;
+}
+
+EncryptedConnectionLog *EspnowConnectionManager::getTemporaryEncryptedConnection(const uint8_t *peerMac)
+{
+ connectionLogIterator connectionIterator = connectionLogEndIterator();
+ if(getTemporaryEncryptedConnectionIterator(peerMac, connectionIterator))
+ {
+ return &(*connectionIterator);
+ }
+
+ return nullptr;
+}
+
+template
+typename T::iterator EspnowConnectionManager::getEncryptedConnectionIterator(const uint8_t *peerMac, T &connectionContainer)
+{
+ typename T::iterator connectionIterator = connectionContainer.begin();
+
+ while(connectionIterator != connectionContainer.end())
+ {
+ if(connectionIterator->connectedTo(peerMac))
+ break;
+ else
+ ++connectionIterator;
+ }
+
+ return connectionIterator;
+}
+
+bool EspnowConnectionManager::getEncryptedConnectionIterator(const uint8_t *peerMac, connectionLogIterator &iterator)
+{
+ connectionLogIterator result = getEncryptedConnectionIterator(peerMac, encryptedConnections());
+
+ if(result != connectionLogEndIterator())
+ {
+ iterator = result;
+ return true;
+ }
+
+ return false;
+}
+
+bool EspnowConnectionManager::getTemporaryEncryptedConnectionIterator(const uint8_t *peerMac, connectionLogIterator &iterator)
+{
+ connectionLogIterator result = connectionLogEndIterator();
+
+ if(getEncryptedConnectionIterator(peerMac, result) && result->temporary())
+ {
+ iterator = result;
+ return true;
+ }
+
+ return false;
+}
+
+ConnectionType EspnowConnectionManager::getConnectionInfoHelper(const EncryptedConnectionLog *encryptedConnection, uint32_t *remainingDuration, uint8_t *peerMac)
+{
+ if(!encryptedConnection)
+ {
+ return ConnectionType::NO_CONNECTION;
+ }
+
+ if(peerMac)
+ encryptedConnection->getEncryptedPeerMac(peerMac);
+
+ if(const ExpiringTimeTracker *timeTracker = encryptedConnection->temporary())
+ {
+ if(remainingDuration)
+ *remainingDuration = timeTracker->remainingDuration();
+
+ return ConnectionType::TEMPORARY_CONNECTION;
+ }
+
+ return ConnectionType::PERMANENT_CONNECTION;
+}
+
+
+void EspnowConnectionManager::setEncryptedConnectionsSoftLimit(const uint8_t softLimit)
+{
+ assert(softLimit <= 6); // Valid values are 0 to 6, but uint8_t is always at least 0.
+ _encryptedConnectionsSoftLimit = softLimit;
+}
+
+uint8_t EspnowConnectionManager::encryptedConnectionsSoftLimit() const { return _encryptedConnectionsSoftLimit; }
+
+bool EspnowConnectionManager::addUnencryptedConnection(const String &serializedConnectionState)
+{
+ return JsonTranslator::getUnsynchronizedMessageID(serializedConnectionState, _unsynchronizedMessageID);
+}
+
+EncryptedConnectionStatus EspnowConnectionManager::addEncryptedConnection(uint8_t *peerStaMac, uint8_t *peerApMac, const uint64_t peerSessionKey, const uint64_t ownSessionKey)
+{
+ assert(encryptedConnections().size() <= maxEncryptedConnections); // If this is not the case, ESP-NOW is no longer in sync with the library
+
+ uint8_t encryptionKeyArray[encryptedConnectionKeyLength] = { 0 };
+
+ if(EncryptedConnectionLog *encryptedConnection = getEncryptedConnection(peerStaMac))
+ {
+ // Encrypted connection with MAC already exists, so no need to replace it, just updating is enough.
+ temporaryEncryptedConnectionToPermanent(peerStaMac);
+ encryptedConnection->setPeerSessionKey(peerSessionKey);
+ encryptedConnection->setOwnSessionKey(ownSessionKey);
+ esp_now_set_peer_key(peerStaMac, getEspnowEncryptedConnectionKey(encryptionKeyArray), encryptedConnectionKeyLength);
+ encryptedConnection->setHashKey(getEspnowHashKey());
+
+ return EncryptedConnectionStatus::CONNECTION_ESTABLISHED;
+ }
+
+ if(encryptedConnections().size() == maxEncryptedConnections)
+ {
+ // No capacity for more encrypted connections.
+ return EncryptedConnectionStatus::MAX_CONNECTIONS_REACHED_SELF;
+ }
+ // returns 0 on success: int esp_now_add_peer(u8 *mac_addr, u8 role, u8 channel, u8 *key, u8 key_len)
+ // Only MAC, encryption key and key length (16) actually matter. The rest is not used by ESP-NOW.
+ else if(0 == esp_now_add_peer(peerStaMac, ESP_NOW_ROLE_CONTROLLER, _database.getWiFiChannel(), getEspnowEncryptedConnectionKey(encryptionKeyArray), encryptedConnectionKeyLength))
+ {
+ encryptedConnections().emplace_back(peerStaMac, peerApMac, peerSessionKey, ownSessionKey, getEspnowHashKey());
+ return EncryptedConnectionStatus::CONNECTION_ESTABLISHED;
+ }
+ else
+ {
+ return EncryptedConnectionStatus::API_CALL_FAILED;
+ }
+}
+
+EncryptedConnectionStatus EspnowConnectionManager::addEncryptedConnection(const String &serializedConnectionState, const bool ignoreDuration)
+{
+ uint32_t duration = 0;
+ bool desync = false;
+ uint64_t ownSessionKey = 0;
+ uint64_t peerSessionKey = 0;
+ uint8_t peerStaMac[6] = { 0 };
+ uint8_t peerApMac[6] = { 0 };
+
+ if(JsonTranslator::getDesync(serializedConnectionState, desync)
+ && JsonTranslator::getOwnSessionKey(serializedConnectionState, ownSessionKey) && JsonTranslator::getPeerSessionKey(serializedConnectionState, peerSessionKey)
+ && JsonTranslator::getPeerStaMac(serializedConnectionState, peerStaMac) && JsonTranslator::getPeerApMac(serializedConnectionState, peerApMac))
+ {
+ EncryptedConnectionStatus result = EncryptedConnectionStatus::API_CALL_FAILED;
+
+ if(!ignoreDuration && JsonTranslator::getDuration(serializedConnectionState, duration))
+ {
+ result = addTemporaryEncryptedConnection(peerStaMac, peerApMac, peerSessionKey, ownSessionKey, duration);
+ }
+ else
+ {
+ result = addEncryptedConnection(peerStaMac, peerApMac, peerSessionKey, ownSessionKey);
+ }
+
+ if(result == EncryptedConnectionStatus::CONNECTION_ESTABLISHED)
+ {
+ EncryptedConnectionLog *encryptedConnection = getEncryptedConnection(peerStaMac);
+ encryptedConnection->setDesync(desync);
+ }
+
+ return result;
+ }
+
+ return EncryptedConnectionStatus::REQUEST_TRANSMISSION_FAILED;
+}
+
+EncryptedConnectionStatus EspnowConnectionManager::addTemporaryEncryptedConnection(uint8_t *peerStaMac, uint8_t *peerApMac, const uint64_t peerSessionKey, const uint64_t ownSessionKey, const uint32_t duration)
+{
+ assert(encryptedConnections().size() <= maxEncryptedConnections); // If this is not the case, ESP-NOW is no longer in sync with the library
+
+ uint8_t encryptionKeyArray[encryptedConnectionKeyLength] = { 0 };
+
+ connectionLogIterator encryptedConnection = connectionLogEndIterator();
+
+ if(getEncryptedConnectionIterator(peerStaMac, encryptedConnection))
+ {
+ // There is already an encrypted connection to this mac, so no need to replace it, just updating is enough.
+ encryptedConnection->setPeerSessionKey(peerSessionKey);
+ encryptedConnection->setOwnSessionKey(ownSessionKey);
+ esp_now_set_peer_key(peerStaMac, getEspnowEncryptedConnectionKey(encryptionKeyArray), encryptedConnectionKeyLength);
+ encryptedConnection->setHashKey(getEspnowHashKey());
+
+ if(encryptedConnection->temporary())
+ {
+ encryptedConnection->setRemainingDuration(duration);
+ }
+
+ return EncryptedConnectionStatus::CONNECTION_ESTABLISHED;
+ }
+
+ EncryptedConnectionStatus result = addEncryptedConnection(peerStaMac, peerApMac, peerSessionKey, ownSessionKey);
+
+ if(result == EncryptedConnectionStatus::CONNECTION_ESTABLISHED)
+ {
+ if(!getEncryptedConnectionIterator(peerStaMac, encryptedConnection))
+ assert(false && String(F("No connection found despite being added in addTemporaryEncryptedConnection.")));
+
+ encryptedConnection->setRemainingDuration(duration);
+ }
+
+ return result;
+}
+
+EncryptedConnectionStatus EspnowConnectionManager::addTemporaryEncryptedConnection(const String &serializedConnectionState, const uint32_t duration)
+{
+ bool desync = false;
+ uint64_t ownSessionKey = 0;
+ uint64_t peerSessionKey = 0;
+ uint8_t peerStaMac[6] = { 0 };
+ uint8_t peerApMac[6] = { 0 };
+
+ if(JsonTranslator::getDesync(serializedConnectionState, desync)
+ && JsonTranslator::getOwnSessionKey(serializedConnectionState, ownSessionKey) && JsonTranslator::getPeerSessionKey(serializedConnectionState, peerSessionKey)
+ && JsonTranslator::getPeerStaMac(serializedConnectionState, peerStaMac) && JsonTranslator::getPeerApMac(serializedConnectionState, peerApMac))
+ {
+ EncryptedConnectionStatus result = addTemporaryEncryptedConnection(peerStaMac, peerApMac, peerSessionKey, ownSessionKey, duration);
+
+ if(result == EncryptedConnectionStatus::CONNECTION_ESTABLISHED)
+ {
+ EncryptedConnectionLog *encryptedConnection = getEncryptedConnection(peerStaMac);
+ encryptedConnection->setDesync(desync);
+ }
+
+ return result;
+ }
+
+ return EncryptedConnectionStatus::REQUEST_TRANSMISSION_FAILED;
+}
+
+EncryptedConnectionRemovalOutcome EspnowConnectionManager::removeEncryptedConnection(const uint8_t *peerMac)
+{
+ auto connectionIterator = getEncryptedConnectionIterator(peerMac, encryptedConnections());
+ if(connectionIterator != encryptedConnections().end())
+ {
+ MutexTracker mutexTracker(EspnowTransmitter::captureEspnowTransmissionMutex());
+ if(!mutexTracker.mutexCaptured())
+ {
+ // We should not remove an encrypted connection while there is a transmission in progress, since that may cause encrypted data to be sent unencrypted.
+ // Thus when a transmission is in progress we just schedule the encrypted connection for removal, so it will be removed during the next updateTemporaryEncryptedConnections() call.
+ connectionIterator->scheduleForRemoval();
+ return EncryptedConnectionRemovalOutcome::REMOVAL_SCHEDULED;
+ }
+ else
+ {
+ return removeEncryptedConnectionUnprotected(peerMac);
+ }
+ }
+
+ // peerMac is already removed
+ return EncryptedConnectionRemovalOutcome::REMOVAL_SUCCEEDED;
+}
+
+
+EncryptedConnectionRemovalOutcome EspnowConnectionManager::removeEncryptedConnectionUnprotected(const uint8_t *peerMac, std::vector::iterator *resultingIterator)
+{
+ connectionLogIterator connectionIterator = getEncryptedConnectionIterator(peerMac, encryptedConnections());
+ return removeEncryptedConnectionUnprotected(connectionIterator, resultingIterator);
+}
+
+EncryptedConnectionRemovalOutcome EspnowConnectionManager::removeEncryptedConnectionUnprotected(connectionLogIterator &connectionIterator, std::vector::iterator *resultingIterator)
+{
+ assert(encryptedConnections().size() <= maxEncryptedConnections); // If this is not the case, ESP-NOW is no longer in sync with the library
+
+ if(connectionIterator != connectionLogEndIterator())
+ {
+ uint8_t encryptedMac[6] {0};
+ connectionIterator->getEncryptedPeerMac(encryptedMac);
+ ConditionalPrinter::staticVerboseModePrint(String(F("Removing connection ")) + TypeCast::macToString(encryptedMac) + String(F("... ")), false);
+ bool removalSucceeded = esp_now_del_peer(encryptedMac) == 0;
+
+ if(removalSucceeded)
+ {
+ if(resultingIterator != nullptr)
+ {
+ *resultingIterator = encryptedConnections().erase(connectionIterator);
+ }
+ else
+ {
+ encryptedConnections().erase(connectionIterator);
+ }
+ ConditionalPrinter::staticVerboseModePrint(String(F("Removal succeeded")));
+
+ // Not deleting encrypted responses here would cause them to be sent unencrypted,
+ // exposing the peer session key which can be misused later if the encrypted connection is re-established.
+ EspnowDatabase::deleteScheduledResponsesByRecipient(encryptedMac, true);
+
+ // Not deleting these entries here may cause issues if the encrypted connection is quickly re-added
+ // and happens to get the same session keys as before (e.g. requestReceived() could then give false positives).
+ EspnowDatabase::deleteEntriesByMac(EspnowDatabase::receivedEspnowTransmissions(), encryptedMac, true);
+ EspnowDatabase::deleteEntriesByMac(EspnowDatabase::sentRequests(), encryptedMac, true);
+ EspnowDatabase::deleteEntriesByMac(EspnowDatabase::receivedRequests(), encryptedMac, true);
+
+ return EncryptedConnectionRemovalOutcome::REMOVAL_SUCCEEDED;
+ }
+ else
+ {
+ ConditionalPrinter::staticVerboseModePrint(String(F("Removal failed")));
+ return EncryptedConnectionRemovalOutcome::REMOVAL_FAILED;
+ }
+ }
+
+ // connection is already removed
+ return EncryptedConnectionRemovalOutcome::REMOVAL_SUCCEEDED;
+}
+
+bool EspnowConnectionManager::temporaryEncryptedConnectionToPermanent(const uint8_t *peerMac)
+{
+ if(EncryptedConnectionLog *temporaryConnection = getTemporaryEncryptedConnection(peerMac))
+ {
+ temporaryConnection->removeDuration();
+ return true;
+ }
+
+ return false;
+}
+
+String EspnowConnectionManager::serializeUnencryptedConnection()
+{
+ return Serializer::serializeUnencryptedConnection(String(_unsynchronizedMessageID));
+}
+
+String EspnowConnectionManager::serializeEncryptedConnection(const uint8_t *peerMac)
+{
+ String serializedConnection(emptyString);
+
+ EncryptedConnectionLog *encryptedConnection = nullptr;
+
+ if(peerMac)
+ encryptedConnection = getEncryptedConnection(peerMac);
+
+ if(encryptedConnection)
+ serializedConnection = encryptedConnection->serialize();
+
+ return serializedConnection;
+}
+
+String EspnowConnectionManager::serializeEncryptedConnection(const uint32_t connectionIndex)
+{
+ String serializedConnection(emptyString);
+
+ if(EncryptedConnectionLog *encryptedConnection = getEncryptedConnection(connectionIndex))
+ serializedConnection = encryptedConnection->serialize();
+
+ return serializedConnection;
+}
+
+void EspnowConnectionManager::handlePostponedRemovals()
+{
+ MutexTracker mutexTracker(EspnowTransmitter::captureEspnowTransmissionMutex());
+ if(!mutexTracker.mutexCaptured())
+ {
+ assert(false && String(F("ERROR! Transmission in progress. Don't call handlePostponedRemovals from callbacks as this may corrupt program state! Aborting.")));
+ return;
+ }
+
+ if(EncryptedConnectionLog::newRemovalsScheduled())
+ {
+ updateTemporaryEncryptedConnections(true);
+ }
+}
+
+void EspnowConnectionManager::updateTemporaryEncryptedConnections(const bool scheduledRemovalOnly)
+{
+ EncryptedConnectionLog::clearSoonestExpiringConnectionTracker();
+
+ for(auto connectionIterator = encryptedConnections().begin(); connectionIterator != encryptedConnections().end(); )
+ {
+ if(auto timeTrackerPointer = connectionIterator->temporary())
+ {
+ if(timeTrackerPointer->expired() && (!scheduledRemovalOnly || connectionIterator->removalScheduled()))
+ {
+ uint8_t macArray[6] = { 0 };
+ removeEncryptedConnectionUnprotected(connectionIterator->getEncryptedPeerMac(macArray), &connectionIterator);
+ continue;
+ }
+ else
+ {
+ EncryptedConnectionLog::updateSoonestExpiringConnectionTracker(timeTrackerPointer->remainingDuration());
+ }
+ }
+ assert(!connectionIterator->removalScheduled()); // timeTracker should always exist and be expired if removal is scheduled.
+
+ ++connectionIterator;
+ }
+
+ EncryptedConnectionLog::setNewRemovalsScheduled(false);
+}
+
+uint64_t EspnowConnectionManager::generateMessageID(const EncryptedConnectionLog *encryptedConnection)
+{
+ if(encryptedConnection)
+ {
+ return encryptedConnection->getOwnSessionKey();
+ }
+
+ return _unsynchronizedMessageID++;
+}
diff --git a/libraries/ESP8266WiFiMesh/src/EspnowConnectionManager.h b/libraries/ESP8266WiFiMesh/src/EspnowConnectionManager.h
new file mode 100644
index 0000000000..7edcfcb930
--- /dev/null
+++ b/libraries/ESP8266WiFiMesh/src/EspnowConnectionManager.h
@@ -0,0 +1,152 @@
+/*
+ Copyright (C) 2020 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
+*/
+
+#ifndef __ESPNOWCONNECTIONMANAGER_H__
+#define __ESPNOWCONNECTIONMANAGER_H__
+
+#include
+#include "ConditionalPrinter.h"
+#include "EspnowDatabase.h"
+#include "EspnowProtocolInterpreter.h"
+#include "EncryptedConnectionLog.h"
+
+class EspnowMeshBackend;
+
+enum class ConnectionType
+{
+ NO_CONNECTION = 0,
+ TEMPORARY_CONNECTION = 1,
+ PERMANENT_CONNECTION = 2
+};
+
+// A value greater than 0 means that an encrypted connection has been established.
+enum class EncryptedConnectionStatus
+{
+ MAX_CONNECTIONS_REACHED_SELF = -3,
+ REQUEST_TRANSMISSION_FAILED = -2,
+ MAX_CONNECTIONS_REACHED_PEER = -1,
+ API_CALL_FAILED = 0,
+ CONNECTION_ESTABLISHED = 1,
+ SOFT_LIMIT_CONNECTION_ESTABLISHED = 2 // Only used if _encryptedConnectionsSoftLimit is less than 6. See the setEncryptedConnectionsSoftLimit method documentation for details.
+};
+
+enum class EncryptedConnectionRemovalOutcome
+{
+ REMOVAL_REQUEST_FAILED = -1,
+ REMOVAL_FAILED = 0,
+ REMOVAL_SUCCEEDED = 1,
+ REMOVAL_SCHEDULED = 2
+};
+
+class EspnowConnectionManager
+{
+
+public:
+
+ using connectionLogIterator = std::vector::iterator;
+
+ EspnowConnectionManager(ConditionalPrinter &conditionalPrinterInstance, EspnowDatabase &databaseInstance);
+
+ static std::vector & encryptedConnections();
+
+ static uint8_t numberOfEncryptedConnections();
+
+ static ConnectionType getConnectionInfo(uint8_t *peerMac, uint32_t *remainingDuration = nullptr);
+ static ConnectionType getConnectionInfo(const uint32_t connectionIndex, uint32_t *remainingDuration = nullptr, uint8_t *peerMac = nullptr);
+
+ static connectionLogIterator connectionLogEndIterator();
+
+ void setEspnowEncryptedConnectionKey(const uint8_t espnowEncryptedConnectionKey[EspnowProtocolInterpreter::encryptedConnectionKeyLength]);
+ void setEspnowEncryptedConnectionKey(const String &espnowEncryptedConnectionKeySeed);
+ const uint8_t *getEspnowEncryptedConnectionKey() const;
+ uint8_t *getEspnowEncryptedConnectionKey(uint8_t resultArray[EspnowProtocolInterpreter::encryptedConnectionKeyLength]) const;
+ // Returns false if failed to apply the current KoK (default KoK is used if no KoK provided)
+ static bool initializeEncryptionKok();
+ static bool setEspnowEncryptionKok(uint8_t espnowEncryptionKok[EspnowProtocolInterpreter::encryptedConnectionKeyLength]);
+ static bool setEspnowEncryptionKok(const String &espnowEncryptionKokSeed);
+ static const uint8_t *getEspnowEncryptionKok();
+ void setEspnowHashKey(const uint8_t espnowHashKey[EspnowProtocolInterpreter::hashKeyLength]);
+ void setEspnowHashKey(const String &espnowHashKeySeed);
+ const uint8_t *getEspnowHashKey() const;
+
+ static uint8_t *getEncryptedMac(const uint8_t *peerMac, uint8_t *resultArray);
+
+ static EncryptedConnectionLog *getEncryptedConnection(const uint8_t *peerMac);
+ static EncryptedConnectionLog *getEncryptedConnection(const uint32_t connectionIndex);
+ static EncryptedConnectionLog *getTemporaryEncryptedConnection(const uint8_t *peerMac);
+
+ //@return iterator to connection in connectionContainer, or connectionContainer.end() if element not found
+ template
+ static typename T::iterator getEncryptedConnectionIterator(const uint8_t *peerMac, T &connectionContainer);
+ static bool getEncryptedConnectionIterator(const uint8_t *peerMac, connectionLogIterator &iterator);
+ // @return true if an encrypted connection to peerMac is found and the found connection is temporary. Only changes iterator if true is returned.
+ static bool getTemporaryEncryptedConnectionIterator(const uint8_t *peerMac, connectionLogIterator &iterator);
+
+ void setEncryptedConnectionsSoftLimit(const uint8_t softLimit);
+ uint8_t encryptedConnectionsSoftLimit() const;
+
+ static bool addUnencryptedConnection(const String &serializedConnectionState);
+ EncryptedConnectionStatus addEncryptedConnection(uint8_t *peerStaMac, uint8_t *peerApMac, const uint64_t peerSessionKey, const uint64_t ownSessionKey);
+ EncryptedConnectionStatus addEncryptedConnection(const String &serializedConnectionState, const bool ignoreDuration = false);
+ EncryptedConnectionStatus addTemporaryEncryptedConnection(uint8_t *peerStaMac, uint8_t *peerApMac, const uint64_t peerSessionKey, const uint64_t ownSessionKey, const uint32_t duration);
+ EncryptedConnectionStatus addTemporaryEncryptedConnection(const String &serializedConnectionState, const uint32_t duration);
+
+ static EncryptedConnectionRemovalOutcome removeEncryptedConnection(const uint8_t *peerMac);
+
+ // Note that removing an encrypted connection while there are encrypted responses scheduled for transmission to the encrypted peer will cause these encrypted responses to be removed without being sent.
+ // Also note that removing an encrypted connection while there is encrypted data to be received will make the node unable to decrypt that data (although an ack will still be sent to confirm data reception).
+ // In other words, it is good to use these methods with care and to make sure that both nodes in an encrypted pair are in a state where it is safe for the encrypted connection to be removed before using them.
+ // Consider using getScheduledResponseRecipient and similar methods for this preparation.
+ // Should only be used when there is no transmissions in progress. In practice when _espnowTransmissionMutex is free.
+ // @param resultingIterator Will be set to the iterator position after the removed element, if an element to remove was found. Otherwise no change will occur.
+ static EncryptedConnectionRemovalOutcome removeEncryptedConnectionUnprotected(const uint8_t *peerMac, std::vector::iterator *resultingIterator = nullptr);
+ static EncryptedConnectionRemovalOutcome removeEncryptedConnectionUnprotected(connectionLogIterator &connectionIterator, std::vector::iterator *resultingIterator);
+
+ static bool temporaryEncryptedConnectionToPermanent(const uint8_t *peerMac);
+
+ static String serializeUnencryptedConnection();
+ static String serializeEncryptedConnection(const uint8_t *peerMac);
+ static String serializeEncryptedConnection(const uint32_t connectionIndex);
+
+ static void handlePostponedRemovals();
+
+ // Should only be used when there is no transmissions in progress, so it is safe to remove encrypted connections. In practice when _espnowTransmissionMutex is free.
+ // @param scheduledRemovalOnly If true, only deletes encrypted connections where removalScheduled() is true. This means only connections which have been requested for removal will be deleted,
+ // not other connections which have expired.
+ static void updateTemporaryEncryptedConnections(const bool scheduledRemovalOnly = false);
+
+ /**
+ * Generate a new message ID to be used when making a data transmission. The generated ID will be different depending on whether an encrypted connection exists or not.
+ *
+ * @param encryptedConnection A pointer to the EncryptedConnectionLog of the encrypted connection. Can be set to nullptr if the connection is unecrypted.
+ * @return The generated message ID.
+ */
+ static uint64_t generateMessageID(const EncryptedConnectionLog *encryptedConnection);
+
+private:
+
+ ConditionalPrinter & _conditionalPrinter;
+ EspnowDatabase & _database;
+
+ static ConnectionType getConnectionInfoHelper(const EncryptedConnectionLog *encryptedConnection, uint32_t *remainingDuration, uint8_t *peerMac = nullptr);
+
+ uint8_t _espnowEncryptedConnectionKey[EspnowProtocolInterpreter::encryptedConnectionKeyLength] {0};
+ uint8_t _espnowHashKey[EspnowProtocolInterpreter::hashKeyLength] {0};
+
+ uint8_t _encryptedConnectionsSoftLimit = 6;
+};
+
+#endif
diff --git a/libraries/ESP8266WiFiMesh/src/EspnowDatabase.cpp b/libraries/ESP8266WiFiMesh/src/EspnowDatabase.cpp
new file mode 100644
index 0000000000..d718726787
--- /dev/null
+++ b/libraries/ESP8266WiFiMesh/src/EspnowDatabase.cpp
@@ -0,0 +1,383 @@
+/*
+ Copyright (C) 2020 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
+*/
+
+#include "EspnowDatabase.h"
+#include "EspnowMeshBackend.h"
+#include "UtilityFunctions.h"
+
+namespace
+{
+ namespace TypeCast = MeshTypeConversionFunctions;
+
+ // _logEntryLifetimeMs is based on someone storing 40 responses of 750 bytes each = 30 000 bytes (roughly full memory),
+ // which takes 2000 ms + some margin to send. Also, we want to avoid old entries taking up memory if they cannot be sent,
+ // so storage duration should not be too long.
+ uint32_t _logEntryLifetimeMs = 2500;
+ uint32_t _broadcastResponseTimeoutMs = 1000; // This is shorter than _logEntryLifetimeMs to preserve RAM since broadcasts are not deleted from sentRequests until they expire.
+ ExpiringTimeTracker _logClearingCooldown(500);
+
+ uint32_t _encryptionRequestTimeoutMs = 300;
+
+ uint32_t _criticalHeapLevel = 6000; // In bytes
+ uint32_t _criticalHeapLevelBuffer = 6000; // In bytes
+
+ using EspnowProtocolInterpreter::macAndType_td;
+ using EspnowProtocolInterpreter::messageID_td;
+ using EspnowProtocolInterpreter::peerMac_td;
+
+ std::list _responsesToSend = {};
+ std::list _peerRequestConfirmationsToSend = {};
+
+ std::map, MessageData> _receivedEspnowTransmissions = {};
+ std::map, RequestData> _sentRequests = {};
+ std::map, TimeTracker> _receivedRequests = {};
+
+ std::shared_ptr _espnowConnectionQueueMutex = std::make_shared(false);
+ std::shared_ptr _responsesToSendMutex = std::make_shared(false);
+}
+
+std::vector EspnowDatabase::_connectionQueue = {};
+std::vector EspnowDatabase::_latestTransmissionOutcomes = {};
+
+EspnowDatabase::EspnowDatabase(ConditionalPrinter &conditionalPrinterInstance, const uint8 espnowWiFiChannel) : _conditionalPrinter(conditionalPrinterInstance), _espnowWiFiChannel(espnowWiFiChannel)
+{
+}
+
+std::vector & EspnowDatabase::connectionQueue()
+{
+ MutexTracker connectionQueueMutexTracker(_espnowConnectionQueueMutex);
+ if(!connectionQueueMutexTracker.mutexCaptured())
+ {
+ assert(false && String(F("ERROR! connectionQueue locked. Don't call connectionQueue() from callbacks other than NetworkFilter as this may corrupt program state!")));
+ }
+
+ return _connectionQueue;
+}
+
+const std::vector & EspnowDatabase::constConnectionQueue()
+{
+ return _connectionQueue;
+}
+
+std::vector & EspnowDatabase::latestTransmissionOutcomes()
+{
+ return _latestTransmissionOutcomes;
+}
+
+void EspnowDatabase::setCriticalHeapLevelBuffer(const uint32_t bufferInBytes)
+{
+ _criticalHeapLevelBuffer = bufferInBytes;
+}
+
+uint32_t EspnowDatabase::criticalHeapLevelBuffer()
+{
+ return _criticalHeapLevelBuffer;
+}
+
+uint32_t EspnowDatabase::criticalHeapLevel()
+{
+ return _criticalHeapLevel;
+}
+
+template
+void EspnowDatabase::deleteExpiredLogEntries(std::map, T> &logEntries, const uint32_t maxEntryLifetimeMs)
+{
+ for(typename std::map, T>::iterator entryIterator = logEntries.begin();
+ entryIterator != logEntries.end(); )
+ {
+ if(entryIterator->second.getTimeTracker().timeSinceCreation() > maxEntryLifetimeMs)
+ {
+ entryIterator = logEntries.erase(entryIterator);
+ }
+ else
+ ++entryIterator;
+ }
+}
+
+template
+void EspnowDatabase::deleteExpiredLogEntries(std::map, TimeTracker> &logEntries, const uint32_t maxEntryLifetimeMs)
+{
+ for(typename std::map, TimeTracker>::iterator entryIterator = logEntries.begin();
+ entryIterator != logEntries.end(); )
+ {
+ if(entryIterator->second.timeSinceCreation() > maxEntryLifetimeMs)
+ {
+ entryIterator = logEntries.erase(entryIterator);
+ }
+ else
+ ++entryIterator;
+ }
+}
+
+void EspnowDatabase::deleteExpiredLogEntries(std::map, RequestData> &logEntries, const uint32_t requestLifetimeMs, const uint32_t broadcastLifetimeMs)
+{
+ for(typename std::map, RequestData>::iterator entryIterator = logEntries.begin();
+ entryIterator != logEntries.end(); )
+ {
+ bool broadcast = entryIterator->first.first == EspnowProtocolInterpreter::uint64BroadcastMac;
+ uint32_t timeSinceCreation = entryIterator->second.getTimeTracker().timeSinceCreation();
+
+ if((!broadcast && timeSinceCreation > requestLifetimeMs)
+ || (broadcast && timeSinceCreation > broadcastLifetimeMs))
+ {
+ entryIterator = logEntries.erase(entryIterator);
+ }
+ else
+ ++entryIterator;
+ }
+}
+
+template
+void EspnowDatabase::deleteExpiredLogEntries(std::list &logEntries, const uint32_t maxEntryLifetimeMs)
+{
+ for(typename std::list::iterator entryIterator = logEntries.begin();
+ entryIterator != logEntries.end(); )
+ {
+ if(entryIterator->getTimeTracker().timeSinceCreation() > maxEntryLifetimeMs)
+ {
+ entryIterator = logEntries.erase(entryIterator);
+ }
+ else
+ ++entryIterator;
+ }
+}
+
+template <>
+void EspnowDatabase::deleteExpiredLogEntries(std::list &logEntries, const uint32_t maxEntryLifetimeMs)
+{
+ for(typename std::list::iterator entryIterator = logEntries.begin();
+ entryIterator != logEntries.end(); )
+ {
+ auto timeTrackerPointer = entryIterator->temporary();
+ if(timeTrackerPointer && timeTrackerPointer->elapsedTime() > maxEntryLifetimeMs)
+ {
+ entryIterator = logEntries.erase(entryIterator);
+ }
+ else
+ ++entryIterator;
+ }
+}
+
+void EspnowDatabase::setLogEntryLifetimeMs(const uint32_t logEntryLifetimeMs)
+{
+ _logEntryLifetimeMs = logEntryLifetimeMs;
+}
+uint32_t EspnowDatabase::logEntryLifetimeMs() { return _logEntryLifetimeMs; }
+
+void EspnowDatabase::setBroadcastResponseTimeoutMs(const uint32_t broadcastResponseTimeoutMs)
+{
+ _broadcastResponseTimeoutMs = broadcastResponseTimeoutMs;
+}
+uint32_t EspnowDatabase::broadcastResponseTimeoutMs() { return _broadcastResponseTimeoutMs; }
+
+String EspnowDatabase::getScheduledResponseMessage(const uint32_t responseIndex)
+{
+ return getScheduledResponse(responseIndex)->getMessage();
+}
+
+const uint8_t *EspnowDatabase::getScheduledResponseRecipient(const uint32_t responseIndex)
+{
+ return getScheduledResponse(responseIndex)->getRecipientMac();
+}
+
+uint32_t EspnowDatabase::numberOfScheduledResponses() {return responsesToSend().size();}
+
+void EspnowDatabase::clearAllScheduledResponses()
+{
+ MutexTracker responsesToSendMutexTracker(_responsesToSendMutex);
+ if(!responsesToSendMutexTracker.mutexCaptured())
+ {
+ assert(false && String(F("ERROR! responsesToSend locked. Don't call clearAllScheduledResponses from callbacks as this may corrupt program state! Aborting.")));
+ }
+
+ responsesToSend().clear();
+}
+
+void EspnowDatabase::deleteScheduledResponsesByRecipient(const uint8_t *recipientMac, const bool encryptedOnly)
+{
+ MutexTracker responsesToSendMutexTracker(_responsesToSendMutex);
+ if(!responsesToSendMutexTracker.mutexCaptured())
+ {
+ assert(false && String(F("ERROR! responsesToSend locked. Don't call deleteScheduledResponsesByRecipient from callbacks as this may corrupt program state! Aborting.")));
+ }
+
+ for(auto responseIterator = responsesToSend().begin(); responseIterator != responsesToSend().end(); )
+ {
+ if(MeshUtilityFunctions::macEqual(responseIterator->getRecipientMac(), recipientMac) &&
+ (!encryptedOnly || EspnowProtocolInterpreter::usesEncryption(responseIterator->getRequestID())))
+ {
+ responseIterator = responsesToSend().erase(responseIterator);
+ }
+ else
+ ++responseIterator;
+ }
+}
+
+void EspnowDatabase::setEncryptionRequestTimeout(const uint32_t timeoutMs)
+{
+ _encryptionRequestTimeoutMs = timeoutMs;
+}
+uint32_t EspnowDatabase::getEncryptionRequestTimeout() {return _encryptionRequestTimeoutMs;}
+
+void EspnowDatabase::setAutoEncryptionDuration(const uint32_t duration)
+{
+ _autoEncryptionDuration = duration;
+}
+uint32_t EspnowDatabase::getAutoEncryptionDuration() const {return _autoEncryptionDuration;}
+
+String EspnowDatabase::getSenderMac() const {return TypeCast::macToString(_senderMac);}
+uint8_t *EspnowDatabase::getSenderMac(uint8_t *macArray) const
+{
+ std::copy_n(_senderMac, 6, macArray);
+ return macArray;
+}
+
+String EspnowDatabase::getSenderAPMac() const {return TypeCast::macToString(_senderAPMac);}
+uint8_t *EspnowDatabase::getSenderAPMac(uint8_t *macArray) const
+{
+ std::copy_n(_senderAPMac, 6, macArray);
+ return macArray;
+}
+
+void EspnowDatabase::clearOldLogEntries(bool forced)
+{
+ // Clearing all old log entries at the same time should help minimize heap fragmentation.
+
+ // uint32_t startTime = millis();
+
+ if(!forced && !_logClearingCooldown) // Clearing too frequently will cause a lot of unnecessary container iterations.
+ {
+ return;
+ }
+
+ _logClearingCooldown.reset();
+
+ deleteExpiredLogEntries(receivedEspnowTransmissions(), logEntryLifetimeMs());
+ deleteExpiredLogEntries(receivedRequests(), logEntryLifetimeMs()); // Just needs to be long enough to not accept repeated transmissions by mistake.
+ deleteExpiredLogEntries(sentRequests(), logEntryLifetimeMs(), broadcastResponseTimeoutMs());
+ deleteExpiredLogEntries(responsesToSend(), logEntryLifetimeMs());
+ deleteExpiredLogEntries(peerRequestConfirmationsToSend(), getEncryptionRequestTimeout());
+}
+
+std::list::const_iterator EspnowDatabase::getScheduledResponse(const uint32_t responseIndex)
+{
+ assert(responseIndex < numberOfScheduledResponses());
+
+ bool startFromBeginning = responseIndex < numberOfScheduledResponses()/2;
+ auto responseIterator = startFromBeginning ? responsesToSend().cbegin() : responsesToSend().cend();
+ uint32_t stepsToTarget = startFromBeginning ? responseIndex : numberOfScheduledResponses() - responseIndex; // cend is one element beyond the last
+
+ while(stepsToTarget > 0)
+ {
+ startFromBeginning ? ++responseIterator : --responseIterator;
+ --stepsToTarget;
+ }
+
+ return responseIterator;
+}
+
+void EspnowDatabase::setSenderMac(const uint8_t *macArray)
+{
+ std::copy_n(macArray, 6, _senderMac);
+}
+
+void EspnowDatabase::setSenderAPMac(const uint8_t *macArray)
+{
+ std::copy_n(macArray, 6, _senderAPMac);
+}
+
+void EspnowDatabase::setWiFiChannel(const uint8 newWiFiChannel)
+{
+ wifi_country_t wifiCountry;
+ wifi_get_country(&wifiCountry); // Note: Should return 0 on success and -1 on failure, but always seems to return 1. Possibly broken API. Channels 1 to 13 are the default limits.
+ assert(wifiCountry.schan <= newWiFiChannel && newWiFiChannel <= wifiCountry.schan + wifiCountry.nchan - 1);
+
+ _espnowWiFiChannel = newWiFiChannel;
+}
+
+uint8 EspnowDatabase::getWiFiChannel() const
+{
+ return _espnowWiFiChannel;
+}
+
+bool EspnowDatabase::requestReceived(const uint64_t requestMac, const uint64_t requestID)
+{
+ return receivedRequests().count(std::make_pair(requestMac, requestID));
+}
+
+MutexTracker EspnowDatabase::captureEspnowConnectionQueueMutex()
+{
+ // Syntax like this will move the resulting value into its new position (similar to NRVO): https://stackoverflow.com/a/11540204
+ return MutexTracker(_espnowConnectionQueueMutex);
+}
+
+MutexTracker EspnowDatabase::captureEspnowConnectionQueueMutex(const std::function destructorHook) { return MutexTracker(_espnowConnectionQueueMutex, destructorHook); }
+
+MutexTracker EspnowDatabase::captureResponsesToSendMutex(){ return MutexTracker(_responsesToSendMutex); }
+
+MutexTracker EspnowDatabase::captureResponsesToSendMutex(const std::function destructorHook) { return MutexTracker(_responsesToSendMutex, destructorHook); }
+
+void EspnowDatabase::storeSentRequest(const uint64_t targetBSSID, const uint64_t messageID, const RequestData &requestData)
+{
+ sentRequests().insert(std::make_pair(std::make_pair(targetBSSID, messageID), requestData));
+}
+
+void EspnowDatabase::storeReceivedRequest(const uint64_t senderBSSID, const uint64_t messageID, const TimeTracker &timeTracker)
+{
+ receivedRequests().insert(std::make_pair(std::make_pair(senderBSSID, messageID), timeTracker));
+}
+
+EspnowMeshBackend *EspnowDatabase::getOwnerOfSentRequest(const uint64_t requestMac, const uint64_t requestID)
+{
+ std::map, RequestData>::iterator sentRequest = sentRequests().find(std::make_pair(requestMac, requestID));
+
+ if(sentRequest != sentRequests().end())
+ {
+ return &sentRequest->second.getMeshInstance();
+ }
+
+ return nullptr;
+}
+
+size_t EspnowDatabase::deleteSentRequest(const uint64_t requestMac, const uint64_t requestID)
+{
+ return sentRequests().erase(std::make_pair(requestMac, requestID));
+}
+
+size_t EspnowDatabase::deleteSentRequestsByOwner(const EspnowMeshBackend *instancePointer)
+{
+ size_t numberDeleted = 0;
+
+ for(std::map, RequestData>::iterator requestIterator = sentRequests().begin();
+ requestIterator != sentRequests().end(); )
+ {
+ if(&requestIterator->second.getMeshInstance() == instancePointer) // If instance at instancePointer made the request
+ {
+ requestIterator = sentRequests().erase(requestIterator);
+ numberDeleted++;
+ }
+ else
+ ++requestIterator;
+ }
+
+ return numberDeleted;
+}
+
+std::list & EspnowDatabase::responsesToSend() { return _responsesToSend; }
+std::list & EspnowDatabase::peerRequestConfirmationsToSend() { return _peerRequestConfirmationsToSend; }
+std::map, MessageData> & EspnowDatabase::receivedEspnowTransmissions() { return _receivedEspnowTransmissions; }
+std::map, RequestData> & EspnowDatabase::sentRequests() { return _sentRequests; }
+std::map, TimeTracker> & EspnowDatabase::receivedRequests() { return _receivedRequests; }
diff --git a/libraries/ESP8266WiFiMesh/src/EspnowDatabase.h b/libraries/ESP8266WiFiMesh/src/EspnowDatabase.h
new file mode 100644
index 0000000000..34ec34f924
--- /dev/null
+++ b/libraries/ESP8266WiFiMesh/src/EspnowDatabase.h
@@ -0,0 +1,223 @@
+/*
+ Copyright (C) 2020 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
+*/
+
+#ifndef __ESPNOWDATABASE_H__
+#define __ESPNOWDATABASE_H__
+
+#include
+#include "EspnowNetworkInfo.h"
+#include "TransmissionOutcome.h"
+#include "ResponseData.h"
+#include "RequestData.h"
+#include "EspnowProtocolInterpreter.h"
+#include
+#include