1
0
mirror of https://github.com/esp8266/Arduino.git synced 2025-04-19 23:22:16 +03:00

- Add HelloTcpIp.ino example for the TcpIpMeshBackend.

- Update HelloEspnow.ino and HelloMesh.ino examples.

- Fix bug with TransmissionOutcomesUpdateHook not being called when the TCP/IP backend retained WiFi.status() == WL_CONNECTED.
This commit is contained in:
Anders 2019-11-03 22:26:52 +01:00
parent 6b763686de
commit 78812a7333
4 changed files with 264 additions and 6 deletions

View File

@ -146,8 +146,9 @@ void networkFilter(int numberOfNetworks, MeshBackendBase &meshInstance) {
If true is returned from this callback, the first broadcast transmission is saved until the entire broadcast message has been received.
The complete broadcast message will then be sent to the requestHandler (manageRequest in this example).
If false is returned from this callback, the broadcast message is discarded.
Note that the BroadcastFilter may be called multiple times for messages that are discarded in this way, but is only called once for accepted messages.
@param firstTransmission The first transmission of the broadcast.
@param firstTransmission The first transmission of the broadcast. Modifications to this String are passed on to the broadcast message.
@param meshInstance The EspnowMeshBackend instance that called the function.
@return True if the broadcast should be accepted. False otherwise.
@ -173,6 +174,25 @@ bool broadcastFilter(String &firstTransmission, EspnowMeshBackend &meshInstance)
}
}
/**
Once passed to the setTransmissionOutcomesUpdateHook method of the ESP-NOW backend,
this function will be called after each update of the latestTransmissionOutcomes vector during attemptTransmission.
(which happens after each individual transmission has finished)
Example use cases is modifying getMessage() between transmissions, or aborting attemptTransmission before all nodes in the connectionQueue have been contacted.
@param meshInstance The MeshBackendBase instance that called the function.
@return True if attemptTransmission should continue with the next entry in the connectionQueue. False if attemptTransmission should stop.
*/
bool exampleTransmissionOutcomesUpdateHook(MeshBackendBase &meshInstance) {
// Currently this is exactly the same as the default hook, but you can modify it to alter the behaviour of attemptTransmission.
(void)meshInstance; // This is useful to remove a "unused parameter" compiler warning. Does nothing else.
return true;
}
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.
@ -213,9 +233,11 @@ void setup() {
espnowNode.activateAP();
// Storing our message in the EspnowMeshBackend instance is not required, but can be useful for organizing code, especially when using many EspnowMeshBackend instances.
// Note that calling espnowNode.attemptTransmission will replace the stored message with whatever message is transmitted.
// Note that calling the multi-recipient versions of espnowNode.attemptTransmission and espnowNode.attemptAutoEncryptingTransmission will replace the stored message with whatever message is transmitted.
// Also note that the maximum allowed number of ASCII characters in a ESP-NOW message is given by EspnowMeshBackend::getMaxMessageLength().
espnowNode.setMessage(String(F("Hello world request #")) + String(requestNumber) + String(F(" from ")) + espnowNode.getMeshName() + espnowNode.getNodeID() + String(F(".")));
espnowNode.setTransmissionOutcomesUpdateHook(exampleTransmissionOutcomesUpdateHook);
}
int32_t timeOfLastScan = -10000;
@ -226,7 +248,7 @@ void loop() {
// Note that depending on the amount of responses to send and their length, this method can take tens or even hundreds of milliseconds to complete.
// More intense transmission activity and less frequent calls to performEspnowMaintainance will likely cause the method to take longer to complete, so plan accordingly.
//Should not be used inside responseHandler, requestHandler or networkFilter callbacks since performEspnowMaintainance() can alter the ESP-NOW state.
//Should not be used inside responseHandler, requestHandler, networkFilter or broadcastFilter callbacks since performEspnowMaintainance() can alter the ESP-NOW state.
EspnowMeshBackend::performEspnowMaintainance();
if (millis() - timeOfLastScan > 10000) { // Give other nodes some time to connect between data transfers.
@ -239,7 +261,7 @@ void loop() {
timeOfLastScan = millis();
// Wait for response. espnowDelay continuously calls performEspnowMaintainance() so we will respond to ESP-NOW request while waiting.
// Should not be used inside responseHandler, requestHandler or networkFilter callbacks since performEspnowMaintainance() can alter the ESP-NOW state.
// Should not be used inside responseHandler, requestHandler, networkFilter or broadcastFilter callbacks since performEspnowMaintainance() can alter the ESP-NOW state.
espnowDelay(100);
// One way to check how attemptTransmission worked out
@ -283,7 +305,7 @@ void loop() {
uint8_t targetBSSID[6] {0};
// We can create encrypted connections to individual nodes so that all ESP-NOW communication with the node will be encrypted.
if (espnowNode.connectionQueue()[0].getBSSID(targetBSSID) && espnowNode.requestEncryptedConnection(targetBSSID) == ECS_CONNECTION_ESTABLISHED) {
if (espnowNode.constConnectionQueue()[0].getBSSID(targetBSSID) && espnowNode.requestEncryptedConnection(targetBSSID) == ECS_CONNECTION_ESTABLISHED) {
// The WiFi scan will detect the AP MAC, but this will automatically be converted to the encrypted STA MAC by the framework.
String peerMac = macToString(targetBSSID);
@ -352,7 +374,7 @@ void loop() {
// Or if we prefer we can just let the library automatically create brief encrypted connections which are long enough to transmit an encrypted message.
// Note that encrypted responses will not be received, unless there already was an encrypted connection established with the peer before attemptAutoEncryptingTransmission was called.
// This can be remedied via the createPermanentConnections argument, though it must be noted that the maximum number of encrypted connections supported at a time is 6.
// This can be remedied via the requestPermanentConnections argument, though it must be noted that the maximum number of encrypted connections supported at a time is 6.
espnowMessage = "This message is always encrypted, regardless of receiver.";
Serial.println("\nTransmitting: " + espnowMessage);
espnowNode.attemptAutoEncryptingTransmission(espnowMessage);

View File

@ -150,8 +150,13 @@ void loop() {
// Among other things, the method cleans up old ESP-NOW log entries (freeing up RAM) and forwards received mesh messages.
// 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.
// More intense transmission activity and less frequent calls to performMeshMaintainance will likely cause the method to take longer to complete, so plan accordingly.
// The maintainance methods should not be used inside the meshMessageHandler callback, since they can alter the mesh node state. The framework will alert you during runtime if you make this mistake.
floodingMeshDelay(1);
// If you wish to transmit only to a single node, try using one of the following methods (requires the node to be within range and know the MAC of the recipient):
// Unencrypted: transmission_status_t floodingMesh.getEspnowMeshBackend().attemptTransmission(message, EspnowNetworkInfo(recipientMac));
// Encrypted (slow): floodingMesh.getEspnowMeshBackend().attemptAutoEncryptingTransmission(message, EspnowNetworkInfo(recipientMac));
if (theOne) {
if (millis() - timeOfLastProclamation > 10000) {
uint32_t startTime = millis();

View File

@ -0,0 +1,229 @@
#include <ESP8266WiFi.h>
#include <TcpIpMeshBackend.h>
#include <TypeConversionFunctions.h>
#include <assert.h>
/**
NOTE: Although we could define the strings below as normal String variables,
here we are using PROGMEM combined with the FPSTR() macro (and also just the F() macro further down in the file).
The reason is that this approach will place the strings in flash memory which will help save RAM during program execution.
Reading strings from flash will be slower than reading them from RAM,
but this will be a negligible difference when printing them to Serial.
More on F(), FPSTR() and PROGMEM:
https://github.com/esp8266/Arduino/issues/1143
https://arduino-esp8266.readthedocs.io/en/latest/PROGMEM.html
*/
const char exampleMeshName[] PROGMEM = "MeshNode_";
const char exampleWiFiPassword[] PROGMEM = "ChangeThisWiFiPassword_TODO";
unsigned int requestNumber = 0;
unsigned int responseNumber = 0;
String manageRequest(const String &request, MeshBackendBase &meshInstance);
transmission_status_t manageResponse(const String &response, MeshBackendBase &meshInstance);
void networkFilter(int numberOfNetworks, MeshBackendBase &meshInstance);
/* Create the mesh node object */
TcpIpMeshBackend tcpIpNode = TcpIpMeshBackend(manageRequest, manageResponse, networkFilter, FPSTR(exampleWiFiPassword), FPSTR(exampleMeshName), uint64ToString(ESP.getChipId()), true);
/**
Callback for when other nodes send you a request
@param request The request string received from another node in the mesh
@param meshInstance The MeshBackendBase instance that called the function.
@return The string to send back to the other node. For ESP-NOW, return an empy string ("") if no response should be sent.
*/
String manageRequest(const String &request, MeshBackendBase &meshInstance) {
// We do not store strings in flash (via F()) in this function.
// The reason is that the other node will be waiting for our response,
// so keeping the strings in RAM will give a (small) improvement in response time.
// Of course, it is advised to adjust this approach based on RAM requirements.
// To get the actual class of the polymorphic meshInstance, do as follows (meshBackendCast replaces dynamic_cast since RTTI is disabled)
if (EspnowMeshBackend *espnowInstance = meshBackendCast<EspnowMeshBackend *>(&meshInstance)) {
String messageEncrypted = espnowInstance->receivedEncryptedMessage() ? ", Encrypted" : ", Unencrypted";
Serial.print("ESP-NOW (" + espnowInstance->getSenderMac() + messageEncrypted + "): ");
} else if (TcpIpMeshBackend *tcpIpInstance = meshBackendCast<TcpIpMeshBackend *>(&meshInstance)) {
(void)tcpIpInstance; // This is useful to remove a "unused parameter" compiler warning. Does nothing else.
Serial.print("TCP/IP: ");
} else {
Serial.print("UNKNOWN!: ");
}
/* Print out received message */
// Only show first 100 characters because printing a large String takes a lot of time, which is a bad thing for a callback function.
// If you need to print the whole String it is better to store it and print it in the loop() later.
Serial.print("Request received: ");
Serial.println(request.substring(0, 100));
/* return a string to send back */
return ("Hello world response #" + String(responseNumber++) + " from " + meshInstance.getMeshName() + meshInstance.getNodeID() + " with AP MAC " + WiFi.softAPmacAddress() + ".");
}
/**
Callback for when you get a response from other nodes
@param response The response string received from another node in the mesh
@param meshInstance The MeshBackendBase instance that called the function.
@return The status code resulting from the response, as an int
*/
transmission_status_t manageResponse(const String &response, MeshBackendBase &meshInstance) {
transmission_status_t statusCode = TS_TRANSMISSION_COMPLETE;
// To get the actual class of the polymorphic meshInstance, do as follows (meshBackendCast replaces dynamic_cast since RTTI is disabled)
if (EspnowMeshBackend *espnowInstance = meshBackendCast<EspnowMeshBackend *>(&meshInstance)) {
String messageEncrypted = espnowInstance->receivedEncryptedMessage() ? ", Encrypted" : ", Unencrypted";
Serial.print("ESP-NOW (" + espnowInstance->getSenderMac() + messageEncrypted + "): ");
} else if (TcpIpMeshBackend *tcpIpInstance = meshBackendCast<TcpIpMeshBackend *>(&meshInstance)) {
Serial.print("TCP/IP: ");
// Getting the sent message like this will work as long as ONLY(!) TCP/IP is used.
// With TCP/IP the response will follow immediately after the request, so the stored message will not have changed.
// 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.
// So for ESP-NOW, adding unique identifiers in the response and request is required to associate a response with a request.
Serial.print(F("Request sent: "));
Serial.println(tcpIpInstance->getCurrentMessage().substring(0, 100));
} else {
Serial.print("UNKNOWN!: ");
}
/* Print out received message */
// Only show first 100 characters because printing a large String takes a lot of time, which is a bad thing for a callback function.
// If you need to print the whole String it is better to store it and print it in the loop() later.
Serial.print(F("Response received: "));
Serial.println(response.substring(0, 100));
return statusCode;
}
/**
Callback used to decide which networks to connect to once a WiFi scan has been completed.
@param numberOfNetworks The number of networks found in the WiFi scan.
@param meshInstance The MeshBackendBase instance that called the function.
*/
void networkFilter(int numberOfNetworks, MeshBackendBase &meshInstance) {
// Note that the network index of a given node may change whenever a new scan is done.
for (int networkIndex = 0; networkIndex < numberOfNetworks; ++networkIndex) {
String currentSSID = WiFi.SSID(networkIndex);
int meshNameIndex = currentSSID.indexOf(meshInstance.getMeshName());
/* Connect to any _suitable_ APs which contain meshInstance.getMeshName() */
if (meshNameIndex >= 0) {
uint64_t targetNodeID = stringToUint64(currentSSID.substring(meshNameIndex + meshInstance.getMeshName().length()));
if (targetNodeID < stringToUint64(meshInstance.getNodeID())) {
if (EspnowMeshBackend *espnowInstance = meshBackendCast<EspnowMeshBackend *>(&meshInstance)) {
espnowInstance->connectionQueue().push_back(networkIndex);
} else if (TcpIpMeshBackend *tcpIpInstance = meshBackendCast<TcpIpMeshBackend *>(&meshInstance)) {
tcpIpInstance->connectionQueue().push_back(networkIndex);
} else {
Serial.println(String(F("Invalid mesh backend!")));
}
}
}
}
}
/**
Once passed to the setTransmissionOutcomesUpdateHook method of the TCP/IP backend,
this function will be called after each update of the latestTransmissionOutcomes vector during attemptTransmission.
(which happens after each individual transmission has finished)
Example use cases is modifying getMessage() between transmissions, or aborting attemptTransmission before all nodes in the connectionQueue have been contacted.
@param meshInstance The MeshBackendBase instance that called the function.
@return True if attemptTransmission should continue with the next entry in the connectionQueue. False if attemptTransmission should stop.
*/
bool exampleTransmissionOutcomesUpdateHook(MeshBackendBase &meshInstance) {
// The default hook only returns true and does nothing else.
if (TcpIpMeshBackend *tcpIpInstance = meshBackendCast<TcpIpMeshBackend *>(&meshInstance)) {
if (tcpIpInstance->latestTransmissionOutcomes().back().transmissionStatus() == TS_TRANSMISSION_COMPLETE) {
// Our last request got a response, so time to create a new request.
meshInstance.setMessage(String(F("Hello world request #")) + String(++requestNumber) + String(F(" from "))
+ meshInstance.getMeshName() + meshInstance.getNodeID() + String(F(".")));
}
} else {
Serial.println(String(F("Invalid mesh backend!")));
}
return true;
}
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(50); // Wait for Serial.
//yield(); // Use this if you don't want to wait for Serial.
// The WiFi.disconnect() ensures that the WiFi is working correctly. If this is not done before receiving WiFi connections,
// those WiFi connections will take a long time to make or sometimes will not work at all.
WiFi.disconnect();
Serial.println();
Serial.println();
Serial.println(F("Note that this library can use static IP:s for the nodes to speed up connection times.\n"
"Use the setStaticIP method as shown in this example to enable this.\n"
"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(F("Setting up mesh node..."));
/* Initialise the mesh node */
tcpIpNode.begin();
tcpIpNode.activateAP(); // Each AP requires a separate server port.
tcpIpNode.setStaticIP(IPAddress(192, 168, 4, 22)); // Activate static IP mode to speed up connection times.
// Storing our message in the TcpIpMeshBackend instance is not required, but can be useful for organizing code, especially when using many TcpIpMeshBackend instances.
// Note that calling the multi-recipient tcpIpNode.attemptTransmission will replace the stored message with whatever message is transmitted.
tcpIpNode.setMessage(String(F("Hello world request #")) + String(requestNumber) + String(F(" from ")) + tcpIpNode.getMeshName() + tcpIpNode.getNodeID() + String(F(".")));
tcpIpNode.setTransmissionOutcomesUpdateHook(exampleTransmissionOutcomesUpdateHook);
}
int32_t timeOfLastScan = -10000;
void loop() {
if (millis() - timeOfLastScan > 3000 // Give other nodes some time to connect between data transfers.
|| (WiFi.status() != WL_CONNECTED && millis() - timeOfLastScan > 2000)) { // Scan for networks with two second intervals when not already connected.
// attemptTransmission(message, scan, scanAllWiFiChannels, concludingDisconnect, initialDisconnect = false)
tcpIpNode.attemptTransmission(tcpIpNode.getMessage(), true, false, false);
timeOfLastScan = millis();
// One way to check how attemptTransmission worked out
if (tcpIpNode.latestTransmissionSuccessful()) {
Serial.println(F("Transmission successful."));
}
// Another way to check how attemptTransmission worked out
if (tcpIpNode.latestTransmissionOutcomes().empty()) {
Serial.println(F("No mesh AP found."));
} else {
for (TransmissionOutcome &transmissionOutcome : tcpIpNode.latestTransmissionOutcomes()) {
if (transmissionOutcome.transmissionStatus() == TS_TRANSMISSION_FAILED) {
Serial.println(String(F("Transmission failed to mesh AP ")) + transmissionOutcome.SSID());
} else if (transmissionOutcome.transmissionStatus() == TS_CONNECTION_FAILED) {
Serial.println(String(F("Connection failed to mesh AP ")) + transmissionOutcome.SSID());
} else if (transmissionOutcome.transmissionStatus() == TS_TRANSMISSION_COMPLETE) {
// No need to do anything, transmission was successful.
} else {
Serial.println(String(F("Invalid transmission status for ")) + transmissionOutcome.SSID() + String(F("!")));
assert(F("Invalid transmission status returned from responseHandler!") && false);
}
}
}
Serial.println();
} else {
/* Accept any incoming connections */
tcpIpNode.acceptRequests();
}
}

View File

@ -451,6 +451,8 @@ void TcpIpMeshBackend::attemptTransmission(const String &message, bool scan, boo
{
transmission_status_t transmissionResult = attemptDataTransfer();
latestTransmissionOutcomes().push_back(TransmissionOutcome(constConnectionQueue().back(), transmissionResult));
getTransmissionOutcomesUpdateHook()(*this);
}
else
{