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>
</td>
</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>
<td class="ltd">
<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_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_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_text": "Failed to get the device's properties. Please check the configuration items.",
"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_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_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_layout_advanced": "Advanced Settings",
"conf_leds_layout_blacklist_num_title": "Number of LEDs",
@ -558,7 +560,7 @@
"edt_dev_spec_brightnessOverwrite_title": "Overwrite brightness",
"edt_dev_spec_brightnessThreshold_title": "Signal detection brightness minimum",
"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_cid_title": "CID",
"edt_dev_spec_clientKey_title": "Clientkey",
@ -615,15 +617,22 @@
"edt_dev_spec_razer_device_title": "Razer Chroma Device",
"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_rgbw_calibration_enable" : "White channel calibration (RGBW only)",
"edt_dev_spec_rgbw_calibration_limit" : "White channel limit",
"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_blue" : "Blue/White channel aspect",
"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_red": "Red/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_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_spipath_title": "SPI Device",
"edt_dev_spec_sslHSTimeoutMax_title": "Streamer handshake timeout maximum",
"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_switchOffOnbelowMinBrightness_title": "Switch-off, below minimum",
"edt_dev_spec_syncOverwrite_title": "Disable synchronisation",
@ -866,11 +875,11 @@
"general_country_us": "United States",
"general_disabled": "disabled",
"general_enabled": "enabled",
"general_speech_ca": "Catalan",
"general_speech_ca": "Catalan",
"general_speech_cs": "Czech",
"general_speech_da": "Danish",
"general_speech_de": "German",
"general_speech_el": "Greek",
"general_speech_el": "Greek",
"general_speech_en": "English",
"general_speech_es": "Spanish",
"general_speech_fr": "French",

View File

@ -1002,6 +1002,21 @@ $(document).ready(function () {
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 () {
var generalOptions = window.serverSchema.properties.device;
@ -1080,8 +1095,8 @@ $(document).ready(function () {
$('#btn_test_controller').hide();
switch (ledType) {
case "cololight":
case "wled":
case "cololight":
case "nanoleaf":
showAllDeviceInputOptions("hostList", false);
case "apa102":
@ -1107,7 +1122,22 @@ $(document).ready(function () {
if (storedAccess === 'expert') {
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;
colorOrderDefault = "rgb";
break;
@ -1211,8 +1241,8 @@ $(document).ready(function () {
}
break;
case "cololight":
case "wled":
case "cololight":
var hostList = conf_editor.getEditor("root.specificOptions.hostList").getValue();
if (hostList !== "SELECT") {
var host = conf_editor.getEditor("root.specificOptions.host").getValue();
@ -1339,7 +1369,9 @@ $(document).ready(function () {
break;
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);
break;
@ -1452,6 +1484,10 @@ $(document).ready(function () {
}
conf_editor.getEditor("root.specificOptions.rateList").setValue(rate);
break;
case "wled":
var hardwareLedCount = conf_editor.getEditor("root.generalOptions.hardwareLedCount").getValue();
validateWledLedCount(hardwareLedCount);
break;
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
conf_editor.watch('root.generalOptions.hardwareLedCountList', () => {
var hwLedCountSelected = conf_editor.getEditor("root.generalOptions.hardwareLedCountList").getValue();
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
@ -1798,8 +1876,8 @@ function saveLedConfig(genDefLayout = false) {
location.reload();
}
// build dynamic enum
var updateSelectList = function (ledType, discoveryInfo) {
// build dynamic enum for hosts or output paths
var updateOutputSelectList = function (ledType, discoveryInfo) {
// Only update, if ledType is equal of selected controller type and discovery info exists
if (ledType !== $("#leddevices").val() || !discoveryInfo.devices) {
return;
@ -1810,7 +1888,7 @@ var updateSelectList = function (ledType, discoveryInfo) {
var key;
var enumVals = [];
var enumTitelVals = [];
var enumTitleVals = [];
var enumDefaultVal = "";
var addSelect = false;
var addCustom = false;
@ -1835,7 +1913,7 @@ var updateSelectList = function (ledType, discoveryInfo) {
if (discoveryInfo.devices.length === 0) {
enumVals.push("NONE");
enumTitelVals.push($.i18n('edt_dev_spec_devices_discovered_none'));
enumTitleVals.push($.i18n('edt_dev_spec_devices_discovered_none'));
}
else {
var name;
@ -1876,7 +1954,7 @@ var updateSelectList = function (ledType, discoveryInfo) {
}
enumVals.push(host);
enumTitelVals.push(name);
enumTitleVals.push(name);
}
//Always allow to add custom configuration
@ -1904,7 +1982,7 @@ var updateSelectList = function (ledType, discoveryInfo) {
if (discoveryInfo.devices.length == 0) {
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);
showAllDeviceInputOptions(key, false);
}
@ -1922,7 +2000,7 @@ var updateSelectList = function (ledType, discoveryInfo) {
} else {
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
@ -1951,7 +2029,7 @@ var updateSelectList = function (ledType, discoveryInfo) {
if (discoveryInfo.devices.length == 0) {
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);
showAllDeviceInputOptions(key, false);
}
@ -1970,7 +2048,7 @@ var updateSelectList = function (ledType, discoveryInfo) {
case "piblaster":
for (const device of discoveryInfo.devices) {
enumVals.push(device.systemLocation);
enumTitelVals.push(device.deviceName + " (" + device.systemLocation + ")");
enumTitleVals.push(device.deviceName + " (" + device.systemLocation + ")");
}
// Select configured device
@ -1993,7 +2071,7 @@ var updateSelectList = function (ledType, discoveryInfo) {
if (discoveryInfo.devices.length == 0) {
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);
showAllDeviceInputOptions(key, false);
@ -2004,18 +2082,19 @@ var updateSelectList = function (ledType, discoveryInfo) {
}
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) {
$('#btn_submit_controller').prop('disabled', true);
const result = await requestLedDeviceDiscovery(ledType, params);
var discoveryResult;
if (result && !result.error) {
var discoveryResult = {};
if (result) {
if (result.error) {
throw (result.error);
}
discoveryResult = result.info;
}
else {
@ -2024,8 +2103,7 @@ async function discover_device(ledType, params) {
ledDevicetype: ledType
}
}
updateSelectList(ledType, discoveryResult);
return discoveryResult;
}
async function getProperties_device(ledType, key, params) {
@ -2089,23 +2167,7 @@ function updateElements(ledType, key) {
conf_editor.getEditor("root.generalOptions.hardwareLedCount").setValue(hardwareLedCount);
break;
case "wled":
var ledProperties = devicesProperties[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);
updateElementsWled(ledType, key);
break;
case "nanoleaf":
@ -2190,3 +2252,162 @@ function disableAutoResolvedGeneralOptions() {
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;
/// 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
QJsonObject _orignalStateValues;
@ -460,6 +463,9 @@ protected:
/// Is the device in error state and stopped?
bool _isDeviceInError;
/// Is the device in error state, but is retries might resolve the situation?
bool _isDeviceRecoverable;
/// Timestamp of last write
QDateTime _lastWriteTime;
@ -476,8 +482,9 @@ protected slots:
/// @brief Set device in error state
///
/// @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:

View File

@ -49,11 +49,13 @@ LedDevice::LedDevice(const QJsonObject& deviceConfig, QObject* parent)
, _latchTime_ms(0)
, _ledCount(0)
, _isRestoreOrigState(false)
, _isStayOnAfterStreaming(false)
, _isEnabled(false)
, _isDeviceInitialised(false)
, _isDeviceReady(false)
, _isOn(false)
, _isDeviceInError(false)
, _isDeviceRecoverable(false)
, _lastWriteTime(QDateTime::currentDateTime())
, _enableAttemptsTimer(nullptr)
, _enableAttemptTimerInterval(DEFAULT_ENABLE_ATTEMPTS_INTERVAL)
@ -117,7 +119,7 @@ int LedDevice::close()
return retval;
}
void LedDevice::setInError(const QString& errorMsg)
void LedDevice::setInError(const QString& errorMsg, bool isRecoverable)
{
_isOn = false;
_isDeviceInError = true;
@ -125,6 +127,10 @@ void LedDevice::setInError(const QString& errorMsg)
_isEnabled = false;
this->stopRefreshTimer();
if (isRecoverable)
{
_isDeviceRecoverable = isRecoverable;
}
Error(_log, "Device disabled, device '%s' signals error: '%s'", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(errorMsg));
emit enableStateChanged(_isEnabled);
}
@ -170,7 +176,7 @@ void LedDevice::enable()
{
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);
startEnableAttemptsTimer();
@ -257,27 +263,30 @@ void LedDevice::startEnableAttemptsTimer()
{
++_enableAttempts;
if (_enableAttempts <= _maxEnableAttempts)
if (_isDeviceRecoverable)
{
if (_enableAttemptTimerInterval.count() > 0)
if (_enableAttempts <= _maxEnableAttempts)
{
// setup enable retry timer
if (_enableAttemptsTimer == nullptr)
if (_enableAttemptTimerInterval.count() > 0)
{
_enableAttemptsTimer = new QTimer(this);
_enableAttemptsTimer->setTimerType(Qt::PreciseTimer);
connect(_enableAttemptsTimer, &QTimer::timeout, this, &LedDevice::enable);
}
_enableAttemptsTimer->setInterval(static_cast<int>(_enableAttemptTimerInterval.count() * 1000)); //NOLINT
// setup enable retry timer
if (_enableAttemptsTimer == nullptr)
{
_enableAttemptsTimer = new QTimer(this);
_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());
_enableAttemptsTimer->start();
Info(_log, "Start %d. attempt of %d to enable the device in %d seconds", _enableAttempts, _maxEnableAttempts, _enableAttemptTimerInterval.count());
_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 rc{ false };
bool 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)
if (!_isStayOnAfterStreaming)
{
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;
}

View File

@ -23,35 +23,61 @@ const bool verbose = false;
const char CONFIG_HOST[] = "host";
const char CONFIG_STREAM_PROTOCOL[] = "streamProtocol";
const char CONFIG_RESTORE_STATE[] = "restoreOriginalState";
const char CONFIG_STAY_ON_AFTER_STREAMING[] = "stayOnAfterStreaming";
const char CONFIG_BRIGHTNESS[] = "brightness";
const char CONFIG_BRIGHTNESS_OVERWRITE[] = "overwriteBrightness";
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";
// UDP-RAW
const int UDP_STREAM_DEFAULT_PORT = 19446;
const int UDP_MAX_LED_NUM = 490;
// DDP
// Version constraints
const char WLED_VERSION_DDP[] = "0.11.0";
const char WLED_VERSION_SEGMENT_STREAMING[] = "0.13.3";
// WLED JSON-API elements
const int API_DEFAULT_PORT = -1; //Use default port per communication scheme
const char API_BASE_PATH[] = "/json/";
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_VALUE_TRUE[] = "true";
const char STATE_VALUE_FALSE[] = "false";
const char STATE_BRI[] = "bri";
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_STAY_ON_AFTER_STREAMING = false;
const bool DEFAULT_IS_BRIGHTNESS_OVERWRITE = true;
const int BRI_MAX = 255;
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 };
@ -61,12 +87,16 @@ LedDeviceWled::LedDeviceWled(const QJsonObject &deviceConfig)
: ProviderUdp(deviceConfig), LedDeviceUdpDdp(deviceConfig), LedDeviceUdpRaw(deviceConfig)
,_restApi(nullptr)
,_apiPort(API_DEFAULT_PORT)
,_currentVersion("")
,_isBrightnessOverwrite(DEFAULT_IS_BRIGHTNESS_OVERWRITE)
,_brightness (BRI_MAX)
,_isSyncOverwrite(DEFAULT_IS_SYNC_OVERWRITE)
,_originalStateUdpnSend(false)
,_originalStateUdpnRecv(true)
,_isStreamDDP(true)
,_streamSegmentId(DEFAULT_SEGMENT_ID)
,_isSwitchOffOtherSegments(DEFAULT_IS_SWITCH_OFF_OTHER_SEGMENTS)
,_isStreamToSegment(false)
{
#ifdef ENABLE_MDNS
QMetaObject::invokeMethod(&MdnsBrowser::getInstance(), "browseForServiceType",
@ -112,6 +142,7 @@ bool LedDeviceWled::init(const QJsonObject &deviceConfig)
{
_apiPort = API_DEFAULT_PORT;
_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);
_isBrightnessOverwrite = _devConfig[CONFIG_BRIGHTNESS_OVERWRITE].toBool(DEFAULT_IS_BRIGHTNESS_OVERWRITE);
_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, "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;
}
@ -193,79 +241,187 @@ int LedDeviceWled::close()
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;
return QString( "\"%1\":%2,\"%3\":%4" ).arg( STATE_ON, state).arg( STATE_LIVE, state);
QJsonObject udpnObj
{
{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
{
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 LedDeviceWled::sendStateUpdateRequest(const QJsonObject &request, const QString requestType)
{
bool rc = true;
_restApi->setPath(API_PATH_STATE);
httpResponse response1 = _restApi->put(QString("{%1}").arg(request));
if ( response1.error() )
httpResponse response = _restApi->put(request);
if ( response.error() )
{
QString errorReason = QString("%1 request failed with error: '%2'").arg(requestType, response.getErrorReason());
this->setInError ( errorReason );
rc = false;
}
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 on = false;
if ( _isDeviceReady)
{
//Power-on WLED device
_restApi->setPath(API_PATH_STATE);
QString cmd = getOnOffRequest(true);
if ( _isBrightnessOverwrite)
QJsonObject cmd;
if (_isStreamToSegment)
{
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)
{
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));
if ( response.error() )
{
QString errorReason = QString("Power-on request failed with error: '%1'").arg(response.getErrorReason());
this->setInError ( errorReason );
on = false;
}
else
{
on = true;
}
on = sendStateUpdateRequest(cmd,"Power-on");
}
return on;
}
@ -279,23 +435,25 @@ bool LedDeviceWled::powerOff()
writeBlack();
//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)
{
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));
if ( response.error() )
{
QString errorReason = QString("Power-off request failed with error: '%1'").arg(response.getErrorReason());
this->setInError ( errorReason );
off = false;
}
off = sendStateUpdateRequest(cmd,"Power-off");
}
return off;
}
@ -304,28 +462,33 @@ bool LedDeviceWled::storeState()
{
bool rc = true;
if ( _isRestoreOrigState || _isSyncOverwrite )
if ( _isRestoreOrigState || _isSyncOverwrite || _isStreamToSegment)
{
_restApi->setPath(API_PATH_STATE);
_restApi->setPath("");
httpResponse response = _restApi->get();
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);
rc = false;
}
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() );
QJsonObject udpn = _originalStateProperties.value("udpn").toObject();
QJsonObject udpn = _originalStateProperties.value(STATE_UDPN).toObject();
if (!udpn.isEmpty())
{
_originalStateUdpnSend = udpn["send"].toBool(false);
_originalStateUdpnRecv = udpn["recv"].toBool(true);
_originalStateUdpnSend = udpn[STATE_UDPN_SEND].toBool(false);
_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);
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_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() )
{
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
QString discoveryMethod("mDNS");
deviceList = MdnsBrowser::getInstance().getServicesDiscoveredJson(
MdnsServiceRegister::getServiceType(_activeDeviceType),
MdnsServiceRegister::getServiceNameFilter(_activeDeviceType),
DEFAULT_DISCOVER_TIMEOUT
);
MdnsServiceRegister::getServiceType(_activeDeviceType),
MdnsServiceRegister::getServiceNameFilter(_activeDeviceType),
DEFAULT_DISCOVER_TIMEOUT
);
devicesDiscovered.insert("discoveryMethod", discoveryMethod);
#endif
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()));
}
QJsonObject propertiesDetails = response.getBody().object();
semver::version currentVersion {""};
if (currentVersion.setVersion(propertiesDetails.value("ver").toString().toStdString()))
else
{
semver::version ddpVersion{WLED_VERSION_DDP};
if (currentVersion < ddpVersion)
QJsonObject propertiesDetails = response.getBody().object();
_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())
{
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() );
@ -441,8 +617,23 @@ void LedDeviceWled::identify(const QJsonObject& params)
_isRestoreOrigState = true;
storeState();
QString request = getOnOffRequest(true) + "," + getLorRequest(1) + "," + getEffectRequest(25);
sendStateUpdateRequest(request);
QJsonObject cmd;
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);

View File

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

View File

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

View File

@ -119,9 +119,10 @@ protected slots:
///
/// @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

View File

@ -35,6 +35,46 @@
"access": "expert",
"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": {
"type": "boolean",
"format": "checkbox",
@ -44,7 +84,18 @@
"options": {
"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": {
"type": "boolean",
@ -53,7 +104,7 @@
"default": true,
"required": true,
"access": "advanced",
"propertyOrder": 5
"propertyOrder": 9
},
"overwriteBrightness": {
"type": "boolean",
@ -62,7 +113,7 @@
"default": true,
"required": true,
"access": "advanced",
"propertyOrder": 6
"propertyOrder": 10
},
"brightness": {
"type": "integer",
@ -76,7 +127,7 @@
}
},
"access": "advanced",
"propertyOrder": 7
"propertyOrder": 11
},
"latchTime": {
"type": "integer",
@ -89,8 +140,8 @@
"options": {
"infoText": "edt_dev_spec_latchtime_title_info"
},
"propertyOrder": 8
"propertyOrder": 12
}
},
"additionalProperties": true
}
"additionalProperties": true
}