/* * Copyright (C) 2019 Anders Löfgren * * License (MIT license): * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #ifndef __FLOODINGMESH_H__ #define __FLOODINGMESH_H__ #include "EspnowMeshBackend.h" #include #include /** * An alternative to standard delay(). Will continuously call performMeshMaintenance() during the waiting time, so that the FloodingMesh node remains responsive. * Note that if there is a lot of FloodingMesh transmission activity to the node during the floodingMeshDelay, the desired duration may be overshot by several ms. * Thus, if precise timing is required, use standard delay() instead. * * Should not be used inside callbacks since performMeshMaintenance() can alter the ESP-NOW state. * * @param durationMs The shortest allowed delay duration, in milliseconds. */ void floodingMeshDelay(const uint32_t durationMs); class FloodingMesh { public: using messageHandlerType = std::function; /** * FloodingMesh constructor method. Creates a FloodingMesh node, ready to be initialised. * * @param messageHandler The callback handler responsible for dealing with messages received from the mesh. * @param meshPassword The WiFi password for the mesh network. * @param espnowEncryptedConnectionKey An uint8_t array containing the secret key used by the EspnowMeshBackend instance for creating encrypted ESP-NOW connections. * @param espnowHashKey An uint8_t array containing the secret key used by the EspnowMeshBackend instance to generate HMACs for encrypted ESP-NOW connections. * @param ssidPrefix The prefix (first part) of the node SSID. * @param ssidSuffix The suffix (last part) of the node SSID. * @param verboseMode Determines if we should print the events occurring in the library to Serial. Off by default. This setting is shared by all EspnowMeshBackend instances. * @param meshWiFiChannel The WiFi channel used by the mesh network. Valid values are integers from 1 to 13. Defaults to 1. * WARNING: The ESP8266 has only one WiFi channel, and the station/client mode is always prioritized for channel selection. * This can cause problems if several mesh instances exist on the same ESP8266 and use different WiFi channels. * In such a case, whenever the station of one mesh instance connects to an AP, it will silently force the * WiFi channel of any active AP on the ESP8266 to match that of the station. This will cause disconnects and possibly * make it impossible for other stations to detect the APs whose WiFi channels have changed. * */ FloodingMesh(messageHandlerType messageHandler, const String &meshPassword, const uint8_t espnowEncryptedConnectionKey[EspnowProtocolInterpreter::encryptedConnectionKeyLength], const uint8_t espnowHashKey[EspnowProtocolInterpreter::hashKeyLength], const String &ssidPrefix, const String &ssidSuffix, const bool verboseMode = false, const uint8 meshWiFiChannel = 1); /** * FloodingMesh constructor method. Creates a FloodingMesh node, ready to be initialised. * * @param messageHandler The callback handler responsible for dealing with messages received from the mesh. * @param meshPassword The WiFi password for the mesh network. * @param espnowEncryptedConnectionKeySeed A string containing the seed that will generate the secret key used by the EspnowMeshBackend instance for creating encrypted ESP-NOW connections. * @param espnowHashKeySeed A string containing the seed that will generate the secret key used by the EspnowMeshBackend to generate HMACs for encrypted ESP-NOW connections. * @param ssidPrefix The prefix (first part) of the node SSID. * @param ssidSuffix The suffix (last part) of the node SSID. * @param verboseMode Determines if we should print the events occurring in the library to Serial. Off by default. This setting is shared by all EspnowMeshBackend instances. * @param meshWiFiChannel The WiFi channel used by the mesh network. Valid values are integers from 1 to 13. Defaults to 1. * WARNING: The ESP8266 has only one WiFi channel, and the station/client mode is always prioritized for channel selection. * This can cause problems if several mesh instances exist on the same ESP8266 and use different WiFi channels. * In such a case, whenever the station of one mesh instance connects to an AP, it will silently force the * WiFi channel of any active AP on the ESP8266 to match that of the station. This will cause disconnects and possibly * make it impossible for other stations to detect the APs whose WiFi channels have changed. * */ FloodingMesh(messageHandlerType messageHandler, const String &meshPassword, const String &espnowEncryptedConnectionKeySeed, const String &espnowHashKeySeed, const String &ssidPrefix, const String &ssidSuffix, const bool verboseMode = false, const uint8 meshWiFiChannel = 1); /** * This constructor should be used in combination with serializeMeshState() when the node has gone to sleep while other nodes stayed awake. * Otherwise the message ID will be reset after sleep, which means that the nodes that stayed awake may ignore new broadcasts for a while. * * @param serializedMeshState A String with a serialized mesh node state that the node should use. */ FloodingMesh(const String &serializedMeshState, messageHandlerType messageHandler, const String &meshPassword, const uint8_t espnowEncryptedConnectionKey[EspnowProtocolInterpreter::encryptedConnectionKeyLength], const uint8_t espnowHashKey[EspnowProtocolInterpreter::hashKeyLength], const String &ssidPrefix, const String &ssidSuffix, const bool verboseMode = false, const uint8 meshWiFiChannel = 1); /** * This constructor should be used in combination with serializeMeshState() when the node has gone to sleep while other nodes stayed awake. * Otherwise the message ID will be reset after sleep, which means that the nodes that stayed awake may ignore new broadcasts for a while. * * @param serializedMeshState A String with a serialized mesh node state that the node should use. */ FloodingMesh(const String &serializedMeshState, messageHandlerType messageHandler, const String &meshPassword, const String &espnowEncryptedConnectionKeySeed, const String &espnowHashKeySeed, const String &ssidPrefix, const String &ssidSuffix, const bool verboseMode = false, const uint8 meshWiFiChannel = 1); virtual ~FloodingMesh(); /** * The method responsible for initialising this FloodingMesh instance. */ void begin(); /** * Activate the WiFi access point of this ESP8266. * This makes it possible to find the node through scans, and also makes it possible to recover from an encrypted ESP-NOW connection where only the other node is encrypted. * Required for encryptedBroadcast() usage, but also slows down the start-up of the node. * * Note that only one AP can be active at a time in total (there is only one WiFi radio on the ESP8266), and this will always be the one which was last activated. * Thus the AP is shared by all backends. * All FloodingMesh instances can still broadcast messages though, even if their AP is not visible. */ void activateAP(); /** * Deactivate the WiFi access point of this ESP8266. * * Note that only one AP can be active at a time in total (there is only one WiFi radio on the ESP8266), and this will always be the one which was last activated. * Thus the AP is shared by all backends. * All FloodingMesh instances can still broadcast messages though, even if their AP is not visible. */ static void deactivateAP(); /** * Performs maintenance for all available Flooding Mesh instances */ static void performMeshMaintenance(); /** * Performs maintenance for this particular Flooding Mesh instance */ void performMeshInstanceMaintenance(); /** * Serialize the current mesh node state. Useful to save a state before the node goes to sleep. * Note that this saves the current state only, so if a broadcast is made after this, the stored state is invalid. * * @return A string with the serialized current mesh node state. */ String serializeMeshState() const; /** * Make an unencrypted broadcast to the entire mesh network. * * activateAP() must have been called for nodes to be able to receive broadcasts. Nodes can however send broadcasts even if their AP is off. * * It is recommended that there is at most one new message transmitted in the mesh every 10, 20, 30 ms for messages up to length maxUnencryptedMessageLength()*n, * where n is (roughly, depending on mesh name length) 1/4, 3/5 and 1 respectively. If transmissions are more frequent than this, message loss will increase. * * @param message The message to broadcast. Maximum message length is given by maxUnencryptedMessageLength(). The longer the message, the longer the transmission time. */ void broadcast(const String &message); /** * Set the maximum number of redundant copies that will be received of every broadcast. (from different senders) * A greater number increases the likelihood that at least one of the copies is received successfully, but will also use more RAM. * * @param redundancy The maximum number of extra copies that will be accepted. Defaults to 2. Valid values are 0 to 254. */ void setBroadcastReceptionRedundancy(const uint8_t redundancy); uint8_t getBroadcastReceptionRedundancy() const; /** * Make an encrypted broadcast to the entire mesh network. * * activateAP() must have been called for encryptedBroadcast to work. * * ########## WARNING! This an experimental feature. API may change at any time. Only use if you like it when things break. ########## * Will be very slow compared to unencrypted broadcasts. Probably works OK in a small mesh with a maximum of 2-3 new messages transmitted in the mesh every second. * Because of the throughput difference, mixing encrypted and unencrypted broadcasts is not recommended if there are frequent mesh broadcasts (multiple per second), * since a lot of unencrypted broadcasts can build up while a single encrypted broadcast is sent. * * It is recommended that verboseMode is turned off if using this, to avoid slowdowns due to excessive Serial printing. * * @param message The message to broadcast. Maximum message length is given by maxEncryptedMessageLength(). The longer the message, the longer the transmission time. */ void encryptedBroadcast(const String &message); /** * Clear the logs used for remembering which messages this node has received from the mesh network. */ void clearMessageLogs(); /** * Remove all messages received from the mesh network which are stored waiting to be forwarded by this node. */ void clearForwardingBacklog(); /** * Set the callback handler responsible for dealing with messages received from the mesh. * * @param messageHandler The message handler callback function to use. */ void setMessageHandler(const messageHandlerType messageHandler); messageHandlerType getMessageHandler() const; /** * Get the origin AP MAC address of the most recently received mesh message. * Returns a String. * * @return A String filled with a hexadecimal representation of the MAC, without delimiters. */ String getOriginMac() const; /** * Get the origin AP MAC address of the most recently received mesh message. * Returns a uint8_t array. * * @param macArray The array that should store the MAC address. Must be at least 6 bytes. * @return macArray filled with the origin MAC. */ uint8_t *getOriginMac(uint8_t *macArray) const; /** * The number of received messageID:s that will be stored by the node. Used to remember which messages have been received. * Setting this too low will cause the same message to be received many times. * Setting this too high will cause the node to run out of RAM. * In practice, setting this value to more than 1337 is probably a bad idea since the node will run out of RAM quickly and crash as a result. * * Defaults to 100. * * @param messageLogSize The size of the message log for this FloodingMesh instance. Valid values are 1 to 65535 (uint16_t_max). * If a value close to the maximum is chosen, there is a high risk the node will ignore transmissions on messageID rollover if they are sent only by one node * (especially if some transmissions are missed), since the messageID also uses uint16_t. */ void setMessageLogSize(const uint16_t messageLogSize); uint16_t messageLogSize() const; /** * Hint: Use String.length() to get the ASCII length of a String. * * @return The maximum length in bytes an unencrypted ASCII message is allowed to be when broadcasted by this node. * Note that non-ASCII characters usually require at least two bytes each. * Also note that for unencrypted messages the maximum size will depend on getEspnowMeshBackend().getMeshName().length() */ uint32_t maxUnencryptedMessageLength() const; /** * Hint: Use String.length() to get the ASCII length of a String. * * @return The maximum length in bytes an encrypted ASCII message is allowed to be when broadcasted by this node. * Note that non-ASCII characters usually require at least two bytes each. */ uint32_t maxEncryptedMessageLength() const; /** * Set the delimiter character used for metadata by every FloodingMesh instance. * Using characters found in the mesh name or in HEX numbers is unwise, as is using ','. * * @param metadataDelimiter The metadata delimiter character to use. * Defaults to 23 = End-of-Transmission-Block (ETB) control character in ASCII */ static void setMetadataDelimiter(const char metadataDelimiter); static char metadataDelimiter(); /* * Gives you access to the EspnowMeshBackend used by the mesh node. * The backend handles all mesh communication, and modifying it allows you to change every aspect of the mesh behaviour. * Random interactions with the backend have a high chance of breaking the mesh network, * and so are discouraged for those who prefer it when things just work. */ EspnowMeshBackend &getEspnowMeshBackend(); const EspnowMeshBackend &getEspnowMeshBackendConst() const; void restoreDefaultRequestHandler(); void restoreDefaultResponseHandler(); void restoreDefaultNetworkFilter(); void restoreDefaultBroadcastFilter(); void restoreDefaultTransmissionOutcomesUpdateHook(); void restoreDefaultResponseTransmittedHook(); protected: using messageQueueElementType = std::map::iterator; static std::set availableFloodingMeshes; String generateMessageID(); void broadcastKernel(const String &message); void encryptedBroadcastKernel(const String &message); bool insertPreliminaryMessageID(const uint64_t messageID); bool insertCompletedMessageID(const uint64_t messageID); void updateMessageQueue(const messageQueueElementType messageIterator); void loadMeshState(const String &serializedMeshState); /** * Set the MAC address considered to be the origin AP MAC address of the most recently received mesh message. * * @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 setOriginMac(const uint8_t *macArray); std::list> & getForwardingBacklog(); String & getMacIgnoreList(); // Experimental, may break in the future. private: EspnowMeshBackend _espnowBackend; messageHandlerType _messageHandler; std::map _messageIDs = {}; std::queue _messageIdOrder = {}; std::list> _forwardingBacklog = {}; String _macIgnoreList; String _defaultRequestHandler(const String &request, MeshBackendBase &meshInstance); TransmissionStatusType _defaultResponseHandler(const String &response, MeshBackendBase &meshInstance); void _defaultNetworkFilter(const int numberOfNetworks, MeshBackendBase &meshInstance); bool _defaultBroadcastFilter(String &firstTransmission, EspnowMeshBackend &meshInstance); bool _defaultTransmissionOutcomesUpdateHook(MeshBackendBase &meshInstance); bool _defaultResponseTransmittedHook(bool transmissionSuccessful, const String &response, const uint8_t *recipientMac, const uint32_t responseIndex, EspnowMeshBackend &meshInstance); uint8_t _originMac[6] = {0}; uint16_t _messageCount = 0; uint16_t _messageLogSize = 100; uint8_t _broadcastReceptionRedundancy = 2; }; #endif