fully E1.17 ACN compliant receiver of E1.31 sACN data

This commit is contained in:
Kevin Matz 2021-01-04 09:52:54 -05:00
parent 7bc93797cb
commit 2e39a01a4c
28 changed files with 1554 additions and 330 deletions

View File

@ -0,0 +1,36 @@
/*
acn.h
Copyright (c) 2020 Kevin Matz (kevin.matz@gmail.com)
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.
*/
#pragma once
#include "pdu.h"
#include "rlp.h"
#include "rlp-udp.h"
#include "sdt.h"
#include "sdt-udp.h"
#include "dmp.h"
// ANSI E1.17- 2015, Architecture for Control Networks
namespace ACN {
}

View File

@ -0,0 +1,95 @@
/*
acn-dmp.cpp
Copyright (c) 2020 Kevin Matz (kevin.matz@gmail.com)
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.
*/
#include "dmp.h"
#include "rlp.h"
namespace ACN {
namespace DMP {
address_type::address_type(uint8_t val) {
z_reserved = (val >> 7) & 0b1;
relative = (val >> 6) & 0b1;
type = (data_type)((val >> 4) & 0b11);
x_reserved = (val >> 2) & 0b11;
width = (address_length)(val & 0b11);
}
range::range(PDU::stream_ptr stream, data_type type, address_length length) {
address = read(stream, length);
if (type == SINGLE)
return;
incriment = read(stream, length);
count = read(stream, length);
}
uint32_t range::read(PDU::stream_ptr stream, address_length length) {
switch (length) {
case ONE:
return PDU::read8(stream);
case TWO:
return PDU::read16(stream);
case FOUR:
return PDU::read32(stream);
default:
return 0;
}
}
void readHeader(PDU::pdu_ptr pdu) {
if (pdu->flags().hasHeader) {
address_type * header = new address_type(pdu->buffer()->get());
pdu->setHeader(header);
}
}
void readSet(PDU::pdu_ptr pdu) {
const address_type *header = static_cast<const address_type*>(pdu->header());
PDU::stream_ptr stream = pdu->buffer();
dmp_set_data * data = new dmp_set_data();
while(stream->good()) {
// Property Address
range pr(stream, header->type, header->width);
// Property Data
std::vector<uint8_t> pd;
pd.resize(pr.address, 0);
for (uint32_t i = 0; i < pr.count * pr.incriment; i ++)
pd.push_back(stream->get());
// Property Fields
set_property fields(pr, pd);
data->properties.push_back(fields);
// set EOF if buffer is drained
if (stream->available() == 0)
stream->setstate(stream->rdstate() | std::ios_base::eofbit);
}
pdu->setData(data);
}
} // DMP
} // ACN

View File

@ -0,0 +1,110 @@
/*
acn-dmp.h
Copyright (c) 2020 Kevin Matz (kevin.matz@gmail.com)
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.
*/
#pragma once
#include <stdint.h>
#include <vector>
#include "pdu.h"
// Architecture for Control Networks Device Management Protocol
namespace ACN {
namespace DMP {
// 5.1.4 Address and Data Types
enum data_type {
SINGLE = 0b00, // 0
RANGE = 0b01, // 1
ARRAY = 0b10, // 2
SERIES = 0b11 // 3
};
enum address_length {
ONE = 0b00, // 0
TWO = 0b01, // 1
FOUR = 0b10, // 2
ZERO = 0b11 // 3 (reserved)
};
struct address_type : PDU::pdu_header {
bool z_reserved : 1; // Z
bool relative : 1; // R
data_type type : 2; // D1, D0
uint8_t x_reserved : 2; // X1, X0
address_length width : 2; // A1, A0
address_type(uint8_t);
};
// 5.1.5
struct range {
uint32_t address;
uint32_t incriment;
uint32_t count;
range() {};
range(PDU::stream_ptr, data_type, address_length);
private:
uint32_t read(PDU::stream_ptr, address_length);
};
typedef std::pair<range, std::vector<uint8_t>> set_property;
struct dmp_set_data : PDU::pdu_data {
std::vector<set_property> properties;
};
// 7 Response Messages
enum failure_reason {
NONSPECIFIC = 1, // Non-specific or non-DMP reason.
NOT_PROPERTY = 2, // The address does not correspond to a property.
WRITE_ONLY = 3, // The propertys value may not be read.
NOT_WRITABLE = 4, // The propertys value may not be written.
DATA_ERROR = 5, // The data does not correspond to the property.
SUBSCIRPTION_NOT_SUPPORTED = 10, // Subscriptions on the specified property are not supported by the device.
NO_SUBSCRIPTIONS_SUPPORTED = 11, // Subscriptions not supported on any property.
INSUFFICIENT_RESOURCES = 12, // The component cannot support more subscriptions due to resource limitations
UNAVAILABLE = 13 // The propertys value is not available due to restrictions imposed by device specific functionality (e.g., access permission mechanisms).
};
// 13.1 Protocol Codes
static const uint32_t DMP_PROTOCOL_ID = 2; // PDU protocol value
// 13.2 Message Codes
static const uint8_t GET_PROPERTY = 1;
static const uint8_t SET_PROPERTY = 2;
static const uint8_t GET_PROPERTY_REPLY = 3;
static const uint8_t EVENT = 4;
static const uint8_t SUBSCRIBE = 7;
static const uint8_t UNSUBSCRIBE = 8;
static const uint8_t GET_PROPERTY_FAIL = 9;
static const uint8_t SET_PROPERTY_FAIL = 10;
static const uint8_t SUBSCRIBE_ACCEPT = 12;
static const uint8_t SUBSCRIBE_REJECT = 13;
static const uint8_t SYNC_EVENT = 17;
void readHeader(PDU::pdu_ptr);
void readSet(PDU::pdu_ptr);
inline PDU::block_ptr readBlock(PDU::stream_ptr s, uint8_t vl = 1) {
return PDU::readBlock(s, vl);
}
} // DMP
} // ACN

View File

@ -0,0 +1,165 @@
/*
acn-pdu.cpp
Copyright (c) 2020 Kevin Matz (kevin.matz@gmail.com)
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.
*/
#include "pdu.h"
#include <memory>
namespace ACN {
namespace PDU {
pdu_flags::pdu_flags(uint8_t val) {
hasLength = (val >> 7) & 0b1;
hasVector = (val >> 6) & 0b1;
hasHeader = (val >> 5) & 0b1;
hasData = (val >> 4) & 0b1;
};
Pdu::Pdu(stream_ptr stream, size_t vector_size)
: flags_(stream->peek())
{
header_ = 0;
data_ = 0;
parent_ = 0;
inherit_ = 0; // pointer to previous PDU in block
// read length and vector off of the stream
length_ = readLength(stream, flags_.hasLength);
if (flags_.hasVector)
vector_ = readVector(stream, vector_size);
// ESP_LOGD(TAG, "length: %d vector: %d", length_, vector_);
// length includes the flags, length, and vector
// calculate the remaining lenght of the PDU
int len = length_;
len -= flags_.hasLength ? 3 : 2;
len -= vector_size;
// abort if the remaining PDU length isn't available
if (!stream->good() || len > stream->available()) {
stream->setstate(stream->rdstate() | std::ios_base::failbit);
return;
}
// create a stream buffer for the header and data
uint8_t buf[len];
stream->read(buf, len);
buffer_ = stream_ptr(new pdu_stream(buf, len));
}
Pdu::~Pdu() {
if (header_) delete header_;
if (data_) delete data_;
}
const uint32_t Pdu::vector() {
if (flags_.hasVector)
return vector_;
if (inherit_ != nullptr)
return inherit_->vector();
return 0;
}
pdu_header * Pdu::header() {
if (flags_.hasHeader)
return header_;
if (inherit_ != nullptr)
return inherit_->header();
return nullptr;
}
pdu_data * Pdu::data() {
if (flags_.hasData)
return data_;
if (inherit_ != nullptr)
return inherit_->data();
return nullptr;
}
block_ptr readBlock(stream_ptr stream, uint8_t vector_size) {
block_ptr block(new pdu_block());
while(stream->good()) {
// create a PDU off of the stream
pdu_ptr pdu(new Pdu(stream, vector_size));
if (stream->fail()) break; // OK if eofbit is set
// set inheritor pointer
if (!block->empty()) pdu->setInherit(block->back());
// add the PDU to the block list
block->push_back(pdu);
// set EOF if buffer is drained
if (stream->available() == 0)
stream->setstate(stream->rdstate() | std::ios_base::eofbit);
}
return block;
}
uint32_t readLength(stream_ptr stream, bool hasLength) {
uint32_t length = 0;
length |= (stream->get() & 0x0f) << 16;
length |= stream->get() << 8;
if (hasLength)
length |= stream->get();
else
length = length >> 8;
return length;
}
uint32_t readVector(stream_ptr stream, uint8_t vector_size) {
uint32_t vect = 0;
for (int o = vector_size - 1; o >= 0; o--) {
vect |= (stream->get() << (8 * o));
}
return vect;
}
uint8_t read8(stream_ptr stream) {
return stream->get();
}
uint16_t read16(stream_ptr stream) {
uint16_t ret = 0;
ret |= stream->get() << 8;
ret |= stream->get();
return ret;
}
uint32_t read32(stream_ptr stream) {
uint32_t ret = 0;
ret |= stream->get() << 24;
ret |= stream->get() << 16;
ret |= stream->get() << 6;
ret |= stream->get();
return ret;
}
} // PDU
} // ACN

View File

@ -0,0 +1,119 @@
/*
acn-pdu.h
Copyright (c) 2020 Kevin Matz (kevin.matz@gmail.com)
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.
*/
#pragma once
#include <stdint.h>
#include <vector>
#include <istream>
#include <memory>
namespace ACN {
namespace PDU {
// 2.4.1. Flags
// Flags is a 4-bit field containing flags L, V, H and D which declare how the PDU is packed.
struct pdu_flags {
bool hasLength : 1;
bool hasVector : 1;
bool hasHeader : 1;
bool hasData : 1;
pdu_flags(uint8_t);
};
class pdu_stream;
class Pdu;
typedef std::shared_ptr<pdu_stream> stream_ptr;
typedef std::shared_ptr<Pdu> pdu_ptr;
typedef std::vector<pdu_ptr> pdu_block;
typedef std::shared_ptr<pdu_block> block_ptr;
// MAYBE: remove virtuals?
// Arduino doen't enable RTTI for run-time polymorphism.
struct pdu_header { virtual ~pdu_header() {} };
struct pdu_data { virtual ~pdu_data() {} };
/*
pdu_buffer and pdu_stream hold the raw data of the packet
*/
class pdu_buffer
: public std::basic_streambuf<uint8_t>
{
public:
pdu_buffer(uint8_t * p, size_t l) { setg(p, p, p + l); }
};
class pdu_stream
: public std::basic_istream<uint8_t>
{
public:
pdu_stream(uint8_t * p, size_t l)
: std::basic_istream<uint8_t>(&_buffer)
, _buffer(p, l) { rdbuf(&_buffer); }
std::streamsize available() { return _buffer.in_avail(); }
private:
pdu_buffer _buffer;
};
class Pdu {
public:
Pdu(stream_ptr, size_t vector_size);
~Pdu();
// getters
const pdu_flags flags() {return flags_;}
const uint32_t length() {return length_;}
const uint32_t vector();
pdu_header * header();
pdu_data * data();
pdu_ptr parent() {return parent_;}
stream_ptr buffer() {return buffer_;}
// setters
void setHeader (pdu_header * h) {header_ = h;}
void setData (pdu_data * d) {data_ = d;}
void setParent (pdu_ptr pdu) {parent_ = pdu;}
void setInherit(pdu_ptr pdu) {inherit_ = pdu;}
private:
pdu_flags flags_;
uint32_t length_;
uint32_t vector_;
pdu_header * header_;
pdu_data * data_;
pdu_ptr parent_;
pdu_ptr inherit_;
stream_ptr buffer_;
};
// utility functions to constuct the PDU from input stream
block_ptr readBlock (stream_ptr, uint8_t vector_length = 4);
uint32_t readLength(stream_ptr, bool hasLength);
uint32_t readVector(stream_ptr, uint8_t);
uint8_t read8 (stream_ptr);
uint16_t read16(stream_ptr);
uint32_t read32(stream_ptr);
} // PDU
} // ACN

View File

@ -0,0 +1,77 @@
/*
acn-rlp-udp.h
Copyright (c) 2020 Kevin Matz (kevin.matz@gmail.com)
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.
*/
#include "rlp-udp.h"
#include "sdt-udp.h"
#include <cstring>
namespace ACN {
namespace RLP {
namespace UDP {
preamble_t::preamble_t(PDU::stream_ptr stream) {
length = PDU::read16(stream);
postamble_size = PDU::read16(stream);
stream->read(acn_id, 12);
}
/*
Validate compliance with ACN EPI 17: ACN Root Layer Protocol on UDP
4.2. Reception
*/
preamble_t::operator bool () {
// 2. Preamble Format: The preamble size includes both size fields so the
// minimum value for preamble size is 16 (octets).
if (length < PREAMBLE_MINIMUM_SIZE)
return false;
// 2. Preamble Format: The ACN Packet Identifier shall be the text string
// “ASC-E1.17\0\0\0” encoded in [ASCII].
if (memcmp(acn_id, ACN_PACKET_IDENTIFIER, 12))
return false;
return true;
}
/*
Process ACN packet
*/
PDU::block_ptr readBlock(PDU::stream_ptr packet) {
// std::streamoff origin = packet->tellg();
preamble_t preamble(packet);
if (!preamble)
packet->setstate(std::ios_base::failbit);
// Preamble length has a standard min, but no max. Discard any extra.
std::streamoff extra_preamble = preamble.length - PREAMBLE_MINIMUM_SIZE;
for (int i = 0; i < extra_preamble; i++)
packet->get();
return PDU::readBlock(packet);
}
} // UDP
} // RLP
} // ACN

View File

@ -0,0 +1,58 @@
/*
acn-rlp-udp.h
Copyright (c) 2020 Kevin Matz (kevin.matz@gmail.com)
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.
*/
#pragma once
#include <stdint.h>
#include "rlp.h"
#include "pdu.h"
// ACN EPI 17. ACN Root Layer Protocol Operation on UDP
namespace ACN {
namespace RLP {
namespace UDP {
// The ACN Packet Identifier shall be the text string
// “ASC-E1.17\0\0\0” encoded in [ASCII].
static constexpr uint8_t ACN_PACKET_IDENTIFIER[] = { 0x41, 0x53, 0x43, 0x2d, 0x45, 0x31, 0x2e, 0x31, 0x37, 0x00, 0x00, 0x00 };
// 2. Preamble Format: The preamble size includes both size fields so the
// minimum value for preamble size is 16 (octets).
static const uint8_t PREAMBLE_MINIMUM_SIZE = 16;
// 2. Preamble Format
struct preamble_t {
uint16_t length;
uint16_t postamble_size;
uint8_t acn_id[12];
preamble_t(PDU::stream_ptr);
operator bool();
};
PDU::block_ptr readBlock(PDU::stream_ptr);
} // UDP
} // RLP
} // ACN

View File

@ -0,0 +1,45 @@
/*
acn-rlp.cpp
Copyright (c) 2020 Kevin Matz (kevin.matz@gmail.com)
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.
*/
#include "rlp.h"
namespace ACN {
namespace RLP {
rlp_header::rlp_header(PDU::stream_ptr stream)
: pdu_header()
{
if (stream->available() > (long int)sizeof(cid))
stream->read(cid, sizeof(cid));
}
void readHeader(PDU::pdu_ptr pdu) {
if (pdu->flags().hasHeader) {
rlp_header * header = new rlp_header(pdu->buffer());
pdu->setHeader(header);
}
}
} // RLP
} // ACN

View File

@ -0,0 +1,43 @@
/*
acn-rlp.h
Copyright (c) 2020 Kevin Matz (kevin.matz@gmail.com)
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.
*/
#pragma once
#include "pdu.h"
#include <stdint.h>
namespace ACN {
namespace RLP {
// 2.6.1.2.2. Header Field in Root Layer PDUs
// The Header field in Root Layer PDUs shall contain the CID of the component
// that generated the PDU (the Source CID).
struct rlp_header : PDU::pdu_header {
uint8_t cid[16];
rlp_header(PDU::stream_ptr);
};
void readHeader(PDU::pdu_ptr);
} // RLP
} // ACN

View File

@ -0,0 +1,64 @@
/*
acn-sdt-udp.h
Copyright (c) 2020 Kevin Matz (kevin.matz@gmail.com)
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.
*/
#pragma once
#include <stdint.h>
#include "sdt.h"
// ACN EPI 17. Operation of SDT on UDP Networks
namespace ACN {
namespace SDT {
namespace UDP {
// Table 1. IPv4 Address Specification
struct acn_sdt_udp_ipv4_addr_t {
ip_addr_spec_t type; // SDT_ADDR_IPV4
uint16_t udp_port;
uint8_t ipv4[4];
};
// Table 2. IPv6 Address Specification
struct acn_sdt_udp_ipv6_addr_t {
ip_addr_spec_t type; // SDT_ADDR_IPV6
uint16_t udp_port;
uint8_t ipv4[16];
};
// Table 3. SDT symbolic parameters
// static const float MAK_TIMEOUT_FACTOR = 0.1;
// static const uint8_t MAK_MAX_RETRIES = 2; // (3 tries total)
// static const uint8_t AD_HOC_TIMEOUT = 200; // ms
// static const uint8_t AD_HOC_RETRIES = 2; // (3 tries total)
// static const float RECIPROCAL_TIMEOUT_FACTOR = 0.2;
// static const uint8_t MIN_EXPIRY_TIME = 2; // s
// static const float NAK_TIMEOUT_FACTOR = 0.1;
// static const uint8_t NAK_MAX_RETRIES = 2; // (3 tries total)
// static const uint8_t NAK_HOLDOFF_INTERVAL = 2; // ms
// static const uint8_t NAK_MAX_TIME = 10 * NAK_HOLDOFF_INTERVAL;
// static const uint8_t NAK_BLANKTIME = 3 * NAK_HOLDOFF_INTERVAL;
// static const uint16_t SDT_MULTICAST_PORT = 5568; // IANA registered “sdt”
} // UDP
} // SDT
} // ACN

View File

@ -0,0 +1,66 @@
/*
acn-sdt.h
Copyright (c) 2020 Kevin Matz (kevin.matz@gmail.com)
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.
*/
#pragma once
#include <stdint.h>
#include "pdu.h"
// ANSI E1.17- 2015, Architecture for Control Networks
// Session Data Transport Protocol
namespace ACN {
namespace SDT {
// 7.1 Protocol Code
static const uint32_t SDT_PROTOCOL_ID = 1; // PDU protocol value
// 7.2 PDU Vector Codes
enum sdt_vector_t {
REL_WRAP = 1,
UNREL_WRAP = 2,
CHANNEL_PARAMS = 3,
JOIN = 4,
JOIN_REFUSE = 5,
JOIN_ACCEPT = 6,
LEAVE = 7,
LEAVING = 8,
CONNECT = 9,
CONNECT_ACCEPT = 10,
CONNECT_REFUSE = 11,
DISCONNECT = 12,
DISCONNECTING = 13,
ACK = 14,
NAK = 15,
GET_SESSIONS = 16,
SESSIONS = 17,
};
// Table 7: Address Specification Types
enum ip_addr_spec_t {
SDT_ADDR_NULL = 0,
SDT_ADDR_IPV4 = 1,
SDT_ADDR_IPV6 = 2
};
} // SDT
} // ACN

View File

@ -1,5 +1,5 @@
/*
dmx_universe.cpp
universe.cpp
Copyright (c) 2020 Kevin Matz (kevin.matz@gmail.com)
@ -22,53 +22,34 @@
SOFTWARE.
*/
#include "universe.h"
#include <Arduino.h>
#include "dmx_universe.h"
namespace DMX {
/*
Constructor
accept new data from receiver
*/
Universe::Universe() {
}
/*
recieve new data from nodeS
*/
bool Universe::setData(e111_universe_t *src) {
switch (src->start_code) {
void Universe::set(std::vector<uint8_t> vect) {
// full length universe is 513, including start code
// truncate larger data and pad shorter data with 0
vect.resize(513, 0);
switch (vect.front()) // start code
{
case E111_NULL_START:
std::copy(std::begin(src->data), std::end(src->data), std::begin(data_.data));
time_ = millis();
std::copy(vect.begin(), vect.end(), null_start_data_.begin());
for (const auto &cb : callbacks_)
cb(this);
break;
default:
return false;
break;
}
for (const auto &cb : callbacks_) {
cb(this);
}
return true;
}
/*
register a data consumer callback function
*/
void Universe::onData(const E111DataHandlerFunction callback)
void Universe::onData(const DataHandlerFunction callback)
{
callbacks_.push_back(callback);
}
/*
milliseconds since last update
*/
uint32_t Universe::age() {
uint32_t now = millis();
if (now > time_) {
return now - time_;
}
// millis values overflow after approximately 50 days.
return (2 ^ 32 - time_) + now;
}
} // DMX

View File

@ -1,5 +1,5 @@
/*
dmx_universe.h
universe.h
Copyright (c) 2020 Kevin Matz (kevin.matz@gmail.com)
@ -23,37 +23,27 @@
*/
#pragma once
#include <cstdint>
#include <stdint.h>
#include <functional>
#include <vector>
#define E111_NULL_START 0
namespace DMX {
// Table D1 - Reserved START Codes
#define E111_ASC_TEXT_ASCII 23
#define E111_ASC_TEST 85
#define E111_ASC_TEXT_UTF8 144
#define E111_ASC_MANUFACTURER 145
#define E111_ASC_SIP 207
static const uint8_t E111_NULL_START = 0;
static const uint8_t E111_ASC_TEXT_ASCII = 23;
static const uint8_t E111_ASC_TEST = 85;
static const uint8_t E111_ASC_TEXT_UTF8 = 144;
static const uint8_t E111_ASC_MANUFACTURER = 145;
static const uint8_t E111_ASC_SIP = 207;
// structure of DMX data
typedef union {
struct {
uint8_t start_code;
uint8_t slot[512]; // 0 index-origin!
} __attribute__((packed));
uint8_t data[513];
} e111_universe_t;
// forward declare the Univserse class
class Universe;
// register type for data users to subscribe callbacks
typedef std::function<void(Universe *)> E111DataHandlerFunction;
class Universe; // forward declare the Univserse class
typedef std::function<void(Universe *)> DataHandlerFunction;
// convience declare the type of null_start_data_
typedef std::array<uint8_t, 513> null_start_data_t;
/*
The Universe class
@ -64,17 +54,14 @@ typedef std::function<void(Universe *)> E111DataHandlerFunction;
*/
class Universe {
public:
Universe();
e111_universe_t * data() {
return &data_;
}
bool setData(e111_universe_t *data);
void onData(const E111DataHandlerFunction callback);
uint32_t age(); // millis() since setData
null_start_data_t * data() { return &null_start_data_; }
void onData (const DataHandlerFunction callback);
void set (std::vector<uint8_t>);
uint8_t slot (uint16_t address) { return null_start_data_[address]; }
private:
e111_universe_t data_;
uint32_t time_;
std::vector<E111DataHandlerFunction> callbacks_;
null_start_data_t null_start_data_;
std::vector<DataHandlerFunction> callbacks_;
};
} // DMX

View File

@ -56,13 +56,10 @@ uint32_t AbstractStrobe::dmxDuration(uint8_t dmx) {
/*
Load data into profile from new DMX
*/
void AbstractStrobe::recvData(Universe * univ) {
if ( sizeof(profile_.dmx) + address_ > sizeof(univ->data()->data)) {
// address is higher than allowable
return;
}
std::copy(univ->data()->data + address_,
univ->data()->data + address_ + sizeof(profile_.dmx),
void AbstractStrobe::recvData(DMX::Universe * univ) {
// copy data from universe to profile
std::copy(univ->data()->begin() + address_,
univ->data()->begin() + address_ + sizeof(profile_.dmx),
std::begin(profile_.dmx));
// reorder multibyte profile values (changes profile_.dmx)

View File

@ -27,7 +27,7 @@
#include <stdint.h>
#include "AbstractFixture.h"
#include "../sacn/dmx_universe.h"
#include "../dmx/universe.h"
typedef union {
uint8_t dmx[4];
@ -50,7 +50,7 @@ class AbstractStrobe
public:
AbstractStrobe(uint16_t address);
void recvData(Universe *); // data recieved callback
void recvData(DMX::Universe *); // data recieved callback
protected:
strobe_state_t state_; // state machine cycle state

View File

@ -1,107 +0,0 @@
/*
sacn.cpp
Copyright (c) 2020 Kevin Matz (kevin.matz@gmail.com)
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.
*/
#include <Arduino.h>
#include <algorithm>
#include <iterator>
#include "ESPsACN.h"
// ESP logging module
#include "esp_log.h"
static const char* TAG = "ESPsACN";
ESPsACN::ESPsACN() {
udp.onPacket(std::bind(&ESPsACN::parsePacket, this,
std::placeholders::_1));
}
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),
ACN_SDT_MULTICAST_PORT);
Universe *u = new Universe();
universes_.emplace(num, u);
return success;
}
void ESPsACN::parsePacket(AsyncUDPPacket _packet) {
buff_ = reinterpret_cast<e131_packet_t *>(_packet.data());
// 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);
}
IPAddress ESPsACN::E131MulticastAddress(uint16_t universe) {
return IPAddress(239, 255, ((universe >> 8) & 0xff), ((universe >> 0) & 0xff));
}

View File

@ -1,128 +0,0 @@
/*
sacn.h
Copyright (c) 2020 Kevin Matz (kevin.matz@gmail.com)
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.
*/
#pragma once
#include <AsyncUDP.h>
#include <unordered_map>
#include "dmx_universe.h"
// Appendix A: Defined Parameters (Normative)
#define VECTOR_ROOT_E131_DATA 0x00000004
#define VECTOR_ROOT_E131_EXTENDED 0x00000008
#define VECTOR_DMP_SET_PROPERTY 0x02 // informative
#define VECTOR_E131_DATA_PACKET 0x00000002
#define VECTOR_E131_EXTENDED_SYNCHRONIZATION 0x00000001
#define VECTOR_E131_EXTENDED_DISCOVERY 0x00000002
#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 {
uint16_t preamble_size;
uint16_t postamble_size;
uint8_t acn_id[12];
uint16_t flength;
uint32_t vector;
uint8_t cid[16];
} __attribute__((packed));
uint8_t raw[37];
} e131_rlp_t;
// 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;
// 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;
typedef std::function<void(e131_packet_t *packet)> E131PacketHandlerFunction;
class ESPsACN {
public:
ESPsACN();
bool subscribe(uint16_t universe = 1);
static IPAddress E131MulticastAddress(uint16_t universe);
Universe * universe(uint16_t num) {
return universes_.at(num);
}
private:
e131_packet_t *buff_; // Pointer to scratch packet buffer
AsyncUDP udp; // AsyncUDP
// UDP packet parser callback
void parsePacket(AsyncUDPPacket);
std::unordered_map <uint16_t, Universe*> universes_;
};

View File

@ -0,0 +1,49 @@
/*
data.h
Copyright (c) 2020 Kevin Matz (kevin.matz@gmail.com)
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.
*/
#include "data.h"
#include "receiver.h"
namespace SACN {
namespace DATA {
using namespace ACN;
frame_header::frame_header(PDU::stream_ptr stream) {
stream->read(source_name, 64);
priority = PDU::read8(stream);
reserved = PDU::read16(stream);
sequence_number = PDU::read8(stream);
options = PDU::read8(stream);
universe = PDU::read16(stream);
}
void readHeader(PDU::pdu_ptr pdu) {
if (pdu->flags().hasHeader) {
frame_header * header = new frame_header(pdu->buffer());
pdu->setHeader(header);
}
}
} // DATA
} // SACN

View File

@ -0,0 +1,54 @@
/*
data.h
Copyright (c) 2020 Kevin Matz (kevin.matz@gmail.com)
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.
*/
#pragma once
#include "sacn.h"
#include "receiver.h"
namespace SACN {
namespace DATA {
using namespace ACN;
// Table 6-1: E1.31 Data Packet Framing Layer
struct frame_header : PDU::pdu_header {
uint8_t source_name[64];
uint8_t priority;
uint16_t reserved;
uint8_t sequence_number;
uint8_t options;
uint16_t universe;
frame_header(PDU::stream_ptr);
};
// 6.2.6 E1.31 Data Packet: Options
enum options_t : uint8_t {
PREVIEW_DATA = 0b10000000, // Bit 7 = Preview_Data
STREAM_TERMINATED = 0b01000000, // Bit 6 = Stream_Terminated
FORCE_SYNCHRONIZATION = 0b00100000, // Bit 5 = Force_Synchronization
};
void readHeader(PDU::pdu_ptr);
} // DATA
} // SACN

View File

@ -0,0 +1,55 @@
/*
extended.h
Copyright (c) 2020 Kevin Matz (kevin.matz@gmail.com)
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.
*/
#pragma once
#include "sacn.h"
#include "receiver.h"
namespace SACN {
namespace EXTENDED {
using namespace ACN;
// 6.3 E1.31 Synchronization Packet Framing Layer
struct frame_sync_header : PDU::pdu_header {
uint8_t sequence_number;
uint16_t sync_address;
uint8_t reserved[2];
};
// 6.4 E1.31 Universe Discovery Packet Framing Layer
struct frame_discovery_header : PDU::pdu_header {
uint8_t source_name[64];
uint8_t reserved[4];
};
// Table 8-9: E1.31 Universe Discovery Packet Universe Discovery Layer
struct discovery_list_header : PDU::pdu_header {
uint8_t page;
uint8_t last_page;
};
} // EXTENDED
} // SACN

View File

@ -0,0 +1,54 @@
/*
receiver-esp.cpp
Copyright (c) 2020 Kevin Matz (kevin.matz@gmail.com)
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.
*/
#include "receiver-esp.h"
// ESP logging module
#include "esp_log.h"
static const char* TAG = "ESPsACN";
using namespace SACN;
EspReceiver::EspReceiver()
: Receiver()
{
udp.onPacket(std::bind(&EspReceiver::udpHandler, this,
std::placeholders::_1));
}
void EspReceiver::subscribe(uint16_t num) {
Receiver::subscribe(num);
// listen multicast; works for unicast too
udp.listenMulticast(IPv4MulticastAddress(num),
ACN_SDT_MULTICAST_PORT);
}
void EspReceiver::udpHandler(AsyncUDPPacket udp) {
// Expecting IANA registered Session Data Transport traffic
if (!udp.localPort() == ACN_SDT_MULTICAST_PORT)
return;
PDU::stream_ptr packet(new PDU::pdu_stream(udp.data(), udp.available()));
packetHandler(packet); // from base class, SACN::Receiver
}

View File

@ -0,0 +1,68 @@
/*
receiver-esp.h
Copyright (c) 2020 Kevin Matz (kevin.matz@gmail.com)
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.
*/
#pragma once
#include <Arduino.h>
#include <IPAddress.h>
#include <IPv6Address.h>
#include <AsyncUDP.h>
#include "receiver.h"
#include "../dmx/universe.h"
/*
Arduino specification additions to the sACN library
*/
namespace SACN {
// 9.3.1 Allocation of IPv4 Multicast Addresses
// Multicast addresses are from the IPv4 Local Scope.
inline IPAddress IPv4MulticastAddress(uint16_t universe) {
return IPAddress(239, 255, (universe >> 8), (universe & 0xff));
};
// 9.3.2 Allocation of IPv6 Multicast Addresses
inline IPv6Address IPv6MulticastAddress(uint16_t universe) {
IPv6Address address;
address.fromString("ff18::83:00:" +
String(universe >> 8, HEX) + ":" +
String(universe & 0xff, HEX));
return address;
};
} // SACN
using namespace SACN;
class EspReceiver
: public Receiver
{
public:
EspReceiver();
void subscribe(uint16_t universe = 1);
private:
AsyncUDP udp; // AsyncUDP
void udpHandler(AsyncUDPPacket); // UDP packet parser callback
};

View File

@ -0,0 +1,120 @@
/*
receiver.cpp
Copyright (c) 2020 Kevin Matz (kevin.matz@gmail.com)
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.
*/
#include "receiver.h"
#include "data.h"
namespace SACN {
using namespace ACN;
void Receiver::subscribe(const uint16_t universe) {
if (universes_.count(universe))
return;
universes_.emplace(universe, new SACN::Universe());
}
SACN::Universe * Receiver::universe(uint16_t universe) {
if (universes_.count(universe))
return universes_.at(universe);
return nullptr;
}
void Receiver::packetHandler(PDU::stream_ptr packet) {
PDU::block_ptr block = RLP::UDP::readBlock(packet);
for(PDU::pdu_ptr pdu : *block) {
// read RLP header
RLP::readHeader(pdu);
// 5.5 Vector
// Receivers shall discard the packet if the received value is not
// VECTOR_ROOT_E131_DATA or VECTOR_ROOT_E131_EXTENDED.
switch (pdu->vector()) {
case VECTOR_ROOT_E131_DATA:
rootDataHandler(pdu);
break;
case VECTOR_ROOT_E131_EXTENDED:
break;
default:
break;
}
}
}
void Receiver::rootDataHandler(PDU::pdu_ptr pdu) {
PDU::block_ptr block = PDU::readBlock(pdu->buffer());
// PDU data will be a block of one E1.31 frame
for(PDU::pdu_ptr child : *block) {
child->setParent(pdu);
// 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. This value indicates that the E1.31 framing
// layer is wrapping a DMP PDU.
switch(child->vector()) {
case VECTOR_E131_DATA_PACKET:
dataPacketHandler(child);
break;
default:
break;
}
}
}
void Receiver::dataPacketHandler(PDU::pdu_ptr pdu) {
DATA::readHeader(pdu);
DATA::frame_header * header = (DATA::frame_header*)pdu->header();
if (!universes_.count(header->universe))
return;
Universe * universe = universes_.at(header->universe);
// TODO: do something with merging/priorty
// RLP::rlp_header * root_header = (RLP::rlp_header*)pdu->parent()->header();
// uint8_t * cid = root_header->cid;
// PDU data will be a block of one DMP PDU
PDU::block_ptr block = DMP::readBlock(pdu->buffer());
for (PDU::pdu_ptr dmp : *block) {
dmp->setParent(pdu);
// all DMP messages have the same header format
DMP::readHeader(dmp);
// 7.2 DMP Layer: Vector
// The DMP Layer's Vector shall be set to VECTOR_DMP_SET_PROPERTY, which
// indicates a DMP Set Property message by sources. Receivers shall discard
// the packet if the received value is not VECTOR_DMP_SET_PROPERTY.
switch(dmp->vector()) {
case DMP::SET_PROPERTY:
DMP::readSet(dmp);
universe->set(dmp);
break;
default:
break;
}
}
}
}; // SACN

View File

@ -0,0 +1,50 @@
/*
receiver.h
Copyright (c) 2020 Kevin Matz (kevin.matz@gmail.com)
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.
*/
#pragma once
#include "sacn.h"
#include "universe.h"
#include <unordered_map>
namespace SACN {
using namespace ACN;
class Receiver
{
public:
Receiver() {};
void subscribe(const uint16_t universe);
SACN::Universe * universe(uint16_t universe);
protected:
void packetHandler(PDU::stream_ptr);
void rootDataHandler(PDU::pdu_ptr);
void dataPacketHandler(PDU::pdu_ptr);
private:
std::unordered_map <uint16_t, SACN::Universe *> universes_;
};
};

View File

@ -0,0 +1,52 @@
/*
sacn.h
Copyright (c) 2020 Kevin Matz (kevin.matz@gmail.com)
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.
*/
#pragma once
#include <stdint.h>
#include <unordered_map>
#include "../acn/acn.h"
// E1.31 Lightweight streaming protocol for transport of DMX512 using ACN
namespace SACN {
using namespace ACN;
// Appendix A: Defined Parameters (Normative)
static const uint32_t VECTOR_ROOT_E131_DATA = 0x00000004;
static const uint32_t VECTOR_ROOT_E131_EXTENDED = 0x00000008;
static const uint8_t VECTOR_DMP_SET_PROPERTY = 0x02;
static const uint32_t VECTOR_E131_DATA_PACKET = 0x00000002;
static const uint32_t VECTOR_E131_EXTENDED_SYNCHRONIZATION = 0x00000001;
static const uint32_t VECTOR_E131_EXTENDED_DISCOVERY = 0x00000002;
static const uint32_t VECTOR_UNIVERSE_DISCOVERY_UNIVERSE_LIST = 0x00000001;
static const uint16_t E131_E131_UNIVERSE_DISCOVER_INTERVAL = 10000; // ms
static const uint16_t E131_NETWORK_DATA_LOSS_TIMEOUT = 2500; // ms
static const uint16_t E131_DISCOVERY_UNIVERSE = 64214;
static const uint16_t ACN_SDT_MULTICAST_PORT = 5568;
} // SACN

View File

@ -0,0 +1,75 @@
/*
universe.cpp
Copyright (c) 2020 Kevin Matz (kevin.matz@gmail.com)
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.
*/
#include "universe.h"
namespace SACN {
using namespace ACN;
Universe::Universe()
: DMX::Universe()
{
}
void Universe::set(PDU::pdu_ptr pdu) {
// 7.3 Address Type and Data Type
// Sources shall set the DMP Layer's Address Type and Data Type to 0xa1.
// Receivers shall discard the packet if the received value is not 0xa1.
DMP::address_type * type = (DMP::address_type*)pdu->header();
if (!type->z_reserved) return; // needs to be true, but why?
if (type->relative) return;
if (type->type != DMP::ARRAY) return;
if (type->x_reserved != 0) return;
if (type->width != DMP::TWO) return;
// only accept the first property pair in the data
DMP::dmp_set_data * data = (DMP::dmp_set_data*)pdu->data();
DMP::set_property property = data->properties.front();
DMP::range pr = property.first;
// 7.4 First Property Address
// Sources shall set the DMP Layer's First Property Address to 0x0000.
// Receivers shall discard the packet if the received value is not 0x0000.
if (pr.address != 0)
return;
// 7.5 Address Increment
// Sources shall set the DMP Layer's Address Increment to 0x0001.
// Receivers shall discard the packet if the received value is not 0x0001.
if (pr.incriment != 1)
return;
// 7.6 Property Value Count
// The DMP Layer's Property Value Count is used to encode the number of
// DMX512-A [DMX] Slots (including the START Code slot).
if (pr.count < 1 || pr.count > 513)
return;
// 7.7 Property Values (DMX512-A Data)
/// The DMP Layer's Property values field is used to encode the
// DMX512-A [DMX] START Code and data.
DMX::Universe::set(property.second);
}
};

View File

@ -0,0 +1,41 @@
/*
universe.h
Copyright (c) 2020 Kevin Matz (kevin.matz@gmail.com)
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.
*/
#pragma once
#include "sacn.h"
#include "../dmx/universe.h"
namespace SACN {
using namespace ACN;
class Universe
: public DMX::Universe
{
public:
Universe();
void set(PDU::pdu_ptr);
};
};

View File

@ -35,7 +35,7 @@
#include <ESPAsyncWebServer.h>
#include "src/wiflash_configure.h"
#include "src/wiflash_status.h"
#include "src/lib/sacn/ESPsACN.h"
#include "src/lib/sacn/receiver-esp.h"
#include "src/lib/fixture/EspStrobe.h"
// ESP logging module
@ -46,7 +46,7 @@ static const char* TAG = "WiFlash";
//// device objects
EspStrobe *strobe;
NeoPixelBus<NeoGrbwFeature, Neo800KbpsMethod> *strip;
ESPsACN *e131 = new ESPsACN();
EspReceiver *e131 = new EspReceiver();
AsyncWebServer *httpd = new AsyncWebServer(80);
@ -112,13 +112,13 @@ void startHTTPD() {
/*
change pixel strip colours on DMX change
*/
void recvPixelData(Universe *universe) {
void recvPixelData(DMX::Universe *universe) {
for (int i = 0; i < config::settings.strip_led_count; i++) {
uint8_t r, g, b, w;
r = universe->data()->data[config::settings.strip_address + (i * 4) + 0];
g = universe->data()->data[config::settings.strip_address + (i * 4) + 1];
b = universe->data()->data[config::settings.strip_address + (i * 4) + 2];
w = universe->data()->data[config::settings.strip_address + (i * 4) + 3];
r = universe->slot(config::settings.strip_address + (i * 4) + 0);
g = universe->slot(config::settings.strip_address + (i * 4) + 1);
b = universe->slot(config::settings.strip_address + (i * 4) + 2);
w = universe->slot(config::settings.strip_address + (i * 4) + 3);
strip->SetPixelColor(i, RgbwColor(r, g, b, w));
}
strip->Show();
@ -130,6 +130,7 @@ void recvPixelData(Universe *universe) {
*/
void setup() {
Serial.begin(115200);
Serial.setDebugOutput(true);
while (!Serial); // wait for serial attach
ESP_LOGI(TAG, "Serial started.");
@ -205,12 +206,9 @@ void setup() {
if (!strobe->begin(config::settings.strobe_led_pin, 0)) {
ESP_LOGW(TAG, "Strobe failed to configure.");
}
if (e131->subscribe(config::settings.strobe_universe)) {
e131->universe(config::settings.strobe_universe)->onData(std::bind(&EspStrobe::recvData,
strobe, std::placeholders::_1));
} else {
ESP_LOGW(TAG, "Failed to subscribe to universe: %u", config::settings.strobe_universe);
}
e131->subscribe(config::settings.strobe_universe);
e131->universe(config::settings.strobe_universe)->onData(std::bind(&EspStrobe::recvData,
strobe, std::placeholders::_1));
}
// pixels
@ -220,10 +218,10 @@ void setup() {
strip = new NeoPixelBus<NeoGrbwFeature, Neo800KbpsMethod>(config::settings.strip_led_count, config::settings.strip_data_pin);
strip->Begin();
strip->Show();
if (e131->subscribe(config::settings.strip_universe)) {
e131->universe(config::settings.strip_universe)->onData(std::bind(&recvPixelData,
std::placeholders::_1));
}
e131->subscribe(config::settings.strip_universe);
e131->universe(config::settings.strip_universe)->onData(std::bind(&recvPixelData,
std::placeholders::_1));
}
ESP_LOGV(TAG, "Setup complete.");