diff --git a/.github/workflows/qt5_6.yml b/.github/workflows/qt5_6.yml
index 2af9e875..9cd59df8 100644
--- a/.github/workflows/qt5_6.yml
+++ b/.github/workflows/qt5_6.yml
@@ -226,13 +226,13 @@ jobs:
echo '::endgroup::'
- name: 💾 Artifact download
- uses: actions/download-artifact@v4.1.4
+ uses: actions/download-artifact@v4.1.7
with:
pattern: artifact-*
path: all-artifacts
- name: 📦 Upload
- uses: softprops/action-gh-release@v1
+ uses: softprops/action-gh-release@v2
with:
name: Hyperion ${{ env.VERSION }}
tag_name: ${{ env.TAG }}
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7ca3c166..75577551 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,14 +8,41 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Breaking
+**JSON-API**
+- Align JSON subscription update elements. `ledcolors-imagestream-update, ledcolors-ledstream-update, logmsg-update` now return data via `data` and not `result
+
### Added
- Support gaps on Matrix Layout (#1696)
+**JSON-API**
+- New subscription support for event updates, i.e. `Suspend, Resume, Idle, idleResume, Restart, Quit`.
+- Support direct or multiple instance addressing via single requests (#809)
+- Support of `serverinfo` subcommands: `getInfo, subscribe, unsubscribe, getSubscriptions, getSubscriptionCommands`
+- [Overview](https://github.com/hyperion-project/hyperion.ng/blob/API_Auth/doc/development/JSON-API%20_Commands_Overview.md) of API commands and subscription updates
+
### Changed
+- Fixed: Cross Site Scripting Vulnerability (CVE-2024-4174, CVE-2024-4175)
+- Fixed: hyperion-v4l2 taking screenshot failed (#1722)
+- Nanoleaf: Support new devices and do not restore ExtControl state
+- Workaround to address Web UI keeps forcing browser to download the html instead (#1692)
+- Fixed: Kodi Color Calibration, Refactor Wizards (#1674)
+- Fixed: Token Dialog not closing
+
+**JSON-API**
+- Refactored JSON-API to ensure consistent authorization behaviour across sessions and single requests with token authorization.
+- Provide additional error details with API responses, esp. on JSON parsing, validation or token errors.
+- Generate random TANs for every API request from the Hyperion UI
+- Fixed: Handling of IP4 addresses wrapped in IPv6 for external network connections-
+
### Removed
+**JSON-API**
+- Removed ability to enable/disable local admin authorization. All admin commands require authorization, i.e. `authorize-adminRequired` will always be `true`.
+- Removed `session-updates` subscription
+- `serverinfo/subscribe` element will be deprecated and replaced by corresponding subcommand
+
## [2.0.16](https://github.com/hyperion-project/hyperion.ng/releases/tag/2.0.16) - 2024-01
### Added
diff --git a/assets/webconfig/i18n/bg.json b/assets/webconfig/i18n/bg.json
new file mode 100644
index 00000000..15fa2b7b
--- /dev/null
+++ b/assets/webconfig/i18n/bg.json
@@ -0,0 +1,93 @@
+{
+ "conf_effect_path_intro": "Заредете ефекти от дефинираните пътища. Освен това можете да деактивирате отделни ефекти по име, за да ги скриете от всички списъци с ефекти.",
+ "conf_general_impexp_expbtn": "Експортиране",
+ "conf_general_impexp_impbtn": "Импортиране",
+ "conf_general_impexp_l1": "Импортирайте конфигурация, като изберете конфигурационен файл по-долу и щракнете върху „Импортиране“.",
+ "conf_general_impexp_l2": "Експортирайте конфигурация, като щракнете върху „Експортиране“. Вашият браузър започва изтегляне.",
+ "conf_general_impexp_title": "Импортиране/Експортиране на Конфигурация",
+ "conf_general_intro": "Основни настройки около Hyperion и WebUI, които не се вписват в друга категория.",
+ "conf_general_label_title": "Общи настройки",
+ "conf_helptable_expl": "Обяснение",
+ "conf_helptable_option": "Опции",
+ "conf_leds_layout_checkp1": "Черният светодиод е вашият първи светодиод, първият светодиод е точката, в която въвеждате вашия сигнал с данни.",
+ "conf_leds_layout_checkp2": "Оформлението винаги е изгледът отпред на вашия телевизор, никога отзад.",
+ "conf_leds_layout_checkp3": "Уверете се, че посоката е правилна. Сивите светодиоди показват светодиод номер 2 и 3 за визуализиране на посоката на данните.",
+ "conf_leds_optgroup_network": "Мрежа",
+ "dashboard_alert_message_confedit": "Вашата конфигурация на Hyperion е променена. За да го приложите, рестартирайте Hyperion.",
+ "dashboard_alert_message_confedit_t": "Конфигурацията е променена",
+ "dashboard_alert_message_confsave_success": "Вашата конфигурация на Hyperion е запазена успешно. Вашите промени вече са активни.",
+ "dashboard_alert_message_confsave_success_t": "Конфигурацията е запаметена",
+ "dashboard_alert_message_disabled": "Тази инстанция в момента е деактивирана! За да я използвате отново, активирайте я на таблото за управление.",
+ "dashboard_alert_message_disabled_t": "ЛЕД хардуерна инстанция е деактивирана",
+ "dashboard_componentbox_label_comp": "Компонент",
+ "dashboard_componentbox_label_status": "Статус",
+ "dashboard_componentbox_label_title": "Статус на компонентите",
+ "dashboard_infobox_label_currenthyp": "Вашата версия на Hyperion:",
+ "dashboard_infobox_label_instance": "Инстанция:",
+ "dashboard_infobox_label_latesthyp": "Последната версия на Hyperion:",
+ "dashboard_infobox_label_platform": "Платформа:",
+ "dashboard_infobox_label_ports": "Портове",
+ "dashboard_infobox_label_statush": "Hyperion статус:",
+ "dashboard_infobox_label_title": "Информация",
+ "dashboard_infobox_message_updatesuccess": "Вие използвате най-новата версия на Hyperion.",
+ "dashboard_infobox_message_updatewarning": "Нова версия на Hyperion е налична! ($1)",
+ "dashboard_newsbox_label_title": "Hyperion-Блог",
+ "dashboard_newsbox_noconn": "Не можете да се свържете с Hyperion Server, за да извлечете най-новите публикации, вашата интернет връзка работи ли?",
+ "dashboard_newsbox_readmore": "Прочети повече",
+ "dashboard_newsbox_visitblog": "Посети Hyperion-Блог",
+ "edt_conf_webc_port_title": "HTTP порт",
+ "general_access_default": "По подразбиране",
+ "general_btn_back": "Назад",
+ "general_btn_cancel": "Откажи",
+ "general_btn_continue": "Продължи",
+ "general_btn_next": "Следващ",
+ "general_btn_off": "Изключване",
+ "general_btn_ok": "ОК",
+ "general_btn_on": "Включване",
+ "general_btn_restarthyperion": "Рестартирай Hyperion",
+ "general_btn_save": "Запази",
+ "general_btn_saveandreload": "Запази и презареди",
+ "general_btn_yes": "Да",
+ "general_button_savesettings": "Запази настройките",
+ "general_col_blue": "синьо",
+ "general_col_green": "зелено",
+ "general_col_red": "червено",
+ "general_comp_LEDDEVICE": "ЛЕД Изход",
+ "general_country_de": "Германия",
+ "general_country_es": "Испания",
+ "general_country_fr": "Франция",
+ "general_country_it": "Италия",
+ "general_country_nl": "Холандия",
+ "general_country_uk": "Англия",
+ "general_country_us": "САЩ",
+ "general_speech_cs": "Чешки",
+ "general_speech_de": "Немски",
+ "general_speech_en": "Английски",
+ "general_speech_es": "Испански",
+ "general_speech_he": "Иврит",
+ "general_speech_id": "Индонезийски",
+ "general_speech_it": "Италиански",
+ "general_speech_uk": "Украински",
+ "general_webui_title": "Hyperion - Уеб Конфигурация",
+ "main_ledsim_btn_togglelednumber": "Брой ЛЕД",
+ "main_ledsim_btn_toggleleds": "Показване на светодиоди",
+ "main_ledsim_btn_togglelivevideo": "Видео на живо",
+ "main_ledsim_text": "Визуализация на живо на LED цветове и по избор текущия видео поток на вашето устройство за прихващане.",
+ "main_ledsim_title": "ЛЕД Визуализация",
+ "main_menu_about_token": "Относно Hyperion",
+ "main_menu_colors_conf_token": "Обработка на изображение",
+ "main_menu_configuration_token": "ЛЕД Инстанции",
+ "main_menu_dashboard_token": "Табло за управление",
+ "main_menu_effect_conf_token": "Ефекти",
+ "main_menu_effectsconfigurator_token": "Конфигуратор на ефекти",
+ "main_menu_general_conf_token": "Общи",
+ "main_menu_input_selection_token": "Ибор на вход",
+ "main_menu_leds_conf_token": "ЛЕД Изход",
+ "main_menu_logging_token": "Дневник",
+ "main_menu_network_conf_token": "Мрежови услуги",
+ "main_menu_remotecontrol_token": "Дистанционно управление",
+ "main_menu_support_token": "Поддръжка",
+ "main_menu_system_token": "Система",
+ "main_menu_update_token": "Актуализация",
+ "main_menu_webconfig_token": "Уеб конфигурация"
+}
\ No newline at end of file
diff --git a/assets/webconfig/i18n/de.json b/assets/webconfig/i18n/de.json
index 56848b0a..a41167a6 100644
--- a/assets/webconfig/i18n/de.json
+++ b/assets/webconfig/i18n/de.json
@@ -360,7 +360,7 @@
"edt_conf_enum_NTSC": "NTSC",
"edt_conf_enum_PAL": "PAL",
"edt_conf_enum_SECAM": "SECAM",
- "edt_conf_enum_VERTICAL": "Horizontal",
+ "edt_conf_enum_VERTICAL": "Vertikal",
"edt_conf_enum_action_idle": "Leerlauf",
"edt_conf_enum_action_restart": "Neustart",
"edt_conf_enum_action_resume": "Aktivieren",
@@ -496,15 +496,12 @@
"edt_conf_log_level_expl": "Abhängig der Stufe sind weniger oder mehr Meldungen sichtbar.",
"edt_conf_log_level_title": "Protokollstufe",
"edt_conf_net_apiAuth_expl": "Zwinge alle Anwendungen, welche die Hyperion API nutzen sich zu authentifizieren. Aktivieren für höhere Sicherheit, da nun jede neue Anwendung einmalig von dir bestätigt werden muss.",
- "edt_conf_net_apiAuth_title": "API-Authentifizierung",
"edt_conf_net_heading_title": "Network",
"edt_conf_net_internetAccessAPI_expl": "Erlaube Zugriff auf das Hyperion API/Webinterface über das Internet. Deaktiviere den Zugriff für höhere Sicherheit.",
"edt_conf_net_internetAccessAPI_title": "Internet API-Zugriff",
"edt_conf_net_ipWhitelist_expl": "Anstatt den Zugriff für alle Verbindungen aus dem Internet zu erlauben kannst du hier Ausnahmen für zugelassene IP-Adressen hinzufügen.",
"edt_conf_net_ipWhitelist_title": "Erlaubte IP-Adressen",
"edt_conf_net_ip_itemtitle": "IP",
- "edt_conf_net_localAdminAuth_expl": "Wenn aktiviert, muss der Administrationszugriff aus dem Heimnetzwerk mit einem Passwort authentifiziert werden.",
- "edt_conf_net_localAdminAuth_title": "Lokale Admin Authentifizierung",
"edt_conf_net_localApiAuth_expl": "Wenn aktiviert, müssen Verbindungen aus dem Heimnetzwerk mit einem Token authentifiziert werden.",
"edt_conf_net_localApiAuth_title": "Lokale API-Authentifizierung",
"edt_conf_net_restirctedInternetAccessAPI_expl": "Den Zugriff auf die API über das Internet auf bestimmte IP-Adressen beschränken",
@@ -513,7 +510,7 @@
"edt_conf_os_events_lockEnable_title": "Reagiere auf Bildschirmsperre",
"edt_conf_os_events_suspendEnable_expl": "Reagiere auf Ereignisse, die das Betriebssystem aussetzen/fortsetzen",
"edt_conf_os_events_suspendEnable_title": "Reagiere auf Ruhezusstand",
- "edt_conf_os_events_suspendOnLockEnable_expl": "Wechsel in den Ruhezustand, wenn der Bildschirm gesperrt ist; andernfalls in den Leerlaufmodus wechseln",
+ "edt_conf_os_events_suspendOnLockEnable_expl": "Wechsel in den Ruhezustand, wenn der Bildschirm gesperrt ist, andernfalls in den Leerlaufmodus wechseln",
"edt_conf_os_events_suspendOnLockEnable_title": "Leerlauf, bei Bildschirmsperre",
"edt_conf_pbs_heading_title": "Protocol Buffers Server",
"edt_conf_pbs_timeout_expl": "Wenn für die angegebene Zeit keine Daten empfangen werden, wird die Komponente (vorübergehend) deaktiviert",
@@ -560,7 +557,7 @@
"edt_conf_v4l2_encoding_title": "Videokodierungsformat",
"edt_conf_v4l2_flip_expl": "Hiermit kannst du das Bild in horizontaler, vertikaler oder in beide Richtungen spiegeln.",
"edt_conf_v4l2_flip_title": "Spiegelung",
- "edt_conf_v4l2_fpsSoftwareDecimation_expl": "Jeder n-te Frame wird übersprungen um Ressourcen zu sparen.\nBeispiel: Ein Wert von 5 resultiert bei einem Aufnahmegerät mit 30fps in einer neuen Framerate von 6 fps.",
+ "edt_conf_v4l2_fpsSoftwareDecimation_expl": "Um Ressourcen zu sparen, wird nur jedes n-te Bild verarbeitet. Wenn z.B. der Grabber auf 30fps eingestellt ist und diese Option auf 5 gesetzt ist, wird das Endergebnis ca. 6fps sein.",
"edt_conf_v4l2_fpsSoftwareDecimation_title": "Überspringen von Frames",
"edt_conf_v4l2_framerate_expl": "Die unterstützten Bilder pro Sekunde des aktiven Gerätes. Auf 'Automatisch' wird der gewählte Modus vom v4l interface beibehalten.",
"edt_conf_v4l2_framerate_title": "Bilder pro Sekunde",
@@ -758,7 +755,7 @@
"edt_eff_colorHour": "Farbe Stunde",
"edt_eff_colorMarker": "Marker Farbe",
"edt_eff_colorMinute": "Farbe Minute",
- "edt_eff_colorSecond": "Farbe Sekunde",
+ "edt_eff_colorSecond": "Farbe der Sekunden",
"edt_eff_colorcount": "Farblänge",
"edt_eff_colorend": "Farbe Ende",
"edt_eff_colorendtime": "Zeit für Start-Farbe",
@@ -1071,7 +1068,7 @@
"remote_input_sourceactiv_btn": "Quelle aktiv",
"remote_input_status": "Status/Aktion",
"remote_losthint": "Hinweis: Alle Änderungen gehen nach einem Neustart verloren.",
- "remote_maptype_intro": "Für gewöhnlich entscheidet dein LED-Layout welcher Bildbereich welche LED zugewiesen bekommt, dies kann hier geändert werden. $1",
+ "remote_maptype_intro": "Für gewöhnlich entscheidet dein LED-Layout welcher Bildbereich welche LED zugewiesen bekommt, dies kann hier geändert werden: $1",
"remote_maptype_label": "LED-Bereich Zuordnung",
"remote_maptype_label_dominant_color": "Dominante Farbe",
"remote_maptype_label_dominant_color_advanced": "Dominante Farbe fortgeschritten",
@@ -1096,7 +1093,6 @@
"support_label_fbtext": "Teile Inhalte in Facebook und halte dich und andere auf dem Laufenden",
"support_label_forumtext": "Diskussion und Hilfestellung von der Community",
"support_label_forumtitle": "Forum",
- "support_label_ggtext": "Platziere uns in deinen Kreisen auf Google+",
"support_label_ghtext": "Besuche uns auf GitHub",
"support_label_igtext": "Schau doch mal bei Instagram vorbei!",
"support_label_intro": "Hyperion ist ein kostenloses Open Source Projekt und ein kleines Team arbeitet an seiner Weiterentwicklung. Darum benötigen wir DEINE Unterstützung, um weiter in bessere Infrastruktur und Weiterentwicklung investieren zu können.",
diff --git a/assets/webconfig/i18n/en.json b/assets/webconfig/i18n/en.json
index 63155f2a..071ba69a 100644
--- a/assets/webconfig/i18n/en.json
+++ b/assets/webconfig/i18n/en.json
@@ -186,6 +186,7 @@
"conf_network_json_intro": "The JSON-RPC-Port of all Hyperion instances, used for remote control.",
"conf_network_net_intro": "Network related settings which are applied to all network services.",
"conf_network_proto_intro": "The PROTO-Port of all Hyperion instances, used for picture streams (HyperionScreenCap, Kodi Addon, Android Hyperion Grabber, ...)",
+ "conf_network_tok_idhead": "ID",
"conf_network_tok_cidhead": "Description",
"conf_network_tok_comment_title": "Token description",
"conf_network_tok_desc": "Tokens grant other applications access to the Hyperion API, an application can request a token where you need to accept it or you create them on your own below. These tokens are just required when \"API Authorization\" is enabled in network settings.",
@@ -457,13 +458,13 @@
"edt_conf_fw_flat_expl": "One flatbuffer target per configuration item",
"edt_conf_fw_flat_itemtitle": "flatbuffer target",
"edt_conf_fw_flat_services_discovered_expl": "Hyperion servers discovered providing flatbuffer services",
- "edt_conf_fw_flat_services_discovered_title": "Flatbuffer targets discoverded",
+ "edt_conf_fw_flat_services_discovered_title": "Flatbuffer targets discovered",
"edt_conf_fw_flat_title": "List of flatbuffer targets",
"edt_conf_fw_heading_title": "Forwarder",
"edt_conf_fw_json_expl": "One JSON target per configuration item",
"edt_conf_fw_json_itemtitle": "JSON target",
"edt_conf_fw_json_services_discovered_expl": "Hyperion servers discovered providing JSON-API services",
- "edt_conf_fw_json_services_discovered_title": "JSON targets discoverded",
+ "edt_conf_fw_json_services_discovered_title": "JSON targets discovered",
"edt_conf_fw_json_title": "List of JSON targets",
"edt_conf_fw_remote_service_discovered_none": "No remote services discovered",
"edt_conf_fw_service_name_expl": "Name of the service provider",
@@ -503,19 +504,16 @@
"edt_conf_log_level_expl": "Depending on loglevel you see less or more messages in your log.",
"edt_conf_log_level_title": "Log-Level",
"edt_conf_net_apiAuth_expl": "Enforce all applications that use the Hyperion API to authenticate themself against Hyperion (Exception: see \"Local API Authentication\"). Higher security, as you control the access and revoke it at any time.",
- "edt_conf_net_apiAuth_title": "API Authentication",
"edt_conf_net_heading_title": "Network",
- "edt_conf_net_internetAccessAPI_expl": "Allow access to the Hyperion API/Webinterface from the internet. Disable for higher security.",
+ "edt_conf_net_internetAccessAPI_expl": "Allow access to the Hyperion API/Web Interface from the Internet. Disable for increased security.",
"edt_conf_net_internetAccessAPI_title": "Internet API Access",
- "edt_conf_net_ipWhitelist_expl": "You can whitelist IP addresses instead allowing all connections from internet to connect to the Hyperion API/Webinterface.",
- "edt_conf_net_ipWhitelist_title": "Whitelisted IP's",
+ "edt_conf_net_ipWhitelist_expl": "Define whitelisted IP addresses from which API requests from the Internet are allowed. All other external connections will be denied.",
+ "edt_conf_net_ipWhitelist_title": "Whitelisted IP addresses",
"edt_conf_net_ip_itemtitle": "IP",
- "edt_conf_net_localAdminAuth_expl": "When enabled, administration access from your local network needs a password.",
- "edt_conf_net_localAdminAuth_title": "Local Admin API Authentication",
- "edt_conf_net_localApiAuth_expl": "When enabled, connections from your home network needs to authenticate themselves against Hyperion with a token.",
+ "edt_conf_net_localApiAuth_expl": "When disabled, API authorisation via password or token is not required for local connections. The exception is administrative commands.",
"edt_conf_net_localApiAuth_title": "Local API Authentication",
- "edt_conf_net_restirctedInternetAccessAPI_expl": "You can restrict the access to the API through the internet to certain IP's.",
- "edt_conf_net_restirctedInternetAccessAPI_title": "Restrict to IP's",
+ "edt_conf_net_restirctedInternetAccessAPI_expl": "You can restrict API requests over the Internet to only those IP addresses on the whitelist.",
+ "edt_conf_net_restirctedInternetAccessAPI_title": "Restrict to IP addresses",
"edt_conf_os_events_lockEnable_title": "Listen to lock events",
"edt_conf_os_events_lockEnable_expl": "Listen to screen lock/unlock events",
"edt_conf_os_events_suspendEnable_title": "Listen to suspend events",
@@ -764,17 +762,17 @@
"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_colorHour": "Color hours",
"edt_eff_colorMarker": "Marker color",
- "edt_eff_colorMinute": "Color minute",
- "edt_eff_colorSecond": "Color second",
+ "edt_eff_colorMinute": "Color minutes",
+ "edt_eff_colorSecond": "Color seconds",
"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_colorone": "Color one",
"edt_eff_colorrandom": "Random color",
- "edt_eff_colorshift": "Color Shift",
+ "edt_eff_colorshift": "Color shift",
"edt_eff_colorstart": "Color start",
"edt_eff_colorstarttime": "Time to hold end color",
"edt_eff_colortwo": "Color two",
@@ -846,7 +844,7 @@
"edt_eff_reversedirection": "Reverse direction",
"edt_eff_rotationtime": "Rotation time",
"edt_eff_saturation": "Saturation",
- "edt_eff_set_post_color": "Set post color after alam",
+ "edt_eff_set_post_color": "Set post color after alarm",
"edt_eff_showseconds": "Show seconds",
"edt_eff_sleeptime": "Sleep time",
"edt_eff_smooth_custom": "Enable smoothing",
@@ -974,6 +972,7 @@
"general_country_us": "United States",
"general_disabled": "disabled",
"general_enabled": "enabled",
+ "general_speech_bg": "Bulgarian",
"general_speech_ca": "Catalan",
"general_speech_cs": "Czech",
"general_speech_da": "Danish",
@@ -1101,7 +1100,6 @@
"support_label_fbtext": "Share our Hyperion Facebook page and get a notice when new updates are released",
"support_label_forumtext": "Showcases, discussions, help and more",
"support_label_forumtitle": "Forum",
- "support_label_ggtext": "Circle us on Google +!",
"support_label_ghtext": "Visit us on GitHub",
"support_label_igtext": "Visit us on Instagram to watch the latest Hyperion pictures!",
"support_label_intro": "Hyperion is a free, non-profit software. A small team is working on it and this is why we need your steady support.",
@@ -1123,7 +1121,7 @@
"update_no_updates_for_branch": "No updates for selected version channel.",
"update_versreminder": "Your version: $1",
"wiz_atmoorb_desc2": "Now choose which Orbs should be added. The position assigns the lamp to a specific position on your \"picture\". Disabled lamps won't be added. To identify single lamps press the button on the right.",
- "wiz_atmoorb_intro1": "This wizards configures Hyperion for AtmoOrbs. Features are the AtmoOrb auto detection, setting each light to a specific position on your picture or disable it and optimise the Hyperion settings automatically! So in short: All you need are some clicks and you are done!",
+ "wiz_atmoorb_intro1": "This wizard configures Hyperion for AtmoOrbs. Features are the AtmoOrb auto detection, setting each light to a specific position on your picture or disable it and optimise the Hyperion settings automatically! So in short: All you need are some clicks and you are done!",
"wiz_atmoorb_title": "AtmoOrb Wizard",
"wiz_cc_adjustgamma": "Gamma: What you have to do is, adjust gamma levels of each channel until you have the same perceived amount of each channel. Hint: Neutral is 1.0! For example, if your Grey is a bit reddish it means that you have to increase red gamma to reduce the amount of red (the more gamma, the less amount of color).",
"wiz_cc_adjustit": "Adjust your \"$1\", until your are fine with it. Take notice: The more you adjust away from the default value the color spectrum will be limited (Also for all colors in between). Depending on TV/LED color spectrum the results will vary.",
@@ -1148,7 +1146,7 @@
"wiz_cc_testintrowok": "Check out the following link to download test videos:",
"wiz_cc_title": "Colour calibration wizard",
"wiz_cololight_desc2": "Now choose which Cololights should be added. To identify single lights, press the button on the right.",
- "wiz_cololight_intro1": "This wizards configures Hyperion for the Cololight system. Features are the Cololight auto detection and tune the Hyperion settings automatically! In short: All you need are some clicks and you are done! Note: In case of Cololight Strip, you might need to manually correct the LED count and layout.",
+ "wiz_cololight_intro1": "This wizard configures Hyperion for the Cololight system. Features are the Cololight auto detection and tune the Hyperion settings automatically! In short: All you need are some clicks and you are done! Note: In case of Cololight Strip, you might need to manually correct the LED count and layout.",
"wiz_cololight_noprops": "Not able to get device properties - Define Hardware LED count manually",
"wiz_cololight_title": "Cololight Wizard",
"wiz_guideyou": "The $1 will guide you through the settings. Just press the button!",
@@ -1162,7 +1160,7 @@
"wiz_hue_e_desc1": "1. Hyperion searches automatically for a Hue-Bridge, in case it cannot find one you need to provide the hostname or IP-address and push the reload button. 2. Provide a user id and the clientkey, if you do not have both, create new ones.",
"wiz_hue_e_desc2": "3. Choose your entertainment group, which has all your lights inside for use with Hyperion.",
"wiz_hue_e_desc3": "4. Choose in which position the respective lamp should be \"in the picture\". A preselection of the position was made based on the configured positions of the lights in the entertainment group. This is just a recommendation and can be customized as desired. You can therefore highlight them briefly by clicking on the right button to improve the selection.",
- "wiz_hue_e_intro1": "This wizards configures Hyperion for the well known Philips Hue Entertainment system. Features are: Hue Bridge auto detection, user and clientkey creation, entertainment group selection, setting group lights to a specific position on your picture and optimise the Hyperion settings automatically! So in short: All you need are some clicks and you are done!",
+ "wiz_hue_e_intro1": "This wizard configures Hyperion for the well known Philips Hue Entertainment system. Features are: Hue Bridge auto detection, user and clientkey creation, entertainment group selection, setting group lights to a specific position on your picture and optimise the Hyperion settings automatically! So in short: All you need are some clicks and you are done!",
"wiz_hue_e_noapisupport": "The Wizard has disabled entertainment API support and will continue in classic mode.",
"wiz_hue_e_noapisupport_hint": "The option \"Use Hue Entertainment API \" was unchecked.",
"wiz_hue_e_noegrpids": "No entertainment groups in this Hue bridge defined.",
@@ -1173,7 +1171,7 @@
"wiz_hue_failure_connection": "Timeout: Please press the bridge button within the period of 30 seconds",
"wiz_hue_failure_ip": "No Bridge found, please provide a valid hostname or IP-address",
"wiz_hue_failure_user": "User not found, create a new one with the button below or input a valid user id and press the \"reload\" symbol.",
- "wiz_hue_intro1": "This wizards configures Hyperion for the well known Philips Hue system. Features are Hue Bridge auto detection, user creation, set each hue light to a specific position on your picture or disable it and tune the Hyperion settings automatically! So in short: All you need are some clicks and you are done!",
+ "wiz_hue_intro1": "This wizard configures Hyperion for the well known Philips Hue system. Features are Hue Bridge auto detection, user creation, set each hue light to a specific position on your picture or disable it and tune the Hyperion settings automatically! So in short: All you need are some clicks and you are done!",
"wiz_hue_ip": "Hostname or IP",
"wiz_hue_noids": "This Hue bridge has no bulbs/stripes, please pair them before with the Hue Apps",
"wiz_hue_press_link": "Please press link button on the Hue Bridge.",
@@ -1205,7 +1203,7 @@
"wiz_cc_try_connect": "Connecting...",
"wiz_wizavail": "Wizard available",
"wiz_yeelight_desc2": "Now choose which lamps should be added. The position assigns the lamp to a specific position on your \"picture\". Disabled lamps won't be added. To identify single lamps press the button on the right.",
- "wiz_yeelight_intro1": "This wizards configures Hyperion for the Yeelight system. Features are the Yeelighs' auto detection, setting each light to a specific position on your picture or disable it and tune the Hyperion settings automatically! So in short: All you need are some clicks and you are done!",
+ "wiz_yeelight_intro1": "This wizard configures Hyperion for the Yeelight system. Features are the Yeelights' auto detection, setting each light to a specific position on your picture or disable it and tune the Hyperion settings automatically! So in short: All you need are some clicks and you are done!",
"wiz_yeelight_title": "Yeelight Wizard",
"wiz_yeelight_unsupported": "Unsupported"
}
diff --git a/assets/webconfig/i18n/es.json b/assets/webconfig/i18n/es.json
index be9ec081..a6c18c11 100644
--- a/assets/webconfig/i18n/es.json
+++ b/assets/webconfig/i18n/es.json
@@ -22,6 +22,8 @@
"about_resources": "$1 librerías",
"about_translations": "Traducciones",
"about_version": "Versión",
+ "conf_cec_events_heading_title": "Eventos CEC",
+ "conf_cec_events_intro": "Ajustes relacionados con los diferentes eventos del protocolo CEC (Consumer Electronics Control) que Hyperion puede gestionar",
"conf_colors_blackborder_intro": "Omite bordes negros dondequiera que estén. Cada modo usa otro algoritmo de detección que está ajustado para situaciones especiales. Sube el umbral si no percibes funcionamiento.",
"conf_colors_color_intro": "Crea uno o más perfiles de calibración, ajusta cada color, brillo, linealización y más.",
"conf_colors_smoothing_intro": "El suavizado aplana los cambios de color/brillo para reducir la distracción molesta.",
@@ -82,6 +84,8 @@
"conf_leds_layout_cl_bottomright": "Inferior Derecha (Esquina)",
"conf_leds_layout_cl_cornergap": "Hueco de esquina",
"conf_leds_layout_cl_edgegap": "Hueco de borde",
+ "conf_leds_layout_cl_entertainment": "Área de Entretenimiento",
+ "conf_leds_layout_cl_entertainment_center": "Centro del Área de Entretenimiento",
"conf_leds_layout_cl_gaglength": "Longitud de hueco",
"conf_leds_layout_cl_gappos": "Posición del hueco",
"conf_leds_layout_cl_hleddepth": "Profundidad LED Horizontal",
@@ -111,7 +115,13 @@
"conf_leds_layout_cl_topright": "Superior Derecha (Esquina)",
"conf_leds_layout_cl_vleddepth": "Profundidad LED vertical",
"conf_leds_layout_frame": "Disposición Clásica (Marco LED)",
+ "conf_leds_layout_gapbottom": "Hueco Inferior",
+ "conf_leds_layout_gapleft": "Hueco izquierdo",
+ "conf_leds_layout_gapright": "Hueco derecho",
+ "conf_leds_layout_gaptop": "Hueco superior",
"conf_leds_layout_generatedconf": "Configuración LED Generada/Actual",
+ "conf_leds_layout_generation_error": "Trazado de LED no generado",
+ "conf_leds_layout_generation_success": "Trazado de LED generado correctamente",
"conf_leds_layout_intro": "Necesitas también un diseño led, que refleje tus posiciones led. La disposición clásica es el marco generalmente usado de la TV, pero también apoyamos la creación de matriz led (paredes led). La vista en esta disposición es SIEMPRE del FRENTE de tu TV.",
"conf_leds_layout_ma_cabling": "Cableado",
"conf_leds_layout_ma_direction": "Dirección",
@@ -184,6 +194,10 @@
"conf_network_tok_intro": "Aquí puedes crear y eliminar Tokens para la autenticación de la API. Los Tokens creados sólo se mostrarán una vez.",
"conf_network_tok_lastuse": "Último uso",
"conf_network_tok_title": "Gestión de Tokens",
+ "conf_os_events_heading_title": "Eventos del sistema operativo",
+ "conf_os_events_intro": "Ajustes relacionados con diferentes eventos del sistema operativo que Hyperion puede gestionar",
+ "conf_sched_events_heading_title": "Eventos programados",
+ "conf_sched_events_intro": "Ajustes relacionados con eventos programados, es decir, basados en el tiempo, que Hyperion gestionará",
"conf_webconfig_label_intro": "Ajustes de configuración web. Editar sabiamente.",
"dashboard_active_instance": "Instalación seleccionada",
"dashboard_alert_message_confedit": "Se ha modificado la configuración de Hyperion. Para aplicarlo, reinicia Hyperion.",
@@ -235,6 +249,9 @@
"edt_append_pixel": "Píxel",
"edt_append_s": "s",
"edt_append_sdegree": "s/grado",
+ "edt_conf_action_expl": "Acción a ser aplicada",
+ "edt_conf_action_record_validation_error": "Un mismo evento sólo puede desencadenar una acción. Limpiar Acciones $1",
+ "edt_conf_action_title": "Acción",
"edt_conf_audio_device_expl": "Dispositivo de entrada de audio seleccionado",
"edt_conf_audio_device_title": "Dispositivo de Audio",
"edt_conf_audio_effect_enum_vumeter": "Medidor-UV",
@@ -271,6 +288,17 @@
"edt_conf_bb_unknownFrameCnt_title": "Fotogramas desconocidos",
"edt_conf_bge_heading_title": "Efecto/color de fondo",
"edt_conf_bobls_heading_title": "Servidor Boblight",
+ "edt_conf_cec_actions_header_expl": "Definir qué acción debe llevarse a cabo en un acontecimiento CEC reconocido",
+ "edt_conf_cec_actions_header_item_title": "Acción",
+ "edt_conf_cec_actions_header_title": "Acciones",
+ "edt_conf_cec_button_release_delay_ms_expl": "Tiempo de liberación del botón remoto",
+ "edt_conf_cec_button_release_delay_ms_title": "Tiempo de liberación del botón",
+ "edt_conf_cec_button_repeat_rate_ms_expl": "Tasa de repetición de botones remotos",
+ "edt_conf_cec_button_repeat_rate_ms_title": "Tasa de repetición de botones",
+ "edt_conf_cec_double_tap_timeout_ms_expl": "Retardo de pulsación de botón remoto antes de repetir",
+ "edt_conf_cec_double_tap_timeout_ms_title": "Retardo del botón antes de repetir",
+ "edt_conf_cec_event_expl": "Evento CEC que desencadenará una acción",
+ "edt_conf_cec_event_title": "Evento CEC",
"edt_conf_color_accuracyLevel_expl": "Nivel de precisión con el que se evalúan los colores dominantes. Un nivel más alto crea resultados más precisos, pero también requiere más potencia de procesamiento. Debe combinarse con un procesamiento de píxeles reducido.",
"edt_conf_color_accuracyLevel_title": "Nivel de precisión",
"edt_conf_color_backlightColored_expl": "Añade un poco de color a tu retroiluminación.",
@@ -333,6 +361,13 @@
"edt_conf_enum_PAL": "PAL",
"edt_conf_enum_SECAM": "SECAM",
"edt_conf_enum_VERTICAL": "Vertical",
+ "edt_conf_enum_action_idle": "Inactivo",
+ "edt_conf_enum_action_restart": "Reiniciar",
+ "edt_conf_enum_action_resume": "Reanudar",
+ "edt_conf_enum_action_resumeIdle": "Reanudar Inactividad",
+ "edt_conf_enum_action_suspend": "Suspender",
+ "edt_conf_enum_action_toggleIdle": "Alternar Inactividad",
+ "edt_conf_enum_action_toggleSuspend": "Alternar Suspension",
"edt_conf_enum_automatic": "Automático",
"edt_conf_enum_bbclassic": "Clásico",
"edt_conf_enum_bbdefault": "Predeterminado",
@@ -341,6 +376,12 @@
"edt_conf_enum_bgr": "BGR",
"edt_conf_enum_bottom_up": "De abajo a arriba",
"edt_conf_enum_brg": "BRG",
+ "edt_conf_enum_cec_key_f1_blue": "Botón azul pulsado",
+ "edt_conf_enum_cec_key_f2_red": "Botón rojo pulsado",
+ "edt_conf_enum_cec_key_f3_green": "Botón verde pulsado",
+ "edt_conf_enum_cec_key_f4_yellow": "Botón amarillo pulsado",
+ "edt_conf_enum_cec_opcode_set stream path": "TV encendida",
+ "edt_conf_enum_cec_opcode_standby": "TV apagada",
"edt_conf_enum_color": "Color",
"edt_conf_enum_custom": "Personalizado",
"edt_conf_enum_decay": "Degradación",
@@ -455,22 +496,28 @@
"edt_conf_log_level_expl": "Dependiendo del nivel de registro verás menos o más mensajes en tu registro.",
"edt_conf_log_level_title": "Nivel de registro",
"edt_conf_net_apiAuth_expl": "Imponer a todas las aplicaciones que utilizan la API de Hyperion a autenticarse contra Hyperion (Excepción: \"Autenticación de la API local\"). Mayor seguridad, ya que se controla el acceso y se revoca en cualquier momento.",
- "edt_conf_net_apiAuth_title": "Autenticación de API",
"edt_conf_net_heading_title": "Red",
"edt_conf_net_internetAccessAPI_expl": "Permite el acceso a la API/interfaz web de Hyperion desde Internet, desactivado para mayor seguridad.",
"edt_conf_net_internetAccessAPI_title": "Acceso a la API de Internet",
"edt_conf_net_ipWhitelist_expl": "Puedes hacer una lista blanca de direcciones IP en vez de permitir que todas las conexiones de internet se conecten a la API/Webinterface de Hyperion.",
"edt_conf_net_ipWhitelist_title": "IPs de la lista blanca",
"edt_conf_net_ip_itemtitle": "IP",
- "edt_conf_net_localAdminAuth_expl": "Cuando está habilitado, el acceso de administración desde tu red local necesita una contraseña.",
- "edt_conf_net_localAdminAuth_title": "Autenticación de la API de administración local",
"edt_conf_net_localApiAuth_expl": "Cuando está habilitado, las conexiones de tu red doméstica también necesitan autenticarse contra Hyperion.",
"edt_conf_net_localApiAuth_title": "Autenticación de API local",
"edt_conf_net_restirctedInternetAccessAPI_expl": "Puedes restringir el acceso a la API a través de Internet a determinadas IP.",
"edt_conf_net_restirctedInternetAccessAPI_title": "Restringir a las IP",
+ "edt_conf_os_events_lockEnable_expl": "Escuchar eventos de bloqueo/desbloqueo",
+ "edt_conf_os_events_lockEnable_title": "Escuchar eventos de bloqueo",
+ "edt_conf_os_events_suspendEnable_expl": "Escuchar eventos de suspension/resumen del sistema operativo",
+ "edt_conf_os_events_suspendEnable_title": "Escuchar eventos de suspensión",
+ "edt_conf_os_events_suspendOnLockEnable_expl": "Suspender cuando la pantalla está bloqueada, de lo contrario pasa al modo inactivo",
+ "edt_conf_os_events_suspendOnLockEnable_title": "Suspender cuando esté bloqueado",
"edt_conf_pbs_heading_title": "Servidor de Buffers de Protocolo",
"edt_conf_pbs_timeout_expl": "Si no se reciben datos para el período dado, el componente se desactivará (suavemente).",
"edt_conf_pbs_timeout_title": "Tiempo de espera",
+ "edt_conf_sched_actions_header_expl": "Defina qué acción debe tener lugar en un momento determinado. La acción se programará diariamente.",
+ "edt_conf_sched_actions_header_item_title": "Acción",
+ "edt_conf_sched_actions_header_title": "Acciones",
"edt_conf_smooth_continuousOutput_expl": "Actualizar los LED incluso si no hay cambio de imagen.",
"edt_conf_smooth_continuousOutput_title": "Salida continua",
"edt_conf_smooth_decay_expl": "La velocidad de degradación. 1 es lineal, los valores mayores tienen un efecto más fuerte.",
@@ -488,6 +535,8 @@
"edt_conf_smooth_updateDelay_title": "Retardo de actualización",
"edt_conf_smooth_updateFrequency_expl": "La velocidad de salida a tu controlador led.",
"edt_conf_smooth_updateFrequency_title": "Frecuencia de actualización",
+ "edt_conf_time_event_expl": "Momento que desencadenará una acción",
+ "edt_conf_time_event_title": "Tiempo",
"edt_conf_v4l2_blueSignalThreshold_expl": "Oscurece los valores bajos de color azul (reconocidos como negros)",
"edt_conf_v4l2_blueSignalThreshold_title": "Umbral de señal azul",
"edt_conf_v4l2_cecDetection_expl": "Si está activado, la captura USB se desactivará temporalmente cuando el evento de espera de CEC se reciba desde el bus HDMI.",
@@ -560,6 +609,8 @@
"edt_conf_webc_port_title": "Puerto HTTP",
"edt_conf_webc_sslport_expl": "Puerto del servidor web HTTPS",
"edt_conf_webc_sslport_title": "Puerto HTTPS",
+ "edt_dev_auth_key_title": "Token de Autorización",
+ "edt_dev_auth_key_title_info": "Token de Autorización requerido para acceder al dispositivo",
"edt_dev_enum_sub_min_cool_adjust": "Min. Ajuste fresco",
"edt_dev_enum_sub_min_warm_adjust": "Min. Ajuste caliente",
"edt_dev_enum_subtract_minimum": "Restar el mínimo",
@@ -677,6 +728,7 @@
"edt_dev_spec_transistionTime_title": "Tiempo de transición",
"edt_dev_spec_uid_title": "UID",
"edt_dev_spec_universe_title": "Universo",
+ "edt_dev_spec_useAPIv2_title": "Usar API v2",
"edt_dev_spec_useEntertainmentAPI_title": "Usar la API de entretenimiento de Hue",
"edt_dev_spec_useOrbSmoothing_title": "Utilizar suavizado de orbe",
"edt_dev_spec_useRgbwProtocol_title": "Utilizar el protocolo RGBW",
@@ -748,6 +800,8 @@
"edt_eff_ledlist": "Lista Led",
"edt_eff_ledtest_header": "Prueba de Led",
"edt_eff_ledtest_header_desc": "Salida giratoria: Rojo, Azul, Verde, Blanco, Negro",
+ "edt_eff_ledtest_seq_header": "Test LED - Secuencia",
+ "edt_eff_ledtest_seq_header_desc": "Encender los LED en secuencia",
"edt_eff_length": "Longitud",
"edt_eff_lightclock_header": "Reloj de luz",
"edt_eff_lightclock_header_desc": "¡Un verdadero reloj como la luz! Ajustar los colores de las horas, los minutos, los segundos. También hay disponible un marcador opcional de 3/6/9/12 en punto. En caso de que el reloj esté equivocado, debes revisar el reloj de tu sistema.",
@@ -916,7 +970,9 @@
"general_speech_en": "Inglés",
"general_speech_es": "Español",
"general_speech_fr": "Francés",
+ "general_speech_he": "Hebreo",
"general_speech_hu": "Húngaro",
+ "general_speech_id": "Indonesio",
"general_speech_it": "Italiano",
"general_speech_ja": "Japonés",
"general_speech_nb": "Noruego (Bokmål)",
@@ -927,6 +983,7 @@
"general_speech_ru": "Ruso",
"general_speech_sv": "Sueco",
"general_speech_tr": "Turco",
+ "general_speech_uk": "Ucraniano",
"general_speech_vi": "Vietnamita",
"general_speech_zh-CN": "Chino (simplificado)",
"general_webui_title": "Hyperion - Configuración Web",
@@ -974,6 +1031,8 @@
"main_menu_dashboard_token": "Cuadro de mandos",
"main_menu_effect_conf_token": "Efectos",
"main_menu_effectsconfigurator_token": "Configurador de Efectos",
+ "main_menu_event_services_token": "Servicios de Evento",
+ "main_menu_events": "Servicios de Evento",
"main_menu_general_conf_token": "General",
"main_menu_grabber_conf_token": "Hardware de Captura",
"main_menu_input_selection_token": "Selección de entrada",
@@ -1034,7 +1093,6 @@
"support_label_fbtext": "Comparte nuestra página de Hyperion en Facebook y obten un aviso cuando se publiquen nuevas actualizaciones",
"support_label_forumtext": "Casos de ejemplo, discusiones, ayuda y mucho más",
"support_label_forumtitle": "Foro",
- "support_label_ggtext": "¡Haznos un círculo en Google+!",
"support_label_ghtext": "Visitanos en Github",
"support_label_igtext": "¡Visítanos en Instagram para ver las últimas imágenes de Hyperion!",
"support_label_intro": "Hyperion es un software libre sin fines de lucro. Un pequeño equipo está trabajando en ello y es por eso que necesitamos tu apoyo constante.",
@@ -1086,6 +1144,7 @@
"wiz_cololight_noprops": "Imposible obtener las propiedades del dispositivo - Define el conteo de LEDs de hardware manualmente",
"wiz_cololight_title": "Asistente Cololight",
"wiz_guideyou": "El $1 te guiará a través de los ajustes. Simplemente ¡presiona el botón!",
+ "wiz_hue_blinkblue": "Deja que se ilumine",
"wiz_hue_clientkey": "Llave de cliente:",
"wiz_hue_create_user": "Crear Usuario",
"wiz_hue_desc1": "1. Busca automáticamente un puente Hue, en caso de que no encuentre uno necesitas proporcionar la dirección IP y pulsar el botón de recarga a la derecha. Ahora necesitas una identificación de usuario, si no tienes una, crea una nueva.",
@@ -1118,6 +1177,13 @@
"wiz_identify_tip": "Identificar el dispositivo configurado iluminándolo",
"wiz_ids_disabled": "Desactivado",
"wiz_ids_entire": "Toda la imagen",
+ "wiz_layout": "Generar Trazado",
+ "wiz_layout_tip": "Generar un diseño para el dispositivo configurado",
+ "wiz_nanoleaf_failure_auth_token": "Pulse el botón de encendido/apagado de Nanoleaf antes de 30 segundos.",
+ "wiz_nanoleaf_failure_auth_token_t": "Tiempo de espera de generación de token de autorización de usuario",
+ "wiz_nanoleaf_press_onoff_button": "Pulsa el botón de encendido/apagado de su dispositivo Nanoleaf durante 5-7 segundos",
+ "wiz_nanoleaf_user_auth_intro": "El asistente ayuda a generar un token de autorización de usuario necesario para que Hyperion pueda acceder al dispositivo.",
+ "wiz_nanoleaf_user_auth_title": "Asistente para generar tokens de autorización",
"wiz_noLights": "¡No se encontró $1! Por favor, conecta las luces a la red o configúralas manualmente.",
"wiz_pos": "Posición/Estado",
"wiz_rgb_expl": "El punto de color cambia cada x segundos el color (rojo, verde), al mismo tiempo que tus leds cambian el color también. Responde las preguntas en la parte inferior para verificar/corregir tu orden de bytes.",
diff --git a/assets/webconfig/i18n/sv.json b/assets/webconfig/i18n/sv.json
index 2bf66b95..b5ad8f86 100644
--- a/assets/webconfig/i18n/sv.json
+++ b/assets/webconfig/i18n/sv.json
@@ -191,6 +191,7 @@
"conf_network_tok_diaTitle": "Ny nyckel skapad!",
"conf_network_tok_grantMsg": "En app begär åtkomst till Hyperion API via en nyckel. Vill du tillåta detta? Vänligen kontrollera informationen!",
"conf_network_tok_grantT": "App-nyckel begärd",
+ "conf_network_tok_idhead": "ID",
"conf_network_tok_intro": "Här kan du skapa eller ta bort nycklar för API-autentisering. Nyskapade nycklar visas en gång.",
"conf_network_tok_lastuse": "Senast använd",
"conf_network_tok_title": "Nyckelhantering",
@@ -496,15 +497,12 @@
"edt_conf_log_level_expl": "Beroende på nivå är färre eller fler meddelanden synliga.",
"edt_conf_log_level_title": "Loggnivå",
"edt_conf_net_apiAuth_expl": "Tvinga alla applikationer som använder Hyperion API att autentisera sig själva. Aktivera för högre säkerhet, eftersom varje ny ansökan nu måste bekräftas av dig en gång.",
- "edt_conf_net_apiAuth_title": "API-autentisering",
"edt_conf_net_heading_title": "Nätverk",
"edt_conf_net_internetAccessAPI_expl": "Tillåt åtkomst till Hyperion API/webbgränssnitt över Internet. Inaktivera åtkomst för ökad säkerhet.",
"edt_conf_net_internetAccessAPI_title": "Internet API-åtkomst",
"edt_conf_net_ipWhitelist_expl": "Istället för att tillåta åtkomst för alla anslutningar från internet kan du lägga till undantag för tillåtna IP-adresser här.",
"edt_conf_net_ipWhitelist_title": "Tillåtna IP-adresser",
"edt_conf_net_ip_itemtitle": "IP",
- "edt_conf_net_localAdminAuth_expl": "Om den är aktiverad måste administrationsåtkomst från hemnätverket autentiseras med ett lösenord.",
- "edt_conf_net_localAdminAuth_title": "Lokal administratörsautentisering",
"edt_conf_net_localApiAuth_expl": "Om den är aktiverad måste anslutningar från hemnätverket autentiseras med en nyckel.",
"edt_conf_net_localApiAuth_title": "Lokal API-autentisering",
"edt_conf_net_restirctedInternetAccessAPI_expl": "Begränsa åtkomsten till API:t över internet till specifika IP-adresser",
@@ -1096,7 +1094,6 @@
"support_label_fbtext": "Dela innehåll på Facebook och håll dig själv och andra uppdaterade",
"support_label_forumtext": "Diskussion och hjälp från samhället",
"support_label_forumtitle": "Forum",
- "support_label_ggtext": "Placera oss i dina cirklar på Google+",
"support_label_ghtext": "Besök oss på GitHub",
"support_label_igtext": "Ta en titt på Instagram!",
"support_label_intro": "Hyperion är ett gratis projekt med öppen källkod och ett litet team arbetar med vidareutvecklingen. Det är därför vi behöver DITT stöd för att fortsätta investera i bättre infrastruktur och vidareutveckling.",
diff --git a/assets/webconfig/js/content_huebridge.js b/assets/webconfig/js/content_huebridge.js
deleted file mode 100644
index 99da3c76..00000000
--- a/assets/webconfig/js/content_huebridge.js
+++ /dev/null
@@ -1,52 +0,0 @@
-$(document).ready( function() {
-
- $("#create_user").on("click", function() {
- var connectionRetries = 15;
- var data = {"devicetype":"hyperion#"+Date.now()};
- var UserInterval = setInterval(function(){
- $.ajax({
- type: "POST",
- url: 'http://'+$("#ip").val()+'/api',
- processData: false,
- timeout: 1000,
- contentType: 'application/json',
- data: JSON.stringify(data),
- success: function(r) {
- connectionRetries--;
- $("#connectionTime").html(connectionRetries);
- if(connectionRetries == 0) {
- abortConnection(UserInterval);
- }
- else
- {
- $("#abortConnection").hide();
- $('#pairmodal').modal('show');
- $("#ip_alert").hide();
- if (typeof r[0].error != 'undefined') {
- console.log("link not pressed");
- }
- if (typeof r[0].success != 'undefined') {
- $('#pairmodal').modal('hide');
- $('#user').val(r[0].success.username);
-
- $( "#hue_lights" ).empty();
- get_hue_lights();
- clearInterval(UserInterval);
- }
- }
- },
- error: function(XMLHttpRequest, textStatus, errorThrown) {
- $("#ip_alert").show();
- clearInterval(UserInterval);
- }
- });
- },1000);
-});
-
-function abortConnection(UserInterval){
- clearInterval(UserInterval);
- $("#abortConnection").show();
- $('#pairmodal').modal('hide');
-}
-
-});
diff --git a/assets/webconfig/js/content_index.js b/assets/webconfig/js/content_index.js
index e8ec28a8..6609b3bd 100644
--- a/assets/webconfig/js/content_index.js
+++ b/assets/webconfig/js/content_index.js
@@ -73,26 +73,30 @@ $(document).ready(function () {
//End language selection
$(window.hyperion).on("cmd-authorize-tokenRequest cmd-authorize-getPendingTokenRequests", function (event) {
- var val = event.response.info;
- if (Array.isArray(event.response.info)) {
- if (event.response.info.length == 0) {
- return
- }
- val = event.response.info[0]
- if (val.comment == '')
- $('#modal_dialog').modal('hide');
- }
- showInfoDialog("grantToken", $.i18n('conf_network_tok_grantT'), $.i18n('conf_network_tok_grantMsg') + 'App: ' + val.comment + ' Code: ' + val.id + ' ')
- $("#tok_grant_acc").off().on('click', function () {
- tokenList.push(val)
- // forward event, in case we need to rebuild the list now
- $(window.hyperion).trigger({ type: "build-token-list" });
- requestHandleTokenRequest(val.id, true)
- });
- $("#tok_deny_acc").off().on('click', function () {
- requestHandleTokenRequest(val.id, false)
- });
+ if (event.response && event.response.info !== undefined) {
+ var val = event.response.info;
+
+ if (Array.isArray(event.response.info)) {
+ if (event.response.info.length == 0) {
+ return
+ }
+ val = event.response.info[0]
+ if (val.comment == '')
+ $('#modal_dialog').modal('hide');
+ }
+
+ showInfoDialog("grantToken", $.i18n('conf_network_tok_grantT'), $.i18n('conf_network_tok_grantMsg') + 'App: ' + val.comment + ' Code: ' + val.id + ' ')
+ $("#tok_grant_acc").off().on('click', function () {
+ tokenList.push(val)
+ // forward event, in case we need to rebuild the list now
+ $(window.hyperion).trigger({ type: "build-token-list" });
+ requestHandleTokenRequest(val.id, true)
+ });
+ $("#tok_deny_acc").off().on('click', function () {
+ requestHandleTokenRequest(val.id, false)
+ });
+ }
});
$(window.hyperion).one("cmd-authorize-getTokenList", function (event) {
@@ -186,21 +190,12 @@ $(document).ready(function () {
}
});
- $(window.hyperion).on("cmd-authorize-adminRequired", function (event) {
- //Check if a admin login is required.
- //If yes: check if default pw is set. If no: go ahead to get server config and render page
- if (event.response.info.adminRequired === true)
- requestRequiresDefaultPasswortChange();
- else
- requestServerConfigSchema();
- });
-
$(window.hyperion).on("error", function (event) {
//If we are getting an error "No Authorization" back with a set loginToken we will forward to new Login (Token is expired.
//e.g.: hyperiond was started new in the meantime)
if (event.reason == "No Authorization" && getStorage("loginToken")) {
removeStorage("loginToken");
- requestRequiresAdminAuth();
+ requestRequiresDefaultPasswortChange();
}
else if (event.reason == "Selected Hyperion instance isn't running") {
//Switch to default instance
@@ -211,7 +206,7 @@ $(document).ready(function () {
});
$(window.hyperion).on("open", function (event) {
- requestRequiresAdminAuth();
+ requestRequiresDefaultPasswortChange();
});
$(window.hyperion).on("ready", function (event) {
diff --git a/assets/webconfig/js/content_leds.js b/assets/webconfig/js/content_leds.js
index 0432524a..a0f216a0 100755
--- a/assets/webconfig/js/content_leds.js
+++ b/assets/webconfig/js/content_leds.js
@@ -1053,28 +1053,28 @@ $(document).ready(function () {
});
$("#leddevices").off().on("change", function () {
- var generalOptions = window.serverSchema.properties.device;
+ const generalOptions = window.serverSchema.properties.device;
- var ledType = $(this).val();
- var specificOptions = window.serverSchema.properties.alldevices[ledType];
+ const ledType = $(this).val();
+ const specificOptions = window.serverSchema.properties.alldevices[ledType];
conf_editor = createJsonEditor('editor_container_leddevice', {
specificOptions: specificOptions,
generalOptions: generalOptions,
});
- var values_general = {};
- var values_specific = {};
- var isCurrentDevice = (window.serverConfig.device.type == ledType);
+ let values_general = {};
+ let values_specific = {};
+ const isCurrentDevice = (window.serverConfig.device.type == ledType);
- for (var key in window.serverConfig.device) {
+ for (const 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);
if (isCurrentDevice) {
- var specificOptions_val = conf_editor.getEditor("root.specificOptions").getValue();
- for (var key in specificOptions_val) {
+ const specificOptions_val = conf_editor.getEditor("root.specificOptions").getValue();
+ for (const 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);
@@ -1086,45 +1086,12 @@ $(document).ready(function () {
conf_editor.validate().length || window.readOnlyMode ? $('#btn_submit_controller').prop('disabled', true) : $('#btn_submit_controller').prop('disabled', false);
// LED controller specific wizards
- $('#btn_wiz_holder').html("");
- $('#btn_led_device_wiz').off();
-
- if (ledType == "philipshue") {
- var ledWizardType = ledType;
- var data = { type: ledWizardType };
- var hue_title = 'wiz_hue_title';
- changeWizard(data, hue_title, startWizardPhilipsHue);
- }
- else if (ledType == "nanoleaf") {
- var ledWizardType = ledType;
- var data = { type: ledWizardType };
- var nanoleaf_user_auth_title = 'wiz_nanoleaf_user_auth_title';
- changeWizard(data, nanoleaf_user_auth_title, startWizardNanoleafUserAuth);
- $('#btn_wiz_holder').hide();
- }
- else if (ledType == "atmoorb") {
- var ledWizardType = (this.checked) ? "atmoorb" : ledType;
- var data = { type: ledWizardType };
- var atmoorb_title = 'wiz_atmoorb_title';
- changeWizard(data, atmoorb_title, startWizardAtmoOrb);
- }
- else if (ledType == "yeelight") {
- var ledWizardType = (this.checked) ? "yeelight" : ledType;
- var data = { type: ledWizardType };
- var yeelight_title = 'wiz_yeelight_title';
- changeWizard(data, yeelight_title, startWizardYeelight);
- }
-
- function changeWizard(data, hint, fn) {
- $('#btn_wiz_holder').html("")
- createHint("wizard", $.i18n(hint), "btn_wiz_holder", "btn_led_device_wiz");
- $('#btn_led_device_wiz').off().on('click', data, fn);
- }
+ createLedDeviceWizards(ledType);
conf_editor.on('ready', function () {
- var hwLedCountDefault = 1;
- var colorOrderDefault = "rgb";
- var filter = {};
+ let hwLedCountDefault = 1;
+ let colorOrderDefault = "rgb";
+ let filter = {};
$('#btn_layout_controller').hide();
$('#btn_test_controller').hide();
@@ -1172,58 +1139,55 @@ $(document).ready(function () {
.catch(error => {
showNotification('danger', "Device discovery for " + ledType + " failed with error:" + error);
});
-
- hwLedCountDefault = 1;
- colorOrderDefault = "rgb";
break;
- case "philipshue":
+ case "philipshue": {
disableAutoResolvedGeneralOptions();
- var lights = conf_editor.getEditor("root.specificOptions.lightIds").getValue();
+ const lights = conf_editor.getEditor("root.specificOptions.lightIds").getValue();
hwLedCountDefault = lights.length;
- colorOrderDefault = "rgb";
+ }
break;
- case "yeelight":
+ case "yeelight": {
disableAutoResolvedGeneralOptions();
- var lights = conf_editor.getEditor("root.specificOptions.lights").getValue();
+ const lights = conf_editor.getEditor("root.specificOptions.lights").getValue();
hwLedCountDefault = lights.length;
- colorOrderDefault = "rgb";
+ }
break;
- case "atmoorb":
+ case "atmoorb": {
disableAutoResolvedGeneralOptions();
- var configruedOrbIds = conf_editor.getEditor("root.specificOptions.orbIds").getValue().trim();
+ const 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;
- case "razer":
+ case "razer": {
disableAutoResolvedGeneralOptions();
- hwLedCountDefault = 1;
colorOrderDefault = "bgr";
- var subType = conf_editor.getEditor("root.specificOptions.subType").getValue();
- let params = { subType: subType };
+ const subType = conf_editor.getEditor("root.specificOptions.subType").getValue();
+ const params = { subType };
getProperties_device(ledType, subType, params);
+ }
break;
default:
}
if (ledType !== window.serverConfig.device.type) {
- var hwLedCount = conf_editor.getEditor("root.generalOptions.hardwareLedCount");
+ let hwLedCount = conf_editor.getEditor("root.generalOptions.hardwareLedCount");
if (hwLedCount) {
hwLedCount.setValue(hwLedCountDefault);
}
- var colorOrder = conf_editor.getEditor("root.generalOptions.colorOrder");
+ let colorOrder = conf_editor.getEditor("root.generalOptions.colorOrder");
if (colorOrder) {
colorOrder.setValue(colorOrderDefault);
}
@@ -1232,8 +1196,8 @@ $(document).ready(function () {
conf_editor.on('change', function () {
// //Check, if device can be identified/tested and/or saved
- var canIdentify = false;
- var canSave = false;
+ let canIdentify = false;
+ let canSave = false;
switch (ledType) {
@@ -1245,11 +1209,12 @@ $(document).ready(function () {
case "udpartnet":
case "udpddp":
case "udph801":
- case "udpraw":
- var host = conf_editor.getEditor("root.specificOptions.host").getValue();
+ case "udpraw": {
+ const host = conf_editor.getEditor("root.specificOptions.host").getValue();
if (host !== "") {
canSave = true;
}
+ }
break;
case "adalight":
@@ -1257,50 +1222,63 @@ $(document).ready(function () {
case "karate":
case "dmx":
case "sedu":
- case "tpm2":
- var rate = conf_editor.getEditor("root.specificOptions.rate").getValue();
+ case "tpm2": {
+ let currentDeviceType = window.serverConfig.device.type;
+ if ($.inArray(currentDeviceType, devSerial) === -1) {
+ canIdentify = true;
+ } else {
+ let output = conf_editor.getEditor("root.specificOptions.output").getValue();
+ if (window.serverConfig.device.output !== output) {
+ canIdentify = true;
+ }
+ }
+
+ const rate = conf_editor.getEditor("root.specificOptions.rate").getValue();
if (rate > 0) {
canSave = true;
}
+ }
break;
- case "philipshue":
- var host = conf_editor.getEditor("root.specificOptions.host").getValue();
- var username = conf_editor.getEditor("root.specificOptions.username").getValue();
+ case "philipshue": {
+ const host = conf_editor.getEditor("root.specificOptions.host").getValue();
+ const username = conf_editor.getEditor("root.specificOptions.username").getValue();
if (host !== "" && username != "") {
- var useEntertainmentAPI = conf_editor.getEditor("root.specificOptions.useEntertainmentAPI").getValue();
- var clientkey = conf_editor.getEditor("root.specificOptions.clientkey").getValue();
+ const useEntertainmentAPI = conf_editor.getEditor("root.specificOptions.useEntertainmentAPI").getValue();
+ const clientkey = conf_editor.getEditor("root.specificOptions.clientkey").getValue();
if (!useEntertainmentAPI || clientkey !== "") {
canSave = true;
}
}
+ }
break;
case "wled":
- case "cololight":
- var hostList = conf_editor.getEditor("root.specificOptions.hostList").getValue();
+ case "cololight": {
+ const hostList = conf_editor.getEditor("root.specificOptions.hostList").getValue();
if (hostList !== "SELECT") {
- var host = conf_editor.getEditor("root.specificOptions.host").getValue();
+ const 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();
+ case "nanoleaf": {
+ const 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();
+ const host = conf_editor.getEditor("root.specificOptions.host").getValue();
+ const token = conf_editor.getEditor("root.specificOptions.token").getValue();
if (host !== "" && token !== "") {
canIdentify = true;
canSave = true;
}
}
+ }
break;
default:
- canIdentify = false;
canSave = true;
}
@@ -1428,30 +1406,27 @@ $(document).ready(function () {
}
});
+
conf_editor.watch('root.specificOptions.output', () => {
- var output = conf_editor.getEditor("root.specificOptions.output").getValue();
+ const output = conf_editor.getEditor("root.specificOptions.output").getValue();
if (output === "NONE" || output === "SELECT" || output === "") {
$('#btn_submit_controller').prop('disabled', true);
$('#btn_test_controller').prop('disabled', true);
$('#btn_test_controller').hide();
-
conf_editor.getEditor("root.generalOptions.hardwareLedCount").setValue(1);
showAllDeviceInputOptions("output", false);
}
else {
showAllDeviceInputOptions("output", true);
let params = {};
- var canIdentify = false;
switch (ledType) {
- case "adalight":
- canIdentify = true;
- break;
case "atmo":
case "karate":
params = { serialPort: output };
getProperties_device(ledType, output, params);
break;
+ case "adalight":
case "dmx":
case "sedu":
case "tpm2":
@@ -1470,8 +1445,8 @@ $(document).ready(function () {
}
if ($.inArray(ledType, devSerial) != -1) {
- var rateList = conf_editor.getEditor("root.specificOptions.rateList").getValue();
- var showRate = false;
+ const rateList = conf_editor.getEditor("root.specificOptions.rateList").getValue();
+ let showRate = false;
if (rateList == "CUSTOM") {
showRate = true;
}
@@ -1479,13 +1454,6 @@ $(document).ready(function () {
}
if (!conf_editor.validate().length) {
- if (canIdentify) {
- $("#btn_test_controller").show();
- $('#btn_test_controller').prop('disabled', false);
- } else {
- $('#btn_test_controller').hide();
- $('#btn_test_controller').prop('disabled', true);
- }
if (!window.readOnlyMode) {
$('#btn_submit_controller').prop('disabled', false);
}
@@ -1537,12 +1505,12 @@ $(document).ready(function () {
});
conf_editor.watch('root.specificOptions.rateList', () => {
- var specOptPath = 'root.specificOptions.';
- var rateList = conf_editor.getEditor("root.specificOptions.rateList");
- if (rateList) {
- var val = rateList.getValue();
- var rate = conf_editor.getEditor("root.specificOptions.rate");
+ const specOptPath = 'root.specificOptions.';
+ const rateList = conf_editor.getEditor("root.specificOptions.rateList");
+ let rate = conf_editor.getEditor("root.specificOptions.rate");
+ if (rateList) {
+ const val = rateList.getValue();
switch (val) {
case 'CUSTOM':
case '':
@@ -2543,6 +2511,10 @@ function nanoleafGeneratelayout(panelLayout, panelOrderTopDown, panelOrderLeftRi
18: { name: "LightLinesSingleZone", led: true, sideLengthX: 77, sideLengthY: 77 },
19: { name: "ControllerCap", led: false, sideLengthX: 11, sideLengthY: 11 },
20: { name: "PowerConnector", led: false, sideLengthX: 11, sideLengthY: 11 },
+ 29: { name: "4DLightstrip", led: true, sideLengthX: 50, sideLengthY: 50 },
+ 30: { name: "Skylight Panel", led: true, sideLengthX: 180, sideLengthY: 180 },
+ 31: { name: "SkylightControllerPrimary", led: true, sideLengthX: 180, sideLengthY: 180 },
+ 32: { name: "SkylightControllerPassive", led: true, sideLengthX: 180, sideLengthY: 180 },
999: { name: "Unknown", led: true, sideLengthX: 100, sideLengthY: 100 }
};
diff --git a/assets/webconfig/js/content_logging.js b/assets/webconfig/js/content_logging.js
index 7a9c791d..a8d5fa36 100644
--- a/assets/webconfig/js/content_logging.js
+++ b/assets/webconfig/js/content_logging.js
@@ -3,10 +3,13 @@ var createdCont = false;
var isScroll = true;
performTranslation();
-requestLoggingStop();
$(document).ready(function () {
+ window.addEventListener('hashchange', function(event) {
+ requestLoggingStop();
+ });
+
requestLoggingStart();
$('#conf_cont').append(createOptPanel('fa-reorder', $.i18n("edt_conf_log_heading_title"), 'editor_container', 'btn_submit'));
@@ -178,9 +181,9 @@ $(document).ready(function () {
if (!window.loggingHandlerInstalled) {
window.loggingHandlerInstalled = true;
- $(window.hyperion).on("cmd-logging-update", function (event) {
+ $(window.hyperion).on("cmd-logmsg-update", function (event) {
- var messages = (event.response.result.messages);
+ var messages = (event.response.data.messages);
if (messages.length != 0) {
if (!createdCont) {
diff --git a/assets/webconfig/js/content_network.js b/assets/webconfig/js/content_network.js
index 26fec2c8..9f9f68c3 100644
--- a/assets/webconfig/js/content_network.js
+++ b/assets/webconfig/js/content_network.js
@@ -213,13 +213,13 @@ $(document).ready(function () {
for (var key in tokenList) {
var lastUse = (tokenList[key].last_use) ? tokenList[key].last_use : "-";
var btn = '' + $.i18n('general_btn_delete') + ' ';
- $('.tktbody').append(createTableRow([tokenList[key].comment, lastUse, btn], false, true));
+ $('.tktbody').append(createTableRow([tokenList[key].id, tokenList[key].comment, lastUse, btn], false, true));
$('#tok' + tokenList[key].id).off().on('click', handleDeleteToken);
}
}
createTable('tkthead', 'tktbody', 'tktable');
- $('.tkthead').html(createTableRow([$.i18n('conf_network_tok_cidhead'), $.i18n('conf_network_tok_lastuse'), $.i18n('general_btn_delete')], true, true));
+ $('.tkthead').html(createTableRow([$.i18n('conf_network_tok_idhead'), $.i18n('conf_network_tok_cidhead'), $.i18n('conf_network_tok_lastuse'), $.i18n('general_btn_delete')], true, true));
buildTokenList();
function handleDeleteToken(e) {
diff --git a/assets/webconfig/js/hyperion.js b/assets/webconfig/js/hyperion.js
index 7bd59c65..8153e827 100644
--- a/assets/webconfig/js/hyperion.js
+++ b/assets/webconfig/js/hyperion.js
@@ -177,6 +177,7 @@ function sendToHyperion(command, subcommand, msg)
else
msg = "";
+ window.wsTan = Math.floor(Math.random() * 1000)
window.websocket.send('{"command":"'+command+'", "tan":'+window.wsTan+subcommand+msg+'}');
}
@@ -187,7 +188,7 @@ function sendToHyperion(command, subcommand, msg)
// data: The json data as Object
// tan: The optional tan, default 1. If the tan is -1, we skip global response error handling
// Returns data of response or false if timeout
-async function sendAsyncToHyperion (command, subcommand, data, tan = 1) {
+async function sendAsyncToHyperion (command, subcommand, data, tan = Math.floor(Math.random() * 1000) ) {
let obj = { command, tan }
if (subcommand) {Object.assign(obj, {subcommand})}
if (data) { Object.assign(obj, data) }
@@ -486,38 +487,38 @@ async function requestLedDeviceDiscovery(type, params)
{
let data = { ledDeviceType: type, params: params };
- return sendAsyncToHyperion("leddevice", "discover", data, Math.floor(Math.random() * 1000) );
+ return sendAsyncToHyperion("leddevice", "discover", data);
}
async function requestLedDeviceProperties(type, params)
{
let data = { ledDeviceType: type, params: params };
- return sendAsyncToHyperion("leddevice", "getProperties", data, Math.floor(Math.random() * 1000));
+ return sendAsyncToHyperion("leddevice", "getProperties", data);
}
function requestLedDeviceIdentification(type, params)
{
let data = { ledDeviceType: type, params: params };
- return sendAsyncToHyperion("leddevice", "identify", data, Math.floor(Math.random() * 1000));
+ return sendAsyncToHyperion("leddevice", "identify", data);
}
async function requestLedDeviceAddAuthorization(type, params) {
let data = { ledDeviceType: type, params: params };
- return sendAsyncToHyperion("leddevice", "addAuthorization", data, Math.floor(Math.random() * 1000));
+ return sendAsyncToHyperion("leddevice", "addAuthorization", data);
}
async function requestInputSourcesDiscovery(type, params) {
let data = { sourceType: type, params: params };
- return sendAsyncToHyperion("inputsource", "discover", data, Math.floor(Math.random() * 1000));
+ return sendAsyncToHyperion("inputsource", "discover", data);
}
async function requestServiceDiscovery(type, params) {
let data = { serviceType: type, params: params };
- return sendAsyncToHyperion("service", "discover", data, Math.floor(Math.random() * 1000));
+ return sendAsyncToHyperion("service", "discover", data);
}
diff --git a/assets/webconfig/js/languages.js b/assets/webconfig/js/languages.js
index bbc5078a..28f9e8c2 100644
--- a/assets/webconfig/js/languages.js
+++ b/assets/webconfig/js/languages.js
@@ -1,6 +1,6 @@
var storedLang;
-var availLang = ['ca', 'cs', 'da', 'de', 'el', 'en', 'es', 'fr', 'he', 'hu', 'id', 'it', 'ja', 'nl', 'nb', 'pl', 'pt', 'ro', 'ru', 'sv', 'tr', 'uk', 'vi', 'zh-CN'];
-var availLangText = ['Català', 'Čeština', 'Dansk', 'Deutsch', 'Ελληνική', 'English', 'Español', 'Français', 'עִברִית' ,'Magyar', 'Indonesia', 'Italiano', '日本語', 'Nederlands', 'Norsk Bokmål', 'Polski', 'Português', 'Română', 'русский', 'Svenska', 'Türkçe', 'Українська', 'Tiếng Việt', '汉语'];
+var availLang = ['bg', 'ca', 'cs', 'da', 'de', 'el', 'en', 'es', 'fr', 'he', 'hu', 'id', 'it', 'ja', 'nl', 'nb', 'pl', 'pt', 'ro', 'ru', 'sv', 'tr', 'uk', 'vi', 'zh-CN'];
+var availLangText = ['Български', 'Català', 'Čeština', 'Dansk', 'Deutsch', 'Ελληνική', 'English', 'Español', 'Français', 'עִברִית' ,'Magyar', 'Indonesia', 'Italiano', '日本語', 'Nederlands', 'Norsk Bokmål', 'Polski', 'Português', 'Română', 'русский', 'Svenska', 'Türkçe', 'Українська', 'Tiếng Việt', '汉语'];
//$.i18n.debug = true;
diff --git a/assets/webconfig/js/ledsim.js b/assets/webconfig/js/ledsim.js
index 32a8898b..af633ba7 100644
--- a/assets/webconfig/js/ledsim.js
+++ b/assets/webconfig/js/ledsim.js
@@ -261,7 +261,7 @@ $(document).ready(function () {
$("body").get(0).style.setProperty("--background-var", "none");
}
else {
- printLedsToCanvas(event.response.result.leds)
+ printLedsToCanvas(event.response.data.leds)
$("body").get(0).style.setProperty("--background-var", "url(" + ($('#leds_preview_canv')[0]).toDataURL("image/jpg") + ") no-repeat top left");
}
});
@@ -275,7 +275,7 @@ $(document).ready(function () {
}
}
else {
- var imageData = (event.response.result.image);
+ var imageData = (event.response.data.image);
var image = new Image();
image.onload = function () {
diff --git a/assets/webconfig/js/ui_utils.js b/assets/webconfig/js/ui_utils.js
index 0061dde9..4bfeb85e 100644
--- a/assets/webconfig/js/ui_utils.js
+++ b/assets/webconfig/js/ui_utils.js
@@ -319,9 +319,9 @@ function showInfoDialog(type, header, message) {
});
$(document).on('click', '[data-dismiss-modal]', function () {
- var target = $(this).attr('data-dismiss-modal');
- $.find(target).modal('hide');
- });
+ var target = $(this).data('dismiss-modal');
+ $($.find(target)).modal('hide');
+});
}
function createHintH(type, text, container) {
@@ -1393,3 +1393,32 @@ function isValidHostnameOrIP(value) {
return (isValidHostnameOrIP4(value) || isValidIPv6(value) || isValidServicename(value));
}
+const loadedScripts = [];
+
+function isScriptLoaded(src) {
+ return loadedScripts.indexOf(src) > -1;
+}
+
+function loadScript(src, callback, ...params) {
+ if (isScriptLoaded(src)) {
+ debugMessage('Script ' + src + ' already loaded');
+ if (callback && typeof callback === 'function') {
+ callback( ...params);
+ }
+ return;
+ }
+
+ const script = document.createElement('script');
+ script.src = src;
+
+ script.onload = function () {
+ debugMessage('Script ' + src + ' loaded successfully');
+ loadedScripts.push(src);
+
+ if (callback && typeof callback === 'function') {
+ callback(...params);
+ }
+ };
+
+ document.head.appendChild(script);
+}
diff --git a/assets/webconfig/js/wizard.js b/assets/webconfig/js/wizard.js
index 70584ddb..2524924f 100755
--- a/assets/webconfig/js/wizard.js
+++ b/assets/webconfig/js/wizard.js
@@ -3,2274 +3,71 @@ $(window.hyperion).one("ready", function (event) {
if (getStorage("wizardactive") === 'true') {
requestPriorityClear();
setStorage("wizardactive", false);
- if (getStorage("kodiAddress") != null) {
- kodiAddress = getStorage("kodiAddress");
-
- if (getStorage("kodiPort") != null) {
- kodiPort = getStorage("kodiPort");
- }
- sendToKodi("stop");
- }
}
});
+$("#btn_wizard_colorcalibration").click(async function () {
+ const { colorCalibrationKodiWizard } = await import('./wizards/colorCalibrationKodiWizard.js');
+ colorCalibrationKodiWizard.start();
+});
+
+$('#btn_wizard_byteorder').on('click', async () => {
+ const { rgbByteOrderWizard } = await import('./wizards/rgbByteOrderWizard.js');
+ rgbByteOrderWizard.start();
+});
+
function resetWizard(reload) {
$("#wizard_modal").modal('hide');
- clearInterval(wIntveralId);
requestPriorityClear();
setStorage("wizardactive", false);
$('#wizp1').toggle(true);
$('#wizp2').toggle(false);
$('#wizp3').toggle(false);
- //cc
- if (withKodi)
- sendToKodi("stop");
- step = 0;
- if (!reload) location.reload();
-}
-
-//rgb byte order wizard
-var wIntveralId;
-var new_rgb_order;
-
-function changeColor() {
- var color = $("#wiz_canv_color").css('background-color');
-
- if (color == 'rgb(255, 0, 0)') {
- $("#wiz_canv_color").css('background-color', 'rgb(0, 255, 0)');
- requestSetColor('0', '255', '0');
- }
- else {
- $("#wiz_canv_color").css('background-color', 'rgb(255, 0, 0)');
- requestSetColor('255', '0', '0');
+ if (!reload) {
+ location.reload();
}
}
-function startWizardRGB() {
- //create html
- $('#wiz_header').html(' ' + $.i18n('wiz_rgb_title'));
- $('#wizp1_body').html('
' + $.i18n('wiz_rgb_title') + ' ' + $.i18n('wiz_rgb_intro1') + '
' + $.i18n('wiz_rgb_intro2') + '
');
- $('#wizp1_footer').html(' ' + $.i18n('general_btn_continue') + ' ' + $.i18n('general_btn_cancel') + ' ');
- $('#wizp2_body').html('' + $.i18n('wiz_rgb_expl') + '
');
- $('#wizp2_body').append('');
- $('#wizp2_body').append('' + $.i18n('wiz_rgb_q') + ' ');
- $('#wizp2_body').append('' + $.i18n('wiz_rgb_qrend') + ' ' + $.i18n('wiz_rgb_qgend') + '
');
- $('#wizp2_footer').html(' ' + $.i18n('general_btn_save') + ' ' + $.i18n('general_btn_ok') + ' ' + $.i18n('general_btn_cancel') + ' ');
+function createLedDeviceWizards(ledType) {
- if (getStorage("darkMode") == "on")
- $('#wizard_logo').attr("src", 'img/hyperion/logo_negativ.png');
+ let data = {};
+ let title;
- //open modal
- $("#wizard_modal").modal({
- backdrop: "static",
- keyboard: false,
- show: true
- });
+ $('#btn_wiz_holder').html("");
+ $('#btn_led_device_wiz').off();
+ if (ledType == "philipshue") {
+ $('#btn_wiz_holder').show();
+ data = { ledType };
+ title = 'wiz_hue_title';
+ }
+ else if (ledType == "nanoleaf") {
+ $('#btn_wiz_holder').hide();
+ data = { ledType };
+ title = 'wiz_nanoleaf_user_auth_title';
+ }
+ else if (ledType == "atmoorb") {
+ $('#btn_wiz_holder').show();
+ data = { ledType };
+ title = 'wiz_atmoorb_title';
+ }
+ else if (ledType == "yeelight") {
+ $('#btn_wiz_holder').show();
+ data = { ledType };
+ title = 'wiz_yeelight_title';
+ }
- //listen for continue
- $('#btn_wiz_cont').off().on('click', function () {
- beginWizardRGB();
- $('#wizp1').toggle(false);
- $('#wizp2').toggle(true);
+ if (Object.keys(data).length !== 0) {
+ startLedDeviceWizard(data, title, ledType + "Wizard");
+ }
+}
+
+function startLedDeviceWizard(data, hint, wizardName) {
+ $('#btn_wiz_holder').html("")
+ createHint("wizard", $.i18n(hint), "btn_wiz_holder", "btn_led_device_wiz");
+ $('#btn_led_device_wiz').off();
+ $('#btn_led_device_wiz').on('click', async (e) => {
+ const { [wizardName]: winzardObject } = await import('./wizards/LedDevice_' + data.ledType + '.js');
+ winzardObject.start(e);
});
}
-function beginWizardRGB() {
- $("#wiz_switchtime_select").off().on('change', function () {
- clearInterval(wIntveralId);
- var time = $("#wiz_switchtime_select").val();
- wIntveralId = setInterval(function () { changeColor(); }, time * 1000);
- });
-
- $('.wselect').on("change", function () {
- var rgb_order = window.serverConfig.device.colorOrder.split("");
- var redS = $("#wiz_r_select").val();
- var greenS = $("#wiz_g_select").val();
- var blueS = rgb_order.toString().replace(/,/g, "").replace(redS, "").replace(greenS, "");
-
- for (var i = 0; i < rgb_order.length; i++) {
- if (redS == rgb_order[i])
- $('#wiz_g_select option[value=' + rgb_order[i] + ']').prop('disabled', true);
- else
- $('#wiz_g_select option[value=' + rgb_order[i] + ']').prop('disabled', false);
- if (greenS == rgb_order[i])
- $('#wiz_r_select option[value=' + rgb_order[i] + ']').prop('disabled', true);
- else
- $('#wiz_r_select option[value=' + rgb_order[i] + ']').prop('disabled', false);
- }
-
- if (redS != 'null' && greenS != 'null') {
- $('#btn_wiz_save').prop('disabled', false);
-
- for (var i = 0; i < rgb_order.length; i++) {
- if (rgb_order[i] == "r")
- rgb_order[i] = redS;
- else if (rgb_order[i] == "g")
- rgb_order[i] = greenS;
- else
- rgb_order[i] = blueS;
- }
-
- rgb_order = rgb_order.toString().replace(/,/g, "");
-
- if (redS == "r" && greenS == "g") {
- $('#btn_wiz_save').toggle(false);
- $('#btn_wiz_checkok').toggle(true);
-
- window.readOnlyMode ? $('#btn_wiz_checkok').prop('disabled', true) : $('#btn_wiz_checkok').prop('disabled', false);
- }
- else {
- $('#btn_wiz_save').toggle(true);
- window.readOnlyMode ? $('#btn_wiz_save').prop('disabled', true) : $('#btn_wiz_save').prop('disabled', false);
-
- $('#btn_wiz_checkok').toggle(false);
- }
- new_rgb_order = rgb_order;
- }
- else
- $('#btn_wiz_save').prop('disabled', true);
- });
-
- $("#wiz_switchtime_select").append(createSelOpt('5', '5'), createSelOpt('10', '10'), createSelOpt('15', '15'), createSelOpt('30', '30'));
- $("#wiz_switchtime_select").trigger('change');
-
- $("#wiz_r_select").append(createSelOpt("null", ""), createSelOpt('r', $.i18n('general_col_red')), createSelOpt('g', $.i18n('general_col_green')), createSelOpt('b', $.i18n('general_col_blue')));
- $("#wiz_g_select").html($("#wiz_r_select").html());
- $("#wiz_r_select").trigger('change');
-
- requestSetColor('255', '0', '0');
- setTimeout(requestSetSource, 100, 'auto');
- setStorage("wizardactive", true);
-
- $('#btn_wiz_abort').off().on('click', function () { resetWizard(true); });
-
- $('#btn_wiz_checkok').off().on('click', function () {
- showInfoDialog('success', "", $.i18n('infoDialog_wizrgb_text'));
- resetWizard();
- });
-
- $('#btn_wiz_save').off().on('click', function () {
- resetWizard();
- window.serverConfig.device.colorOrder = new_rgb_order;
- requestWriteConfig({ "device": window.serverConfig.device });
- });
-}
-
-$('#btn_wizard_byteorder').off().on('click', startWizardRGB);
-
-//color calibration wizard
-
-const defaultKodiPort = 9090;
-
-var kodiAddress = document.location.hostname;
-var kodiPort = defaultKodiPort;
-
-var kodiUrl = new URL("ws://" + kodiAddress);
-kodiUrl.port = kodiPort;
-kodiUrl.pathname = "/jsonrpc/websocket";
-
-var wiz_editor;
-var colorLength;
-var cobj;
-var step = 0;
-var withKodi = false;
-var profile = 0;
-var websAddress;
-var imgAddress;
-var vidAddress = "https://sourceforge.net/projects/hyperion-project/files/resources/vid/";
-var picnr = 0;
-var availVideos = ["Sweet_Cocoon", "Caminandes_2_GranDillama", "Caminandes_3_Llamigos"];
-
-if (getStorage("kodiAddress") != null) {
-
- kodiAddress = getStorage("kodiAddress");
- kodiUrl.host = kodiAddress;
-}
-
-if (getStorage("kodiPort") != null) {
- kodiPort = getStorage("kodiPort");
- kodiUrl.port = kodiPort;
-}
-
-function switchPicture(pictures) {
- if (typeof pictures[picnr] === 'undefined')
- picnr = 0;
-
- sendToKodi('playP', pictures[picnr]);
- picnr++;
-}
-
-function sendToKodi(type, content, cb) {
- var command;
-
- switch (type) {
- case "msg":
- command = { "jsonrpc": "2.0", "method": "GUI.ShowNotification", "params": { "title": $.i18n('wiz_cc_title'), "message": content, "image": "info", "displaytime": 5000 }, "id": "1" };
- break;
- case "stop":
- command = { "jsonrpc": "2.0", "method": "Player.Stop", "params": { "playerid": 2 }, "id": "1" };
- break;
- case "playP":
- content = imgAddress + content + '.png';
- command = { "jsonrpc": "2.0", "method": "Player.Open", "params": { "item": { "file": content } }, "id": "1" };
- break;
- case "playV":
- content = vidAddress + content;
- command = { "jsonrpc": "2.0", "method": "Player.Open", "params": { "item": { "file": content } }, "id": "1" };
- break;
- case "rotate":
- command = { "jsonrpc": "2.0", "method": "Player.Rotate", "params": { "playerid": 2 }, "id": "1" };
- break;
- default:
- if (cb != undefined) {
- cb("error");
- }
- }
-
- if ("WebSocket" in window) {
-
- if (kodiUrl.port === '') {
- kodiUrl.port = defaultKodiPort;
- }
- var ws = new WebSocket(kodiUrl);
-
- ws.onopen = function () {
- ws.send(JSON.stringify(command));
- };
-
- ws.onmessage = function (evt) {
- var response = JSON.parse(evt.data);
- if (response.method === "System.OnQuit") {
- ws.close();
- } else {
- if (cb != undefined) {
- if (response.result != undefined) {
- if (response.result === "OK") {
- cb("success");
- ws.close();
- } else {
- cb("error");
- ws.close();
- }
- }
- }
- }
- };
-
- ws.onerror = function (evt) {
- if (cb != undefined) {
- cb("error");
- ws.close();
- }
- };
-
- ws.onclose = function (evt) {
- };
-
- }
- else {
- console.log("Kodi Access: WebSocket NOT supported by this browser");
- cb("error");
- }
-}
-
-function performAction() {
- var h;
-
- if (step == 1) {
- $('#wiz_cc_desc').html($.i18n('wiz_cc_chooseid'));
- updateWEditor(["id"]);
- $('#btn_wiz_back').prop("disabled", true);
- }
- else
- $('#btn_wiz_back').prop("disabled", false);
-
- if (step == 2) {
- updateWEditor(["white"]);
- h = $.i18n('wiz_cc_adjustit', $.i18n('edt_conf_color_white_title'));
- if (withKodi) {
- h += ' ' + $.i18n('wiz_cc_kodishould', $.i18n('edt_conf_color_white_title'));
- sendToKodi('playP', "white");
- }
- else
- h += ' ' + $.i18n('wiz_cc_lettvshow', $.i18n('edt_conf_color_white_title'));
- $('#wiz_cc_desc').html(h);
- }
- if (step == 3) {
- updateWEditor(["gammaRed", "gammaGreen", "gammaBlue"]);
- h = '' + $.i18n('wiz_cc_adjustgamma') + '
';
- if (withKodi) {
- sendToKodi('playP', "HGradient");
- h += '' + $.i18n('wiz_cc_btn_switchpic') + ' ';
- }
- else
- h += '' + $.i18n('wiz_cc_lettvshowm', "grey_1, grey_2, grey_3, HGradient, VGradient") + '
';
- $('#wiz_cc_desc').html(h);
- $('#wiz_cc_btn_sp').off().on('click', function () {
- switchPicture(["VGradient", "grey_1", "grey_2", "grey_3", "HGradient"]);
- });
- }
- if (step == 4) {
- updateWEditor(["red"]);
- h = $.i18n('wiz_cc_adjustit', $.i18n('edt_conf_color_red_title'));
- if (withKodi) {
- h += ' ' + $.i18n('wiz_cc_kodishould', $.i18n('edt_conf_color_red_title'));
- sendToKodi('playP', "red");
- }
- else
- h += ' ' + $.i18n('wiz_cc_lettvshow', $.i18n('edt_conf_color_red_title'));
- $('#wiz_cc_desc').html(h);
- }
- if (step == 5) {
- updateWEditor(["green"]);
- h = $.i18n('wiz_cc_adjustit', $.i18n('edt_conf_color_green_title'));
- if (withKodi) {
- h += ' ' + $.i18n('wiz_cc_kodishould', $.i18n('edt_conf_color_green_title'));
- sendToKodi('playP', "green");
- }
- else
- h += ' ' + $.i18n('wiz_cc_lettvshow', $.i18n('edt_conf_color_green_title'));
- $('#wiz_cc_desc').html(h);
- }
- if (step == 6) {
- updateWEditor(["blue"]);
- h = $.i18n('wiz_cc_adjustit', $.i18n('edt_conf_color_blue_title'));
- if (withKodi) {
- h += ' ' + $.i18n('wiz_cc_kodishould', $.i18n('edt_conf_color_blue_title'));
- sendToKodi('playP', "blue");
- }
- else
- h += ' ' + $.i18n('wiz_cc_lettvshow', $.i18n('edt_conf_color_blue_title'));
- $('#wiz_cc_desc').html(h);
- }
- if (step == 7) {
- updateWEditor(["cyan"]);
- h = $.i18n('wiz_cc_adjustit', $.i18n('edt_conf_color_cyan_title'));
- if (withKodi) {
- h += ' ' + $.i18n('wiz_cc_kodishould', $.i18n('edt_conf_color_cyan_title'));
- sendToKodi('playP', "cyan");
- }
- else
- h += ' ' + $.i18n('wiz_cc_lettvshow', $.i18n('edt_conf_color_cyan_title'));
- $('#wiz_cc_desc').html(h);
- }
- if (step == 8) {
- updateWEditor(["magenta"]);
- h = $.i18n('wiz_cc_adjustit', $.i18n('edt_conf_color_magenta_title'));
- if (withKodi) {
- h += ' ' + $.i18n('wiz_cc_kodishould', $.i18n('edt_conf_color_magenta_title'));
- sendToKodi('playP', "magenta");
- }
- else
- h += ' ' + $.i18n('wiz_cc_lettvshow', $.i18n('edt_conf_color_magenta_title'));
- $('#wiz_cc_desc').html(h);
- }
- if (step == 9) {
- updateWEditor(["yellow"]);
- h = $.i18n('wiz_cc_adjustit', $.i18n('edt_conf_color_yellow_title'));
- if (withKodi) {
- h += ' ' + $.i18n('wiz_cc_kodishould', $.i18n('edt_conf_color_yellow_title'));
- sendToKodi('playP', "yellow");
- }
- else
- h += ' ' + $.i18n('wiz_cc_lettvshow', $.i18n('edt_conf_color_yellow_title'));
- $('#wiz_cc_desc').html(h);
- }
- if (step == 10) {
- updateWEditor(["backlightThreshold", "backlightColored"]);
- h = $.i18n('wiz_cc_backlight');
- if (withKodi) {
- h += ' ' + $.i18n('wiz_cc_kodishould', $.i18n('edt_conf_color_black_title'));
- sendToKodi('playP', "black");
- }
- else
- h += ' ' + $.i18n('wiz_cc_lettvshow', $.i18n('edt_conf_color_black_title'));
- $('#wiz_cc_desc').html(h);
- }
- if (step == 11) {
- updateWEditor([""], true);
- h = '' + $.i18n('wiz_cc_testintro') + '
';
- if (withKodi) {
- h += '' + $.i18n('wiz_cc_testintrok') + '
';
- sendToKodi('stop');
- for (var i = 0; i < availVideos.length; i++) {
- var txt = availVideos[i].replace(/_/g, " ");
- h += ' ' + txt + '
';
- }
- h += ' ' + $.i18n('wiz_cc_btn_stop') + '
';
- }
- else
- h += '' + $.i18n('wiz_cc_testintrowok') + ' ' + $.i18n('wiz_cc_link') + '
';
- h += '' + $.i18n('wiz_cc_summary') + '
';
- $('#wiz_cc_desc').html(h);
-
- $('.videobtn').off().on('click', function (e) {
- if (e.target.id == "stop")
- sendToKodi("stop");
- else
- sendToKodi("playV", e.target.id + '.mp4');
-
- $(this).prop("disabled", true);
- setTimeout(function () { $('.videobtn').prop("disabled", false) }, 10000);
- });
-
- $('#btn_wiz_next').prop("disabled", true);
- $('#btn_wiz_save').toggle(true);
- window.readOnlyMode ? $('#btn_wiz_save').prop('disabled', true) : $('#btn_wiz_save').prop('disabled', false);
- }
- else {
- $('#btn_wiz_next').prop("disabled", false);
- $('#btn_wiz_save').toggle(false);
- }
-}
-
-function updateWEditor(el, all) {
- for (var key in cobj) {
- if (all === true || el[0] == key || el[1] == key || el[2] == key)
- $('#editor_container_wiz [data-schemapath*=".' + profile + '.' + key + '"]').toggle(true);
- else
- $('#editor_container_wiz [data-schemapath*=".' + profile + '.' + key + '"]').toggle(false);
- }
-}
-
-function startWizardCC() {
-
- //create html
- $('#wiz_header').html(' ' + $.i18n('wiz_cc_title'));
- $('#wizp1_body').html('' + $.i18n('wiz_cc_title') + ' ' +
- '' + $.i18n('wiz_cc_intro1') + '
' +
- '' + $.i18n('wiz_cc_kwebs') + ' ' +
- ' ' +
- ' '
- );
- $('#wizp1_footer').html('' + ' ' + $.i18n('general_btn_continue') + ' ' +
- ' ' + $.i18n('general_btn_cancel') + ' '
- );
- $('#wizp2_body').html('
'
- );
- $('#wizp2_footer').html('' + ' ' + $.i18n('general_btn_back') + ' ' +
- '' + $.i18n('general_btn_next') + ' ' + ' ' +
- ' ' + $.i18n('general_btn_save') + ' ' +
- ' ' + $.i18n('general_btn_cancel') + ' '
- );
-
- if (getStorage("darkMode") == "on")
- $('#wizard_logo').prop("src", 'img/hyperion/logo_negativ.png');
-
- //open modal
- $("#wizard_modal").modal({
- backdrop: "static",
- keyboard: false,
- show: true
- });
-
- $('#wiz_cc_kodiip').off().on('change', function () {
-
- kodiAddress = encodeURIComponent($(this).val().trim());
-
- $('#kodi_status').html('');
- if (kodiAddress !== "") {
-
- if (!isValidHostnameOrIP(kodiAddress)) {
-
- $('#kodi_status').html('' + $.i18n('edt_msgcust_error_hostname_ip') + '
');
- withKodi = false;
-
- } else {
-
- if (isValidIPv6(kodiAddress)) {
- kodiUrl.hostname = "[" + kodiAddress + "]";
- } else {
- kodiUrl.hostname = kodiAddress;
- }
-
- $('#kodi_status').html('' + $.i18n('wiz_cc_try_connect') + '
');
- $('#btn_wiz_cont').prop('disabled', true);
-
- sendToKodi("msg", $.i18n('wiz_cc_kodimsg_start'), function (cb) {
- if (cb == "error") {
- $('#kodi_status').html('' + $.i18n('wiz_cc_kodidiscon') + '
' + $.i18n('wiz_cc_kodidisconlink') + ' ' + $.i18n('wiz_cc_link') + '
');
- withKodi = false;
- }
- else {
- setStorage("kodiAddress", kodiAddress);
- setStorage("kodiPort", defaultKodiPort);
-
- $('#kodi_status').html('' + $.i18n('wiz_cc_kodicon') + '
');
- withKodi = true;
- }
-
- $('#btn_wiz_cont').prop('disabled', false);
- });
- }
- }
- });
-
- //listen for continue
- $('#btn_wiz_cont').off().on('click', function () {
- beginWizardCC();
- $('#wizp1').toggle(false);
- $('#wizp2').toggle(true);
- });
-
- $('#wiz_cc_kodiip').trigger("change");
- colorLength = window.serverConfig.color.channelAdjustment;
- cobj = window.schema.color.properties.channelAdjustment.items.properties;
- websAddress = document.location.hostname + ':' + window.serverConfig.webConfig.port;
- imgAddress = 'http://' + websAddress + '/img/cc/';
- setStorage("wizardactive", true);
-
- //check profile count
- if (colorLength.length > 1) {
- $('#multi_cali').html('' + $.i18n('wiz_cc_morethanone') + '
');
- for (var i = 0; i < colorLength.length; i++)
- $('#wiz_select').append(createSelOpt(i, i + 1 + ' (' + colorLength[i].id + ')'));
-
- $('#wiz_select').off().on('change', function () {
- profile = $(this).val();
- });
- }
-
- //prepare editor
- wiz_editor = createJsonEditor('editor_container_wiz', {
- color: window.schema.color
- }, true, true);
-
- $('#editor_container_wiz h4').toggle(false);
- $('#editor_container_wiz .btn-group').toggle(false);
- $('#editor_container_wiz [data-schemapath="root.color.imageToLedMappingType"]').toggle(false);
- for (var i = 0; i < colorLength.length; i++)
- $('#editor_container_wiz [data-schemapath*="root.color.channelAdjustment.' + i + '."]').toggle(false);
-}
-
-function beginWizardCC() {
- $('#btn_wiz_next').off().on('click', function () {
- step++;
- performAction();
- });
-
- $('#btn_wiz_back').off().on('click', function () {
- step--;
- performAction();
- });
-
- $('#btn_wiz_abort').off().on('click', resetWizard);
-
- $('#btn_wiz_save').off().on('click', function () {
- requestWriteConfig(wiz_editor.getValue());
- resetWizard();
- });
-
- wiz_editor.on("change", function (e) {
- var val = wiz_editor.getEditor('root.color.channelAdjustment.' + profile + '').getValue();
- var temp = JSON.parse(JSON.stringify(val));
- delete temp.leds
- requestAdjustment(JSON.stringify(temp), "", true);
- });
-
- step++
- performAction();
-}
-
-$('#btn_wizard_colorcalibration').off().on('click', startWizardCC);
-
-// Layout positions
-var lightPosTop = { hmin: 0.15, hmax: 0.85, vmin: 0, vmax: 0.2 };
-var lightPosTopLeft = { hmin: 0, hmax: 0.15, vmin: 0, vmax: 0.15 };
-var lightPosTopRight = { hmin: 0.85, hmax: 1.0, vmin: 0, vmax: 0.15 };
-var lightPosBottom = { hmin: 0.15, hmax: 0.85, vmin: 0.8, vmax: 1.0 };
-var lightPosBottomLeft = { hmin: 0, hmax: 0.15, vmin: 0.85, vmax: 1.0 };
-var lightPosBottomRight = { hmin: 0.85, hmax: 1.0, vmin: 0.85, vmax: 1.0 };
-var lightPosLeft = { hmin: 0, hmax: 0.15, vmin: 0.15, vmax: 0.85 };
-var lightPosLeftTop = { hmin: 0, hmax: 0.15, vmin: 0, vmax: 0.5 };
-var lightPosLeftMiddle = { hmin: 0, hmax: 0.15, vmin: 0.25, vmax: 0.75 };
-var lightPosLeftBottom = { hmin: 0, hmax: 0.15, vmin: 0.5, vmax: 1.0 };
-var lightPosRight = { hmin: 0.85, hmax: 1.0, vmin: 0.15, vmax: 0.85 };
-var lightPosRightTop = { hmin: 0.85, hmax: 1.0, vmin: 0, vmax: 0.5 };
-var lightPosRightMiddle = { hmin: 0.85, hmax: 1.0, vmin: 0.25, vmax: 0.75 };
-var lightPosRightBottom = { hmin: 0.85, hmax: 1.0, vmin: 0.5, vmax: 1.0 };
-var lightPosEntire = { hmin: 0.0, hmax: 1.0, vmin: 0.0, vmax: 1.0 };
-
-var lightPosBottomLeft14 = { hmin: 0, hmax: 0.25, vmin: 0.85, vmax: 1.0 };
-var lightPosBottomLeft12 = { hmin: 0.25, hmax: 0.5, vmin: 0.85, vmax: 1.0 };
-var lightPosBottomLeft34 = { hmin: 0.5, hmax: 0.75, vmin: 0.85, vmax: 1.0 };
-var lightPosBottomLeft11 = { hmin: 0.75, hmax: 1, vmin: 0.85, vmax: 1.0 };
-
-var lightPosBottomLeft112 = { hmin: 0, hmax: 0.5, vmin: 0.85, vmax: 1.0 };
-var lightPosBottomLeft121 = { hmin: 0.5, hmax: 1, vmin: 0.85, vmax: 1.0 };
-var lightPosBottomLeftNewMid = { hmin: 0.25, hmax: 0.75, vmin: 0.85, vmax: 1.0 };
-
-var lightPosTopLeft112 = { hmin: 0, hmax: 0.5, vmin: 0, vmax: 0.15 };
-var lightPosTopLeft121 = { hmin: 0.5, hmax: 1, vmin: 0, vmax: 0.15 };
-var lightPosTopLeftNewMid = { hmin: 0.25, hmax: 0.75, vmin: 0, vmax: 0.15 };
-
-function assignLightPos(pos, name) {
- var i = null;
-
- if (pos === "top")
- i = lightPosTop;
- else if (pos === "topleft")
- i = lightPosTopLeft;
- else if (pos === "topright")
- i = lightPosTopRight;
- else if (pos === "bottom")
- i = lightPosBottom;
- else if (pos === "bottomleft")
- i = lightPosBottomLeft;
- else if (pos === "bottomright")
- i = lightPosBottomRight;
- else if (pos === "left")
- i = lightPosLeft;
- else if (pos === "lefttop")
- i = lightPosLeftTop;
- else if (pos === "leftmiddle")
- i = lightPosLeftMiddle;
- else if (pos === "leftbottom")
- i = lightPosLeftBottom;
- else if (pos === "right")
- i = lightPosRight;
- else if (pos === "righttop")
- i = lightPosRightTop;
- else if (pos === "rightmiddle")
- i = lightPosRightMiddle;
- else if (pos === "rightbottom")
- i = lightPosRightBottom;
- else if (pos === "lightPosBottomLeft14")
- i = lightPosBottomLeft14;
- else if (pos === "lightPosBottomLeft12")
- i = lightPosBottomLeft12;
- else if (pos === "lightPosBottomLeft34")
- i = lightPosBottomLeft34;
- else if (pos === "lightPosBottomLeft11")
- i = lightPosBottomLeft11;
- else if (pos === "lightPosBottomLeft112")
- i = lightPosBottomLeft112;
- else if (pos === "lightPosBottomLeft121")
- i = lightPosBottomLeft121;
- else if (pos === "lightPosBottomLeftNewMid")
- i = lightPosBottomLeftNewMid;
- else if (pos === "lightPosTopLeft112")
- i = lightPosTopLeft112;
- else if (pos === "lightPosTopLeft121")
- i = lightPosTopLeft121;
- else if (pos === "lightPosTopLeftNewMid")
- i = lightPosTopLeftNewMid;
- else
- i = lightPosEntire;
-
- i.name = name;
- return i;
-}
-
-function getHostInLights(hostname) {
- return lights.filter(
- function (lights) {
- return lights.host === hostname
- }
- );
-}
-
-function getIpInLights(ip) {
- return lights.filter(
- function (lights) {
- return lights.ip === ip
- }
- );
-}
-
-function getIdInLights(id) {
- return lights.filter(
- function (lights) {
- return lights.id === id
- }
- );
-}
-
-// External properties properties, 2-dimensional arry of [ledType][key]
-devicesProperties = {};
-
-//****************************
-// Wizard Philips Hue
-//****************************
-
-var hueIPs = [];
-var hueIPsinc = 0;
-var hueLights = [];
-var hueEntertainmentConfigs = [];
-var hueEntertainmentServices = [];
-var lightLocation = [];
-var groupLights = [];
-var groupChannels = [];
-var groupLightsLocations = [];
-var isAPIv2Ready = true;
-var isEntertainmentReady = true;
-
-function startWizardPhilipsHue(e) {
- //create html
-
- var hue_title = 'wiz_hue_title';
- var hue_intro1 = 'wiz_hue_e_intro1';
- var hue_desc1 = 'wiz_hue_desc1';
- var hue_create_user = 'wiz_hue_create_user';
-
- $('#wiz_header').html(' ' + $.i18n(hue_title));
- $('#wizp1_body').html('' + $.i18n(hue_title) + ' ' + $.i18n(hue_intro1) + '
');
- $('#wizp1_footer').html(' ' + $.i18n('general_btn_continue') + ' ' + $.i18n('general_btn_cancel') + ' ');
- $('#wizp2_body').html('
');
-
- var topContainer_html = '' + $.i18n(hue_desc1) + '
' +
- '' +
- '
' +
- '
' + $.i18n('wiz_hue_ip') + '
' +
- '
' +
- ' ' +
- ' ' + ' ' + '
' +
- '
';
-
- if (storedAccess === 'expert') {
- topContainer_html += '
';
- }
-
- topContainer_html += '
';
- topContainer_html += '
';
-
- $('#wh_topcontainer').append(topContainer_html);
-
- $('#usrcont').append('' + $.i18n('wiz_hue_username') + '
' +
- '
' +
- ' '
- );
-
- $('#usrcont').append(' ');
-
- $('#usrcont').append(' <\p>' +
- ' ' + $.i18n(hue_create_user) + ' ');
-
- $('#wizp2_body').append('
' + $.i18n('wiz_hue_e_desc2') + '
');
- createTable("gidsh", "gidsb", "hue_grp_ids_t");
- $('.gidsh').append(createTableRow([$.i18n('edt_dev_spec_groupId_title'), ""], true));
-
- $('#wizp2_body').append('' + $.i18n('wiz_hue_e_desc3') + '
');
-
- createTable("lidsh", "lidsb", "hue_ids_t");
- $('.lidsh').append(createTableRow([$.i18n('edt_dev_spec_lightid_title'), $.i18n('wiz_pos'), $.i18n('wiz_identify')], true));
- $('#wizp2_footer').html(' ' + $.i18n('general_btn_save') + ' ' + $.i18n('general_btn_cancel') + ' ');
- $('#wizp3_body').html('' + $.i18n('wiz_hue_press_link') + ' ');
-
- if (getStorage("darkMode") == "on")
- $('#wizard_logo').attr("src", 'img/hyperion/logo_negativ.png');
-
- //open modal
- $("#wizard_modal").modal({
- backdrop: "static",
- keyboard: false,
- show: true
- });
-
- //listen for continue
- $('#btn_wiz_cont').off().on('click', function () {
- beginWizardHue();
- $('#wizp1').toggle(false);
- $('#wizp2').toggle(true);
- });
-}
-
-function checkHueBridge(cb, hueUser) {
- var usr = (typeof hueUser != "undefined") ? hueUser : 'config';
- if (usr === 'config') {
- $('#wiz_hue_discovered').html("");
- }
-
- if (hueIPs[hueIPsinc]) {
- var host = hueIPs[hueIPsinc].host;
- var port = hueIPs[hueIPsinc].port;
-
- if (usr != '')
- {
- getProperties_hue_bridge(cb, decodeURIComponent(host), port, usr);
- }
- else
- {
- cb(false, usr);
- }
-
- if (isAPIv2Ready) {
- $('#port').val(443);
- }
- }
-}
-
-function checkBridgeResult(reply, usr) {
- if (reply) {
- //abort checking, first reachable result is used
- $('#wiz_hue_ipstate').html("");
- $('#host').val(hueIPs[hueIPsinc].host)
- $('#port').val(hueIPs[hueIPsinc].port)
-
- $('#usrcont').toggle(true);
-
- checkHueBridge(checkUserResult, $('#user').val());
- }
- else {
- $('#usrcont').toggle(false);
- $('#wiz_hue_ipstate').html($.i18n('wiz_hue_failure_ip'));
- }
-};
-
-function checkUserResult(reply, username) {
- $('#usrcont').toggle(true);
-
- var hue_create_user = 'wiz_hue_e_create_user';
- if (!isEntertainmentReady) {
- hue_create_user = 'wiz_hue_create_user';
- $('#hue_client_key_r').toggle(false);
- } else {
- $('#hue_client_key_r').toggle(true);
- }
-
- $('#wiz_hue_create_user').text($.i18n(hue_create_user));
- $('#wiz_hue_create_user').toggle(true);
-
- if (reply) {
- $('#user').val(username);
-
- if (isEntertainmentReady && $('#clientkey').val() == "") {
- $('#wiz_hue_usrstate').html($.i18n('wiz_hue_e_clientkey_needed'));
- $('#wiz_hue_create_user').toggle(true);
- } else {
- $('#wiz_hue_usrstate').html("");
- $('#wiz_hue_create_user').toggle(false);
-
- if (isEntertainmentReady) {
- $('#hue_id_headline').text($.i18n('wiz_hue_e_desc3'));
- $('#hue_grp_ids_t').toggle(true);
-
- get_hue_groups(username);
-
- } else {
- $('#hue_id_headline').text($.i18n('wiz_hue_desc2'));
- $('#hue_grp_ids_t').toggle(false);
-
- get_hue_lights(username);
-
- }
- }
- }
- else {
- //abort checking, first reachable result is used
- $('#wiz_hue_usrstate').html($.i18n('wiz_hue_failure_user'));
- $('#wiz_hue_create_user').toggle(true);
- }
-};
-
-function useGroupId(id, username) {
- $('#groupId').val(hueEntertainmentConfigs[id].id);
- if (isAPIv2Ready) {
- var group = hueEntertainmentConfigs[id];
-
- groupLights = [];
- for (const light of group.light_services) {
- groupLights.push(light.rid);
- }
-
- groupChannels = [];
- for (const channel of group.channels) {
- groupChannels.push(channel);
- }
-
- groupLightsLocations = [];
- for (const location of group.locations.service_locations) {
- groupLightsLocations.push(location);
- }
- } else {
- //Ensure ligthIDs are strings
- groupLights = hueEntertainmentConfigs[id].lights.map(num => {
- return String(num);
- });
-
- var lightLocations = hueEntertainmentConfigs[id].locations;
- for (var locationID in lightLocations) {
- var lightLocation = {};
-
- let position = {
- x: lightLocations[locationID][0],
- y: lightLocations[locationID][1],
- z: lightLocations[locationID][2]
- };
- lightLocation.position = position;
-
- groupLightsLocations.push(lightLocation);
- }
- }
-
- get_hue_lights(username);
-}
-
-function updateBridgeDetails(properties) {
- var ledDeviceProperties = properties.config;
-
- if (!jQuery.isEmptyObject(ledDeviceProperties)) {
- isEntertainmentReady = properties.isEntertainmentReady;
- isAPIv2Ready = properties.isAPIv2Ready;
-
- if (ledDeviceProperties.name && ledDeviceProperties.bridgeid && ledDeviceProperties.modelid) {
- $('#wiz_hue_discovered').html(
- "Bridge: " + ledDeviceProperties.name +
- ", Modelid: " + ledDeviceProperties.modelid +
- ", Firmware: " + ledDeviceProperties.swversion + " " +
- "API-Version: " + ledDeviceProperties.apiversion +
- ", Entertainment: " + (isEntertainmentReady ? "✓" : "-") +
- ", APIv2: " + (isAPIv2Ready ? "✓" : "-")
- );
- }
- }
-}
-
-async function discover_hue_bridges() {
- $('#wiz_hue_ipstate').html($.i18n('edt_dev_spec_devices_discovery_inprogress'));
-
- // $('#wiz_hue_discovered').html("")
- const res = await requestLedDeviceDiscovery('philipshue');
- if (res && !res.error) {
- const r = res.info;
-
- // Process devices returned by discovery
- if (r.devices.length == 0) {
- $('#wiz_hue_ipstate').html($.i18n('wiz_hue_failure_ip'));
- $('#wiz_hue_discovered').html("")
- }
- else {
- hueIPs = [];
- hueIPsinc = 0;
-
- var discoveryMethod = "ssdp";
- if (res.info.discoveryMethod) {
- discoveryMethod = res.info.discoveryMethod;
- }
-
- for (const device of r.devices) {
- if (device) {
- var host;
- var port;
- if (discoveryMethod === "ssdp") {
- if (device.hostname && device.domain) {
- host = device.hostname + "." + device.domain;
- port = device.port;
- } else {
- host = device.ip;
- port = device.port;
- }
- } else {
- host = device.service;
- port = device.port;
- }
-
- if (host) {
-
- if (!hueIPs.some(item => item.host === host)) {
- hueIPs.push({ host: host, port: port });
- }
- }
- }
- }
-
- $('#wiz_hue_ipstate').html("");
- $('#host').val(hueIPs[hueIPsinc].host)
- $('#port').val(hueIPs[hueIPsinc].port)
-
- $('#hue_bridge_select').html("");
-
- for (var key in hueIPs) {
- $('#hue_bridge_select').append(createSelOpt(key, hueIPs[key].host));
- }
-
- $('.hue_bridge_sel_watch').on("click", function () {
- hueIPsinc = $(this).val();
-
- var name = $("#hue_bridge_select option:selected").text();
- $('#host').val(name);
- $('#port').val(hueIPs[hueIPsinc].port)
-
- var usr = $('#user').val();
- if (usr != "") {
- checkHueBridge(checkUserResult, usr);
- } else {
- checkHueBridge(checkBridgeResult);
- }
- });
-
- $('.hue_bridge_sel_watch').click();
- }
- }
-}
-
-async function getProperties_hue_bridge(cb, hostAddress, port, username, resourceFilter) {
- let params = { host: hostAddress, username: username, filter: resourceFilter };
- if (port !== 'undefined') {
- params.port = parseInt(port);
- }
-
- var ledType = 'philipshue';
- var key = hostAddress;
-
- //Create ledType cache entry
- if (!devicesProperties[ledType]) {
- devicesProperties[ledType] = {};
- }
-
- // Use device's properties, if properties in chache
- if (devicesProperties[ledType][key] && devicesProperties[ledType][key][username]) {
- updateBridgeDetails(devicesProperties[ledType][key]);
- cb(true, username);
- } else {
- const res = await requestLedDeviceProperties(ledType, params);
- if (res && !res.error) {
- var ledDeviceProperties = res.info.properties;
- if (!jQuery.isEmptyObject(ledDeviceProperties)) {
-
- devicesProperties[ledType][key] = {};
- devicesProperties[ledType][key][username] = ledDeviceProperties;
-
- isAPIv2Ready = res.info.isAPIv2Ready;
- devicesProperties[ledType][key].isAPIv2Ready = isAPIv2Ready;
- isEntertainmentReady = res.info.isEntertainmentReady;
- devicesProperties[ledType][key].isEntertainmentReady = isEntertainmentReady;
-
- updateBridgeDetails(devicesProperties[ledType][key]);
- if (username === "config") {
- cb(true);
- } else {
- cb(true, username);
- }
- } else {
- cb(false, username);
- }
- } else {
- cb(false, username);
- }
- }
-}
-
-async function identify_hue_device(hostAddress, port, username, name, id, id_v1) {
- var disabled = $('#btn_wiz_save').is(':disabled');
- // Take care that new record cannot be save during background process
- $('#btn_wiz_save').prop('disabled', true);
-
- let params = { host: decodeURIComponent(hostAddress), username: username, lightName: decodeURIComponent(name), lightId: id, lightId_v1: id_v1 };
-
- if (port !== 'undefined') {
- params.port = parseInt(port);
- }
-
- await requestLedDeviceIdentification('philipshue', params);
-
- if (!window.readOnlyMode) {
- $('#btn_wiz_save').prop('disabled', disabled);
- }
-}
-
-//return editor Value
-function eV(vn, defaultVal = "") {
- var editor = (vn) ? conf_editor.getEditor("root.specificOptions." + vn) : null;
- return (editor == null) ? defaultVal : ((defaultVal != "" && !isNaN(defaultVal) && isNaN(editor.getValue())) ? defaultVal : editor.getValue());
-}
-
-function beginWizardHue() {
- var usr = eV("username");
- if (usr != "") {
- $('#user').val(usr);
- }
-
- var clkey = eV("clientkey");
- if (clkey != "") {
- $('#clientkey').val(clkey);
- }
-
- //check if host is empty/reachable/search for bridge
- if (eV("host") == "") {
- hueIPs = [];
- hueIPsinc = 0;
-
- discover_hue_bridges();
- }
- else {
- var host = eV("host");
- $('#host').val(host);
-
- var port = eV("port");
- if (port > 0) {
- $('#port').val(port);
- }
- else {
- $('#port').val('');
- }
- hueIPs.push({ host: host, port: port });
-
- if (usr != "") {
- checkHueBridge(checkUserResult, usr);
- } else {
- checkHueBridge(checkBridgeResult);
- }
- }
-
- $('#retry_bridge').off().on('click', function () {
- var host = $('#host').val();
- var port = parseInt($('#port').val());
-
- if (host != "") {
-
- var idx = hueIPs.findIndex(item => item.host === host && item.port === port);
- if (idx === -1) {
- hueIPs.push({ host: host, port: port });
- hueIPsinc = hueIPs.length - 1;
- } else {
- hueIPsinc = idx;
- }
- }
- else {
- discover_hue_bridges();
- }
-
- var usr = $('#user').val();
- if (usr != "") {
- checkHueBridge(checkUserResult, usr);
- } else {
- checkHueBridge(checkBridgeResult);
- }
- });
-
- $('#retry_usr').off().on('click', function () {
- checkHueBridge(checkUserResult, $('#user').val());
- });
-
- $('#wiz_hue_create_user').off().on('click', function () {
- createHueUser();
- });
-
- function assignLightEntertainmentPos(isFocusCenter, position, name, id) {
-
- var x = position.x;
- var z = position.z;
-
- if (isFocusCenter) {
- // Map lights as in centered range -0.5 to 0.5
- if (x < -0.5) {
- x = -0.5;
- } else if (x > 0.5) {
- x = 0.5;
- }
- if (z < -0.5) {
- z = -0.5;
- } else if (z > 0.5) {
- z = 0.5;
- }
- } else {
- // Map lights as in full range -1 to 1
- x /= 2;
- z /= 2;
- }
-
- var h = x + 0.5;
- var v = -z + 0.5;
-
- var hmin = h - 0.05;
- var hmax = h + 0.05;
- var vmin = v - 0.05;
- var vmax = v + 0.05;
-
- let layoutObject = {
- hmin: hmin < 0 ? 0 : hmin,
- hmax: hmax > 1 ? 1 : hmax,
- vmin: vmin < 0 ? 0 : vmin,
- vmax: vmax > 1 ? 1 : vmax,
- name: name
- };
-
- if (id) {
- layoutObject.name += "_" + id;
- }
- return layoutObject;
- }
-
- function assignSegmentedLightPos(segment, position, name) {
- var layoutObjects = [];
-
- var segTotalLength = 0;
- for (var key in segment) {
-
- segTotalLength += segment[key].length;
- }
-
- var min;
- var max;
- var horizontal = true;
-
- var layoutObject = assignLightPos(position, name);
- if (position === "left" || position === "right") {
- // vertical distribution
- min = layoutObject.vmin;
- max = layoutObject.vmax;
- horizontal = false;
-
- } else {
- // horizontal distribution
- min = layoutObject.hmin;
- max = layoutObject.hmax;
- }
-
- var step = (max - min) / segTotalLength;
- var start = min;
-
- for (var key in segment) {
- min = start;
- max = round(start + segment[key].length * step);
-
- if (horizontal) {
- layoutObject.hmin = min;
- layoutObject.hmax = max;
- } else {
- layoutObject.vmin = min;
- layoutObject.vmax = max;
- }
- layoutObject.name = name + "_" + key;
- layoutObjects.push(JSON.parse(JSON.stringify(layoutObject)));
-
- start = max;
- }
-
- return layoutObjects;
- }
-
- $('#btn_wiz_save').off().on("click", function () {
- var hueLedConfig = [];
- var finalLightIds = [];
- var channelNumber = 0;
-
- //create hue led config
- for (var key in groupLights) {
- var lightId = groupLights[key];
-
- if ($('#hue_' + lightId).val() != "disabled") {
- finalLightIds.push(lightId);
-
- var lightName;
- if (isAPIv2Ready) {
- var light = hueLights.find(light => light.id === lightId);
- lightName = light.metadata.name;
- } else {
- lightName = hueLights[lightId].name;
- }
-
- var position = $('#hue_' + lightId).val();
- var lightIdx = groupLights.indexOf(lightId);
- var lightLocation = groupLightsLocations[lightIdx];
-
- var serviceID;
- if (isAPIv2Ready) {
- serviceID = lightLocation.service.rid;
- }
-
- if (position.startsWith("entertainment")) {
-
- // Layout per entertainment area definition at bridge
- var isFocusCenter = false;
- if (position === "entertainment_center") {
- isFocusCenter = true;
- }
-
- if (isAPIv2Ready) {
-
- groupChannels.forEach((channel) => {
- if (channel.members[0].service.rid === serviceID) {
- var layoutObject = assignLightEntertainmentPos(isFocusCenter, channel.position, lightName, channel.channel_id);
- hueLedConfig.push(JSON.parse(JSON.stringify(layoutObject)));
- ++channelNumber;
- }
- });
- } else {
- var layoutObject = assignLightEntertainmentPos(isFocusCenter, lightLocation.position, lightName);
- hueLedConfig.push(JSON.parse(JSON.stringify(layoutObject)));
- }
- }
- else {
- // Layout per manual settings
- var maxSegments = 1;
-
- if (isAPIv2Ready) {
- var service = hueEntertainmentServices.find(service => service.id === serviceID);
- maxSegments = service.segments.max_segments;
- }
-
- if (maxSegments > 1) {
- var segment = service.segments.segments;
- var layoutObjects = assignSegmentedLightPos(segment, position, lightName);
- hueLedConfig.push(...layoutObjects);
- } else {
- var layoutObject = assignLightPos(position, lightName);
- hueLedConfig.push(JSON.parse(JSON.stringify(layoutObject)));
- }
- channelNumber += maxSegments;
- }
- }
- }
-
- var sc = window.serverConfig;
- sc.leds = hueLedConfig;
-
- //Adjust gamma, brightness and compensation
- var c = sc.color.channelAdjustment[0];
- c.gammaBlue = 1.0;
- c.gammaRed = 1.0;
- c.gammaGreen = 1.0;
- c.brightness = 100;
- c.brightnessCompensation = 0;
-
- //device config
-
- //Start with a clean configuration
- var d = {};
- d.host = $('#host').val();
- d.port = parseInt($('#port').val());
- d.username = $('#user').val();
- d.type = 'philipshue';
- d.colorOrder = 'rgb';
- d.lightIds = finalLightIds;
- d.transitiontime = parseInt(eV("transitiontime", 1));
- d.restoreOriginalState = (eV("restoreOriginalState", false) == true);
- d.switchOffOnBlack = (eV("switchOffOnBlack", false) == true);
-
- d.blackLevel = parseFloat(eV("blackLevel", 0.009));
- d.onBlackTimeToPowerOff = parseInt(eV("onBlackTimeToPowerOff", 600));
- d.onBlackTimeToPowerOn = parseInt(eV("onBlackTimeToPowerOn", 300));
- d.brightnessFactor = parseFloat(eV("brightnessFactor", 1));
-
- d.clientkey = $('#clientkey').val();
- d.groupId = $('#groupId').val();
- d.blackLightsTimeout = parseInt(eV("blackLightsTimeout", 5000));
- d.brightnessMin = parseFloat(eV("brightnessMin", 0));
- d.brightnessMax = parseFloat(eV("brightnessMax", 1));
- d.brightnessThreshold = parseFloat(eV("brightnessThreshold", 0.0001));
- d.handshakeTimeoutMin = parseInt(eV("handshakeTimeoutMin", 300));
- d.handshakeTimeoutMax = parseInt(eV("handshakeTimeoutMax", 1000));
- d.verbose = (eV("verbose") == true);
-
- d.autoStart = conf_editor.getEditor("root.generalOptions.autoStart").getValue();
- d.enableAttempts = parseInt(conf_editor.getEditor("root.generalOptions.enableAttempts").getValue());
- d.enableAttemptsInterval = parseInt(conf_editor.getEditor("root.generalOptions.enableAttemptsInterval").getValue());
-
- d.useEntertainmentAPI = isEntertainmentReady;
- d.useAPIv2 = isAPIv2Ready;
-
- if (isEntertainmentReady) {
- d.hardwareLedCount = channelNumber;
- if (window.serverConfig.device.type !== d.type) {
- //smoothing on, if new device
- sc.smoothing = { enable: true };
- }
- } else {
- d.hardwareLedCount = finalLightIds.length;
- d.verbose = false;
- if (window.serverConfig.device.type !== d.type) {
- //smoothing off, if new device
- sc.smoothing = { enable: false };
- }
- }
-
- window.serverConfig.device = d;
-
- requestWriteConfig(sc, true);
- resetWizard();
- });
-
- $('#btn_wiz_abort').off().on('click', resetWizard);
-}
-
-function createHueUser() {
- var host = hueIPs[hueIPsinc].host;
- var port = hueIPs[hueIPsinc].port;
-
- let params = { host: host };
- if (port !== 'undefined') {
- params.port = parseInt(port);
- }
-
- var retryTime = 30;
- var retryInterval = 2;
-
- var UserInterval = setInterval(function () {
-
- $('#wizp1').toggle(false);
- $('#wizp2').toggle(false);
- $('#wizp3').toggle(true);
-
- (async () => {
-
- retryTime -= retryInterval;
- $("#connectionTime").html(retryTime);
- if (retryTime <= 0) {
- abortConnection(UserInterval);
- clearInterval(UserInterval);
- }
- else {
- const res = await requestLedDeviceAddAuthorization('philipshue', params);
- if (res && !res.error) {
- var response = res.info;
-
- if (jQuery.isEmptyObject(response)) {
- debugMessage(retryTime + ": link button not pressed or device not reachable");
- } else {
- $('#wizp1').toggle(false);
- $('#wizp2').toggle(true);
- $('#wizp3').toggle(false);
-
- var username = response.username;
- if (username != 'undefined') {
- $('#user').val(username);
- conf_editor.getEditor("root.specificOptions.username").setValue(username);
- conf_editor.getEditor("root.specificOptions.host").setValue(host);
- conf_editor.getEditor("root.specificOptions.port").setValue(port);
- }
-
- if (isEntertainmentReady) {
- var clientkey = response.clientkey;
- if (clientkey != 'undefined') {
- $('#clientkey').val(clientkey);
- conf_editor.getEditor("root.specificOptions.clientkey").setValue(clientkey);
- }
- }
- checkHueBridge(checkUserResult, username);
- clearInterval(UserInterval);
- }
- } else {
- $('#wizp1').toggle(false);
- $('#wizp2').toggle(true);
- $('#wizp3').toggle(false);
- clearInterval(UserInterval);
- }
- }
- })();
-
- }, retryInterval * 1000);
-}
-
-function get_hue_groups(username) {
- var host = hueIPs[hueIPsinc].host;
-
- if (devicesProperties['philipshue'][host] && devicesProperties['philipshue'][host][username]) {
- var ledProperties = devicesProperties['philipshue'][host][username];
-
- if (isAPIv2Ready) {
- if (!jQuery.isEmptyObject(ledProperties.data)) {
- if (Object.keys(ledProperties.data).length > 0) {
- hueEntertainmentConfigs = ledProperties.data.filter(config => {
- return config.type === "entertainment_configuration";
- });
- hueEntertainmentServices = ledProperties.data.filter(config => {
- return (config.type === "entertainment" && config.renderer === true);
- });
- }
- }
- } else {
- if (!jQuery.isEmptyObject(ledProperties.groups)) {
- hueEntertainmentConfigs = [];
- var hueGroups = ledProperties.groups;
- for (var groupid in hueGroups) {
- if (hueGroups[groupid].type == 'Entertainment') {
- hueGroups[groupid].id = groupid;
- hueEntertainmentConfigs.push(hueGroups[groupid]);
- }
- }
- }
- }
-
- if (Object.keys(hueEntertainmentConfigs).length > 0) {
-
- $('.lidsb').html("");
- $('#wh_topcontainer').toggle(false);
- $('#hue_grp_ids_t').toggle(true);
-
- for (var groupid in hueEntertainmentConfigs) {
- $('.gidsb').append(createTableRow([groupid + ' (' + hueEntertainmentConfigs[groupid].name + ')', '' + $.i18n('wiz_hue_e_use_group') + ' ']));
- }
- } else {
- noAPISupport('wiz_hue_e_noegrpids', username);
- }
- }
-}
-
-function noAPISupport(txt, username) {
- showNotification('danger', $.i18n('wiz_hue_e_title'), $.i18n('wiz_hue_e_noapisupport_hint'));
- conf_editor.getEditor("root.specificOptions.useEntertainmentAPI").setValue(false);
- $("#root_specificOptions_useEntertainmentAPI").trigger("change");
- $('#btn_wiz_holder').append('' + $.i18n('wiz_hue_e_noapisupport_hint') + '
');
- $('#hue_grp_ids_t').toggle(false);
- var txt = (txt) ? $.i18n(txt) : $.i18n('wiz_hue_e_nogrpids');
- $('' + txt + ' ' + $.i18n('wiz_hue_e_noapisupport') + '
').insertBefore('#wizp2_body #hue_ids_t');
- $('#hue_id_headline').html($.i18n('wiz_hue_desc2'));
-
- get_hue_lights(username);
-}
-
-function get_hue_lights(username) {
- var host = hueIPs[hueIPsinc].host;
-
- if (devicesProperties['philipshue'][host] && devicesProperties['philipshue'][host][username]) {
- var ledProperties = devicesProperties['philipshue'][host][username];
-
- if (isAPIv2Ready) {
- if (!jQuery.isEmptyObject(ledProperties.data)) {
- if (Object.keys(ledProperties.data).length > 0) {
- hueLights = ledProperties.data.filter(config => {
- return config.type === "light";
- });
- }
- }
- } else {
- if (!jQuery.isEmptyObject(ledProperties.lights)) {
- hueLights = ledProperties.lights;
- }
- }
-
- if (Object.keys(hueLights).length > 0) {
- if (!isEntertainmentReady) {
- $('#wh_topcontainer').toggle(false);
- }
- $('#hue_ids_t, #btn_wiz_save').toggle(true);
-
- var lightOptions = [
- "top", "topleft", "topright",
- "bottom", "bottomleft", "bottomright",
- "left", "lefttop", "leftmiddle", "leftbottom",
- "right", "righttop", "rightmiddle", "rightbottom",
- "entire",
- "lightPosTopLeft112", "lightPosTopLeftNewMid", "lightPosTopLeft121",
- "lightPosBottomLeft14", "lightPosBottomLeft12", "lightPosBottomLeft34", "lightPosBottomLeft11",
- "lightPosBottomLeft112", "lightPosBottomLeftNewMid", "lightPosBottomLeft121"
- ];
-
- if (isEntertainmentReady) {
- lightOptions.unshift("entertainment_center");
- lightOptions.unshift("entertainment");
- } else {
- lightOptions.unshift("disabled");
- groupLights = Object.keys(hueLights);
- }
-
- $('.lidsb').html("");
-
- var pos = "";
- for (var id in groupLights) {
- var lightId = groupLights[id];
- var lightId_v1 = "/lights/" + lightId;
-
- var lightName;
- if (isAPIv2Ready) {
- var light = hueLights.find(light => light.id === lightId);
- lightName = light.metadata.name;
- lightId_v1 = light.id_v1;
- } else {
- lightName = hueLights[lightId].name;
- }
-
- if (isEntertainmentReady) {
- var lightLocation = {};
- lightLocation = groupLightsLocations[id];
- if (lightLocation) {
- if (isAPIv2Ready) {
- pos = 0;
- } else {
- var x = lightLocation.position.x;
- var y = lightLocation.position.y;
- var z = lightLocation.position.z;
-
- var xval = (x < 0) ? "left" : "right";
- if (z != 1 && x >= -0.25 && x <= 0.25) xval = "";
- switch (z) {
- case 1: // top / Ceiling height
- pos = "top" + xval;
- break;
- case 0: // middle / TV height
- pos = (xval == "" && y >= 0.75) ? "bottom" : xval + "middle";
- break;
- case -1: // bottom / Ground height
- pos = xval + "bottom";
- break;
- }
- }
- }
- }
-
- var options = "";
- for (var opt in lightOptions) {
- var val = lightOptions[opt];
- var txt = (val != 'entire' && val != 'disabled') ? 'conf_leds_layout_cl_' : 'wiz_ids_';
- options += '' + $.i18n(txt + val) + ' ';
- }
-
- $('.lidsb').append(createTableRow([id + ' (' + lightName + ')', ''
- + options
- + ' ', '' + $.i18n('wiz_hue_blinkblue', id) + ' ']));
- }
-
- if (!isEntertainmentReady) {
- $('.hue_sel_watch').on("change", function () {
- var cC = 0;
- for (var key in hueLights) {
- if ($('#hue_' + key).val() != "disabled") {
- cC++;
- }
- }
-
- (cC == 0 || window.readOnlyMode) ? $('#btn_wiz_save').prop("disabled", true) : $('#btn_wiz_save').prop("disabled", false);
- });
- }
- $('.hue_sel_watch').trigger('change');
- }
- else {
- var txt = '' + $.i18n('wiz_hue_noids') + '
';
- $('#wizp2_body').append(txt);
- }
- }
-}
-
-function abortConnection(UserInterval) {
- clearInterval(UserInterval);
- $('#wizp1').toggle(false);
- $('#wizp2').toggle(true);
- $('#wizp3').toggle(false);
- $("#wiz_hue_usrstate").html($.i18n('wiz_hue_failure_connection'));
-}
-
-//****************************
-// Wizard Yeelight
-//****************************
-var lights = null;
-function startWizardYeelight(e) {
- //create html
-
- var yeelight_title = 'wiz_yeelight_title';
- var yeelight_intro1 = 'wiz_yeelight_intro1';
-
- $('#wiz_header').html(' ' + $.i18n(yeelight_title));
- $('#wizp1_body').html('' + $.i18n(yeelight_title) + ' ' + $.i18n(yeelight_intro1) + '
');
-
- $('#wizp1_footer').html(' '
- + $.i18n('general_btn_continue') + ' '
- + $.i18n('general_btn_cancel') + ' ');
-
- $('#wizp2_body').html('
');
-
- $('#wh_topcontainer').append('
');
-
- $('#wizp2_body').append('' + $.i18n('wiz_yeelight_desc2') + '
');
-
- createTable("lidsh", "lidsb", "yee_ids_t");
- $('.lidsh').append(createTableRow([$.i18n('edt_dev_spec_lights_title'), $.i18n('wiz_pos'), $.i18n('wiz_identify')], true));
- $('#wizp2_footer').html(' '
- + $.i18n('general_btn_save') + ' '
- + $.i18n('general_btn_cancel') + '');
-
- if (getStorage("darkMode") == "on")
- $('#wizard_logo').attr("src", 'img/hyperion/logo_negativ.png');
-
- //open modal
- $("#wizard_modal").modal({ backdrop: "static", keyboard: false, show: true });
-
- //listen for continue
- $('#btn_wiz_cont').off().on('click', function () {
- beginWizardYeelight();
- $('#wizp1').toggle(false);
- $('#wizp2').toggle(true);
- });
-}
-
-function beginWizardYeelight() {
- lights = [];
- configuredLights = conf_editor.getEditor("root.specificOptions.lights").getValue();
-
- discover_yeelight_lights();
-
- $('#btn_wiz_save').off().on("click", function () {
- var yeelightLedConfig = [];
- var finalLights = [];
-
- //create yeelight led config
- for (var key in lights) {
- if ($('#yee_' + key).val() !== "disabled") {
-
- var name = lights[key].name;
- // Set Name to layout-position, if empty
- if (name === "") {
- name = lights[key].host;
- }
-
- finalLights.push(lights[key]);
-
- var idx_content = assignLightPos($('#yee_' + key).val(), name);
- yeelightLedConfig.push(JSON.parse(JSON.stringify(idx_content)));
- }
- }
-
- //LED layout
- window.serverConfig.leds = yeelightLedConfig;
-
- //LED device config
- var currentDeviceType = window.serverConfig.device.type;
-
- //Start with a clean configuration
- var d = {};
-
- d.type = 'yeelight';
- d.hardwareLedCount = finalLights.length;
- d.colorOrder = conf_editor.getEditor("root.generalOptions.colorOrder").getValue();
- d.colorModel = parseInt(conf_editor.getEditor("root.specificOptions.colorModel").getValue());
-
- d.transEffect = parseInt(conf_editor.getEditor("root.specificOptions.transEffect").getValue());
- d.transTime = parseInt(conf_editor.getEditor("root.specificOptions.transTime").getValue());
- d.extraTimeDarkness = parseInt(conf_editor.getEditor("root.specificOptions.extraTimeDarkness").getValue());
-
- d.brightnessMin = parseInt(conf_editor.getEditor("root.specificOptions.brightnessMin").getValue());
- d.brightnessSwitchOffOnMinimum = JSON.parse(conf_editor.getEditor("root.specificOptions.brightnessSwitchOffOnMinimum").getValue());
- d.brightnessMax = parseInt(conf_editor.getEditor("root.specificOptions.brightnessMax").getValue());
- d.brightnessFactor = parseFloat(conf_editor.getEditor("root.specificOptions.brightnessFactor").getValue());
-
- d.latchTime = parseInt(conf_editor.getEditor("root.specificOptions.latchTime").getValue());;
- d.debugLevel = parseInt(conf_editor.getEditor("root.specificOptions.debugLevel").getValue());
-
- d.lights = finalLights;
-
- window.serverConfig.device = d;
-
- if (currentDeviceType !== d.type) {
- //smoothing off, if new device
- window.serverConfig.smoothing = { enable: false };
- }
-
- requestWriteConfig(window.serverConfig, true);
- resetWizard();
- });
-
- $('#btn_wiz_abort').off().on('click', resetWizard);
-}
-
-async function discover_yeelight_lights() {
- var light = {};
- // Get discovered lights
- const res = await requestLedDeviceDiscovery('yeelight');
-
- // TODO: error case unhandled
- // res can be: false (timeout) or res.error (not found)
- if (res && !res.error) {
- const r = res.info;
-
- var discoveryMethod = "ssdp";
- if (res.info.discoveryMethod) {
- discoveryMethod = res.info.discoveryMethod;
- }
-
- // Process devices returned by discovery
- for (const device of r.devices) {
- if (device.hostname !== "") {
- if (getHostInLights(device.hostname).length === 0) {
- var light = {};
-
-
-
- if (discoveryMethod === "ssdp") {
- //Create a valid hostname
- if (device.domain) {
- light.host += '.' + device.domain;
- }
- } else {
- light.host = device.service;
- light.name = device.name;
- }
- light.port = device.port;
-
- if (device.txt) {
- light.model = device.txt.md;
- //Yeelight does not provide correct API port with mDNS response, use default one
- light.port = 55443;
- }
- else {
- light.name = device.other.name;
- light.model = device.other.model;
- }
- lights.push(light);
- }
- }
- }
-
- // Add additional items from configuration
- for (var keyConfig in configuredLights) {
- var host = configuredLights[keyConfig].host;
-
- //In case port has been explicitly provided, overwrite port given as part of hostname
- if (configuredLights[keyConfig].port !== 0)
- port = configuredLights[keyConfig].port;
-
- if (host !== "")
- if (getHostInLights(host).length === 0) {
- var light = {};
- light.host = host;
- light.port = port;
- light.name = configuredLights[keyConfig].name;
- light.model = "color4";
- lights.push(light);
- }
- }
-
- assign_yeelight_lights();
- }
-}
-
-function assign_yeelight_lights() {
- // Model mappings, see https://www.home-assistant.io/integrations/yeelight/
- var models = ['color', 'color1', 'YLDP02YL', 'YLDP02YL', 'color2', 'YLDP06YL', 'color4', 'YLDP13YL', 'color6', 'YLDP13AYL', 'colorb', "YLDP005", 'colorc', "YLDP004-A", 'stripe', 'YLDD04YL', 'strip1', 'YLDD01YL', 'YLDD02YL', 'strip4', 'YLDD05YL', 'strip6', 'YLDD05YL'];
-
- // If records are left for configuration
- if (Object.keys(lights).length > 0) {
- $('#wh_topcontainer').toggle(false);
- $('#yee_ids_t, #btn_wiz_save').toggle(true);
-
- var lightOptions = [
- "top", "topleft", "topright",
- "bottom", "bottomleft", "bottomright",
- "left", "lefttop", "leftmiddle", "leftbottom",
- "right", "righttop", "rightmiddle", "rightbottom",
- "entire",
- "lightPosTopLeft112", "lightPosTopLeftNewMid", "lightPosTopLeft121",
- "lightPosBottomLeft14", "lightPosBottomLeft12", "lightPosBottomLeft34", "lightPosBottomLeft11",
- "lightPosBottomLeft112", "lightPosBottomLeftNewMid", "lightPosBottomLeft121"
- ];
-
- lightOptions.unshift("disabled");
-
- $('.lidsb').html("");
- var pos = "";
-
- for (var lightid in lights) {
- var lightHostname = lights[lightid].host;
- var lightPort = lights[lightid].port;
- var lightName = lights[lightid].name;
-
- if (lightName === "")
- lightName = $.i18n('edt_dev_spec_lights_itemtitle') + '(' + lightHostname + ')';
-
- var options = "";
- for (var opt in lightOptions) {
- var val = lightOptions[opt];
- var txt = (val !== 'entire' && val !== 'disabled') ? 'conf_leds_layout_cl_' : 'wiz_ids_';
- options += '' + $.i18n(txt + val) + ' ';
- }
-
- var enabled = 'enabled';
- if (!models.includes(lights[lightid].model)) {
- var enabled = 'disabled';
- options = '' + $.i18n('wiz_yeelight_unsupported') + ' ';
- }
-
- $('.lidsb').append(createTableRow([(parseInt(lightid, 10) + 1) + '. ' + lightName, ''
- + options
- + ' ', ''
- + $.i18n('wiz_identify') + ' ']));
- }
-
- $('.yee_sel_watch').on("change", function () {
- var cC = 0;
- for (var key in lights) {
- if ($('#yee_' + key).val() !== "disabled") {
- cC++;
- }
- }
-
- if (cC === 0 || window.readOnlyMode)
- $('#btn_wiz_save').prop("disabled", true);
- else
- $('#btn_wiz_save').prop("disabled", false);
- });
- $('.yee_sel_watch').trigger('change');
- }
- else {
- var noLightsTxt = '' + $.i18n('wiz_noLights', 'Yeelights') + '
';
- $('#wizp2_body').append(noLightsTxt);
- }
-}
-
-async function getProperties_yeelight(host, port) {
- let params = { host: host, port: port };
-
- const res = await requestLedDeviceProperties('yeelight', params);
-
- // TODO: error case unhandled
- // res can be: false (timeout) or res.error (not found)
- if (res && !res.error) {
- const r = res.info
- console.log("Yeelight properties: ", r);
- }
-}
-
-async function identify_yeelight_device(host, port) {
-
- var disabled = $('#btn_wiz_save').is(':disabled');
-
- // Take care that new record cannot be save during background process
- $('#btn_wiz_save').prop('disabled', true);
-
- let params = { host: host, port: port };
- await requestLedDeviceIdentification("yeelight", params);
-
- if (!window.readOnlyMode) {
- $('#btn_wiz_save').prop('disabled', disabled);
- }
-}
-
-//****************************
-// Wizard AtmoOrb
-//****************************
-var lights = null;
-function startWizardAtmoOrb(e) {
- //create html
-
- var atmoorb_title = 'wiz_atmoorb_title';
- var atmoorb_intro1 = 'wiz_atmoorb_intro1';
-
- $('#wiz_header').html(' ' + $.i18n(atmoorb_title));
- $('#wizp1_body').html('' + $.i18n(atmoorb_title) + ' ' + $.i18n(atmoorb_intro1) + '
');
-
- $('#wizp1_footer').html(' '
- + $.i18n('general_btn_continue') + ' '
- + $.i18n('general_btn_cancel') + ' ');
-
- $('#wizp2_body').html('
');
-
- $('#wh_topcontainer').append('
');
-
- $('#wizp2_body').append('' + $.i18n('wiz_atmoorb_desc2') + '
');
-
- createTable("lidsh", "lidsb", "orb_ids_t");
- $('.lidsh').append(createTableRow([$.i18n('edt_dev_spec_lights_title'), $.i18n('wiz_pos'), $.i18n('wiz_identify')], true));
- $('#wizp2_footer').html(' '
- + $.i18n('general_btn_save') + ' '
- + $.i18n('general_btn_cancel') + '');
-
- if (getStorage("darkMode") == "on")
- $('#wizard_logo').attr("src", 'img/hyperion/logo_negativ.png');
-
- //open modal
- $("#wizard_modal").modal({ backdrop: "static", keyboard: false, show: true });
-
- //listen for continue
- $('#btn_wiz_cont').off().on('click', function () {
- beginWizardAtmoOrb();
- $('#wizp1').toggle(false);
- $('#wizp2').toggle(true);
- });
-}
-
-function beginWizardAtmoOrb() {
- lights = [];
- configuredLights = [];
-
- var configruedOrbIds = conf_editor.getEditor("root.specificOptions.orbIds").getValue().trim();
- if (configruedOrbIds.length !== 0) {
- configuredLights = configruedOrbIds.split(",").map(Number);
- }
-
- var multiCastGroup = conf_editor.getEditor("root.specificOptions.host").getValue();
- var multiCastPort = parseInt(conf_editor.getEditor("root.specificOptions.port").getValue());
-
- discover_atmoorb_lights(multiCastGroup, multiCastPort);
-
- $('#btn_wiz_save').off().on("click", function () {
- var atmoorbLedConfig = [];
- var finalLights = [];
-
- //create atmoorb led config
- for (var key in lights) {
- if ($('#orb_' + key).val() !== "disabled") {
- // Set Name to layout-position, if empty
- if (lights[key].name === "") {
- lights[key].name = $.i18n('conf_leds_layout_cl_' + $('#orb_' + key).val());
- }
-
- finalLights.push(lights[key].id);
-
- var name = lights[key].id;
- if (lights[key].host !== "")
- name += ':' + lights[key].host;
-
- var idx_content = assignLightPos($('#orb_' + key).val(), name);
- atmoorbLedConfig.push(JSON.parse(JSON.stringify(idx_content)));
- }
- }
-
- //LED layout
- window.serverConfig.leds = atmoorbLedConfig;
-
- //LED device config
- //Start with a clean configuration
- var d = {};
-
- d.type = 'atmoorb';
- d.hardwareLedCount = finalLights.length;
- d.colorOrder = conf_editor.getEditor("root.generalOptions.colorOrder").getValue();
-
- d.orbIds = finalLights.toString();
- d.useOrbSmoothing = (eV("useOrbSmoothing") == true);
-
- d.host = conf_editor.getEditor("root.specificOptions.host").getValue();
- d.port = parseInt(conf_editor.getEditor("root.specificOptions.port").getValue());
- d.latchTime = parseInt(conf_editor.getEditor("root.specificOptions.latchTime").getValue());;
-
- window.serverConfig.device = d;
-
- requestWriteConfig(window.serverConfig, true);
- resetWizard();
- });
-
- $('#btn_wiz_abort').off().on('click', resetWizard);
-}
-
-async function discover_atmoorb_lights(multiCastGroup, multiCastPort) {
- var light = {};
-
- var params = {};
- if (multiCastGroup !== "") {
- params.multiCastGroup = multiCastGroup;
- }
-
- if (multiCastPort !== 0) {
- params.multiCastPort = multiCastPort;
- }
-
- // Get discovered lights
- const res = await requestLedDeviceDiscovery('atmoorb', params);
-
- // TODO: error case unhandled
- // res can be: false (timeout) or res.error (not found)
- if (res && !res.error) {
- const r = res.info;
-
- // Process devices returned by discovery
- for (const device of r.devices) {
- if (device.id !== "") {
- if (getIdInLights(device.id).length === 0) {
- var light = {};
- light.id = device.id;
- light.ip = device.ip;
- light.host = device.hostname;
- lights.push(light);
- }
- }
- }
-
- // Add additional items from configuration
- for (const keyConfig in configuredLights) {
- if (configuredLights[keyConfig] !== "" && !isNaN(configuredLights[keyConfig])) {
- if (getIdInLights(configuredLights[keyConfig]).length === 0) {
- var light = {};
- light.id = configuredLights[keyConfig];
- light.ip = "";
- light.host = "";
- lights.push(light);
- }
- }
- }
-
- lights.sort((a, b) => (a.id > b.id) ? 1 : -1);
-
- assign_atmoorb_lights();
- }
-}
-
-function assign_atmoorb_lights() {
- // If records are left for configuration
- if (Object.keys(lights).length > 0) {
- $('#wh_topcontainer').toggle(false);
- $('#orb_ids_t, #btn_wiz_save').toggle(true);
-
- var lightOptions = [
- "top", "topleft", "topright",
- "bottom", "bottomleft", "bottomright",
- "left", "lefttop", "leftmiddle", "leftbottom",
- "right", "righttop", "rightmiddle", "rightbottom",
- "entire",
- "lightPosTopLeft112", "lightPosTopLeftNewMid", "lightPosTopLeft121",
- "lightPosBottomLeft14", "lightPosBottomLeft12", "lightPosBottomLeft34", "lightPosBottomLeft11",
- "lightPosBottomLeft112", "lightPosBottomLeftNewMid", "lightPosBottomLeft121"
- ];
-
- lightOptions.unshift("disabled");
-
- $('.lidsb').html("");
- var pos = "";
-
- for (var lightid in lights) {
- var orbId = lights[lightid].id;
- var orbIp = lights[lightid].ip;
- var orbHostname = lights[lightid].host;
-
- if (orbHostname === "")
- orbHostname = $.i18n('edt_dev_spec_lights_itemtitle');
-
- var options = "";
- for (var opt in lightOptions) {
- var val = lightOptions[opt];
- var txt = (val !== 'entire' && val !== 'disabled') ? 'conf_leds_layout_cl_' : 'wiz_ids_';
- options += '' + $.i18n(txt + val) + ' ';
- }
-
- var enabled = 'enabled';
- if (orbId < 1 || orbId > 255) {
- enabled = 'disabled';
- options = '' + $.i18n('wiz_atmoorb_unsupported') + ' ';
- }
-
- var lightAnnotation = "";
- if (orbIp !== "") {
- lightAnnotation = ': ' + orbIp + ' (' + orbHostname + ')';
- }
-
- $('.lidsb').append(createTableRow([orbId + lightAnnotation, ''
- + options
- + ' ', ''
- + $.i18n('wiz_identify_light', orbId) + ' ']));
- }
-
- $('.orb_sel_watch').on("change", function () {
- var cC = 0;
- for (var key in lights) {
- if ($('#orb_' + key).val() !== "disabled") {
- cC++;
- }
- }
- if (cC === 0 || window.readOnlyMode)
- $('#btn_wiz_save').prop("disabled", true);
- else
- $('#btn_wiz_save').prop("disabled", false);
- });
- $('.orb_sel_watch').trigger('change');
- }
- else {
- var noLightsTxt = '' + $.i18n('wiz_noLights', 'AtmoOrbs') + '
';
- $('#wizp2_body').append(noLightsTxt);
- }
-}
-
-async function identify_atmoorb_device(orbId) {
- var disabled = $('#btn_wiz_save').is(':disabled');
-
- // Take care that new record cannot be save during background process
- $('#btn_wiz_save').prop('disabled', true);
-
- let params = { id: orbId };
- await requestLedDeviceIdentification("atmoorb", params);
-
- if (!window.readOnlyMode) {
- $('#btn_wiz_save').prop('disabled', disabled);
- }
-}
-
-//****************************
-// Nanoleaf Token Wizard
-//****************************
-var lights = null;
-function startWizardNanoleafUserAuth(e) {
- //create html
- var nanoleaf_user_auth_title = 'wiz_nanoleaf_user_auth_title';
- var nanoleaf_user_auth_intro = 'wiz_nanoleaf_user_auth_intro';
-
- $('#wiz_header').html(' ' + $.i18n(nanoleaf_user_auth_title));
- $('#wizp1_body').html('' + $.i18n(nanoleaf_user_auth_title) + ' ' + $.i18n(nanoleaf_user_auth_intro) + '
');
-
- $('#wizp1_footer').html(' '
- + $.i18n('general_btn_continue') + ' '
- + $.i18n('general_btn_cancel') + ' ');
-
- $('#wizp3_body').html('' + $.i18n('wiz_nanoleaf_press_onoff_button') + ' ');
-
- if (getStorage("darkMode") == "on")
- $('#wizard_logo').attr("src", 'img/hyperion/logo_negativ.png');
-
- //open modal
- $("#wizard_modal").modal({ backdrop: "static", keyboard: false, show: true });
-
- //listen for continue
- $('#btn_wiz_cont').off().on('click', function () {
- createNanoleafUserAuthorization();
- $('#wizp1').toggle(false);
- $('#wizp3').toggle(true);
- });
-}
-
-function createNanoleafUserAuthorization() {
- var host = conf_editor.getEditor("root.specificOptions.host").getValue();
-
- let params = { host: host };
-
- var retryTime = 30;
- var retryInterval = 2;
-
- var UserInterval = setInterval(function () {
-
- $('#wizp1').toggle(false);
- $('#wizp3').toggle(true);
-
- (async () => {
-
- retryTime -= retryInterval;
- $("#connectionTime").html(retryTime);
- if (retryTime <= 0) {
- abortConnection(UserInterval);
- clearInterval(UserInterval);
-
- showNotification('warning', $.i18n('wiz_nanoleaf_failure_auth_token'), $.i18n('wiz_nanoleaf_failure_auth_token_t'));
-
- resetWizard(true);
- }
- else {
- const res = await requestLedDeviceAddAuthorization('nanoleaf', params);
- if (res && !res.error) {
- var response = res.info;
-
- if (jQuery.isEmptyObject(response)) {
- debugMessage(retryTime + ": Power On/Off button not pressed or device not reachable");
- } else {
- $('#wizp1').toggle(false);
- $('#wizp3').toggle(false);
-
- var token = response.auth_token;
- if (token != 'undefined') {
- conf_editor.getEditor("root.specificOptions.token").setValue(token);
- }
- clearInterval(UserInterval);
- resetWizard(true);
- }
- } else {
- $('#wizp1').toggle(false);
- $('#wizp3').toggle(false);
- clearInterval(UserInterval);
- resetWizard(true);
- }
- }
- })();
-
- }, retryInterval * 1000);
-}
-
diff --git a/assets/webconfig/js/wizards/LedDevice_atmoorb.js b/assets/webconfig/js/wizards/LedDevice_atmoorb.js
new file mode 100644
index 00000000..67d9bd5a
--- /dev/null
+++ b/assets/webconfig/js/wizards/LedDevice_atmoorb.js
@@ -0,0 +1,283 @@
+//****************************
+// Wizard AtmoOrb
+//****************************
+
+import { ledDeviceWizardUtils as utils } from './LedDevice_utils.js';
+
+const atmoorbWizard = (() => {
+
+ const lights = [];
+ let configuredLights = [];
+
+ function getIdInLights(id) {
+ return lights.filter(
+ function (lights) {
+ return lights.id === id
+ }
+ );
+ }
+
+ function begin() {
+
+ const configruedOrbIds = conf_editor.getEditor("root.specificOptions.orbIds").getValue().trim();
+ if (configruedOrbIds.length !== 0) {
+ configuredLights = configruedOrbIds.split(",").map(Number);
+ }
+
+ const multiCastGroup = conf_editor.getEditor("root.specificOptions.host").getValue();
+ const multiCastPort = parseInt(conf_editor.getEditor("root.specificOptions.port").getValue());
+
+ discover(multiCastGroup, multiCastPort);
+
+ $('#btn_wiz_save').off().on("click", function () {
+ let ledConfig = [];
+ let finalLights = [];
+
+ //create atmoorb led config
+ for (let key in lights) {
+ if ($('#orb_' + key).val() !== "disabled") {
+ // Set Name to layout-position, if empty
+ if (lights[key].name === "") {
+ lights[key].name = $.i18n('conf_leds_layout_cl_' + $('#orb_' + key).val());
+ }
+
+ finalLights.push(lights[key].id);
+
+ let name = lights[key].id;
+ if (lights[key].host !== "")
+ name += ':' + lights[key].host;
+
+ const idx_content = utils.assignLightPos($('#orb_' + key).val(), name);
+ ledConfig.push(JSON.parse(JSON.stringify(idx_content)));
+ }
+ }
+
+ //LED layout
+ window.serverConfig.leds = ledConfig;
+
+ //LED device config
+ //Start with a clean configuration
+ let d = {};
+
+ d.type = 'atmoorb';
+ d.hardwareLedCount = finalLights.length;
+ d.colorOrder = conf_editor.getEditor("root.generalOptions.colorOrder").getValue();
+
+ d.orbIds = finalLights.toString();
+ d.useOrbSmoothing = utils.eV("useOrbSmoothing");
+
+ d.host = conf_editor.getEditor("root.specificOptions.host").getValue();
+ d.port = parseInt(conf_editor.getEditor("root.specificOptions.port").getValue());
+ d.latchTime = parseInt(conf_editor.getEditor("root.specificOptions.latchTime").getValue());;
+
+ window.serverConfig.device = d;
+
+ requestWriteConfig(window.serverConfig, true);
+ resetWizard();
+ });
+
+ $('#btn_wiz_abort').off().on('click', resetWizard);
+ }
+
+ async function discover(multiCastGroup, multiCastPort) {
+ let params = {};
+ if (multiCastGroup !== "") {
+ params.multiCastGroup = multiCastGroup;
+ }
+
+ if (multiCastPort !== 0) {
+ params.multiCastPort = multiCastPort;
+ }
+
+ // Get discovered lights
+ const res = await requestLedDeviceDiscovery('atmoorb', params);
+ if (res && !res.error) {
+ const r = res.info;
+
+ // Process devices returned by discovery
+ processDiscoveredDevices(r.devices);
+
+ // Add additional items from configuration
+ for (const configuredLight of configuredLights) {
+ processConfiguredLight(configuredLight);
+ }
+
+ sortLightsById();
+ assign_lights();
+ }
+ }
+
+ function processDiscoveredDevices(devices) {
+ for (const device of devices) {
+ if (device.id !== "" && getIdInLights(device.id).length === 0) {
+ const light = {
+ id: device.id,
+ ip: device.ip,
+ host: device.hostname
+ };
+ lights.push(light);
+ }
+ }
+ }
+
+ function processConfiguredLight(configuredLight) {
+ if (configuredLight !== "" && !isNaN(configuredLight)) {
+ if (getIdInLights(configuredLight).length === 0) {
+ const light = {
+ id: configuredLight,
+ ip: "",
+ host: ""
+ };
+ lights.push(light);
+ }
+ }
+ }
+
+ function attachIdentifyButtonEvent() {
+ // Use event delegation to handle clicks on buttons with class "btn-identify"
+ $('#wizp2_body').on('click', '.btn-identify', function () {
+ const orbId = $(this).data('orb-id');
+ identify(orbId);
+ });
+ }
+
+ function sortLightsById() {
+ lights.sort((a, b) => (a.id > b.id) ? 1 : -1);
+ }
+
+ function assign_lights() {
+ // If records are left for configuration
+ if (Object.keys(lights).length > 0) {
+ $('#wh_topcontainer').toggle(false);
+ $('#orb_ids_t, #btn_wiz_save').toggle(true);
+
+ const lightOptions = [
+ "top", "topleft", "topright",
+ "bottom", "bottomleft", "bottomright",
+ "left", "lefttop", "leftmiddle", "leftbottom",
+ "right", "righttop", "rightmiddle", "rightbottom",
+ "entire",
+ "lightPosTopLeft112", "lightPosTopLeftNewMid", "lightPosTopLeft121",
+ "lightPosBottomLeft14", "lightPosBottomLeft12", "lightPosBottomLeft34", "lightPosBottomLeft11",
+ "lightPosBottomLeft112", "lightPosBottomLeftNewMid", "lightPosBottomLeft121"
+ ];
+
+ lightOptions.unshift("disabled");
+
+ $('.lidsb').html("");
+ let pos = "";
+
+ for (const lightid in lights) {
+ const orbId = lights[lightid].id;
+ const orbIp = lights[lightid].ip;
+ let orbHostname = lights[lightid].host;
+
+ if (orbHostname === "")
+ orbHostname = $.i18n('edt_dev_spec_lights_itemtitle');
+
+ let options = "";
+ for (const opt in lightOptions) {
+ const val = lightOptions[opt];
+ const txt = (val !== 'entire' && val !== 'disabled') ? 'conf_leds_layout_cl_' : 'wiz_ids_';
+ options += '' + $.i18n(txt + val) + ' ';
+ }
+
+ let enabled = 'enabled';
+ if (orbId < 1 || orbId > 255) {
+ enabled = 'disabled';
+ options = '' + $.i18n('wiz_atmoorb_unsupported') + ' ';
+ }
+
+ let lightAnnotation = "";
+ if (orbIp !== "") {
+ lightAnnotation = ': ' + orbIp + ' (' + orbHostname + ')';
+ }
+
+ $('.lidsb').append(createTableRow([orbId + lightAnnotation, ''
+ + options
+ + ' ', ''
+ + $.i18n('wiz_identify_light', orbId) + ' ']));
+ }
+ attachIdentifyButtonEvent();
+
+ $('.orb_sel_watch').on("change", function () {
+ let cC = 0;
+ for (const key in lights) {
+ if ($('#orb_' + key).val() !== "disabled") {
+ cC++;
+ }
+ }
+ if (cC === 0 || window.readOnlyMode)
+ $('#btn_wiz_save').prop("disabled", true);
+ else
+ $('#btn_wiz_save').prop("disabled", false);
+ });
+ $('.orb_sel_watch').trigger('change');
+ }
+ else {
+ const noLightsTxt = '' + $.i18n('wiz_noLights', 'AtmoOrbs') + '
';
+ $('#wizp2_body').append(noLightsTxt);
+ }
+
+
+ }
+
+ async function identify(orbId) {
+ const disabled = $('#btn_wiz_save').is(':disabled');
+
+ // Take care that new record cannot be save during background process
+ $('#btn_wiz_save').prop('disabled', true);
+
+ const params = { id: orbId };
+ await requestLedDeviceIdentification("atmoorb", params);
+
+ if (!window.readOnlyMode) {
+ $('#btn_wiz_save').prop('disabled', disabled);
+ }
+ }
+
+ return {
+ start: function (e) {
+
+ //create html
+ const atmoorb_title = 'wiz_atmoorb_title';
+ const atmoorb_intro1 = 'wiz_atmoorb_intro1';
+
+ $('#wiz_header').html(' ' + $.i18n(atmoorb_title));
+ $('#wizp1_body').html('' + $.i18n(atmoorb_title) + ' ' + $.i18n(atmoorb_intro1) + '
');
+
+ $('#wizp1_footer').html(' '
+ + $.i18n('general_btn_continue') + ' '
+ + $.i18n('general_btn_cancel') + ' ');
+
+ $('#wizp2_body').html('
');
+
+ $('#wh_topcontainer').append('
');
+
+ $('#wizp2_body').append('' + $.i18n('wiz_atmoorb_desc2') + '
');
+
+ createTable("lidsh", "lidsb", "orb_ids_t");
+ $('.lidsh').append(createTableRow([$.i18n('edt_dev_spec_lights_title'), $.i18n('wiz_pos'), $.i18n('wiz_identify')], true));
+ $('#wizp2_footer').html(' '
+ + $.i18n('general_btn_save') + ' '
+ + $.i18n('general_btn_cancel') + '');
+
+ if (getStorage("darkMode") == "on")
+ $('#wizard_logo').attr("src", 'img/hyperion/logo_negativ.png');
+
+ //open modal
+ $("#wizard_modal").modal({ backdrop: "static", keyboard: false, show: true });
+
+ //listen for continue
+ $('#btn_wiz_cont').off().on('click', function () {
+ begin();
+ $('#wizp1').toggle(false);
+ $('#wizp2').toggle(true);
+ });
+ }
+ };
+})();
+
+export { atmoorbWizard };
diff --git a/assets/webconfig/js/wizards/LedDevice_nanoleaf.js b/assets/webconfig/js/wizards/LedDevice_nanoleaf.js
new file mode 100644
index 00000000..83220003
--- /dev/null
+++ b/assets/webconfig/js/wizards/LedDevice_nanoleaf.js
@@ -0,0 +1,94 @@
+//****************************
+// Wizard Nanoleaf
+//****************************
+
+const nanoleafWizard = (() => {
+
+ const retryInterval = 2;
+
+ async function createNanoleafUserAuthorization() {
+ const host = conf_editor.getEditor("root.specificOptions.host").getValue();
+ const params = { host };
+ let retryTime = 30;
+
+ const UserInterval = setInterval(async function () {
+ retryTime -= retryInterval;
+ $("#connectionTime").html(retryTime);
+
+ if (retryTime <= 0) {
+ handleTimeout();
+ } else {
+ const res = await requestLedDeviceAddAuthorization('nanoleaf', params);
+ handleResponse(res);
+ }
+ }, retryInterval * 1000);
+
+ function handleTimeout() {
+ clearInterval(UserInterval);
+ showNotification(
+ 'warning',
+ $.i18n('wiz_nanoleaf_failure_auth_token'),
+ $.i18n('wiz_nanoleaf_failure_auth_token_t')
+ );
+ resetWizard(true);
+ }
+
+ function handleResponse(res) {
+ if (res && !res.error) {
+ const response = res.info;
+ if (jQuery.isEmptyObject(response)) {
+ debugMessage(`${retryTime}: Power On/Off button not pressed or device not reachable`);
+ } else {
+ const token = response.auth_token;
+ if (token !== 'undefined') {
+ conf_editor.getEditor("root.specificOptions.token").setValue(token);
+ }
+ clearInterval(UserInterval);
+ resetWizard(true);
+ }
+ } else {
+ clearInterval(UserInterval);
+ resetWizard(true);
+ }
+ }
+ }
+
+ return {
+ start: function () {
+ const nanoleaf_user_auth_title = 'wiz_nanoleaf_user_auth_title';
+ const nanoleaf_user_auth_intro = 'wiz_nanoleaf_user_auth_intro';
+
+ $('#wiz_header').html(
+ ` ${$.i18n(nanoleaf_user_auth_title)}`
+ );
+ $('#wizp1_body').html(
+ `${$.i18n(nanoleaf_user_auth_title)} ${$.i18n(nanoleaf_user_auth_intro)}
`
+ );
+ $('#wizp1_footer').html(
+ ` ${$.i18n('general_btn_continue')} ${$.i18n('general_btn_cancel')} `
+ );
+ $('#wizp3_body').html(
+ `${$.i18n('wiz_nanoleaf_press_onoff_button')} `
+ );
+
+ if (getStorage("darkMode") == "on") {
+ $('#wizard_logo').attr("src", 'img/hyperion/logo_negativ.png');
+ }
+
+ $("#wizard_modal").modal({
+ backdrop: "static",
+ keyboard: false,
+ show: true
+ });
+
+ $('#btn_wiz_cont').off().on('click', function () {
+ createNanoleafUserAuthorization();
+ $('#wizp1').toggle(false);
+ $('#wizp3').toggle(true);
+ });
+ }
+ };
+})();
+
+export { nanoleafWizard };
+
diff --git a/assets/webconfig/js/wizards/LedDevice_philipshue.js b/assets/webconfig/js/wizards/LedDevice_philipshue.js
new file mode 100644
index 00000000..92f1c173
--- /dev/null
+++ b/assets/webconfig/js/wizards/LedDevice_philipshue.js
@@ -0,0 +1,988 @@
+//****************************
+// Wizard Philips Hue
+//****************************
+
+import { ledDeviceWizardUtils as utils } from './LedDevice_utils.js';
+
+const philipshueWizard = (() => {
+
+ // External properties, 2-dimensional arry of [ledType][key]
+ let devicesProperties = {};
+
+ let hueIPs = [];
+ let hueIPsinc = 0;
+ let hueLights = [];
+ let hueEntertainmentConfigs = [];
+ let hueEntertainmentServices = [];
+ let groupLights = [];
+ let groupChannels = [];
+ let groupLightsLocations = [];
+ let isAPIv2Ready = true;
+ let isEntertainmentReady = true;
+
+ function checkHueBridge(cb, hueUser) {
+ const usr = (typeof hueUser != "undefined") ? hueUser : 'config';
+ if (usr === 'config') {
+ $('#wiz_hue_discovered').html("");
+ }
+
+ if (hueIPs[hueIPsinc]) {
+ const host = hueIPs[hueIPsinc].host;
+ const port = hueIPs[hueIPsinc].port;
+
+ if (usr != '') {
+ getProperties(cb, decodeURIComponent(host), port, usr);
+ }
+ else {
+ cb(false, usr);
+ }
+
+ if (isAPIv2Ready) {
+ $('#port').val(443);
+ }
+ }
+ }
+
+ function checkBridgeResult(reply, usr) {
+ if (reply) {
+ //abort checking, first reachable result is used
+ $('#wiz_hue_ipstate').html("");
+ $('#host').val(hueIPs[hueIPsinc].host)
+ $('#port').val(hueIPs[hueIPsinc].port)
+
+ $('#usrcont').toggle(true);
+
+ checkHueBridge(checkUserResult, $('#user').val());
+ }
+ else {
+ $('#usrcont').toggle(false);
+ $('#wiz_hue_ipstate').html($.i18n('wiz_hue_failure_ip'));
+ }
+ };
+
+ function checkUserResult(reply, username) {
+ $('#usrcont').toggle(true);
+
+ let hue_create_user = 'wiz_hue_e_create_user';
+ if (!isEntertainmentReady) {
+ hue_create_user = 'wiz_hue_create_user';
+ $('#hue_client_key_r').toggle(false);
+ } else {
+ $('#hue_client_key_r').toggle(true);
+ }
+
+ $('#wiz_hue_create_user').text($.i18n(hue_create_user));
+ $('#wiz_hue_create_user').toggle(true);
+
+ if (reply) {
+ $('#user').val(username);
+
+ if (isEntertainmentReady && $('#clientkey').val() == "") {
+ $('#wiz_hue_usrstate').html($.i18n('wiz_hue_e_clientkey_needed'));
+ $('#wiz_hue_create_user').toggle(true);
+ } else {
+ $('#wiz_hue_usrstate').html("");
+ $('#wiz_hue_create_user').toggle(false);
+
+ if (isEntertainmentReady) {
+ $('#hue_id_headline').text($.i18n('wiz_hue_e_desc3'));
+ $('#hue_grp_ids_t').toggle(true);
+
+ get_hue_groups(username);
+
+ } else {
+ $('#hue_id_headline').text($.i18n('wiz_hue_desc2'));
+ $('#hue_grp_ids_t').toggle(false);
+
+ get_hue_lights(username);
+
+ }
+ }
+ }
+ else {
+ //abort checking, first reachable result is used
+ $('#wiz_hue_usrstate').html($.i18n('wiz_hue_failure_user'));
+ $('#wiz_hue_create_user').toggle(true);
+ }
+ };
+
+ function useGroupId(id, username) {
+ $('#groupId').val(hueEntertainmentConfigs[id].id);
+ if (isAPIv2Ready) {
+ const group = hueEntertainmentConfigs[id];
+
+ groupLights = [];
+ for (const light of group.light_services) {
+ groupLights.push(light.rid);
+ }
+
+ groupChannels = [];
+ for (const channel of group.channels) {
+ groupChannels.push(channel);
+ }
+
+ groupLightsLocations = [];
+ for (const location of group.locations.service_locations) {
+ groupLightsLocations.push(location);
+ }
+ } else {
+ //Ensure ligthIDs are strings
+ groupLights = hueEntertainmentConfigs[id].lights.map(num => {
+ return String(num);
+ });
+
+ const lightLocations = hueEntertainmentConfigs[id].locations;
+ for (const locationID in lightLocations) {
+ let lightLocation = {};
+
+ let position = {
+ x: lightLocations[locationID][0],
+ y: lightLocations[locationID][1],
+ z: lightLocations[locationID][2]
+ };
+ lightLocation.position = position;
+
+ groupLightsLocations.push(lightLocation);
+ }
+ }
+
+ get_hue_lights(username);
+ }
+
+ function assignLightEntertainmentPos(isFocusCenter, position, name, id) {
+
+ let x = position.x;
+ let z = position.z;
+
+ if (isFocusCenter) {
+ // Map lights as in centered range -0.5 to 0.5
+ if (x < -0.5) {
+ x = -0.5;
+ } else if (x > 0.5) {
+ x = 0.5;
+ }
+ if (z < -0.5) {
+ z = -0.5;
+ } else if (z > 0.5) {
+ z = 0.5;
+ }
+ } else {
+ // Map lights as in full range -1 to 1
+ x /= 2;
+ z /= 2;
+ }
+
+ const h = x + 0.5;
+ const v = -z + 0.5;
+
+ const hmin = h - 0.05;
+ const hmax = h + 0.05;
+ const vmin = v - 0.05;
+ const vmax = v + 0.05;
+
+ let layoutObject = {
+ hmin: hmin < 0 ? 0 : hmin,
+ hmax: hmax > 1 ? 1 : hmax,
+ vmin: vmin < 0 ? 0 : vmin,
+ vmax: vmax > 1 ? 1 : vmax,
+ name: name
+ };
+
+ if (id !== undefined && id !== null) {
+ layoutObject.name += "_" + id;
+ }
+ return layoutObject;
+ }
+
+ function assignSegmentedLightPos(segment, position, name) {
+ let layoutObjects = [];
+
+ let segTotalLength = 0;
+ for (const key in segment) {
+
+ segTotalLength += segment[key].length;
+ }
+
+ let min;
+ let max;
+ let horizontal = true;
+
+ let layoutObject = utils.assignLightPos(position, name);
+ if (position === "left" || position === "right") {
+ // vertical distribution
+ min = layoutObject.vmin;
+ max = layoutObject.vmax;
+ horizontal = false;
+
+ } else {
+ // horizontal distribution
+ min = layoutObject.hmin;
+ max = layoutObject.hmax;
+ }
+
+ const step = (max - min) / segTotalLength;
+ let start = min;
+
+ for (const key in segment) {
+ min = start;
+ max = round(start + segment[key].length * step);
+
+ if (horizontal) {
+ layoutObject.hmin = min;
+ layoutObject.hmax = max;
+ } else {
+ layoutObject.vmin = min;
+ layoutObject.vmax = max;
+ }
+ layoutObject.name = name + "_" + key;
+ layoutObjects.push(JSON.parse(JSON.stringify(layoutObject)));
+
+ start = max;
+ }
+
+ return layoutObjects;
+ }
+
+ function updateBridgeDetails(properties) {
+ const ledDeviceProperties = properties.config;
+
+ if (!jQuery.isEmptyObject(ledDeviceProperties)) {
+ isEntertainmentReady = properties.isEntertainmentReady;
+ isAPIv2Ready = properties.isAPIv2Ready;
+
+ if (ledDeviceProperties.name && ledDeviceProperties.bridgeid && ledDeviceProperties.modelid) {
+ $('#wiz_hue_discovered').html(
+ "Bridge: " + ledDeviceProperties.name +
+ ", Modelid: " + ledDeviceProperties.modelid +
+ ", Firmware: " + ledDeviceProperties.swversion + " " +
+ "API-Version: " + ledDeviceProperties.apiversion +
+ ", Entertainment: " + (isEntertainmentReady ? "✓" : "-") +
+ ", APIv2: " + (isAPIv2Ready ? "✓" : "-")
+ );
+ }
+ }
+ }
+
+ async function discover() {
+ $('#wiz_hue_ipstate').html($.i18n('edt_dev_spec_devices_discovery_inprogress'));
+
+ // $('#wiz_hue_discovered').html("")
+ const res = await requestLedDeviceDiscovery('philipshue');
+ if (res && !res.error) {
+ const r = res.info;
+
+ // Process devices returned by discovery
+ if (r.devices.length == 0) {
+ $('#wiz_hue_ipstate').html($.i18n('wiz_hue_failure_ip'));
+ $('#wiz_hue_discovered').html("")
+ }
+ else {
+ hueIPs = [];
+ hueIPsinc = 0;
+
+ let discoveryMethod = "ssdp";
+ if (res.info.discoveryMethod) {
+ discoveryMethod = res.info.discoveryMethod;
+ }
+
+ for (const device of r.devices) {
+ if (device) {
+ let host;
+ let port;
+ if (discoveryMethod === "ssdp") {
+ if (device.hostname && device.domain) {
+ host = device.hostname + "." + device.domain;
+ port = device.port;
+ } else {
+ host = device.ip;
+ port = device.port;
+ }
+ } else {
+ host = device.service;
+ port = device.port;
+ }
+ if (host) {
+
+ if (!hueIPs.some(item => item.host === host)) {
+ hueIPs.push({ host: host, port: port });
+ }
+ }
+ }
+ }
+
+ $('#wiz_hue_ipstate').html("");
+ $('#host').val(hueIPs[hueIPsinc].host)
+ $('#port').val(hueIPs[hueIPsinc].port)
+
+ $('#hue_bridge_select').html("");
+
+ for (const key in hueIPs) {
+ $('#hue_bridge_select').append(createSelOpt(key, hueIPs[key].host));
+ }
+
+ $('.hue_bridge_sel_watch').on("click", function () {
+ hueIPsinc = $(this).val();
+
+ const name = $("#hue_bridge_select option:selected").text();
+ $('#host').val(name);
+ $('#port').val(hueIPs[hueIPsinc].port)
+
+ const usr = $('#user').val();
+ if (usr != "") {
+ checkHueBridge(checkUserResult, usr);
+ } else {
+ checkHueBridge(checkBridgeResult);
+ }
+ });
+
+ $('.hue_bridge_sel_watch').click();
+ }
+ }
+ }
+
+ async function getProperties(cb, hostAddress, port, username, resourceFilter) {
+ let params = { host: hostAddress, username: username, filter: resourceFilter };
+ if (port !== 'undefined') {
+ params.port = parseInt(port);
+ }
+
+ const ledType = 'philipshue';
+ const key = hostAddress;
+
+ //Create ledType cache entry
+ if (!devicesProperties[ledType]) {
+ devicesProperties[ledType] = {};
+ }
+
+ // Use device's properties, if properties in chache
+ if (devicesProperties[ledType][key] && devicesProperties[ledType][key][username]) {
+ updateBridgeDetails(devicesProperties[ledType][key]);
+ cb(true, username);
+ } else {
+ const res = await requestLedDeviceProperties(ledType, params);
+ if (res && !res.error) {
+ const ledDeviceProperties = res.info.properties;
+ if (!jQuery.isEmptyObject(ledDeviceProperties)) {
+
+ devicesProperties[ledType][key] = {};
+ devicesProperties[ledType][key][username] = ledDeviceProperties;
+
+ isAPIv2Ready = res.info.isAPIv2Ready;
+ devicesProperties[ledType][key].isAPIv2Ready = isAPIv2Ready;
+ isEntertainmentReady = res.info.isEntertainmentReady;
+ devicesProperties[ledType][key].isEntertainmentReady = isEntertainmentReady;
+
+ updateBridgeDetails(devicesProperties[ledType][key]);
+ if (username === "config") {
+ cb(true);
+ } else {
+ cb(true, username);
+ }
+ } else {
+ cb(false, username);
+ }
+ } else {
+ cb(false, username);
+ }
+ }
+ }
+
+ async function identify(hostAddress, port, username, name, id, id_v1) {
+ const disabled = $('#btn_wiz_save').is(':disabled');
+ // Take care that new record cannot be save during background process
+ $('#btn_wiz_save').prop('disabled', true);
+
+ let params = { host: decodeURIComponent(hostAddress), username: username, lightName: decodeURIComponent(name), lightId: id, lightId_v1: id_v1 };
+
+ if (port !== 'undefined') {
+ params.port = parseInt(port);
+ }
+
+ await requestLedDeviceIdentification('philipshue', params);
+
+ if (!window.readOnlyMode) {
+ $('#btn_wiz_save').prop('disabled', disabled);
+ }
+ }
+
+ function begin() {
+ const usr = utils.eV("username");
+ if (usr != "") {
+ $('#user').val(usr);
+ }
+
+ const clkey = utils.eV("clientkey");
+ if (clkey != "") {
+ $('#clientkey').val(clkey);
+ }
+
+ //check if host is empty/reachable/search for bridge
+ if (utils.eV("host") == "") {
+ hueIPs = [];
+ hueIPsinc = 0;
+
+ discover();
+ }
+ else {
+ const host = utils.eV("host");
+ $('#host').val(host);
+
+ const port = utils.eV("port");
+ if (port > 0) {
+ $('#port').val(port);
+ }
+ else {
+ $('#port').val('');
+ }
+ hueIPs.push({ host: host, port: port });
+
+ if (usr != "") {
+ checkHueBridge(checkUserResult, usr);
+ } else {
+ checkHueBridge(checkBridgeResult);
+ }
+ }
+
+ $('#retry_bridge').off().on('click', function () {
+ const host = $('#host').val();
+ const port = parseInt($('#port').val());
+
+ if (host != "") {
+
+ const idx = hueIPs.findIndex(item => item.host === host && item.port === port);
+ if (idx === -1) {
+ hueIPs.push({ host: host, port: port });
+ hueIPsinc = hueIPs.length - 1;
+ } else {
+ hueIPsinc = idx;
+ }
+ }
+ else {
+ discover();
+ }
+
+ const usr = $('#user').val();
+ if (usr != "") {
+ checkHueBridge(checkUserResult, usr);
+ } else {
+ checkHueBridge(checkBridgeResult);
+ }
+ });
+
+ $('#retry_usr').off().on('click', function () {
+ checkHueBridge(checkUserResult, $('#user').val());
+ });
+
+ $('#wiz_hue_create_user').off().on('click', function () {
+ createHueUser();
+ });
+ $('#btn_wiz_save').off().on("click", function () {
+ let hueLedConfig = [];
+ let finalLightIds = [];
+ let channelNumber = 0;
+
+ //create hue led config
+ for (const key in groupLights) {
+ const lightId = groupLights[key];
+
+ if ($('#hue_' + lightId).val() != "disabled") {
+ finalLightIds.push(lightId);
+
+ let lightName;
+ if (isAPIv2Ready) {
+ const light = hueLights.find(light => light.id === lightId);
+ lightName = light.metadata.name;
+ } else {
+ lightName = hueLights[lightId].name;
+ }
+
+ const position = $('#hue_' + lightId).val();
+ const lightIdx = groupLights.indexOf(lightId);
+ const lightLocation = groupLightsLocations[lightIdx];
+
+ let serviceID;
+ if (isAPIv2Ready) {
+ serviceID = lightLocation.service.rid;
+ }
+
+ if (position.startsWith("entertainment")) {
+
+ // Layout per entertainment area definition at bridge
+ let isFocusCenter = false;
+ if (position === "entertainment_center") {
+ isFocusCenter = true;
+ }
+
+ if (isAPIv2Ready) {
+
+ groupChannels.forEach((channel) => {
+ if (channel.members[0].service.rid === serviceID) {
+ const layoutObject = assignLightEntertainmentPos(isFocusCenter, channel.position, lightName, channel.channel_id);
+ hueLedConfig.push(JSON.parse(JSON.stringify(layoutObject)));
+ ++channelNumber;
+ }
+ });
+ } else {
+ const layoutObject = assignLightEntertainmentPos(isFocusCenter, lightLocation.position, lightName);
+ hueLedConfig.push(JSON.parse(JSON.stringify(layoutObject)));
+ }
+ }
+ else {
+ // Layout per manual settings
+ let maxSegments = 1;
+
+ if (isAPIv2Ready) {
+ const service = hueEntertainmentServices.find(service => service.id === serviceID);
+ maxSegments = service.segments.max_segments;
+ }
+
+ if (maxSegments > 1) {
+ const segment = service.segments.segments;
+ const layoutObjects = assignSegmentedLightPos(segment, position, lightName);
+ hueLedConfig.push(...layoutObjects);
+ } else {
+ const layoutObject = utils.assignLightPos(position, lightName);
+ hueLedConfig.push(JSON.parse(JSON.stringify(layoutObject)));
+ }
+ channelNumber += maxSegments;
+ }
+ }
+ }
+
+ let sc = window.serverConfig;
+ sc.leds = hueLedConfig;
+
+ //Adjust gamma, brightness and compensation
+ let c = sc.color.channelAdjustment[0];
+ c.gammaBlue = 1.0;
+ c.gammaRed = 1.0;
+ c.gammaGreen = 1.0;
+ c.brightness = 100;
+ c.brightnessCompensation = 0;
+
+ //device config
+
+ //Start with a clean configuration
+ let d = {};
+ d.host = $('#host').val();
+ d.port = parseInt($('#port').val());
+ d.username = $('#user').val();
+ d.type = 'philipshue';
+ d.colorOrder = 'rgb';
+ d.lightIds = finalLightIds;
+ d.transitiontime = parseInt(utils.eV("transitiontime", 1));
+ d.restoreOriginalState = utils.eV("restoreOriginalState", false);
+ d.switchOffOnBlack = utils.eV("switchOffOnBlack", false);
+
+ d.blackLevel = parseFloat(utils.eV("blackLevel", 0.009));
+ d.onBlackTimeToPowerOff = parseInt(utils.eV("onBlackTimeToPowerOff", 600));
+ d.onBlackTimeToPowerOn = parseInt(utils.eV("onBlackTimeToPowerOn", 300));
+ d.brightnessFactor = parseFloat(utils.eV("brightnessFactor", 1));
+
+ d.clientkey = $('#clientkey').val();
+ d.groupId = $('#groupId').val();
+ d.blackLightsTimeout = parseInt(utils.eV("blackLightsTimeout", 5000));
+ d.brightnessMin = parseFloat(utils.eV("brightnessMin", 0));
+ d.brightnessMax = parseFloat(utils.eV("brightnessMax", 1));
+ d.brightnessThreshold = parseFloat(utils.eV("brightnessThreshold", 0.0001));
+ d.handshakeTimeoutMin = parseInt(utils.eV("handshakeTimeoutMin", 300));
+ d.handshakeTimeoutMax = parseInt(utils.eV("handshakeTimeoutMax", 1000));
+ d.verbose = utils.eV("verbose");
+
+ d.autoStart = conf_editor.getEditor("root.generalOptions.autoStart").getValue();
+ d.enableAttempts = parseInt(conf_editor.getEditor("root.generalOptions.enableAttempts").getValue());
+ d.enableAttemptsInterval = parseInt(conf_editor.getEditor("root.generalOptions.enableAttemptsInterval").getValue());
+
+ d.useEntertainmentAPI = isEntertainmentReady;
+ d.useAPIv2 = isAPIv2Ready;
+
+ if (isEntertainmentReady) {
+ d.hardwareLedCount = channelNumber;
+ if (window.serverConfig.device.type !== d.type) {
+ //smoothing on, if new device
+ sc.smoothing = { enable: true };
+ }
+ } else {
+ d.hardwareLedCount = finalLightIds.length;
+ d.verbose = false;
+ if (window.serverConfig.device.type !== d.type) {
+ //smoothing off, if new device
+ sc.smoothing = { enable: false };
+ }
+ }
+
+ window.serverConfig.device = d;
+
+ requestWriteConfig(sc, true);
+ resetWizard();
+ });
+
+ $('#btn_wiz_abort').off().on('click', resetWizard);
+ }
+
+ function createHueUser() {
+ const host = hueIPs[hueIPsinc].host;
+ const port = hueIPs[hueIPsinc].port;
+
+ let params = { host: host };
+ if (port !== 'undefined') {
+ params.port = parseInt(port);
+ }
+
+ let retryTime = 30;
+ const retryInterval = 2;
+
+ const UserInterval = setInterval(function () {
+
+ $('#wizp1').toggle(false);
+ $('#wizp2').toggle(false);
+ $('#wizp3').toggle(true);
+
+ (async () => {
+
+ retryTime -= retryInterval;
+ $("#connectionTime").html(retryTime);
+ if (retryTime <= 0) {
+ abortConnection(UserInterval);
+ clearInterval(UserInterval);
+ }
+ else {
+ const res = await requestLedDeviceAddAuthorization('philipshue', params);
+ if (res && !res.error) {
+ const response = res.info;
+
+ if (jQuery.isEmptyObject(response)) {
+ debugMessage(retryTime + ": link button not pressed or device not reachable");
+ } else {
+ $('#wizp1').toggle(false);
+ $('#wizp2').toggle(true);
+ $('#wizp3').toggle(false);
+
+ const username = response.username;
+ if (username != 'undefined') {
+ $('#user').val(username);
+ conf_editor.getEditor("root.specificOptions.username").setValue(username);
+ conf_editor.getEditor("root.specificOptions.host").setValue(host);
+ conf_editor.getEditor("root.specificOptions.port").setValue(port);
+ }
+
+ if (isEntertainmentReady) {
+ const clientkey = response.clientkey;
+ if (clientkey != 'undefined') {
+ $('#clientkey').val(clientkey);
+ conf_editor.getEditor("root.specificOptions.clientkey").setValue(clientkey);
+ }
+ }
+ checkHueBridge(checkUserResult, username);
+ clearInterval(UserInterval);
+ }
+ } else {
+ $('#wizp1').toggle(false);
+ $('#wizp2').toggle(true);
+ $('#wizp3').toggle(false);
+ clearInterval(UserInterval);
+ }
+ }
+ })();
+
+ }, retryInterval * 1000);
+ }
+
+ function get_hue_groups(username) {
+ const host = hueIPs[hueIPsinc].host;
+
+ if (devicesProperties['philipshue'][host] && devicesProperties['philipshue'][host][username]) {
+ const ledProperties = devicesProperties['philipshue'][host][username];
+
+ if (isAPIv2Ready) {
+ if (!jQuery.isEmptyObject(ledProperties.data)) {
+ if (Object.keys(ledProperties.data).length > 0) {
+ hueEntertainmentConfigs = ledProperties.data.filter(config => {
+ return config.type === "entertainment_configuration";
+ });
+ hueEntertainmentServices = ledProperties.data.filter(config => {
+ return (config.type === "entertainment" && config.renderer === true);
+ });
+ }
+ }
+ } else if (!jQuery.isEmptyObject(ledProperties.groups)) {
+ hueEntertainmentConfigs = [];
+ let hueGroups = ledProperties.groups;
+ for (const groupid in hueGroups) {
+ if (hueGroups[groupid].type == 'Entertainment') {
+ hueGroups[groupid].id = groupid;
+ hueEntertainmentConfigs.push(hueGroups[groupid]);
+ }
+ }
+ }
+
+ if (Object.keys(hueEntertainmentConfigs).length > 0) {
+
+ $('.lidsb').html("");
+ $('#wh_topcontainer').toggle(false);
+ $('#hue_grp_ids_t').toggle(true);
+
+ for (const groupid in hueEntertainmentConfigs) {
+ $('.gidsb').append(createTableRow([groupid + ' (' + hueEntertainmentConfigs[groupid].name + ')',
+ ''
+ + $.i18n('wiz_hue_e_use_group') + ' ']));
+ }
+ attachGroupButtonEvent();
+
+ } else {
+ noAPISupport('wiz_hue_e_noegrpids', username);
+ }
+ }
+ }
+ function attachIdentifyButtonEvent() {
+ $('#wizp2_body').on('click', '.btn-identify', function () {
+ const hostname = $(this).data('hostname');
+ const port = $(this).data('port');
+ const user = $(this).data('user');
+ const lightName = $(this).data('light-name');
+ const lightId = $(this).data('light-id');
+ const lightId_v1 = $(this).data('light-id-v1');
+
+ identify(hostname, port, user, lightName, lightId, lightId_v1);
+ });
+ }
+ function attachGroupButtonEvent() {
+ $('#wizp2_body').on('click', '.btn-group', function () {
+ const groupid = $(this).data('groupid');
+ const username = $(this).data('username');
+
+ useGroupId(groupid, username);
+ });
+ }
+
+ function noAPISupport(txt, username) {
+ showNotification('danger', $.i18n('wiz_hue_e_title'), $.i18n('wiz_hue_e_noapisupport_hint'));
+ conf_editor.getEditor("root.specificOptions.useEntertainmentAPI").setValue(false);
+ $("#root_specificOptions_useEntertainmentAPI").trigger("change");
+ $('#btn_wiz_holder').append('' + $.i18n('wiz_hue_e_noapisupport_hint') + '
');
+ $('#hue_grp_ids_t').toggle(false);
+ const errorMessage = txt ? $.i18n(txt) : $.i18n('wiz_hue_e_nogrpids');
+ $('' + errorMessage + ' ' + $.i18n('wiz_hue_e_noapisupport') + '
').insertBefore('#wizp2_body #hue_ids_t');
+ $('#hue_id_headline').html($.i18n('wiz_hue_desc2'));
+
+ get_hue_lights(username);
+ }
+
+ function get_hue_lights(username) {
+ const host = hueIPs[hueIPsinc].host;
+
+ if (devicesProperties['philipshue'][host] && devicesProperties['philipshue'][host][username]) {
+ const ledProperties = devicesProperties['philipshue'][host][username];
+
+ if (isAPIv2Ready) {
+ if (!jQuery.isEmptyObject(ledProperties.data)) {
+ if (Object.keys(ledProperties.data).length > 0) {
+ hueLights = ledProperties.data.filter(config => {
+ return config.type === "light";
+ });
+ }
+ }
+ } else if (!jQuery.isEmptyObject(ledProperties.lights)) {
+ hueLights = ledProperties.lights;
+ }
+
+ if (Object.keys(hueLights).length > 0) {
+ if (!isEntertainmentReady) {
+ $('#wh_topcontainer').toggle(false);
+ }
+ $('#hue_ids_t, #btn_wiz_save').toggle(true);
+
+ const lightOptions = [
+ "top", "topleft", "topright",
+ "bottom", "bottomleft", "bottomright",
+ "left", "lefttop", "leftmiddle", "leftbottom",
+ "right", "righttop", "rightmiddle", "rightbottom",
+ "entire",
+ "lightPosTopLeft112", "lightPosTopLeftNewMid", "lightPosTopLeft121",
+ "lightPosBottomLeft14", "lightPosBottomLeft12", "lightPosBottomLeft34", "lightPosBottomLeft11",
+ "lightPosBottomLeft112", "lightPosBottomLeftNewMid", "lightPosBottomLeft121"
+ ];
+
+ if (isEntertainmentReady) {
+ lightOptions.unshift("entertainment_center");
+ lightOptions.unshift("entertainment");
+ } else {
+ lightOptions.unshift("disabled");
+ groupLights = Object.keys(hueLights);
+ }
+
+ $('.lidsb').html("");
+
+ let pos = "";
+ for (const id in groupLights) {
+ const lightId = groupLights[id];
+ let lightId_v1 = "/lights/" + lightId;
+
+ let lightName;
+ if (isAPIv2Ready) {
+ const light = hueLights.find(light => light.id === lightId);
+ lightName = light.metadata.name;
+ lightId_v1 = light.id_v1;
+ } else {
+ lightName = hueLights[lightId].name;
+ }
+
+ if (isEntertainmentReady) {
+ let lightLocation = {};
+ lightLocation = groupLightsLocations[id];
+ if (lightLocation) {
+ if (isAPIv2Ready) {
+ pos = 0;
+ } else {
+ const x = lightLocation.position.x;
+ const y = lightLocation.position.y;
+ const z = lightLocation.position.z;
+
+ let xval = (x < 0) ? "left" : "right";
+ if (z != 1 && x >= -0.25 && x <= 0.25) xval = "";
+ switch (z) {
+ case 1: // top / Ceiling height
+ pos = "top" + xval;
+ break;
+ case 0: // middle / TV height
+ pos = (xval == "" && y >= 0.75) ? "bottom" : xval + "middle";
+ break;
+ case -1: // bottom / Ground height
+ pos = xval + "bottom";
+ break;
+ }
+ }
+ }
+ }
+
+ let options = "";
+ for (const opt in lightOptions) {
+ const val = lightOptions[opt];
+ const txt = (val != 'entire' && val != 'disabled') ? 'conf_leds_layout_cl_' : 'wiz_ids_';
+ options += '' + $.i18n(txt + val) + ' ';
+ }
+
+ $('.lidsb').append(createTableRow([id + ' (' + lightName + ')',
+ ''
+ + options
+ + ' ',
+ ''
+ + $.i18n('wiz_hue_blinkblue', id)
+ + ' ']));
+ }
+ attachIdentifyButtonEvent();
+
+ if (!isEntertainmentReady) {
+ $('.hue_sel_watch').on("change", function () {
+ let cC = 0;
+ for (const key in hueLights) {
+ if ($('#hue_' + key).val() != "disabled") {
+ cC++;
+ }
+ }
+
+ (cC == 0 || window.readOnlyMode) ? $('#btn_wiz_save').prop("disabled", true) : $('#btn_wiz_save').prop("disabled", false);
+ });
+ }
+ $('.hue_sel_watch').trigger('change');
+ }
+ else {
+ const txt = '' + $.i18n('wiz_hue_noids') + '
';
+ $('#wizp2_body').append(txt);
+ }
+ }
+ }
+
+ function abortConnection(UserInterval) {
+ clearInterval(UserInterval);
+ $('#wizp1').toggle(false);
+ $('#wizp2').toggle(true);
+ $('#wizp3').toggle(false);
+ $("#wiz_hue_usrstate").html($.i18n('wiz_hue_failure_connection'));
+ }
+
+ return {
+ start: function (e) {
+ //create html
+ const hue_title = 'wiz_hue_title';
+ const hue_intro1 = 'wiz_hue_e_intro1';
+ const hue_desc1 = 'wiz_hue_desc1';
+ const hue_create_user = 'wiz_hue_create_user';
+
+ $('#wiz_header').html(' ' + $.i18n(hue_title));
+ $('#wizp1_body').html('' + $.i18n(hue_title) + ' ' + $.i18n(hue_intro1) + '
');
+ $('#wizp1_footer').html(' ' + $.i18n('general_btn_continue') + ' ' + $.i18n('general_btn_cancel') + ' ');
+ $('#wizp2_body').html('
');
+
+ let topContainer_html = '' + $.i18n(hue_desc1) + '
' +
+ '' +
+ '
' +
+ '
' + $.i18n('wiz_hue_ip') + '
' +
+ '
' +
+ ' ' +
+ ' ' + ' ' + '
' +
+ '
';
+
+ if (storedAccess === 'expert') {
+ topContainer_html += '
';
+ }
+
+ topContainer_html += '
';
+ topContainer_html += '
';
+
+ $('#wh_topcontainer').append(topContainer_html);
+
+ $('#usrcont').append('' + $.i18n('wiz_hue_username') + '
' +
+ '
' +
+ ' '
+ );
+
+ $('#usrcont').append(' ');
+
+ $('#usrcont').append('
' +
+ ' ' + $.i18n(hue_create_user) + ' ');
+
+ $('#wizp2_body').append('' + $.i18n('wiz_hue_e_desc2') + '
');
+ createTable("gidsh", "gidsb", "hue_grp_ids_t");
+ $('.gidsh').append(createTableRow([$.i18n('edt_dev_spec_groupId_title'), ""], true));
+
+ $('#wizp2_body').append('' + $.i18n('wiz_hue_e_desc3') + '
');
+
+ createTable("lidsh", "lidsb", "hue_ids_t");
+ $('.lidsh').append(createTableRow([$.i18n('edt_dev_spec_lightid_title'), $.i18n('wiz_pos'), $.i18n('wiz_identify')], true));
+ $('#wizp2_footer').html(' ' + $.i18n('general_btn_save') + ' ' + $.i18n('general_btn_cancel') + ' ');
+ $('#wizp3_body').html('' + $.i18n('wiz_hue_press_link') + ' ');
+
+ if (getStorage("darkMode") == "on")
+ $('#wizard_logo').attr("src", 'img/hyperion/logo_negativ.png');
+
+ //open modal
+ $("#wizard_modal").modal({
+ backdrop: "static",
+ keyboard: false,
+ show: true
+ });
+
+ //listen for continue
+ $('#btn_wiz_cont').off().on('click', function () {
+ begin();
+ $('#wizp1').toggle(false);
+ $('#wizp2').toggle(true);
+ });
+ }
+ };
+})();
+
+export { philipshueWizard }
+
diff --git a/assets/webconfig/js/wizards/LedDevice_utils.js b/assets/webconfig/js/wizards/LedDevice_utils.js
new file mode 100644
index 00000000..1f3eab3e
--- /dev/null
+++ b/assets/webconfig/js/wizards/LedDevice_utils.js
@@ -0,0 +1,60 @@
+
+const ledDeviceWizardUtils = (() => {
+
+ // Layout positions
+ const positionMap = {
+ "top": { hmin: 0.15, hmax: 0.85, vmin: 0, vmax: 0.2 },
+ "topleft": { hmin: 0, hmax: 0.15, vmin: 0, vmax: 0.15 },
+ "topright": { hmin: 0.85, hmax: 1.0, vmin: 0, vmax: 0.15 },
+ "bottom": { hmin: 0.15, hmax: 0.85, vmin: 0.8, vmax: 1.0 },
+ "bottomleft": { hmin: 0, hmax: 0.15, vmin: 0.85, vmax: 1.0 },
+ "bottomright": { hmin: 0.85, hmax: 1.0, vmin: 0.85, vmax: 1.0 },
+ "left": { hmin: 0, hmax: 0.15, vmin: 0.15, vmax: 0.85 },
+ "lefttop": { hmin: 0, hmax: 0.15, vmin: 0, vmax: 0.5 },
+ "leftmiddle": { hmin: 0, hmax: 0.15, vmin: 0.25, vmax: 0.75 },
+ "leftbottom": { hmin: 0, hmax: 0.15, vmin: 0.5, vmax: 1.0 },
+ "right": { hmin: 0.85, hmax: 1.0, vmin: 0.15, vmax: 0.85 },
+ "righttop": { hmin: 0.85, hmax: 1.0, vmin: 0, vmax: 0.5 },
+ "rightmiddle": { hmin: 0.85, hmax: 1.0, vmin: 0.25, vmax: 0.75 },
+ "rightbottom": { hmin: 0.85, hmax: 1.0, vmin: 0.5, vmax: 1.0 },
+ "lightPosBottomLeft14": { hmin: 0, hmax: 0.25, vmin: 0.85, vmax: 1.0 },
+ "lightPosBottomLeft12": { hmin: 0.25, hmax: 0.5, vmin: 0.85, vmax: 1.0 },
+ "lightPosBottomLeft34": { hmin: 0.5, hmax: 0.75, vmin: 0.85, vmax: 1.0 },
+ "lightPosBottomLeft11": { hmin: 0.75, hmax: 1, vmin: 0.85, vmax: 1.0 },
+ "lightPosBottomLeft112": { hmin: 0, hmax: 0.5, vmin: 0.85, vmax: 1.0 },
+ "lightPosBottomLeft121": { hmin: 0.5, hmax: 1, vmin: 0.85, vmax: 1.0 },
+ "lightPosBottomLeftNewMid": { hmin: 0.25, hmax: 0.75, vmin: 0.85, vmax: 1.0 },
+ "lightPosTopLeft112": { hmin: 0, hmax: 0.5, vmin: 0, vmax: 0.15 },
+ "lightPosTopLeft121": { hmin: 0.5, hmax: 1, vmin: 0, vmax: 0.15 },
+ "lightPosTopLeftNewMid": { hmin: 0.25, hmax: 0.75, vmin: 0, vmax: 0.15 },
+ "lightPosEntire": { hmin: 0.0, hmax: 1.0, vmin: 0.0, vmax: 1.0 }
+ };
+
+ return {
+
+ //return editor Value
+ eV: function (vn, defaultVal = "") {
+ let editor = null;
+ if (vn) {
+ editor = conf_editor.getEditor("root.specificOptions." + vn);
+ }
+
+ if (editor === null) {
+ return defaultVal;
+ } else if (defaultVal !== "" && !isNaN(defaultVal) && isNaN(editor.getValue())) {
+ return defaultVal;
+ } else {
+ return editor.getValue();
+ }
+ },
+ assignLightPos: function (pos, name) {
+ // Retrieve the corresponding position object from the positionMap
+ const i = positionMap[pos] || positionMap["lightPosEntire"];
+ i.name = name;
+ return i;
+ }
+ };
+
+})();
+
+export { ledDeviceWizardUtils };
diff --git a/assets/webconfig/js/wizards/LedDevice_yeelight.js b/assets/webconfig/js/wizards/LedDevice_yeelight.js
new file mode 100644
index 00000000..4f53eb07
--- /dev/null
+++ b/assets/webconfig/js/wizards/LedDevice_yeelight.js
@@ -0,0 +1,300 @@
+//****************************
+// Wizard Yeelight
+//****************************
+
+import { ledDeviceWizardUtils as utils } from './LedDevice_utils.js';
+
+const yeelightWizard = (() => {
+
+ const lights = [];
+ let configuredLights = conf_editor.getEditor("root.specificOptions.lights").getValue();
+
+ function getHostInLights(hostname) {
+ return lights.filter(
+ function (lights) {
+ return lights.host === hostname
+ }
+ );
+ }
+
+ function begin() {
+ discover();
+
+ $('#btn_wiz_save').off().on("click", function () {
+ let ledConfig = [];
+ let finalLights = [];
+
+ //create yeelight led config
+ for (const key in lights) {
+ if ($('#yee_' + key).val() !== "disabled") {
+
+ let name = lights[key].name;
+ // Set Name to layout-position, if empty
+ if (name === "") {
+ name = lights[key].host;
+ }
+
+ finalLights.push(lights[key]);
+
+ const idx_content = utils.assignLightPos($('#yee_' + key).val(), name);
+ ledConfig.push(JSON.parse(JSON.stringify(idx_content)));
+ }
+ }
+
+ //LED layout
+ window.serverConfig.leds = ledConfig;
+
+ //LED device config
+ const currentDeviceType = window.serverConfig.device.type;
+
+ //Start with a clean configuration
+ let d = {};
+
+ d.type = 'yeelight';
+ d.hardwareLedCount = finalLights.length;
+ d.colorOrder = conf_editor.getEditor("root.generalOptions.colorOrder").getValue();
+ d.colorModel = parseInt(conf_editor.getEditor("root.specificOptions.colorModel").getValue());
+
+ d.transEffect = parseInt(conf_editor.getEditor("root.specificOptions.transEffect").getValue());
+ d.transTime = parseInt(conf_editor.getEditor("root.specificOptions.transTime").getValue());
+ d.extraTimeDarkness = parseInt(conf_editor.getEditor("root.specificOptions.extraTimeDarkness").getValue());
+
+ d.brightnessMin = parseInt(conf_editor.getEditor("root.specificOptions.brightnessMin").getValue());
+ d.brightnessSwitchOffOnMinimum = JSON.parse(conf_editor.getEditor("root.specificOptions.brightnessSwitchOffOnMinimum").getValue());
+ d.brightnessMax = parseInt(conf_editor.getEditor("root.specificOptions.brightnessMax").getValue());
+ d.brightnessFactor = parseFloat(conf_editor.getEditor("root.specificOptions.brightnessFactor").getValue());
+
+ d.latchTime = parseInt(conf_editor.getEditor("root.specificOptions.latchTime").getValue());;
+ d.debugLevel = parseInt(conf_editor.getEditor("root.specificOptions.debugLevel").getValue());
+
+ d.lights = finalLights;
+
+ window.serverConfig.device = d;
+
+ if (currentDeviceType !== d.type) {
+ //smoothing off, if new device
+ window.serverConfig.smoothing = { enable: false };
+ }
+
+ requestWriteConfig(window.serverConfig, true);
+ resetWizard();
+ });
+
+ $('#btn_wiz_abort').off().on('click', resetWizard);
+ }
+
+ async function discover() {
+ // Get discovered lights
+ const res = await requestLedDeviceDiscovery('yeelight');
+ if (res && !res.error) {
+ const r = res.info;
+
+ let discoveryMethod = "ssdp";
+ if (res.info.discoveryMethod) {
+ discoveryMethod = res.info.discoveryMethod;
+ }
+
+ // Process devices returned by discovery
+ for (const device of r.devices) {
+ if (device.hostname !== "") {
+ processDiscoverdDevice(device, discoveryMethod);
+ }
+ }
+
+ // Add additional items from configuration
+ for (const configuredLight of configuredLights) {
+ processConfiguredLight(configuredLight);
+ }
+
+ assign_lights();
+ }
+ }
+
+ function processDiscoverdDevice(device, discoveryMethod) {
+ if (getHostInLights(device.hostname).length > 0) {
+ return;
+ }
+
+ const light = {
+ host: device.hostname
+ };
+
+ if (discoveryMethod === "ssdp") {
+ if (device.domain) {
+ light.host += '.' + device.domain;
+ }
+ } else {
+ light.host = device.service;
+ light.name = device.name;
+ }
+
+ light.port = device.port;
+
+ if (device.txt) {
+ light.model = device.txt.md;
+ light.port = 55443; // Yeelight default port
+ } else {
+ light.name = device.other.name;
+ light.model = device.other.model;
+ }
+
+ lights.push(light);
+ }
+ function processConfiguredLight(configuredLight) {
+ const host = configuredLight.host;
+ let port = configuredLight.port || 0;
+
+ if (host !== "" && getHostInLights(host).length === 0) {
+ const light = {
+ host: host,
+ port: port,
+ name: configuredLight.name,
+ model: "color4"
+ };
+
+ lights.push(light);
+ }
+ }
+
+ function attachIdentifyButtonEvent() {
+ $('#wizp2_body').on('click', '.btn-identify', function () {
+ const hostname = $(this).data('hostname');
+ const port = $(this).data('port');
+ identify(hostname, port);
+ });
+ }
+
+ function assign_lights() {
+ // Model mappings, see https://www.home-assistant.io/integrations/yeelight/
+ const models = ['color', 'color1', 'YLDP02YL', 'YLDP02YL', 'color2', 'YLDP06YL', 'color4', 'YLDP13YL', 'color6', 'YLDP13AYL', 'colorb', "YLDP005", 'colorc', "YLDP004-A", 'stripe', 'YLDD04YL', 'strip1', 'YLDD01YL', 'YLDD02YL', 'strip4', 'YLDD05YL', 'strip6', 'YLDD05YL'];
+
+ // If records are left for configuration
+ if (Object.keys(lights).length > 0) {
+ $('#wh_topcontainer').toggle(false);
+ $('#yee_ids_t, #btn_wiz_save').toggle(true);
+
+ const lightOptions = [
+ "top", "topleft", "topright",
+ "bottom", "bottomleft", "bottomright",
+ "left", "lefttop", "leftmiddle", "leftbottom",
+ "right", "righttop", "rightmiddle", "rightbottom",
+ "entire",
+ "lightPosTopLeft112", "lightPosTopLeftNewMid", "lightPosTopLeft121",
+ "lightPosBottomLeft14", "lightPosBottomLeft12", "lightPosBottomLeft34", "lightPosBottomLeft11",
+ "lightPosBottomLeft112", "lightPosBottomLeftNewMid", "lightPosBottomLeft121"
+ ];
+
+ lightOptions.unshift("disabled");
+
+ $('.lidsb').html("");
+ let pos = "";
+
+ for (const lightid in lights) {
+ const lightHostname = lights[lightid].host;
+ const lightPort = lights[lightid].port;
+ let lightName = lights[lightid].name;
+
+ if (lightName === "")
+ lightName = $.i18n('edt_dev_spec_lights_itemtitle') + '(' + lightHostname + ')';
+
+ let options = "";
+ for (const opt in lightOptions) {
+ const val = lightOptions[opt];
+ const txt = (val !== 'entire' && val !== 'disabled') ? 'conf_leds_layout_cl_' : 'wiz_ids_';
+ options += '' + $.i18n(txt + val) + ' ';
+ }
+
+ let enabled = 'enabled';
+ if (!models.includes(lights[lightid].model)) {
+ enabled = 'disabled';
+ options = '' + $.i18n('wiz_yeelight_unsupported') + ' ';
+ }
+
+ $('.lidsb').append(createTableRow([(parseInt(lightid, 10) + 1) + '. ' + lightName, ''
+ + options
+ + ' ', ''
+ + $.i18n('wiz_identify') + ' ']));
+ }
+ attachIdentifyButtonEvent();
+
+ $('.yee_sel_watch').on("change", function () {
+ let cC = 0;
+ for (const key in lights) {
+ if ($('#yee_' + key).val() !== "disabled") {
+ cC++;
+ }
+ }
+
+ if (cC === 0 || window.readOnlyMode)
+ $('#btn_wiz_save').prop("disabled", true);
+ else
+ $('#btn_wiz_save').prop("disabled", false);
+ });
+ $('.yee_sel_watch').trigger('change');
+ }
+ else {
+ const noLightsTxt = '' + $.i18n('wiz_noLights', 'lights') + '
';
+ $('#wizp2_body').append(noLightsTxt);
+ }
+ }
+
+ async function identify(host, port) {
+
+ const disabled = $('#btn_wiz_save').is(':disabled');
+
+ // Take care that new record cannot be save during background process
+ $('#btn_wiz_save').prop('disabled', true);
+
+ const params = { host: host, port: port };
+ await requestLedDeviceIdentification("yeelight", params);
+
+ if (!window.readOnlyMode) {
+ $('#btn_wiz_save').prop('disabled', disabled);
+ }
+ }
+
+ return {
+ start: function (e) {
+ //create html
+ const yeelight_title = 'wiz_yeelight_title';
+ const yeelight_intro1 = 'wiz_yeelight_intro1';
+
+ $('#wiz_header').html(' ' + $.i18n(yeelight_title));
+ $('#wizp1_body').html('' + $.i18n(yeelight_title) + ' ' + $.i18n(yeelight_intro1) + '
');
+
+ $('#wizp1_footer').html(' '
+ + $.i18n('general_btn_continue') + ' '
+ + $.i18n('general_btn_cancel') + ' ');
+
+ $('#wizp2_body').html('
');
+
+ $('#wh_topcontainer').append('
');
+
+ $('#wizp2_body').append('' + $.i18n('wiz_yeelight_desc2') + '
');
+
+ createTable("lidsh", "lidsb", "yee_ids_t");
+ $('.lidsh').append(createTableRow([$.i18n('edt_dev_spec_lights_title'), $.i18n('wiz_pos'), $.i18n('wiz_identify')], true));
+ $('#wizp2_footer').html(' '
+ + $.i18n('general_btn_save') + ' '
+ + $.i18n('general_btn_cancel') + '');
+
+ if (getStorage("darkMode") == "on")
+ $('#wizard_logo').attr("src", 'img/hyperion/logo_negativ.png');
+
+ //open modal
+ $("#wizard_modal").modal({ backdrop: "static", keyboard: false, show: true });
+
+ //listen for continue
+ $('#btn_wiz_cont').off().on('click', function () {
+ begin();
+ $('#wizp1').toggle(false);
+ $('#wizp2').toggle(true);
+ });
+ }
+};
+}) ();
+
+export { yeelightWizard };
+
diff --git a/assets/webconfig/js/wizards/colorCalibrationKodiWizard.js b/assets/webconfig/js/wizards/colorCalibrationKodiWizard.js
new file mode 100644
index 00000000..7879d641
--- /dev/null
+++ b/assets/webconfig/js/wizards/colorCalibrationKodiWizard.js
@@ -0,0 +1,485 @@
+//****************************
+// Wizard Color calibration via Kodi
+//****************************
+const colorCalibrationKodiWizard = (() => {
+
+ let ws;
+ const defaultKodiPort = 9090;
+
+ let kodiAddress = document.location.hostname;
+ let kodiPort = defaultKodiPort;
+
+ const kodiUrl = new URL("ws://" + kodiAddress);
+ kodiUrl.port = kodiPort;
+ kodiUrl.pathname = "/jsonrpc/websocket";
+
+ let wiz_editor;
+ let colorLength;
+ let cobj;
+ let step = 0;
+ let withKodi = false;
+ let profile = 0;
+ let websAddress;
+ let imgAddress;
+ let picnr = 0;
+ let id = 1;
+ const vidAddress = "https://sourceforge.net/projects/hyperion-project/files/resources/vid/";
+ const availVideos = ["Sweet_Cocoon", "Caminandes_2_GranDillama", "Caminandes_3_Llamigos"];
+
+ if (getStorage("kodiAddress") != null) {
+
+ kodiAddress = getStorage("kodiAddress");
+ kodiUrl.host = kodiAddress;
+ }
+
+ if (getStorage("kodiPort") != null) {
+ kodiPort = getStorage("kodiPort");
+ kodiUrl.port = kodiPort;
+ }
+
+ $(window).on('beforeunload', function () {
+ closeWebSocket();
+ });
+
+ function closeWebSocket() {
+ // Check if the WebSocket is open
+ if (ws && ws.readyState === WebSocket.OPEN) {
+ ws.close();
+ }
+ }
+
+ function sendToKodi(type, content) {
+ let command;
+
+ switch (type) {
+ case "msg":
+ command = { "jsonrpc": "2.0", "method": "GUI.ShowNotification", "params": { "title": $.i18n('wiz_cc_title'), "message": content, "image": "info", "displaytime": 5000 }, "id": id };
+ break;
+ case "stop":
+ command = { "jsonrpc": "2.0", "method": "Player.Stop", "params": { "playerid": 2 }, "id": id };
+ break;
+ case "playP":
+ content = imgAddress + content + '.png';
+ command = { "jsonrpc": "2.0", "method": "Player.Open", "params": { "item": { "file": content } }, "id": id };
+ break;
+ case "playV":
+ content = vidAddress + content;
+ command = { "jsonrpc": "2.0", "method": "Player.Open", "params": { "item": { "file": content } }, "id": id };
+ break;
+ case "rotate":
+ command = { "jsonrpc": "2.0", "method": "Player.Rotate", "params": { "playerid": 2 }, "id": id };
+ break;
+ default:
+ console.error('Unknown Kodi command type: ', type);
+ }
+
+ if (ws.readyState === WebSocket.OPEN) {
+ ws.send(JSON.stringify(command));
+ ++id;
+ } else {
+ console.error('WebSocket connection is not open. Unable to send command.');
+ }
+ }
+
+ function performAction() {
+ let h;
+
+ if (step == 1) {
+ $('#wiz_cc_desc').html($.i18n('wiz_cc_chooseid'));
+ updateEditor(["id"]);
+ $('#btn_wiz_back').prop("disabled", true);
+ }
+ else
+ $('#btn_wiz_back').prop("disabled", false);
+
+ if (step == 2) {
+ updateEditor(["white"]);
+ h = $.i18n('wiz_cc_adjustit', $.i18n('edt_conf_color_white_title'));
+ if (withKodi) {
+ h += ' ' + $.i18n('wiz_cc_kodishould', $.i18n('edt_conf_color_white_title'));
+ sendToKodi('playP', "white");
+ }
+ else
+ h += ' ' + $.i18n('wiz_cc_lettvshow', $.i18n('edt_conf_color_white_title'));
+ $('#wiz_cc_desc').html(h);
+ }
+ if (step == 3) {
+ updateEditor(["gammaRed", "gammaGreen", "gammaBlue"]);
+ h = '' + $.i18n('wiz_cc_adjustgamma') + '
';
+ if (withKodi) {
+ sendToKodi('playP', "HGradient");
+ h += '' + $.i18n('wiz_cc_btn_switchpic') + ' ';
+ }
+ else
+ h += '' + $.i18n('wiz_cc_lettvshowm', "grey_1, grey_2, grey_3, HGradient, VGradient") + '
';
+ $('#wiz_cc_desc').html(h);
+ $('#wiz_cc_btn_sp').off().on('click', function () {
+ switchPicture(["VGradient", "grey_1", "grey_2", "grey_3", "HGradient"]);
+ });
+ }
+ if (step == 4) {
+ updateEditor(["red"]);
+ h = $.i18n('wiz_cc_adjustit', $.i18n('edt_conf_color_red_title'));
+ if (withKodi) {
+ h += ' ' + $.i18n('wiz_cc_kodishould', $.i18n('edt_conf_color_red_title'));
+ sendToKodi('playP', "red");
+ }
+ else
+ h += ' ' + $.i18n('wiz_cc_lettvshow', $.i18n('edt_conf_color_red_title'));
+ $('#wiz_cc_desc').html(h);
+ }
+ if (step == 5) {
+ updateEditor(["green"]);
+ h = $.i18n('wiz_cc_adjustit', $.i18n('edt_conf_color_green_title'));
+ if (withKodi) {
+ h += ' ' + $.i18n('wiz_cc_kodishould', $.i18n('edt_conf_color_green_title'));
+ sendToKodi('playP', "green");
+ }
+ else
+ h += ' ' + $.i18n('wiz_cc_lettvshow', $.i18n('edt_conf_color_green_title'));
+ $('#wiz_cc_desc').html(h);
+ }
+ if (step == 6) {
+ updateEditor(["blue"]);
+ h = $.i18n('wiz_cc_adjustit', $.i18n('edt_conf_color_blue_title'));
+ if (withKodi) {
+ h += ' ' + $.i18n('wiz_cc_kodishould', $.i18n('edt_conf_color_blue_title'));
+ sendToKodi('playP', "blue");
+ }
+ else
+ h += ' ' + $.i18n('wiz_cc_lettvshow', $.i18n('edt_conf_color_blue_title'));
+ $('#wiz_cc_desc').html(h);
+ }
+ if (step == 7) {
+ updateEditor(["cyan"]);
+ h = $.i18n('wiz_cc_adjustit', $.i18n('edt_conf_color_cyan_title'));
+ if (withKodi) {
+ h += ' ' + $.i18n('wiz_cc_kodishould', $.i18n('edt_conf_color_cyan_title'));
+ sendToKodi('playP', "cyan");
+ }
+ else
+ h += ' ' + $.i18n('wiz_cc_lettvshow', $.i18n('edt_conf_color_cyan_title'));
+ $('#wiz_cc_desc').html(h);
+ }
+ if (step == 8) {
+ updateEditor(["magenta"]);
+ h = $.i18n('wiz_cc_adjustit', $.i18n('edt_conf_color_magenta_title'));
+ if (withKodi) {
+ h += ' ' + $.i18n('wiz_cc_kodishould', $.i18n('edt_conf_color_magenta_title'));
+ sendToKodi('playP', "magenta");
+ }
+ else
+ h += ' ' + $.i18n('wiz_cc_lettvshow', $.i18n('edt_conf_color_magenta_title'));
+ $('#wiz_cc_desc').html(h);
+ }
+ if (step == 9) {
+ updateEditor(["yellow"]);
+ h = $.i18n('wiz_cc_adjustit', $.i18n('edt_conf_color_yellow_title'));
+ if (withKodi) {
+ h += ' ' + $.i18n('wiz_cc_kodishould', $.i18n('edt_conf_color_yellow_title'));
+ sendToKodi('playP', "yellow");
+ }
+ else
+ h += ' ' + $.i18n('wiz_cc_lettvshow', $.i18n('edt_conf_color_yellow_title'));
+ $('#wiz_cc_desc').html(h);
+ }
+ if (step == 10) {
+ updateEditor(["backlightThreshold", "backlightColored"]);
+ h = $.i18n('wiz_cc_backlight');
+ if (withKodi) {
+ h += ' ' + $.i18n('wiz_cc_kodishould', $.i18n('edt_conf_color_black_title'));
+ sendToKodi('playP', "black");
+ }
+ else
+ h += ' ' + $.i18n('wiz_cc_lettvshow', $.i18n('edt_conf_color_black_title'));
+ $('#wiz_cc_desc').html(h);
+ }
+ if (step == 11) {
+ updateEditor([""], true);
+ h = '' + $.i18n('wiz_cc_testintro') + '
';
+ if (withKodi) {
+ h += '' + $.i18n('wiz_cc_testintrok') + '
';
+ sendToKodi('stop');
+ availVideos.forEach(video => {
+ const txt = video.replace(/_/g, " ");
+ h += ` ${txt}
`;
+ });
+
+ h += ' ' + $.i18n('wiz_cc_btn_stop') + '
';
+ }
+ else
+ h += '' + $.i18n('wiz_cc_testintrowok') + ' ' + $.i18n('wiz_cc_link') + '
';
+ h += '' + $.i18n('wiz_cc_summary') + '
';
+ $('#wiz_cc_desc').html(h);
+
+ $('.videobtn').off().on('click', function (e) {
+ if (e.target.id == "stop")
+ sendToKodi("stop");
+ else
+ sendToKodi("playV", e.target.id + '.mp4');
+
+ $(this).prop("disabled", true);
+ setTimeout(function () { $('.videobtn').prop("disabled", false) }, 10000);
+ });
+
+ $('#btn_wiz_next').prop("disabled", true);
+ $('#btn_wiz_save').toggle(true);
+ window.readOnlyMode ? $('#btn_wiz_save').prop('disabled', true) : $('#btn_wiz_save').prop('disabled', false);
+ }
+ else {
+ $('#btn_wiz_next').prop("disabled", false);
+ $('#btn_wiz_save').toggle(false);
+ }
+ }
+
+
+
+ function switchPicture(pictures) {
+ if (typeof pictures[picnr] === 'undefined')
+ picnr = 0;
+
+ sendToKodi('playP', pictures[picnr]);
+ picnr++;
+ }
+
+
+ function initializeWebSocket(cb) {
+ if ("WebSocket" in window) {
+
+ if (kodiUrl.port === '') {
+ kodiUrl.port = defaultKodiPort;
+ }
+
+ if (!ws || ws.readyState !== WebSocket.OPEN) {
+
+ // Establish WebSocket connection
+ ws = new WebSocket(kodiUrl);
+
+ // WebSocket onopen event
+ ws.onopen = function (event) {
+ withKodi = true;
+ cb("opened");
+ };
+
+ // WebSocket onmessage event (handle incoming messages)
+ ws.onmessage = function (event) {
+ const response = JSON.parse(event.data);
+ if (response.method === "System.OnQuit") {
+ closeWebSocket();
+ } else if (response.result != undefined) {
+ if (response.result !== "OK") {
+ cb("error");
+ }
+ }
+ };
+
+ // WebSocket onerror event
+ ws.onerror = function (error) {
+ cb("error");
+ };
+
+ // WebSocket onclose event
+ ws.onclose = function (event) {
+ withKodi = false;
+ if (event.code === 1006) {
+ // Ignore error 1006 due to Kodi issue
+ console.log("WebSocket closed with error code 1006. Ignoring due to Kodi bug.");
+ }
+ else {
+ console.error("WebSocket closed with code:", event.code);
+ }
+ };
+ } else {
+ console.log("WebSocket connection is already open.");
+ }
+ }
+ else {
+ console.log("Kodi Access: WebSocket NOT supported by this browser");
+ cb("error");
+ }
+ }
+
+ function setupEventListeners() {
+ $('#btn_wiz_cancel').off().on('click', function () {
+ stop(true);
+ });
+ $('#wiz_cc_kodiip').off().on('change', function () {
+
+ kodiAddress = encodeURIComponent($(this).val().trim());
+
+ $('#kodi_status').html('');
+ if (kodiAddress !== "") {
+
+ if (!isValidHostnameOrIP(kodiAddress)) {
+
+ $('#kodi_status').html('' + $.i18n('edt_msgcust_error_hostname_ip') + '
');
+ withKodi = false;
+
+ } else {
+
+ if (isValidIPv6(kodiAddress)) {
+ kodiUrl.hostname = "[" + kodiAddress + "]";
+ } else {
+ kodiUrl.hostname = kodiAddress;
+ }
+
+ $('#kodi_status').html('' + $.i18n('wiz_cc_try_connect') + '
');
+ $('#btn_wiz_cont').prop('disabled', true);
+
+ closeWebSocket();
+ initializeWebSocket(function (cb) {
+
+ if (cb == "opened") {
+ setStorage("kodiAddress", kodiAddress);
+ setStorage("kodiPort", defaultKodiPort);
+
+ $('#kodi_status').html('' + $.i18n('wiz_cc_kodicon') + '
');
+ $('#btn_wiz_cont').prop('disabled', false);
+
+ if (withKodi) {
+ sendToKodi("msg", $.i18n('wiz_cc_kodimsg_start'));
+ }
+ }
+ else {
+ $('#kodi_status').html('' + $.i18n('wiz_cc_kodidiscon') + '
' + $.i18n('wiz_cc_kodidisconlink') + ' ' + $.i18n('wiz_cc_link') + '
');
+ withKodi = false;
+ }
+
+ $('#btn_wiz_cont').prop('disabled', false);
+ });
+ }
+ }
+ });
+
+ //listen for continue
+ $('#btn_wiz_cont').off().on('click', function () {
+ begin();
+ $('#wizp1').toggle(false);
+ $('#wizp2').toggle(true);
+ });
+ }
+
+ function init() {
+ colorLength = window.serverConfig.color.channelAdjustment;
+ cobj = window.schema.color.properties.channelAdjustment.items.properties;
+ websAddress = document.location.hostname + ':' + window.serverConfig.webConfig.port;
+ imgAddress = 'http://' + websAddress + '/img/cc/';
+ setStorage("wizardactive", true);
+ }
+
+ function initProfiles() {
+ //check profile count
+ if (colorLength.length > 1) {
+ $('#multi_cali').html('' + $.i18n('wiz_cc_morethanone') + '
');
+ for (let i = 0; i < colorLength.length; i++)
+ $('#wiz_select').append(createSelOpt(i, i + 1 + ' (' + colorLength[i].id + ')'));
+
+ $('#wiz_select').off().on('change', function () {
+ profile = $(this).val();
+ });
+ }
+ }
+
+ function createEditor() {
+ wiz_editor = createJsonEditor('editor_container_wiz', {
+ color: window.schema.color
+ }, true, true);
+
+ $('#editor_container_wiz h4').toggle(false);
+ $('#editor_container_wiz .btn-group').toggle(false);
+ $('#editor_container_wiz [data-schemapath="root.color.imageToLedMappingType"]').toggle(false);
+ $('#editor_container_wiz [data-schemapath="root.color.reducedPixelSetFactorFactor"]').toggle(false);
+ for (let i = 0; i < colorLength.length; i++)
+ $('#editor_container_wiz [data-schemapath*="root.color.channelAdjustment.' + i + '."]').toggle(false);
+ }
+ function updateEditor(el, all) {
+ for (let key in cobj) {
+ if (all === true || el[0] == key || el[1] == key || el[2] == key)
+ $('#editor_container_wiz [data-schemapath*=".' + profile + '.' + key + '"]').toggle(true);
+ else
+ $('#editor_container_wiz [data-schemapath*=".' + profile + '.' + key + '"]').toggle(false);
+ }
+ }
+
+ function stop(reload) {
+ if (withKodi) {
+ sendToKodi("stop");
+ }
+ closeWebSocket();
+ resetWizard(reload);
+ }
+
+ function begin() {
+ step = 0;
+
+ $('#btn_wiz_next').off().on('click', function () {
+ step++;
+ performAction();
+ });
+
+ $('#btn_wiz_back').off().on('click', function () {
+ step--;
+ performAction();
+ });
+
+ $('#btn_wiz_abort').off().on('click', function () {
+ stop(true);
+ });
+
+ $('#btn_wiz_save').off().on('click', function () {
+ requestWriteConfig(wiz_editor.getValue());
+ stop(true);
+ });
+
+ wiz_editor.on("change", function (e) {
+ const val = wiz_editor.getEditor('root.color.channelAdjustment.' + profile + '').getValue();
+ const temp = JSON.parse(JSON.stringify(val));
+ delete temp.leds
+ requestAdjustment(JSON.stringify(temp), "", true);
+ });
+
+ step++
+ performAction();
+ }
+
+ return {
+ start: function () {
+ //create html
+ $('#wiz_header').html(' ' + $.i18n('wiz_cc_title'));
+ $('#wizp1_body').html('' + $.i18n('wiz_cc_title') + ' ' +
+ '' + $.i18n('wiz_cc_intro1') + '
' +
+ '' + $.i18n('wiz_cc_kwebs') + ' ' +
+ ' ' +
+ ' '
+ );
+ $('#wizp1_footer').html('' + ' ' + $.i18n('general_btn_continue') + ' ' +
+ ' ' + $.i18n('general_btn_cancel') + ' '
+ );
+ $('#wizp2_body').html('
'
+ );
+ $('#wizp2_footer').html('' + ' ' + $.i18n('general_btn_back') + ' ' +
+ '' + $.i18n('general_btn_next') + ' ' + ' ' +
+ ' ' + $.i18n('general_btn_save') + ' ' +
+ ' ' + $.i18n('general_btn_cancel') + ' '
+ );
+
+ if (getStorage("darkMode") == "on")
+ $('#wizard_logo').prop("src", 'img/hyperion/logo_negativ.png');
+
+ //open modal
+ $("#wizard_modal").modal({
+ backdrop: "static",
+ keyboard: false,
+ show: true
+ });
+
+ setupEventListeners();
+ $('#wiz_cc_kodiip').trigger("change");
+ init();
+ initProfiles();
+ createEditor();
+ }
+ };
+})();
+
+export { colorCalibrationKodiWizard };
diff --git a/assets/webconfig/js/wizards/rgbByteOrderWizard.js b/assets/webconfig/js/wizards/rgbByteOrderWizard.js
new file mode 100644
index 00000000..5d612174
--- /dev/null
+++ b/assets/webconfig/js/wizards/rgbByteOrderWizard.js
@@ -0,0 +1,143 @@
+//****************************
+// Wizard RGB byte order
+//****************************
+
+const rgbByteOrderWizard = (() => {
+
+ let wIntveralId;
+ let new_rgb_order;
+
+ function changeColor() {
+ let color = $("#wiz_canv_color").css('background-color');
+
+ if (color == 'rgb(255, 0, 0)') {
+ $("#wiz_canv_color").css('background-color', 'rgb(0, 255, 0)');
+ requestSetColor('0', '255', '0');
+ }
+ else {
+ $("#wiz_canv_color").css('background-color', 'rgb(255, 0, 0)');
+ requestSetColor('255', '0', '0');
+ }
+ }
+
+
+ function stopWizardRGB(reload) {
+ console.log("stopWizardRGB - reload: ", reload);
+ clearInterval(wIntveralId);
+ resetWizard(reload);
+ }
+
+ function beginWizardRGB() {
+ $("#wiz_switchtime_select").off().on('change', function () {
+ clearInterval(wIntveralId);
+ const time = $("#wiz_switchtime_select").val();
+ wIntveralId = setInterval(function () { changeColor(); }, time * 1000);
+ });
+
+ $('.wselect').on("change", function () {
+ let rgb_order = window.serverConfig.device.colorOrder.split("");
+ const redS = $("#wiz_r_select").val();
+ const greenS = $("#wiz_g_select").val();
+ const blueS = rgb_order.toString().replace(/,/g, "").replace(redS, "").replace(greenS, "");
+
+ for (const color of rgb_order) {
+ if (redS == color)
+ $('#wiz_g_select option[value=' + color + ']').prop('disabled', true);
+ else
+ $('#wiz_g_select option[value=' + color + ']').prop('disabled', false);
+ if (greenS == color)
+ $('#wiz_r_select option[value=' + color + ']').prop('disabled', true);
+ else
+ $('#wiz_r_select option[value=' + color + ']').prop('disabled', false);
+ }
+
+ if (redS != 'null' && greenS != 'null') {
+ $('#btn_wiz_save').prop('disabled', false);
+
+ for (let i = 0; i < rgb_order.length; i++) {
+ if (rgb_order[i] == "r")
+ rgb_order[i] = redS;
+ else if (rgb_order[i] == "g")
+ rgb_order[i] = greenS;
+ else
+ rgb_order[i] = blueS;
+ }
+
+ rgb_order = rgb_order.toString().replace(/,/g, "");
+
+ if (redS == "r" && greenS == "g") {
+ $('#btn_wiz_save').toggle(false);
+ $('#btn_wiz_checkok').toggle(true);
+
+ window.readOnlyMode ? $('#btn_wiz_checkok').prop('disabled', true) : $('#btn_wiz_checkok').prop('disabled', false);
+ }
+ else {
+ $('#btn_wiz_save').toggle(true);
+ window.readOnlyMode ? $('#btn_wiz_save').prop('disabled', true) : $('#btn_wiz_save').prop('disabled', false);
+
+ $('#btn_wiz_checkok').toggle(false);
+ }
+ new_rgb_order = rgb_order;
+ }
+ else
+ $('#btn_wiz_save').prop('disabled', true);
+ });
+
+ $("#wiz_switchtime_select").append(createSelOpt('5', '5'), createSelOpt('10', '10'), createSelOpt('15', '15'), createSelOpt('30', '30'));
+ $("#wiz_switchtime_select").trigger('change');
+
+ $("#wiz_r_select").append(createSelOpt("null", ""), createSelOpt('r', $.i18n('general_col_red')), createSelOpt('g', $.i18n('general_col_green')), createSelOpt('b', $.i18n('general_col_blue')));
+ $("#wiz_g_select").html($("#wiz_r_select").html());
+ $("#wiz_r_select").trigger('change');
+
+ requestSetColor('255', '0', '0');
+ setTimeout(requestSetSource, 100, 'auto');
+ setStorage("wizardactive", true);
+
+ $('#btn_wiz_abort').off().on('click', function () { stopWizardRGB(true); });
+
+ $('#btn_wiz_checkok').off().on('click', function () {
+ showInfoDialog('success', "", $.i18n('infoDialog_wizrgb_text'));
+ stopWizardRGB();
+ });
+
+ $('#btn_wiz_save').off().on('click', function () {
+ stopWizardRGB();
+ window.serverConfig.device.colorOrder = new_rgb_order;
+ requestWriteConfig({ "device": window.serverConfig.device });
+ });
+ }
+
+ return {
+ start: function () {
+ //create html
+ $('#wiz_header').html(' ' + $.i18n('wiz_rgb_title'));
+ $('#wizp1_body').html('' + $.i18n('wiz_rgb_title') + ' ' + $.i18n('wiz_rgb_intro1') + '
' + $.i18n('wiz_rgb_intro2') + '
');
+ $('#wizp1_footer').html(' ' + $.i18n('general_btn_continue') + ' ' + $.i18n('general_btn_cancel') + ' ');
+ $('#wizp2_body').html('' + $.i18n('wiz_rgb_expl') + '
');
+ $('#wizp2_body').append('');
+ $('#wizp2_body').append('' + $.i18n('wiz_rgb_q') + ' ');
+ $('#wizp2_body').append('' + $.i18n('wiz_rgb_qrend') + ' ' + $.i18n('wiz_rgb_qgend') + '
');
+ $('#wizp2_footer').html(' ' + $.i18n('general_btn_save') + ' ' + $.i18n('general_btn_ok') + ' ' + $.i18n('general_btn_cancel') + ' ');
+
+ if (getStorage("darkMode") == "on")
+ $('#wizard_logo').attr("src", 'img/hyperion/logo_negativ.png');
+
+ //open modal
+ $("#wizard_modal").modal({
+ backdrop: "static",
+ keyboard: false,
+ show: true
+ });
+
+ //listen for continue
+ $('#btn_wiz_cont').off().on('click', function () {
+ beginWizardRGB();
+ $('#wizp1').toggle(false);
+ $('#wizp2').toggle(true);
+ });
+ }
+ };
+})();
+
+export { rgbByteOrderWizard };
diff --git a/config/hyperion.config.json.default b/config/hyperion.config.json.default
index 212106b4..06c33c33 100644
--- a/config/hyperion.config.json.default
+++ b/config/hyperion.config.json.default
@@ -221,9 +221,7 @@
"internetAccessAPI": false,
"restirctedInternetAccessAPI": false,
"ipWhitelist": [],
- "apiAuth": true,
- "localApiAuth": false,
- "localAdminAuth": true
+ "localApiAuth": false
},
"ledConfig": {
diff --git a/doc/development/JSON-API _Commands_Overview.md b/doc/development/JSON-API _Commands_Overview.md
new file mode 100644
index 00000000..549c5e62
--- /dev/null
+++ b/doc/development/JSON-API _Commands_Overview.md
@@ -0,0 +1,122 @@
+# JSON-API Commands Overview
+
+## Commands & Sub-Commands
+
+List of commands and related sub-commands which can be used via JSON-API requests.
+
+_Authorization (via password or bearer token)_
+
+**No** - No authorization required
+**Yes** - Authorization required, but can be disabled for local network calls
+**Admin**: Authorization is always required
+
+_Instance specific_
+
+**Yes** - A specific instance can be addressed
+**Multi** - Multiple instances can be addressed via one request
+**No** - The command is not instance related
+
+_http/s Support_
+
+**Yes** - Command can be used by individual http/s requests
+**No** - Applies only to WebSocket or http/s sessions
+
+| Command | Sub-Command | Authorization | Instance specific | http/s Support |
+|:---------------|:------------------------|:--------------|:------------------|:---------------|
+| adjustment | - | Yes | Multi | Yes |
+| authorize | adminRequired | No | No | Yes |
+| authorize | answerRequest | Admin | No | No |
+| authorize | createToken | Admin | No | No |
+| authorize | deleteToken | Admin | No | Yes |
+| authorize | getPendingTokenRequests | Admin | No | No |
+| authorize | getTokenList | Admin | No | Yes |
+| authorize | login | No | No | No |
+| authorize | logout | No | No | No |
+| authorize | newPassword | Admin | No | Yes |
+| authorize | newPasswordRequired | No | No | Yes |
+| authorize | renameToken | Admin | No | Yes |
+| authorize | requestToken | No | No | Yes |
+| authorize | tokenRequired | No | No | Yes |
+| clear | - | Yes | Multi | Yes |
+| clearall | - | Yes | Multi | Yes |
+| color | - | Yes | Multi | Yes |
+| componentstate | - | Yes | Multi | Yes |
+| config | getconfig | Admin | Yes | Yes |
+| config | getschema | Admin | Yes | Yes |
+| config | reload | Admin | Yes | Yes |
+| config | restoreconfig | Admin | Yes | Yes |
+| config | setconfig | Admin | Yes | Yes |
+| correction | - | Yes | Yes | Yes |
+| create-effect | - | Yes | Yes | Yes |
+| delete-effect | - | Yes | Yes | Yes |
+| effect | - | Yes | Multi | Yes |
+| image | - | Yes | Multi | Yes |
+| inputsource | discover | Yes | No | Yes |
+| inputsource | getProperties | Yes | No | Yes |
+| instance | createInstance | Admin | No | Yes |
+| instance | deleteInstance | Admin | No | Yes |
+| instance | saveName | Admin | No | Yes |
+| instance | startInstance | Yes | No | Yes |
+| instance | stopInstance | Yes | No | Yes |
+| instance | switchTo | Yes | No | Yes |
+| ledcolors | imagestream-start | Yes | Yes | Yes |
+| ledcolors | imagestream-stop | Yes | Yes | Yes |
+| ledcolors | ledstream-start | Yes | Yes | Yes |
+| ledcolors | ledstream-stop | Yes | Yes | Yes |
+| leddevice | addAuthorization | Yes | Yes | Yes |
+| leddevice | discover | Yes | Yes | Yes |
+| leddevice | getProperties | Yes | Yes | Yes |
+| leddevice | identify | Yes | Yes | Yes |
+| logging | start | Yes | No | Yes |
+| logging | stop | Yes | No | Yes |
+| processing | - | Yes | Multi | Yes |
+| serverinfo | - | Yes | Yes | Yes |
+| serverinfo | getInfo | Yes | Yes | Yes |
+| serverinfo | subscribe | Yes | Yes | No |
+| serverinfo | unsubscribe | Yes | Yes | No |
+| serverinfo | getSubscriptions | Yes | Yes | No |
+| serverinfo | getSubscriptionCommands | No | No | No |
+| service | discover | Yes | No | Yes |
+| sourceselect | - | Yes | Multi | Yes |
+| sysinfo | - | Yes | No | Yes |
+| system | restart | Yes | No | Yes |
+| system | resume | Yes | No | Yes |
+| system | suspend | Yes | No | Yes |
+| system | toggleSuspend | Yes | No | Yes |
+| system | idle | Yes | No | Yes |
+| system | toggleIdle | Yes | No | Yes |
+| temperature | - | Yes | Yes | Yes |
+| transform | - | Yes | Yes | Yes |
+| videomode | - | Yes | No | Yes |
+
+## Subscription updates
+
+List of updates which can be subscribed to via the `serverinfo/subscribe`request.
+
+_Instance specific_
+
+**Yes** - A specific instance can be addressed
+**No** - The command is not instance related
+
+_in "all"_
+
+**Yes** - Updates are subscribed using "all" as the command
+**No** - Subscription is only triggered via JSON-API request
+
+| Subscription Command | Instance specific | in "all" |
+|:-----------------------------|:------------------|:---------|
+| adjustment-update | Yes | Yes |
+| components-update | Yes | Yes |
+| effects-update | Yes | Yes |
+| event-update | No | Yes |
+| imageToLedMapping-update | Yes | Yes |
+| instance-update | Yes | Yes |
+| ledcolors-imagestream-update | Yes | No |
+| ledcolors-ledstream-update | Yes | No |
+| leds-update | Yes | Yes |
+| logmsg-update | No | No |
+| priorities-update | Yes | Yes |
+| settings-update | Yes | Yes |
+| token-update | No | Yes |
+| videomode-update | No | Yes |
+
diff --git a/include/api/API.h b/include/api/API.h
index 31a60f97..96e1f9b8 100644
--- a/include/api/API.h
+++ b/include/api/API.h
@@ -14,11 +14,15 @@
// qt includes
#include
-class QTimer;
-class JsonCB;
+class JsonCallbacks;
class HyperionIManager;
-const QString NO_AUTH = "No Authorization";
+// Constants
+namespace {
+
+const char NO_AUTHORIZATION[] = "No Authorization";;
+
+}
///
/// @brief API for Hyperion to be inherted from a child class with specific protocol implementations
@@ -31,205 +35,214 @@ const QString NO_AUTH = "No Authorization";
class API : public QObject
{
- Q_OBJECT
+ Q_OBJECT
public:
#include
- // workaround Q_ARG std::map template issues
- typedef std::map MapRegister;
- typedef QMap MapAuthDefs;
- ///
- /// Constructor
- ///
- ///@ param The parent Logger
- /// @param localConnection Is this a local network connection? Use utils/NetOrigin to check that
- /// @param parent Parent QObject
- ///
- API(Logger *log, bool localConnection, QObject *parent);
-
-protected:
- ///
- /// @brief Initialize the API
- /// This call is REQUIRED!
- ///
- void init();
-
- ///
- /// @brief Set a single color
- /// @param[in] priority The priority of the written color
- /// @param[in] ledColor The color to write to the leds
- /// @param[in] timeout_ms The time the leds are set to the given color [ms]
- /// @param[in] origin The setter
- ///
- void setColor(int priority, const std::vector &ledColors, int timeout_ms = -1, const QString &origin = "API", hyperion::Components callerComp = hyperion::COMP_INVALID);
-
- ///
- /// @brief Set a image
- /// @param[in] data The command data
- /// @param[in] comp The component that should be used
- /// @param[out] replyMsg The replyMsg on failure
- /// @param callerComp The HYPERION COMPONENT that calls this function! e.g. PROT/FLATBUF
- /// @return True on success
- ///
- bool setImage(ImageCmdData &data, hyperion::Components comp, QString &replyMsg, hyperion::Components callerComp = hyperion::COMP_INVALID);
-
- ///
- /// @brief Clear a priority in the Muxer, if -1 all priorities are cleared
- /// @param priority The priority to clear
- /// @param replyMsg the message on failure
- /// @param callerComp The HYPERION COMPONENT that calls this function! e.g. PROT/FLATBUF
- /// @return True on success
- ///
- bool clearPriority(int priority, QString &replyMsg, hyperion::Components callerComp = hyperion::COMP_INVALID);
-
- ///
- /// @brief Set a new component state
- /// @param comp The component name
- /// @param compState The new state of the comp
- /// @param replyMsg The reply on failure
- /// @param callerComp The HYPERION COMPONENT that calls this function! e.g. PROT/FLATBUF
- /// @ return True on success
- ///
- bool setComponentState(const QString &comp, bool &compState, QString &replyMsg, hyperion::Components callerComp = hyperion::COMP_INVALID);
-
- ///
- /// @brief Set a ledToImageMapping type
- /// @param type mapping type string
- /// @param callerComp The HYPERION COMPONENT that calls this function! e.g. PROT/FLATBUF
- ///
- void setLedMappingType(int type, hyperion::Components callerComp = hyperion::COMP_INVALID);
-
- ///
- /// @brief Set the 2D/3D modes type
- /// @param mode The VideoMode
- /// @param callerComp The HYPERION COMPONENT that calls this function! e.g. PROT/FLATBUF
- ///
- void setVideoMode(VideoMode mode, hyperion::Components callerComp = hyperion::COMP_INVALID);
-
-#if defined(ENABLE_EFFECTENGINE)
- ///
- /// @brief Set an effect
- /// @param dat The effect data
- /// @param callerComp The HYPERION COMPONENT that calls this function! e.g. PROT/FLATBUF
- /// REQUIRED dat fields: effectName, priority, duration, origin
- /// @return True on success else false
- ///
- bool setEffect(const EffectCmdData &dat, hyperion::Components callerComp = hyperion::COMP_INVALID);
-#endif
-
- ///
- /// @brief Set source auto select enabled or disabled
- /// @param sate The new state
- /// @param callerComp The HYPERION COMPONENT that calls this function! e.g. PROT/FLATBUF
- ///
- void setSourceAutoSelect(bool state, hyperion::Components callerComp = hyperion::COMP_INVALID);
-
- ///
- /// @brief Set the visible priority to given priority
- /// @param priority The priority to set
- /// @param callerComp The HYPERION COMPONENT that calls this function! e.g. PROT/FLATBUF
- ///
- void setVisiblePriority(int priority, hyperion::Components callerComp = hyperion::COMP_INVALID);
-
- ///
- /// @brief Register a input or update the meta data of a previous register call
- /// ATTENTION: Check unregisterInput() method description !!!
- /// @param[in] priority The priority of the channel
- /// @param[in] component The component of the channel
- /// @param[in] origin Who set the channel (CustomString@IP)
- /// @param[in] owner Specific owner string, might be empty
- /// @param[in] callerComp The component that call this (e.g. PROTO/FLAT)
- ///
- void registerInput(int priority, hyperion::Components component, const QString &origin, const QString &owner, hyperion::Components callerComp);
-
- ///
- /// @brief Revoke a registerInput() call by priority. We maintain all registered priorities in this scope
- /// ATTENTION: This is MANDATORY if you change (priority change) or stop(clear/timeout) DURING lifetime. If this class destructs it's not needed
- /// @param priority The priority to unregister
- ///
- void unregisterInput(int priority);
-
- ///
- /// @brief Handle the instance switching
- /// @param inst The requested instance
- /// @return True on success else false
- ///
- bool setHyperionInstance(quint8 inst);
+ // workaround Q_ARG std::map template issues
+ typedef std::map MapRegister;
+ typedef QMap MapAuthDefs;
///
- /// @brief Check if Hyperion ist enabled
- /// @return True when enabled else false
- ///
- bool isHyperionEnabled();
+ /// Constructor
+ ///
+ ///@ param The parent Logger
+ /// @param localConnection Is this a local network connection? Use utils/NetOrigin to check that
+ /// @param parent Parent QObject
+ ///
+ API(Logger *log, bool localConnection, QObject *parent);
- ///
- /// @brief Get all instances data
- /// @return The instance data
- ///
- QVector getAllInstanceData();
+protected:
+ ///
+ /// @brief Initialize the API
+ /// This call is REQUIRED!
+ ///
+ void init();
- ///
- /// @brief Start instance
- /// @param index The instance index
- /// @param tan The tan
- /// @return True on success else false
- ///
- bool startInstance(quint8 index, int tan = 0);
+ virtual void stopDataConnections() = 0;
- ///
- /// @brief Stop instance
- /// @param index The instance index
- ///
- void stopInstance(quint8 index);
+ ///
+ /// @brief Set a single color
+ /// @param[in] priority The priority of the written color
+ /// @param[in] ledColor The color to write to the leds
+ /// @param[in] timeout_ms The time the leds are set to the given color [ms]
+ /// @param[in] origin The setter
+ ///
+ void setColor(int priority, const std::vector &ledColors, int timeout_ms = -1, const QString &origin = "API", hyperion::Components callerComp = hyperion::COMP_INVALID);
- //////////////////////////////////
- /// AUTH / ADMINISTRATION METHODS
- //////////////////////////////////
+ ///
+ /// @brief Set a image
+ /// @param[in] data The command data
+ /// @param[in] comp The component that should be used
+ /// @param[out] replyMsg The replyMsg on failure
+ /// @param callerComp The HYPERION COMPONENT that calls this function! e.g. PROT/FLATBUF
+ /// @return True on success
+ ///
+ bool setImage(ImageCmdData &data, hyperion::Components comp, QString &replyMsg, hyperion::Components callerComp = hyperion::COMP_INVALID);
- ///
- /// @brief Delete instance. Requires ADMIN ACCESS
- /// @param index The instance index
- /// @param replyMsg The reply Msg
- /// @return False with reply
- ///
- bool deleteInstance(quint8 index, QString &replyMsg);
+ ///
+ /// @brief Clear a priority in the Muxer, if -1 all priorities are cleared
+ /// @param priority The priority to clear
+ /// @param replyMsg the message on failure
+ /// @param callerComp The HYPERION COMPONENT that calls this function! e.g. PROT/FLATBUF
+ /// @return True on success
+ ///
+ bool clearPriority(int priority, QString &replyMsg, hyperion::Components callerComp = hyperion::COMP_INVALID);
- ///
- /// @brief Create instance. Requires ADMIN ACCESS
- /// @param name With given name
- /// @return False with reply
- ///
- QString createInstance(const QString &name);
+ ///
+ /// @brief Set a new component state
+ /// @param comp The component name
+ /// @param compState The new state of the comp
+ /// @param replyMsg The reply on failure
+ /// @param callerComp The HYPERION COMPONENT that calls this function! e.g. PROT/FLATBUF
+ /// @ return True on success
+ ///
+ bool setComponentState(const QString &comp, bool &compState, QString &replyMsg, hyperion::Components callerComp = hyperion::COMP_INVALID);
- ///
- /// @brief Rename an instance. Requires ADMIN ACCESS
- /// @param index The instance index
- /// @param name With given name
- /// @return False with reply
- ///
- QString setInstanceName(quint8 index, const QString &name);
+ ///
+ /// @brief Set a ledToImageMapping type
+ /// @param type mapping type string
+ /// @param callerComp The HYPERION COMPONENT that calls this function! e.g. PROT/FLATBUF
+ ///
+ void setLedMappingType(int type, hyperion::Components callerComp = hyperion::COMP_INVALID);
+
+ ///
+ /// @brief Set the 2D/3D modes type
+ /// @param mode The VideoMode
+ /// @param callerComp The HYPERION COMPONENT that calls this function! e.g. PROT/FLATBUF
+ ///
+ void setVideoMode(VideoMode mode, hyperion::Components callerComp = hyperion::COMP_INVALID);
#if defined(ENABLE_EFFECTENGINE)
- ///
- /// @brief Delete an effect. Requires ADMIN ACCESS
- /// @param name The effect name
- /// @return True on success else false
- ///
- QString deleteEffect(const QString &name);
-
- ///
- /// @brief Delete an effect. Requires ADMIN ACCESS
- /// @param name The effect name
- /// @return True on success else false
- ///
- QString saveEffect(const QJsonObject &data);
+ ///
+ /// @brief Set an effect
+ /// @param dat The effect data
+ /// @param callerComp The HYPERION COMPONENT that calls this function! e.g. PROT/FLATBUF
+ /// REQUIRED dat fields: effectName, priority, duration, origin
+ /// @return True on success else false
+ ///
+ bool setEffect(const EffectCmdData &dat, hyperion::Components callerComp = hyperion::COMP_INVALID);
#endif
- ///
- /// @brief Save settings object. Requires ADMIN ACCESS
- /// @param data The data object
- ///
+ ///
+ /// @brief Set source auto select enabled or disabled
+ /// @param sate The new state
+ /// @param callerComp The HYPERION COMPONENT that calls this function! e.g. PROT/FLATBUF
+ ///
+ void setSourceAutoSelect(bool state, hyperion::Components callerComp = hyperion::COMP_INVALID);
+
+ ///
+ /// @brief Set the visible priority to given priority
+ /// @param priority The priority to set
+ /// @param callerComp The HYPERION COMPONENT that calls this function! e.g. PROT/FLATBUF
+ ///
+ void setVisiblePriority(int priority, hyperion::Components callerComp = hyperion::COMP_INVALID);
+
+ ///
+ /// @brief Register a input or update the meta data of a previous register call
+ /// ATTENTION: Check unregisterInput() method description !!!
+ /// @param[in] priority The priority of the channel
+ /// @param[in] component The component of the channel
+ /// @param[in] origin Who set the channel (CustomString@IP)
+ /// @param[in] owner Specific owner string, might be empty
+ /// @param[in] callerComp The component that call this (e.g. PROTO/FLAT)
+ ///
+ void registerInput(int priority, hyperion::Components component, const QString &origin, const QString &owner, hyperion::Components callerComp);
+
+ ///
+ /// @brief Revoke a registerInput() call by priority. We maintain all registered priorities in this scope
+ /// ATTENTION: This is MANDATORY if you change (priority change) or stop(clear/timeout) DURING lifetime. If this class destructs it's not needed
+ /// @param priority The priority to unregister
+ ///
+ void unregisterInput(int priority);
+
+ ///
+ /// @brief Handle the instance switching
+ /// @param inst The requested instance
+ /// @return True on success else false
+ ///
+ bool setHyperionInstance(quint8 inst);
+
+ ///
+ /// @brief Check if Hyperion ist enabled
+ /// @return True when enabled else false
+ ///
+ bool isHyperionEnabled();
+
+ ///
+ /// @brief Get all instances data
+ /// @return The instance data
+ ///
+ QVector getAllInstanceData() const;
+
+ ///
+ /// @brief Get the current instances index
+ /// @return The instance index set
+ ///
+ quint8 getCurrentInstanceIndex() const { return _currInstanceIndex; }
+
+ ///
+ /// @brief Start instance
+ /// @param index The instance index
+ /// @param tan The tan
+ /// @return True on success else false
+ ///
+ bool startInstance(quint8 index, int tan = 0);
+
+ ///
+ /// @brief Stop instance
+ /// @param index The instance index
+ ///
+ void stopInstance(quint8 index);
+
+ //////////////////////////////////
+ /// AUTH / ADMINISTRATION METHODS
+ //////////////////////////////////
+
+ ///
+ /// @brief Delete instance. Requires ADMIN ACCESS
+ /// @param index The instance index
+ /// @param replyMsg The reply Msg
+ /// @return False with reply
+ ///
+ bool deleteInstance(quint8 index, QString &replyMsg);
+
+ ///
+ /// @brief Create instance. Requires ADMIN ACCESS
+ /// @param name With given name
+ /// @return False with reply
+ ///
+ QString createInstance(const QString &name);
+
+ ///
+ /// @brief Rename an instance. Requires ADMIN ACCESS
+ /// @param index The instance index
+ /// @param name With given name
+ /// @return False with reply
+ ///
+ QString setInstanceName(quint8 index, const QString &name);
+
+#if defined(ENABLE_EFFECTENGINE)
+ ///
+ /// @brief Delete an effect. Requires ADMIN ACCESS
+ /// @param name The effect name
+ /// @return True on success else false
+ ///
+ QString deleteEffect(const QString &name);
+
+ ///
+ /// @brief Delete an effect. Requires ADMIN ACCESS
+ /// @param name The effect name
+ /// @return True on success else false
+ ///
+ QString saveEffect(const QJsonObject &data);
+#endif
+
+ ///
+ /// @brief Save settings object. Requires ADMIN ACCESS
+ /// @param data The data object
+ ///
bool saveSettings(const QJsonObject &data);
///
@@ -238,171 +251,189 @@ protected:
///
bool restoreSettings(const QJsonObject &data);
- ///
- /// @brief Test if we are authorized to use the interface
- /// @return The result
- ///
- bool isAuthorized() { return _authorized; };
+ ///
+ /// @brief Set the authorizationn state
+ /// @param authorized True, if authorized
+ ///
+ void setAuthorization(bool authorized) { _authorized = authorized; }
- ///
- /// @brief Test if we are authorized to use the admin interface
- /// @return The result
- ///
- bool isAdminAuthorized() { return _adminAuthorized; };
+ ///
+ /// @brief Test if we are authorized to use the interface
+ /// @return The result
+ ///
+ bool isAuthorized() const { return _authorized; }
- ///
- /// @brief Update the Password of Hyperion. Requires ADMIN ACCESS
- /// @param password Old password
- /// @param newPassword New password
- /// @return True on success else false
- ///
- bool updateHyperionPassword(const QString &password, const QString &newPassword);
+ ///
+ /// @brief Set the authorizationn state for admin activities
+ /// @param authorized True, if authorized
+ ///
+ void setAdminAuthorization(bool adminAuthorized) { _adminAuthorized = adminAuthorized; }
- ///
- /// @brief Get a new token from AuthManager. Requires ADMIN ACCESS
- /// @param comment The comment of the request
- /// @param def The final definition
- /// @return Empty string on success else error message
- ///
- QString createToken(const QString &comment, AuthManager::AuthDefinition &def);
+ ///
+ /// @brief Test if we are authorized to use admin activites
+ /// @return The result
+ ///
+ bool isAdminAuthorized() const { return _adminAuthorized; }
- ///
- /// @brief Rename a token by given id. Requires ADMIN ACCESS
- /// @param id The id of the token
- /// @param comment The new comment
- /// @return Empty string on success else error message
- ///
- QString renameToken(const QString &id, const QString &comment);
+ ///
+ /// @brief Return, if connection is from local network segment
+ /// @return The result
+ ///
+ bool islocalConnection() const { return _localConnection; }
- ///
- /// @brief Delete a token by given id. Requires ADMIN ACCESS
- /// @param id The id of the token
- /// @return Empty string on success else error message
- ///
- QString deleteToken(const QString &id);
+ ///
+ /// @brief Update the Password of Hyperion. Requires ADMIN ACCESS
+ /// @param password Old password
+ /// @param newPassword New password
+ /// @return True on success else false
+ ///
+ bool updateHyperionPassword(const QString &password, const QString &newPassword);
- ///
- /// @brief Set a new token request
- /// @param comment The comment
- /// @param id The id
+ ///
+ /// @brief Get a new token from AuthManager. Requires ADMIN ACCESS
+ /// @param comment The comment of the request
+ /// @param def The final definition
+ /// @return Empty string on success else error message
+ ///
+ QString createToken(const QString &comment, AuthManager::AuthDefinition &def);
+
+ ///
+ /// @brief Rename a token by given id. Requires ADMIN ACCESS
+ /// @param tokenId The id of the token
+ /// @param comment The new comment
+ /// @return Empty string on success else error message
+ ///
+ QString renameToken(const QString &tokenId, const QString &comment);
+
+ ///
+ /// @brief Delete a token by given id. Requires ADMIN ACCESS
+ /// @param tokenId The id of the token
+ /// @return Empty string on success else error message
+ ///
+ QString deleteToken(const QString &tokenId);
+
+ ///
+ /// @brief Set a new token request
+ /// @param comment The comment
+ /// @param tokenId The id of the token
/// @param tan The tan
- ///
- void setNewTokenRequest(const QString &comment, const QString &id, const int &tan);
+ ///
+ void setNewTokenRequest(const QString &comment, const QString &tokenId, const int &tan);
- ///
- /// @brief Cancel new token request
- /// @param comment The comment
- /// @param id The id
- ///
- void cancelNewTokenRequest(const QString &comment, const QString &id);
+ ///
+ /// @brief Cancel new token request
+ /// @param comment The comment
+ /// @param tokenId The id of the token
+ ///
+ void cancelNewTokenRequest(const QString &comment, const QString &tokenId);
- ///
- /// @brief Handle a pending token request. Requires ADMIN ACCESS
- /// @param id The id fo the request
- /// @param accept True when it should be accepted, else false
- /// @return True on success
- bool handlePendingTokenRequest(const QString &id, bool accept);
-
- ///
- /// @brief Get the current List of Tokens. Requires ADMIN ACCESS
- /// @param def returns the defintions
- /// @return True on success
- ///
- bool getTokenList(QVector &def);
-
- ///
- /// @brief Get all current pending token requests. Requires ADMIN ACCESS
- /// @return True on success
- ///
- bool getPendingTokenRequests(QVector &map);
-
- ///
- /// @brief Is User Token Authorized. On success this will grant acces to API and ADMIN API
- /// @param userToken The user Token
- /// @return True on succes
- ///
- bool isUserTokenAuthorized(const QString &userToken);
-
- ///
- /// @brief Get the current User Token (session token). Requires ADMIN ACCESS
- /// @param userToken The user Token
- /// @return True on success
- ///
- bool getUserToken(QString &userToken);
-
- ///
- /// @brief Is a token authorized. On success this will grant acces to the API (NOT ADMIN API)
- /// @param token The user Token
+ ///
+ /// @brief Handle a pending token request. Requires ADMIN ACCESS
+ /// @param tokenId The id fo the request
+ /// @param accept True when it should be accepted, else false
/// @return True on success
- ///
- bool isTokenAuthorized(const QString &token);
+ bool handlePendingTokenRequest(const QString &tokenId, bool accept);
- ///
- /// @brief Is User authorized. On success this will grant acces to the API and ADMIN API
- /// @param password The password of the User
- /// @return True if authorized
- ///
- bool isUserAuthorized(const QString &password);
+ ///
+ /// @brief Get the current List of Tokens. Requires ADMIN ACCESS
+ /// @param def returns the defintions
+ /// @return True on success
+ ///
+ bool getTokenList(QVector &def);
- ///
- /// @brief Test if Hyperion has the default PW
- /// @return The result
- ///
- bool hasHyperionDefaultPw();
+ ///
+ /// @brief Get all current pending token requests. Requires ADMIN ACCESS
+ /// @return True on success
+ ///
+ bool getPendingTokenRequests(QVector &map);
- ///
- /// @brief Logout revokes all authorizations
- ///
- void logout();
+ ///
+ /// @brief Is User Token Authorized. On success this will grant acces to API and ADMIN API
+ /// @param userToken The user Token
+ /// @return True on succes
+ ///
+ bool isUserTokenAuthorized(const QString &userToken);
- /// Reflect auth status of this client
- bool _authorized;
- bool _adminAuthorized;
+ ///
+ /// @brief Get the current User Token (session token). Requires ADMIN ACCESS
+ /// @param userToken The user Token
+ /// @return True on success
+ ///
+ bool getUserToken(QString &userToken);
- /// Is this a local connection
- bool _localConnection;
+ ///
+ /// @brief Is a token authorized. On success this will grant acces to the API (NOT ADMIN API)
+ /// @param token The user Token
+ /// @return True on success
+ ///
+ bool isTokenAuthorized(const QString &token);
- AuthManager *_authManager;
- HyperionIManager *_instanceManager;
+ ///
+ /// @brief Is User authorized. On success this will grant acces to the API and ADMIN API
+ /// @param password The password of the User
+ /// @return True if authorized
+ ///
+ bool isUserAuthorized(const QString &password);
- Logger *_log;
- Hyperion *_hyperion;
+ ///
+ /// @brief Test if Hyperion has the default PW
+ /// @return The result
+ ///
+ bool hasHyperionDefaultPw();
+
+ ///
+ /// @brief Logout revokes all authorizations
+ ///
+ void logout();
+
+
+ AuthManager *_authManager;
+ HyperionIManager *_instanceManager;
+
+ Logger *_log;
+ Hyperion *_hyperion;
signals:
- ///
- /// @brief The API might decide to block connections for security reasons, this emitter should close the socket
- ///
- void forceClose();
+ ///
+ /// @brief The API might decide to block connections for security reasons, this emitter should close the socket
+ ///
+ void forceClose();
- ///
- /// @brief Emits whenever a new Token request is pending. This signal is just active when ADMIN ACCESS has been granted
- /// @param id The id of the request
- /// @param comment The comment of the request; If the commen is EMPTY the request has been revoked by the caller. So remove it from the pending list
- ///
- void onPendingTokenRequest(const QString &id, const QString &comment);
+ ///
+ /// @brief Emits whenever a new Token request is pending. This signal is just active when ADMIN ACCESS has been granted
+ /// @param tokenId The id of the request
+ /// @param comment The comment of the request; If the commen is EMPTY the request has been revoked by the caller. So remove it from the pending list
+ ///
+ void onPendingTokenRequest(const QString &tokenId, const QString &comment);
- ///
- /// @brief Handle emits from AuthManager of accepted/denied/timeouts token request, just if QObject matches with this instance it will emit.
- /// @param success If true the request was accepted else false and no token was created
- /// @param token The new token that is now valid
- /// @param comment The comment that was part of the request
- /// @param id The id that was part of the request
- /// @param tan The tan that was part of the request
- ///
- void onTokenResponse(bool success, const QString &token, const QString &comment, const QString &id, const int &tan);
+ ///
+ /// @brief Handle emits from AuthManager of accepted/denied/timeouts token request, just if QObject matches with this instance it will emit.
+ /// @param success If true the request was accepted else false and no token was created
+ /// @param token The new token that is now valid
+ /// @param comment The comment that was part of the request
+ /// @param tokenId The id that was part of the request
+ /// @param tan The tan that was part of the request
+ ///
+ void onTokenResponse(bool success, const QString &token, const QString &comment, const QString &tokenId, const int &tan);
- ///
- /// @brief Handle emits from HyperionIManager of startInstance request, just if QObject matches with this instance it will emit.
- /// @param tan The tan that was part of the request
- ///
- void onStartInstanceResponse(const int &tan);
+ ///
+ /// @brief Handle emits from HyperionIManager of startInstance request, just if QObject matches with this instance it will emit.
+ /// @param tan The tan that was part of the request
+ ///
+ void onStartInstanceResponse(const int &tan);
private:
- void stopDataConnectionss();
- // Contains all active register call data
- std::map _activeRegisters;
+ /// Reflect authorization status of this client
+ bool _authorized;
+ bool _adminAuthorized;
- // current instance index
- quint8 _currInstanceIndex;
+ /// Is this a local connection
+ bool _localConnection;
+
+ // Contains all active register call data
+ std::map _activeRegisters;
+
+ // current instance index
+ quint8 _currInstanceIndex;
};
diff --git a/include/api/JsonAPI.h b/include/api/JsonAPI.h
index 8e36b158..35d7e204 100644
--- a/include/api/JsonAPI.h
+++ b/include/api/JsonAPI.h
@@ -2,19 +2,24 @@
// parent class
#include
+#include
+
#include
// hyperion includes
#include
#include
#include
+#include
// qt includes
#include
#include
+#include
+#include
class QTimer;
-class JsonCB;
+class JsonCallbacks;
class AuthManager;
class JsonAPI : public API
@@ -46,40 +51,24 @@ public:
void initialize();
public slots:
- ///
- /// @brief Is called whenever the current Hyperion instance pushes new led raw values (if enabled)
- /// @param ledColors The current led colors
- ///
- void streamLedcolorsUpdate(const std::vector &ledColors);
-
- ///
- /// @brief Push images whenever hyperion emits (if enabled)
- /// @param image The current image
- ///
- void setImage(const Image &image);
-
- ///
- /// @brief Process and push new log messages from logger (if enabled)
- ///
- void incommingLogMessage(const Logger::T_LOG_MESSAGE &);
private slots:
///
/// @brief Handle emits from API of a new Token request.
- /// @param id The id of the request
+ /// @param identifier The identifier of the request
/// @param comment The comment which needs to be accepted
///
- void newPendingTokenRequest(const QString &id, const QString &comment);
+ void issueNewPendingTokenRequest(const QString &identifier, const QString &comment);
///
/// @brief Handle emits from AuthManager of accepted/denied/timeouts token request, just if QObject matches with this instance we are allowed to send response.
/// @param success If true the request was accepted else false and no token was created
/// @param token The new token that is now valid
/// @param comment The comment that was part of the request
- /// @param id The id that was part of the request
+ /// @param identifier The identifier that was part of the request
/// @param tan The tan that was part of the request
///
- void handleTokenResponse(bool success, const QString &token, const QString &comment, const QString &id, const int &tan);
+ void handleTokenResponse(bool success, const QString &token, const QString &comment, const QString &identifier, const int &tan);
///
/// @brief Handle whenever the state of a instance (HyperionIManager) changes according to enum instanceState
@@ -89,11 +78,6 @@ private slots:
///
void handleInstanceStateChange(InstanceState state, quint8 instance, const QString &name = QString());
- ///
- /// @brief Stream a new LED Colors update
- ///
- void streamLedColorsUpdate();
-
signals:
///
/// Signal emits with the reply message provided with handleMessage()
@@ -111,31 +95,9 @@ signals:
void signalEvent(Event event);
private:
- // true if further callbacks are forbidden (http)
- bool _noListener;
- /// The peer address of the client
- QString _peerAddress;
-
- // The JsonCB instance which handles data subscription/notifications
- JsonCB *_jsonCB;
-
- // streaming buffers
- QJsonObject _streaming_leds_reply;
- QJsonObject _streaming_image_reply;
- QJsonObject _streaming_logging_reply;
-
- /// flag to determine state of log streaming
- bool _streaming_logging_activated;
-
- /// timer for led color refresh
- QTimer *_ledStreamTimer;
-
- /// led stream connection handle
- QMetaObject::Connection _ledStreamConnection;
-
- /// the current streaming led values
- std::vector _currentLedValues;
+ void handleCommand(const JsonApiCommand& cmd, const QJsonObject &message);
+ void handleInstanceCommand(const JsonApiCommand& cmd, const QJsonObject &message);
///
/// @brief Handle the switches of Hyperion instances
@@ -150,14 +112,14 @@ private:
///
/// @param message the incoming message
///
- void handleColorCommand(const QJsonObject &message, const QString &command, int tan);
+ void handleColorCommand(const QJsonObject& message, const JsonApiCommand& cmd);
///
/// Handle an incoming JSON Image message
///
/// @param message the incoming message
///
- void handleImageCommand(const QJsonObject &message, const QString &command, int tan);
+ void handleImageCommand(const QJsonObject &message, const JsonApiCommand& cmd);
#if defined(ENABLE_EFFECTENGINE)
///
@@ -165,21 +127,21 @@ private:
///
/// @param message the incoming message
///
- void handleEffectCommand(const QJsonObject &message, const QString &command, int tan);
+ void handleEffectCommand(const QJsonObject &message, const JsonApiCommand& cmd);
///
/// Handle an incoming JSON Effect message (Write JSON Effect)
///
/// @param message the incoming message
///
- void handleCreateEffectCommand(const QJsonObject &message, const QString &command, int tan);
+ void handleCreateEffectCommand(const QJsonObject &message, const JsonApiCommand& cmd);
///
/// Handle an incoming JSON Effect message (Delete JSON Effect)
///
/// @param message the incoming message
///
- void handleDeleteEffectCommand(const QJsonObject &message, const QString &command, int tan);
+ void handleDeleteEffectCommand(const QJsonObject &message, const JsonApiCommand& cmd);
#endif
///
@@ -187,158 +149,250 @@ private:
///
/// @param message the incoming message
///
- void handleSysInfoCommand(const QJsonObject &message, const QString &command, int tan);
+ void handleSysInfoCommand(const QJsonObject &message, const JsonApiCommand& cmd);
///
/// Handle an incoming JSON Server info message
///
/// @param message the incoming message
///
- void handleServerInfoCommand(const QJsonObject &message, const QString &command, int tan);
+ void handleServerInfoCommand(const QJsonObject &message, const JsonApiCommand& cmd);
///
/// Handle an incoming JSON Clear message
///
/// @param message the incoming message
///
- void handleClearCommand(const QJsonObject &message, const QString &command, int tan);
+ void handleClearCommand(const QJsonObject &message, const JsonApiCommand& cmd);
///
/// Handle an incoming JSON Clearall message
///
/// @param message the incoming message
///
- void handleClearallCommand(const QJsonObject &message, const QString &command, int tan);
+ void handleClearallCommand(const QJsonObject &message, const JsonApiCommand& cmd);
///
/// Handle an incoming JSON Adjustment message
///
/// @param message the incoming message
///
- void handleAdjustmentCommand(const QJsonObject &message, const QString &command, int tan);
+ void handleAdjustmentCommand(const QJsonObject &message, const JsonApiCommand& cmd);
///
/// Handle an incoming JSON SourceSelect message
///
/// @param message the incoming message
///
- void handleSourceSelectCommand(const QJsonObject &message, const QString &command, int tan);
+ void handleSourceSelectCommand(const QJsonObject &message, const JsonApiCommand& cmd);
/// Handle an incoming JSON GetConfig message and check subcommand
///
/// @param message the incoming message
///
- void handleConfigCommand(const QJsonObject &message, const QString &command, int tan);
+ void handleConfigCommand(const QJsonObject &message, const JsonApiCommand& cmd);
/// Handle an incoming JSON GetSchema message from handleConfigCommand()
///
/// @param message the incoming message
///
- void handleSchemaGetCommand(const QJsonObject &message, const QString &command, int tan);
+ void handleSchemaGetCommand(const QJsonObject &message, const JsonApiCommand& cmd);
/// Handle an incoming JSON SetConfig message from handleConfigCommand()
///
/// @param message the incoming message
///
- void handleConfigSetCommand(const QJsonObject &message, const QString &command, int tan);
+ void handleConfigSetCommand(const QJsonObject &message, const JsonApiCommand& cmd);
/// Handle an incoming JSON RestoreConfig message from handleConfigCommand()
///
/// @param message the incoming message
///
- void handleConfigRestoreCommand(const QJsonObject &message, const QString &command, int tan);
+ void handleConfigRestoreCommand(const QJsonObject &message, const JsonApiCommand& cmd);
///
/// Handle an incoming JSON Component State message
///
/// @param message the incoming message
///
- void handleComponentStateCommand(const QJsonObject &message, const QString &command, int tan);
+ void handleComponentStateCommand(const QJsonObject &message, const JsonApiCommand& cmd);
/// Handle an incoming JSON Led Colors message
///
/// @param message the incoming message
///
- void handleLedColorsCommand(const QJsonObject &message, const QString &command, int tan);
+ void handleLedColorsCommand(const QJsonObject &message, const JsonApiCommand& cmd);
/// Handle an incoming JSON Logging message
///
/// @param message the incoming message
///
- void handleLoggingCommand(const QJsonObject &message, const QString &command, int tan);
+ void handleLoggingCommand(const QJsonObject &message, const JsonApiCommand& cmd);
/// Handle an incoming JSON Processing message
///
/// @param message the incoming message
///
- void handleProcessingCommand(const QJsonObject &message, const QString &command, int tan);
+ void handleProcessingCommand(const QJsonObject &message, const JsonApiCommand& cmd);
/// Handle an incoming JSON VideoMode message
///
/// @param message the incoming message
///
- void handleVideoModeCommand(const QJsonObject &message, const QString &command, int tan);
+ void handleVideoModeCommand(const QJsonObject &message, const JsonApiCommand& cmd);
/// Handle an incoming JSON plugin message
///
/// @param message the incoming message
///
- void handleAuthorizeCommand(const QJsonObject &message, const QString &command, int tan);
+ void handleAuthorizeCommand(const QJsonObject &message, const JsonApiCommand& cmd);
/// Handle an incoming JSON instance message
///
/// @param message the incoming message
///
- void handleInstanceCommand(const QJsonObject &message, const QString &command, int tan);
+ void handleInstanceCommand(const QJsonObject &message, const JsonApiCommand& cmd);
/// Handle an incoming JSON Led Device message
///
/// @param message the incoming message
///
- void handleLedDeviceCommand(const QJsonObject &message, const QString &command, int tan);
+ void handleLedDeviceCommand(const QJsonObject &message, const JsonApiCommand& cmd);
/// Handle an incoming JSON message regarding Input Sources (Grabbers)
///
/// @param message the incoming message
///
- void handleInputSourceCommand(const QJsonObject& message, const QString& command, int tan);
+ void handleInputSourceCommand(const QJsonObject& message, const JsonApiCommand& cmd);
/// Handle an incoming JSON message to request remote hyperion servers providing a given hyperion service
///
/// @param message the incoming message
///
- void handleServiceCommand(const QJsonObject &message, const QString &command, int tan);
+ void handleServiceCommand(const QJsonObject &message, const JsonApiCommand& cmd);
/// Handle an incoming JSON message for actions related to the overall Hyperion system
///
/// @param message the incoming message
///
- void handleSystemCommand(const QJsonObject &message, const QString &command, int tan);
+ void handleSystemCommand(const QJsonObject &message, const JsonApiCommand& cmd);
- ///
- /// Handle an incoming JSON message of unknown type
- ///
- void handleNotImplemented(const QString &command, int tan);
+
+ void applyColorAdjustments(const QJsonObject &adjustment, ColorAdjustment *colorAdjustment);
+ void applyColorAdjustment(const QString &colorName, const QJsonObject &adjustment, RgbChannelAdjustment &rgbAdjustment);
+ void applyGammaTransform(const QString &transformName, const QJsonObject &adjustment, RgbTransform &rgbTransform, char channel);
+
+ void applyTransforms(const QJsonObject &adjustment, ColorAdjustment *colorAdjustment);
+ template
+ void applyTransform(const QString &transformName, const QJsonObject &adjustment, T &transform, void (T::*setFunction)(bool));
+ template
+ void applyTransform(const QString &transformName, const QJsonObject &adjustment, T &transform, void (T::*setFunction)(double));
+ template
+ void applyTransform(const QString &transformName, const QJsonObject &adjustment, T &transform, void (T::*setFunction)(uint8_t));
+
+ void handleTokenRequired(const JsonApiCommand& cmd);
+ void handleAdminRequired(const JsonApiCommand& cmd);
+ void handleNewPasswordRequired(const JsonApiCommand& cmd);
+ void handleLogout(const JsonApiCommand& cmd);
+ void handleNewPassword(const QJsonObject &message, const JsonApiCommand& cmd);
+ void handleCreateToken(const QJsonObject &message, const JsonApiCommand& cmd);
+ void handleRenameToken(const QJsonObject &message, const JsonApiCommand& cmd);
+ void handleDeleteToken(const QJsonObject &message, const JsonApiCommand& cmd);
+ void handleRequestToken(const QJsonObject &message, const JsonApiCommand& cmd);
+ void handleGetPendingTokenRequests(const JsonApiCommand& cmd);
+ void handleAnswerRequest(const QJsonObject &message, const JsonApiCommand& cmd);
+ void handleGetTokenList(const JsonApiCommand& cmd);
+ void handleLogin(const QJsonObject &message, const JsonApiCommand& cmd);
+
+ void handleLedDeviceDiscover(LedDevice& ledDevice, const QJsonObject& message, const JsonApiCommand& cmd);
+ void handleLedDeviceGetProperties(LedDevice& ledDevice, const QJsonObject& message, const JsonApiCommand& cmd);
+ void handleLedDeviceIdentify(LedDevice& ledDevice, const QJsonObject& message, const JsonApiCommand& cmd);
+ void handleLedDeviceAddAuthorization(LedDevice& ledDevice, const QJsonObject& message, const JsonApiCommand& cmd);
+
+ QJsonObject getBasicCommandReply(bool success, const QString &command, int tan, InstanceCmd::Type isInstanceCmd) const;
///
/// Send a standard reply indicating success
///
- void sendSuccessReply(const QString &command = "", int tan = 0);
+ void sendSuccessReply(const JsonApiCommand& cmd);
+
+ ///
+ /// Send a standard reply indicating success
+ ///
+ void sendSuccessReply(const QString &command = "", int tan = 0, InstanceCmd::Type isInstanceCmd = InstanceCmd::No);
///
/// Send a standard reply indicating success with data
///
- void sendSuccessDataReply(const QJsonDocument &doc, const QString &command = "", int tan = 0);
+ void sendSuccessDataReply(const QJsonValue &infoData, const JsonApiCommand& cmd);
+
+ ///
+ /// Send a standard reply indicating success with data
+ ///
+ void sendSuccessDataReply(const QJsonValue &infoData, const QString &command = "", int tan = 0, InstanceCmd::Type isInstanceCmd = InstanceCmd::No);
+
+ ///
+ /// Send a standard reply indicating success with data and error details
+ ///
+ void sendSuccessDataReplyWithError(const QJsonValue &infoData, const JsonApiCommand& cmd, const QStringList& errorDetails = {});
+
+ ///
+ /// Send a standard reply indicating success with data and error details
+ ///
+ void sendSuccessDataReplyWithError(const QJsonValue &infoData, const QString &command = "", int tan = 0, const QStringList& errorDetails = {}, InstanceCmd::Type isInstanceCmd = InstanceCmd::No);
+
+ ///
+ /// Send a message with data.
+ /// Note: To be used as a new message and not as a response to a previous request.
+ ///
+ void sendNewRequest(const QJsonValue &infoData, const JsonApiCommand& cmd);
+
+ ///
+ /// Send a message with data
+ /// Note: To be used as a new message and not as a response to a previous request.
+ ///
+ void sendNewRequest(const QJsonValue &infoData, const QString &command, InstanceCmd::Type isInstanceCmd = InstanceCmd::No);
///
/// Send an error message back to the client
///
/// @param error String describing the error
///
- void sendErrorReply(const QString &error, const QString &command = "", int tan = 0);
+ void sendErrorReply(const QString &error, const JsonApiCommand& cmd);
+
+ ///
+ /// Send an error message back to the client
+ ///
+ /// @param error String describing the error
+ /// @param errorDetails additional information detailing the error scenario
+ ///
+ void sendErrorReply(const QString &error, const QStringList& errorDetails, const JsonApiCommand& cmd);
+
+ ///
+ /// Send an error message back to the client
+ ///
+ /// @param error String describing the error
+ /// @param errorDetails additional information detailing the error scenario
+ ///
+ void sendErrorReply(const QString &error, const QStringList& errorDetails = {}, const QString &command = "", int tan = 0, InstanceCmd::Type isInstanceCmd = InstanceCmd::No);
+
+ void sendNoAuthorization(const JsonApiCommand& cmd);
///
/// @brief Kill all signal/slot connections to stop possible data emitter
///
- void stopDataConnections();
+ void stopDataConnections() override;
+
+ static QString findCommand (const QString& jsonS);
+ static int findTan (const QString& jsonString);
+
+ // true if further callbacks are forbidden (http)
+ bool _noListener;
+
+ /// The peer address of the client
+ QString _peerAddress;
+
+ // The JsonCallbacks instance which handles data subscription/notifications
+ QSharedPointer _jsonCB;
+
};
diff --git a/include/api/JsonApiCommand.h b/include/api/JsonApiCommand.h
new file mode 100644
index 00000000..c63d968b
--- /dev/null
+++ b/include/api/JsonApiCommand.h
@@ -0,0 +1,332 @@
+#ifndef JSONAPICOMMAND_H
+#define JSONAPICOMMAND_H
+
+#include
+#include
+#include
+
+class Command {
+public:
+ enum Type {
+ Unknown,
+ Adjustment,
+ Authorize,
+ Clear,
+ ClearAll,
+ Color,
+ ComponentState,
+ Config,
+ Correction,
+ CreateEffect,
+ DeleteEffect,
+ Effect,
+ Image,
+ InputSource,
+ Instance,
+ LedColors,
+ LedDevice,
+ Logging,
+ Processing,
+ ServerInfo,
+ Service,
+ SourceSelect,
+ SysInfo,
+ System,
+ Temperature,
+ Transform,
+ VideoMode
+ };
+
+ static QString toString(Type type) {
+ switch (type) {
+ case Adjustment: return "adjustment";
+ case Authorize: return "authorize";
+ case Clear: return "clear";
+ case ClearAll: return "clearall";
+ case Color: return "color";
+ case ComponentState: return "componentstate";
+ case Config: return "config";
+ case Correction: return "correction";
+ case CreateEffect: return "create-effect";
+ case DeleteEffect: return "delete-effect";
+ case Effect: return "effect";
+ case Image: return "image";
+ case InputSource: return "inputsource";
+ case Instance: return "instance";
+ case LedColors: return "ledcolors";
+ case LedDevice: return "leddevice";
+ case Logging: return "logging";
+ case Processing: return "processing";
+ case ServerInfo: return "serverinfo";
+ case SourceSelect: return "sourceselect";
+ case SysInfo: return "sysinfo";
+ case System: return "system";
+ case Temperature: return "temperature";
+ case Transform: return "transform";
+ case VideoMode: return "videomode";
+ case Service: return "service";
+ default: return "unknown";
+ }
+ }
+};
+
+class SubCommand {
+public:
+ enum Type {
+ Unknown,
+ Empty,
+ AdminRequired,
+ AddAuthorization,
+ AnswerRequest,
+ CreateInstance,
+ CreateToken,
+ DeleteInstance,
+ DeleteToken,
+ Discover,
+ GetConfig,
+ GetInfo,
+ GetPendingTokenRequests,
+ GetProperties,
+ GetSchema,
+ GetSubscriptionCommands,
+ GetSubscriptions,
+ GetTokenList,
+ Identify,
+ Idle,
+ ImageStreamStart,
+ ImageStreamStop,
+ LedStreamStart,
+ LedStreamStop,
+ Login,
+ Logout,
+ NewPassword,
+ NewPasswordRequired,
+ Reload,
+ RenameToken,
+ RequestToken,
+ Restart,
+ RestoreConfig,
+ Resume,
+ SaveName,
+ SetConfig,
+ Start,
+ StartInstance,
+ Stop,
+ StopInstance,
+ Subscribe,
+ Suspend,
+ SwitchTo,
+ ToggleIdle,
+ ToggleSuspend,
+ TokenRequired,
+ Unsubscribe
+ };
+
+ static QString toString(Type type) {
+ switch (type) {
+ case Empty: return "";
+ case AdminRequired: return "adminRequired";
+ case AddAuthorization: return "addAuthorization";
+ case AnswerRequest: return "answerRequest";
+ case CreateInstance: return "createInstance";
+ case CreateToken: return "createToken";
+ case DeleteInstance: return "deleteInstance";
+ case DeleteToken: return "deleteToken";
+ case Discover: return "discover";
+ case GetConfig: return "getconfig";
+ case GetInfo: return "getInfo";
+ case GetPendingTokenRequests: return "getPendingTokenRequests";
+ case GetProperties: return "getProperties";
+ case GetSchema: return "getschema";
+ case GetSubscriptionCommands: return "getSubscriptionCommands";
+ case GetSubscriptions: return "getSubscriptions";
+ case GetTokenList: return "getTokenList";
+ case Identify: return "identify";
+ case Idle: return "idle";
+ case ImageStreamStart: return "imagestream-start";
+ case ImageStreamStop: return "imagestream-stop";
+ case LedStreamStart: return "ledstream-start";
+ case LedStreamStop: return "ledstream-stop";
+ case Login: return "login";
+ case Logout: return "logout";
+ case NewPassword: return "newPassword";
+ case NewPasswordRequired: return "newPasswordRequired";
+ case Reload: return "reload";
+ case RenameToken: return "renameToken";
+ case RequestToken: return "requestToken";
+ case Restart: return "restart";
+ case RestoreConfig: return "restoreconfig";
+ case Resume: return "resume";
+ case SaveName: return "saveName";
+ case SetConfig: return "setconfig";
+ case Start: return "start";
+ case StartInstance: return "startInstance";
+ case Stop: return "stop";
+ case StopInstance: return "stopInstance";
+ case Subscribe: return "subscribe";
+ case Suspend: return "suspend";
+ case SwitchTo: return "switchTo";
+ case ToggleIdle: return "toggleIdle";
+ case ToggleSuspend: return "toggleSuspend";
+ case TokenRequired: return "tokenRequired";
+ case Unsubscribe: return "unsubscribe";
+ default: return "unknown";
+ }
+ }
+};
+
+class Authorization {
+public:
+ enum Type {
+ Admin,
+ Yes,
+ No
+ };
+};
+
+class NoListenerCmd {
+public:
+ enum Type {
+ No,
+ Yes
+ };
+};
+
+class InstanceCmd {
+public:
+ enum Type {
+ No,
+ Yes,
+ Multi
+ };
+};
+
+class JsonApiCommand {
+public:
+
+ JsonApiCommand()
+ : command(Command::Unknown),
+ subCommand(SubCommand::Unknown),
+ tan(0),
+ authorization(Authorization::Admin),
+ isInstanceCmd(InstanceCmd::No),
+ isNolistenerCmd(NoListenerCmd::Yes)
+ {}
+
+ JsonApiCommand(Command::Type command, SubCommand::Type subCommand,
+ Authorization::Type authorization,
+ InstanceCmd::Type isInstanceCmd,
+ NoListenerCmd::Type isNolistenerCmd,
+ int tan = 0)
+ : command(command),
+ subCommand(subCommand),
+ tan(tan),
+ authorization(authorization),
+ isInstanceCmd(isInstanceCmd),
+ isNolistenerCmd(isNolistenerCmd)
+ {}
+
+ Command::Type getCommand() const { return command; }
+ SubCommand::Type getSubCommand() const { return subCommand; }
+ InstanceCmd::Type instanceCmd() const { return isInstanceCmd; }
+ int getTan() const { return tan; }
+
+ QString toString() const {
+ QString cmd = Command::toString(command);
+ if (subCommand > SubCommand::Empty) {
+ cmd += QString("-%2").arg(SubCommand::toString(subCommand));
+ }
+ return cmd;
+ }
+
+ Command::Type command;
+ SubCommand::Type subCommand;
+ int tan;
+
+ Authorization::Type authorization;
+ InstanceCmd::Type isInstanceCmd;
+ NoListenerCmd::Type isNolistenerCmd;
+};
+
+typedef QMap, JsonApiCommand> CommandLookupMap;
+
+class ApiCommandRegister {
+public:
+
+ static const CommandLookupMap& getCommandLookup() {
+ static const CommandLookupMap commandLookup {
+ { {"adjustment", ""}, { Command::Adjustment, SubCommand::Empty, Authorization::Yes, InstanceCmd::Multi, NoListenerCmd::Yes} },
+ { {"authorize", "adminRequired"}, { Command::Authorize, SubCommand::AdminRequired, Authorization::No, InstanceCmd::No, NoListenerCmd::Yes} },
+ { {"authorize", "answerRequest"}, { Command::Authorize, SubCommand::AnswerRequest, Authorization::Admin, InstanceCmd::No, NoListenerCmd::No} },
+ { {"authorize", "createToken"}, { Command::Authorize, SubCommand::CreateToken, Authorization::Admin, InstanceCmd::No, NoListenerCmd::No} },
+ { {"authorize", "deleteToken"}, { Command::Authorize, SubCommand::DeleteToken, Authorization::Admin, InstanceCmd::No, NoListenerCmd::Yes} },
+ { {"authorize", "getPendingTokenRequests"}, { Command::Authorize, SubCommand::GetPendingTokenRequests, Authorization::Admin, InstanceCmd::No, NoListenerCmd::No} },
+ { {"authorize", "getTokenList"}, { Command::Authorize, SubCommand::GetTokenList, Authorization::Admin, InstanceCmd::No, NoListenerCmd::Yes} },
+ { {"authorize", "login"}, { Command::Authorize, SubCommand::Login, Authorization::No, InstanceCmd::No, NoListenerCmd::No} },
+ { {"authorize", "logout"}, { Command::Authorize, SubCommand::Logout, Authorization::No, InstanceCmd::No, NoListenerCmd::No} },
+ { {"authorize", "newPassword"}, { Command::Authorize, SubCommand::NewPassword, Authorization::Admin, InstanceCmd::No, NoListenerCmd::Yes} },
+ { {"authorize", "newPasswordRequired"}, { Command::Authorize, SubCommand::NewPasswordRequired, Authorization::No, InstanceCmd::No, NoListenerCmd::Yes} },
+ { {"authorize", "renameToken"}, { Command::Authorize, SubCommand::RenameToken, Authorization::Admin, InstanceCmd::No, NoListenerCmd::Yes} },
+ { {"authorize", "requestToken"}, { Command::Authorize, SubCommand::RequestToken, Authorization::No, InstanceCmd::No, NoListenerCmd::Yes} },
+ { {"authorize", "tokenRequired"}, { Command::Authorize, SubCommand::TokenRequired, Authorization::No, InstanceCmd::No, NoListenerCmd::Yes} },
+ { {"clear", ""}, { Command::Clear, SubCommand::Empty, Authorization::Yes, InstanceCmd::Multi, NoListenerCmd::Yes} },
+ { {"clearall", ""}, { Command::ClearAll, SubCommand::Empty, Authorization::Yes, InstanceCmd::Multi, NoListenerCmd::Yes} },
+ { {"color", ""}, { Command::Color, SubCommand::Empty, Authorization::Yes, InstanceCmd::Multi, NoListenerCmd::Yes} },
+ { {"componentstate", ""}, { Command::ComponentState, SubCommand::Empty, Authorization::Yes, InstanceCmd::Multi, NoListenerCmd::Yes} },
+ { {"config", "getconfig"}, { Command::Config, SubCommand::GetConfig, Authorization::Admin, InstanceCmd::Yes, NoListenerCmd::Yes} },
+ { {"config", "getschema"}, { Command::Config, SubCommand::GetSchema, Authorization::Admin, InstanceCmd::Yes, NoListenerCmd::Yes} },
+ { {"config", "reload"}, { Command::Config, SubCommand::Reload, Authorization::Admin, InstanceCmd::Yes, NoListenerCmd::Yes} },
+ { {"config", "restoreconfig"}, { Command::Config, SubCommand::RestoreConfig, Authorization::Admin, InstanceCmd::Yes, NoListenerCmd::Yes} },
+ { {"config", "setconfig"}, { Command::Config, SubCommand::SetConfig, Authorization::Admin, InstanceCmd::Yes, NoListenerCmd::Yes} },
+ { {"correction", ""}, { Command::Correction, SubCommand::Empty, Authorization::Yes, InstanceCmd::Yes, NoListenerCmd::Yes} },
+ { {"create-effect", ""}, { Command::CreateEffect, SubCommand::Empty, Authorization::Yes, InstanceCmd::Yes, NoListenerCmd::Yes} },
+ { {"delete-effect", ""}, { Command::DeleteEffect, SubCommand::Empty, Authorization::Yes, InstanceCmd::Yes, NoListenerCmd::Yes} },
+ { {"effect", ""}, { Command::Effect, SubCommand::Empty, Authorization::Yes, InstanceCmd::Multi, NoListenerCmd::Yes} },
+ { {"image", ""}, { Command::Image, SubCommand::Empty, Authorization::Yes, InstanceCmd::Multi, NoListenerCmd::Yes} },
+ { {"inputsource", "discover"}, { Command::InputSource, SubCommand::Discover, Authorization::Yes, InstanceCmd::No, NoListenerCmd::Yes} },
+ { {"inputsource", "getProperties"}, { Command::InputSource, SubCommand::GetProperties, Authorization::Yes, InstanceCmd::No, NoListenerCmd::Yes} },
+ { {"instance", "createInstance"}, { Command::Instance, SubCommand::CreateInstance, Authorization::Admin, InstanceCmd::No, NoListenerCmd::Yes} },
+ { {"instance", "deleteInstance"}, { Command::Instance, SubCommand::DeleteInstance, Authorization::Admin, InstanceCmd::No, NoListenerCmd::Yes} },
+ { {"instance", "saveName"}, { Command::Instance, SubCommand::SaveName, Authorization::Admin, InstanceCmd::No, NoListenerCmd::Yes} },
+ { {"instance", "startInstance"}, { Command::Instance, SubCommand::StartInstance, Authorization::Yes, InstanceCmd::No, NoListenerCmd::Yes} },
+ { {"instance", "stopInstance"}, { Command::Instance, SubCommand::StopInstance, Authorization::Yes, InstanceCmd::No, NoListenerCmd::Yes} },
+ { {"instance", "switchTo"}, { Command::Instance, SubCommand::SwitchTo, Authorization::Yes, InstanceCmd::No, NoListenerCmd::Yes} },
+ { {"ledcolors", "imagestream-start"}, { Command::LedColors, SubCommand::ImageStreamStart, Authorization::Yes, InstanceCmd::Yes, NoListenerCmd::Yes} },
+ { {"ledcolors", "imagestream-stop"}, { Command::LedColors, SubCommand::ImageStreamStop, Authorization::Yes, InstanceCmd::Yes, NoListenerCmd::Yes} },
+ { {"ledcolors", "ledstream-start"}, { Command::LedColors, SubCommand::LedStreamStart, Authorization::Yes, InstanceCmd::Yes, NoListenerCmd::Yes} },
+ { {"ledcolors", "ledstream-stop"}, { Command::LedColors, SubCommand::LedStreamStop, Authorization::Yes, InstanceCmd::Yes, NoListenerCmd::Yes} },
+ { {"leddevice", "addAuthorization"}, { Command::LedDevice, SubCommand::AddAuthorization, Authorization::Yes, InstanceCmd::Yes, NoListenerCmd::Yes} },
+ { {"leddevice", "discover"}, { Command::LedDevice, SubCommand::Discover, Authorization::Yes, InstanceCmd::Yes, NoListenerCmd::Yes} },
+ { {"leddevice", "getProperties"}, { Command::LedDevice, SubCommand::GetProperties, Authorization::Yes, InstanceCmd::Yes, NoListenerCmd::Yes} },
+ { {"leddevice", "identify"}, { Command::LedDevice, SubCommand::Identify, Authorization::Yes, InstanceCmd::Yes, NoListenerCmd::Yes} },
+ { {"logging", "start"}, { Command::Logging, SubCommand::Start, Authorization::Yes, InstanceCmd::No, NoListenerCmd::Yes} },
+ { {"logging", "stop"}, { Command::Logging, SubCommand::Stop, Authorization::Yes, InstanceCmd::No, NoListenerCmd::Yes} },
+ { {"processing", ""}, { Command::Processing, SubCommand::Empty, Authorization::Yes, InstanceCmd::Multi, NoListenerCmd::Yes} },
+ { {"serverinfo", ""}, { Command::ServerInfo, SubCommand::Empty, Authorization::Yes, InstanceCmd::Yes, NoListenerCmd::Yes} },
+ { {"serverinfo", "getInfo"}, { Command::ServerInfo, SubCommand::GetInfo, Authorization::Yes, InstanceCmd::Yes, NoListenerCmd::Yes} },
+ { {"serverinfo", "subscribe"}, { Command::ServerInfo, SubCommand::Subscribe, Authorization::Yes, InstanceCmd::Yes, NoListenerCmd::No} },
+ { {"serverinfo", "unsubscribe"}, { Command::ServerInfo, SubCommand::Unsubscribe, Authorization::Yes, InstanceCmd::Yes, NoListenerCmd::No} },
+ { {"serverinfo", "getSubscriptions"}, { Command::ServerInfo, SubCommand::GetSubscriptions, Authorization::Yes, InstanceCmd::Yes, NoListenerCmd::No} },
+ { {"serverinfo", "getSubscriptionCommands"}, { Command::ServerInfo, SubCommand::GetSubscriptionCommands, Authorization::No, InstanceCmd::No, NoListenerCmd::No} },
+ { {"service", "discover"}, { Command::Service, SubCommand::Discover, Authorization::Yes, InstanceCmd::No, NoListenerCmd::Yes} },
+ { {"sourceselect", ""}, { Command::SourceSelect, SubCommand::Empty, Authorization::Yes, InstanceCmd::Multi, NoListenerCmd::Yes} },
+ { {"sysinfo", ""}, { Command::SysInfo, SubCommand::Empty, Authorization::Yes, InstanceCmd::No, NoListenerCmd::Yes} },
+ { {"system", "restart"}, { Command::System, SubCommand::Restart, Authorization::Yes, InstanceCmd::No, NoListenerCmd::Yes} },
+ { {"system", "resume"}, { Command::System, SubCommand::Resume, Authorization::Yes, InstanceCmd::No, NoListenerCmd::Yes} },
+ { {"system", "suspend"}, { Command::System, SubCommand::Suspend, Authorization::Yes, InstanceCmd::No, NoListenerCmd::Yes} },
+ { {"system", "toggleSuspend"}, { Command::System, SubCommand::ToggleSuspend, Authorization::Yes, InstanceCmd::No, NoListenerCmd::Yes} },
+ { {"system", "idle"}, { Command::System, SubCommand::Idle, Authorization::Yes, InstanceCmd::No, NoListenerCmd::Yes} },
+ { {"system", "toggleIdle"}, { Command::System, SubCommand::ToggleIdle, Authorization::Yes, InstanceCmd::No, NoListenerCmd::Yes} },
+ { {"temperature", ""}, { Command::Temperature, SubCommand::Empty, Authorization::Yes, InstanceCmd::Yes, NoListenerCmd::Yes} },
+ { {"transform", ""}, { Command::Transform, SubCommand::Empty, Authorization::Yes, InstanceCmd::Yes, NoListenerCmd::Yes} },
+ { {"videomode", ""}, { Command::VideoMode, SubCommand::Empty, Authorization::Yes, InstanceCmd::No, NoListenerCmd::Yes} }
+ };
+ return commandLookup;
+ }
+
+ static JsonApiCommand getCommandInfo(const QString& command, const QString& subCommand) {
+ return getCommandLookup().value({command, subCommand});
+ }
+};
+
+#endif // JSONAPICOMMAND_H
diff --git a/include/api/JsonApiSubscription.h b/include/api/JsonApiSubscription.h
new file mode 100644
index 00000000..3d011bbd
--- /dev/null
+++ b/include/api/JsonApiSubscription.h
@@ -0,0 +1,135 @@
+#ifndef JSONAPISUBSCRIPTION_H
+#define JSONAPISUBSCRIPTION_H
+
+#include // Required to determine the cmake options
+
+#include
+#include
+
+
+class Subscription {
+public:
+ enum Type {
+ Unknown,
+ AdjustmentUpdate,
+ ComponentsUpdate,
+#if defined(ENABLE_EFFECTENGINE)
+ EffectsUpdate,
+#endif
+ EventUpdate,
+ ImageToLedMappingUpdate,
+ ImageUpdate,
+ InstanceUpdate,
+ LedColorsUpdate,
+ LedsUpdate,
+ LogMsgUpdate,
+ PrioritiesUpdate,
+ SettingsUpdate,
+ TokenUpdate,
+ VideomodeUpdate
+ };
+
+ static QString toString(Type type) {
+ switch (type) {
+ case AdjustmentUpdate: return "adjustment-update";
+ case ComponentsUpdate: return "components-update";
+#if defined(ENABLE_EFFECTENGINE)
+ case EffectsUpdate: return "effects-update";
+#endif
+ case EventUpdate: return "event-update";
+ case ImageToLedMappingUpdate: return "imageToLedMapping-update";
+ case ImageUpdate: return "ledcolors-imagestream-update";
+ case InstanceUpdate: return "instance-update";
+ case LedColorsUpdate: return "ledcolors-ledstream-update";
+ case LedsUpdate: return "leds-update";
+ case LogMsgUpdate: return "logmsg-update";
+ case PrioritiesUpdate: return "priorities-update";
+ case SettingsUpdate: return "settings-update";
+ case TokenUpdate: return "token-update";
+ case VideomodeUpdate: return "videomode-update";
+ default: return "unknown";
+ }
+ }
+
+ static bool isInstanceSpecific(Type type) {
+ switch (type) {
+ case AdjustmentUpdate:
+ case ComponentsUpdate:
+#if defined(ENABLE_EFFECTENGINE)
+ case EffectsUpdate:
+#endif
+ case ImageToLedMappingUpdate:
+ case ImageUpdate:
+ case LedColorsUpdate:
+ case LedsUpdate:
+ case PrioritiesUpdate:
+ case SettingsUpdate:
+ return true;
+ case EventUpdate:
+ case InstanceUpdate:
+ case LogMsgUpdate:
+ case TokenUpdate:
+ case VideomodeUpdate:
+ default:
+ return false;
+ }
+ }
+};
+
+class JsonApiSubscription {
+public:
+
+ JsonApiSubscription()
+ : cmd(Subscription::Unknown),
+ isAll(false)
+ {}
+
+ JsonApiSubscription(Subscription::Type cmd, bool isAll)
+ : cmd(cmd),
+ isAll(isAll)
+ {}
+
+ Subscription::Type getSubscription() const { return cmd; }
+ bool isPartOfAll() const { return isAll; }
+
+ QString toString() const {
+ return Subscription::toString(cmd);
+ }
+
+ Subscription::Type cmd;
+ bool isAll;
+};
+
+typedef QMap SubscriptionLookupMap;
+
+class ApiSubscriptionRegister {
+public:
+
+ static const SubscriptionLookupMap& getSubscriptionLookup() {
+ static const SubscriptionLookupMap subscriptionLookup {
+ { {"adjustment-update"}, { Subscription::AdjustmentUpdate, true} },
+ { {"components-update"}, { Subscription::ComponentsUpdate, true} },
+#if defined(ENABLE_EFFECTENGINE)
+ { {"effects-update"}, { Subscription::EffectsUpdate, true} },
+#endif
+ { {"event-update"}, { Subscription::EventUpdate, true} },
+ { {"imageToLedMapping-update"}, { Subscription::ImageToLedMappingUpdate, true} },
+ { {"ledcolors-imagestream-update"}, { Subscription::ImageUpdate, false} },
+ { {"ledcolors-ledstream-update"}, { Subscription::LedColorsUpdate, false} },
+ { {"instance-update"}, { Subscription::InstanceUpdate, true} },
+ { {"leds-update"}, { Subscription::LedsUpdate, true} },
+ { {"logmsg-update"}, { Subscription::LogMsgUpdate, false} },
+ { {"priorities-update"}, { Subscription::PrioritiesUpdate, true} },
+ { {"settings-update"}, { Subscription::SettingsUpdate, true} },
+ { {"token-update"}, { Subscription::TokenUpdate, true} },
+ { {"videomode-update"}, { Subscription::VideomodeUpdate, true} }
+ };
+ return subscriptionLookup;
+ }
+
+ static JsonApiSubscription getSubscriptionInfo(const QString& subscription) {
+ return getSubscriptionLookup().value({subscription});
+ }
+};
+
+#endif // JSONAPISUBSCRIPTION_H
diff --git a/include/api/JsonCB.h b/include/api/JsonCallbacks.h
similarity index 53%
rename from include/api/JsonCB.h
rename to include/api/JsonCallbacks.h
index 2d59b3eb..a5c14ce4 100644
--- a/include/api/JsonCB.h
+++ b/include/api/JsonCallbacks.h
@@ -1,51 +1,82 @@
#pragma once
+#include "api/JsonApiSubscription.h"
+#include
+#include
+
// qt incl
#include
#include
+#include
-// components def
#include
-
-// videModes
#include
-// settings
#include
-// AuthManager
#include
-
#include
class Hyperion;
class ComponentRegister;
class PriorityMuxer;
-class JsonCB : public QObject
+class JsonCallbacks : public QObject
{
Q_OBJECT
public:
- JsonCB(QObject* parent);
+ JsonCallbacks(Logger* log, const QString& peerAddress, QObject* parent);
///
/// @brief Subscribe to future data updates given by cmd
- /// @param cmd The cmd which will be subscribed for
- /// @param unsubscribe Revert subscription
+ /// @param cmd The cmd which will be subscribed for
/// @return True on success, false if not found
///
- bool subscribeFor(const QString& cmd, bool unsubscribe = false);
+ bool subscribe(const QString& cmd);
+
+ ///
+ /// @brief Subscribe to future data updates given by subscription list
+ /// @param type Array of subscriptionsm
+ ///
+ QStringList subscribe(const QJsonArray& subscriptions);
+
+ ///
+ /// @brief Subscribe to future data updates given by cmd
+ /// @param cmd The cmd which will be subscribed to
+ /// @return True on success, false if not found
+ ///
+ bool subscribe(Subscription::Type subscription);
+
+ ///
+ /// @brief Unsubscribe to future data updates given by cmd
+ /// @param cmd The cmd which will be unsubscribed
+ /// @return True on success, false if not found
+ ///
+ bool unsubscribe(const QString& cmd);
+
+ ///
+ /// @brief Unsubscribe to future data updates given by subscription list
+ /// @param type Array of subscriptions
+ ///
+ QStringList unsubscribe(const QJsonArray& subscriptions);
+
+ ///
+ /// @brief Unsubscribe to future data updates given by cmd
+ /// @param cmd The cmd which will be subscribed to
+ /// @return True on success, false if not found
+ ///
+ bool unsubscribe(Subscription::Type cmd);
///
/// @brief Get all possible commands to subscribe for
+ /// @param fullList Return all possible commands or those not triggered by API requests (subscriptions="ALL")
/// @return The list of commands
///
- QStringList getCommands() { return _availableCommands; };
-
+ QStringList getCommands(bool fullList = true) const;
///
/// @brief Get all subscribed commands
/// @return The list of commands
///
- QStringList getSubscribedCommands() { return _subscribedCommands; };
+ QStringList getSubscribedCommands() const;
///
/// @brief Reset subscriptions, disconnect all signals
@@ -124,18 +155,49 @@ private slots:
///
void handleTokenChange(const QVector &def);
+ ///
+ /// @brief Is called whenever the current Hyperion instance pushes new led raw values (if enabled)
+ /// @param ledColors The current led colors
+ ///
+ void handleLedColorUpdate(const std::vector &ledColors);
+
+ ///
+ /// @brief Is called whenever the current Hyperion instance pushes new image update (if enabled)
+ /// @param image The current image
+ ///
+ void handleImageUpdate(const Image &image);
+
+ ///
+ /// @brief Process and push new log messages from logger (if enabled)
+ ///
+ void handleLogMessageUpdate(const Logger::T_LOG_MESSAGE &);
+
+ ///
+ /// @brief Is called whenever an event is triggert
+ /// @param image The current event
+ ///
+ void handleEventUpdate(const Event &event);
+
private:
- /// pointer of Hyperion instance
+
+ /// construct callback msg
+ void doCallback(Subscription::Type cmd, const QVariant& data);
+
+ Logger *_log;
Hyperion* _hyperion;
+
+ /// The peer address of the client
+ QString _peerAddress;
+
/// pointer of comp register
ComponentRegister* _componentRegister;
/// priority muxer instance
PriorityMuxer* _prioMuxer;
- /// contains all available commands
- QStringList _availableCommands;
+
/// contains active subscriptions
- QStringList _subscribedCommands;
- /// construct callback msg
- void doCallback(const QString& cmd, const QVariant& data);
+ QSet _subscribedCommands;
+
+ /// flag to determine state of log streaming
+ bool _islogMsgStreamingActive;
};
diff --git a/include/api/JsonInfo.h b/include/api/JsonInfo.h
new file mode 100644
index 00000000..1346f97e
--- /dev/null
+++ b/include/api/JsonInfo.h
@@ -0,0 +1,43 @@
+#ifndef JSONINFO_H
+#define JSONINFO_H
+
+#include
+#include
+#include
+
+#include
+#include
+
+class JsonInfo
+{
+
+public:
+ static QJsonArray getAdjustmentInfo(const Hyperion* hyperion, Logger* log);
+ static QJsonArray getPrioritiestInfo(const Hyperion* hyperion);
+ static QJsonArray getPrioritiestInfo(int currentPriority, const PriorityMuxer::InputsMap& activeInputs);
+ static QJsonArray getEffects(const Hyperion* hyperion);
+ static QJsonArray getAvailableScreenGrabbers();
+ static QJsonArray getAvailableVideoGrabbers();
+ static QJsonArray getAvailableAudioGrabbers();
+ static QJsonObject getGrabbers(const Hyperion* hyperion);
+ static QJsonObject getAvailableLedDevices();
+ static QJsonObject getCecInfo();
+ static QJsonArray getServices();
+ static QJsonArray getComponents(const Hyperion* hyperion);
+ static QJsonArray getInstanceInfo();
+ static QJsonArray getActiveEffects(const Hyperion* hyperion);
+ static QJsonArray getActiveColors(const Hyperion* hyperion);
+ static QJsonArray getTransformationInfo(const Hyperion* hyperion);
+ static QJsonObject getSystemInfo(const Hyperion* hyperion);
+ QJsonObject discoverSources (const QString& sourceType, const QJsonObject& params);
+
+private:
+
+ template
+ void discoverGrabber(QJsonArray& inputs, const QJsonObject& params) const;
+ QJsonArray discoverScreenInputs(const QJsonObject& params) const;
+ QJsonArray discoverVideoInputs(const QJsonObject& params) const;
+ QJsonArray discoverAudioInputs(const QJsonObject& params) const;
+};
+
+#endif // JSONINFO_H
diff --git a/include/api/apiStructs.h b/include/api/apiStructs.h
index 112132fc..de1d6e67 100644
--- a/include/api/apiStructs.h
+++ b/include/api/apiStructs.h
@@ -2,6 +2,9 @@
#include
#include
+#include
+
+#include
struct ImageCmdData
{
diff --git a/include/db/AuthTable.h b/include/db/AuthTable.h
index c6495ea5..161c3856 100644
--- a/include/db/AuthTable.h
+++ b/include/db/AuthTable.h
@@ -8,6 +8,11 @@
#include
#include
+namespace hyperion {
+const char DEFAULT_USER[] = "Hyperion";
+const char DEFAULT_PASSWORD[] = "hyperion";
+}
+
///
/// @brief Authentication table interface
///
@@ -149,10 +154,10 @@ public:
inline bool resetHyperionUser()
{
QVariantMap map;
- map["password"] = calcPasswordHashOfUser("Hyperion", "hyperion");
+ map["password"] = calcPasswordHashOfUser(hyperion::DEFAULT_USER, hyperion::DEFAULT_PASSWORD);
VectorPair cond;
- cond.append(CPair("user", "Hyperion"));
+ cond.append(CPair("user", hyperion::DEFAULT_USER));
return updateRecord(cond, map);
}
diff --git a/include/events/EventEnum.h b/include/events/EventEnum.h
index 54f9c2c8..8d7934a8 100644
--- a/include/events/EventEnum.h
+++ b/include/events/EventEnum.h
@@ -13,7 +13,8 @@ enum class Event
ResumeIdle,
ToggleIdle,
Reload,
- Restart
+ Restart,
+ Quit
};
inline const char* eventToString(Event event)
@@ -24,6 +25,7 @@ inline const char* eventToString(Event event)
case Event::Resume: return "Resume";
case Event::ToggleSuspend: return "ToggleSuspend";
case Event::Idle: return "Idle";
+ case Event::Quit: return "Quit";
case Event::ResumeIdle: return "ResumeIdle";
case Event::ToggleIdle: return "ToggleIdle";
case Event::Reload: return "Reload";
@@ -39,6 +41,7 @@ inline Event stringToEvent(const QString& event)
if (event.compare("Resume")==0) return Event::Resume;
if (event.compare("ToggleSuspend")==0) return Event::ToggleSuspend;
if (event.compare("Idle")==0) return Event::Idle;
+ if (event.compare("Quit")==0) return Event::Quit;
if (event.compare("ResumeIdle")==0) return Event::ResumeIdle;
if (event.compare("ToggleIdle")==0) return Event::ToggleIdle;
if (event.compare("Reload")==0) return Event::Reload;
diff --git a/include/events/OsEventHandler.h b/include/events/OsEventHandler.h
index e65865f5..6bed4f28 100644
--- a/include/events/OsEventHandler.h
+++ b/include/events/OsEventHandler.h
@@ -29,6 +29,7 @@ public:
public slots:
void suspend(bool sleep);
void lock(bool isLocked);
+ void quit();
virtual void handleSettingsUpdate(settings::type type, const QJsonDocument& config);
@@ -101,6 +102,7 @@ public:
void handleSignal(int signum);
+
private:
static OsEventHandlerLinux* getInstance();
diff --git a/include/grabber/GrabberConfig.h b/include/grabber/GrabberConfig.h
new file mode 100644
index 00000000..f3a575c2
--- /dev/null
+++ b/include/grabber/GrabberConfig.h
@@ -0,0 +1,58 @@
+#ifndef GRABBERCONFIG_H
+#define GRABBERCONFIG_H
+
+#if defined(ENABLE_MF)
+#include
+#elif defined(ENABLE_V4L2)
+#include
+#endif
+
+#if defined(ENABLE_AUDIO)
+#include
+
+#ifdef WIN32
+#include
+#endif
+
+#ifdef __linux__
+#include
+#endif
+#endif
+
+#ifdef ENABLE_QT
+#include
+#endif
+
+#ifdef ENABLE_DX
+#include
+#endif
+
+#if defined(ENABLE_X11)
+#include
+#endif
+
+#if defined(ENABLE_XCB)
+#include
+#endif
+
+#if defined(ENABLE_DX)
+#include
+#endif
+
+#if defined(ENABLE_FB)
+#include
+#endif
+
+#if defined(ENABLE_DISPMANX)
+#include
+#endif
+
+#if defined(ENABLE_AMLOGIC)
+#include
+#endif
+
+#if defined(ENABLE_OSX)
+#include
+#endif
+
+#endif // GRABBERCONFIG_H
diff --git a/include/grabber/video/mediafoundation/MFGrabber.h b/include/grabber/video/mediafoundation/MFGrabber.h
index 30142069..47c8cc62 100644
--- a/include/grabber/video/mediafoundation/MFGrabber.h
+++ b/include/grabber/video/mediafoundation/MFGrabber.h
@@ -119,7 +119,7 @@ private:
QAtomicInt _currentFrame;
ColorRgb _noSignalThresholdColor;
bool _signalDetectionEnabled,
- _noSignalDetected,
+ _signalDetected,
_initialized,
_reload;
double _x_frac_min,
diff --git a/include/grabber/video/v4l2/V4L2Grabber.h b/include/grabber/video/v4l2/V4L2Grabber.h
index 75b375df..5ac00738 100644
--- a/include/grabber/video/v4l2/V4L2Grabber.h
+++ b/include/grabber/video/v4l2/V4L2Grabber.h
@@ -158,7 +158,7 @@ private:
// signal detection
int _noSignalCounterThreshold;
ColorRgb _noSignalThresholdColor;
- bool _standbyActivated, _signalDetectionEnabled, _noSignalDetected;
+ bool _standbyActivated, _signalDetectionEnabled, _signalDetected;
int _noSignalCounter;
int _brightness, _contrast, _saturation, _hue;
double _x_frac_min;
diff --git a/include/hyperion/AuthManager.h b/include/hyperion/AuthManager.h
index 3ee594c5..790f70b6 100644
--- a/include/hyperion/AuthManager.h
+++ b/include/hyperion/AuthManager.h
@@ -3,6 +3,8 @@
#include
#include
+#include
+
//qt
#include
#include
@@ -41,24 +43,12 @@ public:
///
QString getID() const { return _uuid; }
- ///
- /// @brief Check authorization is required according to the user setting
- /// @return True if authorization required else false
- ///
- bool isAuthRequired() const { return _authRequired; }
-
///
/// @brief Check if authorization is required for local network connections
/// @return True if authorization required else false
///
bool isLocalAuthRequired() const { return _localAuthRequired; }
- ///
- /// @brief Check if authorization is required for local network connections for admin access
- /// @return True if authorization required else false
- ///
- bool isLocalAdminAuthRequired() const { return _localAdminAuthRequired; }
-
///
/// @brief Reset Hyperion user
/// @return True on success else false
@@ -172,7 +162,7 @@ public slots:
/// @param usr the defined user
/// @return The token
///
- QString getUserToken(const QString &usr = "Hyperion") const;
+ QString getUserToken(const QString &usr = hyperion::DEFAULT_USER) const;
///
/// @brief Get all available token entries
@@ -230,15 +220,9 @@ private:
/// All pending requests
QMap _pendingRequests;
- /// Reflect state of global auth
- bool _authRequired;
-
/// Reflect state of local auth
bool _localAuthRequired;
- /// Reflect state of local admin auth
- bool _localAdminAuthRequired;
-
/// Timer for counting against pendingRequest timeouts
QTimer *_timer;
diff --git a/include/hyperion/Hyperion.h b/include/hyperion/Hyperion.h
index 00f51a8d..2348072a 100644
--- a/include/hyperion/Hyperion.h
+++ b/include/hyperion/Hyperion.h
@@ -69,6 +69,7 @@ class Hyperion : public QObject
Q_OBJECT
public:
/// Type definition of the info structure used by the priority muxer
+ using InputsMap = PriorityMuxer::InputsMap;
using InputInfo = PriorityMuxer::InputInfo;
///
@@ -109,7 +110,7 @@ public:
///
QString getActiveDeviceType() const;
- bool getReadOnlyMode() {return _readOnlyMode; }
+ bool getReadOnlyMode() const {return _readOnlyMode; }
public slots:
@@ -252,13 +253,13 @@ public slots:
/// @param priority The priority channel of the effect
/// @param timeout The timeout of the effect (after the timout, the effect will be cleared)
int setEffect(const QString &effectName
- , const QJsonObject &args
- , int priority
- , int timeout = PriorityMuxer::ENDLESS
- , const QString &pythonScript = ""
- , const QString &origin="System"
- , const QString &imageData = ""
- );
+ , const QJsonObject &args
+ , int priority
+ , int timeout = PriorityMuxer::ENDLESS
+ , const QString &pythonScript = ""
+ , const QString &origin="System"
+ , const QString &imageData = ""
+ );
/// Get the list of available effects
/// @return The list of available effects
@@ -320,7 +321,14 @@ public slots:
QList getActivePriorities() const;
///
- /// Returns the information of a specific priorrity channel
+ /// Returns the information of all priority channels.
+ ///
+ /// @return The information fo all priority channels
+ ///
+ PriorityMuxer::InputsMap getPriorityInfo() const;
+
+ ///
+ /// Returns the information of a specific priority channel
///
/// @param[in] priority The priority channel
///
@@ -363,7 +371,7 @@ public slots:
/// @brief Get the component Register
/// return Component register pointer
///
- ComponentRegister* getComponentRegister() { return _componentRegister; }
+ ComponentRegister* getComponentRegister() const { return _componentRegister; }
///
/// @brief Called from components to update their current state. DO NOT CALL FROM USERS
diff --git a/include/hyperion/HyperionIManager.h b/include/hyperion/HyperionIManager.h
index 03e71690..d7610d5b 100644
--- a/include/hyperion/HyperionIManager.h
+++ b/include/hyperion/HyperionIManager.h
@@ -55,10 +55,16 @@ public slots:
Hyperion* getHyperionInstance(quint8 instance = 0);
///
- /// @brief Get instance data of all instaces in db + running state
+ /// @brief Get instance data of all instances in db + running state
///
QVector getInstanceData() const;
+
+ ///
+ /// @brief Get all instance indicies of running instances
+ ///
+ QList getRunningInstanceIdx() const;
+
///
/// @brief Start a Hyperion instance
/// @param instance Instance index
diff --git a/include/hyperion/PriorityMuxer.h b/include/hyperion/PriorityMuxer.h
index ed8d4fb5..d307843b 100644
--- a/include/hyperion/PriorityMuxer.h
+++ b/include/hyperion/PriorityMuxer.h
@@ -141,6 +141,13 @@ public:
///
QList getPriorities() const;
+ ///
+ /// Returns the information of all priority channels.
+ ///
+ /// @return The information fo all priority channels
+ ///
+ InputsMap getInputInfo() const;
+
///
/// Returns the information of a specified priority channel.
/// If a priority is no longer available the _lowestPriorityInfo (255) is returned
diff --git a/include/utils/JsonUtils.h b/include/utils/JsonUtils.h
index 694e279f..10b89d90 100644
--- a/include/utils/JsonUtils.h
+++ b/include/utils/JsonUtils.h
@@ -3,6 +3,8 @@
#include
#include
+#include
+#include
#include
namespace JsonUtils {
@@ -14,7 +16,7 @@ namespace JsonUtils {
/// @param[in] ignError Ignore errors during file read (no log output)
/// @return true on success else false
///
- bool readFile(const QString& path, QJsonObject& obj, Logger* log, bool ignError=false);
+ QPair readFile(const QString& path, QJsonObject& obj, Logger* log, bool ignError=false);
///
/// @brief read a schema file and resolve $refs
@@ -33,7 +35,7 @@ namespace JsonUtils {
/// @param[in] log The logger of the caller to print errors
/// @return true on success else false
///
- bool parse(const QString& path, const QString& data, QJsonObject& obj, Logger* log);
+ QPair parse(const QString& path, const QString& data, QJsonObject& obj, Logger* log);
///
/// @brief parse a json QString and get a QJsonArray. Overloaded function
@@ -42,8 +44,8 @@ namespace JsonUtils {
/// @param[out] arr Retuns the parsed QJsonArray
/// @param[in] log The logger of the caller to print errors
/// @return true on success else false
- ///
- bool parse(const QString& path, const QString& data, QJsonArray& arr, Logger* log);
+ //
+ QPair parse(const QString& path, const QString& data, QJsonArray& arr, Logger* log);
///
/// @brief parse a json QString and get a QJsonDocument
@@ -53,7 +55,7 @@ namespace JsonUtils {
/// @param[in] log The logger of the caller to print errors
/// @return true on success else false
///
- bool parse(const QString& path, const QString& data, QJsonDocument& doc, Logger* log);
+ QPair parse(const QString& path, const QString& data, QJsonDocument& doc, Logger* log);
///
/// @brief Validate json data against a schema
@@ -63,7 +65,7 @@ namespace JsonUtils {
/// @param[in] log The logger of the caller to print errors
/// @return true on success else false
///
- bool validate(const QString& file, const QJsonObject& json, const QString& schemaPath, Logger* log);
+ QPair validate(const QString& file, const QJsonObject& json, const QString& schemaPath, Logger* log);
///
/// @brief Validate json data against a schema
@@ -73,7 +75,7 @@ namespace JsonUtils {
/// @param[in] log The logger of the caller to print errors
/// @return true on success else false
///
- bool validate(const QString& file, const QJsonObject& json, const QJsonObject& schema, Logger* log);
+ QPair validate(const QString& file, const QJsonObject& json, const QJsonObject& schema, Logger* log);
///
/// @brief Write json data to file
diff --git a/include/utils/NetOrigin.h b/include/utils/NetOrigin.h
index 75796e66..648d3de3 100644
--- a/include/utils/NetOrigin.h
+++ b/include/utils/NetOrigin.h
@@ -47,7 +47,9 @@ private slots:
private:
Logger* _log;
/// True when internet access is allowed
- bool _internetAccessAllowed;
+ bool _isInternetAccessAllowed;
+ /// True when internet access is restricted by a white list
+ bool _isInternetAccessRestricted;
/// Whitelisted ip addresses
QList _ipWhitelist;
diff --git a/include/utils/jsonschema/QJsonFactory.h b/include/utils/jsonschema/QJsonFactory.h
index 2fcf5882..1a9190cd 100644
--- a/include/utils/jsonschema/QJsonFactory.h
+++ b/include/utils/jsonschema/QJsonFactory.h
@@ -31,7 +31,9 @@ public:
if (!schemaChecker.validate(configTree).first)
{
for (int i = 0; i < messages.size(); ++i)
+ {
std::cout << messages[i].toStdString() << std::endl;
+ }
std::cerr << "Validation failed for configuration file: " << config.toStdString() << std::endl;
return -3;
@@ -61,9 +63,10 @@ public:
if (error.error != QJsonParseError::NoError)
{
// report to the user the failure and their locations in the document.
- int errorLine(0), errorColumn(0);
+ int errorLine(0);
+ int errorColumn(0);
- for( int i=0, count=qMin( error.offset,config.size()); i
// stl includes
-#include
-#include
// Qt includes
#include
@@ -27,90 +25,91 @@
// ledmapping int <> string transform methods
#include
-// api includes
-#include
-
using namespace hyperion;
+// Constants
+namespace {
+
+const int IMAGE_HEIGHT_MAX = 2000;
+const int IMAGE_WIDTH_MAX = 2000;
+const int IMAGE_SCALE = 2000;
+}
+
API::API(Logger *log, bool localConnection, QObject *parent)
- : QObject(parent)
+ : QObject(parent)
{
qRegisterMetaType("int64_t");
qRegisterMetaType("VideoMode");
qRegisterMetaType>("std::map");
- // Init
- _log = log;
- _authManager = AuthManager::getInstance();
+ // Init
+ _log = log;
+ _authManager = AuthManager::getInstance();
_instanceManager = HyperionIManager::getInstance();
- _localConnection = localConnection;
+ _localConnection = localConnection;
- _authorized = false;
- _adminAuthorized = false;
+ _authorized = false;
+ _adminAuthorized = false;
- _currInstanceIndex = 0;
+ _currInstanceIndex = 0;
- // connect to possible token responses that has been requested
- connect(_authManager, &AuthManager::tokenResponse, [=] (bool success, QObject *caller, const QString &token, const QString &comment, const QString &id, const int &tan)
- {
- if (this == caller)
- emit onTokenResponse(success, token, comment, id, tan);
- });
+ // connect to possible token responses that has been requested
+ connect(_authManager, &AuthManager::tokenResponse, this, [=] (bool success, const QObject *caller, const QString &token, const QString &comment, const QString &tokenId, const int &tan)
+ {
+ if (this == caller)
+ {
+ emit onTokenResponse(success, token, comment, tokenId, tan);
+ }
+ });
- // connect to possible startInstance responses that has been requested
- connect(_instanceManager, &HyperionIManager::startInstanceResponse, [=] (QObject *caller, const int &tan)
- {
- if (this == caller)
- emit onStartInstanceResponse(tan);
- });
+ connect(_instanceManager, &HyperionIManager::startInstanceResponse, this, [=] (const QObject *caller, const int &tan)
+ {
+ if (this == caller)
+ {
+ emit onStartInstanceResponse(tan);
+ }
+ });
}
void API::init()
{
_hyperion = _instanceManager->getHyperionInstance(0);
+ _authorized = false;
- bool apiAuthRequired = _authManager->isAuthRequired();
-
- // For security we block external connections if default PW is set
- if (!_localConnection && API::hasHyperionDefaultPw())
- {
- emit forceClose();
- }
- // if this is localConnection and network allows unauth locals, set authorized flag
- if (apiAuthRequired && _localConnection)
+ // For security we block external connections, if default PW is set
+ if (!_localConnection && API::hasHyperionDefaultPw())
{
- _authorized = !_authManager->isLocalAuthRequired();
+ Warning(_log, "Non local network connect attempt identified, but default Hyperion passwort set! - Reject connection.");
+ emit forceClose();
}
- // admin access is allowed, when the connection is local and the option for local admin isn't set. Con: All local connections get full access
- if (_localConnection)
- {
- _adminAuthorized = !_authManager->isLocalAdminAuthRequired();
- // just in positive direction
- if (_adminAuthorized)
+ // if this is localConnection and network allows unauth locals
+ if ( _localConnection && !_authManager->isLocalAuthRequired())
+ {
+ _authorized = true;
+ }
+
+ // // admin access is only allowed after login via user & password or via authorization via token.
+ _adminAuthorized = false;
+}
+
+void API::setColor(int priority, const std::vector &ledColors, int timeout_ms, const QString &origin, hyperion::Components /*callerComp*/)
+{
+ if (ledColors.size() % 3 == 0)
+ {
+ std::vector fledColors;
+ for (unsigned i = 0; i < ledColors.size(); i += 3)
{
- _authorized = true;
+ fledColors.emplace_back(ColorRgb{ledColors[i], ledColors[i + 1], ledColors[i + 2]});
}
- }
+ QMetaObject::invokeMethod(_hyperion, "setColor", Qt::QueuedConnection, Q_ARG(int, priority), Q_ARG(std::vector, fledColors), Q_ARG(int, timeout_ms), Q_ARG(QString, origin));
+ }
}
-void API::setColor(int priority, const std::vector &ledColors, int timeout_ms, const QString &origin, hyperion::Components callerComp)
+bool API::setImage(ImageCmdData &data, hyperion::Components comp, QString &replyMsg, hyperion::Components /*callerComp*/)
{
- std::vector fledColors;
- if (ledColors.size() % 3 == 0)
- {
- for (unsigned i = 0; i < ledColors.size(); i += 3)
- {
- fledColors.emplace_back(ColorRgb{ledColors[i], ledColors[i + 1], ledColors[i + 2]});
- }
- QMetaObject::invokeMethod(_hyperion, "setColor", Qt::QueuedConnection, Q_ARG(int, priority), Q_ARG(std::vector, fledColors), Q_ARG(int, timeout_ms), Q_ARG(QString, origin));
- }
-}
-
-bool API::setImage(ImageCmdData &data, hyperion::Components comp, QString &replyMsg, hyperion::Components callerComp)
-{
- // truncate name length
- data.imgName.truncate(16);
+ // truncate name length
+ data.imgName.truncate(16);
if (!data.format.isEmpty())
{
@@ -128,424 +127,475 @@ bool API::setImage(ImageCmdData &data, hyperion::Components comp, QString &reply
}
QImage img = QImage::fromData(data.data, QSTRING_CSTR(data.format));
- if (img.isNull())
- {
+ if (img.isNull())
+ {
replyMsg = "Failed to parse picture, the file might be corrupted or content does not match the given format [" + data.format + "]";
- return false;
- }
+ return false;
+ }
- // check for requested scale
- if (data.scale > 24)
- {
- if (img.height() > data.scale)
- {
- img = img.scaledToHeight(data.scale);
- }
- if (img.width() > data.scale)
- {
- img = img.scaledToWidth(data.scale);
- }
- }
+ // check for requested scale
+ if (data.scale > 24)
+ {
+ if (img.height() > data.scale)
+ {
+ img = img.scaledToHeight(data.scale);
+ }
+ if (img.width() > data.scale)
+ {
+ img = img.scaledToWidth(data.scale);
+ }
+ }
- // check if we need to force a scale
- if (img.width() > 2000 || img.height() > 2000)
- {
- data.scale = 2000;
- if (img.height() > data.scale)
- {
- img = img.scaledToHeight(data.scale);
- }
- if (img.width() > data.scale)
- {
- img = img.scaledToWidth(data.scale);
- }
- }
+ // check if we need to force a scale
+ if (img.width() > IMAGE_WIDTH_MAX || img.height() > IMAGE_HEIGHT_MAX)
+ {
+ data.scale = IMAGE_SCALE;
+ if (img.height() > data.scale)
+ {
+ img = img.scaledToHeight(data.scale);
+ }
+ if (img.width() > data.scale)
+ {
+ img = img.scaledToWidth(data.scale);
+ }
+ }
- data.width = img.width();
- data.height = img.height();
+ data.width = img.width();
+ data.height = img.height();
- // extract image
- img = img.convertToFormat(QImage::Format_ARGB32_Premultiplied);
- data.data.clear();
- data.data.reserve(img.width() * img.height() * 3);
- for (int i = 0; i < img.height(); ++i)
- {
- const QRgb *scanline = reinterpret_cast(img.scanLine(i));
- for (int j = 0; j < img.width(); ++j)
- {
- data.data.append((char)qRed(scanline[j]));
- data.data.append((char)qGreen(scanline[j]));
- data.data.append((char)qBlue(scanline[j]));
- }
- }
- }
- else
- {
- // check consistency of the size of the received data
- if (data.data.size() != data.width * data.height * 3)
- {
- replyMsg = "Size of image data does not match with the width and height";
- return false;
- }
- }
+ // extract image
+ img = img.convertToFormat(QImage::Format_ARGB32_Premultiplied);
+ data.data.clear();
+ data.data.reserve(static_cast(img.width() * img.height() * 3));
+ for (int i = 0; i < img.height(); ++i)
+ {
+ const QRgb *scanline = reinterpret_cast(img.scanLine(i));
+ for (int j = 0; j < img.width(); ++j)
+ {
+ data.data.append(static_cast(qRed(scanline[j])));
+ data.data.append(static_cast(qGreen(scanline[j])));
+ data.data.append(static_cast(qBlue(scanline[j])));
+ }
+ }
+ }
+ else
+ {
+ // check consistency of the size of the received data
+ if (static_cast(data.data.size()) != static_cast(data.width) * static_cast(data.height) * 3)
+ {
+ replyMsg = "Size of image data does not match with the width and height";
+ return false;
+ }
+ }
- // copy image
- Image image(data.width, data.height);
- memcpy(image.memptr(), data.data.data(), data.data.size());
+ // copy image
+ Image image(data.width, data.height);
+ memcpy(image.memptr(), data.data.data(), static_cast(data.data.size()));
- QMetaObject::invokeMethod(_hyperion, "registerInput", Qt::QueuedConnection, Q_ARG(int, data.priority), Q_ARG(hyperion::Components, comp), Q_ARG(QString, data.origin), Q_ARG(QString, data.imgName));
- QMetaObject::invokeMethod(_hyperion, "setInputImage", Qt::QueuedConnection, Q_ARG(int, data.priority), Q_ARG(Image, image), Q_ARG(int64_t, data.duration));
+ QMetaObject::invokeMethod(_hyperion, "registerInput", Qt::QueuedConnection, Q_ARG(int, data.priority), Q_ARG(hyperion::Components, comp), Q_ARG(QString, data.origin), Q_ARG(QString, data.imgName));
+ QMetaObject::invokeMethod(_hyperion, "setInputImage", Qt::QueuedConnection, Q_ARG(int, data.priority), Q_ARG(Image, image), Q_ARG(int64_t, data.duration));
- return true;
+ return true;
}
-bool API::clearPriority(int priority, QString &replyMsg, hyperion::Components callerComp)
+bool API::clearPriority(int priority, QString &replyMsg, hyperion::Components /*callerComp*/)
{
- if (priority < 0 || (priority > 0 && priority < 254))
- {
- QMetaObject::invokeMethod(_hyperion, "clear", Qt::QueuedConnection, Q_ARG(int, priority));
- }
- else
- {
- replyMsg = QString("Priority %1 is not allowed to be cleared").arg(priority);
- return false;
- }
- return true;
+ if (priority < 0 || (priority > 0 && priority < PriorityMuxer::BG_PRIORITY))
+ {
+ QMetaObject::invokeMethod(_hyperion, "clear", Qt::QueuedConnection, Q_ARG(int, priority));
+ }
+ else
+ {
+ replyMsg = QString("Priority %1 is not allowed to be cleared").arg(priority);
+ return false;
+ }
+ return true;
}
-bool API::setComponentState(const QString &comp, bool &compState, QString &replyMsg, hyperion::Components callerComp)
+bool API::setComponentState(const QString &comp, bool &compState, QString &replyMsg, hyperion::Components /*callerComp*/)
{
- Components component = stringToComponent(comp);
+ Components component = stringToComponent(comp);
- if (component != COMP_INVALID)
- {
- QMetaObject::invokeMethod(_hyperion, "compStateChangeRequest", Qt::QueuedConnection, Q_ARG(hyperion::Components, component), Q_ARG(bool, compState));
- return true;
- }
- replyMsg = QString("Unknown component name: %1").arg(comp);
- return false;
+ if (component != COMP_INVALID)
+ {
+ QMetaObject::invokeMethod(_hyperion, "compStateChangeRequest", Qt::QueuedConnection, Q_ARG(hyperion::Components, component), Q_ARG(bool, compState));
+ return true;
+ }
+ replyMsg = QString("Unknown component name: %1").arg(comp);
+ return false;
}
-void API::setLedMappingType(int type, hyperion::Components callerComp)
+void API::setLedMappingType(int type, hyperion::Components /*callerComp*/)
{
- QMetaObject::invokeMethod(_hyperion, "setLedMappingType", Qt::QueuedConnection, Q_ARG(int, type));
+ QMetaObject::invokeMethod(_hyperion, "setLedMappingType", Qt::QueuedConnection, Q_ARG(int, type));
}
-void API::setVideoMode(VideoMode mode, hyperion::Components callerComp)
+void API::setVideoMode(VideoMode mode, hyperion::Components /*callerComp*/)
{
- QMetaObject::invokeMethod(_hyperion, "setVideoMode", Qt::QueuedConnection, Q_ARG(VideoMode, mode));
+ QMetaObject::invokeMethod(_hyperion, "setVideoMode", Qt::QueuedConnection, Q_ARG(VideoMode, mode));
}
#if defined(ENABLE_EFFECTENGINE)
-bool API::setEffect(const EffectCmdData &dat, hyperion::Components callerComp)
+bool API::setEffect(const EffectCmdData &dat, hyperion::Components /*callerComp*/)
{
- int res;
- if (!dat.args.isEmpty())
- {
- QMetaObject::invokeMethod(_hyperion, "setEffect", Qt::BlockingQueuedConnection, Q_RETURN_ARG(int, res), Q_ARG(QString, dat.effectName), Q_ARG(QJsonObject, dat.args), Q_ARG(int, dat.priority), Q_ARG(int, dat.duration), Q_ARG(QString, dat.pythonScript), Q_ARG(QString, dat.origin), Q_ARG(QString, dat.data));
- }
- else
- {
- QMetaObject::invokeMethod(_hyperion, "setEffect", Qt::BlockingQueuedConnection, Q_RETURN_ARG(int, res), Q_ARG(QString, dat.effectName), Q_ARG(int, dat.priority), Q_ARG(int, dat.duration), Q_ARG(QString, dat.origin));
- }
+ int isStarted;
+ if (!dat.args.isEmpty())
+ {
+ QMetaObject::invokeMethod(_hyperion, "setEffect", Qt::BlockingQueuedConnection, Q_RETURN_ARG(int, isStarted), Q_ARG(QString, dat.effectName), Q_ARG(QJsonObject, dat.args), Q_ARG(int, dat.priority), Q_ARG(int, dat.duration), Q_ARG(QString, dat.pythonScript), Q_ARG(QString, dat.origin), Q_ARG(QString, dat.data));
+ }
+ else
+ {
+ QMetaObject::invokeMethod(_hyperion, "setEffect", Qt::BlockingQueuedConnection, Q_RETURN_ARG(int, isStarted), Q_ARG(QString, dat.effectName), Q_ARG(int, dat.priority), Q_ARG(int, dat.duration), Q_ARG(QString, dat.origin));
+ }
- return res >= 0;
+ return isStarted >= 0;
}
#endif
-void API::setSourceAutoSelect(bool state, hyperion::Components callerComp)
+void API::setSourceAutoSelect(bool state, hyperion::Components /*callerComp*/)
{
- QMetaObject::invokeMethod(_hyperion, "setSourceAutoSelect", Qt::QueuedConnection, Q_ARG(bool, state));
+ QMetaObject::invokeMethod(_hyperion, "setSourceAutoSelect", Qt::QueuedConnection, Q_ARG(bool, state));
}
-void API::setVisiblePriority(int priority, hyperion::Components callerComp)
+void API::setVisiblePriority(int priority, hyperion::Components /*callerComp*/)
{
- QMetaObject::invokeMethod(_hyperion, "setVisiblePriority", Qt::QueuedConnection, Q_ARG(int, priority));
+ QMetaObject::invokeMethod(_hyperion, "setVisiblePriority", Qt::QueuedConnection, Q_ARG(int, priority));
}
void API::registerInput(int priority, hyperion::Components component, const QString &origin, const QString &owner, hyperion::Components callerComp)
{
- if (_activeRegisters.count(priority))
- _activeRegisters.erase(priority);
+ if (_activeRegisters.count(priority) != 0)
+ {
+ _activeRegisters.erase(priority);
+ }
- _activeRegisters.insert({priority, registerData{component, origin, owner, callerComp}});
+ _activeRegisters.insert({priority, registerData{component, origin, owner, callerComp}});
- QMetaObject::invokeMethod(_hyperion, "registerInput", Qt::QueuedConnection, Q_ARG(int, priority), Q_ARG(hyperion::Components, component), Q_ARG(QString, origin), Q_ARG(QString, owner));
+ QMetaObject::invokeMethod(_hyperion, "registerInput", Qt::QueuedConnection, Q_ARG(int, priority), Q_ARG(hyperion::Components, component), Q_ARG(QString, origin), Q_ARG(QString, owner));
}
void API::unregisterInput(int priority)
{
- if (_activeRegisters.count(priority))
- _activeRegisters.erase(priority);
+ if (_activeRegisters.count(priority) != 0)
+ {
+ _activeRegisters.erase(priority);
+ }
}
bool API::setHyperionInstance(quint8 inst)
{
- if (_currInstanceIndex == inst)
- return true;
- bool isRunning;
- QMetaObject::invokeMethod(_instanceManager, "IsInstanceRunning", Qt::DirectConnection, Q_RETURN_ARG(bool, isRunning), Q_ARG(quint8, inst));
- if (!isRunning)
- return false;
+ if (_currInstanceIndex == inst)
+ {
+ return true;
+ }
- disconnect(_hyperion, 0, this, 0);
- QMetaObject::invokeMethod(_instanceManager, "getHyperionInstance", Qt::DirectConnection, Q_RETURN_ARG(Hyperion *, _hyperion), Q_ARG(quint8, inst));
- _currInstanceIndex = inst;
- return true;
+ bool isRunning;
+ QMetaObject::invokeMethod(_instanceManager, "IsInstanceRunning", Qt::DirectConnection, Q_RETURN_ARG(bool, isRunning), Q_ARG(quint8, inst));
+ if (!isRunning)
+ {
+ return false;
+ }
+
+ disconnect(_hyperion, nullptr, this, nullptr);
+ QMetaObject::invokeMethod(_instanceManager, "getHyperionInstance", Qt::DirectConnection, Q_RETURN_ARG(Hyperion *, _hyperion), Q_ARG(quint8, inst));
+ _currInstanceIndex = inst;
+ return true;
}
bool API::isHyperionEnabled()
{
- int res;
- QMetaObject::invokeMethod(_hyperion, "isComponentEnabled", Qt::BlockingQueuedConnection, Q_RETURN_ARG(int, res), Q_ARG(hyperion::Components, hyperion::COMP_ALL));
- return res > 0;
+ int isEnabled;
+ QMetaObject::invokeMethod(_hyperion, "isComponentEnabled", Qt::BlockingQueuedConnection, Q_RETURN_ARG(int, isEnabled), Q_ARG(hyperion::Components, hyperion::COMP_ALL));
+ return isEnabled > 0;
}
-QVector API::getAllInstanceData()
+QVector API::getAllInstanceData() const
{
- QVector vec;
- QMetaObject::invokeMethod(_instanceManager, "getInstanceData", Qt::DirectConnection, Q_RETURN_ARG(QVector, vec));
- return vec;
+ QVector vec;
+ QMetaObject::invokeMethod(_instanceManager, "getInstanceData", Qt::DirectConnection, Q_RETURN_ARG(QVector, vec));
+ return vec;
}
bool API::startInstance(quint8 index, int tan)
{
- bool res;
- (_instanceManager->thread() != this->thread())
- ? QMetaObject::invokeMethod(_instanceManager, "startInstance", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, res), Q_ARG(quint8, index), Q_ARG(bool, false), Q_ARG(QObject*, this), Q_ARG(int, tan))
- : res = _instanceManager->startInstance(index, false, this, tan);
+ bool isStarted;
+ (_instanceManager->thread() != this->thread())
+ ? QMetaObject::invokeMethod(_instanceManager, "startInstance", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, isStarted), Q_ARG(quint8, index), Q_ARG(bool, false), Q_ARG(QObject*, this), Q_ARG(int, tan))
+ : isStarted = _instanceManager->startInstance(index, false, this, tan);
- return res;
+ return isStarted;
}
void API::stopInstance(quint8 index)
{
- QMetaObject::invokeMethod(_instanceManager, "stopInstance", Qt::QueuedConnection, Q_ARG(quint8, index));
+ QMetaObject::invokeMethod(_instanceManager, "stopInstance", Qt::QueuedConnection, Q_ARG(quint8, index));
}
bool API::deleteInstance(quint8 index, QString &replyMsg)
{
- if (_adminAuthorized)
- {
- QMetaObject::invokeMethod(_instanceManager, "deleteInstance", Qt::QueuedConnection, Q_ARG(quint8, index));
- return true;
- }
- replyMsg = NO_AUTH;
- return false;
+ if (_adminAuthorized)
+ {
+ QMetaObject::invokeMethod(_instanceManager, "deleteInstance", Qt::QueuedConnection, Q_ARG(quint8, index));
+ return true;
+ }
+ replyMsg = NO_AUTHORIZATION;
+ return false;
}
QString API::createInstance(const QString &name)
{
- if (_adminAuthorized)
- {
- bool success;
- QMetaObject::invokeMethod(_instanceManager, "createInstance", Qt::DirectConnection, Q_RETURN_ARG(bool, success), Q_ARG(QString, name));
- if (!success)
- return QString("Instance name '%1' is already in use").arg(name);
-
- return "";
- }
- return NO_AUTH;
+ if (_adminAuthorized)
+ {
+ bool success;
+ QMetaObject::invokeMethod(_instanceManager, "createInstance", Qt::DirectConnection, Q_RETURN_ARG(bool, success), Q_ARG(QString, name));
+ if (!success)
+ {
+ return QString("Instance name '%1' is already in use").arg(name);
+ }
+ return "";
+ }
+ return NO_AUTHORIZATION;
}
QString API::setInstanceName(quint8 index, const QString &name)
{
- if (_adminAuthorized)
- {
- QMetaObject::invokeMethod(_instanceManager, "saveName", Qt::QueuedConnection, Q_ARG(quint8, index), Q_ARG(QString, name));
- return "";
- }
- return NO_AUTH;
+ if (_adminAuthorized)
+ {
+ QMetaObject::invokeMethod(_instanceManager, "saveName", Qt::QueuedConnection, Q_ARG(quint8, index), Q_ARG(QString, name));
+ return "";
+ }
+ return NO_AUTHORIZATION;
}
#if defined(ENABLE_EFFECTENGINE)
QString API::deleteEffect(const QString &name)
{
- if (_adminAuthorized)
- {
- QString res;
- QMetaObject::invokeMethod(_hyperion, "deleteEffect", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QString, res), Q_ARG(QString, name));
- return res;
- }
- return NO_AUTH;
+ if (_adminAuthorized)
+ {
+ QString res;
+ QMetaObject::invokeMethod(_hyperion, "deleteEffect", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QString, res), Q_ARG(QString, name));
+ return res;
+ }
+ return NO_AUTHORIZATION;
}
QString API::saveEffect(const QJsonObject &data)
{
- if (_adminAuthorized)
- {
- QString res;
- QMetaObject::invokeMethod(_hyperion, "saveEffect", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QString, res), Q_ARG(QJsonObject, data));
- return res;
- }
- return NO_AUTH;
+ if (_adminAuthorized)
+ {
+ QString res;
+ QMetaObject::invokeMethod(_hyperion, "saveEffect", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QString, res), Q_ARG(QJsonObject, data));
+ return res;
+ }
+ return NO_AUTHORIZATION;
}
#endif
bool API::saveSettings(const QJsonObject &data)
{
- bool rc = true;
- if (!_adminAuthorized)
+ bool isSaved {true};
+ if (!_adminAuthorized)
{
- rc = false;
+ isSaved = false;
}
else
{
- QMetaObject::invokeMethod(_hyperion, "saveSettings", Qt::DirectConnection, Q_RETURN_ARG(bool, rc), Q_ARG(QJsonObject, data), Q_ARG(bool, true));
+ QMetaObject::invokeMethod(_hyperion, "saveSettings", Qt::DirectConnection, Q_RETURN_ARG(bool, isSaved), Q_ARG(QJsonObject, data), Q_ARG(bool, true));
}
- return rc;
+ return isSaved;
}
bool API::restoreSettings(const QJsonObject &data)
{
- bool rc = true;
+ bool isRestored {true};
if (!_adminAuthorized)
{
- rc = false;
+ isRestored = false;
}
else
{
- QMetaObject::invokeMethod(_hyperion, "restoreSettings", Qt::DirectConnection, Q_RETURN_ARG(bool, rc), Q_ARG(QJsonObject, data), Q_ARG(bool, true));
+ QMetaObject::invokeMethod(_hyperion, "restoreSettings", Qt::DirectConnection, Q_RETURN_ARG(bool, isRestored), Q_ARG(QJsonObject, data), Q_ARG(bool, true));
}
- return rc;
+ return isRestored;
}
bool API::updateHyperionPassword(const QString &password, const QString &newPassword)
{
- if (!_adminAuthorized)
- return false;
- bool res;
- QMetaObject::invokeMethod(_authManager, "updateUserPassword", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, res), Q_ARG(QString, "Hyperion"), Q_ARG(QString, password), Q_ARG(QString, newPassword));
- return res;
+ bool isPwUpdated {true};
+ if (!_adminAuthorized)
+ {
+ isPwUpdated = false;
+ }
+ else
+ {
+ QMetaObject::invokeMethod(_authManager, "updateUserPassword", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, isPwUpdated), Q_ARG(QString, DEFAULT_USER), Q_ARG(QString, password), Q_ARG(QString, newPassword));
+ }
+ return isPwUpdated;
}
QString API::createToken(const QString &comment, AuthManager::AuthDefinition &def)
{
- if (!_adminAuthorized)
- return NO_AUTH;
- if (comment.isEmpty())
- return "comment is empty";
- QMetaObject::invokeMethod(_authManager, "createToken", Qt::BlockingQueuedConnection, Q_RETURN_ARG(AuthManager::AuthDefinition, def), Q_ARG(QString, comment));
- return "";
+ if (!_adminAuthorized)
+ {
+ return NO_AUTHORIZATION;
+ }
+
+ if (comment.isEmpty())
+ {
+ return "Missing token comment";
+ }
+ QMetaObject::invokeMethod(_authManager, "createToken", Qt::BlockingQueuedConnection, Q_RETURN_ARG(AuthManager::AuthDefinition, def), Q_ARG(QString, comment));
+ return "";
}
-QString API::renameToken(const QString &id, const QString &comment)
+QString API::renameToken(const QString &tokenId, const QString &comment)
{
- if (!_adminAuthorized)
- return NO_AUTH;
- if (comment.isEmpty() || id.isEmpty())
- return "Empty comment or id";
+ if (!_adminAuthorized)
+ {
+ return NO_AUTHORIZATION;
+ }
- QMetaObject::invokeMethod(_authManager, "renameToken", Qt::QueuedConnection, Q_ARG(QString, id), Q_ARG(QString, comment));
- return "";
+ if (comment.isEmpty())
+ {
+ return "Missing token comment";
+ }
+
+ if (tokenId.isEmpty()) {
+ return "Missing token id";
+ }
+
+ bool isTokenRenamed {false};
+ QMetaObject::invokeMethod(_authManager, "renameToken", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, isTokenRenamed), Q_ARG(QString, tokenId), Q_ARG(QString, comment));
+
+ return (!isTokenRenamed) ? "Token does not exist" : "";
}
-QString API::deleteToken(const QString &id)
+QString API::deleteToken(const QString &tokenId)
{
- if (!_adminAuthorized)
- return NO_AUTH;
- if (id.isEmpty())
- return "Empty id";
+ if (!_adminAuthorized)
+ {
+ return NO_AUTHORIZATION;
+ }
- QMetaObject::invokeMethod(_authManager, "deleteToken", Qt::QueuedConnection, Q_ARG(QString, id));
- return "";
+ if (tokenId.isEmpty())
+ {
+ return "Missing token id";
+ }
+
+ bool isTokenDeleted {false};
+ QMetaObject::invokeMethod(_authManager, "deleteToken", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, isTokenDeleted), Q_ARG(QString, tokenId));
+
+ return (!isTokenDeleted) ? "Token does not exist" : "";
}
-void API::setNewTokenRequest(const QString &comment, const QString &id, const int &tan)
+void API::setNewTokenRequest(const QString &comment, const QString &tokenId, const int &tan)
{
- QMetaObject::invokeMethod(_authManager, "setNewTokenRequest", Qt::QueuedConnection, Q_ARG(QObject *, this), Q_ARG(QString, comment), Q_ARG(QString, id), Q_ARG(int, tan));
+ QMetaObject::invokeMethod(_authManager, "setNewTokenRequest", Qt::QueuedConnection, Q_ARG(QObject *, this), Q_ARG(QString, comment), Q_ARG(QString, tokenId), Q_ARG(int, tan));
}
-void API::cancelNewTokenRequest(const QString &comment, const QString &id)
+void API::cancelNewTokenRequest(const QString &comment, const QString &tokenId)
{
- QMetaObject::invokeMethod(_authManager, "cancelNewTokenRequest", Qt::QueuedConnection, Q_ARG(QObject *, this), Q_ARG(QString, comment), Q_ARG(QString, id));
+ QMetaObject::invokeMethod(_authManager, "cancelNewTokenRequest", Qt::QueuedConnection, Q_ARG(QObject *, this), Q_ARG(QString, comment), Q_ARG(QString, tokenId));
}
-bool API::handlePendingTokenRequest(const QString &id, bool accept)
+bool API::handlePendingTokenRequest(const QString &tokenId, bool accept)
{
- if (!_adminAuthorized)
- return false;
- QMetaObject::invokeMethod(_authManager, "handlePendingTokenRequest", Qt::QueuedConnection, Q_ARG(QString, id), Q_ARG(bool, accept));
- return true;
+ if (!_adminAuthorized)
+ {
+ return false;
+ }
+ QMetaObject::invokeMethod(_authManager, "handlePendingTokenRequest", Qt::QueuedConnection, Q_ARG(QString, tokenId), Q_ARG(bool, accept));
+ return true;
}
bool API::getTokenList(QVector &def)
{
- if (!_adminAuthorized)
- return false;
- QMetaObject::invokeMethod(_authManager, "getTokenList", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QVector, def));
- return true;
+ if (!_adminAuthorized)
+ {
+ return false;
+ }
+ QMetaObject::invokeMethod(_authManager, "getTokenList", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QVector, def));
+ return true;
}
bool API::getPendingTokenRequests(QVector &map)
{
- if (!_adminAuthorized)
- return false;
- QMetaObject::invokeMethod(_authManager, "getPendingRequests", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QVector, map));
- return true;
+ if (!_adminAuthorized)
+ {
+ return false;
+ }
+ QMetaObject::invokeMethod(_authManager, "getPendingRequests", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QVector, map));
+ return true;
}
bool API::isUserTokenAuthorized(const QString &userToken)
{
- bool res;
- QMetaObject::invokeMethod(_authManager, "isUserTokenAuthorized", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, res), Q_ARG(QString, "Hyperion"), Q_ARG(QString, userToken));
- if (res)
- {
- _authorized = true;
- _adminAuthorized = true;
- // Listen for ADMIN ACCESS protected signals
- connect(_authManager, &AuthManager::newPendingTokenRequest, this, &API::onPendingTokenRequest, Qt::UniqueConnection);
- }
- return res;
+ QMetaObject::invokeMethod(_authManager, "isUserTokenAuthorized", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, _authorized), Q_ARG(QString, DEFAULT_USER), Q_ARG(QString, userToken));
+ _adminAuthorized = _authorized;
+
+ if (_authorized)
+ {
+ // Listen for ADMIN ACCESS protected signals
+ connect(_authManager, &AuthManager::newPendingTokenRequest, this, &API::onPendingTokenRequest);
+ }
+ else
+ {
+ disconnect(_authManager, &AuthManager::newPendingTokenRequest, this, &API::onPendingTokenRequest);
+ }
+ return _authorized;
}
bool API::getUserToken(QString &userToken)
{
- if (!_adminAuthorized)
- return false;
- QMetaObject::invokeMethod(_authManager, "getUserToken", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QString, userToken));
- return true;
+ if (!_adminAuthorized)
+ {
+ return false;
+ }
+ QMetaObject::invokeMethod(_authManager, "getUserToken", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QString, userToken));
+ return true;
}
bool API::isTokenAuthorized(const QString &token)
{
(_authManager->thread() != this->thread())
- ? QMetaObject::invokeMethod(_authManager, "isTokenAuthorized", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, _authorized), Q_ARG(QString, token))
- : _authorized = _authManager->isTokenAuthorized(token);
+ ? QMetaObject::invokeMethod(_authManager, "isTokenAuthorized", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, _authorized), Q_ARG(QString, token))
+ : _authorized = _authManager->isTokenAuthorized(token);
+ _adminAuthorized = _authorized;
- return _authorized;
+ return _authorized;
}
bool API::isUserAuthorized(const QString &password)
{
- bool res;
- QMetaObject::invokeMethod(_authManager, "isUserAuthorized", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, res), Q_ARG(QString, "Hyperion"), Q_ARG(QString, password));
- if (res)
- {
- _authorized = true;
- _adminAuthorized = true;
- // Listen for ADMIN ACCESS protected signals
- connect(_authManager, &AuthManager::newPendingTokenRequest, this, &API::onPendingTokenRequest, Qt::UniqueConnection);
- }
- return res;
+ bool isUserAuthorized;
+ QMetaObject::invokeMethod(_authManager, "isUserAuthorized", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, isUserAuthorized), Q_ARG(QString, DEFAULT_USER), Q_ARG(QString, password));
+ if (isUserAuthorized)
+ {
+ _authorized = true;
+ _adminAuthorized = true;
+
+ // Listen for ADMIN ACCESS protected signals
+ connect(_authManager, &AuthManager::newPendingTokenRequest, this, &API::onPendingTokenRequest);
+ }
+ else
+ {
+ disconnect(_authManager, &AuthManager::newPendingTokenRequest, this, &API::onPendingTokenRequest);
+ }
+ return isUserAuthorized;
}
bool API::hasHyperionDefaultPw()
{
- bool res;
- QMetaObject::invokeMethod(_authManager, "isUserAuthorized", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, res), Q_ARG(QString, "Hyperion"), Q_ARG(QString, "hyperion"));
- return res;
+ bool isDefaultPassort;
+ QMetaObject::invokeMethod(_authManager, "isUserAuthorized", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, isDefaultPassort), Q_ARG(QString, DEFAULT_USER), Q_ARG(QString, DEFAULT_PASSWORD));
+ return isDefaultPassort;
}
void API::logout()
{
- _authorized = false;
- _adminAuthorized = false;
- // Stop listenig for ADMIN ACCESS protected signals
- disconnect(_authManager, &AuthManager::newPendingTokenRequest, this, &API::onPendingTokenRequest);
- stopDataConnectionss();
-}
-
-void API::stopDataConnectionss()
-{
+ _authorized = false;
+ _adminAuthorized = false;
+ // Stop listenig for ADMIN ACCESS protected signals
+ disconnect(_authManager, &AuthManager::newPendingTokenRequest, this, &API::onPendingTokenRequest);
+ stopDataConnections();
}
diff --git a/libsrc/api/CMakeLists.txt b/libsrc/api/CMakeLists.txt
index ac5cdcfd..6818e752 100644
--- a/libsrc/api/CMakeLists.txt
+++ b/libsrc/api/CMakeLists.txt
@@ -2,10 +2,14 @@ add_library(hyperion-api
${CMAKE_SOURCE_DIR}/include/api/apiStructs.h
${CMAKE_SOURCE_DIR}/include/api/API.h
${CMAKE_SOURCE_DIR}/include/api/JsonAPI.h
- ${CMAKE_SOURCE_DIR}/include/api/JsonCB.h
+ ${CMAKE_SOURCE_DIR}/include/api/JsonCallbacks.h
+ ${CMAKE_SOURCE_DIR}/include/api/JsonApiCommand.h
+ ${CMAKE_SOURCE_DIR}/include/api/JsonApiSubscription.h
+ ${CMAKE_SOURCE_DIR}/include/api/JsonInfo.h
${CMAKE_SOURCE_DIR}/libsrc/api/JsonAPI.cpp
${CMAKE_SOURCE_DIR}/libsrc/api/API.cpp
- ${CMAKE_SOURCE_DIR}/libsrc/api/JsonCB.cpp
+ ${CMAKE_SOURCE_DIR}/libsrc/api/JsonCallbacks.cpp
+ ${CMAKE_SOURCE_DIR}/libsrc/api/JsonInfo.cpp
${CMAKE_SOURCE_DIR}/libsrc/api/JSONRPC_schemas.qrc
)
diff --git a/libsrc/api/JSONRPC_schema/schema-adjustment.json b/libsrc/api/JSONRPC_schema/schema-adjustment.json
index b0663736..3afe5bcd 100644
--- a/libsrc/api/JSONRPC_schema/schema-adjustment.json
+++ b/libsrc/api/JSONRPC_schema/schema-adjustment.json
@@ -7,6 +7,12 @@
"required" : true,
"enum" : ["adjustment"]
},
+ "instance" : {
+ "type": "array",
+ "required": false,
+ "items" : {},
+ "minItems": 1
+ },
"tan" : {
"type" : "integer"
},
diff --git a/libsrc/api/JSONRPC_schema/schema-clear.json b/libsrc/api/JSONRPC_schema/schema-clear.json
index c63a77f7..b55be0a1 100644
--- a/libsrc/api/JSONRPC_schema/schema-clear.json
+++ b/libsrc/api/JSONRPC_schema/schema-clear.json
@@ -7,6 +7,12 @@
"required" : true,
"enum" : ["clear"]
},
+ "instance" : {
+ "type": "array",
+ "required": false,
+ "items" : {},
+ "minItems": 1
+ },
"tan" : {
"type" : "integer"
},
diff --git a/libsrc/api/JSONRPC_schema/schema-clearall.json b/libsrc/api/JSONRPC_schema/schema-clearall.json
index 8c88cc6c..5d5d2d22 100644
--- a/libsrc/api/JSONRPC_schema/schema-clearall.json
+++ b/libsrc/api/JSONRPC_schema/schema-clearall.json
@@ -7,6 +7,12 @@
"required" : true,
"enum" : ["clearall"]
},
+ "instance" : {
+ "type": "array",
+ "required": false,
+ "items" : {},
+ "minItems": 1
+ },
"tan" : {
"type" : "integer"
}
diff --git a/libsrc/api/JSONRPC_schema/schema-color.json b/libsrc/api/JSONRPC_schema/schema-color.json
index 754c5ad3..eeeba069 100644
--- a/libsrc/api/JSONRPC_schema/schema-color.json
+++ b/libsrc/api/JSONRPC_schema/schema-color.json
@@ -7,6 +7,12 @@
"required" : true,
"enum" : ["color"]
},
+ "instance" : {
+ "type": "array",
+ "required": false,
+ "items" : {},
+ "minItems": 1
+ },
"tan" : {
"type" : "integer"
},
diff --git a/libsrc/api/JSONRPC_schema/schema-componentstate.json b/libsrc/api/JSONRPC_schema/schema-componentstate.json
index f46324dc..10ca3bb6 100644
--- a/libsrc/api/JSONRPC_schema/schema-componentstate.json
+++ b/libsrc/api/JSONRPC_schema/schema-componentstate.json
@@ -9,6 +9,12 @@
"required" : true,
"enum" : ["componentstate"]
},
+ "instance" : {
+ "type": "array",
+ "required": false,
+ "items" : {},
+ "minItems": 1
+ },
"tan" : {
"type" : "integer"
},
diff --git a/libsrc/api/JSONRPC_schema/schema-config.json b/libsrc/api/JSONRPC_schema/schema-config.json
index 8a134b54..204661cf 100644
--- a/libsrc/api/JSONRPC_schema/schema-config.json
+++ b/libsrc/api/JSONRPC_schema/schema-config.json
@@ -12,6 +12,11 @@
"required" : true,
"enum" : ["getconfig","getschema","setconfig","restoreconfig","reload"]
},
+ "instance" : {
+ "type" : "integer",
+ "minimum": 0,
+ "maximum": 255
+ },
"tan" : {
"type" : "integer"
},
diff --git a/libsrc/api/JSONRPC_schema/schema-create-effect.json b/libsrc/api/JSONRPC_schema/schema-create-effect.json
index 97b55056..2eceb9a1 100644
--- a/libsrc/api/JSONRPC_schema/schema-create-effect.json
+++ b/libsrc/api/JSONRPC_schema/schema-create-effect.json
@@ -7,6 +7,11 @@
"required" : true,
"enum" : ["create-effect"]
},
+ "instance" : {
+ "type" : "integer",
+ "minimum": 0,
+ "maximum": 255
+ },
"tan" : {
"type" : "integer"
},
diff --git a/libsrc/api/JSONRPC_schema/schema-delete-effect.json b/libsrc/api/JSONRPC_schema/schema-delete-effect.json
index 8279f854..bdbdee7c 100644
--- a/libsrc/api/JSONRPC_schema/schema-delete-effect.json
+++ b/libsrc/api/JSONRPC_schema/schema-delete-effect.json
@@ -8,6 +8,11 @@
"required" : true,
"enum" : ["delete-effect"]
},
+ "instance" : {
+ "type" : "integer",
+ "minimum": 0,
+ "maximum": 255
+ },
"tan" : {
"type" : "integer"
},
diff --git a/libsrc/api/JSONRPC_schema/schema-effect.json b/libsrc/api/JSONRPC_schema/schema-effect.json
index 876173f1..5bd0aff6 100644
--- a/libsrc/api/JSONRPC_schema/schema-effect.json
+++ b/libsrc/api/JSONRPC_schema/schema-effect.json
@@ -7,6 +7,12 @@
"required" : true,
"enum" : ["effect"]
},
+ "instance" : {
+ "type": "array",
+ "required": false,
+ "items" : {},
+ "minItems": 1
+ },
"tan" : {
"type" : "integer"
},
diff --git a/libsrc/api/JSONRPC_schema/schema-image.json b/libsrc/api/JSONRPC_schema/schema-image.json
index 3296babb..fbd2ff40 100644
--- a/libsrc/api/JSONRPC_schema/schema-image.json
+++ b/libsrc/api/JSONRPC_schema/schema-image.json
@@ -7,6 +7,12 @@
"required" : true,
"enum" : ["image"]
},
+ "instance" : {
+ "type": "array",
+ "required": false,
+ "items" : {},
+ "minItems": 1
+ },
"tan" : {
"type" : "integer"
},
diff --git a/libsrc/api/JSONRPC_schema/schema-ledcolors.json b/libsrc/api/JSONRPC_schema/schema-ledcolors.json
index b1bda0a3..086914ec 100644
--- a/libsrc/api/JSONRPC_schema/schema-ledcolors.json
+++ b/libsrc/api/JSONRPC_schema/schema-ledcolors.json
@@ -7,21 +7,18 @@
"required" : true,
"enum" : ["ledcolors"]
},
+ "instance" : {
+ "type" : "integer",
+ "minimum": 0,
+ "maximum": 255
+ },
"tan" : {
"type" : "integer"
},
"subcommand": {
"type" : "string",
"required" : true,
- "enum" : ["ledstream-stop","ledstream-start","testled","imagestream-start","imagestream-stop"]
- },
- "oneshot": {
- "type" : "bool"
- },
- "interval": {
- "type" : "integer",
- "required" : false,
- "minimum": 50
+ "enum" : ["ledstream-stop","ledstream-start","imagestream-start","imagestream-stop"]
}
},
diff --git a/libsrc/api/JSONRPC_schema/schema-leddevice.json b/libsrc/api/JSONRPC_schema/schema-leddevice.json
index 5065ea0d..ac74342c 100644
--- a/libsrc/api/JSONRPC_schema/schema-leddevice.json
+++ b/libsrc/api/JSONRPC_schema/schema-leddevice.json
@@ -7,6 +7,9 @@
"required" : true,
"enum" : ["leddevice"]
},
+ "instance" : {
+ "type" : "integer"
+ },
"tan" : {
"type" : "integer"
},
diff --git a/libsrc/api/JSONRPC_schema/schema-processing.json b/libsrc/api/JSONRPC_schema/schema-processing.json
index d67828f0..0ca7616d 100644
--- a/libsrc/api/JSONRPC_schema/schema-processing.json
+++ b/libsrc/api/JSONRPC_schema/schema-processing.json
@@ -7,6 +7,12 @@
"required" : true,
"enum" : ["processing"]
},
+ "instance" : {
+ "type": "array",
+ "required": false,
+ "items" : {},
+ "minItems": 1
+ },
"tan" : {
"type" : "integer"
},
diff --git a/libsrc/api/JSONRPC_schema/schema-serverinfo.json b/libsrc/api/JSONRPC_schema/schema-serverinfo.json
index 990eec04..74b41453 100644
--- a/libsrc/api/JSONRPC_schema/schema-serverinfo.json
+++ b/libsrc/api/JSONRPC_schema/schema-serverinfo.json
@@ -7,6 +7,25 @@
"required" : true,
"enum" : ["serverinfo"]
},
+ "subcommand": {
+ "type": "string",
+ "enum": ["getInfo", "subscribe", "unsubscribe", "getSubscriptions", "getSubscriptionCommands"]
+ },
+ "instance" : {
+ "type" : "integer",
+ "minimum": 0,
+ "maximum": 255
+ },
+ "data": {
+ "type": ["null", "array"],
+ "properties": {
+ "subscriptions": {
+ "type": "array",
+ "items": {}
+ }
+ },
+ "additionalProperties": false
+ },
"subscribe" : {
"type" : "array"
},
diff --git a/libsrc/api/JSONRPC_schema/schema-sourceselect.json b/libsrc/api/JSONRPC_schema/schema-sourceselect.json
index 14f9aaea..8763595c 100644
--- a/libsrc/api/JSONRPC_schema/schema-sourceselect.json
+++ b/libsrc/api/JSONRPC_schema/schema-sourceselect.json
@@ -7,6 +7,12 @@
"required" : true,
"enum" : ["sourceselect"]
},
+ "instance" : {
+ "type": "array",
+ "required": false,
+ "items" : {},
+ "minItems": 1
+ },
"tan" : {
"type" : "integer"
},
diff --git a/libsrc/api/JsonAPI.cpp b/libsrc/api/JsonAPI.cpp
index 308da960..5c1a7ded 100644
--- a/libsrc/api/JsonAPI.cpp
+++ b/libsrc/api/JsonAPI.cpp
@@ -1,5 +1,6 @@
// project includes
#include
+#include
// Qt includes
#include
@@ -10,8 +11,8 @@
#include
#include
#include
-#include
-#include
+#include
+#include
// hyperion includes
#include
@@ -20,62 +21,11 @@
#include // Required to determine the cmake options
-#include
-#include
-
#include
#include
-#if defined(ENABLE_MF)
- #include
-#elif defined(ENABLE_V4L2)
- #include
-#endif
-
-#if defined(ENABLE_AUDIO)
- #include
-
- #ifdef WIN32
- #include
- #endif
-
- #ifdef __linux__
- #include
- #endif
-#endif
-
-#if defined(ENABLE_X11)
- #include
-#endif
-
-#if defined(ENABLE_XCB)
- #include
-#endif
-
-#if defined(ENABLE_DX)
- #include
-#endif
-
-#if defined(ENABLE_FB)
- #include
-#endif
-
-#if defined(ENABLE_DISPMANX)
- #include
-#endif
-
-#if defined(ENABLE_AMLOGIC)
- #include
-#endif
-
-#if defined(ENABLE_OSX)
- #include
-#endif
-
#include
#include
-#include
-#include
#include
#include
#include
@@ -85,7 +35,7 @@
#include
// api includes
-#include
+#include
#include
// auth manager
@@ -100,23 +50,37 @@
#include
#endif
+#include
+#include
+
using namespace hyperion;
// Constants
-namespace { const bool verbose = false; }
+namespace {
+
+constexpr std::chrono::milliseconds NEW_TOKEN_REQUEST_TIMEOUT{ 180000 };
+
+const char TOKEN_TAG[] = "token";
+constexpr int TOKEN_TAG_LENGTH = sizeof(TOKEN_TAG) - 1;
+const char BEARER_TOKEN_TAG[] = "Bearer";
+constexpr int BEARER_TOKEN_TAG_LENGTH = sizeof(BEARER_TOKEN_TAG) - 1;
+
+const int MIN_PASSWORD_LENGTH = 8;
+const int APP_TOKEN_LENGTH = 36;
+
+const bool verbose = false;
+}
JsonAPI::JsonAPI(QString peerAddress, Logger *log, bool localConnection, QObject *parent, bool noListener)
: API(log, localConnection, parent)
+ ,_noListener(noListener)
+ ,_peerAddress (std::move(peerAddress))
+ ,_jsonCB (nullptr)
{
- _noListener = noListener;
- _peerAddress = peerAddress;
- _jsonCB = new JsonCB(this);
- _streaming_logging_activated = false;
- _ledStreamTimer = new QTimer(this);
-
Q_INIT_RESOURCE(JSONRPC_schemas);
qRegisterMetaType("Event");
+ _jsonCB = QSharedPointer(new JsonCallbacks( _log, _peerAddress, parent));
}
void JsonAPI::initialize()
@@ -125,14 +89,14 @@ void JsonAPI::initialize()
API::init();
// setup auth interface
- connect(this, &API::onPendingTokenRequest, this, &JsonAPI::newPendingTokenRequest);
+ connect(this, &API::onPendingTokenRequest, this, &JsonAPI::issueNewPendingTokenRequest);
connect(this, &API::onTokenResponse, this, &JsonAPI::handleTokenResponse);
// listen for killed instances
connect(_instanceManager, &HyperionIManager::instanceStateChanged, this, &JsonAPI::handleInstanceStateChange);
// pipe callbacks from subscriptions to parent
- connect(_jsonCB, &JsonCB::newCallback, this, &JsonAPI::callbackMessage);
+ connect(_jsonCB.data(), &JsonCallbacks::newCallback, this, &JsonAPI::callbackMessage);
// notify hyperion about a jsonMessageForward
if (_hyperion != nullptr)
@@ -144,8 +108,6 @@ void JsonAPI::initialize()
//notify eventhadler on suspend/resume/idle requests
connect(this, &JsonAPI::signalEvent, EventHandler::getInstance().data(), &EventHandler::handleEvent);
-
- connect(_ledStreamTimer, &QTimer::timeout, this, &JsonAPI::streamLedColorsUpdate, Qt::UniqueConnection);
}
bool JsonAPI::handleInstanceSwitch(quint8 inst, bool /*forced*/)
@@ -165,118 +127,273 @@ void JsonAPI::handleMessage(const QString &messageString, const QString &httpAut
const QString ident = "JsonRpc@" + _peerAddress;
QJsonObject message;
- // parse the message
- if (!JsonUtils::parse(ident, messageString, message, _log))
+ //parse the message
+ QPair parsingResult = JsonUtils::parse(ident, messageString, message, _log);
+ if (!parsingResult.first)
{
- sendErrorReply("Errors during message parsing, please consult the Hyperion Log.");
+ //Try to find command and tan, even parsing failed
+ QString command = findCommand(messageString);
+ int tan = findTan(messageString);
+
+ sendErrorReply("Parse error", parsingResult.second, command, tan);
return;
}
- int tan = 0;
- if (message.value("tan") != QJsonValue::Undefined)
- tan = message["tan"].toInt();
-
- // check basic message
- if (!JsonUtils::validate(ident, message, ":schema", _log))
- {
- sendErrorReply("Errors during message validation, please consult the Hyperion Log.", "" /*command*/, tan);
- return;
- }
+ DebugIf(verbose, _log, "message: [%s]", QJsonDocument(message).toJson(QJsonDocument::Compact).constData() );
// check specific message
- const QString command = message["command"].toString();
- if (!JsonUtils::validate(ident, message, QString(":schema-%1").arg(command), _log))
+ const QString command = message.value("command").toString();
+ const QString subCommand = message.value("subcommand").toString();
+
+ int tan {0};
+ if (message.value("tan") != QJsonValue::Undefined)
{
- sendErrorReply("Errors during specific message validation, please consult the Hyperion Log", command, tan);
+ tan = message["tan"].toInt();
+ }
+
+ // check basic message
+ QJsonObject schemaJson = QJsonFactory::readSchema(":schema");
+ QPair validationResult = JsonUtils::validate(ident, message, schemaJson, _log);
+ if (!validationResult.first)
+ {
+ sendErrorReply("Invalid command", validationResult.second, command, tan);
return;
}
- // client auth before everything else but not for http
- if (!_noListener && command == "authorize")
+ JsonApiCommand cmd = ApiCommandRegister::getCommandInfo(command, subCommand);
+ cmd.tan = tan;
+
+ if (cmd.command == Command::Unknown)
{
- handleAuthorizeCommand(message, command, tan);
+ const QStringList errorDetails (subCommand.isEmpty() ? "subcommand is missing" : QString("Invalid subcommand: %1").arg(subCommand));
+ sendErrorReply("Invalid command", errorDetails, command, tan);
return;
}
- // check auth state
- if (!API::isAuthorized())
+ if (_noListener)
{
- // on the fly auth available for http from http Auth header
- if (_noListener)
+ setAuthorization(false);
+ if(cmd.isNolistenerCmd == NoListenerCmd::No)
{
- QString cToken = httpAuthHeader.mid(5).trimmed();
- if (API::isTokenAuthorized(cToken))
- goto proceed;
+ sendErrorReply("Command not supported via single API calls using HTTP/S", cmd);
+ return;
}
- sendErrorReply("No Authorization", command, tan);
+
+ // Check authorization for HTTP requests
+ if (!httpAuthHeader.isEmpty())
+ {
+ int bearTokenLenght {0};
+ if (httpAuthHeader.startsWith(BEARER_TOKEN_TAG, Qt::CaseInsensitive)) {
+ bearTokenLenght = BEARER_TOKEN_TAG_LENGTH;
+ }
+ else if (httpAuthHeader.startsWith(TOKEN_TAG, Qt::CaseInsensitive)) {
+ bearTokenLenght = TOKEN_TAG_LENGTH;
+ }
+
+ if (bearTokenLenght == 0)
+ {
+ sendErrorReply("No bearer token found in Authorization header", cmd);
+ return;
+ }
+
+ QString cToken =httpAuthHeader.mid(bearTokenLenght).trimmed();
+ API::isTokenAuthorized(cToken); // _authorized && _adminAuthorized are set
+ }
+
+ if (islocalConnection() && !_authManager->isLocalAuthRequired())
+ {
+ // if the request comes via a local network connection, plus authorization is disabled for local request,
+ // no token authorization is required for non-admin requests
+ setAuthorization(true);
+ }
+ }
+
+ if (cmd.authorization != Authorization::No )
+ {
+ if (!isAuthorized() || (cmd.authorization == Authorization::Admin && !isAdminAuthorized()))
+ {
+ sendNoAuthorization(cmd);
+ return;
+ }
+ }
+
+ schemaJson = QJsonFactory::readSchema(QString(":schema-%1").arg(command));
+ validationResult = JsonUtils::validate(ident, message, schemaJson, _log);
+ if (!validationResult.first)
+ {
+ sendErrorReply("Invalid params", validationResult.second, cmd);
return;
}
-proceed:
+
if (_hyperion == nullptr)
{
- sendErrorReply("Service Unavailable", command, tan);
+ sendErrorReply("Service Unavailable", cmd);
return;
}
- // switch over all possible commands and handle them
- if (command == "color")
- handleColorCommand(message, command, tan);
- else if (command == "image")
- handleImageCommand(message, command, tan);
-#if defined(ENABLE_EFFECTENGINE)
- else if (command == "effect")
- handleEffectCommand(message, command, tan);
- else if (command == "create-effect")
- handleCreateEffectCommand(message, command, tan);
- else if (command == "delete-effect")
- handleDeleteEffectCommand(message, command, tan);
-#endif
- else if (command == "sysinfo")
- handleSysInfoCommand(message, command, tan);
- else if (command == "serverinfo")
- handleServerInfoCommand(message, command, tan);
- else if (command == "clear")
- handleClearCommand(message, command, tan);
- else if (command == "adjustment")
- handleAdjustmentCommand(message, command, tan);
- else if (command == "sourceselect")
- handleSourceSelectCommand(message, command, tan);
- else if (command == "config")
- handleConfigCommand(message, command, tan);
- else if (command == "componentstate")
- handleComponentStateCommand(message, command, tan);
- else if (command == "ledcolors")
- handleLedColorsCommand(message, command, tan);
- else if (command == "logging")
- handleLoggingCommand(message, command, tan);
- else if (command == "processing")
- handleProcessingCommand(message, command, tan);
- else if (command == "videomode")
- handleVideoModeCommand(message, command, tan);
- else if (command == "instance")
- handleInstanceCommand(message, command, tan);
- else if (command == "leddevice")
- handleLedDeviceCommand(message, command, tan);
- else if (command == "inputsource")
- handleInputSourceCommand(message, command, tan);
- else if (command == "service")
- handleServiceCommand(message, command, tan);
- else if (command == "system")
- handleSystemCommand(message, command, tan);
-
- // BEGIN | The following commands are deprecated but used to ensure backward compatibility with hyperion Classic remote control
- else if (command == "clearall")
- handleClearallCommand(message, command, tan);
- else if (command == "transform" || command == "correction" || command == "temperature")
- sendErrorReply("The command " + command + "is deprecated, please use the Hyperion Web Interface to configure", command, tan);
- // END
-
- // handle not implemented commands
+ if (!message.contains("instance") || cmd.isInstanceCmd == InstanceCmd::No)
+ {
+ handleCommand(cmd, message);
+ }
else
- handleNotImplemented(command, tan);
+ {
+ handleInstanceCommand(cmd, message);
+ }
}
-void JsonAPI::handleColorCommand(const QJsonObject &message, const QString &command, int tan)
+void JsonAPI::handleInstanceCommand(const JsonApiCommand& cmd, const QJsonObject &message)
+{
+ const QJsonValue instanceElement = message.value("instance");
+ QJsonArray instances;
+ if (instanceElement.isDouble())
+ {
+ instances.append(instanceElement);
+ } else if (instanceElement.isArray())
+ {
+ instances = instanceElement.toArray();
+ }
+
+ QList runningInstanceIdxs = _instanceManager->getRunningInstanceIdx();
+
+ QList instanceIdxList;
+ QStringList errorDetails;
+ if (instances.contains("all"))
+ {
+ for (const auto& instanceIdx : runningInstanceIdxs)
+ {
+ instanceIdxList.append(instanceIdx);
+ }
+ }
+ else
+ {
+ for (const auto &instance : std::as_const(instances)) {
+
+ quint8 instanceIdx = static_cast(instance.toInt());
+ if (instance.isDouble() && runningInstanceIdxs.contains(instanceIdx))
+ {
+ instanceIdxList.append(instanceIdx);
+ }
+ else
+ {
+ errorDetails.append("Not a running or valid instance: " + instance.toVariant().toString());
+ }
+ }
+ }
+
+ if (instanceIdxList.isEmpty() || !errorDetails.isEmpty() )
+ {
+ sendErrorReply("Invalid instance(s) given", errorDetails, cmd);
+ return;
+ }
+
+ quint8 currentInstanceIdx = getCurrentInstanceIndex();
+ if (instanceIdxList.size() > 1)
+ {
+ if (cmd.isInstanceCmd != InstanceCmd::Multi)
+ {
+ sendErrorReply("Command does not support multiple instances", cmd);
+ return;
+ }
+ }
+
+ for (const auto &instanceIdx : instanceIdxList)
+ {
+ if (setHyperionInstance(instanceIdx))
+ {
+ handleCommand(cmd, message);
+ }
+ }
+
+ setHyperionInstance(currentInstanceIdx);
+}
+
+void JsonAPI::handleCommand(const JsonApiCommand& cmd, const QJsonObject &message)
+{
+ switch (cmd.command) {
+ case Command::Authorize:
+ handleAuthorizeCommand(message, cmd);
+ break;
+ case Command::Color:
+ handleColorCommand(message, cmd);
+ break;
+ case Command::Image:
+ handleImageCommand(message, cmd);
+ break;
+#if defined(ENABLE_EFFECTENGINE)
+ case Command::Effect:
+ handleEffectCommand(message, cmd);
+ break;
+ case Command::CreateEffect:
+ handleCreateEffectCommand(message, cmd);
+ break;
+ case Command::DeleteEffect:
+ handleDeleteEffectCommand(message, cmd);
+ break;
+#endif
+ case Command::SysInfo:
+ handleSysInfoCommand(message, cmd);
+ break;
+ case Command::ServerInfo:
+ handleServerInfoCommand(message, cmd);
+ break;
+ case Command::Clear:
+ handleClearCommand(message, cmd);
+ break;
+ case Command::Adjustment:
+ handleAdjustmentCommand(message, cmd);
+ break;
+ case Command::SourceSelect:
+ handleSourceSelectCommand(message, cmd);
+ break;
+ case Command::Config:
+ handleConfigCommand(message, cmd);
+ break;
+ case Command::ComponentState:
+ handleComponentStateCommand(message, cmd);
+ break;
+ case Command::LedColors:
+ handleLedColorsCommand(message, cmd);
+ break;
+ case Command::Logging:
+ handleLoggingCommand(message, cmd);
+ break;
+ case Command::Processing:
+ handleProcessingCommand(message, cmd);
+ break;
+ case Command::VideoMode:
+ handleVideoModeCommand(message, cmd);
+ break;
+ case Command::Instance:
+ handleInstanceCommand(message, cmd);
+ break;
+ case Command::LedDevice:
+ handleLedDeviceCommand(message, cmd);
+ break;
+ case Command::InputSource:
+ handleInputSourceCommand(message, cmd);
+ break;
+ case Command::Service:
+ handleServiceCommand(message, cmd);
+ break;
+ case Command::System:
+ handleSystemCommand(message, cmd);
+ break;
+ case Command::ClearAll:
+ handleClearallCommand(message, cmd);
+ break;
+ // BEGIN | The following commands are deprecated but used to ensure backward compatibility with Hyperion Classic remote control
+ case Command::Transform:
+ case Command::Correction:
+ case Command::Temperature:
+ sendErrorReply("The command is deprecated, please use the Hyperion Web Interface to configure", cmd);
+ break;
+ // END
+ default:
+ break;
+ }
+}
+
+void JsonAPI::handleColorCommand(const QJsonObject &message, const JsonApiCommand& cmd)
{
emit forwardJsonMessage(message);
int priority = message["priority"].toInt();
@@ -285,17 +402,16 @@ void JsonAPI::handleColorCommand(const QJsonObject &message, const QString &comm
const QJsonArray &jsonColor = message["color"].toArray();
std::vector colors;
- // TODO faster copy
- for (const auto &entry : jsonColor)
- {
- colors.emplace_back(uint8_t(entry.toInt()));
- }
+ colors.reserve(static_cast::size_type>(jsonColor.size()));
+ // Transform each entry in jsonColor to uint8_t and append to colors
+ std::transform(jsonColor.begin(), jsonColor.end(), std::back_inserter(colors),
+ [](const QJsonValue &value) { return static_cast(value.toInt()); });
API::setColor(priority, colors, duration, origin);
- sendSuccessReply(command, tan);
+ sendSuccessReply(cmd);
}
-void JsonAPI::handleImageCommand(const QJsonObject &message, const QString &command, int tan)
+void JsonAPI::handleImageCommand(const QJsonObject &message, const JsonApiCommand& cmd)
{
emit forwardJsonMessage(message);
@@ -311,16 +427,15 @@ void JsonAPI::handleImageCommand(const QJsonObject &message, const QString &comm
idata.data = QByteArray::fromBase64(QByteArray(message["imagedata"].toString().toUtf8()));
QString replyMsg;
- if (!API::setImage(idata, COMP_IMAGE, replyMsg))
- {
- sendErrorReply(replyMsg, command, tan);
- return;
+ if (API::setImage(idata, COMP_IMAGE, replyMsg)) {
+ sendSuccessReply(cmd);
+ } else {
+ sendErrorReply(replyMsg, cmd);
}
- sendSuccessReply(command, tan);
}
#if defined(ENABLE_EFFECTENGINE)
-void JsonAPI::handleEffectCommand(const QJsonObject &message, const QString &command, int tan)
+void JsonAPI::handleEffectCommand(const QJsonObject &message, const JsonApiCommand& cmd)
{
emit forwardJsonMessage(message);
@@ -333,526 +448,118 @@ void JsonAPI::handleEffectCommand(const QJsonObject &message, const QString &com
dat.data = message["imageData"].toString("").toUtf8();
dat.args = message["effect"].toObject()["args"].toObject();
- if (API::setEffect(dat))
- sendSuccessReply(command, tan);
- else
- sendErrorReply("Effect '" + dat.effectName + "' not found", command, tan);
+ if (API::setEffect(dat)) {
+ sendSuccessReply(cmd);
+ } else {
+ sendErrorReply("Effect '" + dat.effectName + "' not found", cmd);
+ }
}
-void JsonAPI::handleCreateEffectCommand(const QJsonObject &message, const QString &command, int tan)
+void JsonAPI::handleCreateEffectCommand(const QJsonObject &message, const JsonApiCommand& cmd)
{
const QString resultMsg = API::saveEffect(message);
- resultMsg.isEmpty() ? sendSuccessReply(command, tan) : sendErrorReply(resultMsg, command, tan);
+ resultMsg.isEmpty() ? sendSuccessReply(cmd) : sendErrorReply(resultMsg, cmd);
}
-void JsonAPI::handleDeleteEffectCommand(const QJsonObject &message, const QString &command, int tan)
+void JsonAPI::handleDeleteEffectCommand(const QJsonObject &message, const JsonApiCommand& cmd)
{
const QString res = API::deleteEffect(message["name"].toString());
- res.isEmpty() ? sendSuccessReply(command, tan) : sendErrorReply(res, command, tan);
+ res.isEmpty() ? sendSuccessReply(cmd) : sendErrorReply(res, cmd);
}
#endif
-void JsonAPI::handleSysInfoCommand(const QJsonObject &, const QString &command, int tan)
+void JsonAPI::handleSysInfoCommand(const QJsonObject & /*unused*/, const JsonApiCommand& cmd)
{
- // create result
- QJsonObject result;
- QJsonObject info;
- result["success"] = true;
- result["command"] = command;
- result["tan"] = tan;
-
- SysInfo::HyperionSysInfo data = SysInfo::get();
- QJsonObject system;
- system["kernelType"] = data.kernelType;
- system["kernelVersion"] = data.kernelVersion;
- system["architecture"] = data.architecture;
- system["cpuModelName"] = data.cpuModelName;
- system["cpuModelType"] = data.cpuModelType;
- system["cpuHardware"] = data.cpuHardware;
- system["cpuRevision"] = data.cpuRevision;
- system["wordSize"] = data.wordSize;
- system["productType"] = data.productType;
- system["productVersion"] = data.productVersion;
- system["prettyName"] = data.prettyName;
- system["hostName"] = data.hostName;
- system["domainName"] = data.domainName;
- system["isUserAdmin"] = data.isUserAdmin;
- system["qtVersion"] = data.qtVersion;
-#if defined(ENABLE_EFFECTENGINE)
- system["pyVersion"] = data.pyVersion;
-#endif
- info["system"] = system;
-
- QJsonObject hyperion;
- hyperion["version"] = QString(HYPERION_VERSION);
- hyperion["build"] = QString(HYPERION_BUILD_ID);
- hyperion["gitremote"] = QString(HYPERION_GIT_REMOTE);
- hyperion["time"] = QString(__DATE__ " " __TIME__);
- hyperion["id"] = _authManager->getID();
- hyperion["rootPath"] = _instanceManager->getRootPath();
- hyperion["readOnlyMode"] = _hyperion->getReadOnlyMode();
-
- QCoreApplication* app = QCoreApplication::instance();
- hyperion["isGuiMode"] = qobject_cast(app) ? true : false;
-
- info["hyperion"] = hyperion;
-
- // send the result
- result["info"] = info;
- emit callbackMessage(result);
+ sendSuccessDataReply(JsonInfo::getSystemInfo(_hyperion), cmd);
}
-void JsonAPI::handleServerInfoCommand(const QJsonObject &message, const QString &command, int tan)
+void JsonAPI::handleServerInfoCommand(const QJsonObject &message, const JsonApiCommand& cmd)
{
- QJsonObject info;
+ QJsonObject info {};
+ QStringList errorDetails;
- // collect priority information
- QJsonArray priorities;
- uint64_t now = QDateTime::currentMSecsSinceEpoch();
- QList activePriorities = _hyperion->getActivePriorities();
- activePriorities.removeAll(PriorityMuxer::LOWEST_PRIORITY);
- int currentPriority = _hyperion->getCurrentPriority();
-
- for(int priority : std::as_const(activePriorities))
- {
- const Hyperion::InputInfo &priorityInfo = _hyperion->getPriorityInfo(priority);
-
- QJsonObject item;
- item["priority"] = priority;
-
- if (priorityInfo.timeoutTime_ms > 0 )
- {
- item["duration_ms"] = int(priorityInfo.timeoutTime_ms - now);
- }
-
- // owner has optional informations to the component
- if (!priorityInfo.owner.isEmpty())
- {
- item["owner"] = priorityInfo.owner;
- }
-
- item["componentId"] = QString(hyperion::componentToIdString(priorityInfo.componentId));
- item["origin"] = priorityInfo.origin;
- item["active"] = (priorityInfo.timeoutTime_ms >= -1);
- item["visible"] = (priority == currentPriority);
-
- if (priorityInfo.componentId == hyperion::COMP_COLOR && !priorityInfo.ledColors.empty())
- {
- QJsonObject LEDcolor;
-
- // add RGB Value to Array
- QJsonArray RGBValue;
- RGBValue.append(priorityInfo.ledColors.begin()->red);
- RGBValue.append(priorityInfo.ledColors.begin()->green);
- RGBValue.append(priorityInfo.ledColors.begin()->blue);
- LEDcolor.insert("RGB", RGBValue);
-
- uint16_t Hue;
- float Saturation;
- float Luminace;
-
- // add HSL Value to Array
- QJsonArray HSLValue;
- ColorSys::rgb2hsl(priorityInfo.ledColors.begin()->red,
- priorityInfo.ledColors.begin()->green,
- priorityInfo.ledColors.begin()->blue,
- Hue, Saturation, Luminace);
-
- HSLValue.append(Hue);
- HSLValue.append(Saturation);
- HSLValue.append(Luminace);
- LEDcolor.insert("HSL", HSLValue);
-
- item["value"] = LEDcolor;
- }
-
- (priority == currentPriority)
- ? priorities.prepend(item)
- : priorities.append(item);
- }
-
- info["priorities"] = priorities;
- info["priorities_autoselect"] = _hyperion->sourceAutoSelectEnabled();
-
- // collect adjustment information
- QJsonArray adjustmentArray;
- for (const QString &adjustmentId : _hyperion->getAdjustmentIds())
- {
- const ColorAdjustment *colorAdjustment = _hyperion->getAdjustment(adjustmentId);
- if (colorAdjustment == nullptr)
- {
- Error(_log, "Incorrect color adjustment id: %s", QSTRING_CSTR(adjustmentId));
- continue;
- }
-
- QJsonObject adjustment;
- adjustment["id"] = adjustmentId;
-
- QJsonArray whiteAdjust;
- whiteAdjust.append(colorAdjustment->_rgbWhiteAdjustment.getAdjustmentR());
- whiteAdjust.append(colorAdjustment->_rgbWhiteAdjustment.getAdjustmentG());
- whiteAdjust.append(colorAdjustment->_rgbWhiteAdjustment.getAdjustmentB());
- adjustment.insert("white", whiteAdjust);
-
- QJsonArray redAdjust;
- redAdjust.append(colorAdjustment->_rgbRedAdjustment.getAdjustmentR());
- redAdjust.append(colorAdjustment->_rgbRedAdjustment.getAdjustmentG());
- redAdjust.append(colorAdjustment->_rgbRedAdjustment.getAdjustmentB());
- adjustment.insert("red", redAdjust);
-
- QJsonArray greenAdjust;
- greenAdjust.append(colorAdjustment->_rgbGreenAdjustment.getAdjustmentR());
- greenAdjust.append(colorAdjustment->_rgbGreenAdjustment.getAdjustmentG());
- greenAdjust.append(colorAdjustment->_rgbGreenAdjustment.getAdjustmentB());
- adjustment.insert("green", greenAdjust);
-
- QJsonArray blueAdjust;
- blueAdjust.append(colorAdjustment->_rgbBlueAdjustment.getAdjustmentR());
- blueAdjust.append(colorAdjustment->_rgbBlueAdjustment.getAdjustmentG());
- blueAdjust.append(colorAdjustment->_rgbBlueAdjustment.getAdjustmentB());
- adjustment.insert("blue", blueAdjust);
-
- QJsonArray cyanAdjust;
- cyanAdjust.append(colorAdjustment->_rgbCyanAdjustment.getAdjustmentR());
- cyanAdjust.append(colorAdjustment->_rgbCyanAdjustment.getAdjustmentG());
- cyanAdjust.append(colorAdjustment->_rgbCyanAdjustment.getAdjustmentB());
- adjustment.insert("cyan", cyanAdjust);
-
- QJsonArray magentaAdjust;
- magentaAdjust.append(colorAdjustment->_rgbMagentaAdjustment.getAdjustmentR());
- magentaAdjust.append(colorAdjustment->_rgbMagentaAdjustment.getAdjustmentG());
- magentaAdjust.append(colorAdjustment->_rgbMagentaAdjustment.getAdjustmentB());
- adjustment.insert("magenta", magentaAdjust);
-
- QJsonArray yellowAdjust;
- yellowAdjust.append(colorAdjustment->_rgbYellowAdjustment.getAdjustmentR());
- yellowAdjust.append(colorAdjustment->_rgbYellowAdjustment.getAdjustmentG());
- yellowAdjust.append(colorAdjustment->_rgbYellowAdjustment.getAdjustmentB());
- adjustment.insert("yellow", yellowAdjust);
-
- adjustment["backlightThreshold"] = colorAdjustment->_rgbTransform.getBacklightThreshold();
- adjustment["backlightColored"] = colorAdjustment->_rgbTransform.getBacklightColored();
- adjustment["brightness"] = colorAdjustment->_rgbTransform.getBrightness();
- adjustment["brightnessCompensation"] = colorAdjustment->_rgbTransform.getBrightnessCompensation();
- adjustment["gammaRed"] = colorAdjustment->_rgbTransform.getGammaR();
- adjustment["gammaGreen"] = colorAdjustment->_rgbTransform.getGammaG();
- adjustment["gammaBlue"] = colorAdjustment->_rgbTransform.getGammaB();
-
- adjustment["saturationGain"] = colorAdjustment->_okhsvTransform.getSaturationGain();
- adjustment["brightnessGain"] = colorAdjustment->_okhsvTransform.getBrightnessGain();
-
- adjustment["temperature"] = 6600;
-
- adjustmentArray.append(adjustment);
- }
-
- info["adjustment"] = adjustmentArray;
+ switch (cmd.getSubCommand()) {
+ case SubCommand::Empty:
+ case SubCommand::GetInfo:
+ info["priorities"] = JsonInfo::getPrioritiestInfo(_hyperion);
+ info["priorities_autoselect"] = _hyperion->sourceAutoSelectEnabled();
+ info["adjustment"] = JsonInfo::getAdjustmentInfo(_hyperion, _log);
+ info["ledDevices"] = JsonInfo::getAvailableLedDevices();
+ info["grabbers"] = JsonInfo::getGrabbers(_hyperion);
+ info["videomode"] = QString(videoMode2String(_hyperion->getCurrentVideoMode()));
+ info["cec"] = JsonInfo::getCecInfo();
+ info["services"] = JsonInfo::getServices();
+ info["components"] = JsonInfo::getComponents(_hyperion);
+ info["imageToLedMappingType"] = ImageProcessor::mappingTypeToStr(_hyperion->getLedMappingType());
+ info["instance"] = JsonInfo::getInstanceInfo();
+ info["leds"] = _hyperion->getSetting(settings::LEDS).array();
+ info["activeLedColor"] = JsonInfo::getActiveColors(_hyperion);
#if defined(ENABLE_EFFECTENGINE)
- // collect effect info
- QJsonArray effects;
- const std::list &effectsDefinitions = _hyperion->getEffects();
- for (const EffectDefinition &effectDefinition : effectsDefinitions)
- {
- QJsonObject effect;
- effect["name"] = effectDefinition.name;
- effect["file"] = effectDefinition.file;
- effect["script"] = effectDefinition.script;
- effect["args"] = effectDefinition.args;
- effects.append(effect);
- }
-
- info["effects"] = effects;
+ info["effects"] = JsonInfo::getEffects(_hyperion);
+ info["activeEffects"] = JsonInfo::getActiveEffects(_hyperion);
#endif
- // get available led devices
- QJsonObject ledDevices;
- QJsonArray availableLedDevices;
- for (auto dev : LedDeviceWrapper::getDeviceMap())
- {
- availableLedDevices.append(dev.first);
- }
+ // BEGIN | The following entries are deprecated but used to ensure backward compatibility with hyperion Classic or up to Hyperion 2.0.16
+ info["hostname"] = QHostInfo::localHostName();
+ info["transform"] = JsonInfo::getTransformationInfo(_hyperion);
- ledDevices["available"] = availableLedDevices;
- info["ledDevices"] = ledDevices;
-
- QJsonObject grabbers;
- // SCREEN
- QJsonObject screenGrabbers;
- if (GrabberWrapper::getInstance() != nullptr)
- {
- QStringList activeGrabbers = GrabberWrapper::getInstance()->getActive(_hyperion->getInstanceIndex(), GrabberTypeFilter::SCREEN);
- QJsonArray activeGrabberNames;
- for (auto grabberName : activeGrabbers)
+ if (!_noListener && message.contains("subscribe"))
{
- activeGrabberNames.append(grabberName);
- }
-
- screenGrabbers["active"] = activeGrabberNames;
- }
- QJsonArray availableScreenGrabbers;
- for (auto grabber : GrabberWrapper::availableGrabbers(GrabberTypeFilter::SCREEN))
- {
- availableScreenGrabbers.append(grabber);
- }
- screenGrabbers["available"] = availableScreenGrabbers;
-
- // VIDEO
- QJsonObject videoGrabbers;
- if (GrabberWrapper::getInstance() != nullptr)
- {
- QStringList activeGrabbers = GrabberWrapper::getInstance()->getActive(_hyperion->getInstanceIndex(), GrabberTypeFilter::VIDEO);
- QJsonArray activeGrabberNames;
- for (auto grabberName : activeGrabbers)
- {
- activeGrabberNames.append(grabberName);
- }
-
- videoGrabbers["active"] = activeGrabberNames;
- }
- QJsonArray availableVideoGrabbers;
- for (auto grabber : GrabberWrapper::availableGrabbers(GrabberTypeFilter::VIDEO))
- {
- availableVideoGrabbers.append(grabber);
- }
- videoGrabbers["available"] = availableVideoGrabbers;
-
- // AUDIO
- QJsonObject audioGrabbers;
- if (GrabberWrapper::getInstance() != nullptr)
- {
- QStringList activeGrabbers = GrabberWrapper::getInstance()->getActive(_hyperion->getInstanceIndex(), GrabberTypeFilter::AUDIO);
-
- QJsonArray activeGrabberNames;
- for (auto grabberName : activeGrabbers)
- {
- activeGrabberNames.append(grabberName);
- }
-
- audioGrabbers["active"] = activeGrabberNames;
- }
- QJsonArray availableAudioGrabbers;
- for (auto grabber : GrabberWrapper::availableGrabbers(GrabberTypeFilter::AUDIO))
- {
- availableAudioGrabbers.append(grabber);
- }
- audioGrabbers["available"] = availableAudioGrabbers;
-
- grabbers.insert("screen", screenGrabbers);
- grabbers.insert("video", videoGrabbers);
- grabbers.insert("audio", audioGrabbers);
-
- info["grabbers"] = grabbers;
-
- info["videomode"] = QString(videoMode2String(_hyperion->getCurrentVideoMode()));
-
- QJsonObject cecInfo;
-#if defined(ENABLE_CEC)
- cecInfo["enabled"] = true;
-#else
- cecInfo["enabled"] = false;
-#endif
- info["cec"] = cecInfo;
-
- // get available services
- QJsonArray services;
-
-#if defined(ENABLE_BOBLIGHT_SERVER)
- services.append("boblight");
-#endif
-
-#if defined(ENABLE_CEC)
- services.append("cec");
-#endif
-
-#if defined(ENABLE_EFFECTENGINE)
- services.append("effectengine");
-#endif
-
-#if defined(ENABLE_FORWARDER)
- services.append("forwarder");
-#endif
-
-#if defined(ENABLE_FLATBUF_SERVER)
- services.append("flatbuffer");
-#endif
-
-#if defined(ENABLE_PROTOBUF_SERVER)
- services.append("protobuffer");
-#endif
-
-#if defined(ENABLE_MDNS)
- services.append("mDNS");
-#endif
- services.append("SSDP");
-
- if (!availableScreenGrabbers.isEmpty() || !availableVideoGrabbers.isEmpty() || services.contains("flatbuffer") || services.contains("protobuffer"))
- {
- services.append("borderdetection");
- }
-
- info["services"] = services;
-
- // get available components
- QJsonArray component;
- std::map components = _hyperion->getComponentRegister()->getRegister();
- for (auto comp : components)
- {
- QJsonObject item;
- item["name"] = QString::fromStdString(hyperion::componentToIdString(comp.first));
- item["enabled"] = comp.second;
-
- component.append(item);
- }
-
- info["components"] = component;
- info["imageToLedMappingType"] = ImageProcessor::mappingTypeToStr(_hyperion->getLedMappingType());
-
- // add instance info
- QJsonArray instanceInfo;
- for (const auto &entry : API::getAllInstanceData())
- {
- QJsonObject obj;
- obj.insert("friendly_name", entry["friendly_name"].toString());
- obj.insert("instance", entry["instance"].toInt());
- obj.insert("running", entry["running"].toBool());
- instanceInfo.append(obj);
- }
- info["instance"] = instanceInfo;
-
- // add leds configs
- info["leds"] = _hyperion->getSetting(settings::LEDS).array();
-
- // BEGIN | The following entries are deprecated but used to ensure backward compatibility with hyperion Classic remote control
- // TODO Output the real transformation information instead of default
-
- // HOST NAME
- info["hostname"] = QHostInfo::localHostName();
-
- // TRANSFORM INFORMATION (DEFAULT VALUES)
- QJsonArray transformArray;
- for (const QString &transformId : _hyperion->getAdjustmentIds())
- {
- QJsonObject transform;
- QJsonArray blacklevel, whitelevel, gamma, threshold;
-
- transform["id"] = transformId;
- transform["saturationGain"] = 1.0;
- transform["brightnessGain"] = 1.0;
- transform["saturationLGain"] = 1.0;
- transform["luminanceGain"] = 1.0;
- transform["luminanceMinimum"] = 0.0;
-
- for (int i = 0; i < 3; i++)
- {
- blacklevel.append(0.0);
- whitelevel.append(1.0);
- gamma.append(2.50);
- threshold.append(0.0);
- }
-
- transform.insert("blacklevel", blacklevel);
- transform.insert("whitelevel", whitelevel);
- transform.insert("gamma", gamma);
- transform.insert("threshold", threshold);
-
- transformArray.append(transform);
- }
- info["transform"] = transformArray;
-
-#if defined(ENABLE_EFFECTENGINE)
- // ACTIVE EFFECT INFO
- QJsonArray activeEffects;
- for (const ActiveEffectDefinition &activeEffectDefinition : _hyperion->getActiveEffects())
- {
- if (activeEffectDefinition.priority != PriorityMuxer::LOWEST_PRIORITY - 1)
- {
- QJsonObject activeEffect;
- activeEffect["script"] = activeEffectDefinition.script;
- activeEffect["name"] = activeEffectDefinition.name;
- activeEffect["priority"] = activeEffectDefinition.priority;
- activeEffect["timeout"] = activeEffectDefinition.timeout;
- activeEffect["args"] = activeEffectDefinition.args;
- activeEffects.append(activeEffect);
- }
- }
- info["activeEffects"] = activeEffects;
-#endif
-
- // ACTIVE STATIC LED COLOR
- QJsonArray activeLedColors;
- const Hyperion::InputInfo &priorityInfo = _hyperion->getPriorityInfo(_hyperion->getCurrentPriority());
- if (priorityInfo.componentId == hyperion::COMP_COLOR && !priorityInfo.ledColors.empty())
- {
- // check if LED Color not Black (0,0,0)
- if ((priorityInfo.ledColors.begin()->red +
- priorityInfo.ledColors.begin()->green +
- priorityInfo.ledColors.begin()->blue !=
- 0))
- {
- QJsonObject LEDcolor;
-
- // add RGB Value to Array
- QJsonArray RGBValue;
- RGBValue.append(priorityInfo.ledColors.begin()->red);
- RGBValue.append(priorityInfo.ledColors.begin()->green);
- RGBValue.append(priorityInfo.ledColors.begin()->blue);
- LEDcolor.insert("RGB Value", RGBValue);
-
- uint16_t Hue;
- float Saturation, Luminace;
-
- // add HSL Value to Array
- QJsonArray HSLValue;
- ColorSys::rgb2hsl(priorityInfo.ledColors.begin()->red,
- priorityInfo.ledColors.begin()->green,
- priorityInfo.ledColors.begin()->blue,
- Hue, Saturation, Luminace);
-
- HSLValue.append(Hue);
- HSLValue.append(Saturation);
- HSLValue.append(Luminace);
- LEDcolor.insert("HSL Value", HSLValue);
-
- activeLedColors.append(LEDcolor);
- }
- }
- info["activeLedColor"] = activeLedColors;
-
- // END
-
- sendSuccessDataReply(QJsonDocument(info), command, tan);
-
- // AFTER we send the info, the client might want to subscribe to future updates
- if (message.contains("subscribe"))
- {
- // check if listeners are allowed
- if (_noListener)
- return;
-
- QJsonArray subsArr = message["subscribe"].toArray();
- // catch the all keyword and build a list of all cmds
- if (subsArr.contains("all"))
- {
- subsArr = QJsonArray();
- for (const auto& entry : _jsonCB->getCommands())
+ const QJsonArray &subscriptions = message["subscribe"].toArray();
+ QStringList invaliCommands = _jsonCB->subscribe(subscriptions);
+ if (!invaliCommands.isEmpty())
{
- subsArr.append(entry);
+ errorDetails.append("subscribe - Invalid commands provided: " + invaliCommands.join(','));
}
}
+ // END
- for (const QJsonValueRef entry : subsArr)
+ break;
+
+ case SubCommand::Subscribe:
+ case SubCommand::Unsubscribe:
+ {
+ const QJsonObject ¶ms = message["data"].toObject();
+ const QJsonArray &subscriptions = params["subscriptions"].toArray();
+ if (subscriptions.isEmpty()) {
+ sendErrorReply("Invalid params", {"No subscriptions provided"}, cmd);
+ return;
+ }
+
+ QStringList invaliCommands;
+ if (cmd.subCommand == SubCommand::Subscribe)
{
- // config callbacks just if auth is set
- if ((entry == "settings-update" || entry == "token-update") && !API::isAdminAuthorized())
- continue;
- // silent failure if a subscribe type is not found
- _jsonCB->subscribeFor(entry.toString());
+ invaliCommands = _jsonCB->subscribe(subscriptions);
+ }
+ else
+ {
+ invaliCommands = _jsonCB->unsubscribe(subscriptions);
+ }
+
+ if (!invaliCommands.isEmpty())
+ {
+ errorDetails.append("subscriptions - Invalid commands provided: " + invaliCommands.join(','));
}
}
+ break;
+
+ case SubCommand::GetSubscriptions:
+ info["subscriptions"] = QJsonArray::fromStringList(_jsonCB->getSubscribedCommands());
+ break;
+
+ case SubCommand::GetSubscriptionCommands:
+ info["commands"] = QJsonArray::fromStringList(_jsonCB->getCommands());
+ break;
+
+ default:
+ break;
+ }
+
+ sendSuccessDataReplyWithError(info, cmd, errorDetails);
}
-void JsonAPI::handleClearCommand(const QJsonObject &message, const QString &command, int tan)
+void JsonAPI::handleClearCommand(const QJsonObject &message, const JsonApiCommand& cmd)
{
emit forwardJsonMessage(message);
int priority = message["priority"].toInt();
@@ -860,128 +567,113 @@ void JsonAPI::handleClearCommand(const QJsonObject &message, const QString &comm
if (!API::clearPriority(priority, replyMsg))
{
- sendErrorReply(replyMsg, command, tan);
+ sendErrorReply(replyMsg, cmd);
return;
}
- sendSuccessReply(command, tan);
+ sendSuccessReply(cmd);
}
-void JsonAPI::handleClearallCommand(const QJsonObject &message, const QString &command, int tan)
+void JsonAPI::handleClearallCommand(const QJsonObject &message, const JsonApiCommand& cmd)
{
emit forwardJsonMessage(message);
QString replyMsg;
API::clearPriority(-1, replyMsg);
- sendSuccessReply(command, tan);
+ sendSuccessReply(cmd);
}
-void JsonAPI::handleAdjustmentCommand(const QJsonObject &message, const QString &command, int tan)
+void JsonAPI::handleAdjustmentCommand(const QJsonObject &message, const JsonApiCommand& cmd)
{
const QJsonObject &adjustment = message["adjustment"].toObject();
- const QString adjustmentId = adjustment["id"].toString(_hyperion->getAdjustmentIds().first());
+ const QList adjustmentIds = _hyperion->getAdjustmentIds();
+ if (adjustmentIds.isEmpty()) {
+ sendErrorReply("No adjustment data available", cmd);
+ return;
+ }
+
+ const QString adjustmentId = adjustment["id"].toString(adjustmentIds.first());
ColorAdjustment *colorAdjustment = _hyperion->getAdjustment(adjustmentId);
- if (colorAdjustment == nullptr)
- {
+ if (colorAdjustment == nullptr) {
Warning(_log, "Incorrect adjustment identifier: %s", adjustmentId.toStdString().c_str());
return;
}
- if (adjustment.contains("red"))
- {
- const QJsonArray &values = adjustment["red"].toArray();
- colorAdjustment->_rgbRedAdjustment.setAdjustment(values[0u].toInt(), values[1u].toInt(), values[2u].toInt());
- }
-
- if (adjustment.contains("green"))
- {
- const QJsonArray &values = adjustment["green"].toArray();
- colorAdjustment->_rgbGreenAdjustment.setAdjustment(values[0u].toInt(), values[1u].toInt(), values[2u].toInt());
- }
-
- if (adjustment.contains("blue"))
- {
- const QJsonArray &values = adjustment["blue"].toArray();
- colorAdjustment->_rgbBlueAdjustment.setAdjustment(values[0u].toInt(), values[1u].toInt(), values[2u].toInt());
- }
- if (adjustment.contains("cyan"))
- {
- const QJsonArray &values = adjustment["cyan"].toArray();
- colorAdjustment->_rgbCyanAdjustment.setAdjustment(values[0u].toInt(), values[1u].toInt(), values[2u].toInt());
- }
- if (adjustment.contains("magenta"))
- {
- const QJsonArray &values = adjustment["magenta"].toArray();
- colorAdjustment->_rgbMagentaAdjustment.setAdjustment(values[0u].toInt(), values[1u].toInt(), values[2u].toInt());
- }
- if (adjustment.contains("yellow"))
- {
- const QJsonArray &values = adjustment["yellow"].toArray();
- colorAdjustment->_rgbYellowAdjustment.setAdjustment(values[0u].toInt(), values[1u].toInt(), values[2u].toInt());
- }
- if (adjustment.contains("white"))
- {
- const QJsonArray &values = adjustment["white"].toArray();
- colorAdjustment->_rgbWhiteAdjustment.setAdjustment(values[0u].toInt(), values[1u].toInt(), values[2u].toInt());
- }
-
- if (adjustment.contains("gammaRed"))
- {
- colorAdjustment->_rgbTransform.setGamma(adjustment["gammaRed"].toDouble(), colorAdjustment->_rgbTransform.getGammaG(), colorAdjustment->_rgbTransform.getGammaB());
- }
- if (adjustment.contains("gammaGreen"))
- {
- colorAdjustment->_rgbTransform.setGamma(colorAdjustment->_rgbTransform.getGammaR(), adjustment["gammaGreen"].toDouble(), colorAdjustment->_rgbTransform.getGammaB());
- }
- if (adjustment.contains("gammaBlue"))
- {
- colorAdjustment->_rgbTransform.setGamma(colorAdjustment->_rgbTransform.getGammaR(), colorAdjustment->_rgbTransform.getGammaG(), adjustment["gammaBlue"].toDouble());
- }
-
- if (adjustment.contains("backlightThreshold"))
- {
- colorAdjustment->_rgbTransform.setBacklightThreshold(adjustment["backlightThreshold"].toDouble());
- }
- if (adjustment.contains("backlightColored"))
- {
- colorAdjustment->_rgbTransform.setBacklightColored(adjustment["backlightColored"].toBool());
- }
- if (adjustment.contains("brightness"))
- {
- colorAdjustment->_rgbTransform.setBrightness(adjustment["brightness"].toInt());
- }
- if (adjustment.contains("brightnessCompensation"))
- {
- colorAdjustment->_rgbTransform.setBrightnessCompensation(adjustment["brightnessCompensation"].toInt());
- }
-
- if (adjustment.contains("saturationGain"))
- {
- colorAdjustment->_okhsvTransform.setSaturationGain(adjustment["saturationGain"].toDouble());
- }
-
- if (adjustment.contains("brightnessGain"))
- {
- colorAdjustment->_okhsvTransform.setBrightnessGain(adjustment["brightnessGain"].toDouble());
- }
-
- if (adjustment.contains("temperature"))
- {
- int temperature = adjustment["temperature"].toInt(6500);
- ColorRgb rgb = getRgbFromTemperature(temperature);
-
- ColorCorrection *colorCorrection = _hyperion->getTemperature(adjustmentId);
- colorCorrection->_rgbCorrection.setcorrectionR(rgb.red);
- colorCorrection->_rgbCorrection.setcorrectionG(rgb.green);
- colorCorrection->_rgbCorrection.setcorrectionB(rgb.blue);
- }
-
- // commit the changes
+ applyColorAdjustments(adjustment, colorAdjustment);
+ applyTransforms(adjustment, colorAdjustment);
_hyperion->adjustmentsUpdated();
-
- sendSuccessReply(command, tan);
+ sendSuccessReply(cmd);
}
-void JsonAPI::handleSourceSelectCommand(const QJsonObject &message, const QString &command, int tan)
+void JsonAPI::applyColorAdjustments(const QJsonObject &adjustment, ColorAdjustment *colorAdjustment)
+{
+ applyColorAdjustment("red", adjustment, colorAdjustment->_rgbRedAdjustment);
+ applyColorAdjustment("green", adjustment, colorAdjustment->_rgbGreenAdjustment);
+ applyColorAdjustment("blue", adjustment, colorAdjustment->_rgbBlueAdjustment);
+ applyColorAdjustment("cyan", adjustment, colorAdjustment->_rgbCyanAdjustment);
+ applyColorAdjustment("magenta", adjustment, colorAdjustment->_rgbMagentaAdjustment);
+ applyColorAdjustment("yellow", adjustment, colorAdjustment->_rgbYellowAdjustment);
+ applyColorAdjustment("white", adjustment, colorAdjustment->_rgbWhiteAdjustment);
+}
+
+void JsonAPI::applyColorAdjustment(const QString &colorName, const QJsonObject &adjustment, RgbChannelAdjustment &rgbAdjustment)
+{
+ if (adjustment.contains(colorName)) {
+ const QJsonArray &values = adjustment[colorName].toArray();
+ if (values.size() >= 3) {
+ rgbAdjustment.setAdjustment(static_cast(values[0U].toInt()),
+ static_cast(values[1U].toInt()),
+ static_cast(values[2U].toInt()));
+ }
+ }
+}
+
+void JsonAPI::applyTransforms(const QJsonObject &adjustment, ColorAdjustment *colorAdjustment)
+{
+ applyGammaTransform("gammaRed", adjustment, colorAdjustment->_rgbTransform, 'r');
+ applyGammaTransform("gammaGreen", adjustment, colorAdjustment->_rgbTransform, 'g');
+ applyGammaTransform("gammaBlue", adjustment, colorAdjustment->_rgbTransform, 'b');
+ applyTransform("backlightThreshold", adjustment, colorAdjustment->_rgbTransform, &RgbTransform::setBacklightThreshold);
+ applyTransform("backlightColored", adjustment, colorAdjustment->_rgbTransform, &RgbTransform::setBacklightColored);
+ applyTransform("brightness", adjustment, colorAdjustment->_rgbTransform, &RgbTransform::setBrightness);
+ applyTransform("brightnessCompensation", adjustment, colorAdjustment->_rgbTransform, &RgbTransform::setBrightnessCompensation);
+ applyTransform("saturationGain", adjustment, colorAdjustment->_okhsvTransform, &OkhsvTransform::setSaturationGain);
+ applyTransform("brightnessGain", adjustment, colorAdjustment->_okhsvTransform, &OkhsvTransform::setBrightnessGain);
+}
+
+void JsonAPI::applyGammaTransform(const QString &transformName, const QJsonObject &adjustment, RgbTransform &rgbTransform, char channel)
+{
+ if (adjustment.contains(transformName)) {
+ rgbTransform.setGamma(channel == 'r' ? adjustment[transformName].toDouble() : rgbTransform.getGammaR(),
+ channel == 'g' ? adjustment[transformName].toDouble() : rgbTransform.getGammaG(),
+ channel == 'b' ? adjustment[transformName].toDouble() : rgbTransform.getGammaB());
+ }
+}
+
+template
+void JsonAPI::applyTransform(const QString &transformName, const QJsonObject &adjustment, T &transform, void (T::*setFunction)(bool))
+{
+ if (adjustment.contains(transformName)) {
+ (transform.*setFunction)(adjustment[transformName].toBool());
+ }
+}
+
+template
+void JsonAPI::applyTransform(const QString &transformName, const QJsonObject &adjustment, T &transform, void (T::*setFunction)(double))
+{
+ if (adjustment.contains(transformName)) {
+ (transform.*setFunction)(adjustment[transformName].toDouble());
+ }
+}
+
+template
+void JsonAPI::applyTransform(const QString &transformName, const QJsonObject &adjustment, T &transform, void (T::*setFunction)(uint8_t))
+{
+ if (adjustment.contains(transformName)) {
+ (transform.*setFunction)(static_cast(adjustment[transformName].toInt()));
+ }
+}
+
+void JsonAPI::handleSourceSelectCommand(const QJsonObject &message, const JsonApiCommand& cmd)
{
if (message.contains("auto"))
{
@@ -993,84 +685,63 @@ void JsonAPI::handleSourceSelectCommand(const QJsonObject &message, const QStrin
}
else
{
- sendErrorReply("Priority request is invalid", command, tan);
+ sendErrorReply("Priority request is invalid", cmd);
return;
}
- sendSuccessReply(command, tan);
+ sendSuccessReply(cmd);
}
-void JsonAPI::handleConfigCommand(const QJsonObject &message, const QString &command, int tan)
+void JsonAPI::handleConfigCommand(const QJsonObject& message, const JsonApiCommand& cmd)
{
- QString subcommand = message["subcommand"].toString("");
- QString full_command = command + "-" + subcommand;
+ switch (cmd.subCommand) {
+ case SubCommand::GetSchema:
+ handleSchemaGetCommand(message, cmd);
+ break;
- if (subcommand == "getschema")
- {
- handleSchemaGetCommand(message, full_command, tan);
- }
- else if (subcommand == "getconfig")
- {
- if (_adminAuthorized)
- sendSuccessDataReply(QJsonDocument(_hyperion->getQJsonConfig()), full_command, tan);
- else
- sendErrorReply("No Authorization", command, tan);
- }
- else if (subcommand == "setconfig")
- {
- if (_adminAuthorized)
- handleConfigSetCommand(message, full_command, tan);
- else
- sendErrorReply("No Authorization", command, tan);
- }
- else if (subcommand == "restoreconfig")
- {
- if (_adminAuthorized)
- handleConfigRestoreCommand(message, full_command, tan);
- else
- sendErrorReply("No Authorization", command, tan);
- }
- else if (subcommand == "reload")
- {
- if (_adminAuthorized)
- {
- Debug(_log, "Restarting due to RPC command");
- emit signalEvent(Event::Reload);
+ case SubCommand::GetConfig:
+ sendSuccessDataReply(_hyperion->getQJsonConfig(), cmd);
+ break;
- sendSuccessReply(command + "-" + subcommand, tan);
- }
- else
- {
- sendErrorReply("No Authorization", command, tan);
- }
- }
- else
- {
- sendErrorReply("unknown or missing subcommand", full_command, tan);
+ case SubCommand::SetConfig:
+ handleConfigSetCommand(message, cmd);
+ break;
+
+ case SubCommand::RestoreConfig:
+ handleConfigRestoreCommand(message, cmd);
+ break;
+
+ case SubCommand::Reload:
+ Debug(_log, "Restarting due to RPC command");
+ emit signalEvent(Event::Reload);
+ sendSuccessReply(cmd);
+ break;
+
+ default:
+ break;
}
}
-void JsonAPI::handleConfigSetCommand(const QJsonObject &message, const QString &command, int tan)
+void JsonAPI::handleConfigSetCommand(const QJsonObject &message, const JsonApiCommand& cmd)
{
if (message.contains("config"))
{
QJsonObject config = message["config"].toObject();
if (API::isHyperionEnabled())
{
- if ( API::saveSettings(config) )
- {
- sendSuccessReply(command, tan);
- }
- else
- {
- sendErrorReply("Save settings failed", command, tan);
+ if ( API::saveSettings(config) ) {
+ sendSuccessReply(cmd);
+ } else {
+ sendErrorReply("Save settings failed", cmd);
}
}
else
- sendErrorReply("Saving configuration while Hyperion is disabled isn't possible", command, tan);
+ {
+ sendErrorReply("Saving configuration while Hyperion is disabled isn't possible", cmd);
+ }
}
}
-void JsonAPI::handleConfigRestoreCommand(const QJsonObject &message, const QString &command, int tan)
+void JsonAPI::handleConfigRestoreCommand(const QJsonObject &message, const JsonApiCommand& cmd)
{
if (message.contains("config"))
{
@@ -1079,22 +750,26 @@ void JsonAPI::handleConfigRestoreCommand(const QJsonObject &message, const QStri
{
if ( API::restoreSettings(config) )
{
- sendSuccessReply(command, tan);
+ sendSuccessReply(cmd);
}
else
{
- sendErrorReply("Restore settings failed", command, tan);
+ sendErrorReply("Restore settings failed", cmd);
}
}
else
- sendErrorReply("Restoring configuration while Hyperion is disabled isn't possible", command, tan);
+ {
+ sendErrorReply("Restoring configuration while Hyperion is disabled is not possible", cmd);
+ }
}
}
-void JsonAPI::handleSchemaGetCommand(const QJsonObject& /*message*/, const QString &command, int tan)
+void JsonAPI::handleSchemaGetCommand(const QJsonObject& /*message*/, const JsonApiCommand& cmd)
{
// create result
- QJsonObject schemaJson, alldevices, properties;
+ QJsonObject schemaJson;
+ QJsonObject alldevices;
+ QJsonObject properties;
// make sure the resources are loaded (they may be left out after static linking)
Q_INIT_RESOURCE(resource);
@@ -1142,687 +817,506 @@ void JsonAPI::handleSchemaGetCommand(const QJsonObject& /*message*/, const QStri
schemaJson.insert("properties", properties);
// send the result
- sendSuccessDataReply(QJsonDocument(schemaJson), command, tan);
+ sendSuccessDataReply(schemaJson, cmd);
}
-void JsonAPI::handleComponentStateCommand(const QJsonObject &message, const QString &command, int tan)
+void JsonAPI::handleComponentStateCommand(const QJsonObject &message, const JsonApiCommand& cmd)
{
const QJsonObject &componentState = message["componentstate"].toObject();
QString comp = componentState["component"].toString("invalid");
bool compState = componentState["state"].toBool(true);
QString replyMsg;
- if (!API::setComponentState(comp, compState, replyMsg))
- {
- sendErrorReply(replyMsg, command, tan);
- return;
+ if (API::setComponentState(comp, compState, replyMsg)) {
+ sendSuccessReply(cmd);
+ } else {
+ sendErrorReply(replyMsg, cmd);
}
- sendSuccessReply(command, tan);
}
-void JsonAPI::streamLedColorsUpdate()
+void JsonAPI::handleLedColorsCommand(const QJsonObject& /*message*/, const JsonApiCommand& cmd)
{
- emit streamLedcolorsUpdate(_currentLedValues);
-}
-
-void JsonAPI::handleLedColorsCommand(const QJsonObject &message, const QString &command, int tan)
-{
- // create result
- QString subcommand = message["subcommand"].toString("");
-
- // max 20 Hz (50ms) interval for streaming (default: 10 Hz (100ms))
- qint64 streaming_interval = qMax(message["interval"].toInt(100), 50);
-
- if (subcommand == "ledstream-start")
- {
- _streaming_leds_reply["success"] = true;
- _streaming_leds_reply["command"] = command + "-ledstream-update";
- _streaming_leds_reply["tan"] = tan;
-
- connect(_hyperion, &Hyperion::rawLedColors, this, [=](const std::vector &ledValues) {
-
- if (ledValues != _currentLedValues)
- {
- _currentLedValues = ledValues;
- if (!_ledStreamTimer->isActive() || _ledStreamTimer->interval() != streaming_interval)
- {
- _ledStreamTimer->start(streaming_interval);
- }
- }
- else
- {
- _ledStreamTimer->stop();
- }
- });
-
+ switch (cmd.subCommand) {
+ case SubCommand::LedStreamStart:
+ _jsonCB->subscribe( Subscription::LedColorsUpdate);
// push once
_hyperion->update();
- }
- else if (subcommand == "ledstream-stop")
- {
- disconnect(_hyperion, &Hyperion::rawLedColors, this, 0);
- _ledStreamTimer->stop();
- disconnect(_ledStreamConnection);
- }
- else if (subcommand == "imagestream-start")
- {
- _streaming_image_reply["success"] = true;
- _streaming_image_reply["command"] = command + "-imagestream-update";
- _streaming_image_reply["tan"] = tan;
+ sendSuccessReply(cmd);
+ break;
- connect(_hyperion, &Hyperion::currentImage, this, &JsonAPI::setImage, Qt::UniqueConnection);
- }
- else if (subcommand == "imagestream-stop")
- {
- disconnect(_hyperion, &Hyperion::currentImage, this, 0);
- }
- else
- {
- return;
- }
+ case SubCommand::LedStreamStop:
+ _jsonCB->unsubscribe( Subscription::LedColorsUpdate);
+ sendSuccessReply(cmd);
+ break;
- sendSuccessReply(command + "-" + subcommand, tan);
+ case SubCommand::ImageStreamStart:
+ _jsonCB->subscribe(Subscription::ImageUpdate);
+ sendSuccessReply(cmd);
+ break;
+
+ case SubCommand::ImageStreamStop:
+ _jsonCB->unsubscribe(Subscription::ImageUpdate);
+ sendSuccessReply(cmd);
+ break;
+
+ default:
+ break;
+ }
}
-void JsonAPI::handleLoggingCommand(const QJsonObject &message, const QString &command, int tan)
+void JsonAPI::handleLoggingCommand(const QJsonObject& /*message*/, const JsonApiCommand& cmd)
{
- // create result
- QString subcommand = message["subcommand"].toString("");
+ switch (cmd.subCommand) {
+ case SubCommand::Start:
+ _jsonCB->subscribe("logmsg-update");
+ sendSuccessReply(cmd);
+ break;
- if (API::isAdminAuthorized())
- {
- _streaming_logging_reply["success"] = true;
- _streaming_logging_reply["command"] = command;
- _streaming_logging_reply["tan"] = tan;
-
- if (subcommand == "start")
- {
- if (!_streaming_logging_activated)
- {
- _streaming_logging_reply["command"] = command + "-update";
- connect(LoggerManager::getInstance().data(), &LoggerManager::newLogMessage, this, &JsonAPI::incommingLogMessage);
-
- emit incommingLogMessage (Logger::T_LOG_MESSAGE{}); // needed to trigger log sending
- Debug(_log, "log streaming activated for client %s", _peerAddress.toStdString().c_str());
- }
- }
- else if (subcommand == "stop")
- {
- if (_streaming_logging_activated)
- {
- disconnect(LoggerManager::getInstance().data(), &LoggerManager::newLogMessage, this, &JsonAPI::incommingLogMessage);
- _streaming_logging_activated = false;
- Debug(_log, "log streaming deactivated for client %s", _peerAddress.toStdString().c_str());
- }
- }
- else
- {
- return;
- }
-
- sendSuccessReply(command + "-" + subcommand, tan);
- }
- else
- {
- sendErrorReply("No Authorization", command + "-" + subcommand, tan);
+ case SubCommand::Stop:
+ _jsonCB->unsubscribe("logmsg-update");
+ sendSuccessReply(cmd);
+ break;
+ default:
+ break;
}
}
-void JsonAPI::handleProcessingCommand(const QJsonObject &message, const QString &command, int tan)
+void JsonAPI::handleProcessingCommand(const QJsonObject &message, const JsonApiCommand& cmd)
{
API::setLedMappingType(ImageProcessor::mappingTypeToInt(message["mappingType"].toString("multicolor_mean")));
- sendSuccessReply(command, tan);
+ sendSuccessReply(cmd);
}
-void JsonAPI::handleVideoModeCommand(const QJsonObject &message, const QString &command, int tan)
+void JsonAPI::handleVideoModeCommand(const QJsonObject &message, const JsonApiCommand& cmd)
{
API::setVideoMode(parse3DMode(message["videoMode"].toString("2D")));
- sendSuccessReply(command, tan);
+ sendSuccessReply(cmd);
}
-void JsonAPI::handleAuthorizeCommand(const QJsonObject &message, const QString &command, int tan)
+void JsonAPI::handleAuthorizeCommand(const QJsonObject &message, const JsonApiCommand& cmd)
+{
+ switch (cmd.subCommand) {
+ case SubCommand::TokenRequired:
+ handleTokenRequired(cmd);
+ break;
+ case SubCommand::AdminRequired:
+ handleAdminRequired(cmd);
+ break;
+ case SubCommand::NewPasswordRequired:
+ handleNewPasswordRequired(cmd);
+ break;
+ case SubCommand::Logout:
+ handleLogout(cmd);
+ break;
+ case SubCommand::NewPassword:
+ handleNewPassword(message, cmd);
+ break;
+ case SubCommand::CreateToken:
+ handleCreateToken(message, cmd);
+ break;
+ case SubCommand::RenameToken:
+ handleRenameToken(message, cmd);
+ break;
+ case SubCommand::DeleteToken:
+ handleDeleteToken(message, cmd);
+ break;
+ case SubCommand::RequestToken:
+ handleRequestToken(message, cmd);
+ break;
+ case SubCommand::GetPendingTokenRequests:
+ handleGetPendingTokenRequests(cmd);
+ break;
+ case SubCommand::AnswerRequest:
+ handleAnswerRequest(message, cmd);
+ break;
+ case SubCommand::GetTokenList:
+ handleGetTokenList(cmd);
+ break;
+ case SubCommand::Login:
+ handleLogin(message, cmd);
+ break;
+ default:
+ return;
+ }
+}
+
+void JsonAPI::handleTokenRequired(const JsonApiCommand& cmd)
+{
+ bool isTokenRequired = !islocalConnection() || _authManager->isLocalAuthRequired();
+ QJsonObject response { { "required", isTokenRequired} };
+ sendSuccessDataReply(response, cmd);
+}
+
+void JsonAPI::handleAdminRequired(const JsonApiCommand& cmd)
+{
+ bool isAdminAuthRequired = true;
+ QJsonObject response { { "adminRequired", isAdminAuthRequired} };
+ sendSuccessDataReply(response, cmd);
+}
+
+void JsonAPI::handleNewPasswordRequired(const JsonApiCommand& cmd)
+{
+ QJsonObject response { { "newPasswordRequired", API::hasHyperionDefaultPw() } };
+ sendSuccessDataReply(response, cmd);
+}
+
+void JsonAPI::handleLogout(const JsonApiCommand& cmd)
+{
+ API::logout();
+ sendSuccessReply(cmd);
+}
+
+void JsonAPI::handleNewPassword(const QJsonObject &message, const JsonApiCommand& cmd)
+{
+ const QString password = message["password"].toString().trimmed();
+ const QString newPassword = message["newPassword"].toString().trimmed();
+ if (API::updateHyperionPassword(password, newPassword)) {
+ sendSuccessReply(cmd);
+ } else {
+ sendErrorReply("Failed to update user password", cmd);
+ }
+}
+
+void JsonAPI::handleCreateToken(const QJsonObject &message, const JsonApiCommand& cmd)
{
- const QString &subc = message["subcommand"].toString().trimmed();
- const QString &id = message["id"].toString().trimmed();
- const QString &password = message["password"].toString().trimmed();
- const QString &newPassword = message["newPassword"].toString().trimmed();
const QString &comment = message["comment"].toString().trimmed();
+ AuthManager::AuthDefinition def;
+ const QString createTokenResult = API::createToken(comment, def);
+ if (createTokenResult.isEmpty()) {
+ QJsonObject newTok;
+ newTok["comment"] = def.comment;
+ newTok["id"] = def.id;
+ newTok["token"] = def.token;
- // catch test if auth is required
- if (subc == "tokenRequired")
- {
- QJsonObject req;
- req["required"] = !API::isAuthorized();
-
- sendSuccessDataReply(QJsonDocument(req), command + "-" + subc, tan);
- return;
- }
-
- // catch test if admin auth is required
- if (subc == "adminRequired")
- {
- QJsonObject req;
- req["adminRequired"] = !API::isAdminAuthorized();
- sendSuccessDataReply(QJsonDocument(req), command + "-" + subc, tan);
- return;
- }
-
- // default hyperion password is a security risk, replace it asap
- if (subc == "newPasswordRequired")
- {
- QJsonObject req;
- req["newPasswordRequired"] = API::hasHyperionDefaultPw();
- sendSuccessDataReply(QJsonDocument(req), command + "-" + subc, tan);
- return;
- }
-
- // catch logout
- if (subc == "logout")
- {
- // disconnect all kind of data callbacks
- JsonAPI::stopDataConnections(); // TODO move to API
- API::logout();
- sendSuccessReply(command + "-" + subc, tan);
- return;
- }
-
- // change password
- if (subc == "newPassword")
- {
- // use password, newPassword
- if (API::isAdminAuthorized())
- {
- if (API::updateHyperionPassword(password, newPassword))
- {
- sendSuccessReply(command + "-" + subc, tan);
- return;
- }
- sendErrorReply("Failed to update user password", command + "-" + subc, tan);
- return;
- }
- sendErrorReply("No Authorization", command + "-" + subc, tan);
- return;
- }
-
- // token created from ui
- if (subc == "createToken")
- {
- // use comment
- // for user authorized sessions
- AuthManager::AuthDefinition def;
- const QString createTokenResult = API::createToken(comment, def);
- if (createTokenResult.isEmpty())
- {
- QJsonObject newTok;
- newTok["comment"] = def.comment;
- newTok["id"] = def.id;
- newTok["token"] = def.token;
-
- sendSuccessDataReply(QJsonDocument(newTok), command + "-" + subc, tan);
- return;
- }
- sendErrorReply(createTokenResult, command + "-" + subc, tan);
- return;
- }
-
- // rename Token
- if (subc == "renameToken")
- {
- // use id/comment
- const QString renameTokenResult = API::renameToken(id, comment);
- if (renameTokenResult.isEmpty())
- {
- sendSuccessReply(command + "-" + subc, tan);
- return;
- }
- sendErrorReply(renameTokenResult, command + "-" + subc, tan);
- return;
- }
-
- // delete token
- if (subc == "deleteToken")
- {
- // use id
- const QString deleteTokenResult = API::deleteToken(id);
- if (deleteTokenResult.isEmpty())
- {
- sendSuccessReply(command + "-" + subc, tan);
- return;
- }
- sendErrorReply(deleteTokenResult, command + "-" + subc, tan);
- return;
- }
-
- // catch token request
- if (subc == "requestToken")
- {
- // use id/comment
- const bool &acc = message["accept"].toBool(true);
- if (acc)
- API::setNewTokenRequest(comment, id, tan);
- else
- API::cancelNewTokenRequest(comment, id);
- // client should wait for answer
- return;
- }
-
- // get pending token requests
- if (subc == "getPendingTokenRequests")
- {
- QVector vec;
- if (API::getPendingTokenRequests(vec))
- {
- QJsonArray arr;
- for (const auto &entry : std::as_const(vec))
- {
- QJsonObject obj;
- obj["comment"] = entry.comment;
- obj["id"] = entry.id;
- obj["timeout"] = int(entry.timeoutTime);
- arr.append(obj);
- }
- sendSuccessDataReply(QJsonDocument(arr), command + "-" + subc, tan);
- }
- else
- {
- sendErrorReply("No Authorization", command + "-" + subc, tan);
- }
-
- return;
- }
-
- // accept/deny token request
- if (subc == "answerRequest")
- {
- // use id
- const bool &accept = message["accept"].toBool(false);
- if (!API::handlePendingTokenRequest(id, accept))
- sendErrorReply("No Authorization", command + "-" + subc, tan);
- return;
- }
-
- // get token list
- if (subc == "getTokenList")
- {
- QVector defVect;
- if (API::getTokenList(defVect))
- {
- QJsonArray tArr;
- for (const auto &entry : defVect)
- {
- QJsonObject subO;
- subO["comment"] = entry.comment;
- subO["id"] = entry.id;
- subO["last_use"] = entry.lastUse;
-
- tArr.append(subO);
- }
- sendSuccessDataReply(QJsonDocument(tArr), command + "-" + subc, tan);
- return;
- }
- sendErrorReply("No Authorization", command + "-" + subc, tan);
- return;
- }
-
- // login
- if (subc == "login")
- {
- const QString &token = message["token"].toString().trimmed();
-
- // catch token
- if (!token.isEmpty())
- {
- // userToken is longer
- if (token.size() > 36)
- {
- if (API::isUserTokenAuthorized(token))
- sendSuccessReply(command + "-" + subc, tan);
- else
- sendErrorReply("No Authorization", command + "-" + subc, tan);
-
- return;
- }
- // usual app token is 36
- if (token.size() == 36)
- {
- if (API::isTokenAuthorized(token))
- {
- sendSuccessReply(command + "-" + subc, tan);
- }
- else
- sendErrorReply("No Authorization", command + "-" + subc, tan);
- }
- return;
- }
-
- // password
- // use password
- if (password.size() >= 8)
- {
- QString userTokenRep;
- if (API::isUserAuthorized(password) && API::getUserToken(userTokenRep))
- {
- // Return the current valid Hyperion user token
- QJsonObject obj;
- obj["token"] = userTokenRep;
- sendSuccessDataReply(QJsonDocument(obj), command + "-" + subc, tan);
- }
- else
- sendErrorReply("No Authorization", command + "-" + subc, tan);
- }
- else
- sendErrorReply("Password too short", command + "-" + subc, tan);
+ sendSuccessDataReply(newTok, cmd);
+ } else {
+ sendErrorReply("Token creation failed", {createTokenResult}, cmd);
}
}
-void JsonAPI::handleInstanceCommand(const QJsonObject &message, const QString &command, int tan)
+void JsonAPI::handleRenameToken(const QJsonObject &message, const JsonApiCommand& cmd)
{
- const QString &subc = message["subcommand"].toString();
+ const QString &identifier = message["id"].toString().trimmed();
+ const QString &comment = message["comment"].toString().trimmed();
+ const QString renameTokenResult = API::renameToken(identifier, comment);
+ if (renameTokenResult.isEmpty()) {
+ sendSuccessReply(cmd);
+ } else {
+ sendErrorReply("Token rename failed", {renameTokenResult}, cmd);
+ }
+}
+
+void JsonAPI::handleDeleteToken(const QJsonObject &message, const JsonApiCommand& cmd)
+{
+ const QString &identifier = message["id"].toString().trimmed();
+ const QString deleteTokenResult = API::deleteToken(identifier);
+ if (deleteTokenResult.isEmpty()) {
+ sendSuccessReply(cmd);
+ } else {
+ sendErrorReply("Token deletion failed", {deleteTokenResult}, cmd);
+ }
+}
+
+void JsonAPI::handleRequestToken(const QJsonObject &message, const JsonApiCommand& cmd)
+{
+ const QString &identifier = message["id"].toString().trimmed();
+ const QString &comment = message["comment"].toString().trimmed();
+ const bool &acc = message["accept"].toBool(true);
+ if (acc) {
+ API::setNewTokenRequest(comment, identifier, cmd.tan);
+ } else {
+ API::cancelNewTokenRequest(comment, identifier);
+ // client should wait for answer
+ }
+}
+
+void JsonAPI::handleGetPendingTokenRequests(const JsonApiCommand& cmd)
+{
+ QVector vec;
+ if (API::getPendingTokenRequests(vec)) {
+ QJsonArray pendingTokeRequests;
+ for (const auto &entry : std::as_const(vec))
+ {
+ QJsonObject obj;
+ obj["comment"] = entry.comment;
+ obj["id"] = entry.id;
+ obj["timeout"] = int(entry.timeoutTime);
+ obj["tan"] = entry.tan;
+ pendingTokeRequests.append(obj);
+ }
+ sendSuccessDataReply(pendingTokeRequests, cmd);
+ }
+}
+
+void JsonAPI::handleAnswerRequest(const QJsonObject &message, const JsonApiCommand& cmd)
+{
+ const QString &identifier = message["id"].toString().trimmed();
+ const bool &accept = message["accept"].toBool(false);
+ if (API::handlePendingTokenRequest(identifier, accept)) {
+ sendSuccessReply(cmd);
+ } else {
+ sendErrorReply("Unable to handle token acceptance or denial", cmd);
+ }
+}
+
+void JsonAPI::handleGetTokenList(const JsonApiCommand& cmd)
+{
+ QVector defVect;
+ if (API::getTokenList(defVect))
+ {
+ QJsonArray tokenList;
+ for (const auto &entry : std::as_const(defVect))
+ {
+ QJsonObject token;
+ token["comment"] = entry.comment;
+ token["id"] = entry.id;
+ token["last_use"] = entry.lastUse;
+
+ tokenList.append(token);
+ }
+ sendSuccessDataReply(tokenList, cmd);
+ }
+}
+
+void JsonAPI::handleLogin(const QJsonObject &message, const JsonApiCommand& cmd)
+{
+ const QString &token = message["token"].toString().trimmed();
+ if (!token.isEmpty())
+ {
+ // userToken is longer than app token
+ if (token.size() > APP_TOKEN_LENGTH)
+ {
+ if (API::isUserTokenAuthorized(token)) {
+ sendSuccessReply(cmd);
+ } else {
+ sendNoAuthorization(cmd);
+ }
+
+ return;
+ }
+
+ if (token.size() == APP_TOKEN_LENGTH)
+ {
+ if (API::isTokenAuthorized(token)) {
+ sendSuccessReply(cmd);
+ } else {
+ sendNoAuthorization(cmd);
+ }
+ }
+ return;
+ }
+
+ // password
+ const QString &password = message["password"].toString().trimmed();
+ if (password.size() >= MIN_PASSWORD_LENGTH)
+ {
+ QString userTokenRep;
+ if (API::isUserAuthorized(password) && API::getUserToken(userTokenRep))
+ {
+ // Return the current valid Hyperion user token
+ QJsonObject response { { "token", userTokenRep } };
+ sendSuccessDataReply(response, cmd);
+ }
+ else
+ {
+ sendNoAuthorization(cmd);
+ }
+ }
+ else
+ {
+ sendErrorReply(QString("Password is too short. Minimum length: %1 characters").arg(MIN_PASSWORD_LENGTH), cmd);
+ }
+}
+
+void JsonAPI::issueNewPendingTokenRequest(const QString &identifier, const QString &comment)
+{
+ QJsonObject tokenRequest;
+ tokenRequest["comment"] = comment;
+ tokenRequest["id"] = identifier;
+ tokenRequest["timeout"] = static_cast(NEW_TOKEN_REQUEST_TIMEOUT.count());
+
+ sendNewRequest(tokenRequest, "authorize-tokenRequest");
+}
+
+void JsonAPI::handleTokenResponse(bool success, const QString &token, const QString &comment, const QString &identifier, const int &tan)
+{
+ const QString cmd = "authorize-requestToken";
+ QJsonObject result;
+ result["token"] = token;
+ result["comment"] = comment;
+ result["id"] = identifier;
+
+ if (success) {
+ sendSuccessDataReply(result, cmd, tan);
+ } else {
+ sendErrorReply("Token request timeout or denied", {}, cmd, tan);
+ }
+}
+
+void JsonAPI::handleInstanceCommand(const QJsonObject &message, const JsonApiCommand& cmd)
+{
+ QString replyMsg;
+
const quint8 &inst = static_cast(message["instance"].toInt());
const QString &name = message["name"].toString();
- if (subc == "switchTo")
- {
+ switch (cmd.subCommand) {
+ case SubCommand::SwitchTo:
if (handleInstanceSwitch(inst))
{
- QJsonObject obj;
- obj["instance"] = inst;
- sendSuccessDataReply(QJsonDocument(obj), command + "-" + subc, tan);
+ QJsonObject response { { "instance", inst } };
+ sendSuccessDataReply(response, cmd);
}
else
- sendErrorReply("Selected Hyperion instance isn't running", command + "-" + subc, tan);
- return;
- }
-
- if (subc == "startInstance")
- {
- //Only send update once
- weakConnect(this, &API::onStartInstanceResponse, [this, command, subc] (int tan)
{
- sendSuccessReply(command + "-" + subc, tan);
+ sendErrorReply("Selected Hyperion instance is not running", cmd);
+ }
+ break;
+
+ case SubCommand::StartInstance:
+ //Only send update once
+ weakConnect(this, &API::onStartInstanceResponse, [this, cmd] ()
+ {
+ sendSuccessReply(cmd);
});
- if (!API::startInstance(inst, tan))
- sendErrorReply("Can't start Hyperion instance index " + QString::number(inst), command + "-" + subc, tan);
-
- return;
- }
-
- if (subc == "stopInstance")
- {
+ if (!API::startInstance(inst, cmd.tan))
+ {
+ sendErrorReply("Cannot start Hyperion instance index " + QString::number(inst), cmd);
+ }
+ break;
+ case SubCommand::StopInstance:
// silent fail
API::stopInstance(inst);
- sendSuccessReply(command + "-" + subc, tan);
- return;
- }
+ sendSuccessReply(cmd);
+ break;
- if (subc == "deleteInstance")
- {
- QString replyMsg;
+ case SubCommand::DeleteInstance:
+ handleConfigRestoreCommand(message, cmd);
if (API::deleteInstance(inst, replyMsg))
- sendSuccessReply(command + "-" + subc, tan);
+ {
+ sendSuccessReply(cmd);
+ }
else
- sendErrorReply(replyMsg, command + "-" + subc, tan);
- return;
- }
+ {
+ sendErrorReply(replyMsg, cmd);
+ }
+ break;
- // create and save name requires name
- if (name.isEmpty())
- sendErrorReply("Name string required for this command", command + "-" + subc, tan);
+ case SubCommand::CreateInstance:
+ case SubCommand::SaveName:
+ // create and save name requires name
+ if (name.isEmpty()) {
+ sendErrorReply("Name string required for this command", cmd);
+ return;
+ }
- if (subc == "createInstance")
- {
- QString replyMsg = API::createInstance(name);
- if (replyMsg.isEmpty())
- sendSuccessReply(command + "-" + subc, tan);
- else
- sendErrorReply(replyMsg, command + "-" + subc, tan);
- return;
- }
+ if (cmd.subCommand == SubCommand::CreateInstance) {
+ replyMsg = API::createInstance(name);
+ } else {
+ replyMsg = setInstanceName(inst, name);
+ }
- if (subc == "saveName")
- {
- QString replyMsg = API::setInstanceName(inst, name);
- if (replyMsg.isEmpty())
- sendSuccessReply(command + "-" + subc, tan);
- else
- sendErrorReply(replyMsg, command + "-" + subc, tan);
- return;
+ if (replyMsg.isEmpty()) {
+ sendSuccessReply(cmd);
+ } else {
+ sendErrorReply(replyMsg, cmd);
+ }
+ break;
+ default:
+ break;
}
}
-void JsonAPI::handleLedDeviceCommand(const QJsonObject &message, const QString &command, int tan)
+void JsonAPI::handleLedDeviceCommand(const QJsonObject &message, const JsonApiCommand& cmd)
{
- Debug(_log, "message: [%s]", QString(QJsonDocument(message).toJson(QJsonDocument::Compact)).toUtf8().constData() );
-
- const QString &subc = message["subcommand"].toString().trimmed();
const QString &devType = message["ledDeviceType"].toString().trimmed();
+ const LedDeviceRegistry& ledDevices = LedDeviceWrapper::getDeviceMap();
- QString full_command = command + "-" + subc;
-
- // TODO: Validate that device type is a valid one
-
- {
- QJsonObject config;
- config.insert("type", devType);
- LedDevice* ledDevice = nullptr;
-
- if (subc == "discover")
- {
- ledDevice = LedDeviceFactory::construct(config);
- const QJsonObject ¶ms = message["params"].toObject();
- const QJsonObject devicesDiscovered = ledDevice->discover(params);
-
- Debug(_log, "response: [%s]", QString(QJsonDocument(devicesDiscovered).toJson(QJsonDocument::Compact)).toUtf8().constData() );
-
- sendSuccessDataReply(QJsonDocument(devicesDiscovered), full_command, tan);
- }
- else if (subc == "getProperties")
- {
- ledDevice = LedDeviceFactory::construct(config);
- const QJsonObject ¶ms = message["params"].toObject();
- const QJsonObject deviceProperties = ledDevice->getProperties(params);
-
- Debug(_log, "response: [%s]", QString(QJsonDocument(deviceProperties).toJson(QJsonDocument::Compact)).toUtf8().constData() );
-
- sendSuccessDataReply(QJsonDocument(deviceProperties), full_command, tan);
- }
- else if (subc == "identify")
- {
- ledDevice = LedDeviceFactory::construct(config);
- const QJsonObject ¶ms = message["params"].toObject();
- ledDevice->identify(params);
-
- sendSuccessReply(full_command, tan);
- }
- else if (subc == "addAuthorization")
- {
- ledDevice = LedDeviceFactory::construct(config);
- const QJsonObject& params = message["params"].toObject();
- const QJsonObject response = ledDevice->addAuthorization(params);
-
- Debug(_log, "response: [%s]", QString(QJsonDocument(response).toJson(QJsonDocument::Compact)).toUtf8().constData());
-
- sendSuccessDataReply(QJsonDocument(response), full_command, tan);
- }
- else
- {
- sendErrorReply("Unknown or missing subcommand", full_command, tan);
- }
-
- delete ledDevice;
+ if (ledDevices.count(devType) == 0) {
+ sendErrorReply(QString("Unknown LED-Device type: %1").arg(devType), cmd);
+ return;
}
+
+ QJsonObject config { { "type", devType } };
+ LedDevice* ledDevice = LedDeviceFactory::construct(config);
+
+ switch (cmd.subCommand) {
+ case SubCommand::Discover:
+ handleLedDeviceDiscover(*ledDevice, message, cmd);
+ break;
+ case SubCommand::GetProperties:
+ handleLedDeviceGetProperties(*ledDevice, message, cmd);
+ break;
+ case SubCommand::Identify:
+ handleLedDeviceIdentify(*ledDevice, message, cmd);
+ break;
+ case SubCommand::AddAuthorization:
+ handleLedDeviceAddAuthorization(*ledDevice, message, cmd);
+ break;
+ default:
+ break;
+ }
+
+ delete ledDevice;
}
-void JsonAPI::handleInputSourceCommand(const QJsonObject& message, const QString& command, int tan)
+void JsonAPI::handleLedDeviceDiscover(LedDevice& ledDevice, const QJsonObject& message, const JsonApiCommand& cmd)
{
- DebugIf(verbose, _log, "message: [%s]", QString(QJsonDocument(message).toJson(QJsonDocument::Compact)).toUtf8().constData());
+ const QJsonObject ¶ms = message["params"].toObject();
+ const QJsonObject devicesDiscovered = ledDevice.discover(params);
+ Debug(_log, "response: [%s]", QJsonDocument(devicesDiscovered).toJson(QJsonDocument::Compact).constData() );
+ sendSuccessDataReply(devicesDiscovered, cmd);
+}
- const QString& subc = message["subcommand"].toString().trimmed();
+void JsonAPI::handleLedDeviceGetProperties(LedDevice& ledDevice, const QJsonObject& message, const JsonApiCommand& cmd)
+{
+ const QJsonObject ¶ms = message["params"].toObject();
+ const QJsonObject deviceProperties = ledDevice.getProperties(params);
+ Debug(_log, "response: [%s]", QJsonDocument(deviceProperties).toJson(QJsonDocument::Compact).constData() );
+ sendSuccessDataReply(deviceProperties, cmd);
+}
+
+void JsonAPI::handleLedDeviceIdentify(LedDevice& ledDevice, const QJsonObject& message, const JsonApiCommand& cmd)
+{
+ const QJsonObject ¶ms = message["params"].toObject();
+ ledDevice.identify(params);
+ sendSuccessReply(cmd);
+}
+
+void JsonAPI::handleLedDeviceAddAuthorization(LedDevice& ledDevice, const QJsonObject& message, const JsonApiCommand& cmd)
+{
+ const QJsonObject& params = message["params"].toObject();
+ const QJsonObject response = ledDevice.addAuthorization(params);
+ sendSuccessDataReply(response, cmd);
+}
+
+void JsonAPI::handleInputSourceCommand(const QJsonObject& message, const JsonApiCommand& cmd) {
const QString& sourceType = message["sourceType"].toString().trimmed();
+ const QStringList sourceTypes {"screen", "video", "audio"};
- QString full_command = command + "-" + subc;
+ if (!sourceTypes.contains(sourceType)) {
+ sendErrorReply(QString("Unknown input source type: %1").arg(sourceType), cmd);
+ return;
+ }
- // TODO: Validate that source type is a valid one
- {
- if (subc == "discover")
- {
- QJsonObject inputSourcesDiscovered;
- inputSourcesDiscovered.insert("sourceType", sourceType);
- QJsonArray videoInputs;
- QJsonArray audioInputs;
+ if (cmd.subCommand == SubCommand::Discover) {
-#if defined(ENABLE_V4L2) || defined(ENABLE_MF)
+ const QJsonObject& params = message["params"].toObject();
+ QJsonObject inputSourcesDiscovered = JsonInfo().discoverSources(sourceType, params);
- if (sourceType == "video" )
- {
-#if defined(ENABLE_MF)
- MFGrabber* grabber = new MFGrabber();
-#elif defined(ENABLE_V4L2)
- V4L2Grabber* grabber = new V4L2Grabber();
-#endif
- QJsonObject params;
- videoInputs = grabber->discover(params);
- delete grabber;
- }
- else
-#endif
+ DebugIf(verbose, _log, "response: [%s]", QJsonDocument(inputSourcesDiscovered).toJson(QJsonDocument::Compact).constData());
-#if defined(ENABLE_AUDIO)
- if (sourceType == "audio")
- {
- AudioGrabber* grabber;
-#ifdef WIN32
- grabber = new AudioGrabberWindows();
-#endif
-
-#ifdef __linux__
- grabber = new AudioGrabberLinux();
-#endif
- QJsonObject params;
- audioInputs = grabber->discover(params);
- delete grabber;
- }
- else
-#endif
- {
- DebugIf(verbose, _log, "sourceType: [%s]", QSTRING_CSTR(sourceType));
-
- if (sourceType == "screen")
- {
- QJsonObject params;
-
- QJsonObject device;
- #ifdef ENABLE_QT
- QScopedPointer qtgrabber(new QtGrabber());
- device = qtgrabber->discover(params);
- if (!device.isEmpty() )
- {
- videoInputs.append(device);
- }
- #endif
-
- #ifdef ENABLE_DX
- QScopedPointer dxgrabber (new DirectXGrabber());
- device = dxgrabber->discover(params);
- if (!device.isEmpty() )
- {
- videoInputs.append(device);
- }
- #endif
-
- #ifdef ENABLE_X11
- QScopedPointer x11Grabber(new X11Grabber());
- device = x11Grabber->discover(params);
- if (!device.isEmpty() )
- {
- videoInputs.append(device);
- }
- #endif
-
- #ifdef ENABLE_XCB
- QScopedPointer xcbGrabber (new XcbGrabber());
- device = xcbGrabber->discover(params);
- if (!device.isEmpty() )
- {
- videoInputs.append(device);
- }
- #endif
-
- //Ignore FB for Amlogic, as it is embedded in the Amlogic grabber itself
- #if defined(ENABLE_FB) && !defined(ENABLE_AMLOGIC)
-
- QScopedPointer fbGrabber(new FramebufferFrameGrabber());
- device = fbGrabber->discover(params);
- if (!device.isEmpty() )
- {
- videoInputs.append(device);
- }
- #endif
-
- #if defined(ENABLE_DISPMANX)
- QScopedPointer dispmanx(new DispmanxFrameGrabber());
- if (dispmanx->isAvailable())
- {
- device = dispmanx->discover(params);
- if (!device.isEmpty() )
- {
- videoInputs.append(device);
- }
- }
- #endif
-
- #if defined(ENABLE_AMLOGIC)
- QScopedPointer amlGrabber(new AmlogicGrabber());
- device = amlGrabber->discover(params);
- if (!device.isEmpty() )
- {
- videoInputs.append(device);
- }
- #endif
-
- #if defined(ENABLE_OSX)
- QScopedPointer osxGrabber(new OsxFrameGrabber());
- device = osxGrabber->discover(params);
- if (!device.isEmpty() )
- {
- videoInputs.append(device);
- }
- #endif
- }
-
- }
- inputSourcesDiscovered["video_sources"] = videoInputs;
- inputSourcesDiscovered["audio_sources"] = audioInputs;
-
- DebugIf(verbose, _log, "response: [%s]", QString(QJsonDocument(inputSourcesDiscovered).toJson(QJsonDocument::Compact)).toUtf8().constData());
-
- sendSuccessDataReply(QJsonDocument(inputSourcesDiscovered), full_command, tan);
- }
- else
- {
- sendErrorReply("Unknown or missing subcommand", full_command, tan);
- }
+ sendSuccessDataReply(inputSourcesDiscovered, cmd);
}
}
-void JsonAPI::handleServiceCommand(const QJsonObject &message, const QString &command, int tan)
+void JsonAPI::handleServiceCommand(const QJsonObject &message, const JsonApiCommand& cmd)
{
- DebugIf(verbose, _log, "message: [%s]", QString(QJsonDocument(message).toJson(QJsonDocument::Compact)).toUtf8().constData());
-
- const QString &subc = message["subcommand"].toString().trimmed();
- const QString type = message["serviceType"].toString().trimmed();
-
- QString full_command = command + "-" + subc;
-
- if (subc == "discover")
+ if (cmd.subCommand == SubCommand::Discover)
{
QByteArray serviceType;
-
- QJsonObject servicesDiscovered;
- QJsonObject servicesOfType;
- QJsonArray serviceList;
-
+ const QString type = message["serviceType"].toString().trimmed();
#ifdef ENABLE_MDNS
QString discoveryMethod("mDNS");
serviceType = MdnsServiceRegister::getServiceType(type);
@@ -1831,212 +1325,168 @@ void JsonAPI::handleServiceCommand(const QJsonObject &message, const QString &co
#endif
if (!serviceType.isEmpty())
{
+ QJsonArray serviceList;
#ifdef ENABLE_MDNS
QMetaObject::invokeMethod(MdnsBrowser::getInstance().data(), "browseForServiceType",
- Qt::QueuedConnection, Q_ARG(QByteArray, serviceType));
+ Qt::QueuedConnection, Q_ARG(QByteArray, serviceType));
serviceList = MdnsBrowser::getInstance().data()->getServicesDiscoveredJson(serviceType, MdnsServiceRegister::getServiceNameFilter(type), DEFAULT_DISCOVER_TIMEOUT);
#endif
+ QJsonObject servicesDiscovered;
+ QJsonObject servicesOfType;
+
servicesOfType.insert(type, serviceList);
servicesDiscovered.insert("discoveryMethod", discoveryMethod);
servicesDiscovered.insert("services", servicesOfType);
- sendSuccessDataReply(QJsonDocument(servicesDiscovered), full_command, tan);
+ sendSuccessDataReply(servicesDiscovered, cmd);
}
else
{
- sendErrorReply(QString("Discovery of service type [%1] via %2 not supported").arg(type, discoveryMethod), full_command, tan);
+ sendErrorReply(QString("Discovery of service type [%1] via %2 not supported").arg(type, discoveryMethod), cmd);
}
}
- else
- {
- sendErrorReply("Unknown or missing subcommand", full_command, tan);
- }
}
-void JsonAPI::handleSystemCommand(const QJsonObject &message, const QString &command, int tan)
+void JsonAPI::handleSystemCommand(const QJsonObject& /*message*/, const JsonApiCommand& cmd)
{
- DebugIf(verbose, _log, "message: [%s]", QString(QJsonDocument(message).toJson(QJsonDocument::Compact)).toUtf8().constData());
-
- const QString &subc = message["subcommand"].toString().trimmed();
-
- if (subc == "suspend")
- {
+ switch (cmd.subCommand) {
+ case SubCommand::Suspend:
emit signalEvent(Event::Suspend);
- sendSuccessReply(command + "-" + subc, tan);
- }
- else if (subc == "resume")
- {
+ break;
+ case SubCommand::Resume:
emit signalEvent(Event::Resume);
- sendSuccessReply(command + "-" + subc, tan);
- }
- else if (subc == "restart")
- {
+ break;
+ case SubCommand::Restart:
emit signalEvent(Event::Restart);
- sendSuccessReply(command + "-" + subc, tan);
- }
- else if (subc == "toggleSuspend")
- {
+ break;
+ case SubCommand::ToggleSuspend:
emit signalEvent(Event::ToggleSuspend);
- sendSuccessReply(command + "-" + subc, tan);
- }
- else if (subc == "idle")
- {
+ break;
+ case SubCommand::Idle:
emit signalEvent(Event::Idle);
- sendSuccessReply(command + "-" + subc, tan);
- }
- else if (subc == "resumeIdle")
- {
- emit signalEvent(Event::ResumeIdle);
- sendSuccessReply(command + "-" + subc, tan);
- }
- else if (subc == "toggleIdle")
- {
+ break;
+ case SubCommand::ToggleIdle:
emit signalEvent(Event::ToggleIdle);
- sendSuccessReply(command + "-" + subc, tan);
+ break;
+ default:
+ return;
}
- else
+ sendSuccessReply(cmd);
+}
+
+QJsonObject JsonAPI::getBasicCommandReply(bool success, const QString &command, int tan, InstanceCmd::Type isInstanceCmd) const
+{
+ QJsonObject reply;
+ reply["success"] = success;
+ reply["command"] = command;
+ reply["tan"] = tan;
+
+ if (isInstanceCmd == InstanceCmd::Yes || ( isInstanceCmd == InstanceCmd::Multi && !_noListener))
{
- QString full_command = command + "-" + subc;
- sendErrorReply("Unknown or missing subcommand", full_command, tan);
+ reply["instance"] = _hyperion->getInstanceIndex();
}
+ return reply;
}
-void JsonAPI::handleNotImplemented(const QString &command, int tan)
+void JsonAPI::sendSuccessReply(const JsonApiCommand& cmd)
{
- sendErrorReply("Command not implemented", command, tan);
+ sendSuccessReply(cmd.toString(), cmd.tan, cmd.isInstanceCmd);
}
-void JsonAPI::sendSuccessReply(const QString &command, int tan)
+void JsonAPI::sendSuccessReply(const QString &command, int tan, InstanceCmd::Type isInstanceCmd)
{
- // create reply
- QJsonObject reply;
- reply["instance"] = _hyperion->getInstanceIndex();
- reply["success"] = true;
- reply["command"] = command;
- reply["tan"] = tan;
-
- // send reply
- emit callbackMessage(reply);
+ emit callbackMessage(getBasicCommandReply(true, command, tan , isInstanceCmd));
}
-void JsonAPI::sendSuccessDataReply(const QJsonDocument &doc, const QString &command, int tan)
+void JsonAPI::sendSuccessDataReply(const QJsonValue &infoData, const JsonApiCommand& cmd)
{
- QJsonObject reply;
- reply["instance"] = _hyperion->getInstanceIndex();
- reply["success"] = true;
- reply["command"] = command;
- reply["tan"] = tan;
- if (doc.isArray())
- reply["info"] = doc.array();
- else
- reply["info"] = doc.object();
+ sendSuccessDataReplyWithError(infoData, cmd.toString(), cmd.tan, {}, cmd.isInstanceCmd);
+}
+
+void JsonAPI::sendSuccessDataReply(const QJsonValue &infoData, const QString &command, int tan, InstanceCmd::Type isInstanceCmd)
+{
+ sendSuccessDataReplyWithError(infoData, command, tan, {}, isInstanceCmd);
+}
+
+void JsonAPI::sendSuccessDataReplyWithError(const QJsonValue &infoData, const JsonApiCommand& cmd, const QStringList& errorDetails)
+{
+ sendSuccessDataReplyWithError(infoData, cmd.toString(), cmd.tan, errorDetails, cmd.isInstanceCmd);
+}
+
+void JsonAPI::sendSuccessDataReplyWithError(const QJsonValue &infoData, const QString &command, int tan, const QStringList& errorDetails, InstanceCmd::Type isInstanceCmd)
+{
+ QJsonObject reply {getBasicCommandReply(true, command, tan , isInstanceCmd)};
+ reply["info"] = infoData;
+
+ if (!errorDetails.isEmpty())
+ {
+ QJsonArray errorsArray;
+ for (const QString& errorString : errorDetails)
+ {
+ QJsonObject errorObject;
+ errorObject["description"] = errorString;
+ errorsArray.append(errorObject);
+ }
+ reply["errorData"] = errorsArray;
+ }
emit callbackMessage(reply);
}
-void JsonAPI::sendErrorReply(const QString &error, const QString &command, int tan)
+void JsonAPI::sendErrorReply(const QString &error, const JsonApiCommand& cmd)
{
- // create reply
- QJsonObject reply;
- reply["instance"] = _hyperion->getInstanceIndex();
- reply["success"] = false;
+ sendErrorReply(error, {}, cmd.toString(), cmd.tan, cmd.isInstanceCmd);
+}
+
+void JsonAPI::sendErrorReply(const QString &error, const QStringList& errorDetails, const JsonApiCommand& cmd)
+{
+ sendErrorReply(error, errorDetails, cmd.toString(), cmd.tan, cmd.isInstanceCmd);
+}
+
+void JsonAPI::sendErrorReply(const QString &error, const QStringList& errorDetails, const QString &command, int tan, InstanceCmd::Type isInstanceCmd)
+{
+ QJsonObject reply {getBasicCommandReply(false, command, tan , isInstanceCmd)};
reply["error"] = error;
- reply["command"] = command;
- reply["tan"] = tan;
+ if (!errorDetails.isEmpty())
+ {
+ QJsonArray errorsArray;
+ for (const QString& errorString : errorDetails)
+ {
+ QJsonObject errorObject;
+ errorObject["description"] = errorString;
+ errorsArray.append(errorObject);
+ }
+ reply["errorData"] = errorsArray;
+ }
- // send reply
emit callbackMessage(reply);
}
-void JsonAPI::streamLedcolorsUpdate(const std::vector &ledColors)
+void JsonAPI::sendNewRequest(const QJsonValue &infoData, const JsonApiCommand& cmd)
{
- QJsonObject result;
- QJsonArray leds;
+ sendSuccessDataReplyWithError(infoData, cmd.toString(), cmd.isInstanceCmd);
+}
- for (const auto &color : ledColors)
+void JsonAPI::sendNewRequest(const QJsonValue &infoData, const QString &command, InstanceCmd::Type isInstanceCmd)
+{
+ QJsonObject request;
+ request["command"] = command;
+
+ if (isInstanceCmd != InstanceCmd::No)
{
- leds << QJsonValue(color.red) << QJsonValue(color.green) << QJsonValue(color.blue);
+ request["instance"] = _hyperion->getInstanceIndex();
}
- result["leds"] = leds;
- _streaming_leds_reply["result"] = result;
+ request["info"] = infoData;
- // send the result
- emit callbackMessage(_streaming_leds_reply);
+ emit callbackMessage(request);
}
-void JsonAPI::setImage(const Image &image)
+void JsonAPI::sendNoAuthorization(const JsonApiCommand& cmd)
{
- QImage jpgImage((const uint8_t *)image.memptr(), image.width(), image.height(), 3 * image.width(), QImage::Format_RGB888);
- QByteArray ba;
- QBuffer buffer(&ba);
- buffer.open(QIODevice::WriteOnly);
- jpgImage.save(&buffer, "jpg");
-
- QJsonObject result;
- result["image"] = "data:image/jpg;base64," + QString(ba.toBase64());
- _streaming_image_reply["result"] = result;
- emit callbackMessage(_streaming_image_reply);
-}
-
-void JsonAPI::incommingLogMessage(const Logger::T_LOG_MESSAGE &msg)
-{
- QJsonObject result, message;
- QJsonArray messageArray;
-
- if (!_streaming_logging_activated)
- {
- _streaming_logging_activated = true;
- QMetaObject::invokeMethod(LoggerManager::getInstance().data(), "getLogMessageBuffer",
- Qt::DirectConnection,
- Q_RETURN_ARG(QJsonArray, messageArray),
- Q_ARG(Logger::LogLevel, _log->getLogLevel()));
- }
- else
- {
- message["loggerName"] = msg.loggerName;
- message["loggerSubName"] = msg.loggerSubName;
- message["function"] = msg.function;
- message["line"] = QString::number(msg.line);
- message["fileName"] = msg.fileName;
- message["message"] = msg.message;
- message["levelString"] = msg.levelString;
- message["utime"] = QString::number(msg.utime);
-
- messageArray.append(message);
- }
-
- result.insert("messages", messageArray);
- _streaming_logging_reply["result"] = result;
-
- // send the result
- emit callbackMessage(_streaming_logging_reply);
-}
-
-void JsonAPI::newPendingTokenRequest(const QString &id, const QString &comment)
-{
- QJsonObject obj;
- obj["comment"] = comment;
- obj["id"] = id;
- obj["timeout"] = 180000;
-
- sendSuccessDataReply(QJsonDocument(obj), "authorize-tokenRequest", 1);
-}
-
-void JsonAPI::handleTokenResponse(bool success, const QString &token, const QString &comment, const QString &id, const int &tan)
-{
- const QString cmd = "authorize-requestToken";
- QJsonObject result;
- result["token"] = token;
- result["comment"] = comment;
- result["id"] = id;
-
- if (success)
- sendSuccessDataReply(QJsonDocument(result), cmd, tan);
- else
- sendErrorReply("Token request timeout or denied", cmd, tan);
+ sendErrorReply(NO_AUTHORIZATION, cmd);
}
void JsonAPI::handleInstanceStateChange(InstanceState state, quint8 instance, const QString& /*name */)
@@ -2048,24 +1498,45 @@ void JsonAPI::handleInstanceStateChange(InstanceState state, quint8 instance, co
{
handleInstanceSwitch();
}
- break;
+ break;
case InstanceState::H_STARTED:
case InstanceState::H_STOPPED:
case InstanceState::H_CREATED:
case InstanceState::H_DELETED:
- default:
- break;
+ break;
}
}
void JsonAPI::stopDataConnections()
{
- LoggerManager::getInstance()->disconnect();
- _streaming_logging_activated = false;
_jsonCB->resetSubscriptions();
- // led stream colors
- disconnect(_hyperion, &Hyperion::rawLedColors, this, 0);
- _ledStreamTimer->stop();
- disconnect(_ledStreamConnection);
+ LoggerManager::getInstance()->disconnect();
+}
+
+QString JsonAPI::findCommand (const QString& jsonString)
+{
+ QString commandValue {"unknown"};
+
+ // Define a regular expression pattern to match the value associated with the key "command"
+ static QRegularExpression regex("\"command\"\\s*:\\s*\"([^\"]+)\"");
+ QRegularExpressionMatch match = regex.match(jsonString);
+
+ if (match.hasMatch()) {
+ commandValue = match.captured(1);
+ }
+ return commandValue;
+}
+
+int JsonAPI::findTan (const QString& jsonString)
+{
+ int tanValue {0};
+ static QRegularExpression regex("\"tan\"\\s*:\\s*(\\d+)");
+ QRegularExpressionMatch match = regex.match(jsonString);
+
+ if (match.hasMatch()) {
+ QString valueStr = match.captured(1);
+ tanValue = valueStr.toInt();
+ }
+ return tanValue;
}
diff --git a/libsrc/api/JsonCB.cpp b/libsrc/api/JsonCB.cpp
deleted file mode 100644
index e3c4b32a..00000000
--- a/libsrc/api/JsonCB.cpp
+++ /dev/null
@@ -1,416 +0,0 @@
-// proj incl
-#include
-
-// hyperion
-#include
-
-// HyperionIManager
-#include
-// components
-
-#include
-// priorityMuxer
-
-#include
-
-// utils
-#include
-
-// qt
-#include
-#include
-
-// Image to led map helper
-#include
-
-using namespace hyperion;
-
-JsonCB::JsonCB(QObject* parent)
- : QObject(parent)
- , _hyperion(nullptr)
- , _componentRegister(nullptr)
- , _prioMuxer(nullptr)
-{
- _availableCommands << "components-update" << "priorities-update" << "imageToLedMapping-update"
- << "adjustment-update" << "videomode-update" << "settings-update" << "leds-update" << "instance-update" << "token-update";
-
- #if defined(ENABLE_EFFECTENGINE)
- _availableCommands << "effects-update";
- #endif
-
- qRegisterMetaType("InputsMap");
-}
-
-bool JsonCB::subscribeFor(const QString& type, bool unsubscribe)
-{
- if(!_availableCommands.contains(type))
- return false;
-
- if(unsubscribe)
- _subscribedCommands.removeAll(type);
- else
- _subscribedCommands << type;
-
- if(type == "components-update")
- {
- if(unsubscribe)
- disconnect(_componentRegister, &ComponentRegister::updatedComponentState, this, &JsonCB::handleComponentState);
- else
- connect(_componentRegister, &ComponentRegister::updatedComponentState, this, &JsonCB::handleComponentState, Qt::UniqueConnection);
- }
-
- if(type == "priorities-update")
- {
- if (unsubscribe)
- disconnect(_prioMuxer, &PriorityMuxer::prioritiesChanged, this, &JsonCB::handlePriorityUpdate);
- else
- connect(_prioMuxer, &PriorityMuxer::prioritiesChanged, this, &JsonCB::handlePriorityUpdate, Qt::UniqueConnection);
- }
-
- if(type == "imageToLedMapping-update")
- {
- if(unsubscribe)
- disconnect(_hyperion, &Hyperion::imageToLedsMappingChanged, this, &JsonCB::handleImageToLedsMappingChange);
- else
- connect(_hyperion, &Hyperion::imageToLedsMappingChanged, this, &JsonCB::handleImageToLedsMappingChange, Qt::UniqueConnection);
- }
-
- if(type == "adjustment-update")
- {
- if(unsubscribe)
- disconnect(_hyperion, &Hyperion::adjustmentChanged, this, &JsonCB::handleAdjustmentChange);
- else
- connect(_hyperion, &Hyperion::adjustmentChanged, this, &JsonCB::handleAdjustmentChange, Qt::UniqueConnection);
- }
-
- if(type == "videomode-update")
- {
- if(unsubscribe)
- disconnect(_hyperion, &Hyperion::newVideoMode, this, &JsonCB::handleVideoModeChange);
- else
- connect(_hyperion, &Hyperion::newVideoMode, this, &JsonCB::handleVideoModeChange, Qt::UniqueConnection);
- }
-
-#if defined(ENABLE_EFFECTENGINE)
- if(type == "effects-update")
- {
- if(unsubscribe)
- disconnect(_hyperion, &Hyperion::effectListUpdated, this, &JsonCB::handleEffectListChange);
- else
- connect(_hyperion, &Hyperion::effectListUpdated, this, &JsonCB::handleEffectListChange, Qt::UniqueConnection);
- }
-#endif
-
- if(type == "settings-update")
- {
- if(unsubscribe)
- disconnect(_hyperion, &Hyperion::settingsChanged, this, &JsonCB::handleSettingsChange);
- else
- connect(_hyperion, &Hyperion::settingsChanged, this, &JsonCB::handleSettingsChange, Qt::UniqueConnection);
- }
-
- if(type == "leds-update")
- {
- if(unsubscribe)
- disconnect(_hyperion, &Hyperion::settingsChanged, this, &JsonCB::handleLedsConfigChange);
- else
- connect(_hyperion, &Hyperion::settingsChanged, this, &JsonCB::handleLedsConfigChange, Qt::UniqueConnection);
- }
-
-
- if(type == "instance-update")
- {
- if(unsubscribe)
- disconnect(HyperionIManager::getInstance(), &HyperionIManager::change, this, &JsonCB::handleInstanceChange);
- else
- connect(HyperionIManager::getInstance(), &HyperionIManager::change, this, &JsonCB::handleInstanceChange, Qt::UniqueConnection);
- }
-
- if (type == "token-update")
- {
- if (unsubscribe)
- disconnect(AuthManager::getInstance(), &AuthManager::tokenChange, this, &JsonCB::handleTokenChange);
- else
- connect(AuthManager::getInstance(), &AuthManager::tokenChange, this, &JsonCB::handleTokenChange, Qt::UniqueConnection);
- }
-
- return true;
-}
-
-void JsonCB::resetSubscriptions()
-{
- for(const auto & entry : getSubscribedCommands())
- {
- subscribeFor(entry, true);
- }
-}
-
-void JsonCB::setSubscriptionsTo(Hyperion* hyperion)
-{
- assert(hyperion);
-
- // get current subs
- QStringList currSubs(getSubscribedCommands());
-
- // stop subs
- resetSubscriptions();
-
- // update pointer
- _hyperion = hyperion;
- _componentRegister = _hyperion->getComponentRegister();
- _prioMuxer = _hyperion->getMuxerInstance();
-
- // re-apply subs
- for(const auto & entry : currSubs)
- {
- subscribeFor(entry);
- }
-}
-
-void JsonCB::doCallback(const QString& cmd, const QVariant& data)
-{
- QJsonObject obj;
- obj["instance"] = _hyperion->getInstanceIndex();
- obj["command"] = cmd;
-
- if (data.userType() == QMetaType::QJsonArray)
- obj["data"] = data.toJsonArray();
- else
- obj["data"] = data.toJsonObject();
-
- emit newCallback(obj);
-}
-
-void JsonCB::handleComponentState(hyperion::Components comp, bool state)
-{
- QJsonObject data;
- data["name"] = componentToIdString(comp);
- data["enabled"] = state;
-
- doCallback("components-update", QVariant(data));
-}
-
-void JsonCB::handlePriorityUpdate(int currentPriority, const PriorityMuxer::InputsMap& activeInputs)
-{
- QJsonObject data;
- QJsonArray priorities;
- uint64_t now = QDateTime::currentMSecsSinceEpoch();
- QList activePriorities = activeInputs.keys();
-
- activePriorities.removeAll(PriorityMuxer::LOWEST_PRIORITY);
-
- for (int priority : std::as_const(activePriorities)) {
-
- const Hyperion::InputInfo& priorityInfo = activeInputs[priority];
-
- QJsonObject item;
- item["priority"] = priority;
-
- if (priorityInfo.timeoutTime_ms > 0 )
- {
- item["duration_ms"] = int(priorityInfo.timeoutTime_ms - now);
- }
-
- // owner has optional informations to the component
- if(!priorityInfo.owner.isEmpty())
- {
- item["owner"] = priorityInfo.owner;
- }
-
- item["componentId"] = QString(hyperion::componentToIdString(priorityInfo.componentId));
- item["origin"] = priorityInfo.origin;
- item["active"] = (priorityInfo.timeoutTime_ms >= -1);
- item["visible"] = (priority == currentPriority);
-
- if(priorityInfo.componentId == hyperion::COMP_COLOR && !priorityInfo.ledColors.empty())
- {
- QJsonObject LEDcolor;
-
- // add RGB Value to Array
- QJsonArray RGBValue;
- RGBValue.append(priorityInfo.ledColors.begin()->red);
- RGBValue.append(priorityInfo.ledColors.begin()->green);
- RGBValue.append(priorityInfo.ledColors.begin()->blue);
- LEDcolor.insert("RGB", RGBValue);
-
- uint16_t Hue;
- float Saturation;
- float Luminace;
-
- // add HSL Value to Array
- QJsonArray HSLValue;
- ColorSys::rgb2hsl(priorityInfo.ledColors.begin()->red,
- priorityInfo.ledColors.begin()->green,
- priorityInfo.ledColors.begin()->blue,
- Hue, Saturation, Luminace);
-
- HSLValue.append(Hue);
- HSLValue.append(Saturation);
- HSLValue.append(Luminace);
- LEDcolor.insert("HSL", HSLValue);
-
- item["value"] = LEDcolor;
- }
- priorities.append(item);
- }
-
- data["priorities"] = priorities;
- data["priorities_autoselect"] = _hyperion->sourceAutoSelectEnabled();
-
- doCallback("priorities-update", QVariant(data));
-}
-
-void JsonCB::handleImageToLedsMappingChange(int mappingType)
-{
- QJsonObject data;
- data["imageToLedMappingType"] = ImageProcessor::mappingTypeToStr(mappingType);
-
- doCallback("imageToLedMapping-update", QVariant(data));
-}
-
-void JsonCB::handleAdjustmentChange()
-{
- QJsonArray adjustmentArray;
- for (const QString& adjustmentId : _hyperion->getAdjustmentIds())
- {
- const ColorAdjustment * colorAdjustment = _hyperion->getAdjustment(adjustmentId);
- if (colorAdjustment == nullptr)
- {
- continue;
- }
-
- QJsonObject adjustment;
- adjustment["id"] = adjustmentId;
-
- QJsonArray whiteAdjust;
- whiteAdjust.append(colorAdjustment->_rgbWhiteAdjustment.getAdjustmentR());
- whiteAdjust.append(colorAdjustment->_rgbWhiteAdjustment.getAdjustmentG());
- whiteAdjust.append(colorAdjustment->_rgbWhiteAdjustment.getAdjustmentB());
- adjustment.insert("white", whiteAdjust);
-
- QJsonArray redAdjust;
- redAdjust.append(colorAdjustment->_rgbRedAdjustment.getAdjustmentR());
- redAdjust.append(colorAdjustment->_rgbRedAdjustment.getAdjustmentG());
- redAdjust.append(colorAdjustment->_rgbRedAdjustment.getAdjustmentB());
- adjustment.insert("red", redAdjust);
-
- QJsonArray greenAdjust;
- greenAdjust.append(colorAdjustment->_rgbGreenAdjustment.getAdjustmentR());
- greenAdjust.append(colorAdjustment->_rgbGreenAdjustment.getAdjustmentG());
- greenAdjust.append(colorAdjustment->_rgbGreenAdjustment.getAdjustmentB());
- adjustment.insert("green", greenAdjust);
-
- QJsonArray blueAdjust;
- blueAdjust.append(colorAdjustment->_rgbBlueAdjustment.getAdjustmentR());
- blueAdjust.append(colorAdjustment->_rgbBlueAdjustment.getAdjustmentG());
- blueAdjust.append(colorAdjustment->_rgbBlueAdjustment.getAdjustmentB());
- adjustment.insert("blue", blueAdjust);
-
- QJsonArray cyanAdjust;
- cyanAdjust.append(colorAdjustment->_rgbCyanAdjustment.getAdjustmentR());
- cyanAdjust.append(colorAdjustment->_rgbCyanAdjustment.getAdjustmentG());
- cyanAdjust.append(colorAdjustment->_rgbCyanAdjustment.getAdjustmentB());
- adjustment.insert("cyan", cyanAdjust);
-
- QJsonArray magentaAdjust;
- magentaAdjust.append(colorAdjustment->_rgbMagentaAdjustment.getAdjustmentR());
- magentaAdjust.append(colorAdjustment->_rgbMagentaAdjustment.getAdjustmentG());
- magentaAdjust.append(colorAdjustment->_rgbMagentaAdjustment.getAdjustmentB());
- adjustment.insert("magenta", magentaAdjust);
-
- QJsonArray yellowAdjust;
- yellowAdjust.append(colorAdjustment->_rgbYellowAdjustment.getAdjustmentR());
- yellowAdjust.append(colorAdjustment->_rgbYellowAdjustment.getAdjustmentG());
- yellowAdjust.append(colorAdjustment->_rgbYellowAdjustment.getAdjustmentB());
- adjustment.insert("yellow", yellowAdjust);
-
- adjustment["backlightThreshold"] = colorAdjustment->_rgbTransform.getBacklightThreshold();
- adjustment["backlightColored"] = colorAdjustment->_rgbTransform.getBacklightColored();
- adjustment["brightness"] = colorAdjustment->_rgbTransform.getBrightness();
- adjustment["brightnessCompensation"] = colorAdjustment->_rgbTransform.getBrightnessCompensation();
- adjustment["gammaRed"] = colorAdjustment->_rgbTransform.getGammaR();
- adjustment["gammaGreen"] = colorAdjustment->_rgbTransform.getGammaG();
- adjustment["gammaBlue"] = colorAdjustment->_rgbTransform.getGammaB();
-
- adjustmentArray.append(adjustment);
- }
-
- doCallback("adjustment-update", QVariant(adjustmentArray));
-}
-
-void JsonCB::handleVideoModeChange(VideoMode mode)
-{
- QJsonObject data;
- data["videomode"] = QString(videoMode2String(mode));
- doCallback("videomode-update", QVariant(data));
-}
-
-#if defined(ENABLE_EFFECTENGINE)
-void JsonCB::handleEffectListChange()
-{
- QJsonArray effectList;
- QJsonObject effects;
- const std::list & effectsDefinitions = _hyperion->getEffects();
- for (const EffectDefinition & effectDefinition : effectsDefinitions)
- {
- QJsonObject effect;
- effect["name"] = effectDefinition.name;
- effect["file"] = effectDefinition.file;
- effect["script"] = effectDefinition.script;
- effect["args"] = effectDefinition.args;
- effectList.append(effect);
- };
- effects["effects"] = effectList;
- doCallback("effects-update", QVariant(effects));
-}
-#endif
-
-void JsonCB::handleSettingsChange(settings::type type, const QJsonDocument& data)
-{
- QJsonObject dat;
- if(data.isObject())
- dat[typeToString(type)] = data.object();
- else
- dat[typeToString(type)] = data.array();
-
- doCallback("settings-update", QVariant(dat));
-}
-
-void JsonCB::handleLedsConfigChange(settings::type type, const QJsonDocument& data)
-{
- if(type == settings::LEDS)
- {
- QJsonObject dat;
- dat[typeToString(type)] = data.array();
- doCallback("leds-update", QVariant(dat));
- }
-}
-
-void JsonCB::handleInstanceChange()
-{
- QJsonArray arr;
-
- for(const auto & entry : HyperionIManager::getInstance()->getInstanceData())
- {
- QJsonObject obj;
- obj.insert("friendly_name", entry["friendly_name"].toString());
- obj.insert("instance", entry["instance"].toInt());
- obj.insert("running", entry["running"].toBool());
- arr.append(obj);
- }
- doCallback("instance-update", QVariant(arr));
-}
-
-void JsonCB::handleTokenChange(const QVector &def)
-{
- QJsonArray arr;
- for (const auto &entry : def)
- {
- QJsonObject sub;
- sub["comment"] = entry.comment;
- sub["id"] = entry.id;
- sub["last_use"] = entry.lastUse;
- arr.push_back(sub);
- }
- doCallback("token-update", QVariant(arr));
-}
diff --git a/libsrc/api/JsonCallbacks.cpp b/libsrc/api/JsonCallbacks.cpp
new file mode 100644
index 00000000..0e156727
--- /dev/null
+++ b/libsrc/api/JsonCallbacks.cpp
@@ -0,0 +1,459 @@
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include