Skip to content

Commit 49a0927

Browse files
authored
Merge pull request #6280 from aerlon/wifi_mesh_update_2.2
WiFi Mesh Update 2.2
2 parents 4440021 + 7fbf620 commit 49a0927

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

72 files changed

+12347
-361
lines changed

libraries/ESP8266WiFiMesh/README.md

Lines changed: 371 additions & 54 deletions
Large diffs are not rendered by default.

libraries/ESP8266WiFiMesh/examples/HelloEspnow/HelloEspnow.ino

Lines changed: 454 additions & 0 deletions
Large diffs are not rendered by default.

libraries/ESP8266WiFiMesh/examples/HelloMesh/HelloMesh.ino

Lines changed: 141 additions & 106 deletions
Large diffs are not rendered by default.
Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
#define ESP8266WIFIMESH_DISABLE_COMPATIBILITY // Excludes redundant compatibility code. TODO: Should be used for new code until the compatibility code is removed with release 3.0.0 of the Arduino core.
2+
3+
#include <ESP8266WiFi.h>
4+
#include <TcpIpMeshBackend.h>
5+
#include <EspnowMeshBackend.h>
6+
#include <TypeConversionFunctions.h>
7+
#include <assert.h>
8+
9+
namespace TypeCast = MeshTypeConversionFunctions;
10+
11+
/**
12+
NOTE: Although we could define the strings below as normal String variables,
13+
here we are using PROGMEM combined with the FPSTR() macro (and also just the F() macro further down in the file).
14+
The reason is that this approach will place the strings in flash memory which will help save RAM during program execution.
15+
Reading strings from flash will be slower than reading them from RAM,
16+
but this will be a negligible difference when printing them to Serial.
17+
18+
More on F(), FPSTR() and PROGMEM:
19+
https://github.com/esp8266/Arduino/issues/1143
20+
https://arduino-esp8266.readthedocs.io/en/latest/PROGMEM.html
21+
*/
22+
constexpr char exampleMeshName[] PROGMEM = "MeshNode_";
23+
constexpr char exampleWiFiPassword[] PROGMEM = "ChangeThisWiFiPassword_TODO"; // Note: " is an illegal character. The password has to be min 8 and max 64 characters long, otherwise an AP which uses it will not be found during scans.
24+
25+
unsigned int requestNumber = 0;
26+
unsigned int responseNumber = 0;
27+
28+
String manageRequest(const String &request, MeshBackendBase &meshInstance);
29+
TransmissionStatusType manageResponse(const String &response, MeshBackendBase &meshInstance);
30+
void networkFilter(int numberOfNetworks, MeshBackendBase &meshInstance);
31+
32+
/* Create the mesh node object */
33+
TcpIpMeshBackend tcpIpNode = TcpIpMeshBackend(manageRequest, manageResponse, networkFilter, FPSTR(exampleWiFiPassword), FPSTR(exampleMeshName), TypeCast::uint64ToString(ESP.getChipId()), true);
34+
35+
/**
36+
Callback for when other nodes send you a request
37+
38+
@param request The request string received from another node in the mesh
39+
@param meshInstance The MeshBackendBase instance that called the function.
40+
@return The string to send back to the other node. For ESP-NOW, return an empy string ("") if no response should be sent.
41+
*/
42+
String manageRequest(const String &request, MeshBackendBase &meshInstance) {
43+
// To get the actual class of the polymorphic meshInstance, do as follows (meshBackendCast replaces dynamic_cast since RTTI is disabled)
44+
if (EspnowMeshBackend *espnowInstance = TypeCast::meshBackendCast<EspnowMeshBackend *>(&meshInstance)) {
45+
String transmissionEncrypted = espnowInstance->receivedEncryptedTransmission() ? F(", Encrypted transmission") : F(", Unencrypted transmission");
46+
Serial.print(String(F("ESP-NOW (")) + espnowInstance->getSenderMac() + transmissionEncrypted + F("): "));
47+
} else if (TcpIpMeshBackend *tcpIpInstance = TypeCast::meshBackendCast<TcpIpMeshBackend *>(&meshInstance)) {
48+
(void)tcpIpInstance; // This is useful to remove a "unused parameter" compiler warning. Does nothing else.
49+
Serial.print(F("TCP/IP: "));
50+
} else {
51+
Serial.print(F("UNKNOWN!: "));
52+
}
53+
54+
/* Print out received message */
55+
// Only show first 100 characters because printing a large String takes a lot of time, which is a bad thing for a callback function.
56+
// If you need to print the whole String it is better to store it and print it in the loop() later.
57+
// Note that request.substring will not work as expected if the String contains null values as data.
58+
Serial.print(F("Request received: "));
59+
Serial.println(request.substring(0, 100));
60+
61+
/* return a string to send back */
62+
return (String(F("Hello world response #")) + String(responseNumber++) + F(" from ") + meshInstance.getMeshName() + meshInstance.getNodeID() + F(" with AP MAC ") + WiFi.softAPmacAddress() + String('.'));
63+
}
64+
65+
/**
66+
Callback for when you get a response from other nodes
67+
68+
@param response The response string received from another node in the mesh
69+
@param meshInstance The MeshBackendBase instance that called the function.
70+
@return The status code resulting from the response, as an int
71+
*/
72+
TransmissionStatusType manageResponse(const String &response, MeshBackendBase &meshInstance) {
73+
TransmissionStatusType statusCode = TransmissionStatusType::TRANSMISSION_COMPLETE;
74+
75+
// To get the actual class of the polymorphic meshInstance, do as follows (meshBackendCast replaces dynamic_cast since RTTI is disabled)
76+
if (EspnowMeshBackend *espnowInstance = TypeCast::meshBackendCast<EspnowMeshBackend *>(&meshInstance)) {
77+
String transmissionEncrypted = espnowInstance->receivedEncryptedTransmission() ? F(", Encrypted transmission") : F(", Unencrypted transmission");
78+
Serial.print(String(F("ESP-NOW (")) + espnowInstance->getSenderMac() + transmissionEncrypted + F("): "));
79+
} else if (TcpIpMeshBackend *tcpIpInstance = TypeCast::meshBackendCast<TcpIpMeshBackend *>(&meshInstance)) {
80+
Serial.print(F("TCP/IP: "));
81+
82+
// Getting the sent message like this will work as long as ONLY(!) TCP/IP is used.
83+
// With TCP/IP the response will follow immediately after the request, so the stored message will not have changed.
84+
// 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.
85+
// So for ESP-NOW, adding unique identifiers in the response and request is required to associate a response with a request.
86+
Serial.print(F("Request sent: "));
87+
Serial.println(tcpIpInstance->getCurrentMessage().substring(0, 100));
88+
} else {
89+
Serial.print(F("UNKNOWN!: "));
90+
}
91+
92+
/* Print out received message */
93+
// Only show first 100 characters because printing a large String takes a lot of time, which is a bad thing for a callback function.
94+
// If you need to print the whole String it is better to store it and print it in the loop() later.
95+
// Note that response.substring will not work as expected if the String contains null values as data.
96+
Serial.print(F("Response received: "));
97+
Serial.println(response.substring(0, 100));
98+
99+
return statusCode;
100+
}
101+
102+
/**
103+
Callback used to decide which networks to connect to once a WiFi scan has been completed.
104+
105+
@param numberOfNetworks The number of networks found in the WiFi scan.
106+
@param meshInstance The MeshBackendBase instance that called the function.
107+
*/
108+
void networkFilter(int numberOfNetworks, MeshBackendBase &meshInstance) {
109+
// Note that the network index of a given node may change whenever a new scan is done.
110+
for (int networkIndex = 0; networkIndex < numberOfNetworks; ++networkIndex) {
111+
String currentSSID = WiFi.SSID(networkIndex);
112+
int meshNameIndex = currentSSID.indexOf(meshInstance.getMeshName());
113+
114+
/* Connect to any _suitable_ APs which contain meshInstance.getMeshName() */
115+
if (meshNameIndex >= 0) {
116+
uint64_t targetNodeID = TypeCast::stringToUint64(currentSSID.substring(meshNameIndex + meshInstance.getMeshName().length()));
117+
118+
if (targetNodeID < TypeCast::stringToUint64(meshInstance.getNodeID())) {
119+
if (EspnowMeshBackend *espnowInstance = TypeCast::meshBackendCast<EspnowMeshBackend *>(&meshInstance)) {
120+
espnowInstance->connectionQueue().emplace_back(networkIndex);
121+
} else if (TcpIpMeshBackend *tcpIpInstance = TypeCast::meshBackendCast<TcpIpMeshBackend *>(&meshInstance)) {
122+
tcpIpInstance->connectionQueue().emplace_back(networkIndex);
123+
} else {
124+
Serial.println(F("Invalid mesh backend!"));
125+
}
126+
}
127+
}
128+
}
129+
}
130+
131+
/**
132+
Once passed to the setTransmissionOutcomesUpdateHook method of the TCP/IP backend,
133+
this function will be called after each update of the latestTransmissionOutcomes vector during attemptTransmission.
134+
(which happens after each individual transmission has finished)
135+
136+
Example use cases is modifying getMessage() between transmissions, or aborting attemptTransmission before all nodes in the connectionQueue have been contacted.
137+
138+
@param meshInstance The MeshBackendBase instance that called the function.
139+
140+
@return True if attemptTransmission should continue with the next entry in the connectionQueue. False if attemptTransmission should stop.
141+
*/
142+
bool exampleTransmissionOutcomesUpdateHook(MeshBackendBase &meshInstance) {
143+
// The default hook only returns true and does nothing else.
144+
145+
if (TcpIpMeshBackend *tcpIpInstance = TypeCast::meshBackendCast<TcpIpMeshBackend *>(&meshInstance)) {
146+
if (tcpIpInstance->latestTransmissionOutcomes().back().transmissionStatus() == TransmissionStatusType::TRANSMISSION_COMPLETE) {
147+
// Our last request got a response, so time to create a new request.
148+
meshInstance.setMessage(String(F("Hello world request #")) + String(++requestNumber) + F(" from ")
149+
+ meshInstance.getMeshName() + meshInstance.getNodeID() + String('.'));
150+
}
151+
} else {
152+
Serial.println(F("Invalid mesh backend!"));
153+
}
154+
155+
return true;
156+
}
157+
158+
void setup() {
159+
// Prevents the flash memory from being worn out, see: https://github.com/esp8266/Arduino/issues/1054 .
160+
// 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.
161+
WiFi.persistent(false);
162+
163+
Serial.begin(115200);
164+
165+
Serial.println();
166+
Serial.println();
167+
168+
Serial.println(F("Note that this library can use static IP:s for the nodes to speed up connection times.\n"
169+
"Use the setStaticIP method as shown in this example to enable this.\n"
170+
"Ensure that nodes connecting to the same AP have distinct static IP:s.\n"
171+
"Also, remember to change the default mesh network password!\n\n"));
172+
173+
Serial.println(F("Setting up mesh node..."));
174+
175+
/* Initialise the mesh node */
176+
tcpIpNode.begin();
177+
tcpIpNode.activateAP(); // Each AP requires a separate server port.
178+
tcpIpNode.setStaticIP(IPAddress(192, 168, 4, 22)); // Activate static IP mode to speed up connection times.
179+
180+
// Storing our message in the TcpIpMeshBackend instance is not required, but can be useful for organizing code, especially when using many TcpIpMeshBackend instances.
181+
// Note that calling the multi-recipient tcpIpNode.attemptTransmission will replace the stored message with whatever message is transmitted.
182+
tcpIpNode.setMessage(String(F("Hello world request #")) + String(requestNumber) + F(" from ") + tcpIpNode.getMeshName() + tcpIpNode.getNodeID() + String('.'));
183+
184+
tcpIpNode.setTransmissionOutcomesUpdateHook(exampleTransmissionOutcomesUpdateHook);
185+
}
186+
187+
int32_t timeOfLastScan = -10000;
188+
void loop() {
189+
if (millis() - timeOfLastScan > 3000 // Give other nodes some time to connect between data transfers.
190+
|| (WiFi.status() != WL_CONNECTED && millis() - timeOfLastScan > 2000)) { // Scan for networks with two second intervals when not already connected.
191+
192+
// attemptTransmission(message, scan, scanAllWiFiChannels, concludingDisconnect, initialDisconnect = false)
193+
tcpIpNode.attemptTransmission(tcpIpNode.getMessage(), true, false, false);
194+
timeOfLastScan = millis();
195+
196+
// One way to check how attemptTransmission worked out
197+
if (tcpIpNode.latestTransmissionSuccessful()) {
198+
Serial.println(F("Transmission successful."));
199+
}
200+
201+
// Another way to check how attemptTransmission worked out
202+
if (tcpIpNode.latestTransmissionOutcomes().empty()) {
203+
Serial.println(F("No mesh AP found."));
204+
} else {
205+
for (TransmissionOutcome &transmissionOutcome : tcpIpNode.latestTransmissionOutcomes()) {
206+
if (transmissionOutcome.transmissionStatus() == TransmissionStatusType::TRANSMISSION_FAILED) {
207+
Serial.println(String(F("Transmission failed to mesh AP ")) + transmissionOutcome.SSID());
208+
} else if (transmissionOutcome.transmissionStatus() == TransmissionStatusType::CONNECTION_FAILED) {
209+
Serial.println(String(F("Connection failed to mesh AP ")) + transmissionOutcome.SSID());
210+
} else if (transmissionOutcome.transmissionStatus() == TransmissionStatusType::TRANSMISSION_COMPLETE) {
211+
// No need to do anything, transmission was successful.
212+
} else {
213+
Serial.println(String(F("Invalid transmission status for ")) + transmissionOutcome.SSID() + String('!'));
214+
assert(F("Invalid transmission status returned from responseHandler!") && false);
215+
}
216+
}
217+
}
218+
Serial.println();
219+
} else {
220+
/* Accept any incoming connections */
221+
tcpIpNode.acceptRequests();
222+
}
223+
}

0 commit comments

Comments
 (0)