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
+}