From 0a3a53db503d13bc9dbc61d431f8a5ca1989f49c Mon Sep 17 00:00:00 2001 From: aleigh Date: Mon, 25 Apr 2022 14:18:42 -0700 Subject: [PATCH] WIP on the ESP32 modules --- leigh-esp32-arduinocore/.gitignore | 5 + .../ESPAsyncE131/.github/workflows/build.yaml | 56 ++++++ .../lib/ESPAsyncE131/Changelog.md | 13 ++ .../lib/ESPAsyncE131/ESPAsyncE131.cpp | 181 +++++++++++++++++ .../lib/ESPAsyncE131/ESPAsyncE131.h | 175 ++++++++++++++++ leigh-esp32-cube/lib/ESPAsyncE131/README.md | 51 +++++ leigh-esp32-cube/lib/ESPAsyncE131/RingBuf.c | 186 ++++++++++++++++++ leigh-esp32-cube/lib/ESPAsyncE131/RingBuf.h | 143 ++++++++++++++ .../examples/E131_Test/E131_Test.ino | 77 ++++++++ .../lib/ESPAsyncE131/keywords.txt | 12 ++ .../lib/ESPAsyncE131/library.json | 25 +++ .../lib/ESPAsyncE131/library.properties | 10 + leigh-esp32-cube/platformio.ini | 2 +- leigh-esp32-cube/src/main.c | 3 + 14 files changed, 938 insertions(+), 1 deletion(-) create mode 100644 leigh-esp32-arduinocore/.gitignore create mode 100644 leigh-esp32-cube/lib/ESPAsyncE131/.github/workflows/build.yaml create mode 100644 leigh-esp32-cube/lib/ESPAsyncE131/Changelog.md create mode 100644 leigh-esp32-cube/lib/ESPAsyncE131/ESPAsyncE131.cpp create mode 100644 leigh-esp32-cube/lib/ESPAsyncE131/ESPAsyncE131.h create mode 100644 leigh-esp32-cube/lib/ESPAsyncE131/README.md create mode 100644 leigh-esp32-cube/lib/ESPAsyncE131/RingBuf.c create mode 100644 leigh-esp32-cube/lib/ESPAsyncE131/RingBuf.h create mode 100644 leigh-esp32-cube/lib/ESPAsyncE131/examples/E131_Test/E131_Test.ino create mode 100644 leigh-esp32-cube/lib/ESPAsyncE131/keywords.txt create mode 100644 leigh-esp32-cube/lib/ESPAsyncE131/library.json create mode 100644 leigh-esp32-cube/lib/ESPAsyncE131/library.properties diff --git a/leigh-esp32-arduinocore/.gitignore b/leigh-esp32-arduinocore/.gitignore new file mode 100644 index 000000000..89cc49cbd --- /dev/null +++ b/leigh-esp32-arduinocore/.gitignore @@ -0,0 +1,5 @@ +.pio +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch diff --git a/leigh-esp32-cube/lib/ESPAsyncE131/.github/workflows/build.yaml b/leigh-esp32-cube/lib/ESPAsyncE131/.github/workflows/build.yaml new file mode 100644 index 000000000..9a95f19e0 --- /dev/null +++ b/leigh-esp32-cube/lib/ESPAsyncE131/.github/workflows/build.yaml @@ -0,0 +1,56 @@ +name: ESPAsyncE131 CI +on: [ push, pull_request ] +env: + ESP8266_PLAT: 'esp8266:esp8266@3.0.2' + ESP8266_FQBN: 'esp8266:esp8266:generic:xtal=160,vt=flash,exception=disabled,ResetMethod=nodemcu,CrystalFreq=26,FlashFreq=40,FlashMode=dio,eesz=4M2M,led=2,ip=hb2f,dbg=Disabled' + ESP32_PLAT: 'esp32:esp32@1.0.6' + ESP32_FQBN: 'esp32:esp32:d32_pro:PSRAM=enabled,PartitionScheme=default,FlashFreq=80,DebugLevel=none' + +jobs: + build: + runs-on: ubuntu-latest + steps: + # Checkout Ourself + - uses: actions/checkout@v2 + + # Arduino stuff + - uses: arduino/setup-arduino-cli@v1 + - name: Update board indexes and set paths + run: | + arduino-cli config init + arduino-cli config add board_manager.additional_urls https://arduino.esp8266.com/stable/package_esp8266com_index.json https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json + arduino-cli config set directories.user ~/arduino + - name: Install platforms + run: | + arduino-cli core install $ESP8266_PLAT + arduino-cli core install $ESP32_PLAT + + # Grab all the required libraries + # Checkout won't allow paths outside of our workspace, so we put them in alib and move later. + - name: Checkout ESPAsyncE131 + uses: actions/checkout@v2 + with: + repository: forkineye/ESPAsyncE131 + path: alib/ESPAsyncE131 + + - name: Checkout ESPAsyncUDP + uses: actions/checkout@v2 + with: + repository: me-no-dev/ESPAsyncUDP + path: alib/ESPAsyncUDP + + # Move Arduino libraries where they need to be. + - name: Move Arduino Libraries + run: | + mkdir -p ~/arduino/libraries + mv alib/* ~/arduino/libraries + rmdir alib + + # Build examples + - name: Compile for ESP8266 + run: | + arduino-cli compile --fqbn $ESP8266_FQBN examples/E131_Test/E131_Test.ino + + - name: Compile for ESP32 + run: | + arduino-cli compile --fqbn $ESP32_FQBN examples/E131_Test/E131_Test.ino diff --git a/leigh-esp32-cube/lib/ESPAsyncE131/Changelog.md b/leigh-esp32-cube/lib/ESPAsyncE131/Changelog.md new file mode 100644 index 000000000..7638df127 --- /dev/null +++ b/leigh-esp32-cube/lib/ESPAsyncE131/Changelog.md @@ -0,0 +1,13 @@ +# Changelog + +### 1.0.2 + +- Added callback support + +### 1.0.1 + +- Added ESP32 support. + +### 1.0.0 + +- Initial release. diff --git a/leigh-esp32-cube/lib/ESPAsyncE131/ESPAsyncE131.cpp b/leigh-esp32-cube/lib/ESPAsyncE131/ESPAsyncE131.cpp new file mode 100644 index 000000000..d5655f83c --- /dev/null +++ b/leigh-esp32-cube/lib/ESPAsyncE131/ESPAsyncE131.cpp @@ -0,0 +1,181 @@ +/* +* ESPAsyncE131.cpp +* +* Project: ESPAsyncE131 - Asynchronous E.131 (sACN) library for Arduino ESP8266 and ESP32 +* Copyright (c) 2019 Shelby Merrick +* http://www.forkineye.com +* +* This program is provided free for you to use in any way that you wish, +* subject to the laws and regulations where you are using it. Due diligence +* is strongly suggested before using this code. Please give credit where due. +* +* The Author makes no warranty of any kind, express or implied, with regard +* to this program or the documentation contained in this document. The +* Author shall not be liable in any event for incidental or consequential +* damages in connection with, or arising out of, the furnishing, performance +* or use of these programs. +* +*/ + +#include "ESPAsyncE131.h" +#include + +// E1.17 ACN Packet Identifier +const byte ESPAsyncE131::ACN_ID[12] = { 0x41, 0x53, 0x43, 0x2d, 0x45, 0x31, 0x2e, 0x31, 0x37, 0x00, 0x00, 0x00 }; + +// Constructor +ESPAsyncE131::ESPAsyncE131(uint8_t buffers) { + + pbuff = nullptr; + if (buffers) + { + pbuff = RingBuf_new (sizeof (e131_packet_t), buffers); + } + + stats.num_packets = 0; + stats.packet_errors = 0; +} + +///////////////////////////////////////////////////////// +// +// Public begin() members +// +///////////////////////////////////////////////////////// + +bool ESPAsyncE131::begin(e131_listen_t type, uint16_t universe, uint8_t n) +{ + return begin (type, E131_ListenPort, universe, n); +} + +bool ESPAsyncE131::begin (e131_listen_t type, ESPAsyncE131PortId UdpPortId, uint16_t universe, uint8_t n) +{ + bool success = false; + + E131_ListenPort = UdpPortId; + + if (type == E131_UNICAST) + success = initUnicast (); + if (type == E131_MULTICAST) + success = initMulticast (universe, n); + + return success; +} + +///////////////////////////////////////////////////////// +// +// Private init() members +// +///////////////////////////////////////////////////////// + +bool ESPAsyncE131::initUnicast() { + bool success = false; + delay(100); + + if (udp.listen(E131_ListenPort)) { + udp.onPacket(std::bind(&ESPAsyncE131::parsePacket, this, + std::placeholders::_1)); + success = true; + } + return success; +} + +bool ESPAsyncE131::initMulticast(uint16_t universe, uint8_t n) { + bool success = false; + delay(100); + + IPAddress address = IPAddress(239, 255, ((universe >> 8) & 0xff), + ((universe >> 0) & 0xff)); + + if (udp.listenMulticast(address, E131_ListenPort)) { + ip4_addr_t ifaddr; + ip4_addr_t multicast_addr; + + ifaddr.addr = static_cast(WiFi.localIP()); + for (uint8_t i = 1; i < n; i++) { + multicast_addr.addr = static_cast(IPAddress(239, 255, + (((universe + i) >> 8) & 0xff), (((universe + i) >> 0) + & 0xff))); + igmp_joingroup(&ifaddr, &multicast_addr); + } + + udp.onPacket(std::bind(&ESPAsyncE131::parsePacket, this, + std::placeholders::_1)); + + success = true; + } + return success; +} + +///////////////////////////////////////////////////////// +// +// Packet parsing - Private +// +///////////////////////////////////////////////////////// + +void ESPAsyncE131::parsePacket(AsyncUDPPacket _packet) { + e131_error_t error = ERROR_NONE; + + sbuff = reinterpret_cast(_packet.data()); + if (memcmp(sbuff->acn_id, ESPAsyncE131::ACN_ID, sizeof(sbuff->acn_id))) + error = ERROR_ACN_ID; + if (htonl(sbuff->root_vector) != ESPAsyncE131::VECTOR_ROOT) + error = ERROR_VECTOR_ROOT; + if (htonl(sbuff->frame_vector) != ESPAsyncE131::VECTOR_FRAME) + error = ERROR_VECTOR_FRAME; + if (sbuff->dmp_vector != ESPAsyncE131::VECTOR_DMP) + error = ERROR_VECTOR_DMP; + if (sbuff->property_values[0] != 0) + error = ERROR_IGNORE; + + + if (!error) { + if (PacketCallback) { (*PacketCallback) (sbuff, UserInfo); } + if (pbuff) { pbuff->add (pbuff, sbuff); } + + stats.num_packets++; + stats.last_clientIP = _packet.remoteIP(); + stats.last_clientPort = _packet.remotePort(); + stats.last_seen = millis(); + } else if (error == ERROR_IGNORE) { + // Do nothing + } else { + if (Serial) + dumpError(error); + stats.packet_errors++; + } +} + +///////////////////////////////////////////////////////// +// +// Debugging functions - Public +// +///////////////////////////////////////////////////////// + +void ESPAsyncE131::dumpError(e131_error_t error) { + switch (error) { + case ERROR_ACN_ID: + Serial.print(F("INVALID PACKET ID: ")); + for (uint i = 0; i < sizeof(ACN_ID); i++) + Serial.print(sbuff->acn_id[i], HEX); + Serial.println(""); + break; + case ERROR_PACKET_SIZE: + Serial.println(F("INVALID PACKET SIZE: ")); + break; + case ERROR_VECTOR_ROOT: + Serial.print(F("INVALID ROOT VECTOR: 0x")); + Serial.println(htonl(sbuff->root_vector), HEX); + break; + case ERROR_VECTOR_FRAME: + Serial.print(F("INVALID FRAME VECTOR: 0x")); + Serial.println(htonl(sbuff->frame_vector), HEX); + break; + case ERROR_VECTOR_DMP: + Serial.print(F("INVALID DMP VECTOR: 0x")); + Serial.println(sbuff->dmp_vector, HEX); + case ERROR_NONE: + break; + case ERROR_IGNORE: + break; + } +} diff --git a/leigh-esp32-cube/lib/ESPAsyncE131/ESPAsyncE131.h b/leigh-esp32-cube/lib/ESPAsyncE131/ESPAsyncE131.h new file mode 100644 index 000000000..8353a6358 --- /dev/null +++ b/leigh-esp32-cube/lib/ESPAsyncE131/ESPAsyncE131.h @@ -0,0 +1,175 @@ +/* +* ESPAsyncE131.h +* +* Project: ESPAsyncE131 - Asynchronous E.131 (sACN) library for Arduino ESP8266 and ESP32 +* Copyright (c) 2019 Shelby Merrick +* http://www.forkineye.com +* +* This program is provided free for you to use in any way that you wish, +* subject to the laws and regulations where you are using it. Due diligence +* is strongly suggested before using this code. Please give credit where due. +* +* The Author makes no warranty of any kind, express or implied, with regard +* to this program or the documentation contained in this document. The +* Author shall not be liable in any event for incidental or consequential +* damages in connection with, or arising out of, the furnishing, performance +* or use of these programs. +* +*/ + +#ifndef ESPASYNCE131_H_ +#define ESPASYNCE131_H_ + +#ifdef ESP32 +#include +#include +#elif defined (ESP8266) +#include +#include +#include +#else +#error Platform not supported +#endif + +#include +#include +#include +#include "RingBuf.h" + +#if LWIP_VERSION_MAJOR == 1 +typedef struct ip_addr ip4_addr_t; +#endif + +// Defaults +#define E131_DEFAULT_PORT 5568 + +// E1.31 Packet Offsets +#define E131_ROOT_PREAMBLE_SIZE 0 +#define E131_ROOT_POSTAMBLE_SIZE 2 +#define E131_ROOT_ID 4 +#define E131_ROOT_FLENGTH 16 +#define E131_ROOT_VECTOR 18 +#define E131_ROOT_CID 22 + +#define E131_FRAME_FLENGTH 38 +#define E131_FRAME_VECTOR 40 +#define E131_FRAME_SOURCE 44 +#define E131_FRAME_PRIORITY 108 +#define E131_FRAME_RESERVED 109 +#define E131_FRAME_SEQ 111 +#define E131_FRAME_OPT 112 +#define E131_FRAME_UNIVERSE 113 + +#define E131_DMP_FLENGTH 115 +#define E131_DMP_VECTOR 117 +#define E131_DMP_TYPE 118 +#define E131_DMP_ADDR_FIRST 119 +#define E131_DMP_ADDR_INC 121 +#define E131_DMP_COUNT 123 +#define E131_DMP_DATA 125 + +// E1.31 Packet Structure +typedef union { + struct { + // Root Layer + uint16_t preamble_size; + uint16_t postamble_size; + uint8_t acn_id[12]; + uint16_t root_flength; + uint32_t root_vector; + uint8_t cid[16]; + + // Frame Layer + uint16_t frame_flength; + uint32_t frame_vector; + uint8_t source_name[64]; + uint8_t priority; + uint16_t reserved; + uint8_t sequence_number; + uint8_t options; + uint16_t universe; + + // DMP Layer + uint16_t dmp_flength; + uint8_t dmp_vector; + uint8_t type; + uint16_t first_address; + uint16_t address_increment; + uint16_t property_value_count; + uint8_t property_values[513]; + } __attribute__((packed)); + + uint8_t raw[638]; +} e131_packet_t; + +// Error Types +typedef enum { + ERROR_NONE, + ERROR_IGNORE, + ERROR_ACN_ID, + ERROR_PACKET_SIZE, + ERROR_VECTOR_ROOT, + ERROR_VECTOR_FRAME, + ERROR_VECTOR_DMP +} e131_error_t; + +// E1.31 Listener Types +typedef enum { + E131_UNICAST, + E131_MULTICAST +} e131_listen_t; + +// Status structure +typedef struct { + uint32_t num_packets; + uint32_t packet_errors; + IPAddress last_clientIP; + uint16_t last_clientPort; + unsigned long last_seen; +} e131_stats_t; + +typedef uint16_t ESPAsyncE131PortId; + +class ESPAsyncE131 { + private: + // Constants for packet validation + static const uint8_t ACN_ID[]; + static const uint32_t VECTOR_ROOT = 4; + static const uint32_t VECTOR_FRAME = 2; + static const uint8_t VECTOR_DMP = 2; + + e131_packet_t *sbuff; // Pointer to scratch packet buffer + AsyncUDP udp; // AsyncUDP + RingBuf *pbuff; // Ring Buffer of universe packet buffers + void * UserInfo = nullptr; + + // Internal Initializers + bool initUnicast(); + bool initMulticast(uint16_t universe, uint8_t n = 1); + + // Packet parser callback + void parsePacket(AsyncUDPPacket _packet); + + void (*PacketCallback)(e131_packet_t* ReceivedData, void* UserInfo) = nullptr; + ESPAsyncE131PortId E131_ListenPort = E131_DEFAULT_PORT; + + public: + e131_stats_t stats; // Statistics tracker + ESPAsyncE131(uint8_t buffers = 1); + + // Generic UDP listener, no physical or IP configuration + bool begin (e131_listen_t type, uint16_t universe = 1, uint8_t n = 1); + bool begin (e131_listen_t type, ESPAsyncE131PortId UdpPortId, uint16_t universe, uint8_t n); + + // Ring buffer access + inline bool isEmpty() { return pbuff->isEmpty(pbuff); } + inline void *pull(e131_packet_t *packet) { return pbuff->pull(pbuff, packet); } + + // Callback support + void registerCallback(void* _UserInfo, void (*cbFunction)(e131_packet_t*, void*)) { PacketCallback = cbFunction; UserInfo = _UserInfo; } + + // Diag functions + void dumpError(e131_error_t error); +}; + +#endif // ESPASYNCE131_H_ diff --git a/leigh-esp32-cube/lib/ESPAsyncE131/README.md b/leigh-esp32-cube/lib/ESPAsyncE131/README.md new file mode 100644 index 000000000..39fa8e448 --- /dev/null +++ b/leigh-esp32-cube/lib/ESPAsyncE131/README.md @@ -0,0 +1,51 @@ +# ESPAsyncE131 - Asynchronous E.131 (sACN) library for Arduino ESP8266 and ESP32 + +[![Build Status](https://github.com/forkineye/ESPAsyncE131/actions/workflows/build.yaml/badge.svg)](https://github.com/forkineye/ESPAsyncE131/actions/workflows/build.yaml) + +This library is to simplify the validation and handling of E1.31 sACN (DMX over Ethernet) traffic on Arduino ESP8266 and +ESP32 based platforms. It supports both Unicast and Multicast configurations, exposing the full E1.31 packet to the +user. If you require support for traditional Arduino devices or polling modes, please refer to +the [E131](https://github.com/forkineye/E131) library from which this project was derived from. + +## Requirements + +Development is always done against the latest git pulls for the ESP8266 and ESP32 cores. If you are experiencing issues +with your current Arduino core, consider updating it. + +### ESP8266 Platforms + +- [Adruino for ESP8266](https://github.com/esp8266/Arduino) - Arduino core for ESP8266 +- [ESPAsyncUDP](https://github.com/me-no-dev/ESPAsyncUDP) - Asynchronous UDP Library + +### ESP32 Platforms + +- [Arduino for ESP32](https://github.com/espressif/arduino-esp32) - Arduino core for ESP32 + +## API / Usage + +### Notes + +- All incoming E1.31 packets are put into a ring buffer once validated. You access incoming packets by checking the ring + buffer and pulling packets as needed. +- WiFi connection attempts will timeout after 10 seconds if a successful connection has not been + established. ```WIFI_CONNECT_TIMEOUT``` can be changed in ```ESPAsyncE131.h```. + +### Constructor + +- ```ESPAsyncE131(uint8_t buffers = 1)```: Creates a new ESPAsyncE131 instance and allocates a ring buffer with X buffer + slots. You'll want at least 1 buffer for each Universe that may reach your device. + +### Member Functions and Exposed Data Structures + +- ```bool begin(e131_listen_t type, uint16_t universe = 1)```: This should be called from within ```setup()``` or + whenever you need to re-configure your E1.31 listener. Valid ```type```s are ```E131_UNICAST``` + and ```E131_MULTICAST```. ```universe``` is optional and only used for Multicast configuration. Returns true if + successful. +- ```bool isEmpty()```: Returns if the internal ring buffer is empty +- ```void *pull(e131_packet_t *packet)```: Pulls oldest packet from the ring buffer and copies it to ```packet``` +- ```e131_stats_t stats```: E1.31 Statistics + +### Resources + +- Latest code: +- Other Stuff: \ No newline at end of file diff --git a/leigh-esp32-cube/lib/ESPAsyncE131/RingBuf.c b/leigh-esp32-cube/lib/ESPAsyncE131/RingBuf.c new file mode 100644 index 000000000..42b466c36 --- /dev/null +++ b/leigh-esp32-cube/lib/ESPAsyncE131/RingBuf.c @@ -0,0 +1,186 @@ +/* + RingBuf.c - Library for implementing a simple Ring Buffer on Arduino boards. + Created by D. Aaron Wisner (daw268@cornell.edu) + January 17, 2015. + Released into the public domain. +*/ +#include "RingBuf.h" +#include + +/////// Constructor ////////// +RingBuf *RingBuf_new(int size, int len) +{ + RingBuf *self = (RingBuf *)malloc(sizeof(RingBuf)); + if (!self) return NULL; + memset(self, 0, sizeof(RingBuf)); + if (RingBuf_init(self, size, len) < 0) + { + free(self); + return NULL; + } + return self; +} + +int RingBuf_init(RingBuf *self, int size, int len) +{ + self->buf = (unsigned char *)malloc(size*len); + if (!self->buf) return -1; + memset(self->buf, 0, size*len); + + self->size = size; + self->len = len; + self->start = 0; + self->end = 0; + self->elements = 0; + + self->next_end_index = &RingBufNextEndIndex; + self->incr_end_index = &RingBufIncrEnd; + self->incr_start_index = &RingBufIncrStart; + self->isFull = &RingBufIsFull; + self->isEmpty = &RingBufIsEmpty; + self->add = &RingBufAdd; + self->numElements = &RingBufNumElements; + self->peek = &RingBufPeek; + self->pull = &RingBufPull; + return 0; +} +/////// Deconstructor ////////// +int RingBuf_delete(RingBuf *self) +{ + free(self->buf); + free(self); + return 0; +} + +/////// PRIVATE METHODS ////////// + +// get next empty index +int RingBufNextEndIndex(RingBuf *self) +{ + //buffer is full + if (self->isFull(self)) return -1; + //if empty dont incriment + return (self->end+(unsigned int)!self->isEmpty(self))%self->len; +} + +// incriment index of RingBuf struct, only call if safe to do so +int RingBufIncrEnd(RingBuf *self) +{ + self->end = (self->end+1)%self->len; + return self->end; +} + + +// incriment index of RingBuf struct, only call if safe to do so +int RingBufIncrStart(RingBuf *self) +{ + self->start = (self->start+1)%self->len; + return self->start; +} + +/////// PUBLIC METHODS ////////// + +// Add an object struct to RingBuf +int RingBufAdd(RingBuf *self, const void *object) +{ + int index; + // Perform all atomic opertaions + RB_ATOMIC_START + { + index = self->next_end_index(self); + //if not full + if (index >= 0) + { + memcpy(self->buf + index*self->size, object, self->size); + if (!self->isEmpty(self)) self->incr_end_index(self); + self->elements++; + } + } + RB_ATOMIC_END + + return index; +} + +// Return pointer to num element, return null on empty or num out of bounds +void *RingBufPeek(RingBuf *self, unsigned int num) +{ + void *ret = NULL; + // Perform all atomic opertaions + RB_ATOMIC_START + { + //empty or out of bounds + if (self->isEmpty(self) || num > self->elements - 1) ret = NULL; + else ret = &self->buf[((self->start + num)%self->len)*self->size]; + } + RB_ATOMIC_END + + return ret; +} + +// Returns and removes first buffer element +void *RingBufPull(RingBuf *self, void *object) +{ + void *ret = NULL; + // Perform all atomic opertaions + RB_ATOMIC_START + { + if (self->isEmpty(self)) ret = NULL; + // Else copy Object + else + { + memcpy(object, self->buf+self->start*self->size, self->size); + self->elements--; + // don't incriment start if removing last element + if (!self->isEmpty(self)) self->incr_start_index(self); + ret = object; + } + } + RB_ATOMIC_END + + return ret; +} + +// Returns number of elemnts in buffer +unsigned int RingBufNumElements(RingBuf *self) +{ + unsigned int elements; + + // Perform all atomic opertaions + RB_ATOMIC_START + { + elements = self->elements; + } + RB_ATOMIC_END + + return elements; +} + +// Returns true if buffer is full +bool RingBufIsFull(RingBuf *self) +{ + bool ret; + + // Perform all atomic opertaions + RB_ATOMIC_START + { + ret = self->elements == self->len; + } + RB_ATOMIC_END + + return ret; +} + +// Returns true if buffer is empty +bool RingBufIsEmpty(RingBuf *self) +{ + bool ret; + + // Perform all atomic opertaions + RB_ATOMIC_START + { + ret = !self->elements; + } + RB_ATOMIC_END + + return ret; +} diff --git a/leigh-esp32-cube/lib/ESPAsyncE131/RingBuf.h b/leigh-esp32-cube/lib/ESPAsyncE131/RingBuf.h new file mode 100644 index 000000000..72645542f --- /dev/null +++ b/leigh-esp32-cube/lib/ESPAsyncE131/RingBuf.h @@ -0,0 +1,143 @@ +/* + RingBuf.h - Library for implementing a simple Ring Buffer on Arduino boards. + Created by D. Aaron Wisner (daw268@cornell.edu) + January 17, 2015. + Released into the public domain. +*/ +#ifndef RingBuf_h +#define RingBuf_h + +#ifdef ARDUINO + #include +#else + #include +#endif + +#ifndef __cplusplus +#ifndef bool + #define bool uint8_t +#endif +#endif + + +#ifdef ARDUINO + + #if defined(ARDUINO_ARCH_AVR) + #include + #define RB_ATOMIC_START ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { + #define RB_ATOMIC_END } + + + #elif defined(ARDUINO_ARCH_ESP8266) + #ifndef __STRINGIFY + #define __STRINGIFY(a) #a + #endif + + #ifndef xt_rsil + #define xt_rsil(level) (__extension__({uint32_t state; __asm__ __volatile__("rsil %0," __STRINGIFY(level) : "=a" (state)); state;})) + #endif + + #ifndef xt_wsr_ps + #define xt_wsr_ps(state) __asm__ __volatile__("wsr %0,ps; isync" :: "a" (state) : "memory") + #endif + + #define RB_ATOMIC_START do { uint32_t _savedIS = xt_rsil(15) ; + #define RB_ATOMIC_END xt_wsr_ps(_savedIS); } while(0); + + #else + #define RB_ATOMIC_START { + #define RB_ATOMIC_END } + #warning "This library only fully supports AVR and ESP8266 Boards." + #warning "Operations on the buffer in ISRs are not safe!" + #endif + +#else + #define RB_ATOMIC_START { + #define RB_ATOMIC_END } + #warning "Operations on the buffer in ISRs are not safe!" + #warning "Impliment RB_ATOMIC_START and RB_ATOMIC_END macros for safe ISR operation!" +#endif + + + +typedef struct RingBuf RingBuf; + +typedef struct RingBuf +{ + // Invariant: end and start is always in bounds + unsigned char *buf; + unsigned int len, size, start, end, elements; + + // Private: + int (*next_end_index) (RingBuf*); + int (*incr_end_index) (RingBuf*); + + int (*incr_start_index) (RingBuf*); + + //public: + // Returns true if full + bool (*isFull) (RingBuf*); + // Returns true if empty + bool (*isEmpty) (RingBuf*); + // Returns number of elemnts in buffer + unsigned int (*numElements)(RingBuf*); + // Add Event, Returns index where added in buffer, -1 on full buffer + int (*add) (RingBuf*, const void*); + // Returns pointer to nth element, NULL when nth element is empty + void *(*peek) (RingBuf*, unsigned int); + // Removes element and copies it to location pointed to by void * + // Returns pointer passed in, NULL on empty buffer + void *(*pull) (RingBuf*, void *); + +} RingBuf; + +#ifdef __cplusplus +extern "C" { +#endif + +RingBuf *RingBuf_new(int size, int len); +int RingBuf_init(RingBuf *self, int size, int len); +int RingBuf_delete(RingBuf *self); + +int RingBufNextEndIndex(RingBuf *self); +int RingBufIncrEnd(RingBuf *self); +int RingBufIncrStart(RingBuf *self); +int RingBufAdd(RingBuf *self, const void *object); +void *RingBufPeek(RingBuf *self, unsigned int num); +void *RingBufPull(RingBuf *self, void *object); +bool RingBufIsFull(RingBuf *self); +bool RingBufIsEmpty(RingBuf *self); +unsigned int RingBufNumElements(RingBuf *self); + +#ifdef __cplusplus +} +#endif + + +// For those of you who cant live without pretty C++ objects.... +#ifdef __cplusplus +class RingBufC +{ + +public: + RingBufC(int size, int len) { buf = RingBuf_new(size, len); } + ~RingBufC() { RingBuf_delete(buf); } + + bool isFull() { return RingBufIsFull(buf); } + bool isEmpty() { return RingBufIsEmpty(buf); } + unsigned int numElements() { return RingBufNumElements(buf); } + + unsigned int add(const void *object) { return RingBufAdd(buf, object); } + void *peek(unsigned int num) { return RingBufPeek(buf, num); } + void *pull(void *object) { return RingBufPull(buf, object); } + + // Use this to check if memory allocation failed + bool allocFailed() { return !buf; } + +private: + RingBuf *buf; +}; +#endif + + +#endif diff --git a/leigh-esp32-cube/lib/ESPAsyncE131/examples/E131_Test/E131_Test.ino b/leigh-esp32-cube/lib/ESPAsyncE131/examples/E131_Test/E131_Test.ino new file mode 100644 index 000000000..6a21d3365 --- /dev/null +++ b/leigh-esp32-cube/lib/ESPAsyncE131/examples/E131_Test/E131_Test.ino @@ -0,0 +1,77 @@ +/* +* E131_Test.ino - Simple sketch to listen for E1.31 data on an ESP32 +* and print some statistics. +* +* Project: ESPAsyncE131 - Asynchronous E.131 (sACN) library for Arduino ESP8266 and ESP32 +* Copyright (c) 2019 Shelby Merrick +* http://www.forkineye.com +* +* This program is provided free for you to use in any way that you wish, +* subject to the laws and regulations where you are using it. Due diligence +* is strongly suggested before using this code. Please give credit where due. +* +* The Author makes no warranty of any kind, express or implied, with regard +* to this program or the documentation contained in this document. The +* Author shall not be liable in any event for incidental or consequential +* damages in connection with, or arising out of, the furnishing, performance +* or use of these programs. +* +*/ + +#include + +#define UNIVERSE 1 // First DMX Universe to listen for +#define UNIVERSE_COUNT 2 // Total number of Universes to listen for, starting at UNIVERSE + +const char ssid[] = "........"; // Replace with your SSID +const char passphrase[] = "........"; // Replace with your WPA2 passphrase + +// ESPAsyncE131 instance with UNIVERSE_COUNT buffer slots +ESPAsyncE131 e131(UNIVERSE_COUNT); + +void setup() { + Serial.begin(115200); + delay(10); + + // Make sure you're in station mode + WiFi.mode(WIFI_STA); + + Serial.println(""); + Serial.print(F("Connecting to ")); + Serial.print(ssid); + + if (passphrase != NULL) + WiFi.begin(ssid, passphrase); + else + WiFi.begin(ssid); + + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + + Serial.println(""); + Serial.print(F("Connected with IP: ")); + Serial.println(WiFi.localIP()); + + // Choose one to begin listening for E1.31 data + //if (e131.begin(E131_UNICAST)) // Listen via Unicast + if (e131.begin(E131_MULTICAST, UNIVERSE, UNIVERSE_COUNT)) // Listen via Multicast + Serial.println(F("Listening for data...")); + else + Serial.println(F("*** e131.begin failed ***")); +} + +void loop() { + if (!e131.isEmpty()) { + e131_packet_t packet; + e131.pull(&packet); // Pull packet from ring buffer + + Serial.printf("Universe %u / %u Channels | Packet#: %u / Errors: %u / CH1: %u\n", + htons(packet.universe), // The Universe for this packet + htons(packet.property_value_count) - 1, // Start code is ignored, we're interested in dimmer data + e131.stats.num_packets, // Packet counter + e131.stats.packet_errors, // Packet error counter + packet.property_values[1]); // Dimmer data for Channel 1 + } +} diff --git a/leigh-esp32-cube/lib/ESPAsyncE131/keywords.txt b/leigh-esp32-cube/lib/ESPAsyncE131/keywords.txt new file mode 100644 index 000000000..b5c25de62 --- /dev/null +++ b/leigh-esp32-cube/lib/ESPAsyncE131/keywords.txt @@ -0,0 +1,12 @@ +# Data Types +ESPAsyncE131 KEYWORD1 +e131_packet_t KEYWORD1 +e131_error_t KEYWORD1 +e131_stats_t KEYWORD1 + +# Members +begin KEYWORD2 +isEmpty KEYWORD2 +pull KEYWORD2 +dumpError KEYWORD2 +stats KEYWORD2 \ No newline at end of file diff --git a/leigh-esp32-cube/lib/ESPAsyncE131/library.json b/leigh-esp32-cube/lib/ESPAsyncE131/library.json new file mode 100644 index 000000000..07cec2299 --- /dev/null +++ b/leigh-esp32-cube/lib/ESPAsyncE131/library.json @@ -0,0 +1,25 @@ +{ + "name": "ESPAsyncE131", + "keywords": "dmx, e131, e1.31, sacn", + "description": "Library for the asynchronous processing of sACN (E1.31 DMX over Ethernet) data.", + "repository": { + "type": "git", + "url": "https://github.com/forkineye/ESPAsyncE131.git" + }, + "version": "1.0.4", + "authors": { + "name": "Shelby Merrick", + "url": "http://forkineye.com", + "maintainer": true + }, + "frameworks": "arduino", + "platforms": [ + "espressif8266", + "espressif32" + ], + "headers": "ESPAsyncE131.h", + "dependencies": { + "name": "ESPAsyncUDP", + "platforms": "espressif8266" + } +} diff --git a/leigh-esp32-cube/lib/ESPAsyncE131/library.properties b/leigh-esp32-cube/lib/ESPAsyncE131/library.properties new file mode 100644 index 000000000..fd4a28e57 --- /dev/null +++ b/leigh-esp32-cube/lib/ESPAsyncE131/library.properties @@ -0,0 +1,10 @@ +name=ESP Async E1.31 +version=1.0.4 +author=forkineye +maintainer=forkineye +sentence=Async E1.31 sACN for ESP8266. +paragraph=Library for the asynchronous processing of sACN (E1.31 DMX over Ethernet) data. +category=Communication +url=https://github.com/forkineye/ESPAsyncE131 +architectures=esp8266,esp32 +includes=ESPAsyncE131.h diff --git a/leigh-esp32-cube/platformio.ini b/leigh-esp32-cube/platformio.ini index 89f0d6252..f383ef71c 100644 --- a/leigh-esp32-cube/platformio.ini +++ b/leigh-esp32-cube/platformio.ini @@ -11,4 +11,4 @@ [env:esp-wrover-kit] platform = espressif32 board = esp-wrover-kit -framework = espidf +framework = arduino \ No newline at end of file diff --git a/leigh-esp32-cube/src/main.c b/leigh-esp32-cube/src/main.c index 3b5b685b9..79fdae254 100644 --- a/leigh-esp32-cube/src/main.c +++ b/leigh-esp32-cube/src/main.c @@ -1 +1,4 @@ + +#include + void app_main() {} \ No newline at end of file -- GitLab