diff --git a/CHANGELOG.md b/CHANGELOG.md index 480bc72f..2f38c07f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,10 +10,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- hyperion-remote & standalone grabbers: IPv6 support + ### Changed +- LED-Devices: Removed IPv6 limitations +- Philips-Hue Wizard optimizations +- JSON/Flatbuffer forwarder: Removed IPv6 limitations +- Allow to import configurations from previous versions + +Note: Existing configurations are migrated to new structures automatically + ### Fixed +- Fixed hyperion-remote when sending multiple Hex-Colors with "Set Color" option + ### Removed ## [2.0.0-alpha.11](https://github.com/hyperion-project/hyperion.ng/releases/tag/2.0.0-alpha.11) - 2021-10-06 diff --git a/assets/webconfig/i18n/en.json b/assets/webconfig/i18n/en.json index 84c5da1e..32a37f67 100644 --- a/assets/webconfig/i18n/en.json +++ b/assets/webconfig/i18n/en.json @@ -46,7 +46,7 @@ "conf_general_intro": "Basic settings around Hyperion and WebUI that don't fit into another category.", "conf_general_label_title": "General settings", "conf_grabber_fg_intro": "Screen capture is your local system capture as input source, Hyperion is installed on.", - "conf_grabber_inst_grabber_config_info": "Configure your capturing hardware devices to be used by the instance in advance", + "conf_grabber_inst_grabber_config_info": "Configure your capturing hardware devices to be used by the instance in advance", "conf_grabber_v4l_intro": "USB capture is a (capture) device connected via USB which is used to input source pictures for processing.", "conf_helptable_expl": "Explanation", "conf_helptable_option": "Option", @@ -581,9 +581,9 @@ "edt_dev_spec_switchOffOnBlack_title": "Switch off on black", "edt_dev_spec_switchOffOnbelowMinBrightness_title": "Switch-off, below minimum", "edt_dev_spec_syncOverwrite_title": "Disable synchronisation", - "edt_dev_spec_targetIpHost_title": "Target Hostname/IP-address", + "edt_dev_spec_targetIpHost_title": "Hostname/IP-address", "edt_dev_spec_targetIpHost_title_info": "The device's hostname or IP-address", - "edt_dev_spec_targetIp_title": "Target IP-address", + "edt_dev_spec_targetIp_title": "IP-address", "edt_dev_spec_transeffect_title": "Transition effect", "edt_dev_spec_transistionTimeExtra_title": "Extra time darkness", "edt_dev_spec_transistionTime_title": "Transition time", @@ -654,7 +654,7 @@ "edt_eff_image_source_url": "URL", "edt_eff_image": "Image file", "edt_eff_url": "Image adress", - "edt_eff_initial_blink" : "Flash for attention", + "edt_eff_initial_blink": "Flash for attention", "edt_eff_interval": "Interval", "edt_eff_knightrider_header": "Knight Rider", "edt_eff_knightrider_header_desc": "K.I.T.T is back! The front-scanner of the well known car, this time not just in red.", @@ -692,7 +692,7 @@ "edt_eff_reversedirection": "Reverse direction", "edt_eff_rotationtime": "Rotation time", "edt_eff_saturation": "Saturation", - "edt_eff_set_post_color" : "Set post color after alam", + "edt_eff_set_post_color": "Set post color after alam", "edt_eff_showseconds": "Show seconds", "edt_eff_sleeptime": "Sleep time", "edt_eff_smooth_custom": "Enable smoothing", @@ -735,6 +735,10 @@ "edt_msg_error_disallow": "Value must not be of type $1", "edt_msg_error_disallow_union": "Value must not be one of the provided disallowed types", "edt_msg_error_enum": "Value must be one of the enumerated values", + "edt_msg_error_hostname": "The hostname has the wrong format", + "edt_msg_error_invalid_epoch": "Date must be greater than 1 January 1970", + "edt_msg_error_ipv4": "Value must be a valid IPv4 address in the form of 4 numbers between 0 and 255, separated by dots", + "edt_msg_error_ipv6": "Value must be a valid IPv6 address", "edt_msg_error_maxItems": "Value must have at most $1 items", "edt_msg_error_maxLength": "Value must be at most $1 characters long", "edt_msg_error_maxProperties": "Object must have at most $1 properties", @@ -755,6 +759,8 @@ "edt_msg_error_type": "Value must be of type $1", "edt_msg_error_type_union": "Value must be one of the provided types", "edt_msg_error_uniqueItems": "Array must have unique items", + "edt_msgcust_error_hostname_ip": "Not a valid Hostname nor IPv4 nor IPv6", + "edt_msgcust_error_hostname_ip4": "Not a valid Hostname nor IPv4", "effectsconfigurator_button_conttest": "Continuous test", "effectsconfigurator_button_deleffect": "Delete Effect", "effectsconfigurator_button_editeffect": "Load Effect", @@ -979,15 +985,15 @@ "wiz_cololight_title": "Cololight Wizard", "wiz_guideyou": "The $1 will guide you through the settings. Just press the button!", "wiz_hue_blinkblue": "Let ID $1 light up blue", - "wiz_hue_clientkey": "Clientkey:", + "wiz_hue_clientkey": "Clientkey", "wiz_hue_create_user": "Create new User", - "wiz_hue_desc1": "It searches automatically for a Hue-Bridge, in case it can't find one you need to provide the IP-address and push the reload button on the right. Now you need a user ID, if you don't have one create a new one.", - "wiz_hue_desc2": "Now choose which lamps should be added. The position assigns the lamp to a specific position on your \"picture\". Disabled lamps won't be added. To identify single lamps press the button on the right.", + "wiz_hue_desc1": "1. Hyperion searches automatically for a Hue-Bridge, in case it cannot find one you need to provide the hostname or IP-address and push the reload button.
2. Provide a user ID, if you do not have one create a new one.", + "wiz_hue_desc2": "3. Now choose which lamps should be added. The position assigns the lamp to a specific position on your \"picture\". Disabled lamps won't be added. To identify single lamps press the button on the right.", "wiz_hue_e_clientkey_needed": "A clientkey that matches the username is required to use the entertainment API. Please enter an existing one or use the button below to create a new one.", "wiz_hue_e_create_user": "Create new User and clientkey", - "wiz_hue_e_desc1": "It searches automatically for a hue bridge, in case it can't find one you need to provide the ip address and push the reload button on the right. Now you need a user id and the clientkey, if you don't have both, create a new one.", - "wiz_hue_e_desc2": "Now choose your entertainment group, which has all your lights inside for use with Hyperion.", - "wiz_hue_e_desc3": "Now you can choose in which position the respective lamp should be \"in the picture\". A preselection of the position was made based on the configured positions of the lights in the entertainment group. This is just a recommendation and can be customized as desired. You can therefore highlight them briefly by clicking on the right button to improve the selection.", + "wiz_hue_e_desc1": "1. Hyperion searches automatically for a Hue-Bridge, in case it cannot find one you need to provide the hostname or IP-address and push the reload button.
2. Provide a user id and the clientkey, if you do not have both, create new ones.", + "wiz_hue_e_desc2": "3. Choose your entertainment group, which has all your lights inside for use with Hyperion.", + "wiz_hue_e_desc3": "4. Choose in which position the respective lamp should be \"in the picture\". A preselection of the position was made based on the configured positions of the lights in the entertainment group. This is just a recommendation and can be customized as desired. You can therefore highlight them briefly by clicking on the right button to improve the selection.", "wiz_hue_e_intro1": "This wizards configures Hyperion for the well known Philips Hue Entertainment system. Features are: Hue Bridge auto detection, user and clientkey creation, entertainment group selection, setting group lights to a specific position on your picture and optimise the Hyperion settings automatically! So in short: All you need are some clicks and you are done!", "wiz_hue_e_noapisupport": "The Wizard has disabled entertainment API support and will continue in classic mode.", "wiz_hue_e_noapisupport_hint": "The option \"Use Hue Entertainment API\" was unchecked.", @@ -997,15 +1003,15 @@ "wiz_hue_e_use_group": "Use group", "wiz_hue_e_use_groupid": "Use group ID $1", "wiz_hue_failure_connection": "Timeout: Please press the bridge button within the period of 30 seconds", - "wiz_hue_failure_ip": "No Bridge found, please type in a valid IP-Address", + "wiz_hue_failure_ip": "No Bridge found, please provide a valid hostname or IP-address", "wiz_hue_failure_user": "User not found, create a new one with the button below or input a valid user id and press the \"reload\" symbol.", "wiz_hue_intro1": "This wizards configures Hyperion for the well known Philips Hue system. Features are Hue Bridge auto detection, user creation, set each hue light to a specific position on your picture or disable it and tune the Hyperion settings automatically! So in short: All you need are some clicks and you are done!", - "wiz_hue_ip": "Hue Bridge IP:", + "wiz_hue_ip": "Hostname or IP", "wiz_hue_noids": "This Hue bridge has no bulbs/stripes, please pair them before with the Hue Apps", "wiz_hue_press_link": "Please press link button on the Hue Bridge.", "wiz_hue_searchb": "Searching for bridge...", "wiz_hue_title": "Philips Hue Wizard", - "wiz_hue_username": "User ID:", + "wiz_hue_username": "User ID", "wiz_identify": "Identify", "wiz_identify_tip": "Identify configured device by lighting it up", "wiz_identify_light": "Identify $1", @@ -1021,6 +1027,7 @@ "wiz_rgb_qrend": "...red?", "wiz_rgb_switchevery": "Switch color every...", "wiz_rgb_title": "RGB Byte Order Wizard", + "wiz_cc_try_connect": "Connecting...", "wiz_wizavail": "Wizard available", "wiz_yeelight_desc2": "Now choose which lamps should be added. The position assigns the lamp to a specific position on your \"picture\". Disabled lamps won't be added. To identify single lamps press the button on the right.", "wiz_yeelight_intro1": "This wizards configures Hyperion for the Yeelight system. Features are the Yeelighs' auto detection, setting each light to a specific position on your picture or disable it and tune the Hyperion settings automatically! So in short: All you need are some clicks and you are done!", diff --git a/assets/webconfig/js/content_general.js b/assets/webconfig/js/content_general.js index da972ba6..c6865c51 100644 --- a/assets/webconfig/js/content_general.js +++ b/assets/webconfig/js/content_general.js @@ -144,7 +144,7 @@ $(document).ready(function () { showInfoDialog('import', $.i18n('infoDialog_import_confirm_title'), $.i18n('infoDialog_import_confirm_text', confName)); $('#id_btn_import').off().on('click', function () { - requestWriteConfig(importedConf, true); + requestRestoreConfig(importedConf); setTimeout(initRestart, 100); }); }); diff --git a/assets/webconfig/js/content_leds.js b/assets/webconfig/js/content_leds.js index d76f5fd3..83180850 100755 --- a/assets/webconfig/js/content_leds.js +++ b/assets/webconfig/js/content_leds.js @@ -641,6 +641,8 @@ $(document).ready(function () { // External properties properties, 2-dimensional arry of [ledType][key] devicesProperties = {}; + addJsonEditorHostValidation(); + $("#leddevices").off().on("change", function () { var generalOptions = window.serverSchema.properties.device; @@ -1092,7 +1094,7 @@ $(document).ready(function () { hwLedCount.setValue(lights.length); } }); - + //Handle Hardware Led Count constraint list conf_editor.watch('root.generalOptions.hardwareLedCountList', () => { var hwLedCountSelected = conf_editor.getEditor("root.generalOptions.hardwareLedCountList").getValue(); @@ -1629,9 +1631,8 @@ function updateElements(ledType, key) { if (ledProperties && ledProperties.leds && ledProperties.maxLedCount) { hardwareLedCount = ledProperties.leds.count; var maxLedCount = ledProperties.maxLedCount - if (hardwareLedCount > maxLedCount) - { - showInfoDialog('warning', $.i18n("conf_leds_config_warning"), $.i18n('conf_leds_error_hwled_gt_maxled', hardwareLedCount, maxLedCount, maxLedCount)); + if (hardwareLedCount > maxLedCount) { + showInfoDialog('warning', $.i18n("conf_leds_config_warning"), $.i18n('conf_leds_error_hwled_gt_maxled', hardwareLedCount, maxLedCount, maxLedCount)); hardwareLedCount = maxLedCount; } } @@ -1661,8 +1662,7 @@ function updateElements(ledType, key) { if (ledProperties && ledProperties.maxLedCount) { hardwareLedCount = conf_editor.getEditor("root.generalOptions.hardwareLedCount").getValue(); var maxLedCount = ledProperties.maxLedCount - if (hardwareLedCount > maxLedCount) - { + if (hardwareLedCount > maxLedCount) { showInfoDialog('warning', $.i18n("conf_leds_config_warning"), $.i18n('conf_leds_error_hwled_gt_maxled', hardwareLedCount, maxLedCount, maxLedCount)); hardwareLedCount = maxLedCount; } @@ -1678,8 +1678,8 @@ function updateElements(ledType, key) { if (ledProperties.ledCount.length > 0) { var configuredLedCount = window.serverConfig.device.hardwareLedCount; showInputOptionForItem(conf_editor, 'generalOptions', "hardwareLedCount", false); - updateJsonEditorSelection(conf_editor, 'root.generalOptions', "hardwareLedCountList", {"title": "edt_dev_general_hardwareLedCount_title"}, - ledProperties.ledCount.map(String), [], configuredLedCount); + updateJsonEditorSelection(conf_editor, 'root.generalOptions', "hardwareLedCountList", { "title": "edt_dev_general_hardwareLedCount_title" }, + ledProperties.ledCount.map(String), [], configuredLedCount); } } break; diff --git a/assets/webconfig/js/content_network.js b/assets/webconfig/js/content_network.js index fb96119a..41c5f601 100644 --- a/assets/webconfig/js/content_network.js +++ b/assets/webconfig/js/content_network.js @@ -10,6 +10,8 @@ $(document).ready(function () { var conf_editor_bobl = null; var conf_editor_forw = null; + addJsonEditorHostValidation(); + if (window.showOptHelp) { //network $('#conf_cont').append(createRow('conf_cont_net')) diff --git a/assets/webconfig/js/hyperion.js b/assets/webconfig/js/hyperion.js index 9402e383..2a6de47f 100644 --- a/assets/webconfig/js/hyperion.js +++ b/assets/webconfig/js/hyperion.js @@ -410,6 +410,10 @@ function requestWriteConfig(config, full) sendToHyperion("config","setconfig", '"config":'+JSON.stringify(window.serverConfig)); } +function requestRestoreConfig(config) { + sendToHyperion("config", "restoreconfig", '"config":' + JSON.stringify(config)); +} + function requestWriteEffect(effectName,effectPy,effectArgs,data) { var cutArgs = effectArgs.slice(1, -1); diff --git a/assets/webconfig/js/ui_utils.js b/assets/webconfig/js/ui_utils.js index b6c72e4b..526852d9 100644 --- a/assets/webconfig/js/ui_utils.js +++ b/assets/webconfig/js/ui_utils.js @@ -88,7 +88,7 @@ function loadContent(event, forceRefresh) { var tag; var lastSelectedInstance = getStorage('lastSelectedInstance', false); - + if (lastSelectedInstance && (lastSelectedInstance != window.currentHyperionInstance)) { if (window.serverInfo.instance[lastSelectedInstance] && window.serverInfo.instance[lastSelectedInstance].running) { instanceSwitch(lastSelectedInstance); @@ -681,6 +681,68 @@ function updateJsonEditorRange(rootEditor, path, key, minimum, maximum, defaultV rootEditor.getEditor(path + "." + key).setValue(currentValue); } +function addJsonEditorHostValidation() { + + JSONEditor.defaults.custom_validators.push(function (schema, value, path) { + var errors = []; + + if (!jQuery.isEmptyObject(value)) { + switch (schema.format) { + case "hostname_or_ip": + if (!isValidHostnameOrIP(value)) { + errors.push({ + path: path, + property: 'format', + message: $.i18n('edt_msgcust_error_hostname_ip') + }); + } + break; + case "hostname_or_ip4": + if (!isValidHostnameOrIP4(value)) { + errors.push({ + path: path, + property: 'format', + message: $.i18n('edt_msgcust_error_hostname_ip4') + }); + } + break; + + //Remove, when new json-editor 2.x is used + case "ipv4": + if (!isValidIPv4(value)) { + errors.push({ + path: path, + property: 'format', + message: $.i18n('edt_msg_error_ipv4') + }); + } + break; + case "ipv6": + if (!isValidIPv6(value)) { + errors.push({ + path: path, + property: 'format', + message: $.i18n('edt_msg_error_ipv6') + }); + } + break; + case "hostname": + if (!isValidHostname(value)) { + errors.push({ + path: path, + property: 'format', + message: $.i18n('edt_msg_error_hostname') + }); + } + break; + + default: + } + } + return errors; + }); +} + function buildWL(link, linkt, cl) { var baseLink = "https://docs.hyperion-project.org/"; var lang; @@ -1246,3 +1308,42 @@ function showInputOptionsForKey(editor, item, showForKeys, state) { function encodeHTML(s) { return s.replace(/&/g, '&').replace(/ 255) { + return false; + } + } + return true; +} + +function isValidIPv6(value) { + if (value.match( + '^(?:(?:(?:[a-fA-F0-9]{1,4}:){6}|(?=(?:[a-fA-F0-9]{0,4}:){2,6}(?:[0-9]{1,3}.){3}[0-9]{1,3}$)(([0-9a-fA-F]{1,4}:){1,5}|:)((:[0-9a-fA-F]{1,4}){1,5}:|:)|::(?:[a-fA-F0-9]{1,4}:){5})(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9]).){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])|(?:[a-fA-F0-9]{1,4}:){7}[a-fA-F0-9]{1,4}|(?=(?:[a-fA-F0-9]{0,4}:){0,7}[a-fA-F0-9]{0,4}$)(([0-9a-fA-F]{1,4}:){1,7}|:)((:[0-9a-fA-F]{1,4}){1,7}|:)|(?:[a-fA-F0-9]{1,4}:){7}:|:(:[a-fA-F0-9]{1,4}){7})$' + )) + return true; + else + return false; +} + +function isValidHostname(value) { + if (value.match( + '(?=^.{4,253}$)(^((?!-)[a-zA-Z0-9-]{0,62}[a-zA-Z0-9].)+[a-zA-Z]{2,63}$)' + )) + return true; + else + return false; +} + +function isValidHostnameOrIP4(value) { + return (isValidHostname(value) || isValidIPv4(value)); +} + +function isValidHostnameOrIP(value) { + return (isValidHostnameOrIP4(value) || isValidIPv6(value)); +} diff --git a/assets/webconfig/js/wizard.js b/assets/webconfig/js/wizard.js index 3f8ee564..5d2bed58 100755 --- a/assets/webconfig/js/wizard.js +++ b/assets/webconfig/js/wizard.js @@ -5,6 +5,10 @@ $(window.hyperion).one("ready", function (event) { setStorage("wizardactive", false); if (getStorage("kodiAddress") != null) { kodiAddress = getStorage("kodiAddress"); + + if (getStorage("kodiPort") != null) { + kodiPort = getStorage("kodiPort"); + } sendToKodi("stop"); } } @@ -156,9 +160,14 @@ $('#btn_wizard_byteorder').off().on('click', startWizardRGB); //color calibration wizard -var kodiHost = document.location.hostname; -var kodiPort = 9090; -var kodiAddress = kodiHost; +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; @@ -175,14 +184,12 @@ var availVideos = ["Sweet_Cocoon", "Caminandes_2_GranDillama", "Caminandes_3_Lla if (getStorage("kodiAddress") != null) { kodiAddress = getStorage("kodiAddress"); - [kodiHost, kodiPort] = kodiAddress.split(":", 2); + kodiUrl.host = kodiAddress; +} - // Ensure that Kodi's default REST-API port is not used, as now the Web-Socket port is used - if (kodiPort === "8080") { - kodiAddress = kodiHost; - kodiPort = undefined; - setStorage("kodiAddress", kodiAddress); - } +if (getStorage("kodiPort") != null) { + kodiPort = getStorage("kodiPort"); + kodiUrl.port = kodiPort; } function switchPicture(pictures) { @@ -221,12 +228,11 @@ function sendToKodi(type, content, cb) { } if ("WebSocket" in window) { - //Add kodi default web-socket port, in case port has been explicitly provided - if (kodiPort == undefined) { - kodiPort = 9090; - } - var ws = new WebSocket("ws://" + kodiHost + ":" + kodiPort + "/jsonrpc/websocket"); + if (kodiUrl.port === '') { + kodiUrl.port = defaultKodiPort; + } + var ws = new WebSocket(kodiUrl); ws.onopen = function () { ws.send(JSON.stringify(command)); @@ -234,13 +240,18 @@ function sendToKodi(type, content, cb) { ws.onmessage = function (evt) { var response = JSON.parse(evt.data); - - if (cb != undefined) { - if (response.result != undefined) { - if (response.result === "OK") { - cb("success"); - } else { - cb("error"); + 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(); + } } } } @@ -249,8 +260,13 @@ function sendToKodi(type, content, cb) { 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"); @@ -419,18 +435,24 @@ function updateWEditor(el, all) { function startWizardCC() { - // Ensure that Kodi's default REST-API port is not used, as now the Web-Socket port is used - [kodiHost, kodiPort] = kodiAddress.split(":", 2); - if (kodiPort === "8080") { - kodiAddress = kodiHost; - kodiPort = undefined; - } //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(''); + $('#wizp1_body').html('

' + $.i18n('wiz_cc_title') + '

' + + '

' + $.i18n('wiz_cc_intro1') + '

' + + '' + + '' + + '' + ); + $('#wizp1_footer').html('' + + '' + ); + $('#wizp2_body').html('
' + ); + $('#wizp2_footer').html('' + + '' + + '' + + '' + ); if (getStorage("darkMode", false) == "on") $('#wizard_logo').attr("src", 'img/hyperion/logo_negativ.png'); @@ -443,32 +465,44 @@ function startWizardCC() { }); $('#wiz_cc_kodiip').off().on('change', function () { + kodiAddress = $(this).val().trim(); - $('#wizp1_body').find("kodiAddress").val(kodiAddress); $('#kodi_status').html(''); - - // Remove Kodi's default Web-Socket port (9090) from display and ensure Kodi's default REST-API port (8080) is mapped to web-socket port to ease migration if (kodiAddress !== "") { - [kodiHost, kodiPort] = kodiAddress.split(":", 2); - if (kodiPort === "9090" || kodiPort === "8080") { - kodiAddress = kodiHost; - kodiPort = undefined; + + 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').attr('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').attr('disabled', false); + }); } - 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); - - $('#kodi_status').html('

' + $.i18n('wiz_cc_kodicon') + '

'); - withKodi = true; - } - - $('#btn_wiz_cont').attr('disabled', false); - }); } }); @@ -632,6 +666,8 @@ var groupLights = []; var groupLightsLocations = []; var hueType = "philipshue"; +let hueUrl = new URL('http://dummy'); + function startWizardPhilipsHue(e) { if (typeof e.data.type != "undefined") hueType = e.data.type; @@ -651,24 +687,52 @@ function startWizardPhilipsHue(e) { $('#wizp1_body').html('

' + $.i18n(hue_title) + '

' + $.i18n(hue_intro1) + '

'); $('#wizp1_footer').html(''); $('#wizp2_body').html('
'); - $('#wh_topcontainer').append('

' + $.i18n(hue_desc1) + '

'); + + var hidePort = "hidden-lg"; + if (storedAccess === 'expert') { + hidePort = ""; + } + + $('#wh_topcontainer').append('

' + $.i18n(hue_desc1) + '

' + + '
' + + '
' + + '

' + $.i18n('wiz_hue_ip') + '

' + + '
' + + ' ' + + '
' + + '
' + + ' :' + + '
' + + '

' + ); $('#wh_topcontainer').append(); $('#wh_topcontainer').append(''); - if (hueType == 'philipshue') { - $('#usrcont').append('
'); - } + + $('#usrcont').append('

' + $.i18n('wiz_hue_username') + '

' + + '
' + + '
' + + ' ' + + ' ' + + '

' + + '' + ); + if (hueType == 'philipshueentertainment') { - $('#usrcont').append('
'); + $('#usrcont').append('

' + $.i18n('wiz_hue_clientkey') + + '


'); } - $('#usrcont').append('
'); + + $('#usrcont').append('

<\p>' + + ''); + if (hueType == 'philipshueentertainment') { - $('#wizp2_body').append('

'); + $('#wizp2_body').append(''); createTable("gidsh", "gidsb", "hue_grp_ids_t"); $('.gidsh').append(createTableRow([$.i18n('edt_dev_spec_groupId_title'), $.i18n('wiz_hue_e_use_group')], true)); - $('#wizp2_body').append(''); + $('#wizp2_body').append(''); } else { - $('#wizp2_body').append(''); + $('#wizp2_body').append(''); } createTable("lidsh", "lidsb", "hue_ids_t"); $('.lidsh').append(createTableRow([$.i18n('edt_dev_spec_lightid_title'), $.i18n('wiz_pos'), $.i18n('wiz_identify')], true)); @@ -698,18 +762,36 @@ function checkHueBridge(cb, hueUser) { if (usr == 'config') $('#wiz_hue_discovered').html(""); if (hueIPs[hueIPsinc]) { + + hueUrl.hostname = "dummy"; + var host = hueIPs[hueIPsinc].host; + + if (isValidIPv6(host)) { + hueUrl.hostname = "[" + host + "]"; + } else { + hueUrl.hostname = host; + } + + var port = hueIPs[hueIPsinc].port; + if (port > 0) { + hueUrl.port = port; + } + + hueUrl.pathname = '/api/' + usr; $.ajax({ - url: 'http://' + hueIPs[hueIPsinc].internalipaddress + '/api/' + usr, + url: hueUrl, type: "GET", dataType: "json", success: function (json) { if (json.config) { cb(true, usr); - } else if (json.name && json.bridgeid && json.modelid) { - $('#wiz_hue_discovered').html("Bridge: " + json.name + ", Modelid: " + json.modelid + ", API-Version: " + json.apiversion); - cb(true); } else { - cb(false); + if (json.name && json.bridgeid && json.modelid) { + $('#wiz_hue_discovered').html("Bridge: " + json.name + ", Modelid: " + json.modelid + ", API-Version: " + json.apiversion); + cb(true); + } else { + cb(false); + } } }, timeout: 2500 @@ -723,7 +805,8 @@ function checkBridgeResult(reply, usr) { if (reply) { //abort checking, first reachable result is used $('#wiz_hue_ipstate').html(""); - $('#ip').val(hueIPs[hueIPsinc].internalipaddress) + $('#host').val(hueIPs[hueIPsinc].host) + $('#port').val(hueIPs[hueIPsinc].port) //now check hue user on this bridge $('#usrcont').toggle(true); @@ -743,10 +826,11 @@ function checkBridgeResult(reply, usr) { }; function checkUserResult(reply, usr) { + $('#usrcont').toggle(true); if (reply) { $('#user').val(usr); if (hueType == 'philipshueentertainment' && $('#clientkey').val() == "") { - $('#usrcont').toggle(true); + $('#wiz_hue_usrstate').html($.i18n('wiz_hue_e_clientkey_needed')); $('#wiz_hue_create_user').toggle(true); } else { @@ -761,6 +845,7 @@ function checkUserResult(reply, usr) { } } else { + //abort checking, first reachable result is used $('#wiz_hue_usrstate').html($.i18n('wiz_hue_failure_user')); $('#wiz_hue_create_user').toggle(true); } @@ -775,8 +860,9 @@ function identHueId(id, off, oState) { var put_data = '{"on":' + oState.on + ',"bri":' + oState.bri + ',"hue":' + oState.hue + ',"sat":' + oState.sat + '}'; } + hueUrl.pathname = '/api/' + $('#user').val() + '/lights/' + id + '/state', $.ajax({ - url: 'http://' + $('#ip').val() + '/api/' + $('#user').val() + '/lights/' + id + '/state', + url: hueUrl, type: 'PUT', timeout: 2000, data: put_data @@ -806,25 +892,32 @@ async function discover_hue_bridges() { $('#wiz_hue_discovered').html("") } else { - for (const device of r.devices) { - //console.log("Device:", device); - if (device && device.ip && device.port) { + hueIPs = []; + hueIPsinc = 0; - var ip; + for (const device of r.devices) { + if (device && device.ip && device.port) { + var host; + var port; if (device.hostname && device.domain) { - ip = device.hostname + "." + device.domain + ":" + device.port; + host = device.hostname + "." + device.domain; + port = device.port; } else { - ip = device.ip + ":" + device.port; + host = device.ip; + port = device.port; } - if (ip) { + if (host) { - if (!hueIPs.some(item => item.internalipaddress === ip)) { - hueIPs.push({ internalipaddress: ip }); + 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) var usr = $('#user').val(); if (usr != "") { @@ -836,8 +929,11 @@ async function discover_hue_bridges() { } } -async function getProperties_hue_bridge(hostAddress, username, resourceFilter) { +async function getProperties_hue_bridge(hostAddress, port, username, resourceFilter) { let params = { host: hostAddress, user: username, filter: resourceFilter }; + if (port !== 'undefined') { + params.port = port; + } const res = await requestLedDeviceProperties('philipshue', params); @@ -851,12 +947,15 @@ async function getProperties_hue_bridge(hostAddress, username, resourceFilter) { } } -async function identify_hue_device(hostAddress, username, id) { +async function identify_hue_device(hostAddress, port, username, id) { // Take care that new record cannot be save during background process $('#btn_wiz_save').attr('disabled', true); let params = { host: hostAddress, user: username, lightId: id }; + if (port !== 'undefined') { + params.port = port; + } await requestLedDeviceIdentification('philipshue', params); if (!window.readOnlyMode) { @@ -866,6 +965,7 @@ async function identify_hue_device(hostAddress, username, id) { function getHueIPs() { $('#wiz_hue_ipstate').html($.i18n('wiz_hue_searchb')); + $.ajax({ url: 'https://discovery.meethue.com', crossDomain: true, @@ -902,15 +1002,28 @@ function beginWizardHue() { $('#clientkey').val(clkey); } } - //check if ip is empty/reachable/search for bridge + + //check if host is empty/reachable/search for bridge if (eV("host") == "") { + hueIPs = []; + hueIPsinc = 0; + //getHueIPs(); discover_hue_bridges(); } else { - var ip = eV("host"); - $('#ip').val(ip); - hueIPs.unshift({ internalipaddress: ip }); + var host = eV("host"); + $('#host').val(host); + + var port = eV("port"); + if (port > 0) { + $('#port').val(port); + } + else { + $('#port').val(''); + } + hueIPs.unshift({ host: host, port: port }); + if (usr != "") { checkHueBridge(checkUserResult, usr); } else { @@ -919,11 +1032,16 @@ function beginWizardHue() { } $('#retry_bridge').off().on('click', function () { - if ($('#ip').val() != "") { - hueIPs.unshift({ internalipaddress: $('#ip').val() }) + + if ($('#host').val() != "") { + + hueIPs = []; hueIPsinc = 0; + hueIPs.push({ host: $('#host').val(), port: $('#port').val() }); + } + else { + discover_hue_bridges(); } - else discover_hue_bridges(); var usr = $('#user').val(); if (usr != "") { @@ -938,7 +1056,9 @@ function beginWizardHue() { }); $('#wiz_hue_create_user').off().on('click', function () { - if ($('#ip').val() != "") hueIPs.unshift({ internalipaddress: $('#ip').val() }); + if ($('#host').val() != "") { + hueIPs.unshift({ host: $('#host').val(), port: $('#port').val() }); + } createHueUser(); }); @@ -973,12 +1093,12 @@ function beginWizardHue() { //Start with a clean configuration var d = {}; - d.host = $('#ip').val(); + d.host = $('#host').val(); + d.port = parseInt($('#port').val()); d.username = $('#user').val(); d.type = 'philipshue'; d.colorOrder = 'rgb'; d.lightIds = finalLightIds; - d.latchTime = 0; d.transitiontime = parseInt(eV("transitiontime")); d.restoreOriginalState = (eV("restoreOriginalState") == true); d.switchOffOnBlack = (eV("switchOffOnBlack") == true); @@ -1000,7 +1120,6 @@ function beginWizardHue() { if (hueType == 'philipshue') { d.useEntertainmentAPI = false; d.hardwareLedCount = finalLightIds.length; - d.rewriteTime = 0; d.verbose = false; //smoothing off sc.smoothing.enable = false; @@ -1030,9 +1149,11 @@ function createHueUser() { data = { "devicetype": "hyperion#" + Date.now(), "generateclientkey": true } } var UserInterval = setInterval(function () { + + hueUrl.pathname = '/api/'; $.ajax({ type: "POST", - url: 'http://' + $("#ip").val() + '/api', + url: hueUrl, processData: false, timeout: 1000, contentType: 'application/json', @@ -1081,9 +1202,10 @@ function createHueUser() { } function get_hue_groups() { + hueUrl.pathname = '/api/' + $("#user").val() + '/groups'; $.ajax({ type: "GET", - url: 'http://' + $("#ip").val() + '/api/' + $("#user").val() + '/groups', + url: hueUrl, processData: false, contentType: 'application/json', success: function (r) { @@ -1125,9 +1247,10 @@ function noAPISupport(txt) { } function get_light_state(id) { + hueUrl.pathname = '/api/' + $("#user").val() + '/lights/' + id; $.ajax({ type: "GET", - url: 'http://' + $("#ip").val() + '/api/' + $("#user").val() + '/lights/' + id, + url: hueUrl, processData: false, contentType: 'application/json', success: function (r) { @@ -1139,9 +1262,10 @@ function get_light_state(id) { } function get_hue_lights() { + hueUrl.pathname = '/api/' + $("#user").val() + '/lights'; $.ajax({ type: "GET", - url: 'http://' + $("#ip").val() + '/api/' + $("#user").val() + '/lights', + url: hueUrl, processData: false, contentType: 'application/json', success: function (r) { @@ -1199,7 +1323,7 @@ function get_hue_lights() { } $('.lidsb').append(createTableRow([lightid + ' (' + r[lightid].name + ')', '', ''])); + + '', ''])); } if (hueType != 'philipshueentertainment') { @@ -1356,8 +1480,6 @@ async function discover_yeelight_lights() { // Process devices returned by discovery for (const device of r.devices) { - //console.log("Device:", device); - if (device.hostname !== "") { if (getHostInLights(device.hostname).length === 0) { var light = {}; @@ -1381,7 +1503,7 @@ async function discover_yeelight_lights() { // Add additional items from configuration for (var keyConfig in configuredLights) { - var [host, port] = configuredLights[keyConfig].host.split(":", 2); + var host = configuredLights[keyConfig].host; //In case port has been explicitly provided, overwrite port given as part of hostname if (configuredLights[keyConfig].port !== 0) diff --git a/config/hyperion.config.json.default b/config/hyperion.config.json.default index 5461841f..1f0ac39b 100644 --- a/config/hyperion.config.json.default +++ b/config/hyperion.config.json.default @@ -139,8 +139,8 @@ "forwarder" : { "enable" : false, - "json" : ["127.0.0.1:19446"], - "flat" : ["127.0.0.1:19401"] + "json" : [], + "flat" : [] }, "jsonServer" : diff --git a/include/api/API.h b/include/api/API.h index 04a65676..f5802428 100644 --- a/include/api/API.h +++ b/include/api/API.h @@ -233,6 +233,12 @@ protected: /// bool saveSettings(const QJsonObject &data); + /// + /// @brief Restore settings object. Requires ADMIN ACCESS + /// @param data The data object + /// + bool restoreSettings(const QJsonObject &data); + /// /// @brief Test if we are authorized to use the interface /// @return The result diff --git a/include/api/JsonAPI.h b/include/api/JsonAPI.h index a68c9d90..738c6fdc 100644 --- a/include/api/JsonAPI.h +++ b/include/api/JsonAPI.h @@ -229,6 +229,12 @@ private: /// void handleConfigSetCommand(const QJsonObject &message, const QString &command, int tan); + /// Handle an incoming JSON RestoreConfig message from handleConfigCommand() + /// + /// @param message the incoming message + /// + void handleConfigRestoreCommand(const QJsonObject &message, const QString &command, int tan); + /// /// Handle an incoming JSON Component State message /// diff --git a/include/flatbufserver/FlatBufferConnection.h b/include/flatbufserver/FlatBufferConnection.h index 1234d7b6..080eccad 100644 --- a/include/flatbufserver/FlatBufferConnection.h +++ b/include/flatbufserver/FlatBufferConnection.h @@ -7,6 +7,7 @@ #include #include #include +#include // hyperion util #include @@ -16,6 +17,8 @@ #include +const int FLATBUFFER_DEFAULT_PORT = 19400; + namespace hyperionnet { struct Reply; @@ -32,10 +35,11 @@ class FlatBufferConnection : public QObject public: /// /// @brief Constructor - /// @param address The address of the Hyperion server (for example "192.168.0.32:19444) + /// @param host The hostname or IP-address of the Hyperion Flatbuffer server (for example "192.168.0.32") + /// @param port The port of the Hyperion Flatpuffer server (default is 19400) /// @param skipReply If true skip reply /// - FlatBufferConnection(const QString& origin, const QString & address, int priority, bool skipReply); + FlatBufferConnection(const QString& origin, const QString& host, int priority, bool skipReply, quint16 port = FLATBUFFER_DEFAULT_PORT); /// /// @brief Destructor diff --git a/include/hyperion/Hyperion.h b/include/hyperion/Hyperion.h index 93730e1f..58c4f920 100644 --- a/include/hyperion/Hyperion.h +++ b/include/hyperion/Hyperion.h @@ -324,6 +324,14 @@ public slots: /// bool saveSettings(const QJsonObject& config, bool correct = false); + /// + /// @brief Restore a complete json config + /// @param config The entire config object + /// @param correct If true will correct json against schema before save + /// @return True on success else false + /// + bool restoreSettings(const QJsonObject& config, bool correct = false); + /// ############ /// COMPONENTREGISTER /// diff --git a/include/hyperion/MessageForwarder.h b/include/hyperion/MessageForwarder.h index be4fd4db..ccce0970 100644 --- a/include/hyperion/MessageForwarder.h +++ b/include/hyperion/MessageForwarder.h @@ -36,8 +36,8 @@ public: MessageForwarder(Hyperion* hyperion); ~MessageForwarder() override; - void addJsonSlave(const QString& slave); - void addFlatbufferSlave(const QString& slave); + void addJsonTarget(const QJsonObject& targetConfig); + void addFlatbufferTarget(const QJsonObject& targetConfig); private slots: /// @@ -61,25 +61,36 @@ private slots: void handlePriorityChanges(quint8 priority); /// - /// @brief Forward message to all json slaves + /// @brief Forward message to all json target hosts /// @param message The JSON message to send /// void forwardJsonMessage(const QJsonObject &message); /// - /// @brief Forward image to all flatbuffer slaves + /// @brief Forward image to all flatbuffer target hosts /// @param image The flatbuffer image to send /// void forwardFlatbufferMessage(const QString& name, const Image &image); /// - /// @brief Forward message to a single json slave + /// @brief Forward message to a single json target host /// @param message The JSON message to send - /// @param socket The TCP-Socket with the connection to the slave + /// @param socket The TCP-Socket with the connection to the target host /// void sendJsonMessage(const QJsonObject &message, QTcpSocket *socket); private: + + struct TargetHost { + QHostAddress host; + quint16 port; + + bool operator == (TargetHost const& a) const + { + return ((host == a.host) && (port == a.port)); + } + }; + /// Hyperion instance Hyperion *_hyperion; @@ -89,11 +100,11 @@ private: /// Muxer instance PriorityMuxer *_muxer; - // JSON connection for forwarding - QStringList _jsonSlaves; + // JSON connections for forwarding + QList _jsonTargets; - /// Proto connection for forwarding - QStringList _flatSlaves; + /// Flatbuffer connection for forwarding + QList _flatbufferTargets; QList _forwardClients; /// Flag if forwarder is enabled diff --git a/include/hyperion/SettingsManager.h b/include/hyperion/SettingsManager.h index cdb22f3c..9d525cf6 100644 --- a/include/hyperion/SettingsManager.h +++ b/include/hyperion/SettingsManager.h @@ -36,6 +36,14 @@ public: /// bool saveSettings(QJsonObject config, bool correct = false); + /// + /// @brief Restore a complete json configuration + /// @param config The entire config object + /// @param correct If true will correct json against schema before save + /// @return True on success else false + /// + bool restoreSettings(QJsonObject config, bool correct = false); + /// /// @brief get a single setting json from configuration /// @param type The settings::type from enum diff --git a/include/utils/NetUtils.h b/include/utils/NetUtils.h index 57294acc..5756185e 100644 --- a/include/utils/NetUtils.h +++ b/include/utils/NetUtils.h @@ -3,15 +3,21 @@ #include #include +#include +#include +#include namespace NetUtils { + + const int MAX_PORT = 65535; + /// /// @brief Check if the port is available for listening /// @param[in/out] port The port to test, will be incremented if port is in use /// @param log The logger of the caller to print /// @return True on success else false /// - static bool portAvailable(quint16& port, Logger* log) + inline bool portAvailable(quint16& port, Logger* log) { const quint16 prevPort = port; QTcpServer server; @@ -29,4 +35,89 @@ namespace NetUtils { return true; } + /// + /// @brief Check if the port is in the valid range + /// @param log The logger of the caller to print/// + /// @param[in] port The port to be tested + /// @param[in] host A hostname/IP-address to make reference to during logging + /// @return True on success else false + /// + inline bool isValidPort(Logger* log, int port, const QString& host) + { + if (port <= 0 || port > MAX_PORT) + { + Error(log, "Invalid port [%d] for host: %s!", port, QSTRING_CSTR(host)); + return false; + } + return true; + } + + /// + /// @brief Get host and port from an host address + /// @param[in] address Hostname or IP-address with or without port (e.g. 192.168.1.100:4711, 2003:e4:c73a:8e00:d5bb:dc3c:50cb:c76e, hyperion.fritz.box) + /// @param[in/out] host The resolved hostname or IP-address + /// @param[in/out] port The resolved port, if available. + /// @return True on success else false + /// + inline bool resolveHostPort(const QString& address, QString& host, quint16& port) + { + QString testUrl; + if (address.at(0) != '[' && address.count(':') > 1) + { + testUrl = QString("http://[%1]").arg(address); + } + else + { + testUrl = QString("http://%1").arg(address); + } + + QUrl url(testUrl); + if (!url.isValid()) + { + return false; + } + + host = url.host(); + if (url.port() != -1) + { + port = url.port(); + } + return true; + } + + /// + /// @brief Check if the port is in the valid range + /// @param log The logger of the caller to print + /// @param[in] address The port to be tested + /// @param[out] hostAddress A hostname to make reference to during logging + /// @return True on success else false + /// + + inline bool resolveHostAddress(Logger* log, const QString& address, QHostAddress& hostAddress) + { + bool isHostAddressOK{ false }; + + if (hostAddress.setAddress(address)) + { + Debug(log, "Successfully parsed %s as an IP-address.", QSTRING_CSTR(hostAddress.toString())); + isHostAddressOK = true; + } + else + { + QHostInfo hostInfo = QHostInfo::fromName(address); + if (hostInfo.error() == QHostInfo::NoError) + { + hostAddress = hostInfo.addresses().first(); + Debug(log, "Successfully resolved IP-address (%s) for hostname (%s).", QSTRING_CSTR(hostAddress.toString()), QSTRING_CSTR(address)); + isHostAddressOK = true; + } + else + { + QString errortext = QString("Failed resolving IP-address for [%1], (%2) %3").arg(address).arg(hostInfo.error()).arg(hostInfo.errorString()); + Error(log, "%s", QSTRING_CSTR(errortext)); + isHostAddressOK = false; + } + } + return isHostAddressOK; + } } diff --git a/libsrc/api/API.cpp b/libsrc/api/API.cpp index b3413318..12d3f23e 100644 --- a/libsrc/api/API.cpp +++ b/libsrc/api/API.cpp @@ -392,6 +392,20 @@ bool API::saveSettings(const QJsonObject &data) return rc; } +bool API::restoreSettings(const QJsonObject &data) +{ + bool rc = true; + if (!_adminAuthorized) + { + rc = false; + } + else + { + QMetaObject::invokeMethod(_hyperion, "restoreSettings", Qt::DirectConnection, Q_RETURN_ARG(bool, rc), Q_ARG(QJsonObject, data), Q_ARG(bool, true)); + } + return rc; +} + bool API::updateHyperionPassword(const QString &password, const QString &newPassword) { if (!_adminAuthorized) diff --git a/libsrc/api/JSONRPC_schema/schema-config.json b/libsrc/api/JSONRPC_schema/schema-config.json index dfe6b219..8a134b54 100644 --- a/libsrc/api/JSONRPC_schema/schema-config.json +++ b/libsrc/api/JSONRPC_schema/schema-config.json @@ -10,7 +10,7 @@ "subcommand": { "type" : "string", "required" : true, - "enum" : ["setconfig","getconfig","getschema","reload"] + "enum" : ["getconfig","getschema","setconfig","restoreconfig","reload"] }, "tan" : { "type" : "integer" diff --git a/libsrc/api/JsonAPI.cpp b/libsrc/api/JsonAPI.cpp index 8b6d6159..7ed4b35f 100644 --- a/libsrc/api/JsonAPI.cpp +++ b/libsrc/api/JsonAPI.cpp @@ -860,6 +860,13 @@ void JsonAPI::handleConfigCommand(const QJsonObject &message, const QString &com { 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) @@ -867,10 +874,10 @@ void JsonAPI::handleConfigCommand(const QJsonObject &message, const QString &com else sendErrorReply("No Authorization", command, tan); } - else if (subcommand == "getconfig") + else if (subcommand == "restoreconfig") { if (_adminAuthorized) - sendSuccessDataReply(QJsonDocument(_hyperion->getQJsonConfig()), full_command, tan); + handleConfigRestoreCommand(message, full_command, tan); else sendErrorReply("No Authorization", command, tan); } @@ -916,6 +923,27 @@ void JsonAPI::handleConfigSetCommand(const QJsonObject &message, const QString & } } +void JsonAPI::handleConfigRestoreCommand(const QJsonObject &message, const QString &command, int tan) +{ + if (message.contains("config")) + { + QJsonObject config = message["config"].toObject(); + if (API::isHyperionEnabled()) + { + if ( API::restoreSettings(config) ) + { + sendSuccessReply(command, tan); + } + else + { + sendErrorReply("Restore settings failed", command, tan); + } + } + else + sendErrorReply("Restoring configuration while Hyperion is disabled isn't possible", command, tan); + } +} + void JsonAPI::handleSchemaGetCommand(const QJsonObject &message, const QString &command, int tan) { // create result diff --git a/libsrc/commandline/ColorsOption.cpp b/libsrc/commandline/ColorsOption.cpp index d7499a51..ab26b585 100644 --- a/libsrc/commandline/ColorsOption.cpp +++ b/libsrc/commandline/ColorsOption.cpp @@ -17,15 +17,18 @@ bool ColorsOption::validate(Parser & parser, QString & value) return true; } - // check if we can create the color by hex RRGGBB getColors - QRegularExpression hexRe("^([0-9A-F]{6})+$", QRegularExpression::CaseInsensitiveOption); - QRegularExpressionMatch match = hexRe.match(value); - if(match.hasMatch()) + // check if we can create the colors by hex RRGGBB getColors + QRegularExpression re("(([A-F0-9]){6})(?=(?:..)*)"); + QRegularExpressionMatchIterator i = re.globalMatch(value); + + while (i.hasNext()) { + QRegularExpressionMatch match = i.next(); + QString captured = match.captured(1); + _colors.push_back(QColor(QString("#%1").arg(captured))); + } + + if (!_colors.isEmpty() && (_colors.size() * 6) == value.length()) { - for(const QString & m : match.capturedTexts()) - { - _colors.push_back(QColor(QString("#%1").arg(m))); - } return true; } diff --git a/libsrc/flatbufserver/FlatBufferConnection.cpp b/libsrc/flatbufserver/FlatBufferConnection.cpp index 6c81bc78..53cb178d 100644 --- a/libsrc/flatbufserver/FlatBufferConnection.cpp +++ b/libsrc/flatbufserver/FlatBufferConnection.cpp @@ -11,33 +11,21 @@ #include "hyperion_reply_generated.h" #include "hyperion_request_generated.h" -FlatBufferConnection::FlatBufferConnection(const QString& origin, const QString & address, int priority, bool skipReply) +FlatBufferConnection::FlatBufferConnection(const QString& origin, const QString& host, int priority, bool skipReply, quint16 port) : _socket() , _origin(origin) , _priority(priority) + , _host(host) + , _port(port) , _prevSocketState(QAbstractSocket::UnconnectedState) , _log(Logger::getInstance("FLATBUFCONN")) , _registered(false) { - QStringList parts = address.split(":"); - if (parts.size() != 2) - { - throw std::runtime_error(QString("FLATBUFCONNECTION ERROR: Unable to parse address (%1)").arg(address).toStdString()); - } - _host = parts[0]; - - bool ok; - _port = parts[1].toUShort(&ok); - if (!ok) - { - throw std::runtime_error(QString("FLATBUFCONNECTION ERROR: Unable to parse the port (%1)").arg(parts[1]).toStdString()); - } - if(!skipReply) connect(&_socket, &QTcpSocket::readyRead, this, &FlatBufferConnection::readData, Qt::UniqueConnection); // init connect - Info(_log, "Connecting to Hyperion: %s:%d", _host.toStdString().c_str(), _port); + Info(_log, "Connecting to Hyperion: %s:%u", QSTRING_CSTR(_host), _port); connectToHost(); // start the connection timer @@ -167,13 +155,13 @@ void FlatBufferConnection::sendMessage(const uint8_t* buffer, uint32_t size) switch (_socket.state() ) { case QAbstractSocket::UnconnectedState: - Info(_log, "No connection to Hyperion: %s:%d", _host.toStdString().c_str(), _port); + Info(_log, "No connection to Hyperion: %s:%u", QSTRING_CSTR(_host), _port); break; case QAbstractSocket::ConnectedState: - Info(_log, "Connected to Hyperion: %s:%d", _host.toStdString().c_str(), _port); + Info(_log, "Connected to Hyperion: %s:%u", QSTRING_CSTR(_host), _port); break; default: - Debug(_log, "Connecting to Hyperion: %s:%d", _host.toStdString().c_str(), _port); + Debug(_log, "Connecting to Hyperion: %s:%u", QSTRING_CSTR(_host), _port); break; } _prevSocketState = _socket.state(); diff --git a/libsrc/hyperion/Hyperion.cpp b/libsrc/hyperion/Hyperion.cpp index 61ce9424..077798ea 100644 --- a/libsrc/hyperion/Hyperion.cpp +++ b/libsrc/hyperion/Hyperion.cpp @@ -279,6 +279,11 @@ bool Hyperion::saveSettings(const QJsonObject& config, bool correct) return _settingsManager->saveSettings(config, correct); } +bool Hyperion::restoreSettings(const QJsonObject& config, bool correct) +{ + return _settingsManager->restoreSettings(config, correct); +} + int Hyperion::getLatchTime() const { return _ledDeviceWrapper->getLatchTime(); diff --git a/libsrc/hyperion/MessageForwarder.cpp b/libsrc/hyperion/MessageForwarder.cpp index 5dc2385c..591eacba 100644 --- a/libsrc/hyperion/MessageForwarder.cpp +++ b/libsrc/hyperion/MessageForwarder.cpp @@ -9,16 +9,18 @@ // utils includes #include +#include // qt includes #include #include +#include +#include #include -MessageForwarder::MessageForwarder(Hyperion *hyperion) - : QObject() - , _hyperion(hyperion) +MessageForwarder::MessageForwarder(Hyperion* hyperion) + : _hyperion(hyperion) , _log(Logger::getInstance("NETFORWARDER")) , _muxer(_hyperion->getMuxerInstance()) , _forwarder_enabled(true) @@ -40,58 +42,74 @@ MessageForwarder::MessageForwarder(Hyperion *hyperion) MessageForwarder::~MessageForwarder() { while (!_forwardClients.isEmpty()) + { delete _forwardClients.takeFirst(); + } } -void MessageForwarder::handleSettingsUpdate(settings::type type, const QJsonDocument &config) +void MessageForwarder::handleSettingsUpdate(settings::type type, const QJsonDocument& config) { - if(type == settings::NETFORWARD) + if (type == settings::NETFORWARD) { // clear the current targets - _jsonSlaves.clear(); - _flatSlaves.clear(); + _jsonTargets.clear(); + _flatbufferTargets.clear(); while (!_forwardClients.isEmpty()) + { delete _forwardClients.takeFirst(); + } // build new one - const QJsonObject &obj = config.object(); - if ( !obj["json"].isNull() ) + const QJsonObject& obj = config.object(); + if (!obj["json"].isNull()) { - const QJsonArray & addr = obj["json"].toArray(); + const QJsonArray& addr = obj["json"].toArray(); for (const auto& entry : addr) { - addJsonSlave(entry.toString()); + addJsonTarget(entry.toObject()); } } - if ( !obj["flat"].isNull() ) + if (!obj["flat"].isNull()) { - const QJsonArray & addr = obj["flat"].toArray(); + const QJsonArray& addr = obj["flat"].toArray(); for (const auto& entry : addr) { - addFlatbufferSlave(entry.toString()); + addFlatbufferTarget(entry.toObject()); } } - if (!_jsonSlaves.isEmpty() && obj["enable"].toBool() && _forwarder_enabled) + bool isForwarderEnabledinSettings = obj["enable"].toBool(false); + + if (!_jsonTargets.isEmpty() && isForwarderEnabledinSettings && _forwarder_enabled) { - InfoIf(obj["enable"].toBool(true), _log, "Forward now to json targets '%s'", QSTRING_CSTR(_jsonSlaves.join(", "))); + for (const auto& targetHost : qAsConst(_jsonTargets)) + { + InfoIf(isForwarderEnabledinSettings, _log, "Forwarding now to JSON-target host: %s port: %u", QSTRING_CSTR(targetHost.host.toString()), targetHost.port); + } + connect(_hyperion, &Hyperion::forwardJsonMessage, this, &MessageForwarder::forwardJsonMessage, Qt::UniqueConnection); - } else if (_jsonSlaves.isEmpty() || ! obj["enable"].toBool() || !_forwarder_enabled) - disconnect(_hyperion, &Hyperion::forwardJsonMessage, 0, 0); - - if (!_flatSlaves.isEmpty() && obj["enable"].toBool() && _forwarder_enabled) - { - InfoIf(obj["enable"].toBool(true), _log, "Forward now to flatbuffer targets '%s'", QSTRING_CSTR(_flatSlaves.join(", "))); } - else if ( _flatSlaves.isEmpty() || ! obj["enable"].toBool() || !_forwarder_enabled) + else if (_jsonTargets.isEmpty() || !isForwarderEnabledinSettings || !_forwarder_enabled) { - disconnect(_hyperion, &Hyperion::forwardSystemProtoMessage, 0, 0); - disconnect(_hyperion, &Hyperion::forwardV4lProtoMessage, 0, 0); + disconnect(_hyperion, &Hyperion::forwardJsonMessage, nullptr, nullptr); + } + + if (!_flatbufferTargets.isEmpty() && isForwarderEnabledinSettings && _forwarder_enabled) + { + for (const auto& targetHost : qAsConst(_flatbufferTargets)) + { + InfoIf(isForwarderEnabledinSettings, _log, "Forwarding now to Flatbuffer-target host: %s port: %u", QSTRING_CSTR(targetHost.host.toString()), targetHost.port); + } + } + else if (_flatbufferTargets.isEmpty() || !isForwarderEnabledinSettings || !_forwarder_enabled) + { + disconnect(_hyperion, &Hyperion::forwardSystemProtoMessage, nullptr, nullptr); + disconnect(_hyperion, &Hyperion::forwardV4lProtoMessage, nullptr, nullptr); } // update comp state - _hyperion->setNewComponentState(hyperion::COMP_FORWARDER, obj["enable"].toBool(true)); + _hyperion->setNewComponentState(hyperion::COMP_FORWARDER, isForwarderEnabledinSettings); } } @@ -111,146 +129,161 @@ void MessageForwarder::handlePriorityChanges(quint8 priority) const QJsonObject obj = _hyperion->getSetting(settings::NETFORWARD).object(); if (priority != 0 && _forwarder_enabled && obj["enable"].toBool()) { - //_flatSlaves.clear(); - //while (!_forwardClients.isEmpty()) - // delete _forwardClients.takeFirst(); - hyperion::Components activeCompId = _hyperion->getPriorityInfo(priority).componentId; if (activeCompId == hyperion::COMP_GRABBER || activeCompId == hyperion::COMP_V4L) { - if ( !obj["flat"].isNull() ) + if (!obj["flat"].isNull()) { - const QJsonArray & addr = obj["flat"].toArray(); + const QJsonArray& addr = obj["flat"].toArray(); for (const auto& entry : addr) { - addFlatbufferSlave(entry.toString()); + addFlatbufferTarget(entry.toObject()); } } - switch(activeCompId) + switch (activeCompId) { - case hyperion::COMP_GRABBER: - { - disconnect(_hyperion, &Hyperion::forwardV4lProtoMessage, 0, 0); - connect(_hyperion, &Hyperion::forwardSystemProtoMessage, this, &MessageForwarder::forwardFlatbufferMessage, Qt::UniqueConnection); - } - break; - case hyperion::COMP_V4L: - { - disconnect(_hyperion, &Hyperion::forwardSystemProtoMessage, 0, 0); - connect(_hyperion, &Hyperion::forwardV4lProtoMessage, this, &MessageForwarder::forwardFlatbufferMessage, Qt::UniqueConnection); - } - break; - default: - { - disconnect(_hyperion, &Hyperion::forwardSystemProtoMessage, 0, 0); - disconnect(_hyperion, &Hyperion::forwardV4lProtoMessage, 0, 0); - } + case hyperion::COMP_GRABBER: + { + disconnect(_hyperion, &Hyperion::forwardV4lProtoMessage, nullptr, nullptr); + connect(_hyperion, &Hyperion::forwardSystemProtoMessage, this, &MessageForwarder::forwardFlatbufferMessage, Qt::UniqueConnection); + } + break; + case hyperion::COMP_V4L: + { + disconnect(_hyperion, &Hyperion::forwardSystemProtoMessage, nullptr, nullptr); + connect(_hyperion, &Hyperion::forwardV4lProtoMessage, this, &MessageForwarder::forwardFlatbufferMessage, Qt::UniqueConnection); + } + break; + default: + { + disconnect(_hyperion, &Hyperion::forwardSystemProtoMessage, nullptr, nullptr); + disconnect(_hyperion, &Hyperion::forwardV4lProtoMessage, nullptr, nullptr); + } } } else { - disconnect(_hyperion, &Hyperion::forwardSystemProtoMessage, 0, 0); - disconnect(_hyperion, &Hyperion::forwardV4lProtoMessage, 0, 0); + disconnect(_hyperion, &Hyperion::forwardSystemProtoMessage, nullptr, nullptr); + disconnect(_hyperion, &Hyperion::forwardV4lProtoMessage, nullptr, nullptr); } } } -void MessageForwarder::addJsonSlave(const QString& slave) +void MessageForwarder::addJsonTarget(const QJsonObject& targetConfig) { - QStringList parts = slave.split(":"); - if (parts.size() != 2) - { - Error(_log, "Unable to parse address (%s)",QSTRING_CSTR(slave)); - return; - } + TargetHost targetHost; - bool ok; - parts[1].toUShort(&ok); - if (!ok) + QString config_host = targetConfig["host"].toString(); + if (NetUtils::resolveHostAddress(_log, config_host, targetHost.host)) { - Error(_log, "Unable to parse port number (%s)",QSTRING_CSTR(parts[1])); - return; - } + int config_port = targetConfig["port"].toInt(); + if (NetUtils::isValidPort(_log, config_port, config_host)) + { + targetHost.port = static_cast(config_port); - // verify loop with jsonserver - const QJsonObject &obj = _hyperion->getSetting(settings::JSONSERVER).object(); - if(QHostAddress(parts[0]) == QHostAddress::LocalHost && parts[1].toInt() == obj["port"].toInt()) - { - Error(_log, "Loop between JsonServer and Forwarder! (%s)",QSTRING_CSTR(slave)); - return; - } + // verify loop with JSON-server + const QJsonObject& obj = _hyperion->getSetting(settings::JSONSERVER).object(); + if ((QNetworkInterface::allAddresses().indexOf(targetHost.host) != -1) && targetHost.port == static_cast(obj["port"].toInt())) + { + Error(_log, "Loop between JSON-Server and Forwarder! Configuration for host: %s, port: %d is ignored.", QSTRING_CSTR(config_host), config_port); + } + else + { + if (_forwarder_enabled) + { + if (_jsonTargets.indexOf(targetHost) == -1) + { + Info(_log, "JSON-Forwarder settings: Adding target host: %s port: %u", QSTRING_CSTR(targetHost.host.toString()), targetHost.port); + _jsonTargets << targetHost; + } + else + { + Warning(_log, "JSON Forwarder settings: Duplicate target host configuration! Configuration for host: %s, port: %d is ignored.", QSTRING_CSTR(targetHost.host.toString()), targetHost.port); + } + } - if (_forwarder_enabled && !_jsonSlaves.contains(slave)) - _jsonSlaves << slave; -} - -void MessageForwarder::addFlatbufferSlave(const QString& slave) -{ - QStringList parts = slave.split(":"); - if (parts.size() != 2) - { - Error(_log, "Unable to parse address (%s)",QSTRING_CSTR(slave)); - return; - } - - bool ok; - parts[1].toUShort(&ok); - if (!ok) - { - Error(_log, "Unable to parse port number (%s)",QSTRING_CSTR(parts[1])); - return; - } - - // verify loop with flatbufserver - const QJsonObject &obj = _hyperion->getSetting(settings::FLATBUFSERVER).object(); - if(QHostAddress(parts[0]) == QHostAddress::LocalHost && parts[1].toInt() == obj["port"].toInt()) - { - Error(_log, "Loop between Flatbuffer Server and Forwarder! (%s)",QSTRING_CSTR(slave)); - return; - } - - if (_forwarder_enabled && !_flatSlaves.contains(slave)) - { - _flatSlaves << slave; - FlatBufferConnection* flatbuf = new FlatBufferConnection("Forwarder", slave.toLocal8Bit().constData(), _priority, false); - _forwardClients << flatbuf; + } + } } } -void MessageForwarder::forwardJsonMessage(const QJsonObject &message) +void MessageForwarder::addFlatbufferTarget(const QJsonObject& targetConfig) +{ + TargetHost targetHost; + + QString config_host = targetConfig["host"].toString(); + if (NetUtils::resolveHostAddress(_log, config_host, targetHost.host)) + { + int config_port = targetConfig["port"].toInt(); + if (NetUtils::isValidPort(_log, config_port, config_host)) + { + targetHost.port = static_cast(config_port); + + // verify loop with Flatbuffer-server + const QJsonObject& obj = _hyperion->getSetting(settings::FLATBUFSERVER).object(); + if ((QNetworkInterface::allAddresses().indexOf(targetHost.host) != -1) && targetHost.port == static_cast(obj["port"].toInt())) + { + Error(_log, "Loop between Flatbuffer-Server and Forwarder! Configuration for host: %s, port: %d is ignored.", QSTRING_CSTR(config_host), config_port); + } + else + { + if (_forwarder_enabled) + { + if (_flatbufferTargets.indexOf(targetHost) == -1) + { + Info(_log, "Flatbuffer-Forwarder settings: Adding target host: %s port: %u", QSTRING_CSTR(targetHost.host.toString()), targetHost.port); + _flatbufferTargets << targetHost; + + FlatBufferConnection* flatbuf = new FlatBufferConnection("Forwarder", targetHost.host.toString(), _priority, false, targetHost.port); + _forwardClients << flatbuf; + } + else + { + Warning(_log, "Flatbuffer Forwarder settings: Duplicate target host configuration! Configuration for host: %s, port: %d is ignored.", QSTRING_CSTR(targetHost.host.toString()), targetHost.port); + } + } + } + } + } +} + +void MessageForwarder::forwardJsonMessage(const QJsonObject& message) { if (_forwarder_enabled) { QTcpSocket client; - for (int i=0; i<_jsonSlaves.size(); i++) + for (const auto& targetHost : qAsConst(_jsonTargets)) { - QStringList parts = _jsonSlaves.at(i).split(":"); - client.connectToHost(QHostAddress(parts[0]), parts[1].toUShort()); - if ( client.waitForConnected(500) ) + client.connectToHost(targetHost.host, targetHost.port); + if (client.waitForConnected(500)) { - sendJsonMessage(message,&client); + sendJsonMessage(message, &client); client.close(); } } } } -void MessageForwarder::forwardFlatbufferMessage(const QString& name, const Image &image) +void MessageForwarder::forwardFlatbufferMessage(const QString& /*name*/, const Image& image) { if (_forwarder_enabled) { - for (int i=0; i < _forwardClients.size(); i++) + for (int i = 0; i < _forwardClients.size(); i++) + { _forwardClients.at(i)->setImage(image); + } } } -void MessageForwarder::sendJsonMessage(const QJsonObject &message, QTcpSocket *socket) +void MessageForwarder::sendJsonMessage(const QJsonObject& message, QTcpSocket* socket) { // for hyperion classic compatibility QJsonObject jsonMessage = message; if (jsonMessage.contains("tan") && jsonMessage["tan"].isNull()) + { jsonMessage["tan"] = 100; + } // serialize message QJsonDocument writer(jsonMessage); @@ -280,7 +313,7 @@ void MessageForwarder::sendJsonMessage(const QJsonObject &message, QTcpSocket *s // parse reply data QJsonParseError error; - QJsonDocument reply = QJsonDocument::fromJson(serializedReply ,&error); + QJsonDocument::fromJson(serializedReply, &error); if (error.error != QJsonParseError::NoError) { @@ -288,3 +321,4 @@ void MessageForwarder::sendJsonMessage(const QJsonObject &message, QTcpSocket *s return; } } + diff --git a/libsrc/hyperion/SettingsManager.cpp b/libsrc/hyperion/SettingsManager.cpp index c3540121..83e2e673 100644 --- a/libsrc/hyperion/SettingsManager.cpp +++ b/libsrc/hyperion/SettingsManager.cpp @@ -3,6 +3,8 @@ // util #include +#include + #include #include "HyperionConfig.h" @@ -14,6 +16,7 @@ #include #include + using namespace semver; // Constants @@ -25,23 +28,23 @@ QJsonObject SettingsManager::schemaJson; SettingsManager::SettingsManager(quint8 instance, QObject* parent, bool readonlyMode) : QObject(parent) - , _log(Logger::getInstance("SETTINGSMGR")) - , _instance(instance) - , _sTable(new SettingsTable(instance, this)) - , _configVersion(DEFAULT_VERSION) - , _previousVersion(DEFAULT_VERSION) - , _readonlyMode(readonlyMode) + , _log(Logger::getInstance("SETTINGSMGR")) + , _instance(instance) + , _sTable(new SettingsTable(instance, this)) + , _configVersion(DEFAULT_VERSION) + , _previousVersion(DEFAULT_VERSION) + , _readonlyMode(readonlyMode) { _sTable->setReadonlyMode(_readonlyMode); // get schema - if(schemaJson.isEmpty()) + if (schemaJson.isEmpty()) { Q_INIT_RESOURCE(resource); try { schemaJson = QJsonFactory::readSchema(":/hyperion-schema"); } - catch(const std::runtime_error& error) + catch (const std::runtime_error& error) { throw std::runtime_error(error.what()); } @@ -49,7 +52,7 @@ SettingsManager::SettingsManager(quint8 instance, QObject* parent, bool readonly // get default config QJsonObject defaultConfig; - if(!JsonUtils::readFile(":/hyperion_default.config", defaultConfig, _log)) + if (!JsonUtils::readFile(":/hyperion_default.config", defaultConfig, _log)) { throw std::runtime_error("Failed to read default config"); } @@ -57,45 +60,51 @@ SettingsManager::SettingsManager(quint8 instance, QObject* parent, bool readonly // transform json to string lists QStringList keyList = defaultConfig.keys(); QStringList defValueList; - for(const auto & key : keyList) + for (const auto& key : qAsConst(keyList)) { - if(defaultConfig[key].isObject()) + if (defaultConfig[key].isObject()) { defValueList << QString(QJsonDocument(defaultConfig[key].toObject()).toJson(QJsonDocument::Compact)); } - else if(defaultConfig[key].isArray()) + else if (defaultConfig[key].isArray()) { defValueList << QString(QJsonDocument(defaultConfig[key].toArray()).toJson(QJsonDocument::Compact)); } } // fill database with default data if required - for(const auto & key : keyList) + for (const auto& key : qAsConst(keyList)) { QString val = defValueList.takeFirst(); // prevent overwrite - if(!_sTable->recordExist(key)) - _sTable->createSettingsRecord(key,val); + if (!_sTable->recordExist(key)) + { + _sTable->createSettingsRecord(key, val); + } } // need to validate all data in database construct the entire data object // TODO refactor schemaChecker to accept QJsonArray in validate(); QJsonDocument container? To validate them per entry... QJsonObject dbConfig; - for(const auto & key : keyList) + for (const auto& key : qAsConst(keyList)) { QJsonDocument doc = _sTable->getSettingsRecord(key); - if(doc.isArray()) + if (doc.isArray()) + { dbConfig[key] = doc.array(); + } else + { dbConfig[key] = doc.object(); + } } //Check, if database requires migration bool isNewRelease = false; // Use instance independent SettingsManager to track migration status - if ( instance == GLOABL_INSTANCE_ID) + if (_instance == GLOABL_INSTANCE_ID) { - if ( resolveConfigVersion(dbConfig) ) + if (resolveConfigVersion(dbConfig)) { QJsonObject newGeneralConfig = dbConfig["general"].toObject(); @@ -107,16 +116,16 @@ SettingsManager::SettingsManager(quint8 instance, QObject* parent, bool readonly exit(1); } - if ( _configVersion > BUILD_VERSION ) + if (_configVersion > BUILD_VERSION) { Error(_log, "Database version [%s] is greater than current Hyperion version [%s]", _configVersion.getVersion().c_str(), BUILD_VERSION.getVersion().c_str()); // TODO: Remove version checking and Settingsmanager from components' constructor to be able to stop hyperion. } else { - if ( _previousVersion < BUILD_VERSION ) + if (_previousVersion < BUILD_VERSION) { - if ( _configVersion == BUILD_VERSION ) + if (_configVersion == BUILD_VERSION) { newGeneralConfig["previousVersion"] = BUILD_VERSION.getVersion().c_str(); dbConfig["general"] = newGeneralConfig; @@ -139,7 +148,7 @@ SettingsManager::SettingsManager(quint8 instance, QObject* parent, bool readonly // possible data upgrade steps to prevent data loss bool migrated = handleConfigUpgrade(dbConfig); - if ( isNewRelease || migrated ) + if (isNewRelease || migrated) { saveSettings(dbConfig, true); } @@ -147,28 +156,34 @@ SettingsManager::SettingsManager(quint8 instance, QObject* parent, bool readonly // validate full dbconfig against schema, on error we need to rewrite entire table QJsonSchemaChecker schemaChecker; schemaChecker.setSchema(schemaJson); - QPair valid = schemaChecker.validate(dbConfig); + QPair valid = schemaChecker.validate(dbConfig); // check if our main schema syntax is IO if (!valid.second) { - for (auto & schemaError : schemaChecker.getMessages()) + for (auto& schemaError : schemaChecker.getMessages()) + { Error(_log, "Schema Syntax Error: %s", QSTRING_CSTR(schemaError)); + } throw std::runtime_error("The config schema has invalid syntax. This should never happen! Go fix it!"); } if (!valid.first) { - Info(_log,"Table upgrade required..."); + Info(_log, "Table upgrade required..."); dbConfig = schemaChecker.getAutoCorrectedConfig(dbConfig); - for (auto & schemaError : schemaChecker.getMessages()) + for (auto& schemaError : schemaChecker.getMessages()) + { Warning(_log, "Config Fix: %s", QSTRING_CSTR(schemaError)); + } - saveSettings(dbConfig,true); + saveSettings(dbConfig, true); } else + { _qconfig = dbConfig; + } - Debug(_log,"Settings database initialized"); + Debug(_log, "Settings database initialized"); } QJsonDocument SettingsManager::getSetting(settings::type type) const @@ -179,11 +194,11 @@ QJsonDocument SettingsManager::getSetting(settings::type type) const QJsonObject SettingsManager::getSettings() const { QJsonObject config; - for(const auto & key : _qconfig.keys()) + for (const auto& key : _qconfig.keys()) { //Read all records from database to ensure that global settings are read across instances QJsonDocument doc = _sTable->getSettingsRecord(key); - if(doc.isArray()) + if (doc.isArray()) { config.insert(key, doc.array()); } @@ -195,26 +210,32 @@ QJsonObject SettingsManager::getSettings() const return config; } -bool SettingsManager::saveSettings(QJsonObject config, bool correct) +bool SettingsManager::restoreSettings(QJsonObject config, bool correct) { // optional data upgrades e.g. imported legacy/older configs - // handleConfigUpgrade(config); + handleConfigUpgrade(config); + return saveSettings(config, correct); +} +bool SettingsManager::saveSettings(QJsonObject config, bool correct) +{ // we need to validate data against schema QJsonSchemaChecker schemaChecker; schemaChecker.setSchema(schemaJson); if (!schemaChecker.validate(config).first) { - if(!correct) + if (!correct) { - Error(_log,"Failed to save configuration, errors during validation"); + Error(_log, "Failed to save configuration, errors during validation"); return false; } - Warning(_log,"Fixing json data!"); + Warning(_log, "Fixing json data!"); config = schemaChecker.getAutoCorrectedConfig(config); - for (const auto & schemaError : schemaChecker.getMessages()) + for (const auto& schemaError : schemaChecker.getMessages()) + { Warning(_log, "Config Fix: %s", QSTRING_CSTR(schemaError)); + } } // store the new config @@ -223,26 +244,26 @@ bool SettingsManager::saveSettings(QJsonObject config, bool correct) // extract keys and data QStringList keyList = config.keys(); QStringList newValueList; - for(const auto & key : keyList) + for (const auto& key : qAsConst(keyList)) { - if(config[key].isObject()) + if (config[key].isObject()) { newValueList << QString(QJsonDocument(config[key].toObject()).toJson(QJsonDocument::Compact)); } - else if(config[key].isArray()) + else if (config[key].isArray()) { newValueList << QString(QJsonDocument(config[key].toArray()).toJson(QJsonDocument::Compact)); } } - int rc = true; + bool rc = true; // compare database data with new data to emit/save changes accordingly - for(const auto & key : keyList) + for (const auto& key : qAsConst(keyList)) { QString data = newValueList.takeFirst(); - if(_sTable->getSettingsRecordString(key) != data) + if (_sTable->getSettingsRecordString(key) != data) { - if ( ! _sTable->createSettingsRecord(key, data) ) + if (!_sTable->createSettingsRecord(key, data)) { rc = false; } @@ -255,7 +276,7 @@ bool SettingsManager::saveSettings(QJsonObject config, bool correct) return rc; } -inline QString fixVersion (const QString& version) +inline QString fixVersion(const QString& version) { QString newVersion; //Try fixing version number, remove dot separated pre-release identifiers not supported @@ -279,12 +300,12 @@ bool SettingsManager::resolveConfigVersion(QJsonObject& config) QString configVersion = generalConfig["configVersion"].toString(); QString previousVersion = generalConfig["previousVersion"].toString(); - if ( !configVersion.isEmpty() ) + if (!configVersion.isEmpty()) { isValid = _configVersion.setVersion(configVersion.toStdString()); if (!isValid) { - isValid = _configVersion.setVersion( fixVersion(configVersion).toStdString() ); + isValid = _configVersion.setVersion(fixVersion(configVersion).toStdString()); if (isValid) { Info(_log, "Invalid config version [%s] fixed. Updated to [%s]", QSTRING_CSTR(configVersion), _configVersion.getVersion().c_str()); @@ -293,16 +314,15 @@ bool SettingsManager::resolveConfigVersion(QJsonObject& config) } else { - _configVersion.setVersion(DEFAULT_VERSION); isValid = true; } - if ( !previousVersion.isEmpty() && isValid ) + if (!previousVersion.isEmpty() && isValid) { isValid = _previousVersion.setVersion(previousVersion.toStdString()); if (!isValid) { - isValid = _previousVersion.setVersion( fixVersion(previousVersion).toStdString() ); + isValid = _previousVersion.setVersion(fixVersion(previousVersion).toStdString()); if (isValid) { Info(_log, "Invalid previous version [%s] fixed. Updated to [%s]", QSTRING_CSTR(previousVersion), _previousVersion.getVersion().c_str()); @@ -323,7 +343,7 @@ bool SettingsManager::handleConfigUpgrade(QJsonObject& config) bool migrated = false; //Only migrate, if valid versions are available - if ( !resolveConfigVersion(config) ) + if (!resolveConfigVersion(config)) { Warning(_log, "Invalid version information found in configuration. No database migration executed."); } @@ -333,26 +353,26 @@ bool SettingsManager::handleConfigUpgrade(QJsonObject& config) if (_previousVersion < _configVersion) { //Migration steps for versions <= alpha 9 - semver::version targetVersion {"2.0.0-alpha.9"}; - if (_previousVersion <= targetVersion ) + semver::version targetVersion{ "2.0.0-alpha.9" }; + if (_previousVersion <= targetVersion) { - Info(_log, "Instance [%u]: Migrate LED Layout from current version [%s] to version [%s] or later", _instance, _previousVersion.getVersion().c_str(), targetVersion.getVersion().c_str()); + Info(_log, "Instance [%u]: Migrate from version [%s] to version [%s] or later", _instance, _previousVersion.getVersion().c_str(), targetVersion.getVersion().c_str()); // LED LAYOUT UPGRADE // from { hscan: { minimum: 0.2, maximum: 0.3 }, vscan: { minimum: 0.2, maximum: 0.3 } } // from { h: { min: 0.2, max: 0.3 }, v: { min: 0.2, max: 0.3 } } // to { hmin: 0.2, hmax: 0.3, vmin: 0.2, vmax: 0.3} - if(config.contains("leds")) + if (config.contains("leds")) { const QJsonArray ledarr = config["leds"].toArray(); const QJsonObject led = ledarr[0].toObject(); - if(led.contains("hscan") || led.contains("h")) + if (led.contains("hscan") || led.contains("h")) { const bool whscan = led.contains("hscan"); QJsonArray newLedarr; - for(const auto & entry : ledarr) + for (const auto& entry : ledarr) { const QJsonObject led = entry.toObject(); QJsonObject hscan; @@ -363,7 +383,7 @@ bool SettingsManager::handleConfigUpgrade(QJsonObject& config) QJsonValue vmax; QJsonObject nL; - if(whscan) + if (whscan) { hscan = led["hscan"].toObject(); vscan = led["vscan"].toObject(); @@ -391,27 +411,27 @@ bool SettingsManager::handleConfigUpgrade(QJsonObject& config) // replace config["leds"] = newLedarr; migrated = true; - Info(_log,"Instance [%u]: LED Layout migrated", _instance); + Info(_log, "Instance [%u]: LED Layout migrated", _instance); } } - if(config.contains("ledConfig")) + if (config.contains("ledConfig")) { QJsonObject oldLedConfig = config["ledConfig"].toObject(); - if ( !oldLedConfig.contains("classic")) + if (!oldLedConfig.contains("classic")) { QJsonObject newLedConfig; - newLedConfig.insert("classic", oldLedConfig ); - QJsonObject defaultMatrixConfig {{"ledshoriz", 1} + newLedConfig.insert("classic", oldLedConfig); + QJsonObject defaultMatrixConfig{ {"ledshoriz", 1} ,{"ledsvert", 1} ,{"cabling","snake"} ,{"start","top-left"} }; - newLedConfig.insert("matrix", defaultMatrixConfig ); + newLedConfig.insert("matrix", defaultMatrixConfig); config["ledConfig"] = newLedConfig; migrated = true; - Info(_log,"Instance [%u]: LED-Config migrated", _instance); + Info(_log, "Instance [%u]: LED-Config migrated", _instance); } } @@ -429,7 +449,7 @@ bool SettingsManager::handleConfigUpgrade(QJsonObject& config) const QJsonArray ledarr = config["leds"].toArray(); int layoutLedCount = ledarr.size(); - if (hwLedcount < layoutLedCount ) + if (hwLedcount < layoutLedCount) { Warning(_log, "Instance [%u]: HwLedCount/Layout mismatch! Setting Hardware LED count to number of LEDs configured via layout", _instance); hwLedcount = layoutLedCount; @@ -442,7 +462,7 @@ bool SettingsManager::handleConfigUpgrade(QJsonObject& config) if (newDeviceConfig.contains("type")) { QString type = newDeviceConfig["type"].toString(); - if (type == "atmoorb" || type == "fadecandy" || type == "philipshue" ) + if (type == "atmoorb" || type == "fadecandy" || type == "philipshue") { if (newDeviceConfig.contains("output")) { @@ -520,8 +540,150 @@ bool SettingsManager::handleConfigUpgrade(QJsonObject& config) Debug(_log, "Framegrabber records migrated"); } } + + //Migration steps for versions <= 2.0.12 + _previousVersion = targetVersion; + targetVersion.setVersion("2.0.12"); + if (_previousVersion <= targetVersion) + { + Info(_log, "Instance [%u]: Migrate from version [%s] to version [%s] or later", _instance, _previousVersion.getVersion().c_str(), targetVersion.getVersion().c_str()); + + // Have Hostname/IP-address separate from port for LED-Devices + if (config.contains("device")) + { + QJsonObject newDeviceConfig = config["device"].toObject(); + + if (newDeviceConfig.contains("host")) + { + QString oldHost = newDeviceConfig["host"].toString(); + + // Resolve hostname and port + QStringList addressparts = QStringUtils::split(oldHost, ":", QStringUtils::SplitBehavior::SkipEmptyParts); + + newDeviceConfig["host"] = addressparts[0]; + + if (addressparts.size() > 1) + { + if (!newDeviceConfig.contains("port")) + { + newDeviceConfig["port"] = addressparts[1].toInt(); + } + migrated = true; + } + + if (migrated) + { + config["device"] = newDeviceConfig; + Debug(_log, "LED-Device records migrated"); + } + } + } + + // Have Hostname/IP-address separate from port for Forwarder + if (config.contains("forwarder")) + { + QJsonObject newForwarderConfig = config["forwarder"].toObject(); + + QJsonArray json; + if (newForwarderConfig.contains("json")) + { + QJsonArray oldJson = newForwarderConfig["json"].toArray(); + QJsonObject newJsonConfig; + + for (const QJsonValue& value : qAsConst(oldJson)) + { + if (value.isString()) + { + QString oldHost = value.toString(); + // Resolve hostname and port + QStringList addressparts = QStringUtils::split(oldHost, ":", QStringUtils::SplitBehavior::SkipEmptyParts); + QString host = addressparts[0]; + + if (host != "127.0.0.1") + { + newJsonConfig["host"] = host; + + if (addressparts.size() > 1) + { + newJsonConfig["port"] = addressparts[1].toInt(); + } + else + { + newJsonConfig["port"] = 19444; + } + json.append(newJsonConfig); + migrated = true; + } + } + } + + if (!json.isEmpty()) + { + newForwarderConfig["json"] = json; + } + else + { + newForwarderConfig.remove("json"); + } + } + + QJsonArray flatbuffer; + if (newForwarderConfig.contains("flat")) + { + QJsonArray oldFlatbuffer = newForwarderConfig["flat"].toArray(); + QJsonObject newFlattbufferConfig; + + for (const QJsonValue& value : qAsConst(oldFlatbuffer)) + { + if (value.isString()) + { + QString oldHost = value.toString(); + // Resolve hostname and port + QStringList addressparts = QStringUtils::split(oldHost, ":", QStringUtils::SplitBehavior::SkipEmptyParts); + QString host = addressparts[0]; + + if (host != "127.0.0.1") + { + newFlattbufferConfig["host"] = host; + + if (addressparts.size() > 1) + { + newFlattbufferConfig["port"] = addressparts[1].toInt(); + } + else + { + newFlattbufferConfig["port"] = 19400; + } + + flatbuffer.append(newFlattbufferConfig); + migrated = true; + } + } + + if (!flatbuffer.isEmpty()) + { + newForwarderConfig["flat"] = flatbuffer; + } + else + { + newForwarderConfig.remove("flat"); + } + } + } + + if (json.isEmpty() && flatbuffer.isEmpty()) + { + newForwarderConfig["enable"] = false; + } + + if (migrated) + { + config["forwarder"] = newForwarderConfig; + Debug(_log, "Forwarder records migrated"); + } + } + } } } - return migrated; } diff --git a/libsrc/hyperion/schema/schema-forwarder.json b/libsrc/hyperion/schema/schema-forwarder.json index bd33e7bc..6dc06499 100644 --- a/libsrc/hyperion/schema/schema-forwarder.json +++ b/libsrc/hyperion/schema/schema-forwarder.json @@ -2,40 +2,76 @@ "type" : "object", "title" : "edt_conf_fw_heading_title", "required" : true, - "properties" : - { - "enable" : - { - "type" : "boolean", - "title" : "edt_conf_general_enable_title", - "required" : true, - "default" : false, - "propertyOrder" : 1 + "properties": { + "enable": { + "type": "boolean", + "title": "edt_conf_general_enable_title", + "required": true, + "default": false, + "propertyOrder": 1 }, - "json" : - { - "type" : "array", - "title" : "edt_conf_fw_json_title", - "required" : true, - "default" : ["127.0.0.1:19446"], - "items" : { - "type": "string", - "title" : "edt_conf_fw_json_itemtitle" - }, - "propertyOrder" : 2 + "json": { + "type": "array", + "title": "edt_conf_fw_json_title", + "propertyOrder": 2, + "uniqueItems": true, + "items": { + "type": "object", + "title": "edt_conf_fw_json_itemtitle", + "required": true, + "properties": { + "host": { + "type": "string", + "format": "hostname_or_ip", + "minLength": 7, + "title": "edt_dev_spec_targetIpHost_title", + "required": true, + "propertyOrder": 1 + }, + "port": { + "type": "integer", + "minimum": 1, + "maximum": 65535, + "default": 19444, + "title": "edt_dev_spec_port_title", + "required": true, + "access": "expert", + "propertyOrder": 2 + } + } + } }, - "flat" : - { - "type" : "array", - "title" : "edt_conf_fw_flat_title", - "required" : true, - "default" : ["127.0.0.1:19401"], - "items" : { - "type": "string", - "title" : "edt_conf_fw_flat_itemtitle" - }, - "propertyOrder" : 3 + "flat": { + "type": "array", + "title": "edt_conf_fw_flat_title", + "propertyOrder": 3, + "uniqueItems": true, + "items": { + "type": "object", + "title": "edt_conf_fw_flat_itemtitle", + "required": true, + "properties": { + "host": { + "type": "string", + "format": "hostname_or_ip", + "minLength": 7, + "title": "edt_dev_spec_targetIpHost_title", + "required": true, + "propertyOrder": 1 + }, + "port": { + "type": "integer", + "minimum": 1, + "maximum": 65535, + "default": 19400, + "title": "edt_dev_spec_port_title", + "required": true, + "access": "expert", + "propertyOrder": 2 + } + } + } } }, - "additionalProperties" : false -} + "additionalProperties": false + } diff --git a/libsrc/hyperion/schema/schema-framegrabber.json b/libsrc/hyperion/schema/schema-framegrabber.json index 8c3f2ab2..950893b1 100644 --- a/libsrc/hyperion/schema/schema-framegrabber.json +++ b/libsrc/hyperion/schema/schema-framegrabber.json @@ -27,6 +27,7 @@ "hidden": true }, "required": true, + "default": "", "comment": "The 'available_devices' settings are dynamically inserted into the WebUI under PropertyOrder '2'.", "propertyOrder": 3 }, diff --git a/libsrc/leddevice/dev_net/LedDeviceNanoleaf.cpp b/libsrc/leddevice/dev_net/LedDeviceNanoleaf.cpp index 6f075bf6..12f4f600 100644 --- a/libsrc/leddevice/dev_net/LedDeviceNanoleaf.cpp +++ b/libsrc/leddevice/dev_net/LedDeviceNanoleaf.cpp @@ -19,7 +19,6 @@ const bool verbose3 = false; // Configuration settings const char CONFIG_ADDRESS[] = "host"; -//const char CONFIG_PORT[] = "port"; const char CONFIG_AUTH_TOKEN[] = "token"; const char CONFIG_RESTORE_STATE[] = "restoreOriginalState"; const char CONFIG_BRIGHTNESS[] = "brightness"; @@ -182,8 +181,6 @@ bool LedDeviceNanoleaf::init(const QJsonObject& deviceConfig) _startPos = deviceConfig[CONFIG_PANEL_START_POS].toInt(0); - // TODO: Allow to handle port dynamically - //Set hostname as per configuration and_defaultHost default port _hostName = deviceConfig[CONFIG_ADDRESS].toString(); _apiPort = API_DEFAULT_PORT; @@ -442,21 +439,7 @@ QJsonObject LedDeviceNanoleaf::getProperties(const QJsonObject& params) QString authToken = params["token"].toString(""); QString filter = params["filter"].toString(""); - // Resolve hostname and port (or use default API port) - QStringList addressparts = QStringUtils::split(hostName, ":", QStringUtils::SplitBehavior::SkipEmptyParts); - QString apiHost = addressparts[0]; - int apiPort; - - if (addressparts.size() > 1) - { - apiPort = addressparts[1].toInt(); - } - else - { - apiPort = API_DEFAULT_PORT; - } - - initRestAPI(apiHost, apiPort, authToken); + initRestAPI(hostName, API_DEFAULT_PORT, authToken); _restApi->setPath(filter); // Perform request @@ -482,21 +465,7 @@ void LedDeviceNanoleaf::identify(const QJsonObject& params) { QString authToken = params["token"].toString(""); - // Resolve hostname and port (or use default API port) - QStringList addressparts = QStringUtils::split(hostName, ":", QStringUtils::SplitBehavior::SkipEmptyParts); - QString apiHost = addressparts[0]; - int apiPort; - - if (addressparts.size() > 1) - { - apiPort = addressparts[1].toInt(); - } - else - { - apiPort = API_DEFAULT_PORT; - } - - initRestAPI(apiHost, apiPort, authToken); + initRestAPI(hostName, API_DEFAULT_PORT, authToken); _restApi->setPath("identify"); // Perform request diff --git a/libsrc/leddevice/dev_net/LedDeviceNanoleaf.h b/libsrc/leddevice/dev_net/LedDeviceNanoleaf.h index 17126db9..05f49887 100644 --- a/libsrc/leddevice/dev_net/LedDeviceNanoleaf.h +++ b/libsrc/leddevice/dev_net/LedDeviceNanoleaf.h @@ -61,7 +61,7 @@ public: /// Following parameters are required /// @code /// { - /// "host" : "hostname or IP [:port]", + /// "host" : "hostname or IP", /// "token" : "authentication token", /// "filter": "resource to query", root "/" is used, if empty /// } @@ -78,7 +78,7 @@ public: /// Following parameters are required /// @code /// { - /// "host" : "hostname or IP [:port]", + /// "host" : "hostname or IP", /// "token" : "authentication token", /// } ///@endcode diff --git a/libsrc/leddevice/dev_net/LedDevicePhilipsHue.cpp b/libsrc/leddevice/dev_net/LedDevicePhilipsHue.cpp index 470caa57..4f7eeba7 100644 --- a/libsrc/leddevice/dev_net/LedDevicePhilipsHue.cpp +++ b/libsrc/leddevice/dev_net/LedDevicePhilipsHue.cpp @@ -12,8 +12,8 @@ namespace { bool verbose = false; // Configuration settings -const char CONFIG_ADDRESS[] = "host"; -//const char CONFIG_PORT[] = "port"; +const char CONFIG_HOST[] = "host"; +const char CONFIG_PORT[] = "port"; const char CONFIG_USERNAME[] = "username"; const char CONFIG_CLIENTKEY[] = "clientkey"; const char CONFIG_BRIGHTNESSFACTOR[] = "brightnessFactor"; @@ -282,31 +282,30 @@ bool LedDevicePhilipsHueBridge::init(const QJsonObject &deviceConfig) log( "LatchTime", "%d", this->getLatchTime() ); //Set hostname as per configuration and_defaultHost default port - QString address = deviceConfig[ CONFIG_ADDRESS ].toString(); + _hostname = deviceConfig[CONFIG_HOST].toString(); //If host not configured the init failed - if ( address.isEmpty() ) + if (_hostname.isEmpty()) { - this->setInError("No target hostname nor IP defined"); + this->setInError("No target hostname defined"); isInitOK = false; } else { - QStringList addressparts = QStringUtils::split(address,":", QStringUtils::SplitBehavior::SkipEmptyParts); - _hostname = addressparts[0]; - log( "Hostname/IP", "%s", QSTRING_CSTR( _hostname ) ); + log("Hostname", "%s", QSTRING_CSTR(_hostname)); - if ( addressparts.size() > 1 ) + int _apiPort = deviceConfig[CONFIG_PORT].toInt(); + if (_apiPort == 0) { - _apiPort = addressparts[1].toInt(); - log( "Port", "%u", _apiPort ); + _apiPort = API_DEFAULT_PORT; } + log("Port", "%d", _apiPort); - _username = deviceConfig[ CONFIG_USERNAME ].toString(); + _username = deviceConfig[CONFIG_USERNAME].toString(); - if ( initRestAPI( _hostname, _apiPort, _username ) ) + if (initRestAPI(_hostname, _apiPort, _username)) { - if ( initMaps() ) + if (initMaps()) { isInitOK = ProviderUdpSSL::init(_devConfig); } @@ -1602,7 +1601,7 @@ QJsonObject LedDevicePhilipsHue::discover(const QJsonObject& /*params*/) // Discover Devices SSDPDiscover discover; - discover.skipDuplicateKeys(false); + discover.skipDuplicateKeys(true); discover.setSearchFilter(SSDP_FILTER, SSDP_FILTER_HEADER); QString searchTarget = SSDP_ID; @@ -1619,37 +1618,39 @@ QJsonObject LedDevicePhilipsHue::discover(const QJsonObject& /*params*/) QJsonObject LedDevicePhilipsHue::getProperties(const QJsonObject& params) { + DebugIf(verbose, _log, "params: [%s]", QString(QJsonDocument(params).toJson(QJsonDocument::Compact)).toUtf8().constData()); QJsonObject properties; // Get Phillips-Bridge device properties - QString host = params["host"].toString(""); - if ( !host.isEmpty() ) + QString hostName = params[CONFIG_HOST].toString(""); + if (!hostName.isEmpty()) { QString username = params["user"].toString(""); QString filter = params["filter"].toString(""); - // Resolve hostname and port (or use default API port) - QStringList addressparts = QStringUtils::split(host,":", QStringUtils::SplitBehavior::SkipEmptyParts); - QString apiHost = addressparts[0]; int apiPort; - - if ( addressparts.size() > 1 ) + if (params[CONFIG_PORT].isString()) { - apiPort = addressparts[1].toInt(); + apiPort = params[CONFIG_PORT].toString().toInt(); } else + { + apiPort = params[CONFIG_PORT].toInt(); + } + + if (apiPort == 0) { apiPort = API_DEFAULT_PORT; } - initRestAPI(apiHost, apiPort, username); + initRestAPI(hostName, apiPort, username); _restApi->setPath(filter); // Perform request httpResponse response = _restApi->get(); - if ( response.error() ) + if (response.error()) { - Warning (_log, "%s get properties failed with error: '%s'", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(response.getErrorReason())); + Warning(_log, "%s get properties failed with error: '%s'", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(response.getErrorReason())); } // Perform request @@ -1660,45 +1661,46 @@ QJsonObject LedDevicePhilipsHue::getProperties(const QJsonObject& params) void LedDevicePhilipsHue::identify(const QJsonObject& params) { - Debug(_log, "params: [%s]", QString(QJsonDocument(params).toJson(QJsonDocument::Compact)).toUtf8().constData() ); + DebugIf(verbose, _log, "params: [%s]", QString(QJsonDocument(params).toJson(QJsonDocument::Compact)).toUtf8().constData()); QJsonObject properties; // Identify Phillips-Bridge device - QString host = params["host"].toString(""); - if ( !host.isEmpty() ) + QString hostName = params[CONFIG_HOST].toString(""); + if (!hostName.isEmpty()) { QString username = params["user"].toString(""); int lightId = params["lightId"].toInt(0); - // Resolve hostname and port (or use default API port) - QStringList addressparts = QStringUtils::split(host,":", QStringUtils::SplitBehavior::SkipEmptyParts); - QString apiHost = addressparts[0]; int apiPort; - - if ( addressparts.size() > 1 ) + if (params[CONFIG_PORT].isString()) { - apiPort = addressparts[1].toInt(); + apiPort = params[CONFIG_PORT].toString().toInt(); } else { - apiPort = API_DEFAULT_PORT; + apiPort = params[CONFIG_PORT].toInt(); } - initRestAPI(apiHost, apiPort, username); + if (apiPort == 0) + { + apiPort = API_DEFAULT_PORT; + } - QString resource = QString("%1/%2/%3").arg( API_LIGHTS ).arg( lightId ).arg( API_STATE); + initRestAPI(hostName, apiPort, username); + + QString resource = QString("%1/%2/%3").arg(API_LIGHTS).arg(lightId).arg(API_STATE); _restApi->setPath(resource); QString stateCmd; - stateCmd += QString("\"%1\":%2,").arg( API_STATE_ON, API_STATE_VALUE_TRUE ); - stateCmd += QString("\"%1\":\"%2\"").arg( "alert", "select" ); + stateCmd += QString("\"%1\":%2,").arg(API_STATE_ON, API_STATE_VALUE_TRUE); + stateCmd += QString("\"%1\":\"%2\"").arg("alert", "select"); stateCmd = "{" + stateCmd + "}"; // Perform request httpResponse response = _restApi->put(stateCmd); - if ( response.error() ) + if (response.error()) { - Warning (_log, "%s identification failed with error: '%s'", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(response.getErrorReason())); + Warning(_log, "%s identification failed with error: '%s'", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(response.getErrorReason())); } } } diff --git a/libsrc/leddevice/dev_net/LedDevicePhilipsHue.h b/libsrc/leddevice/dev_net/LedDevicePhilipsHue.h index de3961a0..e7fe24f4 100644 --- a/libsrc/leddevice/dev_net/LedDevicePhilipsHue.h +++ b/libsrc/leddevice/dev_net/LedDevicePhilipsHue.h @@ -376,7 +376,8 @@ public: /// Following parameters are required /// @code /// { - /// "host" : "hostname or IP [:port]", + /// "host" : "hostname or IP + /// "port" : port /// "user" : "username", /// "filter": "resource to query", root "/" is used, if empty /// } @@ -390,7 +391,15 @@ public: /// /// @brief Send an update to the device to identify it. /// - /// Used in context of a set of devices of the same type. + /// Following parameters are required + /// @code + /// { + /// "host" : "hostname or IP + /// "port" : port + /// "user" : "username", + /// "filter": "resource to query", root "/" is used, if empty + /// } + ///@endcode /// /// @param[in] params Parameters to address device /// diff --git a/libsrc/leddevice/dev_net/LedDeviceWled.h b/libsrc/leddevice/dev_net/LedDeviceWled.h index 818467c0..2f89b59c 100644 --- a/libsrc/leddevice/dev_net/LedDeviceWled.h +++ b/libsrc/leddevice/dev_net/LedDeviceWled.h @@ -47,7 +47,7 @@ public: /// Following parameters are required /// @code /// { - /// "host" : "hostname or IP [:port]", + /// "host" : "hostname or IP", /// "filter": "resource to query", root "/" is used, if empty /// } ///@endcode @@ -63,7 +63,7 @@ public: /// Following parameters are required /// @code /// { - /// "host" : "hostname or IP [:port]", + /// "host" : "hostname or IP", /// } ///@endcode /// diff --git a/libsrc/leddevice/dev_net/LedDeviceYeelight.cpp b/libsrc/leddevice/dev_net/LedDeviceYeelight.cpp index aaf0ec24..d96f7288 100644 --- a/libsrc/leddevice/dev_net/LedDeviceYeelight.cpp +++ b/libsrc/leddevice/dev_net/LedDeviceYeelight.cpp @@ -1112,11 +1112,7 @@ bool LedDeviceYeelight::init(const QJsonObject &deviceConfig) QString hostName = configuredYeelightLights[j].toObject().value("host").toString(); int port = configuredYeelightLights[j].toObject().value("port").toInt(API_DEFAULT_PORT); - QStringList addressparts = QStringUtils::split(hostName,":", QStringUtils::SplitBehavior::SkipEmptyParts); - QString apiHost = addressparts[0]; - int apiPort = port; - - _lightsAddressList.append( {apiHost, apiPort} ); + _lightsAddressList.append( { hostName, port} ); } if ( updateLights(_lightsAddressList) ) @@ -1150,19 +1146,19 @@ bool LedDeviceYeelight::startMusicModeServer() } else { - QList ipAddressesList = QNetworkInterface::allAddresses(); - // use the first non-localhost IPv4 address - for (int i = 0; i < ipAddressesList.size(); ++i) { - if (ipAddressesList.at(i) != QHostAddress::LocalHost && - (ipAddressesList.at(i).toIPv4Address() != 0U)) + // use the first non-localhost IPv4 address, IPv6 are not supported by Yeelight currently + for (const auto& address : QNetworkInterface::allAddresses()) + { + // is valid when, no loopback, IPv4 + if (!address.isLoopback() && (address.protocol() == QAbstractSocket::IPv4Protocol)) { - _musicModeServerAddress = ipAddressesList.at(i); + _musicModeServerAddress = address; break; } } - if ( _musicModeServerAddress.isNull() ) + if (_musicModeServerAddress.isNull()) { - Error( _log, "Failed to resolve IP for music mode server"); + Error(_log, "Failed to resolve IP for music mode server"); } } } diff --git a/libsrc/leddevice/dev_net/ProviderUdp.cpp b/libsrc/leddevice/dev_net/ProviderUdp.cpp index f424cbe1..fc3cc016 100644 --- a/libsrc/leddevice/dev_net/ProviderUdp.cpp +++ b/libsrc/leddevice/dev_net/ProviderUdp.cpp @@ -70,7 +70,7 @@ bool ProviderUdp::init(const QJsonObject& deviceConfig) else { _port = static_cast(config_port); - Debug(_log, "UDP socket will write to %s:%u", QSTRING_CSTR(_address.toString()), _port); + Debug(_log, "UDP socket will write to %s port: %u", QSTRING_CSTR(_address.toString()), _port); _udpSocket = new QUdpSocket(this); diff --git a/libsrc/leddevice/dev_net/ProviderUdpSSL.cpp b/libsrc/leddevice/dev_net/ProviderUdpSSL.cpp index 244cdb3f..507e4d0f 100644 --- a/libsrc/leddevice/dev_net/ProviderUdpSSL.cpp +++ b/libsrc/leddevice/dev_net/ProviderUdpSSL.cpp @@ -74,9 +74,6 @@ bool ProviderUdpSSL::init(const QJsonObject &deviceConfig) if( deviceConfig.contains("hs_attempts") ) _handshake_attempts = deviceConfig["hs_attempts"].toInt(5); QString host = deviceConfig["host"].toString(_defaultHost); - //Split hostname from API-port in case given - QStringList addressparts = QStringUtils::split(host, ":", QStringUtils::SplitBehavior::SkipEmptyParts); - QString udpHost = addressparts[0]; QStringList debugLevels = QStringList() << "No Debug" << "Error" << "State Change" << "Informational" << "Verbose"; @@ -96,25 +93,24 @@ bool ProviderUdpSSL::init(const QJsonObject &deviceConfig) configLog( "SSL Handshake Timeout max", "%d", _handshake_timeout_max ); configLog( "SSL Handshake attempts", "%d", _handshake_attempts ); - if ( _address.setAddress(udpHost) ) + if (_address.setAddress(host)) { - Debug( _log, "Successfully parsed %s as an ip address.", QSTRING_CSTR(udpHost) ); + Debug(_log, "Successfully parsed %s as an IP-address.", QSTRING_CSTR(_address.toString())); } else { - Debug( _log, "Failed to parse [%s] as an ip address.", QSTRING_CSTR(udpHost) ); - QHostInfo info = QHostInfo::fromName(udpHost); - if ( info.addresses().isEmpty() ) + QHostInfo hostInfo = QHostInfo::fromName(host); + if (hostInfo.error() == QHostInfo::NoError) { - Debug( _log, "Failed to parse [%s] as a hostname.", QSTRING_CSTR(udpHost) ); - QString errortext = QString("Invalid target address [%1]!").arg(host); - this->setInError( errortext ); - isInitOK = false; + _address = hostInfo.addresses().first(); + Debug(_log, "Successfully resolved IP-address (%s) for hostname (%s).", QSTRING_CSTR(_address.toString()), QSTRING_CSTR(host)); } else { - Debug( _log, "Successfully parsed %s as a hostname.", QSTRING_CSTR(udpHost) ); - _address = info.addresses().first(); + QString errortext = QString("Failed resolving IP-address for [%1], (%2) %3").arg(host).arg(hostInfo.error()).arg(hostInfo.errorString()); + this->setInError(errortext); + isInitOK = false; + return isInitOK; } } @@ -129,7 +125,7 @@ bool ProviderUdpSSL::init(const QJsonObject &deviceConfig) else { _ssl_port = config_port; - Debug( _log, "UDP SSL using %s:%u", QSTRING_CSTR( _address.toString() ), _ssl_port ); + Debug(_log, "UDP SSL will write to %s port: %u", QSTRING_CSTR(_address.toString()), _ssl_port); isInitOK = true; } } @@ -250,23 +246,26 @@ bool ProviderUdpSSL::initConnection() bool ProviderUdpSSL::seedingRNG() { - int ret = 0; - - sslLog( "Seeding the random number generator..." ); + sslLog("Seeding the random number generator..."); mbedtls_entropy_init(&entropy); - sslLog( "Set mbedtls_ctr_drbg_seed..." ); + sslLog("Set mbedtls_ctr_drbg_seed..."); - const char* custom = QSTRING_CSTR( _custom ); + QByteArray customDataArray = _custom.toLocal8Bit(); + const char* customData = customDataArray.constData(); - if ((ret = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy, reinterpret_cast(custom), strlen(custom))) != 0) + int ret = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, + &entropy, reinterpret_cast(customData), + std::min(strlen(customData), (size_t)MBEDTLS_CTR_DRBG_MAX_SEED_INPUT)); + + if (ret != 0) { - sslLog( QString("mbedtls_ctr_drbg_seed FAILED %1").arg( errorMsg( ret ) ), "error" ); + sslLog(QString("mbedtls_ctr_drbg_seed FAILED %1").arg(errorMsg(ret)), "error"); return false; } - sslLog( "Seeding the random number generator...ok" ); + sslLog("Seeding the random number generator...ok"); return true; } diff --git a/libsrc/leddevice/schemas/schema-artnet.json b/libsrc/leddevice/schemas/schema-artnet.json index fce78dae..6692e53a 100644 --- a/libsrc/leddevice/schemas/schema-artnet.json +++ b/libsrc/leddevice/schemas/schema-artnet.json @@ -1,42 +1,43 @@ { - "type":"object", - "required":true, - "properties":{ - "host" : { - "type": "string", - "title":"edt_dev_spec_targetIp_title", - "propertyOrder" : 1 - }, - "port" : { - "type": "integer", - "title":"edt_dev_spec_port_title", - "default": 6454, - "minimum": 0, - "maximum": 65535, - "propertyOrder" : 2 - }, - "universe": { - "type": "integer", - "title":"edt_dev_spec_universe_title", - "default": 1, - "propertyOrder" : 3 - }, - "channelsPerFixture": { - "type": "integer", - "title":"edt_dev_spec_chanperfixture_title", - "default": 3, - "propertyOrder" : 4 - }, - "latchTime": { - "type": "integer", - "title":"edt_dev_spec_latchtime_title", - "default": 0, - "append": "edt_append_ms", - "minimum": 0, - "maximum": 1000, - "access" : "expert", - "propertyOrder" : 5 - } - }, - "additionalProperties": true + "type": "object", + "required": true, + "properties": { + "host": { + "type": "string", + "format": "hostname_or_ip", + "title": "edt_dev_spec_targetIpHost_title", + "propertyOrder": 1 + }, + "port": { + "type": "integer", + "title": "edt_dev_spec_port_title", + "default": 6454, + "minimum": 0, + "maximum": 65535, + "propertyOrder": 2 + }, + "universe": { + "type": "integer", + "title": "edt_dev_spec_universe_title", + "default": 1, + "propertyOrder": 3 + }, + "channelsPerFixture": { + "type": "integer", + "title": "edt_dev_spec_chanperfixture_title", + "default": 3, + "propertyOrder": 4 + }, + "latchTime": { + "type": "integer", + "title": "edt_dev_spec_latchtime_title", + "default": 0, + "append": "edt_append_ms", + "minimum": 0, + "maximum": 1000, + "access": "expert", + "propertyOrder": 5 + } + }, + "additionalProperties": true } diff --git a/libsrc/leddevice/schemas/schema-atmoorb.json b/libsrc/leddevice/schemas/schema-atmoorb.json index 9015fa03..90e7c4b6 100644 --- a/libsrc/leddevice/schemas/schema-atmoorb.json +++ b/libsrc/leddevice/schemas/schema-atmoorb.json @@ -18,6 +18,7 @@ }, "host": { "type": "string", + "format": "ipv4", "title": "edt_dev_spec_multicastGroup_title", "default": "239.255.255.250", "propertyOrder": 3 diff --git a/libsrc/leddevice/schemas/schema-cololight.json b/libsrc/leddevice/schemas/schema-cololight.json index 11942ed2..ed1c7fdf 100644 --- a/libsrc/leddevice/schemas/schema-cololight.json +++ b/libsrc/leddevice/schemas/schema-cololight.json @@ -15,6 +15,7 @@ }, "host": { "type": "string", + "format": "hostname_or_ip4", "title": "edt_dev_spec_targetIpHost_title", "options": { "infoText": "edt_dev_spec_targetIpHost_title_info" diff --git a/libsrc/leddevice/schemas/schema-e131.json b/libsrc/leddevice/schemas/schema-e131.json index fa0dc204..02740727 100644 --- a/libsrc/leddevice/schemas/schema-e131.json +++ b/libsrc/leddevice/schemas/schema-e131.json @@ -1,41 +1,42 @@ { - "type":"object", - "required":true, - "properties":{ - "host" : { - "type": "string", - "title":"edt_dev_spec_targetIp_title", - "propertyOrder" : 1 - }, - "port" : { - "type": "integer", - "title":"edt_dev_spec_port_title", - "default": 5568, - "minimum" : 0, - "maximum" : 65535, - "propertyOrder" : 2 - }, - "universe": { - "type": "integer", - "title":"edt_dev_spec_universe_title", - "default": 1, - "propertyOrder" : 3 - }, - "latchTime": { - "type": "integer", - "title":"edt_dev_spec_latchtime_title", - "default": 0, - "append" : "edt_append_ms", - "minimum": 0, - "maximum": 1000, - "access" : "expert", - "propertyOrder" : 4 - }, - "cid": { - "type": "string", - "title":"edt_dev_spec_cid_title", - "propertyOrder" : 5 - } - }, - "additionalProperties": true + "type": "object", + "required": true, + "properties": { + "host": { + "type": "string", + "format": "hostname_or_ip", + "title": "edt_dev_spec_targetIpHost_title", + "propertyOrder": 1 + }, + "port": { + "type": "integer", + "title": "edt_dev_spec_port_title", + "default": 5568, + "minimum": 0, + "maximum": 65535, + "propertyOrder": 2 + }, + "universe": { + "type": "integer", + "title": "edt_dev_spec_universe_title", + "default": 1, + "propertyOrder": 3 + }, + "latchTime": { + "type": "integer", + "title": "edt_dev_spec_latchtime_title", + "default": 0, + "append": "edt_append_ms", + "minimum": 0, + "maximum": 1000, + "access": "expert", + "propertyOrder": 4 + }, + "cid": { + "type": "string", + "title": "edt_dev_spec_cid_title", + "propertyOrder": 5 + } + }, + "additionalProperties": true } diff --git a/libsrc/leddevice/schemas/schema-fadecandy.json b/libsrc/leddevice/schemas/schema-fadecandy.json index d6d777f5..3376bf4d 100644 --- a/libsrc/leddevice/schemas/schema-fadecandy.json +++ b/libsrc/leddevice/schemas/schema-fadecandy.json @@ -1,113 +1,113 @@ { - "type":"object", - "required":true, - "properties": { - "host": { - "type": "string", - "title": "edt_dev_spec_targetIp_title", - "default": "127.0.0.1", - "propertyOrder": 1 - }, - "port": { - "type": "number", - "title": "edt_dev_spec_port_title", - "default": 7890, - "propertyOrder": 2 - }, - "latchTime": { - "type": "integer", - "title": "edt_dev_spec_latchtime_title", - "default": 0, - "append": "edt_append_ms", - "minimum": 0, - "maximum": 1000, - "access": "expert", - "propertyOrder": 3 - }, - "setFcConfig": { - "type": "boolean", - "title": "edt_dev_spec_FCsetConfig_title", - "default": false, - "propertyOrder": 4 - }, - "manualLed": { - "type": "boolean", - "title": "edt_dev_spec_FCmanualControl_title", - "default": false, - "options": { - "dependencies": { - "setFcConfig": true - } - }, - "propertyOrder": 5 - }, - "ledOn": { - "type": "boolean", - "title": "edt_dev_spec_FCledToOn_title", - "default": false, - "options": { - "dependencies": { - "setFcConfig": true - } - }, - "propertyOrder": 6 - }, - "interpolation": { - "type": "boolean", - "title": "edt_dev_spec_interpolation_title", - "default": false, - "options": { - "dependencies": { - "setFcConfig": true - } - }, - "propertyOrder": 7 - }, - "dither": { - "type": "boolean", - "title": "edt_dev_spec_dithering_title", - "default": false, - "options": { - "dependencies": { - "setFcConfig": true - } - }, - "propertyOrder": 8 - }, - "gamma": { - "type": "number", - "title": "edt_dev_spec_gamma_title", - "default": 1.0, - "minimum": 0.1, - "maximum": 5.0, - "options": { - "dependencies": { - "setFcConfig": true - } - }, - "propertyOrder": 9 - }, - "whitepoint": { - "type": "array", - "title": "edt_dev_spec_whitepoint_title", - "options": { - "dependencies": { - "setFcConfig": true - } - }, - "propertyOrder": 10, - "default": [ 255, 255, 255 ], - "maxItems": 3, - "minItems": 3, - "format": "colorpicker", - "items": { - "type": "integer", - "minimum": 0, - "maximum": 255, - "default": 255 - } - } - }, - "additionalProperties": true + "type": "object", + "required": true, + "properties": { + "host": { + "type": "string", + "format": "hostname_or_ip4", + "title": "edt_dev_spec_targetIpHost_title", + "default": "127.0.0.1", + "propertyOrder": 1 + }, + "port": { + "type": "number", + "title": "edt_dev_spec_port_title", + "default": 7890, + "propertyOrder": 2 + }, + "latchTime": { + "type": "integer", + "title": "edt_dev_spec_latchtime_title", + "default": 0, + "append": "edt_append_ms", + "minimum": 0, + "maximum": 1000, + "access": "expert", + "propertyOrder": 3 + }, + "setFcConfig": { + "type": "boolean", + "title": "edt_dev_spec_FCsetConfig_title", + "default": false, + "propertyOrder": 4 + }, + "manualLed": { + "type": "boolean", + "title": "edt_dev_spec_FCmanualControl_title", + "default": false, + "options": { + "dependencies": { + "setFcConfig": true + } + }, + "propertyOrder": 5 + }, + "ledOn": { + "type": "boolean", + "title": "edt_dev_spec_FCledToOn_title", + "default": false, + "options": { + "dependencies": { + "setFcConfig": true + } + }, + "propertyOrder": 6 + }, + "interpolation": { + "type": "boolean", + "title": "edt_dev_spec_interpolation_title", + "default": false, + "options": { + "dependencies": { + "setFcConfig": true + } + }, + "propertyOrder": 7 + }, + "dither": { + "type": "boolean", + "title": "edt_dev_spec_dithering_title", + "default": false, + "options": { + "dependencies": { + "setFcConfig": true + } + }, + "propertyOrder": 8 + }, + "gamma": { + "type": "number", + "title": "edt_dev_spec_gamma_title", + "default": 1.0, + "minimum": 0.1, + "maximum": 5.0, + "options": { + "dependencies": { + "setFcConfig": true + } + }, + "propertyOrder": 9 + }, + "whitepoint": { + "type": "array", + "title": "edt_dev_spec_whitepoint_title", + "options": { + "dependencies": { + "setFcConfig": true + } + }, + "propertyOrder": 10, + "default": [ 255, 255, 255 ], + "maxItems": 3, + "minItems": 3, + "format": "colorpicker", + "items": { + "type": "integer", + "minimum": 0, + "maximum": 255, + "default": 255 + } + } + }, + "additionalProperties": true } - diff --git a/libsrc/leddevice/schemas/schema-h801.json b/libsrc/leddevice/schemas/schema-h801.json index c06765a1..8e030f43 100644 --- a/libsrc/leddevice/schemas/schema-h801.json +++ b/libsrc/leddevice/schemas/schema-h801.json @@ -1,38 +1,39 @@ { - "type":"object", - "required":true, - "properties":{ - "host" : { - "type": "string", - "title":"edt_dev_spec_targetIp_title", - "default": "255.255.255.255", - "propertyOrder" : 1 - }, - "port" : { - "type": "integer", - "title":"edt_dev_spec_port_title", - "default": 30977, - "propertyOrder" : 2 - }, - "lightIds": { - "type": "array", - "title":"edt_dev_spec_lightid_title", - "items" : { - "type" : "string", - "title" : "edt_dev_spec_lightid_itemtitle" - }, - "propertyOrder" : 3 - }, - "latchTime": { - "type": "integer", - "title":"edt_dev_spec_latchtime_title", - "default": 10, - "append" : "edt_append_ms", - "minimum": 0, - "maximum": 1000, - "access" : "expert", - "propertyOrder" : 4 - } - }, - "additionalProperties": true + "type": "object", + "required": true, + "properties": { + "host": { + "type": "string", + "format": "ipv4", + "title": "edt_dev_spec_targetIp_title", + "default": "255.255.255.255", + "propertyOrder": 1 + }, + "port": { + "type": "integer", + "title": "edt_dev_spec_port_title", + "default": 30977, + "propertyOrder": 2 + }, + "lightIds": { + "type": "array", + "title": "edt_dev_spec_lightid_title", + "items": { + "type": "string", + "title": "edt_dev_spec_lightid_itemtitle" + }, + "propertyOrder": 3 + }, + "latchTime": { + "type": "integer", + "title": "edt_dev_spec_latchtime_title", + "default": 10, + "append": "edt_append_ms", + "minimum": 0, + "maximum": 1000, + "access": "expert", + "propertyOrder": 4 + } + }, + "additionalProperties": true } diff --git a/libsrc/leddevice/schemas/schema-nanoleaf.json b/libsrc/leddevice/schemas/schema-nanoleaf.json index fae3bf24..e975d763 100644 --- a/libsrc/leddevice/schemas/schema-nanoleaf.json +++ b/libsrc/leddevice/schemas/schema-nanoleaf.json @@ -15,6 +15,7 @@ }, "host": { "type": "string", + "format": "hostname_or_ip", "title": "edt_dev_spec_targetIpHost_title", "options": { "infoText": "edt_dev_spec_targetIpHost_title_info" diff --git a/libsrc/leddevice/schemas/schema-philipshue.json b/libsrc/leddevice/schemas/schema-philipshue.json index a6f509ec..7a4d46da 100644 --- a/libsrc/leddevice/schemas/schema-philipshue.json +++ b/libsrc/leddevice/schemas/schema-philipshue.json @@ -4,15 +4,25 @@ "properties": { "host": { "type": "string", - "title": "edt_dev_spec_targetIp_title", + "format": "hostname_or_ip", + "title": "edt_dev_spec_targetIpHost_title", "default": "", "propertyOrder": 1 }, + "port": { + "type": "integer", + "title": "edt_dev_spec_port_title", + "default": 0, + "minimum": 0, + "maximum": 65535, + "access": "expert", + "propertyOrder": 2 + }, "username": { "type": "string", "title": "edt_dev_spec_username_title", "default": "", - "propertyOrder": 2 + "propertyOrder": 3 }, "clientkey": { "type": "string", @@ -23,13 +33,13 @@ "useEntertainmentAPI": true } }, - "propertyOrder": 3 + "propertyOrder": 4 }, "useEntertainmentAPI": { "type": "boolean", "title": "edt_dev_spec_useEntertainmentAPI_title", "default": true, - "propertyOrder": 4 + "propertyOrder": 5 }, "transitiontime": { "type": "number", @@ -41,19 +51,19 @@ "useEntertainmentAPI": false } }, - "propertyOrder": 5 + "propertyOrder": 6 }, "switchOffOnBlack": { "type": "boolean", "title": "edt_dev_spec_switchOffOnBlack_title", "default": false, - "propertyOrder": 6 + "propertyOrder": 7 }, "restoreOriginalState": { "type": "boolean", "title": "edt_dev_spec_restoreOriginalState_title", "default": true, - "propertyOrder": 7 + "propertyOrder": 8 }, "lightIds": { "type": "array", @@ -71,7 +81,7 @@ "useEntertainmentAPI": false } }, - "propertyOrder": 8 + "propertyOrder": 9 }, "groupId": { "type": "number", @@ -82,7 +92,7 @@ "useEntertainmentAPI": true } }, - "propertyOrder": 9 + "propertyOrder": 10 }, "blackLightsTimeout": { "type": "number", @@ -98,7 +108,7 @@ "useEntertainmentAPI": true } }, - "propertyOrder": 10 + "propertyOrder": 11 }, "brightnessThreshold": { "type": "number", @@ -113,7 +123,7 @@ "useEntertainmentAPI": true } }, - "propertyOrder": 11 + "propertyOrder": 12 }, "brightnessFactor": { "type": "number", @@ -123,7 +133,7 @@ "minimum": 0.5, "maximum": 10.0, "access": "advanced", - "propertyOrder": 12 + "propertyOrder": 13 }, "brightnessMin": { "type": "number", @@ -138,7 +148,7 @@ "useEntertainmentAPI": true } }, - "propertyOrder": 13 + "propertyOrder": 14 }, "brightnessMax": { "type": "number", @@ -153,7 +163,7 @@ "useEntertainmentAPI": true } }, - "propertyOrder": 14 + "propertyOrder": 15 }, "sslReadTimeout": { "type": "number", @@ -169,7 +179,7 @@ "useEntertainmentAPI": true } }, - "propertyOrder": 15 + "propertyOrder": 16 }, "sslHSTimeoutMin": { "type": "number", @@ -185,7 +195,7 @@ "useEntertainmentAPI": true } }, - "propertyOrder": 16 + "propertyOrder": 17 }, "sslHSTimeoutMax": { "type": "number", @@ -201,14 +211,14 @@ "useEntertainmentAPI": true } }, - "propertyOrder": 17 + "propertyOrder": 18 }, "verbose": { "type": "boolean", "title": "edt_dev_spec_verbose_title", "default": false, "access": "expert", - "propertyOrder": 18 + "propertyOrder": 19 }, "debugStreamer": { "type": "boolean", @@ -220,7 +230,7 @@ "useEntertainmentAPI": true } }, - "propertyOrder": 19 + "propertyOrder": 20 }, "debugLevel": { "type": "string", @@ -236,7 +246,7 @@ "minimum": 0, "maximum": 4, "access": "expert", - "propertyOrder": 20 + "propertyOrder": 21 } }, "additionalProperties": true diff --git a/libsrc/leddevice/schemas/schema-tinkerforge.json b/libsrc/leddevice/schemas/schema-tinkerforge.json index cc13a53d..3ed5ea3d 100644 --- a/libsrc/leddevice/schemas/schema-tinkerforge.json +++ b/libsrc/leddevice/schemas/schema-tinkerforge.json @@ -2,11 +2,12 @@ "type":"object", "required":true, "properties":{ - "output": { + "host": { "type": "string", - "title":"edt_dev_spec_targetIp_title", - "default" : "127.0.0.1", - "propertyOrder" : 1 + "format": "hostname_or_ip", + "title": "edt_dev_spec_targetIpHost_title", + "default": "127.0.0.1", + "propertyOrder": 1 }, "port": { "type": "integer", diff --git a/libsrc/leddevice/schemas/schema-tpm2net.json b/libsrc/leddevice/schemas/schema-tpm2net.json index 4d87bac2..16fca011 100644 --- a/libsrc/leddevice/schemas/schema-tpm2net.json +++ b/libsrc/leddevice/schemas/schema-tpm2net.json @@ -1,37 +1,38 @@ { - "type":"object", - "required":true, - "properties":{ - "host" : { - "type": "string", - "title":"edt_dev_spec_targetIpHost_title", - "propertyOrder" : 1 - }, - "port": { - "type": "integer", - "title":"edt_dev_spec_port_title", - "minimum" : 0, - "maximum" : 65535, - "default" : 50200, - "propertyOrder" : 2 - }, - "max-packet": { - "type": "integer", - "title":"edt_dev_spec_maxPacket_title", - "minimum" : 0, - "default" : 170, - "propertyOrder" : 3 - }, - "latchTime": { - "type": "integer", - "title":"edt_dev_spec_latchtime_title", - "default": 0, - "append" : "edt_append_ms", - "minimum": 0, - "maximum": 1000, - "access" : "expert", - "propertyOrder" : 4 - } - }, - "additionalProperties": true + "type": "object", + "required": true, + "properties": { + "host": { + "type": "string", + "format": "hostname_or_ip", + "title": "edt_dev_spec_targetIpHost_title", + "propertyOrder": 1 + }, + "port": { + "type": "integer", + "title": "edt_dev_spec_port_title", + "minimum": 0, + "maximum": 65535, + "default": 50200, + "propertyOrder": 2 + }, + "max-packet": { + "type": "integer", + "title": "edt_dev_spec_maxPacket_title", + "minimum": 0, + "default": 170, + "propertyOrder": 3 + }, + "latchTime": { + "type": "integer", + "title": "edt_dev_spec_latchtime_title", + "default": 0, + "append": "edt_append_ms", + "minimum": 0, + "maximum": 1000, + "access": "expert", + "propertyOrder": 4 + } + }, + "additionalProperties": true } diff --git a/libsrc/leddevice/schemas/schema-udpraw.json b/libsrc/leddevice/schemas/schema-udpraw.json index bd9e893f..e589014f 100644 --- a/libsrc/leddevice/schemas/schema-udpraw.json +++ b/libsrc/leddevice/schemas/schema-udpraw.json @@ -1,30 +1,31 @@ { - "type":"object", - "required":true, - "properties":{ - "host" : { - "type": "string", - "title":"edt_dev_spec_targetIp_title", - "propertyOrder" : 1 - }, - "port" : { - "type": "integer", - "title":"edt_dev_spec_port_title", - "default": 5568, - "minimum" : 0, - "maximum" : 65535, - "propertyOrder" : 2 - }, - "latchTime": { - "type": "integer", - "title":"edt_dev_spec_latchtime_title", - "default": 0, - "append" : "edt_append_ms", - "minimum": 0, - "maximum": 1000, - "access" : "expert", - "propertyOrder" : 3 - } - }, - "additionalProperties": true + "type": "object", + "required": true, + "properties": { + "host": { + "type": "string", + "title": "edt_dev_spec_targetIpHost_title", + "format": "hostname_or_ip", + "propertyOrder": 1 + }, + "port": { + "type": "integer", + "title": "edt_dev_spec_port_title", + "default": 5568, + "minimum": 0, + "maximum": 65535, + "propertyOrder": 2 + }, + "latchTime": { + "type": "integer", + "title": "edt_dev_spec_latchtime_title", + "default": 0, + "append": "edt_append_ms", + "minimum": 0, + "maximum": 1000, + "access": "expert", + "propertyOrder": 3 + } + }, + "additionalProperties": true } diff --git a/libsrc/leddevice/schemas/schema-wled.json b/libsrc/leddevice/schemas/schema-wled.json index 881220c8..1a8a71ff 100644 --- a/libsrc/leddevice/schemas/schema-wled.json +++ b/libsrc/leddevice/schemas/schema-wled.json @@ -16,6 +16,7 @@ }, "host": { "type": "string", + "format": "hostname_or_ip4", "title": "edt_dev_spec_targetIpHost_title", "options": { "infoText": "edt_dev_spec_targetIpHost_title_info" diff --git a/libsrc/leddevice/schemas/schema-yeelight.json b/libsrc/leddevice/schemas/schema-yeelight.json index 1fe8f173..47661fbc 100644 --- a/libsrc/leddevice/schemas/schema-yeelight.json +++ b/libsrc/leddevice/schemas/schema-yeelight.json @@ -112,8 +112,9 @@ "properties": { "host": { "type": "string", + "format": "hostname_or_ip", "minLength": 7, - "title": "edt_dev_spec_networkDeviceName_title", + "title": "edt_dev_spec_targetIpHost_title", "required": true, "propertyOrder": 1 }, @@ -122,7 +123,7 @@ "minimum": 0, "maximum": 65535, "default": 55443, - "title": "edt_dev_spec_networkDevicePort_title", + "title": "edt_dev_spec_port_title", "required": false, "access": "expert", "propertyOrder": 2 diff --git a/libsrc/webserver/WebServer.cpp b/libsrc/webserver/WebServer.cpp index beeecbea..9486f075 100644 --- a/libsrc/webserver/WebServer.cpp +++ b/libsrc/webserver/WebServer.cpp @@ -56,7 +56,8 @@ void WebServer::onServerStarted (quint16 port) { _inited = true; - Info(_log, "Started on port %d name '%s'", port ,_server->getServerName().toStdString().c_str()); + Info(_log, "'%s' started on port %d",_server->getServerName().toStdString().c_str(), port); + #ifdef ENABLE_AVAHI if(_serviceRegister == nullptr) { diff --git a/src/hyperion-aml/hyperion-aml.cpp b/src/hyperion-aml/hyperion-aml.cpp index 16a8eef4..bbdeffa4 100644 --- a/src/hyperion-aml/hyperion-aml.cpp +++ b/src/hyperion-aml/hyperion-aml.cpp @@ -11,6 +11,7 @@ // ssdp discover #include +#include #include @@ -53,7 +54,7 @@ int main(int argc, char ** argv) BooleanOption & arg3DSBS = parser.add(0x0, "3DSBS", "Interpret the incoming video stream as 3D side-by-side"); BooleanOption & arg3DTAB = parser.add(0x0, "3DTAB", "Interpret the incoming video stream as 3D top-and-bottom"); - Option & argAddress = parser.add