From a3c8fba7a3c82030fba03afd19c498f67db26c15 Mon Sep 17 00:00:00 2001 From: LordGrey Date: Thu, 8 Apr 2021 09:26:10 +0200 Subject: [PATCH 01/58] Revert "temporarily reset mbedtls to version 2.25 #1214" This reverts commit dc0e953c40fd46ebc20f90a1c69d35a01225e5ec. --- dependencies/CMakeLists-mbedtls.txt.in | 2 +- dependencies/CMakeLists.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dependencies/CMakeLists-mbedtls.txt.in b/dependencies/CMakeLists-mbedtls.txt.in index cb81adc8..12888d5b 100644 --- a/dependencies/CMakeLists-mbedtls.txt.in +++ b/dependencies/CMakeLists-mbedtls.txt.in @@ -14,7 +14,7 @@ include(ExternalProject) ExternalProject_Add( mbedtls GIT_REPOSITORY "https://github.com/ARMmbed/mbedtls.git" - GIT_TAG "v2.25.0" # Reset to origin/master if issue https://github.com/ARMmbed/mbedtls/issues/4233 if fixed + GIT_TAG origin/master BUILD_ALWAYS OFF DOWNLOAD_DIR "${DOWNLOAD_DIR}" SOURCE_DIR "${SOURCE_DIR}" diff --git a/dependencies/CMakeLists.txt b/dependencies/CMakeLists.txt index f37aa83a..318787de 100644 --- a/dependencies/CMakeLists.txt +++ b/dependencies/CMakeLists.txt @@ -233,7 +233,7 @@ if (NOT USE_SYSTEM_MBEDTLS_LIBS) FetchContent_Declare( mbedtls GIT_REPOSITORY https://github.com/ARMmbed/mbedtls.git - GIT_TAG "v2.25.0" # Reset to origin/master if issue https://github.com/ARMmbed/mbedtls/issues/4233 if fixed + GIT_TAG origin/master BUILD_ALWAYS OFF GIT_PROGRESS 1 DOWNLOAD_DIR "${MBEDTLS_DOWNLOAD_DIR}" From 6f0ccdbe25a7532f766b613430ef13838d9719c0 Mon Sep 17 00:00:00 2001 From: LordGrey Date: Sat, 10 Apr 2021 17:19:47 +0200 Subject: [PATCH 02/58] "temporarily reset mbedtls to version 2.25 #1214" --- dependencies/CMakeLists-mbedtls.txt.in | 2 +- dependencies/CMakeLists.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dependencies/CMakeLists-mbedtls.txt.in b/dependencies/CMakeLists-mbedtls.txt.in index 12888d5b..cb81adc8 100644 --- a/dependencies/CMakeLists-mbedtls.txt.in +++ b/dependencies/CMakeLists-mbedtls.txt.in @@ -14,7 +14,7 @@ include(ExternalProject) ExternalProject_Add( mbedtls GIT_REPOSITORY "https://github.com/ARMmbed/mbedtls.git" - GIT_TAG origin/master + GIT_TAG "v2.25.0" # Reset to origin/master if issue https://github.com/ARMmbed/mbedtls/issues/4233 if fixed BUILD_ALWAYS OFF DOWNLOAD_DIR "${DOWNLOAD_DIR}" SOURCE_DIR "${SOURCE_DIR}" diff --git a/dependencies/CMakeLists.txt b/dependencies/CMakeLists.txt index 318787de..f37aa83a 100644 --- a/dependencies/CMakeLists.txt +++ b/dependencies/CMakeLists.txt @@ -233,7 +233,7 @@ if (NOT USE_SYSTEM_MBEDTLS_LIBS) FetchContent_Declare( mbedtls GIT_REPOSITORY https://github.com/ARMmbed/mbedtls.git - GIT_TAG origin/master + GIT_TAG "v2.25.0" # Reset to origin/master if issue https://github.com/ARMmbed/mbedtls/issues/4233 if fixed BUILD_ALWAYS OFF GIT_PROGRESS 1 DOWNLOAD_DIR "${MBEDTLS_DOWNLOAD_DIR}" From a4d98fd916e17f1a3834c43ba220d7838313d711 Mon Sep 17 00:00:00 2001 From: TheGroundZero <2406013+TheGroundZero@users.noreply.github.com> Date: Wed, 14 Apr 2021 14:25:51 +0000 Subject: [PATCH 03/58] Fixes: #1229 (#1230) * Fix "LED Test" effect colour order (#1229) --- CHANGELOG.md | 1 + assets/webconfig/i18n/en.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b9137a4..e00f0ccc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - LED-Hue: Proper black in Entertainement mode if min brightness is set - LED-Hue: Minor fix of setColor command - Nanoleaf: Fix,if external control mode cannot be set +- Fix issue #1229: "LED Test" effect description is in wrong order ### Removed diff --git a/assets/webconfig/i18n/en.json b/assets/webconfig/i18n/en.json index e9b29205..f57e13cf 100644 --- a/assets/webconfig/i18n/en.json +++ b/assets/webconfig/i18n/en.json @@ -580,7 +580,7 @@ "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.", "edt_eff_ledlist": "Led List", "edt_eff_ledtest_header": "Led Test", - "edt_eff_ledtest_header_desc": "Rotating output: Red, Blue, Green, White, Black", + "edt_eff_ledtest_header_desc": "Rotating output: Red, Green, Blue, White, Black", "edt_eff_length": "Length", "edt_eff_lightclock_header": "Light Clock", "edt_eff_lightclock_header_desc": "A real clock as light! Adjust the colors of hours, minute, seconds. A optional 3/6/9/12 o'clock marker is also available. In case the clock is wrong, you need to check your system clock.", From 7eeb7401777d97308cdd7490c824ae73f5ec0f71 Mon Sep 17 00:00:00 2001 From: LordGrey <48840279+Lord-Grey@users.noreply.github.com> Date: Sat, 24 Apr 2021 19:37:29 +0200 Subject: [PATCH 04/58] Dynamic Device Selection/Configuration (#1164) --- .github/workflows/pull-request.yml | 6 +- assets/webconfig/content/conf_leds.html | 740 +++--- assets/webconfig/i18n/en.json | 146 +- assets/webconfig/index.html | 4 +- assets/webconfig/js/content_colors.js | 154 +- assets/webconfig/js/content_index.js | 575 ++--- assets/webconfig/js/content_leds.js | 2019 ++++++++++++----- assets/webconfig/js/ui_utils.js | 239 +- assets/webconfig/js/wizard.js | 437 +--- cmake/debian/postinst | 4 +- cmake/packages.cmake | 5 +- cmake/rpm/postinst | 4 +- include/hyperion/SettingsManager.h | 17 +- include/leddevice/LedDevice.h | 17 +- include/utils/version.hpp | 550 +++++ libsrc/hyperion/Hyperion.cpp | 6 +- libsrc/hyperion/SettingsManager.cpp | 252 +- libsrc/hyperion/schema/schema-device.json | 91 +- libsrc/hyperion/schema/schema-general.json | 19 + libsrc/hyperion/schema/schema-ledConfig.json | 38 +- libsrc/leddevice/LedDevice.cpp | 48 +- libsrc/leddevice/dev_net/LedDeviceWled.cpp | 63 +- libsrc/leddevice/dev_net/LedDeviceWled.h | 8 + .../leddevice/dev_net/LedDeviceYeelight.cpp | 9 +- .../dev_other/LedDevicePiBlaster.cpp | 39 + .../leddevice/dev_other/LedDevicePiBlaster.h | 6 + libsrc/leddevice/dev_serial/ProviderRs232.cpp | 51 +- libsrc/leddevice/dev_serial/ProviderRs232.h | 14 + libsrc/leddevice/dev_spi/ProviderSpi.cpp | 41 + libsrc/leddevice/dev_spi/ProviderSpi.h | 6 + libsrc/leddevice/schemas/schema-adalight.json | 18 +- libsrc/leddevice/schemas/schema-apa102.json | 2 - libsrc/leddevice/schemas/schema-apa104.json | 1 - libsrc/leddevice/schemas/schema-atmoorb.json | 89 +- .../leddevice/schemas/schema-cololight.json | 58 +- libsrc/leddevice/schemas/schema-lpd6803.json | 1 - libsrc/leddevice/schemas/schema-lpd8806.json | 1 - libsrc/leddevice/schemas/schema-nanoleaf.json | 132 +- libsrc/leddevice/schemas/schema-p9813.json | 1 - .../leddevice/schemas/schema-philipshue.json | 481 ++-- .../leddevice/schemas/schema-sk6812spi.json | 1 - .../leddevice/schemas/schema-sk6822spi.json | 1 - libsrc/leddevice/schemas/schema-sk9822.json | 1 - libsrc/leddevice/schemas/schema-wled.json | 110 +- libsrc/leddevice/schemas/schema-ws2801.json | 1 - .../leddevice/schemas/schema-ws2812spi.json | 1 - libsrc/leddevice/schemas/schema-yeelight.json | 352 ++- src/hyperiond/hyperiond.cpp | 4 +- 48 files changed, 4296 insertions(+), 2567 deletions(-) mode change 100644 => 100755 assets/webconfig/js/content_colors.js mode change 100644 => 100755 assets/webconfig/js/content_index.js mode change 100644 => 100755 assets/webconfig/js/content_leds.js mode change 100644 => 100755 assets/webconfig/js/ui_utils.js mode change 100644 => 100755 assets/webconfig/js/wizard.js create mode 100644 include/utils/version.hpp diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 3d064eb2..3b00f57d 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -41,7 +41,7 @@ jobs: shell: bash run: | tr -d '\n' < version > temp && mv temp version - echo -n -PR#${{ github.event.pull_request.number }} >> version + echo -n "+PR${{ github.event.pull_request.number }}" >> version # Build packages - name: Build packages @@ -87,7 +87,7 @@ jobs: shell: bash run: | tr -d '\n' < version > temp && mv temp version - echo -n "-PR#${{ github.event.pull_request.number }}" >> version + echo -n "+PR${{ github.event.pull_request.number }}" >> version # Install dependencies - name: Install dependencies @@ -136,7 +136,7 @@ jobs: shell: bash run: | tr -d '\n' < version > temp && mv temp version - echo -n "-PR#${{ github.event.pull_request.number }}" >> version + echo -n "+PR${{ github.event.pull_request.number }}" >> version - name: Cache Qt uses: actions/cache@v2 diff --git a/assets/webconfig/content/conf_leds.html b/assets/webconfig/content/conf_leds.html index eca4412f..60e2d16e 100755 --- a/assets/webconfig/content/conf_leds.html +++ b/assets/webconfig/content/conf_leds.html @@ -1,374 +1,382 @@ + + + + Hyperion - LED Device Configuration +
- - + + -
+
+ - - -
+
+
+
+
+
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
LEDs
+
+ + + +
LEDs
+
+ + + +
LEDs
+
+ + + +
LEDs
+
+ + + +
LEDs
+
+ + + +
+ + + +
+ + +
+ + +
+
+
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
%
+
+ + + +
%
+
+ + + +
%
+
+ + + +
%
+
+ + + +
%h
+
+ +
%v
+
+ + + +
%
+
+ +
%
+
+ + + +
%
+
+ +
%
+
+ + + +
%
+
+ +
%
+
+
+
+
+
+ +
+
+
+ +
+
+ + + + + + + + + + + + + + + + + + + +
+ + + +
LEDs
+
+ + + +
LEDs
+
+ + + +
+ + + +
+
+ +
+
+
+
+

+ Blacklist LEDs +

+
+
+
+
+
+ +
+
+
+ +
+
+

This textfield shows by default your current loaded layout and will be overwritten if you generate a new one above. Optional you could perform further edits.

+
+
+ +
+
+
+
+
+
+
+

LED Layout preview

+
+
+

+

+

+
+
+
+
+
+ +
+
+
+
+
diff --git a/assets/webconfig/i18n/en.json b/assets/webconfig/i18n/en.json index f57e13cf..20beaeb8 100644 --- a/assets/webconfig/i18n/en.json +++ b/assets/webconfig/i18n/en.json @@ -1,15 +1,4 @@ { - "InfoDialog_access_text": "Depending on settings level you could adjust more options or get access to more features. Recommended is the \"Default\" level.", - "InfoDialog_access_title": "Settings level", - "InfoDialog_changePassword_success": "Password successfully saved!", - "InfoDialog_changePassword_title": "Change Password", - "InfoDialog_iswitch_text": "If you run Hyperion more than once in your local network, you could switch between the web configurations. Select the Hyperion instance below and switch!", - "InfoDialog_iswitch_title": "Hyperion switcher", - "InfoDialog_lang_text": "If you don't like the result of the automatic language detection you could overwrite it here.", - "InfoDialog_lang_title": "Language setting", - "InfoDialog_nowrite_foottext": "The WebUI will be unlocked automatically after you solved the problem!", - "InfoDialog_nowrite_text": "Hyperion can't write to your current loaded configuration file. Please repair the file permissions to proceed.", - "InfoDialog_nowrite_title": "write permission error!", "about_3rd_party_licenses": "3rd party licenses", "about_3rd_party_licenses_error": "We had trouble collecting 3rd party licenses information from web.
Please follow this link to the GitHub Resource.", "about_build": "Build", @@ -45,9 +34,19 @@ "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", + "conf_leds_config_error": "Error in LED/LED layout configuration", + "conf_leds_config_warning": "Check your LED/LED layout configuration", "conf_leds_contr_label_contrtype": "Controller type:", + "conf_leds_device_info_log": "In case your LEDs do not work, check here for errors:", "conf_leds_device_intro": "Hyperion supports a lot of controllers to transmit data to your target device. Select a LED controller out of the sorted list and configure it. We have chosen the best default settings for each device.", + "conf_leds_error_hwled_gt_layout": "The hardware LED count ($1) is greater than LEDs configured via layout ($2),
$3 {{plural:$1|LED|LEDs}} will stay black if you continue.", + "conf_leds_error_hwled_lt_layout": "The hardware LED count ($1) is less than LEDs configured via layout ($2).
The number of LEDs configured in the layout must not exceed the available LEDs", "conf_leds_layout_advanced": "Advanced Settings", + "conf_leds_layout_blacklist_num_title": "Number of LEDs", + "conf_leds_layout_blacklist_rule_title": "Blacklist rule", + "conf_leds_layout_blacklist_rules_title": "Blacklist rules", + "conf_leds_layout_blacklist_start_title": "Start LED", + "conf_leds_layout_blacklistleds_title": "Blacklist LEDs", "conf_leds_layout_btn_checklist": "Show checklist", "conf_leds_layout_button_savelay": "Save Layout", "conf_leds_layout_button_updsim": "Update Preview", @@ -113,11 +112,12 @@ "conf_leds_layout_textf1": "This text field shows by default your current loaded layout and will be overwritten if you generate a new one with the options above. Optional you could perform further edits.", "conf_leds_nav_label_ledcontroller": "LED Controller", "conf_leds_nav_label_ledlayout": "LED Layout", + "conf_leds_note_layout_overwrite": "Note: Overwrite creates a default layout for {{plural:$1| $1 LED| all $1 LEDs}} given by the hardware LED count", + "conf_leds_optgroup_network": "Network", + "conf_leds_optgroup_other": "Other", "conf_leds_optgroup_RPiGPIO": "RPi GPIO", "conf_leds_optgroup_RPiPWM": "RPi PWM", "conf_leds_optgroup_RPiSPI": "RPi SPI", - "conf_leds_optgroup_debug": "Debug", - "conf_leds_optgroup_network": "Network", "conf_leds_optgroup_usb": "USB/Serial", "conf_logging_btn_autoscroll": "Auto scrolling", "conf_logging_btn_pbupload": "Upload a report for support requests", @@ -215,10 +215,10 @@ "edt_conf_color_black_title": "Black", "edt_conf_color_blue_expl": "The calibrated blue value.", "edt_conf_color_blue_title": "Blue", - "edt_conf_color_brightnessComp_expl": "Compensates brightness differences between red green blue, cyan magenta yellow and white. 100 means full compensation, 0 no compensation", - "edt_conf_color_brightnessComp_title": "Brightness compensation", "edt_conf_color_brightness_expl": "set overall brightness of LEDs", "edt_conf_color_brightness_title": "Brightness", + "edt_conf_color_brightnessComp_expl": "Compensates brightness differences between red green blue, cyan magenta yellow and white. 100 means full compensation, 0 no compensation", + "edt_conf_color_brightnessComp_title": "Brightness compensation", "edt_conf_color_channelAdjustment_header_expl": "Create color profiles that could be assigned to a specific component. Adjust color, gamma, brightness, compensation and more.", "edt_conf_color_channelAdjustment_header_itemtitle": "Profile", "edt_conf_color_channelAdjustment_header_title": "Color channel adjustments", @@ -254,10 +254,6 @@ "edt_conf_effp_paths_expl": "You could define more folders that contain effects. The effect configurator will always save inside the first folder.", "edt_conf_effp_paths_itemtitle": "Path", "edt_conf_effp_paths_title": "Effect Path(s)", - "edt_conf_enum_NO_CHANGE": "Automatic", - "edt_conf_enum_NTSC": "NTSC", - "edt_conf_enum_PAL": "PAL", - "edt_conf_enum_SECAM": "SECAM", "edt_conf_enum_automatic": "Automatic", "edt_conf_enum_bbclassic": "Classic", "edt_conf_enum_bbdefault": "Default", @@ -288,9 +284,14 @@ "edt_conf_enum_logverbose": "Verbose", "edt_conf_enum_logwarn": "Warning", "edt_conf_enum_multicolor_mean": "Multicolor", + "edt_conf_enum_NO_CHANGE": "Automatic", + "edt_conf_enum_NTSC": "NTSC", + "edt_conf_enum_PAL": "PAL", + "edt_conf_enum_please_select": "Please Select", "edt_conf_enum_rbg": "RBG", "edt_conf_enum_rgb": "RGB", "edt_conf_enum_right_left": "Right to left", + "edt_conf_enum_SECAM": "SECAM", "edt_conf_enum_top_down": "Top down", "edt_conf_enum_transeffect_smooth": "Smooth", "edt_conf_enum_transeffect_sudden": "Sudden", @@ -322,11 +323,12 @@ "edt_conf_fge_type_title": "Type", "edt_conf_fw_flat_expl": "One flatbuffer target per line. Contains IP:PORT (Example: 127.0.0.1:19401)", "edt_conf_fw_flat_itemtitle": "flatbuffer target", - "edt_conf_fw_flat_title": "List of flatbuffer clients", + "edt_conf_fw_flat_title": "List of flatbuffer targets", "edt_conf_fw_heading_title": "Forwarder", "edt_conf_fw_json_expl": "One json target per line. Contains IP:PORT (Example: 127.0.0.1:19446)", "edt_conf_fw_json_itemtitle": "Json target", - "edt_conf_fw_json_title": "List of json clients", + "edt_conf_fw_json_title": "List of json targets", + "edt_conf_gen_configVersion_title": "Configuration version", "edt_conf_gen_heading_title": "General Settings", "edt_conf_gen_name_expl": "A user defined name which is used to detect Hyperion. (Helpful with more than one Hyperion instance)", "edt_conf_gen_name_title": "Configuration name", @@ -354,9 +356,9 @@ "edt_conf_net_heading_title": "Network", "edt_conf_net_internetAccessAPI_expl": "Allow access to the Hyperion API/Webinterface from the internet, disable for higher security.", "edt_conf_net_internetAccessAPI_title": "Internet API Access", + "edt_conf_net_ip_itemtitle": "IP", "edt_conf_net_ipWhitelist_expl": "You can whitelist IP addresses instead allowing all connections from internet to connect to the Hyperion API/Webinterface.", "edt_conf_net_ipWhitelist_title": "Whitelisted IP's", - "edt_conf_net_ip_itemtitle": "IP", "edt_conf_net_localAdminAuth_expl": "When enabled, administration access from your local network needs a password.", "edt_conf_net_localAdminAuth_title": "Local Admin API Authentication", "edt_conf_net_localApiAuth_expl": "When enabled, connections from your home network needs to authenticate themself against Hyperion with a token.", @@ -436,25 +438,25 @@ "edt_conf_webc_sslport_expl": "Port oft the HTTPS-Webserver", "edt_conf_webc_sslport_title": "HTTPS Port", "edt_dev_auth_key_title": "Authentication Token", + "edt_dev_auth_key_title_info": "Authentication Token required to acccess the device", "edt_dev_enum_sub_min_cool_adjust": "Subtract cool white", "edt_dev_enum_sub_min_warm_adjust": "Subtract warm white", "edt_dev_enum_subtract_minimum": "Subtract minimum", "edt_dev_enum_white_off": "White off", "edt_dev_general_colorOrder_title": "RGB byte order", + "edt_dev_general_colorOrder_title_info": "The device's color order", "edt_dev_general_hardwareLedCount_title": "Hardware LED count", + "edt_dev_general_hardwareLedCount_title_info": "The number of physical LEDs availabe for the given device", "edt_dev_general_heading_title": "General Settings", "edt_dev_general_name_title": "Configuration name", "edt_dev_general_rewriteTime_title": "Refresh time", - "edt_dev_spec_FCledToOn_title": "Fadecandy LED set to on", - "edt_dev_spec_FCmanualControl_title": "Manual control of fadecandy LED", - "edt_dev_spec_FCsetConfig_title": "Set fadecandy configuration", - "edt_dev_spec_LBap102Mode_title": "LightBerry APA102 Mode", - "edt_dev_spec_PBFiFo_title": "Pi-Blaster FiFo", "edt_dev_spec_baudrate_title": "Baudrate", "edt_dev_spec_blackLightsTimeout_title": "Signal detection timeout on black", + "edt_dev_spec_brightness_title": "Brightness", "edt_dev_spec_brightnessFactor_title": "Brightness factor", "edt_dev_spec_brightnessMax_title": "Brightness maximum", "edt_dev_spec_brightnessMin_title": "Brightness minimum", + "edt_dev_spec_brightnessOverwrite_title": "Overwrite brightness", "edt_dev_spec_brightnessThreshold_title": "Signal detection brightness minimum", "edt_dev_spec_chanperfixture_title": "Channels per Fixture", "edt_dev_spec_cid_title": "CID", @@ -463,8 +465,16 @@ "edt_dev_spec_debugLevel_title": "Debug Level", "edt_dev_spec_debugStreamer_title": "Streamer Debug", "edt_dev_spec_delayAfterConnect_title": "Delay after connect", + "edt_dev_spec_devices_discovered_none": "No Devices Discovered", + "edt_dev_spec_devices_discovered_title": "Devices Discovered", + "edt_dev_spec_devices_discovered_title_info": "Select your LED-Device discovered", + "edt_dev_spec_devices_discovered_title_info_custom": "Select your LED-Device discovered or configure a custome one", + "edt_dev_spec_devices_discovery_inprogress": "Discovery in progress", "edt_dev_spec_dithering_title": "Dithering", "edt_dev_spec_dmaNumber_title": "DMA channel", + "edt_dev_spec_FCledToOn_title": "Fadecandy LED set to on", + "edt_dev_spec_FCmanualControl_title": "Manual control of fadecandy LED", + "edt_dev_spec_FCsetConfig_title": "Set fadecandy configuration", "edt_dev_spec_gamma_title": "Gamma", "edt_dev_spec_globalBrightnessControlMaxLevel_title": "Max Current Level", "edt_dev_spec_globalBrightnessControlThreshold_title": "Adaptive Current Threshold", @@ -477,6 +487,8 @@ "edt_dev_spec_intervall_title": "Interval", "edt_dev_spec_invert_title": "Invert signal", "edt_dev_spec_latchtime_title": "Latch time", + "edt_dev_spec_latchtime_title_info": "Latch time is the time-frame a device requires until the next update can be processed. During that time-frame any updates done via ignored.", + "edt_dev_spec_LBap102Mode_title": "LightBerry APA102 Mode", "edt_dev_spec_ledIndex_title": "LED index", "edt_dev_spec_ledType_title": "LED Type", "edt_dev_spec_lightid_itemtitle": "ID", @@ -484,8 +496,8 @@ "edt_dev_spec_lights_itemtitle": "Light", "edt_dev_spec_lights_name": "Name", "edt_dev_spec_lights_title": "Light(s)", - "edt_dev_spec_maxPacket_title": "Max packet", "edt_dev_spec_maximumLedCount_title": "Maximum LED count", + "edt_dev_spec_maxPacket_title": "Max packet", "edt_dev_spec_multicastGroup_title": "Multicast group", "edt_dev_spec_networkDeviceName_title": "Network devicename", "edt_dev_spec_networkDevicePort_title": "Port", @@ -496,23 +508,27 @@ "edt_dev_spec_outputPath_title": "Output path", "edt_dev_spec_panel_start_position": "Start panel [0-max panels]", "edt_dev_spec_panelorganisation_title": "Panel numbering sequence", + "edt_dev_spec_PBFiFo_title": "Pi-Blaster FiFo", "edt_dev_spec_pid_title": "PID", "edt_dev_spec_port_title": "Port", "edt_dev_spec_printTimeStamp_title": "Add timestamp", "edt_dev_spec_pwmChannel_title": "PWM channel", - "edt_dev_spec_restoreOriginalState_title": "Restore lights' original state when disabled", + "edt_dev_spec_restoreOriginalState_title": "Restore lights' state", + "edt_dev_spec_restoreOriginalState_title_info": "Restore the device's original state when device is disabled", "edt_dev_spec_serial_title": "Serial number", - "edt_dev_spec_spipath_title": "SPI path", + "edt_dev_spec_spipath_title": "SPI Device", "edt_dev_spec_sslHSTimeoutMax_title": "Streamer handshake timeout maximum", "edt_dev_spec_sslHSTimeoutMin_title": "Streamer handshake timeout minimum", "edt_dev_spec_sslReadTimeout_title": "Streamer read timeout", - "edt_dev_spec_switchOffOnBlack_title": "Switch off on black", "edt_dev_spec_switchOffOnbelowMinBrightness_title": "Switch-off, below minimum", - "edt_dev_spec_targetIpHost_title": "Target IP/Hostname", - "edt_dev_spec_targetIp_title": "Target IP", + "edt_dev_spec_switchOffOnBlack_title": "Switch off on black", + "edt_dev_spec_syncOverwrite_title": "Disable synchronisation", + "edt_dev_spec_targetIp_title": "Target IP-address", + "edt_dev_spec_targetIpHost_title": "Target Hostname/IP-address", + "edt_dev_spec_targetIpHost_title_info": "The device's hostname or IP-address", "edt_dev_spec_transeffect_title": "Transition effect", - "edt_dev_spec_transistionTimeExtra_title": "Extra time darkness", "edt_dev_spec_transistionTime_title": "Transition time", + "edt_dev_spec_transistionTimeExtra_title": "Extra time darkness", "edt_dev_spec_uid_title": "UID", "edt_dev_spec_universe_title": "Universe", "edt_dev_spec_useEntertainmentAPI_title": "Use Hue Entertainment API", @@ -538,16 +554,16 @@ "edt_eff_collision_header": "color collision", "edt_eff_collision_header_desc": "Two color projectiles are sent from random positions and collide with each other", "edt_eff_color": "Color", - "edt_eff_colorHour": "Color hour", - "edt_eff_colorMarker": "Marker color", - "edt_eff_colorMinute": "Color minute", - "edt_eff_colorSecond": "Color second", "edt_eff_colorcount": "Color length", "edt_eff_colorend": "Color end", "edt_eff_colorendtime": "Time to hold start color", "edt_eff_colorevel": "Color level", + "edt_eff_colorHour": "Color hour", + "edt_eff_colorMarker": "Marker color", + "edt_eff_colorMinute": "Color minute", "edt_eff_colorone": "Color one", "edt_eff_colorrandom": "Random color", + "edt_eff_colorSecond": "Color second", "edt_eff_colorshift": "Color Shift", "edt_eff_colorstart": "Color start", "edt_eff_colorstarttime": "Time to hold end color", @@ -603,13 +619,13 @@ "edt_eff_postcolor": "Post color", "edt_eff_rainbowmood_header": "Rainbow Mood", "edt_eff_rainbowmood_header_desc": "All LEDs rainbow mood", - "edt_eff_randomCenter": "Random Center", "edt_eff_random_header": "Random", "edt_eff_random_header_desc": "Pixel Dot, dot, dot...", + "edt_eff_randomCenter": "Random Center", "edt_eff_repeat": "Repeat", "edt_eff_repeatcount": "Repeat count", - "edt_eff_reverseRandomTime": "Reverse every", "edt_eff_reversedirection": "Reverse direction", + "edt_eff_reverseRandomTime": "Reverse every", "edt_eff_rotationtime": "Rotation time", "edt_eff_saturation": "Saturation", "edt_eff_showseconds": "Show seconds", @@ -647,23 +663,23 @@ "edt_msg_button_expand": "Expand", "edt_msg_button_move_down_title": "Move down", "edt_msg_button_move_up_title": "Move up", - "edt_msg_error_additionalItems": "No additional items allowed in this array", "edt_msg_error_additional_properties": "No additional properties allowed, but property $1 is set", + "edt_msg_error_additionalItems": "No additional items allowed in this array", "edt_msg_error_anyOf": "Value must validate against at least one of the provided schemas", "edt_msg_error_dependency": "Must have property $1", "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_maximum_excl": "Value must be less than $1", + "edt_msg_error_maximum_incl": "Value must be at most $1", "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", - "edt_msg_error_maximum_excl": "Value must be less than $1", - "edt_msg_error_maximum_incl": "Value must be at most $1", + "edt_msg_error_minimum_excl": "Value must be greater than $1", + "edt_msg_error_minimum_incl": "Value must be at least $1", "edt_msg_error_minItems": "Value must have at least $1 items", "edt_msg_error_minLength": "Value must be at least $1 characters long", "edt_msg_error_minProperties": "Object must have at least $1 properties", - "edt_msg_error_minimum_excl": "Value must be greater than $1", - "edt_msg_error_minimum_incl": "Value must be at least $1", "edt_msg_error_multipleOf": "Value must be a multiple of $1", "edt_msg_error_not": "Value must not validate against the provided schema", "edt_msg_error_notempty": "Value required", @@ -698,6 +714,7 @@ "general_btn_off": "Off", "general_btn_ok": "OK", "general_btn_on": "On", + "general_btn_overwrite": "Overwrite", "general_btn_rename": "Rename", "general_btn_restarthyperion": "Restart Hyperion", "general_btn_save": "Save", @@ -745,20 +762,6 @@ "general_speech_zh-CN": "Chinese (simplified)", "general_webui_title": "Hyperion - Web Configuration", "general_wiki_moreto": "More information to \"$1\" at our Wiki", - "infoDialog_checklist_title": "Checklist!", - "infoDialog_effconf_created_text": "The effect \"$1\" has been created successfully!", - "infoDialog_effconf_deleted_text": "The effect \"$1\" has been deleted successfully!", - "infoDialog_general_error_title": "Error", - "infoDialog_general_success_title": "Success", - "infoDialog_general_warning_title": "Warning", - "infoDialog_import_comperror_text": "Sad! Your browser doesn't support a import. Please try again with another browser.", - "infoDialog_import_confirm_text": "Are you sure to import \"$1\"? This process can't be reverted!", - "infoDialog_import_confirm_title": "Confirm import", - "infoDialog_import_hyperror_text": "The selected configuration file \"$1\" can't be imported. It's not compatible with Hyperion 2.0 and higher!", - "infoDialog_import_jsonerror_text": "The selected configuration file \"$1\" is no .json file or it's corrupted. Error message: ($2)", - "infoDialog_wizrgb_text": "Your RGB Byte Order is already well adjusted.", - "infoDialog_writeconf_error_text": "Saving your configuration failed.", - "infoDialog_writeimage_error_text": "The selected file \"$1\" is no image file or it's corrupted! Please select another image file.", "info_404": "The page you requested is not available!", "info_conlost_label_autorecon": "We reconnect again after Hyperion is available.", "info_conlost_label_autorefresh": "This page will be automatically refreshed.", @@ -772,6 +775,31 @@ "info_restart_contusa": "...with your last steps. Thank you!", "info_restart_rightback": "Hyperion will be right back immediately!", "info_restart_title": "Restarts currently...", + "InfoDialog_access_text": "Depending on settings level you could adjust more options or get access to more features. Recommended is the \"Default\" level.", + "InfoDialog_access_title": "Settings level", + "InfoDialog_changePassword_success": "Password successfully saved!", + "InfoDialog_changePassword_title": "Change Password", + "infoDialog_checklist_title": "Checklist!", + "infoDialog_effconf_created_text": "The effect \"$1\" has been created successfully!", + "infoDialog_effconf_deleted_text": "The effect \"$1\" has been deleted successfully!", + "infoDialog_general_error_title": "Error", + "infoDialog_general_success_title": "Success", + "infoDialog_general_warning_title": "Warning", + "infoDialog_import_comperror_text": "Sad! Your browser doesn't support a import. Please try again with another browser.", + "infoDialog_import_confirm_text": "Are you sure to import \"$1\"? This process can't be reverted!", + "infoDialog_import_confirm_title": "Confirm import", + "infoDialog_import_hyperror_text": "The selected configuration file \"$1\" can't be imported. It's not compatible with Hyperion 2.0 and higher!", + "infoDialog_import_jsonerror_text": "The selected configuration file \"$1\" is no .json file or it's corrupted. Error message: ($2)", + "InfoDialog_iswitch_text": "If you run Hyperion more than once in your local network, you could switch between the web configurations. Select the Hyperion instance below and switch!", + "InfoDialog_iswitch_title": "Hyperion switcher", + "InfoDialog_lang_text": "If you don't like the result of the automatic language detection you could overwrite it here.", + "InfoDialog_lang_title": "Language setting", + "InfoDialog_nowrite_foottext": "The WebUI will be unlocked automatically after you solved the problem!", + "InfoDialog_nowrite_text": "Hyperion can't write to your current loaded configuration file. Please repair the file permissions to proceed.", + "InfoDialog_nowrite_title": "write permission error!", + "infoDialog_wizrgb_text": "Your RGB Byte Order is already well adjusted.", + "infoDialog_writeconf_error_text": "Saving your configuration failed.", + "infoDialog_writeimage_error_text": "The selected file \"$1\" is no image file or it's corrupted! Please select another image file.", "main_ledsim_btn_togglelednumber": "LED numbers", "main_ledsim_btn_toggleleds": "Show LEDs", "main_ledsim_btn_togglelivevideo": "Live video", diff --git a/assets/webconfig/index.html b/assets/webconfig/index.html index 12854f57..3ed6fa9a 100644 --- a/assets/webconfig/index.html +++ b/assets/webconfig/index.html @@ -101,7 +101,7 @@ - Redefine ambient light! + Redefine ambient light! @@ -234,7 +234,7 @@ System diff --git a/assets/webconfig/js/content_colors.js b/assets/webconfig/js/content_colors.js old mode 100644 new mode 100755 index 87428047..3800cc26 --- a/assets/webconfig/js/content_colors.js +++ b/assets/webconfig/js/content_colors.js @@ -1,84 +1,80 @@ -$(document).ready( function() { - performTranslation(); - var editor_color = null; - var editor_smoothing = null; - var editor_blackborder = null; - - if(window.showOptHelp) - { - //color - $('#conf_cont').append(createRow('conf_cont_color')); - $('#conf_cont_color').append(createOptPanel('fa-photo', $.i18n("edt_conf_color_heading_title"), 'editor_container_color', 'btn_submit_color')); - $('#conf_cont_color').append(createHelpTable(window.schema.color.properties, $.i18n("edt_conf_color_heading_title"))); - - //smoothing - $('#conf_cont').append(createRow('conf_cont_smoothing')); - $('#conf_cont_smoothing').append(createOptPanel('fa-photo', $.i18n("edt_conf_smooth_heading_title"), 'editor_container_smoothing', 'btn_submit_smoothing')); - $('#conf_cont_smoothing').append(createHelpTable(window.schema.smoothing.properties, $.i18n("edt_conf_smooth_heading_title"))); - - //blackborder - $('#conf_cont').append(createRow('conf_cont_blackborder')); - $('#conf_cont_blackborder').append(createOptPanel('fa-photo', $.i18n("edt_conf_bb_heading_title"), 'editor_container_blackborder', 'btn_submit_blackborder')); - $('#conf_cont_blackborder').append(createHelpTable(window.schema.blackborderdetector.properties, $.i18n("edt_conf_bb_heading_title"))); - } - else - { - $('#conf_cont').addClass('row'); - $('#conf_cont').append(createOptPanel('fa-photo', $.i18n("edt_conf_color_heading_title"), 'editor_container_color', 'btn_submit_color')); - $('#conf_cont').append(createOptPanel('fa-photo', $.i18n("edt_conf_smooth_heading_title"), 'editor_container_smoothing', 'btn_submit_smoothing')); - $('#conf_cont').append(createOptPanel('fa-photo', $.i18n("edt_conf_bb_heading_title"), 'editor_container_blackborder', 'btn_submit_blackborder')); - } - - //color - editor_color = createJsonEditor('editor_container_color', { - color : window.schema.color - }, true, true); +$(document).ready(function () { + performTranslation(); + var editor_color = null; + var editor_smoothing = null; + var editor_blackborder = null; - editor_color.on('change',function() { - editor_color.validate().length || window.readOnlyMode ? $('#btn_submit_color').attr('disabled', true) : $('#btn_submit_color').attr('disabled', false); - }); - - $('#btn_submit_color').off().on('click',function() { - requestWriteConfig(editor_color.getValue()); - }); - - //smoothing - editor_smoothing = createJsonEditor('editor_container_smoothing', { - smoothing : window.schema.smoothing - }, true, true); + if (window.showOptHelp) { + //color + $('#conf_cont').append(createRow('conf_cont_color')); + $('#conf_cont_color').append(createOptPanel('fa-photo', $.i18n("edt_conf_color_heading_title"), 'editor_container_color', 'btn_submit_color')); + $('#conf_cont_color').append(createHelpTable(window.schema.color.properties, $.i18n("edt_conf_color_heading_title"))); - editor_smoothing.on('change',function() { - editor_smoothing.validate().length || window.readOnlyMode ? $('#btn_submit_smoothing').attr('disabled', true) : $('#btn_submit_smoothing').attr('disabled', false); - - }); - - $('#btn_submit_smoothing').off().on('click',function() { - requestWriteConfig(editor_smoothing.getValue()); - }); + //smoothing + $('#conf_cont').append(createRow('conf_cont_smoothing')); + $('#conf_cont_smoothing').append(createOptPanel('fa-photo', $.i18n("edt_conf_smooth_heading_title"), 'editor_container_smoothing', 'btn_submit_smoothing')); + $('#conf_cont_smoothing').append(createHelpTable(window.schema.smoothing.properties, $.i18n("edt_conf_smooth_heading_title"))); - //blackborder - editor_blackborder = createJsonEditor('editor_container_blackborder', { - blackborderdetector: window.schema.blackborderdetector - }, true, true); + //blackborder + $('#conf_cont').append(createRow('conf_cont_blackborder')); + $('#conf_cont_blackborder').append(createOptPanel('fa-photo', $.i18n("edt_conf_bb_heading_title"), 'editor_container_blackborder', 'btn_submit_blackborder')); + $('#conf_cont_blackborder').append(createHelpTable(window.schema.blackborderdetector.properties, $.i18n("edt_conf_bb_heading_title"))); + } + else { + $('#conf_cont').addClass('row'); + $('#conf_cont').append(createOptPanel('fa-photo', $.i18n("edt_conf_color_heading_title"), 'editor_container_color', 'btn_submit_color')); + $('#conf_cont').append(createOptPanel('fa-photo', $.i18n("edt_conf_smooth_heading_title"), 'editor_container_smoothing', 'btn_submit_smoothing')); + $('#conf_cont').append(createOptPanel('fa-photo', $.i18n("edt_conf_bb_heading_title"), 'editor_container_blackborder', 'btn_submit_blackborder')); + } - editor_blackborder.on('change',function() { - editor_blackborder.validate().length || window.readOnlyMode ? $('#btn_submit_blackborder').attr('disabled', true) : $('#btn_submit_blackborder').attr('disabled', false); - }); - - $('#btn_submit_blackborder').off().on('click',function() { - requestWriteConfig(editor_blackborder.getValue()); - }); - - //wiki links - $('#editor_container_blackborder').append(buildWL("user/moretopics/bbmode","edt_conf_bb_mode_title",true)); - - //create introduction - if(window.showOptHelp) - { - createHint("intro", $.i18n('conf_colors_color_intro'), "editor_container_color"); - createHint("intro", $.i18n('conf_colors_smoothing_intro'), "editor_container_smoothing"); - createHint("intro", $.i18n('conf_colors_blackborder_intro'), "editor_container_blackborder"); - } - - removeOverlay(); + //color + editor_color = createJsonEditor('editor_container_color', { + color: window.schema.color + }, true, true); + + editor_color.on('change', function () { + editor_color.validate().length || window.readOnlyMode ? $('#btn_submit_color').attr('disabled', true) : $('#btn_submit_color').attr('disabled', false); + }); + + $('#btn_submit_color').off().on('click', function () { + requestWriteConfig(editor_color.getValue()); + }); + + //smoothing + editor_smoothing = createJsonEditor('editor_container_smoothing', { + smoothing: window.schema.smoothing + }, true, true); + + editor_smoothing.on('change', function () { + editor_smoothing.validate().length || window.readOnlyMode ? $('#btn_submit_smoothing').attr('disabled', true) : $('#btn_submit_smoothing').attr('disabled', false); + }); + + $('#btn_submit_smoothing').off().on('click', function () { + requestWriteConfig(editor_smoothing.getValue()); + }); + + //blackborder + editor_blackborder = createJsonEditor('editor_container_blackborder', { + blackborderdetector: window.schema.blackborderdetector + }, true, true); + + editor_blackborder.on('change', function () { + editor_blackborder.validate().length || window.readOnlyMode ? $('#btn_submit_blackborder').attr('disabled', true) : $('#btn_submit_blackborder').attr('disabled', false); + }); + + $('#btn_submit_blackborder').off().on('click', function () { + requestWriteConfig(editor_blackborder.getValue()); + }); + + //wiki links + $('#editor_container_blackborder').append(buildWL("user/advanced/Advanced.html#blackbar-detection", "edt_conf_bb_mode_title", true)); + + //create introduction + if (window.showOptHelp) { + createHint("intro", $.i18n('conf_colors_color_intro'), "editor_container_color"); + createHint("intro", $.i18n('conf_colors_smoothing_intro'), "editor_container_smoothing"); + createHint("intro", $.i18n('conf_colors_blackborder_intro'), "editor_container_blackborder"); + } + + removeOverlay(); }); diff --git a/assets/webconfig/js/content_index.js b/assets/webconfig/js/content_index.js old mode 100644 new mode 100755 index c57f1626..c2263509 --- a/assets/webconfig/js/content_index.js +++ b/assets/webconfig/js/content_index.js @@ -1,354 +1,357 @@ var instNameInit = false $(document).ready(function () { + var darkModeOverwrite = getStorage("darkModeOverwrite", true); - var darkModeOverwrite = getStorage("darkModeOverwrite", true); + if (darkModeOverwrite == "false" || darkModeOverwrite == null) { + if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) { + handleDarkMode(); + } - if(darkModeOverwrite == "false" || darkModeOverwrite == null) - { - if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) { - handleDarkMode(); - } - - if (window.matchMedia && window.matchMedia('(prefers-color-scheme: light)').matches) { - setStorage("darkMode", "off", false); - } - } - - if(getStorage("darkMode", false) == "on") - { - handleDarkMode(); - } + if (window.matchMedia && window.matchMedia('(prefers-color-scheme: light)').matches) { + setStorage("darkMode", "off", false); + } + } - loadContentTo("#container_connection_lost", "connection_lost"); - loadContentTo("#container_restart", "restart"); - initWebSocket(); + if (getStorage("darkMode", false) == "on") { + handleDarkMode(); + } - $(window.hyperion).on("cmd-serverinfo", function (event) { - window.serverInfo = event.response.info; - - window.readOnlyMode = window.sysInfo.hyperion.readOnlyMode; - - // comps - window.comps = event.response.info.components + loadContentTo("#container_connection_lost", "connection_lost"); + loadContentTo("#container_restart", "restart"); + initWebSocket(); - $(window.hyperion).trigger("ready"); + $(window.hyperion).on("cmd-serverinfo", function (event) { + window.serverInfo = event.response.info; - window.comps.forEach(function (obj) { - if (obj.name == "ALL") { - if (obj.enabled) - $("#hyperion_disabled_notify").fadeOut("fast"); - else - $("#hyperion_disabled_notify").fadeIn("fast"); - } - }); + window.readOnlyMode = window.sysInfo.hyperion.readOnlyMode; - // determine button visibility - var running = window.serverInfo.instance.filter(entry => entry.running); - if (running.length > 1) - $('#btn_hypinstanceswitch').toggle(true) - else - $('#btn_hypinstanceswitch').toggle(false) - // update listing at button - updateHyperionInstanceListing() - if (!instNameInit) { - window.currentHyperionInstanceName = getInstanceNameByIndex(0); - instNameInit = true; - } + // comps + window.comps = event.response.info.components - updateSessions(); - }); // end cmd-serverinfo + $(window.hyperion).trigger("ready"); - // Update language selection - $("#language-select").on('changed.bs.select',function (e, clickedIndex, isSelected, previousValue){ - var newLang = availLang[clickedIndex-1]; - if (newLang !== storedLang) - { - setStorage("langcode", newLang); - reload(); - } - }); + window.comps.forEach(function (obj) { + if (obj.name == "ALL") { + if (obj.enabled) + $("#hyperion_disabled_notify").fadeOut("fast"); + else + $("#hyperion_disabled_notify").fadeIn("fast"); + } + }); - $("#language-select").selectpicker( - { - container: 'body' - }); + // determine button visibility + var running = window.serverInfo.instance.filter(entry => entry.running); + if (running.length > 1) + $('#btn_hypinstanceswitch').toggle(true) + else + $('#btn_hypinstanceswitch').toggle(false) + // update listing at button + updateHyperionInstanceListing() + if (!instNameInit) { + window.currentHyperionInstanceName = getInstanceNameByIndex(0); + instNameInit = true; + } - $(".bootstrap-select").click(function () { - $(this).addClass("open"); - }); + updateSessions(); + }); // end cmd-serverinfo - $(document).click(function(){ - $(".bootstrap-select").removeClass("open"); - }); + // Update language selection + $("#language-select").on('changed.bs.select', function (e, clickedIndex, isSelected, previousValue) { + var newLang = availLang[clickedIndex - 1]; + if (newLang !== storedLang) { + setStorage("langcode", newLang); + reload(); + } + }); - $(".bootstrap-select").click(function(e){ - e.stopPropagation(); - }); - - //End language selection - - $(window.hyperion).on("cmd-sessions-update", function (event) { - window.serverInfo.sessions = event.response.data; - updateSessions(); - }); + $("#language-select").selectpicker( + { + container: 'body' + }); - $(window.hyperion).on("cmd-authorize-tokenRequest cmd-authorize-getPendingTokenRequests", function (event) { - var val = event.response.info; - if (Array.isArray(event.response.info)) { - if (event.response.info.length == 0) { - return - } - val = event.response.info[0] - if (val.comment == '') - $('#modal_dialog').modal('hide'); - } + $(".bootstrap-select").click(function () { + $(this).addClass("open"); + }); - showInfoDialog("grantToken", $.i18n('conf_network_tok_grantT'), $.i18n('conf_network_tok_grantMsg') + '
App: ' + val.comment + '
Code: ' + val.id + '') - $("#tok_grant_acc").off().on('click', function () { - tokenList.push(val) - // forward event, in case we need to rebuild the list now - $(window.hyperion).trigger({ type: "build-token-list" }); - requestHandleTokenRequest(val.id, true) - }); - $("#tok_deny_acc").off().on('click', function () { - requestHandleTokenRequest(val.id, false) - }); - }); + $(document).click(function () { + $(".bootstrap-select").removeClass("open"); + }); - $(window.hyperion).one("cmd-authorize-getTokenList", function (event) { - tokenList = event.response.info; - requestServerInfo(); - }); + $(".bootstrap-select").click(function (e) { + e.stopPropagation(); + }); - $(window.hyperion).on("cmd-sysinfo", function (event) { - requestServerInfo(); - window.sysInfo = event.response.info; + //End language selection - window.currentVersion = window.sysInfo.hyperion.version; - window.currentChannel = window.sysInfo.hyperion.channel; - }); + $(window.hyperion).on("cmd-sessions-update", function (event) { + window.serverInfo.sessions = event.response.data; + updateSessions(); + }); - $(window.hyperion).one("cmd-config-getschema", function (event) { - window.serverSchema = event.response.info; - requestServerConfig(); + $(window.hyperion).on("cmd-authorize-tokenRequest cmd-authorize-getPendingTokenRequests", function (event) { + var val = event.response.info; + if (Array.isArray(event.response.info)) { + if (event.response.info.length == 0) { + return + } + val = event.response.info[0] + if (val.comment == '') + $('#modal_dialog').modal('hide'); + } + + showInfoDialog("grantToken", $.i18n('conf_network_tok_grantT'), $.i18n('conf_network_tok_grantMsg') + '
App: ' + val.comment + '
Code: ' + val.id + '') + $("#tok_grant_acc").off().on('click', function () { + tokenList.push(val) + // forward event, in case we need to rebuild the list now + $(window.hyperion).trigger({ type: "build-token-list" }); + requestHandleTokenRequest(val.id, true) + }); + $("#tok_deny_acc").off().on('click', function () { + requestHandleTokenRequest(val.id, false) + }); + }); + + $(window.hyperion).one("cmd-authorize-getTokenList", function (event) { + tokenList = event.response.info; + requestServerInfo(); + }); + + $(window.hyperion).on("cmd-sysinfo", function (event) { + requestServerInfo(); + window.sysInfo = event.response.info; + + window.currentVersion = window.sysInfo.hyperion.version; + window.currentChannel = window.sysInfo.hyperion.channel; + }); + + $(window.hyperion).one("cmd-config-getschema", function (event) { + window.serverSchema = event.response.info; + requestServerConfig(); requestTokenInfo(); requestGetPendingTokenRequests(); - window.schema = window.serverSchema.properties; - }); + window.schema = window.serverSchema.properties; + }); - $(window.hyperion).on("cmd-config-getconfig", function (event) { - window.serverConfig = event.response.info; - requestSysInfo(); + $(window.hyperion).on("cmd-config-getconfig", function (event) { + window.serverConfig = event.response.info; + requestSysInfo(); - window.showOptHelp = window.serverConfig.general.showOptHelp; - }); + window.showOptHelp = window.serverConfig.general.showOptHelp; + }); - $(window.hyperion).on("cmd-config-setconfig", function (event) { - if (event.response.success === true) { - showNotification('success', $.i18n('dashboard_alert_message_confsave_success'), $.i18n('dashboard_alert_message_confsave_success_t')) - } - }); + $(window.hyperion).on("cmd-config-setconfig", function (event) { + if (event.response.success === true) { + showNotification('success', $.i18n('dashboard_alert_message_confsave_success'), $.i18n('dashboard_alert_message_confsave_success_t')) + } + }); - $(window.hyperion).on("cmd-authorize-login", function (event) { - $("#main-nav").removeAttr('style') - $("#top-navbar").removeAttr('style') + $(window.hyperion).on("cmd-authorize-login", function (event) { + $("#main-nav").removeAttr('style') + $("#top-navbar").removeAttr('style') - if (window.defaultPasswordIsSet === true && getStorage("suppressDefaultPwWarning") !== "true" ) - { - var supprPwWarnCheckbox = '
'+$.i18n('dashboard_message_do_not_show_again') - + '
' - showNotification('warning', $.i18n('dashboard_message_default_password'), $.i18n('dashboard_message_default_password_t'), '' - + $.i18n('InfoDialog_changePassword_title') + '' + supprPwWarnCheckbox) - } - else - //if logged on and pw != default show option to lock ui - $("#btn_lock_ui").removeAttr('style') + if (window.defaultPasswordIsSet === true && getStorage("suppressDefaultPwWarning") !== "true") { + var supprPwWarnCheckbox = '
' + $.i18n('dashboard_message_do_not_show_again') + + '
' + showNotification('warning', $.i18n('dashboard_message_default_password'), $.i18n('dashboard_message_default_password_t'), '' + + $.i18n('InfoDialog_changePassword_title') + '' + supprPwWarnCheckbox) + } + else + //if logged on and pw != default show option to lock ui + $("#btn_lock_ui").removeAttr('style') + if (event.response.hasOwnProperty('info')) + setStorage("loginToken", event.response.info.token, true); - if (event.response.hasOwnProperty('info')) - setStorage("loginToken", event.response.info.token, true); + requestServerConfigSchema(); + }); - requestServerConfigSchema(); - }); + $(window.hyperion).on("cmd-authorize-newPassword", function (event) { + if (event.response.success === true) { + showInfoDialog("success", $.i18n('InfoDialog_changePassword_success')); + // not necessarily true, but better than nothing + window.defaultPasswordIsSet = false; + } + }); - $(window.hyperion).on("cmd-authorize-newPassword", function (event) { - if (event.response.success === true) { - showInfoDialog("success", $.i18n('InfoDialog_changePassword_success')); - // not necessarily true, but better than nothing - window.defaultPasswordIsSet = false; - } - }); + $(window.hyperion).on("cmd-authorize-newPasswordRequired", function (event) { + var loginToken = getStorage("loginToken", true) - $(window.hyperion).on("cmd-authorize-newPasswordRequired", function (event) { - var loginToken = getStorage("loginToken", true) + if (event.response.info.newPasswordRequired == true) { + window.defaultPasswordIsSet = true; - if (event.response.info.newPasswordRequired == true) { - window.defaultPasswordIsSet = true; + if (loginToken) + requestTokenAuthorization(loginToken) + else + requestAuthorization('hyperion'); + } + else { + $("#main-nav").attr('style', 'display:none') + $("#top-navbar").attr('style', 'display:none') - if (loginToken) - requestTokenAuthorization(loginToken) - else - requestAuthorization('hyperion'); - } - else { - $("#main-nav").attr('style', 'display:none') - $("#top-navbar").attr('style', 'display:none') + if (loginToken) + requestTokenAuthorization(loginToken) + else + loadContentTo("#page-content", "login") + } + }); - if (loginToken) - requestTokenAuthorization(loginToken) - else - loadContentTo("#page-content", "login") - } - }); + $(window.hyperion).on("cmd-authorize-adminRequired", function (event) { + //Check if a admin login is required. + //If yes: check if default pw is set. If no: go ahead to get server config and render page + if (event.response.info.adminRequired === true) + requestRequiresDefaultPasswortChange(); + else + requestServerConfigSchema(); + }); - $(window.hyperion).on("cmd-authorize-adminRequired", function (event) { - //Check if a admin login is required. - //If yes: check if default pw is set. If no: go ahead to get server config and render page - if (event.response.info.adminRequired === true) - requestRequiresDefaultPasswortChange(); - else - requestServerConfigSchema(); - }); + $(window.hyperion).on("error", function (event) { + //If we are getting an error "No Authorization" back with a set loginToken we will forward to new Login (Token is expired. + //e.g.: hyperiond was started new in the meantime) + if (event.reason == "No Authorization" && getStorage("loginToken", true)) { + removeStorage("loginToken", true); + requestRequiresAdminAuth(); + } + else { + showInfoDialog("error", "Error", event.reason); + } + }); - $(window.hyperion).on("error", function (event) { - //If we are getting an error "No Authorization" back with a set loginToken we will forward to new Login (Token is expired. - //e.g.: hyperiond was started new in the meantime) - if (event.reason == "No Authorization" && getStorage("loginToken", true)) { - removeStorage("loginToken", true); - requestRequiresAdminAuth(); - } - else { - showInfoDialog("error", "Error", event.reason); - } - }); + $(window.hyperion).on("open", function (event) { + requestRequiresAdminAuth(); + }); - $(window.hyperion).on("open", function (event) { - requestRequiresAdminAuth(); - }); + $(window.hyperion).one("ready", function (event) { + loadContent(); + }); - $(window.hyperion).one("ready", function (event) { - loadContent(); - }); + $(window.hyperion).on("cmd-adjustment-update", function (event) { + window.serverInfo.adjustment = event.response.data + }); - $(window.hyperion).on("cmd-adjustment-update", function (event) { - window.serverInfo.adjustment = event.response.data - }); + $(window.hyperion).on("cmd-videomode-update", function (event) { + window.serverInfo.videomode = event.response.data.videomode + }); - $(window.hyperion).on("cmd-videomode-update", function (event) { - window.serverInfo.videomode = event.response.data.videomode - }); + $(window.hyperion).on("cmd-components-update", function (event) { + let obj = event.response.data - $(window.hyperion).on("cmd-components-update", function (event) { - let obj = event.response.data + // notfication in index + if (obj.name == "ALL") { + if (obj.enabled) + $("#hyperion_disabled_notify").fadeOut("fast"); + else + $("#hyperion_disabled_notify").fadeIn("fast"); + } - // notfication in index - if (obj.name == "ALL") { - if (obj.enabled) - $("#hyperion_disabled_notify").fadeOut("fast"); - else - $("#hyperion_disabled_notify").fadeIn("fast"); - } + window.comps.forEach((entry, index) => { + if (entry.name === obj.name) { + window.comps[index] = obj; + } + }); + // notify the update + $(window.hyperion).trigger("components-updated", event.response.data); + }); - window.comps.forEach((entry, index) => { - if (entry.name === obj.name) { - window.comps[index] = obj; - } - }); - // notify the update - $(window.hyperion).trigger("components-updated", event.response.data); - }); + $(window.hyperion).on("cmd-instance-update", function (event) { + window.serverInfo.instance = event.response.data + var avail = event.response.data; + // notify the update + $(window.hyperion).trigger("instance-updated"); - $(window.hyperion).on("cmd-instance-update", function (event) { - window.serverInfo.instance = event.response.data - var avail = event.response.data; - // notify the update - $(window.hyperion).trigger("instance-updated"); + // if our current instance is no longer available we are at instance 0 again. + var isInData = false; + for (var key in avail) { + if (avail[key].instance == currentHyperionInstance && avail[key].running) { + isInData = true; + } + } - // if our current instance is no longer available we are at instance 0 again. - var isInData = false; - for (var key in avail) { - if (avail[key].instance == currentHyperionInstance && avail[key].running) { - isInData = true; - } - } + if (!isInData) { + //Delete Storage information about the last used but now stopped instance + if (getStorage('lastSelectedInstance', false)) + removeStorage('lastSelectedInstance', false) - if (!isInData) { - //Delete Storage information about the last used but now stopped instance - if (getStorage('lastSelectedInstance', false)) - removeStorage('lastSelectedInstance', false) + currentHyperionInstance = 0; + currentHyperionInstanceName = getInstanceNameByIndex(0); + requestServerConfig(); + setTimeout(requestServerInfo, 100) + setTimeout(requestTokenInfo, 200) + setTimeout(loadContent, 300, undefined, true) + } - currentHyperionInstance = 0; - currentHyperionInstanceName = getInstanceNameByIndex(0); - requestServerConfig(); - setTimeout(requestServerInfo, 100) - setTimeout(requestTokenInfo, 200) - setTimeout(loadContent, 300, undefined, true) - } + // determine button visibility + var running = serverInfo.instance.filter(entry => entry.running); + if (running.length > 1) + $('#btn_hypinstanceswitch').toggle(true) + else + $('#btn_hypinstanceswitch').toggle(false) - // determine button visibility - var running = serverInfo.instance.filter(entry => entry.running); - if (running.length > 1) - $('#btn_hypinstanceswitch').toggle(true) - else - $('#btn_hypinstanceswitch').toggle(false) + // update listing for button + updateHyperionInstanceListing() + }); - // update listing for button - updateHyperionInstanceListing() - }); + $(window.hyperion).on("cmd-instance-switchTo", function (event) { + requestServerConfig(); + setTimeout(requestServerInfo, 200) + setTimeout(requestTokenInfo, 400) + setTimeout(loadContent, 400, undefined, true) + }); - $(window.hyperion).on("cmd-instance-switchTo", function (event) { - requestServerConfig(); - setTimeout(requestServerInfo, 200) - setTimeout(requestTokenInfo, 400) - setTimeout(loadContent, 400, undefined, true) - }); - - $(window.hyperion).on("cmd-effects-update", function (event) { - window.serverInfo.effects = event.response.data.effects - }); - - $(".mnava").bind('click.menu', function (e) { - loadContent(e); - window.scrollTo(0, 0); - }); + $(window.hyperion).on("cmd-effects-update", function (event) { + window.serverInfo.effects = event.response.data.effects + }); + $(".mnava").bind('click.menu', function (e) { + loadContent(e); + window.scrollTo(0, 0); + }); }); -function suppressDefaultPwWarning(){ - - if (document.getElementById('chk_suppressDefaultPw').checked) - setStorage("suppressDefaultPwWarning", "true"); - else - setStorage("suppressDefaultPwWarning", "false"); +function suppressDefaultPwWarning() { + if (document.getElementById('chk_suppressDefaultPw').checked) + setStorage("suppressDefaultPwWarning", "true"); + else + setStorage("suppressDefaultPwWarning", "false"); } $(function () { - var sidebar = $('#side-menu'); // cache sidebar to a variable for performance - sidebar.delegate('a.inactive', 'click', function () { - sidebar.find('.active').toggleClass('active inactive'); - $(this).toggleClass('active inactive'); - }); + var sidebar = $('#side-menu'); // cache sidebar to a variable for performance + sidebar.delegate('a.inactive', 'click', function () { + sidebar.find('.active').toggleClass('active inactive'); + $(this).toggleClass('active inactive'); + }); }); // hotfix body padding when bs modals overlap $(document.body).on('hide.bs.modal,hidden.bs.modal', function () { - $('body').css('padding-right', '0'); + $('body').css('padding-right', '0'); }); //Dark Mode -$("#btn_darkmode").off().on("click",function(e){ - - if(getStorage("darkMode", false) != "on") - { - handleDarkMode(); - setStorage("darkModeOverwrite", true, true); - } - else { - setStorage("darkMode", "off", false); - setStorage("darkModeOverwrite", true, true); - location.reload(); - } - +$("#btn_darkmode").off().on("click", function (e) { + if (getStorage("darkMode", false) != "on") { + handleDarkMode(); + setStorage("darkModeOverwrite", true, true); + } + else { + setStorage("darkMode", "off", false); + setStorage("darkModeOverwrite", true, true); + location.reload(); + } + }); + +// Menuitem toggle; +function SwitchToMenuItem(target) { + document.getElementById(target).click(); // Get '+((led.name) ? led.name : idx)+''; - } - $('#leds_preview').html(leds_html); - $('#ledc_0').css({"background-color":"black","z-index":"12"}); - $('#ledc_1').css({"background-color":"grey","z-index":"11"}); - $('#ledc_2').css({"background-color":"#A9A9A9","z-index":"10"}); - - if($('#leds_prev_toggle_num').hasClass('btn-success')) - $('.led_prev_num').css("display", "inline"); - - // update ace Editor content - aceEdt.set(finalLedArray); + var leds_html = ""; + for (var idx = 0; idx < leds.length; idx++) { + var led = leds[idx]; + var led_id = 'ledc_' + [idx]; + var bgcolor = "background-color:hsla(" + (idx * 360 / leds.length) + ",100%,50%,0.75);"; + var pos = "left:" + (led.hmin * canvas_width) + "px;" + + "top:" + (led.vmin * canvas_height) + "px;" + + "width:" + ((led.hmax - led.hmin) * (canvas_width - 1)) + "px;" + + "height:" + ((led.vmax - led.vmin) * (canvas_height - 1)) + "px;"; + leds_html += '
' + ((led.name) ? led.name : idx) + '
'; + } + $('#leds_preview').html(leds_html); + $('#ledc_0').css({ "background-color": "black", "z-index": "12" }); + $('#ledc_1').css({ "background-color": "grey", "z-index": "11" }); + $('#ledc_2').css({ "background-color": "#A9A9A9", "z-index": "10" }); + if ($('#leds_prev_toggle_num').hasClass('btn-success')) + $('.led_prev_num').css("display", "inline"); } -function createClassicLedLayoutSimple( ledstop,ledsleft,ledsright,ledsbottom,position,reverse ){ +function createClassicLedLayoutSimple(ledstop, ledsleft, ledsright, ledsbottom, position, reverse) { + let params = { + ledstop: 0, ledsleft: 0, ledsright: 0, ledsbottom: 0, + ledsglength: 0, ledsgpos: 0, position: 0, + ledsHDepth: 0.08, ledsVDepth: 0.05, overlap: 0, + edgeVGap: 0, + ptblh: 0, ptblv: 1, ptbrh: 1, ptbrv: 1, + pttlh: 0, pttlv: 0, pttrh: 1, pttrv: 0, + reverse: false + }; - let params = { - ledstop: 0, ledsleft: 0, ledsright: 0, ledsbottom: 0, - ledsglength: 0, ledsgpos: 0, position: 0, - ledsHDepth: 0.08, ledsVDepth: 0.05, overlap: 0, - edgeVGap: 0, - ptblh: 0, ptblv: 1, ptbrh: 1, ptbrv: 1, - pttlh: 0, pttlv: 0, pttrh: 1, pttrv: 0, - reverse:false - }; - - params.ledstop = ledstop; - params.ledsleft = ledsleft; - params.ledsright = ledsright; - params.ledsbottom = ledsbottom; - params.position = position; - params.reverse = reverse; + params.ledstop = ledstop; + params.ledsleft = ledsleft; + params.ledsright = ledsright; + params.ledsbottom = ledsbottom; + params.position = position; + params.reverse = reverse; - return createClassicLedLayout( params ); + return createClassicLedLayout(params); } -function createClassicLedLayout( params ){ +function createClassicLedLayout(params) { + var edgeHGap = params.edgeVGap / (16 / 9); + var ledArray = []; - //helper - var edgeHGap = params.edgeVGap/(16/9); - var ledArray = []; + function createFinalArray(array) { + var finalLedArray = []; + for (var i = 0; i < array.length; i++) { + var hmin = array[i].hmin; + var hmax = array[i].hmax; + var vmin = array[i].vmin; + var vmax = array[i].vmax; + finalLedArray[i] = { "hmax": hmax, "hmin": hmin, "vmax": vmax, "vmin": vmin } + } + return finalLedArray; + } - function createFinalArray(array){ - var finalLedArray = []; - for(var i = 0; i 0) { + while (times--) { + array.push(array.shift()) + } + return array; + } + else { + while (times++) { + array.unshift(array.pop()) + } + return array; + } + } - function rotateArray(array, times){ - if (times > 0){ - while( times-- ){ - array.push(array.shift()) - } - return array; - } - else - { - while( times++ ){ - array.unshift(array.pop()) - } - return array; - } - } + function valScan(val) { + if (val > 1) + return 1; + if (val < 0) + return 0; + return val; + } - function valScan(val) - { - if(val > 1) - return 1; - if(val < 0) - return 0; - return val; - } + function ovl(scan, val) { + if (scan == "+") + return valScan(val += params.overlap); + else + return valScan(val -= params.overlap); + } - function ovl(scan,val) - { - if(scan == "+") - return valScan(val += params.overlap); - else - return valScan(val -= params.overlap); - } + function createLedArray(hmin, hmax, vmin, vmax) { + hmin = round(hmin); + hmax = round(hmax); + vmin = round(vmin); + vmax = round(vmax); + ledArray.push({ "hmin": hmin, "hmax": hmax, "vmin": vmin, "vmax": vmax }); + } - function createLedArray(hmin, hmax, vmin, vmax){ - hmin = round(hmin); - hmax = round(hmax); - vmin = round(vmin); - vmax = round(vmax); - ledArray.push({ "hmin": hmin, "hmax": hmax, "vmin": vmin, "vmax": vmax }); - } + function createTopLeds() { + var steph = (params.pttrh - params.pttlh - (2 * edgeHGap)) / params.ledstop; + var stepv = (params.pttrv - params.pttlv) / params.ledstop; - function createTopLeds(){ - var steph = (params.pttrh - params.pttlh - (2*edgeHGap))/params.ledstop; - var stepv = (params.pttrv - params.pttlv)/params.ledstop; + for (var i = 0; i < params.ledstop; i++) { + var hmin = ovl("-", params.pttlh + (steph * Number([i])) + edgeHGap); + var hmax = ovl("+", params.pttlh + (steph * Number([i + 1])) + edgeHGap); + var vmin = params.pttlv + (stepv * Number([i])); + var vmax = vmin + params.ledsHDepth; + createLedArray(hmin, hmax, vmin, vmax); + } + } - for (var i = 0; i -1; i--) { + var hmin = ovl("-", params.ptblh + (steph * Number([i])) + edgeHGap); + var hmax = ovl("+", params.ptblh + (steph * Number([i + 1])) + edgeHGap); + var vmax = params.ptblv + (stepv * Number([i])); + var vmin = vmax - params.ledsHDepth; + createLedArray(hmin, hmax, vmin, vmax); + } + } - for (var i = params.ledsbottom-1; i>-1; i--){ - var hmin = ovl("-",params.ptblh+(steph*Number([i]))+edgeHGap); - var hmax = ovl("+",params.ptblh+(steph*Number([i+1]))+edgeHGap); - var vmax= params.ptblv+(stepv*Number([i])); - var vmin = vmax-params.ledsHDepth; - createLedArray(hmin, hmax, vmin, vmax); - } - } + function createLeftLeds() { + var steph = (params.ptblh - params.pttlh) / params.ledsleft; + var stepv = (params.ptblv - params.pttlv - (2 * params.edgeVGap)) / params.ledsleft; - function createLeftLeds(){ - var steph = (params.ptblh - params.pttlh)/params.ledsleft; - var stepv = (params.ptblv - params.pttlv - (2*params.edgeVGap))/params.ledsleft; + for (var i = params.ledsleft - 1; i > -1; i--) { + var hmin = params.pttlh + (steph * Number([i])); + var hmax = hmin + params.ledsVDepth; + var vmin = ovl("-", params.pttlv + (stepv * Number([i])) + params.edgeVGap); + var vmax = ovl("+", params.pttlv + (stepv * Number([i + 1])) + params.edgeVGap); + createLedArray(hmin, hmax, vmin, vmax); + } + } - for (var i = params.ledsleft-1; i>-1; i--){ - var hmin = params.pttlh+(steph*Number([i])); - var hmax = hmin+params.ledsVDepth; - var vmin = ovl("-",params.pttlv+(stepv*Number([i]))+params.edgeVGap); - var vmax = ovl("+",params.pttlv+(stepv*Number([i+1]))+params.edgeVGap); - createLedArray(hmin, hmax, vmin, vmax); - } + //rectangle + createTopLeds(); + createRightLeds(); + createBottomLeds(); + createLeftLeds(); - } + //check led gap pos + if (params.ledsgpos + params.ledsglength > ledArray.length) { + var mpos = Math.max(0, ledArray.length - params.ledsglength); + //$('#ip_cl_ledsgpos').val(mpos); + ledsgpos = mpos; + } - //rectangle - createTopLeds(); - createRightLeds(); - createBottomLeds(); - createLeftLeds(); - - //check led gap pos - if (params.ledsgpos+params.ledsglength > ledArray.length) - { - var mpos = Math.max(0,ledArray.length-params.ledsglength); - //$('#ip_cl_ledsgpos').val(mpos); - ledsgpos = mpos; - } + //check led gap length + if (params.ledsglength >= ledArray.length) { + //$('#ip_cl_ledsglength').val(ledArray.length-1); + params.ledsglength = ledArray.length - params.ledsglength - 1; + } - //check led gap length - if(params.ledsglength >= ledArray.length) - { - //$('#ip_cl_ledsglength').val(ledArray.length-1); - params.ledsglength = ledArray.length-params.ledsglength-1; - } + if (params.ledsglength != 0) { + ledArray.splice(params.ledsgpos, params.ledsglength); + } - if(params.ledsglength != 0){ - ledArray.splice(params.ledsgpos, params.ledsglength); - } + if (params.position != 0) { + rotateArray(ledArray, params.position); + } - if (params.position != 0){ - rotateArray(ledArray, params.position); - } + if (params.reverse) + ledArray.reverse(); - if (params.reverse) - ledArray.reverse(); - - return createFinalArray(ledArray); + return createFinalArray(ledArray); } -function createClassicLeds(){ - - //get values - let params = { - ledstop : parseInt($("#ip_cl_top").val()), - ledsbottom : parseInt($("#ip_cl_bottom").val()), - ledsleft : parseInt($("#ip_cl_left").val()), - ledsright : parseInt($("#ip_cl_right").val()), - ledsglength : parseInt($("#ip_cl_glength").val()), - ledsgpos : parseInt($("#ip_cl_gpos").val()), - position : parseInt($("#ip_cl_position").val()), - reverse : $("#ip_cl_reverse").is(":checked"), +function createClassicLeds() { + //get values + let params = { + ledstop: parseInt($("#ip_cl_top").val()), + ledsbottom: parseInt($("#ip_cl_bottom").val()), + ledsleft: parseInt($("#ip_cl_left").val()), + ledsright: parseInt($("#ip_cl_right").val()), + ledsglength: parseInt($("#ip_cl_glength").val()), + ledsgpos: parseInt($("#ip_cl_gpos").val()), + position: parseInt($("#ip_cl_position").val()), + reverse: $("#ip_cl_reverse").is(":checked"), - //advanced values - ledsVDepth : parseInt($("#ip_cl_vdepth").val())/100, - ledsHDepth : parseInt($("#ip_cl_hdepth").val())/100, - edgeVGap : parseInt($("#ip_cl_edgegap").val())/100/2, - //cornerVGap : parseInt($("#ip_cl_cornergap").val())/100/2, - overlap : $("#ip_cl_overlap").val()/100, + //advanced values + ledsVDepth: parseInt($("#ip_cl_vdepth").val()) / 100, + ledsHDepth: parseInt($("#ip_cl_hdepth").val()) / 100, + edgeVGap: parseInt($("#ip_cl_edgegap").val()) / 100 / 2, + //cornerVGap : parseInt($("#ip_cl_cornergap").val())/100/2, + overlap: $("#ip_cl_overlap").val() / 100, - //trapezoid values % -> float - ptblh : parseInt($("#ip_cl_pblh").val())/100, - ptblv : parseInt($("#ip_cl_pblv").val())/100, - ptbrh : parseInt($("#ip_cl_pbrh").val())/100, - ptbrv : parseInt($("#ip_cl_pbrv").val())/100, - pttlh : parseInt($("#ip_cl_ptlh").val())/100, - pttlv : parseInt($("#ip_cl_ptlv").val())/100, - pttrh : parseInt($("#ip_cl_ptrh").val())/100, - pttrv : parseInt($("#ip_cl_ptrv").val())/100, - } - - finalLedArray = createClassicLedLayout( params ); - - //check led gap pos - if (params.ledsgpos+params.ledsglength > finalLedArray.length) { - var mpos = Math.max(0,finalLedArray.length-params.ledsglength); - $('#ip_cl_ledsgpos').val(mpos); - } - //check led gap length - if(params.ledsglength >= finalLedArray.length) { - $('#ip_cl_ledsglength').val(finalLedArray.length-1); - } - - createLedPreview(finalLedArray, 'classic'); + //trapezoid values % -> float + ptblh: parseInt($("#ip_cl_pblh").val()) / 100, + ptblv: parseInt($("#ip_cl_pblv").val()) / 100, + ptbrh: parseInt($("#ip_cl_pbrh").val()) / 100, + ptbrv: parseInt($("#ip_cl_pbrv").val()) / 100, + pttlh: parseInt($("#ip_cl_ptlh").val()) / 100, + pttlv: parseInt($("#ip_cl_ptlv").val()) / 100, + pttrh: parseInt($("#ip_cl_ptrh").val()) / 100, + pttrv: parseInt($("#ip_cl_ptrv").val()) / 100, + } + + nonBlacklistLedArray = createClassicLedLayout(params); + finalLedArray = blackListLeds(nonBlacklistLedArray, ledBlacklist); + + //check led gap pos + if (params.ledsgpos + params.ledsglength > finalLedArray.length) { + var mpos = Math.max(0, finalLedArray.length - params.ledsglength); + $('#ip_cl_ledsgpos').val(mpos); + } + //check led gap length + if (params.ledsglength >= finalLedArray.length) { + $('#ip_cl_ledsglength').val(finalLedArray.length - 1); + } + + createLedPreview(finalLedArray, 'classic'); + aceEdt.set(finalLedArray); } +function createMatrixLayout(ledshoriz, ledsvert, cabling, start) { + // Big thank you to RanzQ (Juha Rantanen) from Github for this script + // https://raw.githubusercontent.com/RanzQ/hyperion-audio-effects/master/matrix-config.js -function createMatrixLayout( ledshoriz, ledsvert, cabling, start){ -// Big thank you to RanzQ (Juha Rantanen) from Github for this script -// https://raw.githubusercontent.com/RanzQ/hyperion-audio-effects/master/matrix-config.js + var parallel = false + var leds = [] + var hblock = 1.0 / ledshoriz + var vblock = 1.0 / ledsvert - var parallel = false - var leds = [] - var hblock = 1.0 / ledshoriz - var vblock = 1.0 / ledsvert + if (cabling == "parallel") { + parallel = true + } - if (cabling == "parallel"){ - parallel = true - } + /** + * Adds led to the hyperion config led array + * @param {Number} x Horizontal position in matrix + * @param {Number} y Vertical position in matrix + */ + function addLed(x, y) { + var hscanMin = x * hblock + var hscanMax = (x + 1) * hblock + var vscanMin = y * vblock + var vscanMax = (y + 1) * vblock -/** - * Adds led to the hyperion config led array - * @param {Number} x Horizontal position in matrix - * @param {Number} y Vertical position in matrix - */ - function addLed (x, y) { - var hscanMin = x * hblock - var hscanMax = (x + 1) * hblock - var vscanMin = y * vblock - var vscanMax = (y + 1) * vblock + hscanMin = round(hscanMin); + hscanMax = round(hscanMax); + vscanMin = round(vscanMin); + vscanMax = round(vscanMax); - hscanMin = round(hscanMin); - hscanMax = round(hscanMax); - vscanMin = round(vscanMin); - vscanMax = round(vscanMax); + leds.push({ + hmin: hscanMin, + hmax: hscanMax, + vmin: vscanMin, + vmax: vscanMax + }) + } - leds.push({ - hmin: hscanMin, - hmax: hscanMax, - vmin: vscanMin, - vmax: vscanMax - }) - } + var startYX = start.split('-') + var startX = startYX[1] === 'right' ? ledshoriz - 1 : 0 + var startY = startYX[0] === 'bottom' ? ledsvert - 1 : 0 + var endX = startX === 0 ? ledshoriz - 1 : 0 + var endY = startY === 0 ? ledsvert - 1 : 0 + var forward = startX < endX - var startYX = start.split('-') - var startX = startYX[1] === 'right' ? ledshoriz - 1 : 0 - var startY = startYX[0] === 'bottom' ? ledsvert - 1 : 0 - var endX = startX === 0 ? ledshoriz - 1 : 0 - var endY = startY === 0 ? ledsvert - 1 : 0 - var forward = startX < endX + var downward = startY < endY - var downward = startY < endY + var x, y - var x, y + for (y = startY; downward && y <= endY || !downward && y >= endY; y += downward ? 1 : -1) { + for (x = startX; forward && x <= endX || !forward && x >= endX; x += forward ? 1 : -1) { + addLed(x, y) + } + if (!parallel) { + forward = !forward + var tmp = startX + startX = endX + endX = tmp + } + } - for (y = startY; downward && y <= endY || !downward && y >= endY; y += downward ? 1 : -1) { - for (x = startX; forward && x <= endX || !forward && x >= endX; x += forward ? 1 : -1) { - addLed(x, y) - } - if (!parallel) { - forward = !forward - var tmp = startX - startX = endX - endX = tmp - } - } - - return leds; + return leds; } +function createMatrixLeds() { + // Big thank you to RanzQ (Juha Rantanen) from Github for this script + // https://raw.githubusercontent.com/RanzQ/hyperion-audio-effects/master/matrix-config.js -function createMatrixLeds(){ -// Big thank you to RanzQ (Juha Rantanen) from Github for this script -// https://raw.githubusercontent.com/RanzQ/hyperion-audio-effects/master/matrix-config.js + //get values + var ledshoriz = parseInt($("#ip_ma_ledshoriz").val()); + var ledsvert = parseInt($("#ip_ma_ledsvert").val()); + var cabling = $("#ip_ma_cabling").val(); + var start = $("#ip_ma_start").val(); - //get values - var ledshoriz = parseInt($("#ip_ma_ledshoriz").val()); - var ledsvert = parseInt($("#ip_ma_ledsvert").val()); - var cabling = $("#ip_ma_cabling").val(); - var start = $("#ip_ma_start").val(); + nonBlacklistLedArray = createMatrixLayout(ledshoriz, ledsvert, cabling, start); + finalLedArray = blackListLeds(nonBlacklistLedArray, ledBlacklist); - finalLedArray = createMatrixLayout(ledshoriz,ledsvert,cabling,start); - createLedPreview(finalLedArray, 'matrix'); + createLedPreview(finalLedArray, 'matrix'); + aceEdt.set(finalLedArray); } -function migrateLedConfig(slConfig){ +function blackListLeds(nonBlacklistLedArray, blackList) { - var newLedConfig = {classic:{}, matrix:{}}; + var blacklistedLedArray = [...nonBlacklistLedArray]; + if (blackList && blackList.length > 0) { - //Default Classic layout - newLedConfig.classic = { - "top" : 1, - "bottom" : 0, - "left" : 0, - "right" : 0, - "glength" : 0, - "gpos" : 0, - "position" : 0, - "reverse" : false, - "hdepth" : 8, - "vdepth" : 5, - "overlap" : 0, - "edgegap" : 0 - } + for (let item of blackList) { + var start = item.start; + var num = item.num + var layoutSize = blacklistedLedArray.length; - //Move Classic layout - newLedConfig.classic.top = slConfig.top; - newLedConfig.classic.bottom = slConfig.bottom; - newLedConfig.classic.left = slConfig.left; - newLedConfig.classic.right = slConfig.right; - newLedConfig.classic.glength = slConfig.glength; - newLedConfig.classic.position = slConfig.position; - newLedConfig.classic.reverse = slConfig.reverse; - newLedConfig.classic.hdepth = slConfig.hdepth; - newLedConfig.classic.vdepth = slConfig.vdepth; - newLedConfig.classic.overlap = slConfig.overlap; + //Only consider rules which are in rage of defined number of LEDs + if (start >= 0 && start < layoutSize) { + // If number of LEDs exceeds layoutSize, use apply number until layout size + if (start + num > layoutSize) { + num = layoutSize - start; - //Default Matrix layout - newLedConfig["matrix"] = { "ledshoriz": 1, - "ledsvert" : 1, - "cabling" : "snake", - "start" : "top-left" - } + } + for (var i = 0; i < num; i++) { + blacklistedLedArray[start + i] = { hmax: 0, hmin: 0, vmax: 0, vmin: 0 }; + } + } + } + } - // Persit new structure - requestWriteConfig({ledConfig:newLedConfig}) - return newLedConfig + return blacklistedLedArray; +} +function getLedConfig() { + var ledConfig = { classic: {}, matrix: {} }; + var slConfig = window.serverConfig.ledConfig; + + for (var key in slConfig.classic) { + if (typeof (slConfig.classic[key]) === "boolean") + ledConfig.classic[key] = $('#ip_cl_' + key).is(':checked'); + else if (Number.isInteger(slConfig.classic[key])) + ledConfig.classic[key] = parseInt($('#ip_cl_' + key).val()); + else + ledConfig.classic[key] = $('#ip_cl_' + key).val(); + } + + for (var key in slConfig.matrix) { + if (typeof (slConfig.matrix[key]) === "boolean") + ledConfig.matrix[key] = $('#ip_ma_' + key).is(':checked'); + else if (Number.isInteger(slConfig.matrix[key])) + ledConfig.matrix[key] = parseInt($('#ip_ma_' + key).val()); + else + ledConfig.matrix[key] = $('#ip_ma_' + key).val(); + } + + ledConfig.ledBlacklist = blacklist_editor.getEditor("root.ledBlacklist").getValue(); + + return ledConfig; } function isEmpty(obj) { - for(var key in obj) { - if(obj.hasOwnProperty(key)) - return false; - } - return true; + for (var key in obj) { + if (obj.hasOwnProperty(key)) + return false; + } + return true; } -$(document).ready(function() { - // translate - performTranslation(); +$(document).ready(function () { + // translate + performTranslation(); - //add intros - if(window.showOptHelp) - { - createHintH("intro", $.i18n('conf_leds_device_intro'), "leddevice_intro"); - createHintH("intro", $.i18n('conf_leds_layout_intro'), "layout_intro"); - $('#led_vis_help').html('
'+$.i18n('conf_leds_layout_preview_l1')+'
'+$.i18n('conf_leds_layout_preview_l2')+'
'); - } + //add intros + if (window.showOptHelp) { + createHintH("intro", $.i18n('conf_leds_device_intro'), "leddevice_intro"); + createHintH("intro", $.i18n('conf_leds_layout_intro'), "layout_intro"); + $('#led_vis_help').html('
' + $.i18n('conf_leds_layout_preview_l1') + '
' + $.i18n('conf_leds_layout_preview_l2') + '
'); + } - var slConfig = window.serverConfig.ledConfig; + //************************************************** + // Handle LED-Layout Configuration + //************************************************** + var slConfig = window.serverConfig.ledConfig; - //Check, if structure is not aligned to expected -> migrate structure + //restore ledConfig - Classic + for (var key in slConfig.classic) { + if (typeof (slConfig.classic[key]) === "boolean") + $('#ip_cl_' + key).prop('checked', slConfig.classic[key]); + else + $('#ip_cl_' + key).val(slConfig.classic[key]); + } - if ( isEmpty(slConfig.classic) ) - { - slConfig = migrateLedConfig( slConfig ); - } + //restore ledConfig - Matrix + for (var key in slConfig.matrix) { + if (typeof (slConfig.matrix[key]) === "boolean") + $('#ip_ma_' + key).prop('checked', slConfig.matrix[key]); + else + $('#ip_ma_' + key).val(slConfig.matrix[key]); + } - //restore ledConfig - Classic - for(var key in slConfig.classic) - { - if(typeof(slConfig.classic[key]) === "boolean") - $('#ip_cl_'+key).prop('checked', slConfig.classic[key]); - else - $('#ip_cl_'+key).val(slConfig.classic[key]); - } + // check access level and adjust ui + if (storedAccess == "default") { + $('#texfield_panel').toggle(false); + $('#previewcreator').toggle(false); + } - //restore ledConfig - Matrix - for(var key in slConfig.matrix) - { - if(typeof(slConfig.matrix[key]) === "boolean") - $('#ip_ma_'+key).prop('checked', slConfig.matrix[key]); - else - $('#ip_ma_'+key).val(slConfig.matrix[key]); - } + // Wiki link + $('#leds_wl').append('

' + $.i18n('general_wiki_moreto', $.i18n('conf_leds_nav_label_ledlayout')) + buildWL("user/advanced/Advanced.html#led-layout", "Wiki") + '

'); - function saveValues() - { - var ledConfig = {classic:{}, matrix:{}}; + // bind change event to all inputs + $('.ledCLconstr').bind("change", function () { + valValue(this.id, this.value, this.min, this.max); + createClassicLeds(); + }); - for(var key in slConfig.classic) - { - if(typeof(slConfig.classic[key]) === "boolean") - ledConfig.classic[key] = $('#ip_cl_'+key).is(':checked'); - else if(Number.isInteger(slConfig.classic[key])) - ledConfig.classic[key] = parseInt($('#ip_cl_'+key).val()); - else - ledConfig.classic[key] = $('#ip_cl_'+key).val(); - } + $('.ledMAconstr').bind("change", function () { + valValue(this.id, this.value, this.min, this.max); + createMatrixLeds(); + }); - for(var key in slConfig.matrix) - { - if(typeof(slConfig.matrix[key]) === "boolean") - ledConfig.matrix[key] = $('#ip_ma_'+key).is(':checked'); - else if(Number.isInteger(slConfig.matrix[key])) - ledConfig.matrix[key] = parseInt($('#ip_ma_'+key).val()); - else - ledConfig.matrix[key] = $('#ip_ma_'+key).val(); - } - requestWriteConfig({ledConfig}); - } + $(document).on('click', "#classic_panel", function (e) { + createClassicLeds(); + }); - // check access level and adjust ui - if(storedAccess == "default") - { - $('#texfield_panel').toggle(false); - $('#previewcreator').toggle(false); - } + $(document).on('click', "#matrix_panel", function (e) { + createMatrixLeds(); + }); - //Wiki link - $('#leds_wl').append('

'+$.i18n('general_wiki_moreto',$.i18n('conf_leds_nav_label_ledlayout'))+buildWL("user/moretopics/ledarea","Wiki")+'

'); + $(document).on('click', "#current_config_panel", function (e) { + aceEdt.set(finalLedArray); + }); - // bind change event to all inputs - $('.ledCLconstr').bind("change", function() { - valValue(this.id,this.value,this.min,this.max); - createClassicLeds(); - }); + // Initialise from config and apply blacklist rules + nonBlacklistLedArray = window.serverConfig.leds; + ledBlacklist = window.serverConfig.ledConfig.ledBlacklist; + finalLedArray = blackListLeds(nonBlacklistLedArray, ledBlacklist); - $('.ledMAconstr').bind("change", function() { - valValue(this.id,this.value,this.min,this.max); - createMatrixLeds(); - }); + var blacklistOptions = window.serverSchema.properties.ledConfig.properties.ledBlacklist; + blacklist_editor = createJsonEditor('editor_container_blacklist_conf', { + ledBlacklist: blacklistOptions, + }); + blacklist_editor.getEditor("root.ledBlacklist").setValue(ledBlacklist); - // v4 of json schema with diff required assignment - remove when hyperion schema moved to v4 - var ledschema = { "items": { "additionalProperties": false, "required": ["hmin", "hmax", "vmin", "vmax"], "properties": { "name": { "type": "string" }, "colorOrder": { "enum": ["rgb", "bgr", "rbg", "brg", "gbr", "grb"], "type": "string" }, "hmin": { "maximum": 1, "minimum": 0, "type": "number" }, "hmax": { "maximum": 1, "minimum": 0, "type": "number" }, "vmin": { "maximum": 1, "minimum": 0, "type": "number" }, "vmax": { "maximum": 1, "minimum": 0, "type": "number" } }, "type": "object" }, "type": "array" }; - //create jsonace editor - aceEdt = new JSONACEEditor(document.getElementById("aceedit"),{ - mode: 'code', - schema: ledschema, - onChange: function(){ - var success = true; - try{ - aceEdt.get(); - } - catch(err) - { - success = false; - } + // v4 of json schema with diff required assignment - remove when hyperion schema moved to v4 + var ledschema = { "items": { "additionalProperties": false, "required": ["hmin", "hmax", "vmin", "vmax"], "properties": { "name": { "type": "string" }, "colorOrder": { "enum": ["rgb", "bgr", "rbg", "brg", "gbr", "grb"], "type": "string" }, "hmin": { "maximum": 1, "minimum": 0, "type": "number" }, "hmax": { "maximum": 1, "minimum": 0, "type": "number" }, "vmin": { "maximum": 1, "minimum": 0, "type": "number" }, "vmax": { "maximum": 1, "minimum": 0, "type": "number" } }, "type": "object" }, "type": "array" }; + //create jsonace editor + aceEdt = new JSONACEEditor(document.getElementById("aceedit"), { + mode: 'code', + schema: ledschema, + onChange: function () { + var success = true; + try { + aceEdt.get(); + } + catch (err) { + success = false; + } - if(success) - { - $('#leds_custom_updsim').attr("disabled", false); - $('#leds_custom_save').attr("disabled", false); - } - else - { - $('#leds_custom_updsim').attr("disabled", true); - $('#leds_custom_save').attr("disabled", true); - } + if (success) { + $('#leds_custom_updsim').attr("disabled", false); + $('#leds_custom_save').attr("disabled", false); + } + else { + $('#leds_custom_updsim').attr("disabled", true); + $('#leds_custom_save').attr("disabled", true); + } - if ( window.readOnlyMode ) - { - $('#leds_custom_save').attr('disabled', true); - } - } - }, window.serverConfig.leds); + if (window.readOnlyMode) { + $('#leds_custom_save').attr('disabled', true); + } + } + }, finalLedArray); - //TODO: HACK! No callback for schema validation - Add it! - setInterval(function(){ - if($('#aceedit table').hasClass('jsoneditor-text-errors')) - { - $('#leds_custom_updsim').attr("disabled", true); - $('#leds_custom_save').attr("disabled", true); - } - },1000); + //TODO: HACK! No callback for schema validation - Add it! + setInterval(function () { + if ($('#aceedit table').hasClass('jsoneditor-text-errors')) { + $('#leds_custom_updsim').attr("disabled", true); + $('#leds_custom_save').attr("disabled", true); + } + }, 1000); - $('.jsoneditor-menu').toggle(); + $('.jsoneditor-menu').toggle(); - // leds to finalLedArray - finalLedArray = window.serverConfig.leds; + // validate textfield and update preview + $("#leds_custom_updsim").off().on("click", function () { + nonBlacklistLedArray = aceEdt.get(); + finalLedArray = blackListLeds(nonBlacklistLedArray, ledBlacklist); + createLedPreview(finalLedArray, 'text'); + }); - // create and update editor - $("#leddevices").off().on("change", function() { - var generalOptions = window.serverSchema.properties.device; + // save led layout, the generated textfield configuration always represents the latest layout + $("#btn_ma_save, #btn_cl_save, #btn_bl_save, #leds_custom_save").off().on("click", function () { + var hardwareLedCount = conf_editor.getEditor("root.generalOptions.hardwareLedCount").getValue(); + var layoutLedCount = aceEdt.get().length; + + if (hardwareLedCount < layoutLedCount) { + // Not enough hardware LEDs for configured layout + showInfoDialog('error', $.i18n("conf_leds_config_error"), $.i18n('conf_leds_error_hwled_lt_layout', hardwareLedCount, layoutLedCount)); + } else { + saveLedConfig(false); + } + }); + + // toggle led numbers + $('#leds_prev_toggle_num').off().on("click", function () { + $('.led_prev_num').toggle(); + toggleClass('#leds_prev_toggle_num', "btn-danger", "btn-success"); + }); + + // open checklist + $('#leds_prev_checklist').off().on("click", function () { + var liList = [$.i18n('conf_leds_layout_checkp1'), $.i18n('conf_leds_layout_checkp3'), $.i18n('conf_leds_layout_checkp2'), $.i18n('conf_leds_layout_checkp4')]; + var ul = document.createElement("ul"); + ul.className = "checklist" + + for (var i = 0; i < liList.length; i++) { + var li = document.createElement("li"); + li.innerHTML = liList[i]; + ul.appendChild(li); + } + showInfoDialog('checklist', "", ul); + }); + + // nav + $('#leds_cfg_nav a[data-toggle="tab"]').off().on('shown.bs.tab', function (e) { + var target = $(e.target).attr("href") // activated tab + if (target == "#menu_gencfg" && !ledsCustomCfgInitialized) { + $('#leds_custom_updsim').trigger('click'); + ledsCustomCfgInitialized = true; + } + + blacklist_editor.on('change', function () { + // only update preview, if config is valid + if (blacklist_editor.validate().length <= 0) { + + ledBlacklist = blacklist_editor.getEditor("root.ledBlacklist").getValue(); + finalLedArray = blackListLeds(nonBlacklistLedArray, ledBlacklist); + createLedPreview(finalLedArray); + aceEdt.set(finalLedArray); + } + + // change save button state based on validation result + blacklist_editor.validate().length || window.readOnlyMode ? $('#btn_bl_save').attr('disabled', true) : $('#btn_bl_save').attr('disabled', false); + }); + + }); + + //************************************************** + // Handle LED-Device Configuration + //************************************************** + + // External properties properties, 2-dimensional arry of [ledType][key] + devicesProperties = {}; + + $("#leddevices").off().on("change", function () { + var generalOptions = window.serverSchema.properties.device; - // Modified schema entry "hardwareLedCount" in generalOptions to minimum LedCount var ledType = $(this).val(); - //philipshueentertainment backward fix - if(ledType == "philipshueentertainment") ledType = "philipshue"; + // philipshueentertainment backward fix + if (ledType == "philipshueentertainment") + ledType = "philipshue"; var specificOptions = window.serverSchema.properties.alldevices[ledType]; - conf_editor = createJsonEditor('editor_container', { - generalOptions : generalOptions, - specificOptions : specificOptions, - }); - var values_general = {}; - var values_specific = {}; - var isCurrentDevice = (window.serverConfig.device.type == ledType); + conf_editor = createJsonEditor('editor_container_leddevice', { + specificOptions: specificOptions, + generalOptions: generalOptions, + }); - for(var key in window.serverConfig.device) { - if (key != "type" && key in generalOptions.properties) values_general[key] = window.serverConfig.device[key]; - }; - conf_editor.getEditor("root.generalOptions").setValue( values_general ); + var values_general = {}; + var values_specific = {}; + var isCurrentDevice = (window.serverConfig.device.type == ledType); - if (isCurrentDevice) - { - var specificOptions_val = conf_editor.getEditor("root.specificOptions").getValue(); - for(var key in specificOptions_val){ - values_specific[key] = (key in window.serverConfig.device) ? window.serverConfig.device[key] : specificOptions_val[key]; - }; - conf_editor.getEditor("root.specificOptions").setValue( values_specific ); - }; + for (var key in window.serverConfig.device) { + if (key != "type" && key in generalOptions.properties) values_general[key] = window.serverConfig.device[key]; + }; + conf_editor.getEditor("root.generalOptions").setValue(values_general); - // change save button state based on validation result - conf_editor.validate().length || window.readOnlyMode ? $('#btn_submit_controller').attr('disabled', true) : $('#btn_submit_controller').attr('disabled', false); + if (isCurrentDevice) { + var specificOptions_val = conf_editor.getEditor("root.specificOptions").getValue(); + for (var key in specificOptions_val) { + values_specific[key] = (key in window.serverConfig.device) ? window.serverConfig.device[key] : specificOptions_val[key]; + }; + conf_editor.getEditor("root.specificOptions").setValue(values_specific); + }; - conf_editor.on('change',function() { - window.readOnlyMode ? $('#btn_cl_save').attr('disabled', true) : $('#btn_submit').attr('disabled', false); - window.readOnlyMode ? $('#btn_ma_save').attr('disabled', true) : $('#btn_submit').attr('disabled', false); - window.readOnlyMode ? $('#leds_custom_save').attr('disabled', true) : $('#btn_submit').attr('disabled', false); - }); + // change save button state based on validation result + conf_editor.validate().length || window.readOnlyMode ? $('#btn_submit_controller').attr('disabled', true) : $('#btn_submit_controller').attr('disabled', false); - // led controller sepecific wizards - $('#btn_wiz_holder').html(""); - $('#btn_led_device_wiz').off(); + // led controller sepecific wizards + $('#btn_wiz_holder').html(""); + $('#btn_led_device_wiz').off(); - if(ledType == "philipshue") { - $('#root_specificOptions_useEntertainmentAPI').bind("change", function() { + if (ledType == "philipshue") { + $('#root_specificOptions_useEntertainmentAPI').bind("change", function () { var ledWizardType = (this.checked) ? "philipshueentertainment" : ledType; var data = { type: ledWizardType }; var hue_title = (this.checked) ? 'wiz_hue_e_title' : 'wiz_hue_title'; @@ -596,150 +646,901 @@ $(document).ready(function() { }); $("#root_specificOptions_useEntertainmentAPI").trigger("change"); } -/* - else if(ledType == "wled") { - var ledWizardType = (this.checked) ? "wled" : ledType; - var data = { type: ledWizardType }; - var wled_title = 'wiz_wled_title'; - changeWizard(data, wled_title, startWizardWLED); - } -*/ - else if(ledType == "atmoorb") { - var ledWizardType = (this.checked) ? "atmoorb" : ledType; - var data = { type: ledWizardType }; - var atmoorb_title = 'wiz_atmoorb_title'; - changeWizard(data, atmoorb_title, startWizardAtmoOrb); - } - else if(ledType == "cololight") { - var ledWizardType = (this.checked) ? "cololight" : ledType; - var data = { type: ledWizardType }; - var cololight_title = 'wiz_cololight_title'; - changeWizard(data, cololight_title, startWizardCololight); - } - else if(ledType == "yeelight") { - var ledWizardType = (this.checked) ? "yeelight" : ledType; - var data = { type: ledWizardType }; - var yeelight_title = 'wiz_yeelight_title'; - changeWizard(data, yeelight_title, startWizardYeelight); - } + else if (ledType == "atmoorb") { + var ledWizardType = (this.checked) ? "atmoorb" : ledType; + var data = { type: ledWizardType }; + var atmoorb_title = 'wiz_atmoorb_title'; + changeWizard(data, atmoorb_title, startWizardAtmoOrb); + } + else if (ledType == "yeelight") { + var ledWizardType = (this.checked) ? "yeelight" : ledType; + var data = { type: ledWizardType }; + var yeelight_title = 'wiz_yeelight_title'; + changeWizard(data, yeelight_title, startWizardYeelight); + } function changeWizard(data, hint, fn) { $('#btn_wiz_holder').html("") - createHint("wizard", $.i18n(hint), "btn_wiz_holder","btn_led_device_wiz"); - $('#btn_led_device_wiz').off().on('click', data , fn); + createHint("wizard", $.i18n(hint), "btn_wiz_holder", "btn_led_device_wiz"); + $('#btn_led_device_wiz').off().on('click', data, fn); } - }); + + conf_editor.on('ready', function () { + var hwLedCountDefault = 1; + var colorOrderDefault = "rgb"; + + switch (ledType) { + case "cololight": + case "wled": + case "nanoleaf": + showAllDeviceInputOptions("hostList", false); + case "adalight": + case "atmo": + case "dmx": + case "karate": + case "sedu": + case "tpm2": + case "apa102": + case "apa104": + case "ws2801": + case "lpd6803": + case "lpd8806": + case "p9813": + case "sk6812spi": + case "sk6822spi": + case "sk9822": + case "ws2812spi": + case "piblaster": + discover_device(ledType); + hwLedCountDefault = 1; + colorOrderDefault = "rgb"; + break; + + case "philipshue": + var lights = conf_editor.getEditor("root.specificOptions.lightIds").getValue(); + hwLedCountDefault = lights.length; + colorOrderDefault = "rgb"; + break; + + case "yeelight": + conf_editor.getEditor("root.generalOptions").disable(); + var lights = conf_editor.getEditor("root.specificOptions.lights").getValue(); + hwLedCountDefault = lights.length; + colorOrderDefault = "rgb"; + break; + + case "atmoorb": + conf_editor.getEditor("root.generalOptions").disable(); + + var configruedOrbIds = conf_editor.getEditor("root.specificOptions.orbIds").getValue().trim(); + if (configruedOrbIds.length !== 0) { + hwLedCountDefault = configruedOrbIds.split(",").map(Number).length; + } else { + hwLedCountDefault = 0; + } + colorOrderDefault = "rgb"; + break; + + default: + } + + if (ledType !== window.serverConfig.device.type) { + var hwLedCount = conf_editor.getEditor("root.generalOptions.hardwareLedCount") + if (hwLedCount) { + hwLedCount.setValue(hwLedCountDefault); + } + var colorOrder = conf_editor.getEditor("root.generalOptions.colorOrder") + if (colorOrder) { + colorOrder.setValue(colorOrderDefault); + } + } + }); + + conf_editor.on('change', function () { + //Check, if device can be identified/tested and/or saved + var canIdentify = false; + var canSave = false; + + switch (ledType) { + case "cololight": + case "wled": + var hostList = conf_editor.getEditor("root.specificOptions.hostList").getValue(); + if (hostList !== "SELECT") { + var host = conf_editor.getEditor("root.specificOptions.host").getValue(); + if (host !== "") { + canIdentify = true; + canSave = true; + } + } + break; + + case "nanoleaf": + var hostList = conf_editor.getEditor("root.specificOptions.hostList").getValue(); + if (hostList !== "SELECT") { + var host = conf_editor.getEditor("root.specificOptions.host").getValue(); + var token = conf_editor.getEditor("root.specificOptions.token").getValue(); + if (host !== "" && token !== "") { + canIdentify = true; + canSave = true; + } + } + break; + + case "adalight": + var output = conf_editor.getEditor("root.specificOptions.output").getValue(); + if (output !== "NONE" && output !== "SELECT" && output !== "") { + canIdentify = true; + } + case "atmo": + case "dmx": + case "karate": + case "sedu": + case "tpm2": + case "apa102": + case "apa104": + case "ws2801": + case "lpd6803": + case "lpd8806": + case "p9813": + case "sk6812spi": + case "sk6822spi": + case "sk9822": + case "ws2812spi": + case "piblaster": + var output = conf_editor.getEditor("root.specificOptions.output").getValue(); + if (output !== "NONE" && output !== "SELECT" && output !== "") { + canSave = true; + } + break; + default: + canIdentify = false; + canSave = true; + } + + if (canIdentify) { + $("#btn_test_controller").removeClass('hidden'); + $('#btn_test_controller').attr('disabled', false); + } + else { + $('#btn_test_controller').attr('disabled', true); + } + + var hardwareLedCount = conf_editor.getEditor("root.generalOptions.hardwareLedCount").getValue(); + if (hardwareLedCount < 1) { + canSave = false; + } + + if (canSave) { + if (!window.readOnlyMode) { + $('#btn_submit_controller').attr('disabled', false); + } + } + else { + $('#btn_submit_controller').attr('disabled', true); + } + + window.readOnlyMode ? $('#btn_cl_save').attr('disabled', true) : $('#btn_submit').attr('disabled', false); + window.readOnlyMode ? $('#btn_ma_save').attr('disabled', true) : $('#btn_submit').attr('disabled', false); + window.readOnlyMode ? $('#leds_custom_save').attr('disabled', true) : $('#btn_submit').attr('disabled', false); + }); + + conf_editor.watch('root.specificOptions.hostList', () => { + var specOptPath = 'root.specificOptions.'; + + //Disable General Options, as LED count will be resolved from device itself + conf_editor.getEditor("root.generalOptions").disable(); + + var hostList = conf_editor.getEditor("root.specificOptions.hostList") + if (hostList) { + var val = hostList.getValue(); + var showOptions = true; + + switch (val) { + case 'CUSTOM': + case '': + conf_editor.getEditor(specOptPath + "host").enable(); + conf_editor.getEditor(specOptPath + "host").setValue(""); + break; + case 'NONE': + conf_editor.getEditor(specOptPath + "host").enable(); + break; + case 'SELECT': + conf_editor.getEditor(specOptPath + "host").setValue(""); + conf_editor.getEditor(specOptPath + "host").disable(); + showOptions = false; + break; + default: + conf_editor.getEditor(specOptPath + "host").disable(); + conf_editor.getEditor(specOptPath + "host").setValue(val); + break; + } + } + + showAllDeviceInputOptions("hostList", showOptions); + }); + + conf_editor.watch('root.specificOptions.host', () => { + var host = conf_editor.getEditor("root.specificOptions.host").getValue(); + + if (host === "") { + conf_editor.getEditor("root.generalOptions.hardwareLedCount").setValue(1); + } + else { + let params = {}; + switch (ledType) { + case "cololight": + params = { host: host }; + break; + + case "nanoleaf": + var token = conf_editor.getEditor("root.specificOptions.token").getValue(); + if (token === "") { + return + } + params = { host: host, token: token }; + break; + + case "wled": + params = { host: host, filter: "info" }; + break; + default: + } + + getProperties_device(ledType, host, params); + } + }); + + conf_editor.watch('root.specificOptions.output', () => { + var output = conf_editor.getEditor("root.specificOptions.output").getValue(); + if (output === "NONE" || output === "SELECT" || output === "") { + conf_editor.getEditor("root.generalOptions.hardwareLedCount").setValue(1); + showAllDeviceInputOptions("output", false); + } + else { + showAllDeviceInputOptions("output", true); + let params = {}; + switch (ledType) { + case "atmo": + case "karate": + params = { serialPort: output }; + getProperties_device(ledType, output, params); + break; + } + } + }); + + conf_editor.watch('root.specificOptions.token', () => { + var token = conf_editor.getEditor("root.specificOptions.token").getValue(); + + if (token !== "") { + let params = {}; + + var host = ""; + switch (ledType) { + case "nanoleaf": + host = conf_editor.getEditor("root.specificOptions.host").getValue(); + if (host === "") { + return + } + params = { host: host, token: token }; + break; + default: + } + + getProperties_device(ledType, host, params); + } + }); + + //Yeelight + conf_editor.watch('root.specificOptions.lights', () => { + //Disable General Options, as LED count will be resolved from number of lights configured + conf_editor.getEditor("root.generalOptions").disable(); + + var hwLedCount = conf_editor.getEditor("root.generalOptions.hardwareLedCount") + if (hwLedCount) { + var lights = conf_editor.getEditor("root.specificOptions.lights").getValue(); + hwLedCount.setValue(lights.length); + } + }); + + //Philips Hue + conf_editor.watch('root.specificOptions.lightIds', () => { + //Disable General Options, as LED count will be resolved from number of lights configured + conf_editor.getEditor("root.generalOptions").disable(); + + var hwLedCount = conf_editor.getEditor("root.generalOptions.hardwareLedCount") + if (hwLedCount) { + var lights = conf_editor.getEditor("root.specificOptions.lightIds").getValue(); + hwLedCount.setValue(lights.length); + } + }); + + //Atmo Orb + conf_editor.watch('root.specificOptions.orbIds', () => { + //Disable General Options, as LED count will be resolved from number of lights configured + conf_editor.getEditor("root.generalOptions").disable(); + + var hwLedCount = conf_editor.getEditor("root.generalOptions.hardwareLedCount") + if (hwLedCount) { + var lights = 0; + var configruedOrbIds = conf_editor.getEditor("root.specificOptions.orbIds").getValue().trim(); + if (configruedOrbIds.length !== 0) { + lights = configruedOrbIds.split(",").map(Number); + } + hwLedCount.setValue(lights.length); + } + }); + }); //philipshueentertainment backward fix - if(window.serverConfig.device.type == "philipshueentertainment") window.serverConfig.device.type = "philipshue"; + if (window.serverConfig.device.type == "philipshueentertainment") window.serverConfig.device.type = "philipshue"; - // create led device selection - var ledDevices = window.serverInfo.ledDevices.available; - var devRPiSPI = ['apa102', 'apa104', 'ws2801', 'lpd6803', 'lpd8806', 'p9813', 'sk6812spi', 'sk6822spi', 'sk9822', 'ws2812spi']; - var devRPiPWM = ['ws281x']; - var devRPiGPIO = ['piblaster']; - - var devNET = ['atmoorb', 'cololight', 'fadecandy', 'philipshue', 'nanoleaf', 'tinkerforge', 'tpm2net', 'udpe131', 'udpartnet', 'udph801', 'udpraw', 'wled', 'yeelight']; - var devUSB = ['adalight', 'dmx', 'atmo', 'hyperionusbasp', 'lightpack', 'paintpack', 'rawhid', 'sedu', 'tpm2', 'karate']; + // create led device selection + var ledDevices = window.serverInfo.ledDevices.available; var optArr = [[]]; - optArr[1]=[]; - optArr[2]=[]; - optArr[3]=[]; - optArr[4]=[]; - optArr[5]=[]; + optArr[1] = []; + optArr[2] = []; + optArr[3] = []; + optArr[4] = []; + optArr[5] = []; - for (var idx=0; idx layoutLedCount) { + // More Hardware LEDs than on layout + $('#id_body').html(''); + $('#id_body').append('

' + $.i18n("conf_leds_config_warning") + '

'); + $('#id_body').append($.i18n('conf_leds_error_hwled_gt_layout', hardwareLedCount, layoutLedCount, hardwareLedCount - layoutLedCount)); + $('#id_body').append('
'); + $('#id_body').append($.i18n('conf_leds_note_layout_overwrite', hardwareLedCount)); + $('#id_footer').html(''); + $('#id_footer').append(''); + $('#id_footer').append(''); + } + else { + // Less Hardware LEDs than on layout + $('#id_body').html(''); + $('#id_body').append('

' + $.i18n("conf_leds_config_error") + '

'); + $('#id_body').append($.i18n('conf_leds_error_hwled_lt_layout', hardwareLedCount, layoutLedCount)); + $('#id_body').append('
'); + $('#id_body').append($.i18n('conf_leds_note_layout_overwrite', hardwareLedCount)); + $('#id_footer').html(''); + $('#id_footer').append(''); + } - var general = conf_editor.getEditor("root.generalOptions").getValue(); - var specific = conf_editor.getEditor("root.specificOptions").getValue(); - for(var key in general){ - result.device[key] = general[key]; - } + $("#modal_dialog").modal({ + backdrop: "static", + keyboard: false, + show: true + }); - for(var key in specific){ - result.device[key] = specific[key]; - } - result.device.type=ledDevice; - requestWriteConfig(result) - }); + $('#btn_back').off().on('click', function () { + //Continue with the configuration + }); - removeOverlay(); + $('#btn_continue').off().on('click', function () { + saveLedConfig(false); + }); + + $('#btn_overwrite').off().on('click', function () { + saveLedConfig(true); + }); + } + }); + + removeOverlay(); }); + +function saveLedConfig(genDefLayout = false) { + var ledType = $("#leddevices").val(); + var result = { device: {} }; + + var general = conf_editor.getEditor("root.generalOptions").getValue(); + var specific = conf_editor.getEditor("root.specificOptions").getValue(); + for (var key in general) { + result.device[key] = general[key]; + } + + for (var key in specific) { + result.device[key] = specific[key]; + } + result.device.type = ledType; + + var ledConfig = {}; + var leds = []; + + var hardwareLedCount = conf_editor.getEditor("root.generalOptions.hardwareLedCount").getValue(); + result.device.hardwareLedCount = hardwareLedCount; + + // Special handling per LED-type + switch (ledType) { + case "cololight": + + var host = conf_editor.getEditor("root.specificOptions.host").getValue(); + result.smoothing = { enable: false }; + + if (genDefLayout === true) { + if (devicesProperties[ledType][host].modelType === "Strip") { + ledConfig = { + "classic": { + "top": hardwareLedCount / 2, + "bottom": 0, + "left": hardwareLedCount / 4, + "right": hardwareLedCount / 4, + "position": hardwareLedCount / 4 * 3 + }, + "matrix": { "cabling": "snake", "ledshoriz": 1, "ledsvert": 1, "start": "top-left" } + }; + leds = createClassicLedLayoutSimple(hardwareLedCount / 2, hardwareLedCount / 4, hardwareLedCount / 4, 0, hardwareLedCount / 4 * 3, false); + } + else { + ledConfig = { + "classic": { + "top": hardwareLedCount, + "bottom": 0, + "left": 0, + "right": 0 + }, + "matrix": { "cabling": "snake", "ledshoriz": 1, "ledsvert": 1, "start": "top-left" } + }; + leds = createClassicLedLayoutSimple(hardwareLedCount, 0, 0, 0, 0, false); + } + result.ledConfig = ledConfig; + result.leds = leds; + } + break; + + case "nanoleaf": + case "wled": + result.smoothing = { enable: false }; + + case "adalight": + case "atmo": + case "dmx": + case "karate": + case "sedu": + case "tpm2": + case "apa102": + case "apa104": + case "ws2801": + case "lpd6803": + case "lpd8806": + case "p9813": + case "sk6812spi": + case "sk6822spi": + case "sk9822": + case "ws2812spi": + case "piblaster": + default: + if (genDefLayout === true) { + ledConfig = { + "classic": { + "top": hardwareLedCount, + "bottom": 0, + "left": 0, + "right": 0 + }, + "matrix": { "cabling": "snake", "ledshoriz": 1, "ledsvert": 1, "start": "top-left" } + } + ; + result.ledConfig = ledConfig; + leds = createClassicLedLayoutSimple(hardwareLedCount, 0, 0, 0, 0, false); + result.leds = leds; + } + break; + } + + //Rewrite whole LED & Layout configuration, in case changes were done accross tabs and no default layout + if (genDefLayout !== true) { + result.ledConfig = getLedConfig(); + result.leds = JSON.parse(aceEdt.getText()); + } + + requestWriteConfig(result); + location.reload(); +} + +// build dynamic enum +var updateSelectList = function (ledType, discoveryInfo) { + + // Only update, if ledType is equal of selected controller type and discovery info exists + if (ledType !== $("#leddevices").val() || !discoveryInfo.devices) { + return; + } + + let addSchemaElements = { + }; + + var key; + var enumVals = []; + var enumTitelVals = []; + var enumDefaultVal = ""; + var addSelect = false; + var addCustom = false; + + var ledTypeGroup; + + if ($.inArray(ledType, devNET) != -1) { + ledTypeGroup = "devNET"; + } else if ($.inArray(ledType, devSerial) != -1) { + ledTypeGroup = "devSerial"; + } else if ($.inArray(ledType, devRPiSPI) != -1) { + ledTypeGroup = "devRPiSPI"; + } else if ($.inArray(ledType, devRPiGPIO) != -1) { + ledTypeGroup = "devRPiGPIO"; + } + + var specOpt = conf_editor.getEditor('root.specificOptions'); // get specificOptions of the editor + + switch (ledTypeGroup) { + case "devNET": + key = "hostList"; + + if (discoveryInfo.devices.length === 0) { + enumVals.push("NONE"); + enumTitelVals.push($.i18n('edt_dev_spec_devices_discovered_none')); + } + else { + var name; + + var discoveryMethod = "ssdp"; + if (discoveryInfo.discoveryMethod) { + discoveryMethod = discoveryInfo.discoveryMethod; + } + + for (const device of discoveryInfo.devices) { + var name; + var host; + + switch (ledType) { + case "nanoleaf": + if (discoveryMethod === "ssdp") { + name = device.other["nl-devicename"]; + } + else { + name = device.name; + } + break; + case "cololight": + if (discoveryMethod === "ssdp") { + name = device.hostname; + } + else { + name = device.name; + } + break; + case "wled": + name = device.name; + break; + default: + name = device.name; + } + + if (discoveryMethod === "ssdp") { + host = device.ip; + } + else { + host = device.name; + } + + enumVals.push(host); + if (host !== name) { + enumTitelVals.push(name + " (" + host + ")"); + } + else { + enumTitelVals.push(host); + } + + addCustom = true; + + // Select configured device + var configuredDeviceType = window.serverConfig.device.type; + var configuredHost = window.serverConfig.device.hostList; + if (ledType === configuredDeviceType && $.inArray(configuredHost, enumVals) != -1) { + enumDefaultVal = configuredHost; + } + else { + addSelect = true; + addCustom = true; + } + } + } + break; + + case "devSerial": + key = "output"; + + if (discoveryInfo.devices.length == 0) { + enumVals.push("NONE"); + enumTitelVals.push($.i18n('edt_dev_spec_devices_discovered_none')); + $('#btn_submit_controller').attr('disabled', true); + showAllDeviceInputOptions(key, false); + } + else { + switch (ledType) { + case "adalight": + case "atmo": + case "dmx": + case "karate": + case "sedu": + case "tpm2": + for (const device of discoveryInfo.devices) { + enumVals.push(device.portName); + enumTitelVals.push(device.portName + " (" + device.vendorIdentifier + "|" + device.productIdentifier + ") - " + device.manufacturer); + } + + // Select configured device + var configuredDeviceType = window.serverConfig.device.type; + var configuredOutput = window.serverConfig.device.output; + if (ledType === configuredDeviceType && $.inArray(configuredOutput, enumVals) != -1) { + enumDefaultVal = configuredOutput; + } + else { + addSelect = true; + } + + break; + default: + } + } + break; + case "devRPiSPI": + case "devRPiGPIO": + key = "output"; + + if (discoveryInfo.devices.length == 0) { + enumVals.push("NONE"); + enumTitelVals.push($.i18n('edt_dev_spec_devices_discovered_none')); + $('#btn_submit_controller').attr('disabled', true); + showAllDeviceInputOptions(key, false); + } + else { + switch (ledType) { + case "apa102": + case "apa104": + case "ws2801": + case "lpd6803": + case "lpd8806": + case "p9813": + case "sk6812spi": + case "sk6822spi": + case "sk9822": + case "ws2812spi": + case "piblaster": + for (const device of discoveryInfo.devices) { + enumVals.push(device.systemLocation); + enumTitelVals.push(device.deviceName + " (" + device.systemLocation + ")"); + } + + // Select configured device + var configuredDeviceType = window.serverConfig.device.type; + var configuredOutput = window.serverConfig.device.output; + if (ledType === configuredDeviceType && $.inArray(configuredOutput, enumVals) != -1) { + enumDefaultVal = configuredOutput; + } + else { + addSelect = true; + } + + break; + default: + } + } + break; + default: + } + + if (enumVals.length > 0) { + updateJsonEditorSelection(specOpt, key, addSchemaElements, enumVals, enumTitelVals, enumDefaultVal, addSelect, addCustom); + } +} + +async function discover_device(ledType, params) { + const result = await requestLedDeviceDiscovery(ledType, params); + + var discoveryResult; + if (result && !result.error) { + discoveryResult = result.info; + } + else { + discoveryResult = { + devices: [], + ledDevicetype: ledType + } + } + + updateSelectList(ledType, discoveryResult); +} + +async function getProperties_device(ledType, key, params) { + // Take care that connfig cannot be saved during background processing + $('#btn_submit_controller').attr('disabled', true); + + //Create ledType cache entry + if (!devicesProperties[ledType]) { + devicesProperties[ledType] = {}; + } + + // get device's properties, if properties not available in chache + if (!devicesProperties[ledType][key]) { + const res = await requestLedDeviceProperties(ledType, params); + if (res && !res.error) { + var deviceProperties = res.info.properties; + + if (!jQuery.isEmptyObject(deviceProperties)) { + devicesProperties[ledType][key] = deviceProperties; + + if (!window.readOnlyMode) { + $('#btn_submit_controller').attr('disabled', false); + } + } + else { + $('#btn_submit_controller').attr('disabled', true); + } + } + } + + updateElements(ledType, key); +} + +async function identify_device(type, params) { + // Take care that connfig cannot be saved and identification cannot be retriggerred during background processing + $('#btn_submit_controller').attr('disabled', true); + $('#btn_test_controller').attr('disabled', true); + + await requestLedDeviceIdentification(type, params); + + $('#btn_test_controller').attr('disabled', false); + if (!window.readOnlyMode) { + $('#btn_submit_controller').attr('disabled', false); + } +} + +function updateElements(ledType, key) { + if (devicesProperties[ledType][key]) { + switch (ledType) { + case "cololight": + var ledProperties = devicesProperties[ledType][key]; + + var hardwareLedCount = 1; + if (ledProperties) { + hardwareLedCount = ledProperties.ledCount; + } + conf_editor.getEditor("root.generalOptions.hardwareLedCount").setValue(hardwareLedCount); + break; + case "wled": + var ledProperties = devicesProperties[ledType][key]; + + if (ledProperties && ledProperties.leds) { + hardwareLedCount = ledProperties.leds.count; + } + conf_editor.getEditor("root.generalOptions.hardwareLedCount").setValue(hardwareLedCount); + break; + + case "nanoleaf": + var ledProperties = devicesProperties[ledType][key]; + + if (ledProperties && ledProperties.panelLayout.layout) { + //Identify non-LED type panels, e.g. Rhythm (1) and Shapes Controller (12) + var nonLedNum = 0; + for (const panel of ledProperties.panelLayout.layout.positionData) { + if (panel.shapeType === 1 || panel.shapeType === 12) { + nonLedNum++; + } + } + hardwareLedCount = ledProperties.panelLayout.layout.numPanels - nonLedNum; + } + conf_editor.getEditor("root.generalOptions.hardwareLedCount").setValue(hardwareLedCount); + + break; + + case "atmo": + case "karate": + var ledProperties = devicesProperties[ledType][key]; + + if (ledProperties && ledProperties.ledCount) { + if (ledProperties.ledCount.length > 0) { + var configuredLedCount = window.serverConfig.device.hardwareLedCount; + var generalOpt = conf_editor.getEditor('root.generalOptions'); + updateJsonEditorSelection(generalOpt, "hardwareLedCount", {}, ledProperties.ledCount, [], configuredLedCount); + } + } + break; + + default: + } + } +} + +function showInputOptions(path, elements, state) { + for (var i = 0; i < elements.length; i++) { + $('[data-schemapath="' + path + '.' + elements[i] + '"]').toggle(state); + } +} + +function showInputOptionsForKey(editor, item, showForKey, state) { + var elements = []; + for (var key in editor.schema.properties[item].properties) { + if (showForKey !== key) { + var accessLevel = editor.schema.properties[item].properties[key].access; + + //Only enable elements, if access level compliant + if (state && isAccessLevelCompliant(accessLevel)) { + elements.push(key); + } + } + showInputOptions("root." + item, elements, state); + } +} + +function showAllDeviceInputOptions(showForKey, state) { + showInputOptionsForKey(conf_editor, "generalOptions", showForKey, state); + showInputOptionsForKey(conf_editor, "specificOptions", showForKey, state); +} diff --git a/assets/webconfig/js/ui_utils.js b/assets/webconfig/js/ui_utils.js old mode 100644 new mode 100755 index 3f9ea3ef..126efbec --- a/assets/webconfig/js/ui_utils.js +++ b/assets/webconfig/js/ui_utils.js @@ -53,7 +53,7 @@ function updateSessions() { if (sess && sess.length) { window.wSess = []; for (var i = 0; i < sess.length; i++) { - if (sess[i].type == "_hyperiond-http._tcp.") { + if (sess[i].type == "_http._tcp." || sess[i].type == "_https._tcp." || sess[i].type == "_hyperiond-http._tcp.") { window.wSess.push(sess[i]); } } @@ -134,13 +134,13 @@ function updateHyperionInstanceListing() { var currInstMarker = (data[key].instance == window.currentHyperionInstance) ? "component-on" : ""; var html = '
  • \ - \ -
    \ - \ - '+ data[key].friendly_name + ' \ -
    \ -
    \ -
  • ' + \ +
    \ + \ + '+ data[key].friendly_name + ' \ +
    \ +
    \ + ' if (data.length - 1 > key) html += '
  • ' @@ -183,7 +183,6 @@ function initLanguageSelection() { langText = availLangText[langIdx]; } } - //console.log("langLocale: ", langLocale, "langText: ", langText); $('#language-select').prop('title', langText); $("#language-select").val(langIdx); @@ -463,6 +462,170 @@ function createJsonEditor(container, schema, setconfig, usePanel, arrayre) { return editor; } +function updateJsonEditorSelection(editor, key, addElements, newEnumVals, newTitelVals, newDefaultVal, addSelect, addCustom, addCustomAsFirst, customText) { + var orginalProperties = editor.schema.properties[key]; + + var newSchema = []; + newSchema[key] = + { + "type": "string", + "enum": [], + "required": true, + "options": { "enum_titles": [], "infoText": "" }, + "propertyOrder": 1 + }; + + //Add additional elements to overwrite defaults + for (var item in addElements) { + newSchema[key][item] = addElements[item]; + } + + if (orginalProperties) { + if (orginalProperties["title"]) { + newSchema[key]["title"] = orginalProperties["title"]; + } + + if (orginalProperties["options"] && orginalProperties["options"]["infoText"]) { + newSchema[key]["options"]["infoText"] = orginalProperties["options"]["infoText"]; + } + + if (orginalProperties["propertyOrder"]) { + newSchema[key]["propertyOrder"] = orginalProperties["propertyOrder"]; + } + } + + if (addCustom) { + + if (newTitelVals.length === 0) { + newTitelVals = [...newEnumVals]; + } + + if (!!!customText) { + customText = "edt_conf_enum_custom"; + } + + if (addCustomAsFirst) { + newEnumVals.unshift("CUSTOM"); + newTitelVals.unshift(customText); + } else { + newEnumVals.push("CUSTOM"); + newTitelVals.push(customText); + } + + if (newSchema[key].options.infoText) { + var customInfoText = newSchema[key].options.infoText + "_custom"; + newSchema[key].options.infoText = customInfoText; + } + } + + if (addSelect) { + newEnumVals.unshift("SELECT"); + newTitelVals.unshift("edt_conf_enum_please_select"); + newDefaultVal = "SELECT"; + } + + if (newEnumVals) { + newSchema[key]["enum"] = newEnumVals; + } + + if (newTitelVals) { + newSchema[key]["options"]["enum_titles"] = newTitelVals; + } + if (newDefaultVal) { + newSchema[key]["default"] = newDefaultVal; + } + + editor.original_schema.properties[key] = orginalProperties; + editor.schema.properties[key] = newSchema[key]; + + editor.removeObjectProperty(key); + delete editor.cached_editors[key]; + editor.addObjectProperty(key); +} + +function updateJsonEditorMultiSelection(editor, key, addElements, newEnumVals, newTitelVals, newDefaultVal) { + var orginalProperties = editor.schema.properties[key]; + + var newSchema = []; + newSchema[key] = + { + "type": "array", + "format": "select", + "items": { + "type": "string", + "enum": [], + "options": { "enum_titles": [] }, + }, + "options": { "infoText": "" }, + "default": [], + "propertyOrder": 1 + }; + + //Add additional elements to overwrite defaults + for (var item in addElements) { + newSchema[key][item] = addElements[item]; + } + + if (orginalProperties) { + if (orginalProperties["title"]) { + newSchema[key]["title"] = orginalProperties["title"]; + } + + if (orginalProperties["options"] && orginalProperties["options"]["infoText"]) { + newSchema[key]["options"]["infoText"] = orginalProperties["options"]["infoText"]; + } + + if (orginalProperties["propertyOrder"]) { + newSchema[key]["propertyOrder"] = orginalProperties["propertyOrder"]; + } + } + + if (newEnumVals) { + newSchema[key]["items"]["enum"] = newEnumVals; + } + + if (newTitelVals) { + newSchema[key]["items"]["options"]["enum_titles"] = newTitelVals; + } + + if (newDefaultVal) { + newSchema[key]["default"] = newDefaultVal; + } + + editor.original_schema.properties[key] = orginalProperties; + editor.schema.properties[key] = newSchema[key]; + + editor.removeObjectProperty(key); + delete editor.cached_editors[key]; + editor.addObjectProperty(key); +} + +function updateJsonEditorRange(editor, key, minimum, maximum, defaultValue, step) { + var orginalProperties = editor.schema.properties[key]; + var newSchema = []; + newSchema[key] = orginalProperties; + + if (minimum) { + newSchema[key]["minimum"] = minimum; + } + if (maximum) { + newSchema[key]["maximum"] = maximum; + } + if (defaultValue) { + newSchema[key]["default"] = defaultValue; + } + if (step) { + newSchema[key]["step"] = step; + } + + editor.original_schema.properties[key] = orginalProperties; + editor.schema.properties[key] = newSchema[key]; + + editor.removeObjectProperty(key); + delete editor.cached_editors[key]; + editor.addObjectProperty(key); +} + function buildWL(link, linkt, cl) { var baseLink = "https://docs.hyperion-project.org/"; var lang; @@ -655,6 +818,48 @@ function createOptPanel(phicon, phead, bodyid, footerid) { return createPanel(phead, "", pfooter, "panel-default", bodyid); } +function compareTwoValues(key1, key2, order = 'asc') { + return function innerSort(a, b) { + if (!a.hasOwnProperty(key1) || !b.hasOwnProperty(key1)) { + // property key1 doesn't exist on either object + return 0; + } + + const varA1 = (typeof a[key1] === 'string') + ? a[key1].toUpperCase() : a[key1]; + const varB1 = (typeof b[key1] === 'string') + ? b[key1].toUpperCase() : b[key1]; + + let comparison = 0; + if (varA1 > varB1) { + comparison = 1; + } else { + if (varA1 < varB1) { + comparison = -1; + } else { + if (!a.hasOwnProperty(key2) || !b.hasOwnProperty(key2)) { + // property key2 doesn't exist on either object + return 0; + } + + const varA2 = (typeof a[key2] === 'string') + ? a[key2].toUpperCase() : a[key2]; + const varB2 = (typeof b[key1] === 'string') + ? b[key2].toUpperCase() : b[key2]; + + if (varA2 > varB2) { + comparison = 1; + } else { + comparison = -1; + } + } + } + return ( + (order === 'desc') ? (comparison * -1) : comparison + ); + }; +} + function sortProperties(list) { for (var key in list) { list[key].key = key; @@ -876,3 +1081,19 @@ function handleDarkMode() { $('#btn_darkmode_icon').removeClass('fa fa-moon-o'); $('#btn_darkmode_icon').addClass('fa fa-sun-o'); } + +function isAccessLevelCompliant(accessLevel) { + var isOK = true; + if (accessLevel) { + if (accessLevel === 'system') { + isOK = false; + } + else if (accessLevel === 'advanced' && storedAccess === 'default') { + isOK = false; + } + else if (accessLevel === 'expert' && storedAccess !== 'expert') { + isOK = false; + } + } + return isOK +} diff --git a/assets/webconfig/js/wizard.js b/assets/webconfig/js/wizard.js old mode 100644 new mode 100755 index 36bc09aa..620b3b62 --- a/assets/webconfig/js/wizard.js +++ b/assets/webconfig/js/wizard.js @@ -424,7 +424,7 @@ function startWizardCC() { } //create html $('#wiz_header').html('' + $.i18n('wiz_cc_title')); - $('#wizp1_body').html('

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

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

    '); + $('#wizp1_body').html('

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

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

    '); $('#wizp1_footer').html(''); $('#wizp2_body').html('
    '); $('#wizp2_footer').html(''); @@ -437,10 +437,9 @@ 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 @@ -788,8 +787,6 @@ async function discover_hue_bridges() { const r = res.info; // Process devices returned by discovery - console.log(r); - if (r.devices.length == 0) $('#wiz_hue_ipstate').html($.i18n('wiz_hue_failure_ip')); else { @@ -832,7 +829,7 @@ async function identify_hue_device(hostAddress, username, id) { $('#btn_wiz_save').attr('disabled', true); let params = { host: hostAddress, user: username, lightId: id }; - const res = await requestLedDeviceIdentification('philipshue', params); + await requestLedDeviceIdentification('philipshue', params); if (!window.readOnlyMode) { $('#btn_wiz_save').attr('disabled', false); @@ -1187,7 +1184,6 @@ function get_hue_lights() { } (cC == 0 || window.readOnlyMode) ? $('#btn_wiz_save').attr("disabled", true) : $('#btn_wiz_save').attr("disabled", false); - }); } $('.hue_sel_watch').trigger('change'); @@ -1208,108 +1204,6 @@ function abortConnection(UserInterval) { $("#wiz_hue_usrstate").html($.i18n('wiz_hue_failure_connection')); } -//**************************** -// Wizard WLED -//**************************** -var lights = null; -function startWizardWLED(e) { - //create html - - var wled_title = 'wiz_wled_title'; - var wled_intro1 = 'wiz_wled_intro1'; - - $('#wiz_header').html('' + $.i18n(wled_title)); - $('#wizp1_body').html('

    ' + $.i18n(wled_title) + '

    ' + $.i18n(wled_intro1) + '

    '); - $('#wizp1_footer').html(''); - - /*$('#wizp2_body').html('
    '); - - $('#wh_topcontainer').append(''); - - $('#wizp2_body').append(''); - - createTable("lidsh", "lidsb", "hue_ids_t"); - $('.lidsh').append(createTableRow([$.i18n('edt_dev_spec_lights_title'),$.i18n('wiz_pos'),$.i18n('wiz_identify')], true)); - $('#wizp2_footer').html(''); -*/ - //open modal - $("#wizard_modal").modal({ - backdrop: "static", - keyboard: false, - show: true - }); - - //listen for continue - $('#btn_wiz_cont').off().on('click', function () { - /* For testing only - - discover_wled(); - - var hostAddress = conf_editor.getEditor("root.specificOptions.host").getValue(); - if(hostAddress != "") - { - getProperties_wled(hostAddress,"info"); - identify_wled(hostAddress) - } - - For testing only */ - }); -} - -async function discover_wled() { - const res = await requestLedDeviceDiscovery('wled'); - - // TODO: error case unhandled - // res can be: false (timeout) or res.error (not found) - if (res && !res.error) { - const r = res.info - - // Process devices returned by discovery - console.log(r); - - if (r.devices.length == 0) - $('#wiz_hue_ipstate').html($.i18n('wiz_hue_failure_ip')); - else { - for (const device of r.devices) { - console.log("Device:", device); - - var ip = device.hostname + ":" + device.port; - console.log("Host:", ip); - - //wledIPs.push({internalipaddress : ip}); - } - } - } -} - -async function getProperties_wled(hostAddress, resourceFilter) { - let params = { host: hostAddress, filter: resourceFilter }; - - const res = await requestLedDeviceProperties('wled', params); - - // TODO: error case unhandled - // res can be: false (timeout) or res.error (not found) - if (res && !res.error) { - const r = res.info - - // Process properties returned - console.log(r); - } -} - -async function identify_wled(hostAddress) { - - // Take care that new record cannot be save during background process - $('#btn_wiz_save').attr('disabled', true); - - let params = { host: hostAddress }; - const res = await requestLedDeviceIdentification('wled', params); - - if (!window.readOnlyMode) { - $('#btn_wiz_save').attr('disabled', false); - } -} - //**************************** // Wizard Yeelight //**************************** @@ -1438,8 +1332,17 @@ async function discover_yeelight_lights() { var light = {}; light.host = device.hostname; light.port = device.port; - light.name = device.other.name; - light.model = device.other.model; + + if (device.txt) { + light.name = device.name; + light.model = device.txt.md; + //Yeelight does not provide correct API port with mDNS response, use default one + light.port = 55443; + } + else { + light.name = device.other.name; + light.model = device.other.model; + } lights.push(light); } } @@ -1469,7 +1372,8 @@ async function discover_yeelight_lights() { } function assign_yeelight_lights() { - var models = ['color', 'color1', 'color2', 'color4', 'stripe', 'strip1']; + // Model mappings, see https://www.home-assistant.io/integrations/yeelight/ + var models = ['color', 'color1', 'YLDP02YL', 'YLDP02YL', 'color2', 'YLDP06YL', 'color4', 'YLDP13YL', 'stripe', 'YLDD04YL', 'strip1', 'YLDD01YL', 'YLDD02YL']; // If records are left for configuration if (Object.keys(lights).length > 0) { @@ -1534,7 +1438,7 @@ function assign_yeelight_lights() { $('.yee_sel_watch').trigger('change'); } else { - var noLightsTxt = '

    ' + $.i18n('wiz_noLights','Yeelights') + '

    '; + var noLightsTxt = '

    ' + $.i18n('wiz_noLights', 'Yeelights') + '

    '; $('#wizp2_body').append(noLightsTxt); } } @@ -1555,15 +1459,14 @@ async function getProperties_yeelight(hostname, port) { } async function identify_yeelight_device(hostname, port) { - // Take care that new record cannot be save during background process $('#btn_wiz_save').attr('disabled', true); let params = { hostname: hostname, port: port }; - const res = await requestLedDeviceIdentification("yeelight", params); + await requestLedDeviceIdentification("yeelight", params); if (!window.readOnlyMode) { - $('#btn_wiz_save').attr('disabled', false); + $('#btn_wiz_save').attr('disabled', false); } } @@ -1675,14 +1578,12 @@ async function discover_atmoorb_lights(multiCastGroup, multiCastPort) { var light = {}; var params = {}; - if (multiCastGroup !== "") - { + if (multiCastGroup !== "") { params.multiCastGroup = multiCastGroup; } - if (multiCastPort !== 0) - { - params.multiCastPort = multiCastPort; + if (multiCastPort !== 0) { + params.multiCastPort = multiCastPort; } // Get discovered lights @@ -1692,7 +1593,7 @@ async function discover_atmoorb_lights(multiCastGroup, multiCastPort) { // res can be: false (timeout) or res.error (not found) if (res && !res.error) { const r = res.info; - + // Process devices returned by discovery for (const device of r.devices) { if (device.id !== "") { @@ -1774,7 +1675,7 @@ function assign_atmoorb_lights() { $('.lidsb').append(createTableRow([orbId + lightAnnotation, '', ''])); } @@ -1793,303 +1694,19 @@ function assign_atmoorb_lights() { $('.orb_sel_watch').trigger('change'); } else { - var noLightsTxt = '

    ' + $.i18n('wiz_noLights','AtmoOrbs') + '

    '; + var noLightsTxt = '

    ' + $.i18n('wiz_noLights', 'AtmoOrbs') + '

    '; $('#wizp2_body').append(noLightsTxt); } } async function identify_atmoorb_device(orbId) { - // Take care that new record cannot be save during background process $('#btn_wiz_save').attr('disabled', true); let params = { id: orbId }; - const res = await requestLedDeviceIdentification("atmoorb", params); + await requestLedDeviceIdentification("atmoorb", params); if (!window.readOnlyMode) { - $('#btn_wiz_save').attr('disabled', false); - } -} - -//**************************** -// Wizard/Routines Nanoleaf -//**************************** -async function discover_nanoleaf() { - const res = await requestLedDeviceDiscovery('nanoleaf'); - - // TODO: error case unhandled - // res can be: false (timeout) or res.error (not found) - if (res && !res.error) { - const r = res.info - - // Process devices returned by discovery - console.log(r); - - if (r.devices.length == 0) - $('#wiz_hue_ipstate').html($.i18n('wiz_hue_failure_ip')); - else { - for (const device of r.devices) { - console.log("Device:", device); - - var ip = device.hostname + ":" + device.port; - console.log("Host:", ip); - - //nanoleafIPs.push({internalipaddress : ip}); - } - } - } -} - -async function getProperties_nanoleaf(hostAddress, authToken, resourceFilter) { - let params = { host: hostAddress, token: authToken, filter: resourceFilter }; - - const res = await requestLedDeviceProperties('nanoleaf', params); - - // TODO: error case unhandled - // res can be: false (timeout) or res.error (not found) - if (res && !res.error) { - const r = res.info - - // Process properties returned - console.log(r); - } -} - -async function identify_nanoleaf(hostAddress, authToken) { - - // Take care that new record cannot be save during background process - $('#btn_wiz_save').attr('disabled', true); - - let params = { host: hostAddress, token: authToken }; - const res = await requestLedDeviceIdentification('nanoleaf', params); - - if (!window.readOnlyMode) { - $('#btn_wiz_save').attr('disabled', false); - } -} - -//**************************** -// Wizard Cololight -//**************************** -var lights = null; -var selectedLightId = null; - -function startWizardCololight(e) { - //create html - - var cololight_title = 'wiz_cololight_title'; - var cololight_intro1 = 'wiz_cololight_intro1'; - - $('#wiz_header').html('' + $.i18n(cololight_title)); - $('#wizp1_body').html('

    ' + $.i18n(cololight_title) + '

    ' + $.i18n(cololight_intro1) + '

    '); - $('#wizp1_footer').html(''); - - $('#wizp2_body').html('
    '); - - $('#wh_topcontainer').append(''); - - $('#wizp2_body').append(''); - - createTable("lidsh", "lidsb", "colo_ids_t"); - $('.lidsh').append(createTableRow([$.i18n('edt_dev_spec_lights_title'), $.i18n('wiz_identify')], true)); - $('#wizp2_footer').html('' - + $.i18n('general_btn_cancel') + ''); - - //open modal - $("#wizard_modal").modal({ - backdrop: "static", - keyboard: false, - show: true - }); - - //listen for continue - $('#btn_wiz_cont').off().on('click', function () { - beginWizardCololight(); - $('#wizp1').toggle(false); - $('#wizp2').toggle(true); - }); -} - -function beginWizardCololight() { - lights = []; - - discover_cololights(); - - $('#btn_wiz_save').off().on("click", function () { - //LED device config - //Start with a clean configuration - var d = {}; - - d.type = 'cololight'; - - //Cololight does not resolve into stable hostnames (as devices named the same), therefore use IP - if (!lights[selectedLightId].ip) { - d.host = lights[selectedLightId].host; - } else { - d.host = lights[selectedLightId].ip; - } - - var coloLightProperties = lights[selectedLightId].props.properties; - if (Object.keys(coloLightProperties).length === 0) { - alert($.i18n('wiz_cololight_noprops')); - d.hardwareLedCount = 1; - } - else { - d.hardwareLedCount = coloLightProperties.ledCount; - } - - d.colorOrder = conf_editor.getEditor("root.generalOptions.colorOrder").getValue(); - d.latchTime = parseInt(conf_editor.getEditor("root.specificOptions.latchTime").getValue());; - - window.serverConfig.device = d; - - //LED layout - have initial layout prepared matching the LED-count - - var coloLightLedConfig = []; - - if (coloLightProperties.modelType === "Strip") { - coloLightLedConfig = createClassicLedLayoutSimple(d.hardwareLedCount / 2, d.hardwareLedCount / 4, d.hardwareLedCount / 4, 0, d.hardwareLedCount / 4 * 3, false); - } else { - coloLightLedConfig = createClassicLedLayoutSimple(0, 0, 0, d.hardwareLedCount, 0, true); - } - - window.serverConfig.leds = coloLightLedConfig; - - //smoothing off - window.serverConfig.smoothing.enable = false; - - requestWriteConfig(window.serverConfig, true); - - resetWizard(); - }); - - $('#btn_wiz_abort').off().on('click', resetWizard); -} - -async function discover_cololights() { - const res = await requestLedDeviceDiscovery('cololight'); - - if (res && !res.error) { - const r = res.info; - - // Process devices returned by discovery - for (const device of r.devices) { - if (device.ip !== "") { - if (getIpInLights(device.ip).length === 0) { - var light = {}; - light.ip = device.ip; - light.host = device.hostname; - light.name = device.name; - light.type = device.type; - lights.push(light); - } - } - } - assign_cololight_lights(); - } -} - -function assign_cololight_lights() { - // If records are left for configuration - if (Object.keys(lights).length > 0) { - $('#wh_topcontainer').toggle(false); - $('#colo_ids_t, #btn_wiz_save').toggle(true); - - $('.lidsb').html(""); - - var options = ""; - - for (var lightid in lights) { - lights[lightid].id = lightid; - - var lightHostname = lights[lightid].host; - var lightIP = lights[lightid].ip; - - var val = lightHostname + " (" + lightIP + ")"; - options += ''; - } - - var enabled = 'enabled'; - - $('.lidsb').append(createTableRow(['', ''])); - - $('.colo_sel_watch').bind("change", function () { - selectedLightId = $('#colo_select_id').val(); - var lightIP = lights[selectedLightId].ip; - - $('#wiz_identify_btn').unbind().bind('click', function (event) { identify_cololight_device(lightIP); }); - - if (!lights[selectedLightId].props) { - getProperties_cololight(lightIP); - } - }); - $('.colo_sel_watch').trigger('change'); - } - else { - var noLightsTxt = '

    ' + $.i18n('wiz_noLights','Cololights') + '

    '; - $('#wizp2_body').append(noLightsTxt); - } -} - -async function getProperties_cololight(ip) { - let params = { host: ip }; - - const res = await requestLedDeviceProperties('cololight', params); - - if (res && !res.error) { - var coloLightProperties = res.info; - - //Store properties along light with given IP-address - var id = getIpInLights(ip)[0].id; - lights[id].props = coloLightProperties; - } -} - -async function identify_cololight_device(hostAddress) { - - // Take care that new record cannot be save during background process - $('#btn_wiz_save').attr('disabled', true); - - let params = { host: hostAddress }; - const res = await requestLedDeviceIdentification('cololight', params); - - if (!window.readOnlyMode) { - $('#btn_wiz_save').attr('disabled', false); - } -} - -//**************************** -// Wizard/Routines RS232-Devices -//**************************** -async function discover_providerRs232(rs232Type) { - const res = await requestLedDeviceDiscovery(rs232Type); - - // TODO: error case unhandled - // res can be: false (timeout) or res.error (not found) - if (res && !res.error) { - const r = res.info - - // Process serialPorts returned by discover - console.log(r); - } -} - -//**************************** -// Wizard/Routines HID (USB)-Devices -//**************************** -async function discover_providerHid(hidType) { - const res = await requestLedDeviceDiscovery(hidType); - - // TODO: error case unhandled - // res can be: false (timeout) or res.error (not found) - if (res && !res.error) { - const r = res.info - - // Process HID returned by discover - console.log(r); + $('#btn_wiz_save').attr('disabled', false); } } diff --git a/cmake/debian/postinst b/cmake/debian/postinst index 140a6962..4cef9515 100644 --- a/cmake/debian/postinst +++ b/cmake/debian/postinst @@ -141,8 +141,8 @@ echo "---> or if already used by another service try: ${NET_IP}:8091" $REBOOTMESSAGE echo "-----------------------------------------------------------------------------" echo "Webpage: www.hyperion-project.org" -echo "Wiki: wiki.hyperion-project.org" -echo "Forum: forum.hyperion-project.org" +echo "Forum: www.hyperion-project.org" +echo "Documenation: docs.hyperion-project.org" echo "-----------------------------------------------------------------------------" diff --git a/cmake/packages.cmake b/cmake/packages.cmake index 2626516e..d58ae627 100644 --- a/cmake/packages.cmake +++ b/cmake/packages.cmake @@ -34,7 +34,10 @@ endif() SET ( CPACK_PACKAGE_NAME "Hyperion" ) SET ( CPACK_PACKAGE_DESCRIPTION_SUMMARY "Hyperion is an open source ambient light implementation" ) SET ( CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_SOURCE_DIR}/README.md" ) -SET ( CPACK_PACKAGE_FILE_NAME "Hyperion-${HYPERION_VERSION}-${CMAKE_SYSTEM_NAME}-${CMAKE_SYSTEM_PROCESSOR}") + +# Replease "+", as cmake/rpm has an issue if "+" occurs in CPACK_PACKAGE_VERSION +string(REPLACE "+" "." HYPERION_PACKAGE_VERSION ${HYPERION_VERSION}) +SET ( CPACK_PACKAGE_FILE_NAME "Hyperion-${HYPERION_PACKAGE_VERSION}-${CMAKE_SYSTEM_NAME}-${CMAKE_SYSTEM_PROCESSOR}") SET ( CPACK_PACKAGE_CONTACT "packages@hyperion-project.org") SET ( CPACK_PACKAGE_VENDOR "hyperion-project") diff --git a/cmake/rpm/postinst b/cmake/rpm/postinst index 836b7ff3..171b97b5 100644 --- a/cmake/rpm/postinst +++ b/cmake/rpm/postinst @@ -131,8 +131,8 @@ echo "---> or if already used by another service try: ${NET_IP}:8091" $REBOOTMESSAGE echo "-----------------------------------------------------------------------------" echo "Webpage: www.hyperion-project.org" -echo "Wiki: wiki.hyperion-project.org" -echo "Forum: forum.hyperion-project.org" +echo "Forum: www.hyperion-project.org" +echo "Documenation: docs.hyperion-project.org" echo "-----------------------------------------------------------------------------" diff --git a/include/hyperion/SettingsManager.h b/include/hyperion/SettingsManager.h index 027d0ece..cdb22f3c 100644 --- a/include/hyperion/SettingsManager.h +++ b/include/hyperion/SettingsManager.h @@ -3,9 +3,14 @@ #include #include +#include +using namespace semver; + // qt includes #include +const int GLOABL_INSTANCE_ID = 255; + class Hyperion; class SettingsTable; @@ -61,12 +66,17 @@ private: bool handleConfigUpgrade(QJsonObject& config); - /// Hyperion instance - Hyperion* _hyperion; + bool resolveConfigVersion(QJsonObject& config); /// Logger instance Logger* _log; + /// Hyperion instance + Hyperion* _hyperion; + + /// Instance number + quint8 _instance; + /// instance of database table interface SettingsTable* _sTable; @@ -76,5 +86,8 @@ private: /// the current configuration of this instance QJsonObject _qconfig; + semver::version _configVersion; + semver::version _previousVersion; + bool _readonlyMode; }; diff --git a/include/leddevice/LedDevice.h b/include/leddevice/LedDevice.h index d725517e..aebfcd46 100644 --- a/include/leddevice/LedDevice.h +++ b/include/leddevice/LedDevice.h @@ -300,10 +300,21 @@ protected: /// even if the device is not in enabled state (allowing to have a defined state during device power-off). /// @note: latch-time is considered between each write /// - /// @param[in] numberOfWrites Write Black given number of times + /// @param[in] numberOfWrites Write Black a given number of times /// @return Zero on success else negative /// - virtual int writeBlack(int numberOfBlack=1); + virtual int writeBlack(int numberOfWrites = 1); + + /// + /// @brief Writes a color to the output stream, + /// even if the device is not in enabled state (allowing to have a defined state during device power-off). + /// @note: latch-time is considered between each write + /// + /// @param[in] color to be written + /// @param[in] numberOfWrites Write the color a given number of times + /// @return Zero on success else negative + /// + virtual int writeColor(const ColorRgb& color, int numberOfWrites = 1); /// /// @brief Power-/turn on the LED-device. @@ -431,7 +442,7 @@ protected slots: /// /// @param[in] errorMsg The error message to be logged /// - virtual void setInError( const QString& errorMsg); + virtual void setInError( const QString& errorMsg); private: diff --git a/include/utils/version.hpp b/include/utils/version.hpp new file mode 100644 index 00000000..1e4d020d --- /dev/null +++ b/include/utils/version.hpp @@ -0,0 +1,550 @@ +#ifndef VERSION_H +#define VERSION_H + +/** + * Semver - The Semantic Versioning, https://github.com/euskadi31/semver-cpp + * + * Copyright (c) 2013 Axel Etcheverry, 2021 enhancements & fixes Lord-Grey + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is furnished + * to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include +#include +#include +#include + +namespace semver { + + enum PRE_RELEASE { + PRE_RELEASE_ALPHA, + PRE_RELEASE_BETA, + PRE_RELEASE_RC, + PRE_RELEASE_NONE + }; + + typedef enum PRE_RELEASE pre_release_t; + + class version + { + private: + std::string m_version; + int m_major; + int m_minor; + int m_patch; + pre_release_t m_pre_release_type; + std::string m_pre_release_id; + std::string m_pre_release; + std::string m_build; + bool m_is_valid; + bool m_is_stable; + + enum m_type { + TYPE_MAJOR, + TYPE_MINOR, + TYPE_PATCH, + TYPE_PRE_RELEASE, + TYPE_PRE_RELEASE_ID, + TYPE_BUILD + }; + + void parse() + { + int type = TYPE_MAJOR; + + std::string major, minor, patch; + + for (std::size_t i = 0; i < m_version.length(); i++) + { + char chr = m_version[i]; + int chr_dec = chr; + + switch (type) + { + case TYPE_MAJOR: + + if (chr == '.') + { + type = TYPE_MINOR; + continue; + } + + if (chr_dec < 48 || chr_dec > 57) + { + m_is_valid = false; + } + + // major + major += chr; + break; + + case TYPE_MINOR: + + if (chr == '.') + { + type = TYPE_PATCH; + continue; + } + + if (chr_dec < 48 || chr_dec > 57) + { + m_is_valid = false; + } + + minor += chr; + + break; + + case TYPE_PATCH: + + if (chr == '-') + { + type = TYPE_PRE_RELEASE; + continue; + } + + if (chr == '+') + { + type = TYPE_BUILD; + continue; + } + + + if (chr_dec < 48 || chr_dec > 57) + { + m_is_valid = false; + } + + patch += chr; + + break; + + case TYPE_PRE_RELEASE: + + if (chr == '.') + { + type = TYPE_PRE_RELEASE_ID; + m_pre_release += chr; + continue; + } + + if (chr == '+') + { + type = TYPE_BUILD; + continue; + } + + if ( + (chr_dec < 48 || chr_dec > 57) && // 0-9 + (chr_dec < 65 || chr_dec > 90) && // A-Z + (chr_dec < 97 || chr_dec > 122) && // a-z + (chr_dec != 45) && // - + (chr_dec != 46) // . + ) + { + m_is_valid = false; + } + + m_pre_release += chr; + break; + + case TYPE_PRE_RELEASE_ID: + + if (chr == '+') + { + type = TYPE_BUILD; + continue; + } + + if ( + (chr_dec < 48 || chr_dec > 57) && // 0-9 + (chr_dec < 65 || chr_dec > 90) && // A-Z + (chr_dec < 97 || chr_dec > 122) && // a-z + (chr_dec != 45) // - + ) + { + m_is_valid = false; + } + + m_pre_release += chr; + m_pre_release_id += chr; + break; + + case TYPE_BUILD: + + if ( + (chr_dec < 48 || chr_dec > 57) && // 0-9 + (chr_dec < 65 || chr_dec > 90) && // A-Z + (chr_dec < 97 || chr_dec > 122) && // a-z + (chr_dec != 45) // - + ) + { + m_is_valid = false; + } + + m_build += chr; + break; + } + } + + if (m_is_valid) + { + std::istringstream(major) >> m_major; + std::istringstream(minor) >> m_minor; + std::istringstream(patch) >> m_patch; + + if (m_pre_release.empty()) + { + m_pre_release_type = PRE_RELEASE_NONE; + } + else if (m_pre_release.find("alpha") != std::string::npos) + { + m_pre_release_type = PRE_RELEASE_ALPHA; + } + else if (m_pre_release.find("beta") != std::string::npos) + { + m_pre_release_type = PRE_RELEASE_BETA; + } + else if (m_pre_release.find("rc") != std::string::npos) + { + m_pre_release_type = PRE_RELEASE_RC; + } + + if (m_major == 0 && m_minor == 0 && m_patch == 0) + { + m_is_valid = false; + } + + if (!m_pre_release_id.empty() && m_pre_release_id[0] == '0') + { + m_is_valid = false; + } + + if (m_major == 0) + { + m_is_stable = false; + } + + if (m_pre_release_type != PRE_RELEASE_NONE) + { + m_is_stable = false; + } + } + } + + public: + /** + * Parse the version string + */ + version(const std::string& version) + { + setVersion(version); + } + + version(const version&) = default; + + ~version() = default; + + /** + * Set version + */ + bool setVersion(const std::string& version) + { + m_version = version; + m_major = 0; + m_minor = 0; + m_patch = 0; + m_build = ""; + m_pre_release = ""; + m_pre_release_id = ""; + m_is_stable = true; + + if (version.empty()) + { + m_is_valid = false; + } + else + { + m_is_valid = true; + + parse(); + } + return m_is_valid; + } + + /** + * Get full version + */ + const std::string& getVersion() const + { + return m_version; + } + + /** + * Get the major of the version + */ + const int& getMajor() const + { + return m_major; + } + + /** + * Get the minor of the version + */ + const int& getMinor() const + { + return m_minor; + } + + /** + * Get the patch of the version + */ + const int& getPatch() const + { + return m_patch; + } + + /** + * Get the build of the version + */ + const std::string& getBuild() const + { + return m_build; + } + + /** + * Get the release type of the version + */ + const pre_release_t& getPreReleaseType() const + { + return m_pre_release_type; + } + + /** + * Get the release identifier of the version + */ + const std::string& getPreReleaseId() const + { + return m_pre_release_id; + } + + /** + * Get the release of the version + */ + const std::string& getPreRelease() const + { + return m_pre_release; + } + + /** + * Check if the version is stable + */ + const bool& isStable() const + { + return m_is_stable; + } + + /** + * Check if the version is valid + */ + const bool& isValid() const + { + return m_is_valid; + } + + + int compare(version& rgt) + { + + if ((*this) == rgt) + { + return 0; + } + + if ((*this) > rgt) + { + return 1; + } + + return -1; + } + + version& operator= (version& rgt) + { + if ((*this) != rgt) + { + this->m_version = rgt.getVersion(); + this->m_major = rgt.getMajor(); + this->m_minor = rgt.getMinor(); + this->m_patch = rgt.getPatch(); + this->m_pre_release_type = rgt.getPreReleaseType(); + this->m_pre_release_id = rgt.getPreReleaseId(); + this->m_pre_release = rgt.getPreRelease(); + this->m_build = rgt.getBuild(); + this->m_is_valid = rgt.isValid(); + this->m_is_stable = rgt.isStable(); + } + + return *this; + } + + friend bool operator== (version &lft, version &rgt) + { + return lft.getVersion().compare(rgt.getVersion()) == 0; + } + + friend bool operator!= (version &lft, version &rgt) + { + return !(lft == rgt); + } + + friend bool operator> (version &lft, version &rgt) + { + // Major + if (lft.getMajor() < 0 && rgt.getMajor() >= 0) + { + return false; + } + + if (lft.getMajor() >= 0 && rgt.getMajor() < 0) + { + return true; + } + + if (lft.getMajor() > rgt.getMajor()) + { + return true; + } + + if (lft.getMajor() < rgt.getMajor()) + { + return false; + } + + + // Minor + if (lft.getMinor() < 0 && rgt.getMinor() >= 0) + { + return false; + } + + if (lft.getMinor() >= 0 && rgt.getMinor() < 0) + { + return true; + } + + if (lft.getMinor() > rgt.getMinor()) + { + return true; + } + + if (lft.getMinor() < rgt.getMinor()) + { + return false; + } + + + // Patch + if (lft.getPatch() < 0 && rgt.getPatch() >= 0) + { + return false; + } + + if (lft.getPatch() >= 0 && rgt.getPatch() < 0) + { + return true; + } + + if (lft.getPatch() > rgt.getPatch()) + { + return true; + } + + if (lft.getPatch() < rgt.getPatch()) + { + return false; + } + + // Pre release + if ( + (lft.getPreReleaseType() == rgt.getPreReleaseType()) && + (lft.getPreReleaseId() == rgt.getPreReleaseId()) + ) + { + return false; + } + + if ( + (lft.getPreReleaseType() == rgt.getPreReleaseType()) && + (lft.getPreReleaseId().find_first_not_of("0123456789") == std::string::npos) && + (rgt.getPreReleaseId().find_first_not_of("0123456789") == std::string::npos) + ) + { + if (atoi(lft.getPreReleaseId().c_str()) > atoi(rgt.getPreReleaseId().c_str())) + { + return true; + } + else + { + return false; + } + } + + if ( + (lft.getPreReleaseType() == rgt.getPreReleaseType()) && + (lft.getPreReleaseId().compare(rgt.getPreReleaseId()) > 0) + ) + { + return true; + } + + if (lft.getPreReleaseType() > rgt.getPreReleaseType()) + { + return true; + } + + return false; + } + + friend bool operator>= (version &lft, version &rgt) + { + return (lft > rgt) || (lft == rgt); + } + + friend bool operator< (version &lft, version &rgt) + { + return (rgt > lft); + } + + friend bool operator<= (version &lft, version &rgt) + { + return (lft < rgt) || (lft == rgt); + } + + friend std::ostream& operator<< (std::ostream& out, const version& value) + { + out << value.getVersion(); + + return out; + } + }; + +} // end semver namespace + +#endif // VERSION_H diff --git a/libsrc/hyperion/Hyperion.cpp b/libsrc/hyperion/Hyperion.cpp index 14e14132..149a4458 100644 --- a/libsrc/hyperion/Hyperion.cpp +++ b/libsrc/hyperion/Hyperion.cpp @@ -83,7 +83,7 @@ void Hyperion::start() } // handle hwLedCount - _hwLedCount = qMax(getSetting(settings::DEVICE).object()["hardwareLedCount"].toInt(getLedCount()), getLedCount()); + _hwLedCount = getSetting(settings::DEVICE).object()["hardwareLedCount"].toInt(getLedCount()); // Initialize colororder vector for (const Led& led : _ledString.leds()) @@ -217,7 +217,7 @@ void Hyperion::handleSettingsUpdate(settings::type type, const QJsonDocument& co } // handle hwLedCount update - _hwLedCount = qMax(getSetting(settings::DEVICE).object()["hardwareLedCount"].toInt(getLedCount()), getLedCount()); + _hwLedCount = getSetting(settings::DEVICE).object()["hardwareLedCount"].toInt(getLedCount()); // change in leds are also reflected in adjustment delete _raw2ledAdjustment; @@ -231,7 +231,7 @@ void Hyperion::handleSettingsUpdate(settings::type type, const QJsonDocument& co QJsonObject dev = config.object(); // handle hwLedCount update - _hwLedCount = qMax(dev["hardwareLedCount"].toInt(getLedCount()), getLedCount()); + _hwLedCount = dev["hardwareLedCount"].toInt(getLedCount()); // force ledString update, if device ByteOrder changed if(_ledDeviceWrapper->getColorOrder() != dev["colorOrder"].toString("rgb")) diff --git a/libsrc/hyperion/SettingsManager.cpp b/libsrc/hyperion/SettingsManager.cpp index b21c4c96..dca21298 100644 --- a/libsrc/hyperion/SettingsManager.cpp +++ b/libsrc/hyperion/SettingsManager.cpp @@ -4,6 +4,7 @@ // util #include #include +#include "HyperionConfig.h" // json schema process #include @@ -12,12 +13,23 @@ // write config to filesystem #include +#include +using namespace semver; + +// Constants +namespace { +const char DEFAULT_VERSION[] = "2.0.0-alpha.8"; +} //End of constants + 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) { _sTable->setReadonlyMode(_readonlyMode); @@ -38,7 +50,9 @@ SettingsManager::SettingsManager(quint8 instance, QObject* parent, bool readonly // get default config QJsonObject defaultConfig; if(!JsonUtils::readFile(":/hyperion_default.config", defaultConfig, _log)) + { throw std::runtime_error("Failed to read default config"); + } // transform json to string lists QStringList keyList = defaultConfig.keys(); @@ -64,7 +78,7 @@ SettingsManager::SettingsManager(quint8 instance, QObject* parent, bool readonly _sTable->createSettingsRecord(key,val); } - // need to validate all data in database constuct the entire data object + // 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) @@ -76,8 +90,49 @@ SettingsManager::SettingsManager(quint8 instance, QObject* parent, bool readonly 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 ( resolveConfigVersion(dbConfig) ) + { + QJsonObject newGeneralConfig = dbConfig["general"].toObject(); + + semver::version BUILD_VERSION(HYPERION_VERSION); + if ( _configVersion > BUILD_VERSION ) + { + Error(_log, "Database version [%s] is greater that 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 ( _configVersion == BUILD_VERSION ) + { + newGeneralConfig["previousVersion"] = BUILD_VERSION.getVersion().c_str(); + dbConfig["general"] = newGeneralConfig; + isNewRelease = true; + Info(_log, "Migration completed to version [%s]", BUILD_VERSION.getVersion().c_str()); + } + else + { + Info(_log, "Migration from current version [%s] to new version [%s] started", _previousVersion.getVersion().c_str(), BUILD_VERSION.getVersion().c_str()); + + newGeneralConfig["previousVersion"] = _configVersion.getVersion().c_str(); + newGeneralConfig["configVersion"] = BUILD_VERSION.getVersion().c_str(); + dbConfig["general"] = newGeneralConfig; + isNewRelease = true; + } + } + } + } + } + // possible data upgrade steps to prevent data loss - if(handleConfigUpgrade(dbConfig)) + bool migrated = handleConfigUpgrade(dbConfig); + if ( isNewRelease || migrated ) { saveSettings(dbConfig, true); } @@ -193,77 +248,156 @@ bool SettingsManager::saveSettings(QJsonObject config, bool correct) return rc; } +bool SettingsManager::resolveConfigVersion(QJsonObject& config) +{ + bool isValid = false; + if (config.contains("general")) + { + QJsonObject generalConfig = config["general"].toObject(); + QString configVersion = generalConfig["configVersion"].toString(); + QString previousVersion = generalConfig["previousVersion"].toString(); + + if ( !configVersion.isEmpty() ) + { + isValid = _configVersion.setVersion(configVersion.toStdString()); + } + else + { + _configVersion.setVersion(DEFAULT_VERSION); + isValid = true; + } + + if ( !previousVersion.isEmpty() && isValid ) + { + isValid = _previousVersion.setVersion(previousVersion.toStdString()); + } + else + { + _previousVersion.setVersion(DEFAULT_VERSION); + isValid = true; + } + } + return isValid; +} + bool SettingsManager::handleConfigUpgrade(QJsonObject& config) { bool migrated = false; - // LED LAYOUT UPGRADE - // from { hscan: { minimum: 0.2, maximum: 0.3 }, vscan: { minimum: 0.2, maximumn: 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")) + resolveConfigVersion(config); + + //Do only migrate, if configuration is not up to date + if (_previousVersion < _configVersion) { - const QJsonArray ledarr = config["leds"].toArray(); - const QJsonObject led = ledarr[0].toObject(); - - if(led.contains("hscan") || led.contains("h")) + //Migration steps for versions <= alpha 9 + semver::version targetVersion {"2.0.0-alpha.9"}; + if (_previousVersion <= targetVersion ) { - const bool whscan = led.contains("hscan"); - QJsonArray newLedarr; + 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()); - for(const auto & entry : ledarr) + // 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")) { - const QJsonObject led = entry.toObject(); - QJsonObject hscan; - QJsonObject vscan; - QJsonValue hmin; - QJsonValue hmax; - QJsonValue vmin; - QJsonValue vmax; - QJsonObject nL; + const QJsonArray ledarr = config["leds"].toArray(); + const QJsonObject led = ledarr[0].toObject(); - if(whscan) + if(led.contains("hscan") || led.contains("h")) { - hscan = led["hscan"].toObject(); - vscan = led["vscan"].toObject(); - hmin = hscan["minimum"]; - hmax = hscan["maximum"]; - vmin = vscan["minimum"]; - vmax = vscan["maximum"]; + const bool whscan = led.contains("hscan"); + QJsonArray newLedarr; + + for(const auto & entry : ledarr) + { + const QJsonObject led = entry.toObject(); + QJsonObject hscan; + QJsonObject vscan; + QJsonValue hmin; + QJsonValue hmax; + QJsonValue vmin; + QJsonValue vmax; + QJsonObject nL; + + if(whscan) + { + hscan = led["hscan"].toObject(); + vscan = led["vscan"].toObject(); + hmin = hscan["minimum"]; + hmax = hscan["maximum"]; + vmin = vscan["minimum"]; + vmax = vscan["maximum"]; + } + else + { + hscan = led["h"].toObject(); + vscan = led["v"].toObject(); + hmin = hscan["min"]; + hmax = hscan["max"]; + vmin = vscan["min"]; + vmax = vscan["max"]; + } + // append to led object + nL["hmin"] = hmin; + nL["hmax"] = hmax; + nL["vmin"] = vmin; + nL["vmax"] = vmax; + newLedarr.append(nL); + } + // replace + config["leds"] = newLedarr; + migrated = true; + Info(_log,"Instance [%u]: LED Layout migrated", _instance); } - else - { - hscan = led["h"].toObject(); - vscan = led["v"].toObject(); - hmin = hscan["min"]; - hmax = hscan["max"]; - vmin = vscan["min"]; - vmax = vscan["max"]; - } - // append to led object - nL["hmin"] = hmin; - nL["hmax"] = hmax; - nL["vmin"] = vmin; - nL["vmax"] = vmax; - newLedarr.append(nL); } - // replace - config["leds"] = newLedarr; - migrated = true; - Debug(_log,"LED Layout migrated"); - } - } - if (config.contains("grabberV4L2")) - { - QJsonObject newGrabberV4L2Config = config["grabberV4L2"].toObject(); + if(config.contains("ledConfig")) + { + QJsonObject oldLedConfig = config["ledConfig"].toObject(); + if ( !oldLedConfig.contains("classic")) + { + QJsonObject newLedConfig; + newLedConfig.insert("classic", oldLedConfig ); + QJsonObject defaultMatrixConfig {{"ledshoriz", 1} + ,{"ledsvert", 1} + ,{"cabling","snake"} + ,{"start","top-left"} + }; + newLedConfig.insert("matrix", defaultMatrixConfig ); - if (newGrabberV4L2Config.contains("encoding_format")) - { - newGrabberV4L2Config.remove("encoding_format"); - config["grabberV4L2"] = newGrabberV4L2Config; - migrated = true; - Debug(_log, "GrabberV4L2 Layout migrated"); + config["ledConfig"] = newLedConfig; + migrated = true; + Info(_log,"Instance [%u]: LED-Config migrated", _instance); + } + } + + // LED Hardware count is leading for versions after alpha 9 + // Setting Hardware LED count to number of LEDs configured via layout, if layout number is greater than number of hardware LEDs + if (config.contains("device")) + { + QJsonObject newDeviceConfig = config["device"].toObject(); + + if (newDeviceConfig.contains("hardwareLedCount")) + { + int hwLedcount = newDeviceConfig["hardwareLedCount"].toInt(); + if (config.contains("leds")) + { + const QJsonArray ledarr = config["leds"].toArray(); + int layoutLedCount = ledarr.size(); + + if (hwLedcount < layoutLedCount ) + { + Warning(_log, "Instance [%u]: HwLedCount/Layout mismatch! Setting Hardware LED count to number of LEDs configured via layout", _instance); + hwLedcount = layoutLedCount; + newDeviceConfig["hardwareLedCount"] = hwLedcount; + + config["device"] = newDeviceConfig; + migrated = true; + } + } + } + } } } return migrated; diff --git a/libsrc/hyperion/schema/schema-device.json b/libsrc/hyperion/schema/schema-device.json index 4c337200..e08bca96 100644 --- a/libsrc/hyperion/schema/schema-device.json +++ b/libsrc/hyperion/schema/schema-device.json @@ -1,50 +1,45 @@ { - "type" : "object", - "title" : "edt_dev_general_heading_title", - "required" : true, - "defaultProperties": ["hardwareLedCount", "colorOrder"], - "properties" : - { - "type" : - { - "type" : "string", - "propertyOrder" : 1 - }, - "hardwareLedCount" : - { - "type" : "integer", - "title" : "edt_dev_general_hardwareLedCount_title", - "minimum" : 1, - "default" : 1, - "propertyOrder" : 2 - }, - "colorOrder" : - { - "type" : "string", - "title" : "edt_dev_general_colorOrder_title", - "enum" : ["rgb", "bgr", "rbg", "brg", "gbr", "grb"], - "default" : "rgb", - "required" : true, - "options": { - "enum_titles": [ "edt_conf_enum_rgb", "edt_conf_enum_bgr", "edt_conf_enum_rbg", "edt_conf_enum_brg", "edt_conf_enum_gbr", "edt_conf_enum_grb" ] - }, - "access" : "expert", - "propertyOrder" : 3 - } - }, - "dependencies" : - { - "rewriteTime" : - { - "properties" : - { - "type" : - { - "enum" : ["file", "apa102", "apa104", "ws2801", "lpd6803", "lpd8806", "p9813", "sk6812spi", "sk6822spi", "sk9822", "ws2812spi","ws281x", "piblaster", "adalight", "dmx", "atmo", "hyperionusbasp", "lightpack", "multilightpack", "paintpack", "rawhid", "sedu", "tpm2", "karate"] - } - }, - "additionalProperties" : true - } - }, - "additionalProperties" : true + "type": "object", + "title": " ", + "defaultProperties": [ "hardwareLedCount", "colorOrder" ], + "properties": { + "type": { + "type": "string", + "propertyOrder": 1 + }, + "hardwareLedCount": { + "type": "integer", + "title": "edt_dev_general_hardwareLedCount_title", + "minimum": 1, + "default": 1, + "options": { + "infoText": "edt_dev_general_hardwareLedCount_title_info" + }, + "propertyOrder": 2 + }, + "colorOrder": { + "type": "string", + "title": "edt_dev_general_colorOrder_title", + "enum": [ "rgb", "bgr", "rbg", "brg", "gbr", "grb" ], + "default": "rgb", + "required": true, + "options": { + "enum_titles": [ "edt_conf_enum_rgb", "edt_conf_enum_bgr", "edt_conf_enum_rbg", "edt_conf_enum_brg", "edt_conf_enum_gbr", "edt_conf_enum_grb" ], + "infoText": "edt_dev_general_colorOrder_title_info" + }, + "access": "expert", + "propertyOrder": 3 + } + }, + "dependencies": { + "rewriteTime": { + "properties": { + "type": { + "enum": [ "file", "apa102", "apa104", "ws2801", "lpd6803", "lpd8806", "p9813", "sk6812spi", "sk6822spi", "sk9822", "ws2812spi", "ws281x", "piblaster", "adalight", "dmx", "atmo", "hyperionusbasp", "lightpack", "multilightpack", "paintpack", "rawhid", "sedu", "tpm2", "karate" ] + } + }, + "additionalProperties": true + } + }, + "additionalProperties": true } diff --git a/libsrc/hyperion/schema/schema-general.json b/libsrc/hyperion/schema/schema-general.json index afab0c29..422054b5 100644 --- a/libsrc/hyperion/schema/schema-general.json +++ b/libsrc/hyperion/schema/schema-general.json @@ -34,6 +34,25 @@ "default" : true, "required" : true, "propertyOrder" : 3 + }, + "configVersion" : + { + "type" : "string", + "title" : "edt_conf_gen_configVersion_title", + "options" : { + "hidden":true + }, + "access" : "expert", + "propertyOrder" : 4 + }, + "previousVersion" : + { + "type" : "string", + "options" : { + "hidden":true + }, + "access" : "expert", + "propertyOrder" : 5 } }, "additionalProperties" : false diff --git a/libsrc/hyperion/schema/schema-ledConfig.json b/libsrc/hyperion/schema/schema-ledConfig.json index f5f58487..4fa79f87 100644 --- a/libsrc/hyperion/schema/schema-ledConfig.json +++ b/libsrc/hyperion/schema/schema-ledConfig.json @@ -135,15 +135,45 @@ }, "cabling": { "type": "string", - "enum": ["snake", "parallel"] + "enum": [ "snake", "parallel" ] }, "start": { "type": "string", - "enum": ["top-left", "top-right", "bottom-left", "bottom-right"] + "enum": [ "top-left", "top-right", "bottom-left", "bottom-right" ] } }, "additionalProperties": false + }, + "ledBlacklist": { + "type": "array", + "title": "conf_leds_layout_blacklist_rules_title", + "minimum": 1, + "uniqueItems": true, + "items": { + "type": "object", + "title": "conf_leds_layout_blacklist_rule_title", + "required": true, + "properties": { + "start": { + "type": "integer", + "minimum": 0, + "default": 0, + "title": "conf_leds_layout_blacklist_start_title", + "required": true, + "propertyOrder": 1 + }, + "num": { + "type": "integer", + "minimum": 1, + "default": 1, + "title": "conf_leds_layout_blacklist_num_title", + "required": true, + "propertyOrder": 2 + } + } + }, + "propertyOrder": 1 } }, - "additionalProperties": true -} + "additionalProperties": true + } diff --git a/libsrc/leddevice/LedDevice.cpp b/libsrc/leddevice/LedDevice.cpp index 2ab15bf0..27dc3e8c 100644 --- a/libsrc/leddevice/LedDevice.cpp +++ b/libsrc/leddevice/LedDevice.cpp @@ -234,20 +234,25 @@ int LedDevice::rewriteLEDs() return retval; } -int LedDevice::writeBlack(int numberOfBlack) +int LedDevice::writeBlack(int numberOfWrites) +{ + return writeColor(ColorRgb::BLACK, numberOfWrites); +} + +int LedDevice::writeColor(const ColorRgb& color, int numberOfWrites) { int rc = -1; - for (int i = 0; i < numberOfBlack; i++) + for (int i = 0; i < numberOfWrites; i++) { - if ( _latchTime_ms > 0 ) + if (_latchTime_ms > 0) { // Wait latch time before writing black QEventLoop loop; QTimer::singleShot(_latchTime_ms, &loop, &QEventLoop::quit); loop.exec(); } - _lastLedValues = std::vector(static_cast(_ledCount), ColorRgb::BLACK ); + _lastLedValues = std::vector(static_cast(_ledCount),color); rc = write(_lastLedValues); } return rc; @@ -411,28 +416,33 @@ void LedDevice::setLatchTime( int latchTime_ms ) void LedDevice::setRewriteTime( int rewriteTime_ms ) { assert(rewriteTime_ms >= 0); - _refreshTimerInterval_ms = rewriteTime_ms; - if ( _refreshTimerInterval_ms > 0 ) + //Check, if refresh timer was not initialised due to getProperties/identify sceanrios + if (_refreshTimer != nullptr) { + _refreshTimerInterval_ms = rewriteTime_ms; - _isRefreshEnabled = true; - - if (_refreshTimerInterval_ms <= _latchTime_ms ) + if (_refreshTimerInterval_ms > 0) { - int new_refresh_timer_interval = _latchTime_ms + 10; - Warning(_log, "latchTime(%d) is bigger/equal rewriteTime(%d), set rewriteTime to %dms", _latchTime_ms, _refreshTimerInterval_ms, new_refresh_timer_interval); - _refreshTimerInterval_ms = new_refresh_timer_interval; - _refreshTimer->setInterval( _refreshTimerInterval_ms ); + + _isRefreshEnabled = true; + + if (_refreshTimerInterval_ms <= _latchTime_ms) + { + int new_refresh_timer_interval = _latchTime_ms + 10; + Warning(_log, "latchTime(%d) is bigger/equal rewriteTime(%d), set rewriteTime to %dms", _latchTime_ms, _refreshTimerInterval_ms, new_refresh_timer_interval); + _refreshTimerInterval_ms = new_refresh_timer_interval; + _refreshTimer->setInterval(_refreshTimerInterval_ms); + } + + Debug(_log, "Refresh interval = %dms", _refreshTimerInterval_ms); + _refreshTimer->setInterval(_refreshTimerInterval_ms); + + _lastWriteTime = QDateTime::currentDateTime(); } - Debug(_log, "Refresh interval = %dms",_refreshTimerInterval_ms ); - _refreshTimer->setInterval( _refreshTimerInterval_ms ); - - _lastWriteTime = QDateTime::currentDateTime(); + Debug(_log, "RewriteTime updated to %dms", _refreshTimerInterval_ms); } - - Debug(_log, "RewriteTime updated to %dms", _refreshTimerInterval_ms); } void LedDevice::printLedValues(const std::vector& ledValues) diff --git a/libsrc/leddevice/dev_net/LedDeviceWled.cpp b/libsrc/leddevice/dev_net/LedDeviceWled.cpp index 1f5511cb..a2139342 100644 --- a/libsrc/leddevice/dev_net/LedDeviceWled.cpp +++ b/libsrc/leddevice/dev_net/LedDeviceWled.cpp @@ -15,6 +15,9 @@ const bool verbose = false; // Configuration settings const char CONFIG_ADDRESS[] = "host"; const char CONFIG_RESTORE_STATE[] = "restoreOriginalState"; +const char CONFIG_BRIGHTNESS[] = "brightness"; +const char CONFIG_BRIGHTNESS_OVERWRITE[] = "overwriteBrightness"; +const char CONFIG_SYNC_OVERWRITE[] = "overwriteSync"; // UDP elements const quint16 STREAM_DEFAULT_PORT = 19446; @@ -32,7 +35,10 @@ const char STATE_VALUE_TRUE[] = "true"; const char STATE_VALUE_FALSE[] = "false"; const char STATE_LIVE[] = "live"; +const bool DEFAULT_IS_RESTORE_STATE = false; +const bool DEFAULT_IS_BRIGHTNESS_OVERWRITE = true; const int BRI_MAX = 255; +const bool DEFAULT_IS_SYNC_OVERWRITE = true; constexpr std::chrono::milliseconds DEFAULT_IDENTIFY_TIME{ 2000 }; @@ -42,6 +48,11 @@ LedDeviceWled::LedDeviceWled(const QJsonObject &deviceConfig) : ProviderUdp(deviceConfig) ,_restApi(nullptr) ,_apiPort(API_DEFAULT_PORT) + ,_isBrightnessOverwrite(DEFAULT_IS_BRIGHTNESS_OVERWRITE) + ,_brightness (BRI_MAX) + ,_isSyncOverwrite(DEFAULT_IS_SYNC_OVERWRITE) + ,_originalStateUdpnSend(false) + ,_originalStateUdpnRecv(true) { } @@ -70,8 +81,15 @@ bool LedDeviceWled::init(const QJsonObject &deviceConfig) Debug(_log, "ColorOrder : %s", QSTRING_CSTR( this->getColorOrder() )); Debug(_log, "LatchTime : %d", this->getLatchTime()); - _isRestoreOrigState = _devConfig[CONFIG_RESTORE_STATE].toBool(false); + _isRestoreOrigState = _devConfig[CONFIG_RESTORE_STATE].toBool(DEFAULT_IS_RESTORE_STATE); + _isSyncOverwrite = _devConfig[CONFIG_SYNC_OVERWRITE].toBool(DEFAULT_IS_SYNC_OVERWRITE); + _isBrightnessOverwrite = _devConfig[CONFIG_BRIGHTNESS_OVERWRITE].toBool(DEFAULT_IS_BRIGHTNESS_OVERWRITE); + _brightness = _devConfig[CONFIG_BRIGHTNESS].toInt(BRI_MAX); + Debug(_log, "RestoreOrigState : %d", _isRestoreOrigState); + Debug(_log, "Overwrite Sync. : %d", _isSyncOverwrite); + Debug(_log, "Overwrite Brightn.: %d", _isBrightnessOverwrite); + Debug(_log, "Set Brightness to : %d", _brightness); //Set hostname as per configuration QString hostName = deviceConfig[ CONFIG_ADDRESS ].toString(); @@ -145,6 +163,13 @@ QString LedDeviceWled::getLorRequest(int lor) const return QString( "\"lor\":%1" ).arg(lor); } +QString LedDeviceWled::getUdpnRequest(bool isSendOn, bool isRecvOn) const +{ + QString send = isSendOn ? STATE_VALUE_TRUE : STATE_VALUE_FALSE; + QString recv = isRecvOn ? STATE_VALUE_TRUE : STATE_VALUE_FALSE; + return QString( "\"udpn\":{\"send\":%1,\"recv\":%2}" ).arg(send, recv); +} + bool LedDeviceWled::sendStateUpdateRequest(const QString &request) { bool rc = true; @@ -166,7 +191,20 @@ bool LedDeviceWled::powerOn() //Power-on WLED device _restApi->setPath(API_PATH_STATE); - httpResponse response = _restApi->put(QString("{%1,%2}").arg(getOnOffRequest(true)).arg(getBrightnessRequest(BRI_MAX))); + QString cmd = getOnOffRequest(true); + + if ( _isBrightnessOverwrite) + { + cmd += "," + getBrightnessRequest(_brightness); + } + + if (_isSyncOverwrite) + { + Debug( _log, "Disable synchronisation with other WLED devices"); + cmd += "," + getUdpnRequest(false, false); + } + + httpResponse response = _restApi->put(QString("{%1}").arg(cmd)); if ( response.error() ) { QString errorReason = QString("Power-on request failed with error: '%1'").arg(response.getErrorReason()); @@ -191,7 +229,16 @@ bool LedDeviceWled::powerOff() //Power-off the WLED device physically _restApi->setPath(API_PATH_STATE); - httpResponse response = _restApi->put(QString("{%1}").arg(getOnOffRequest(false))); + + QString cmd = getOnOffRequest(false); + + if (_isSyncOverwrite) + { + Debug( _log, "Restore synchronisation with other WLED devices"); + cmd += "," + getUdpnRequest(_originalStateUdpnSend, _originalStateUdpnRecv); + } + + httpResponse response = _restApi->put(QString("{%1}").arg(cmd)); if ( response.error() ) { QString errorReason = QString("Power-off request failed with error: '%1'").arg(response.getErrorReason()); @@ -206,7 +253,7 @@ bool LedDeviceWled::storeState() { bool rc = true; - if ( _isRestoreOrigState ) + if ( _isRestoreOrigState || _isSyncOverwrite ) { _restApi->setPath(API_PATH_STATE); @@ -221,6 +268,13 @@ bool LedDeviceWled::storeState() { _originalStateProperties = response.getBody().object(); DebugIf(verbose, _log, "state: [%s]", QString(QJsonDocument(_originalStateProperties).toJson(QJsonDocument::Compact)).toUtf8().constData() ); + + QJsonObject udpn = _originalStateProperties.value("udpn").toObject(); + if (!udpn.isEmpty()) + { + _originalStateUdpnSend = udpn["send"].toBool(false); + _originalStateUdpnRecv = udpn["recv"].toBool(true); + } } } @@ -233,7 +287,6 @@ bool LedDeviceWled::restoreState() if ( _isRestoreOrigState ) { - //powerOff(); _restApi->setPath(API_PATH_STATE); _originalStateProperties[STATE_LIVE] = false; diff --git a/libsrc/leddevice/dev_net/LedDeviceWled.h b/libsrc/leddevice/dev_net/LedDeviceWled.h index 5871a44a..818467c0 100644 --- a/libsrc/leddevice/dev_net/LedDeviceWled.h +++ b/libsrc/leddevice/dev_net/LedDeviceWled.h @@ -140,9 +140,11 @@ private: /// @return Command to switch device on/off /// QString getOnOffRequest (bool isOn ) const; + QString getBrightnessRequest (int bri ) const; QString getEffectRequest(int effect, int speed=128) const; QString getLorRequest(int lor) const; + QString getUdpnRequest(bool send, bool recv) const; bool sendStateUpdateRequest(const QString &request); @@ -154,6 +156,12 @@ private: QJsonObject _originalStateProperties; + bool _isBrightnessOverwrite; + int _brightness; + + bool _isSyncOverwrite; + bool _originalStateUdpnSend; + bool _originalStateUdpnRecv; }; #endif // LEDDEVICEWLED_H diff --git a/libsrc/leddevice/dev_net/LedDeviceYeelight.cpp b/libsrc/leddevice/dev_net/LedDeviceYeelight.cpp index 3a14cb0b..7749c23e 100644 --- a/libsrc/leddevice/dev_net/LedDeviceYeelight.cpp +++ b/libsrc/leddevice/dev_net/LedDeviceYeelight.cpp @@ -1,4 +1,4 @@ -#include "LedDeviceYeelight.h" +#include "LedDeviceYeelight.h" #include #include @@ -1018,10 +1018,9 @@ bool LedDeviceYeelight::init(const QJsonObject &deviceConfig) //Get device specific configuration - bool ok; if ( deviceConfig[ CONFIG_COLOR_MODEL ].isString() ) { - _outputColorModel = deviceConfig[ CONFIG_COLOR_MODEL ].toString().toInt(&ok,MODEL_RGB); + _outputColorModel = deviceConfig[ CONFIG_COLOR_MODEL ].toString(QString(MODEL_RGB)).toInt(); } else { @@ -1030,7 +1029,7 @@ bool LedDeviceYeelight::init(const QJsonObject &deviceConfig) if ( deviceConfig[ CONFIG_TRANS_EFFECT ].isString() ) { - _transitionEffect = static_cast( deviceConfig[ CONFIG_TRANS_EFFECT ].toString().toInt(&ok, YeelightLight::API_EFFECT_SMOOTH) ); + _transitionEffect = static_cast( deviceConfig[ CONFIG_TRANS_EFFECT ].toString(QString(YeelightLight::API_EFFECT_SMOOTH)).toInt() ); } else { @@ -1047,7 +1046,7 @@ bool LedDeviceYeelight::init(const QJsonObject &deviceConfig) if ( deviceConfig[ CONFIG_DEBUGLEVEL ].isString() ) { - _debuglevel = deviceConfig[ CONFIG_DEBUGLEVEL ].toString().toInt(); + _debuglevel = deviceConfig[ CONFIG_DEBUGLEVEL ].toString(QString("0")).toInt(); } else { diff --git a/libsrc/leddevice/dev_other/LedDevicePiBlaster.cpp b/libsrc/leddevice/dev_other/LedDevicePiBlaster.cpp index 508ba0b4..20eaf0f7 100644 --- a/libsrc/leddevice/dev_other/LedDevicePiBlaster.cpp +++ b/libsrc/leddevice/dev_other/LedDevicePiBlaster.cpp @@ -4,11 +4,22 @@ #include // QT includes +#include #include // Local LedDevice includes #include "LedDevicePiBlaster.h" +// Constants +namespace { + const bool verbose = false; + + // Pi-Blaster discovery service + const char DISCOVERY_DIRECTORY[] = "/dev/"; + const char DISCOVERY_FILEPATTERN[] = "pi-blaster"; + +} //End of constants + LedDevicePiBlaster::LedDevicePiBlaster(const QJsonObject &deviceConfig) : LedDevice(deviceConfig) , _fid(nullptr) @@ -184,3 +195,31 @@ int LedDevicePiBlaster::write(const std::vector & ledValues) return 0; } + +QJsonObject LedDevicePiBlaster::discover(const QJsonObject& /*params*/) +{ + QJsonObject devicesDiscovered; + devicesDiscovered.insert("ledDeviceType", _activeDeviceType ); + + QJsonArray deviceList; + + QDir deviceDirectory (DISCOVERY_DIRECTORY); + QStringList deviceFilter(DISCOVERY_FILEPATTERN); + deviceDirectory.setNameFilters(deviceFilter); + deviceDirectory.setSorting(QDir::Name); + QFileInfoList deviceFiles = deviceDirectory.entryInfoList(QDir::System); + + QFileInfoList::const_iterator deviceFileIterator; + for (deviceFileIterator = deviceFiles.constBegin(); deviceFileIterator != deviceFiles.constEnd(); ++deviceFileIterator) + { + QJsonObject deviceInfo; + deviceInfo.insert("deviceName", (*deviceFileIterator).fileName()); + deviceInfo.insert("systemLocation", (*deviceFileIterator).absoluteFilePath()); + deviceList.append(deviceInfo); + } + devicesDiscovered.insert("devices", deviceList); + + DebugIf(verbose,_log, "devicesDiscovered: [%s]", QString(QJsonDocument(devicesDiscovered).toJson(QJsonDocument::Compact)).toUtf8().constData()); + + return devicesDiscovered; +} diff --git a/libsrc/leddevice/dev_other/LedDevicePiBlaster.h b/libsrc/leddevice/dev_other/LedDevicePiBlaster.h index 4d6b0732..71759571 100644 --- a/libsrc/leddevice/dev_other/LedDevicePiBlaster.h +++ b/libsrc/leddevice/dev_other/LedDevicePiBlaster.h @@ -29,6 +29,12 @@ public: /// @return LedDevice constructed static LedDevice* construct(const QJsonObject &deviceConfig); + /// @param[in] params Parameters used to overwrite discovery default behaviour + /// + /// @return A JSON structure holding a list of devices found + /// + QJsonObject discover(const QJsonObject& params) override; + protected: /// diff --git a/libsrc/leddevice/dev_serial/ProviderRs232.cpp b/libsrc/leddevice/dev_serial/ProviderRs232.cpp index f6796749..8c5d6eaa 100644 --- a/libsrc/leddevice/dev_serial/ProviderRs232.cpp +++ b/libsrc/leddevice/dev_serial/ProviderRs232.cpp @@ -2,6 +2,7 @@ // LedDevice includes #include #include "ProviderRs232.h" +#include // qt includes #include @@ -10,10 +11,16 @@ #include // Constants -constexpr std::chrono::milliseconds WRITE_TIMEOUT{1000}; // device write timeout in ms -constexpr std::chrono::milliseconds OPEN_TIMEOUT{5000}; // device open timeout in ms -const int MAX_WRITE_TIMEOUTS = 5; // Maximum number of allowed timeouts -const int NUM_POWEROFF_WRITE_BLACK = 2; // Number of write "BLACK" during powering off +namespace { + const bool verbose = false; + + constexpr std::chrono::milliseconds WRITE_TIMEOUT{ 1000 }; // device write timeout in ms + constexpr std::chrono::milliseconds OPEN_TIMEOUT{ 5000 }; // device open timeout in ms + const int MAX_WRITE_TIMEOUTS = 5; // Maximum number of allowed timeouts + const int NUM_POWEROFF_WRITE_BLACK = 2; // Number of write "BLACK" during powering off + + constexpr std::chrono::milliseconds DEFAULT_IDENTIFY_TIME{ 500 }; +} //End of constants ProviderRs232::ProviderRs232(const QJsonObject &deviceConfig) : LedDevice(deviceConfig) @@ -278,7 +285,7 @@ QJsonObject ProviderRs232::discover(const QJsonObject& /*params*/) // Discover serial Devices for (auto &port : QSerialPortInfo::availablePorts() ) { - if ( !port.isNull() && !port.portName().startsWith("ttyS")) + if ( !port.isNull() && port.vendorIdentifier() != 0) { QJsonObject portInfo; portInfo.insert("description", port.description()); @@ -294,5 +301,39 @@ QJsonObject ProviderRs232::discover(const QJsonObject& /*params*/) } devicesDiscovered.insert("devices", deviceList); + DebugIf(verbose,_log, "devicesDiscovered: [%s]", QString(QJsonDocument(devicesDiscovered).toJson(QJsonDocument::Compact)).toUtf8().constData()); + return devicesDiscovered; } + +void ProviderRs232::identify(const QJsonObject& params) +{ + DebugIf(verbose,_log, "params: [%s]", QString(QJsonDocument(params).toJson(QJsonDocument::Compact)).toUtf8().constData()); + + QString deviceName = params["output"].toString(""); + if (!deviceName.isEmpty()) + { + _devConfig = params; + init(_devConfig); + { + if ( open() == 0 ) + { + for (int i = 0; i < 2; ++i) + { + if (writeColor(ColorRgb::RED) == 0) + { + wait(DEFAULT_IDENTIFY_TIME); + + writeColor(ColorRgb::BLACK); + wait(DEFAULT_IDENTIFY_TIME); + } + else + { + break; + } + } + close(); + } + } + } +} diff --git a/libsrc/leddevice/dev_serial/ProviderRs232.h b/libsrc/leddevice/dev_serial/ProviderRs232.h index 537c51ce..2ca95982 100644 --- a/libsrc/leddevice/dev_serial/ProviderRs232.h +++ b/libsrc/leddevice/dev_serial/ProviderRs232.h @@ -26,6 +26,20 @@ public: /// ~ProviderRs232() override; + /// + /// @brief Send an update to the RS232 device to identify it. + /// + /// Following parameters are required + /// @code + /// { + /// "deviceConfig" : + /// } + ///@endcode + /// + /// @param[in] params Parameters to configure device + /// + void identify(const QJsonObject& params) override; + protected: /// diff --git a/libsrc/leddevice/dev_spi/ProviderSpi.cpp b/libsrc/leddevice/dev_spi/ProviderSpi.cpp index 44619791..2cc46d50 100644 --- a/libsrc/leddevice/dev_spi/ProviderSpi.cpp +++ b/libsrc/leddevice/dev_spi/ProviderSpi.cpp @@ -14,6 +14,19 @@ #include "ProviderSpi.h" #include +// qt includes +#include + +// Constants +namespace { + const bool verbose = false; + + // SPI discovery service + const char DISCOVERY_DIRECTORY[] = "/dev/"; + const char DISCOVERY_FILEPATTERN[] = "spidev*"; + +} //End of constants + ProviderSpi::ProviderSpi(const QJsonObject &deviceConfig) : LedDevice(deviceConfig) , _deviceName("/dev/spidev0.0") @@ -148,3 +161,31 @@ int ProviderSpi::writeBytes(unsigned size, const uint8_t * data) return retVal; } + +QJsonObject ProviderSpi::discover(const QJsonObject& /*params*/) +{ + QJsonObject devicesDiscovered; + devicesDiscovered.insert("ledDeviceType", _activeDeviceType ); + + QJsonArray deviceList; + + QDir deviceDirectory (DISCOVERY_DIRECTORY); + QStringList deviceFilter(DISCOVERY_FILEPATTERN); + deviceDirectory.setNameFilters(deviceFilter); + deviceDirectory.setSorting(QDir::Name); + QFileInfoList deviceFiles = deviceDirectory.entryInfoList(QDir::System); + + QFileInfoList::const_iterator deviceFileIterator; + for (deviceFileIterator = deviceFiles.constBegin(); deviceFileIterator != deviceFiles.constEnd(); ++deviceFileIterator) + { + QJsonObject deviceInfo; + deviceInfo.insert("deviceName", (*deviceFileIterator).fileName().remove(0,6)); + deviceInfo.insert("systemLocation", (*deviceFileIterator).absoluteFilePath()); + deviceList.append(deviceInfo); + } + devicesDiscovered.insert("devices", deviceList); + + DebugIf(verbose,_log, "devicesDiscovered: [%s]", QString(QJsonDocument(devicesDiscovered).toJson(QJsonDocument::Compact)).toUtf8().constData()); + + return devicesDiscovered; +} diff --git a/libsrc/leddevice/dev_spi/ProviderSpi.h b/libsrc/leddevice/dev_spi/ProviderSpi.h index e3956b72..63dbb20f 100644 --- a/libsrc/leddevice/dev_spi/ProviderSpi.h +++ b/libsrc/leddevice/dev_spi/ProviderSpi.h @@ -36,6 +36,12 @@ public: /// int open() override; + /// @param[in] params Parameters used to overwrite discovery default behaviour + /// + /// @return A JSON structure holding a list of devices found + /// + QJsonObject discover(const QJsonObject& params) override; + public slots: /// /// Closes the output device. diff --git a/libsrc/leddevice/schemas/schema-adalight.json b/libsrc/leddevice/schemas/schema-adalight.json index 3d42e4cf..2aa1142e 100644 --- a/libsrc/leddevice/schemas/schema-adalight.json +++ b/libsrc/leddevice/schemas/schema-adalight.json @@ -25,20 +25,24 @@ }, "lightberry_apa102_mode": { "type": "boolean", - "title":"edt_dev_spec_LBap102Mode_title", + "title": "edt_dev_spec_LBap102Mode_title", "default": false, - "access" : "advanced", - "propertyOrder" : 4 + "required": true, + "access": "advanced", + "propertyOrder": 4 }, "latchTime": { "type": "integer", - "title":"edt_dev_spec_latchtime_title", + "title": "edt_dev_spec_latchtime_title", "default": 30, - "append" : "edt_append_ms", + "append": "edt_append_ms", "minimum": 0, "maximum": 1000, - "access" : "expert", - "propertyOrder" : 5 + "access": "expert", + "options": { + "infoText": "edt_dev_spec_latchtime_title_info" + }, + "propertyOrder": 5 }, "rewriteTime": { "type": "integer", diff --git a/libsrc/leddevice/schemas/schema-apa102.json b/libsrc/leddevice/schemas/schema-apa102.json index eba9a708..ec8f08fc 100644 --- a/libsrc/leddevice/schemas/schema-apa102.json +++ b/libsrc/leddevice/schemas/schema-apa102.json @@ -5,8 +5,6 @@ "output": { "type": "string", "title":"edt_dev_spec_spipath_title", - "enum" : ["/dev/spidev0.0","/dev/spidev0.1"], - "default" : "/dev/spidev0.0", "propertyOrder" : 1 }, "rate": { diff --git a/libsrc/leddevice/schemas/schema-apa104.json b/libsrc/leddevice/schemas/schema-apa104.json index fa409884..100f755a 100644 --- a/libsrc/leddevice/schemas/schema-apa104.json +++ b/libsrc/leddevice/schemas/schema-apa104.json @@ -5,7 +5,6 @@ "output": { "type": "string", "title":"edt_dev_spec_spipath_title", - "enum" : ["/dev/spidev0.0","/dev/spidev0.1"], "propertyOrder" : 1 }, "rate": { diff --git a/libsrc/leddevice/schemas/schema-atmoorb.json b/libsrc/leddevice/schemas/schema-atmoorb.json index 0f7b6e25..4b6cc4aa 100644 --- a/libsrc/leddevice/schemas/schema-atmoorb.json +++ b/libsrc/leddevice/schemas/schema-atmoorb.json @@ -1,46 +1,47 @@ { - "type":"object", - "required":true, - "properties":{ - "orbIds": { - "type": "string", - "title":"edt_dev_spec_orbIds_title", - "default": "", - "propertyOrder" : 1 - }, - "useOrbSmoothing": { - "type": "boolean", - "title":"edt_dev_spec_useOrbSmoothing_title", - "default": true, - "access" : "advanced", - "propertyOrder" : 2 - }, - "output": { - "type": "string", - "title":"edt_dev_spec_multicastGroup_title", - "default" : "239.255.255.250", - "access" : "expert", - "propertyOrder" : 3 - }, - "port": { - "type": "integer", - "title":"edt_dev_spec_port_title", - "minimum" : 0, - "maximum" : 65535, - "default": 49692, - "access" : "expert", - "propertyOrder" : 4 - }, - "latchTime": { - "type": "integer", - "title":"edt_dev_spec_latchtime_title", - "default": 30, - "append" : "edt_append_ms", - "minimum": 0, - "maximum": 1000, - "access" : "expert", - "propertyOrder" : 5 - } - }, - "additionalProperties": true + "type": "object", + "required": true, + "properties": { + "orbIds": { + "type": "string", + "title": "edt_dev_spec_orbIds_title", + "minLength": 1, + "default": "", + "propertyOrder": 1 + }, + "useOrbSmoothing": { + "type": "boolean", + "title": "edt_dev_spec_useOrbSmoothing_title", + "default": true, + "access": "advanced", + "propertyOrder": 2 + }, + "output": { + "type": "string", + "title": "edt_dev_spec_multicastGroup_title", + "default": "239.255.255.250", + "access": "expert", + "propertyOrder": 3 + }, + "port": { + "type": "integer", + "title": "edt_dev_spec_port_title", + "minimum": 0, + "maximum": 65535, + "default": 49692, + "access": "expert", + "propertyOrder": 4 + }, + "latchTime": { + "type": "integer", + "title": "edt_dev_spec_latchtime_title", + "default": 30, + "append": "edt_append_ms", + "minimum": 0, + "maximum": 1000, + "access": "expert", + "propertyOrder": 5 + } + }, + "additionalProperties": true } diff --git a/libsrc/leddevice/schemas/schema-cololight.json b/libsrc/leddevice/schemas/schema-cololight.json index 349826d1..11942ed2 100644 --- a/libsrc/leddevice/schemas/schema-cololight.json +++ b/libsrc/leddevice/schemas/schema-cololight.json @@ -1,22 +1,40 @@ { - "type":"object", - "required":true, - "properties": { - "host" : { - "type": "string", - "title":"edt_dev_spec_targetIpHost_title", - "propertyOrder" : 1 - }, - "latchTime": { - "type": "integer", - "title":"edt_dev_spec_latchtime_title", - "default": 0, - "append" : "edt_append_ms", - "minimum": 0, - "maximum": 1000, - "access" : "expert", - "propertyOrder" : 2 - } - }, - "additionalProperties": true + "type": "object", + "required": true, + "properties": { + "hostList": { + "type": "string", + "title": "edt_dev_spec_devices_discovered_title", + "enum": [ "NONE" ], + "options": { + "enum_titles": [ "edt_dev_spec_devices_discovery_inprogress" ], + "infoText": "edt_dev_spec_devices_discovered_title_info" + }, + "required": true, + "propertyOrder": 1 + }, + "host": { + "type": "string", + "title": "edt_dev_spec_targetIpHost_title", + "options": { + "infoText": "edt_dev_spec_targetIpHost_title_info" + }, + "required": true, + "propertyOrder": 2 + }, + "latchTime": { + "type": "integer", + "title": "edt_dev_spec_latchtime_title", + "default": 0, + "append": "edt_append_ms", + "minimum": 0, + "maximum": 1000, + "access": "expert", + "options": { + "infoText": "edt_dev_spec_latchtime_title_info" + }, + "propertyOrder": 3 + } + }, + "additionalProperties": true } diff --git a/libsrc/leddevice/schemas/schema-lpd6803.json b/libsrc/leddevice/schemas/schema-lpd6803.json index 28c31866..4bd34050 100644 --- a/libsrc/leddevice/schemas/schema-lpd6803.json +++ b/libsrc/leddevice/schemas/schema-lpd6803.json @@ -5,7 +5,6 @@ "output": { "type": "string", "title":"edt_dev_spec_spipath_title", - "enum" : ["/dev/spidev0.0","/dev/spidev0.1"], "propertyOrder" : 1 }, "rate": { diff --git a/libsrc/leddevice/schemas/schema-lpd8806.json b/libsrc/leddevice/schemas/schema-lpd8806.json index bbe5b474..e2252f10 100644 --- a/libsrc/leddevice/schemas/schema-lpd8806.json +++ b/libsrc/leddevice/schemas/schema-lpd8806.json @@ -5,7 +5,6 @@ "output": { "type": "string", "title":"edt_dev_spec_spipath_title", - "enum" : ["/dev/spidev0.0","/dev/spidev0.1"], "propertyOrder" : 1 }, "rate": { diff --git a/libsrc/leddevice/schemas/schema-nanoleaf.json b/libsrc/leddevice/schemas/schema-nanoleaf.json index 97fac11f..ec3a81d3 100644 --- a/libsrc/leddevice/schemas/schema-nanoleaf.json +++ b/libsrc/leddevice/schemas/schema-nanoleaf.json @@ -1,58 +1,78 @@ { - "type":"object", - "required":true, - "properties":{ - "host": { - "type": "string", - "title":"edt_dev_spec_targetIpHost_title", - "propertyOrder" : 1 - }, - "token": { - "type": "string", - "title":"edt_dev_auth_key_title", - "propertyOrder" : 2 - }, - "title": { - "type" : "object", - "title":"edt_dev_spec_panelorganisation_title", - "access" : "advanced", - "propertyOrder" : 3 - }, - "panelOrderTopDown": { - "type": "integer", - "title":"edt_dev_spec_order_top_down_title", - "enum" : [0, 1], - "default" : 0, - "options" : { - "enum_titles" : ["edt_conf_enum_top_down", "edt_conf_enum_bottom_up"] - }, - "minimum" : 0, - "maximum" : 1, - "access" : "advanced", - "propertyOrder" : 4 - }, - "panelOrderLeftRight": { - "type": "integer", - "title":"edt_dev_spec_order_left_right_title", - "enum" : [0, 1], - "default" : 0, - "options" : { - "enum_titles" : ["edt_conf_enum_left_right", "edt_conf_enum_right_left"] - }, - "minimum" : 0, - "maximum" : 1, - "access" : "advanced", - "propertyOrder" : 5 - }, - "panelStartPos": { - "type": "integer", - "title":"edt_dev_spec_panel_start_position", - "step": 1, - "minimum" : 0, - "default": 0, - "access" : "advanced", - "propertyOrder" : 6 - } - }, - "additionalProperties": true + "type": "object", + "required": true, + "properties": { + "hostList": { + "type": "string", + "title": "edt_dev_spec_devices_discovered_title", + "enum": [ "NONE" ], + "options": { + "enum_titles": [ "edt_dev_spec_devices_discovery_inprogress" ], + "infoText": "edt_dev_spec_devices_discovered_title_info" + }, + "required": true, + "propertyOrder": 1 + }, + "host": { + "type": "string", + "title": "edt_dev_spec_targetIpHost_title", + "options": { + "infoText": "edt_dev_spec_targetIpHost_title_info" + }, + "required": true, + "propertyOrder": 2 + }, + "token": { + "type": "string", + "title": "edt_dev_auth_key_title", + "options": { + "infoText": "edt_dev_auth_key_title_info" + }, + "propertyOrder": 4 + }, + "title": { + "type": "object", + "title": "edt_dev_spec_panelorganisation_title", + "access": "advanced", + "propertyOrder": 5 + }, + "panelOrderTopDown": { + "type": "integer", + "title": "edt_dev_spec_order_top_down_title", + "enum": [ 0, 1 ], + "default": 0, + "required": true, + "options": { + "enum_titles": [ "edt_conf_enum_top_down", "edt_conf_enum_bottom_up" ] + }, + "minimum": 0, + "maximum": 1, + "access": "advanced", + "propertyOrder": 6 + }, + "panelOrderLeftRight": { + "type": "integer", + "title": "edt_dev_spec_order_left_right_title", + "enum": [ 0, 1 ], + "default": 0, + "required": true, + "options": { + "enum_titles": [ "edt_conf_enum_left_right", "edt_conf_enum_right_left" ] + }, + "minimum": 0, + "maximum": 1, + "access": "advanced", + "propertyOrder": 7 + }, + "panelStartPos": { + "type": "integer", + "title": "edt_dev_spec_panel_start_position", + "step": 1, + "minimum": 0, + "default": 0, + "access": "advanced", + "propertyOrder": 8 + } + }, + "additionalProperties": true } diff --git a/libsrc/leddevice/schemas/schema-p9813.json b/libsrc/leddevice/schemas/schema-p9813.json index a2450b5a..9309c289 100644 --- a/libsrc/leddevice/schemas/schema-p9813.json +++ b/libsrc/leddevice/schemas/schema-p9813.json @@ -5,7 +5,6 @@ "output": { "type": "string", "title":"edt_dev_spec_spipath_title", - "enum" : ["/dev/spidev0.0","/dev/spidev0.1"], "propertyOrder" : 1 }, "rate": { diff --git a/libsrc/leddevice/schemas/schema-philipshue.json b/libsrc/leddevice/schemas/schema-philipshue.json index 46b917e3..834f2f1b 100644 --- a/libsrc/leddevice/schemas/schema-philipshue.json +++ b/libsrc/leddevice/schemas/schema-philipshue.json @@ -1,242 +1,243 @@ { - "type":"object", - "required":true, - "properties":{ - "output": { - "type": "string", - "title":"edt_dev_spec_targetIp_title", - "default":"", - "propertyOrder" : 1 - }, - "username": { - "type": "string", - "title":"edt_dev_spec_username_title", - "default": "", - "propertyOrder" : 2 - }, - "clientkey": { - "type": "string", - "title":"edt_dev_spec_clientKey_title", - "default" : "", - "options": { - "dependencies": { - "useEntertainmentAPI": true - } - }, - "propertyOrder" : 3 - }, - "useEntertainmentAPI": { - "type": "boolean", - "title":"edt_dev_spec_useEntertainmentAPI_title", - "default" : false, - "propertyOrder" : 4 - }, - "transitiontime": { - "type": "number", - "title":"edt_dev_spec_transistionTime_title", - "default" : 1, - "append" : "x100ms", - "options": { - "dependencies": { - "useEntertainmentAPI": false - } - }, - "propertyOrder" : 5 - }, - "switchOffOnBlack": { - "type": "boolean", - "title":"edt_dev_spec_switchOffOnBlack_title", - "default" : false, - "propertyOrder" : 6 - }, - "restoreOriginalState": { - "type": "boolean", - "title":"edt_dev_spec_restoreOriginalState_title", - "default" : true, - "propertyOrder" : 7 - }, - "lightIds": { - "type": "array", - "title":"edt_dev_spec_lightid_title", - "minItems": 1, - "uniqueItems" : true, - "items" : { - "type" : "string", - "minimum" : 0, - "title" : "edt_dev_spec_lightid_itemtitle" - }, - "options": { - "dependencies": { - "useEntertainmentAPI": false - } - }, - "propertyOrder" : 8 - }, - "groupId": { - "type": "number", - "title":"edt_dev_spec_groupId_title", - "default" : 0, - "options": { - "dependencies": { - "useEntertainmentAPI": true - } - }, - "propertyOrder" : 9 - }, - "blackLightsTimeout": { - "type": "number", - "title":"edt_dev_spec_blackLightsTimeout_title", - "default" : 15000, - "step": 500, - "minimum" : 10000, - "maximum" : 60000, - "access" : "advanced", - "append" : "edt_append_ms", - "options": { - "dependencies": { - "useEntertainmentAPI": true - } - }, - "propertyOrder" : 10 - }, - "brightnessThreshold": { - "type": "number", - "title":"edt_dev_spec_brightnessThreshold_title", - "default" : 0, - "step": 0.005, - "minimum" : 0, - "maximum" : 1.0, - "access" : "advanced", - "options": { - "dependencies": { - "useEntertainmentAPI": true - } - }, - "propertyOrder" : 11 - }, - "brightnessFactor": { - "type": "number", - "title":"edt_dev_spec_brightnessFactor_title", - "default" : 1.0, - "step": 0.25, - "minimum" : 0.5, - "maximum" : 10.0, - "access" : "advanced", - "propertyOrder" : 12 - }, - "brightnessMin": { - "type": "number", - "title":"edt_dev_spec_brightnessMin_title", - "default" : 0, - "step": 0.05, - "minimum" : 0, - "maximum" : 1.0, - "access" : "advanced", - "options": { - "dependencies": { - "useEntertainmentAPI": true - } - }, - "propertyOrder" : 13 - }, - "brightnessMax": { - "type": "number", - "title":"edt_dev_spec_brightnessMax_title", - "default" : 1.0, - "step": 0.05, - "minimum" : 0, - "maximum" : 1.0, - "access" : "advanced", - "options": { - "dependencies": { - "useEntertainmentAPI": true - } - }, - "propertyOrder" : 14 - }, - "sslReadTimeout": { - "type": "number", - "title":"edt_dev_spec_sslReadTimeout_title", - "default" : 0, - "step": 100, - "minimum" : 0, - "maximum" : 30000, - "access" : "expert", - "append" : "edt_append_ms", - "options": { - "dependencies": { - "useEntertainmentAPI": true - } - }, - "propertyOrder" : 15 - }, - "sslHSTimeoutMin": { - "type": "number", - "title":"edt_dev_spec_sslHSTimeoutMin_title", - "default" : 400, - "step": 100, - "minimum" : 0, - "maximum" : 30000, - "access" : "expert", - "append" : "edt_append_ms", - "options": { - "dependencies": { - "useEntertainmentAPI": true - } - }, - "propertyOrder" : 16 - }, - "sslHSTimeoutMax": { - "type": "number", - "title":"edt_dev_spec_sslHSTimeoutMax_title", - "default" : 1000, - "step": 100, - "minimum" : 0, - "maximum" : 30000, - "access" : "expert", - "append" : "edt_append_ms", - "options": { - "dependencies": { - "useEntertainmentAPI": true - } - }, - "propertyOrder" : 17 - }, - "verbose": { - "type": "boolean", - "title":"edt_dev_spec_verbose_title", - "default" : false, - "access" : "expert", - "propertyOrder" : 18 - }, - "debugStreamer": { - "type": "boolean", - "title":"edt_dev_spec_debugStreamer_title", - "default" : false, - "access" : "expert", - "options": { - "dependencies": { - "useEntertainmentAPI": true - } - }, - "propertyOrder" : 19 - }, - "debugLevel": { - "type": "string", - "title":"edt_dev_spec_debugLevel_title", - "enum" : ["0", "1", "2", "3", "4"], - "default" : "0", - "options" : { - "enum_titles" : ["edt_conf_enum_dl_nodebug", "edt_conf_enum_dl_error", "edt_conf_enum_dl_statechange", "edt_conf_enum_dl_informational", "edt_conf_enum_dl_verbose"], - "dependencies": { - "useEntertainmentAPI": true - } - }, - "minimum" : 0, - "maximum" : 4, - "access" : "expert", - "propertyOrder" : 20 - } - }, - "additionalProperties": true + "type": "object", + "required": true, + "properties": { + "output": { + "type": "string", + "title": "edt_dev_spec_targetIp_title", + "default": "", + "propertyOrder": 1 + }, + "username": { + "type": "string", + "title": "edt_dev_spec_username_title", + "default": "", + "propertyOrder": 2 + }, + "clientkey": { + "type": "string", + "title": "edt_dev_spec_clientKey_title", + "default": "", + "options": { + "dependencies": { + "useEntertainmentAPI": true + } + }, + "propertyOrder": 3 + }, + "useEntertainmentAPI": { + "type": "boolean", + "title": "edt_dev_spec_useEntertainmentAPI_title", + "default": false, + "propertyOrder": 4 + }, + "transitiontime": { + "type": "number", + "title": "edt_dev_spec_transistionTime_title", + "default": 1, + "append": "x100ms", + "options": { + "dependencies": { + "useEntertainmentAPI": false + } + }, + "propertyOrder": 5 + }, + "switchOffOnBlack": { + "type": "boolean", + "title": "edt_dev_spec_switchOffOnBlack_title", + "default": false, + "propertyOrder": 6 + }, + "restoreOriginalState": { + "type": "boolean", + "title": "edt_dev_spec_restoreOriginalState_title", + "default": true, + "propertyOrder": 7 + }, + "lightIds": { + "type": "array", + "title": "edt_dev_spec_lightid_title", + "minimum": 1, + "uniqueItems": true, + "items": { + "type": "string", + "minLength": 1, + "required": true, + "title": "edt_dev_spec_lightid_itemtitle" + }, + "options": { + "dependencies": { + "useEntertainmentAPI": false + } + }, + "propertyOrder": 8 + }, + "groupId": { + "type": "number", + "title": "edt_dev_spec_groupId_title", + "default": 0, + "options": { + "dependencies": { + "useEntertainmentAPI": true + } + }, + "propertyOrder": 9 + }, + "blackLightsTimeout": { + "type": "number", + "title": "edt_dev_spec_blackLightsTimeout_title", + "default": 15000, + "step": 500, + "minimum": 10000, + "maximum": 60000, + "access": "advanced", + "append": "edt_append_ms", + "options": { + "dependencies": { + "useEntertainmentAPI": true + } + }, + "propertyOrder": 10 + }, + "brightnessThreshold": { + "type": "number", + "title": "edt_dev_spec_brightnessThreshold_title", + "default": 0, + "step": 0.005, + "minimum": 0, + "maximum": 1.0, + "access": "advanced", + "options": { + "dependencies": { + "useEntertainmentAPI": true + } + }, + "propertyOrder": 11 + }, + "brightnessFactor": { + "type": "number", + "title": "edt_dev_spec_brightnessFactor_title", + "default": 1.0, + "step": 0.25, + "minimum": 0.5, + "maximum": 10.0, + "access": "advanced", + "propertyOrder": 12 + }, + "brightnessMin": { + "type": "number", + "title": "edt_dev_spec_brightnessMin_title", + "default": 0, + "step": 0.05, + "minimum": 0, + "maximum": 1.0, + "access": "advanced", + "options": { + "dependencies": { + "useEntertainmentAPI": true + } + }, + "propertyOrder": 13 + }, + "brightnessMax": { + "type": "number", + "title": "edt_dev_spec_brightnessMax_title", + "default": 1.0, + "step": 0.05, + "minimum": 0, + "maximum": 1.0, + "access": "advanced", + "options": { + "dependencies": { + "useEntertainmentAPI": true + } + }, + "propertyOrder": 14 + }, + "sslReadTimeout": { + "type": "number", + "title": "edt_dev_spec_sslReadTimeout_title", + "default": 0, + "step": 100, + "minimum": 0, + "maximum": 30000, + "access": "expert", + "append": "edt_append_ms", + "options": { + "dependencies": { + "useEntertainmentAPI": true + } + }, + "propertyOrder": 15 + }, + "sslHSTimeoutMin": { + "type": "number", + "title": "edt_dev_spec_sslHSTimeoutMin_title", + "default": 400, + "step": 100, + "minimum": 0, + "maximum": 30000, + "access": "expert", + "append": "edt_append_ms", + "options": { + "dependencies": { + "useEntertainmentAPI": true + } + }, + "propertyOrder": 16 + }, + "sslHSTimeoutMax": { + "type": "number", + "title": "edt_dev_spec_sslHSTimeoutMax_title", + "default": 1000, + "step": 100, + "minimum": 0, + "maximum": 30000, + "access": "expert", + "append": "edt_append_ms", + "options": { + "dependencies": { + "useEntertainmentAPI": true + } + }, + "propertyOrder": 17 + }, + "verbose": { + "type": "boolean", + "title": "edt_dev_spec_verbose_title", + "default": false, + "access": "expert", + "propertyOrder": 18 + }, + "debugStreamer": { + "type": "boolean", + "title": "edt_dev_spec_debugStreamer_title", + "default": false, + "access": "expert", + "options": { + "dependencies": { + "useEntertainmentAPI": true + } + }, + "propertyOrder": 19 + }, + "debugLevel": { + "type": "string", + "title": "edt_dev_spec_debugLevel_title", + "enum": [ "0", "1", "2", "3", "4" ], + "default": "0", + "options": { + "enum_titles": [ "edt_conf_enum_dl_nodebug", "edt_conf_enum_dl_error", "edt_conf_enum_dl_statechange", "edt_conf_enum_dl_informational", "edt_conf_enum_dl_verbose" ], + "dependencies": { + "useEntertainmentAPI": true + } + }, + "minimum": 0, + "maximum": 4, + "access": "expert", + "propertyOrder": 20 + } + }, + "additionalProperties": true } diff --git a/libsrc/leddevice/schemas/schema-sk6812spi.json b/libsrc/leddevice/schemas/schema-sk6812spi.json index 229181d4..49b7fef7 100644 --- a/libsrc/leddevice/schemas/schema-sk6812spi.json +++ b/libsrc/leddevice/schemas/schema-sk6812spi.json @@ -5,7 +5,6 @@ "output": { "type": "string", "title":"edt_dev_spec_spipath_title", - "enum" : ["/dev/spidev0.0","/dev/spidev0.1"], "propertyOrder" : 1 }, "rate": { diff --git a/libsrc/leddevice/schemas/schema-sk6822spi.json b/libsrc/leddevice/schemas/schema-sk6822spi.json index 6116937e..b6620279 100644 --- a/libsrc/leddevice/schemas/schema-sk6822spi.json +++ b/libsrc/leddevice/schemas/schema-sk6822spi.json @@ -5,7 +5,6 @@ "output": { "type": "string", "title":"edt_dev_spec_spipath_title", - "enum" : ["/dev/spidev0.0","/dev/spidev0.1"], "propertyOrder" : 1 }, "rate": { diff --git a/libsrc/leddevice/schemas/schema-sk9822.json b/libsrc/leddevice/schemas/schema-sk9822.json index 7b5d5584..3777b652 100644 --- a/libsrc/leddevice/schemas/schema-sk9822.json +++ b/libsrc/leddevice/schemas/schema-sk9822.json @@ -5,7 +5,6 @@ "output": { "type": "string", "title":"edt_dev_spec_spipath_title", - "enum" : ["/dev/spidev0.0","/dev/spidev0.1"], "default" : "/dev/spidev0.0", "propertyOrder" : 1 }, diff --git a/libsrc/leddevice/schemas/schema-wled.json b/libsrc/leddevice/schemas/schema-wled.json index bb00f3de..881220c8 100644 --- a/libsrc/leddevice/schemas/schema-wled.json +++ b/libsrc/leddevice/schemas/schema-wled.json @@ -1,30 +1,84 @@ { - "type":"object", - "required":true, - "properties":{ - "host" : { - "type": "string", - "title": "edt_dev_spec_targetIpHost_title", - "required": true, - "propertyOrder": 1 - }, - "restoreOriginalState": { - "type": "boolean", - "title": "edt_dev_spec_restoreOriginalState_title", - "default": false, - "required": true, - "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", + "title": "", + "required": true, + "properties": { + "hostList": { + "type": "string", + "title": "edt_dev_spec_devices_discovered_title", + "enum": [ "NONE" ], + "options": { + "enum_titles": [ "edt_dev_spec_devices_discovery_inprogress" ], + "infoText": "edt_dev_spec_devices_discovered_title_info" + }, + "required": true, + "propertyOrder": 1 + }, + "host": { + "type": "string", + "title": "edt_dev_spec_targetIpHost_title", + "options": { + "infoText": "edt_dev_spec_targetIpHost_title_info" + }, + "required": true, + "propertyOrder": 2 + }, + "restoreOriginalState": { + "type": "boolean", + "format": "checkbox", + "title": "edt_dev_spec_restoreOriginalState_title", + "default": false, + "required": true, + "options": { + "infoText": "edt_dev_spec_restoreOriginalState_title_info" + }, + "propertyOrder": 3 + }, + "overwriteSync": { + "type": "boolean", + "format": "checkbox", + "title": "edt_dev_spec_syncOverwrite_title", + "default": true, + "required": true, + "access": "advanced", + "propertyOrder": 4 + }, + "overwriteBrightness": { + "type": "boolean", + "format": "checkbox", + "title": "edt_dev_spec_brightnessOverwrite_title", + "default": true, + "required": true, + "access": "advanced", + "propertyOrder": 5 + }, + "brightness": { + "type": "integer", + "title": "edt_dev_spec_brightness_title", + "default": 255, + "minimum": 1, + "maximum": 255, + "options": { + "dependencies": { + "overwriteBrightness": true + } + }, + "access": "advanced", + "propertyOrder": 6 + }, + "latchTime": { + "type": "integer", + "title": "edt_dev_spec_latchtime_title", + "default": 0, + "append": "edt_append_ms", + "minimum": 0, + "maximum": 1000, + "access": "expert", + "options": { + "infoText": "edt_dev_spec_latchtime_title_info" + }, + "propertyOrder": 7 + } + }, + "additionalProperties": true } diff --git a/libsrc/leddevice/schemas/schema-ws2801.json b/libsrc/leddevice/schemas/schema-ws2801.json index c1830381..ec8f08fc 100644 --- a/libsrc/leddevice/schemas/schema-ws2801.json +++ b/libsrc/leddevice/schemas/schema-ws2801.json @@ -5,7 +5,6 @@ "output": { "type": "string", "title":"edt_dev_spec_spipath_title", - "enum" : ["/dev/spidev0.0","/dev/spidev0.1","/dev/spidev1.0","/dev/spidev1.1"], "propertyOrder" : 1 }, "rate": { diff --git a/libsrc/leddevice/schemas/schema-ws2812spi.json b/libsrc/leddevice/schemas/schema-ws2812spi.json index 2949565f..d8c21d4d 100644 --- a/libsrc/leddevice/schemas/schema-ws2812spi.json +++ b/libsrc/leddevice/schemas/schema-ws2812spi.json @@ -5,7 +5,6 @@ "output": { "type": "string", "title":"edt_dev_spec_spipath_title", - "enum" : ["/dev/spidev0.0","/dev/spidev0.1"], "propertyOrder" : 1 }, "rate": { diff --git a/libsrc/leddevice/schemas/schema-yeelight.json b/libsrc/leddevice/schemas/schema-yeelight.json index d91f7675..1fe8f173 100644 --- a/libsrc/leddevice/schemas/schema-yeelight.json +++ b/libsrc/leddevice/schemas/schema-yeelight.json @@ -1,180 +1,176 @@ { - "type":"object", - "required":true, - "properties":{ - "colorModel": { - "type": "integer", - "title":"Output Type", - "enum" : [0, 1], - "default" : 1, - "options" : { - "enum_titles" : ["edt_conf_enum_hsv", "edt_conf_enum_rgb"] - }, - "minimum" : 0, - "maximum" : 1, - "access" : "advanced", - "propertyOrder" : 1 - }, - "transEffect": { - "type": "integer", - "title":"edt_dev_spec_transeffect_title", - "enum" : [0, 1], - "default" : 0, - "options" : { - "enum_titles" : ["edt_conf_enum_transeffect_smooth", "edt_conf_enum_transeffect_sudden" ] - }, - "minimum" : 0, - "maximum" : 1, - "access" : "advanced", - "propertyOrder" : 2 - }, - "transTime": { - "type": "integer", - "title":"edt_dev_spec_transistionTime_title", - "default": 40, - "append" : "ms", - "minimum": 30, - "maximum": 5000, - "access" : "advanced", - "options": { - "dependencies": { - "transEffect": 0 - } - }, - "propertyOrder" : 3 - }, - "extraTimeDarkness": { - "type": "integer", - "title":"edt_dev_spec_transistionTimeExtra_title", - "default" : 0, - "step": 100, - "minimum" : 0, - "maximum" : 8000, - "append" : "ms", - "access" : "advanced", - "propertyOrder" : 4 - }, - "brightnessMin": { - "type": "integer", - "title":"edt_dev_spec_brightnessMin_title", - "default" : 1, - "step": 1, - "minimum" : 1, - "maximum" : 99, - "append" : "%", - "access" : "advanced", - "propertyOrder" : 5 - }, - "brightnessSwitchOffOnMinimum": { - "type": "boolean", - "title":"edt_dev_spec_switchOffOnbelowMinBrightness_title", - "default" : true, - "access" : "advanced", - "propertyOrder" : 6 - }, - "brightnessMax": { - "type": "integer", - "title":"edt_dev_spec_brightnessMax_title", - "default" : 100, - "step": 1, - "minimum" : 0, - "maximum" : 100, - "append" : "%", - "access" : "advanced", - "propertyOrder" : 7 - }, - "brightnessFactor": { - "type": "number", - "title":"edt_dev_spec_brightnessFactor_title", - "default" : 1.0, - "step": 0.25, - "minimum" : 0.5, - "maximum" : 10.0, - "access" : "expert", - "propertyOrder" : 8 - }, - "restoreOriginalState": { - "type": "boolean", - "title":"edt_dev_spec_restoreOriginalState_title", - "default" : false, - "propertyOrder" : 9 - }, - "lights": { - "type": "array", - "title":"edt_dev_spec_lights_title", - "propertyOrder" : 9, - "minimum" : 1, - "uniqueItems" : true, - "items" : { - "type" : "object", - "title" : "edt_dev_spec_lights_itemtitle", - "required" : true, - "properties" : - { - "host" : - { - "type" : "string", - "minimum" : 7, - "title" : "edt_dev_spec_networkDeviceName_title", - "required" : true, - "propertyOrder" : 1 - }, - "port" : - { - "type" : "integer", - "minimum" : 0, - "maximum" : 65535, - "default":55443, - "title" : "edt_dev_spec_networkDevicePort_title", - "required" : false, - "access" : "expert", - "propertyOrder" : 2 - }, - "name" : - { - "type" : "string", - "title" : "edt_dev_spec_lights_name", - "minimum" : 0, - "propertyOrder" : 3 - } - } - }, - "propertyOrder" : 10 - }, - "quotaWait": { - "type": "integer", - "title":"Wait time (quota)", - "default": 1000, - "append" : "edt_append_ms", - "minimum": 0, - "maximum": 10000, - "step": 100, - "access" : "expert", - "propertyOrder" : 11 - }, - "latchTime": { - "type": "integer", - "title":"edt_dev_spec_latchtime_title", - "default": 40, - "append" : "edt_append_ms", - "minimum": 0, - "maximum": 1000, - "access" : "expert", - "propertyOrder" : 12 - }, - "debugLevel": { - "type": "integer", - "title":"edt_dev_spec_debugLevel_title", - "enum" : [0, 1, 2, 3], - "default" : 0, - "options" : { - "enum_titles" : ["edt_conf_enum_dl_nodebug", "edt_conf_enum_dl_verbose1", "edt_conf_enum_dl_verbose2", "edt_conf_enum_dl_verbose3"] - }, - "minimum" : 0, - "maximum" : 3, - "access" : "expert", - "propertyOrder" : 13 - } - }, - "additionalProperties": true + "type": "object", + "required": true, + "properties": { + "colorModel": { + "type": "string", + "title": "Output Type", + "enum": [ "0", "1" ], + "default": "1", + "options": { + "enum_titles": [ "edt_conf_enum_hsv", "edt_conf_enum_rgb" ] + }, + "minimum": 0, + "maximum": 1, + "access": "advanced", + "propertyOrder": 1 + }, + "transEffect": { + "type": "string", + "title": "edt_dev_spec_transeffect_title", + "enum": [ "0", "1" ], + "default": "0", + "options": { + "enum_titles": [ "edt_conf_enum_transeffect_smooth", "edt_conf_enum_transeffect_sudden" ] + }, + "minimum": 0, + "maximum": 1, + "access": "advanced", + "propertyOrder": 2 + }, + "transTime": { + "type": "integer", + "title": "edt_dev_spec_transistionTime_title", + "default": 40, + "append": "ms", + "minimum": 30, + "maximum": 5000, + "access": "advanced", + "options": { + "dependencies": { + "transEffect": 0 + } + }, + "propertyOrder": 3 + }, + "extraTimeDarkness": { + "type": "integer", + "title": "edt_dev_spec_transistionTimeExtra_title", + "default": 0, + "step": 100, + "minimum": 0, + "maximum": 8000, + "append": "ms", + "access": "advanced", + "propertyOrder": 4 + }, + "brightnessMin": { + "type": "integer", + "title": "edt_dev_spec_brightnessMin_title", + "default": 1, + "step": 1, + "minimum": 1, + "maximum": 99, + "append": "%", + "access": "advanced", + "propertyOrder": 5 + }, + "brightnessSwitchOffOnMinimum": { + "type": "boolean", + "title": "edt_dev_spec_switchOffOnbelowMinBrightness_title", + "default": true, + "access": "advanced", + "propertyOrder": 6 + }, + "brightnessMax": { + "type": "integer", + "title": "edt_dev_spec_brightnessMax_title", + "default": 100, + "step": 1, + "minimum": 0, + "maximum": 100, + "append": "%", + "access": "advanced", + "propertyOrder": 7 + }, + "brightnessFactor": { + "type": "number", + "title": "edt_dev_spec_brightnessFactor_title", + "default": 1.0, + "step": 0.25, + "minimum": 0.5, + "maximum": 10.0, + "access": "expert", + "propertyOrder": 8 + }, + "restoreOriginalState": { + "type": "boolean", + "title": "edt_dev_spec_restoreOriginalState_title", + "default": false, + "propertyOrder": 9 + }, + "lights": { + "type": "array", + "title": "edt_dev_spec_lights_title", + "propertyOrder": 9, + "minimum": 1, + "uniqueItems": true, + "items": { + "type": "object", + "title": "edt_dev_spec_lights_itemtitle", + "required": true, + "properties": { + "host": { + "type": "string", + "minLength": 7, + "title": "edt_dev_spec_networkDeviceName_title", + "required": true, + "propertyOrder": 1 + }, + "port": { + "type": "integer", + "minimum": 0, + "maximum": 65535, + "default": 55443, + "title": "edt_dev_spec_networkDevicePort_title", + "required": false, + "access": "expert", + "propertyOrder": 2 + }, + "name": { + "type": "string", + "title": "edt_dev_spec_lights_name", + "minimum": 0, + "propertyOrder": 3 + } + } + }, + "propertyOrder": 10 + }, + "quotaWait": { + "type": "integer", + "title": "Wait time (quota)", + "default": 1000, + "append": "edt_append_ms", + "minimum": 0, + "maximum": 10000, + "step": 100, + "access": "expert", + "propertyOrder": 11 + }, + "latchTime": { + "type": "integer", + "title": "edt_dev_spec_latchtime_title", + "default": 40, + "append": "edt_append_ms", + "minimum": 0, + "maximum": 1000, + "access": "expert", + "propertyOrder": 12 + }, + "debugLevel": { + "type": "string", + "title": "edt_dev_spec_debugLevel_title", + "enum": [ "0", "1", "2", "3" ], + "default": "0", + "options": { + "enum_titles": [ "edt_conf_enum_dl_nodebug", "edt_conf_enum_dl_verbose1", "edt_conf_enum_dl_verbose2", "edt_conf_enum_dl_verbose3" ] + }, + "minimum": 0, + "maximum": 3, + "access": "expert", + "propertyOrder": 13 + } + }, + "additionalProperties": true } diff --git a/src/hyperiond/hyperiond.cpp b/src/hyperiond/hyperiond.cpp index 1c12289d..ebc986f7 100644 --- a/src/hyperiond/hyperiond.cpp +++ b/src/hyperiond/hyperiond.cpp @@ -96,8 +96,8 @@ HyperionDaemon::HyperionDaemon(const QString& rootPath, QObject* parent, bool lo qRegisterMetaType>("QMap"); qRegisterMetaType>("std::vector"); - // init settings - _settingsManager = new SettingsManager(0, this, readonlyMode); + // init settings, this settingsManager accesses global settings which are independent from instances + _settingsManager = new SettingsManager(GLOABL_INSTANCE_ID, this, readonlyMode); // set inital log lvl if the loglvl wasn't overwritten by arg if (!logLvlOverwrite) From aede77b7caab25b23f69ba01e0efeabe08aea98a Mon Sep 17 00:00:00 2001 From: Portisch Date: Sun, 25 Apr 2021 16:49:42 +0200 Subject: [PATCH 05/58] AmlogicGrabber: set and decrease frame grab timeout to 500ms (#1225) This prevent an internal timeout when no frame is delivered like on playback pause. --- libsrc/grabber/amlogic/AmlogicGrabber.cpp | 3 ++- libsrc/grabber/amlogic/Amvideocap.h | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/libsrc/grabber/amlogic/AmlogicGrabber.cpp b/libsrc/grabber/amlogic/AmlogicGrabber.cpp index 34d531d0..d4ee2f08 100644 --- a/libsrc/grabber/amlogic/AmlogicGrabber.cpp +++ b/libsrc/grabber/amlogic/AmlogicGrabber.cpp @@ -132,8 +132,9 @@ int AmlogicGrabber::grabFrame_amvideocap(Image & image) long r1 = ioctl(_captureDev, AMVIDEOCAP_IOW_SET_WANTFRAME_WIDTH, _width); long r2 = ioctl(_captureDev, AMVIDEOCAP_IOW_SET_WANTFRAME_HEIGHT, _height); long r3 = ioctl(_captureDev, AMVIDEOCAP_IOW_SET_WANTFRAME_AT_FLAGS, CAP_FLAG_AT_END); + long r4 = ioctl(_captureDev, AMVIDEOCAP_IOW_SET_WANTFRAME_WAIT_MAX_MS, 500); - if (r1<0 || r2<0 || r3<0 || _height==0 || _width==0) + if (r1<0 || r2<0 || r3<0 || r4<0 || _height==0 || _width==0) { ErrorIf(_lastError != 2,_log,"Failed to configure capture device (%d - %s)", errno, strerror(errno)); _lastError = 2; diff --git a/libsrc/grabber/amlogic/Amvideocap.h b/libsrc/grabber/amlogic/Amvideocap.h index 828aa895..40c58b33 100644 --- a/libsrc/grabber/amlogic/Amvideocap.h +++ b/libsrc/grabber/amlogic/Amvideocap.h @@ -14,6 +14,7 @@ // #define AMVIDEOCAP_IOW_SET_WANTFRAME_FORMAT _IOW(AMVIDEOCAP_IOC_MAGIC, 0x01, int) #define AMVIDEOCAP_IOW_SET_WANTFRAME_WIDTH _IOW(AMVIDEOCAP_IOC_MAGIC, 0x02, int) #define AMVIDEOCAP_IOW_SET_WANTFRAME_HEIGHT _IOW(AMVIDEOCAP_IOC_MAGIC, 0x03, int) +#define AMVIDEOCAP_IOW_SET_WANTFRAME_WAIT_MAX_MS _IOW(AMVIDEOCAP_IOC_MAGIC, 0x05, unsigned long long) #define AMVIDEOCAP_IOW_SET_WANTFRAME_AT_FLAGS _IOW(AMVIDEOCAP_IOC_MAGIC, 0x06, int) #define _A_M 'S' From 35ffeb740f35181298e15d3e31622cff0b9bbe4b Mon Sep 17 00:00:00 2001 From: LordGrey <48840279+Lord-Grey@users.noreply.github.com> Date: Sun, 25 Apr 2021 16:50:27 +0200 Subject: [PATCH 06/58] Stop background effect, when it gets out of scope (#1226) --- include/hyperion/BGEffectHandler.h | 44 ++++++++++++++++++++++++++---- 1 file changed, 39 insertions(+), 5 deletions(-) diff --git a/include/hyperion/BGEffectHandler.h b/include/hyperion/BGEffectHandler.h index 9c0dfcbd..ae3362f0 100644 --- a/include/hyperion/BGEffectHandler.h +++ b/include/hyperion/BGEffectHandler.h @@ -4,6 +4,7 @@ #include #include #include +#include /// /// @brief Handle the background Effect settings, reacts on runtime to settings changes @@ -14,11 +15,19 @@ class BGEffectHandler : public QObject public: BGEffectHandler(Hyperion* hyperion) - : QObject(hyperion) - , _hyperion(hyperion) + : QObject(hyperion) + , _hyperion(hyperion) + , _prioMuxer(_hyperion->getMuxerInstance()) + , _isBgEffectConfigured(false) { // listen for config changes - connect(_hyperion, &Hyperion::settingsChanged, this, &BGEffectHandler::handleSettingsUpdate); + connect(_hyperion, &Hyperion::settingsChanged, + [=](settings::type type, const QJsonDocument& config) { this->handleSettingsUpdate(type, config); } + ); + + connect(_prioMuxer, &PriorityMuxer::prioritiesChanged, + [=]() { this->handlePriorityUpdate(); } + ); // initialization handleSettingsUpdate(settings::BGEFFECT, _hyperion->getSetting(settings::BGEFFECT)); @@ -34,14 +43,18 @@ private slots: { if(type == settings::BGEFFECT) { - const QJsonObject& BGEffectConfig = config.object(); + _isBgEffectConfigured = false; + _bgEffectConfig = config; + const QJsonObject& BGEffectConfig = _bgEffectConfig.object(); #define BGCONFIG_ARRAY bgColorConfig.toArray() // clear background priority _hyperion->clear(PriorityMuxer::BG_PRIORITY); // initial background effect/color if (BGEffectConfig["enable"].toBool(true)) { + _isBgEffectConfigured = true; + const QString bgTypeConfig = BGEffectConfig["type"].toString("effect"); const QString bgEffectConfig = BGEffectConfig["effect"].toString("Warm mood blobs"); const QJsonValue bgColorConfig = BGEffectConfig["color"]; @@ -63,12 +76,33 @@ private slots: Info(Logger::getInstance("HYPERION"),"Initial background effect '%s' %s", QSTRING_CSTR(bgEffectConfig), ((result == 0) ? "started" : "failed")); } } - #undef BGCONFIG_ARRAY } } + /// + /// @brief Handle priority updates. + /// In case the background effect is not current priority, stop BG-effect to save resources; otherwise start effect again. + /// + void handlePriorityUpdate() + { + if (_prioMuxer->getCurrentPriority() != PriorityMuxer::BG_PRIORITY && _prioMuxer->hasPriority(PriorityMuxer::BG_PRIORITY)) + { + Debug(Logger::getInstance("HYPERION"),"Stop background (color-) effect as it moved out of scope"); + _hyperion->clear(PriorityMuxer::BG_PRIORITY); + } + else if (_prioMuxer->getCurrentPriority() == PriorityMuxer::LOWEST_PRIORITY && _isBgEffectConfigured) + { + emit handleSettingsUpdate (settings::BGEFFECT, _bgEffectConfig); + } + } + private: /// Hyperion instance pointer Hyperion* _hyperion; + /// priority muxer instance + PriorityMuxer* _prioMuxer; + + QJsonDocument _bgEffectConfig; + bool _isBgEffectConfigured; }; From 724d90bfdb0175e24c434dc1bd0bfe7300628bc4 Mon Sep 17 00:00:00 2001 From: Daniel Schwierzeck Date: Sun, 25 Apr 2021 17:22:16 +0200 Subject: [PATCH 07/58] Initial support for Hue Play Gradient Lightstrip (#1187) Model ID is LCX001. Add as Gamut C device. --- CHANGELOG.md | 1 + libsrc/leddevice/dev_net/LedDevicePhilipsHue.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e00f0ccc..db71eb76 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Breaking ### Added +- LED-Devices: basic support for Hue Play Gradient Lightstrip - WLED: Support of ["live" property] (https://github.com/Aircoookie/WLED/issues/1308), addresses #1095 - WLED: Support storing/restoring state, fixes #1101 diff --git a/libsrc/leddevice/dev_net/LedDevicePhilipsHue.cpp b/libsrc/leddevice/dev_net/LedDevicePhilipsHue.cpp index 46e2c4b7..f5cb0f86 100644 --- a/libsrc/leddevice/dev_net/LedDevicePhilipsHue.cpp +++ b/libsrc/leddevice/dev_net/LedDevicePhilipsHue.cpp @@ -652,7 +652,7 @@ const std::set PhilipsHueLight::GAMUT_A_MODEL_IDS = const std::set PhilipsHueLight::GAMUT_B_MODEL_IDS = { "LCT001", "LCT002", "LCT003", "LCT007", "LLM001" }; const std::set PhilipsHueLight::GAMUT_C_MODEL_IDS = - { "LCA001", "LCA002", "LCA003", "LCG002", "LCP001", "LCP002", "LCT010", "LCT011", "LCT012", "LCT014", "LCT015", "LCT016", "LCT024", "LLC020", "LST002" }; + { "LCA001", "LCA002", "LCA003", "LCG002", "LCP001", "LCP002", "LCT010", "LCT011", "LCT012", "LCT014", "LCT015", "LCT016", "LCT024", "LCX001", "LLC020", "LST002" }; PhilipsHueLight::PhilipsHueLight(Logger* log, unsigned int id, QJsonObject values, unsigned int ledidx) : _log(log) From 71e34a6a55ed40ed36dfbbd674ee9c5bc354cbf0 Mon Sep 17 00:00:00 2001 From: LordGrey <48840279+Lord-Grey@users.noreply.github.com> Date: Sun, 25 Apr 2021 17:22:59 +0200 Subject: [PATCH 08/58] UI System Log - Fixes and enhancements (#1223) --- assets/webconfig/content/about.html | 36 +-- assets/webconfig/content/conf_logging.html | 35 +-- assets/webconfig/i18n/en.json | 1 + assets/webconfig/js/content_index.js | 4 +- assets/webconfig/js/content_logging.js | 334 ++++++++++----------- assets/webconfig/js/content_remote.js | 281 ++++++++--------- assets/webconfig/js/ledsim.js | 14 +- assets/webconfig/js/ui_utils.js | 39 ++- 8 files changed, 351 insertions(+), 393 deletions(-) diff --git a/assets/webconfig/content/about.html b/assets/webconfig/content/about.html index 665dbc4b..a293b203 100644 --- a/assets/webconfig/content/about.html +++ b/assets/webconfig/content/about.html @@ -31,39 +31,9 @@ libh += "
    " + $.i18n("about_credits"); lang = lang.toString().replace(/,/g, ", "); - // Github Issue bugreport infos - var sys = window.sysInfo.system; - var shy = window.sysInfo.hyperion; - var info = "
    Hyperion Server: \n";
    -	info += '- Build:           ' + shy.build + '\n';
    -	info += '- Build time:      ' + shy.time + '\n';
    -	info += '- Git Remote:      ' + shy.gitremote + '\n';
    -	info += '- Version:         ' + shy.version + '\n';
    -	info += '- UI Lang:         ' + storedLang + ' (BrowserLang: ' + navigator.language + ')\n';
    -	info += '- UI Access:       ' + storedAccess + '\n';
    -	//info += '- Log lvl:         ' + window.serverConfig.logger.level + '\n';
    -	info += '- Avail Capt:      ' + window.serverInfo.grabbers.available + '\n';
    -	info += '- Database:        ' + (shy.readOnlyMode ? "ready-only" : "read/write") + '\n';
    -
    -	info += '\n';
    -
    -	info += 'Hyperion Server OS: \n';
    -	info += '- Distribution:   ' + sys.prettyName + '\n';
    -	info += '- Architecture:   ' + sys.architecture + '\n';
    -
    -	if (sys.cpuModelName)
    -	  info += '- CPU Model:      ' + sys.cpuModelName + '\n';
    -	if (sys.cpuModelType)
    -	  info += '- CPU Type:       ' + sys.cpuModelType + '\n';
    -	if (sys.cpuRevision)
    -	  info += '- CPU Revision:   ' + sys.cpuRevision + '\n';
    -	if (sys.cpuHardware)
    -	  info += '- CPU Hardware:   ' + sys.cpuHardware + '\n';	
    -
    -	info += '- Kernel:         ' + sys.kernelType + ' (' + sys.kernelVersion + ' (WS: ' + sys.wordSize + '))\n';
    -	info += '- Qt Version:     ' + sys.qtVersion + '\n';
    -	info += '- Python Version: ' + sys.pyVersion + '\n';	
    -	info += '- Browser:        ' + navigator.userAgent + ' 
    '; + // Github Issue bugreport infos + var sysInfo = getSystemInfo(); + var info = '
    ' + sysInfo + '
    '; var fc = ['' + $.i18n("about_version") + '', $.i18n("about_build"), $.i18n("about_builddate"), $.i18n("about_translations"), $.i18n("about_resources", $.i18n("general_webui_title")), "System info (Github Issue)", $.i18n("about_3rd_party_licenses")]; var sc = [currentVersion, si.build, si.time, '(' + availLang.length + ')

    ' + lang + '

    ' + $.i18n("about_contribute") + '

    ', libh, info, '
    ']; diff --git a/assets/webconfig/content/conf_logging.html b/assets/webconfig/content/conf_logging.html index a9995b2f..768f9908 100644 --- a/assets/webconfig/content/conf_logging.html +++ b/assets/webconfig/content/conf_logging.html @@ -1,21 +1,22 @@
    -
    -
    - -
    -
    -
    -
    -
    -
    -

    Bericht

    - -
    - -
    -
    -
    -
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    diff --git a/assets/webconfig/i18n/en.json b/assets/webconfig/i18n/en.json index 20beaeb8..be7abeb6 100644 --- a/assets/webconfig/i18n/en.json +++ b/assets/webconfig/i18n/en.json @@ -125,6 +125,7 @@ "conf_logging_label_intro": "Area to check log messages, you will see more or less information depending on the logging level set.", "conf_logging_lastreports": "Previous reports", "conf_logging_nomessage": "No log messages available.", + "conf_logging_logoutput": "Log output", "conf_logging_report": "Report", "conf_logging_uplfailed": "Upload failed! Please check your internet connection!", "conf_logging_uploading": "Prepare data...", diff --git a/assets/webconfig/js/content_index.js b/assets/webconfig/js/content_index.js index c2263509..ff44d7fc 100755 --- a/assets/webconfig/js/content_index.js +++ b/assets/webconfig/js/content_index.js @@ -24,8 +24,6 @@ $(document).ready(function () { $(window.hyperion).on("cmd-serverinfo", function (event) { window.serverInfo = event.response.info; - window.readOnlyMode = window.sysInfo.hyperion.readOnlyMode; - // comps window.comps = event.response.info.components @@ -123,6 +121,7 @@ $(document).ready(function () { window.currentVersion = window.sysInfo.hyperion.version; window.currentChannel = window.sysInfo.hyperion.channel; + window.readOnlyMode = window.sysInfo.hyperion.readOnlyMode; }); $(window.hyperion).one("cmd-config-getschema", function (event) { @@ -340,7 +339,6 @@ $("#btn_darkmode").off().on("click", function (e) { setStorage("darkModeOverwrite", true, true); location.reload(); } - }); // Menuitem toggle; diff --git a/assets/webconfig/js/content_logging.js b/assets/webconfig/js/content_logging.js index 8ace208d..901afaa1 100644 --- a/assets/webconfig/js/content_logging.js +++ b/assets/webconfig/js/content_logging.js @@ -1,212 +1,192 @@ var conf_editor = null; var createdCont = false; +var isScroll = true; performTranslation(); +requestLoggingStop(); requestLoggingStart(); -$(document).ready(function() { - var messages; - var loguplmess = ""; - var reportUrl = 'https://report.hyperion-project.org/#'; +$(document).ready(function () { - $('#conf_cont').append(createOptPanel('fa-reorder', $.i18n("edt_conf_log_heading_title"), 'editor_container', 'btn_submit')); - if(window.showOptHelp) - { - $('#conf_cont').append(createHelpTable(window.schema.logger.properties, $.i18n("edt_conf_log_heading_title"))); - createHintH("intro", $.i18n('conf_logging_label_intro'), "log_head"); - } - $("#log_upl_pol").append(''+$.i18n("conf_logging_uplpolicy")+' '+buildWL("user/support#report_privacy_policy",$.i18n("conf_logging_contpolicy"))); + $('#conf_cont').append(createOptPanel('fa-reorder', $.i18n("edt_conf_log_heading_title"), 'editor_container', 'btn_submit')); + if (window.showOptHelp) { + $('#conf_cont').append(createHelpTable(window.schema.logger.properties, $.i18n("edt_conf_log_heading_title"))); + createHintH("intro", $.i18n('conf_logging_label_intro'), "log_head"); + } - conf_editor = createJsonEditor('editor_container', { - logger : window.schema.logger - }, true, true); + conf_editor = createJsonEditor('editor_container', { + logger: window.schema.logger + }, true, true); - conf_editor.on('change',function() { - conf_editor.validate().length || window.readOnlyMode ? $('#btn_submit').attr('disabled', true) : $('#btn_submit').attr('disabled', false); - }); + conf_editor.on('change', function () { + conf_editor.validate().length || window.readOnlyMode ? $('#btn_submit').attr('disabled', true) : $('#btn_submit').attr('disabled', false); + }); - $('#btn_submit').off().on('click',function() { - requestWriteConfig(conf_editor.getValue()); - }); + $('#btn_submit').off().on('click', function () { - $('#btn_logupload').off().on('click',function() { - uploadLog(); - $(this).attr("disabled", true); - $('#upl_link').html($.i18n('conf_logging_uploading')) - }); + var displayedLogLevel = conf_editor.getEditor("root.logger.level").getValue(); + var newLogLevel = {logger:{}}; + newLogLevel.logger.level = displayedLogLevel; - //show prev uploads - var ent; + requestWriteConfig(newLogLevel); + }); - if(getStorage("prev_reports")) - { - ent = JSON.parse(getStorage("prev_reports")); - $('#prev_reports').append('

    '+$.i18n('conf_logging_lastreports')+'

    '); - for(var i = 0; i'+ent[i].title+'('+ent[i].time+')

    '); - } - } - else - ent = []; + function infoSummary() { + var info = ""; - function updateLastReports(id,time,title) - { - if(ent.length > 4) - ent.pop(); - ent.unshift({"id": id ,"time": time,"title": title}) - setStorage("prev_reports",JSON.stringify(ent)); - } + info += 'Hyperion System Summary Report (' + window.serverConfig.general.name + '), Reported instance: ' + window.currentHyperionInstanceName + '\n'; - function uploadLog() - { - var log = ""; - var config = JSON.stringify(window.serverConfig, null).replace(/"/g, '\\"'); - var prios = window.serverInfo.priorities; - var comps = window.serverInfo.components; - var sys = window.sysInfo.system; - var shy = window.sysInfo.hyperion; - var info; + info += "\n< ----- System information -------------------- >\n"; + info += getSystemInfo() + '\n'; - //create log - log = (messages ? loguplmess : "Log was empty!"); + info += "\n< ----- Configured Instances ------------------ >\n"; + var instances = window.serverInfo.instance; + for (var i = 0; i < instances.length; i++) { + info += instances[i].instance + ': ' + instances[i].friendly_name + ' Running: ' + instances[i].running + '\n'; + } - //create general info - info = "### GENERAL ### \n"; - info += 'Build: '+shy.build+'\n'; - info += 'Build time: '+shy.time+'\n'; - info += 'Version: '+shy.version+'\n'; - info += 'UI Lang: '+storedLang+' (BrowserL: '+navigator.language+')\n'; - info += 'UI Access: '+storedAccess+'\n'; - info += 'Log lvl: '+window.serverConfig.logger.level+'\n'; - info += 'Avail Capt: '+window.serverInfo.grabbers.available+'\n'; - info += 'Database: '+(shy.readOnlyMode ? "ready-only" : "read/write")+'\n'; - info += '\n'; + info += "\n< ----- This instance's priorities ------------ >\n"; + var prios = window.serverInfo.priorities; + for (var i = 0; i < prios.length; i++) { + info += prios[i].priority + ': '; + if (prios[i].visible) { + info += ' VISIBLE!'; + } + else { + info += ' '; + } + info += ' (' + prios[i].componentId + ') Owner: ' + prios[i].owner + '\n'; + } + info += 'priorities_autoselect: ' + window.serverInfo.priorities_autoselect + '\n'; - info += 'Distribution: '+sys.prettyName+'\n'; - info += 'Architecture: '+sys.architecture+'\n'; + info += "\n< ----- This instance components' status ------->\n"; + var comps = window.serverInfo.components; + for (var i = 0; i < comps.length; i++) { + info += comps[i].name + ' - ' + comps[i].enabled + '\n'; + } - if (sys.cpuModelName) - info += 'CPU Model: ' + sys.cpuModelName + '\n'; - if (sys.cpuModelType) - info += 'CPU Type: ' + sys.cpuModelType + '\n'; - if (sys.cpuRevision) - info += 'CPU Revision: ' + sys.cpuRevision + '\n'; - if (sys.cpuHardware) - info += 'CPU Hardware: ' + sys.cpuHardware + '\n'; + info += "\n< ----- This instance's configuration --------- >\n"; + info += JSON.stringify(window.serverConfig) + '\n'; - info += 'Kernel: ' + sys.kernelType+' ('+sys.kernelVersion+' (WS: '+sys.wordSize+'))' + '\n'; - info += 'Qt Version: ' + sys.qtVersion + '\n'; - info += 'Python Version: ' + sys.pyVersion + '\n'; - info += 'Browser/OS: ' + navigator.userAgent + '\n\n'; + info += "\n< ----- Current Log --------------------------- >\n"; + var logMsgs = document.getElementById("logmessages").textContent; + if (logMsgs.length !== 0) { + info += logMsgs; + } else { + info += "Log is empty!"; + } - //create prios - info += "### PRIORITIES ### \n"; - for(var i = 0; i'+reportUrl+''); - $("html, body").animate({ scrollTop: 9999 }, "fast"); - updateLastReports(data.id,data.time,title); - } - else - { - $('#btn_logupload').attr("disabled", false); - $('#upl_link').html(''+$.i18n('conf_logging_uplfailed')+''); - } - }) - .fail( function( jqXHR, textStatus ) { - console.log(jqXHR,textStatus); - $('#btn_logupload').attr("disabled", false); - $('#upl_link').html(''+$.i18n('conf_logging_uplfailed')+''); - }); - } + $('#log_content').html('
    '); + $('#log_footer').append('' + ); - if (!window.loggingHandlerInstalled) - { - window.loggingHandlerInstalled = true; - $(window.hyperion).on("cmd-logging-update",function(event){ + $(`#btn_scroll`).bootstrapToggle(); + $(`#btn_scroll`).change(e => { + if (e.currentTarget.checked) { + //Scroll to end of log + isScroll = true; + if ($("#logmessages").length > 0) { + $('#logmessages')[0].scrollTop = $('#logmessages')[0].scrollHeight; + } + } else { + isScroll = false; + } + }); - if ($("#logmessages").length == 0 && window.loggingStreamActive) - { - requestLoggingStop(); - window.loggingStreamActive = false; - } + $('#log_footer').append(''); - messages = (event.response.result.messages); - if(messages.length != 0 && !createdCont) - { - $('#log_content').html('
    '); - createdCont = true; + $('#btn_clipboard').off().on('click', function () { + const temp = document.createElement('textarea'); + temp.textContent = infoSummary(); + document.body.append(temp); + temp.select(); + document.execCommand("copy"); + temp.remove(); + }); + } - $('#btn_autoscroll').off().on('click',function() { - toggleClass('#btn_autoscroll', "btn-success", "btn-danger"); - }); - } + function updateLogOutput(messages) { - for(var idx = 0; idx < messages.length; idx++) - { - var app_name = messages[idx].appName; - var logger_name = messages[idx].loggerName; - var function_ = messages[idx].function; - var line = messages[idx].line; - var file_name = messages[idx].fileName; - var msg = messages[idx].message; - var level_string = messages[idx].levelString; - var utime = messages[idx].utime; + if (messages.length != 0) { - var debug = ""; - if(level_string == "DEBUG") { - debug = "("+file_name+":"+line+":"+function_+"()) "; - } + for (var idx = 0; idx < messages.length; idx++) { + var app_name = messages[idx].appName; + var logger_name = messages[idx].loggerName; + var function_ = messages[idx].function; + var line = messages[idx].line; + var file_name = messages[idx].fileName; + var msg = messages[idx].message; + var level_string = messages[idx].levelString; + var utime = messages[idx].utime; - var date = new Date(parseInt(utime)); + var debug = ""; + if (level_string == "DEBUG") { + debug = "(" + file_name + ":" + line + ":" + function_ + "()) "; + } - $("#logmessages").append("\n "+date.toISOString()+" ["+app_name+" "+logger_name+"] ("+level_string+") "+debug+msg+""); - loguplmess += "["+app_name+" "+logger_name+"] ("+level_string+") "+debug+msg+"\n"; - } + var date = new Date(parseInt(utime)); + var newLogLine = date.toISOString() + " [" + app_name + " " + logger_name + "] (" + level_string + ") " + debug + msg; - if($("#btn_autoscroll").hasClass('btn-success')) - { - $('#logmessages').stop().animate({ - scrollTop: $('#logmessages')[0].scrollHeight - }, 800); - } - }); - } + $("#logmessages").append("" + newLogLine + "\n"); + } - removeOverlay(); + if (isScroll && $("#logmessages").length > 0) { + $('#logmessages').stop().animate({ + scrollTop: $('#logmessages')[0].scrollHeight + }, 800); + } + } + } + + if (!window.loggingHandlerInstalled) { + window.loggingHandlerInstalled = true; + + $(window.hyperion).on("cmd-logging-update", function (event) { + + messages = (event.response.result.messages); + + if (messages.length != 0) { + if (!createdCont) { + createLogContainer(); + createdCont = true; + } + + var currentlogLevel = window.serverConfig.logger.level; + + updateLogOutput(messages) + } + }); + } + + $(window.hyperion).on("cmd-settings-update", function (event) { + + var obj = event.response.data + if (obj.logger) { + Object.getOwnPropertyNames(obj).forEach(function (val, idx, array) { + window.serverConfig[val] = obj[val]; + }); + + var currentlogLevel = window.serverConfig.logger.level; + var displayedLogLevel = conf_editor.getEditor("root.logger.level").getValue(); + + //if ( currentlogLevel !== displayedLogLevel ) + { + conf_editor.getEditor("root.logger.level").setValue(currentlogLevel); + location.reload(); + } + } + + }); + + removeOverlay(); }); diff --git a/assets/webconfig/js/content_remote.js b/assets/webconfig/js/content_remote.js index f6898fa1..c8e07602 100644 --- a/assets/webconfig/js/content_remote.js +++ b/assets/webconfig/js/content_remote.js @@ -1,13 +1,13 @@ -$(document).ready(function() { +$(document).ready(function () { performTranslation(); var oldEffects = []; var cpcolor = '#B500FF'; var mappingList = window.serverSchema.properties.color.properties.imageToLedMappingType.enum; var duration = 0; - var rgb = {r:255,g:0,b:0}; + var rgb = { r: 255, g: 0, b: 0 }; var lastImgData = ""; - var lastFileName= ""; + var lastFileName = ""; //create html createTable('ssthead', 'sstbody', 'sstcont'); @@ -16,8 +16,7 @@ $(document).ready(function() { //create introduction - if(window.showOptHelp) - { + if (window.showOptHelp) { createHint("intro", $.i18n('remote_color_intro', $.i18n('remote_losthint')), "color_intro"); createHint("intro", $.i18n('remote_input_intro', $.i18n('remote_losthint')), "sstcont"); createHint("intro", $.i18n('remote_adjustment_intro', $.i18n('remote_losthint')), "adjust_content"); @@ -30,83 +29,73 @@ $(document).ready(function() { var sColor = sortProperties(window.serverSchema.properties.color.properties.channelAdjustment.items.properties); var values = window.serverInfo.adjustment[0]; - for(var key in sColor) - { - if(sColor[key].key != "id" && sColor[key].key != "leds") - { - var title = ''; + for (var key in sColor) { + if (sColor[key].key != "id" && sColor[key].key != "leds") { + var title = ''; var property; var value = values[sColor[key].key]; - if(sColor[key].type == "array") - { - property = '
    '; + if (sColor[key].type == "array") { + property = '
    '; $('.crtbody').append(createTableRow([title, property], false, true)); - createCP('cr_'+sColor[key].key, value, function(rgb,hex,e){ - requestAdjustment(e.target.id.substr(e.target.id.indexOf("_") + 1), '['+rgb.r+','+rgb.g+','+rgb.b+']'); + createCP('cr_' + sColor[key].key, value, function (rgb, hex, e) { + requestAdjustment(e.target.id.substr(e.target.id.indexOf("_") + 1), '[' + rgb.r + ',' + rgb.g + ',' + rgb.b + ']'); }); } - else if(sColor[key].type == "boolean") - { - property = '
    '; + else if (sColor[key].type == "boolean") { + property = '
    '; $('.crtbody').append(createTableRow([title, property], false, true)); - $('#cr_'+sColor[key].key).off().on('change', function(e){ + $('#cr_' + sColor[key].key).off().on('change', function (e) { requestAdjustment(e.target.id.substr(e.target.id.indexOf("_") + 1), e.currentTarget.checked); }); } - else - { - if(sColor[key].key == "brightness" || sColor[key].key == "brightnessCompensation" || sColor[key].key == "backlightThreshold") - property = '
    '+$.i18n("edt_append_percent")+'
    '; + else { + if (sColor[key].key == "brightness" || sColor[key].key == "brightnessCompensation" || sColor[key].key == "backlightThreshold") + property = '
    ' + $.i18n("edt_append_percent") + '
    '; else - property = ''; + property = ''; $('.crtbody').append(createTableRow([title, property], false, true)); - $('#cr_'+sColor[key].key).off().on('change', function(e){ - valValue(this.id,this.value,this.min,this.max); + $('#cr_' + sColor[key].key).off().on('change', function (e) { + valValue(this.id, this.value, this.min, this.max); requestAdjustment(e.target.id.substr(e.target.id.indexOf("_") + 1), e.currentTarget.value); }); } } } - function sendEffect() - { + function sendEffect() { var efx = $("#effect_select").val(); - if(efx != "__none__") - { + if (efx != "__none__") { requestPriorityClear(); - $(window.hyperion).one("cmd-clear", function(event) { - setTimeout(function() {requestPlayEffect(efx,duration)}, 100); + $(window.hyperion).one("cmd-clear", function (event) { + setTimeout(function () { requestPlayEffect(efx, duration) }, 100); }); } } - function sendColor() - { - requestSetColor(rgb.r, rgb.g, rgb.b,duration); + function sendColor() { + requestSetColor(rgb.r, rgb.g, rgb.b, duration); } - function updateInputSelect() - { + function updateInputSelect() { $('.sstbody').html(""); var prios = window.serverInfo.priorities; var clearAll = false; - for(var i = 0; i < prios.length; i++) - { - var origin = prios[i].origin ? prios[i].origin : "System"; + for (var i = 0; i < prios.length; i++) { + var origin = prios[i].origin ? prios[i].origin : "System"; origin = origin.split("@"); var ip = origin[1]; origin = origin[0]; - var owner = prios[i].owner; - var active = prios[i].active; - var visible = prios[i].visible; + var owner = prios[i].owner; + var active = prios[i].active; + var visible = prios[i].visible; var priority = prios[i].priority; - var compId = prios[i].componentId; - var duration = prios[i].duration_ms/1000; + var compId = prios[i].componentId; + var duration = prios[i].duration_ms / 1000; var value = "0,0,0"; var btn_type = "default"; var btn_text = $.i18n('remote_input_setsource_btn'); @@ -115,40 +104,38 @@ $(document).ready(function() { if (active) btn_type = "primary"; - if(priority > 254) + if (priority > 254) continue; - if(priority < 254 && (compId == "EFFECT" || compId == "COLOR" || compId == "IMAGE") ) + if (priority < 254 && (compId == "EFFECT" || compId == "COLOR" || compId == "IMAGE")) clearAll = true; - if (visible) - { + if (visible) { btn_state = "disabled"; btn_type = "success"; btn_text = $.i18n('remote_input_sourceactiv_btn'); } - if(ip) - origin += '
    '+$.i18n('remote_input_ip')+' '+ip+''; + if (ip) + origin += '
    ' + $.i18n('remote_input_ip') + ' ' + ip + ''; - if("value" in prios[i]) + if ("value" in prios[i]) value = prios[i].value.RGB; - switch (compId) - { + switch (compId) { case "EFFECT": - owner = $.i18n('remote_effects_label_effects')+' '+owner; + owner = $.i18n('remote_effects_label_effects') + ' ' + owner; break; case "COLOR": - owner = $.i18n('remote_color_label_color')+' '+'
    '; + owner = $.i18n('remote_color_label_color') + ' ' + '
    '; break; case "IMAGE": - owner = $.i18n('remote_effects_label_picture')+' '+owner; + owner = $.i18n('remote_effects_label_picture') + ' ' + owner; break; - case "GRABBER": - owner = $.i18n('general_comp_GRABBER')+': ('+owner+')'; + case "GRABBER": + owner = $.i18n('general_comp_GRABBER') + ': (' + owner + ')'; break; case "V4L": - owner = $.i18n('general_comp_V4L')+': ('+owner+')'; + owner = $.i18n('general_comp_V4L') + ': (' + owner + ')'; break; case "BOBLIGHTSERVER": owner = $.i18n('general_comp_BOBLIGHTSERVER'); @@ -161,126 +148,112 @@ $(document).ready(function() { break; } - if(duration && compId != "GRABBER" && compId != "FLATBUFSERVER" && compId != "PROTOSERVER") - owner += '
    '+$.i18n('remote_input_duration')+' '+duration.toFixed(0)+$.i18n('edt_append_s')+''; + if (duration && compId != "GRABBER" && compId != "FLATBUFSERVER" && compId != "PROTOSERVER") + owner += '
    ' + $.i18n('remote_input_duration') + ' ' + duration.toFixed(0) + $.i18n('edt_append_s') + ''; - var btn = ''; + var btn = ''; - if((compId == "EFFECT" || compId == "COLOR" || compId == "IMAGE") && priority < 254) - btn += ''; + if ((compId == "EFFECT" || compId == "COLOR" || compId == "IMAGE") && priority < 254) + btn += ''; - if(btn_type != 'default') + if (btn_type != 'default') $('.sstbody').append(createTableRow([origin, owner, priority, btn], false, true)); } - var btn_auto_color = (window.serverInfo.priorities_autoselect? "btn-success" : "btn-danger"); - var btn_auto_state = (window.serverInfo.priorities_autoselect? "disabled" : "enabled"); - var btn_auto_text = (window.serverInfo.priorities_autoselect? $.i18n('general_btn_on') : $.i18n('general_btn_off')); - var btn_call_state = (clearAll? "enabled" : "disabled"); - $('#auto_btn').html(''); - $('#auto_btn').append(''); + var btn_auto_color = (window.serverInfo.priorities_autoselect ? "btn-success" : "btn-danger"); + var btn_auto_state = (window.serverInfo.priorities_autoselect ? "disabled" : "enabled"); + var btn_auto_text = (window.serverInfo.priorities_autoselect ? $.i18n('general_btn_on') : $.i18n('general_btn_off')); + var btn_call_state = (clearAll ? "enabled" : "disabled"); + $('#auto_btn').html(''); + $('#auto_btn').append(''); - var max_width=100; - $('.btn_input_selection').each(function() { + var max_width = 100; + $('.btn_input_selection').each(function () { if ($(this).innerWidth() > max_width) max_width = $(this).innerWidth(); }); - $('.btn_input_selection').css("min-width",max_width+"px"); + $('.btn_input_selection').css("min-width", max_width + "px"); } - function updateLedMapping() - { + function updateLedMapping() { var mapping = window.serverInfo.imageToLedMappingType; $('#mappingsbutton').html(""); - for(var ix = 0; ix < mappingList.length; ix++) - { - if(mapping == mappingList[ix]) + for (var ix = 0; ix < mappingList.length; ix++) { + if (mapping == mappingList[ix]) var btn_style = 'btn-success'; else var btn_style = 'btn-primary'; - $('#mappingsbutton').append('
    '); + $('#mappingsbutton').append('
    '); } } - function initComponents() - { + function initComponents() { var components = window.comps; var hyperionEnabled = true; - components.forEach( function(obj) { - if (obj.name == "ALL") - { + components.forEach(function (obj) { + if (obj.name == "ALL") { hyperionEnabled = obj.enabled; } }); - for (const comp of components) - { - if(comp.name === "ALL") + for (const comp of components) { + if (comp.name === "ALL") continue; - const enable_style = (comp.enabled? "checked" : ""); - const comp_btn_id = "comp_btn_"+comp.name; + const enable_style = (comp.enabled ? "checked" : ""); + const comp_btn_id = "comp_btn_" + comp.name; - if ($("#"+comp_btn_id).length === 0) - { - var d='' - +'' - +'   ' - +''; + if ($("#" + comp_btn_id).length === 0) { + var d = '' + + ' ' + + ''; $('#componentsbutton').append(d); $(`#${comp_btn_id}`).bootstrapToggle(); $(`#${comp_btn_id}`).bootstrapToggle((hyperionEnabled ? "enable" : "disable")); $(`#${comp_btn_id}`).change(e => { - requestSetComponentState(e.currentTarget.id.split('_').pop(), e.currentTarget.checked); - //console.log(e.currentTarget.checked) + requestSetComponentState(e.currentTarget.id.split('_').pop(), e.currentTarget.checked); }); } } } - function updateComponent( component ) - { - if (component.name == "ALL") - { + function updateComponent(component) { + if (component.name == "ALL") { var components = window.comps; var hyperionEnabled = component.enabled; - for (const comp of components) - { + for (const comp of components) { - if(comp.name === "ALL") - continue; + if (comp.name === "ALL") + continue; - const comp_btn_id = "comp_btn_"+comp.name; + const comp_btn_id = "comp_btn_" + comp.name; - if ( !hyperionEnabled ) - { + if (!hyperionEnabled) { $(`#${comp_btn_id}`).bootstrapToggle('off'); $(`#${comp_btn_id}`).bootstrapToggle("disable"); } - else - { + else { $(`#${comp_btn_id}`).bootstrapToggle("enable"); - if ( comp.enabled !== $(`#${comp_btn_id}`).prop("checked") ) - { + if (comp.enabled !== $(`#${comp_btn_id}`).prop("checked")) { $(`#${comp_btn_id}`).bootstrapToggle().prop('checked', comp.enabled).change(); } } } } - else - { - const comp_btn_id = "comp_btn_"+component.name; + else { + const comp_btn_id = "comp_btn_" + component.name; //console.log ("updateComponent: ", component.name, "Current Checked: ", $(`#${comp_btn_id}`).prop("checked"), "New Checked: ", component.enabled, ); // In case Buttons were disabled before, status may be different to component status - if ( component.enabled != $(`#${comp_btn_id}`).prop("checked") ) - { + if (component.enabled != $(`#${comp_btn_id}`).prop("checked")) { // console.log ("Update status to Checked = ", component.enabled); - if ( component.enabled ) + if (component.enabled) $(`#${comp_btn_id}`).bootstrapToggle("on"); else $(`#${comp_btn_id}`).bootstrapToggle("off"); @@ -288,21 +261,19 @@ $(document).ready(function() { } } - function updateEffectlist() - { + function updateEffectlist() { var newEffects = window.serverInfo.effects; - if (newEffects.length != oldEffects.length) - { + if (newEffects.length != oldEffects.length) { $('#effect_select').html(''); var usrEffArr = []; var sysEffArr = []; - for(var i = 0; i < newEffects.length; i++) { + for (var i = 0; i < newEffects.length; i++) { var effectName = newEffects[i].name; - if(!/^\:/.test(newEffects[i].file)){ + if (!/^\:/.test(newEffects[i].file)) { usrEffArr.push(effectName); } - else{ + else { sysEffArr.push(effectName); } } @@ -312,74 +283,70 @@ $(document).ready(function() { } } - function updateVideoMode() - { - var videoModes = ["2D","3DSBS","3DTAB"]; + function updateVideoMode() { + var videoModes = ["2D", "3DSBS", "3DTAB"]; var currVideoMode = window.serverInfo.videomode; $('#videomodebtns').html(""); - for(var ix = 0; ix < videoModes.length; ix++) - { - if(currVideoMode == videoModes[ix]) + for (var ix = 0; ix < videoModes.length; ix++) { + if (currVideoMode == videoModes[ix]) var btn_style = 'btn-success'; else var btn_style = 'btn-primary'; - $('#videomodebtns').append('
    '); + $('#videomodebtns').append('
    '); } } // colorpicker and effect - if (getStorage('rmcpcolor') != null) - { + if (getStorage('rmcpcolor') != null) { cpcolor = getStorage('rmcpcolor'); rgb = hexToRgb(cpcolor); } - if (getStorage('rmduration') != null) - { + if (getStorage('rmduration') != null) { $("#remote_duration").val(getStorage('rmduration')); duration = getStorage('rmduration'); } - createCP('cp2', cpcolor, function(rgbT,hex){ + createCP('cp2', cpcolor, function (rgbT, hex) { rgb = rgbT; sendColor(); setStorage('rmcpcolor', hex); updateInputSelect(); }); - $("#reset_color").off().on("click", function(){ + $("#reset_color").off().on("click", function () { requestPriorityClear(); lastImgData = ""; $("#effect_select").val("__none__"); $("#remote_input_img").val(""); }); - $("#remote_duration").off().on("change", function(){ - duration = valValue(this.id,this.value,this.min,this.max); + $("#remote_duration").off().on("change", function () { + duration = valValue(this.id, this.value, this.min, this.max); setStorage('rmduration', duration); }); - $("#effect_select").off().on("change", function(event) { + $("#effect_select").off().on("change", function (event) { sendEffect(); }); - $("#remote_input_reseff, #remote_input_rescol").off().on("click", function(){ - if(this.id == "remote_input_rescol") + $("#remote_input_reseff, #remote_input_rescol").off().on("click", function () { + if (this.id == "remote_input_rescol") sendColor(); else sendEffect(); }); - $("#remote_input_repimg").off().on("click", function(){ - if(lastImgData != "") + $("#remote_input_repimg").off().on("click", function () { + if (lastImgData != "") requestSetImage(lastImgData, duration, lastFileName); }); - $("#remote_input_img").change(function(){ - readImg(this, function(src,fileName){ + $("#remote_input_img").change(function () { + readImg(this, function (src, fileName) { lastFileName = fileName; - if(src.includes(",")) + if (src.includes(",")) lastImgData = src.split(",")[1]; else lastImgData = src; @@ -397,27 +364,27 @@ $(document).ready(function() { // interval updates - $(window.hyperion).on('components-updated', function(e, comp){ + $(window.hyperion).on('components-updated', function (e, comp) { //console.log ("components-updated", e, comp); - updateComponent (comp); + updateComponent(comp); }); - $(window.hyperion).on("cmd-priorities-update", function(event){ + $(window.hyperion).on("cmd-priorities-update", function (event) { window.serverInfo.priorities = event.response.data.priorities; window.serverInfo.priorities_autoselect = event.response.data.priorities_autoselect; updateInputSelect(); }); - $(window.hyperion).on("cmd-imageToLedMapping-update", function(event){ + $(window.hyperion).on("cmd-imageToLedMapping-update", function (event) { window.serverInfo.imageToLedMappingType = event.response.data.imageToLedMappingType; updateLedMapping(); }); - $(window.hyperion).on("cmd-videomode-update", function(event){ + $(window.hyperion).on("cmd-videomode-update", function (event) { window.serverInfo.videomode = event.response.data.videomode; updateVideoMode(); }); - $(window.hyperion).on("cmd-effects-update", function(event){ + $(window.hyperion).on("cmd-effects-update", function (event) { window.serverInfo.effects = event.response.data.effects; updateEffectlist(); }); diff --git a/assets/webconfig/js/ledsim.js b/assets/webconfig/js/ledsim.js index 7fbdcbae..ace97bcb 100644 --- a/assets/webconfig/js/ledsim.js +++ b/assets/webconfig/js/ledsim.js @@ -249,12 +249,16 @@ $(document).ready(function() { // ------------------------------------------------------------------ $(window.hyperion).on("cmd-settings-update",function(event){ + var obj = event.response.data - Object.getOwnPropertyNames(obj).forEach(function(val, idx, array) { - window.serverInfo[val] = obj[val]; - }); - leds = window.serverConfig.leds - updateLedLayout(); + if ( obj.leds) { + console.log("ledsim: cmd-settings-update", event.response.data); + Object.getOwnPropertyNames(obj).forEach(function(val, idx, array) { + window.serverInfo[val] = obj[val]; + }); + leds = window.serverConfig.leds + updateLedLayout(); + } }); function resetImage(){ diff --git a/assets/webconfig/js/ui_utils.js b/assets/webconfig/js/ui_utils.js index 126efbec..26221f88 100755 --- a/assets/webconfig/js/ui_utils.js +++ b/assets/webconfig/js/ui_utils.js @@ -330,7 +330,7 @@ function showInfoDialog(type, header, message) { $(document).on('click', '[data-dismiss-modal]', function () { var target = $(this).attr('data-dismiss-modal'); - $.find(target).modal.hide(); + $(target).modal('hide'); }); } @@ -1070,6 +1070,43 @@ function getReleases(callback) { }); } +function getSystemInfo() { + var sys = window.sysInfo.system; + var shy = window.sysInfo.hyperion; + + var info = "Hyperion Server: \n"; + info += '- Build: ' + shy.build + '\n'; + info += '- Build time: ' + shy.time + '\n'; + info += '- Git Remote: ' + shy.gitremote + '\n'; + info += '- Version: ' + shy.version + '\n'; + info += '- UI Lang: ' + storedLang + ' (BrowserLang: ' + navigator.language + ')\n'; + info += '- UI Access: ' + storedAccess + '\n'; + //info += '- Log lvl: ' + window.serverConfig.logger.level + '\n'; + info += '- Avail Capt: ' + window.serverInfo.grabbers.available + '\n'; + info += '- Database: ' + (shy.readOnlyMode ? "ready-only" : "read/write") + '\n'; + + info += '\n'; + + info += 'Hyperion Server OS: \n'; + info += '- Distribution: ' + sys.prettyName + '\n'; + info += '- Architecture: ' + sys.architecture + '\n'; + + if (sys.cpuModelName) + info += '- CPU Model: ' + sys.cpuModelName + '\n'; + if (sys.cpuModelType) + info += '- CPU Type: ' + sys.cpuModelType + '\n'; + if (sys.cpuRevision) + info += '- CPU Revision: ' + sys.cpuRevision + '\n'; + if (sys.cpuHardware) + info += '- CPU Hardware: ' + sys.cpuHardware + '\n'; + + info += '- Kernel: ' + sys.kernelType + ' (' + sys.kernelVersion + ' (WS: ' + sys.wordSize + '))\n'; + info += '- Qt Version: ' + sys.qtVersion + '\n'; + info += '- Python Version: ' + sys.pyVersion + '\n'; + info += '- Browser: ' + navigator.userAgent; + return info; +} + function handleDarkMode() { $("", { rel: "stylesheet", From aec24e92462700f139985152af4daa4d9be656ad Mon Sep 17 00:00:00 2001 From: Paulchen Panther <16664240+Paulchen-Panther@users.noreply.github.com> Date: Sat, 1 May 2021 17:00:15 +0200 Subject: [PATCH 09/58] Snap (Linux x86_64) (#1232) --- .github/workflows/pull-request.yml | 30 ++++++++- .github/workflows/push-master.yml | 67 +++++++++++++++++++-- resources/icons/hyperion-icon-512px.png | Bin 0 -> 69597 bytes snap/snapcraft.yaml | 77 ++++++++++++++++++++++++ 4 files changed, 166 insertions(+), 8 deletions(-) create mode 100644 resources/icons/hyperion-icon-512px.png create mode 100644 snap/snapcraft.yaml diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 3b00f57d..8b221e12 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -63,7 +63,7 @@ jobs: # Upload artifacts - name: Upload artifacts - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v2 with: name: ${{ matrix.dockerImage }} path: ${{ matrix.dockerImage }} @@ -110,7 +110,7 @@ jobs: # Upload artifacts - name: Upload artifacts - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v2 with: name: macOS path: macOS @@ -184,7 +184,31 @@ jobs: # Upload artifacts - name: Upload artifacts - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v2 with: name: windows path: windows + +########################## +#### Snap (x86_64) ####### +########################## + + snap: + name: Snap (x86_64) + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + # Build snap package + - name: Build snap + id: build + uses: snapcore/action-build@v1 + + # Upload snap artifact (only on tagged commit) + - name: Upload snap artifact + uses: actions/upload-artifact@v2 + with: + name: snap + path: ${{ steps.build.outputs.snap }} diff --git a/.github/workflows/push-master.yml b/.github/workflows/push-master.yml index 2a002216..e6d658eb 100644 --- a/.github/workflows/push-master.yml +++ b/.github/workflows/push-master.yml @@ -146,12 +146,37 @@ jobs: with: path: build/Hyperion-* -################################ -###### Publish Releases ######## -################################ +########################## +#### Snap (x86_64) ####### +########################## - publish: - name: Publish Releases + snap: + name: Snap (x86_64) + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + # Build snap package + - name: Build snap + id: build + uses: snapcore/action-build@v1 + + # Upload snap artifact (only on tagged commit) + - name: Upload snap artifact + if: startsWith(github.event.ref, 'refs/tags') + uses: actions/upload-artifact@v2 + with: + name: snap + path: ${{ steps.build.outputs.snap }} + +####################################### +###### Publish GitHub Releases ######## +####################################### + + github_publish: + name: Publish GitHub Releases if: startsWith(github.event.ref, 'refs/tags') needs: [Linux, macOS, windows] runs-on: ubuntu-latest @@ -188,3 +213,35 @@ jobs: prerelease: ${{ env.preRelease }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + +############################ +###### Snap Release ######## +############################ + + snap_publish: + name: Publish Snap Release + if: startsWith(github.event.ref, 'refs/tags') + needs: [snap] + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + + # Download snap from snap job + - name: Download snap from snap build + id: download-artifact + uses: actions/download-artifact@v2 + with: + name: snap + + # Get file name of the snap + - name: Get file name of the snap + run: echo "snap=$(ls ${{ steps.download-artifact.outputs.download-path }}/hyperion-ng_*.snap)" >> $GITHUB_ENV + + # Publish snap build to edge channel + - name: Publish snap build to edge channel + uses: snapcore/action-publish@v1 + with: + store_login: ${{ secrets.SNAP_STORE_LOGIN }} + snap: ${{ env.snap }} + release: edge diff --git a/resources/icons/hyperion-icon-512px.png b/resources/icons/hyperion-icon-512px.png new file mode 100644 index 0000000000000000000000000000000000000000..53ea8fe8feb1093db2049c3f8a8fe5acb24e27e2 GIT binary patch literal 69597 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 Date: Sat, 1 May 2021 17:00:44 +0200 Subject: [PATCH 10/58] Refactor PR #957 (#1234) --- 3RD_PARTY_LICENSES | 68 +++++- README.md | 2 +- .../network_bridge/udpraw_serialadalight.py | 18 +- assets/webconfig/apple-touch-icon.png | Bin 28314 -> 39175 bytes assets/webconfig/content/about.html | 122 +++++----- assets/webconfig/content/conf_colors.html | 18 +- assets/webconfig/content/conf_effect.html | 13 + assets/webconfig/content/conf_general.html | 2 +- assets/webconfig/content/conf_leds.html | 19 +- assets/webconfig/content/conf_network.html | 6 +- assets/webconfig/content/connection_lost.html | 11 +- assets/webconfig/content/dashboard.html | 188 +++++++------- .../content/effects_configurator.html | 17 +- .../webconfig/content/ie_not_supported.html | 2 +- assets/webconfig/content/login.html | 56 ++--- assets/webconfig/content/remote.html | 13 + assets/webconfig/css/darkMode.css | 32 ++- assets/webconfig/css/hyperion.css | 211 ++++++++++------ .../webconfig/css/materialdesignicons.min.css | 3 + assets/webconfig/favicon.png | Bin 2612 -> 1459 bytes .../fonts/materialdesignicons-webfont.eot | Bin 0 -> 1026396 bytes .../fonts/materialdesignicons-webfont.ttf | Bin 0 -> 1026176 bytes .../fonts/materialdesignicons-webfont.woff | Bin 0 -> 465188 bytes .../fonts/materialdesignicons-webfont.woff2 | Bin 0 -> 325244 bytes assets/webconfig/i18n/cs.json | 5 +- assets/webconfig/i18n/de.json | 7 +- assets/webconfig/i18n/en.json | 12 +- assets/webconfig/i18n/es.json | 5 +- assets/webconfig/i18n/it.json | 5 +- assets/webconfig/i18n/nl.json | 5 +- assets/webconfig/i18n/pl.json | 5 +- assets/webconfig/i18n/ru.json | 5 +- assets/webconfig/i18n/sv.json | 5 +- assets/webconfig/i18n/tr.json | 5 +- assets/webconfig/i18n/vi.json | 5 +- .../webconfig/img/hyperion/hyperionlogo.png | Bin 10549 -> 0 bytes .../img/hyperion/hyperionwhitelogo.png | Bin 23592 -> 0 bytes .../webconfig/img/hyperion/logo_negativ.png | Bin 0 -> 12854 bytes .../webconfig/img/hyperion/logo_positiv.png | Bin 0 -> 13790 bytes assets/webconfig/img/hyperion/ssdp_icon.png | Bin 11111 -> 16532 bytes assets/webconfig/index.html | 49 +--- assets/webconfig/js/content_colors.js | 31 ++- assets/webconfig/js/content_dashboard.js | 229 +++++++++--------- assets/webconfig/js/content_effects.js | 32 ++- .../js/content_effectsconfigurator.js | 6 +- assets/webconfig/js/content_general.js | 34 ++- assets/webconfig/js/content_index.js | 7 + assets/webconfig/js/content_leds.js | 69 +++--- assets/webconfig/js/content_logging.js | 14 +- assets/webconfig/js/content_network.js | 64 +++-- assets/webconfig/js/content_remote.js | 3 + assets/webconfig/js/content_webconfig.js | 2 +- assets/webconfig/js/hyperion.js | 2 +- assets/webconfig/js/ledsim.js | 20 +- assets/webconfig/js/ui_utils.js | 81 ++++--- assets/webconfig/js/wizard.js | 19 +- assets/webconfig/mstile-144x144.png | Bin 20241 -> 30612 bytes cmake/nsis/header.bmp | Bin 0 -> 34254 bytes cmake/nsis/hyperion-logo-vert.bmp | Bin 154542 -> 0 bytes cmake/nsis/hyperion-logo.bmp | Bin 88054 -> 0 bytes cmake/nsis/installer.ico | Bin 89173 -> 203536 bytes cmake/nsis/logo.bmp | Bin 0 -> 206038 bytes cmake/packages.cmake | 6 +- doc/logo.png | Bin 0 -> 21219 bytes .../leddevice/dev_net/LedDeviceYeelight.cpp | 10 +- 65 files changed, 943 insertions(+), 600 deletions(-) create mode 100644 assets/webconfig/css/materialdesignicons.min.css create mode 100644 assets/webconfig/fonts/materialdesignicons-webfont.eot create mode 100644 assets/webconfig/fonts/materialdesignicons-webfont.ttf create mode 100644 assets/webconfig/fonts/materialdesignicons-webfont.woff create mode 100644 assets/webconfig/fonts/materialdesignicons-webfont.woff2 delete mode 100644 assets/webconfig/img/hyperion/hyperionlogo.png delete mode 100644 assets/webconfig/img/hyperion/hyperionwhitelogo.png create mode 100644 assets/webconfig/img/hyperion/logo_negativ.png create mode 100644 assets/webconfig/img/hyperion/logo_positiv.png create mode 100644 cmake/nsis/header.bmp delete mode 100644 cmake/nsis/hyperion-logo-vert.bmp delete mode 100644 cmake/nsis/hyperion-logo.bmp create mode 100644 cmake/nsis/logo.bmp create mode 100644 doc/logo.png diff --git a/3RD_PARTY_LICENSES b/3RD_PARTY_LICENSES index 76ea8ba1..e573395a 100644 --- a/3RD_PARTY_LICENSES +++ b/3RD_PARTY_LICENSES @@ -704,6 +704,64 @@ trademarks does not indicate endorsement of the trademark holder by Font Awesome, nor vice versa. **Please do not use brand logos for any purpose except to represent the company, product, or service to which they refer.** +============ +Material Design Icons +============ + +Copyright (c) 2014, Austin Andrews (http://materialdesignicons.com/), with Reserved Font Name Material Design Icons. + +Copyright (c) 2014, Google (http://www.google.com/design/) uses the license at https://github.com/google/material-design-icons/blob/master/LICENSE + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL + +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +PREAMBLE The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, +to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and +improved in partnership with others. + +The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. +The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. +The fonts and derivatives, however, cannot be released under any other type of license. +The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives. + +DEFINITIONS "Font Software" refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. +This may include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the copyright statement(s). + +"Original Version" refers to the collection of Font Software components as distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, or substituting -- in part or in whole -- any of the components of the Original Version, +by changing formats or by porting the Font Software to a new environment. + +"Author" refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, +to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions: + +Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself. + +Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, +provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, +human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user. + +No Modified Version of the Font Software may use the Reserved Font Name(s) unless explicit written permission is granted by the corresponding Copyright Holder. +This restriction only applies to the primary font name as presented to the users. + +The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall not be used to promote, +endorse or advertise any Modified Version, except to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with their explicit written permission. + +The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. +The requirement for fonts to remain under this license does not apply to any document created using the Font Software. + +TERMINATION This license becomes null and void if any of the above conditions are not met. + +DISCLAIMER THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, +DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. + ===== Gijgo ===== @@ -739,7 +797,7 @@ HIDAPI Copyright 2009, Alan Ott, Signal 11 Software. All Rights Reserved. - + This software may be used by anyone for any reason so long as the copyright notice in the source files remains intact. @@ -856,8 +914,8 @@ THE SOFTWARE. ## Markdown -Copyright © 2004, John Gruber -http://daringfireball.net/ +Copyright © 2004, John Gruber +http://daringfireball.net/ All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: @@ -1377,7 +1435,7 @@ tinkerforge Copyright (C) 2012-2013 Matthias Bolte Copyright (C) 2011 Olaf Lüke - + Redistribution and use in source and binary forms of this file, with or without modification, are permitted. @@ -1415,7 +1473,7 @@ Pulse-Eight Licensing http://www.pulse-eight.net/ - + GNU GENERAL PUBLIC LICENSE Version 2, June 1991 diff --git a/README.md b/README.md index 22a3a73a..e6258b08 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -![Hyperion](https://raw.githubusercontent.com/hyperion-project/hyperion.ng/master/assets/webconfig/img/hyperion/hyperionlogo.png) +![Hyperion](doc/logo.png) [![Latest-Release](https://img.shields.io/github/v/release/hyperion-project/hyperion.ng?include_prereleases)](https://github.com/hyperion-project/hyperion.ng/releases) [![GitHub Actions](https://github.com/hyperion-project/hyperion.ng/workflows/Hyperion%20CI%20Build/badge.svg?branch=master)](https://github.com/hyperion-project/hyperion.ng/actions) diff --git a/assets/firmware/arduino/network_bridge/udpraw_serialadalight.py b/assets/firmware/arduino/network_bridge/udpraw_serialadalight.py index b69efe20..b46597a9 100755 --- a/assets/firmware/arduino/network_bridge/udpraw_serialadalight.py +++ b/assets/firmware/arduino/network_bridge/udpraw_serialadalight.py @@ -138,7 +138,7 @@ to this service over the network. sys.stderr.write( '--- UDP to Serial redirector\n' '--- listening on udp port {a.localport}\n' - '--- sending to {p.name} {p.baudrate},{p.bytesize}{p.parity}{p.stopbits}\n' + '--- sending to {p.name} {p.baudrate},{p.bytesize}{p.parity}{p.stopbits}\n' '--- type Ctrl-C / BREAK to quit\n'.format(p=ser, a=args)) try: @@ -153,7 +153,7 @@ to this service over the network. srv = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - srv.bind(('0.0.0.0', args.localport)) + srv.bind(('0.0.0.0', args.localport)) # lgtm [py/bind-socket-all-network-interfaces] try: intentional_exit = False @@ -165,14 +165,14 @@ to this service over the network. if not data: break - if args.ada: - numleds = len(data)/3 - hi = (numleds-1)/256 - lo = (numleds-1)&255 - sum = hi^lo^0x55 - ser.write ("Ada"+ chr(hi) + chr(lo) + chr(sum)) + if args.ada: + numleds = len(data)/3 + hi = (numleds-1)/256 + lo = (numleds-1)&255 + sum = hi^lo^0x55 + ser.write ("Ada"+ chr(hi) + chr(lo) + chr(sum)) - ser.write(data) # get a bunch of bytes and send them + ser.write(data) # get a bunch of bytes and send them except socket.error as msg: if args.develop: raise diff --git a/assets/webconfig/apple-touch-icon.png b/assets/webconfig/apple-touch-icon.png index 48342175311803dff0d5989e21b9c6f298e45ab5..f6e7d55f6efda274e3213a3b221bedad87a42ca3 100644 GIT binary patch literal 39175 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 literal 28314 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 - -
    -
    -
    + +
    +
    +
    -
    - -
    +
    + +
    diff --git a/assets/webconfig/content/conf_colors.html b/assets/webconfig/content/conf_colors.html index e3972070..0ec85948 100644 --- a/assets/webconfig/content/conf_colors.html +++ b/assets/webconfig/content/conf_colors.html @@ -2,11 +2,23 @@
    - -
    + + +
    +
    + +
    +
    + +
    - diff --git a/assets/webconfig/content/conf_effect.html b/assets/webconfig/content/conf_effect.html index 704172d8..282cdf1f 100644 --- a/assets/webconfig/content/conf_effect.html +++ b/assets/webconfig/content/conf_effect.html @@ -2,6 +2,19 @@
    + +
    +
    + +
    +
    +
    diff --git a/assets/webconfig/content/conf_general.html b/assets/webconfig/content/conf_general.html index 50a439c4..161a551c 100644 --- a/assets/webconfig/content/conf_general.html +++ b/assets/webconfig/content/conf_general.html @@ -6,7 +6,7 @@
    -
    +
    diff --git a/assets/webconfig/content/conf_leds.html b/assets/webconfig/content/conf_leds.html index 60e2d16e..6236da28 100755 --- a/assets/webconfig/content/conf_leds.html +++ b/assets/webconfig/content/conf_leds.html @@ -4,7 +4,20 @@ Hyperion - LED Device Configuration
    - + + +
    +
    + +
    +
    +
    diff --git a/assets/webconfig/content/conf_network.html b/assets/webconfig/content/conf_network.html index 6b370360..ab4b7c33 100644 --- a/assets/webconfig/content/conf_network.html +++ b/assets/webconfig/content/conf_network.html @@ -1,12 +1,12 @@
    - +
    -
    +
    @@ -34,4 +34,4 @@
    - \ No newline at end of file + diff --git a/assets/webconfig/content/connection_lost.html b/assets/webconfig/content/connection_lost.html index 760f46d2..d1a1c697 100644 --- a/assets/webconfig/content/connection_lost.html +++ b/assets/webconfig/content/connection_lost.html @@ -1,6 +1,6 @@
    - Redefine ambient light! + Redefine ambient light!

    Lost connection to Hyperion service!


    @@ -29,6 +29,15 @@ var count = 1; var reconnectInterval = 4000; var connURL = window.location.protocol+"//"+window.location.hostname+":"+window.jsonPort+window.location.pathname+window.location.hash; + +if($('body').css("background-color") == "rgb(33, 33, 33)") { + // Dark Mode (darkMode.css:3) + document.getElementById("hyperion").src = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAfQAAAB4CAMAAAAzDfCvAAADAFBMVEVHcEz///////////////////+96M3////////////////////////5///+///////////////////////////////////////jD03n2AHfAYb///////8B+9H///////9Zvxia79P9///t9AL////Evv7/////////////Dw3/JRD/////Ay7d/fj08wEY7/wSE/r///+YBMtC0DQZEvtKyCYA+fFRuxH/DQSVBMwICPkF+ZoA+OwA8PyjE8wJIfzj++hWwBb/////////CQr/EBL///+XBMzy+wL0AI//BwjbvfV8A9f39gAA9vgA7/v///+++P8U63D+AGhlww7wAJkHGvwsDvMA9OMA+u9MwhdLwBsJ9In///////7/////EQX3+gK71P/ZAKenAcCMDNAB9bZQvxj/FQc00joE96CbA8v9AHD/BQ8C/aVOAej///+h4Ad+A9YA9s1XwRbyAJj/DQyiBMnmAKf///8l3lMa6GUC/aMA+ulzAtoAkvz/nAK46wT9AUzDArz/Axkg31bl+gQA+vT9ksAb5F8A7Pz////72wPz+4m+ALRQwgz/mYoA+PjMArgA/uEcqPz/laT/AixewhQBafv7lwc4zjTwAJ37+f/4AJBzANQ2zDX/AU5HCuwnAPT/VwT+lQX/qQP9lwf9IQDlAKnWAbPJArqyA8PzkdYA3vz/ATJMAej9XwjikeIB+7mH1hD/AGFpCuEE+5r6tQM1AOrJ8gsAovwAZfz884j+ABb+WwbD7wAAq/r9zwUKpPkA/cXG8gf/AHgp1EIKcPn///8Ac/mc4wD6ZAAKPvp10gCK1Qv/AGfU9wf/VATR8Y/+0gPh943i4v8Kz/v/AEEAPvqo5AlWAN+n5Q+A0A226wub3gzC65D6AI/9sIu45pDXlen/AGX////8w4v74IrZ2P+Oi/727fv80ovj4/9U8fz+UE3C7LOc23HS/P62t//bs/L16/zKfedva/7b3f/67v3////6/v3u7v717f3t+//s9f71/fMldEOMAAAA/nRSTlMADGJD7woBXkuP81JxBSgs6OK0/PglFBsEBgnNPRWfdAQgZU4hRzVrqi4WRx7+IQpigxwT3zMwDA4RwDvIIDU1/VzGl5hX21WI6kVMJ92V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"; +} else { + // Light Mode + document.getElementById("hyperion").src = "data:image/png;base64,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"; +} + function tryReconnect() { if(count > 100) diff --git a/assets/webconfig/content/dashboard.html b/assets/webconfig/content/dashboard.html index a83d72ec..a83ef2e9 100644 --- a/assets/webconfig/content/dashboard.html +++ b/assets/webconfig/content/dashboard.html @@ -1,98 +1,92 @@ -
    -
    -
    - -
    -
    -
    -
    -
    - - Information -
    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    unknown
    Platform:
    LED type:
    Instance
    Ports
    Hyperion version:unknown
    Watched version branch:unknown
    Latest version:unknown
    -
    -

    Smart Access

    - -


    - -
    -
    -
    -
    -
    -
    - - Components status -
    -
    - - - - - - - - - -
    ComponentStatus
    -
    -
    -
    - -
    -
    - -
    - -
    - +
    +
    +
    + - + +
    + + +
    + + +
    +
    +
    + Status +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + Ports +
    protounknown
    flatunknown
    jsonunknown
    websocketunknown
    + + + + + + + + + + + + + + + + + + + + + + + +
    + + Version +
    Hyperion version:unknown
    Watched version branch:unknown
    Latest version:unknown
    +
    + +
    +
    +
    + +
    +
    + +
    + +
    + + + diff --git a/assets/webconfig/content/effects_configurator.html b/assets/webconfig/content/effects_configurator.html index a6b7b344..2d7015f5 100644 --- a/assets/webconfig/content/effects_configurator.html +++ b/assets/webconfig/content/effects_configurator.html @@ -1,7 +1,20 @@
    - + + +
    +
    + +
    +
    +
    @@ -20,7 +33,7 @@ - +