diff --git a/assets/webconfig/js/content_leds.js b/assets/webconfig/js/content_leds.js
index 575c76e7..2cf7e07b 100644
--- a/assets/webconfig/js/content_leds.js
+++ b/assets/webconfig/js/content_leds.js
@@ -470,7 +470,7 @@ $(document).ready(function() {
// create led device selection
ledDevices = serverInfo.ledDevices.available
- devRPiSPI = ['apa102', 'ws2801', 'lpd6803', 'lpd8806', 'p9813', 'sk6812spi', 'sk6822spi', 'ws2812spi'];
+ devRPiSPI = ['apa102', 'apa104', 'ws2801', 'lpd6803', 'lpd8806', 'p9813', 'sk6812spi', 'sk6822spi', 'ws2812spi'];
devRPiPWM = ['ws281x'];
devRPiGPIO = ['piblaster'];
devNET = ['atmoorb', 'fadecandy', 'philipshue', 'tinkerforge', 'tpm2net', 'udpe131', 'udpartnet', 'udph801', 'udpraw'];
diff --git a/libsrc/leddevice/LedDeviceSchemas.qrc b/libsrc/leddevice/LedDeviceSchemas.qrc
index bd7afbae..9536835f 100644
--- a/libsrc/leddevice/LedDeviceSchemas.qrc
+++ b/libsrc/leddevice/LedDeviceSchemas.qrc
@@ -29,6 +29,7 @@
schemas/schema-udpraw.json
schemas/schema-ws2801.json
schemas/schema-ws2812spi.json
+ schemas/schema-apa104.json
schemas/schema-ws281x.json
schemas/schema-karate.json
diff --git a/libsrc/leddevice/dev_spi/LedDeviceAPA104.cpp b/libsrc/leddevice/dev_spi/LedDeviceAPA104.cpp
new file mode 100644
index 00000000..3a99f1c1
--- /dev/null
+++ b/libsrc/leddevice/dev_spi/LedDeviceAPA104.cpp
@@ -0,0 +1,95 @@
+#include "LedDeviceAPA104.h"
+
+/*
+From the data sheet:
+
+(TH+TL=1.25μs±600ns)
+
+T0H, 0 code, high level time, 350ns ±150ns
+T0L, 0 code, low level time, 1360ns ±150ns
+T1H, 1 code, high level time, 360ns ±150ns
+T1L, 1 code, low level time, 45ns ±150ns
+WT, Wait for the processing time, NA
+Trst, Reset code,low level time, 24µs
+
+To normalise the pulse times so they fit in 4 SPI bits:
+
+On the assumption that the "low" time doesnt matter much
+
+A SPI bit time of 0.40uS = 2.5 Mbit/sec
+T0 is sent as 1000
+T1 is sent as 1110
+
+With a bit of excel testing, we can work out the maximum and minimum speeds:
+2000000 MIN
+2235000 AVG
+2470000 MAX
+
+Wait time:
+Not Applicable for WS2812
+
+Reset time:
+using the max of 2470000, the bit time is 405nS
+Reset time is 24uS = 59 bits = 8 bytes
+
+*/
+
+LedDeviceAPA104::LedDeviceAPA104(const QJsonObject &deviceConfig)
+ : ProviderSpi()
+ , SPI_BYTES_PER_COLOUR(4)
+ , SPI_FRAME_END_LATCH_BYTES(116)
+ , bitpair_to_byte {
+ 0b10001000,
+ 0b10001100,
+ 0b11001000,
+ 0b11001100,
+ }
+{
+ _deviceReady = init(deviceConfig);
+}
+
+LedDevice* LedDeviceAPA104::construct(const QJsonObject &deviceConfig)
+{
+ return new LedDeviceAPA104(deviceConfig);
+}
+
+bool LedDeviceAPA104::init(const QJsonObject &deviceConfig)
+{
+ _baudRate_Hz = 2235000;
+ if ( !ProviderSpi::init(deviceConfig) )
+ {
+ return false;
+ }
+ WarningIf(( _baudRate_Hz < 2000000 || _baudRate_Hz > 2470000 ), _log, "SPI rate %d outside recommended range (2000000 -> 2470000)", _baudRate_Hz);
+
+ _ledBuffer.resize(_ledRGBCount * SPI_BYTES_PER_COLOUR + SPI_FRAME_END_LATCH_BYTES, 0x00);
+
+ return true;
+}
+
+int LedDeviceAPA104::write(const std::vector &ledValues)
+{
+ unsigned spi_ptr = 0;
+ const int SPI_BYTES_PER_LED = sizeof(ColorRgb) * SPI_BYTES_PER_COLOUR;
+
+ for (const ColorRgb& color : ledValues)
+ {
+ uint32_t colorBits = ((unsigned int)color.red << 16)
+ | ((unsigned int)color.green << 8)
+ | color.blue;
+
+ for (int j=SPI_BYTES_PER_LED - 1; j>=0; j--)
+ {
+ _ledBuffer[spi_ptr+j] = bitpair_to_byte[ colorBits & 0x3 ];
+ colorBits >>= 2;
+ }
+ spi_ptr += SPI_BYTES_PER_LED;
+ }
+
+ for (int j=0; j < SPI_FRAME_END_LATCH_BYTES; j++)
+ {
+ _ledBuffer[spi_ptr++] = 0;
+ }
+
+ return writeBytes(_ledBuffer.size(), _ledBuffer.data());
+}
diff --git a/libsrc/leddevice/dev_spi/LedDeviceAPA104.h b/libsrc/leddevice/dev_spi/LedDeviceAPA104.h
new file mode 100644
index 00000000..23f9500d
--- /dev/null
+++ b/libsrc/leddevice/dev_spi/LedDeviceAPA104.h
@@ -0,0 +1,43 @@
+#pragma once
+
+// hyperion incluse
+#include "ProviderSpi.h"
+
+///
+/// Implementation of the LedDevice interface for writing to APA104 led device via spi.
+///
+class LedDeviceAPA104 : public ProviderSpi
+{
+public:
+ ///
+ /// Constructs specific LedDevice
+ ///
+ /// @param deviceConfig json device config
+ ///
+ LedDeviceAPA104(const QJsonObject &deviceConfig);
+
+ /// constructs leddevice
+ static LedDevice* construct(const QJsonObject &deviceConfig);
+
+ ///
+ /// Sets configuration
+ ///
+ /// @param deviceConfig the json device config
+ /// @return true if success
+ virtual bool init(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);
+
+ const int SPI_BYTES_PER_COLOUR;
+
+ const int SPI_FRAME_END_LATCH_BYTES;
+
+ uint8_t bitpair_to_byte[4];
+};
diff --git a/libsrc/leddevice/schemas/schema-apa104.json b/libsrc/leddevice/schemas/schema-apa104.json
new file mode 100644
index 00000000..0478669d
--- /dev/null
+++ b/libsrc/leddevice/schemas/schema-apa104.json
@@ -0,0 +1,35 @@
+{
+ "type":"object",
+ "required":true,
+ "properties":{
+ "output": {
+ "type": "string",
+ "title":"edt_dev_spec_spipath_title",
+ "enum" : ["/dev/spidev0.0","/dev/spidev0.1"],
+ "propertyOrder" : 1
+ },
+ "rate": {
+ "type": "integer",
+ "title":"edt_dev_spec_baudrate_title",
+ "default": 2235000,
+ "propertyOrder" : 2
+ },
+ "invert": {
+ "type": "boolean",
+ "title":"edt_dev_spec_invert_title",
+ "default": false,
+ "propertyOrder" : 3
+ },
+ "latchTime": {
+ "type": "integer",
+ "title":"edt_dev_spec_latchtime_title",
+ "default": 1,
+ "append" : "edt_append_ms",
+ "minimum": 1,
+ "maximum": 1000,
+ "access" : "expert",
+ "propertyOrder" : 4
+ }
+ },
+ "additionalProperties": true
+}