From ac57fea09a219efd89550c7409e2404163db649e Mon Sep 17 00:00:00 2001
From: LordGrey <48840279+Lord-Grey@users.noreply.github.com>
Date: Sat, 30 Mar 2024 15:43:50 +0100
Subject: [PATCH 01/17] Fix Kodi Color Calibration, Refactor Wizards (#1718)
* Fix #1674 and refactor wizards
* Have own code file per LED-Device Wizard
* Include SonarLint feedback
* Cleanups
* Apply module pattern
* Address CodeQL findings
* Address CodeQL findings
---
assets/webconfig/js/content_huebridge.js | 52 -
assets/webconfig/js/content_leds.js | 35 +-
assets/webconfig/js/ui_utils.js | 29 +
assets/webconfig/js/wizard.js | 2301 +----------------
.../webconfig/js/wizards/LedDevice_atmoorb.js | 283 ++
.../js/wizards/LedDevice_nanoleaf.js | 94 +
.../js/wizards/LedDevice_philipshue.js | 988 +++++++
.../webconfig/js/wizards/LedDevice_utils.js | 60 +
.../js/wizards/LedDevice_yeelight.js | 300 +++
.../js/wizards/colorCalibrationKodiWizard.js | 485 ++++
.../js/wizards/rgbByteOrderWizard.js | 143 +
11 files changed, 2432 insertions(+), 2338 deletions(-)
delete mode 100644 assets/webconfig/js/content_huebridge.js
create mode 100644 assets/webconfig/js/wizards/LedDevice_atmoorb.js
create mode 100644 assets/webconfig/js/wizards/LedDevice_nanoleaf.js
create mode 100644 assets/webconfig/js/wizards/LedDevice_philipshue.js
create mode 100644 assets/webconfig/js/wizards/LedDevice_utils.js
create mode 100644 assets/webconfig/js/wizards/LedDevice_yeelight.js
create mode 100644 assets/webconfig/js/wizards/colorCalibrationKodiWizard.js
create mode 100644 assets/webconfig/js/wizards/rgbByteOrderWizard.js
diff --git a/assets/webconfig/js/content_huebridge.js b/assets/webconfig/js/content_huebridge.js
deleted file mode 100644
index 99da3c76..00000000
--- a/assets/webconfig/js/content_huebridge.js
+++ /dev/null
@@ -1,52 +0,0 @@
-$(document).ready( function() {
-
- $("#create_user").on("click", function() {
- var connectionRetries = 15;
- var data = {"devicetype":"hyperion#"+Date.now()};
- var UserInterval = setInterval(function(){
- $.ajax({
- type: "POST",
- url: 'http://'+$("#ip").val()+'/api',
- processData: false,
- timeout: 1000,
- contentType: 'application/json',
- data: JSON.stringify(data),
- success: function(r) {
- connectionRetries--;
- $("#connectionTime").html(connectionRetries);
- if(connectionRetries == 0) {
- abortConnection(UserInterval);
- }
- else
- {
- $("#abortConnection").hide();
- $('#pairmodal').modal('show');
- $("#ip_alert").hide();
- if (typeof r[0].error != 'undefined') {
- console.log("link not pressed");
- }
- if (typeof r[0].success != 'undefined') {
- $('#pairmodal').modal('hide');
- $('#user').val(r[0].success.username);
-
- $( "#hue_lights" ).empty();
- get_hue_lights();
- clearInterval(UserInterval);
- }
- }
- },
- error: function(XMLHttpRequest, textStatus, errorThrown) {
- $("#ip_alert").show();
- clearInterval(UserInterval);
- }
- });
- },1000);
-});
-
-function abortConnection(UserInterval){
- clearInterval(UserInterval);
- $("#abortConnection").show();
- $('#pairmodal').modal('hide');
-}
-
-});
diff --git a/assets/webconfig/js/content_leds.js b/assets/webconfig/js/content_leds.js
index 0432524a..938bf8ee 100755
--- a/assets/webconfig/js/content_leds.js
+++ b/assets/webconfig/js/content_leds.js
@@ -1086,40 +1086,7 @@ $(document).ready(function () {
conf_editor.validate().length || window.readOnlyMode ? $('#btn_submit_controller').prop('disabled', true) : $('#btn_submit_controller').prop('disabled', false);
// LED controller specific wizards
- $('#btn_wiz_holder').html("");
- $('#btn_led_device_wiz').off();
-
- if (ledType == "philipshue") {
- var ledWizardType = ledType;
- var data = { type: ledWizardType };
- var hue_title = 'wiz_hue_title';
- changeWizard(data, hue_title, startWizardPhilipsHue);
- }
- else if (ledType == "nanoleaf") {
- var ledWizardType = ledType;
- var data = { type: ledWizardType };
- var nanoleaf_user_auth_title = 'wiz_nanoleaf_user_auth_title';
- changeWizard(data, nanoleaf_user_auth_title, startWizardNanoleafUserAuth);
- $('#btn_wiz_holder').hide();
- }
- else if (ledType == "atmoorb") {
- var ledWizardType = (this.checked) ? "atmoorb" : ledType;
- var data = { type: ledWizardType };
- var atmoorb_title = 'wiz_atmoorb_title';
- changeWizard(data, atmoorb_title, startWizardAtmoOrb);
- }
- else if (ledType == "yeelight") {
- var ledWizardType = (this.checked) ? "yeelight" : ledType;
- var data = { type: ledWizardType };
- var yeelight_title = 'wiz_yeelight_title';
- changeWizard(data, yeelight_title, startWizardYeelight);
- }
-
- function changeWizard(data, hint, fn) {
- $('#btn_wiz_holder').html("")
- createHint("wizard", $.i18n(hint), "btn_wiz_holder", "btn_led_device_wiz");
- $('#btn_led_device_wiz').off().on('click', data, fn);
- }
+ createLedDeviceWizards(ledType);
conf_editor.on('ready', function () {
var hwLedCountDefault = 1;
diff --git a/assets/webconfig/js/ui_utils.js b/assets/webconfig/js/ui_utils.js
index 0061dde9..4cbb7967 100644
--- a/assets/webconfig/js/ui_utils.js
+++ b/assets/webconfig/js/ui_utils.js
@@ -1393,3 +1393,32 @@ function isValidHostnameOrIP(value) {
return (isValidHostnameOrIP4(value) || isValidIPv6(value) || isValidServicename(value));
}
+const loadedScripts = [];
+
+function isScriptLoaded(src) {
+ return loadedScripts.indexOf(src) > -1;
+}
+
+function loadScript(src, callback, ...params) {
+ if (isScriptLoaded(src)) {
+ debugMessage('Script ' + src + ' already loaded');
+ if (callback && typeof callback === 'function') {
+ callback( ...params);
+ }
+ return;
+ }
+
+ const script = document.createElement('script');
+ script.src = src;
+
+ script.onload = function () {
+ debugMessage('Script ' + src + ' loaded successfully');
+ loadedScripts.push(src);
+
+ if (callback && typeof callback === 'function') {
+ callback(...params);
+ }
+ };
+
+ document.head.appendChild(script);
+}
diff --git a/assets/webconfig/js/wizard.js b/assets/webconfig/js/wizard.js
index 70584ddb..2524924f 100755
--- a/assets/webconfig/js/wizard.js
+++ b/assets/webconfig/js/wizard.js
@@ -3,2274 +3,71 @@ $(window.hyperion).one("ready", function (event) {
if (getStorage("wizardactive") === 'true') {
requestPriorityClear();
setStorage("wizardactive", false);
- if (getStorage("kodiAddress") != null) {
- kodiAddress = getStorage("kodiAddress");
-
- if (getStorage("kodiPort") != null) {
- kodiPort = getStorage("kodiPort");
- }
- sendToKodi("stop");
- }
}
});
+$("#btn_wizard_colorcalibration").click(async function () {
+ const { colorCalibrationKodiWizard } = await import('./wizards/colorCalibrationKodiWizard.js');
+ colorCalibrationKodiWizard.start();
+});
+
+$('#btn_wizard_byteorder').on('click', async () => {
+ const { rgbByteOrderWizard } = await import('./wizards/rgbByteOrderWizard.js');
+ rgbByteOrderWizard.start();
+});
+
function resetWizard(reload) {
$("#wizard_modal").modal('hide');
- clearInterval(wIntveralId);
requestPriorityClear();
setStorage("wizardactive", false);
$('#wizp1').toggle(true);
$('#wizp2').toggle(false);
$('#wizp3').toggle(false);
- //cc
- if (withKodi)
- sendToKodi("stop");
- step = 0;
- if (!reload) location.reload();
-}
-
-//rgb byte order wizard
-var wIntveralId;
-var new_rgb_order;
-
-function changeColor() {
- var color = $("#wiz_canv_color").css('background-color');
-
- if (color == 'rgb(255, 0, 0)') {
- $("#wiz_canv_color").css('background-color', 'rgb(0, 255, 0)');
- requestSetColor('0', '255', '0');
- }
- else {
- $("#wiz_canv_color").css('background-color', 'rgb(255, 0, 0)');
- requestSetColor('255', '0', '0');
+ if (!reload) {
+ location.reload();
}
}
-function startWizardRGB() {
- //create html
- $('#wiz_header').html('' + $.i18n('wiz_rgb_title'));
- $('#wizp1_body').html('
' + $.i18n('wiz_rgb_title') + '
' + $.i18n('wiz_rgb_intro1') + '
' + $.i18n('wiz_rgb_intro2') + '
');
- $('#wizp1_footer').html('');
- $('#wizp2_body').html('' + $.i18n('wiz_rgb_expl') + '
');
- $('#wizp2_body').append('');
- $('#wizp2_body').append('');
- $('#wizp2_body').append(' | |
| |
');
- $('#wizp2_footer').html('');
+function createLedDeviceWizards(ledType) {
- if (getStorage("darkMode") == "on")
- $('#wizard_logo').attr("src", 'img/hyperion/logo_negativ.png');
+ let data = {};
+ let title;
- //open modal
- $("#wizard_modal").modal({
- backdrop: "static",
- keyboard: false,
- show: true
- });
+ $('#btn_wiz_holder').html("");
+ $('#btn_led_device_wiz').off();
+ if (ledType == "philipshue") {
+ $('#btn_wiz_holder').show();
+ data = { ledType };
+ title = 'wiz_hue_title';
+ }
+ else if (ledType == "nanoleaf") {
+ $('#btn_wiz_holder').hide();
+ data = { ledType };
+ title = 'wiz_nanoleaf_user_auth_title';
+ }
+ else if (ledType == "atmoorb") {
+ $('#btn_wiz_holder').show();
+ data = { ledType };
+ title = 'wiz_atmoorb_title';
+ }
+ else if (ledType == "yeelight") {
+ $('#btn_wiz_holder').show();
+ data = { ledType };
+ title = 'wiz_yeelight_title';
+ }
- //listen for continue
- $('#btn_wiz_cont').off().on('click', function () {
- beginWizardRGB();
- $('#wizp1').toggle(false);
- $('#wizp2').toggle(true);
+ if (Object.keys(data).length !== 0) {
+ startLedDeviceWizard(data, title, ledType + "Wizard");
+ }
+}
+
+function startLedDeviceWizard(data, hint, wizardName) {
+ $('#btn_wiz_holder').html("")
+ createHint("wizard", $.i18n(hint), "btn_wiz_holder", "btn_led_device_wiz");
+ $('#btn_led_device_wiz').off();
+ $('#btn_led_device_wiz').on('click', async (e) => {
+ const { [wizardName]: winzardObject } = await import('./wizards/LedDevice_' + data.ledType + '.js');
+ winzardObject.start(e);
});
}
-function beginWizardRGB() {
- $("#wiz_switchtime_select").off().on('change', function () {
- clearInterval(wIntveralId);
- var time = $("#wiz_switchtime_select").val();
- wIntveralId = setInterval(function () { changeColor(); }, time * 1000);
- });
-
- $('.wselect').on("change", function () {
- var rgb_order = window.serverConfig.device.colorOrder.split("");
- var redS = $("#wiz_r_select").val();
- var greenS = $("#wiz_g_select").val();
- var blueS = rgb_order.toString().replace(/,/g, "").replace(redS, "").replace(greenS, "");
-
- for (var i = 0; i < rgb_order.length; i++) {
- if (redS == rgb_order[i])
- $('#wiz_g_select option[value=' + rgb_order[i] + ']').prop('disabled', true);
- else
- $('#wiz_g_select option[value=' + rgb_order[i] + ']').prop('disabled', false);
- if (greenS == rgb_order[i])
- $('#wiz_r_select option[value=' + rgb_order[i] + ']').prop('disabled', true);
- else
- $('#wiz_r_select option[value=' + rgb_order[i] + ']').prop('disabled', false);
- }
-
- if (redS != 'null' && greenS != 'null') {
- $('#btn_wiz_save').prop('disabled', false);
-
- for (var i = 0; i < rgb_order.length; i++) {
- if (rgb_order[i] == "r")
- rgb_order[i] = redS;
- else if (rgb_order[i] == "g")
- rgb_order[i] = greenS;
- else
- rgb_order[i] = blueS;
- }
-
- rgb_order = rgb_order.toString().replace(/,/g, "");
-
- if (redS == "r" && greenS == "g") {
- $('#btn_wiz_save').toggle(false);
- $('#btn_wiz_checkok').toggle(true);
-
- window.readOnlyMode ? $('#btn_wiz_checkok').prop('disabled', true) : $('#btn_wiz_checkok').prop('disabled', false);
- }
- else {
- $('#btn_wiz_save').toggle(true);
- window.readOnlyMode ? $('#btn_wiz_save').prop('disabled', true) : $('#btn_wiz_save').prop('disabled', false);
-
- $('#btn_wiz_checkok').toggle(false);
- }
- new_rgb_order = rgb_order;
- }
- else
- $('#btn_wiz_save').prop('disabled', true);
- });
-
- $("#wiz_switchtime_select").append(createSelOpt('5', '5'), createSelOpt('10', '10'), createSelOpt('15', '15'), createSelOpt('30', '30'));
- $("#wiz_switchtime_select").trigger('change');
-
- $("#wiz_r_select").append(createSelOpt("null", ""), createSelOpt('r', $.i18n('general_col_red')), createSelOpt('g', $.i18n('general_col_green')), createSelOpt('b', $.i18n('general_col_blue')));
- $("#wiz_g_select").html($("#wiz_r_select").html());
- $("#wiz_r_select").trigger('change');
-
- requestSetColor('255', '0', '0');
- setTimeout(requestSetSource, 100, 'auto');
- setStorage("wizardactive", true);
-
- $('#btn_wiz_abort').off().on('click', function () { resetWizard(true); });
-
- $('#btn_wiz_checkok').off().on('click', function () {
- showInfoDialog('success', "", $.i18n('infoDialog_wizrgb_text'));
- resetWizard();
- });
-
- $('#btn_wiz_save').off().on('click', function () {
- resetWizard();
- window.serverConfig.device.colorOrder = new_rgb_order;
- requestWriteConfig({ "device": window.serverConfig.device });
- });
-}
-
-$('#btn_wizard_byteorder').off().on('click', startWizardRGB);
-
-//color calibration wizard
-
-const defaultKodiPort = 9090;
-
-var kodiAddress = document.location.hostname;
-var kodiPort = defaultKodiPort;
-
-var kodiUrl = new URL("ws://" + kodiAddress);
-kodiUrl.port = kodiPort;
-kodiUrl.pathname = "/jsonrpc/websocket";
-
-var wiz_editor;
-var colorLength;
-var cobj;
-var step = 0;
-var withKodi = false;
-var profile = 0;
-var websAddress;
-var imgAddress;
-var vidAddress = "https://sourceforge.net/projects/hyperion-project/files/resources/vid/";
-var picnr = 0;
-var availVideos = ["Sweet_Cocoon", "Caminandes_2_GranDillama", "Caminandes_3_Llamigos"];
-
-if (getStorage("kodiAddress") != null) {
-
- kodiAddress = getStorage("kodiAddress");
- kodiUrl.host = kodiAddress;
-}
-
-if (getStorage("kodiPort") != null) {
- kodiPort = getStorage("kodiPort");
- kodiUrl.port = kodiPort;
-}
-
-function switchPicture(pictures) {
- if (typeof pictures[picnr] === 'undefined')
- picnr = 0;
-
- sendToKodi('playP', pictures[picnr]);
- picnr++;
-}
-
-function sendToKodi(type, content, cb) {
- var command;
-
- switch (type) {
- case "msg":
- command = { "jsonrpc": "2.0", "method": "GUI.ShowNotification", "params": { "title": $.i18n('wiz_cc_title'), "message": content, "image": "info", "displaytime": 5000 }, "id": "1" };
- break;
- case "stop":
- command = { "jsonrpc": "2.0", "method": "Player.Stop", "params": { "playerid": 2 }, "id": "1" };
- break;
- case "playP":
- content = imgAddress + content + '.png';
- command = { "jsonrpc": "2.0", "method": "Player.Open", "params": { "item": { "file": content } }, "id": "1" };
- break;
- case "playV":
- content = vidAddress + content;
- command = { "jsonrpc": "2.0", "method": "Player.Open", "params": { "item": { "file": content } }, "id": "1" };
- break;
- case "rotate":
- command = { "jsonrpc": "2.0", "method": "Player.Rotate", "params": { "playerid": 2 }, "id": "1" };
- break;
- default:
- if (cb != undefined) {
- cb("error");
- }
- }
-
- if ("WebSocket" in window) {
-
- if (kodiUrl.port === '') {
- kodiUrl.port = defaultKodiPort;
- }
- var ws = new WebSocket(kodiUrl);
-
- ws.onopen = function () {
- ws.send(JSON.stringify(command));
- };
-
- ws.onmessage = function (evt) {
- var response = JSON.parse(evt.data);
- if (response.method === "System.OnQuit") {
- ws.close();
- } else {
- if (cb != undefined) {
- if (response.result != undefined) {
- if (response.result === "OK") {
- cb("success");
- ws.close();
- } else {
- cb("error");
- ws.close();
- }
- }
- }
- }
- };
-
- ws.onerror = function (evt) {
- if (cb != undefined) {
- cb("error");
- ws.close();
- }
- };
-
- ws.onclose = function (evt) {
- };
-
- }
- else {
- console.log("Kodi Access: WebSocket NOT supported by this browser");
- cb("error");
- }
-}
-
-function performAction() {
- var h;
-
- if (step == 1) {
- $('#wiz_cc_desc').html($.i18n('wiz_cc_chooseid'));
- updateWEditor(["id"]);
- $('#btn_wiz_back').prop("disabled", true);
- }
- else
- $('#btn_wiz_back').prop("disabled", false);
-
- if (step == 2) {
- updateWEditor(["white"]);
- h = $.i18n('wiz_cc_adjustit', $.i18n('edt_conf_color_white_title'));
- if (withKodi) {
- h += '
' + $.i18n('wiz_cc_kodishould', $.i18n('edt_conf_color_white_title'));
- sendToKodi('playP', "white");
- }
- else
- h += '
' + $.i18n('wiz_cc_lettvshow', $.i18n('edt_conf_color_white_title'));
- $('#wiz_cc_desc').html(h);
- }
- if (step == 3) {
- updateWEditor(["gammaRed", "gammaGreen", "gammaBlue"]);
- h = '' + $.i18n('wiz_cc_adjustgamma') + '
';
- if (withKodi) {
- sendToKodi('playP', "HGradient");
- h += '';
- }
- else
- h += '' + $.i18n('wiz_cc_lettvshowm', "grey_1, grey_2, grey_3, HGradient, VGradient") + '
';
- $('#wiz_cc_desc').html(h);
- $('#wiz_cc_btn_sp').off().on('click', function () {
- switchPicture(["VGradient", "grey_1", "grey_2", "grey_3", "HGradient"]);
- });
- }
- if (step == 4) {
- updateWEditor(["red"]);
- h = $.i18n('wiz_cc_adjustit', $.i18n('edt_conf_color_red_title'));
- if (withKodi) {
- h += '
' + $.i18n('wiz_cc_kodishould', $.i18n('edt_conf_color_red_title'));
- sendToKodi('playP', "red");
- }
- else
- h += '
' + $.i18n('wiz_cc_lettvshow', $.i18n('edt_conf_color_red_title'));
- $('#wiz_cc_desc').html(h);
- }
- if (step == 5) {
- updateWEditor(["green"]);
- h = $.i18n('wiz_cc_adjustit', $.i18n('edt_conf_color_green_title'));
- if (withKodi) {
- h += '
' + $.i18n('wiz_cc_kodishould', $.i18n('edt_conf_color_green_title'));
- sendToKodi('playP', "green");
- }
- else
- h += '
' + $.i18n('wiz_cc_lettvshow', $.i18n('edt_conf_color_green_title'));
- $('#wiz_cc_desc').html(h);
- }
- if (step == 6) {
- updateWEditor(["blue"]);
- h = $.i18n('wiz_cc_adjustit', $.i18n('edt_conf_color_blue_title'));
- if (withKodi) {
- h += '
' + $.i18n('wiz_cc_kodishould', $.i18n('edt_conf_color_blue_title'));
- sendToKodi('playP', "blue");
- }
- else
- h += '
' + $.i18n('wiz_cc_lettvshow', $.i18n('edt_conf_color_blue_title'));
- $('#wiz_cc_desc').html(h);
- }
- if (step == 7) {
- updateWEditor(["cyan"]);
- h = $.i18n('wiz_cc_adjustit', $.i18n('edt_conf_color_cyan_title'));
- if (withKodi) {
- h += '
' + $.i18n('wiz_cc_kodishould', $.i18n('edt_conf_color_cyan_title'));
- sendToKodi('playP', "cyan");
- }
- else
- h += '
' + $.i18n('wiz_cc_lettvshow', $.i18n('edt_conf_color_cyan_title'));
- $('#wiz_cc_desc').html(h);
- }
- if (step == 8) {
- updateWEditor(["magenta"]);
- h = $.i18n('wiz_cc_adjustit', $.i18n('edt_conf_color_magenta_title'));
- if (withKodi) {
- h += '
' + $.i18n('wiz_cc_kodishould', $.i18n('edt_conf_color_magenta_title'));
- sendToKodi('playP', "magenta");
- }
- else
- h += '
' + $.i18n('wiz_cc_lettvshow', $.i18n('edt_conf_color_magenta_title'));
- $('#wiz_cc_desc').html(h);
- }
- if (step == 9) {
- updateWEditor(["yellow"]);
- h = $.i18n('wiz_cc_adjustit', $.i18n('edt_conf_color_yellow_title'));
- if (withKodi) {
- h += '
' + $.i18n('wiz_cc_kodishould', $.i18n('edt_conf_color_yellow_title'));
- sendToKodi('playP', "yellow");
- }
- else
- h += '
' + $.i18n('wiz_cc_lettvshow', $.i18n('edt_conf_color_yellow_title'));
- $('#wiz_cc_desc').html(h);
- }
- if (step == 10) {
- updateWEditor(["backlightThreshold", "backlightColored"]);
- h = $.i18n('wiz_cc_backlight');
- if (withKodi) {
- h += '
' + $.i18n('wiz_cc_kodishould', $.i18n('edt_conf_color_black_title'));
- sendToKodi('playP', "black");
- }
- else
- h += '
' + $.i18n('wiz_cc_lettvshow', $.i18n('edt_conf_color_black_title'));
- $('#wiz_cc_desc').html(h);
- }
- if (step == 11) {
- updateWEditor([""], true);
- h = '' + $.i18n('wiz_cc_testintro') + '
';
- if (withKodi) {
- h += '' + $.i18n('wiz_cc_testintrok') + '
';
- sendToKodi('stop');
- for (var i = 0; i < availVideos.length; i++) {
- var txt = availVideos[i].replace(/_/g, " ");
- h += '';
- }
- h += '';
- }
- else
- h += '' + $.i18n('wiz_cc_testintrowok') + ' ' + $.i18n('wiz_cc_link') + '
';
- h += '' + $.i18n('wiz_cc_summary') + '
';
- $('#wiz_cc_desc').html(h);
-
- $('.videobtn').off().on('click', function (e) {
- if (e.target.id == "stop")
- sendToKodi("stop");
- else
- sendToKodi("playV", e.target.id + '.mp4');
-
- $(this).prop("disabled", true);
- setTimeout(function () { $('.videobtn').prop("disabled", false) }, 10000);
- });
-
- $('#btn_wiz_next').prop("disabled", true);
- $('#btn_wiz_save').toggle(true);
- window.readOnlyMode ? $('#btn_wiz_save').prop('disabled', true) : $('#btn_wiz_save').prop('disabled', false);
- }
- else {
- $('#btn_wiz_next').prop("disabled", false);
- $('#btn_wiz_save').toggle(false);
- }
-}
-
-function updateWEditor(el, all) {
- for (var key in cobj) {
- if (all === true || el[0] == key || el[1] == key || el[2] == key)
- $('#editor_container_wiz [data-schemapath*=".' + profile + '.' + key + '"]').toggle(true);
- else
- $('#editor_container_wiz [data-schemapath*=".' + profile + '.' + key + '"]').toggle(false);
- }
-}
-
-function startWizardCC() {
-
- //create html
- $('#wiz_header').html('' + $.i18n('wiz_cc_title'));
- $('#wizp1_body').html('' + $.i18n('wiz_cc_title') + '
' +
- '' + $.i18n('wiz_cc_intro1') + '
' +
- '' +
- '' +
- ''
- );
- $('#wizp1_footer').html('' +
- ''
- );
- $('#wizp2_body').html(''
- );
- $('#wizp2_footer').html('' +
- '' +
- '' +
- ''
- );
-
- if (getStorage("darkMode") == "on")
- $('#wizard_logo').prop("src", 'img/hyperion/logo_negativ.png');
-
- //open modal
- $("#wizard_modal").modal({
- backdrop: "static",
- keyboard: false,
- show: true
- });
-
- $('#wiz_cc_kodiip').off().on('change', function () {
-
- kodiAddress = encodeURIComponent($(this).val().trim());
-
- $('#kodi_status').html('');
- if (kodiAddress !== "") {
-
- if (!isValidHostnameOrIP(kodiAddress)) {
-
- $('#kodi_status').html('' + $.i18n('edt_msgcust_error_hostname_ip') + '
');
- withKodi = false;
-
- } else {
-
- if (isValidIPv6(kodiAddress)) {
- kodiUrl.hostname = "[" + kodiAddress + "]";
- } else {
- kodiUrl.hostname = kodiAddress;
- }
-
- $('#kodi_status').html('' + $.i18n('wiz_cc_try_connect') + '
');
- $('#btn_wiz_cont').prop('disabled', true);
-
- sendToKodi("msg", $.i18n('wiz_cc_kodimsg_start'), function (cb) {
- if (cb == "error") {
- $('#kodi_status').html('' + $.i18n('wiz_cc_kodidiscon') + '
' + $.i18n('wiz_cc_kodidisconlink') + ' ' + $.i18n('wiz_cc_link') + '
');
- withKodi = false;
- }
- else {
- setStorage("kodiAddress", kodiAddress);
- setStorage("kodiPort", defaultKodiPort);
-
- $('#kodi_status').html('' + $.i18n('wiz_cc_kodicon') + '
');
- withKodi = true;
- }
-
- $('#btn_wiz_cont').prop('disabled', false);
- });
- }
- }
- });
-
- //listen for continue
- $('#btn_wiz_cont').off().on('click', function () {
- beginWizardCC();
- $('#wizp1').toggle(false);
- $('#wizp2').toggle(true);
- });
-
- $('#wiz_cc_kodiip').trigger("change");
- colorLength = window.serverConfig.color.channelAdjustment;
- cobj = window.schema.color.properties.channelAdjustment.items.properties;
- websAddress = document.location.hostname + ':' + window.serverConfig.webConfig.port;
- imgAddress = 'http://' + websAddress + '/img/cc/';
- setStorage("wizardactive", true);
-
- //check profile count
- if (colorLength.length > 1) {
- $('#multi_cali').html('' + $.i18n('wiz_cc_morethanone') + '
');
- for (var i = 0; i < colorLength.length; i++)
- $('#wiz_select').append(createSelOpt(i, i + 1 + ' (' + colorLength[i].id + ')'));
-
- $('#wiz_select').off().on('change', function () {
- profile = $(this).val();
- });
- }
-
- //prepare editor
- wiz_editor = createJsonEditor('editor_container_wiz', {
- color: window.schema.color
- }, true, true);
-
- $('#editor_container_wiz h4').toggle(false);
- $('#editor_container_wiz .btn-group').toggle(false);
- $('#editor_container_wiz [data-schemapath="root.color.imageToLedMappingType"]').toggle(false);
- for (var i = 0; i < colorLength.length; i++)
- $('#editor_container_wiz [data-schemapath*="root.color.channelAdjustment.' + i + '."]').toggle(false);
-}
-
-function beginWizardCC() {
- $('#btn_wiz_next').off().on('click', function () {
- step++;
- performAction();
- });
-
- $('#btn_wiz_back').off().on('click', function () {
- step--;
- performAction();
- });
-
- $('#btn_wiz_abort').off().on('click', resetWizard);
-
- $('#btn_wiz_save').off().on('click', function () {
- requestWriteConfig(wiz_editor.getValue());
- resetWizard();
- });
-
- wiz_editor.on("change", function (e) {
- var val = wiz_editor.getEditor('root.color.channelAdjustment.' + profile + '').getValue();
- var temp = JSON.parse(JSON.stringify(val));
- delete temp.leds
- requestAdjustment(JSON.stringify(temp), "", true);
- });
-
- step++
- performAction();
-}
-
-$('#btn_wizard_colorcalibration').off().on('click', startWizardCC);
-
-// Layout positions
-var lightPosTop = { hmin: 0.15, hmax: 0.85, vmin: 0, vmax: 0.2 };
-var lightPosTopLeft = { hmin: 0, hmax: 0.15, vmin: 0, vmax: 0.15 };
-var lightPosTopRight = { hmin: 0.85, hmax: 1.0, vmin: 0, vmax: 0.15 };
-var lightPosBottom = { hmin: 0.15, hmax: 0.85, vmin: 0.8, vmax: 1.0 };
-var lightPosBottomLeft = { hmin: 0, hmax: 0.15, vmin: 0.85, vmax: 1.0 };
-var lightPosBottomRight = { hmin: 0.85, hmax: 1.0, vmin: 0.85, vmax: 1.0 };
-var lightPosLeft = { hmin: 0, hmax: 0.15, vmin: 0.15, vmax: 0.85 };
-var lightPosLeftTop = { hmin: 0, hmax: 0.15, vmin: 0, vmax: 0.5 };
-var lightPosLeftMiddle = { hmin: 0, hmax: 0.15, vmin: 0.25, vmax: 0.75 };
-var lightPosLeftBottom = { hmin: 0, hmax: 0.15, vmin: 0.5, vmax: 1.0 };
-var lightPosRight = { hmin: 0.85, hmax: 1.0, vmin: 0.15, vmax: 0.85 };
-var lightPosRightTop = { hmin: 0.85, hmax: 1.0, vmin: 0, vmax: 0.5 };
-var lightPosRightMiddle = { hmin: 0.85, hmax: 1.0, vmin: 0.25, vmax: 0.75 };
-var lightPosRightBottom = { hmin: 0.85, hmax: 1.0, vmin: 0.5, vmax: 1.0 };
-var lightPosEntire = { hmin: 0.0, hmax: 1.0, vmin: 0.0, vmax: 1.0 };
-
-var lightPosBottomLeft14 = { hmin: 0, hmax: 0.25, vmin: 0.85, vmax: 1.0 };
-var lightPosBottomLeft12 = { hmin: 0.25, hmax: 0.5, vmin: 0.85, vmax: 1.0 };
-var lightPosBottomLeft34 = { hmin: 0.5, hmax: 0.75, vmin: 0.85, vmax: 1.0 };
-var lightPosBottomLeft11 = { hmin: 0.75, hmax: 1, vmin: 0.85, vmax: 1.0 };
-
-var lightPosBottomLeft112 = { hmin: 0, hmax: 0.5, vmin: 0.85, vmax: 1.0 };
-var lightPosBottomLeft121 = { hmin: 0.5, hmax: 1, vmin: 0.85, vmax: 1.0 };
-var lightPosBottomLeftNewMid = { hmin: 0.25, hmax: 0.75, vmin: 0.85, vmax: 1.0 };
-
-var lightPosTopLeft112 = { hmin: 0, hmax: 0.5, vmin: 0, vmax: 0.15 };
-var lightPosTopLeft121 = { hmin: 0.5, hmax: 1, vmin: 0, vmax: 0.15 };
-var lightPosTopLeftNewMid = { hmin: 0.25, hmax: 0.75, vmin: 0, vmax: 0.15 };
-
-function assignLightPos(pos, name) {
- var i = null;
-
- if (pos === "top")
- i = lightPosTop;
- else if (pos === "topleft")
- i = lightPosTopLeft;
- else if (pos === "topright")
- i = lightPosTopRight;
- else if (pos === "bottom")
- i = lightPosBottom;
- else if (pos === "bottomleft")
- i = lightPosBottomLeft;
- else if (pos === "bottomright")
- i = lightPosBottomRight;
- else if (pos === "left")
- i = lightPosLeft;
- else if (pos === "lefttop")
- i = lightPosLeftTop;
- else if (pos === "leftmiddle")
- i = lightPosLeftMiddle;
- else if (pos === "leftbottom")
- i = lightPosLeftBottom;
- else if (pos === "right")
- i = lightPosRight;
- else if (pos === "righttop")
- i = lightPosRightTop;
- else if (pos === "rightmiddle")
- i = lightPosRightMiddle;
- else if (pos === "rightbottom")
- i = lightPosRightBottom;
- else if (pos === "lightPosBottomLeft14")
- i = lightPosBottomLeft14;
- else if (pos === "lightPosBottomLeft12")
- i = lightPosBottomLeft12;
- else if (pos === "lightPosBottomLeft34")
- i = lightPosBottomLeft34;
- else if (pos === "lightPosBottomLeft11")
- i = lightPosBottomLeft11;
- else if (pos === "lightPosBottomLeft112")
- i = lightPosBottomLeft112;
- else if (pos === "lightPosBottomLeft121")
- i = lightPosBottomLeft121;
- else if (pos === "lightPosBottomLeftNewMid")
- i = lightPosBottomLeftNewMid;
- else if (pos === "lightPosTopLeft112")
- i = lightPosTopLeft112;
- else if (pos === "lightPosTopLeft121")
- i = lightPosTopLeft121;
- else if (pos === "lightPosTopLeftNewMid")
- i = lightPosTopLeftNewMid;
- else
- i = lightPosEntire;
-
- i.name = name;
- return i;
-}
-
-function getHostInLights(hostname) {
- return lights.filter(
- function (lights) {
- return lights.host === hostname
- }
- );
-}
-
-function getIpInLights(ip) {
- return lights.filter(
- function (lights) {
- return lights.ip === ip
- }
- );
-}
-
-function getIdInLights(id) {
- return lights.filter(
- function (lights) {
- return lights.id === id
- }
- );
-}
-
-// External properties properties, 2-dimensional arry of [ledType][key]
-devicesProperties = {};
-
-//****************************
-// Wizard Philips Hue
-//****************************
-
-var hueIPs = [];
-var hueIPsinc = 0;
-var hueLights = [];
-var hueEntertainmentConfigs = [];
-var hueEntertainmentServices = [];
-var lightLocation = [];
-var groupLights = [];
-var groupChannels = [];
-var groupLightsLocations = [];
-var isAPIv2Ready = true;
-var isEntertainmentReady = true;
-
-function startWizardPhilipsHue(e) {
- //create html
-
- var hue_title = 'wiz_hue_title';
- var hue_intro1 = 'wiz_hue_e_intro1';
- var hue_desc1 = 'wiz_hue_desc1';
- var hue_create_user = 'wiz_hue_create_user';
-
- $('#wiz_header').html('' + $.i18n(hue_title));
- $('#wizp1_body').html('' + $.i18n(hue_title) + '
' + $.i18n(hue_intro1) + '
');
- $('#wizp1_footer').html('');
- $('#wizp2_body').html('');
-
- var topContainer_html = '' + $.i18n(hue_desc1) + '
' +
- '' +
- '
' +
- '
' + $.i18n('wiz_hue_ip') + '
' +
- '
' +
- ' ' +
- ' ' + '
' +
- '
';
-
- if (storedAccess === 'expert') {
- topContainer_html += '
';
- }
-
- topContainer_html += '
';
- topContainer_html += '';
-
- $('#wh_topcontainer').append(topContainer_html);
-
- $('#usrcont').append('' + $.i18n('wiz_hue_username') + '
' +
- '
' +
- ''
- );
-
- $('#usrcont').append('
');
-
- $('#usrcont').append('<\p>' +
- '');
-
- $('#wizp2_body').append('
' + $.i18n('wiz_hue_e_desc2') + '
');
- createTable("gidsh", "gidsb", "hue_grp_ids_t");
- $('.gidsh').append(createTableRow([$.i18n('edt_dev_spec_groupId_title'), ""], true));
-
- $('#wizp2_body').append('' + $.i18n('wiz_hue_e_desc3') + '
');
-
- createTable("lidsh", "lidsb", "hue_ids_t");
- $('.lidsh').append(createTableRow([$.i18n('edt_dev_spec_lightid_title'), $.i18n('wiz_pos'), $.i18n('wiz_identify')], true));
- $('#wizp2_footer').html('');
- $('#wizp3_body').html('' + $.i18n('wiz_hue_press_link') + '
');
-
- if (getStorage("darkMode") == "on")
- $('#wizard_logo').attr("src", 'img/hyperion/logo_negativ.png');
-
- //open modal
- $("#wizard_modal").modal({
- backdrop: "static",
- keyboard: false,
- show: true
- });
-
- //listen for continue
- $('#btn_wiz_cont').off().on('click', function () {
- beginWizardHue();
- $('#wizp1').toggle(false);
- $('#wizp2').toggle(true);
- });
-}
-
-function checkHueBridge(cb, hueUser) {
- var usr = (typeof hueUser != "undefined") ? hueUser : 'config';
- if (usr === 'config') {
- $('#wiz_hue_discovered').html("");
- }
-
- if (hueIPs[hueIPsinc]) {
- var host = hueIPs[hueIPsinc].host;
- var port = hueIPs[hueIPsinc].port;
-
- if (usr != '')
- {
- getProperties_hue_bridge(cb, decodeURIComponent(host), port, usr);
- }
- else
- {
- cb(false, usr);
- }
-
- if (isAPIv2Ready) {
- $('#port').val(443);
- }
- }
-}
-
-function checkBridgeResult(reply, usr) {
- if (reply) {
- //abort checking, first reachable result is used
- $('#wiz_hue_ipstate').html("");
- $('#host').val(hueIPs[hueIPsinc].host)
- $('#port').val(hueIPs[hueIPsinc].port)
-
- $('#usrcont').toggle(true);
-
- checkHueBridge(checkUserResult, $('#user').val());
- }
- else {
- $('#usrcont').toggle(false);
- $('#wiz_hue_ipstate').html($.i18n('wiz_hue_failure_ip'));
- }
-};
-
-function checkUserResult(reply, username) {
- $('#usrcont').toggle(true);
-
- var hue_create_user = 'wiz_hue_e_create_user';
- if (!isEntertainmentReady) {
- hue_create_user = 'wiz_hue_create_user';
- $('#hue_client_key_r').toggle(false);
- } else {
- $('#hue_client_key_r').toggle(true);
- }
-
- $('#wiz_hue_create_user').text($.i18n(hue_create_user));
- $('#wiz_hue_create_user').toggle(true);
-
- if (reply) {
- $('#user').val(username);
-
- if (isEntertainmentReady && $('#clientkey').val() == "") {
- $('#wiz_hue_usrstate').html($.i18n('wiz_hue_e_clientkey_needed'));
- $('#wiz_hue_create_user').toggle(true);
- } else {
- $('#wiz_hue_usrstate').html("");
- $('#wiz_hue_create_user').toggle(false);
-
- if (isEntertainmentReady) {
- $('#hue_id_headline').text($.i18n('wiz_hue_e_desc3'));
- $('#hue_grp_ids_t').toggle(true);
-
- get_hue_groups(username);
-
- } else {
- $('#hue_id_headline').text($.i18n('wiz_hue_desc2'));
- $('#hue_grp_ids_t').toggle(false);
-
- get_hue_lights(username);
-
- }
- }
- }
- else {
- //abort checking, first reachable result is used
- $('#wiz_hue_usrstate').html($.i18n('wiz_hue_failure_user'));
- $('#wiz_hue_create_user').toggle(true);
- }
-};
-
-function useGroupId(id, username) {
- $('#groupId').val(hueEntertainmentConfigs[id].id);
- if (isAPIv2Ready) {
- var group = hueEntertainmentConfigs[id];
-
- groupLights = [];
- for (const light of group.light_services) {
- groupLights.push(light.rid);
- }
-
- groupChannels = [];
- for (const channel of group.channels) {
- groupChannels.push(channel);
- }
-
- groupLightsLocations = [];
- for (const location of group.locations.service_locations) {
- groupLightsLocations.push(location);
- }
- } else {
- //Ensure ligthIDs are strings
- groupLights = hueEntertainmentConfigs[id].lights.map(num => {
- return String(num);
- });
-
- var lightLocations = hueEntertainmentConfigs[id].locations;
- for (var locationID in lightLocations) {
- var lightLocation = {};
-
- let position = {
- x: lightLocations[locationID][0],
- y: lightLocations[locationID][1],
- z: lightLocations[locationID][2]
- };
- lightLocation.position = position;
-
- groupLightsLocations.push(lightLocation);
- }
- }
-
- get_hue_lights(username);
-}
-
-function updateBridgeDetails(properties) {
- var ledDeviceProperties = properties.config;
-
- if (!jQuery.isEmptyObject(ledDeviceProperties)) {
- isEntertainmentReady = properties.isEntertainmentReady;
- isAPIv2Ready = properties.isAPIv2Ready;
-
- if (ledDeviceProperties.name && ledDeviceProperties.bridgeid && ledDeviceProperties.modelid) {
- $('#wiz_hue_discovered').html(
- "Bridge: " + ledDeviceProperties.name +
- ", Modelid: " + ledDeviceProperties.modelid +
- ", Firmware: " + ledDeviceProperties.swversion + "
" +
- "API-Version: " + ledDeviceProperties.apiversion +
- ", Entertainment: " + (isEntertainmentReady ? "✓" : "-") +
- ", APIv2: " + (isAPIv2Ready ? "✓" : "-")
- );
- }
- }
-}
-
-async function discover_hue_bridges() {
- $('#wiz_hue_ipstate').html($.i18n('edt_dev_spec_devices_discovery_inprogress'));
-
- // $('#wiz_hue_discovered').html("")
- const res = await requestLedDeviceDiscovery('philipshue');
- if (res && !res.error) {
- const r = res.info;
-
- // Process devices returned by discovery
- if (r.devices.length == 0) {
- $('#wiz_hue_ipstate').html($.i18n('wiz_hue_failure_ip'));
- $('#wiz_hue_discovered').html("")
- }
- else {
- hueIPs = [];
- hueIPsinc = 0;
-
- var discoveryMethod = "ssdp";
- if (res.info.discoveryMethod) {
- discoveryMethod = res.info.discoveryMethod;
- }
-
- for (const device of r.devices) {
- if (device) {
- var host;
- var port;
- if (discoveryMethod === "ssdp") {
- if (device.hostname && device.domain) {
- host = device.hostname + "." + device.domain;
- port = device.port;
- } else {
- host = device.ip;
- port = device.port;
- }
- } else {
- host = device.service;
- port = device.port;
- }
-
- if (host) {
-
- if (!hueIPs.some(item => item.host === host)) {
- hueIPs.push({ host: host, port: port });
- }
- }
- }
- }
-
- $('#wiz_hue_ipstate').html("");
- $('#host').val(hueIPs[hueIPsinc].host)
- $('#port').val(hueIPs[hueIPsinc].port)
-
- $('#hue_bridge_select').html("");
-
- for (var key in hueIPs) {
- $('#hue_bridge_select').append(createSelOpt(key, hueIPs[key].host));
- }
-
- $('.hue_bridge_sel_watch').on("click", function () {
- hueIPsinc = $(this).val();
-
- var name = $("#hue_bridge_select option:selected").text();
- $('#host').val(name);
- $('#port').val(hueIPs[hueIPsinc].port)
-
- var usr = $('#user').val();
- if (usr != "") {
- checkHueBridge(checkUserResult, usr);
- } else {
- checkHueBridge(checkBridgeResult);
- }
- });
-
- $('.hue_bridge_sel_watch').click();
- }
- }
-}
-
-async function getProperties_hue_bridge(cb, hostAddress, port, username, resourceFilter) {
- let params = { host: hostAddress, username: username, filter: resourceFilter };
- if (port !== 'undefined') {
- params.port = parseInt(port);
- }
-
- var ledType = 'philipshue';
- var key = hostAddress;
-
- //Create ledType cache entry
- if (!devicesProperties[ledType]) {
- devicesProperties[ledType] = {};
- }
-
- // Use device's properties, if properties in chache
- if (devicesProperties[ledType][key] && devicesProperties[ledType][key][username]) {
- updateBridgeDetails(devicesProperties[ledType][key]);
- cb(true, username);
- } else {
- const res = await requestLedDeviceProperties(ledType, params);
- if (res && !res.error) {
- var ledDeviceProperties = res.info.properties;
- if (!jQuery.isEmptyObject(ledDeviceProperties)) {
-
- devicesProperties[ledType][key] = {};
- devicesProperties[ledType][key][username] = ledDeviceProperties;
-
- isAPIv2Ready = res.info.isAPIv2Ready;
- devicesProperties[ledType][key].isAPIv2Ready = isAPIv2Ready;
- isEntertainmentReady = res.info.isEntertainmentReady;
- devicesProperties[ledType][key].isEntertainmentReady = isEntertainmentReady;
-
- updateBridgeDetails(devicesProperties[ledType][key]);
- if (username === "config") {
- cb(true);
- } else {
- cb(true, username);
- }
- } else {
- cb(false, username);
- }
- } else {
- cb(false, username);
- }
- }
-}
-
-async function identify_hue_device(hostAddress, port, username, name, id, id_v1) {
- var disabled = $('#btn_wiz_save').is(':disabled');
- // Take care that new record cannot be save during background process
- $('#btn_wiz_save').prop('disabled', true);
-
- let params = { host: decodeURIComponent(hostAddress), username: username, lightName: decodeURIComponent(name), lightId: id, lightId_v1: id_v1 };
-
- if (port !== 'undefined') {
- params.port = parseInt(port);
- }
-
- await requestLedDeviceIdentification('philipshue', params);
-
- if (!window.readOnlyMode) {
- $('#btn_wiz_save').prop('disabled', disabled);
- }
-}
-
-//return editor Value
-function eV(vn, defaultVal = "") {
- var editor = (vn) ? conf_editor.getEditor("root.specificOptions." + vn) : null;
- return (editor == null) ? defaultVal : ((defaultVal != "" && !isNaN(defaultVal) && isNaN(editor.getValue())) ? defaultVal : editor.getValue());
-}
-
-function beginWizardHue() {
- var usr = eV("username");
- if (usr != "") {
- $('#user').val(usr);
- }
-
- var clkey = eV("clientkey");
- if (clkey != "") {
- $('#clientkey').val(clkey);
- }
-
- //check if host is empty/reachable/search for bridge
- if (eV("host") == "") {
- hueIPs = [];
- hueIPsinc = 0;
-
- discover_hue_bridges();
- }
- else {
- var host = eV("host");
- $('#host').val(host);
-
- var port = eV("port");
- if (port > 0) {
- $('#port').val(port);
- }
- else {
- $('#port').val('');
- }
- hueIPs.push({ host: host, port: port });
-
- if (usr != "") {
- checkHueBridge(checkUserResult, usr);
- } else {
- checkHueBridge(checkBridgeResult);
- }
- }
-
- $('#retry_bridge').off().on('click', function () {
- var host = $('#host').val();
- var port = parseInt($('#port').val());
-
- if (host != "") {
-
- var idx = hueIPs.findIndex(item => item.host === host && item.port === port);
- if (idx === -1) {
- hueIPs.push({ host: host, port: port });
- hueIPsinc = hueIPs.length - 1;
- } else {
- hueIPsinc = idx;
- }
- }
- else {
- discover_hue_bridges();
- }
-
- var usr = $('#user').val();
- if (usr != "") {
- checkHueBridge(checkUserResult, usr);
- } else {
- checkHueBridge(checkBridgeResult);
- }
- });
-
- $('#retry_usr').off().on('click', function () {
- checkHueBridge(checkUserResult, $('#user').val());
- });
-
- $('#wiz_hue_create_user').off().on('click', function () {
- createHueUser();
- });
-
- function assignLightEntertainmentPos(isFocusCenter, position, name, id) {
-
- var x = position.x;
- var z = position.z;
-
- if (isFocusCenter) {
- // Map lights as in centered range -0.5 to 0.5
- if (x < -0.5) {
- x = -0.5;
- } else if (x > 0.5) {
- x = 0.5;
- }
- if (z < -0.5) {
- z = -0.5;
- } else if (z > 0.5) {
- z = 0.5;
- }
- } else {
- // Map lights as in full range -1 to 1
- x /= 2;
- z /= 2;
- }
-
- var h = x + 0.5;
- var v = -z + 0.5;
-
- var hmin = h - 0.05;
- var hmax = h + 0.05;
- var vmin = v - 0.05;
- var vmax = v + 0.05;
-
- let layoutObject = {
- hmin: hmin < 0 ? 0 : hmin,
- hmax: hmax > 1 ? 1 : hmax,
- vmin: vmin < 0 ? 0 : vmin,
- vmax: vmax > 1 ? 1 : vmax,
- name: name
- };
-
- if (id) {
- layoutObject.name += "_" + id;
- }
- return layoutObject;
- }
-
- function assignSegmentedLightPos(segment, position, name) {
- var layoutObjects = [];
-
- var segTotalLength = 0;
- for (var key in segment) {
-
- segTotalLength += segment[key].length;
- }
-
- var min;
- var max;
- var horizontal = true;
-
- var layoutObject = assignLightPos(position, name);
- if (position === "left" || position === "right") {
- // vertical distribution
- min = layoutObject.vmin;
- max = layoutObject.vmax;
- horizontal = false;
-
- } else {
- // horizontal distribution
- min = layoutObject.hmin;
- max = layoutObject.hmax;
- }
-
- var step = (max - min) / segTotalLength;
- var start = min;
-
- for (var key in segment) {
- min = start;
- max = round(start + segment[key].length * step);
-
- if (horizontal) {
- layoutObject.hmin = min;
- layoutObject.hmax = max;
- } else {
- layoutObject.vmin = min;
- layoutObject.vmax = max;
- }
- layoutObject.name = name + "_" + key;
- layoutObjects.push(JSON.parse(JSON.stringify(layoutObject)));
-
- start = max;
- }
-
- return layoutObjects;
- }
-
- $('#btn_wiz_save').off().on("click", function () {
- var hueLedConfig = [];
- var finalLightIds = [];
- var channelNumber = 0;
-
- //create hue led config
- for (var key in groupLights) {
- var lightId = groupLights[key];
-
- if ($('#hue_' + lightId).val() != "disabled") {
- finalLightIds.push(lightId);
-
- var lightName;
- if (isAPIv2Ready) {
- var light = hueLights.find(light => light.id === lightId);
- lightName = light.metadata.name;
- } else {
- lightName = hueLights[lightId].name;
- }
-
- var position = $('#hue_' + lightId).val();
- var lightIdx = groupLights.indexOf(lightId);
- var lightLocation = groupLightsLocations[lightIdx];
-
- var serviceID;
- if (isAPIv2Ready) {
- serviceID = lightLocation.service.rid;
- }
-
- if (position.startsWith("entertainment")) {
-
- // Layout per entertainment area definition at bridge
- var isFocusCenter = false;
- if (position === "entertainment_center") {
- isFocusCenter = true;
- }
-
- if (isAPIv2Ready) {
-
- groupChannels.forEach((channel) => {
- if (channel.members[0].service.rid === serviceID) {
- var layoutObject = assignLightEntertainmentPos(isFocusCenter, channel.position, lightName, channel.channel_id);
- hueLedConfig.push(JSON.parse(JSON.stringify(layoutObject)));
- ++channelNumber;
- }
- });
- } else {
- var layoutObject = assignLightEntertainmentPos(isFocusCenter, lightLocation.position, lightName);
- hueLedConfig.push(JSON.parse(JSON.stringify(layoutObject)));
- }
- }
- else {
- // Layout per manual settings
- var maxSegments = 1;
-
- if (isAPIv2Ready) {
- var service = hueEntertainmentServices.find(service => service.id === serviceID);
- maxSegments = service.segments.max_segments;
- }
-
- if (maxSegments > 1) {
- var segment = service.segments.segments;
- var layoutObjects = assignSegmentedLightPos(segment, position, lightName);
- hueLedConfig.push(...layoutObjects);
- } else {
- var layoutObject = assignLightPos(position, lightName);
- hueLedConfig.push(JSON.parse(JSON.stringify(layoutObject)));
- }
- channelNumber += maxSegments;
- }
- }
- }
-
- var sc = window.serverConfig;
- sc.leds = hueLedConfig;
-
- //Adjust gamma, brightness and compensation
- var c = sc.color.channelAdjustment[0];
- c.gammaBlue = 1.0;
- c.gammaRed = 1.0;
- c.gammaGreen = 1.0;
- c.brightness = 100;
- c.brightnessCompensation = 0;
-
- //device config
-
- //Start with a clean configuration
- var d = {};
- d.host = $('#host').val();
- d.port = parseInt($('#port').val());
- d.username = $('#user').val();
- d.type = 'philipshue';
- d.colorOrder = 'rgb';
- d.lightIds = finalLightIds;
- d.transitiontime = parseInt(eV("transitiontime", 1));
- d.restoreOriginalState = (eV("restoreOriginalState", false) == true);
- d.switchOffOnBlack = (eV("switchOffOnBlack", false) == true);
-
- d.blackLevel = parseFloat(eV("blackLevel", 0.009));
- d.onBlackTimeToPowerOff = parseInt(eV("onBlackTimeToPowerOff", 600));
- d.onBlackTimeToPowerOn = parseInt(eV("onBlackTimeToPowerOn", 300));
- d.brightnessFactor = parseFloat(eV("brightnessFactor", 1));
-
- d.clientkey = $('#clientkey').val();
- d.groupId = $('#groupId').val();
- d.blackLightsTimeout = parseInt(eV("blackLightsTimeout", 5000));
- d.brightnessMin = parseFloat(eV("brightnessMin", 0));
- d.brightnessMax = parseFloat(eV("brightnessMax", 1));
- d.brightnessThreshold = parseFloat(eV("brightnessThreshold", 0.0001));
- d.handshakeTimeoutMin = parseInt(eV("handshakeTimeoutMin", 300));
- d.handshakeTimeoutMax = parseInt(eV("handshakeTimeoutMax", 1000));
- d.verbose = (eV("verbose") == true);
-
- d.autoStart = conf_editor.getEditor("root.generalOptions.autoStart").getValue();
- d.enableAttempts = parseInt(conf_editor.getEditor("root.generalOptions.enableAttempts").getValue());
- d.enableAttemptsInterval = parseInt(conf_editor.getEditor("root.generalOptions.enableAttemptsInterval").getValue());
-
- d.useEntertainmentAPI = isEntertainmentReady;
- d.useAPIv2 = isAPIv2Ready;
-
- if (isEntertainmentReady) {
- d.hardwareLedCount = channelNumber;
- if (window.serverConfig.device.type !== d.type) {
- //smoothing on, if new device
- sc.smoothing = { enable: true };
- }
- } else {
- d.hardwareLedCount = finalLightIds.length;
- d.verbose = false;
- if (window.serverConfig.device.type !== d.type) {
- //smoothing off, if new device
- sc.smoothing = { enable: false };
- }
- }
-
- window.serverConfig.device = d;
-
- requestWriteConfig(sc, true);
- resetWizard();
- });
-
- $('#btn_wiz_abort').off().on('click', resetWizard);
-}
-
-function createHueUser() {
- var host = hueIPs[hueIPsinc].host;
- var port = hueIPs[hueIPsinc].port;
-
- let params = { host: host };
- if (port !== 'undefined') {
- params.port = parseInt(port);
- }
-
- var retryTime = 30;
- var retryInterval = 2;
-
- var UserInterval = setInterval(function () {
-
- $('#wizp1').toggle(false);
- $('#wizp2').toggle(false);
- $('#wizp3').toggle(true);
-
- (async () => {
-
- retryTime -= retryInterval;
- $("#connectionTime").html(retryTime);
- if (retryTime <= 0) {
- abortConnection(UserInterval);
- clearInterval(UserInterval);
- }
- else {
- const res = await requestLedDeviceAddAuthorization('philipshue', params);
- if (res && !res.error) {
- var response = res.info;
-
- if (jQuery.isEmptyObject(response)) {
- debugMessage(retryTime + ": link button not pressed or device not reachable");
- } else {
- $('#wizp1').toggle(false);
- $('#wizp2').toggle(true);
- $('#wizp3').toggle(false);
-
- var username = response.username;
- if (username != 'undefined') {
- $('#user').val(username);
- conf_editor.getEditor("root.specificOptions.username").setValue(username);
- conf_editor.getEditor("root.specificOptions.host").setValue(host);
- conf_editor.getEditor("root.specificOptions.port").setValue(port);
- }
-
- if (isEntertainmentReady) {
- var clientkey = response.clientkey;
- if (clientkey != 'undefined') {
- $('#clientkey').val(clientkey);
- conf_editor.getEditor("root.specificOptions.clientkey").setValue(clientkey);
- }
- }
- checkHueBridge(checkUserResult, username);
- clearInterval(UserInterval);
- }
- } else {
- $('#wizp1').toggle(false);
- $('#wizp2').toggle(true);
- $('#wizp3').toggle(false);
- clearInterval(UserInterval);
- }
- }
- })();
-
- }, retryInterval * 1000);
-}
-
-function get_hue_groups(username) {
- var host = hueIPs[hueIPsinc].host;
-
- if (devicesProperties['philipshue'][host] && devicesProperties['philipshue'][host][username]) {
- var ledProperties = devicesProperties['philipshue'][host][username];
-
- if (isAPIv2Ready) {
- if (!jQuery.isEmptyObject(ledProperties.data)) {
- if (Object.keys(ledProperties.data).length > 0) {
- hueEntertainmentConfigs = ledProperties.data.filter(config => {
- return config.type === "entertainment_configuration";
- });
- hueEntertainmentServices = ledProperties.data.filter(config => {
- return (config.type === "entertainment" && config.renderer === true);
- });
- }
- }
- } else {
- if (!jQuery.isEmptyObject(ledProperties.groups)) {
- hueEntertainmentConfigs = [];
- var hueGroups = ledProperties.groups;
- for (var groupid in hueGroups) {
- if (hueGroups[groupid].type == 'Entertainment') {
- hueGroups[groupid].id = groupid;
- hueEntertainmentConfigs.push(hueGroups[groupid]);
- }
- }
- }
- }
-
- if (Object.keys(hueEntertainmentConfigs).length > 0) {
-
- $('.lidsb').html("");
- $('#wh_topcontainer').toggle(false);
- $('#hue_grp_ids_t').toggle(true);
-
- for (var groupid in hueEntertainmentConfigs) {
- $('.gidsb').append(createTableRow([groupid + ' (' + hueEntertainmentConfigs[groupid].name + ')', '']));
- }
- } else {
- noAPISupport('wiz_hue_e_noegrpids', username);
- }
- }
-}
-
-function noAPISupport(txt, username) {
- showNotification('danger', $.i18n('wiz_hue_e_title'), $.i18n('wiz_hue_e_noapisupport_hint'));
- conf_editor.getEditor("root.specificOptions.useEntertainmentAPI").setValue(false);
- $("#root_specificOptions_useEntertainmentAPI").trigger("change");
- $('#btn_wiz_holder').append('' + $.i18n('wiz_hue_e_noapisupport_hint') + '
');
- $('#hue_grp_ids_t').toggle(false);
- var txt = (txt) ? $.i18n(txt) : $.i18n('wiz_hue_e_nogrpids');
- $('' + txt + '
' + $.i18n('wiz_hue_e_noapisupport') + '
').insertBefore('#wizp2_body #hue_ids_t');
- $('#hue_id_headline').html($.i18n('wiz_hue_desc2'));
-
- get_hue_lights(username);
-}
-
-function get_hue_lights(username) {
- var host = hueIPs[hueIPsinc].host;
-
- if (devicesProperties['philipshue'][host] && devicesProperties['philipshue'][host][username]) {
- var ledProperties = devicesProperties['philipshue'][host][username];
-
- if (isAPIv2Ready) {
- if (!jQuery.isEmptyObject(ledProperties.data)) {
- if (Object.keys(ledProperties.data).length > 0) {
- hueLights = ledProperties.data.filter(config => {
- return config.type === "light";
- });
- }
- }
- } else {
- if (!jQuery.isEmptyObject(ledProperties.lights)) {
- hueLights = ledProperties.lights;
- }
- }
-
- if (Object.keys(hueLights).length > 0) {
- if (!isEntertainmentReady) {
- $('#wh_topcontainer').toggle(false);
- }
- $('#hue_ids_t, #btn_wiz_save').toggle(true);
-
- var lightOptions = [
- "top", "topleft", "topright",
- "bottom", "bottomleft", "bottomright",
- "left", "lefttop", "leftmiddle", "leftbottom",
- "right", "righttop", "rightmiddle", "rightbottom",
- "entire",
- "lightPosTopLeft112", "lightPosTopLeftNewMid", "lightPosTopLeft121",
- "lightPosBottomLeft14", "lightPosBottomLeft12", "lightPosBottomLeft34", "lightPosBottomLeft11",
- "lightPosBottomLeft112", "lightPosBottomLeftNewMid", "lightPosBottomLeft121"
- ];
-
- if (isEntertainmentReady) {
- lightOptions.unshift("entertainment_center");
- lightOptions.unshift("entertainment");
- } else {
- lightOptions.unshift("disabled");
- groupLights = Object.keys(hueLights);
- }
-
- $('.lidsb').html("");
-
- var pos = "";
- for (var id in groupLights) {
- var lightId = groupLights[id];
- var lightId_v1 = "/lights/" + lightId;
-
- var lightName;
- if (isAPIv2Ready) {
- var light = hueLights.find(light => light.id === lightId);
- lightName = light.metadata.name;
- lightId_v1 = light.id_v1;
- } else {
- lightName = hueLights[lightId].name;
- }
-
- if (isEntertainmentReady) {
- var lightLocation = {};
- lightLocation = groupLightsLocations[id];
- if (lightLocation) {
- if (isAPIv2Ready) {
- pos = 0;
- } else {
- var x = lightLocation.position.x;
- var y = lightLocation.position.y;
- var z = lightLocation.position.z;
-
- var xval = (x < 0) ? "left" : "right";
- if (z != 1 && x >= -0.25 && x <= 0.25) xval = "";
- switch (z) {
- case 1: // top / Ceiling height
- pos = "top" + xval;
- break;
- case 0: // middle / TV height
- pos = (xval == "" && y >= 0.75) ? "bottom" : xval + "middle";
- break;
- case -1: // bottom / Ground height
- pos = xval + "bottom";
- break;
- }
- }
- }
- }
-
- var options = "";
- for (var opt in lightOptions) {
- var val = lightOptions[opt];
- var txt = (val != 'entire' && val != 'disabled') ? 'conf_leds_layout_cl_' : 'wiz_ids_';
- options += '';
- }
-
- $('.lidsb').append(createTableRow([id + ' (' + lightName + ')', '', '']));
- }
-
- if (!isEntertainmentReady) {
- $('.hue_sel_watch').on("change", function () {
- var cC = 0;
- for (var key in hueLights) {
- if ($('#hue_' + key).val() != "disabled") {
- cC++;
- }
- }
-
- (cC == 0 || window.readOnlyMode) ? $('#btn_wiz_save').prop("disabled", true) : $('#btn_wiz_save').prop("disabled", false);
- });
- }
- $('.hue_sel_watch').trigger('change');
- }
- else {
- var txt = '' + $.i18n('wiz_hue_noids') + '
';
- $('#wizp2_body').append(txt);
- }
- }
-}
-
-function abortConnection(UserInterval) {
- clearInterval(UserInterval);
- $('#wizp1').toggle(false);
- $('#wizp2').toggle(true);
- $('#wizp3').toggle(false);
- $("#wiz_hue_usrstate").html($.i18n('wiz_hue_failure_connection'));
-}
-
-//****************************
-// Wizard Yeelight
-//****************************
-var lights = null;
-function startWizardYeelight(e) {
- //create html
-
- var yeelight_title = 'wiz_yeelight_title';
- var yeelight_intro1 = 'wiz_yeelight_intro1';
-
- $('#wiz_header').html('' + $.i18n(yeelight_title));
- $('#wizp1_body').html('' + $.i18n(yeelight_title) + '
' + $.i18n(yeelight_intro1) + '
');
-
- $('#wizp1_footer').html('');
-
- $('#wizp2_body').html('');
-
- $('#wh_topcontainer').append('');
-
- $('#wizp2_body').append('' + $.i18n('wiz_yeelight_desc2') + '
');
-
- createTable("lidsh", "lidsb", "yee_ids_t");
- $('.lidsh').append(createTableRow([$.i18n('edt_dev_spec_lights_title'), $.i18n('wiz_pos'), $.i18n('wiz_identify')], true));
- $('#wizp2_footer').html(''
- + $.i18n('general_btn_cancel') + '');
-
- if (getStorage("darkMode") == "on")
- $('#wizard_logo').attr("src", 'img/hyperion/logo_negativ.png');
-
- //open modal
- $("#wizard_modal").modal({ backdrop: "static", keyboard: false, show: true });
-
- //listen for continue
- $('#btn_wiz_cont').off().on('click', function () {
- beginWizardYeelight();
- $('#wizp1').toggle(false);
- $('#wizp2').toggle(true);
- });
-}
-
-function beginWizardYeelight() {
- lights = [];
- configuredLights = conf_editor.getEditor("root.specificOptions.lights").getValue();
-
- discover_yeelight_lights();
-
- $('#btn_wiz_save').off().on("click", function () {
- var yeelightLedConfig = [];
- var finalLights = [];
-
- //create yeelight led config
- for (var key in lights) {
- if ($('#yee_' + key).val() !== "disabled") {
-
- var name = lights[key].name;
- // Set Name to layout-position, if empty
- if (name === "") {
- name = lights[key].host;
- }
-
- finalLights.push(lights[key]);
-
- var idx_content = assignLightPos($('#yee_' + key).val(), name);
- yeelightLedConfig.push(JSON.parse(JSON.stringify(idx_content)));
- }
- }
-
- //LED layout
- window.serverConfig.leds = yeelightLedConfig;
-
- //LED device config
- var currentDeviceType = window.serverConfig.device.type;
-
- //Start with a clean configuration
- var d = {};
-
- d.type = 'yeelight';
- d.hardwareLedCount = finalLights.length;
- d.colorOrder = conf_editor.getEditor("root.generalOptions.colorOrder").getValue();
- d.colorModel = parseInt(conf_editor.getEditor("root.specificOptions.colorModel").getValue());
-
- d.transEffect = parseInt(conf_editor.getEditor("root.specificOptions.transEffect").getValue());
- d.transTime = parseInt(conf_editor.getEditor("root.specificOptions.transTime").getValue());
- d.extraTimeDarkness = parseInt(conf_editor.getEditor("root.specificOptions.extraTimeDarkness").getValue());
-
- d.brightnessMin = parseInt(conf_editor.getEditor("root.specificOptions.brightnessMin").getValue());
- d.brightnessSwitchOffOnMinimum = JSON.parse(conf_editor.getEditor("root.specificOptions.brightnessSwitchOffOnMinimum").getValue());
- d.brightnessMax = parseInt(conf_editor.getEditor("root.specificOptions.brightnessMax").getValue());
- d.brightnessFactor = parseFloat(conf_editor.getEditor("root.specificOptions.brightnessFactor").getValue());
-
- d.latchTime = parseInt(conf_editor.getEditor("root.specificOptions.latchTime").getValue());;
- d.debugLevel = parseInt(conf_editor.getEditor("root.specificOptions.debugLevel").getValue());
-
- d.lights = finalLights;
-
- window.serverConfig.device = d;
-
- if (currentDeviceType !== d.type) {
- //smoothing off, if new device
- window.serverConfig.smoothing = { enable: false };
- }
-
- requestWriteConfig(window.serverConfig, true);
- resetWizard();
- });
-
- $('#btn_wiz_abort').off().on('click', resetWizard);
-}
-
-async function discover_yeelight_lights() {
- var light = {};
- // Get discovered lights
- const res = await requestLedDeviceDiscovery('yeelight');
-
- // TODO: error case unhandled
- // res can be: false (timeout) or res.error (not found)
- if (res && !res.error) {
- const r = res.info;
-
- var discoveryMethod = "ssdp";
- if (res.info.discoveryMethod) {
- discoveryMethod = res.info.discoveryMethod;
- }
-
- // Process devices returned by discovery
- for (const device of r.devices) {
- if (device.hostname !== "") {
- if (getHostInLights(device.hostname).length === 0) {
- var light = {};
-
-
-
- if (discoveryMethod === "ssdp") {
- //Create a valid hostname
- if (device.domain) {
- light.host += '.' + device.domain;
- }
- } else {
- light.host = device.service;
- light.name = device.name;
- }
- light.port = device.port;
-
- if (device.txt) {
- light.model = device.txt.md;
- //Yeelight does not provide correct API port with mDNS response, use default one
- light.port = 55443;
- }
- else {
- light.name = device.other.name;
- light.model = device.other.model;
- }
- lights.push(light);
- }
- }
- }
-
- // Add additional items from configuration
- for (var keyConfig in configuredLights) {
- var host = configuredLights[keyConfig].host;
-
- //In case port has been explicitly provided, overwrite port given as part of hostname
- if (configuredLights[keyConfig].port !== 0)
- port = configuredLights[keyConfig].port;
-
- if (host !== "")
- if (getHostInLights(host).length === 0) {
- var light = {};
- light.host = host;
- light.port = port;
- light.name = configuredLights[keyConfig].name;
- light.model = "color4";
- lights.push(light);
- }
- }
-
- assign_yeelight_lights();
- }
-}
-
-function assign_yeelight_lights() {
- // Model mappings, see https://www.home-assistant.io/integrations/yeelight/
- var models = ['color', 'color1', 'YLDP02YL', 'YLDP02YL', 'color2', 'YLDP06YL', 'color4', 'YLDP13YL', 'color6', 'YLDP13AYL', 'colorb', "YLDP005", 'colorc', "YLDP004-A", 'stripe', 'YLDD04YL', 'strip1', 'YLDD01YL', 'YLDD02YL', 'strip4', 'YLDD05YL', 'strip6', 'YLDD05YL'];
-
- // If records are left for configuration
- if (Object.keys(lights).length > 0) {
- $('#wh_topcontainer').toggle(false);
- $('#yee_ids_t, #btn_wiz_save').toggle(true);
-
- var lightOptions = [
- "top", "topleft", "topright",
- "bottom", "bottomleft", "bottomright",
- "left", "lefttop", "leftmiddle", "leftbottom",
- "right", "righttop", "rightmiddle", "rightbottom",
- "entire",
- "lightPosTopLeft112", "lightPosTopLeftNewMid", "lightPosTopLeft121",
- "lightPosBottomLeft14", "lightPosBottomLeft12", "lightPosBottomLeft34", "lightPosBottomLeft11",
- "lightPosBottomLeft112", "lightPosBottomLeftNewMid", "lightPosBottomLeft121"
- ];
-
- lightOptions.unshift("disabled");
-
- $('.lidsb').html("");
- var pos = "";
-
- for (var lightid in lights) {
- var lightHostname = lights[lightid].host;
- var lightPort = lights[lightid].port;
- var lightName = lights[lightid].name;
-
- if (lightName === "")
- lightName = $.i18n('edt_dev_spec_lights_itemtitle') + '(' + lightHostname + ')';
-
- var options = "";
- for (var opt in lightOptions) {
- var val = lightOptions[opt];
- var txt = (val !== 'entire' && val !== 'disabled') ? 'conf_leds_layout_cl_' : 'wiz_ids_';
- options += '';
- }
-
- var enabled = 'enabled';
- if (!models.includes(lights[lightid].model)) {
- var enabled = 'disabled';
- options = '';
- }
-
- $('.lidsb').append(createTableRow([(parseInt(lightid, 10) + 1) + '. ' + lightName, '', '']));
- }
-
- $('.yee_sel_watch').on("change", function () {
- var cC = 0;
- for (var key in lights) {
- if ($('#yee_' + key).val() !== "disabled") {
- cC++;
- }
- }
-
- if (cC === 0 || window.readOnlyMode)
- $('#btn_wiz_save').prop("disabled", true);
- else
- $('#btn_wiz_save').prop("disabled", false);
- });
- $('.yee_sel_watch').trigger('change');
- }
- else {
- var noLightsTxt = '' + $.i18n('wiz_noLights', 'Yeelights') + '
';
- $('#wizp2_body').append(noLightsTxt);
- }
-}
-
-async function getProperties_yeelight(host, port) {
- let params = { host: host, port: port };
-
- const res = await requestLedDeviceProperties('yeelight', params);
-
- // TODO: error case unhandled
- // res can be: false (timeout) or res.error (not found)
- if (res && !res.error) {
- const r = res.info
- console.log("Yeelight properties: ", r);
- }
-}
-
-async function identify_yeelight_device(host, port) {
-
- var disabled = $('#btn_wiz_save').is(':disabled');
-
- // Take care that new record cannot be save during background process
- $('#btn_wiz_save').prop('disabled', true);
-
- let params = { host: host, port: port };
- await requestLedDeviceIdentification("yeelight", params);
-
- if (!window.readOnlyMode) {
- $('#btn_wiz_save').prop('disabled', disabled);
- }
-}
-
-//****************************
-// Wizard AtmoOrb
-//****************************
-var lights = null;
-function startWizardAtmoOrb(e) {
- //create html
-
- var atmoorb_title = 'wiz_atmoorb_title';
- var atmoorb_intro1 = 'wiz_atmoorb_intro1';
-
- $('#wiz_header').html('' + $.i18n(atmoorb_title));
- $('#wizp1_body').html('' + $.i18n(atmoorb_title) + '
' + $.i18n(atmoorb_intro1) + '
');
-
- $('#wizp1_footer').html('');
-
- $('#wizp2_body').html('');
-
- $('#wh_topcontainer').append('');
-
- $('#wizp2_body').append('' + $.i18n('wiz_atmoorb_desc2') + '
');
-
- createTable("lidsh", "lidsb", "orb_ids_t");
- $('.lidsh').append(createTableRow([$.i18n('edt_dev_spec_lights_title'), $.i18n('wiz_pos'), $.i18n('wiz_identify')], true));
- $('#wizp2_footer').html(''
- + $.i18n('general_btn_cancel') + '');
-
- if (getStorage("darkMode") == "on")
- $('#wizard_logo').attr("src", 'img/hyperion/logo_negativ.png');
-
- //open modal
- $("#wizard_modal").modal({ backdrop: "static", keyboard: false, show: true });
-
- //listen for continue
- $('#btn_wiz_cont').off().on('click', function () {
- beginWizardAtmoOrb();
- $('#wizp1').toggle(false);
- $('#wizp2').toggle(true);
- });
-}
-
-function beginWizardAtmoOrb() {
- lights = [];
- configuredLights = [];
-
- var configruedOrbIds = conf_editor.getEditor("root.specificOptions.orbIds").getValue().trim();
- if (configruedOrbIds.length !== 0) {
- configuredLights = configruedOrbIds.split(",").map(Number);
- }
-
- var multiCastGroup = conf_editor.getEditor("root.specificOptions.host").getValue();
- var multiCastPort = parseInt(conf_editor.getEditor("root.specificOptions.port").getValue());
-
- discover_atmoorb_lights(multiCastGroup, multiCastPort);
-
- $('#btn_wiz_save').off().on("click", function () {
- var atmoorbLedConfig = [];
- var finalLights = [];
-
- //create atmoorb led config
- for (var key in lights) {
- if ($('#orb_' + key).val() !== "disabled") {
- // Set Name to layout-position, if empty
- if (lights[key].name === "") {
- lights[key].name = $.i18n('conf_leds_layout_cl_' + $('#orb_' + key).val());
- }
-
- finalLights.push(lights[key].id);
-
- var name = lights[key].id;
- if (lights[key].host !== "")
- name += ':' + lights[key].host;
-
- var idx_content = assignLightPos($('#orb_' + key).val(), name);
- atmoorbLedConfig.push(JSON.parse(JSON.stringify(idx_content)));
- }
- }
-
- //LED layout
- window.serverConfig.leds = atmoorbLedConfig;
-
- //LED device config
- //Start with a clean configuration
- var d = {};
-
- d.type = 'atmoorb';
- d.hardwareLedCount = finalLights.length;
- d.colorOrder = conf_editor.getEditor("root.generalOptions.colorOrder").getValue();
-
- d.orbIds = finalLights.toString();
- d.useOrbSmoothing = (eV("useOrbSmoothing") == true);
-
- d.host = conf_editor.getEditor("root.specificOptions.host").getValue();
- d.port = parseInt(conf_editor.getEditor("root.specificOptions.port").getValue());
- d.latchTime = parseInt(conf_editor.getEditor("root.specificOptions.latchTime").getValue());;
-
- window.serverConfig.device = d;
-
- requestWriteConfig(window.serverConfig, true);
- resetWizard();
- });
-
- $('#btn_wiz_abort').off().on('click', resetWizard);
-}
-
-async function discover_atmoorb_lights(multiCastGroup, multiCastPort) {
- var light = {};
-
- var params = {};
- if (multiCastGroup !== "") {
- params.multiCastGroup = multiCastGroup;
- }
-
- if (multiCastPort !== 0) {
- params.multiCastPort = multiCastPort;
- }
-
- // Get discovered lights
- const res = await requestLedDeviceDiscovery('atmoorb', params);
-
- // TODO: error case unhandled
- // res can be: false (timeout) or res.error (not found)
- if (res && !res.error) {
- const r = res.info;
-
- // Process devices returned by discovery
- for (const device of r.devices) {
- if (device.id !== "") {
- if (getIdInLights(device.id).length === 0) {
- var light = {};
- light.id = device.id;
- light.ip = device.ip;
- light.host = device.hostname;
- lights.push(light);
- }
- }
- }
-
- // Add additional items from configuration
- for (const keyConfig in configuredLights) {
- if (configuredLights[keyConfig] !== "" && !isNaN(configuredLights[keyConfig])) {
- if (getIdInLights(configuredLights[keyConfig]).length === 0) {
- var light = {};
- light.id = configuredLights[keyConfig];
- light.ip = "";
- light.host = "";
- lights.push(light);
- }
- }
- }
-
- lights.sort((a, b) => (a.id > b.id) ? 1 : -1);
-
- assign_atmoorb_lights();
- }
-}
-
-function assign_atmoorb_lights() {
- // If records are left for configuration
- if (Object.keys(lights).length > 0) {
- $('#wh_topcontainer').toggle(false);
- $('#orb_ids_t, #btn_wiz_save').toggle(true);
-
- var lightOptions = [
- "top", "topleft", "topright",
- "bottom", "bottomleft", "bottomright",
- "left", "lefttop", "leftmiddle", "leftbottom",
- "right", "righttop", "rightmiddle", "rightbottom",
- "entire",
- "lightPosTopLeft112", "lightPosTopLeftNewMid", "lightPosTopLeft121",
- "lightPosBottomLeft14", "lightPosBottomLeft12", "lightPosBottomLeft34", "lightPosBottomLeft11",
- "lightPosBottomLeft112", "lightPosBottomLeftNewMid", "lightPosBottomLeft121"
- ];
-
- lightOptions.unshift("disabled");
-
- $('.lidsb').html("");
- var pos = "";
-
- for (var lightid in lights) {
- var orbId = lights[lightid].id;
- var orbIp = lights[lightid].ip;
- var orbHostname = lights[lightid].host;
-
- if (orbHostname === "")
- orbHostname = $.i18n('edt_dev_spec_lights_itemtitle');
-
- var options = "";
- for (var opt in lightOptions) {
- var val = lightOptions[opt];
- var txt = (val !== 'entire' && val !== 'disabled') ? 'conf_leds_layout_cl_' : 'wiz_ids_';
- options += '';
- }
-
- var enabled = 'enabled';
- if (orbId < 1 || orbId > 255) {
- enabled = 'disabled';
- options = '';
- }
-
- var lightAnnotation = "";
- if (orbIp !== "") {
- lightAnnotation = ': ' + orbIp + '
(' + orbHostname + ')';
- }
-
- $('.lidsb').append(createTableRow([orbId + lightAnnotation, '', '']));
- }
-
- $('.orb_sel_watch').on("change", function () {
- var cC = 0;
- for (var key in lights) {
- if ($('#orb_' + key).val() !== "disabled") {
- cC++;
- }
- }
- if (cC === 0 || window.readOnlyMode)
- $('#btn_wiz_save').prop("disabled", true);
- else
- $('#btn_wiz_save').prop("disabled", false);
- });
- $('.orb_sel_watch').trigger('change');
- }
- else {
- var noLightsTxt = '' + $.i18n('wiz_noLights', 'AtmoOrbs') + '
';
- $('#wizp2_body').append(noLightsTxt);
- }
-}
-
-async function identify_atmoorb_device(orbId) {
- var disabled = $('#btn_wiz_save').is(':disabled');
-
- // Take care that new record cannot be save during background process
- $('#btn_wiz_save').prop('disabled', true);
-
- let params = { id: orbId };
- await requestLedDeviceIdentification("atmoorb", params);
-
- if (!window.readOnlyMode) {
- $('#btn_wiz_save').prop('disabled', disabled);
- }
-}
-
-//****************************
-// Nanoleaf Token Wizard
-//****************************
-var lights = null;
-function startWizardNanoleafUserAuth(e) {
- //create html
- var nanoleaf_user_auth_title = 'wiz_nanoleaf_user_auth_title';
- var nanoleaf_user_auth_intro = 'wiz_nanoleaf_user_auth_intro';
-
- $('#wiz_header').html('' + $.i18n(nanoleaf_user_auth_title));
- $('#wizp1_body').html('' + $.i18n(nanoleaf_user_auth_title) + '
' + $.i18n(nanoleaf_user_auth_intro) + '
');
-
- $('#wizp1_footer').html('');
-
- $('#wizp3_body').html('' + $.i18n('wiz_nanoleaf_press_onoff_button') + '
');
-
- if (getStorage("darkMode") == "on")
- $('#wizard_logo').attr("src", 'img/hyperion/logo_negativ.png');
-
- //open modal
- $("#wizard_modal").modal({ backdrop: "static", keyboard: false, show: true });
-
- //listen for continue
- $('#btn_wiz_cont').off().on('click', function () {
- createNanoleafUserAuthorization();
- $('#wizp1').toggle(false);
- $('#wizp3').toggle(true);
- });
-}
-
-function createNanoleafUserAuthorization() {
- var host = conf_editor.getEditor("root.specificOptions.host").getValue();
-
- let params = { host: host };
-
- var retryTime = 30;
- var retryInterval = 2;
-
- var UserInterval = setInterval(function () {
-
- $('#wizp1').toggle(false);
- $('#wizp3').toggle(true);
-
- (async () => {
-
- retryTime -= retryInterval;
- $("#connectionTime").html(retryTime);
- if (retryTime <= 0) {
- abortConnection(UserInterval);
- clearInterval(UserInterval);
-
- showNotification('warning', $.i18n('wiz_nanoleaf_failure_auth_token'), $.i18n('wiz_nanoleaf_failure_auth_token_t'));
-
- resetWizard(true);
- }
- else {
- const res = await requestLedDeviceAddAuthorization('nanoleaf', params);
- if (res && !res.error) {
- var response = res.info;
-
- if (jQuery.isEmptyObject(response)) {
- debugMessage(retryTime + ": Power On/Off button not pressed or device not reachable");
- } else {
- $('#wizp1').toggle(false);
- $('#wizp3').toggle(false);
-
- var token = response.auth_token;
- if (token != 'undefined') {
- conf_editor.getEditor("root.specificOptions.token").setValue(token);
- }
- clearInterval(UserInterval);
- resetWizard(true);
- }
- } else {
- $('#wizp1').toggle(false);
- $('#wizp3').toggle(false);
- clearInterval(UserInterval);
- resetWizard(true);
- }
- }
- })();
-
- }, retryInterval * 1000);
-}
-
diff --git a/assets/webconfig/js/wizards/LedDevice_atmoorb.js b/assets/webconfig/js/wizards/LedDevice_atmoorb.js
new file mode 100644
index 00000000..67d9bd5a
--- /dev/null
+++ b/assets/webconfig/js/wizards/LedDevice_atmoorb.js
@@ -0,0 +1,283 @@
+//****************************
+// Wizard AtmoOrb
+//****************************
+
+import { ledDeviceWizardUtils as utils } from './LedDevice_utils.js';
+
+const atmoorbWizard = (() => {
+
+ const lights = [];
+ let configuredLights = [];
+
+ function getIdInLights(id) {
+ return lights.filter(
+ function (lights) {
+ return lights.id === id
+ }
+ );
+ }
+
+ function begin() {
+
+ const configruedOrbIds = conf_editor.getEditor("root.specificOptions.orbIds").getValue().trim();
+ if (configruedOrbIds.length !== 0) {
+ configuredLights = configruedOrbIds.split(",").map(Number);
+ }
+
+ const multiCastGroup = conf_editor.getEditor("root.specificOptions.host").getValue();
+ const multiCastPort = parseInt(conf_editor.getEditor("root.specificOptions.port").getValue());
+
+ discover(multiCastGroup, multiCastPort);
+
+ $('#btn_wiz_save').off().on("click", function () {
+ let ledConfig = [];
+ let finalLights = [];
+
+ //create atmoorb led config
+ for (let key in lights) {
+ if ($('#orb_' + key).val() !== "disabled") {
+ // Set Name to layout-position, if empty
+ if (lights[key].name === "") {
+ lights[key].name = $.i18n('conf_leds_layout_cl_' + $('#orb_' + key).val());
+ }
+
+ finalLights.push(lights[key].id);
+
+ let name = lights[key].id;
+ if (lights[key].host !== "")
+ name += ':' + lights[key].host;
+
+ const idx_content = utils.assignLightPos($('#orb_' + key).val(), name);
+ ledConfig.push(JSON.parse(JSON.stringify(idx_content)));
+ }
+ }
+
+ //LED layout
+ window.serverConfig.leds = ledConfig;
+
+ //LED device config
+ //Start with a clean configuration
+ let d = {};
+
+ d.type = 'atmoorb';
+ d.hardwareLedCount = finalLights.length;
+ d.colorOrder = conf_editor.getEditor("root.generalOptions.colorOrder").getValue();
+
+ d.orbIds = finalLights.toString();
+ d.useOrbSmoothing = utils.eV("useOrbSmoothing");
+
+ d.host = conf_editor.getEditor("root.specificOptions.host").getValue();
+ d.port = parseInt(conf_editor.getEditor("root.specificOptions.port").getValue());
+ d.latchTime = parseInt(conf_editor.getEditor("root.specificOptions.latchTime").getValue());;
+
+ window.serverConfig.device = d;
+
+ requestWriteConfig(window.serverConfig, true);
+ resetWizard();
+ });
+
+ $('#btn_wiz_abort').off().on('click', resetWizard);
+ }
+
+ async function discover(multiCastGroup, multiCastPort) {
+ let params = {};
+ if (multiCastGroup !== "") {
+ params.multiCastGroup = multiCastGroup;
+ }
+
+ if (multiCastPort !== 0) {
+ params.multiCastPort = multiCastPort;
+ }
+
+ // Get discovered lights
+ const res = await requestLedDeviceDiscovery('atmoorb', params);
+ if (res && !res.error) {
+ const r = res.info;
+
+ // Process devices returned by discovery
+ processDiscoveredDevices(r.devices);
+
+ // Add additional items from configuration
+ for (const configuredLight of configuredLights) {
+ processConfiguredLight(configuredLight);
+ }
+
+ sortLightsById();
+ assign_lights();
+ }
+ }
+
+ function processDiscoveredDevices(devices) {
+ for (const device of devices) {
+ if (device.id !== "" && getIdInLights(device.id).length === 0) {
+ const light = {
+ id: device.id,
+ ip: device.ip,
+ host: device.hostname
+ };
+ lights.push(light);
+ }
+ }
+ }
+
+ function processConfiguredLight(configuredLight) {
+ if (configuredLight !== "" && !isNaN(configuredLight)) {
+ if (getIdInLights(configuredLight).length === 0) {
+ const light = {
+ id: configuredLight,
+ ip: "",
+ host: ""
+ };
+ lights.push(light);
+ }
+ }
+ }
+
+ function attachIdentifyButtonEvent() {
+ // Use event delegation to handle clicks on buttons with class "btn-identify"
+ $('#wizp2_body').on('click', '.btn-identify', function () {
+ const orbId = $(this).data('orb-id');
+ identify(orbId);
+ });
+ }
+
+ function sortLightsById() {
+ lights.sort((a, b) => (a.id > b.id) ? 1 : -1);
+ }
+
+ function assign_lights() {
+ // If records are left for configuration
+ if (Object.keys(lights).length > 0) {
+ $('#wh_topcontainer').toggle(false);
+ $('#orb_ids_t, #btn_wiz_save').toggle(true);
+
+ const lightOptions = [
+ "top", "topleft", "topright",
+ "bottom", "bottomleft", "bottomright",
+ "left", "lefttop", "leftmiddle", "leftbottom",
+ "right", "righttop", "rightmiddle", "rightbottom",
+ "entire",
+ "lightPosTopLeft112", "lightPosTopLeftNewMid", "lightPosTopLeft121",
+ "lightPosBottomLeft14", "lightPosBottomLeft12", "lightPosBottomLeft34", "lightPosBottomLeft11",
+ "lightPosBottomLeft112", "lightPosBottomLeftNewMid", "lightPosBottomLeft121"
+ ];
+
+ lightOptions.unshift("disabled");
+
+ $('.lidsb').html("");
+ let pos = "";
+
+ for (const lightid in lights) {
+ const orbId = lights[lightid].id;
+ const orbIp = lights[lightid].ip;
+ let orbHostname = lights[lightid].host;
+
+ if (orbHostname === "")
+ orbHostname = $.i18n('edt_dev_spec_lights_itemtitle');
+
+ let options = "";
+ for (const opt in lightOptions) {
+ const val = lightOptions[opt];
+ const txt = (val !== 'entire' && val !== 'disabled') ? 'conf_leds_layout_cl_' : 'wiz_ids_';
+ options += '';
+ }
+
+ let enabled = 'enabled';
+ if (orbId < 1 || orbId > 255) {
+ enabled = 'disabled';
+ options = '';
+ }
+
+ let lightAnnotation = "";
+ if (orbIp !== "") {
+ lightAnnotation = ': ' + orbIp + '
(' + orbHostname + ')';
+ }
+
+ $('.lidsb').append(createTableRow([orbId + lightAnnotation, '', '']));
+ }
+ attachIdentifyButtonEvent();
+
+ $('.orb_sel_watch').on("change", function () {
+ let cC = 0;
+ for (const key in lights) {
+ if ($('#orb_' + key).val() !== "disabled") {
+ cC++;
+ }
+ }
+ if (cC === 0 || window.readOnlyMode)
+ $('#btn_wiz_save').prop("disabled", true);
+ else
+ $('#btn_wiz_save').prop("disabled", false);
+ });
+ $('.orb_sel_watch').trigger('change');
+ }
+ else {
+ const noLightsTxt = '' + $.i18n('wiz_noLights', 'AtmoOrbs') + '
';
+ $('#wizp2_body').append(noLightsTxt);
+ }
+
+
+ }
+
+ async function identify(orbId) {
+ const disabled = $('#btn_wiz_save').is(':disabled');
+
+ // Take care that new record cannot be save during background process
+ $('#btn_wiz_save').prop('disabled', true);
+
+ const params = { id: orbId };
+ await requestLedDeviceIdentification("atmoorb", params);
+
+ if (!window.readOnlyMode) {
+ $('#btn_wiz_save').prop('disabled', disabled);
+ }
+ }
+
+ return {
+ start: function (e) {
+
+ //create html
+ const atmoorb_title = 'wiz_atmoorb_title';
+ const atmoorb_intro1 = 'wiz_atmoorb_intro1';
+
+ $('#wiz_header').html('' + $.i18n(atmoorb_title));
+ $('#wizp1_body').html('' + $.i18n(atmoorb_title) + '
' + $.i18n(atmoorb_intro1) + '
');
+
+ $('#wizp1_footer').html('');
+
+ $('#wizp2_body').html('');
+
+ $('#wh_topcontainer').append('');
+
+ $('#wizp2_body').append('' + $.i18n('wiz_atmoorb_desc2') + '
');
+
+ createTable("lidsh", "lidsb", "orb_ids_t");
+ $('.lidsh').append(createTableRow([$.i18n('edt_dev_spec_lights_title'), $.i18n('wiz_pos'), $.i18n('wiz_identify')], true));
+ $('#wizp2_footer').html(''
+ + $.i18n('general_btn_cancel') + '');
+
+ if (getStorage("darkMode") == "on")
+ $('#wizard_logo').attr("src", 'img/hyperion/logo_negativ.png');
+
+ //open modal
+ $("#wizard_modal").modal({ backdrop: "static", keyboard: false, show: true });
+
+ //listen for continue
+ $('#btn_wiz_cont').off().on('click', function () {
+ begin();
+ $('#wizp1').toggle(false);
+ $('#wizp2').toggle(true);
+ });
+ }
+ };
+})();
+
+export { atmoorbWizard };
diff --git a/assets/webconfig/js/wizards/LedDevice_nanoleaf.js b/assets/webconfig/js/wizards/LedDevice_nanoleaf.js
new file mode 100644
index 00000000..83220003
--- /dev/null
+++ b/assets/webconfig/js/wizards/LedDevice_nanoleaf.js
@@ -0,0 +1,94 @@
+//****************************
+// Wizard Nanoleaf
+//****************************
+
+const nanoleafWizard = (() => {
+
+ const retryInterval = 2;
+
+ async function createNanoleafUserAuthorization() {
+ const host = conf_editor.getEditor("root.specificOptions.host").getValue();
+ const params = { host };
+ let retryTime = 30;
+
+ const UserInterval = setInterval(async function () {
+ retryTime -= retryInterval;
+ $("#connectionTime").html(retryTime);
+
+ if (retryTime <= 0) {
+ handleTimeout();
+ } else {
+ const res = await requestLedDeviceAddAuthorization('nanoleaf', params);
+ handleResponse(res);
+ }
+ }, retryInterval * 1000);
+
+ function handleTimeout() {
+ clearInterval(UserInterval);
+ showNotification(
+ 'warning',
+ $.i18n('wiz_nanoleaf_failure_auth_token'),
+ $.i18n('wiz_nanoleaf_failure_auth_token_t')
+ );
+ resetWizard(true);
+ }
+
+ function handleResponse(res) {
+ if (res && !res.error) {
+ const response = res.info;
+ if (jQuery.isEmptyObject(response)) {
+ debugMessage(`${retryTime}: Power On/Off button not pressed or device not reachable`);
+ } else {
+ const token = response.auth_token;
+ if (token !== 'undefined') {
+ conf_editor.getEditor("root.specificOptions.token").setValue(token);
+ }
+ clearInterval(UserInterval);
+ resetWizard(true);
+ }
+ } else {
+ clearInterval(UserInterval);
+ resetWizard(true);
+ }
+ }
+ }
+
+ return {
+ start: function () {
+ const nanoleaf_user_auth_title = 'wiz_nanoleaf_user_auth_title';
+ const nanoleaf_user_auth_intro = 'wiz_nanoleaf_user_auth_intro';
+
+ $('#wiz_header').html(
+ `${$.i18n(nanoleaf_user_auth_title)}`
+ );
+ $('#wizp1_body').html(
+ `${$.i18n(nanoleaf_user_auth_title)}
${$.i18n(nanoleaf_user_auth_intro)}
`
+ );
+ $('#wizp1_footer').html(
+ ``
+ );
+ $('#wizp3_body').html(
+ `${$.i18n('wiz_nanoleaf_press_onoff_button')}
`
+ );
+
+ if (getStorage("darkMode") == "on") {
+ $('#wizard_logo').attr("src", 'img/hyperion/logo_negativ.png');
+ }
+
+ $("#wizard_modal").modal({
+ backdrop: "static",
+ keyboard: false,
+ show: true
+ });
+
+ $('#btn_wiz_cont').off().on('click', function () {
+ createNanoleafUserAuthorization();
+ $('#wizp1').toggle(false);
+ $('#wizp3').toggle(true);
+ });
+ }
+ };
+})();
+
+export { nanoleafWizard };
+
diff --git a/assets/webconfig/js/wizards/LedDevice_philipshue.js b/assets/webconfig/js/wizards/LedDevice_philipshue.js
new file mode 100644
index 00000000..92f1c173
--- /dev/null
+++ b/assets/webconfig/js/wizards/LedDevice_philipshue.js
@@ -0,0 +1,988 @@
+//****************************
+// Wizard Philips Hue
+//****************************
+
+import { ledDeviceWizardUtils as utils } from './LedDevice_utils.js';
+
+const philipshueWizard = (() => {
+
+ // External properties, 2-dimensional arry of [ledType][key]
+ let devicesProperties = {};
+
+ let hueIPs = [];
+ let hueIPsinc = 0;
+ let hueLights = [];
+ let hueEntertainmentConfigs = [];
+ let hueEntertainmentServices = [];
+ let groupLights = [];
+ let groupChannels = [];
+ let groupLightsLocations = [];
+ let isAPIv2Ready = true;
+ let isEntertainmentReady = true;
+
+ function checkHueBridge(cb, hueUser) {
+ const usr = (typeof hueUser != "undefined") ? hueUser : 'config';
+ if (usr === 'config') {
+ $('#wiz_hue_discovered').html("");
+ }
+
+ if (hueIPs[hueIPsinc]) {
+ const host = hueIPs[hueIPsinc].host;
+ const port = hueIPs[hueIPsinc].port;
+
+ if (usr != '') {
+ getProperties(cb, decodeURIComponent(host), port, usr);
+ }
+ else {
+ cb(false, usr);
+ }
+
+ if (isAPIv2Ready) {
+ $('#port').val(443);
+ }
+ }
+ }
+
+ function checkBridgeResult(reply, usr) {
+ if (reply) {
+ //abort checking, first reachable result is used
+ $('#wiz_hue_ipstate').html("");
+ $('#host').val(hueIPs[hueIPsinc].host)
+ $('#port').val(hueIPs[hueIPsinc].port)
+
+ $('#usrcont').toggle(true);
+
+ checkHueBridge(checkUserResult, $('#user').val());
+ }
+ else {
+ $('#usrcont').toggle(false);
+ $('#wiz_hue_ipstate').html($.i18n('wiz_hue_failure_ip'));
+ }
+ };
+
+ function checkUserResult(reply, username) {
+ $('#usrcont').toggle(true);
+
+ let hue_create_user = 'wiz_hue_e_create_user';
+ if (!isEntertainmentReady) {
+ hue_create_user = 'wiz_hue_create_user';
+ $('#hue_client_key_r').toggle(false);
+ } else {
+ $('#hue_client_key_r').toggle(true);
+ }
+
+ $('#wiz_hue_create_user').text($.i18n(hue_create_user));
+ $('#wiz_hue_create_user').toggle(true);
+
+ if (reply) {
+ $('#user').val(username);
+
+ if (isEntertainmentReady && $('#clientkey').val() == "") {
+ $('#wiz_hue_usrstate').html($.i18n('wiz_hue_e_clientkey_needed'));
+ $('#wiz_hue_create_user').toggle(true);
+ } else {
+ $('#wiz_hue_usrstate').html("");
+ $('#wiz_hue_create_user').toggle(false);
+
+ if (isEntertainmentReady) {
+ $('#hue_id_headline').text($.i18n('wiz_hue_e_desc3'));
+ $('#hue_grp_ids_t').toggle(true);
+
+ get_hue_groups(username);
+
+ } else {
+ $('#hue_id_headline').text($.i18n('wiz_hue_desc2'));
+ $('#hue_grp_ids_t').toggle(false);
+
+ get_hue_lights(username);
+
+ }
+ }
+ }
+ else {
+ //abort checking, first reachable result is used
+ $('#wiz_hue_usrstate').html($.i18n('wiz_hue_failure_user'));
+ $('#wiz_hue_create_user').toggle(true);
+ }
+ };
+
+ function useGroupId(id, username) {
+ $('#groupId').val(hueEntertainmentConfigs[id].id);
+ if (isAPIv2Ready) {
+ const group = hueEntertainmentConfigs[id];
+
+ groupLights = [];
+ for (const light of group.light_services) {
+ groupLights.push(light.rid);
+ }
+
+ groupChannels = [];
+ for (const channel of group.channels) {
+ groupChannels.push(channel);
+ }
+
+ groupLightsLocations = [];
+ for (const location of group.locations.service_locations) {
+ groupLightsLocations.push(location);
+ }
+ } else {
+ //Ensure ligthIDs are strings
+ groupLights = hueEntertainmentConfigs[id].lights.map(num => {
+ return String(num);
+ });
+
+ const lightLocations = hueEntertainmentConfigs[id].locations;
+ for (const locationID in lightLocations) {
+ let lightLocation = {};
+
+ let position = {
+ x: lightLocations[locationID][0],
+ y: lightLocations[locationID][1],
+ z: lightLocations[locationID][2]
+ };
+ lightLocation.position = position;
+
+ groupLightsLocations.push(lightLocation);
+ }
+ }
+
+ get_hue_lights(username);
+ }
+
+ function assignLightEntertainmentPos(isFocusCenter, position, name, id) {
+
+ let x = position.x;
+ let z = position.z;
+
+ if (isFocusCenter) {
+ // Map lights as in centered range -0.5 to 0.5
+ if (x < -0.5) {
+ x = -0.5;
+ } else if (x > 0.5) {
+ x = 0.5;
+ }
+ if (z < -0.5) {
+ z = -0.5;
+ } else if (z > 0.5) {
+ z = 0.5;
+ }
+ } else {
+ // Map lights as in full range -1 to 1
+ x /= 2;
+ z /= 2;
+ }
+
+ const h = x + 0.5;
+ const v = -z + 0.5;
+
+ const hmin = h - 0.05;
+ const hmax = h + 0.05;
+ const vmin = v - 0.05;
+ const vmax = v + 0.05;
+
+ let layoutObject = {
+ hmin: hmin < 0 ? 0 : hmin,
+ hmax: hmax > 1 ? 1 : hmax,
+ vmin: vmin < 0 ? 0 : vmin,
+ vmax: vmax > 1 ? 1 : vmax,
+ name: name
+ };
+
+ if (id !== undefined && id !== null) {
+ layoutObject.name += "_" + id;
+ }
+ return layoutObject;
+ }
+
+ function assignSegmentedLightPos(segment, position, name) {
+ let layoutObjects = [];
+
+ let segTotalLength = 0;
+ for (const key in segment) {
+
+ segTotalLength += segment[key].length;
+ }
+
+ let min;
+ let max;
+ let horizontal = true;
+
+ let layoutObject = utils.assignLightPos(position, name);
+ if (position === "left" || position === "right") {
+ // vertical distribution
+ min = layoutObject.vmin;
+ max = layoutObject.vmax;
+ horizontal = false;
+
+ } else {
+ // horizontal distribution
+ min = layoutObject.hmin;
+ max = layoutObject.hmax;
+ }
+
+ const step = (max - min) / segTotalLength;
+ let start = min;
+
+ for (const key in segment) {
+ min = start;
+ max = round(start + segment[key].length * step);
+
+ if (horizontal) {
+ layoutObject.hmin = min;
+ layoutObject.hmax = max;
+ } else {
+ layoutObject.vmin = min;
+ layoutObject.vmax = max;
+ }
+ layoutObject.name = name + "_" + key;
+ layoutObjects.push(JSON.parse(JSON.stringify(layoutObject)));
+
+ start = max;
+ }
+
+ return layoutObjects;
+ }
+
+ function updateBridgeDetails(properties) {
+ const ledDeviceProperties = properties.config;
+
+ if (!jQuery.isEmptyObject(ledDeviceProperties)) {
+ isEntertainmentReady = properties.isEntertainmentReady;
+ isAPIv2Ready = properties.isAPIv2Ready;
+
+ if (ledDeviceProperties.name && ledDeviceProperties.bridgeid && ledDeviceProperties.modelid) {
+ $('#wiz_hue_discovered').html(
+ "Bridge: " + ledDeviceProperties.name +
+ ", Modelid: " + ledDeviceProperties.modelid +
+ ", Firmware: " + ledDeviceProperties.swversion + "
" +
+ "API-Version: " + ledDeviceProperties.apiversion +
+ ", Entertainment: " + (isEntertainmentReady ? "✓" : "-") +
+ ", APIv2: " + (isAPIv2Ready ? "✓" : "-")
+ );
+ }
+ }
+ }
+
+ async function discover() {
+ $('#wiz_hue_ipstate').html($.i18n('edt_dev_spec_devices_discovery_inprogress'));
+
+ // $('#wiz_hue_discovered').html("")
+ const res = await requestLedDeviceDiscovery('philipshue');
+ if (res && !res.error) {
+ const r = res.info;
+
+ // Process devices returned by discovery
+ if (r.devices.length == 0) {
+ $('#wiz_hue_ipstate').html($.i18n('wiz_hue_failure_ip'));
+ $('#wiz_hue_discovered').html("")
+ }
+ else {
+ hueIPs = [];
+ hueIPsinc = 0;
+
+ let discoveryMethod = "ssdp";
+ if (res.info.discoveryMethod) {
+ discoveryMethod = res.info.discoveryMethod;
+ }
+
+ for (const device of r.devices) {
+ if (device) {
+ let host;
+ let port;
+ if (discoveryMethod === "ssdp") {
+ if (device.hostname && device.domain) {
+ host = device.hostname + "." + device.domain;
+ port = device.port;
+ } else {
+ host = device.ip;
+ port = device.port;
+ }
+ } else {
+ host = device.service;
+ port = device.port;
+ }
+ if (host) {
+
+ if (!hueIPs.some(item => item.host === host)) {
+ hueIPs.push({ host: host, port: port });
+ }
+ }
+ }
+ }
+
+ $('#wiz_hue_ipstate').html("");
+ $('#host').val(hueIPs[hueIPsinc].host)
+ $('#port').val(hueIPs[hueIPsinc].port)
+
+ $('#hue_bridge_select').html("");
+
+ for (const key in hueIPs) {
+ $('#hue_bridge_select').append(createSelOpt(key, hueIPs[key].host));
+ }
+
+ $('.hue_bridge_sel_watch').on("click", function () {
+ hueIPsinc = $(this).val();
+
+ const name = $("#hue_bridge_select option:selected").text();
+ $('#host').val(name);
+ $('#port').val(hueIPs[hueIPsinc].port)
+
+ const usr = $('#user').val();
+ if (usr != "") {
+ checkHueBridge(checkUserResult, usr);
+ } else {
+ checkHueBridge(checkBridgeResult);
+ }
+ });
+
+ $('.hue_bridge_sel_watch').click();
+ }
+ }
+ }
+
+ async function getProperties(cb, hostAddress, port, username, resourceFilter) {
+ let params = { host: hostAddress, username: username, filter: resourceFilter };
+ if (port !== 'undefined') {
+ params.port = parseInt(port);
+ }
+
+ const ledType = 'philipshue';
+ const key = hostAddress;
+
+ //Create ledType cache entry
+ if (!devicesProperties[ledType]) {
+ devicesProperties[ledType] = {};
+ }
+
+ // Use device's properties, if properties in chache
+ if (devicesProperties[ledType][key] && devicesProperties[ledType][key][username]) {
+ updateBridgeDetails(devicesProperties[ledType][key]);
+ cb(true, username);
+ } else {
+ const res = await requestLedDeviceProperties(ledType, params);
+ if (res && !res.error) {
+ const ledDeviceProperties = res.info.properties;
+ if (!jQuery.isEmptyObject(ledDeviceProperties)) {
+
+ devicesProperties[ledType][key] = {};
+ devicesProperties[ledType][key][username] = ledDeviceProperties;
+
+ isAPIv2Ready = res.info.isAPIv2Ready;
+ devicesProperties[ledType][key].isAPIv2Ready = isAPIv2Ready;
+ isEntertainmentReady = res.info.isEntertainmentReady;
+ devicesProperties[ledType][key].isEntertainmentReady = isEntertainmentReady;
+
+ updateBridgeDetails(devicesProperties[ledType][key]);
+ if (username === "config") {
+ cb(true);
+ } else {
+ cb(true, username);
+ }
+ } else {
+ cb(false, username);
+ }
+ } else {
+ cb(false, username);
+ }
+ }
+ }
+
+ async function identify(hostAddress, port, username, name, id, id_v1) {
+ const disabled = $('#btn_wiz_save').is(':disabled');
+ // Take care that new record cannot be save during background process
+ $('#btn_wiz_save').prop('disabled', true);
+
+ let params = { host: decodeURIComponent(hostAddress), username: username, lightName: decodeURIComponent(name), lightId: id, lightId_v1: id_v1 };
+
+ if (port !== 'undefined') {
+ params.port = parseInt(port);
+ }
+
+ await requestLedDeviceIdentification('philipshue', params);
+
+ if (!window.readOnlyMode) {
+ $('#btn_wiz_save').prop('disabled', disabled);
+ }
+ }
+
+ function begin() {
+ const usr = utils.eV("username");
+ if (usr != "") {
+ $('#user').val(usr);
+ }
+
+ const clkey = utils.eV("clientkey");
+ if (clkey != "") {
+ $('#clientkey').val(clkey);
+ }
+
+ //check if host is empty/reachable/search for bridge
+ if (utils.eV("host") == "") {
+ hueIPs = [];
+ hueIPsinc = 0;
+
+ discover();
+ }
+ else {
+ const host = utils.eV("host");
+ $('#host').val(host);
+
+ const port = utils.eV("port");
+ if (port > 0) {
+ $('#port').val(port);
+ }
+ else {
+ $('#port').val('');
+ }
+ hueIPs.push({ host: host, port: port });
+
+ if (usr != "") {
+ checkHueBridge(checkUserResult, usr);
+ } else {
+ checkHueBridge(checkBridgeResult);
+ }
+ }
+
+ $('#retry_bridge').off().on('click', function () {
+ const host = $('#host').val();
+ const port = parseInt($('#port').val());
+
+ if (host != "") {
+
+ const idx = hueIPs.findIndex(item => item.host === host && item.port === port);
+ if (idx === -1) {
+ hueIPs.push({ host: host, port: port });
+ hueIPsinc = hueIPs.length - 1;
+ } else {
+ hueIPsinc = idx;
+ }
+ }
+ else {
+ discover();
+ }
+
+ const usr = $('#user').val();
+ if (usr != "") {
+ checkHueBridge(checkUserResult, usr);
+ } else {
+ checkHueBridge(checkBridgeResult);
+ }
+ });
+
+ $('#retry_usr').off().on('click', function () {
+ checkHueBridge(checkUserResult, $('#user').val());
+ });
+
+ $('#wiz_hue_create_user').off().on('click', function () {
+ createHueUser();
+ });
+ $('#btn_wiz_save').off().on("click", function () {
+ let hueLedConfig = [];
+ let finalLightIds = [];
+ let channelNumber = 0;
+
+ //create hue led config
+ for (const key in groupLights) {
+ const lightId = groupLights[key];
+
+ if ($('#hue_' + lightId).val() != "disabled") {
+ finalLightIds.push(lightId);
+
+ let lightName;
+ if (isAPIv2Ready) {
+ const light = hueLights.find(light => light.id === lightId);
+ lightName = light.metadata.name;
+ } else {
+ lightName = hueLights[lightId].name;
+ }
+
+ const position = $('#hue_' + lightId).val();
+ const lightIdx = groupLights.indexOf(lightId);
+ const lightLocation = groupLightsLocations[lightIdx];
+
+ let serviceID;
+ if (isAPIv2Ready) {
+ serviceID = lightLocation.service.rid;
+ }
+
+ if (position.startsWith("entertainment")) {
+
+ // Layout per entertainment area definition at bridge
+ let isFocusCenter = false;
+ if (position === "entertainment_center") {
+ isFocusCenter = true;
+ }
+
+ if (isAPIv2Ready) {
+
+ groupChannels.forEach((channel) => {
+ if (channel.members[0].service.rid === serviceID) {
+ const layoutObject = assignLightEntertainmentPos(isFocusCenter, channel.position, lightName, channel.channel_id);
+ hueLedConfig.push(JSON.parse(JSON.stringify(layoutObject)));
+ ++channelNumber;
+ }
+ });
+ } else {
+ const layoutObject = assignLightEntertainmentPos(isFocusCenter, lightLocation.position, lightName);
+ hueLedConfig.push(JSON.parse(JSON.stringify(layoutObject)));
+ }
+ }
+ else {
+ // Layout per manual settings
+ let maxSegments = 1;
+
+ if (isAPIv2Ready) {
+ const service = hueEntertainmentServices.find(service => service.id === serviceID);
+ maxSegments = service.segments.max_segments;
+ }
+
+ if (maxSegments > 1) {
+ const segment = service.segments.segments;
+ const layoutObjects = assignSegmentedLightPos(segment, position, lightName);
+ hueLedConfig.push(...layoutObjects);
+ } else {
+ const layoutObject = utils.assignLightPos(position, lightName);
+ hueLedConfig.push(JSON.parse(JSON.stringify(layoutObject)));
+ }
+ channelNumber += maxSegments;
+ }
+ }
+ }
+
+ let sc = window.serverConfig;
+ sc.leds = hueLedConfig;
+
+ //Adjust gamma, brightness and compensation
+ let c = sc.color.channelAdjustment[0];
+ c.gammaBlue = 1.0;
+ c.gammaRed = 1.0;
+ c.gammaGreen = 1.0;
+ c.brightness = 100;
+ c.brightnessCompensation = 0;
+
+ //device config
+
+ //Start with a clean configuration
+ let d = {};
+ d.host = $('#host').val();
+ d.port = parseInt($('#port').val());
+ d.username = $('#user').val();
+ d.type = 'philipshue';
+ d.colorOrder = 'rgb';
+ d.lightIds = finalLightIds;
+ d.transitiontime = parseInt(utils.eV("transitiontime", 1));
+ d.restoreOriginalState = utils.eV("restoreOriginalState", false);
+ d.switchOffOnBlack = utils.eV("switchOffOnBlack", false);
+
+ d.blackLevel = parseFloat(utils.eV("blackLevel", 0.009));
+ d.onBlackTimeToPowerOff = parseInt(utils.eV("onBlackTimeToPowerOff", 600));
+ d.onBlackTimeToPowerOn = parseInt(utils.eV("onBlackTimeToPowerOn", 300));
+ d.brightnessFactor = parseFloat(utils.eV("brightnessFactor", 1));
+
+ d.clientkey = $('#clientkey').val();
+ d.groupId = $('#groupId').val();
+ d.blackLightsTimeout = parseInt(utils.eV("blackLightsTimeout", 5000));
+ d.brightnessMin = parseFloat(utils.eV("brightnessMin", 0));
+ d.brightnessMax = parseFloat(utils.eV("brightnessMax", 1));
+ d.brightnessThreshold = parseFloat(utils.eV("brightnessThreshold", 0.0001));
+ d.handshakeTimeoutMin = parseInt(utils.eV("handshakeTimeoutMin", 300));
+ d.handshakeTimeoutMax = parseInt(utils.eV("handshakeTimeoutMax", 1000));
+ d.verbose = utils.eV("verbose");
+
+ d.autoStart = conf_editor.getEditor("root.generalOptions.autoStart").getValue();
+ d.enableAttempts = parseInt(conf_editor.getEditor("root.generalOptions.enableAttempts").getValue());
+ d.enableAttemptsInterval = parseInt(conf_editor.getEditor("root.generalOptions.enableAttemptsInterval").getValue());
+
+ d.useEntertainmentAPI = isEntertainmentReady;
+ d.useAPIv2 = isAPIv2Ready;
+
+ if (isEntertainmentReady) {
+ d.hardwareLedCount = channelNumber;
+ if (window.serverConfig.device.type !== d.type) {
+ //smoothing on, if new device
+ sc.smoothing = { enable: true };
+ }
+ } else {
+ d.hardwareLedCount = finalLightIds.length;
+ d.verbose = false;
+ if (window.serverConfig.device.type !== d.type) {
+ //smoothing off, if new device
+ sc.smoothing = { enable: false };
+ }
+ }
+
+ window.serverConfig.device = d;
+
+ requestWriteConfig(sc, true);
+ resetWizard();
+ });
+
+ $('#btn_wiz_abort').off().on('click', resetWizard);
+ }
+
+ function createHueUser() {
+ const host = hueIPs[hueIPsinc].host;
+ const port = hueIPs[hueIPsinc].port;
+
+ let params = { host: host };
+ if (port !== 'undefined') {
+ params.port = parseInt(port);
+ }
+
+ let retryTime = 30;
+ const retryInterval = 2;
+
+ const UserInterval = setInterval(function () {
+
+ $('#wizp1').toggle(false);
+ $('#wizp2').toggle(false);
+ $('#wizp3').toggle(true);
+
+ (async () => {
+
+ retryTime -= retryInterval;
+ $("#connectionTime").html(retryTime);
+ if (retryTime <= 0) {
+ abortConnection(UserInterval);
+ clearInterval(UserInterval);
+ }
+ else {
+ const res = await requestLedDeviceAddAuthorization('philipshue', params);
+ if (res && !res.error) {
+ const response = res.info;
+
+ if (jQuery.isEmptyObject(response)) {
+ debugMessage(retryTime + ": link button not pressed or device not reachable");
+ } else {
+ $('#wizp1').toggle(false);
+ $('#wizp2').toggle(true);
+ $('#wizp3').toggle(false);
+
+ const username = response.username;
+ if (username != 'undefined') {
+ $('#user').val(username);
+ conf_editor.getEditor("root.specificOptions.username").setValue(username);
+ conf_editor.getEditor("root.specificOptions.host").setValue(host);
+ conf_editor.getEditor("root.specificOptions.port").setValue(port);
+ }
+
+ if (isEntertainmentReady) {
+ const clientkey = response.clientkey;
+ if (clientkey != 'undefined') {
+ $('#clientkey').val(clientkey);
+ conf_editor.getEditor("root.specificOptions.clientkey").setValue(clientkey);
+ }
+ }
+ checkHueBridge(checkUserResult, username);
+ clearInterval(UserInterval);
+ }
+ } else {
+ $('#wizp1').toggle(false);
+ $('#wizp2').toggle(true);
+ $('#wizp3').toggle(false);
+ clearInterval(UserInterval);
+ }
+ }
+ })();
+
+ }, retryInterval * 1000);
+ }
+
+ function get_hue_groups(username) {
+ const host = hueIPs[hueIPsinc].host;
+
+ if (devicesProperties['philipshue'][host] && devicesProperties['philipshue'][host][username]) {
+ const ledProperties = devicesProperties['philipshue'][host][username];
+
+ if (isAPIv2Ready) {
+ if (!jQuery.isEmptyObject(ledProperties.data)) {
+ if (Object.keys(ledProperties.data).length > 0) {
+ hueEntertainmentConfigs = ledProperties.data.filter(config => {
+ return config.type === "entertainment_configuration";
+ });
+ hueEntertainmentServices = ledProperties.data.filter(config => {
+ return (config.type === "entertainment" && config.renderer === true);
+ });
+ }
+ }
+ } else if (!jQuery.isEmptyObject(ledProperties.groups)) {
+ hueEntertainmentConfigs = [];
+ let hueGroups = ledProperties.groups;
+ for (const groupid in hueGroups) {
+ if (hueGroups[groupid].type == 'Entertainment') {
+ hueGroups[groupid].id = groupid;
+ hueEntertainmentConfigs.push(hueGroups[groupid]);
+ }
+ }
+ }
+
+ if (Object.keys(hueEntertainmentConfigs).length > 0) {
+
+ $('.lidsb').html("");
+ $('#wh_topcontainer').toggle(false);
+ $('#hue_grp_ids_t').toggle(true);
+
+ for (const groupid in hueEntertainmentConfigs) {
+ $('.gidsb').append(createTableRow([groupid + ' (' + hueEntertainmentConfigs[groupid].name + ')',
+ '']));
+ }
+ attachGroupButtonEvent();
+
+ } else {
+ noAPISupport('wiz_hue_e_noegrpids', username);
+ }
+ }
+ }
+ function attachIdentifyButtonEvent() {
+ $('#wizp2_body').on('click', '.btn-identify', function () {
+ const hostname = $(this).data('hostname');
+ const port = $(this).data('port');
+ const user = $(this).data('user');
+ const lightName = $(this).data('light-name');
+ const lightId = $(this).data('light-id');
+ const lightId_v1 = $(this).data('light-id-v1');
+
+ identify(hostname, port, user, lightName, lightId, lightId_v1);
+ });
+ }
+ function attachGroupButtonEvent() {
+ $('#wizp2_body').on('click', '.btn-group', function () {
+ const groupid = $(this).data('groupid');
+ const username = $(this).data('username');
+
+ useGroupId(groupid, username);
+ });
+ }
+
+ function noAPISupport(txt, username) {
+ showNotification('danger', $.i18n('wiz_hue_e_title'), $.i18n('wiz_hue_e_noapisupport_hint'));
+ conf_editor.getEditor("root.specificOptions.useEntertainmentAPI").setValue(false);
+ $("#root_specificOptions_useEntertainmentAPI").trigger("change");
+ $('#btn_wiz_holder').append('' + $.i18n('wiz_hue_e_noapisupport_hint') + '
');
+ $('#hue_grp_ids_t').toggle(false);
+ const errorMessage = txt ? $.i18n(txt) : $.i18n('wiz_hue_e_nogrpids');
+ $('' + errorMessage + '
' + $.i18n('wiz_hue_e_noapisupport') + '
').insertBefore('#wizp2_body #hue_ids_t');
+ $('#hue_id_headline').html($.i18n('wiz_hue_desc2'));
+
+ get_hue_lights(username);
+ }
+
+ function get_hue_lights(username) {
+ const host = hueIPs[hueIPsinc].host;
+
+ if (devicesProperties['philipshue'][host] && devicesProperties['philipshue'][host][username]) {
+ const ledProperties = devicesProperties['philipshue'][host][username];
+
+ if (isAPIv2Ready) {
+ if (!jQuery.isEmptyObject(ledProperties.data)) {
+ if (Object.keys(ledProperties.data).length > 0) {
+ hueLights = ledProperties.data.filter(config => {
+ return config.type === "light";
+ });
+ }
+ }
+ } else if (!jQuery.isEmptyObject(ledProperties.lights)) {
+ hueLights = ledProperties.lights;
+ }
+
+ if (Object.keys(hueLights).length > 0) {
+ if (!isEntertainmentReady) {
+ $('#wh_topcontainer').toggle(false);
+ }
+ $('#hue_ids_t, #btn_wiz_save').toggle(true);
+
+ const lightOptions = [
+ "top", "topleft", "topright",
+ "bottom", "bottomleft", "bottomright",
+ "left", "lefttop", "leftmiddle", "leftbottom",
+ "right", "righttop", "rightmiddle", "rightbottom",
+ "entire",
+ "lightPosTopLeft112", "lightPosTopLeftNewMid", "lightPosTopLeft121",
+ "lightPosBottomLeft14", "lightPosBottomLeft12", "lightPosBottomLeft34", "lightPosBottomLeft11",
+ "lightPosBottomLeft112", "lightPosBottomLeftNewMid", "lightPosBottomLeft121"
+ ];
+
+ if (isEntertainmentReady) {
+ lightOptions.unshift("entertainment_center");
+ lightOptions.unshift("entertainment");
+ } else {
+ lightOptions.unshift("disabled");
+ groupLights = Object.keys(hueLights);
+ }
+
+ $('.lidsb').html("");
+
+ let pos = "";
+ for (const id in groupLights) {
+ const lightId = groupLights[id];
+ let lightId_v1 = "/lights/" + lightId;
+
+ let lightName;
+ if (isAPIv2Ready) {
+ const light = hueLights.find(light => light.id === lightId);
+ lightName = light.metadata.name;
+ lightId_v1 = light.id_v1;
+ } else {
+ lightName = hueLights[lightId].name;
+ }
+
+ if (isEntertainmentReady) {
+ let lightLocation = {};
+ lightLocation = groupLightsLocations[id];
+ if (lightLocation) {
+ if (isAPIv2Ready) {
+ pos = 0;
+ } else {
+ const x = lightLocation.position.x;
+ const y = lightLocation.position.y;
+ const z = lightLocation.position.z;
+
+ let xval = (x < 0) ? "left" : "right";
+ if (z != 1 && x >= -0.25 && x <= 0.25) xval = "";
+ switch (z) {
+ case 1: // top / Ceiling height
+ pos = "top" + xval;
+ break;
+ case 0: // middle / TV height
+ pos = (xval == "" && y >= 0.75) ? "bottom" : xval + "middle";
+ break;
+ case -1: // bottom / Ground height
+ pos = xval + "bottom";
+ break;
+ }
+ }
+ }
+ }
+
+ let options = "";
+ for (const opt in lightOptions) {
+ const val = lightOptions[opt];
+ const txt = (val != 'entire' && val != 'disabled') ? 'conf_leds_layout_cl_' : 'wiz_ids_';
+ options += '';
+ }
+
+ $('.lidsb').append(createTableRow([id + ' (' + lightName + ')',
+ '',
+ '']));
+ }
+ attachIdentifyButtonEvent();
+
+ if (!isEntertainmentReady) {
+ $('.hue_sel_watch').on("change", function () {
+ let cC = 0;
+ for (const key in hueLights) {
+ if ($('#hue_' + key).val() != "disabled") {
+ cC++;
+ }
+ }
+
+ (cC == 0 || window.readOnlyMode) ? $('#btn_wiz_save').prop("disabled", true) : $('#btn_wiz_save').prop("disabled", false);
+ });
+ }
+ $('.hue_sel_watch').trigger('change');
+ }
+ else {
+ const txt = '' + $.i18n('wiz_hue_noids') + '
';
+ $('#wizp2_body').append(txt);
+ }
+ }
+ }
+
+ function abortConnection(UserInterval) {
+ clearInterval(UserInterval);
+ $('#wizp1').toggle(false);
+ $('#wizp2').toggle(true);
+ $('#wizp3').toggle(false);
+ $("#wiz_hue_usrstate").html($.i18n('wiz_hue_failure_connection'));
+ }
+
+ return {
+ start: function (e) {
+ //create html
+ const hue_title = 'wiz_hue_title';
+ const hue_intro1 = 'wiz_hue_e_intro1';
+ const hue_desc1 = 'wiz_hue_desc1';
+ const hue_create_user = 'wiz_hue_create_user';
+
+ $('#wiz_header').html('' + $.i18n(hue_title));
+ $('#wizp1_body').html('' + $.i18n(hue_title) + '
' + $.i18n(hue_intro1) + '
');
+ $('#wizp1_footer').html('');
+ $('#wizp2_body').html('');
+
+ let topContainer_html = '' + $.i18n(hue_desc1) + '
' +
+ '' +
+ '
' +
+ '
' + $.i18n('wiz_hue_ip') + '
' +
+ '
' +
+ ' ' +
+ ' ' + '
' +
+ '
';
+
+ if (storedAccess === 'expert') {
+ topContainer_html += '
';
+ }
+
+ topContainer_html += '
';
+ topContainer_html += '';
+
+ $('#wh_topcontainer').append(topContainer_html);
+
+ $('#usrcont').append('' + $.i18n('wiz_hue_username') + '
' +
+ '
' +
+ ''
+ );
+
+ $('#usrcont').append('
');
+
+ $('#usrcont').append('
' +
+ '');
+
+ $('#wizp2_body').append('' + $.i18n('wiz_hue_e_desc2') + '
');
+ createTable("gidsh", "gidsb", "hue_grp_ids_t");
+ $('.gidsh').append(createTableRow([$.i18n('edt_dev_spec_groupId_title'), ""], true));
+
+ $('#wizp2_body').append('' + $.i18n('wiz_hue_e_desc3') + '
');
+
+ createTable("lidsh", "lidsb", "hue_ids_t");
+ $('.lidsh').append(createTableRow([$.i18n('edt_dev_spec_lightid_title'), $.i18n('wiz_pos'), $.i18n('wiz_identify')], true));
+ $('#wizp2_footer').html('');
+ $('#wizp3_body').html('' + $.i18n('wiz_hue_press_link') + '
');
+
+ if (getStorage("darkMode") == "on")
+ $('#wizard_logo').attr("src", 'img/hyperion/logo_negativ.png');
+
+ //open modal
+ $("#wizard_modal").modal({
+ backdrop: "static",
+ keyboard: false,
+ show: true
+ });
+
+ //listen for continue
+ $('#btn_wiz_cont').off().on('click', function () {
+ begin();
+ $('#wizp1').toggle(false);
+ $('#wizp2').toggle(true);
+ });
+ }
+ };
+})();
+
+export { philipshueWizard }
+
diff --git a/assets/webconfig/js/wizards/LedDevice_utils.js b/assets/webconfig/js/wizards/LedDevice_utils.js
new file mode 100644
index 00000000..1f3eab3e
--- /dev/null
+++ b/assets/webconfig/js/wizards/LedDevice_utils.js
@@ -0,0 +1,60 @@
+
+const ledDeviceWizardUtils = (() => {
+
+ // Layout positions
+ const positionMap = {
+ "top": { hmin: 0.15, hmax: 0.85, vmin: 0, vmax: 0.2 },
+ "topleft": { hmin: 0, hmax: 0.15, vmin: 0, vmax: 0.15 },
+ "topright": { hmin: 0.85, hmax: 1.0, vmin: 0, vmax: 0.15 },
+ "bottom": { hmin: 0.15, hmax: 0.85, vmin: 0.8, vmax: 1.0 },
+ "bottomleft": { hmin: 0, hmax: 0.15, vmin: 0.85, vmax: 1.0 },
+ "bottomright": { hmin: 0.85, hmax: 1.0, vmin: 0.85, vmax: 1.0 },
+ "left": { hmin: 0, hmax: 0.15, vmin: 0.15, vmax: 0.85 },
+ "lefttop": { hmin: 0, hmax: 0.15, vmin: 0, vmax: 0.5 },
+ "leftmiddle": { hmin: 0, hmax: 0.15, vmin: 0.25, vmax: 0.75 },
+ "leftbottom": { hmin: 0, hmax: 0.15, vmin: 0.5, vmax: 1.0 },
+ "right": { hmin: 0.85, hmax: 1.0, vmin: 0.15, vmax: 0.85 },
+ "righttop": { hmin: 0.85, hmax: 1.0, vmin: 0, vmax: 0.5 },
+ "rightmiddle": { hmin: 0.85, hmax: 1.0, vmin: 0.25, vmax: 0.75 },
+ "rightbottom": { hmin: 0.85, hmax: 1.0, vmin: 0.5, vmax: 1.0 },
+ "lightPosBottomLeft14": { hmin: 0, hmax: 0.25, vmin: 0.85, vmax: 1.0 },
+ "lightPosBottomLeft12": { hmin: 0.25, hmax: 0.5, vmin: 0.85, vmax: 1.0 },
+ "lightPosBottomLeft34": { hmin: 0.5, hmax: 0.75, vmin: 0.85, vmax: 1.0 },
+ "lightPosBottomLeft11": { hmin: 0.75, hmax: 1, vmin: 0.85, vmax: 1.0 },
+ "lightPosBottomLeft112": { hmin: 0, hmax: 0.5, vmin: 0.85, vmax: 1.0 },
+ "lightPosBottomLeft121": { hmin: 0.5, hmax: 1, vmin: 0.85, vmax: 1.0 },
+ "lightPosBottomLeftNewMid": { hmin: 0.25, hmax: 0.75, vmin: 0.85, vmax: 1.0 },
+ "lightPosTopLeft112": { hmin: 0, hmax: 0.5, vmin: 0, vmax: 0.15 },
+ "lightPosTopLeft121": { hmin: 0.5, hmax: 1, vmin: 0, vmax: 0.15 },
+ "lightPosTopLeftNewMid": { hmin: 0.25, hmax: 0.75, vmin: 0, vmax: 0.15 },
+ "lightPosEntire": { hmin: 0.0, hmax: 1.0, vmin: 0.0, vmax: 1.0 }
+ };
+
+ return {
+
+ //return editor Value
+ eV: function (vn, defaultVal = "") {
+ let editor = null;
+ if (vn) {
+ editor = conf_editor.getEditor("root.specificOptions." + vn);
+ }
+
+ if (editor === null) {
+ return defaultVal;
+ } else if (defaultVal !== "" && !isNaN(defaultVal) && isNaN(editor.getValue())) {
+ return defaultVal;
+ } else {
+ return editor.getValue();
+ }
+ },
+ assignLightPos: function (pos, name) {
+ // Retrieve the corresponding position object from the positionMap
+ const i = positionMap[pos] || positionMap["lightPosEntire"];
+ i.name = name;
+ return i;
+ }
+ };
+
+})();
+
+export { ledDeviceWizardUtils };
diff --git a/assets/webconfig/js/wizards/LedDevice_yeelight.js b/assets/webconfig/js/wizards/LedDevice_yeelight.js
new file mode 100644
index 00000000..4f53eb07
--- /dev/null
+++ b/assets/webconfig/js/wizards/LedDevice_yeelight.js
@@ -0,0 +1,300 @@
+//****************************
+// Wizard Yeelight
+//****************************
+
+import { ledDeviceWizardUtils as utils } from './LedDevice_utils.js';
+
+const yeelightWizard = (() => {
+
+ const lights = [];
+ let configuredLights = conf_editor.getEditor("root.specificOptions.lights").getValue();
+
+ function getHostInLights(hostname) {
+ return lights.filter(
+ function (lights) {
+ return lights.host === hostname
+ }
+ );
+ }
+
+ function begin() {
+ discover();
+
+ $('#btn_wiz_save').off().on("click", function () {
+ let ledConfig = [];
+ let finalLights = [];
+
+ //create yeelight led config
+ for (const key in lights) {
+ if ($('#yee_' + key).val() !== "disabled") {
+
+ let name = lights[key].name;
+ // Set Name to layout-position, if empty
+ if (name === "") {
+ name = lights[key].host;
+ }
+
+ finalLights.push(lights[key]);
+
+ const idx_content = utils.assignLightPos($('#yee_' + key).val(), name);
+ ledConfig.push(JSON.parse(JSON.stringify(idx_content)));
+ }
+ }
+
+ //LED layout
+ window.serverConfig.leds = ledConfig;
+
+ //LED device config
+ const currentDeviceType = window.serverConfig.device.type;
+
+ //Start with a clean configuration
+ let d = {};
+
+ d.type = 'yeelight';
+ d.hardwareLedCount = finalLights.length;
+ d.colorOrder = conf_editor.getEditor("root.generalOptions.colorOrder").getValue();
+ d.colorModel = parseInt(conf_editor.getEditor("root.specificOptions.colorModel").getValue());
+
+ d.transEffect = parseInt(conf_editor.getEditor("root.specificOptions.transEffect").getValue());
+ d.transTime = parseInt(conf_editor.getEditor("root.specificOptions.transTime").getValue());
+ d.extraTimeDarkness = parseInt(conf_editor.getEditor("root.specificOptions.extraTimeDarkness").getValue());
+
+ d.brightnessMin = parseInt(conf_editor.getEditor("root.specificOptions.brightnessMin").getValue());
+ d.brightnessSwitchOffOnMinimum = JSON.parse(conf_editor.getEditor("root.specificOptions.brightnessSwitchOffOnMinimum").getValue());
+ d.brightnessMax = parseInt(conf_editor.getEditor("root.specificOptions.brightnessMax").getValue());
+ d.brightnessFactor = parseFloat(conf_editor.getEditor("root.specificOptions.brightnessFactor").getValue());
+
+ d.latchTime = parseInt(conf_editor.getEditor("root.specificOptions.latchTime").getValue());;
+ d.debugLevel = parseInt(conf_editor.getEditor("root.specificOptions.debugLevel").getValue());
+
+ d.lights = finalLights;
+
+ window.serverConfig.device = d;
+
+ if (currentDeviceType !== d.type) {
+ //smoothing off, if new device
+ window.serverConfig.smoothing = { enable: false };
+ }
+
+ requestWriteConfig(window.serverConfig, true);
+ resetWizard();
+ });
+
+ $('#btn_wiz_abort').off().on('click', resetWizard);
+ }
+
+ async function discover() {
+ // Get discovered lights
+ const res = await requestLedDeviceDiscovery('yeelight');
+ if (res && !res.error) {
+ const r = res.info;
+
+ let discoveryMethod = "ssdp";
+ if (res.info.discoveryMethod) {
+ discoveryMethod = res.info.discoveryMethod;
+ }
+
+ // Process devices returned by discovery
+ for (const device of r.devices) {
+ if (device.hostname !== "") {
+ processDiscoverdDevice(device, discoveryMethod);
+ }
+ }
+
+ // Add additional items from configuration
+ for (const configuredLight of configuredLights) {
+ processConfiguredLight(configuredLight);
+ }
+
+ assign_lights();
+ }
+ }
+
+ function processDiscoverdDevice(device, discoveryMethod) {
+ if (getHostInLights(device.hostname).length > 0) {
+ return;
+ }
+
+ const light = {
+ host: device.hostname
+ };
+
+ if (discoveryMethod === "ssdp") {
+ if (device.domain) {
+ light.host += '.' + device.domain;
+ }
+ } else {
+ light.host = device.service;
+ light.name = device.name;
+ }
+
+ light.port = device.port;
+
+ if (device.txt) {
+ light.model = device.txt.md;
+ light.port = 55443; // Yeelight default port
+ } else {
+ light.name = device.other.name;
+ light.model = device.other.model;
+ }
+
+ lights.push(light);
+ }
+ function processConfiguredLight(configuredLight) {
+ const host = configuredLight.host;
+ let port = configuredLight.port || 0;
+
+ if (host !== "" && getHostInLights(host).length === 0) {
+ const light = {
+ host: host,
+ port: port,
+ name: configuredLight.name,
+ model: "color4"
+ };
+
+ lights.push(light);
+ }
+ }
+
+ function attachIdentifyButtonEvent() {
+ $('#wizp2_body').on('click', '.btn-identify', function () {
+ const hostname = $(this).data('hostname');
+ const port = $(this).data('port');
+ identify(hostname, port);
+ });
+ }
+
+ function assign_lights() {
+ // Model mappings, see https://www.home-assistant.io/integrations/yeelight/
+ const models = ['color', 'color1', 'YLDP02YL', 'YLDP02YL', 'color2', 'YLDP06YL', 'color4', 'YLDP13YL', 'color6', 'YLDP13AYL', 'colorb', "YLDP005", 'colorc', "YLDP004-A", 'stripe', 'YLDD04YL', 'strip1', 'YLDD01YL', 'YLDD02YL', 'strip4', 'YLDD05YL', 'strip6', 'YLDD05YL'];
+
+ // If records are left for configuration
+ if (Object.keys(lights).length > 0) {
+ $('#wh_topcontainer').toggle(false);
+ $('#yee_ids_t, #btn_wiz_save').toggle(true);
+
+ const lightOptions = [
+ "top", "topleft", "topright",
+ "bottom", "bottomleft", "bottomright",
+ "left", "lefttop", "leftmiddle", "leftbottom",
+ "right", "righttop", "rightmiddle", "rightbottom",
+ "entire",
+ "lightPosTopLeft112", "lightPosTopLeftNewMid", "lightPosTopLeft121",
+ "lightPosBottomLeft14", "lightPosBottomLeft12", "lightPosBottomLeft34", "lightPosBottomLeft11",
+ "lightPosBottomLeft112", "lightPosBottomLeftNewMid", "lightPosBottomLeft121"
+ ];
+
+ lightOptions.unshift("disabled");
+
+ $('.lidsb').html("");
+ let pos = "";
+
+ for (const lightid in lights) {
+ const lightHostname = lights[lightid].host;
+ const lightPort = lights[lightid].port;
+ let lightName = lights[lightid].name;
+
+ if (lightName === "")
+ lightName = $.i18n('edt_dev_spec_lights_itemtitle') + '(' + lightHostname + ')';
+
+ let options = "";
+ for (const opt in lightOptions) {
+ const val = lightOptions[opt];
+ const txt = (val !== 'entire' && val !== 'disabled') ? 'conf_leds_layout_cl_' : 'wiz_ids_';
+ options += '';
+ }
+
+ let enabled = 'enabled';
+ if (!models.includes(lights[lightid].model)) {
+ enabled = 'disabled';
+ options = '';
+ }
+
+ $('.lidsb').append(createTableRow([(parseInt(lightid, 10) + 1) + '. ' + lightName, '', '']));
+ }
+ attachIdentifyButtonEvent();
+
+ $('.yee_sel_watch').on("change", function () {
+ let cC = 0;
+ for (const key in lights) {
+ if ($('#yee_' + key).val() !== "disabled") {
+ cC++;
+ }
+ }
+
+ if (cC === 0 || window.readOnlyMode)
+ $('#btn_wiz_save').prop("disabled", true);
+ else
+ $('#btn_wiz_save').prop("disabled", false);
+ });
+ $('.yee_sel_watch').trigger('change');
+ }
+ else {
+ const noLightsTxt = '' + $.i18n('wiz_noLights', 'lights') + '
';
+ $('#wizp2_body').append(noLightsTxt);
+ }
+ }
+
+ async function identify(host, port) {
+
+ const disabled = $('#btn_wiz_save').is(':disabled');
+
+ // Take care that new record cannot be save during background process
+ $('#btn_wiz_save').prop('disabled', true);
+
+ const params = { host: host, port: port };
+ await requestLedDeviceIdentification("yeelight", params);
+
+ if (!window.readOnlyMode) {
+ $('#btn_wiz_save').prop('disabled', disabled);
+ }
+ }
+
+ return {
+ start: function (e) {
+ //create html
+ const yeelight_title = 'wiz_yeelight_title';
+ const yeelight_intro1 = 'wiz_yeelight_intro1';
+
+ $('#wiz_header').html('' + $.i18n(yeelight_title));
+ $('#wizp1_body').html('' + $.i18n(yeelight_title) + '
' + $.i18n(yeelight_intro1) + '
');
+
+ $('#wizp1_footer').html('');
+
+ $('#wizp2_body').html('');
+
+ $('#wh_topcontainer').append('');
+
+ $('#wizp2_body').append('' + $.i18n('wiz_yeelight_desc2') + '
');
+
+ createTable("lidsh", "lidsb", "yee_ids_t");
+ $('.lidsh').append(createTableRow([$.i18n('edt_dev_spec_lights_title'), $.i18n('wiz_pos'), $.i18n('wiz_identify')], true));
+ $('#wizp2_footer').html(''
+ + $.i18n('general_btn_cancel') + '');
+
+ if (getStorage("darkMode") == "on")
+ $('#wizard_logo').attr("src", 'img/hyperion/logo_negativ.png');
+
+ //open modal
+ $("#wizard_modal").modal({ backdrop: "static", keyboard: false, show: true });
+
+ //listen for continue
+ $('#btn_wiz_cont').off().on('click', function () {
+ begin();
+ $('#wizp1').toggle(false);
+ $('#wizp2').toggle(true);
+ });
+ }
+};
+}) ();
+
+export { yeelightWizard };
+
diff --git a/assets/webconfig/js/wizards/colorCalibrationKodiWizard.js b/assets/webconfig/js/wizards/colorCalibrationKodiWizard.js
new file mode 100644
index 00000000..7879d641
--- /dev/null
+++ b/assets/webconfig/js/wizards/colorCalibrationKodiWizard.js
@@ -0,0 +1,485 @@
+//****************************
+// Wizard Color calibration via Kodi
+//****************************
+const colorCalibrationKodiWizard = (() => {
+
+ let ws;
+ const defaultKodiPort = 9090;
+
+ let kodiAddress = document.location.hostname;
+ let kodiPort = defaultKodiPort;
+
+ const kodiUrl = new URL("ws://" + kodiAddress);
+ kodiUrl.port = kodiPort;
+ kodiUrl.pathname = "/jsonrpc/websocket";
+
+ let wiz_editor;
+ let colorLength;
+ let cobj;
+ let step = 0;
+ let withKodi = false;
+ let profile = 0;
+ let websAddress;
+ let imgAddress;
+ let picnr = 0;
+ let id = 1;
+ const vidAddress = "https://sourceforge.net/projects/hyperion-project/files/resources/vid/";
+ const availVideos = ["Sweet_Cocoon", "Caminandes_2_GranDillama", "Caminandes_3_Llamigos"];
+
+ if (getStorage("kodiAddress") != null) {
+
+ kodiAddress = getStorage("kodiAddress");
+ kodiUrl.host = kodiAddress;
+ }
+
+ if (getStorage("kodiPort") != null) {
+ kodiPort = getStorage("kodiPort");
+ kodiUrl.port = kodiPort;
+ }
+
+ $(window).on('beforeunload', function () {
+ closeWebSocket();
+ });
+
+ function closeWebSocket() {
+ // Check if the WebSocket is open
+ if (ws && ws.readyState === WebSocket.OPEN) {
+ ws.close();
+ }
+ }
+
+ function sendToKodi(type, content) {
+ let command;
+
+ switch (type) {
+ case "msg":
+ command = { "jsonrpc": "2.0", "method": "GUI.ShowNotification", "params": { "title": $.i18n('wiz_cc_title'), "message": content, "image": "info", "displaytime": 5000 }, "id": id };
+ break;
+ case "stop":
+ command = { "jsonrpc": "2.0", "method": "Player.Stop", "params": { "playerid": 2 }, "id": id };
+ break;
+ case "playP":
+ content = imgAddress + content + '.png';
+ command = { "jsonrpc": "2.0", "method": "Player.Open", "params": { "item": { "file": content } }, "id": id };
+ break;
+ case "playV":
+ content = vidAddress + content;
+ command = { "jsonrpc": "2.0", "method": "Player.Open", "params": { "item": { "file": content } }, "id": id };
+ break;
+ case "rotate":
+ command = { "jsonrpc": "2.0", "method": "Player.Rotate", "params": { "playerid": 2 }, "id": id };
+ break;
+ default:
+ console.error('Unknown Kodi command type: ', type);
+ }
+
+ if (ws.readyState === WebSocket.OPEN) {
+ ws.send(JSON.stringify(command));
+ ++id;
+ } else {
+ console.error('WebSocket connection is not open. Unable to send command.');
+ }
+ }
+
+ function performAction() {
+ let h;
+
+ if (step == 1) {
+ $('#wiz_cc_desc').html($.i18n('wiz_cc_chooseid'));
+ updateEditor(["id"]);
+ $('#btn_wiz_back').prop("disabled", true);
+ }
+ else
+ $('#btn_wiz_back').prop("disabled", false);
+
+ if (step == 2) {
+ updateEditor(["white"]);
+ h = $.i18n('wiz_cc_adjustit', $.i18n('edt_conf_color_white_title'));
+ if (withKodi) {
+ h += '
' + $.i18n('wiz_cc_kodishould', $.i18n('edt_conf_color_white_title'));
+ sendToKodi('playP', "white");
+ }
+ else
+ h += '
' + $.i18n('wiz_cc_lettvshow', $.i18n('edt_conf_color_white_title'));
+ $('#wiz_cc_desc').html(h);
+ }
+ if (step == 3) {
+ updateEditor(["gammaRed", "gammaGreen", "gammaBlue"]);
+ h = '' + $.i18n('wiz_cc_adjustgamma') + '
';
+ if (withKodi) {
+ sendToKodi('playP', "HGradient");
+ h += '';
+ }
+ else
+ h += '' + $.i18n('wiz_cc_lettvshowm', "grey_1, grey_2, grey_3, HGradient, VGradient") + '
';
+ $('#wiz_cc_desc').html(h);
+ $('#wiz_cc_btn_sp').off().on('click', function () {
+ switchPicture(["VGradient", "grey_1", "grey_2", "grey_3", "HGradient"]);
+ });
+ }
+ if (step == 4) {
+ updateEditor(["red"]);
+ h = $.i18n('wiz_cc_adjustit', $.i18n('edt_conf_color_red_title'));
+ if (withKodi) {
+ h += '
' + $.i18n('wiz_cc_kodishould', $.i18n('edt_conf_color_red_title'));
+ sendToKodi('playP', "red");
+ }
+ else
+ h += '
' + $.i18n('wiz_cc_lettvshow', $.i18n('edt_conf_color_red_title'));
+ $('#wiz_cc_desc').html(h);
+ }
+ if (step == 5) {
+ updateEditor(["green"]);
+ h = $.i18n('wiz_cc_adjustit', $.i18n('edt_conf_color_green_title'));
+ if (withKodi) {
+ h += '
' + $.i18n('wiz_cc_kodishould', $.i18n('edt_conf_color_green_title'));
+ sendToKodi('playP', "green");
+ }
+ else
+ h += '
' + $.i18n('wiz_cc_lettvshow', $.i18n('edt_conf_color_green_title'));
+ $('#wiz_cc_desc').html(h);
+ }
+ if (step == 6) {
+ updateEditor(["blue"]);
+ h = $.i18n('wiz_cc_adjustit', $.i18n('edt_conf_color_blue_title'));
+ if (withKodi) {
+ h += '
' + $.i18n('wiz_cc_kodishould', $.i18n('edt_conf_color_blue_title'));
+ sendToKodi('playP', "blue");
+ }
+ else
+ h += '
' + $.i18n('wiz_cc_lettvshow', $.i18n('edt_conf_color_blue_title'));
+ $('#wiz_cc_desc').html(h);
+ }
+ if (step == 7) {
+ updateEditor(["cyan"]);
+ h = $.i18n('wiz_cc_adjustit', $.i18n('edt_conf_color_cyan_title'));
+ if (withKodi) {
+ h += '
' + $.i18n('wiz_cc_kodishould', $.i18n('edt_conf_color_cyan_title'));
+ sendToKodi('playP', "cyan");
+ }
+ else
+ h += '
' + $.i18n('wiz_cc_lettvshow', $.i18n('edt_conf_color_cyan_title'));
+ $('#wiz_cc_desc').html(h);
+ }
+ if (step == 8) {
+ updateEditor(["magenta"]);
+ h = $.i18n('wiz_cc_adjustit', $.i18n('edt_conf_color_magenta_title'));
+ if (withKodi) {
+ h += '
' + $.i18n('wiz_cc_kodishould', $.i18n('edt_conf_color_magenta_title'));
+ sendToKodi('playP', "magenta");
+ }
+ else
+ h += '
' + $.i18n('wiz_cc_lettvshow', $.i18n('edt_conf_color_magenta_title'));
+ $('#wiz_cc_desc').html(h);
+ }
+ if (step == 9) {
+ updateEditor(["yellow"]);
+ h = $.i18n('wiz_cc_adjustit', $.i18n('edt_conf_color_yellow_title'));
+ if (withKodi) {
+ h += '
' + $.i18n('wiz_cc_kodishould', $.i18n('edt_conf_color_yellow_title'));
+ sendToKodi('playP', "yellow");
+ }
+ else
+ h += '
' + $.i18n('wiz_cc_lettvshow', $.i18n('edt_conf_color_yellow_title'));
+ $('#wiz_cc_desc').html(h);
+ }
+ if (step == 10) {
+ updateEditor(["backlightThreshold", "backlightColored"]);
+ h = $.i18n('wiz_cc_backlight');
+ if (withKodi) {
+ h += '
' + $.i18n('wiz_cc_kodishould', $.i18n('edt_conf_color_black_title'));
+ sendToKodi('playP', "black");
+ }
+ else
+ h += '
' + $.i18n('wiz_cc_lettvshow', $.i18n('edt_conf_color_black_title'));
+ $('#wiz_cc_desc').html(h);
+ }
+ if (step == 11) {
+ updateEditor([""], true);
+ h = '' + $.i18n('wiz_cc_testintro') + '
';
+ if (withKodi) {
+ h += '' + $.i18n('wiz_cc_testintrok') + '
';
+ sendToKodi('stop');
+ availVideos.forEach(video => {
+ const txt = video.replace(/_/g, " ");
+ h += ``;
+ });
+
+ h += '';
+ }
+ else
+ h += '' + $.i18n('wiz_cc_testintrowok') + ' ' + $.i18n('wiz_cc_link') + '
';
+ h += '' + $.i18n('wiz_cc_summary') + '
';
+ $('#wiz_cc_desc').html(h);
+
+ $('.videobtn').off().on('click', function (e) {
+ if (e.target.id == "stop")
+ sendToKodi("stop");
+ else
+ sendToKodi("playV", e.target.id + '.mp4');
+
+ $(this).prop("disabled", true);
+ setTimeout(function () { $('.videobtn').prop("disabled", false) }, 10000);
+ });
+
+ $('#btn_wiz_next').prop("disabled", true);
+ $('#btn_wiz_save').toggle(true);
+ window.readOnlyMode ? $('#btn_wiz_save').prop('disabled', true) : $('#btn_wiz_save').prop('disabled', false);
+ }
+ else {
+ $('#btn_wiz_next').prop("disabled", false);
+ $('#btn_wiz_save').toggle(false);
+ }
+ }
+
+
+
+ function switchPicture(pictures) {
+ if (typeof pictures[picnr] === 'undefined')
+ picnr = 0;
+
+ sendToKodi('playP', pictures[picnr]);
+ picnr++;
+ }
+
+
+ function initializeWebSocket(cb) {
+ if ("WebSocket" in window) {
+
+ if (kodiUrl.port === '') {
+ kodiUrl.port = defaultKodiPort;
+ }
+
+ if (!ws || ws.readyState !== WebSocket.OPEN) {
+
+ // Establish WebSocket connection
+ ws = new WebSocket(kodiUrl);
+
+ // WebSocket onopen event
+ ws.onopen = function (event) {
+ withKodi = true;
+ cb("opened");
+ };
+
+ // WebSocket onmessage event (handle incoming messages)
+ ws.onmessage = function (event) {
+ const response = JSON.parse(event.data);
+ if (response.method === "System.OnQuit") {
+ closeWebSocket();
+ } else if (response.result != undefined) {
+ if (response.result !== "OK") {
+ cb("error");
+ }
+ }
+ };
+
+ // WebSocket onerror event
+ ws.onerror = function (error) {
+ cb("error");
+ };
+
+ // WebSocket onclose event
+ ws.onclose = function (event) {
+ withKodi = false;
+ if (event.code === 1006) {
+ // Ignore error 1006 due to Kodi issue
+ console.log("WebSocket closed with error code 1006. Ignoring due to Kodi bug.");
+ }
+ else {
+ console.error("WebSocket closed with code:", event.code);
+ }
+ };
+ } else {
+ console.log("WebSocket connection is already open.");
+ }
+ }
+ else {
+ console.log("Kodi Access: WebSocket NOT supported by this browser");
+ cb("error");
+ }
+ }
+
+ function setupEventListeners() {
+ $('#btn_wiz_cancel').off().on('click', function () {
+ stop(true);
+ });
+ $('#wiz_cc_kodiip').off().on('change', function () {
+
+ kodiAddress = encodeURIComponent($(this).val().trim());
+
+ $('#kodi_status').html('');
+ if (kodiAddress !== "") {
+
+ if (!isValidHostnameOrIP(kodiAddress)) {
+
+ $('#kodi_status').html('' + $.i18n('edt_msgcust_error_hostname_ip') + '
');
+ withKodi = false;
+
+ } else {
+
+ if (isValidIPv6(kodiAddress)) {
+ kodiUrl.hostname = "[" + kodiAddress + "]";
+ } else {
+ kodiUrl.hostname = kodiAddress;
+ }
+
+ $('#kodi_status').html('' + $.i18n('wiz_cc_try_connect') + '
');
+ $('#btn_wiz_cont').prop('disabled', true);
+
+ closeWebSocket();
+ initializeWebSocket(function (cb) {
+
+ if (cb == "opened") {
+ setStorage("kodiAddress", kodiAddress);
+ setStorage("kodiPort", defaultKodiPort);
+
+ $('#kodi_status').html('' + $.i18n('wiz_cc_kodicon') + '
');
+ $('#btn_wiz_cont').prop('disabled', false);
+
+ if (withKodi) {
+ sendToKodi("msg", $.i18n('wiz_cc_kodimsg_start'));
+ }
+ }
+ else {
+ $('#kodi_status').html('' + $.i18n('wiz_cc_kodidiscon') + '
' + $.i18n('wiz_cc_kodidisconlink') + ' ' + $.i18n('wiz_cc_link') + '
');
+ withKodi = false;
+ }
+
+ $('#btn_wiz_cont').prop('disabled', false);
+ });
+ }
+ }
+ });
+
+ //listen for continue
+ $('#btn_wiz_cont').off().on('click', function () {
+ begin();
+ $('#wizp1').toggle(false);
+ $('#wizp2').toggle(true);
+ });
+ }
+
+ function init() {
+ colorLength = window.serverConfig.color.channelAdjustment;
+ cobj = window.schema.color.properties.channelAdjustment.items.properties;
+ websAddress = document.location.hostname + ':' + window.serverConfig.webConfig.port;
+ imgAddress = 'http://' + websAddress + '/img/cc/';
+ setStorage("wizardactive", true);
+ }
+
+ function initProfiles() {
+ //check profile count
+ if (colorLength.length > 1) {
+ $('#multi_cali').html('' + $.i18n('wiz_cc_morethanone') + '
');
+ for (let i = 0; i < colorLength.length; i++)
+ $('#wiz_select').append(createSelOpt(i, i + 1 + ' (' + colorLength[i].id + ')'));
+
+ $('#wiz_select').off().on('change', function () {
+ profile = $(this).val();
+ });
+ }
+ }
+
+ function createEditor() {
+ wiz_editor = createJsonEditor('editor_container_wiz', {
+ color: window.schema.color
+ }, true, true);
+
+ $('#editor_container_wiz h4').toggle(false);
+ $('#editor_container_wiz .btn-group').toggle(false);
+ $('#editor_container_wiz [data-schemapath="root.color.imageToLedMappingType"]').toggle(false);
+ $('#editor_container_wiz [data-schemapath="root.color.reducedPixelSetFactorFactor"]').toggle(false);
+ for (let i = 0; i < colorLength.length; i++)
+ $('#editor_container_wiz [data-schemapath*="root.color.channelAdjustment.' + i + '."]').toggle(false);
+ }
+ function updateEditor(el, all) {
+ for (let key in cobj) {
+ if (all === true || el[0] == key || el[1] == key || el[2] == key)
+ $('#editor_container_wiz [data-schemapath*=".' + profile + '.' + key + '"]').toggle(true);
+ else
+ $('#editor_container_wiz [data-schemapath*=".' + profile + '.' + key + '"]').toggle(false);
+ }
+ }
+
+ function stop(reload) {
+ if (withKodi) {
+ sendToKodi("stop");
+ }
+ closeWebSocket();
+ resetWizard(reload);
+ }
+
+ function begin() {
+ step = 0;
+
+ $('#btn_wiz_next').off().on('click', function () {
+ step++;
+ performAction();
+ });
+
+ $('#btn_wiz_back').off().on('click', function () {
+ step--;
+ performAction();
+ });
+
+ $('#btn_wiz_abort').off().on('click', function () {
+ stop(true);
+ });
+
+ $('#btn_wiz_save').off().on('click', function () {
+ requestWriteConfig(wiz_editor.getValue());
+ stop(true);
+ });
+
+ wiz_editor.on("change", function (e) {
+ const val = wiz_editor.getEditor('root.color.channelAdjustment.' + profile + '').getValue();
+ const temp = JSON.parse(JSON.stringify(val));
+ delete temp.leds
+ requestAdjustment(JSON.stringify(temp), "", true);
+ });
+
+ step++
+ performAction();
+ }
+
+ return {
+ start: function () {
+ //create html
+ $('#wiz_header').html('' + $.i18n('wiz_cc_title'));
+ $('#wizp1_body').html('' + $.i18n('wiz_cc_title') + '
' +
+ '' + $.i18n('wiz_cc_intro1') + '
' +
+ '' +
+ '' +
+ ''
+ );
+ $('#wizp1_footer').html('' +
+ ''
+ );
+ $('#wizp2_body').html(''
+ );
+ $('#wizp2_footer').html('' +
+ '' +
+ '' +
+ ''
+ );
+
+ if (getStorage("darkMode") == "on")
+ $('#wizard_logo').prop("src", 'img/hyperion/logo_negativ.png');
+
+ //open modal
+ $("#wizard_modal").modal({
+ backdrop: "static",
+ keyboard: false,
+ show: true
+ });
+
+ setupEventListeners();
+ $('#wiz_cc_kodiip').trigger("change");
+ init();
+ initProfiles();
+ createEditor();
+ }
+ };
+})();
+
+export { colorCalibrationKodiWizard };
diff --git a/assets/webconfig/js/wizards/rgbByteOrderWizard.js b/assets/webconfig/js/wizards/rgbByteOrderWizard.js
new file mode 100644
index 00000000..5d612174
--- /dev/null
+++ b/assets/webconfig/js/wizards/rgbByteOrderWizard.js
@@ -0,0 +1,143 @@
+//****************************
+// Wizard RGB byte order
+//****************************
+
+const rgbByteOrderWizard = (() => {
+
+ let wIntveralId;
+ let new_rgb_order;
+
+ function changeColor() {
+ let color = $("#wiz_canv_color").css('background-color');
+
+ if (color == 'rgb(255, 0, 0)') {
+ $("#wiz_canv_color").css('background-color', 'rgb(0, 255, 0)');
+ requestSetColor('0', '255', '0');
+ }
+ else {
+ $("#wiz_canv_color").css('background-color', 'rgb(255, 0, 0)');
+ requestSetColor('255', '0', '0');
+ }
+ }
+
+
+ function stopWizardRGB(reload) {
+ console.log("stopWizardRGB - reload: ", reload);
+ clearInterval(wIntveralId);
+ resetWizard(reload);
+ }
+
+ function beginWizardRGB() {
+ $("#wiz_switchtime_select").off().on('change', function () {
+ clearInterval(wIntveralId);
+ const time = $("#wiz_switchtime_select").val();
+ wIntveralId = setInterval(function () { changeColor(); }, time * 1000);
+ });
+
+ $('.wselect').on("change", function () {
+ let rgb_order = window.serverConfig.device.colorOrder.split("");
+ const redS = $("#wiz_r_select").val();
+ const greenS = $("#wiz_g_select").val();
+ const blueS = rgb_order.toString().replace(/,/g, "").replace(redS, "").replace(greenS, "");
+
+ for (const color of rgb_order) {
+ if (redS == color)
+ $('#wiz_g_select option[value=' + color + ']').prop('disabled', true);
+ else
+ $('#wiz_g_select option[value=' + color + ']').prop('disabled', false);
+ if (greenS == color)
+ $('#wiz_r_select option[value=' + color + ']').prop('disabled', true);
+ else
+ $('#wiz_r_select option[value=' + color + ']').prop('disabled', false);
+ }
+
+ if (redS != 'null' && greenS != 'null') {
+ $('#btn_wiz_save').prop('disabled', false);
+
+ for (let i = 0; i < rgb_order.length; i++) {
+ if (rgb_order[i] == "r")
+ rgb_order[i] = redS;
+ else if (rgb_order[i] == "g")
+ rgb_order[i] = greenS;
+ else
+ rgb_order[i] = blueS;
+ }
+
+ rgb_order = rgb_order.toString().replace(/,/g, "");
+
+ if (redS == "r" && greenS == "g") {
+ $('#btn_wiz_save').toggle(false);
+ $('#btn_wiz_checkok').toggle(true);
+
+ window.readOnlyMode ? $('#btn_wiz_checkok').prop('disabled', true) : $('#btn_wiz_checkok').prop('disabled', false);
+ }
+ else {
+ $('#btn_wiz_save').toggle(true);
+ window.readOnlyMode ? $('#btn_wiz_save').prop('disabled', true) : $('#btn_wiz_save').prop('disabled', false);
+
+ $('#btn_wiz_checkok').toggle(false);
+ }
+ new_rgb_order = rgb_order;
+ }
+ else
+ $('#btn_wiz_save').prop('disabled', true);
+ });
+
+ $("#wiz_switchtime_select").append(createSelOpt('5', '5'), createSelOpt('10', '10'), createSelOpt('15', '15'), createSelOpt('30', '30'));
+ $("#wiz_switchtime_select").trigger('change');
+
+ $("#wiz_r_select").append(createSelOpt("null", ""), createSelOpt('r', $.i18n('general_col_red')), createSelOpt('g', $.i18n('general_col_green')), createSelOpt('b', $.i18n('general_col_blue')));
+ $("#wiz_g_select").html($("#wiz_r_select").html());
+ $("#wiz_r_select").trigger('change');
+
+ requestSetColor('255', '0', '0');
+ setTimeout(requestSetSource, 100, 'auto');
+ setStorage("wizardactive", true);
+
+ $('#btn_wiz_abort').off().on('click', function () { stopWizardRGB(true); });
+
+ $('#btn_wiz_checkok').off().on('click', function () {
+ showInfoDialog('success', "", $.i18n('infoDialog_wizrgb_text'));
+ stopWizardRGB();
+ });
+
+ $('#btn_wiz_save').off().on('click', function () {
+ stopWizardRGB();
+ window.serverConfig.device.colorOrder = new_rgb_order;
+ requestWriteConfig({ "device": window.serverConfig.device });
+ });
+ }
+
+ return {
+ start: function () {
+ //create html
+ $('#wiz_header').html('' + $.i18n('wiz_rgb_title'));
+ $('#wizp1_body').html('' + $.i18n('wiz_rgb_title') + '
' + $.i18n('wiz_rgb_intro1') + '
' + $.i18n('wiz_rgb_intro2') + '
');
+ $('#wizp1_footer').html('');
+ $('#wizp2_body').html('' + $.i18n('wiz_rgb_expl') + '
');
+ $('#wizp2_body').append('');
+ $('#wizp2_body').append('');
+ $('#wizp2_body').append(' | |
| |
');
+ $('#wizp2_footer').html('');
+
+ if (getStorage("darkMode") == "on")
+ $('#wizard_logo').attr("src", 'img/hyperion/logo_negativ.png');
+
+ //open modal
+ $("#wizard_modal").modal({
+ backdrop: "static",
+ keyboard: false,
+ show: true
+ });
+
+ //listen for continue
+ $('#btn_wiz_cont').off().on('click', function () {
+ beginWizardRGB();
+ $('#wizp1').toggle(false);
+ $('#wizp2').toggle(true);
+ });
+ }
+ };
+})();
+
+export { rgbByteOrderWizard };
From 86d08823a81740f8ab893e4059193b3400e59c13 Mon Sep 17 00:00:00 2001
From: LordGrey <48840279+Lord-Grey@users.noreply.github.com>
Date: Sat, 30 Mar 2024 15:44:18 +0100
Subject: [PATCH 02/17] Add workaround for issue #1692 (#1695)
---
libsrc/webserver/StaticFileServing.cpp | 11 ++++++++++-
1 file changed, 10 insertions(+), 1 deletion(-)
diff --git a/libsrc/webserver/StaticFileServing.cpp b/libsrc/webserver/StaticFileServing.cpp
index 4c550280..4133ce66 100644
--- a/libsrc/webserver/StaticFileServing.cpp
+++ b/libsrc/webserver/StaticFileServing.cpp
@@ -132,7 +132,16 @@ void StaticFileServing::onRequestNeedsReply (QtHttpRequest * request, QtHttpRepl
QMimeType mime = _mimeDb->mimeTypeForFile (file.fileName ());
if (file.open (QFile::ReadOnly)) {
QByteArray data = file.readAll ();
- reply->addHeader ("Content-Type", mime.name ().toLocal8Bit ());
+
+ // Workaround https://bugreports.qt.io/browse/QTBUG-97392
+ if (mime.name() == QStringLiteral("application/x-extension-html"))
+ {
+ reply->addHeader ("Content-Type", "text/html");
+ }
+ else
+ {
+ reply->addHeader ("Content-Type", mime.name().toLocal8Bit());
+ }
reply->addHeader(QtHttpHeader::AccessControlAllow, "*" );
reply->appendRawData (data);
file.close ();
From d5438acbf4e27de61f5f7fe20c2acba793652233 Mon Sep 17 00:00:00 2001
From: LordGrey <48840279+Lord-Grey@users.noreply.github.com>
Date: Tue, 2 Apr 2024 21:44:46 +0200
Subject: [PATCH 03/17] Fix Cross Site Scripting Vulnerability 1 (#1720)
---
libsrc/webserver/StaticFileServing.cpp | 14 +++++++++-----
libsrc/webserver/StaticFileServing.h | 2 +-
2 files changed, 10 insertions(+), 6 deletions(-)
diff --git a/libsrc/webserver/StaticFileServing.cpp b/libsrc/webserver/StaticFileServing.cpp
index 4133ce66..3f3bc5a7 100644
--- a/libsrc/webserver/StaticFileServing.cpp
+++ b/libsrc/webserver/StaticFileServing.cpp
@@ -39,12 +39,15 @@ void StaticFileServing::setBaseUrl(const QString& url)
void StaticFileServing::setSSDPDescription(const QString& desc)
{
if(desc.isEmpty())
+ {
_ssdpDescription.clear();
- else
+ } else
+ {
_ssdpDescription = desc.toLocal8Bit();
+ }
}
-void StaticFileServing::printErrorToReply (QtHttpReply * reply, QtHttpReply::StatusCode code, QString errorMessage)
+void StaticFileServing::printErrorToReply (QtHttpReply * reply, QtHttpReply::StatusCode code, const QString& errorMessage)
{
reply->setStatusCode(code);
reply->addHeader ("Content-Type", QByteArrayLiteral ("text/html"));
@@ -62,13 +65,13 @@ void StaticFileServing::printErrorToReply (QtHttpReply * reply, QtHttpReply::Sta
if (errorPage.open (QFile::ReadOnly))
{
QByteArray data = errorPage.readAll();
- data = data.replace("{MESSAGE}", errorMessage.toLocal8Bit() );
+ data = data.replace("{MESSAGE}", QString(errorMessage.toLocal8Bit()).toHtmlEscaped().toLocal8Bit() );
reply->appendRawData (data);
errorPage.close ();
}
else
{
- reply->appendRawData (QString(QString::number(code) + " - " +errorMessage).toLocal8Bit());
+ reply->appendRawData (QString(QString::number(code) + " - " +errorMessage.toLocal8Bit()).toHtmlEscaped().toLocal8Bit());
}
if (errorPageFooter.open (QFile::ReadOnly))
@@ -103,7 +106,8 @@ void StaticFileServing::onRequestNeedsReply (QtHttpRequest * request, QtHttpRepl
}
return;
}
- else if(uri_parts.at(0) == "description.xml" && !_ssdpDescription.isNull())
+
+ if(uri_parts.at(0) == "description.xml" && !_ssdpDescription.isNull())
{
reply->addHeader ("Content-Type", "text/xml");
reply->appendRawData (_ssdpDescription);
diff --git a/libsrc/webserver/StaticFileServing.h b/libsrc/webserver/StaticFileServing.h
index b328bf16..8a058ea6 100644
--- a/libsrc/webserver/StaticFileServing.h
+++ b/libsrc/webserver/StaticFileServing.h
@@ -37,7 +37,7 @@ private:
Logger * _log;
QByteArray _ssdpDescription;
- void printErrorToReply (QtHttpReply * reply, QtHttpReply::StatusCode code, QString errorMessage);
+ void printErrorToReply (QtHttpReply * reply, QtHttpReply::StatusCode code, const QString& errorMessage);
};
From c2fe42a7314cce3289444204951badfe2d6dd42b Mon Sep 17 00:00:00 2001
From: LordGrey <48840279+Lord-Grey@users.noreply.github.com>
Date: Sat, 13 Apr 2024 22:54:17 +0200
Subject: [PATCH 04/17] Fix #1722 (#1723)
---
src/hyperion-v4l2/hyperion-v4l2.cpp | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/hyperion-v4l2/hyperion-v4l2.cpp b/src/hyperion-v4l2/hyperion-v4l2.cpp
index b166948b..ecc6ed58 100644
--- a/src/hyperion-v4l2/hyperion-v4l2.cpp
+++ b/src/hyperion-v4l2/hyperion-v4l2.cpp
@@ -231,6 +231,7 @@ int main(int argc, char** argv)
ScreenshotHandler handler("screenshot.png", signalDetectionOffset);
QObject::connect(&grabber, SIGNAL(newFrame(Image)), &handler, SLOT(receiveImage(Image)));
+ grabber.prepare();
grabber.start();
QCoreApplication::exec();
grabber.stop();
From aca757138ef50931454b68f92a5ba27a0f8f5d46 Mon Sep 17 00:00:00 2001
From: LordGrey <48840279+Lord-Grey@users.noreply.github.com>
Date: Tue, 16 Apr 2024 21:57:51 +0200
Subject: [PATCH 05/17] Nanoleaf Updates (#1724)
* Add new devices
* Do not restore ExtControl state
---
assets/webconfig/js/content_leds.js | 4 ++++
libsrc/leddevice/dev_net/LedDeviceNanoleaf.cpp | 4 ++--
libsrc/leddevice/dev_net/LedDeviceNanoleaf.h | 6 +++++-
3 files changed, 11 insertions(+), 3 deletions(-)
diff --git a/assets/webconfig/js/content_leds.js b/assets/webconfig/js/content_leds.js
index 938bf8ee..f058450a 100755
--- a/assets/webconfig/js/content_leds.js
+++ b/assets/webconfig/js/content_leds.js
@@ -2510,6 +2510,10 @@ function nanoleafGeneratelayout(panelLayout, panelOrderTopDown, panelOrderLeftRi
18: { name: "LightLinesSingleZone", led: true, sideLengthX: 77, sideLengthY: 77 },
19: { name: "ControllerCap", led: false, sideLengthX: 11, sideLengthY: 11 },
20: { name: "PowerConnector", led: false, sideLengthX: 11, sideLengthY: 11 },
+ 29: { name: "4DLightstrip", led: true, sideLengthX: 50, sideLengthY: 50 },
+ 30: { name: "Skylight Panel", led: true, sideLengthX: 180, sideLengthY: 180 },
+ 31: { name: "SkylightControllerPrimary", led: true, sideLengthX: 180, sideLengthY: 180 },
+ 32: { name: "SkylightControllerPassive", led: true, sideLengthX: 180, sideLengthY: 180 },
999: { name: "Unknown", led: true, sideLengthX: 100, sideLengthY: 100 }
};
diff --git a/libsrc/leddevice/dev_net/LedDeviceNanoleaf.cpp b/libsrc/leddevice/dev_net/LedDeviceNanoleaf.cpp
index e2b8b3fb..d7711a02 100644
--- a/libsrc/leddevice/dev_net/LedDeviceNanoleaf.cpp
+++ b/libsrc/leddevice/dev_net/LedDeviceNanoleaf.cpp
@@ -708,7 +708,7 @@ bool LedDeviceNanoleaf::storeState()
QJsonObject effects = responseEffects.getBody().object();
DebugIf(verbose, _log, "effects: [%s]", QString(QJsonDocument(_originalStateProperties).toJson(QJsonDocument::Compact)).toUtf8().constData());
_originalEffect = effects[API_EFFECT_SELECT].toString();
- _originalIsDynEffect = _originalEffect == "*Dynamic*" || _originalEffect == "*Solid*";
+ _originalIsDynEffect = _originalEffect != "*Dynamic*" || _originalEffect == "*Solid*" || _originalEffect == "*ExtControl*";
}
break;
}
@@ -759,7 +759,7 @@ bool LedDeviceNanoleaf::restoreState()
}
}
else {
- Warning(_log, "%s restoring effect failed with error: Cannot restore dynamic or solid effect. Device is switched off", QSTRING_CSTR(_activeDeviceType));
+ Info(_log, "%s cannot restore dynamic or solid effects. Device is switched off instead", QSTRING_CSTR(_activeDeviceType));
_originalIsOn = false;
}
break;
diff --git a/libsrc/leddevice/dev_net/LedDeviceNanoleaf.h b/libsrc/leddevice/dev_net/LedDeviceNanoleaf.h
index 8aae03ec..dd9353b6 100644
--- a/libsrc/leddevice/dev_net/LedDeviceNanoleaf.h
+++ b/libsrc/leddevice/dev_net/LedDeviceNanoleaf.h
@@ -179,7 +179,11 @@ private:
LIGHT_LINES = 17,
LIGHT_LINES_SINGLZONE = 18,
CONTROLLER_CAP = 19,
- POWER_CONNECTOR = 20
+ POWER_CONNECTOR = 20,
+ NL_4D_LIGHTSTRIP = 29,
+ SKYLIGHT_PANEL = 30,
+ SKYLIGHT_CONTROLLER_PRIMARY = 31,
+ SKYLIGHT_CONTROLLER_PASSIV = 32
};
///
From 7645ebb526c9cb4618015201fbcef23fdf151152 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Tue, 16 Apr 2024 21:58:33 +0200
Subject: [PATCH 06/17] Bump softprops/action-gh-release from 1 to 2 (#1719)
Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 1 to 2.
- [Release notes](https://github.com/softprops/action-gh-release/releases)
- [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md)
- [Commits](https://github.com/softprops/action-gh-release/compare/v1...v2)
---
updated-dependencies:
- dependency-name: softprops/action-gh-release
dependency-type: direct:production
update-type: version-update:semver-major
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
.github/workflows/qt5_6.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/qt5_6.yml b/.github/workflows/qt5_6.yml
index 2af9e875..b8096150 100644
--- a/.github/workflows/qt5_6.yml
+++ b/.github/workflows/qt5_6.yml
@@ -232,7 +232,7 @@ jobs:
path: all-artifacts
- name: 📦 Upload
- uses: softprops/action-gh-release@v1
+ uses: softprops/action-gh-release@v2
with:
name: Hyperion ${{ env.VERSION }}
tag_name: ${{ env.TAG }}
From accf27a09c092ae76d62b639151a1af4c857075d Mon Sep 17 00:00:00 2001
From: Portisch
Date: Tue, 30 Apr 2024 07:28:19 +0200
Subject: [PATCH 07/17] Remove unused libs for Amlogic platform (#1725)
---
src/hyperion-aml/CMakeLists.txt | 4 ----
src/hyperion-framebuffer/CMakeLists.txt | 4 ----
src/hyperion-remote/CMakeLists.txt | 4 ----
src/hyperion-v4l2/CMakeLists.txt | 4 ----
src/hyperiond/CMakeLists.txt | 4 ----
5 files changed, 20 deletions(-)
diff --git a/src/hyperion-aml/CMakeLists.txt b/src/hyperion-aml/CMakeLists.txt
index 40b04df3..fea62f34 100644
--- a/src/hyperion-aml/CMakeLists.txt
+++ b/src/hyperion-aml/CMakeLists.txt
@@ -23,10 +23,6 @@ else()
target_link_libraries(${PROJECT_NAME} ssdp)
endif()
-if(ENABLE_AMLOGIC)
- target_link_libraries(${PROJECT_NAME} pcre16 dl z)
-endif()
-
install (TARGETS ${PROJECT_NAME} DESTINATION "share/hyperion/bin" COMPONENT "hyperion_aml")
if(CMAKE_HOST_UNIX)
diff --git a/src/hyperion-framebuffer/CMakeLists.txt b/src/hyperion-framebuffer/CMakeLists.txt
index a9470f5c..8ac51da4 100644
--- a/src/hyperion-framebuffer/CMakeLists.txt
+++ b/src/hyperion-framebuffer/CMakeLists.txt
@@ -22,10 +22,6 @@ else()
target_link_libraries(${PROJECT_NAME} ssdp)
endif()
-if(ENABLE_AMLOGIC)
- target_link_libraries(${PROJECT_NAME} pcre16 dl z)
-endif()
-
install (TARGETS ${PROJECT_NAME} DESTINATION "share/hyperion/bin" COMPONENT "hyperion_framebuffer")
if(CMAKE_HOST_UNIX)
diff --git a/src/hyperion-remote/CMakeLists.txt b/src/hyperion-remote/CMakeLists.txt
index ef60a7dc..8046fc15 100644
--- a/src/hyperion-remote/CMakeLists.txt
+++ b/src/hyperion-remote/CMakeLists.txt
@@ -21,10 +21,6 @@ target_link_libraries(${PROJECT_NAME}
Qt${QT_VERSION_MAJOR}::Widgets
)
-if(ENABLE_AMLOGIC)
- target_link_libraries(${PROJECT_NAME} pcre16 dl z)
-endif()
-
if(ENABLE_MDNS)
target_link_libraries(${PROJECT_NAME} mdns)
else()
diff --git a/src/hyperion-v4l2/CMakeLists.txt b/src/hyperion-v4l2/CMakeLists.txt
index 37189cdc..92aadb28 100644
--- a/src/hyperion-v4l2/CMakeLists.txt
+++ b/src/hyperion-v4l2/CMakeLists.txt
@@ -22,10 +22,6 @@ else()
target_link_libraries(${PROJECT_NAME} ssdp)
endif()
-if(ENABLE_AMLOGIC)
- target_link_libraries(${PROJECT_NAME} pcre16 dl z)
-endif()
-
install (TARGETS ${PROJECT_NAME} DESTINATION "share/hyperion/bin" COMPONENT "hyperion_v4l2")
if(CMAKE_HOST_UNIX)
diff --git a/src/hyperiond/CMakeLists.txt b/src/hyperiond/CMakeLists.txt
index 79a0b221..8ecbce4c 100644
--- a/src/hyperiond/CMakeLists.txt
+++ b/src/hyperiond/CMakeLists.txt
@@ -71,10 +71,6 @@ if(ENABLE_PROTOBUF_SERVER)
target_link_libraries(${PROJECT_NAME} protoserver)
endif()
-if(ENABLE_AMLOGIC)
- target_link_libraries(${PROJECT_NAME} pcre16 dl z)
-endif(ENABLE_AMLOGIC)
-
if(ENABLE_DISPMANX)
target_link_libraries(${PROJECT_NAME} dispmanx-grabber)
endif (ENABLE_DISPMANX)
From 94850d890a152ce999289bb5b5ec3d44899abbd2 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Thu, 2 May 2024 16:37:45 +0200
Subject: [PATCH 08/17] Bump actions/download-artifact from 4.1.4 to 4.1.7
(#1730)
---
.github/workflows/qt5_6.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/qt5_6.yml b/.github/workflows/qt5_6.yml
index b8096150..9cd59df8 100644
--- a/.github/workflows/qt5_6.yml
+++ b/.github/workflows/qt5_6.yml
@@ -226,7 +226,7 @@ jobs:
echo '::endgroup::'
- name: 💾 Artifact download
- uses: actions/download-artifact@v4.1.4
+ uses: actions/download-artifact@v4.1.7
with:
pattern: artifact-*
path: all-artifacts
From cf287f5adb3ff5ef2a7fc23e1ab94064ed6b24fb Mon Sep 17 00:00:00 2001
From: LordGrey <48840279+Lord-Grey@users.noreply.github.com>
Date: Wed, 8 May 2024 22:06:32 +0200
Subject: [PATCH 09/17] Refactor Hyperion JSON-API (#1727)
---
CHANGELOG.md | 27 +
assets/webconfig/i18n/en.json | 16 +-
assets/webconfig/js/content_index.js | 55 +-
assets/webconfig/js/content_logging.js | 9 +-
assets/webconfig/js/content_network.js | 4 +-
assets/webconfig/js/hyperion.js | 15 +-
assets/webconfig/js/ledsim.js | 4 +-
assets/webconfig/js/ui_utils.js | 6 +-
config/hyperion.config.json.default | 4 +-
.../JSON-API _Commands_Overview.md | 122 +
include/api/API.h | 683 +++--
include/api/JsonAPI.h | 222 +-
include/api/JsonApiCommand.h | 332 +++
include/api/JsonApiSubscription.h | 135 +
include/api/{JsonCB.h => JsonCallbacks.h} | 102 +-
include/api/JsonInfo.h | 43 +
include/api/apiStructs.h | 3 +
include/db/AuthTable.h | 9 +-
include/events/EventEnum.h | 5 +-
include/events/OsEventHandler.h | 2 +
include/grabber/GrabberConfig.h | 58 +
include/hyperion/AuthManager.h | 22 +-
include/hyperion/Hyperion.h | 28 +-
include/hyperion/HyperionIManager.h | 8 +-
include/hyperion/PriorityMuxer.h | 7 +
include/utils/JsonUtils.h | 16 +-
include/utils/NetOrigin.h | 4 +-
include/utils/jsonschema/QJsonFactory.h | 7 +-
libsrc/api/API.cpp | 680 +++--
libsrc/api/CMakeLists.txt | 8 +-
.../api/JSONRPC_schema/schema-adjustment.json | 6 +
libsrc/api/JSONRPC_schema/schema-clear.json | 6 +
.../api/JSONRPC_schema/schema-clearall.json | 6 +
libsrc/api/JSONRPC_schema/schema-color.json | 6 +
.../JSONRPC_schema/schema-componentstate.json | 6 +
libsrc/api/JSONRPC_schema/schema-config.json | 5 +
.../JSONRPC_schema/schema-create-effect.json | 5 +
.../JSONRPC_schema/schema-delete-effect.json | 5 +
libsrc/api/JSONRPC_schema/schema-effect.json | 6 +
libsrc/api/JSONRPC_schema/schema-image.json | 6 +
.../api/JSONRPC_schema/schema-ledcolors.json | 15 +-
.../api/JSONRPC_schema/schema-leddevice.json | 3 +
.../api/JSONRPC_schema/schema-processing.json | 6 +
.../api/JSONRPC_schema/schema-serverinfo.json | 19 +
.../JSONRPC_schema/schema-sourceselect.json | 6 +
libsrc/api/JsonAPI.cpp | 2610 +++++++----------
libsrc/api/JsonCB.cpp | 416 ---
libsrc/api/JsonCallbacks.cpp | 459 +++
libsrc/api/JsonInfo.cpp | 620 ++++
libsrc/effectengine/EffectFileHandler.cpp | 8 +-
libsrc/events/EventHandler.cpp | 8 +-
libsrc/events/OsEventHandler.cpp | 8 +-
libsrc/hyperion/AuthManager.cpp | 29 +-
libsrc/hyperion/Hyperion.cpp | 5 +
libsrc/hyperion/HyperionIManager.cpp | 5 +
libsrc/hyperion/PriorityMuxer.cpp | 5 +
libsrc/hyperion/SettingsManager.cpp | 2 +-
libsrc/hyperion/schema/schema-network.json | 29 +-
libsrc/leddevice/LedDeviceWrapper.cpp | 10 +-
libsrc/utils/JsonUtils.cpp | 98 +-
libsrc/utils/NetOrigin.cpp | 97 +-
src/hyperion-remote/JsonConnection.cpp | 6 +-
src/hyperion-v4l2/hyperion-v4l2.cpp | 2 +-
src/hyperiond/systray.cpp | 6 +-
64 files changed, 4203 insertions(+), 2962 deletions(-)
create mode 100644 doc/development/JSON-API _Commands_Overview.md
create mode 100644 include/api/JsonApiCommand.h
create mode 100644 include/api/JsonApiSubscription.h
rename include/api/{JsonCB.h => JsonCallbacks.h} (53%)
create mode 100644 include/api/JsonInfo.h
create mode 100644 include/grabber/GrabberConfig.h
delete mode 100644 libsrc/api/JsonCB.cpp
create mode 100644 libsrc/api/JsonCallbacks.cpp
create mode 100644 libsrc/api/JsonInfo.cpp
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7ca3c166..75577551 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,14 +8,41 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Breaking
+**JSON-API**
+- Align JSON subscription update elements. `ledcolors-imagestream-update, ledcolors-ledstream-update, logmsg-update` now return data via `data` and not `result
+
### Added
- Support gaps on Matrix Layout (#1696)
+**JSON-API**
+- New subscription support for event updates, i.e. `Suspend, Resume, Idle, idleResume, Restart, Quit`.
+- Support direct or multiple instance addressing via single requests (#809)
+- Support of `serverinfo` subcommands: `getInfo, subscribe, unsubscribe, getSubscriptions, getSubscriptionCommands`
+- [Overview](https://github.com/hyperion-project/hyperion.ng/blob/API_Auth/doc/development/JSON-API%20_Commands_Overview.md) of API commands and subscription updates
+
### Changed
+- Fixed: Cross Site Scripting Vulnerability (CVE-2024-4174, CVE-2024-4175)
+- Fixed: hyperion-v4l2 taking screenshot failed (#1722)
+- Nanoleaf: Support new devices and do not restore ExtControl state
+- Workaround to address Web UI keeps forcing browser to download the html instead (#1692)
+- Fixed: Kodi Color Calibration, Refactor Wizards (#1674)
+- Fixed: Token Dialog not closing
+
+**JSON-API**
+- Refactored JSON-API to ensure consistent authorization behaviour across sessions and single requests with token authorization.
+- Provide additional error details with API responses, esp. on JSON parsing, validation or token errors.
+- Generate random TANs for every API request from the Hyperion UI
+- Fixed: Handling of IP4 addresses wrapped in IPv6 for external network connections-
+
### Removed
+**JSON-API**
+- Removed ability to enable/disable local admin authorization. All admin commands require authorization, i.e. `authorize-adminRequired` will always be `true`.
+- Removed `session-updates` subscription
+- `serverinfo/subscribe` element will be deprecated and replaced by corresponding subcommand
+
## [2.0.16](https://github.com/hyperion-project/hyperion.ng/releases/tag/2.0.16) - 2024-01
### Added
diff --git a/assets/webconfig/i18n/en.json b/assets/webconfig/i18n/en.json
index 68344d73..0d638c53 100644
--- a/assets/webconfig/i18n/en.json
+++ b/assets/webconfig/i18n/en.json
@@ -186,6 +186,7 @@
"conf_network_json_intro": "The JSON-RPC-Port of all Hyperion instances, used for remote control.",
"conf_network_net_intro": "Network related settings which are applied to all network services.",
"conf_network_proto_intro": "The PROTO-Port of all Hyperion instances, used for picture streams (HyperionScreenCap, Kodi Addon, Android Hyperion Grabber, ...)",
+ "conf_network_tok_idhead": "ID",
"conf_network_tok_cidhead": "Description",
"conf_network_tok_comment_title": "Token description",
"conf_network_tok_desc": "Tokens grant other applications access to the Hyperion API, an application can request a token where you need to accept it or you create them on your own below. These tokens are just required when \"API Authorization\" is enabled in network settings.",
@@ -500,19 +501,16 @@
"edt_conf_log_level_expl": "Depending on loglevel you see less or more messages in your log.",
"edt_conf_log_level_title": "Log-Level",
"edt_conf_net_apiAuth_expl": "Enforce all applications that use the Hyperion API to authenticate themself against Hyperion (Exception: see \"Local API Authentication\"). Higher security, as you control the access and revoke it at any time.",
- "edt_conf_net_apiAuth_title": "API Authentication",
"edt_conf_net_heading_title": "Network",
- "edt_conf_net_internetAccessAPI_expl": "Allow access to the Hyperion API/Webinterface from the internet. Disable for higher security.",
+ "edt_conf_net_internetAccessAPI_expl": "Allow access to the Hyperion API/Web Interface from the Internet. Disable for increased security.",
"edt_conf_net_internetAccessAPI_title": "Internet API Access",
- "edt_conf_net_ipWhitelist_expl": "You can whitelist IP addresses instead allowing all connections from internet to connect to the Hyperion API/Webinterface.",
- "edt_conf_net_ipWhitelist_title": "Whitelisted IP's",
+ "edt_conf_net_ipWhitelist_expl": "Define whitelisted IP addresses from which API requests from the Internet are allowed. All other external connections will be denied.",
+ "edt_conf_net_ipWhitelist_title": "Whitelisted IP addresses",
"edt_conf_net_ip_itemtitle": "IP",
- "edt_conf_net_localAdminAuth_expl": "When enabled, administration access from your local network needs a password.",
- "edt_conf_net_localAdminAuth_title": "Local Admin API Authentication",
- "edt_conf_net_localApiAuth_expl": "When enabled, connections from your home network needs to authenticate themselves against Hyperion with a token.",
+ "edt_conf_net_localApiAuth_expl": "When disabled, API authorisation via password or token is not required for local connections. The exception is administrative commands.",
"edt_conf_net_localApiAuth_title": "Local API Authentication",
- "edt_conf_net_restirctedInternetAccessAPI_expl": "You can restrict the access to the API through the internet to certain IP's.",
- "edt_conf_net_restirctedInternetAccessAPI_title": "Restrict to IP's",
+ "edt_conf_net_restirctedInternetAccessAPI_expl": "You can restrict API requests over the Internet to only those IP addresses on the whitelist.",
+ "edt_conf_net_restirctedInternetAccessAPI_title": "Restrict to IP addresses",
"edt_conf_os_events_lockEnable_title": "Listen to lock events",
"edt_conf_os_events_lockEnable_expl": "Listen to screen lock/unlock events",
"edt_conf_os_events_suspendEnable_title": "Listen to suspend events",
diff --git a/assets/webconfig/js/content_index.js b/assets/webconfig/js/content_index.js
index e8ec28a8..6609b3bd 100644
--- a/assets/webconfig/js/content_index.js
+++ b/assets/webconfig/js/content_index.js
@@ -73,26 +73,30 @@ $(document).ready(function () {
//End language selection
$(window.hyperion).on("cmd-authorize-tokenRequest cmd-authorize-getPendingTokenRequests", function (event) {
- var val = event.response.info;
- if (Array.isArray(event.response.info)) {
- if (event.response.info.length == 0) {
- return
- }
- val = event.response.info[0]
- if (val.comment == '')
- $('#modal_dialog').modal('hide');
- }
- showInfoDialog("grantToken", $.i18n('conf_network_tok_grantT'), $.i18n('conf_network_tok_grantMsg') + '
App: ' + val.comment + '
Code: ' + val.id + '')
- $("#tok_grant_acc").off().on('click', function () {
- tokenList.push(val)
- // forward event, in case we need to rebuild the list now
- $(window.hyperion).trigger({ type: "build-token-list" });
- requestHandleTokenRequest(val.id, true)
- });
- $("#tok_deny_acc").off().on('click', function () {
- requestHandleTokenRequest(val.id, false)
- });
+ if (event.response && event.response.info !== undefined) {
+ var val = event.response.info;
+
+ if (Array.isArray(event.response.info)) {
+ if (event.response.info.length == 0) {
+ return
+ }
+ val = event.response.info[0]
+ if (val.comment == '')
+ $('#modal_dialog').modal('hide');
+ }
+
+ showInfoDialog("grantToken", $.i18n('conf_network_tok_grantT'), $.i18n('conf_network_tok_grantMsg') + '
App: ' + val.comment + '
Code: ' + val.id + '')
+ $("#tok_grant_acc").off().on('click', function () {
+ tokenList.push(val)
+ // forward event, in case we need to rebuild the list now
+ $(window.hyperion).trigger({ type: "build-token-list" });
+ requestHandleTokenRequest(val.id, true)
+ });
+ $("#tok_deny_acc").off().on('click', function () {
+ requestHandleTokenRequest(val.id, false)
+ });
+ }
});
$(window.hyperion).one("cmd-authorize-getTokenList", function (event) {
@@ -186,21 +190,12 @@ $(document).ready(function () {
}
});
- $(window.hyperion).on("cmd-authorize-adminRequired", function (event) {
- //Check if a admin login is required.
- //If yes: check if default pw is set. If no: go ahead to get server config and render page
- if (event.response.info.adminRequired === true)
- requestRequiresDefaultPasswortChange();
- else
- requestServerConfigSchema();
- });
-
$(window.hyperion).on("error", function (event) {
//If we are getting an error "No Authorization" back with a set loginToken we will forward to new Login (Token is expired.
//e.g.: hyperiond was started new in the meantime)
if (event.reason == "No Authorization" && getStorage("loginToken")) {
removeStorage("loginToken");
- requestRequiresAdminAuth();
+ requestRequiresDefaultPasswortChange();
}
else if (event.reason == "Selected Hyperion instance isn't running") {
//Switch to default instance
@@ -211,7 +206,7 @@ $(document).ready(function () {
});
$(window.hyperion).on("open", function (event) {
- requestRequiresAdminAuth();
+ requestRequiresDefaultPasswortChange();
});
$(window.hyperion).on("ready", function (event) {
diff --git a/assets/webconfig/js/content_logging.js b/assets/webconfig/js/content_logging.js
index 7a9c791d..a8d5fa36 100644
--- a/assets/webconfig/js/content_logging.js
+++ b/assets/webconfig/js/content_logging.js
@@ -3,10 +3,13 @@ var createdCont = false;
var isScroll = true;
performTranslation();
-requestLoggingStop();
$(document).ready(function () {
+ window.addEventListener('hashchange', function(event) {
+ requestLoggingStop();
+ });
+
requestLoggingStart();
$('#conf_cont').append(createOptPanel('fa-reorder', $.i18n("edt_conf_log_heading_title"), 'editor_container', 'btn_submit'));
@@ -178,9 +181,9 @@ $(document).ready(function () {
if (!window.loggingHandlerInstalled) {
window.loggingHandlerInstalled = true;
- $(window.hyperion).on("cmd-logging-update", function (event) {
+ $(window.hyperion).on("cmd-logmsg-update", function (event) {
- var messages = (event.response.result.messages);
+ var messages = (event.response.data.messages);
if (messages.length != 0) {
if (!createdCont) {
diff --git a/assets/webconfig/js/content_network.js b/assets/webconfig/js/content_network.js
index 26fec2c8..9f9f68c3 100644
--- a/assets/webconfig/js/content_network.js
+++ b/assets/webconfig/js/content_network.js
@@ -213,13 +213,13 @@ $(document).ready(function () {
for (var key in tokenList) {
var lastUse = (tokenList[key].last_use) ? tokenList[key].last_use : "-";
var btn = '';
- $('.tktbody').append(createTableRow([tokenList[key].comment, lastUse, btn], false, true));
+ $('.tktbody').append(createTableRow([tokenList[key].id, tokenList[key].comment, lastUse, btn], false, true));
$('#tok' + tokenList[key].id).off().on('click', handleDeleteToken);
}
}
createTable('tkthead', 'tktbody', 'tktable');
- $('.tkthead').html(createTableRow([$.i18n('conf_network_tok_cidhead'), $.i18n('conf_network_tok_lastuse'), $.i18n('general_btn_delete')], true, true));
+ $('.tkthead').html(createTableRow([$.i18n('conf_network_tok_idhead'), $.i18n('conf_network_tok_cidhead'), $.i18n('conf_network_tok_lastuse'), $.i18n('general_btn_delete')], true, true));
buildTokenList();
function handleDeleteToken(e) {
diff --git a/assets/webconfig/js/hyperion.js b/assets/webconfig/js/hyperion.js
index 7bd59c65..8153e827 100644
--- a/assets/webconfig/js/hyperion.js
+++ b/assets/webconfig/js/hyperion.js
@@ -177,6 +177,7 @@ function sendToHyperion(command, subcommand, msg)
else
msg = "";
+ window.wsTan = Math.floor(Math.random() * 1000)
window.websocket.send('{"command":"'+command+'", "tan":'+window.wsTan+subcommand+msg+'}');
}
@@ -187,7 +188,7 @@ function sendToHyperion(command, subcommand, msg)
// data: The json data as Object
// tan: The optional tan, default 1. If the tan is -1, we skip global response error handling
// Returns data of response or false if timeout
-async function sendAsyncToHyperion (command, subcommand, data, tan = 1) {
+async function sendAsyncToHyperion (command, subcommand, data, tan = Math.floor(Math.random() * 1000) ) {
let obj = { command, tan }
if (subcommand) {Object.assign(obj, {subcommand})}
if (data) { Object.assign(obj, data) }
@@ -486,38 +487,38 @@ async function requestLedDeviceDiscovery(type, params)
{
let data = { ledDeviceType: type, params: params };
- return sendAsyncToHyperion("leddevice", "discover", data, Math.floor(Math.random() * 1000) );
+ return sendAsyncToHyperion("leddevice", "discover", data);
}
async function requestLedDeviceProperties(type, params)
{
let data = { ledDeviceType: type, params: params };
- return sendAsyncToHyperion("leddevice", "getProperties", data, Math.floor(Math.random() * 1000));
+ return sendAsyncToHyperion("leddevice", "getProperties", data);
}
function requestLedDeviceIdentification(type, params)
{
let data = { ledDeviceType: type, params: params };
- return sendAsyncToHyperion("leddevice", "identify", data, Math.floor(Math.random() * 1000));
+ return sendAsyncToHyperion("leddevice", "identify", data);
}
async function requestLedDeviceAddAuthorization(type, params) {
let data = { ledDeviceType: type, params: params };
- return sendAsyncToHyperion("leddevice", "addAuthorization", data, Math.floor(Math.random() * 1000));
+ return sendAsyncToHyperion("leddevice", "addAuthorization", data);
}
async function requestInputSourcesDiscovery(type, params) {
let data = { sourceType: type, params: params };
- return sendAsyncToHyperion("inputsource", "discover", data, Math.floor(Math.random() * 1000));
+ return sendAsyncToHyperion("inputsource", "discover", data);
}
async function requestServiceDiscovery(type, params) {
let data = { serviceType: type, params: params };
- return sendAsyncToHyperion("service", "discover", data, Math.floor(Math.random() * 1000));
+ return sendAsyncToHyperion("service", "discover", data);
}
diff --git a/assets/webconfig/js/ledsim.js b/assets/webconfig/js/ledsim.js
index 32a8898b..af633ba7 100644
--- a/assets/webconfig/js/ledsim.js
+++ b/assets/webconfig/js/ledsim.js
@@ -261,7 +261,7 @@ $(document).ready(function () {
$("body").get(0).style.setProperty("--background-var", "none");
}
else {
- printLedsToCanvas(event.response.result.leds)
+ printLedsToCanvas(event.response.data.leds)
$("body").get(0).style.setProperty("--background-var", "url(" + ($('#leds_preview_canv')[0]).toDataURL("image/jpg") + ") no-repeat top left");
}
});
@@ -275,7 +275,7 @@ $(document).ready(function () {
}
}
else {
- var imageData = (event.response.result.image);
+ var imageData = (event.response.data.image);
var image = new Image();
image.onload = function () {
diff --git a/assets/webconfig/js/ui_utils.js b/assets/webconfig/js/ui_utils.js
index 4cbb7967..4bfeb85e 100644
--- a/assets/webconfig/js/ui_utils.js
+++ b/assets/webconfig/js/ui_utils.js
@@ -319,9 +319,9 @@ function showInfoDialog(type, header, message) {
});
$(document).on('click', '[data-dismiss-modal]', function () {
- var target = $(this).attr('data-dismiss-modal');
- $.find(target).modal('hide');
- });
+ var target = $(this).data('dismiss-modal');
+ $($.find(target)).modal('hide');
+});
}
function createHintH(type, text, container) {
diff --git a/config/hyperion.config.json.default b/config/hyperion.config.json.default
index 18fc9dc9..f126b638 100644
--- a/config/hyperion.config.json.default
+++ b/config/hyperion.config.json.default
@@ -220,9 +220,7 @@
"internetAccessAPI": false,
"restirctedInternetAccessAPI": false,
"ipWhitelist": [],
- "apiAuth": true,
- "localApiAuth": false,
- "localAdminAuth": true
+ "localApiAuth": false
},
"ledConfig": {
diff --git a/doc/development/JSON-API _Commands_Overview.md b/doc/development/JSON-API _Commands_Overview.md
new file mode 100644
index 00000000..549c5e62
--- /dev/null
+++ b/doc/development/JSON-API _Commands_Overview.md
@@ -0,0 +1,122 @@
+# JSON-API Commands Overview
+
+## Commands & Sub-Commands
+
+List of commands and related sub-commands which can be used via JSON-API requests.
+
+_Authorization (via password or bearer token)_
+
+**No** - No authorization required
+**Yes** - Authorization required, but can be disabled for local network calls
+**Admin**: Authorization is always required
+
+_Instance specific_
+
+**Yes** - A specific instance can be addressed
+**Multi** - Multiple instances can be addressed via one request
+**No** - The command is not instance related
+
+_http/s Support_
+
+**Yes** - Command can be used by individual http/s requests
+**No** - Applies only to WebSocket or http/s sessions
+
+| Command | Sub-Command | Authorization | Instance specific | http/s Support |
+|:---------------|:------------------------|:--------------|:------------------|:---------------|
+| adjustment | - | Yes | Multi | Yes |
+| authorize | adminRequired | No | No | Yes |
+| authorize | answerRequest | Admin | No | No |
+| authorize | createToken | Admin | No | No |
+| authorize | deleteToken | Admin | No | Yes |
+| authorize | getPendingTokenRequests | Admin | No | No |
+| authorize | getTokenList | Admin | No | Yes |
+| authorize | login | No | No | No |
+| authorize | logout | No | No | No |
+| authorize | newPassword | Admin | No | Yes |
+| authorize | newPasswordRequired | No | No | Yes |
+| authorize | renameToken | Admin | No | Yes |
+| authorize | requestToken | No | No | Yes |
+| authorize | tokenRequired | No | No | Yes |
+| clear | - | Yes | Multi | Yes |
+| clearall | - | Yes | Multi | Yes |
+| color | - | Yes | Multi | Yes |
+| componentstate | - | Yes | Multi | Yes |
+| config | getconfig | Admin | Yes | Yes |
+| config | getschema | Admin | Yes | Yes |
+| config | reload | Admin | Yes | Yes |
+| config | restoreconfig | Admin | Yes | Yes |
+| config | setconfig | Admin | Yes | Yes |
+| correction | - | Yes | Yes | Yes |
+| create-effect | - | Yes | Yes | Yes |
+| delete-effect | - | Yes | Yes | Yes |
+| effect | - | Yes | Multi | Yes |
+| image | - | Yes | Multi | Yes |
+| inputsource | discover | Yes | No | Yes |
+| inputsource | getProperties | Yes | No | Yes |
+| instance | createInstance | Admin | No | Yes |
+| instance | deleteInstance | Admin | No | Yes |
+| instance | saveName | Admin | No | Yes |
+| instance | startInstance | Yes | No | Yes |
+| instance | stopInstance | Yes | No | Yes |
+| instance | switchTo | Yes | No | Yes |
+| ledcolors | imagestream-start | Yes | Yes | Yes |
+| ledcolors | imagestream-stop | Yes | Yes | Yes |
+| ledcolors | ledstream-start | Yes | Yes | Yes |
+| ledcolors | ledstream-stop | Yes | Yes | Yes |
+| leddevice | addAuthorization | Yes | Yes | Yes |
+| leddevice | discover | Yes | Yes | Yes |
+| leddevice | getProperties | Yes | Yes | Yes |
+| leddevice | identify | Yes | Yes | Yes |
+| logging | start | Yes | No | Yes |
+| logging | stop | Yes | No | Yes |
+| processing | - | Yes | Multi | Yes |
+| serverinfo | - | Yes | Yes | Yes |
+| serverinfo | getInfo | Yes | Yes | Yes |
+| serverinfo | subscribe | Yes | Yes | No |
+| serverinfo | unsubscribe | Yes | Yes | No |
+| serverinfo | getSubscriptions | Yes | Yes | No |
+| serverinfo | getSubscriptionCommands | No | No | No |
+| service | discover | Yes | No | Yes |
+| sourceselect | - | Yes | Multi | Yes |
+| sysinfo | - | Yes | No | Yes |
+| system | restart | Yes | No | Yes |
+| system | resume | Yes | No | Yes |
+| system | suspend | Yes | No | Yes |
+| system | toggleSuspend | Yes | No | Yes |
+| system | idle | Yes | No | Yes |
+| system | toggleIdle | Yes | No | Yes |
+| temperature | - | Yes | Yes | Yes |
+| transform | - | Yes | Yes | Yes |
+| videomode | - | Yes | No | Yes |
+
+## Subscription updates
+
+List of updates which can be subscribed to via the `serverinfo/subscribe`request.
+
+_Instance specific_
+
+**Yes** - A specific instance can be addressed
+**No** - The command is not instance related
+
+_in "all"_
+
+**Yes** - Updates are subscribed using "all" as the command
+**No** - Subscription is only triggered via JSON-API request
+
+| Subscription Command | Instance specific | in "all" |
+|:-----------------------------|:------------------|:---------|
+| adjustment-update | Yes | Yes |
+| components-update | Yes | Yes |
+| effects-update | Yes | Yes |
+| event-update | No | Yes |
+| imageToLedMapping-update | Yes | Yes |
+| instance-update | Yes | Yes |
+| ledcolors-imagestream-update | Yes | No |
+| ledcolors-ledstream-update | Yes | No |
+| leds-update | Yes | Yes |
+| logmsg-update | No | No |
+| priorities-update | Yes | Yes |
+| settings-update | Yes | Yes |
+| token-update | No | Yes |
+| videomode-update | No | Yes |
+
diff --git a/include/api/API.h b/include/api/API.h
index 31a60f97..96e1f9b8 100644
--- a/include/api/API.h
+++ b/include/api/API.h
@@ -14,11 +14,15 @@
// qt includes
#include
-class QTimer;
-class JsonCB;
+class JsonCallbacks;
class HyperionIManager;
-const QString NO_AUTH = "No Authorization";
+// Constants
+namespace {
+
+const char NO_AUTHORIZATION[] = "No Authorization";;
+
+}
///
/// @brief API for Hyperion to be inherted from a child class with specific protocol implementations
@@ -31,205 +35,214 @@ const QString NO_AUTH = "No Authorization";
class API : public QObject
{
- Q_OBJECT
+ Q_OBJECT
public:
#include
- // workaround Q_ARG std::map template issues
- typedef std::map MapRegister;
- typedef QMap MapAuthDefs;
- ///
- /// Constructor
- ///
- ///@ param The parent Logger
- /// @param localConnection Is this a local network connection? Use utils/NetOrigin to check that
- /// @param parent Parent QObject
- ///
- API(Logger *log, bool localConnection, QObject *parent);
-
-protected:
- ///
- /// @brief Initialize the API
- /// This call is REQUIRED!
- ///
- void init();
-
- ///
- /// @brief Set a single color
- /// @param[in] priority The priority of the written color
- /// @param[in] ledColor The color to write to the leds
- /// @param[in] timeout_ms The time the leds are set to the given color [ms]
- /// @param[in] origin The setter
- ///
- void setColor(int priority, const std::vector &ledColors, int timeout_ms = -1, const QString &origin = "API", hyperion::Components callerComp = hyperion::COMP_INVALID);
-
- ///
- /// @brief Set a image
- /// @param[in] data The command data
- /// @param[in] comp The component that should be used
- /// @param[out] replyMsg The replyMsg on failure
- /// @param callerComp The HYPERION COMPONENT that calls this function! e.g. PROT/FLATBUF
- /// @return True on success
- ///
- bool setImage(ImageCmdData &data, hyperion::Components comp, QString &replyMsg, hyperion::Components callerComp = hyperion::COMP_INVALID);
-
- ///
- /// @brief Clear a priority in the Muxer, if -1 all priorities are cleared
- /// @param priority The priority to clear
- /// @param replyMsg the message on failure
- /// @param callerComp The HYPERION COMPONENT that calls this function! e.g. PROT/FLATBUF
- /// @return True on success
- ///
- bool clearPriority(int priority, QString &replyMsg, hyperion::Components callerComp = hyperion::COMP_INVALID);
-
- ///
- /// @brief Set a new component state
- /// @param comp The component name
- /// @param compState The new state of the comp
- /// @param replyMsg The reply on failure
- /// @param callerComp The HYPERION COMPONENT that calls this function! e.g. PROT/FLATBUF
- /// @ return True on success
- ///
- bool setComponentState(const QString &comp, bool &compState, QString &replyMsg, hyperion::Components callerComp = hyperion::COMP_INVALID);
-
- ///
- /// @brief Set a ledToImageMapping type
- /// @param type mapping type string
- /// @param callerComp The HYPERION COMPONENT that calls this function! e.g. PROT/FLATBUF
- ///
- void setLedMappingType(int type, hyperion::Components callerComp = hyperion::COMP_INVALID);
-
- ///
- /// @brief Set the 2D/3D modes type
- /// @param mode The VideoMode
- /// @param callerComp The HYPERION COMPONENT that calls this function! e.g. PROT/FLATBUF
- ///
- void setVideoMode(VideoMode mode, hyperion::Components callerComp = hyperion::COMP_INVALID);
-
-#if defined(ENABLE_EFFECTENGINE)
- ///
- /// @brief Set an effect
- /// @param dat The effect data
- /// @param callerComp The HYPERION COMPONENT that calls this function! e.g. PROT/FLATBUF
- /// REQUIRED dat fields: effectName, priority, duration, origin
- /// @return True on success else false
- ///
- bool setEffect(const EffectCmdData &dat, hyperion::Components callerComp = hyperion::COMP_INVALID);
-#endif
-
- ///
- /// @brief Set source auto select enabled or disabled
- /// @param sate The new state
- /// @param callerComp The HYPERION COMPONENT that calls this function! e.g. PROT/FLATBUF
- ///
- void setSourceAutoSelect(bool state, hyperion::Components callerComp = hyperion::COMP_INVALID);
-
- ///
- /// @brief Set the visible priority to given priority
- /// @param priority The priority to set
- /// @param callerComp The HYPERION COMPONENT that calls this function! e.g. PROT/FLATBUF
- ///
- void setVisiblePriority(int priority, hyperion::Components callerComp = hyperion::COMP_INVALID);
-
- ///
- /// @brief Register a input or update the meta data of a previous register call
- /// ATTENTION: Check unregisterInput() method description !!!
- /// @param[in] priority The priority of the channel
- /// @param[in] component The component of the channel
- /// @param[in] origin Who set the channel (CustomString@IP)
- /// @param[in] owner Specific owner string, might be empty
- /// @param[in] callerComp The component that call this (e.g. PROTO/FLAT)
- ///
- void registerInput(int priority, hyperion::Components component, const QString &origin, const QString &owner, hyperion::Components callerComp);
-
- ///
- /// @brief Revoke a registerInput() call by priority. We maintain all registered priorities in this scope
- /// ATTENTION: This is MANDATORY if you change (priority change) or stop(clear/timeout) DURING lifetime. If this class destructs it's not needed
- /// @param priority The priority to unregister
- ///
- void unregisterInput(int priority);
-
- ///
- /// @brief Handle the instance switching
- /// @param inst The requested instance
- /// @return True on success else false
- ///
- bool setHyperionInstance(quint8 inst);
+ // workaround Q_ARG std::map template issues
+ typedef std::map MapRegister;
+ typedef QMap MapAuthDefs;
///
- /// @brief Check if Hyperion ist enabled
- /// @return True when enabled else false
- ///
- bool isHyperionEnabled();
+ /// Constructor
+ ///
+ ///@ param The parent Logger
+ /// @param localConnection Is this a local network connection? Use utils/NetOrigin to check that
+ /// @param parent Parent QObject
+ ///
+ API(Logger *log, bool localConnection, QObject *parent);
- ///
- /// @brief Get all instances data
- /// @return The instance data
- ///
- QVector getAllInstanceData();
+protected:
+ ///
+ /// @brief Initialize the API
+ /// This call is REQUIRED!
+ ///
+ void init();
- ///
- /// @brief Start instance
- /// @param index The instance index
- /// @param tan The tan
- /// @return True on success else false
- ///
- bool startInstance(quint8 index, int tan = 0);
+ virtual void stopDataConnections() = 0;
- ///
- /// @brief Stop instance
- /// @param index The instance index
- ///
- void stopInstance(quint8 index);
+ ///
+ /// @brief Set a single color
+ /// @param[in] priority The priority of the written color
+ /// @param[in] ledColor The color to write to the leds
+ /// @param[in] timeout_ms The time the leds are set to the given color [ms]
+ /// @param[in] origin The setter
+ ///
+ void setColor(int priority, const std::vector &ledColors, int timeout_ms = -1, const QString &origin = "API", hyperion::Components callerComp = hyperion::COMP_INVALID);
- //////////////////////////////////
- /// AUTH / ADMINISTRATION METHODS
- //////////////////////////////////
+ ///
+ /// @brief Set a image
+ /// @param[in] data The command data
+ /// @param[in] comp The component that should be used
+ /// @param[out] replyMsg The replyMsg on failure
+ /// @param callerComp The HYPERION COMPONENT that calls this function! e.g. PROT/FLATBUF
+ /// @return True on success
+ ///
+ bool setImage(ImageCmdData &data, hyperion::Components comp, QString &replyMsg, hyperion::Components callerComp = hyperion::COMP_INVALID);
- ///
- /// @brief Delete instance. Requires ADMIN ACCESS
- /// @param index The instance index
- /// @param replyMsg The reply Msg
- /// @return False with reply
- ///
- bool deleteInstance(quint8 index, QString &replyMsg);
+ ///
+ /// @brief Clear a priority in the Muxer, if -1 all priorities are cleared
+ /// @param priority The priority to clear
+ /// @param replyMsg the message on failure
+ /// @param callerComp The HYPERION COMPONENT that calls this function! e.g. PROT/FLATBUF
+ /// @return True on success
+ ///
+ bool clearPriority(int priority, QString &replyMsg, hyperion::Components callerComp = hyperion::COMP_INVALID);
- ///
- /// @brief Create instance. Requires ADMIN ACCESS
- /// @param name With given name
- /// @return False with reply
- ///
- QString createInstance(const QString &name);
+ ///
+ /// @brief Set a new component state
+ /// @param comp The component name
+ /// @param compState The new state of the comp
+ /// @param replyMsg The reply on failure
+ /// @param callerComp The HYPERION COMPONENT that calls this function! e.g. PROT/FLATBUF
+ /// @ return True on success
+ ///
+ bool setComponentState(const QString &comp, bool &compState, QString &replyMsg, hyperion::Components callerComp = hyperion::COMP_INVALID);
- ///
- /// @brief Rename an instance. Requires ADMIN ACCESS
- /// @param index The instance index
- /// @param name With given name
- /// @return False with reply
- ///
- QString setInstanceName(quint8 index, const QString &name);
+ ///
+ /// @brief Set a ledToImageMapping type
+ /// @param type mapping type string
+ /// @param callerComp The HYPERION COMPONENT that calls this function! e.g. PROT/FLATBUF
+ ///
+ void setLedMappingType(int type, hyperion::Components callerComp = hyperion::COMP_INVALID);
+
+ ///
+ /// @brief Set the 2D/3D modes type
+ /// @param mode The VideoMode
+ /// @param callerComp The HYPERION COMPONENT that calls this function! e.g. PROT/FLATBUF
+ ///
+ void setVideoMode(VideoMode mode, hyperion::Components callerComp = hyperion::COMP_INVALID);
#if defined(ENABLE_EFFECTENGINE)
- ///
- /// @brief Delete an effect. Requires ADMIN ACCESS
- /// @param name The effect name
- /// @return True on success else false
- ///
- QString deleteEffect(const QString &name);
-
- ///
- /// @brief Delete an effect. Requires ADMIN ACCESS
- /// @param name The effect name
- /// @return True on success else false
- ///
- QString saveEffect(const QJsonObject &data);
+ ///
+ /// @brief Set an effect
+ /// @param dat The effect data
+ /// @param callerComp The HYPERION COMPONENT that calls this function! e.g. PROT/FLATBUF
+ /// REQUIRED dat fields: effectName, priority, duration, origin
+ /// @return True on success else false
+ ///
+ bool setEffect(const EffectCmdData &dat, hyperion::Components callerComp = hyperion::COMP_INVALID);
#endif
- ///
- /// @brief Save settings object. Requires ADMIN ACCESS
- /// @param data The data object
- ///
+ ///
+ /// @brief Set source auto select enabled or disabled
+ /// @param sate The new state
+ /// @param callerComp The HYPERION COMPONENT that calls this function! e.g. PROT/FLATBUF
+ ///
+ void setSourceAutoSelect(bool state, hyperion::Components callerComp = hyperion::COMP_INVALID);
+
+ ///
+ /// @brief Set the visible priority to given priority
+ /// @param priority The priority to set
+ /// @param callerComp The HYPERION COMPONENT that calls this function! e.g. PROT/FLATBUF
+ ///
+ void setVisiblePriority(int priority, hyperion::Components callerComp = hyperion::COMP_INVALID);
+
+ ///
+ /// @brief Register a input or update the meta data of a previous register call
+ /// ATTENTION: Check unregisterInput() method description !!!
+ /// @param[in] priority The priority of the channel
+ /// @param[in] component The component of the channel
+ /// @param[in] origin Who set the channel (CustomString@IP)
+ /// @param[in] owner Specific owner string, might be empty
+ /// @param[in] callerComp The component that call this (e.g. PROTO/FLAT)
+ ///
+ void registerInput(int priority, hyperion::Components component, const QString &origin, const QString &owner, hyperion::Components callerComp);
+
+ ///
+ /// @brief Revoke a registerInput() call by priority. We maintain all registered priorities in this scope
+ /// ATTENTION: This is MANDATORY if you change (priority change) or stop(clear/timeout) DURING lifetime. If this class destructs it's not needed
+ /// @param priority The priority to unregister
+ ///
+ void unregisterInput(int priority);
+
+ ///
+ /// @brief Handle the instance switching
+ /// @param inst The requested instance
+ /// @return True on success else false
+ ///
+ bool setHyperionInstance(quint8 inst);
+
+ ///
+ /// @brief Check if Hyperion ist enabled
+ /// @return True when enabled else false
+ ///
+ bool isHyperionEnabled();
+
+ ///
+ /// @brief Get all instances data
+ /// @return The instance data
+ ///
+ QVector getAllInstanceData() const;
+
+ ///
+ /// @brief Get the current instances index
+ /// @return The instance index set
+ ///
+ quint8 getCurrentInstanceIndex() const { return _currInstanceIndex; }
+
+ ///
+ /// @brief Start instance
+ /// @param index The instance index
+ /// @param tan The tan
+ /// @return True on success else false
+ ///
+ bool startInstance(quint8 index, int tan = 0);
+
+ ///
+ /// @brief Stop instance
+ /// @param index The instance index
+ ///
+ void stopInstance(quint8 index);
+
+ //////////////////////////////////
+ /// AUTH / ADMINISTRATION METHODS
+ //////////////////////////////////
+
+ ///
+ /// @brief Delete instance. Requires ADMIN ACCESS
+ /// @param index The instance index
+ /// @param replyMsg The reply Msg
+ /// @return False with reply
+ ///
+ bool deleteInstance(quint8 index, QString &replyMsg);
+
+ ///
+ /// @brief Create instance. Requires ADMIN ACCESS
+ /// @param name With given name
+ /// @return False with reply
+ ///
+ QString createInstance(const QString &name);
+
+ ///
+ /// @brief Rename an instance. Requires ADMIN ACCESS
+ /// @param index The instance index
+ /// @param name With given name
+ /// @return False with reply
+ ///
+ QString setInstanceName(quint8 index, const QString &name);
+
+#if defined(ENABLE_EFFECTENGINE)
+ ///
+ /// @brief Delete an effect. Requires ADMIN ACCESS
+ /// @param name The effect name
+ /// @return True on success else false
+ ///
+ QString deleteEffect(const QString &name);
+
+ ///
+ /// @brief Delete an effect. Requires ADMIN ACCESS
+ /// @param name The effect name
+ /// @return True on success else false
+ ///
+ QString saveEffect(const QJsonObject &data);
+#endif
+
+ ///
+ /// @brief Save settings object. Requires ADMIN ACCESS
+ /// @param data The data object
+ ///
bool saveSettings(const QJsonObject &data);
///
@@ -238,171 +251,189 @@ protected:
///
bool restoreSettings(const QJsonObject &data);
- ///
- /// @brief Test if we are authorized to use the interface
- /// @return The result
- ///
- bool isAuthorized() { return _authorized; };
+ ///
+ /// @brief Set the authorizationn state
+ /// @param authorized True, if authorized
+ ///
+ void setAuthorization(bool authorized) { _authorized = authorized; }
- ///
- /// @brief Test if we are authorized to use the admin interface
- /// @return The result
- ///
- bool isAdminAuthorized() { return _adminAuthorized; };
+ ///
+ /// @brief Test if we are authorized to use the interface
+ /// @return The result
+ ///
+ bool isAuthorized() const { return _authorized; }
- ///
- /// @brief Update the Password of Hyperion. Requires ADMIN ACCESS
- /// @param password Old password
- /// @param newPassword New password
- /// @return True on success else false
- ///
- bool updateHyperionPassword(const QString &password, const QString &newPassword);
+ ///
+ /// @brief Set the authorizationn state for admin activities
+ /// @param authorized True, if authorized
+ ///
+ void setAdminAuthorization(bool adminAuthorized) { _adminAuthorized = adminAuthorized; }
- ///
- /// @brief Get a new token from AuthManager. Requires ADMIN ACCESS
- /// @param comment The comment of the request
- /// @param def The final definition
- /// @return Empty string on success else error message
- ///
- QString createToken(const QString &comment, AuthManager::AuthDefinition &def);
+ ///
+ /// @brief Test if we are authorized to use admin activites
+ /// @return The result
+ ///
+ bool isAdminAuthorized() const { return _adminAuthorized; }
- ///
- /// @brief Rename a token by given id. Requires ADMIN ACCESS
- /// @param id The id of the token
- /// @param comment The new comment
- /// @return Empty string on success else error message
- ///
- QString renameToken(const QString &id, const QString &comment);
+ ///
+ /// @brief Return, if connection is from local network segment
+ /// @return The result
+ ///
+ bool islocalConnection() const { return _localConnection; }
- ///
- /// @brief Delete a token by given id. Requires ADMIN ACCESS
- /// @param id The id of the token
- /// @return Empty string on success else error message
- ///
- QString deleteToken(const QString &id);
+ ///
+ /// @brief Update the Password of Hyperion. Requires ADMIN ACCESS
+ /// @param password Old password
+ /// @param newPassword New password
+ /// @return True on success else false
+ ///
+ bool updateHyperionPassword(const QString &password, const QString &newPassword);
- ///
- /// @brief Set a new token request
- /// @param comment The comment
- /// @param id The id
+ ///
+ /// @brief Get a new token from AuthManager. Requires ADMIN ACCESS
+ /// @param comment The comment of the request
+ /// @param def The final definition
+ /// @return Empty string on success else error message
+ ///
+ QString createToken(const QString &comment, AuthManager::AuthDefinition &def);
+
+ ///
+ /// @brief Rename a token by given id. Requires ADMIN ACCESS
+ /// @param tokenId The id of the token
+ /// @param comment The new comment
+ /// @return Empty string on success else error message
+ ///
+ QString renameToken(const QString &tokenId, const QString &comment);
+
+ ///
+ /// @brief Delete a token by given id. Requires ADMIN ACCESS
+ /// @param tokenId The id of the token
+ /// @return Empty string on success else error message
+ ///
+ QString deleteToken(const QString &tokenId);
+
+ ///
+ /// @brief Set a new token request
+ /// @param comment The comment
+ /// @param tokenId The id of the token
/// @param tan The tan
- ///
- void setNewTokenRequest(const QString &comment, const QString &id, const int &tan);
+ ///
+ void setNewTokenRequest(const QString &comment, const QString &tokenId, const int &tan);
- ///
- /// @brief Cancel new token request
- /// @param comment The comment
- /// @param id The id
- ///
- void cancelNewTokenRequest(const QString &comment, const QString &id);
+ ///
+ /// @brief Cancel new token request
+ /// @param comment The comment
+ /// @param tokenId The id of the token
+ ///
+ void cancelNewTokenRequest(const QString &comment, const QString &tokenId);
- ///
- /// @brief Handle a pending token request. Requires ADMIN ACCESS
- /// @param id The id fo the request
- /// @param accept True when it should be accepted, else false
- /// @return True on success
- bool handlePendingTokenRequest(const QString &id, bool accept);
-
- ///
- /// @brief Get the current List of Tokens. Requires ADMIN ACCESS
- /// @param def returns the defintions
- /// @return True on success
- ///
- bool getTokenList(QVector &def);
-
- ///
- /// @brief Get all current pending token requests. Requires ADMIN ACCESS
- /// @return True on success
- ///
- bool getPendingTokenRequests(QVector &map);
-
- ///
- /// @brief Is User Token Authorized. On success this will grant acces to API and ADMIN API
- /// @param userToken The user Token
- /// @return True on succes
- ///
- bool isUserTokenAuthorized(const QString &userToken);
-
- ///
- /// @brief Get the current User Token (session token). Requires ADMIN ACCESS
- /// @param userToken The user Token
- /// @return True on success
- ///
- bool getUserToken(QString &userToken);
-
- ///
- /// @brief Is a token authorized. On success this will grant acces to the API (NOT ADMIN API)
- /// @param token The user Token
+ ///
+ /// @brief Handle a pending token request. Requires ADMIN ACCESS
+ /// @param tokenId The id fo the request
+ /// @param accept True when it should be accepted, else false
/// @return True on success
- ///
- bool isTokenAuthorized(const QString &token);
+ bool handlePendingTokenRequest(const QString &tokenId, bool accept);
- ///
- /// @brief Is User authorized. On success this will grant acces to the API and ADMIN API
- /// @param password The password of the User
- /// @return True if authorized
- ///
- bool isUserAuthorized(const QString &password);
+ ///
+ /// @brief Get the current List of Tokens. Requires ADMIN ACCESS
+ /// @param def returns the defintions
+ /// @return True on success
+ ///
+ bool getTokenList(QVector &def);
- ///
- /// @brief Test if Hyperion has the default PW
- /// @return The result
- ///
- bool hasHyperionDefaultPw();
+ ///
+ /// @brief Get all current pending token requests. Requires ADMIN ACCESS
+ /// @return True on success
+ ///
+ bool getPendingTokenRequests(QVector &map);
- ///
- /// @brief Logout revokes all authorizations
- ///
- void logout();
+ ///
+ /// @brief Is User Token Authorized. On success this will grant acces to API and ADMIN API
+ /// @param userToken The user Token
+ /// @return True on succes
+ ///
+ bool isUserTokenAuthorized(const QString &userToken);
- /// Reflect auth status of this client
- bool _authorized;
- bool _adminAuthorized;
+ ///
+ /// @brief Get the current User Token (session token). Requires ADMIN ACCESS
+ /// @param userToken The user Token
+ /// @return True on success
+ ///
+ bool getUserToken(QString &userToken);
- /// Is this a local connection
- bool _localConnection;
+ ///
+ /// @brief Is a token authorized. On success this will grant acces to the API (NOT ADMIN API)
+ /// @param token The user Token
+ /// @return True on success
+ ///
+ bool isTokenAuthorized(const QString &token);
- AuthManager *_authManager;
- HyperionIManager *_instanceManager;
+ ///
+ /// @brief Is User authorized. On success this will grant acces to the API and ADMIN API
+ /// @param password The password of the User
+ /// @return True if authorized
+ ///
+ bool isUserAuthorized(const QString &password);
- Logger *_log;
- Hyperion *_hyperion;
+ ///
+ /// @brief Test if Hyperion has the default PW
+ /// @return The result
+ ///
+ bool hasHyperionDefaultPw();
+
+ ///
+ /// @brief Logout revokes all authorizations
+ ///
+ void logout();
+
+
+ AuthManager *_authManager;
+ HyperionIManager *_instanceManager;
+
+ Logger *_log;
+ Hyperion *_hyperion;
signals:
- ///
- /// @brief The API might decide to block connections for security reasons, this emitter should close the socket
- ///
- void forceClose();
+ ///
+ /// @brief The API might decide to block connections for security reasons, this emitter should close the socket
+ ///
+ void forceClose();
- ///
- /// @brief Emits whenever a new Token request is pending. This signal is just active when ADMIN ACCESS has been granted
- /// @param id The id of the request
- /// @param comment The comment of the request; If the commen is EMPTY the request has been revoked by the caller. So remove it from the pending list
- ///
- void onPendingTokenRequest(const QString &id, const QString &comment);
+ ///
+ /// @brief Emits whenever a new Token request is pending. This signal is just active when ADMIN ACCESS has been granted
+ /// @param tokenId The id of the request
+ /// @param comment The comment of the request; If the commen is EMPTY the request has been revoked by the caller. So remove it from the pending list
+ ///
+ void onPendingTokenRequest(const QString &tokenId, const QString &comment);
- ///
- /// @brief Handle emits from AuthManager of accepted/denied/timeouts token request, just if QObject matches with this instance it will emit.
- /// @param success If true the request was accepted else false and no token was created
- /// @param token The new token that is now valid
- /// @param comment The comment that was part of the request
- /// @param id The id that was part of the request
- /// @param tan The tan that was part of the request
- ///
- void onTokenResponse(bool success, const QString &token, const QString &comment, const QString &id, const int &tan);
+ ///
+ /// @brief Handle emits from AuthManager of accepted/denied/timeouts token request, just if QObject matches with this instance it will emit.
+ /// @param success If true the request was accepted else false and no token was created
+ /// @param token The new token that is now valid
+ /// @param comment The comment that was part of the request
+ /// @param tokenId The id that was part of the request
+ /// @param tan The tan that was part of the request
+ ///
+ void onTokenResponse(bool success, const QString &token, const QString &comment, const QString &tokenId, const int &tan);
- ///
- /// @brief Handle emits from HyperionIManager of startInstance request, just if QObject matches with this instance it will emit.
- /// @param tan The tan that was part of the request
- ///
- void onStartInstanceResponse(const int &tan);
+ ///
+ /// @brief Handle emits from HyperionIManager of startInstance request, just if QObject matches with this instance it will emit.
+ /// @param tan The tan that was part of the request
+ ///
+ void onStartInstanceResponse(const int &tan);
private:
- void stopDataConnectionss();
- // Contains all active register call data
- std::map _activeRegisters;
+ /// Reflect authorization status of this client
+ bool _authorized;
+ bool _adminAuthorized;
- // current instance index
- quint8 _currInstanceIndex;
+ /// Is this a local connection
+ bool _localConnection;
+
+ // Contains all active register call data
+ std::map _activeRegisters;
+
+ // current instance index
+ quint8 _currInstanceIndex;
};
diff --git a/include/api/JsonAPI.h b/include/api/JsonAPI.h
index 8e36b158..35d7e204 100644
--- a/include/api/JsonAPI.h
+++ b/include/api/JsonAPI.h
@@ -2,19 +2,24 @@
// parent class
#include
+#include
+
#include
// hyperion includes
#include
#include
#include
+#include
// qt includes
#include
#include
+#include
+#include
class QTimer;
-class JsonCB;
+class JsonCallbacks;
class AuthManager;
class JsonAPI : public API
@@ -46,40 +51,24 @@ public:
void initialize();
public slots:
- ///
- /// @brief Is called whenever the current Hyperion instance pushes new led raw values (if enabled)
- /// @param ledColors The current led colors
- ///
- void streamLedcolorsUpdate(const std::vector &ledColors);
-
- ///
- /// @brief Push images whenever hyperion emits (if enabled)
- /// @param image The current image
- ///
- void setImage(const Image &image);
-
- ///
- /// @brief Process and push new log messages from logger (if enabled)
- ///
- void incommingLogMessage(const Logger::T_LOG_MESSAGE &);
private slots:
///
/// @brief Handle emits from API of a new Token request.
- /// @param id The id of the request
+ /// @param identifier The identifier of the request
/// @param comment The comment which needs to be accepted
///
- void newPendingTokenRequest(const QString &id, const QString &comment);
+ void issueNewPendingTokenRequest(const QString &identifier, const QString &comment);
///
/// @brief Handle emits from AuthManager of accepted/denied/timeouts token request, just if QObject matches with this instance we are allowed to send response.
/// @param success If true the request was accepted else false and no token was created
/// @param token The new token that is now valid
/// @param comment The comment that was part of the request
- /// @param id The id that was part of the request
+ /// @param identifier The identifier that was part of the request
/// @param tan The tan that was part of the request
///
- void handleTokenResponse(bool success, const QString &token, const QString &comment, const QString &id, const int &tan);
+ void handleTokenResponse(bool success, const QString &token, const QString &comment, const QString &identifier, const int &tan);
///
/// @brief Handle whenever the state of a instance (HyperionIManager) changes according to enum instanceState
@@ -89,11 +78,6 @@ private slots:
///
void handleInstanceStateChange(InstanceState state, quint8 instance, const QString &name = QString());
- ///
- /// @brief Stream a new LED Colors update
- ///
- void streamLedColorsUpdate();
-
signals:
///
/// Signal emits with the reply message provided with handleMessage()
@@ -111,31 +95,9 @@ signals:
void signalEvent(Event event);
private:
- // true if further callbacks are forbidden (http)
- bool _noListener;
- /// The peer address of the client
- QString _peerAddress;
-
- // The JsonCB instance which handles data subscription/notifications
- JsonCB *_jsonCB;
-
- // streaming buffers
- QJsonObject _streaming_leds_reply;
- QJsonObject _streaming_image_reply;
- QJsonObject _streaming_logging_reply;
-
- /// flag to determine state of log streaming
- bool _streaming_logging_activated;
-
- /// timer for led color refresh
- QTimer *_ledStreamTimer;
-
- /// led stream connection handle
- QMetaObject::Connection _ledStreamConnection;
-
- /// the current streaming led values
- std::vector _currentLedValues;
+ void handleCommand(const JsonApiCommand& cmd, const QJsonObject &message);
+ void handleInstanceCommand(const JsonApiCommand& cmd, const QJsonObject &message);
///
/// @brief Handle the switches of Hyperion instances
@@ -150,14 +112,14 @@ private:
///
/// @param message the incoming message
///
- void handleColorCommand(const QJsonObject &message, const QString &command, int tan);
+ void handleColorCommand(const QJsonObject& message, const JsonApiCommand& cmd);
///
/// Handle an incoming JSON Image message
///
/// @param message the incoming message
///
- void handleImageCommand(const QJsonObject &message, const QString &command, int tan);
+ void handleImageCommand(const QJsonObject &message, const JsonApiCommand& cmd);
#if defined(ENABLE_EFFECTENGINE)
///
@@ -165,21 +127,21 @@ private:
///
/// @param message the incoming message
///
- void handleEffectCommand(const QJsonObject &message, const QString &command, int tan);
+ void handleEffectCommand(const QJsonObject &message, const JsonApiCommand& cmd);
///
/// Handle an incoming JSON Effect message (Write JSON Effect)
///
/// @param message the incoming message
///
- void handleCreateEffectCommand(const QJsonObject &message, const QString &command, int tan);
+ void handleCreateEffectCommand(const QJsonObject &message, const JsonApiCommand& cmd);
///
/// Handle an incoming JSON Effect message (Delete JSON Effect)
///
/// @param message the incoming message
///
- void handleDeleteEffectCommand(const QJsonObject &message, const QString &command, int tan);
+ void handleDeleteEffectCommand(const QJsonObject &message, const JsonApiCommand& cmd);
#endif
///
@@ -187,158 +149,250 @@ private:
///
/// @param message the incoming message
///
- void handleSysInfoCommand(const QJsonObject &message, const QString &command, int tan);
+ void handleSysInfoCommand(const QJsonObject &message, const JsonApiCommand& cmd);
///
/// Handle an incoming JSON Server info message
///
/// @param message the incoming message
///
- void handleServerInfoCommand(const QJsonObject &message, const QString &command, int tan);
+ void handleServerInfoCommand(const QJsonObject &message, const JsonApiCommand& cmd);
///
/// Handle an incoming JSON Clear message
///
/// @param message the incoming message
///
- void handleClearCommand(const QJsonObject &message, const QString &command, int tan);
+ void handleClearCommand(const QJsonObject &message, const JsonApiCommand& cmd);
///
/// Handle an incoming JSON Clearall message
///
/// @param message the incoming message
///
- void handleClearallCommand(const QJsonObject &message, const QString &command, int tan);
+ void handleClearallCommand(const QJsonObject &message, const JsonApiCommand& cmd);
///
/// Handle an incoming JSON Adjustment message
///
/// @param message the incoming message
///
- void handleAdjustmentCommand(const QJsonObject &message, const QString &command, int tan);
+ void handleAdjustmentCommand(const QJsonObject &message, const JsonApiCommand& cmd);
///
/// Handle an incoming JSON SourceSelect message
///
/// @param message the incoming message
///
- void handleSourceSelectCommand(const QJsonObject &message, const QString &command, int tan);
+ void handleSourceSelectCommand(const QJsonObject &message, const JsonApiCommand& cmd);
/// Handle an incoming JSON GetConfig message and check subcommand
///
/// @param message the incoming message
///
- void handleConfigCommand(const QJsonObject &message, const QString &command, int tan);
+ void handleConfigCommand(const QJsonObject &message, const JsonApiCommand& cmd);
/// Handle an incoming JSON GetSchema message from handleConfigCommand()
///
/// @param message the incoming message
///
- void handleSchemaGetCommand(const QJsonObject &message, const QString &command, int tan);
+ void handleSchemaGetCommand(const QJsonObject &message, const JsonApiCommand& cmd);
/// Handle an incoming JSON SetConfig message from handleConfigCommand()
///
/// @param message the incoming message
///
- void handleConfigSetCommand(const QJsonObject &message, const QString &command, int tan);
+ void handleConfigSetCommand(const QJsonObject &message, const JsonApiCommand& cmd);
/// Handle an incoming JSON RestoreConfig message from handleConfigCommand()
///
/// @param message the incoming message
///
- void handleConfigRestoreCommand(const QJsonObject &message, const QString &command, int tan);
+ void handleConfigRestoreCommand(const QJsonObject &message, const JsonApiCommand& cmd);
///
/// Handle an incoming JSON Component State message
///
/// @param message the incoming message
///
- void handleComponentStateCommand(const QJsonObject &message, const QString &command, int tan);
+ void handleComponentStateCommand(const QJsonObject &message, const JsonApiCommand& cmd);
/// Handle an incoming JSON Led Colors message
///
/// @param message the incoming message
///
- void handleLedColorsCommand(const QJsonObject &message, const QString &command, int tan);
+ void handleLedColorsCommand(const QJsonObject &message, const JsonApiCommand& cmd);
/// Handle an incoming JSON Logging message
///
/// @param message the incoming message
///
- void handleLoggingCommand(const QJsonObject &message, const QString &command, int tan);
+ void handleLoggingCommand(const QJsonObject &message, const JsonApiCommand& cmd);
/// Handle an incoming JSON Processing message
///
/// @param message the incoming message
///
- void handleProcessingCommand(const QJsonObject &message, const QString &command, int tan);
+ void handleProcessingCommand(const QJsonObject &message, const JsonApiCommand& cmd);
/// Handle an incoming JSON VideoMode message
///
/// @param message the incoming message
///
- void handleVideoModeCommand(const QJsonObject &message, const QString &command, int tan);
+ void handleVideoModeCommand(const QJsonObject &message, const JsonApiCommand& cmd);
/// Handle an incoming JSON plugin message
///
/// @param message the incoming message
///
- void handleAuthorizeCommand(const QJsonObject &message, const QString &command, int tan);
+ void handleAuthorizeCommand(const QJsonObject &message, const JsonApiCommand& cmd);
/// Handle an incoming JSON instance message
///
/// @param message the incoming message
///
- void handleInstanceCommand(const QJsonObject &message, const QString &command, int tan);
+ void handleInstanceCommand(const QJsonObject &message, const JsonApiCommand& cmd);
/// Handle an incoming JSON Led Device message
///
/// @param message the incoming message
///
- void handleLedDeviceCommand(const QJsonObject &message, const QString &command, int tan);
+ void handleLedDeviceCommand(const QJsonObject &message, const JsonApiCommand& cmd);
/// Handle an incoming JSON message regarding Input Sources (Grabbers)
///
/// @param message the incoming message
///
- void handleInputSourceCommand(const QJsonObject& message, const QString& command, int tan);
+ void handleInputSourceCommand(const QJsonObject& message, const JsonApiCommand& cmd);
/// Handle an incoming JSON message to request remote hyperion servers providing a given hyperion service
///
/// @param message the incoming message
///
- void handleServiceCommand(const QJsonObject &message, const QString &command, int tan);
+ void handleServiceCommand(const QJsonObject &message, const JsonApiCommand& cmd);
/// Handle an incoming JSON message for actions related to the overall Hyperion system
///
/// @param message the incoming message
///
- void handleSystemCommand(const QJsonObject &message, const QString &command, int tan);
+ void handleSystemCommand(const QJsonObject &message, const JsonApiCommand& cmd);
- ///
- /// Handle an incoming JSON message of unknown type
- ///
- void handleNotImplemented(const QString &command, int tan);
+
+ void applyColorAdjustments(const QJsonObject &adjustment, ColorAdjustment *colorAdjustment);
+ void applyColorAdjustment(const QString &colorName, const QJsonObject &adjustment, RgbChannelAdjustment &rgbAdjustment);
+ void applyGammaTransform(const QString &transformName, const QJsonObject &adjustment, RgbTransform &rgbTransform, char channel);
+
+ void applyTransforms(const QJsonObject &adjustment, ColorAdjustment *colorAdjustment);
+ template
+ void applyTransform(const QString &transformName, const QJsonObject &adjustment, T &transform, void (T::*setFunction)(bool));
+ template
+ void applyTransform(const QString &transformName, const QJsonObject &adjustment, T &transform, void (T::*setFunction)(double));
+ template
+ void applyTransform(const QString &transformName, const QJsonObject &adjustment, T &transform, void (T::*setFunction)(uint8_t));
+
+ void handleTokenRequired(const JsonApiCommand& cmd);
+ void handleAdminRequired(const JsonApiCommand& cmd);
+ void handleNewPasswordRequired(const JsonApiCommand& cmd);
+ void handleLogout(const JsonApiCommand& cmd);
+ void handleNewPassword(const QJsonObject &message, const JsonApiCommand& cmd);
+ void handleCreateToken(const QJsonObject &message, const JsonApiCommand& cmd);
+ void handleRenameToken(const QJsonObject &message, const JsonApiCommand& cmd);
+ void handleDeleteToken(const QJsonObject &message, const JsonApiCommand& cmd);
+ void handleRequestToken(const QJsonObject &message, const JsonApiCommand& cmd);
+ void handleGetPendingTokenRequests(const JsonApiCommand& cmd);
+ void handleAnswerRequest(const QJsonObject &message, const JsonApiCommand& cmd);
+ void handleGetTokenList(const JsonApiCommand& cmd);
+ void handleLogin(const QJsonObject &message, const JsonApiCommand& cmd);
+
+ void handleLedDeviceDiscover(LedDevice& ledDevice, const QJsonObject& message, const JsonApiCommand& cmd);
+ void handleLedDeviceGetProperties(LedDevice& ledDevice, const QJsonObject& message, const JsonApiCommand& cmd);
+ void handleLedDeviceIdentify(LedDevice& ledDevice, const QJsonObject& message, const JsonApiCommand& cmd);
+ void handleLedDeviceAddAuthorization(LedDevice& ledDevice, const QJsonObject& message, const JsonApiCommand& cmd);
+
+ QJsonObject getBasicCommandReply(bool success, const QString &command, int tan, InstanceCmd::Type isInstanceCmd) const;
///
/// Send a standard reply indicating success
///
- void sendSuccessReply(const QString &command = "", int tan = 0);
+ void sendSuccessReply(const JsonApiCommand& cmd);
+
+ ///
+ /// Send a standard reply indicating success
+ ///
+ void sendSuccessReply(const QString &command = "", int tan = 0, InstanceCmd::Type isInstanceCmd = InstanceCmd::No);
///
/// Send a standard reply indicating success with data
///
- void sendSuccessDataReply(const QJsonDocument &doc, const QString &command = "", int tan = 0);
+ void sendSuccessDataReply(const QJsonValue &infoData, const JsonApiCommand& cmd);
+
+ ///
+ /// Send a standard reply indicating success with data
+ ///
+ void sendSuccessDataReply(const QJsonValue &infoData, const QString &command = "", int tan = 0, InstanceCmd::Type isInstanceCmd = InstanceCmd::No);
+
+ ///
+ /// Send a standard reply indicating success with data and error details
+ ///
+ void sendSuccessDataReplyWithError(const QJsonValue &infoData, const JsonApiCommand& cmd, const QStringList& errorDetails = {});
+
+ ///
+ /// Send a standard reply indicating success with data and error details
+ ///
+ void sendSuccessDataReplyWithError(const QJsonValue &infoData, const QString &command = "", int tan = 0, const QStringList& errorDetails = {}, InstanceCmd::Type isInstanceCmd = InstanceCmd::No);
+
+ ///
+ /// Send a message with data.
+ /// Note: To be used as a new message and not as a response to a previous request.
+ ///
+ void sendNewRequest(const QJsonValue &infoData, const JsonApiCommand& cmd);
+
+ ///
+ /// Send a message with data
+ /// Note: To be used as a new message and not as a response to a previous request.
+ ///
+ void sendNewRequest(const QJsonValue &infoData, const QString &command, InstanceCmd::Type isInstanceCmd = InstanceCmd::No);
///
/// Send an error message back to the client
///
/// @param error String describing the error
///
- void sendErrorReply(const QString &error, const QString &command = "", int tan = 0);
+ void sendErrorReply(const QString &error, const JsonApiCommand& cmd);
+
+ ///
+ /// Send an error message back to the client
+ ///
+ /// @param error String describing the error
+ /// @param errorDetails additional information detailing the error scenario
+ ///
+ void sendErrorReply(const QString &error, const QStringList& errorDetails, const JsonApiCommand& cmd);
+
+ ///
+ /// Send an error message back to the client
+ ///
+ /// @param error String describing the error
+ /// @param errorDetails additional information detailing the error scenario
+ ///
+ void sendErrorReply(const QString &error, const QStringList& errorDetails = {}, const QString &command = "", int tan = 0, InstanceCmd::Type isInstanceCmd = InstanceCmd::No);
+
+ void sendNoAuthorization(const JsonApiCommand& cmd);
///
/// @brief Kill all signal/slot connections to stop possible data emitter
///
- void stopDataConnections();
+ void stopDataConnections() override;
+
+ static QString findCommand (const QString& jsonS);
+ static int findTan (const QString& jsonString);
+
+ // true if further callbacks are forbidden (http)
+ bool _noListener;
+
+ /// The peer address of the client
+ QString _peerAddress;
+
+ // The JsonCallbacks instance which handles data subscription/notifications
+ QSharedPointer _jsonCB;
+
};
diff --git a/include/api/JsonApiCommand.h b/include/api/JsonApiCommand.h
new file mode 100644
index 00000000..c63d968b
--- /dev/null
+++ b/include/api/JsonApiCommand.h
@@ -0,0 +1,332 @@
+#ifndef JSONAPICOMMAND_H
+#define JSONAPICOMMAND_H
+
+#include
+#include
+#include
+
+class Command {
+public:
+ enum Type {
+ Unknown,
+ Adjustment,
+ Authorize,
+ Clear,
+ ClearAll,
+ Color,
+ ComponentState,
+ Config,
+ Correction,
+ CreateEffect,
+ DeleteEffect,
+ Effect,
+ Image,
+ InputSource,
+ Instance,
+ LedColors,
+ LedDevice,
+ Logging,
+ Processing,
+ ServerInfo,
+ Service,
+ SourceSelect,
+ SysInfo,
+ System,
+ Temperature,
+ Transform,
+ VideoMode
+ };
+
+ static QString toString(Type type) {
+ switch (type) {
+ case Adjustment: return "adjustment";
+ case Authorize: return "authorize";
+ case Clear: return "clear";
+ case ClearAll: return "clearall";
+ case Color: return "color";
+ case ComponentState: return "componentstate";
+ case Config: return "config";
+ case Correction: return "correction";
+ case CreateEffect: return "create-effect";
+ case DeleteEffect: return "delete-effect";
+ case Effect: return "effect";
+ case Image: return "image";
+ case InputSource: return "inputsource";
+ case Instance: return "instance";
+ case LedColors: return "ledcolors";
+ case LedDevice: return "leddevice";
+ case Logging: return "logging";
+ case Processing: return "processing";
+ case ServerInfo: return "serverinfo";
+ case SourceSelect: return "sourceselect";
+ case SysInfo: return "sysinfo";
+ case System: return "system";
+ case Temperature: return "temperature";
+ case Transform: return "transform";
+ case VideoMode: return "videomode";
+ case Service: return "service";
+ default: return "unknown";
+ }
+ }
+};
+
+class SubCommand {
+public:
+ enum Type {
+ Unknown,
+ Empty,
+ AdminRequired,
+ AddAuthorization,
+ AnswerRequest,
+ CreateInstance,
+ CreateToken,
+ DeleteInstance,
+ DeleteToken,
+ Discover,
+ GetConfig,
+ GetInfo,
+ GetPendingTokenRequests,
+ GetProperties,
+ GetSchema,
+ GetSubscriptionCommands,
+ GetSubscriptions,
+ GetTokenList,
+ Identify,
+ Idle,
+ ImageStreamStart,
+ ImageStreamStop,
+ LedStreamStart,
+ LedStreamStop,
+ Login,
+ Logout,
+ NewPassword,
+ NewPasswordRequired,
+ Reload,
+ RenameToken,
+ RequestToken,
+ Restart,
+ RestoreConfig,
+ Resume,
+ SaveName,
+ SetConfig,
+ Start,
+ StartInstance,
+ Stop,
+ StopInstance,
+ Subscribe,
+ Suspend,
+ SwitchTo,
+ ToggleIdle,
+ ToggleSuspend,
+ TokenRequired,
+ Unsubscribe
+ };
+
+ static QString toString(Type type) {
+ switch (type) {
+ case Empty: return "";
+ case AdminRequired: return "adminRequired";
+ case AddAuthorization: return "addAuthorization";
+ case AnswerRequest: return "answerRequest";
+ case CreateInstance: return "createInstance";
+ case CreateToken: return "createToken";
+ case DeleteInstance: return "deleteInstance";
+ case DeleteToken: return "deleteToken";
+ case Discover: return "discover";
+ case GetConfig: return "getconfig";
+ case GetInfo: return "getInfo";
+ case GetPendingTokenRequests: return "getPendingTokenRequests";
+ case GetProperties: return "getProperties";
+ case GetSchema: return "getschema";
+ case GetSubscriptionCommands: return "getSubscriptionCommands";
+ case GetSubscriptions: return "getSubscriptions";
+ case GetTokenList: return "getTokenList";
+ case Identify: return "identify";
+ case Idle: return "idle";
+ case ImageStreamStart: return "imagestream-start";
+ case ImageStreamStop: return "imagestream-stop";
+ case LedStreamStart: return "ledstream-start";
+ case LedStreamStop: return "ledstream-stop";
+ case Login: return "login";
+ case Logout: return "logout";
+ case NewPassword: return "newPassword";
+ case NewPasswordRequired: return "newPasswordRequired";
+ case Reload: return "reload";
+ case RenameToken: return "renameToken";
+ case RequestToken: return "requestToken";
+ case Restart: return "restart";
+ case RestoreConfig: return "restoreconfig";
+ case Resume: return "resume";
+ case SaveName: return "saveName";
+ case SetConfig: return "setconfig";
+ case Start: return "start";
+ case StartInstance: return "startInstance";
+ case Stop: return "stop";
+ case StopInstance: return "stopInstance";
+ case Subscribe: return "subscribe";
+ case Suspend: return "suspend";
+ case SwitchTo: return "switchTo";
+ case ToggleIdle: return "toggleIdle";
+ case ToggleSuspend: return "toggleSuspend";
+ case TokenRequired: return "tokenRequired";
+ case Unsubscribe: return "unsubscribe";
+ default: return "unknown";
+ }
+ }
+};
+
+class Authorization {
+public:
+ enum Type {
+ Admin,
+ Yes,
+ No
+ };
+};
+
+class NoListenerCmd {
+public:
+ enum Type {
+ No,
+ Yes
+ };
+};
+
+class InstanceCmd {
+public:
+ enum Type {
+ No,
+ Yes,
+ Multi
+ };
+};
+
+class JsonApiCommand {
+public:
+
+ JsonApiCommand()
+ : command(Command::Unknown),
+ subCommand(SubCommand::Unknown),
+ tan(0),
+ authorization(Authorization::Admin),
+ isInstanceCmd(InstanceCmd::No),
+ isNolistenerCmd(NoListenerCmd::Yes)
+ {}
+
+ JsonApiCommand(Command::Type command, SubCommand::Type subCommand,
+ Authorization::Type authorization,
+ InstanceCmd::Type isInstanceCmd,
+ NoListenerCmd::Type isNolistenerCmd,
+ int tan = 0)
+ : command(command),
+ subCommand(subCommand),
+ tan(tan),
+ authorization(authorization),
+ isInstanceCmd(isInstanceCmd),
+ isNolistenerCmd(isNolistenerCmd)
+ {}
+
+ Command::Type getCommand() const { return command; }
+ SubCommand::Type getSubCommand() const { return subCommand; }
+ InstanceCmd::Type instanceCmd() const { return isInstanceCmd; }
+ int getTan() const { return tan; }
+
+ QString toString() const {
+ QString cmd = Command::toString(command);
+ if (subCommand > SubCommand::Empty) {
+ cmd += QString("-%2").arg(SubCommand::toString(subCommand));
+ }
+ return cmd;
+ }
+
+ Command::Type command;
+ SubCommand::Type subCommand;
+ int tan;
+
+ Authorization::Type authorization;
+ InstanceCmd::Type isInstanceCmd;
+ NoListenerCmd::Type isNolistenerCmd;
+};
+
+typedef QMap, JsonApiCommand> CommandLookupMap;
+
+class ApiCommandRegister {
+public:
+
+ static const CommandLookupMap& getCommandLookup() {
+ static const CommandLookupMap commandLookup {
+ { {"adjustment", ""}, { Command::Adjustment, SubCommand::Empty, Authorization::Yes, InstanceCmd::Multi, NoListenerCmd::Yes} },
+ { {"authorize", "adminRequired"}, { Command::Authorize, SubCommand::AdminRequired, Authorization::No, InstanceCmd::No, NoListenerCmd::Yes} },
+ { {"authorize", "answerRequest"}, { Command::Authorize, SubCommand::AnswerRequest, Authorization::Admin, InstanceCmd::No, NoListenerCmd::No} },
+ { {"authorize", "createToken"}, { Command::Authorize, SubCommand::CreateToken, Authorization::Admin, InstanceCmd::No, NoListenerCmd::No} },
+ { {"authorize", "deleteToken"}, { Command::Authorize, SubCommand::DeleteToken, Authorization::Admin, InstanceCmd::No, NoListenerCmd::Yes} },
+ { {"authorize", "getPendingTokenRequests"}, { Command::Authorize, SubCommand::GetPendingTokenRequests, Authorization::Admin, InstanceCmd::No, NoListenerCmd::No} },
+ { {"authorize", "getTokenList"}, { Command::Authorize, SubCommand::GetTokenList, Authorization::Admin, InstanceCmd::No, NoListenerCmd::Yes} },
+ { {"authorize", "login"}, { Command::Authorize, SubCommand::Login, Authorization::No, InstanceCmd::No, NoListenerCmd::No} },
+ { {"authorize", "logout"}, { Command::Authorize, SubCommand::Logout, Authorization::No, InstanceCmd::No, NoListenerCmd::No} },
+ { {"authorize", "newPassword"}, { Command::Authorize, SubCommand::NewPassword, Authorization::Admin, InstanceCmd::No, NoListenerCmd::Yes} },
+ { {"authorize", "newPasswordRequired"}, { Command::Authorize, SubCommand::NewPasswordRequired, Authorization::No, InstanceCmd::No, NoListenerCmd::Yes} },
+ { {"authorize", "renameToken"}, { Command::Authorize, SubCommand::RenameToken, Authorization::Admin, InstanceCmd::No, NoListenerCmd::Yes} },
+ { {"authorize", "requestToken"}, { Command::Authorize, SubCommand::RequestToken, Authorization::No, InstanceCmd::No, NoListenerCmd::Yes} },
+ { {"authorize", "tokenRequired"}, { Command::Authorize, SubCommand::TokenRequired, Authorization::No, InstanceCmd::No, NoListenerCmd::Yes} },
+ { {"clear", ""}, { Command::Clear, SubCommand::Empty, Authorization::Yes, InstanceCmd::Multi, NoListenerCmd::Yes} },
+ { {"clearall", ""}, { Command::ClearAll, SubCommand::Empty, Authorization::Yes, InstanceCmd::Multi, NoListenerCmd::Yes} },
+ { {"color", ""}, { Command::Color, SubCommand::Empty, Authorization::Yes, InstanceCmd::Multi, NoListenerCmd::Yes} },
+ { {"componentstate", ""}, { Command::ComponentState, SubCommand::Empty, Authorization::Yes, InstanceCmd::Multi, NoListenerCmd::Yes} },
+ { {"config", "getconfig"}, { Command::Config, SubCommand::GetConfig, Authorization::Admin, InstanceCmd::Yes, NoListenerCmd::Yes} },
+ { {"config", "getschema"}, { Command::Config, SubCommand::GetSchema, Authorization::Admin, InstanceCmd::Yes, NoListenerCmd::Yes} },
+ { {"config", "reload"}, { Command::Config, SubCommand::Reload, Authorization::Admin, InstanceCmd::Yes, NoListenerCmd::Yes} },
+ { {"config", "restoreconfig"}, { Command::Config, SubCommand::RestoreConfig, Authorization::Admin, InstanceCmd::Yes, NoListenerCmd::Yes} },
+ { {"config", "setconfig"}, { Command::Config, SubCommand::SetConfig, Authorization::Admin, InstanceCmd::Yes, NoListenerCmd::Yes} },
+ { {"correction", ""}, { Command::Correction, SubCommand::Empty, Authorization::Yes, InstanceCmd::Yes, NoListenerCmd::Yes} },
+ { {"create-effect", ""}, { Command::CreateEffect, SubCommand::Empty, Authorization::Yes, InstanceCmd::Yes, NoListenerCmd::Yes} },
+ { {"delete-effect", ""}, { Command::DeleteEffect, SubCommand::Empty, Authorization::Yes, InstanceCmd::Yes, NoListenerCmd::Yes} },
+ { {"effect", ""}, { Command::Effect, SubCommand::Empty, Authorization::Yes, InstanceCmd::Multi, NoListenerCmd::Yes} },
+ { {"image", ""}, { Command::Image, SubCommand::Empty, Authorization::Yes, InstanceCmd::Multi, NoListenerCmd::Yes} },
+ { {"inputsource", "discover"}, { Command::InputSource, SubCommand::Discover, Authorization::Yes, InstanceCmd::No, NoListenerCmd::Yes} },
+ { {"inputsource", "getProperties"}, { Command::InputSource, SubCommand::GetProperties, Authorization::Yes, InstanceCmd::No, NoListenerCmd::Yes} },
+ { {"instance", "createInstance"}, { Command::Instance, SubCommand::CreateInstance, Authorization::Admin, InstanceCmd::No, NoListenerCmd::Yes} },
+ { {"instance", "deleteInstance"}, { Command::Instance, SubCommand::DeleteInstance, Authorization::Admin, InstanceCmd::No, NoListenerCmd::Yes} },
+ { {"instance", "saveName"}, { Command::Instance, SubCommand::SaveName, Authorization::Admin, InstanceCmd::No, NoListenerCmd::Yes} },
+ { {"instance", "startInstance"}, { Command::Instance, SubCommand::StartInstance, Authorization::Yes, InstanceCmd::No, NoListenerCmd::Yes} },
+ { {"instance", "stopInstance"}, { Command::Instance, SubCommand::StopInstance, Authorization::Yes, InstanceCmd::No, NoListenerCmd::Yes} },
+ { {"instance", "switchTo"}, { Command::Instance, SubCommand::SwitchTo, Authorization::Yes, InstanceCmd::No, NoListenerCmd::Yes} },
+ { {"ledcolors", "imagestream-start"}, { Command::LedColors, SubCommand::ImageStreamStart, Authorization::Yes, InstanceCmd::Yes, NoListenerCmd::Yes} },
+ { {"ledcolors", "imagestream-stop"}, { Command::LedColors, SubCommand::ImageStreamStop, Authorization::Yes, InstanceCmd::Yes, NoListenerCmd::Yes} },
+ { {"ledcolors", "ledstream-start"}, { Command::LedColors, SubCommand::LedStreamStart, Authorization::Yes, InstanceCmd::Yes, NoListenerCmd::Yes} },
+ { {"ledcolors", "ledstream-stop"}, { Command::LedColors, SubCommand::LedStreamStop, Authorization::Yes, InstanceCmd::Yes, NoListenerCmd::Yes} },
+ { {"leddevice", "addAuthorization"}, { Command::LedDevice, SubCommand::AddAuthorization, Authorization::Yes, InstanceCmd::Yes, NoListenerCmd::Yes} },
+ { {"leddevice", "discover"}, { Command::LedDevice, SubCommand::Discover, Authorization::Yes, InstanceCmd::Yes, NoListenerCmd::Yes} },
+ { {"leddevice", "getProperties"}, { Command::LedDevice, SubCommand::GetProperties, Authorization::Yes, InstanceCmd::Yes, NoListenerCmd::Yes} },
+ { {"leddevice", "identify"}, { Command::LedDevice, SubCommand::Identify, Authorization::Yes, InstanceCmd::Yes, NoListenerCmd::Yes} },
+ { {"logging", "start"}, { Command::Logging, SubCommand::Start, Authorization::Yes, InstanceCmd::No, NoListenerCmd::Yes} },
+ { {"logging", "stop"}, { Command::Logging, SubCommand::Stop, Authorization::Yes, InstanceCmd::No, NoListenerCmd::Yes} },
+ { {"processing", ""}, { Command::Processing, SubCommand::Empty, Authorization::Yes, InstanceCmd::Multi, NoListenerCmd::Yes} },
+ { {"serverinfo", ""}, { Command::ServerInfo, SubCommand::Empty, Authorization::Yes, InstanceCmd::Yes, NoListenerCmd::Yes} },
+ { {"serverinfo", "getInfo"}, { Command::ServerInfo, SubCommand::GetInfo, Authorization::Yes, InstanceCmd::Yes, NoListenerCmd::Yes} },
+ { {"serverinfo", "subscribe"}, { Command::ServerInfo, SubCommand::Subscribe, Authorization::Yes, InstanceCmd::Yes, NoListenerCmd::No} },
+ { {"serverinfo", "unsubscribe"}, { Command::ServerInfo, SubCommand::Unsubscribe, Authorization::Yes, InstanceCmd::Yes, NoListenerCmd::No} },
+ { {"serverinfo", "getSubscriptions"}, { Command::ServerInfo, SubCommand::GetSubscriptions, Authorization::Yes, InstanceCmd::Yes, NoListenerCmd::No} },
+ { {"serverinfo", "getSubscriptionCommands"}, { Command::ServerInfo, SubCommand::GetSubscriptionCommands, Authorization::No, InstanceCmd::No, NoListenerCmd::No} },
+ { {"service", "discover"}, { Command::Service, SubCommand::Discover, Authorization::Yes, InstanceCmd::No, NoListenerCmd::Yes} },
+ { {"sourceselect", ""}, { Command::SourceSelect, SubCommand::Empty, Authorization::Yes, InstanceCmd::Multi, NoListenerCmd::Yes} },
+ { {"sysinfo", ""}, { Command::SysInfo, SubCommand::Empty, Authorization::Yes, InstanceCmd::No, NoListenerCmd::Yes} },
+ { {"system", "restart"}, { Command::System, SubCommand::Restart, Authorization::Yes, InstanceCmd::No, NoListenerCmd::Yes} },
+ { {"system", "resume"}, { Command::System, SubCommand::Resume, Authorization::Yes, InstanceCmd::No, NoListenerCmd::Yes} },
+ { {"system", "suspend"}, { Command::System, SubCommand::Suspend, Authorization::Yes, InstanceCmd::No, NoListenerCmd::Yes} },
+ { {"system", "toggleSuspend"}, { Command::System, SubCommand::ToggleSuspend, Authorization::Yes, InstanceCmd::No, NoListenerCmd::Yes} },
+ { {"system", "idle"}, { Command::System, SubCommand::Idle, Authorization::Yes, InstanceCmd::No, NoListenerCmd::Yes} },
+ { {"system", "toggleIdle"}, { Command::System, SubCommand::ToggleIdle, Authorization::Yes, InstanceCmd::No, NoListenerCmd::Yes} },
+ { {"temperature", ""}, { Command::Temperature, SubCommand::Empty, Authorization::Yes, InstanceCmd::Yes, NoListenerCmd::Yes} },
+ { {"transform", ""}, { Command::Transform, SubCommand::Empty, Authorization::Yes, InstanceCmd::Yes, NoListenerCmd::Yes} },
+ { {"videomode", ""}, { Command::VideoMode, SubCommand::Empty, Authorization::Yes, InstanceCmd::No, NoListenerCmd::Yes} }
+ };
+ return commandLookup;
+ }
+
+ static JsonApiCommand getCommandInfo(const QString& command, const QString& subCommand) {
+ return getCommandLookup().value({command, subCommand});
+ }
+};
+
+#endif // JSONAPICOMMAND_H
diff --git a/include/api/JsonApiSubscription.h b/include/api/JsonApiSubscription.h
new file mode 100644
index 00000000..3d011bbd
--- /dev/null
+++ b/include/api/JsonApiSubscription.h
@@ -0,0 +1,135 @@
+#ifndef JSONAPISUBSCRIPTION_H
+#define JSONAPISUBSCRIPTION_H
+
+#include // Required to determine the cmake options
+
+#include
+#include
+
+
+class Subscription {
+public:
+ enum Type {
+ Unknown,
+ AdjustmentUpdate,
+ ComponentsUpdate,
+#if defined(ENABLE_EFFECTENGINE)
+ EffectsUpdate,
+#endif
+ EventUpdate,
+ ImageToLedMappingUpdate,
+ ImageUpdate,
+ InstanceUpdate,
+ LedColorsUpdate,
+ LedsUpdate,
+ LogMsgUpdate,
+ PrioritiesUpdate,
+ SettingsUpdate,
+ TokenUpdate,
+ VideomodeUpdate
+ };
+
+ static QString toString(Type type) {
+ switch (type) {
+ case AdjustmentUpdate: return "adjustment-update";
+ case ComponentsUpdate: return "components-update";
+#if defined(ENABLE_EFFECTENGINE)
+ case EffectsUpdate: return "effects-update";
+#endif
+ case EventUpdate: return "event-update";
+ case ImageToLedMappingUpdate: return "imageToLedMapping-update";
+ case ImageUpdate: return "ledcolors-imagestream-update";
+ case InstanceUpdate: return "instance-update";
+ case LedColorsUpdate: return "ledcolors-ledstream-update";
+ case LedsUpdate: return "leds-update";
+ case LogMsgUpdate: return "logmsg-update";
+ case PrioritiesUpdate: return "priorities-update";
+ case SettingsUpdate: return "settings-update";
+ case TokenUpdate: return "token-update";
+ case VideomodeUpdate: return "videomode-update";
+ default: return "unknown";
+ }
+ }
+
+ static bool isInstanceSpecific(Type type) {
+ switch (type) {
+ case AdjustmentUpdate:
+ case ComponentsUpdate:
+#if defined(ENABLE_EFFECTENGINE)
+ case EffectsUpdate:
+#endif
+ case ImageToLedMappingUpdate:
+ case ImageUpdate:
+ case LedColorsUpdate:
+ case LedsUpdate:
+ case PrioritiesUpdate:
+ case SettingsUpdate:
+ return true;
+ case EventUpdate:
+ case InstanceUpdate:
+ case LogMsgUpdate:
+ case TokenUpdate:
+ case VideomodeUpdate:
+ default:
+ return false;
+ }
+ }
+};
+
+class JsonApiSubscription {
+public:
+
+ JsonApiSubscription()
+ : cmd(Subscription::Unknown),
+ isAll(false)
+ {}
+
+ JsonApiSubscription(Subscription::Type cmd, bool isAll)
+ : cmd(cmd),
+ isAll(isAll)
+ {}
+
+ Subscription::Type getSubscription() const { return cmd; }
+ bool isPartOfAll() const { return isAll; }
+
+ QString toString() const {
+ return Subscription::toString(cmd);
+ }
+
+ Subscription::Type cmd;
+ bool isAll;
+};
+
+typedef QMap SubscriptionLookupMap;
+
+class ApiSubscriptionRegister {
+public:
+
+ static const SubscriptionLookupMap& getSubscriptionLookup() {
+ static const SubscriptionLookupMap subscriptionLookup {
+ { {"adjustment-update"}, { Subscription::AdjustmentUpdate, true} },
+ { {"components-update"}, { Subscription::ComponentsUpdate, true} },
+#if defined(ENABLE_EFFECTENGINE)
+ { {"effects-update"}, { Subscription::EffectsUpdate, true} },
+#endif
+ { {"event-update"}, { Subscription::EventUpdate, true} },
+ { {"imageToLedMapping-update"}, { Subscription::ImageToLedMappingUpdate, true} },
+ { {"ledcolors-imagestream-update"}, { Subscription::ImageUpdate, false} },
+ { {"ledcolors-ledstream-update"}, { Subscription::LedColorsUpdate, false} },
+ { {"instance-update"}, { Subscription::InstanceUpdate, true} },
+ { {"leds-update"}, { Subscription::LedsUpdate, true} },
+ { {"logmsg-update"}, { Subscription::LogMsgUpdate, false} },
+ { {"priorities-update"}, { Subscription::PrioritiesUpdate, true} },
+ { {"settings-update"}, { Subscription::SettingsUpdate, true} },
+ { {"token-update"}, { Subscription::TokenUpdate, true} },
+ { {"videomode-update"}, { Subscription::VideomodeUpdate, true} }
+ };
+ return subscriptionLookup;
+ }
+
+ static JsonApiSubscription getSubscriptionInfo(const QString& subscription) {
+ return getSubscriptionLookup().value({subscription});
+ }
+};
+
+#endif // JSONAPISUBSCRIPTION_H
diff --git a/include/api/JsonCB.h b/include/api/JsonCallbacks.h
similarity index 53%
rename from include/api/JsonCB.h
rename to include/api/JsonCallbacks.h
index 2d59b3eb..a5c14ce4 100644
--- a/include/api/JsonCB.h
+++ b/include/api/JsonCallbacks.h
@@ -1,51 +1,82 @@
#pragma once
+#include "api/JsonApiSubscription.h"
+#include
+#include
+
// qt incl
#include
#include
+#include
-// components def
#include
-
-// videModes
#include
-// settings
#include
-// AuthManager
#include
-
#include
class Hyperion;
class ComponentRegister;
class PriorityMuxer;
-class JsonCB : public QObject
+class JsonCallbacks : public QObject
{
Q_OBJECT
public:
- JsonCB(QObject* parent);
+ JsonCallbacks(Logger* log, const QString& peerAddress, QObject* parent);
///
/// @brief Subscribe to future data updates given by cmd
- /// @param cmd The cmd which will be subscribed for
- /// @param unsubscribe Revert subscription
+ /// @param cmd The cmd which will be subscribed for
/// @return True on success, false if not found
///
- bool subscribeFor(const QString& cmd, bool unsubscribe = false);
+ bool subscribe(const QString& cmd);
+
+ ///
+ /// @brief Subscribe to future data updates given by subscription list
+ /// @param type Array of subscriptionsm
+ ///
+ QStringList subscribe(const QJsonArray& subscriptions);
+
+ ///
+ /// @brief Subscribe to future data updates given by cmd
+ /// @param cmd The cmd which will be subscribed to
+ /// @return True on success, false if not found
+ ///
+ bool subscribe(Subscription::Type subscription);
+
+ ///
+ /// @brief Unsubscribe to future data updates given by cmd
+ /// @param cmd The cmd which will be unsubscribed
+ /// @return True on success, false if not found
+ ///
+ bool unsubscribe(const QString& cmd);
+
+ ///
+ /// @brief Unsubscribe to future data updates given by subscription list
+ /// @param type Array of subscriptions
+ ///
+ QStringList unsubscribe(const QJsonArray& subscriptions);
+
+ ///
+ /// @brief Unsubscribe to future data updates given by cmd
+ /// @param cmd The cmd which will be subscribed to
+ /// @return True on success, false if not found
+ ///
+ bool unsubscribe(Subscription::Type cmd);
///
/// @brief Get all possible commands to subscribe for
+ /// @param fullList Return all possible commands or those not triggered by API requests (subscriptions="ALL")
/// @return The list of commands
///
- QStringList getCommands() { return _availableCommands; };
-
+ QStringList getCommands(bool fullList = true) const;
///
/// @brief Get all subscribed commands
/// @return The list of commands
///
- QStringList getSubscribedCommands() { return _subscribedCommands; };
+ QStringList getSubscribedCommands() const;
///
/// @brief Reset subscriptions, disconnect all signals
@@ -124,18 +155,49 @@ private slots:
///
void handleTokenChange(const QVector &def);
+ ///
+ /// @brief Is called whenever the current Hyperion instance pushes new led raw values (if enabled)
+ /// @param ledColors The current led colors
+ ///
+ void handleLedColorUpdate(const std::vector &ledColors);
+
+ ///
+ /// @brief Is called whenever the current Hyperion instance pushes new image update (if enabled)
+ /// @param image The current image
+ ///
+ void handleImageUpdate(const Image &image);
+
+ ///
+ /// @brief Process and push new log messages from logger (if enabled)
+ ///
+ void handleLogMessageUpdate(const Logger::T_LOG_MESSAGE &);
+
+ ///
+ /// @brief Is called whenever an event is triggert
+ /// @param image The current event
+ ///
+ void handleEventUpdate(const Event &event);
+
private:
- /// pointer of Hyperion instance
+
+ /// construct callback msg
+ void doCallback(Subscription::Type cmd, const QVariant& data);
+
+ Logger *_log;
Hyperion* _hyperion;
+
+ /// The peer address of the client
+ QString _peerAddress;
+
/// pointer of comp register
ComponentRegister* _componentRegister;
/// priority muxer instance
PriorityMuxer* _prioMuxer;
- /// contains all available commands
- QStringList _availableCommands;
+
/// contains active subscriptions
- QStringList _subscribedCommands;
- /// construct callback msg
- void doCallback(const QString& cmd, const QVariant& data);
+ QSet _subscribedCommands;
+
+ /// flag to determine state of log streaming
+ bool _islogMsgStreamingActive;
};
diff --git a/include/api/JsonInfo.h b/include/api/JsonInfo.h
new file mode 100644
index 00000000..1346f97e
--- /dev/null
+++ b/include/api/JsonInfo.h
@@ -0,0 +1,43 @@
+#ifndef JSONINFO_H
+#define JSONINFO_H
+
+#include
+#include
+#include
+
+#include
+#include
+
+class JsonInfo
+{
+
+public:
+ static QJsonArray getAdjustmentInfo(const Hyperion* hyperion, Logger* log);
+ static QJsonArray getPrioritiestInfo(const Hyperion* hyperion);
+ static QJsonArray getPrioritiestInfo(int currentPriority, const PriorityMuxer::InputsMap& activeInputs);
+ static QJsonArray getEffects(const Hyperion* hyperion);
+ static QJsonArray getAvailableScreenGrabbers();
+ static QJsonArray getAvailableVideoGrabbers();
+ static QJsonArray getAvailableAudioGrabbers();
+ static QJsonObject getGrabbers(const Hyperion* hyperion);
+ static QJsonObject getAvailableLedDevices();
+ static QJsonObject getCecInfo();
+ static QJsonArray getServices();
+ static QJsonArray getComponents(const Hyperion* hyperion);
+ static QJsonArray getInstanceInfo();
+ static QJsonArray getActiveEffects(const Hyperion* hyperion);
+ static QJsonArray getActiveColors(const Hyperion* hyperion);
+ static QJsonArray getTransformationInfo(const Hyperion* hyperion);
+ static QJsonObject getSystemInfo(const Hyperion* hyperion);
+ QJsonObject discoverSources (const QString& sourceType, const QJsonObject& params);
+
+private:
+
+ template
+ void discoverGrabber(QJsonArray& inputs, const QJsonObject& params) const;
+ QJsonArray discoverScreenInputs(const QJsonObject& params) const;
+ QJsonArray discoverVideoInputs(const QJsonObject& params) const;
+ QJsonArray discoverAudioInputs(const QJsonObject& params) const;
+};
+
+#endif // JSONINFO_H
diff --git a/include/api/apiStructs.h b/include/api/apiStructs.h
index 112132fc..de1d6e67 100644
--- a/include/api/apiStructs.h
+++ b/include/api/apiStructs.h
@@ -2,6 +2,9 @@
#include
#include
+#include
+
+#include
struct ImageCmdData
{
diff --git a/include/db/AuthTable.h b/include/db/AuthTable.h
index c6495ea5..161c3856 100644
--- a/include/db/AuthTable.h
+++ b/include/db/AuthTable.h
@@ -8,6 +8,11 @@
#include
#include
+namespace hyperion {
+const char DEFAULT_USER[] = "Hyperion";
+const char DEFAULT_PASSWORD[] = "hyperion";
+}
+
///
/// @brief Authentication table interface
///
@@ -149,10 +154,10 @@ public:
inline bool resetHyperionUser()
{
QVariantMap map;
- map["password"] = calcPasswordHashOfUser("Hyperion", "hyperion");
+ map["password"] = calcPasswordHashOfUser(hyperion::DEFAULT_USER, hyperion::DEFAULT_PASSWORD);
VectorPair cond;
- cond.append(CPair("user", "Hyperion"));
+ cond.append(CPair("user", hyperion::DEFAULT_USER));
return updateRecord(cond, map);
}
diff --git a/include/events/EventEnum.h b/include/events/EventEnum.h
index 54f9c2c8..8d7934a8 100644
--- a/include/events/EventEnum.h
+++ b/include/events/EventEnum.h
@@ -13,7 +13,8 @@ enum class Event
ResumeIdle,
ToggleIdle,
Reload,
- Restart
+ Restart,
+ Quit
};
inline const char* eventToString(Event event)
@@ -24,6 +25,7 @@ inline const char* eventToString(Event event)
case Event::Resume: return "Resume";
case Event::ToggleSuspend: return "ToggleSuspend";
case Event::Idle: return "Idle";
+ case Event::Quit: return "Quit";
case Event::ResumeIdle: return "ResumeIdle";
case Event::ToggleIdle: return "ToggleIdle";
case Event::Reload: return "Reload";
@@ -39,6 +41,7 @@ inline Event stringToEvent(const QString& event)
if (event.compare("Resume")==0) return Event::Resume;
if (event.compare("ToggleSuspend")==0) return Event::ToggleSuspend;
if (event.compare("Idle")==0) return Event::Idle;
+ if (event.compare("Quit")==0) return Event::Quit;
if (event.compare("ResumeIdle")==0) return Event::ResumeIdle;
if (event.compare("ToggleIdle")==0) return Event::ToggleIdle;
if (event.compare("Reload")==0) return Event::Reload;
diff --git a/include/events/OsEventHandler.h b/include/events/OsEventHandler.h
index e65865f5..6bed4f28 100644
--- a/include/events/OsEventHandler.h
+++ b/include/events/OsEventHandler.h
@@ -29,6 +29,7 @@ public:
public slots:
void suspend(bool sleep);
void lock(bool isLocked);
+ void quit();
virtual void handleSettingsUpdate(settings::type type, const QJsonDocument& config);
@@ -101,6 +102,7 @@ public:
void handleSignal(int signum);
+
private:
static OsEventHandlerLinux* getInstance();
diff --git a/include/grabber/GrabberConfig.h b/include/grabber/GrabberConfig.h
new file mode 100644
index 00000000..f3a575c2
--- /dev/null
+++ b/include/grabber/GrabberConfig.h
@@ -0,0 +1,58 @@
+#ifndef GRABBERCONFIG_H
+#define GRABBERCONFIG_H
+
+#if defined(ENABLE_MF)
+#include
+#elif defined(ENABLE_V4L2)
+#include
+#endif
+
+#if defined(ENABLE_AUDIO)
+#include
+
+#ifdef WIN32
+#include
+#endif
+
+#ifdef __linux__
+#include
+#endif
+#endif
+
+#ifdef ENABLE_QT
+#include
+#endif
+
+#ifdef ENABLE_DX
+#include
+#endif
+
+#if defined(ENABLE_X11)
+#include
+#endif
+
+#if defined(ENABLE_XCB)
+#include
+#endif
+
+#if defined(ENABLE_DX)
+#include
+#endif
+
+#if defined(ENABLE_FB)
+#include
+#endif
+
+#if defined(ENABLE_DISPMANX)
+#include
+#endif
+
+#if defined(ENABLE_AMLOGIC)
+#include
+#endif
+
+#if defined(ENABLE_OSX)
+#include
+#endif
+
+#endif // GRABBERCONFIG_H
diff --git a/include/hyperion/AuthManager.h b/include/hyperion/AuthManager.h
index 3ee594c5..790f70b6 100644
--- a/include/hyperion/AuthManager.h
+++ b/include/hyperion/AuthManager.h
@@ -3,6 +3,8 @@
#include
#include
+#include
+
//qt
#include
#include
@@ -41,24 +43,12 @@ public:
///
QString getID() const { return _uuid; }
- ///
- /// @brief Check authorization is required according to the user setting
- /// @return True if authorization required else false
- ///
- bool isAuthRequired() const { return _authRequired; }
-
///
/// @brief Check if authorization is required for local network connections
/// @return True if authorization required else false
///
bool isLocalAuthRequired() const { return _localAuthRequired; }
- ///
- /// @brief Check if authorization is required for local network connections for admin access
- /// @return True if authorization required else false
- ///
- bool isLocalAdminAuthRequired() const { return _localAdminAuthRequired; }
-
///
/// @brief Reset Hyperion user
/// @return True on success else false
@@ -172,7 +162,7 @@ public slots:
/// @param usr the defined user
/// @return The token
///
- QString getUserToken(const QString &usr = "Hyperion") const;
+ QString getUserToken(const QString &usr = hyperion::DEFAULT_USER) const;
///
/// @brief Get all available token entries
@@ -230,15 +220,9 @@ private:
/// All pending requests
QMap _pendingRequests;
- /// Reflect state of global auth
- bool _authRequired;
-
/// Reflect state of local auth
bool _localAuthRequired;
- /// Reflect state of local admin auth
- bool _localAdminAuthRequired;
-
/// Timer for counting against pendingRequest timeouts
QTimer *_timer;
diff --git a/include/hyperion/Hyperion.h b/include/hyperion/Hyperion.h
index 3a442f40..a15eda8e 100644
--- a/include/hyperion/Hyperion.h
+++ b/include/hyperion/Hyperion.h
@@ -67,6 +67,7 @@ class Hyperion : public QObject
Q_OBJECT
public:
/// Type definition of the info structure used by the priority muxer
+ using InputsMap = PriorityMuxer::InputsMap;
using InputInfo = PriorityMuxer::InputInfo;
///
@@ -107,7 +108,7 @@ public:
///
QString getActiveDeviceType() const;
- bool getReadOnlyMode() {return _readOnlyMode; }
+ bool getReadOnlyMode() const {return _readOnlyMode; }
public slots:
@@ -235,13 +236,13 @@ public slots:
/// @param priority The priority channel of the effect
/// @param timeout The timeout of the effect (after the timout, the effect will be cleared)
int setEffect(const QString &effectName
- , const QJsonObject &args
- , int priority
- , int timeout = PriorityMuxer::ENDLESS
- , const QString &pythonScript = ""
- , const QString &origin="System"
- , const QString &imageData = ""
- );
+ , const QJsonObject &args
+ , int priority
+ , int timeout = PriorityMuxer::ENDLESS
+ , const QString &pythonScript = ""
+ , const QString &origin="System"
+ , const QString &imageData = ""
+ );
/// Get the list of available effects
/// @return The list of available effects
@@ -303,7 +304,14 @@ public slots:
QList getActivePriorities() const;
///
- /// Returns the information of a specific priorrity channel
+ /// Returns the information of all priority channels.
+ ///
+ /// @return The information fo all priority channels
+ ///
+ PriorityMuxer::InputsMap getPriorityInfo() const;
+
+ ///
+ /// Returns the information of a specific priority channel
///
/// @param[in] priority The priority channel
///
@@ -346,7 +354,7 @@ public slots:
/// @brief Get the component Register
/// return Component register pointer
///
- ComponentRegister* getComponentRegister() { return _componentRegister; }
+ ComponentRegister* getComponentRegister() const { return _componentRegister; }
///
/// @brief Called from components to update their current state. DO NOT CALL FROM USERS
diff --git a/include/hyperion/HyperionIManager.h b/include/hyperion/HyperionIManager.h
index 03e71690..d7610d5b 100644
--- a/include/hyperion/HyperionIManager.h
+++ b/include/hyperion/HyperionIManager.h
@@ -55,10 +55,16 @@ public slots:
Hyperion* getHyperionInstance(quint8 instance = 0);
///
- /// @brief Get instance data of all instaces in db + running state
+ /// @brief Get instance data of all instances in db + running state
///
QVector getInstanceData() const;
+
+ ///
+ /// @brief Get all instance indicies of running instances
+ ///
+ QList getRunningInstanceIdx() const;
+
///
/// @brief Start a Hyperion instance
/// @param instance Instance index
diff --git a/include/hyperion/PriorityMuxer.h b/include/hyperion/PriorityMuxer.h
index ed8d4fb5..d307843b 100644
--- a/include/hyperion/PriorityMuxer.h
+++ b/include/hyperion/PriorityMuxer.h
@@ -141,6 +141,13 @@ public:
///
QList getPriorities() const;
+ ///
+ /// Returns the information of all priority channels.
+ ///
+ /// @return The information fo all priority channels
+ ///
+ InputsMap getInputInfo() const;
+
///
/// Returns the information of a specified priority channel.
/// If a priority is no longer available the _lowestPriorityInfo (255) is returned
diff --git a/include/utils/JsonUtils.h b/include/utils/JsonUtils.h
index 694e279f..10b89d90 100644
--- a/include/utils/JsonUtils.h
+++ b/include/utils/JsonUtils.h
@@ -3,6 +3,8 @@
#include
#include
+#include
+#include
#include
namespace JsonUtils {
@@ -14,7 +16,7 @@ namespace JsonUtils {
/// @param[in] ignError Ignore errors during file read (no log output)
/// @return true on success else false
///
- bool readFile(const QString& path, QJsonObject& obj, Logger* log, bool ignError=false);
+ QPair readFile(const QString& path, QJsonObject& obj, Logger* log, bool ignError=false);
///
/// @brief read a schema file and resolve $refs
@@ -33,7 +35,7 @@ namespace JsonUtils {
/// @param[in] log The logger of the caller to print errors
/// @return true on success else false
///
- bool parse(const QString& path, const QString& data, QJsonObject& obj, Logger* log);
+ QPair parse(const QString& path, const QString& data, QJsonObject& obj, Logger* log);
///
/// @brief parse a json QString and get a QJsonArray. Overloaded function
@@ -42,8 +44,8 @@ namespace JsonUtils {
/// @param[out] arr Retuns the parsed QJsonArray
/// @param[in] log The logger of the caller to print errors
/// @return true on success else false
- ///
- bool parse(const QString& path, const QString& data, QJsonArray& arr, Logger* log);
+ //
+ QPair parse(const QString& path, const QString& data, QJsonArray& arr, Logger* log);
///
/// @brief parse a json QString and get a QJsonDocument
@@ -53,7 +55,7 @@ namespace JsonUtils {
/// @param[in] log The logger of the caller to print errors
/// @return true on success else false
///
- bool parse(const QString& path, const QString& data, QJsonDocument& doc, Logger* log);
+ QPair parse(const QString& path, const QString& data, QJsonDocument& doc, Logger* log);
///
/// @brief Validate json data against a schema
@@ -63,7 +65,7 @@ namespace JsonUtils {
/// @param[in] log The logger of the caller to print errors
/// @return true on success else false
///
- bool validate(const QString& file, const QJsonObject& json, const QString& schemaPath, Logger* log);
+ QPair validate(const QString& file, const QJsonObject& json, const QString& schemaPath, Logger* log);
///
/// @brief Validate json data against a schema
@@ -73,7 +75,7 @@ namespace JsonUtils {
/// @param[in] log The logger of the caller to print errors
/// @return true on success else false
///
- bool validate(const QString& file, const QJsonObject& json, const QJsonObject& schema, Logger* log);
+ QPair validate(const QString& file, const QJsonObject& json, const QJsonObject& schema, Logger* log);
///
/// @brief Write json data to file
diff --git a/include/utils/NetOrigin.h b/include/utils/NetOrigin.h
index 75796e66..648d3de3 100644
--- a/include/utils/NetOrigin.h
+++ b/include/utils/NetOrigin.h
@@ -47,7 +47,9 @@ private slots:
private:
Logger* _log;
/// True when internet access is allowed
- bool _internetAccessAllowed;
+ bool _isInternetAccessAllowed;
+ /// True when internet access is restricted by a white list
+ bool _isInternetAccessRestricted;
/// Whitelisted ip addresses
QList _ipWhitelist;
diff --git a/include/utils/jsonschema/QJsonFactory.h b/include/utils/jsonschema/QJsonFactory.h
index 2fcf5882..1a9190cd 100644
--- a/include/utils/jsonschema/QJsonFactory.h
+++ b/include/utils/jsonschema/QJsonFactory.h
@@ -31,7 +31,9 @@ public:
if (!schemaChecker.validate(configTree).first)
{
for (int i = 0; i < messages.size(); ++i)
+ {
std::cout << messages[i].toStdString() << std::endl;
+ }
std::cerr << "Validation failed for configuration file: " << config.toStdString() << std::endl;
return -3;
@@ -61,9 +63,10 @@ public:
if (error.error != QJsonParseError::NoError)
{
// report to the user the failure and their locations in the document.
- int errorLine(0), errorColumn(0);
+ int errorLine(0);
+ int errorColumn(0);
- for( int i=0, count=qMin( error.offset,config.size()); i
// stl includes
-#include
-#include
// Qt includes
#include
@@ -27,90 +25,91 @@
// ledmapping int <> string transform methods
#include
-// api includes
-#include
-
using namespace hyperion;
+// Constants
+namespace {
+
+const int IMAGE_HEIGHT_MAX = 2000;
+const int IMAGE_WIDTH_MAX = 2000;
+const int IMAGE_SCALE = 2000;
+}
+
API::API(Logger *log, bool localConnection, QObject *parent)
- : QObject(parent)
+ : QObject(parent)
{
qRegisterMetaType("int64_t");
qRegisterMetaType("VideoMode");
qRegisterMetaType>("std::map");
- // Init
- _log = log;
- _authManager = AuthManager::getInstance();
+ // Init
+ _log = log;
+ _authManager = AuthManager::getInstance();
_instanceManager = HyperionIManager::getInstance();
- _localConnection = localConnection;
+ _localConnection = localConnection;
- _authorized = false;
- _adminAuthorized = false;
+ _authorized = false;
+ _adminAuthorized = false;
- _currInstanceIndex = 0;
+ _currInstanceIndex = 0;
- // connect to possible token responses that has been requested
- connect(_authManager, &AuthManager::tokenResponse, [=] (bool success, QObject *caller, const QString &token, const QString &comment, const QString &id, const int &tan)
- {
- if (this == caller)
- emit onTokenResponse(success, token, comment, id, tan);
- });
+ // connect to possible token responses that has been requested
+ connect(_authManager, &AuthManager::tokenResponse, this, [=] (bool success, const QObject *caller, const QString &token, const QString &comment, const QString &tokenId, const int &tan)
+ {
+ if (this == caller)
+ {
+ emit onTokenResponse(success, token, comment, tokenId, tan);
+ }
+ });
- // connect to possible startInstance responses that has been requested
- connect(_instanceManager, &HyperionIManager::startInstanceResponse, [=] (QObject *caller, const int &tan)
- {
- if (this == caller)
- emit onStartInstanceResponse(tan);
- });
+ connect(_instanceManager, &HyperionIManager::startInstanceResponse, this, [=] (const QObject *caller, const int &tan)
+ {
+ if (this == caller)
+ {
+ emit onStartInstanceResponse(tan);
+ }
+ });
}
void API::init()
{
_hyperion = _instanceManager->getHyperionInstance(0);
+ _authorized = false;
- bool apiAuthRequired = _authManager->isAuthRequired();
-
- // For security we block external connections if default PW is set
- if (!_localConnection && API::hasHyperionDefaultPw())
- {
- emit forceClose();
- }
- // if this is localConnection and network allows unauth locals, set authorized flag
- if (apiAuthRequired && _localConnection)
+ // For security we block external connections, if default PW is set
+ if (!_localConnection && API::hasHyperionDefaultPw())
{
- _authorized = !_authManager->isLocalAuthRequired();
+ Warning(_log, "Non local network connect attempt identified, but default Hyperion passwort set! - Reject connection.");
+ emit forceClose();
}
- // admin access is allowed, when the connection is local and the option for local admin isn't set. Con: All local connections get full access
- if (_localConnection)
- {
- _adminAuthorized = !_authManager->isLocalAdminAuthRequired();
- // just in positive direction
- if (_adminAuthorized)
+ // if this is localConnection and network allows unauth locals
+ if ( _localConnection && !_authManager->isLocalAuthRequired())
+ {
+ _authorized = true;
+ }
+
+ // // admin access is only allowed after login via user & password or via authorization via token.
+ _adminAuthorized = false;
+}
+
+void API::setColor(int priority, const std::vector &ledColors, int timeout_ms, const QString &origin, hyperion::Components /*callerComp*/)
+{
+ if (ledColors.size() % 3 == 0)
+ {
+ std::vector fledColors;
+ for (unsigned i = 0; i < ledColors.size(); i += 3)
{
- _authorized = true;
+ fledColors.emplace_back(ColorRgb{ledColors[i], ledColors[i + 1], ledColors[i + 2]});
}
- }
+ QMetaObject::invokeMethod(_hyperion, "setColor", Qt::QueuedConnection, Q_ARG(int, priority), Q_ARG(std::vector, fledColors), Q_ARG(int, timeout_ms), Q_ARG(QString, origin));
+ }
}
-void API::setColor(int priority, const std::vector &ledColors, int timeout_ms, const QString &origin, hyperion::Components callerComp)
+bool API::setImage(ImageCmdData &data, hyperion::Components comp, QString &replyMsg, hyperion::Components /*callerComp*/)
{
- std::vector fledColors;
- if (ledColors.size() % 3 == 0)
- {
- for (unsigned i = 0; i < ledColors.size(); i += 3)
- {
- fledColors.emplace_back(ColorRgb{ledColors[i], ledColors[i + 1], ledColors[i + 2]});
- }
- QMetaObject::invokeMethod(_hyperion, "setColor", Qt::QueuedConnection, Q_ARG(int, priority), Q_ARG(std::vector, fledColors), Q_ARG(int, timeout_ms), Q_ARG(QString, origin));
- }
-}
-
-bool API::setImage(ImageCmdData &data, hyperion::Components comp, QString &replyMsg, hyperion::Components callerComp)
-{
- // truncate name length
- data.imgName.truncate(16);
+ // truncate name length
+ data.imgName.truncate(16);
if (!data.format.isEmpty())
{
@@ -128,424 +127,475 @@ bool API::setImage(ImageCmdData &data, hyperion::Components comp, QString &reply
}
QImage img = QImage::fromData(data.data, QSTRING_CSTR(data.format));
- if (img.isNull())
- {
+ if (img.isNull())
+ {
replyMsg = "Failed to parse picture, the file might be corrupted or content does not match the given format [" + data.format + "]";
- return false;
- }
+ return false;
+ }
- // check for requested scale
- if (data.scale > 24)
- {
- if (img.height() > data.scale)
- {
- img = img.scaledToHeight(data.scale);
- }
- if (img.width() > data.scale)
- {
- img = img.scaledToWidth(data.scale);
- }
- }
+ // check for requested scale
+ if (data.scale > 24)
+ {
+ if (img.height() > data.scale)
+ {
+ img = img.scaledToHeight(data.scale);
+ }
+ if (img.width() > data.scale)
+ {
+ img = img.scaledToWidth(data.scale);
+ }
+ }
- // check if we need to force a scale
- if (img.width() > 2000 || img.height() > 2000)
- {
- data.scale = 2000;
- if (img.height() > data.scale)
- {
- img = img.scaledToHeight(data.scale);
- }
- if (img.width() > data.scale)
- {
- img = img.scaledToWidth(data.scale);
- }
- }
+ // check if we need to force a scale
+ if (img.width() > IMAGE_WIDTH_MAX || img.height() > IMAGE_HEIGHT_MAX)
+ {
+ data.scale = IMAGE_SCALE;
+ if (img.height() > data.scale)
+ {
+ img = img.scaledToHeight(data.scale);
+ }
+ if (img.width() > data.scale)
+ {
+ img = img.scaledToWidth(data.scale);
+ }
+ }
- data.width = img.width();
- data.height = img.height();
+ data.width = img.width();
+ data.height = img.height();
- // extract image
- img = img.convertToFormat(QImage::Format_ARGB32_Premultiplied);
- data.data.clear();
- data.data.reserve(img.width() * img.height() * 3);
- for (int i = 0; i < img.height(); ++i)
- {
- const QRgb *scanline = reinterpret_cast(img.scanLine(i));
- for (int j = 0; j < img.width(); ++j)
- {
- data.data.append((char)qRed(scanline[j]));
- data.data.append((char)qGreen(scanline[j]));
- data.data.append((char)qBlue(scanline[j]));
- }
- }
- }
- else
- {
- // check consistency of the size of the received data
- if (data.data.size() != data.width * data.height * 3)
- {
- replyMsg = "Size of image data does not match with the width and height";
- return false;
- }
- }
+ // extract image
+ img = img.convertToFormat(QImage::Format_ARGB32_Premultiplied);
+ data.data.clear();
+ data.data.reserve(static_cast(img.width() * img.height() * 3));
+ for (int i = 0; i < img.height(); ++i)
+ {
+ const QRgb *scanline = reinterpret_cast(img.scanLine(i));
+ for (int j = 0; j < img.width(); ++j)
+ {
+ data.data.append(static_cast(qRed(scanline[j])));
+ data.data.append(static_cast(qGreen(scanline[j])));
+ data.data.append(static_cast(qBlue(scanline[j])));
+ }
+ }
+ }
+ else
+ {
+ // check consistency of the size of the received data
+ if (static_cast(data.data.size()) != static_cast(data.width) * static_cast(data.height) * 3)
+ {
+ replyMsg = "Size of image data does not match with the width and height";
+ return false;
+ }
+ }
- // copy image
- Image image(data.width, data.height);
- memcpy(image.memptr(), data.data.data(), data.data.size());
+ // copy image
+ Image image(data.width, data.height);
+ memcpy(image.memptr(), data.data.data(), static_cast(data.data.size()));
- QMetaObject::invokeMethod(_hyperion, "registerInput", Qt::QueuedConnection, Q_ARG(int, data.priority), Q_ARG(hyperion::Components, comp), Q_ARG(QString, data.origin), Q_ARG(QString, data.imgName));
- QMetaObject::invokeMethod(_hyperion, "setInputImage", Qt::QueuedConnection, Q_ARG(int, data.priority), Q_ARG(Image, image), Q_ARG(int64_t, data.duration));
+ QMetaObject::invokeMethod(_hyperion, "registerInput", Qt::QueuedConnection, Q_ARG(int, data.priority), Q_ARG(hyperion::Components, comp), Q_ARG(QString, data.origin), Q_ARG(QString, data.imgName));
+ QMetaObject::invokeMethod(_hyperion, "setInputImage", Qt::QueuedConnection, Q_ARG(int, data.priority), Q_ARG(Image, image), Q_ARG(int64_t, data.duration));
- return true;
+ return true;
}
-bool API::clearPriority(int priority, QString &replyMsg, hyperion::Components callerComp)
+bool API::clearPriority(int priority, QString &replyMsg, hyperion::Components /*callerComp*/)
{
- if (priority < 0 || (priority > 0 && priority < 254))
- {
- QMetaObject::invokeMethod(_hyperion, "clear", Qt::QueuedConnection, Q_ARG(int, priority));
- }
- else
- {
- replyMsg = QString("Priority %1 is not allowed to be cleared").arg(priority);
- return false;
- }
- return true;
+ if (priority < 0 || (priority > 0 && priority < PriorityMuxer::BG_PRIORITY))
+ {
+ QMetaObject::invokeMethod(_hyperion, "clear", Qt::QueuedConnection, Q_ARG(int, priority));
+ }
+ else
+ {
+ replyMsg = QString("Priority %1 is not allowed to be cleared").arg(priority);
+ return false;
+ }
+ return true;
}
-bool API::setComponentState(const QString &comp, bool &compState, QString &replyMsg, hyperion::Components callerComp)
+bool API::setComponentState(const QString &comp, bool &compState, QString &replyMsg, hyperion::Components /*callerComp*/)
{
- Components component = stringToComponent(comp);
+ Components component = stringToComponent(comp);
- if (component != COMP_INVALID)
- {
- QMetaObject::invokeMethod(_hyperion, "compStateChangeRequest", Qt::QueuedConnection, Q_ARG(hyperion::Components, component), Q_ARG(bool, compState));
- return true;
- }
- replyMsg = QString("Unknown component name: %1").arg(comp);
- return false;
+ if (component != COMP_INVALID)
+ {
+ QMetaObject::invokeMethod(_hyperion, "compStateChangeRequest", Qt::QueuedConnection, Q_ARG(hyperion::Components, component), Q_ARG(bool, compState));
+ return true;
+ }
+ replyMsg = QString("Unknown component name: %1").arg(comp);
+ return false;
}
-void API::setLedMappingType(int type, hyperion::Components callerComp)
+void API::setLedMappingType(int type, hyperion::Components /*callerComp*/)
{
- QMetaObject::invokeMethod(_hyperion, "setLedMappingType", Qt::QueuedConnection, Q_ARG(int, type));
+ QMetaObject::invokeMethod(_hyperion, "setLedMappingType", Qt::QueuedConnection, Q_ARG(int, type));
}
-void API::setVideoMode(VideoMode mode, hyperion::Components callerComp)
+void API::setVideoMode(VideoMode mode, hyperion::Components /*callerComp*/)
{
- QMetaObject::invokeMethod(_hyperion, "setVideoMode", Qt::QueuedConnection, Q_ARG(VideoMode, mode));
+ QMetaObject::invokeMethod(_hyperion, "setVideoMode", Qt::QueuedConnection, Q_ARG(VideoMode, mode));
}
#if defined(ENABLE_EFFECTENGINE)
-bool API::setEffect(const EffectCmdData &dat, hyperion::Components callerComp)
+bool API::setEffect(const EffectCmdData &dat, hyperion::Components /*callerComp*/)
{
- int res;
- if (!dat.args.isEmpty())
- {
- QMetaObject::invokeMethod(_hyperion, "setEffect", Qt::BlockingQueuedConnection, Q_RETURN_ARG(int, res), Q_ARG(QString, dat.effectName), Q_ARG(QJsonObject, dat.args), Q_ARG(int, dat.priority), Q_ARG(int, dat.duration), Q_ARG(QString, dat.pythonScript), Q_ARG(QString, dat.origin), Q_ARG(QString, dat.data));
- }
- else
- {
- QMetaObject::invokeMethod(_hyperion, "setEffect", Qt::BlockingQueuedConnection, Q_RETURN_ARG(int, res), Q_ARG(QString, dat.effectName), Q_ARG(int, dat.priority), Q_ARG(int, dat.duration), Q_ARG(QString, dat.origin));
- }
+ int isStarted;
+ if (!dat.args.isEmpty())
+ {
+ QMetaObject::invokeMethod(_hyperion, "setEffect", Qt::BlockingQueuedConnection, Q_RETURN_ARG(int, isStarted), Q_ARG(QString, dat.effectName), Q_ARG(QJsonObject, dat.args), Q_ARG(int, dat.priority), Q_ARG(int, dat.duration), Q_ARG(QString, dat.pythonScript), Q_ARG(QString, dat.origin), Q_ARG(QString, dat.data));
+ }
+ else
+ {
+ QMetaObject::invokeMethod(_hyperion, "setEffect", Qt::BlockingQueuedConnection, Q_RETURN_ARG(int, isStarted), Q_ARG(QString, dat.effectName), Q_ARG(int, dat.priority), Q_ARG(int, dat.duration), Q_ARG(QString, dat.origin));
+ }
- return res >= 0;
+ return isStarted >= 0;
}
#endif
-void API::setSourceAutoSelect(bool state, hyperion::Components callerComp)
+void API::setSourceAutoSelect(bool state, hyperion::Components /*callerComp*/)
{
- QMetaObject::invokeMethod(_hyperion, "setSourceAutoSelect", Qt::QueuedConnection, Q_ARG(bool, state));
+ QMetaObject::invokeMethod(_hyperion, "setSourceAutoSelect", Qt::QueuedConnection, Q_ARG(bool, state));
}
-void API::setVisiblePriority(int priority, hyperion::Components callerComp)
+void API::setVisiblePriority(int priority, hyperion::Components /*callerComp*/)
{
- QMetaObject::invokeMethod(_hyperion, "setVisiblePriority", Qt::QueuedConnection, Q_ARG(int, priority));
+ QMetaObject::invokeMethod(_hyperion, "setVisiblePriority", Qt::QueuedConnection, Q_ARG(int, priority));
}
void API::registerInput(int priority, hyperion::Components component, const QString &origin, const QString &owner, hyperion::Components callerComp)
{
- if (_activeRegisters.count(priority))
- _activeRegisters.erase(priority);
+ if (_activeRegisters.count(priority) != 0)
+ {
+ _activeRegisters.erase(priority);
+ }
- _activeRegisters.insert({priority, registerData{component, origin, owner, callerComp}});
+ _activeRegisters.insert({priority, registerData{component, origin, owner, callerComp}});
- QMetaObject::invokeMethod(_hyperion, "registerInput", Qt::QueuedConnection, Q_ARG(int, priority), Q_ARG(hyperion::Components, component), Q_ARG(QString, origin), Q_ARG(QString, owner));
+ QMetaObject::invokeMethod(_hyperion, "registerInput", Qt::QueuedConnection, Q_ARG(int, priority), Q_ARG(hyperion::Components, component), Q_ARG(QString, origin), Q_ARG(QString, owner));
}
void API::unregisterInput(int priority)
{
- if (_activeRegisters.count(priority))
- _activeRegisters.erase(priority);
+ if (_activeRegisters.count(priority) != 0)
+ {
+ _activeRegisters.erase(priority);
+ }
}
bool API::setHyperionInstance(quint8 inst)
{
- if (_currInstanceIndex == inst)
- return true;
- bool isRunning;
- QMetaObject::invokeMethod(_instanceManager, "IsInstanceRunning", Qt::DirectConnection, Q_RETURN_ARG(bool, isRunning), Q_ARG(quint8, inst));
- if (!isRunning)
- return false;
+ if (_currInstanceIndex == inst)
+ {
+ return true;
+ }
- disconnect(_hyperion, 0, this, 0);
- QMetaObject::invokeMethod(_instanceManager, "getHyperionInstance", Qt::DirectConnection, Q_RETURN_ARG(Hyperion *, _hyperion), Q_ARG(quint8, inst));
- _currInstanceIndex = inst;
- return true;
+ bool isRunning;
+ QMetaObject::invokeMethod(_instanceManager, "IsInstanceRunning", Qt::DirectConnection, Q_RETURN_ARG(bool, isRunning), Q_ARG(quint8, inst));
+ if (!isRunning)
+ {
+ return false;
+ }
+
+ disconnect(_hyperion, nullptr, this, nullptr);
+ QMetaObject::invokeMethod(_instanceManager, "getHyperionInstance", Qt::DirectConnection, Q_RETURN_ARG(Hyperion *, _hyperion), Q_ARG(quint8, inst));
+ _currInstanceIndex = inst;
+ return true;
}
bool API::isHyperionEnabled()
{
- int res;
- QMetaObject::invokeMethod(_hyperion, "isComponentEnabled", Qt::BlockingQueuedConnection, Q_RETURN_ARG(int, res), Q_ARG(hyperion::Components, hyperion::COMP_ALL));
- return res > 0;
+ int isEnabled;
+ QMetaObject::invokeMethod(_hyperion, "isComponentEnabled", Qt::BlockingQueuedConnection, Q_RETURN_ARG(int, isEnabled), Q_ARG(hyperion::Components, hyperion::COMP_ALL));
+ return isEnabled > 0;
}
-QVector API::getAllInstanceData()
+QVector API::getAllInstanceData() const
{
- QVector vec;
- QMetaObject::invokeMethod(_instanceManager, "getInstanceData", Qt::DirectConnection, Q_RETURN_ARG(QVector, vec));
- return vec;
+ QVector vec;
+ QMetaObject::invokeMethod(_instanceManager, "getInstanceData", Qt::DirectConnection, Q_RETURN_ARG(QVector, vec));
+ return vec;
}
bool API::startInstance(quint8 index, int tan)
{
- bool res;
- (_instanceManager->thread() != this->thread())
- ? QMetaObject::invokeMethod(_instanceManager, "startInstance", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, res), Q_ARG(quint8, index), Q_ARG(bool, false), Q_ARG(QObject*, this), Q_ARG(int, tan))
- : res = _instanceManager->startInstance(index, false, this, tan);
+ bool isStarted;
+ (_instanceManager->thread() != this->thread())
+ ? QMetaObject::invokeMethod(_instanceManager, "startInstance", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, isStarted), Q_ARG(quint8, index), Q_ARG(bool, false), Q_ARG(QObject*, this), Q_ARG(int, tan))
+ : isStarted = _instanceManager->startInstance(index, false, this, tan);
- return res;
+ return isStarted;
}
void API::stopInstance(quint8 index)
{
- QMetaObject::invokeMethod(_instanceManager, "stopInstance", Qt::QueuedConnection, Q_ARG(quint8, index));
+ QMetaObject::invokeMethod(_instanceManager, "stopInstance", Qt::QueuedConnection, Q_ARG(quint8, index));
}
bool API::deleteInstance(quint8 index, QString &replyMsg)
{
- if (_adminAuthorized)
- {
- QMetaObject::invokeMethod(_instanceManager, "deleteInstance", Qt::QueuedConnection, Q_ARG(quint8, index));
- return true;
- }
- replyMsg = NO_AUTH;
- return false;
+ if (_adminAuthorized)
+ {
+ QMetaObject::invokeMethod(_instanceManager, "deleteInstance", Qt::QueuedConnection, Q_ARG(quint8, index));
+ return true;
+ }
+ replyMsg = NO_AUTHORIZATION;
+ return false;
}
QString API::createInstance(const QString &name)
{
- if (_adminAuthorized)
- {
- bool success;
- QMetaObject::invokeMethod(_instanceManager, "createInstance", Qt::DirectConnection, Q_RETURN_ARG(bool, success), Q_ARG(QString, name));
- if (!success)
- return QString("Instance name '%1' is already in use").arg(name);
-
- return "";
- }
- return NO_AUTH;
+ if (_adminAuthorized)
+ {
+ bool success;
+ QMetaObject::invokeMethod(_instanceManager, "createInstance", Qt::DirectConnection, Q_RETURN_ARG(bool, success), Q_ARG(QString, name));
+ if (!success)
+ {
+ return QString("Instance name '%1' is already in use").arg(name);
+ }
+ return "";
+ }
+ return NO_AUTHORIZATION;
}
QString API::setInstanceName(quint8 index, const QString &name)
{
- if (_adminAuthorized)
- {
- QMetaObject::invokeMethod(_instanceManager, "saveName", Qt::QueuedConnection, Q_ARG(quint8, index), Q_ARG(QString, name));
- return "";
- }
- return NO_AUTH;
+ if (_adminAuthorized)
+ {
+ QMetaObject::invokeMethod(_instanceManager, "saveName", Qt::QueuedConnection, Q_ARG(quint8, index), Q_ARG(QString, name));
+ return "";
+ }
+ return NO_AUTHORIZATION;
}
#if defined(ENABLE_EFFECTENGINE)
QString API::deleteEffect(const QString &name)
{
- if (_adminAuthorized)
- {
- QString res;
- QMetaObject::invokeMethod(_hyperion, "deleteEffect", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QString, res), Q_ARG(QString, name));
- return res;
- }
- return NO_AUTH;
+ if (_adminAuthorized)
+ {
+ QString res;
+ QMetaObject::invokeMethod(_hyperion, "deleteEffect", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QString, res), Q_ARG(QString, name));
+ return res;
+ }
+ return NO_AUTHORIZATION;
}
QString API::saveEffect(const QJsonObject &data)
{
- if (_adminAuthorized)
- {
- QString res;
- QMetaObject::invokeMethod(_hyperion, "saveEffect", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QString, res), Q_ARG(QJsonObject, data));
- return res;
- }
- return NO_AUTH;
+ if (_adminAuthorized)
+ {
+ QString res;
+ QMetaObject::invokeMethod(_hyperion, "saveEffect", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QString, res), Q_ARG(QJsonObject, data));
+ return res;
+ }
+ return NO_AUTHORIZATION;
}
#endif
bool API::saveSettings(const QJsonObject &data)
{
- bool rc = true;
- if (!_adminAuthorized)
+ bool isSaved {true};
+ if (!_adminAuthorized)
{
- rc = false;
+ isSaved = false;
}
else
{
- QMetaObject::invokeMethod(_hyperion, "saveSettings", Qt::DirectConnection, Q_RETURN_ARG(bool, rc), Q_ARG(QJsonObject, data), Q_ARG(bool, true));
+ QMetaObject::invokeMethod(_hyperion, "saveSettings", Qt::DirectConnection, Q_RETURN_ARG(bool, isSaved), Q_ARG(QJsonObject, data), Q_ARG(bool, true));
}
- return rc;
+ return isSaved;
}
bool API::restoreSettings(const QJsonObject &data)
{
- bool rc = true;
+ bool isRestored {true};
if (!_adminAuthorized)
{
- rc = false;
+ isRestored = false;
}
else
{
- QMetaObject::invokeMethod(_hyperion, "restoreSettings", Qt::DirectConnection, Q_RETURN_ARG(bool, rc), Q_ARG(QJsonObject, data), Q_ARG(bool, true));
+ QMetaObject::invokeMethod(_hyperion, "restoreSettings", Qt::DirectConnection, Q_RETURN_ARG(bool, isRestored), Q_ARG(QJsonObject, data), Q_ARG(bool, true));
}
- return rc;
+ return isRestored;
}
bool API::updateHyperionPassword(const QString &password, const QString &newPassword)
{
- if (!_adminAuthorized)
- return false;
- bool res;
- QMetaObject::invokeMethod(_authManager, "updateUserPassword", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, res), Q_ARG(QString, "Hyperion"), Q_ARG(QString, password), Q_ARG(QString, newPassword));
- return res;
+ bool isPwUpdated {true};
+ if (!_adminAuthorized)
+ {
+ isPwUpdated = false;
+ }
+ else
+ {
+ QMetaObject::invokeMethod(_authManager, "updateUserPassword", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, isPwUpdated), Q_ARG(QString, DEFAULT_USER), Q_ARG(QString, password), Q_ARG(QString, newPassword));
+ }
+ return isPwUpdated;
}
QString API::createToken(const QString &comment, AuthManager::AuthDefinition &def)
{
- if (!_adminAuthorized)
- return NO_AUTH;
- if (comment.isEmpty())
- return "comment is empty";
- QMetaObject::invokeMethod(_authManager, "createToken", Qt::BlockingQueuedConnection, Q_RETURN_ARG(AuthManager::AuthDefinition, def), Q_ARG(QString, comment));
- return "";
+ if (!_adminAuthorized)
+ {
+ return NO_AUTHORIZATION;
+ }
+
+ if (comment.isEmpty())
+ {
+ return "Missing token comment";
+ }
+ QMetaObject::invokeMethod(_authManager, "createToken", Qt::BlockingQueuedConnection, Q_RETURN_ARG(AuthManager::AuthDefinition, def), Q_ARG(QString, comment));
+ return "";
}
-QString API::renameToken(const QString &id, const QString &comment)
+QString API::renameToken(const QString &tokenId, const QString &comment)
{
- if (!_adminAuthorized)
- return NO_AUTH;
- if (comment.isEmpty() || id.isEmpty())
- return "Empty comment or id";
+ if (!_adminAuthorized)
+ {
+ return NO_AUTHORIZATION;
+ }
- QMetaObject::invokeMethod(_authManager, "renameToken", Qt::QueuedConnection, Q_ARG(QString, id), Q_ARG(QString, comment));
- return "";
+ if (comment.isEmpty())
+ {
+ return "Missing token comment";
+ }
+
+ if (tokenId.isEmpty()) {
+ return "Missing token id";
+ }
+
+ bool isTokenRenamed {false};
+ QMetaObject::invokeMethod(_authManager, "renameToken", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, isTokenRenamed), Q_ARG(QString, tokenId), Q_ARG(QString, comment));
+
+ return (!isTokenRenamed) ? "Token does not exist" : "";
}
-QString API::deleteToken(const QString &id)
+QString API::deleteToken(const QString &tokenId)
{
- if (!_adminAuthorized)
- return NO_AUTH;
- if (id.isEmpty())
- return "Empty id";
+ if (!_adminAuthorized)
+ {
+ return NO_AUTHORIZATION;
+ }
- QMetaObject::invokeMethod(_authManager, "deleteToken", Qt::QueuedConnection, Q_ARG(QString, id));
- return "";
+ if (tokenId.isEmpty())
+ {
+ return "Missing token id";
+ }
+
+ bool isTokenDeleted {false};
+ QMetaObject::invokeMethod(_authManager, "deleteToken", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, isTokenDeleted), Q_ARG(QString, tokenId));
+
+ return (!isTokenDeleted) ? "Token does not exist" : "";
}
-void API::setNewTokenRequest(const QString &comment, const QString &id, const int &tan)
+void API::setNewTokenRequest(const QString &comment, const QString &tokenId, const int &tan)
{
- QMetaObject::invokeMethod(_authManager, "setNewTokenRequest", Qt::QueuedConnection, Q_ARG(QObject *, this), Q_ARG(QString, comment), Q_ARG(QString, id), Q_ARG(int, tan));
+ QMetaObject::invokeMethod(_authManager, "setNewTokenRequest", Qt::QueuedConnection, Q_ARG(QObject *, this), Q_ARG(QString, comment), Q_ARG(QString, tokenId), Q_ARG(int, tan));
}
-void API::cancelNewTokenRequest(const QString &comment, const QString &id)
+void API::cancelNewTokenRequest(const QString &comment, const QString &tokenId)
{
- QMetaObject::invokeMethod(_authManager, "cancelNewTokenRequest", Qt::QueuedConnection, Q_ARG(QObject *, this), Q_ARG(QString, comment), Q_ARG(QString, id));
+ QMetaObject::invokeMethod(_authManager, "cancelNewTokenRequest", Qt::QueuedConnection, Q_ARG(QObject *, this), Q_ARG(QString, comment), Q_ARG(QString, tokenId));
}
-bool API::handlePendingTokenRequest(const QString &id, bool accept)
+bool API::handlePendingTokenRequest(const QString &tokenId, bool accept)
{
- if (!_adminAuthorized)
- return false;
- QMetaObject::invokeMethod(_authManager, "handlePendingTokenRequest", Qt::QueuedConnection, Q_ARG(QString, id), Q_ARG(bool, accept));
- return true;
+ if (!_adminAuthorized)
+ {
+ return false;
+ }
+ QMetaObject::invokeMethod(_authManager, "handlePendingTokenRequest", Qt::QueuedConnection, Q_ARG(QString, tokenId), Q_ARG(bool, accept));
+ return true;
}
bool API::getTokenList(QVector &def)
{
- if (!_adminAuthorized)
- return false;
- QMetaObject::invokeMethod(_authManager, "getTokenList", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QVector, def));
- return true;
+ if (!_adminAuthorized)
+ {
+ return false;
+ }
+ QMetaObject::invokeMethod(_authManager, "getTokenList", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QVector, def));
+ return true;
}
bool API::getPendingTokenRequests(QVector &map)
{
- if (!_adminAuthorized)
- return false;
- QMetaObject::invokeMethod(_authManager, "getPendingRequests", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QVector, map));
- return true;
+ if (!_adminAuthorized)
+ {
+ return false;
+ }
+ QMetaObject::invokeMethod(_authManager, "getPendingRequests", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QVector, map));
+ return true;
}
bool API::isUserTokenAuthorized(const QString &userToken)
{
- bool res;
- QMetaObject::invokeMethod(_authManager, "isUserTokenAuthorized", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, res), Q_ARG(QString, "Hyperion"), Q_ARG(QString, userToken));
- if (res)
- {
- _authorized = true;
- _adminAuthorized = true;
- // Listen for ADMIN ACCESS protected signals
- connect(_authManager, &AuthManager::newPendingTokenRequest, this, &API::onPendingTokenRequest, Qt::UniqueConnection);
- }
- return res;
+ QMetaObject::invokeMethod(_authManager, "isUserTokenAuthorized", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, _authorized), Q_ARG(QString, DEFAULT_USER), Q_ARG(QString, userToken));
+ _adminAuthorized = _authorized;
+
+ if (_authorized)
+ {
+ // Listen for ADMIN ACCESS protected signals
+ connect(_authManager, &AuthManager::newPendingTokenRequest, this, &API::onPendingTokenRequest);
+ }
+ else
+ {
+ disconnect(_authManager, &AuthManager::newPendingTokenRequest, this, &API::onPendingTokenRequest);
+ }
+ return _authorized;
}
bool API::getUserToken(QString &userToken)
{
- if (!_adminAuthorized)
- return false;
- QMetaObject::invokeMethod(_authManager, "getUserToken", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QString, userToken));
- return true;
+ if (!_adminAuthorized)
+ {
+ return false;
+ }
+ QMetaObject::invokeMethod(_authManager, "getUserToken", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QString, userToken));
+ return true;
}
bool API::isTokenAuthorized(const QString &token)
{
(_authManager->thread() != this->thread())
- ? QMetaObject::invokeMethod(_authManager, "isTokenAuthorized", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, _authorized), Q_ARG(QString, token))
- : _authorized = _authManager->isTokenAuthorized(token);
+ ? QMetaObject::invokeMethod(_authManager, "isTokenAuthorized", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, _authorized), Q_ARG(QString, token))
+ : _authorized = _authManager->isTokenAuthorized(token);
+ _adminAuthorized = _authorized;
- return _authorized;
+ return _authorized;
}
bool API::isUserAuthorized(const QString &password)
{
- bool res;
- QMetaObject::invokeMethod(_authManager, "isUserAuthorized", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, res), Q_ARG(QString, "Hyperion"), Q_ARG(QString, password));
- if (res)
- {
- _authorized = true;
- _adminAuthorized = true;
- // Listen for ADMIN ACCESS protected signals
- connect(_authManager, &AuthManager::newPendingTokenRequest, this, &API::onPendingTokenRequest, Qt::UniqueConnection);
- }
- return res;
+ bool isUserAuthorized;
+ QMetaObject::invokeMethod(_authManager, "isUserAuthorized", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, isUserAuthorized), Q_ARG(QString, DEFAULT_USER), Q_ARG(QString, password));
+ if (isUserAuthorized)
+ {
+ _authorized = true;
+ _adminAuthorized = true;
+
+ // Listen for ADMIN ACCESS protected signals
+ connect(_authManager, &AuthManager::newPendingTokenRequest, this, &API::onPendingTokenRequest);
+ }
+ else
+ {
+ disconnect(_authManager, &AuthManager::newPendingTokenRequest, this, &API::onPendingTokenRequest);
+ }
+ return isUserAuthorized;
}
bool API::hasHyperionDefaultPw()
{
- bool res;
- QMetaObject::invokeMethod(_authManager, "isUserAuthorized", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, res), Q_ARG(QString, "Hyperion"), Q_ARG(QString, "hyperion"));
- return res;
+ bool isDefaultPassort;
+ QMetaObject::invokeMethod(_authManager, "isUserAuthorized", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, isDefaultPassort), Q_ARG(QString, DEFAULT_USER), Q_ARG(QString, DEFAULT_PASSWORD));
+ return isDefaultPassort;
}
void API::logout()
{
- _authorized = false;
- _adminAuthorized = false;
- // Stop listenig for ADMIN ACCESS protected signals
- disconnect(_authManager, &AuthManager::newPendingTokenRequest, this, &API::onPendingTokenRequest);
- stopDataConnectionss();
-}
-
-void API::stopDataConnectionss()
-{
+ _authorized = false;
+ _adminAuthorized = false;
+ // Stop listenig for ADMIN ACCESS protected signals
+ disconnect(_authManager, &AuthManager::newPendingTokenRequest, this, &API::onPendingTokenRequest);
+ stopDataConnections();
}
diff --git a/libsrc/api/CMakeLists.txt b/libsrc/api/CMakeLists.txt
index ac5cdcfd..6818e752 100644
--- a/libsrc/api/CMakeLists.txt
+++ b/libsrc/api/CMakeLists.txt
@@ -2,10 +2,14 @@ add_library(hyperion-api
${CMAKE_SOURCE_DIR}/include/api/apiStructs.h
${CMAKE_SOURCE_DIR}/include/api/API.h
${CMAKE_SOURCE_DIR}/include/api/JsonAPI.h
- ${CMAKE_SOURCE_DIR}/include/api/JsonCB.h
+ ${CMAKE_SOURCE_DIR}/include/api/JsonCallbacks.h
+ ${CMAKE_SOURCE_DIR}/include/api/JsonApiCommand.h
+ ${CMAKE_SOURCE_DIR}/include/api/JsonApiSubscription.h
+ ${CMAKE_SOURCE_DIR}/include/api/JsonInfo.h
${CMAKE_SOURCE_DIR}/libsrc/api/JsonAPI.cpp
${CMAKE_SOURCE_DIR}/libsrc/api/API.cpp
- ${CMAKE_SOURCE_DIR}/libsrc/api/JsonCB.cpp
+ ${CMAKE_SOURCE_DIR}/libsrc/api/JsonCallbacks.cpp
+ ${CMAKE_SOURCE_DIR}/libsrc/api/JsonInfo.cpp
${CMAKE_SOURCE_DIR}/libsrc/api/JSONRPC_schemas.qrc
)
diff --git a/libsrc/api/JSONRPC_schema/schema-adjustment.json b/libsrc/api/JSONRPC_schema/schema-adjustment.json
index b8856ef9..5efed868 100644
--- a/libsrc/api/JSONRPC_schema/schema-adjustment.json
+++ b/libsrc/api/JSONRPC_schema/schema-adjustment.json
@@ -7,6 +7,12 @@
"required" : true,
"enum" : ["adjustment"]
},
+ "instance" : {
+ "type": "array",
+ "required": false,
+ "items" : {},
+ "minItems": 1
+ },
"tan" : {
"type" : "integer"
},
diff --git a/libsrc/api/JSONRPC_schema/schema-clear.json b/libsrc/api/JSONRPC_schema/schema-clear.json
index c63a77f7..b55be0a1 100644
--- a/libsrc/api/JSONRPC_schema/schema-clear.json
+++ b/libsrc/api/JSONRPC_schema/schema-clear.json
@@ -7,6 +7,12 @@
"required" : true,
"enum" : ["clear"]
},
+ "instance" : {
+ "type": "array",
+ "required": false,
+ "items" : {},
+ "minItems": 1
+ },
"tan" : {
"type" : "integer"
},
diff --git a/libsrc/api/JSONRPC_schema/schema-clearall.json b/libsrc/api/JSONRPC_schema/schema-clearall.json
index 8c88cc6c..5d5d2d22 100644
--- a/libsrc/api/JSONRPC_schema/schema-clearall.json
+++ b/libsrc/api/JSONRPC_schema/schema-clearall.json
@@ -7,6 +7,12 @@
"required" : true,
"enum" : ["clearall"]
},
+ "instance" : {
+ "type": "array",
+ "required": false,
+ "items" : {},
+ "minItems": 1
+ },
"tan" : {
"type" : "integer"
}
diff --git a/libsrc/api/JSONRPC_schema/schema-color.json b/libsrc/api/JSONRPC_schema/schema-color.json
index 754c5ad3..eeeba069 100644
--- a/libsrc/api/JSONRPC_schema/schema-color.json
+++ b/libsrc/api/JSONRPC_schema/schema-color.json
@@ -7,6 +7,12 @@
"required" : true,
"enum" : ["color"]
},
+ "instance" : {
+ "type": "array",
+ "required": false,
+ "items" : {},
+ "minItems": 1
+ },
"tan" : {
"type" : "integer"
},
diff --git a/libsrc/api/JSONRPC_schema/schema-componentstate.json b/libsrc/api/JSONRPC_schema/schema-componentstate.json
index f46324dc..10ca3bb6 100644
--- a/libsrc/api/JSONRPC_schema/schema-componentstate.json
+++ b/libsrc/api/JSONRPC_schema/schema-componentstate.json
@@ -9,6 +9,12 @@
"required" : true,
"enum" : ["componentstate"]
},
+ "instance" : {
+ "type": "array",
+ "required": false,
+ "items" : {},
+ "minItems": 1
+ },
"tan" : {
"type" : "integer"
},
diff --git a/libsrc/api/JSONRPC_schema/schema-config.json b/libsrc/api/JSONRPC_schema/schema-config.json
index 8a134b54..204661cf 100644
--- a/libsrc/api/JSONRPC_schema/schema-config.json
+++ b/libsrc/api/JSONRPC_schema/schema-config.json
@@ -12,6 +12,11 @@
"required" : true,
"enum" : ["getconfig","getschema","setconfig","restoreconfig","reload"]
},
+ "instance" : {
+ "type" : "integer",
+ "minimum": 0,
+ "maximum": 255
+ },
"tan" : {
"type" : "integer"
},
diff --git a/libsrc/api/JSONRPC_schema/schema-create-effect.json b/libsrc/api/JSONRPC_schema/schema-create-effect.json
index 97b55056..2eceb9a1 100644
--- a/libsrc/api/JSONRPC_schema/schema-create-effect.json
+++ b/libsrc/api/JSONRPC_schema/schema-create-effect.json
@@ -7,6 +7,11 @@
"required" : true,
"enum" : ["create-effect"]
},
+ "instance" : {
+ "type" : "integer",
+ "minimum": 0,
+ "maximum": 255
+ },
"tan" : {
"type" : "integer"
},
diff --git a/libsrc/api/JSONRPC_schema/schema-delete-effect.json b/libsrc/api/JSONRPC_schema/schema-delete-effect.json
index 8279f854..bdbdee7c 100644
--- a/libsrc/api/JSONRPC_schema/schema-delete-effect.json
+++ b/libsrc/api/JSONRPC_schema/schema-delete-effect.json
@@ -8,6 +8,11 @@
"required" : true,
"enum" : ["delete-effect"]
},
+ "instance" : {
+ "type" : "integer",
+ "minimum": 0,
+ "maximum": 255
+ },
"tan" : {
"type" : "integer"
},
diff --git a/libsrc/api/JSONRPC_schema/schema-effect.json b/libsrc/api/JSONRPC_schema/schema-effect.json
index 876173f1..5bd0aff6 100644
--- a/libsrc/api/JSONRPC_schema/schema-effect.json
+++ b/libsrc/api/JSONRPC_schema/schema-effect.json
@@ -7,6 +7,12 @@
"required" : true,
"enum" : ["effect"]
},
+ "instance" : {
+ "type": "array",
+ "required": false,
+ "items" : {},
+ "minItems": 1
+ },
"tan" : {
"type" : "integer"
},
diff --git a/libsrc/api/JSONRPC_schema/schema-image.json b/libsrc/api/JSONRPC_schema/schema-image.json
index 3296babb..fbd2ff40 100644
--- a/libsrc/api/JSONRPC_schema/schema-image.json
+++ b/libsrc/api/JSONRPC_schema/schema-image.json
@@ -7,6 +7,12 @@
"required" : true,
"enum" : ["image"]
},
+ "instance" : {
+ "type": "array",
+ "required": false,
+ "items" : {},
+ "minItems": 1
+ },
"tan" : {
"type" : "integer"
},
diff --git a/libsrc/api/JSONRPC_schema/schema-ledcolors.json b/libsrc/api/JSONRPC_schema/schema-ledcolors.json
index b1bda0a3..086914ec 100644
--- a/libsrc/api/JSONRPC_schema/schema-ledcolors.json
+++ b/libsrc/api/JSONRPC_schema/schema-ledcolors.json
@@ -7,21 +7,18 @@
"required" : true,
"enum" : ["ledcolors"]
},
+ "instance" : {
+ "type" : "integer",
+ "minimum": 0,
+ "maximum": 255
+ },
"tan" : {
"type" : "integer"
},
"subcommand": {
"type" : "string",
"required" : true,
- "enum" : ["ledstream-stop","ledstream-start","testled","imagestream-start","imagestream-stop"]
- },
- "oneshot": {
- "type" : "bool"
- },
- "interval": {
- "type" : "integer",
- "required" : false,
- "minimum": 50
+ "enum" : ["ledstream-stop","ledstream-start","imagestream-start","imagestream-stop"]
}
},
diff --git a/libsrc/api/JSONRPC_schema/schema-leddevice.json b/libsrc/api/JSONRPC_schema/schema-leddevice.json
index 5065ea0d..ac74342c 100644
--- a/libsrc/api/JSONRPC_schema/schema-leddevice.json
+++ b/libsrc/api/JSONRPC_schema/schema-leddevice.json
@@ -7,6 +7,9 @@
"required" : true,
"enum" : ["leddevice"]
},
+ "instance" : {
+ "type" : "integer"
+ },
"tan" : {
"type" : "integer"
},
diff --git a/libsrc/api/JSONRPC_schema/schema-processing.json b/libsrc/api/JSONRPC_schema/schema-processing.json
index d67828f0..0ca7616d 100644
--- a/libsrc/api/JSONRPC_schema/schema-processing.json
+++ b/libsrc/api/JSONRPC_schema/schema-processing.json
@@ -7,6 +7,12 @@
"required" : true,
"enum" : ["processing"]
},
+ "instance" : {
+ "type": "array",
+ "required": false,
+ "items" : {},
+ "minItems": 1
+ },
"tan" : {
"type" : "integer"
},
diff --git a/libsrc/api/JSONRPC_schema/schema-serverinfo.json b/libsrc/api/JSONRPC_schema/schema-serverinfo.json
index 990eec04..74b41453 100644
--- a/libsrc/api/JSONRPC_schema/schema-serverinfo.json
+++ b/libsrc/api/JSONRPC_schema/schema-serverinfo.json
@@ -7,6 +7,25 @@
"required" : true,
"enum" : ["serverinfo"]
},
+ "subcommand": {
+ "type": "string",
+ "enum": ["getInfo", "subscribe", "unsubscribe", "getSubscriptions", "getSubscriptionCommands"]
+ },
+ "instance" : {
+ "type" : "integer",
+ "minimum": 0,
+ "maximum": 255
+ },
+ "data": {
+ "type": ["null", "array"],
+ "properties": {
+ "subscriptions": {
+ "type": "array",
+ "items": {}
+ }
+ },
+ "additionalProperties": false
+ },
"subscribe" : {
"type" : "array"
},
diff --git a/libsrc/api/JSONRPC_schema/schema-sourceselect.json b/libsrc/api/JSONRPC_schema/schema-sourceselect.json
index 14f9aaea..8763595c 100644
--- a/libsrc/api/JSONRPC_schema/schema-sourceselect.json
+++ b/libsrc/api/JSONRPC_schema/schema-sourceselect.json
@@ -7,6 +7,12 @@
"required" : true,
"enum" : ["sourceselect"]
},
+ "instance" : {
+ "type": "array",
+ "required": false,
+ "items" : {},
+ "minItems": 1
+ },
"tan" : {
"type" : "integer"
},
diff --git a/libsrc/api/JsonAPI.cpp b/libsrc/api/JsonAPI.cpp
index 6becc6f7..ff4c6841 100644
--- a/libsrc/api/JsonAPI.cpp
+++ b/libsrc/api/JsonAPI.cpp
@@ -1,5 +1,6 @@
// project includes
#include
+#include
// Qt includes
#include
@@ -10,8 +11,8 @@
#include
#include
#include
-#include
-#include
+#include
+#include
// hyperion includes
#include
@@ -20,62 +21,11 @@
#include // Required to determine the cmake options
-#include
-#include
-
#include
#include
-#if defined(ENABLE_MF)
- #include
-#elif defined(ENABLE_V4L2)
- #include
-#endif
-
-#if defined(ENABLE_AUDIO)
- #include
-
- #ifdef WIN32
- #include
- #endif
-
- #ifdef __linux__
- #include
- #endif
-#endif
-
-#if defined(ENABLE_X11)
- #include
-#endif
-
-#if defined(ENABLE_XCB)
- #include
-#endif
-
-#if defined(ENABLE_DX)
- #include
-#endif
-
-#if defined(ENABLE_FB)
- #include
-#endif
-
-#if defined(ENABLE_DISPMANX)
- #include
-#endif
-
-#if defined(ENABLE_AMLOGIC)
- #include
-#endif
-
-#if defined(ENABLE_OSX)
- #include
-#endif
-
#include
#include
-#include
-#include
#include
#include
#include
@@ -84,7 +34,7 @@
#include
// api includes
-#include
+#include
#include
// auth manager
@@ -99,23 +49,37 @@
#include
#endif
+#include
+#include
+
using namespace hyperion;
// Constants
-namespace { const bool verbose = false; }
+namespace {
+
+constexpr std::chrono::milliseconds NEW_TOKEN_REQUEST_TIMEOUT{ 180000 };
+
+const char TOKEN_TAG[] = "token";
+constexpr int TOKEN_TAG_LENGTH = sizeof(TOKEN_TAG) - 1;
+const char BEARER_TOKEN_TAG[] = "Bearer";
+constexpr int BEARER_TOKEN_TAG_LENGTH = sizeof(BEARER_TOKEN_TAG) - 1;
+
+const int MIN_PASSWORD_LENGTH = 8;
+const int APP_TOKEN_LENGTH = 36;
+
+const bool verbose = false;
+}
JsonAPI::JsonAPI(QString peerAddress, Logger *log, bool localConnection, QObject *parent, bool noListener)
: API(log, localConnection, parent)
+ ,_noListener(noListener)
+ ,_peerAddress (std::move(peerAddress))
+ ,_jsonCB (nullptr)
{
- _noListener = noListener;
- _peerAddress = peerAddress;
- _jsonCB = new JsonCB(this);
- _streaming_logging_activated = false;
- _ledStreamTimer = new QTimer(this);
-
Q_INIT_RESOURCE(JSONRPC_schemas);
qRegisterMetaType("Event");
+ _jsonCB = QSharedPointer(new JsonCallbacks( _log, _peerAddress, parent));
}
void JsonAPI::initialize()
@@ -124,14 +88,14 @@ void JsonAPI::initialize()
API::init();
// setup auth interface
- connect(this, &API::onPendingTokenRequest, this, &JsonAPI::newPendingTokenRequest);
+ connect(this, &API::onPendingTokenRequest, this, &JsonAPI::issueNewPendingTokenRequest);
connect(this, &API::onTokenResponse, this, &JsonAPI::handleTokenResponse);
// listen for killed instances
connect(_instanceManager, &HyperionIManager::instanceStateChanged, this, &JsonAPI::handleInstanceStateChange);
// pipe callbacks from subscriptions to parent
- connect(_jsonCB, &JsonCB::newCallback, this, &JsonAPI::callbackMessage);
+ connect(_jsonCB.data(), &JsonCallbacks::newCallback, this, &JsonAPI::callbackMessage);
// notify hyperion about a jsonMessageForward
if (_hyperion != nullptr)
@@ -143,8 +107,6 @@ void JsonAPI::initialize()
//notify eventhadler on suspend/resume/idle requests
connect(this, &JsonAPI::signalEvent, EventHandler::getInstance().data(), &EventHandler::handleEvent);
-
- connect(_ledStreamTimer, &QTimer::timeout, this, &JsonAPI::streamLedColorsUpdate, Qt::UniqueConnection);
}
bool JsonAPI::handleInstanceSwitch(quint8 inst, bool /*forced*/)
@@ -164,118 +126,273 @@ void JsonAPI::handleMessage(const QString &messageString, const QString &httpAut
const QString ident = "JsonRpc@" + _peerAddress;
QJsonObject message;
- // parse the message
- if (!JsonUtils::parse(ident, messageString, message, _log))
+ //parse the message
+ QPair parsingResult = JsonUtils::parse(ident, messageString, message, _log);
+ if (!parsingResult.first)
{
- sendErrorReply("Errors during message parsing, please consult the Hyperion Log.");
+ //Try to find command and tan, even parsing failed
+ QString command = findCommand(messageString);
+ int tan = findTan(messageString);
+
+ sendErrorReply("Parse error", parsingResult.second, command, tan);
return;
}
- int tan = 0;
- if (message.value("tan") != QJsonValue::Undefined)
- tan = message["tan"].toInt();
-
- // check basic message
- if (!JsonUtils::validate(ident, message, ":schema", _log))
- {
- sendErrorReply("Errors during message validation, please consult the Hyperion Log.", "" /*command*/, tan);
- return;
- }
+ DebugIf(verbose, _log, "message: [%s]", QJsonDocument(message).toJson(QJsonDocument::Compact).constData() );
// check specific message
- const QString command = message["command"].toString();
- if (!JsonUtils::validate(ident, message, QString(":schema-%1").arg(command), _log))
+ const QString command = message.value("command").toString();
+ const QString subCommand = message.value("subcommand").toString();
+
+ int tan {0};
+ if (message.value("tan") != QJsonValue::Undefined)
{
- sendErrorReply("Errors during specific message validation, please consult the Hyperion Log", command, tan);
+ tan = message["tan"].toInt();
+ }
+
+ // check basic message
+ QJsonObject schemaJson = QJsonFactory::readSchema(":schema");
+ QPair validationResult = JsonUtils::validate(ident, message, schemaJson, _log);
+ if (!validationResult.first)
+ {
+ sendErrorReply("Invalid command", validationResult.second, command, tan);
return;
}
- // client auth before everything else but not for http
- if (!_noListener && command == "authorize")
+ JsonApiCommand cmd = ApiCommandRegister::getCommandInfo(command, subCommand);
+ cmd.tan = tan;
+
+ if (cmd.command == Command::Unknown)
{
- handleAuthorizeCommand(message, command, tan);
+ const QStringList errorDetails (subCommand.isEmpty() ? "subcommand is missing" : QString("Invalid subcommand: %1").arg(subCommand));
+ sendErrorReply("Invalid command", errorDetails, command, tan);
return;
}
- // check auth state
- if (!API::isAuthorized())
+ if (_noListener)
{
- // on the fly auth available for http from http Auth header
- if (_noListener)
+ setAuthorization(false);
+ if(cmd.isNolistenerCmd == NoListenerCmd::No)
{
- QString cToken = httpAuthHeader.mid(5).trimmed();
- if (API::isTokenAuthorized(cToken))
- goto proceed;
+ sendErrorReply("Command not supported via single API calls using HTTP/S", cmd);
+ return;
}
- sendErrorReply("No Authorization", command, tan);
+
+ // Check authorization for HTTP requests
+ if (!httpAuthHeader.isEmpty())
+ {
+ int bearTokenLenght {0};
+ if (httpAuthHeader.startsWith(BEARER_TOKEN_TAG, Qt::CaseInsensitive)) {
+ bearTokenLenght = BEARER_TOKEN_TAG_LENGTH;
+ }
+ else if (httpAuthHeader.startsWith(TOKEN_TAG, Qt::CaseInsensitive)) {
+ bearTokenLenght = TOKEN_TAG_LENGTH;
+ }
+
+ if (bearTokenLenght == 0)
+ {
+ sendErrorReply("No bearer token found in Authorization header", cmd);
+ return;
+ }
+
+ QString cToken =httpAuthHeader.mid(bearTokenLenght).trimmed();
+ API::isTokenAuthorized(cToken); // _authorized && _adminAuthorized are set
+ }
+
+ if (islocalConnection() && !_authManager->isLocalAuthRequired())
+ {
+ // if the request comes via a local network connection, plus authorization is disabled for local request,
+ // no token authorization is required for non-admin requests
+ setAuthorization(true);
+ }
+ }
+
+ if (cmd.authorization != Authorization::No )
+ {
+ if (!isAuthorized() || (cmd.authorization == Authorization::Admin && !isAdminAuthorized()))
+ {
+ sendNoAuthorization(cmd);
+ return;
+ }
+ }
+
+ schemaJson = QJsonFactory::readSchema(QString(":schema-%1").arg(command));
+ validationResult = JsonUtils::validate(ident, message, schemaJson, _log);
+ if (!validationResult.first)
+ {
+ sendErrorReply("Invalid params", validationResult.second, cmd);
return;
}
-proceed:
+
if (_hyperion == nullptr)
{
- sendErrorReply("Service Unavailable", command, tan);
+ sendErrorReply("Service Unavailable", cmd);
return;
}
- // switch over all possible commands and handle them
- if (command == "color")
- handleColorCommand(message, command, tan);
- else if (command == "image")
- handleImageCommand(message, command, tan);
-#if defined(ENABLE_EFFECTENGINE)
- else if (command == "effect")
- handleEffectCommand(message, command, tan);
- else if (command == "create-effect")
- handleCreateEffectCommand(message, command, tan);
- else if (command == "delete-effect")
- handleDeleteEffectCommand(message, command, tan);
-#endif
- else if (command == "sysinfo")
- handleSysInfoCommand(message, command, tan);
- else if (command == "serverinfo")
- handleServerInfoCommand(message, command, tan);
- else if (command == "clear")
- handleClearCommand(message, command, tan);
- else if (command == "adjustment")
- handleAdjustmentCommand(message, command, tan);
- else if (command == "sourceselect")
- handleSourceSelectCommand(message, command, tan);
- else if (command == "config")
- handleConfigCommand(message, command, tan);
- else if (command == "componentstate")
- handleComponentStateCommand(message, command, tan);
- else if (command == "ledcolors")
- handleLedColorsCommand(message, command, tan);
- else if (command == "logging")
- handleLoggingCommand(message, command, tan);
- else if (command == "processing")
- handleProcessingCommand(message, command, tan);
- else if (command == "videomode")
- handleVideoModeCommand(message, command, tan);
- else if (command == "instance")
- handleInstanceCommand(message, command, tan);
- else if (command == "leddevice")
- handleLedDeviceCommand(message, command, tan);
- else if (command == "inputsource")
- handleInputSourceCommand(message, command, tan);
- else if (command == "service")
- handleServiceCommand(message, command, tan);
- else if (command == "system")
- handleSystemCommand(message, command, tan);
-
- // BEGIN | The following commands are deprecated but used to ensure backward compatibility with hyperion Classic remote control
- else if (command == "clearall")
- handleClearallCommand(message, command, tan);
- else if (command == "transform" || command == "correction" || command == "temperature")
- sendErrorReply("The command " + command + "is deprecated, please use the Hyperion Web Interface to configure", command, tan);
- // END
-
- // handle not implemented commands
+ if (!message.contains("instance") || cmd.isInstanceCmd == InstanceCmd::No)
+ {
+ handleCommand(cmd, message);
+ }
else
- handleNotImplemented(command, tan);
+ {
+ handleInstanceCommand(cmd, message);
+ }
}
-void JsonAPI::handleColorCommand(const QJsonObject &message, const QString &command, int tan)
+void JsonAPI::handleInstanceCommand(const JsonApiCommand& cmd, const QJsonObject &message)
+{
+ const QJsonValue instanceElement = message.value("instance");
+ QJsonArray instances;
+ if (instanceElement.isDouble())
+ {
+ instances.append(instanceElement);
+ } else if (instanceElement.isArray())
+ {
+ instances = instanceElement.toArray();
+ }
+
+ QList runningInstanceIdxs = _instanceManager->getRunningInstanceIdx();
+
+ QList instanceIdxList;
+ QStringList errorDetails;
+ if (instances.contains("all"))
+ {
+ for (const auto& instanceIdx : runningInstanceIdxs)
+ {
+ instanceIdxList.append(instanceIdx);
+ }
+ }
+ else
+ {
+ for (const auto &instance : std::as_const(instances)) {
+
+ quint8 instanceIdx = static_cast(instance.toInt());
+ if (instance.isDouble() && runningInstanceIdxs.contains(instanceIdx))
+ {
+ instanceIdxList.append(instanceIdx);
+ }
+ else
+ {
+ errorDetails.append("Not a running or valid instance: " + instance.toVariant().toString());
+ }
+ }
+ }
+
+ if (instanceIdxList.isEmpty() || !errorDetails.isEmpty() )
+ {
+ sendErrorReply("Invalid instance(s) given", errorDetails, cmd);
+ return;
+ }
+
+ quint8 currentInstanceIdx = getCurrentInstanceIndex();
+ if (instanceIdxList.size() > 1)
+ {
+ if (cmd.isInstanceCmd != InstanceCmd::Multi)
+ {
+ sendErrorReply("Command does not support multiple instances", cmd);
+ return;
+ }
+ }
+
+ for (const auto &instanceIdx : instanceIdxList)
+ {
+ if (setHyperionInstance(instanceIdx))
+ {
+ handleCommand(cmd, message);
+ }
+ }
+
+ setHyperionInstance(currentInstanceIdx);
+}
+
+void JsonAPI::handleCommand(const JsonApiCommand& cmd, const QJsonObject &message)
+{
+ switch (cmd.command) {
+ case Command::Authorize:
+ handleAuthorizeCommand(message, cmd);
+ break;
+ case Command::Color:
+ handleColorCommand(message, cmd);
+ break;
+ case Command::Image:
+ handleImageCommand(message, cmd);
+ break;
+#if defined(ENABLE_EFFECTENGINE)
+ case Command::Effect:
+ handleEffectCommand(message, cmd);
+ break;
+ case Command::CreateEffect:
+ handleCreateEffectCommand(message, cmd);
+ break;
+ case Command::DeleteEffect:
+ handleDeleteEffectCommand(message, cmd);
+ break;
+#endif
+ case Command::SysInfo:
+ handleSysInfoCommand(message, cmd);
+ break;
+ case Command::ServerInfo:
+ handleServerInfoCommand(message, cmd);
+ break;
+ case Command::Clear:
+ handleClearCommand(message, cmd);
+ break;
+ case Command::Adjustment:
+ handleAdjustmentCommand(message, cmd);
+ break;
+ case Command::SourceSelect:
+ handleSourceSelectCommand(message, cmd);
+ break;
+ case Command::Config:
+ handleConfigCommand(message, cmd);
+ break;
+ case Command::ComponentState:
+ handleComponentStateCommand(message, cmd);
+ break;
+ case Command::LedColors:
+ handleLedColorsCommand(message, cmd);
+ break;
+ case Command::Logging:
+ handleLoggingCommand(message, cmd);
+ break;
+ case Command::Processing:
+ handleProcessingCommand(message, cmd);
+ break;
+ case Command::VideoMode:
+ handleVideoModeCommand(message, cmd);
+ break;
+ case Command::Instance:
+ handleInstanceCommand(message, cmd);
+ break;
+ case Command::LedDevice:
+ handleLedDeviceCommand(message, cmd);
+ break;
+ case Command::InputSource:
+ handleInputSourceCommand(message, cmd);
+ break;
+ case Command::Service:
+ handleServiceCommand(message, cmd);
+ break;
+ case Command::System:
+ handleSystemCommand(message, cmd);
+ break;
+ case Command::ClearAll:
+ handleClearallCommand(message, cmd);
+ break;
+ // BEGIN | The following commands are deprecated but used to ensure backward compatibility with Hyperion Classic remote control
+ case Command::Transform:
+ case Command::Correction:
+ case Command::Temperature:
+ sendErrorReply("The command is deprecated, please use the Hyperion Web Interface to configure", cmd);
+ break;
+ // END
+ default:
+ break;
+ }
+}
+
+void JsonAPI::handleColorCommand(const QJsonObject &message, const JsonApiCommand& cmd)
{
emit forwardJsonMessage(message);
int priority = message["priority"].toInt();
@@ -284,17 +401,16 @@ void JsonAPI::handleColorCommand(const QJsonObject &message, const QString &comm
const QJsonArray &jsonColor = message["color"].toArray();
std::vector colors;
- // TODO faster copy
- for (const auto &entry : jsonColor)
- {
- colors.emplace_back(uint8_t(entry.toInt()));
- }
+ colors.reserve(static_cast::size_type>(jsonColor.size()));
+ // Transform each entry in jsonColor to uint8_t and append to colors
+ std::transform(jsonColor.begin(), jsonColor.end(), std::back_inserter(colors),
+ [](const QJsonValue &value) { return static_cast(value.toInt()); });
API::setColor(priority, colors, duration, origin);
- sendSuccessReply(command, tan);
+ sendSuccessReply(cmd);
}
-void JsonAPI::handleImageCommand(const QJsonObject &message, const QString &command, int tan)
+void JsonAPI::handleImageCommand(const QJsonObject &message, const JsonApiCommand& cmd)
{
emit forwardJsonMessage(message);
@@ -310,16 +426,15 @@ void JsonAPI::handleImageCommand(const QJsonObject &message, const QString &comm
idata.data = QByteArray::fromBase64(QByteArray(message["imagedata"].toString().toUtf8()));
QString replyMsg;
- if (!API::setImage(idata, COMP_IMAGE, replyMsg))
- {
- sendErrorReply(replyMsg, command, tan);
- return;
+ if (API::setImage(idata, COMP_IMAGE, replyMsg)) {
+ sendSuccessReply(cmd);
+ } else {
+ sendErrorReply(replyMsg, cmd);
}
- sendSuccessReply(command, tan);
}
#if defined(ENABLE_EFFECTENGINE)
-void JsonAPI::handleEffectCommand(const QJsonObject &message, const QString &command, int tan)
+void JsonAPI::handleEffectCommand(const QJsonObject &message, const JsonApiCommand& cmd)
{
emit forwardJsonMessage(message);
@@ -332,524 +447,118 @@ void JsonAPI::handleEffectCommand(const QJsonObject &message, const QString &com
dat.data = message["imageData"].toString("").toUtf8();
dat.args = message["effect"].toObject()["args"].toObject();
- if (API::setEffect(dat))
- sendSuccessReply(command, tan);
- else
- sendErrorReply("Effect '" + dat.effectName + "' not found", command, tan);
+ if (API::setEffect(dat)) {
+ sendSuccessReply(cmd);
+ } else {
+ sendErrorReply("Effect '" + dat.effectName + "' not found", cmd);
+ }
}
-void JsonAPI::handleCreateEffectCommand(const QJsonObject &message, const QString &command, int tan)
+void JsonAPI::handleCreateEffectCommand(const QJsonObject &message, const JsonApiCommand& cmd)
{
const QString resultMsg = API::saveEffect(message);
- resultMsg.isEmpty() ? sendSuccessReply(command, tan) : sendErrorReply(resultMsg, command, tan);
+ resultMsg.isEmpty() ? sendSuccessReply(cmd) : sendErrorReply(resultMsg, cmd);
}
-void JsonAPI::handleDeleteEffectCommand(const QJsonObject &message, const QString &command, int tan)
+void JsonAPI::handleDeleteEffectCommand(const QJsonObject &message, const JsonApiCommand& cmd)
{
const QString res = API::deleteEffect(message["name"].toString());
- res.isEmpty() ? sendSuccessReply(command, tan) : sendErrorReply(res, command, tan);
+ res.isEmpty() ? sendSuccessReply(cmd) : sendErrorReply(res, cmd);
}
#endif
-void JsonAPI::handleSysInfoCommand(const QJsonObject &, const QString &command, int tan)
+void JsonAPI::handleSysInfoCommand(const QJsonObject & /*unused*/, const JsonApiCommand& cmd)
{
- // create result
- QJsonObject result;
- QJsonObject info;
- result["success"] = true;
- result["command"] = command;
- result["tan"] = tan;
-
- SysInfo::HyperionSysInfo data = SysInfo::get();
- QJsonObject system;
- system["kernelType"] = data.kernelType;
- system["kernelVersion"] = data.kernelVersion;
- system["architecture"] = data.architecture;
- system["cpuModelName"] = data.cpuModelName;
- system["cpuModelType"] = data.cpuModelType;
- system["cpuHardware"] = data.cpuHardware;
- system["cpuRevision"] = data.cpuRevision;
- system["wordSize"] = data.wordSize;
- system["productType"] = data.productType;
- system["productVersion"] = data.productVersion;
- system["prettyName"] = data.prettyName;
- system["hostName"] = data.hostName;
- system["domainName"] = data.domainName;
- system["isUserAdmin"] = data.isUserAdmin;
- system["qtVersion"] = data.qtVersion;
-#if defined(ENABLE_EFFECTENGINE)
- system["pyVersion"] = data.pyVersion;
-#endif
- info["system"] = system;
-
- QJsonObject hyperion;
- hyperion["version"] = QString(HYPERION_VERSION);
- hyperion["build"] = QString(HYPERION_BUILD_ID);
- hyperion["gitremote"] = QString(HYPERION_GIT_REMOTE);
- hyperion["time"] = QString(__DATE__ " " __TIME__);
- hyperion["id"] = _authManager->getID();
- hyperion["rootPath"] = _instanceManager->getRootPath();
- hyperion["readOnlyMode"] = _hyperion->getReadOnlyMode();
-
- QCoreApplication* app = QCoreApplication::instance();
- hyperion["isGuiMode"] = qobject_cast(app) ? true : false;
-
- info["hyperion"] = hyperion;
-
- // send the result
- result["info"] = info;
- emit callbackMessage(result);
+ sendSuccessDataReply(JsonInfo::getSystemInfo(_hyperion), cmd);
}
-void JsonAPI::handleServerInfoCommand(const QJsonObject &message, const QString &command, int tan)
+void JsonAPI::handleServerInfoCommand(const QJsonObject &message, const JsonApiCommand& cmd)
{
- QJsonObject info;
+ QJsonObject info {};
+ QStringList errorDetails;
- // collect priority information
- QJsonArray priorities;
- uint64_t now = QDateTime::currentMSecsSinceEpoch();
- QList activePriorities = _hyperion->getActivePriorities();
- activePriorities.removeAll(PriorityMuxer::LOWEST_PRIORITY);
- int currentPriority = _hyperion->getCurrentPriority();
-
- for(int priority : std::as_const(activePriorities))
- {
- const Hyperion::InputInfo &priorityInfo = _hyperion->getPriorityInfo(priority);
-
- QJsonObject item;
- item["priority"] = priority;
-
- if (priorityInfo.timeoutTime_ms > 0 )
- {
- item["duration_ms"] = int(priorityInfo.timeoutTime_ms - now);
- }
-
- // owner has optional informations to the component
- if (!priorityInfo.owner.isEmpty())
- {
- item["owner"] = priorityInfo.owner;
- }
-
- item["componentId"] = QString(hyperion::componentToIdString(priorityInfo.componentId));
- item["origin"] = priorityInfo.origin;
- item["active"] = (priorityInfo.timeoutTime_ms >= -1);
- item["visible"] = (priority == currentPriority);
-
- if (priorityInfo.componentId == hyperion::COMP_COLOR && !priorityInfo.ledColors.empty())
- {
- QJsonObject LEDcolor;
-
- // add RGB Value to Array
- QJsonArray RGBValue;
- RGBValue.append(priorityInfo.ledColors.begin()->red);
- RGBValue.append(priorityInfo.ledColors.begin()->green);
- RGBValue.append(priorityInfo.ledColors.begin()->blue);
- LEDcolor.insert("RGB", RGBValue);
-
- uint16_t Hue;
- float Saturation;
- float Luminace;
-
- // add HSL Value to Array
- QJsonArray HSLValue;
- ColorSys::rgb2hsl(priorityInfo.ledColors.begin()->red,
- priorityInfo.ledColors.begin()->green,
- priorityInfo.ledColors.begin()->blue,
- Hue, Saturation, Luminace);
-
- HSLValue.append(Hue);
- HSLValue.append(Saturation);
- HSLValue.append(Luminace);
- LEDcolor.insert("HSL", HSLValue);
-
- item["value"] = LEDcolor;
- }
-
- (priority == currentPriority)
- ? priorities.prepend(item)
- : priorities.append(item);
- }
-
- info["priorities"] = priorities;
- info["priorities_autoselect"] = _hyperion->sourceAutoSelectEnabled();
-
- // collect adjustment information
- QJsonArray adjustmentArray;
- for (const QString &adjustmentId : _hyperion->getAdjustmentIds())
- {
- const ColorAdjustment *colorAdjustment = _hyperion->getAdjustment(adjustmentId);
- if (colorAdjustment == nullptr)
- {
- Error(_log, "Incorrect color adjustment id: %s", QSTRING_CSTR(adjustmentId));
- continue;
- }
-
- QJsonObject adjustment;
- adjustment["id"] = adjustmentId;
-
- QJsonArray whiteAdjust;
- whiteAdjust.append(colorAdjustment->_rgbWhiteAdjustment.getAdjustmentR());
- whiteAdjust.append(colorAdjustment->_rgbWhiteAdjustment.getAdjustmentG());
- whiteAdjust.append(colorAdjustment->_rgbWhiteAdjustment.getAdjustmentB());
- adjustment.insert("white", whiteAdjust);
-
- QJsonArray redAdjust;
- redAdjust.append(colorAdjustment->_rgbRedAdjustment.getAdjustmentR());
- redAdjust.append(colorAdjustment->_rgbRedAdjustment.getAdjustmentG());
- redAdjust.append(colorAdjustment->_rgbRedAdjustment.getAdjustmentB());
- adjustment.insert("red", redAdjust);
-
- QJsonArray greenAdjust;
- greenAdjust.append(colorAdjustment->_rgbGreenAdjustment.getAdjustmentR());
- greenAdjust.append(colorAdjustment->_rgbGreenAdjustment.getAdjustmentG());
- greenAdjust.append(colorAdjustment->_rgbGreenAdjustment.getAdjustmentB());
- adjustment.insert("green", greenAdjust);
-
- QJsonArray blueAdjust;
- blueAdjust.append(colorAdjustment->_rgbBlueAdjustment.getAdjustmentR());
- blueAdjust.append(colorAdjustment->_rgbBlueAdjustment.getAdjustmentG());
- blueAdjust.append(colorAdjustment->_rgbBlueAdjustment.getAdjustmentB());
- adjustment.insert("blue", blueAdjust);
-
- QJsonArray cyanAdjust;
- cyanAdjust.append(colorAdjustment->_rgbCyanAdjustment.getAdjustmentR());
- cyanAdjust.append(colorAdjustment->_rgbCyanAdjustment.getAdjustmentG());
- cyanAdjust.append(colorAdjustment->_rgbCyanAdjustment.getAdjustmentB());
- adjustment.insert("cyan", cyanAdjust);
-
- QJsonArray magentaAdjust;
- magentaAdjust.append(colorAdjustment->_rgbMagentaAdjustment.getAdjustmentR());
- magentaAdjust.append(colorAdjustment->_rgbMagentaAdjustment.getAdjustmentG());
- magentaAdjust.append(colorAdjustment->_rgbMagentaAdjustment.getAdjustmentB());
- adjustment.insert("magenta", magentaAdjust);
-
- QJsonArray yellowAdjust;
- yellowAdjust.append(colorAdjustment->_rgbYellowAdjustment.getAdjustmentR());
- yellowAdjust.append(colorAdjustment->_rgbYellowAdjustment.getAdjustmentG());
- yellowAdjust.append(colorAdjustment->_rgbYellowAdjustment.getAdjustmentB());
- adjustment.insert("yellow", yellowAdjust);
-
- adjustment["backlightThreshold"] = colorAdjustment->_rgbTransform.getBacklightThreshold();
- adjustment["backlightColored"] = colorAdjustment->_rgbTransform.getBacklightColored();
- adjustment["brightness"] = colorAdjustment->_rgbTransform.getBrightness();
- adjustment["brightnessCompensation"] = colorAdjustment->_rgbTransform.getBrightnessCompensation();
- adjustment["gammaRed"] = colorAdjustment->_rgbTransform.getGammaR();
- adjustment["gammaGreen"] = colorAdjustment->_rgbTransform.getGammaG();
- adjustment["gammaBlue"] = colorAdjustment->_rgbTransform.getGammaB();
-
- adjustment["saturationGain"] = colorAdjustment->_okhsvTransform.getSaturationGain();
- adjustment["brightnessGain"] = colorAdjustment->_okhsvTransform.getBrightnessGain();
-
- adjustmentArray.append(adjustment);
- }
-
- info["adjustment"] = adjustmentArray;
+ switch (cmd.getSubCommand()) {
+ case SubCommand::Empty:
+ case SubCommand::GetInfo:
+ info["priorities"] = JsonInfo::getPrioritiestInfo(_hyperion);
+ info["priorities_autoselect"] = _hyperion->sourceAutoSelectEnabled();
+ info["adjustment"] = JsonInfo::getAdjustmentInfo(_hyperion, _log);
+ info["ledDevices"] = JsonInfo::getAvailableLedDevices();
+ info["grabbers"] = JsonInfo::getGrabbers(_hyperion);
+ info["videomode"] = QString(videoMode2String(_hyperion->getCurrentVideoMode()));
+ info["cec"] = JsonInfo::getCecInfo();
+ info["services"] = JsonInfo::getServices();
+ info["components"] = JsonInfo::getComponents(_hyperion);
+ info["imageToLedMappingType"] = ImageProcessor::mappingTypeToStr(_hyperion->getLedMappingType());
+ info["instance"] = JsonInfo::getInstanceInfo();
+ info["leds"] = _hyperion->getSetting(settings::LEDS).array();
+ info["activeLedColor"] = JsonInfo::getActiveColors(_hyperion);
#if defined(ENABLE_EFFECTENGINE)
- // collect effect info
- QJsonArray effects;
- const std::list &effectsDefinitions = _hyperion->getEffects();
- for (const EffectDefinition &effectDefinition : effectsDefinitions)
- {
- QJsonObject effect;
- effect["name"] = effectDefinition.name;
- effect["file"] = effectDefinition.file;
- effect["script"] = effectDefinition.script;
- effect["args"] = effectDefinition.args;
- effects.append(effect);
- }
-
- info["effects"] = effects;
+ info["effects"] = JsonInfo::getEffects(_hyperion);
+ info["activeEffects"] = JsonInfo::getActiveEffects(_hyperion);
#endif
- // get available led devices
- QJsonObject ledDevices;
- QJsonArray availableLedDevices;
- for (auto dev : LedDeviceWrapper::getDeviceMap())
- {
- availableLedDevices.append(dev.first);
- }
+ // BEGIN | The following entries are deprecated but used to ensure backward compatibility with hyperion Classic or up to Hyperion 2.0.16
+ info["hostname"] = QHostInfo::localHostName();
+ info["transform"] = JsonInfo::getTransformationInfo(_hyperion);
- ledDevices["available"] = availableLedDevices;
- info["ledDevices"] = ledDevices;
-
- QJsonObject grabbers;
- // SCREEN
- QJsonObject screenGrabbers;
- if (GrabberWrapper::getInstance() != nullptr)
- {
- QStringList activeGrabbers = GrabberWrapper::getInstance()->getActive(_hyperion->getInstanceIndex(), GrabberTypeFilter::SCREEN);
- QJsonArray activeGrabberNames;
- for (auto grabberName : activeGrabbers)
+ if (!_noListener && message.contains("subscribe"))
{
- activeGrabberNames.append(grabberName);
- }
-
- screenGrabbers["active"] = activeGrabberNames;
- }
- QJsonArray availableScreenGrabbers;
- for (auto grabber : GrabberWrapper::availableGrabbers(GrabberTypeFilter::SCREEN))
- {
- availableScreenGrabbers.append(grabber);
- }
- screenGrabbers["available"] = availableScreenGrabbers;
-
- // VIDEO
- QJsonObject videoGrabbers;
- if (GrabberWrapper::getInstance() != nullptr)
- {
- QStringList activeGrabbers = GrabberWrapper::getInstance()->getActive(_hyperion->getInstanceIndex(), GrabberTypeFilter::VIDEO);
- QJsonArray activeGrabberNames;
- for (auto grabberName : activeGrabbers)
- {
- activeGrabberNames.append(grabberName);
- }
-
- videoGrabbers["active"] = activeGrabberNames;
- }
- QJsonArray availableVideoGrabbers;
- for (auto grabber : GrabberWrapper::availableGrabbers(GrabberTypeFilter::VIDEO))
- {
- availableVideoGrabbers.append(grabber);
- }
- videoGrabbers["available"] = availableVideoGrabbers;
-
- // AUDIO
- QJsonObject audioGrabbers;
- if (GrabberWrapper::getInstance() != nullptr)
- {
- QStringList activeGrabbers = GrabberWrapper::getInstance()->getActive(_hyperion->getInstanceIndex(), GrabberTypeFilter::AUDIO);
-
- QJsonArray activeGrabberNames;
- for (auto grabberName : activeGrabbers)
- {
- activeGrabberNames.append(grabberName);
- }
-
- audioGrabbers["active"] = activeGrabberNames;
- }
- QJsonArray availableAudioGrabbers;
- for (auto grabber : GrabberWrapper::availableGrabbers(GrabberTypeFilter::AUDIO))
- {
- availableAudioGrabbers.append(grabber);
- }
- audioGrabbers["available"] = availableAudioGrabbers;
-
- grabbers.insert("screen", screenGrabbers);
- grabbers.insert("video", videoGrabbers);
- grabbers.insert("audio", audioGrabbers);
-
- info["grabbers"] = grabbers;
-
- info["videomode"] = QString(videoMode2String(_hyperion->getCurrentVideoMode()));
-
- QJsonObject cecInfo;
-#if defined(ENABLE_CEC)
- cecInfo["enabled"] = true;
-#else
- cecInfo["enabled"] = false;
-#endif
- info["cec"] = cecInfo;
-
- // get available services
- QJsonArray services;
-
-#if defined(ENABLE_BOBLIGHT_SERVER)
- services.append("boblight");
-#endif
-
-#if defined(ENABLE_CEC)
- services.append("cec");
-#endif
-
-#if defined(ENABLE_EFFECTENGINE)
- services.append("effectengine");
-#endif
-
-#if defined(ENABLE_FORWARDER)
- services.append("forwarder");
-#endif
-
-#if defined(ENABLE_FLATBUF_SERVER)
- services.append("flatbuffer");
-#endif
-
-#if defined(ENABLE_PROTOBUF_SERVER)
- services.append("protobuffer");
-#endif
-
-#if defined(ENABLE_MDNS)
- services.append("mDNS");
-#endif
- services.append("SSDP");
-
- if (!availableScreenGrabbers.isEmpty() || !availableVideoGrabbers.isEmpty() || services.contains("flatbuffer") || services.contains("protobuffer"))
- {
- services.append("borderdetection");
- }
-
- info["services"] = services;
-
- // get available components
- QJsonArray component;
- std::map components = _hyperion->getComponentRegister()->getRegister();
- for (auto comp : components)
- {
- QJsonObject item;
- item["name"] = QString::fromStdString(hyperion::componentToIdString(comp.first));
- item["enabled"] = comp.second;
-
- component.append(item);
- }
-
- info["components"] = component;
- info["imageToLedMappingType"] = ImageProcessor::mappingTypeToStr(_hyperion->getLedMappingType());
-
- // add instance info
- QJsonArray instanceInfo;
- for (const auto &entry : API::getAllInstanceData())
- {
- QJsonObject obj;
- obj.insert("friendly_name", entry["friendly_name"].toString());
- obj.insert("instance", entry["instance"].toInt());
- obj.insert("running", entry["running"].toBool());
- instanceInfo.append(obj);
- }
- info["instance"] = instanceInfo;
-
- // add leds configs
- info["leds"] = _hyperion->getSetting(settings::LEDS).array();
-
- // BEGIN | The following entries are deprecated but used to ensure backward compatibility with hyperion Classic remote control
- // TODO Output the real transformation information instead of default
-
- // HOST NAME
- info["hostname"] = QHostInfo::localHostName();
-
- // TRANSFORM INFORMATION (DEFAULT VALUES)
- QJsonArray transformArray;
- for (const QString &transformId : _hyperion->getAdjustmentIds())
- {
- QJsonObject transform;
- QJsonArray blacklevel, whitelevel, gamma, threshold;
-
- transform["id"] = transformId;
- transform["saturationGain"] = 1.0;
- transform["brightnessGain"] = 1.0;
- transform["saturationLGain"] = 1.0;
- transform["luminanceGain"] = 1.0;
- transform["luminanceMinimum"] = 0.0;
-
- for (int i = 0; i < 3; i++)
- {
- blacklevel.append(0.0);
- whitelevel.append(1.0);
- gamma.append(2.50);
- threshold.append(0.0);
- }
-
- transform.insert("blacklevel", blacklevel);
- transform.insert("whitelevel", whitelevel);
- transform.insert("gamma", gamma);
- transform.insert("threshold", threshold);
-
- transformArray.append(transform);
- }
- info["transform"] = transformArray;
-
-#if defined(ENABLE_EFFECTENGINE)
- // ACTIVE EFFECT INFO
- QJsonArray activeEffects;
- for (const ActiveEffectDefinition &activeEffectDefinition : _hyperion->getActiveEffects())
- {
- if (activeEffectDefinition.priority != PriorityMuxer::LOWEST_PRIORITY - 1)
- {
- QJsonObject activeEffect;
- activeEffect["script"] = activeEffectDefinition.script;
- activeEffect["name"] = activeEffectDefinition.name;
- activeEffect["priority"] = activeEffectDefinition.priority;
- activeEffect["timeout"] = activeEffectDefinition.timeout;
- activeEffect["args"] = activeEffectDefinition.args;
- activeEffects.append(activeEffect);
- }
- }
- info["activeEffects"] = activeEffects;
-#endif
-
- // ACTIVE STATIC LED COLOR
- QJsonArray activeLedColors;
- const Hyperion::InputInfo &priorityInfo = _hyperion->getPriorityInfo(_hyperion->getCurrentPriority());
- if (priorityInfo.componentId == hyperion::COMP_COLOR && !priorityInfo.ledColors.empty())
- {
- // check if LED Color not Black (0,0,0)
- if ((priorityInfo.ledColors.begin()->red +
- priorityInfo.ledColors.begin()->green +
- priorityInfo.ledColors.begin()->blue !=
- 0))
- {
- QJsonObject LEDcolor;
-
- // add RGB Value to Array
- QJsonArray RGBValue;
- RGBValue.append(priorityInfo.ledColors.begin()->red);
- RGBValue.append(priorityInfo.ledColors.begin()->green);
- RGBValue.append(priorityInfo.ledColors.begin()->blue);
- LEDcolor.insert("RGB Value", RGBValue);
-
- uint16_t Hue;
- float Saturation, Luminace;
-
- // add HSL Value to Array
- QJsonArray HSLValue;
- ColorSys::rgb2hsl(priorityInfo.ledColors.begin()->red,
- priorityInfo.ledColors.begin()->green,
- priorityInfo.ledColors.begin()->blue,
- Hue, Saturation, Luminace);
-
- HSLValue.append(Hue);
- HSLValue.append(Saturation);
- HSLValue.append(Luminace);
- LEDcolor.insert("HSL Value", HSLValue);
-
- activeLedColors.append(LEDcolor);
- }
- }
- info["activeLedColor"] = activeLedColors;
-
- // END
-
- sendSuccessDataReply(QJsonDocument(info), command, tan);
-
- // AFTER we send the info, the client might want to subscribe to future updates
- if (message.contains("subscribe"))
- {
- // check if listeners are allowed
- if (_noListener)
- return;
-
- QJsonArray subsArr = message["subscribe"].toArray();
- // catch the all keyword and build a list of all cmds
- if (subsArr.contains("all"))
- {
- subsArr = QJsonArray();
- for (const auto& entry : _jsonCB->getCommands())
+ const QJsonArray &subscriptions = message["subscribe"].toArray();
+ QStringList invaliCommands = _jsonCB->subscribe(subscriptions);
+ if (!invaliCommands.isEmpty())
{
- subsArr.append(entry);
+ errorDetails.append("subscribe - Invalid commands provided: " + invaliCommands.join(','));
}
}
+ // END
- for (const QJsonValueRef entry : subsArr)
+ break;
+
+ case SubCommand::Subscribe:
+ case SubCommand::Unsubscribe:
+ {
+ const QJsonObject ¶ms = message["data"].toObject();
+ const QJsonArray &subscriptions = params["subscriptions"].toArray();
+ if (subscriptions.isEmpty()) {
+ sendErrorReply("Invalid params", {"No subscriptions provided"}, cmd);
+ return;
+ }
+
+ QStringList invaliCommands;
+ if (cmd.subCommand == SubCommand::Subscribe)
{
- // config callbacks just if auth is set
- if ((entry == "settings-update" || entry == "token-update") && !API::isAdminAuthorized())
- continue;
- // silent failure if a subscribe type is not found
- _jsonCB->subscribeFor(entry.toString());
+ invaliCommands = _jsonCB->subscribe(subscriptions);
+ }
+ else
+ {
+ invaliCommands = _jsonCB->unsubscribe(subscriptions);
+ }
+
+ if (!invaliCommands.isEmpty())
+ {
+ errorDetails.append("subscriptions - Invalid commands provided: " + invaliCommands.join(','));
}
}
+ break;
+
+ case SubCommand::GetSubscriptions:
+ info["subscriptions"] = QJsonArray::fromStringList(_jsonCB->getSubscribedCommands());
+ break;
+
+ case SubCommand::GetSubscriptionCommands:
+ info["commands"] = QJsonArray::fromStringList(_jsonCB->getCommands());
+ break;
+
+ default:
+ break;
+ }
+
+ sendSuccessDataReplyWithError(info, cmd, errorDetails);
}
-void JsonAPI::handleClearCommand(const QJsonObject &message, const QString &command, int tan)
+void JsonAPI::handleClearCommand(const QJsonObject &message, const JsonApiCommand& cmd)
{
emit forwardJsonMessage(message);
int priority = message["priority"].toInt();
@@ -857,117 +566,113 @@ void JsonAPI::handleClearCommand(const QJsonObject &message, const QString &comm
if (!API::clearPriority(priority, replyMsg))
{
- sendErrorReply(replyMsg, command, tan);
+ sendErrorReply(replyMsg, cmd);
return;
}
- sendSuccessReply(command, tan);
+ sendSuccessReply(cmd);
}
-void JsonAPI::handleClearallCommand(const QJsonObject &message, const QString &command, int tan)
+void JsonAPI::handleClearallCommand(const QJsonObject &message, const JsonApiCommand& cmd)
{
emit forwardJsonMessage(message);
QString replyMsg;
API::clearPriority(-1, replyMsg);
- sendSuccessReply(command, tan);
+ sendSuccessReply(cmd);
}
-void JsonAPI::handleAdjustmentCommand(const QJsonObject &message, const QString &command, int tan)
+void JsonAPI::handleAdjustmentCommand(const QJsonObject &message, const JsonApiCommand& cmd)
{
const QJsonObject &adjustment = message["adjustment"].toObject();
- const QString adjustmentId = adjustment["id"].toString(_hyperion->getAdjustmentIds().first());
+ const QList adjustmentIds = _hyperion->getAdjustmentIds();
+ if (adjustmentIds.isEmpty()) {
+ sendErrorReply("No adjustment data available", cmd);
+ return;
+ }
+
+ const QString adjustmentId = adjustment["id"].toString(adjustmentIds.first());
ColorAdjustment *colorAdjustment = _hyperion->getAdjustment(adjustmentId);
- if (colorAdjustment == nullptr)
- {
+ if (colorAdjustment == nullptr) {
Warning(_log, "Incorrect adjustment identifier: %s", adjustmentId.toStdString().c_str());
return;
}
- if (adjustment.contains("red"))
- {
- const QJsonArray &values = adjustment["red"].toArray();
- colorAdjustment->_rgbRedAdjustment.setAdjustment(values[0u].toInt(), values[1u].toInt(), values[2u].toInt());
- }
-
- if (adjustment.contains("green"))
- {
- const QJsonArray &values = adjustment["green"].toArray();
- colorAdjustment->_rgbGreenAdjustment.setAdjustment(values[0u].toInt(), values[1u].toInt(), values[2u].toInt());
- }
-
- if (adjustment.contains("blue"))
- {
- const QJsonArray &values = adjustment["blue"].toArray();
- colorAdjustment->_rgbBlueAdjustment.setAdjustment(values[0u].toInt(), values[1u].toInt(), values[2u].toInt());
- }
- if (adjustment.contains("cyan"))
- {
- const QJsonArray &values = adjustment["cyan"].toArray();
- colorAdjustment->_rgbCyanAdjustment.setAdjustment(values[0u].toInt(), values[1u].toInt(), values[2u].toInt());
- }
- if (adjustment.contains("magenta"))
- {
- const QJsonArray &values = adjustment["magenta"].toArray();
- colorAdjustment->_rgbMagentaAdjustment.setAdjustment(values[0u].toInt(), values[1u].toInt(), values[2u].toInt());
- }
- if (adjustment.contains("yellow"))
- {
- const QJsonArray &values = adjustment["yellow"].toArray();
- colorAdjustment->_rgbYellowAdjustment.setAdjustment(values[0u].toInt(), values[1u].toInt(), values[2u].toInt());
- }
- if (adjustment.contains("white"))
- {
- const QJsonArray &values = adjustment["white"].toArray();
- colorAdjustment->_rgbWhiteAdjustment.setAdjustment(values[0u].toInt(), values[1u].toInt(), values[2u].toInt());
- }
-
- if (adjustment.contains("gammaRed"))
- {
- colorAdjustment->_rgbTransform.setGamma(adjustment["gammaRed"].toDouble(), colorAdjustment->_rgbTransform.getGammaG(), colorAdjustment->_rgbTransform.getGammaB());
- }
- if (adjustment.contains("gammaGreen"))
- {
- colorAdjustment->_rgbTransform.setGamma(colorAdjustment->_rgbTransform.getGammaR(), adjustment["gammaGreen"].toDouble(), colorAdjustment->_rgbTransform.getGammaB());
- }
- if (adjustment.contains("gammaBlue"))
- {
- colorAdjustment->_rgbTransform.setGamma(colorAdjustment->_rgbTransform.getGammaR(), colorAdjustment->_rgbTransform.getGammaG(), adjustment["gammaBlue"].toDouble());
- }
-
- if (adjustment.contains("backlightThreshold"))
- {
- colorAdjustment->_rgbTransform.setBacklightThreshold(adjustment["backlightThreshold"].toDouble());
- }
- if (adjustment.contains("backlightColored"))
- {
- colorAdjustment->_rgbTransform.setBacklightColored(adjustment["backlightColored"].toBool());
- }
- if (adjustment.contains("brightness"))
- {
- colorAdjustment->_rgbTransform.setBrightness(adjustment["brightness"].toInt());
- }
- if (adjustment.contains("brightnessCompensation"))
- {
- colorAdjustment->_rgbTransform.setBrightnessCompensation(adjustment["brightnessCompensation"].toInt());
- }
-
- if (adjustment.contains("saturationGain"))
- {
- colorAdjustment->_okhsvTransform.setSaturationGain(adjustment["saturationGain"].toDouble());
- }
-
- if (adjustment.contains("brightnessGain"))
- {
- colorAdjustment->_okhsvTransform.setBrightnessGain(adjustment["brightnessGain"].toDouble());
- }
-
- // commit the changes
+ applyColorAdjustments(adjustment, colorAdjustment);
+ applyTransforms(adjustment, colorAdjustment);
_hyperion->adjustmentsUpdated();
-
- sendSuccessReply(command, tan);
+ sendSuccessReply(cmd);
}
-void JsonAPI::handleSourceSelectCommand(const QJsonObject &message, const QString &command, int tan)
+void JsonAPI::applyColorAdjustments(const QJsonObject &adjustment, ColorAdjustment *colorAdjustment)
+{
+ applyColorAdjustment("red", adjustment, colorAdjustment->_rgbRedAdjustment);
+ applyColorAdjustment("green", adjustment, colorAdjustment->_rgbGreenAdjustment);
+ applyColorAdjustment("blue", adjustment, colorAdjustment->_rgbBlueAdjustment);
+ applyColorAdjustment("cyan", adjustment, colorAdjustment->_rgbCyanAdjustment);
+ applyColorAdjustment("magenta", adjustment, colorAdjustment->_rgbMagentaAdjustment);
+ applyColorAdjustment("yellow", adjustment, colorAdjustment->_rgbYellowAdjustment);
+ applyColorAdjustment("white", adjustment, colorAdjustment->_rgbWhiteAdjustment);
+}
+
+void JsonAPI::applyColorAdjustment(const QString &colorName, const QJsonObject &adjustment, RgbChannelAdjustment &rgbAdjustment)
+{
+ if (adjustment.contains(colorName)) {
+ const QJsonArray &values = adjustment[colorName].toArray();
+ if (values.size() >= 3) {
+ rgbAdjustment.setAdjustment(static_cast(values[0U].toInt()),
+ static_cast(values[1U].toInt()),
+ static_cast(values[2U].toInt()));
+ }
+ }
+}
+
+void JsonAPI::applyTransforms(const QJsonObject &adjustment, ColorAdjustment *colorAdjustment)
+{
+ applyGammaTransform("gammaRed", adjustment, colorAdjustment->_rgbTransform, 'r');
+ applyGammaTransform("gammaGreen", adjustment, colorAdjustment->_rgbTransform, 'g');
+ applyGammaTransform("gammaBlue", adjustment, colorAdjustment->_rgbTransform, 'b');
+ applyTransform("backlightThreshold", adjustment, colorAdjustment->_rgbTransform, &RgbTransform::setBacklightThreshold);
+ applyTransform("backlightColored", adjustment, colorAdjustment->_rgbTransform, &RgbTransform::setBacklightColored);
+ applyTransform("brightness", adjustment, colorAdjustment->_rgbTransform, &RgbTransform::setBrightness);
+ applyTransform("brightnessCompensation", adjustment, colorAdjustment->_rgbTransform, &RgbTransform::setBrightnessCompensation);
+ applyTransform("saturationGain", adjustment, colorAdjustment->_okhsvTransform, &OkhsvTransform::setSaturationGain);
+ applyTransform("brightnessGain", adjustment, colorAdjustment->_okhsvTransform, &OkhsvTransform::setBrightnessGain);
+}
+
+void JsonAPI::applyGammaTransform(const QString &transformName, const QJsonObject &adjustment, RgbTransform &rgbTransform, char channel)
+{
+ if (adjustment.contains(transformName)) {
+ rgbTransform.setGamma(channel == 'r' ? adjustment[transformName].toDouble() : rgbTransform.getGammaR(),
+ channel == 'g' ? adjustment[transformName].toDouble() : rgbTransform.getGammaG(),
+ channel == 'b' ? adjustment[transformName].toDouble() : rgbTransform.getGammaB());
+ }
+}
+
+template
+void JsonAPI::applyTransform(const QString &transformName, const QJsonObject &adjustment, T &transform, void (T::*setFunction)(bool))
+{
+ if (adjustment.contains(transformName)) {
+ (transform.*setFunction)(adjustment[transformName].toBool());
+ }
+}
+
+template
+void JsonAPI::applyTransform(const QString &transformName, const QJsonObject &adjustment, T &transform, void (T::*setFunction)(double))
+{
+ if (adjustment.contains(transformName)) {
+ (transform.*setFunction)(adjustment[transformName].toDouble());
+ }
+}
+
+template
+void JsonAPI::applyTransform(const QString &transformName, const QJsonObject &adjustment, T &transform, void (T::*setFunction)(uint8_t))
+{
+ if (adjustment.contains(transformName)) {
+ (transform.*setFunction)(static_cast(adjustment[transformName].toInt()));
+ }
+}
+
+void JsonAPI::handleSourceSelectCommand(const QJsonObject &message, const JsonApiCommand& cmd)
{
if (message.contains("auto"))
{
@@ -979,84 +684,63 @@ void JsonAPI::handleSourceSelectCommand(const QJsonObject &message, const QStrin
}
else
{
- sendErrorReply("Priority request is invalid", command, tan);
+ sendErrorReply("Priority request is invalid", cmd);
return;
}
- sendSuccessReply(command, tan);
+ sendSuccessReply(cmd);
}
-void JsonAPI::handleConfigCommand(const QJsonObject &message, const QString &command, int tan)
+void JsonAPI::handleConfigCommand(const QJsonObject& message, const JsonApiCommand& cmd)
{
- QString subcommand = message["subcommand"].toString("");
- QString full_command = command + "-" + subcommand;
+ switch (cmd.subCommand) {
+ case SubCommand::GetSchema:
+ handleSchemaGetCommand(message, cmd);
+ break;
- if (subcommand == "getschema")
- {
- handleSchemaGetCommand(message, full_command, tan);
- }
- else if (subcommand == "getconfig")
- {
- if (_adminAuthorized)
- sendSuccessDataReply(QJsonDocument(_hyperion->getQJsonConfig()), full_command, tan);
- else
- sendErrorReply("No Authorization", command, tan);
- }
- else if (subcommand == "setconfig")
- {
- if (_adminAuthorized)
- handleConfigSetCommand(message, full_command, tan);
- else
- sendErrorReply("No Authorization", command, tan);
- }
- else if (subcommand == "restoreconfig")
- {
- if (_adminAuthorized)
- handleConfigRestoreCommand(message, full_command, tan);
- else
- sendErrorReply("No Authorization", command, tan);
- }
- else if (subcommand == "reload")
- {
- if (_adminAuthorized)
- {
- Debug(_log, "Restarting due to RPC command");
- emit signalEvent(Event::Reload);
+ case SubCommand::GetConfig:
+ sendSuccessDataReply(_hyperion->getQJsonConfig(), cmd);
+ break;
- sendSuccessReply(command + "-" + subcommand, tan);
- }
- else
- {
- sendErrorReply("No Authorization", command, tan);
- }
- }
- else
- {
- sendErrorReply("unknown or missing subcommand", full_command, tan);
+ case SubCommand::SetConfig:
+ handleConfigSetCommand(message, cmd);
+ break;
+
+ case SubCommand::RestoreConfig:
+ handleConfigRestoreCommand(message, cmd);
+ break;
+
+ case SubCommand::Reload:
+ Debug(_log, "Restarting due to RPC command");
+ emit signalEvent(Event::Reload);
+ sendSuccessReply(cmd);
+ break;
+
+ default:
+ break;
}
}
-void JsonAPI::handleConfigSetCommand(const QJsonObject &message, const QString &command, int tan)
+void JsonAPI::handleConfigSetCommand(const QJsonObject &message, const JsonApiCommand& cmd)
{
if (message.contains("config"))
{
QJsonObject config = message["config"].toObject();
if (API::isHyperionEnabled())
{
- if ( API::saveSettings(config) )
- {
- sendSuccessReply(command, tan);
- }
- else
- {
- sendErrorReply("Save settings failed", command, tan);
+ if ( API::saveSettings(config) ) {
+ sendSuccessReply(cmd);
+ } else {
+ sendErrorReply("Save settings failed", cmd);
}
}
else
- sendErrorReply("Saving configuration while Hyperion is disabled isn't possible", command, tan);
+ {
+ sendErrorReply("Saving configuration while Hyperion is disabled isn't possible", cmd);
+ }
}
}
-void JsonAPI::handleConfigRestoreCommand(const QJsonObject &message, const QString &command, int tan)
+void JsonAPI::handleConfigRestoreCommand(const QJsonObject &message, const JsonApiCommand& cmd)
{
if (message.contains("config"))
{
@@ -1065,22 +749,26 @@ void JsonAPI::handleConfigRestoreCommand(const QJsonObject &message, const QStri
{
if ( API::restoreSettings(config) )
{
- sendSuccessReply(command, tan);
+ sendSuccessReply(cmd);
}
else
{
- sendErrorReply("Restore settings failed", command, tan);
+ sendErrorReply("Restore settings failed", cmd);
}
}
else
- sendErrorReply("Restoring configuration while Hyperion is disabled isn't possible", command, tan);
+ {
+ sendErrorReply("Restoring configuration while Hyperion is disabled is not possible", cmd);
+ }
}
}
-void JsonAPI::handleSchemaGetCommand(const QJsonObject& /*message*/, const QString &command, int tan)
+void JsonAPI::handleSchemaGetCommand(const QJsonObject& /*message*/, const JsonApiCommand& cmd)
{
// create result
- QJsonObject schemaJson, alldevices, properties;
+ QJsonObject schemaJson;
+ QJsonObject alldevices;
+ QJsonObject properties;
// make sure the resources are loaded (they may be left out after static linking)
Q_INIT_RESOURCE(resource);
@@ -1128,687 +816,506 @@ void JsonAPI::handleSchemaGetCommand(const QJsonObject& /*message*/, const QStri
schemaJson.insert("properties", properties);
// send the result
- sendSuccessDataReply(QJsonDocument(schemaJson), command, tan);
+ sendSuccessDataReply(schemaJson, cmd);
}
-void JsonAPI::handleComponentStateCommand(const QJsonObject &message, const QString &command, int tan)
+void JsonAPI::handleComponentStateCommand(const QJsonObject &message, const JsonApiCommand& cmd)
{
const QJsonObject &componentState = message["componentstate"].toObject();
QString comp = componentState["component"].toString("invalid");
bool compState = componentState["state"].toBool(true);
QString replyMsg;
- if (!API::setComponentState(comp, compState, replyMsg))
- {
- sendErrorReply(replyMsg, command, tan);
- return;
+ if (API::setComponentState(comp, compState, replyMsg)) {
+ sendSuccessReply(cmd);
+ } else {
+ sendErrorReply(replyMsg, cmd);
}
- sendSuccessReply(command, tan);
}
-void JsonAPI::streamLedColorsUpdate()
+void JsonAPI::handleLedColorsCommand(const QJsonObject& /*message*/, const JsonApiCommand& cmd)
{
- emit streamLedcolorsUpdate(_currentLedValues);
-}
-
-void JsonAPI::handleLedColorsCommand(const QJsonObject &message, const QString &command, int tan)
-{
- // create result
- QString subcommand = message["subcommand"].toString("");
-
- // max 20 Hz (50ms) interval for streaming (default: 10 Hz (100ms))
- qint64 streaming_interval = qMax(message["interval"].toInt(100), 50);
-
- if (subcommand == "ledstream-start")
- {
- _streaming_leds_reply["success"] = true;
- _streaming_leds_reply["command"] = command + "-ledstream-update";
- _streaming_leds_reply["tan"] = tan;
-
- connect(_hyperion, &Hyperion::rawLedColors, this, [=](const std::vector &ledValues) {
-
- if (ledValues != _currentLedValues)
- {
- _currentLedValues = ledValues;
- if (!_ledStreamTimer->isActive() || _ledStreamTimer->interval() != streaming_interval)
- {
- _ledStreamTimer->start(streaming_interval);
- }
- }
- else
- {
- _ledStreamTimer->stop();
- }
- });
-
+ switch (cmd.subCommand) {
+ case SubCommand::LedStreamStart:
+ _jsonCB->subscribe( Subscription::LedColorsUpdate);
// push once
_hyperion->update();
- }
- else if (subcommand == "ledstream-stop")
- {
- disconnect(_hyperion, &Hyperion::rawLedColors, this, 0);
- _ledStreamTimer->stop();
- disconnect(_ledStreamConnection);
- }
- else if (subcommand == "imagestream-start")
- {
- _streaming_image_reply["success"] = true;
- _streaming_image_reply["command"] = command + "-imagestream-update";
- _streaming_image_reply["tan"] = tan;
+ sendSuccessReply(cmd);
+ break;
- connect(_hyperion, &Hyperion::currentImage, this, &JsonAPI::setImage, Qt::UniqueConnection);
- }
- else if (subcommand == "imagestream-stop")
- {
- disconnect(_hyperion, &Hyperion::currentImage, this, 0);
- }
- else
- {
- return;
- }
+ case SubCommand::LedStreamStop:
+ _jsonCB->unsubscribe( Subscription::LedColorsUpdate);
+ sendSuccessReply(cmd);
+ break;
- sendSuccessReply(command + "-" + subcommand, tan);
+ case SubCommand::ImageStreamStart:
+ _jsonCB->subscribe(Subscription::ImageUpdate);
+ sendSuccessReply(cmd);
+ break;
+
+ case SubCommand::ImageStreamStop:
+ _jsonCB->unsubscribe(Subscription::ImageUpdate);
+ sendSuccessReply(cmd);
+ break;
+
+ default:
+ break;
+ }
}
-void JsonAPI::handleLoggingCommand(const QJsonObject &message, const QString &command, int tan)
+void JsonAPI::handleLoggingCommand(const QJsonObject& /*message*/, const JsonApiCommand& cmd)
{
- // create result
- QString subcommand = message["subcommand"].toString("");
+ switch (cmd.subCommand) {
+ case SubCommand::Start:
+ _jsonCB->subscribe("logmsg-update");
+ sendSuccessReply(cmd);
+ break;
- if (API::isAdminAuthorized())
- {
- _streaming_logging_reply["success"] = true;
- _streaming_logging_reply["command"] = command;
- _streaming_logging_reply["tan"] = tan;
-
- if (subcommand == "start")
- {
- if (!_streaming_logging_activated)
- {
- _streaming_logging_reply["command"] = command + "-update";
- connect(LoggerManager::getInstance().data(), &LoggerManager::newLogMessage, this, &JsonAPI::incommingLogMessage);
-
- emit incommingLogMessage (Logger::T_LOG_MESSAGE{}); // needed to trigger log sending
- Debug(_log, "log streaming activated for client %s", _peerAddress.toStdString().c_str());
- }
- }
- else if (subcommand == "stop")
- {
- if (_streaming_logging_activated)
- {
- disconnect(LoggerManager::getInstance().data(), &LoggerManager::newLogMessage, this, &JsonAPI::incommingLogMessage);
- _streaming_logging_activated = false;
- Debug(_log, "log streaming deactivated for client %s", _peerAddress.toStdString().c_str());
- }
- }
- else
- {
- return;
- }
-
- sendSuccessReply(command + "-" + subcommand, tan);
- }
- else
- {
- sendErrorReply("No Authorization", command + "-" + subcommand, tan);
+ case SubCommand::Stop:
+ _jsonCB->unsubscribe("logmsg-update");
+ sendSuccessReply(cmd);
+ break;
+ default:
+ break;
}
}
-void JsonAPI::handleProcessingCommand(const QJsonObject &message, const QString &command, int tan)
+void JsonAPI::handleProcessingCommand(const QJsonObject &message, const JsonApiCommand& cmd)
{
API::setLedMappingType(ImageProcessor::mappingTypeToInt(message["mappingType"].toString("multicolor_mean")));
- sendSuccessReply(command, tan);
+ sendSuccessReply(cmd);
}
-void JsonAPI::handleVideoModeCommand(const QJsonObject &message, const QString &command, int tan)
+void JsonAPI::handleVideoModeCommand(const QJsonObject &message, const JsonApiCommand& cmd)
{
API::setVideoMode(parse3DMode(message["videoMode"].toString("2D")));
- sendSuccessReply(command, tan);
+ sendSuccessReply(cmd);
}
-void JsonAPI::handleAuthorizeCommand(const QJsonObject &message, const QString &command, int tan)
+void JsonAPI::handleAuthorizeCommand(const QJsonObject &message, const JsonApiCommand& cmd)
+{
+ switch (cmd.subCommand) {
+ case SubCommand::TokenRequired:
+ handleTokenRequired(cmd);
+ break;
+ case SubCommand::AdminRequired:
+ handleAdminRequired(cmd);
+ break;
+ case SubCommand::NewPasswordRequired:
+ handleNewPasswordRequired(cmd);
+ break;
+ case SubCommand::Logout:
+ handleLogout(cmd);
+ break;
+ case SubCommand::NewPassword:
+ handleNewPassword(message, cmd);
+ break;
+ case SubCommand::CreateToken:
+ handleCreateToken(message, cmd);
+ break;
+ case SubCommand::RenameToken:
+ handleRenameToken(message, cmd);
+ break;
+ case SubCommand::DeleteToken:
+ handleDeleteToken(message, cmd);
+ break;
+ case SubCommand::RequestToken:
+ handleRequestToken(message, cmd);
+ break;
+ case SubCommand::GetPendingTokenRequests:
+ handleGetPendingTokenRequests(cmd);
+ break;
+ case SubCommand::AnswerRequest:
+ handleAnswerRequest(message, cmd);
+ break;
+ case SubCommand::GetTokenList:
+ handleGetTokenList(cmd);
+ break;
+ case SubCommand::Login:
+ handleLogin(message, cmd);
+ break;
+ default:
+ return;
+ }
+}
+
+void JsonAPI::handleTokenRequired(const JsonApiCommand& cmd)
+{
+ bool isTokenRequired = !islocalConnection() || _authManager->isLocalAuthRequired();
+ QJsonObject response { { "required", isTokenRequired} };
+ sendSuccessDataReply(response, cmd);
+}
+
+void JsonAPI::handleAdminRequired(const JsonApiCommand& cmd)
+{
+ bool isAdminAuthRequired = true;
+ QJsonObject response { { "adminRequired", isAdminAuthRequired} };
+ sendSuccessDataReply(response, cmd);
+}
+
+void JsonAPI::handleNewPasswordRequired(const JsonApiCommand& cmd)
+{
+ QJsonObject response { { "newPasswordRequired", API::hasHyperionDefaultPw() } };
+ sendSuccessDataReply(response, cmd);
+}
+
+void JsonAPI::handleLogout(const JsonApiCommand& cmd)
+{
+ API::logout();
+ sendSuccessReply(cmd);
+}
+
+void JsonAPI::handleNewPassword(const QJsonObject &message, const JsonApiCommand& cmd)
+{
+ const QString password = message["password"].toString().trimmed();
+ const QString newPassword = message["newPassword"].toString().trimmed();
+ if (API::updateHyperionPassword(password, newPassword)) {
+ sendSuccessReply(cmd);
+ } else {
+ sendErrorReply("Failed to update user password", cmd);
+ }
+}
+
+void JsonAPI::handleCreateToken(const QJsonObject &message, const JsonApiCommand& cmd)
{
- const QString &subc = message["subcommand"].toString().trimmed();
- const QString &id = message["id"].toString().trimmed();
- const QString &password = message["password"].toString().trimmed();
- const QString &newPassword = message["newPassword"].toString().trimmed();
const QString &comment = message["comment"].toString().trimmed();
+ AuthManager::AuthDefinition def;
+ const QString createTokenResult = API::createToken(comment, def);
+ if (createTokenResult.isEmpty()) {
+ QJsonObject newTok;
+ newTok["comment"] = def.comment;
+ newTok["id"] = def.id;
+ newTok["token"] = def.token;
- // catch test if auth is required
- if (subc == "tokenRequired")
- {
- QJsonObject req;
- req["required"] = !API::isAuthorized();
-
- sendSuccessDataReply(QJsonDocument(req), command + "-" + subc, tan);
- return;
- }
-
- // catch test if admin auth is required
- if (subc == "adminRequired")
- {
- QJsonObject req;
- req["adminRequired"] = !API::isAdminAuthorized();
- sendSuccessDataReply(QJsonDocument(req), command + "-" + subc, tan);
- return;
- }
-
- // default hyperion password is a security risk, replace it asap
- if (subc == "newPasswordRequired")
- {
- QJsonObject req;
- req["newPasswordRequired"] = API::hasHyperionDefaultPw();
- sendSuccessDataReply(QJsonDocument(req), command + "-" + subc, tan);
- return;
- }
-
- // catch logout
- if (subc == "logout")
- {
- // disconnect all kind of data callbacks
- JsonAPI::stopDataConnections(); // TODO move to API
- API::logout();
- sendSuccessReply(command + "-" + subc, tan);
- return;
- }
-
- // change password
- if (subc == "newPassword")
- {
- // use password, newPassword
- if (API::isAdminAuthorized())
- {
- if (API::updateHyperionPassword(password, newPassword))
- {
- sendSuccessReply(command + "-" + subc, tan);
- return;
- }
- sendErrorReply("Failed to update user password", command + "-" + subc, tan);
- return;
- }
- sendErrorReply("No Authorization", command + "-" + subc, tan);
- return;
- }
-
- // token created from ui
- if (subc == "createToken")
- {
- // use comment
- // for user authorized sessions
- AuthManager::AuthDefinition def;
- const QString createTokenResult = API::createToken(comment, def);
- if (createTokenResult.isEmpty())
- {
- QJsonObject newTok;
- newTok["comment"] = def.comment;
- newTok["id"] = def.id;
- newTok["token"] = def.token;
-
- sendSuccessDataReply(QJsonDocument(newTok), command + "-" + subc, tan);
- return;
- }
- sendErrorReply(createTokenResult, command + "-" + subc, tan);
- return;
- }
-
- // rename Token
- if (subc == "renameToken")
- {
- // use id/comment
- const QString renameTokenResult = API::renameToken(id, comment);
- if (renameTokenResult.isEmpty())
- {
- sendSuccessReply(command + "-" + subc, tan);
- return;
- }
- sendErrorReply(renameTokenResult, command + "-" + subc, tan);
- return;
- }
-
- // delete token
- if (subc == "deleteToken")
- {
- // use id
- const QString deleteTokenResult = API::deleteToken(id);
- if (deleteTokenResult.isEmpty())
- {
- sendSuccessReply(command + "-" + subc, tan);
- return;
- }
- sendErrorReply(deleteTokenResult, command + "-" + subc, tan);
- return;
- }
-
- // catch token request
- if (subc == "requestToken")
- {
- // use id/comment
- const bool &acc = message["accept"].toBool(true);
- if (acc)
- API::setNewTokenRequest(comment, id, tan);
- else
- API::cancelNewTokenRequest(comment, id);
- // client should wait for answer
- return;
- }
-
- // get pending token requests
- if (subc == "getPendingTokenRequests")
- {
- QVector vec;
- if (API::getPendingTokenRequests(vec))
- {
- QJsonArray arr;
- for (const auto &entry : std::as_const(vec))
- {
- QJsonObject obj;
- obj["comment"] = entry.comment;
- obj["id"] = entry.id;
- obj["timeout"] = int(entry.timeoutTime);
- arr.append(obj);
- }
- sendSuccessDataReply(QJsonDocument(arr), command + "-" + subc, tan);
- }
- else
- {
- sendErrorReply("No Authorization", command + "-" + subc, tan);
- }
-
- return;
- }
-
- // accept/deny token request
- if (subc == "answerRequest")
- {
- // use id
- const bool &accept = message["accept"].toBool(false);
- if (!API::handlePendingTokenRequest(id, accept))
- sendErrorReply("No Authorization", command + "-" + subc, tan);
- return;
- }
-
- // get token list
- if (subc == "getTokenList")
- {
- QVector defVect;
- if (API::getTokenList(defVect))
- {
- QJsonArray tArr;
- for (const auto &entry : defVect)
- {
- QJsonObject subO;
- subO["comment"] = entry.comment;
- subO["id"] = entry.id;
- subO["last_use"] = entry.lastUse;
-
- tArr.append(subO);
- }
- sendSuccessDataReply(QJsonDocument(tArr), command + "-" + subc, tan);
- return;
- }
- sendErrorReply("No Authorization", command + "-" + subc, tan);
- return;
- }
-
- // login
- if (subc == "login")
- {
- const QString &token = message["token"].toString().trimmed();
-
- // catch token
- if (!token.isEmpty())
- {
- // userToken is longer
- if (token.size() > 36)
- {
- if (API::isUserTokenAuthorized(token))
- sendSuccessReply(command + "-" + subc, tan);
- else
- sendErrorReply("No Authorization", command + "-" + subc, tan);
-
- return;
- }
- // usual app token is 36
- if (token.size() == 36)
- {
- if (API::isTokenAuthorized(token))
- {
- sendSuccessReply(command + "-" + subc, tan);
- }
- else
- sendErrorReply("No Authorization", command + "-" + subc, tan);
- }
- return;
- }
-
- // password
- // use password
- if (password.size() >= 8)
- {
- QString userTokenRep;
- if (API::isUserAuthorized(password) && API::getUserToken(userTokenRep))
- {
- // Return the current valid Hyperion user token
- QJsonObject obj;
- obj["token"] = userTokenRep;
- sendSuccessDataReply(QJsonDocument(obj), command + "-" + subc, tan);
- }
- else
- sendErrorReply("No Authorization", command + "-" + subc, tan);
- }
- else
- sendErrorReply("Password too short", command + "-" + subc, tan);
+ sendSuccessDataReply(newTok, cmd);
+ } else {
+ sendErrorReply("Token creation failed", {createTokenResult}, cmd);
}
}
-void JsonAPI::handleInstanceCommand(const QJsonObject &message, const QString &command, int tan)
+void JsonAPI::handleRenameToken(const QJsonObject &message, const JsonApiCommand& cmd)
{
- const QString &subc = message["subcommand"].toString();
+ const QString &identifier = message["id"].toString().trimmed();
+ const QString &comment = message["comment"].toString().trimmed();
+ const QString renameTokenResult = API::renameToken(identifier, comment);
+ if (renameTokenResult.isEmpty()) {
+ sendSuccessReply(cmd);
+ } else {
+ sendErrorReply("Token rename failed", {renameTokenResult}, cmd);
+ }
+}
+
+void JsonAPI::handleDeleteToken(const QJsonObject &message, const JsonApiCommand& cmd)
+{
+ const QString &identifier = message["id"].toString().trimmed();
+ const QString deleteTokenResult = API::deleteToken(identifier);
+ if (deleteTokenResult.isEmpty()) {
+ sendSuccessReply(cmd);
+ } else {
+ sendErrorReply("Token deletion failed", {deleteTokenResult}, cmd);
+ }
+}
+
+void JsonAPI::handleRequestToken(const QJsonObject &message, const JsonApiCommand& cmd)
+{
+ const QString &identifier = message["id"].toString().trimmed();
+ const QString &comment = message["comment"].toString().trimmed();
+ const bool &acc = message["accept"].toBool(true);
+ if (acc) {
+ API::setNewTokenRequest(comment, identifier, cmd.tan);
+ } else {
+ API::cancelNewTokenRequest(comment, identifier);
+ // client should wait for answer
+ }
+}
+
+void JsonAPI::handleGetPendingTokenRequests(const JsonApiCommand& cmd)
+{
+ QVector vec;
+ if (API::getPendingTokenRequests(vec)) {
+ QJsonArray pendingTokeRequests;
+ for (const auto &entry : std::as_const(vec))
+ {
+ QJsonObject obj;
+ obj["comment"] = entry.comment;
+ obj["id"] = entry.id;
+ obj["timeout"] = int(entry.timeoutTime);
+ obj["tan"] = entry.tan;
+ pendingTokeRequests.append(obj);
+ }
+ sendSuccessDataReply(pendingTokeRequests, cmd);
+ }
+}
+
+void JsonAPI::handleAnswerRequest(const QJsonObject &message, const JsonApiCommand& cmd)
+{
+ const QString &identifier = message["id"].toString().trimmed();
+ const bool &accept = message["accept"].toBool(false);
+ if (API::handlePendingTokenRequest(identifier, accept)) {
+ sendSuccessReply(cmd);
+ } else {
+ sendErrorReply("Unable to handle token acceptance or denial", cmd);
+ }
+}
+
+void JsonAPI::handleGetTokenList(const JsonApiCommand& cmd)
+{
+ QVector defVect;
+ if (API::getTokenList(defVect))
+ {
+ QJsonArray tokenList;
+ for (const auto &entry : std::as_const(defVect))
+ {
+ QJsonObject token;
+ token["comment"] = entry.comment;
+ token["id"] = entry.id;
+ token["last_use"] = entry.lastUse;
+
+ tokenList.append(token);
+ }
+ sendSuccessDataReply(tokenList, cmd);
+ }
+}
+
+void JsonAPI::handleLogin(const QJsonObject &message, const JsonApiCommand& cmd)
+{
+ const QString &token = message["token"].toString().trimmed();
+ if (!token.isEmpty())
+ {
+ // userToken is longer than app token
+ if (token.size() > APP_TOKEN_LENGTH)
+ {
+ if (API::isUserTokenAuthorized(token)) {
+ sendSuccessReply(cmd);
+ } else {
+ sendNoAuthorization(cmd);
+ }
+
+ return;
+ }
+
+ if (token.size() == APP_TOKEN_LENGTH)
+ {
+ if (API::isTokenAuthorized(token)) {
+ sendSuccessReply(cmd);
+ } else {
+ sendNoAuthorization(cmd);
+ }
+ }
+ return;
+ }
+
+ // password
+ const QString &password = message["password"].toString().trimmed();
+ if (password.size() >= MIN_PASSWORD_LENGTH)
+ {
+ QString userTokenRep;
+ if (API::isUserAuthorized(password) && API::getUserToken(userTokenRep))
+ {
+ // Return the current valid Hyperion user token
+ QJsonObject response { { "token", userTokenRep } };
+ sendSuccessDataReply(response, cmd);
+ }
+ else
+ {
+ sendNoAuthorization(cmd);
+ }
+ }
+ else
+ {
+ sendErrorReply(QString("Password is too short. Minimum length: %1 characters").arg(MIN_PASSWORD_LENGTH), cmd);
+ }
+}
+
+void JsonAPI::issueNewPendingTokenRequest(const QString &identifier, const QString &comment)
+{
+ QJsonObject tokenRequest;
+ tokenRequest["comment"] = comment;
+ tokenRequest["id"] = identifier;
+ tokenRequest["timeout"] = static_cast(NEW_TOKEN_REQUEST_TIMEOUT.count());
+
+ sendNewRequest(tokenRequest, "authorize-tokenRequest");
+}
+
+void JsonAPI::handleTokenResponse(bool success, const QString &token, const QString &comment, const QString &identifier, const int &tan)
+{
+ const QString cmd = "authorize-requestToken";
+ QJsonObject result;
+ result["token"] = token;
+ result["comment"] = comment;
+ result["id"] = identifier;
+
+ if (success) {
+ sendSuccessDataReply(result, cmd, tan);
+ } else {
+ sendErrorReply("Token request timeout or denied", {}, cmd, tan);
+ }
+}
+
+void JsonAPI::handleInstanceCommand(const QJsonObject &message, const JsonApiCommand& cmd)
+{
+ QString replyMsg;
+
const quint8 &inst = static_cast(message["instance"].toInt());
const QString &name = message["name"].toString();
- if (subc == "switchTo")
- {
+ switch (cmd.subCommand) {
+ case SubCommand::SwitchTo:
if (handleInstanceSwitch(inst))
{
- QJsonObject obj;
- obj["instance"] = inst;
- sendSuccessDataReply(QJsonDocument(obj), command + "-" + subc, tan);
+ QJsonObject response { { "instance", inst } };
+ sendSuccessDataReply(response, cmd);
}
else
- sendErrorReply("Selected Hyperion instance isn't running", command + "-" + subc, tan);
- return;
- }
-
- if (subc == "startInstance")
- {
- //Only send update once
- weakConnect(this, &API::onStartInstanceResponse, [this, command, subc] (int tan)
{
- sendSuccessReply(command + "-" + subc, tan);
+ sendErrorReply("Selected Hyperion instance is not running", cmd);
+ }
+ break;
+
+ case SubCommand::StartInstance:
+ //Only send update once
+ weakConnect(this, &API::onStartInstanceResponse, [this, cmd] ()
+ {
+ sendSuccessReply(cmd);
});
- if (!API::startInstance(inst, tan))
- sendErrorReply("Can't start Hyperion instance index " + QString::number(inst), command + "-" + subc, tan);
-
- return;
- }
-
- if (subc == "stopInstance")
- {
+ if (!API::startInstance(inst, cmd.tan))
+ {
+ sendErrorReply("Cannot start Hyperion instance index " + QString::number(inst), cmd);
+ }
+ break;
+ case SubCommand::StopInstance:
// silent fail
API::stopInstance(inst);
- sendSuccessReply(command + "-" + subc, tan);
- return;
- }
+ sendSuccessReply(cmd);
+ break;
- if (subc == "deleteInstance")
- {
- QString replyMsg;
+ case SubCommand::DeleteInstance:
+ handleConfigRestoreCommand(message, cmd);
if (API::deleteInstance(inst, replyMsg))
- sendSuccessReply(command + "-" + subc, tan);
+ {
+ sendSuccessReply(cmd);
+ }
else
- sendErrorReply(replyMsg, command + "-" + subc, tan);
- return;
- }
+ {
+ sendErrorReply(replyMsg, cmd);
+ }
+ break;
- // create and save name requires name
- if (name.isEmpty())
- sendErrorReply("Name string required for this command", command + "-" + subc, tan);
+ case SubCommand::CreateInstance:
+ case SubCommand::SaveName:
+ // create and save name requires name
+ if (name.isEmpty()) {
+ sendErrorReply("Name string required for this command", cmd);
+ return;
+ }
- if (subc == "createInstance")
- {
- QString replyMsg = API::createInstance(name);
- if (replyMsg.isEmpty())
- sendSuccessReply(command + "-" + subc, tan);
- else
- sendErrorReply(replyMsg, command + "-" + subc, tan);
- return;
- }
+ if (cmd.subCommand == SubCommand::CreateInstance) {
+ replyMsg = API::createInstance(name);
+ } else {
+ replyMsg = setInstanceName(inst, name);
+ }
- if (subc == "saveName")
- {
- QString replyMsg = API::setInstanceName(inst, name);
- if (replyMsg.isEmpty())
- sendSuccessReply(command + "-" + subc, tan);
- else
- sendErrorReply(replyMsg, command + "-" + subc, tan);
- return;
+ if (replyMsg.isEmpty()) {
+ sendSuccessReply(cmd);
+ } else {
+ sendErrorReply(replyMsg, cmd);
+ }
+ break;
+ default:
+ break;
}
}
-void JsonAPI::handleLedDeviceCommand(const QJsonObject &message, const QString &command, int tan)
+void JsonAPI::handleLedDeviceCommand(const QJsonObject &message, const JsonApiCommand& cmd)
{
- Debug(_log, "message: [%s]", QString(QJsonDocument(message).toJson(QJsonDocument::Compact)).toUtf8().constData() );
-
- const QString &subc = message["subcommand"].toString().trimmed();
const QString &devType = message["ledDeviceType"].toString().trimmed();
+ const LedDeviceRegistry& ledDevices = LedDeviceWrapper::getDeviceMap();
- QString full_command = command + "-" + subc;
-
- // TODO: Validate that device type is a valid one
-
- {
- QJsonObject config;
- config.insert("type", devType);
- LedDevice* ledDevice = nullptr;
-
- if (subc == "discover")
- {
- ledDevice = LedDeviceFactory::construct(config);
- const QJsonObject ¶ms = message["params"].toObject();
- const QJsonObject devicesDiscovered = ledDevice->discover(params);
-
- Debug(_log, "response: [%s]", QString(QJsonDocument(devicesDiscovered).toJson(QJsonDocument::Compact)).toUtf8().constData() );
-
- sendSuccessDataReply(QJsonDocument(devicesDiscovered), full_command, tan);
- }
- else if (subc == "getProperties")
- {
- ledDevice = LedDeviceFactory::construct(config);
- const QJsonObject ¶ms = message["params"].toObject();
- const QJsonObject deviceProperties = ledDevice->getProperties(params);
-
- Debug(_log, "response: [%s]", QString(QJsonDocument(deviceProperties).toJson(QJsonDocument::Compact)).toUtf8().constData() );
-
- sendSuccessDataReply(QJsonDocument(deviceProperties), full_command, tan);
- }
- else if (subc == "identify")
- {
- ledDevice = LedDeviceFactory::construct(config);
- const QJsonObject ¶ms = message["params"].toObject();
- ledDevice->identify(params);
-
- sendSuccessReply(full_command, tan);
- }
- else if (subc == "addAuthorization")
- {
- ledDevice = LedDeviceFactory::construct(config);
- const QJsonObject& params = message["params"].toObject();
- const QJsonObject response = ledDevice->addAuthorization(params);
-
- Debug(_log, "response: [%s]", QString(QJsonDocument(response).toJson(QJsonDocument::Compact)).toUtf8().constData());
-
- sendSuccessDataReply(QJsonDocument(response), full_command, tan);
- }
- else
- {
- sendErrorReply("Unknown or missing subcommand", full_command, tan);
- }
-
- delete ledDevice;
+ if (ledDevices.count(devType) == 0) {
+ sendErrorReply(QString("Unknown LED-Device type: %1").arg(devType), cmd);
+ return;
}
+
+ QJsonObject config { { "type", devType } };
+ LedDevice* ledDevice = LedDeviceFactory::construct(config);
+
+ switch (cmd.subCommand) {
+ case SubCommand::Discover:
+ handleLedDeviceDiscover(*ledDevice, message, cmd);
+ break;
+ case SubCommand::GetProperties:
+ handleLedDeviceGetProperties(*ledDevice, message, cmd);
+ break;
+ case SubCommand::Identify:
+ handleLedDeviceIdentify(*ledDevice, message, cmd);
+ break;
+ case SubCommand::AddAuthorization:
+ handleLedDeviceAddAuthorization(*ledDevice, message, cmd);
+ break;
+ default:
+ break;
+ }
+
+ delete ledDevice;
}
-void JsonAPI::handleInputSourceCommand(const QJsonObject& message, const QString& command, int tan)
+void JsonAPI::handleLedDeviceDiscover(LedDevice& ledDevice, const QJsonObject& message, const JsonApiCommand& cmd)
{
- DebugIf(verbose, _log, "message: [%s]", QString(QJsonDocument(message).toJson(QJsonDocument::Compact)).toUtf8().constData());
+ const QJsonObject ¶ms = message["params"].toObject();
+ const QJsonObject devicesDiscovered = ledDevice.discover(params);
+ Debug(_log, "response: [%s]", QJsonDocument(devicesDiscovered).toJson(QJsonDocument::Compact).constData() );
+ sendSuccessDataReply(devicesDiscovered, cmd);
+}
- const QString& subc = message["subcommand"].toString().trimmed();
+void JsonAPI::handleLedDeviceGetProperties(LedDevice& ledDevice, const QJsonObject& message, const JsonApiCommand& cmd)
+{
+ const QJsonObject ¶ms = message["params"].toObject();
+ const QJsonObject deviceProperties = ledDevice.getProperties(params);
+ Debug(_log, "response: [%s]", QJsonDocument(deviceProperties).toJson(QJsonDocument::Compact).constData() );
+ sendSuccessDataReply(deviceProperties, cmd);
+}
+
+void JsonAPI::handleLedDeviceIdentify(LedDevice& ledDevice, const QJsonObject& message, const JsonApiCommand& cmd)
+{
+ const QJsonObject ¶ms = message["params"].toObject();
+ ledDevice.identify(params);
+ sendSuccessReply(cmd);
+}
+
+void JsonAPI::handleLedDeviceAddAuthorization(LedDevice& ledDevice, const QJsonObject& message, const JsonApiCommand& cmd)
+{
+ const QJsonObject& params = message["params"].toObject();
+ const QJsonObject response = ledDevice.addAuthorization(params);
+ sendSuccessDataReply(response, cmd);
+}
+
+void JsonAPI::handleInputSourceCommand(const QJsonObject& message, const JsonApiCommand& cmd) {
const QString& sourceType = message["sourceType"].toString().trimmed();
+ const QStringList sourceTypes {"screen", "video", "audio"};
- QString full_command = command + "-" + subc;
+ if (!sourceTypes.contains(sourceType)) {
+ sendErrorReply(QString("Unknown input source type: %1").arg(sourceType), cmd);
+ return;
+ }
- // TODO: Validate that source type is a valid one
- {
- if (subc == "discover")
- {
- QJsonObject inputSourcesDiscovered;
- inputSourcesDiscovered.insert("sourceType", sourceType);
- QJsonArray videoInputs;
- QJsonArray audioInputs;
+ if (cmd.subCommand == SubCommand::Discover) {
-#if defined(ENABLE_V4L2) || defined(ENABLE_MF)
+ const QJsonObject& params = message["params"].toObject();
+ QJsonObject inputSourcesDiscovered = JsonInfo().discoverSources(sourceType, params);
- if (sourceType == "video" )
- {
-#if defined(ENABLE_MF)
- MFGrabber* grabber = new MFGrabber();
-#elif defined(ENABLE_V4L2)
- V4L2Grabber* grabber = new V4L2Grabber();
-#endif
- QJsonObject params;
- videoInputs = grabber->discover(params);
- delete grabber;
- }
- else
-#endif
+ DebugIf(verbose, _log, "response: [%s]", QJsonDocument(inputSourcesDiscovered).toJson(QJsonDocument::Compact).constData());
-#if defined(ENABLE_AUDIO)
- if (sourceType == "audio")
- {
- AudioGrabber* grabber;
-#ifdef WIN32
- grabber = new AudioGrabberWindows();
-#endif
-
-#ifdef __linux__
- grabber = new AudioGrabberLinux();
-#endif
- QJsonObject params;
- audioInputs = grabber->discover(params);
- delete grabber;
- }
- else
-#endif
- {
- DebugIf(verbose, _log, "sourceType: [%s]", QSTRING_CSTR(sourceType));
-
- if (sourceType == "screen")
- {
- QJsonObject params;
-
- QJsonObject device;
- #ifdef ENABLE_QT
- QScopedPointer qtgrabber(new QtGrabber());
- device = qtgrabber->discover(params);
- if (!device.isEmpty() )
- {
- videoInputs.append(device);
- }
- #endif
-
- #ifdef ENABLE_DX
- QScopedPointer dxgrabber (new DirectXGrabber());
- device = dxgrabber->discover(params);
- if (!device.isEmpty() )
- {
- videoInputs.append(device);
- }
- #endif
-
- #ifdef ENABLE_X11
- QScopedPointer x11Grabber(new X11Grabber());
- device = x11Grabber->discover(params);
- if (!device.isEmpty() )
- {
- videoInputs.append(device);
- }
- #endif
-
- #ifdef ENABLE_XCB
- QScopedPointer xcbGrabber (new XcbGrabber());
- device = xcbGrabber->discover(params);
- if (!device.isEmpty() )
- {
- videoInputs.append(device);
- }
- #endif
-
- //Ignore FB for Amlogic, as it is embedded in the Amlogic grabber itself
- #if defined(ENABLE_FB) && !defined(ENABLE_AMLOGIC)
-
- QScopedPointer fbGrabber(new FramebufferFrameGrabber());
- device = fbGrabber->discover(params);
- if (!device.isEmpty() )
- {
- videoInputs.append(device);
- }
- #endif
-
- #if defined(ENABLE_DISPMANX)
- QScopedPointer dispmanx(new DispmanxFrameGrabber());
- if (dispmanx->isAvailable())
- {
- device = dispmanx->discover(params);
- if (!device.isEmpty() )
- {
- videoInputs.append(device);
- }
- }
- #endif
-
- #if defined(ENABLE_AMLOGIC)
- QScopedPointer amlGrabber(new AmlogicGrabber());
- device = amlGrabber->discover(params);
- if (!device.isEmpty() )
- {
- videoInputs.append(device);
- }
- #endif
-
- #if defined(ENABLE_OSX)
- QScopedPointer osxGrabber(new OsxFrameGrabber());
- device = osxGrabber->discover(params);
- if (!device.isEmpty() )
- {
- videoInputs.append(device);
- }
- #endif
- }
-
- }
- inputSourcesDiscovered["video_sources"] = videoInputs;
- inputSourcesDiscovered["audio_sources"] = audioInputs;
-
- DebugIf(verbose, _log, "response: [%s]", QString(QJsonDocument(inputSourcesDiscovered).toJson(QJsonDocument::Compact)).toUtf8().constData());
-
- sendSuccessDataReply(QJsonDocument(inputSourcesDiscovered), full_command, tan);
- }
- else
- {
- sendErrorReply("Unknown or missing subcommand", full_command, tan);
- }
+ sendSuccessDataReply(inputSourcesDiscovered, cmd);
}
}
-void JsonAPI::handleServiceCommand(const QJsonObject &message, const QString &command, int tan)
+void JsonAPI::handleServiceCommand(const QJsonObject &message, const JsonApiCommand& cmd)
{
- DebugIf(verbose, _log, "message: [%s]", QString(QJsonDocument(message).toJson(QJsonDocument::Compact)).toUtf8().constData());
-
- const QString &subc = message["subcommand"].toString().trimmed();
- const QString type = message["serviceType"].toString().trimmed();
-
- QString full_command = command + "-" + subc;
-
- if (subc == "discover")
+ if (cmd.subCommand == SubCommand::Discover)
{
QByteArray serviceType;
-
- QJsonObject servicesDiscovered;
- QJsonObject servicesOfType;
- QJsonArray serviceList;
-
+ const QString type = message["serviceType"].toString().trimmed();
#ifdef ENABLE_MDNS
QString discoveryMethod("mDNS");
serviceType = MdnsServiceRegister::getServiceType(type);
@@ -1817,212 +1324,168 @@ void JsonAPI::handleServiceCommand(const QJsonObject &message, const QString &co
#endif
if (!serviceType.isEmpty())
{
+ QJsonArray serviceList;
#ifdef ENABLE_MDNS
QMetaObject::invokeMethod(MdnsBrowser::getInstance().data(), "browseForServiceType",
- Qt::QueuedConnection, Q_ARG(QByteArray, serviceType));
+ Qt::QueuedConnection, Q_ARG(QByteArray, serviceType));
serviceList = MdnsBrowser::getInstance().data()->getServicesDiscoveredJson(serviceType, MdnsServiceRegister::getServiceNameFilter(type), DEFAULT_DISCOVER_TIMEOUT);
#endif
+ QJsonObject servicesDiscovered;
+ QJsonObject servicesOfType;
+
servicesOfType.insert(type, serviceList);
servicesDiscovered.insert("discoveryMethod", discoveryMethod);
servicesDiscovered.insert("services", servicesOfType);
- sendSuccessDataReply(QJsonDocument(servicesDiscovered), full_command, tan);
+ sendSuccessDataReply(servicesDiscovered, cmd);
}
else
{
- sendErrorReply(QString("Discovery of service type [%1] via %2 not supported").arg(type, discoveryMethod), full_command, tan);
+ sendErrorReply(QString("Discovery of service type [%1] via %2 not supported").arg(type, discoveryMethod), cmd);
}
}
- else
- {
- sendErrorReply("Unknown or missing subcommand", full_command, tan);
- }
}
-void JsonAPI::handleSystemCommand(const QJsonObject &message, const QString &command, int tan)
+void JsonAPI::handleSystemCommand(const QJsonObject& /*message*/, const JsonApiCommand& cmd)
{
- DebugIf(verbose, _log, "message: [%s]", QString(QJsonDocument(message).toJson(QJsonDocument::Compact)).toUtf8().constData());
-
- const QString &subc = message["subcommand"].toString().trimmed();
-
- if (subc == "suspend")
- {
+ switch (cmd.subCommand) {
+ case SubCommand::Suspend:
emit signalEvent(Event::Suspend);
- sendSuccessReply(command + "-" + subc, tan);
- }
- else if (subc == "resume")
- {
+ break;
+ case SubCommand::Resume:
emit signalEvent(Event::Resume);
- sendSuccessReply(command + "-" + subc, tan);
- }
- else if (subc == "restart")
- {
+ break;
+ case SubCommand::Restart:
emit signalEvent(Event::Restart);
- sendSuccessReply(command + "-" + subc, tan);
- }
- else if (subc == "toggleSuspend")
- {
+ break;
+ case SubCommand::ToggleSuspend:
emit signalEvent(Event::ToggleSuspend);
- sendSuccessReply(command + "-" + subc, tan);
- }
- else if (subc == "idle")
- {
+ break;
+ case SubCommand::Idle:
emit signalEvent(Event::Idle);
- sendSuccessReply(command + "-" + subc, tan);
- }
- else if (subc == "resumeIdle")
- {
- emit signalEvent(Event::ResumeIdle);
- sendSuccessReply(command + "-" + subc, tan);
- }
- else if (subc == "toggleIdle")
- {
+ break;
+ case SubCommand::ToggleIdle:
emit signalEvent(Event::ToggleIdle);
- sendSuccessReply(command + "-" + subc, tan);
+ break;
+ default:
+ return;
}
- else
+ sendSuccessReply(cmd);
+}
+
+QJsonObject JsonAPI::getBasicCommandReply(bool success, const QString &command, int tan, InstanceCmd::Type isInstanceCmd) const
+{
+ QJsonObject reply;
+ reply["success"] = success;
+ reply["command"] = command;
+ reply["tan"] = tan;
+
+ if (isInstanceCmd == InstanceCmd::Yes || ( isInstanceCmd == InstanceCmd::Multi && !_noListener))
{
- QString full_command = command + "-" + subc;
- sendErrorReply("Unknown or missing subcommand", full_command, tan);
+ reply["instance"] = _hyperion->getInstanceIndex();
}
+ return reply;
}
-void JsonAPI::handleNotImplemented(const QString &command, int tan)
+void JsonAPI::sendSuccessReply(const JsonApiCommand& cmd)
{
- sendErrorReply("Command not implemented", command, tan);
+ sendSuccessReply(cmd.toString(), cmd.tan, cmd.isInstanceCmd);
}
-void JsonAPI::sendSuccessReply(const QString &command, int tan)
+void JsonAPI::sendSuccessReply(const QString &command, int tan, InstanceCmd::Type isInstanceCmd)
{
- // create reply
- QJsonObject reply;
- reply["instance"] = _hyperion->getInstanceIndex();
- reply["success"] = true;
- reply["command"] = command;
- reply["tan"] = tan;
-
- // send reply
- emit callbackMessage(reply);
+ emit callbackMessage(getBasicCommandReply(true, command, tan , isInstanceCmd));
}
-void JsonAPI::sendSuccessDataReply(const QJsonDocument &doc, const QString &command, int tan)
+void JsonAPI::sendSuccessDataReply(const QJsonValue &infoData, const JsonApiCommand& cmd)
{
- QJsonObject reply;
- reply["instance"] = _hyperion->getInstanceIndex();
- reply["success"] = true;
- reply["command"] = command;
- reply["tan"] = tan;
- if (doc.isArray())
- reply["info"] = doc.array();
- else
- reply["info"] = doc.object();
+ sendSuccessDataReplyWithError(infoData, cmd.toString(), cmd.tan, {}, cmd.isInstanceCmd);
+}
+
+void JsonAPI::sendSuccessDataReply(const QJsonValue &infoData, const QString &command, int tan, InstanceCmd::Type isInstanceCmd)
+{
+ sendSuccessDataReplyWithError(infoData, command, tan, {}, isInstanceCmd);
+}
+
+void JsonAPI::sendSuccessDataReplyWithError(const QJsonValue &infoData, const JsonApiCommand& cmd, const QStringList& errorDetails)
+{
+ sendSuccessDataReplyWithError(infoData, cmd.toString(), cmd.tan, errorDetails, cmd.isInstanceCmd);
+}
+
+void JsonAPI::sendSuccessDataReplyWithError(const QJsonValue &infoData, const QString &command, int tan, const QStringList& errorDetails, InstanceCmd::Type isInstanceCmd)
+{
+ QJsonObject reply {getBasicCommandReply(true, command, tan , isInstanceCmd)};
+ reply["info"] = infoData;
+
+ if (!errorDetails.isEmpty())
+ {
+ QJsonArray errorsArray;
+ for (const QString& errorString : errorDetails)
+ {
+ QJsonObject errorObject;
+ errorObject["description"] = errorString;
+ errorsArray.append(errorObject);
+ }
+ reply["errorData"] = errorsArray;
+ }
emit callbackMessage(reply);
}
-void JsonAPI::sendErrorReply(const QString &error, const QString &command, int tan)
+void JsonAPI::sendErrorReply(const QString &error, const JsonApiCommand& cmd)
{
- // create reply
- QJsonObject reply;
- reply["instance"] = _hyperion->getInstanceIndex();
- reply["success"] = false;
+ sendErrorReply(error, {}, cmd.toString(), cmd.tan, cmd.isInstanceCmd);
+}
+
+void JsonAPI::sendErrorReply(const QString &error, const QStringList& errorDetails, const JsonApiCommand& cmd)
+{
+ sendErrorReply(error, errorDetails, cmd.toString(), cmd.tan, cmd.isInstanceCmd);
+}
+
+void JsonAPI::sendErrorReply(const QString &error, const QStringList& errorDetails, const QString &command, int tan, InstanceCmd::Type isInstanceCmd)
+{
+ QJsonObject reply {getBasicCommandReply(false, command, tan , isInstanceCmd)};
reply["error"] = error;
- reply["command"] = command;
- reply["tan"] = tan;
+ if (!errorDetails.isEmpty())
+ {
+ QJsonArray errorsArray;
+ for (const QString& errorString : errorDetails)
+ {
+ QJsonObject errorObject;
+ errorObject["description"] = errorString;
+ errorsArray.append(errorObject);
+ }
+ reply["errorData"] = errorsArray;
+ }
- // send reply
emit callbackMessage(reply);
}
-void JsonAPI::streamLedcolorsUpdate(const std::vector &ledColors)
+void JsonAPI::sendNewRequest(const QJsonValue &infoData, const JsonApiCommand& cmd)
{
- QJsonObject result;
- QJsonArray leds;
+ sendSuccessDataReplyWithError(infoData, cmd.toString(), cmd.isInstanceCmd);
+}
- for (const auto &color : ledColors)
+void JsonAPI::sendNewRequest(const QJsonValue &infoData, const QString &command, InstanceCmd::Type isInstanceCmd)
+{
+ QJsonObject request;
+ request["command"] = command;
+
+ if (isInstanceCmd != InstanceCmd::No)
{
- leds << QJsonValue(color.red) << QJsonValue(color.green) << QJsonValue(color.blue);
+ request["instance"] = _hyperion->getInstanceIndex();
}
- result["leds"] = leds;
- _streaming_leds_reply["result"] = result;
+ request["info"] = infoData;
- // send the result
- emit callbackMessage(_streaming_leds_reply);
+ emit callbackMessage(request);
}
-void JsonAPI::setImage(const Image &image)
+void JsonAPI::sendNoAuthorization(const JsonApiCommand& cmd)
{
- QImage jpgImage((const uint8_t *)image.memptr(), image.width(), image.height(), 3 * image.width(), QImage::Format_RGB888);
- QByteArray ba;
- QBuffer buffer(&ba);
- buffer.open(QIODevice::WriteOnly);
- jpgImage.save(&buffer, "jpg");
-
- QJsonObject result;
- result["image"] = "data:image/jpg;base64," + QString(ba.toBase64());
- _streaming_image_reply["result"] = result;
- emit callbackMessage(_streaming_image_reply);
-}
-
-void JsonAPI::incommingLogMessage(const Logger::T_LOG_MESSAGE &msg)
-{
- QJsonObject result, message;
- QJsonArray messageArray;
-
- if (!_streaming_logging_activated)
- {
- _streaming_logging_activated = true;
- QMetaObject::invokeMethod(LoggerManager::getInstance().data(), "getLogMessageBuffer",
- Qt::DirectConnection,
- Q_RETURN_ARG(QJsonArray, messageArray),
- Q_ARG(Logger::LogLevel, _log->getLogLevel()));
- }
- else
- {
- message["loggerName"] = msg.loggerName;
- message["loggerSubName"] = msg.loggerSubName;
- message["function"] = msg.function;
- message["line"] = QString::number(msg.line);
- message["fileName"] = msg.fileName;
- message["message"] = msg.message;
- message["levelString"] = msg.levelString;
- message["utime"] = QString::number(msg.utime);
-
- messageArray.append(message);
- }
-
- result.insert("messages", messageArray);
- _streaming_logging_reply["result"] = result;
-
- // send the result
- emit callbackMessage(_streaming_logging_reply);
-}
-
-void JsonAPI::newPendingTokenRequest(const QString &id, const QString &comment)
-{
- QJsonObject obj;
- obj["comment"] = comment;
- obj["id"] = id;
- obj["timeout"] = 180000;
-
- sendSuccessDataReply(QJsonDocument(obj), "authorize-tokenRequest", 1);
-}
-
-void JsonAPI::handleTokenResponse(bool success, const QString &token, const QString &comment, const QString &id, const int &tan)
-{
- const QString cmd = "authorize-requestToken";
- QJsonObject result;
- result["token"] = token;
- result["comment"] = comment;
- result["id"] = id;
-
- if (success)
- sendSuccessDataReply(QJsonDocument(result), cmd, tan);
- else
- sendErrorReply("Token request timeout or denied", cmd, tan);
+ sendErrorReply(NO_AUTHORIZATION, cmd);
}
void JsonAPI::handleInstanceStateChange(InstanceState state, quint8 instance, const QString& /*name */)
@@ -2034,24 +1497,45 @@ void JsonAPI::handleInstanceStateChange(InstanceState state, quint8 instance, co
{
handleInstanceSwitch();
}
- break;
+ break;
case InstanceState::H_STARTED:
case InstanceState::H_STOPPED:
case InstanceState::H_CREATED:
case InstanceState::H_DELETED:
- default:
- break;
+ break;
}
}
void JsonAPI::stopDataConnections()
{
- LoggerManager::getInstance()->disconnect();
- _streaming_logging_activated = false;
_jsonCB->resetSubscriptions();
- // led stream colors
- disconnect(_hyperion, &Hyperion::rawLedColors, this, 0);
- _ledStreamTimer->stop();
- disconnect(_ledStreamConnection);
+ LoggerManager::getInstance()->disconnect();
+}
+
+QString JsonAPI::findCommand (const QString& jsonString)
+{
+ QString commandValue {"unknown"};
+
+ // Define a regular expression pattern to match the value associated with the key "command"
+ static QRegularExpression regex("\"command\"\\s*:\\s*\"([^\"]+)\"");
+ QRegularExpressionMatch match = regex.match(jsonString);
+
+ if (match.hasMatch()) {
+ commandValue = match.captured(1);
+ }
+ return commandValue;
+}
+
+int JsonAPI::findTan (const QString& jsonString)
+{
+ int tanValue {0};
+ static QRegularExpression regex("\"tan\"\\s*:\\s*(\\d+)");
+ QRegularExpressionMatch match = regex.match(jsonString);
+
+ if (match.hasMatch()) {
+ QString valueStr = match.captured(1);
+ tanValue = valueStr.toInt();
+ }
+ return tanValue;
}
diff --git a/libsrc/api/JsonCB.cpp b/libsrc/api/JsonCB.cpp
deleted file mode 100644
index e3c4b32a..00000000
--- a/libsrc/api/JsonCB.cpp
+++ /dev/null
@@ -1,416 +0,0 @@
-// proj incl
-#include
-
-// hyperion
-#include
-
-// HyperionIManager
-#include
-// components
-
-#include
-// priorityMuxer
-
-#include
-
-// utils
-#include
-
-// qt
-#include
-#include
-
-// Image to led map helper
-#include
-
-using namespace hyperion;
-
-JsonCB::JsonCB(QObject* parent)
- : QObject(parent)
- , _hyperion(nullptr)
- , _componentRegister(nullptr)
- , _prioMuxer(nullptr)
-{
- _availableCommands << "components-update" << "priorities-update" << "imageToLedMapping-update"
- << "adjustment-update" << "videomode-update" << "settings-update" << "leds-update" << "instance-update" << "token-update";
-
- #if defined(ENABLE_EFFECTENGINE)
- _availableCommands << "effects-update";
- #endif
-
- qRegisterMetaType("InputsMap");
-}
-
-bool JsonCB::subscribeFor(const QString& type, bool unsubscribe)
-{
- if(!_availableCommands.contains(type))
- return false;
-
- if(unsubscribe)
- _subscribedCommands.removeAll(type);
- else
- _subscribedCommands << type;
-
- if(type == "components-update")
- {
- if(unsubscribe)
- disconnect(_componentRegister, &ComponentRegister::updatedComponentState, this, &JsonCB::handleComponentState);
- else
- connect(_componentRegister, &ComponentRegister::updatedComponentState, this, &JsonCB::handleComponentState, Qt::UniqueConnection);
- }
-
- if(type == "priorities-update")
- {
- if (unsubscribe)
- disconnect(_prioMuxer, &PriorityMuxer::prioritiesChanged, this, &JsonCB::handlePriorityUpdate);
- else
- connect(_prioMuxer, &PriorityMuxer::prioritiesChanged, this, &JsonCB::handlePriorityUpdate, Qt::UniqueConnection);
- }
-
- if(type == "imageToLedMapping-update")
- {
- if(unsubscribe)
- disconnect(_hyperion, &Hyperion::imageToLedsMappingChanged, this, &JsonCB::handleImageToLedsMappingChange);
- else
- connect(_hyperion, &Hyperion::imageToLedsMappingChanged, this, &JsonCB::handleImageToLedsMappingChange, Qt::UniqueConnection);
- }
-
- if(type == "adjustment-update")
- {
- if(unsubscribe)
- disconnect(_hyperion, &Hyperion::adjustmentChanged, this, &JsonCB::handleAdjustmentChange);
- else
- connect(_hyperion, &Hyperion::adjustmentChanged, this, &JsonCB::handleAdjustmentChange, Qt::UniqueConnection);
- }
-
- if(type == "videomode-update")
- {
- if(unsubscribe)
- disconnect(_hyperion, &Hyperion::newVideoMode, this, &JsonCB::handleVideoModeChange);
- else
- connect(_hyperion, &Hyperion::newVideoMode, this, &JsonCB::handleVideoModeChange, Qt::UniqueConnection);
- }
-
-#if defined(ENABLE_EFFECTENGINE)
- if(type == "effects-update")
- {
- if(unsubscribe)
- disconnect(_hyperion, &Hyperion::effectListUpdated, this, &JsonCB::handleEffectListChange);
- else
- connect(_hyperion, &Hyperion::effectListUpdated, this, &JsonCB::handleEffectListChange, Qt::UniqueConnection);
- }
-#endif
-
- if(type == "settings-update")
- {
- if(unsubscribe)
- disconnect(_hyperion, &Hyperion::settingsChanged, this, &JsonCB::handleSettingsChange);
- else
- connect(_hyperion, &Hyperion::settingsChanged, this, &JsonCB::handleSettingsChange, Qt::UniqueConnection);
- }
-
- if(type == "leds-update")
- {
- if(unsubscribe)
- disconnect(_hyperion, &Hyperion::settingsChanged, this, &JsonCB::handleLedsConfigChange);
- else
- connect(_hyperion, &Hyperion::settingsChanged, this, &JsonCB::handleLedsConfigChange, Qt::UniqueConnection);
- }
-
-
- if(type == "instance-update")
- {
- if(unsubscribe)
- disconnect(HyperionIManager::getInstance(), &HyperionIManager::change, this, &JsonCB::handleInstanceChange);
- else
- connect(HyperionIManager::getInstance(), &HyperionIManager::change, this, &JsonCB::handleInstanceChange, Qt::UniqueConnection);
- }
-
- if (type == "token-update")
- {
- if (unsubscribe)
- disconnect(AuthManager::getInstance(), &AuthManager::tokenChange, this, &JsonCB::handleTokenChange);
- else
- connect(AuthManager::getInstance(), &AuthManager::tokenChange, this, &JsonCB::handleTokenChange, Qt::UniqueConnection);
- }
-
- return true;
-}
-
-void JsonCB::resetSubscriptions()
-{
- for(const auto & entry : getSubscribedCommands())
- {
- subscribeFor(entry, true);
- }
-}
-
-void JsonCB::setSubscriptionsTo(Hyperion* hyperion)
-{
- assert(hyperion);
-
- // get current subs
- QStringList currSubs(getSubscribedCommands());
-
- // stop subs
- resetSubscriptions();
-
- // update pointer
- _hyperion = hyperion;
- _componentRegister = _hyperion->getComponentRegister();
- _prioMuxer = _hyperion->getMuxerInstance();
-
- // re-apply subs
- for(const auto & entry : currSubs)
- {
- subscribeFor(entry);
- }
-}
-
-void JsonCB::doCallback(const QString& cmd, const QVariant& data)
-{
- QJsonObject obj;
- obj["instance"] = _hyperion->getInstanceIndex();
- obj["command"] = cmd;
-
- if (data.userType() == QMetaType::QJsonArray)
- obj["data"] = data.toJsonArray();
- else
- obj["data"] = data.toJsonObject();
-
- emit newCallback(obj);
-}
-
-void JsonCB::handleComponentState(hyperion::Components comp, bool state)
-{
- QJsonObject data;
- data["name"] = componentToIdString(comp);
- data["enabled"] = state;
-
- doCallback("components-update", QVariant(data));
-}
-
-void JsonCB::handlePriorityUpdate(int currentPriority, const PriorityMuxer::InputsMap& activeInputs)
-{
- QJsonObject data;
- QJsonArray priorities;
- uint64_t now = QDateTime::currentMSecsSinceEpoch();
- QList activePriorities = activeInputs.keys();
-
- activePriorities.removeAll(PriorityMuxer::LOWEST_PRIORITY);
-
- for (int priority : std::as_const(activePriorities)) {
-
- const Hyperion::InputInfo& priorityInfo = activeInputs[priority];
-
- QJsonObject item;
- item["priority"] = priority;
-
- if (priorityInfo.timeoutTime_ms > 0 )
- {
- item["duration_ms"] = int(priorityInfo.timeoutTime_ms - now);
- }
-
- // owner has optional informations to the component
- if(!priorityInfo.owner.isEmpty())
- {
- item["owner"] = priorityInfo.owner;
- }
-
- item["componentId"] = QString(hyperion::componentToIdString(priorityInfo.componentId));
- item["origin"] = priorityInfo.origin;
- item["active"] = (priorityInfo.timeoutTime_ms >= -1);
- item["visible"] = (priority == currentPriority);
-
- if(priorityInfo.componentId == hyperion::COMP_COLOR && !priorityInfo.ledColors.empty())
- {
- QJsonObject LEDcolor;
-
- // add RGB Value to Array
- QJsonArray RGBValue;
- RGBValue.append(priorityInfo.ledColors.begin()->red);
- RGBValue.append(priorityInfo.ledColors.begin()->green);
- RGBValue.append(priorityInfo.ledColors.begin()->blue);
- LEDcolor.insert("RGB", RGBValue);
-
- uint16_t Hue;
- float Saturation;
- float Luminace;
-
- // add HSL Value to Array
- QJsonArray HSLValue;
- ColorSys::rgb2hsl(priorityInfo.ledColors.begin()->red,
- priorityInfo.ledColors.begin()->green,
- priorityInfo.ledColors.begin()->blue,
- Hue, Saturation, Luminace);
-
- HSLValue.append(Hue);
- HSLValue.append(Saturation);
- HSLValue.append(Luminace);
- LEDcolor.insert("HSL", HSLValue);
-
- item["value"] = LEDcolor;
- }
- priorities.append(item);
- }
-
- data["priorities"] = priorities;
- data["priorities_autoselect"] = _hyperion->sourceAutoSelectEnabled();
-
- doCallback("priorities-update", QVariant(data));
-}
-
-void JsonCB::handleImageToLedsMappingChange(int mappingType)
-{
- QJsonObject data;
- data["imageToLedMappingType"] = ImageProcessor::mappingTypeToStr(mappingType);
-
- doCallback("imageToLedMapping-update", QVariant(data));
-}
-
-void JsonCB::handleAdjustmentChange()
-{
- QJsonArray adjustmentArray;
- for (const QString& adjustmentId : _hyperion->getAdjustmentIds())
- {
- const ColorAdjustment * colorAdjustment = _hyperion->getAdjustment(adjustmentId);
- if (colorAdjustment == nullptr)
- {
- continue;
- }
-
- QJsonObject adjustment;
- adjustment["id"] = adjustmentId;
-
- QJsonArray whiteAdjust;
- whiteAdjust.append(colorAdjustment->_rgbWhiteAdjustment.getAdjustmentR());
- whiteAdjust.append(colorAdjustment->_rgbWhiteAdjustment.getAdjustmentG());
- whiteAdjust.append(colorAdjustment->_rgbWhiteAdjustment.getAdjustmentB());
- adjustment.insert("white", whiteAdjust);
-
- QJsonArray redAdjust;
- redAdjust.append(colorAdjustment->_rgbRedAdjustment.getAdjustmentR());
- redAdjust.append(colorAdjustment->_rgbRedAdjustment.getAdjustmentG());
- redAdjust.append(colorAdjustment->_rgbRedAdjustment.getAdjustmentB());
- adjustment.insert("red", redAdjust);
-
- QJsonArray greenAdjust;
- greenAdjust.append(colorAdjustment->_rgbGreenAdjustment.getAdjustmentR());
- greenAdjust.append(colorAdjustment->_rgbGreenAdjustment.getAdjustmentG());
- greenAdjust.append(colorAdjustment->_rgbGreenAdjustment.getAdjustmentB());
- adjustment.insert("green", greenAdjust);
-
- QJsonArray blueAdjust;
- blueAdjust.append(colorAdjustment->_rgbBlueAdjustment.getAdjustmentR());
- blueAdjust.append(colorAdjustment->_rgbBlueAdjustment.getAdjustmentG());
- blueAdjust.append(colorAdjustment->_rgbBlueAdjustment.getAdjustmentB());
- adjustment.insert("blue", blueAdjust);
-
- QJsonArray cyanAdjust;
- cyanAdjust.append(colorAdjustment->_rgbCyanAdjustment.getAdjustmentR());
- cyanAdjust.append(colorAdjustment->_rgbCyanAdjustment.getAdjustmentG());
- cyanAdjust.append(colorAdjustment->_rgbCyanAdjustment.getAdjustmentB());
- adjustment.insert("cyan", cyanAdjust);
-
- QJsonArray magentaAdjust;
- magentaAdjust.append(colorAdjustment->_rgbMagentaAdjustment.getAdjustmentR());
- magentaAdjust.append(colorAdjustment->_rgbMagentaAdjustment.getAdjustmentG());
- magentaAdjust.append(colorAdjustment->_rgbMagentaAdjustment.getAdjustmentB());
- adjustment.insert("magenta", magentaAdjust);
-
- QJsonArray yellowAdjust;
- yellowAdjust.append(colorAdjustment->_rgbYellowAdjustment.getAdjustmentR());
- yellowAdjust.append(colorAdjustment->_rgbYellowAdjustment.getAdjustmentG());
- yellowAdjust.append(colorAdjustment->_rgbYellowAdjustment.getAdjustmentB());
- adjustment.insert("yellow", yellowAdjust);
-
- adjustment["backlightThreshold"] = colorAdjustment->_rgbTransform.getBacklightThreshold();
- adjustment["backlightColored"] = colorAdjustment->_rgbTransform.getBacklightColored();
- adjustment["brightness"] = colorAdjustment->_rgbTransform.getBrightness();
- adjustment["brightnessCompensation"] = colorAdjustment->_rgbTransform.getBrightnessCompensation();
- adjustment["gammaRed"] = colorAdjustment->_rgbTransform.getGammaR();
- adjustment["gammaGreen"] = colorAdjustment->_rgbTransform.getGammaG();
- adjustment["gammaBlue"] = colorAdjustment->_rgbTransform.getGammaB();
-
- adjustmentArray.append(adjustment);
- }
-
- doCallback("adjustment-update", QVariant(adjustmentArray));
-}
-
-void JsonCB::handleVideoModeChange(VideoMode mode)
-{
- QJsonObject data;
- data["videomode"] = QString(videoMode2String(mode));
- doCallback("videomode-update", QVariant(data));
-}
-
-#if defined(ENABLE_EFFECTENGINE)
-void JsonCB::handleEffectListChange()
-{
- QJsonArray effectList;
- QJsonObject effects;
- const std::list & effectsDefinitions = _hyperion->getEffects();
- for (const EffectDefinition & effectDefinition : effectsDefinitions)
- {
- QJsonObject effect;
- effect["name"] = effectDefinition.name;
- effect["file"] = effectDefinition.file;
- effect["script"] = effectDefinition.script;
- effect["args"] = effectDefinition.args;
- effectList.append(effect);
- };
- effects["effects"] = effectList;
- doCallback("effects-update", QVariant(effects));
-}
-#endif
-
-void JsonCB::handleSettingsChange(settings::type type, const QJsonDocument& data)
-{
- QJsonObject dat;
- if(data.isObject())
- dat[typeToString(type)] = data.object();
- else
- dat[typeToString(type)] = data.array();
-
- doCallback("settings-update", QVariant(dat));
-}
-
-void JsonCB::handleLedsConfigChange(settings::type type, const QJsonDocument& data)
-{
- if(type == settings::LEDS)
- {
- QJsonObject dat;
- dat[typeToString(type)] = data.array();
- doCallback("leds-update", QVariant(dat));
- }
-}
-
-void JsonCB::handleInstanceChange()
-{
- QJsonArray arr;
-
- for(const auto & entry : HyperionIManager::getInstance()->getInstanceData())
- {
- QJsonObject obj;
- obj.insert("friendly_name", entry["friendly_name"].toString());
- obj.insert("instance", entry["instance"].toInt());
- obj.insert("running", entry["running"].toBool());
- arr.append(obj);
- }
- doCallback("instance-update", QVariant(arr));
-}
-
-void JsonCB::handleTokenChange(const QVector &def)
-{
- QJsonArray arr;
- for (const auto &entry : def)
- {
- QJsonObject sub;
- sub["comment"] = entry.comment;
- sub["id"] = entry.id;
- sub["last_use"] = entry.lastUse;
- arr.push_back(sub);
- }
- doCallback("token-update", QVariant(arr));
-}
diff --git a/libsrc/api/JsonCallbacks.cpp b/libsrc/api/JsonCallbacks.cpp
new file mode 100644
index 00000000..0e156727
--- /dev/null
+++ b/libsrc/api/JsonCallbacks.cpp
@@ -0,0 +1,459 @@
+#include
+#include
+#include
+
+#include
+#include
+#include