Skip to content

Commit 6b76368

Browse files
committed
- Add working FloodingMesh. Unencrypted broadcasts should work well, but are untested in large mesh networks. Encrypted broadcast support is currently experimental.
- Add BroadcastTransmissionRedundancy and related functionality to reduce the transmission loss during broadcasts. Broadcast transmissions are now re-transmitted once per default. Broadcast throughput halved per default. - Add getSenderAPMac method. - Add FloodingMesh example in the HelloMesh.ino file. - Improve JSON identifier names. - Improve comments. - Improve documentation.
1 parent 176f285 commit 6b76368

File tree

9 files changed

+998
-134
lines changed

9 files changed

+998
-134
lines changed
Lines changed: 109 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#include <ESP8266WiFi.h>
2-
#include <ESP8266WiFiMesh.h>
32
#include <TypeConversionFunctions.h>
43
#include <assert.h>
4+
#include <FloodingMesh.h>
55

66
/**
77
NOTE: Although we could define the strings below as normal String variables,
@@ -14,84 +14,94 @@
1414
https://github.com/esp8266/Arduino/issues/1143
1515
https://arduino-esp8266.readthedocs.io/en/latest/PROGMEM.html
1616
*/
17-
const char exampleMeshName[] PROGMEM = "MeshNode_";
18-
const char exampleWiFiPassword[] PROGMEM = "ChangeThisWiFiPassword_TODO";
17+
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 and broadcastFilter functions below.
18+
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.
1919

20-
unsigned int requestNumber = 0;
21-
unsigned int responseNumber = 0;
20+
// 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.
21+
// All ESP-NOW keys below must match in an encrypted connection pair for encrypted communication to be possible.
22+
uint8_t espnowEncryptionKey[16] = {0x33, 0x44, 0x33, 0x44, 0x33, 0x44, 0x33, 0x44, // This is the key for encrypting transmissions.
23+
0x33, 0x44, 0x33, 0x44, 0x33, 0x44, 0x32, 0x11
24+
};
25+
uint8_t espnowHashKey[16] = {0xEF, 0x44, 0x33, 0x0C, 0x33, 0x44, 0xFE, 0x44, // This is the secret key used for HMAC during encrypted connection requests.
26+
0x33, 0x44, 0x33, 0xB0, 0x33, 0x44, 0x32, 0xAD
27+
};
2228

23-
String manageRequest(const String &request, ESP8266WiFiMesh &meshInstance);
24-
transmission_status_t manageResponse(const String &response, ESP8266WiFiMesh &meshInstance);
25-
void networkFilter(int numberOfNetworks, ESP8266WiFiMesh &meshInstance);
29+
bool meshMessageHandler(String &message, FloodingMesh &meshInstance);
2630

2731
/* Create the mesh node object */
28-
ESP8266WiFiMesh meshNode = ESP8266WiFiMesh(manageRequest, manageResponse, networkFilter, FPSTR(exampleWiFiPassword), FPSTR(exampleMeshName), "", true);
32+
FloodingMesh floodingMesh = FloodingMesh(meshMessageHandler, FPSTR(exampleWiFiPassword), espnowEncryptionKey, espnowHashKey, FPSTR(exampleMeshName), uint64ToString(ESP.getChipId()), true);
2933

30-
/**
31-
Callback for when other nodes send you a request
34+
bool theOne = true;
35+
String theOneMac = "";
3236

33-
@param request The request string received from another node in the mesh
34-
@param meshInstance The ESP8266WiFiMesh instance that called the function.
35-
@returns The string to send back to the other node
36-
*/
37-
String manageRequest(const String &request, ESP8266WiFiMesh &meshInstance) {
38-
// We do not store strings in flash (via F()) in this function.
39-
// The reason is that the other node will be waiting for our response,
40-
// so keeping the strings in RAM will give a (small) improvement in response time.
41-
// Of course, it is advised to adjust this approach based on RAM requirements.
42-
43-
/* Print out received message */
44-
Serial.print("Request received: ");
45-
Serial.println(request);
46-
47-
/* return a string to send back */
48-
return ("Hello world response #" + String(responseNumber++) + " from " + meshInstance.getMeshName() + meshInstance.getNodeID() + ".");
49-
}
37+
bool useLED = false; // Change this to true if you wish the onboard LED to mark The One.
5038

5139
/**
52-
Callback for when you get a response from other nodes
53-
54-
@param response The response string received from another node in the mesh
55-
@param meshInstance The ESP8266WiFiMesh instance that called the function.
56-
@returns The status code resulting from the response, as an int
40+
Callback for when a message is received from the mesh network.
41+
42+
@param message The message String received from the mesh.
43+
Modifications to this String are passed on when the message is forwarded from this node to other nodes.
44+
However, the forwarded message will still use the same messageID.
45+
Thus it will not be sent to nodes that have already received this messageID.
46+
If you want to send a new message to the whole network, use a new broadcast from within the loop() instead.
47+
@param meshInstance The FloodingMesh instance that received the message.
48+
@return True if this node should forward the received message to other nodes. False otherwise.
5749
*/
58-
transmission_status_t manageResponse(const String &response, ESP8266WiFiMesh &meshInstance) {
59-
transmission_status_t statusCode = TS_TRANSMISSION_COMPLETE;
50+
bool meshMessageHandler(String &message, FloodingMesh &meshInstance) {
51+
int32_t delimiterIndex = message.indexOf(meshInstance.broadcastMetadataDelimiter());
52+
if (delimiterIndex == 0) {
53+
Serial.print("Message received from STA " + meshInstance.getEspnowMeshBackend().getSenderMac() + ": ");
54+
Serial.println(message.substring(1, 101));
55+
56+
String potentialMac = message.substring(1, 13);
57+
58+
if (potentialMac > theOneMac) {
59+
if (theOne) {
60+
if (useLED) {
61+
digitalWrite(LED_BUILTIN, HIGH); // Turn LED off (LED is active low)
62+
}
6063

61-
/* Print out received message */
62-
Serial.print(F("Request sent: "));
63-
Serial.println(meshInstance.getMessage());
64-
Serial.print(F("Response received: "));
65-
Serial.println(response);
64+
theOne = false;
65+
}
6666

67-
// Our last request got a response, so time to create a new request.
68-
meshInstance.setMessage(String(F("Hello world request #")) + String(++requestNumber) + String(F(" from "))
69-
+ meshInstance.getMeshName() + meshInstance.getNodeID() + String(F(".")));
67+
theOneMac = potentialMac;
7068

71-
// (void)meshInstance; // This is useful to remove a "unused parameter" compiler warning. Does nothing else.
72-
return statusCode;
73-
}
69+
return true;
70+
} else {
71+
return false;
72+
}
73+
} else if (delimiterIndex > 0) {
74+
if (meshInstance.getOriginMac() == theOneMac) {
75+
uint32_t totalBroadcasts = strtoul(message.c_str(), nullptr, 0); // strtoul stops reading input when an invalid character is discovered.
7476

75-
/**
76-
Callback used to decide which networks to connect to once a WiFi scan has been completed.
77+
// Static variables are only initialized once.
78+
static uint32_t firstBroadcast = totalBroadcasts;
7779

78-
@param numberOfNetworks The number of networks found in the WiFi scan.
79-
@param meshInstance The ESP8266WiFiMesh instance that called the function.
80-
*/
81-
void networkFilter(int numberOfNetworks, ESP8266WiFiMesh &meshInstance) {
82-
for (int networkIndex = 0; networkIndex < numberOfNetworks; ++networkIndex) {
83-
String currentSSID = WiFi.SSID(networkIndex);
84-
int meshNameIndex = currentSSID.indexOf(meshInstance.getMeshName());
80+
if (totalBroadcasts - firstBroadcast >= 100) { // Wait a little to avoid start-up glitches
81+
static uint32_t missedBroadcasts = 1; // Starting at one to compensate for initial -1 below.
82+
static uint32_t previousTotalBroadcasts = totalBroadcasts;
83+
static uint32_t totalReceivedBroadcasts = 0;
84+
totalReceivedBroadcasts++;
8585

86-
/* Connect to any _suitable_ APs which contain meshInstance.getMeshName() */
87-
if (meshNameIndex >= 0) {
88-
uint64_t targetNodeID = stringToUint64(currentSSID.substring(meshNameIndex + meshInstance.getMeshName().length()));
86+
missedBroadcasts += totalBroadcasts - previousTotalBroadcasts - 1; // We expect an increment by 1.
87+
previousTotalBroadcasts = totalBroadcasts;
8988

90-
if (targetNodeID < stringToUint64(meshInstance.getNodeID())) {
91-
ESP8266WiFiMesh::connectionQueue.push_back(NetworkInfo(networkIndex));
89+
if (totalReceivedBroadcasts % 50 == 0) {
90+
Serial.println("missed/total: " + String(missedBroadcasts) + '/' + String(totalReceivedBroadcasts));
91+
}
92+
if (totalReceivedBroadcasts % 500 == 0) {
93+
Serial.println("Benchmark message: " + message.substring(0, 100));
94+
}
9295
}
9396
}
97+
} else {
98+
// Only show first 100 characters because printing a large String takes a lot of time, which is a bad thing for a callback function.
99+
// If you need to print the whole String it is better to store it and print it in the loop() later.
100+
Serial.print("Message with origin " + meshInstance.getOriginMac() + " received: ");
101+
Serial.println(message.substring(0, 100));
94102
}
103+
104+
return true;
95105
}
96106

97107
void setup() {
@@ -102,7 +112,7 @@ void setup() {
102112
Serial.begin(115200);
103113
delay(50); // Wait for Serial.
104114

105-
//yield(); // Use this if you don't want to wait for Serial.
115+
//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).
106116

107117
// The WiFi.disconnect() ensures that the WiFi is working correctly. If this is not done before receiving WiFi connections,
108118
// those WiFi connections will take a long time to make or sometimes will not work at all.
@@ -111,52 +121,52 @@ void setup() {
111121
Serial.println();
112122
Serial.println();
113123

114-
Serial.println(F("Note that this library can use static IP:s for the nodes to speed up connection times.\n"
115-
"Use the setStaticIP method as shown in this example to enable this.\n"
116-
"Ensure that nodes connecting to the same AP have distinct static IP:s.\n"
117-
"Also, remember to change the default mesh network password!\n\n"));
124+
Serial.println(F("If you have an onboard LED on your ESP8266 it is recommended that you change the useLED variable to true.\n"
125+
"That way you will get instant confirmation of the mesh communication.\n"
126+
"Also, remember to change the default mesh network password and ESP-NOW keys!\n"));
118127

119128
Serial.println(F("Setting up mesh node..."));
120129

121-
/* Initialise the mesh node */
122-
meshNode.begin();
123-
meshNode.activateAP(); // Each AP requires a separate server port.
124-
meshNode.setStaticIP(IPAddress(192, 168, 4, 22)); // Activate static IP mode to speed up connection times.
130+
floodingMesh.begin();
131+
132+
uint8_t apMacArray[6] {0};
133+
theOneMac = macToString(WiFi.softAPmacAddress(apMacArray));
134+
135+
if (useLED) {
136+
pinMode(LED_BUILTIN, OUTPUT); // Initialize the LED_BUILTIN pin as an output
137+
digitalWrite(LED_BUILTIN, LOW); // Turn LED on (LED is active low)
138+
}
139+
140+
floodingMeshDelay(5000); // Give some time for user to start the nodes
125141
}
126142

127-
int32_t timeOfLastScan = -10000;
143+
int32_t timeOfLastProclamation = -10000;
128144
void loop() {
129-
if (millis() - timeOfLastScan > 3000 // Give other nodes some time to connect between data transfers.
130-
|| (WiFi.status() != WL_CONNECTED && millis() - timeOfLastScan > 2000)) { // Scan for networks with two second intervals when not already connected.
131-
String request = String(F("Hello world request #")) + String(requestNumber) + String(F(" from ")) + meshNode.getMeshName() + meshNode.getNodeID() + String(F("."));
132-
meshNode.attemptTransmission(request, false);
133-
timeOfLastScan = millis();
134-
135-
// One way to check how attemptTransmission worked out
136-
if (ESP8266WiFiMesh::latestTransmissionSuccessful()) {
137-
Serial.println(F("Transmission successful."));
145+
static uint32_t benchmarkCount = 0;
146+
static uint32_t loopStart = millis();
147+
148+
// The floodingMeshDelay() method performs all the background operations for the FloodingMesh (via FloodingMesh::performMeshMaintainance()).
149+
// It is recommended to place one of these methods in the beginning of the loop(), unless there is a need to put them elsewhere.
150+
// Among other things, the method cleans up old ESP-NOW log entries (freeing up RAM) and forwards received mesh messages.
151+
// Note that depending on the amount of messages to forward and their length, this method can take tens or even hundreds of milliseconds to complete.
152+
// More intense transmission activity and less frequent calls to performMeshMaintainance will likely cause the method to take longer to complete, so plan accordingly.
153+
floodingMeshDelay(1);
154+
155+
if (theOne) {
156+
if (millis() - timeOfLastProclamation > 10000) {
157+
uint32_t startTime = millis();
158+
floodingMesh.broadcast(String(floodingMesh.broadcastMetadataDelimiter()) + theOneMac + " is The One.");
159+
Serial.println("Proclamation broadcast done in " + String(millis() - startTime) + " ms.");
160+
161+
timeOfLastProclamation = millis();
162+
floodingMeshDelay(20);
138163
}
139164

140-
// Another way to check how attemptTransmission worked out
141-
if (ESP8266WiFiMesh::latestTransmissionOutcomes.empty()) {
142-
Serial.println(F("No mesh AP found."));
143-
} else {
144-
for (TransmissionResult &transmissionResult : ESP8266WiFiMesh::latestTransmissionOutcomes) {
145-
if (transmissionResult.transmissionStatus == TS_TRANSMISSION_FAILED) {
146-
Serial.println(String(F("Transmission failed to mesh AP ")) + transmissionResult.SSID);
147-
} else if (transmissionResult.transmissionStatus == TS_CONNECTION_FAILED) {
148-
Serial.println(String(F("Connection failed to mesh AP ")) + transmissionResult.SSID);
149-
} else if (transmissionResult.transmissionStatus == TS_TRANSMISSION_COMPLETE) {
150-
// No need to do anything, transmission was successful.
151-
} else {
152-
Serial.println(String(F("Invalid transmission status for ")) + transmissionResult.SSID + String(F("!")));
153-
assert(F("Invalid transmission status returned from responseHandler!") && false);
154-
}
155-
}
165+
if (millis() - loopStart > 23000) { // Start benchmarking the mesh once three proclamations have been made
166+
uint32_t startTime = millis();
167+
floodingMesh.broadcast(String(benchmarkCount++) + String(floodingMesh.broadcastMetadataDelimiter()) + ": Not a spoon in sight.");
168+
Serial.println("Benchmark broadcast done in " + String(millis() - startTime) + " ms.");
169+
floodingMeshDelay(20);
156170
}
157-
Serial.println();
158-
} else {
159-
/* Accept any incoming connections */
160-
meshNode.acceptRequest();
161171
}
162172
}

0 commit comments

Comments
 (0)