diff --git a/assets/webconfig/i18n/de.json b/assets/webconfig/i18n/de.json
index 3ff99434..f876dc1c 100644
--- a/assets/webconfig/i18n/de.json
+++ b/assets/webconfig/i18n/de.json
@@ -466,6 +466,8 @@
"edt_dev_spec_intervall_title": "Intervall",
"edt_dev_spec_invert_title": "Invertiere Signal",
"edt_dev_spec_latchtime_title": "Sperrzeit",
+ "edt_dev_spec_globalBrightnessControlMaxLevel_title": "Maximalstufe Stromstärke",
+ "edt_dev_spec_globalBrightnessControlThreshold_title": "Grenzwert für adaptive Stromstärke",
"edt_dev_spec_ledIndex_title": "LED-Index",
"edt_dev_spec_ledType_title": "LED-Typ",
"edt_dev_spec_lightid_itemtitle": "ID",
@@ -917,4 +919,4 @@
"wiz_yeelight_noLights": "Es wurden keine Yeelights gefunden! Bitte verbinde die Yeelights mit dem Netzwerk oder konfiguriere sie manuell.",
"wiz_yeelight_title": "Yeelight Einrichtungsassistent",
"wiz_yeelight_unsupported": "Nicht unterstützt"
-}
\ No newline at end of file
+}
diff --git a/assets/webconfig/i18n/en.json b/assets/webconfig/i18n/en.json
index 47ac6882..5bb4bb28 100644
--- a/assets/webconfig/i18n/en.json
+++ b/assets/webconfig/i18n/en.json
@@ -466,6 +466,8 @@
"edt_dev_spec_intervall_title": "Interval",
"edt_dev_spec_invert_title": "Invert signal",
"edt_dev_spec_latchtime_title": "Latch time",
+ "edt_dev_spec_globalBrightnessControlMaxLevel_title": "Max Current Level",
+ "edt_dev_spec_globalBrightnessControlThreshold_title": "Adaptive Current Threshold",
"edt_dev_spec_ledIndex_title": "LED index",
"edt_dev_spec_ledType_title": "LED Type",
"edt_dev_spec_lightid_itemtitle": "ID",
@@ -917,4 +919,4 @@
"wiz_yeelight_noLights": "No Yeelights found! Please get the lights connected to the network or configure them manually.",
"wiz_yeelight_title": "Yeelight Wizard",
"wiz_yeelight_unsupported": "Unsupported"
-}
\ No newline at end of file
+}
diff --git a/assets/webconfig/js/content_leds.js b/assets/webconfig/js/content_leds.js
index 8a092d9e..21b7f32f 100644
--- a/assets/webconfig/js/content_leds.js
+++ b/assets/webconfig/js/content_leds.js
@@ -568,7 +568,7 @@ $(document).ready(function() {
// create led device selection
var ledDevices = window.serverInfo.ledDevices.available;
- var devRPiSPI = ['apa102', 'apa104', 'ws2801', 'lpd6803', 'lpd8806', 'p9813', 'sk6812spi', 'sk6822spi', 'ws2812spi'];
+ var devRPiSPI = ['apa102', 'apa104', 'ws2801', 'lpd6803', 'lpd8806', 'p9813', 'sk6812spi', 'sk6822spi', 'sk9822', 'ws2812spi'];
var devRPiPWM = ['ws281x'];
var devRPiGPIO = ['piblaster'];
var devNET = ['atmoorb', 'fadecandy', 'philipshue', 'nanoleaf', 'tinkerforge', 'tpm2net', 'udpe131', 'udpartnet', 'udph801', 'udpraw', 'wled', 'yeelight'];
diff --git a/docs/docs/en/user/LedDevices.md b/docs/docs/en/user/LedDevices.md
index 73c150f3..ddeb1139 100644
--- a/docs/docs/en/user/LedDevices.md
+++ b/docs/docs/en/user/LedDevices.md
@@ -31,6 +31,9 @@ The SK6812 are **3** wire leds, you could also drive them via spi.
#### sk6822spi
The SK6822 are **3** wire leds, you could also drive them via spi.
+#### sk9822
+The SK9822 are **4** wire leds compatible to APA 102 with addition of global brightness control.
+
#### ws2812spi
The WS2812 are **3** wire leds, you could also drive them via spi.
diff --git a/libsrc/hyperion/schema/schema-device.json b/libsrc/hyperion/schema/schema-device.json
index 571280ae..23deb34a 100644
--- a/libsrc/hyperion/schema/schema-device.json
+++ b/libsrc/hyperion/schema/schema-device.json
@@ -39,7 +39,7 @@
{
"type" :
{
- "enum" : ["file", "apa102", "apa104", "ws2801", "lpd6803", "lpd8806", "p9813", "sk6812spi", "sk6822spi", "ws2812spi","ws281x", "piblaster", "adalight", "dmx", "atmo", "hyperionusbasp", "lightpack", "multilightpack", "paintpack", "rawhid", "sedu", "tpm2", "karate"]
+ "enum" : ["file", "apa102", "apa104", "ws2801", "lpd6803", "lpd8806", "p9813", "sk6812spi", "sk6822spi", "sk9822", "ws2812spi","ws281x", "piblaster", "adalight", "dmx", "atmo", "hyperionusbasp", "lightpack", "multilightpack", "paintpack", "rawhid", "sedu", "tpm2", "karate"]
}
},
"additionalProperties" : true
diff --git a/libsrc/leddevice/LedDeviceSchemas.qrc b/libsrc/leddevice/LedDeviceSchemas.qrc
index f90c354f..8977d9ca 100644
--- a/libsrc/leddevice/LedDeviceSchemas.qrc
+++ b/libsrc/leddevice/LedDeviceSchemas.qrc
@@ -20,6 +20,7 @@
schemas/schema-sedu.json
schemas/schema-sk6812spi.json
schemas/schema-sk6822spi.json
+ schemas/schema-sk9822.json
schemas/schema-tinkerforge.json
schemas/schema-tpm2net.json
schemas/schema-tpm2.json
diff --git a/libsrc/leddevice/dev_spi/LedDeviceSK9822.cpp b/libsrc/leddevice/dev_spi/LedDeviceSK9822.cpp
new file mode 100644
index 00000000..e0fcc84e
--- /dev/null
+++ b/libsrc/leddevice/dev_spi/LedDeviceSK9822.cpp
@@ -0,0 +1,142 @@
+#include "LedDeviceSK9822.h"
+
+// Local Hyperion includes
+#include
+
+
+/// The value that determines the higher bits of the SK9822 global brightness control field
+const int SK9822_GBC_UPPER_BITS = 0xE0;
+
+/// The maximal current level supported by the SK9822 global brightness control field, 31
+const int SK9822_GBC_MAX_LEVEL = 0x1F;
+
+LedDeviceSK9822::LedDeviceSK9822(const QJsonObject &deviceConfig)
+ : ProviderSpi(deviceConfig)
+ , _globalBrightnessControlThreshold(255)
+ , _globalBrightnessControlMaxLevel(SK9822_GBC_MAX_LEVEL)
+{
+}
+
+LedDevice *LedDeviceSK9822::construct(const QJsonObject &deviceConfig)
+{
+ return new LedDeviceSK9822(deviceConfig);
+}
+
+bool LedDeviceSK9822::init(const QJsonObject &deviceConfig)
+{
+ bool isInitOK = false;
+
+ // Initialise sub-class
+ if (ProviderSpi::init(deviceConfig))
+ {
+ _globalBrightnessControlThreshold = deviceConfig["globalBrightnessControlThreshold"].toInt(255);
+ _globalBrightnessControlMaxLevel = deviceConfig["globalBrightnessControlMaxLevel"].toInt(SK9822_GBC_MAX_LEVEL);
+ Info(_log, "[SK9822] Using global brightness control with threshold of %d and max level of %d", _globalBrightnessControlThreshold, _globalBrightnessControlMaxLevel);
+
+ const unsigned int startFrameSize = 4;
+ const unsigned int endFrameSize = qMax(((_ledCount + 15) / 16), 4);
+ const unsigned int bufferSize = (_ledCount * 4) + startFrameSize + endFrameSize;
+
+ _ledBuffer.resize(bufferSize, 0xFF);
+ _ledBuffer[0] = 0x00;
+ _ledBuffer[1] = 0x00;
+ _ledBuffer[2] = 0x00;
+ _ledBuffer[3] = 0x00;
+
+ isInitOK = true;
+ }
+ return isInitOK;
+}
+
+
+void LedDeviceSK9822::bufferWithMaxCurrent(std::vector &txBuf, const std::vector & ledValues, const int maxLevel) {
+ const int ledCount = static_cast(_ledCount);
+
+ for (int iLed = 0; iLed < ledCount; ++iLed)
+ {
+ const ColorRgb &rgb = ledValues[iLed];
+ const uint8_t red = rgb.red;
+ const uint8_t green = rgb.green;
+ const uint8_t blue = rgb.blue;
+
+ /// The LED index in the buffer
+ const int b = 4 + iLed * 4;
+
+ // Use 0/31 LED-Current for Black, and full LED-Current for all other colors,
+ // with PWM control on RGB-Channels
+ const int ored = (red|green|blue);
+
+ txBuf[b + 0] = ((ored > 0) * (maxLevel & SK9822_GBC_MAX_LEVEL)) | SK9822_GBC_UPPER_BITS; // (ored > 0) is 1 for any r,g,b > 0, 0 otherwise; branch free
+ txBuf[b + 1] = red;
+ txBuf[b + 2] = green;
+ txBuf[b + 3] = blue;
+ }
+}
+
+inline __attribute__((always_inline)) unsigned LedDeviceSK9822::scale(const uint8_t value, const int maxLevel, const uint16_t brightness) {
+ return (((maxLevel * value + (brightness >> 1)) / brightness));
+}
+
+void LedDeviceSK9822::bufferWithAdjustedCurrent(std::vector &txBuf, const std::vector & ledValues, const int threshold, const int maxLevel) {
+ const int ledCount = static_cast(_ledCount);
+
+ for (int iLed = 0; iLed < ledCount; ++iLed)
+ {
+ const ColorRgb &rgb = ledValues[iLed];
+ uint8_t red = rgb.red;
+ uint8_t green = rgb.green;
+ uint8_t blue = rgb.blue;
+ uint8_t level;
+
+ /// The LED index in the buffer
+ const int b = 4 + iLed * 4;
+
+ /// The maximal r,g,b-channel grayscale value of the LED
+ const uint16_t /* expand to 16 bit! */ maxValue = std::max(std::max(red, green), blue);
+
+ if (maxValue == 0) {
+ // Use 0/31 LED-Current for Black
+ level = 0;
+ red = 0x00;
+ green = 0x00;
+ blue = 0x00;
+ } else if (maxValue >= threshold) {
+ // Use full LED-Current when maximal r,g,b-channel grayscale value >= threshold and just use PWM control
+ level = (maxLevel & SK9822_GBC_MAX_LEVEL);
+ } else {
+ // Use adjusted LED-Current for other r,g,b-channel grayscale values
+ // See also: https://github.com/FastLED/FastLED/issues/656
+
+ // Scale the r,g,b-channel grayscale values to adjusted current = brightness level
+ const uint16_t /* 16 bit! */ brightness = (((maxValue + 1) * maxLevel - 1) >> 8) + 1;
+
+ level = (brightness & SK9822_GBC_MAX_LEVEL);
+ red = scale(red, maxLevel, brightness);
+ green = scale(green, maxLevel, brightness);
+ blue = scale(blue, maxLevel, brightness);
+ }
+
+ txBuf[b + 0] = level | SK9822_GBC_UPPER_BITS;
+ txBuf[b + 1] = red;
+ txBuf[b + 2] = green;
+ txBuf[b + 3] = blue;
+
+ //if(iLed == 0) {
+ // std::cout << std::to_string((int)rgb.red) << "," << std::to_string((int)rgb.green) << "," << std::to_string((int)rgb.blue) << ": " << std::to_string(maxValue) << (maxValue >= threshold ? " >= " : " < ") << std::to_string(threshold) << " -> " << std::to_string((int)(level&SK9822_GBC_MAX_LEVEL))<< "@" << std::to_string((int)red) << "," << std::to_string((int)green) << "," << std::to_string((int)blue) << std::endl;
+ //}
+ }
+}
+
+int LedDeviceSK9822::write(const std::vector &ledValues)
+{
+ const int threshold = _globalBrightnessControlThreshold;
+ const int maxLevel = _globalBrightnessControlMaxLevel;
+
+ if(threshold > 0) {
+ this->bufferWithAdjustedCurrent(_ledBuffer, ledValues, threshold, maxLevel);
+ } else {
+ this->bufferWithMaxCurrent(_ledBuffer, ledValues, maxLevel);
+ }
+
+ return writeBytes(_ledBuffer.size(), _ledBuffer.data());
+}
diff --git a/libsrc/leddevice/dev_spi/LedDeviceSK9822.h b/libsrc/leddevice/dev_spi/LedDeviceSK9822.h
new file mode 100644
index 00000000..da4f1f81
--- /dev/null
+++ b/libsrc/leddevice/dev_spi/LedDeviceSK9822.h
@@ -0,0 +1,83 @@
+#ifndef LEDEVICESK9822_H
+#define LEDEVICESK9822_H
+
+// hyperion includes
+#include "ProviderSpi.h"
+
+///
+/// Implementation of the LedDevice interface for writing to SK9822 led device via SPI.
+///
+class LedDeviceSK9822 : public ProviderSpi
+{
+public:
+
+ ///
+ /// @brief Constructs an SK9822 LED-device
+ ///
+ /// @param deviceConfig Device's configuration as JSON-Object
+ ///
+ explicit LedDeviceSK9822(const QJsonObject &deviceConfig);
+
+ ///
+ /// @brief Constructs the LED-device
+ ///
+ /// @param[in] deviceConfig Device's configuration as JSON-Object
+ /// @return LedDevice constructed
+ ///
+ static LedDevice* construct(const QJsonObject &deviceConfig);
+
+private:
+ ///
+ /// @brief Writes the RGB-Color values to the SPI Tx buffer setting SK9822 current level to maximal value.
+ ///
+ /// @param[in,out] txBuf The packed spi transfer buffer of the LED's color values
+ /// @param[in] ledValues The RGB-color per LED
+ /// @param[in] maxLevel The maximal current level 1 .. 31 to use
+ ///
+ void bufferWithMaxCurrent(std::vector &txBuf, const std::vector & ledValues, const int maxLevel);
+
+ ///
+ /// @brief Writes the RGB-Color values to the SPI Tx buffer using an adjusted SK9822 current level for LED maximal rgb-grayscale values not exceeding the threshold, uses maximal level otherwise.
+ ///
+ /// @param[in,out] txBuf The packed spi transfer buffer of the LED's color values
+ /// @param[in] ledValues The RGB-color per LED
+ /// @param[in] threshold The threshold 0 .. 255 that defines whether to use adjusted SK9822 current level per LED
+ /// @param[in] maxLevel The maximal current level 1 .. 31 to use
+ ///
+ void bufferWithAdjustedCurrent(std::vector &txBuf, const std::vector & ledValues, const int threshold, const int maxLevel);
+
+ /// The threshold that defines use of SK9822 global brightness control for maximal rgb grayscale values below.
+ /// i.e. global brightness control is used for rgb-values when max(r,g,b) < threshold.
+ int _globalBrightnessControlThreshold;
+
+ /// The maximal current level that is targeted. Possibile values 1 .. 31.
+ int _globalBrightnessControlMaxLevel;
+
+ ///
+ /// @brief Scales the given value such that a given grayscale stimulus is reached for the targeted brightness and defined max current value.
+ ///
+ /// @param[in] value The grayscale value to scale
+ /// @param[in] maxLevel The maximal current level 1 .. 31 to use
+ /// @param[in] brightness The target brightness
+ /// @return The scaled grayscale stimulus
+ ///
+ inline __attribute__((always_inline)) unsigned scale(const uint8_t value, const int maxLevel, const uint16_t brightness);
+
+ ///
+ /// @brief Initialise the device's configuration
+ ///
+ /// @param[in] deviceConfig the JSON device configuration
+ /// @return True, if success
+ ///
+ bool init(const QJsonObject &deviceConfig) override;
+
+ ///
+ /// @brief Writes the RGB-Color values to the LEDs.
+ ///
+ /// @param[in] ledValues The RGB-color per LED
+ /// @return Zero on success, else negative
+ ///
+ int write(const std::vector & ledValues) override;
+};
+
+#endif // LEDEVICESK9822_H
diff --git a/libsrc/leddevice/schemas/schema-sk9822.json b/libsrc/leddevice/schemas/schema-sk9822.json
new file mode 100644
index 00000000..7b5d5584
--- /dev/null
+++ b/libsrc/leddevice/schemas/schema-sk9822.json
@@ -0,0 +1,61 @@
+{
+ "type":"object",
+ "required":true,
+ "properties":{
+ "output": {
+ "type": "string",
+ "title":"edt_dev_spec_spipath_title",
+ "enum" : ["/dev/spidev0.0","/dev/spidev0.1"],
+ "default" : "/dev/spidev0.0",
+ "propertyOrder" : 1
+ },
+ "rate": {
+ "type": "integer",
+ "title":"edt_dev_spec_baudrate_title",
+ "default": 1000000,
+ "propertyOrder" : 2
+ },
+ "invert": {
+ "type": "boolean",
+ "title":"edt_dev_spec_invert_title",
+ "default": false,
+ "propertyOrder" : 3
+ },
+ "globalBrightnessControlMaxLevel": {
+ "type": "integer",
+ "title":"edt_dev_spec_globalBrightnessControlMaxLevel_title",
+ "default": 31,
+ "minimum": 1,
+ "maximum": 31,
+ "propertyOrder" : 4
+ },
+ "globalBrightnessControlThreshold": {
+ "type": "integer",
+ "title":"edt_dev_spec_globalBrightnessControlThreshold_title",
+ "default": 255,
+ "minimum": 0,
+ "maximum": 255,
+ "propertyOrder" : 5
+ },
+ "latchTime": {
+ "type": "integer",
+ "title":"edt_dev_spec_latchtime_title",
+ "default": 0,
+ "append" : "edt_append_ms",
+ "minimum": 0,
+ "maximum": 1000,
+ "access" : "expert",
+ "propertyOrder" : 6
+ },
+ "rewriteTime": {
+ "type": "integer",
+ "title":"edt_dev_general_rewriteTime_title",
+ "default": 1000,
+ "append" : "edt_append_ms",
+ "minimum": 0,
+ "access" : "expert",
+ "propertyOrder" : 7
+ }
+ },
+ "additionalProperties": true
+}