diff --git a/assets/webconfig/js/content_leds.js b/assets/webconfig/js/content_leds.js index f1d37f30..8f426b30 100644 --- a/assets/webconfig/js/content_leds.js +++ b/assets/webconfig/js/content_leds.js @@ -474,7 +474,7 @@ $(document).ready(function() { devRPiSPI = ['apa102', 'ws2801', 'lpd6803', 'lpd8806', 'p9813', 'sk6812spi', 'sk6822spi', 'ws2812spi']; devRPiPWM = ['ws281x']; devRPiGPIO = ['piblaster']; - devNET = ['atmoorb', 'fadecandy', 'philipshue', 'tinkerforge', 'tpm2net', 'udpe131', 'udph801', 'udpraw']; + devNET = ['atmoorb', 'fadecandy', 'philipshue', 'tinkerforge', 'tpm2net', 'udpe131', 'udpartnet', 'udph801', 'udpraw']; devUSB = ['adalight', 'dmx', 'atmo', 'hyperionusbasp', 'lightpack', 'multilightpack', 'paintpack', 'rawhid', 'sedu', 'tpm2']; var optArr = [[]]; diff --git a/libsrc/leddevice/CMakeLists.txt b/libsrc/leddevice/CMakeLists.txt index b1357282..40d0a6c6 100755 --- a/libsrc/leddevice/CMakeLists.txt +++ b/libsrc/leddevice/CMakeLists.txt @@ -37,6 +37,7 @@ SET(Leddevice_HEADERS ${CURRENT_SOURCE_DIR}/LedDeviceUdpRaw.h ${CURRENT_SOURCE_DIR}/LedDeviceUdpH801.h ${CURRENT_SOURCE_DIR}/LedDeviceUdpE131.h + ${CURRENT_SOURCE_DIR}/LedDeviceUdpArtNet.h ${CURRENT_SOURCE_DIR}/ProviderUdp.h ${CURRENT_SOURCE_DIR}/LedDeviceHyperionUsbasp.h ${CURRENT_SOURCE_DIR}/LedDeviceTpm2.h @@ -65,6 +66,7 @@ SET(Leddevice_SOURCES ${CURRENT_SOURCE_DIR}/LedDeviceUdpRaw.cpp ${CURRENT_SOURCE_DIR}/LedDeviceUdpH801.cpp ${CURRENT_SOURCE_DIR}/LedDeviceUdpE131.cpp + ${CURRENT_SOURCE_DIR}/LedDeviceUdpArtNet.cpp ${CURRENT_SOURCE_DIR}/ProviderUdp.cpp ${CURRENT_SOURCE_DIR}/LedDeviceHyperionUsbasp.cpp ${CURRENT_SOURCE_DIR}/LedDevicePhilipsHue.cpp diff --git a/libsrc/leddevice/LedDeviceFactory.cpp b/libsrc/leddevice/LedDeviceFactory.cpp index 012ed993..ed022f7f 100755 --- a/libsrc/leddevice/LedDeviceFactory.cpp +++ b/libsrc/leddevice/LedDeviceFactory.cpp @@ -39,6 +39,7 @@ #include "LedDeviceTpm2net.h" #include "LedDeviceUdpRaw.h" #include "LedDeviceUdpE131.h" +#include "LedDeviceUdpArtNet.h" #include "LedDeviceHyperionUsbasp.h" #include "LedDevicePhilipsHue.h" #include "LedDeviceTpm2.h" @@ -91,6 +92,7 @@ LedDevice * LedDeviceFactory::construct(const QJsonObject & deviceConfig, const REGISTER(Tpm2net); REGISTER(UdpRaw); REGISTER(UdpE131); + REGISTER(UdpArtNet); REGISTER(UdpH801); REGISTER(PhilipsHue); REGISTER(AtmoOrb); diff --git a/libsrc/leddevice/LedDeviceSchemas.qrc b/libsrc/leddevice/LedDeviceSchemas.qrc index 80701d44..896c4463 100644 --- a/libsrc/leddevice/LedDeviceSchemas.qrc +++ b/libsrc/leddevice/LedDeviceSchemas.qrc @@ -24,6 +24,7 @@ schemas/schema-tpm2net.json schemas/schema-tpm2.json schemas/schema-e131.json + schemas/schema-artnet.json schemas/schema-h801.json schemas/schema-udpraw.json schemas/schema-ws2801.json diff --git a/libsrc/leddevice/LedDeviceUdpArtNet.cpp b/libsrc/leddevice/LedDeviceUdpArtNet.cpp new file mode 100644 index 00000000..8738e972 --- /dev/null +++ b/libsrc/leddevice/LedDeviceUdpArtNet.cpp @@ -0,0 +1,93 @@ +#include +#include + +// hyperion local includes +#include "LedDeviceUdpArtNet.h" + +LedDeviceUdpArtNet::LedDeviceUdpArtNet(const QJsonObject &deviceConfig) + : ProviderUdp() +{ + _deviceReady = init(deviceConfig); +} + +bool LedDeviceUdpArtNet::init(const QJsonObject &deviceConfig) +{ + _port = 6454; + ProviderUdp::init(deviceConfig); + _artnet_universe = deviceConfig["universe"].toInt(1); + _artnet_channelsPerFixture = deviceConfig["channelsPerFixture"].toInt(3); + + return true; +} + +LedDevice* LedDeviceUdpArtNet::construct(const QJsonObject &deviceConfig) +{ + return new LedDeviceUdpArtNet(deviceConfig); +} + + +// populates the headers +void LedDeviceUdpArtNet::prepare(const unsigned this_universe, const unsigned this_sequence, unsigned this_dmxChannelCount) +{ +// WTF? why do the specs say: +// "This value should be an even number in the range 2 – 512. " + if (this_dmxChannelCount & 0x1) + { + this_dmxChannelCount++; + } + + memcpy (artnet_packet.ID, "Art-Net\0", 8); + + artnet_packet.OpCode = htons(0x0050); // OpOutput / OpDmx + artnet_packet.ProtVer = htons(0x000e); + artnet_packet.Sequence = this_sequence; + artnet_packet.Physical = 0; + artnet_packet.SubUni = this_universe & 0xff ; + artnet_packet.Net = (this_universe >> 8) & 0x7f; + artnet_packet.Length = htons(this_dmxChannelCount); + +} + +int LedDeviceUdpArtNet::write(const std::vector &ledValues) +{ + int retVal = 0; + int thisUniverse = _artnet_universe; + const uint8_t * rawdata = reinterpret_cast(ledValues.data()); + +/* +This field is incremented in the range 0x01 to 0xff to allow the receiving node to resequence packets. +The Sequence field is set to 0x00 to disable this feature. +*/ + if (_artnet_seq++ == 0) + { + _artnet_seq = 1; + } + + int dmxIdx = 0; // offset into the current dmx packet + + memset(artnet_packet.raw, 0, sizeof(artnet_packet.raw)); + for (int ledIdx = 0; ledIdx < _ledRGBCount; ledIdx++) + { + + artnet_packet.Data[dmxIdx++] = rawdata[ledIdx]; + if ( (ledIdx % 3 == 2) && (ledIdx > 0) ) + { + dmxIdx += (_artnet_channelsPerFixture-3); + } + +// is this the last byte of last packet || last byte of other packets + if ( (ledIdx == _ledRGBCount-1) || (dmxIdx >= DMX_MAX) ) + { + prepare(thisUniverse, _artnet_seq, dmxIdx); + retVal &= writeBytes(18 + std::min(dmxIdx, DMX_MAX), artnet_packet.raw); + + memset(artnet_packet.raw, 0, sizeof(artnet_packet.raw)); + thisUniverse ++; + dmxIdx = 0; + } + + } + + return retVal; +} + diff --git a/libsrc/leddevice/LedDeviceUdpArtNet.h b/libsrc/leddevice/LedDeviceUdpArtNet.h new file mode 100644 index 00000000..08abede2 --- /dev/null +++ b/libsrc/leddevice/LedDeviceUdpArtNet.h @@ -0,0 +1,79 @@ +#pragma once + +// hyperion includes +#include "ProviderUdp.h" + +#include + +/** + * + * 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. + * + **/ + +#define ArtNet_DEFAULT_PORT 5568 + +#define DMX_MAX 512 // 512 usable slots + +// http://stackoverflow.com/questions/16396013/artnet-packet-structure +typedef union +{ + struct { + char ID[8]; // "Art-Net" + uint16_t OpCode; // See Doc. Table 1 - OpCodes eg. 0x5000 OpOutput / OpDmx + uint16_t ProtVer; // 0x0e00 (aka 14) + uint8_t Sequence; // monotonic counter + uint8_t Physical; // 0x00 + uint8_t SubUni; // low universe (0-255) + uint8_t Net; // high universe (not used) + uint16_t Length; // data length (2 - 512) + uint8_t Data[ DMX_MAX ]; // universe data + } __attribute__((packed)); + + uint8_t raw[ 18 + DMX_MAX ]; + +} artnet_packet_t; + +/// +/// Implementation of the LedDevice interface for sending led colors via udp/E1.31 packets +/// +class LedDeviceUdpArtNet : public ProviderUdp +{ +public: + /// + /// Constructs specific LedDevice + /// + /// @param deviceConfig json device config + /// + LedDeviceUdpArtNet(const QJsonObject &deviceConfig); + + /// + /// Sets configuration + /// + /// @param deviceConfig the json device config + /// @return true if success + bool init(const QJsonObject &deviceConfig); + + /// constructs leddevice + static LedDevice* construct(const QJsonObject &deviceConfig); + + +private: + /// + /// Writes the led color values to the led-device + /// + /// @param ledValues The color-value per led + /// @return Zero on succes else negative + /// + virtual int write(const std::vector &ledValues); + + void prepare(const unsigned this_universe, const unsigned this_sequence, const unsigned this_dmxChannelCount); + + + artnet_packet_t artnet_packet; + uint8_t _artnet_seq = 1; + uint8_t _artnet_channelsPerFixture = 3; + unsigned _artnet_universe = 1; +}; diff --git a/libsrc/leddevice/schemas/schema-artnet.json b/libsrc/leddevice/schemas/schema-artnet.json new file mode 100644 index 00000000..e2557dab --- /dev/null +++ b/libsrc/leddevice/schemas/schema-artnet.json @@ -0,0 +1,42 @@ +{ + "type":"object", + "required":true, + "properties":{ + "host" : { + "type": "string", + "title":"edt_dev_spec_targetIp_title", + "propertyOrder" : 1 + }, + "port" : { + "type": "integer", + "title":"edt_dev_spec_port_title", + "default": 6454, + "minimum" : 0, + "maximum" : 65535, + "propertyOrder" : 2 + }, + "universe": { + "type": "integer", + "title":"edt_dev_spec_universe_title", + "default": 1, + "propertyOrder" : 3 + }, + "channelsPerFixture": { + "type": "integer", + "title":"edt_dev_spec_chanperfixture_title", + "default": 3, + "propertyOrder" : 4 + }, + "latchTime": { + "type": "integer", + "title":"edt_dev_spec_latchtime_title", + "default": 1, + "append" : "edt_append_ms", + "minimum": 1, + "maximum": 1000, + "access" : "expert", + "propertyOrder" : 5 + } + }, + "additionalProperties": true +}