mDNS Support (#1452)

* Allow build, if no grabbers are enabled

* Align available functions to right Qt version

* Update to next development version

* Align available functions to right Qt version

* fix workflows (apt/nightly)

* Disable QNetworkConfigurationManager deprecation warnings

* Initial go on Smart Pointers

* Add Deallocation

* Correct QT_WARNING_DISABLE_DEPRECATED (available since 5.9)

* Cluster Build Variables

* Hyperion Light

* Address build warnings

* Hyperion Light - UI

* Update Protobuf to latest master

* Removed compiler warnings

* Added restart ability to systray

* Correct Protobuf

* Ignore 'no-return' warning on protobuf build

* hyperion-remote: Fix auto discovery of hyperion server

* Fix Qt version override

* Update changelog

* Remove Grabber Components, if no Grabber exists

* Standalone Grabber - Fix fps default

* Remote Control - Have Source Selction accrosswhole screen

* Enable Blackborder detection only, if relevant input sources available

* Enable Blackborder detection only, if relevant input sources available

* Remote UI - rearrange containers

* Checkout

* Fix compilation on windows

* Re-added qmdnsengine template cmake

* chrono added for linux

* Removed existing AVAHI/Bonjour, allow to enable/disable mDNS

* hyperiond macos typo fix

* Fix macOS Bundle build

* Fix macOS bundle info details

* Correct CMake files

* Removed existing AVAHI/Bonjour (2)

* Share hyperion's services via mDNS

* Add mDNS Browser and mDNS for LED-Devices

* Support mDNS discovery for standalone grabbers

* Remove ZLib Dependency & Cleanup

* mDNS - hanle 2.local2 an ".local." domains equally

* Hue - Link discovery to bridge class, workaround port 443 for mDNS discovery

* Fix save button state when switching between devices

* Removed sessions (of other hyperions)

* mDNS Publisher - Simplify service naming

* mDNS refactoring & Forwarder discovery

* mDNS Updates to use device service name

* Consistency of standalone grabbers with mDNS Service Registry

* Merge branch 'hyperion-project:master' into mDNS

* Start JSON and WebServers only after Instance 0 is available

* Remove bespoke qDebug Output again

* MDNS updates and refactor Forwarder

* Minor updates

* Upgrade to CMake 3.1

* typo

* macOS fix

* Correct merge

* - Remove dynamic linker flag from standalone dispmanX Grabber
- Added ability to use system qmdns libs

* Cec handler library will load at runtime

* typo fix

* protobuf changes

* mDNS changes for Windows/macOS

* test window build qmdnsengine

* absolute path to protobuf cmake dir

* Rework Hue Wizard supporting mDNS

* LED-Devices - Retry support + Refactoring (excl. Hue)

* LED-Devices - Refactoring/Retry support Hue + additional alignments

* Address LGTM findings

* Fix CI-Build, revert test changes

* Build Windows in Release mode to avoid python problem

* Correct that WebServerObject is available earlier

* Ensure that instance name in logs for one instance are presented

* Update content LEDs

* Rework mDNS Address lookup

* Fix LED UI

* Fix for non mDNS Services (ignore default port)

* Disbale device when now input is available

* Revert back some updates, ensure last color is updated when switched on

* Handle reopening case and changed IP, port for API-calls

* Add UPD-DDP Device

* WLED support for DDP

* Fix printout

* LEDDevice - Allow more retries, udapte defaults

* LED-Net Devices - Select Custom device, if configured

Co-authored-by: Paulchen Panther <16664240+Paulchen-Panther@users.noreply.github.com>
Co-authored-by: Paulchen Panther <Paulchen-Panter@protonmail.com>
This commit is contained in:
LordGrey 2022-05-01 19:42:47 +02:00 committed by GitHub
parent 3ef4ebc1a4
commit e9936e131b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
148 changed files with 5885 additions and 4459 deletions

View File

@ -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

3
.gitmodules vendored
View File

@ -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

View File

@ -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"
]

View File

@ -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.

View File

@ -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)

View File

@ -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" }
]
}

View File

@ -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

View File

@ -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",

View File

@ -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) {
}
};

View File

@ -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();
}

View File

@ -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]) {

View File

@ -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;
}
};
});

View File

@ -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));
}

View File

@ -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<availAccess.length; lcx++)
{
$('#id_select').append(createSelOpt(availAccess[lcx], $.i18n('general_access_'+availAccess[lcx])));
for (var lcx = 0; lcx < availAccess.length; lcx++) {
$('#id_select').append(createSelOpt(availAccess[lcx], $.i18n('general_access_' + availAccess[lcx])));
}
$('#id_select').val(storedAccess);
$('#id_select').off().on('change',function() {
$('#id_select').off().on('change', function () {
newAccess = $('#id_select').val();
if (newAccess == storedAccess)
$('#id_btn_saveset').prop('disabled', true);
@ -63,7 +62,7 @@ $(document).ready(function () {
$('#id_btn_saveset').prop('disabled', false);
});
$('#id_btn_saveset').off().on('click',function() {
$('#id_btn_saveset').off().on('click', function () {
setStorage("accesslevel", newAccess);
reload();
});
@ -72,13 +71,13 @@ $(document).ready(function () {
});
// change pw btn
$('#btn_changePassword').off().on('click',function() {
$('#btn_changePassword').off().on('click', function () {
changePassword();
});
//Lock Ui
$('#btn_lock_ui').off().on('click',function() {
removeStorage('loginToken', true);
$('#btn_lock_ui').off().on('click', function () {
removeStorage('loginToken');
location.replace('/');
});
@ -86,27 +85,5 @@ $(document).ready(function () {
if (storedAccess != 'expert')
$('#load_webconfig').toggle(false);
// instance switcher
$('#btn_instanceswitch').off().on('click',function() {
var lsys = window.sysInfo.system.hostName+':'+window.serverConfig.webConfig.port;
showInfoDialog('iswitch', $.i18n('InfoDialog_iswitch_title'), $.i18n('InfoDialog_iswitch_text'));
for (var i = 0; i<window.wSess.length; i++)
{
if(lsys != window.wSess[i].host+':'+window.wSess[i].port)
{
var hyperionAddress = window.wSess[i].address;
if(hyperionAddress.indexOf(':') > -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();
});
});
});

View File

@ -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('<select id="id_select" class="form-control" style="margin-top:10px;width:auto;"></select>');
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));
}

View File

@ -57,7 +57,7 @@ function startWizardRGB() {
$('#wizp2_body').append('<table class="table borderless" style="width:200px"><tbody><tr><td class="ltd"><label>' + $.i18n('wiz_rgb_qrend') + '</label></td><td class="itd"><select id="wiz_r_select" class="form-control wselect"></select></td></tr><tr><td class="ltd"><label>' + $.i18n('wiz_rgb_qgend') + '</label></td><td class="itd"><select id="wiz_g_select" class="form-control wselect"></select></td></tr></tbody></table>');
$('#wizp2_footer').html('<button type="button" class="btn btn-primary" id="btn_wiz_save"><i class="fa fa-fw fa-save"></i>' + $.i18n('general_btn_save') + '</button><button type="button" class="btn btn-primary" id="btn_wiz_checkok" style="display:none" data-dismiss="modal"><i class="fa fa-fw fa-check"></i>' + $.i18n('general_btn_ok') + '</button><button type="button" class="btn btn-danger" id="btn_wiz_abort"><i class="fa fa-fw fa-close"></i>' + $.i18n('general_btn_cancel') + '</button>');
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() {
'<button type="button" class="btn btn-danger" id="btn_wiz_abort"><i class="fa fa-fw fa-close"></i>' + $.i18n('general_btn_cancel') + '</button>'
);
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('<button type="button" class="btn btn-primary" id="btn_wiz_save" style="display:none"><i class="fa fa-fw fa-save"></i>' + $.i18n('general_btn_save') + '</button><button type="button" class="btn btn-danger" id="btn_wiz_abort"><i class="fa fa-fw fa-close"></i>' + $.i18n('general_btn_cancel') + '</button>');
$('#wizp3_body').html('<span>' + $.i18n('wiz_hue_press_link') + '</span> <br /><br /><center><span id="connectionTime"></span><br /><i class="fa fa-cog fa-spin" style="font-size:100px"></i></center>');
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 + ')', '<button class="btn btn-sm btn-primary" onClick=useGroupId(' + groupid + ')>' + $.i18n('wiz_hue_e_use_groupid', groupid) + '</button>']));
for (var groupid in hueGroups) {
if (hueGroups[groupid].type == 'Entertainment') {
$('.gidsb').append(createTableRow([groupid + ' (' + hueGroups[groupid].name + ')', '<button class="btn btn-sm btn-primary" onClick=useGroupId(' + groupid + ')>' + $.i18n('wiz_hue_e_use_groupid', groupid) + '</button>']));
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) + '</option>';
}
$('.lidsb').append(createTableRow([lightid + ' (' + r[lightid].name + ')', '<select id="hue_' + lightid + '" class="hue_sel_watch form-control">'
$('.lidsb').append(createTableRow([lightid + ' (' + hueLights[lightid].name + ')', '<select id="hue_' + lightid + '" class="hue_sel_watch form-control">'
+ options
+ '</select>', '<button class="btn btn-sm btn-primary" onClick=identify_hue_device("' + $("#host").val() + '","' + $("#port").val() + '","' + $("#user").val() + '",' + lightid + ')>' + $.i18n('wiz_hue_blinkblue', lightid) + '</button>']));
+ '</select>', '<button class="btn btn-sm btn-primary" onClick=identify_hue_device("' + encodeURIComponent($("#host").val()) + '","' + $('#port').val() + '","' + $("#user").val() + '",' + lightid + ')>' + $.i18n('wiz_hue_blinkblue', lightid) + '</button>']));
}
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 = '<p style="font-weight:bold;color:red;">' + $.i18n('wiz_hue_noids') + '</p>';
$('#wizp2_body').append(txt);
}
})
}
}
}
function abortConnection(UserInterval) {
@ -1386,7 +1385,7 @@ function startWizardYeelight(e) {
+ $.i18n('general_btn_save') + '</button><buttowindow.serverConfig.device = d;n type="button" class="btn btn-danger" id="btn_wiz_abort"><i class="fa fa-fw fa-close"></i>'
+ $.i18n('general_btn_cancel') + '</button>');
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 = '<option value=disabled>' + $.i18n('wiz_yeelight_unsupported') + '</option>';
}
$('.lidsb').append(createTableRow([(parseInt(lightid, 10) + 1) + '. ' + lightName + '<br>(' + lightHostname + ')', '<select id="yee_' + lightid + '" ' + enabled + ' class="yee_sel_watch form-control">'
$('.lidsb').append(createTableRow([(parseInt(lightid, 10) + 1) + '. ' + lightName, '<select id="yee_' + lightid + '" ' + enabled + ' class="yee_sel_watch form-control">'
+ options
+ '</select>', '<button class="btn btn-sm btn-primary" onClick=identify_yeelight_device("' + lightHostname + '",' + lightPort + ')>'
+ $.i18n('wiz_identify_light', lightName) + '</button>']));
+ $.i18n('wiz_identify') + '</button>']));
}
$('.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') + '</button><buttowindow.serverConfig.device = d;n type="button" class="btn btn-danger" id="btn_wiz_abort"><i class="fa fa-fw fa-close"></i>'
+ $.i18n('general_btn_cancel') + '</button>');
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);
}
}

View File

@ -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}

View File

@ -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)

View File

@ -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" :

View File

@ -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}
)

View File

@ -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 "$<TARGET_FILE:protoc>")
# 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)

1
dependencies/external/qmdnsengine vendored Submodule

@ -0,0 +1 @@
Subproject commit 0ca80117e853671d909b3cec9e2bdcac85a13b9f

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 198 KiB

After

Width:  |  Height:  |  Size: 412 KiB

View File

@ -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();

View File

@ -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
///

View File

@ -6,10 +6,7 @@
// components def
#include <utils/Components.h>
// bonjour
#ifdef ENABLE_AVAHI
#include <bonjour/bonjourrecord.h>
#endif
// videModes
#include <utils/VideoMode.h>
// 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<QString,BonjourRecord>& 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

View File

@ -1,68 +0,0 @@
#pragma once
// qt incl
#include <QObject>
#include <QMap>
#include <QHostInfo>
#include <bonjour/bonjourrecord.h>
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<QString, BonjourRecord> getAllServices() { return _hyperionSessions; }
static BonjourBrowserWrapper* instance;
static BonjourBrowserWrapper *getInstance() { return instance; }
signals:
///
/// @brief Emits whenever a change happend
///
void browserChange( const QMap<QString, BonjourRecord> &bRegisters );
private:
/// map of service names and browsers
QMap<QString, BonjourServiceBrowser *> _browsedServices;
/// Resolver
BonjourServiceResolver *_bonjourResolver;
// contains all current active service sessions
QMap<QString, BonjourRecord> _hyperionSessions;
QString _bonjourCurrentServiceToResolve;
/// timer to resolve changes
QTimer *_timerBonjourResolver;
private slots:
///
/// @brief is called whenever a BonjourServiceBrowser emits change
void currentBonjourRecordsChanged( const QList<BonjourRecord> &list );
/// @brief new record resolved
void bonjourRecordResolved( const QHostInfo &hostInfo, int port );
///
/// @brief timer slot which updates regularly entries
///
void bonjourResolve();
};

View File

@ -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 <QtCore/QMetaType>
#include <QtCore/QString>
class BonjourRecord
{
public:
BonjourRecord() : port(-1) {}
BonjourRecord(const QString &name, const QString &regType, 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

View File

@ -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 <QtCore/QObject>
#ifndef PLATFORM_AMLOGIC
#include <dns_sd.h>
#else
#include <avahi-compat-libdns_sd/dns_sd.h>
#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<BonjourRecord> currentRecords() const { return bonjourRecords; }
inline QString serviceType() const { return browsingType; }
signals:
void currentBonjourRecordsChanged(const QList<BonjourRecord> &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<BonjourRecord> bonjourRecords;
QString browsingType;
};
#endif // BONJOURSERVICEBROWSER_H

View File

@ -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 <QtCore/QObject>
#include "bonjourrecord.h"
class QSocketNotifier;
#ifndef PLATFORM_AMLOGIC
#include <dns_sd.h>
#else
#include <avahi-compat-libdns_sd/dns_sd.h>
#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<std::pair<std::string, std::string>>& 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

View File

@ -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 <QtCore/QObject>
#ifndef PLATFORM_AMLOGIC
#include <dns_sd.h>
#else
#include <avahi-compat-libdns_sd/dns_sd.h>
#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

View File

@ -7,7 +7,6 @@
// qt
#include <QVector>
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<FlatBufferClient*> _openConnections;
};

View File

@ -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<TargetHost> _flatbufferTargets;
QList<FlatBufferConnection*> _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<ColorRgb>& image);
void addClientHandler(const QString& origin, const TargetHost& targetHost, int priority, bool skipReply);
void clearClientsHandler();
private:
QList<FlatBufferConnection*> _forwardClients;
bool _free;
};

View File

@ -22,6 +22,10 @@
#include <sys/ipc.h>
#include <sys/shm.h>
#ifdef Bool
#undef Bool
#endif
class X11Grabber : public Grabber , public QAbstractNativeEventFilter
{
public:

View File

@ -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();

View File

@ -1,4 +1,4 @@
#ifndef LEDEVICE_H
#ifndef LEDEVICE_H
#define LEDEVICE_H
// qt includes
@ -14,6 +14,7 @@
#include <vector>
#include <map>
#include <algorithm>
#include <chrono>
// Utility includes
#include <utils/ColorRgb.h>
@ -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<ColorRgb>& 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<ColorRgb>& ledValues);
virtual int updateLeds(std::vector<ColorRgb> 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;

View File

@ -94,16 +94,6 @@ signals:
///
int updateLeds(const std::vector<ColorRgb>& 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:

170
include/mdns/MdnsBrowser.h Normal file
View File

@ -0,0 +1,170 @@
#ifndef MDNS_BROWSER_H
#define MDNS_BROWSER_H
#include <chrono>
#include <type_traits>
#include <qmdnsengine/server.h>
#include <qmdnsengine/service.h>
#include <qmdnsengine/browser.h>
#include <qmdnsengine/cache.h>
#include <qmdnsengine/resolver.h>
#include <qmdnsengine/dns.h>
#include <qmdnsengine/record.h>
// Qt includes
#include <QObject>
#include <QByteArray>
// Utility includes
#include <utils/Logger.h>
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 <typename Func1, typename Func2, typename std::enable_if_t<std::is_member_pointer<Func2>::value, int> = 0>
// static inline QMetaObject::Connection weakConnect(typename QtPrivate::FunctionPointer<Func1>::Object* sender,
// Func1 signal,
// typename QtPrivate::FunctionPointer<Func2>::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 <typename Func1, typename Func2, typename std::enable_if_t<!std::is_member_pointer<Func2>::value, int> = 0>
static inline QMetaObject::Connection weakConnect(typename QtPrivate::FunctionPointer<Func1>::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 <typename Func1, typename Func2, typename std::enable_if_t<!std::is_member_pointer<Func2>::value, int> = 0>
// static inline QMetaObject::Connection weakConnect(typename QtPrivate::FunctionPointer<Func1>::Object* sender,
// Func1 signal,
// typename QtPrivate::FunctionPointer<Func2>::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<QByteArray, QMdnsEngine::Browser*> _browsedServiceTypes;
};
#endif // MDNSBROWSER_H

View File

@ -0,0 +1,51 @@
#ifndef MDNSPROVIDER_H
#define MDNSPROVIDER_H
#include <qmdnsengine/server.h>
#include <qmdnsengine/hostname.h>
#include <qmdnsengine/provider.h>
#include <qmdnsengine/service.h>
// Qt includes
#include <QObject>
#include <QByteArray>
// Utility includes
#include <utils/Logger.h>
class MdnsProvider : public QObject
{
public:
MdnsProvider(QObject* parent = nullptr);
~MdnsProvider() override;
QList<QByteArray> 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<QByteArray, QMdnsEngine::Provider*> _providedServiceTypes;
};
#endif // MDNSPROVIDER_H

View File

@ -0,0 +1,38 @@
#ifndef MDNSSERVICEREGISTER_H
#define MDNSSERVICEREGISTER_H
#include <QByteArray>
#include <QMap>
struct mdnsConfig
{
QByteArray serviceType;
QString serviceNameFilter;
};
typedef QMap<QString, mdnsConfig> 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

View File

@ -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

View File

@ -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

View File

@ -1,12 +1,17 @@
#pragma once
#include <utils/Logger.h>
#include <QTcpServer>
#include <QUrl>
#include <QHostAddress>
#include <QHostInfo>
#include <HyperionConfig.h>
#include <utils/Logger.h>
#ifdef ENABLE_MDNS
#include <mdns/MdnsBrowser.h>
#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);
}
}

View File

@ -11,7 +11,6 @@
// settings
#include <utils/settings.h>
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

View File

@ -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)

View File

@ -14,6 +14,7 @@
#include <QBuffer>
#include <QByteArray>
#include <QTimer>
#include <QThread>
// hyperion includes
#include <utils/jsonschema/QJsonFactory.h>
@ -23,9 +24,6 @@
#include <utils/ColorSys.h>
#include <utils/Process.h>
// bonjour wrapper
#include <bonjour/bonjourbrowserwrapper.h>
// ledmapping int <> string transform methods
#include <hyperion/ImageProcessor.h>
@ -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)

View File

@ -13,7 +13,7 @@
"subcommand": {
"type" : "string",
"required" : true,
"enum" : ["discover","getProperties","identify"]
"enum": [ "discover", "getProperties", "identify", "addAuthorization" ]
},
"ledDeviceType": {
"type" : "string",

View File

@ -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
}

View File

@ -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" ]
}
}
}

View File

@ -22,6 +22,7 @@
<file alias="schema-instance">JSONRPC_schema/schema-instance.json</file>
<file alias="schema-leddevice">JSONRPC_schema/schema-leddevice.json</file>
<file alias="schema-inputsource">JSONRPC_schema/schema-inputsource.json</file>
<file alias="schema-service">JSONRPC_schema/schema-service.json</file>
<!-- The following schemas are derecated but used to ensure backward compatibility with hyperion Classic remote control-->
<file alias="schema-transform">JSONRPC_schema/schema-hyperion-classic.json</file>
<file alias="schema-correction">JSONRPC_schema/schema-hyperion-classic.json</file>

View File

@ -63,11 +63,6 @@
#include <utils/Process.h>
#include <utils/JsonUtils.h>
// bonjour wrapper
#ifdef ENABLE_AVAHI
#include <bonjour/bonjourbrowserwrapper.h>
#endif
// ledmapping int <> string transform methods
#include <hyperion/ImageProcessor.h>
@ -77,6 +72,15 @@
// auth manager
#include <hyperion/AuthManager.h>
#ifdef ENABLE_MDNS
// mDNS discover
#include <mdns/MdnsBrowser.h>
#include <mdns/MdnsServiceRegister.h>
#else
// ssdp discover
#include <ssdp/SSDPDiscover.h>
#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);

View File

@ -9,10 +9,6 @@
// components
#include <hyperion/ComponentRegister.h>
// bonjour wrapper
#ifdef ENABLE_AVAHI
#include <bonjour/bonjourbrowserwrapper.h>
#endif
// priorityMuxer
#include <hyperion/PriorityMuxer.h>
@ -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<QString,BonjourRecord>& 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)
{

View File

@ -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()

View File

@ -1,84 +0,0 @@
#include <bonjour/bonjourbrowserwrapper.h>
//qt incl
#include <QTimer>
// bonjour
#include <bonjour/bonjourservicebrowser.h>
#include <bonjour/bonjourserviceresolver.h>
BonjourBrowserWrapper* BonjourBrowserWrapper::instance = nullptr;
BonjourBrowserWrapper::BonjourBrowserWrapper(QObject * parent)
: QObject(parent)
, _bonjourResolver(new BonjourServiceResolver(this))
, _timerBonjourResolver(new QTimer(this))
{
// register meta
qRegisterMetaType<QMap<QString,BonjourRecord>>("QMap<QString,BonjourRecord>");
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<BonjourRecord> &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;
}
}
}

View File

@ -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 <QtCore/QSocketNotifier>
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<BonjourServiceBrowser *>(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);
}
}
}

View File

@ -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 <bonjour/bonjourserviceregister.h>
#include <stdlib.h>
#include <QtCore/QSocketNotifier>
#include <QHostInfo>
#include <utils/Logger.h>
#include <HyperionConfig.h>
#include <hyperion/AuthManager.h>
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<std::pair<std::string, std::string>>& 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<std::pair<std::string, std::string> > 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<std::pair<std::string, std::string> >::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<BonjourServiceRegister *>(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);
}
}

View File

@ -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 <QtCore/QSocketNotifier>
#include <QtNetwork/QHostInfo>
#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<BonjourServiceResolver *>(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);
}

View File

@ -8,6 +8,8 @@
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QDebug>
#include <QFile>
/* 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())
{

View File

@ -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}
)

View File

@ -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()

View File

@ -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();
}

View File

@ -6,16 +6,18 @@
#include <utils/NetOrigin.h>
#include <utils/GlobalSignals.h>
// bonjour
#ifdef ENABLE_AVAHI
#include <bonjour/bonjourserviceregister.h>
#endif
// qt
#include <QJsonObject>
#include <QTcpServer>
#include <QTcpSocket>
// 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);
}
}
}

View File

@ -1,5 +1,5 @@
// STL includes
#include <stdexcept>
#include <chrono>
// project includes
#include <forwarder/MessageForwarder.h>
@ -12,23 +12,46 @@
#include <utils/NetUtils.h>
// qt includes
#include <QTcpServer>
#include <QTcpSocket>
#include <QHostInfo>
#include <QNetworkInterface>
#include <QThread>
#include <flatbufserver/FlatBufferConnection.h>
// mDNS discover
#ifdef ENABLE_MDNS
#include <mdns/MdnsBrowser.h>
#include <mdns/MdnsServiceRegister.h>
#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>("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<quint16>(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<quint16>(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<quint16>(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<quint16>(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<quint16>(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<quint16>(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<quint16>(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<quint16>(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<ColorRgb>& 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<ColorRgb>, 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<ColorRgb>& image)
{
_free = false;
for (int i = 0; i < _forwardClients.size(); i++)
{
_forwardClients.at(i)->setImage(image);
}
_free = true;
}

View File

@ -41,7 +41,3 @@ endif()
if(ENABLE_FORWARDER)
target_link_libraries(hyperion forwarder)
endif()
if (ENABLE_AVAHI)
target_link_libraries(hyperion bonjour)
endif ()

View File

@ -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
}

View File

@ -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<QVariantMap> HyperionIManager::getInstanceData() const

View File

@ -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);

View File

@ -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": {

View File

@ -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
}

View File

@ -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()

View File

@ -1,37 +1,35 @@
// system includes
#include <stdexcept>
// project includes
#include "HyperionConfig.h"
#include <jsonserver/JsonServer.h>
#include "JsonClientConnection.h"
// bonjour include
#ifdef ENABLE_AVAHI
#include <bonjour/bonjourserviceregister.h>
#endif
#include <utils/NetOrigin.h>
// qt includes
#include <QTcpServer>
#include <QTcpSocket>
#include <QJsonDocument>
#include <QByteArray>
// project includes
#include "HyperionConfig.h"
#include <jsonserver/JsonServer.h>
#include "JsonClientConnection.h"
#include <utils/NetOrigin.h>
// 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()

View File

@ -141,3 +141,6 @@ if (ENABLE_DEV_USB_HID)
endif()
endif()
if(ENABLE_MDNS)
target_link_libraries(leddevice mdns)
endif()

View File

@ -15,84 +15,91 @@
//std includes
#include <sstream>
#include <iomanip>
#include <chrono>
// 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<ColorRgb>& 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<int>(_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<ColorRgb> 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<ColorRgb>& 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<unsigned long>(_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<unsigned long>(_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<ColorRgb>(static_cast<unsigned long>(_ledCount),color);
_lastLedValues = std::vector<ColorRgb>(static_cast<unsigned long>(_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<uint>(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<int>(_enableAttemptTimerInterval.count()));
}
void LedDevice::printLedValues(const std::vector<ColorRgb>& 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<ColorRgb>& 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<const char*>(data), number);
#if (QT_VERSION >= QT_VERSION_CHECK(5, 9, 0))
return bytes.toHex(':');
#else
return bytes.toHex();
#endif
QByteArray bytes(reinterpret_cast<const char*>(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<int>(_ledCount);
}
QString LedDevice::getActiveDeviceType() const
{
return _activeDeviceType;
}
QString LedDevice::getColorOrder() const
{
return _colorOrder;
}
bool LedDevice::componentState() const {
return _isEnabled;
}

View File

@ -27,6 +27,7 @@
<file alias="schema-udpartnet">schemas/schema-artnet.json</file>
<file alias="schema-udph801">schemas/schema-h801.json</file>
<file alias="schema-udpraw">schemas/schema-udpraw.json</file>
<file alias="schema-udpddp">schemas/schema-udpddp.json</file>
<file alias="schema-ws2801">schemas/schema-ws2801.json</file>
<file alias="schema-ws2812spi">schemas/schema-ws2812spi.json</file>
<file alias="schema-apa104">schemas/schema-apa104.json</file>

View File

@ -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;
}

View File

@ -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;

View File

@ -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<quint16>(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()));

View File

@ -8,21 +8,27 @@
#include <chrono>
// mDNS discover
#ifdef ENABLE_MDNS
#include <mdns/MdnsBrowser.h>
#include <mdns/MdnsServiceRegister.h>
#endif
#include <utils/NetUtils.h>
// 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<const char*>(PACKET_HEADER), sizeof(PACKET_HEADER));
_packetFixPart.append(reinterpret_cast<const char*>(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<quint16>(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<quint16>(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))
{

View File

@ -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.
///

View File

@ -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<quint16>(static_cast<quint16>(_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<quint16>(_port));
_client->connectToHost(_hostName, static_cast<quint16>(_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();

View File

@ -130,7 +130,7 @@ private:
void sendFadeCandyConfiguration();
QTcpSocket* _client;
QString _host;
QString _hostName;
int _port;
int _channel;
QByteArray _opc_data;

View File

@ -1,16 +1,23 @@
// Local-Hyperion includes
#include "LedDeviceNanoleaf.h"
#include <ssdp/SSDPDiscover.h>
#include <utils/QStringUtils.h>
//std includes
#include <sstream>
#include <iomanip>
// Qt includes
#include <QNetworkReply>
#include <QtEndian>
//std includes
#include <sstream>
#include <iomanip>
#include <ssdp/SSDPDiscover.h>
#include <utils/QStringUtils.h>
// mDNS discover
#ifdef ENABLE_MDNS
#include <mdns/MdnsBrowser.h>
#include <mdns/MdnsServiceRegister.h>
#endif
#include <utils/NetUtils.h>
// 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;

View File

@ -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;

File diff suppressed because it is too large Load Diff

View File

@ -7,7 +7,6 @@
// Qt includes
#include <QNetworkAccessManager>
#include <QEventLoop>
#include <QNetworkReply>
#include <QtCore/qmath.h>
#include <QStringList>
@ -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<quint16,QJsonObject> getLightMap() const;
QMap<int,QJsonObject> getLightMap() const;
QMap<quint16,QJsonObject> getGroupMap() const;
QString getGroupName(quint16 groupId = 0) const;
QJsonArray getGroupLights(quint16 groupId = 0) const;
QMap<int,QJsonObject> 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<quint16,QJsonObject> _lightsMap;
QMap<quint16,QJsonObject> _groupsMap;
QMap<int,QJsonObject> _lightsMap;
QMap<int,QJsonObject> _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<quint16, QJsonObject> 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<quint16, QJsonObject> &map);
bool updateLights(const QMap<int, QJsonObject> &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<ColorRgb>& ledValues);
bool noSignalDetection();
void stopBlackTimeoutTimer();
QByteArray prepareStreamData() const;
///
@ -574,32 +582,28 @@ private:
bool _isInitLeds;
/// Array of the light ids.
std::vector<quint16> _lightIds;
std::vector<int> _lightIds;
/// Array to save the lamps.
std::vector<PhilipsHueLight> _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;
};

View File

@ -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;

View File

@ -1,6 +1,13 @@
#include "LedDeviceTpm2net.h"
#include <utils/NetUtils.h>
// 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<ColorRgb> &ledValues)
{
int retVal = 0;

View File

@ -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.
///

View File

@ -9,7 +9,16 @@
#include <QHostInfo>
#include <utils/NetUtils.h>
// 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)
{

View File

@ -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.
///

View File

@ -0,0 +1,163 @@
#include "LedDeviceUdpDdp.h"
#include <QtEndian>
#include <utils/NetUtils.h>
// 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<ColorRgb> &ledValues)
{
int rc {0};
int channelCount = static_cast<int>(_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<char>(_packageSequenceNumber++ & 0x0F);
/*4*/qToBigEndian<quint32>(static_cast<quint32>(channel), _ddpData.data() + 4);
/*8*/qToBigEndian<quint16>(static_cast<quint16>(packetSize), _ddpData.data() + 8);
_ddpData.replace(DDP::HEADER_LEN, channel, reinterpret_cast<const char*>(ledValues.data())+channel, packetSize);
_ddpData.resize(DDP::HEADER_LEN + packetSize);
rc = writeBytes(_ddpData);
if (rc != 0)
{
break;
}
channel += packetSize;
}
return rc;
}

View File

@ -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<ColorRgb> & ledValues) override;
private:
QByteArray _ddpData;
int _packageSequenceNumber;
};
#endif // LEDEVICEUDPDDP_H

View File

@ -8,6 +8,13 @@
// hyperion local includes
#include "LedDeviceUdpE131.h"
#include <utils/NetUtils.h>
// 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)
{

View File

@ -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.
///

View File

@ -1,8 +1,12 @@
#include "LedDeviceUdpH801.h"
#include <utils/NetUtils.h>
// 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<ColorRgb> &ledValues)
{
ColorRgb color = ledValues[0];

View File

@ -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.
///

View File

@ -1,10 +1,14 @@
#include "LedDeviceUdpRaw.h"
#include <utils/NetUtils.h>
// 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<ColorRgb> &ledValues)
{
const uint8_t * dataPtr = reinterpret_cast<const uint8_t *>(ledValues.data());
@ -59,8 +76,11 @@ int LedDeviceUdpRaw::write(const std::vector<ColorRgb> &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);

View File

@ -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.
///

View File

@ -1,11 +1,18 @@
// Local-Hyperion includes
#include "LedDeviceWled.h"
#include <chrono>
#include <utils/QStringUtils.h>
#include <utils/WaitTime.h>
#include <QThread>
#include <chrono>
// mDNS discover
#ifdef ENABLE_MDNS
#include <mdns/MdnsBrowser.h>
#include <mdns/MdnsServiceRegister.h>
#endif
#include <utils/NetUtils.h>
#include <utils/version.hpp>
// 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<ColorRgb> &ledValues)
{
const uint8_t * dataPtr = reinterpret_cast<const uint8_t *>(ledValues.data());
int rc {0};
return writeBytes( _ledRGBCount, dataPtr);
if (_isStreamDDP)
{
rc = LedDeviceUdpDdp::write(ledValues);
}
else
{
rc = LedDeviceUdpRaw::write(ledValues);
}
return rc;
}

Some files were not shown because too many files have changed in this diff Show More