diff --git a/libraries/ESP8266WiFiMesh/examples/HelloEspnow/HelloEspnow.ino b/libraries/ESP8266WiFiMesh/examples/HelloEspnow/HelloEspnow.ino index bcbad36f2..d698f6a9e 100644 --- a/libraries/ESP8266WiFiMesh/examples/HelloEspnow/HelloEspnow.ino +++ b/libraries/ESP8266WiFiMesh/examples/HelloEspnow/HelloEspnow.ino @@ -14,7 +14,7 @@ https://github.com/esp8266/Arduino/issues/1143 https://arduino-esp8266.readthedocs.io/en/latest/PROGMEM.html */ -const char exampleMeshName[] PROGMEM = "MeshNode_"; // The name of the mesh network. Used as prefix for the node SSID and to find other network nodes in the example networkFilter function below. +const char exampleMeshName[] PROGMEM = "MeshNode_"; // The name of the mesh network. Used as prefix for the node SSID and to find other network nodes during ESP-NOW broadcasts and in the example networkFilter function below. const char exampleWiFiPassword[] PROGMEM = "ChangeThisWiFiPassword_TODO"; // The password has to be min 8 and max 64 characters long, otherwise an AP which uses it will not be found during scans. // A custom encryption key is required when using encrypted ESP-NOW transmissions. There is always a default Kok set, but it can be replaced if desired. @@ -35,16 +35,17 @@ 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); +bool broadcastFilter(String &firstTransmission, EspnowMeshBackend &meshInstance); /* Create the mesh node object */ -EspnowMeshBackend espnowNode = EspnowMeshBackend(manageRequest, manageResponse, networkFilter, FPSTR(exampleWiFiPassword), espnowEncryptionKey, espnowHashKey, FPSTR(exampleMeshName), uint64ToString(ESP.getChipId()), true); +EspnowMeshBackend espnowNode = EspnowMeshBackend(manageRequest, manageResponse, networkFilter, broadcastFilter, FPSTR(exampleWiFiPassword), espnowEncryptionKey, espnowHashKey, 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. - @returns The string to send back to the other node. For ESP-NOW, return an empy string ("") if no response should be sent. + @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. @@ -78,7 +79,7 @@ String manageRequest(const String &request, MeshBackendBase &meshInstance) { @param response The response string received from another node in the mesh @param meshInstance The MeshBackendBase instance that called the function. - @returns The status code resulting from the response, as an int + @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; @@ -132,6 +133,42 @@ void networkFilter(int numberOfNetworks, MeshBackendBase &meshInstance) { } } +const char broadcastMetadataDelimiter = 23; // 23 = End-of-Transmission-Block (ETB) control character in ASCII + +/** + Callback used to decide which broadcast messages to accept. Only called for the first transmission in each broadcast. + 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. + + @param firstTransmission The first transmission of the broadcast. + @param meshInstance The EspnowMeshBackend instance that called the function. + + @return True if the broadcast should be accepted. False otherwise. +*/ +bool broadcastFilter(String &firstTransmission, EspnowMeshBackend &meshInstance) { + /** + This example broadcastFilter will accept a transmission if it contains the broadcastMetadataDelimiter + and as metaData either no targetMeshName or a targetMeshName that matches the MeshName of meshInstance. + */ + + int32_t metadataEndIndex = firstTransmission.indexOf(broadcastMetadataDelimiter); + + if (metadataEndIndex == -1) { + return false; // broadcastMetadataDelimiter not found + } + + String targetMeshName = firstTransmission.substring(0, metadataEndIndex); + + if (targetMeshName != "" && meshInstance.getMeshName() != targetMeshName) { + return false; // Broadcast is for another mesh network + } else { + // Remove metadata from message and mark as accepted broadcast. + firstTransmission = firstTransmission.substring(metadataEndIndex + 1); + 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. @@ -173,6 +210,7 @@ void setup() { // 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. + // 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("."))); } @@ -188,18 +226,18 @@ void loop() { EspnowMeshBackend::performEspnowMaintainance(); if (millis() - timeOfLastScan > 10000) { // Give other nodes some time to connect between data transfers. - uint32_t startTime = millis(); - Serial.println("\nPerforming unencrypted ESP-NOW transmissions."); + + uint32_t startTime = millis(); espnowNode.attemptTransmission(espnowNode.getMessage()); Serial.println("Scan and " + String(MeshBackendBase::latestTransmissionOutcomes.size()) + " transmissions done in " + String(millis() - startTime) + " ms."); + 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. espnowDelay(100); - timeOfLastScan = millis(); - // One way to check how attemptTransmission worked out if (MeshBackendBase::latestTransmissionSuccessful()) { Serial.println(F("Transmission successful.")); @@ -222,6 +260,20 @@ void loop() { } } + Serial.println("\nPerforming ESP-NOW broadcast."); + + startTime = millis(); + + // Remove espnowNode.getMeshName() from the broadcastMetadata below to broadcast to all ESP-NOW nodes regardless of MeshName. + // Note that data that comes before broadcastMetadataDelimiter should not contain any broadcastMetadataDelimiter characters, + // otherwise the broadcastFilter function used in this example file will not work. + String broadcastMetadata = espnowNode.getMeshName() + String(broadcastMetadataDelimiter); + String broadcastMessage = String(F("Broadcast #")) + String(requestNumber) + String(F(" from ")) + espnowNode.getMeshName() + espnowNode.getNodeID() + String(F(".")); + espnowNode.broadcast(broadcastMetadata + broadcastMessage); + Serial.println("Broadcast to all mesh nodes done in " + String(millis() - startTime) + " ms."); + + espnowDelay(100); // Wait for responses (broadcasts can receive an unlimited number of responses, other transmissions can only receive one response). + Serial.println("\nPerforming encrypted ESP-NOW transmissions."); // We can create encrypted connections to individual nodes so that all ESP-NOW communication with the node will be encrypted. @@ -287,6 +339,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. espnowMessage = "This message is always encrypted, regardless of receiver."; Serial.println("\nTransmitting: " + espnowMessage); espnowNode.attemptAutoEncryptingTransmission(espnowMessage); diff --git a/libraries/ESP8266WiFiMesh/src/CompatibilityLayer.cpp b/libraries/ESP8266WiFiMesh/src/CompatibilityLayer.cpp index fd926a935..0bb480acd 100644 --- a/libraries/ESP8266WiFiMesh/src/CompatibilityLayer.cpp +++ b/libraries/ESP8266WiFiMesh/src/CompatibilityLayer.cpp @@ -27,9 +27,9 @@ /******************************************************************************************** * NOTE! * -* All method signatures in this file are deprecated and will be removed in core version 2.5.0. +* All method signatures in this file are deprecated and will be removed in core version 3.0.0. * If you are still using these methods, please consider migrating to the new API shown in -* the ESP8266WiFiMesh.h source file. +* the EspnowMeshBackend.h or TcpIpMeshBackend.h source files. * * TODO: delete this file. ********************************************************************************************/ diff --git a/libraries/ESP8266WiFiMesh/src/ESP8266WiFiMesh.cpp b/libraries/ESP8266WiFiMesh/src/ESP8266WiFiMesh.cpp index 12a41dd8e..f4d198a5c 100644 --- a/libraries/ESP8266WiFiMesh/src/ESP8266WiFiMesh.cpp +++ b/libraries/ESP8266WiFiMesh/src/ESP8266WiFiMesh.cpp @@ -337,7 +337,7 @@ void ESP8266WiFiMesh::fullStop(WiFiClient &currClient) /** * Wait for a WiFiClient to transmit * - * @returns: True if the client is ready, false otherwise. + * @return: True if the client is ready, false otherwise. * */ bool ESP8266WiFiMesh::waitForClientTransmission(WiFiClient &currClient, uint32_t maxWait) @@ -365,7 +365,7 @@ bool ESP8266WiFiMesh::waitForClientTransmission(WiFiClient &currClient, uint32_t * and pass that to the user-supplied responseHandler. * * @param currClient The client to which the message should be transmitted. - * @returns: A status code based on the outcome of the exchange. + * @return: A status code based on the outcome of the exchange. * */ transmission_status_t ESP8266WiFiMesh::exchangeInfo(WiFiClient &currClient) @@ -398,7 +398,7 @@ transmission_status_t ESP8266WiFiMesh::exchangeInfo(WiFiClient &currClient) /** * Handle data transfer process with a connected AP. * - * @returns: A status code based on the outcome of the data transfer attempt. + * @return: A status code based on the outcome of the data transfer attempt. */ transmission_status_t ESP8266WiFiMesh::attemptDataTransfer() { @@ -418,7 +418,7 @@ transmission_status_t ESP8266WiFiMesh::attemptDataTransfer() /** * Helper function that contains the core functionality for the data transfer process with a connected AP. * - * @returns: A status code based on the outcome of the data transfer attempt. + * @return: A status code based on the outcome of the data transfer attempt. */ transmission_status_t ESP8266WiFiMesh::attemptDataTransferKernel() { @@ -462,7 +462,7 @@ void ESP8266WiFiMesh::initiateConnectionToAP(const String &targetSSID, int targe * @param targetSSID The name of the AP the other node has set up. * @param targetChannel The WiFI channel of the AP the other node has set up. * @param targetBSSID The mac address of the AP the other node has set up. - * @returns: A status code based on the outcome of the connection and data transfer process. + * @return: A status code based on the outcome of the connection and data transfer process. * */ transmission_status_t ESP8266WiFiMesh::connectToNode(const String &targetSSID, int targetChannel, uint8_t *targetBSSID) diff --git a/libraries/ESP8266WiFiMesh/src/ESP8266WiFiMesh.h b/libraries/ESP8266WiFiMesh/src/ESP8266WiFiMesh.h index 7c5e90838..139b2e980 100644 --- a/libraries/ESP8266WiFiMesh/src/ESP8266WiFiMesh.h +++ b/libraries/ESP8266WiFiMesh/src/ESP8266WiFiMesh.h @@ -194,7 +194,7 @@ public: static std::vector latestTransmissionOutcomes; /** - * @returns True if latest transmission was successful (i.e. latestTransmissionOutcomes is not empty and all entries have transmissionStatus TS_TRANSMISSION_COMPLETE). False otherwise. + * @return True if latest transmission was successful (i.e. latestTransmissionOutcomes is not empty and all entries have transmissionStatus TS_TRANSMISSION_COMPLETE). False otherwise. */ static bool latestTransmissionSuccessful(); @@ -217,7 +217,7 @@ public: * If another instance takes control over the AP after the pointer is created, * the created pointer will still point to the old AP instance. * - * @returns A pointer to the ESP8266WiFiMesh instance currently in control of the ESP8266 AP, + * @return A pointer to the ESP8266WiFiMesh instance currently in control of the ESP8266 AP, * or nullptr if there is no active AP controller. */ static ESP8266WiFiMesh * getAPController(); @@ -225,7 +225,7 @@ public: /** * Check if this ESP8266WiFiMesh instance is in control of the ESP8266 AP. * - * @returns True if this ESP8266WiFiMesh instance is in control of the ESP8266 AP. False otherwise. + * @return True if this ESP8266WiFiMesh instance is in control of the ESP8266 AP. False otherwise. */ bool isAPController(); diff --git a/libraries/ESP8266WiFiMesh/src/EncryptedConnectionData.h b/libraries/ESP8266WiFiMesh/src/EncryptedConnectionData.h index 6b008b99a..312b5139a 100644 --- a/libraries/ESP8266WiFiMesh/src/EncryptedConnectionData.h +++ b/libraries/ESP8266WiFiMesh/src/EncryptedConnectionData.h @@ -48,7 +48,7 @@ public: /** * @param resultArray An uint8_t array with at least size 6. * - * @returns The interface MAC used for communicating with the peer. + * @return The interface MAC used for communicating with the peer. */ uint8_t *getEncryptedPeerMac(uint8_t *resultArray) const; uint8_t *getUnencryptedPeerMac(uint8_t *resultArray) const; diff --git a/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.cpp b/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.cpp index 7bcca0d57..6c249452d 100644 --- a/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.cpp +++ b/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.cpp @@ -35,6 +35,8 @@ static const uint8_t maxEncryptedConnections = 6; // This is limited by the ESP- static const uint64_t uint64MSB = 0x8000000000000000; +const uint8_t EspnowMeshBackend::broadcastMac[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + bool EspnowMeshBackend::_espnowTransmissionMutex = false; EspnowMeshBackend *EspnowMeshBackend::_espnowRequestManager = nullptr; @@ -51,7 +53,7 @@ std::vector EspnowMeshBackend::encryptedConnections = {} uint32_t EspnowMeshBackend::_espnowTransmissionTimeoutMs = 40; uint32_t EspnowMeshBackend::_espnowRetransmissionIntervalMs = 15; -uint32_t EspnowMeshBackend::_encryptionRequestTimeoutMs = 500; +uint32_t EspnowMeshBackend::_encryptionRequestTimeoutMs = 300; bool EspnowMeshBackend::_espnowSendConfirmed = false; @@ -69,7 +71,7 @@ uint32_t EspnowMeshBackend::_unencryptedMessageID = 0; // which takes 2000 ms + some margin to send. Also, we want to avoid old entries taking up memory if they cannot be sent, // so storage duration should not be too long. uint32_t EspnowMeshBackend::_logEntryLifetimeMs = 2500; -uint32_t EspnowMeshBackend::_responseTimeoutMs = 5000; +uint32_t EspnowMeshBackend::_broadcastResponseTimeoutMs = 1000; // This is shorter than _logEntryLifetimeMs to preserve RAM since broadcasts are not deleted from sentRequests until they expire. uint32_t EspnowMeshBackend::_timeOfLastLogClear = 0; uint32_t EspnowMeshBackend::_criticalHeapLevel = 6000; // In bytes uint32_t EspnowMeshBackend::_criticalHeapLevelBuffer = 6000; // In bytes @@ -95,15 +97,16 @@ void espnowDelay(uint32_t durationMs) } } -EspnowMeshBackend::EspnowMeshBackend(requestHandlerType requestHandler, responseHandlerType responseHandler, - networkFilterType networkFilter, const String &meshPassword, const uint8_t espnowEncryptionKey[espnowEncryptionKeyLength], +EspnowMeshBackend::EspnowMeshBackend(requestHandlerType requestHandler, responseHandlerType responseHandler, networkFilterType networkFilter, + broadcastFilterType broadcastFilter, const String &meshPassword, const uint8_t espnowEncryptionKey[espnowEncryptionKeyLength], const uint8_t espnowHashKey[espnowHashKeyLength], const String &ssidPrefix, const String &ssidSuffix, bool verboseMode, uint8 meshWiFiChannel) : MeshBackendBase(requestHandler, responseHandler, networkFilter, MB_ESP_NOW) { // Reserve the maximum possible usage early on to prevent heap fragmentation later. encryptedConnections.reserve(maxEncryptedConnections); - + + setBroadcastFilter(broadcastFilter); setSSID(ssidPrefix, "", ssidSuffix); setMeshPassword(meshPassword); setEspnowEncryptionKey(espnowEncryptionKey); @@ -206,7 +209,7 @@ void EspnowMeshBackend::espnowReceiveCallbackWrapper(uint8_t *macaddr, uint8_t * //Serial.print("Received from Mac: " + macToString(macaddr) + " ID: " + uint64ToString(receivedMessageID)); //Serial.println(transmissionEncrypted ? " Encrypted" : " Unencrypted"); - if(messageType == 'Q') // Question (request) + if(messageType == 'Q' || messageType == 'B') // Question (request) or Broadcast { if(ESP.getFreeHeap() <= criticalHeapLevel()) { @@ -215,7 +218,7 @@ void EspnowMeshBackend::espnowReceiveCallbackWrapper(uint8_t *macaddr, uint8_t * } if(currentEspnowRequestManager) - { + { if(!requestReceived(uint64StationMac, receivedMessageID)) // If the request has not already been received { if(transmissionEncrypted) @@ -262,6 +265,12 @@ void EspnowMeshBackend::espnowReceiveCallbackWrapper(uint8_t *macaddr, uint8_t * requestMac = uint64StationMac; requestSender = getOwnerOfSentRequest(requestMac, receivedMessageID); } + + // Or if it was sent as a broadcast. (A broadcast can never be encrypted) + if(!requestSender) + { + requestSender = getOwnerOfSentRequest(uint64BroadcastMac, receivedMessageID); + } } // If this node sent the request and it has not already been answered. @@ -286,7 +295,7 @@ void EspnowMeshBackend::espnowReceiveCallbackWrapper(uint8_t *macaddr, uint8_t * } else { - assert(messageType == 'Q' || messageType == 'A' || messageType == 'S' || messageType == 'P' || messageType == 'C'); + assert(messageType == 'Q' || messageType == 'A' || messageType == 'B' || messageType == 'S' || messageType == 'P' || messageType == 'C'); } //Serial.println("espnowReceiveCallbackWrapper duration " + String(millis() - callbackStart)); @@ -497,8 +506,9 @@ bool EspnowMeshBackend::activateEspnow() esp_now_register_recv_cb(espnowReceiveCallbackWrapper); esp_now_register_send_cb([](uint8_t* mac, uint8_t sendStatus) { - (void)mac; // This is useful to remove a "unused parameter" compiler warning. Does nothing else. - if(!sendStatus) // sendStatus == 0 when send was OK. + if(_espnowSendConfirmed) + return; + else if(!sendStatus && macEqual(mac, _transmissionTargetBSSID)) // sendStatus == 0 when send was OK. _espnowSendConfirmed = true; // We do not want to reset this to false. That only happens before transmissions. Otherwise subsequent failed send attempts may obscure an initial successful one. }); @@ -546,9 +556,9 @@ uint32_t EspnowMeshBackend::logEntryLifetimeMs() return _logEntryLifetimeMs; } -uint32_t EspnowMeshBackend::responseTimeoutMs() +uint32_t EspnowMeshBackend::broadcastResponseTimeoutMs() { - return _responseTimeoutMs; + return _broadcastResponseTimeoutMs; } void EspnowMeshBackend::setCriticalHeapLevelBuffer(uint32_t bufferInBytes) @@ -576,6 +586,24 @@ void EspnowMeshBackend::deleteExpiredLogEntries(std::map, } } +void EspnowMeshBackend::deleteExpiredLogEntries(std::map, RequestData> &logEntries, uint32_t requestLifetimeMs, uint32_t broadcastLifetimeMs) +{ + for(typename std::map, RequestData>::iterator entryIterator = logEntries.begin(); + entryIterator != logEntries.end(); ) + { + bool broadcast = entryIterator->first.first == uint64BroadcastMac; + uint32_t timeSinceCreation = entryIterator->second.timeSinceCreation(); + + if((!broadcast && timeSinceCreation > requestLifetimeMs) + || (broadcast && timeSinceCreation > broadcastLifetimeMs)) + { + entryIterator = logEntries.erase(entryIterator); + } + else + ++entryIterator; + } +} + template void EspnowMeshBackend::deleteExpiredLogEntries(std::list &logEntries, uint32_t maxEntryLifetimeMs) { @@ -633,7 +661,7 @@ void EspnowMeshBackend::clearOldLogEntries() deleteExpiredLogEntries(receivedEspnowTransmissions, logEntryLifetimeMs()); deleteExpiredLogEntries(receivedRequests, logEntryLifetimeMs()); // Just needs to be long enough to not accept repeated transmissions by mistake. - deleteExpiredLogEntries(sentRequests, logEntryLifetimeMs()); + deleteExpiredLogEntries(sentRequests, logEntryLifetimeMs(), broadcastResponseTimeoutMs()); deleteExpiredLogEntries(responsesToSend, logEntryLifetimeMs()); deleteExpiredLogEntries(peerRequestConfirmationsToSend, getEncryptionRequestTimeout()); } @@ -747,6 +775,9 @@ void EspnowMeshBackend::setAutoEncryptionDuration(uint32_t duration) } uint32_t EspnowMeshBackend::getAutoEncryptionDuration() {return _autoEncryptionDuration;} +void EspnowMeshBackend::setBroadcastFilter(broadcastFilterType broadcastFilter) {_broadcastFilter = broadcastFilter;} +EspnowMeshBackend::broadcastFilterType EspnowMeshBackend::getBroadcastFilter() {return _broadcastFilter;} + bool EspnowMeshBackend::usesConstantSessionKey(char messageType) { return messageType == 'A' || messageType == 'C'; @@ -781,7 +812,6 @@ transmission_status_t EspnowMeshBackend::espnowSendToNode(const String &message, } } -static const uint8_t broadcastMac[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; // Saved for future use. TODO transmission_status_t EspnowMeshBackend::espnowSendToNodeUnsynchronized(const String message, const uint8_t *targetBSSID, char messageType, uint64_t messageID, EspnowMeshBackend *espnowInstance) { using namespace EspnowProtocolInterpreter; @@ -810,7 +840,7 @@ transmission_status_t EspnowMeshBackend::espnowSendToNodeUnsynchronized(const St // We thus prefer to keep the code simple and performant instead. // Very large messages can always be split by the user as required. assert(transmissionsRequired <= getMaxTransmissionsPerMessage()); - assert(messageType == 'Q' || messageType == 'A' || messageType == 'S' || messageType == 'P' || messageType == 'C'); + assert(messageType == 'Q' || messageType == 'A' || messageType == 'B' || messageType == 'S' || messageType == 'P' || messageType == 'C'); if(messageType == 'P' || messageType == 'C') { assert(transmissionsRequired == 1); // These messages are assumed to be contained in one message by the receive callbacks. @@ -824,9 +854,9 @@ transmission_status_t EspnowMeshBackend::espnowSendToNodeUnsynchronized(const St { ////// Manage logs ////// - if(transmissionsRemaining == 0 && messageType == 'Q') + if(transmissionsRemaining == 0 && (messageType == 'Q' || messageType == 'B')) { - assert(espnowInstance); // espnowInstance required when transmitting 'Q' type messages. + assert(espnowInstance); // espnowInstance required when transmitting 'Q' and 'B' type messages. // If we are sending the last transmission of a request we should store the sent request in the log no matter if we receive an ack for the final transmission or not. // That way we will always be ready to receive the response to the request when there is a chance the request message was transmitted successfully, // even if the final ack for the request message was lost. @@ -1000,8 +1030,27 @@ void EspnowMeshBackend::espnowReceiveCallback(uint8_t *macaddr, uint8_t *dataArr if(espnowIsMessageStart(dataArray)) { - // Does nothing if key already in receivedEspnowTransmissions - receivedEspnowTransmissions.insert(std::make_pair(std::make_pair(macAndType, messageID), MessageData(dataArray, len))); + if(messageType == 'B') + { + String message = espnowGetMessageContent(dataArray, len); + setSenderMac(macaddr); + setReceivedEncryptedMessage(usesEncryption(messageID)); + bool acceptBroadcast = getBroadcastFilter()(message, *this); + if(acceptBroadcast) + { + // Does nothing if key already in receivedEspnowTransmissions + receivedEspnowTransmissions.insert(std::make_pair(std::make_pair(macAndType, messageID), MessageData(message, espnowGetTransmissionsRemaining(dataArray)))); + } + else + { + return; + } + } + else + { + // Does nothing if key already in receivedEspnowTransmissions + receivedEspnowTransmissions.insert(std::make_pair(std::make_pair(macAndType, messageID), MessageData(dataArray, len))); + } } else { @@ -1038,7 +1087,7 @@ void EspnowMeshBackend::espnowReceiveCallback(uint8_t *macaddr, uint8_t *dataArr std::map, MessageData>::iterator storedMessageIterator = receivedEspnowTransmissions.find(std::make_pair(macAndType, messageID)); assert(storedMessageIterator != receivedEspnowTransmissions.end()); - + // Copy totalMessage in case user callbacks (request/responseHandler) do something odd with receivedEspnowTransmissions list. String totalMessage = storedMessageIterator->second.getTotalMessage(); // https://stackoverflow.com/questions/134731/returning-a-const-reference-to-an-object-instead-of-a-copy It is likely that most compilers will perform Named Value Return Value Optimisation in this case @@ -1046,7 +1095,7 @@ void EspnowMeshBackend::espnowReceiveCallback(uint8_t *macaddr, uint8_t *dataArr //Serial.println("methodStart erase done " + String(millis() - methodStart)); - if(messageType == 'Q') // Question (request) + if(messageType == 'Q' || messageType == 'B') // Question (request) or Broadcast { storeReceivedRequest(uint64Mac, messageID, TimeTracker(millis())); //Serial.println("methodStart request stored " + String(millis() - methodStart)); @@ -1082,7 +1131,7 @@ void EspnowMeshBackend::espnowReceiveCallback(uint8_t *macaddr, uint8_t *dataArr } else { - assert(messageType == 'Q' || messageType == 'A'); + assert(messageType == 'Q' || messageType == 'A' || messageType == 'B'); } ESP.wdtFeed(); // Prevents WDT reset in case we receive a lot of transmissions without break. @@ -1286,7 +1335,7 @@ encrypted_connection_status_t EspnowMeshBackend::addEncryptedConnection(uint8_t // No capacity for more encrypted connections. return ECS_MAX_CONNECTIONS_REACHED_SELF; } - // int esp_now_add_peer(u8 *mac_addr, u8 role, u8 channel, u8 *key, u8 key_len), returns 0 on success + // returns 0 on success: int esp_now_add_peer(u8 *mac_addr, u8 role, u8 channel, u8 *key, u8 key_len) // Only MAC, encryption key and key length (16) actually matter. The rest is not used by ESP-NOW. else if(0 == esp_now_add_peer(peerStaMac, ESP_NOW_ROLE_CONTROLLER, getWiFiChannel(), getEspnowEncryptionKey(encryptionKeyArray), espnowEncryptionKeyLength)) { @@ -1932,7 +1981,7 @@ void EspnowMeshBackend::attemptTransmission(const String &message, bool scan, bo } } -void EspnowMeshBackend::attemptAutoEncryptingTransmission(const String &message, bool scan, bool scanAllWiFiChannels) +void EspnowMeshBackend::attemptAutoEncryptingTransmission(const String &message, bool scan, bool scanAllWiFiChannels, bool createPermanentConnections) { MutexTracker outerMutexTracker(_espnowTransmissionMutex, handlePostponedRemovals); if(!outerMutexTracker.mutexCaptured()) @@ -1990,7 +2039,10 @@ void EspnowMeshBackend::attemptAutoEncryptingTransmission(const String &message, innerMutexTracker.releaseMutex(); - connectionStatus = requestFlexibleTemporaryEncryptedConnection(currentBSSID, getAutoEncryptionDuration()); + if(createPermanentConnections) + connectionStatus = requestEncryptedConnection(currentBSSID); + else + connectionStatus = requestFlexibleTemporaryEncryptedConnection(currentBSSID, getAutoEncryptionDuration()); innerMutexTracker = MutexTracker(_espnowTransmissionMutex); if(!innerMutexTracker.mutexCaptured()) @@ -2023,7 +2075,7 @@ void EspnowMeshBackend::attemptAutoEncryptingTransmission(const String &message, latestTransmissionOutcomes.push_back(TransmissionResult{.origin = currentNetwork, .transmissionStatus = TS_CONNECTION_FAILED}); } - if(!encryptedConnection) + if(!encryptedConnection && !createPermanentConnections) { // Remove any connection that was added during the transmission attempt. removeEncryptedConnectionUnprotected(currentBSSID); @@ -2041,6 +2093,18 @@ void EspnowMeshBackend::attemptAutoEncryptingTransmission(const String &message, } } +void EspnowMeshBackend::broadcast(const String &message) +{ + MutexTracker mutexTracker(_espnowTransmissionMutex, handlePostponedRemovals); + if(!mutexTracker.mutexCaptured()) + { + assert(false && "ERROR! Transmission in progress. Don't call broadcast from callbacks as this may corrupt program state! Aborting."); + return; + } + + espnowSendToNode(message, broadcastMac, 'B', this); +} + void EspnowMeshBackend::sendEspnowResponses() { //uint32_t startTime = millis(); diff --git a/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.h b/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.h index a340029a2..6292a5eb3 100644 --- a/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.h +++ b/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.h @@ -96,6 +96,10 @@ class RequestData; class EspnowMeshBackend : public MeshBackendBase { +protected: + + typedef std::function broadcastFilterType; + public: /** @@ -106,6 +110,7 @@ public: * @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 broadcastFilter The callback handler for deciding which ESP-NOW broadcasts to accept. * @param meshPassword The WiFi password for the mesh network. * @param ssidPrefix The prefix (first part) of the node SSID. * @param ssidSuffix The suffix (last part) of the node SSID. @@ -118,7 +123,7 @@ public: * make it impossible for other stations to detect the APs whose WiFi channels have changed. * */ - EspnowMeshBackend(requestHandlerType requestHandler, responseHandlerType responseHandler, networkFilterType networkFilter, + EspnowMeshBackend(requestHandlerType requestHandler, responseHandlerType responseHandler, networkFilterType networkFilter, broadcastFilterType broadcastFilter, const String &meshPassword, const uint8_t espnowEncryptionKey[EspnowProtocolInterpreter::espnowEncryptionKeyLength], const uint8_t espnowHashKey[EspnowProtocolInterpreter::espnowHashKeyLength], const String &ssidPrefix, const String &ssidSuffix, bool verboseMode = false, uint8 meshWiFiChannel = 1); @@ -167,21 +172,45 @@ public: /** * Deactivates Espnow for this node. Call begin() on a EspnowMeshBackend instance to reactivate Espnow. * - * @returns True if deactivation was successful. False otherwise. + * @return True if deactivation was successful. False otherwise. */ static bool deactivateEspnow(); void attemptTransmission(const String &message, bool scan = true, bool scanAllWiFiChannels = false) override; - // Will ensure that an encrypted connection exists to each target node before sending the message, - // establishing a temporary encrypted connection with duration getAutoEncryptionDuration() first if neccessary. - // If an encrypted connection cannot be established to a target node, no message will be sent to that node. - // Note that if an encrypted connection to a target node is not present before this method is called, the response from said node will likely not be received - // since it will be encrypted and the auto encrypted connection to the node is immediately removed after transmission. - // Also note that if a temporary encrypted connection already exists to a target node, this method will slightly extend the connection duration - // depending on the time it takes to verify the connection to the node. This can substantially increase the connection duration if many auto encrypting - // transmissions occurs. - void attemptAutoEncryptingTransmission(const String &message, bool scan = true, bool scanAllWiFiChannels = false); + /* + * Will ensure that an encrypted connection exists to each target node before sending the message, + * establishing a temporary encrypted connection with duration getAutoEncryptionDuration() first if neccessary. + * If an encrypted connection cannot be established to a target node, no message will be sent to that node. + * Note that if an encrypted connection to a target node is not present before this method is called, the response from said node will likely not be received + * since it will be encrypted and the auto encrypted connection to the node is immediately removed after transmission (unless the createPermanentConnections argument is set to true). + * Also note that if a temporary encrypted connection already exists to a target node, this method will slightly extend the connection duration + * depending on the time it takes to verify the connection to the node. This can substantially increase the connection duration if many auto encrypting + * transmissions occurs. + * + * @param message The message to send to other nodes. It will be stored in the class instance until replaced via attemptTransmission or setMessage. + * @param scan Scan for new networks and call the networkFilter function with the scan results. When set to false, only the data already in connectionQueue will be used for the transmission. + * @param scanAllWiFiChannels Scan all WiFi channels during a WiFi scan, instead of just the channel the MeshBackendBase instance is using. + * Scanning all WiFi channels takes about 2100 ms, compared to just 60 ms if only channel 1 (standard) is scanned. + * 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. + * This can make it impossible for other nodes to detect the AP if they are scanning the wrong WiFi channel. + * @param createPermanentConnections Ensures encrypted connections used for this transmission are permanent and not removed once the transmission is complete. + * This guarantees that encrypted responses to the transmission is received, as long as the encrypted connection is not removed by other means. + * Note that a maximum of 6 encrypted ESP-NOW connections can be maintained at the same time by the node. + * Defaults to false. + */ + void attemptAutoEncryptingTransmission(const String &message, bool scan = true, bool scanAllWiFiChannels = false, bool createPermanentConnections = false); + + /** + * Send a message simultaneously to all nearby nodes which have ESP-NOW activated. + * A broadcast is always treated as a request by the receiving node. + * There is no limit to the number of responses a node can get when sending a broadcast, it will always accept new responses until the broadcastResponseTimeout is reached. + * This also means that the broadcaster can receive duplicate responses from the same node if transmission conditions are poor and an ack is lost. + * A broadcast can never be sent encrypted. + * + * @param message The message to send to the other nodes. Unlike the attemptTransmission method, the message will not be stored in the class instance, since there is no certain way to change the message during an ongoing broadcast. + */ + void broadcast(const String &message); /** * Set the EspnowMeshBackend instance responsible for handling incoming requests. The requestHandler of the instance will be called upon receiving ESP-NOW requests. @@ -196,7 +225,7 @@ public: /** * Check if this EspnowMeshBackend instance is the espnowRequestManager. * - * @returns True if this EspnowMeshBackend is the espnowRequestManager. False otherwise. + * @return True if this EspnowMeshBackend is the espnowRequestManager. False otherwise. */ bool isEspnowRequestManager(); @@ -260,7 +289,7 @@ public: /** * Hint: Use String.length() to get the ASCII length of a String. * - * @returns The maximum number of bytes (or ASCII characters) a transmission can contain. Note that non-ASCII characters usually require the space of at least two ASCII characters each. + * @return The maximum number of bytes (or ASCII characters) a transmission can contain. Note that non-ASCII characters usually require the space of at least two ASCII characters each. */ static uint32_t getMaxMessageBytesPerTransmission(); @@ -282,11 +311,11 @@ public: /** * Hint: Use String.length() to get the ASCII length of a String. * - * @returns The maximum length in bytes an ASCII message is allowed to be when transmitted by this node. Note that non-ASCII characters usually require at least two bytes each. + * @return The maximum length in bytes an ASCII message is allowed to be when transmitted/broadcasted by this node. Note that non-ASCII characters usually require at least two bytes each. */ static uint32_t getMaxMessageLength(); - /** + /** * Set whether the normal events occurring in the library will be printed to Serial or not. Off by default. * This setting is shared by all EspnowMeshBackend instances. * @@ -306,7 +335,7 @@ public: /** * Same as verboseMode(), but used for printing from static functions. * - * @returns True if the normal events occurring in the library will be printed to Serial. False otherwise. + * @return True if the normal events occurring in the library will be printed to Serial. False otherwise. */ static bool staticVerboseMode(); @@ -322,7 +351,7 @@ public: * Get the message of the response at responseIndex among the responses that are scheduled for transmission from this node. * * @param responseIndex The index of the response. Must be lower than numberOfScheduledResponses(). - * @returns A String containing the message of the response at responseIndex. + * @return A String containing the message of the response at responseIndex. */ static String getScheduledResponseMessage(uint32_t responseIndex); @@ -330,14 +359,14 @@ public: * Get the MAC address for the recipient of the response at responseIndex among the responses that are scheduled for transmission from this node. * * @param responseIndex The index of the response. Must be lower than numberOfScheduledResponses(). - * @returns An array with six bytes containing the MAC address for the recipient of the response at responseIndex. + * @return An array with six bytes containing the MAC address for the recipient of the response at responseIndex. */ static const uint8_t *getScheduledResponseRecipient(uint32_t responseIndex); /** * Get the number of ESP-NOW responses that are scheduled for transmission from this node. * - * @returns The number of ESP-NOW responses scheduled for transmission. + * @return The number of ESP-NOW responses scheduled for transmission. */ static uint32_t numberOfScheduledResponses(); @@ -383,32 +412,35 @@ public: void setAutoEncryptionDuration(uint32_t duration); uint32_t getAutoEncryptionDuration(); + + void setBroadcastFilter(broadcastFilterType broadcastFilter); + broadcastFilterType getBroadcastFilter(); /** - * Get the MAC address of the sender of the most recently received ESP-NOW request or response to this EspnowMeshBackend instance. + * Get the MAC address of the sender of the most recently received ESP-NOW request, response or broadcast to this EspnowMeshBackend instance. * Returns a String. * By default the MAC will be that of the sender's station interface. The only exception is for unencrypted * responses to requests sent to an AP interface, which will return the response sender's AP interface MAC. * - * @returns A String filled with a hexadecimal representation of the MAC, without delimiters. + * @return A String filled with a hexadecimal representation of the MAC, without delimiters. */ String getSenderMac(); /** - * Get the MAC address of the sender of the most recently received ESP-NOW request or response to this EspnowMeshBackend instance. + * Get the MAC address of the sender of the most recently received ESP-NOW request, response or broadcast to this EspnowMeshBackend instance. * Returns a uint8_t array. * By default the MAC will be that of the sender's station interface. The only exception is for unencrypted * responses to requests sent to an AP interface, which will return the response sender's AP interface MAC. * * @param macArray The array that should store the MAC address. Must be at least 6 bytes. - * @returns macArray filled with the sender MAC. + * @return macArray filled with the sender MAC. */ uint8_t *getSenderMac(uint8_t *macArray); /** - * Get whether the ESP-NOW request or response which was most recently received by this EspnowMeshBackend instance was encrypted or not. + * Get whether the ESP-NOW request, response or broadcast which was most recently received by this EspnowMeshBackend instance was encrypted or not. * - * @returns If true, the request or response was encrypted. If false, it was unencrypted. + * @return If true, the request, response or broadcast was encrypted. If false, it was unencrypted. */ bool receivedEncryptedMessage(); @@ -457,7 +489,7 @@ public: */ static uint8_t numberOfEncryptedConnections(); - // @returns resultArray filled with the MAC to the encrypted interface of the node, if an encrypted connection exists. nulltpr otherwise. + // @return resultArray filled with the MAC to the encrypted interface of the node, if an encrypted connection exists. nulltpr otherwise. static uint8_t *getEncryptedMac(const uint8_t *peerMac, uint8_t *resultArray); // Create a string containing the current state of the encrypted connection for this node. The result can be used as input to addEncryptedConnection. @@ -492,7 +524,7 @@ public: static espnow_connection_type_t getConnectionInfo(uint32_t connectionIndex, uint32_t *remainingDuration = nullptr, uint8_t *peerMac = nullptr); /** - * @returns The proportion of ESP-NOW requests made by this node that have failed, since power on or latest reset. + * @return The proportion of ESP-NOW requests made by this node that have failed, since power on or latest reset. */ static double getTransmissionFailRate(); @@ -506,6 +538,9 @@ protected: typedef std::vector::iterator connectionLogIterator; static connectionLogIterator connectionLogEndIterator(); + static const uint8_t broadcastMac[6]; + static const uint64_t uint64BroadcastMac = 0xFFFFFFFFFFFF; + bool activateEspnow(); /* @@ -533,16 +568,16 @@ protected: static encrypted_connection_removal_outcome_t removeEncryptedConnectionUnprotected(connectionLogIterator &connectionIterator, std::vector::iterator *resultingIterator); /** - * Set the MAC address considered to be the sender of the most recently received ESP-NOW request or response. + * Set the MAC address considered to be the sender of the most recently received ESP-NOW request, response or broadcast. * * @param macArray An uint8_t array which contains the MAC address to store. The method will store the first 6 bytes of the array. */ void setSenderMac(uint8_t *macArray); /** - * Set whether the most recently received ESP-NOW request or response is presented as having been encrypted or not. + * Set whether the most recently received ESP-NOW request, response or broadcast is presented as having been encrypted or not. * - * @param receivedEncryptedMessage If true, the request or response is presented as having been encrypted. + * @param receivedEncryptedMessage If true, the request, response or broadcast is presented as having been encrypted. */ void setReceivedEncryptedMessage(bool receivedEncryptedMessage); @@ -556,7 +591,7 @@ protected: /** * Check if there is an ongoing ESP-NOW transmission in the library. Used to avoid interrupting transmissions. * - * @returns True if a transmission initiated by a public method is in progress. + * @return True if a transmission initiated by a public method is in progress. */ static bool transmissionInProgress(); @@ -587,8 +622,8 @@ protected: * Send an ESP-NOW message to the ESP8266 that has the MAC address specified in targetBSSID. * * @param messageType The identifier character for the type of message to send. Choices are 'Q' for question (request), - * 'A' for answer (response), 'S' for synchronization request, 'P' for peer request and 'C' for peer request confirmation. - * @returns The transmission status for the transmission. + * 'A' for answer (response), 'B' for broadcast, 'S' for synchronization request, 'P' for peer request and 'C' for peer request confirmation. + * @return The transmission status for the transmission. */ // Send a message to the node having targetBSSID as mac, changing targetBSSID to the mac of the encrypted connection if it exists and ensuring such an encrypted connection is synchronized. static transmission_status_t espnowSendToNode(const String &message, const uint8_t *targetBSSID, char messageType, EspnowMeshBackend *espnowInstance = nullptr); @@ -648,11 +683,11 @@ private: static EncryptedConnectionLog *getEncryptedConnection(const uint8_t *peerMac); static EncryptedConnectionLog *getTemporaryEncryptedConnection(const uint8_t *peerMac); - //@returns iterator to connection in connectionVector, or connectionVector.end() if element not found + //@return iterator to connection in connectionVector, or connectionVector.end() if element not found template static typename std::vector::iterator getEncryptedConnectionIterator(const uint8_t *peerMac, typename std::vector &connectionVector); static bool getEncryptedConnectionIterator(const uint8_t *peerMac, connectionLogIterator &iterator); - // @returns true if an encrypted connection to peerMac is found and the found connection is temporary. Only changes iterator if true is returned. + // @return true if an encrypted connection to peerMac is found and the found connection is temporary. Only changes iterator if true is returned. static bool getTemporaryEncryptedConnectionIterator(const uint8_t *peerMac, connectionLogIterator &iterator); static espnow_connection_type_t getConnectionInfoHelper(const EncryptedConnectionLog *encryptedConnection, uint32_t *remainingDuration, uint8_t *peerMac = nullptr); @@ -665,13 +700,15 @@ private: template static void deleteExpiredLogEntries(std::map, T> &logEntries, uint32_t maxEntryLifetimeMs); + static void deleteExpiredLogEntries(std::map, RequestData> &logEntries, uint32_t requestLifetimeMs, uint32_t broadcastLifetimeMs); + template static void deleteExpiredLogEntries(std::list &logEntries, uint32_t maxEntryLifetimeMs); static uint32_t _logEntryLifetimeMs; static uint32_t logEntryLifetimeMs(); - static uint32_t _responseTimeoutMs; - static uint32_t responseTimeoutMs(); + static uint32_t _broadcastResponseTimeoutMs; + static uint32_t broadcastResponseTimeoutMs(); static uint32_t _encryptionRequestTimeoutMs; @@ -681,6 +718,8 @@ private: static bool _espnowSendConfirmed; + broadcastFilterType _broadcastFilter; + static String _ongoingPeerRequestNonce; static EspnowMeshBackend *_ongoingPeerRequester; static encrypted_connection_status_t _ongoingPeerRequestResult; @@ -711,14 +750,14 @@ private: /** * Get a pointer to the EspnowMeshBackend instance that sent a request with the given requestID to the specified mac address. * - * @returns A valid EspnowMeshBackend pointer if a matching entry is found in the EspnowMeshBackend sentRequests container. nullptr otherwise. + * @return A valid EspnowMeshBackend pointer if a matching entry is found in the EspnowMeshBackend sentRequests container. nullptr otherwise. */ static EspnowMeshBackend *getOwnerOfSentRequest(uint64_t requestMac, uint64_t requestID); /** * Delete all entries in the sentRequests container where requestMac is noted as having received requestID. * - * @returns The number of entries deleted. + * @return The number of entries deleted. */ static size_t deleteSentRequest(uint64_t requestMac, uint64_t requestID); @@ -731,7 +770,7 @@ private: * @param encryptionRequestBuilder A function which is responsible for constructing the request message to send. * Called twice when the request is successful. First to build the initial request message and then to build the connection verification message. * The request message should typically be of the form: JsonTranslator::createEncryptionRequestIntro() + JsonTranslator::createEncryptionRequestEnding(). - * @returns The ultimate status of the requested encrypted connection, as encrypted_connection_status_t. + * @return The ultimate status of the requested encrypted connection, as encrypted_connection_status_t. */ encrypted_connection_status_t requestEncryptedConnectionKernel(uint8_t *peerMac, const encryptionRequestBuilderType &encryptionRequestBuilder); @@ -739,7 +778,7 @@ private: * Generate a new message ID to be used when making a data transmission. The generated ID will be different depending on whether an encrypted connection exists or not. * * @param encryptedConnection A pointer to the EncryptedConnectionLog of the encrypted connection. Can be set to nullptr if the connection is unecrypted. - * @returns The generated message ID. + * @return The generated message ID. */ static uint64_t generateMessageID(EncryptedConnectionLog *encryptedConnection); @@ -748,7 +787,7 @@ private: * Should only be used when initializing a new connection. * Use generateMessageID instead when the encrypted connection is already initialized to keep the connection synchronized. * - * @returns A uint64_t containing a new session key for an encrypted connection. + * @return A uint64_t containing a new session key for an encrypted connection. */ static uint64_t createSessionKey(); diff --git a/libraries/ESP8266WiFiMesh/src/EspnowProtocolInterpreter.h b/libraries/ESP8266WiFiMesh/src/EspnowProtocolInterpreter.h index 45fa46b55..5ed6acdea 100644 --- a/libraries/ESP8266WiFiMesh/src/EspnowProtocolInterpreter.h +++ b/libraries/ESP8266WiFiMesh/src/EspnowProtocolInterpreter.h @@ -52,7 +52,7 @@ namespace EspnowProtocolInterpreter const uint8_t espnowTransmissionsRemainingIndex = 1; const uint8_t espnowTransmissionMacIndex = 2; const uint8_t espnowMessageIDIndex = 8; - + uint8_t espnowProtocolBytesSize(); const uint8_t espnowEncryptionKeyLength = 16; // This is restricted to exactly 16 bytes by the ESP-NOW API. It should not be changed unless the ESP-NOW API is changed. diff --git a/libraries/ESP8266WiFiMesh/src/JsonTranslator.h b/libraries/ESP8266WiFiMesh/src/JsonTranslator.h index e03b468b0..7e75ab9f1 100644 --- a/libraries/ESP8266WiFiMesh/src/JsonTranslator.h +++ b/libraries/ESP8266WiFiMesh/src/JsonTranslator.h @@ -62,7 +62,7 @@ namespace JsonTranslator * @param valueIdentifier The identifier to search for. * @param searchStartIndex Optional argument that makes it possible to decide at which index of jsonString the search starts. Search will begin at index 0 if not provided. * - * @returns An int32_t containing the index within jsonString where the value of valueIdentifier starts, or a negative value if valueIdentifier was not found. + * @return An int32_t containing the index within jsonString where the value of valueIdentifier starts, or a negative value if valueIdentifier was not found. */ int32_t getStartIndex(const String &jsonString, const String &valueIdentifier, int32_t searchStartIndex = 0); @@ -72,7 +72,7 @@ namespace JsonTranslator * @param jsonString The String to search within. * @param searchStartIndex The index of jsonString where the search will start. * - * @returns An int32_t containing the index within jsonString where the next JSON termination character is found, or a negative value if no such character was found. + * @return An int32_t containing the index within jsonString where the next JSON termination character is found, or a negative value if no such character was found. */ int32_t getEndIndex(const String &jsonString, int32_t searchStartIndex); @@ -83,7 +83,7 @@ namespace JsonTranslator * @param jsonString The String to search within. * @param result The String where the value should be stored. * - * @returns True if a value was found. False otherwise. + * @return True if a value was found. False otherwise. */ bool getPassword(const String &jsonString, String &result); bool getOwnSessionKey(const String &jsonString, uint64_t &result); @@ -96,7 +96,7 @@ namespace JsonTranslator * @param jsonString The String to search within. * @param resultArray The uint8_t array where the value should be stored. Must be at least 6 bytes. * - * @returns True if a value was found. False otherwise. + * @return True if a value was found. False otherwise. */ bool getPeerStaMac(const String &jsonString, uint8_t *resultArray); bool getPeerApMac(const String &jsonString, uint8_t *resultArray); diff --git a/libraries/ESP8266WiFiMesh/src/MeshBackendBase.cpp b/libraries/ESP8266WiFiMesh/src/MeshBackendBase.cpp index f9454ddbd..d4212743a 100644 --- a/libraries/ESP8266WiFiMesh/src/MeshBackendBase.cpp +++ b/libraries/ESP8266WiFiMesh/src/MeshBackendBase.cpp @@ -134,6 +134,8 @@ void MeshBackendBase::setSSID(const String &newSSIDPrefix, const String &newSSID String newSSID = _SSIDPrefix + _SSIDRoot + _SSIDSuffix; + assert(newSSID.length() <= 31); + if(getSSID() != newSSID) { _SSID = newSSID; diff --git a/libraries/ESP8266WiFiMesh/src/MeshBackendBase.h b/libraries/ESP8266WiFiMesh/src/MeshBackendBase.h index cfaae01e8..393309822 100644 --- a/libraries/ESP8266WiFiMesh/src/MeshBackendBase.h +++ b/libraries/ESP8266WiFiMesh/src/MeshBackendBase.h @@ -61,7 +61,7 @@ public: static std::vector latestTransmissionOutcomes; /** - * @returns True if latest transmission was successful (i.e. latestTransmissionOutcomes is not empty and all entries have transmissionStatus TS_TRANSMISSION_COMPLETE). False otherwise. + * @return True if latest transmission was successful (i.e. latestTransmissionOutcomes is not empty and all entries have transmissionStatus TS_TRANSMISSION_COMPLETE). False otherwise. */ static bool latestTransmissionSuccessful(); @@ -84,7 +84,7 @@ public: * If another instance takes control over the AP after the pointer is created, * the created pointer will still point to the old AP instance. * - * @returns A pointer to the MeshBackendBase instance currently in control of the ESP8266 AP, + * @return A pointer to the MeshBackendBase instance currently in control of the ESP8266 AP, * or nullptr if there is no active AP controller. */ static MeshBackendBase *getAPController(); @@ -92,13 +92,14 @@ public: /** * Check if this MeshBackendBase instance is in control of the ESP8266 AP. * - * @returns True if this MeshBackendBase instance is in control of the ESP8266 AP. False otherwise. + * @return True if this MeshBackendBase instance is in control of the ESP8266 AP. False otherwise. */ bool isAPController(); /** * Change the WiFi channel used by this MeshBackendBase instance. - * Will also change the WiFi channel for the active AP if this MeshBackendBase instance is the current AP controller and it is possible to change channel. + * Will also change the WiFi channel for the active AP (via an AP restart) + * if this MeshBackendBase instance is the current AP controller and it is possible to change channel. * * 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 MeshBackendBase instances exist on the same ESP8266 and use different WiFi channels. @@ -114,7 +115,9 @@ public: /** * Change the SSID used by this MeshBackendBase instance. - * Will also change the SSID for the active AP if this MeshBackendBase instance is the current AP controller. + * The SSID can be at most 31 characters long. + * Will also change the SSID for the active AP (via an AP restart) + * if this MeshBackendBase instance is the current AP controller. * * @param newSSIDPrefix The first part of the new SSID. * @param newSSIDRoot The middle part of the new SSID. @@ -126,7 +129,8 @@ public: /** * Change the first part of the SSID used by this MeshBackendBase instance. - * Will also change the first part of the SSID for the active AP if this MeshBackendBase instance is the current AP controller. + * Will also change the first part of the SSID for the active AP (via an AP restart) + * if this MeshBackendBase instance is the current AP controller. * * @param newSSIDPrefix The new first part of the SSID. */ @@ -135,7 +139,8 @@ public: /** * Change the middle part of the SSID used by this MeshBackendBase instance. - * Will also change the middle part of the SSID for the active AP if this MeshBackendBase instance is the current AP controller. + * Will also change the middle part of the SSID for the active AP (via an AP restart) + * if this MeshBackendBase instance is the current AP controller. * * @param newSSIDPrefix The new middle part of the SSID. */ @@ -144,7 +149,8 @@ public: /** * Change the last part of the SSID used by this MeshBackendBase instance. - * Will also change the last part of the SSID for the active AP if this MeshBackendBase instance is the current AP controller. + * Will also change the last part of the SSID for the active AP (via an AP restart) + * if this MeshBackendBase instance is the current AP controller. * * @param newSSIDSuffix The new last part of the SSID. */ @@ -153,7 +159,9 @@ public: /** * Change the mesh name used by this MeshBackendBase instance. - * Will also change the mesh name for the active AP if this MeshBackendBase instance is the current AP controller. + * Will also change the mesh name for the active AP (via an AP restart) + * if this MeshBackendBase instance is the current AP controller. + * * Used as alias for setSSIDPrefix by default. Feel free to override this method in a subclass if your mesh name is not equal to SSIDPrefix. * * @param newMeshName The mesh name to change to. @@ -163,7 +171,9 @@ public: /** * Change the node id used by this MeshBackendBase instance. - * Will also change the node id for the active AP if this MeshBackendBase instance is the current AP controller. + * Will also change the node id for the active AP (via an AP restart) + * if this MeshBackendBase instance is the current AP controller. + * * Used as alias for setSSIDSuffix by default. Feel free to override this method in a subclass if your node id is not equal to SSIDSuffix. * * @param newNodeID The node id to change to. @@ -173,7 +183,8 @@ public: /** * Set the password used when connecting to other AP:s and when other nodes connect to the AP of this node. - * Will also change the setting for the active AP if this MeshBackendBase instance is the current AP controller. + * Will also change the setting for the active AP (via an AP restart) + * if this MeshBackendBase instance is the current AP controller. * * @param newMeshPassword The password to use. */ @@ -213,7 +224,8 @@ public: /** * Set whether the AP controlled by this MeshBackendBase instance will have a WiFi network with hidden SSID. * This is false by default. - * Will also change the setting for the active AP if this MeshBackendBase instance is the current AP controller. + * Will also change the setting for the active AP (via an AP restart) + * if this MeshBackendBase instance is the current AP controller. * * @param apHidden If true, the WiFi network created will have a hidden SSID. */ diff --git a/libraries/ESP8266WiFiMesh/src/MessageData.cpp b/libraries/ESP8266WiFiMesh/src/MessageData.cpp index f0070c2f5..89508bd43 100644 --- a/libraries/ESP8266WiFiMesh/src/MessageData.cpp +++ b/libraries/ESP8266WiFiMesh/src/MessageData.cpp @@ -27,7 +27,16 @@ #include "EspnowMeshBackend.h" #include -MessageData::MessageData(uint8_t *initialTransmission, uint8_t transmissionLength,uint32_t creationTimeMs) : +MessageData::MessageData(String &message, uint8_t transmissionsRemaining, uint32_t creationTimeMs) : + TimeTracker(creationTimeMs) +{ + _transmissionsExpected = transmissionsRemaining + 1; + assert(message.length() <= EspnowMeshBackend::getMaxMessageBytesPerTransmission()); // Should catch some cases where transmission is not null terminated. + _totalMessage += message; + _transmissionsReceived++; +} + +MessageData::MessageData(uint8_t *initialTransmission, uint8_t transmissionLength, uint32_t creationTimeMs) : TimeTracker(creationTimeMs) { _transmissionsExpected = EspnowProtocolInterpreter::espnowGetTransmissionsRemaining(initialTransmission) + 1; diff --git a/libraries/ESP8266WiFiMesh/src/MessageData.h b/libraries/ESP8266WiFiMesh/src/MessageData.h index 2d9ad9da7..71e291fb1 100644 --- a/libraries/ESP8266WiFiMesh/src/MessageData.h +++ b/libraries/ESP8266WiFiMesh/src/MessageData.h @@ -32,6 +32,7 @@ class MessageData : public TimeTracker { public: + MessageData(String &message, uint8_t transmissionsRemaining, uint32_t creationTimeMs = millis()); MessageData(uint8_t *initialTransmission, uint8_t transmissionLength, uint32_t creationTimeMs = millis()); /** * @transmission A string of characters, including initial protocol bytes. diff --git a/libraries/ESP8266WiFiMesh/src/MutexTracker.h b/libraries/ESP8266WiFiMesh/src/MutexTracker.h index b7144cb27..a3922dc63 100644 --- a/libraries/ESP8266WiFiMesh/src/MutexTracker.h +++ b/libraries/ESP8266WiFiMesh/src/MutexTracker.h @@ -63,7 +63,7 @@ class MutexTracker /** * Attempt to capture the mutex. * - * @returns True if mutex was caught (meaning no other instance is holding the mutex). False otherwise. + * @return True if mutex was caught (meaning no other instance is holding the mutex). False otherwise. */ bool attemptMutexCapture(bool &mutexToCapture); }; diff --git a/libraries/ESP8266WiFiMesh/src/TcpIpMeshBackend.cpp b/libraries/ESP8266WiFiMesh/src/TcpIpMeshBackend.cpp index b00f8744a..4f73877e7 100644 --- a/libraries/ESP8266WiFiMesh/src/TcpIpMeshBackend.cpp +++ b/libraries/ESP8266WiFiMesh/src/TcpIpMeshBackend.cpp @@ -166,7 +166,7 @@ void TcpIpMeshBackend::fullStop(WiFiClient &currClient) /** * Wait for a WiFiClient to transmit * - * @returns: True if the client is ready, false otherwise. + * @return True if the client is ready, false otherwise. * */ bool TcpIpMeshBackend::waitForClientTransmission(WiFiClient &currClient, uint32_t maxWait) @@ -194,7 +194,7 @@ bool TcpIpMeshBackend::waitForClientTransmission(WiFiClient &currClient, uint32_ * and pass that to the user-supplied responseHandler. * * @param currClient The client to which the message should be transmitted. - * @returns: A status code based on the outcome of the exchange. + * @return A status code based on the outcome of the exchange. * */ transmission_status_t TcpIpMeshBackend::exchangeInfo(WiFiClient &currClient) @@ -227,7 +227,7 @@ transmission_status_t TcpIpMeshBackend::exchangeInfo(WiFiClient &currClient) /** * Handle data transfer process with a connected AP. * - * @returns: A status code based on the outcome of the data transfer attempt. + * @return A status code based on the outcome of the data transfer attempt. */ transmission_status_t TcpIpMeshBackend::attemptDataTransfer() { @@ -247,7 +247,7 @@ transmission_status_t TcpIpMeshBackend::attemptDataTransfer() /** * Helper function that contains the core functionality for the data transfer process with a connected AP. * - * @returns: A status code based on the outcome of the data transfer attempt. + * @return A status code based on the outcome of the data transfer attempt. */ transmission_status_t TcpIpMeshBackend::attemptDataTransferKernel() { @@ -291,7 +291,7 @@ void TcpIpMeshBackend::initiateConnectionToAP(const String &targetSSID, int targ * @param targetSSID The name of the AP the other node has set up. * @param targetChannel The WiFI channel of the AP the other node has set up. * @param targetBSSID The MAC address of the AP the other node has set up. - * @returns: A status code based on the outcome of the connection and data transfer process. + * @return A status code based on the outcome of the connection and data transfer process. * */ transmission_status_t TcpIpMeshBackend::connectToNode(const String &targetSSID, int targetChannel, uint8_t *targetBSSID) diff --git a/libraries/ESP8266WiFiMesh/src/TcpIpMeshBackend.h b/libraries/ESP8266WiFiMesh/src/TcpIpMeshBackend.h index 86dc7ce03..0c9f80966 100644 --- a/libraries/ESP8266WiFiMesh/src/TcpIpMeshBackend.h +++ b/libraries/ESP8266WiFiMesh/src/TcpIpMeshBackend.h @@ -110,7 +110,8 @@ public: * 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, they will not be able to have both server instances active at the same time. * This is managed automatically by the activateAP method. - * Will also change the setting for the active AP if this TcpIpMeshBackend instance is the current AP controller. + * Will also change the setting for the active AP (via an AP restart) + * if this TcpIpMeshBackend instance is the current AP controller. * * @param serverPort The server port to use. * @@ -123,7 +124,8 @@ public: * This number is 4 by default. * Once the max number has been reached, any other station that wants to connect will be forced to wait until an already connected station disconnects. * The more stations that are connected, the more memory is required. - * Will also change the setting for the active AP if this TcpIpMeshBackend instance is the current AP controller. + * Will also change the setting for the active AP (via an AP restart) + * if this TcpIpMeshBackend instance is the current AP controller. * * @param maxAPStations The maximum number of simultaneous station connections allowed. Valid values are 0 to 8. */ @@ -153,7 +155,8 @@ public: * Set the timeout to use for transmissions when this TcpIpMeshBackend instance acts as an AP (i.e. when receiving connections from other stations). * This will affect the timeout of the acceptRequest method. * The timeout is 4 500 ms by default. - * Will also change the setting for the active AP if this TcpIpMeshBackend instance is the current AP controller. + * Will also change the setting for the active AP (without an AP restart) + * if this TcpIpMeshBackend instance is the current AP controller. * * @param apModeTimeoutMs The timeout to use, in milliseconds. */ @@ -182,7 +185,7 @@ protected: /** * Check if there is an ongoing TCP/IP transmission in the library. Used to avoid interrupting transmissions. * - * @returns True if a transmission initiated by a public method is in progress. + * @return True if a transmission initiated by a public method is in progress. */ static bool transmissionInProgress(); diff --git a/libraries/ESP8266WiFiMesh/src/TypeConversionFunctions.h b/libraries/ESP8266WiFiMesh/src/TypeConversionFunctions.h index a6fa11465..1e59dcc77 100644 --- a/libraries/ESP8266WiFiMesh/src/TypeConversionFunctions.h +++ b/libraries/ESP8266WiFiMesh/src/TypeConversionFunctions.h @@ -37,7 +37,7 @@ * * @param number The number to convert to a string with radix "base". * @param base The radix to convert "number" into. Must be between 2 and 36. - * @returns A string of "number" encoded in radix "base". + * @return A string of "number" encoded in radix "base". */ String uint64ToString(uint64_t number, byte base = 16); @@ -46,7 +46,7 @@ String uint64ToString(uint64_t number, byte base = 16); * * @param string The string to convert to uint64_t. String must use radix "base". * @param base The radix of "string". Must be between 2 and 36. - * @returns A uint64_t of the string, using radix "base" during decoding. + * @return A uint64_t of the string, using radix "base" during decoding. */ uint64_t stringToUint64(const String &string, byte base = 16); @@ -60,7 +60,7 @@ uint8_t *hexStringToUint8Array(const String &hexString, uint8_t *uint8Array, uin * Takes a uint8_t array and converts the first 6 bytes to a hexadecimal string. * * @param mac A uint8_t array with the mac address to convert to a string. Should be 6 bytes in total. - * @returns A hexadecimal string representation of the mac. + * @return A hexadecimal string representation of the mac. */ String macToString(const uint8_t *mac); @@ -69,7 +69,7 @@ String macToString(const uint8_t *mac); * * @param macString A String which begins with the mac address to store in the array as a hexadecimal number. * @param macArray A uint8_t array that will hold the mac address once the function returns. Should have a size of at least 6 bytes. - * @returns The macArray. + * @return The macArray. */ uint8_t *stringToMac(const String &macString, uint8_t *macArray); @@ -77,7 +77,7 @@ uint8_t *stringToMac(const String &macString, uint8_t *macArray); * Takes a uint8_t array and converts the first 6 bytes to a uint64_t. Assumes index 0 of the array contains MSB. * * @param macArray A uint8_t array with the mac address to convert to a uint64_t. Should be 6 bytes in total. - * @returns A uint64_t representation of the mac. + * @return A uint64_t representation of the mac. */ uint64_t macToUint64(const uint8_t *macArray); @@ -86,7 +86,7 @@ uint64_t macToUint64(const uint8_t *macArray); * * @param macValue The uint64_t value to convert to a mac array. Value must fit within 6 bytes. * @param macArray A uint8_t array that will hold the mac address once the function returns. Should have a size of at least 6 bytes. - * @returns The macArray. + * @return The macArray. */ uint8_t *uint64ToMac(uint64_t macValue, uint8_t *macArray); @@ -95,7 +95,7 @@ uint8_t *uint64ToMac(uint64_t macValue, uint8_t *macArray); * * @param T The MeshBackend class pointer type to cast the meshBackendBaseInstance pointer into. * @param meshBackendBaseInstance The instance pointer to cast. - * @returns A pointer of type T to meshBackendBaseInstance if meshBackendBaseInstance is of type T. nullptr otherwise. + * @return A pointer of type T to meshBackendBaseInstance if meshBackendBaseInstance is of type T. nullptr otherwise. */ template T meshBackendCast(MeshBackendBase *meshBackendBaseInstance)