Skip to content
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

Merged
merged 29 commits into from
Aug 1, 2018
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
8e9a2d2
Make mesh network actually usable. Make mesh network use static IP du…
aerlon May 10, 2018
139c4de
Fix compiler warnings. Fix code style of HelloMesh.ino to avoid upset…
aerlon May 10, 2018
b1c3236
Remove stray spaces.
aerlon May 10, 2018
c6397f5
Make mesh network WiFi password settable via the ESP8266WiFiMesh cons…
aerlon May 11, 2018
e2816cf
Merge branch 'master' into wifi_mesh_update
earlephilhower May 17, 2018
ec13de5
Merge branch 'master' into wifi_mesh_update
devyte May 25, 2018
2533a33
Merge branch 'master' into wifi_mesh_update
aerlon Jun 4, 2018
4fb131f
Increase specificity in the conditions of the waitForClientTransmissi…
aerlon Jun 5, 2018
350b8e7
Merge branch 'master' into wifi_mesh_update
devyte Jun 7, 2018
0f9155e
Merge branch 'master' into wifi_mesh_update
devyte Jul 3, 2018
757d1f7
Merge branch 'master' into wifi_mesh_update
aerlon Jul 8, 2018
b5ea495
Improve most parts of the library to achieve better performance and g…
aerlon Jul 9, 2018
9c4ebc0
Improve README.rst formatting.
aerlon Jul 10, 2018
f03724a
Further improve README.rst.
aerlon Jul 10, 2018
b19f619
Even further improve README.rst.
aerlon Jul 10, 2018
3fccdf9
Make source code comments Doxygen compatible. Improve README file and…
aerlon Jul 22, 2018
367a680
Add temporary compatibility layer to ensure backwards compatibility w…
aerlon Jul 22, 2018
59418c3
Polish documentation slightly.
aerlon Jul 22, 2018
d3153ec
Merge branch 'master' into wifi_mesh_update
aerlon Jul 22, 2018
7f853a5
Add scan_all_wifi_channels option to attemptTransmission method.
aerlon Jul 23, 2018
887246e
Merge branch 'master' into wifi_mesh_update
d-a-v Jul 30, 2018
751f9af
- Add getter and setter for the WiFi channel of a ESP8266WiFiMesh ins…
aerlon Jul 31, 2018
ed15ab2
Make the code more stylish.
aerlon Jul 31, 2018
75a5549
Update README.md with the new ESP8266WiFiMesh constructor documentation.
aerlon Jul 31, 2018
a0edf99
Make attemptScan method in CompatibilityLayer use reference as argument.
aerlon Jul 31, 2018
40a25e6
Make it possible to use const String as argument to attemptScan.
aerlon Jul 31, 2018
0eadbb0
- Make code use camelCase instead of snake_case.
aerlon Jul 31, 2018
b6fc4bb
Rename Uint64ToString to uint64ToString and StringToUint64 to stringT…
aerlon Jul 31, 2018
cdbf7eb
Merge branch 'master' into wifi_mesh_update
devyte Aug 1, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions libraries/ESP8266WiFiMesh/README.md
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.
111 changes: 95 additions & 16 deletions libraries/ESP8266WiFiMesh/examples/HelloMesh/HelloMesh.ino
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";
Copy link
Collaborator

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.

Copy link
Contributor Author

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")); or String meshName(F("MeshNode_")); or some such mesh name format may be useful.

Copy link
Collaborator

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.


unsigned int request_i = 0;
unsigned int response_i = 0;

String manageRequest(String request);
String manageRequest(String request, ESP8266WiFiMesh *mesh_instance);
Copy link
Collaborator

Choose a reason for hiding this comment

The 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.
While strictly speaking it's ok to declare the mesh_instance as a pointer, it's better to declare as reference to make it explicit that the arg can never be a nullptr.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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);
Copy link
Collaborator

Choose a reason for hiding this comment

The 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) {
Copy link
Collaborator

Choose a reason for hiding this comment

The 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());
Copy link
Collaborator

Choose a reason for hiding this comment

The 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) {
Copy link
Collaborator

Choose a reason for hiding this comment

The 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) {
Copy link
Collaborator

Choose a reason for hiding this comment

The 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());
Copy link
Collaborator

Choose a reason for hiding this comment

The 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);
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

else ? (Robustness)

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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).

Copy link
Collaborator

Choose a reason for hiding this comment

The 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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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();
}
}
28 changes: 27 additions & 1 deletion libraries/ESP8266WiFiMesh/keywords.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,37 @@ ESP8266WiFiMesh KEYWORD3
#######################################

ESP8266WiFiMesh KEYWORD1
NetworkInfo KEYWORD1
TransmissionResult KEYWORD1
transmission_status_t KEYWORD1

#######################################
# Methods and Functions (KEYWORD2)
#######################################

connection_queue KEYWORD2
latest_transmission_outcomes KEYWORD2
begin KEYWORD2
attemptScan KEYWORD2
activateAP KEYWORD2
deactivateAP KEYWORD2
restartAP KEYWORD2
getMeshName KEYWORD2
getNodeID KEYWORD2
getMessage KEYWORD2
setMessage KEYWORD2
attemptTransmission KEYWORD2
acceptRequest KEYWORD2
setStaticIP KEYWORD2
getStaticIP KEYWORD2
disableStaticIP->KEYWORD2
Uint64ToString KEYWORD2
StringToUint64 KEYWORD2
getNetworkFilter KEYWORD2
setNetworkFilter KEYWORD2

#######################################
# Constants (LITERAL1)
#######################################

empty_IP LITERAL1
NETWORK_INFO_DEFAULT_INT LITERAL1
4 changes: 2 additions & 2 deletions libraries/ESP8266WiFiMesh/library.properties
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name=ESP8266WiFiMesh
version=1.0
version=2.0
author=Julian Fell
maintainer=
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.
category=Communication
Expand Down
Loading