-
Notifications
You must be signed in to change notification settings - Fork 13.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Update of mesh network library. #4718
Changes from 20 commits
8e9a2d2
139c4de
b1c3236
c6397f5
e2816cf
ec13de5
2533a33
4fb131f
350b8e7
0f9155e
757d1f7
b5ea495
9c4ebc0
f03724a
b19f619
3fccdf9
367a680
59418c3
d3153ec
7f853a5
887246e
751f9af
ed15ab2
75a5549
a0edf99
40a25e6
0eadbb0
b6fc4bb
cdbf7eb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
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). | ||
|
||
**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. | ||
|
||
Usage | ||
----- | ||
|
||
The basic operation of a mesh node is as follows: | ||
|
||
The `attemptTransmission` method of the ESP8266WiFiMesh instance is called with a message to send to other nodes in the mesh network. If the node is already connected to an AP, the message is sent only to that AP. Otherwise a WiFi scan is performed. The scan results are sent to the `networkFilter` callback function of the ESP8266WiFiMesh instance which adds the AP:s of interest to the `connection_queue` vector. The message is then transmitted to the networks in the `connection_queue`, and the response from each AP is sent to the `responseHandler` callback of the ESP8266WiFiMesh instance. The outcome from each transmission attempt can be found in the `latest_transmission_outcomes` vector. | ||
|
||
The node receives messages from other nodes by calling the `acceptRequest` method of the ESP8266WiFiMesh instance. These received messages are passed to the `requestHandler` callback of the mesh instance. For each received message the return value of `requestHandler` is sent to the other node as a response to the message. | ||
|
||
For more details, see the included example. The main functions to modify in the example are `manageRequest` (`requestHandler`), `manageResponse` (`responseHandler`) and `networkFilter`. There is also more information to be found in the source code comments. An example is the ESP8266WiFiMesh constructor comment, which is shown below for reference: | ||
``` | ||
/** | ||
* WiFiMesh Constructor method. Creates a WiFi Mesh Node, ready to be initialised. | ||
* | ||
* @param requestHandler The callback handler for dealing with received requests. Takes a string as an argument which | ||
* is the request string received from another node and returns the string to send back. | ||
* @param responseHandler The callback handler for dealing with received responses. Takes a string as an argument which | ||
* is the response string received from another node. Returns a transmission status code as a transmission_status_t. | ||
* @param networkFilter The callback handler for deciding which WiFi networks to connect to. | ||
* @param mesh_password The WiFi password for the mesh network. | ||
* @param mesh_name The name of the mesh network. Used as prefix for the node SSID and to find other network nodes in the example network filter function. | ||
* @param node_id The id for this mesh node. Used as suffix for the node SSID. If set to "", the id will default to ESP.getChipId(). | ||
* @param verbose_mode Determines if we should print the events occurring in the library to Serial. Off by default. | ||
* @param mesh_wifi_channel The WiFi channel used by the mesh network. Valid values are integers from 1 to 13. Defaults to 1. | ||
* WARNING: The ESP8266 has only one WiFi channel, and the the station/client mode is always prioritized for channel selection. | ||
* This can cause problems if several ESP8266WiFiMesh instances exist on the same ESP8266 and use different WiFi channels. | ||
* In such a case, whenever the station of one ESP8266WiFiMesh instance connects to an AP, it will silently force the | ||
* WiFi channel of any active AP on the ESP8266 to match that of the station. This will cause disconnects and possibly | ||
* make it impossible for other stations to detect the APs whose WiFi channels have changed. | ||
* @param server_port The server port used by the AP of the ESP8266WiFiMesh instance. If multiple APs exist on a single ESP8266, each requires a separate server port. | ||
* If two AP:s on the same ESP8266 are using the same server port, you must call deactivateAP on the active AP before calling activateAP on the inactive AP. | ||
* | ||
*/ | ||
ESP8266WiFiMesh(std::function<String(String, ESP8266WiFiMesh *)> requestHandler, std::function<transmission_status_t(String, ESP8266WiFiMesh *)> responseHandler, | ||
std::function<void(int, ESP8266WiFiMesh *)> networkFilter, String mesh_password, String mesh_name = "Mesh_Node", String node_id = "", | ||
bool verbose_mode = false, uint8 mesh_wifi_channel = 1, int server_port = 4011); | ||
``` | ||
|
||
### 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. | ||
|
||
* 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 `scan_all_wifi_channels` 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, use the `restartAP` method to force the AP back on the original channel once the ESP8266 has disconnected from the other AP. | ||
|
||
* It is possible to have several ESP8266WiFiMesh instances running on every ESP8266 (e.g. to communicate with different mesh networks). However, because the ESP8266 has one WiFi radio only one AP per ESP8266 can be active at a time. Also note that if the ESP8266WiFiMesh instances use different WiFi channels, active APs are forced to use the same WiFi channel as active stations, possibly causing AP disconnections. | ||
|
||
* While it is possible to connect to other nodes by only giving their SSID, e.g. `ESP8266WiFiMesh::connection_queue.push_back(NetworkInfo("NodeSSID"));`, it is recommended that AP WiFi channel and AP BSSID are given as well, to minimize connection delay. | ||
|
||
* Also, remember to change the default mesh network WiFi password! | ||
|
||
General Information | ||
--------------------------- | ||
|
||
This library uses the standard Arduino core for ESP8266 WiFi functions. Therefore, other code that also uses these WiFi functions may cause conflicts with the library, resulting in strange behaviour. | ||
|
||
A maximum of 5 stations can be connected at a time to each AP. | ||
|
||
Unlike `WiFi.mode(WIFI_AP)`, the `WiFi.mode(WIFI_AP_STA)` which is used in this library allows nodes to stay connected to an AP they connect to while in STA mode, at the same time as they can receive connections from other stations. Nodes cannot send data to an AP while in STA_AP mode though, that requires STA mode. Switching to STA mode will sometimes disconnect stations connected to the node AP (though they can request a reconnect even while the previous AP node is in STA mode). | ||
|
||
Scanning for networks (e.g. via the `attemptTransmission` method) without the WiFi scan optimizations for core version 2.4.2 mentioned above, causes the WiFi radio to cycle through all WiFi channels which means existing WiFi connections are likely to break or work poorly if done frequently. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,50 +1,129 @@ | ||
#include <ESP8266WiFi.h> | ||
#include <ESP8266WiFiMesh.h> | ||
|
||
String mesh_name = "Mesh_Node"; | ||
|
||
unsigned int request_i = 0; | ||
unsigned int response_i = 0; | ||
|
||
String manageRequest(String request); | ||
String manageRequest(String request, ESP8266WiFiMesh *mesh_instance); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The request argument is declared for passing by value, which needlessly duplicated mem. It should probably be declared as reference, or better a const reference. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Makes sense. |
||
transmission_status_t manageResponse(String response, ESP8266WiFiMesh *mesh_instance); | ||
void networkFilter(int number_of_networks, ESP8266WiFiMesh *mesh_instance); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. mesh_instanc declare as reference |
||
|
||
/* Create the mesh node object */ | ||
ESP8266WiFiMesh mesh_node = ESP8266WiFiMesh(ESP.getChipId(), manageRequest); | ||
ESP8266WiFiMesh mesh_node = ESP8266WiFiMesh(manageRequest, manageResponse, networkFilter, "ChangeThisWiFiPassword_TODO", mesh_name, "", true); | ||
|
||
/** | ||
Callback for when other nodes send you data | ||
Callback for when other nodes send you a request | ||
|
||
@request The string received from another node in the mesh | ||
@param request The request string received from another node in the mesh | ||
@param mesh_instance The "this" pointer of the ESP8266WiFiMesh instance that called the function. | ||
@returns The string to send back to the other node | ||
*/ | ||
String manageRequest(String request) { | ||
String manageRequest(String request, ESP8266WiFiMesh *mesh_instance) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. request => ref, mesh_instance => ref |
||
/* Print out received message */ | ||
Serial.print("received: "); | ||
Serial.print("Request received: "); | ||
Serial.println(request); | ||
|
||
/* return a string to send back */ | ||
char response[60]; | ||
sprintf(response, "Hello world response #%d from Mesh_Node%d.", response_i++, ESP.getChipId()); | ||
sprintf(response, "Hello world response #%d from %s%s.", response_i++, mesh_instance->getMeshName().c_str(), mesh_instance->getNodeID().c_str()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This string could easily be made larger than the size of response[60] by a user playing with the example. I suggest a more robust approach. |
||
return response; | ||
} | ||
|
||
/** | ||
Callback used to decide which networks to connect to once a WiFi scan has been completed. | ||
|
||
@param number_of_networks The number of networks found in the WiFi scan. | ||
@param mesh_instance The "this" pointer of the ESP8266WiFiMesh instance that called the function. | ||
*/ | ||
void networkFilter(int number_of_networks, ESP8266WiFiMesh *mesh_instance) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. mesh_instance => ref |
||
for (int i = 0; i < number_of_networks; ++i) { | ||
String current_ssid = WiFi.SSID(i); | ||
int mesh_name_index = current_ssid.indexOf(mesh_instance->getMeshName()); | ||
|
||
/* Connect to any _suitable_ APs which contain mesh_instance->getMeshName() */ | ||
if (mesh_name_index >= 0) { | ||
uint64_t target_node_id = ESP8266WiFiMesh::StringToUint64(current_ssid.substring(mesh_name_index + mesh_instance->getMeshName().length())); | ||
|
||
if (target_node_id < ESP8266WiFiMesh::StringToUint64(mesh_instance->getNodeID())) { | ||
ESP8266WiFiMesh::connection_queue.push_back(NetworkInfo(i)); | ||
} | ||
} | ||
} | ||
} | ||
|
||
/** | ||
Callback for when you get a response from other nodes | ||
|
||
@param response The response string received from another node in the mesh | ||
@param mesh_instance The "this" pointer of the ESP8266WiFiMesh instance that called the function. | ||
@returns The status code resulting from the response, as an int | ||
*/ | ||
transmission_status_t manageResponse(String response, ESP8266WiFiMesh *mesh_instance) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. response => ref, mesh_instance => ref |
||
transmission_status_t status_code = TS_TRANSMISSION_COMPLETE; | ||
|
||
/* Print out received message */ | ||
Serial.print("Request sent: "); | ||
Serial.println(mesh_instance->getMessage()); | ||
Serial.print("Response received: "); | ||
Serial.println(response); | ||
|
||
// Our last request got a response, so time to create a new request. | ||
mesh_instance->setMessage("Hello world request #" + String(++request_i) + " from " + mesh_instance->getMeshName() + mesh_instance->getNodeID() + "."); | ||
|
||
// (void)mesh_instance; // This is useful to remove a "unused parameter" compiler warning. Does nothing else. | ||
return status_code; | ||
} | ||
|
||
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(10); | ||
delay(50); // Wait for Serial. | ||
|
||
//yield(); // Use this if you don't want to wait for Serial. | ||
|
||
Serial.println(); | ||
Serial.println(); | ||
|
||
Serial.println("Note that this library can use static IP:s for the nodes to speed up connection times.\n" | ||
"Use the setStaticIP method to enable this and ensure that nodes connecting to the same AP have distinct static IP:s.\n" | ||
"Also, remember to change the default mesh network password!\n\n"); | ||
|
||
Serial.println("Setting up mesh node..."); | ||
|
||
/* Initialise the mesh node */ | ||
mesh_node.begin(); | ||
mesh_node.activateAP(); // Each AP requires a separate server port. | ||
mesh_node.setStaticIP(IPAddress(192, 168, 4, 22)); // Activate static IP mode to speed up connection times. | ||
} | ||
|
||
int32_t time_of_last_scan = -10000; | ||
void loop() { | ||
/* Accept any incoming connections */ | ||
mesh_node.acceptRequest(); | ||
|
||
/* Scan for other nodes and send them a message */ | ||
char request[60]; | ||
sprintf(request, "Hello world request #%d from Mesh_Node%d.", request_i++, ESP.getChipId()); | ||
mesh_node.attemptScan(request); | ||
delay(1000); | ||
if (millis() - time_of_last_scan > 3000 // Give other nodes some time to connect between data transfers. | ||
|| (WiFi.status() != WL_CONNECTED && millis() - time_of_last_scan > 2000)) { // Scan for networks with two second intervals when not already connected. | ||
char request[60]; | ||
sprintf(request, "Hello world request #%d from %s%s.", request_i, mesh_node.getMeshName().c_str(), mesh_node.getNodeID().c_str()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. String could become larger than 60 chars => More robust print approach |
||
mesh_node.attemptTransmission(request, false); | ||
time_of_last_scan = millis(); | ||
|
||
if (ESP8266WiFiMesh::latest_transmission_outcomes.empty()) { | ||
Serial.println("No mesh AP found."); | ||
} else { | ||
for (TransmissionResult &transmission_result : ESP8266WiFiMesh::latest_transmission_outcomes) { | ||
if (transmission_result.transmission_status == TS_TRANSMISSION_FAILED) { | ||
Serial.println("Transmission failed to mesh AP " + transmission_result.ssid); | ||
} else if (transmission_result.transmission_status == TS_CONNECTION_FAILED) { | ||
Serial.println("Connection failed to mesh AP " + transmission_result.ssid); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. else ? (Robustness) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In this particular case I’d argue that we only care about if there is an error occurring, and those are the cases checked for here. Adding an else would not really aid the user since it would only capture all the successful transmissions which we already get confirmations about elsewhere in the code (from the responseHandler). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If there is ever another error status value added and returned, the if-else-if clause will silently not catch anything. For cases like this, the else should be akin to an assert. It's a similar case as having default: in a switch. It is rarely a good practice not to have one. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I realized that a good way to get the robustness advantage you mentioned while avoiding too many notification events for the user was simply to add a condition for every current state, and then an else. This is now implemented in the most recent code update. |
||
} | ||
} | ||
Serial.println(); | ||
} else { | ||
/* Accept any incoming connections */ | ||
mesh_node.acceptRequest(); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should probably be declared as String meshName(F("meshNode"));
Also, please use camelCase for naming convention.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Camel case or snake case work either way for me. I noticed snake case was used in the original mesh library, but I take it style conventions has changed since then?
Regarding the actual mesh name, though, I think it may be prudent to not write it in the same style as a programming object. It is just a network name, after all, and its lack of relationship to other program objects may be easier for the novice user to deduce when it does not follow the typical naming conventions for objects in the library. Thus
String meshName(F("Mesh_Node"));
orString meshName(F("MeshNode_"));
or some such mesh name format may be useful.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Style wasn't really pursued before, commenting on it is rather recent. In general, current naming convention is camelCase for C++ and snake_case for C.