diff --git a/README.md b/README.md index 666738b44b..1599c00c0e 100644 --- a/README.md +++ b/README.md @@ -1,70 +1,7 @@ +# PJON -![PJON](http://www.gioblu.com/PJON/PJON-github-header-tiny.png) -## PJON 12.1 -PJON® (Padded Jittering Operative Network) is an arduino-compatible, multi-master, multi-media network protocol. It proposes a new Open Standard, it is designed as a framework and implements a totally software-defined network protocol stack that can be easily cross-compiled on many MCUs and architectures like ATtiny, ATmega, SAMD, ESP8266, ESP32, STM32, Teensy, Raspberry Pi, Linux, Windows x86, Apple and Android. PJON operates on a wide range of media and protocols like PJDL, PJDLR, PJDLS, Serial, RS485, USB, ASK/FSK, LoRa, UDP, TCP, MQTT and ESPNOW. +This fork of [PJON](http://www.gioblu.com/PJON/PJON-github-header-tiny.png) aimed to add support of ESP-NOW strategy to esp8266 microcontroller. -[![Get PJON bus id](https://img.shields.io/badge/get-PJON%20bus%20id-lightgrey.svg)](http://www.pjon.org/get-bus-id.php) -[![Join the chat at https://gitter.im/gioblu/PJON](https://badges.gitter.im/gioblu/PJON.svg)](https://gitter.im/gioblu/PJON?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) - -PJON is used in thousands of devices and its community has spread worldwide because of the following 5 key factors: -- **New technology**: [PJON](specification/PJON-protocol-specification-v4.0.md) is an experimental network protocol stack crafted in 10 years of research and experimentation. It was originally developed as an open-source alternative to i2c and 1-Wire but during development its scope and features have been extended to cover use cases where IP is generally applied. PJON has been engineered to have a variable footprint (4.2-8.2 kB program memory) and overhead (5-35 bytes per packet) depending on its configuration. -- **Multi-media support**: PJON operates upon a wide range of protocols like TCP, UDP, MQTT, ESPNOW, USB, Serial, RS485 and LoRa. The PJON network protocol stack specifies and implements also [PJDL](src/strategies/SoftwareBitBang/specification/PJDL-specification-v5.0.md) that operates over a single wire of up to 2000m shared by up to 255 devices, [PJDLR](src/strategies/OverSampling/specification/PJDLR-specification-v3.0.md) that operates with many ASK/FSK/OOK radio modules, and also [PJDLS](src/strategies/AnalogSampling/specification/PJDLS-specification-v2.0.md) that operates wirelessly with light pulses using off-the-shelf LEDs and laser diodes. -- **Increased reliability**: Many protocols massively applied worldwide expose dangerous vulnerabilities, have weak error detection algorithms and are not resilient to interference. PJON is based on years of analysis and study not to make the same mistakes present in most alternatives and provide with a simpler and more efficient solution. -- **High flexibility**: PJON is totally software-defined and it is designed to be easily extensible. it builds out-of-the-box in all supported devices and operates transparently on top of any supported protocol or medium. -- **Low cost**: Without any additional hardware needed to operate, minimal network wiring requirements and direct pin-to-pin or LED-to-LED communication, PJON is extremely energy efficient, cheap to be implemented and maintained. This implementation is kept updated and meticulously tested thanks to the strong commitment of its growing community of end users, testers and developers. - -### Features -- Cross-compilation support with the [interfaces](src/interfaces) system calls abstraction -- Multi-media support with the [strategies](src/strategies) data link layer abstraction -- Modular packet format that includes only the field used (overhead 5-35 bytes) -- Hot-swap support, no need of system reset or shut down when replacing or adding devices -- Flexible local (device id) and shared (bus id) network identification -- Safe error detection done with modern CRC8 and CRC32 polynomials -- Optional inclusion of MAC addresses -- Optional acknowledgement -- Error handling - -### Specifications -- [PJON v4.0](specification/PJON-protocol-specification-v4.0.md) -- [PJDL v5.0](src/strategies/SoftwareBitBang/specification/PJDL-specification-v5.0.md) -- [PJDLR v3.0](src/strategies/OverSampling/specification/PJDLR-specification-v3.0.md) -- [PJDLS v2.0](src/strategies/AnalogSampling/specification/PJDLS-specification-v2.0.md) -- [TSDL v3.0](src/strategies/ThroughSerial/specification/TSDL-specification-v3.0.md) - -### Documentation -- [Addressing](/documentation/addressing.md) -- [Configuration](/documentation/configuration.md) -- [Data reception](/documentation/data-reception.md) -- [Data structures](/documentation/data-structures.md) -- [Data transmission](/documentation/data-transmission.md) -- [Error handling](/documentation/error-handling.md) -- [Routing](/documentation/routing.md) -- [IO setup](/documentation/io-setup.md) - -### Publications -- [PJON protocol handbook](https://www.pjon-technologies.com/collections/frontpage/products/pjon-protocol-hand-book) by Giovanni Blu Mitolo - Distributed by [PJON Technologies srl](https://www.pjon-technologies.com) -- [PJON 12.0 big box](https://www.pjon-technologies.com/collections/frontpage/products/pjon-protocol-12-0-big-box) by Giovanni Blu Mitolo - Distributed by [PJON Technologies srl](https://www.pjon-technologies.com) - -### Academic studies -Researchers are active in many universities worldwide using PJON in different environments. The following list contains all the known published academic studies about PJON: -- [Definition and Application of PJON-PLC for sensor networks](https://repositorio.unican.es/xmlui/bitstream/handle/10902/14012/408952.pdf?sequence=1) by Jorge Gómez Segurola, Ingeniería de Tecnologías de -Telecomunicación - [Universidad de Cantabria](https://web.unican.es/) (ES) -- [Biomimetic electronics](http://c.harl.ie/biomimetic.html) by Charlie Williams with scientific input from researchers Vítor Martins dos Santos, Diana Machado de Sousa and Sabine Vreeburg - Artist in Residency at [Wageningen University](https://www.wur.nl/en.htm) (NL) - -### Contribute -Feel free to send a pull request sharing something you have made that could help, if you want to support this project you can also try to solve an [issue](https://github.com/gioblu/PJON/issues). Thanks to support, expertise, kindness and talent of the following contributors, the protocol's documentation, specification and implementation have been strongly tested, enhanced and verified: - -[Fred Larsen](https://github.com/fredilarsen), [Zbigniew Zasieczny](https://github.com/girgitt), [Matheus Garbelini](https://github.com/Matheus-Garbelini), [sticilface](https://github.com/sticilface), [Felix Barbalet](https://github.com/xlfe), [Oleh Halitskiy](https://github.com/Halytskyi), [fotosettore](https://github.com/fotosettore), [fabpolli](https://github.com/fabpolli), [Adrian Sławiński](https://github.com/4ib3r), [Osman Selçuk Aktepe](https://github.com/osman-aktepe), [Jorgen-VikingGod](https://github.com/Jorgen-VikingGod), [drtrigon](https://github.com/drtrigon), [Endre Karlson](https://github.com/ekarlso), [Wilfried Klaas](https://github.com/willie68), [budaics](https://github.com/budaics), [ibantxo](https://github.com/ibantxo), [gonnavis](https://github.com/gonnavis), [maxidroms83](https://github.com/maxidroms83), [Evgeny Dontsov](https://github.com/dontsovcmc), [zcattacz](https://github.com/zcattacz), [Valerii Koval](https://github.com/valeros), [Ivan Kravets](https://github.com/ivankravets), [Esben Soeltoft](https://github.com/EsbenSoeltoft), [Alex Grishin](https://github.com/240974a), [Andrew Grande](https://github.com/aperepel), [Michael Teeww](https://github.com/MichMich), [Paolo Paolucci](https://github.com/PaoloP74), [per1234](https://github.com/per1234), [Santiago Castro](https://github.com/bryant1410), [pacproduct](https://github.com/pacproduct), [elusive-code](https://github.com/elusive-code), [Emanuele Iannone](https://github.com/eiannone), [Christian Pointner](https://github.com/equinox0815), [Fabian Gärtner](https://github.com/TeeTrizZz), [Mauro Mombelli](https://github.com/MauroMombelli), [Remo Kallio](https://github.com/shacal), [hyndruide](https://github.com/hyndruide), [sigmaeo](https://github.com/sigmaeo), [filogranaf](https://github.com/filogranaf), [Maximiliano Duarte](https://github.com/domonetic), [Viktor Szépe](https://github.com/szepeviktor), [Shachar Limor](), [Pantovich](), [Mauro Zancarlin](), [Franketto](), [jzobac](), [DanRoad](), [fcastrovilli](https://github.com/fcastrovilli), [Andrei Volkau](https://github.com/andrei-volkau), [maniekq](https://github.com/maniekq), [DetAtHome](https://github.com/DetAtHome), [Michael Branson](https://github.com/mxbranson), [chestwood96](https://github.com/chestwood96), [Mattze96](https://github.com/Mattze96), [Steven Bense](https://github.com/justoke), [Jack Anderson](https://github.com/jdaandersj), [callalilychen](https://github.com/callalilychen) and [Julio Aguirre](https://github.com/jcallano). - -### Compliant tools -- [ModuleInterface](https://github.com/fredilarsen/ModuleInterface) - easy config and value sync between IoT modules by Fred Larsen -- [PJON-cython](https://github.com/xlfe/PJON-cython) - cython PJON wrapper by xlfe github user -- [PJON-piper](https://github.com/Girgitt/PJON-piper) - command line wrapper by Zbigniew Zasieczny -- [PJON-python](https://github.com/Girgitt/PJON-python) - python interface by Zbigniew Zasieczny -- [PJON-gRPC](https://github.com/Halytskyi/PJON-gRPC) - gRPC server-client by Oleh Halytskyi - -### License -All the software included in this project is experimental and it is distributed "AS IS" without any warranty, use it at your own risk. [Licensed](https://github.com/gioblu/PJON/blob/master/LICENSE.md) under the Apache License, Version 2.0. PJON® and its brand are registered trademarks, property of Giovanni Blu Mitolo gioscarab@gmail.com - -### Safety warning -When installing or maintaining a PJON network, extreme care must be taken to avoid any danger. If devices are connected to AC power you are exposed to a high chance of being electrocuted if hardware is not installed carefully and properly. If you are not experienced enough ask the support of a skilled technician and consider that many countries prohibit uncertified installations. When a [SoftwareBitBang](/src/strategies/SoftwareBitBang) bus is installed [interference mitigation](https://github.com/gioblu/PJON/wiki/Mitigate-interference) and [protective circuitry](https://github.com/gioblu/PJON/wiki/Protective-circuitry) guidelines must be followed. When working with an [AnalogSampling](/src/strategies/AnalogSampling) LED or laser based setup safety glasses must be worn and transceivers must be operated cautiously to avoid potential eye injuries. Before any practical test or a hardware purchase for a wireless [OverSampling](/src/strategies/OverSampling), [ThroughSerial](/src/strategies/ThroughSerial) or [ThroughLoRa](/src/strategies/ThroughLoRa) radio setup, compliance with government requirements and regulations must be ensured. When connecting a local bus to the internet all devices must be considered potentially compromised, manipulated or remotely actuated against your will. It should be considered a good practice not to connect to the internet systems that may create a damage (fire, flood, data-leak) if hacked. +Originally, I needed this for my project, but then I changed the approach in my project and this was no longer a priority for me. +Realistically, I will not finish this. Sorry. It's a good news that it is possible to make ESP-NOW work on esp8266. If anyone needs +it in the future, this repo and the [associated pull request](https://github.com/gioblu/PJON/pull/361) can be used as a rough guide for a new implementation. diff --git a/src/interfaces/ARDUINO/ESPNOWHelper.h b/src/interfaces/ARDUINO/ESPNOWHelper.h index ed25ce60cf..6d194e47b0 100644 --- a/src/interfaces/ARDUINO/ESPNOWHelper.h +++ b/src/interfaces/ARDUINO/ESPNOWHelper.h @@ -1,56 +1,47 @@ #pragma once -#ifndef ESP32 - #error "ESP32 constant is not defined." +#if !defined(ESP8266) && !defined(ESP32) + #error "ESP8266 or ESP32 constant is not defined." #endif -// ESP includes -#include -#include -#include -#include -#include "freertos/FreeRTOS.h" -#include "freertos/semphr.h" -#include "freertos/timers.h" -#include "nvs_flash.h" -#include "esp_event_loop.h" -#include "tcpip_adapter.h" -#include "esp_wifi.h" -#include "esp_log.h" -#include "esp_system.h" -#include "esp_now.h" -#include "rom/ets_sys.h" -#include "rom/crc.h" -#include "esp_wifi_types.h" - -static const char *TAG = "espnow"; - -/* ESPNOW can work in both station and softap mode. - It is configured in menuconfig. */ -#if CONFIG_STATION_MODE - #define ESPNOW_WIFI_MODE WIFI_MODE_STA - #define ESPNOW_WIFI_IF ESP_IF_WIFI_STA +#include "PacketQueue.h" +#include "PJONDefines.h" + +#if defined(ESP8266) + #include + #include + #include +#elif defined(ESP32) + #include + #include + #include +#endif + +#if defined(CONFIG_STATION_MODE) // ESPNOW can work in both station and softap mode. It is configured in menuconfig. + #if defined(ESP8266) + #define ESPNOW_WIFI_MODE WIFI_STA + #define ESPNOW_WIFI_ROLE ESP_NOW_ROLE_CONTROLLER + #elif defined(ESP32) + #define ESPNOW_WIFI_MODE WIFI_MODE_STA + #define ESPNOW_WIFI_IF ESP_IF_WIFI_STA + #endif + #else - #define ESPNOW_WIFI_MODE WIFI_MODE_AP - #define ESPNOW_WIFI_IF ESP_IF_WIFI_AP + #if defined(ESP8266) + #define ESPNOW_WIFI_MODE WIFI_AP + #define ESPNOW_WIFI_ROLE ESP_NOW_ROLE_COMBO + #elif defined(ESP32) + #define ESPNOW_WIFI_MODE WIFI_MODE_AP + #define ESPNOW_WIFI_IF ESP_IF_WIFI_AP + #endif #endif -#define ESPNOW_MAX_PACKET 250 -#define ESPNOW_QUEUE_SIZE 6 -static uint8_t espnow_broadcast_mac[ESP_NOW_ETH_ALEN] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; -#define IS_BROADCAST_ADDR(addr) (memcmp(addr, espnow_broadcast_mac, ESP_NOW_ETH_ALEN) == 0) -typedef struct { - uint8_t mac_addr[ESP_NOW_ETH_ALEN]; - esp_now_send_status_t status; -} espnow_event_send_cb_t; -typedef struct { - uint8_t mac_addr[ESP_NOW_ETH_ALEN]; - uint8_t *data; - int data_len; -} espnow_packet_t; +static uint8_t espnow_broadcast_mac[ESP_NOW_MAC_LENGTH] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + + enum { ESPNOW_DATA_BROADCAST, @@ -58,193 +49,153 @@ enum { ESPNOW_DATA_MAX, }; -static uint8_t last_mac[ESP_NOW_ETH_ALEN]; -static TaskHandle_t pjon_task_h = NULL; -static xQueueHandle espnow_recv_queue = NULL; +static uint8_t last_mac[ESP_NOW_MAC_LENGTH]; +static PacketQueue packetQueue; +volatile bool sendingDone = true; -static void espnow_send_cb(const uint8_t *mac_addr, esp_now_send_status_t status) { - // The only thing we do in the send callback is unblock the - // other thread which blocks after posting data to the MAC - xTaskNotifyGive(pjon_task_h); - if(mac_addr == NULL) ESP_LOGE(TAG, "Send cb arg error"); - return; +static void espnow_send_cb(const uint8_t *mac_addr, uint8_t status) { + // The only thing we do in the send callback is unblock the other thread which blocks after posting data to the MAC + sendingDone = true; }; static void espnow_recv_cb(const uint8_t *mac_addr, const uint8_t *data, int len) { - espnow_packet_t packet; - if(mac_addr == NULL || data == NULL || len <= 0) { - ESP_LOGE(TAG, "Receive cb arg error"); - return; - } - memcpy(packet.mac_addr, mac_addr, ESP_NOW_ETH_ALEN); - packet.data = (uint8_t *)malloc(len); - if(packet.data == NULL) { - ESP_LOGE(TAG, "Malloc receive data fail"); - return; - } - memcpy(packet.data, data, len); - packet.data_len = len; - // Post to the queue, but don't wait - if(xQueueSend(espnow_recv_queue, &packet, 0) != pdTRUE) { - ESP_LOGW(TAG, "Send receive queue fail"); - free(packet.data); - } + // Add the packet to the incoming queue. + // no error checking, if queue is full just ignore the packet. + packetQueue.push(mac_addr, data, len); }; class ENHelper { - uint8_t _magic_header[4]; - uint8_t _channel = 14; - uint8_t _esp_pmk[16]; + uint8_t _channel = 14; + uint8_t _esp_pmk[16]; public: - void add_node_mac(uint8_t mac_addr[ESP_NOW_ETH_ALEN]) { - ESP_ERROR_CHECK(add_peer(mac_addr)); - }; - - esp_err_t add_peer(uint8_t mac_addr[ESP_NOW_ETH_ALEN]) { - if(esp_now_is_peer_exist(mac_addr)) - return ESP_OK; - /* Add broadcast peer information to peer list. */ - esp_now_peer_info_t *peer = - (esp_now_peer_info_t *)malloc(sizeof(esp_now_peer_info_t)); - if(peer == NULL) { - ESP_LOGE(TAG, "Malloc peer information fail"); - vSemaphoreDelete(espnow_recv_queue); - esp_now_deinit(); - return ESP_FAIL; - } - memset(peer, 0, sizeof(esp_now_peer_info_t)); - peer->channel = _channel; - peer->ifidx = ESPNOW_WIFI_IF; - if(IS_BROADCAST_ADDR(mac_addr)) - peer->encrypt = false; - // else { - // peer->encrypt = true; - // memcpy(peer->lmk, _esp_pmk, 16); - // } - memcpy(peer->peer_addr, mac_addr, ESP_NOW_ETH_ALEN); - ESP_ERROR_CHECK(esp_now_add_peer(peer)); - free(peer); - return ESP_OK; - }; - - bool begin(uint8_t channel, uint8_t *espnow_pmk) { - esp_err_t ret = nvs_flash_init(); - if( - ret == ESP_ERR_NVS_NO_FREE_PAGES // || - // ret == ESP_ERR_NVS_NEW_VERSION_FOUND - // error: ESP_ERR_NVS_NEW_VERSION_FOUND was not declared in this scope - ) { - ESP_ERROR_CHECK(nvs_flash_erase()); - ret = nvs_flash_init(); - } - ESP_ERROR_CHECK(ret); - pjon_task_h = xTaskGetCurrentTaskHandle(); - _channel = channel; - memcpy(_esp_pmk, espnow_pmk, 16); - if(espnow_recv_queue != NULL) - return ESP_FAIL; - espnow_recv_queue = - xQueueCreate(ESPNOW_QUEUE_SIZE, sizeof(espnow_packet_t)); - if(espnow_recv_queue == NULL) { - ESP_LOGE(TAG, "Create mutex fail"); - return ESP_FAIL; - } - tcpip_adapter_init(); - wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); - ESP_ERROR_CHECK(esp_wifi_init(&cfg)); - ESP_ERROR_CHECK(esp_wifi_set_country(&wifi_country)); - ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM)); - ESP_ERROR_CHECK(esp_wifi_set_mode(ESPNOW_WIFI_MODE)); - /* These two steps are required BEFORE the channel can be set - As per the documentation from Espressif: - docs.espressif.com/projects/esp-idf/en/latest/api-reference/network/ - esp_wifi.html#_CPPv420esp_wifi_set_channel7uint8_t18wifi_second_chan_t */ - ESP_ERROR_CHECK(esp_wifi_start()); - ESP_ERROR_CHECK(esp_wifi_set_promiscuous(true)); - ESP_ERROR_CHECK(esp_wifi_set_channel(_channel, WIFI_SECOND_CHAN_NONE)); - // Initialize ESPNOW and register sending & receiving callback function - ESP_ERROR_CHECK(esp_now_init()); - ESP_ERROR_CHECK(esp_now_register_send_cb(espnow_send_cb)); - ESP_ERROR_CHECK(esp_now_register_recv_cb(espnow_recv_cb)); - // Set primary master key - ESP_ERROR_CHECK(esp_now_set_pmk(_esp_pmk)); - // Add broadcast peer information to peer list - add_peer(espnow_broadcast_mac); - return true; - }; - - uint16_t receive_frame(uint8_t *data, uint16_t max_length) { - // see if there's any received data waiting - espnow_packet_t packet; - if(xQueueReceive(espnow_recv_queue, &packet, 0) == pdTRUE) { - if(packet.data_len >= 4) { - uint8_t len = packet.data_len - 4; - if( - (packet.data[0] ^ len) != _magic_header[0] || - (packet.data[1] ^ len) != _magic_header[1] || - (packet.data[packet.data_len - 2] ^ len) != _magic_header[2] || - (packet.data[packet.data_len - 1] ^ len) != _magic_header[3] - ) { - ESP_LOGE(TAG, "magic mismatch"); - free(packet.data); - return PJON_FAIL; - } - if(len > max_length) { - free(packet.data); - ESP_LOGE(TAG, "buffer overflow - %d bytes but max is %d", len, max_length); - return PJON_FAIL; + void add_node_mac(uint8_t mac_addr[ESP_NOW_MAC_LENGTH]) { + add_peer(mac_addr); + }; + + void add_peer(uint8_t mac_addr[ESP_NOW_MAC_LENGTH]) { + if(esp_now_is_peer_exist(mac_addr)) + return; + + #if defined(ESP8266) // TODO: Fix encryption! ESP_NOW_ROLE_SLAVE doesn't seem to matter. From the doc: The peer's Role does not affect any function, but only stores the Role information for the application layer. + esp_now_add_peer(mac_addr, ESP_NOW_ROLE_SLAVE,_channel, NULL, 0); + + #elif defined(ESP32) // TODO: Why malloc a local variable? Add broadcast peer information to peer list. + esp_now_peer_info_t *peer = (esp_now_peer_info_t *)malloc(sizeof(esp_now_peer_info_t)); + + memset(peer, 0, sizeof(esp_now_peer_info_t)); + peer->channel = _channel; + peer->ifidx = ESPNOW_WIFI_IF; + if( (memcmp(mac_addr, espnow_broadcast_mac, ESP_NOW_MAC_LENGTH) == 0)) + peer->encrypt = false; + // else { peer->encrypt = true; memcpy(peer->lmk, _esp_pmk, 16); } + memcpy(peer->peer_addr, mac_addr, ESP_NOW_MAC_LENGTH); + esp_now_add_peer(peer); + free(peer); + #endif + }; + + bool begin(uint8_t channel, uint8_t *espnow_pmk) { + #if defined(ESP32) // TODO: Is this necessary? + esp_err_t ret = nvs_flash_init(); + if(ret == ESP_ERR_NVS_NO_FREE_PAGES) { + ESP_ERROR_CHECK(nvs_flash_erase()); + ret = nvs_flash_init(); + } + ESP_ERROR_CHECK(ret); + #endif + + _channel = channel; + memcpy(_esp_pmk, espnow_pmk, 16); + + #ifdef ESP8266 + WiFi.persistent(false); + + if (ESPNOW_WIFI_MODE == WIFI_AP) { + WiFi.mode(WIFI_AP); + WiFi.softAP("ESPNOW", nullptr, _channel); + WiFi.softAPdisconnect(false); + } + else { + WiFi.mode(WIFI_STA); + WiFi.begin("ESPNOW", nullptr, _channel); + WiFi.disconnect(false); + } + + esp_now_init(); + esp_now_set_self_role(ESPNOW_WIFI_ROLE); + esp_now_register_send_cb(reinterpret_cast(espnow_send_cb)); + esp_now_register_recv_cb(reinterpret_cast(espnow_recv_cb)); + #else + tcpip_adapter_init(); + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + esp_wifi_init(&cfg); + //esp_wifi_set_country(&wifi_country); + esp_wifi_set_storage(WIFI_STORAGE_RAM); + esp_wifi_set_mode(ESPNOW_WIFI_MODE); + + // These two steps are required BEFORE the channel can be set + esp_wifi_start(); + esp_wifi_set_promiscuous(true); + + esp_wifi_set_channel(_channel, WIFI_SECOND_CHAN_NONE); + esp_now_init(); + esp_now_register_send_cb(reinterpret_cast(espnow_send_cb)); + esp_now_register_recv_cb(reinterpret_cast(espnow_recv_cb)); + ESP_ERROR_CHECK(esp_now_set_pmk(_esp_pmk)); + #endif + + add_peer(espnow_broadcast_mac); // Add broadcast peer information to peer list + + return true; + }; + + uint16_t receive_frame(uint8_t *data, uint16_t max_length) { + return packetQueue.pop(last_mac, data, max_length); + }; + + void send_frame(uint8_t *data, uint16_t length, uint8_t dest_mac[ESP_NOW_MAC_LENGTH]) { + if (!sendingDone) { + // we are still sending the previous frame - discard this one + return; + } + + if (length > PJON_PACKET_MAX_LENGTH) { + // the data is too long + return; + } + + uint8_t packet[PJON_PACKET_MAX_LENGTH + ESPNOW_PACKET_HEADER_LENGTH + ESPNOW_PACKET_FOOTER_LENGTH]; + + uint8_t len = length; + packet[0] = ESP_NOW_MAGIC_HEADER[0] ^ len; + packet[1] = ESP_NOW_MAGIC_HEADER[1] ^ len; + memcpy(packet + ESPNOW_PACKET_HEADER_LENGTH, data, len); + packet[len + 2] = ESP_NOW_MAGIC_HEADER[2] ^ len; + packet[len + 3] = ESP_NOW_MAGIC_HEADER[3] ^ len; + + noInterrupts(); + { + sendingDone = false; } - memcpy(data, packet.data + 2, len); - free(packet.data); - // Update last mac received from - memcpy(last_mac, packet.mac_addr, ESP_NOW_ETH_ALEN); - return len; - } else { - ESP_LOGE(TAG, "packet < 4 received"); - free(packet.data); - return PJON_FAIL; // No data waiting - } - } - return PJON_FAIL; - }; - - void send_frame( - uint8_t *data, - uint16_t length, - uint8_t dest_mac[ESP_NOW_ETH_ALEN] - ) { - uint8_t packet[ESPNOW_MAX_PACKET]; - if(length + 4 > ESPNOW_MAX_PACKET) { - ESP_LOGE(TAG, "Packet send error - too long :%d", length + 4); - return; - } - uint8_t len = length; - packet[0] = _magic_header[0] ^ len; - packet[1] = _magic_header[1] ^ len; - memcpy(packet + 2, data, len); - packet[len + 2] = _magic_header[2] ^ len; - packet[len + 3] = _magic_header[3] ^ len; - if(esp_now_send(dest_mac, packet, len + 4) != ESP_OK) - ESP_LOGE(TAG, "Send error"); - else // Wait for notification that the data has been received by the MAC - ulTaskNotifyTake(pdTRUE, portMAX_DELAY); - }; + interrupts(); - void send_response(uint8_t response) { - send_frame(&response, 1, last_mac); + if(esp_now_send(dest_mac, packet, len + ESPNOW_PACKET_HEADER_LENGTH + ESPNOW_PACKET_FOOTER_LENGTH) == 0) { + do { // wait for sending finished interrupt to be called + yield(); + } while(!sendingDone); + } }; - void send_frame(uint8_t *data, uint16_t length) { - // Broadcast - send_frame(data, length, espnow_broadcast_mac); - }; + void send_response(uint8_t response) { + send_frame(&response, 1, last_mac); + }; - void set_magic_header(uint8_t *magic_header) { - memcpy(_magic_header, magic_header, 4); - }; + void send_frame(uint8_t *data, uint16_t length) { // Broadcast + send_frame(data, length, espnow_broadcast_mac); + }; - void get_sender(uint8_t *ip) { - memcpy(ip, last_mac, ESP_NOW_ETH_ALEN); - }; + void get_sender(uint8_t *ip) { + memcpy(ip, last_mac, ESP_NOW_MAC_LENGTH); + }; }; diff --git a/src/interfaces/ARDUINO/PacketQueue.h b/src/interfaces/ARDUINO/PacketQueue.h new file mode 100644 index 0000000000..10024c2bad --- /dev/null +++ b/src/interfaces/ARDUINO/PacketQueue.h @@ -0,0 +1,128 @@ +#pragma once + +#if defined(ESP8266) + #include + #include +#elif defined(ESP32) + #include +#endif + +#include +#include "PJONDefines.h" + +#define ESP_NOW_MAC_LENGTH 6 +#define ESPNOW_PACKET_HEADER_LENGTH 2 +#define ESPNOW_PACKET_FOOTER_LENGTH 2 +uint8_t* const ESP_NOW_MAGIC_HEADER = (uint8_t*)"\xEE\xFE\x0E\xEF"; + +class EspNowPacket +{ + private: + uint8_t mac_address[ESP_NOW_MAC_LENGTH]; + uint8_t data[PJON_PACKET_MAX_LENGTH + ESPNOW_PACKET_HEADER_LENGTH + ESPNOW_PACKET_FOOTER_LENGTH]; + int data_len; + + public: + void set(const uint8_t *mac_address, const uint8_t *data, int len) { + memcpy(this->mac_address, mac_address, ESP_NOW_MAC_LENGTH); + memcpy(this->data, data, len); + this->data_len = len; + } + + uint16_t checkAndGet(uint8_t *out_mac_address, uint8_t *out_data, uint16_t max_length) { + if (data_len < ESPNOW_PACKET_HEADER_LENGTH + ESPNOW_PACKET_FOOTER_LENGTH) { + // The packet is too small + return PJON_FAIL; + } + + uint8_t len = data_len - ESPNOW_PACKET_HEADER_LENGTH - ESPNOW_PACKET_FOOTER_LENGTH; + + if( + (data[0] ^ len) != ESP_NOW_MAGIC_HEADER[0] || + (data[1] ^ len) != ESP_NOW_MAGIC_HEADER[1] || + (data[data_len - 2] ^ len) != ESP_NOW_MAGIC_HEADER[2] || + (data[data_len - 1] ^ len) != ESP_NOW_MAGIC_HEADER[3] + ) { + // Magic mismatch + return PJON_FAIL; + } + + if (len > max_length) { + // Too short buffer + return PJON_FAIL; + } + + memcpy(out_mac_address, mac_address, ESP_NOW_MAC_LENGTH); + memcpy(out_data, data + ESPNOW_PACKET_HEADER_LENGTH, len); + + return len; + } +}; + +/** + * A queue of incoming packets of max capacity PJON_MAX_PACKETS. + * The queue should be atomic (not tested). + * This was done to remove a dependency on FreeRTOS - replacement of xQueueHandle (and related functions). + */ +class PacketQueue +{ + private: + // 1 extra capacity is necessary to distinguisch between the queue being full and empty + EspNowPacket queue[PJON_MAX_PACKETS + 1]; + volatile uint8_t firstElement = 0; + volatile uint8_t firstSpace = 0; + + public: + /** + * Adds a packet to the queue. + * @return true if successfull, false if the queue was full and the packet was not added. + */ + bool push(const uint8_t *mac_addr, const uint8_t *data, int len); + + /** + * Remove the first packet from the queue, check it for length, remove ESPNOW headers and return it. + * @param out_mac_address - pass in a buffer, the method will copy the sender's MAC address into it. + * @param out_data - pass in an allocated buffer, the method will copy the data into it. + * @param max_length - length of the allocated buffer. + * @return length of the data or PJON_FAIL in case the queue is empty or there was an error + */ + uint16_t pop(uint8_t *out_mac_address, uint8_t *out_data, uint16_t max_length); +}; + +bool PacketQueue::push(const uint8_t *mac_addr, const uint8_t *data, int len) +{ + bool isFull; + + noInterrupts(); + { + int firstSpacePlus1 = (firstSpace + 1) % (PJON_MAX_PACKETS + 1); + isFull = firstSpacePlus1 == firstElement; + if (!isFull) + { + queue[firstSpace].set(mac_addr, data, len); + firstSpace = firstSpacePlus1; + } + } + interrupts(); + + return !isFull; +} + +uint16_t PacketQueue::pop(uint8_t *out_mac_address, uint8_t *out_data, uint16_t max_length) { + if (firstElement == firstSpace) { + return PJON_FAIL; + } + else + { + uint16_t length; + + noInterrupts(); + { + length = queue[firstElement].checkAndGet(out_mac_address, out_data, max_length); + firstElement = (firstElement + 1) % (PJON_MAX_PACKETS + 1); + } + interrupts(); + + return length; + } +} \ No newline at end of file diff --git a/src/strategies/ESPNOW/ESPNOW.h b/src/strategies/ESPNOW/ESPNOW.h index d5696c5560..6eee627fa5 100644 --- a/src/strategies/ESPNOW/ESPNOW.h +++ b/src/strategies/ESPNOW/ESPNOW.h @@ -36,7 +36,7 @@ #define EN_RECEIVE_TIME 0 #endif -#define EN_MAGIC_HEADER (uint8_t*)"\xEE\xFE\x0E\xEF" + class ESPNOW { bool _espnow_initialised = false; @@ -48,13 +48,12 @@ class ESPNOW { // Remote nodes uint8_t _remote_node_count = 0; uint8_t _remote_id[EN_MAX_REMOTE_NODES]; - uint8_t _remote_mac[EN_MAX_REMOTE_NODES][ESP_NOW_ETH_ALEN]; + uint8_t _remote_mac[EN_MAX_REMOTE_NODES][ESP_NOW_MAC_LENGTH]; ENHelper en; bool check_en() { if(!_espnow_initialised) { - en.set_magic_header(EN_MAGIC_HEADER); if(en.begin(_channel,(uint8_t*)_espnow_pmk)) _espnow_initialised = true; } @@ -76,31 +75,37 @@ class ESPNOW { PJONTools::parse_header(message, packet_info); uint8_t sender_id = packet_info.tx.id; if(sender_id == 0) { - ESP_LOGE("ESPNOW", "AutoRegister parsing failed"); + #if defined(ESP32) + ESP_LOGE("ESPNOW", "AutoRegister parsing failed"); + #endif return; // If parsing fails, it will be 0 } // Then get the mac address of the sender - uint8_t sender_mac[ESP_NOW_ETH_ALEN]; + uint8_t sender_mac[ESP_NOW_MAC_LENGTH]; en.get_sender(sender_mac); // See if PJON id is already registered, add if not int16_t pos = find_remote_node(sender_id); if(pos == -1) { - ESP_LOGI("ESPNOW", "Autoregister new sender %d",sender_id); + #if defined(ESP32) + ESP_LOGI("ESPNOW", "Autoregister new sender %d",sender_id); + #endif add_node(sender_id, sender_mac); } - else if(memcmp(_remote_mac[pos], sender_mac, ESP_NOW_ETH_ALEN) != 0) { + else if(memcmp(_remote_mac[pos], sender_mac, ESP_NOW_MAC_LENGTH) != 0) { // Update mac of existing node - ESP_LOGI( - "ESPNOW", - "Update sender mac %d %d:%d:%d", - sender_id, - sender_mac[1], - sender_mac[2], - sender_mac[3] - ); - memcpy(_remote_mac[pos], sender_mac, ESP_NOW_ETH_ALEN); + #if defined(ESP32) + ESP_LOGI( + "ESPNOW", + "Update sender mac %d %d:%d:%d", + sender_id, + sender_mac[1], + sender_mac[2], + sender_mac[3] + ); + #endif + memcpy(_remote_mac[pos], sender_mac, ESP_NOW_MAC_LENGTH); } } }; @@ -123,7 +128,7 @@ class ESPNOW { ) { if(_remote_node_count == EN_MAX_REMOTE_NODES) return -1; _remote_id[_remote_node_count] = remote_id; - memcpy(_remote_mac[_remote_node_count], remote_mac, ESP_NOW_ETH_ALEN); + memcpy(_remote_mac[_remote_node_count], remote_mac, ESP_NOW_MAC_LENGTH); en.add_node_mac(remote_mac); _remote_node_count++; return _remote_node_count - 1; @@ -195,6 +200,8 @@ class ESPNOW { if(result[0] == PJON_ACK) return result[0]; + yield(); + } while ((uint32_t)(PJON_MICROS() - start) < EN_RESPONSE_TIMEOUT); return PJON_FAIL; }; diff --git a/src/strategies/ESPNOW/README.md b/src/strategies/ESPNOW/README.md index 361c12a15c..edc73624de 100644 --- a/src/strategies/ESPNOW/README.md +++ b/src/strategies/ESPNOW/README.md @@ -4,7 +4,7 @@ |--------|-----------|--------------------| | ESPNOW over WiFi | NA | `#include ` | -With the `ESPNOW` PJON strategy, up to 10 ESP32 devices can use PJON to communicate with each other over +With the `ESPNOW` PJON strategy, up to 10 ESP32 or ESP8266 devices can use PJON to communicate with each other over the [Espressif ESPNOW protocol](https://www.espressif.com/en/products/software/esp-now/overview) (peer-to-peer 802.11). PJON over ESPNOW has the following benefits: @@ -38,6 +38,13 @@ void setup() { } ``` +You can also choose between Access Point and Station mode. To switch to Station mode, define CONFIG_STATION_MODE before +including PJONESPNOW.h. +```cpp + #define CONFIG_STATION_MODE + #include +``` + The ESPNOW strategy will send a broadcast message if the device_id is not already registered. Once a response is received (assuming auto-registration is enabled) the device automatically adds the node id and mac in its table. Sender auto-registration is enabled by default and can be disabled using the following setter: ```cpp