diff --git a/.ci/ci_build.sh b/.ci/ci_build.sh index 1eda6dc0..df276f00 100755 --- a/.ci/ci_build.sh +++ b/.ci/ci_build.sh @@ -3,6 +3,7 @@ # detect CI if [ "$HOME" != "" ]; then # GitHub Actions + echo "Github Actions detected" CI_NAME="$(uname -s | tr '[:upper:]' '[:lower:]')" CI_BUILD_DIR="$GITHUB_WORKSPACE" else @@ -20,8 +21,11 @@ else PLATFORM=${PLATFORM}-dev fi +echo "Platform: ${PLATFORM}, build type: ${BUILD_TYPE}, CI_NAME: $CI_NAME, docker image: ${DOCKER_IMAGE}, docker type: ${DOCKER_TAG}" + # Build the package on osx or linux if [[ "$CI_NAME" == 'osx' || "$CI_NAME" == 'darwin' ]]; then + echo "Compile Hyperion on OSX or Darwin" # compile prepare mkdir build || exit 1 cd build @@ -31,12 +35,13 @@ if [[ "$CI_NAME" == 'osx' || "$CI_NAME" == 'darwin' ]]; then exit 0; exit 1 || { echo "---> Hyperion compilation failed! Abort"; exit 5; } elif [[ $CI_NAME == *"mingw64_nt"* || "$CI_NAME" == 'windows_nt' ]]; then + echo "Compile Hyperion on Windows" # compile prepare echo "Number of Cores $NUMBER_OF_PROCESSORS" mkdir build || exit 1 cd build - cmake -G "Visual Studio 17 2022" -A x64 -DPLATFORM=${PLATFORM} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} ../ || exit 2 - cmake --build . --target package --config Release -- -nologo -v:m -maxcpucount || exit 3 + cmake -G "Visual Studio 17 2022" -A x64 -DPLATFORM=${PLATFORM} -DCMAKE_BUILD_TYPE="Release" ../ || exit 2 + cmake --build . --target package --config "Release" -- -nologo -v:m -maxcpucount || exit 3 exit 0; exit 1 || { echo "---> Hyperion compilation failed! Abort"; exit 5; } elif [[ "$CI_NAME" == 'linux' ]]; then diff --git a/.gitmodules b/.gitmodules index c618661c..2fbcb55e 100644 --- a/.gitmodules +++ b/.gitmodules @@ -9,3 +9,6 @@ [submodule "dependencies/external/protobuf"] path = dependencies/external/protobuf url = https://github.com/protocolbuffers/protobuf +[submodule "dependencies/external/qmdnsengine"] + path = dependencies/external/qmdnsengine + url = https://github.com/nitroshare/qmdnsengine.git diff --git a/.vscode/hyperion.code-workspace b/.vscode/hyperion.code-workspace index 3b5db3c7..52540678 100644 --- a/.vscode/hyperion.code-workspace +++ b/.vscode/hyperion.code-workspace @@ -16,7 +16,6 @@ "ms-vscode.cmake-tools", "spmeesseman.vscode-taskexplorer", "yzhang.markdown-all-in-one", - "CoenraadS.bracket-pair-colorizer", "vscode-icons-team.vscode-icons", "editorconfig.editorconfig" ] diff --git a/CHANGELOG.md b/CHANGELOG.md index c02ab0f6..4b30ac7c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,10 +12,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Allow to build a "light" version of Hyperion, i.e. no grabbers, or services like flat-/proto buffers, boblight, CEC - Allow to restart Hyperion via Systray +- LED-Device: Support retry attempts enabling devices, e.g. to open devices after network or a device itself got available (#1302) + (Fixes that devices got "stuck", if initial open failed e.g. for WLED, Hue) +- LED-Devices: New UDP-DDP (Distributed Display Protocol) device to overcome the 490 LEDs limitation of UDP-RAW - LED Matrix Layout - Support vertical cabling direction (#1420) - Support additional Yeelight models - LED-Devices: Show warning, if get properties failed (Network devices: indication that network device is not reachable) - hyperion-remote: Show image filename in UI for images sent +- mDNS support for all platforms inkl. Windows (#740) +- LED-Devices mDNS discovery support and ease of configuration (Cololight, Nanoleaf, Philips-Hue, WLED, Yeelight); removes the need to configure IP-Address, as address is resolved automatically. +- Forwarder: mDNS discovery support and ease of configuration of other Hyperion instances +- Grabber: mDNS discovery for standalone grabbers - New language: Japanese ### Changed @@ -24,6 +31,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Boblight: Support multiple Boblight clients with different priorities - UI: Allow configuration of a Boblight server per LED-instance - UI: LED Layout - Removed limitations on indention +- mDNS Publisher :Aligned Hyperion mDNS names to general conventions and simplified namings +- Refactored Philips Hue wizard and LED-Device +- LED-Devices: WLED's default streaming protocol is now UDP-DDP. More than 490 LEDs are supported now (requires minimum WLED 0.11.0). UDP-RAW is still supported in parallel (via expert settings). ### Fixed @@ -31,6 +41,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Effects: Fix that start effect is stuck on UI - Fixes that the Led-Device output flow was interrupted, by an enabling API request on an already enabled device (#967 - Yeelight - Workaround: Ignore error when setting music mode = off, but the music-mode is already off (#1372) +- Fixed: Hue Entertainment mode does not resume after no signal (#930) - Standalone grabbers: Improved fps help/error text, fixed default address and port, fixed auto discovery of Hyperion server in hyperion-remote - Fixed Qt version override, e.g. set via QTDIR - Remote control UI: Treat duration=0 as endless @@ -40,8 +51,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fixed: Signal detection does not switch off all instances (#1281) - Reworked PriorityMuxer and Sub-scriptions - Do not kill application on SIGILL-signal (#1435) +- Start JSON and WebServer only, if Hyperion's instance 0 is available ## Removed +- UI Removed sessions (of other Hyperions) +- Replaced existing AVAHI/Bonjour code by QMdnsEngine ## [2.0.12](https://github.com/hyperion-project/hyperion.ng/releases/tag/2.0.12) - 2021-11-20 Hyperion's November release brings you some new features, removed IPv6 address related limitations, as well as fixing a couple of issues. diff --git a/CMakeLists.txt b/CMakeLists.txt index 18daf817..06b9c04d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.0.0) +cmake_minimum_required(VERSION 3.1.0) message( STATUS "CMake Version: ${CMAKE_VERSION}" ) @@ -38,6 +38,26 @@ if ( CCACHE_FOUND ) set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK ccache) endif(CCACHE_FOUND) +# enable C++14; MSVC doesn't have c++14 feature switch +if(NOT CMAKE_CXX_COMPILER_ID MATCHES "MSVC") + if(APPLE) + include(CheckCXXCompilerFlag) + CHECK_CXX_COMPILER_FLAG("Werror=unguarded-availability" REQUIRED_UNGUARDED_AVAILABILITY) + if(REQUIRED_UNGUARDED_AVAILABILITY) + list(APPEND CMAKE_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS} "Werror=unguarded-availability") + endif() + endif() + + if(CMAKE_CXX_COMPILER_ID MATCHES "GNU" OR CMAKE_C_COMPILER_ID MATCHES "GNU") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-psabi") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-psabi") + endif() + + set(CMAKE_CXX_STANDARD 14) + set(CXX_STANDARD_REQUIRED ON) + set(CXX_EXTENSIONS OFF) +endif() + # Set build variables # Grabber SET ( DEFAULT_AMLOGIC OFF ) @@ -69,18 +89,18 @@ SET ( DEFAULT_DEV_USB_HID OFF ) SET ( DEFAULT_DEV_WS281XPWM OFF ) # Services -SET ( DEFAULT_AVAHI ON ) SET ( DEFAULT_EFFECTENGINE ON ) SET ( DEFAULT_EXPERIMENTAL OFF ) +SET ( DEFAULT_MDNS ON ) SET ( DEFAULT_REMOTE_CTL ON ) # Build SET ( DEFAULT_JSONCHECKS ON ) SET ( DEFAULT_DEPLOY_DEPENDENCIES ON ) -SET ( DEFAULT_USE_SHARED_AVAHI_LIBS ON ) SET ( DEFAULT_USE_SYSTEM_FLATBUFFERS_LIBS OFF ) SET ( DEFAULT_USE_SYSTEM_PROTO_LIBS OFF ) SET ( DEFAULT_USE_SYSTEM_MBEDTLS_LIBS OFF ) +SET ( DEFAULT_USE_SYSTEM_QMDNS_LIBS OFF ) SET ( DEFAULT_TESTS OFF ) # Build Hyperion with a reduced set of functionality, overwrites other default values @@ -175,8 +195,6 @@ elseif ( "${PLATFORM}" MATCHES "x11" ) endif() elseif ( "${PLATFORM}" STREQUAL "imx6" ) SET ( DEFAULT_FB ON ) -elseif (WIN32) - SET ( DEFAULT_AVAHI OFF) endif() # enable tests for -dev builds @@ -319,15 +337,15 @@ removeIndent() message(STATUS "Services options:") addIndent(" - ") -option(ENABLE_AVAHI "Enable Zeroconf" ${DEFAULT_AVAHI}) -message(STATUS "ENABLE_AVAHI = " ${ENABLE_AVAHI}) - option(ENABLE_EFFECTENGINE "Enable Effect-Engine" ${DEFAULT_EFFECTENGINE}) message(STATUS "ENABLE_EFFECTENGINE = " ${ENABLE_EFFECTENGINE}) option(ENABLE_EXPERIMENTAL "Compile experimental features" ${DEFAULT_EXPERIMENTAL}) message(STATUS "ENABLE_EXPERIMENTAL = ${ENABLE_EXPERIMENTAL}") +option(ENABLE_MDNS "Enable mDNS (aka Zeroconf)" ${DEFAULT_MDNS}) +message(STATUS "ENABLE_MDNS = " ${ENABLE_MDNS}) + option(ENABLE_REMOTE_CTL "Enable Hyperion remote control" ${DEFAULT_REMOTE_CTL}) message(STATUS "ENABLE_REMOTE_CTL = " ${ENABLE_REMOTE_CTL}) @@ -342,19 +360,27 @@ message(STATUS "ENABLE_JSONCHECKS = ${ENABLE_JSONCHECKS}") option(ENABLE_DEPLOY_DEPENDENCIES "Deploy with dependencies" ${DEFAULT_DEPLOY_DEPENDENCIES}) message(STATUS "ENABLE_DEPLOY_DEPENDENCIES = ${ENABLE_DEPLOY_DEPENDENCIES}") +if(ENABLE_FLATBUF_SERVER OR ENABLE_FLATBUF_CONNECT) + message(STATUS "DEFAULT_USE_SYSTEM_FLATBUFFERS_LIBS = ${DEFAULT_USE_SYSTEM_FLATBUFFERS_LIBS}") +endif() + +if(ENABLE_PROTOBUF_SERVER) + message(STATUS "DEFAULT_USE_SYSTEM_PROTO_LIBS = ${DEFAULT_USE_SYSTEM_PROTO_LIBS}") +endif() + +message(STATUS "DEFAULT_USE_SYSTEM_MBEDTLS_LIBS = ${DEFAULT_USE_SYSTEM_MBEDTLS_LIBS}") + +if (ENABLE_MDNS) + message(STATUS "DEFAULT_USE_SYSTEM_QMDNS_LIBS = ${DEFAULT_USE_SYSTEM_QMDNS_LIBS}") +endif() + + option(ENABLE_PROFILER "enable profiler capabilities - not for release code" OFF) message(STATUS "ENABLE_PROFILER = ${ENABLE_PROFILER}") option(ENABLE_TESTS "Compile additional test applications" ${DEFAULT_TESTS}) message(STATUS "ENABLE_TESTS = ${ENABLE_TESTS}") -if (ENABLE_AVAHI) -message(STATUS "DEFAULT_USE_SHARED_AVAHI_LIBS = ${DEFAULT_USE_SHARED_AVAHI_LIBS}") -endif() -message(STATUS "DEFAULT_USE_SYSTEM_FLATBUFFERS_LIBS = ${DEFAULT_USE_SYSTEM_FLATBUFFERS_LIBS}") -message(STATUS "DEFAULT_USE_SYSTEM_MBEDTLS_LIBS = ${DEFAULT_USE_SYSTEM_MBEDTLS_LIBS}") -message(STATUS "DEFAULT_USE_SYSTEM_PROTO_LIBS = ${DEFAULT_USE_SYSTEM_PROTO_LIBS}") - removeIndent() SET ( FLATBUFFERS_INSTALL_BIN_DIR ${CMAKE_BINARY_DIR}/flatbuf ) @@ -504,21 +530,23 @@ if (DEFINED QTDIR) list(PREPEND CMAKE_PREFIX_PATH ${QTDIR} "${QTDIR}/lib") endif() -message( STATUS "CMAKE_PREFIX_PATH used: ${CMAKE_PREFIX_PATH}" ) +if (CMAKE_PREFIX_PATH) + message( STATUS "CMAKE_PREFIX_PATH used: ${CMAKE_PREFIX_PATH}" ) +endif() # find QT libs find_package(QT NAMES Qt6 Qt5 COMPONENTS Core Gui Network Sql Widgets REQUIRED) message( STATUS "Found Qt Version: ${QT_VERSION}" ) if (${QT_VERSION_MAJOR} GREATER_EQUAL 6 ) - SET(QT_MIN_VERSION "6.2.0") -ELSE() + SET(QT_MIN_VERSION "6.2.2") +else() SET(QT_MIN_VERSION "5.5.0") -ENDIF() +endif() -IF ( "${QT_VERSION}" VERSION_LESS "${QT_MIN_VERSION}" ) +if ( "${QT_VERSION}" VERSION_LESS "${QT_MIN_VERSION}" ) message( FATAL_ERROR "Your Qt version is to old! Minimum required ${QT_MIN_VERSION}" ) -ENDIF() +endif() find_package(Qt${QT_VERSION_MAJOR} ${QT_VERSION} COMPONENTS Core Gui Network Sql Widgets REQUIRED) @@ -526,7 +554,7 @@ message( STATUS "Qt version used: ${QT_VERSION}" ) if (APPLE AND (${QT_VERSION_MAJOR} GREATER_EQUAL 6) ) set(OPENSSL_ROOT_DIR /usr/local/opt/openssl) -ENDIF() +endif() # Add libusb and pthreads find_package(libusb-1.0 REQUIRED) @@ -557,33 +585,6 @@ endif () set_directory_properties(PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES "${GENERATED_QRC}" ) -# enable C++11; MSVC doesn't have c++11 feature switch -if(NOT CMAKE_CXX_COMPILER_ID MATCHES "MSVC") - include(CheckCXXCompilerFlag) - CHECK_CXX_COMPILER_FLAG("-std=c++11" COMPILER_SUPPORTS_CXX11) - CHECK_CXX_COMPILER_FLAG("-std=c++0x" COMPILER_SUPPORTS_CXX0X) - - if(APPLE) - CHECK_CXX_COMPILER_FLAG("Werror=unguarded-availability" REQUIRED_UNGUARDED_AVAILABILITY) - if(REQUIRED_UNGUARDED_AVAILABILITY) - list(APPEND CMAKE_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS} "Werror=unguarded-availability") - endif() - endif() - - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall") - if (CMAKE_COMPILER_IS_GNUCXX) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-psabi") - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-psabi") - endif() - if(COMPILER_SUPPORTS_CXX11) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") - elseif(COMPILER_SUPPORTS_CXX0X) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x") - else() - message(STATUS "No support for C++11 detected. Compilation will most likely fail on your compiler") - endif() -endif() - # uninstall target configure_file( "${CMAKE_CURRENT_SOURCE_DIR}/cmake/cmake_uninstall.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake" IMMEDIATE @ONLY) add_custom_target(uninstall COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake) diff --git a/CMakeSettings.json b/CMakeSettings.json index 2dfe3af3..a6c02f16 100644 --- a/CMakeSettings.json +++ b/CMakeSettings.json @@ -17,7 +17,7 @@ ], "environments": [ // Replace path with your installation path - //{ "QTDIR": "C:/Qt/6.2.0/msvc2019_64/" }, + //{ "QTDIR": "C:/Qt/6.2.2/msvc2019_64/" }, //{ "VULKAN_SDK": "C:/VulkanSDK/1.2.182.0" } ] }, @@ -38,7 +38,7 @@ ], "environments": [ // Replace path with your installation path - //{ "QTDIR": "C:/Qt/6.2.0/msvc2019_64/" }, + //{ "QTDIR": "C:/Qt/6.2.2/msvc2019_64/" }, //{ "VULKAN_SDK": "C:/VulkanSDK/1.2.182.0" } ] } diff --git a/HyperionConfig.h.in b/HyperionConfig.h.in index 87db4cd3..4f49658c 100644 --- a/HyperionConfig.h.in +++ b/HyperionConfig.h.in @@ -69,8 +69,8 @@ // Define to enable the WS281x-PWM-via-DMA-device using jgarff's library #cmakedefine ENABLE_DEV_WS281XPWM -// Define to enable AVAHI -#cmakedefine ENABLE_AVAHI +// Define to enable MDNS +#cmakedefine ENABLE_MDNS // Define to enable EFFECTENGINE #cmakedefine ENABLE_EFFECTENGINE diff --git a/assets/webconfig/i18n/en.json b/assets/webconfig/i18n/en.json index 565ecd8f..1c1d2bf0 100644 --- a/assets/webconfig/i18n/en.json +++ b/assets/webconfig/i18n/en.json @@ -88,6 +88,16 @@ "conf_leds_layout_cl_leftbottom": "Left 50% - 100% Bottom", "conf_leds_layout_cl_leftmiddle": "Left 25% - 75% Middle", "conf_leds_layout_cl_lefttop": "Left 0% - 50% Top", + "conf_leds_layout_cl_lightPosBottomLeft14": "Bottom: 0 - 25% from Left", + "conf_leds_layout_cl_lightPosBottomLeft12": "Bottom: 25 - 50% from Left", + "conf_leds_layout_cl_lightPosBottomLeft34": "Bottom: 50 - 75% from Left", + "conf_leds_layout_cl_lightPosBottomLeft11": "Bottom: 75 - 100% from Left", + "conf_leds_layout_cl_lightPosBottomLeft112": "Bottom: 0 - 50% from Left", + "conf_leds_layout_cl_lightPosBottomLeft121": "Bottom: 50 - 100% from Left", + "conf_leds_layout_cl_lightPosBottomLeftNewMid": "Bottom: 25 - 75% from Left", + "conf_leds_layout_cl_lightPosTopLeft112": "Top: 0 - 50% from Left", + "conf_leds_layout_cl_lightPosTopLeft121": "Top: 50 - 100% from Left", + "conf_leds_layout_cl_lightPosTopLeftNewMid": "Top: 25 - 75% from Left", "conf_leds_layout_cl_overlap": "Overlap", "conf_leds_layout_cl_reversdir": "Reverse direction", "conf_leds_layout_cl_right": "Right", @@ -329,6 +339,8 @@ "edt_conf_enum_top_down": "Top down", "edt_conf_enum_transeffect_smooth": "Smooth", "edt_conf_enum_transeffect_sudden": "Sudden", + "edt_conf_enum_udp_ddp": "DDP", + "edt_conf_enum_udp_raw": "RAW", "edt_conf_enum_unicolor_mean": "Unicolor", "edt_conf_fbs_heading_title": "Flatbuffers Server", "edt_conf_fbs_timeout_expl": "If no data is received for the given period, the component will be (soft) disabled.", @@ -355,13 +367,20 @@ "edt_conf_fge_heading_title": "Boot Effect/Color", "edt_conf_fge_type_expl": "Choose between a color or effect.", "edt_conf_fge_type_title": "Type", - "edt_conf_fw_flat_expl": "One flatbuffer target per line. Contains IP:PORT (Example: 127.0.0.1:19401)", + "edt_conf_fw_flat_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_title": "List of flatbuffer targets", "edt_conf_fw_heading_title": "Forwarder", - "edt_conf_fw_json_expl": "One json target per line. Contains IP:PORT (Example: 127.0.0.1:19446)", - "edt_conf_fw_json_itemtitle": "Json target", - "edt_conf_fw_json_title": "List of json targets", + "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_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", + "edt_conf_fw_service_name_title": "Service name", "edt_conf_gen_configVersion_title": "Configuration version", "edt_conf_gen_heading_title": "General Settings", "edt_conf_gen_name_expl": "A user defined name which is used to detect Hyperion. (Helpful with more than one Hyperion instance)", @@ -510,6 +529,10 @@ "edt_dev_general_autostart_title_info": "The LED device is switched-on during startup or not", "edt_dev_general_colorOrder_title": "RGB byte order", "edt_dev_general_colorOrder_title_info": "The device's color order", + "edt_dev_general_enableAttempts_title": "Connection attempts", + "edt_dev_general_enableAttempts_title_info": "Number of attempts connecting a device before it goes into an error state.", + "edt_dev_general_enableAttemptsInterval_title": "Retry interval", + "edt_dev_general_enableAttemptsInterval_title_info": "Intervall between two connection attempts.", "edt_dev_general_hardwareLedCount_title": "Hardware LED count", "edt_dev_general_hardwareLedCount_title_info": "The number of physical LEDs availabe for the given device", "edt_dev_general_heading_title": "General Settings", @@ -523,17 +546,15 @@ "edt_dev_spec_baudrate_title": "Baudrate", "edt_dev_spec_blackLightsTimeout_title": "Signal detection timeout on black", "edt_dev_spec_brightnessFactor_title": "Brightness factor", - "edt_dev_spec_brightnessMax_title": "Brightness maximum", - "edt_dev_spec_brightnessMin_title": "Brightness minimum", "edt_dev_spec_brightnessOverwrite_title": "Overwrite brightness", "edt_dev_spec_brightnessThreshold_title": "Signal detection brightness minimum", "edt_dev_spec_brightness_title": "Brightness", + "edt_dev_spec_candyGamma_title" : "'Candy' mode (double gamma correction)", "edt_dev_spec_chanperfixture_title": "Channels per Fixture", "edt_dev_spec_cid_title": "CID", "edt_dev_spec_clientKey_title": "Clientkey", "edt_dev_spec_colorComponent_title": "Colour component", "edt_dev_spec_debugLevel_title": "Debug Level", - "edt_dev_spec_debugStreamer_title": "Streamer Debug", "edt_dev_spec_delayAfterConnect_title": "Delay after connect", "edt_dev_spec_devices_discovered_none": "No Devices Discovered", "edt_dev_spec_devices_discovered_title": "Devices Discovered", @@ -568,6 +589,8 @@ "edt_dev_spec_networkDeviceName_title": "Network devicename", "edt_dev_spec_networkDevicePort_title": "Port", "edt_dev_spec_numberOfLeds_title": "Number of LEDs", + "edt_dev_spec_onBlackTimeToPowerOff": "Time to power off the lamp if the black level is triggered", + "edt_dev_spec_onBlackTimeToPowerOn": "Time to power on the lamp if the signal is restored", "edt_dev_spec_orbIds_title": "Orb ID(s)", "edt_dev_spec_order_left_right_title": "2.", "edt_dev_spec_order_top_down_title": "1.", @@ -575,8 +598,10 @@ "edt_dev_spec_panel_start_position": "Start panel [0-max panels]", "edt_dev_spec_panelorganisation_title": "Panel numbering sequence", "edt_dev_spec_pid_title": "PID", + "edt_dev_spec_port_expl": "Service Port [1-65535]", "edt_dev_spec_port_title": "Port", "edt_dev_spec_printTimeStamp_title": "Add timestamp", + "edt_dev_spec_stream_protocol_title": "Streaming protocol", "edt_dev_spec_pwmChannel_title": "PWM channel", "edt_dev_spec_razer_device_title": "Razer Chroma Device", "edt_dev_spec_restoreOriginalState_title": "Restore lights' state", @@ -585,10 +610,10 @@ "edt_dev_spec_spipath_title": "SPI Device", "edt_dev_spec_sslHSTimeoutMax_title": "Streamer handshake timeout maximum", "edt_dev_spec_sslHSTimeoutMin_title": "Streamer handshake timeout minimum", - "edt_dev_spec_sslReadTimeout_title": "Streamer read timeout", "edt_dev_spec_switchOffOnBlack_title": "Switch off on black", "edt_dev_spec_switchOffOnbelowMinBrightness_title": "Switch-off, below minimum", "edt_dev_spec_syncOverwrite_title": "Disable synchronisation", + "edt_dev_spec_targetIpHost_expl": "Hostname (DNS/mDNS) or IP-address (IPv4 orIPv6)", "edt_dev_spec_targetIpHost_title": "Hostname/IP-address", "edt_dev_spec_targetIpHost_title_info": "The device's hostname or IP-address", "edt_dev_spec_targetIp_title": "IP-address", diff --git a/assets/webconfig/js/content_index.js b/assets/webconfig/js/content_index.js index 816bbabf..a3691555 100644 --- a/assets/webconfig/js/content_index.js +++ b/assets/webconfig/js/content_index.js @@ -1,5 +1,5 @@ $(document).ready(function () { - var darkModeOverwrite = getStorage("darkModeOverwrite", true); + var darkModeOverwrite = getStorage("darkModeOverwrite"); if (darkModeOverwrite == "false" || darkModeOverwrite == null) { if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) { @@ -7,11 +7,11 @@ $(document).ready(function () { } if (window.matchMedia && window.matchMedia('(prefers-color-scheme: light)').matches) { - setStorage("darkMode", "off", false); + setStorage("darkMode", "off"); } } - if (getStorage("darkMode", false) == "on") { + if (getStorage("darkMode") == "on") { handleDarkMode(); } @@ -42,7 +42,6 @@ $(document).ready(function () { $('#btn_hypinstanceswitch').toggle(true) else $('#btn_hypinstanceswitch').toggle(false) - updateSessions(); }); // end cmd-serverinfo // Update language selection @@ -73,11 +72,6 @@ $(document).ready(function () { //End language selection - $(window.hyperion).on("cmd-sessions-update", function (event) { - window.serverInfo.sessions = event.response.data; - updateSessions(); - }); - $(window.hyperion).on("cmd-authorize-tokenRequest cmd-authorize-getPendingTokenRequests", function (event) { var val = event.response.info; if (Array.isArray(event.response.info)) { @@ -121,7 +115,7 @@ $(document).ready(function () { requestGetPendingTokenRequests(); //Switch to last selected instance and load related config - var lastSelectedInstance = getStorage('lastSelectedInstance', false); + var lastSelectedInstance = getStorage('lastSelectedInstance'); if (lastSelectedInstance == null || window.serverInfo.instance && !window.serverInfo.instance[lastSelectedInstance]) { lastSelectedInstance = 0; } @@ -157,7 +151,7 @@ $(document).ready(function () { $("#btn_lock_ui").removeAttr('style') if (event.response.hasOwnProperty('info')) - setStorage("loginToken", event.response.info.token, true); + setStorage("loginToken", event.response.info.token); requestServerConfigSchema(); }); @@ -171,7 +165,7 @@ $(document).ready(function () { }); $(window.hyperion).on("cmd-authorize-newPasswordRequired", function (event) { - var loginToken = getStorage("loginToken", true) + var loginToken = getStorage("loginToken") if (event.response.info.newPasswordRequired == true) { window.defaultPasswordIsSet = true; @@ -204,8 +198,8 @@ $(document).ready(function () { $(window.hyperion).on("error", function (event) { //If we are getting an error "No Authorization" back with a set loginToken we will forward to new Login (Token is expired. //e.g.: hyperiond was started new in the meantime) - if (event.reason == "No Authorization" && getStorage("loginToken", true)) { - removeStorage("loginToken", true); + if (event.reason == "No Authorization" && getStorage("loginToken")) { + removeStorage("loginToken"); requestRequiresAdminAuth(); } else if (event.reason == "Selected Hyperion instance isn't running") { @@ -281,8 +275,8 @@ $(document).ready(function () { if (!isInData) { //Delete Storage information about the last used but now stopped instance - if (getStorage('lastSelectedInstance', false)) - removeStorage('lastSelectedInstance', false) + if (getStorage('lastSelectedInstance')) + removeStorage('lastSelectedInstance') currentHyperionInstance = 0; currentHyperionInstanceName = getInstanceNameByIndex(0); @@ -341,7 +335,7 @@ function suppressDefaultPwWarning() { $(function () { var sidebar = $('#side-menu'); // cache sidebar to a variable for performance - sidebar.on("click", 'a.inactive' , function () { + sidebar.on("click", 'a.inactive', function () { sidebar.find('.active').toggleClass('active inactive'); $(this).toggleClass('active inactive'); }); @@ -354,13 +348,13 @@ $(document.body).on('hide.bs.modal,hidden.bs.modal', function () { //Dark Mode $("#btn_darkmode").off().on("click", function (e) { - if (getStorage("darkMode", false) != "on") { + if (getStorage("darkMode") != "on") { handleDarkMode(); - setStorage("darkModeOverwrite", true, true); + setStorage("darkModeOverwrite", true); } else { - setStorage("darkMode", "off", false); - setStorage("darkModeOverwrite", true, true); + setStorage("darkMode", "off",); + setStorage("darkModeOverwrite", true); location.reload(); } }); @@ -392,3 +386,4 @@ function SwitchToMenuItem(target, item) { } }; + diff --git a/assets/webconfig/js/content_leds.js b/assets/webconfig/js/content_leds.js index 9822bd22..aaca22fa 100755 --- a/assets/webconfig/js/content_leds.js +++ b/assets/webconfig/js/content_leds.js @@ -21,7 +21,7 @@ var toggleKeystoneCorrectionArea = false; var devRPiSPI = ['apa102', 'apa104', 'ws2801', 'lpd6803', 'lpd8806', 'p9813', 'sk6812spi', 'sk6822spi', 'sk9822', 'ws2812spi']; var devRPiPWM = ['ws281x']; var devRPiGPIO = ['piblaster']; -var devNET = ['atmoorb', 'cololight', 'fadecandy', 'philipshue', 'nanoleaf', 'razer', 'tinkerforge', 'tpm2net', 'udpe131', 'udpartnet', 'udph801', 'udpraw', 'wled', 'yeelight']; +var devNET = ['atmoorb', 'cololight', 'fadecandy', 'philipshue', 'nanoleaf', 'razer', 'tinkerforge', 'tpm2net', 'udpe131', 'udpartnet', 'udpddp', 'udph801', 'udpraw', 'wled', 'yeelight']; var devSerial = ['adalight', 'dmx', 'atmo', 'sedu', 'tpm2', 'karate']; var devHID = ['hyperionusbasp', 'lightpack', 'paintpack', 'rawhid']; @@ -1075,6 +1075,7 @@ $(document).ready(function () { conf_editor.on('ready', function () { var hwLedCountDefault = 1; var colorOrderDefault = "rgb"; + var filter = {}; $('#btn_test_controller').hide(); @@ -1083,13 +1084,7 @@ $(document).ready(function () { case "wled": case "nanoleaf": showAllDeviceInputOptions("hostList", false); - case "adalight": - case "atmo": - case "dmx": - case "karate": - case "sedu": - case "tpm2": - case "apa102": + case "apa102": case "apa104": case "ws2801": case "lpd6803": @@ -1101,7 +1096,18 @@ $(document).ready(function () { case "ws2812spi": case "piblaster": case "ws281x": - discover_device(ledType); + + //Serial devices + case "adalight": + case "atmo": + case "dmx": + case "karate": + case "sedu": + case "tpm2": + if (storedAccess === 'expert') { + filter.discoverAll = true; + } + discover_device(ledType, filter); hwLedCountDefault = 1; colorOrderDefault = "rgb"; break; @@ -1172,6 +1178,7 @@ $(document).ready(function () { case "tpm2net": case "udpe131": case "udpartnet": + case "udpddp": case "udph801": case "udpraw": var host = conf_editor.getEditor("root.specificOptions.host").getValue(); @@ -1255,33 +1262,43 @@ $(document).ready(function () { var hostList = conf_editor.getEditor("root.specificOptions.hostList"); if (hostList) { var val = hostList.getValue(); + var host = conf_editor.getEditor("root.specificOptions.host"); var showOptions = true; - + switch (val) { case 'CUSTOM': case '': - conf_editor.getEditor(specOptPath + "host").enable(); - conf_editor.getEditor(specOptPath + "host").setValue(""); + host.enable(); + //Populate existing host for current custom config + if (ledType === window.serverConfig.device.type) { + host.setValue(window.serverConfig.device.host); + } else { + host.setValue(""); + } break; case 'NONE': - conf_editor.getEditor(specOptPath + "host").enable(); + host.enable(); //Trigger getProperties via host value conf_editor.notifyWatchers(specOptPath + "host"); break; case 'SELECT': - conf_editor.getEditor(specOptPath + "host").setValue(""); - conf_editor.getEditor(specOptPath + "host").disable(); + host.setValue(""); + host.disable(); showOptions = false; break; default: - conf_editor.getEditor(specOptPath + "host").disable(); - conf_editor.getEditor(specOptPath + "host").setValue(val); + host.disable(); + host.setValue(val); //Trigger getProperties via host value conf_editor.notifyWatchers(specOptPath + "host"); break; } showAllDeviceInputOptions("hostList", showOptions); + + if (!host.isEnabled() && host.getValue().endsWith("._tcp.local")) { + showInputOptionForItem(conf_editor, 'specificOptions', 'host', false); + } } }); @@ -1749,44 +1766,34 @@ var updateSelectList = function (ledType, discoveryInfo) { var name; var host; - switch (ledType) { - case "nanoleaf": - if (discoveryMethod === "ssdp") { - name = device.other["nl-devicename"]; - } - else { - name = device.name; - } - break; - case "cololight": - if (discoveryMethod === "ssdp") { - name = device.hostname; - } - else { - name = device.name; - } - break; - case "wled": - name = device.name; - break; - default: - name = device.name; - } - if (discoveryMethod === "ssdp") { host = device.ip; } else { - host = device.name; + host = device.service; + } + + switch (ledType) { + case "nanoleaf": + if (discoveryMethod === "ssdp") { + name = device.other["nl-devicename"] + " (" + host + ")"; + } + else { + name = device.name; + } + break; + default: + if (discoveryMethod === "ssdp") { + name = device.hostname + " (" + host + ")"; + } + else { + name = device.name; + } + break; } enumVals.push(host); - if (host !== name) { - enumTitelVals.push(name + " (" + host + ")"); - } - else { - enumTitelVals.push(host); - } + enumTitelVals.push(name); } //Always allow to add custom configuration @@ -1794,8 +1801,14 @@ var updateSelectList = function (ledType, discoveryInfo) { // Select configured device var configuredDeviceType = window.serverConfig.device.type; var configuredHost = window.serverConfig.device.hostList; - if (ledType === configuredDeviceType && $.inArray(configuredHost, enumVals) != -1) { - enumDefaultVal = configuredHost; + if (ledType === configuredDeviceType) { + if ($.inArray(configuredHost, enumVals) != -1) { + enumDefaultVal = configuredHost; + } else if (configuredHost === "CUSTOM") { + enumDefaultVal = "CUSTOM"; + } else { + addSelect = true; + } } else { addSelect = true; @@ -1928,6 +1941,7 @@ async function discover_device(ledType, params) { } async function getProperties_device(ledType, key, params) { + var disabled = $('#btn_submit_controller').is(':disabled'); // Take care that connfig cannot be saved during background processing $('#btn_submit_controller').prop('disabled', true); @@ -1946,7 +1960,7 @@ async function getProperties_device(ledType, key, params) { devicesProperties[ledType][key] = ledDeviceProperties; if (!window.readOnlyMode) { - $('#btn_submit_controller').prop('disabled', false); + $('#btn_submit_controller').prop('disabled', disabled); } } else { @@ -1961,6 +1975,7 @@ async function getProperties_device(ledType, key, params) { } async function identify_device(type, params) { + var disabled = $('#btn_submit_controller').is(':disabled'); // Take care that connfig cannot be saved and identification cannot be retriggerred during background processing $('#btn_submit_controller').prop('disabled', true); $('#btn_test_controller').prop('disabled', true); @@ -1969,7 +1984,7 @@ async function identify_device(type, params) { $('#btn_test_controller').prop('disabled', false); if (!window.readOnlyMode) { - $('#btn_submit_controller').prop('disabled', false); + $('#btn_submit_controller').prop('disabled', disabled); } } @@ -1988,12 +2003,18 @@ function updateElements(ledType, key) { case "wled": var ledProperties = devicesProperties[ledType][key]; - if (ledProperties && ledProperties.leds && ledProperties.maxLedCount) { + if (ledProperties && ledProperties.leds) { hardwareLedCount = ledProperties.leds.count; - var maxLedCount = ledProperties.maxLedCount; - if (hardwareLedCount > maxLedCount) { - showInfoDialog('warning', $.i18n("conf_leds_config_warning"), $.i18n('conf_leds_error_hwled_gt_maxled', hardwareLedCount, maxLedCount, maxLedCount)); - hardwareLedCount = maxLedCount; + if (ledProperties.maxLedCount) { + var maxLedCount = ledProperties.maxLedCount; + if (hardwareLedCount > maxLedCount) { + showInfoDialog('warning', $.i18n("conf_leds_config_warning"), $.i18n('conf_leds_error_hwled_gt_maxled', hardwareLedCount, maxLedCount, maxLedCount)); + hardwareLedCount = maxLedCount; + conf_editor.getEditor("root.specificOptions.streamProtocol").setValue("RAW"); + //Workaround, as value seems to getting updated property when a 'getEditor("root.specificOptions").getValue()' is done during save + var editor = conf_editor.getEditor("root.specificOptions"); + editor.value["streamProtocol"] = "RAW"; + } } } conf_editor.getEditor("root.generalOptions.hardwareLedCount").setValue(hardwareLedCount); @@ -2060,6 +2081,15 @@ function updateElements(ledType, key) { default: } } + + if (!conf_editor.validate().length) { + if (!window.readOnlyMode) { + $('#btn_submit_controller').attr('disabled', false); + } + } + else { + $('#btn_submit_controller').attr('disabled', true); + } } function showAllDeviceInputOptions(showForKey, state) { @@ -2068,6 +2098,6 @@ function showAllDeviceInputOptions(showForKey, state) { } function disableAutoResolvedGeneralOptions() { - conf_editor.getEditor("root.generalOptions.hardwareLedCount").disable(); - conf_editor.getEditor("root.generalOptions.colorOrder").disable(); + conf_editor.getEditor("root.generalOptions.hardwareLedCount").disable(); + conf_editor.getEditor("root.generalOptions.colorOrder").disable(); } diff --git a/assets/webconfig/js/content_logging.js b/assets/webconfig/js/content_logging.js index 4cbdf8db..ac77e959 100644 --- a/assets/webconfig/js/content_logging.js +++ b/assets/webconfig/js/content_logging.js @@ -137,7 +137,7 @@ $(document).ready(function () { var date = new Date(parseInt(utime)); var subComponent = ""; - if (window.serverInfo.instance.length > 1) { + if (window.serverInfo.instance.length >= 1) { if (logger_subname.startsWith("I")) { var instanceNum = logger_subname.substring(1); if (window.serverInfo.instance[instanceNum]) { diff --git a/assets/webconfig/js/content_network.js b/assets/webconfig/js/content_network.js index 96c13d4c..1e497975 100644 --- a/assets/webconfig/js/content_network.js +++ b/assets/webconfig/js/content_network.js @@ -11,6 +11,9 @@ $(document).ready(function () { var conf_editor_fbs = null; var conf_editor_forw = null; + // Service properties , 2-dimensional array of [servicetype][id] + discoveredRemoteServices = {}; + addJsonEditorHostValidation(); if (window.showOptHelp) { @@ -142,10 +145,21 @@ $(document).ready(function () { forwarder: window.schema.forwarder }, true, true); + conf_editor_forw.on('ready', function () { + + updateServiceCacheForwarderConfiguredItems("jsonapi"); + updateServiceCacheForwarderConfiguredItems("flatbuffer"); + + var forwarderEnable = conf_editor_forw.getEditor("root.forwarder.enable").getValue(); + if (forwarderEnable) { + discoverRemoteHyperionServices("jsonapi"); + discoverRemoteHyperionServices("flatbuffer"); + } + }); + conf_editor_forw.on('change', function () { var forwarderEnable = conf_editor_forw.getEditor("root.forwarder.enable").getValue(); if (forwarderEnable) { - showInputOptionsForKey(conf_editor_forw, "forwarder", "enable", true); $('#forwarderHelpPanelId').show(); } else { showInputOptionsForKey(conf_editor_forw, "forwarder", "enable", false); @@ -154,6 +168,23 @@ $(document).ready(function () { conf_editor_forw.validate().length || window.readOnlyMode ? $('#btn_submit_forwarder').prop('disabled', true) : $('#btn_submit_forwarder').prop('disabled', false); }); + conf_editor_forw.watch('root.forwarder.jsonapiselect', () => { + updateForwarderServiceSections("jsonapi"); + }); + + conf_editor_forw.watch('root.forwarder.flatbufferselect', () => { + updateForwarderServiceSections("flatbuffer"); + }); + + conf_editor_forw.watch('root.forwarder.enable', () => { + + var forwarderEnable = conf_editor_forw.getEditor("root.forwarder.enable").getValue(); + if (forwarderEnable) { + discoverRemoteHyperionServices("jsonapi"); + discoverRemoteHyperionServices("flatbuffer"); + } + }); + $('#btn_submit_forwarder').off().on('click', function () { requestWriteConfig(conf_editor_forw.getValue()); }); @@ -238,5 +269,143 @@ $(document).ready(function () { checkApiTokenState(window.serverConfig.network.apiAuth); removeOverlay(); + + function updateForwarderServiceSections(type) { + + var editorPath = "root.forwarder." + type + var selectedServices = conf_editor_forw.getEditor(editorPath + "select").getValue(); + + if (jQuery.isEmptyObject(selectedServices) || selectedServices[0] === "NONE") { + conf_editor_forw.getEditor(editorPath).setValue([]); + showInputOptionForItem(conf_editor_forw, "forwarder", type, false); + } else { + + var newServices = []; + for (var i = 0; i < selectedServices.length; ++i) { + + var service = discoveredRemoteServices[type][selectedServices[i]]; + var newrecord = {}; + + newrecord.name = service.name; + newrecord.host = service.host; + newrecord.port = service.port; + + newServices.push(newrecord); + } + conf_editor_forw.getEditor(editorPath).setValue(newServices); + + showInputOptionForItem(conf_editor_forw, "forwarder", type, true); + conf_editor_forw.getEditor(editorPath).disable(); + } + } + + function updateForwarderSelectList(type) { + + var selectionElement = type + "select" + + var enumVals = []; + var enumTitelVals = []; + var enumDefaultVals = []; + + for (var key in discoveredRemoteServices[type]) { + + var service = discoveredRemoteServices[type][key]; + enumVals.push(service.host); + enumTitelVals.push(service.name); + + if (service.inConfig == true) { + enumDefaultVals.push(service.host); + } + } + + let addSchemaElements = { + "uniqueItems": true + }; + + if (jQuery.isEmptyObject(enumVals)) { + enumVals.push("NONE"); + enumTitelVals.push($.i18n('edt_conf_fw_remote_service_discovered_none')); + } + + updateJsonEditorMultiSelection(conf_editor_forw, 'root.forwarder', selectionElement, addSchemaElements, enumVals, enumTitelVals, enumDefaultVals); + }; + + function updateServiceCacheForwarderConfiguredItems(serviceType) { + + var editor = conf_editor_forw.getEditor("root.forwarder." + serviceType); + + if (editor) { + if (!discoveredRemoteServices[serviceType]) { + discoveredRemoteServices[serviceType] = {}; + } + + var configuredServices = JSON.parse(JSON.stringify(editor.getValue('items'))); + for (const service of configuredServices) { + + //Handle not named sceanrios + if (!service.name) { + service.name = service.host; + } + + service.inConfig = true; + + discoveredRemoteServices[serviceType][service.host] = service; + } + } + } + + function updateRemoteServiceCache(discoveryInfo) { + + for (var serviceType in discoveryInfo) { + + if (!discoveredRemoteServices[serviceType]) { + discoveredRemoteServices[serviceType] = {}; + } + + var discoveredServices = discoveryInfo[serviceType]; + for (const service of discoveredServices) { + + if (!service.sameHost) + { + //Handle non mDNS sceanrios + if (!service.name) { + service.name = service.host; + } else { + service.host = service.service; + } + + if (discoveredRemoteServices[serviceType][service.host]) { + service.inConfig = true; + } + + discoveredRemoteServices[serviceType][service.host] = service; + } + } + } + }; + + async function discoverRemoteHyperionServices(type, params) { + + const result = await requestServiceDiscovery(type, params); + + var discoveryResult; + if (result && !result.error) { + discoveryResult = result.info; + } + else { + discoveryResult = { + "services": [] + }; + } + + switch (type) { + case "jsonapi": + case "flatbuffer": + updateRemoteServiceCache(discoveryResult.services); + updateForwarderSelectList(type); + break; + } + }; + }); diff --git a/assets/webconfig/js/hyperion.js b/assets/webconfig/js/hyperion.js index 1515dc03..5193da05 100644 --- a/assets/webconfig/js/hyperion.js +++ b/assets/webconfig/js/hyperion.js @@ -26,7 +26,6 @@ window.loggingStreamActive = false; window.loggingHandlerInstalled = false; window.watchdog = 0; window.debugMessagesActive = true; -window.wSess = []; window.currentHyperionInstance = 0; window.currentHyperionInstanceName = "?"; window.comps = []; @@ -136,7 +135,11 @@ function initWebSocket() // skip tan -1 error handling if(tan != -1){ var error = response.hasOwnProperty("error")? response.error : "unknown"; - $(window.hyperion).trigger({type:"error",reason:error}); + if (error == "Service Unavailable") { + window.location.reload(); + } else { + $(window.hyperion).trigger({type:"error",reason:error}); + } console.log("[window.websocket::onmessage] ",error) } } @@ -307,7 +310,7 @@ function requestInstanceSwitch(inst) function requestServerInfo() { - sendToHyperion("serverinfo","",'"subscribe":["components-update","sessions-update","priorities-update", "imageToLedMapping-update", "adjustment-update", "videomode-update", "effects-update", "settings-update", "instance-update"]'); + sendToHyperion("serverinfo","",'"subscribe":["components-update", "priorities-update", "imageToLedMapping-update", "adjustment-update", "videomode-update", "effects-update", "settings-update", "instance-update"]'); } function requestSysInfo() @@ -485,9 +488,21 @@ function requestLedDeviceIdentification(type, params) return sendAsyncToHyperion("leddevice", "identify", data, Math.floor(Math.random() * 1000)); } +async function requestLedDeviceAddAuthorization(type, params) { + let data = { ledDeviceType: type, params: params }; + + return sendAsyncToHyperion("leddevice", "addAuthorization", data, Math.floor(Math.random() * 1000)); +} + async function requestInputSourcesDiscovery(type, params) { let data = { sourceType: type, params: params }; return sendAsyncToHyperion("inputsource", "discover", data, Math.floor(Math.random() * 1000)); } +async function requestServiceDiscovery(type, params) { + let data = { serviceType: type, params: params }; + + return sendAsyncToHyperion("service", "discover", data, Math.floor(Math.random() * 1000)); +} + diff --git a/assets/webconfig/js/settings.js b/assets/webconfig/js/settings.js index 08225959..c07e2beb 100644 --- a/assets/webconfig/js/settings.js +++ b/assets/webconfig/js/settings.js @@ -2,14 +2,14 @@ var availAccess = ['default', 'advanced', 'expert']; var storedAccess; //Change Password -function changePassword(){ +function changePassword() { showInfoDialog('changePassword', $.i18n('InfoDialog_changePassword_title')); // fill default pw if default is set - if(window.defaultPasswordIsSet) + if (window.defaultPasswordIsSet) $('#current-password').val('hyperion') - $('#id_btn_ok').off().on('click',function() { + $('#id_btn_ok').off().on('click', function () { var oldPw = $('#current-password').val(); var newPw = $('#new-password').val(); @@ -17,7 +17,7 @@ function changePassword(){ history.pushState({}, "New password"); }); - $('#new-password, #current-password').off().on('input',function(e) { + $('#new-password, #current-password').off().on('input', function (e) { ($('#current-password').val().length >= 8 && $('#new-password').val().length >= 8) && !window.readOnlyMode ? $('#id_btn_ok').prop('disabled', false) : $('#id_btn_ok').prop('disabled', true); }); } @@ -44,18 +44,17 @@ $(document).ready(function () { $('#btn_setlang').prop("disabled", true); } - $('#btn_setaccess').off().on('click',function() { + $('#btn_setaccess').off().on('click', function () { var newAccess; showInfoDialog('select', $.i18n('InfoDialog_access_title'), $.i18n('InfoDialog_access_text')); - for (var lcx = 0; lcx -1 && hyperionAddress.length == 36) hyperionAddress = '['+hyperionAddress+']'; - hyperionAddress = 'http://'+hyperionAddress+':'+window.wSess[i].port; - $('#id_select').append(createSelOpt(hyperionAddress, window.wSess[i].name)); - } - } - - $('#id_btn_saveset').off().on('click',function() { - $("#loading_overlay").addClass("overlay"); - window.location.href = $('#id_select').val(); - }); - - }); }); + diff --git a/assets/webconfig/js/ui_utils.js b/assets/webconfig/js/ui_utils.js index 945bb140..3eb6e25a 100644 --- a/assets/webconfig/js/ui_utils.js +++ b/assets/webconfig/js/ui_utils.js @@ -14,31 +14,22 @@ function storageComp() { return false; } -function getStorage(item, session) { +function getStorage(item) { if (storageComp()) { - if (session === true) - return sessionStorage.getItem(item); - else - return localStorage.getItem(item); + return localStorage.getItem(item); } return null; } -function setStorage(item, value, session) { +function setStorage(item, value) { if (storageComp()) { - if (session === true) - sessionStorage.setItem(item, value); - else - localStorage.setItem(item, value); + localStorage.setItem(item, value); } } -function removeStorage(item, session) { +function removeStorage(item) { if (storageComp()) { - if (session === true) - sessionStorage.removeItem(item); - else - localStorage.removeItem(item); + localStorage.removeItem(item); } } @@ -48,23 +39,6 @@ function debugMessage(msg) { } } -function updateSessions() { - var sess = window.serverInfo.sessions; - if (sess && sess.length) { - window.wSess = []; - for (var i = 0; i < sess.length; i++) { - if (sess[i].type == "_http._tcp." || sess[i].type == "_https._tcp." || sess[i].type == "_hyperiond-http._tcp.") { - window.wSess.push(sess[i]); - } - } - - if (window.wSess.length > 1) - $('#btn_instanceswitch').toggle(true); - else - $('#btn_instanceswitch').toggle(false); - } -} - function validateDuration(d) { if (typeof d === "undefined" || d < 0) return ENDLESS; @@ -73,8 +47,8 @@ function validateDuration(d) { } function getHashtag() { - if (getStorage('lasthashtag', true) != null) - return getStorage('lasthashtag', true); + if (getStorage('lasthashtag') != null) + return getStorage('lasthashtag'); else { var tag = document.URL; tag = tag.substr(tag.indexOf("#") + 1); @@ -87,20 +61,20 @@ function getHashtag() { function loadContent(event, forceRefresh) { var tag; - var lastSelectedInstance = getStorage('lastSelectedInstance', false); + var lastSelectedInstance = getStorage('lastSelectedInstance'); if (lastSelectedInstance && (lastSelectedInstance != window.currentHyperionInstance)) { if (window.serverInfo.instance[lastSelectedInstance] && window.serverInfo.instance[lastSelectedInstance].running) { instanceSwitch(lastSelectedInstance); } else { - removeStorage('lastSelectedInstance', false); + removeStorage('lastSelectedInstance'); } } if (typeof event != "undefined") { tag = event.currentTarget.hash; tag = tag.substr(tag.indexOf("#") + 1); - setStorage('lasthashtag', tag, true); + setStorage('lasthashtag', tag); } else tag = getHashtag(); @@ -112,7 +86,7 @@ function loadContent(event, forceRefresh) { if (status == "error") { tag = 'dashboard'; console.log("Could not find page:", prevTag, ", Redirecting to:", tag); - setStorage('lasthashtag', tag, true); + setStorage('lasthashtag', tag); $("#page-content").load("/content/" + tag + ".html", function (response, status, xhr) { if (status == "error") { @@ -215,7 +189,7 @@ function instanceSwitch(inst) { requestInstanceSwitch(inst) window.currentHyperionInstance = inst; window.currentHyperionInstanceName = getInstanceNameByIndex(inst); - setStorage('lastSelectedInstance', inst, false) + setStorage('lastSelectedInstance', inst) updateHyperionInstanceListing() } @@ -332,7 +306,7 @@ function showInfoDialog(type, header, message) { if (type == "select" || type == "iswitch") $('#id_body').append(''); - if (getStorage("darkMode", false) == "on") + if (getStorage("darkMode") == "on") $('#id_logo').attr("src", 'img/hyperion/logo_negativ.png'); $(type == "renInst" || type == "changePassword" ? "#modal_dialog_rename" : "#modal_dialog").modal({ @@ -1246,7 +1220,7 @@ function handleDarkMode() { href: "../css/darkMode.css" }).appendTo("head"); - setStorage("darkMode", "on", false); + setStorage("darkMode", "on"); $('#btn_darkmode_icon').removeClass('fa fa-moon-o'); $('#btn_darkmode_icon').addClass('mdi mdi-white-balance-sunny'); $('#navbar_brand_logo').attr("src", 'img/hyperion/logo_negativ.png'); @@ -1337,7 +1311,16 @@ function isValidIPv6(value) { function isValidHostname(value) { if (value.match( - '(?=^.{4,253}$)(^((?!-)[a-zA-Z0-9-]{0,62}[a-zA-Z0-9].)+[a-zA-Z]{2,63}$)' + '^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])(\.([a-zA-Z0-9]|[_a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9]))*$' + )) + return true; + else + return false; +} + +function isValidServicename(value) { + if (value.match( + '^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9 \-]{0,61}[a-zA-Z0-9])(\.([a-zA-Z0-9]|[_a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9]))*$' )) return true; else @@ -1349,6 +1332,5 @@ function isValidHostnameOrIP4(value) { } function isValidHostnameOrIP(value) { - return (isValidHostnameOrIP4(value) || isValidIPv6(value)); + return (isValidHostnameOrIP4(value) || isValidIPv6(value) || isValidServicename(value)); } - diff --git a/assets/webconfig/js/wizard.js b/assets/webconfig/js/wizard.js index 79939279..a2a028d8 100755 --- a/assets/webconfig/js/wizard.js +++ b/assets/webconfig/js/wizard.js @@ -57,7 +57,7 @@ function startWizardRGB() { $('#wizp2_body').append('
'); $('#wizp2_footer').html(''); - if (getStorage("darkMode", false) == "on") + if (getStorage("darkMode") == "on") $('#wizard_logo').attr("src", 'img/hyperion/logo_negativ.png'); //open modal @@ -454,7 +454,7 @@ function startWizardCC() { '' ); - if (getStorage("darkMode", false) == "on") + if (getStorage("darkMode") == "on") $('#wizard_logo').prop("src", 'img/hyperion/logo_negativ.png'); //open modal @@ -591,6 +591,19 @@ 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(id, pos, name) { var i = null; @@ -622,6 +635,26 @@ function assignLightPos(id, pos, name) { 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; @@ -653,21 +686,22 @@ function getIdInLights(id) { ); } +// External properties properties, 2-dimensional arry of [ledType][key] +devicesProperties = {}; + //**************************** // Wizard Philips Hue //**************************** var hueIPs = []; var hueIPsinc = 0; -var lightIDs = null; -var groupIDs = null; +var hueLights = null; +var hueGroups = null; var lightLocation = []; var groupLights = []; var groupLightsLocations = []; var hueType = "philipshue"; -let hueUrl = new URL('http://dummy'); - function startWizardPhilipsHue(e) { if (typeof e.data.type != "undefined") hueType = e.data.type; @@ -739,7 +773,7 @@ function startWizardPhilipsHue(e) { $('#wizp2_footer').html(''); $('#wizp3_body').html('' + $.i18n('wiz_hue_press_link') + '


'); - if (getStorage("darkMode", false) == "on") + if (getStorage("darkMode") == "on") $('#wizard_logo').attr("src", 'img/hyperion/logo_negativ.png'); //open modal @@ -762,43 +796,10 @@ function checkHueBridge(cb, hueUser) { if (usr == 'config') $('#wiz_hue_discovered').html(""); if (hueIPs[hueIPsinc]) { - - hueUrl.hostname = "dummy"; var host = hueIPs[hueIPsinc].host; - - if (isValidIPv6(host)) { - hueUrl.hostname = "[" + host + "]"; - } else { - hueUrl.hostname = host; - } - var port = hueIPs[hueIPsinc].port; - if (port > 0) { - hueUrl.port = port; - } - hueUrl.pathname = '/api/' + usr; - $.ajax({ - url: hueUrl, - type: "GET", - dataType: "json", - timeout: 2500 - }) - .done(function (json) { - if (json.config) { - cb(true, usr); - } else { - if (json.name && json.bridgeid && json.modelid) { - $('#wiz_hue_discovered').html("Bridge: " + json.name + ", Modelid: " + json.modelid + ", API-Version: " + json.apiversion); - cb(true); - } else { - cb(false); - } - } - }) - .fail(function () { - cb(false); - }); + getProperties_hue_bridge(cb, decodeURIComponent(host), port, usr); } } @@ -809,7 +810,6 @@ function checkBridgeResult(reply, usr) { $('#host').val(hueIPs[hueIPsinc].host) $('#port').val(hueIPs[hueIPsinc].port) - //now check hue user on this bridge $('#usrcont').toggle(true); checkHueBridge(checkUserResult, $('#user').val() ? $('#user').val() : "newdeveloper"); } @@ -852,28 +852,10 @@ function checkUserResult(reply, usr) { } }; -function identHueId(id, off, oState) { - if (off !== true) { - setTimeout(identHueId, 1500, id, true, oState); - var put_data = '{"on":true,"bri":254,"hue":47000,"sat":254}'; - } - else { - var put_data = '{"on":' + oState.on + ',"bri":' + oState.bri + ',"hue":' + oState.hue + ',"sat":' + oState.sat + '}'; - } - - hueUrl.pathname = '/api/' + $('#user').val() + '/lights/' + id + '/state', - $.ajax({ - url: hueUrl, - type: 'PUT', - timeout: 2000, - data: put_data - }) -} - function useGroupId(id) { $('#groupId').val(id); - groupLights = groupIDs[id].lights; - groupLightsLocations = groupIDs[id].locations; + groupLights = hueGroups[id].lights; + groupLightsLocations = hueGroups[id].locations; get_hue_lights(); } @@ -881,9 +863,6 @@ 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'); - - // TODO: error case unhandled - // res can be: false (timeout) or res.error (not found) if (res && !res.error) { const r = res.info; @@ -896,18 +875,33 @@ async function discover_hue_bridges() { hueIPs = []; hueIPsinc = 0; + var discoveryMethod = "ssdp"; + if (res.info.discoveryMethod) { + discoveryMethod = res.info.discoveryMethod; + } + for (const device of r.devices) { - if (device && device.ip && device.port) { + if (device) { var host; var port; - if (device.hostname && device.domain) { - host = device.hostname + "." + device.domain; - port = device.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.ip; + host = device.service; port = device.port; } + //Remap https port to http port until Hue-API v2 is supported + if (port == 443) { + port = 80; + } + if (host) { if (!hueIPs.some(item => item.host === host)) { @@ -930,65 +924,71 @@ async function discover_hue_bridges() { } } -async function getProperties_hue_bridge(hostAddress, port, username, resourceFilter) { +async function getProperties_hue_bridge(cb, hostAddress, port, username, resourceFilter) { let params = { host: hostAddress, user: username, filter: resourceFilter }; if (port !== 'undefined') { - params.port = port; + params.port = parseInt(port); } - const res = await requestLedDeviceProperties('philipshue', params); + var ledType = 'philipshue'; + var key = hostAddress; - // TODO: error case unhandled - // res can be: false (timeout) or res.error (not found) - if (res && !res.error) { - const r = res.info + //Create ledType cache entry + if (!devicesProperties[ledType]) { + devicesProperties[ledType] = {}; + } - // Process properties returned - console.log(r); + // Use device's properties, if properties in chache + if (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)) { + + if (username === "config") { + if (ledDeviceProperties.name && ledDeviceProperties.bridgeid && ledDeviceProperties.modelid) { + $('#wiz_hue_discovered').html("Bridge: " + ledDeviceProperties.name + ", Modelid: " + ledDeviceProperties.modelid + ", API-Version: " + ledDeviceProperties.apiversion); + cb(true); + } + } else { + devicesProperties[ledType][key] = ledDeviceProperties; + cb(true, username); + } + } else { + cb(false, username); + } + } else { + cb(false, username); + } } } async function identify_hue_device(hostAddress, port, username, id) { - + 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: hostAddress, user: username, lightId: id }; + let params = { host: decodeURIComponent(hostAddress), user: username, lightId: id }; + if (port !== 'undefined') { - params.port = port; + params.port = parseInt(port); } + await requestLedDeviceIdentification('philipshue', params); if (!window.readOnlyMode) { - $('#btn_wiz_save').prop('disabled', false); + $('#btn_wiz_save').prop('disabled', disabled); } } -function getHueIPs() { - $('#wiz_hue_ipstate').html($.i18n('wiz_hue_searchb')); - - $.ajax({ - url: 'https://discovery.meethue.com', - crossDomain: true, - type: 'GET', - timeout: 3000 - }) - .done(function (data, textStatus, jqXHR) { - if (data.length == 0) { - $('#wiz_hue_ipstate').html($.i18n('wiz_hue_failure_ip')); - } else { - hueIPs = data; - checkHueBridge(checkBridgeResult); - } - }) - .fail(function (jqXHR, textStatus) { - $('#wiz_hue_ipstate').html($.i18n('wiz_hue_failure_ip')); - }); -}; - //return editor Value -function eV(vn) { - return (vn) ? conf_editor.getEditor("root.specificOptions." + vn).getValue() : ""; +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() { @@ -1009,7 +1009,6 @@ function beginWizardHue() { hueIPs = []; hueIPsinc = 0; - //getHueIPs(); discover_hue_bridges(); } else { @@ -1068,13 +1067,13 @@ function beginWizardHue() { var finalLightIds = []; //create hue led config - for (var key in lightIDs) { + for (var key in hueLights) { if (hueType == 'philipshueentertainment') { if (groupLights.indexOf(key) == -1) continue; } if ($('#hue_' + key).val() != "disabled") { finalLightIds.push(key); - var idx_content = assignLightPos(key, $('#hue_' + key).val(), lightIDs[key].name); + var idx_content = assignLightPos(key, $('#hue_' + key).val(), hueLights[key].name); hueLedConfig.push(JSON.parse(JSON.stringify(idx_content))); } } @@ -1100,23 +1099,28 @@ function beginWizardHue() { d.type = 'philipshue'; d.colorOrder = 'rgb'; d.lightIds = finalLightIds; - d.transitiontime = parseInt(eV("transitiontime")); - d.restoreOriginalState = (eV("restoreOriginalState") == true); - d.switchOffOnBlack = (eV("switchOffOnBlack") == true); - d.brightnessFactor = parseFloat(eV("brightnessFactor")); + 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 = parseInt($('#groupId').val()); - d.blackLightsTimeout = parseInt(eV("blackLightsTimeout")); - d.brightnessMin = parseFloat(eV("brightnessMin")); - d.brightnessMax = parseFloat(eV("brightnessMax")); - d.brightnessThreshold = parseFloat(eV("brightnessThreshold")); - d.sslReadTimeout = parseInt(eV("sslReadTimeout")); - d.sslHSTimeoutMin = parseInt(eV("sslHSTimeoutMin")); - d.sslHSTimeoutMax = parseInt(eV("sslHSTimeoutMax")); + 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.debugStreamer = (eV("debugStreamer") == true); - d.debugLevel = (eV("debugLevel")); + + 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()); if (hueType == 'philipshue') { d.useEntertainmentAPI = false; @@ -1129,7 +1133,6 @@ function beginWizardHue() { if (hueType == 'philipshueentertainment') { d.useEntertainmentAPI = true; d.hardwareLedCount = groupLights.length; - d.rewriteTime = 20; //smoothing on sc.smoothing.enable = true; } @@ -1144,83 +1147,92 @@ function beginWizardHue() { } function createHueUser() { - var connectionRetries = 30; - var data = { "devicetype": "hyperion#" + Date.now() } - if (hueType == 'philipshueentertainment') { - data = { "devicetype": "hyperion#" + Date.now(), "generateclientkey": true } + + 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 () { - hueUrl.pathname = '/api/'; - $.ajax({ - type: "POST", - url: hueUrl, - processData: false, - timeout: 1000, - contentType: 'application/json', - data: JSON.stringify(data) - }) - .done(function (r) { - $('#wizp1').toggle(false); - $('#wizp2').toggle(false); - $('#wizp3').toggle(true); + $('#wizp1').toggle(false); + $('#wizp2').toggle(false); + $('#wizp3').toggle(true); - connectionRetries--; - $("#connectionTime").html(connectionRetries); - if (connectionRetries == 0) { - abortConnection(UserInterval); - } - else { - if (typeof r[0].error != 'undefined') { - console.log(connectionRetries + ": link not pressed"); - } - if (typeof r[0].success != 'undefined') { + (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); - if (r[0].success.username != 'undefined') { - $('#user').val(r[0].success.username); - conf_editor.getEditor("root.specificOptions.username").setValue(r[0].success.username); + + 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 (hueType == 'philipshueentertainment') { - if (r[0].success.clientkey != 'undefined') { - $('#clientkey').val(r[0].success.clientkey); - conf_editor.getEditor("root.specificOptions.clientkey").setValue(r[0].success.clientkey); + var clientkey = response.clientkey; + if (clientkey != 'undefined') { + $('#clientkey').val(clientkey); + conf_editor.getEditor("root.specificOptions.clientkey").setValue(clientkey); } } - checkHueBridge(checkUserResult, r[0].success.username); + checkHueBridge(checkUserResult, username); clearInterval(UserInterval); } + } else { + $('#wizp1').toggle(false); + $('#wizp2').toggle(true); + $('#wizp3').toggle(false); + clearInterval(UserInterval); } - }) - .fail(function (XMLHttpRequest, textStatus, errorThrown) { - $('#wizp1').toggle(false); - $('#wizp2').toggle(true); - $('#wizp3').toggle(false); - clearInterval(UserInterval); - }) - }, 1000); + } + })(); + + }, retryInterval * 1000); } function get_hue_groups() { - hueUrl.pathname = '/api/' + $("#user").val() + '/groups'; - $.ajax({ - type: "GET", - url: hueUrl, - processData: false, - contentType: 'application/json' - }) - .done(function (r) { - if (Object.keys(r).length > 0) { + + var host = hueIPs[hueIPsinc].host; + + if (devicesProperties['philipshue'][host]) { + var ledProperties = devicesProperties['philipshue'][host]; + + if (!jQuery.isEmptyObject(ledProperties)) { + hueGroups = ledProperties.groups; + if (Object.keys(hueGroups).length > 0) { + + $('.lidsb').html(""); $('#wh_topcontainer').toggle(false); $('#hue_grp_ids_t').toggle(true); - groupIDs = r; - var gC = 0; - for (var groupid in r) { - if (r[groupid].type == 'Entertainment') { - $('.gidsb').append(createTableRow([groupid + ' (' + r[groupid].name + ')', ''])); + for (var groupid in hueGroups) { + if (hueGroups[groupid].type == 'Entertainment') { + $('.gidsb').append(createTableRow([groupid + ' (' + hueGroups[groupid].name + ')', ''])); gC++; } } @@ -1228,10 +1240,8 @@ function get_hue_groups() { noAPISupport('wiz_hue_e_noegrpids'); } } - else { - noAPISupport('wiz_hue_e_nogrpids'); - } - }) + } + } } function noAPISupport(txt) { @@ -1247,42 +1257,30 @@ function noAPISupport(txt) { get_hue_lights(); } -function get_light_state(id) { - hueUrl.pathname = '/api/' + $("#user").val() + '/lights/' + id; - $.ajax({ - type: "GET", - url: hueUrl, - processData: false, - contentType: 'application/json' - }) - .done(function (r) { - if (Object.keys(r).length > 0) { - identHueId(id, false, r['state']); - } - }) -} - function get_hue_lights() { - hueUrl.pathname = '/api/' + $("#user").val() + '/lights'; - $.ajax({ - type: "GET", - url: hueUrl, - processData: false, - contentType: 'application/json' - }) - .done(function (r) { - if (Object.keys(r).length > 0) { + + var host = hueIPs[hueIPsinc].host; + + if (devicesProperties['philipshue'][host]) { + var ledProperties = devicesProperties['philipshue'][host]; + + if (!jQuery.isEmptyObject(ledProperties.lights)) { + hueLights = ledProperties.lights; + if (Object.keys(hueLights).length > 0) { if (hueType == 'philipshue') { $('#wh_topcontainer').toggle(false); } $('#hue_ids_t, #btn_wiz_save').toggle(true); - lightIDs = r; + var lightOptions = [ "top", "topleft", "topright", "bottom", "bottomleft", "bottomright", "left", "lefttop", "leftmiddle", "leftbottom", "right", "righttop", "rightmiddle", "rightbottom", - "entire" + "entire", + "lightPosTopLeft112", "lightPosTopLeftNewMid", "lightPosTopLeft121", + "lightPosBottomLeft14", "lightPosBottomLeft12", "lightPosBottomLeft34", "lightPosBottomLeft11", + "lightPosBottomLeft112", "lightPosBottomLeftNewMid", "lightPosBottomLeft121" ]; if (hueType == 'philipshue') { @@ -1291,7 +1289,7 @@ function get_hue_lights() { $('.lidsb').html(""); var pos = ""; - for (var lightid in r) { + for (var lightid in hueLights) { if (hueType == 'philipshueentertainment') { if (groupLights.indexOf(lightid) == -1) continue; @@ -1323,15 +1321,15 @@ function get_hue_lights() { if (pos == val) options += ' selected="selected"'; options += '>' + $.i18n(txt + val) + ''; } - $('.lidsb').append(createTableRow([lightid + ' (' + r[lightid].name + ')', '' + options - + '', ''])); + + '', ''])); } if (hueType != 'philipshueentertainment') { $('.hue_sel_watch').on("change", function () { var cC = 0; - for (var key in lightIDs) { + for (var key in hueLights) { if ($('#hue_' + key).val() != "disabled") { cC++; } @@ -1346,7 +1344,8 @@ function get_hue_lights() { var txt = '

' + $.i18n('wiz_hue_noids') + '

'; $('#wizp2_body').append(txt); } - }) + } + } } function abortConnection(UserInterval) { @@ -1386,7 +1385,7 @@ function startWizardYeelight(e) { + $.i18n('general_btn_save') + '' + $.i18n('general_btn_cancel') + ''); - if (getStorage("darkMode", false) == "on") + if (getStorage("darkMode") == "on") $('#wizard_logo').attr("src", 'img/hyperion/logo_negativ.png'); //open modal @@ -1413,19 +1412,15 @@ function beginWizardYeelight() { //create yeelight led config for (var key in lights) { if ($('#yee_' + key).val() !== "disabled") { - //delete lights[key].model; + var name = lights[key].name; // Set Name to layout-position, if empty - if (lights[key].name === "") { - lights[key].name = $.i18n('conf_leds_layout_cl_' + $('#yee_' + key).val()); + if (name === "") { + name = lights[key].host; } finalLights.push(lights[key]); - var name = lights[key].host; - if (lights[key].name !== "") - name += '_' + lights[key].name; - var idx_content = assignLightPos(key, $('#yee_' + key).val(), name); yeelightLedConfig.push(JSON.parse(JSON.stringify(idx_content))); } @@ -1460,7 +1455,8 @@ function beginWizardYeelight() { window.serverConfig.device = d; //smoothing off - window.serverConfig.smoothing.enable = false; + if (!(window.serverConfig.smoothing == null)) + window.serverConfig.smoothing.enable = false; requestWriteConfig(window.serverConfig, true); resetWizard(); @@ -1479,24 +1475,31 @@ async function discover_yeelight_lights() { 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 = {}; - light.host = device.hostname; - //Create a valid hostname - if (device.domain) - { - light.host += '.' + device.domain; + + 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.name = device.name; light.model = device.txt.md; //Yeelight does not provide correct API port with mDNS response, use default one light.port = 55443; @@ -1547,7 +1550,10 @@ function assign_yeelight_lights() { "bottom", "bottomleft", "bottomright", "left", "lefttop", "leftmiddle", "leftbottom", "right", "righttop", "rightmiddle", "rightbottom", - "entire" + "entire", + "lightPosTopLeft112", "lightPosTopLeftNewMid", "lightPosTopLeft121", + "lightPosBottomLeft14", "lightPosBottomLeft12", "lightPosBottomLeft34", "lightPosBottomLeft11", + "lightPosBottomLeft112", "lightPosBottomLeftNewMid", "lightPosBottomLeft121" ]; lightOptions.unshift("disabled"); @@ -1561,7 +1567,7 @@ function assign_yeelight_lights() { var lightName = lights[lightid].name; if (lightName === "") - lightName = $.i18n('edt_dev_spec_lights_itemtitle'); + lightName = $.i18n('edt_dev_spec_lights_itemtitle') + '(' + lightHostname + ')'; var options = ""; for (var opt in lightOptions) { @@ -1578,10 +1584,10 @@ function assign_yeelight_lights() { options = ''; } - $('.lidsb').append(createTableRow([(parseInt(lightid, 10) + 1) + '. ' + lightName + '
(' + lightHostname + ')', '' + options + '', ''])); + + $.i18n('wiz_identify') + ''])); } $('.yee_sel_watch').on("change", function () { @@ -1605,8 +1611,8 @@ function assign_yeelight_lights() { } } -async function getProperties_yeelight(hostname, port) { - let params = { hostname: hostname, port: port }; +async function getProperties_yeelight(host, port) { + let params = { host: host, port: port }; const res = await requestLedDeviceProperties('yeelight', params); @@ -1614,21 +1620,22 @@ async function getProperties_yeelight(hostname, port) { // res can be: false (timeout) or res.error (not found) if (res && !res.error) { const r = res.info - - // Process properties returned - console.log(r); + console.log("Yeelight properties: ", r); } } -async function identify_yeelight_device(hostname, port) { +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 = { hostname: hostname, port: port }; + let params = { host: host, port: port }; await requestLedDeviceIdentification("yeelight", params); if (!window.readOnlyMode) { - $('#btn_wiz_save').prop('disabled', false); + $('#btn_wiz_save').prop('disabled', disabled); } } @@ -1661,7 +1668,7 @@ function startWizardAtmoOrb(e) { + $.i18n('general_btn_save') + '' + $.i18n('general_btn_cancel') + ''); - if (getStorage("darkMode", false) == "on") + if (getStorage("darkMode") == "on") $('#wizard_logo').attr("src", 'img/hyperion/logo_negativ.png'); //open modal @@ -1802,7 +1809,10 @@ function assign_atmoorb_lights() { "bottom", "bottomleft", "bottomright", "left", "lefttop", "leftmiddle", "leftbottom", "right", "righttop", "rightmiddle", "rightbottom", - "entire" + "entire", + "lightPosTopLeft112", "lightPosTopLeftNewMid", "lightPosTopLeft121", + "lightPosBottomLeft14", "lightPosBottomLeft12", "lightPosBottomLeft34", "lightPosBottomLeft11", + "lightPosBottomLeft112", "lightPosBottomLeftNewMid", "lightPosBottomLeft121" ]; lightOptions.unshift("disabled"); @@ -1865,6 +1875,8 @@ function assign_atmoorb_lights() { } 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); @@ -1872,6 +1884,7 @@ async function identify_atmoorb_device(orbId) { await requestLedDeviceIdentification("atmoorb", params); if (!window.readOnlyMode) { - $('#btn_wiz_save').prop('disabled', false); + $('#btn_wiz_save').prop('disabled', disabled); } } + diff --git a/cmake/Dependencies.cmake b/cmake/Dependencies.cmake index 87036292..ec53e239 100644 --- a/cmake/Dependencies.cmake +++ b/cmake/Dependencies.cmake @@ -9,7 +9,7 @@ macro(DeployMacOS TARGET) OUTPUT_STRIP_TRAILING_WHITESPACE ) - install(CODE "set(TARGET_FILE \"${TARGET_FILE}\") \n set(TARGET_BUNDLE_NAME \"${TARGET}.app\") \n set(PLUGIN_DIR \"${QT_PLUGIN_DIR}\")" COMPONENT "Hyperion") + install(CODE "set(TARGET_FILE \"${TARGET_FILE}\") \n set(TARGET_BUNDLE_NAME \"${TARGET}.app\") \n set(PLUGIN_DIR \"${QT_PLUGIN_DIR}\") \n set(BUILD_DIR \"${CMAKE_BINARY_DIR}\")" COMPONENT "Hyperion") install(CODE [[ file(GET_RUNTIME_DEPENDENCIES EXECUTABLES ${TARGET_FILE} @@ -176,7 +176,7 @@ macro(DeployLinux TARGET) COMMAND ${QT_QMAKE_EXECUTABLE} -query QT_INSTALL_PLUGINS OUTPUT_VARIABLE QT_PLUGINS_DIR OUTPUT_STRIP_TRAILING_WHITESPACE - ) + ) endif() # Copy Qt plugins to 'share/hyperion/lib' @@ -258,7 +258,7 @@ macro(DeployLinux TARGET) PATTERN "sitecustomize.py" EXCLUDE # site-specific configs ) endif(PYTHON_MODULES_DIR) - endif(ENABLE_EFFECTENGINE) + endif(ENABLE_EFFECTENGINE) else() # Run CMake after target was built to run get_prerequisites on ${TARGET_FILE} diff --git a/cmake/Findqmdnsengine.cmake b/cmake/Findqmdnsengine.cmake new file mode 100644 index 00000000..ceb7a6b8 --- /dev/null +++ b/cmake/Findqmdnsengine.cmake @@ -0,0 +1,22 @@ +# QMDNSENGINE_FOUND +# QMDNS_INCLUDE_DIR +# QMDNS_LIBRARIES + +find_path(QMDNS_INCLUDE_DIR + NAMES qmdnsengine/mdns.h + PATH_SUFFIXES include +) + +find_library(QMDNS_LIBRARIES + NAMES libqmdnsengine.a + PATHS /usr/local /usr + PATH_SUFFIXES lib64 lib +) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(qmdnsengine + FOUND_VAR QMDNSENGINE_FOUND + REQUIRED_VARS QMDNS_INCLUDE_DIR QMDNS_LIBRARIES +) + +mark_as_advanced(QMDNS_INCLUDE_DIR QMDNS_LIBRARIES) diff --git a/config/hyperion.config.json.default b/config/hyperion.config.json.default index 489c9b66..e6e0c9af 100644 --- a/config/hyperion.config.json.default +++ b/config/hyperion.config.json.default @@ -20,7 +20,9 @@ "output" : "/dev/null", "colorOrder" : "rgb", "latchTime" : 0, - "rewriteTime": 0 + "rewriteTime": 0, + "enableAttempts": 6, + "enableAttemptsInterval": 15 }, "color" : @@ -139,9 +141,9 @@ "forwarder" : { - "enable" : false, - "json" : [], - "flat" : [] + "enable" : false, + "jsonapi" : [], + "flatbuffer" : [] }, "jsonServer" : diff --git a/dependencies/CMakeLists-qmdnsengine.txt.in b/dependencies/CMakeLists-qmdnsengine.txt.in new file mode 100644 index 00000000..12e45328 --- /dev/null +++ b/dependencies/CMakeLists-qmdnsengine.txt.in @@ -0,0 +1,26 @@ +cmake_minimum_required(VERSION 3.2) + +project(qmdnsengine) + +set(WORK_DIR "@QMDNS_WORK_DIR@") +set(SOURCE_DIR "@QMDNS_SOURCE_DIR@") +set(INSTALL_DIR "@QMDNS_INSTALL_DIR@") +set(CMAKE_ARGS "@QMDNS_CMAKE_ARGS@") +set(QMDNS_LOGGING "@QMDNS_LOGGING@") + +include(ExternalProject) + +ExternalProject_Add(qmdnsengine + PREFIX ${WORK_DIR} + BUILD_ALWAYS OFF + DOWNLOAD_COMMAND "" + SOURCE_DIR ${SOURCE_DIR} + INSTALL_DIR ${INSTALL_DIR} + CMAKE_ARGS ${CMAKE_ARGS} + LOG_DOWNLOAD ${QMDNS_LOGGING} + LOG_UPDATE ${QMDNS_LOGGING} + LOG_CONFIGURE ${QMDNS_LOGGING} + LOG_BUILD ${QMDNS_LOGGING} + LOG_INSTALL ${QMDNS_LOGGING} + LOG_TEST ${QMDNS_LOGGING} +) diff --git a/dependencies/CMakeLists.txt b/dependencies/CMakeLists.txt index 9be0d48f..482b5e5a 100644 --- a/dependencies/CMakeLists.txt +++ b/dependencies/CMakeLists.txt @@ -14,6 +14,58 @@ if(ENABLE_DEV_WS281XPWM) external/rpi_ws281x/rpihw.c) endif() +#============================================================================= +# QMdnsEngine +#============================================================================= + +if (ENABLE_MDNS) + set(USE_SYSTEM_QMDNS_LIBS ${DEFAULT_USE_SYSTEM_QMDNS_LIBS} CACHE BOOL "use qmdnsengine library from system") + + if (USE_SYSTEM_QMDNS_LIBS) + find_package(qmdnsengine REQUIRED) + else () + if (NOT DEFINED BUILD_QMDNS_ONCE) + set(BUILD_QMDNS_ONCE CACHE INTERNAL "Done") + set(QMDNS_WORK_DIR "${CMAKE_BINARY_DIR}/dependencies/external/qmdnsengine") + set(QMDNS_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/external/qmdnsengine") + set(QMDNS_INSTALL_DIR ${CMAKE_BINARY_DIR}) + set(QMDNS_CMAKE_ARGS + -DBUILD_SHARED_LIBS:BOOL=OFF + -DCMAKE_INSTALL_PREFIX:PATH=${CMAKE_BINARY_DIR} + -DBIN_INSTALL_DIR:STRING=lib + -DLIB_INSTALL_DIR:STRING=lib + -DINCLUDE_INSTALL_DIR:STRING=include + -DCMAKE_PREFIX_PATH:PATH=${CMAKE_PREFIX_PATH} + -Wno-dev + ) + + if(${CMAKE_BUILD_TYPE} AND ${CMAKE_BUILD_TYPE} EQUAL "Debug") + set(QMDNS_LOGGING 1) + else () + set(QMDNS_LOGGING 0) + endif () + + configure_file(${CMAKE_SOURCE_DIR}/dependencies/CMakeLists-qmdnsengine.txt.in ${QMDNS_WORK_DIR}/CMakeLists.txt @ONLY) + execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" . WORKING_DIRECTORY ${QMDNS_WORK_DIR}) + execute_process(COMMAND ${CMAKE_COMMAND} --build . --config "${CMAKE_BUILD_TYPE}" WORKING_DIRECTORY ${QMDNS_WORK_DIR}) + endif() + + set(QMDNS_INCLUDE_DIR "${CMAKE_BINARY_DIR}/include") + + if(WIN32) + set(QMDNS_LIBRARIES ${CMAKE_BINARY_DIR}/lib/qmdnsengine${CMAKE_STATIC_LIBRARY_SUFFIX}) + else() + set(QMDNS_LIBRARIES ${CMAKE_BINARY_DIR}/lib/${CMAKE_STATIC_LIBRARY_PREFIX}qmdnsengine${CMAKE_STATIC_LIBRARY_SUFFIX}) + endif() + + mark_as_advanced (QMDNS_INCLUDE_DIR QMDNS_LIBRARIES) + endif () + + set(QMDNS_INCLUDE_DIR ${QMDNS_INCLUDE_DIR} PARENT_SCOPE) + set(QMDNS_LIBRARIES ${QMDNS_LIBRARIES} PARENT_SCOPE) + include_directories(${QMDNS_INCLUDE_DIR}) +endif() + #============================================================================= # FLATBUFFER #============================================================================= @@ -50,12 +102,16 @@ if(ENABLE_FLATBUF_SERVER OR ENABLE_FLATBUF_CONNECT) set ( FLATBUFFERS_FLATC_EXECUTABLE "${CMAKE_BINARY_DIR}/../build-x86x64/bin/flatc") endif() endif() + set(FLATBUFFERS_FLATC_EXECUTABLE ${FLATBUFFERS_FLATC_EXECUTABLE} PARENT_SCOPE) set(FLATBUFFERS_INCLUDE_DIRS ${FLATBUFFERS_INCLUDE_DIRS} PARENT_SCOPE) include_directories(${FLATBUFFERS_INCLUDE_DIRS}) - - # message(STATUS "Using flatbuffers compiler: " ${FLATBUFFERS_FLATC_EXECUTABLE}) + if (FLATBUFFERS_INCLUDE_DIRS AND EXISTS "${FLATBUFFERS_INCLUDE_DIRS}/../package.json") + file(STRINGS "${FLATBUFFERS_INCLUDE_DIRS}/../package.json" _FLATBUFFERS_VERSION_STRING REGEX "^[ \t\r\n]+\"version\":[ \t\r\n]+\"[0-9]+.[0-9]+.[0-9]+\",") + string(REGEX REPLACE "^[ \t\r\n]+\"version\":[ \t\r\n]+\"([0-9]+.[0-9]+.[0-9]+)\"," "\\1" FLATBUFFERS_PARSE_VERSION "${_FLATBUFFERS_VERSION_STRING}") + message(STATUS "Flatbuffers version used: ${FLATBUFFERS_PARSE_VERSION}") + endif () function(compile_flattbuffer_schema SRC_FBS OUTPUT_DIR) string(REGEX REPLACE "\\.fbs$" "_generated.h" GEN_HEADER ${SRC_FBS}) @@ -76,11 +132,15 @@ endif() #============================================================================= if(ENABLE_PROTOBUF_SERVER) - set(USE_SYSTEM_PROTO_LIBS ${DEFAULT_USE_SYSTEM_PROTO_LIBS} CACHE BOOL "use protobuf library from system") if (USE_SYSTEM_PROTO_LIBS) find_package(Protobuf REQUIRED) + if(CMAKE_VERSION VERSION_GREATER 3.5.2) + set(PROTOBUF_INCLUDE_DIRS ${Protobuf_INCLUDE_DIRS}) + set(PROTOBUF_PROTOC_EXECUTABLE ${Protobuf_PROTOC_EXECUTABLE}) + set(PROTOBUF_LIBRARIES ${Protobuf_LIBRARIES}) + endif() else () set(protobuf_BUILD_TESTS OFF CACHE BOOL "Build protobuf with tests") set(protobuf_BUILD_SHARED_LIBS OFF CACHE BOOL "Build protobuf shared") @@ -90,26 +150,24 @@ if(ENABLE_PROTOBUF_SERVER) set(protobuf_MSVC_STATIC_RUNTIME OFF CACHE BOOL "Build protobuf static") endif() - add_subdirectory(external/protobuf/cmake) + add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/external/protobuf/cmake") - if(CMAKE_CROSSCOMPILING) - # when crosscompiling import the protoc executable targets from a file generated by a native build - option(IMPORT_PROTOC "Protoc export file (protoc_export.cmake) from a native build" "IMPORT_PROTOC-FILE_NOT_FOUND") - include(${IMPORT_PROTOC}) - else() - # export the protoc compiler so it can be used when cross compiling - export(TARGETS protoc FILE "${CMAKE_BINARY_DIR}/protoc_export.cmake") - endif() - - # define the include for the protobuf library at the parent scope + # define the include for the protobuf library set(PROTOBUF_INCLUDE_DIRS "${CMAKE_CURRENT_SOURCE_DIR}/external/protobuf/src") - # define the protoc executable at the parent scope - set(PROTOBUF_PROTOC_EXECUTABLE "$") + # define the protoc executable + set(PROTOBUF_PROTOC_EXECUTABLE protobuf::protoc) + + # define the protobuf library + set(PROTOBUF_LIBRARIES protobuf::libprotobuf) endif() - set(PROTOBUF_PROTOC_EXECUTABLE ${PROTOBUF_PROTOC_EXECUTABLE} PARENT_SCOPE) + # redefine at parent scope set(PROTOBUF_INCLUDE_DIRS ${PROTOBUF_INCLUDE_DIRS} PARENT_SCOPE) + set(PROTOBUF_PROTOC_EXECUTABLE ${PROTOBUF_PROTOC_EXECUTABLE} PARENT_SCOPE) + set(PROTOBUF_LIBRARIES ${PROTOBUF_LIBRARIES} PARENT_SCOPE) + + # include headers include_directories(${PROTOBUF_INCLUDE_DIRS}) # message(STATUS "Using protobuf compiler: " ${PROTOBUF_PROTOC_EXECUTABLE}) @@ -210,6 +268,7 @@ if(ENABLE_DEV_NETWORK) endif (USE_SYSTEM_MBEDTLS_LIBS) if (NOT USE_SYSTEM_MBEDTLS_LIBS) + cmake_minimum_required(VERSION 3.2) set(DEFAULT_USE_SYSTEM_MBEDTLS_LIBS OFF CACHE BOOL "system mbedtls libraries not found, disable use system mbedtls libraries") set(BUILD_SHARED_LIBS OFF CACHE BOOL "Build shared mbedtls libraries") @@ -219,7 +278,7 @@ if(ENABLE_DEV_NETWORK) set(USE_STATIC_MBEDTLS_LIBRARY ON CACHE BOOL "Enable mbedTLS static libraries") set(MBEDTLS_DOWNLOAD_DIR "${CMAKE_BINARY_DIR}/dependencies/external/mbedtls/download") - set(MBEDTLS_SOURCE_DIR "${CMAKE_BINARY_DIR}/dependencies/external/mbedtls/src") + set(MBEDTLS_SOURCE_DIR "${CMAKE_SOURCE_DIR}/dependencies/external/mbedtls") set(MBEDTLS_BINARY_DIR "${CMAKE_BINARY_DIR}/dependencies/external/mbedtls/build") set(MBEDTLS_INSTALL_DIR "${CMAKE_BINARY_DIR}") if(${CMAKE_BUILD_TYPE} AND ${CMAKE_BUILD_TYPE} EQUAL "Debug") @@ -330,4 +389,3 @@ if(ENABLE_DEV_NETWORK) endif (NOT USE_SYSTEM_MBEDTLS_LIBS) endif(ENABLE_DEV_NETWORK) - diff --git a/dependencies/external/qmdnsengine b/dependencies/external/qmdnsengine new file mode 160000 index 00000000..0ca80117 --- /dev/null +++ b/dependencies/external/qmdnsengine @@ -0,0 +1 @@ +Subproject commit 0ca80117e853671d909b3cec9e2bdcac85a13b9f diff --git a/doc/development/LedDevice_Flows.drawio b/doc/development/LedDevice_Flows.drawio index c7646141..8b1ea149 100644 --- a/doc/development/LedDevice_Flows.drawio +++ b/doc/development/LedDevice_Flows.drawio @@ -1 +1 @@ -7V1de5u4Ev41eZ5zLuwHCRBw2bRpenbbpJt0t9u92YfYJKF1jGuTr/76I2ywQTPGGCMJ2/RiN8YOwZrRzDtfr07Mtw8v51N/cv8pGgajE2oMX07MdyeUmqZp8/8lV17TK8Qiiyt303C4uJa7cB3+CtKLRnr1MRwGs8IH4ygaxeGkeHEQjcfBIC5c86fT6Ln4sdtoVPyrE/8uABeuB/4IXv0aDuP79Cph3uqND0F4d5/+aZc6izdu/MGPu2n0OE7/3gk1b+f/Fm8/+Nm90i86u/eH0XPuknl2Yr6dRlG8+Onh5W0wShY3W7bF771f8+7yuafBOK7yC8+9L7+P3PPnWfz0cvXx9MuXi5uLHl3c5ckfPQbZ15g/bPyaLdD8KwbJTYwT8/T5PoyD64k/SN595jrBr93HDyP+ivAf09sF0zh4WfucZPntuVoF0UMQT1/5R9Jf6NlmumKpSvUINdIrzysJmYabXrzPSYfa2XL7qV7cLf/Aam34D+nybLFUJrJUbMT/7uls4o8La8Z+PiZSPb2NxnFvNlf5N/wDJpu8rN7kP90l//94xn/DeBc8hXxN+UJNo0EwmyWqPEqUO/0b/JEXf2bxS0BKfL3njxJPox/B22gUTfn1cTQOkscIRyPhUk5kyUOm25Ja2ev0xsn7iTBDvl/ejMK7Mb/2EA6HyZunfnphwAUbTBsSvuW6fVsQP2FQ/JaHSN9qQPaXZ6fPH2aDp8GHnx/+7fX+/POvx/seAbL/GAyXInvvD+Io/St5meQW+T6ahr/4wvqjNasaR5OiLAiUBb6WyIqXLW9xb7kuXFrioEvbwNqeDy5uLy6vvl/HP76+frr4dX36jfUoK1vc9i8pdUVzZSCLSh2GmSuTSVtWe/2yfuV2e8K37B6srqiw+OqiKku8JnzB6803z//twz/3k69vvr4ZX396OPd6maHJrV0w5LAifRlN4/voLhr7o7PV1dOVI03Wc/WZj1GylPOL34M4fk3X03+Mo6Kl5ss4ff07dcTzF9+SF9xepi/fveTffPeavhJFdxPFcfQAvMUcwBj83/wGwzcJuEq+zCQYL66kD+YuTT0i2ln0OB2kS2K69MYjN7ZNyM3QuHUyMxr707sgLlne9HPJkpYqyTQY+XH4VERyuwh73RPnNtEUbht+D45WBbfK4d4keX/Gvy3fEn4semK+3IaRLvd6QRSVZokhk7cG0UM4SH8e+TfB6HSJSEUAwHfle/8hHCUa8VcwHfpjX9i8NH2NPR1w9MNwyqF4GCUXucCTXbheI7bZ7ETY7J5tZmigAP3gZjcb2OpP/1yfWz+eh3d//P7H7O/rf/9+In6PuEe+1YOXMP579cf5q2/ZQ/KfV8+RvMgeo5J5uLyMP9sXL+fhm+sr9vn56nbgv2Amwvv28c77PP71zu5ZlHx7efPv7dee2biNSH/1cxTyZ17qpGsVVdIz+6ZbvMniO6W/J2jb8kHq+xpisCPXwErahG9eo6LHcVw16mRZQnDDbK94j8WjNqFNpSvSeTQ9Hs1x7L6R+0f1ejck36PCtqAuZelFUJdyGPaoKgKmhiL/1iMUJFwsZvSz8FGVWeqAtlazZJpW38v9szWjbtMC+jAYRTOYECqKbUN22p8OCpnOopIkuftBuZLIUwtrG7VoQgUINcV97zFuCkp9E6GIEjSRwsKVAKawwnEYdzrQkA6YrPUa4OiEJ9nPOaCxFp7ICLxrIp768CSzunl4UrozpaMTDxipZcViQ/gN7mVDbbeFW0kGORaMva4T0HKyKv8Jyp5W9fJop2KBD9iLtWU8zF4Wa77NIA5x8QlZgsx8XQ+rksgyLxZEnddJXeMY5UE9R7MwKBDG2di/GQVHKQ6HwiqXLHHg9UMPiKOLyNRFZIyoi8BQ+ZvQWf05GSayTDtSbqbLDpZgODuivcmwdhSVWzOrfXRJu6aTdqWWcGNNylZUROhR28A1cltM3KOCbruiPWkOEuOqDJ3+MJwNoiekO6UL82saMErMQlRvFUTuIUhDWpSPKwFsreyghjqo4Zm6oQZM9q6MgAg2FqHAMeEND2l4U4s37A5vKMQbmTnM441S56kAbxQ10qmbg+tRwfaAO8nGG7DZOFljanyecilyJUAsS4c7pOAOYljagYfTAQ+NwIMYCsvMuAK4a8zBJGcOjFu+ZEeYgyQEGbtSCzy8DnioBB5OVeDBNAEPT5xCqg08wJ0kAw+k9jcNvk75vkazpx3mqGnEhNlRx9aNMZCyFpB2BiYeXu6SKev+zeRh3E8v5gWdWYuPiVg+R7MwddJLq7FGXqvR5HVzXEAW3PuFfAGSn9+lAOjz6tIpRwWjcMyVKRvKNlYXF6hoPPSnw0S3Xh9uouQLxPyXm5KyiCQcphlIWFpD5G0aVQ7CU2WbaqOnysyudE9lMiK0lzCnrq8yidiqAu4l21sd+8hJnZ21gzojYypohYmpAl4m6JZyHFJTnQnoM2QuqaTOXET+a+5jk+QDs/WPveSJELJL6x5N/DwzC5/nPyyeoNG9ZXeuQmlQU9VV2I6avUUEiFq1CxHcyNlwn+bmEvH1gqnUZW3GyOo3XGUPNmHCzXKfW5jlPyLUVXWXbWyY3YSWpyy3mWzB2pnNRhaYlS6w7rplFjN2llyNJWcVLTmjeiw5MVi1qblNphzeSLItZ7AROkt3i/X3Qmbcf/LD0aJH90itPCFEs5nPtP1Qzbz+KpEDHensOYwH9/zaZcb3l0/nPYcPI3+8TkVzImik/LcQTkZemFG6VSgIykjAYn3rjoFIi0gbGrNg5/rHs3dzczVFDBomwbZar3L1rCwkZhsFEgJQ91Nm0co4YPY4Yz69jx5uHmf5jHmBLvSOm9vnJLGwKZGebfEsiR68DEaPMw5Zzp9lKohAusZosY8jo+zLG2QbMcj27gqC73CgINNgFkfTYOHHRGXpimlN6gY1BOPhFHRFMC2YrqidISZ7M0Pc6iitVHvq0HqV7WzZkRtHHwWl9aziHZoLt9DVwKiEj9TBBeNCkfguGAfThHNUnv2yRedWJGSyTGXBBqoblg5r1SL2gmYqb5WtVZkVylursn0snzROHNioZK3AfZhAPud5gMuwubqyHf/2/e76nz/+OX2JJhd//GZ+Ny97kNDlagHc+MUUu3F5vgdb4MDjMSH9Z8L4y0bgdRPxFyolWNj5tkcDNw3JhFChZq0uKEaFAjuVL6Jjk4mnL1GBfgO3LDE4hpiuywvmiE+R6Q/FiUIIwyfRczBFJNdlECpsguoJf6F7U2WPLp4i0NPudhQpArcq6LYh6C7bteoPByh76rwH6BKRasyImIgUohv9VqVr9FNqVXD2fOZWtCuNUxruRsxdIUeosqrf0J5lInONZp5hqmePtme/zf9ALo27TQBX2bUjhAalSi/9UI/scMeM9ajaGQxIQq38Ps2VEfA0JcwJlDY6wLhmr/IFNayNZZf1OSikWERTOgQx6UdlfHawM/gHEV9fjgrUe/vSB++IKGoQUTQWTuhCJuUqemAIEFRfNS9zVnup0PoczuC1q8AfwpNfD92vCu3qSB4eO5BTWh7e01I0Vxlp502opII91JaqgxkZ7N04mOHRdnld2CgcPCTnixjBnHh8eLKqSg/u/fEdMs3WpfTyItrC2xJPOHGoYFJ6tusV3naBhVFL45Gpblu88fYLbjpUWGKnKAFLmSfGV/jg+zRbbcSJgRRgcEEpIiEgbtbotGIMRI2CooNYHWTHH3m4XlW5vKq6tdTClkAED6nvzQkCwzH/zxlfLRimd5CgnofawBdqGTBUVIwBYGPc3mEAt2+tBV0WkhBVCwFgyiOLxY0k+C7bdW0NuesIySwTkqMua40fTWshWr+fLeo1iD4r+UCUu8lDusr/HF44o6cf7Pvd+c34Z8S+nAfesiwgH18JubieR7w+ITnVK96yueZwXLFMLW1YUmcfVjDtWx7P7XJyY3X0jmuXIkY8Ajo+l3TCikjslmvSzVnBQWKuZP5oFIzmc8Qi6mvAixHmobFa5sWQ480wL2bJghrEKMEay2SfgffxHRDWYMXp3Z5NnH7mgFQgjLe//zy7fP9z9vR79Ntp+Omyd/2KzP+HQ/4dw1tYaOliLSmxFiHIuIu0YAtVAphv7SriCo9mII7Cwiwmf9j/uDIC3ZlQhCpsXkLlo7XkeshHM5RZw0JjU4nrVH4wAzFERat9MgO8VYNxJ7ZmXZu+Sk1GmEDQz2li4yXEbYjDEdyowfIUtmDr6HiRPqaFIw2PmJyXLG+py38eODmvWgCJdvbpOTxqz2w0Pq+BJBOvwr8+XA79D9H/vLM79tfjm/PgvSq4QS3xcI2eZ7p9efYV1SewYecBJlCyY57y7/H9LEqKGEh7KN/2yw8Whv0tc71y7GQOOpTXhAUp2xnSaZCoMG2ldv8ffHvawSsQNRzBOClWIQJ9SJfUrJrULHXKlT2UEO7ZrNBq4SJlKVmgFQVUMOtdNoxi/G8cxiFfz197FMo1I0eLigko1qc5UWa9/bq6ZAgyXHA2XhyVsG9HCTcjMdtz+1vLzJMks7Jopi0huZyNIlg8BtdcbZSeMVAebZiO1zQywLdDFxreZIU1CqH9aoqomHte0SP3CDP7tBouQ0IEMfwk3Oo41TK6TaE8TaeWt0ej62tnBpA3k4A1rp07WbEs93+0Ipd2XireSovwPJROeMrnk8+OmVz2UZkeNt7Y9IGnxBKtJ91w5Cn4DZs1e+jpmt5eLfm3FvGa76DtSON427Td0aPtDmultjuwtzQYo8fkHXOxwBJOX+65CCUwsbCwvRFOYByBUBgFXuc5nZPgfRDuefC+VNDqncIaubdxQdkV+HG67n6pOiHwwCTHhAGlkHUwGOoqybEnE7aUdR57mC698ciNbRNyMzRunR7C6oLT2GmLxXAdgFM/XZFlpyLL9pZBaOnUTfLVNiq1plbZdMR1RgmxtS077HHbhlrtLCNhOi6kRUyBIxnjKZVFr7bOCeREOPc7oky6OawmVYAJJRuFs1i4hzc7WFUXVpVilDy2Wrf1pLdVsTVJlRrny226k+Tj5TIkemB+HsIpXW4eX/WutVKpRSlV/c3U13bj1Ne4NSDeGhijyhhsMRCToE//IYEp45vZZC6e/Sb6bQqLijJUyPSLfgU9JJEt4R6SWnrCjYVBoVkpPcxRtlVhQsjrEmlWZU1fX4eF62pT9bJ948q0m9HpTmeon1HcnYsCEBVDLmilaS0P5kRWFcLb23X0FHuDHRpgp9BeHMz8VrdltWxZIULNzvXS1mRsQDaZQlH/6Hdsz6ZmH5nYVrtr9XTOtqSnWwO6t/UBMhzdO4JaWq5idG/tTS7rIGhLM73ayFqqKmflmEUFdLL5mG1z4YQ5xWnlniNyWcrW5Sx3k0+AhbP9HEHb3sO54rRZj+ZGQRSMmK0Zv8FoJLiZEAVxzI2hGIvEslm0ComEtO5QmsUV7fcPrc4Yle+NAyeSoIaeFGKnRAdFJkENWO3uchw7NTquvHNdPgkmZCopcFqKOdDWwQ3jKridBrMkAfIlfAig3nQdXY3qCSU2K2iKbRRSZC6Fx+/JI9wunQzvBiq2HqjYHuISCuIT03QK1AwOtB2SZilwfSB6GK/aAzlWWTzHZQXUZLm0HDfxFzklrJ4lKT0QaGNvD1V1dBC3oQwwMiaEoDW5c4knzgJ6ZulRRLKxVbaQbekkrHOKmHCyh2cnx4pV6xWkkDSjQbsCizHbNGYlZ7LwVRgCeRxM7orLzhB3l+vZfYZQFpuI8OShyZ0lF00mhy05JiYdHTTpyJTW1Q7h8GoELgkHiGuedlr5873ASy2pOVVCReWx7eaW50z/2zKhejBNrMpL1tRAStalzc7Sk8lg6MUWsKrsYzORk+yAfnVxe0OOyIVxj82KzU7QDSkN2+mxNyhL4yIrIw3dHJsbigwSx6D1D0uA5s0TG8WMajF+Y+aN6mnJaY8+7+ItK58KrKhqxlWxeOykCOppbU0lrjjIapIiKxlRq7gdD2izME/rYekmJX2LWdS1HZL8t3ic8dzCIm/vrsMb8qCyddg6lFBlF12sOlq7NLdtiTNBXND1BygcWyJCgko307px2N31S0yvvru+bPe1hWfdskhBPSlhVWnWa3gT9Jsi1YPj6UcGEzcebGiVNW2DSsPVzJW43YxBS8xIfXOADT9rDcWI6woOi3qK4qUyO5izDsPMOojJzCPujQekyRRpjFdOmmxXOM22S0GjGlC6F6rnpYUqRELFu2EAV1JSGuXiOfaU9HZi3+hLKIItcVaWdlHzItzqXURaNSJtxlC0ZlAfV5CyOf2EWmPf2Pcbsu5OMXYgxNgkNqWD+wxSrW23qxvuL2poq5gCQBaGOzzYsa+WXhny0JQeFpoG28Mj2zyAJRtjp5HVj4eiIQtIoEND1dBQKcipQH2pjVes9MG74ayaw1nNGAjAtl1k16UMWAtpg1noWYcQNA9G0QxioE4pGsXJgvPXqRa4NXOgYhwC6gKpDIHtWjeXmIOscje9L83ll5nEzR7fUZVMLwQHrif0NdEsVth6MA7SDPB7FTaE8KzN1fBQ7HpoNeTdmkSrKrEqOIorERPSBz1AxV65P0n0iqr52ZztSN2NcRTvO5V7U26Vbo6CZVG5490Ae3Vmdh1Gx1a1sS2tjQYq9930BMLcSfScBMWLlLCgQ8dcHBYr+lht2PRYP6Miabw6jAuwO5ARk/cOVR9za80gidALJYOCoig8oBH/QnqoRhT2EeqLqUzEQ5Tu040OQlWHkmDNPBOd924eeqIhpw2t2Cx8eBwlNmkN7/9/vk7D+bvcsgx+/Beo+KEk7LYye/JMnG32bQr5k1LtMZdxdM7IuZKMHK5BMHaJH6fjNTimUw6FyuFhJ5fLUg70K2XKsR/RULu8Ge6kDOjN8JXX1iNV+txdK+W8QHGS66S8C8bB1B/JtBPUs/u2vc5MMMxMyALKuHboado/iuIDZlpKoeBmoGy0yrQglqTzMTspAttPRSDH3oEksfpTHaAgRcxScyO9hkncMoxMqFWRmqFGyI3bq70iVGuXvdpR5diRqly2Qh361oG+Hdo31muD7iS1Dds1oX3S3W+1/aKbFimEPALhkzBJZimk2cRbf/Sw6rSDLbPu9DRUnspFAcRlXF7Gn+2Ll/PwzfUV+/x8dTvwX7IRP/mNVlSscUqjBsetAGRbfJwMF1WAj2fvZtBdHHFBOiH7FtrXbBvrO5F3jhcuRDg6s5CdsZDlEEhxr7qDljpavQfetPvUzZn5Ig5TeJ4z+oUYhGVdC8FOLQTbq4h40pFmNMYoUIm5iE+66ZldSmvb6wVxRRNPMEoKbDbClqYcWk/YO+x+kkxDNkJHhkBH9INO49mG3ZRnb86APgzlQVmQzIq6wxRlqkxHtHKuMBBWk7ZWHHfacFvJJEzIJP/zoomp86ESfajFirTIdjHzpXP2ELfYFZoUtKfCakRBllGIgsT8o65BRFwGWtxUS4pxzdF57ohxkApw6Y6RTvPpkr5TJKfOTu3a1jMtcgKCc1LqjZwWtYWXj/ocBhIrReeboRhtFYxHRgWBNh1LKU/kJAxeBqPHGRfBNqSE2/tTT8TMjBZOuHXV0RLiFLh6GgzaUUtSYSfKQjvpEZtVetRIRVeGFKDEkwjFO0kuQLnQjLVUaVvE9dw47qPtUnbAVFczG2HaonZ7tqJ8RNnydT5893acGhUfC/HfSNlHqcuGTTjXQaLAYTJIdcZtAKz3tLV2u/sZIxbTXJh1dxtbMmo6I+WHebhVW/69xpt1d5NPN3uPbWNpZwAZRXtpUWPT2aAqU4ZehQ7GY3Gnsk+KJe7KWWa8S47jFONfvb7U0zJz2ipWHIacylK6daT3NzqLQ1hc12K2ZxpZqWepQjZ/2yLEYZZj2K5bM7dLiUn6iztw/aKeJfwZK/kzq7czfg5FeV+vO9Faq50iZtFOZXl4bYbq2E9SlXakdRm53GabqOpE68RYCSpq1TrbFzkES2i/qMyNWMPWoX3zLSpx7XGnWtUJOAeJ41CxKKI4cpyi4xV5Yqv6c5eW36c5h126rF2YqSLMdABDNnTPksJKdK/o6ZdsjzmS5p1L6Tk3DvGqor62bDFNLDJUV6OoRkptjkh+3Vv2liuifaMtG/rcHt+Lx9/oa2NDCcUz5eisR9PWo2xMc6PxoKryHaa5Vf9zZdthiafbEiFikMcfiVYyoR1ZjSmNgyDRZh55mO/Bbjic6hVsnbBdWL+ykcxCE/UrVCoQt34L4Ajv4YgAzGoxRAKyKoioBGCX3kV0VAJAhuWUCqDLtarLtTImOCXHwWhpZSVX8YMBLC1sYC1BUnWB4PxVTuOqw7I1pzMghaiy7Sr9zBhTNFQ9h7E+rQvHWEa5sLqb4qNjsmHjLtBoOtAo09ONR3QxRQpNmCdqoC0EBJVUu6l4oOP5Veh3HUMMBlE6eMV+V0/p5wgMUvlZgBtdbPNsrbhJst2ih01yH05dBwtVXIwYJDdt0L0hZmvRFqjvXzF+e9zBquLyFMtSMkvp+JJ05J1a24ZM/U6VHn05Ya1sNx+WmzXRSLcUnuirTNWWgnaWQqel8GzIYNKQneAvp1EU5/UlEd+naBgkn/g/ \ No newline at end of file +7V1bd5s4tP41XeucB3shAQIek2manmmbtE2nt5dZxCYJrWM8Nrn11x9hgw3a2xhjJGGbPswk2MFYe+vbn/b1lfnX/fP51J/cfYiGwegVNYbPr8zXryilzDP4/5IrL+kVxyGLK7fTcLi4lrtwFf4J0ovpH94+hMNgVnhjHEWjOJwULw6i8TgYxIVr/nQaPRXfdhONip868W8DcOFq4I/g1W/hML5LrxLmrV54G4S3d+lHu9RZvHDtD37fTqOHcfp5r6h5M/+3ePnez+6VftHZnT+MnnKXzLNX5l/TKIoXP90//xWMksXNlm3xd2/WvLp87mkwjqv8wVPvy7uRe/40ix+fP78//fLl4vqiRxd3efRHD0H2NeYPG79kCzT/ikFyE+OVefp0F8bB1cQfJK8+cZ3g1+7i+xH/jfAf4UOlz/kYTOPgOXcpfcjzILoP4ukLf0v6as820xVLVapHqJFeeVpJyDTc9OJdTjrUzpbbT/XidvkBq7XhP6TLs8VSmchSsRH/3NPZxB8X1oz995BI9fQmGse92VzlT/gbTDZ5Xr3If7pN/v/+jP+F8Tp4DPma8rWbRoNgNktUeZQod/oZ/JEXH7P4IyAlvrjzR4mn0e/gr2gUTfn1cTQOkscIRyPhUk5kyUOm25Ja2e/pjZPXE8mFfL+cjMLbMb92Hw6HyYunfnphwGUdTBsSvuW6fVsQP3FsIH7LQ6RvNSD7y7PTp7ezwePg7X9v/+31/vnn68NdjwDZvw+GS5G98QdxlH5KXia5Rb6LpuEfvrD+aM2qxtGkKAsCZdHI8hb3luvCnUUcdGkbWNvzwcXNxeXnX1fx728vHy7+XJ3+YD3Kyha3/UtKXRGuDERfqcMwuDKZtGW11y/rN47bE75l92B1RYXlq1tVZYnXhC14uf7h+X+//Xk3+Xby7WR89eH+3OtlQJNbu2DIaUX6azSN76LbaOyPzlZXT1eGNFnP1XveR8lSzi/+CuL4JV1P/yGOikjNl3H68j01xPNffiS/cLxMf339nH/x9Uv6myi66yiOo3tgLeYExuD/5jcYniTkKvkyk2C8uJI+mFsG9bPoYTpIl8R06bVHrm2bkOuhceNkMBr709sgLlne9H3JkpYqyTQY+XH4WGRyuwh73RPnNtEUbht+D85WBbPK6d4keX3Gvy3fEn4sWmK+3IaRLvd6QRSVZskhk5cG0X04SH8e+dfB6HTJSEUCwHflG/8+HCUa8TWYDv2xL2xemv6OPR0w9MNwyql4GCUXucCTXdiM8SdgsxtWH4Kpiex2s4G9/vjz6tz6/TS8/fTu0+z71b/fH4nfI+6R7/XgOYy/rz6c//Yje0j+8+o5kl+yx6iED5eX8Uf74vk8PLn6zD4+fb4Z+M8YRng/3t96H8d/Xts9i5Ifzyf/3nzrmY2DRPqnH6OQP/NSKV2rqJOe2Tfd4k0W3yn9O0Hblg9S39gQgx25BlbSJnzzGhVNjuOqUSfLEk43DhGAa/GoTWhT6Yp0Jk2NSTNEefMDrpH7R/VaN8ThowJbUJOytCKoSTkMPKpKgamhyL71CAUeF4sZ/ez8qAqWOqatlWmbptX3cv9szazbtIA+DEbRDHqEimLb4J72p4OCq7OoJInzflCuJPLUwtpGLZpQAUJN4Gk1iNe3S40ToYgWNOHEwrUAOrHCcRh3StCQEpis/Srg6CQo2c85qrGWoMg4etfkPPUJSoa7eYJSujWl8xMPwJRnCndZcwAH97IRdTeEe0nmORY8fl0lvOXVKgQoaHsa2csTnopBPoAYa0N5GGIW477NkA5RkIwueWY+todFSmThiwWJ51US2zhGeVDP0SwMCoRxNvavR8FxiEPg5I4NI12yxIHHED0gju5Qpu5Qxoi6QxgqfxMaq38mw0SWaVbK9XSZxRIMZwe9N4tQyZjmrZlRoM5v17TfrhQJN4albEVxhB61DVwjtyXFPSrotiviSXOUGFdlaPSH4WwQPSIZKt1BvyaAUWIWjvVWQeTLTBkVx3xcCWB6ZUc11FENz9RNNaC/dwUCItlYHAWOiW94SNKbWr5hd3xDId/I4DDPN0qNpwK+UdRIx63PN4zyO8nmGzDhOFljanyccilyJUCQpeMdUngHMSztxMPpiIdG4kEMhZFmXAHcNXAwycGBccOX7Ch8kEWUJwQpvVJLPLyOeKgkHk5V4sE0EQ9PrESqTTzAnSQTDyT2Nw2+Tfm+Rr2nHeeoCWJC/SgWRlHLMZCwFpB2Ribun2+TSuv+9eR+3E8v5gWdocX7RCwfo1mYGuklaqyR16o8eV0tF5AFt34hX4Dk59cpAfq4unTKWcEoHHNlygqzjdXFBSsaD/3pMNGtl/vrKPkCMf/jxipFxWME00wkLK1H5G0yVQ7CUmWbaqOlymBXuqUyGRHyS5hT11aZRMxVAfeSba2Oveqkzs7aQZ2RShU0wsRUES8TpEste3tsrc4EZBoyt1riFReR/5J72yR5w2z9Yy97RQjepXWPJr6fmYX38x8WT9Do3rI7U6H0UFPVVNiOmr1FBIrq2dWwHdzI2XCf5koT8fWCrtRlbMbI4jdcZQ/WYcJhuc8RZvmPCHFV3WEbG3o3IfKU+TaTLVjbs9nIArPSBdYdt8zOjB2Sq0FyVhHJGdWD5MRg1QrnNkE5vJFkLGcwETpzd4vx94Jn3H/0w9EiR/dIUZ4QohnmM20/VJjXHyVyoCGdPYXx4I5fu8x6/uXdeU/h/cgfr1PRnAgaCf8thJM1MMzaulUICMpwwDpINpljINIi0qrGLJi5/v7s9RyupgigYRJsK3qVq2dlITHbKPQhAHE/ZYhW1gZmjz3m07vo/vphlveYF1qG3nK4fUocC5sc6dkWz5zowfNg9DDjlOX8SaaCCI3XGC3mcWRt+/KAbCOAbO+uIPgOBwoyDWZxNA0WdkxUli6Y1qRuUEMAD6egKwK0YLqitoiY7E0RcatPaaXaU6ezV9nOln1y4+yjoLSeVbxDc8ctdDWwdsJHauCCcSFIfBuMg2nSd1QeftmicbOLgGUqO2ygumHpQKsWtS9oJvJWGa3KUCiPVmX7WH7fOLFgoxJagfswof+c54F2hs3Fle3471+3Vz8//Tx9jiYXn/42f5mXPdjS5fOCuPGLKXfj8nwDtsCBn8cE958Jz182Qq+bOH+hUoKBnR97VHDTkEwIFWLW6g7FqFBgpvJFdGwy8fQ5KtBv4JY5BseQ03V+wVWDC4ZUfyh2FEIaPomegikiuc6DUGETVHf4C9mbKnN0cReBnnS3o3ARuFVJtw1Jd9muVT8goOyp8xagc0SqgRHRESmcbvSjSpfopxRV8Ab6zK2IK433NNytN3cFH6HKqH5De5aJnWs0txqmevZoe/bb/ANybtxtDnCVTTvS0KBU6aXP9cgGPGZdj7ziHaqmbTGr/D7NhRFwNyX0CZQmOsBzzV75C2qgjWWX5TkgqSpKXToEgfSjAp8dcAZ/I2Lry1mBemtf+uBdI4oajSgaO07oYiblKnpgDBBEXzUvcxZ7qZD6HM7gtc+BP4TTXw/drgrp6ogfHhvKKc0P72kJmqs8aechVFLAHmpL1cKMjPZuLMzwaLusLkwUDu6TCSNGMG88Pny1ikoP7vzxLVLN1rn08iLawtoSTxg6VICUnu16hZddgDBq23hkqtsWa7z9gpsOFZbYKUrAUmaJ8RU++DzNVoM4MZAADC4oRU0IiJslOq06BqKgoGgWq4Ps+CM/rldVLq+qbi21sCUUwUPie/MGgeGY/+eMrxY8pneUoJ6F2tAvdDlNVR8HgIlxe8cB3L61lnRZiENULQWALo/sLG4kh++yXdfWI3cdIZllQnLUea3x6bQWovX7maJeo9FnJRuI9m7ykKzyf4YXzujxN/t1e349/i9iX84DbxkWkM+vBF8cp+BGn5Cc6hVv2VxyOK5YppY0LKm1Dyua9iPP53YZ3VidvePapagjHgEZn8t2woqa2C3XpKuzgoXEXMn80SgYzeuIRdbXgBUjzEPPapkVQ8abYVbMkkU1iFHCNZbOPgPP4zsgrsGK1bs9mzj9zACpYBh/vfvv7PLNf7PHd9Hfp+GHy97VC1L/Hw75dwxvYKClO2tJOWsRgpS7SDtsoUoA/a1dRFzhaAbiKAzMYvKH+Y8rEOhmQhGqMHkJlY/WkOshj2YoQ8NCYlOJ6VQ+mIEYoqLVnswAb9XguRNbsy5NX6UmI51A0Pdp6sZLiNtQD0dwowbDU9iCrWvHi+QxLQxpeMTNecnylrrs54E351VLINHMPj3Do/YMo/F6DcSZ+Dn8+vZy6L+N/s87u2VfH07Ogzeq6Aa1xOEaPc/1+vLwFdUnsGHnB0ygZMdc5d/j+1mUFDEYhFq+7ZdvLBT7W+Z65dgJDjqW1wSClO0M6W2QqFBtpXb/H3x62sErEDUcAZwUqxCBNqRzalZ1apYa5coWSjju2ayQauEiYSlZpBUlVNDrXVaMYvzfOIxDvp5/9ugo14wcLSo6oFif5kSZ5fbrypIhSHHB2XgxKmHfRgk3IzHbc/tby8yTJLOy00xbjuRyNoqAeAyuudpTetaB8miP6XhMIyN8O2Sh4UlWWKIQmq+mqBVzzyta5B5hZp9W42XIEUE8fhKOOk41j25TLE/T1PL2aHR97cwI8uYmYI1r504olvn+j1bk0ual4qm0SJ+H0gpP+f3kszGTyzwq08PKG5seeEosET3phpGn4C9s1uzQ0zW5vVr8by3qa76DtiOJ423TdkePtjusldruwNzSYIyOyTvmYIElTF/m+gsDBcRCj+1ZqUDz5xEKT4FX+Z7OyeF9EO754X2poNUzhTX23sYFZVfoj9Nl90vVCaEPTDImDCiFrMFgqKkkx+5M2FLWee5huvTaI9e2Tcj10LhxekhXF7yNnbazGK4DsOqnC7LsFGTZHhmElE7dTb7a1kqtqVU2bU9YZ7QhtrZlhzlu27RWO8uaMB0X0yKmJbQ9UdhebZ0RyIlwbndEmXR1WE2qABNCNgprsXALb3a0qi6tKuUoeW61butJT6tia5wqNebLbbqT5PFyGRM9MDsP6ZQuM4+vepdaqRRRSlV/c+tru/HW1zgaECGwS0R+IhsMtiiISdinf5/QlPH1bDIXz343+m2Ki4oyVEhF0e4lnc9Ps8+PGOIEF5VOPxzRrIOZmazQpgXVBr9UtkrlxmZjWyJFRomaYoKQY4L8oKp8l1DB/+JKs3C/wvB09uHi9D0Lfo7M66+Dd6M/y1ZOWIrp1cNgEMxmNw+jts9TBthTWQHXgxQRQosKJyrjkqJAUi2f3itFKhlQZ2zCVleDi4oFHhfTkj5juY8gETyJ4+B+kuQpGF84eMLQwhH7wqRojchBLSRLQJY7DN/NFfrKyXYzyFhpMZxAbKbL0YCv+7En1s7RKXtHupFR3lSTw5WBZJ7BleyK5t0IJimMeSVFFa1YwYfc1tnmtpJTxvXMPmhJS12pGZX4scSgUKnRlEpHURsaJnSPcYm0o8SacrUuxFNXm6pnozeuTDv5TZBu612ijLoWi2D+DhxxpDRbI+v2jSa+3tys67oIFKZNp9iGmy5qz3nN7Fa3ZbVsWSHwmo2r1lY7a8AmqYVc9aPfsT2brty72natnoLQlpQqa2D3WNBBESHD2b0jqKUlL1CwJkK2NykaBzGNI9OrtkS9iGMKQa+s7cPWbhPmFH2EPUcc0SBblz0Y9Xodzvazs8r2Fs4Vm6j0aK7DgYLOKWu6SmDdETlMiII45npHrDnisgaySm/ERmYt4OJDAhjttA+t9hiV740D749IDT0uxE6JDqpHIjVgVL7zcexUv7eyznXbJDLBU0mB0VLc2nsd3TA+BzfTYJY4QLrkjG0LlWroSZIjUNAUuxBVNVwKp8rLmyNV2vCsyxneOmd4e4pLKDifmKZT6DjoQOxQmS3MD8NaaEp7KMfKi+e4rMCaLJeW8yb+y+aUYcRLUjrndmPJClU1EZdjKAODBhynagNBrARGnLDrlU7Ylc2tkDzU7ZiV7qkj1HSEgZXEMCyOL9VS0yhsBtkgsMBozDYFR8msUb4MQyCQg3Fe0STnStxeFusj8yGyEng1ARpvZ8lFk8lhS46JXkcH9ToypYE17wDgDOFLxcOWrS7rFse1pUHfC8LUkqBTJVpUfrjdXMqb6X9bOi8dTBar8pg1NZCYdWnetnRvstjMQXVKKkUmtAP96g7uDRkiFxx8OLF2iulO0A4pPbjTY09RltZku2waxubTuaGsdhcezitPAYT45ompYrbQU1Y2vlE9STnt0eddzCXSQAl3MimKm/U8s2/nwNISWf3KMbp1zo0rdmgySbHdNlGruN2Ai2Z5Xql3VLbimpT0LWZR13ZI8l8XIizy8u46vMETKluHrUM5q+yii1V7Ri3hti0HTXAw6DIEFBYuEcFDpXuEmHHY+fVLTq8+v75s97VlgJiV9YNI1ZMSVnV+WA1rgn5TJHxwPBnJoObGgymtsuptUGm4mocAbFdl0BIYqQ8HWPmz1qMYcV3BYFFP0XmpDAdz6DDM0EH0Zh5xdjyYBkSR1Hh8GpC8xHhiwxb2UGqdDxrTgNK9UN0xLYQhknaTG0pwJTml0Sazx+6S3k7sG20JRbgl3pelXTNnkKFh3Ym06om0GaBoTak+riBllfpJc419GyvXELo7xbMDKfb6wsSmtHSfwR7i2+3qhhOMGtoqpkCQhfIOD+bsq50bBDvRlKXjGelhe3hkmydpYSfuHiA4WQl5KBvS0of7INhQKcmpMNNBW2ex0gfvyrNqlmc1AxBgjFSxmytlAC2klWY9/rw6t34/DW8/vfs0+3717/dH4iNhnMEomkEO1ClFozxZMP461QJHMwcqxiGwLuDKEMY46e4m5iCr3NXvSzP5ZZC42eI7qpzphcOB6wl5TTQ7K2xdGgcbDfB7FTaE8KzNxfBQ7npoMeTdkkSrKrEqOoorEXPEnGXxaFM5P0m0iqo7tDnbTSszxlG87zPKmjKrdPMpWNaMMjwbwNBiR3esl9qmp2Or0tiWaKOhmftuegJp7iR6Sg7FC5ewoEPHHBwWI/pYbNj0WD9L4W88OowLEHYW6aI+O0V9zK01gyRCL4QMCorC1BXP4l9IT7MRhXmE+s5UJmIhSvfpRgOhKkNJQDPPRAu+m6ee6JHThig2C+8fRgkmren8/z/fpuH8VY4sg9//C1T8UBx2W8GePIizzb5NYQelVHvM5Tk6B3KuJJDDNQieXeKH6XgNj+mUQ6FyeNh0XlnKgX6lTDn24zTULmuGGykDWjN85bXlSJU+d5dKOQ9QvMplUt4G42Dqj2TiBPXsvm2vgwl8iLckmMC1Q0/S/lEEHzBoKaWCm4my0SpoQZCkszE7KQLbT0Ugx56BJDH6U52gIEHMUriRHsMkbhlHJtSq2JqhxpEbx6u96qjWLrzaUeXYkapctkId+9bBvh3aN9Zrg24ntQ3TNSE+6c632n7RTYsUjjxCwyehksxS2GcTT/3R01WnHe0y61ZPQ+WpHBRATMblZfzRvng+D0+uPrOPT59vBv5zVuInP9GKijFOac3BcRSA7RYfJsNFFOD92esZNBdHHJBOun0L6Wu2jeWdyJvkhQsRls4sZGcsZDkEUtyr7KCljlbPgTftPnVzMF/kYQonOqNfiEFa1qUQ7JRCsL2KiLOONLMxRoFKzEX8qque2SW0tr1eEFeEeIK1pMBqI2xpyqF1xt5h55NkGrKROjKEOqJvdBr3NuymPHszBfowlAftgmRW1B2myFNlOiLKuUJBWM22tWK504bbSm7ChFTyPy2SmDobKtGGWqzYFtkuer501h7iiF0hSUG7K6zGKcgyCqcg0f+oqxARl4EWM9WSYFxz7Tx35DhIBLh0x0hv8+mSvlNsTp2N7drWMi18AoJxUmqNnBalhZeX+hwGEytl55upGG0VjUdKBYE2HUsoT+xJGDwPRg8zLoJtmhJub089kTMzWphx66prS4i3wNWTYNCOWJIKnCg72kk/sVmlo0YqmjIkACWOIhTvJDkA5UIYa6nStqjXc+O8j7ZL2UGnupreCNMWtduzFfkjypavs+G7p+PUiPhYiP1Gwj5KTTZMwrkKEgUOk0KqM44BMN7T1tjt7jNGLKY5MOvuVrZk1DRGyod5uFVT/r3Gk3V3k09Xe49tY2kzgIwiXlrU2DQbVKXL0KuQwXgs5lT2qFjiroxl1nfJ5WeHwvlXry31tNSctqorDkOmspRuHen5jc5iCIvrWsz2TCML9SxViBh9zyLEYZZj2K5b07dLiUn6iztw/aKeJXyMnXzM6uWsP4civ6/XjbTWilPEdAo4lfnhtQHVsU9SlTbSuqy53GZMVDXROgErQUWtWrN9kSFYQvpF5d6INbAOzZtvUYhrjzPVqlbAOcg5DhWLohZHjlM0vGKf2Kr23KXl92nOYJcua3fMVHHMdECHbGieJR0r0b2iJ1+yPXAkzTqXtufcWMSrqvW1ZYtuYrFDdbUW1UiozRGbX/eWueWK2r7RlhV9bs/vxfE3+tLY0IbimXJ06NE0epSVaW4ED6rK32GaW+U/V8YOS5xuS4QTg7z+kWgkE+LIqkxpHASJNvOTh/kG7IbDiV7B1AnbhfErG/EsNBG/QqUCeeuPAJbwHo4IQK0WQyQgK4KISgBm6V1ERyUApFhOqQC6hoMKna2OOL6boH1pZXlX8ckAlh4f1BFQsPKhRBuTx5pvG4eTJdstYlJCwpy6xAuquAhdkqNHdG86xLRoC1RS51Il3TiMS1WpJhH9YzJ9+viSdF3EtMYvTf1GlR69X2OtbDdP7fNUuS490VaZqpGCdkihNdNhWeSrACh+heHp7MPF6XsW/ByZ118H70Z/kOm+6Qzos3EyrT1xCoHBIidxHNxP4qRfVdfhRqjOB/qAaM16FbE9ARBciwD9UDznN+uSc7SWpA61noeTs7umCleJ46KbFAm5lW1m6XaLZRlRhbRA2MOzeStWtjydEatWabsjRBERooSsYnXMF1WGCrmbBwtfqfSb8i4JOFLvfI5kOK/fxBKyAwTfEoequukA0DiLt2rOs4TDOyRrbQ8X7Yg1lig80wLoIitagYtAS2c/CaQGIygqO4/UQ5id2JElB2FsoSN0zxGzgesjjEOlZQng6n3sk1nW8/YqqrrZB+3K0UEHkLLaVo6JPinXUGzlYEj+dfAYcggAjogwcUFcXH6Bryy8F8MDto1OljmXyYkhzNtRaRvpsTsKymyqfPaNbyak4KCkwFACMgGTVhuZHDGljmt8mddBMk5RyMaB/rfc7ZBmDufdDhv8C1zFx0N/WmgFEs99wI2gGqhg7zkG4k+QlCCMixmGSC4SI5LzkKf+8MM1NqDxR48p7PyBi0VPi/X2GBvNhqUy5XXkUF6gkQ4TatGr1mD2TCZijnirNSaKy8d/yb1tkrxhtv6ZTQYMmFH6aOAPHMcUds7iGSoaNv7rNIri/NsTs/QhGgbJO/4f \ No newline at end of file diff --git a/doc/development/LedDevice_Flows.png b/doc/development/LedDevice_Flows.png index 1c12dba0..2f61ebd3 100644 Binary files a/doc/development/LedDevice_Flows.png and b/doc/development/LedDevice_Flows.png differ diff --git a/include/api/API.h b/include/api/API.h index ff22fea1..8cb5e527 100644 --- a/include/api/API.h +++ b/include/api/API.h @@ -402,13 +402,6 @@ signals: /// void onStartInstanceResponse(const int &tan); -private slots: - /// - /// @brief Is called whenever a Hyperion instance wants the current register list - /// @param callerInstance The instance should be returned in the answer call - /// - void requestActiveRegister(QObject *callerInstance); - private: void stopDataConnectionss(); diff --git a/include/api/JsonAPI.h b/include/api/JsonAPI.h index 119d87bb..ca8becf9 100644 --- a/include/api/JsonAPI.h +++ b/include/api/JsonAPI.h @@ -292,6 +292,12 @@ private: /// void handleInputSourceCommand(const QJsonObject& message, const QString& command, int tan); + /// 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); + /// /// Handle an incoming JSON message of unknown type /// diff --git a/include/api/JsonCB.h b/include/api/JsonCB.h index 97dcc3b1..2d59b3eb 100644 --- a/include/api/JsonCB.h +++ b/include/api/JsonCB.h @@ -6,10 +6,7 @@ // components def #include -// bonjour -#ifdef ENABLE_AVAHI -#include -#endif + // videModes #include // settings @@ -21,7 +18,6 @@ class Hyperion; class ComponentRegister; -class BonjourBrowserWrapper; class PriorityMuxer; class JsonCB : public QObject @@ -73,13 +69,6 @@ private slots: /// @brief handle component state changes /// void handleComponentState(hyperion::Components comp, bool state); -#ifdef ENABLE_AVAHI - /// - /// @brief handle emits from bonjour wrapper - /// @param bRegisters The full register map - /// - void handleBonjourChange(const QMap& bRegisters); -#endif /// /// @brief handle emits from PriorityMuxer @@ -140,10 +129,7 @@ private: Hyperion* _hyperion; /// pointer of comp register ComponentRegister* _componentRegister; -#ifdef ENABLE_AVAHI - /// Bonjour instance - BonjourBrowserWrapper* _bonjour; -#endif + /// priority muxer instance PriorityMuxer* _prioMuxer; /// contains all available commands diff --git a/include/bonjour/bonjourbrowserwrapper.h b/include/bonjour/bonjourbrowserwrapper.h deleted file mode 100644 index 849cc981..00000000 --- a/include/bonjour/bonjourbrowserwrapper.h +++ /dev/null @@ -1,68 +0,0 @@ -#pragma once -// qt incl -#include -#include -#include - -#include - -class BonjourServiceBrowser; -class BonjourServiceResolver; -class QTimer; - -class BonjourBrowserWrapper : public QObject -{ - Q_OBJECT -private: - friend class HyperionDaemon; - /// - /// @brief Browse for hyperion services in bonjour, constructed from HyperionDaemon - /// Searching for hyperion http service by default - /// - BonjourBrowserWrapper(QObject * parent = nullptr); - -public: - - /// - /// @brief Browse for a service - /// - bool browseForServiceType(const QString &serviceType); - /// - /// @brief Get all available sessions - /// - QMap getAllServices() { return _hyperionSessions; } - - static BonjourBrowserWrapper* instance; - static BonjourBrowserWrapper *getInstance() { return instance; } - -signals: - /// - /// @brief Emits whenever a change happend - /// - void browserChange( const QMap &bRegisters ); - -private: - /// map of service names and browsers - QMap _browsedServices; - /// Resolver - BonjourServiceResolver *_bonjourResolver; - - // contains all current active service sessions - QMap _hyperionSessions; - - QString _bonjourCurrentServiceToResolve; - /// timer to resolve changes - QTimer *_timerBonjourResolver; - -private slots: - /// - /// @brief is called whenever a BonjourServiceBrowser emits change - void currentBonjourRecordsChanged( const QList &list ); - /// @brief new record resolved - void bonjourRecordResolved( const QHostInfo &hostInfo, int port ); - - /// - /// @brief timer slot which updates regularly entries - /// - void bonjourResolve(); -}; diff --git a/include/bonjour/bonjourrecord.h b/include/bonjour/bonjourrecord.h deleted file mode 100644 index 0b8a522e..00000000 --- a/include/bonjour/bonjourrecord.h +++ /dev/null @@ -1,71 +0,0 @@ -/* -Copyright (c) 2007, Trenton Schulz - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - - 3. The name of the author may not be used to endorse or promote products - derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED -WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO -EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; -OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR -OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF -ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#ifndef BONJOURRECORD_H -#define BONJOURRECORD_H - -#include -#include - -class BonjourRecord -{ -public: - BonjourRecord() : port(-1) {} - BonjourRecord(const QString &name, const QString ®Type, const QString &domain) - : serviceName(name) - , registeredType(regType) - , replyDomain(domain) - , port(-1) - {} - - BonjourRecord(const char *name, const char *regType, const char *domain) - : serviceName(QString::fromUtf8(name)) - , registeredType(QString::fromUtf8(regType)) - , replyDomain(QString::fromUtf8(domain)) - , port(-1) - { - } - - QString serviceName; - QString registeredType; - QString replyDomain; - QString hostName; - QString address; - int port; - - bool operator==(const BonjourRecord &other) const - { - return serviceName == other.serviceName - && registeredType == other.registeredType - && replyDomain == other.replyDomain; - } -}; - -Q_DECLARE_METATYPE(BonjourRecord) - -#endif // BONJOURRECORD_H diff --git a/include/bonjour/bonjourservicebrowser.h b/include/bonjour/bonjourservicebrowser.h deleted file mode 100644 index 6c6874c7..00000000 --- a/include/bonjour/bonjourservicebrowser.h +++ /dev/null @@ -1,69 +0,0 @@ -/* -Copyright (c) 2007, Trenton Schulz - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - - 3. The name of the author may not be used to endorse or promote products - derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED -WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO -EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; -OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR -OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF -ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#ifndef BONJOURSERVICEBROWSER_H -#define BONJOURSERVICEBROWSER_H - -#include -#ifndef PLATFORM_AMLOGIC -#include -#else -#include -#endif -#include "bonjour/bonjourrecord.h" - - -class QSocketNotifier; -class BonjourServiceBrowser : public QObject -{ - Q_OBJECT -public: - BonjourServiceBrowser(QObject *parent = 0); - ~BonjourServiceBrowser() override; - void browseForServiceType(const QString &serviceType); - inline QList currentRecords() const { return bonjourRecords; } - inline QString serviceType() const { return browsingType; } - -signals: - void currentBonjourRecordsChanged(const QList &list); - void error(DNSServiceErrorType err); - -private slots: - void bonjourSocketReadyRead(); - -private: - static void DNSSD_API bonjourBrowseReply(DNSServiceRef , DNSServiceFlags flags, quint32, - DNSServiceErrorType errorCode, const char *serviceName, - const char *regType, const char *replyDomain, void *context); - DNSServiceRef dnssref; - QSocketNotifier *bonjourSocket; - QList bonjourRecords; - QString browsingType; -}; - -#endif // BONJOURSERVICEBROWSER_H diff --git a/include/bonjour/bonjourserviceregister.h b/include/bonjour/bonjourserviceregister.h deleted file mode 100644 index d656dd08..00000000 --- a/include/bonjour/bonjourserviceregister.h +++ /dev/null @@ -1,76 +0,0 @@ -/* -Copyright (c) 2007, Trenton Schulz - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - - 3. The name of the author may not be used to endorse or promote products - derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED -WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO -EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; -OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR -OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF -ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#ifndef BONJOURSERVICEREGISTER_H -#define BONJOURSERVICEREGISTER_H - -#include - -#include "bonjourrecord.h" -class QSocketNotifier; - -#ifndef PLATFORM_AMLOGIC -#include -#else -#include -#endif - -class BonjourServiceRegister : public QObject -{ - Q_OBJECT -public: - BonjourServiceRegister(QObject *parent = 0); - ~BonjourServiceRegister() override; - - void registerService(const QString& service, int port); - void registerService(const BonjourRecord &record, quint16 servicePort, const std::vector>& txt = {}); - inline BonjourRecord registeredRecord() const { return finalRecord; } - - quint16 getPort() const { return _port; } - -signals: - void error(DNSServiceErrorType error); - void serviceRegistered(const BonjourRecord &record); - -private slots: - void bonjourSocketReadyRead(); - -private: - static void DNSSD_API bonjourRegisterService(DNSServiceRef sdRef, DNSServiceFlags, - DNSServiceErrorType errorCode, const char *name, - const char *regtype, const char *domain, - void *context); - DNSServiceRef dnssref; - QSocketNotifier *bonjourSocket; - BonjourRecord finalRecord; - - // current port - quint16 _port = 0; -}; - -#endif // BONJOURSERVICEREGISTER_H diff --git a/include/bonjour/bonjourserviceresolver.h b/include/bonjour/bonjourserviceresolver.h deleted file mode 100644 index 5ff1ebcb..00000000 --- a/include/bonjour/bonjourserviceresolver.h +++ /dev/null @@ -1,71 +0,0 @@ -/* -Copyright (c) 2007, Trenton Schulz - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - - 3. The name of the author may not be used to endorse or promote products - derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED -WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO -EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; -OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR -OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF -ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#ifndef BONJOURSERVICERESOLVER_H -#define BONJOURSERVICERESOLVER_H - -#include - -#ifndef PLATFORM_AMLOGIC -#include -#else -#include -#endif -class QSocketNotifier; -class QHostInfo; -class BonjourRecord; - -class BonjourServiceResolver : public QObject -{ - Q_OBJECT -public: - BonjourServiceResolver(QObject *parent); - ~BonjourServiceResolver() override; - - bool resolveBonjourRecord(const BonjourRecord &record); - -signals: - void bonjourRecordResolved(const QHostInfo &hostInfo, int port); - void error(DNSServiceErrorType error); - -private slots: - void bonjourSocketReadyRead(); - void cleanupResolve(); - void finishConnect(const QHostInfo &hostInfo); - -private: - static void DNSSD_API bonjourResolveReply(DNSServiceRef sdRef, DNSServiceFlags flags, - quint32 interfaceIndex, DNSServiceErrorType errorCode, - const char *fullName, const char *hosttarget, quint16 port, - quint16 txtLen, const char *txtRecord, void *context); - DNSServiceRef dnssref; - QSocketNotifier *bonjourSocket; - int bonjourPort; -}; - -#endif // BONJOURSERVICERESOLVER_H diff --git a/include/flatbufserver/FlatBufferServer.h b/include/flatbufserver/FlatBufferServer.h index 8149e1fb..ba39c647 100644 --- a/include/flatbufserver/FlatBufferServer.h +++ b/include/flatbufserver/FlatBufferServer.h @@ -7,7 +7,6 @@ // qt #include -class BonjourServiceRegister; class QTcpServer; class FlatBufferClient; class NetOrigin; @@ -20,6 +19,7 @@ class NetOrigin; class FlatBufferServer : public QObject { Q_OBJECT + public: FlatBufferServer(const QJsonDocument& config, QObject* parent = nullptr); ~FlatBufferServer() override; @@ -34,6 +34,12 @@ public slots: void initServer(); +signals: + /// + /// @emits whenever the server would like to announce its service details + /// + void publishService(const QString& serviceType, quint16 servicePort, const QByteArray& serviceName = ""); + private slots: /// /// @brief Is called whenever a new socket wants to connect @@ -64,7 +70,6 @@ private: int _timeout; quint16 _port; const QJsonDocument _config; - BonjourServiceRegister * _serviceRegister = nullptr; QVector _openConnections; }; diff --git a/include/forwarder/MessageForwarder.h b/include/forwarder/MessageForwarder.h index 771b7f7e..585d8148 100644 --- a/include/forwarder/MessageForwarder.h +++ b/include/forwarder/MessageForwarder.h @@ -28,6 +28,17 @@ class Hyperion; class QTcpSocket; class FlatBufferConnection; +class MessageForwarderFlatbufferClientsHelper; + +struct TargetHost { + QHostAddress host; + quint16 port; + + bool operator == (TargetHost const& a) const + { + return ((host == a.host) && (port == a.port)); + } +}; class MessageForwarder : public QObject { @@ -39,14 +50,15 @@ public: void addJsonTarget(const QJsonObject& targetConfig); void addFlatbufferTarget(const QJsonObject& targetConfig); -private slots: +public slots: /// /// @brief Handle settings update from Hyperion Settingsmanager emit or this constructor - /// @param type settingyType from enum + /// @param type settingsType from enum /// @param config configuration object /// void handleSettingsUpdate(settings::type type, const QJsonDocument &config); +private slots: /// /// @brief Handle component state change MessageForwarder /// @param component The component from enum @@ -81,15 +93,13 @@ private slots: private: - struct TargetHost { - QHostAddress host; - quint16 port; + void enableTargets(bool enable, const QJsonObject& config); - bool operator == (TargetHost const& a) const - { - return ((host == a.host) && (port == a.port)); - } - }; + int startJsonTargets(const QJsonObject& config); + void stopJsonTargets(); + + int startFlatbufferTargets(const QJsonObject& config); + void stopFlatbufferTargets(); /// Hyperion instance Hyperion *_hyperion; @@ -105,10 +115,37 @@ private: /// Flatbuffer connection for forwarding QList _flatbufferTargets; - QList _forwardClients; /// Flag if forwarder is enabled bool _forwarder_enabled = true; const int _priority; + + MessageForwarderFlatbufferClientsHelper* _messageForwarderFlatBufHelper; +}; + +class MessageForwarderFlatbufferClientsHelper : public QObject +{ + Q_OBJECT + + +public: + MessageForwarderFlatbufferClientsHelper(); + ~MessageForwarderFlatbufferClientsHelper(); + +signals: + void addClient(const QString& origin, const TargetHost& targetHost, int priority, bool skipReply); + void clearClients(); + +public slots: + bool isFree() const; + + void forwardImage(const Image& image); + void addClientHandler(const QString& origin, const TargetHost& targetHost, int priority, bool skipReply); + void clearClientsHandler(); + +private: + + QList _forwardClients; + bool _free; }; diff --git a/include/grabber/X11Grabber.h b/include/grabber/X11Grabber.h index 323afb6b..308af5e8 100644 --- a/include/grabber/X11Grabber.h +++ b/include/grabber/X11Grabber.h @@ -22,6 +22,10 @@ #include #include +#ifdef Bool + #undef Bool +#endif + class X11Grabber : public Grabber , public QAbstractNativeEventFilter { public: diff --git a/include/jsonserver/JsonServer.h b/include/jsonserver/JsonServer.h index 21407d6a..305031f1 100644 --- a/include/jsonserver/JsonServer.h +++ b/include/jsonserver/JsonServer.h @@ -11,7 +11,6 @@ class QTcpServer; class QTcpSocket; class JsonClientConnection; -class BonjourServiceRegister; class NetOrigin; /// @@ -31,11 +30,18 @@ public: JsonServer(const QJsonDocument& config); ~JsonServer() override; + void initServer(); + /// /// @return the port number on which this TCP listens for incoming connections /// uint16_t getPort() const; +signals: + /// + /// @emits whenever the server would like to announce its service details + /// + void publishService(const QString& serviceType, quint16 servicePort, const QByteArray& serviceName = ""); private slots: /// @@ -71,7 +77,7 @@ private: /// port uint16_t _port = 0; - BonjourServiceRegister * _serviceRegister = nullptr; + const QJsonDocument _config; void start(); void stop(); diff --git a/include/leddevice/LedDevice.h b/include/leddevice/LedDevice.h index eca58f2e..f43ee2d9 100644 --- a/include/leddevice/LedDevice.h +++ b/include/leddevice/LedDevice.h @@ -1,4 +1,4 @@ -#ifndef LEDEVICE_H +#ifndef LEDEVICE_H #define LEDEVICE_H // qt includes @@ -14,6 +14,7 @@ #include #include #include +#include // Utility includes #include @@ -50,6 +51,13 @@ public: /// ~LedDevice() override; + /// + /// @brief Set the common logger for LED-devices. + /// + /// @param[in] log The logger to be used + /// + void setLogger(Logger* log); + /// /// @brief Set the current active LED-device type. /// @@ -64,6 +72,8 @@ public: /// void setLedCount(int ledCount); + void setColorOrder(const QString& colorOrder); + /// /// @brief Set a device's latch time. /// @@ -83,6 +93,19 @@ public: /// void setRewriteTime(int rewriteTime_ms); + /// @brief Set a device's enablement cycle's parameters. + /// + /// @param[in] maxEnableRetries Maximum number of attempts to enable a device, if reached retries will be stopped + /// @param[in] enableAttemptsTimerInterval Interval in seconds between two enablement attempts + /// + void setEnableAttempts(int maxEnablAttempts, std::chrono::seconds enableAttemptsTimerInterval); + + /// @brief Enable a device automatically after Hyperion startup or not + /// + /// @param[in] isAutoStart + /// + void setAutoStart(bool isAutoStart); + /// /// @brief Discover devices of this type available (for configuration). /// @note Mainly used for network devices. Allows to find devices, e.g. via ssdp, mDNS or cloud ways. @@ -120,6 +143,15 @@ public: /// virtual void identify(const QJsonObject& /*params*/) {} + /// + /// @brief Add an authorization/client-key or token to the device + /// + /// Used in context of a set of devices of the same type. + /// + /// @param[in] params Parameters to address device + /// @return A JSON structure holding the authorization key/token + virtual QJsonObject addAuthorization(const QJsonObject& /*params*/) { return QJsonObject(); } + /// /// @brief Check, if device is properly initialised /// @@ -127,7 +159,7 @@ public: /// /// @return True, if device is initialised /// - bool isInitialised() const { return _isDeviceInitialised; } + bool isInitialised() const; /// /// @brief Check, if device is ready to be used. @@ -136,14 +168,14 @@ public: /// /// @return True, if device is ready /// - bool isReady() const { return _isDeviceReady; } + bool isReady() const; /// /// @brief Check, if device is in error state. /// /// @return True, if device is in error /// - bool isInError() const { return _isDeviceInError; } + bool isInError() const; /// /// @brief Prints the color values to stdout. @@ -152,13 +184,6 @@ public: /// static void printLedValues(const std::vector& ledValues); - /// - /// @brief Set the common logger for LED-devices. - /// - /// @param[in] log The logger to be used - /// - void setLogger(Logger* log) { _log = log; } - public slots: /// @@ -181,47 +206,47 @@ public slots: /// @param[in] ledValues The color per LED /// @return Zero on success else negative (i.e. device is not ready) /// - virtual int updateLeds(const std::vector& ledValues); + virtual int updateLeds(std::vector ledValues); /// /// @brief Get the currently defined LatchTime. /// /// @return Latch time in milliseconds /// - int getLatchTime() const { return _latchTime_ms; } + int getLatchTime() const; /// /// @brief Get the currently defined RewriteTime. /// /// @return Rewrite time in milliseconds /// - int getRewriteTime() const { return _refreshTimerInterval_ms; } + int getRewriteTime() const; /// /// @brief Get the number of LEDs supported by the device. /// /// @return Number of device's LEDs, 0 = unknown number /// - int getLedCount() const { return _ledCount; } + int getLedCount() const; /// /// @brief Get the current active LED-device type. /// - QString getActiveDeviceType() const { return _activeDeviceType; } + QString getActiveDeviceType() const; /// /// @brief Get color order of device. /// /// @return The color order /// - QString getColorOrder() const { return _colorOrder; } + QString getColorOrder() const; /// /// @brief Get the LED-Device component's state. /// /// @return True, if enabled /// - inline bool componentState() const { return _isEnabled; } + bool componentState() const; /// /// @brief Enables the device for output. @@ -257,11 +282,6 @@ public slots: /// virtual bool switchOff(); - bool switchOnOff(bool onState) - { - return onState == true ? switchOn() : switchOff(); - } - signals: /// /// @brief Emits whenever the LED-Device switches between on/off. @@ -361,6 +381,16 @@ protected: /// virtual bool restoreState(); + /// + /// @brief Start a new enable cycle + /// + void startEnableAttemptsTimer(); + + /// + /// @brief Stop a new enable cycle + /// + void stopEnableAttemptsTimer(); + /// /// @brief Converts an uint8_t array to hex string. /// @@ -368,7 +398,7 @@ protected: /// @param size of the array /// @param number Number of array items to be converted. /// @return array as string of hex values - QString uint8_t_to_hex_string(const uint8_t * data, const int size, int number = -1) const; + static QString uint8_t_to_hex_string(const uint8_t * data, const int size, int number = -1) ; /// /// @brief Converts a ByteArray to hex string. @@ -376,7 +406,7 @@ protected: /// @param data ByteArray /// @param number Number of array items to be converted. /// @return array as string of hex values - QString toHex(const QByteArray& data, int number = -1) const; + static QString toHex(const QByteArray& data, int number = -1) ; /// Current device's type QString _activeDeviceType; @@ -414,6 +444,7 @@ protected: QJsonObject _orignalStateValues; // Device states + /// Is the device enabled? bool _isEnabled; @@ -429,9 +460,6 @@ protected: /// Is the device in error state and stopped? bool _isDeviceInError; - /// Is the device in the switchOff process? - bool _isInSwitchOff; - /// Timestamp of last write QDateTime _lastWriteTime; @@ -459,6 +487,15 @@ private: /// @brief Stop refresh cycle void stopRefreshTimer(); + /// Timer that enables a device (used to retry enablement, if enabled failed before) + QTimer* _enableAttemptsTimer; + + // Device configuration parameters + + std::chrono::seconds _enableAttemptTimerInterval; + int _enableAttempts; + int _maxEnableAttempts; + /// Is last write refreshing enabled? bool _isRefreshEnabled; diff --git a/include/leddevice/LedDeviceWrapper.h b/include/leddevice/LedDeviceWrapper.h index adad0741..b86c7619 100644 --- a/include/leddevice/LedDeviceWrapper.h +++ b/include/leddevice/LedDeviceWrapper.h @@ -94,16 +94,6 @@ signals: /// int updateLeds(const std::vector& ledValues); - /// - /// @brief Enables the LED-Device. - /// - void enable(); - - /// - /// @brief Disables the LED-Device. - /// - void disable(); - /// /// @brief Switch the LEDs on. /// @@ -113,7 +103,7 @@ signals: /// @brief Switch the LEDs off. /// void switchOff(); - + void stopLedDevice(); private slots: diff --git a/include/mdns/MdnsBrowser.h b/include/mdns/MdnsBrowser.h new file mode 100644 index 00000000..0ac35a4e --- /dev/null +++ b/include/mdns/MdnsBrowser.h @@ -0,0 +1,170 @@ +#ifndef MDNS_BROWSER_H +#define MDNS_BROWSER_H + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +// Qt includes +#include +#include + +// Utility includes +#include + +namespace { + constexpr std::chrono::milliseconds DEFAULT_DISCOVER_TIMEOUT{ 500 }; + constexpr std::chrono::milliseconds DEFAULT_ADDRESS_RESOLVE_TIMEOUT{ 1000 }; + +} //End of constants + +class MdnsBrowser : public QObject +{ + Q_OBJECT + + // Run MdnsBrowser as singleton + +private: + /// + /// @brief Browse for hyperion services in bonjour, constructed from HyperionDaemon + /// Searching for hyperion http service by default + /// + // Run MdnsBrowser as singleton + MdnsBrowser(QObject* parent = nullptr); + ~MdnsBrowser() override; + +public: + + static MdnsBrowser& getInstance() + { + static MdnsBrowser* instance = new MdnsBrowser(); + return *instance; + } + + MdnsBrowser(const MdnsBrowser&) = delete; + MdnsBrowser(MdnsBrowser&&) = delete; + MdnsBrowser& operator=(const MdnsBrowser&) = delete; + MdnsBrowser& operator=(MdnsBrowser&&) = delete; + + QMdnsEngine::Service getFirstService(const QByteArray& serviceType, const QString& filter = ".*", const std::chrono::milliseconds waitTime = DEFAULT_DISCOVER_TIMEOUT) const; + QJsonArray getServicesDiscoveredJson(const QByteArray& serviceType, const QString& filter = ".*", const std::chrono::milliseconds waitTime = std::chrono::milliseconds{ 0 }) const; + + + void printCache(const QByteArray& name = QByteArray(), quint16 type = QMdnsEngine::ANY) const; + +public slots: + + /// + /// @brief Browse for a service of type + /// + void browseForServiceType(const QByteArray& serviceType); + + QHostAddress getHostFirstAddress(const QByteArray& hostname); + + void onHostNameResolved(const QHostAddress& address); + + QMdnsEngine::Record getServiceInstanceRecord(const QByteArray& serviceInstance, const std::chrono::milliseconds waitTime = DEFAULT_DISCOVER_TIMEOUT) const; + + bool resolveAddress(Logger* log, const QString& hostname, QHostAddress& hostAddress, std::chrono::milliseconds timeout = DEFAULT_ADDRESS_RESOLVE_TIMEOUT); + +Q_SIGNALS: + + /** + * @brief Indicate that the specified service was updated + * + * This signal is emitted when the SRV record for a service (identified by + * its name and type) or a TXT record has changed. + */ + void serviceFound(const QMdnsEngine::Service& service); + + /** + * @brief Indicate that the specified service was removed + * + * This signal is emitted when an essential record (PTR or SRV) is + * expiring from the cache. This will also occur when an updated PTR or + * SRV record is received with a TTL of 0. + */ + void serviceRemoved(const QMdnsEngine::Service& service); + + void addressResolved(const QHostAddress address); + +private slots: + + void onServiceAdded(const QMdnsEngine::Service& service); + void onServiceUpdated(const QMdnsEngine::Service& service); + void onServiceRemoved(const QMdnsEngine::Service& service); + +private: + + // template ::value, int> = 0> + // static inline QMetaObject::Connection weakConnect(typename QtPrivate::FunctionPointer::Object* sender, + // Func1 signal, + // typename QtPrivate::FunctionPointer::Object* receiver, + // Func2 slot) + // { + // QMetaObject::Connection conn_normal = QObject::connect(sender, signal, receiver, slot); + + // QMetaObject::Connection* conn_delete = new QMetaObject::Connection(); + + // *conn_delete = QObject::connect(sender, signal, [conn_normal, conn_delete]() { + // QObject::disconnect(conn_normal); + // QObject::disconnect(*conn_delete); + // delete conn_delete; + // }); + // return conn_normal; + // } + + template ::value, int> = 0> + static inline QMetaObject::Connection weakConnect(typename QtPrivate::FunctionPointer::Object* sender, + Func1 signal, + Func2 slot) + { + QMetaObject::Connection conn_normal = QObject::connect(sender, signal, slot); + + QMetaObject::Connection* conn_delete = new QMetaObject::Connection(); + + *conn_delete = QObject::connect(sender, signal, [conn_normal, conn_delete]() { + QObject::disconnect(conn_normal); + QObject::disconnect(*conn_delete); + delete conn_delete; + }); + return conn_normal; + } + + // template ::value, int> = 0> + // static inline QMetaObject::Connection weakConnect(typename QtPrivate::FunctionPointer::Object* sender, + // Func1 signal, + // typename QtPrivate::FunctionPointer::Object* receiver, + // Func2 slot) + // { + // Q_UNUSED(receiver); + // QMetaObject::Connection conn_normal = QObject::connect(sender, signal, slot); + + // QMetaObject::Connection* conn_delete = new QMetaObject::Connection(); + + // *conn_delete = QObject::connect(sender, signal, [conn_normal, conn_delete]() { + // QObject::disconnect(conn_normal); + // QObject::disconnect(*conn_delete); + // delete conn_delete; + // }); + // return conn_normal; + // } + + /// The logger instance for mDNS-Service + Logger* _log; + + QMdnsEngine::Server _server; + QMdnsEngine::Cache _cache; + + QMap _browsedServiceTypes; +}; + +#endif // MDNSBROWSER_H diff --git a/include/mdns/MdnsProvider.h b/include/mdns/MdnsProvider.h new file mode 100644 index 00000000..80deb7c1 --- /dev/null +++ b/include/mdns/MdnsProvider.h @@ -0,0 +1,51 @@ +#ifndef MDNSPROVIDER_H +#define MDNSPROVIDER_H + +#include +#include +#include +#include + +// Qt includes +#include +#include + +// Utility includes +#include + +class MdnsProvider : public QObject +{ + +public: + + MdnsProvider(QObject* parent = nullptr); + ~MdnsProvider() override; + + QList getServiceTypesProvided() const { return _providedServiceTypes.keys(); } + +public slots: + + /// + /// @brief Init MdnsProvider after thread start + /// + void init(); + + void publishService (const QString& serviceType, quint16 servicePort, const QByteArray& serviceName = ""); + +private slots: + + void onHostnameChanged(const QByteArray& hostname); + +private: + + /// The logger instance for mDNS-Service + Logger* _log; + + QMdnsEngine::Server* _server; + QMdnsEngine::Hostname* _hostname; + + /// map of services provided + QMap _providedServiceTypes; +}; + +#endif // MDNSPROVIDER_H diff --git a/include/mdns/MdnsServiceRegister.h b/include/mdns/MdnsServiceRegister.h new file mode 100644 index 00000000..33bf7057 --- /dev/null +++ b/include/mdns/MdnsServiceRegister.h @@ -0,0 +1,38 @@ +#ifndef MDNSSERVICEREGISTER_H +#define MDNSSERVICEREGISTER_H + +#include +#include + +struct mdnsConfig +{ + QByteArray serviceType; + QString serviceNameFilter; +}; + +typedef QMap MdnsServiceMap; + +const MdnsServiceMap mDnsServiceMap = { + //Hyperion + {"jsonapi" , {"_hyperiond-json._tcp.local.", ".*"}}, + {"flatbuffer" , {"_hyperiond-flatbuf._tcp.local.", ".*"}}, + {"protobuffer" , {"_hyperiond-protobuf._tcp.local.", ".*"}}, + {"http" , {"_http._tcp.local.", ".*"}}, + {"https" , {"_https._tcp.local.", ".*"}}, + + //LED Devices + {"cololight" , {"_hap._tcp.local.", "ColoLight.*"}}, + {"nanoleaf" , {"_nanoleafapi._tcp.local.", ".*"}}, + {"philipshue" , {"_hue._tcp.local.", ".*"}}, + {"wled" , {"_wled._tcp.local.", ".*"}}, + {"yeelight" , {"_hap._tcp.local.", "Yeelight.*|YLBulb.*"}}, +}; + +class MdnsServiceRegister { +public: + static QByteArray getServiceType(const QString &serviceType) { return mDnsServiceMap[serviceType].serviceType; } + static QString getServiceNameFilter(const QString &serviceType) { return mDnsServiceMap[serviceType].serviceNameFilter; } + static MdnsServiceMap getAllConfigs () { return mDnsServiceMap; } +}; + +#endif // MDNSSERVICEREGISTER_H diff --git a/include/protoserver/ProtoServer.h b/include/protoserver/ProtoServer.h index b722dbc4..1834fb8f 100644 --- a/include/protoserver/ProtoServer.h +++ b/include/protoserver/ProtoServer.h @@ -24,6 +24,12 @@ public: ProtoServer(const QJsonDocument& config, QObject* parent = nullptr); ~ProtoServer() override; +signals: + /// + /// @emits whenever the server would like to announce its service details + /// + void publishService(const QString& serviceType, quint16 servicePort, const QByteArray& serviceName = ""); + public slots: /// /// @brief Handle settings update diff --git a/include/utils/ColorRgb.h b/include/utils/ColorRgb.h index 7f11849c..ae4d3145 100644 --- a/include/utils/ColorRgb.h +++ b/include/utils/ColorRgb.h @@ -31,6 +31,30 @@ struct ColorRgb static const ColorRgb YELLOW; /// 'White' RgbColor (255, 255, 255) static const ColorRgb WHITE; + + ColorRgb() = default; + + ColorRgb(uint8_t _red, uint8_t _green,uint8_t _blue): + red(_red), + green(_green), + blue(_blue) + { + + } + + ColorRgb operator-(const ColorRgb& b) const + { + ColorRgb a(*this); + a.red -= b.red; + a.green -= b.green; + a.blue -= b.blue; + return a; + } + + QString toQString() const + { + return QString("(%1,%2,%3)").arg(red).arg(green).arg(blue); + } }; /// Assert to ensure that the size of the structure is 'only' 3 bytes diff --git a/include/utils/NetUtils.h b/include/utils/NetUtils.h index 03974748..856062a1 100644 --- a/include/utils/NetUtils.h +++ b/include/utils/NetUtils.h @@ -1,12 +1,17 @@ #pragma once -#include - #include #include #include #include +#include +#include + +#ifdef ENABLE_MDNS +#include +#endif + namespace NetUtils { const int MAX_PORT = 65535; @@ -38,15 +43,15 @@ namespace NetUtils { /// /// @brief Check if the port is in the valid range /// @param log The logger of the caller to print/// - /// @param[in] port The port to be tested + /// @param[in] port The port to be tested (port = -1 is ignored for testing) /// @param[in] host A hostname/IP-address to make reference to during logging /// @return True on success else false /// inline bool isValidPort(Logger* log, int port, const QString& host) { - if (port <= 0 || port > MAX_PORT) + if ((port <= 0 || port > MAX_PORT) && port != -1) { - Error(log, "Invalid port [%d] for host: %s!", port, QSTRING_CSTR(host)); + Error(log, "Invalid port [%d] for host: (%s)! - Port must be in range [0 - %d]", port, QSTRING_CSTR(host), MAX_PORT); return false; } return true; @@ -59,7 +64,7 @@ namespace NetUtils { /// @param[in/out] port The resolved port, if available. /// @return True on success else false /// - inline bool resolveHostPort(const QString& address, QString& host, quint16& port) + inline bool resolveHostPort(const QString& address, QString& host, int& port) { if (address.isEmpty()) { @@ -91,38 +96,109 @@ namespace NetUtils { } /// - /// @brief Check if the port is in the valid range - /// @param log The logger of the caller to print - /// @param[in] address The port to be tested - /// @param[out] hostAddress A hostname to make reference to during logging - /// @return True on success else false + /// @brief Resolve a hostname (DNS/mDNS) into an IP-address. A given IP address will be passed through + /// @param[in/out] log The logger of the caller to print + /// @param[in] hostname The hostname to be resolved + /// @param[out] hostAddress The resolved IP-Address + /// @return True on success else false /// - - inline bool resolveHostAddress(Logger* log, const QString& address, QHostAddress& hostAddress) + inline bool resolveMdDnsHostToAddress(Logger* log, const QString& hostname, QHostAddress& hostAddress) { bool isHostAddressOK{ false }; - - if (hostAddress.setAddress(address)) + if (!hostname.isEmpty()) { - Debug(log, "Successfully parsed %s as an IP-address.", QSTRING_CSTR(hostAddress.toString())); - isHostAddressOK = true; - } - else - { - QHostInfo hostInfo = QHostInfo::fromName(address); - if (hostInfo.error() == QHostInfo::NoError) + #ifdef ENABLE_MDNS + if (hostname.endsWith(".local") || hostname.endsWith(".local.")) { - hostAddress = hostInfo.addresses().first(); - Debug(log, "Successfully resolved IP-address (%s) for hostname (%s).", QSTRING_CSTR(hostAddress.toString()), QSTRING_CSTR(address)); - isHostAddressOK = true; + QHostAddress resolvedAddress; + QMetaObject::invokeMethod(&MdnsBrowser::getInstance(), "resolveAddress", + Qt::BlockingQueuedConnection, + Q_RETURN_ARG(bool, isHostAddressOK), + Q_ARG(Logger*, log), Q_ARG(QString, hostname), Q_ARG(QHostAddress&, resolvedAddress)); + hostAddress = resolvedAddress; } else + #endif { - QString errortext = QString("Failed resolving IP-address for [%1], (%2) %3").arg(address).arg(hostInfo.error()).arg(hostInfo.errorString()); - Error(log, "%s", QSTRING_CSTR(errortext)); - isHostAddressOK = false; + if (hostAddress.setAddress(hostname)) + { + //Debug(log, "IP-address (%s) not required to be resolved.", QSTRING_CSTR(hostAddress.toString())); + isHostAddressOK = true; + } + else + { + QHostInfo hostInfo = QHostInfo::fromName(hostname); + if (hostInfo.error() == QHostInfo::NoError) + { + hostAddress = hostInfo.addresses().at(0); + Debug(log, "Successfully resolved hostname (%s) to IP-address (%s)", QSTRING_CSTR(hostname), QSTRING_CSTR(hostAddress.toString())); + isHostAddressOK = true; + } + else + { + QString errortext = QString("Failed resolving hostname (%1) to IP-address. Error: (%2) %3").arg(hostname).arg(hostInfo.error()).arg(hostInfo.errorString()); + Error(log, "%s", QSTRING_CSTR(errortext)); + isHostAddressOK = false; + } + } } } return isHostAddressOK; } -} + + /// + /// @brief Resolve a hostname(DNS) or mDNS service name into an IP-address. A given IP address will be passed through + /// @param[in/out] log The logger of the caller to print + /// @param[in] hostname The hostname/mDNS service name to be resolved + /// @param[out] hostAddress The resolved IP-Address + /// @param[in/out] port The port provided by the mDNS service, if not mDNS the input port is returned + /// @return True on success else false + /// + inline bool resolveHostToAddress(Logger* log, const QString& hostname, QHostAddress& hostAddress, int& port) + { + bool areHostAddressPartOK{ false }; + QString target {hostname}; + + #ifdef ENABLE_MDNS + if (hostname.endsWith("._tcp.local")) + { + //Treat hostname as service instance name that requires to be resolved into an mDNS-Hostname first + QMdnsEngine::Record service = MdnsBrowser::getInstance().getServiceInstanceRecord(hostname.toUtf8()); + if (!service.target().isEmpty()) + { + Info(log, "Resolved service [%s] to mDNS hostname [%s], service port [%d]", QSTRING_CSTR(hostname), service.target().constData(), service.port()); + target = service.target(); + port = service.port(); + } + else + { + Error(log, "Cannot resolve mDNS hostname for given service [%s]!", QSTRING_CSTR(hostname)); + return areHostAddressPartOK; + } + } + #endif + + QHostAddress resolvedAddress; + if (NetUtils::resolveMdDnsHostToAddress(log, target, resolvedAddress)) + { + hostAddress = resolvedAddress; + if (hostname != hostAddress.toString()) + { + Info(log, "Resolved hostname (%s) to IP-address (%s)", QSTRING_CSTR(hostname), QSTRING_CSTR(hostAddress.toString())); + } + + if (NetUtils::isValidPort(log, port, hostAddress.toString())) + { + areHostAddressPartOK = true; + } + } + return areHostAddressPartOK; + } + + inline bool resolveHostToAddress(Logger* log, const QString& hostname, QHostAddress& hostAddress) + { + int ignoredPort {MAX_PORT}; + return resolveHostToAddress(log, hostname, hostAddress, ignoredPort); + } + + } diff --git a/include/webserver/WebServer.h b/include/webserver/WebServer.h index f2537eba..d5b87c42 100644 --- a/include/webserver/WebServer.h +++ b/include/webserver/WebServer.h @@ -11,7 +11,6 @@ // settings #include -class BonjourServiceRegister; class StaticFileServing; class QtHttpServer; @@ -52,6 +51,11 @@ signals: /// void portChanged(quint16 port); + /// + /// @emits whenever the server would like to announce its service details + /// + void publishService(const QString& serviceType, quint16 servicePort, const QByteArray& serviceName = ""); + public slots: /// /// @brief Init server after thread start @@ -93,8 +97,6 @@ private: const QString WEBSERVER_DEFAULT_CRT_PATH = ":/hyperion.crt"; const QString WEBSERVER_DEFAULT_KEY_PATH = ":/hyperion.key"; quint16 WEBSERVER_DEFAULT_PORT = 8090; - - BonjourServiceRegister * _serviceRegister = nullptr; }; #endif // WEBSERVER_H diff --git a/libsrc/CMakeLists.txt b/libsrc/CMakeLists.txt index cc18654d..ba5716ba 100644 --- a/libsrc/CMakeLists.txt +++ b/libsrc/CMakeLists.txt @@ -32,9 +32,9 @@ add_subdirectory(db) add_subdirectory(api) add_subdirectory(ssdp) -if(ENABLE_AVAHI) - add_subdirectory(bonjour) -endif() +if(ENABLE_MDNS) + add_subdirectory(mdns) +endif() if(ENABLE_EFFECTENGINE) add_subdirectory(effectengine) diff --git a/libsrc/api/API.cpp b/libsrc/api/API.cpp index 043b59ac..eaf4c04d 100644 --- a/libsrc/api/API.cpp +++ b/libsrc/api/API.cpp @@ -14,6 +14,7 @@ #include #include #include +#include // hyperion includes #include @@ -23,9 +24,6 @@ #include #include -// bonjour wrapper -#include - // ledmapping int <> string transform methods #include @@ -44,17 +42,13 @@ API::API(Logger *log, bool localConnection, QObject *parent) // Init _log = log; _authManager = AuthManager::getInstance(); - _instanceManager = HyperionIManager::getInstance(); + _instanceManager = HyperionIManager::getInstance(); _localConnection = localConnection; _authorized = false; _adminAuthorized = false; - _hyperion = _instanceManager->getHyperionInstance(0); _currInstanceIndex = 0; - // TODO FIXME - // report back current registers when a Hyperion instance request it - //connect(ApiSync::getInstance(), &ApiSync::requestActiveRegister, this, &API::requestActiveRegister, Qt::QueuedConnection); // 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) @@ -73,7 +67,7 @@ API::API(Logger *log, bool localConnection, QObject *parent) void API::init() { - assert(_hyperion); + _hyperion = _instanceManager->getHyperionInstance(0); bool apiAuthRequired = _authManager->isAuthRequired(); @@ -336,13 +330,6 @@ void API::stopInstance(quint8 index) QMetaObject::invokeMethod(_instanceManager, "stopInstance", Qt::QueuedConnection, Q_ARG(quint8, index)); } -void API::requestActiveRegister(QObject *callerInstance) -{ - // TODO FIXME - //if (_activeRegisters.size()) - // QMetaObject::invokeMethod(ApiSync::getInstance(), "answerActiveRegister", Qt::QueuedConnection, Q_ARG(QObject *, callerInstance), Q_ARG(MapRegister, _activeRegisters)); -} - bool API::deleteInstance(quint8 index, QString &replyMsg) { if (_adminAuthorized) diff --git a/libsrc/api/JSONRPC_schema/schema-leddevice.json b/libsrc/api/JSONRPC_schema/schema-leddevice.json index 847c1c1d..5065ea0d 100644 --- a/libsrc/api/JSONRPC_schema/schema-leddevice.json +++ b/libsrc/api/JSONRPC_schema/schema-leddevice.json @@ -13,7 +13,7 @@ "subcommand": { "type" : "string", "required" : true, - "enum" : ["discover","getProperties","identify"] + "enum": [ "discover", "getProperties", "identify", "addAuthorization" ] }, "ledDeviceType": { "type" : "string", diff --git a/libsrc/api/JSONRPC_schema/schema-service.json b/libsrc/api/JSONRPC_schema/schema-service.json new file mode 100644 index 00000000..cb07db2f --- /dev/null +++ b/libsrc/api/JSONRPC_schema/schema-service.json @@ -0,0 +1,28 @@ +{ + "type":"object", + "required":true, + "properties":{ + "command": { + "type" : "string", + "required" : true, + "enum" : ["service"] + }, + "tan" : { + "type" : "integer" + }, + "subcommand": { + "type" : "string", + "required" : true, + "enum" : ["discover"] + }, + "serviceType": { + "type" : "string", + "required" : true + }, + "params": { + "type" : "object", + "required" : false + } + }, + "additionalProperties": false +} diff --git a/libsrc/api/JSONRPC_schema/schema.json b/libsrc/api/JSONRPC_schema/schema.json index 12dcb6c1..2c78f5a8 100644 --- a/libsrc/api/JSONRPC_schema/schema.json +++ b/libsrc/api/JSONRPC_schema/schema.json @@ -5,7 +5,7 @@ "command": { "type" : "string", "required" : true, - "enum": [ "color", "image", "effect", "create-effect", "delete-effect", "serverinfo", "clear", "clearall", "adjustment", "sourceselect", "config", "componentstate", "ledcolors", "logging", "processing", "sysinfo", "videomode", "authorize", "instance", "leddevice", "inputsource", "transform", "correction", "temperature" ] + "enum": [ "color", "image", "effect", "create-effect", "delete-effect", "serverinfo", "clear", "clearall", "adjustment", "sourceselect", "config", "componentstate", "ledcolors", "logging", "processing", "sysinfo", "videomode", "authorize", "instance", "leddevice", "inputsource", "service", "transform", "correction", "temperature" ] } } } diff --git a/libsrc/api/JSONRPC_schemas.qrc b/libsrc/api/JSONRPC_schemas.qrc index 1f09fe1a..55f6a938 100644 --- a/libsrc/api/JSONRPC_schemas.qrc +++ b/libsrc/api/JSONRPC_schemas.qrc @@ -22,6 +22,7 @@ JSONRPC_schema/schema-instance.json JSONRPC_schema/schema-leddevice.json JSONRPC_schema/schema-inputsource.json + JSONRPC_schema/schema-service.json JSONRPC_schema/schema-hyperion-classic.json JSONRPC_schema/schema-hyperion-classic.json diff --git a/libsrc/api/JsonAPI.cpp b/libsrc/api/JsonAPI.cpp index 1f39f6f0..411e6398 100644 --- a/libsrc/api/JsonAPI.cpp +++ b/libsrc/api/JsonAPI.cpp @@ -63,11 +63,6 @@ #include #include -// bonjour wrapper -#ifdef ENABLE_AVAHI -#include -#endif - // ledmapping int <> string transform methods #include @@ -77,6 +72,15 @@ // auth manager #include +#ifdef ENABLE_MDNS +// mDNS discover +#include +#include +#else +// ssdp discover +#include +#endif + using namespace hyperion; // Constants @@ -98,8 +102,6 @@ void JsonAPI::initialize() { // init API, REQUIRED! API::init(); - // Initialise jsonCB with current instance - _jsonCB->setSubscriptionsTo(_hyperion); // setup auth interface connect(this, &API::onPendingTokenRequest, this, &JsonAPI::newPendingTokenRequest); @@ -112,7 +114,12 @@ void JsonAPI::initialize() connect(_jsonCB, &JsonCB::newCallback, this, &JsonAPI::callbackMessage); // notify hyperion about a jsonMessageForward - connect(this, &JsonAPI::forwardJsonMessage, _hyperion, &Hyperion::forwardJsonMessage); + if (_hyperion != nullptr) + { + // Initialise jsonCB with current instance + _jsonCB->setSubscriptionsTo(_hyperion); + connect(this, &JsonAPI::forwardJsonMessage, _hyperion, &Hyperion::forwardJsonMessage); + } } bool JsonAPI::handleInstanceSwitch(quint8 inst, bool forced) @@ -180,6 +187,12 @@ void JsonAPI::handleMessage(const QString &messageString, const QString &httpAut return; } proceed: + if (_hyperion == nullptr) + { + sendErrorReply("Service Unavailable", command, tan); + return; + } + // switch over all possible commands and handle them if (command == "color") handleColorCommand(message, command, tan); @@ -221,6 +234,8 @@ proceed: handleLedDeviceCommand(message, command, tan); else if (command == "inputsource") handleInputSourceCommand(message, command, tan); + else if (command == "service") + handleServiceCommand(message, command, tan); // BEGIN | The following commands are deprecated but used to ensure backward compatibility with hyperion Classic remote control else if (command == "clearall") @@ -627,6 +642,11 @@ void JsonAPI::handleServerInfoCommand(const QJsonObject &message, const QString 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"); @@ -649,24 +669,6 @@ void JsonAPI::handleServerInfoCommand(const QJsonObject &message, const QString info["components"] = component; info["imageToLedMappingType"] = ImageProcessor::mappingTypeToStr(_hyperion->getLedMappingType()); - // add sessions - QJsonArray sessions; -#ifdef ENABLE_AVAHI - for (auto session: BonjourBrowserWrapper::getInstance()->getAllServices()) - { - if (session.port < 0) - continue; - QJsonObject item; - item["name"] = session.serviceName; - item["type"] = session.registeredType; - item["domain"] = session.replyDomain; - item["host"] = session.hostName; - item["address"] = session.address; - item["port"] = session.port; - sessions.append(item); - } - info["sessions"] = sessions; -#endif // add instance info QJsonArray instanceInfo; for (const auto &entry : API::getAllInstanceData()) @@ -1571,6 +1573,16 @@ void JsonAPI::handleLedDeviceCommand(const QJsonObject &message, const QString & 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); @@ -1725,6 +1737,55 @@ void JsonAPI::handleInputSourceCommand(const QJsonObject& message, const QString } } +void JsonAPI::handleServiceCommand(const QJsonObject &message, const QString &command, int tan) +{ + 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") + { + QByteArray serviceType; + + QJsonObject servicesDiscovered; + QJsonObject servicesOfType; + QJsonArray serviceList; + +#ifdef ENABLE_MDNS + QString discoveryMethod("mDNS"); + serviceType = MdnsServiceRegister::getServiceType(type); +#else + QString discoveryMethod("ssdp"); +#endif + if (!serviceType.isEmpty()) + { +#ifdef ENABLE_MDNS + QMetaObject::invokeMethod(&MdnsBrowser::getInstance(), "browseForServiceType", + Qt::QueuedConnection, Q_ARG(QByteArray, serviceType)); + + serviceList = MdnsBrowser::getInstance().getServicesDiscoveredJson(serviceType, MdnsServiceRegister::getServiceNameFilter(type), DEFAULT_DISCOVER_TIMEOUT); +#endif + servicesOfType.insert(type, serviceList); + + servicesDiscovered.insert("discoveryMethod", discoveryMethod); + servicesDiscovered.insert("services", servicesOfType); + + sendSuccessDataReply(QJsonDocument(servicesDiscovered), full_command, tan); + } + else + { + sendErrorReply(QString("Discovery of service type [%1] via %2 not supported").arg(type, discoveryMethod), full_command, tan); + } + } + else + { + sendErrorReply("Unknown or missing subcommand", full_command, tan); + } +} + void JsonAPI::handleNotImplemented(const QString &command, int tan) { sendErrorReply("Command not implemented", command, tan); diff --git a/libsrc/api/JsonCB.cpp b/libsrc/api/JsonCB.cpp index 83be862f..23d1c7dc 100644 --- a/libsrc/api/JsonCB.cpp +++ b/libsrc/api/JsonCB.cpp @@ -9,10 +9,6 @@ // components #include -// bonjour wrapper -#ifdef ENABLE_AVAHI -#include -#endif // priorityMuxer #include @@ -33,12 +29,9 @@ JsonCB::JsonCB(QObject* parent) : QObject(parent) , _hyperion(nullptr) , _componentRegister(nullptr) - #ifdef ENABLE_AVAHI - , _bonjour(BonjourBrowserWrapper::getInstance()) - #endif , _prioMuxer(nullptr) { - _availableCommands << "components-update" << "sessions-update" << "priorities-update" << "imageToLedMapping-update" + _availableCommands << "components-update" << "priorities-update" << "imageToLedMapping-update" << "adjustment-update" << "videomode-update" << "settings-update" << "leds-update" << "instance-update" << "token-update"; #if defined(ENABLE_EFFECTENGINE) @@ -66,16 +59,6 @@ bool JsonCB::subscribeFor(const QString& type, bool unsubscribe) connect(_componentRegister, &ComponentRegister::updatedComponentState, this, &JsonCB::handleComponentState, Qt::UniqueConnection); } - if(type == "sessions-update") - { -#ifdef ENABLE_AVAHI - if(unsubscribe) - disconnect(_bonjour, &BonjourBrowserWrapper::browserChange, this, &JsonCB::handleBonjourChange); - else - connect(_bonjour, &BonjourBrowserWrapper::browserChange, this, &JsonCB::handleBonjourChange, Qt::UniqueConnection); -#endif - } - if(type == "priorities-update") { if (unsubscribe) @@ -208,26 +191,6 @@ void JsonCB::handleComponentState(hyperion::Components comp, bool state) doCallback("components-update", QVariant(data)); } -#ifdef ENABLE_AVAHI -void JsonCB::handleBonjourChange(const QMap& bRegisters) -{ - QJsonArray data; - for (const auto & session: bRegisters) - { - if (session.port<0) continue; - QJsonObject item; - item["name"] = session.serviceName; - item["type"] = session.registeredType; - item["domain"] = session.replyDomain; - item["host"] = session.hostName; - item["address"]= session.address; - item["port"] = session.port; - data.append(item); - } - - doCallback("sessions-update", QVariant(data)); -} -#endif void JsonCB::handlePriorityUpdate(int currentPriority, const PriorityMuxer::InputsMap& activeInputs) { diff --git a/libsrc/bonjour/CMakeLists.txt b/libsrc/bonjour/CMakeLists.txt deleted file mode 100644 index d569df69..00000000 --- a/libsrc/bonjour/CMakeLists.txt +++ /dev/null @@ -1,33 +0,0 @@ - -# Define the current source locations -set(CURRENT_HEADER_DIR ${CMAKE_SOURCE_DIR}/include/bonjour) -set(CURRENT_SOURCE_DIR ${CMAKE_SOURCE_DIR}/libsrc/bonjour) - -FILE ( GLOB Bonjour_SOURCES "${CURRENT_HEADER_DIR}/*.h" "${CURRENT_SOURCE_DIR}/*.h" "${CURRENT_SOURCE_DIR}/*.cpp" ) - -add_library(bonjour ${Bonjour_SOURCES} ) - -target_link_libraries(bonjour - hyperion - hyperion-utils - Qt${QT_VERSION_MAJOR}::Network -) - -IF (NOT APPLE) - set(USE_SHARED_AVAHI_LIBS ${DEFAULT_USE_SHARED_AVAHI_LIBS} CACHE BOOL "use avahi libraries from system") - - if (USE_SHARED_AVAHI_LIBS) - target_link_libraries(bonjour - dns_sd - avahi-client - avahi-common - avahi-core) - else() - target_link_libraries(bonjour - libdns_sd.a - libavahi-client.a - libavahi-common.a - libavahi-core.a) - endif() - target_link_libraries(bonjour dbus-1) -ENDIF() diff --git a/libsrc/bonjour/bonjourbrowserwrapper.cpp b/libsrc/bonjour/bonjourbrowserwrapper.cpp deleted file mode 100644 index df13b950..00000000 --- a/libsrc/bonjour/bonjourbrowserwrapper.cpp +++ /dev/null @@ -1,84 +0,0 @@ -#include - -//qt incl -#include - -// bonjour -#include -#include - -BonjourBrowserWrapper* BonjourBrowserWrapper::instance = nullptr; - -BonjourBrowserWrapper::BonjourBrowserWrapper(QObject * parent) - : QObject(parent) - , _bonjourResolver(new BonjourServiceResolver(this)) - , _timerBonjourResolver(new QTimer(this)) -{ - // register meta - qRegisterMetaType>("QMap"); - - BonjourBrowserWrapper::instance = this; - connect(_bonjourResolver, &BonjourServiceResolver::bonjourRecordResolved, this, &BonjourBrowserWrapper::bonjourRecordResolved); - - connect(_timerBonjourResolver, &QTimer::timeout, this, &BonjourBrowserWrapper::bonjourResolve); - _timerBonjourResolver->setInterval(1000); - _timerBonjourResolver->start(); - - // browse for _hyperiond-http._tcp - browseForServiceType(QLatin1String("_hyperiond-http._tcp")); -} - -bool BonjourBrowserWrapper::browseForServiceType(const QString &serviceType) -{ - if(!_browsedServices.contains(serviceType)) - { - BonjourServiceBrowser* newBrowser = new BonjourServiceBrowser(this); - connect(newBrowser, &BonjourServiceBrowser::currentBonjourRecordsChanged, this, &BonjourBrowserWrapper::currentBonjourRecordsChanged); - newBrowser->browseForServiceType(serviceType); - _browsedServices.insert(serviceType, newBrowser); - return true; - } - return false; -} - -void BonjourBrowserWrapper::currentBonjourRecordsChanged(const QList &list) -{ - _hyperionSessions.clear(); - for ( auto rec : list ) - { - _hyperionSessions.insert(rec.serviceName, rec); - } -} - -void BonjourBrowserWrapper::bonjourRecordResolved(const QHostInfo &hostInfo, int port) -{ - if ( _hyperionSessions.contains(_bonjourCurrentServiceToResolve)) - { - QString host = hostInfo.hostName(); - QString domain = _hyperionSessions[_bonjourCurrentServiceToResolve].replyDomain; - if (host.endsWith("."+domain)) - { - host.remove(host.length()-domain.length()-1,domain.length()+1); - } - _hyperionSessions[_bonjourCurrentServiceToResolve].hostName = host; - _hyperionSessions[_bonjourCurrentServiceToResolve].port = port; - _hyperionSessions[_bonjourCurrentServiceToResolve].address = hostInfo.addresses().isEmpty() ? "" : hostInfo.addresses().first().toString(); - //Debug(_log, "found hyperion session: %s:%d",QSTRING_CSTR(hostInfo.hostName()), port); - - //emit change - emit browserChange(_hyperionSessions); - } -} - -void BonjourBrowserWrapper::bonjourResolve() -{ - for(auto key : _hyperionSessions.keys()) - { - if (_hyperionSessions[key].port < 0) - { - _bonjourCurrentServiceToResolve = key; - _bonjourResolver->resolveBonjourRecord(_hyperionSessions[key]); - break; - } - } -} diff --git a/libsrc/bonjour/bonjourservicebrowser.cpp b/libsrc/bonjour/bonjourservicebrowser.cpp deleted file mode 100644 index a43346b0..00000000 --- a/libsrc/bonjour/bonjourservicebrowser.cpp +++ /dev/null @@ -1,109 +0,0 @@ -/* -Copyright (c) 2007, Trenton Schulz - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - - 3. The name of the author may not be used to endorse or promote products - derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED -WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO -EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; -OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR -OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF -ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "bonjour/bonjourservicebrowser.h" - -#include - -BonjourServiceBrowser::BonjourServiceBrowser(QObject *parent) - : QObject(parent) - , dnssref(0) - , bonjourSocket(0) -{ -} - -BonjourServiceBrowser::~BonjourServiceBrowser() -{ - if (dnssref) - { - DNSServiceRefDeallocate(dnssref); - dnssref = 0; - } -} - -void BonjourServiceBrowser::browseForServiceType(const QString &serviceType) -{ - DNSServiceErrorType err = DNSServiceBrowse(&dnssref, 0, 0, serviceType.toUtf8().constData(), 0, bonjourBrowseReply, this); - if (err != kDNSServiceErr_NoError) - { - emit error(err); - } - else - { - int sockfd = DNSServiceRefSockFD(dnssref); - if (sockfd == -1) - { - emit error(kDNSServiceErr_Invalid); - } - else - { - bonjourSocket = new QSocketNotifier(sockfd, QSocketNotifier::Read, this); - connect(bonjourSocket, &QSocketNotifier::activated, this, &BonjourServiceBrowser::bonjourSocketReadyRead); - } - } -} - -void BonjourServiceBrowser::bonjourSocketReadyRead() -{ - DNSServiceErrorType err = DNSServiceProcessResult(dnssref); - if (err != kDNSServiceErr_NoError) - { - emit error(err); - } -} - -void BonjourServiceBrowser::bonjourBrowseReply(DNSServiceRef , DNSServiceFlags flags, - quint32 , DNSServiceErrorType errorCode, - const char *serviceName, const char *regType, - const char *replyDomain, void *context) -{ - BonjourServiceBrowser *serviceBrowser = static_cast(context); - if (errorCode != kDNSServiceErr_NoError) - { - emit serviceBrowser->error(errorCode); - } - else - { - BonjourRecord bonjourRecord(serviceName, regType, replyDomain); - if ((flags & kDNSServiceFlagsAdd) != 0) - { - if (!serviceBrowser->bonjourRecords.contains(bonjourRecord)) - { - serviceBrowser->bonjourRecords.append(bonjourRecord); - } - } - else - { - serviceBrowser->bonjourRecords.removeAll(bonjourRecord); - } - if (!(flags & kDNSServiceFlagsMoreComing)) - { - emit serviceBrowser->currentBonjourRecordsChanged(serviceBrowser->bonjourRecords); - } - } -} diff --git a/libsrc/bonjour/bonjourserviceregister.cpp b/libsrc/bonjour/bonjourserviceregister.cpp deleted file mode 100644 index 3ff0b8be..00000000 --- a/libsrc/bonjour/bonjourserviceregister.cpp +++ /dev/null @@ -1,151 +0,0 @@ -/* -Copyright (c) 2007, Trenton Schulz - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - - 3. The name of the author may not be used to endorse or promote products - derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED -WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO -EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; -OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR -OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF -ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include -#include - -#include -#include - -#include -#include -#include - -BonjourServiceRegister::BonjourServiceRegister(QObject *parent) - : QObject(parent), dnssref(0), bonjourSocket(0) -{ - setenv("AVAHI_COMPAT_NOWARN", "1", 1); -} - -BonjourServiceRegister::~BonjourServiceRegister() -{ - if (dnssref) - { - DNSServiceRefDeallocate(dnssref); - dnssref = 0; - } -} - -void BonjourServiceRegister::registerService(const QString& service, int port) -{ - _port = port; - // zeroconf $configname@$hostname:port - // TODO add name of the main instance - registerService( - BonjourRecord(QHostInfo::localHostName()+ ":" + QString::number(port), - service, - QString() - ), - port - ); -} - -void BonjourServiceRegister::registerService(const BonjourRecord &record, quint16 servicePort, const std::vector>& txt) -{ - if (dnssref) - { - Warning(Logger::getInstance("BonJour"), "Already registered a service for this object, aborting new register"); - return; - } - quint16 bigEndianPort = servicePort; -#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN - { - bigEndianPort = 0 | ((servicePort & 0x00ff) << 8) | ((servicePort & 0xff00) >> 8); - } -#endif - // base txtRec - std::vector > txtBase = {{"id",AuthManager::getInstance()->getID().toStdString()},{"version",HYPERION_VERSION}}; - // create txt record - TXTRecordRef txtRec; - TXTRecordCreate(&txtRec,0,NULL); - - if(!txt.empty()) - { - txtBase.insert(txtBase.end(), txt.begin(), txt.end()); - } - // add txt records - for(std::vector >::const_iterator it = txtBase.begin(); it != txtBase.end(); ++it) - { - //Debug(Logger::getInstance("BonJour"), "TXTRecord: key:%s, value:%s",it->first.c_str(),it->second.c_str()); - uint8_t txtLen = (uint8_t)strlen(it->second.c_str()); - TXTRecordSetValue(&txtRec, it->first.c_str(), txtLen, it->second.c_str()); - } - - - DNSServiceErrorType err = DNSServiceRegister(&dnssref, 0, 0, record.serviceName.toUtf8().constData(), - record.registeredType.toUtf8().constData(), - (record.replyDomain.isEmpty() ? 0 : record.replyDomain.toUtf8().constData()), - 0, bigEndianPort, TXTRecordGetLength(&txtRec), TXTRecordGetBytesPtr(&txtRec), bonjourRegisterService, this); - if (err != kDNSServiceErr_NoError) - { - emit error(err); - } - else - { - int sockfd = DNSServiceRefSockFD(dnssref); - if (sockfd == -1) - { - emit error(kDNSServiceErr_Invalid); - } - else - { - bonjourSocket = new QSocketNotifier(sockfd, QSocketNotifier::Read, this); - connect(bonjourSocket, &QSocketNotifier::activated, this, &BonjourServiceRegister::bonjourSocketReadyRead); - } - } - - TXTRecordDeallocate(&txtRec); -} - - -void BonjourServiceRegister::bonjourSocketReadyRead() -{ - DNSServiceErrorType err = DNSServiceProcessResult(dnssref); - if (err != kDNSServiceErr_NoError) - emit error(err); -} - - -void BonjourServiceRegister::bonjourRegisterService(DNSServiceRef, DNSServiceFlags, - DNSServiceErrorType errorCode, const char *name, - const char *regtype, const char *domain, - void *data) -{ - BonjourServiceRegister *serviceRegister = static_cast(data); - if (errorCode != kDNSServiceErr_NoError) - { - emit serviceRegister->error(errorCode); - } - else - { - serviceRegister->finalRecord = BonjourRecord(QString::fromUtf8(name), - QString::fromUtf8(regtype), - QString::fromUtf8(domain)); - emit serviceRegister->serviceRegistered(serviceRegister->finalRecord); - } -} diff --git a/libsrc/bonjour/bonjourserviceresolver.cpp b/libsrc/bonjour/bonjourserviceresolver.cpp deleted file mode 100644 index 14cebcb1..00000000 --- a/libsrc/bonjour/bonjourserviceresolver.cpp +++ /dev/null @@ -1,122 +0,0 @@ -/* -Copyright (c) 2007, Trenton Schulz - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - - 3. The name of the author may not be used to endorse or promote products - derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED -WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO -EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; -OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR -OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF -ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include -#include - -#include "bonjour/bonjourrecord.h" -#include "bonjour/bonjourserviceresolver.h" - -BonjourServiceResolver::BonjourServiceResolver(QObject *parent) - : QObject(parent) - , dnssref(0) - , bonjourSocket(0) - , bonjourPort(-1) -{ -} - -BonjourServiceResolver::~BonjourServiceResolver() -{ - cleanupResolve(); -} - -void BonjourServiceResolver::cleanupResolve() -{ - if (dnssref) - { - DNSServiceRefDeallocate(dnssref); - dnssref = 0; - delete bonjourSocket; - bonjourPort = -1; - } -} - -bool BonjourServiceResolver::resolveBonjourRecord(const BonjourRecord &record) -{ - if (dnssref) - { - //qWarning("resolve in process, aborting"); - return false; - } - DNSServiceErrorType err = DNSServiceResolve(&dnssref, 0, 0, - record.serviceName.toUtf8().constData(), - record.registeredType.toUtf8().constData(), - record.replyDomain.toUtf8().constData(), - (DNSServiceResolveReply)bonjourResolveReply, this); - if (err != kDNSServiceErr_NoError) - { - emit error(err); - } - else - { - int sockfd = DNSServiceRefSockFD(dnssref); - if (sockfd == -1) - { - emit error(kDNSServiceErr_Invalid); - } - else - { - bonjourSocket = new QSocketNotifier(sockfd, QSocketNotifier::Read, this); - connect(bonjourSocket, &QSocketNotifier::activated, this, &BonjourServiceResolver::bonjourSocketReadyRead); - } - } - return true; -} - -void BonjourServiceResolver::bonjourSocketReadyRead() -{ - DNSServiceErrorType err = DNSServiceProcessResult(dnssref); - if (err != kDNSServiceErr_NoError) - emit error(err); -} - -void BonjourServiceResolver::bonjourResolveReply(DNSServiceRef sdRef, DNSServiceFlags , - quint32 , DNSServiceErrorType errorCode, - const char *, const char *hosttarget, quint16 port, - quint16 , const char *, void *context) -{ - BonjourServiceResolver *serviceResolver = static_cast(context); - if (errorCode != kDNSServiceErr_NoError) { - emit serviceResolver->error(errorCode); - return; - } -#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN - { - port = 0 | ((port & 0x00ff) << 8) | ((port & 0xff00) >> 8); - } -#endif - serviceResolver->bonjourPort = port; - - QHostInfo::lookupHost(QString::fromUtf8(hosttarget), serviceResolver, SLOT(finishConnect(const QHostInfo &))); -} - -void BonjourServiceResolver::finishConnect(const QHostInfo &hostInfo) -{ - emit bonjourRecordResolved(hostInfo, bonjourPort); - QMetaObject::invokeMethod(this, "cleanupResolve", Qt::QueuedConnection); -} diff --git a/libsrc/cec/CECHandler.cpp b/libsrc/cec/CECHandler.cpp index 3f717364..f33c30c8 100644 --- a/libsrc/cec/CECHandler.cpp +++ b/libsrc/cec/CECHandler.cpp @@ -8,6 +8,8 @@ #include #include #include +#include +#include /* Enable to turn on detailed CEC logs */ // #define VERBOSE_CEC @@ -34,15 +36,16 @@ bool CECHandler::start() if (_cecAdapter) return true; - Info(_logger, "Starting CEC handler"); - - _cecAdapter = LibCecInitialise(&_cecConfig); + std::string library = std::string("" CEC_LIBRARY); + _cecAdapter = LibCecInitialise(&_cecConfig, QFile::exists(QString::fromStdString(library)) ? library.c_str() : nullptr); if(!_cecAdapter) { - Error(_logger, "Failed loading libcec.so"); + Error(_logger, "Failed to loading libcec.so"); return false; } + Info(_logger, "CEC handler started"); + auto adapters = getAdapters(); if (adapters.isEmpty()) { diff --git a/libsrc/cec/CMakeLists.txt b/libsrc/cec/CMakeLists.txt index f99d19b7..00a1497b 100644 --- a/libsrc/cec/CMakeLists.txt +++ b/libsrc/cec/CMakeLists.txt @@ -5,13 +5,14 @@ SET(CURRENT_HEADER_DIR ${CMAKE_SOURCE_DIR}/include/cec) SET(CURRENT_SOURCE_DIR ${CMAKE_SOURCE_DIR}/libsrc/cec) FILE (GLOB CEC_SOURCES "${CURRENT_HEADER_DIR}/*.h" "${CURRENT_SOURCE_DIR}/*.h" "${CURRENT_SOURCE_DIR}/*.cpp") + add_library(cechandler ${CEC_SOURCES}) +add_definitions(-DCEC_LIBRARY="${CEC_LIBRARIES}") include_directories(${CEC_INCLUDE_DIRS}) target_link_libraries(cechandler - dl - ${CEC_LIBRARIES} Qt${QT_VERSION_MAJOR}::Core + ${CMAKE_DL_LIBS} ) diff --git a/libsrc/flatbufserver/CMakeLists.txt b/libsrc/flatbufserver/CMakeLists.txt index 614208d5..836652f8 100644 --- a/libsrc/flatbufserver/CMakeLists.txt +++ b/libsrc/flatbufserver/CMakeLists.txt @@ -60,5 +60,10 @@ flatbuffers Qt${QT_VERSION_MAJOR}::Network Qt${QT_VERSION_MAJOR}::Core ) + +if(ENABLE_MDNS) + target_link_libraries(flatbufserver mdns) +endif() + endif() diff --git a/libsrc/flatbufserver/FlatBufferConnection.cpp b/libsrc/flatbufserver/FlatBufferConnection.cpp index 53cb178d..c33889de 100644 --- a/libsrc/flatbufserver/FlatBufferConnection.cpp +++ b/libsrc/flatbufserver/FlatBufferConnection.cpp @@ -37,6 +37,7 @@ FlatBufferConnection::FlatBufferConnection(const QString& origin, const QString& FlatBufferConnection::~FlatBufferConnection() { + Debug(_log, "Closing connection to: %s:%u", QSTRING_CSTR(_host), _port); _timer.stop(); _socket.close(); } diff --git a/libsrc/flatbufserver/FlatBufferServer.cpp b/libsrc/flatbufserver/FlatBufferServer.cpp index d18e8271..75caa3b7 100644 --- a/libsrc/flatbufserver/FlatBufferServer.cpp +++ b/libsrc/flatbufserver/FlatBufferServer.cpp @@ -6,16 +6,18 @@ #include #include -// bonjour -#ifdef ENABLE_AVAHI -#include -#endif - // qt #include #include #include +// Constants +namespace { + +const char SERVICE_TYPE[] = "flatbuffer"; + +} //End of constants + FlatBufferServer::FlatBufferServer(const QJsonDocument& config, QObject* parent) : QObject(parent) , _server(new QTcpServer(this)) @@ -106,19 +108,8 @@ void FlatBufferServer::startServer() else { Info(_log,"Started on port %d", _port); -#ifdef ENABLE_AVAHI - if(_serviceRegister == nullptr) - { - _serviceRegister = new BonjourServiceRegister(this); - _serviceRegister->registerService("_hyperiond-flatbuf._tcp", _port); - } - else if(_serviceRegister->getPort() != _port) - { - delete _serviceRegister; - _serviceRegister = new BonjourServiceRegister(this); - _serviceRegister->registerService("_hyperiond-flatbuf._tcp", _port); - } -#endif + + emit publishService(SERVICE_TYPE, _port); } } } diff --git a/libsrc/forwarder/MessageForwarder.cpp b/libsrc/forwarder/MessageForwarder.cpp index 8a12fb0a..f2bc0859 100644 --- a/libsrc/forwarder/MessageForwarder.cpp +++ b/libsrc/forwarder/MessageForwarder.cpp @@ -1,5 +1,5 @@ // STL includes -#include +#include // project includes #include @@ -12,23 +12,46 @@ #include // qt includes -#include #include #include #include +#include #include +// mDNS discover +#ifdef ENABLE_MDNS +#include +#include +#endif + +// Constants +namespace { + +const int DEFAULT_FORWARDER_FLATBUFFFER_PRIORITY = 140; + +constexpr std::chrono::milliseconds CONNECT_TIMEOUT{500}; // JSON-socket connect timeout in ms + +} //End of constants + MessageForwarder::MessageForwarder(Hyperion* hyperion) : _hyperion(hyperion) - , _log(nullptr) - , _muxer(_hyperion->getMuxerInstance()) - , _forwarder_enabled(true) - , _priority(140) + , _log(nullptr) + , _muxer(_hyperion->getMuxerInstance()) + , _forwarder_enabled(false) + , _priority(DEFAULT_FORWARDER_FLATBUFFFER_PRIORITY) + , _messageForwarderFlatBufHelper(nullptr) { QString subComponent = hyperion->property("instance").toString(); _log= Logger::getInstance("NETFORWARDER", subComponent); + qRegisterMetaType("TargetHost"); + +#ifdef ENABLE_MDNS + QMetaObject::invokeMethod(&MdnsBrowser::getInstance(), "browseForServiceType", + Qt::QueuedConnection, Q_ARG(QByteArray, MdnsServiceRegister::getServiceType("jsonapi"))); +#endif + // get settings updates connect(_hyperion, &Hyperion::settingsChanged, this, &MessageForwarder::handleSettingsUpdate); @@ -37,82 +60,23 @@ MessageForwarder::MessageForwarder(Hyperion* hyperion) // connect with Muxer visible priority changes connect(_muxer, &PriorityMuxer::visiblePriorityChanged, this, &MessageForwarder::handlePriorityChanges); - - // init - handleSettingsUpdate(settings::NETFORWARD, _hyperion->getSetting(settings::NETFORWARD)); } MessageForwarder::~MessageForwarder() { - while (!_forwardClients.isEmpty()) - { - delete _forwardClients.takeFirst(); - } + stopJsonTargets(); + stopFlatbufferTargets(); } + void MessageForwarder::handleSettingsUpdate(settings::type type, const QJsonDocument& config) { if (type == settings::NETFORWARD) { - // clear the current targets - _jsonTargets.clear(); - _flatbufferTargets.clear(); - while (!_forwardClients.isEmpty()) - { - delete _forwardClients.takeFirst(); - } - - // build new one const QJsonObject& obj = config.object(); - if (!obj["json"].isNull()) - { - const QJsonArray& addr = obj["json"].toArray(); - for (const auto& entry : addr) - { - addJsonTarget(entry.toObject()); - } - } - - if (!obj["flat"].isNull()) - { - const QJsonArray& addr = obj["flat"].toArray(); - for (const auto& entry : addr) - { - addFlatbufferTarget(entry.toObject()); - } - } bool isForwarderEnabledinSettings = obj["enable"].toBool(false); - - if (!_jsonTargets.isEmpty() && isForwarderEnabledinSettings && _forwarder_enabled) - { - for (const auto& targetHost : qAsConst(_jsonTargets)) - { - InfoIf(isForwarderEnabledinSettings, _log, "Forwarding now to JSON-target host: %s port: %u", QSTRING_CSTR(targetHost.host.toString()), targetHost.port); - } - - connect(_hyperion, &Hyperion::forwardJsonMessage, this, &MessageForwarder::forwardJsonMessage, Qt::UniqueConnection); - } - else if (_jsonTargets.isEmpty() || !isForwarderEnabledinSettings || !_forwarder_enabled) - { - disconnect(_hyperion, &Hyperion::forwardJsonMessage, nullptr, nullptr); - } - - if (!_flatbufferTargets.isEmpty() && isForwarderEnabledinSettings && _forwarder_enabled) - { - for (const auto& targetHost : qAsConst(_flatbufferTargets)) - { - InfoIf(isForwarderEnabledinSettings, _log, "Forwarding now to Flatbuffer-target host: %s port: %u", QSTRING_CSTR(targetHost.host.toString()), targetHost.port); - } - } - else if (_flatbufferTargets.isEmpty() || !isForwarderEnabledinSettings || !_forwarder_enabled) - { - disconnect(_hyperion, &Hyperion::forwardSystemProtoMessage, nullptr, nullptr); - disconnect(_hyperion, &Hyperion::forwardV4lProtoMessage, nullptr, nullptr); - } - - // update comp state - _hyperion->setNewComponentState(hyperion::COMP_FORWARDER, isForwarderEnabledinSettings); + enableTargets(isForwarderEnabledinSettings, obj); } } @@ -120,30 +84,59 @@ void MessageForwarder::handleCompStateChangeRequest(hyperion::Components compone { if (component == hyperion::COMP_FORWARDER && _forwarder_enabled != enable) { - _forwarder_enabled = enable; - handleSettingsUpdate(settings::NETFORWARD, _hyperion->getSetting(settings::NETFORWARD)); - Info(_log, "Forwarder change state to %s", (_forwarder_enabled ? "enabled" : "disabled")); - _hyperion->setNewComponentState(component, _forwarder_enabled); + Info(_log, "Forwarder is %s", (enable ? "enabled" : "disabled")); + QJsonDocument config {_hyperion->getSetting(settings::type::NETFORWARD)}; + enableTargets(enable, config.object()); } } +void MessageForwarder::enableTargets(bool enable, const QJsonObject& config) +{ + if (!enable) + { + _forwarder_enabled = false; + stopJsonTargets(); + stopFlatbufferTargets(); + + } + else + { + int jsonTargetNum = startJsonTargets(config); + int flatbufTargetNum = startFlatbufferTargets(config); + + if (flatbufTargetNum > 0) + { + hyperion::Components activeCompId = _hyperion->getPriorityInfo(_hyperion->getCurrentPriority()).componentId; + if (activeCompId == hyperion::COMP_GRABBER) + { + connect(_hyperion, &Hyperion::forwardSystemProtoMessage, this, &MessageForwarder::forwardFlatbufferMessage, Qt::UniqueConnection); + } + else if (activeCompId == hyperion::COMP_V4L) + { + connect(_hyperion, &Hyperion::forwardV4lProtoMessage, this, &MessageForwarder::forwardFlatbufferMessage, Qt::UniqueConnection); + } + } + + if (jsonTargetNum > 0 || flatbufTargetNum > 0) + { + _forwarder_enabled = true; + } + else + { + _forwarder_enabled = false; + Warning(_log,"No JSON- nor Flatbuffer-Forwarder configured -> Forwarding disabled", _forwarder_enabled); + } + } + _hyperion->setNewComponentState(hyperion::COMP_FORWARDER, _forwarder_enabled); +} + void MessageForwarder::handlePriorityChanges(int priority) { - const QJsonObject obj = _hyperion->getSetting(settings::NETFORWARD).object(); - if (priority != 0 && _forwarder_enabled && obj["enable"].toBool()) + if (priority != 0 && _forwarder_enabled) { hyperion::Components activeCompId = _hyperion->getPriorityInfo(priority).componentId; if (activeCompId == hyperion::COMP_GRABBER || activeCompId == hyperion::COMP_V4L) { - if (!obj["flat"].isNull()) - { - const QJsonArray& addr = obj["flat"].toArray(); - for (const auto& entry : addr) - { - addFlatbufferTarget(entry.toObject()); - } - } - switch (activeCompId) { case hyperion::COMP_GRABBER: @@ -177,69 +170,131 @@ void MessageForwarder::addJsonTarget(const QJsonObject& targetConfig) { TargetHost targetHost; - QString config_host = targetConfig["host"].toString(); - if (NetUtils::resolveHostAddress(_log, config_host, targetHost.host)) - { - int config_port = targetConfig["port"].toInt(); - if (NetUtils::isValidPort(_log, config_port, config_host)) - { - targetHost.port = static_cast(config_port); + QString hostName = targetConfig["host"].toString(); + int port = targetConfig["port"].toInt(); - // verify loop with JSON-server - const QJsonObject& obj = _hyperion->getSetting(settings::JSONSERVER).object(); - if ((QNetworkInterface::allAddresses().indexOf(targetHost.host) != -1) && targetHost.port == static_cast(obj["port"].toInt())) + if (!hostName.isEmpty()) + { + if (NetUtils::resolveHostToAddress(_log, hostName, targetHost.host, port)) + { + QString address = targetHost.host.toString(); + if (hostName != address) { - Error(_log, "Loop between JSON-Server and Forwarder! Configuration for host: %s, port: %d is ignored.", QSTRING_CSTR(config_host), config_port); + Info(_log, "Resolved hostname [%s] to address [%s]", QSTRING_CSTR(hostName), QSTRING_CSTR(address)); } - else + + if (NetUtils::isValidPort(_log, port, targetHost.host.toString())) { - if (_forwarder_enabled) + targetHost.port = static_cast(port); + + // verify loop with JSON-server + const QJsonObject& obj = _hyperion->getSetting(settings::JSONSERVER).object(); + if ((QNetworkInterface::allAddresses().indexOf(targetHost.host) != -1) && targetHost.port == static_cast(obj["port"].toInt())) + { + Error(_log, "Loop between JSON-Server and Forwarder! Configuration for host: %s, port: %d is ignored.", QSTRING_CSTR(targetHost.host.toString()), port); + } + else { if (_jsonTargets.indexOf(targetHost) == -1) { - Info(_log, "JSON-Forwarder settings: Adding target host: %s port: %u", QSTRING_CSTR(targetHost.host.toString()), targetHost.port); + Debug(_log, "JSON-Forwarder settings: Adding target host: %s port: %u", QSTRING_CSTR(targetHost.host.toString()), targetHost.port); _jsonTargets << targetHost; } else { - Warning(_log, "JSON Forwarder settings: Duplicate target host configuration! Configuration for host: %s, port: %d is ignored.", QSTRING_CSTR(targetHost.host.toString()), targetHost.port); + Warning(_log, "JSON-Forwarder settings: Duplicate target host configuration! Configuration for host: %s, port: %d is ignored.", QSTRING_CSTR(targetHost.host.toString()), targetHost.port); } } - } } } } +int MessageForwarder::startJsonTargets(const QJsonObject& config) +{ + if (!config["jsonapi"].isNull()) + { + _jsonTargets.clear(); + const QJsonArray& addr = config["jsonapi"].toArray(); + +#ifdef ENABLE_MDNS + if (!addr.isEmpty()) + { + QMetaObject::invokeMethod(&MdnsBrowser::getInstance(), "browseForServiceType", + Qt::QueuedConnection, Q_ARG(QByteArray, MdnsServiceRegister::getServiceType("jsonapi"))); + } +#endif + + for (const auto& entry : addr) + { + addJsonTarget(entry.toObject()); + } + + if (!_jsonTargets.isEmpty()) + { + for (const auto& targetHost : qAsConst(_jsonTargets)) + { + Info(_log, "Forwarding now to JSON-target host: %s port: %u", QSTRING_CSTR(targetHost.host.toString()), targetHost.port); + } + + connect(_hyperion, &Hyperion::forwardJsonMessage, this, &MessageForwarder::forwardJsonMessage, Qt::UniqueConnection); + } + } + return _jsonTargets.size(); +} + + +void MessageForwarder::stopJsonTargets() +{ + if (!_jsonTargets.isEmpty()) + { + disconnect(_hyperion, &Hyperion::forwardJsonMessage, nullptr, nullptr); + for (const auto& targetHost : qAsConst(_jsonTargets)) + { + Info(_log, "Stopped forwarding to JSON-target host: %s port: %u", QSTRING_CSTR(targetHost.host.toString()), targetHost.port); + } + _jsonTargets.clear(); + } +} + void MessageForwarder::addFlatbufferTarget(const QJsonObject& targetConfig) { TargetHost targetHost; - QString config_host = targetConfig["host"].toString(); - if (NetUtils::resolveHostAddress(_log, config_host, targetHost.host)) - { - int config_port = targetConfig["port"].toInt(); - if (NetUtils::isValidPort(_log, config_port, config_host)) - { - targetHost.port = static_cast(config_port); + QString hostName = targetConfig["host"].toString(); + int port = targetConfig["port"].toInt(); - // verify loop with Flatbuffer-server - const QJsonObject& obj = _hyperion->getSetting(settings::FLATBUFSERVER).object(); - if ((QNetworkInterface::allAddresses().indexOf(targetHost.host) != -1) && targetHost.port == static_cast(obj["port"].toInt())) + if (!hostName.isEmpty()) + { + if (NetUtils::resolveHostToAddress(_log, hostName, targetHost.host, port)) + { + QString address = targetHost.host.toString(); + if (hostName != address) { - Error(_log, "Loop between Flatbuffer-Server and Forwarder! Configuration for host: %s, port: %d is ignored.", QSTRING_CSTR(config_host), config_port); + Info(_log, "Resolved hostname [%s] to address [%s]", QSTRING_CSTR(hostName), QSTRING_CSTR(address)); } - else + + if (NetUtils::isValidPort(_log, port, targetHost.host.toString())) { - if (_forwarder_enabled) + targetHost.port = static_cast(port); + + // verify loop with Flatbuffer-server + const QJsonObject& obj = _hyperion->getSetting(settings::FLATBUFSERVER).object(); + if ((QNetworkInterface::allAddresses().indexOf(targetHost.host) != -1) && targetHost.port == static_cast(obj["port"].toInt())) + { + Error(_log, "Loop between Flatbuffer-Server and Forwarder! Configuration for host: %s, port: %d is ignored.", QSTRING_CSTR(targetHost.host.toString()), port); + } + else { if (_flatbufferTargets.indexOf(targetHost) == -1) { - Info(_log, "Flatbuffer-Forwarder settings: Adding target host: %s port: %u", QSTRING_CSTR(targetHost.host.toString()), targetHost.port); + Debug(_log, "Flatbuffer-Forwarder settings: Adding target host: %s port: %u", QSTRING_CSTR(targetHost.host.toString()), targetHost.port); _flatbufferTargets << targetHost; - FlatBufferConnection* flatbuf = new FlatBufferConnection("Forwarder", targetHost.host.toString(), _priority, false, targetHost.port); - _forwardClients << flatbuf; + if (_messageForwarderFlatBufHelper != nullptr) + { + emit _messageForwarderFlatBufHelper->addClient("Forwarder", targetHost, _priority, false); + } } else { @@ -251,6 +306,66 @@ void MessageForwarder::addFlatbufferTarget(const QJsonObject& targetConfig) } } +int MessageForwarder::startFlatbufferTargets(const QJsonObject& config) +{ + if (!config["flatbuffer"].isNull()) + { + if (_messageForwarderFlatBufHelper == nullptr) + { + _messageForwarderFlatBufHelper = new MessageForwarderFlatbufferClientsHelper(); + } + else + { + emit _messageForwarderFlatBufHelper->clearClients(); + } + _flatbufferTargets.clear(); + + const QJsonArray& addr = config["flatbuffer"].toArray(); + +#ifdef ENABLE_MDNS + if (!addr.isEmpty()) + { + QMetaObject::invokeMethod(&MdnsBrowser::getInstance(), "browseForServiceType", + Qt::QueuedConnection, Q_ARG(QByteArray, MdnsServiceRegister::getServiceType("flatbuffer"))); + } +#endif + for (const auto& entry : addr) + { + addFlatbufferTarget(entry.toObject()); + } + + if (!_flatbufferTargets.isEmpty()) + { + for (const auto& targetHost : qAsConst(_flatbufferTargets)) + { + Info(_log, "Forwarding now to Flatbuffer-target host: %s port: %u", QSTRING_CSTR(targetHost.host.toString()), targetHost.port); + } + } + } + return _flatbufferTargets.size(); +} + +void MessageForwarder::stopFlatbufferTargets() +{ + if (!_flatbufferTargets.isEmpty()) + { + disconnect(_hyperion, &Hyperion::forwardSystemProtoMessage, nullptr, nullptr); + disconnect(_hyperion, &Hyperion::forwardV4lProtoMessage, nullptr, nullptr); + + if (_messageForwarderFlatBufHelper != nullptr) + { + delete _messageForwarderFlatBufHelper; + _messageForwarderFlatBufHelper = nullptr; + } + + for (const auto& targetHost : qAsConst(_flatbufferTargets)) + { + Info(_log, "Stopped forwarding to Flatbuffer-target host: %s port: %u", QSTRING_CSTR(targetHost.host.toString()), targetHost.port); + } + _flatbufferTargets.clear(); + } +} + void MessageForwarder::forwardJsonMessage(const QJsonObject& message) { if (_forwarder_enabled) @@ -259,7 +374,7 @@ void MessageForwarder::forwardJsonMessage(const QJsonObject& message) for (const auto& targetHost : qAsConst(_jsonTargets)) { client.connectToHost(targetHost.host, targetHost.port); - if (client.waitForConnected(500)) + if (client.waitForConnected(CONNECT_TIMEOUT.count())) { sendJsonMessage(message, &client); client.close(); @@ -270,11 +385,13 @@ void MessageForwarder::forwardJsonMessage(const QJsonObject& message) void MessageForwarder::forwardFlatbufferMessage(const QString& /*name*/, const Image& image) { - if (_forwarder_enabled) + if (_messageForwarderFlatBufHelper != nullptr) { - for (int i = 0; i < _forwardClients.size(); i++) + bool isfree = _messageForwarderFlatBufHelper->isFree(); + + if (isfree && _forwarder_enabled) { - _forwardClients.at(i)->setImage(image); + QMetaObject::invokeMethod(_messageForwarderFlatBufHelper, "forwardImage", Qt::QueuedConnection, Q_ARG(Image, image)); } } } @@ -316,12 +433,72 @@ void MessageForwarder::sendJsonMessage(const QJsonObject& message, QTcpSocket* s // parse reply data QJsonParseError error; - QJsonDocument::fromJson(serializedReply, &error); + /* QJsonDocument reply = */ QJsonDocument::fromJson(serializedReply, &error); if (error.error != QJsonParseError::NoError) { - Error(_log, "Error while parsing reply: invalid json"); + Error(_log, "Error while parsing reply: invalid JSON"); return; } } +MessageForwarderFlatbufferClientsHelper::MessageForwarderFlatbufferClientsHelper() +{ + QThread* mainThread = new QThread(); + mainThread->setObjectName("ForwarderHelperThread"); + this->moveToThread(mainThread); + mainThread->start(); + + _free = true; + connect(this, &MessageForwarderFlatbufferClientsHelper::addClient, this, &MessageForwarderFlatbufferClientsHelper::addClientHandler); + connect(this, &MessageForwarderFlatbufferClientsHelper::clearClients, this, &MessageForwarderFlatbufferClientsHelper::clearClientsHandler); +} + +MessageForwarderFlatbufferClientsHelper::~MessageForwarderFlatbufferClientsHelper() +{ + _free=false; + while (!_forwardClients.isEmpty()) + { + _forwardClients.takeFirst()->deleteLater(); + } + + + QThread* oldThread = this->thread(); + disconnect(oldThread, nullptr, nullptr, nullptr); + oldThread->quit(); + oldThread->wait(); + delete oldThread; +} + +void MessageForwarderFlatbufferClientsHelper::addClientHandler(const QString& origin, const TargetHost& targetHost, int priority, bool skipReply) +{ + FlatBufferConnection* flatbuf = new FlatBufferConnection(origin, targetHost.host.toString(), priority, skipReply, targetHost.port); + _forwardClients << flatbuf; + _free = true; +} + +void MessageForwarderFlatbufferClientsHelper::clearClientsHandler() +{ + while (!_forwardClients.isEmpty()) + { + delete _forwardClients.takeFirst(); + } + _free = false; +} + +bool MessageForwarderFlatbufferClientsHelper::isFree() const +{ + return _free; +} + +void MessageForwarderFlatbufferClientsHelper::forwardImage(const Image& image) +{ + _free = false; + + for (int i = 0; i < _forwardClients.size(); i++) + { + _forwardClients.at(i)->setImage(image); + } + + _free = true; +} diff --git a/libsrc/hyperion/CMakeLists.txt b/libsrc/hyperion/CMakeLists.txt index 538d2278..3a880a91 100644 --- a/libsrc/hyperion/CMakeLists.txt +++ b/libsrc/hyperion/CMakeLists.txt @@ -41,7 +41,3 @@ endif() if(ENABLE_FORWARDER) target_link_libraries(hyperion forwarder) endif() - -if (ENABLE_AVAHI) - target_link_libraries(hyperion bonjour) -endif () diff --git a/libsrc/hyperion/Hyperion.cpp b/libsrc/hyperion/Hyperion.cpp index e9022344..a289f361 100644 --- a/libsrc/hyperion/Hyperion.cpp +++ b/libsrc/hyperion/Hyperion.cpp @@ -149,6 +149,7 @@ void Hyperion::start() if (_instIndex == 0) { _messageForwarder = new MessageForwarder(this); + _messageForwarder->handleSettingsUpdate(settings::NETFORWARD, getSetting(settings::NETFORWARD)); } #endif @@ -698,7 +699,6 @@ void Hyperion::update() // Smoothing is disabled if (! _deviceSmooth->enabled()) { - //std::cout << "Hyperion::update()> Non-Smoothing - "; LedDevice::printLedValues ( _ledBuffer); emit ledDeviceData(_ledBuffer); } else @@ -710,11 +710,4 @@ void Hyperion::update() } } } - #if 0 - else - { - //LEDDevice is disabled - Debug(_log, "LEDDevice is disabled - no update required"); - } - #endif } diff --git a/libsrc/hyperion/HyperionIManager.cpp b/libsrc/hyperion/HyperionIManager.cpp index ec1ffe65..dc905253 100644 --- a/libsrc/hyperion/HyperionIManager.cpp +++ b/libsrc/hyperion/HyperionIManager.cpp @@ -22,11 +22,16 @@ HyperionIManager::HyperionIManager(const QString& rootPath, QObject* parent, boo Hyperion* HyperionIManager::getHyperionInstance(quint8 instance) { + Hyperion* pInstance {nullptr}; if(_runningInstances.contains(instance)) return _runningInstances.value(instance); - Warning(_log,"The requested instance index '%d' with name '%s' isn't running, return main instance", instance, QSTRING_CSTR(_instanceTable->getNamebyIndex(instance))); - return _runningInstances.value(0); + if (!_runningInstances.isEmpty()) + { + Warning(_log,"The requested instance index '%d' with name '%s' isn't running, return main instance", instance, QSTRING_CSTR(_instanceTable->getNamebyIndex(instance))); + pInstance = _runningInstances.value(0); + } + return pInstance; } QVector HyperionIManager::getInstanceData() const diff --git a/libsrc/hyperion/PriorityMuxer.cpp b/libsrc/hyperion/PriorityMuxer.cpp index 48fc54f5..f091980b 100644 --- a/libsrc/hyperion/PriorityMuxer.cpp +++ b/libsrc/hyperion/PriorityMuxer.cpp @@ -22,15 +22,15 @@ const int PriorityMuxer::ENDLESS = -1; PriorityMuxer::PriorityMuxer(int ledCount, QObject * parent) : QObject(parent) - , _log(nullptr) - , _currentPriority(PriorityMuxer::LOWEST_PRIORITY) - , _previousPriority(_currentPriority) - , _manualSelectedPriority(MANUAL_SELECTED_PRIORITY) - , _prevVisComp (hyperion::Components::COMP_COLOR) - , _sourceAutoSelectEnabled(true) - , _updateTimer(new QTimer(this)) - , _timer(new QTimer(this)) - , _blockTimer(new QTimer(this)) + , _log(nullptr) + , _currentPriority(PriorityMuxer::LOWEST_PRIORITY) + , _previousPriority(_currentPriority) + , _manualSelectedPriority(MANUAL_SELECTED_PRIORITY) + , _prevVisComp (hyperion::Components::COMP_COLOR) + , _sourceAutoSelectEnabled(true) + , _updateTimer(new QTimer(this)) + , _timer(new QTimer(this)) + , _blockTimer(new QTimer(this)) { QString subComponent = parent->property("instance").toString(); _log= Logger::getInstance("MUXER", subComponent); diff --git a/libsrc/hyperion/schema/schema-device.json b/libsrc/hyperion/schema/schema-device.json index 397409ae..04d163d9 100644 --- a/libsrc/hyperion/schema/schema-device.json +++ b/libsrc/hyperion/schema/schema-device.json @@ -41,6 +41,33 @@ }, "access": "advanced", "propertyOrder": 4 + }, + "enableAttempts": { + "type": "integer", + "title": "edt_dev_general_enableAttempts_title", + "minimum": 0, + "maximum": 120, + "default": 12, + "required": true, + "options": { + "infoText": "edt_dev_general_enableAttempts_title_info" + }, + "access": "advanced", + "propertyOrder": 5 + }, + "enableAttemptsInterval": { + "type": "integer", + "title": "edt_dev_general_enableAttemptsInterval_title", + "minimum": 5, + "maximum": 120, + "default": 15, + "required": true, + "append": "edt_append_s", + "options": { + "infoText": "edt_dev_general_enableAttemptsInterval_title_info" + }, + "access": "advanced", + "propertyOrder": 6 } }, "dependencies": { diff --git a/libsrc/hyperion/schema/schema-forwarder.json b/libsrc/hyperion/schema/schema-forwarder.json index 6dc06499..07b7d050 100644 --- a/libsrc/hyperion/schema/schema-forwarder.json +++ b/libsrc/hyperion/schema/schema-forwarder.json @@ -1,77 +1,106 @@ { - "type" : "object", - "title" : "edt_conf_fw_heading_title", - "required" : true, - "properties": { - "enable": { - "type": "boolean", - "title": "edt_conf_general_enable_title", - "required": true, - "default": false, - "propertyOrder": 1 - }, - "json": { - "type": "array", - "title": "edt_conf_fw_json_title", - "propertyOrder": 2, - "uniqueItems": true, - "items": { - "type": "object", - "title": "edt_conf_fw_json_itemtitle", - "required": true, - "properties": { - "host": { - "type": "string", - "format": "hostname_or_ip", - "minLength": 7, - "title": "edt_dev_spec_targetIpHost_title", - "required": true, - "propertyOrder": 1 - }, - "port": { - "type": "integer", - "minimum": 1, - "maximum": 65535, - "default": 19444, - "title": "edt_dev_spec_port_title", - "required": true, - "access": "expert", - "propertyOrder": 2 - } - } - } - }, - "flat": { - "type": "array", - "title": "edt_conf_fw_flat_title", - "propertyOrder": 3, - "uniqueItems": true, - "items": { - "type": "object", - "title": "edt_conf_fw_flat_itemtitle", - "required": true, - "properties": { - "host": { - "type": "string", - "format": "hostname_or_ip", - "minLength": 7, - "title": "edt_dev_spec_targetIpHost_title", - "required": true, - "propertyOrder": 1 - }, - "port": { - "type": "integer", - "minimum": 1, - "maximum": 65535, - "default": 19400, - "title": "edt_dev_spec_port_title", - "required": true, - "access": "expert", - "propertyOrder": 2 - } - } - } - } - }, - "additionalProperties": false - } + "type": "object", + "title": "edt_conf_fw_heading_title", + "required": true, + "properties": { + "enable": { + "type": "boolean", + "title": "edt_conf_general_enable_title", + "required": true, + "default": false, + "propertyOrder": 1 + }, + "jsonapiselect": { + "type": "array", + "uniqueItems": true, + "format": "select", + "title": "edt_conf_fw_json_services_discovered_title", + "propertyOrder": 2 + }, + "jsonapi": { + "type": "array", + "title": "edt_conf_fw_json_title", + "uniqueItems": true, + "access": "expert", + "items": { + "type": "object", + "title": "edt_conf_fw_json_itemtitle", + "properties": { + "name": { + "type": "string", + "title": "edt_conf_fw_service_name_title", + "required": true, + "access": "expert", + "propertyOrder": 1 + }, + "host": { + "type": "string", + "format": "hostname_or_ip", + "minLength": 7, + "title": "edt_dev_spec_targetIpHost_title", + "required": true, + "access": "expert", + "propertyOrder": 2 + }, + "port": { + "type": "integer", + "minimum": 1, + "maximum": 65535, + "title": "edt_dev_spec_port_title", + "required": true, + "access": "expert", + "propertyOrder": 3 + } + } + }, + "propertyOrder": 3 + }, + "flatbufferselect": { + "type": "array", + "uniqueItems": true, + "format": "select", + "title": "edt_conf_fw_flat_services_discovered_title", + "propertyOrder": 4 + }, + "flatbuffer": { + "type": "array", + "title": "edt_conf_fw_flat_title", + "uniqueItems": true, + "access": "expert", + "items": { + "type": "object", + "title": "edt_conf_fw_flat_itemtitle", + "properties": { + "name": { + "type": "string", + "title": "edt_conf_fw_service_name_title", + "access": "expert", + "propertyOrder": 1 + }, + "host": { + "type": "string", + "format": "hostname_or_ip", + "minLength": 7, + "title": "edt_dev_spec_targetIpHost_title", + "required": true, + "access": "expert", + "propertyOrder": 2 + }, + "port": { + "type": "integer", + "minimum": 1, + "maximum": 65535, + "default": 19400, + "title": "edt_dev_spec_port_title", + "required": true, + "access": "expert", + "propertyOrder": 3 + } + } + }, + "propertyOrder": 5 + } + }, + "additionalProperties": false +} + diff --git a/libsrc/jsonserver/CMakeLists.txt b/libsrc/jsonserver/CMakeLists.txt index 99734f8c..30ca1373 100644 --- a/libsrc/jsonserver/CMakeLists.txt +++ b/libsrc/jsonserver/CMakeLists.txt @@ -13,3 +13,8 @@ target_link_libraries(jsonserver Qt${QT_VERSION_MAJOR}::Network Qt${QT_VERSION_MAJOR}::Gui ) + +if(ENABLE_MDNS) + target_link_libraries(jsonserver mdns) +endif() + diff --git a/libsrc/jsonserver/JsonServer.cpp b/libsrc/jsonserver/JsonServer.cpp index aeef23f5..5e77f95c 100644 --- a/libsrc/jsonserver/JsonServer.cpp +++ b/libsrc/jsonserver/JsonServer.cpp @@ -1,37 +1,35 @@ // system includes #include -// project includes -#include "HyperionConfig.h" -#include -#include "JsonClientConnection.h" - -// bonjour include -#ifdef ENABLE_AVAHI -#include -#endif -#include - // qt includes #include #include #include #include +// project includes +#include "HyperionConfig.h" +#include +#include "JsonClientConnection.h" + +#include + +// Constants +namespace { + +const char SERVICE_TYPE[] = "jsonapi"; + +} //End of constants + JsonServer::JsonServer(const QJsonDocument& config) : QObject() , _server(new QTcpServer(this)) , _openConnections() , _log(Logger::getInstance("JSONSERVER")) , _netOrigin(NetOrigin::getInstance()) + , _config(config) { Debug(_log, "Created instance"); - - // Set trigger for incoming connections - connect(_server, &QTcpServer::newConnection, this, &JsonServer::newConnection); - - // init - handleSettingsUpdate(settings::JSONSERVER, config); } JsonServer::~JsonServer() @@ -39,31 +37,29 @@ JsonServer::~JsonServer() qDeleteAll(_openConnections); } +void JsonServer::initServer() +{ + // Set trigger for incoming connections + connect(_server, &QTcpServer::newConnection, this, &JsonServer::newConnection); + + // init + handleSettingsUpdate(settings::JSONSERVER, _config); +} + void JsonServer::start() { - if(_server->isListening()) - return; - - if (!_server->listen(QHostAddress::Any, _port)) + if(!_server->isListening()) { - Error(_log,"Could not bind to port '%d', please use an available port", _port); - return; + if (!_server->listen(QHostAddress::Any, _port)) + { + Error(_log,"Could not bind to port '%d', please use an available port", _port); + } + else + { + Info(_log, "Started on port %d", _port); + emit publishService(SERVICE_TYPE, _port); + } } - Info(_log, "Started on port %d", _port); - -#ifdef ENABLE_AVAHI - if(_serviceRegister == nullptr) - { - _serviceRegister = new BonjourServiceRegister(this); - _serviceRegister->registerService("_hyperiond-json._tcp", _port); - } - else if( _serviceRegister->getPort() != _port) - { - delete _serviceRegister; - _serviceRegister = new BonjourServiceRegister(this); - _serviceRegister->registerService("_hyperiond-json._tcp", _port); - } -#endif } void JsonServer::stop() diff --git a/libsrc/leddevice/CMakeLists.txt b/libsrc/leddevice/CMakeLists.txt index f71faffe..82be7df2 100644 --- a/libsrc/leddevice/CMakeLists.txt +++ b/libsrc/leddevice/CMakeLists.txt @@ -141,3 +141,6 @@ if (ENABLE_DEV_USB_HID) endif() endif() +if(ENABLE_MDNS) + target_link_libraries(leddevice mdns) +endif() diff --git a/libsrc/leddevice/LedDevice.cpp b/libsrc/leddevice/LedDevice.cpp index 161c289a..69dbed0e 100644 --- a/libsrc/leddevice/LedDevice.cpp +++ b/libsrc/leddevice/LedDevice.cpp @@ -15,84 +15,91 @@ //std includes #include #include +#include // Constants namespace { -// Configuration settings -const char CONFIG_CURRENT_LED_COUNT[] = "currentLedCount"; -const char CONFIG_COLOR_ORDER[] = "colorOrder"; -const char CONFIG_AUTOSTART[] = "autoStart"; -const char CONFIG_LATCH_TIME[] = "latchTime"; -const char CONFIG_REWRITE_TIME[] = "rewriteTime"; + // Configuration settings + const char CONFIG_CURRENT_LED_COUNT[] = "currentLedCount"; + const char CONFIG_COLOR_ORDER[] = "colorOrder"; + const char CONFIG_AUTOSTART[] = "autoStart"; + const char CONFIG_LATCH_TIME[] = "latchTime"; + const char CONFIG_REWRITE_TIME[] = "rewriteTime"; -int DEFAULT_LED_COUNT = 1; -const char DEFAULT_COLOR_ORDER[] = "RGB"; -const bool DEFAULT_IS_AUTOSTART = true; + int DEFAULT_LED_COUNT{ 1 }; + const char DEFAULT_COLOR_ORDER[]{ "RGB" }; + const bool DEFAULT_IS_AUTOSTART{ true }; + + const char CONFIG_ENABLE_ATTEMPTS[] = "enableAttempts"; + const char CONFIG_ENABLE_ATTEMPTS_INTERVALL[] = "enableAttemptsInterval"; + + const int DEFAULT_MAX_ENABLE_ATTEMPTS{ 5 }; + constexpr std::chrono::seconds DEFAULT_ENABLE_ATTEMPTS_INTERVAL{ 5 }; } //End of constants LedDevice::LedDevice(const QJsonObject& deviceConfig, QObject* parent) : QObject(parent) - , _devConfig(deviceConfig) - , _log(Logger::getInstance("LEDDEVICE")) - , _ledBuffer(0) - , _refreshTimer(nullptr) - , _refreshTimerInterval_ms(0) - , _latchTime_ms(0) - , _ledCount(0) - , _isRestoreOrigState(false) - , _isEnabled(false) - , _isDeviceInitialised(false) - , _isDeviceReady(false) - , _isOn(false) - , _isDeviceInError(false) - , _isInSwitchOff (false) - , _lastWriteTime(QDateTime::currentDateTime()) - , _isRefreshEnabled (false) - , _isAutoStart(true) + , _devConfig(deviceConfig) + , _log(Logger::getInstance("LEDDEVICE")) + , _ledBuffer(0) + , _refreshTimer(nullptr) + , _enableAttemptsTimer(nullptr) + , _refreshTimerInterval_ms(0) + , _enableAttemptTimerInterval(DEFAULT_ENABLE_ATTEMPTS_INTERVAL) + , _enableAttempts(0) + , _maxEnableAttempts(DEFAULT_MAX_ENABLE_ATTEMPTS) + , _latchTime_ms(0) + , _ledCount(0) + , _isRestoreOrigState(false) + , _isEnabled(false) + , _isDeviceInitialised(false) + , _isDeviceReady(false) + , _isOn(false) + , _isDeviceInError(false) + , _lastWriteTime(QDateTime::currentDateTime()) + , _isRefreshEnabled(false) + , _isAutoStart(true) { _activeDeviceType = deviceConfig["type"].toString("UNSPECIFIED").toLower(); } LedDevice::~LedDevice() { - delete _refreshTimer; + this->stopEnableAttemptsTimer(); + this->stopRefreshTimer(); } void LedDevice::start() { Info(_log, "Start LedDevice '%s'.", QSTRING_CSTR(_activeDeviceType)); - // setup refreshTimer - if ( _refreshTimer == nullptr ) - { - _refreshTimer = new QTimer(this); - _refreshTimer->setTimerType(Qt::PreciseTimer); - _refreshTimer->setInterval( _refreshTimerInterval_ms ); - connect(_refreshTimer, &QTimer::timeout, this, &LedDevice::rewriteLEDs ); - } - close(); - _isDeviceInitialised = false; - // General initialisation and configuration of LedDevice - if ( init(_devConfig) ) + + if (init(_devConfig)) { // Everything is OK -> enable device _isDeviceInitialised = true; + if (_isAutoStart) { - this->enable(); + if (!_isEnabled) + { + Debug(_log, "Not enabled -> enable device"); + enable(); + } } } } void LedDevice::stop() { + Debug(_log, "Stop device"); this->disable(); this->stopRefreshTimer(); - Info(_log, " Stopped LedDevice '%s'", QSTRING_CSTR(_activeDeviceType) ); + Info(_log, " Stopped LedDevice '%s'", QSTRING_CSTR(_activeDeviceType)); } int LedDevice::open() @@ -113,6 +120,7 @@ int LedDevice::close() void LedDevice::setInError(const QString& errorMsg) { + _isOn = false; _isDeviceInError = true; _isDeviceReady = false; _isEnabled = false; @@ -124,21 +132,53 @@ void LedDevice::setInError(const QString& errorMsg) void LedDevice::enable() { - if ( !_isEnabled ) + Debug(_log, "Enable device %s'", QSTRING_CSTR(_activeDeviceType)); + + if (!_isEnabled) { + if (_enableAttemptsTimer != nullptr && _enableAttemptsTimer->isActive()) + { + _enableAttemptsTimer->stop(); + } + _isDeviceInError = false; - if ( ! _isDeviceReady ) + if (!_isDeviceInitialised) + { + _isDeviceInitialised = init(_devConfig); + } + + if (!_isDeviceReady) { open(); } - if ( _isDeviceReady ) + bool isEnableFailed(true); + + if (_isDeviceReady) { - _isEnabled = true; - if ( switchOn() ) + if (switchOn()) { + stopEnableAttemptsTimer(); + _isEnabled = true; + isEnableFailed = false; emit enableStateChanged(_isEnabled); + Info(_log, "LedDevice '%s' enabled", QSTRING_CSTR(_activeDeviceType)); + } + } + + if (isEnableFailed) + { + emit enableStateChanged(false); + + if (_maxEnableAttempts > 0) + { + Debug(_log, "Device's enablement failed - Start retry timer. Retried already done [%d], isEnabled: [%d]", _enableAttempts, _isEnabled); + startEnableAttemptsTimer(); + } + else + { + Debug(_log, "Device's enablement failed"); } } } @@ -146,9 +186,11 @@ void LedDevice::enable() void LedDevice::disable() { - if ( _isEnabled ) + Debug(_log, "Disable device %s'", QSTRING_CSTR(_activeDeviceType)); + if (_isEnabled) { _isEnabled = false; + this->stopEnableAttemptsTimer(); this->stopRefreshTimer(); switchOff(); @@ -163,47 +205,110 @@ void LedDevice::setActiveDeviceType(const QString& deviceType) _activeDeviceType = deviceType; } -bool LedDevice::init(const QJsonObject &deviceConfig) +bool LedDevice::init(const QJsonObject& deviceConfig) { - Debug(_log, "deviceConfig: [%s]", QString(QJsonDocument(_devConfig).toJson(QJsonDocument::Compact)).toUtf8().constData() ); + Debug(_log, "deviceConfig: [%s]", QString(QJsonDocument(_devConfig).toJson(QJsonDocument::Compact)).toUtf8().constData()); - _colorOrder = deviceConfig[CONFIG_COLOR_ORDER].toString(DEFAULT_COLOR_ORDER); - _isAutoStart = deviceConfig[CONFIG_AUTOSTART].toBool(DEFAULT_IS_AUTOSTART); - - setLedCount( deviceConfig[CONFIG_CURRENT_LED_COUNT].toInt(DEFAULT_LED_COUNT) ); // property injected to reflect real led count - setLatchTime( deviceConfig[CONFIG_LATCH_TIME].toInt( _latchTime_ms ) ); - setRewriteTime ( deviceConfig[CONFIG_REWRITE_TIME].toInt( _refreshTimerInterval_ms) ); + setLedCount(deviceConfig[CONFIG_CURRENT_LED_COUNT].toInt(DEFAULT_LED_COUNT)); // property injected to reflect real led count + setColorOrder(deviceConfig[CONFIG_COLOR_ORDER].toString(DEFAULT_COLOR_ORDER)); + setLatchTime(deviceConfig[CONFIG_LATCH_TIME].toInt(_latchTime_ms)); + setRewriteTime(deviceConfig[CONFIG_REWRITE_TIME].toInt(_refreshTimerInterval_ms)); + setAutoStart(deviceConfig[CONFIG_AUTOSTART].toBool(DEFAULT_IS_AUTOSTART)); + setEnableAttempts(deviceConfig[CONFIG_ENABLE_ATTEMPTS].toInt(DEFAULT_MAX_ENABLE_ATTEMPTS), + std::chrono::seconds(deviceConfig[CONFIG_ENABLE_ATTEMPTS_INTERVALL].toInt(DEFAULT_ENABLE_ATTEMPTS_INTERVAL.count())) + ); return true; } void LedDevice::startRefreshTimer() { - if ( _isDeviceReady && _isEnabled ) + if (_refreshTimerInterval_ms > 0) { - _refreshTimer->start(); + if (_isDeviceReady && _isOn) + { + // setup refreshTimer + if (_refreshTimer == nullptr) + { + _refreshTimer = new QTimer(this); + _refreshTimer->setTimerType(Qt::PreciseTimer); + connect(_refreshTimer, &QTimer::timeout, this, &LedDevice::rewriteLEDs); + } + _refreshTimer->setInterval(_refreshTimerInterval_ms); + + //Debug(_log, "Start refresh timer with interval = %ims", _refreshTimer->interval()); + _refreshTimer->start(); + } + else + { + Debug(_log, "Device is not ready to start a refresh timer"); + } } } void LedDevice::stopRefreshTimer() { - if ( _refreshTimer != nullptr ) + if (_refreshTimer != nullptr) { + //Debug(_log, "Stopping refresh timer"); _refreshTimer->stop(); + delete _refreshTimer; + _refreshTimer = nullptr; + } } -int LedDevice::updateLeds(const std::vector& ledValues) +void LedDevice::startEnableAttemptsTimer() +{ + ++_enableAttempts; + + if (_enableAttempts <= _maxEnableAttempts) + { + if (_enableAttemptTimerInterval.count() > 0) + { + // setup enable retry timer + if (_enableAttemptsTimer == nullptr) + { + _enableAttemptsTimer = new QTimer(this); + _enableAttemptsTimer->setTimerType(Qt::PreciseTimer); + connect(_enableAttemptsTimer, &QTimer::timeout, this, &LedDevice::enable); + } + _enableAttemptsTimer->setInterval(static_cast(_enableAttemptTimerInterval.count() * 1000)); //NOLINT + + Info(_log, "Start %d. attempt of %d to enable the device in %d seconds", _enableAttempts, _maxEnableAttempts, _enableAttemptTimerInterval.count()); + _enableAttemptsTimer->start(); + } + } + else + { + Error(_log, "Device disabled. Maximum number of %d attempts enabling the device reached. Tried for %d seconds.", _maxEnableAttempts, _enableAttempts * _enableAttemptTimerInterval.count()); + _enableAttempts = 0; + } +} + +void LedDevice::stopEnableAttemptsTimer() +{ + if (_enableAttemptsTimer != nullptr) + { + Debug(_log, "Stopping enable retry timer"); + _enableAttemptsTimer->stop(); + delete _enableAttemptsTimer; + _enableAttemptsTimer = nullptr; + _enableAttempts = 0; + } +} + +int LedDevice::updateLeds(std::vector ledValues) { int retval = 0; - if ( !_isEnabled || !_isOn || !_isDeviceReady || _isDeviceInError ) + if (!_isEnabled || !_isOn || !_isDeviceReady || _isDeviceInError) { //std::cout << "LedDevice::updateLeds(), LedDevice NOT ready! "; retval = -1; } else { - qint64 elapsedTimeMs = _lastWriteTime.msecsTo( QDateTime::currentDateTime() ); + qint64 elapsedTimeMs = _lastWriteTime.msecsTo(QDateTime::currentDateTime()); if (_latchTime_ms == 0 || elapsedTimeMs >= _latchTime_ms) { //std::cout << "LedDevice::updateLeds(), Elapsed time since last write (" << elapsedTimeMs << ") ms > _latchTime_ms (" << _latchTime_ms << ") ms" << std::endl; @@ -211,16 +316,16 @@ int LedDevice::updateLeds(const std::vector& ledValues) _lastWriteTime = QDateTime::currentDateTime(); // if device requires refreshing, save Led-Values and restart the timer - if ( _isRefreshEnabled && _isEnabled ) + if (_isRefreshEnabled && _isEnabled) { - this->startRefreshTimer(); _lastLedValues = ledValues; + this->startRefreshTimer(); } } else { //std::cout << "LedDevice::updateLeds(), Skip write. elapsedTime (" << elapsedTimeMs << ") ms < _latchTime_ms (" << _latchTime_ms << ") ms" << std::endl; - if ( _isRefreshEnabled ) + if (_isRefreshEnabled) { //Stop timer to allow for next non-refresh update this->stopRefreshTimer(); @@ -234,18 +339,21 @@ int LedDevice::rewriteLEDs() { int retval = -1; - if ( _isDeviceReady && _isEnabled ) + if (_isEnabled && _isOn && _isDeviceReady && !_isDeviceInError) { -// qint64 elapsedTimeMs = _lastWriteTime.msecsTo(QDateTime::currentDateTime()); -// std::cout << "LedDevice::rewriteLEDs(): Rewrite LEDs now, elapsedTime [" << elapsedTimeMs << "] ms" << std::endl; -// //:TESTING: Inject "white" output records to differentiate from normal writes -// _lastLedValues.clear(); -// _lastLedValues.resize(static_cast(_ledCount), ColorRgb::WHITE); -// printLedValues(_lastLedValues); -// //:TESTING: + // qint64 elapsedTimeMs = _lastWriteTime.msecsTo(QDateTime::currentDateTime()); + // std::cout << "LedDevice::rewriteLEDs(): Rewrite LEDs now, elapsedTime [" << elapsedTimeMs << "] ms" << std::endl; + // //:TESTING: Inject "white" output records to differentiate from normal writes + // _lastLedValues.clear(); + // _lastLedValues.resize(static_cast(_ledCount), ColorRgb::WHITE); + // printLedValues(_lastLedValues); + // //:TESTING: - retval = write(_lastLedValues); - _lastWriteTime = QDateTime::currentDateTime(); + if (!_lastLedValues.empty()) + { + retval = write(_lastLedValues); + _lastWriteTime = QDateTime::currentDateTime(); + } } else { @@ -257,6 +365,7 @@ int LedDevice::rewriteLEDs() int LedDevice::writeBlack(int numberOfWrites) { + Debug(_log, "Set LED strip to black to switch of LEDs"); return writeColor(ColorRgb::BLACK, numberOfWrites); } @@ -273,7 +382,7 @@ int LedDevice::writeColor(const ColorRgb& color, int numberOfWrites) QTimer::singleShot(_latchTime_ms, &loop, &QEventLoop::quit); loop.exec(); } - _lastLedValues = std::vector(static_cast(_ledCount),color); + _lastLedValues = std::vector(static_cast(_ledCount), color); rc = write(_lastLedValues); } return rc; @@ -281,24 +390,31 @@ int LedDevice::writeColor(const ColorRgb& color, int numberOfWrites) bool LedDevice::switchOn() { - bool rc = false; + bool rc{ false }; - if ( _isOn ) + if (_isOn) { + Debug(_log, "Device %s is already on. Skipping.", QSTRING_CSTR(_activeDeviceType)); rc = true; } else { - if ( _isEnabled &&_isDeviceInitialised ) + if (_isDeviceReady) { - if ( storeState() ) + Info(_log, "Switching device %s ON", QSTRING_CSTR(_activeDeviceType)); + if (storeState()) { - if ( powerOn() ) + if (powerOn()) { + Info(_log, "Device %s is ON", QSTRING_CSTR(_activeDeviceType)); _isOn = true; - _isInSwitchOff = false; + emit enableStateChanged(_isEnabled); rc = true; } + else + { + Warning(_log, "Failed switching device %s ON", QSTRING_CSTR(_activeDeviceType)); + } } } } @@ -307,32 +423,40 @@ bool LedDevice::switchOn() bool LedDevice::switchOff() { - bool rc = false; + bool rc{ false }; - if ( !_isOn ) + if (!_isOn) { rc = true; } else { - if ( _isDeviceInitialised ) + if (_isDeviceInitialised) { - // Disable device to ensure no standard Led updates are written/processed + Info(_log, "Switching device %s OFF", QSTRING_CSTR(_activeDeviceType)); + + // Disable device to ensure no standard LED updates are written/processed _isOn = false; - _isInSwitchOff = true; rc = true; - if ( _isDeviceReady ) + if (_isDeviceReady) { - if ( _isRestoreOrigState ) + if (_isRestoreOrigState) { //Restore devices state restoreState(); } else { - powerOff(); + if (powerOff()) + { + Info(_log, "Device %s is OFF", QSTRING_CSTR(_activeDeviceType)); + } + else + { + Warning(_log, "Failed switching device %s OFF", QSTRING_CSTR(_activeDeviceType)); + } } } } @@ -342,10 +466,12 @@ bool LedDevice::switchOff() bool LedDevice::powerOff() { - bool rc = false; + bool rc{ false }; + + Debug(_log, "Power Off: %s", QSTRING_CSTR(_activeDeviceType)); // Simulate power-off by writing a final "Black" to have a defined outcome - if ( writeBlack() >= 0 ) + if (writeBlack() >= 0) { rc = true; } @@ -354,15 +480,18 @@ bool LedDevice::powerOff() bool LedDevice::powerOn() { - bool rc = true; + bool rc{ true }; + + Debug(_log, "Power On: %s", QSTRING_CSTR(_activeDeviceType)); + return rc; } bool LedDevice::storeState() { - bool rc = true; + bool rc{ true }; - if ( _isRestoreOrigState ) + if (_isRestoreOrigState) { // Save device's original state // _originalStateValues = get device's state; @@ -373,9 +502,9 @@ bool LedDevice::storeState() bool LedDevice::restoreState() { - bool rc = true; + bool rc{ true }; - if ( _isRestoreOrigState ) + if (_isRestoreOrigState) { // Restore device's original state // update device using _originalStateValues @@ -393,7 +522,7 @@ QJsonObject LedDevice::discover(const QJsonObject& /*params*/) QJsonArray deviceList; devicesDiscovered.insert("devices", deviceList); - Debug(_log, "devicesDiscovered: [%s]", QString(QJsonDocument(devicesDiscovered).toJson(QJsonDocument::Compact)).toUtf8().constData() ); + Debug(_log, "devicesDiscovered: [%s]", QString(QJsonDocument(devicesDiscovered).toJson(QJsonDocument::Compact)).toUtf8().constData()); return devicesDiscovered; } @@ -401,75 +530,98 @@ QString LedDevice::discoverFirst() { QString deviceDiscovered; - Debug(_log, "deviceDiscovered: [%s]", QSTRING_CSTR(deviceDiscovered) ); + Debug(_log, "deviceDiscovered: [%s]", QSTRING_CSTR(deviceDiscovered)); return deviceDiscovered; } QJsonObject LedDevice::getProperties(const QJsonObject& params) { - Debug(_log, "params: [%s]", QString(QJsonDocument(params).toJson(QJsonDocument::Compact)).toUtf8().constData() ); + Debug(_log, "params: [%s]", QString(QJsonDocument(params).toJson(QJsonDocument::Compact)).toUtf8().constData()); QJsonObject properties; QJsonObject deviceProperties; properties.insert("properties", deviceProperties); - Debug(_log, "properties: [%s]", QString(QJsonDocument(properties).toJson(QJsonDocument::Compact)).toUtf8().constData() ); + Debug(_log, "properties: [%s]", QString(QJsonDocument(properties).toJson(QJsonDocument::Compact)).toUtf8().constData()); return properties; } +void LedDevice::setLogger(Logger* log) +{ + _log = log; +} + void LedDevice::setLedCount(int ledCount) { assert(ledCount >= 0); - _ledCount = ledCount; - _ledRGBCount = _ledCount * sizeof(ColorRgb); + _ledCount = static_cast(ledCount); + _ledRGBCount = _ledCount * sizeof(ColorRgb); _ledRGBWCount = _ledCount * sizeof(ColorRgbw); + Debug(_log, "LedCount set to %d", _ledCount); } -void LedDevice::setLatchTime( int latchTime_ms ) +void LedDevice::setColorOrder(const QString& colorOrder) +{ + _colorOrder = colorOrder; + Debug(_log, "ColorOrder set to %s", QSTRING_CSTR(_colorOrder.toUpper())); +} + +void LedDevice::setLatchTime(int latchTime_ms) { assert(latchTime_ms >= 0); _latchTime_ms = latchTime_ms; - Debug(_log, "LatchTime updated to %dms", _latchTime_ms); + Debug(_log, "LatchTime set to %dms", _latchTime_ms); } -void LedDevice::setRewriteTime( int rewriteTime_ms ) +void LedDevice::setAutoStart(bool isAutoStart) { - assert(rewriteTime_ms >= 0); + _isAutoStart = isAutoStart; + Debug(_log, "AutoStart %s", (_isAutoStart ? "enabled" : "disabled")); +} - //Check, if refresh timer was not initialised due to getProperties/identify sceanrios - if (_refreshTimer != nullptr) +void LedDevice::setRewriteTime(int rewriteTime_ms) +{ + _refreshTimerInterval_ms = qMax(rewriteTime_ms, 0); + + if (_refreshTimerInterval_ms > 0) { - _refreshTimerInterval_ms = rewriteTime_ms; + _isRefreshEnabled = true; - if (_refreshTimerInterval_ms > 0) + if (_refreshTimerInterval_ms <= _latchTime_ms) { - - _isRefreshEnabled = true; - - if (_refreshTimerInterval_ms <= _latchTime_ms) - { - int new_refresh_timer_interval = _latchTime_ms + 10; - Warning(_log, "latchTime(%d) is bigger/equal rewriteTime(%d), set rewriteTime to %dms", _latchTime_ms, _refreshTimerInterval_ms, new_refresh_timer_interval); - _refreshTimerInterval_ms = new_refresh_timer_interval; - _refreshTimer->setInterval(_refreshTimerInterval_ms); - } - - Debug(_log, "Refresh interval = %dms", _refreshTimerInterval_ms); - _refreshTimer->setInterval(_refreshTimerInterval_ms); - - _lastWriteTime = QDateTime::currentDateTime(); + int new_refresh_timer_interval = _latchTime_ms + 10; //NOLINT + Warning(_log, "latchTime(%d) is bigger/equal rewriteTime(%d), set rewriteTime to %dms", _latchTime_ms, _refreshTimerInterval_ms, new_refresh_timer_interval); + _refreshTimerInterval_ms = new_refresh_timer_interval; } - Debug(_log, "RewriteTime updated to %dms", _refreshTimerInterval_ms); + Debug(_log, "Refresh interval = %dms", _refreshTimerInterval_ms); + startRefreshTimer(); } + else + { + _isRefreshEnabled = false; + stopRefreshTimer(); + } +} + +void LedDevice::setEnableAttempts(int maxEnableRetries, std::chrono::seconds enableRetryTimerInterval) +{ + stopEnableAttemptsTimer(); + maxEnableRetries = qMax(maxEnableRetries, 0); + + _enableAttempts = 0; + _maxEnableAttempts = maxEnableRetries; + _enableAttemptTimerInterval = enableRetryTimerInterval; + + Debug(_log, "Max enable retries: %d, enable retry interval = %llds", _maxEnableAttempts, static_cast(_enableAttemptTimerInterval.count())); } void LedDevice::printLedValues(const std::vector& ledValues) { - std::cout << "LedValues [" << ledValues.size() <<"] ["; + std::cout << "LedValues [" << ledValues.size() << "] ["; for (const ColorRgb& color : ledValues) { std::cout << color; @@ -477,24 +629,24 @@ void LedDevice::printLedValues(const std::vector& ledValues) std::cout << "]" << std::endl; } -QString LedDevice::uint8_t_to_hex_string(const uint8_t * data, const int size, int number) const +QString LedDevice::uint8_t_to_hex_string(const uint8_t* data, const int size, int number) { - if ( number <= 0 || number > size) + if (number <= 0 || number > size) { number = size; } - QByteArray bytes (reinterpret_cast(data), number); - #if (QT_VERSION >= QT_VERSION_CHECK(5, 9, 0)) - return bytes.toHex(':'); - #else - return bytes.toHex(); - #endif + QByteArray bytes(reinterpret_cast(data), number); +#if (QT_VERSION >= QT_VERSION_CHECK(5, 9, 0)) + return bytes.toHex(':'); +#else + return bytes.toHex(); +#endif } -QString LedDevice::toHex(const QByteArray& data, int number) const +QString LedDevice::toHex(const QByteArray& data, int number) { - if ( number <= 0 || number > data.size()) + if (number <= 0 || number > data.size()) { number = data.size(); } @@ -505,3 +657,47 @@ QString LedDevice::toHex(const QByteArray& data, int number) const return data.left(number).toHex(); #endif } +bool LedDevice::isInitialised() const +{ + return _isDeviceInitialised; +} + +bool LedDevice::isReady() const +{ + return _isDeviceReady; +} + +bool LedDevice::isInError() const +{ + return _isDeviceInError; +} + +int LedDevice::getLatchTime() const +{ + return _latchTime_ms; +} + +int LedDevice::getRewriteTime() const +{ + return _refreshTimerInterval_ms; +} + +int LedDevice::getLedCount() const +{ + return static_cast(_ledCount); +} + +QString LedDevice::getActiveDeviceType() const +{ + return _activeDeviceType; +} + +QString LedDevice::getColorOrder() const +{ + return _colorOrder; +} + +bool LedDevice::componentState() const { + return _isEnabled; +} + diff --git a/libsrc/leddevice/LedDeviceSchemas.qrc b/libsrc/leddevice/LedDeviceSchemas.qrc index 2718c281..be976000 100644 --- a/libsrc/leddevice/LedDeviceSchemas.qrc +++ b/libsrc/leddevice/LedDeviceSchemas.qrc @@ -27,6 +27,7 @@ schemas/schema-artnet.json schemas/schema-h801.json schemas/schema-udpraw.json + schemas/schema-udpddp.json schemas/schema-ws2801.json schemas/schema-ws2812spi.json schemas/schema-apa104.json diff --git a/libsrc/leddevice/LedDeviceWrapper.cpp b/libsrc/leddevice/LedDeviceWrapper.cpp index 0f976316..5cba7af9 100644 --- a/libsrc/leddevice/LedDeviceWrapper.cpp +++ b/libsrc/leddevice/LedDeviceWrapper.cpp @@ -67,9 +67,6 @@ void LedDeviceWrapper::createLedDevice(const QJsonObject& config) // further signals connect(this, &LedDeviceWrapper::updateLeds, _ledDevice, &LedDevice::updateLeds, Qt::QueuedConnection); - connect(this, &LedDeviceWrapper::enable, _ledDevice, &LedDevice::enable); - connect(this, &LedDeviceWrapper::disable, _ledDevice, &LedDevice::disable); - connect(this, &LedDeviceWrapper::switchOn, _ledDevice, &LedDevice::switchOn); connect(this, &LedDeviceWrapper::switchOff, _ledDevice, &LedDevice::switchOff); @@ -81,6 +78,100 @@ void LedDeviceWrapper::createLedDevice(const QJsonObject& config) thread->start(); } +void LedDeviceWrapper::handleComponentState(hyperion::Components component, bool state) +{ + if (component == hyperion::COMP_LEDDEVICE) + { + if (state) + { + QMetaObject::invokeMethod(_ledDevice, "enable", Qt::BlockingQueuedConnection); + } + else + { + QMetaObject::invokeMethod(_ledDevice, "disable", Qt::BlockingQueuedConnection); + } + + QMetaObject::invokeMethod(_ledDevice, "componentState", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, _enabled)); + } +} + +void LedDeviceWrapper::handleInternalEnableState(bool newState) +{ + _hyperion->setNewComponentState(hyperion::COMP_LEDDEVICE, newState); + _enabled = newState; + + if (_enabled) + { + _hyperion->update(); + } +} + +void LedDeviceWrapper::stopDeviceThread() +{ + // turns the LEDs off & stop refresh timers + emit stopLedDevice(); + + // get current thread + QThread* oldThread = _ledDevice->thread(); + disconnect(oldThread, nullptr, nullptr, nullptr); + oldThread->quit(); + oldThread->wait(); + delete oldThread; + + disconnect(_ledDevice, nullptr, nullptr, nullptr); + delete _ledDevice; + _ledDevice = nullptr; +} + +QString LedDeviceWrapper::getActiveDeviceType() const +{ + QString value = 0; + QMetaObject::invokeMethod(_ledDevice, "getActiveDeviceType", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QString, value)); + return value; +} + +unsigned int LedDeviceWrapper::getLedCount() const +{ + int value = 0; + QMetaObject::invokeMethod(_ledDevice, "getLedCount", Qt::BlockingQueuedConnection, Q_RETURN_ARG(int, value)); + return value; +} + +QString LedDeviceWrapper::getColorOrder() const +{ + QString value; + QMetaObject::invokeMethod(_ledDevice, "getColorOrder", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QString, value)); + return value; +} + +int LedDeviceWrapper::getLatchTime() const +{ + int value = 0; + QMetaObject::invokeMethod(_ledDevice, "getLatchTime", Qt::BlockingQueuedConnection, Q_RETURN_ARG(int, value)); + return value; +} + +bool LedDeviceWrapper::enabled() const +{ + return _enabled; +} + +int LedDeviceWrapper::addToDeviceMap(QString name, LedDeviceCreateFuncType funcPtr) +{ + QMutexLocker lock(&_ledDeviceMapLock); + + _ledDeviceMap.emplace(name,funcPtr); + + return 0; +} + +const LedDeviceRegistry& LedDeviceWrapper::getDeviceMap() +{ + QMutexLocker lock(&_ledDeviceMapLock); + + return _ledDeviceMap; +} + QJsonObject LedDeviceWrapper::getLedDeviceSchemas() { // make sure the resources are loaded (they may be left out after static linking) @@ -115,101 +206,3 @@ QJsonObject LedDeviceWrapper::getLedDeviceSchemas() return result; } - -int LedDeviceWrapper::addToDeviceMap(QString name, LedDeviceCreateFuncType funcPtr) -{ - QMutexLocker lock(&_ledDeviceMapLock); - - _ledDeviceMap.emplace(name,funcPtr); - - return 0; -} - -const LedDeviceRegistry& LedDeviceWrapper::getDeviceMap() -{ - QMutexLocker lock(&_ledDeviceMapLock); - - return _ledDeviceMap; -} - -int LedDeviceWrapper::getLatchTime() const -{ - int value = 0; - QMetaObject::invokeMethod(_ledDevice, "getLatchTime", Qt::BlockingQueuedConnection, Q_RETURN_ARG(int, value)); - return value; -} - -QString LedDeviceWrapper::getActiveDeviceType() const -{ - QString value = 0; - QMetaObject::invokeMethod(_ledDevice, "getActiveDeviceType", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QString, value)); - return value; -} - -QString LedDeviceWrapper::getColorOrder() const -{ - QString value; - QMetaObject::invokeMethod(_ledDevice, "getColorOrder", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QString, value)); - return value; -} - -unsigned int LedDeviceWrapper::getLedCount() const -{ - int value = 0; - QMetaObject::invokeMethod(_ledDevice, "getLedCount", Qt::BlockingQueuedConnection, Q_RETURN_ARG(int, value)); - return value; -} - -bool LedDeviceWrapper::enabled() const -{ - return _enabled; -} - -void LedDeviceWrapper::handleComponentState(hyperion::Components component, bool state) -{ - if(component == hyperion::COMP_LEDDEVICE) - { - if ( state ) - { - emit enable(); - } - else - { - emit disable(); - } - - //Get device's state, considering situations where it is not ready - bool deviceState = false; - QMetaObject::invokeMethod(_ledDevice, "componentState", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, deviceState)); - _hyperion->setNewComponentState(hyperion::COMP_LEDDEVICE, deviceState); - _enabled = deviceState; - } -} - -void LedDeviceWrapper::handleInternalEnableState(bool newState) -{ - _hyperion->setNewComponentState(hyperion::COMP_LEDDEVICE, newState); - _enabled = newState; - - if (_enabled) - { - _hyperion->update(); - } -} - -void LedDeviceWrapper::stopDeviceThread() -{ - // turns the LEDs off & stop refresh timers - emit stopLedDevice(); - - // get current thread - QThread* oldThread = _ledDevice->thread(); - disconnect(oldThread, nullptr, nullptr, nullptr); - oldThread->quit(); - oldThread->wait(); - delete oldThread; - - disconnect(_ledDevice, nullptr, nullptr, nullptr); - delete _ledDevice; - _ledDevice = nullptr; -} diff --git a/libsrc/leddevice/dev_hid/LedDeviceHyperionUsbasp.h b/libsrc/leddevice/dev_hid/LedDeviceHyperionUsbasp.h index 769c0d46..d2fff8ac 100644 --- a/libsrc/leddevice/dev_hid/LedDeviceHyperionUsbasp.h +++ b/libsrc/leddevice/dev_hid/LedDeviceHyperionUsbasp.h @@ -33,7 +33,7 @@ public: /// /// Sets configuration /// - /// @para#endif // LEDEVICETEMPLATE_Hm deviceConfig the json device config + /// @param deviceConfig the json device config /// @return true if success bool init(const QJsonObject &deviceConfig) override; diff --git a/libsrc/leddevice/dev_net/LedDeviceAtmoOrb.cpp b/libsrc/leddevice/dev_net/LedDeviceAtmoOrb.cpp index 485d72e9..7a50a344 100644 --- a/libsrc/leddevice/dev_net/LedDeviceAtmoOrb.cpp +++ b/libsrc/leddevice/dev_net/LedDeviceAtmoOrb.cpp @@ -45,23 +45,16 @@ LedDeviceAtmoOrb::~LedDeviceAtmoOrb() bool LedDeviceAtmoOrb::init(const QJsonObject &deviceConfig) { - bool isInitOK = false; + bool isInitOK {false}; if ( LedDevice::init(deviceConfig) ) { - _multicastGroup = deviceConfig["host"].toString(MULTICAST_GROUP_DEFAULT_ADDRESS); _multiCastGroupPort = static_cast(deviceConfig["port"].toInt(MULTICAST_GROUP_DEFAULT_PORT)); _useOrbSmoothing = deviceConfig["useOrbSmoothing"].toBool(false); _skipSmoothingDiff = deviceConfig["skipSmoothingDiff"].toInt(0); QStringList orbIds = QStringUtils::split(deviceConfig["orbIds"].toString().simplified().remove(" "),",", QStringUtils::SplitBehavior::SkipEmptyParts); - Debug(_log, "DeviceType : %s", QSTRING_CSTR( this->getActiveDeviceType() )); - Debug(_log, "LedCount : %d", this->getLedCount()); - Debug(_log, "ColorOrder : %s", QSTRING_CSTR( this->getColorOrder() )); - Debug(_log, "RefreshTime : %d", _refreshTimerInterval_ms); - Debug(_log, "LatchTime : %d", this->getLatchTime()); - Debug(_log, "MulticastGroup : %s", QSTRING_CSTR(_multicastGroup)); Debug(_log, "MulticastGroupPort: %d", _multiCastGroupPort); Debug(_log, "Orb ID list : %s", QSTRING_CSTR(deviceConfig["orbIds"].toString())); diff --git a/libsrc/leddevice/dev_net/LedDeviceCololight.cpp b/libsrc/leddevice/dev_net/LedDeviceCololight.cpp index 1d640865..8fa45481 100644 --- a/libsrc/leddevice/dev_net/LedDeviceCololight.cpp +++ b/libsrc/leddevice/dev_net/LedDeviceCololight.cpp @@ -8,21 +8,27 @@ #include +// mDNS discover +#ifdef ENABLE_MDNS +#include +#include +#endif +#include + // Constants namespace { const bool verbose = false; const bool verbose3 = false; // Configuration settings - + const char CONFIG_HOST[] = "host"; const char CONFIG_HW_LED_COUNT[] = "hardwareLedCount"; const int COLOLIGHT_BEADS_PER_MODULE = 19; + const int STREAM_DEFAULT_PORT = 8900; + // Cololight discovery service - - const int API_DEFAULT_PORT = 8900; - const char DISCOVERY_ADDRESS[] = "255.255.255.255"; const quint16 DISCOVERY_PORT = 12345; const char DISCOVERY_MESSAGE[] = "Z-SEARCH * \r\n"; @@ -46,6 +52,11 @@ LedDeviceCololight::LedDeviceCololight(const QJsonObject& deviceConfig) , _distance(0) , _sequenceNumber(1) { +#ifdef ENABLE_MDNS + QMetaObject::invokeMethod(&MdnsBrowser::getInstance(), "browseForServiceType", + Qt::QueuedConnection, Q_ARG(QByteArray, MdnsServiceRegister::getServiceType(_activeDeviceType))); +#endif + _packetFixPart.append(reinterpret_cast(PACKET_HEADER), sizeof(PACKET_HEADER)); _packetFixPart.append(reinterpret_cast(PACKET_SECU), sizeof(PACKET_SECU)); } @@ -57,22 +68,13 @@ LedDevice* LedDeviceCololight::construct(const QJsonObject& deviceConfig) bool LedDeviceCololight::init(const QJsonObject& deviceConfig) { - bool isInitOK = false; + bool isInitOK {false}; - _port = API_DEFAULT_PORT; - - if (ProviderUdp::init(deviceConfig)) + if ( ProviderUdp::init(deviceConfig) ) { - // Initialise LedDevice configuration and execution environment - Debug(_log, "DeviceType : %s", QSTRING_CSTR(this->getActiveDeviceType())); - Debug(_log, "ColorOrder : %s", QSTRING_CSTR(this->getColorOrder())); - Debug(_log, "LatchTime : %d", this->getLatchTime()); - - if (initLedsConfiguration()) - { - initDirectColorCmdTemplate(); - isInitOK = true; - } + _hostName = _devConfig[ CONFIG_HOST ].toString(); + _port = STREAM_DEFAULT_PORT; + isInitOK = true; } return isInitOK; } @@ -161,6 +163,27 @@ void LedDeviceCololight::initDirectColorCmdTemplate() } } +int LedDeviceCololight::open() +{ + int retval = -1; + _isDeviceReady = false; + + if (NetUtils::resolveHostToAddress(_log, _hostName, _address)) + { + if (ProviderUdp::open() == 0) + { + if (initLedsConfiguration()) + { + initDirectColorCmdTemplate(); + // Everything is OK, device is ready + _isDeviceReady = true; + retval = 0; + } + } + } + return retval; +} + bool LedDeviceCololight::getInfo() { bool isCmdOK = false; @@ -652,10 +675,19 @@ QJsonObject LedDeviceCololight::discover(const QJsonObject& /*params*/) QJsonObject devicesDiscovered; devicesDiscovered.insert("ledDeviceType", _activeDeviceType); - QString discoveryMethod("ssdp"); QJsonArray deviceList; +#ifdef ENABLE_MDNS + QString discoveryMethod("mDNS"); + deviceList = MdnsBrowser::getInstance().getServicesDiscoveredJson( + MdnsServiceRegister::getServiceType(_activeDeviceType), + MdnsServiceRegister::getServiceNameFilter(_activeDeviceType), + DEFAULT_DISCOVER_TIMEOUT + ); +#else + QString discoveryMethod("ssdp"); deviceList = discover(); +#endif devicesDiscovered.insert("discoveryMethod", discoveryMethod); devicesDiscovered.insert("devices", deviceList); @@ -669,19 +701,16 @@ QJsonObject LedDeviceCololight::getProperties(const QJsonObject& params) { DebugIf(verbose,_log, "params: [%s]", QString(QJsonDocument(params).toJson(QJsonDocument::Compact)).toUtf8().constData()); QJsonObject properties; - - QString hostName = params["host"].toString(""); - quint16 apiPort = static_cast(params["port"].toInt(API_DEFAULT_PORT)); - QJsonObject propertiesDetails; - if (!hostName.isEmpty()) + + _hostName = params[CONFIG_HOST].toString(""); + _port = STREAM_DEFAULT_PORT; + + Info(_log, "Get properties for %s, hostname (%s)", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(_hostName) ); + + if (NetUtils::resolveHostToAddress(_log, _hostName, _address)) { - QJsonObject deviceConfig; - - deviceConfig.insert("host", hostName); - deviceConfig.insert("port", apiPort); - - if (ProviderUdp::init(deviceConfig)) + if (ProviderUdp::open() == 0) { if (getInfo()) { @@ -717,16 +746,14 @@ void LedDeviceCololight::identify(const QJsonObject& params) { DebugIf(verbose,_log, "params: [%s]", QString(QJsonDocument(params).toJson(QJsonDocument::Compact)).toUtf8().constData()); - QString hostName = params["host"].toString(""); - quint16 apiPort = static_cast(params["port"].toInt(API_DEFAULT_PORT)); + _hostName = params[CONFIG_HOST].toString(""); + _port = STREAM_DEFAULT_PORT; - if (!hostName.isEmpty()) + Info(_log, "Identify %s, hostname (%s)", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(_hostName) ); + + if (NetUtils::resolveHostToAddress(_log, _hostName, _address)) { - QJsonObject deviceConfig; - - deviceConfig.insert("host", hostName); - deviceConfig.insert("port", apiPort); - if (ProviderUdp::init(deviceConfig)) + if (ProviderUdp::open() == 0) { if (setStateDirect(false) && setState(true)) { diff --git a/libsrc/leddevice/dev_net/LedDeviceCololight.h b/libsrc/leddevice/dev_net/LedDeviceCololight.h index 0818176b..8d1915c2 100644 --- a/libsrc/leddevice/dev_net/LedDeviceCololight.h +++ b/libsrc/leddevice/dev_net/LedDeviceCololight.h @@ -161,6 +161,13 @@ protected: /// bool init(const QJsonObject& deviceConfig) override; + /// + /// @brief Opens the output device. + /// + /// @return Zero on success (i.e. device is ready), else negative + /// + int open() override; + /// /// @brief Writes the RGB-Color values to the LEDs. /// diff --git a/libsrc/leddevice/dev_net/LedDeviceFadeCandy.cpp b/libsrc/leddevice/dev_net/LedDeviceFadeCandy.cpp index 9d546916..cab98a7d 100644 --- a/libsrc/leddevice/dev_net/LedDeviceFadeCandy.cpp +++ b/libsrc/leddevice/dev_net/LedDeviceFadeCandy.cpp @@ -18,15 +18,20 @@ const int MAX_NUM_LEDS = 10000; // OPC can handle 21845 LEDs - in theory, fadeca const int OPC_SET_PIXELS = 0; // OPC command codes const int OPC_SYS_EX = 255; // OPC command codes const int OPC_HEADER_SIZE = 4; // OPC header size -} //End of constants // TCP elements +const char CONFIG_HOST[] = "host"; +const char CONFIG_PORT[] = "port"; + +const char DEFAULT_HOST[] = "127.0.0.1"; const int STREAM_DEFAULT_PORT = 7890; +} //End of constants + LedDeviceFadeCandy::LedDeviceFadeCandy(const QJsonObject& deviceConfig) : LedDevice(deviceConfig) , _client(nullptr) - , _host() + , _hostName() , _port(STREAM_DEFAULT_PORT) { } @@ -43,7 +48,7 @@ LedDevice* LedDeviceFadeCandy::construct(const QJsonObject& deviceConfig) bool LedDeviceFadeCandy::init(const QJsonObject& deviceConfig) { - bool isInitOK = false; + bool isInitOK {false}; if (LedDevice::init(deviceConfig)) { @@ -55,11 +60,11 @@ bool LedDeviceFadeCandy::init(const QJsonObject& deviceConfig) } else { - _host = deviceConfig["host"].toString("127.0.0.1"); - _port = deviceConfig["port"].toInt(STREAM_DEFAULT_PORT); + _hostName = _devConfig[ CONFIG_HOST ].toString(DEFAULT_HOST); + _port = deviceConfig[CONFIG_PORT].toInt(STREAM_DEFAULT_PORT); //If host not configured the init fails - if (_host.isEmpty()) + if (_hostName.isEmpty()) { this->setInError("No target hostname nor IP defined"); } @@ -90,10 +95,7 @@ bool LedDeviceFadeCandy::init(const QJsonObject& deviceConfig) _opc_data[1] = OPC_SET_PIXELS; qToBigEndian(static_cast(_ledRGBCount), _opc_data.data() + 2); - if (initNetwork()) - { - isInitOK = true; - } + isInitOK = true; } } } @@ -102,12 +104,11 @@ bool LedDeviceFadeCandy::init(const QJsonObject& deviceConfig) bool LedDeviceFadeCandy::initNetwork() { - bool isInitOK = false; + bool isInitOK = true; if (_client == nullptr) { _client = new QTcpSocket(this); - isInitOK = true; } return isInitOK; } @@ -118,17 +119,20 @@ int LedDeviceFadeCandy::open() QString errortext; _isDeviceReady = false; + if (initNetwork()) + { // Try to open the LedDevice - if (!tryConnect()) - { - errortext = QString("Failed to open device."); - this->setInError(errortext); - } - else - { - // Everything is OK, device is ready - _isDeviceReady = true; - retval = 0; + if (!tryConnect()) + { + errortext = QString("Failed to open device."); + this->setInError(errortext); + } + else + { + // Everything is OK, device is ready + _isDeviceReady = true; + retval = 0; + } } return retval; } @@ -162,10 +166,10 @@ bool LedDeviceFadeCandy::tryConnect() if (_client != nullptr) { if (_client->state() == QAbstractSocket::UnconnectedState) { - _client->connectToHost(_host, static_cast(_port)); + _client->connectToHost(_hostName, static_cast(_port)); if (_client->waitForConnected(CONNECT_TIMEOUT.count())) { - Info(_log, "fadecandy/opc: connected to %s:%d on channel %d", QSTRING_CSTR(_host), _port, _channel); + Info(_log, "fadecandy/opc: connected to %s:%d on channel %d", QSTRING_CSTR(_hostName), _port, _channel); if (_setFcConfig) { sendFadeCandyConfiguration(); diff --git a/libsrc/leddevice/dev_net/LedDeviceFadeCandy.h b/libsrc/leddevice/dev_net/LedDeviceFadeCandy.h index 82f61e9a..f601e251 100644 --- a/libsrc/leddevice/dev_net/LedDeviceFadeCandy.h +++ b/libsrc/leddevice/dev_net/LedDeviceFadeCandy.h @@ -130,7 +130,7 @@ private: void sendFadeCandyConfiguration(); QTcpSocket* _client; - QString _host; + QString _hostName; int _port; int _channel; QByteArray _opc_data; diff --git a/libsrc/leddevice/dev_net/LedDeviceNanoleaf.cpp b/libsrc/leddevice/dev_net/LedDeviceNanoleaf.cpp index 12f4f600..62ba1fe3 100644 --- a/libsrc/leddevice/dev_net/LedDeviceNanoleaf.cpp +++ b/libsrc/leddevice/dev_net/LedDeviceNanoleaf.cpp @@ -1,16 +1,23 @@ // Local-Hyperion includes #include "LedDeviceNanoleaf.h" -#include -#include +//std includes +#include +#include // Qt includes #include #include -//std includes -#include -#include +#include +#include + +// mDNS discover +#ifdef ENABLE_MDNS +#include +#include +#endif +#include // Constants namespace { @@ -18,7 +25,7 @@ const bool verbose = false; const bool verbose3 = false; // Configuration settings -const char CONFIG_ADDRESS[] = "host"; +const char CONFIG_HOST[] = "host"; const char CONFIG_AUTH_TOKEN[] = "token"; const char CONFIG_RESTORE_STATE[] = "restoreOriginalState"; const char CONFIG_BRIGHTNESS[] = "brightness"; @@ -115,6 +122,10 @@ LedDeviceNanoleaf::LedDeviceNanoleaf(const QJsonObject& deviceConfig) , _extControlVersion(EXTCTRLVER_V2) , _panelLedCount(0) { +#ifdef ENABLE_MDNS + QMetaObject::invokeMethod(&MdnsBrowser::getInstance(), "browseForServiceType", + Qt::QueuedConnection, Q_ARG(QByteArray, MdnsServiceRegister::getServiceType(_activeDeviceType))); +#endif } LedDevice* LedDeviceNanoleaf::construct(const QJsonObject& deviceConfig) @@ -130,6 +141,8 @@ LedDeviceNanoleaf::~LedDeviceNanoleaf() bool LedDeviceNanoleaf::init(const QJsonObject& deviceConfig) { + bool isInitOK {false}; + // Overwrite non supported/required features setLatchTime(0); setRewriteTime(0); @@ -141,21 +154,19 @@ bool LedDeviceNanoleaf::init(const QJsonObject& deviceConfig) DebugIf(verbose,_log, "deviceConfig: [%s]", QString(QJsonDocument(_devConfig).toJson(QJsonDocument::Compact)).toUtf8().constData()); - bool isInitOK = false; - - if (LedDevice::init(deviceConfig)) + if ( ProviderUdp::init(deviceConfig) ) { - int configuredLedCount = this->getLedCount(); - Debug(_log, "DeviceType : %s", QSTRING_CSTR(this->getActiveDeviceType())); - Debug(_log, "LedCount : %d", configuredLedCount); - Debug(_log, "ColorOrder : %s", QSTRING_CSTR(this->getColorOrder())); - Debug(_log, "RewriteTime : %d", this->getRewriteTime()); - Debug(_log, "LatchTime : %d", this->getLatchTime()); + //Set hostname as per configuration and default port + _hostName = deviceConfig[CONFIG_HOST].toString(); + _port = STREAM_CONTROL_DEFAULT_PORT; + _apiPort = API_DEFAULT_PORT; + _authToken = deviceConfig[CONFIG_AUTH_TOKEN].toString(); _isRestoreOrigState = _devConfig[CONFIG_RESTORE_STATE].toBool(DEFAULT_IS_RESTORE_STATE); _isBrightnessOverwrite = _devConfig[CONFIG_BRIGHTNESS_OVERWRITE].toBool(DEFAULT_IS_BRIGHTNESS_OVERWRITE); _brightness = _devConfig[CONFIG_BRIGHTNESS].toInt(BRI_MAX); + Debug(_log, "Hostname/IP : %s", QSTRING_CSTR(_hostName) ); Debug(_log, "RestoreOrigState : %d", _isRestoreOrigState); Debug(_log, "Overwrite Brightn.: %d", _isBrightnessOverwrite); Debug(_log, "Set Brightness to : %d", _brightness); @@ -178,37 +189,9 @@ bool LedDeviceNanoleaf::init(const QJsonObject& deviceConfig) { _leftRight = deviceConfig[CONFIG_PANEL_ORDER_LEFT_RIGHT].toInt() == 0; } - _startPos = deviceConfig[CONFIG_PANEL_START_POS].toInt(0); - //Set hostname as per configuration and_defaultHost default port - _hostName = deviceConfig[CONFIG_ADDRESS].toString(); - _apiPort = API_DEFAULT_PORT; - _authToken = deviceConfig[CONFIG_AUTH_TOKEN].toString(); - - //If host not configured the init failed - if (_hostName.isEmpty()) - { - this->setInError("No target hostname nor IP defined"); - isInitOK = false; - } - else - { - if (initRestAPI(_hostName, _apiPort, _authToken)) - { - // Read LedDevice configuration and validate against device configuration - if (initLedsConfiguration()) - { - // Set UDP streaming host and port - _devConfig["host"] = _hostName; - _devConfig["port"] = STREAM_CONTROL_DEFAULT_PORT; - - isInitOK = ProviderUdp::init(_devConfig); - Debug(_log, "Hostname/IP : %s", QSTRING_CSTR(_hostName)); - Debug(_log, "Port : %d", _port); - } - } - } + isInitOK = true; } return isInitOK; } @@ -358,18 +341,17 @@ bool LedDeviceNanoleaf::initLedsConfiguration() return isInitOK; } -bool LedDeviceNanoleaf::initRestAPI(const QString& hostname, int port, const QString& token) +bool LedDeviceNanoleaf::openRestAPI() { - bool isInitOK = false; + bool isInitOK {true}; if (_restApi == nullptr) { - _restApi = new ProviderRestApi(hostname, port); + _restApi = new ProviderRestApi(_address.toString(), _apiPort); + _restApi->setLogger(_log); //Base-path is api-path + authentication token - _restApi->setBasePath(QString(API_BASE_PATH).arg(token)); - - isInitOK = true; + _restApi->setBasePath(QString(API_BASE_PATH).arg(_authToken)); } return isInitOK; } @@ -379,13 +361,27 @@ int LedDeviceNanoleaf::open() int retval = -1; _isDeviceReady = false; - if (ProviderUdp::open() == 0) + if (NetUtils::resolveHostToAddress(_log, _hostName, _address, _apiPort)) { - // Everything is OK, device is ready - _isDeviceReady = true; - retval = 0; + if ( openRestAPI() ) + { + // Read LedDevice configuration and validate against device configuration + if (initLedsConfiguration()) + { + if (ProviderUdp::open() == 0) + { + // Everything is OK, device is ready + _isDeviceReady = true; + retval = 0; + } + } + } + else + { + _restApi->setHost(_address.toString()); + _restApi->setPort(_apiPort); + } } - return retval; } @@ -414,13 +410,23 @@ QJsonObject LedDeviceNanoleaf::discover(const QJsonObject& /*params*/) QJsonObject devicesDiscovered; devicesDiscovered.insert("ledDeviceType", _activeDeviceType); - QString discoveryMethod("ssdp"); QJsonArray deviceList; +#ifdef ENABLE_MDNS + QString discoveryMethod("mDNS"); + deviceList = MdnsBrowser::getInstance().getServicesDiscoveredJson( + MdnsServiceRegister::getServiceType(_activeDeviceType), + MdnsServiceRegister::getServiceNameFilter(_activeDeviceType), + DEFAULT_DISCOVER_TIMEOUT + ); +#else + QString discoveryMethod("ssdp"); deviceList = discover(); +#endif devicesDiscovered.insert("discoveryMethod", discoveryMethod); devicesDiscovered.insert("devices", deviceList); + DebugIf(verbose,_log, "devicesDiscovered: [%s]", QString(QJsonDocument(devicesDiscovered).toJson(QJsonDocument::Compact)).toUtf8().constData()); return devicesDiscovered; @@ -431,27 +437,29 @@ QJsonObject LedDeviceNanoleaf::getProperties(const QJsonObject& params) DebugIf(verbose,_log, "params: [%s]", QString(QJsonDocument(params).toJson(QJsonDocument::Compact)).toUtf8().constData()); QJsonObject properties; - // Get Nanoleaf device properties - QString hostName = params["host"].toString(""); + _hostName = params[CONFIG_HOST].toString(""); + _apiPort = API_DEFAULT_PORT; + _authToken = params["token"].toString(""); - if (!hostName.isEmpty()) + Info(_log, "Get properties for %s, hostname (%s)", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(_hostName) ); + + if (NetUtils::resolveHostToAddress(_log, _hostName, _address, _apiPort)) { - QString authToken = params["token"].toString(""); - QString filter = params["filter"].toString(""); - - initRestAPI(hostName, API_DEFAULT_PORT, authToken); - _restApi->setPath(filter); - - // Perform request - httpResponse response = _restApi->get(); - if (response.error()) + if ( openRestAPI() ) { - Warning(_log, "%s get properties failed with error: '%s'", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(response.getErrorReason())); + QString filter = params["filter"].toString(""); + _restApi->setPath(filter); + + // Perform request + httpResponse response = _restApi->get(); + if (response.error()) + { + Warning(_log, "%s get properties failed with error: '%s'", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(response.getErrorReason())); + } + properties.insert("properties", response.getBody().object()); } - properties.insert("properties", response.getBody().object()); - - DebugIf(verbose,_log, "properties: [%s]", QString(QJsonDocument(properties).toJson(QJsonDocument::Compact)).toUtf8().constData()); + DebugIf(verbose, _log, "properties: [%s]", QString(QJsonDocument(properties).toJson(QJsonDocument::Compact)).toUtf8().constData()); } return properties; } @@ -460,19 +468,24 @@ void LedDeviceNanoleaf::identify(const QJsonObject& params) { DebugIf(verbose,_log, "params: [%s]", QString(QJsonDocument(params).toJson(QJsonDocument::Compact)).toUtf8().constData()); - QString hostName = params["host"].toString(""); - if (!hostName.isEmpty()) + _hostName = params[CONFIG_HOST].toString(""); + _apiPort = API_DEFAULT_PORT;if (NetUtils::resolveHostToAddress(_log, _hostName, _address)) + _authToken = params["token"].toString(""); + + Info(_log, "Identify %s, hostname (%s)", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(_hostName) ); + + if (NetUtils::resolveHostToAddress(_log, _hostName, _address, _apiPort)) { - QString authToken = params["token"].toString(""); - - initRestAPI(hostName, API_DEFAULT_PORT, authToken); - _restApi->setPath("identify"); - - // Perform request - httpResponse response = _restApi->put(); - if (response.error()) + if ( openRestAPI() ) { - Warning(_log, "%s identification failed with error: '%s'", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(response.getErrorReason())); + _restApi->setPath("identify"); + + // Perform request + httpResponse response = _restApi->put(); + if (response.error()) + { + Warning(_log, "%s identification failed with error: '%s'", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(response.getErrorReason())); + } } } } @@ -662,7 +675,7 @@ bool LedDeviceNanoleaf::restoreState() Warning (_log, "%s restoring effect failed with error: '%s'", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(response.getErrorReason())); } } else { - Warning (_log, "%s restoring effect failed with error: Cannot restore dynamic or solid effect. Turning device off", QSTRING_CSTR(_activeDeviceType)); + Warning (_log, "%s restoring effect failed with error: Cannot restore dynamic or solid effect. Device is switched off", QSTRING_CSTR(_activeDeviceType)); _originalIsOn = false; } break; diff --git a/libsrc/leddevice/dev_net/LedDeviceNanoleaf.h b/libsrc/leddevice/dev_net/LedDeviceNanoleaf.h index 05f49887..a1df1f80 100644 --- a/libsrc/leddevice/dev_net/LedDeviceNanoleaf.h +++ b/libsrc/leddevice/dev_net/LedDeviceNanoleaf.h @@ -150,13 +150,9 @@ private: /// /// @brief Initialise the access to the REST-API wrapper /// - /// @param[in] host - /// @param[in] port - /// @param[in] authentication token - /// /// @return True, if success /// - bool initRestAPI(const QString& hostname, int port, const QString& token); + bool openRestAPI(); /// /// @brief Get Nanoleaf device details and configuration @@ -188,9 +184,7 @@ private: ///REST-API wrapper ProviderRestApi* _restApi; - - QString _hostName; - int _apiPort; + int _apiPort; QString _authToken; bool _topDown; diff --git a/libsrc/leddevice/dev_net/LedDevicePhilipsHue.cpp b/libsrc/leddevice/dev_net/LedDevicePhilipsHue.cpp index 4f7eeba7..4ceda587 100644 --- a/libsrc/leddevice/dev_net/LedDevicePhilipsHue.cpp +++ b/libsrc/leddevice/dev_net/LedDevicePhilipsHue.cpp @@ -1,10 +1,17 @@ // Local-Hyperion includes #include "LedDevicePhilipsHue.h" +#include + #include #include -#include +// mDNS discover +#ifdef ENABLE_MDNS +#include +#include +#endif +#include // Constants namespace { @@ -26,13 +33,6 @@ const char CONFIG_USE_HUE_ENTERTAINMENT_API[] = "useEntertainmentAPI"; const char CONFIG_GROUPID[] = "groupId"; const char CONFIG_VERBOSE[] = "verbose"; -const char CONFIG_BRIGHTNESS_MIN[] = "brightnessMin"; -const char CONFIG_BRIGHTNESS_MAX[] = "brightnessMax"; -const char CONFIG_BRIGHTNESS_THRESHOLD[] = "brightnessThreshold"; - -const char CONFIG_SSL_HANDSHAKE_TIMEOUT_MIN[] = "sslHSTimeoutMin"; -const char CONFIG_SSL_HANDSHAKE_TIMEOUT_MAX[] = "sslHSTimeoutMax"; -const char CONFIG_SSL_READ_TIMEOUT[] = "sslReadTimeout"; // Device Data elements const char DEV_DATA_BRIDGEID[] = "bridgeid"; @@ -44,7 +44,7 @@ const char DEV_DATA_APIVERSION[] = "apiversion"; // Philips Hue OpenAPI URLs const int API_DEFAULT_PORT = -1; //Use default port per communication scheme -const char API_BASE_PATH[] = "/api/%1/"; +const char API_BASE_PATH[] = "/api/%1"; const char API_ROOT[] = ""; const char API_STATE[] = "state"; const char API_CONFIG[] = "config"; @@ -92,11 +92,13 @@ const char SSDP_FILTER_HEADER[] = "SERVER"; const char API_SSL_SERVER_NAME[] = "Hue"; const char API_SSL_SEED_CUSTOM[] = "dtls_client"; const int API_SSL_SERVER_PORT = 2100; -const int STREAM_CONNECTION_RETRYS = 5; +const int STREAM_CONNECTION_RETRYS = 20; const int STREAM_SSL_HANDSHAKE_ATTEMPTS = 5; -constexpr std::chrono::milliseconds STREAM_REWRITE_TIME{20}; const int SSL_CIPHERSUITES[2] = { MBEDTLS_TLS_PSK_WITH_AES_128_GCM_SHA256, 0 }; +//Enable rewrites that Hue-Bridge does not close the connection ("After 10 seconds of no activity the connection is closed automatically, and status is set back to inactive.") +constexpr std::chrono::milliseconds STREAM_REWRITE_TIME{5000}; + } //End of constants bool operator ==(const CiColor& p1, const CiColor& p2) @@ -109,18 +111,25 @@ bool operator != (const CiColor& p1, const CiColor& p2) return !(p1 == p2); } -CiColor CiColor::rgbToCiColor(double red, double green, double blue, const CiColorTriangle &colorSpace) +CiColor CiColor::rgbToCiColor(double red, double green, double blue, const CiColorTriangle& colorSpace, bool candyGamma) { double cx; double cy; double bri; - if( (red + green + blue) > 0) + if (red + green + blue > 0) { // Apply gamma correction. - double r = (red > 0.04045) ? pow((red + 0.055) / (1.0 + 0.055), 2.4) : (red / 12.92); - double g = (green > 0.04045) ? pow((green + 0.055) / (1.0 + 0.055), 2.4) : (green / 12.92); - double b = (blue > 0.04045) ? pow((blue + 0.055) / (1.0 + 0.055), 2.4) : (blue / 12.92); + double r = red; + double g = green; + double b = blue; + + if (candyGamma) + { + r = (red > 0.04045) ? pow((red + 0.055) / (1.0 + 0.055), 2.4) : (red / 12.92); + g = (green > 0.04045) ? pow((green + 0.055) / (1.0 + 0.055), 2.4) : (green / 12.92); + b = (blue > 0.04045) ? pow((blue + 0.055) / (1.0 + 0.055), 2.4) : (blue / 12.92); + } // Convert to XYZ space. double X = r * 0.664511 + g * 0.154324 + b * 0.162028; @@ -248,6 +257,10 @@ LedDevicePhilipsHueBridge::LedDevicePhilipsHueBridge(const QJsonObject &deviceCo , _api_patch(0) , _isHueEntertainmentReady(false) { +#ifdef ENABLE_MDNS + QMetaObject::invokeMethod(&MdnsBrowser::getInstance(), "browseForServiceType", + Qt::QueuedConnection, Q_ARG(QByteArray, MdnsServiceRegister::getServiceType(_activeDeviceType))); +#endif } LedDevicePhilipsHueBridge::~LedDevicePhilipsHueBridge() @@ -258,103 +271,138 @@ LedDevicePhilipsHueBridge::~LedDevicePhilipsHueBridge() bool LedDevicePhilipsHueBridge::init(const QJsonObject &deviceConfig) { - _useHueEntertainmentAPI = deviceConfig[CONFIG_USE_HUE_ENTERTAINMENT_API].toBool(false); - - // Overwrite non supported/required features - _devConfig["latchTime"] = 0; - if ( deviceConfig["rewriteTime"].toInt(0) > 0 ) - { - InfoIf ( ( !_useHueEntertainmentAPI ), _log, "Device Philips Hue does not require rewrites. Refresh time is ignored." ); - _devConfig["rewriteTime"] = 0; - } - DebugIf( verbose, _log, "deviceConfig: [%s]", QString(QJsonDocument(_devConfig).toJson(QJsonDocument::Compact)).toUtf8().constData() ); bool isInitOK = false; - if ( LedDevice::init(deviceConfig) ) + //Set hostname as per configuration and default port + _hostName = deviceConfig[CONFIG_HOST].toString(); + _apiPort = deviceConfig[CONFIG_PORT].toInt(); + _authToken = deviceConfig[CONFIG_USERNAME].toString(); + + Debug(_log, "Hostname/IP: %s", QSTRING_CSTR(_hostName) ); + + if( _useHueEntertainmentAPI ) { + setLatchTime( 0); + _devConfig["sslport"] = API_SSL_SERVER_PORT; + _devConfig["servername"] = API_SSL_SERVER_NAME; + _devConfig["psk"] = _devConfig[ CONFIG_CLIENTKEY ].toString(); + _devConfig["psk_identity"] = _devConfig[ CONFIG_USERNAME ].toString(); + _devConfig["seed_custom"] = API_SSL_SEED_CUSTOM; + _devConfig["retry_left"] = STREAM_CONNECTION_RETRYS; + _devConfig["hs_attempts"] = STREAM_SSL_HANDSHAKE_ATTEMPTS; + _devConfig["hs_timeout_min"] = 600; + _devConfig["hs_timeout_max"] = 1000; - log( "DeviceType", "%s", QSTRING_CSTR( this->getActiveDeviceType() ) ); - log( "LedCount", "%d", this->getLedCount() ); - log( "ColorOrder", "%s", QSTRING_CSTR( this->getColorOrder() ) ); - log( "RefreshTime", "%d", _refreshTimerInterval_ms ); - log( "LatchTime", "%d", this->getLatchTime() ); - - //Set hostname as per configuration and_defaultHost default port - _hostname = deviceConfig[CONFIG_HOST].toString(); - - //If host not configured the init failed - if (_hostname.isEmpty()) - { - this->setInError("No target hostname defined"); - isInitOK = false; - } - else - { - log("Hostname", "%s", QSTRING_CSTR(_hostname)); - - int _apiPort = deviceConfig[CONFIG_PORT].toInt(); - if (_apiPort == 0) - { - _apiPort = API_DEFAULT_PORT; - } - log("Port", "%d", _apiPort); - - _username = deviceConfig[CONFIG_USERNAME].toString(); - - if (initRestAPI(_hostname, _apiPort, _username)) - { - if (initMaps()) - { - isInitOK = ProviderUdpSSL::init(_devConfig); - } - } - } + isInitOK = ProviderUdpSSL::init(_devConfig); + } + else + { + isInitOK = LedDevice::init(_devConfig); // NOLINT } return isInitOK; } -bool LedDevicePhilipsHueBridge::initRestAPI(const QString &hostname, int port, const QString &token) +bool LedDevicePhilipsHueBridge::openRestAPI() { - bool isInitOK = false; + bool isInitOK {true}; - if ( _restApi == nullptr ) + if (_restApi == nullptr) { - _restApi = new ProviderRestApi(hostname, port); + _restApi = new ProviderRestApi(_address.toString(), _apiPort); + _restApi->setLogger(_log); //Base-path is api-path + authentication token (here username) - _restApi->setBasePath( QString(API_BASE_PATH).arg(token) ); - - isInitOK = true; + _restApi->setBasePath(QString(API_BASE_PATH).arg(_authToken)); + } + else + { + _restApi->setHost(_address.toString()); + _restApi->setPort(_apiPort); } - + //Workaround until API v2 with https is supported + if (_apiPort == 0 || _apiPort == 443) + { + _apiPort = API_DEFAULT_PORT; + _restApi->setPort(_apiPort); + } return isInitOK; } +bool LedDevicePhilipsHueBridge::checkApiError(const QJsonDocument &response, bool supressError) +{ + bool apiError = false; + QString errorReason; + + QString strJson(response.toJson(QJsonDocument::Compact)); + DebugIf(verbose, _log, "Reply: [%s]", strJson.toUtf8().constData()); + + QVariantList rspList = response.toVariant().toList(); + if ( !rspList.isEmpty() ) + { + QVariantMap map = rspList.first().toMap(); + if ( map.contains( API_ERROR ) ) + { + // API call failed to execute an error message was returned + QString errorAddress = map.value(API_ERROR).toMap().value(API_ERROR_ADDRESS).toString(); + QString errorDesc = map.value(API_ERROR).toMap().value(API_ERROR_DESCRIPTION).toString(); + QString errorType = map.value(API_ERROR).toMap().value(API_ERROR_TYPE).toString(); + + log("Error Type", "%s", QSTRING_CSTR(errorType)); + log("Error Address", "%s", QSTRING_CSTR(errorAddress)); + log("Error Address Description", "%s", QSTRING_CSTR(errorDesc)); + + if( errorType != "901" ) + { + errorReason = QString ("(%1) %2, Resource:%3").arg(errorType, errorDesc, errorAddress); + if (!supressError) + { + this->setInError(errorReason); + } + else + { + Warning(_log, "Suppresing error: %s", QSTRING_CSTR(errorReason)); + } + apiError = true; + } + } + } + return apiError; +} + int LedDevicePhilipsHueBridge::open() { int retval = -1; _isDeviceReady = false; - if( _useHueEntertainmentAPI ) + if (NetUtils::resolveHostToAddress(_log, _hostName, _address, _apiPort)) { - // Open bridge for streaming - if ( ProviderUdpSSL::open() == 0 ) + if ( openRestAPI() ) { - // Everything is OK, device is ready - _isDeviceReady = true; - retval = 0; + if (initMaps()) + { + if ( _useHueEntertainmentAPI ) + { + // Open bridge for streaming + if ( ProviderUdpSSL::open() == 0 ) + { + // Everything is OK, device is ready + _isDeviceReady = true; + retval = 0; + } + } + else + { + // Everything is OK, device is ready + _isDeviceReady = true; + retval = 0; + } + } } } - else - { - // Everything is OK, device is ready - _isDeviceReady = true; - retval = 0; - } return retval; } @@ -407,6 +455,11 @@ bool LedDevicePhilipsHueBridge::initMaps() DebugIf( verbose, _log, "doc: [%s]", QString(QJsonDocument(doc).toJson(QJsonDocument::Compact)).toUtf8().constData() ); + if (doc.isEmpty()) + { + setInError("Could not read the Hue Bridge details"); + } + if ( this->isInError() ) { isInitOK = false; @@ -510,17 +563,17 @@ void LedDevicePhilipsHueBridge::setGroupMap(const QJsonDocument &doc) } } -QMap LedDevicePhilipsHueBridge::getLightMap() const +QMap LedDevicePhilipsHueBridge::getLightMap() const { return _lightsMap; } -QMap LedDevicePhilipsHueBridge::getGroupMap() const +QMap LedDevicePhilipsHueBridge::getGroupMap() const { return _groupsMap; } -QString LedDevicePhilipsHueBridge::getGroupName(quint16 groupId) const +QString LedDevicePhilipsHueBridge::getGroupName(int groupId) const { QString groupName; if( _groupsMap.contains( groupId ) ) @@ -535,7 +588,7 @@ QString LedDevicePhilipsHueBridge::getGroupName(quint16 groupId) const return groupName; } -QJsonArray LedDevicePhilipsHueBridge::getGroupLights(quint16 groupId) const +QJsonArray LedDevicePhilipsHueBridge::getGroupLights(int groupId) const { QJsonArray groupLights; // search user groupid inside _groupsMap and create light if found @@ -565,38 +618,28 @@ QJsonArray LedDevicePhilipsHueBridge::getGroupLights(quint16 groupId) const return groupLights; } -bool LedDevicePhilipsHueBridge::checkApiError(const QJsonDocument &response) +QJsonDocument LedDevicePhilipsHueBridge::getLightState(int lightId) { - bool apiError = false; - QString errorReason; + DebugIf( verbose, _log, "GetLightState [%d]", lightId ); + return get( QString("%1/%2").arg( API_LIGHTS ).arg( lightId ) ); +} - QString strJson(response.toJson(QJsonDocument::Compact)); - DebugIf(verbose, _log, "Reply: [%s]", strJson.toUtf8().constData()); +void LedDevicePhilipsHueBridge::setLightState(int lightId, const QString &state) +{ + DebugIf( verbose, _log, "SetLightState [%d]: %s", lightId, QSTRING_CSTR(state) ); + put( QString("%1/%2/%3").arg( API_LIGHTS ).arg( lightId ).arg( API_STATE ), state ); +} - QVariantList rspList = response.toVariant().toList(); - if ( !rspList.isEmpty() ) - { - QVariantMap map = rspList.first().toMap(); - if ( map.contains( API_ERROR ) ) - { - // API call failed to execute an error message was returned - QString errorAddress = map.value(API_ERROR).toMap().value(API_ERROR_ADDRESS).toString(); - QString errorDesc = map.value(API_ERROR).toMap().value(API_ERROR_DESCRIPTION).toString(); - QString errorType = map.value(API_ERROR).toMap().value(API_ERROR_TYPE).toString(); +QJsonDocument LedDevicePhilipsHueBridge::getGroupState(int groupId) +{ + DebugIf( verbose, _log, "GetGroupState [%d]", groupId ); + return get( QString("%1/%2").arg( API_GROUPS ).arg( groupId ) ); +} - log( "Error Type", "%s", QSTRING_CSTR( errorType ) ); - log( "Error Address", "%s", QSTRING_CSTR( errorAddress ) ); - log( "Error Address Description", "%s", QSTRING_CSTR( errorDesc ) ); - - if( errorType != "901" ) - { - errorReason = QString ("(%1) %2, Resource:%3").arg(errorType, errorDesc, errorAddress); - this->setInError( errorReason ); - apiError = true; - } - } - } - return apiError; +QJsonDocument LedDevicePhilipsHueBridge::setGroupState(int groupId, bool state) +{ + QString active = state ? API_STREAM_ACTIVE_VALUE_TRUE : API_STREAM_ACTIVE_VALUE_FALSE; + return put(QString("%1/%2").arg(API_GROUPS).arg(groupId), QString("{\"%1\":{\"%2\":%3}}").arg(API_STREAM, API_STREAM_ACTIVE, active), true); } QJsonDocument LedDevicePhilipsHueBridge::get(const QString& route) @@ -604,46 +647,146 @@ QJsonDocument LedDevicePhilipsHueBridge::get(const QString& route) _restApi->setPath(route); httpResponse response = _restApi->get(); + + if (route.isEmpty() && response.error() && + ( response.getNetworkReplyError() == QNetworkReply::UnknownNetworkError || + response.getNetworkReplyError() == QNetworkReply::ConnectionRefusedError || + response.getNetworkReplyError() == QNetworkReply::RemoteHostClosedError || + response.getNetworkReplyError() == QNetworkReply::OperationCanceledError )) + { + Warning(_log, "The Hue Bridge is not ready."); + } + checkApiError(response.getBody()); return response.getBody(); } -QJsonDocument LedDevicePhilipsHueBridge::post(const QString& route, const QString& content) +QJsonDocument LedDevicePhilipsHueBridge::put(const QString& route, const QString& content, bool supressError) { _restApi->setPath(route); httpResponse response = _restApi->put(content); - checkApiError(response.getBody()); + checkApiError(response.getBody(), supressError); return response.getBody(); } -QJsonDocument LedDevicePhilipsHueBridge::getLightState(unsigned int lightId) -{ - DebugIf( verbose, _log, "GetLightState [%u]", lightId ); - return get( QString("%1/%2").arg( API_LIGHTS ).arg( lightId ) ); -} - -void LedDevicePhilipsHueBridge::setLightState(unsigned int lightId, const QString &state) -{ - DebugIf( verbose, _log, "SetLightState [%u]: %s", lightId, QSTRING_CSTR(state) ); - post( QString("%1/%2/%3").arg( API_LIGHTS ).arg( lightId ).arg( API_STATE ), state ); -} - -QJsonDocument LedDevicePhilipsHueBridge::getGroupState(unsigned int groupId) -{ - DebugIf( verbose, _log, "GetGroupState [%u]", groupId ); - return get( QString("%1/%2").arg( API_GROUPS ).arg( groupId ) ); -} - -QJsonDocument LedDevicePhilipsHueBridge::setGroupState(unsigned int groupId, bool state) -{ - QString active = state ? API_STREAM_ACTIVE_VALUE_TRUE : API_STREAM_ACTIVE_VALUE_FALSE; - return post( QString("%1/%2").arg( API_GROUPS ).arg( groupId ), QString("{\"%1\":{\"%2\":%3}}").arg( API_STREAM, API_STREAM_ACTIVE, active ) ); -} - bool LedDevicePhilipsHueBridge::isStreamOwner(const QString &streamOwner) const { - return ( streamOwner != "" && streamOwner == _username ); + return ( streamOwner != "" && streamOwner == _authToken ); +} + +QJsonArray LedDevicePhilipsHueBridge::discover() +{ + QJsonArray deviceList; + + SSDPDiscover discover; + + discover.skipDuplicateKeys(true); + discover.setSearchFilter(SSDP_FILTER, SSDP_FILTER_HEADER); + QString searchTarget = SSDP_ID; + + if (discover.discoverServices(searchTarget) > 0) + { + deviceList = discover.getServicesDiscoveredJson(); + } + + return deviceList; +} + +QJsonObject LedDevicePhilipsHueBridge::discover(const QJsonObject& /*params*/) +{ + QJsonObject devicesDiscovered; + devicesDiscovered.insert("ledDeviceType", _activeDeviceType ); + + QJsonArray deviceList; + +#ifdef ENABLE_MDNS + QString discoveryMethod("mDNS"); + deviceList = MdnsBrowser::getInstance().getServicesDiscoveredJson( + MdnsServiceRegister::getServiceType(_activeDeviceType), + MdnsServiceRegister::getServiceNameFilter(_activeDeviceType), + DEFAULT_DISCOVER_TIMEOUT + ); +#else + QString discoveryMethod("ssdp"); + deviceList = discover(); +#endif + + devicesDiscovered.insert("discoveryMethod", discoveryMethod); + devicesDiscovered.insert("devices", deviceList); + + Debug(_log, "devicesDiscovered: [%s]", QString(QJsonDocument(devicesDiscovered).toJson(QJsonDocument::Compact)).toUtf8().constData() ); + + return devicesDiscovered; +} + +QJsonObject LedDevicePhilipsHueBridge::getProperties(const QJsonObject& params) +{ + DebugIf(verbose, _log, "params: [%s]", QString(QJsonDocument(params).toJson(QJsonDocument::Compact)).toUtf8().constData()); + QJsonObject properties; + + _hostName = params[CONFIG_HOST].toString(""); + _apiPort = params[CONFIG_PORT].toInt(); + _authToken = params["user"].toString(""); + + Info(_log, "Get properties for %s, hostname (%s)", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(_hostName) ); + + if (NetUtils::resolveHostToAddress(_log, _hostName, _address, _apiPort)) + { + if ( openRestAPI() ) + { + QString filter = params["filter"].toString(""); + _restApi->setPath(filter); + + // Perform request + httpResponse response = _restApi->get(); + if (response.error()) + { + Warning(_log, "%s get properties failed with error: '%s'", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(response.getErrorReason())); + } + + // Perform request + properties.insert("properties", response.getBody().object()); + } + + DebugIf(verbose, _log, "properties: [%s]", QString(QJsonDocument(properties).toJson(QJsonDocument::Compact)).toUtf8().constData()); + } + return properties; +} + +QJsonObject LedDevicePhilipsHueBridge::addAuthorization(const QJsonObject& params) +{ + Debug(_log, "params: [%s]", QString(QJsonDocument(params).toJson(QJsonDocument::Compact)).toUtf8().constData()); + QJsonObject responseBody; + + // New Phillips-Bridge device client/application key + _hostName = params[CONFIG_HOST].toString(""); + _apiPort = params[CONFIG_PORT].toInt(); + + Info(_log, "Add authorized user for %s, hostname (%s)", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(_hostName) ); + + if (NetUtils::resolveHostToAddress(_log, _hostName, _address, _apiPort)) + { + if ( openRestAPI() ) + { + QJsonObject clientKeyCmd{ {"devicetype", "hyperion#" + QHostInfo::localHostName()}, {"generateclientkey", true } }; + _restApi->setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + + httpResponse response = _restApi->post(clientKeyCmd); + if (response.error()) + { + Warning(_log, "%s generation of authorization/client key failed with error: '%s'", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(response.getErrorReason())); + } + else + { + if (!checkApiError(response.getBody(),false)) + { + responseBody = response.getBody().array().first().toObject().value("success").toObject(); + } + } + } + } + return responseBody; } const std::set PhilipsHueLight::GAMUT_A_MODEL_IDS = @@ -651,16 +794,25 @@ const std::set PhilipsHueLight::GAMUT_A_MODEL_IDS = const std::set PhilipsHueLight::GAMUT_B_MODEL_IDS = { "LCT001", "LCT002", "LCT003", "LCT007", "LLM001" }; const std::set PhilipsHueLight::GAMUT_C_MODEL_IDS = - { "LCA001", "LCA002", "LCA003", "LCG002", "LCP001", "LCP002", "LCT010", "LCT011", "LCT012", "LCT014", "LCT015", "LCT016", "LCT024", "LCX001", "LLC020", "LST002" }; + { "LCA001", "LCA002", "LCA003", "LCG002", "LCP001", "LCP002", "LCT010", "LCT011", "LCT012", "LCT014", "LCT015", "LCT016", "LCT024", "LCX001", "LCX002", "LLC020", "LST002" }; -PhilipsHueLight::PhilipsHueLight(Logger* log, unsigned int id, QJsonObject values, unsigned int ledidx) +PhilipsHueLight::PhilipsHueLight(Logger* log, int id, QJsonObject values, int ledidx, int onBlackTimeToPowerOff, + int onBlackTimeToPowerOn) : _log(log) - , _id(id) - , _ledidx(ledidx) - , _on(false) - , _transitionTime(0) - , _colorBlack({0.0, 0.0, 0.0}) - , _modelId(values[API_MODEID].toString().trimmed().replace("\"", "")) + , _id(id) + , _ledidx(ledidx) + , _on(false) + , _transitionTime(0) + , _color({ 0.0, 0.0, 0.0 }) + , _hasColor(false) + , _colorBlack({ 0.0, 0.0, 0.0 }) + , _modelId(values[API_MODEID].toString().trimmed().replace("\"", "")) + , _lastSendColorTime(0) + , _lastBlackTime(-1) + , _lastWhiteTime(-1) + , _blackScreenTriggered(false) + , _onBlackTimeToPowerOff(onBlackTimeToPowerOff) + , _onBlackTimeToPowerOn(onBlackTimeToPowerOn) { // Find id in the sets and set the appropriate color space. if (GAMUT_A_MODEL_IDS.find(_modelId) != GAMUT_A_MODEL_IDS.end()) @@ -697,15 +849,74 @@ PhilipsHueLight::PhilipsHueLight(Logger* log, unsigned int id, QJsonObject value } _lightname = values["name"].toString().trimmed().replace("\"", ""); - Info(_log, "Light ID %d (\"%s\", LED index \"%d\") created", id, QSTRING_CSTR(_lightname), ledidx ); + Info(_log, "Light ID %d (\"%s\", LED index \"%d\", onBlackTimeToPowerOff: %d, _onBlackTimeToPowerOn: %d) created", id, QSTRING_CSTR(_lightname), ledidx, _onBlackTimeToPowerOff, _onBlackTimeToPowerOn); } -PhilipsHueLight::~PhilipsHueLight() +void PhilipsHueLight::blackScreenTriggered() { - DebugIf(verbose, _log, "Light ID %d (\"%s\", LED index \"%d\") deconstructed", _id, QSTRING_CSTR(_lightname), _ledidx ); + _blackScreenTriggered = true; } -unsigned int PhilipsHueLight::getId() const +bool PhilipsHueLight::isBusy() +{ + bool temp = true; + + qint64 _currentTime = QDateTime::currentMSecsSinceEpoch(); + if (_currentTime - _lastSendColorTime >= 100) + { + _lastSendColorTime = _currentTime; + temp = false; + } + + return temp; +} + +void PhilipsHueLight::setBlack() +{ + CiColor black; + black.bri = 0; + black.x = 0; + black.y = 0; + setColor(black); +} + +bool PhilipsHueLight::isBlack(bool isBlack) +{ + if (!isBlack) + { + _lastBlackTime = 0; + return false; + } + + if (_lastBlackTime == 0) + { + _lastBlackTime = QDateTime::currentMSecsSinceEpoch(); + return false; + } + + qint64 _currentTime = QDateTime::currentMSecsSinceEpoch(); + return _currentTime - _lastBlackTime >= _onBlackTimeToPowerOff; +} + +bool PhilipsHueLight::isWhite(bool isWhite) +{ + if (!isWhite) + { + _lastWhiteTime = 0; + return false; + } + + if (_lastWhiteTime == 0) + { + _lastWhiteTime = QDateTime::currentMSecsSinceEpoch(); + return false; + } + + qint64 _currentTime = QDateTime::currentMSecsSinceEpoch(); + return _currentTime - _lastWhiteTime >= _onBlackTimeToPowerOn; +} + +int PhilipsHueLight::getId() const { return _id; } @@ -717,6 +928,11 @@ QString PhilipsHueLight::getOriginalState() const void PhilipsHueLight::saveOriginalState(const QJsonObject& values) { + if (_blackScreenTriggered) + { + _blackScreenTriggered = false; + return; + } // Get state object values which are subject to change. if (!values[API_STATE].toObject().contains("on")) { @@ -727,7 +943,10 @@ void PhilipsHueLight::saveOriginalState(const QJsonObject& values) QJsonObject state; state["on"] = lState["on"]; - _originalColor = _colorBlack; + _originalColor = CiColor(); + _originalColor.bri = 0; + _originalColor.x = 0; + _originalColor.y = 0; QString c; if (state[API_STATE_ON].toBool()) { @@ -741,7 +960,7 @@ void PhilipsHueLight::saveOriginalState(const QJsonObject& values) }; _originalColor = _color; c = QString("{ \"%1\": [%2, %3], \"%4\": %5 }").arg(API_XY_COORDINATES).arg(_originalColor.x, 0, 'd', 4).arg(_originalColor.y, 0, 'd', 4).arg(API_BRIGHTNESS).arg((_originalColor.bri * 254.0), 0, 'd', 4); - DebugIf(verbose, _log, "OriginalColor state on: %s", QSTRING_CSTR(c)); + Debug(_log, "Philips original state stored: %s", QSTRING_CSTR(c)); _transitionTime = values[API_STATE].toObject()[API_TRANSITIONTIME].toInt(); } //Determine the original state. @@ -760,6 +979,7 @@ void PhilipsHueLight::setTransitionTime(int transitionTime) void PhilipsHueLight::setColor(const CiColor& color) { + this->_hasColor = true; this->_color = color; } @@ -778,6 +998,10 @@ CiColor PhilipsHueLight::getColor() const return _color; } +bool PhilipsHueLight::hasColor() const +{ + return _hasColor; +} CiColorTriangle PhilipsHueLight::getColorSpace() const { return _colorSpace; @@ -785,24 +1009,23 @@ CiColorTriangle PhilipsHueLight::getColorSpace() const LedDevicePhilipsHue::LedDevicePhilipsHue(const QJsonObject& deviceConfig) : LedDevicePhilipsHueBridge(deviceConfig) - , _switchOffOnBlack(false) - , _brightnessFactor(1.0) - , _transitionTime(1) - , _isInitLeds(false) - , _lightsCount(0) - , _groupId(0) - , _brightnessMin(0.0) - , _brightnessMax(1.0) - , _allLightsBlack(false) - , _blackLightsTimer(nullptr) - , _blackLightsTimeout(15000) - , _brightnessThreshold(0.0) - , _handshake_timeout_min(STREAM_SSL_HANDSHAKE_TIMEOUT_MIN.count()) - , _handshake_timeout_max(STREAM_SSL_HANDSHAKE_TIMEOUT_MAX.count()) - , _ssl_read_timeout(STREAM_SSL_READ_TIMEOUT.count()) - , _stopConnection(false) - , start_retry_left(3) - , stop_retry_left(3) + , _switchOffOnBlack(false) + , _brightnessFactor(1.0) + , _transitionTime(1) + , _isInitLeds(false) + , _lightsCount(0) + , _groupId(0) + , _blackLightsTimeout(15000) + , _blackLevel(0.0) + , _onBlackTimeToPowerOff(100) + , _onBlackTimeToPowerOn(100) + , _candyGamma(true) + , _handshake_timeout_min(600) + , _handshake_timeout_max(2000) + , _stopConnection(false) + , _lastConfirm(0) + , _lastId(-1) + , _groupStreamState(false) { } @@ -813,60 +1036,67 @@ LedDevice* LedDevicePhilipsHue::construct(const QJsonObject &deviceConfig) LedDevicePhilipsHue::~LedDevicePhilipsHue() { - delete _blackLightsTimer; } bool LedDevicePhilipsHue::init(const QJsonObject &deviceConfig) { - verbose = deviceConfig[CONFIG_VERBOSE].toBool(false); + bool isInitOK {false}; - bool isInitOK = LedDevicePhilipsHueBridge::init(deviceConfig); - - if ( isInitOK ) + if (!verbose) { - // Initialise LedDevice configuration and execution environment - _switchOffOnBlack = _devConfig[CONFIG_ON_OFF_BLACK].toBool(true); - _blackLightsTimeout = _devConfig[CONFIG_BLACK_LIGHTS_TIMEOUT].toInt(15000); - _brightnessFactor = _devConfig[CONFIG_BRIGHTNESSFACTOR].toDouble(1.0); - _transitionTime = _devConfig[CONFIG_TRANSITIONTIME].toInt(1); - _isRestoreOrigState = _devConfig[CONFIG_RESTORE_STATE].toBool(true); - _groupId = static_cast(_devConfig[CONFIG_GROUPID].toInt(0)); - _brightnessMin = _devConfig[CONFIG_BRIGHTNESS_MIN].toDouble(0.0); - _brightnessMax = _devConfig[CONFIG_BRIGHTNESS_MAX].toDouble(1.0); - _brightnessThreshold = _devConfig[CONFIG_BRIGHTNESS_THRESHOLD].toDouble(0.0); - _handshake_timeout_min = _devConfig[CONFIG_SSL_HANDSHAKE_TIMEOUT_MIN].toInt(STREAM_SSL_HANDSHAKE_TIMEOUT_MIN.count()); - _handshake_timeout_max = _devConfig[CONFIG_SSL_HANDSHAKE_TIMEOUT_MAX].toInt(STREAM_SSL_HANDSHAKE_TIMEOUT_MAX.count()); - _ssl_read_timeout = _devConfig[CONFIG_SSL_READ_TIMEOUT].toInt(STREAM_SSL_READ_TIMEOUT.count()); + verbose = deviceConfig[CONFIG_VERBOSE].toBool(false); + } - if( _brightnessMin < 0.0 ) { _brightnessMin = 0.0; } - if( _brightnessMax > 1.0 ) { _brightnessMax = 1.0; } - if( _brightnessThreshold < 0.0 ) { _brightnessThreshold = 0.0; } - if( _brightnessThreshold > 1.0 ) { _brightnessThreshold = 1.0; } + // Initialise LedDevice configuration and execution environment + _useHueEntertainmentAPI = deviceConfig[CONFIG_USE_HUE_ENTERTAINMENT_API].toBool(false); - if( _handshake_timeout_min <= 0 ) { _handshake_timeout_min = 1; } + // Overwrite non supported/required features + if ( deviceConfig["rewriteTime"].toInt(0) > 0 ) + { + InfoIf ( ( !_useHueEntertainmentAPI ), _log, "Device Philips Hue does not require rewrites. Refresh time is ignored." ); + _devConfig["rewriteTime"] = 0; + } + _switchOffOnBlack = _devConfig[CONFIG_ON_OFF_BLACK].toBool(true); + _blackLightsTimeout = _devConfig[CONFIG_BLACK_LIGHTS_TIMEOUT].toInt(15000); + _brightnessFactor = _devConfig[CONFIG_BRIGHTNESSFACTOR].toDouble(1.0); + _transitionTime = _devConfig[CONFIG_TRANSITIONTIME].toInt(1); + _isRestoreOrigState = _devConfig[CONFIG_RESTORE_STATE].toBool(true); + _groupId = _devConfig[CONFIG_GROUPID].toInt(0); + _blackLevel = _devConfig["blackLevel"].toDouble(0.0); + _onBlackTimeToPowerOff = _devConfig["onBlackTimeToPowerOff"].toInt(100); + _onBlackTimeToPowerOn = _devConfig["onBlackTimeToPowerOn"].toInt(100); + _candyGamma = _devConfig["candyGamma"].toBool(true); + + if (_blackLevel < 0.0) { _blackLevel = 0.0; } + if (_blackLevel > 1.0) { _blackLevel = 1.0; } + + if (LedDevicePhilipsHueBridge::init(_devConfig)) + { log( "Off on Black", "%d", static_cast( _switchOffOnBlack ) ); log( "Brightness Factor", "%f", _brightnessFactor ); log( "Transition Time", "%d", _transitionTime ); log( "Restore Original State", "%d", static_cast( _isRestoreOrigState ) ); log( "Use Hue Entertainment API", "%d", static_cast( _useHueEntertainmentAPI) ); + log("Brightness Threshold", "%f", _blackLevel); + log("CandyGamma", "%d", static_cast(_candyGamma)); + log("Time powering off when black", "%d", _onBlackTimeToPowerOff); + log("Time powering on when signalled", "%d", _onBlackTimeToPowerOn); if( _useHueEntertainmentAPI ) { log( "Entertainment API Group-ID", "%d", _groupId ); - log( "Signal Timeout on Black", "%dms", _blackLightsTimeout ); - log( "Brightness Min", "%f", _brightnessMin ); - log( "Brightness Max", "%f", _brightnessMax ); - log( "Brightness Threshold", "%f", _brightnessThreshold ); if( _groupId == 0 ) { - Error(_log, "Disabling Entertainment API as Group-ID is invalid" ); + Error(_log, "Disabling usage of HueEntertainmentAPI: Group-ID is invalid", "%d", _groupId); _useHueEntertainmentAPI = false; } } - isInitOK = initLeds(); + // Everything is OK -> enable + _isDeviceInitialised = true; + isInitOK = true; } return isInitOK; @@ -899,9 +1129,9 @@ bool LedDevicePhilipsHue::setLights() if( !lArray.empty() ) { - for (const QJsonValueRef id : lArray) + for (const QJsonValue &id : qAsConst(lArray)) { - unsigned int lightId = id.toString().toUInt(); + int lightId = id.toString().toInt(); if( lightId > 0 ) { if(std::find(_lightIds.begin(), _lightIds.end(), lightId) == _lightIds.end()) @@ -918,7 +1148,7 @@ bool LedDevicePhilipsHue::setLights() std::sort( _lightIds.begin(), _lightIds.end() ); } - unsigned int configuredLightsCount = static_cast(_lightIds.size()); + int configuredLightsCount = static_cast(_lightIds.size()); log( "Light-IDs configured", "%d", configuredLightsCount ); @@ -947,30 +1177,6 @@ bool LedDevicePhilipsHue::initLeds() if( _useHueEntertainmentAPI ) { _groupName = getGroupName( _groupId ); - _devConfig["latchTime"] = 0; - _devConfig["host"] = _hostname; - _devConfig["sslport"] = API_SSL_SERVER_PORT; - _devConfig["servername"] = API_SSL_SERVER_NAME; - _devConfig["rewriteTime"] = static_cast( STREAM_REWRITE_TIME.count() ); - _devConfig["psk"] = _devConfig[ CONFIG_CLIENTKEY ].toString(); - _devConfig["psk_identity"] = _devConfig[ CONFIG_USERNAME ].toString(); - _devConfig["seed_custom"] = API_SSL_SEED_CUSTOM; - _devConfig["retry_left"] = STREAM_CONNECTION_RETRYS; - _devConfig["hs_attempts"] = STREAM_SSL_HANDSHAKE_ATTEMPTS; - _devConfig["hs_timeout_min"] = _handshake_timeout_min; - _devConfig["hs_timeout_max"] = _handshake_timeout_max; - _devConfig["read_timeout"] = _ssl_read_timeout; - - isInitOK = ProviderUdpSSL::init( _devConfig ); - - if( isInitOK ) - { - if ( _blackLightsTimer == nullptr ) - { - _blackLightsTimer = new QTimer(this); - connect( _blackLightsTimer, &QTimer::timeout, this, &LedDevicePhilipsHue::noSignalTimeout ); - } - } } else { @@ -989,7 +1195,7 @@ bool LedDevicePhilipsHue::initLeds() return isInitOK; } -bool LedDevicePhilipsHue::updateLights(const QMap &map) +bool LedDevicePhilipsHue::updateLights(const QMap &map) { bool isInitOK = true; @@ -998,13 +1204,13 @@ bool LedDevicePhilipsHue::updateLights(const QMap &map) if(!_lightIds.empty()) { - unsigned int ledidx = 0; + int ledidx = 0; _lights.reserve(_lightIds.size()); for(const auto id : _lightIds) { if (map.contains(id)) { - _lights.emplace_back(_log, id, map.value(id), ledidx); + _lights.emplace_back(_log, id, map.value(id), ledidx, _onBlackTimeToPowerOff, _onBlackTimeToPowerOn); } else { @@ -1030,9 +1236,6 @@ bool LedDevicePhilipsHue::updateLights(const QMap &map) bool LedDevicePhilipsHue::openStream() { bool isInitOK = false; - - start_retry_left = 3; - bool streamState = getStreamGroupState(); if ( !this->isInError() ) @@ -1075,18 +1278,16 @@ bool LedDevicePhilipsHue::openStream() if( isInitOK ) { - Info(_log, "Philips Hue Entertainment API successful connected! Start Streaming." ); - _allLightsBlack = true; - noSignalDetection(); + Info(_log, "Philips Hue Entertainment API successful connected! Start Streaming."); } else { - Error(_log, "Philips Hue Entertainment API not connected!" ); + Error(_log, "Philips Hue Entertainment API not connected!"); } } else { - Error(_log, "Philips Hue Entertainment API could not be initialised!" ); + Error(_log, "Philips Hue Entertainment API could not be initialized!"); } return isInitOK; @@ -1094,61 +1295,44 @@ bool LedDevicePhilipsHue::openStream() bool LedDevicePhilipsHue::startStream() { - Debug(_log, "Start entertainment stream"); - - bool rc = false; - if ( setStreamGroupState( true ) ) + int retries {3}; + while (!setStreamGroupState(true) && --retries > 0) { - start_retry_left = 3; - rc = true; + Debug(_log, "Start Entertainment stream. Retrying..."); + QThread::msleep(500); + } + + bool rc = (retries > 0); + if (rc) + { + Debug(_log, "The Entertainment stream started successfully"); } else { - if ( !this->isInError() ) - { - QThread::msleep(500); - bool streamState = getStreamGroupState(); - - if ( !this->isInError() ) - { - // stream is not active - if( !streamState ) - { - rc = ( start_retry_left-- > 0 ) ? startStream() : false; - } - } - } + this->setInError("The Entertainment stream failed to start. Give up."); } - return rc; } bool LedDevicePhilipsHue::stopStream() { - ProviderUdpSSL::closeSSLConnection(); + stopConnection(); - bool rc = false; - if ( setStreamGroupState( false ) ) + int retries = 3; + while (!setStreamGroupState(false) && --retries > 0) { - stop_retry_left = 3; - rc = true; + Debug(_log, "Stop Entertainment stream. Retrying..."); + QThread::msleep(500); + } + + bool rc = (retries > 0); + if (rc) + { + Debug(_log, "The Entertainment stream stopped successfully"); } else { - if ( !this->isInError() ) - { - QThread::msleep(500); - bool streamState = getStreamGroupState(); - - if ( !this->isInError() ) - { - // stream is still active - if( streamState ) - { - rc = (stop_retry_left-- > 0) ? stopStream() : false; - } - } - } + this->setInError("The Entertainment stream did NOT stop. Give up."); } return rc; @@ -1164,7 +1348,7 @@ bool LedDevicePhilipsHue::getStreamGroupState() if( obj.isEmpty() ) { - this->setInError( "no Streaming Infos in Group found" ); + this->setInError( "No Entertainment/Streaming details in Group found" ); } else { @@ -1192,7 +1376,7 @@ bool LedDevicePhilipsHue::setStreamGroupState(bool state) if ( !map.contains( API_SUCCESS ) ) { - this->setInError( QString("set stream to %1: Neither error nor success contained in Bridge response...").arg( active ) ); + Warning(_log, "%s", QSTRING_CSTR(QString("Set stream to %1: Neither error nor success contained in Bridge response...").arg(active))); } else { @@ -1211,14 +1395,14 @@ bool LedDevicePhilipsHue::setStreamGroupState(bool state) } else { - bool groupStreamState = diyHueActiveState[API_STREAM_ACTIVE].toBool(); - return ( groupStreamState == state ); + _groupStreamState = diyHueActiveState[API_STREAM_ACTIVE].toBool(); + return (_groupStreamState == state); } } else { - bool groupStreamState = map.value( API_SUCCESS ).toMap().value( valueName ).toBool(); - return ( groupStreamState == state ); + _groupStreamState = map.value(API_SUCCESS).toMap().value(valueName).toBool(); + return (_groupStreamState == state); } } } @@ -1253,74 +1437,20 @@ QByteArray LedDevicePhilipsHue::prepareStreamData() const void LedDevicePhilipsHue::stop() { - stopBlackTimeoutTimer(); LedDevicePhilipsHueBridge::stop(); } int LedDevicePhilipsHue::open() -{ - int retval = 0; - _isDeviceReady = true; - - return retval; -} - -int LedDevicePhilipsHue::close() { int retval = -1; - - retval = LedDevicePhilipsHueBridge::close(); - - return retval; -} - -bool LedDevicePhilipsHue::switchOn() -{ - Debug(_log, ""); - - bool rc = false; - - if ( _isOn ) + if ( LedDevicePhilipsHueBridge::open() == 0) { - rc = true; - } - else - { - if ( _isEnabled && _isDeviceInitialised ) + if (initLeds()) { - storeState(); - - if ( _useHueEntertainmentAPI) - { - if ( openStream() ) - { - _isOn = true; - rc = true; - } - } - else if ( powerOn() ) - { - _isOn = true; - rc = true; - } + retval = 0; } } - return rc; -} - -bool LedDevicePhilipsHue::switchOff() -{ - Debug(_log, ""); - - this->stopBlackTimeoutTimer(); - - stop_retry_left = 3; - if (_useHueEntertainmentAPI) - { - stopStream(); - } - - return LedDevicePhilipsHueBridge::switchOff(); + return retval; } int LedDevicePhilipsHue::write(const std::vector & ledValues) @@ -1331,121 +1461,117 @@ int LedDevicePhilipsHue::write(const std::vector & ledValues) return -1; } - // more lights then leds, stop always + // more lights than LEDs, stop always if( ledValues.size() < getLightsCount() ) { - Error(_log, "More light-IDs configured than leds, each light-ID requires one led!" ); + Error(_log, "More light-IDs configured than LEDs, each light-ID requires one LED!" ); return -1; } - writeSingleLights( ledValues ); - - if( _useHueEntertainmentAPI && !noSignalDetection() && _isInitLeds ) + if (_isOn) { - writeStream(); - } + writeSingleLights( ledValues ); + if (_useHueEntertainmentAPI && _isInitLeds) + { + writeStream(); + } + } return 0; } -void LedDevicePhilipsHue::noSignalTimeout() -{ - Debug(_log, "No Signal (timeout: %dms), only black color detected - stop stream for \"%s\" [%u]", _blackLightsTimer->remainingTime(), QSTRING_CSTR(_groupName), _groupId ); - _stopConnection = true; - switchOff(); -} - -void LedDevicePhilipsHue::stopBlackTimeoutTimer() -{ - if ( _blackLightsTimer != nullptr && _blackLightsTimer->isActive() ) - { - _blackLightsTimer->stop(); - } -} - -bool LedDevicePhilipsHue::noSignalDetection() -{ - if( _allLightsBlack && _switchOffOnBlack) - { - if( !_stopConnection && _isInitLeds ) - { - if ( !_blackLightsTimer->isActive() ) - { - DebugIf( verbose, _log, "No Signal detected - timeout timer started" ); - _blackLightsTimer->start( ( _blackLightsTimeout + 500 ) ); - } - } - } - else - { - if ( _blackLightsTimer->isActive() ) - { - DebugIf( verbose, _log, "Signal detected - timeout timer stopped" ); - this->stopBlackTimeoutTimer(); - } - - if( _stopConnection ) - { - _stopConnection = false; - Debug(_log, "Signal detected - restart stream for \"%s\" [%u]", QSTRING_CSTR(_groupName), _groupId ); - switchOn(); - } - } - return _stopConnection; -} - int LedDevicePhilipsHue::writeSingleLights(const std::vector& ledValues) { // Iterate through lights and set colors. unsigned int idx = 0; - unsigned int blackCounter = 0; for ( PhilipsHueLight& light : _lights ) { // Get color. ColorRgb color = ledValues.at(idx); // Scale colors from [0, 255] to [0, 1] and convert to xy space. - CiColor xy = CiColor::rgbToCiColor(color.red / 255.0, color.green / 255.0, color.blue / 255.0, light.getColorSpace()); + CiColor xy = CiColor::rgbToCiColor(color.red / 255.0, color.green / 255.0, color.blue / 255.0, light.getColorSpace(), _candyGamma); - if( _useHueEntertainmentAPI ) + if (_switchOffOnBlack && xy.bri <= _blackLevel && light.isBlack(true)) { - this->setColor(light, xy); - if( xy.bri >= 0.0 && xy.bri <= _brightnessThreshold ) + xy.bri = 0; + xy.x = 0; + xy.y = 0; + + if( _useHueEntertainmentAPI ) { - blackCounter++; + if (light.getOnOffState()) + { + this->setColor(light, xy); + this->setOnOffState(light, false); + } + } + else + { + if (light.getOnOffState()) + { + setState(light, false, xy); + } } } else { - if ( _switchOffOnBlack && xy.bri == 0.0 ) + bool currentstate = light.getOnOffState(); + + if (_switchOffOnBlack && xy.bri > _blackLevel && light.isWhite(true)) { - this->setOnOffState( light, false ); + if (!currentstate) + { + xy.bri = xy.bri / 2; + } + + if (_useHueEntertainmentAPI) + { + this->setOnOffState(light, true); + this->setColor(light, xy); + } + else + { + this->setState(light, true, xy); + } } - else + else if (!_switchOffOnBlack) { // Write color if color has been changed. - this->setState( light, true, xy ); + if (_useHueEntertainmentAPI) + { + this->setOnOffState(light, true); + this->setColor(light, xy); + } + else + { + this->setState( light, true, xy ); + } } } - idx++; - } + if (xy.bri > _blackLevel) + { + light.isBlack(false); + } + else if (xy.bri <= _blackLevel) + { + light.isWhite(false); + } - if( _useHueEntertainmentAPI ) - { - _allLightsBlack = ( blackCounter == _lightsCount ); + ++idx; } return 0; } -void LedDevicePhilipsHue::writeStream() +void LedDevicePhilipsHue::writeStream(bool flush) { QByteArray streamData = prepareStreamData(); - writeBytes( static_cast(streamData.size()), reinterpret_cast( streamData.data() ) ); + writeBytes(static_cast(streamData.size()), reinterpret_cast(streamData.data()), flush); } -void LedDevicePhilipsHue::setOnOffState(PhilipsHueLight& light, bool on) +void LedDevicePhilipsHue::setOnOffState(PhilipsHueLight& light, bool on, bool force) { - if (light.getOnOffState() != on) + if (light.getOnOffState() != on || force) { light.setOnOffState( on ); QString state = on ? API_STATE_VALUE_TRUE : API_STATE_VALUE_FALSE; @@ -1464,7 +1590,7 @@ void LedDevicePhilipsHue::setTransitionTime(PhilipsHueLight& light) void LedDevicePhilipsHue::setColor(PhilipsHueLight& light, CiColor& color) { - if ( light.getColor() != color ) + if (!light.hasColor() || light.getColor() != color) { if( !_useHueEntertainmentAPI ) { @@ -1474,8 +1600,7 @@ void LedDevicePhilipsHue::setColor(PhilipsHueLight& light, CiColor& color) } else { - color.bri = ( qMin( _brightnessMax, _brightnessFactor * qMax( _brightnessMin, color.bri ) ) ); - //if(color.x == 0.0 && color.y == 0.0) color = colorBlack; + color.bri = (qMin((double)1.0, _brightnessFactor * qMax((double)0, color.bri))); } light.setColor( color ); } @@ -1484,12 +1609,15 @@ void LedDevicePhilipsHue::setColor(PhilipsHueLight& light, CiColor& color) void LedDevicePhilipsHue::setState(PhilipsHueLight& light, bool on, const CiColor& color) { QString stateCmd; + QString powerCmd; + bool priority = false; if ( light.getOnOffState() != on ) { light.setOnOffState( on ); QString state = on ? API_STATE_VALUE_TRUE : API_STATE_VALUE_FALSE; - stateCmd += QString("\"%1\":%2,").arg( API_STATE_ON, state ); + powerCmd = QString("\"%1\":%2").arg(API_STATE_ON, state); + priority = true; } if ( light.getTransitionTime() != _transitionTime ) @@ -1499,16 +1627,42 @@ void LedDevicePhilipsHue::setState(PhilipsHueLight& light, bool on, const CiColo } const int bri = qRound( qMin( 254.0, _brightnessFactor * qMax( 1.0, color.bri * 254.0 ) ) ); - if ( light.getColor() != color ) + if (!light.hasColor() || light.getColor() != color) { - light.setColor( color ); - stateCmd += QString("\"%1\":[%2,%3],\"%4\":%5").arg( API_XY_COORDINATES ).arg( color.x, 0, 'd', 4 ).arg( color.y, 0, 'd', 4 ).arg( API_BRIGHTNESS ).arg( bri ); + if (!light.isBusy() || priority) + { + light.setColor( color ); + stateCmd += QString("\"%1\":[%2,%3],\"%4\":%5,").arg(API_XY_COORDINATES).arg(color.x, 0, 'd', 4).arg(color.y, 0, 'd', 4).arg(API_BRIGHTNESS).arg(bri); + } } - if ( !stateCmd.isEmpty() ) + if (!stateCmd.isEmpty() || !powerCmd.isEmpty()) { - setLightState( light.getId(), "{" + stateCmd + "}" ); + if ( !stateCmd.isEmpty() ) + { + stateCmd = QString("\"%1\":%2").arg(API_STATE_ON, "true") + "," + stateCmd; + stateCmd = stateCmd.left(stateCmd.length() - 1); + } + + qint64 _currentTime = QDateTime::currentMSecsSinceEpoch(); + + if ((_currentTime - _lastConfirm > 1500 && ((int)light.getId()) != _lastId) || + (_currentTime - _lastConfirm > 3000)) + { + _lastId = light.getId(); + _lastConfirm = _currentTime; + } + + if (!stateCmd.isEmpty()) + { + setLightState( light.getId(), "{" + stateCmd + "}" ); + } + if (!powerCmd.isEmpty() && !on) + { + QThread::msleep(50); + setLightState(light.getId(), "{" + powerCmd + "}"); + } } } @@ -1517,46 +1671,152 @@ void LedDevicePhilipsHue::setLightsCount( unsigned int lightsCount ) _lightsCount = lightsCount; } -bool LedDevicePhilipsHue::powerOn() + +bool LedDevicePhilipsHue::switchOn() { - if ( _isDeviceReady) + bool rc {false}; + + if ( _isOn ) { - // TODO: Question: Not clear, if setstream state on will turn of the lights - // or do they need to be turned off classically? - if ( !_useHueEntertainmentAPI ) + Debug(_log, "Device %s is already on. Skipping.", QSTRING_CSTR(_activeDeviceType)); + rc = true; + } + else + { + if ( _isDeviceReady ) { - //Switch off Philips Hue devices physically - for ( PhilipsHueLight& light : _lights ) + Info(_log, "Switching device %s ON", QSTRING_CSTR(_activeDeviceType)); + if ( storeState() ) { - setOnOffState( light, true ); + if (_useHueEntertainmentAPI) + { + if (openStream()) + { + if (startConnection()) + { + if ( powerOn() ) + { + _isOn = true; + setRewriteTime(STREAM_REWRITE_TIME.count()); + } + } + } + } + else + { + if ( powerOn() ) + { + _isOn = true; + } + } + } + + if (_isOn) + { + Info(_log, "Device %s is ON", QSTRING_CSTR(_activeDeviceType)); + emit enableStateChanged(_isEnabled); + rc =true; + } + else + { + Warning(_log, "Failed switching device %s ON", QSTRING_CSTR(_activeDeviceType)); } } } - return true; + return rc; +} + +bool LedDevicePhilipsHue::switchOff() +{ + bool rc {false}; + + if ( !_isOn ) + { + rc = true; + } + else + { + if ( _isDeviceInitialised ) + { + if ( _isDeviceReady ) + { + if (_useHueEntertainmentAPI && _groupStreamState) + { + Info(_log, "Switching device %s OFF", QSTRING_CSTR(_activeDeviceType)); + _isOn = false; + setRewriteTime(0); + + if ( _isRestoreOrigState ) + { + stopStream(); + rc = restoreState(); + } + else + { + for (PhilipsHueLight& light : _lights) + { + light.setBlack(); + } + writeStream(true); + + stopStream(); + rc = powerOff(); + } + + if (rc) + { + Info(_log, "Device %s is OFF", QSTRING_CSTR(_activeDeviceType)); + rc = true; + } + else + { + Warning(_log, "Failed switching device %s OFF", QSTRING_CSTR(_activeDeviceType)); + } + + } + else + { + rc = LedDevicePhilipsHueBridge::switchOff(); + } + } + else + { + rc = true; + } + } + } + return rc; +} + +bool LedDevicePhilipsHue::powerOn() +{ + bool rc {true}; + if (_isDeviceReady) + { + for ( PhilipsHueLight& light : _lights ) + { + setOnOffState(light, true, true); + } + } + return rc; } bool LedDevicePhilipsHue::powerOff() { - if ( _isDeviceReady) + bool rc {true}; + if (_isDeviceReady) { - // TODO: Question: Not clear, if setstream state off will turn of the lights - // or do they need to be turned off classically - if ( !_useHueEntertainmentAPI ) + for ( PhilipsHueLight& light : _lights ) { - //Switch off Philips Hue devices physically - for ( PhilipsHueLight& light : _lights ) - { - setOnOffState( light, false ); - } + setOnOffState(light, false, true); } } - return true; + return rc; } bool LedDevicePhilipsHue::storeState() { - bool rc = true; - + bool rc {true}; if ( _isRestoreOrigState ) { if( !_lightIds.empty() ) @@ -1568,14 +1828,12 @@ bool LedDevicePhilipsHue::storeState() } } } - return rc; } bool LedDevicePhilipsHue::restoreState() { - bool rc = true; - + bool rc {true}; if ( _isRestoreOrigState ) { // Restore device's original state @@ -1587,120 +1845,40 @@ bool LedDevicePhilipsHue::restoreState() } } } - return rc; } -QJsonObject LedDevicePhilipsHue::discover(const QJsonObject& /*params*/) -{ - QJsonObject devicesDiscovered; - devicesDiscovered.insert("ledDeviceType", _activeDeviceType ); - - QJsonArray deviceList; - - // Discover Devices - SSDPDiscover discover; - - discover.skipDuplicateKeys(true); - discover.setSearchFilter(SSDP_FILTER, SSDP_FILTER_HEADER); - QString searchTarget = SSDP_ID; - - if ( discover.discoverServices(searchTarget) > 0 ) - { - deviceList = discover.getServicesDiscoveredJson(); - } - - devicesDiscovered.insert("devices", deviceList); - Debug(_log, "devicesDiscovered: [%s]", QString(QJsonDocument(devicesDiscovered).toJson(QJsonDocument::Compact)).toUtf8().constData() ); - - return devicesDiscovered; -} - -QJsonObject LedDevicePhilipsHue::getProperties(const QJsonObject& params) -{ - DebugIf(verbose, _log, "params: [%s]", QString(QJsonDocument(params).toJson(QJsonDocument::Compact)).toUtf8().constData()); - QJsonObject properties; - - // Get Phillips-Bridge device properties - QString hostName = params[CONFIG_HOST].toString(""); - if (!hostName.isEmpty()) - { - QString username = params["user"].toString(""); - QString filter = params["filter"].toString(""); - - int apiPort; - if (params[CONFIG_PORT].isString()) - { - apiPort = params[CONFIG_PORT].toString().toInt(); - } - else - { - apiPort = params[CONFIG_PORT].toInt(); - } - - if (apiPort == 0) - { - apiPort = API_DEFAULT_PORT; - } - - initRestAPI(hostName, apiPort, username); - _restApi->setPath(filter); - - // Perform request - httpResponse response = _restApi->get(); - if (response.error()) - { - Warning(_log, "%s get properties failed with error: '%s'", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(response.getErrorReason())); - } - - // Perform request - properties.insert("properties", response.getBody().object()); - } - return properties; -} - void LedDevicePhilipsHue::identify(const QJsonObject& params) { DebugIf(verbose, _log, "params: [%s]", QString(QJsonDocument(params).toJson(QJsonDocument::Compact)).toUtf8().constData()); QJsonObject properties; - // Identify Phillips-Bridge device - QString hostName = params[CONFIG_HOST].toString(""); - if (!hostName.isEmpty()) + _hostName = params[CONFIG_HOST].toString(""); + _apiPort = params[CONFIG_PORT].toInt(); + _authToken = params["user"].toString(""); + + Info(_log, "Identify %s, hostname (%s)", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(_hostName) ); + + if (NetUtils::resolveHostToAddress(_log, _hostName, _address, _apiPort)) { - QString username = params["user"].toString(""); - int lightId = params["lightId"].toInt(0); - - int apiPort; - if (params[CONFIG_PORT].isString()) + if ( openRestAPI() ) { - apiPort = params[CONFIG_PORT].toString().toInt(); - } - else - { - apiPort = params[CONFIG_PORT].toInt(); - } + int lightId = params["lightId"].toInt(0); - if (apiPort == 0) - { - apiPort = API_DEFAULT_PORT; - } + QString resource = QString("%1/%2/%3").arg(API_LIGHTS).arg(lightId).arg(API_STATE); + _restApi->setPath(resource); - initRestAPI(hostName, apiPort, username); + QString stateCmd; + stateCmd += QString("\"%1\":%2,").arg(API_STATE_ON, API_STATE_VALUE_TRUE); + stateCmd += QString("\"%1\":\"%2\"").arg("alert", "select"); + stateCmd = "{" + stateCmd + "}"; - QString resource = QString("%1/%2/%3").arg(API_LIGHTS).arg(lightId).arg(API_STATE); - _restApi->setPath(resource); - - QString stateCmd; - stateCmd += QString("\"%1\":%2,").arg(API_STATE_ON, API_STATE_VALUE_TRUE); - stateCmd += QString("\"%1\":\"%2\"").arg("alert", "select"); - stateCmd = "{" + stateCmd + "}"; - - // Perform request - httpResponse response = _restApi->put(stateCmd); - if (response.error()) - { - Warning(_log, "%s identification failed with error: '%s'", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(response.getErrorReason())); + // Perform request + httpResponse response = _restApi->put(stateCmd); + if (response.error()) + { + Warning(_log, "%s identification failed with error: '%s'", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(response.getErrorReason())); + } } } } diff --git a/libsrc/leddevice/dev_net/LedDevicePhilipsHue.h b/libsrc/leddevice/dev_net/LedDevicePhilipsHue.h index e7fe24f4..633ecc3e 100644 --- a/libsrc/leddevice/dev_net/LedDevicePhilipsHue.h +++ b/libsrc/leddevice/dev_net/LedDevicePhilipsHue.h @@ -7,7 +7,6 @@ // Qt includes #include -#include #include #include #include @@ -85,7 +84,7 @@ struct CiColor /// /// @return color point /// - static CiColor rgbToCiColor(double red, double green, double blue, const CiColorTriangle &colorSpace); + static CiColor rgbToCiColor(double red, double green, double blue, const CiColorTriangle& colorSpace, bool candyGamma); /// /// @param p the color point to check @@ -149,8 +148,9 @@ public: /// @param bridge the bridge /// @param id the light id /// - PhilipsHueLight(Logger* log, unsigned int id, QJsonObject values, unsigned int ledidx); - ~PhilipsHueLight(); + PhilipsHueLight(Logger* log, int id, QJsonObject values, int ledidx, + int onBlackTimeToPowerOff, + int onBlackTimeToPowerOn); /// /// @param on @@ -167,11 +167,12 @@ public: /// void setColor(const CiColor& color); - unsigned int getId() const; + int getId() const; bool getOnOffState() const; int getTransitionTime() const; CiColor getColor() const; + bool hasColor() const; /// /// @return the color space of the light determined by the model id reported by the bridge. @@ -180,15 +181,21 @@ public: void saveOriginalState(const QJsonObject& values); QString getOriginalState() const; + bool isBusy(); + bool isBlack(bool isBlack); + bool isWhite(bool isWhite); + void setBlack(); + void blackScreenTriggered(); private: Logger* _log; /// light id - unsigned int _id; - unsigned int _ledidx; + int _id; + int _ledidx; bool _on; int _transitionTime; CiColor _color; + bool _hasColor; /// darkes blue color in hue lamp GAMUT = black CiColor _colorBlack; /// The model id of the hue lamp which is used to determine the color space. @@ -201,6 +208,12 @@ private: QString _originalState; CiColor _originalColor; + qint64 _lastSendColorTime; + qint64 _lastBlackTime; + qint64 _lastWhiteTime; + bool _blackScreenTriggered; + qint64 _onBlackTimeToPowerOff; + qint64 _onBlackTimeToPowerOn; }; class LedDevicePhilipsHueBridge : public ProviderUdpSSL @@ -215,13 +228,9 @@ public: /// /// @brief Initialise the access to the REST-API wrapper /// - /// @param[in] host - /// @param[in] port - /// @param[in] authentication token - /// /// @return True, if success /// - bool initRestAPI(const QString &hostname, int port, const QString &token ); + bool openRestAPI(); /// /// @brief Perform a REST-API GET @@ -238,20 +247,18 @@ public: /// @param route the route of the POST request. /// @param content the content of the POST request. /// - QJsonDocument post(const QString& route, const QString& content); + QJsonDocument put(const QString& route, const QString& content, bool supressError = false); - QJsonDocument getLightState(unsigned int lightId); - void setLightState(unsigned int lightId = 0, const QString &state = ""); + QJsonDocument getLightState( int lightId); + void setLightState( int lightId = 0, const QString &state = ""); - QMap getLightMap() const; + QMap getLightMap() const; - QMap getGroupMap() const; - - QString getGroupName(quint16 groupId = 0) const; - - QJsonArray getGroupLights(quint16 groupId = 0) const; + QMap getGroupMap() const; + QString getGroupName(int groupId = 0) const; + QJsonArray getGroupLights(int groupId = 0) const; protected: @@ -281,23 +288,66 @@ protected: /// @brief Check, if Hue API response indicate error /// /// @param[in] response from Hue-Bridge in JSON-format + /// @param[in] suppressError Treat an error as a warning + /// /// return True, Hue Bridge reports error /// - bool checkApiError(const QJsonDocument &response ); + bool checkApiError(const QJsonDocument& response, bool supressError = false); + + /// + /// @brief Discover devices of this type available (for configuration). + /// @note Mainly used for network devices. Allows to find devices, e.g. via ssdp, mDNS or cloud ways. + /// + /// @param[in] params Parameters used to overwrite discovery default behaviour + /// + /// @return A JSON structure holding a list of devices found + /// + QJsonObject discover(const QJsonObject& params) override; + + /// + /// @brief Get the Hue Bridge device's resource properties + /// + /// Following parameters are required + /// @code + /// { + /// "host" : "hostname or IP", + /// "port" : port + /// "user" : "username", + /// "filter": "resource to query", root "/" is used, if empty + /// } + ///@endcode + /// + /// @param[in] params Parameters to query device + /// @return A JSON structure holding the device's properties + /// + QJsonObject getProperties(const QJsonObject& params) override; + + /// + /// @brief Add an authorization/client-key to the Hue Bridge device + /// + /// Following parameters are required + /// @code + /// { + /// "host" : "hostname or IP", + /// "port" : port + /// } + ///@endcode + /// + /// @param[in] params Parameters to query device + /// @return A JSON structure holding the authorization keys + /// + QJsonObject addAuthorization(const QJsonObject& params) override; ///REST-API wrapper ProviderRestApi* _restApi; - - /// Ip address of the bridge - QString _hostname; int _apiPort; /// User name for the API ("newdeveloper") - QString _username; + QString _authToken; bool _useHueEntertainmentAPI; - QJsonDocument getGroupState( unsigned int groupId ); - QJsonDocument setGroupState( unsigned int groupId, bool state); + QJsonDocument getGroupState( int groupId ); + QJsonDocument setGroupState( int groupId, bool state); bool isStreamOwner(const QString &streamOwner) const; bool initMaps(); @@ -308,6 +358,14 @@ protected: private: + /// + /// @brief Discover Philips-Hue devices available (for configuration). + /// Philips-Hue specific ssdp discovery + /// + /// @return A JSON structure holding a list of devices found + /// + QJsonArray discover(); + QJsonDocument getAllBridgeInfos(); void setBridgeConfig( const QJsonDocument &doc ); void setLightsMap( const QJsonDocument &doc ); @@ -324,8 +382,8 @@ private: bool _isHueEntertainmentReady; - QMap _lightsMap; - QMap _groupsMap; + QMap _lightsMap; + QMap _groupsMap; }; /** @@ -360,34 +418,6 @@ public: /// @return LedDevice constructed static LedDevice* construct(const QJsonObject &deviceConfig); - /// - /// @brief Discover devices of this type available (for configuration). - /// @note Mainly used for network devices. Allows to find devices, e.g. via ssdp, mDNS or cloud ways. - /// - /// @param[in] params Parameters used to overwrite discovery default behaviour - /// - /// @return A JSON structure holding a list of devices found - /// - QJsonObject discover(const QJsonObject& params) override; - - /// - /// @brief Get the Hue Bridge device's resource properties - /// - /// Following parameters are required - /// @code - /// { - /// "host" : "hostname or IP - /// "port" : port - /// "user" : "username", - /// "filter": "resource to query", root "/" is used, if empty - /// } - ///@endcode - /// - /// @param[in] params Parameters to query device - /// @return A JSON structure holding the device's properties - /// - QJsonObject getProperties(const QJsonObject& params) override; - /// /// @brief Send an update to the device to identify it. /// @@ -412,7 +442,7 @@ public: /// unsigned int getLightsCount() const { return _lightsCount; } - void setOnOffState(PhilipsHueLight& light, bool on); + void setOnOffState(PhilipsHueLight& light, bool on, bool force = false); void setTransitionTime(PhilipsHueLight& light); void setColor(PhilipsHueLight& light, CiColor& color); void setState(PhilipsHueLight& light, bool on, const CiColor& color); @@ -443,13 +473,6 @@ protected: /// int open() override; - /// - /// @brief Closes the output device. - /// - /// @return Zero on success (i.e. device is closed), else negative - /// - int close() override; - /// /// @brief Writes the RGB-Color values to the LEDs. /// @@ -465,7 +488,7 @@ protected: /// Depending on the configuration, the device may store its current state for later restore. /// @see powerOn, storeState /// - /// @return True if success + /// @return True, if success /// bool switchOn() override; @@ -518,28 +541,17 @@ protected: /// bool restoreState() override; -private slots: - - void noSignalTimeout(); - private: bool initLeds(); - /// - /// @brief Creates new PhilipsHueLight(s) based on user lightid with bridge feedback - /// - /// @param map Map of lightid/value pairs of bridge - /// - void newLights(QMap map); - bool setLights(); /// creates new PhilipsHueLight(s) based on user lightid with bridge feedback /// /// @param map Map of lightid/value pairs of bridge /// - bool updateLights(const QMap &map); + bool updateLights(const QMap &map); /// /// @brief Set the number of LEDs supported by the device. @@ -554,13 +566,9 @@ private: bool startStream(); bool stopStream(); - void writeStream(); + void writeStream(bool flush = false); int writeSingleLights(const std::vector& ledValues); - bool noSignalDetection(); - - void stopBlackTimeoutTimer(); - QByteArray prepareStreamData() const; /// @@ -574,32 +582,28 @@ private: bool _isInitLeds; /// Array of the light ids. - std::vector _lightIds; + std::vector _lightIds; /// Array to save the lamps. std::vector _lights; - unsigned int _lightsCount; - quint16 _groupId; + int _lightsCount; + int _groupId; - double _brightnessMin; - double _brightnessMax; - - bool _allLightsBlack; - - QTimer* _blackLightsTimer; int _blackLightsTimeout; - double _brightnessThreshold; - - int _handshake_timeout_min; - int _handshake_timeout_max; - int _ssl_read_timeout; + double _blackLevel; + int _onBlackTimeToPowerOff; + int _onBlackTimeToPowerOn; + bool _candyGamma; + // TODO: Check what is the correct class + uint32_t _handshake_timeout_min; + uint32_t _handshake_timeout_max; bool _stopConnection; QString _groupName; QString _streamOwner; - int start_retry_left; - int stop_retry_left; - + qint64 _lastConfirm; + int _lastId; + bool _groupStreamState; }; diff --git a/libsrc/leddevice/dev_net/LedDeviceRazer.cpp b/libsrc/leddevice/dev_net/LedDeviceRazer.cpp index ee9b3385..8637a31a 100644 --- a/libsrc/leddevice/dev_net/LedDeviceRazer.cpp +++ b/libsrc/leddevice/dev_net/LedDeviceRazer.cpp @@ -65,13 +65,6 @@ bool LedDeviceRazer::init(const QJsonObject& deviceConfig) // Initialise sub-class if (LedDevice::init(deviceConfig)) { - // Initialise LedDevice configuration and execution environment - uint configuredLedCount = this->getLedCount(); - Debug(_log, "DeviceType : %s", QSTRING_CSTR(this->getActiveDeviceType())); - Debug(_log, "LedCount : %u", configuredLedCount); - Debug(_log, "ColorOrder : %s", QSTRING_CSTR(this->getColorOrder())); - Debug(_log, "LatchTime : %d", this->getLatchTime()); - Debug(_log, "RefreshTime : %d", _refreshTimerInterval_ms); //Razer Chroma SDK allows localhost connection only _hostname = API_DEFAULT_HOST; @@ -86,6 +79,7 @@ bool LedDeviceRazer::init(const QJsonObject& deviceConfig) Debug(_log, "Razer Device : %s", QSTRING_CSTR(_razerDeviceType)); Debug(_log, "Single Color : %d", _isSingleColor); + int configuredLedCount = this->getLedCount(); if (resolveDeviceProperties(_razerDeviceType)) { if (_isSingleColor && configuredLedCount > 1) @@ -125,6 +119,8 @@ bool LedDeviceRazer::initRestAPI(const QString& hostname, int port) if (_restApi == nullptr) { _restApi = new ProviderRestApi(hostname, port); + _restApi->setLogger(_log); + _restApi->setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); isInitOK = true; diff --git a/libsrc/leddevice/dev_net/LedDeviceTpm2net.cpp b/libsrc/leddevice/dev_net/LedDeviceTpm2net.cpp index 5bd407bd..261643ca 100644 --- a/libsrc/leddevice/dev_net/LedDeviceTpm2net.cpp +++ b/libsrc/leddevice/dev_net/LedDeviceTpm2net.cpp @@ -1,6 +1,13 @@ #include "LedDeviceTpm2net.h" +#include +// Constants +namespace { + +const char CONFIG_HOST[] = "host"; +const char CONFIG_PORT[] = "port"; const ushort TPM2_DEFAULT_PORT = 65506; +} LedDeviceTpm2net::LedDeviceTpm2net(const QJsonObject &deviceConfig) : ProviderUdp(deviceConfig) @@ -20,13 +27,14 @@ LedDevice* LedDeviceTpm2net::construct(const QJsonObject &deviceConfig) bool LedDeviceTpm2net::init(const QJsonObject &deviceConfig) { - bool isInitOK = false; - - _port = TPM2_DEFAULT_PORT; + bool isInitOK {false}; // Initialise sub-class if ( ProviderUdp::init(deviceConfig) ) { + _hostName = _devConfig[ CONFIG_HOST ].toString(); + _port = deviceConfig[CONFIG_PORT].toInt(TPM2_DEFAULT_PORT); + _tpm2_max = deviceConfig["max-packet"].toInt(170); _tpm2ByteCount = 3 * _ledCount; _tpm2TotalPackets = (_tpm2ByteCount / _tpm2_max) + ((_tpm2ByteCount % _tpm2_max) != 0); @@ -38,6 +46,23 @@ bool LedDeviceTpm2net::init(const QJsonObject &deviceConfig) return isInitOK; } +int LedDeviceTpm2net::open() +{ + int retval = -1; + _isDeviceReady = false; + + if (NetUtils::resolveHostToAddress(_log, _hostName, _address)) + { + if (ProviderUdp::open() == 0) + { + // Everything is OK, device is ready + _isDeviceReady = true; + retval = 0; + } + } + return retval; +} + int LedDeviceTpm2net::write(const std::vector &ledValues) { int retVal = 0; diff --git a/libsrc/leddevice/dev_net/LedDeviceTpm2net.h b/libsrc/leddevice/dev_net/LedDeviceTpm2net.h index 299e7871..c1c7779d 100644 --- a/libsrc/leddevice/dev_net/LedDeviceTpm2net.h +++ b/libsrc/leddevice/dev_net/LedDeviceTpm2net.h @@ -41,6 +41,13 @@ private: /// bool init(const QJsonObject &deviceConfig) override; + /// + /// @brief Opens the output device. + /// + /// @return Zero on success (i.e. device is ready), else negative + /// + int open() override; + /// /// @brief Writes the RGB-Color values to the LEDs. /// diff --git a/libsrc/leddevice/dev_net/LedDeviceUdpArtNet.cpp b/libsrc/leddevice/dev_net/LedDeviceUdpArtNet.cpp index f0a68596..66ca3245 100644 --- a/libsrc/leddevice/dev_net/LedDeviceUdpArtNet.cpp +++ b/libsrc/leddevice/dev_net/LedDeviceUdpArtNet.cpp @@ -9,7 +9,16 @@ #include +#include + +// Constants +namespace { + +const char CONFIG_HOST[] = "host"; +const char CONFIG_PORT[] = "port"; + const ushort ARTNET_DEFAULT_PORT = 6454; +} LedDeviceUdpArtNet::LedDeviceUdpArtNet(const QJsonObject &deviceConfig) : ProviderUdp(deviceConfig) @@ -23,13 +32,14 @@ LedDevice* LedDeviceUdpArtNet::construct(const QJsonObject &deviceConfig) bool LedDeviceUdpArtNet::init(const QJsonObject &deviceConfig) { - bool isInitOK = false; - - _port = ARTNET_DEFAULT_PORT; + bool isInitOK {false}; // Initialise sub-class if ( ProviderUdp::init(deviceConfig) ) { + _hostName = _devConfig[ CONFIG_HOST ].toString(); + _port = deviceConfig[CONFIG_PORT].toInt(ARTNET_DEFAULT_PORT); + _artnet_universe = deviceConfig["universe"].toInt(1); _artnet_channelsPerFixture = deviceConfig["channelsPerFixture"].toInt(3); @@ -38,6 +48,23 @@ bool LedDeviceUdpArtNet::init(const QJsonObject &deviceConfig) return isInitOK; } +int LedDeviceUdpArtNet::open() +{ + int retval = -1; + _isDeviceReady = false; + + if (NetUtils::resolveHostToAddress(_log, _hostName, _address)) + { + if (ProviderUdp::open() == 0) + { + // Everything is OK, device is ready + _isDeviceReady = true; + retval = 0; + } + } + return retval; +} + // populates the headers void LedDeviceUdpArtNet::prepare(unsigned this_universe, unsigned this_sequence, unsigned this_dmxChannelCount) { diff --git a/libsrc/leddevice/dev_net/LedDeviceUdpArtNet.h b/libsrc/leddevice/dev_net/LedDeviceUdpArtNet.h index 6687a916..8b6afc9f 100644 --- a/libsrc/leddevice/dev_net/LedDeviceUdpArtNet.h +++ b/libsrc/leddevice/dev_net/LedDeviceUdpArtNet.h @@ -69,6 +69,13 @@ private: /// bool init(const QJsonObject &deviceConfig) override; + /// + /// @brief Opens the output device. + /// + /// @return Zero on success (i.e. device is ready), else negative + /// + int open() override; + /// /// @brief Writes the RGB-Color values to the LEDs. /// diff --git a/libsrc/leddevice/dev_net/LedDeviceUdpDdp.cpp b/libsrc/leddevice/dev_net/LedDeviceUdpDdp.cpp new file mode 100644 index 00000000..a5e36282 --- /dev/null +++ b/libsrc/leddevice/dev_net/LedDeviceUdpDdp.cpp @@ -0,0 +1,163 @@ +#include "LedDeviceUdpDdp.h" + +#include + +#include + +// DDP header format +// header is 10 bytes (14 if TIME flag used) +struct ddp_hdr_struct { + uint8_t flags1; + uint8_t flags2; + uint8_t type; + uint8_t id; + uint32_t offset; + uint16_t len; +}; + +// Constants +namespace { + +const char CONFIG_HOST[] = "host"; +const char CONFIG_PORT[] = "port"; + +const ushort DDP_DEFAULT_PORT = 4048; + +namespace DDP { + + // DDP protocol header definitions + struct Header { + uint8_t flags1; + uint8_t flags2; + uint8_t type; + uint8_t id; + uint8_t offset[4]; + uint8_t len[2]; + }; + + static constexpr int HEADER_LEN = (sizeof(struct Header)); // header is 10 bytes (14 if TIME flag used) + static constexpr int MAX_LEDS = 480; + static constexpr int CHANNELS_PER_PACKET = MAX_LEDS*3; + + namespace flags1 { + static constexpr auto VER_MASK = 0xc0; + static constexpr auto VER1 = 0x40; + static constexpr auto PUSH = 0x01; + static constexpr auto QUERY = 0x02; + static constexpr auto REPLY = 0x04; + static constexpr auto STORAGE = 0x08; + static constexpr auto TIME = 0x10; + } // namespace flags1 + + namespace id { + static constexpr auto DISPLAY = 1; + static constexpr auto CONTROL = 246; + static constexpr auto CONFIG = 250; + static constexpr auto STATUS = 251; + static constexpr auto DMXTRANSIT = 254; + static constexpr auto ALLDEVICES = 255; + } // namespace id + +} // namespace DDP + +} //End of constants + +LedDeviceUdpDdp::LedDeviceUdpDdp(const QJsonObject &deviceConfig) + : ProviderUdp(deviceConfig) + ,_packageSequenceNumber(0) +{ +} + +LedDevice* LedDeviceUdpDdp::construct(const QJsonObject &deviceConfig) +{ + return new LedDeviceUdpDdp(deviceConfig); +} + +bool LedDeviceUdpDdp::init(const QJsonObject &deviceConfig) +{ + bool isInitOK {false}; + + if ( ProviderUdp::init(deviceConfig) ) + { + _hostName = _devConfig[ CONFIG_HOST ].toString(); + _port = deviceConfig[CONFIG_PORT].toInt(DDP_DEFAULT_PORT); + + Debug(_log, "Hostname/IP : %s", QSTRING_CSTR(_hostName) ); + Debug(_log, "Port : %d", _port ); + + _ddpData.resize(DDP::HEADER_LEN + DDP::CHANNELS_PER_PACKET); + _ddpData[0] = DDP::flags1::VER1; // flags1 + _ddpData[1] = 0; // flags2 + _ddpData[2] = 1; // type + _ddpData[3] = DDP::id::DISPLAY; // id + + isInitOK = true; + } + return isInitOK; +} + +int LedDeviceUdpDdp::open() +{ + int retval = -1; + _isDeviceReady = false; + + if (NetUtils::resolveHostToAddress(_log, _hostName, _address)) + { + if (ProviderUdp::open() == 0) + { + // Everything is OK, device is ready + _isDeviceReady = true; + retval = 0; + } + } + return retval; +} + +int LedDeviceUdpDdp::write(const std::vector &ledValues) +{ + int rc {0}; + + int channelCount = static_cast(_ledCount) * 3; // 1 channel for every R,G,B value + int packetCount = ((channelCount-1) / DDP::CHANNELS_PER_PACKET) + 1; + int channel = 0; + + _ddpData[0] = DDP::flags1::VER1; + + for (int currentPacket = 0; currentPacket < packetCount; currentPacket++) + { + if (_packageSequenceNumber > 15) + { + _packageSequenceNumber = 0; + } + + int packetSize = DDP::CHANNELS_PER_PACKET; + + if (currentPacket == (packetCount - 1)) + { + // last packet, set the push flag + /*0*/_ddpData[0] = DDP::flags1::VER1 | DDP::flags1::PUSH; + + if (channelCount % DDP::CHANNELS_PER_PACKET != 0) + { + packetSize = channelCount % DDP::CHANNELS_PER_PACKET; + } + } + + /*1*/_ddpData[1] = static_cast(_packageSequenceNumber++ & 0x0F); + /*4*/qToBigEndian(static_cast(channel), _ddpData.data() + 4); + /*8*/qToBigEndian(static_cast(packetSize), _ddpData.data() + 8); + + _ddpData.replace(DDP::HEADER_LEN, channel, reinterpret_cast(ledValues.data())+channel, packetSize); + _ddpData.resize(DDP::HEADER_LEN + packetSize); + + rc = writeBytes(_ddpData); + + if (rc != 0) + { + break; + } + channel += packetSize; + } + return rc; +} + diff --git a/libsrc/leddevice/dev_net/LedDeviceUdpDdp.h b/libsrc/leddevice/dev_net/LedDeviceUdpDdp.h new file mode 100644 index 00000000..623700a4 --- /dev/null +++ b/libsrc/leddevice/dev_net/LedDeviceUdpDdp.h @@ -0,0 +1,62 @@ +#ifndef LEDEVICEUDPDDP_H +#define LEDEVICEUDPDDP_H + +// hyperion includes +#include "ProviderUdp.h" + +/// +/// Implementation of the LedDevice interface for sending LED colors via UDP and the Distributed Display Protocol (DDP) +/// http://www.3waylabs.com/ddp/#Data%20Types +/// +class LedDeviceUdpDdp : public virtual ProviderUdp +{ +public: + + /// + /// @brief Constructs a LED-device fed via DDP + /// + /// @param deviceConfig Device's configuration as JSON-Object + /// + explicit LedDeviceUdpDdp(const QJsonObject &deviceConfig); + + /// + /// @brief Constructs the LED-device + /// + /// @param[in] deviceConfig Device's configuration as JSON-Object + /// @return LedDevice constructed + /// + static LedDevice* construct(const QJsonObject &deviceConfig); + +protected: + + /// + /// @brief Initialise the device's configuration + /// + /// @param[in] deviceConfig the JSON device configuration + /// @return True, if success + /// + bool init(const QJsonObject &deviceConfig) override; + + /// + /// @brief Opens the output device. + /// + /// @return Zero on success (i.e. device is ready), else negative + /// + int open() override; + + /// + /// @brief Writes the RGB-Color values to the LEDs. + /// + /// @param[in] ledValues The RGB-color per LED + /// @return Zero on success, else negative + /// + int write(const std::vector & ledValues) override; + +private: + + QByteArray _ddpData; + + int _packageSequenceNumber; +}; + +#endif // LEDEVICEUDPDDP_H diff --git a/libsrc/leddevice/dev_net/LedDeviceUdpE131.cpp b/libsrc/leddevice/dev_net/LedDeviceUdpE131.cpp index e3a6f4de..65cebf78 100644 --- a/libsrc/leddevice/dev_net/LedDeviceUdpE131.cpp +++ b/libsrc/leddevice/dev_net/LedDeviceUdpE131.cpp @@ -8,6 +8,13 @@ // hyperion local includes #include "LedDeviceUdpE131.h" +#include + +// Constants +namespace { + +const char CONFIG_HOST[] = "host"; +const char CONFIG_PORT[] = "port"; const ushort E131_DEFAULT_PORT = 5568; @@ -23,6 +30,7 @@ const uint32_t VECTOR_E131_DATA_PACKET = 0x00000002; //#define E131_NETWORK_DATA_LOSS_TIMEOUT 2500 // milli econds //#define E131_DISCOVERY_UNIVERSE 64214 const int DMX_MAX = 512; // 512 usable slots +} LedDeviceUdpE131::LedDeviceUdpE131(const QJsonObject &deviceConfig) : ProviderUdp(deviceConfig) @@ -36,13 +44,14 @@ LedDevice* LedDeviceUdpE131::construct(const QJsonObject &deviceConfig) bool LedDeviceUdpE131::init(const QJsonObject &deviceConfig) { - bool isInitOK = false; - - _port = E131_DEFAULT_PORT; + bool isInitOK {false}; // Initialise sub-class if ( ProviderUdp::init(deviceConfig) ) { + _hostName = _devConfig[ CONFIG_HOST ].toString(); + _port = deviceConfig[CONFIG_PORT].toInt(E131_DEFAULT_PORT); + _e131_universe = deviceConfig["universe"].toInt(1); _e131_source_name = deviceConfig["source-name"].toString("hyperion on "+QHostInfo::localHostName()); QString _json_cid = deviceConfig["cid"].toString(""); @@ -70,6 +79,23 @@ bool LedDeviceUdpE131::init(const QJsonObject &deviceConfig) return isInitOK; } +int LedDeviceUdpE131::open() +{ + int retval = -1; + _isDeviceReady = false; + + if (NetUtils::resolveHostToAddress(_log, _hostName, _address)) + { + if (ProviderUdp::open() == 0) + { + // Everything is OK, device is ready + _isDeviceReady = true; + retval = 0; + } + } + return retval; +} + // populates the headers void LedDeviceUdpE131::prepare(unsigned this_universe, unsigned this_dmxChannelCount) { diff --git a/libsrc/leddevice/dev_net/LedDeviceUdpE131.h b/libsrc/leddevice/dev_net/LedDeviceUdpE131.h index 13c3c6ea..12ec50e2 100644 --- a/libsrc/leddevice/dev_net/LedDeviceUdpE131.h +++ b/libsrc/leddevice/dev_net/LedDeviceUdpE131.h @@ -114,6 +114,13 @@ private: /// bool init(const QJsonObject &deviceConfig) override; + /// + /// @brief Opens the output device. + /// + /// @return Zero on success (i.e. device is ready), else negative + /// + int open() override; + /// /// @brief Writes the RGB-Color values to the LEDs. /// diff --git a/libsrc/leddevice/dev_net/LedDeviceUdpH801.cpp b/libsrc/leddevice/dev_net/LedDeviceUdpH801.cpp index d8673eea..23e771c8 100644 --- a/libsrc/leddevice/dev_net/LedDeviceUdpH801.cpp +++ b/libsrc/leddevice/dev_net/LedDeviceUdpH801.cpp @@ -1,8 +1,12 @@ #include "LedDeviceUdpH801.h" +#include // Constants namespace { +const char CONFIG_HOST[] = "host"; +const char CONFIG_PORT[] = "port"; + const ushort H801_DEFAULT_PORT = 30977; const char H801_DEFAULT_HOST[] = "255.255.255.255"; @@ -20,16 +24,17 @@ LedDevice* LedDeviceUdpH801::construct(const QJsonObject &deviceConfig) bool LedDeviceUdpH801::init(const QJsonObject &deviceConfig) { - bool isInitOK = false; + bool isInitOK {false}; /* The H801 port is fixed */ _latchTime_ms = 10; - _port = H801_DEFAULT_PORT; - _defaultHost = H801_DEFAULT_HOST; // Initialise sub-class if ( ProviderUdp::init(deviceConfig) ) { + _hostName = _devConfig[ CONFIG_HOST ].toString(H801_DEFAULT_HOST); + _port = deviceConfig[CONFIG_PORT].toInt(H801_DEFAULT_PORT); + _ids.clear(); QJsonArray lArray = deviceConfig["lightIds"].toArray(); for (int i = 0; i < lArray.size(); i++) @@ -47,14 +52,28 @@ bool LedDeviceUdpH801::init(const QJsonObject &deviceConfig) _message[_prefix_size + _colors + i * _id_size + 1] = (_ids[i] >> 0x08) & 0xFF; _message[_prefix_size + _colors + i * _id_size + 2] = (_ids[i] >> 0x10) & 0xFF; } - - Debug(_log, "H801 using %s:%d", _address.toString().toStdString().c_str(), _port); - isInitOK = true; } return isInitOK; } +int LedDeviceUdpH801::open() +{ + int retval = -1; + _isDeviceReady = false; + + if (NetUtils::resolveHostToAddress(_log, _hostName, _address)) + { + if (ProviderUdp::open() == 0) + { + // Everything is OK, device is ready + _isDeviceReady = true; + retval = 0; + } + } + return retval; +} + int LedDeviceUdpH801::write(const std::vector &ledValues) { ColorRgb color = ledValues[0]; diff --git a/libsrc/leddevice/dev_net/LedDeviceUdpH801.h b/libsrc/leddevice/dev_net/LedDeviceUdpH801.h index c0897a73..cc6c7ba8 100644 --- a/libsrc/leddevice/dev_net/LedDeviceUdpH801.h +++ b/libsrc/leddevice/dev_net/LedDeviceUdpH801.h @@ -37,6 +37,13 @@ private: /// bool init(const QJsonObject &deviceConfig) override; + /// + /// @brief Opens the output device. + /// + /// @return Zero on success (i.e. device is ready), else negative + /// + int open() override; + /// /// @brief Writes the RGB-Color values to the LEDs. /// diff --git a/libsrc/leddevice/dev_net/LedDeviceUdpRaw.cpp b/libsrc/leddevice/dev_net/LedDeviceUdpRaw.cpp index d6e83b8b..0f0d167f 100644 --- a/libsrc/leddevice/dev_net/LedDeviceUdpRaw.cpp +++ b/libsrc/leddevice/dev_net/LedDeviceUdpRaw.cpp @@ -1,10 +1,14 @@ #include "LedDeviceUdpRaw.h" +#include + // Constants namespace { const bool verbose = false; +const char CONFIG_HOST[] = "host"; +const char CONFIG_PORT[] = "port"; const ushort RAW_DEFAULT_PORT=5568; const int UDP_MAX_LED_NUM = 490; @@ -22,33 +26,46 @@ LedDevice* LedDeviceUdpRaw::construct(const QJsonObject &deviceConfig) bool LedDeviceUdpRaw::init(const QJsonObject &deviceConfig) { - _port = RAW_DEFAULT_PORT; + bool isInitOK {false}; - bool isInitOK = false; - if ( LedDevice::init(deviceConfig) ) + if ( ProviderUdp::init(deviceConfig) ) { - // Initialise LedDevice configuration and execution environment - int configuredLedCount = this->getLedCount(); - Debug(_log, "DeviceType : %s", QSTRING_CSTR( this->getActiveDeviceType() )); - Debug(_log, "LedCount : %d", configuredLedCount); - Debug(_log, "ColorOrder : %s", QSTRING_CSTR( this->getColorOrder() )); - Debug(_log, "LatchTime : %d", this->getLatchTime()); - - if (configuredLedCount > UDP_MAX_LED_NUM) + if (this->getLedCount() > UDP_MAX_LED_NUM) { - QString errorReason = QString("Device type %1 can only be run with maximum %2 LEDs!").arg(this->getActiveDeviceType()).arg(UDP_MAX_LED_NUM); + QString errorReason = QString("Device type %1 can only be run with maximum %2 LEDs for streaming protocol = UDP-RAW!").arg(this->getActiveDeviceType()).arg(UDP_MAX_LED_NUM); this->setInError ( errorReason ); - isInitOK = false; } else { - // Initialise sub-class - isInitOK = ProviderUdp::init(deviceConfig); + _hostName = deviceConfig[ CONFIG_HOST ].toString(); + _port = deviceConfig[CONFIG_PORT].toInt(RAW_DEFAULT_PORT); + + Debug(_log, "Hostname/IP : %s", QSTRING_CSTR(_hostName) ); + Debug(_log, "Port : %d", _port ); + + isInitOK = true; } } return isInitOK; } +int LedDeviceUdpRaw::open() +{ + int retval = -1; + _isDeviceReady = false; + + if (NetUtils::resolveHostToAddress(_log, _hostName, _address)) + { + if (ProviderUdp::open() == 0) + { + // Everything is OK, device is ready + _isDeviceReady = true; + retval = 0; + } + } + return retval; +} + int LedDeviceUdpRaw::write(const std::vector &ledValues) { const uint8_t * dataPtr = reinterpret_cast(ledValues.data()); @@ -59,8 +76,11 @@ int LedDeviceUdpRaw::write(const std::vector &ledValues) QJsonObject LedDeviceUdpRaw::getProperties(const QJsonObject& params) { DebugIf(verbose, _log, "params: [%s]", QString(QJsonDocument(params).toJson(QJsonDocument::Compact)).toUtf8().constData() ); + QJsonObject properties; + Info(_log, "Get properties for %s", QSTRING_CSTR(_activeDeviceType)); + QJsonObject propertiesDetails; propertiesDetails.insert("maxLedCount", UDP_MAX_LED_NUM); diff --git a/libsrc/leddevice/dev_net/LedDeviceUdpRaw.h b/libsrc/leddevice/dev_net/LedDeviceUdpRaw.h index 9e7d8210..3a63518e 100644 --- a/libsrc/leddevice/dev_net/LedDeviceUdpRaw.h +++ b/libsrc/leddevice/dev_net/LedDeviceUdpRaw.h @@ -7,7 +7,7 @@ /// /// Implementation of the LedDevice interface for sending LED colors via UDP /// -class LedDeviceUdpRaw : public ProviderUdp +class LedDeviceUdpRaw : public virtual ProviderUdp { public: @@ -44,6 +44,13 @@ protected: /// bool init(const QJsonObject &deviceConfig) override; + /// + /// @brief Opens the output device. + /// + /// @return Zero on success (i.e. device is ready), else negative + /// + int open() override; + /// /// @brief Writes the RGB-Color values to the LEDs. /// diff --git a/libsrc/leddevice/dev_net/LedDeviceWled.cpp b/libsrc/leddevice/dev_net/LedDeviceWled.cpp index 3998311f..8c772545 100644 --- a/libsrc/leddevice/dev_net/LedDeviceWled.cpp +++ b/libsrc/leddevice/dev_net/LedDeviceWled.cpp @@ -1,11 +1,18 @@ // Local-Hyperion includes #include "LedDeviceWled.h" +#include + #include #include -#include -#include +// mDNS discover +#ifdef ENABLE_MDNS +#include +#include +#endif +#include +#include // Constants namespace { @@ -13,16 +20,22 @@ namespace { const bool verbose = false; // Configuration settings -const char CONFIG_ADDRESS[] = "host"; +const char CONFIG_HOST[] = "host"; +const char CONFIG_STREAM_PROTOCOL[] = "streamProtocol"; const char CONFIG_RESTORE_STATE[] = "restoreOriginalState"; const char CONFIG_BRIGHTNESS[] = "brightness"; const char CONFIG_BRIGHTNESS_OVERWRITE[] = "overwriteBrightness"; const char CONFIG_SYNC_OVERWRITE[] = "overwriteSync"; -// UDP elements -const quint16 STREAM_DEFAULT_PORT = 19446; +const char DEFAULT_STREAM_PROTOCOL[] = "DDP"; + +// UDP-RAW +const int UDP_STREAM_DEFAULT_PORT = 19446; const int UDP_MAX_LED_NUM = 490; +// DDP +const char WLED_VERSION_DDP[] = "0.11.0"; + // WLED JSON-API elements const int API_DEFAULT_PORT = -1; //Use default port per communication scheme @@ -46,7 +59,7 @@ constexpr std::chrono::milliseconds DEFAULT_IDENTIFY_TIME{ 2000 }; } //End of constants LedDeviceWled::LedDeviceWled(const QJsonObject &deviceConfig) - : ProviderUdp(deviceConfig) + : ProviderUdp(deviceConfig), LedDeviceUdpDdp(deviceConfig), LedDeviceUdpRaw(deviceConfig) ,_restApi(nullptr) ,_apiPort(API_DEFAULT_PORT) ,_isBrightnessOverwrite(DEFAULT_IS_BRIGHTNESS_OVERWRITE) @@ -54,7 +67,12 @@ LedDeviceWled::LedDeviceWled(const QJsonObject &deviceConfig) ,_isSyncOverwrite(DEFAULT_IS_SYNC_OVERWRITE) ,_originalStateUdpnSend(false) ,_originalStateUdpnRecv(true) + ,_isStreamDDP(true) { +#ifdef ENABLE_MDNS + QMetaObject::invokeMethod(&MdnsBrowser::getInstance(), "browseForServiceType", + Qt::QueuedConnection, Q_ARG(QByteArray, MdnsServiceRegister::getServiceType(_activeDeviceType))); +#endif } LedDeviceWled::~LedDeviceWled() @@ -70,25 +88,30 @@ LedDevice* LedDeviceWled::construct(const QJsonObject &deviceConfig) bool LedDeviceWled::init(const QJsonObject &deviceConfig) { - bool isInitOK = false; + bool isInitOK {false}; - // Initialise LedDevice sub-class, ProviderUdp::init will be executed later, if connectivity is defined - if ( LedDevice::init(deviceConfig) ) + QString streamProtocol = _devConfig[CONFIG_STREAM_PROTOCOL].toString(DEFAULT_STREAM_PROTOCOL); + + if (streamProtocol != DEFAULT_STREAM_PROTOCOL) { - // Initialise LedDevice configuration and execution environment - int configuredLedCount = this->getLedCount(); - Debug(_log, "DeviceType : %s", QSTRING_CSTR( this->getActiveDeviceType() )); - Debug(_log, "LedCount : %d", configuredLedCount); - Debug(_log, "ColorOrder : %s", QSTRING_CSTR( this->getColorOrder() )); - Debug(_log, "LatchTime : %d", this->getLatchTime()); + _isStreamDDP = false; + } + Debug(_log, "Stream protocol : %s", QSTRING_CSTR(streamProtocol)); + Debug(_log, "Stream DDP : %d", _isStreamDDP); - if (configuredLedCount > UDP_MAX_LED_NUM) - { - QString errorReason = QString("Device type %1 can only be run with maximum %2 LEDs!").arg(this->getActiveDeviceType()).arg(UDP_MAX_LED_NUM); - this->setInError ( errorReason ); - return false; - } + if (_isStreamDDP) + { + LedDeviceUdpDdp::init(deviceConfig); + } + else + { + _devConfig["port"] = UDP_STREAM_DEFAULT_PORT; + LedDeviceUdpRaw::init(_devConfig); + } + if (!_isDeviceInError) + { + _apiPort = API_DEFAULT_PORT; _isRestoreOrigState = _devConfig[CONFIG_RESTORE_STATE].toBool(DEFAULT_IS_RESTORE_STATE); _isSyncOverwrite = _devConfig[CONFIG_SYNC_OVERWRITE].toBool(DEFAULT_IS_SYNC_OVERWRITE); _isBrightnessOverwrite = _devConfig[CONFIG_BRIGHTNESS_OVERWRITE].toBool(DEFAULT_IS_BRIGHTNESS_OVERWRITE); @@ -99,57 +122,78 @@ bool LedDeviceWled::init(const QJsonObject &deviceConfig) Debug(_log, "Overwrite Brightn.: %d", _isBrightnessOverwrite); Debug(_log, "Set Brightness to : %d", _brightness); - //Set hostname as per configuration - QString hostName = deviceConfig[ CONFIG_ADDRESS ].toString(); - - //If host not configured the init fails - if ( hostName.isEmpty() ) - { - this->setInError("No target hostname nor IP defined"); - return false; - } - else - { - QStringList addressparts = QStringUtils::split(hostName,":", QStringUtils::SplitBehavior::SkipEmptyParts); - _hostname = addressparts[0]; - if ( addressparts.size() > 1 ) - { - _apiPort = addressparts[1].toInt(); - } - else - { - _apiPort = API_DEFAULT_PORT; - } - - if ( initRestAPI( _hostname, _apiPort ) ) - { - // Update configuration with hostname without port - _devConfig["host"] = _hostname; - _devConfig["port"] = STREAM_DEFAULT_PORT; - - isInitOK = ProviderUdp::init(_devConfig); - Debug(_log, "Hostname/IP : %s", QSTRING_CSTR( _hostname )); - Debug(_log, "Port : %d", _port); - } - } + isInitOK = true; } + return isInitOK; } -bool LedDeviceWled::initRestAPI(const QString &hostname, int port) +bool LedDeviceWled::openRestAPI() { - bool isInitOK = false; + bool isInitOK {true}; if ( _restApi == nullptr ) { - _restApi = new ProviderRestApi(hostname, port); - _restApi->setBasePath( API_BASE_PATH ); + _restApi = new ProviderRestApi(_address.toString(), _apiPort); + _restApi->setLogger(_log); - isInitOK = true; + _restApi->setBasePath( API_BASE_PATH ); } + else + { + _restApi->setHost(_address.toString()); + _restApi->setPort(_apiPort); + } + return isInitOK; } +int LedDeviceWled::open() +{ + int retval = -1; + _isDeviceReady = false; + + if (NetUtils::resolveHostToAddress(_log, _hostName, _address, _apiPort)) + { + if ( openRestAPI() ) + { + if (_isStreamDDP) + { + if (LedDeviceUdpDdp::open() == 0) + { + // Everything is OK, device is ready + _isDeviceReady = true; + retval = 0; + } + } + else + { + if (LedDeviceUdpRaw::open() == 0) + { + // Everything is OK, device is ready + _isDeviceReady = true; + retval = 0; + } + } + } + } + return retval; +} + +int LedDeviceWled::close() +{ + int retval = -1; + if (_isStreamDDP) + { + retval = LedDeviceUdpDdp::close(); + } + else + { + retval = LedDeviceUdpRaw::close(); + } + return retval; +} + QString LedDeviceWled::getOnOffRequest(bool isOn) const { QString state = isOn ? STATE_VALUE_TRUE : STATE_VALUE_FALSE; @@ -316,6 +360,16 @@ QJsonObject LedDeviceWled::discover(const QJsonObject& /*params*/) devicesDiscovered.insert("ledDeviceType", _activeDeviceType ); QJsonArray deviceList; + +#ifdef ENABLE_MDNS + QString discoveryMethod("mDNS"); + deviceList = MdnsBrowser::getInstance().getServicesDiscoveredJson( + MdnsServiceRegister::getServiceType(_activeDeviceType), + MdnsServiceRegister::getServiceNameFilter(_activeDeviceType), + DEFAULT_DISCOVER_TIMEOUT + ); + devicesDiscovered.insert("discoveryMethod", discoveryMethod); +#endif devicesDiscovered.insert("devices", deviceList); DebugIf(verbose, _log, "devicesDiscovered: [%s]", QString(QJsonDocument(devicesDiscovered).toJson(QJsonDocument::Compact)).toUtf8().constData() ); @@ -327,41 +381,45 @@ QJsonObject LedDeviceWled::getProperties(const QJsonObject& params) DebugIf(verbose, _log, "params: [%s]", QString(QJsonDocument(params).toJson(QJsonDocument::Compact)).toUtf8().constData() ); QJsonObject properties; - QString hostName = params["host"].toString(""); + _hostName = params[CONFIG_HOST].toString(""); + _apiPort = API_DEFAULT_PORT; - if ( !hostName.isEmpty() ) + Info(_log, "Get properties for %s, hostname (%s)", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(_hostName) ); + + if (NetUtils::resolveHostToAddress(_log, _hostName, _address, _apiPort)) { - QString filter = params["filter"].toString(""); - - // Resolve hostname and port (or use default API port) - QStringList addressparts = QStringUtils::split(hostName,":", QStringUtils::SplitBehavior::SkipEmptyParts); - QString apiHost = addressparts[0]; - int apiPort; - - if ( addressparts.size() > 1) + if ( openRestAPI() ) { - apiPort = addressparts[1].toInt(); - } - else - { - apiPort = API_DEFAULT_PORT; - } + QString filter = params["filter"].toString(""); + _restApi->setPath(filter); - initRestAPI(apiHost, apiPort); - _restApi->setPath(filter); + httpResponse response = _restApi->get(); + if ( response.error() ) + { + Warning (_log, "%s get properties failed with error: '%s'", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(response.getErrorReason())); + } - httpResponse response = _restApi->get(); - if ( response.error() ) - { - Warning (_log, "%s get properties failed with error: '%s'", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(response.getErrorReason())); - } + QJsonObject propertiesDetails = response.getBody().object(); - QJsonObject propertiesDetails = response.getBody().object(); - if (!propertiesDetails.isEmpty()) - { - propertiesDetails.insert("maxLedCount", UDP_MAX_LED_NUM); + semver::version currentVersion {""}; + if (currentVersion.setVersion(propertiesDetails.value("ver").toString().toStdString())) + { + semver::version ddpVersion{WLED_VERSION_DDP}; + if (currentVersion < ddpVersion) + { + Warning(_log, "DDP streaming not supported by your WLED device version [%s], minimum version expected [%s]. Fall back to UDP-Streaming (%d LEDs max)", currentVersion.getVersion().c_str(), ddpVersion.getVersion().c_str(), UDP_MAX_LED_NUM); + if (!propertiesDetails.isEmpty()) + { + propertiesDetails.insert("maxLedCount", UDP_MAX_LED_NUM); + } + } + else + { + Info(_log, "DDP streaming is supported by your WLED device version [%s]. No limitation in number of LEDs.", currentVersion.getVersion().c_str(), ddpVersion.getVersion().c_str()); + } + } + properties.insert("properties", propertiesDetails); } - properties.insert("properties", propertiesDetails); DebugIf(verbose, _log, "properties: [%s]", QString(QJsonDocument(properties).toJson(QJsonDocument::Compact)).toUtf8().constData() ); } @@ -372,41 +430,40 @@ void LedDeviceWled::identify(const QJsonObject& params) { DebugIf(verbose, _log, "params: [%s]", QString(QJsonDocument(params).toJson(QJsonDocument::Compact)).toUtf8().constData()); - QString hostName = params["host"].toString(""); + _hostName = params[CONFIG_HOST].toString(""); + _apiPort = API_DEFAULT_PORT; - if ( !hostName.isEmpty() ) + Info(_log, "Identify %s, hostname (%s)", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(_hostName) ); + + if (NetUtils::resolveHostToAddress(_log, _hostName, _address, _apiPort)) { - // Resolve hostname and port (or use default API port) - QStringList addressparts = QStringUtils::split(hostName,":", QStringUtils::SplitBehavior::SkipEmptyParts); - QString apiHost = addressparts[0]; - int apiPort; - - if ( addressparts.size() > 1) + if ( openRestAPI() ) { - apiPort = addressparts[1].toInt(); + _isRestoreOrigState = true; + storeState(); + + QString request = getOnOffRequest(true) + "," + getLorRequest(1) + "," + getEffectRequest(25); + sendStateUpdateRequest(request); + + wait(DEFAULT_IDENTIFY_TIME); + + restoreState(); } - else - { - apiPort = API_DEFAULT_PORT; - } - - initRestAPI(apiHost, apiPort); - - _isRestoreOrigState = true; - storeState(); - - QString request = getOnOffRequest(true) + "," + getLorRequest(1) + "," + getEffectRequest(25); - sendStateUpdateRequest(request); - - wait(DEFAULT_IDENTIFY_TIME); - - restoreState(); } } int LedDeviceWled::write(const std::vector &ledValues) { - const uint8_t * dataPtr = reinterpret_cast(ledValues.data()); + int rc {0}; - return writeBytes( _ledRGBCount, dataPtr); + if (_isStreamDDP) + { + rc = LedDeviceUdpDdp::write(ledValues); + } + else + { + rc = LedDeviceUdpRaw::write(ledValues); + } + + return rc; } diff --git a/libsrc/leddevice/dev_net/LedDeviceWled.h b/libsrc/leddevice/dev_net/LedDeviceWled.h index 2f89b59c..5b5f9940 100644 --- a/libsrc/leddevice/dev_net/LedDeviceWled.h +++ b/libsrc/leddevice/dev_net/LedDeviceWled.h @@ -4,12 +4,13 @@ // LedDevice includes #include #include "ProviderRestApi.h" -#include "ProviderUdp.h" +#include "LedDeviceUdpDdp.h" +#include "LedDeviceUdpRaw.h" /// /// Implementation of a WLED-device /// -class LedDeviceWled : public ProviderUdp +class LedDeviceWled : public LedDeviceUdpDdp, LedDeviceUdpRaw { public: @@ -81,6 +82,20 @@ protected: /// bool init(const QJsonObject &deviceConfig) override; + /// + /// @brief Opens the output device. + /// + /// @return Zero on success (i.e. device is ready), else negative + /// + int open() override; + + /// + /// @brief Closes the UDP device. + /// + /// @return Zero on success (i.e. device is closed), else negative + /// + int close() override; + /// /// @brief Writes the RGB-Color values to the LEDs. /// @@ -127,11 +142,9 @@ private: /// /// @brief Initialise the access to the REST-API wrapper /// - /// @param[in] host - /// @param[in] port /// @return True, if success /// - bool initRestAPI(const QString &hostname, int port ); + bool openRestAPI(); /// /// @brief Get command to power WLED-device on or off @@ -148,10 +161,12 @@ private: bool sendStateUpdateRequest(const QString &request); + QString resolveAddress (const QString& hostName); + ///REST-API wrapper ProviderRestApi* _restApi; - QString _hostname; + QString _hostAddress; int _apiPort; QJsonObject _originalStateProperties; @@ -162,6 +177,8 @@ private: bool _isSyncOverwrite; bool _originalStateUdpnSend; bool _originalStateUdpnRecv; + + bool _isStreamDDP; }; #endif // LEDDEVICEWLED_H diff --git a/libsrc/leddevice/dev_net/LedDeviceYeelight.cpp b/libsrc/leddevice/dev_net/LedDeviceYeelight.cpp index 7c41f99d..55bdfdf4 100644 --- a/libsrc/leddevice/dev_net/LedDeviceYeelight.cpp +++ b/libsrc/leddevice/dev_net/LedDeviceYeelight.cpp @@ -1,7 +1,7 @@ -#include "LedDeviceYeelight.h" +#include "LedDeviceYeelight.h" -#include -#include +#include +#include // Qt includes #include @@ -11,8 +11,15 @@ #include #include -#include -#include +#include +#include + +// mDNS discover +#ifdef ENABLE_MDNS +#include +#include +#endif +#include // Constants namespace { @@ -28,6 +35,9 @@ constexpr std::chrono::milliseconds CONNECT_STREAM_TIMEOUT{1000}; // device stre const bool TEST_CORRELATION_IDS = false; //Ignore, if yeelight sends responses in different order as request commands // Configuration settings +const char CONFIG_HOST[] = "host"; +const char CONFIG_PORT[] = "port"; + const char CONFIG_LIGHTS [] = "lights"; const char CONFIG_COLOR_MODEL [] = "colorModel"; @@ -111,7 +121,6 @@ YeelightLight::YeelightLight( Logger *log, const QString &hostname, quint16 port ,_isInMusicMode(false) { _name = hostname; - } YeelightLight::~YeelightLight() @@ -151,25 +160,29 @@ bool YeelightLight::open() } else { - _tcpSocket->connectToHost( _host, _port); - - if ( _tcpSocket->waitForConnected( CONNECT_TIMEOUT.count() ) ) + QHostAddress address; + if (NetUtils::resolveHostToAddress(_log, _host, address)) { - if ( _tcpSocket->state() != QAbstractSocket::ConnectedState ) + _tcpSocket->connectToHost( address.toString(), _port); + + if ( _tcpSocket->waitForConnected( CONNECT_TIMEOUT.count() ) ) + { + if ( _tcpSocket->state() != QAbstractSocket::ConnectedState ) + { + this->setInError( _tcpSocket->errorString() ); + rc = false; + } + else + { + log (2,"open()","Successfully opened Yeelight: %s", QSTRING_CSTR(_host)); + rc = true; + } + } + else { this->setInError( _tcpSocket->errorString() ); rc = false; } - else - { - log (2,"open()","Successfully opened Yeelight: %s", QSTRING_CSTR(_host)); - rc = true; - } - } - else - { - this->setInError( _tcpSocket->errorString() ); - rc = false; } } return rc; @@ -1006,6 +1019,10 @@ LedDeviceYeelight::LedDeviceYeelight(const QJsonObject &deviceConfig) ,_debuglevel(0) ,_musicModeServerPort(-1) { +#ifdef ENABLE_MDNS + QMetaObject::invokeMethod(&MdnsBrowser::getInstance(), "browseForServiceType", + Qt::QueuedConnection, Q_ARG(QByteArray, MdnsServiceRegister::getServiceType(_activeDeviceType))); +#endif } LedDeviceYeelight::~LedDeviceYeelight() @@ -1034,12 +1051,6 @@ bool LedDeviceYeelight::init(const QJsonObject &deviceConfig) if ( LedDevice::init(deviceConfig) ) { - Debug(_log, "DeviceType : %s", QSTRING_CSTR( this->getActiveDeviceType() )); - Debug(_log, "LedCount : %d", this->getLedCount()); - Debug(_log, "ColorOrder : %s", QSTRING_CSTR( this->getColorOrder() )); - Debug(_log, "RewriteTime : %d", this->getRewriteTime()); - Debug(_log, "LatchTime : %d", this->getLatchTime()); - //Get device specific configuration if ( deviceConfig[ CONFIG_COLOR_MODEL ].isString() ) @@ -1102,8 +1113,9 @@ bool LedDeviceYeelight::init(const QJsonObject &deviceConfig) int configuredYeelightsCount = 0; for (const QJsonValueRef light : configuredYeelightLights) { - QString hostName = light.toObject().value("host").toString(); - int port = light.toObject().value("port").toInt(API_DEFAULT_PORT); + QString hostName = light.toObject().value(CONFIG_HOST).toString(); + int port = light.toObject().value(CONFIG_PORT).toInt(API_DEFAULT_PORT); + if ( !hostName.isEmpty() ) { QString name = light.toObject().value("name").toString(); @@ -1133,9 +1145,8 @@ bool LedDeviceYeelight::init(const QJsonObject &deviceConfig) _lightsAddressList.clear(); for (int j = 0; j < static_cast( configuredLedCount ); ++j) { - QString hostName = configuredYeelightLights[j].toObject().value("host").toString(); - int port = configuredYeelightLights[j].toObject().value("port").toInt(API_DEFAULT_PORT); - + QString hostName = configuredYeelightLights[j].toObject().value(CONFIG_HOST).toString(); + int port = configuredYeelightLights[j].toObject().value(CONFIG_PORT).toInt(API_DEFAULT_PORT); _lightsAddressList.append( { hostName, port} ); } @@ -1160,13 +1171,10 @@ bool LedDeviceYeelight::startMusicModeServer() if ( ! _tcpMusicModeServer->isListening() ) { - if (! _tcpMusicModeServer->listen()) + if (! _tcpMusicModeServer->listen(QHostAddress::AnyIPv4)) { - QString errorReason = QString ("(%1) %2").arg(_tcpMusicModeServer->serverError()).arg( _tcpMusicModeServer->errorString()); - Error( _log, "Error: MusicModeServer: %s", QSTRING_CSTR(errorReason)); + QString errorReason = QString ("Failed to start music mode server: (%1) %2").arg(_tcpMusicModeServer->serverError()).arg( _tcpMusicModeServer->errorString()); this->setInError ( errorReason ); - - Error( _log, "Failed to start music mode server"); } else { @@ -1182,12 +1190,14 @@ bool LedDeviceYeelight::startMusicModeServer() } if (_musicModeServerAddress.isNull()) { - Error(_log, "Failed to resolve IP for music mode server"); + _tcpMusicModeServer->close(); + QString errorReason = QString ("Network error - failed to resolve IP for music mode server"); + this->setInError ( errorReason ); } } } - if ( _tcpMusicModeServer->isListening() ) + if ( !_isDeviceInError && _tcpMusicModeServer->isListening() ) { _musicModeServerPort = _tcpMusicModeServer->serverPort(); Debug (_log, "The music mode server is running at %s:%d", QSTRING_CSTR(_musicModeServerAddress.toString()), _musicModeServerPort); @@ -1391,10 +1401,21 @@ QJsonObject LedDeviceYeelight::discover(const QJsonObject& /*params*/) QJsonObject devicesDiscovered; devicesDiscovered.insert("ledDeviceType", _activeDeviceType ); - QString discoveryMethod("ssdp"); QJsonArray deviceList; - deviceList = discover(); +#ifdef ENABLE_MDNS + QString discoveryMethod("mDNS"); + deviceList = MdnsBrowser::getInstance().getServicesDiscoveredJson( + MdnsServiceRegister::getServiceType(_activeDeviceType), + MdnsServiceRegister::getServiceNameFilter(_activeDeviceType), + DEFAULT_DISCOVER_TIMEOUT + ); +#else + QString discoveryMethod("ssdp"); + deviceList = discover(); +#endif + + devicesDiscovered.insert("discoveryMethod", discoveryMethod); devicesDiscovered.insert("devices", deviceList); DebugIf(verbose,_log, "devicesDiscovered: [%s]", QString(QJsonDocument(devicesDiscovered).toJson(QJsonDocument::Compact)).toUtf8().constData() ); @@ -1407,21 +1428,22 @@ QJsonObject LedDeviceYeelight::getProperties(const QJsonObject& params) DebugIf(verbose,_log, "params: [%s]", QString(QJsonDocument(params).toJson(QJsonDocument::Compact)).toUtf8().constData() ); QJsonObject properties; - QString hostName = params["hostname"].toString(""); - quint16 apiPort = static_cast( params["port"].toInt(API_DEFAULT_PORT) ); + QString hostName = params[CONFIG_HOST].toString(""); + quint16 apiPort = static_cast( params[CONFIG_PORT].toInt(API_DEFAULT_PORT) ); - if ( !hostName.isEmpty() ) + Info(_log, "Get properties for %s, hostname (%s)", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(hostName) ); + + QHostAddress address; + if (NetUtils::resolveHostToAddress(_log, hostName, address)) { - YeelightLight yeelight(_log, hostName, apiPort); - - //yeelight.setDebuglevel(3); + YeelightLight yeelight(_log, address.toString(), apiPort); if ( yeelight.open() ) { properties.insert("properties", yeelight.getProperties()); yeelight.close(); } } - Debug(_log, "properties: [%s]", QString(QJsonDocument(properties).toJson(QJsonDocument::Compact)).toUtf8().constData() ); + DebugIf(verbose, _log, "properties: [%s]", QString(QJsonDocument(properties).toJson(QJsonDocument::Compact)).toUtf8().constData() ); return properties; } @@ -1430,15 +1452,15 @@ void LedDeviceYeelight::identify(const QJsonObject& params) { DebugIf(verbose,_log, "params: [%s]", QString(QJsonDocument(params).toJson(QJsonDocument::Compact)).toUtf8().constData() ); - QString hostName = params["hostname"].toString(""); - quint16 apiPort = static_cast( params["port"].toInt(API_DEFAULT_PORT) ); - Debug (_log, "apiHost [%s], apiPort [%d]", QSTRING_CSTR(hostName), apiPort); + QString hostName = params[CONFIG_HOST].toString(""); + quint16 apiPort = static_cast( params[CONFIG_PORT].toInt(API_DEFAULT_PORT) ); - if ( !hostName.isEmpty() ) + Info(_log, "Identify %s, hostname (%s)", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(hostName) ); + + QHostAddress address; + if (NetUtils::resolveHostToAddress(_log, hostName, address)) { - YeelightLight yeelight(_log, hostName, apiPort); - //yeelight.setDebuglevel(3); - + YeelightLight yeelight(_log, address.toString(), apiPort); if ( yeelight.open() ) { yeelight.identify(); diff --git a/libsrc/leddevice/dev_net/LedDeviceYeelight.h b/libsrc/leddevice/dev_net/LedDeviceYeelight.h index c0957aca..ab99d0bd 100644 --- a/libsrc/leddevice/dev_net/LedDeviceYeelight.h +++ b/libsrc/leddevice/dev_net/LedDeviceYeelight.h @@ -449,8 +449,8 @@ public: /// Following parameters are required /// @code /// { - /// "hostname" : "hostname or IP", - /// "port" : port, default port 55443 is used when not provided + /// "host" : "hostname or IP", + /// "port" : port, default port 55443 is used when not provided /// } ///@endcode /// @@ -465,8 +465,8 @@ public: /// Following parameters are required /// @code /// { - /// "hostname" : "hostname or IP", - /// "port" : port, default port 55443 is used when not provided + /// "host" : "hostname or IP", + /// "port" : port, default port 55443 is used when not provided /// } ///@endcode /// diff --git a/libsrc/leddevice/dev_net/ProviderRestApi.cpp b/libsrc/leddevice/dev_net/ProviderRestApi.cpp index b1185c2a..6957891c 100644 --- a/libsrc/leddevice/dev_net/ProviderRestApi.cpp +++ b/libsrc/leddevice/dev_net/ProviderRestApi.cpp @@ -136,13 +136,19 @@ httpResponse ProviderRestApi::get(const QUrl& url) QNetworkRequest request(_networkRequestHeaders); request.setUrl(url); +#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) + _networkManager->setTransferTimeout(DEFAULT_REST_TIMEOUT.count()); +#endif + QNetworkReply* reply = _networkManager->get(request); // Connect requestFinished signal to quit slot of the loop. QEventLoop loop; QEventLoop::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit); +#if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0)) ReplyTimeout::set(reply, DEFAULT_REST_TIMEOUT.count()); +#endif // Go into the loop until the request is finished. loop.exec(); @@ -178,12 +184,18 @@ httpResponse ProviderRestApi::put(const QUrl &url, const QByteArray &body) QNetworkRequest request(_networkRequestHeaders); request.setUrl(url); +#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) + _networkManager->setTransferTimeout(DEFAULT_REST_TIMEOUT.count()); +#endif + QNetworkReply* reply = _networkManager->put(request, body); // Connect requestFinished signal to quit slot of the loop. QEventLoop loop; QEventLoop::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit); +#if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0)) ReplyTimeout::set(reply, DEFAULT_REST_TIMEOUT.count()); +#endif // Go into the loop until the request is finished. loop.exec(); @@ -220,10 +232,19 @@ httpResponse ProviderRestApi::post(const QUrl& url, const QByteArray& body) QNetworkRequest request(_networkRequestHeaders); request.setUrl(url); +#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) + _networkManager->setTransferTimeout(DEFAULT_REST_TIMEOUT.count()); +#endif + QNetworkReply* reply = _networkManager->post(request, body); // Connect requestFinished signal to quit slot of the loop. QEventLoop loop; QEventLoop::connect(reply,&QNetworkReply::finished,&loop,&QEventLoop::quit); + +#if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0)) + ReplyTimeout::set(reply, DEFAULT_REST_TIMEOUT.count()); +#endif + // Go into the loop until the request is finished. loop.exec(); @@ -249,6 +270,10 @@ httpResponse ProviderRestApi::deleteResource(const QUrl& url) QNetworkRequest request(_networkRequestHeaders); request.setUrl(url); +#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) + _networkManager->setTransferTimeout(DEFAULT_REST_TIMEOUT.count()); +#endif + QNetworkReply* reply = _networkManager->deleteResource(request); // Connect requestFinished signal to quit slot of the loop. QEventLoop loop; @@ -256,6 +281,10 @@ httpResponse ProviderRestApi::deleteResource(const QUrl& url) // Go into the loop until the request is finished. loop.exec(); +#if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0)) + ReplyTimeout::set(reply, DEFAULT_REST_TIMEOUT.count()); +#endif + httpResponse response; if (reply->operation() == QNetworkAccessManager::DeleteOperation) { @@ -331,16 +360,9 @@ httpResponse ProviderRestApi::getResponse(QNetworkReply* const& reply) } errorReason = QString ("[%3 %4] - %5").arg(httpStatusCode).arg(httpReason, advise); } - else { - + else + { errorReason = reply->errorString(); - - if ( reply->error() == QNetworkReply::OperationCanceledError ) - { - //Do not report errors caused by request cancellation because of timeouts - Debug(_log, "Reply: [%s]", QSTRING_CSTR(errorReason) ); - } - else { response.setError(true); response.setErrorReason(errorReason); diff --git a/libsrc/leddevice/dev_net/ProviderRestApi.h b/libsrc/leddevice/dev_net/ProviderRestApi.h index 50d8a399..a87c5f2c 100644 --- a/libsrc/leddevice/dev_net/ProviderRestApi.h +++ b/libsrc/leddevice/dev_net/ProviderRestApi.h @@ -77,12 +77,12 @@ public: private: - QJsonDocument _responseBody; + QJsonDocument _responseBody {}; bool _hasError = false; QString _errorReason; int _httpStatusCode = 0; - QNetworkReply::NetworkError _networkReplyError = QNetworkReply::NoError; + QNetworkReply::NetworkError _networkReplyError { QNetworkReply::NoError }; }; /// @@ -291,6 +291,13 @@ public: /// void removeAllHeaders() { _networkRequestHeaders = QNetworkRequest(); } + /// + /// @brief Set the common logger for LED-devices. + /// + /// @param[in] log The logger to be used + /// + void setLogger(Logger* log) { _log = log; } + private: /// diff --git a/libsrc/leddevice/dev_net/ProviderUdp.cpp b/libsrc/leddevice/dev_net/ProviderUdp.cpp index fc3cc016..4a82bc87 100644 --- a/libsrc/leddevice/dev_net/ProviderUdp.cpp +++ b/libsrc/leddevice/dev_net/ProviderUdp.cpp @@ -10,16 +10,19 @@ #include #include +// mDNS discover +#ifdef ENABLE_MDNS +#include +#endif +#include + // Local Hyperion includes #include "ProviderUdp.h" -const ushort MAX_PORT = 65535; - ProviderUdp::ProviderUdp(const QJsonObject& deviceConfig) : LedDevice(deviceConfig) , _udpSocket(nullptr) - , _port(1) - , _defaultHost("127.0.0.1") + , _port(-1) { _latchTime_ms = 0; } @@ -29,83 +32,38 @@ ProviderUdp::~ProviderUdp() delete _udpSocket; } -bool ProviderUdp::init(const QJsonObject& deviceConfig) -{ - bool isInitOK = false; - - // Initialise sub-class - if (LedDevice::init(deviceConfig)) - { - QString host = deviceConfig["host"].toString(_defaultHost); - - if (_address.setAddress(host)) - { - Debug(_log, "Successfully parsed %s as an IP-address.", QSTRING_CSTR(_address.toString())); - } - else - { - QHostInfo hostInfo = QHostInfo::fromName(host); - if (hostInfo.error() == QHostInfo::NoError) - { - _address = hostInfo.addresses().first(); - Debug(_log, "Successfully resolved IP-address (%s) for hostname (%s).", QSTRING_CSTR(_address.toString()), QSTRING_CSTR(host)); - } - else - { - QString errortext = QString("Failed resolving IP-address for [%1], (%2) %3").arg(host).arg(hostInfo.error()).arg(hostInfo.errorString()); - this->setInError(errortext); - isInitOK = false; - } - } - - if (!_isDeviceInError) - { - int config_port = deviceConfig["port"].toInt(_port); - if (config_port <= 0 || config_port > MAX_PORT) - { - QString errortext = QString("Invalid target port [%1]!").arg(config_port); - this->setInError(errortext); - isInitOK = false; - } - else - { - _port = static_cast(config_port); - Debug(_log, "UDP socket will write to %s port: %u", QSTRING_CSTR(_address.toString()), _port); - - _udpSocket = new QUdpSocket(this); - - isInitOK = true; - } - } - } - return isInitOK; -} - int ProviderUdp::open() { int retval = -1; _isDeviceReady = false; - // Try to bind the UDP-Socket - if (_udpSocket != nullptr) + if (!_isDeviceInError) { - if (_udpSocket->state() != QAbstractSocket::BoundState) - { - QHostAddress localAddress = QHostAddress::Any; - quint16 localPort = 0; - if (!_udpSocket->bind(localAddress, localPort)) + if (_udpSocket == nullptr) { - QString warntext = QString("Could not bind local address: %1, (%2) %3").arg(localAddress.toString()).arg(_udpSocket->error()).arg(_udpSocket->errorString()); - Warning(_log, "%s", QSTRING_CSTR(warntext)); + _udpSocket = new QUdpSocket(this); + } + + // Try to bind the UDP-Socket + if (_udpSocket != nullptr) + { + Info(_log, "Stream UDP data to %s port: %d", QSTRING_CSTR(_address.toString()), _port); + if (_udpSocket->state() != QAbstractSocket::BoundState) + { + QHostAddress localAddress = QHostAddress::Any; + quint16 localPort = 0; + if (!_udpSocket->bind(localAddress, localPort)) + { + QString warntext = QString("Could not bind local address: %1, (%2) %3").arg(localAddress.toString()).arg(_udpSocket->error()).arg(_udpSocket->errorString()); + Warning(_log, "%s", QSTRING_CSTR(warntext)); + } + } + retval = 0; + } + else + { + this->setInError(" Open error. UDP Socket not initialised!"); } - } - // Everything is OK, device is ready - _isDeviceReady = true; - retval = 0; - } - else - { - this->setInError(" Open error. UDP Socket not initialised!"); } return retval; } @@ -131,7 +89,7 @@ int ProviderUdp::close() int ProviderUdp::writeBytes(const unsigned size, const uint8_t* data) { int rc = 0; - qint64 bytesWritten = _udpSocket->writeDatagram(reinterpret_cast(data), size, _address, _port); + qint64 bytesWritten = _udpSocket->writeDatagram(reinterpret_cast(data), size, _address, static_cast(_port)); if (bytesWritten == -1 || bytesWritten != size) { @@ -144,7 +102,7 @@ int ProviderUdp::writeBytes(const unsigned size, const uint8_t* data) int ProviderUdp::writeBytes(const QByteArray& bytes) { int rc = 0; - qint64 bytesWritten = _udpSocket->writeDatagram(bytes, _address, _port); + qint64 bytesWritten = _udpSocket->writeDatagram(bytes, _address, static_cast(_port)); if (bytesWritten == -1 || bytesWritten != bytes.size()) { diff --git a/libsrc/leddevice/dev_net/ProviderUdp.h b/libsrc/leddevice/dev_net/ProviderUdp.h index 7a30c570..c3af5844 100644 --- a/libsrc/leddevice/dev_net/ProviderUdp.h +++ b/libsrc/leddevice/dev_net/ProviderUdp.h @@ -32,14 +32,6 @@ public: protected: - /// - /// @brief Initialise the UDP device's configuration and network address details - /// - /// @param[in] deviceConfig the JSON device configuration - /// @return True, if success - /// - bool init(const QJsonObject& deviceConfig) override; - /// /// @brief Opens the output device. /// @@ -74,10 +66,10 @@ protected: int writeBytes(const QByteArray& bytes); /// - QUdpSocket* _udpSocket; + QUdpSocket* _udpSocket; + QString _hostName; QHostAddress _address; - quint16 _port; - QString _defaultHost; + int _port; }; #endif // PROVIDERUDP_H diff --git a/libsrc/leddevice/dev_net/ProviderUdpSSL.cpp b/libsrc/leddevice/dev_net/ProviderUdpSSL.cpp index 5de82f0a..d71af932 100644 --- a/libsrc/leddevice/dev_net/ProviderUdpSSL.cpp +++ b/libsrc/leddevice/dev_net/ProviderUdpSSL.cpp @@ -12,12 +12,27 @@ // Local Hyperion includes #include "ProviderUdpSSL.h" +#include + +#include "mbedtls/version.h" + +// Constants +namespace { + +const int DEFAULT_SSLPORT = 2100; + +const char DEFAULT_TRANSPORT_TYPE[] = "DTLS"; +const char DEFAULT_SEED_CUSTOM[] = "dtls_client"; + +const int DEFAULT_HANDSHAKE_ATTEMPTS = 5; +const int DEFAULT_HANDSHAKE_TIMEOUT_MIN = 300; +const int DEFAULT_HANDSHAKE_TIMEOUT_MAX = 1000; +} -const int MAX_RETRY = 5; -const ushort MAX_PORT_SSL = 65535; ProviderUdpSSL::ProviderUdpSSL(const QJsonObject &deviceConfig) : LedDevice(deviceConfig) + , _port(-1) , client_fd() , entropy() , ssl() @@ -25,29 +40,43 @@ ProviderUdpSSL::ProviderUdpSSL(const QJsonObject &deviceConfig) , cacert() , ctr_drbg() , timer() - , _transport_type("DTLS") - , _custom("dtls_client") - , _address("127.0.0.1") - , _defaultHost("127.0.0.1") - , _port(1) + , _transport_type(DEFAULT_TRANSPORT_TYPE) + , _custom(DEFAULT_SEED_CUSTOM) , _ssl_port(1) , _server_name() , _psk() , _psk_identity() - , _read_timeout(STREAM_SSL_READ_TIMEOUT.count()) - , _handshake_timeout_min(STREAM_SSL_HANDSHAKE_TIMEOUT_MIN.count()) - , _handshake_timeout_max(STREAM_SSL_HANDSHAKE_TIMEOUT_MAX.count()) - , _handshake_attempts(5) - , _retry_left(MAX_RETRY) - , _stopConnection(true) - , _debugStreamer(false) - , _debugLevel(0) + , _handshake_attempts(DEFAULT_HANDSHAKE_ATTEMPTS) + , _handshake_timeout_min(DEFAULT_HANDSHAKE_TIMEOUT_MIN) + , _handshake_timeout_max(DEFAULT_HANDSHAKE_TIMEOUT_MAX) + , _streamReady(false) + , _streamPaused(false) + { - _latchTime_ms = 1; + bool error = false; + + try + { + mbedtls_ctr_drbg_init(&ctr_drbg); + error = !seedingRNG(); + } + catch (...) + { + error = true; + } + + if (error) + { + Error(_log, "Failed to initialize mbedtls seed"); + } } ProviderUdpSSL::~ProviderUdpSSL() { + stopConnection(); + + mbedtls_ctr_drbg_free(&ctr_drbg); + mbedtls_entropy_free(&entropy); } bool ProviderUdpSSL::init(const QJsonObject &deviceConfig) @@ -57,76 +86,26 @@ bool ProviderUdpSSL::init(const QJsonObject &deviceConfig) // Initialise sub-class if ( LedDevice::init(deviceConfig) ) { - _debugStreamer = deviceConfig["debugStreamer"].toBool(false); - _debugLevel = deviceConfig["debugLevel"].toString().toInt(0); - //PSK Pre Shared Key _psk = deviceConfig["psk"].toString(); _psk_identity = deviceConfig["psk_identity"].toString(); - _port = deviceConfig["sslport"].toInt(2100); + _ssl_port = deviceConfig["sslport"].toInt(DEFAULT_SSLPORT); _server_name = deviceConfig["servername"].toString(); - if( deviceConfig.contains("transport_type") ) _transport_type = deviceConfig["transport_type"].toString("DTLS"); - if( deviceConfig.contains("seed_custom") ) _custom = deviceConfig["seed_custom"].toString("dtls_client"); - if( deviceConfig.contains("retry_left") ) _retry_left = deviceConfig["retry_left"].toInt(MAX_RETRY); - if( deviceConfig.contains("read_timeout") ) _read_timeout = deviceConfig["read_timeout"].toInt(0); - if( deviceConfig.contains("hs_timeout_min") ) _handshake_timeout_min = deviceConfig["hs_timeout_min"].toInt(400); - if( deviceConfig.contains("hs_timeout_max") ) _handshake_timeout_max = deviceConfig["hs_timeout_max"].toInt(1000); - if( deviceConfig.contains("hs_attempts") ) _handshake_attempts = deviceConfig["hs_attempts"].toInt(5); + if( deviceConfig.contains("transport_type") ) { _transport_type = deviceConfig["transport_type"].toString(DEFAULT_TRANSPORT_TYPE); } + if( deviceConfig.contains("seed_custom") ) { _custom = deviceConfig["seed_custom"].toString(DEFAULT_SEED_CUSTOM); } + if( deviceConfig.contains("hs_attempts") ) { _handshake_attempts = deviceConfig["hs_attempts"].toInt(DEFAULT_HANDSHAKE_ATTEMPTS); } + if (deviceConfig.contains("hs_timeout_min")) { _handshake_timeout_min = static_cast(deviceConfig["hs_timeout_min"].toInt(DEFAULT_HANDSHAKE_TIMEOUT_MIN)); } + if (deviceConfig.contains("hs_timeout_max")) { _handshake_timeout_max = static_cast(deviceConfig["hs_timeout_max"].toInt(DEFAULT_HANDSHAKE_TIMEOUT_MAX)); } - QString host = deviceConfig["host"].toString(_defaultHost); - - QStringList debugLevels = QStringList() << "No Debug" << "Error" << "State Change" << "Informational" << "Verbose"; - - configLog( "SSL Streamer Debug", "%s", ( _debugStreamer ) ? "yes" : "no" ); - configLog( "SSL DebugLevel", "[%d] %s", _debugLevel, QSTRING_CSTR( debugLevels[ _debugLevel ]) ); - - configLog( "SSL Servername", "%s", QSTRING_CSTR( _server_name ) ); - configLog( "SSL Host", "%s", QSTRING_CSTR( host ) ); - configLog( "SSL Port", "%d", _port ); - configLog( "PSK", "%s", QSTRING_CSTR( _psk ) ); - configLog( "PSK-Identity", "%s", QSTRING_CSTR( _psk_identity ) ); - configLog( "SSL Transport Type", "%s", QSTRING_CSTR( _transport_type ) ); - configLog( "SSL Seed Custom", "%s", QSTRING_CSTR( _custom ) ); - configLog( "SSL Retry Left", "%d", _retry_left ); - configLog( "SSL Read Timeout", "%d", _read_timeout ); - configLog( "SSL Handshake Timeout min", "%d", _handshake_timeout_min ); - configLog( "SSL Handshake Timeout max", "%d", _handshake_timeout_max ); - configLog( "SSL Handshake attempts", "%d", _handshake_attempts ); - - if (_address.setAddress(host)) + if (!NetUtils::isValidPort(_log,_ssl_port,_server_name)) { - Debug(_log, "Successfully parsed %s as an IP-address.", QSTRING_CSTR(_address.toString())); - } - else - { - QHostInfo hostInfo = QHostInfo::fromName(host); - if (hostInfo.error() == QHostInfo::NoError) - { - _address = hostInfo.addresses().first(); - Debug(_log, "Successfully resolved IP-address (%s) for hostname (%s).", QSTRING_CSTR(_address.toString()), QSTRING_CSTR(host)); - } - else - { - QString errortext = QString("Failed resolving IP-address for [%1], (%2) %3").arg(host).arg(hostInfo.error()).arg(hostInfo.errorString()); - this->setInError(errortext); - isInitOK = false; - return isInitOK; - } - } - - int config_port = deviceConfig["sslport"].toInt(_port); - - if ( config_port <= 0 || config_port > MAX_PORT_SSL ) - { - QString errortext = QString ("Invalid target port [%1]!").arg(config_port); + QString errortext = QString ("Invalid SSL port [%1]!").arg(_ssl_port); this->setInError( errortext ); isInitOK = false; } else { - _ssl_port = config_port; - Debug(_log, "UDP SSL will write to %s port: %u", QSTRING_CSTR(_address.toString()), _ssl_port); isInitOK = true; } } @@ -138,9 +117,7 @@ int ProviderUdpSSL::open() int retval = -1; _isDeviceReady = false; - // TODO: Question: Just checking .... Is this one time initialisation or required with every open request (during switch-off/switch-on)? - // In case one time initialisation, it should go to the init method. - // Everything that is required to pen a UDP-SSL connection again (after it maybe was closed remotely should go here) + Info(_log, "Open UDP SSL streaming to %s port: %d", QSTRING_CSTR(_address.toString()), _ssl_port); if ( !initNetwork() ) { @@ -149,6 +126,7 @@ int ProviderUdpSSL::open() else { // Everything is OK -> enable device + Info(_log, "Stream UDP SSL data to %s port: %d", QSTRING_CSTR(_address.toString()), _ssl_port); _isDeviceReady = true; retval = 0; } @@ -157,750 +135,252 @@ int ProviderUdpSSL::open() int ProviderUdpSSL::close() { - // LedDevice specific closing activities int retval = 0; _isDeviceReady = false; - // TODO: You may want to check, if the device is already closed or close it and return, if ok or not - // Test, if device requires closing - if ( true /*If device is still open*/ ) - { - // Close device + Debug(_log, "Close SSL UDP-device: %s", QSTRING_CSTR(this->getActiveDeviceType())); + stopConnection(); - LedDevice::close(); - closeSSLConnection(); - // Everything is OK -> device is closed - } + // Everything is OK -> device is closed return retval; } -void ProviderUdpSSL::closeSSLConnection() -{ - if( _isDeviceReady && !_stopConnection ) - { - closeSSLNotify(); - freeSSLConnection(); - } -} - const int *ProviderUdpSSL::getCiphersuites() const { return mbedtls_ssl_list_ciphersuites(); } -void ProviderUdpSSL::configLog(const char* msg, const char* type, ...) -{ - if( _debugStreamer ) - { - const size_t max_val_length = 1024; - char val[max_val_length]; - va_list args; - va_start(args, type); - vsnprintf(val, max_val_length, type, args); - va_end(args); - std::string s = msg; - int max = 30; - s.append(max - s.length(), ' '); - Debug( _log, "%s: %s", s.c_str(), val ); - } -} - -void ProviderUdpSSL::sslLog(const QString &msg, const char* errorType) -{ - sslLog( QSTRING_CSTR( msg ), errorType ); -} - -void ProviderUdpSSL::sslLog(const char* msg, const char* errorType) -{ - if( strcmp("fatal", errorType) == 0 ) Error( _log, "%s", msg ); - - if( _debugStreamer ) - { - if( strcmp("debug", errorType) == 0 ) Debug( _log, "%s", msg ); - if( strcmp("warning", errorType) == 0 ) Warning( _log, "%s", msg ); - if( strcmp("error", errorType) == 0 ) Error( _log, "%s", msg ); - } -} - bool ProviderUdpSSL::initNetwork() { - sslLog( "init SSL Network..." ); - QMutexLocker locker(&_hueMutex); - if (!initConnection()) return false; - sslLog( "init SSL Network...ok" ); - _stopConnection = false; - return true; + if ((!_isDeviceReady || _streamPaused) && _streamReady) + { + stopConnection(); + } + + return initConnection(); } bool ProviderUdpSSL::initConnection() { - sslLog( "init SSL Network -> initConnection" ); + if (_streamReady) + { + return true; + } mbedtls_net_init(&client_fd); mbedtls_ssl_init(&ssl); mbedtls_ssl_config_init(&conf); mbedtls_x509_crt_init(&cacert); - mbedtls_ctr_drbg_init(&ctr_drbg); - if(!seedingRNG()) return false; - return setupStructure(); + + if (setupStructure()) + { + _streamReady = true; + _streamPaused = false; + return true; + } + + return false; } bool ProviderUdpSSL::seedingRNG() { - sslLog( "Seeding the random number generator..." ); - mbedtls_entropy_init(&entropy); - sslLog( "Set mbedtls_ctr_drbg_seed..." ); - QByteArray customDataArray = _custom.toLocal8Bit(); const char* customData = customDataArray.constData(); int ret = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, - &entropy, reinterpret_cast(customData), - std::min(strlen(customData), (size_t)MBEDTLS_CTR_DRBG_MAX_SEED_INPUT)); + &entropy, reinterpret_cast(customData), + std::min(strlen(customData), static_cast(MBEDTLS_CTR_DRBG_MAX_SEED_INPUT))); if (ret != 0) { - sslLog( QString("mbedtls_ctr_drbg_seed FAILED %1").arg( errorMsg( ret ) ), "error" ); + Error(_log, "%s", QSTRING_CSTR(QString("mbedtls_ctr_drbg_seed FAILED %1").arg(errorMsg(ret)))); return false; } - - sslLog( "Seeding the random number generator...ok" ); - return true; } bool ProviderUdpSSL::setupStructure() { - sslLog( QString( "Setting up the %1 structure").arg( _transport_type ) ); - - //TLS MBEDTLS_SSL_TRANSPORT_STREAM - //DTLS MBEDTLS_SSL_TRANSPORT_DATAGRAM - int transport = ( _transport_type == "DTLS" ) ? MBEDTLS_SSL_TRANSPORT_DATAGRAM : MBEDTLS_SSL_TRANSPORT_STREAM; int ret = mbedtls_ssl_config_defaults(&conf, MBEDTLS_SSL_IS_CLIENT, transport, MBEDTLS_SSL_PRESET_DEFAULT); if (ret != 0) { - sslLog( QString("mbedtls_ssl_config_defaults FAILED %1").arg( errorMsg( ret ) ), "error" ); + Error(_log, "%s", QSTRING_CSTR(QString("mbedtls_ssl_config_defaults FAILED %1").arg(errorMsg(ret)))); return false; } const int * ciphersuites = getCiphersuites(); - if( _debugStreamer ) - { - QString cipher_values; - for(int i=0; ciphersuites != nullptr && ciphersuites[i] != 0; i++) - { - if (i > 0) - cipher_values.append(", "); - cipher_values.append(QString::number(ciphersuites[i])); - } - - sslLog( ( QString("used ciphersuites value: %1").arg( cipher_values ) ) ); - } - mbedtls_ssl_conf_authmode(&conf, MBEDTLS_SSL_VERIFY_REQUIRED); - mbedtls_ssl_conf_ca_chain(&conf, &cacert, NULL); + mbedtls_ssl_conf_ca_chain(&conf, &cacert, nullptr); + + mbedtls_ssl_conf_handshake_timeout(&conf, _handshake_timeout_min, _handshake_timeout_max); mbedtls_ssl_conf_ciphersuites(&conf, ciphersuites); mbedtls_ssl_conf_rng(&conf, mbedtls_ctr_drbg_random, &ctr_drbg); - if ( _debugLevel > 0) - { - mbedtls_ssl_conf_verify(&conf, ProviderUdpSSLVerify, NULL); - mbedtls_ssl_conf_dbg(&conf, ProviderUdpSSLDebug, NULL); - mbedtls_debug_set_threshold( _debugLevel ); - } - - if( _read_timeout > 0 ) mbedtls_ssl_conf_read_timeout(&conf, _read_timeout); - - mbedtls_ssl_conf_handshake_timeout(&conf, _handshake_timeout_min, _handshake_timeout_max); - if ((ret = mbedtls_ssl_setup(&ssl, &conf)) != 0) { - sslLog( QString("mbedtls_ssl_setup FAILED %1").arg( errorMsg( ret ) ), "error" ); + Error(_log, "%s", QSTRING_CSTR(QString("mbedtls_ssl_setup FAILED %1").arg(errorMsg(ret)))); return false; } - if ((ret = mbedtls_ssl_set_hostname(&ssl, QSTRING_CSTR( _server_name ))) != 0) - { - sslLog( QString("mbedtls_ssl_set_hostname FAILED %1").arg( errorMsg( ret ) ), "error" ); - return false; - } - - sslLog( QString( "Setting up the %1 structure...ok").arg( _transport_type ) ); - - return startUPDConnection(); + return setupPSK(); } -bool ProviderUdpSSL::startUPDConnection() +bool ProviderUdpSSL::startConnection() { - sslLog( "init SSL Network -> startUPDConnection" ); - mbedtls_ssl_session_reset(&ssl); - if(!setupPSK()) return false; - - sslLog( QString("Connecting to udp %1:%2").arg( _address.toString() ).arg( _ssl_port ) ); - int ret = mbedtls_net_connect(&client_fd, _address.toString().toUtf8(), std::to_string(_ssl_port).c_str(), MBEDTLS_NET_PROTO_UDP); if (ret != 0) { - sslLog( QString("mbedtls_net_connect FAILED %1").arg( errorMsg( ret ) ), "error" ); + Error(_log, "%s", QSTRING_CSTR(QString("mbedtls_net_connect FAILED %1").arg(errorMsg(ret)))); return false; } mbedtls_ssl_set_bio(&ssl, &client_fd, mbedtls_net_send, mbedtls_net_recv, mbedtls_net_recv_timeout); mbedtls_ssl_set_timer_cb(&ssl, &timer, mbedtls_timing_set_delay, mbedtls_timing_get_delay); - sslLog( "Connecting...ok" ); - return startSSLHandshake(); } bool ProviderUdpSSL::setupPSK() { - QByteArray pskArray = _psk.toUtf8(); - QByteArray pskRawArray = QByteArray::fromHex(pskArray); - - QByteArray pskIdArray = _psk_identity.toUtf8(); - QByteArray pskIdRawArray = pskIdArray; + QByteArray pskRawArray = QByteArray::fromHex(_psk.toUtf8()); + QByteArray pskIdRawArray = _psk_identity.toUtf8(); int ret = mbedtls_ssl_conf_psk( &conf, reinterpret_cast (pskRawArray.constData()), - pskRawArray.length() * sizeof(char), + pskRawArray.length(), reinterpret_cast (pskIdRawArray.constData()), - pskIdRawArray.length() * sizeof(char)); + pskIdRawArray.length()); if (ret != 0) { - sslLog( QString("mbedtls_ssl_conf_psk FAILED %1").arg( errorMsg( ret ) ), "error" ); + Error(_log, "%s", QSTRING_CSTR(QString("mbedtls_ssl_conf_psk FAILED %1").arg(errorMsg(ret)))); return false; } - return true; } bool ProviderUdpSSL::startSSLHandshake() { - sslLog( "init SSL Network -> startSSLHandshake" ); - int ret = 0; - - sslLog( QString( "Performing the SSL/%1 handshake...").arg( _transport_type ) ); - - for (unsigned int attempt = 1; attempt <= _handshake_attempts; ++attempt) + for (int attempt = 1; attempt <= _handshake_attempts; ++attempt) { - sslLog( QString("handshake attempt %1/%2").arg( attempt ).arg( _handshake_attempts ) ); do { ret = mbedtls_ssl_handshake(&ssl); - } - while (ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE); + } while (ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE); if (ret == 0) { break; } - else - { - sslLog( QString("mbedtls_ssl_handshake attempt %1/%2 FAILED %3").arg( attempt ).arg( _handshake_attempts ).arg( errorMsg( ret ) ) ); - } + Warning(_log, "%s", QSTRING_CSTR(QString("mbedtls_ssl_handshake attempt %1/%2 FAILED. Reason: %3").arg(attempt).arg(_handshake_attempts).arg(errorMsg(ret)))); QThread::msleep(200); } if (ret != 0) { - sslLog( QString("mbedtls_ssl_handshake FAILED %1").arg( errorMsg( ret ) ), "error" ); - handleReturn(ret); - sslLog( "UDP SSL Connection failed!", "fatal" ); + Error(_log, "%s", QSTRING_CSTR(QString("mbedtls_ssl_handshake FAILED %1").arg(errorMsg(ret)))); return false; } - else - { - if( ( mbedtls_ssl_get_verify_result( &ssl ) ) != 0 ) { - sslLog( "SSL certificate verification failed!", "fatal" ); - return false; - } - } - sslLog( QString( "Performing the SSL/%1 handshake...ok").arg( _transport_type ) ); + if (mbedtls_ssl_get_verify_result(&ssl) != 0) + { + Error(_log, "SSL certificate verification failed!"); + return false; + } return true; } +void ProviderUdpSSL::stopConnection() +{ + if (_streamReady) + { + closeSSLNotify(); + freeSSLConnection(); + _streamReady = false; + } +} + void ProviderUdpSSL::freeSSLConnection() { - sslLog( "SSL Connection clean-up..." ); - - _stopConnection = true; - try { + Debug(_log, "Release mbedtls"); mbedtls_ssl_session_reset(&ssl); mbedtls_net_free(&client_fd); mbedtls_ssl_free(&ssl); mbedtls_ssl_config_free(&conf); mbedtls_x509_crt_free(&cacert); - mbedtls_ctr_drbg_free(&ctr_drbg); - mbedtls_entropy_free(&entropy); - sslLog( "SSL Connection clean-up...ok" ); } catch (std::exception &e) { - sslLog( QString("SSL Connection clean-up Error: %s").arg( e.what() ) ); + Error(_log, "%s", QSTRING_CSTR(QString("SSL Connection clean-up Error: %s").arg(e.what()))); } catch (...) { - sslLog( "SSL Connection clean-up Error: " ); + Error(_log, "SSL Connection clean-up Error: "); } } -void ProviderUdpSSL::writeBytes(unsigned int size, const uint8_t* data) +void ProviderUdpSSL::writeBytes(unsigned int size, const uint8_t* data, bool flush) { - if ( _stopConnection ) + if (!_streamReady || _streamPaused) { return; } - QMutexLocker locker(&_hueMutex); + if (!_streamReady || _streamPaused) + { + return; + } + + _streamPaused = flush; int ret = 0; do { ret = mbedtls_ssl_write(&ssl, data, size); - } - while (ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE); + } while (ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE); if (ret <= 0) { - handleReturn(ret); + Error(_log, "Error while writing UDP SSL stream updates. mbedtls_ssl_write returned: %s", QSTRING_CSTR(errorMsg(ret))); + + if (_streamReady) + { + stopConnection(); + disable(); + + startEnableAttemptsTimer(); + } } } -void ProviderUdpSSL::handleReturn(int ret) +QString ProviderUdpSSL::errorMsg(int ret) { - bool closeNotify = false; - bool gotoExit = false; + char error_buf[1024]; + mbedtls_strerror(ret, error_buf, 1024); - switch (ret) - { - case MBEDTLS_ERR_SSL_TIMEOUT: - sslLog( errorMsg( ret ), "warning" ); - if ( _retry_left-- > 0 ) return; - gotoExit = true; - break; - case MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY: - sslLog( "SSL Connection was closed gracefully", "warning" ); - ret = 0; - closeNotify = true; - break; - default: - sslLog( QString("mbedtls_ssl_read returned %1").arg( errorMsg( ret ) ), "warning" ); - gotoExit = true; - } - - if (closeNotify) - { - closeSSLNotify(); - gotoExit = true; - } - - if (gotoExit) - { - sslLog( "Exit SSL connection" ); - _stopConnection = true; - } -} - -QString ProviderUdpSSL::errorMsg(int ret) { - - QString msg; - -#ifdef MBEDTLS_ERROR_C - char error_buf[1024]; - mbedtls_strerror(ret, error_buf, 1024); - msg = QString("Last error was: %1 - %2").arg( ret ).arg( error_buf ); -#else - switch (ret) - { -#if defined(MBEDTLS_ERR_SSL_DECODE_ERROR) - case MBEDTLS_ERR_SSL_DECODE_ERROR: - msg = "The requested feature is not available. - MBEDTLS_ERR_SSL_DECODE_ERROR -0x7300"; - break; -#endif -#if defined(MBEDTLS_ERR_SSL_ILLEGAL_PARAMETER) - case MBEDTLS_ERR_SSL_ILLEGAL_PARAMETER: - msg = "The requested feature is not available. - MBEDTLS_ERR_SSL_ILLEGAL_PARAMETER -0x6600"; - break; -#endif -#if defined(MBEDTLS_ERR_SSL_HANDSHAKE_FAILURE) - case MBEDTLS_ERR_SSL_HANDSHAKE_FAILURE: - msg = "The requested feature is not available. - MBEDTLS_ERR_SSL_HANDSHAKE_FAILURE -0x6E00"; - break; -#endif -#if defined(MBEDTLS_ERR_SSL_BAD_PROTOCOL_VERSION) - case MBEDTLS_ERR_SSL_BAD_PROTOCOL_VERSION: - msg = "The requested feature is not available. - MBEDTLS_ERR_SSL_BAD_PROTOCOL_VERSION -0x6E80"; - break; -#endif -#if defined(MBEDTLS_ERR_SSL_BAD_CERTIFICATE) - case MBEDTLS_ERR_SSL_BAD_CERTIFICATE: - msg = "The requested feature is not available. - MBEDTLS_ERR_SSL_BAD_CERTIFICATE -0x7A00"; - break; -#endif -#if defined(MBEDTLS_ERR_SSL_UNRECOGNIZED_NAME) - case MBEDTLS_ERR_SSL_UNRECOGNIZED_NAME: - msg = "The requested feature is not available. - MBEDTLS_ERR_SSL_UNRECOGNIZED_NAME -0x7800"; - break; -#endif -#if defined(MBEDTLS_ERR_SSL_UNSUPPORTED_EXTENSION) - case MBEDTLS_ERR_SSL_UNSUPPORTED_EXTENSION: - msg = "The requested feature is not available. - MBEDTLS_ERR_SSL_UNSUPPORTED_EXTENSION -0x7500"; - break; -#endif -#if defined(MBEDTLS_ERR_SSL_NO_APPLICATION_PROTOCOL) - case MBEDTLS_ERR_SSL_NO_APPLICATION_PROTOCOL: - msg = "The requested feature is not available. - MBEDTLS_ERR_SSL_NO_APPLICATION_PROTOCOL -0x7580"; - break; -#endif -#if defined(MBEDTLS_ERR_SSL_FEATURE_UNAVAILABLE) - case MBEDTLS_ERR_SSL_FEATURE_UNAVAILABLE: - msg = "The requested feature is not available. - MBEDTLS_ERR_SSL_FEATURE_UNAVAILABLE -0x7080"; - break; -#endif -#if defined(MBEDTLS_ERR_SSL_BAD_INPUT_DATA) - case MBEDTLS_ERR_SSL_BAD_INPUT_DATA: - msg = "Bad input parameters to function. - MBEDTLS_ERR_SSL_BAD_INPUT_DATA -0x7100"; - break; -#endif -#if defined(MBEDTLS_ERR_SSL_INVALID_MAC) - case MBEDTLS_ERR_SSL_INVALID_MAC: - msg = "Verification of the message MAC failed. - MBEDTLS_ERR_SSL_INVALID_MAC -0x7180"; - break; -#endif -#if defined(MBEDTLS_ERR_SSL_INVALID_RECORD) - case MBEDTLS_ERR_SSL_INVALID_RECORD: - msg = "An invalid SSL record was received. - MBEDTLS_ERR_SSL_INVALID_RECORD -0x7200"; - break; -#endif -#if defined(MBEDTLS_ERR_SSL_CONN_EOF) - case MBEDTLS_ERR_SSL_CONN_EOF: - msg = "The connection indicated an EOF. - MBEDTLS_ERR_SSL_CONN_EOF -0x7280"; - break; -#endif -#if defined(MBEDTLS_ERR_SSL_UNKNOWN_CIPHER) - case MBEDTLS_ERR_SSL_UNKNOWN_CIPHER: - msg = "An unknown cipher was received. - MBEDTLS_ERR_SSL_UNKNOWN_CIPHER -0x7300"; - break; -#endif -#if defined(MBEDTLS_ERR_SSL_NO_CIPHER_CHOSEN) - case MBEDTLS_ERR_SSL_NO_CIPHER_CHOSEN: - msg = "The server has no ciphersuites in common with the client. - MBEDTLS_ERR_SSL_NO_CIPHER_CHOSEN -0x7380"; - break; -#endif -#if defined(MBEDTLS_ERR_SSL_NO_RNG) - case MBEDTLS_ERR_SSL_NO_RNG: - msg = "No RNG was provided to the SSL module. - MBEDTLS_ERR_SSL_NO_RNG -0x7400"; - break; -#endif -#if defined(MBEDTLS_ERR_SSL_NO_CLIENT_CERTIFICATE) - case MBEDTLS_ERR_SSL_NO_CLIENT_CERTIFICATE: - msg = "No client certification received from the client, but required by the authentication mode. - MBEDTLS_ERR_SSL_NO_CLIENT_CERTIFICATE -0x7480"; - break; -#endif -#if defined(MBEDTLS_ERR_SSL_CERTIFICATE_TOO_LARGE) - case MBEDTLS_ERR_SSL_CERTIFICATE_TOO_LARGE: - msg = "Our own certificate(s) is/are too large to send in an SSL message. - MBEDTLS_ERR_SSL_CERTIFICATE_TOO_LARGE -0x7500"; - break; -#endif -#if defined(MBEDTLS_ERR_SSL_CERTIFICATE_REQUIRED) - case MBEDTLS_ERR_SSL_CERTIFICATE_REQUIRED: - msg = "The own certificate is not set, but needed by the server. - MBEDTLS_ERR_SSL_CERTIFICATE_REQUIRED -0x7580"; - break; -#endif -#if defined(MBEDTLS_ERR_SSL_PRIVATE_KEY_REQUIRED) - case MBEDTLS_ERR_SSL_PRIVATE_KEY_REQUIRED: - msg = "The own private key or pre-shared key is not set, but needed. - MBEDTLS_ERR_SSL_PRIVATE_KEY_REQUIRED -0x7600"; - break; -#endif -#if defined(MBEDTLS_ERR_SSL_CA_CHAIN_REQUIRED) - case MBEDTLS_ERR_SSL_CA_CHAIN_REQUIRED: - msg = "No CA Chain is set, but required to operate. - MBEDTLS_ERR_SSL_CA_CHAIN_REQUIRED -0x7680"; - break; -#endif -#if defined(MBEDTLS_ERR_SSL_UNEXPECTED_MESSAGE) - case MBEDTLS_ERR_SSL_UNEXPECTED_MESSAGE: - msg = "An unexpected message was received from our peer. - MBEDTLS_ERR_SSL_UNEXPECTED_MESSAGE -0x7700"; - break; -#endif -#if defined(MBEDTLS_ERR_SSL_FATAL_ALERT_MESSAGE) - case MBEDTLS_ERR_SSL_FATAL_ALERT_MESSAGE: - msg = "A fatal alert message was received from our peer. - MBEDTLS_ERR_SSL_FATAL_ALERT_MESSAGE -0x7780"; - break; -#endif -#if defined(MBEDTLS_ERR_SSL_PEER_VERIFY_FAILED) - case MBEDTLS_ERR_SSL_PEER_VERIFY_FAILED: - msg = "Verification of our peer failed. - MBEDTLS_ERR_SSL_PEER_VERIFY_FAILED -0x7800"; - break; -#endif -#if defined(MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY) - case MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY: - msg = "The peer notified us that the connection is going to be closed. - MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY -0x7880"; - break; -#endif -#if defined(MBEDTLS_ERR_SSL_BAD_HS_CLIENT_HELLO) - case MBEDTLS_ERR_SSL_BAD_HS_CLIENT_HELLO: - msg = "Processing of the ClientHello handshake message failed. - MBEDTLS_ERR_SSL_BAD_HS_CLIENT_HELLO -0x7900"; - break; -#endif -#if defined(MBEDTLS_ERR_SSL_BAD_HS_SERVER_HELLO) - case MBEDTLS_ERR_SSL_BAD_HS_SERVER_HELLO: - msg = "Processing of the ServerHello handshake message failed. - MBEDTLS_ERR_SSL_BAD_HS_SERVER_HELLO -0x7980"; - break; -#endif -#if defined(MBEDTLS_ERR_SSL_BAD_HS_CERTIFICATE) - case MBEDTLS_ERR_SSL_BAD_HS_CERTIFICATE: - msg = "Processing of the Certificate handshake message failed. - MBEDTLS_ERR_SSL_BAD_HS_CERTIFICATE -0x7A00"; - break; -#endif -#if defined(MBEDTLS_ERR_SSL_BAD_HS_CERTIFICATE_REQUEST) - case MBEDTLS_ERR_SSL_BAD_HS_CERTIFICATE_REQUEST: - msg = "Processing of the CertificateRequest handshake message failed. - MBEDTLS_ERR_SSL_BAD_HS_CERTIFICATE_REQUEST -0x7A80"; - break; -#endif -#if defined(MBEDTLS_ERR_SSL_BAD_HS_SERVER_KEY_EXCHANGE) - case MBEDTLS_ERR_SSL_BAD_HS_SERVER_KEY_EXCHANGE: - msg = "Processing of the ServerKeyExchange handshake message failed. - MBEDTLS_ERR_SSL_BAD_HS_SERVER_KEY_EXCHANGE -0x7B00"; - break; -#endif -#if defined(MBEDTLS_ERR_SSL_BAD_HS_SERVER_HELLO_DONE) - case MBEDTLS_ERR_SSL_BAD_HS_SERVER_HELLO_DONE: - msg = "Processing of the ServerHelloDone handshake message failed. - MBEDTLS_ERR_SSL_BAD_HS_SERVER_HELLO_DONE -0x7B80"; - break; -#endif -#if defined(MBEDTLS_ERR_SSL_BAD_HS_CLIENT_KEY_EXCHANGE) - case MBEDTLS_ERR_SSL_BAD_HS_CLIENT_KEY_EXCHANGE: - msg = "Processing of the ClientKeyExchange handshake message failed. - MBEDTLS_ERR_SSL_BAD_HS_CLIENT_KEY_EXCHANGE -0x7C00"; - break; -#endif -#if defined(MBEDTLS_ERR_SSL_BAD_HS_CLIENT_KEY_EXCHANGE_RP) - case MBEDTLS_ERR_SSL_BAD_HS_CLIENT_KEY_EXCHANGE_RP: - msg = "Processing of the ClientKeyExchange handshake message failed in DHM / ECDH Read Public. - MBEDTLS_ERR_SSL_BAD_HS_CLIENT_KEY_EXCHANGE_RP -0x7C80"; - break; -#endif -#if defined(MBEDTLS_ERR_SSL_BAD_HS_CLIENT_KEY_EXCHANGE_CS) - case MBEDTLS_ERR_SSL_BAD_HS_CLIENT_KEY_EXCHANGE_CS: - msg = "Processing of the ClientKeyExchange handshake message failed in DHM / ECDH Calculate Secret. - MBEDTLS_ERR_SSL_BAD_HS_CLIENT_KEY_EXCHANGE_CS -0x7D00"; - break; -#endif -#if defined(MBEDTLS_ERR_SSL_BAD_HS_CERTIFICATE_VERIFY) - case MBEDTLS_ERR_SSL_BAD_HS_CERTIFICATE_VERIFY: - msg = "Processing of the CertificateVerify handshake message failed. - MBEDTLS_ERR_SSL_BAD_HS_CERTIFICATE_VERIFY -0x7D80"; - break; -#endif -#if defined(MBEDTLS_ERR_SSL_BAD_HS_CHANGE_CIPHER_SPEC) - case MBEDTLS_ERR_SSL_BAD_HS_CHANGE_CIPHER_SPEC: - msg = "Processing of the ChangeCipherSpec handshake message failed. - MBEDTLS_ERR_SSL_BAD_HS_CHANGE_CIPHER_SPEC -0x7E00"; - break; -#endif -#if defined(MBEDTLS_ERR_SSL_BAD_HS_FINISHED) - case MBEDTLS_ERR_SSL_BAD_HS_FINISHED: - msg = "Processing of the Finished handshake message failed. - MBEDTLS_ERR_SSL_BAD_HS_FINISHED -0x7E80"; - break; -#endif -#if defined(MBEDTLS_ERR_SSL_ALLOC_FAILED) - case MBEDTLS_ERR_SSL_ALLOC_FAILED: - msg = "Memory allocation failed. - MBEDTLS_ERR_SSL_ALLOC_FAILED -0x7F00"; - break; -#endif -#if defined(MBEDTLS_ERR_SSL_HW_ACCEL_FAILED) - case MBEDTLS_ERR_SSL_HW_ACCEL_FAILED: - msg = "Hardware acceleration function returned with error. - MBEDTLS_ERR_SSL_HW_ACCEL_FAILED -0x7F80"; - break; -#endif -#if defined(MBEDTLS_ERR_SSL_HW_ACCEL_FALLTHROUGH) - case MBEDTLS_ERR_SSL_HW_ACCEL_FALLTHROUGH: - msg = "Hardware acceleration function skipped / left alone data. - MBEDTLS_ERR_SSL_HW_ACCEL_FALLTHROUGH -0x6F80"; - break; -#endif -#if defined(MBEDTLS_ERR_SSL_COMPRESSION_FAILED) - case MBEDTLS_ERR_SSL_COMPRESSION_FAILED: - msg = "Processing of the compression / decompression failed. - MBEDTLS_ERR_SSL_COMPRESSION_FAILED -0x6F00"; - break; -#endif -#if defined(MBEDTLS_ERR_SSL_BAD_HS_PROTOCOL_VERSION) - case MBEDTLS_ERR_SSL_BAD_HS_PROTOCOL_VERSION: - msg = "Handshake protocol not within min/max boundaries. - MBEDTLS_ERR_SSL_BAD_HS_PROTOCOL_VERSION -0x6E80"; - break; -#endif -#if defined(MBEDTLS_ERR_SSL_BAD_HS_NEW_SESSION_TICKET) - case MBEDTLS_ERR_SSL_BAD_HS_NEW_SESSION_TICKET: - msg = "Processing of the NewSessionTicket handshake message failed. - MBEDTLS_ERR_SSL_BAD_HS_NEW_SESSION_TICKET -0x6E00"; - break; -#endif -#if defined(MBEDTLS_ERR_SSL_SESSION_TICKET_EXPIRED) - case MBEDTLS_ERR_SSL_SESSION_TICKET_EXPIRED: - msg = "Session ticket has expired. - MBEDTLS_ERR_SSL_SESSION_TICKET_EXPIRED -0x6D80"; - break; -#endif -#if defined(MBEDTLS_ERR_SSL_PK_TYPE_MISMATCH) - case MBEDTLS_ERR_SSL_PK_TYPE_MISMATCH: - msg = "Public key type mismatch (eg, asked for RSA key exchange and presented EC key) - MBEDTLS_ERR_SSL_PK_TYPE_MISMATCH -0x6D00"; - break; -#endif -#if defined(MBEDTLS_ERR_SSL_UNKNOWN_IDENTITY) - case MBEDTLS_ERR_SSL_UNKNOWN_IDENTITY: - msg = "Unknown identity received (eg, PSK identity) - MBEDTLS_ERR_SSL_UNKNOWN_IDENTITY -0x6C80"; - break; -#endif -#if defined(MBEDTLS_ERR_SSL_INTERNAL_ERROR) - case MBEDTLS_ERR_SSL_INTERNAL_ERROR: - msg = "Internal error (eg, unexpected failure in lower-level module) - MBEDTLS_ERR_SSL_INTERNAL_ERROR -0x6C00"; - break; -#endif -#if defined(MBEDTLS_ERR_SSL_COUNTER_WRAPPING) - case MBEDTLS_ERR_SSL_COUNTER_WRAPPING: - msg = "A counter would wrap (eg, too many messages exchanged). - MBEDTLS_ERR_SSL_COUNTER_WRAPPING -0x6B80"; - break; -#endif -#if defined(MBEDTLS_ERR_SSL_WAITING_SERVER_HELLO_RENEGO) - case MBEDTLS_ERR_SSL_WAITING_SERVER_HELLO_RENEGO: - msg = "Unexpected message at ServerHello in renegotiation. - MBEDTLS_ERR_SSL_WAITING_SERVER_HELLO_RENEGO -0x6B00"; - break; -#endif -#if defined(MBEDTLS_ERR_SSL_HELLO_VERIFY_REQUIRED) - case MBEDTLS_ERR_SSL_HELLO_VERIFY_REQUIRED: - msg = "DTLS client must retry for hello verification. - MBEDTLS_ERR_SSL_HELLO_VERIFY_REQUIRED -0x6A80"; - break; -#endif -#if defined(MBEDTLS_ERR_SSL_BUFFER_TOO_SMALL) - case MBEDTLS_ERR_SSL_BUFFER_TOO_SMALL: - msg = "A buffer is too small to receive or write a message. - MBEDTLS_ERR_SSL_BUFFER_TOO_SMALL -0x6A00"; - break; -#endif -#if defined(MBEDTLS_ERR_SSL_NO_USABLE_CIPHERSUITE) - case MBEDTLS_ERR_SSL_NO_USABLE_CIPHERSUITE: - msg = "None of the common ciphersuites is usable (eg, no suitable certificate, see debug messages). - MBEDTLS_ERR_SSL_NO_USABLE_CIPHERSUITE -0x6980"; - break; -#endif -#if defined(MBEDTLS_ERR_SSL_WANT_READ) - case MBEDTLS_ERR_SSL_WANT_READ: - msg = "No data of requested type currently available on underlying transport. - MBEDTLS_ERR_SSL_WANT_READ -0x6900"; - break; -#endif -#if defined(MBEDTLS_ERR_SSL_WANT_WRITE) - case MBEDTLS_ERR_SSL_WANT_WRITE: - msg = "Connection requires a write call. - MBEDTLS_ERR_SSL_WANT_WRITE -0x6880"; - break; -#endif -#if defined(MBEDTLS_ERR_SSL_TIMEOUT) - case MBEDTLS_ERR_SSL_TIMEOUT: - msg = "The operation timed out. - MBEDTLS_ERR_SSL_TIMEOUT -0x6800"; - break; -#endif -#if defined(MBEDTLS_ERR_SSL_CLIENT_RECONNECT) - case MBEDTLS_ERR_SSL_CLIENT_RECONNECT: - msg = "The client initiated a reconnect from the same port. - MBEDTLS_ERR_SSL_CLIENT_RECONNECT -0x6780"; - break; -#endif -#if defined(MBEDTLS_ERR_SSL_UNEXPECTED_RECORD) - case MBEDTLS_ERR_SSL_UNEXPECTED_RECORD: - msg = "Record header looks valid but is not expected. - MBEDTLS_ERR_SSL_UNEXPECTED_RECORD -0x6700"; - break; -#endif -#if defined(MBEDTLS_ERR_SSL_NON_FATAL) - case MBEDTLS_ERR_SSL_NON_FATAL: - msg = "The alert message received indicates a non-fatal error. - MBEDTLS_ERR_SSL_NON_FATAL -0x6680"; - break; -#endif -#if defined(MBEDTLS_ERR_SSL_INVALID_VERIFY_HASH) - case MBEDTLS_ERR_SSL_INVALID_VERIFY_HASH: - msg = "Couldn't set the hash for verifying CertificateVerify. - MBEDTLS_ERR_SSL_INVALID_VERIFY_HASH -0x6600"; - break; -#endif -#if defined(MBEDTLS_ERR_SSL_CONTINUE_PROCESSING) - case MBEDTLS_ERR_SSL_CONTINUE_PROCESSING: - msg = "Internal-only message signaling that further message-processing should be done. - MBEDTLS_ERR_SSL_CONTINUE_PROCESSING -0x6580"; - break; -#endif -#if defined(MBEDTLS_ERR_SSL_ASYNC_IN_PROGRESS) - case MBEDTLS_ERR_SSL_ASYNC_IN_PROGRESS: - msg = "The asynchronous operation is not completed yet. - MBEDTLS_ERR_SSL_ASYNC_IN_PROGRESS -0x6500"; - break; -#endif -#if defined(MBEDTLS_ERR_SSL_EARLY_MESSAGE) - case MBEDTLS_ERR_SSL_EARLY_MESSAGE: - msg = "Internal-only message signaling that a message arrived early. - MBEDTLS_ERR_SSL_EARLY_MESSAGE -0x6480"; - break; -#endif -#if defined(MBEDTLS_ERR_SSL_CRYPTO_IN_PROGRESS) - case MBEDTLS_ERR_SSL_CRYPTO_IN_PROGRESS: - msg = "A cryptographic operation is in progress. - MBEDTLS_ERR_SSL_CRYPTO_IN_PROGRESS -0x7000"; - break; -#endif - default: - msg.append("Last error was: ").append( QString::number(ret) ); - } -#endif - return msg; + return QString("Last error was: code = %1, description = %2").arg(ret).arg(error_buf); } void ProviderUdpSSL::closeSSLNotify() { - int ret = 0; - - sslLog( "Closing SSL connection..." ); /* No error checking, the connection might be closed already */ - do + while (mbedtls_ssl_close_notify(&ssl) == MBEDTLS_ERR_SSL_WANT_WRITE) { - ret = mbedtls_ssl_close_notify(&ssl); } - while (ret == MBEDTLS_ERR_SSL_WANT_WRITE); - - sslLog( "SSL Connection successful closed" ); -} - -void ProviderUdpSSL::ProviderUdpSSLDebug(void* ctx, int level, const char* file, int line, const char* str) -{ - const char* p, * basename; - (void)ctx; - /* Extract basename from file */ - for (p = basename = file; *p != '\0'; p++) - { - if (*p == '/' || *p == '\\') - { - basename = p + 1; - } - } - mbedtls_printf("%s:%04d: |%d| %s", basename, line, level, str); -} - -int ProviderUdpSSL::ProviderUdpSSLVerify(void* data, mbedtls_x509_crt* crt, int depth, uint32_t* flags) -{ - const uint32_t buf_size = 1024; - char* buf = new char[buf_size]; - (void)data; - - mbedtls_printf("\nVerifying certificate at depth %d:\n", depth); - mbedtls_x509_crt_info(buf, buf_size - 1, " ", crt); - mbedtls_printf("%s", buf); - - if (*flags == 0) - mbedtls_printf("No verification issue for this certificate\n"); - else - { - mbedtls_x509_crt_verify_info(buf, buf_size, " ! ", *flags); - mbedtls_printf("%s\n", buf); - } - - delete[] buf; - return 0; } diff --git a/libsrc/leddevice/dev_net/ProviderUdpSSL.h b/libsrc/leddevice/dev_net/ProviderUdpSSL.h index 704f115a..d9558514 100644 --- a/libsrc/leddevice/dev_net/ProviderUdpSSL.h +++ b/libsrc/leddevice/dev_net/ProviderUdpSSL.h @@ -23,19 +23,6 @@ #if defined(MBEDTLS_PLATFORM_C) #include -#else -#include -#include -#define mbedtls_time time -#define mbedtls_time_t time_t -#define mbedtls_printf printf -#define mbedtls_fprintf fprintf -#define mbedtls_snprintf snprintf -#define mbedtls_calloc calloc -#define mbedtls_free free -#define mbedtls_exit exit -#define MBEDTLS_EXIT_SUCCESS EXIT_SUCCESS -#define MBEDTLS_EXIT_FAILURE EXIT_FAILURE #endif #include @@ -50,12 +37,6 @@ #include #include -//----------- END mbedtls - -constexpr std::chrono::milliseconds STREAM_SSL_HANDSHAKE_TIMEOUT_MIN{400}; -constexpr std::chrono::milliseconds STREAM_SSL_HANDSHAKE_TIMEOUT_MAX{1000}; -constexpr std::chrono::milliseconds STREAM_SSL_READ_TIMEOUT{0}; - class ProviderUdpSSL : public LedDevice { Q_OBJECT @@ -71,6 +52,11 @@ public: /// ~ProviderUdpSSL() override; + /// + QString _hostName; + QHostAddress _address; + int _port; + protected: /// @@ -102,6 +88,18 @@ protected: /// bool initNetwork(); + /// + /// @brief Start astreaming connection + /// + /// @return True, if success + /// + bool startConnection(); + + /// + /// @brief Stop the streaming connection + /// + void stopConnection(); + /// /// Writes the given bytes/bits to the UDP-device and sleeps the latch time to ensure that the /// values are latched. @@ -109,7 +107,7 @@ protected: /// @param[in] size The length of the data /// @param[in] data The data /// - void writeBytes(unsigned int size, const uint8_t *data); + void writeBytes(unsigned int size, const uint8_t* data, bool flush = false); /// /// get ciphersuites list from mbedtls_ssl_list_ciphersuites @@ -118,36 +116,16 @@ protected: /// virtual const int * getCiphersuites() const; - void sslLog(const QString &msg, const char* errorType = "debug"); - void sslLog(const char* msg, const char* errorType = "debug"); - void configLog(const char* msg, const char* type, ...); - - /** - * Debug callback for mbed TLS - * Just prints on the USB serial port - */ - static void ProviderUdpSSLDebug(void* ctx, int level, const char* file, int line, const char* str); - - /** - * Certificate verification callback for mbed TLS - * Here we only use it to display information on each cert in the chain - */ - static int ProviderUdpSSLVerify(void* data, mbedtls_x509_crt* crt, int depth, uint32_t* flags); - - /// - /// closeSSLNotify and freeSSLConnection - /// - void closeSSLConnection(); - private: bool initConnection(); + bool seedingRNG(); bool setupStructure(); - bool startUPDConnection(); + bool setupPSK(); bool startSSLHandshake(); - void handleReturn(int ret); + QString errorMsg(int ret); void closeSSLNotify(); void freeSSLConnection(); @@ -160,24 +138,19 @@ private: mbedtls_ctr_drbg_context ctr_drbg; mbedtls_timing_delay_context timer; - QMutex _hueMutex; QString _transport_type; QString _custom; - QHostAddress _address; - QString _defaultHost; - int _port; int _ssl_port; QString _server_name; QString _psk; QString _psk_identity; - uint32_t _read_timeout; + + int _handshake_attempts; uint32_t _handshake_timeout_min; uint32_t _handshake_timeout_max; - unsigned int _handshake_attempts; - int _retry_left; - bool _stopConnection; - bool _debugStreamer; - int _debugLevel; + + bool _streamReady; + bool _streamPaused; }; #endif // PROVIDERUDPSSL_H diff --git a/libsrc/leddevice/dev_other/LedDevicePiBlaster.h b/libsrc/leddevice/dev_other/LedDevicePiBlaster.h index 71759571..31efa9cd 100644 --- a/libsrc/leddevice/dev_other/LedDevicePiBlaster.h +++ b/libsrc/leddevice/dev_other/LedDevicePiBlaster.h @@ -79,4 +79,4 @@ private: }; -#endif // LEDEVICETEMPLATE_H +#endif // LEDEVICEPIBLASTER_H diff --git a/libsrc/leddevice/dev_serial/ProviderRs232.cpp b/libsrc/leddevice/dev_serial/ProviderRs232.cpp index 2d03b3b0..8a995996 100644 --- a/libsrc/leddevice/dev_serial/ProviderRs232.cpp +++ b/libsrc/leddevice/dev_serial/ProviderRs232.cpp @@ -44,13 +44,6 @@ bool ProviderRs232::init(const QJsonObject &deviceConfig) // Initialise sub-class if ( LedDevice::init(deviceConfig) ) { - - Debug(_log, "DeviceType : %s", QSTRING_CSTR( this->getActiveDeviceType() )); - Debug(_log, "LedCount : %d", this->getLedCount()); - Debug(_log, "ColorOrder : %s", QSTRING_CSTR( this->getColorOrder() )); - Debug(_log, "RefreshTime : %d", _refreshTimerInterval_ms); - Debug(_log, "LatchTime : %d", this->getLatchTime()); - _deviceName = deviceConfig["output"].toString("auto"); _isAutoDeviceName = _deviceName.toLower() == "auto"; @@ -89,7 +82,6 @@ int ProviderRs232::open() { int retval = -1; _isDeviceReady = false; - _isInSwitchOff = false; // open device physically if ( tryOpen(_delayAfterConnect_ms) ) @@ -190,18 +182,6 @@ bool ProviderRs232::tryOpen(int delayAfterConnect_ms) { QString errortext = QString("Invalid serial device name: %1 %2!").arg(_deviceName, _location); this->setInError( errortext ); - - // List available device - for (auto &port : QSerialPortInfo::availablePorts() ) { - Debug(_log, "Avail. serial device: [%s]-(%s|%s), Manufacturer: %s, Description: %s", - QSTRING_CSTR(port.portName()), - QSTRING_CSTR(QString("0x%1").arg(port.vendorIdentifier(), 0, 16)), - QSTRING_CSTR(QString("0x%1").arg(port.productIdentifier(), 0, 16)), - QSTRING_CSTR(port.manufacturer()), - QSTRING_CSTR(port.description()) - ); - } - return false; } } @@ -295,17 +275,21 @@ QString ProviderRs232::discoverFirst() return ""; } -QJsonObject ProviderRs232::discover(const QJsonObject& /*params*/) +QJsonObject ProviderRs232::discover(const QJsonObject& params) { + DebugIf(verbose,_log, "params: [%s]", QString(QJsonDocument(params).toJson(QJsonDocument::Compact)).toUtf8().constData()); + QJsonObject devicesDiscovered; devicesDiscovered.insert("ledDeviceType", _activeDeviceType ); QJsonArray deviceList; + bool showAll = params["discoverAll"].toBool(false); + // Discover serial Devices for (auto &port : QSerialPortInfo::availablePorts() ) { - if ( !port.isNull() && port.vendorIdentifier() != 0) + if ( !port.isNull() && (showAll || port.vendorIdentifier() != 0) ) { QJsonObject portInfo; portInfo.insert("description", port.description()); @@ -364,6 +348,8 @@ void ProviderRs232::identify(const QJsonObject& params) QString deviceName = params["output"].toString(""); if (!deviceName.isEmpty()) { + Info(_log, "Identify %s, device: %s", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(deviceName) ); + _devConfig = params; init(_devConfig); { diff --git a/libsrc/leddevice/dev_serial/ProviderRs232.h b/libsrc/leddevice/dev_serial/ProviderRs232.h index 108ea1b3..2cb94398 100644 --- a/libsrc/leddevice/dev_serial/ProviderRs232.h +++ b/libsrc/leddevice/dev_serial/ProviderRs232.h @@ -74,12 +74,22 @@ protected: bool powerOff() override; /// - /// @brief Discover first devices of a serial device available (for configuration) + /// @brief Discover first device of serial devices available (for configuration) /// /// @return A string of the device found /// QString discoverFirst() override; + /// + /// @brief Discover serial devices available (for configuration). + /// + /// Following parameters can be provided optional + /// @code + /// { + /// "discoverAll" : true/false , "true", in case devices without vendor-id are to be included in the discovery result + /// } + ///@endcode + /// /// @param[in] params Parameters used to overwrite discovery default behaviour /// /// @return A JSON structure holding a list of devices found diff --git a/libsrc/leddevice/dev_spi/ProviderSpi.cpp b/libsrc/leddevice/dev_spi/ProviderSpi.cpp index f0ce55c7..f57c54f9 100644 --- a/libsrc/leddevice/dev_spi/ProviderSpi.cpp +++ b/libsrc/leddevice/dev_spi/ProviderSpi.cpp @@ -37,7 +37,6 @@ ProviderSpi::ProviderSpi(const QJsonObject &deviceConfig) { memset(&_spi, 0, sizeof(_spi)); _latchTime_ms = 1; - _isInSwitchOff = false; } ProviderSpi::~ProviderSpi() @@ -69,7 +68,6 @@ int ProviderSpi::open() int retval = -1; QString errortext; _isDeviceReady = false; - _isInSwitchOff = false; const int bitsPerWord = 8; diff --git a/libsrc/leddevice/schemas/schema-philipshue.json b/libsrc/leddevice/schemas/schema-philipshue.json index 7a4d46da..209c9b9f 100644 --- a/libsrc/leddevice/schemas/schema-philipshue.json +++ b/libsrc/leddevice/schemas/schema-philipshue.json @@ -37,43 +37,74 @@ }, "useEntertainmentAPI": { "type": "boolean", + "format": "checkbox", "title": "edt_dev_spec_useEntertainmentAPI_title", "default": true, "propertyOrder": 5 }, - "transitiontime": { - "type": "number", - "title": "edt_dev_spec_transistionTime_title", - "default": 1, - "append": "x100ms", - "options": { - "dependencies": { - "useEntertainmentAPI": false - } - }, - "propertyOrder": 6 - }, "switchOffOnBlack": { "type": "boolean", + "format": "checkbox", "title": "edt_dev_spec_switchOffOnBlack_title", "default": false, - "propertyOrder": 7 + "propertyOrder": 6 }, "restoreOriginalState": { "type": "boolean", + "format": "checkbox", "title": "edt_dev_spec_restoreOriginalState_title", - "default": true, + "default": false, + "propertyOrder": 7 + }, + "blackLevel": { + "type": "number", + "format": "stepper", + "title": "edt_dev_spec_brightnessThreshold_title", + "default": 0.009, + "step": 0.01, + "minimum": 0.001, + "maximum": 1.0, "propertyOrder": 8 }, + "onBlackTimeToPowerOff": { + "type": "integer", + "format": "stepper", + "step": 50, + "title": "edt_dev_spec_onBlackTimeToPowerOff", + "append": "edt_append_ms", + "minimum": 100, + "maximum": 100000, + "default": 600, + "required": true, + "propertyOrder": 9 + }, + "onBlackTimeToPowerOn": { + "type": "integer", + "format": "stepper", + "step": 50, + "title": "edt_dev_spec_onBlackTimeToPowerOn", + "append": "edt_append_ms", + "minimum": 100, + "maximum": 100000, + "default": 300, + "required": true, + "propertyOrder": 9 + }, + "candyGamma": { + "type": "boolean", + "format": "checkbox", + "title": "edt_dev_spec_candyGamma_title", + "default": true, + "propertyOrder": 10 + }, "lightIds": { "type": "array", "title": "edt_dev_spec_lightid_title", - "minimum": 1, + "minItems": 1, "uniqueItems": true, "items": { "type": "string", - "minLength": 1, - "required": true, + "minimum": 0, "title": "edt_dev_spec_lightid_itemtitle" }, "options": { @@ -81,10 +112,12 @@ "useEntertainmentAPI": false } }, - "propertyOrder": 9 + "propertyOrder": 11 }, "groupId": { "type": "number", + "format": "stepper", + "step": 1, "title": "edt_dev_spec_groupId_title", "default": 0, "options": { @@ -92,41 +125,11 @@ "useEntertainmentAPI": true } }, - "propertyOrder": 10 - }, - "blackLightsTimeout": { - "type": "number", - "title": "edt_dev_spec_blackLightsTimeout_title", - "default": 15000, - "step": 500, - "minimum": 10000, - "maximum": 60000, - "access": "advanced", - "append": "edt_append_ms", - "options": { - "dependencies": { - "useEntertainmentAPI": true - } - }, - "propertyOrder": 11 - }, - "brightnessThreshold": { - "type": "number", - "title": "edt_dev_spec_brightnessThreshold_title", - "default": 0, - "step": 0.005, - "minimum": 0, - "maximum": 1.0, - "access": "advanced", - "options": { - "dependencies": { - "useEntertainmentAPI": true - } - }, "propertyOrder": 12 }, "brightnessFactor": { "type": "number", + "format": "stepper", "title": "edt_dev_spec_brightnessFactor_title", "default": 1.0, "step": 0.25, @@ -135,6 +138,82 @@ "access": "advanced", "propertyOrder": 13 }, + "handshakeTimeoutMin": { + "type": "number", + "format": "stepper", + "title": "edt_dev_spec_sslHSTimeoutMin_title", + "default": 600, + "step": 100, + "minimum": 100, + "maximum": 30000, + "access": "expert", + "append": "edt_append_ms", + "required": true, + "options": { + "dependencies": { + "useEntertainmentAPI": true + } + }, + "propertyOrder": 14 + }, + "handshakeTimeoutMax": { + "type": "number", + "format": "stepper", + "title": "edt_dev_spec_sslHSTimeoutMax_title", + "default": 1000, + "step": 100, + "minimum": 100, + "maximum": 30000, + "access": "expert", + "append": "edt_append_ms", + "required": true, + "options": { + "dependencies": { + "useEntertainmentAPI": true + } + }, + "propertyOrder": 15 + }, + "verbose": { + "type": "boolean", + "format": "checkbox", + "title": "edt_dev_spec_verbose_title", + "default": false, + "access": "expert", + "propertyOrder": 16 + }, + "transitiontime": { + "type": "number", + "title": "edt_dev_spec_transistionTime_title", + "default": 1, + "minimum": 0, + "maximum": 100000, + "required": true, + "append": "x100ms", + "options": { + "dependencies": { + "useEntertainmentAPI": false + } + }, + "propertyOrder": 17 + }, + "blackLightsTimeout": { + "type": "number", + "default": 5000, + "options": { + "hidden": true + }, + "propertyOrder": 18 + }, + "brightnessThreshold": { + "type": "number", + "title": "edt_dev_spec_brightnessThreshold_title", + "default": 0.0001, + "options": { + "hidden": true + }, + "propertyOrder": 19 + }, "brightnessMin": { "type": "number", "title": "edt_dev_spec_brightnessMin_title", @@ -144,11 +223,9 @@ "maximum": 1.0, "access": "advanced", "options": { - "dependencies": { - "useEntertainmentAPI": true - } + "hidden": true }, - "propertyOrder": 14 + "propertyOrder": 20 }, "brightnessMax": { "type": "number", @@ -159,93 +236,8 @@ "maximum": 1.0, "access": "advanced", "options": { - "dependencies": { - "useEntertainmentAPI": true - } + "hidden": true }, - "propertyOrder": 15 - }, - "sslReadTimeout": { - "type": "number", - "title": "edt_dev_spec_sslReadTimeout_title", - "default": 0, - "step": 100, - "minimum": 0, - "maximum": 30000, - "access": "expert", - "append": "edt_append_ms", - "options": { - "dependencies": { - "useEntertainmentAPI": true - } - }, - "propertyOrder": 16 - }, - "sslHSTimeoutMin": { - "type": "number", - "title": "edt_dev_spec_sslHSTimeoutMin_title", - "default": 400, - "step": 100, - "minimum": 0, - "maximum": 30000, - "access": "expert", - "append": "edt_append_ms", - "options": { - "dependencies": { - "useEntertainmentAPI": true - } - }, - "propertyOrder": 17 - }, - "sslHSTimeoutMax": { - "type": "number", - "title": "edt_dev_spec_sslHSTimeoutMax_title", - "default": 1000, - "step": 100, - "minimum": 0, - "maximum": 30000, - "access": "expert", - "append": "edt_append_ms", - "options": { - "dependencies": { - "useEntertainmentAPI": true - } - }, - "propertyOrder": 18 - }, - "verbose": { - "type": "boolean", - "title": "edt_dev_spec_verbose_title", - "default": false, - "access": "expert", - "propertyOrder": 19 - }, - "debugStreamer": { - "type": "boolean", - "title": "edt_dev_spec_debugStreamer_title", - "default": false, - "access": "expert", - "options": { - "dependencies": { - "useEntertainmentAPI": true - } - }, - "propertyOrder": 20 - }, - "debugLevel": { - "type": "string", - "title": "edt_dev_spec_debugLevel_title", - "enum": [ "0", "1", "2", "3", "4" ], - "default": "0", - "options": { - "enum_titles": [ "edt_conf_enum_dl_nodebug", "edt_conf_enum_dl_error", "edt_conf_enum_dl_statechange", "edt_conf_enum_dl_informational", "edt_conf_enum_dl_verbose" ], - "dependencies": { - "useEntertainmentAPI": true - } - }, - "minimum": 0, - "maximum": 4, - "access": "expert", "propertyOrder": 21 } }, diff --git a/libsrc/leddevice/schemas/schema-udpddp.json b/libsrc/leddevice/schemas/schema-udpddp.json new file mode 100644 index 00000000..d9f0c62f --- /dev/null +++ b/libsrc/leddevice/schemas/schema-udpddp.json @@ -0,0 +1,32 @@ +{ + "type": "object", + "required": true, + "properties": { + "host": { + "type": "string", + "title": "edt_dev_spec_targetIpHost_title", + "format": "hostname_or_ip", + "propertyOrder": 1 + }, + "port": { + "type": "integer", + "title": "edt_dev_spec_port_title", + "default": 4048, + "minimum": 0, + "maximum": 65535, + "access": "expert", + "propertyOrder": 2 + }, + "latchTime": { + "type": "integer", + "title": "edt_dev_spec_latchtime_title", + "default": 0, + "append": "edt_append_ms", + "minimum": 0, + "maximum": 1000, + "access": "expert", + "propertyOrder": 3 + } + }, + "additionalProperties": true +} diff --git a/libsrc/leddevice/schemas/schema-wled.json b/libsrc/leddevice/schemas/schema-wled.json index 1a8a71ff..125b7d99 100644 --- a/libsrc/leddevice/schemas/schema-wled.json +++ b/libsrc/leddevice/schemas/schema-wled.json @@ -24,6 +24,17 @@ "required": true, "propertyOrder": 2 }, + "streamProtocol": { + "type": "string", + "title": "edt_dev_spec_stream_protocol_title", + "enum": [ "DDP", "RAW" ], + "default": "DDP", + "options": { + "enum_titles": [ "edt_conf_enum_udp_ddp", "edt_conf_enum_udp_raw" ] + }, + "access": "expert", + "propertyOrder": 3 + }, "restoreOriginalState": { "type": "boolean", "format": "checkbox", @@ -33,7 +44,7 @@ "options": { "infoText": "edt_dev_spec_restoreOriginalState_title_info" }, - "propertyOrder": 3 + "propertyOrder": 4 }, "overwriteSync": { "type": "boolean", @@ -42,7 +53,7 @@ "default": true, "required": true, "access": "advanced", - "propertyOrder": 4 + "propertyOrder": 5 }, "overwriteBrightness": { "type": "boolean", @@ -51,7 +62,7 @@ "default": true, "required": true, "access": "advanced", - "propertyOrder": 5 + "propertyOrder": 6 }, "brightness": { "type": "integer", @@ -65,7 +76,7 @@ } }, "access": "advanced", - "propertyOrder": 6 + "propertyOrder": 7 }, "latchTime": { "type": "integer", @@ -78,7 +89,7 @@ "options": { "infoText": "edt_dev_spec_latchtime_title_info" }, - "propertyOrder": 7 + "propertyOrder": 8 } }, "additionalProperties": true diff --git a/libsrc/mdns/CMakeLists.txt b/libsrc/mdns/CMakeLists.txt new file mode 100644 index 00000000..4fdd5ed5 --- /dev/null +++ b/libsrc/mdns/CMakeLists.txt @@ -0,0 +1,16 @@ + +# Define the current source locations +set(CURRENT_HEADER_DIR ${CMAKE_SOURCE_DIR}/include/mdns) +set(CURRENT_SOURCE_DIR ${CMAKE_SOURCE_DIR}/libsrc/mdns) + +FILE ( GLOB MDNS_SOURCES "${CURRENT_HEADER_DIR}/*.h" "${CURRENT_SOURCE_DIR}/*.h" "${CURRENT_SOURCE_DIR}/*.cpp" ) +add_library(mdns ${MDNS_SOURCES}) + +include_directories(${QMDNS_INCLUDE_DIR}) + +target_link_libraries(mdns +hyperion-utils +${QMDNS_LIBRARIES} +) + +target_include_directories(mdns PUBLIC ${QMDNS_INCLUDE_DIR}) diff --git a/libsrc/mdns/MdnsBrowser.cpp b/libsrc/mdns/MdnsBrowser.cpp new file mode 100644 index 00000000..60bd7ee4 --- /dev/null +++ b/libsrc/mdns/MdnsBrowser.cpp @@ -0,0 +1,471 @@ +#include +#include +#include + +//Qt includes +#include + +#include +#include +#include +#include +#include + +// Utility includes +#include +#include +#include +#include + +namespace { + const bool verboseBrowser = false; +} //End of constants + +MdnsBrowser::MdnsBrowser(QObject* parent) + : QObject(parent) + , _log(Logger::getInstance("MDNS")) +{ + qRegisterMetaType("QHostAddress"); +} + +MdnsBrowser::~MdnsBrowser() +{ + qDeleteAll(_browsedServiceTypes); +} + +void MdnsBrowser::browseForServiceType(const QByteArray& serviceType) +{ + if (!_browsedServiceTypes.contains(serviceType)) + { + DebugIf(verboseBrowser, _log, "Start new mDNS browser for serviceType [%s], Thread: %s", serviceType.constData(), QSTRING_CSTR(QThread::currentThread()->objectName())); + QMdnsEngine::Browser* newBrowser = new QMdnsEngine::Browser(&_server, serviceType, &_cache); + + QObject::connect(newBrowser, &QMdnsEngine::Browser::serviceAdded, this, &MdnsBrowser::onServiceAdded); + QObject::connect(newBrowser, &QMdnsEngine::Browser::serviceUpdated, this, &MdnsBrowser::onServiceUpdated); + QObject::connect(newBrowser, &QMdnsEngine::Browser::serviceRemoved, this, &MdnsBrowser::onServiceRemoved); + + _browsedServiceTypes.insert(serviceType, newBrowser); + } + else + { + DebugIf(verboseBrowser, _log, "Use existing mDNS browser for serviceType [%s], Thread: %s", serviceType.constData(), QSTRING_CSTR(QThread::currentThread()->objectName())); + } +} + +void MdnsBrowser::onServiceAdded(const QMdnsEngine::Service& service) +{ + DebugIf(verboseBrowser, _log, "Discovered service [%s] at host: %s, port: %u, Thread: %s", service.name().constData(), service.hostname().constData(), service.port(), QSTRING_CSTR(QThread::currentThread()->objectName())); + emit serviceFound(service); +} + +void MdnsBrowser::onServiceUpdated(const QMdnsEngine::Service& service) +{ + DebugIf(verboseBrowser, _log, "[%s], Name: [%s], Port: [%u], Thread: %s", service.type().constData(), service.name().constData(), service.port(), QSTRING_CSTR(QThread::currentThread()->objectName())); +} + +void MdnsBrowser::onServiceRemoved(const QMdnsEngine::Service& service) +{ + DebugIf(verboseBrowser, _log, "[%s], Name: [%s], Port: [%u], Thread: %s", service.type().constData(), service.name().constData(), service.port(), QSTRING_CSTR(QThread::currentThread()->objectName())); + emit serviceRemoved(service); +} + +QHostAddress MdnsBrowser::getHostFirstAddress(const QByteArray& hostname) +{ + DebugIf(verboseBrowser, _log, "for hostname [%s], Thread: %s", hostname.constData(), QSTRING_CSTR(QThread::currentThread()->objectName())); + QByteArray toBeResolvedHostName {hostname}; + + QHostAddress hostAddress; + + if (toBeResolvedHostName.endsWith(".local")) + { + toBeResolvedHostName.append('.'); + } + if (toBeResolvedHostName.endsWith(".local.")) + { + QList aRecords; + if (_cache.lookupRecords(toBeResolvedHostName, QMdnsEngine::A, aRecords)) + { + foreach(QMdnsEngine::Record record, aRecords) + { + // Do not publish link local addresses +#if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0)) + if (!record.address().isLinkLocal()) +#else + if (!record.address().toString().startsWith("fe80")) +#endif + { + hostAddress = record.address(); + DebugIf(verboseBrowser, _log, "Hostname [%s] translates to IPv4-address [%s]", toBeResolvedHostName.constData(), QSTRING_CSTR(hostAddress.toString())); + break; + } + } + } + else + { + QList aaaaRecords; + if (_cache.lookupRecords(toBeResolvedHostName, QMdnsEngine::AAAA, aaaaRecords)) + { + foreach(QMdnsEngine::Record record, aaaaRecords) + { + // Do not publish link local addresses +#if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0)) + if (!record.address().isLinkLocal()) +#else + if (!record.address().toString().startsWith("fe80")) +#endif + { + hostAddress = record.address(); + DebugIf(verboseBrowser, _log, "Hostname [%s] translates to IPv6-address [%s]", toBeResolvedHostName.constData(), QSTRING_CSTR(hostAddress.toString())); + break; + } + } + } + else + { + DebugIf(verboseBrowser, _log, "IP-address for hostname [%s] not yet in cache, start resolver.", toBeResolvedHostName.constData()); + qRegisterMetaType("Message"); + auto* resolver = new QMdnsEngine::Resolver(&_server, toBeResolvedHostName, &_cache); + connect(resolver, &QMdnsEngine::Resolver::resolved, this, &MdnsBrowser::onHostNameResolved); + } + } + } + return hostAddress; +} + +void MdnsBrowser::onHostNameResolved(const QHostAddress& address) +{ + DebugIf(verboseBrowser, _log, "for address [%s], Thread: %s", QSTRING_CSTR(address.toString()), QSTRING_CSTR(QThread::currentThread()->objectName())); + + // Do not publish link local addresses +#if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0)) + if (!address.isLinkLocal()) +#else + if (!address.toString().startsWith("fe80")) +#endif + { + emit addressResolved(address); + } +} + +bool MdnsBrowser::resolveAddress(Logger* log, const QString& hostname, QHostAddress& hostAddress, std::chrono::milliseconds timeout) +{ + DebugIf(verboseBrowser, _log, "Get address for hostname [%s], Thread: %s", QSTRING_CSTR(hostname), QSTRING_CSTR(QThread::currentThread()->objectName())); + + bool isHostAddressOK{ false }; + if (hostname.endsWith(".local") || hostname.endsWith(".local.")) + { + hostAddress = getHostFirstAddress(hostname.toUtf8()); + + if (hostAddress.isNull()) + { + DebugIf(verboseBrowser, _log, "Wait for resolver on hostname [%s]", QSTRING_CSTR(hostname)); + + QEventLoop loop; + QTimer t; + QObject::connect(&MdnsBrowser::getInstance(), &MdnsBrowser::addressResolved, &loop, &QEventLoop::quit); + + weakConnect(&MdnsBrowser::getInstance(), &MdnsBrowser::addressResolved, + [&hostAddress, hostname](const QHostAddress& resolvedAddress) { + DebugIf(verboseBrowser, Logger::getInstance("MDNS"), "Resolver resolved hostname [%s] to address [%s], Thread: %s", QSTRING_CSTR(hostname), QSTRING_CSTR(resolvedAddress.toString()), QSTRING_CSTR(QThread::currentThread()->objectName())); + hostAddress = resolvedAddress; + }); + + QTimer::connect(&t, &QTimer::timeout, &loop, &QEventLoop::quit); + t.start(static_cast(timeout.count())); + loop.exec(); + } + + if (!hostAddress.isNull()) + { + Debug(log, "Resolved mDNS hostname [%s] to address [%s]", QSTRING_CSTR(hostname), QSTRING_CSTR(hostAddress.toString())); + isHostAddressOK = true; + } + else + { + Error(log, "Resolved mDNS hostname [%s] timed out", QSTRING_CSTR(hostname)); + } + } + else + { + Error(log, "Hostname [%s] is not an mDNS hostname.", QSTRING_CSTR(hostname)); + isHostAddressOK = false; + } + return isHostAddressOK; +} + +QMdnsEngine::Record MdnsBrowser::getServiceInstanceRecord(const QByteArray& serviceInstance, const std::chrono::milliseconds waitTime) const +{ + DebugIf(verboseBrowser, _log, "Get service instance [%s] details, Thread: %s",serviceInstance.constData(), QSTRING_CSTR(QThread::currentThread()->objectName())); + + QByteArray service{ serviceInstance }; + + if (!service.endsWith('.')) + { + service.append('.'); + } + + QMdnsEngine::Record srvRecord; + bool found{ false }; + int retries = 5; + do + { + if (_cache.lookupRecord(service, QMdnsEngine::SRV, srvRecord)) + { + found = true; + } + else + { + wait(waitTime); + --retries; + } + + } while (!found && retries >= 0); + + if (found) + { + DebugIf(verboseBrowser, _log, "Service record found for service instance [%s]", service.constData()); + + } + else + { + Debug(_log, "No service record found for service instance [%s]", service.constData()); + } + return srvRecord; +} + +QMdnsEngine::Service MdnsBrowser::getFirstService(const QByteArray& serviceType, const QString& filter, const std::chrono::milliseconds waitTime) const +{ + DebugIf(verboseBrowser,_log, "Get first service of type [%s], matching name: [%s]", QSTRING_CSTR(QString(serviceType)), QSTRING_CSTR(filter)); + + QMdnsEngine::Service service; + + QRegularExpression regEx(filter); + if (!regEx.isValid()) { + QString errorString = regEx.errorString(); + int errorOffset = regEx.patternErrorOffset(); + + Error(_log, "Filtering regular expression [%s] error [%d]:[%s]", QSTRING_CSTR(filter), errorOffset, QSTRING_CSTR(errorString)); + } + else + { + QList ptrRecords; + + bool found {false}; + int retries = 3; + do + { + if (_cache.lookupRecords(serviceType, QMdnsEngine::PTR, ptrRecords)) + { + for (int ptrCounter = 0; ptrCounter < ptrRecords.size(); ++ptrCounter) + { + QByteArray serviceNameFull = ptrRecords.at(ptrCounter).target(); + + QRegularExpressionMatch match = regEx.match(serviceNameFull.constData()); + if (match.hasMatch()) + { + QMdnsEngine::Record srvRecord; + if (!_cache.lookupRecord(serviceNameFull, QMdnsEngine::SRV, srvRecord)) + { + DebugIf(verboseBrowser, _log, "No SRV record for [%s] found, skip entry", serviceNameFull.constData()); + } + else + { + if (serviceNameFull.endsWith("." + serviceType)) + { + service.setName(serviceNameFull.left(serviceNameFull.length() - serviceType.length() - 1)); + } + else + { + service.setName(srvRecord.name()); + } + service.setPort(srvRecord.port()); + + QByteArray hostName = srvRecord.target(); + //Remove trailing dot + hostName.chop(1); + service.setHostname(hostName); + service.setAttributes(srvRecord.attributes()); + found = true; + } + } + } + } + else + { + wait(waitTime); + --retries; + } + + } while (!found && retries >= 0); + + if (found) + { + DebugIf(verboseBrowser,_log, "Service of type [%s] found", serviceType.constData()); + } + else + { + Debug(_log, "No service of type [%s] found", serviceType.constData()); + + } + } + + return service; +} + +QJsonArray MdnsBrowser::getServicesDiscoveredJson(const QByteArray& serviceType, const QString& filter, const std::chrono::milliseconds waitTime) const +{ + DebugIf(verboseBrowser,_log, "Get services of type [%s], matching name: [%s], Thread: %s", QSTRING_CSTR(QString(serviceType)), QSTRING_CSTR(filter), QSTRING_CSTR(QThread::currentThread()->objectName())); + + QJsonArray result; + + QRegularExpression regEx(filter); + if (!regEx.isValid()) { + QString errorString = regEx.errorString(); + int errorOffset = regEx.patternErrorOffset(); + + Error(_log, "Filtering regular expression [%s] error [%d]:[%s]", QSTRING_CSTR(filter), errorOffset, QSTRING_CSTR(errorString)); + } + else + { + QList ptrRecords; + + int retries = 3; + do + { + if (_cache.lookupRecords(serviceType, QMdnsEngine::PTR, ptrRecords)) + { + for (int ptrCounter = 0; ptrCounter < ptrRecords.size(); ++ptrCounter) + { + QByteArray serviceName = ptrRecords.at(ptrCounter).target(); + + QRegularExpressionMatch match = regEx.match(serviceName.constData()); + if (match.hasMatch()) + { + QMdnsEngine::Record srvRecord; + if (!_cache.lookupRecord(serviceName, QMdnsEngine::SRV, srvRecord)) + { + Debug(_log, "No SRV record for [%s] found, skip entry", serviceName.constData()); + } + else + { + QJsonObject obj; + QString domain = "local."; + + obj.insert("id", serviceName.constData()); + + QString service = serviceName; + service.chop(1); + obj.insert("service", service); + obj.insert("type", serviceType.constData()); + + QString name; + if (serviceName.endsWith("." + serviceType)) + { + name = serviceName.left(serviceName.length() - serviceType.length() - 1); + obj.insert("name", QString(name)); + } + + QByteArray hostName = srvRecord.target(); + //Remove trailing dot + hostName.chop(1); + + obj.insert("hostname", QString(hostName)); + obj.insert("domain", domain); + + //Tag records where the service is provided by this host + QByteArray localHostname = QHostInfo::localHostName().toUtf8(); + localHostname = localHostname.replace('.', '-'); + + bool isSameHost {false}; + if ( name == localHostname ) + { + isSameHost = true; + } + obj.insert("sameHost", isSameHost); + + quint16 port = srvRecord.port(); + obj.insert("port", port); + + QMdnsEngine::Record txtRecord; + if (_cache.lookupRecord(serviceName, QMdnsEngine::TXT, txtRecord)) + { + QMap txtAttributes = txtRecord.attributes(); + + QVariantMap txtMap; + QMapIterator i(txtAttributes); + while (i.hasNext()) { + i.next(); + txtMap.insert(i.key(), i.value()); + } + obj.insert("txt", QJsonObject::fromVariantMap(txtMap)); + } + result << obj; + } + } + } + } + + if ( result.isEmpty()) + { + wait(waitTime); + --retries; + } + + } while (result.isEmpty() && retries >= 0); + + if (!result.isEmpty()) + { + DebugIf(verboseBrowser,_log, "result: [%s]", QString(QJsonDocument(result).toJson(QJsonDocument::Compact)).toUtf8().constData()); + } + else + { + Debug(_log, "No service of type [%s] found", serviceType.constData()); + + } + } + + return result; +} + +void MdnsBrowser::printCache(const QByteArray& name, quint16 type) const +{ + DebugIf(verboseBrowser,_log, "for type: ", QSTRING_CSTR(QMdnsEngine::typeName(type))); + QList records; + if (_cache.lookupRecords(name, type, records)) + { + qDebug() << ""; + foreach(QMdnsEngine::Record record, records) + { + qDebug() << QMdnsEngine::typeName(record.type()) << "," << record.name() << "], ttl : " << record.ttl(); + + switch (record.type()) { + case QMdnsEngine::PTR: + qDebug() << QMdnsEngine::typeName(record.type()) << "," << record.name() << ", target : " << record.target(); + break; + + case QMdnsEngine::SRV: + qDebug() << QMdnsEngine::typeName(record.type()) << "," << record.name() << ", target : " << record.target(); + qDebug() << QMdnsEngine::typeName(record.type()) << "," << record.name() << ", port : " << record.port(); + qDebug() << QMdnsEngine::typeName(record.type()) << "," << record.name() << ", priority : " << record.priority(); + qDebug() << QMdnsEngine::typeName(record.type()) << "," << record.name() << ", weight : " << record.weight(); + break; + case QMdnsEngine::TXT: + qDebug() << QMdnsEngine::typeName(record.type()) << "," << record.name() << ", attributes: " << record.attributes(); + break; + + case QMdnsEngine::NSEC: + qDebug() << QMdnsEngine::typeName(record.type()) << "," << record.name() << ", nextDomNam: " << record.nextDomainName(); + break; + + case QMdnsEngine::A: + case QMdnsEngine::AAAA: + qDebug() << QMdnsEngine::typeName(record.type()) << "," << record.name() << ", address : " << record.address(); + break; + } + } + } + else + { + DebugIf(verboseBrowser,_log, "Cash is empty for type: ", QSTRING_CSTR(QMdnsEngine::typeName(type))); + } +} diff --git a/libsrc/mdns/MdnsProvider.cpp b/libsrc/mdns/MdnsProvider.cpp new file mode 100644 index 00000000..40e17c7b --- /dev/null +++ b/libsrc/mdns/MdnsProvider.cpp @@ -0,0 +1,85 @@ +#include +#include + +//Qt includes +#include +#include + +// Utility includes +#include +#include +#include + +namespace { + const bool verboseProvider = false; +} //End of constants + +MdnsProvider::MdnsProvider(QObject* parent) + : QObject(parent) + , _log(Logger::getInstance("MDNS")) + , _server(nullptr) + , _hostname(nullptr) +{ +} + +void MdnsProvider::init() +{ + _server = new QMdnsEngine::Server(); + _hostname = new QMdnsEngine::Hostname(_server); + + connect(_hostname, &QMdnsEngine::Hostname::hostnameChanged, this, &MdnsProvider::onHostnameChanged); + DebugIf(verboseProvider, _log, "Hostname [%s], isRegistered [%d]", _hostname->hostname().constData(), _hostname->isRegistered()); +} + +MdnsProvider::~MdnsProvider() +{ + qDeleteAll(_providedServiceTypes); + + _hostname->deleteLater(); + _server->deleteLater(); +} + +void MdnsProvider::publishService(const QString& serviceType, quint16 servicePort, const QByteArray& serviceName) +{ + QMdnsEngine::Provider* provider(nullptr); + + QByteArray type = MdnsServiceRegister::getServiceType(serviceType); + if (!type.isEmpty()) + { + DebugIf(verboseProvider, _log, "Publish new mDNS serviceType [%s], Thread: %s", type.constData(), QSTRING_CSTR(QThread::currentThread()->objectName())); + + if (!_providedServiceTypes.contains(type)) + { + provider = new QMdnsEngine::Provider(_server, _hostname); + _providedServiceTypes.insert(type, provider); + } + else + { + provider = _providedServiceTypes[type]; + } + + QMdnsEngine::Service service; + service.setType(type); + service.setPort(servicePort); + + QByteArray name(QHostInfo::localHostName().toUtf8()); + if (!serviceName.isEmpty()) + { + name.prepend(serviceName + "@"); + } + service.setName(name); + + QByteArray id = AuthManager::getInstance()->getID().toUtf8(); + const QMap attributes = { {"id", id}, {"version", HYPERION_VERSION} }; + service.setAttributes(attributes); + + DebugIf(verboseProvider, _log, "[%s], Name: [%s], Port: [%u] ", service.type().constData(), service.name().constData(), service.port()); + + provider->update(service); + } +} + +void MdnsProvider::onHostnameChanged(const QByteArray& hostname) +{ + DebugIf(verboseProvider, _log, "mDNS-hostname changed to hostname [%s]", hostname.constData()); +} diff --git a/libsrc/protoserver/CMakeLists.txt b/libsrc/protoserver/CMakeLists.txt index dd8890b8..5fefa7ef 100644 --- a/libsrc/protoserver/CMakeLists.txt +++ b/libsrc/protoserver/CMakeLists.txt @@ -35,7 +35,7 @@ endif() target_link_libraries(protoclient hyperion hyperion-utils - libprotobuf + ${PROTOBUF_LIBRARIES} Qt${QT_VERSION_MAJOR}::Gui ) @@ -45,3 +45,7 @@ target_link_libraries(protoserver protoclient Qt${QT_VERSION_MAJOR}::Gui ) + +if(ENABLE_MDNS) + target_link_libraries(protoserver mdns) +endif() diff --git a/libsrc/protoserver/ProtoServer.cpp b/libsrc/protoserver/ProtoServer.cpp index fb45ae60..5ba8d98a 100644 --- a/libsrc/protoserver/ProtoServer.cpp +++ b/libsrc/protoserver/ProtoServer.cpp @@ -10,6 +10,13 @@ #include #include +// Constants +namespace { + +const char SERVICE_TYPE[] = "protobuffer"; + +} //End of constants + ProtoServer::ProtoServer(const QJsonDocument& config, QObject* parent) : QObject(parent) , _server(new QTcpServer(this)) @@ -95,11 +102,12 @@ void ProtoServer::startServer() { if(!_server->listen(QHostAddress::Any, _port)) { - Error(_log,"Failed to bind port %d", _port); + Error(_log,"Failed to bind port %d", _port); } else { - Info(_log,"Started on port %d", _port); + Info(_log,"Started on port %d", _port); + emit publishService(SERVICE_TYPE, _port); } } } diff --git a/libsrc/utils/JsonUtils.cpp b/libsrc/utils/JsonUtils.cpp index 0040f241..c24d0939 100644 --- a/libsrc/utils/JsonUtils.cpp +++ b/libsrc/utils/JsonUtils.cpp @@ -78,7 +78,7 @@ namespace JsonUtils { ++errorLine; } } - Error(log,"Failed to parse json data from %s: Error: %s at Line: %i, Column: %i", QSTRING_CSTR(path), QSTRING_CSTR(error.errorString()), errorLine, errorColumn); + Error(log, "Failed to parse json data from %s: Error: %s at Line: %i, Column: %i, Data: '%s'", QSTRING_CSTR(path), QSTRING_CSTR(error.errorString()), errorLine, errorColumn, QSTRING_CSTR(data)); return false; } return true; diff --git a/libsrc/webserver/CMakeLists.txt b/libsrc/webserver/CMakeLists.txt index a849ced8..3dcfde15 100644 --- a/libsrc/webserver/CMakeLists.txt +++ b/libsrc/webserver/CMakeLists.txt @@ -25,3 +25,7 @@ target_link_libraries(webserver hyperion-api Qt${QT_VERSION_MAJOR}::Network ) + +if(ENABLE_MDNS) + target_link_libraries(webserver mdns) +endif() diff --git a/libsrc/webserver/WebServer.cpp b/libsrc/webserver/WebServer.cpp index 9486f075..a5733d95 100644 --- a/libsrc/webserver/WebServer.cpp +++ b/libsrc/webserver/WebServer.cpp @@ -6,21 +6,25 @@ #include #include -// bonjour -#ifdef ENABLE_AVAHI -#include -#endif // netUtil #include -WebServer::WebServer(const QJsonDocument& config, bool useSsl, QObject * parent) - : QObject(parent) +// Constants +namespace { + + const char HTTP_SERVICE_TYPE[] = "http"; + const char HTTPS_SERVICE_TYPE[] = "https"; + const char HYPERION_SERVICENAME[] = "Hyperion"; + +} //End of constants + +WebServer::WebServer(const QJsonDocument& config, bool useSsl, QObject* parent) + : QObject(parent) , _config(config) , _useSsl(useSsl) , _log(Logger::getInstance("WEBSERVER")) , _server() { - } WebServer::~WebServer() @@ -30,64 +34,60 @@ WebServer::~WebServer() void WebServer::initServer() { - Debug(_log, "Initialize Webserver"); - _server = new QtHttpServer (this); - _server->setServerName (QStringLiteral ("Hyperion Webserver")); + Debug(_log, "Initialize %s-Webserver", _useSsl ? "https" : "http"); + _server = new QtHttpServer(this); + _server->setServerName(QStringLiteral("Hyperion %1-Webserver").arg(_useSsl ? "https" : "http")); - if(_useSsl) + if (_useSsl) { _server->setUseSecure(); WEBSERVER_DEFAULT_PORT = 8092; } - connect (_server, &QtHttpServer::started, this, &WebServer::onServerStarted); - connect (_server, &QtHttpServer::stopped, this, &WebServer::onServerStopped); - connect (_server, &QtHttpServer::error, this, &WebServer::onServerError); + connect(_server, &QtHttpServer::started, this, &WebServer::onServerStarted); + connect(_server, &QtHttpServer::stopped, this, &WebServer::onServerStopped); + connect(_server, &QtHttpServer::error, this, &WebServer::onServerError); // create StaticFileServing - _staticFileServing = new StaticFileServing (this); + _staticFileServing = new StaticFileServing(this); connect(_server, &QtHttpServer::requestNeedsReply, _staticFileServing, &StaticFileServing::onRequestNeedsReply); // init handleSettingsUpdate(settings::WEBSERVER, _config); } -void WebServer::onServerStarted (quint16 port) +void WebServer::onServerStarted(quint16 port) { _inited = true; - Info(_log, "'%s' started on port %d",_server->getServerName().toStdString().c_str(), port); + Info(_log, "'%s' started on port %d", _server->getServerName().toStdString().c_str(), port); -#ifdef ENABLE_AVAHI - if(_serviceRegister == nullptr) + if (_useSsl) { - _serviceRegister = new BonjourServiceRegister(this); - _serviceRegister->registerService("_hyperiond-http._tcp", port); + emit publishService(HTTPS_SERVICE_TYPE, _port, HYPERION_SERVICENAME); } - else if( _serviceRegister->getPort() != port) + else { - delete _serviceRegister; - _serviceRegister = new BonjourServiceRegister(this); - _serviceRegister->registerService("_hyperiond-http._tcp", port); + emit publishService(HTTP_SERVICE_TYPE, _port, HYPERION_SERVICENAME); } -#endif + emit stateChange(true); } -void WebServer::onServerStopped () +void WebServer::onServerStopped() { Info(_log, "Stopped %s", _server->getServerName().toStdString().c_str()); emit stateChange(false); } -void WebServer::onServerError (QString msg) +void WebServer::onServerError(QString msg) { Error(_log, "%s", msg.toStdString().c_str()); } void WebServer::handleSettingsUpdate(settings::type type, const QJsonDocument& config) { - if(type == settings::WEBSERVER) + if (type == settings::WEBSERVER) { Debug(_log, "Apply Webserver settings"); const QJsonObject& obj = config.object(); @@ -95,7 +95,7 @@ void WebServer::handleSettingsUpdate(settings::type type, const QJsonDocument& c _baseUrl = obj["document_root"].toString(WEBSERVER_DEFAULT_PATH); - if ( (_baseUrl != ":/webconfig") && !_baseUrl.trimmed().isEmpty()) + if ((_baseUrl != ":/webconfig") && !_baseUrl.trimmed().isEmpty()) { QFileInfo info(_baseUrl); if (!info.exists() || !info.isDir()) @@ -112,18 +112,18 @@ void WebServer::handleSettingsUpdate(settings::type type, const QJsonDocument& c // ssl different port quint16 newPort = _useSsl ? obj["sslPort"].toInt(WEBSERVER_DEFAULT_PORT) : obj["port"].toInt(WEBSERVER_DEFAULT_PORT); - if(_port != newPort) + if (_port != newPort) { _port = newPort; stop(); } // eval if the port is available, will be incremented if not - if(!_server->isListening()) + if (!_server->isListening()) NetUtils::portAvailable(_port, _log); // on ssl we want .key .cert and probably key password - if(_useSsl) + if (_useSsl) { QString keyPath = obj["keyPath"].toString(WEBSERVER_DEFAULT_KEY_PATH); QString crtPath = obj["crtPath"].toString(WEBSERVER_DEFAULT_CRT_PATH); @@ -132,7 +132,7 @@ void WebServer::handleSettingsUpdate(settings::type type, const QJsonDocument& c QList currCerts = _server->getCertificates(); // check keyPath - if ( (keyPath != WEBSERVER_DEFAULT_KEY_PATH) && !keyPath.trimmed().isEmpty()) + if ((keyPath != WEBSERVER_DEFAULT_KEY_PATH) && !keyPath.trimmed().isEmpty()) { QFileInfo kinfo(keyPath); if (!kinfo.exists()) @@ -145,7 +145,7 @@ void WebServer::handleSettingsUpdate(settings::type type, const QJsonDocument& c keyPath = WEBSERVER_DEFAULT_KEY_PATH; // check crtPath - if ( (crtPath != WEBSERVER_DEFAULT_CRT_PATH) && !crtPath.trimmed().isEmpty()) + if ((crtPath != WEBSERVER_DEFAULT_CRT_PATH) && !crtPath.trimmed().isEmpty()) { QFileInfo cinfo(crtPath); if (!cinfo.exists()) @@ -161,21 +161,22 @@ void WebServer::handleSettingsUpdate(settings::type type, const QJsonDocument& c QFile cfile(crtPath); cfile.open(QIODevice::ReadOnly); QList validList; - QList cList = QSslCertificate::fromDevice(&cfile, QSsl::Pem); + QList cList = QSslCertificate::fromDevice(&cfile, QSsl::Pem); cfile.close(); // Filter for valid certs - for(const auto & entry : cList){ - if(!entry.isNull() && QDateTime::currentDateTime().daysTo(entry.expiryDate()) > 0) + for (const auto& entry : cList) { + if (!entry.isNull() && QDateTime::currentDateTime().daysTo(entry.expiryDate()) > 0) validList.append(entry); else Error(_log, "The provided SSL certificate is invalid/not supported/reached expiry date ('%s')", crtPath.toUtf8().constData()); } - if(!validList.isEmpty()){ - Debug(_log,"Setup SSL certificate"); + if (!validList.isEmpty()) { + Debug(_log, "Setup SSL certificate"); _server->setCertificates(validList); - } else { + } + else { Error(_log, "No valid SSL certificate has been found ('%s')", crtPath.toUtf8().constData()); } @@ -186,10 +187,11 @@ void WebServer::handleSettingsUpdate(settings::type type, const QJsonDocument& c QSslKey key(&kfile, QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey, obj["keyPassPhrase"].toString().toUtf8()); kfile.close(); - if(key.isNull()){ + if (key.isNull()) { Error(_log, "The provided SSL key is invalid or not supported use RSA encrypt and PEM format ('%s')", keyPath.toUtf8().constData()); - } else { - Debug(_log,"Setup private SSL key"); + } + else { + Debug(_log, "Setup private SSL key"); _server->setPrivateKey(key); } } @@ -209,7 +211,7 @@ void WebServer::stop() _server->stop(); } -void WebServer::setSSDPDescription(const QString & desc) +void WebServer::setSSDPDescription(const QString& desc) { _staticFileServing->setSSDPDescription(desc); } diff --git a/src/hyperion-aml/CMakeLists.txt b/src/hyperion-aml/CMakeLists.txt index 11c641a0..37bed4af 100644 --- a/src/hyperion-aml/CMakeLists.txt +++ b/src/hyperion-aml/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.0.0) +cmake_minimum_required(VERSION 3.1.0) project(hyperion-aml) find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Core Network Widgets REQUIRED) @@ -29,12 +29,17 @@ target_link_libraries(${PROJECT_NAME} flatbuffers amlogic-grabber framebuffer-grabber - ssdp Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Network Qt${QT_VERSION_MAJOR}::Widgets ) +if(ENABLE_MDNS) + target_link_libraries(${PROJECT_NAME} mdns) +else() + target_link_libraries(${PROJECT_NAME} ssdp) +endif() + if (ENABLE_AMLOGIC) target_link_libraries(${PROJECT_NAME} pcre16 dl z diff --git a/src/hyperion-aml/hyperion-aml.cpp b/src/hyperion-aml/hyperion-aml.cpp index c0391b78..15895447 100644 --- a/src/hyperion-aml/hyperion-aml.cpp +++ b/src/hyperion-aml/hyperion-aml.cpp @@ -9,12 +9,25 @@ #include "HyperionConfig.h" #include +#ifdef ENABLE_MDNS +// mDNS discover +#include +#include +#else // ssdp discover #include +#endif #include #include +// Constants +namespace { + + const char SERVICE_TYPE[] = "flatbuffer"; + +} //End of constants + using namespace commandline; // save the image as screenshot @@ -111,27 +124,34 @@ int main(int argc, char ** argv) } else { - // server searching by ssdp - QString address = argAddress.value(parser); - if(address == "127.0.0.1" || address == "127.0.0.1:19400") - { - SSDPDiscover discover; - address = discover.getFirstService(searchType::STY_FLATBUFSERVER); - if(address.isEmpty()) - { - address = argAddress.value(parser); - } - } - - // Resolve hostname and port (or use default port) QString host; - quint16 port{ FLATBUFFER_DEFAULT_PORT }; + QString serviceName{ QHostInfo::localHostName() }; + int port{ FLATBUFFER_DEFAULT_PORT }; - if (!NetUtils::resolveHostPort(address, host, port)) + // Split hostname and port (or use default port) + QString givenAddress = argAddress.value(parser); + if (!NetUtils::resolveHostPort(givenAddress, host, port)) { - throw std::runtime_error(QString("Wrong address: unable to parse address (%1)").arg(address).toStdString()); + throw std::runtime_error(QString("Wrong address: unable to parse address (%1)").arg(givenAddress).toStdString()); } + // Search available Hyperion services via mDNS, if default/localhost IP is given + if (host == "127.0.0.1" || host == "::1") + { +#ifndef ENABLE_MDNS + SSDPDiscover discover; + host = discover.getFirstService(searchType::STY_FLATBUFSERVER); +#endif + QHostAddress address; + if (!NetUtils::resolveHostToAddress(log, host, address, port)) + { + throw std::runtime_error(QString("Address could not be resolved for hostname: %2").arg(QSTRING_CSTR(host)).toStdString()); + } + host = address.toString(); + } + + Info(log, "Connecting to Hyperion host: %s, port: %u using service: %s", QSTRING_CSTR(host), port, QSTRING_CSTR(serviceName)); + // Create the Flabuf-connection FlatBufferConnection flatbuf("AML Standalone", host, argPriority.getInt(parser), parser.isSet(argSkipReply), port); diff --git a/src/hyperion-dispmanx/CMakeLists.txt b/src/hyperion-dispmanx/CMakeLists.txt index bcf4b489..17720483 100644 --- a/src/hyperion-dispmanx/CMakeLists.txt +++ b/src/hyperion-dispmanx/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.0.0) +cmake_minimum_required(VERSION 3.1.0) project(hyperion-dispmanx) find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Core Network Widgets REQUIRED) @@ -22,21 +22,23 @@ add_executable( ${PROJECT_NAME} ${Hyperion_Dispmanx_SOURCES} ) -SET(CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} "-rdynamic") -SET(CMAKE_C_FLAGS ${CMAKE_CXX_FLAGS} "-rdynamic") - target_link_libraries( ${PROJECT_NAME} commandline hyperion-utils flatbufconnect flatbuffers dispmanx-grabber - ssdp Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Network Qt${QT_VERSION_MAJOR}::Widgets ) +if(ENABLE_MDNS) + target_link_libraries(${PROJECT_NAME} mdns) +else() + target_link_libraries(${PROJECT_NAME} ssdp) +endif() + install ( TARGETS ${PROJECT_NAME} DESTINATION "share/hyperion/bin" COMPONENT "hyperion_dispmanx" ) if(CMAKE_HOST_UNIX) diff --git a/src/hyperion-dispmanx/hyperion-dispmanx.cpp b/src/hyperion-dispmanx/hyperion-dispmanx.cpp index 42900ccf..07ccf37f 100644 --- a/src/hyperion-dispmanx/hyperion-dispmanx.cpp +++ b/src/hyperion-dispmanx/hyperion-dispmanx.cpp @@ -10,12 +10,25 @@ #include "HyperionConfig.h" #include +#ifdef ENABLE_MDNS +// mDNS discover +#include +#include +#else // ssdp discover #include +#endif #include #include +// Constants +namespace { + + const char SERVICE_TYPE[] = "flatbuffer"; + +} //End of constants + using namespace commandline; // save the image as screenshot @@ -114,27 +127,34 @@ int main(int argc, char ** argv) } else { - // server searching by ssdp - QString address = argAddress.value(parser); - if(address == "127.0.0.1" || address == "127.0.0.1:19400") - { - SSDPDiscover discover; - address = discover.getFirstService(searchType::STY_FLATBUFSERVER); - if(address.isEmpty()) - { - address = argAddress.value(parser); - } - } - - // Resolve hostname and port (or use default port) QString host; - quint16 port{ FLATBUFFER_DEFAULT_PORT }; + QString serviceName{ QHostInfo::localHostName() }; + int port{ FLATBUFFER_DEFAULT_PORT }; - if (!NetUtils::resolveHostPort(address, host, port)) + // Split hostname and port (or use default port) + QString givenAddress = argAddress.value(parser); + if (!NetUtils::resolveHostPort(givenAddress, host, port)) { - throw std::runtime_error(QString("Wrong address: unable to parse address (%1)").arg(address).toStdString()); + throw std::runtime_error(QString("Wrong address: unable to parse address (%1)").arg(givenAddress).toStdString()); } + // Search available Hyperion services via mDNS, if default/localhost IP is given + if (host == "127.0.0.1" || host == "::1") + { +#ifndef ENABLE_MDNS + SSDPDiscover discover; + host = discover.getFirstService(searchType::STY_FLATBUFSERVER); +#endif + QHostAddress address; + if (!NetUtils::resolveHostToAddress(log, host, address, port)) + { + throw std::runtime_error(QString("Address could not be resolved for hostname: %2").arg(QSTRING_CSTR(host)).toStdString()); + } + host = address.toString(); + } + + Info(log, "Connecting to Hyperion host: %s, port: %u using service: %s", QSTRING_CSTR(host), port, QSTRING_CSTR(serviceName)); + // Create the Flabuf-connection FlatBufferConnection flatbuf("Dispmanx Standalone", host, argPriority.getInt(parser), parser.isSet(argSkipReply), port); diff --git a/src/hyperion-framebuffer/CMakeLists.txt b/src/hyperion-framebuffer/CMakeLists.txt index dd65d8db..5667203a 100644 --- a/src/hyperion-framebuffer/CMakeLists.txt +++ b/src/hyperion-framebuffer/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.0.0) +cmake_minimum_required(VERSION 3.1.0) project(hyperion-framebuffer) find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Core Network Widgets REQUIRED) @@ -28,13 +28,18 @@ target_link_libraries( ${PROJECT_NAME} flatbufconnect flatbuffers framebuffer-grabber - ssdp Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Gui Qt${QT_VERSION_MAJOR}::Network Qt${QT_VERSION_MAJOR}::Widgets ) +if(ENABLE_MDNS) + target_link_libraries(${PROJECT_NAME} mdns) +else() + target_link_libraries(${PROJECT_NAME} ssdp) +endif() + if (ENABLE_AMLOGIC) target_link_libraries( ${PROJECT_NAME} pcre16 dl z diff --git a/src/hyperion-framebuffer/hyperion-framebuffer.cpp b/src/hyperion-framebuffer/hyperion-framebuffer.cpp index 34001833..ba82953c 100644 --- a/src/hyperion-framebuffer/hyperion-framebuffer.cpp +++ b/src/hyperion-framebuffer/hyperion-framebuffer.cpp @@ -9,12 +9,25 @@ #include "HyperionConfig.h" #include +#ifdef ENABLE_MDNS +// mDNS discover +#include +#include +#else // ssdp discover #include +#endif #include #include +// Constants +namespace { + + const char SERVICE_TYPE[] = "flatbuffer"; + +} //End of constants + using namespace commandline; // save the image as screenshot @@ -114,27 +127,34 @@ int main(int argc, char ** argv) } else { - // server searching by ssdp - QString address = argAddress.value(parser); - if(address == "127.0.0.1" || address == "127.0.0.1:19400") - { - SSDPDiscover discover; - address = discover.getFirstService(searchType::STY_FLATBUFSERVER); - if(address.isEmpty()) - { - address = argAddress.value(parser); - } - } - - // Resolve hostname and port (or use default port) QString host; - quint16 port{ FLATBUFFER_DEFAULT_PORT }; + QString serviceName{ QHostInfo::localHostName() }; + int port{ FLATBUFFER_DEFAULT_PORT }; - if (!NetUtils::resolveHostPort(address, host, port)) + // Split hostname and port (or use default port) + QString givenAddress = argAddress.value(parser); + if (!NetUtils::resolveHostPort(givenAddress, host, port)) { - throw std::runtime_error(QString("Wrong address: unable to parse address (%1)").arg(address).toStdString()); + throw std::runtime_error(QString("Wrong address: unable to parse address (%1)").arg(givenAddress).toStdString()); } + // Search available Hyperion services via mDNS, if default/localhost IP is given + if (host == "127.0.0.1" || host == "::1") + { +#ifndef ENABLE_MDNS + SSDPDiscover discover; + host = discover.getFirstService(searchType::STY_FLATBUFSERVER); +#endif + QHostAddress address; + if (!NetUtils::resolveHostToAddress(log, host, address, port)) + { + throw std::runtime_error(QString("Address could not be resolved for hostname: %2").arg(QSTRING_CSTR(host)).toStdString()); + } + host = address.toString(); + } + + Info(log, "Connecting to Hyperion host: %s, port: %u using service: %s", QSTRING_CSTR(host), port, QSTRING_CSTR(serviceName)); + // Create the Flabuf-connection FlatBufferConnection flatbuf("Framebuffer Standalone", host, argPriority.getInt(parser), parser.isSet(argSkipReply), port); diff --git a/src/hyperion-osx/CMakeLists.txt b/src/hyperion-osx/CMakeLists.txt index 8365fdbd..12f17a9b 100644 --- a/src/hyperion-osx/CMakeLists.txt +++ b/src/hyperion-osx/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.0.0) +cmake_minimum_required(VERSION 3.1.0) project(hyperion-osx) find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Core Gui Network Widgets REQUIRED) @@ -28,11 +28,16 @@ target_link_libraries( ${PROJECT_NAME} flatbufconnect flatbuffers osx-grabber - ssdp Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Gui Qt${QT_VERSION_MAJOR}::Network Qt${QT_VERSION_MAJOR}::Widgets ) +if(ENABLE_MDNS) + target_link_libraries(${PROJECT_NAME} mdns) +else() + target_link_libraries(${PROJECT_NAME} ssdp) +endif() + install ( TARGETS ${PROJECT_NAME} DESTINATION "." COMPONENT "hyperion_osx" ) diff --git a/src/hyperion-osx/hyperion-osx.cpp b/src/hyperion-osx/hyperion-osx.cpp index 4d15a99c..b94b2881 100644 --- a/src/hyperion-osx/hyperion-osx.cpp +++ b/src/hyperion-osx/hyperion-osx.cpp @@ -8,12 +8,25 @@ #include "OsxWrapper.h" #include +#ifdef ENABLE_MDNS +// mDNS discover +#include +#include +#else // ssdp discover #include +#endif #include #include +// Constants +namespace { + + const char SERVICE_TYPE[] = "flatbuffer"; + +} //End of constants + using namespace commandline; // save the image as screenshot @@ -108,27 +121,35 @@ int main(int argc, char ** argv) } else { - // server searching by ssdp - QString address = argAddress.value(parser); - if(address == "127.0.0.1" || address == "127.0.0.1:19400") - { - SSDPDiscover discover; - address = discover.getFirstService(searchType::STY_FLATBUFSERVER); - if(address.isEmpty()) - { - address = argAddress.value(parser); - } - } - - // Resolve hostname and port (or use default port) QString host; - quint16 port{ FLATBUFFER_DEFAULT_PORT }; + QString serviceName{ QHostInfo::localHostName() }; + int port{ FLATBUFFER_DEFAULT_PORT }; - if (!NetUtils::resolveHostPort(address, host, port)) + // Split hostname and port (or use default port) + QString givenAddress = argAddress.value(parser); + if (!NetUtils::resolveHostPort(givenAddress, host, port)) { - throw std::runtime_error(QString("Wrong address: unable to parse address (%1)").arg(address).toStdString()); + throw std::runtime_error(QString("Wrong address: unable to parse address (%1)").arg(givenAddress).toStdString()); } + // Search available Hyperion services via mDNS, if default/localhost IP is given + if (host == "127.0.0.1" || host == "::1") + { +#ifndef ENABLE_MDNS + SSDPDiscover discover; + host = discover.getFirstService(searchType::STY_FLATBUFSERVER); +#endif + + QHostAddress address; + if (!NetUtils::resolveHostToAddress(log, host, address, port)) + { + throw std::runtime_error(QString("Address could not be resolved for hostname: %2").arg(QSTRING_CSTR(host)).toStdString()); + } + host = address.toString(); + } + + Info(log, "Connecting to Hyperion host: %s, port: %u using service: %s", QSTRING_CSTR(host), port, QSTRING_CSTR(serviceName)); + // Create the Flabuf-connection FlatBufferConnection flatbuf("OSX Standalone", host, argPriority.getInt(parser), parser.isSet(argSkipReply), port); diff --git a/src/hyperion-qt/CMakeLists.txt b/src/hyperion-qt/CMakeLists.txt index a3f6cb97..6cc4b386 100644 --- a/src/hyperion-qt/CMakeLists.txt +++ b/src/hyperion-qt/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.0.0) +cmake_minimum_required(VERSION 3.1.0) project(hyperion-qt) find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Core Gui Network Widgets REQUIRED) @@ -34,13 +34,18 @@ target_link_libraries(${PROJECT_NAME} qt-grabber flatbufconnect flatbuffers - ssdp Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Gui Qt${QT_VERSION_MAJOR}::Network Qt${QT_VERSION_MAJOR}::Widgets ) +if(ENABLE_MDNS) + target_link_libraries(${PROJECT_NAME} mdns) +else() + target_link_libraries(${PROJECT_NAME} ssdp) +endif() + if(APPLE) install ( TARGETS ${PROJECT_NAME} DESTINATION "." COMPONENT "hyperion_qt" ) elseif(NOT WIN32) diff --git a/src/hyperion-qt/hyperion-qt.cpp b/src/hyperion-qt/hyperion-qt.cpp index 223c211e..29060088 100644 --- a/src/hyperion-qt/hyperion-qt.cpp +++ b/src/hyperion-qt/hyperion-qt.cpp @@ -1,4 +1,3 @@ - // QT includes #include #include @@ -10,20 +9,33 @@ #include "HyperionConfig.h" #include +#ifdef ENABLE_MDNS +// mDNS discover +#include +#include +#else +// ssdp discover +#include +#endif +#include + //flatbuf sending #include -// ssdp discover -#include -#include +// Constants +namespace { + +const char SERVICE_TYPE[] = "flatbuffer"; + +} //End of constants using namespace commandline; // save the image as screenshot -void saveScreenshot(QString filename, const Image & image) +void saveScreenshot(const QString& filename, const Image & image) { // store as PNG - QImage pngImage((const uint8_t *) image.memptr(), image.width(), image.height(), 3*image.width(), QImage::Format_RGB888); + QImage pngImage(reinterpret_cast(image.memptr()), static_cast(image.width()), static_cast(image.height()), static_cast(3*image.width()), QImage::Format_RGB888); pngImage.save(filename); } @@ -115,38 +127,46 @@ int main(int argc, char ** argv) } else { - // server searching by ssdp - QString address = argAddress.value(parser); - if(address == "127.0.0.1" || address == "127.0.0.1:19400") - { - SSDPDiscover discover; - address = discover.getFirstService(searchType::STY_FLATBUFSERVER); - if(address.isEmpty()) - { - address = argAddress.value(parser); - } - } - - // Resolve hostname and port (or use default port) QString host; - quint16 port { FLATBUFFER_DEFAULT_PORT }; + QString serviceName {QHostInfo::localHostName()}; + int port {FLATBUFFER_DEFAULT_PORT}; - if ( !NetUtils::resolveHostPort(address, host, port) ) + // Split hostname and port (or use default port) + QString givenAddress = argAddress.value(parser); + if (!NetUtils::resolveHostPort(givenAddress, host, port)) { - throw std::runtime_error(QString("Wrong address: unable to parse address (%1)").arg(address).toStdString()); + throw std::runtime_error(QString("Wrong address: unable to parse address (%1)").arg(givenAddress).toStdString()); } + // Search available Hyperion services via mDNS, if default/localhost IP is given + if (host == "127.0.0.1" || host == "::1") + { +#ifndef ENABLE_MDNS + SSDPDiscover discover; + host = discover.getFirstService(searchType::STY_FLATBUFSERVER); +#endif + + QHostAddress address; + if (!NetUtils::resolveHostToAddress(log, host, address, port)) + { + throw std::runtime_error(QString("Address could not be resolved for hostname: %2").arg(QSTRING_CSTR(host)).toStdString()); + } + host = address.toString(); + } + + Info(log, "Connecting to Hyperion host: %s, port: %u using service: %s", QSTRING_CSTR(host), port, QSTRING_CSTR(serviceName)); + // Create the Flabuf-connection FlatBufferConnection flatbuf("Qt Standalone", host, argPriority.getInt(parser), parser.isSet(argSkipReply), port); // Connect the screen capturing to flatbuf connection processing - QObject::connect(&qtWrapper, SIGNAL(sig_screenshot(const Image &)), &flatbuf, SLOT(setImage(Image))); + QObject::connect(&qtWrapper, &QtWrapper::sig_screenshot, &flatbuf, &FlatBufferConnection::setImage); // Start the capturing qtWrapper.start(); // Start the application - app.exec(); + QGuiApplication::exec(); } } catch (const std::runtime_error & e) diff --git a/src/hyperion-remote/CMakeLists.txt b/src/hyperion-remote/CMakeLists.txt index ab2b6508..a7ce5cfe 100644 --- a/src/hyperion-remote/CMakeLists.txt +++ b/src/hyperion-remote/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.0.0) +cmake_minimum_required(VERSION 3.1.0) project(hyperion-remote) find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Core Network Widgets REQUIRED) @@ -32,7 +32,6 @@ add_executable(${PROJECT_NAME} target_link_libraries(${PROJECT_NAME} commandline hyperion-utils - ssdp Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Network Qt${QT_VERSION_MAJOR}::Widgets @@ -44,6 +43,12 @@ if (ENABLE_AMLOGIC) ) endif() +if(ENABLE_MDNS) + target_link_libraries(${PROJECT_NAME} mdns) +else() + target_link_libraries(${PROJECT_NAME} ssdp) +endif() + if(ENABLE_EFFECTENGINE) target_link_libraries(${PROJECT_NAME} effectengine) endif() diff --git a/src/hyperion-remote/JsonConnection.h b/src/hyperion-remote/JsonConnection.h index 111aab42..03c23c3f 100644 --- a/src/hyperion-remote/JsonConnection.h +++ b/src/hyperion-remote/JsonConnection.h @@ -9,7 +9,7 @@ //forward class decl class Logger; -const int JSON_DEFAULT_PORT = 19444; +const int JSONAPI_DEFAULT_PORT = 19444; /// /// Connection class to setup an connection to the hyperion server and execute commands @@ -24,7 +24,7 @@ public: /// @param address The port of the Hyperion JSON-server (default port = 19444) /// @param printJson Boolean indicating if the sent and received json is written to stdout /// - JsonConnection(const QString& host, bool printJson, quint16 port = JSON_DEFAULT_PORT); + JsonConnection(const QString& host, bool printJson, quint16 port = JSONAPI_DEFAULT_PORT); /// /// Destructor diff --git a/src/hyperion-remote/hyperion-remote.cpp b/src/hyperion-remote/hyperion-remote.cpp index 97ecd3c1..d492a699 100644 --- a/src/hyperion-remote/hyperion-remote.cpp +++ b/src/hyperion-remote/hyperion-remote.cpp @@ -9,18 +9,34 @@ #include #include +#include + #include "HyperionConfig.h" #include +#ifdef ENABLE_MDNS +// mDNS discover +#include +#include +#else +// ssdp discover +#include +#endif +#include + // hyperion-remote include #include "JsonConnection.h" -// ssdp discover -#include -#include - #include +// Constants +namespace { + + const char SERVICE_TYPE[] = "jsonapi"; + +} //End of constants + + using namespace commandline; /// Count the number of true values in a list of booleans @@ -38,10 +54,10 @@ int count(std::initializer_list values) void showHelp(Option & option){ QString shortOption; - QString longOption = QString("--%1").arg(option.names().last()); + QString longOption = QString("--%1").arg(option.names().constLast()); if(option.names().size() == 2){ - shortOption = QString("-%1").arg(option.names().first()); + shortOption = QString("-%1").arg(option.names().constFirst()); } qWarning() << qPrintable(QString("\t%1\t%2\t%3").arg(shortOption, longOption, option.description())); @@ -51,7 +67,7 @@ int getInstaneIdbyName(const QJsonObject & reply, const QString & name){ if(reply.contains("instance")){ QJsonArray list = reply.value("instance").toArray(); - for (const QJsonValueRef entry : list) { + for ( const auto &entry : qAsConst(list) ) { const QJsonObject obj = entry.toObject(); if(obj["friendly_name"] == name && obj["running"].toBool()) { @@ -65,10 +81,6 @@ int getInstaneIdbyName(const QJsonObject & reply, const QString & name){ int main(int argc, char * argv[]) { -#ifndef _WIN32 - setenv("AVAHI_COMPAT_NOWARN", "1", 1); -#endif - Logger* log = Logger::getInstance("REMOTE"); Logger::setLogLevel(Logger::INFO); @@ -204,28 +216,34 @@ int main(int argc, char * argv[]) showHelp(argYAdjust); return 1; } - - // server searching by ssdp - QString address = argAddress.value(parser); - if(address == "127.0.0.1" || address == "127.0.0.1:19444") - { - SSDPDiscover discover; - address = discover.getFirstService(searchType::STY_JSONSERVER); - if(address.isEmpty()) - { - address = argAddress.value(parser); - } - } - - // Resolve hostname and port (or use default port) QString host; - quint16 port{ JSON_DEFAULT_PORT }; + QString serviceName{ QHostInfo::localHostName() }; + int port{ JSONAPI_DEFAULT_PORT }; - if (!NetUtils::resolveHostPort(address, host, port)) + // Split hostname and port (or use default port) + QString givenAddress = argAddress.value(parser); + if (!NetUtils::resolveHostPort(givenAddress, host, port)) { - throw std::runtime_error(QString("Wrong address: unable to parse address (%1)").arg(address).toStdString()); + throw std::runtime_error(QString("Wrong address: unable to parse address (%1)").arg(givenAddress).toStdString()); } + // Search available Hyperion services via mDNS, if default/localhost IP is given + if (host == "127.0.0.1" || host == "::1") + { +#ifndef ENABLE_MDNS + SSDPDiscover discover; + host = discover.getFirstService(searchType::STY_FLATBUFSERVER); +#endif + QHostAddress address; + if (!NetUtils::resolveHostToAddress(log, host, address, port)) + { + throw std::runtime_error(QString("Address could not be resolved for hostname: %2").arg(QSTRING_CSTR(host)).toStdString()); + } + host = address.toString(); + } + + Info(log, "Connecting to Hyperion host: %s, port: %u using service: %s", QSTRING_CSTR(host), port, QSTRING_CSTR(serviceName)); + // create the connection to the hyperion server JsonConnection connection(host, parser.isSet(argPrint), port); diff --git a/src/hyperion-v4l2/CMakeLists.txt b/src/hyperion-v4l2/CMakeLists.txt index c653544f..9c8783dd 100644 --- a/src/hyperion-v4l2/CMakeLists.txt +++ b/src/hyperion-v4l2/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.0.0) +cmake_minimum_required(VERSION 3.1.0) project(hyperion-v4l2) find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Core Network Widgets REQUIRED) @@ -28,12 +28,17 @@ target_link_libraries(${PROJECT_NAME} hyperion-utils flatbufconnect flatbuffers - ssdp Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Network Qt${QT_VERSION_MAJOR}::Widgets ) +if(ENABLE_MDNS) + target_link_libraries(${PROJECT_NAME} mdns) +else() + target_link_libraries(${PROJECT_NAME} ssdp) +endif() + if (ENABLE_AMLOGIC) target_link_libraries(${PROJECT_NAME} pcre16 dl z diff --git a/src/hyperion-v4l2/hyperion-v4l2.cpp b/src/hyperion-v4l2/hyperion-v4l2.cpp index 28f89ead..7a0a09b1 100644 --- a/src/hyperion-v4l2/hyperion-v4l2.cpp +++ b/src/hyperion-v4l2/hyperion-v4l2.cpp @@ -21,12 +21,25 @@ #include "HyperionConfig.h" #include +#ifdef ENABLE_MDNS +// mDNS discover +#include +#include +#else // ssdp discover #include +#endif #include #include +// Constants +namespace { + + const char SERVICE_TYPE[] = "flatbuffer"; + +} //End of constants + using namespace commandline; int main(int argc, char** argv) @@ -231,27 +244,35 @@ int main(int argc, char** argv) } else { - // server searching by ssdp - QString address = argAddress.value(parser); - if(address == "127.0.0.1" || address == "127.0.0.1:19400") - { - SSDPDiscover discover; - address = discover.getFirstService(searchType::STY_FLATBUFSERVER); - if(address.isEmpty()) - { - address = argAddress.value(parser); - } - } - - // Resolve hostname and port (or use default port) QString host; - quint16 port{ FLATBUFFER_DEFAULT_PORT }; + QString serviceName{ QHostInfo::localHostName() }; + int port{ FLATBUFFER_DEFAULT_PORT }; - if (!NetUtils::resolveHostPort(address, host, port)) + // Split hostname and port (or use default port) + QString givenAddress = argAddress.value(parser); + if (!NetUtils::resolveHostPort(givenAddress, host, port)) { - throw std::runtime_error(QString("Wrong address: unable to parse address (%1)").arg(address).toStdString()); + throw std::runtime_error(QString("Wrong address: unable to parse address (%1)").arg(givenAddress).toStdString()); } + // Search available Hyperion services via mDNS, if default/localhost IP is given + if (host == "127.0.0.1" || host == "::1") + { +#ifndef ENABLE_MDNS + SSDPDiscover discover; + host = discover.getFirstService(searchType::STY_FLATBUFSERVER); +#endif + + QHostAddress address; + if (!NetUtils::resolveHostToAddress(log, host, address, port)) + { + throw std::runtime_error(QString("Address could not be resolved for hostname: %2").arg(QSTRING_CSTR(host)).toStdString()); + } + host = address.toString(); + } + + Info(log, "Connecting to Hyperion host: %s, port: %u using service: %s", QSTRING_CSTR(host), port, QSTRING_CSTR(serviceName)); + // Create the Flabuf-connection FlatBufferConnection flatbuf("V4L2 Standalone", host, argPriority.getInt(parser), parser.isSet(argSkipReply), port); diff --git a/src/hyperion-x11/CMakeLists.txt b/src/hyperion-x11/CMakeLists.txt index 59f9db47..a7459ce6 100644 --- a/src/hyperion-x11/CMakeLists.txt +++ b/src/hyperion-x11/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.0.0) +cmake_minimum_required(VERSION 3.1.0) project(hyperion-x11) find_package(X11 REQUIRED) @@ -35,7 +35,6 @@ target_link_libraries(${PROJECT_NAME} flatbufconnect flatbuffers x11-grabber - ssdp ${X11_LIBRARIES} ${X11_Xrandr_LIB} ${X11_Xrender_LIB} @@ -45,6 +44,12 @@ target_link_libraries(${PROJECT_NAME} Qt${QT_VERSION_MAJOR}::Widgets ) +if(ENABLE_MDNS) + target_link_libraries(${PROJECT_NAME} mdns) +else() + target_link_libraries(${PROJECT_NAME} ssdp) +endif() + install ( TARGETS ${PROJECT_NAME} DESTINATION "share/hyperion/bin" COMPONENT "hyperion_x11" ) if(CMAKE_HOST_UNIX) diff --git a/src/hyperion-x11/hyperion-x11.cpp b/src/hyperion-x11/hyperion-x11.cpp index 9f2bbd61..4c386310 100644 --- a/src/hyperion-x11/hyperion-x11.cpp +++ b/src/hyperion-x11/hyperion-x11.cpp @@ -9,10 +9,23 @@ #include "X11Wrapper.h" #include "HyperionConfig.h" +#ifdef ENABLE_MDNS +// mDNS discover +#include +#include +#else // ssdp discover #include +#endif #include +// Constants +namespace { + + const char SERVICE_TYPE[] = "flatbuffer"; + +} //End of constants + using namespace commandline; // save the image as screenshot @@ -111,27 +124,35 @@ int main(int argc, char ** argv) } else { - // server searching by ssdp - QString address = argAddress.value(parser); - if(address == "127.0.0.1" || address == "127.0.0.1:19400") - { - SSDPDiscover discover; - address = discover.getFirstService(searchType::STY_FLATBUFSERVER); - if(address.isEmpty()) - { - address = argAddress.value(parser); - } - } - - // Resolve hostname and port (or use default port) QString host; - quint16 port{ FLATBUFFER_DEFAULT_PORT }; + QString serviceName{ QHostInfo::localHostName() }; + int port{ FLATBUFFER_DEFAULT_PORT }; - if (!NetUtils::resolveHostPort(address, host, port)) + // Split hostname and port (or use default port) + QString givenAddress = argAddress.value(parser); + if (!NetUtils::resolveHostPort(givenAddress, host, port)) { - throw std::runtime_error(QString("Wrong address: unable to parse address (%1)").arg(address).toStdString()); + throw std::runtime_error(QString("Wrong address: unable to parse address (%1)").arg(givenAddress).toStdString()); } + // Search available Hyperion services via mDNS, if default/localhost IP is given + if (host == "127.0.0.1" || host == "::1") + { +#ifndef ENABLE_MDNS + SSDPDiscover discover; + host = discover.getFirstService(searchType::STY_FLATBUFSERVER); +#endif + + QHostAddress address; + if (!NetUtils::resolveHostToAddress(log, host, address, port)) + { + throw std::runtime_error(QString("Address could not be resolved for hostname: %2").arg(QSTRING_CSTR(host)).toStdString()); + } + host = address.toString(); + } + + Info(log, "Connecting to Hyperion host: %s, port: %u using service: %s", QSTRING_CSTR(host), port, QSTRING_CSTR(serviceName)); + // Create the Flabuf-connection FlatBufferConnection flatbuf("X11 Standalone", host, argPriority.getInt(parser), parser.isSet(argSkipReply), port); diff --git a/src/hyperion-xcb/CMakeLists.txt b/src/hyperion-xcb/CMakeLists.txt index cecb8af2..cd374d5a 100644 --- a/src/hyperion-xcb/CMakeLists.txt +++ b/src/hyperion-xcb/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.0.0) +cmake_minimum_required(VERSION 3.1.0) project(hyperion-xcb) find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Core Network Widgets REQUIRED) @@ -28,12 +28,17 @@ target_link_libraries(${PROJECT_NAME} flatbufconnect flatbuffers xcb-grabber - ssdp Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Network Qt${QT_VERSION_MAJOR}::Widgets ) +if(ENABLE_MDNS) + target_link_libraries(${PROJECT_NAME} mdns) +else() + target_link_libraries(${PROJECT_NAME} ssdp) +endif() + install (TARGETS ${PROJECT_NAME} DESTINATION "share/hyperion/bin" COMPONENT "hyperion_xcb") if(CMAKE_HOST_UNIX) diff --git a/src/hyperion-xcb/hyperion-xcb.cpp b/src/hyperion-xcb/hyperion-xcb.cpp index 62915867..79c885c8 100644 --- a/src/hyperion-xcb/hyperion-xcb.cpp +++ b/src/hyperion-xcb/hyperion-xcb.cpp @@ -9,10 +9,23 @@ #include "XcbWrapper.h" #include "HyperionConfig.h" +#ifdef ENABLE_MDNS +// mDNS discover +#include +#include +#else // ssdp discover #include +#endif #include +// Constants +namespace { + + const char SERVICE_TYPE[] = "flatbuffer"; + +} //End of constants + using namespace commandline; // save the image as screenshot @@ -111,27 +124,34 @@ int main(int argc, char ** argv) } else { - // server searching by ssdp - QString address = argAddress.value(parser); - if(address == "127.0.0.1" || address == "127.0.0.1:19400") - { - SSDPDiscover discover; - address = discover.getFirstService(searchType::STY_FLATBUFSERVER); - if(address.isEmpty()) - { - address = argAddress.value(parser); - } - } - - // Resolve hostname and port (or use default port) QString host; - quint16 port{ FLATBUFFER_DEFAULT_PORT }; + QString serviceName{ QHostInfo::localHostName() }; + int port{ FLATBUFFER_DEFAULT_PORT }; - if (!NetUtils::resolveHostPort(address, host, port)) + // Split hostname and port (or use default port) + QString givenAddress = argAddress.value(parser); + if (!NetUtils::resolveHostPort(givenAddress, host, port)) { - throw std::runtime_error(QString("Wrong address: unable to parse address (%1)").arg(address).toStdString()); + throw std::runtime_error(QString("Wrong address: unable to parse address (%1)").arg(givenAddress).toStdString()); } + // Search available Hyperion services via mDNS, if default/localhost IP is given + if (host == "127.0.0.1" || host == "::1") + { +#ifndef ENABLE_MDNS + SSDPDiscover discover; + host = discover.getFirstService(searchType::STY_FLATBUFSERVER); +#endif + QHostAddress address; + if (!NetUtils::resolveHostToAddress(log, host, address, port)) + { + throw std::runtime_error(QString("Address could not be resolved for hostname: %2").arg(QSTRING_CSTR(host)).toStdString()); + } + host = address.toString(); + } + + Info(log, "Connecting to Hyperion host: %s, port: %u using service: %s", QSTRING_CSTR(host), port, QSTRING_CSTR(serviceName)); + // Create the Flabuf-connection FlatBufferConnection flatbuf("XCB Standalone", host, argPriority.getInt(parser), parser.isSet(argSkipReply), port); diff --git a/src/hyperiond/CMakeLists.txt b/src/hyperiond/CMakeLists.txt index b6c83f72..d064c3db 100644 --- a/src/hyperiond/CMakeLists.txt +++ b/src/hyperiond/CMakeLists.txt @@ -78,10 +78,6 @@ if(ENABLE_PROTOBUF_SERVER) target_link_libraries(${PROJECT_NAME} protoserver) endif() -if (ENABLE_AVAHI) - target_link_libraries(${PROJECT_NAME} bonjour) -endif (ENABLE_AVAHI) - if (ENABLE_AMLOGIC) target_link_libraries(${PROJECT_NAME} #Qt${QT_VERSION_MAJOR}::Core @@ -137,18 +133,22 @@ if (ENABLE_CEC) target_link_libraries(${PROJECT_NAME} cechandler) endif (ENABLE_CEC) +if (ENABLE_MDNS) + target_link_libraries(${PROJECT_NAME} mdns) +endif() + if (APPLE) set_target_properties( ${PROJECT_NAME} PROPERTIES MACOSX_BUNDLE TRUE MACOSX_BUNDLE_INFO_PLIST ${CMAKE_SOURCE_DIR}/cmake/osxbundle/Info.plist.in MACOSX_BUNDLE_BUNDLE_NAME "Hyperion" MACOSX_BUNDLE_BUNDLE_VERSION ${HYPERION_VERSION} - MACOSX_BUNDLE_COPYRIGHT "Copyright (c) 2014-2021 Hyperion Project" + MACOSX_BUNDLE_COPYRIGHT "Copyright(c) 2014-2022 Hyperion Project" MACOSX_BUNDLE_GUI_IDENTIFIER "com.hyperion-project.${PROJECT_NAME}" MACOSX_BUNDLE_ICON_FILE "Hyperion.icns" MACOSX_BUNDLE_INFO_STRING "${PROJECT_NAME} ${HYPERION_VERSION}" - MACOSX_BUNDLE_LONG_VERSION_STRING ${HYPERION_VERSION} MACOSX_BUNDLE_SHORT_VERSION_STRING ${HYPERION_VERSION} + MACOSX_BUNDLE_LONG_VERSION_STRING ${HYPERION_VERSION} ) install ( TARGETS ${PROJECT_NAME} DESTINATION . COMPONENT "Hyperion") @@ -195,7 +195,7 @@ if (WIN32) add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD COMMAND ${WINDEPLOYQT_EXECUTABLE} ${WINDEPLOYQT_PARAMS_RUNTIME} "$") endif() - find_package(OpenSSL REQUIRED) + find_package(OpenSSL REQUIRED) if (OPENSSL_FOUND) string(REGEX MATCHALL "[0-9]+" openssl_versions "${OPENSSL_VERSION}") list(GET openssl_versions 0 openssl_version_major) diff --git a/src/hyperiond/hyperiond.cpp b/src/hyperiond/hyperiond.cpp index ddc235f7..0cf0eb9c 100644 --- a/src/hyperiond/hyperiond.cpp +++ b/src/hyperiond/hyperiond.cpp @@ -20,10 +20,12 @@ #include // Required to determine the cmake options -// bonjour browser -#ifdef ENABLE_AVAHI -#include +// mDNS +#ifdef ENABLE_MDNS +#include +#include #endif + #include #include #include "hyperiond.h" @@ -65,36 +67,36 @@ #include #endif -HyperionDaemon *HyperionDaemon::daemon = nullptr; +HyperionDaemon* HyperionDaemon::daemon = nullptr; HyperionDaemon::HyperionDaemon(const QString& rootPath, QObject* parent, bool logLvlOverwrite, bool readonlyMode) : QObject(parent), _log(Logger::getInstance("DAEMON")) - , _instanceManager(new HyperionIManager(rootPath, this, readonlyMode)) - , _authManager(new AuthManager(this, readonlyMode)) -#ifdef ENABLE_AVAHI - , _bonjourBrowserWrapper(new BonjourBrowserWrapper()) + , _instanceManager(new HyperionIManager(rootPath, this, readonlyMode)) + , _authManager(new AuthManager(this, readonlyMode)) +#ifdef ENABLE_MDNS + , _mDNSProvider(nullptr) #endif - , _netOrigin(new NetOrigin(this)) + , _netOrigin(new NetOrigin(this)) #if defined(ENABLE_EFFECTENGINE) - , _pyInit(new PythonInit()) + , _pyInit(new PythonInit()) #endif - , _webserver(nullptr) - , _sslWebserver(nullptr) - , _jsonServer(nullptr) - , _videoGrabber(nullptr) - , _dispmanx(nullptr) - , _x11Grabber(nullptr) - , _xcbGrabber(nullptr) - , _amlGrabber(nullptr) - , _fbGrabber(nullptr) - , _osxGrabber(nullptr) - , _qtGrabber(nullptr) - , _dxGrabber(nullptr) - , _ssdp(nullptr) + , _webserver(nullptr) + , _sslWebserver(nullptr) + , _jsonServer(nullptr) + , _videoGrabber(nullptr) + , _dispmanx(nullptr) + , _x11Grabber(nullptr) + , _xcbGrabber(nullptr) + , _amlGrabber(nullptr) + , _fbGrabber(nullptr) + , _osxGrabber(nullptr) + , _qtGrabber(nullptr) + , _dxGrabber(nullptr) + , _ssdp(nullptr) #ifdef ENABLE_CEC - , _cecHandler(nullptr) + , _cecHandler(nullptr) #endif - , _currVideoMode(VideoMode::VIDEO_2D) + , _currVideoMode(VideoMode::VIDEO_2D) { HyperionDaemon::daemon = this; @@ -117,6 +119,11 @@ HyperionDaemon::HyperionDaemon(const QString& rootPath, QObject* parent, bool lo createCecHandler(); + //Create MdnsBrowser singleton in main tread to ensure thread affinity during destruction +#ifdef ENABLE_MDNS + MdnsBrowser::getInstance(); +#endif + #if defined(ENABLE_EFFECTENGINE) // init EffectFileHandler EffectFileHandler* efh = new EffectFileHandler(rootPath, getSetting(settings::EFFECTS), this); @@ -135,7 +142,10 @@ HyperionDaemon::HyperionDaemon(const QString& rootPath, QObject* parent, bool lo _instanceManager->startAll(); //Cleaning up Hyperion before quit - connect(parent, SIGNAL(aboutToQuit()), this, SLOT(freeObjects())); + connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, &HyperionDaemon::freeObjects); + + //Handle services dependent on the on first instance's availability + connect(_instanceManager, &HyperionIManager::instanceStateChanged, this, &HyperionDaemon::handleInstanceStateChange); // pipe settings changes from HyperionIManager to Daemon connect(_instanceManager, &HyperionIManager::settingsChanged, this, &HyperionDaemon::settingsChanged); @@ -171,6 +181,62 @@ HyperionDaemon::~HyperionDaemon() #endif } +void HyperionDaemon::handleInstanceStateChange(InstanceState state, quint8 instance) +{ + switch (state) + { + case InstanceState::H_STARTED: + + if (instance == 0) + { + // Start Json server in own thread + _jsonServer = new JsonServer(getSetting(settings::JSONSERVER)); + QThread* jsonThread = new QThread(this); + jsonThread->setObjectName("JSONServerThread"); + _jsonServer->moveToThread(jsonThread); + connect(jsonThread, &QThread::started, _jsonServer, &JsonServer::initServer); + connect(jsonThread, &QThread::finished, _jsonServer, &JsonServer::deleteLater); + connect(this, &HyperionDaemon::settingsChanged, _jsonServer, &JsonServer::handleSettingsUpdate); +#ifdef ENABLE_MDNS + connect(_jsonServer, &JsonServer::publishService, _mDNSProvider, &MdnsProvider::publishService); +#endif + jsonThread->start(); + + // Start Webserver in own thread + QThread* wsThread = new QThread(this); + wsThread->setObjectName("WebServerThread"); + _webserver->moveToThread(wsThread); + connect(wsThread, &QThread::started, _webserver, &WebServer::initServer); + connect(wsThread, &QThread::finished, _webserver, &WebServer::deleteLater); + connect(this, &HyperionDaemon::settingsChanged, _webserver, &WebServer::handleSettingsUpdate); +#ifdef ENABLE_MDNS + connect(_webserver, &WebServer::publishService, _mDNSProvider, &MdnsProvider::publishService); +#endif + wsThread->start(); + + // Start SSL Webserver in own thread + _sslWebserver = new WebServer(getSetting(settings::WEBSERVER), true); + QThread* sslWsThread = new QThread(this); + sslWsThread->setObjectName("SSLWebServerThread"); + _sslWebserver->moveToThread(sslWsThread); + connect(sslWsThread, &QThread::started, _sslWebserver, &WebServer::initServer); + connect(sslWsThread, &QThread::finished, _sslWebserver, &WebServer::deleteLater); + connect(this, &HyperionDaemon::settingsChanged, _sslWebserver, &WebServer::handleSettingsUpdate); +#ifdef ENABLE_MDNS + connect(_sslWebserver, &WebServer::publishService, _mDNSProvider, &MdnsProvider::publishService); +#endif + sslWsThread->start(); + } + break; + + case InstanceState::H_STOPPED: + break; + + default: + break; + } +} + void HyperionDaemon::setVideoMode(VideoMode mode) { if (_currVideoMode != mode) @@ -189,9 +255,25 @@ void HyperionDaemon::freeObjects() { Debug(_log, "Cleaning up Hyperion before quit."); - // destroy network first as a client might want to access hyperion - delete _jsonServer; - _jsonServer = nullptr; +#ifdef ENABLE_MDNS + if (_mDNSProvider != nullptr) + { + auto mDnsThread = _mDNSProvider->thread(); + mDnsThread->quit(); + mDnsThread->wait(); + delete mDnsThread; + _mDNSProvider = nullptr; + } +#endif + + if (_jsonServer != nullptr) + { + auto jsonThread = _jsonServer->thread(); + jsonThread->quit(); + jsonThread->wait(); + delete jsonThread; + _jsonServer = nullptr; + } #if defined(ENABLE_FLATBUF_SERVER) if (_flatBufferServer != nullptr) @@ -258,11 +340,6 @@ void HyperionDaemon::freeObjects() // stop Hyperions (non blocking) _instanceManager->stopAll(); -#ifdef ENABLE_AVAHI - delete _bonjourBrowserWrapper; - _bonjourBrowserWrapper = nullptr; -#endif - delete _amlGrabber; if (_dispmanx != nullptr) delete _dispmanx; @@ -273,19 +350,26 @@ void HyperionDaemon::freeObjects() delete _videoGrabber; _videoGrabber = nullptr; - _amlGrabber = nullptr; - _dispmanx = nullptr; - _fbGrabber = nullptr; - _osxGrabber = nullptr; - _qtGrabber = nullptr; - _dxGrabber = nullptr; + _amlGrabber = nullptr; + _dispmanx = nullptr; + _fbGrabber = nullptr; + _osxGrabber = nullptr; + _qtGrabber = nullptr; + _dxGrabber = nullptr; } void HyperionDaemon::startNetworkServices() { - // Create Json server - _jsonServer = new JsonServer(getSetting(settings::JSONSERVER)); - connect(this, &HyperionDaemon::settingsChanged, _jsonServer, &JsonServer::handleSettingsUpdate); + // Create mDNS-Provider in thread to allow publishing other services +#ifdef ENABLE_MDNS + _mDNSProvider = new MdnsProvider(); + QThread* mDnsThread = new QThread(this); + mDnsThread->setObjectName("mDNSProviderThread"); + _mDNSProvider->moveToThread(mDnsThread); + connect(mDnsThread, &QThread::started, _mDNSProvider, &MdnsProvider::init); + connect(mDnsThread, &QThread::finished, _mDNSProvider, &MdnsProvider::deleteLater); + mDnsThread->start(); +#endif #if defined(ENABLE_FLATBUF_SERVER) // Create FlatBuffer server in thread @@ -296,6 +380,9 @@ void HyperionDaemon::startNetworkServices() connect(fbThread, &QThread::started, _flatBufferServer, &FlatBufferServer::initServer); connect(fbThread, &QThread::finished, _flatBufferServer, &FlatBufferServer::deleteLater); connect(this, &HyperionDaemon::settingsChanged, _flatBufferServer, &FlatBufferServer::handleSettingsUpdate); +#ifdef ENABLE_MDNS + connect(_flatBufferServer, &FlatBufferServer::publishService, _mDNSProvider, &MdnsProvider::publishService); +#endif fbThread->start(); #endif @@ -308,36 +395,22 @@ void HyperionDaemon::startNetworkServices() connect(pThread, &QThread::started, _protoServer, &ProtoServer::initServer); connect(pThread, &QThread::finished, _protoServer, &ProtoServer::deleteLater); connect(this, &HyperionDaemon::settingsChanged, _protoServer, &ProtoServer::handleSettingsUpdate); +#ifdef ENABLE_MDNS + connect(_protoServer, &ProtoServer::publishService, _mDNSProvider, &MdnsProvider::publishService); +#endif pThread->start(); #endif - // Create Webserver in thread + // Create Webserver _webserver = new WebServer(getSetting(settings::WEBSERVER), false); - QThread* wsThread = new QThread(this); - wsThread->setObjectName("WebServerThread"); - _webserver->moveToThread(wsThread); - connect(wsThread, &QThread::started, _webserver, &WebServer::initServer); - connect(wsThread, &QThread::finished, _webserver, &WebServer::deleteLater); - connect(this, &HyperionDaemon::settingsChanged, _webserver, &WebServer::handleSettingsUpdate); - wsThread->start(); - // Create SSL Webserver in thread - _sslWebserver = new WebServer(getSetting(settings::WEBSERVER), true); - QThread* sslWsThread = new QThread(this); - sslWsThread->setObjectName("SSLWebServerThread"); - _sslWebserver->moveToThread(sslWsThread); - connect(sslWsThread, &QThread::started, _sslWebserver, &WebServer::initServer); - connect(sslWsThread, &QThread::finished, _sslWebserver, &WebServer::deleteLater); - connect(this, &HyperionDaemon::settingsChanged, _sslWebserver, &WebServer::handleSettingsUpdate); - sslWsThread->start(); - - // Create SSDP server in thread + // Create SSDP server _ssdp = new SSDPHandler(_webserver, - getSetting(settings::FLATBUFSERVER).object()["port"].toInt(), - getSetting(settings::PROTOSERVER).object()["port"].toInt(), - getSetting(settings::JSONSERVER).object()["port"].toInt(), - getSetting(settings::WEBSERVER).object()["sslPort"].toInt(), - getSetting(settings::GENERAL).object()["name"].toString()); + getSetting(settings::FLATBUFSERVER).object()["port"].toInt(), + getSetting(settings::PROTOSERVER).object()["port"].toInt(), + getSetting(settings::JSONSERVER).object()["port"].toInt(), + getSetting(settings::WEBSERVER).object()["sslPort"].toInt(), + getSetting(settings::GENERAL).object()["name"].toString()); QThread* ssdpThread = new QThread(this); ssdpThread->setObjectName("SSDPThread"); _ssdp->moveToThread(ssdpThread); @@ -387,11 +460,11 @@ void HyperionDaemon::handleSettingsUpdate(settings::type settingsType, const QJs _grabber_cropTop = grabberConfig["cropTop"].toInt(0); _grabber_cropBottom = grabberConfig["cropBottom"].toInt(0); - #ifdef ENABLE_OSX +#ifdef ENABLE_OSX QString type = grabberConfig["device"].toString("osx"); - #else +#else QString type = grabberConfig["device"].toString("auto"); - #endif +#endif // auto eval of type if (type == "auto") @@ -408,7 +481,7 @@ void HyperionDaemon::handleSettingsUpdate(settings::type settingsType, const QJs { type = "amlogic"; - QString amlDevice ("/dev/amvideocap0"); + QString amlDevice("/dev/amvideocap0"); if (!QFile::exists(amlDevice)) { Error(_log, "grabber device '%s' for type amlogic not found!", QSTRING_CSTR(amlDevice)); @@ -420,13 +493,13 @@ void HyperionDaemon::handleSettingsUpdate(settings::type settingsType, const QJs QByteArray envDisplay = qgetenv("DISPLAY"); if (!envDisplay.isEmpty()) { - #if defined(ENABLE_X11) +#if defined(ENABLE_X11) type = "x11"; - #elif defined(ENABLE_XCB) +#elif defined(ENABLE_XCB) type = "xcb"; - #else +#else type = "qt"; - #endif +#endif } // qt -> if nothing other applies else @@ -440,70 +513,70 @@ void HyperionDaemon::handleSettingsUpdate(settings::type settingsType, const QJs if (_prevType != type) { // stop all capture interfaces - #ifdef ENABLE_FB +#ifdef ENABLE_FB if (_fbGrabber != nullptr) { _fbGrabber->stop(); delete _fbGrabber; _fbGrabber = nullptr; } - #endif - #ifdef ENABLE_DISPMANX +#endif +#ifdef ENABLE_DISPMANX if (_dispmanx != nullptr) { _dispmanx->stop(); delete _dispmanx; _dispmanx = nullptr; } - #endif - #ifdef ENABLE_AMLOGIC +#endif +#ifdef ENABLE_AMLOGIC if (_amlGrabber != nullptr) { _amlGrabber->stop(); delete _amlGrabber; _amlGrabber = nullptr; } - #endif - #ifdef ENABLE_OSX +#endif +#ifdef ENABLE_OSX if (_osxGrabber != nullptr) { _osxGrabber->stop(); delete _osxGrabber; _osxGrabber = nullptr; } - #endif - #ifdef ENABLE_X11 +#endif +#ifdef ENABLE_X11 if (_x11Grabber != nullptr) { _x11Grabber->stop(); delete _x11Grabber; _x11Grabber = nullptr; } - #endif - #ifdef ENABLE_XCB +#endif +#ifdef ENABLE_XCB if (_xcbGrabber != nullptr) { _xcbGrabber->stop(); delete _xcbGrabber; _xcbGrabber = nullptr; } - #endif - #ifdef ENABLE_QT +#endif +#ifdef ENABLE_QT if (_qtGrabber != nullptr) { _qtGrabber->stop(); delete _qtGrabber; _qtGrabber = nullptr; } - #endif - #ifdef ENABLE_DX +#endif +#ifdef ENABLE_DX if (_dxGrabber != nullptr) { _dxGrabber->stop(); delete _dxGrabber; _dxGrabber = nullptr; } - #endif +#endif // create/start capture interface if (type == "framebuffer") @@ -512,10 +585,10 @@ void HyperionDaemon::handleSettingsUpdate(settings::type settingsType, const QJs { createGrabberFramebuffer(grabberConfig); } - #ifdef ENABLE_FB - _fbGrabber->handleSettingsUpdate(settings::SYSTEMCAPTURE, getSetting(settings::SYSTEMCAPTURE)); - _fbGrabber->tryStart(); - #endif +#ifdef ENABLE_FB + _fbGrabber->handleSettingsUpdate(settings::SYSTEMCAPTURE, getSetting(settings::SYSTEMCAPTURE)); + _fbGrabber->tryStart(); +#endif } else if (type == "dispmanx") { @@ -524,13 +597,13 @@ void HyperionDaemon::handleSettingsUpdate(settings::type settingsType, const QJs createGrabberDispmanx(grabberConfig); } - #ifdef ENABLE_DISPMANX +#ifdef ENABLE_DISPMANX if (_dispmanx != nullptr) { _dispmanx->handleSettingsUpdate(settings::SYSTEMCAPTURE, getSetting(settings::SYSTEMCAPTURE)); _dispmanx->tryStart(); } - #endif +#endif } else if (type == "amlogic") { @@ -538,10 +611,10 @@ void HyperionDaemon::handleSettingsUpdate(settings::type settingsType, const QJs { createGrabberAmlogic(grabberConfig); } - #ifdef ENABLE_AMLOGIC - _amlGrabber->handleSettingsUpdate(settings::SYSTEMCAPTURE, getSetting(settings::SYSTEMCAPTURE)); - _amlGrabber->tryStart(); - #endif +#ifdef ENABLE_AMLOGIC + _amlGrabber->handleSettingsUpdate(settings::SYSTEMCAPTURE, getSetting(settings::SYSTEMCAPTURE)); + _amlGrabber->tryStart(); +#endif } else if (type == "osx") { @@ -549,10 +622,10 @@ void HyperionDaemon::handleSettingsUpdate(settings::type settingsType, const QJs { createGrabberOsx(grabberConfig); } - #ifdef ENABLE_OSX - _osxGrabber->handleSettingsUpdate(settings::SYSTEMCAPTURE, getSetting(settings::SYSTEMCAPTURE)); - _osxGrabber->tryStart(); - #endif +#ifdef ENABLE_OSX + _osxGrabber->handleSettingsUpdate(settings::SYSTEMCAPTURE, getSetting(settings::SYSTEMCAPTURE)); + _osxGrabber->tryStart(); +#endif } else if (type == "x11") { @@ -560,10 +633,10 @@ void HyperionDaemon::handleSettingsUpdate(settings::type settingsType, const QJs { createGrabberX11(grabberConfig); } - #ifdef ENABLE_X11 - _x11Grabber->handleSettingsUpdate(settings::SYSTEMCAPTURE, getSetting(settings::SYSTEMCAPTURE)); - _x11Grabber->tryStart(); - #endif +#ifdef ENABLE_X11 + _x11Grabber->handleSettingsUpdate(settings::SYSTEMCAPTURE, getSetting(settings::SYSTEMCAPTURE)); + _x11Grabber->tryStart(); +#endif } else if (type == "xcb") { @@ -571,10 +644,10 @@ void HyperionDaemon::handleSettingsUpdate(settings::type settingsType, const QJs { createGrabberXcb(grabberConfig); } - #ifdef ENABLE_XCB - _xcbGrabber->handleSettingsUpdate(settings::SYSTEMCAPTURE, getSetting(settings::SYSTEMCAPTURE)); - _xcbGrabber->tryStart(); - #endif +#ifdef ENABLE_XCB + _xcbGrabber->handleSettingsUpdate(settings::SYSTEMCAPTURE, getSetting(settings::SYSTEMCAPTURE)); + _xcbGrabber->tryStart(); +#endif } else if (type == "qt") { @@ -582,10 +655,10 @@ void HyperionDaemon::handleSettingsUpdate(settings::type settingsType, const QJs { createGrabberQt(grabberConfig); } - #ifdef ENABLE_QT - _qtGrabber->handleSettingsUpdate(settings::SYSTEMCAPTURE, getSetting(settings::SYSTEMCAPTURE)); - _qtGrabber->tryStart(); - #endif +#ifdef ENABLE_QT + _qtGrabber->handleSettingsUpdate(settings::SYSTEMCAPTURE, getSetting(settings::SYSTEMCAPTURE)); + _qtGrabber->tryStart(); +#endif } else if (type == "dx") { @@ -593,10 +666,10 @@ void HyperionDaemon::handleSettingsUpdate(settings::type settingsType, const QJs { createGrabberDx(grabberConfig); } - #ifdef ENABLE_DX - _dxGrabber->handleSettingsUpdate(settings::SYSTEMCAPTURE, getSetting(settings::SYSTEMCAPTURE)); - _dxGrabber->tryStart(); - #endif +#ifdef ENABLE_DX + _dxGrabber->handleSettingsUpdate(settings::SYSTEMCAPTURE, getSetting(settings::SYSTEMCAPTURE)); + _dxGrabber->tryStart(); +#endif } else { @@ -677,7 +750,7 @@ void HyperionDaemon::createGrabberAmlogic(const QJsonObject& /*grabberConfig*/) _amlGrabber = new AmlogicWrapper( _grabber_frequency, _grabber_pixelDecimation - ); + ); _amlGrabber->setCropping(_grabber_cropLeft, _grabber_cropRight, _grabber_cropTop, _grabber_cropBottom); // connect to HyperionDaemon signal @@ -697,7 +770,7 @@ void HyperionDaemon::createGrabberX11(const QJsonObject& /*grabberConfig*/) _grabber_frequency, _grabber_pixelDecimation, _grabber_cropLeft, _grabber_cropRight, _grabber_cropTop, _grabber_cropBottom - ); + ); _x11Grabber->setCropping(_grabber_cropLeft, _grabber_cropRight, _grabber_cropTop, _grabber_cropBottom); // connect to HyperionDaemon signal @@ -717,7 +790,7 @@ void HyperionDaemon::createGrabberXcb(const QJsonObject& /*grabberConfig*/) _grabber_frequency, _grabber_pixelDecimation, _grabber_cropLeft, _grabber_cropRight, _grabber_cropTop, _grabber_cropBottom - ); + ); _xcbGrabber->setCropping(_grabber_cropLeft, _grabber_cropRight, _grabber_cropTop, _grabber_cropBottom); // connect to HyperionDaemon signal @@ -738,7 +811,7 @@ void HyperionDaemon::createGrabberQt(const QJsonObject& grabberConfig) grabberConfig["input"].toInt(0), _grabber_pixelDecimation, _grabber_cropLeft, _grabber_cropRight, _grabber_cropTop, _grabber_cropBottom - ); + ); // connect to HyperionDaemon signal connect(this, &HyperionDaemon::videoMode, _qtGrabber, &QtWrapper::setVideoMode); @@ -758,7 +831,7 @@ void HyperionDaemon::createGrabberDx(const QJsonObject& grabberConfig) grabberConfig["display"].toInt(0), _grabber_pixelDecimation, _grabber_cropLeft, _grabber_cropRight, _grabber_cropTop, _grabber_cropBottom - ); + ); // connect to HyperionDaemon signal connect(this, &HyperionDaemon::videoMode, _dxGrabber, &DirectXWrapper::setVideoMode); @@ -781,7 +854,7 @@ void HyperionDaemon::createGrabberFramebuffer(const QJsonObject& grabberConfig) _grabber_frequency, devicePath, _grabber_pixelDecimation - ); + ); _fbGrabber->setCropping(_grabber_cropLeft, _grabber_cropRight, _grabber_cropTop, _grabber_cropBottom); // connect to HyperionDaemon signal connect(this, &HyperionDaemon::videoMode, _fbGrabber, &FramebufferWrapper::setVideoMode); @@ -793,7 +866,7 @@ void HyperionDaemon::createGrabberFramebuffer(const QJsonObject& grabberConfig) #endif } - void HyperionDaemon::createGrabberOsx(const QJsonObject& grabberConfig) +void HyperionDaemon::createGrabberOsx(const QJsonObject& grabberConfig) { #ifdef ENABLE_OSX // Construct and start the osx grabber if the configuration is present @@ -801,7 +874,7 @@ void HyperionDaemon::createGrabberFramebuffer(const QJsonObject& grabberConfig) _grabber_frequency, grabberConfig["input"].toInt(0), _grabber_pixelDecimation - ); + ); _osxGrabber->setCropping(_grabber_cropLeft, _grabber_cropRight, _grabber_cropTop, _grabber_cropBottom); // connect to HyperionDaemon signal @@ -829,10 +902,11 @@ void HyperionDaemon::createCecHandler() { _videoGrabber->handleCecEvent(event); } - }); + }); Info(_log, "CEC handler created"); #else Debug(_log, "The CEC handler is not supported on this platform"); #endif } + diff --git a/src/hyperiond/hyperiond.h b/src/hyperiond/hyperiond.h index f3d8f5e4..8bc28114 100644 --- a/src/hyperiond/hyperiond.h +++ b/src/hyperiond/hyperiond.h @@ -4,6 +4,8 @@ #include #include +#include + #ifdef ENABLE_DISPMANX #include #else @@ -70,7 +72,9 @@ class HyperionIManager; class SysTray; class JsonServer; -class BonjourBrowserWrapper; +#ifdef ENABLE_MDNS +class MdnsProvider; +#endif class WebServer; class SettingsManager; #if defined(ENABLE_EFFECTENGINE) @@ -154,6 +158,12 @@ private slots: /// void setVideoMode(VideoMode mode); + /// @brief Handle whenever the state of a instance (HyperionIManager) changes according to enum instanceState + /// @param instaneState A state from enum + /// @param instance The index of instance + /// + void handleInstanceStateChange(InstanceState state, quint8 instance); + private: void createGrabberDispmanx(const QJsonObject & grabberConfig); void createGrabberAmlogic(const QJsonObject & grabberConfig); @@ -168,7 +178,9 @@ private: Logger* _log; HyperionIManager* _instanceManager; AuthManager* _authManager; - BonjourBrowserWrapper* _bonjourBrowserWrapper; +#ifdef ENABLE_MDNS + MdnsProvider* _mDNSProvider; +#endif NetOrigin* _netOrigin; #if defined(ENABLE_EFFECTENGINE) PythonInit* _pyInit; diff --git a/src/hyperiond/main.cpp b/src/hyperiond/main.cpp index d9a57e48..1842ec8e 100644 --- a/src/hyperiond/main.cpp +++ b/src/hyperiond/main.cpp @@ -130,9 +130,6 @@ QCoreApplication* createApplication(int &argc, char *argv[]) int main(int argc, char** argv) { -#ifndef _WIN32 - setenv("AVAHI_COMPAT_NOWARN", "1", 1); -#endif // initialize main logger and set global log level Logger *log = Logger::getInstance("MAIN"); Logger::setLogLevel(Logger::WARNING); diff --git a/src/hyperiond/systray.h b/src/hyperiond/systray.h index 737a1286..93ceae86 100644 --- a/src/hyperiond/systray.h +++ b/src/hyperiond/systray.h @@ -1,5 +1,9 @@ #pragma once +#ifdef Status + #undef Status +#endif + #include #include #include