From 545f184c500b8cc089e9439964c27e08caeccb58 Mon Sep 17 00:00:00 2001 From: Lord-Grey Date: Wed, 3 Nov 2021 00:40:02 +0100 Subject: [PATCH] Razer - Support individual LEDs and have default layout per device type --- assets/webconfig/js/content_leds.js | 36 +++- libsrc/leddevice/dev_net/LedDeviceRazer.cpp | 202 +++++++++++++++++--- libsrc/leddevice/dev_net/LedDeviceRazer.h | 106 ++++++++++ libsrc/leddevice/schemas/schema-razer.json | 2 +- 4 files changed, 313 insertions(+), 33 deletions(-) diff --git a/assets/webconfig/js/content_leds.js b/assets/webconfig/js/content_leds.js index ad27c41d..d8f49bfe 100755 --- a/assets/webconfig/js/content_leds.js +++ b/assets/webconfig/js/content_leds.js @@ -1,4 +1,3 @@ -var ledsCustomCfgInitialized = false; var onLedLayoutTab = false; var nonBlacklistLedArray = []; var ledBlacklist = []; @@ -614,10 +613,7 @@ $(document).ready(function () { var target = $(e.target).attr("href") // activated tab if (target == "#menu_gencfg") { onLedLayoutTab = true; - if (!ledsCustomCfgInitialized) { - $('#leds_custom_updsim').trigger('click'); - ledsCustomCfgInitialized = true; - } + $('#leds_custom_updsim').trigger('click'); } else { onLedLayoutTab = false; } @@ -781,6 +777,10 @@ $(document).ready(function () { conf_editor.getEditor("root.generalOptions").disable(); hwLedCountDefault = 1; colorOrderDefault = "bgr"; + + var subType = conf_editor.getEditor("root.specificOptions.subType").getValue(); + params = { subType: subType }; + getProperties_device(ledType, subType, params); break; default: @@ -1018,6 +1018,19 @@ $(document).ready(function () { } }); + conf_editor.watch('root.specificOptions.subType', () => { + var subType = conf_editor.getEditor("root.specificOptions.subType").getValue(); + let params = {}; + + switch (ledType) { + case "razer": + params = { subType: subType }; + getProperties_device(ledType, subType, params); + break; + default: + } + }); + conf_editor.watch('root.specificOptions.token', () => { var token = conf_editor.getEditor("root.specificOptions.token").getValue(); @@ -1295,7 +1308,6 @@ function saveLedConfig(genDefLayout = false) { case "sk9822": case "ws2812spi": case "piblaster": - case "razer": default: if (genDefLayout === true) { ledConfig = { @@ -1672,6 +1684,18 @@ function updateElements(ledType, key) { } break; + case "razer": + var ledProperties = devicesProperties[ledType][key]; + if (ledProperties) { + conf_editor.getEditor("root.generalOptions.hardwareLedCount").setValue(ledProperties.maxLedCount); + $("#ip_ma_ledshoriz").val(ledProperties.maxColumn); + $("#ip_ma_ledsvert").val(ledProperties.maxRow); + $("#ip_ma_cabling").val("parallel"); + $("#ip_ma_start").val("top-left"); + createMatrixLeds(); + } + break; + default: } } diff --git a/libsrc/leddevice/dev_net/LedDeviceRazer.cpp b/libsrc/leddevice/dev_net/LedDeviceRazer.cpp index eb5cb592..a282e957 100644 --- a/libsrc/leddevice/dev_net/LedDeviceRazer.cpp +++ b/libsrc/leddevice/dev_net/LedDeviceRazer.cpp @@ -7,28 +7,40 @@ #include +using namespace Chroma; +using namespace Chroma::Keyboard; +using namespace Chroma::Keypad; +using namespace Chroma::Mouse; +using namespace Chroma::Mousepad; +using namespace Chroma::Headset; +using namespace Chroma::Chromalink; + // Constants namespace { -bool verbose = false; + bool verbose = true; -// Configuration settings -const char RAZER_DEVICE_TYPE[] = "razerDevice"; + // Configuration settings + const char CONFIG_RAZER_DEVICE_TYPE[] = "subType"; + const char CONFIG_SINGLE_COLOR[] = "singleColor"; -// WLED JSON-API elements -const char API_DEFAULT_HOST[] = "localhost"; -const int API_DEFAULT_PORT = 54235; + // WLED JSON-API elements + const char API_DEFAULT_HOST[] = "localhost"; + const int API_DEFAULT_PORT = 54235; -const char API_BASE_PATH[] = "/razer/chromasdk"; -const char API_RESULT[] = "result"; + const char API_BASE_PATH[] = "/razer/chromasdk"; + const char API_RESULT[] = "result"; -constexpr std::chrono::milliseconds HEARTBEAT_INTERVALL{1000}; + constexpr std::chrono::milliseconds HEARTBEAT_INTERVALL{ 1000 }; } //End of constants -LedDeviceRazer::LedDeviceRazer(const QJsonObject& deviceConfig) +LedDeviceRazer::LedDeviceRazer(const QJsonObject& deviceConfig) : LedDevice(deviceConfig) - ,_restApi(nullptr) - ,_apiPort(API_DEFAULT_PORT) + , _restApi(nullptr) + , _apiPort(API_DEFAULT_PORT) + , _maxRow(Chroma::MAX_ROW) + , _maxColumn(Chroma::MAX_COLUMN) + , _maxLeds(Chroma::MAX_LEDS) { } @@ -43,6 +55,7 @@ LedDeviceRazer::~LedDeviceRazer() _restApi = nullptr; } + bool LedDeviceRazer::init(const QJsonObject& deviceConfig) { bool isInitOK = false; @@ -67,13 +80,38 @@ bool LedDeviceRazer::init(const QJsonObject& deviceConfig) Debug(_log, "Hostname : %s", QSTRING_CSTR(_hostname)); Debug(_log, "Port : %d", _apiPort); - _razerDeviceType = deviceConfig[RAZER_DEVICE_TYPE].toString("keyboard"); + _razerDeviceType = deviceConfig[CONFIG_RAZER_DEVICE_TYPE].toString("invalid").toLower(); + _isSingleColor = deviceConfig[CONFIG_SINGLE_COLOR].toBool(); - Debug(_log, "Razer device : %s", QSTRING_CSTR(_razerDeviceType)); + Debug(_log, "Razer Device : %s", QSTRING_CSTR(_razerDeviceType)); + Debug(_log, "Single Color : %d", _isSingleColor); - if (initRestAPI(_hostname, _apiPort)) + if (resolveDeviceProperties(_razerDeviceType)) { - isInitOK = true; + if (_isSingleColor && configuredLedCount > 1) + { + Info(_log, "In single color mode only the first LED of the configured [%d] will be used.", configuredLedCount); + } + else + { + if (_maxLeds != configuredLedCount) + { + Warning(_log, "Razer device might not work as expected. The type \"%s\" requires an LED-matrix [%d,%d] configured, i.e. %d LEDs. Currently only %d are defined via the layout.", + QSTRING_CSTR(_razerDeviceType), + _maxRow, _maxColumn, _maxLeds, + configuredLedCount + ); + } + } + + if (initRestAPI(_hostname, _apiPort)) + { + isInitOK = true; + } + } + else + { + Error(_log, "Razer devicetype \"%s\" not supported", QSTRING_CSTR(_razerDeviceType)); } } @@ -109,7 +147,7 @@ bool LedDeviceRazer::checkApiError(const httpResponse& response) QString errorReason; QString strJson(response.getBody().toJson(QJsonDocument::Compact)); - DebugIf(verbose, _log, "Reply: [%s]", strJson.toUtf8().constData()); + //DebugIf(verbose, _log, "Reply: [%s]", strJson.toUtf8().constData()); QJsonObject jsonObj = response.getBody().object(); @@ -145,9 +183,7 @@ int LedDeviceRazer::open() authorDetails.insert("contact", "https://github.com/hyperion-project/hyperion.ng"); obj.insert("author", authorDetails); - - QJsonArray deviceList = { "keyboard","mouse","headset","mousepad","keypad","chromalink" }; - obj.insert("device_supported", deviceList); + obj.insert("device_supported", QJsonArray::fromStringList(Chroma::SupportedDevices)); obj.insert("category", "application"); @@ -217,14 +253,58 @@ int LedDeviceRazer::write(const std::vector& ledValues) int retval = -1; QJsonObject effectObj; - effectObj.insert("effect", "CHROMA_STATIC"); - ColorRgb color = ledValues[0]; - int colorParam = (color.red * 65536) + (color.green * 256) + color.blue; + if (_isSingleColor) + { + //Static effect + effectObj.insert("effect", "CHROMA_STATIC"); - QJsonObject param; - param.insert("color", colorParam); - effectObj.insert("param", param); + ColorRgb color = ledValues[0]; + int colorParam = (color.red * 65536) + (color.green * 256) + color.blue; + + QJsonObject param; + param.insert("color", colorParam); + effectObj.insert("param", param); + } + else + { + //Custom effect + if (_customEffectType == Chroma::CHROMA_CUSTOM2) + { + effectObj.insert("effect", "CHROMA_CUSTOM2"); + } + else { + effectObj.insert("effect", "CHROMA_CUSTOM"); + } + + QJsonArray rowParams; + for (int row = 0; row < _maxRow; row++) { + QJsonArray columnParams; + for (int col = 0; col < _maxColumn; col++) { + int pos = row * _maxColumn + col; + int bgrColor; + if (pos < ledValues.size()) + { + bgrColor = (ledValues[pos].red * 65536) + (ledValues[pos].green * 256) + ledValues[pos].blue; + } + else + { + bgrColor = 0; + } + columnParams.append(bgrColor); + } + + if (_maxRow == 1) + { + rowParams = columnParams; + } + else + { + rowParams.append(columnParams); + } + } + effectObj.insert("param", rowParams); + } _restApi->setPath(_razerDeviceType); httpResponse response = _restApi->put(effectObj); @@ -247,3 +327,73 @@ int LedDeviceRazer::rewriteLEDs() } return retval; } + +bool LedDeviceRazer::resolveDeviceProperties(const QString& deviceType) +{ + bool rc = true; + + int typeID = Chroma::SupportedDevices.indexOf(deviceType); + + switch (typeID) + { + case Chroma::DEVICE_KEYBOARD: + _maxRow = Chroma::Keyboard::MAX_ROW; + _maxColumn = Chroma::Keyboard::MAX_COLUMN; + _customEffectType = Chroma::Keyboard::CUSTOM_EFFECT_TYPE; + break; + case Chroma::DEVICE_MOUSE: + _maxRow = Chroma::Mouse::MAX_ROW; + _maxColumn = Chroma::Mouse::MAX_COLUMN; + _customEffectType = Chroma::Mouse::CUSTOM_EFFECT_TYPE; + break; + case Chroma::DEVICE_HEADSET: + _maxRow = Chroma::Headset::MAX_ROW; + _maxColumn = Chroma::Headset::MAX_COLUMN; + _customEffectType = Chroma::Headset::CUSTOM_EFFECT_TYPE; + break; + case Chroma::DEVICE_MOUSEPAD: + _maxRow = Chroma::Mousepad::MAX_ROW; + _maxColumn = Chroma::Mousepad::MAX_COLUMN; + _customEffectType = Chroma::Mousepad::CUSTOM_EFFECT_TYPE; + break; + case Chroma::DEVICE_KEYPAD: + _maxRow = Chroma::Keypad::MAX_ROW; + _maxColumn = Chroma::Keypad::MAX_COLUMN; + _customEffectType = Chroma::Keypad::CUSTOM_EFFECT_TYPE; + break; + case Chroma::DEVICE_CHROMALINK: + _maxRow = Chroma::Chromalink::MAX_ROW; + _maxColumn = Chroma::Chromalink::MAX_COLUMN; + _customEffectType = Chroma::Chromalink::CUSTOM_EFFECT_TYPE; + break; + default: + rc = false; + break; + } + + if (rc) + { + _maxLeds = _maxRow * _maxColumn; + } + return rc; +} + +QJsonObject LedDeviceRazer::getProperties(const QJsonObject& params) +{ + DebugIf(verbose, _log, "params: [%s]", QString(QJsonDocument(params).toJson(QJsonDocument::Compact)).toUtf8().constData()); + QJsonObject properties; + + _razerDeviceType = params[CONFIG_RAZER_DEVICE_TYPE].toString("invalid").toLower(); + if (resolveDeviceProperties(_razerDeviceType)) + { + QJsonObject propertiesDetails; + propertiesDetails.insert("maxRow", _maxRow); + propertiesDetails.insert("maxColumn", _maxColumn); + propertiesDetails.insert("maxLedCount", _maxLeds); + + properties.insert("properties", propertiesDetails); + + DebugIf(verbose, _log, "properties: [%s]", QString(QJsonDocument(properties).toJson(QJsonDocument::Compact)).toUtf8().constData()); + } + return properties; +} diff --git a/libsrc/leddevice/dev_net/LedDeviceRazer.h b/libsrc/leddevice/dev_net/LedDeviceRazer.h index 7bac6549..f76ca957 100644 --- a/libsrc/leddevice/dev_net/LedDeviceRazer.h +++ b/libsrc/leddevice/dev_net/LedDeviceRazer.h @@ -5,6 +5,84 @@ #include #include "ProviderRestApi.h" +namespace Chroma +{ + typedef enum DeviceType + { + DEVICE_KEYBOARD = 0, + DEVICE_MOUSE = 1, + DEVICE_HEADSET = 2, + DEVICE_MOUSEPAD = 3, + DEVICE_KEYPAD = 4, + DEVICE_CHROMALINK = 5, + DEVICE_SYSTEM = 6, + DEVICE_SPEAKERS = 7, + DEVICE_INVALID + } DEVICETYPE; + + typedef enum CustomEffectType + { + CHROMA_CUSTOM = 1, + CHROMA_CUSTOM2 = 2, + } CUSTOM_EFFECT_TYPE; + + const int MAX_ROW = 30; //!< Maximum rows for custom effects. + const int MAX_COLUMN = 30; //!< Maximum columns for custom effects. + const int MAX_LEDS = MAX_ROW * MAX_COLUMN; + + namespace Keyboard + { + const char TYPE_NAME[] = "keyboard"; + const int MAX_ROW = 6; + const int MAX_COLUMN = 22; + const CustomEffectType CUSTOM_EFFECT_TYPE = CHROMA_CUSTOM; + } + namespace Mouse + { + const char TYPE_NAME[] = "mouse"; + const int MAX_ROW = 9; + const int MAX_COLUMN = 7; + const CustomEffectType CUSTOM_EFFECT_TYPE = CHROMA_CUSTOM2; + } + namespace Headset + { + const char TYPE_NAME[] = "headset"; + const int MAX_ROW = 1; + const int MAX_COLUMN = 5; + const CustomEffectType CUSTOM_EFFECT_TYPE = CHROMA_CUSTOM; + } + namespace Mousepad + { + const char TYPE_NAME[] = "mousepad"; + const int MAX_ROW = 1; + const int MAX_COLUMN = 15; + const CustomEffectType CUSTOM_EFFECT_TYPE = CHROMA_CUSTOM; + } + namespace Keypad + { + const char TYPE_NAME[] = "keypad"; + const int MAX_ROW = 4; + const int MAX_COLUMN = 5; + const CustomEffectType CUSTOM_EFFECT_TYPE = CHROMA_CUSTOM; + } + namespace Chromalink + { + const char TYPE_NAME[] = "chromalink"; + const int MAX_ROW = 1; + const int MAX_COLUMN = 5; + const CustomEffectType CUSTOM_EFFECT_TYPE = CHROMA_CUSTOM; + } + + const QStringList SupportedDevices{ + Keyboard::TYPE_NAME, + Mouse::TYPE_NAME, + Headset::TYPE_NAME, + Mousepad::TYPE_NAME, + Keypad::TYPE_NAME, + Chromalink::TYPE_NAME + }; +} + /// /// Implementation of a Razer Chroma LedDevice /// Supported Razer Chroma device types: Keyboard, Mouse, Headset, Mousepad, Keypad, Chromalink @@ -28,6 +106,20 @@ public: /// static LedDevice* construct(const QJsonObject& deviceConfig); + /// + /// @brief Get a Razer device's resource properties + /// + /// Following parameters are required + /// @code + /// { + /// "subType" : "razer_device_type", + /// } + /// @endcode + /// @param[in] params Parameters to query device + /// @return A JSON structure holding the device's properties + /// + QJsonObject getProperties(const QJsonObject& params) override; + /// /// @brief Destructor of the LED-device /// @@ -93,6 +185,14 @@ private: /// bool checkApiError(const httpResponse& response); + /// + /// @brief Update object with properties for a given device + /// + /// @param[in] deviceType + /// return True, if success + /// + bool resolveDeviceProperties(const QString& deviceType); + ///REST-API wrapper ProviderRestApi* _restApi; @@ -101,6 +201,12 @@ private: QUrl _uri; QString _razerDeviceType; + int _maxRow; + int _maxColumn; + int _maxLeds; + Chroma::CustomEffectType _customEffectType; + + bool _isSingleColor; }; #endif // LEDEVICERAZER_H diff --git a/libsrc/leddevice/schemas/schema-razer.json b/libsrc/leddevice/schemas/schema-razer.json index bc699d04..41202f2a 100644 --- a/libsrc/leddevice/schemas/schema-razer.json +++ b/libsrc/leddevice/schemas/schema-razer.json @@ -2,7 +2,7 @@ "type": "object", "required": true, "properties": { - "razerDevice": { + "subType": { "type": "string", "title": "edt_dev_spec_razer_device_title", "enum": [ "keyboard", "mouse", "headset", "mousepad", "keypad", "chromalink" ],