rewrite the sACN class

This commit is contained in:
Kevin Matz 2020-12-22 11:34:52 -05:00
parent c4cccc97e1
commit 9248104350
5 changed files with 139 additions and 179 deletions

View File

@ -165,7 +165,6 @@ SOFTWARE.
| Library | Copyright | License |
| :- | :- | :-: |
[WiFlash](https//git.company235.com/kevin/WiFlasher) | Copyright © 2020 Kevin Matz | [MIT](https//git.company235.com/kevin/WiFlasher/src/branch/master/LICENSE) |
[ESPAsyncE131](https//github.com/forkineye/ESPAsyncE131) | Copyright © 2019 Shelby Merrick |
[NeoPixelBus](https//github.com/Makuna/NeoPixelBus) | Michael Miller| [LGPL 3.0](https//github.com/Makuna/NeoPixelBus/blob/master/COPYING) |
[ESPAsyncWebServer](https//github.com/me-no-dev/ESPAsyncWebServer) | Copyright © 2016 Hristo Gochkov | [LGPL 2.1](http//www.gnu.org/licenses/lgpl-2.1.html) |
[arduino-esp32](https//github.com/espressif/arduino-esp32) | Copyright © 2015 Ivan Grokhotkov | [LGPL 2.1](https//github.com/espressif/arduino-esp32/blob/master/LICENSE.md) |

View File

@ -363,11 +363,6 @@
<td>Copyright &copy; 2020 Kevin Matz</td>
<td><a href="https//git.company235.com/kevin/WiFlasher/src/branch/master/LICENSE">MIT</a></td>
</tr>
<tr>
<td><a href="https//github.com/forkineye/ESPAsyncE131">ESPAsyncE131</a></td>
<td>Copyright &copy; 2019 Shelby Merrick</td>
<td></td>
</tr>
<tr>
<td><a href="https//github.com/Makuna/NeoPixelBus">NeoPixelBus</a></td>
<td>Michael Miller</td>

View File

@ -1,19 +1,25 @@
/*
ESPAsyncE131.cpp
sacn.cpp
Project: ESPAsyncE131 - Asynchronous E.131 (sACN) library for Arduino ESP8266 and ESP32
Copyright (c) 2019 Shelby Merrick
http://www.forkineye.com
Copyright (c) 2020 Kevin Matz (kevin.matz@gmail.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.
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 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.
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.
*/
#include <Arduino.h>
@ -21,27 +27,23 @@
#include <iterator>
#include "sacn.h"
// E1.17 ACN Packet Identifier
const uint8_t ESPAsyncE131::ACN_ID[12] = { 0x41, 0x53, 0x43, 0x2d, 0x45, 0x31, 0x2e, 0x31, 0x37, 0x00, 0x00, 0x00 };
ESPAsyncE131::ESPAsyncE131() {
stats.num_packets = 0;
stats.packet_errors = 0;
udp.onPacket(std::bind(&ESPAsyncE131::parsePacket, this,
ESPsACN::ESPsACN() {
udp.onPacket(std::bind(&ESPsACN::parsePacket, this,
std::placeholders::_1));
}
bool ESPAsyncE131::subscribe(uint16_t num) {
bool success = false;
bool ESPsACN::subscribe(uint16_t num) {
bool success;
if (universes_.count(num)) {
// already subscribed
return true;
}
// listen multicast; works for unicast too
success = udp.listenMulticast(E131MulticastAddress(num),
E131_DEFAULT_PORT);
ACN_SDT_MULTICAST_PORT);
Universe *u = new Universe();
universes_.emplace(num, u);
@ -49,80 +51,54 @@ bool ESPAsyncE131::subscribe(uint16_t num) {
return success;
}
void ESPAsyncE131::parsePacket(AsyncUDPPacket _packet) {
e131_error_t error = ERROR_NONE;
void ESPsACN::parsePacket(AsyncUDPPacket _packet) {
buff_ = reinterpret_cast<e131_packet_t *>(_packet.data());
if (memcmp(buff_->acn_id, ESPAsyncE131::ACN_ID, sizeof(buff_->acn_id)))
error = ERROR_ACN_ID;
else if (htonl(buff_->root_vector) != ESPAsyncE131::VECTOR_ROOT)
error = ERROR_VECTOR_ROOT;
else if (htonl(buff_->frame_vector) != ESPAsyncE131::VECTOR_FRAME)
error = ERROR_VECTOR_FRAME;
else if (buff_->dmp_vector != ESPAsyncE131::VECTOR_DMP)
error = ERROR_VECTOR_DMP;
else if (buff_->property_values[0] != 0)
error = ERROR_IGNORE;
if (error == ERROR_NONE) {
stats.num_packets++;
stats.last_clientIP = _packet.remoteIP();
stats.last_clientPort = _packet.remotePort();
stats.last_seen = millis();
uint16_t univ_number = buff_->universe >> 8 |
buff_->universe << 8;
if (universes_.count(univ_number)) {
// slots beyond the recieved count should be zero'd
std::array<uint8_t, 513> full = {0};
uint16_t slot_count = buff_->property_value_count >> 8 |
buff_->property_value_count << 8;
std::copy(std::begin(buff_->property_values),
std::begin(buff_->property_values) + slot_count,
std::begin(full));
// copy full-lenght universe
e111_universe_t u;
std::copy(std::begin(full), std::end(full), std::begin(u.data));
// set universe data
universes_.at(univ_number)->setData(&u);
}
} else if (error == ERROR_IGNORE) {
// Do nothing
} else {
if (Serial)
dumpError(buff_, error);
stats.packet_errors++;
}
// E1.31 - 5.3 ACN Packet Identifier
// The ACN Packet Identifier shall contain the following sequence of characters
if (memcmp(buff_->rlp.acn_id, ACN_PACKET_IDENTIFIER, sizeof(buff_->rlp.acn_id)))
return;
// E1.31 - 5.5 Vector
// Sources shall set the Root Layer's Vector to VECTOR_ROOT_E131_DATA
// if the packet contains E1.31 Data
if (htonl(buff_->rlp.vector) != VECTOR_ROOT_E131_DATA)
return;
// E1.31 - 6.2.1 E1.31 Data Packet: Vector
// Sources sending an E1.31 Data Packet shall set the E1.31 Layer's
// Vector to VECTOR_E131_DATA_PACKET.
if (htonl(buff_->frame.vector) != VECTOR_E131_DATA_PACKET)
return;
// E1.31 - 7.2 DMP Layer: Vector
// The DMP Layer's Vector shall be set to VECTOR_DMP_SET_PROPERTY
if (buff_->dmp.vector != VECTOR_DMP_SET_PROPERTY)
return;
// ignore unverses recieved without active subscriptions
if (!universes_.count(htons(buff_->frame.universe)))
return;
// E1.11 - 8.5.2 Dimmer class data
// Dimmer level data should be sent in NULL START Code packets.
if (buff_->dmp.property_values[0] != E111_NULL_START)
return;
// slots beyond the recieved count should be zero'd
std::array<uint8_t, 513> full = {0};
std::copy(std::begin(buff_->dmp.property_values),
std::begin(buff_->dmp.property_values) + htons(buff_->dmp.property_value_count),
std::begin(full));
// copy full-lenght universe
e111_universe_t u;
std::copy(std::begin(full), std::end(full), std::begin(u.data));
// set universe data
universes_.at(htons(buff_->frame.universe))->setData(&u);
}
void ESPAsyncE131::dumpError(e131_packet_t *packet, 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(packet->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(packet->root_vector), HEX);
break;
case ERROR_VECTOR_FRAME:
Serial.print(F("INVALID FRAME VECTOR: 0x"));
Serial.println(htonl(packet->frame_vector), HEX);
break;
case ERROR_VECTOR_DMP:
Serial.print(F("INVALID DMP VECTOR: 0x"));
Serial.println(packet->dmp_vector, HEX);
case ERROR_NONE:
break;
case ERROR_IGNORE:
break;
}
}
IPAddress ESPAsyncE131::E131MulticastAddress(uint16_t universe) {
IPAddress ESPsACN::E131MulticastAddress(uint16_t universe) {
return IPAddress(239, 255, ((universe >> 8) & 0xff), ((universe >> 0) & 0xff));
}

View File

@ -1,135 +1,125 @@
/*
ESPAsyncE131.h
sacn.h
Project: ESPAsyncE131 - Asynchronous E.131 (sACN) library for Arduino ESP8266 and ESP32
Copyright (c) 2019 Shelby Merrick
http://www.forkineye.com
Copyright (c) 2020 Kevin Matz (kevin.matz@gmail.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.
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 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.
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#ifndef ESPASYNCE131_H_
#define ESPASYNCE131_H_
#ifndef SACN_H_
#define SACN_H_
#include <AsyncUDP.h>
#include <unordered_map>
#include "dmx_universe.h"
// Defaults
#define E131_DEFAULT_PORT 5568
// Appendix A: Defined Parameters (Normative)
#define VECTOR_ROOT_E131_DATA 0x00000004
#define VECTOR_ROOT_E131_EXTENDED 0x00000008
// 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 VECTOR_DMP_SET_PROPERTY 0x02 // informative
#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 VECTOR_E131_DATA_PACKET 0x00000002
#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
#define VECTOR_E131_EXTENDED_SYNCHRONIZATION 0x00000001
#define VECTOR_E131_EXTENDED_DISCOVERY 0x00000002
// E1.31 Packet Structure
#define E131_E131_UNIVERSE_DISCOVER_INTERVAL 10000 // ms
#define E131_NETWORK_DATA_LOSS_TIMEOUT 2500 // ms
#define E131_DISCOVERY_UNIVERSE 64214
#define ACN_SDT_MULTICAST_PORT 5568
// A S C - E 1 . 1 7
static constexpr uint8_t ACN_PACKET_IDENTIFIER[] = { 0x41, 0x53, 0x43, 0x2d, 0x45, 0x31, 0x2e, 0x31, 0x37, 0x00, 0x00, 0x00 };
// Table 5-1: ACN Root Layer
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;
uint16_t flength;
uint32_t vector;
uint8_t cid[16];
} __attribute__((packed));
uint8_t raw[37];
} e131_rlp_t;
// Frame Layer
uint16_t frame_flength;
uint32_t frame_vector;
// Table 6-1: E1.31 Data Packet Framing Layer
typedef union {
struct {
uint16_t flength;
uint32_t vector;
uint8_t source_name[64];
uint8_t priority;
uint16_t reserved;
uint8_t sequence_number;
uint8_t options;
uint16_t universe;
} __attribute__((packed));
uint8_t raw[77];
} e131_frame_t;
// DMP Layer
uint16_t dmp_flength;
uint8_t dmp_vector;
// Table 7-1: E131 Data Packet DMP Layer
typedef union {
struct {
uint16_t flength;
uint8_t 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[523];
} e131_dmp_t;
// Table 4-1: E1.31 Data Packet
typedef union {
struct {
e131_rlp_t rlp;
e131_frame_t frame;
e131_dmp_t dmp;
} __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;
// 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 std::function<void(e131_packet_t *packet)> E131PacketHandlerFunction;
class ESPAsyncE131 {
class ESPsACN {
public:
ESPAsyncE131();
ESPsACN();
bool subscribe(uint16_t universe = 1);
static void dumpError(e131_packet_t *packet, e131_error_t error);
static IPAddress E131MulticastAddress(uint16_t universe);
Universe * universe(uint16_t num) {
return universes_.at(num);
}
e131_stats_t stats; // Statistics tracker
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 *buff_; // Pointer to scratch packet buffer
AsyncUDP udp; // AsyncUDP
@ -139,4 +129,4 @@ class ESPAsyncE131 {
std::unordered_map <uint16_t, Universe*> universes_;
};
#endif // ESPASYNCE131_H_
#endif // SACN_H_

View File

@ -43,7 +43,7 @@
//// device objects
Strobe *strobe;
NeoPixelBus<NeoGrbwFeature, Neo800KbpsMethod> *strip;
ESPAsyncE131 *e131 = new ESPAsyncE131();
ESPsACN *e131 = new ESPsACN();
AsyncWebServer *httpd = new AsyncWebServer(80);