WLED segment streaming support (#1556)

* WLED segment streaming support

* Address CodeQL findings

* WLED - Remove that interim color is shown when WLED is powered off

* Allow LEDDevice to stay on after streaming

* Apply stayOn on segment streamed to

* Fix LED-Matrix Layout: Add Cabling direction selection element again
This commit is contained in:
LordGrey 2023-02-07 07:15:22 +00:00 committed by GitHub
parent ef7ceb0bbf
commit a57bcbc2b8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 2078 additions and 1526 deletions

View File

@ -322,6 +322,17 @@
</select> </select>
</td> </td>
</tr> </tr>
<tr>
<td class="ltd">
<label class="ltdlabel" for="ip_ma_direction" data-i18n="conf_leds_layout_ma_direction">Cabling</label>
</td>
<td class="itd">
<select class="form-control ledMAconstr" id="ip_ma_direction">
<option value="horizontal" data-i18n="conf_leds_layout_ma_opthoriz">Horizontal</option>
<option value="vertical" data-i18n="conf_leds_layout_ma_optvert">Vertical</option>
</select>
</td>
</tr>
<tr> <tr>
<td class="ltd"> <td class="ltd">
<label class="ltdlabel" for="ip_ma_start" data-i18n="conf_leds_layout_ma_position">Input</label> <label class="ltdlabel" for="ip_ma_start" data-i18n="conf_leds_layout_ma_position">Input</label>

View File

@ -58,11 +58,13 @@
"conf_leds_contr_label_contrtype": "Controller type:", "conf_leds_contr_label_contrtype": "Controller type:",
"conf_leds_device_info_log": "In case your LEDs do not work, check here for errors:", "conf_leds_device_info_log": "In case your LEDs do not work, check here for errors:",
"conf_leds_device_intro": "Hyperion supports a lot of controllers to transmit data to your target device. Select a LED controller out of the sorted list and configure it. We have chosen the best default settings for each device.", "conf_leds_device_intro": "Hyperion supports a lot of controllers to transmit data to your target device. Select a LED controller out of the sorted list and configure it. We have chosen the best default settings for each device.",
"conf_leds_error_get_properties_text" : "Failed to get the device's properties. Please check the configuration items.", "conf_leds_error_get_properties_text": "Failed to get the device's properties. Please check the configuration items.",
"conf_leds_error_get_properties_title" : "Device properties", "conf_leds_error_get_properties_title": "Device properties",
"conf_leds_error_hwled_gt_layout": "The hardware LED count ($1) is greater than LEDs configured via layout ($2),<br>$3 {{plural:$3|LED|LEDs}} will stay black if you continue.", "conf_leds_error_hwled_gt_layout": "The hardware LED count ($1) is greater than LEDs configured via layout ($2),<br>$3 {{plural:$3|LED|LEDs}} will stay black if you continue.",
"conf_leds_error_hwled_lt_layout": "The hardware LED count ($1) is less than LEDs configured via layout ($2). <br> The number of LEDs configured in the layout must not exceed the available LEDs", "conf_leds_error_hwled_lt_layout": "The hardware LED count ($1) is less than LEDs configured via layout ($2). <br> The number of LEDs configured in the layout must not exceed the available LEDs",
"conf_leds_error_hwled_gt_maxled": "The hardware LED count ($1) is greater than the maximum number of LEDs supported by the device ($2). <br> The hardware LED count is set to ($3).", "conf_leds_error_hwled_gt_maxled": "The hardware LED count ($1) is greater than the maximum number of LEDs supported by the device ($2). <br> The hardware LED count is set to ($3).",
"conf_leds_error_hwled_gt_maxled_protocol": "The hardware LED count ($1) is greater than the maximum number of LEDs supported by the streaming protocol ($2). <br> The streaming protocol will be changed to ($3).",
"conf_leds_error_wled_segment_missing": "The currently configured segment ($1) is not configured at your WLED device.<br>You might need to check the WLED configuration!<br>The configuration page represents the current WLED setup.",
"conf_leds_info_ws281x": "Hyperion must run with 'root' privileges for this controller type!", "conf_leds_info_ws281x": "Hyperion must run with 'root' privileges for this controller type!",
"conf_leds_layout_advanced": "Advanced Settings", "conf_leds_layout_advanced": "Advanced Settings",
"conf_leds_layout_blacklist_num_title": "Number of LEDs", "conf_leds_layout_blacklist_num_title": "Number of LEDs",
@ -558,7 +560,7 @@
"edt_dev_spec_brightnessOverwrite_title": "Overwrite brightness", "edt_dev_spec_brightnessOverwrite_title": "Overwrite brightness",
"edt_dev_spec_brightnessThreshold_title": "Signal detection brightness minimum", "edt_dev_spec_brightnessThreshold_title": "Signal detection brightness minimum",
"edt_dev_spec_brightness_title": "Brightness", "edt_dev_spec_brightness_title": "Brightness",
"edt_dev_spec_candyGamma_title" : "'Candy' mode (double gamma correction)", "edt_dev_spec_candyGamma_title": "'Candy' mode (double gamma correction)",
"edt_dev_spec_chanperfixture_title": "Channels per Fixture", "edt_dev_spec_chanperfixture_title": "Channels per Fixture",
"edt_dev_spec_cid_title": "CID", "edt_dev_spec_cid_title": "CID",
"edt_dev_spec_clientKey_title": "Clientkey", "edt_dev_spec_clientKey_title": "Clientkey",
@ -615,15 +617,22 @@
"edt_dev_spec_razer_device_title": "Razer Chroma Device", "edt_dev_spec_razer_device_title": "Razer Chroma Device",
"edt_dev_spec_restoreOriginalState_title": "Restore lights' state", "edt_dev_spec_restoreOriginalState_title": "Restore lights' state",
"edt_dev_spec_restoreOriginalState_title_info": "Restore the device's original state when device is disabled", "edt_dev_spec_restoreOriginalState_title_info": "Restore the device's original state when device is disabled",
"edt_dev_spec_rgbw_calibration_enable" : "White channel calibration (RGBW only)", "edt_dev_spec_rgbw_calibration_enable": "White channel calibration (RGBW only)",
"edt_dev_spec_rgbw_calibration_limit" : "White channel limit", "edt_dev_spec_rgbw_calibration_limit": "White channel limit",
"edt_dev_spec_rgbw_calibration_red" : "Red/White channel aspect", "edt_dev_spec_rgbw_calibration_red": "Red/White channel aspect",
"edt_dev_spec_rgbw_calibration_green" : "Green/White channel aspect", "edt_dev_spec_rgbw_calibration_green": "Green/White channel aspect",
"edt_dev_spec_rgbw_calibration_blue" : "Blue/White channel aspect", "edt_dev_spec_rgbw_calibration_blue": "Blue/White channel aspect",
"edt_dev_spec_segments_disabled_title": "Segment streaming disabled at WLED.",
"edt_dev_spec_segments_title": "Stream to segment",
"edt_dev_spec_segmentId_title": "Segment-ID",
"edt_dev_spec_segmentsSwitchOffOthers_title": "Switch-off other segments",
"edt_dev_spec_segmentsOverlapValidation_error": "Correct the WLED setup! The segment must not overlap with {{plural:$1| segment|segments}}: \"$2\".",
"edt_dev_spec_serial_title": "Serial number", "edt_dev_spec_serial_title": "Serial number",
"edt_dev_spec_spipath_title": "SPI Device", "edt_dev_spec_spipath_title": "SPI Device",
"edt_dev_spec_sslHSTimeoutMax_title": "Streamer handshake timeout maximum", "edt_dev_spec_sslHSTimeoutMax_title": "Streamer handshake timeout maximum",
"edt_dev_spec_sslHSTimeoutMin_title": "Streamer handshake timeout minimum", "edt_dev_spec_sslHSTimeoutMin_title": "Streamer handshake timeout minimum",
"edt_dev_spec_stayOnAfterStreaming_title": "Stay on after streaming",
"edt_dev_spec_stayOnAfterStreaming_title_info": "The device will stay on after streaming or restoring state.",
"edt_dev_spec_switchOffOnBlack_title": "Switch off on black", "edt_dev_spec_switchOffOnBlack_title": "Switch off on black",
"edt_dev_spec_switchOffOnbelowMinBrightness_title": "Switch-off, below minimum", "edt_dev_spec_switchOffOnbelowMinBrightness_title": "Switch-off, below minimum",
"edt_dev_spec_syncOverwrite_title": "Disable synchronisation", "edt_dev_spec_syncOverwrite_title": "Disable synchronisation",
@ -866,11 +875,11 @@
"general_country_us": "United States", "general_country_us": "United States",
"general_disabled": "disabled", "general_disabled": "disabled",
"general_enabled": "enabled", "general_enabled": "enabled",
"general_speech_ca": "Catalan", "general_speech_ca": "Catalan",
"general_speech_cs": "Czech", "general_speech_cs": "Czech",
"general_speech_da": "Danish", "general_speech_da": "Danish",
"general_speech_de": "German", "general_speech_de": "German",
"general_speech_el": "Greek", "general_speech_el": "Greek",
"general_speech_en": "English", "general_speech_en": "English",
"general_speech_es": "Spanish", "general_speech_es": "Spanish",
"general_speech_fr": "French", "general_speech_fr": "French",

View File

@ -1002,6 +1002,21 @@ $(document).ready(function () {
addJsonEditorHostValidation(); addJsonEditorHostValidation();
JSONEditor.defaults.custom_validators.push(function (schema, value, path) {
var errors = [];
if (path === "root.specificOptions.segments.segmentList") {
var overlapSegNames = validateWledSegmentConfig(value);
if (overlapSegNames.length > 0) {
errors.push({
path: "root.specificOptions.segments",
message: $.i18n('edt_dev_spec_segmentsOverlapValidation_error', overlapSegNames.length, overlapSegNames.join(', '))
});
}
}
return errors;
});
$("#leddevices").off().on("change", function () { $("#leddevices").off().on("change", function () {
var generalOptions = window.serverSchema.properties.device; var generalOptions = window.serverSchema.properties.device;
@ -1080,8 +1095,8 @@ $(document).ready(function () {
$('#btn_test_controller').hide(); $('#btn_test_controller').hide();
switch (ledType) { switch (ledType) {
case "cololight":
case "wled": case "wled":
case "cololight":
case "nanoleaf": case "nanoleaf":
showAllDeviceInputOptions("hostList", false); showAllDeviceInputOptions("hostList", false);
case "apa102": case "apa102":
@ -1107,7 +1122,22 @@ $(document).ready(function () {
if (storedAccess === 'expert') { if (storedAccess === 'expert') {
filter.discoverAll = true; filter.discoverAll = true;
} }
discover_device(ledType, filter);
$('#btn_submit_controller').prop('disabled', true);
discover_device(ledType, filter)
.then(discoveryResult => {
updateOutputSelectList(ledType, discoveryResult);
})
.then(discoveryResult => {
if (ledType === "wled") {
updateElementsWled(ledType);
}
})
.catch(error => {
showNotification('danger', "Device discovery for " + ledType + " failed with error:" + error);
});
hwLedCountDefault = 1; hwLedCountDefault = 1;
colorOrderDefault = "rgb"; colorOrderDefault = "rgb";
break; break;
@ -1211,8 +1241,8 @@ $(document).ready(function () {
} }
break; break;
case "cololight":
case "wled": case "wled":
case "cololight":
var hostList = conf_editor.getEditor("root.specificOptions.hostList").getValue(); var hostList = conf_editor.getEditor("root.specificOptions.hostList").getValue();
if (hostList !== "SELECT") { if (hostList !== "SELECT") {
var host = conf_editor.getEditor("root.specificOptions.host").getValue(); var host = conf_editor.getEditor("root.specificOptions.host").getValue();
@ -1339,7 +1369,9 @@ $(document).ready(function () {
break; break;
case "wled": case "wled":
params = { host: host, filter: "info" }; //Ensure that elements are defaulted for new host
updateElementsWled(ledType, host);
params = { host: host };
getProperties_device(ledType, host, params); getProperties_device(ledType, host, params);
break; break;
@ -1452,6 +1484,10 @@ $(document).ready(function () {
} }
conf_editor.getEditor("root.specificOptions.rateList").setValue(rate); conf_editor.getEditor("root.specificOptions.rateList").setValue(rate);
break; break;
case "wled":
var hardwareLedCount = conf_editor.getEditor("root.generalOptions.hardwareLedCount").getValue();
validateWledLedCount(hardwareLedCount);
break;
default: default:
} }
}); });
@ -1547,12 +1583,54 @@ $(document).ready(function () {
} }
}); });
//WLED
conf_editor.watch('root.specificOptions.segments.segmentList', () => {
//Update hidden streamSegmentId element
var selectedSegment = conf_editor.getEditor("root.specificOptions.segments.segmentList").getValue();
var streamSegmentId = parseInt(selectedSegment);
conf_editor.getEditor("root.specificOptions.segments.streamSegmentId").setValue(streamSegmentId);
if (devicesProperties[ledType]) {
var host = conf_editor.getEditor("root.specificOptions.host").getValue();
var ledDeviceProperties = devicesProperties[ledType][host];
if (ledDeviceProperties) {
var hardwareLedCount = 1;
if (streamSegmentId > -1) {
// Set hardware LED count to segment length
if (ledDeviceProperties.state) {
var segments = ledDeviceProperties.state.seg;
var segmentConfig = segments.filter(seg => seg.id == streamSegmentId)[0];
hardwareLedCount = segmentConfig.len;
}
} else {
//"Use main segment only" is disabled, i.e. stream to all LEDs
if (ledDeviceProperties.info) {
hardwareLedCount = ledDeviceProperties.info.leds.count;
}
}
conf_editor.getEditor("root.generalOptions.hardwareLedCount").setValue(hardwareLedCount);
}
}
});
//Handle Hardware Led Count constraint list //Handle Hardware Led Count constraint list
conf_editor.watch('root.generalOptions.hardwareLedCountList', () => { conf_editor.watch('root.generalOptions.hardwareLedCountList', () => {
var hwLedCountSelected = conf_editor.getEditor("root.generalOptions.hardwareLedCountList").getValue(); var hwLedCountSelected = conf_editor.getEditor("root.generalOptions.hardwareLedCountList").getValue();
conf_editor.getEditor("root.generalOptions.hardwareLedCount").setValue(Number(hwLedCountSelected)); conf_editor.getEditor("root.generalOptions.hardwareLedCount").setValue(Number(hwLedCountSelected));
}); });
//Handle Hardware Led update and constraints
conf_editor.watch('root.generalOptions.hardwareLedCount', () => {
var hardwareLedCount = conf_editor.getEditor("root.generalOptions.hardwareLedCount").getValue();
switch (ledType) {
case "wled":
validateWledLedCount(hardwareLedCount);
break;
default:
}
});
}); });
//philipshueentertainment backward fix //philipshueentertainment backward fix
@ -1798,8 +1876,8 @@ function saveLedConfig(genDefLayout = false) {
location.reload(); location.reload();
} }
// build dynamic enum // build dynamic enum for hosts or output paths
var updateSelectList = function (ledType, discoveryInfo) { var updateOutputSelectList = function (ledType, discoveryInfo) {
// Only update, if ledType is equal of selected controller type and discovery info exists // Only update, if ledType is equal of selected controller type and discovery info exists
if (ledType !== $("#leddevices").val() || !discoveryInfo.devices) { if (ledType !== $("#leddevices").val() || !discoveryInfo.devices) {
return; return;
@ -1810,7 +1888,7 @@ var updateSelectList = function (ledType, discoveryInfo) {
var key; var key;
var enumVals = []; var enumVals = [];
var enumTitelVals = []; var enumTitleVals = [];
var enumDefaultVal = ""; var enumDefaultVal = "";
var addSelect = false; var addSelect = false;
var addCustom = false; var addCustom = false;
@ -1835,7 +1913,7 @@ var updateSelectList = function (ledType, discoveryInfo) {
if (discoveryInfo.devices.length === 0) { if (discoveryInfo.devices.length === 0) {
enumVals.push("NONE"); enumVals.push("NONE");
enumTitelVals.push($.i18n('edt_dev_spec_devices_discovered_none')); enumTitleVals.push($.i18n('edt_dev_spec_devices_discovered_none'));
} }
else { else {
var name; var name;
@ -1876,7 +1954,7 @@ var updateSelectList = function (ledType, discoveryInfo) {
} }
enumVals.push(host); enumVals.push(host);
enumTitelVals.push(name); enumTitleVals.push(name);
} }
//Always allow to add custom configuration //Always allow to add custom configuration
@ -1904,7 +1982,7 @@ var updateSelectList = function (ledType, discoveryInfo) {
if (discoveryInfo.devices.length == 0) { if (discoveryInfo.devices.length == 0) {
enumVals.push("NONE"); enumVals.push("NONE");
enumTitelVals.push($.i18n('edt_dev_spec_devices_discovered_none')); enumTitleVals.push($.i18n('edt_dev_spec_devices_discovered_none'));
$('#btn_submit_controller').prop('disabled', true); $('#btn_submit_controller').prop('disabled', true);
showAllDeviceInputOptions(key, false); showAllDeviceInputOptions(key, false);
} }
@ -1922,7 +2000,7 @@ var updateSelectList = function (ledType, discoveryInfo) {
} else { } else {
enumVals.push(device.portName); enumVals.push(device.portName);
} }
enumTitelVals.push(device.portName + " (" + device.vendorIdentifier + "|" + device.productIdentifier + ") - " + device.manufacturer); enumTitleVals.push(device.portName + " (" + device.vendorIdentifier + "|" + device.productIdentifier + ") - " + device.manufacturer);
} }
// Select configured device // Select configured device
@ -1951,7 +2029,7 @@ var updateSelectList = function (ledType, discoveryInfo) {
if (discoveryInfo.devices.length == 0) { if (discoveryInfo.devices.length == 0) {
enumVals.push("NONE"); enumVals.push("NONE");
enumTitelVals.push($.i18n('edt_dev_spec_devices_discovered_none')); enumTitleVals.push($.i18n('edt_dev_spec_devices_discovered_none'));
$('#btn_submit_controller').prop('disabled', true); $('#btn_submit_controller').prop('disabled', true);
showAllDeviceInputOptions(key, false); showAllDeviceInputOptions(key, false);
} }
@ -1970,7 +2048,7 @@ var updateSelectList = function (ledType, discoveryInfo) {
case "piblaster": case "piblaster":
for (const device of discoveryInfo.devices) { for (const device of discoveryInfo.devices) {
enumVals.push(device.systemLocation); enumVals.push(device.systemLocation);
enumTitelVals.push(device.deviceName + " (" + device.systemLocation + ")"); enumTitleVals.push(device.deviceName + " (" + device.systemLocation + ")");
} }
// Select configured device // Select configured device
@ -1993,7 +2071,7 @@ var updateSelectList = function (ledType, discoveryInfo) {
if (discoveryInfo.devices.length == 0) { if (discoveryInfo.devices.length == 0) {
enumVals.push("NONE"); enumVals.push("NONE");
enumTitelVals.push($.i18n('edt_dev_spec_devices_discovered_none')); enumTitleVals.push($.i18n('edt_dev_spec_devices_discovered_none'));
$('#btn_submit_controller').prop('disabled', true); $('#btn_submit_controller').prop('disabled', true);
showAllDeviceInputOptions(key, false); showAllDeviceInputOptions(key, false);
@ -2004,18 +2082,19 @@ var updateSelectList = function (ledType, discoveryInfo) {
} }
if (enumVals.length > 0) { if (enumVals.length > 0) {
updateJsonEditorSelection(conf_editor, 'root.specificOptions', key, addSchemaElements, enumVals, enumTitelVals, enumDefaultVal, addSelect, addCustom); updateJsonEditorSelection(conf_editor, 'root.specificOptions', key, addSchemaElements, enumVals, enumTitleVals, enumDefaultVal, addSelect, addCustom);
} }
}; };
async function discover_device(ledType, params) { async function discover_device(ledType, params) {
$('#btn_submit_controller').prop('disabled', true);
const result = await requestLedDeviceDiscovery(ledType, params); const result = await requestLedDeviceDiscovery(ledType, params);
var discoveryResult; var discoveryResult = {};
if (result && !result.error) { if (result) {
if (result.error) {
throw (result.error);
}
discoveryResult = result.info; discoveryResult = result.info;
} }
else { else {
@ -2024,8 +2103,7 @@ async function discover_device(ledType, params) {
ledDevicetype: ledType ledDevicetype: ledType
} }
} }
return discoveryResult;
updateSelectList(ledType, discoveryResult);
} }
async function getProperties_device(ledType, key, params) { async function getProperties_device(ledType, key, params) {
@ -2089,23 +2167,7 @@ function updateElements(ledType, key) {
conf_editor.getEditor("root.generalOptions.hardwareLedCount").setValue(hardwareLedCount); conf_editor.getEditor("root.generalOptions.hardwareLedCount").setValue(hardwareLedCount);
break; break;
case "wled": case "wled":
var ledProperties = devicesProperties[ledType][key]; updateElementsWled(ledType, key);
if (ledProperties && ledProperties.leds) {
hardwareLedCount = ledProperties.leds.count;
if (ledProperties.maxLedCount) {
var maxLedCount = ledProperties.maxLedCount;
if (hardwareLedCount > maxLedCount) {
showInfoDialog('warning', $.i18n("conf_leds_config_warning"), $.i18n('conf_leds_error_hwled_gt_maxled', hardwareLedCount, maxLedCount, maxLedCount));
hardwareLedCount = maxLedCount;
conf_editor.getEditor("root.specificOptions.streamProtocol").setValue("RAW");
//Workaround, as value seems to getting updated property when a 'getEditor("root.specificOptions").getValue()' is done during save
var editor = conf_editor.getEditor("root.specificOptions");
editor.value["streamProtocol"] = "RAW";
}
}
}
conf_editor.getEditor("root.generalOptions.hardwareLedCount").setValue(hardwareLedCount);
break; break;
case "nanoleaf": case "nanoleaf":
@ -2190,3 +2252,162 @@ function disableAutoResolvedGeneralOptions() {
conf_editor.getEditor("root.generalOptions.colorOrder").disable(); conf_editor.getEditor("root.generalOptions.colorOrder").disable();
} }
function validateWledSegmentConfig(streamSegmentId) {
var overlapSegNames = [];
if (streamSegmentId > -1) {
if (!jQuery.isEmptyObject(devicesProperties)) {
var host = conf_editor.getEditor("root.specificOptions.host").getValue();
var ledProperties = devicesProperties['wled'][host];
if (ledProperties && ledProperties.state) {
var segments = ledProperties.state.seg;
var segmentConfig = segments.filter(seg => seg.id == streamSegmentId)[0];
var overlappingSegments = segments.filter((seg) => {
if (seg.id != streamSegmentId) {
if ((segmentConfig.start >= seg.stop) || (segmentConfig.start < seg.start && segmentConfig.stop <= seg.start)) {
return false;
}
return true;
}
});
if (overlappingSegments.length > 0) {
var overlapSegNames = [];
for (const segment of overlappingSegments) {
if (segment.n) {
overlapSegNames.push(segment.n);
} else {
overlapSegNames.push("Segment " + segment.id);
}
}
}
}
}
}
return overlapSegNames;
}
function validateWledLedCount(hardwareLedCount) {
if (!jQuery.isEmptyObject(devicesProperties)) {
var host = conf_editor.getEditor("root.specificOptions.host").getValue();
var ledDeviceProperties = devicesProperties["wled"][host];
if (ledDeviceProperties) {
var streamProtocol = conf_editor.getEditor("root.specificOptions.streamProtocol").getValue();
if (streamProtocol === "RAW") {
var maxLedCount = 490;
if (ledDeviceProperties.maxLedCount) {
//WLED not DDP ready
maxLedCount = ledDeviceProperties.maxLedCount;
if (hardwareLedCount > maxLedCount) {
showInfoDialog('warning', $.i18n("conf_leds_config_warning"), $.i18n('conf_leds_error_hwled_gt_maxled', hardwareLedCount, maxLedCount, maxLedCount));
hardwareLedCount = maxLedCount;
conf_editor.getEditor("root.generalOptions.hardwareLedCount").setValue(hardwareLedCount);
conf_editor.getEditor("root.specificOptions.streamProtocol").setValue("RAW");
}
} else {
//WLED is DDP ready
if (hardwareLedCount > maxLedCount) {
var newStreamingProtocol = "DDP";
showInfoDialog('warning', $.i18n("conf_leds_config_warning"), $.i18n('conf_leds_error_hwled_gt_maxled_protocol', hardwareLedCount, maxLedCount, newStreamingProtocol));
conf_editor.getEditor("root.specificOptions.streamProtocol").setValue(newStreamingProtocol);
}
}
}
}
}
}
function updateElementsWled(ledType, key) {
// Get configured device's details
var configuredDeviceType = window.serverConfig.device.type;
var configuredHost = window.serverConfig.device.host;
var host = conf_editor.getEditor("root.specificOptions.host").getValue();
//New segment selection list values
var enumSegSelectVals = [];
var enumSegSelectTitleVals = [];
var enumSegSelectDefaultVal = "";
if (devicesProperties[ledType] && devicesProperties[ledType][key]) {
var ledDeviceProperties = devicesProperties[ledType][key];
if (!jQuery.isEmptyObject(ledDeviceProperties)) {
if (ledDeviceProperties.info) {
if (ledDeviceProperties.info.liveseg && ledDeviceProperties.info.liveseg < 0) {
// "Use main segment only" is disabled
var defaultSegmentId = "-1";
enumSegSelectVals.push(defaultSegmentId);
enumSegSelectTitleVals.push($.i18n('edt_dev_spec_segments_disabled_title'));
enumSegSelectDefaultVal = defaultSegmentId;
} else {
if (ledDeviceProperties.state) {
//Prepare new segment selection list
var segments = ledDeviceProperties.state.seg;
for (const segment of segments) {
enumSegSelectVals.push(segment.id.toString());
if (segment.n) {
enumSegSelectTitleVals.push(segment.n);
} else {
enumSegSelectTitleVals.push("Segment " + segment.id);
}
}
var currentSegmentId = conf_editor.getEditor("root.specificOptions.segments.streamSegmentId").getValue().toString();
enumSegSelectDefaultVal = currentSegmentId;
}
}
// Check if currently configured segment is available at WLED
var configuredDeviceType = window.serverConfig.device.type;
var configuredHost = window.serverConfig.device.host;
var host = conf_editor.getEditor("root.specificOptions.host").getValue();
if (configuredDeviceType == ledType && configuredHost == host) {
var configuredStreamSegmentId = window.serverConfig.device.segments.streamSegmentId.toString();
var segmentIdFound = enumSegSelectVals.filter(segId => segId == configuredStreamSegmentId).length;
if (!segmentIdFound) {
showInfoDialog('warning', $.i18n("conf_leds_config_warning"), $.i18n('conf_leds_error_wled_segment_missing', configuredStreamSegmentId));
}
}
}
}
} else {
//If failed to get properties
var hardwareLedCount;
if (configuredDeviceType == ledType && configuredHost == host) {
// Populate elements from existing configuration
var configuredstreamSegmentId = window.serverConfig.device.segments.streamSegmentId.toString();
enumSegSelectVals = [configuredstreamSegmentId];
enumSegSelectTitleVals = ["Segment " + configuredstreamSegmentId];
enumSegSelectDefaultVal = configuredstreamSegmentId;
hardwareLedCount = window.serverConfig.device.hardwareLedCount;
} else {
// Populate elements with default values
defaultSegmentId = "-1";
enumSegSelectVals.push(defaultSegmentId);
enumSegSelectTitleVals.push($.i18n('edt_dev_spec_segments_disabled_title'));
enumSegSelectDefaultVal = defaultSegmentId;
hardwareLedCount = 1;
}
conf_editor.getEditor("root.generalOptions.hardwareLedCount").setValue(hardwareLedCount);
}
updateJsonEditorSelection(conf_editor, 'root.specificOptions.segments',
'segmentList', {}, enumSegSelectVals, enumSegSelectTitleVals, enumSegSelectDefaultVal, false, false);
//Show additional configuration options, if more than one segment is available
var showAdditionalOptions = false;
if (enumSegSelectVals.length > 1) {
showAdditionalOptions = true;
}
showInputOptionForItem(conf_editor, "root.specificOptions.segments", "switchOffOtherSegments", showAdditionalOptions);
}

File diff suppressed because it is too large Load Diff

View File

@ -438,7 +438,10 @@ protected:
uint _ledRGBWCount; uint _ledRGBWCount;
/// Does the device allow restoring the original state? /// Does the device allow restoring the original state?
bool _isRestoreOrigState; bool _isRestoreOrigState;
/// Does the device should be kept on after streaming
bool _isStayOnAfterStreaming;
/// Device, lights state before streaming via hyperion /// Device, lights state before streaming via hyperion
QJsonObject _orignalStateValues; QJsonObject _orignalStateValues;
@ -460,6 +463,9 @@ protected:
/// Is the device in error state and stopped? /// Is the device in error state and stopped?
bool _isDeviceInError; bool _isDeviceInError;
/// Is the device in error state, but is retries might resolve the situation?
bool _isDeviceRecoverable;
/// Timestamp of last write /// Timestamp of last write
QDateTime _lastWriteTime; QDateTime _lastWriteTime;
@ -476,8 +482,9 @@ protected slots:
/// @brief Set device in error state /// @brief Set device in error state
/// ///
/// @param[in] errorMsg The error message to be logged /// @param[in] errorMsg The error message to be logged
/// @param[in] isRecoverable If False, no further retries will be done
/// ///
virtual void setInError( const QString& errorMsg); virtual void setInError( const QString& errorMsg, bool isRecoverable=true);
private: private:

View File

@ -49,11 +49,13 @@ LedDevice::LedDevice(const QJsonObject& deviceConfig, QObject* parent)
, _latchTime_ms(0) , _latchTime_ms(0)
, _ledCount(0) , _ledCount(0)
, _isRestoreOrigState(false) , _isRestoreOrigState(false)
, _isStayOnAfterStreaming(false)
, _isEnabled(false) , _isEnabled(false)
, _isDeviceInitialised(false) , _isDeviceInitialised(false)
, _isDeviceReady(false) , _isDeviceReady(false)
, _isOn(false) , _isOn(false)
, _isDeviceInError(false) , _isDeviceInError(false)
, _isDeviceRecoverable(false)
, _lastWriteTime(QDateTime::currentDateTime()) , _lastWriteTime(QDateTime::currentDateTime())
, _enableAttemptsTimer(nullptr) , _enableAttemptsTimer(nullptr)
, _enableAttemptTimerInterval(DEFAULT_ENABLE_ATTEMPTS_INTERVAL) , _enableAttemptTimerInterval(DEFAULT_ENABLE_ATTEMPTS_INTERVAL)
@ -117,7 +119,7 @@ int LedDevice::close()
return retval; return retval;
} }
void LedDevice::setInError(const QString& errorMsg) void LedDevice::setInError(const QString& errorMsg, bool isRecoverable)
{ {
_isOn = false; _isOn = false;
_isDeviceInError = true; _isDeviceInError = true;
@ -125,6 +127,10 @@ void LedDevice::setInError(const QString& errorMsg)
_isEnabled = false; _isEnabled = false;
this->stopRefreshTimer(); this->stopRefreshTimer();
if (isRecoverable)
{
_isDeviceRecoverable = isRecoverable;
}
Error(_log, "Device disabled, device '%s' signals error: '%s'", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(errorMsg)); Error(_log, "Device disabled, device '%s' signals error: '%s'", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(errorMsg));
emit enableStateChanged(_isEnabled); emit enableStateChanged(_isEnabled);
} }
@ -170,7 +176,7 @@ void LedDevice::enable()
{ {
emit enableStateChanged(false); emit enableStateChanged(false);
if (_maxEnableAttempts > 0) if (_maxEnableAttempts > 0 && _isDeviceRecoverable)
{ {
Debug(_log, "Device's enablement failed - Start retry timer. Retried already done [%d], isEnabled: [%d]", _enableAttempts, _isEnabled); Debug(_log, "Device's enablement failed - Start retry timer. Retried already done [%d], isEnabled: [%d]", _enableAttempts, _isEnabled);
startEnableAttemptsTimer(); startEnableAttemptsTimer();
@ -257,27 +263,30 @@ void LedDevice::startEnableAttemptsTimer()
{ {
++_enableAttempts; ++_enableAttempts;
if (_enableAttempts <= _maxEnableAttempts) if (_isDeviceRecoverable)
{ {
if (_enableAttemptTimerInterval.count() > 0) if (_enableAttempts <= _maxEnableAttempts)
{ {
// setup enable retry timer if (_enableAttemptTimerInterval.count() > 0)
if (_enableAttemptsTimer == nullptr)
{ {
_enableAttemptsTimer = new QTimer(this); // setup enable retry timer
_enableAttemptsTimer->setTimerType(Qt::PreciseTimer); if (_enableAttemptsTimer == nullptr)
connect(_enableAttemptsTimer, &QTimer::timeout, this, &LedDevice::enable); {
} _enableAttemptsTimer = new QTimer(this);
_enableAttemptsTimer->setInterval(static_cast<int>(_enableAttemptTimerInterval.count() * 1000)); //NOLINT _enableAttemptsTimer->setTimerType(Qt::PreciseTimer);
connect(_enableAttemptsTimer, &QTimer::timeout, this, &LedDevice::enable);
}
_enableAttemptsTimer->setInterval(static_cast<int>(_enableAttemptTimerInterval.count() * 1000)); //NOLINT
Info(_log, "Start %d. attempt of %d to enable the device in %d seconds", _enableAttempts, _maxEnableAttempts, _enableAttemptTimerInterval.count()); Info(_log, "Start %d. attempt of %d to enable the device in %d seconds", _enableAttempts, _maxEnableAttempts, _enableAttemptTimerInterval.count());
_enableAttemptsTimer->start(); _enableAttemptsTimer->start();
}
}
else
{
Error(_log, "Device disabled. Maximum number of %d attempts enabling the device reached. Tried for %d seconds.", _maxEnableAttempts, _enableAttempts * _enableAttemptTimerInterval.count());
_enableAttempts = 0;
} }
}
else
{
Error(_log, "Device disabled. Maximum number of %d attempts enabling the device reached. Tried for %d seconds.", _maxEnableAttempts, _enableAttempts * _enableAttemptTimerInterval.count());
_enableAttempts = 0;
} }
} }
@ -452,14 +461,16 @@ bool LedDevice::switchOff()
bool LedDevice::powerOff() bool LedDevice::powerOff()
{ {
bool rc{ false }; bool rc{ true };
Debug(_log, "Power Off: %s", QSTRING_CSTR(_activeDeviceType)); if (!_isStayOnAfterStreaming)
// Simulate power-off by writing a final "Black" to have a defined outcome
if (writeBlack() >= 0)
{ {
rc = true; Debug(_log, "Power Off: %s", QSTRING_CSTR(_activeDeviceType));
// Simulate power-off by writing a final "Black" to have a defined outcome
if (writeBlack() < 0)
{
rc = false;
}
} }
return rc; return rc;
} }

View File

@ -23,35 +23,61 @@ const bool verbose = false;
const char CONFIG_HOST[] = "host"; const char CONFIG_HOST[] = "host";
const char CONFIG_STREAM_PROTOCOL[] = "streamProtocol"; const char CONFIG_STREAM_PROTOCOL[] = "streamProtocol";
const char CONFIG_RESTORE_STATE[] = "restoreOriginalState"; const char CONFIG_RESTORE_STATE[] = "restoreOriginalState";
const char CONFIG_STAY_ON_AFTER_STREAMING[] = "stayOnAfterStreaming";
const char CONFIG_BRIGHTNESS[] = "brightness"; const char CONFIG_BRIGHTNESS[] = "brightness";
const char CONFIG_BRIGHTNESS_OVERWRITE[] = "overwriteBrightness"; const char CONFIG_BRIGHTNESS_OVERWRITE[] = "overwriteBrightness";
const char CONFIG_SYNC_OVERWRITE[] = "overwriteSync"; const char CONFIG_SYNC_OVERWRITE[] = "overwriteSync";
const char CONFIG_STREAM_SEGMENTS[] = "segments";
const char CONFIG_STREAM_SEGMENT_ID[] = "streamSegmentId";
const char CONFIG_SWITCH_OFF_OTHER_SEGMENTS[] = "switchOffOtherSegments";
const char DEFAULT_STREAM_PROTOCOL[] = "DDP"; const char DEFAULT_STREAM_PROTOCOL[] = "DDP";
// UDP-RAW // UDP-RAW
const int UDP_STREAM_DEFAULT_PORT = 19446; const int UDP_STREAM_DEFAULT_PORT = 19446;
const int UDP_MAX_LED_NUM = 490; const int UDP_MAX_LED_NUM = 490;
// DDP // Version constraints
const char WLED_VERSION_DDP[] = "0.11.0"; const char WLED_VERSION_DDP[] = "0.11.0";
const char WLED_VERSION_SEGMENT_STREAMING[] = "0.13.3";
// WLED JSON-API elements // WLED JSON-API elements
const int API_DEFAULT_PORT = -1; //Use default port per communication scheme const int API_DEFAULT_PORT = -1; //Use default port per communication scheme
const char API_BASE_PATH[] = "/json/"; const char API_BASE_PATH[] = "/json/";
const char API_PATH_STATE[] = "state"; const char API_PATH_STATE[] = "state";
const char API_PATH_INFO[] = "info";
// List of State Information // List of State keys
const char STATE_ON[] = "on"; const char STATE_ON[] = "on";
const char STATE_VALUE_TRUE[] = "true"; const char STATE_BRI[] = "bri";
const char STATE_VALUE_FALSE[] = "false";
const char STATE_LIVE[] = "live"; const char STATE_LIVE[] = "live";
const char STATE_LOR[] = "lor";
const char STATE_SEG[] = "seg";
const char STATE_SEG_ID[] = "id";
const char STATE_SEG_LEN[] = "len";
const char STATE_SEG_FX[] = "fx";
const char STATE_SEG_SX[] = "sx";
const char STATE_MAINSEG[] = "mainseg";
const char STATE_UDPN[] = "udpn";
const char STATE_UDPN_SEND[] = "send";
const char STATE_UDPN_RECV[] = "recv";
const char STATE_TRANSITIONTIME_CURRENTCALL[] = "tt";
// List of Info keys
const char INFO_VER[] = "ver";
const char INFO_LIVESEG[] = "liveseg";
//Default state values
const bool DEFAULT_IS_RESTORE_STATE = false; const bool DEFAULT_IS_RESTORE_STATE = false;
const bool DEFAULT_IS_STAY_ON_AFTER_STREAMING = false;
const bool DEFAULT_IS_BRIGHTNESS_OVERWRITE = true; const bool DEFAULT_IS_BRIGHTNESS_OVERWRITE = true;
const int BRI_MAX = 255; const int BRI_MAX = 255;
const bool DEFAULT_IS_SYNC_OVERWRITE = true; const bool DEFAULT_IS_SYNC_OVERWRITE = true;
const int DEFAULT_SEGMENT_ID = -1;
const bool DEFAULT_IS_SWITCH_OFF_OTHER_SEGMENTS = true;
constexpr std::chrono::milliseconds DEFAULT_IDENTIFY_TIME{ 2000 }; constexpr std::chrono::milliseconds DEFAULT_IDENTIFY_TIME{ 2000 };
@ -61,12 +87,16 @@ LedDeviceWled::LedDeviceWled(const QJsonObject &deviceConfig)
: ProviderUdp(deviceConfig), LedDeviceUdpDdp(deviceConfig), LedDeviceUdpRaw(deviceConfig) : ProviderUdp(deviceConfig), LedDeviceUdpDdp(deviceConfig), LedDeviceUdpRaw(deviceConfig)
,_restApi(nullptr) ,_restApi(nullptr)
,_apiPort(API_DEFAULT_PORT) ,_apiPort(API_DEFAULT_PORT)
,_currentVersion("")
,_isBrightnessOverwrite(DEFAULT_IS_BRIGHTNESS_OVERWRITE) ,_isBrightnessOverwrite(DEFAULT_IS_BRIGHTNESS_OVERWRITE)
,_brightness (BRI_MAX) ,_brightness (BRI_MAX)
,_isSyncOverwrite(DEFAULT_IS_SYNC_OVERWRITE) ,_isSyncOverwrite(DEFAULT_IS_SYNC_OVERWRITE)
,_originalStateUdpnSend(false) ,_originalStateUdpnSend(false)
,_originalStateUdpnRecv(true) ,_originalStateUdpnRecv(true)
,_isStreamDDP(true) ,_isStreamDDP(true)
,_streamSegmentId(DEFAULT_SEGMENT_ID)
,_isSwitchOffOtherSegments(DEFAULT_IS_SWITCH_OFF_OTHER_SEGMENTS)
,_isStreamToSegment(false)
{ {
#ifdef ENABLE_MDNS #ifdef ENABLE_MDNS
QMetaObject::invokeMethod(&MdnsBrowser::getInstance(), "browseForServiceType", QMetaObject::invokeMethod(&MdnsBrowser::getInstance(), "browseForServiceType",
@ -112,6 +142,7 @@ bool LedDeviceWled::init(const QJsonObject &deviceConfig)
{ {
_apiPort = API_DEFAULT_PORT; _apiPort = API_DEFAULT_PORT;
_isRestoreOrigState = _devConfig[CONFIG_RESTORE_STATE].toBool(DEFAULT_IS_RESTORE_STATE); _isRestoreOrigState = _devConfig[CONFIG_RESTORE_STATE].toBool(DEFAULT_IS_RESTORE_STATE);
_isStayOnAfterStreaming = _devConfig[CONFIG_STAY_ON_AFTER_STREAMING].toBool(DEFAULT_IS_STAY_ON_AFTER_STREAMING);
_isSyncOverwrite = _devConfig[CONFIG_SYNC_OVERWRITE].toBool(DEFAULT_IS_SYNC_OVERWRITE); _isSyncOverwrite = _devConfig[CONFIG_SYNC_OVERWRITE].toBool(DEFAULT_IS_SYNC_OVERWRITE);
_isBrightnessOverwrite = _devConfig[CONFIG_BRIGHTNESS_OVERWRITE].toBool(DEFAULT_IS_BRIGHTNESS_OVERWRITE); _isBrightnessOverwrite = _devConfig[CONFIG_BRIGHTNESS_OVERWRITE].toBool(DEFAULT_IS_BRIGHTNESS_OVERWRITE);
_brightness = _devConfig[CONFIG_BRIGHTNESS].toInt(BRI_MAX); _brightness = _devConfig[CONFIG_BRIGHTNESS].toInt(BRI_MAX);
@ -121,6 +152,23 @@ bool LedDeviceWled::init(const QJsonObject &deviceConfig)
Debug(_log, "Overwrite Brightn.: %d", _isBrightnessOverwrite); Debug(_log, "Overwrite Brightn.: %d", _isBrightnessOverwrite);
Debug(_log, "Set Brightness to : %d", _brightness); Debug(_log, "Set Brightness to : %d", _brightness);
QJsonObject segments = _devConfig[CONFIG_STREAM_SEGMENTS].toObject();
_streamSegmentId = segments[CONFIG_STREAM_SEGMENT_ID].toInt(DEFAULT_SEGMENT_ID);
if (_streamSegmentId > DEFAULT_SEGMENT_ID)
{
_isStreamToSegment = true;
}
_isSwitchOffOtherSegments = segments[CONFIG_SWITCH_OFF_OTHER_SEGMENTS].toBool(DEFAULT_IS_SWITCH_OFF_OTHER_SEGMENTS);
Debug(_log, "Stream to one segment: %s", _isStreamToSegment ? "Yes" : "No");
if (_isStreamToSegment )
{
Debug(_log, "Stream to segment [%d]", _streamSegmentId);
Debug(_log, "Switch-off other segments: %s", _isSwitchOffOtherSegments ? "Yes" : "No");
}
isInitOK = true; isInitOK = true;
} }
@ -193,79 +241,187 @@ int LedDeviceWled::close()
return retval; return retval;
} }
QString LedDeviceWled::getOnOffRequest(bool isOn) const QJsonObject LedDeviceWled::getUdpnObject(bool isSendOn, bool isRecvOn) const
{ {
QString state = isOn ? STATE_VALUE_TRUE : STATE_VALUE_FALSE; QJsonObject udpnObj
return QString( "\"%1\":%2,\"%3\":%4" ).arg( STATE_ON, state).arg( STATE_LIVE, state); {
{STATE_UDPN_SEND, isSendOn},
{STATE_UDPN_RECV, isRecvOn}
};
return udpnObj;
} }
QString LedDeviceWled::getBrightnessRequest(int bri) const QJsonObject LedDeviceWled::getSegmentObject(int segmentId, bool isOn, int brightness) const
{ {
return QString( "\"bri\":%1" ).arg(bri); QJsonObject segmentObj
{
{STATE_SEG_ID, segmentId},
{STATE_ON, isOn}
};
if ( brightness > -1)
{
segmentObj.insert(STATE_BRI, brightness);
}
return segmentObj;
} }
QString LedDeviceWled::getEffectRequest(int effect, int speed) const bool LedDeviceWled::sendStateUpdateRequest(const QJsonObject &request, const QString requestType)
{
return QString( "\"seg\":{\"fx\":%1,\"sx\":%2}" ).arg(effect).arg(speed);
}
QString LedDeviceWled::getLorRequest(int lor) const
{
return QString( "\"lor\":%1" ).arg(lor);
}
QString LedDeviceWled::getUdpnRequest(bool isSendOn, bool isRecvOn) const
{
QString send = isSendOn ? STATE_VALUE_TRUE : STATE_VALUE_FALSE;
QString recv = isRecvOn ? STATE_VALUE_TRUE : STATE_VALUE_FALSE;
return QString( "\"udpn\":{\"send\":%1,\"recv\":%2}" ).arg(send, recv);
}
bool LedDeviceWled::sendStateUpdateRequest(const QString &request)
{ {
bool rc = true; bool rc = true;
_restApi->setPath(API_PATH_STATE); _restApi->setPath(API_PATH_STATE);
httpResponse response1 = _restApi->put(QString("{%1}").arg(request)); httpResponse response = _restApi->put(request);
if ( response1.error() ) if ( response.error() )
{ {
QString errorReason = QString("%1 request failed with error: '%2'").arg(requestType, response.getErrorReason());
this->setInError ( errorReason );
rc = false; rc = false;
} }
return rc; return rc;
} }
bool LedDeviceWled::isReadyForSegmentStreaming(semver::version& version) const
{
bool isReady{false};
if (version.isValid())
{
semver::version segmentStreamingVersion{WLED_VERSION_SEGMENT_STREAMING};
if (version < segmentStreamingVersion)
{
Warning(_log, "Segment streaming not supported by your WLED device version [%s], minimum version expected [%s].", _currentVersion.getVersion().c_str(), segmentStreamingVersion.getVersion().c_str());
}
else
{
Debug(_log, "Segment streaming is supported by your WLED device version [%s].", _currentVersion.getVersion().c_str());
isReady = true;
}
}
else
{
Error(_log, "Version provided to test for streaming readiness is not valid ");
}
return isReady;
}
bool LedDeviceWled::isReadyForDDPStreaming(semver::version& version) const
{
bool isReady{false};
if (version.isValid())
{
semver::version ddpVersion{WLED_VERSION_DDP};
if (version < ddpVersion)
{
Warning(_log, "DDP streaming not supported by your WLED device version [%s], minimum version expected [%s]. Fall back to UDP-Streaming (%d LEDs max)", _currentVersion.getVersion().c_str(), ddpVersion.getVersion().c_str(), UDP_MAX_LED_NUM);
}
else
{
Debug(_log, "DDP streaming is supported by your WLED device version [%s]. No limitation in number of LEDs.", _currentVersion.getVersion().c_str());
isReady = true;
}
}
else
{
Error(_log, "Version provided to test for streaming readiness is not valid ");
}
return isReady;
}
bool LedDeviceWled::powerOn() bool LedDeviceWled::powerOn()
{ {
bool on = false; bool on = false;
if ( _isDeviceReady) if ( _isDeviceReady)
{ {
//Power-on WLED device //Power-on WLED device
_restApi->setPath(API_PATH_STATE); QJsonObject cmd;
if (_isStreamToSegment)
QString cmd = getOnOffRequest(true);
if ( _isBrightnessOverwrite)
{ {
cmd += "," + getBrightnessRequest(_brightness); if (!isReadyForSegmentStreaming(_currentVersion))
{
return false;
}
if (_wledInfo[INFO_LIVESEG].toInt() == -1)
{
stopEnableAttemptsTimer();
this->setInError( "Segment streaming configured, but \"Use main segment only\" in WLED Sync Interface configuration is not enabled!", false);
return false;
}
else
{
QJsonArray propertiesSegments = _originalStateProperties[STATE_SEG].toArray();
bool isStreamSegmentIdFound { false };
QJsonArray segments;
for (const auto& segmentItem : qAsConst(propertiesSegments))
{
QJsonObject segmentObj = segmentItem.toObject();
int segmentID = segmentObj.value(STATE_SEG_ID).toInt();
if (segmentID == _streamSegmentId)
{
isStreamSegmentIdFound = true;
int len = segmentObj.value(STATE_SEG_LEN).toInt();
if (getLedCount() > len)
{
QString errorReason = QString("Too many LEDs [%1] configured for segment [%2], which supports maximum [%3] LEDs. Check your WLED setup!").arg(getLedCount()).arg(_streamSegmentId).arg(len);
this->setInError(errorReason, false);
return false;
}
else
{
int brightness{ -1 };
if (_isBrightnessOverwrite)
{
brightness = _brightness;
}
segments.append(getSegmentObject(segmentID, true, brightness));
}
}
else
{
if (_isSwitchOffOtherSegments)
{
segments.append(getSegmentObject(segmentID, false));
}
}
}
if (!isStreamSegmentIdFound)
{
QString errorReason = QString("Segment streaming to segment [%1] configured, but segment does not exist on WLED. Check your WLED setup!").arg(_streamSegmentId);
this->setInError(errorReason, false);
return false;
}
cmd.insert(STATE_SEG, segments);
//Set segment to be streamed to
cmd.insert(STATE_MAINSEG, _streamSegmentId);
}
} }
else
{
if (_isBrightnessOverwrite)
{
cmd.insert(STATE_BRI, _brightness);
}
}
cmd.insert(STATE_LIVE, true);
cmd.insert(STATE_ON, true);
if (_isSyncOverwrite) if (_isSyncOverwrite)
{ {
Debug( _log, "Disable synchronisation with other WLED devices"); Debug( _log, "Disable synchronisation with other WLED devices");
cmd += "," + getUdpnRequest(false, false); cmd.insert(STATE_UDPN, getUdpnObject(false, false));
} }
httpResponse response = _restApi->put(QString("{%1}").arg(cmd)); on = sendStateUpdateRequest(cmd,"Power-on");
if ( response.error() )
{
QString errorReason = QString("Power-on request failed with error: '%1'").arg(response.getErrorReason());
this->setInError ( errorReason );
on = false;
}
else
{
on = true;
}
} }
return on; return on;
} }
@ -279,23 +435,25 @@ bool LedDeviceWled::powerOff()
writeBlack(); writeBlack();
//Power-off the WLED device physically //Power-off the WLED device physically
_restApi->setPath(API_PATH_STATE); QJsonObject cmd;
if (_isStreamToSegment)
{
QJsonArray segments;
segments.append(getSegmentObject(_streamSegmentId, _isStayOnAfterStreaming));
cmd.insert(STATE_SEG, segments);
}
QString cmd = getOnOffRequest(false); cmd.insert(STATE_LIVE, false);
cmd.insert(STATE_TRANSITIONTIME_CURRENTCALL, 0);
cmd.insert(STATE_ON, _isStayOnAfterStreaming);
if (_isSyncOverwrite) if (_isSyncOverwrite)
{ {
Debug( _log, "Restore synchronisation with other WLED devices"); Debug( _log, "Restore synchronisation with other WLED devices");
cmd += "," + getUdpnRequest(_originalStateUdpnSend, _originalStateUdpnRecv); cmd.insert(STATE_UDPN, getUdpnObject(_originalStateUdpnSend, _originalStateUdpnRecv));
} }
httpResponse response = _restApi->put(QString("{%1}").arg(cmd)); off = sendStateUpdateRequest(cmd,"Power-off");
if ( response.error() )
{
QString errorReason = QString("Power-off request failed with error: '%1'").arg(response.getErrorReason());
this->setInError ( errorReason );
off = false;
}
} }
return off; return off;
} }
@ -304,28 +462,33 @@ bool LedDeviceWled::storeState()
{ {
bool rc = true; bool rc = true;
if ( _isRestoreOrigState || _isSyncOverwrite ) if ( _isRestoreOrigState || _isSyncOverwrite || _isStreamToSegment)
{ {
_restApi->setPath(API_PATH_STATE); _restApi->setPath("");
httpResponse response = _restApi->get(); httpResponse response = _restApi->get();
if ( response.error() ) if ( response.error() )
{ {
QString errorReason = QString("Storing device state failed with error: '%1'").arg(response.getErrorReason()); QString errorReason = QString("Retrieving device properties failed with error: '%1'").arg(response.getErrorReason());
setInError(errorReason); setInError(errorReason);
rc = false; rc = false;
} }
else else
{ {
_originalStateProperties = response.getBody().object(); _originalStateProperties = response.getBody().object().value(API_PATH_STATE).toObject();
DebugIf(verbose, _log, "state: [%s]", QString(QJsonDocument(_originalStateProperties).toJson(QJsonDocument::Compact)).toUtf8().constData() ); DebugIf(verbose, _log, "state: [%s]", QString(QJsonDocument(_originalStateProperties).toJson(QJsonDocument::Compact)).toUtf8().constData() );
QJsonObject udpn = _originalStateProperties.value("udpn").toObject(); QJsonObject udpn = _originalStateProperties.value(STATE_UDPN).toObject();
if (!udpn.isEmpty()) if (!udpn.isEmpty())
{ {
_originalStateUdpnSend = udpn["send"].toBool(false); _originalStateUdpnSend = udpn[STATE_UDPN_SEND].toBool(false);
_originalStateUdpnRecv = udpn["recv"].toBool(true); _originalStateUdpnRecv = udpn[STATE_UDPN_RECV].toBool(true);
} }
_wledInfo = response.getBody().object().value(API_PATH_INFO).toObject();
DebugIf(verbose, _log, "info: [%s]", QString(QJsonDocument(_wledInfo).toJson(QJsonDocument::Compact)).toUtf8().constData() );
_currentVersion.setVersion(_wledInfo.value(INFO_VER).toString().toStdString());
} }
} }
@ -340,10 +503,29 @@ bool LedDeviceWled::restoreState()
{ {
_restApi->setPath(API_PATH_STATE); _restApi->setPath(API_PATH_STATE);
if (_isStreamToSegment)
{
QJsonArray propertiesSegments = _originalStateProperties[STATE_SEG].toArray();
QJsonArray segments;
for (const auto& segmentItem : qAsConst(propertiesSegments))
{
QJsonObject segmentObj = segmentItem.toObject();
int segmentID = segmentObj.value(STATE_SEG_ID).toInt();
if (segmentID == _streamSegmentId)
{
segmentObj[STATE_ON] = _isStayOnAfterStreaming;
}
segments.append(segmentObj);
}
_originalStateProperties[STATE_SEG] = segments;
}
_originalStateProperties[STATE_LIVE] = false; _originalStateProperties[STATE_LIVE] = false;
_originalStateProperties[STATE_TRANSITIONTIME_CURRENTCALL] = 0;
_originalStateProperties[STATE_ON] = _isStayOnAfterStreaming;
httpResponse response = _restApi->put(QString(QJsonDocument(_originalStateProperties).toJson(QJsonDocument::Compact)).toUtf8().constData()); httpResponse response = _restApi->put(_originalStateProperties);
if ( response.error() ) if ( response.error() )
{ {
Warning (_log, "%s restoring state failed with error: '%s'", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(response.getErrorReason())); Warning (_log, "%s restoring state failed with error: '%s'", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(response.getErrorReason()));
@ -363,10 +545,10 @@ QJsonObject LedDeviceWled::discover(const QJsonObject& /*params*/)
#ifdef ENABLE_MDNS #ifdef ENABLE_MDNS
QString discoveryMethod("mDNS"); QString discoveryMethod("mDNS");
deviceList = MdnsBrowser::getInstance().getServicesDiscoveredJson( deviceList = MdnsBrowser::getInstance().getServicesDiscoveredJson(
MdnsServiceRegister::getServiceType(_activeDeviceType), MdnsServiceRegister::getServiceType(_activeDeviceType),
MdnsServiceRegister::getServiceNameFilter(_activeDeviceType), MdnsServiceRegister::getServiceNameFilter(_activeDeviceType),
DEFAULT_DISCOVER_TIMEOUT DEFAULT_DISCOVER_TIMEOUT
); );
devicesDiscovered.insert("discoveryMethod", discoveryMethod); devicesDiscovered.insert("discoveryMethod", discoveryMethod);
#endif #endif
devicesDiscovered.insert("devices", deviceList); devicesDiscovered.insert("devices", deviceList);
@ -397,27 +579,21 @@ QJsonObject LedDeviceWled::getProperties(const QJsonObject& params)
{ {
Warning (_log, "%s get properties failed with error: '%s'", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(response.getErrorReason())); Warning (_log, "%s get properties failed with error: '%s'", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(response.getErrorReason()));
} }
else
QJsonObject propertiesDetails = response.getBody().object();
semver::version currentVersion {""};
if (currentVersion.setVersion(propertiesDetails.value("ver").toString().toStdString()))
{ {
semver::version ddpVersion{WLED_VERSION_DDP}; QJsonObject propertiesDetails = response.getBody().object();
if (currentVersion < ddpVersion)
_wledInfo = propertiesDetails.value(API_PATH_INFO).toObject();
_currentVersion.setVersion(_wledInfo.value(INFO_VER).toString().toStdString());
if (!isReadyForDDPStreaming(_currentVersion))
{ {
Warning(_log, "DDP streaming not supported by your WLED device version [%s], minimum version expected [%s]. Fall back to UDP-Streaming (%d LEDs max)", currentVersion.getVersion().c_str(), ddpVersion.getVersion().c_str(), UDP_MAX_LED_NUM);
if (!propertiesDetails.isEmpty()) if (!propertiesDetails.isEmpty())
{ {
propertiesDetails.insert("maxLedCount", UDP_MAX_LED_NUM); propertiesDetails.insert("maxLedCount", UDP_MAX_LED_NUM);
} }
} }
else properties.insert("properties", propertiesDetails);
{
Info(_log, "DDP streaming is supported by your WLED device version [%s]. No limitation in number of LEDs.", currentVersion.getVersion().c_str());
}
} }
properties.insert("properties", propertiesDetails);
} }
DebugIf(verbose, _log, "properties: [%s]", QString(QJsonDocument(properties).toJson(QJsonDocument::Compact)).toUtf8().constData() ); DebugIf(verbose, _log, "properties: [%s]", QString(QJsonDocument(properties).toJson(QJsonDocument::Compact)).toUtf8().constData() );
@ -441,8 +617,23 @@ void LedDeviceWled::identify(const QJsonObject& params)
_isRestoreOrigState = true; _isRestoreOrigState = true;
storeState(); storeState();
QString request = getOnOffRequest(true) + "," + getLorRequest(1) + "," + getEffectRequest(25); QJsonObject cmd;
sendStateUpdateRequest(request);
cmd.insert(STATE_ON, true);
cmd.insert(STATE_LOR, 1);
_streamSegmentId = params[CONFIG_STREAM_SEGMENT_ID].toInt(0);
QJsonObject segment;
segment = getSegmentObject(_streamSegmentId, true, BRI_MAX);
segment.insert(STATE_SEG_FX, 25);
segment.insert(STATE_SEG_SX, 128);
QJsonArray segments;
segments.append(segment);
cmd.insert(STATE_SEG, segments);
sendStateUpdateRequest(cmd,"Identify");
wait(DEFAULT_IDENTIFY_TIME); wait(DEFAULT_IDENTIFY_TIME);

View File

@ -7,6 +7,7 @@
#include "LedDeviceUdpDdp.h" #include "LedDeviceUdpDdp.h"
#include "LedDeviceUdpRaw.h" #include "LedDeviceUdpRaw.h"
#include <utils/version.hpp>
/// ///
/// Implementation of a WLED-device /// Implementation of a WLED-device
/// ///
@ -146,20 +147,13 @@ private:
/// ///
bool openRestAPI(); bool openRestAPI();
/// QJsonObject getUdpnObject(bool send, bool recv) const;
/// @brief Get command to power WLED-device on or off QJsonObject getSegmentObject(int segmentId, bool isOn, int brightness=-1) const;
///
/// @param isOn True, if to switch on device
/// @return Command to switch device on/off
///
QString getOnOffRequest (bool isOn ) const;
QString getBrightnessRequest (int bri ) const; bool sendStateUpdateRequest(const QJsonObject &request, const QString requestType = "");
QString getEffectRequest(int effect, int speed=128) const;
QString getLorRequest(int lor) const;
QString getUdpnRequest(bool send, bool recv) const;
bool sendStateUpdateRequest(const QString &request); bool isReadyForSegmentStreaming(semver::version& version) const;
bool isReadyForDDPStreaming(semver::version& version) const;
QString resolveAddress (const QString& hostName); QString resolveAddress (const QString& hostName);
@ -169,8 +163,11 @@ private:
QString _hostAddress; QString _hostAddress;
int _apiPort; int _apiPort;
QJsonObject _wledInfo;
QJsonObject _originalStateProperties; QJsonObject _originalStateProperties;
semver::version _currentVersion;
bool _isBrightnessOverwrite; bool _isBrightnessOverwrite;
int _brightness; int _brightness;
@ -179,6 +176,10 @@ private:
bool _originalStateUdpnRecv; bool _originalStateUdpnRecv;
bool _isStreamDDP; bool _isStreamDDP;
int _streamSegmentId;
bool _isSwitchOffOtherSegments;
bool _isStreamToSegment;
}; };
#endif // LEDDEVICEWLED_H #endif // LEDDEVICEWLED_H

View File

@ -207,12 +207,12 @@ bool ProviderRs232::tryOpen(int delayAfterConnect_ms)
return _rs232Port.isOpen(); return _rs232Port.isOpen();
} }
void ProviderRs232::setInError(const QString& errorMsg) void ProviderRs232::setInError(const QString& errorMsg, bool isRecoverable)
{ {
_rs232Port.clearError(); _rs232Port.clearError();
this->close(); this->close();
LedDevice::setInError( errorMsg ); LedDevice::setInError( errorMsg, isRecoverable );
} }
int ProviderRs232::writeBytes(const qint64 size, const uint8_t *data) int ProviderRs232::writeBytes(const qint64 size, const uint8_t *data)

View File

@ -119,9 +119,10 @@ protected slots:
/// ///
/// @brief Set device in error state /// @brief Set device in error state
/// ///
/// @param errorMsg The error message to be logged /// @param[in] errorMsg The error message to be logged
/// @param[in] isRecoverable If False, no further retries will be done
/// ///
void setInError( const QString& errorMsg) override; void setInError( const QString& errorMsg, bool isRecoverable=true) override;
/// ///
/// @brief Handle any feedback provided by the device /// @brief Handle any feedback provided by the device

View File

@ -35,6 +35,46 @@
"access": "expert", "access": "expert",
"propertyOrder": 3 "propertyOrder": 3
}, },
"segments": {
"type": "object",
"title": "Segment streaming",
"required": false,
"properties": {
"segmentList": {
"type": "string",
"title": "edt_dev_spec_segments_title",
"enum": [ "-1" ],
"default": "-1",
"options": {
"enum_titles": [ "edt_dev_spec_segments_disabled_title" ]
},
"propertyOrder": 1
},
"streamSegmentId": {
"type": "integer",
"title": "edt_dev_spec_segmentId_title",
"default": -1,
"minimum": -1,
"maximum": 16,
"options": {
"hidden": true
},
"access": "expert",
"propertyOrder": 2
},
"switchOffOtherSegments": {
"type": "boolean",
"format": "checkbox",
"title": "edt_dev_spec_segmentsSwitchOffOthers_title",
"default": true,
"required": true,
"access": "advanced",
"propertyOrder": 3
}
},
"propertyOrder": 4,
"additionalProperties": false
},
"restoreOriginalState": { "restoreOriginalState": {
"type": "boolean", "type": "boolean",
"format": "checkbox", "format": "checkbox",
@ -44,7 +84,18 @@
"options": { "options": {
"infoText": "edt_dev_spec_restoreOriginalState_title_info" "infoText": "edt_dev_spec_restoreOriginalState_title_info"
}, },
"propertyOrder": 4 "propertyOrder": 7
},
"stayOnAfterStreaming": {
"type": "boolean",
"format": "checkbox",
"title": "edt_dev_spec_stayOnAfterStreaming_title",
"default": false,
"required": true,
"options": {
"infoText": "edt_dev_spec_stayOnAfterStreaming_title_info"
},
"propertyOrder": 8
}, },
"overwriteSync": { "overwriteSync": {
"type": "boolean", "type": "boolean",
@ -53,7 +104,7 @@
"default": true, "default": true,
"required": true, "required": true,
"access": "advanced", "access": "advanced",
"propertyOrder": 5 "propertyOrder": 9
}, },
"overwriteBrightness": { "overwriteBrightness": {
"type": "boolean", "type": "boolean",
@ -62,7 +113,7 @@
"default": true, "default": true,
"required": true, "required": true,
"access": "advanced", "access": "advanced",
"propertyOrder": 6 "propertyOrder": 10
}, },
"brightness": { "brightness": {
"type": "integer", "type": "integer",
@ -76,7 +127,7 @@
} }
}, },
"access": "advanced", "access": "advanced",
"propertyOrder": 7 "propertyOrder": 11
}, },
"latchTime": { "latchTime": {
"type": "integer", "type": "integer",
@ -89,8 +140,8 @@
"options": { "options": {
"infoText": "edt_dev_spec_latchtime_title_info" "infoText": "edt_dev_spec_latchtime_title_info"
}, },
"propertyOrder": 8 "propertyOrder": 12
} }
}, },
"additionalProperties": true "additionalProperties": true
} }