mirror of
https://github.com/esp8266/Arduino.git
synced 2025-06-06 05:21:22 +03:00
- Add new ESP-NOW mesh backend.
- Add HelloEspnow.ino example to demonstrate the ESP-NOW mesh backend features. - Deprecate the ESP8266WiFiMesh class in favour of the new ESP-NOW and TCP/IP backends. - Update the TCP/IP mesh backend to use the new lwIP version preprocessor flag and remove obsolete preprocessor flags.
This commit is contained in:
parent
1b3ac4f5e9
commit
d20177ae35
@ -3,9 +3,9 @@ ESP8266 WiFi Mesh
|
||||
|
||||
A library for turning your ESP8266 into a mesh network node.
|
||||
|
||||
The library has been tested and works with Arduino core for ESP8266 version 2.3.0 (with default lwIP) and 2.4.2 or higher (with lwIP 1.4 and lwIP2).
|
||||
The library has been tested and works with Arduino core for ESP8266 version 2.6.0 (with lwIP2). It may work with earlier and later core releases, but this has not been tested during development.
|
||||
|
||||
**Note:** This mesh library has been 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.
|
||||
**Note:** This mesh library has been rewritten for core release 2.6.0. The old method signatures have been retained for compatibility purposes, but will be removed in core release 3.0.0. If you are still using these old method signatures please consider migrating to the new API shown in the `EspnowMeshBackend.h` or `TcpIpMeshBackend.h` source files.
|
||||
|
||||
Usage
|
||||
-----
|
||||
@ -48,14 +48,18 @@ ESP8266WiFiMesh(requestHandlerType requestHandler, responseHandlerType responseH
|
||||
|
||||
### Note
|
||||
|
||||
* This library can use static IP:s for the nodes to speed up connection times. To enable this, use the `setStaticIP` method after calling the `begin` method, as in the included example. Ensure that nodes connecting to the same AP have distinct static IP:s. Node IP:s need to be at the same subnet as the server gateway (192.168.4 for this library by default). It may also be worth noting that station gateway IP must match the IP for the server on the nodes, though this is the default setting for the library.
|
||||
* 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:
|
||||
|
||||
At the moment static IP is a global setting, meaning that all ESP8266WiFiMesh instances on the same ESP8266 share the same static IP settings.
|
||||
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 ESP8266WiFiMesh instances on the same ESP8266 share the same static IP settings.
|
||||
|
||||
* When Arduino core for ESP8266 version 2.4.2 or higher is used, there are optimizations available for WiFi scans and static IP use to reduce the time it takes for nodes to connect to each other. These optimizations are enabled by default. To take advantage of the static IP optimizations you also need to use lwIP2. The lwIP version can be changed in the Tools menu of Arduino IDE.
|
||||
|
||||
If you are using a core version prior to 2.4.2 it is possible to disable the WiFi scan and static IP optimizations by commenting out the `ENABLE_STATIC_IP_OPTIMIZATION` and `ENABLE_WIFI_SCAN_OPTIMIZATION` defines in ESP8266WiFiMesh.h. Press Ctrl+K in the Arduino IDE while an example from the mesh library is opened, to open the library folder (or click "Show Sketch Folder" in the Sketch menu). ESP8266WiFiMesh.h can then be found at ESP8266WiFiMesh/src. Edit the file with any text editor.
|
||||
|
||||
* The WiFi scan optimization mentioned above works by making WiFi scans only search through the same WiFi channel as the ESP8266WiFiMesh instance is using. If you would like to scan all WiFi channels instead, set the `scanAllWiFiChannels` argument of the `attemptTransmission` method to `true`. Note that scanning all WiFi channels will slow down scans considerably and make it more likely that existing WiFi connections will break during scans. Also note that if the ESP8266 has an active AP, that AP will switch WiFi channel to match that of any other AP the ESP8266 connects to (compare next bullet point). This can make it impossible for other nodes to detect the AP if they are scanning the wrong WiFi channel. To remedy this, force the AP back on the original channel by using the `restartAP` method of the current AP controller once the ESP8266 has disconnected from the other AP. This would typically be done like so:
|
||||
|
||||
```
|
||||
|
312
libraries/ESP8266WiFiMesh/examples/HelloEspnow/HelloEspnow.ino
Normal file
312
libraries/ESP8266WiFiMesh/examples/HelloEspnow/HelloEspnow.ino
Normal file
@ -0,0 +1,312 @@
|
||||
#include <ESP8266WiFi.h>
|
||||
#include <EspnowMeshBackend.h>
|
||||
#include <TypeConversionFunctions.h>
|
||||
#include <assert.h>
|
||||
|
||||
/**
|
||||
NOTE: Although we could define the strings below as normal String variables,
|
||||
here we are using PROGMEM combined with the FPSTR() macro (and also just the F() macro further down in the file).
|
||||
The reason is that this approach will place the strings in flash memory which will help save RAM during program execution.
|
||||
Reading strings from flash will be slower than reading them from RAM,
|
||||
but this will be a negligible difference when printing them to Serial.
|
||||
|
||||
More on F(), FPSTR() and PROGMEM:
|
||||
https://github.com/esp8266/Arduino/issues/1143
|
||||
https://arduino-esp8266.readthedocs.io/en/latest/PROGMEM.html
|
||||
*/
|
||||
const char exampleMeshName[] PROGMEM = "MeshNode_"; // The name of the mesh network. Used as prefix for the node SSID and to find other network nodes in the example networkFilter function below.
|
||||
const char exampleWiFiPassword[] PROGMEM = "ChangeThisWiFiPassword_TODO"; // 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.
|
||||
uint8_t espnowEncryptionKey[16] = {0x33, 0x44, 0x33, 0x44, 0x33, 0x44, 0x33, 0x44, // This is the key for encrypting transmissions.
|
||||
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 encryption 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;
|
||||
|
||||
String manageRequest(const String &request, MeshBackendBase &meshInstance);
|
||||
transmission_status_t manageResponse(const String &response, MeshBackendBase &meshInstance);
|
||||
void networkFilter(int numberOfNetworks, MeshBackendBase &meshInstance);
|
||||
|
||||
/* Create the mesh node object */
|
||||
EspnowMeshBackend espnowNode = EspnowMeshBackend(manageRequest, manageResponse, networkFilter, FPSTR(exampleWiFiPassword), espnowEncryptionKey, espnowHashKey, FPSTR(exampleMeshName), 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.
|
||||
@returns 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) {
|
||||
// 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.
|
||||
|
||||
// To get the actual class of the polymorphic meshInstance, do as follows (meshBackendCast replaces dynamic_cast since RTTI is disabled)
|
||||
if (EspnowMeshBackend *espnowInstance = meshBackendCast<EspnowMeshBackend *>(&meshInstance)) {
|
||||
String messageEncrypted = espnowInstance->receivedEncryptedMessage() ? ", Encrypted" : ", Unencrypted";
|
||||
Serial.print("ESP-NOW (" + espnowInstance->getSenderMac() + messageEncrypted + "): ");
|
||||
} else if (TcpIpMeshBackend *tcpIpInstance = meshBackendCast<TcpIpMeshBackend *>(&meshInstance)) {
|
||||
(void)tcpIpInstance; // This is useful to remove a "unused parameter" compiler warning. Does nothing else.
|
||||
Serial.print("TCP/IP: ");
|
||||
} else {
|
||||
Serial.print("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.
|
||||
Serial.print("Request received: ");
|
||||
Serial.println(request.substring(0, 100));
|
||||
|
||||
/* return a string to send back */
|
||||
return ("Hello world response #" + String(responseNumber++) + " from " + meshInstance.getMeshName() + meshInstance.getNodeID() + " with AP MAC " + WiFi.softAPmacAddress() + ".");
|
||||
}
|
||||
|
||||
/**
|
||||
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.
|
||||
@returns The status code resulting from the response, as an int
|
||||
*/
|
||||
transmission_status_t manageResponse(const String &response, MeshBackendBase &meshInstance) {
|
||||
transmission_status_t statusCode = TS_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 = meshBackendCast<EspnowMeshBackend *>(&meshInstance)) {
|
||||
String messageEncrypted = espnowInstance->receivedEncryptedMessage() ? ", Encrypted" : ", Unencrypted";
|
||||
Serial.print("ESP-NOW (" + espnowInstance->getSenderMac() + messageEncrypted + "): ");
|
||||
} else if (TcpIpMeshBackend *tcpIpInstance = meshBackendCast<TcpIpMeshBackend *>(&meshInstance)) {
|
||||
Serial.print("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->getMessage().substring(0, 100));
|
||||
} else {
|
||||
Serial.print("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.
|
||||
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 = stringToUint64(currentSSID.substring(meshNameIndex + meshInstance.getMeshName().length()));
|
||||
|
||||
if (targetNodeID < stringToUint64(meshInstance.getNodeID())) {
|
||||
MeshBackendBase::connectionQueue.push_back(NetworkInfo(networkIndex));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void setup() {
|
||||
// Prevents the flash memory from being worn out, see: https://github.com/esp8266/Arduino/issues/1054 .
|
||||
// This will however delay node WiFi start-up by about 700 ms. The delay is 900 ms if we otherwise would have stored the WiFi network we want to connect to.
|
||||
WiFi.persistent(false);
|
||||
|
||||
Serial.begin(115200);
|
||||
delay(50); // Wait for Serial.
|
||||
|
||||
//yield(); // Use this if you don't want to wait for Serial, but not with the ESP-NOW backend (yield() causes crashes with ESP-NOW).
|
||||
|
||||
// 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 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 encryption 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.setEspnowEncryptionKey(espnowEncryptionKey);
|
||||
|
||||
// Makes it possible to find the node through scans, and also makes it possible to recover from an encrypted connection where only the other node is encrypted.
|
||||
// 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 espnowNode.attemptTransmission will replace the stored message with whatever message is transmitted.
|
||||
espnowNode.setMessage(String(F("Hello world request #")) + String(requestNumber) + String(F(" from ")) + espnowNode.getMeshName() + espnowNode.getNodeID() + String(F(".")));
|
||||
}
|
||||
|
||||
int32_t timeOfLastScan = -10000;
|
||||
void loop() {
|
||||
// The performEspnowMaintainance() 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 performEspnowMaintainance will likely cause the method to take longer to complete, so plan accordingly.
|
||||
|
||||
//Should not be used inside responseHandler, requestHandler or networkFilter callbacks since performEspnowMaintainance() can alter the ESP-NOW state.
|
||||
EspnowMeshBackend::performEspnowMaintainance();
|
||||
|
||||
if (millis() - timeOfLastScan > 10000) { // Give other nodes some time to connect between data transfers.
|
||||
uint32_t startTime = millis();
|
||||
|
||||
Serial.println("\nPerforming unencrypted ESP-NOW transmissions.");
|
||||
espnowNode.attemptTransmission(espnowNode.getMessage());
|
||||
Serial.println("Scan and " + String(MeshBackendBase::latestTransmissionOutcomes.size()) + " transmissions done in " + String(millis() - startTime) + " ms.");
|
||||
|
||||
// Wait for response. espnowDelay continuously calls performEspnowMaintainance() so we will respond to ESP-NOW request while waiting.
|
||||
// Should not be used inside responseHandler, requestHandler or networkFilter callbacks since performEspnowMaintainance() can alter the ESP-NOW state.
|
||||
espnowDelay(100);
|
||||
|
||||
timeOfLastScan = millis();
|
||||
|
||||
// One way to check how attemptTransmission worked out
|
||||
if (MeshBackendBase::latestTransmissionSuccessful()) {
|
||||
Serial.println(F("Transmission successful."));
|
||||
}
|
||||
|
||||
// Another way to check how attemptTransmission worked out
|
||||
if (MeshBackendBase::latestTransmissionOutcomes.empty()) {
|
||||
Serial.println(F("No mesh AP found."));
|
||||
} else {
|
||||
for (TransmissionResult &transmissionResult : MeshBackendBase::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);
|
||||
}
|
||||
}
|
||||
|
||||
Serial.println("\nPerforming encrypted ESP-NOW transmissions.");
|
||||
|
||||
// We can create encrypted connections to individual nodes so that all ESP-NOW communication with the node will be encrypted.
|
||||
if (espnowNode.requestEncryptedConnection(MeshBackendBase::connectionQueue[0].BSSID) == ECS_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 = macToString(MeshBackendBase::connectionQueue[0].BSSID);
|
||||
|
||||
Serial.println("Encrypted ESP-NOW connection with " + peerMac + " established!");
|
||||
|
||||
// Making a transmission now will cause messages to MeshBackendBase::connectionQueue[0].BSSID to be encrypted.
|
||||
String espnowMessage = "This message is encrypted only when received by node " + peerMac;
|
||||
Serial.println("\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(MeshBackendBase::connectionQueue[0].BSSID);
|
||||
|
||||
Serial.println();
|
||||
// We can remove an encrypted connection like so.
|
||||
espnowNode.removeEncryptedConnection(MeshBackendBase::connectionQueue[0].BSSID);
|
||||
|
||||
// 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 = "This message is no longer encrypted when received by node " + peerMac;
|
||||
Serial.println("\nTransmitting: " + espnowMessage);
|
||||
espnowNode.attemptTransmission(espnowMessage, false);
|
||||
espnowDelay(100); // Wait for response.
|
||||
Serial.println("Cannot read the encrypted response...");
|
||||
|
||||
// Let's re-add our stored connection so we can communicate properly with MeshBackendBase::connectionQueue[0].BSSID again!
|
||||
espnowNode.addEncryptedConnection(serializedEncryptedConnection);
|
||||
|
||||
espnowMessage = "This message is once again encrypted when received by node " + peerMac;
|
||||
Serial.println("\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.
|
||||
encrypted_connection_removal_outcome_t removalOutcome = espnowNode.requestEncryptedConnectionRemoval(MeshBackendBase::connectionQueue[0].BSSID);
|
||||
if (removalOutcome == ECRO_REMOVAL_SUCCEEDED) {
|
||||
Serial.println(peerMac + " is no longer encrypted!\n");
|
||||
|
||||
// Of course, we can also just create a temporary encrypted connection that will remove itself once its duration has passed.
|
||||
if (espnowNode.requestTemporaryEncryptedConnection(MeshBackendBase::connectionQueue[0].BSSID, 1000) == ECS_CONNECTION_ESTABLISHED) {
|
||||
espnowDelay(42);
|
||||
uint32_t remainingDuration = 0;
|
||||
EspnowMeshBackend::getConnectionInfo(MeshBackendBase::connectionQueue[0].BSSID, &remainingDuration);
|
||||
|
||||
espnowMessage = "Messages this node sends to " + peerMac + " will be encrypted for " + String(remainingDuration) + " ms more.";
|
||||
Serial.println("\nTransmitting: " + espnowMessage);
|
||||
espnowNode.attemptTransmission(espnowMessage, false);
|
||||
|
||||
EspnowMeshBackend::getConnectionInfo(MeshBackendBase::connectionQueue[0].BSSID, &remainingDuration);
|
||||
espnowDelay(remainingDuration + 100);
|
||||
|
||||
espnowMessage = "Due to encrypted connection expiration, this message is no longer encrypted when received by node " + peerMac;
|
||||
Serial.println("\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.
|
||||
espnowMessage = "This message is always encrypted, regardless of receiver.";
|
||||
Serial.println("\nTransmitting: " + espnowMessage);
|
||||
espnowNode.attemptAutoEncryptingTransmission(espnowMessage);
|
||||
espnowDelay(100); // Wait for response.
|
||||
} else {
|
||||
Serial.println("Ooops! Encrypted connection removal failed. Status: " + String(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("\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) + String(F(" from "))
|
||||
+ espnowNode.getMeshName() + espnowNode.getNodeID() + String(F(".")));
|
||||
}
|
||||
|
||||
Serial.println();
|
||||
}
|
||||
}
|
@ -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.
|
||||
|
1002
libraries/ESP8266WiFiMesh/src/Crypto.cpp
Normal file
1002
libraries/ESP8266WiFiMesh/src/Crypto.cpp
Normal file
File diff suppressed because it is too large
Load Diff
254
libraries/ESP8266WiFiMesh/src/Crypto.h
Normal file
254
libraries/ESP8266WiFiMesh/src/Crypto.h
Normal file
@ -0,0 +1,254 @@
|
||||
/**
|
||||
* An extremely minimal crypto library for Arduino devices.
|
||||
*
|
||||
* The SHA256 and AES implementations are derived from axTLS
|
||||
* (http://axtls.sourceforge.net/), Copyright (c) 2008, Cameron Rich.
|
||||
*
|
||||
* Ported and refactored by Chris Ellis 2016.
|
||||
* pkcs7 padding routines added by Mike Killewald Nov 26, 2017 (adopted from https://github.com/spaniakos/AES).
|
||||
*
|
||||
License
|
||||
=======
|
||||
Balsa SCGI
|
||||
Copyright (c) 2012, Chris Ellis
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef CRYPTO_h
|
||||
#define CRYPTO_h
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#if defined ESP8266
|
||||
#include <osapi.h>
|
||||
#endif
|
||||
|
||||
#define SHA256_SIZE 32
|
||||
#define SHA256HMAC_SIZE 32
|
||||
#define SHA256HMAC_BLOCKSIZE 64
|
||||
#define AES_MAXROUNDS 14
|
||||
#define AES_BLOCKSIZE 16
|
||||
#define AES_IV_SIZE 16
|
||||
#define AES_IV_LENGTH 16
|
||||
#define AES_128_KEY_LENGTH 16
|
||||
#define AES_256_KEY_LENGTH 16
|
||||
|
||||
/**
|
||||
* Compute a SHA256 hash
|
||||
*/
|
||||
class SHA256
|
||||
{
|
||||
public:
|
||||
SHA256();
|
||||
/**
|
||||
* Update the hash with new data
|
||||
*/
|
||||
void doUpdate(const byte *msg, uint32_t len);
|
||||
void doUpdate(const char *msg, unsigned int len) { doUpdate((byte*) msg, len); }
|
||||
void doUpdate(const char *msg) { doUpdate((byte*) msg, strlen(msg)); }
|
||||
/**
|
||||
* Compute the final hash and store it in [digest], digest must be
|
||||
* at least 32 bytes
|
||||
*/
|
||||
void doFinal(byte *digest);
|
||||
/**
|
||||
* Compute the final hash and check it matches this given expected hash
|
||||
*/
|
||||
bool matches(const byte *expected);
|
||||
private:
|
||||
void SHA256_Process(const byte digest[64]);
|
||||
uint32_t total[2];
|
||||
uint32_t state[8];
|
||||
uint8_t buffer[64];
|
||||
};
|
||||
|
||||
#define HMAC_OPAD 0x5C
|
||||
#define HMAC_IPAD 0x36
|
||||
|
||||
/**
|
||||
* Compute a HMAC using SHA256
|
||||
*/
|
||||
class SHA256HMAC
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Compute a SHA256 HMAC with the given [key] key of [length] bytes
|
||||
* for authenticity
|
||||
*/
|
||||
SHA256HMAC(const byte *key, unsigned int keyLen);
|
||||
/**
|
||||
* Update the hash with new data
|
||||
*/
|
||||
void doUpdate(const byte *msg, unsigned int len);
|
||||
void doUpdate(const char *msg, unsigned int len) { doUpdate((byte*) msg, len); }
|
||||
void doUpdate(const char *msg) { doUpdate((byte*) msg, strlen(msg)); }
|
||||
/**
|
||||
* Compute the final hash and store it in [digest], digest must be
|
||||
* at least 32 bytes
|
||||
*/
|
||||
void doFinal(byte *digest);
|
||||
/**
|
||||
* Compute the final hash and check it matches this given expected hash
|
||||
*/
|
||||
bool matches(const byte *expected);
|
||||
private:
|
||||
void blockXor(const byte *in, byte *out, byte val, byte len);
|
||||
SHA256 _hash;
|
||||
byte _innerKey[SHA256HMAC_BLOCKSIZE];
|
||||
byte _outerKey[SHA256HMAC_BLOCKSIZE];
|
||||
};
|
||||
|
||||
/**
|
||||
* AES 128 and 256, based on code from axTLS
|
||||
*/
|
||||
class AES
|
||||
{
|
||||
public:
|
||||
typedef enum
|
||||
{
|
||||
AES_MODE_128,
|
||||
AES_MODE_256
|
||||
} AES_MODE;
|
||||
typedef enum
|
||||
{
|
||||
CIPHER_ENCRYPT = 0x01,
|
||||
CIPHER_DECRYPT = 0x02
|
||||
} CIPHER_MODE;
|
||||
|
||||
/**
|
||||
* Create this cipher instance in either encrypt or decrypt mode
|
||||
*
|
||||
* Use the given [key] which must be 16 bytes long for AES 128 and
|
||||
* 32 bytes for AES 256
|
||||
*
|
||||
* Use the given [iv] initialistion vection which must be 16 bytes long
|
||||
*
|
||||
* Use the either AES 128 or AES 256 as specified by [mode]
|
||||
*
|
||||
* Either encrypt or decrypt as specified by [cipherMode]
|
||||
*/
|
||||
AES(const uint8_t *key, const uint8_t *iv, AES_MODE mode, CIPHER_MODE cipherMode);
|
||||
|
||||
/**
|
||||
* Either encrypt or decrypt [in] and store into [out] for [length] bytes, applying no padding
|
||||
*
|
||||
* Note: the length must be a multiple of 16 bytes
|
||||
*/
|
||||
void processNoPad(const uint8_t *in, uint8_t *out, int length);
|
||||
|
||||
/**
|
||||
* Either encrypt or decrypt [in] and store into [out] for [length] bytes, applying padding as needed
|
||||
*
|
||||
* Note: the length must be a multiple of 16 bytes
|
||||
*/
|
||||
void process(const uint8_t *in, uint8_t *out, int length);
|
||||
|
||||
/** Getter method for size
|
||||
*
|
||||
* This function returns the size
|
||||
* @return an integer, that is the size of the of the padded plaintext,
|
||||
* thus, the size of the ciphertext.
|
||||
*/
|
||||
int getSize();
|
||||
|
||||
/** Setter method for size
|
||||
*
|
||||
* This function sets the size of the plaintext+pad
|
||||
*
|
||||
*/
|
||||
void setSize(int size);
|
||||
|
||||
/** Calculates the size of the plaintext and the padding.
|
||||
*
|
||||
* Calculates the size of the plaintext with the size of the
|
||||
* padding needed. Moreover it stores them in their class variables.
|
||||
*
|
||||
* @param in_size the size of the byte array ex sizeof(plaintext)
|
||||
* @return an int the size of the plaintext plus the padding
|
||||
*/
|
||||
int calcSizeAndPad(int in_size);
|
||||
|
||||
/** Pads the plaintext
|
||||
*
|
||||
* This function pads the plaintext and returns an char array with the
|
||||
* plaintext and the padding in order for the plaintext to be compatible with
|
||||
* 16bit size blocks required by AES
|
||||
*
|
||||
* @param in the string of the plaintext in a byte array
|
||||
* @param out The string of the out array.
|
||||
* @return no return, The padded plaintext is stored in the out pointer.
|
||||
*/
|
||||
void padPlaintext(const uint8_t* in, uint8_t* out);
|
||||
|
||||
/** Check the if the padding is correct.
|
||||
*
|
||||
* This functions checks the padding of the plaintext.
|
||||
*
|
||||
* @param in the string of the plaintext in a byte array
|
||||
* @param size the size of the string
|
||||
* @return true if correct / false if not
|
||||
*/
|
||||
bool checkPad(uint8_t* in, int lsize);
|
||||
|
||||
private:
|
||||
void encryptCBC(const uint8_t *in, uint8_t *out, int length);
|
||||
void decryptCBC(const uint8_t *in, uint8_t *out, int length);
|
||||
void convertKey();
|
||||
void encrypt(uint32_t *data);
|
||||
void decrypt(uint32_t *data);
|
||||
uint16_t _rounds;
|
||||
uint16_t _key_size;
|
||||
uint32_t _ks[(AES_MAXROUNDS+1)*8];
|
||||
uint8_t _iv[AES_IV_SIZE];
|
||||
int _pad_size; // size of padding to add to plaintext
|
||||
int _size; // size of plaintext plus padding to be ciphered
|
||||
uint8_t _arr_pad[15];
|
||||
|
||||
CIPHER_MODE _cipherMode;
|
||||
};
|
||||
|
||||
#if defined ESP8266 || defined ESP32
|
||||
/**
|
||||
* ESP8266 and ESP32 specific true random number generator
|
||||
*/
|
||||
class RNG
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Fill the [dst] array with [length] random bytes
|
||||
*/
|
||||
static void fill(uint8_t *dst, unsigned int length);
|
||||
/**
|
||||
* Get a random byte
|
||||
*/
|
||||
static byte get();
|
||||
/**
|
||||
* Get a 32bit random number
|
||||
*/
|
||||
static uint32_t getLong();
|
||||
private:
|
||||
};
|
||||
#endif
|
||||
|
||||
|
||||
#endif
|
@ -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 <ESP8266WiFi.h>
|
||||
#include <WiFiClient.h>
|
||||
#include <WiFiServer.h>
|
||||
@ -29,7 +51,6 @@
|
||||
#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,10 +72,8 @@ 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();
|
||||
|
||||
: _server(serverPort)
|
||||
{
|
||||
updateNetworkNames(meshName, (nodeID != "" ? nodeID : uint64ToString(ESP.getChipId())));
|
||||
_requestHandler = requestHandler;
|
||||
_responseHandler = responseHandler;
|
||||
@ -99,15 +118,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
|
||||
}
|
||||
}
|
||||
@ -455,25 +469,18 @@ transmission_status_t ESP8266WiFiMesh::connectToNode(const String &targetSSID, i
|
||||
{
|
||||
if(staticIPActivated && lastSSID != "" && lastSSID != targetSSID) // So we only do this once per connection, in case there is a performance impact.
|
||||
{
|
||||
#ifdef ENABLE_STATIC_IP_OPTIMIZATION
|
||||
if(atLeastLwipVersion(lwipVersion203Signature))
|
||||
{
|
||||
// Can be used with Arduino core for ESP8266 version 2.4.2 or higher with lwIP2 enabled to keep static IP on even during network switches.
|
||||
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 +544,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 +556,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,18 +653,18 @@ 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 */
|
||||
if (_client.connected())
|
||||
{
|
||||
verboseModePrint("Responding"); // Not storing strings in flash (via F()) to avoid performance impacts when using the string.
|
||||
verboseModePrint("Responding"); // Not storing strings in flash (via F()) to avoid performance impacts when using the string.
|
||||
_client.print(response + "\r");
|
||||
_client.flush();
|
||||
yield();
|
||||
@ -669,3 +672,15 @@ void ESP8266WiFiMesh::acceptRequest()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void ESP8266WiFiMesh::verboseModePrint(const String &stringToPrint, bool newline)
|
||||
{
|
||||
if(_verboseMode)
|
||||
{
|
||||
if(newline)
|
||||
Serial.println(stringToPrint);
|
||||
else
|
||||
Serial.print(stringToPrint);
|
||||
}
|
||||
}
|
||||
|
@ -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__
|
||||
|
||||
@ -28,9 +50,6 @@
|
||||
#include "NetworkInfo.h"
|
||||
#include "TransmissionResult.h"
|
||||
|
||||
#define ENABLE_STATIC_IP_OPTIMIZATION // Requires Arduino core for ESP8266 version 2.4.2 or higher and lwIP2 (lwIP can be changed in "Tools" menu of Arduino IDE).
|
||||
#define ENABLE_WIFI_SCAN_OPTIMIZATION // Requires Arduino core for ESP8266 version 2.4.2 or higher. Scan time should go from about 2100 ms to around 60 ms if channel 1 (standard) is used.
|
||||
|
||||
const String WIFI_MESH_EMPTY_STRING = "";
|
||||
|
||||
class ESP8266WiFiMesh {
|
||||
@ -44,8 +63,6 @@ private:
|
||||
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 +73,7 @@ private:
|
||||
|
||||
static String lastSSID;
|
||||
static bool staticIPActivated;
|
||||
bool useStaticIP;
|
||||
static IPAddress staticIP;
|
||||
static IPAddress gateway;
|
||||
static IPAddress subnetMask;
|
||||
@ -78,8 +96,6 @@ private:
|
||||
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 +149,7 @@ public:
|
||||
////////////////////////////</DEPRECATED> TODO: REMOVE IN 2.5.0////////////////////////////
|
||||
|
||||
~ESP8266WiFiMesh();
|
||||
|
||||
|
||||
/**
|
||||
* WiFiMesh Constructor method. Creates a WiFi Mesh Node, ready to be initialised.
|
||||
*
|
||||
|
181
libraries/ESP8266WiFiMesh/src/EncryptedConnectionData.cpp
Normal file
181
libraries/ESP8266WiFiMesh/src/EncryptedConnectionData.cpp
Normal file
@ -0,0 +1,181 @@
|
||||
/*
|
||||
* 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"
|
||||
|
||||
using EspnowProtocolInterpreter::espnowHashKeyLength;
|
||||
|
||||
EncryptedConnectionData::EncryptedConnectionData(const uint8_t peerStaMac[6], const uint8_t peerApMac[6], uint64_t peerSessionKey, uint64_t ownSessionKey, const uint8_t hashKey[espnowHashKeyLength])
|
||||
: _peerSessionKey(peerSessionKey), _ownSessionKey(ownSessionKey)
|
||||
{
|
||||
std::copy_n(peerStaMac, 6, _peerStaMac);
|
||||
std::copy_n(peerApMac, 6, _peerApMac);
|
||||
std::copy_n(hashKey, espnowHashKeyLength, _hashKey);
|
||||
}
|
||||
|
||||
EncryptedConnectionData::EncryptedConnectionData(const uint8_t peerStaMac[6], const uint8_t peerApMac[6], uint64_t peerSessionKey, uint64_t ownSessionKey, uint32_t duration, const uint8_t hashKey[espnowHashKeyLength])
|
||||
: EncryptedConnectionData(peerStaMac, peerApMac, peerSessionKey, ownSessionKey, hashKey)
|
||||
{
|
||||
setRemainingDuration(duration);
|
||||
}
|
||||
|
||||
EncryptedConnectionData::EncryptedConnectionData(const EncryptedConnectionData &other)
|
||||
: _peerSessionKey(other.getPeerSessionKey()), _ownSessionKey(other.getOwnSessionKey()), _desync(other.desync()),
|
||||
_timeTracker(other.temporary() ? new ExpiringTimeTracker(*other.temporary()) : nullptr)
|
||||
{
|
||||
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<ExpiringTimeTracker>(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;
|
||||
}
|
||||
|
||||
bool EncryptedConnectionData::connectedTo(const uint8_t *peerMac) const
|
||||
{
|
||||
if(macEqual(peerMac, _peerStaMac) || macEqual(peerMac, _peerApMac))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void EncryptedConnectionData::setHashKey(const uint8_t hashKey[espnowHashKeyLength])
|
||||
{
|
||||
assert(hashKey != nullptr);
|
||||
|
||||
std::copy_n(hashKey, espnowHashKeyLength, _hashKey);
|
||||
}
|
||||
|
||||
uint8_t *EncryptedConnectionData::getHashKey(uint8_t *resultArray) const
|
||||
{
|
||||
std::copy_n(_hashKey, espnowHashKeyLength, resultArray);
|
||||
return resultArray;
|
||||
}
|
||||
|
||||
void EncryptedConnectionData::setPeerSessionKey(uint64_t sessionKey) { _peerSessionKey = sessionKey; }
|
||||
uint64_t EncryptedConnectionData::getPeerSessionKey() const { return _peerSessionKey; }
|
||||
|
||||
void EncryptedConnectionData::setOwnSessionKey(uint64_t sessionKey) { _ownSessionKey = sessionKey; }
|
||||
uint64_t EncryptedConnectionData::getOwnSessionKey() const { return _ownSessionKey; }
|
||||
|
||||
uint64_t EncryptedConnectionData::incrementSessionKey(uint64_t sessionKey, const uint8_t *hashKey, uint8_t hashKeyLength)
|
||||
{
|
||||
String hmac = JsonTranslator::createHmac(uint64ToString(sessionKey), hashKey, hashKeyLength);
|
||||
|
||||
/* 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 = strtoul(hmac.substring(0, 8).c_str(), nullptr, HEX); // strtoul stops reading input when an invalid character is discovered.
|
||||
if(newLeftmostBits == 0)
|
||||
newLeftmostBits = RANDOM_REG32 | (1 << 31); // We never want newLeftmostBits == 0 since that would indicate an unencrypted transmission.
|
||||
|
||||
uint64_t newRightmostBits = (uint32_t)(sessionKey + 1);
|
||||
|
||||
return (newLeftmostBits << 32) | newRightmostBits;
|
||||
}
|
||||
|
||||
void EncryptedConnectionData::incrementOwnSessionKey()
|
||||
{
|
||||
setOwnSessionKey(incrementSessionKey(getOwnSessionKey(), _hashKey, EspnowProtocolInterpreter::espnowHashKeyLength));
|
||||
}
|
||||
|
||||
void EncryptedConnectionData::setDesync(bool desync) { _desync = desync; }
|
||||
bool EncryptedConnectionData::desync() const { return _desync; }
|
||||
|
||||
String EncryptedConnectionData::serialize() const
|
||||
{
|
||||
// Returns: {"connectionState":{"duration":"123","password":"abc","ownSessionKey":"1A2","peerSessionKey":"3B4","peerStaMac":"F2","peerApMac":"E3"}}
|
||||
|
||||
return
|
||||
"{\"connectionState\":{"
|
||||
+ (temporary() ? JsonTranslator::jsonDuration + "\"" + String(temporary()->remainingDuration()) + "\"," : "")
|
||||
+ JsonTranslator::jsonDesync + "\"" + String(desync()) + "\","
|
||||
+ JsonTranslator::jsonOwnSessionKey + "\"" + uint64ToString(getOwnSessionKey()) + "\","
|
||||
+ JsonTranslator::jsonPeerSessionKey + "\"" + uint64ToString(getPeerSessionKey()) + "\","
|
||||
+ JsonTranslator::jsonPeerStaMac + "\"" + macToString(_peerStaMac) + "\","
|
||||
+ JsonTranslator::jsonPeerApMac + "\"" + macToString(_peerApMac) + "\"}}";
|
||||
}
|
||||
|
||||
const ExpiringTimeTracker *EncryptedConnectionData::temporary() const
|
||||
{
|
||||
return _timeTracker.get();
|
||||
}
|
||||
|
||||
void EncryptedConnectionData::setRemainingDuration(uint32_t remainingDuration)
|
||||
{
|
||||
if(!_timeTracker)
|
||||
{
|
||||
_timeTracker = std::unique_ptr<ExpiringTimeTracker>(new ExpiringTimeTracker(remainingDuration)); // TODO: Change to std::make_unique<ExpiringTimeTracker>(remainingDuration); once compiler fully supports C++14
|
||||
}
|
||||
else
|
||||
{
|
||||
_timeTracker->setRemainingDuration(remainingDuration);
|
||||
}
|
||||
}
|
||||
|
||||
void EncryptedConnectionData::removeDuration()
|
||||
{
|
||||
_timeTracker = nullptr;
|
||||
}
|
96
libraries/ESP8266WiFiMesh/src/EncryptedConnectionData.h
Normal file
96
libraries/ESP8266WiFiMesh/src/EncryptedConnectionData.h
Normal file
@ -0,0 +1,96 @@
|
||||
/*
|
||||
* 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 <WString.h>
|
||||
#include <memory>
|
||||
|
||||
class EncryptedConnectionData {
|
||||
|
||||
public:
|
||||
|
||||
virtual ~EncryptedConnectionData() = default;
|
||||
|
||||
EncryptedConnectionData(const uint8_t peerStaMac[6], const uint8_t peerApMac[6], uint64_t peerSessionKey, uint64_t ownSessionKey,
|
||||
const uint8_t hashKey[EspnowProtocolInterpreter::espnowHashKeyLength]);
|
||||
EncryptedConnectionData(const uint8_t peerStaMac[6], const uint8_t peerApMac[6], uint64_t peerSessionKey, uint64_t ownSessionKey,
|
||||
uint32_t duration, const uint8_t hashKey[EspnowProtocolInterpreter::espnowHashKeyLength]);
|
||||
|
||||
EncryptedConnectionData(const EncryptedConnectionData &other);
|
||||
|
||||
EncryptedConnectionData & operator=(const EncryptedConnectionData &other);
|
||||
|
||||
/**
|
||||
* @param resultArray An uint8_t array with at least size 6.
|
||||
*
|
||||
* @returns 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;
|
||||
uint8_t *getPeerApMac(uint8_t *resultArray) const;
|
||||
|
||||
bool connectedTo(const uint8_t *peerMac) const;
|
||||
|
||||
void setHashKey(const uint8_t hashKey[EspnowProtocolInterpreter::espnowHashKeyLength]);
|
||||
// @param resultArray At least size espnowHashKeyLength.
|
||||
uint8_t *getHashKey(uint8_t *resultArray) const;
|
||||
|
||||
void setPeerSessionKey(uint64_t sessionKey);
|
||||
uint64_t getPeerSessionKey() const;
|
||||
void setOwnSessionKey(uint64_t sessionKey);
|
||||
uint64_t getOwnSessionKey() const;
|
||||
|
||||
static uint64_t incrementSessionKey(uint64_t sessionKey, const uint8_t *hashKey, uint8_t hashKeyLength);
|
||||
void incrementOwnSessionKey();
|
||||
|
||||
void setDesync(bool desync);
|
||||
bool desync() const;
|
||||
|
||||
// Note that the espnowEncryptionKey, espnowEncryptionKok and espnowHashKey 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(uint32_t remainingDuration);
|
||||
virtual void removeDuration();
|
||||
|
||||
private:
|
||||
|
||||
uint8_t _peerStaMac[6] {0};
|
||||
uint8_t _peerApMac[6] {0};
|
||||
uint64_t _peerSessionKey;
|
||||
uint64_t _ownSessionKey;
|
||||
uint8_t _hashKey[EspnowProtocolInterpreter::espnowHashKeyLength] {0};
|
||||
bool _desync = false;
|
||||
std::unique_ptr<ExpiringTimeTracker> _timeTracker = nullptr;
|
||||
};
|
||||
|
||||
#endif
|
92
libraries/ESP8266WiFiMesh/src/EncryptedConnectionLog.cpp
Normal file
92
libraries/ESP8266WiFiMesh/src/EncryptedConnectionLog.cpp
Normal file
@ -0,0 +1,92 @@
|
||||
/*
|
||||
* 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"
|
||||
|
||||
using EspnowProtocolInterpreter::espnowHashKeyLength;
|
||||
|
||||
EncryptedConnectionLog::EncryptedConnectionLog(const uint8_t peerStaMac[6], const uint8_t peerApMac[6], uint64_t peerSessionKey, uint64_t ownSessionKey, const uint8_t hashKey[espnowHashKeyLength])
|
||||
: EncryptedConnectionData(peerStaMac, peerApMac, peerSessionKey, ownSessionKey, hashKey)
|
||||
{ }
|
||||
|
||||
EncryptedConnectionLog::EncryptedConnectionLog(const uint8_t peerStaMac[6], const uint8_t peerApMac[6], uint64_t peerSessionKey, uint64_t ownSessionKey, uint32_t duration, const uint8_t hashKey[espnowHashKeyLength])
|
||||
: EncryptedConnectionData(peerStaMac, peerApMac, peerSessionKey, ownSessionKey, duration, hashKey)
|
||||
{ }
|
||||
|
||||
std::unique_ptr<ExpiringTimeTracker> EncryptedConnectionLog::_soonestExpiringConnectionTracker = nullptr;
|
||||
|
||||
bool EncryptedConnectionLog::_newRemovalsScheduled = false;
|
||||
|
||||
void EncryptedConnectionLog::setRemainingDuration(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 performEspnowMaintainance() 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(bool scheduledForRemoval)
|
||||
{
|
||||
_scheduledForRemoval = scheduledForRemoval;
|
||||
|
||||
if(scheduledForRemoval)
|
||||
setNewRemovalsScheduled(true);
|
||||
}
|
||||
bool EncryptedConnectionLog::removalScheduled() const { return _scheduledForRemoval; }
|
||||
|
||||
void EncryptedConnectionLog::setNewRemovalsScheduled(bool newRemovalsScheduled) { _newRemovalsScheduled = newRemovalsScheduled; }
|
||||
bool EncryptedConnectionLog::newRemovalsScheduled( ){ return _newRemovalsScheduled; }
|
||||
|
||||
const ExpiringTimeTracker *EncryptedConnectionLog::getSoonestExpiringConnectionTracker()
|
||||
{
|
||||
return _soonestExpiringConnectionTracker.get();
|
||||
}
|
||||
|
||||
void EncryptedConnectionLog::updateSoonestExpiringConnectionTracker(uint32_t remainingDuration)
|
||||
{
|
||||
if(!getSoonestExpiringConnectionTracker() || remainingDuration < getSoonestExpiringConnectionTracker()->remainingDuration())
|
||||
{
|
||||
_soonestExpiringConnectionTracker = std::unique_ptr<ExpiringTimeTracker>(new ExpiringTimeTracker(remainingDuration)); // TODO: Change to std::make_unique<ExpiringTimeTracker>(remainingDuration); once compiler fully supports C++14
|
||||
}
|
||||
}
|
||||
|
||||
void EncryptedConnectionLog::clearSoonestExpiringConnectionTracker()
|
||||
{
|
||||
_soonestExpiringConnectionTracker = nullptr;
|
||||
}
|
68
libraries/ESP8266WiFiMesh/src/EncryptedConnectionLog.h
Normal file
68
libraries/ESP8266WiFiMesh/src/EncryptedConnectionLog.h
Normal file
@ -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], uint64_t peerSessionKey, uint64_t ownSessionKey,
|
||||
const uint8_t hashKey[EspnowProtocolInterpreter::espnowHashKeyLength]);
|
||||
EncryptedConnectionLog(const uint8_t peerStaMac[6], const uint8_t peerApMac[6], uint64_t peerSessionKey, uint64_t ownSessionKey,
|
||||
uint32_t duration, const uint8_t hashKey[EspnowProtocolInterpreter::espnowHashKeyLength]);
|
||||
|
||||
// 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<ExpiringTimeTracker> _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(uint32_t remainingDuration) override;
|
||||
void removeDuration() override;
|
||||
|
||||
void scheduleForRemoval();
|
||||
bool removalScheduled() const;
|
||||
|
||||
static void setNewRemovalsScheduled(bool newRemovalsScheduled);
|
||||
static bool newRemovalsScheduled();
|
||||
|
||||
static const ExpiringTimeTracker *getSoonestExpiringConnectionTracker();
|
||||
static void updateSoonestExpiringConnectionTracker(uint32_t remainingDuration);
|
||||
static void clearSoonestExpiringConnectionTracker();
|
||||
|
||||
private:
|
||||
|
||||
bool _scheduledForRemoval = false;
|
||||
void setScheduledForRemoval(bool scheduledForRemoval);
|
||||
};
|
||||
|
||||
#endif
|
2295
libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.cpp
Normal file
2295
libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.cpp
Normal file
File diff suppressed because it is too large
Load Diff
764
libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.h
Normal file
764
libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.h
Normal file
@ -0,0 +1,764 @@
|
||||
/*
|
||||
EspnowMeshBackend
|
||||
|
||||
Copyright (C) 2019 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
|
||||
*/
|
||||
|
||||
// ESP-NOW is faster for small data payloads (up to a few kB, split over multiple messages). Transfer of up to 234 bytes takes 4 ms.
|
||||
// In general ESP-NOW transfer time can be approximated with the following function: transferTime = ceil(bytesToTransfer / 234.0)*3 ms.
|
||||
// If you only transfer 234 bytes at a time, this adds up to around 56kB/s. Finally a chance to relive the glory of the olden days
|
||||
// when people were restricted to V90 dial-up modems for internet access!
|
||||
// TCP-IP takes longer to connect (around 1000 ms), and an AP has to disconnect all connected stations in order to transfer data to another AP,
|
||||
// but this backend has a much higher data transfer speed than ESP-NOW once connected (100x faster or so).
|
||||
|
||||
/**
|
||||
* Encryption pairing process, schematic overview:
|
||||
*
|
||||
* Connection | Peer sends: | Peer requester sends: | Connection
|
||||
* encrypted: | | | encrypted:
|
||||
* | | Peer request + Nonce |
|
||||
* | StaMac + Nonce + HMAC | |
|
||||
* | | Ack |
|
||||
* X | SessionKeys + Nonce + Password | | X
|
||||
* X | | Ack | X
|
||||
* X | | SessionKey | X
|
||||
* X | Ack | | X
|
||||
* | | |
|
||||
*
|
||||
*
|
||||
* The ESP-NOW CCMP encryption should have replay attack protection built in,
|
||||
* but since there is no official documentation from Espressif about this a 128 bit random nonce is included in encrypted connection requests.
|
||||
*/
|
||||
|
||||
#ifndef __ESPNOWMESHBACKEND_H__
|
||||
#define __ESPNOWMESHBACKEND_H__
|
||||
|
||||
#include "MeshBackendBase.h"
|
||||
#include "EspnowProtocolInterpreter.h"
|
||||
#include "EncryptedConnectionLog.h"
|
||||
#include "PeerRequestLog.h"
|
||||
#include "RequestData.h"
|
||||
#include "ResponseData.h"
|
||||
#include "MessageData.h"
|
||||
#include <map>
|
||||
#include <list>
|
||||
#include "Crypto.h"
|
||||
|
||||
typedef enum
|
||||
{
|
||||
ECT_NO_CONNECTION = 0,
|
||||
ECT_TEMPORARY_CONNECTION = 1,
|
||||
ECT_PERMANENT_CONNECTION = 2
|
||||
} espnow_connection_type_t;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
ECS_MAX_CONNECTIONS_REACHED_SELF = -3,
|
||||
ECS_REQUEST_TRANSMISSION_FAILED = -2,
|
||||
ECS_MAX_CONNECTIONS_REACHED_PEER = -1,
|
||||
ECS_API_CALL_FAILED = 0,
|
||||
ECS_CONNECTION_ESTABLISHED = 1
|
||||
} encrypted_connection_status_t;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
ECRO_REMOVAL_REQUEST_FAILED = -1,
|
||||
ECRO_REMOVAL_FAILED = 0,
|
||||
ECRO_REMOVAL_SUCCEEDED = 1,
|
||||
ECRO_REMOVAL_SCHEDULED = 2
|
||||
} encrypted_connection_removal_outcome_t;
|
||||
|
||||
|
||||
/**
|
||||
* An alternative to standard delay(). Will continuously call performEspnowMaintainance() during the waiting time, so that the ESP-NOW node remains responsive.
|
||||
* Note that if there is a lot of ESP-NOW transmission activity to the node during the espnowDelay, the desired duration may be overshot by several ms.
|
||||
* Thus, if precise timing is required, use standard delay() instead.
|
||||
*
|
||||
* Should not be used inside responseHandler, requestHandler or networkFilter callbacks since performEspnowMaintainance() can alter the ESP-NOW state.
|
||||
*
|
||||
* @param durationMs The shortest allowed delay duration, in milliseconds.
|
||||
*/
|
||||
void espnowDelay(uint32_t durationMs);
|
||||
|
||||
class RequestData;
|
||||
|
||||
class EspnowMeshBackend : public MeshBackendBase {
|
||||
|
||||
public:
|
||||
|
||||
/**
|
||||
* 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 ssidPrefix The prefix (first part) of the node SSID.
|
||||
* @param ssidSuffix The suffix (last part) of the node SSID.
|
||||
* @param verboseMode Determines if we should print the events occurring in the library to Serial. Off by default. This setting is shared by all EspnowMeshBackend instances.
|
||||
* @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 mesh instances exist on the same ESP8266 and use different WiFi channels.
|
||||
* In such a case, whenever the station of one mesh 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.
|
||||
*
|
||||
*/
|
||||
EspnowMeshBackend(requestHandlerType requestHandler, responseHandlerType responseHandler, networkFilterType networkFilter,
|
||||
const String &meshPassword, const uint8_t espnowEncryptionKey[EspnowProtocolInterpreter::espnowEncryptionKeyLength],
|
||||
const uint8_t espnowHashKey[EspnowProtocolInterpreter::espnowHashKeyLength], const String &ssidPrefix,
|
||||
const String &ssidSuffix, bool verboseMode = false, uint8 meshWiFiChannel = 1);
|
||||
|
||||
~EspnowMeshBackend() override;
|
||||
|
||||
/**
|
||||
* Initialises the node.
|
||||
*/
|
||||
void begin() override;
|
||||
|
||||
/**
|
||||
* This 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 performEspnowMaintainance will likely cause the method to take longer to complete, so plan accordingly.
|
||||
*
|
||||
* Should not be used inside responseHandler, requestHandler or networkFilter callbacks since performEspnowMaintainance() can alter the ESP-NOW state.
|
||||
*/
|
||||
static void performEspnowMaintainance();
|
||||
|
||||
/**
|
||||
* At critical heap level no more incoming requests are accepted.
|
||||
*/
|
||||
static uint32_t criticalHeapLevel();
|
||||
|
||||
/**
|
||||
* At critical heap level no more incoming requests are accepted.
|
||||
* This method sets the maximum number of bytes above the critical heap level that will trigger an early ESP-NOW log clearing in an attempt to increase available heap size.
|
||||
* A too high value may cause very frequent early log clearings, which will slow things down. Especially if you are using a lot of heap in other parts of your program.
|
||||
* A too low value may cause some incoming requests to be lost and/or an increase in heap fragmentation,
|
||||
* especially if you quickly fill the heap by receiving a lot of large ESP-NOW messages or sending a lot of large ESP-NOW responses.
|
||||
* The buffer is set to 6000 bytes by default, which should be enough to prevent lost incoming requests while giving plenty of heap to fill up before early clearing in most circumstances.
|
||||
*
|
||||
* The buffer can be set lower than the default if you are running low on heap, since it may otherwise be hard to get responses sent.
|
||||
* However, lower values tend to result in more heap fragmentation during intense transmission activity.
|
||||
* Depending on your situation (message size, transmission frequency), values below 2000-3000 bytes will also start to cause lost incoming requests due to heap shortage.
|
||||
*
|
||||
* If the buffer is set to 0 bytes a significant number of incoming requests are likely to be lost during intense transmission activity,
|
||||
* and there is a greater risk of heap space completely running out before log clearing occurs (which may cause crashes or empty transmissions).
|
||||
*/
|
||||
static void setCriticalHeapLevelBuffer(uint32_t bufferInBytes);
|
||||
static uint32_t criticalHeapLevelBuffer();
|
||||
|
||||
/**
|
||||
* Deactivates Espnow for this node. Call begin() on a EspnowMeshBackend instance to reactivate Espnow.
|
||||
*
|
||||
* @returns True if deactivation was successful. False otherwise.
|
||||
*/
|
||||
static bool deactivateEspnow();
|
||||
|
||||
void attemptTransmission(const String &message, bool scan = true, bool scanAllWiFiChannels = false) override;
|
||||
|
||||
// Will ensure that an encrypted connection exists to each target node before sending the message,
|
||||
// establishing a temporary encrypted connection with duration getAutoEncryptionDuration() first if neccessary.
|
||||
// If an encrypted connection cannot be established to a target node, no message will be sent to that node.
|
||||
// Note that if an encrypted connection to a target node is not present before this method is called, the response from said node will likely not be received
|
||||
// since it will be encrypted and the auto encrypted connection to the node is immediately removed after transmission.
|
||||
// Also note that if a temporary encrypted connection already exists to a target node, this method will slightly extend the connection duration
|
||||
// depending on the time it takes to verify the connection to the node. This can substantially increase the connection duration if many auto encrypting
|
||||
// transmissions occurs.
|
||||
void attemptAutoEncryptingTransmission(const String &message, bool scan = true, bool scanAllWiFiChannels = false);
|
||||
|
||||
/**
|
||||
* Set the EspnowMeshBackend instance responsible for handling incoming requests. The requestHandler of the instance will be called upon receiving ESP-NOW requests.
|
||||
*
|
||||
* Set to nullptr to stop processing the ESP-NOW requests received by this node (requests will be ignored, but still received (ack will be sent)).
|
||||
* The node can still send ESP-NOW transmissions to other nodes, even when the espnowRequestManager is nullptr.
|
||||
*/
|
||||
static void setEspnowRequestManager(EspnowMeshBackend *espnowMeshInstance);
|
||||
|
||||
static EspnowMeshBackend *getEspnowRequestManager();
|
||||
|
||||
/**
|
||||
* Check if this EspnowMeshBackend instance is the espnowRequestManager.
|
||||
*
|
||||
* @returns True if this EspnowMeshBackend is the espnowRequestManager. False otherwise.
|
||||
*/
|
||||
bool isEspnowRequestManager();
|
||||
|
||||
/**
|
||||
* Change the key used by this EspnowMeshBackend instance for creating encrypted ESP-NOW connections.
|
||||
* Will apply to any new received requests for encrypted connection if this EspnowMeshBackend instance is the current request manager.
|
||||
* Will apply to any new encrypted connections requested or added by this EspnowMeshBackend instance.
|
||||
*
|
||||
* NOTE: Encrypted connections added before the encryption key change will retain their old encryption key.
|
||||
* Only changes the encryption key used by this EspnowMeshBackend instance, so each instance can use a separate key.
|
||||
* Both Kok and encryption 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.
|
||||
*
|
||||
* @param espnowEncryptionKey An array containing the espnowEncryptionKeyLength bytes that will be used as the encryption key.
|
||||
*/
|
||||
void setEspnowEncryptionKey(const uint8_t espnowEncryptionKey[EspnowProtocolInterpreter::espnowEncryptionKeyLength]);
|
||||
|
||||
/**
|
||||
* Get the encryption key used by this EspnowMeshBackend instance for creating encrypted ESP-NOW connections.
|
||||
*
|
||||
* @return The current espnowEncryptionKey for this EspnowMeshBackend instance.
|
||||
*/
|
||||
const uint8_t *getEspnowEncryptionKey();
|
||||
uint8_t *getEspnowEncryptionKey(uint8_t resultArray[EspnowProtocolInterpreter::espnowEncryptionKeyLength]);
|
||||
|
||||
/**
|
||||
* Change the key used to encrypt/decrypt the encryption key when creating encrypted ESP-NOW connections. (Kok = key of keys, perhaps) If no Kok is provided by the user, a default Kok is used.
|
||||
* Will apply to any new encrypted connections.
|
||||
* Must be called after begin() to take effect.
|
||||
*
|
||||
* NOTE: Encrypted connections added before the Kok change will retain their old Kok.
|
||||
* This changes the Kok for all EspnowMeshBackend instances on this ESP8266.
|
||||
* Both Kok and encryption 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.
|
||||
*
|
||||
* @param espnowEncryptionKok An array containing the espnowEncryptionKeyLength bytes that will be used as the Kok.
|
||||
* @return True if Kok was changed successfully. False if Kok was not changed.
|
||||
*/
|
||||
static bool setEspnowEncryptionKok(uint8_t espnowEncryptionKok[EspnowProtocolInterpreter::espnowEncryptionKeyLength]);
|
||||
|
||||
/**
|
||||
* Get the key used to encrypt the encryption keys when creating encrypted ESP-NOW connections. (Kok = key of keys, perhaps) Returns nullptr if no Kok has been provided by the user.
|
||||
*
|
||||
* @return nullptr if default Kok is used, or current espnowEncryptionKok if a custom Kok has been set via the setEspnowEncryptionKok method.
|
||||
*/
|
||||
static const uint8_t *getEspnowEncryptionKok();
|
||||
|
||||
/**
|
||||
* Change the secret key used to generate HMACs for encrypted ESP-NOW connections.
|
||||
* Will apply to any new received requests for encrypted connection if this EspnowMeshBackend instance is the current request manager.
|
||||
* Will apply to any new encrypted connections requested or added by this EspnowMeshBackend instance.
|
||||
*
|
||||
* NOTE: Encrypted connections added before the key change will retain their old key.
|
||||
* Only changes the secret hash key used by this EspnowMeshBackend instance, so each instance can use a separate secret key.
|
||||
*
|
||||
* @param espnowHashKey An array containing the espnowHashKeyLength bytes that will be used as the HMAC key.
|
||||
*/
|
||||
void setEspnowHashKey(const uint8_t espnowHashKey[EspnowProtocolInterpreter::espnowHashKeyLength]);
|
||||
const uint8_t *getEspnowHashKey();
|
||||
|
||||
/**
|
||||
* Hint: Use String.length() to get the ASCII length of a String.
|
||||
*
|
||||
* @returns The maximum number of bytes (or ASCII characters) a transmission can contain. Note that non-ASCII characters usually require the space of at least two ASCII characters each.
|
||||
*/
|
||||
static uint32_t getMaxMessageBytesPerTransmission();
|
||||
|
||||
/**
|
||||
* Set the maximum acceptable message length, in terms of transmissions, when sending a message from this node.
|
||||
* This has no effect when receiving messages, the limit for receiving is always 256 transmissions per message.
|
||||
* Note that although values up to 128 are possible, this would in practice fill almost all the RAM available on the ESP8266 with just one message.
|
||||
* Thus, if this value is set higher than the default, make sure there is enough heap available to store the messages
|
||||
* and don't send messages more frequently than they can be processed.
|
||||
* Also note that a higher value will make the node less responsive as it will be spending a long time transmitting.
|
||||
*
|
||||
* Typical symptoms of running out of heap are crashes and messages that become empty even though they shouldn't be. Keep this in mind if going beyond the default.
|
||||
*
|
||||
* @param maxTransmissionsPerMessage The maximum acceptable message length, in terms of transmissions, when sending a message from this node. Valid values are 1 to 128. Defaults to 3.
|
||||
*/
|
||||
static void setMaxTransmissionsPerMessage(uint8_t maxTransmissionsPerMessage);
|
||||
static uint8_t getMaxTransmissionsPerMessage();
|
||||
|
||||
/**
|
||||
* Hint: Use String.length() to get the ASCII length of a String.
|
||||
*
|
||||
* @returns The maximum length in bytes an ASCII message is allowed to be when transmitted by this node. Note that non-ASCII characters usually require at least two bytes each.
|
||||
*/
|
||||
static uint32_t getMaxMessageLength();
|
||||
|
||||
/**
|
||||
* Set whether the normal events occurring in the library will be printed to Serial or not. Off by default.
|
||||
* This setting is shared by all EspnowMeshBackend instances.
|
||||
*
|
||||
* @param enabled If true, library Serial prints are activated.
|
||||
*/
|
||||
void setVerboseModeState(bool enabled) override;
|
||||
bool verboseMode() override;
|
||||
|
||||
/**
|
||||
* 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, bool newline = true) override;
|
||||
|
||||
/**
|
||||
* Same as verboseMode(), but used for printing from static functions.
|
||||
*
|
||||
* @returns True if the normal events occurring in the library will be printed to Serial. False otherwise.
|
||||
*/
|
||||
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, bool newline = true);
|
||||
|
||||
/**
|
||||
* Get the message of the response at responseIndex among the responses that are scheduled for transmission from this node.
|
||||
*
|
||||
* @param responseIndex The index of the response. Must be lower than numberOfScheduledResponses().
|
||||
* @returns A String containing the message of the response at responseIndex.
|
||||
*/
|
||||
static String getScheduledResponseMessage(uint32_t responseIndex);
|
||||
|
||||
/**
|
||||
* Get the MAC address for the recipient of the response at responseIndex among the responses that are scheduled for transmission from this node.
|
||||
*
|
||||
* @param responseIndex The index of the response. Must be lower than numberOfScheduledResponses().
|
||||
* @returns An array with six bytes containing the MAC address for the recipient of the response at responseIndex.
|
||||
*/
|
||||
static const uint8_t *getScheduledResponseRecipient(uint32_t responseIndex);
|
||||
|
||||
/**
|
||||
* Get the number of ESP-NOW responses that are scheduled for transmission from this node.
|
||||
*
|
||||
* @returns The number of ESP-NOW responses scheduled for transmission.
|
||||
*/
|
||||
static uint32_t numberOfScheduledResponses();
|
||||
|
||||
/**
|
||||
* Remove all responses that have been scheduled for transmission but are not yet transmitted.
|
||||
* Note that cleared responses will not be received by their recipient.
|
||||
*/
|
||||
static void clearAllScheduledResponses();
|
||||
|
||||
/**
|
||||
* Remove all responses targeting recipientMac that have been scheduled for transmission but are not yet transmitted.
|
||||
* Optionally deletes only responses to encrypted requests.
|
||||
* Note that deleted responses will not be received by their recipient.
|
||||
*
|
||||
* @param recipientMac The MAC address of the response recipient.
|
||||
* @param encryptedOnly If true, only responses to encrypted requests will be deleted.
|
||||
*/
|
||||
static void deleteScheduledResponsesByRecipient(const uint8_t *recipientMac, bool encryptedOnly);
|
||||
|
||||
/**
|
||||
* Set the timeout to use for each ESP-NOW transmission when transmitting.
|
||||
* Note that for multi-part transmissions (where message length is greater than getMaxMessageBytesPerTransmission()), the timeout is reset for each transmission part.
|
||||
* The default timeouts should fit most use cases, but in case you do a lot of time consuming processing when the node receives a message, you may need to relax them a bit.
|
||||
*
|
||||
* @param timeoutMs The timeout that should be used for each ESP-NOW transmission, in milliseconds. Defaults to 40 ms.
|
||||
*/
|
||||
static void setEspnowTransmissionTimeout(uint32_t timeoutMs);
|
||||
static uint32_t getEspnowTransmissionTimeout();
|
||||
|
||||
/**
|
||||
* Set the time to wait for an ack after having made an ESP-NOW transmission. If no ack is received within said time, a new transmission attempt is made.
|
||||
* Note that if a retransmission causes duplicate transmissions to reach the receiver, the duplicates will be detected and ignored automatically.
|
||||
* The default timeouts should fit most use cases, but in case you do a lot of time consuming processing when the node receives a message, you may need to relax them a bit.
|
||||
*
|
||||
* @param intervalMs The time to wait for an ack after having made an ESP-NOW transmission, in milliseconds. Defaults to 15 ms.
|
||||
*/
|
||||
static void setEspnowRetransmissionInterval(uint32_t intervalMs);
|
||||
static uint32_t getEspnowRetransmissionInterval();
|
||||
|
||||
// The maximum amount of time each of the two stages in an encrypted connection request may take.
|
||||
static void setEncryptionRequestTimeout(uint32_t timeoutMs);
|
||||
static uint32_t getEncryptionRequestTimeout();
|
||||
|
||||
void setAutoEncryptionDuration(uint32_t duration);
|
||||
uint32_t getAutoEncryptionDuration();
|
||||
|
||||
/**
|
||||
* Get the MAC address of the sender of the most recently received ESP-NOW request or response to this EspnowMeshBackend instance.
|
||||
* Returns a String.
|
||||
* By default the MAC will be that of the sender's station interface. The only exception is for unencrypted
|
||||
* responses to requests sent to an AP interface, which will return the response sender's AP interface MAC.
|
||||
*
|
||||
* @returns A String filled with a hexadecimal representation of the MAC, without delimiters.
|
||||
*/
|
||||
String getSenderMac();
|
||||
|
||||
/**
|
||||
* Get the MAC address of the sender of the most recently received ESP-NOW request or response to this EspnowMeshBackend instance.
|
||||
* Returns a uint8_t array.
|
||||
* By default the MAC will be that of the sender's station interface. The only exception is for unencrypted
|
||||
* responses to requests sent to an AP interface, which will return the response sender's AP interface MAC.
|
||||
*
|
||||
* @param macArray The array that should store the MAC address. Must be at least 6 bytes.
|
||||
* @returns macArray filled with the sender MAC.
|
||||
*/
|
||||
uint8_t *getSenderMac(uint8_t *macArray);
|
||||
|
||||
/**
|
||||
* Get whether the ESP-NOW request or response which was most recently received by this EspnowMeshBackend instance was encrypted or not.
|
||||
*
|
||||
* @returns If true, the request or response was encrypted. If false, it was unencrypted.
|
||||
*/
|
||||
bool receivedEncryptedMessage();
|
||||
|
||||
// Updates connection with current stored encryption key.
|
||||
// At least one of the leftmost 32 bits in each of the session keys should be 1, since the key otherwise indicates the connection is unencrypted.
|
||||
encrypted_connection_status_t addEncryptedConnection(uint8_t *peerStaMac, uint8_t *peerApMac, uint64_t peerSessionKey, uint64_t ownSessionKey);
|
||||
// Note that the espnowEncryptionKey, espnowEncryptionKok and espnowHashKey are not serialized.
|
||||
// These will be set to the values of the EspnowMeshBackend instance that is adding the serialized encrypted connection.
|
||||
// @param ignoreDuration Ignores any stored duration serializedConnectionState, guaranteeing that the created connection will be permanent. Returns: ECS_REQUEST_TRANSMISSION_FAILED indicates malformed serializedConnectionState.
|
||||
encrypted_connection_status_t addEncryptedConnection(const String &serializedConnectionState, bool ignoreDuration = false);
|
||||
|
||||
// Adds a new temporary encrypted connection, or changes the duration of an existing temporary connection (only updates keys, not duration, for existing permanent connections).
|
||||
// As with all these methods, changes will only take effect once the requester proves it has the ability to decrypt the session key.
|
||||
// At least one of the leftmost 32 bits in each of the session keys should be 1, since the key otherwise indicates the connection is unencrypted.
|
||||
encrypted_connection_status_t addTemporaryEncryptedConnection(uint8_t *peerStaMac, uint8_t *peerApMac, uint64_t peerSessionKey, uint64_t ownSessionKey, uint32_t duration);
|
||||
// Note that the espnowEncryptionKey, espnowEncryptionKok and espnowHashKey are not serialized.
|
||||
// These will be set to the values of the EspnowMeshBackend instance that is adding the serialized encrypted connection.
|
||||
// Uses duration argument instead of any stored duration in serializedConnectionState. Returns: ECS_REQUEST_TRANSMISSION_FAILED indicates malformed serializedConnectionState.
|
||||
encrypted_connection_status_t addTemporaryEncryptedConnection(const String &serializedConnectionState, uint32_t duration);
|
||||
|
||||
// If an encrypted connection to peerMac already exists, only connection duration is updated. All other settings are kept as is. Use removeEncryptedConnection/requestEncryptedConnectionRemoval first if encryption keys should be updated.
|
||||
// Makes sure both nodes have an encrypted connection to each other that's permanent.
|
||||
encrypted_connection_status_t requestEncryptedConnection(uint8_t *peerMac);
|
||||
// Makes sure both nodes have an encrypted connection to each other that's either permanent or has the duration specified.
|
||||
encrypted_connection_status_t requestTemporaryEncryptedConnection(uint8_t *peerMac, uint32_t durationMs);
|
||||
// Makes sure both nodes have an encrypted connection to each other that's either permanent or has at least the duration specified.
|
||||
// Note that if a temporary encrypted connection already exists to a target node, this method will slightly extend the connection duration
|
||||
// depending on the time it takes to verify the connection to the node.
|
||||
encrypted_connection_status_t requestFlexibleTemporaryEncryptedConnection(uint8_t *peerMac, uint32_t minDurationMs);
|
||||
static encrypted_connection_removal_outcome_t removeEncryptedConnection(uint8_t *peerMac);
|
||||
encrypted_connection_removal_outcome_t requestEncryptedConnectionRemoval(uint8_t *peerMac);
|
||||
|
||||
/**
|
||||
* Set whether this EspnowMeshBackend instance will accept unencrypted ESP-NOW requests or not, when acting as EspnowRequestManager.
|
||||
* When set to false and combined with already existing encrypted connections, this can be used to ensure only encrypted transmissions are processed.
|
||||
* When set to false it will also make it impossible to send unencrypted requests for encrypted connection to the node,
|
||||
* which can be useful if too many such requests could otherwise be expected.
|
||||
*
|
||||
* @param acceptsUnencryptedRequests If and only if true, unencrypted requests will be processed when this EspnowMeshBackend instance is acting as EspnowRequestManager. True by default.
|
||||
*/
|
||||
void setAcceptsUnencryptedRequests(bool acceptsUnencryptedRequests);
|
||||
bool acceptsUnencryptedRequests();
|
||||
|
||||
/**
|
||||
* @ returns The current number of encrypted ESP-NOW connections.
|
||||
*/
|
||||
static uint8_t numberOfEncryptedConnections();
|
||||
|
||||
// @returns resultArray filled with the MAC to the encrypted interface of the node, if an encrypted connection exists. nulltpr otherwise.
|
||||
static uint8_t *getEncryptedMac(const uint8_t *peerMac, uint8_t *resultArray);
|
||||
|
||||
// Create a string containing the current state of the encrypted connection for this node. The result can be used as input to addEncryptedConnection.
|
||||
// Note that transferring the serialized state over an unencrypted connection will compromise the security of the stored connection.
|
||||
// @ returns A String containing the serialized encrypted connection, or an empty String if there is no matching encrypted connection.
|
||||
static String serializeEncryptedConnection(const uint8_t *peerMac);
|
||||
static String serializeEncryptedConnection(uint32_t connectionIndex);
|
||||
|
||||
/**
|
||||
* Get information about any current ESP-NOW connection with another node.
|
||||
*
|
||||
* @param peerMac The node MAC for which to get information. Both MAC for AP interface and MAC for STA interface can be used (and will yield the same result).
|
||||
* Use the getEncryptedMac method or the indexed based getConnectionInfo if there is a need to find the actual encrypted interface.
|
||||
* @param remainingDuration An optional pointer to a uint32_t variable.
|
||||
* If supplied and the connection type is ECT_TEMPORARY_CONNECTION the variable will be set to the remaining duration of the connection.
|
||||
* Otherwise the variable value is not modified.
|
||||
* @ returns The espnow_connection_type_t of the connection with peerMac.
|
||||
*/
|
||||
static espnow_connection_type_t getConnectionInfo(uint8_t *peerMac, uint32_t *remainingDuration = nullptr);
|
||||
|
||||
/**
|
||||
* Get information about any current ESP-NOW connection with another node.
|
||||
*
|
||||
* @param connectionIndex The connection index of the node for which to get information. Valid values are limited by numberOfEncryptedConnections().
|
||||
* @param remainingDuration An optional pointer to a uint32_t variable.
|
||||
* If supplied and the connection type is ECT_TEMPORARY_CONNECTION the variable will be set to the remaining duration of the connection.
|
||||
* Otherwise the variable value is not modified.
|
||||
* @param peerMac An optional pointer to an uint8_t array with at least size 6. It will be filled with the MAC of the encrypted peer interface if an encrypted connection exists.
|
||||
* Otherwise the array is not modified.
|
||||
* @ returns The espnow_connection_type_t of the connection given by connectionIndex.
|
||||
*/
|
||||
static espnow_connection_type_t getConnectionInfo(uint32_t connectionIndex, uint32_t *remainingDuration = nullptr, uint8_t *peerMac = nullptr);
|
||||
|
||||
/**
|
||||
* @returns The proportion of ESP-NOW requests made by this node that have failed, since power on or latest reset.
|
||||
*/
|
||||
static double getTransmissionFailRate();
|
||||
|
||||
/**
|
||||
* Reset TransmissionFailRate back to 0.
|
||||
*/
|
||||
static void resetTransmissionFailRate();
|
||||
|
||||
protected:
|
||||
|
||||
typedef std::vector<EncryptedConnectionLog>::iterator connectionLogIterator;
|
||||
static connectionLogIterator connectionLogEndIterator();
|
||||
|
||||
bool activateEspnow();
|
||||
|
||||
/*
|
||||
* Note that ESP-NOW is not perfect and in rare cases messages may be dropped.
|
||||
* This needs to be compensated for in the application via extra verification
|
||||
* (e.g. by always sending a response such as a message hash), if message delivery must be guaranteed.
|
||||
*
|
||||
* Note that although 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 fail to transmit while others transmit successfully.
|
||||
*/
|
||||
static void sendEspnowResponses();
|
||||
static void clearOldLogEntries();
|
||||
|
||||
static uint32_t getMaxBytesPerTransmission();
|
||||
|
||||
static std::list<ResponseData>::const_iterator getScheduledResponse(uint32_t responseIndex);
|
||||
|
||||
// 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 encrypted_connection_removal_outcome_t removeEncryptedConnectionUnprotected(uint8_t *peerMac, std::vector<EncryptedConnectionLog>::iterator *resultingIterator = nullptr);
|
||||
static encrypted_connection_removal_outcome_t removeEncryptedConnectionUnprotected(connectionLogIterator &connectionIterator, std::vector<EncryptedConnectionLog>::iterator *resultingIterator);
|
||||
|
||||
/**
|
||||
* Set the MAC address considered to be the sender of the most recently received ESP-NOW request or response.
|
||||
*
|
||||
* @param macArray An uint8_t array which contains the MAC address to store. The method will store the first 6 bytes of the array.
|
||||
*/
|
||||
void setSenderMac(uint8_t *macArray);
|
||||
|
||||
/**
|
||||
* Set whether the most recently received ESP-NOW request or response is presented as having been encrypted or not.
|
||||
*
|
||||
* @param receivedEncryptedMessage If true, the request or response is presented as having been encrypted.
|
||||
*/
|
||||
void setReceivedEncryptedMessage(bool receivedEncryptedMessage);
|
||||
|
||||
static bool temporaryEncryptedConnectionToPermanent(uint8_t *peerMac);
|
||||
|
||||
/**
|
||||
* Will be true if a transmission initiated by a public method is in progress.
|
||||
*/
|
||||
static bool _espnowTransmissionMutex;
|
||||
|
||||
/**
|
||||
* Check if there is an ongoing ESP-NOW transmission in the library. Used to avoid interrupting transmissions.
|
||||
*
|
||||
* @returns True if a transmission initiated by a public method is in progress.
|
||||
*/
|
||||
static bool transmissionInProgress();
|
||||
|
||||
enum class macAndType_td : uint64_t {};
|
||||
typedef uint64_t messageID_td;
|
||||
typedef uint64_t peerMac_td;
|
||||
|
||||
static macAndType_td createMacAndTypeValue(uint64_t uint64Mac, char messageType);
|
||||
static uint64_t macAndTypeToUint64Mac(const macAndType_td &macAndTypeValue);
|
||||
|
||||
/**
|
||||
* Remove all entries which target peerMac in the logEntries map.
|
||||
* Optionally deletes only entries sent/received by encrypted transmissions.
|
||||
*
|
||||
* @param logEntries The map to process.
|
||||
* @param peerMac The MAC address of the peer node.
|
||||
* @param encryptedOnly If true, only entries sent/received by encrypted transmissions will be deleted.
|
||||
*/
|
||||
template <typename T>
|
||||
static void deleteEntriesByMac(std::map<std::pair<uint64_t, uint64_t>, T> &logEntries, const uint8_t *peerMac, bool encryptedOnly);
|
||||
|
||||
template <typename T>
|
||||
static void deleteEntriesByMac(std::map<std::pair<macAndType_td, uint64_t>, T> &logEntries, const uint8_t *peerMac, bool encryptedOnly);
|
||||
|
||||
static bool requestReceived(uint64_t requestMac, uint64_t requestID);
|
||||
|
||||
/**
|
||||
* Send an ESP-NOW message to the ESP8266 that has the MAC address specified in targetBSSID.
|
||||
*
|
||||
* @param messageType The identifier character for the type of message to send. Choices are 'Q' for question (request),
|
||||
* 'A' for answer (response), 'S' for synchronization request, 'P' for peer request and 'C' for peer request confirmation.
|
||||
* @returns The transmission status for the transmission.
|
||||
*/
|
||||
// Send a message to the node having targetBSSID as mac, changing targetBSSID to the mac of the encrypted connection if it exists and ensuring such an encrypted connection is synchronized.
|
||||
static transmission_status_t espnowSendToNode(const String &message, const uint8_t *targetBSSID, char messageType, EspnowMeshBackend *espnowInstance = nullptr);
|
||||
// Send a message using exactly the arguments given, without consideration for any encrypted connections.
|
||||
static transmission_status_t espnowSendToNodeUnsynchronized(const String message, const uint8_t *targetBSSID, char messageType, uint64_t messageID, EspnowMeshBackend *espnowInstance = nullptr);
|
||||
|
||||
transmission_status_t sendRequest(const String &message, const uint8_t *targetBSSID);
|
||||
transmission_status_t sendResponse(const String &message, uint64_t requestID, const uint8_t *targetBSSID);
|
||||
|
||||
private:
|
||||
|
||||
typedef std::function<String(const String &, const ExpiringTimeTracker &)> encryptionRequestBuilderType;
|
||||
static String defaultEncryptionRequestBuilder(const String &requestHeader, const uint32_t durationMs, const String &requestNonce, const ExpiringTimeTracker &existingTimeTracker);
|
||||
static String flexibleEncryptionRequestBuilder(const uint32_t minDurationMs, const String &requestNonce, const ExpiringTimeTracker &existingTimeTracker);
|
||||
|
||||
/**
|
||||
* We can't feed esp_now_register_recv_cb our EspnowMeshBackend instance's espnowReceiveCallback method directly, so this callback wrapper is a workaround.
|
||||
*
|
||||
* This method is very time critical so avoid Serial printing in it and in methods called from it (such as espnowReceiveCallback) as much as possible.
|
||||
* Otherwise transmission fail rate is likely to skyrocket.
|
||||
*/
|
||||
static void espnowReceiveCallbackWrapper(uint8_t *macaddr, uint8_t *dataArray, uint8_t len);
|
||||
void espnowReceiveCallback(uint8_t *macaddr, uint8_t *data, uint8_t len);
|
||||
|
||||
static void handlePeerRequest(uint8_t *macaddr, uint8_t *dataArray, uint8_t len, uint64_t uint64StationMac, uint64_t receivedMessageID);
|
||||
static void handlePeerRequestConfirmation(uint8_t *macaddr, uint8_t *dataArray, uint8_t len);
|
||||
|
||||
static void handlePostponedRemovals();
|
||||
|
||||
static bool verifyPeerSessionKey(uint64_t sessionKey, const uint8_t *peerMac, char messageType);
|
||||
static bool verifyPeerSessionKey(uint64_t sessionKey, EncryptedConnectionLog &encryptedConnection, uint64_t uint64PeerMac, char messageType);
|
||||
|
||||
static bool synchronizePeerSessionKey(uint64_t sessionKey, const uint8_t *peerMac);
|
||||
static bool synchronizePeerSessionKey(uint64_t sessionKey, EncryptedConnectionLog &encryptedConnection);
|
||||
|
||||
static const uint32_t _maxBytesPerTransmission = 250;
|
||||
static uint8_t _maxTransmissionsPerMessage;
|
||||
|
||||
static uint32_t _espnowTransmissionTimeoutMs;
|
||||
static uint32_t _espnowRetransmissionIntervalMs;
|
||||
|
||||
uint32_t _autoEncryptionDuration = 50;
|
||||
|
||||
static bool _staticVerboseMode;
|
||||
|
||||
static EspnowMeshBackend *_espnowRequestManager;
|
||||
|
||||
static std::map<std::pair<macAndType_td, messageID_td>, MessageData> receivedEspnowTransmissions;
|
||||
static std::map<std::pair<peerMac_td, messageID_td>, RequestData> sentRequests;
|
||||
static std::map<std::pair<peerMac_td, messageID_td>, TimeTracker> receivedRequests;
|
||||
|
||||
static std::list<ResponseData> responsesToSend;
|
||||
static std::list<PeerRequestLog> peerRequestConfirmationsToSend;
|
||||
|
||||
static std::vector<EncryptedConnectionLog> encryptedConnections;
|
||||
|
||||
static EncryptedConnectionLog *getEncryptedConnection(const uint8_t *peerMac);
|
||||
static EncryptedConnectionLog *getTemporaryEncryptedConnection(const uint8_t *peerMac);
|
||||
|
||||
//@returns iterator to connection in connectionVector, or connectionVector.end() if element not found
|
||||
template <typename T>
|
||||
static typename std::vector<T>::iterator getEncryptedConnectionIterator(const uint8_t *peerMac, typename std::vector<T> &connectionVector);
|
||||
static bool getEncryptedConnectionIterator(const uint8_t *peerMac, connectionLogIterator &iterator);
|
||||
// @returns 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);
|
||||
|
||||
static espnow_connection_type_t getConnectionInfoHelper(const EncryptedConnectionLog *encryptedConnection, uint32_t *remainingDuration, uint8_t *peerMac = nullptr);
|
||||
|
||||
// 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(bool scheduledRemovalOnly = false);
|
||||
|
||||
template <typename T, typename U>
|
||||
static void deleteExpiredLogEntries(std::map<std::pair<U, uint64_t>, T> &logEntries, uint32_t maxEntryLifetimeMs);
|
||||
|
||||
template <typename T>
|
||||
static void deleteExpiredLogEntries(std::list<T> &logEntries, uint32_t maxEntryLifetimeMs);
|
||||
|
||||
static uint32_t _logEntryLifetimeMs;
|
||||
static uint32_t logEntryLifetimeMs();
|
||||
static uint32_t _responseTimeoutMs;
|
||||
static uint32_t responseTimeoutMs();
|
||||
|
||||
static uint32_t _encryptionRequestTimeoutMs;
|
||||
|
||||
static uint32_t _timeOfLastLogClear;
|
||||
static uint32_t _criticalHeapLevel;
|
||||
static uint32_t _criticalHeapLevelBuffer;
|
||||
|
||||
static bool _espnowSendConfirmed;
|
||||
|
||||
static String _ongoingPeerRequestNonce;
|
||||
static EspnowMeshBackend *_ongoingPeerRequester;
|
||||
static encrypted_connection_status_t _ongoingPeerRequestResult;
|
||||
static uint32_t _ongoingPeerRequestEncryptionStart;
|
||||
|
||||
template <typename T>
|
||||
static T *getMapValue(std::map<uint64_t, T> &mapIn, uint64_t keyIn);
|
||||
|
||||
static bool usesConstantSessionKey(char messageType);
|
||||
|
||||
bool _acceptsUnencryptedRequests = true;
|
||||
|
||||
uint8_t _espnowEncryptionKey[EspnowProtocolInterpreter::espnowEncryptionKeyLength] {0};
|
||||
uint8_t _espnowHashKey[EspnowProtocolInterpreter::espnowHashKeyLength] {0};
|
||||
static uint8_t _espnowEncryptionKok[EspnowProtocolInterpreter::espnowEncryptionKeyLength];
|
||||
static bool _espnowEncryptionKokSet;
|
||||
static uint32_t _unencryptedMessageID;
|
||||
|
||||
uint8_t _senderMac[6] = {0};
|
||||
bool _receivedEncryptedMessage = false;
|
||||
|
||||
static bool _espnowSendToNodeMutex;
|
||||
static uint8_t _transmissionTargetBSSID[6];
|
||||
|
||||
static void storeSentRequest(const uint64_t targetBSSID, const uint64_t messageID, const RequestData &requestData);
|
||||
static void storeReceivedRequest(const uint64_t senderBSSID, const uint64_t messageID, const TimeTracker &timeTracker);
|
||||
|
||||
/**
|
||||
* Get a pointer to the EspnowMeshBackend instance that sent a request with the given requestID to the specified mac address.
|
||||
*
|
||||
* @returns A valid EspnowMeshBackend pointer if a matching entry is found in the EspnowMeshBackend sentRequests container. nullptr otherwise.
|
||||
*/
|
||||
static EspnowMeshBackend *getOwnerOfSentRequest(uint64_t requestMac, uint64_t requestID);
|
||||
|
||||
/**
|
||||
* Delete all entries in the sentRequests container where requestMac is noted as having received requestID.
|
||||
*
|
||||
* @returns The number of entries deleted.
|
||||
*/
|
||||
static size_t deleteSentRequest(uint64_t requestMac, uint64_t requestID);
|
||||
|
||||
static size_t deleteSentRequestsByOwner(EspnowMeshBackend *instancePointer);
|
||||
|
||||
/**
|
||||
* Contains the core logic used for requesting an encrypted connection to a peerMac.
|
||||
*
|
||||
* @param peerMac The MAC of the node with which an encrypted connection should be established.
|
||||
* @param encryptionRequestBuilder A function which is responsible for constructing the request message to send.
|
||||
* Called twice when the request is successful. First to build the initial request message and then to build the connection verification message.
|
||||
* The request message should typically be of the form: JsonTranslator::createEncryptionRequestIntro() + JsonTranslator::createEncryptionRequestEnding().
|
||||
* @returns The ultimate status of the requested encrypted connection, as encrypted_connection_status_t.
|
||||
*/
|
||||
encrypted_connection_status_t requestEncryptedConnectionKernel(uint8_t *peerMac, const encryptionRequestBuilderType &encryptionRequestBuilder);
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @returns The generated message ID.
|
||||
*/
|
||||
static uint64_t generateMessageID(EncryptedConnectionLog *encryptedConnection);
|
||||
|
||||
/**
|
||||
* Create a new session key for an encrypted connection using the built in RANDOM_REG32 of the ESP8266.
|
||||
* Should only be used when initializing a new connection.
|
||||
* Use generateMessageID instead when the encrypted connection is already initialized to keep the connection synchronized.
|
||||
*
|
||||
* @returns A uint64_t containing a new session key for an encrypted connection.
|
||||
*/
|
||||
static uint64_t createSessionKey();
|
||||
|
||||
// Used for verboseMode printing in attemptTransmission, _AT suffix used to reduce namespace clutter
|
||||
uint32_t totalDurationWhenSuccessful_AT = 0;
|
||||
uint32_t successfulTransmissions_AT = 0;
|
||||
uint32_t maxTransmissionDuration_AT = 0;
|
||||
|
||||
static double _transmissionsTotal;
|
||||
static double _transmissionsFailed;
|
||||
};
|
||||
|
||||
#endif
|
105
libraries/ESP8266WiFiMesh/src/EspnowProtocolInterpreter.cpp
Normal file
105
libraries/ESP8266WiFiMesh/src/EspnowProtocolInterpreter.cpp
Normal file
@ -0,0 +1,105 @@
|
||||
/*
|
||||
* 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 "EspnowProtocolInterpreter.h"
|
||||
#include "TypeConversionFunctions.h"
|
||||
#include <algorithm>
|
||||
|
||||
namespace EspnowProtocolInterpreter
|
||||
{
|
||||
const uint64_t uint64LeftmostBits = 0xFFFFFFFF00000000;
|
||||
|
||||
uint8_t espnowProtocolBytesSize()
|
||||
{
|
||||
return 16;
|
||||
}
|
||||
|
||||
String espnowGetMessageContent(uint8_t *transmission, uint8_t transmissionLength)
|
||||
{
|
||||
if(transmissionLength < espnowProtocolBytesSize())
|
||||
{
|
||||
return "";
|
||||
}
|
||||
else
|
||||
{
|
||||
// Ensure we have a NULL terminated character array so the String() constructor knows where to stop.
|
||||
uint8_t bufferedTransmission[transmissionLength + 1];
|
||||
std::copy_n(transmission, transmissionLength, bufferedTransmission);
|
||||
bufferedTransmission[transmissionLength] = 0;
|
||||
return String((char *)(bufferedTransmission + espnowProtocolBytesSize()));
|
||||
}
|
||||
}
|
||||
|
||||
char espnowGetMessageType(const uint8_t *transmissionDataArray)
|
||||
{
|
||||
return char(transmissionDataArray[espnowMessageTypeIndex]);
|
||||
}
|
||||
|
||||
uint8_t espnowGetTransmissionsRemaining(const uint8_t *transmissionDataArray)
|
||||
{
|
||||
return (transmissionDataArray[espnowTransmissionsRemainingIndex] & 0x7F);
|
||||
}
|
||||
|
||||
bool espnowIsMessageStart(const uint8_t *transmissionDataArray)
|
||||
{
|
||||
return (transmissionDataArray[espnowTransmissionsRemainingIndex] & 0x80); // If MSB is one we have messageStart
|
||||
}
|
||||
|
||||
uint64_t espnowGetTransmissionMac(const uint8_t *transmissionDataArray)
|
||||
{
|
||||
return macToUint64(transmissionDataArray + espnowTransmissionMacIndex);
|
||||
}
|
||||
|
||||
uint8_t *espnowGetTransmissionMac(const uint8_t *transmissionDataArray, uint8_t *resultArray)
|
||||
{
|
||||
std::copy_n((transmissionDataArray + espnowTransmissionMacIndex), 6, resultArray);
|
||||
return resultArray;
|
||||
}
|
||||
|
||||
uint64_t espnowGetMessageID(const uint8_t *transmissionDataArray)
|
||||
{
|
||||
uint64_t outcome = 0;
|
||||
for(int shiftingFortune = 56; shiftingFortune >= 0; shiftingFortune -= 8)
|
||||
{
|
||||
outcome |= ((uint64_t)transmissionDataArray[espnowMessageIDIndex + 7 - shiftingFortune/8] << shiftingFortune);
|
||||
}
|
||||
|
||||
return outcome;
|
||||
}
|
||||
|
||||
uint8_t *espnowSetMessageID(uint8_t *transmissionDataArray, uint64_t messageID)
|
||||
{
|
||||
for(int shiftingFortune = 56; shiftingFortune >= 0; shiftingFortune -= 8)
|
||||
{
|
||||
transmissionDataArray[espnowMessageIDIndex + 7 - shiftingFortune/8] = messageID >> shiftingFortune & 0xFF;
|
||||
}
|
||||
return transmissionDataArray;
|
||||
}
|
||||
|
||||
bool usesEncryption(uint64_t messageID)
|
||||
{
|
||||
// At least one of the leftmost half of bits in messageID is 1 if the transmission is encrypted.
|
||||
return messageID & uint64LeftmostBits;
|
||||
}
|
||||
}
|
74
libraries/ESP8266WiFiMesh/src/EspnowProtocolInterpreter.h
Normal file
74
libraries/ESP8266WiFiMesh/src/EspnowProtocolInterpreter.h
Normal file
@ -0,0 +1,74 @@
|
||||
/*
|
||||
* 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 __ESPNOWPROTOCOLINTERPRETER_H__
|
||||
#define __ESPNOWPROTOCOLINTERPRETER_H__
|
||||
|
||||
#include <WString.h>
|
||||
|
||||
// The following protocol is used on top of ESP-NOW (for the bits and bytes in each transmission):
|
||||
// Bit 0-7: Message type. The type for requests must be different from the type for responses if they may require more than one transmission. Otherwise multi-part requests and responses with the same ID may be mixed together.
|
||||
// Bit 8: Flag for message start.
|
||||
// Bit 9-15: Transmissions remaining for the message.
|
||||
// Byte 2-7: Transmission sender MAC address for AP interface. Since we always transmit from the station interface, this ensures both sender MAC addresses are available to the receiver.
|
||||
// Byte 8-15: Message ID. 32 rightmost bits used for unencrypted messages (the rest is 0). 64 bits used for encrypted messages (with at least one of the leftmost 32 bits set to 1).
|
||||
// This distinction based on encryption is required since the ESP-NOW API does not provide information about whether a received transmission is encrypted or not.
|
||||
// Byte 16-249: The message.
|
||||
// Each message can be split in up to EspnowMeshBackend::getMaxTransmissionsPerMessage() transmissions, based on message size. (max three transmissions per message is the default)
|
||||
|
||||
namespace EspnowProtocolInterpreter
|
||||
{
|
||||
const String synchronizationRequestHeader = "Synchronization request.";
|
||||
const String encryptionRequestHeader = "AddEC:"; // Add encrypted connection
|
||||
const String temporaryEncryptionRequestHeader = "AddTEC:"; // Add temporary encrypted connection
|
||||
const String basicConnectionInfoHeader = "BasicCI:"; // Basic connection info
|
||||
const String encryptedConnectionInfoHeader = "EncryptedCI:"; // Encrypted connection info
|
||||
const String maxConnectionsReachedHeader = "ECS_MAX_CONNECTIONS_REACHED_PEER:";
|
||||
const String encryptedConnectionVerificationHeader = "ECVerified:"; // Encrypted connection verified
|
||||
const String encryptedConnectionRemovalRequestHeader = "RemoveEC:"; // Remove encrypted connection
|
||||
|
||||
const uint8_t espnowMessageTypeIndex = 0;
|
||||
const uint8_t espnowTransmissionsRemainingIndex = 1;
|
||||
const uint8_t espnowTransmissionMacIndex = 2;
|
||||
const uint8_t espnowMessageIDIndex = 8;
|
||||
|
||||
uint8_t espnowProtocolBytesSize();
|
||||
|
||||
const uint8_t espnowEncryptionKeyLength = 16; // This is restricted to exactly 16 bytes by the ESP-NOW API. It should not be changed unless the ESP-NOW API is changed.
|
||||
const uint8_t espnowHashKeyLength = 16; // This can be changed to any value up to 255. Common values are 16 and 32.
|
||||
|
||||
String espnowGetMessageContent(uint8_t *transmission, uint8_t transmissionLength);
|
||||
char espnowGetMessageType(const uint8_t *transmissionDataArray);
|
||||
uint8_t espnowGetTransmissionsRemaining(const uint8_t *transmissionDataArray);
|
||||
bool espnowIsMessageStart(const uint8_t *transmissionDataArray);
|
||||
uint64_t espnowGetTransmissionMac(const uint8_t *transmissionDataArray);
|
||||
uint8_t *espnowGetTransmissionMac(const uint8_t *transmissionDataArray, uint8_t *resultArray);
|
||||
uint64_t espnowGetMessageID(const uint8_t *transmissionDataArray);
|
||||
// @return a pointer to transmissionDataArray
|
||||
uint8_t *espnowSetMessageID(uint8_t *transmissionDataArray, uint64_t messageID);
|
||||
|
||||
bool usesEncryption(uint64_t messageID);
|
||||
}
|
||||
|
||||
#endif
|
59
libraries/ESP8266WiFiMesh/src/ExpiringTimeTracker.cpp
Normal file
59
libraries/ESP8266WiFiMesh/src/ExpiringTimeTracker.cpp
Normal file
@ -0,0 +1,59 @@
|
||||
/*
|
||||
* 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 "ExpiringTimeTracker.h"
|
||||
|
||||
ExpiringTimeTracker::ExpiringTimeTracker(uint32_t duration, uint32_t creationTimeMs) :
|
||||
TimeTracker(creationTimeMs), _duration(duration)
|
||||
{ }
|
||||
|
||||
uint32_t ExpiringTimeTracker::duration() const
|
||||
{
|
||||
return _duration;
|
||||
}
|
||||
|
||||
void ExpiringTimeTracker::setRemainingDuration(uint32_t remainingDuration)
|
||||
{
|
||||
_duration = timeSinceCreation() + remainingDuration;
|
||||
}
|
||||
|
||||
uint32_t ExpiringTimeTracker::remainingDuration() const
|
||||
{
|
||||
uint32_t remainingDuration = duration() - timeSinceCreation();
|
||||
|
||||
if(expired())
|
||||
{
|
||||
// Overflow probably occured for remainingDuration calculation.
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
return remainingDuration;
|
||||
}
|
||||
}
|
||||
|
||||
bool ExpiringTimeTracker::expired() const
|
||||
{
|
||||
return timeSinceCreation() > duration();
|
||||
}
|
48
libraries/ESP8266WiFiMesh/src/ExpiringTimeTracker.h
Normal file
48
libraries/ESP8266WiFiMesh/src/ExpiringTimeTracker.h
Normal file
@ -0,0 +1,48 @@
|
||||
/*
|
||||
* 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 __EXPIRINGTIMETRACKER_H__
|
||||
#define __EXPIRINGTIMETRACKER_H__
|
||||
|
||||
#include "TimeTracker.h"
|
||||
#include <Arduino.h>
|
||||
|
||||
class ExpiringTimeTracker : public TimeTracker {
|
||||
|
||||
public:
|
||||
|
||||
~ExpiringTimeTracker() override = default;
|
||||
|
||||
ExpiringTimeTracker(uint32_t duration, uint32_t creationTimeMs = millis());
|
||||
uint32_t duration() const;
|
||||
void setRemainingDuration(uint32_t remainingDuration);
|
||||
uint32_t remainingDuration() const;
|
||||
bool expired() const;
|
||||
|
||||
private:
|
||||
|
||||
uint32_t _duration;
|
||||
};
|
||||
|
||||
#endif
|
269
libraries/ESP8266WiFiMesh/src/JsonTranslator.cpp
Normal file
269
libraries/ESP8266WiFiMesh/src/JsonTranslator.cpp
Normal file
@ -0,0 +1,269 @@
|
||||
/*
|
||||
* 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 "JsonTranslator.h"
|
||||
#include "Crypto.h"
|
||||
#include "EspnowProtocolInterpreter.h"
|
||||
#include "TypeConversionFunctions.h"
|
||||
|
||||
namespace JsonTranslator
|
||||
{
|
||||
String createJsonPair(const String &valueIdentifier, const String &value)
|
||||
{
|
||||
return valueIdentifier + "\"" + value + "\",";
|
||||
}
|
||||
|
||||
String createJsonEndPair(const String &valueIdentifier, const String &value)
|
||||
{
|
||||
return valueIdentifier + "\"" + value + "\"}}";
|
||||
}
|
||||
|
||||
uint8_t *createHmac(const String &message, const uint8_t *hashKey, uint8_t hashKeyLength, uint8_t resultArray[SHA256HMAC_SIZE])
|
||||
{
|
||||
// Create the HMAC instance with our key
|
||||
SHA256HMAC hmac(hashKey, hashKeyLength);
|
||||
|
||||
// Update the HMAC with our message
|
||||
hmac.doUpdate(message.c_str());
|
||||
|
||||
// Finish the HMAC calculation and return the authentication code
|
||||
hmac.doFinal(resultArray);
|
||||
|
||||
// resultArray now contains our SHA256HMAC_SIZE byte authentication code
|
||||
return resultArray;
|
||||
}
|
||||
|
||||
String createHmac(const String &message, const uint8_t *hashKey, uint8_t hashKeyLength)
|
||||
{
|
||||
byte hmac[SHA256HMAC_SIZE];
|
||||
createHmac(message, hashKey, hashKeyLength, hmac);
|
||||
return uint8ArrayToHexString(hmac, SHA256HMAC_SIZE);
|
||||
}
|
||||
|
||||
bool verifyHmac(const String &message, const String &messageHmac, const uint8_t *hashKey, uint8_t hashKeyLength)
|
||||
{
|
||||
if(messageHmac.length() != 2*SHA256HMAC_SIZE) // We know that each HMAC byte should become 2 String characters due to uint8ArrayToHexString.
|
||||
return false;
|
||||
|
||||
String generatedHmac = createHmac(message, hashKey, hashKeyLength);
|
||||
if(generatedHmac == messageHmac)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
bool verifyHmac(const String &encryptionRequestHmacMessage, const uint8_t *hashKey, uint8_t hashKeyLength)
|
||||
{
|
||||
String hmac = "";
|
||||
if(getHmac(encryptionRequestHmacMessage, hmac))
|
||||
{
|
||||
int32_t hmacStartIndex = encryptionRequestHmacMessage.indexOf(jsonHmac);
|
||||
if(hmacStartIndex < 0)
|
||||
return false;
|
||||
|
||||
if(verifyHmac(encryptionRequestHmacMessage.substring(0, hmacStartIndex), hmac, hashKey, hashKeyLength))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
String createEncryptedConnectionInfo(const String &requestNonce, const String &authenticationPassword, uint64_t ownSessionKey, uint64_t peerSessionKey)
|
||||
{
|
||||
// Returns: Encrypted connection info:{"arguments":{"nonce":"1F2","password":"abc","ownSessionKey":"3B4","peerSessionKey":"1A2"}}
|
||||
|
||||
|
||||
return
|
||||
EspnowProtocolInterpreter::encryptedConnectionInfoHeader + "{\"arguments\":{"
|
||||
+ createJsonPair(jsonNonce, requestNonce)
|
||||
+ createJsonPair(jsonPassword, authenticationPassword)
|
||||
+ createJsonPair(jsonOwnSessionKey, uint64ToString(peerSessionKey)) // Exchanges session keys since it should be valid for the receiver.
|
||||
+ createJsonEndPair(jsonPeerSessionKey, uint64ToString(ownSessionKey));
|
||||
}
|
||||
|
||||
String createEncryptionRequestIntro(const String &requestHeader, uint32_t duration)
|
||||
{
|
||||
return
|
||||
requestHeader + "{\"arguments\":{"
|
||||
+ (requestHeader == EspnowProtocolInterpreter::temporaryEncryptionRequestHeader ? createJsonPair(jsonDuration, String(duration)) : "");
|
||||
}
|
||||
|
||||
String createEncryptionRequestEnding(const String &requestNonce)
|
||||
{
|
||||
return createJsonEndPair(jsonNonce, requestNonce);
|
||||
}
|
||||
|
||||
String createEncryptionRequestMessage(const String &requestHeader, const String &requestNonce, uint32_t duration)
|
||||
{
|
||||
return createEncryptionRequestIntro(requestHeader, duration) + createEncryptionRequestEnding(requestNonce);
|
||||
}
|
||||
|
||||
String createEncryptionRequestHmacMessage(const String &requestHeader, const String &requestNonce, const uint8_t *hashKey, uint8_t hashKeyLength, uint32_t duration)
|
||||
{
|
||||
String mainMessage = createEncryptionRequestIntro(requestHeader, duration) + createJsonPair(jsonNonce, requestNonce);
|
||||
String hmac = createHmac(mainMessage, hashKey, hashKeyLength);
|
||||
return mainMessage + createJsonEndPair(jsonHmac, hmac);
|
||||
}
|
||||
|
||||
int32_t getStartIndex(const String &jsonString, const String &valueIdentifier, int32_t searchStartIndex)
|
||||
{
|
||||
int32_t startIndex = jsonString.indexOf(valueIdentifier, searchStartIndex);
|
||||
if(startIndex < 0)
|
||||
return startIndex;
|
||||
|
||||
startIndex += valueIdentifier.length() + 1; // Do not include valueIdentifier and initial quotation mark
|
||||
return startIndex;
|
||||
}
|
||||
|
||||
int32_t getEndIndex(const String &jsonString, int32_t searchStartIndex)
|
||||
{
|
||||
int32_t endIndex = jsonString.indexOf(',', searchStartIndex);
|
||||
if(endIndex < 0)
|
||||
endIndex = jsonString.indexOf('}', searchStartIndex);
|
||||
|
||||
endIndex -= 1; // End index will be at the character after the closing quotation mark, so need to subtract 1.
|
||||
|
||||
return endIndex;
|
||||
}
|
||||
|
||||
bool getPassword(const String &jsonString, String &result)
|
||||
{
|
||||
int32_t startIndex = getStartIndex(jsonString, jsonPassword);
|
||||
if(startIndex < 0)
|
||||
return false;
|
||||
|
||||
int32_t endIndex = getEndIndex(jsonString, startIndex);
|
||||
if(endIndex < 0)
|
||||
return false;
|
||||
|
||||
result = jsonString.substring(startIndex, endIndex);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool getOwnSessionKey(const String &jsonString, uint64_t &result)
|
||||
{
|
||||
int32_t startIndex = getStartIndex(jsonString, jsonOwnSessionKey);
|
||||
if(startIndex < 0)
|
||||
return false;
|
||||
|
||||
int32_t endIndex = getEndIndex(jsonString, startIndex);
|
||||
if(endIndex < 0)
|
||||
return false;
|
||||
|
||||
result = stringToUint64(jsonString.substring(startIndex, endIndex));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool getPeerSessionKey(const String &jsonString, uint64_t &result)
|
||||
{
|
||||
int32_t startIndex = getStartIndex(jsonString, jsonPeerSessionKey);
|
||||
if(startIndex < 0)
|
||||
return false;
|
||||
|
||||
int32_t endIndex = getEndIndex(jsonString, startIndex);
|
||||
if(endIndex < 0)
|
||||
return false;
|
||||
|
||||
result = stringToUint64(jsonString.substring(startIndex, endIndex));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool getPeerStaMac(const String &jsonString, uint8_t *resultArray)
|
||||
{
|
||||
int32_t startIndex = getStartIndex(jsonString, jsonPeerStaMac);
|
||||
if(startIndex < 0)
|
||||
return false;
|
||||
|
||||
int32_t endIndex = getEndIndex(jsonString, startIndex);
|
||||
if(endIndex < 0 || endIndex - startIndex != 12) // Mac String is always 12 characters long
|
||||
return false;
|
||||
|
||||
stringToMac(jsonString.substring(startIndex, endIndex), resultArray);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool getPeerApMac(const String &jsonString, uint8_t *resultArray)
|
||||
{
|
||||
int32_t startIndex = getStartIndex(jsonString, jsonPeerApMac);
|
||||
if(startIndex < 0)
|
||||
return false;
|
||||
|
||||
int32_t endIndex = getEndIndex(jsonString, startIndex);
|
||||
if(endIndex < 0 || endIndex - startIndex != 12) // Mac String is always 12 characters long
|
||||
return false;
|
||||
|
||||
stringToMac(jsonString.substring(startIndex, endIndex), resultArray);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool getDuration(const String &jsonString, uint32_t &result)
|
||||
{
|
||||
int32_t startIndex = getStartIndex(jsonString, jsonDuration);
|
||||
if(startIndex < 0)
|
||||
return false;
|
||||
|
||||
result = strtoul(jsonString.substring(startIndex).c_str(), nullptr, 0); // strtoul stops reading input when an invalid character is discovered.
|
||||
return true;
|
||||
}
|
||||
|
||||
bool getNonce(const String &jsonString, String &result)
|
||||
{
|
||||
int32_t startIndex = getStartIndex(jsonString, jsonNonce);
|
||||
if(startIndex < 0)
|
||||
return false;
|
||||
|
||||
int32_t endIndex = getEndIndex(jsonString, startIndex);
|
||||
if(endIndex < 0)
|
||||
return false;
|
||||
|
||||
result = jsonString.substring(startIndex, endIndex);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool getHmac(const String &jsonString, String &result)
|
||||
{
|
||||
int32_t startIndex = getStartIndex(jsonString, jsonHmac);
|
||||
if(startIndex < 0)
|
||||
return false;
|
||||
|
||||
int32_t endIndex = getEndIndex(jsonString, startIndex);
|
||||
if(endIndex < 0)
|
||||
return false;
|
||||
|
||||
result = jsonString.substring(startIndex, endIndex);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool getDesync(const String &jsonString, bool &result)
|
||||
{
|
||||
int32_t startIndex = getStartIndex(jsonString, jsonDesync);
|
||||
if(startIndex < 0)
|
||||
return false;
|
||||
|
||||
result = bool(strtoul(jsonString.substring(startIndex).c_str(), nullptr, 0)); // strtoul stops reading input when an invalid character is discovered.
|
||||
return true;
|
||||
}
|
||||
}
|
109
libraries/ESP8266WiFiMesh/src/JsonTranslator.h
Normal file
109
libraries/ESP8266WiFiMesh/src/JsonTranslator.h
Normal file
@ -0,0 +1,109 @@
|
||||
/*
|
||||
* 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 __ESPNOWJSONTRANSLATOR_H__
|
||||
#define __ESPNOWJSONTRANSLATOR_H__
|
||||
|
||||
#include <WString.h>
|
||||
#include "Crypto.h"
|
||||
|
||||
namespace JsonTranslator
|
||||
{
|
||||
const String jsonPassword = "\"password\":";
|
||||
const String jsonOwnSessionKey = "\"ownSK\":";
|
||||
const String jsonPeerSessionKey = "\"peerSK\":";
|
||||
const String jsonPeerStaMac = "\"peerStaMac\":";
|
||||
const String jsonPeerApMac = "\"peerApMac\":";
|
||||
const String jsonDuration = "\"duration\":";
|
||||
const String jsonNonce = "\"nonce\":";
|
||||
const String jsonHmac = "\"hmac\":";
|
||||
const String jsonDesync = "\"desync\":";
|
||||
|
||||
String createJsonPair(const String &valueIdentifier, const String &value);
|
||||
String createJsonEndPair(const String &valueIdentifier, const String &value);
|
||||
|
||||
uint8_t *createHmac(const String &message, const uint8_t *hashKey, uint8_t hashKeyLength, uint8_t resultArray[SHA256HMAC_SIZE]);
|
||||
String createHmac(const String &message, const uint8_t *hashKey, uint8_t hashKeyLength);
|
||||
|
||||
bool verifyHmac(const String &message, const String &messageHmac, const uint8_t *hashKey, uint8_t hashKeyLength);
|
||||
bool verifyHmac(const String &encryptionRequestHmacMessage, const uint8_t *hashKey, uint8_t hashKeyLength);
|
||||
|
||||
String createEncryptedConnectionInfo(const String &requestNonce, const String &authenticationPassword, uint64_t ownSessionKey, uint64_t peerSessionKey);
|
||||
String createEncryptionRequestIntro(const String &requestHeader, uint32_t duration = 0);
|
||||
String createEncryptionRequestEnding(const String &requestNonce);
|
||||
String createEncryptionRequestMessage(const String &requestHeader, const String &requestNonce, uint32_t duration = 0);
|
||||
String createEncryptionRequestHmacMessage(const String &requestHeader, const String &requestNonce, const uint8_t *hashKey, uint8_t hashKeyLength, uint32_t duration = 0);
|
||||
|
||||
/**
|
||||
* Provides the index within jsonString where the value of valueIdentifier starts.
|
||||
*
|
||||
* @param jsonString The String to search within.
|
||||
* @param valueIdentifier The identifier to search for.
|
||||
* @param searchStartIndex Optional argument that makes it possible to decide at which index of jsonString the search starts. Search will begin at index 0 if not provided.
|
||||
*
|
||||
* @returns An int32_t containing the index within jsonString where the value of valueIdentifier starts, or a negative value if valueIdentifier was not found.
|
||||
*/
|
||||
int32_t getStartIndex(const String &jsonString, const String &valueIdentifier, int32_t searchStartIndex = 0);
|
||||
|
||||
/**
|
||||
* Provides the index within jsonString where the next JSON termination character (',' or '}') is found, starting from searchStartIndex.
|
||||
*
|
||||
* @param jsonString The String to search within.
|
||||
* @param searchStartIndex The index of jsonString where the search will start.
|
||||
*
|
||||
* @returns An int32_t containing the index within jsonString where the next JSON termination character is found, or a negative value if no such character was found.
|
||||
*/
|
||||
int32_t getEndIndex(const String &jsonString, int32_t searchStartIndex);
|
||||
|
||||
/**
|
||||
* Stores the value of the password field within jsonString into the result variable.
|
||||
* No changes to the result variable are made if jsonString does not contain a password.
|
||||
*
|
||||
* @param jsonString The String to search within.
|
||||
* @param result The String where the value should be stored.
|
||||
*
|
||||
* @returns True if a value was found. False otherwise.
|
||||
*/
|
||||
bool getPassword(const String &jsonString, String &result);
|
||||
bool getOwnSessionKey(const String &jsonString, uint64_t &result);
|
||||
bool getPeerSessionKey(const String &jsonString, uint64_t &result);
|
||||
|
||||
/**
|
||||
* Stores the value of the peerStaMac field within jsonString into the resultArray.
|
||||
* No changes to the resultArray are made if jsonString does not contain a peerStaMac.
|
||||
*
|
||||
* @param jsonString The String to search within.
|
||||
* @param resultArray The uint8_t array where the value should be stored. Must be at least 6 bytes.
|
||||
*
|
||||
* @returns True if a value was found. False otherwise.
|
||||
*/
|
||||
bool getPeerStaMac(const String &jsonString, uint8_t *resultArray);
|
||||
bool getPeerApMac(const String &jsonString, uint8_t *resultArray);
|
||||
bool getDuration(const String &jsonString, uint32_t &result);
|
||||
bool getNonce(const String &jsonString, String &result);
|
||||
bool getHmac(const String &jsonString, String &result);
|
||||
bool getDesync(const String &jsonString, bool &result);
|
||||
}
|
||||
|
||||
#endif
|
276
libraries/ESP8266WiFiMesh/src/MeshBackendBase.cpp
Normal file
276
libraries/ESP8266WiFiMesh/src/MeshBackendBase.cpp
Normal file
@ -0,0 +1,276 @@
|
||||
/*
|
||||
MeshBackendBase
|
||||
|
||||
Copyright (c) 2015 Julian Fell and 2019 Anders Löfgren. All rights reserved.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#include "MeshBackendBase.h"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
MeshBackendBase *MeshBackendBase::apController = nullptr;
|
||||
|
||||
std::vector<NetworkInfo> MeshBackendBase::connectionQueue = {};
|
||||
std::vector<TransmissionResult> MeshBackendBase::latestTransmissionOutcomes = {};
|
||||
|
||||
bool MeshBackendBase::_printWarnings = true;
|
||||
|
||||
MeshBackendBase::MeshBackendBase(requestHandlerType requestHandler, responseHandlerType responseHandler, networkFilterType networkFilter, mesh_backend_t classType)
|
||||
{
|
||||
setRequestHandler(requestHandler);
|
||||
setResponseHandler(responseHandler);
|
||||
setNetworkFilter(networkFilter);
|
||||
setClassType(classType);
|
||||
}
|
||||
|
||||
MeshBackendBase::~MeshBackendBase()
|
||||
{
|
||||
deactivateAP();
|
||||
}
|
||||
|
||||
void MeshBackendBase::setClassType(mesh_backend_t classType)
|
||||
{
|
||||
_classType = classType;
|
||||
}
|
||||
|
||||
mesh_backend_t MeshBackendBase::getClassType() {return _classType;}
|
||||
|
||||
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()
|
||||
{
|
||||
}
|
||||
|
||||
void MeshBackendBase::restartAP()
|
||||
{
|
||||
deactivateAP();
|
||||
yield();
|
||||
activateAP();
|
||||
yield();
|
||||
}
|
||||
|
||||
MeshBackendBase *MeshBackendBase::getAPController()
|
||||
{
|
||||
return apController;
|
||||
}
|
||||
|
||||
bool MeshBackendBase::isAPController()
|
||||
{
|
||||
return (this == getAPController());
|
||||
}
|
||||
|
||||
void MeshBackendBase::setWiFiChannel(uint8 newWiFiChannel)
|
||||
{
|
||||
assert(1 <= newWiFiChannel && newWiFiChannel <= 13);
|
||||
|
||||
_meshWiFiChannel = newWiFiChannel;
|
||||
|
||||
// WiFi.channel() will change if this node connects to an AP with another channel,
|
||||
// so there is no guarantee we are using _meshWiFiChannel.
|
||||
// Also, we cannot change the WiFi channel while we are still connected to the other AP.
|
||||
if(WiFi.channel() != getWiFiChannel() && WiFi.status() != WL_CONNECTED)
|
||||
{
|
||||
// Apply changes to active AP.
|
||||
if(isAPController())
|
||||
restartAP();
|
||||
}
|
||||
}
|
||||
|
||||
uint8 MeshBackendBase::getWiFiChannel()
|
||||
{
|
||||
return _meshWiFiChannel;
|
||||
}
|
||||
|
||||
void MeshBackendBase::setSSID(const String &newSSIDPrefix, const String &newSSIDRoot, const String &newSSIDSuffix)
|
||||
{
|
||||
if(newSSIDPrefix != "")
|
||||
_SSIDPrefix = newSSIDPrefix;
|
||||
if(newSSIDRoot != "")
|
||||
_SSIDRoot = newSSIDRoot;
|
||||
if(newSSIDSuffix != "")
|
||||
_SSIDSuffix = newSSIDSuffix;
|
||||
|
||||
String newSSID = _SSIDPrefix + _SSIDRoot + _SSIDSuffix;
|
||||
|
||||
if(getSSID() != newSSID)
|
||||
{
|
||||
_SSID = newSSID;
|
||||
|
||||
// Apply SSID changes to active AP.
|
||||
if(isAPController())
|
||||
restartAP();
|
||||
}
|
||||
}
|
||||
|
||||
String MeshBackendBase::getSSID() {return _SSID;}
|
||||
|
||||
void MeshBackendBase::setSSIDPrefix(const String &newSSIDPrefix)
|
||||
{
|
||||
setSSID(newSSIDPrefix);
|
||||
}
|
||||
|
||||
String MeshBackendBase::getSSIDPrefix() {return _SSIDPrefix;}
|
||||
|
||||
void MeshBackendBase::setSSIDRoot(const String &newSSIDRoot)
|
||||
{
|
||||
setSSID("", newSSIDRoot);
|
||||
}
|
||||
|
||||
String MeshBackendBase::getSSIDRoot() {return _SSIDRoot;}
|
||||
|
||||
void MeshBackendBase::setSSIDSuffix(const String &newSSIDSuffix)
|
||||
{
|
||||
setSSID("", "", newSSIDSuffix);
|
||||
}
|
||||
|
||||
String MeshBackendBase::getSSIDSuffix() {return _SSIDSuffix;}
|
||||
|
||||
void MeshBackendBase::setMeshName(const String &newMeshName)
|
||||
{
|
||||
setSSIDPrefix(newMeshName);
|
||||
}
|
||||
|
||||
String MeshBackendBase::getMeshName() {return getSSIDPrefix();}
|
||||
|
||||
void MeshBackendBase::setNodeID(const String &newNodeID)
|
||||
{
|
||||
setSSIDSuffix(newNodeID);
|
||||
}
|
||||
|
||||
String MeshBackendBase::getNodeID() {return getSSIDSuffix();}
|
||||
|
||||
void MeshBackendBase::setMeshPassword(const String &newMeshPassword)
|
||||
{
|
||||
assert(8 <= newMeshPassword.length() && newMeshPassword.length() <= 64); // Limited by the ESP8266 API.
|
||||
|
||||
_meshPassword = newMeshPassword;
|
||||
|
||||
// Apply changes to active AP.
|
||||
if(isAPController())
|
||||
restartAP();
|
||||
}
|
||||
|
||||
String MeshBackendBase::getMeshPassword() {return _meshPassword;}
|
||||
|
||||
void MeshBackendBase::setMessage(const String &newMessage) {_message = newMessage;}
|
||||
String MeshBackendBase::getMessage() {return _message;}
|
||||
|
||||
void MeshBackendBase::setRequestHandler(MeshBackendBase::requestHandlerType requestHandler) {_requestHandler = requestHandler;}
|
||||
MeshBackendBase::requestHandlerType MeshBackendBase::getRequestHandler() {return _requestHandler;}
|
||||
|
||||
void MeshBackendBase::setResponseHandler(MeshBackendBase::responseHandlerType responseHandler) {_responseHandler = responseHandler;}
|
||||
MeshBackendBase::responseHandlerType MeshBackendBase::getResponseHandler() {return _responseHandler;}
|
||||
|
||||
void MeshBackendBase::setNetworkFilter(MeshBackendBase::networkFilterType networkFilter) {_networkFilter = networkFilter;}
|
||||
MeshBackendBase::networkFilterType MeshBackendBase::getNetworkFilter() {return _networkFilter;}
|
||||
|
||||
void MeshBackendBase::setScanHidden(bool scanHidden)
|
||||
{
|
||||
_scanHidden = scanHidden;
|
||||
}
|
||||
|
||||
bool MeshBackendBase::getScanHidden() {return _scanHidden;}
|
||||
|
||||
void MeshBackendBase::setAPHidden(bool apHidden)
|
||||
{
|
||||
if(getAPHidden() != apHidden)
|
||||
{
|
||||
_apHidden = apHidden;
|
||||
|
||||
// Apply changes to active AP.
|
||||
if(isAPController())
|
||||
restartAP();
|
||||
}
|
||||
}
|
||||
|
||||
bool MeshBackendBase::getAPHidden() {return _apHidden;}
|
||||
|
||||
bool MeshBackendBase::latestTransmissionSuccessful()
|
||||
{
|
||||
if(MeshBackendBase::latestTransmissionOutcomes.empty())
|
||||
return false;
|
||||
else
|
||||
for(TransmissionResult &transmissionResult : MeshBackendBase::latestTransmissionOutcomes)
|
||||
if(transmissionResult.transmissionStatus != TS_TRANSMISSION_COMPLETE)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void MeshBackendBase::scanForNetworks(bool scanAllWiFiChannels)
|
||||
{
|
||||
verboseModePrint(F("Scanning... "), false);
|
||||
|
||||
/* Scan for APs */
|
||||
connectionQueue.clear();
|
||||
|
||||
// If scanAllWiFiChannels is true, scanning will cause the WiFi radio to cycle through all WiFi channels.
|
||||
// This means existing WiFi connections are likely to break or work poorly if done frequently.
|
||||
int n = 0;
|
||||
if(scanAllWiFiChannels)
|
||||
{
|
||||
n = WiFi.scanNetworks(false, getScanHidden());
|
||||
}
|
||||
else
|
||||
{
|
||||
// Scan function argument overview: scanNetworks(bool async = false, bool show_hidden = false, uint8 channel = 0, uint8* ssid = NULL)
|
||||
n = WiFi.scanNetworks(false, getScanHidden(), getWiFiChannel());
|
||||
}
|
||||
|
||||
getNetworkFilter()(n, *this); // Update the connectionQueue.
|
||||
}
|
||||
|
||||
void MeshBackendBase::printAPInfo(const int apNetworkIndex, const String &apSSID, const int apWiFiChannel)
|
||||
{
|
||||
verboseModePrint(String(F("AP acquired: ")) + apSSID + String(F(", Ch:")) + String(apWiFiChannel) + " ", false);
|
||||
|
||||
if(apNetworkIndex != NETWORK_INFO_DEFAULT_INT)
|
||||
{
|
||||
verboseModePrint("(" + String(WiFi.RSSI(apNetworkIndex)) + String(F("dBm) ")) +
|
||||
(WiFi.encryptionType(apNetworkIndex) == ENC_TYPE_NONE ? String(F("open")) : ""), false);
|
||||
}
|
||||
|
||||
verboseModePrint(F("... "), false);
|
||||
}
|
302
libraries/ESP8266WiFiMesh/src/MeshBackendBase.h
Normal file
302
libraries/ESP8266WiFiMesh/src/MeshBackendBase.h
Normal file
@ -0,0 +1,302 @@
|
||||
/*
|
||||
MeshBackendBase
|
||||
|
||||
Copyright (c) 2015 Julian Fell and 2019 Anders Löfgren. All rights reserved.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#ifndef __MESHBACKENDBASE_H__
|
||||
#define __MESHBACKENDBASE_H__
|
||||
|
||||
#include <ESP8266WiFi.h>
|
||||
#include "TransmissionResult.h"
|
||||
|
||||
const String ESP8266_MESH_EMPTY_STRING = "";
|
||||
|
||||
typedef enum
|
||||
{
|
||||
MB_TCP_IP = 0,
|
||||
MB_ESP_NOW = 1
|
||||
} mesh_backend_t;
|
||||
|
||||
class MeshBackendBase {
|
||||
|
||||
protected:
|
||||
|
||||
typedef std::function<String(const String &, MeshBackendBase &)> requestHandlerType;
|
||||
typedef std::function<transmission_status_t(const String &, MeshBackendBase &)> responseHandlerType;
|
||||
typedef std::function<void(int, MeshBackendBase &)> networkFilterType;
|
||||
|
||||
public:
|
||||
|
||||
MeshBackendBase(requestHandlerType requestHandler, responseHandlerType responseHandler, networkFilterType networkFilter, mesh_backend_t classType);
|
||||
|
||||
virtual ~MeshBackendBase();
|
||||
|
||||
/**
|
||||
* A vector that contains the NetworkInfo for each WiFi network to connect to.
|
||||
* The connectionQueue vector is cleared before each new scan and filled via the networkFilter callback function once the scan completes.
|
||||
* WiFi connections will start with connectionQueue[0] and then incrementally proceed to higher vector positions.
|
||||
* Note that old network indicies often are invalidated whenever a new WiFi network scan occurs.
|
||||
*/
|
||||
static std::vector<NetworkInfo> connectionQueue;
|
||||
|
||||
/**
|
||||
* A vector with the TransmissionResult for each AP to which a transmission was attempted during the latest attemptTransmission call.
|
||||
* The latestTransmissionOutcomes vector is cleared before each new transmission attempt.
|
||||
* Connection attempts are indexed in the same order they were attempted.
|
||||
* Note that old network indicies often are invalidated whenever a new WiFi network scan occurs.
|
||||
*/
|
||||
static std::vector<TransmissionResult> latestTransmissionOutcomes;
|
||||
|
||||
/**
|
||||
* @returns True if latest transmission was successful (i.e. latestTransmissionOutcomes is not empty and all entries have transmissionStatus TS_TRANSMISSION_COMPLETE). False otherwise.
|
||||
*/
|
||||
static bool latestTransmissionSuccessful();
|
||||
|
||||
/**
|
||||
* Initialises the node.
|
||||
*/
|
||||
virtual void begin() = 0;
|
||||
|
||||
/**
|
||||
* Each AP requires a separate server port. If two AP:s are using the same server port, they will not be able to have both server instances active at the same time.
|
||||
* This is managed automatically by the activateAP method.
|
||||
*/
|
||||
void activateAP();
|
||||
void deactivateAP();
|
||||
void restartAP();
|
||||
|
||||
/**
|
||||
* Get the MeshBackendBase instance currently in control of the ESP8266 AP.
|
||||
* Note that the result will be nullptr when there is no active AP controller.
|
||||
* If another instance takes control over the AP after the pointer is created,
|
||||
* the created pointer will still point to the old AP instance.
|
||||
*
|
||||
* @returns A pointer to the MeshBackendBase instance currently in control of the ESP8266 AP,
|
||||
* or nullptr if there is no active AP controller.
|
||||
*/
|
||||
static MeshBackendBase *getAPController();
|
||||
|
||||
/**
|
||||
* Check if this MeshBackendBase instance is in control of the ESP8266 AP.
|
||||
*
|
||||
* @returns True if this MeshBackendBase instance is in control of the ESP8266 AP. False otherwise.
|
||||
*/
|
||||
bool isAPController();
|
||||
|
||||
/**
|
||||
* Change the WiFi channel used by this MeshBackendBase instance.
|
||||
* Will also change the WiFi channel for the active AP if this MeshBackendBase instance is the current AP controller and it is possible to change channel.
|
||||
*
|
||||
* WARNING: The ESP8266 has only one WiFi channel, and the the station/client mode is always prioritized for channel selection.
|
||||
* This can cause problems if several MeshBackendBase instances exist on the same ESP8266 and use different WiFi channels.
|
||||
* In such a case, whenever the station of one MeshBackendBase instance connects to an AP, it will silently force the
|
||||
* WiFi channel of any active AP on the ESP8266 to match that of the station. This will cause disconnects and possibly
|
||||
* make it impossible for other stations to detect the APs whose WiFi channels have changed.
|
||||
*
|
||||
* @param newWiFiChannel The WiFi channel to change to. Valid values are integers from 1 to 13.
|
||||
*
|
||||
*/
|
||||
void setWiFiChannel(uint8 newWiFiChannel);
|
||||
uint8 getWiFiChannel();
|
||||
|
||||
/**
|
||||
* Change the SSID used by this MeshBackendBase instance.
|
||||
* Will also change the SSID for the active AP if this MeshBackendBase instance is the current AP controller.
|
||||
*
|
||||
* @param newSSIDPrefix The first part of the new SSID.
|
||||
* @param newSSIDRoot The middle part of the new SSID.
|
||||
* @param newSSIDSuffix The last part of the new SSID.
|
||||
*/
|
||||
void setSSID(const String &newSSIDPrefix = ESP8266_MESH_EMPTY_STRING, const String &newSSIDRoot = ESP8266_MESH_EMPTY_STRING,
|
||||
const String &newSSIDSuffix = ESP8266_MESH_EMPTY_STRING);
|
||||
String getSSID();
|
||||
|
||||
/**
|
||||
* Change the first part of the SSID used by this MeshBackendBase instance.
|
||||
* Will also change the first part of the SSID for the active AP if this MeshBackendBase instance is the current AP controller.
|
||||
*
|
||||
* @param newSSIDPrefix The new first part of the SSID.
|
||||
*/
|
||||
void setSSIDPrefix(const String &newSSIDPrefix);
|
||||
String getSSIDPrefix();
|
||||
|
||||
/**
|
||||
* Change the middle part of the SSID used by this MeshBackendBase instance.
|
||||
* Will also change the middle part of the SSID for the active AP if this MeshBackendBase instance is the current AP controller.
|
||||
*
|
||||
* @param newSSIDPrefix The new middle part of the SSID.
|
||||
*/
|
||||
void setSSIDRoot(const String &newSSIDRoot);
|
||||
String getSSIDRoot();
|
||||
|
||||
/**
|
||||
* Change the last part of the SSID used by this MeshBackendBase instance.
|
||||
* Will also change the last part of the SSID for the active AP if this MeshBackendBase instance is the current AP controller.
|
||||
*
|
||||
* @param newSSIDSuffix The new last part of the SSID.
|
||||
*/
|
||||
void setSSIDSuffix(const String &newSSIDSuffix);
|
||||
String getSSIDSuffix();
|
||||
|
||||
/**
|
||||
* Change the mesh name used by this MeshBackendBase instance.
|
||||
* Will also change the mesh name for the active AP if this MeshBackendBase instance is the current AP controller.
|
||||
* Used as alias for setSSIDPrefix by default. Feel free to override this method in a subclass if your mesh name is not equal to SSIDPrefix.
|
||||
*
|
||||
* @param newMeshName The mesh name to change to.
|
||||
*/
|
||||
virtual void setMeshName(const String &newMeshName);
|
||||
virtual String getMeshName();
|
||||
|
||||
/**
|
||||
* Change the node id used by this MeshBackendBase instance.
|
||||
* Will also change the node id for the active AP if this MeshBackendBase instance is the current AP controller.
|
||||
* Used as alias for setSSIDSuffix by default. Feel free to override this method in a subclass if your node id is not equal to SSIDSuffix.
|
||||
*
|
||||
* @param newNodeID The node id to change to.
|
||||
*/
|
||||
virtual void setNodeID(const String &newNodeID);
|
||||
virtual String getNodeID();
|
||||
|
||||
/**
|
||||
* Set the password used when connecting to other AP:s and when other nodes connect to the AP of this node.
|
||||
* Will also change the setting for the active AP if this MeshBackendBase instance is the current AP controller.
|
||||
*
|
||||
* @param newMeshPassword The password to use.
|
||||
*/
|
||||
void setMeshPassword(const String &newMeshPassword);
|
||||
String getMeshPassword();
|
||||
|
||||
/**
|
||||
* Set the message that will be sent to other nodes when calling attemptTransmission.
|
||||
*
|
||||
* @param newMessage The message to send.
|
||||
*/
|
||||
void setMessage(const String &newMessage);
|
||||
String getMessage();
|
||||
|
||||
virtual void attemptTransmission(const String &message, bool scan = true, bool scanAllWiFiChannels = false) = 0;
|
||||
|
||||
void setRequestHandler(requestHandlerType requestHandler);
|
||||
requestHandlerType getRequestHandler();
|
||||
|
||||
void setResponseHandler(responseHandlerType responseHandler);
|
||||
responseHandlerType getResponseHandler();
|
||||
|
||||
void setNetworkFilter(networkFilterType networkFilter);
|
||||
networkFilterType getNetworkFilter();
|
||||
|
||||
/**
|
||||
* Set whether scan results from this MeshBackendBase instance will include WiFi networks with hidden SSIDs.
|
||||
* This is false by default.
|
||||
* The SSID field of a found hidden network will be blank in the scan results.
|
||||
* WiFi.isHidden(networkIndex) can be used to verify that a found network is hidden.
|
||||
*
|
||||
* @param scanHidden If true, WiFi networks with hidden SSIDs will be included in scan results.
|
||||
*/
|
||||
void setScanHidden(bool scanHidden);
|
||||
bool getScanHidden();
|
||||
|
||||
/**
|
||||
* Set whether the AP controlled by this MeshBackendBase instance will have a WiFi network with hidden SSID.
|
||||
* This is false by default.
|
||||
* Will also change the setting for the active AP if this MeshBackendBase instance is the current AP controller.
|
||||
*
|
||||
* @param apHidden If true, the WiFi network created will have a hidden SSID.
|
||||
*/
|
||||
void setAPHidden(bool apHidden);
|
||||
bool getAPHidden();
|
||||
|
||||
/**
|
||||
* Set whether the normal events occurring in the library will be printed to Serial or not. Off by default.
|
||||
* This setting is separate for each mesh instance.
|
||||
*
|
||||
* @param enabled If true, library Serial prints are activated.
|
||||
*/
|
||||
virtual void setVerboseModeState(bool enabled);
|
||||
virtual bool verboseMode();
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
virtual void verboseModePrint(const String &stringToPrint, bool newline = true);
|
||||
|
||||
/**
|
||||
* Set whether the warnings occurring in the library will be printed to Serial or not. On by default.
|
||||
* This setting will affect all mesh instances.
|
||||
*
|
||||
* @param printEnabled If true, warning Serial prints from the library are activated.
|
||||
*/
|
||||
static void setPrintWarnings(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, bool newline = true);
|
||||
|
||||
mesh_backend_t getClassType();
|
||||
|
||||
protected:
|
||||
|
||||
virtual void scanForNetworks(bool scanAllWiFiChannels);
|
||||
virtual void printAPInfo(const int apNetworkIndex, const String &apSSID, const int apWiFiChannel);
|
||||
|
||||
/**
|
||||
* Called just before we activate the AP.
|
||||
* Put _server.stop() in deactivateAPHook() in case you use _server.begin() here.
|
||||
*/
|
||||
virtual void activateAPHook();
|
||||
|
||||
/**
|
||||
* Called just before we deactivate the AP.
|
||||
* Put _server.stop() here in case you use _server.begin() in activateAPHook().
|
||||
*/
|
||||
virtual void deactivateAPHook();
|
||||
|
||||
void setClassType(mesh_backend_t classType);
|
||||
|
||||
private:
|
||||
|
||||
mesh_backend_t _classType;
|
||||
|
||||
static MeshBackendBase *apController;
|
||||
|
||||
String _SSID;
|
||||
String _SSIDPrefix;
|
||||
String _SSIDRoot;
|
||||
String _SSIDSuffix;
|
||||
String _meshPassword;
|
||||
uint8 _meshWiFiChannel;
|
||||
bool _verboseMode;
|
||||
String _message = ESP8266_MESH_EMPTY_STRING;
|
||||
bool _scanHidden = false;
|
||||
bool _apHidden = false;
|
||||
|
||||
requestHandlerType _requestHandler;
|
||||
responseHandlerType _responseHandler;
|
||||
networkFilterType _networkFilter;
|
||||
|
||||
static bool _printWarnings;
|
||||
};
|
||||
|
||||
#endif
|
69
libraries/ESP8266WiFiMesh/src/MessageData.cpp
Normal file
69
libraries/ESP8266WiFiMesh/src/MessageData.cpp
Normal file
@ -0,0 +1,69 @@
|
||||
/*
|
||||
* 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 "MessageData.h"
|
||||
#include "EspnowProtocolInterpreter.h"
|
||||
#include "EspnowMeshBackend.h"
|
||||
#include <assert.h>
|
||||
|
||||
MessageData::MessageData(uint8_t *initialTransmission, uint8_t transmissionLength,uint32_t creationTimeMs) :
|
||||
TimeTracker(creationTimeMs)
|
||||
{
|
||||
_transmissionsExpected = EspnowProtocolInterpreter::espnowGetTransmissionsRemaining(initialTransmission) + 1;
|
||||
addToMessage(initialTransmission, transmissionLength);
|
||||
}
|
||||
|
||||
bool MessageData::addToMessage(uint8_t *transmission, uint8_t transmissionLength)
|
||||
{
|
||||
if(EspnowProtocolInterpreter::espnowGetTransmissionsRemaining(transmission) == getTransmissionsRemaining() - 1)
|
||||
{
|
||||
String message = EspnowProtocolInterpreter::espnowGetMessageContent(transmission, transmissionLength);
|
||||
assert(message.length() <= EspnowMeshBackend::getMaxMessageBytesPerTransmission()); // Should catch some cases where transmission is not null terminated.
|
||||
_totalMessage += message;
|
||||
_transmissionsReceived++;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t MessageData::getTransmissionsReceived()
|
||||
{
|
||||
return _transmissionsReceived;
|
||||
}
|
||||
|
||||
uint8_t MessageData::getTransmissionsExpected()
|
||||
{
|
||||
return _transmissionsExpected;
|
||||
}
|
||||
|
||||
uint8_t MessageData::getTransmissionsRemaining()
|
||||
{
|
||||
return getTransmissionsExpected() - getTransmissionsReceived();
|
||||
}
|
||||
|
||||
String MessageData::getTotalMessage()
|
||||
{
|
||||
return _totalMessage;
|
||||
}
|
54
libraries/ESP8266WiFiMesh/src/MessageData.h
Normal file
54
libraries/ESP8266WiFiMesh/src/MessageData.h
Normal file
@ -0,0 +1,54 @@
|
||||
/*
|
||||
* 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 __ESPNOWMESSAGEDATA_H__
|
||||
#define __ESPNOWMESSAGEDATA_H__
|
||||
|
||||
#include "TimeTracker.h"
|
||||
#include <Arduino.h>
|
||||
|
||||
class MessageData : public TimeTracker {
|
||||
|
||||
public:
|
||||
|
||||
MessageData(uint8_t *initialTransmission, uint8_t transmissionLength, uint32_t creationTimeMs = millis());
|
||||
/**
|
||||
* @transmission A string of characters, including initial protocol bytes.
|
||||
* @transmissionLength Length of transmission.
|
||||
*/
|
||||
bool addToMessage(uint8_t *transmission, uint8_t transmissionLength);
|
||||
uint8_t getTransmissionsReceived();
|
||||
uint8_t getTransmissionsExpected();
|
||||
uint8_t getTransmissionsRemaining();
|
||||
String getTotalMessage();
|
||||
|
||||
private:
|
||||
|
||||
uint8_t _transmissionsReceived = 0;
|
||||
uint8_t _transmissionsExpected;
|
||||
String _totalMessage = "";
|
||||
|
||||
};
|
||||
|
||||
#endif
|
72
libraries/ESP8266WiFiMesh/src/MutexTracker.cpp
Normal file
72
libraries/ESP8266WiFiMesh/src/MutexTracker.cpp
Normal file
@ -0,0 +1,72 @@
|
||||
/*
|
||||
* 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 "MutexTracker.h"
|
||||
|
||||
MutexTracker::MutexTracker(bool &mutexToCapture)
|
||||
{
|
||||
attemptMutexCapture(mutexToCapture);
|
||||
}
|
||||
|
||||
MutexTracker::MutexTracker(bool &mutexToCapture, std::function<void()> destructorHook) : MutexTracker(mutexToCapture)
|
||||
{
|
||||
_destructorHook = destructorHook;
|
||||
}
|
||||
|
||||
MutexTracker::~MutexTracker()
|
||||
{
|
||||
releaseMutex();
|
||||
_destructorHook();
|
||||
}
|
||||
|
||||
bool MutexTracker::mutexCaptured()
|
||||
{
|
||||
if(_capturedMutex)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
void MutexTracker::releaseMutex()
|
||||
{
|
||||
if(mutexCaptured())
|
||||
{
|
||||
*_capturedMutex = false;
|
||||
_capturedMutex = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool MutexTracker::attemptMutexCapture(bool &mutexToCapture)
|
||||
{
|
||||
if(!mutexToCapture)
|
||||
{
|
||||
_capturedMutex = &mutexToCapture;
|
||||
*_capturedMutex = true;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
71
libraries/ESP8266WiFiMesh/src/MutexTracker.h
Normal file
71
libraries/ESP8266WiFiMesh/src/MutexTracker.h
Normal file
@ -0,0 +1,71 @@
|
||||
/*
|
||||
* 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 __MUTEXTRACKER_H__
|
||||
#define __MUTEXTRACKER_H__
|
||||
|
||||
#include <functional>
|
||||
|
||||
/**
|
||||
* A SLIM (Scope LImited Manager)/Scope-Bound Resource Management/RAII class to manage the state of a mutex.
|
||||
*/
|
||||
class MutexTracker
|
||||
{
|
||||
public:
|
||||
|
||||
/**
|
||||
* Attempts to capture the mutex. Use the mutexCaptured() method to check success.
|
||||
*/
|
||||
MutexTracker(bool &mutexToCapture);
|
||||
|
||||
/**
|
||||
* Attempts to capture the mutex. Use the mutexCaptured() method to check success.
|
||||
*
|
||||
* @param destructorHook A function to hook into the MutexTracker destructor. Will be called when the MutexTracker instance is being destroyed, after the mutex has been released.
|
||||
*/
|
||||
MutexTracker(bool &mutexToCapture, std::function<void()> destructorHook);
|
||||
|
||||
~MutexTracker();
|
||||
|
||||
bool mutexCaptured();
|
||||
|
||||
/**
|
||||
* Set the mutex free to roam the binary plains, giving new MutexTrackers a chance to capture it.
|
||||
*/
|
||||
void releaseMutex();
|
||||
|
||||
private:
|
||||
|
||||
bool *_capturedMutex = nullptr;
|
||||
std::function<void()> _destructorHook = [](){ };
|
||||
|
||||
/**
|
||||
* Attempt to capture the mutex.
|
||||
*
|
||||
* @returns True if mutex was caught (meaning no other instance is holding the mutex). False otherwise.
|
||||
*/
|
||||
bool attemptMutexCapture(bool &mutexToCapture);
|
||||
};
|
||||
|
||||
#endif
|
50
libraries/ESP8266WiFiMesh/src/PeerRequestLog.cpp
Normal file
50
libraries/ESP8266WiFiMesh/src/PeerRequestLog.cpp
Normal file
@ -0,0 +1,50 @@
|
||||
/*
|
||||
* 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 "PeerRequestLog.h"
|
||||
#include "EspnowMeshBackend.h"
|
||||
|
||||
using EspnowProtocolInterpreter::espnowHashKeyLength;
|
||||
|
||||
PeerRequestLog::PeerRequestLog(uint64_t requestID, bool requestEncrypted, const String &authenticationPassword, const String &peerRequestNonce, const uint8_t peerStaMac[6], const uint8_t peerApMac[6], const uint8_t hashKey[espnowHashKeyLength])
|
||||
: EncryptedConnectionData(peerStaMac, peerApMac, 0, 0, EspnowMeshBackend::getEncryptionRequestTimeout(), hashKey),
|
||||
_requestID(requestID), _requestEncrypted(requestEncrypted), _authenticationPassword(authenticationPassword), _peerRequestNonce(peerRequestNonce)
|
||||
{ }
|
||||
|
||||
PeerRequestLog::PeerRequestLog(uint64_t requestID, bool requestEncrypted, const String &authenticationPassword, const String &peerRequestNonce, const uint8_t peerStaMac[6], const uint8_t peerApMac[6], uint64_t peerSessionKey, uint64_t ownSessionKey, const uint8_t hashKey[espnowHashKeyLength])
|
||||
: EncryptedConnectionData(peerStaMac, peerApMac, peerSessionKey, ownSessionKey, EspnowMeshBackend::getEncryptionRequestTimeout(), hashKey),
|
||||
_requestID(requestID), _requestEncrypted(requestEncrypted), _authenticationPassword(authenticationPassword), _peerRequestNonce(peerRequestNonce)
|
||||
{ }
|
||||
|
||||
void PeerRequestLog::setRequestID(uint64_t requestID) { _requestID = requestID; }
|
||||
uint64_t PeerRequestLog::getRequestID() { return _requestID; }
|
||||
|
||||
void PeerRequestLog::setRequestEncrypted(bool requestEncrypted) { _requestEncrypted = requestEncrypted; }
|
||||
bool PeerRequestLog::requestEncrypted() { return _requestEncrypted; }
|
||||
|
||||
void PeerRequestLog::setAuthenticationPassword(const String &password) { _authenticationPassword = password; }
|
||||
String PeerRequestLog::getAuthenticationPassword() { return _authenticationPassword; }
|
||||
|
||||
void PeerRequestLog::setPeerRequestNonce(const String &nonce) { _peerRequestNonce = nonce; }
|
||||
String PeerRequestLog::getPeerRequestNonce() { return _peerRequestNonce; }
|
60
libraries/ESP8266WiFiMesh/src/PeerRequestLog.h
Normal file
60
libraries/ESP8266WiFiMesh/src/PeerRequestLog.h
Normal file
@ -0,0 +1,60 @@
|
||||
/*
|
||||
* 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 __ESPNOWPEERREQUESTLOG_H__
|
||||
#define __ESPNOWPEERREQUESTLOG_H__
|
||||
|
||||
#include "EncryptedConnectionData.h"
|
||||
#include "EspnowProtocolInterpreter.h"
|
||||
|
||||
class PeerRequestLog : public EncryptedConnectionData {
|
||||
|
||||
public:
|
||||
|
||||
PeerRequestLog(uint64_t requestID, bool requestEncrypted, const String &authenticationPassword, const String &peerRequestNonce, const uint8_t peerStaMac[6],
|
||||
const uint8_t peerApMac[6], const uint8_t hashKey[EspnowProtocolInterpreter::espnowHashKeyLength]);
|
||||
PeerRequestLog(uint64_t requestID, bool requestEncrypted, const String &authenticationPassword, const String &peerRequestNonce, const uint8_t peerStaMac[6],
|
||||
const uint8_t peerApMac[6], uint64_t peerSessionKey, uint64_t ownSessionKey, const uint8_t hashKey[EspnowProtocolInterpreter::espnowHashKeyLength]);
|
||||
|
||||
void setRequestID(uint64_t requestID);
|
||||
uint64_t getRequestID();
|
||||
|
||||
void setRequestEncrypted(bool requestEncrypted);
|
||||
bool requestEncrypted();
|
||||
|
||||
void setAuthenticationPassword(const String &password);
|
||||
String getAuthenticationPassword();
|
||||
|
||||
void setPeerRequestNonce(const String &nonce);
|
||||
String getPeerRequestNonce();
|
||||
|
||||
private:
|
||||
|
||||
uint64_t _requestID;
|
||||
bool _requestEncrypted;
|
||||
String _authenticationPassword = "";
|
||||
String _peerRequestNonce = "";
|
||||
};
|
||||
|
||||
#endif
|
32
libraries/ESP8266WiFiMesh/src/RequestData.cpp
Normal file
32
libraries/ESP8266WiFiMesh/src/RequestData.cpp
Normal file
@ -0,0 +1,32 @@
|
||||
/*
|
||||
* 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 "RequestData.h"
|
||||
|
||||
RequestData::RequestData(EspnowMeshBackend &meshInstance, uint32_t creationTimeMs) :
|
||||
TimeTracker(creationTimeMs), _meshInstance(meshInstance)
|
||||
{ }
|
||||
|
||||
void RequestData::setMeshInstance(EspnowMeshBackend &meshInstance) { _meshInstance = meshInstance; }
|
||||
EspnowMeshBackend &RequestData::getMeshInstance() { return _meshInstance; }
|
47
libraries/ESP8266WiFiMesh/src/RequestData.h
Normal file
47
libraries/ESP8266WiFiMesh/src/RequestData.h
Normal file
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 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 __ESPNOWREQUESTDATA_H__
|
||||
#define __ESPNOWREQUESTDATA_H__
|
||||
|
||||
#include "TimeTracker.h"
|
||||
#include "EspnowMeshBackend.h"
|
||||
|
||||
class EspnowMeshBackend;
|
||||
|
||||
class RequestData : public TimeTracker {
|
||||
|
||||
public:
|
||||
|
||||
RequestData(EspnowMeshBackend &meshInstance, uint32_t creationTimeMs = millis());
|
||||
|
||||
void setMeshInstance(EspnowMeshBackend &meshInstance);
|
||||
EspnowMeshBackend &getMeshInstance();
|
||||
|
||||
private:
|
||||
|
||||
EspnowMeshBackend &_meshInstance;
|
||||
};
|
||||
|
||||
#endif
|
79
libraries/ESP8266WiFiMesh/src/ResponseData.cpp
Normal file
79
libraries/ESP8266WiFiMesh/src/ResponseData.cpp
Normal file
@ -0,0 +1,79 @@
|
||||
/*
|
||||
* 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 "ResponseData.h"
|
||||
|
||||
ResponseData::ResponseData(const String &message, const uint8_t recipientMac[6], uint64_t requestID, uint32_t creationTimeMs) :
|
||||
TimeTracker(creationTimeMs), _message(message), _requestID(requestID)
|
||||
{
|
||||
storeRecipientMac(recipientMac);
|
||||
}
|
||||
|
||||
ResponseData::ResponseData(const ResponseData &other)
|
||||
: TimeTracker(other), _message(other.getMessage()), _requestID(other.getRequestID())
|
||||
{
|
||||
storeRecipientMac(other.getRecipientMac());
|
||||
}
|
||||
|
||||
ResponseData & ResponseData::operator=(const ResponseData &other)
|
||||
{
|
||||
if(this != &other)
|
||||
{
|
||||
TimeTracker::operator=(other);
|
||||
_message = other.getMessage();
|
||||
_requestID = other.getRequestID();
|
||||
storeRecipientMac(other.getRecipientMac());
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
void ResponseData::storeRecipientMac(const uint8_t newRecipientMac[6])
|
||||
{
|
||||
if(newRecipientMac != nullptr)
|
||||
{
|
||||
if(_recipientMac == nullptr)
|
||||
{
|
||||
_recipientMac = _recipientMacArray;
|
||||
}
|
||||
|
||||
for(int i = 0; i < 6; i++)
|
||||
{
|
||||
_recipientMac[i] = newRecipientMac[i];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_recipientMac = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void ResponseData::setRecipientMac(const uint8_t recipientMac[6]) { storeRecipientMac(recipientMac); }
|
||||
const uint8_t *ResponseData::getRecipientMac() const { return _recipientMac; }
|
||||
|
||||
void ResponseData::setMessage(String &message) { _message = message; }
|
||||
String ResponseData::getMessage() const { return _message; }
|
||||
|
||||
void ResponseData::setRequestID(uint64_t requestID) { _requestID = requestID; }
|
||||
uint64_t ResponseData::getRequestID() const { return _requestID; }
|
59
libraries/ESP8266WiFiMesh/src/ResponseData.h
Normal file
59
libraries/ESP8266WiFiMesh/src/ResponseData.h
Normal file
@ -0,0 +1,59 @@
|
||||
/*
|
||||
* 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 __ESPNOWRESPONSEDATA_H__
|
||||
#define __ESPNOWRESPONSEDATA_H__
|
||||
|
||||
#include "TimeTracker.h"
|
||||
#include <Arduino.h>
|
||||
|
||||
class ResponseData : public TimeTracker {
|
||||
|
||||
public:
|
||||
|
||||
ResponseData(const String &message, const uint8_t recipientMac[6], uint64_t requestID, uint32_t creationTimeMs = millis());
|
||||
ResponseData(const ResponseData &other);
|
||||
ResponseData & operator=(const ResponseData &other);
|
||||
// No need for explicit destructor with current class design
|
||||
|
||||
void setRecipientMac(const uint8_t recipientMac[6]);
|
||||
const uint8_t *getRecipientMac() const;
|
||||
|
||||
void setMessage(String &message);
|
||||
String getMessage() const;
|
||||
|
||||
void setRequestID(uint64_t requestID);
|
||||
uint64_t getRequestID() const;
|
||||
|
||||
private:
|
||||
|
||||
void storeRecipientMac(const uint8_t newRecipientMac[6]);
|
||||
|
||||
uint8_t _recipientMacArray[6] {0};
|
||||
uint8_t *_recipientMac = nullptr;
|
||||
String _message = "";
|
||||
uint64_t _requestID = 0;
|
||||
};
|
||||
|
||||
#endif
|
469
libraries/ESP8266WiFiMesh/src/TcpIpMeshBackend.cpp
Normal file
469
libraries/ESP8266WiFiMesh/src/TcpIpMeshBackend.cpp
Normal file
@ -0,0 +1,469 @@
|
||||
/*
|
||||
TcpIpMeshBackend
|
||||
|
||||
Copyright (c) 2015 Julian Fell and 2019 Anders Löfgren. All rights reserved.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#include <WiFiClient.h>
|
||||
#include <WiFiServer.h>
|
||||
#include <assert.h>
|
||||
#include <Schedule.h>
|
||||
|
||||
#include "TcpIpMeshBackend.h"
|
||||
#include "TypeConversionFunctions.h"
|
||||
#include "MutexTracker.h"
|
||||
|
||||
#define SERVER_IP_ADDR "192.168.4.1"
|
||||
|
||||
const IPAddress TcpIpMeshBackend::emptyIP = IPAddress();
|
||||
|
||||
bool TcpIpMeshBackend::_tcpIpTransmissionMutex = false;
|
||||
|
||||
String TcpIpMeshBackend::lastSSID = "";
|
||||
bool TcpIpMeshBackend::staticIPActivated = false;
|
||||
|
||||
// IP needs to be at the same subnet as server gateway (192.168.4 in this case). Station gateway ip must match ip for server.
|
||||
IPAddress TcpIpMeshBackend::staticIP = emptyIP;
|
||||
IPAddress TcpIpMeshBackend::gateway = IPAddress(192,168,4,1);
|
||||
IPAddress TcpIpMeshBackend::subnetMask = IPAddress(255,255,255,0);
|
||||
|
||||
TcpIpMeshBackend::TcpIpMeshBackend(requestHandlerType requestHandler, responseHandlerType responseHandler,
|
||||
networkFilterType networkFilter, const String &meshPassword, const String &ssidPrefix,
|
||||
const String &ssidSuffix, bool verboseMode, uint8 meshWiFiChannel, uint16_t serverPort)
|
||||
: MeshBackendBase(requestHandler, responseHandler, networkFilter, MB_TCP_IP), _server(serverPort)
|
||||
{
|
||||
setSSID(ssidPrefix, "", ssidSuffix);
|
||||
setMeshPassword(meshPassword);
|
||||
setVerboseModeState(verboseMode);
|
||||
setWiFiChannel(meshWiFiChannel);
|
||||
setServerPort(serverPort);
|
||||
}
|
||||
|
||||
void TcpIpMeshBackend::begin()
|
||||
{
|
||||
if(!TcpIpMeshBackend::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.
|
||||
|
||||
#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
|
||||
}
|
||||
|
||||
void TcpIpMeshBackend::activateAPHook()
|
||||
{
|
||||
WiFi.softAP( getSSID().c_str(), getMeshPassword().c_str(), getWiFiChannel(), getAPHidden(), _maxAPStations ); // Note that a maximum of 8 TCP/IP stations can be connected at a time to each AP, max 4 by default.
|
||||
|
||||
_server = WiFiServer(getServerPort()); // Fixes an occasional crash bug that occurs when using the copy constructor to duplicate the AP controller.
|
||||
_server.begin(); // Actually calls _server.stop()/_server.close() first.
|
||||
}
|
||||
|
||||
void TcpIpMeshBackend::deactivateAPHook()
|
||||
{
|
||||
_server.stop();
|
||||
}
|
||||
|
||||
bool TcpIpMeshBackend::transmissionInProgress(){return _tcpIpTransmissionMutex;}
|
||||
|
||||
void TcpIpMeshBackend::setStaticIP(const IPAddress &newIP)
|
||||
{
|
||||
// Comment out the line below to remove static IP and use DHCP instead.
|
||||
// DHCP makes WiFi connection happen slower, but there is no need to care about manually giving different IPs to the nodes and less need to worry about used IPs giving "Server unavailable" issues.
|
||||
// Static IP has faster connection times (50 % of DHCP) and will make sending of data to a node that is already transmitting data happen more reliably.
|
||||
// Note that after WiFi.config(staticIP, gateway, subnetMask) is used, static IP will always be active, even for new connections, unless WiFi.config(0u,0u,0u); is called.
|
||||
WiFi.config(newIP, gateway, subnetMask);
|
||||
staticIPActivated = true;
|
||||
staticIP = newIP;
|
||||
}
|
||||
|
||||
IPAddress TcpIpMeshBackend::getStaticIP()
|
||||
{
|
||||
if(staticIPActivated)
|
||||
return staticIP;
|
||||
|
||||
return emptyIP;
|
||||
}
|
||||
|
||||
void TcpIpMeshBackend::disableStaticIP()
|
||||
{
|
||||
WiFi.config(0u,0u,0u);
|
||||
yield();
|
||||
staticIPActivated = false;
|
||||
}
|
||||
|
||||
void TcpIpMeshBackend::setServerPort(uint16_t serverPort)
|
||||
{
|
||||
_serverPort = serverPort;
|
||||
|
||||
// Apply changes to active AP.
|
||||
if(isAPController())
|
||||
restartAP();
|
||||
}
|
||||
|
||||
uint16_t TcpIpMeshBackend::getServerPort() {return _serverPort;}
|
||||
|
||||
void TcpIpMeshBackend::setMaxAPStations(uint8_t maxAPStations)
|
||||
{
|
||||
assert(maxAPStations <= 8); // Valid values are 0 to 8, but uint8_t is always at least 0.
|
||||
|
||||
if(_maxAPStations != maxAPStations)
|
||||
{
|
||||
_maxAPStations = maxAPStations;
|
||||
|
||||
// Apply changes to active AP.
|
||||
if(isAPController())
|
||||
restartAP();
|
||||
}
|
||||
}
|
||||
|
||||
bool TcpIpMeshBackend::getMaxAPStations() {return _maxAPStations;}
|
||||
|
||||
void TcpIpMeshBackend::setConnectionAttemptTimeout(int32_t connectionAttemptTimeoutMs)
|
||||
{
|
||||
_connectionAttemptTimeoutMs = connectionAttemptTimeoutMs;
|
||||
}
|
||||
|
||||
int32_t TcpIpMeshBackend::getConnectionAttemptTimeout() {return _connectionAttemptTimeoutMs;}
|
||||
|
||||
void TcpIpMeshBackend::setStationModeTimeout(int stationModeTimeoutMs)
|
||||
{
|
||||
_stationModeTimeoutMs = stationModeTimeoutMs;
|
||||
}
|
||||
|
||||
int TcpIpMeshBackend::getStationModeTimeout() {return _stationModeTimeoutMs;}
|
||||
|
||||
void TcpIpMeshBackend::setAPModeTimeout(uint32_t apModeTimeoutMs)
|
||||
{
|
||||
_apModeTimeoutMs = apModeTimeoutMs;
|
||||
}
|
||||
|
||||
uint32_t TcpIpMeshBackend::getAPModeTimeout() {return _apModeTimeoutMs;}
|
||||
|
||||
/**
|
||||
* Disconnect completely from a network.
|
||||
*/
|
||||
void TcpIpMeshBackend::fullStop(WiFiClient &currClient)
|
||||
{
|
||||
currClient.stop();
|
||||
yield();
|
||||
WiFi.disconnect();
|
||||
yield();
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for a WiFiClient to transmit
|
||||
*
|
||||
* @returns: True if the client is ready, false otherwise.
|
||||
*
|
||||
*/
|
||||
bool TcpIpMeshBackend::waitForClientTransmission(WiFiClient &currClient, uint32_t maxWait)
|
||||
{
|
||||
uint32_t connectionStartTime = millis();
|
||||
uint32_t waitingTime = millis() - connectionStartTime;
|
||||
while(currClient.connected() && !currClient.available() && waitingTime < maxWait)
|
||||
{
|
||||
delay(1);
|
||||
waitingTime = millis() - connectionStartTime;
|
||||
}
|
||||
|
||||
/* Return false if the client isn't ready to communicate */
|
||||
if (WiFi.status() == WL_DISCONNECTED && !currClient.available())
|
||||
{
|
||||
verboseModePrint(F("Disconnected!"));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the mesh instance's current message then read back the other node's response
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
transmission_status_t TcpIpMeshBackend::exchangeInfo(WiFiClient &currClient)
|
||||
{
|
||||
verboseModePrint("Transmitting"); // Not storing strings in flash (via F()) to avoid performance impacts when using the string.
|
||||
|
||||
currClient.print(getMessage() + "\r");
|
||||
yield();
|
||||
|
||||
if (!waitForClientTransmission(currClient, _stationModeTimeoutMs))
|
||||
{
|
||||
fullStop(currClient);
|
||||
return TS_CONNECTION_FAILED;
|
||||
}
|
||||
|
||||
if (!currClient.available())
|
||||
{
|
||||
verboseModePrint(F("No response!"));
|
||||
return TS_TRANSMISSION_FAILED; // WiFi.status() != WL_DISCONNECTED so we do not want to use fullStop(currClient) here since that would force the node to scan for WiFi networks.
|
||||
}
|
||||
|
||||
String response = currClient.readStringUntil('\r');
|
||||
yield();
|
||||
currClient.flush();
|
||||
|
||||
/* Pass data to user callback */
|
||||
return getResponseHandler()(response, *this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle data transfer process with a connected AP.
|
||||
*
|
||||
* @returns: A status code based on the outcome of the data transfer attempt.
|
||||
*/
|
||||
transmission_status_t TcpIpMeshBackend::attemptDataTransfer()
|
||||
{
|
||||
// Unlike WiFi.mode(WIFI_AP);, WiFi.mode(WIFI_AP_STA); allows us to stay connected to the AP we connected to in STA mode, at the same time as we can receive connections from other stations.
|
||||
// We cannot send data to the AP in STA_AP mode though, that requires STA mode.
|
||||
// Switching to STA mode will disconnect all stations connected to the node AP (though they can request a reconnect even while we are in STA mode).
|
||||
WiFiMode_t storedWiFiMode = WiFi.getMode();
|
||||
WiFi.mode(WIFI_STA);
|
||||
delay(1);
|
||||
transmission_status_t transmissionOutcome = attemptDataTransferKernel();
|
||||
WiFi.mode(storedWiFiMode);
|
||||
delay(1);
|
||||
|
||||
return transmissionOutcome;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function that contains the core functionality for the data transfer process with a connected AP.
|
||||
*
|
||||
* @returns: A status code based on the outcome of the data transfer attempt.
|
||||
*/
|
||||
transmission_status_t TcpIpMeshBackend::attemptDataTransferKernel()
|
||||
{
|
||||
WiFiClient currClient;
|
||||
currClient.setTimeout(_stationModeTimeoutMs);
|
||||
|
||||
/* Connect to the node's server */
|
||||
if (!currClient.connect(SERVER_IP_ADDR, getServerPort()))
|
||||
{
|
||||
fullStop(currClient);
|
||||
verboseModePrint(F("Server unavailable"));
|
||||
return TS_CONNECTION_FAILED;
|
||||
}
|
||||
|
||||
transmission_status_t transmissionOutcome = exchangeInfo(currClient);
|
||||
if (transmissionOutcome <= 0)
|
||||
{
|
||||
verboseModePrint(F("Transmission failed during exchangeInfo."));
|
||||
return transmissionOutcome;
|
||||
}
|
||||
|
||||
currClient.stop();
|
||||
yield();
|
||||
|
||||
return transmissionOutcome;
|
||||
}
|
||||
|
||||
void TcpIpMeshBackend::initiateConnectionToAP(const String &targetSSID, int targetChannel, uint8_t *targetBSSID)
|
||||
{
|
||||
if(targetChannel == NETWORK_INFO_DEFAULT_INT)
|
||||
WiFi.begin( targetSSID.c_str(), getMeshPassword().c_str() ); // Without giving channel and BSSID, connection time is longer.
|
||||
else if(targetBSSID == NULL)
|
||||
WiFi.begin( targetSSID.c_str(), getMeshPassword().c_str(), targetChannel ); // Without giving channel and BSSID, connection time is longer.
|
||||
else
|
||||
WiFi.begin( targetSSID.c_str(), getMeshPassword().c_str(), targetChannel, targetBSSID );
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to the AP at SSID and transmit the mesh instance's current message.
|
||||
*
|
||||
* @param targetSSID The name of the AP the other node has set up.
|
||||
* @param targetChannel The WiFI channel of the AP the other node has set up.
|
||||
* @param targetBSSID The MAC address of the AP the other node has set up.
|
||||
* @returns: A status code based on the outcome of the connection and data transfer process.
|
||||
*
|
||||
*/
|
||||
transmission_status_t TcpIpMeshBackend::connectToNode(const String &targetSSID, int targetChannel, uint8_t *targetBSSID)
|
||||
{
|
||||
if(staticIPActivated && lastSSID != "" && lastSSID != targetSSID) // So we only do this once per connection, in case there is a performance impact.
|
||||
{
|
||||
#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;
|
||||
|
||||
verboseModePrint(F("Connecting... "), false);
|
||||
initiateConnectionToAP(targetSSID, targetChannel, targetBSSID);
|
||||
|
||||
int connectionStartTime = millis();
|
||||
int attemptNumber = 1;
|
||||
|
||||
int waitingTime = millis() - connectionStartTime;
|
||||
while((WiFi.status() == WL_DISCONNECTED) && waitingTime <= _connectionAttemptTimeoutMs)
|
||||
{
|
||||
if(waitingTime > attemptNumber * _connectionAttemptTimeoutMs) // _connectionAttemptTimeoutMs can be replaced (lowered) if you want to limit the time allowed for each connection attempt.
|
||||
{
|
||||
verboseModePrint(F("... "), false);
|
||||
WiFi.disconnect();
|
||||
yield();
|
||||
initiateConnectionToAP(targetSSID, targetChannel, targetBSSID);
|
||||
attemptNumber++;
|
||||
}
|
||||
delay(1);
|
||||
waitingTime = millis() - connectionStartTime;
|
||||
}
|
||||
|
||||
verboseModePrint(String(waitingTime));
|
||||
|
||||
/* If the connection timed out */
|
||||
if (WiFi.status() != WL_CONNECTED)
|
||||
{
|
||||
verboseModePrint(F("Timeout"));
|
||||
return TS_CONNECTION_FAILED;
|
||||
}
|
||||
|
||||
return attemptDataTransfer();
|
||||
}
|
||||
|
||||
void TcpIpMeshBackend::attemptTransmission(const String &message, bool scan, bool scanAllWiFiChannels, bool concludingDisconnect, bool initialDisconnect )
|
||||
{
|
||||
MutexTracker mutexTracker(_tcpIpTransmissionMutex);
|
||||
if(!mutexTracker.mutexCaptured())
|
||||
{
|
||||
assert(false && "ERROR! TCP/IP transmission in progress. Don't call attemptTransmission from callbacks as this may corrupt program state! Aborting.");
|
||||
return;
|
||||
}
|
||||
|
||||
if(initialDisconnect)
|
||||
{
|
||||
WiFi.disconnect();
|
||||
yield();
|
||||
}
|
||||
|
||||
setMessage(message);
|
||||
|
||||
latestTransmissionOutcomes.clear();
|
||||
|
||||
if(WiFi.status() == WL_CONNECTED)
|
||||
{
|
||||
transmission_status_t transmissionResult = attemptDataTransfer();
|
||||
latestTransmissionOutcomes.push_back(TransmissionResult(connectionQueue.back(), transmissionResult));
|
||||
}
|
||||
else
|
||||
{
|
||||
if(scan)
|
||||
{
|
||||
scanForNetworks(scanAllWiFiChannels);
|
||||
}
|
||||
|
||||
for(NetworkInfo ¤tNetwork : connectionQueue)
|
||||
{
|
||||
WiFi.disconnect();
|
||||
yield();
|
||||
|
||||
String currentSSID = "";
|
||||
int currentWiFiChannel = NETWORK_INFO_DEFAULT_INT;
|
||||
uint8_t *currentBSSID = NULL;
|
||||
|
||||
// If an SSID has been assigned, it is prioritized over an assigned networkIndex since the networkIndex is more likely to change.
|
||||
if(currentNetwork.SSID != "")
|
||||
{
|
||||
currentSSID = currentNetwork.SSID;
|
||||
currentWiFiChannel = currentNetwork.wifiChannel;
|
||||
currentBSSID = currentNetwork.BSSID;
|
||||
}
|
||||
else // Use only networkIndex
|
||||
{
|
||||
currentSSID = WiFi.SSID(currentNetwork.networkIndex);
|
||||
currentWiFiChannel = WiFi.channel(currentNetwork.networkIndex);
|
||||
currentBSSID = WiFi.BSSID(currentNetwork.networkIndex);
|
||||
}
|
||||
|
||||
if(verboseMode()) // Avoid string generation if not required
|
||||
{
|
||||
printAPInfo(currentNetwork.networkIndex, currentSSID, currentWiFiChannel);
|
||||
}
|
||||
|
||||
transmission_status_t transmissionResult = connectToNode(currentSSID, currentWiFiChannel, currentBSSID);
|
||||
|
||||
latestTransmissionOutcomes.push_back(TransmissionResult{.origin = currentNetwork, .transmissionStatus = transmissionResult});
|
||||
}
|
||||
}
|
||||
|
||||
if(WiFi.status() == WL_CONNECTED && staticIP != emptyIP && !staticIPActivated)
|
||||
{
|
||||
verboseModePrint(F("Reactivating static IP to allow for faster re-connects."));
|
||||
setStaticIP(staticIP);
|
||||
}
|
||||
|
||||
// If we do not want to be connected at end of transmission, disconnect here so we can re-enable static IP first (above).
|
||||
if(concludingDisconnect)
|
||||
{
|
||||
WiFi.disconnect();
|
||||
yield();
|
||||
}
|
||||
}
|
||||
|
||||
void TcpIpMeshBackend::attemptTransmission(const String &message, bool scan, bool scanAllWiFiChannels)
|
||||
{
|
||||
attemptTransmission(message, scan, scanAllWiFiChannels, true, false);
|
||||
}
|
||||
|
||||
void TcpIpMeshBackend::acceptRequest()
|
||||
{
|
||||
MutexTracker mutexTracker(_tcpIpTransmissionMutex);
|
||||
if(!mutexTracker.mutexCaptured())
|
||||
{
|
||||
assert(false && "ERROR! TCP/IP transmission in progress. Don't call acceptRequest from TCP/IP callbacks as this may corrupt program state! Aborting.");
|
||||
return;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
WiFiClient _client = _server.available();
|
||||
|
||||
if (!_client)
|
||||
break;
|
||||
|
||||
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 = getRequestHandler()(request, *this);
|
||||
|
||||
/* Send the response back to the client */
|
||||
if (_client.connected())
|
||||
{
|
||||
verboseModePrint("Responding"); // Not storing strings in flash (via F()) to avoid performance impacts when using the string.
|
||||
_client.print(response + "\r");
|
||||
_client.flush();
|
||||
yield();
|
||||
}
|
||||
}
|
||||
}
|
214
libraries/ESP8266WiFiMesh/src/TcpIpMeshBackend.h
Normal file
214
libraries/ESP8266WiFiMesh/src/TcpIpMeshBackend.h
Normal file
@ -0,0 +1,214 @@
|
||||
/*
|
||||
TcpIpMeshBackend
|
||||
|
||||
Copyright (c) 2015 Julian Fell and 2019 Anders Löfgren. All rights reserved.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
// ESP-NOW is faster for small data payloads (up to a few kB, split over multiple messages). Transfer of up to 234 bytes takes 4 ms.
|
||||
// In general ESP-NOW transfer time can be approximated with the following function: transferTime = ceil(bytesToTransfer / 234.0)*3 ms.
|
||||
// If you only transfer 234 bytes at a time, this adds up to around 56kB/s. Finally a chance to relive the glory of the olden days
|
||||
// when people were restricted to V90 dial-up modems for internet access!
|
||||
// TCP-IP takes longer to connect (around 1000 ms), and an AP has to disconnect all connected stations in order to transfer data to another AP,
|
||||
// but this backend has a much higher data transfer speed than ESP-NOW once connected (100x faster or so).
|
||||
|
||||
#ifndef __TCPIPMESHBACKEND_H__
|
||||
#define __TCPIPMESHBACKEND_H__
|
||||
|
||||
#include <WiFiClient.h>
|
||||
#include <WiFiServer.h>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
#include "MeshBackendBase.h"
|
||||
#include "NetworkInfo.h"
|
||||
|
||||
class TcpIpMeshBackend : public MeshBackendBase {
|
||||
|
||||
public:
|
||||
|
||||
/**
|
||||
* 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 ssidPrefix The prefix (first part) of the node SSID.
|
||||
* @param ssidSuffix The suffix (last part) of the node SSID.
|
||||
* @param verboseMode Determines if we should print the events occurring in the library to Serial. Off by default. This setting is separate for each TcpIpMeshBackend instance.
|
||||
* @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 mesh instances exist on the same ESP8266 and use different WiFi channels.
|
||||
* In such a case, whenever the station of one mesh 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 both by the AP of the TcpIpMeshBackend instance and when the instance connects to other APs.
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
TcpIpMeshBackend(requestHandlerType requestHandler, responseHandlerType responseHandler, networkFilterType networkFilter,
|
||||
const String &meshPassword, const String &ssidPrefix, const String &ssidSuffix, bool verboseMode = false,
|
||||
uint8 meshWiFiChannel = 1, uint16_t serverPort = 4011);
|
||||
|
||||
/**
|
||||
* Initialises the node.
|
||||
*/
|
||||
void begin() override;
|
||||
|
||||
/**
|
||||
* If AP connection already exists, and the initialDisconnect argument is set to false, send message only to the already connected AP.
|
||||
* Otherwise, scan for other networks, send the scan result to networkFilter and then transmit the message to the networks found in connectionQueue.
|
||||
*
|
||||
* @param message The message to send to other nodes. It will be stored in the class instance until replaced via attemptTransmission or setMessage.
|
||||
* @param concludingDisconnect Disconnect from AP once transmission is complete. Defaults to true.
|
||||
* @param initialDisconnect Disconnect from any currently connected AP before attempting transmission. Defaults to false.
|
||||
* @param scan Scan for new networks and call the networkFilter function with the scan results. When set to false, only the data already in connectionQueue will be used for the transmission.
|
||||
* @param scanAllWiFiChannels Scan all WiFi channels during a WiFi scan, instead of just the channel the MeshBackendBase instance is using.
|
||||
* Scanning all WiFi channels takes about 2100 ms, compared to just 60 ms if only channel 1 (standard) is scanned.
|
||||
* Note that if the ESP8266 has an active AP, that AP will switch WiFi channel to match that of any other AP the ESP8266 connects to.
|
||||
* This can make it impossible for other nodes to detect the AP if they are scanning the wrong WiFi channel.
|
||||
*/
|
||||
void attemptTransmission(const String &message, bool scan, bool scanAllWiFiChannels, bool concludingDisconnect, bool initialDisconnect = false);
|
||||
|
||||
void attemptTransmission(const String &message, bool scan = true, bool scanAllWiFiChannels = false) override;
|
||||
|
||||
/**
|
||||
* If any clients are connected, accept their requests and call the requestHandler function for each one.
|
||||
*/
|
||||
void acceptRequest();
|
||||
|
||||
/**
|
||||
* Set a static IP address for the ESP8266 and activate use of static IP.
|
||||
* The static IP needs to be at the same subnet as the server's gateway.
|
||||
*/
|
||||
void setStaticIP(const IPAddress &newIP);
|
||||
IPAddress getStaticIP();
|
||||
void disableStaticIP();
|
||||
|
||||
/**
|
||||
* An empty IPAddress. Used as default when no IP is set.
|
||||
*/
|
||||
static const IPAddress emptyIP;
|
||||
|
||||
/**
|
||||
* Set the server port used both by the AP of the TcpIpMeshBackend instance and when the instance connects to other APs.
|
||||
* 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.
|
||||
* Will also change the setting for the active AP if this TcpIpMeshBackend instance is the current AP controller.
|
||||
*
|
||||
* @param serverPort The server port to use.
|
||||
*
|
||||
*/
|
||||
void setServerPort(uint16_t serverPort);
|
||||
uint16_t getServerPort();
|
||||
|
||||
/**
|
||||
* Set the maximum number of stations that can simultaneously be connected to the AP controlled by this TcpIpMeshBackend instance.
|
||||
* This number is 4 by default.
|
||||
* Once the max number has been reached, any other station that wants to connect will be forced to wait until an already connected station disconnects.
|
||||
* The more stations that are connected, the more memory is required.
|
||||
* Will also change the setting for the active AP if this TcpIpMeshBackend instance is the current AP controller.
|
||||
*
|
||||
* @param maxAPStations The maximum number of simultaneous station connections allowed. Valid values are 0 to 8.
|
||||
*/
|
||||
void setMaxAPStations(uint8_t maxAPStations);
|
||||
bool getMaxAPStations();
|
||||
|
||||
/**
|
||||
* Set the timeout for each attempt to connect to another AP that occurs through the attemptTransmission method by this TcpIpMeshBackend instance.
|
||||
* The timeout is 10 000 ms by default.
|
||||
*
|
||||
* @param connectionAttemptTimeoutMs The timeout for each connection attempt, in milliseconds.
|
||||
*/
|
||||
void setConnectionAttemptTimeout(int32_t connectionAttemptTimeoutMs);
|
||||
int32_t getConnectionAttemptTimeout();
|
||||
|
||||
/**
|
||||
* Set the timeout to use for transmissions when this TcpIpMeshBackend instance acts as a station (i.e. when connected to another AP).
|
||||
* This will affect the timeout of the attemptTransmission method once a connection to an AP has been established.
|
||||
* The timeout is 5 000 ms by default.
|
||||
*
|
||||
* @param stationModeTimeoutMs The timeout to use, in milliseconds.
|
||||
*/
|
||||
void setStationModeTimeout(int stationModeTimeoutMs);
|
||||
int getStationModeTimeout();
|
||||
|
||||
/**
|
||||
* Set the timeout to use for transmissions when this TcpIpMeshBackend instance acts as an AP (i.e. when receiving connections from other stations).
|
||||
* This will affect the timeout of the acceptRequest method.
|
||||
* The timeout is 4 500 ms by default.
|
||||
* Will also change the setting for the active AP if this TcpIpMeshBackend instance is the current AP controller.
|
||||
*
|
||||
* @param apModeTimeoutMs The timeout to use, in milliseconds.
|
||||
*/
|
||||
void setAPModeTimeout(uint32_t apModeTimeoutMs);
|
||||
uint32_t getAPModeTimeout();
|
||||
|
||||
protected:
|
||||
|
||||
/**
|
||||
* Called just before we activate the AP.
|
||||
* Put _server.stop() in deactivateAPHook() in case you use _server.begin() here.
|
||||
*/
|
||||
void activateAPHook() override;
|
||||
|
||||
/**
|
||||
* Called just before we deactivate the AP.
|
||||
* Put _server.stop() here in case you use _server.begin() in activateAPHook().
|
||||
*/
|
||||
void deactivateAPHook() override;
|
||||
|
||||
/**
|
||||
* Will be true if a transmission initiated by a public method is in progress.
|
||||
*/
|
||||
static bool _tcpIpTransmissionMutex;
|
||||
|
||||
/**
|
||||
* Check if there is an ongoing TCP/IP transmission in the library. Used to avoid interrupting transmissions.
|
||||
*
|
||||
* @returns True if a transmission initiated by a public method is in progress.
|
||||
*/
|
||||
static bool transmissionInProgress();
|
||||
|
||||
private:
|
||||
|
||||
uint16_t _serverPort;
|
||||
WiFiServer _server;
|
||||
uint8_t _maxAPStations = 4; // Only affects TCP/IP connections, not ESP-NOW connections
|
||||
int32_t _connectionAttemptTimeoutMs = 10000;
|
||||
int _stationModeTimeoutMs = 5000; // int is the type used in the Arduino core for this particular API, not uint32_t, which is why we use int here.
|
||||
uint32_t _apModeTimeoutMs = 4500;
|
||||
|
||||
static String lastSSID;
|
||||
static bool staticIPActivated;
|
||||
bool useStaticIP;
|
||||
static IPAddress staticIP;
|
||||
static IPAddress gateway;
|
||||
static IPAddress subnetMask;
|
||||
|
||||
void fullStop(WiFiClient &currClient);
|
||||
void initiateConnectionToAP(const String &targetSSID, int targetChannel = NETWORK_INFO_DEFAULT_INT, uint8_t *targetBSSID = NULL);
|
||||
transmission_status_t connectToNode(const String &targetSSID, int targetChannel = NETWORK_INFO_DEFAULT_INT, uint8_t *targetBSSID = NULL);
|
||||
transmission_status_t exchangeInfo(WiFiClient &currClient);
|
||||
bool waitForClientTransmission(WiFiClient &currClient, uint32_t maxWait);
|
||||
transmission_status_t attemptDataTransfer();
|
||||
transmission_status_t attemptDataTransferKernel();
|
||||
};
|
||||
|
||||
#endif
|
39
libraries/ESP8266WiFiMesh/src/TimeTracker.cpp
Normal file
39
libraries/ESP8266WiFiMesh/src/TimeTracker.cpp
Normal file
@ -0,0 +1,39 @@
|
||||
/*
|
||||
* 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 "TimeTracker.h"
|
||||
#include <Arduino.h>
|
||||
|
||||
TimeTracker::TimeTracker(uint32_t creationTimeMs) : _creationTimeMs(creationTimeMs)
|
||||
{ }
|
||||
|
||||
uint32_t TimeTracker::timeSinceCreation() const
|
||||
{
|
||||
return millis() - creationTimeMs(); // Will work even when millis() overflow: http://forum.arduino.cc/index.php/topic,42997.0.html
|
||||
}
|
||||
|
||||
uint32_t TimeTracker::creationTimeMs() const
|
||||
{
|
||||
return _creationTimeMs;
|
||||
}
|
45
libraries/ESP8266WiFiMesh/src/TimeTracker.h
Normal file
45
libraries/ESP8266WiFiMesh/src/TimeTracker.h
Normal file
@ -0,0 +1,45 @@
|
||||
/*
|
||||
* 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 __TIMETRACKER_H__
|
||||
#define __TIMETRACKER_H__
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
class TimeTracker {
|
||||
|
||||
public:
|
||||
|
||||
virtual ~TimeTracker() = default;
|
||||
|
||||
TimeTracker(uint32_t creationTimeMs);
|
||||
uint32_t timeSinceCreation() const;
|
||||
uint32_t creationTimeMs() const;
|
||||
|
||||
private:
|
||||
|
||||
uint32_t _creationTimeMs;
|
||||
};
|
||||
|
||||
#endif
|
@ -24,6 +24,7 @@
|
||||
*/
|
||||
|
||||
#include "TypeConversionFunctions.h"
|
||||
#include "Crypto.h"
|
||||
|
||||
String uint64ToString(uint64_t number, byte base)
|
||||
{
|
||||
@ -56,3 +57,89 @@ uint64_t stringToUint64(const String &string, byte base)
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
String uint8ArrayToHexString(const uint8_t *uint8Array, uint32_t arrayLength)
|
||||
{
|
||||
char hexString[2*arrayLength + 1]; // Each uint8_t will become two characters (00 to FF) and we want a null terminated char array.
|
||||
hexString[arrayLength + 1] = { 0 };
|
||||
for(uint32_t i = 0; i < arrayLength; i++)
|
||||
{
|
||||
sprintf(hexString + 2*i, "%02X", uint8Array[i]);
|
||||
}
|
||||
|
||||
return String(hexString);
|
||||
}
|
||||
|
||||
uint8_t *hexStringToUint8Array(const String &hexString, uint8_t *uint8Array, uint32_t arrayLength)
|
||||
{
|
||||
assert(hexString.length() >= arrayLength*2); // Each array element can hold two hexString characters
|
||||
|
||||
for(uint32_t i = 0; i < arrayLength; i++)
|
||||
{
|
||||
uint8Array[i] = strtoul(hexString.substring(i*2, (i+1)*2).c_str(), nullptr, 16);
|
||||
}
|
||||
|
||||
return uint8Array;
|
||||
}
|
||||
|
||||
String macToString(const uint8_t *mac)
|
||||
{
|
||||
char macString[13] = { 0 };
|
||||
sprintf(macString, "%02X%02X%02X%02X%02X%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
|
||||
return String(macString);
|
||||
}
|
||||
|
||||
uint8_t *stringToMac(const String &macString, uint8_t *macArray)
|
||||
{
|
||||
return hexStringToUint8Array(macString, macArray, 6);
|
||||
}
|
||||
|
||||
uint64_t macToUint64(const uint8_t *macArray)
|
||||
{
|
||||
uint64_t outcome = 0;
|
||||
for(int shiftingFortune = 40; shiftingFortune >= 0; shiftingFortune -= 8)
|
||||
{
|
||||
outcome |= ((uint64_t)macArray[5 - shiftingFortune/8] << shiftingFortune);
|
||||
}
|
||||
|
||||
return outcome;
|
||||
}
|
||||
|
||||
uint8_t *uint64ToMac(uint64_t macValue, uint8_t *macArray)
|
||||
{
|
||||
assert(macValue <= 0xFFFFFFFFFFFF); // Overflow will occur if value can't fit within 6 bytes
|
||||
|
||||
for(int shiftingFortune = 40; shiftingFortune >= 0; shiftingFortune -= 8)
|
||||
{
|
||||
macArray[5 - shiftingFortune/8] = macValue >> shiftingFortune & 0xFF;
|
||||
}
|
||||
return macArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function for meshBackendCast.
|
||||
*/
|
||||
template <typename T>
|
||||
T attemptPointerCast(MeshBackendBase *meshBackendBaseInstance, mesh_backend_t resultClassType)
|
||||
{
|
||||
if(meshBackendBaseInstance && meshBackendBaseInstance->getClassType() == resultClassType)
|
||||
{
|
||||
return static_cast<T>(meshBackendBaseInstance);
|
||||
}
|
||||
else
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
EspnowMeshBackend *meshBackendCast<EspnowMeshBackend *>(MeshBackendBase *meshBackendBaseInstance)
|
||||
{
|
||||
return attemptPointerCast<EspnowMeshBackend *>(meshBackendBaseInstance, MB_ESP_NOW);
|
||||
}
|
||||
|
||||
template <>
|
||||
TcpIpMeshBackend *meshBackendCast<TcpIpMeshBackend *>(MeshBackendBase *meshBackendBaseInstance)
|
||||
{
|
||||
return attemptPointerCast<TcpIpMeshBackend *>(meshBackendBaseInstance, MB_TCP_IP);
|
||||
}
|
||||
|
@ -28,6 +28,9 @@
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <assert.h>
|
||||
#include "MeshBackendBase.h"
|
||||
#include "TcpIpMeshBackend.h"
|
||||
#include "EspnowMeshBackend.h"
|
||||
|
||||
/**
|
||||
* Note that using a base higher than 16 increases likelihood of randomly generating SSID strings containing controversial words.
|
||||
@ -47,4 +50,66 @@ String uint64ToString(uint64_t number, byte base = 16);
|
||||
*/
|
||||
uint64_t stringToUint64(const String &string, byte base = 16);
|
||||
|
||||
// All array elements will be padded with zeroes to ensure they are converted to 2 string characters each.
|
||||
String uint8ArrayToHexString(const uint8_t *uint8Array, uint32_t arrayLength);
|
||||
|
||||
// There must be 2 string characters for each array element. Use padding with zeroes where required.
|
||||
uint8_t *hexStringToUint8Array(const String &hexString, uint8_t *uint8Array, uint32_t arrayLength);
|
||||
|
||||
/**
|
||||
* Takes a uint8_t array and converts the first 6 bytes to a hexadecimal string.
|
||||
*
|
||||
* @param mac A uint8_t array with the mac address to convert to a string. Should be 6 bytes in total.
|
||||
* @returns A hexadecimal string representation of the mac.
|
||||
*/
|
||||
String macToString(const uint8_t *mac);
|
||||
|
||||
/**
|
||||
* Takes a String and converts the first 12 characters to uint8_t numbers which are stored in the macArray from low to high index. Assumes hexadecimal number encoding.
|
||||
*
|
||||
* @param macString A String which begins with the mac address to store in the array as a hexadecimal number.
|
||||
* @param macArray A uint8_t array that will hold the mac address once the function returns. Should have a size of at least 6 bytes.
|
||||
* @returns The macArray.
|
||||
*/
|
||||
uint8_t *stringToMac(const String &macString, uint8_t *macArray);
|
||||
|
||||
/**
|
||||
* Takes a uint8_t array and converts the first 6 bytes to a uint64_t. Assumes index 0 of the array contains MSB.
|
||||
*
|
||||
* @param macArray A uint8_t array with the mac address to convert to a uint64_t. Should be 6 bytes in total.
|
||||
* @returns A uint64_t representation of the mac.
|
||||
*/
|
||||
uint64_t macToUint64(const uint8_t *macArray);
|
||||
|
||||
/**
|
||||
* Takes a uint64_t value and stores the bits of the first 6 bytes in a uint8_t array. Assumes index 0 of the array should contain MSB.
|
||||
*
|
||||
* @param macValue The uint64_t value to convert to a mac array. Value must fit within 6 bytes.
|
||||
* @param macArray A uint8_t array that will hold the mac address once the function returns. Should have a size of at least 6 bytes.
|
||||
* @returns The macArray.
|
||||
*/
|
||||
uint8_t *uint64ToMac(uint64_t macValue, uint8_t *macArray);
|
||||
|
||||
/**
|
||||
* Conversion function that can be used on MeshBackend classes instead of dynamic_cast since RTTI is disabled.
|
||||
*
|
||||
* @param T The MeshBackend class pointer type to cast the meshBackendBaseInstance pointer into.
|
||||
* @param meshBackendBaseInstance The instance pointer to cast.
|
||||
* @returns A pointer of type T to meshBackendBaseInstance if meshBackendBaseInstance is of type T. nullptr otherwise.
|
||||
*/
|
||||
template <typename T>
|
||||
T meshBackendCast(MeshBackendBase *meshBackendBaseInstance)
|
||||
{
|
||||
// The only valid template arguments are handled by the template specializations below, so ending up here is an error.
|
||||
static_assert(std::is_same<T, EspnowMeshBackend *>::value || std::is_same<T, TcpIpMeshBackend *>::value,
|
||||
"Error: Invalid MeshBackend class type. Make sure the template argument to meshBackendCast is supported!");
|
||||
}
|
||||
|
||||
// These template specializations allow us to put the main template functionality in the .cpp file (which gives better encapsulation).
|
||||
template <>
|
||||
EspnowMeshBackend *meshBackendCast<EspnowMeshBackend *>(MeshBackendBase *meshBackendBaseInstance);
|
||||
|
||||
template <>
|
||||
TcpIpMeshBackend *meshBackendCast<TcpIpMeshBackend *>(MeshBackendBase *meshBackendBaseInstance);
|
||||
|
||||
#endif
|
||||
|
45
libraries/ESP8266WiFiMesh/src/UtilityFunctions.cpp
Normal file
45
libraries/ESP8266WiFiMesh/src/UtilityFunctions.cpp
Normal file
@ -0,0 +1,45 @@
|
||||
/*
|
||||
* UtilityFunctions
|
||||
* 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 "UtilityFunctions.h"
|
||||
#include <esp8266_peri.h>
|
||||
|
||||
bool macEqual(const uint8_t *macOne, const uint8_t *macTwo)
|
||||
{
|
||||
for(int i = 0; i <= 5; i++)
|
||||
{
|
||||
if(macOne[i] != macTwo[i])
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
uint64_t randomUint64()
|
||||
{
|
||||
return (((uint64_t)RANDOM_REG32 << 32) | (uint64_t)RANDOM_REG32);
|
||||
}
|
35
libraries/ESP8266WiFiMesh/src/UtilityFunctions.h
Normal file
35
libraries/ESP8266WiFiMesh/src/UtilityFunctions.h
Normal file
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* UtilityFunctions
|
||||
* 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 __UTILITYFUNCTIONS_H__
|
||||
#define __UTILITYFUNCTIONS_H__
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
bool macEqual(const uint8_t *macOne, const uint8_t *macTwo);
|
||||
|
||||
uint64_t randomUint64();
|
||||
|
||||
#endif
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* TransmissionResult
|
||||
* UtilityMethods
|
||||
* Copyright (C) 2018 Anders Löfgren
|
||||
*
|
||||
* License (MIT license):
|
||||
@ -24,11 +24,15 @@
|
||||
*/
|
||||
|
||||
#include "TypeConversionFunctions.h"
|
||||
#include "ESP8266WiFiMesh.h"
|
||||
#include "MeshBackendBase.h"
|
||||
#include "EspnowMeshBackend.h"
|
||||
|
||||
void ESP8266WiFiMesh::verboseModePrint(const String &stringToPrint, bool newline)
|
||||
void MeshBackendBase::setVerboseModeState(bool enabled) {_verboseMode = enabled;}
|
||||
bool MeshBackendBase::verboseMode() {return _verboseMode;}
|
||||
|
||||
void MeshBackendBase::verboseModePrint(const String &stringToPrint, bool newline)
|
||||
{
|
||||
if(_verboseMode)
|
||||
if(verboseMode())
|
||||
{
|
||||
if(newline)
|
||||
Serial.println(stringToPrint);
|
||||
@ -37,45 +41,43 @@ void ESP8266WiFiMesh::verboseModePrint(const String &stringToPrint, bool newline
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the current lwIP version number and store the numbers in the _lwipVersion array.
|
||||
* lwIP version can be changed in the "Tools" menu of Arduino IDE.
|
||||
*/
|
||||
void ESP8266WiFiMesh::storeLwipVersion()
|
||||
void EspnowMeshBackend::setVerboseModeState(bool enabled) {MeshBackendBase::setVerboseModeState(enabled); _staticVerboseMode = enabled;}
|
||||
bool EspnowMeshBackend::verboseMode() {return staticVerboseMode();}
|
||||
|
||||
void EspnowMeshBackend::verboseModePrint(const String &stringToPrint, bool newline)
|
||||
{
|
||||
// ESP.getFullVersion() looks something like:
|
||||
// SDK:2.2.1(cfd48f3)/Core:win-2.5.0-dev/lwIP:2.0.3(STABLE-2_0_3_RELEASE/glue:arduino-2.4.1-10-g0c0d8c2)/BearSSL:94e9704
|
||||
String fullVersion = ESP.getFullVersion();
|
||||
|
||||
int i = fullVersion.indexOf("lwIP:") + 5;
|
||||
char currentChar = fullVersion.charAt(i);
|
||||
|
||||
for(int versionPart = 0; versionPart < 3; versionPart++)
|
||||
if(verboseMode())
|
||||
{
|
||||
while(!isdigit(currentChar))
|
||||
{
|
||||
currentChar = fullVersion.charAt(++i);
|
||||
}
|
||||
while(isdigit(currentChar))
|
||||
{
|
||||
_lwipVersion[versionPart] = 10 * _lwipVersion[versionPart] + (currentChar - '0'); // Left shift and add digit value, in base 10.
|
||||
currentChar = fullVersion.charAt(++i);
|
||||
}
|
||||
if(newline)
|
||||
Serial.println(stringToPrint);
|
||||
else
|
||||
Serial.print(stringToPrint);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the code is running on a version of lwIP that is at least minLwipVersion.
|
||||
*/
|
||||
bool ESP8266WiFiMesh::atLeastLwipVersion(const uint32_t minLwipVersion[3])
|
||||
{
|
||||
for(int versionPart = 0; versionPart < 3; versionPart++)
|
||||
{
|
||||
if(_lwipVersion[versionPart] > minLwipVersion[versionPart])
|
||||
return true;
|
||||
else if(_lwipVersion[versionPart] < minLwipVersion[versionPart])
|
||||
return false;
|
||||
}
|
||||
bool EspnowMeshBackend::staticVerboseMode() {return _staticVerboseMode;}
|
||||
|
||||
return true;
|
||||
void EspnowMeshBackend::staticVerboseModePrint(const String &stringToPrint, bool newline)
|
||||
{
|
||||
if(staticVerboseMode())
|
||||
{
|
||||
if(newline)
|
||||
Serial.println(stringToPrint);
|
||||
else
|
||||
Serial.print(stringToPrint);
|
||||
}
|
||||
}
|
||||
|
||||
void MeshBackendBase::setPrintWarnings(bool printEnabled) {_printWarnings = printEnabled;}
|
||||
bool MeshBackendBase::printWarnings() {return _printWarnings;}
|
||||
|
||||
void MeshBackendBase::warningPrint(const String &stringToPrint, bool newline)
|
||||
{
|
||||
if(printWarnings())
|
||||
{
|
||||
if(newline)
|
||||
Serial.println(stringToPrint);
|
||||
else
|
||||
Serial.print(stringToPrint);
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user