diff --git a/sources/leddevice/LedDeviceSchemas.qrc b/sources/leddevice/LedDeviceSchemas.qrc
index 3df33f8d7..6d563b21c 100644
--- a/sources/leddevice/LedDeviceSchemas.qrc
+++ b/sources/leddevice/LedDeviceSchemas.qrc
@@ -19,6 +19,7 @@
schemas/schema-sk6812spi.json
schemas/schema-sk6822spi.json
schemas/schema-sk9822.json
+ schemas/schema-hd108.json
schemas/schema-tinkerforge.json
schemas/schema-tpm2net.json
schemas/schema-tpm2.json
diff --git a/sources/leddevice/dev_spi/LedDeviceHD108.cpp b/sources/leddevice/dev_spi/LedDeviceHD108.cpp
new file mode 100644
index 000000000..20ff5c999
--- /dev/null
+++ b/sources/leddevice/dev_spi/LedDeviceHD108.cpp
@@ -0,0 +1,129 @@
+/* LedDeviceHD108.cpp
+*
+* MIT License
+*
+* Copyright (c) 2023 awawa-dev
+*
+* Project homesite: https://github.com/awawa-dev/HyperHDR
+*
+* Permission is hereby granted, free of charge, to any person obtaining a copy
+* of this software and associated documentation files (the "Software"), to deal
+* in the Software without restriction, including without limitation the rights
+* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+* copies of the Software, and to permit persons to whom the Software is
+* furnished to do so, subject to the following conditions:
+*
+* The above copyright notice and this permission notice shall be included in all
+* copies or substantial portions of the Software.
+
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+* SOFTWARE.
+ */
+
+#include "LedDeviceHD108.h"
+
+// Local HyperHDR includes
+#include
+#include
+#include
+
+const int HD108_MAX_LEVEL = 0x1F;
+const uint32_t HD108_MAX_THRESHOLD = 100u;
+const uint32_t HD108_MAX_THRESHOLD_2 = HD108_MAX_THRESHOLD * HD108_MAX_THRESHOLD;
+const uint16_t HD108_MAX_LEVEL_BIT = 0b1000000000000000;
+
+LedDeviceHD108::LedDeviceHD108(const QJsonObject& deviceConfig)
+ : ProviderSpi(deviceConfig)
+ , _globalBrightnessControlThreshold(HD108_MAX_THRESHOLD)
+ , _globalBrightnessControlMaxLevel(HD108_MAX_LEVEL)
+{
+}
+
+LedDevice* LedDeviceHD108::construct(const QJsonObject& deviceConfig)
+{
+ return new LedDeviceHD108(deviceConfig);
+}
+
+bool LedDeviceHD108::init(const QJsonObject& deviceConfig)
+{
+ bool isInitOK = false;
+
+ // Initialise sub-class
+ if (ProviderSpi::init(deviceConfig))
+ {
+ _globalBrightnessControlThreshold = static_cast(std::min(
+ std::lround(deviceConfig["globalBrightnessControlThreshold"].toDouble(HD108_MAX_THRESHOLD) * HD108_MAX_THRESHOLD),
+ (long)HD108_MAX_THRESHOLD_2));
+ _globalBrightnessControlMaxLevel = deviceConfig["globalBrightnessControlMaxLevel"].toInt(HD108_MAX_LEVEL);
+ Info(_log, "[HD108] Using global brightness control with threshold of %d and max level of %d", _globalBrightnessControlThreshold, _globalBrightnessControlMaxLevel);
+
+ _ledBuffer.resize(0, 0x00);
+ _ledBuffer.resize((_ledCount * 8) + 16, 0x00);
+
+ isInitOK = true;
+ }
+ return isInitOK;
+}
+
+static inline uint16_t MSB_FIRST(uint16_t x, bool littleEndian)
+{
+ if (littleEndian)
+ return (x >> 8) | (x << 8);
+ else
+ return x;
+}
+
+int LedDeviceHD108::write(const std::vector& ledValues)
+{
+ int littleEndian = 1;
+
+ if (*(char*)&littleEndian != 1)
+ littleEndian = 0;
+
+
+ if (_ledCount != ledValues.size())
+ {
+ Warning(_log, "HD108 led's number has changed (old: %d, new: %d). Rebuilding buffer.", _ledCount, ledValues.size());
+ _ledCount = static_cast(ledValues.size());
+ _ledBuffer.resize(0, 0x00);
+ _ledBuffer.resize((_ledCount * 8) + 16, 0x00);
+ }
+
+
+ int index = 8;
+ uint16_t* data = reinterpret_cast(_ledBuffer.data());
+ for (auto const& rgb : ledValues)
+ {
+ const int isLit = (rgb.red || rgb.green || rgb.blue);
+ uint16_t red = rgb.red * 256u;
+ uint16_t green = rgb.green * 256u;
+ uint16_t blue = rgb.blue * 256u;
+ uint16_t level = HD108_MAX_LEVEL_BIT;
+
+ for (int i = 0; i < 3 && isLit; i++)
+ {
+ level |= (_globalBrightnessControlMaxLevel & HD108_MAX_LEVEL) << (5 * i);
+ }
+
+ if (_globalBrightnessControlThreshold < HD108_MAX_THRESHOLD_2)
+ {
+ red = (red * _globalBrightnessControlThreshold) / HD108_MAX_THRESHOLD_2;
+ green = (green * _globalBrightnessControlThreshold) / HD108_MAX_THRESHOLD_2;
+ blue = (blue * _globalBrightnessControlThreshold) / HD108_MAX_THRESHOLD_2;
+ }
+
+
+ data[index + 0] = MSB_FIRST(level, littleEndian);
+ data[index + 1] = MSB_FIRST(red, littleEndian);
+ data[index + 2] = MSB_FIRST(green, littleEndian);
+ data[index + 3] = MSB_FIRST(blue, littleEndian);
+ index += 4;
+ }
+
+ return writeBytes(_ledBuffer.size(), _ledBuffer.data());
+}
diff --git a/sources/leddevice/dev_spi/LedDeviceHD108.h b/sources/leddevice/dev_spi/LedDeviceHD108.h
new file mode 100644
index 000000000..5ab0af43b
--- /dev/null
+++ b/sources/leddevice/dev_spi/LedDeviceHD108.h
@@ -0,0 +1,53 @@
+/* LedDeviceHD108.h
+*
+* MIT License
+*
+* Copyright (c) 2023 awawa-dev
+*
+* Project homesite: https://github.com/awawa-dev/HyperHDR
+*
+* Permission is hereby granted, free of charge, to any person obtaining a copy
+* of this software and associated documentation files (the "Software"), to deal
+* in the Software without restriction, including without limitation the rights
+* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+* copies of the Software, and to permit persons to whom the Software is
+* furnished to do so, subject to the following conditions:
+*
+* The above copyright notice and this permission notice shall be included in all
+* copies or substantial portions of the Software.
+
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+* SOFTWARE.
+ */
+
+#ifndef LEDEVICEHD108_H
+#define LEDEVICEHD108_H
+
+// HyperHDR includes
+#include "ProviderSpi.h"
+
+///
+/// Implementation of the LedDevice interface for writing to LedDeviceHD108 led device via SPI.
+///
+class LedDeviceHD108 : public ProviderSpi
+{
+public:
+ explicit LedDeviceHD108(const QJsonObject& deviceConfig);
+
+ static LedDevice* construct(const QJsonObject& deviceConfig);
+
+private:
+ uint32_t _globalBrightnessControlThreshold;
+ uint16_t _globalBrightnessControlMaxLevel;
+
+ bool init(const QJsonObject& deviceConfig) override;
+
+ int write(const std::vector& ledValues) override;
+};
+
+#endif // LEDEVICEHD108_H
diff --git a/sources/leddevice/schemas/schema-hd108.json b/sources/leddevice/schemas/schema-hd108.json
new file mode 100644
index 000000000..bfde25f13
--- /dev/null
+++ b/sources/leddevice/schemas/schema-hd108.json
@@ -0,0 +1,54 @@
+{
+ "type":"object",
+ "required":true,
+ "properties":{
+ "output": {
+ "type": "string",
+ "title":"edt_dev_spec_spipath_title",
+ "default" : "/dev/spidev0.0",
+ "required" : true,
+ "propertyOrder" : 1
+ },
+ "rate": {
+ "type": "integer",
+ "format" : "stepper",
+ "step" : 1000000,
+ "title":"edt_dev_spec_baudrate_title",
+ "default": 1000000,
+ "minimum": 1000000,
+ "required" : true,
+ "propertyOrder" : 2
+ },
+ "invert": {
+ "type": "boolean",
+ "format": "checkbox",
+ "title":"edt_dev_spec_invert_title",
+ "default": false,
+ "propertyOrder" : 3
+ },
+ "globalBrightnessControlMaxLevel": {
+ "type": "integer",
+ "format" : "stepper",
+ "step" : 1,
+ "title":"edt_dev_spec_globalBrightnessControlMaxLevel_title",
+ "default": 31,
+ "minimum": 1,
+ "maximum": 31,
+ "required" : true,
+ "propertyOrder" : 4
+ },
+ "globalBrightnessControlThreshold": {
+ "type": "number",
+ "format" : "stepper",
+ "title": "edt_conf_color_brightness_title",
+ "default": 100.0,
+ "minimum": 0,
+ "maximum": 100.0,
+ "step" : 0.25,
+ "append" : "edt_append_percent",
+ "required" : true,
+ "propertyOrder" : 5
+ }
+ },
+ "additionalProperties": true
+}
diff --git a/www/js/light_source.js b/www/js/light_source.js
index d8895fcea..913b5d421 100644
--- a/www/js/light_source.js
+++ b/www/js/light_source.js
@@ -880,7 +880,7 @@ $(document).ready(function()
var yeelight_title = 'wiz_yeelight_title';
changeWizard(data, yeelight_title, startWizardYeelight);
}
- else if (["apa102", "apa104", "awa_spi", "lpd6803", "lpd8806", "p9813", "sk6812spi", "sk6822spi", "sk9822", "ws2801", "ws2812spi", "wled", "adalight", "atmo", "dmx", "karate", "sedu", "tpm2"].includes(ledType))
+ else if (["apa102", "apa104", "awa_spi", "lpd6803", "lpd8806", "p9813", "sk6812spi", "sk6822spi", "sk9822", "ws2801", "ws2812spi", "wled", "adalight", "atmo", "dmx", "karate", "sedu", "tpm2", "hd108"].includes(ledType))
{
let selectorControl = $("");
let targetControl = 'output';
@@ -918,7 +918,7 @@ $(document).ready(function()
// create led device selection
var ledDevices = window.serverInfo.ledDevices.available;
- var devRPiSPI = ['apa102', 'apa104', 'ws2801', 'lpd6803', 'lpd8806', 'p9813', 'sk6812spi', 'sk6822spi', 'sk9822', 'ws2812spi','awa_spi'];
+ var devRPiSPI = ['apa102', 'apa104', 'ws2801', 'lpd6803', 'lpd8806', 'p9813', 'sk6812spi', 'sk6822spi', 'sk9822', 'ws2812spi', 'awa_spi', 'hd108'];
var devRPiPWM = ['ws281x'];
var devRPiGPIO = ['piblaster'];