mirror of
https://github.com/hyperion-project/hyperion.ng.git
synced 2023-10-10 13:36:59 +02:00
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:
parent
ef7ceb0bbf
commit
a57bcbc2b8
@ -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>
|
||||||
|
@ -63,6 +63,8 @@
|
|||||||
"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",
|
||||||
@ -620,10 +622,17 @@
|
|||||||
"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",
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -411,6 +411,32 @@ function isJsonString(str) {
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getObjectProperty = (obj, path) => path.split(".").reduce((o, key) => o && typeof o[key] !== 'undefined' ? o[key] : undefined, obj);
|
||||||
|
|
||||||
|
const setObjectProperty = (object, path, value) => {
|
||||||
|
const parts = path.split('.');
|
||||||
|
const limit = parts.length - 1;
|
||||||
|
for (let i = 0; i < limit; ++i) {
|
||||||
|
const key = parts[i];
|
||||||
|
if (key === "__proto__" || key === "constructor") continue;
|
||||||
|
object = object[key] ?? (object[key] = {});
|
||||||
|
}
|
||||||
|
const key = parts[limit];
|
||||||
|
object[key] = value;
|
||||||
|
};
|
||||||
|
|
||||||
|
function getLongPropertiesPath(path) {
|
||||||
|
if (path) {
|
||||||
|
var path = path.replace('root.', '');
|
||||||
|
const parts = path.split('.');
|
||||||
|
parts.forEach(function (part, index) {
|
||||||
|
this[index] += ".properties";
|
||||||
|
}, parts);
|
||||||
|
path = parts.join('.') + '.';
|
||||||
|
}
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
function createJsonEditor(container, schema, setconfig, usePanel, arrayre) {
|
function createJsonEditor(container, schema, setconfig, usePanel, arrayre) {
|
||||||
$('#' + container).off();
|
$('#' + container).off();
|
||||||
$('#' + container).html("");
|
$('#' + container).html("");
|
||||||
@ -531,7 +557,8 @@ function updateJsonEditorSelection(rootEditor, path, key, addElements, newEnumVa
|
|||||||
|
|
||||||
editor.original_schema.properties[key] = orginalProperties;
|
editor.original_schema.properties[key] = orginalProperties;
|
||||||
editor.schema.properties[key] = newSchema[key];
|
editor.schema.properties[key] = newSchema[key];
|
||||||
rootEditor.validator.schema.properties[editor.key].properties[key] = newSchema[key];
|
//Update schema properties for validator
|
||||||
|
setObjectProperty(rootEditor.validator.schema.properties, getLongPropertiesPath(path) + key, newSchema[key]);
|
||||||
|
|
||||||
editor.removeObjectProperty(key);
|
editor.removeObjectProperty(key);
|
||||||
delete editor.cached_editors[key];
|
delete editor.cached_editors[key];
|
||||||
@ -600,7 +627,8 @@ function updateJsonEditorMultiSelection(rootEditor, path, key, addElements, newE
|
|||||||
|
|
||||||
editor.original_schema.properties[key] = orginalProperties;
|
editor.original_schema.properties[key] = orginalProperties;
|
||||||
editor.schema.properties[key] = newSchema[key];
|
editor.schema.properties[key] = newSchema[key];
|
||||||
rootEditor.validator.schema.properties[editor.key].properties[key] = newSchema[key];
|
//Update schema properties for validator
|
||||||
|
setObjectProperty(rootEditor.validator.schema.properties, getLongPropertiesPath(path) + key, newSchema[key]);
|
||||||
|
|
||||||
editor.removeObjectProperty(key);
|
editor.removeObjectProperty(key);
|
||||||
delete editor.cached_editors[key];
|
delete editor.cached_editors[key];
|
||||||
@ -648,7 +676,8 @@ function updateJsonEditorRange(rootEditor, path, key, minimum, maximum, defaultV
|
|||||||
|
|
||||||
editor.original_schema.properties[key] = orginalProperties;
|
editor.original_schema.properties[key] = orginalProperties;
|
||||||
editor.schema.properties[key] = newSchema[key];
|
editor.schema.properties[key] = newSchema[key];
|
||||||
rootEditor.validator.schema.properties[editor.key].properties[key] = newSchema[key];
|
//Update schema properties for validator
|
||||||
|
setObjectProperty(rootEditor.validator.schema.properties, getLongPropertiesPath(path) + key, newSchema[key]);
|
||||||
|
|
||||||
editor.removeObjectProperty(key);
|
editor.removeObjectProperty(key);
|
||||||
delete editor.cached_editors[key];
|
delete editor.cached_editors[key];
|
||||||
@ -1250,15 +1279,26 @@ function isAccessLevelCompliant(accessLevel) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function showInputOptions(path, elements, state) {
|
function showInputOptions(path, elements, state) {
|
||||||
|
|
||||||
|
if (!path.startsWith("root.")) {
|
||||||
|
path = ["root", path].join('.');
|
||||||
|
}
|
||||||
|
|
||||||
for (var i = 0; i < elements.length; i++) {
|
for (var i = 0; i < elements.length; i++) {
|
||||||
$('[data-schemapath="root.' + path + '.' + elements[i] + '"]').toggle(state);
|
$('[data-schemapath="' + path + '.' + elements[i] + '"]').toggle(state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function showInputOptionForItem(editor, path, item, state) {
|
function showInputOptionForItem(editor, path, item, state) {
|
||||||
var accessLevel = editor.schema.properties[path].properties[item].access;
|
//Get access level for full path and item
|
||||||
|
var accessLevel = getObjectProperty(editor.schema.properties, getLongPropertiesPath(path) + item + ".access");
|
||||||
// Enable element only, if access level compliant
|
// Enable element only, if access level compliant
|
||||||
if (!state || isAccessLevelCompliant(accessLevel)) {
|
if (!state || isAccessLevelCompliant(accessLevel)) {
|
||||||
|
|
||||||
|
if (!path) {
|
||||||
|
debugger;
|
||||||
|
path = editor.path;
|
||||||
|
}
|
||||||
showInputOptions(path, [item], state);
|
showInputOptions(path, [item], state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1273,7 +1313,7 @@ function showInputOptionsForKey(editor, item, showForKeys, state) {
|
|||||||
if (typeof showForKeys === 'string') {
|
if (typeof showForKeys === 'string') {
|
||||||
keysToshow.push(showForKeys);
|
keysToshow.push(showForKeys);
|
||||||
} else {
|
} else {
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1281,12 +1321,21 @@ function showInputOptionsForKey(editor, item, showForKeys, state) {
|
|||||||
if ($.inArray(key, keysToshow) === -1) {
|
if ($.inArray(key, keysToshow) === -1) {
|
||||||
var accessLevel = editor.schema.properties[item].properties[key].access;
|
var accessLevel = editor.schema.properties[item].properties[key].access;
|
||||||
|
|
||||||
|
var hidden = false;
|
||||||
|
if (editor.schema.properties[item].properties[key].options) {
|
||||||
|
hidden = editor.schema.properties[item].properties[key].options.hidden;
|
||||||
|
if (typeof hidden === 'undefined') {
|
||||||
|
hidden = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
//Always disable all elements, but only enable elements, if access level compliant
|
//Always disable all elements, but only enable elements, if access level compliant
|
||||||
if (!state || isAccessLevelCompliant(accessLevel)) {
|
if (!state || isAccessLevelCompliant(accessLevel)) {
|
||||||
|
if (!hidden) {
|
||||||
elements.push(key);
|
elements.push(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
showInputOptions(item, elements, state);
|
showInputOptions(item, elements, state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -440,6 +440,9 @@ protected:
|
|||||||
/// 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:
|
||||||
|
|
||||||
|
@ -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,6 +263,8 @@ void LedDevice::startEnableAttemptsTimer()
|
|||||||
{
|
{
|
||||||
++_enableAttempts;
|
++_enableAttempts;
|
||||||
|
|
||||||
|
if (_isDeviceRecoverable)
|
||||||
|
{
|
||||||
if (_enableAttempts <= _maxEnableAttempts)
|
if (_enableAttempts <= _maxEnableAttempts)
|
||||||
{
|
{
|
||||||
if (_enableAttemptTimerInterval.count() > 0)
|
if (_enableAttemptTimerInterval.count() > 0)
|
||||||
@ -280,6 +288,7 @@ void LedDevice::startEnableAttemptsTimer()
|
|||||||
_enableAttempts = 0;
|
_enableAttempts = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void LedDevice::stopEnableAttemptsTimer()
|
void LedDevice::stopEnableAttemptsTimer()
|
||||||
{
|
{
|
||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
{
|
||||||
|
if (!isReadyForSegmentStreaming(_currentVersion))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
QString cmd = getOnOffRequest(true);
|
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)
|
if (_isBrightnessOverwrite)
|
||||||
{
|
{
|
||||||
cmd += "," + getBrightnessRequest(_brightness);
|
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()));
|
||||||
@ -397,28 +579,22 @@ 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();
|
QJsonObject propertiesDetails = response.getBody().object();
|
||||||
|
|
||||||
semver::version currentVersion {""};
|
_wledInfo = propertiesDetails.value(API_PATH_INFO).toObject();
|
||||||
if (currentVersion.setVersion(propertiesDetails.value("ver").toString().toStdString()))
|
_currentVersion.setVersion(_wledInfo.value(INFO_VER).toString().toStdString());
|
||||||
|
if (!isReadyForDDPStreaming(_currentVersion))
|
||||||
{
|
{
|
||||||
semver::version ddpVersion{WLED_VERSION_DDP};
|
|
||||||
if (currentVersion < 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);
|
|
||||||
if (!propertiesDetails.isEmpty())
|
if (!propertiesDetails.isEmpty())
|
||||||
{
|
{
|
||||||
propertiesDetails.insert("maxLedCount", UDP_MAX_LED_NUM);
|
propertiesDetails.insert("maxLedCount", UDP_MAX_LED_NUM);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
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);
|
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);
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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,7 +140,7 @@
|
|||||||
"options": {
|
"options": {
|
||||||
"infoText": "edt_dev_spec_latchtime_title_info"
|
"infoText": "edt_dev_spec_latchtime_title_info"
|
||||||
},
|
},
|
||||||
"propertyOrder": 8
|
"propertyOrder": 12
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"additionalProperties": true
|
"additionalProperties": true
|
||||||
|
Loading…
x
Reference in New Issue
Block a user