1
0
mirror of https://github.com/esp8266/Arduino.git synced 2025-07-11 15:22:13 +03:00

- Add broadcast functionality.

- Add createPermanentConnections argument to attemptAutoEncryptingTransmission method.

- Reduce risk of misinterpreting acks by adding check for ack sender MAC.

- Reduce _encryptionRequestTimeoutMs from 500 ms to 300 ms since this should give enough (100 %) margin to the level where problems start appearing (150 ms timeout) and also save a lot of time in case of request failure.

- Improve comments.
This commit is contained in:
Anders
2019-08-10 20:35:48 +02:00
parent 26e626de19
commit 5834c54717
17 changed files with 305 additions and 122 deletions

View File

@ -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<EncryptedConnectionLog> 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<std::pair<U, uint64_t>,
}
}
void EspnowMeshBackend::deleteExpiredLogEntries(std::map<std::pair<peerMac_td, messageID_td>, RequestData> &logEntries, uint32_t requestLifetimeMs, uint32_t broadcastLifetimeMs)
{
for(typename std::map<std::pair<peerMac_td, messageID_td>, 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 <typename T>
void EspnowMeshBackend::deleteExpiredLogEntries(std::list<T> &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<std::pair<macAndType_td, messageID_td>, 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();