Skip to content

Commit a49f047

Browse files
committed
- Move all Strings to flash and optimize String usage, saving 4-5 kB of RAM.
- Replace const with constexpr where possible. - Use default constructor instead of copy constructor for IPAddress variable initialization. - Add MeshTypeConversionFunctions namespace around TypeConversionFunctions. - Add MeshUtilityFunctions namespace around UtilityFunctions. - Add ESP8266WIFIMESH_DISABLE_COMPATIBILITY preprocessor flag to retain compatibility with old code despite new namespaces. - Add setLogEntryLifetimeMs and setBroadcastResponseTimeoutMs methods to EspnowMeshBackend. - Move FloodingMesh constant definitions from header to .cpp file to reduce the risk of extra RAM consumption. - Add deactivateAP method to FloodingMesh. - Make deactivateAP static and add new non-static deactivateControlledAP method to MeshBackendBase. - Add example of how to transfer null values using multiStrings to HelloEspnow.ino. - Improve documentation. - Improve comments.
1 parent 962a23d commit a49f047

27 files changed

+790
-653
lines changed

libraries/ESP8266WiFiMesh/examples/HelloEspnow/HelloEspnow.ino

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
1+
#define ESP8266WIFIMESH_DISABLE_COMPATIBILITY // Excludes redundant compatibility code. Should be used for new code until the compatibility code is removed with release 3.0.0 of the Arduino core.
2+
13
#include <ESP8266WiFi.h>
24
#include <EspnowMeshBackend.h>
35
#include <TypeConversionFunctions.h>
46
#include <assert.h>
57

8+
namespace TypeCast = MeshTypeConversionFunctions;
9+
610
/**
711
NOTE: Although we could define the strings below as normal String variables,
812
here we are using PROGMEM combined with the FPSTR() macro (and also just the F() macro further down in the file).
@@ -14,8 +18,8 @@
1418
https://github.com/esp8266/Arduino/issues/1143
1519
https://arduino-esp8266.readthedocs.io/en/latest/PROGMEM.html
1620
*/
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.
21+
constexpr char exampleMeshName[] PROGMEM = "MeshNode_"; // The name of the mesh network. Used as prefix for the node SSID and to find other network nodes in the example networkFilter and broadcastFilter functions below.
22+
constexpr 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.
1923

2024
// 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.
2125
// All ESP-NOW keys below must match in an encrypted connection pair for encrypted communication to be possible.
@@ -41,7 +45,7 @@ void networkFilter(int numberOfNetworks, MeshBackendBase &meshInstance);
4145
bool broadcastFilter(String &firstTransmission, EspnowMeshBackend &meshInstance);
4246

4347
/* Create the mesh node object */
44-
EspnowMeshBackend espnowNode = EspnowMeshBackend(manageRequest, manageResponse, networkFilter, broadcastFilter, FPSTR(exampleWiFiPassword), espnowEncryptedConnectionKey, espnowHashKey, FPSTR(exampleMeshName), uint64ToString(ESP.getChipId()), true);
48+
EspnowMeshBackend espnowNode = EspnowMeshBackend(manageRequest, manageResponse, networkFilter, broadcastFilter, FPSTR(exampleWiFiPassword), espnowEncryptedConnectionKey, espnowHashKey, FPSTR(exampleMeshName), TypeCast::uint64ToString(ESP.getChipId()), true);
4549

4650
/**
4751
Callback for when other nodes send you a request
@@ -57,10 +61,10 @@ String manageRequest(const String &request, MeshBackendBase &meshInstance) {
5761
// Of course, it is advised to adjust this approach based on RAM requirements.
5862

5963
// To get the actual class of the polymorphic meshInstance, do as follows (meshBackendCast replaces dynamic_cast since RTTI is disabled)
60-
if (EspnowMeshBackend *espnowInstance = meshBackendCast<EspnowMeshBackend *>(&meshInstance)) {
64+
if (EspnowMeshBackend *espnowInstance = TypeCast::meshBackendCast<EspnowMeshBackend *>(&meshInstance)) {
6165
String transmissionEncrypted = espnowInstance->receivedEncryptedTransmission() ? ", Encrypted transmission" : ", Unencrypted transmission";
6266
Serial.print("ESP-NOW (" + espnowInstance->getSenderMac() + transmissionEncrypted + "): ");
63-
} else if (TcpIpMeshBackend *tcpIpInstance = meshBackendCast<TcpIpMeshBackend *>(&meshInstance)) {
67+
} else if (TcpIpMeshBackend *tcpIpInstance = TypeCast::meshBackendCast<TcpIpMeshBackend *>(&meshInstance)) {
6468
(void)tcpIpInstance; // This is useful to remove a "unused parameter" compiler warning. Does nothing else.
6569
Serial.print("TCP/IP: ");
6670
} else {
@@ -72,7 +76,12 @@ String manageRequest(const String &request, MeshBackendBase &meshInstance) {
7276
// If you need to print the whole String it is better to store it and print it in the loop() later.
7377
// Note that request.substring will not work as expected if the String contains null values as data.
7478
Serial.print("Request received: ");
75-
Serial.println(request.substring(0, 100));
79+
80+
if (request.charAt(0) == 0) {
81+
Serial.println(request); // substring will not work for multiStrings.
82+
} else {
83+
Serial.println(request.substring(0, 100));
84+
}
7685

7786
/* return a string to send back */
7887
return ("Hello world response #" + String(responseNumber++) + " from " + meshInstance.getMeshName() + meshInstance.getNodeID() + " with AP MAC " + WiFi.softAPmacAddress() + ".");
@@ -89,10 +98,10 @@ transmission_status_t manageResponse(const String &response, MeshBackendBase &me
8998
transmission_status_t statusCode = TS_TRANSMISSION_COMPLETE;
9099

91100
// To get the actual class of the polymorphic meshInstance, do as follows (meshBackendCast replaces dynamic_cast since RTTI is disabled)
92-
if (EspnowMeshBackend *espnowInstance = meshBackendCast<EspnowMeshBackend *>(&meshInstance)) {
101+
if (EspnowMeshBackend *espnowInstance = TypeCast::meshBackendCast<EspnowMeshBackend *>(&meshInstance)) {
93102
String transmissionEncrypted = espnowInstance->receivedEncryptedTransmission() ? ", Encrypted transmission" : ", Unencrypted transmission";
94103
Serial.print("ESP-NOW (" + espnowInstance->getSenderMac() + transmissionEncrypted + "): ");
95-
} else if (TcpIpMeshBackend *tcpIpInstance = meshBackendCast<TcpIpMeshBackend *>(&meshInstance)) {
104+
} else if (TcpIpMeshBackend *tcpIpInstance = TypeCast::meshBackendCast<TcpIpMeshBackend *>(&meshInstance)) {
96105
Serial.print("TCP/IP: ");
97106

98107
// Getting the sent message like this will work as long as ONLY(!) TCP/IP is used.
@@ -129,12 +138,12 @@ void networkFilter(int numberOfNetworks, MeshBackendBase &meshInstance) {
129138

130139
/* Connect to any _suitable_ APs which contain meshInstance.getMeshName() */
131140
if (meshNameIndex >= 0) {
132-
uint64_t targetNodeID = stringToUint64(currentSSID.substring(meshNameIndex + meshInstance.getMeshName().length()));
141+
uint64_t targetNodeID = TypeCast::stringToUint64(currentSSID.substring(meshNameIndex + meshInstance.getMeshName().length()));
133142

134-
if (targetNodeID < stringToUint64(meshInstance.getNodeID())) {
135-
if (EspnowMeshBackend *espnowInstance = meshBackendCast<EspnowMeshBackend *>(&meshInstance)) {
143+
if (targetNodeID < TypeCast::stringToUint64(meshInstance.getNodeID())) {
144+
if (EspnowMeshBackend *espnowInstance = TypeCast::meshBackendCast<EspnowMeshBackend *>(&meshInstance)) {
136145
espnowInstance->connectionQueue().push_back(networkIndex);
137-
} else if (TcpIpMeshBackend *tcpIpInstance = meshBackendCast<TcpIpMeshBackend *>(&meshInstance)) {
146+
} else if (TcpIpMeshBackend *tcpIpInstance = TypeCast::meshBackendCast<TcpIpMeshBackend *>(&meshInstance)) {
138147
tcpIpInstance->connectionQueue().push_back(networkIndex);
139148
} else {
140149
Serial.println(String(F("Invalid mesh backend!")));
@@ -345,14 +354,23 @@ void loop() {
345354

346355
espnowDelay(100); // Wait for responses (broadcasts can receive an unlimited number of responses, other transmissions can only receive one response).
347356

357+
// If you have a data array containing null values it is possible to transmit the raw data by making the array into a multiString as shown below.
358+
// You can use String::c_str() or String::begin() to retreive the data array later.
359+
// Note that certain String methods such as String::substring use null values to determine String length, which means they will not work as normal with multiStrings.
360+
uint8_t dataArray[] = {0, '\'', 0, '\'', ' ', '(', 'n', 'u', 'l', 'l', ')', ' ', 'v', 'a', 'l', 'u', 'e'};
361+
String espnowMessage = TypeCast::uint8ArrayToMultiString(dataArray, sizeof dataArray) + String(F(" from ")) + espnowNode.getMeshName() + espnowNode.getNodeID() + String(F("."));
362+
Serial.println("\nTransmitting: " + espnowMessage);
363+
espnowNode.attemptTransmission(espnowMessage, false);
364+
espnowDelay(100); // Wait for response.
365+
348366
Serial.println("\nPerforming encrypted ESP-NOW transmissions.");
349367

350368
uint8_t targetBSSID[6] {0};
351369

352370
// We can create encrypted connections to individual nodes so that all ESP-NOW communication with the node will be encrypted.
353371
if (espnowNode.constConnectionQueue()[0].getBSSID(targetBSSID) && espnowNode.requestEncryptedConnection(targetBSSID) == ECS_CONNECTION_ESTABLISHED) {
354372
// The WiFi scan will detect the AP MAC, but this will automatically be converted to the encrypted STA MAC by the framework.
355-
String peerMac = macToString(targetBSSID);
373+
String peerMac = TypeCast::macToString(targetBSSID);
356374

357375
Serial.println("Encrypted ESP-NOW connection with " + peerMac + " established!");
358376

libraries/ESP8266WiFiMesh/examples/HelloMesh/HelloMesh.ino

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,15 @@
55
That way you will get instant confirmation of the mesh communication without checking the Serial Monitor.
66
*/
77

8+
#define ESP8266WIFIMESH_DISABLE_COMPATIBILITY // Excludes redundant compatibility code. Should be used for new code until the compatibility code is removed with release 3.0.0 of the Arduino core.
9+
810
#include <ESP8266WiFi.h>
911
#include <TypeConversionFunctions.h>
1012
#include <assert.h>
1113
#include <FloodingMesh.h>
1214

15+
namespace TypeCast = MeshTypeConversionFunctions;
16+
1317
/**
1418
NOTE: Although we could define the strings below as normal String variables,
1519
here we are using PROGMEM combined with the FPSTR() macro (and also just the F() macro further down in the file).
@@ -21,8 +25,8 @@
2125
https://github.com/esp8266/Arduino/issues/1143
2226
https://arduino-esp8266.readthedocs.io/en/latest/PROGMEM.html
2327
*/
24-
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.
25-
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.
28+
constexpr char exampleMeshName[] PROGMEM = "MeshNode_"; // The name of the mesh network. Used as prefix for the node SSID and to find other network nodes in the example networkFilter and broadcastFilter functions below.
29+
constexpr 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.
2630

2731
// 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.
2832
// All ESP-NOW keys below must match in an encrypted connection pair for encrypted communication to be possible.
@@ -37,10 +41,10 @@ uint8_t espnowHashKey[16] = {0xEF, 0x44, 0x33, 0x0C, 0x33, 0x44, 0xFE, 0x44, //
3741
bool meshMessageHandler(String &message, FloodingMesh &meshInstance);
3842

3943
/* Create the mesh node object */
40-
FloodingMesh floodingMesh = FloodingMesh(meshMessageHandler, FPSTR(exampleWiFiPassword), espnowEncryptedConnectionKey, espnowHashKey, FPSTR(exampleMeshName), uint64ToString(ESP.getChipId()), true);
44+
FloodingMesh floodingMesh = FloodingMesh(meshMessageHandler, FPSTR(exampleWiFiPassword), espnowEncryptedConnectionKey, espnowHashKey, FPSTR(exampleMeshName), TypeCast::uint64ToString(ESP.getChipId()), true);
4145

4246
bool theOne = true;
43-
String theOneMac = "";
47+
String theOneMac;
4448

4549
bool useLED = false; // Change this to true if you wish the onboard LED to mark The One.
4650

@@ -58,7 +62,7 @@ bool useLED = false; // Change this to true if you wish the onboard LED to mark
5862
bool meshMessageHandler(String &message, FloodingMesh &meshInstance) {
5963
int32_t delimiterIndex = message.indexOf(meshInstance.metadataDelimiter());
6064
if (delimiterIndex == 0) {
61-
Serial.print("Message received from STA MAC " + meshInstance.getEspnowMeshBackend().getSenderMac() + ": ");
65+
Serial.print(String(F("Message received from STA MAC ")) + meshInstance.getEspnowMeshBackend().getSenderMac() + F(": "));
6266
Serial.println(message.substring(2, 102));
6367

6468
String potentialMac = message.substring(2, 14);
@@ -95,17 +99,17 @@ bool meshMessageHandler(String &message, FloodingMesh &meshInstance) {
9599
previousTotalBroadcasts = totalBroadcasts;
96100

97101
if (totalReceivedBroadcasts % 50 == 0) {
98-
Serial.println("missed/total: " + String(missedBroadcasts) + '/' + String(totalReceivedBroadcasts));
102+
Serial.println(String(F("missed/total: ")) + String(missedBroadcasts) + '/' + String(totalReceivedBroadcasts));
99103
}
100104
if (totalReceivedBroadcasts % 500 == 0) {
101-
Serial.println("Benchmark message: " + message.substring(0, 100));
105+
Serial.println(String(F("Benchmark message: ")) + message.substring(0, 100));
102106
}
103107
}
104108
}
105109
} else {
106110
// Only show first 100 characters because printing a large String takes a lot of time, which is a bad thing for a callback function.
107111
// If you need to print the whole String it is better to store it and print it in the loop() later.
108-
Serial.print("Message with origin " + meshInstance.getOriginMac() + " received: ");
112+
Serial.print(String(F("Message with origin ")) + meshInstance.getOriginMac() + F(" received: "));
109113
Serial.println(message.substring(0, 100));
110114
}
111115

@@ -139,7 +143,7 @@ void setup() {
139143
floodingMesh.activateAP();
140144

141145
uint8_t apMacArray[6] {0};
142-
theOneMac = macToString(WiFi.softAPmacAddress(apMacArray));
146+
theOneMac = TypeCast::macToString(WiFi.softAPmacAddress(apMacArray));
143147

144148
if (useLED) {
145149
pinMode(LED_BUILTIN, OUTPUT); // Initialize the LED_BUILTIN pin as an output
@@ -150,7 +154,7 @@ void setup() {
150154
// The main benefit of AEAD encryption is that it can be used with normal broadcasts (which are substantially faster than encryptedBroadcasts).
151155
// The main drawbacks are that AEAD only encrypts the message data (not transmission metadata), transfers less data per message and lacks replay attack protection.
152156
// When using AEAD, potential replay attacks must thus be handled manually.
153-
//floodingMesh.getEspnowMeshBackend().setEspnowMessageEncryptionKey("ChangeThisKeySeed_TODO"); // The message encryption key should always be set manually. Otherwise a default key (all zeroes) is used.
157+
//floodingMesh.getEspnowMeshBackend().setEspnowMessageEncryptionKey(F("ChangeThisKeySeed_TODO")); // The message encryption key should always be set manually. Otherwise a default key (all zeroes) is used.
154158
//floodingMesh.getEspnowMeshBackend().setUseEncryptedMessages(true);
155159

156160
floodingMeshDelay(5000); // Give some time for user to start the nodes
@@ -180,17 +184,17 @@ void loop() {
180184
ledState = ledState ^ bool(benchmarkCount); // Make other nodes' LEDs alternate between on and off once benchmarking begins.
181185

182186
// Note: The maximum length of an unencrypted broadcast message is given by floodingMesh.maxUnencryptedMessageLength(). It is around 670 bytes by default.
183-
floodingMesh.broadcast(String(floodingMesh.metadataDelimiter()) + String(ledState) + theOneMac + " is The One.");
184-
Serial.println("Proclamation broadcast done in " + String(millis() - startTime) + " ms.");
187+
floodingMesh.broadcast(String(floodingMesh.metadataDelimiter()) + String(ledState) + theOneMac + F(" is The One."));
188+
Serial.println(String(F("Proclamation broadcast done in ")) + String(millis() - startTime) + F(" ms."));
185189

186190
timeOfLastProclamation = millis();
187191
floodingMeshDelay(20);
188192
}
189193

190194
if (millis() - loopStart > 23000) { // Start benchmarking the mesh once three proclamations have been made
191195
uint32_t startTime = millis();
192-
floodingMesh.broadcast(String(benchmarkCount++) + String(floodingMesh.metadataDelimiter()) + ": Not a spoon in sight.");
193-
Serial.println("Benchmark broadcast done in " + String(millis() - startTime) + " ms.");
196+
floodingMesh.broadcast(String(benchmarkCount++) + String(floodingMesh.metadataDelimiter()) + F(": Not a spoon in sight."));
197+
Serial.println(String(F("Benchmark broadcast done in ")) + String(millis() - startTime) + F(" ms."));
194198
floodingMeshDelay(20);
195199
}
196200
}

0 commit comments

Comments
 (0)