From c076331fadf0d54721f012c754c19d388705d65a Mon Sep 17 00:00:00 2001 From: Theo Arends <11044339+arendst@users.noreply.github.com> Date: Wed, 12 Feb 2025 17:45:06 +0100 Subject: [PATCH] Add support for Wiz Smart Remote using `#define USE_WIZMOTE` and command `SetOption164 1` --- BUILDS.md | 2 + CHANGELOG.md | 3 +- CODE_OWNERS.md | 6 +- RELEASENOTES.md | 2 + lib/lib_div/QuickESPNow/LICENSE.md | 19 + lib/lib_div/QuickESPNow/README.md | 80 +++ lib/lib_div/QuickESPNow/Throughput.xlsx | Bin 0 -> 12171 bytes .../advancedespnow/advancedespnow.cpp | 78 +++ .../examples/basicespnow/basicespnow.cpp | 67 +++ .../wifi_ap_and_espnow/wifi_ap_and_espnow.cpp | 70 +++ .../wifi_sta_and_espnow.cpp | 63 +++ lib/lib_div/QuickESPNow/library.json | 35 ++ lib/lib_div/QuickESPNow/library.properties | 9 + lib/lib_div/QuickESPNow/src/Comms_hal.h | 108 ++++ lib/lib_div/QuickESPNow/src/QuickEspNow.h | 12 + .../QuickESPNow/src/QuickEspNow_esp32.cpp | 521 ++++++++++++++++++ .../QuickESPNow/src/QuickEspNow_esp32.h | 169 ++++++ .../QuickESPNow/src/QuickEspNow_esp8266.cpp | 366 ++++++++++++ .../QuickESPNow/src/QuickEspNow_esp8266.h | 145 +++++ lib/lib_div/QuickESPNow/src/RingBuffer.h | 144 +++++ lib/lib_div/QuickESPNow/test/README | 11 + .../test/test_peer_list/test_peer_list.cpp | 135 +++++ tasmota/include/tasmota_types.h | 10 +- tasmota/my_user_config.h | 5 +- tasmota/tasmota_support/support_features.ino | 4 +- .../tasmota_xdrv_driver/xdrv_77_wizmote.ino | 199 +++++++ tools/decode-status.py | 5 +- 27 files changed, 2255 insertions(+), 13 deletions(-) create mode 100644 lib/lib_div/QuickESPNow/LICENSE.md create mode 100644 lib/lib_div/QuickESPNow/README.md create mode 100644 lib/lib_div/QuickESPNow/Throughput.xlsx create mode 100644 lib/lib_div/QuickESPNow/examples/advancedespnow/advancedespnow.cpp create mode 100644 lib/lib_div/QuickESPNow/examples/basicespnow/basicespnow.cpp create mode 100644 lib/lib_div/QuickESPNow/examples/wifi_ap_and_espnow/wifi_ap_and_espnow.cpp create mode 100644 lib/lib_div/QuickESPNow/examples/wifi_sta_and_espnow/wifi_sta_and_espnow.cpp create mode 100644 lib/lib_div/QuickESPNow/library.json create mode 100644 lib/lib_div/QuickESPNow/library.properties create mode 100644 lib/lib_div/QuickESPNow/src/Comms_hal.h create mode 100644 lib/lib_div/QuickESPNow/src/QuickEspNow.h create mode 100644 lib/lib_div/QuickESPNow/src/QuickEspNow_esp32.cpp create mode 100644 lib/lib_div/QuickESPNow/src/QuickEspNow_esp32.h create mode 100644 lib/lib_div/QuickESPNow/src/QuickEspNow_esp8266.cpp create mode 100644 lib/lib_div/QuickESPNow/src/QuickEspNow_esp8266.h create mode 100644 lib/lib_div/QuickESPNow/src/RingBuffer.h create mode 100644 lib/lib_div/QuickESPNow/test/README create mode 100644 lib/lib_div/QuickESPNow/test/test_peer_list/test_peer_list.cpp create mode 100644 tasmota/tasmota_xdrv_driver/xdrv_77_wizmote.ino diff --git a/BUILDS.md b/BUILDS.md index 5aa175e4983b..f73691be2875 100644 --- a/BUILDS.md +++ b/BUILDS.md @@ -246,6 +246,8 @@ Note: the `minimal` variant is not listed as it shouldn't be used outside of the | USE_IR_RECEIVE | - | x / - | x | x | x | x | | USE_IR_REMOTE_FULL | - | - / - | - | - | x | - | Enable ALL protocols | | | | | | | | | +| USE_WIZMOTE | - | - / - | - | - | - | - | +| | | | | | | | | USE_SR04 | - | - / - | - | x | - | - | | USE_ME007 | - | - / - | - | - | - | - | | USE_DYP | - | - / - | - | - | - | - | diff --git a/CHANGELOG.md b/CHANGELOG.md index 98971bf443f4..f3f091e9a6f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,8 @@ All notable changes to this project will be documented in this file. - Support for RC-switch decoding of 64-bit received data - Berry `tasmota.defer()` (#22976) - Support for Lithuanian language translations by zzdovydas (#22971) -- `MqttTLS` field in `Status 6` to indicate if the MQTT connection is encrypted +- `MqttTLS` field in `Status 6` to indicate if the MQTT connection is encrypted (#22995) +- Support for Wiz Smart Remote using `#define USE_WIZMOTE` and command `SetOption164 1` ### Breaking Changed diff --git a/CODE_OWNERS.md b/CODE_OWNERS.md index 908dbb37d3c2..b2de285ee689 100644 --- a/CODE_OWNERS.md +++ b/CODE_OWNERS.md @@ -85,10 +85,10 @@ In addition to @arendst the following code is mainly owned by: | xdrv_71_magic_switch | @barbudor | xdrv_72_pipsolar | @chefpro | xdrv_73_lora | @arendst -| xdrv_74 | +| xdrv_74_lorawan | @arendst | xdrv_75_dali | @eeak, @arendst -| xdrv_76 | -| xdrv_77 | +| xdrv_76_serial_i2c | @s-hadinger +| xdrv_77_wizmote | @arendst | xdrv_78 | | xdrv_79_esp32_ble | @staars, @btsimonh | xdrv_81_esp32_webcam | @gemu, @philrich diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 95f81d626cdd..8c751fb97a4e 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -127,6 +127,8 @@ The latter links can be used for OTA upgrades too like ``OtaUrl https://ota.tasm - Support for ESP32 Two-Wire Automotive Interface (TWAI) or Controller Area Network (CAN) busses - `#define FIX_JSON_HEXADECIMAL` to change JSON hexadecimal value "FF5F78" into "0xFF5F78" [#22919](https://github.com/arendst/Tasmota/issues/22919) - Support for RC-switch decoding of 64-bit received data +- Support for Wiz Smart Remote using `#define USE_WIZMOTE` and command `SetOption164 1` +- `MqttTLS` field in `Status 6` to indicate if the MQTT connection is encrypted [#22995](https://github.com/arendst/Tasmota/issues/22995) - Formatter `%_U` for `ext_snprintf_P()` to print uint64_t variable as decimal equivalent to `%llu` - GPS driver select baudrate using GPIO GPS_RX1 (9600bps), GPS_RX2 (19200bps) or GPS_RX3 (38400bps) [#22869](https://github.com/arendst/Tasmota/issues/22869) - I2S AAC support for web radio [#22787](https://github.com/arendst/Tasmota/issues/22787) diff --git a/lib/lib_div/QuickESPNow/LICENSE.md b/lib/lib_div/QuickESPNow/LICENSE.md new file mode 100644 index 000000000000..21cd757e40c5 --- /dev/null +++ b/lib/lib_div/QuickESPNow/LICENSE.md @@ -0,0 +1,19 @@ +# Copyright (c) 2022 gmag11@github + +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. diff --git a/lib/lib_div/QuickESPNow/README.md b/lib/lib_div/QuickESPNow/README.md new file mode 100644 index 000000000000..d5fccb7f4cb9 --- /dev/null +++ b/lib/lib_div/QuickESPNow/README.md @@ -0,0 +1,80 @@ +# Quick ESP-NOW + +[![PlatformIO Registry](https://badges.registry.platformio.org/packages/gmag11/library/QuickEspNow.svg)](https://registry.platformio.org/libraries/gmag11/QuickEspNow) + +## Introduction + +This fork includes a change to the rx_cb signature for compatibility with the 5.x Espressif IDF. + +This is a library for Arduino framework to be used with Espressif ESP8266 and ESP32 series microcontrollers. + +ESP-NOW is a wireless communication protocol that allows two devices to send data to each other without the need for a wireless network. You can read more about it here [https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/network/esp_now.html](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/network/esp_now.html) + +## Benefits + +Usage of ESP-NOW may not be straightforward. There are some restrictions that has to be considered, as limit of number of devices, WiFi channel selection, and other factors. + +This library pretends to hide all that restrictions so that it can be used with just a few lines of code. + +Besides, it removes some limitations: + +- No more 20 devices limit. You can use ESP-NOW with **any number of devices**. Library takes control of peer registration and makes it transparent to you. +- Channel selection is not required for WiFi coexistence. +- No need to assign a role to each device. Just use it for peer to peer communication. +- **RSSI** information of every message, so that receiver can estimate how close the sender is. +- Receiver can distiguish between broadcast and unicast messages. +- Tested maximum througput, about 600 kbps continuous with default parameters. +- Encryption is not supported. Usage of ESP-NOW encryption restrict system to 6 devices. You can implement data encryption in a higher layer. +- Asynchronous Sending Mode: The send method now supports a blocking mode, returning a value only after the message send has been confirmed. It returns 0 if the message was sent successfully and a different value if there was an error. This feature ensures reliable message delivery by waiting for confirmation before proceeding withour the user needing to implement a TX callback function. +It is importatnt to know that the best performance is achieved when using asynchronous mode, as it allows the library to send messages in the background while the user code continues to run. The synchronous mode is provided for users who require a blocking send method for ease of use. +- Default Mode Change: For ease of use, the asynchronous mode is now the default configuration. This mode can be set during the `begin` function call, streamlining the setup process for new projects. This change aims to simplify the initial configuration for developers by automatically opting for the more user-friendly synchronous sending mode. If you require best performance, you can still change the mode to asynchronous by calling `begin` function with the proper parameter. + +## Performance + +I include this table to show the performance of the library. It was tested with ESP32 and ESP8266, sending messages as fastest as possible. The test has been repeated with different message lengths and using synchronous and asynchronous modes. + +| Device | broadcast/unicast | sync/async | 250 bytes | 125 bytes | 75 bytes | 35 bytes | 12 bytes | +|--------|-------------------|------------|-----------|-----------|----------|----------|-----------| +| ESP32 | broadcast | async | 640 kbps | 450 kbps | 340 kbps | 190 kbps | 75 kbps | +| ESP32 | broadcast | sync | 615 kbps | 440 kbps | 320 kbps | 180 kbps | 73 kbps | +| ESP8266| broadcast | async | 200 kbps | 100 kbps | 60 kbps | 28 kbps | 9.5 kbps | +| ESP8266| broadcast | sync | 200 kbps | 100 kbps | 60 kbps | 28 kbps | 9.5 kbps | +| ESP32 | unicast | async | 570 kbps | 400 kbps | 285 kbps | 160 kbps | 60 kbps | +| ESP32 | unicast | sync | 550 kbps | 375 kbps | 270 kbps | 150 kbps | 57 kbps | +| ESP8266| unicast | async | 200 kbps | 100 kbps | 60 kbps | 28 kbps | 9.5 kbps | +| ESP8266| unicast | sync | 200 kbps | 100 kbps | 60 kbps | 28 kbps | 9.5 kbps | + +**Note** : In previous versions of the library, esp8266 was able to send messages at 600 kBps, but it was a mistake. The actual performance is 200 kbps. The table has been updated to reflect the correct values. It was due a to a missing check to avoid sending a message before the previous one was confirmed. This check has been added in version 0.8.1. +It seems that this check is not completely mandarory and both ESP8266 and ESP32 are able to send messages correctly even if latest one has not been confirmed. I will investigate what implications this may have and if it is possible to (optionally) remove this check in future versions. + +Please note that these maximum values represent the best-case scenario without any message loss, assuming the microcontroller is not running any other tasks. + +However, it's important to consider that in synchronous mode, where the user code is blocked until the message is sent (which can take from 1 to 20 ms), the actual performance may be significantly lower depending on the rest of the code. + +On the other hand, in asynchronous mode, the `send` function returns in just 22us for both ESP32 and ESP8266, so it is not expected to have a significant impact on the rest of the code. + +Please note that the performance of ESP8266 is lower than ESP32. This may cause problems if an ESP32 is sending messages at a higher rate than the ESP8266 can handle. In such cases, the receiver may lose messages or even crash. If you need to use both devices in the same network, it is recommended to keep the message rate at a safe level for the slowest device. + +## Usage + +To get started with Quick ESP-NOW, simply call the `begin` function and set a receiving callback function if you need to receive data. Then, you can use the `send` function to send data to a specific device or use `ESPNOW_BROADCAST_ADDRESS` to send data to all listening devices on the same channel. + +```C++ +void dataReceived (uint8_t* address, uint8_t* data, uint8_t len, signed int rssi, bool broadcast) { + Serial.printf("Received message: %.*s\n", len, data); +} + +void setup () { + Serial.begin (115200); + WiFi.mode (WIFI_MODE_STA); + WiFi.disconnect (false); + quickEspNow.onDataRcvd (dataReceived); + quickEspNow.begin (1); // If you are not connected to WiFi, channel should be specified +} + +void loop () { + String message = "Hello, world!"; + quickEspNow.send (DEST_ADDR, (uint8_t*)message.c_str (), message.length ()); + delay (1000); +} +``` diff --git a/lib/lib_div/QuickESPNow/Throughput.xlsx b/lib/lib_div/QuickESPNow/Throughput.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..1ee0095bb2b51bb5552e2504b0e6b6ac00a425d4 GIT binary patch literal 12171 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%0D`O8Vp?3>*S(R z6-bl0heixS-lzs@iN)QS?UdMUyyKBNsC->{k!Yx*Mtu57Y5$ZfN1f^#C%`_*w0tTf z-gz#kjQas0b+?!y6a-v|qe*3mGlnSa1P&f^>tp2U6V!gk|@GWwIS6qoEL~!{?zd?2J0QoccEi@ z@3s1cI#f8}^3|pt%G9Q}U{&6_;?JSJH5`H+1{Loswo6JFFM&@^+X|!Fn_u0O;wwxOe9YK>ZrpmJ&rYZ=jf%5h?N>6vRa;FxP=W*C`3&plD!go$PSGPn|TXYNYJEEVQ0t$hD;thB+I*oSBs2hk`5)3*7&)1lsyRDZ+L{02p7Wa_y9`#$<=2C+ zwBF{S_sl^8(ZN(cHA^Q?yz_&|?0}z3vYr@TofW~xYAZ$ES=u?PiVXeHMqzISX{#XP zk)fO;Z%bf1O6C3ZcySSr?l8ew{Y5JIGRIH&&8#MrABFI&h>Y$|h*xixojMCyT_AfV z-+Wcd=yQxYbXTD%M44*&Nvd>0#+538td#(s=0xrZJKm$Y>Z%6mkcGNmIlShh`(1KG zKx45XM`eH#j+#ANJk{pbI;IUAGkXT_;o2VZDAe1&Sa(bh;!A})!}m2X?@!jmg3i)n zar#i(qwV6?acOg!Syd%q5QouNuSqea-SKZdv?Gv+Mod8M2O0^82kEZ`qlT;Wofw6p zzX6WU~Rg;Gqn#b|KXLe@{OA&_o+bK^%URQ8qEm_`_(xrkYV{wQJttlK2J`W}+4pQQ z96(%2H>bT<(@63zN0ak1`^S@Y8wtK2 z7*Sk}hrB_32g3Mn0eUed?g!?h*B3&wuUlIV-?q5Z?*)qpfXMNTQ%&H(nkIVal3rF=c{7iisFQ~=Vih@SO;z+ z3C2o`t!&Q?U}{(tYfl(Ay)^^*g;cel4r9DY1nqL$N2Sld1$j;yg4c)06_D{3^=?H!r9D8&CJ>PCo@=n`_0B| zY-x9H2y3WpcxohVN(+=WqcuJSPoNTs2apPUB17tDfbNth6+kJZhG8`6H|#h3YS;|` zk767~biE{mBRn%V&Q&r^OVFb-37a2I!jXZTe+mhK4tW-7 zd^QB{xIw{|bZLT)OBVxvT{Qt30*rVqL|EZ^fMe7tjnvYfx}@b)lT^72(Bv;~{cp3< zpY+8{{lzD|U(P_#Ud}*F?M;-O>>WTXCiYHdKV|ymr)sGFgW zsQRv3mGqZr-8m?@i zh)!TGX{W(kgeLZOWaq(tZ7kOcy#nziHr>;=gi8Wyh`sri0lXM&2#_)H0V3Z1tpj%gu)Jp5|Yg)id=!_PLu{&T6xpaMa(rfUSEz?@TEbc$JjQ zyn^in8Ce<}6$&cZ*b7sXz54fU_SXyoAVUswFe-#@O5M1|PNpNC7*4=_V8 zcDe-!I@#;)!A)60eudJmd?=%zt7fX0yy|N14;;~J75p*kZ{4r%&Z3y?f3%tjL=CuW zo^R!>A%47$c3ez&HT9`t1@sKcE##xKLX$RD1<+TXF*Wpy7`)3&T^7=suM_rm5~U8CGlQqZt>` znxiz2-&?P}0dC5x-m|1$5Z*Um>6ae^ZaRwry)uqT{Vcsgd7;Yp0;GbYx7D;I$mHA} zTm?cI@aMv@ybB$s%l?bTps9yt+_ya&msC^dK$RAz^G0f3q$1M5Xr%$`-4dt|ATVzZuS)5D}*MY78GV+~$& za&dVF-G*|#8r8w$C&IzDNOXri6tO7`F+MvEe0z_voNG}g%H2KayB|32@J3-{H zL6_6gWeZbddKt-BqK}zk`!>VXzDmA>8-8o>x|#Tc$z&(;rh;-{QQ^01_wosEZy#;D z!4&nk%#rC?A&K_cY?V!lT;~jQ3;9Mpd?|4&HsyY=5S;;+`| ebbqln`Cr9QmV +#if defined ESP32 +#include +#include +#elif defined ESP8266 +#include +#define WIFI_MODE_STA WIFI_STA +#else +#error "Unsupported platform" +#endif //ESP32 +#include + +static const String msg = "Hello ESP-NOW!"; + +#define USE_BROADCAST 1 // Set this to 1 to use broadcast communication + +#if USE_BROADCAST != 1 +// set the MAC address of the receiver for unicast +static uint8_t receiver[] = { 0x12, 0x34, 0x56, 0x78, 0x90, 0x12 }; +#define DEST_ADDR receiver +#else //USE_BROADCAST != 1 +#define DEST_ADDR ESPNOW_BROADCAST_ADDRESS +#endif //USE_BROADCAST != 1 + +bool sent = true; + +const unsigned int SEND_MSG_MSEC = 2000; + +void dataSent (uint8_t* address, uint8_t status) { + sent = true; + Serial.printf ("Message sent to " MACSTR ", status: %d\n", MAC2STR (address), status); +} + +void dataReceived (uint8_t* address, uint8_t* data, uint8_t len, signed int rssi, bool broadcast) { + Serial.print ("Received: "); + Serial.printf ("%.*s\n", len, data); + Serial.printf ("RSSI: %d dBm\n", rssi); + Serial.printf ("From: " MACSTR "\n", MAC2STR (address)); + Serial.printf ("%s\n", broadcast ? "Broadcast" : "Unicast"); +} + +void setup () { + Serial.begin (115200); + WiFi.mode (WIFI_MODE_STA); +#if defined ESP32 + WiFi.disconnect (false, true); +#elif defined ESP8266 + WiFi.disconnect (false); +#endif //ESP32 + + Serial.printf ("Connected to %s in channel %d\n", WiFi.SSID ().c_str (), WiFi.channel ()); + Serial.printf ("IP address: %s\n", WiFi.localIP ().toString ().c_str ()); + Serial.printf ("MAC address: %s\n", WiFi.macAddress ().c_str ()); + quickEspNow.begin (1, 0, false); + quickEspNow.onDataSent (dataSent); + quickEspNow.onDataRcvd (dataReceived); +} + +void loop () { + static time_t lastSend = 0; + static unsigned int counter = 0; + + // Sent flag is needed to wait for the message to be actually sent. Avoids messages dropping, maximizing throughput. + // readyToSendData() is used to avoid sending messages too fast, which can lead to messages dropping. + if (quickEspNow.readyToSendData() && sent && ((millis () - lastSend) > SEND_MSG_MSEC)) { + lastSend = millis (); + String message = String (msg) + " " + String (counter++); + sent = false; + if (!quickEspNow.send (DEST_ADDR, (uint8_t*)message.c_str (), message.length ())) { + Serial.printf (">>>>>>>>>> Message sent\n"); + } else { + Serial.printf (">>>>>>>>>> Message not sent\n"); + sent = true; // In case of error we need to set the flag to true to avoid blocking the loop + } + + } + +} diff --git a/lib/lib_div/QuickESPNow/examples/basicespnow/basicespnow.cpp b/lib/lib_div/QuickESPNow/examples/basicespnow/basicespnow.cpp new file mode 100644 index 000000000000..3a1627cdd619 --- /dev/null +++ b/lib/lib_div/QuickESPNow/examples/basicespnow/basicespnow.cpp @@ -0,0 +1,67 @@ +#include +#if defined ESP32 +#include +#include +#elif defined ESP8266 +#include +#define WIFI_MODE_STA WIFI_STA +#else +#error "Unsupported platform" +#endif //ESP32 +#include + +static const String msg = "Hello ESP-NOW!"; + +#define USE_BROADCAST 1 // Set this to 1 to use broadcast communication + +#if USE_BROADCAST != 1 +// set the MAC address of the receiver for unicast +static uint8_t receiver[] = { 0x12, 0x34, 0x56, 0x78, 0x90, 0x12 }; +#define DEST_ADDR receiver +#else //USE_BROADCAST != 1 +#define DEST_ADDR ESPNOW_BROADCAST_ADDRESS +#endif //USE_BROADCAST != 1 + +// Send message every 2 seconds +const unsigned int SEND_MSG_MSEC = 2000; + +void dataReceived (uint8_t* address, uint8_t* data, uint8_t len, signed int rssi, bool broadcast) { + Serial.print ("Received: "); + Serial.printf ("%.*s\n", len, data); + Serial.printf ("RSSI: %d dBm\n", rssi); + Serial.printf ("From: " MACSTR "\n", MAC2STR (address)); + Serial.printf ("%s\n", broadcast ? "Broadcast" : "Unicast"); +} + +void setup () { + Serial.begin (115200); + WiFi.mode (WIFI_MODE_STA); +#if defined ESP32 + WiFi.disconnect (false, true); +#elif defined ESP8266 + WiFi.disconnect (false); +#endif //ESP32 + Serial.printf ("Connected to %s in channel %d\n", WiFi.SSID ().c_str (), WiFi.channel ()); + Serial.printf ("IP address: %s\n", WiFi.localIP ().toString ().c_str ()); + Serial.printf ("MAC address: %s\n", WiFi.macAddress ().c_str ()); + quickEspNow.onDataRcvd (dataReceived); +#ifdef ESP32 + quickEspNow.setWiFiBandwidth (WIFI_IF_STA, WIFI_BW_HT20); // Only needed for ESP32 in case you need coexistence with ESP8266 in the same network +#endif //ESP32 + quickEspNow.begin (1); // If you use no connected WiFi channel needs to be specified +} + +void loop () { + static unsigned int counter = 0; + + String message = String (msg) + " " + String (counter++); + if (!quickEspNow.send (DEST_ADDR, (uint8_t*)message.c_str (), message.length ())) { + Serial.println (">>>>>>>>>> Message sent"); + } else { + Serial.println (">>>>>>>>>> Message not sent"); + } + + delay (SEND_MSG_MSEC); + +} + diff --git a/lib/lib_div/QuickESPNow/examples/wifi_ap_and_espnow/wifi_ap_and_espnow.cpp b/lib/lib_div/QuickESPNow/examples/wifi_ap_and_espnow/wifi_ap_and_espnow.cpp new file mode 100644 index 000000000000..501cf2fe0726 --- /dev/null +++ b/lib/lib_div/QuickESPNow/examples/wifi_ap_and_espnow/wifi_ap_and_espnow.cpp @@ -0,0 +1,70 @@ +#include +#if defined ESP32 +#include +#include +#elif defined ESP8266 +#include +#define WIFI_MODE_STA WIFI_STA +#define WIFI_MODE_AP WIFI_AP +#else +#error "Unsupported platform" +#endif //ESP32 +#include + +static const String msg = "Hello esp-now from TTGO"; + +#define USE_BROADCAST 1 // Set this to 1 to use broadcast communication + +#if USE_BROADCAST != 1 +// set the MAC address of the receiver for unicast +static uint8_t receiver[] = { 0x12, 0x34, 0x56, 0x78, 0x90, 0x12 }; +#define DEST_ADDR receiver +#else //USE_BROADCAST != 1 +#define DEST_ADDR ESPNOW_BROADCAST_ADDRESS +#endif //USE_BROADCAST != 1 + +static const uint8_t CHANNEL = 1; + +void dataReceived (uint8_t* address, uint8_t* data, uint8_t len, signed int rssi, bool broadcast) { + Serial.print ("Received: "); + Serial.printf ("%.*s\n", len, data); + Serial.printf ("RSSI: %d dBm\n", rssi); + Serial.printf ("From: " MACSTR "\n", MAC2STR (address)); + Serial.printf ("%s\n", broadcast ? "Broadcast" : "Unicast"); +} + +void setup () { + Serial.begin (115200); + if (!WiFi.mode (WIFI_MODE_AP)) { + Serial.println ("WiFi mode not supported"); + } + if (!WiFi.softAP ("espnowAP", "1234567890", CHANNEL)) { + Serial.println ("WiFi access point not started"); + } + + Serial.printf ("Started AP %s in channel %d\n", WiFi.softAPSSID ().c_str (), WiFi.channel ()); + Serial.printf ("IP address: %s\n", WiFi.softAPIP ().toString ().c_str ()); + Serial.printf ("MAC address: %s\n", WiFi.softAPmacAddress ().c_str ()); +#ifdef ESP32 + quickEspNow.setWiFiBandwidth (WIFI_IF_AP, WIFI_BW_HT20); // Only needed for ESP32 in case you need coexistence with ESP8266 in the same network +#endif //ESP32 + quickEspNow.onDataRcvd (dataReceived); + quickEspNow.begin (CURRENT_WIFI_CHANNEL, WIFI_IF_AP); // Same channel must be used for both AP and ESP-NOW +} + +void loop () { + static time_t lastSend = 60000; + static unsigned int counter = 0; + + if (millis () - lastSend >= 1000) { + lastSend = millis (); + String message = String (msg) + " " + String (counter++); + if (!quickEspNow.send (DEST_ADDR, (uint8_t*)message.c_str (), message.length ())) { + Serial.printf (">>>>>>>>>> Message sent\n"); + } else { + Serial.printf (">>>>>>>>>> Message not sent\n"); + } + + } + +} \ No newline at end of file diff --git a/lib/lib_div/QuickESPNow/examples/wifi_sta_and_espnow/wifi_sta_and_espnow.cpp b/lib/lib_div/QuickESPNow/examples/wifi_sta_and_espnow/wifi_sta_and_espnow.cpp new file mode 100644 index 000000000000..62fbc4c5fd61 --- /dev/null +++ b/lib/lib_div/QuickESPNow/examples/wifi_sta_and_espnow/wifi_sta_and_espnow.cpp @@ -0,0 +1,63 @@ +#include +#if defined ESP32 +#include +#include +#elif defined ESP8266 +#include +#define WIFI_MODE_STA WIFI_STA +#else +#error "Unsupported platform" +#endif //ESP32 +#include + +static const String msg = "Hello esp-now!"; + +#define USE_BROADCAST 1 // Set this to 1 to use broadcast communication + +#if USE_BROADCAST != 1 +// set the MAC address of the receiver for unicast +static uint8_t receiver[] = { 0x12, 0x34, 0x56, 0x78, 0x90, 0x12 }; +#define DEST_ADDR receiver +#else //USE_BROADCAST != 1 +#define DEST_ADDR ESPNOW_BROADCAST_ADDRESS +#endif //USE_BROADCAST != 1 + +void dataReceived (uint8_t* address, uint8_t* data, uint8_t len, signed int rssi, bool broadcast) { + Serial.print ("Received: "); + Serial.printf ("%.*s\n", len, data); + Serial.printf ("RSSI: %d dBm\n", rssi); + Serial.printf ("From: " MACSTR "\n", MAC2STR(address)); + Serial.printf ("%s\n", broadcast ? "Broadcast" : "Unicast"); +} + +void setup () { + Serial.begin (115200); + WiFi.mode (WIFI_MODE_STA); + WiFi.begin ("ssid", "pass"); + while (WiFi.status () != WL_CONNECTED) { + delay (500); + Serial.print ("."); + } + Serial.printf ("Connected to %s in channel %d\n", WiFi.SSID ().c_str (), WiFi.channel ()); + Serial.printf ("IP address: %s\n", WiFi.localIP ().toString ().c_str ()); + Serial.printf ("MAC address: %s\n", WiFi.macAddress ().c_str ()); + quickEspNow.onDataRcvd (dataReceived); + quickEspNow.begin (); // Use no parameters to start ESP-NOW on same channel as WiFi, in STA mode and synchronous send mode +} + +void loop() { + static time_t lastSend = 60000; + static unsigned int counter = 0; + + if (millis () - lastSend >= 1000) { + lastSend = millis (); + String message = String (msg) + " " + String (counter++); + if (!quickEspNow.send (DEST_ADDR, (uint8_t*)message.c_str (), message.length ())) { + Serial.printf (">>>>>>>>>> Message sent\n"); + } else { + Serial.printf (">>>>>>>>>> Message not sent\n"); + } + + } + +} \ No newline at end of file diff --git a/lib/lib_div/QuickESPNow/library.json b/lib/lib_div/QuickESPNow/library.json new file mode 100644 index 000000000000..1e52b54b58c9 --- /dev/null +++ b/lib/lib_div/QuickESPNow/library.json @@ -0,0 +1,35 @@ +{ + "name": "QuickEspNow", + "frameworks": "arduino", + "version": "0.8.1", + "keywords": "esp-now, gateway, node, home", + "platforms": ["espressif32", "espressif8266"], + "description": "QuickEspNow is a library for ESP8266/ESP32 that allows you to send data over the ESP-NOW protocol.", + "url": "https://github.com/gmag11/QuickEspNow.git", + "authors": + { + "name": "Germán Martín", + "email": "enigmaiot@gmartin.net" + }, + "repository": + { + "type": "git", + "url": "https://github.com/gmag11/QuickEspNow.git" + }, + "examples": "examples/*/*.ino", + "license": "GPL-3.0-or-later", + "export": + { + "exclude": + [ + "docs/*", + "include/*", + "lib/*" + ] + }, + "dependencies": + { + "gmag11/QuickDebug": "0.7.0" + } +} + diff --git a/lib/lib_div/QuickESPNow/library.properties b/lib/lib_div/QuickESPNow/library.properties new file mode 100644 index 000000000000..039148c96e35 --- /dev/null +++ b/lib/lib_div/QuickESPNow/library.properties @@ -0,0 +1,9 @@ +name=QuickEspNow +version=0.8.1 +author=German Martin +maintainer=German Martin +sentence=EspNow wrapper for ESP32 +paragraph=EspNow wrapper for ESP32 which adds simplicity and addtitional features to EspNow +category=Communication +url=https://github.com/gmag11/QuickEspNow +architectures=esp32,esp8266 diff --git a/lib/lib_div/QuickESPNow/src/Comms_hal.h b/lib/lib_div/QuickESPNow/src/Comms_hal.h new file mode 100644 index 000000000000..82ed71dd630e --- /dev/null +++ b/lib/lib_div/QuickESPNow/src/Comms_hal.h @@ -0,0 +1,108 @@ +/** + * @file Comms_hal.h + * @author German Martin + * @brief Generic communication system abstraction layer + * + * This is the interface that communication definition should implement to be used as layer 1 on EnigmaIoT + */ +#ifndef _COMMS_HAL_h +#define _COMMS_HAL_h + +#if defined(ARDUINO) && ARDUINO >= 100 +#include "Arduino.h" +#else +#include "WProgram.h" +#endif + +//typedef void (*comms_hal_rcvd_data)(uint8_t* address, uint8_t* data, uint8_t len, signed int rssi, bool broadcast); +typedef std::function comms_hal_rcvd_data; +//typedef void (*comms_hal_sent_data)(uint8_t* address, uint8_t status); +typedef std::function comms_hal_sent_data; + +typedef enum { + COMMS_SEND_OK = 0, /**< Data was enqued for sending successfully */ + COMMS_SEND_PARAM_ERROR = -1, /**< Data was not sent due to parameter call error */ + COMMS_SEND_PAYLOAD_LENGTH_ERROR = -2, /**< Data was not sent due to payload too long */ + COMMS_SEND_QUEUE_FULL_ERROR = -3, /**< Data was not sent due to queue full */ + COMMS_SEND_MSG_ENQUEUE_ERROR = -4, /**< Data was not sent due to message queue push error */ + COMMS_SEND_CONFIRM_ERROR = -5, /**< Data was not sent due to confirmation error (only for synchronous send) */ +} comms_send_error_t; + +/** + * @brief Interface for communication subsystem abstraction layer definition + */ +class Comms_halClass { +protected: + //uint8_t gateway[ESPNOW_ADDR_LEN]; ///< @brief Gateway address + uint8_t channel; ///< @brief Comms channel to be used + + comms_hal_rcvd_data dataRcvd = 0; ///< @brief Pointer to a function to be called on every received message + comms_hal_sent_data sentResult = 0; ///< @brief Pointer to a function to be called to notify last sending status + //peerType_t _ownPeerType; ///< @brief Stores peer type, node or gateway + + /** + * @brief Communication subsistem initialization + * @param peerType Role that peer plays into the system, node or gateway. + */ + virtual void initComms () = 0; + + +public: + Comms_halClass () {} + + /** + * @brief Setup communication environment and establish the connection from node to gateway + * @param gateway Address of gateway. It may be `NULL` in case this is used in the own gateway + * @param channel Establishes a channel for the communication. Its use depends on actual communications subsystem + * @param peerType Role that peer plays into the system, node or gateway. + * @return Returns `true` if the communication subsystem was successfully initialized, `false` otherwise + */ + virtual bool begin (uint8_t channel, uint32_t interface = 0, bool synchronousSend = true) = 0; + + /** + * @brief Terminates communication and closes all connectrions + */ + virtual void stop () = 0; + + /** + * @brief Sends data to the other peer + * @param da Destination address to send the message to + * @param data Data buffer that contain the message to be sent + * @param len Data length in number of bytes + * @return Returns sending status. 0 for success, any other value to indicate an error. + */ + virtual comms_send_error_t send (const uint8_t* da, const uint8_t* data, size_t len) = 0; + + /** + * @brief Attach a callback function to be run on every received message + * @param dataRcvd Pointer to the callback function + */ + virtual void onDataRcvd (comms_hal_rcvd_data dataRcvd) = 0; + + /** + * @brief Attach a callback function to be run after sending a message to receive its status + * @param dataRcvd Pointer to the callback function + */ + virtual void onDataSent (comms_hal_sent_data dataRcvd) = 0; + + /** + * @brief Get address length that a specific communication subsystem uses + * @return Returns number of bytes that is used to represent an address + */ + virtual uint8_t getAddressLength () = 0; + + /** + * @brief Get max message length for a specific communication subsystems + * @return Returns number of bytes of longer supported message + */ + virtual uint8_t getMaxMessageLength () = 0; + + /** + * @brief Enables or disables transmission of queued messages. Used to disable communication during wifi scan + * @param enable `true` to enable transmission, `false` to disable it + */ + virtual void enableTransmit (bool enable) = 0; +}; + +#endif + diff --git a/lib/lib_div/QuickESPNow/src/QuickEspNow.h b/lib/lib_div/QuickESPNow/src/QuickEspNow.h new file mode 100644 index 000000000000..678541a36661 --- /dev/null +++ b/lib/lib_div/QuickESPNow/src/QuickEspNow.h @@ -0,0 +1,12 @@ +#ifndef _QUICK_ESPNOW_h +#define _QUICK_ESPNOW_h + +#if defined ESP32 +#include "QuickEspNow_esp32.h" +#elif defined ESP8266 +#include "QuickEspNow_esp8266.h" +#else +#error "Unsupported platform" +#endif //ESP32 + +#endif //_QUICK_ESPNOW_h \ No newline at end of file diff --git a/lib/lib_div/QuickESPNow/src/QuickEspNow_esp32.cpp b/lib/lib_div/QuickESPNow/src/QuickEspNow_esp32.cpp new file mode 100644 index 000000000000..7ba3093a2896 --- /dev/null +++ b/lib/lib_div/QuickESPNow/src/QuickEspNow_esp32.cpp @@ -0,0 +1,521 @@ +#include "QuickEspNow.h" + +#ifdef ESP32 + +QuickEspNow quickEspNow; + +constexpr auto PEERLIST_TAG = "PEERLIST"; + + +bool QuickEspNow::begin (uint8_t channel, uint32_t wifi_interface, bool synchronousSend) { + + wifi_second_chan_t ch2 = WIFI_SECOND_CHAN_NONE; + this->synchronousSend = synchronousSend; + + DEBUG_DBG (QESPNOW_TAG, "Channel: %d, Interface: %d", channel, wifi_interface); + // Set the wifi interface + switch (wifi_interface) { + case WIFI_IF_STA: + wifi_if = WIFI_IF_STA; + break; + case WIFI_IF_AP: + wifi_if = WIFI_IF_AP; + break; + default: + DEBUG_ERROR (QESPNOW_TAG, "Unknown wifi interface"); + return false; + break; + } + + // check channel + if (channel != CURRENT_WIFI_CHANNEL && (channel < MIN_WIFI_CHANNEL || channel > MAX_WIFI_CHANNEL)) { + DEBUG_ERROR (QESPNOW_TAG, "Invalid wifi channel %d", channel); + return false; + } + + // use current channel + if (channel == CURRENT_WIFI_CHANNEL) { + uint8_t ch; + esp_wifi_get_channel (&ch, &ch2); + channel = ch; + DEBUG_DBG (QESPNOW_TAG, "Current channel: %d : %d", channel, ch2); + followWiFiChannel = true; + } + setChannel (channel, ch2); + + DEBUG_INFO (QESPNOW_TAG, ARDUHAL_LOG_COLOR (ARDUHAL_LOG_COLOR_RED) "Starting ESP-NOW in in channel %u interface %s", channel, wifi_if == WIFI_IF_STA ? "STA" : "AP"); + + this->channel = channel; + initComms (); + return true; +} + +void QuickEspNow::stop () { + DEBUG_INFO (QESPNOW_TAG, "-------------> ESP-NOW STOP"); + vTaskDelete (espnowTxTask); + vTaskDelete (espnowRxTask); + esp_now_unregister_recv_cb (); + esp_now_unregister_send_cb (); + esp_now_deinit (); +} + +bool QuickEspNow::readyToSendData () { + return uxQueueMessagesWaiting (tx_queue) < queueSize; +} + +bool QuickEspNow::setChannel (uint8_t channel, wifi_second_chan_t ch2) { + + if (followWiFiChannel) { + DEBUG_WARN(QESPNOW_TAG, "Cannot set channel while following WiFi channel"); + return false; + } + + esp_err_t err_ok; + if ((err_ok = esp_wifi_set_promiscuous (true))) { + DEBUG_ERROR (QESPNOW_TAG, "Error setting promiscuous mode: %s", esp_err_to_name (err_ok)); + return false; + } + if ((err_ok = esp_wifi_set_channel (channel, ch2))) { // This is needed even in STA mode. If not done and using IDF > 4.0, the ESP-NOW will not work. + DEBUG_DBG (QESPNOW_TAG, "Error setting wifi channel: %d - %s", err_ok, esp_err_to_name (err_ok)); + return false; + } + if ((err_ok = esp_wifi_set_promiscuous (false))) { + DEBUG_ERROR (QESPNOW_TAG, "Error setting promiscuous mode off: %s", esp_err_to_name (err_ok)); + return false; + } + + this->channel = channel; + + return true; +} + +comms_send_error_t QuickEspNow::send (const uint8_t* dstAddress, const uint8_t* payload, size_t payload_len) { + comms_tx_queue_item_t message; + + if (!dstAddress || !payload || !payload_len) { + DEBUG_WARN (QESPNOW_TAG, "Parameters error"); + return COMMS_SEND_PAYLOAD_LENGTH_ERROR; + } + + if (payload_len > ESP_NOW_MAX_DATA_LEN) { + DEBUG_WARN (QESPNOW_TAG, "Length error. %d", payload_len); + return COMMS_SEND_PAYLOAD_LENGTH_ERROR; + } + + if (uxQueueMessagesWaiting (tx_queue) >= queueSize) { + // comms_tx_queue_item_t tempBuffer; + // xQueueReceive (tx_queue, &tempBuffer, 0); +#ifdef MEAS_TPUT + //txDataDropped += tempBuffer.payload_len; +#endif // MEAS_TPUT + //DEBUG_DBG (QESPNOW_TAG, "Message dropped"); + return COMMS_SEND_QUEUE_FULL_ERROR; + } + memcpy (message.dstAddress, dstAddress, ESP_NOW_ETH_ALEN); + message.payload_len = payload_len; + memcpy (message.payload, payload, payload_len); + + if (xQueueSend (tx_queue, &message, pdMS_TO_TICKS (10))) { +#ifdef MEAS_TPUT + txDataSent += message.payload_len; +#endif // MEAS_TPUT + DEBUG_DBG (QESPNOW_TAG, "--------- %d Comms messages queued. Len: %d", uxQueueMessagesWaiting (tx_queue), payload_len); + DEBUG_VERBOSE (QESPNOW_TAG, "--------- Ready to send is %s", readyToSend ? "true" : "false"); + DEBUG_VERBOSE (QESPNOW_TAG, "--------- SyncronousSend is %s", synchronousSend ? "true" : "false"); + if (synchronousSend) { + waitingForConfirmation = true; + DEBUG_INFO (QESPNOW_TAG, "--------- Waiting for send confirmation"); + while (waitingForConfirmation) { + taskYIELD (); + } + DEBUG_INFO (QESPNOW_TAG, "--------- Confirmation is %s", sentStatus == ESP_NOW_SEND_SUCCESS ? "true" : "false"); + return (sentStatus == ESP_NOW_SEND_SUCCESS) ? COMMS_SEND_OK : COMMS_SEND_CONFIRM_ERROR; + } + return COMMS_SEND_OK; + } else { + DEBUG_WARN (QESPNOW_TAG, "Error queuing Comms message to " MACSTR, MAC2STR (dstAddress)); + return COMMS_SEND_MSG_ENQUEUE_ERROR; + } +} + +void QuickEspNow::onDataRcvd (comms_hal_rcvd_data dataRcvd) { + this->dataRcvd = dataRcvd; +} + +#ifdef MEAS_TPUT +void QuickEspNow::calculateDataTP () { + time_t measTime = (millis () - lastDataTPMeas); + lastDataTPMeas = millis (); + + if (txDataSent > 0) { + txDataTP = txDataSent * 1000 / measTime; + //DEBUG_WARN("Meas time: %d, Data sent: %d, Data TP: %f", measTime, txDataSent, txDataTP); + txDroppedDataRatio = (float)txDataDropped / (float)txDataSent; + //DEBUG_WARN("Data dropped: %d, Drop ratio: %f", txDataDropped, txDroppedDataRatio); + txDataSent = 0; + } else { + txDataTP = 0; + txDroppedDataRatio = 0; + } + if (rxDataReceived > 0) { + rxDataTP = rxDataReceived * 1000 / measTime; + //DEBUG_WARN("Meas time: %d, Data received: %d, Data TP: %f", measTime, rxDataReceived, rxDataTP); + rxDataReceived = 0; + } else { + rxDataTP = 0; + } + txDataDropped = 0; +} + +void QuickEspNow::tp_timer_cb (void* param) { + quickEspNow.calculateDataTP (); + DEBUG_WARN (QESPNOW_TAG, "TxData TP: %.3f kbps, Drop Ratio: %.2f %%, RxDataTP: %.3f kbps", + quickEspNow.txDataTP * 8 / 1000, + quickEspNow.txDroppedDataRatio * 100, + quickEspNow.rxDataTP * 8 / 1000); +} + +#endif // MEAS_TPUT + +void QuickEspNow::onDataSent (comms_hal_sent_data sentResult) { + this->sentResult = sentResult; +} + +int32_t QuickEspNow::sendEspNowMessage (comms_tx_queue_item_t* message) { + int32_t error; + + if (!message) { + DEBUG_WARN (QESPNOW_TAG, "Message is null"); + return -1; + } + if (!(message->payload_len) || (message->payload_len > ESP_NOW_MAX_DATA_LEN)) { + DEBUG_WARN (QESPNOW_TAG, "Message length error"); + return -1; + } + + DEBUG_VERBOSE (QESPNOW_TAG, "ESP-NOW message to " MACSTR, MAC2STR (message->dstAddress)); + + + addPeer (message->dstAddress); + DEBUG_DBG (QESPNOW_TAG, "Peer added " MACSTR, MAC2STR (message->dstAddress)); + readyToSend = false; + DEBUG_VERBOSE (QESPNOW_TAG, "-------------- Ready to send: false"); + + error = esp_now_send (message->dstAddress, message->payload, message->payload_len); + DEBUG_DBG (QESPNOW_TAG, "esp now send result = %s", esp_err_to_name (error)); + if (error != ESP_OK) { + DEBUG_WARN (QESPNOW_TAG, "Error sending message: %s", esp_err_to_name (error)); + } + // if (error == ESP_OK) { + // txDataSent += message->payload_len; + // } + if (error == ESP_ERR_ESPNOW_NO_MEM) { + delay (2); + } + + return error; +} + +void QuickEspNow::espnowTxHandle () { + if (readyToSend) { + //DEBUG_WARN ("Process queue: Elements: %d", tx_queue.size ()); + comms_tx_queue_item_t message; + while (xQueueReceive (tx_queue, &message, pdMS_TO_TICKS (1000))) { + DEBUG_DBG (QESPNOW_TAG, "Comms message got from queue. %d left", uxQueueMessagesWaiting (tx_queue)); + while (!readyToSend && !synchronousSend) { + delay (0); + } + if (!sendEspNowMessage (&message)) { + DEBUG_DBG (QESPNOW_TAG, "Message to " MACSTR " sent. Len: %u", MAC2STR (message.dstAddress), message.payload_len); + } else { + DEBUG_WARN (QESPNOW_TAG, "Error sending message to " MACSTR ". Len: %u", MAC2STR (message.dstAddress), message.payload_len); + } + //message.payload_len = 0; + DEBUG_DBG (QESPNOW_TAG, "Comms message pop. Queue size %d", uxQueueMessagesWaiting (tx_queue)); + } + + } else { + DEBUG_DBG (QESPNOW_TAG, "Not ready to send"); + } +} + +void QuickEspNow::enableTransmit (bool enable) { + DEBUG_DBG (QESPNOW_TAG, "Send esp-now task %s", enable ? "enabled" : "disabled"); + if (enable) { + if (espnowTxTask_cb) { + vTaskResume (espnowTxTask); + vTaskResume (espnowRxTask); + } + } else { + if (espnowTxTask_cb) { + vTaskSuspend (espnowTxTask); + vTaskSuspend (espnowRxTask); + } + } +} + +bool QuickEspNow::addPeer (const uint8_t* peer_addr) { + esp_now_peer_info_t peer; + esp_err_t error = ESP_OK; + + if (peer_list.get_peer_number () >= ESP_NOW_MAX_TOTAL_PEER_NUM) { + DEBUG_VERBOSE (QESPNOW_TAG, "Peer list full. Deleting older"); + if (uint8_t* deleted_mac = peer_list.delete_peer ()) { + esp_now_del_peer (deleted_mac); + } else { + DEBUG_ERROR (QESPNOW_TAG, "Error deleting peer"); + return false; + } + } + + if (peer_list.peer_exists (peer_addr)) { + DEBUG_VERBOSE (QESPNOW_TAG, "Peer already exists"); + ESP_ERROR_CHECK (esp_now_get_peer (peer_addr, &peer)); + + uint8_t currentChannel = peer.channel; + DEBUG_DBG (QESPNOW_TAG, "Peer " MACSTR " is using channel %d", MAC2STR (peer_addr), currentChannel); + if (currentChannel != this->channel) { + DEBUG_DBG (QESPNOW_TAG, "Peer channel has to change from %d to %d", currentChannel, this->channel); + ESP_ERROR_CHECK_WITHOUT_ABORT (esp_now_get_peer (peer_addr, &peer)); + peer.channel = this->channel; + ESP_ERROR_CHECK_WITHOUT_ABORT (esp_now_mod_peer (&peer)); + DEBUG_ERROR (QESPNOW_TAG, "Peer channel changed to %d", this->channel); + } + return true; + } + + memcpy (peer.peer_addr, peer_addr, ESP_NOW_ETH_ALEN); + uint8_t ch; + wifi_second_chan_t secondCh; + esp_wifi_get_channel (&ch, &secondCh); + peer.channel = ch; + peer.ifidx = wifi_if; + peer.encrypt = false; + error = esp_now_add_peer (&peer); + if (!error) { + DEBUG_DBG (QESPNOW_TAG, "Peer added"); + peer_list.add_peer (peer_addr); + } else { + DEBUG_ERROR (QESPNOW_TAG, "Error adding peer: %s", esp_err_to_name (error)); + return false; + } + DEBUG_DBG (QESPNOW_TAG, "Peer " MACSTR " added on channel %u. Result 0x%X %s", MAC2STR (peer_addr), ch, error, esp_err_to_name (error)); + return error == ESP_OK; +} + +void QuickEspNow::initComms () { + if (esp_now_init ()) { + DEBUG_ERROR (QESPNOW_TAG, "Failed to init ESP-NOW"); + ESP.restart (); + delay (1); + } + + esp_now_register_recv_cb (rx_cb); + esp_now_register_send_cb (reinterpret_cast(tx_cb)); + + int txQueueSize = queueSize; + if (synchronousSend) { + txQueueSize = 1; + } + + tx_queue = xQueueCreate (txQueueSize, sizeof (comms_tx_queue_item_t)); + xTaskCreateUniversal (espnowTxTask_cb, "espnow_loop", 8 * 1024, NULL, 1, &espnowTxTask, CONFIG_ARDUINO_RUNNING_CORE); + + rx_queue = xQueueCreate (queueSize, sizeof (comms_rx_queue_item_t)); + xTaskCreateUniversal (espnowRxTask_cb, "receive_handle", 4 * 1024, NULL, 1, &espnowRxTask, CONFIG_ARDUINO_RUNNING_CORE); + +#ifdef MEAS_TPUT + dataTPTimer = xTimerCreate ("espnow_tp_timer", pdMS_TO_TICKS (MEAS_TP_EVERY_MS), pdTRUE, NULL, tp_timer_cb); + xTimerStart (dataTPTimer, 0); +#endif // MEAS_TPUT +} + +void QuickEspNow::espnowTxTask_cb (void* param) { + for (;;) { + quickEspNow.espnowTxHandle (); + } + +} + +void QuickEspNow::espnowRxHandle () { + comms_rx_queue_item_t rxMessage; + + if (xQueueReceive (rx_queue, &rxMessage, portMAX_DELAY)) { + DEBUG_DBG (QESPNOW_TAG, "Comms message got from queue. %d left", uxQueueMessagesWaiting (rx_queue)); + DEBUG_VERBOSE (QESPNOW_TAG, "Received message from " MACSTR " Len: %u", MAC2STR (rxMessage.srcAddress), rxMessage.payload_len); + DEBUG_VERBOSE (QESPNOW_TAG, "Message: %.*s", rxMessage.payload_len, rxMessage.payload); + + if (quickEspNow.dataRcvd) { + bool broadcast = !memcmp (rxMessage.dstAddress, ESPNOW_BROADCAST_ADDRESS, ESP_NOW_ETH_ALEN); + quickEspNow.dataRcvd (rxMessage.srcAddress, rxMessage.payload, rxMessage.payload_len, rxMessage.rssi, broadcast); // rssi should be in dBm but it has added almost 100 dB. Do not know why + } + } else { + DEBUG_DBG (QESPNOW_TAG, "No message in queue"); + } + +} + +void QuickEspNow::espnowRxTask_cb (void* param) { + for (;;) { + quickEspNow.espnowRxHandle (); + } +} + +void QuickEspNow::rx_cb(const esp_now_recv_info_t* esp_now_info, const uint8_t* data, int len) { + espnow_frame_format_t* espnow_data = (espnow_frame_format_t*)(data - sizeof(espnow_frame_format_t)); + wifi_promiscuous_pkt_t* promiscuous_pkt = (wifi_promiscuous_pkt_t*)(data - sizeof(wifi_pkt_rx_ctrl_t) - sizeof(espnow_frame_format_t)); + wifi_pkt_rx_ctrl_t* rx_ctrl = &promiscuous_pkt->rx_ctrl; + const uint8_t* mac_addr = esp_now_info->src_addr; + comms_rx_queue_item_t message; + + DEBUG_DBG(QESPNOW_TAG, "Received message with RSSI %d from " MACSTR " Len: %u", rx_ctrl->rssi, MAC2STR(esp_now_info->src_addr), len); + + memcpy(message.srcAddress, mac_addr, ESP_NOW_ETH_ALEN); + memcpy(message.payload, data, len); + message.payload_len = len; + message.rssi = rx_ctrl->rssi; + memcpy(message.dstAddress, espnow_data->destination_address, ESP_NOW_ETH_ALEN); + + if (uxQueueMessagesWaiting(quickEspNow.rx_queue) >= quickEspNow.queueSize) { + comms_rx_queue_item_t tempBuffer; + xQueueReceive(quickEspNow.rx_queue, &tempBuffer, 0); + DEBUG_DBG(QESPNOW_TAG, "Rx Message dropped"); + } +#ifdef MEAS_TPUT + quickEspNow.rxDataReceived += len; +#endif // MEAS_TPUT + + if (!xQueueSend(quickEspNow.rx_queue, &message, pdMS_TO_TICKS(100))) { + DEBUG_WARN(QESPNOW_TAG, "Error sending message to queue"); + } +} + +void QuickEspNow::tx_cb (uint8_t* mac_addr, uint8_t status) { + quickEspNow.readyToSend = true; + quickEspNow.sentStatus = status; + quickEspNow.waitingForConfirmation = false; + DEBUG_DBG (QESPNOW_TAG, "-------------- Ready to send: true. Status: %d", status); + if (quickEspNow.sentResult) { + quickEspNow.sentResult (mac_addr, status); + } +} + +uint8_t PeerListClass::get_peer_number () { + return peer_list.peer_number; +} + +bool PeerListClass::peer_exists (const uint8_t* mac) { + for (uint8_t i = 0; i < ESP_NOW_MAX_TOTAL_PEER_NUM; i++) { + if (memcmp (peer_list.peer[i].mac, mac, ESP_NOW_ETH_ALEN) == 0) { + if (peer_list.peer[i].active) { + peer_list.peer[i].last_msg = millis (); + DEBUG_VERBOSE (PEERLIST_TAG, "Peer " MACSTR " found. Updated last_msg", MAC2STR (mac)); + return true; + } + } + } + return false; +} + +peer_t* PeerListClass::get_peer (const uint8_t* mac) { + for (uint8_t i = 0; i < ESP_NOW_MAX_TOTAL_PEER_NUM; i++) { + if (memcmp (peer_list.peer[i].mac, mac, ESP_NOW_ETH_ALEN) == 0) { + if (peer_list.peer[i].active) { + DEBUG_VERBOSE (PEERLIST_TAG, "Peer " MACSTR " found", MAC2STR (mac)); + return &(peer_list.peer[i]); + } + } + } + return NULL; +} + +bool PeerListClass::update_peer_use (const uint8_t* mac) { + peer_t* peer = get_peer (mac); + if (peer) { + peer->last_msg = millis (); + return true; + } + return false; +} + +bool PeerListClass::add_peer (const uint8_t* mac) { + if (int i = peer_exists (mac)) { + DEBUG_VERBOSE (PEERLIST_TAG, "Peer " MACSTR " already exists", MAC2STR (mac)); + return false; + } + if (peer_list.peer_number >= ESP_NOW_MAX_TOTAL_PEER_NUM) { + //DEBUG_VERBOSE (PEERLIST_TAG, "Peer list full. Deleting older"); +#ifndef UNIT_TEST + DEBUG_ERROR (PEERLIST_TAG, "Should never happen"); +#endif + return false; + // delete_peer (); // Delete should happen in higher level + } + + for (uint8_t i = 0; i < ESP_NOW_MAX_TOTAL_PEER_NUM; i++) { + if (!peer_list.peer[i].active) { + memcpy (peer_list.peer[i].mac, mac, ESP_NOW_ETH_ALEN); + peer_list.peer[i].active = true; + peer_list.peer[i].last_msg = millis (); + peer_list.peer_number++; + DEBUG_VERBOSE (PEERLIST_TAG, "Peer " MACSTR " added. Total peers = %d", MAC2STR (mac), peer_list.peer_number); + return true; + } + } + + return false; +} + +bool PeerListClass::delete_peer (const uint8_t* mac) { + peer_t* peer = get_peer (mac); + if (peer) { + peer->active = false; + peer_list.peer_number--; + DEBUG_VERBOSE (PEERLIST_TAG, "Peer " MACSTR " deleted. Total peers = %d", MAC2STR (mac), peer_list.peer_number); + return true; + } + return false; +} + +// Delete peer with older message +uint8_t* PeerListClass::delete_peer () { + uint32_t oldest_msg = 0; + int oldest_index = -1; + uint8_t* mac = NULL; + for (int i = 0; i < ESP_NOW_MAX_TOTAL_PEER_NUM; i++) { + if (peer_list.peer[i].active) { + if (peer_list.peer[i].last_msg < oldest_msg || oldest_msg == 0) { + oldest_msg = peer_list.peer[i].last_msg; + oldest_index = i; + DEBUG_VERBOSE (PEERLIST_TAG, "Peer " MACSTR " is %d ms old. Deleting", MAC2STR (peer_list.peer[i].mac), oldest_msg); + } + } + } + if (oldest_index != -1) { + peer_list.peer[oldest_index].active = false; + peer_list.peer_number--; + mac = peer_list.peer[oldest_index].mac; + DEBUG_VERBOSE (PEERLIST_TAG, "Peer " MACSTR " deleted. Last message %d ms ago. Total peers = %d", MAC2STR (mac), millis () - peer_list.peer[oldest_index].last_msg, peer_list.peer_number); + } + return mac; +} + +bool QuickEspNow::setWiFiBandwidth (wifi_interface_t iface, wifi_bandwidth_t bw) { + esp_err_t err_ok; + if ((err_ok = esp_wifi_set_bandwidth (iface, bw))) { + DEBUG_ERROR (QESPNOW_TAG, "Error setting wifi bandwidth: %s", esp_err_to_name (err_ok)); + } + return !err_ok; +} + +#ifdef UNIT_TEST +void PeerListClass::dump_peer_list () { + Serial.printf ("Number of peers %d\n", peer_list.peer_number); + for (int i = 0; i < ESP_NOW_MAX_TOTAL_PEER_NUM; i++) { + if (peer_list.peer[i].active) { + Serial.printf ("Peer " MACSTR " is %d ms old\n", MAC2STR (peer_list.peer[i].mac), millis () - peer_list.peer[i].last_msg); + } + } +} +#endif // UNIT_TEST +#endif // ESP32 diff --git a/lib/lib_div/QuickESPNow/src/QuickEspNow_esp32.h b/lib/lib_div/QuickESPNow/src/QuickEspNow_esp32.h new file mode 100644 index 000000000000..b9d56e02c825 --- /dev/null +++ b/lib/lib_div/QuickESPNow/src/QuickEspNow_esp32.h @@ -0,0 +1,169 @@ +#ifndef _QUICK_ESPNOW_ESP32_h +#define _QUICK_ESPNOW_ESP32_h +#ifdef ESP32 + +#include "Arduino.h" +#include "Comms_hal.h" + +#include +#include + +#include +#include +#include +#include + +// Disable debug dependency if debug level is 0 +#if CORE_DEBUG_LEVEL > 0 +#include +constexpr auto QESPNOW_TAG = "QESPNOW"; +#else // CORE_DEBUG_LEVEL +#define DEBUG_ERROR(...) +#define DEBUG_INFO(...) +#define DEBUG_VERBOSE(...) +#define DEBUG_WARN(...) +#define DEBUG_DBG(...) +#endif + +//#define MEAS_TPUT + +static uint8_t ESPNOW_BROADCAST_ADDRESS[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; +static const uint8_t MIN_WIFI_CHANNEL = 0; +static const uint8_t MAX_WIFI_CHANNEL = 14; +static const uint8_t CURRENT_WIFI_CHANNEL = 255; +static const size_t ESPNOW_MAX_MESSAGE_LENGTH = 250; ///< @brief Maximum message length +static const uint8_t ESPNOW_ADDR_LEN = 6; ///< @brief Address length +static const uint8_t ESPNOW_QUEUE_SIZE = 3; ///< @brief Queue size + +#ifdef MEAS_TPUT +static const time_t MEAS_TP_EVERY_MS = 10000; ///< @brief Measurement time period +#endif // MEAS_TPUT + +typedef struct { + uint16_t frame_head; + uint16_t duration; + uint8_t destination_address[6]; + uint8_t source_address[6]; + uint8_t broadcast_address[6]; + uint16_t sequence_control; + + uint8_t category_code; + uint8_t organization_identifier[3]; // 0x18fe34 + uint8_t random_values[4]; + struct { + uint8_t element_id; // 0xdd + uint8_t lenght; // + uint8_t organization_identifier[3]; // 0x18fe34 + uint8_t type; // 4 + uint8_t version; + uint8_t body[0]; + } vendor_specific_content; +} __attribute__ ((packed)) espnow_frame_format_t; + +typedef struct { + uint8_t dstAddress[ESPNOW_ADDR_LEN]; /**< Message topic*/ + uint8_t payload[ESPNOW_MAX_MESSAGE_LENGTH]; /**< Message payload*/ + size_t payload_len; /**< Payload length*/ +} comms_tx_queue_item_t; + +typedef struct { + uint8_t srcAddress[ESPNOW_ADDR_LEN]; /**< Source Address */ + uint8_t dstAddress[ESPNOW_ADDR_LEN]; /**< Destination Address */ + uint8_t payload[ESPNOW_MAX_MESSAGE_LENGTH]; /**< Message payload */ + size_t payload_len; /**< Payload length */ + int8_t rssi; /**< RSSI */ +} comms_rx_queue_item_t; + +typedef struct { + uint8_t mac[ESP_NOW_ETH_ALEN]; + time_t last_msg; + bool active; +} peer_t; +typedef struct { + uint8_t peer_number; + peer_t peer[ESP_NOW_MAX_TOTAL_PEER_NUM]; +} peer_list_t; + +class PeerListClass { +protected: + peer_list_t peer_list; + +public: + bool peer_exists (const uint8_t* mac); + peer_t* get_peer (const uint8_t* mac); + bool update_peer_use (const uint8_t* mac); + bool delete_peer (const uint8_t* mac); + uint8_t* delete_peer (); + bool add_peer (const uint8_t* mac); + uint8_t get_peer_number (); +#ifdef UNIT_TEST + void dump_peer_list (); +#endif +}; + +class QuickEspNow : public Comms_halClass { +public: + bool begin (uint8_t channel = CURRENT_WIFI_CHANNEL, uint32_t interface = 0, bool synchronousSend = true) override; + void stop () override; + comms_send_error_t send (const uint8_t* dstAddress, const uint8_t* payload, size_t payload_len) override; + comms_send_error_t sendBcast (const uint8_t* payload, size_t payload_len) { + return send (ESPNOW_BROADCAST_ADDRESS, payload, payload_len); + } + void onDataRcvd (comms_hal_rcvd_data dataRcvd) override; + void onDataSent (comms_hal_sent_data sentResult) override; + uint8_t getAddressLength () override { return ESPNOW_ADDR_LEN; } + uint8_t getMaxMessageLength () override { return ESPNOW_MAX_MESSAGE_LENGTH; } + void enableTransmit (bool enable) override; + bool setChannel (uint8_t channel, wifi_second_chan_t ch2 = WIFI_SECOND_CHAN_NONE); + bool setWiFiBandwidth (wifi_interface_t iface = WIFI_IF_AP, wifi_bandwidth_t bw = WIFI_BW_HT20); + bool readyToSendData (); + +protected: + wifi_interface_t wifi_if; + PeerListClass peer_list; + TaskHandle_t espnowTxTask; + TaskHandle_t espnowRxTask; + +#ifdef MEAS_TPUT + unsigned long txDataSent = 0; + unsigned long rxDataReceived = 0; + unsigned long txDataDropped = 0; + time_t lastDataTPMeas = 0; + TimerHandle_t dataTPTimer; + float txDataTP = 0; + float rxDataTP = 0; + float txDroppedDataRatio = 0; + + static void tp_timer_cb (void* param); + void calculateDataTP (); +#endif // MEAS_TPUT + + bool readyToSend = true; + bool waitingForConfirmation = false; + bool synchronousSend = false; + uint8_t sentStatus; + int queueSize = ESPNOW_QUEUE_SIZE; + + QueueHandle_t tx_queue; + QueueHandle_t rx_queue; + //SemaphoreHandle_t espnow_send_mutex; + //uint8_t channel; + bool followWiFiChannel = false; + + void initComms (); + bool addPeer (const uint8_t* peer_addr); + static void espnowTxTask_cb (void* param); + int32_t sendEspNowMessage (comms_tx_queue_item_t* message); + void espnowTxHandle (); + + static void espnowRxTask_cb (void* param); + void espnowRxHandle (); + + static void ICACHE_FLASH_ATTR rx_cb(const esp_now_recv_info_t* esp_now_info, const uint8_t* data, int len); + static void ICACHE_FLASH_ATTR tx_cb (uint8_t* mac_addr, uint8_t status); +}; + +extern QuickEspNow quickEspNow; + +#endif // ESP32 +#endif // _QUICK_ESPNOW_ESP32_h diff --git a/lib/lib_div/QuickESPNow/src/QuickEspNow_esp8266.cpp b/lib/lib_div/QuickESPNow/src/QuickEspNow_esp8266.cpp new file mode 100644 index 000000000000..b1309849e95f --- /dev/null +++ b/lib/lib_div/QuickESPNow/src/QuickEspNow_esp8266.cpp @@ -0,0 +1,366 @@ +#include "QuickEspNow.h" + +#ifdef ESP8266 + +typedef struct { + signed rssi : 8; + unsigned rate : 4; + unsigned is_group : 1; + unsigned : 1; + unsigned sig_mode : 2; + unsigned legacy_length : 12; + unsigned damatch0 : 1; + unsigned damatch1 : 1; + unsigned bssidmatch0 : 1; + unsigned bssidmatch1 : 1; + unsigned MCS : 7; + unsigned CWB : 1; + unsigned HT_length : 16; + unsigned Smoothing : 1; + unsigned Not_Sounding : 1; + unsigned : 1; + unsigned Aggregation : 1; + unsigned STBC : 2; + unsigned FEC_CODING : 1; + unsigned SGI : 1; + unsigned rxend_state : 8; + unsigned ampdu_cnt : 8; + unsigned channel : 4; + unsigned : 12; +} wifi_pkt_rx_ctrl_t; + +typedef struct { + wifi_pkt_rx_ctrl_t rx_ctrl; + uint8_t payload[0]; /* ieee80211 packet buff */ +} wifi_promiscuous_pkt_t; + +QuickEspNow quickEspNow; + +bool QuickEspNow::begin (uint8_t channel, uint32_t wifi_interface, bool synchronousSend) { + + this->synchronousSend = synchronousSend; + + DEBUG_DBG (QESPNOW_TAG, "Channel: %d, Interface: %d", channel, wifi_interface); + // Set the wifi interface + switch (wifi_interface) { + case WIFI_IF_STA: + wifi_if = WIFI_IF_STA; + break; + case WIFI_IF_AP: + wifi_if = WIFI_IF_AP; + break; + default: + DEBUG_ERROR (QESPNOW_TAG, "Unknown wifi interface"); + return false; + break; + } + + // check channel + if (channel != CURRENT_WIFI_CHANNEL && (channel < MIN_WIFI_CHANNEL || channel > MAX_WIFI_CHANNEL)) { + DEBUG_ERROR (QESPNOW_TAG, "Invalid wifi channel %d", channel); + return false; + } + + // use current channel + if (channel == CURRENT_WIFI_CHANNEL) { + uint8_t ch; + ch = WiFi.channel (); + DEBUG_DBG (QESPNOW_TAG, "Current channel: %d", ch); + channel = ch; + followWiFiChannel = true; + } else { + setChannel (channel); + } + DEBUG_INFO (QESPNOW_TAG, "Starting ESP-NOW in in channel %u interface %s", channel, wifi_if == WIFI_IF_STA ? "STA" : "AP"); + + this->channel = channel; + initComms (); + // addPeer (ESPNOW_BROADCAST_ADDRESS); // Not needed ? + return true; +} + +void QuickEspNow::stop () { + DEBUG_INFO (QESPNOW_TAG, "-------------> ESP-NOW STOP"); + os_timer_disarm (&espnowTxTask); + os_timer_disarm (&espnowRxTask); + esp_now_unregister_recv_cb (); + esp_now_unregister_send_cb (); + esp_now_deinit (); +} + +bool QuickEspNow::readyToSendData () { + return tx_queue.size () < queueSize; +} + +bool QuickEspNow::setChannel (uint8_t channel) { + + if (followWiFiChannel) { + DEBUG_WARN (QESPNOW_TAG, "Cannot set channel while following WiFi channel"); + return false; + } + + if (!wifi_set_channel (channel)) { + DEBUG_ERROR (QESPNOW_TAG, "Error setting wifi channel: %u", channel); + return false; + } + + this->channel = channel; + + return true; +} + +comms_send_error_t QuickEspNow::send (const uint8_t* dstAddress, const uint8_t* payload, size_t payload_len) { + comms_tx_queue_item_t message; + + if (!dstAddress || !payload || !payload_len) { + DEBUG_WARN (QESPNOW_TAG, "Parameters error"); + return COMMS_SEND_PARAM_ERROR; + } + + if (payload_len > ESP_NOW_MAX_DATA_LEN) { + DEBUG_WARN (QESPNOW_TAG, "Length error. %d", payload_len); + return COMMS_SEND_PAYLOAD_LENGTH_ERROR; + } + + if (tx_queue.size () >= ESPNOW_QUEUE_SIZE) { +#ifdef MEAS_TPUT + //comms_tx_queue_item_t* tempBuffer; + //tempBuffer = tx_queue.front (); + //txDataDropped += tempBuffer->payload_len; +#endif // MEAS_TPUT + // tx_queue.pop (); + // DEBUG_DBG (QESPNOW_TAG, "Message dropped"); + return COMMS_SEND_QUEUE_FULL_ERROR; + } + + memcpy (message.dstAddress, dstAddress, ESP_NOW_ETH_ALEN); + message.payload_len = payload_len; + memcpy (message.payload, payload, payload_len); + + if (tx_queue.push (&message)) { +#ifdef MEAS_TPUT + txDataSent += message.payload_len; +#endif // MEAS_TPUT + DEBUG_DBG (QESPNOW_TAG, "--------- %d Comms messages queued. Len: %d", tx_queue.size (), payload_len); + DEBUG_VERBOSE (QESPNOW_TAG, "--------- Ready to send is %s", readyToSend ? "true" : "false"); + if (synchronousSend) { + waitingForConfirmation = true; + DEBUG_INFO (QESPNOW_TAG, "--------- Waiting for send confirmation"); + while (waitingForConfirmation) { +// esp_yield (); + delay(0); + } + DEBUG_INFO (QESPNOW_TAG, "--------- Confirmation is %s", sentStatus == ESP_NOW_SEND_SUCCESS ? "true" : "false"); + return (sentStatus == ESP_NOW_SEND_SUCCESS) ? COMMS_SEND_OK : COMMS_SEND_CONFIRM_ERROR; + } + return COMMS_SEND_OK; + } else { + DEBUG_WARN (QESPNOW_TAG, "Error queuing Comms message to " MACSTR, MAC2STR (dstAddress)); + return COMMS_SEND_MSG_ENQUEUE_ERROR; + } +} + +void QuickEspNow::onDataRcvd (comms_hal_rcvd_data dataRcvd) { + this->dataRcvd = dataRcvd; +} + +#ifdef MEAS_TPUT +void QuickEspNow::calculateDataTP () { + time_t measTime = (millis () - lastDataTPMeas); + lastDataTPMeas = millis (); + + if (txDataSent > 0) { + txDataTP = txDataSent * 1000 / measTime; + //DEBUG_WARN(QESPNOW_TAG, "Meas time: %d, Data sent: %d, Data TP: %f", measTime, txDataSent, txDataTP); + txDroppedDataRatio = (float)txDataDropped / (float)txDataSent; + //DEBUG_WARN(QESPNOW_TAG, "Data dropped: %d, Drop ratio: %f", txDataDropped, txDroppedDataRatio); + txDataSent = 0; + } else { + txDataTP = 0; + txDroppedDataRatio = 0; + } + if (rxDataReceived > 0) { + rxDataTP = rxDataReceived * 1000 / measTime; + //DEBUG_WARN(QESPNOW_TAG, "Meas time: %d, Data received: %d, Data TP: %f", measTime, rxDataReceived, rxDataTP); + rxDataReceived = 0; + } else { + rxDataTP = 0; + } + txDataDropped = 0; +} + +void QuickEspNow::tp_timer_cb (void* param) { + quickEspNow.calculateDataTP (); + DEBUG_WARN (QESPNOW_TAG, "TxData TP: %.3f kbps, Drop Ratio: %.2f %%, RxDataTP: %.3f kbps", + quickEspNow.txDataTP * 8 / 1000, + quickEspNow.txDroppedDataRatio * 100, + quickEspNow.rxDataTP * 8 / 1000); +} +#endif // MEAS_TPUT + +void QuickEspNow::onDataSent (comms_hal_sent_data sentResult) { + this->sentResult = sentResult; +} + +int32_t QuickEspNow::sendEspNowMessage (comms_tx_queue_item_t* message) { + int32_t error; + + if (!message) { + DEBUG_WARN (QESPNOW_TAG, "Message is null"); + return -1; + } + if (!(message->payload_len) || (message->payload_len > ESP_NOW_MAX_DATA_LEN)) { + DEBUG_WARN (QESPNOW_TAG, "Message length error"); + return -1; + } + + DEBUG_VERBOSE (QESPNOW_TAG, "ESP-NOW message to " MACSTR, MAC2STR (message->dstAddress)); + + readyToSend = false; + DEBUG_VERBOSE (QESPNOW_TAG, "-------------- Ready to send: false"); + + error = esp_now_send (message->dstAddress, message->payload, message->payload_len); + DEBUG_DBG (QESPNOW_TAG, "esp now send result = %d", error); + + return error; +} + +void QuickEspNow::espnowTxHandle () { + if (readyToSend) { + //DEBUG_WARN ("Process queue: Elements: %d", tx_queue.size ()); + comms_tx_queue_item_t* message; + while (!tx_queue.empty ()) { + if (!readyToSend) return; + message = tx_queue.front (); + DEBUG_DBG (QESPNOW_TAG, "Comms message got from queue. %d left", tx_queue.size ()); + DEBUG_VERBOSE (QESPNOW_TAG, "Ready to send is %s", readyToSend ? "true" : "false"); + DEBUG_VERBOSE (QESPNOW_TAG, "synchrnousSend is %s", synchronousSend ? "true" : "false"); + if (!sendEspNowMessage (message)) { + DEBUG_DBG (QESPNOW_TAG, "Message to " MACSTR " sent. Len: %u", MAC2STR (message->dstAddress), message->payload_len); + } else { + DEBUG_WARN (QESPNOW_TAG, "Error sending message to " MACSTR ". Len: %u", MAC2STR (message->dstAddress), message->payload_len); + } + message->payload_len = 0; + tx_queue.pop (); + DEBUG_DBG (QESPNOW_TAG, "Comms message pop. Queue size %d", tx_queue.size ()); + } + + } else { + DEBUG_DBG (QESPNOW_TAG, "Not ready to send"); + } +} + +void QuickEspNow::enableTransmit (bool enable) { + DEBUG_DBG (QESPNOW_TAG, "Send esp-now task %s", enable ? "enabled" : "disabled"); + if (enable) { + os_timer_arm (&espnowTxTask, TASK_PERIOD, true); + os_timer_arm (&espnowRxTask, TASK_PERIOD, true); + } else { + os_timer_disarm (&espnowTxTask); + os_timer_disarm (&espnowRxTask); + } +} + +void QuickEspNow::initComms () { + if (esp_now_init ()) { + DEBUG_ERROR (QESPNOW_TAG, "Failed to init ESP-NOW"); + ESP.restart (); + delay (1); + } + + if (wifi_if == WIFI_IF_STA) { + esp_now_set_self_role (ESP_NOW_ROLE_SLAVE); + } else { + esp_now_set_self_role (ESP_NOW_ROLE_CONTROLLER); + } + + esp_now_register_recv_cb (reinterpret_cast(rx_cb)); + esp_now_register_send_cb (reinterpret_cast(tx_cb)); + + os_timer_setfn (&espnowTxTask, espnowTxTask_cb, NULL); + os_timer_arm (&espnowTxTask, TASK_PERIOD, true); + + os_timer_setfn (&espnowRxTask, espnowRxTask_cb, NULL); + os_timer_arm (&espnowRxTask, TASK_PERIOD, true); + +#ifdef MEAS_TPUT + os_timer_setfn (&dataTPTimer, tp_timer_cb, NULL); + os_timer_arm (&dataTPTimer, MEAS_TP_EVERY_MS, true); +#endif // MEAS_TPUT + +} + +void QuickEspNow::espnowTxTask_cb (void* param) { + quickEspNow.espnowTxHandle (); +} + +void QuickEspNow::rx_cb (uint8_t* mac_addr, uint8_t* data, uint8_t len) { + espnow_frame_format_t* espnow_data = (espnow_frame_format_t*)(data - sizeof (espnow_frame_format_t)); + wifi_promiscuous_pkt_t* promiscuous_pkt = (wifi_promiscuous_pkt_t*)(data - sizeof (wifi_pkt_rx_ctrl_t) - sizeof (espnow_frame_format_t)); + wifi_pkt_rx_ctrl_t* rx_ctrl = &promiscuous_pkt->rx_ctrl; + + comms_rx_queue_item_t message; + + DEBUG_DBG (QESPNOW_TAG, "Received message with RSSI %d from " MACSTR " Len: %u", rx_ctrl->rssi, MAC2STR (mac_addr), len); + + memcpy (message.srcAddress, mac_addr, ESP_NOW_ETH_ALEN); + memcpy (message.payload, data, len); + message.payload_len = len; + message.rssi = rx_ctrl->rssi - 100; + memcpy (message.dstAddress, espnow_data->destination_address, ESP_NOW_ETH_ALEN); + + if (quickEspNow.rx_queue.size () >= ESPNOW_QUEUE_SIZE) { + quickEspNow.tx_queue.pop (); + DEBUG_DBG (QESPNOW_TAG, "Rx Message dropped"); + } + +#ifdef MEAS_TPUT + quickEspNow.rxDataReceived += len; +#endif // MEAS_TPUT + + if (quickEspNow.rx_queue.push (&message)) { + DEBUG_DBG (QESPNOW_TAG, "Message pushed to queue"); + } else { + DEBUG_WARN (QESPNOW_TAG, "Error queuing message"); + } +} + +void QuickEspNow::espnowRxTask_cb (void* param) { + quickEspNow.espnowRxHandle (); +} + +void QuickEspNow::espnowRxHandle () { + comms_rx_queue_item_t *rxMessage; + + if (!rx_queue.empty ()) { + rxMessage = rx_queue.front (); + DEBUG_DBG (QESPNOW_TAG, "Comms message got from queue. %d left", rx_queue.size ()); + DEBUG_VERBOSE (QESPNOW_TAG, "Received message from " MACSTR " Len: %u", MAC2STR (rxMessage->srcAddress), rxMessage->payload_len); + DEBUG_VERBOSE (QESPNOW_TAG, "Message: %.*s", rxMessage->payload_len, rxMessage->payload); + + + if (quickEspNow.dataRcvd) { + bool broadcast = ! memcmp (rxMessage->dstAddress, ESPNOW_BROADCAST_ADDRESS, ESP_NOW_ETH_ALEN); + // quickEspNow.dataRcvd (mac_addr, data, len, rx_ctrl->rssi - 98); // rssi should be in dBm but it has added almost 100 dB. Do not know why + quickEspNow.dataRcvd (rxMessage->srcAddress, rxMessage->payload, rxMessage->payload_len, rxMessage->rssi, broadcast); // rssi should be in dBm but it has added almost 100 dB. Do not know why + } + + rxMessage->payload_len = 0; + rx_queue.pop (); + DEBUG_DBG (QESPNOW_TAG, "RX Comms message pop. Queue size %d", rx_queue.size ()); + } + +} + +void QuickEspNow::tx_cb (uint8_t* mac_addr, uint8_t status) { + quickEspNow.readyToSend = true; + quickEspNow.sentStatus = status; + DEBUG_DBG (QESPNOW_TAG, "-------------- Tx Confirmed %s", status == ESP_NOW_SEND_SUCCESS ? "true" : "false"); + quickEspNow.waitingForConfirmation = false; + DEBUG_DBG (QESPNOW_TAG, "-------------- Ready to send: true"); + if (quickEspNow.sentResult) { + quickEspNow.sentResult (mac_addr, status); + } +} + +#endif // ESP8266 \ No newline at end of file diff --git a/lib/lib_div/QuickESPNow/src/QuickEspNow_esp8266.h b/lib/lib_div/QuickESPNow/src/QuickEspNow_esp8266.h new file mode 100644 index 000000000000..39b49432c5fc --- /dev/null +++ b/lib/lib_div/QuickESPNow/src/QuickEspNow_esp8266.h @@ -0,0 +1,145 @@ +#ifndef _QUICK_ESPNOW_ESP8266_h +#define _QUICK_ESPNOW_ESP8266_h +#ifdef ESP8266 + +#include "Arduino.h" +#include "Comms_hal.h" + +#include +#include +#include "RingBuffer.h" +// Disable debug dependency if debug level is 0 +#if DEBUG_LEVEL > 0 +#include +constexpr auto QESPNOW_TAG = "QESPNOW"; +#else // DEBUG_LEVEL +#define DEBUG_ERROR(...) +#define DEBUG_INFO(...) +#define DEBUG_VERBOSE(...) +#define DEBUG_WARN(...) +#define DEBUG_DBG(...) +#endif + +//#define MEAS_TPUT + +static const uint8_t ESPNOW_BROADCAST_ADDRESS[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; +static const uint8_t MIN_WIFI_CHANNEL = 0; +static const uint8_t MAX_WIFI_CHANNEL = 14; +static const uint8_t CURRENT_WIFI_CHANNEL = 255; +static const size_t ESPNOW_MAX_MESSAGE_LENGTH = 255; ///< @brief Maximum message length +static const uint8_t ESPNOW_ADDR_LEN = 6; ///< @brief Address length +static const uint8_t ESPNOW_QUEUE_SIZE = 3; ///< @brief Queue size +static const int TASK_PERIOD = 10; ///< @brief Rx and Tx tasks period +#ifdef MEAS_TPUT +static const time_t MEAS_TP_EVERY_MS = 10000; ///< @brief Measurement time period +#endif // MEAS_TPUT + +#define ESP_NOW_ETH_ALEN 6 +#define ESP_NOW_MAX_DATA_LEN 250 +#define WIFI_IF_STA STATION_IF +#define WIFI_IF_AP SOFTAP_IF + +typedef enum { + ESP_NOW_SEND_SUCCESS = 0, /**< Send ESPNOW data successfully */ + ESP_NOW_SEND_FAIL, /**< Send ESPNOW data fail */ +} esp_now_send_status_t; + +typedef struct { + uint16_t frame_head; + uint16_t duration; + uint8_t destination_address[6]; + uint8_t source_address[6]; + uint8_t broadcast_address[6]; + uint16_t sequence_control; + + uint8_t category_code; + uint8_t organization_identifier[3]; // 0x18fe34 + uint8_t random_values[4]; + struct { + uint8_t element_id; // 0xdd + uint8_t lenght; // + uint8_t organization_identifier[3]; // 0x18fe34 + uint8_t type; // 4 + uint8_t version; + uint8_t body[0]; + } vendor_specific_content; +} __attribute__ ((packed)) espnow_frame_format_t; + +typedef struct { + uint8_t dstAddress[ESPNOW_ADDR_LEN]; /**< Message topic*/ + uint8_t payload[ESPNOW_MAX_MESSAGE_LENGTH]; /**< Message payload*/ + size_t payload_len; /**< Payload length*/ +} comms_tx_queue_item_t; + +typedef struct { + uint8_t srcAddress[ESPNOW_ADDR_LEN]; /**< Source Address */ + uint8_t dstAddress[ESPNOW_ADDR_LEN]; /**< Destination Address */ + uint8_t payload[ESPNOW_MAX_MESSAGE_LENGTH]; /**< Message payload */ + size_t payload_len; /**< Payload length */ + int8_t rssi; /**< RSSI */ +} comms_rx_queue_item_t; + +class QuickEspNow : public Comms_halClass { +public: + QuickEspNow () : + tx_queue (ESPNOW_QUEUE_SIZE), rx_queue (ESPNOW_QUEUE_SIZE) {} + bool begin (uint8_t channel = 255, uint32_t interface = 0, bool synchronousSend = true) override; + void stop () override; + comms_send_error_t send (const uint8_t* dstAddress, const uint8_t* payload, size_t payload_len) override; + comms_send_error_t sendBcast (const uint8_t* payload, size_t payload_len) { + return send (ESPNOW_BROADCAST_ADDRESS, payload, payload_len); + } + void onDataRcvd (comms_hal_rcvd_data dataRcvd) override; + void onDataSent (comms_hal_sent_data sentResult) override; + uint8_t getAddressLength () override { return ESPNOW_ADDR_LEN; } + uint8_t getMaxMessageLength () override { return ESPNOW_MAX_MESSAGE_LENGTH; } + void enableTransmit (bool enable) override; + bool setChannel (uint8_t channel); + bool readyToSendData (); + +protected: + uint8_t wifi_if; + ETSTimer espnowTxTask; + ETSTimer espnowRxTask; +#ifdef MEAS_TPUT + ETSTimer dataTPTimer; + unsigned long txDataSent = 0; + unsigned long rxDataReceived = 0; + unsigned long txDataDropped = 0; + time_t lastDataTPMeas = 0; + float txDataTP = 0; + float rxDataTP = 0; + float txDroppedDataRatio = 0; + + static void tp_timer_cb (void* param); + void calculateDataTP (); +#endif // MEAS_TPUT + + bool readyToSend = true; + + bool waitingForConfirmation = false; + bool synchronousSend = false; + uint8_t sentStatus; + int queueSize = ESPNOW_QUEUE_SIZE; + + RingBuffer tx_queue; + RingBuffer rx_queue; + //uint8_t channel; + bool followWiFiChannel = false; + + void initComms (); + static void espnowTxTask_cb (void* param); + static void espnowRxTask_cb (void* param); + int32_t sendEspNowMessage (comms_tx_queue_item_t* message); + void espnowTxHandle (); + void espnowRxHandle (); + + + static void ICACHE_FLASH_ATTR rx_cb (uint8_t* mac_addr, uint8_t* data, uint8_t len); + static void ICACHE_FLASH_ATTR tx_cb (uint8_t* mac_addr, uint8_t status); +}; + +extern QuickEspNow quickEspNow; + +#endif // ESP8266 +#endif // _QUICK_ESPNOW_ESP8266_h \ No newline at end of file diff --git a/lib/lib_div/QuickESPNow/src/RingBuffer.h b/lib/lib_div/QuickESPNow/src/RingBuffer.h new file mode 100644 index 000000000000..03fa36e97e57 --- /dev/null +++ b/lib/lib_div/QuickESPNow/src/RingBuffer.h @@ -0,0 +1,144 @@ +/** + * @file RingBuffer.h + * @author German Martin + * @brief Library to build a gateway for EnigmaIoT system + */ + +#ifndef _RINGBUFFER_h +#define _RINGBUFFER_h + +#if defined(ARDUINO) && ARDUINO >= 100 +#include "Arduino.h" +#else +#include "WProgram.h" +#endif +// Disable debug dependency if debug level is 0 +#if DEBUG_LEVEL > 0 +#include +static const char* RINGBUFFER_DEBUG_TAG = "RINGBUFFER"; +#else // DEBUG_LEVEL +#define DEBUG_ERROR(...) +#define DEBUG_INFO(...) +#define DEBUG_VERBOSE(...) +#define DEBUG_WARN(...) +#define DEBUG_DBG(...) +#endif + + +/** + * @brief Ring buffer class. Used to implement message buffer + * + */ +template +class RingBuffer { +protected: + int maxSize; ///< @brief Buffer size + int numElements = 0; ///< @brief Number of elements that buffer currently has + int readIndex = 0; ///< @brief Pointer to next item to be read + int writeIndex = 0; ///< @brief Pointer to next position to write onto + Telement* buffer; ///< @brief Actual buffer + +public: + /** + * @brief Creates a ring buffer to hold `Telement` objects + * @param range Buffer depth + */ + RingBuffer (int range) : maxSize (range) { + buffer = new Telement[maxSize]; + } + + /** + * @brief EnigmaIOTRingBuffer destructor + * @param range Free up buffer memory + */ + ~RingBuffer () { + maxSize = 0; + delete[] (buffer); + } + + /** + * @brief Returns actual number of elements that buffer holds + * @return Returns Actual number of elements that buffer holds + */ + int size () { return numElements; } + + /** + * @brief Checks if buffer is full + * @return Returns `true`if buffer is full, `false` otherwise + */ + bool isFull () { return numElements == maxSize; } + + /** + * @brief Checks if buffer is empty + * @return Returns `true`if buffer has no elements stored, `false` otherwise + */ + bool empty () { return (numElements == 0); } + + /** + * @brief Adds a new item to buffer, deleting older element if it is full + * @param item Element to add to buffer + * @return Returns `false` if buffer was full before inserting the new element, `true` otherwise + */ + bool push (Telement* item) { + bool wasFull = isFull (); + DEBUG_DBG (RINGBUFFER_DEBUG_TAG, "Add element. Buffer was %s", wasFull ? "full" : "not full"); + DEBUG_DBG (RINGBUFFER_DEBUG_TAG, "Before -- > ReadIdx: %d. WriteIdx: %d. Size: %d", readIndex, writeIndex, numElements); +#ifdef ESP32 + portMUX_TYPE myMutex = portMUX_INITIALIZER_UNLOCKED; + portENTER_CRITICAL (&myMutex); +#endif + memcpy (&(buffer[writeIndex]), item, sizeof (Telement)); + //Serial.printf ("Copied: %d bytes\n", sizeof (Telement)); + writeIndex++; + if (writeIndex >= maxSize) { + writeIndex %= maxSize; + } + if (wasFull) { // old value is no longer valid + readIndex++; + if (readIndex >= maxSize) { + readIndex %= maxSize; + } + } else { + numElements++; + } +#ifdef ESP32 + portEXIT_CRITICAL (&myMutex); +#endif + DEBUG_DBG (RINGBUFFER_DEBUG_TAG, "After -- > ReadIdx: %d. WriteIdx: %d. Size: %d", readIndex, writeIndex, numElements); + return !wasFull; + } + + /** + * @brief Deletes older item from buffer, if buffer is not empty + * @return Returns `false` if buffer was empty before trying to delete element, `true` otherwise + */ + bool pop () { + bool wasEmpty = empty (); + DEBUG_DBG (RINGBUFFER_DEBUG_TAG, "Remove element. Buffer was %s", wasEmpty ? "empty" : "not empty"); + DEBUG_DBG (RINGBUFFER_DEBUG_TAG, "Before -- > ReadIdx: %d. WriteIdx: %d. Size: %d", readIndex, writeIndex, numElements); + if (!wasEmpty) { + readIndex++; + if (readIndex >= maxSize) { + readIndex %= maxSize; + } + numElements--; + } + DEBUG_DBG (RINGBUFFER_DEBUG_TAG, "After -- > ReadIdx: %d. WriteIdx: %d. Size: %d", readIndex, writeIndex, numElements); + return !wasEmpty; + } + + /** + * @brief Gets a pointer to older item in buffer, if buffer is not empty + * @return Returns pointer to element. If buffer was empty before calling this method it returns `NULL` + */ + Telement* front () { + DEBUG_DBG (RINGBUFFER_DEBUG_TAG, "Read element. ReadIdx: %d. WriteIdx: %d. Size: %d", readIndex, writeIndex, numElements); + if (!empty ()) { + return &(buffer[readIndex]); + } else { + return NULL; + } + } +}; + +#endif // _RINGBUFFER_h \ No newline at end of file diff --git a/lib/lib_div/QuickESPNow/test/README b/lib/lib_div/QuickESPNow/test/README new file mode 100644 index 000000000000..b94d0890faa0 --- /dev/null +++ b/lib/lib_div/QuickESPNow/test/README @@ -0,0 +1,11 @@ + +This directory is intended for PlatformIO Unit Testing and project tests. + +Unit Testing is a software testing method by which individual units of +source code, sets of one or more MCU program modules together with associated +control data, usage procedures, and operating procedures, are tested to +determine whether they are fit for use. Unit testing finds problems early +in the development cycle. + +More information about PlatformIO Unit Testing: +- https://docs.platformio.org/page/plus/unit-testing.html diff --git a/lib/lib_div/QuickESPNow/test/test_peer_list/test_peer_list.cpp b/lib/lib_div/QuickESPNow/test/test_peer_list/test_peer_list.cpp new file mode 100644 index 000000000000..959988927396 --- /dev/null +++ b/lib/lib_div/QuickESPNow/test/test_peer_list/test_peer_list.cpp @@ -0,0 +1,135 @@ +#define UNIT_TEST + +#include +#include + + +PeerListClass PeerList; + +uint8_t macs[ESP_NOW_MAX_TOTAL_PEER_NUM+1][6] = { + {0x00,0x01,0x02,0x03,0x04,0x01}, + {0x00,0x01,0x02,0x03,0x04,0x02}, + {0x00,0x01,0x02,0x03,0x04,0x03}, + {0x00,0x01,0x02,0x03,0x04,0x04}, + {0x00,0x01,0x02,0x03,0x04,0x05}, + {0x00,0x01,0x02,0x03,0x04,0x06}, + {0x00,0x01,0x02,0x03,0x04,0x07}, + {0x00,0x01,0x02,0x03,0x04,0x08}, + {0x00,0x01,0x02,0x03,0x04,0x09}, + {0x00,0x01,0x02,0x03,0x04,0x10}, + {0x00,0x01,0x02,0x03,0x04,0x11}, + {0x00,0x01,0x02,0x03,0x04,0x12}, + {0x00,0x01,0x02,0x03,0x04,0x13}, + {0x00,0x01,0x02,0x03,0x04,0x14}, + {0x00,0x01,0x02,0x03,0x04,0x15}, + {0x00,0x01,0x02,0x03,0x04,0x16}, + {0x00,0x01,0x02,0x03,0x04,0x17}, + {0x00,0x01,0x02,0x03,0x04,0x18}, + {0x00,0x01,0x02,0x03,0x04,0x19}, + {0x00,0x01,0x02,0x03,0x04,0x20}, + {0x00,0x01,0x02,0x03,0x04,0x21} +}; + + +void setUp (void) { + // set stuff up here + Serial.begin (115200); +} + +void tearDown (void) { + // clean stuff up here +} + +void test_add_one_peer () { + uint8_t mac[6] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05 }; + TEST_ASSERT_FALSE (PeerList.peer_exists (mac)); + TEST_ASSERT_TRUE (PeerList.add_peer (mac)); + TEST_ASSERT_TRUE (PeerList.peer_exists (mac)); + TEST_ASSERT_EQUAL (1, PeerList.get_peer_number ()); + TEST_ASSERT_TRUE (PeerList.delete_peer (mac)); + TEST_ASSERT_EQUAL (0, PeerList.get_peer_number ()); +} + +void test_add_existing_peer () { + // Serial.println ("test_add_existing_peer"); + uint8_t mac[6] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05 }; + TEST_ASSERT_FALSE (PeerList.peer_exists (mac)); + TEST_ASSERT_TRUE(PeerList.add_peer(mac)); + TEST_ASSERT_TRUE (PeerList.peer_exists (mac)); + TEST_ASSERT_EQUAL (1, PeerList.get_peer_number ()); + TEST_ASSERT_FALSE (PeerList.add_peer (mac)); + TEST_ASSERT_EQUAL (1, PeerList.get_peer_number ()); + TEST_ASSERT_TRUE (PeerList.delete_peer (mac)); + TEST_ASSERT_EQUAL (0, PeerList.get_peer_number ()); +} + +void test_add_max_peers () { + // Serial.println ("test_add_max_peers"); + for (uint8_t i = 0; i < ESP_NOW_MAX_TOTAL_PEER_NUM; i++) { + TEST_ASSERT_FALSE (PeerList.peer_exists (macs[i])); + TEST_ASSERT_TRUE(PeerList.add_peer(macs[i])); + TEST_ASSERT_TRUE (PeerList.peer_exists (macs[i])); + TEST_ASSERT_EQUAL (i + 1, PeerList.get_peer_number ()); + } + // PeerList.dump_peer_list (); + for (uint8_t i = 0; i < ESP_NOW_MAX_TOTAL_PEER_NUM; i++) { + TEST_ASSERT_TRUE (PeerList.delete_peer (macs[i])); + TEST_ASSERT_EQUAL (ESP_NOW_MAX_TOTAL_PEER_NUM - i - 1, PeerList.get_peer_number ()); + } + // PeerList.dump_peer_list (); +} + +void test_add_max_peers_plus_1 () { + // Serial.println ("test_add_max_peers_plus_1"); + for (uint8_t i = 0; i < ESP_NOW_MAX_TOTAL_PEER_NUM; i++) { + TEST_ASSERT_FALSE (PeerList.peer_exists (macs[i])); + TEST_ASSERT_TRUE (PeerList.add_peer (macs[i])); + TEST_ASSERT_TRUE (PeerList.peer_exists (macs[i])); + TEST_ASSERT_EQUAL (i + 1, PeerList.get_peer_number ()); + } + // PeerList.dump_peer_list (); + TEST_ASSERT_FALSE (PeerList.peer_exists (macs[ESP_NOW_MAX_TOTAL_PEER_NUM])); + TEST_ASSERT_FALSE (PeerList.add_peer (macs[ESP_NOW_MAX_TOTAL_PEER_NUM])); + // PeerList.dump_peer_list (); + TEST_ASSERT_FALSE (PeerList.peer_exists (macs[ESP_NOW_MAX_TOTAL_PEER_NUM])); + TEST_ASSERT_EQUAL (ESP_NOW_MAX_TOTAL_PEER_NUM, PeerList.get_peer_number ()); + + for (uint8_t i = 0; i < ESP_NOW_MAX_TOTAL_PEER_NUM; i++) { + TEST_ASSERT_TRUE (PeerList.delete_peer ()); + TEST_ASSERT_EQUAL (ESP_NOW_MAX_TOTAL_PEER_NUM - i - 1, PeerList.get_peer_number ()); + } + // PeerList.dump_peer_list (); +} + +void process () { + UNITY_BEGIN (); + RUN_TEST (test_add_one_peer); + RUN_TEST (test_add_existing_peer); + RUN_TEST (test_add_max_peers); + RUN_TEST (test_add_max_peers_plus_1); + UNITY_END (); +} + +#ifdef ARDUINO + +#include +void setup () { + // NOTE!!! Wait for >2 secs + // if board doesn't support software reset via Serial.DTR/RTS + delay (2000); + + process (); +} + +void loop () { + delay (1); +} + +#else + +int main (int argc, char** argv) { + process (); + return 0; +} + +#endif \ No newline at end of file diff --git a/tasmota/include/tasmota_types.h b/tasmota/include/tasmota_types.h index 5e325fe089b0..3bbc7f2a6e83 100644 --- a/tasmota/include/tasmota_types.h +++ b/tasmota/include/tasmota_types.h @@ -198,7 +198,7 @@ typedef union { // Restricted by MISRA-C Rule 18.4 bu uint32_t gui_no_state_text : 1; // bit 15 (v14.3.0.7) - SetOption161 - GUI_NOSHOW_STATETEXT - (GUI) Disable display of state text (1) uint32_t no_export_energy_today : 1; // bit 16 (v14.3.0.7) - SetOption162 - (Energy) Do not add export energy to energy today (1) uint32_t gui_device_name : 1; // bit 17 (v14.4.1.1) - SetOption163 - GUI_NOSHOW_DEVICENAME - (GUI) Disable display of GUI device name (1) - uint32_t spare18 : 1; // bit 18 + uint32_t wizmote_enabled : 1; // bit 18 (v14.4.1.4) - SetOption164 - (WizMote) Enable WiZ Smart Remote support (1) uint32_t spare19 : 1; // bit 19 uint32_t spare20 : 1; // bit 20 uint32_t spare21 : 1; // bit 21 @@ -242,9 +242,11 @@ typedef union { uint32_t data; // Allow bit manipulation struct { uint32_t log_file_idx : 4; // bit 0.3 (v14.4.1.2) - FileLog log rotate index - uint32_t light_pixels_order : 3; // bit 4.6 (v14.4.1.3) - LED light order /GRB/RGB/RBG/BRG/BGR/GBR, high bit indicates W before (for RGBW) - uint32_t light_pixels_rgbw : 1; // bit 7 (v14.4.1.3) - LED true is 4 channels RGBW, false is 3 channels RGB - uint32_t light_pixels_w_first : 1; // bit 8 (v14.4.1.3) - LED true if W channel comes first, default is W + uint32_t spare04 : 1; // bit 4 + uint32_t spare05 : 1; // bit 5 + uint32_t spare06 : 1; // bit 6 + uint32_t spare07 : 1; // bit 7 + uint32_t spare08 : 1; // bit 8 uint32_t spare09 : 1; // bit 9 uint32_t spare10 : 1; // bit 10 uint32_t spare11 : 1; // bit 11 diff --git a/tasmota/my_user_config.h b/tasmota/my_user_config.h index bde2cc98246f..ef6d61c2566b 100644 --- a/tasmota/my_user_config.h +++ b/tasmota/my_user_config.h @@ -417,8 +417,9 @@ // -- ESP-NOW ------------------------------------- //#define USE_TASMESH // Enable Tasmota Mesh using ESP-NOW (+11k code) -//#define USE_TASMESH_HEARTBEAT // If enabled, the broker will detect when nodes come online and offline and send Birth and LWT messages over MQTT correspondingly -//#define TASMESH_OFFLINE_DELAY 3 // Maximum number of seconds since the last heartbeat before the broker considers a node to be offline +// #define USE_TASMESH_HEARTBEAT // If enabled, the broker will detect when nodes come online and offline and send Birth and LWT messages over MQTT correspondingly +// #define TASMESH_OFFLINE_DELAY 3 // Maximum number of seconds since the last heartbeat before the broker considers a node to be offline +//#define USE_WIZMOTE // [SetOption164 1] Add support for WiZ Smart Remote (uses ESP-NOW) (+4k2 code) // -- OTA ----------------------------------------- //#define USE_ARDUINO_OTA // Add optional support for Arduino OTA with ESP8266 (+13k code) diff --git a/tasmota/tasmota_support/support_features.ino b/tasmota/tasmota_support/support_features.ino index e8a8cf007d17..f08d13dea070 100644 --- a/tasmota/tasmota_support/support_features.ino +++ b/tasmota/tasmota_support/support_features.ino @@ -940,7 +940,9 @@ constexpr uint32_t feature[] = { #ifdef USE_C8_CO2_5K 0x00001000 | // xsns_117_c8_co2_5k.ino #endif -// 0x00002000 | // +#ifdef USE_WIZMOTE + 0x00002000 | // xdrv_77_wizmote.ino +#endif // 0x00004000 | // // 0x00008000 | // // 0x00010000 | // diff --git a/tasmota/tasmota_xdrv_driver/xdrv_77_wizmote.ino b/tasmota/tasmota_xdrv_driver/xdrv_77_wizmote.ino new file mode 100644 index 000000000000..787662231068 --- /dev/null +++ b/tasmota/tasmota_xdrv_driver/xdrv_77_wizmote.ino @@ -0,0 +1,199 @@ +/* + xdrv_77_wizmote.ino - WiZ Smart Remote decoder support for Tasmota + + SPDX-FileCopyrightText: 2025 Theo Arends + + SPDX-License-Identifier: GPL-3.0-only +*/ + +#ifdef USE_WIZMOTE +/*********************************************************************************************\ + * WiZ WiFi ESP-NOW Smart Remote decoder + * + * Example rule + * on wizmote#action do var1 %value% endon - Store current button + * on wizmote#id=12318994 do event action=%var1% endon - Test for correct Id and execute event + * on event#action=on do power 1 endon - On button ON do power 1 + * on event#action=off do power 0 endon - On button OFF do power 0 + * on event#action=bd do dimmer - endon - On button Brihtness - do dimmer - (10% decrease) + * on event#action=bu do dimmer + endon - On button Brightness + do dimmer + (10% increase) + * on event#action=bn do dimmer 20 endon - On button Moon do dimmer 20 +\*********************************************************************************************/ + +#define XDRV_77 77 + +#include + +struct WizMote { + uint32_t last_seq; + uint32_t id; + int rssi; + uint8_t index; + uint8_t battery_level; + bool active; +} WizMote; + +/*********************************************************************************************\ + * WiZ Smart Remote +\*********************************************************************************************/ + +#define WIZMOTE_BUTTON_ON 1 +#define WIZMOTE_BUTTON_OFF 2 +#define WIZMOTE_BUTTON_NIGHT 3 +#define WIZMOTE_BUTTON_BRIGHT_DOWN 8 +#define WIZMOTE_BUTTON_BRIGHT_UP 9 +#define WIZMOTE_BUTTON_ONE 16 +#define WIZMOTE_BUTTON_TWO 17 +#define WIZMOTE_BUTTON_THREE 18 +#define WIZMOTE_BUTTON_FOUR 19 +#define WIZSMART_BUTTON_ON 100 +#define WIZSMART_BUTTON_OFF 101 +#define WIZSMART_BUTTON_BRIGHT_UP 102 +#define WIZSMART_BUTTON_BRIGHT_DOWN 103 + +// 01 2 3 4 5 6 7 8 9 +const char kWMButtons[] PROGMEM = "|OFF|ON|BN|B1|B2|B3|B4|BU|BD"; +const uint8_t sWMButtons[] PROGMEM = { + WIZMOTE_BUTTON_ON, WIZMOTE_BUTTON_OFF, WIZMOTE_BUTTON_NIGHT, + WIZMOTE_BUTTON_ONE, WIZMOTE_BUTTON_TWO, WIZMOTE_BUTTON_THREE, WIZMOTE_BUTTON_FOUR, + WIZMOTE_BUTTON_BRIGHT_DOWN, WIZMOTE_BUTTON_BRIGHT_UP, + WIZSMART_BUTTON_ON, WIZSMART_BUTTON_OFF, + WIZSMART_BUTTON_BRIGHT_DOWN, WIZSMART_BUTTON_BRIGHT_UP +}; +const uint8_t dWMButtons[] PROGMEM = { + 2, 1, 3, + 4, 5, 6, 7, + 9, 8, + 2, 1, + 9, 8 +}; + +typedef struct WizMotePacket { + union { + uint8_t program; // 0x91 for ON button, 0x81 for all others + struct { + uint8_t : 4; + uint8_t pairing : 1; + uint8_t : 3; + }; + }; + uint32_t sequence; // Incremental sequence number 32 bit unsigned integer LE + uint8_t data_type1; // Data type: button (32) + uint8_t button; // Identifies which button is being pressed + uint8_t data_type2; // Data type: batteryLevel (1) + uint8_t battery_level; // WiZMote batteryLevel out of 0 .. 100 + uint8_t mac[4]; // CCM MAC (Message Authentication Code) +} __attribute__((packed)) WizMotePacket_t; + +void WizMoteHandleRemoteData(uint8_t *mac, uint8_t *incoming_data, size_t len, signed int rssi) { + // WiZ mac = 44:4F:8E:00:00:00 to 44:4F:8E:FF:FF:FF + + WizMotePacket_t *incoming = reinterpret_cast(incoming_data); + + if (len != sizeof(WizMotePacket_t)) { + AddLog(LOG_LEVEL_DEBUG, PSTR("WIZ: ERROR Rcvd unknown message of length %d"), len); + return; + } + + if (incoming->sequence == WizMote.last_seq) { + AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("WIZ: WARNING Rcvd same sequence %d"), incoming->sequence); + return; + } + + WizMote.index = 0; + for (uint32_t i = 0; i < 13; i++) { + if (incoming->button == pgm_read_byte(sWMButtons + i)) { + WizMote.index = pgm_read_byte(dWMButtons + i); + break; + } + } + if (0 == WizMote.index) { + AddLog(LOG_LEVEL_DEBUG, PSTR("WIZ: ERROR Rcvd invalid button code %d"), incoming->button); + return; + } + WizMote.id = mac[5] | (mac[4] << 8) | (mac[3] << 16); // WiZ mac block range + WizMote.rssi = rssi; + WizMote.battery_level = incoming->battery_level; + WizMote.last_seq = incoming->sequence; +} + +void WizMoteResponse(void) { + // This needs to be executed here as it will panic if placed within the above Callback + char text[5]; + Response_P(PSTR("{\"WiZMote\":{\"Id\":%d,\"Action\":\"%s\",\"RSSI\":%d,\"Battery\":%d}}"), + WizMote.id, GetTextIndexed(text, sizeof(text), WizMote.index, kWMButtons), WizMote.rssi, WizMote.battery_level); + if (Settings->flag6.mqtt_disable_publish ) { // SetOption147 - If it is activated, Tasmota will not publish MQTT message, but it will proccess event trigger rules + XdrvRulesProcess(0); + } else { + MqttPublishPrefixTopicRulesProcess_P(RESULT_OR_TELE, PSTR("WIZMOTE")); + } + WizMote.index = 0; +} + +/*********************************************************************************************\ + * ESP-NOW +\*********************************************************************************************/ + +void EspNowDataReceived(uint8_t* mac, uint8_t* data, uint8_t len, signed int rssi, bool broadcast) { + char _destMAC[18]; + ToHex_P(mac, 6, _destMAC, 18, ':'); + + AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("NOW: ESP-NOW Rcvd %*_H, RSSI %d dBm, From %s, %s"), + len, data, rssi, _destMAC, broadcast ? "Broadcast" : "Unicast"); + + if (data[0] == 0x91 || data[0] == 0x81 || data[0] == 0x80) { + WizMoteHandleRemoteData(mac, data, len, rssi); + } +} + +/*********************************************************************************************\ + * +\*********************************************************************************************/ + +void EspNowInit(void) { + if (!WizMote.active) { + quickEspNow.onDataRcvd(EspNowDataReceived); +#ifdef ESP32 +// quickEspNow.setWiFiBandwidth (WIFI_IF_STA, WIFI_BW_HT20); // Only needed for ESP32 in case you need coexistence with ESP8266 in the same network +#endif //ESP32 + quickEspNow.begin(); + AddLog(LOG_LEVEL_INFO, PSTR("NOW: ESP-NOW started")); + WizMote.active = true; + } +} + +/*********************************************************************************************\ + * Interface +\*********************************************************************************************/ + +bool Xdrv77(uint32_t function) { + if (!Settings->flag6.wizmote_enabled) { return false; } // SetOption164 - (WizMote) Enable WiZ Smart Remote support (1) + + bool result = false; + +/* + if (FUNC_INIT == function) { + EspNowInit(); + } +*/ + if (FUNC_NETWORK_UP == function) { + if (!TasmotaGlobal.global_state.wifi_down) { + EspNowInit(); + } + } + else if (WizMote.active) { + switch (function) { + case FUNC_LOOP: + if (WizMote.index > 0) { + WizMoteResponse(); + } + break; + case FUNC_ACTIVE: + result = true; + break; + } + } + return result; +} + +#endif // USE_WIZMOTE diff --git a/tools/decode-status.py b/tools/decode-status.py index 724d02f333b0..fc1e750bea79 100755 --- a/tools/decode-status.py +++ b/tools/decode-status.py @@ -219,7 +219,8 @@ "(GUI) Disable display of state text (1)", "(Energy) Do not add export energy to energy today (1)", "(GUI) Disable display of GUI device name (1)", - "","", + "(WizMote) Enable WiZ Smart Remote support (1)", + "", "","","","", "","","","", "","","","" @@ -342,7 +343,7 @@ obj = json.load(fp) def StartDecode(): - print ("\n*** decode-status.py v14.4.1.2 by Theo Arends and Jacek Ziolkowski ***") + print ("\n*** decode-status.py v14.4.1.4 by Theo Arends and Jacek Ziolkowski ***") # print("Decoding\n{}".format(obj))