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