From dc662f984d1fda37768430dae53bd35d37120e18 Mon Sep 17 00:00:00 2001 From: Alexey Zholtikov Date: Fri, 9 Dec 2022 11:41:11 +0300 Subject: [PATCH] Version 1.0 Initial version. --- .gitignore | 5 + README.md | 302 +++++++++++++- examples/Receiver/main.cpp | 57 +++ examples/Transmitter/main.cpp | 63 +++ library.properties | 7 + src/ZHNetwork.cpp | 714 ++++++++++++++++++++++++++++++++++ src/ZHNetwork.h | 146 +++++++ 7 files changed, 1293 insertions(+), 1 deletion(-) create mode 100644 .gitignore create mode 100644 examples/Receiver/main.cpp create mode 100644 examples/Transmitter/main.cpp create mode 100644 library.properties create mode 100644 src/ZHNetwork.cpp create mode 100644 src/ZHNetwork.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1330bce --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.pio +.vscode +.DS_Store +src/main.cpp +platformio.ini \ No newline at end of file diff --git a/README.md b/README.md index 285fa33..cadeb08 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,302 @@ -# ZHNetwork +# ESP-NOW based Mesh network for ESP8266/ESP32 +A simple library for creating ESP-NOW based Mesh network for ESP8266/ESP32. + +## Features + +1. The maximum size of transmitted data is 200 bytes. Currently only unencrypted messages. +2. All nodes are not visible to the network scanner (for the ESP_NOW mode only). +3. Not required a pre-pairings for data transfer. +4. Broadcast or unicast data transmission. +5. There are no periodic/synchronous messages on the network. All devices are in "silent mode" and do not "hum" into the air (for the ESP_NOW mode only). +6. Each node has its own independent routing table, updated only as needed. +7. Each node will receive/send a message if it "sees" at least one device on the network. +8. The number of devices on the network and the area of use is not limited (hypothetically). :-) + +## Testing + +1. Program 2 receivers and 1 transmitter (specify the MAC of the 1st receiver in the transmitter code). +2. Connect the 1st receiver to the computer. Switch on serial port monitor. Turn transmitter on. Receiver will start receiving data. +3. Move transmitter as far away from receiver as possible until receiver is able to receive data (reduce tx power and shield module if necessary). +4. Turn on the 2nd receiver and place it between the 1st receiver and transmitter (preferably in the middle). The 1st receiver will resume data reception (with relaying through the 2nd receiver). P.S. You can use a transmitter instead of the 2nd receiver - makes no difference. +5. Voila. ;-) +6. P.S. Uncomment #define PRINT_LOG in ZHNetwork.h for display to serial port the full operation log. + +## Function descriptions + +For more details and correctly using see [examples](https://github.com/aZholtikov/ZHNetwork/tree/master/examples). + +### Sets the callback function for processing a received broadcast message + +Note. Possibility uses one callback function for recieved unicast and broadcast messages. + +```cpp +myNet.setOnBroadcastReceivingCallback(onBroadcastReceiving); +void onBroadcastReceiving(const char *data, const uint8_t *sender) +{ + // Do something when receiving a broadcast message. +} +``` + +### Sets the callback function for processing a received unicast message + +Note. Possibility uses one callback function for recieved unicast and broadcast messages. + +```cpp +myNet.setOnUnicastReceivingCallback(onUnicastReceiving); +void onUnicastReceiving(const char *data, const uint8_t *sender) +{ + // Do something when receiving a unicast message. +} +``` + +### Sets the callback function for processing a received delivery/undelivery confirm message + +Note. Called only at broadcast or unicast with confirm message. Status will always true at sending broadcast message. + +```cpp +myNet.setOnConfirmReceivingCallback(onConfirmReceiving); +void onConfirmReceiving(const uint8_t *target, const bool status) +{ + // Do something when receiving a delivery/undelivery confirm message. +} +``` + +### Sets one of the three possibility operating modes + +* ESP_NOW. Default mode. ESP-NOW Mesh network only. +* ESP_NOW_AP. ESP-NOW Mesh network + access point. +* ESP_NOW_STA. ESP-NOW Mesh network + connect to your WiFi router. + +Attention! For correct work on ESP_NOW_STA mode at ESP8266 your WiFi router must be set on channel 1. + +```cpp +myNet.setWorkMode(ESP_NOW); +``` + +### Gets used operating mode + +```cpp +myNet.getWorkMode(); +``` + +### Sets ESP-NOW Mesh network name + +1-20 characters. + +Note. If network name not set node will work with all ESP-NOW networks. If set node will work with only one network. + +```cpp +myNet.setNetName("ZHNetwork"); +``` + +### Gets used ESP-NOW Mesh network name + +```cpp +myNet.getNetName(); +``` + +### Sets WiFi ssid and password for ESP_NOW_STA mode + +Note. Must be called before Mesh network initialization. + +```cpp +myNet.setStaSetting("SSID", "PASSWORD"); +``` + +### Sets access point ssid and password for ESP_NOW_AP mode + +Note. Must be called before Mesh network initialization. + +```cpp +myNet.setApSetting("SSID", "PASSWORD"); +``` + +### ESP-NOW Mesh network initialization + +```cpp +myNet.begin(); +``` + +### Sends broadcast message to all nodes + +```cpp +myNet.sendBroadcastMessage("Hello world!"); +``` + +### Sends unicast message to node + +```cpp +myNet.sendUnicastMessage("Hello world!", target); // Without confirm. +myNet.sendUnicastMessage("Hello world!", target, true); // With confirm. +``` + +### System processing + +Attention! Must be uncluded in loop. + +```cpp +myNet.maintenance(); +``` + +### Gets node MAC adress + +```cpp +myNet.getNodeMac(); +``` + +### Gets node IP address + +```cpp +myNet.getNodeIp(); +``` + +### Gets version of this library + +```cpp +myNet.getFirmwareVersion(); +``` + +### Converts MAC adress to string + +```cpp +myNet.macToString(mac); +``` + +### Converts string to MAC adress + +```cpp +uint8_t mac[6] +myNet.stringToMac(string, mac); +``` + +### Sets max number of attempts to send message + +1-10. 3 default value. + +```cpp +myNet.setMaxNumberOfAttempts(3); +``` + +### Gets max number of attempts to send message + +```cpp +myNet.getMaxNumberOfAttempts(); +``` + +### Sets max waiting time between transmissions + +50-250 ms. 50 default value. + +```cpp +myNet.setMaxWaitingTimeBetweenTransmissions(50); +``` + +### Gets max waiting time between transmissions + +```cpp +myNet.getMaxWaitingTimeBetweenTransmissions(); +``` + +### Sets max waiting time for routing info + +500-5000 ms. 500 default value. + +```cpp +myNet.setMaxWaitingTimeForRoutingInfo(500); +``` + +### Gets max waiting time for routing info + +```cpp +myNet.getMaxWaitingTimeForRoutingInfo(); +``` + +## Example + +```cpp +#include "ZHNetwork.h" + +void onBroadcastReceiving(const char *data, const uint8_t *sender); +void onUnicastReceiving(const char *data, const uint8_t *sender); +void onConfirmReceiving(const uint8_t *target, const bool status); + +ZHNetwork myNet; + +uint64_t messagelastTime{0}; +uint16_t messageTimerDelay{5000}; +const uint8_t target[6]{0xA8, 0x48, 0xFA, 0xDC, 0x5B, 0xFA}; + +void setup() +{ + Serial.begin(115200); + Serial.println(); + // *** ESP-NOW mode only. + myNet.setWorkMode(ESP_NOW); + // *** Or ESP-NOW + access point mode. + // myNet.setWorkMode(ESP_NOW_AP); + // myNet.setApSetting("ESP NODE TEST", "12345678"); + // *** Or ESP-NOW + connect to your router mode. + // myNet.setWorkMode(ESP_NOW_STA); + // myNet.setStaSetting("SSID", "PASSWORD"); + // *** + myNet.setNetName("ZHNetwork"); // Optional. + myNet.setMaxNumberOfAttempts(3); // Optional. + myNet.setMaxWaitingTimeBetweenTransmissions(50); // Optional. + myNet.setMaxWaitingTimeForRoutingInfo(500); // Optional. + myNet.begin(); + myNet.setOnBroadcastReceivingCallback(onBroadcastReceiving); + myNet.setOnUnicastReceivingCallback(onUnicastReceiving); + myNet.setOnConfirmReceivingCallback(onConfirmReceiving); + Serial.print("MAC: "); + Serial.print(myNet.getNodeMac()); + Serial.print(". IP: "); + Serial.print(myNet.getNodeIp()); + Serial.print(". Firmware version: "); + Serial.print(myNet.getFirmwareVersion()); + Serial.println("."); +} + +void loop() +{ + if ((millis() - messagelastTime) > messageTimerDelay) + { + Serial.println("Broadcast message sended."); + myNet.sendBroadcastMessage("Hello world!"); + Serial.print("Unicast message to MAC "); + Serial.print(myNet.macToString(target)); + Serial.println(" sended."); + myNet.sendUnicastMessage("Hello world!", target); + Serial.print("Unicast with confirm message to MAC "); + Serial.print(myNet.macToString(target)); + Serial.println(" sended."); + myNet.sendUnicastMessage("Hello world!", target, true); + messagelastTime = millis(); + } + myNet.maintenance(); +} + +void onBroadcastReceiving(const char *data, const uint8_t *sender) +{ + Serial.print("Broadcast message from MAC "); + Serial.print(myNet.macToString(sender)); + Serial.println(" received."); + Serial.print("Message: "); + Serial.println(data); +} +void onUnicastReceiving(const char *data, const uint8_t *sender) +{ + Serial.print("Unicast message from MAC "); + Serial.print(myNet.macToString(sender)); + Serial.println(" received."); + Serial.print("Message: "); + Serial.println(data); +} + +void onConfirmReceiving(const uint8_t *target, const bool status) +{ + Serial.print("Message to MAC "); + Serial.print(myNet.macToString(target)); + Serial.println(status ? " delivered." : " undelivered."); +} +``` diff --git a/examples/Receiver/main.cpp b/examples/Receiver/main.cpp new file mode 100644 index 0000000..db342b3 --- /dev/null +++ b/examples/Receiver/main.cpp @@ -0,0 +1,57 @@ +#include "ZHNetwork.h" + +void onBroadcastReceiving(const char *data, const uint8_t *sender); +void onUnicastReceiving(const char *data, const uint8_t *sender); + +ZHNetwork myNet; + +void setup() +{ + Serial.begin(115200); + Serial.println(); + // *** ESP-NOW mode only. + myNet.setWorkMode(ESP_NOW); + // *** Or ESP-NOW + access point mode. + // myNet.setWorkMode(ESP_NOW_AP); + // myNet.setApSetting("ESP NODE TEST", "12345678"); + // *** Or ESP-NOW + connect to your router mode. + // myNet.setWorkMode(ESP_NOW_STA); + // myNet.setStaSetting("SSID", "PASSWORD"); + // *** + myNet.setNetName("ZHNetwork"); // Optional. + myNet.setMaxNumberOfAttempts(3); // Optional. + myNet.setMaxWaitingTimeBetweenTransmissions(50); // Optional. + myNet.setMaxWaitingTimeForRoutingInfo(500); // Optional. + myNet.begin(); + myNet.setOnBroadcastReceivingCallback(onBroadcastReceiving); + myNet.setOnUnicastReceivingCallback(onUnicastReceiving); + Serial.print("MAC: "); + Serial.print(myNet.getNodeMac()); + Serial.print(". IP: "); + Serial.print(myNet.getNodeIp()); + Serial.print(". Firmware version: "); + Serial.print(myNet.getFirmwareVersion()); + Serial.println("."); +} + +void loop() +{ + myNet.maintenance(); +} + +void onBroadcastReceiving(const char *data, const uint8_t *sender) +{ + Serial.print("Broadcast message from MAC "); + Serial.print(myNet.macToString(sender)); + Serial.println(" received."); + Serial.print("Message: "); + Serial.println(data); +} +void onUnicastReceiving(const char *data, const uint8_t *sender) +{ + Serial.print("Unicast message from MAC "); + Serial.print(myNet.macToString(sender)); + Serial.println(" received."); + Serial.print("Message: "); + Serial.println(data); +} \ No newline at end of file diff --git a/examples/Transmitter/main.cpp b/examples/Transmitter/main.cpp new file mode 100644 index 0000000..a348e78 --- /dev/null +++ b/examples/Transmitter/main.cpp @@ -0,0 +1,63 @@ +#include "ZHNetwork.h" + +void onConfirmReceiving(const uint8_t *target, const bool status); + +ZHNetwork myNet; + +uint64_t messagelastTime{0}; +uint16_t messageTimerDelay{5000}; +const uint8_t target[6]{0xA8, 0x48, 0xFA, 0xDC, 0x5B, 0xFA}; + +void setup() +{ + Serial.begin(115200); + Serial.println(); + // *** ESP-NOW mode only. + myNet.setWorkMode(ESP_NOW); + // *** Or ESP-NOW + access point mode. + // myNet.setWorkMode(ESP_NOW_AP); + // myNet.setApSetting("ESP NODE TEST", "12345678"); + // *** Or ESP-NOW + connect to your router mode. + // myNet.setWorkMode(ESP_NOW_STA); + // myNet.setStaSetting("SSID", "PASSWORD"); + // *** + myNet.setNetName("ZHNetwork"); // Optional. + myNet.setMaxNumberOfAttempts(3); // Optional. + myNet.setMaxWaitingTimeBetweenTransmissions(50); // Optional. + myNet.setMaxWaitingTimeForRoutingInfo(500); // Optional. + myNet.begin(); + myNet.setOnConfirmReceivingCallback(onConfirmReceiving); + Serial.print("MAC: "); + Serial.print(myNet.getNodeMac()); + Serial.print(". IP: "); + Serial.print(myNet.getNodeIp()); + Serial.print(". Firmware version: "); + Serial.print(myNet.getFirmwareVersion()); + Serial.println("."); +} + +void loop() +{ + if ((millis() - messagelastTime) > messageTimerDelay) + { + Serial.println("Broadcast message sended."); + myNet.sendBroadcastMessage("Hello world!"); + Serial.print("Unicast message to MAC "); + Serial.print(myNet.macToString(target)); + Serial.println(" sended."); + myNet.sendUnicastMessage("Hello world!", target); + Serial.print("Unicast with confirm message to MAC "); + Serial.print(myNet.macToString(target)); + Serial.println(" sended."); + myNet.sendUnicastMessage("Hello world!", target, true); + messagelastTime = millis(); + } + myNet.maintenance(); +} + +void onConfirmReceiving(const uint8_t *target, const bool status) +{ + Serial.print("Message to MAC "); + Serial.print(myNet.macToString(target)); + Serial.println(status ? " delivered." : " undelivered."); +} \ No newline at end of file diff --git a/library.properties b/library.properties new file mode 100644 index 0000000..5dbc165 --- /dev/null +++ b/library.properties @@ -0,0 +1,7 @@ +name=ZHNetwork +version=1.0 +author=Alexey Zholtikov +maintainer=Alexey Zholtikov +sentence=ESP-NOW based Mesh network for ESP8266/ESP32 +repository=https://github.com/aZholtikov/ZHNetwork.git +license=MIT \ No newline at end of file diff --git a/src/ZHNetwork.cpp b/src/ZHNetwork.cpp new file mode 100644 index 0000000..010899e --- /dev/null +++ b/src/ZHNetwork.cpp @@ -0,0 +1,714 @@ +#include "ZHNetwork.h" + +routing_vector_t routingVector; +incoming_queue_t queueForIncomingData; +outgoing_queue_t queueForOutgoingData; +waiting_queue_t queueForRoutingVectorWaiting; + +const String firmware{"1.0"}; +const uint8_t broadcastMAC[6]{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + +bool criticalProcessSemaphore{false}; +bool sentMessageSemaphore{false}; +bool confirmReceivingSemaphore{false}; +bool confirmReceiving{false}; +uint8_t localMAC[6]{0}; +uint8_t numberOfAttemptsToSend{1}; +uint16_t lastMessageID[10]{0}; +uint64_t lastMessageSentTime{0}; + +work_mode_t workMode_{ESP_NOW}; +char netName_[20]{0}; +char apSsid_[32]{"ESP-NOW NODE"}; +char apPassword_[64]{0}; +char staSsid_[32]{0}; +char staPassword_[64]{0}; +uint8_t maxNumberOfAttempts_{3}; +uint8_t maxWaitingTimeBetweenTransmissions_{50}; +uint16_t maxTimeForRoutingInfoWaiting_{500}; + +ZHNetwork &ZHNetwork::setOnBroadcastReceivingCallback(on_message_t onBroadcastReceivingCallback) +{ + this->onBroadcastReceivingCallback = onBroadcastReceivingCallback; + return *this; +} + +ZHNetwork &ZHNetwork::setOnUnicastReceivingCallback(on_message_t onUnicastReceivingCallback) +{ + this->onUnicastReceivingCallback = onUnicastReceivingCallback; + return *this; +} + +ZHNetwork &ZHNetwork::setOnConfirmReceivingCallback(on_confirm_t onConfirmReceivingCallback) +{ + this->onConfirmReceivingCallback = onConfirmReceivingCallback; + return *this; +} + +error_code_t ZHNetwork::setWorkMode(const work_mode_t workMode) +{ + if (workMode < ESP_NOW || workMode > ESP_NOW_STA) + return ERROR; + workMode_ = workMode; + return SUCCESS; +} + +work_mode_t ZHNetwork::getWorkMode() +{ + return workMode_; +} + +error_code_t ZHNetwork::setNetName(const char *netName) +{ + if (strlen(netName) < 1 || strlen(netName) > 20) + return ERROR; + memset(&netName_, 0, strlen(netName)); + strcpy(netName_, netName); + return SUCCESS; +} + +String ZHNetwork::getNetName() +{ + return String(netName_); +} + +error_code_t ZHNetwork::setStaSetting(const char *ssid, const char *password) +{ + if (strlen(ssid) < 1 || strlen(ssid) > 32 || strlen(password) > 64) + return ERROR; + memset(&staSsid_, 0, strlen(ssid)); + strcpy(staSsid_, ssid); + memset(&staPassword_, 0, strlen(password)); + strcpy(staPassword_, password); + return SUCCESS; +} + +error_code_t ZHNetwork::setApSetting(const char *ssid, const char *password) +{ + if (strlen(ssid) < 1 || strlen(ssid) > 32 || strlen(password) < 8 || strlen(password) > 64) + return ERROR; + memset(&apSsid_, 0, strlen(ssid)); + strcpy(apSsid_, ssid); + memset(&apPassword_, 0, strlen(password)); + strcpy(apPassword_, password); + return SUCCESS; +} + +error_code_t ZHNetwork::begin() +{ + randomSeed(analogRead(0)); +#ifdef PRINT_LOG + Serial.begin(115200); +#endif + switch (workMode_) + { + case ESP_NOW: + WiFi.mode(WIFI_STA); + break; + case ESP_NOW_AP: + WiFi.mode(WIFI_AP_STA); + WiFi.softAP(apSsid_, apPassword_); + break; + case ESP_NOW_STA: + WiFi.mode(WIFI_STA); + WiFi.begin(staSsid_, staPassword_); + while (WiFi.status() != WL_CONNECTED) + { + if (WiFi.status() == WL_NO_SSID_AVAIL || WiFi.status() == WL_CONNECT_FAILED) + return ERROR; + delay(500); + } + break; + default: + break; + } + esp_now_init(); +#if defined(ESP8266) + wifi_get_macaddr(STATION_IF, localMAC); + esp_now_set_self_role(ESP_NOW_ROLE_COMBO); +#endif +#if defined(ESP32) + esp_wifi_get_mac((wifi_interface_t)ESP_IF_WIFI_STA, localMAC); +#endif + esp_now_register_send_cb(onDataSent); + esp_now_register_recv_cb(onDataReceive); + return SUCCESS; +} + +void ZHNetwork::sendBroadcastMessage(const char *data) +{ + broadcastMessage(data, broadcastMAC, BROADCAST); +} + +void ZHNetwork::sendUnicastMessage(const char *data, const uint8_t *target, const bool confirm) +{ + unicastMessage(data, target, localMAC, confirm ? UNICAST_WITH_CONFIRM : UNICAST); +} + +void ZHNetwork::maintenance() +{ + if (sentMessageSemaphore && confirmReceivingSemaphore) + { + sentMessageSemaphore = false; + confirmReceivingSemaphore = false; + if (confirmReceiving) + { +#ifdef PRINT_LOG + Serial.println("OK."); +#endif + outgoing_data_t outgoingData = queueForOutgoingData.front(); + queueForOutgoingData.pop(); +#if defined(ESP32) + esp_now_del_peer(outgoingData.intermediateTargetMAC); +#endif + if (onConfirmReceivingCallback && macToString(outgoingData.transmittedData.originalSenderMAC) == macToString(localMAC) && outgoingData.transmittedData.messageType == BROADCAST) + onConfirmReceivingCallback(outgoingData.transmittedData.originalTargetMAC, true); + } + else + { +#ifdef PRINT_LOG + Serial.println("FAULT."); +#endif + if (numberOfAttemptsToSend < maxNumberOfAttempts_) + ++numberOfAttemptsToSend; + else + { + outgoing_data_t outgoingData = queueForOutgoingData.front(); + queueForOutgoingData.pop(); +#if defined(ESP32) + esp_now_del_peer(outgoingData.intermediateTargetMAC); +#endif + numberOfAttemptsToSend = 1; + for (uint16_t i{0}; i < routingVector.size(); ++i) + { + routing_table_t routingTable = routingVector[i]; + if (macToString(routingTable.originalTargetMAC) == macToString(outgoingData.transmittedData.originalTargetMAC)) + { + routingVector.erase(routingVector.begin() + i); +#ifdef PRINT_LOG + Serial.print("CHECKING ROUTING TABLE... Routing to MAC "); + Serial.print(macToString(outgoingData.transmittedData.originalTargetMAC)); + Serial.println(" deleted."); +#endif + } + } + waiting_data_t waitingData; + esp_memset(&waitingData, 0, sizeof(waiting_data_t)); + waitingData.time = millis(); + memcpy(&waitingData.intermediateTargetMAC, &outgoingData.intermediateTargetMAC, 6); + memcpy(&waitingData.transmittedData, &outgoingData.transmittedData, sizeof(transmitted_data_t)); + queueForRoutingVectorWaiting.push(waitingData); + broadcastMessage("", outgoingData.transmittedData.originalTargetMAC, SEARCH_REQUEST); + } + } + } + if (!queueForOutgoingData.empty() && ((millis() - lastMessageSentTime) > maxWaitingTimeBetweenTransmissions_)) + { + outgoing_data_t outgoingData = queueForOutgoingData.front(); +#if defined(ESP32) + esp_now_peer_info_t peerInfo; + memset(&peerInfo, 0, sizeof(peerInfo)); + memcpy(peerInfo.peer_addr, outgoingData.intermediateTargetMAC, 6); + peerInfo.channel = 1; + peerInfo.encrypt = false; + esp_now_add_peer(&peerInfo); +#endif + esp_now_send(outgoingData.intermediateTargetMAC, (uint8_t *)&outgoingData.transmittedData, sizeof(transmitted_data_t)); + lastMessageSentTime = millis(); + sentMessageSemaphore = true; +#ifdef PRINT_LOG + switch (outgoingData.transmittedData.messageType) + { + case BROADCAST: + Serial.print("BROADCAST"); + break; + case UNICAST: + Serial.print("UNICAST"); + break; + case UNICAST_WITH_CONFIRM: + Serial.print("UNICAST_WITH_CONFIRM"); + break; + case DELIVERY_CONFIRM_RESPONSE: + Serial.print("DELIVERY_CONFIRM_RESPONSE"); + break; + case SEARCH_REQUEST: + Serial.print("SEARCH_REQUEST"); + break; + case SEARCH_RESPONSE: + Serial.print("SEARCH_RESPONSE"); + break; + default: + break; + } + Serial.print(" message from MAC "); + Serial.print(macToString(outgoingData.transmittedData.originalSenderMAC)); + Serial.print(" to MAC "); + Serial.print(macToString(outgoingData.transmittedData.originalTargetMAC)); + Serial.print(" via MAC "); + Serial.print(macToString(outgoingData.intermediateTargetMAC)); + Serial.print(" sended. Status "); +#endif + } + if (!queueForIncomingData.empty()) + { + criticalProcessSemaphore = true; + incoming_data_t incomingData = queueForIncomingData.front(); + queueForIncomingData.pop(); + criticalProcessSemaphore = false; + bool forward{false}; + bool routingUpdate{false}; + switch (incomingData.transmittedData.messageType) + { + case BROADCAST: +#ifdef PRINT_LOG + Serial.print("BROADCAST message from MAC "); + Serial.print(macToString(incomingData.transmittedData.originalSenderMAC)); + Serial.println(" received."); +#endif + if (onBroadcastReceivingCallback) + onBroadcastReceivingCallback(incomingData.transmittedData.message, incomingData.transmittedData.originalSenderMAC); + forward = true; + break; + case UNICAST: +#ifdef PRINT_LOG + Serial.print("UNICAST message from MAC "); + Serial.print(macToString(incomingData.transmittedData.originalSenderMAC)); + Serial.print(" to MAC "); + Serial.print(macToString(incomingData.transmittedData.originalTargetMAC)); + Serial.print(" via MAC "); + Serial.print(macToString(incomingData.intermediateSenderMAC)); + Serial.println(" received."); +#endif + if (macToString(incomingData.transmittedData.originalTargetMAC) == macToString(localMAC)) + { + if (onUnicastReceivingCallback) + onUnicastReceivingCallback(incomingData.transmittedData.message, incomingData.transmittedData.originalSenderMAC); + } + else + unicastMessage(incomingData.transmittedData.message, incomingData.transmittedData.originalTargetMAC, incomingData.transmittedData.originalSenderMAC, UNICAST); + break; + case UNICAST_WITH_CONFIRM: +#ifdef PRINT_LOG + Serial.print("UNICAST_WITH_CONFIRM message from MAC "); + Serial.print(macToString(incomingData.transmittedData.originalSenderMAC)); + Serial.print(" to MAC "); + Serial.print(macToString(incomingData.transmittedData.originalTargetMAC)); + Serial.print(" via MAC "); + Serial.print(macToString(incomingData.intermediateSenderMAC)); + Serial.println(" received."); +#endif + if (macToString(incomingData.transmittedData.originalTargetMAC) == macToString(localMAC)) + { + if (onUnicastReceivingCallback) + onUnicastReceivingCallback(incomingData.transmittedData.message, incomingData.transmittedData.originalSenderMAC); + unicastMessage("", incomingData.transmittedData.originalSenderMAC, localMAC, DELIVERY_CONFIRM_RESPONSE); + } + else + unicastMessage(incomingData.transmittedData.message, incomingData.transmittedData.originalTargetMAC, incomingData.transmittedData.originalSenderMAC, UNICAST_WITH_CONFIRM); + break; + case DELIVERY_CONFIRM_RESPONSE: +#ifdef PRINT_LOG + Serial.print("DELIVERY_CONFIRM_RESPONSE message from MAC "); + Serial.print(macToString(incomingData.transmittedData.originalSenderMAC)); + Serial.print(" to MAC "); + Serial.print(macToString(incomingData.transmittedData.originalTargetMAC)); + Serial.print(" via MAC "); + Serial.print(macToString(incomingData.intermediateSenderMAC)); + Serial.println(" received."); +#endif + if (macToString(incomingData.transmittedData.originalTargetMAC) == macToString(localMAC)) + { + if (onConfirmReceivingCallback) + onConfirmReceivingCallback(incomingData.transmittedData.originalSenderMAC, true); + } + else + unicastMessage(incomingData.transmittedData.message, incomingData.transmittedData.originalTargetMAC, incomingData.transmittedData.originalSenderMAC, DELIVERY_CONFIRM_RESPONSE); + break; + case SEARCH_REQUEST: +#ifdef PRINT_LOG + Serial.print("SEARCH_REQUEST message from MAC "); + Serial.print(macToString(incomingData.transmittedData.originalSenderMAC)); + Serial.print(" to MAC "); + Serial.print(macToString(incomingData.transmittedData.originalTargetMAC)); + Serial.println(" received."); +#endif + if (macToString(incomingData.transmittedData.originalTargetMAC) == macToString(localMAC)) + broadcastMessage("", incomingData.transmittedData.originalSenderMAC, SEARCH_RESPONSE); + else + forward = true; + routingUpdate = true; + break; + case SEARCH_RESPONSE: +#ifdef PRINT_LOG + Serial.print("SEARCH_RESPONSE message from MAC "); + Serial.print(macToString(incomingData.transmittedData.originalSenderMAC)); + Serial.print(" to MAC "); + Serial.print(macToString(incomingData.transmittedData.originalTargetMAC)); + Serial.println(" received."); +#endif + if (macToString(incomingData.transmittedData.originalTargetMAC) != macToString(localMAC)) + forward = true; + routingUpdate = true; + break; + default: + break; + } + if (forward) + { + outgoing_data_t outgoingData; + memcpy(&outgoingData.transmittedData, &incomingData.transmittedData, sizeof(transmitted_data_t)); + memcpy(&outgoingData.intermediateTargetMAC, &broadcastMAC, 6); + queueForOutgoingData.push(outgoingData); + delay(random(10)); + } + if (routingUpdate) + { + bool routeFound{false}; + for (uint16_t i{0}; i < routingVector.size(); ++i) + { + routing_table_t routingTable = routingVector[i]; + if (macToString(routingTable.originalTargetMAC) == macToString(incomingData.transmittedData.originalSenderMAC)) + { + routeFound = true; + if (macToString(routingTable.intermediateTargetMAC) != macToString(incomingData.intermediateSenderMAC)) + { + memcpy(&routingTable.intermediateTargetMAC, &incomingData.intermediateSenderMAC, 6); + routingVector.at(i) = routingTable; +#ifdef PRINT_LOG + Serial.print("CHECKING ROUTING TABLE... Routing to MAC "); + Serial.print(macToString(incomingData.transmittedData.originalSenderMAC)); + Serial.print(" updated. Target is "); + Serial.print(macToString(incomingData.intermediateSenderMAC)); + Serial.println("."); +#endif + } + } + } + if (!routeFound) + { + if (macToString(incomingData.transmittedData.originalSenderMAC) != macToString(incomingData.intermediateSenderMAC)) + { + routing_table_t routingTable; + memcpy(&routingTable.originalTargetMAC, &incomingData.transmittedData.originalSenderMAC, 6); + memcpy(&routingTable.intermediateTargetMAC, &incomingData.intermediateSenderMAC, 6); + routingVector.push_back(routingTable); +#ifdef PRINT_LOG + Serial.print("CHECKING ROUTING TABLE... Routing to MAC "); + Serial.print(macToString(incomingData.transmittedData.originalSenderMAC)); + Serial.print(" added. Target is "); + Serial.print(macToString(incomingData.intermediateSenderMAC)); + Serial.println("."); +#endif + } + } + } + } + if (!queueForRoutingVectorWaiting.empty()) + { + waiting_data_t waitingData = queueForRoutingVectorWaiting.front(); + for (uint16_t i{0}; i < routingVector.size(); ++i) + { + routing_table_t routingTable = routingVector[i]; + if (macToString(routingTable.originalTargetMAC) == macToString(waitingData.transmittedData.originalTargetMAC)) + { + queueForRoutingVectorWaiting.pop(); + outgoing_data_t outgoingData; + esp_memset(&outgoingData, 0, sizeof(outgoing_data_t)); + memcpy(&outgoingData.transmittedData, &waitingData.transmittedData, sizeof(transmitted_data_t)); + memcpy(&outgoingData.intermediateTargetMAC, &routingTable.intermediateTargetMAC, 6); + queueForOutgoingData.push(outgoingData); +#ifdef PRINT_LOG + Serial.print("CHECKING ROUTING TABLE... Routing to MAC "); + Serial.print(macToString(outgoingData.transmittedData.originalTargetMAC)); + Serial.print(" found. Target is "); + Serial.print(macToString(outgoingData.intermediateTargetMAC)); + Serial.println("."); +#endif + return; + } + } + if ((millis() - waitingData.time) > maxTimeForRoutingInfoWaiting_) + { + queueForRoutingVectorWaiting.pop(); +#ifdef PRINT_LOG + Serial.print("CHECKING ROUTING TABLE... Routing to MAC "); + Serial.print(macToString(waitingData.transmittedData.originalTargetMAC)); + Serial.println(" not found."); + switch (waitingData.transmittedData.messageType) + { + case UNICAST: + Serial.print("UNICAST"); + break; + case UNICAST_WITH_CONFIRM: + Serial.print("UNICAST_WITH_CONFIRM"); + break; + case DELIVERY_CONFIRM_RESPONSE: + Serial.print("DELIVERY_CONFIRM_RESPONSE"); + break; + default: + break; + } + Serial.print(" message from MAC "); + Serial.print(macToString(waitingData.transmittedData.originalSenderMAC)); + Serial.print(" to MAC "); + Serial.print(macToString(waitingData.transmittedData.originalTargetMAC)); + Serial.print(" via MAC "); + Serial.print(macToString(waitingData.intermediateTargetMAC)); + Serial.println(" undelivered."); +#endif + if (waitingData.transmittedData.messageType == UNICAST_WITH_CONFIRM && macToString(waitingData.transmittedData.originalSenderMAC) == macToString(localMAC)) + if (onConfirmReceivingCallback) + onConfirmReceivingCallback(waitingData.transmittedData.originalTargetMAC, false); + } + } +} + +String ZHNetwork::getNodeMac() +{ + return macToString(localMAC); +} + +IPAddress ZHNetwork::getNodeIp() +{ + if (workMode_ == ESP_NOW_AP) + return WiFi.softAPIP(); + if (workMode_ == ESP_NOW_STA) + return WiFi.localIP(); + return IPAddress(0, 0, 0, 0); +} + +String ZHNetwork::getFirmwareVersion() +{ + return firmware; +} + +String ZHNetwork::macToString(const uint8_t *mac) +{ + String string; + const char baseChars[16] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; + for (uint32_t i{0}; i < 6; ++i) + { + string += (char)pgm_read_byte(baseChars + (mac[i] >> 4)); + string += (char)pgm_read_byte(baseChars + mac[i] % 16); + } + return string; +} + +uint8_t *ZHNetwork::stringToMac(const String &string, uint8_t *mac) +{ + const uint8_t baseChars[75]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0, 0, + 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 0, 0, 0, 0, 0, 0, + 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35}; + for (uint32_t i = 0; i < 6; ++i) + mac[i] = (pgm_read_byte(baseChars + string.charAt(i * 2) - '0') << 4) + pgm_read_byte(baseChars + string.charAt(i * 2 + 1) - '0'); + return mac; +} + +error_code_t ZHNetwork::setMaxNumberOfAttempts(const uint8_t maxNumberOfAttempts) +{ + if (maxNumberOfAttempts < 1 || maxNumberOfAttempts > 10) + return ERROR; + maxNumberOfAttempts_ = maxNumberOfAttempts; + return SUCCESS; +} + +uint8_t ZHNetwork::getMaxNumberOfAttempts() +{ + return maxNumberOfAttempts_; +} + +error_code_t ZHNetwork::setMaxWaitingTimeBetweenTransmissions(const uint8_t maxWaitingTimeBetweenTransmissions) +{ + if (maxWaitingTimeBetweenTransmissions < 50 || maxWaitingTimeBetweenTransmissions > 250) + return ERROR; + maxWaitingTimeBetweenTransmissions_ = maxWaitingTimeBetweenTransmissions; + return SUCCESS; +} + +uint8_t ZHNetwork::getMaxWaitingTimeBetweenTransmissions() +{ + return maxWaitingTimeBetweenTransmissions_; +} + +error_code_t ZHNetwork::setMaxWaitingTimeForRoutingInfo(const uint16_t maxTimeForRoutingInfoWaiting) +{ + if (maxTimeForRoutingInfoWaiting < 500 || maxTimeForRoutingInfoWaiting > 5000) + return ERROR; + maxTimeForRoutingInfoWaiting_ = maxTimeForRoutingInfoWaiting; + return SUCCESS; +} + +uint16_t ZHNetwork::getMaxWaitingTimeForRoutingInfo() +{ + return maxTimeForRoutingInfoWaiting_; +} + +#if defined(ESP8266) +void IRAM_ATTR ZHNetwork::onDataSent(uint8_t *mac, uint8_t status) +#endif +#if defined(ESP32) + void IRAM_ATTR ZHNetwork::onDataSent(const uint8_t *mac, esp_now_send_status_t status) +#endif +{ + confirmReceivingSemaphore = true; + confirmReceiving = status ? false : true; +} + +#if defined(ESP8266) +void IRAM_ATTR ZHNetwork::onDataReceive(uint8_t *mac, uint8_t *data, uint8_t length) +#endif +#if defined(ESP32) + void IRAM_ATTR ZHNetwork::onDataReceive(const uint8_t *mac, const uint8_t *data, int length) +#endif +{ + if (criticalProcessSemaphore) + return; + criticalProcessSemaphore = true; + if (length != sizeof(transmitted_data_t)) + { + criticalProcessSemaphore = false; + return; + } + incoming_data_t incomingData; + memcpy(&incomingData.transmittedData, data, sizeof(transmitted_data_t)); + if (macToString(incomingData.transmittedData.originalSenderMAC) == macToString(localMAC)) + { + criticalProcessSemaphore = false; + return; + } + if (String(netName_) != "") + { + if (String(incomingData.transmittedData.netName) != String(netName_)) + { + criticalProcessSemaphore = false; + return; + } + } + for (uint8_t i{0}; i < sizeof(lastMessageID) / 2; ++i) + if (lastMessageID[i] == incomingData.transmittedData.messageID) + { + criticalProcessSemaphore = false; + return; + } + for (uint8_t i{sizeof(lastMessageID) / 2 - 1}; i >= 1; --i) + lastMessageID[i] = lastMessageID[i - 1]; + lastMessageID[0] = incomingData.transmittedData.messageID; + memcpy(&incomingData.intermediateSenderMAC, mac, 6); + queueForIncomingData.push(incomingData); + criticalProcessSemaphore = false; +} + +void ZHNetwork::broadcastMessage(const char *data, const uint8_t *target, message_type_t type) +{ + outgoing_data_t outgoingData; + esp_memset(&outgoingData, 0, sizeof(outgoing_data_t)); + outgoingData.transmittedData.messageType = type; + outgoingData.transmittedData.messageID = ((uint16_t)random(32767) << 8) | (uint16_t)random(32767); + memcpy(&outgoingData.transmittedData.netName, &netName_, 20); + memcpy(&outgoingData.transmittedData.originalTargetMAC, target, 6); + memcpy(&outgoingData.transmittedData.originalSenderMAC, &localMAC, 6); + strcpy(outgoingData.transmittedData.message, data); + memcpy(&outgoingData.intermediateTargetMAC, &broadcastMAC, 6); + queueForOutgoingData.push(outgoingData); +#ifdef PRINT_LOG + switch (outgoingData.transmittedData.messageType) + { + case BROADCAST: + Serial.print("BROADCAST"); + break; + case SEARCH_REQUEST: + Serial.print("SEARCH_REQUEST"); + break; + case SEARCH_RESPONSE: + Serial.print("SEARCH_RESPONSE"); + break; + default: + break; + } + Serial.print(" message from MAC "); + Serial.print(macToString(outgoingData.transmittedData.originalSenderMAC)); + Serial.print(" to MAC "); + Serial.print(macToString(outgoingData.transmittedData.originalTargetMAC)); + Serial.println(" added to queue."); +#endif +} + +void ZHNetwork::unicastMessage(const char *data, const uint8_t *target, const uint8_t *sender, message_type_t type) +{ + outgoing_data_t outgoingData; + esp_memset(&outgoingData, 0, sizeof(outgoing_data_t)); + outgoingData.transmittedData.messageType = type; + outgoingData.transmittedData.messageID = ((uint16_t)random(32767) << 8) | (uint16_t)random(32767); + memcpy(&outgoingData.transmittedData.netName, &netName_, 20); + memcpy(&outgoingData.transmittedData.originalTargetMAC, target, 6); + memcpy(&outgoingData.transmittedData.originalSenderMAC, sender, 6); + strcpy(outgoingData.transmittedData.message, data); + for (uint16_t i{0}; i < routingVector.size(); ++i) + { + routing_table_t routingTable = routingVector[i]; + if (macToString(routingTable.originalTargetMAC) == macToString(target)) + { + memcpy(&outgoingData.intermediateTargetMAC, &routingTable.intermediateTargetMAC, 6); + queueForOutgoingData.push(outgoingData); +#ifdef PRINT_LOG + Serial.print("CHECKING ROUTING TABLE... Routing to MAC "); + Serial.print(macToString(outgoingData.transmittedData.originalTargetMAC)); + Serial.print(" found. Target is "); + Serial.print(macToString(outgoingData.intermediateTargetMAC)); + Serial.println("."); + switch (outgoingData.transmittedData.messageType) + { + case UNICAST: + Serial.print("UNICAST"); + break; + case UNICAST_WITH_CONFIRM: + Serial.print("UNICAST_WITH_CONFIRM"); + break; + case DELIVERY_CONFIRM_RESPONSE: + Serial.print("DELIVERY_CONFIRM_RESPONSE"); + break; + default: + break; + } + Serial.print(" message from MAC "); + Serial.print(macToString(outgoingData.transmittedData.originalSenderMAC)); + Serial.print(" to MAC "); + Serial.print(macToString(outgoingData.transmittedData.originalTargetMAC)); + Serial.print(" via MAC "); + Serial.print(macToString(outgoingData.intermediateTargetMAC)); + Serial.println(" added to queue."); +#endif + return; + } + } + memcpy(&outgoingData.intermediateTargetMAC, target, 6); + queueForOutgoingData.push(outgoingData); +#ifdef PRINT_LOG + Serial.print("CHECKING ROUTING TABLE... Routing to MAC "); + Serial.print(macToString(outgoingData.transmittedData.originalTargetMAC)); + Serial.print(" not found. Target is "); + Serial.print(macToString(outgoingData.intermediateTargetMAC)); + Serial.println("."); + switch (outgoingData.transmittedData.messageType) + { + case UNICAST: + Serial.print("UNICAST"); + break; + case UNICAST_WITH_CONFIRM: + Serial.print("UNICAST_WITH_CONFIRM"); + break; + case DELIVERY_CONFIRM_RESPONSE: + Serial.print("DELIVERY_CONFIRM_RESPONSE"); + break; + default: + break; + } + Serial.print(" message from MAC "); + Serial.print(macToString(outgoingData.transmittedData.originalSenderMAC)); + Serial.print(" to MAC "); + Serial.print(macToString(outgoingData.transmittedData.originalTargetMAC)); + Serial.print(" via MAC "); + Serial.print(macToString(outgoingData.intermediateTargetMAC)); + Serial.println(" added to queue."); +#endif +} \ No newline at end of file diff --git a/src/ZHNetwork.h b/src/ZHNetwork.h new file mode 100644 index 0000000..03f6740 --- /dev/null +++ b/src/ZHNetwork.h @@ -0,0 +1,146 @@ +#ifndef ZHNETWORK_H +#define ZHNETWORK_H + +#include "Arduino.h" +#include "bits/stdc++.h" +#if defined(ESP8266) +#include "ESP8266WiFi.h" +#include "espnow.h" +#endif +#if defined(ESP32) +#include "WiFi.h" +#include "esp_wifi.h" +#include "esp_now.h" +#endif + +// #define PRINT_LOG // Uncomment to display to serial port the full operation log. + +#if defined(ESP8266) +#define esp_memset memset // Just for remove the compiler notice for ESP8266 at "memset". I don't know why this is happening... +#endif +#if defined(ESP32) +#define esp_memset memset +#endif + +typedef struct +{ + uint8_t messageType; + uint16_t messageID; + char netName[20]; + uint8_t originalTargetMAC[6]; + uint8_t originalSenderMAC[6]; + char message[200]; +} transmitted_data_t; + +typedef struct +{ + uint8_t intermediateTargetMAC[6]; + transmitted_data_t transmittedData; +} outgoing_data_t; + +typedef struct +{ + uint8_t intermediateSenderMAC[6]; + transmitted_data_t transmittedData; +} incoming_data_t; + +typedef struct +{ + uint64_t time; + uint8_t intermediateTargetMAC[6]; + transmitted_data_t transmittedData; +} waiting_data_t; + +typedef struct +{ + uint8_t originalTargetMAC[6]; + uint8_t intermediateTargetMAC[6]; +} routing_table_t; + +typedef enum +{ + ESP_NOW = 1, + ESP_NOW_AP, + ESP_NOW_STA +} work_mode_t; + +typedef enum +{ + BROADCAST = 1, + UNICAST, + UNICAST_WITH_CONFIRM, + DELIVERY_CONFIRM_RESPONSE, + SEARCH_REQUEST, + SEARCH_RESPONSE +} message_type_t; + +typedef enum // Just for further development. +{ + SUCCESS = 1, + ERROR = 0 +} error_code_t; + +typedef std::function on_message_t; +typedef std::function on_confirm_t; +typedef std::vector routing_vector_t; +typedef std::queue outgoing_queue_t; +typedef std::queue incoming_queue_t; +typedef std::queue waiting_queue_t; + +class ZHNetwork +{ +public: + ZHNetwork &setOnBroadcastReceivingCallback(on_message_t onBroadcastReceivingCallback); + ZHNetwork &setOnUnicastReceivingCallback(on_message_t onUnicastReceivingCallback); + ZHNetwork &setOnConfirmReceivingCallback(on_confirm_t onConfirmReceivingCallback); + + error_code_t setWorkMode(const work_mode_t workMode); + work_mode_t getWorkMode(void); + + error_code_t setNetName(const char *netName); + String getNetName(void); + + error_code_t setStaSetting(const char *ssid, const char *password); + error_code_t setApSetting(const char *ssid, const char *password); + + error_code_t begin(void); + + void sendBroadcastMessage(const char *data); + void sendUnicastMessage(const char *data, const uint8_t *target, const bool confirm = false); + + void maintenance(void); + + String getNodeMac(void); + IPAddress getNodeIp(void); + String getFirmwareVersion(void); + String readErrorCode(error_code_t code); // Just for further development. + + static String macToString(const uint8_t *mac); + uint8_t *stringToMac(const String &string, uint8_t *mac); + + error_code_t setMaxNumberOfAttempts(const uint8_t maxNumberOfAttempts); + uint8_t getMaxNumberOfAttempts(void); + error_code_t setMaxWaitingTimeBetweenTransmissions(const uint8_t maxWaitingTimeBetweenTransmissions); + uint8_t getMaxWaitingTimeBetweenTransmissions(void); + error_code_t setMaxWaitingTimeForRoutingInfo(const uint16_t maxTimeForRoutingInfoWaiting); + uint16_t getMaxWaitingTimeForRoutingInfo(void); + +private: +#if defined(ESP8266) + static void onDataSent(uint8_t *mac, uint8_t status); + static void onDataReceive(uint8_t *mac, uint8_t *data, uint8_t length); +#endif +#if defined(ESP32) + static void onDataSent(const uint8_t *mac, esp_now_send_status_t status); + static void onDataReceive(const uint8_t *mac, const uint8_t *data, int length); +#endif + void broadcastMessage(const char *data, const uint8_t *target, message_type_t type); + void unicastMessage(const char *data, const uint8_t *target, const uint8_t *sender, message_type_t type); + on_message_t onBroadcastReceivingCallback; + on_message_t onUnicastReceivingCallback; + on_confirm_t onConfirmReceivingCallback; + +protected: +}; + +#endif \ No newline at end of file