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