diff --git a/.devcontainer.json b/.devcontainer.json new file mode 100644 index 00000000..04de050b --- /dev/null +++ b/.devcontainer.json @@ -0,0 +1,20 @@ +{ + "name": "Hyperion.ng Linux", + "extensions": [ + "twxs.cmake", + "ms-vscode.cpptools", + "ms-vscode.cmake-tools", + "spmeesseman.vscode-taskexplorer", + "yzhang.markdown-all-in-one", + "CoenraadS.bracket-pair-colorizer", + "vscode-icons-team.vscode-icons", + "editorconfig.editorconfig" + ], + "settings": { + "editor.formatOnSave": false, + "cmake.environment": { + }, + }, + "forwardPorts": [8090, 8092], + "postCreateCommand": "git submodule update --recursive --init && sudo apt-get update && sudo apt-get install -y git cmake build-essential qtbase5-dev libqt5serialport5-dev libqt5sql5-sqlite libqt5svg5-dev libqt5x11extras5-dev libusb-1.0-0-dev python3-dev libcec-dev libxcb-image0-dev libxcb-util0-dev libxcb-shm0-dev libxcb-render0-dev libxcb-randr0-dev libxrandr-dev libxrender-dev libavahi-core-dev libavahi-compat-libdnssd-dev libjpeg-dev libturbojpeg0-dev libssl-dev zlib1g-dev" +} diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000..3ba13e0c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1 @@ +blank_issues_enabled: false diff --git a/.github/issues.yml b/.github/issues.yml new file mode 100644 index 00000000..62cb0dc8 --- /dev/null +++ b/.github/issues.yml @@ -0,0 +1,12 @@ +Issues: + opened: | + Hello @$AUTHOR + + We make use of an **[ISSUE TEMPLATE](https://github.com/$REPO_FULL_NAME/issues/new/choose)** to capture relevant information to support you best. Unfortunately, **you ignored or deleted** the given sections. Please take care that all information requested is provided. + + This issue will be automatically closed by our bot, please do not take it personally. We would like asking you to open a new issue following the **[ISSUE TEMPLATE](https://github.com/$REPO_FULL_NAME/issues/new/choose)**. + + Thanks for your continuous support! + + Best regards, + Hyperion-Project diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index e44a3cc5..a5c462e9 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -124,6 +124,7 @@ jobs: runs-on: windows-latest env: VCINSTALLDIR: 'C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC' + QT_VERSION: 5.15.0 steps: - name: Checkout uses: actions/checkout@v1 @@ -137,25 +138,37 @@ jobs: tr -d '\n' < version > temp && mv temp version echo -n "-PR#${{ github.event.pull_request.number }}" >> version + - name: Cache Qt + uses: actions/cache@v2 + id: cache-qt-windows + with: + path: ${{ runner.workspace }}/Qt + key: ${{ runner.os }}-Qt.${{ env.QT_VERSION }} + - name: Install Qt uses: jurplel/install-qt-action@v2 with: - version: '5.15.0' + version: ${{env.QT_VERSION}} target: 'desktop' arch: 'win64_msvc2019_64' + cached: ${{ steps.cache-qt-windows.outputs.cache-hit }} - - name: Install Python - uses: actions/setup-python@v1 + - name: Cache Chocolatey downloads + uses: actions/cache@v2 with: - python-version: '3.x' + path: C:\Users\runneradmin\AppData\Local\Temp\chocolatey + key: ${{ runner.os }}-chocolatey - - name: Install NSIS & copy plugins + - name: "Remove Redistributable" + shell: cmd run: | - choco install --no-progress nsis -y - copy "cmake\nsis\template\*.dll" "C:\Program Files (x86)\NSIS\Plugins\x86-ansi\" + MsiExec.exe /passive /X{F0C3E5D1-1ADE-321E-8167-68EF0DE699A5} + MsiExec.exe /passive /X{1D8E6291-B0D5-35EC-8441-6616F567A0F7} - - name: Install OpenSSL - run: choco install --no-progress openssl -y + - name: Install Python, NSIS, OpenSSL, DirectX SDK + shell: powershell + run: | + choco install --no-progress python nsis openssl directx-sdk -y - name: Set up x64 build architecture environment shell: cmd diff --git a/.github/workflows/push-master.yml b/.github/workflows/push-master.yml index 768599fd..647e5f99 100644 --- a/.github/workflows/push-master.yml +++ b/.github/workflows/push-master.yml @@ -95,31 +95,44 @@ jobs: runs-on: windows-latest env: VCINSTALLDIR: 'C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC' + QT_VERSION: 5.15.0 steps: - name: Checkout uses: actions/checkout@v1 with: submodules: true + - name: Cache Qt + uses: actions/cache@v2 + id: cache-qt-windows + with: + path: ${{ runner.workspace }}/Qt + key: ${{ runner.os }}-Qt.${{ env.QT_VERSION }} + - name: Install Qt uses: jurplel/install-qt-action@v2 with: - version: '5.15.0' + version: ${{ env.QT_VERSION }} target: 'desktop' arch: 'win64_msvc2019_64' + cached: ${{ steps.cache-qt-windows.outputs.cache-hit }} - - name: Install Python - uses: actions/setup-python@v1 + - name: Cache Chocolatey downloads + uses: actions/cache@v2 with: - python-version: '3.x' + path: C:\Users\runneradmin\AppData\Local\Temp\chocolatey + key: ${{ runner.os }}-chocolatey - - name: Install NSIS & copy plugins + - name: "Remove Redistributable" + shell: cmd run: | - choco install --no-progress nsis -y - copy "cmake\nsis\template\*.dll" "C:\Program Files (x86)\NSIS\Plugins\x86-ansi\" + MsiExec.exe /passive /X{F0C3E5D1-1ADE-321E-8167-68EF0DE699A5} + MsiExec.exe /passive /X{1D8E6291-B0D5-35EC-8441-6616F567A0F7} - - name: Install OpenSSL - run: choco install --no-progress openssl -y + - name: Install Python, NSIS, OpenSSL, DirectX SDK + shell: powershell + run: | + choco install --no-progress python nsis openssl directx-sdk -y - name: Set up x64 build architecture environment shell: cmd @@ -192,14 +205,14 @@ jobs: # generate environment variables - name: Generate environment variables from version and tag run: | - echo ::set-env name=TAG::${GITHUB_REF/refs\/tags\//} - echo ::set-env name=VERSION::$(tr -d '\n' < version) - echo ::set-env name=preRelease::false + echo "TAG=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_ENV + echo "VERSION=$(tr -d '\n' < version)" >> $GITHUB_ENV + echo "preRelease=false" >> $GITHUB_ENV # If version contains alpha or beta, mark draft release as pre-release - name: Mark release as pre-release if: contains(env.VERSION, 'alpha') || contains(env.VERSION, 'beta') - run: echo ::set-env name=preRelease::true + run: echo "preRelease=true" >> $GITHUB_ENV # Download artifacts from previous build process - name: Download artifacts diff --git a/.gitignore b/.gitignore index f92845f5..0713ddb3 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,7 @@ libsrc/flatbufserver/hyperion_request_generated.h # Kdevelop project files *.kdev* + +# Visual Studio 2015/2017/2019 cache/options directory +.vs/ +CMakeSettings.json diff --git a/.vscode/hyperion.code-workspace b/.vscode/hyperion.code-workspace index a86bfc59..3b5db3c7 100644 --- a/.vscode/hyperion.code-workspace +++ b/.vscode/hyperion.code-workspace @@ -16,9 +16,7 @@ "ms-vscode.cmake-tools", "spmeesseman.vscode-taskexplorer", "yzhang.markdown-all-in-one", - "formulahendry.auto-rename-tag", "CoenraadS.bracket-pair-colorizer", - "eamodio.gitlens", "vscode-icons-team.vscode-icons", "editorconfig.editorconfig" ] diff --git a/.vscode/launch.json b/.vscode/launch.json index 90c1d1b3..ad6f92cc 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -9,7 +9,7 @@ "type": "cppdbg", "request": "launch", "program": "${workspaceFolder}/build/bin/hyperiond", - "args": [], + "args": ["-d"], "stopAtEntry": false, "cwd": "${workspaceFolder}", "environment": [], @@ -28,7 +28,7 @@ "type": "cppvsdbg", "request": "launch", "program": "${workspaceFolder}/build/bin/Debug/hyperiond.exe", - "args": [], + "args": ["-d"], "stopAtEntry": false, "cwd": "${workspaceFolder}", "environment": [], diff --git a/.vscode/tasks.json b/.vscode/tasks.json index f0d5e0ae..cc5e97ae 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -11,7 +11,7 @@ "windows": { "command": "cmake -G \"Visual Studio 16 2019\" -A x64 -B ${workspaceFolder}/build" }, - "group": "build", + "group": "build" }, { "label": "cmake:conf Debug", @@ -21,7 +21,7 @@ "windows": { "command": "cmake -G \"Visual Studio 16 2019\" -A x64 -B ${workspaceFolder}/build" }, - "group": "build", + "group": "build" }, { "label": "build:debug hyperiond", diff --git a/CHANGELOG.md b/CHANGELOG.md index 90c91022..12e42638 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [Unreleased](https://github.com/hyperion-project/hyperion.ng/compare/2.0.0-alpha.8...HEAD) +## [Unreleased](https://github.com/hyperion-project/hyperion.ng/compare/2.0.0-alpha.9...HEAD) ### Breaking @@ -13,15 +13,54 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed ### Fixed - - Also allow an 8-LED configuration when using Karatelight - -- Fix Lightpack issue (#1015) - -- Fix color calibration for Kodi 18 (Fixes #771) ### Removed -- Replace Multi-Lightpack by multi-instance Lightpack configuration +## [2.0.0-alpha.9](https://github.com/hyperion-project/hyperion.ng/releases/tag/2.0.0-alpha.9) - 2020-11-18 +### Added +- Grabber: DirectX9 support (#1039) +- New blackbar detection mode "Letterbox", that considers only bars at the top and bottom of picture + +- LED-Devices: Cololight support (Cololight Plus & Strip) incl. configuration wizard +- LED-Devices: SK9822 support (#1005,#1017) + +- UX: New language support: Russian and Chinese (simplified) (#1005) +- UX: Additional details on Hardware/CPU information (#1045) +- UX: Systray icons added - Issue #925 (#1040) + +- Read-Only configuration database support +- Hide Window Systray icon on Hyperion exit & Install DirectX Redistributable +- Read-Only configuration database support + +### Changed +- boblight: reduce cpu time spent on memcopy and parsing rgb values (#1016) +- Windows Installer/Uninstaller notification when Hyperion is running (#1033) +- Updated Windows Dependencies +- Documentation: Optimized images (#1058) +- UX: Default LED-layout is now one LED only to avoid errors as in #673 +- UX: Change links from http to https (#1067) +- Change links from http to https (#1067) +- Cleanup packages.cmake & extend NSIS plugin directory +- Optimize images (#1058) +- Docs: Refreshed EN JSON API documentation + +### Fixed +- Color calibration for Kodi 18 (#1044) +- LED-Devices: Karatelight, allow an 8-LED configuration (#1037) +- LED-Devices: Save Hue light state between sessions (#1014) +- LED-Devices: LED's retain last state after clearing a source (#1008) +- LED-Devices: Lightpack issue #1015 (#1049) +- Fix various JSON API issues (#1036) +- Fix issue #909, Have ratio correction first and then scale (#1047) +- Fix display argument in hyperion-qt (#1027) +- Fix Python reset thread state +- AVAHI included in Webserver (#996) +- Fix add libcec to deb/rpm dependency list +- Fix Hyperion configuration is corrected during start-up, if required +- Fix color comparison / Signal detection (#1087) + +### Removed +- Replace Multi-Lightpack by multi-instance Lightpack configuration (#1049) ## [2.0.0-alpha.8](https://github.com/hyperion-project/hyperion.ng/releases/tag/2.0.0-alpha.8) - 2020-09-14 ### Added @@ -35,7 +74,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Improved UDP-Device Error handling (#961) - NSIS/Systray option to launch Hyperion on Windows start (HKCU) (#887) - Updated some dependencies (#929, #1003, #1004) -- refactor: Modernize Qt connections (#914) +- refactor: Modernize Qt connections (#914) - refactor: Resolve some clang warnings (#915) - refactor: Several random fixes + Experimental playground (#917) - Use query interface for void returning X requests (#945) diff --git a/CMakeLists.txt b/CMakeLists.txt index dd5c7606..62200bb2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,9 +39,9 @@ endif() SET ( DEFAULT_AMLOGIC OFF ) SET ( DEFAULT_DISPMANX OFF ) SET ( DEFAULT_OSX OFF ) +SET ( DEFAULT_QT ON ) SET ( DEFAULT_X11 OFF ) SET ( DEFAULT_XCB OFF ) -SET ( DEFAULT_QT ON ) SET ( DEFAULT_WS281XPWM OFF ) SET ( DEFAULT_AVAHI ON ) SET ( DEFAULT_USE_SHARED_AVAHI_LIBS ON ) @@ -58,6 +58,8 @@ IF ( ${CMAKE_SYSTEM} MATCHES "Linux" ) SET ( DEFAULT_FB ON ) SET ( DEFAULT_USB_HID ON ) SET ( DEFAULT_CEC ON ) +ELSEIF ( WIN32 ) + SET ( DEFAULT_DX ON ) ELSE() SET ( DEFAULT_V4L2 OFF ) SET ( DEFAULT_FB OFF ) @@ -190,6 +192,9 @@ message(STATUS "ENABLE_XCB = ${ENABLE_XCB}") option(ENABLE_QT "Enable the qt grabber" ${DEFAULT_QT}) message(STATUS "ENABLE_QT = ${ENABLE_QT}") +option(ENABLE_DX "Enable the DirectX grabber" ${DEFAULT_DX}) +message(STATUS "ENABLE_DX = ${ENABLE_DX}") + option(ENABLE_TESTS "Compile additional test applications" ${DEFAULT_TESTS}) message(STATUS "ENABLE_TESTS = ${ENABLE_TESTS}") @@ -297,12 +302,46 @@ if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC") # Qt5 default install path with msvc2017 64bit component # The Qt5_DIR should point to Qt5Config.cmake -> C:/Qt/5.xx/msvc2017_64/lib/cmake/Qt5 # The CMAKE_PREFIX_PATH should point to the install directory -> C:/Qt/5.xx/msvc2017_64 - FIRSTSUBDIR(SUBDIRQT "C:/Qt") - SET(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} "${SUBDIRQT}/msvc2019_64") - if (NOT DEFINED ENV{Qt5_DIR}) - message(STATUS "Set Qt5 module path: ${SUBDIRQT}") - SET(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${SUBDIRQT}/msvc2019_64/lib/cmake/Qt5") + # + # Alternatively, use Qt5_BASE_DIR environment variable to point to Qt version to be used + # In MSVC19 add into CMakeSettings.json + # + # "environments": [ + # { + # "Qt5_BASE_DIR": "D:/Qt/5.15.1/msvc2019_64" + # } + # ] + + if (NOT DEFINED ENV{Qt5_BASE_DIR}) + FIRSTSUBDIR(SUBDIRQT "C:/Qt") + SET(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} "${SUBDIRQT}/msvc2019_64") + else() + message(STATUS "Qt5_BASE_DIR: $ENV{Qt5_BASE_DIR}") + message(STATUS "Add Qt5_BASE_DIR: $ENV{Qt5_BASE_DIR} to CMAKE_PREFIX_PATH") + SET(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} "$ENV{Qt5_BASE_DIR}") endif() + + if (NOT DEFINED ENV{Qt5_DIR}) + if (NOT DEFINED ENV{Qt5_BASE_DIR}) + SET (qt_module_path "${SUBDIRQT}/msvc2019_64/lib/cmake/Qt5") + else () + SET (qt_module_path "$ENV{Qt5_BASE_DIR}/lib/cmake/Qt5") + endif() + else() + SET (qt_module_path "$ENV{Qt5_DIR}") + endif() + + message(STATUS "Add ${qt_module_path} to CMAKE_MODULE_PATH") + SET(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${qt_module_path}") + + #message(STATUS "CMAKE_PREFIX_PATH: ${CMAKE_PREFIX_PATH}") + #message(STATUS "CMAKE_MODULE_PATH: ${CMAKE_MODULE_PATH}") + + # Search for DirectX9 + if (ENABLE_DX) + find_package(DirectX9 REQUIRED) + endif() + endif() # Use GNU gold linker if available diff --git a/CompileHowto.md b/CompileHowto.md index 0917928b..031f6c46 100644 --- a/CompileHowto.md +++ b/CompileHowto.md @@ -48,7 +48,7 @@ wget -qN https://raw.github.com/hyperion-project/hyperion.ng/master/bin/scripts/ ``` sudo apt-get update -sudo apt-get install git cmake build-essential qtbase5-dev libqt5serialport5-dev libqt5sql5-sqlite libqt5x11extras5-dev libusb-1.0-0-dev python3-dev libcec-dev libxcb-image0-dev libxcb-util0-dev libxcb-shm0-dev libxcb-render0-dev libxcb-randr0-dev libxrandr-dev libxrender-dev libavahi-core-dev libavahi-compat-libdnssd-dev libjpeg-dev libturbojpeg0-dev libssl-dev zlib1g-dev +sudo apt-get install git cmake build-essential qtbase5-dev libqt5serialport5-dev libqt5sql5-sqlite libqt5svg5-dev libqt5x11extras5-dev libusb-1.0-0-dev python3-dev libcec-dev libxcb-image0-dev libxcb-util0-dev libxcb-shm0-dev libxcb-render0-dev libxcb-randr0-dev libxrandr-dev libxrender-dev libavahi-core-dev libavahi-compat-libdnssd-dev libjpeg-dev libturbojpeg0-dev libssl-dev zlib1g-dev ``` **on RPI you need the videocore IV headers** @@ -84,16 +84,19 @@ brew install zlib ``` ## Windows (WIP) -We assume a 64bit Windows 7 or higher. Install the following +We assume a 64bit Windows 7 or higher. Install the following; - [Git](https://git-scm.com/downloads) (Check: Add to PATH) -- [Python 3 (Windows x86-64 executable installer)](https://www.python.org/downloads/windows/) (Check: Add to PATH and Debug Symbols) - - Open a console window and execute `pip install aqtinstall`. - - Now we can download Qt to _C:\Qt_ `mkdir c:\Qt && aqt install -O c:\Qt 5.15.0 windows desktop win64_msvc2019_64` - [CMake (Windows win64-x64 Installer)](https://cmake.org/download/) (Check: Add to PATH) -- [Win64 OpenSSL v1.1.1g](https://slproweb.com/products/Win32OpenSSL.html) ([direct link](https://slproweb.com/download/Win64OpenSSL-1_1_1g.exe)) - [Visual Studio 2019 Build Tools](https://go.microsoft.com/fwlink/?linkid=840931) ([direct link](https://aka.ms/vs/16/release/vs_buildtools.exe)) - Select C++ Buildtools - On the right, just select `MSVC v142 VS 2019 C++ x64/x86-Buildtools` and latest `Windows 10 SDK`. Everything else is not needed. +- [Win64 OpenSSL v1.1.1h](https://slproweb.com/products/Win32OpenSSL.html) ([direct link](https://slproweb.com/download/Win64OpenSSL-1_1_1h.exe)) +- [Python 3 (Windows x86-64 executable installer)](https://www.python.org/downloads/windows/) (Check: Add to PATH and Debug Symbols) + - Open a console window and execute `pip install aqtinstall`. + - Now we can download Qt to _C:\Qt_ `mkdir c:\Qt && aqt install -O c:\Qt 5.15.0 windows desktop win64_msvc2019_64` +- [DirectX Software Development Kit](https://www.microsoft.com/en-us/download/details.aspx?id=6812) ([direct link](https://download.microsoft.com/download/A/E/7/AE743F1F-632B-4809-87A9-AA1BB3458E31/DXSDK_Jun10.exe)) + +- Optional for package creation: [NSIS 3.x](https://sourceforge.net/projects/nsis/files/NSIS%203/) ([direct link](https://sourceforge.net/projects/nsis/files/latest/download)) # Compiling and installing Hyperion diff --git a/CrossCompileHowto.md b/CrossCompileHowto.md index 8ab9f020..4617370a 100644 --- a/CrossCompileHowto.md +++ b/CrossCompileHowto.md @@ -4,14 +4,14 @@ Use a clean Raspbian Stretch Lite (on target) and Ubuntu 18/19 (on host) to exec ## On the Target system (here Raspberry Pi) Install required additional packages. ``` -sudo apt-get install qtbase5-dev libqt5serialport5-dev libusb-1.0-0-dev python3-dev libcec-dev libxcb-util0-dev libxcb-randr0-dev libxrandr-dev libxrender-dev libavahi-core-dev libavahi-compat-libdnssd-dev libjpeg-dev libturbojpeg0-dev libqt5sql5-sqlite aptitude qt5-default rsync libssl-dev zlib1g-dev +sudo apt-get install qtbase5-dev libqt5serialport5-dev libqt5svg5-dev libusb-1.0-0-dev python3-dev libcec-dev libxcb-util0-dev libxcb-randr0-dev libxrandr-dev libxrender-dev libavahi-core-dev libavahi-compat-libdnssd-dev libjpeg-dev libturbojpeg0-dev libqt5sql5-sqlite aptitude qt5-default rsync libssl-dev zlib1g-dev ``` ## On the Host system (here Ubuntu) Update the Ubuntu environment to the latest stage and install required additional packages. ``` sudo apt-get update sudo apt-get upgrade -sudo apt-get -qq -y install git rsync cmake build-essential qtbase5-dev libqt5serialport5-dev libqt5sql5-sqlite libqt5x11extras5-dev libusb-1.0-0-dev python3-dev libcec-dev libxcb-image0-dev libxcb-util0-dev libxcb-shm0-dev libxcb-render0-dev libxcb-randr0-dev libxrandr-dev libxrender-dev libavahi-core-dev libavahi-compat-libdnssd-dev libjpeg-dev libturbojpeg0-dev libssl-dev zlib1g-dev +sudo apt-get -qq -y install git rsync cmake build-essential qtbase5-dev libqt5serialport5-dev libqt5svg5-dev libqt5sql5-sqlite libqt5x11extras5-dev libusb-1.0-0-dev python3-dev libcec-dev libxcb-image0-dev libxcb-util0-dev libxcb-shm0-dev libxcb-render0-dev libxcb-randr0-dev libxrandr-dev libxrender-dev libavahi-core-dev libavahi-compat-libdnssd-dev libjpeg-dev libturbojpeg0-dev libssl-dev zlib1g-dev ``` Refine the target IP or hostname, plus userID as required and set-up cross-compilation environment: diff --git a/HyperionConfig.h.in b/HyperionConfig.h.in index 655a6ec3..00537e71 100644 --- a/HyperionConfig.h.in +++ b/HyperionConfig.h.in @@ -24,6 +24,9 @@ // Define to enable the qt grabber #cmakedefine ENABLE_QT +// Define to enable the DirectX grabber +#cmakedefine ENABLE_DX + // Define to enable the spi-device #cmakedefine ENABLE_SPIDEV diff --git a/LICENSE b/LICENSE index cb9da7b4..49f17f41 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2014 hyperion team +Copyright (c) 2014-2020 Hyperion Project Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index ee06895c..7a3ec484 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,6 @@ See [CompileHowto](CompileHowto.md) and [CrossCompileHowto](CrossCompileHowto.md Alpha releases available from the [Hyperion release page](https://github.com/hyperion-project/hyperion.ng/releases) ## License -The source is released under MIT-License (see http://opensource.org/licenses/MIT).
+The source is released under MIT-License (see https://opensource.org/licenses/MIT).
[![GitHub license](https://img.shields.io/badge/License-MIT-yellow.svg)](https://raw.githubusercontent.com/hyperion-project/hyperion.ng/master/LICENSE) diff --git a/assets/firmware/arduino/network_bridge/udpraw_serialadalight.py b/assets/firmware/arduino/network_bridge/udpraw_serialadalight.py index 5c6a2dfc..b69efe20 100755 --- a/assets/firmware/arduino/network_bridge/udpraw_serialadalight.py +++ b/assets/firmware/arduino/network_bridge/udpraw_serialadalight.py @@ -21,6 +21,7 @@ import socket import serial import serial.threaded +from __future__ import division class SerialToNet(serial.threaded.Protocol): """serial->socket""" @@ -152,7 +153,7 @@ to this service over the network. srv = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - srv.bind(('', args.localport)) + srv.bind(('0.0.0.0', args.localport)) try: intentional_exit = False diff --git a/assets/webconfig/apple-touch-icon.png b/assets/webconfig/apple-touch-icon.png index 95d1d4de..48342175 100644 Binary files a/assets/webconfig/apple-touch-icon.png and b/assets/webconfig/apple-touch-icon.png differ diff --git a/assets/webconfig/content/about.html b/assets/webconfig/content/about.html index 7246ca87..665dbc4b 100644 --- a/assets/webconfig/content/about.html +++ b/assets/webconfig/content/about.html @@ -18,7 +18,7 @@ performTranslation(); var si = sysInfo.hyperion; - var libs = { "Bootstrap 3": "http://getbootstrap.com/", "JQuery": "https://jquery.com/", "Bootstrap Colorpicker": "https://itsjavi.com/bootstrap-colorpicker/", "Bootstrap Toggle": "https://www.bootstraptoggle.com/", "Bootstrap Select": "https://developer.snapappointments.com/bootstrap-select/", "JSON-Editor": "http://jeremydorn.com/json-editor/", "jQuery.i18n": "https://github.com/wikimedia/jquery.i18n", "metisMenu": "http://mm.onokumus.com/index.html", "download.js": "http://danml.com/download.html", "gijgo": "http://gijgo.com/" }; + var libs = { "Bootstrap 3": "https://getbootstrap.com/", "JQuery": "https://jquery.com/", "Bootstrap Colorpicker": "https://itsjavi.com/bootstrap-colorpicker/", "Bootstrap Toggle": "https://www.bootstraptoggle.com/", "Bootstrap Select": "https://developer.snapappointments.com/bootstrap-select/", "JSON-Editor": "https://www.jeremydorn.com/json-editor", "jQuery.i18n": "https://github.com/wikimedia/jquery.i18n", "metisMenu": "https://mm.onokumus.com/index.html", "download.js": "https://github.com/rndme/download", "gijgo": "https://gijgo.com/" }; var libh = ""; var lang = []; var dcount = 0; @@ -35,19 +35,35 @@ var sys = window.sysInfo.system; var shy = window.sysInfo.hyperion; var info = "
Hyperion Server: \n";
-	info += '- Build:       ' + shy.build + '\n';
-	info += '- Build time:  ' + shy.time + '\n';
-	info += '- Git Remote:  ' + shy.gitremote + '\n';
-	info += '- Version:     ' + shy.version + '\n';
-	info += '- UI Lang:     ' + storedLang + ' (BrowserLang: ' + navigator.language + ')\n';
-	info += '- UI Access:   ' + storedAccess + '\n';
-	//info += 'Log lvl:     ' + window.serverConfig.logger.level + '\n';
-	info += '- Avail Capt:  ' + window.serverInfo.grabbers.available + '\n\n';
+	info += '- Build:           ' + shy.build + '\n';
+	info += '- Build time:      ' + shy.time + '\n';
+	info += '- Git Remote:      ' + shy.gitremote + '\n';
+	info += '- Version:         ' + shy.version + '\n';
+	info += '- UI Lang:         ' + storedLang + ' (BrowserLang: ' + navigator.language + ')\n';
+	info += '- UI Access:       ' + storedAccess + '\n';
+	//info += '- Log lvl:         ' + window.serverConfig.logger.level + '\n';
+	info += '- Avail Capt:      ' + window.serverInfo.grabbers.available + '\n';
+	info += '- Database:        ' + (shy.readOnlyMode ? "ready-only" : "read/write") + '\n';
+
+	info += '\n';
+
 	info += 'Hyperion Server OS: \n';
-	info += '- Distribution: ' + sys.prettyName + '\n';
-	info += '- Arch:         ' + sys.architecture + '\n';
-	info += '- Kernel:       ' + sys.kernelType + ' (' + sys.kernelVersion + ' (WS: ' + sys.wordSize + '))\n';
-	info += '- Browser:      ' + navigator.userAgent + ' 
'; + info += '- Distribution: ' + sys.prettyName + '\n'; + info += '- Architecture: ' + sys.architecture + '\n'; + + if (sys.cpuModelName) + info += '- CPU Model: ' + sys.cpuModelName + '\n'; + if (sys.cpuModelType) + info += '- CPU Type: ' + sys.cpuModelType + '\n'; + if (sys.cpuRevision) + info += '- CPU Revision: ' + sys.cpuRevision + '\n'; + if (sys.cpuHardware) + info += '- CPU Hardware: ' + sys.cpuHardware + '\n'; + + info += '- Kernel: ' + sys.kernelType + ' (' + sys.kernelVersion + ' (WS: ' + sys.wordSize + '))\n'; + info += '- Qt Version: ' + sys.qtVersion + '\n'; + info += '- Python Version: ' + sys.pyVersion + '\n'; + info += '- Browser: ' + navigator.userAgent + ' '; var fc = ['' + $.i18n("about_version") + '', $.i18n("about_build"), $.i18n("about_builddate"), $.i18n("about_translations"), $.i18n("about_resources", $.i18n("general_webui_title")), "System info (Github Issue)", $.i18n("about_3rd_party_licenses")]; var sc = [currentVersion, si.build, si.time, '(' + availLang.length + ')

' + lang + '

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

', libh, info, '
']; diff --git a/assets/webconfig/content/conf_leds.html b/assets/webconfig/content/conf_leds.html index 3e3b54c9..eca4412f 100755 --- a/assets/webconfig/content/conf_leds.html +++ b/assets/webconfig/content/conf_leds.html @@ -50,7 +50,7 @@ - +
LEDs
@@ -59,7 +59,7 @@ - +
LEDs
@@ -68,7 +68,7 @@ - +
LEDs
@@ -77,7 +77,7 @@ - +
LEDs
@@ -266,7 +266,7 @@ - +
LEDs
@@ -275,7 +275,7 @@ - +
LEDs
diff --git a/assets/webconfig/content/support.html b/assets/webconfig/content/support.html index 310b499c..ddc2dfed 100644 --- a/assets/webconfig/content/support.html +++ b/assets/webconfig/content/support.html @@ -54,30 +54,30 @@

Amazon

-

Paypal

+

PayPal

Donation:

Paypal
diff --git a/assets/webconfig/favicon.png b/assets/webconfig/favicon.png index ae52d10a..ebf71108 100644 Binary files a/assets/webconfig/favicon.png and b/assets/webconfig/favicon.png differ diff --git a/assets/webconfig/fonts/fontawesome-webfont.svg b/assets/webconfig/fonts/fontawesome-webfont.svg index 8b66187f..6131ed16 100644 --- a/assets/webconfig/fonts/fontawesome-webfont.svg +++ b/assets/webconfig/fonts/fontawesome-webfont.svg @@ -1,685 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + \ No newline at end of file diff --git a/assets/webconfig/i18n/cs.json b/assets/webconfig/i18n/cs.json index 4a5899ee..204b23d4 100644 --- a/assets/webconfig/i18n/cs.json +++ b/assets/webconfig/i18n/cs.json @@ -258,7 +258,6 @@ "edt_conf_fbs_heading_title": "Flatbuffers Server\n", "edt_conf_fbs_timeout_expl": "Pokud nebudou v daném časovém období přijata žádná data, bude komponenta (soft) deaktivována.", "edt_conf_fbs_timeout_title": "Časový limit", - "edt_conf_fg_device_title": "Zařízení", "edt_conf_fg_display_expl": "Vyberte, která plocha by měla být zachycena (nastavení více monitorů)", "edt_conf_fg_display_title": "Displej", "edt_conf_fg_frequency_Hz_expl": "Jak často se pořizují nové snímky", diff --git a/assets/webconfig/i18n/de.json b/assets/webconfig/i18n/de.json index f876dc1c..f2c1bca4 100644 --- a/assets/webconfig/i18n/de.json +++ b/assets/webconfig/i18n/de.json @@ -261,12 +261,14 @@ "edt_conf_enum_automatic": "Automatisch", "edt_conf_enum_bbclassic": "Klassisch", "edt_conf_enum_bbdefault": "Standard", + "edt_conf_enum_bbletterbox": "Letterbox", "edt_conf_enum_bbosd": "OSD", "edt_conf_enum_bgr": "BGR", "edt_conf_enum_bottom_up": "von unten nach oben", "edt_conf_enum_brg": "BRG", "edt_conf_enum_color": "Farbe", "edt_conf_enum_custom": "Benutzerdefiniert", + "edt_conf_enum_decay": "Dämpfung", "edt_conf_enum_dl_error": "nur Fehler", "edt_conf_enum_dl_informational": "informativ", "edt_conf_enum_dl_nodebug": "keine Debugausgabe", @@ -296,7 +298,6 @@ "edt_conf_fbs_heading_title": "Flatbuffers Server", "edt_conf_fbs_timeout_expl": "Wenn für die angegebene Zeit keine Daten empfangen werden, wird die Komponente (vorübergehend) deaktiviert", "edt_conf_fbs_timeout_title": "Zeitüberschreitung", - "edt_conf_fg_device_title": "Device", "edt_conf_fg_display_expl": "Gebe an, von welchem Desktop aufgenommen werden soll. (Multi Monitor Setup)", "edt_conf_fg_display_title": "Display", "edt_conf_fg_frequency_Hz_expl": "Wie schnell werden neue Bilder aufgenommen.", @@ -367,7 +368,15 @@ "edt_conf_pbs_timeout_title": "Zeitüberschreitung", "edt_conf_smooth_continuousOutput_expl": "Aktualisiere die LEDs, auch wenn das Bild sich nicht geändert hat.", "edt_conf_smooth_continuousOutput_title": "Fortlaufende Ausgabe", + "edt_conf_smooth_decay_expl": "Dämpfungsgrad. Linare Dämpfung = 1; Werte größer eins haben einen stärkeren effekt.", + "edt_conf_smooth_decay_title": "Dämpfungsgrad", + "edt_conf_smooth_dithering_expl": "Erhöhung des Farbgenauigkeitsgrad durch Berücksichtigung benachbarter Farbwerte.", + "edt_conf_smooth_dithering_title": "Dithering", "edt_conf_smooth_heading_title": "Glättung", + "edt_conf_smooth_interpolationRate_expl": "Frequenz in der Zwischenschritte zur Glättung berechnet werden.", + "edt_conf_smooth_interpolationRate_title": "Interpolationsfrequenz", + "edt_conf_smooth_outputRate_expl": "Die Ausgangfrequnz zum LED-Device", + "edt_conf_smooth_outputRate_title": "Ausgabefrequenz", "edt_conf_smooth_time_ms_expl": "Wie lange soll die Glättung Bilder sammeln?", "edt_conf_smooth_time_ms_title": "Zeit", "edt_conf_smooth_type_expl": "Algorithmus der Glättung.", @@ -457,6 +466,8 @@ "edt_dev_spec_dithering_title": "Dithering", "edt_dev_spec_dmaNumber_title": "DMA Kanal", "edt_dev_spec_gamma_title": "Gamma", + "edt_dev_spec_globalBrightnessControlMaxLevel_title": "Maximalstufe Stromstärke", + "edt_dev_spec_globalBrightnessControlThreshold_title": "Grenzwert für adaptive Stromstärke", "edt_dev_spec_gpioBcm_title": "GPIO Pin", "edt_dev_spec_gpioMap_title": "GPIO Zuweisung", "edt_dev_spec_gpioNumber_title": "GPIO Nummer", @@ -466,8 +477,6 @@ "edt_dev_spec_intervall_title": "Intervall", "edt_dev_spec_invert_title": "Invertiere Signal", "edt_dev_spec_latchtime_title": "Sperrzeit", - "edt_dev_spec_globalBrightnessControlMaxLevel_title": "Maximalstufe Stromstärke", - "edt_dev_spec_globalBrightnessControlThreshold_title": "Grenzwert für adaptive Stromstärke", "edt_dev_spec_ledIndex_title": "LED-Index", "edt_dev_spec_ledType_title": "LED-Typ", "edt_dev_spec_lightid_itemtitle": "ID", @@ -711,11 +720,13 @@ "general_comp_PROTOSERVER": "Protocol Buffers Server", "general_comp_SMOOTHING": "Glättung", "general_comp_V4L": "USB-Aufnahme", + "general_country_cn": "China", "general_country_de": "Deutschland", "general_country_es": "Spanien", "general_country_fr": "Frankreich", "general_country_it": "Italien", "general_country_nl": "Niederlande", + "general_country_ru": "Russland", "general_country_uk": "England", "general_country_us": "Amerika", "general_speech_cs": "Tschechisch", @@ -727,9 +738,11 @@ "general_speech_nl": "Niederländisch", "general_speech_pl": "Polnisch", "general_speech_ro": "Rumänisch", + "general_speech_ru": "Russisch", "general_speech_sv": "Schwedisch", "general_speech_tr": "Türkisch", "general_speech_vi": "Vietnamesisch", + "general_speech_zh-CN": "Chinesisch (vereinfacht)", "general_webui_title": "Hyperion - Web Konfiguration", "general_wiki_moreto": "Mehr Informationen zu \"$1\" findest du in unserem Wiki", "infoDialog_checklist_title": "Checkliste!", @@ -847,7 +860,6 @@ "update_versreminder": "Deine Version: $1", "wiz_atmoorb_desc2": "Nun kannst du auswählen, welche der Orbs hinzugefügt werden sollen. Mit der Position wählst du aus, wo die jeweilige Lampe \"im Bild\" sitzen soll. Deaktivierte Lampen werden nicht hinzugefügt. Als Hilfe zur Identifizierung kannst du sie mit einem Klick auf den rechten Button kurz aufleuchten lassen.", "wiz_atmoorb_intro1": "Dieser Assistent hilft dir bei der Konfiguration von Hyperion für AtmoOrb. Zu den Funktionen zählen ein automatisches Finden der AtmoOrbs, die einzelnen Lampen unterschiedlichen Bereichen im Bild zuzuordnen und weitere Einstellungen von Hyperion automatisch anzupassen. Kurz gesagt: Komplette Einrichtung mit ein paar Klicks.", - "wiz_atmoorb_noLights": "Es wurden keine AtmoOrbs gefunden! Bitte verbinde die AtmoOrbs mit dem Netzwerk oder konfiguriere sie manuell.", "wiz_atmoorb_title": "AtmoOrb Einrichtungsassistent", "wiz_cc_adjustgamma": "Gamma: Was du jetzt tun musst ist, jeden Gamma-Kanal so einstellen, dass der \"Grauverlauf\" auf den LEDs nicht grünlich/rötlich/bläulich aussieht. Neutral ist übrigens 1.0. Beispiel: Sollte dein grau etwas rötlich sein bedeutet dies, dass du dein Gamma für Rot erhöhen musst, um den Rot-Anteil zu verringern (Je mehr Gamma, desto weniger Farbe).", "wiz_cc_adjustit": "Verändere dein \"$1\", bis du zufrieden bist. Beachte: Je mehr du reduzierst bzw. von dem Standardwert abweichst, je mehr veränderst du den maximalen Farbraum. Das betrifft alle Farben, die daraus abgeleitet werden. Je nach TV/LED Farbspektrum sind die Ergebnisse unterschiedlich.", @@ -871,6 +883,10 @@ "wiz_cc_testintrok": "Klicke auf einen Button, um eines der Testvideos abzuspielen.", "wiz_cc_testintrowok": "Unter folgendem Link findest du ein paar Testvideos zum Herunterladen und Abspielen:", "wiz_cc_title": "Farbkalibrierungs Assistent", + "wiz_cololight_desc2": "Nun kannst du auswählen, welche der Lampen hinzugefügt werden sollen. Als Hilfe zur Identifizierung kannst du sie mit einem Klick auf den rechten Button kurz aufleuchten lassen.", + "wiz_cololight_intro1": "Dieser Assistent hilft dir bei der Konfiguration von Hyperion für Cololight. Zu den Funktionen zählen ein automatisches Finden der Cololights und weitere Einstellungen von Hyperion automatisch anzupassen. Kurz gesagt: Komplette Einrichtung mit ein paar Klicks.
Achtung: Wenn Du ein Cololight Strip benutzt, musst Du ggf. die Anzahl der LEDs und das Layout manuell anpassen.", + "wiz_cololight_noprops": "Auf die Device Eigenschaften kann nicht zugegriffen werden - Konfiguriere die Anzahl der LEDs manuell.", + "wiz_cololight_title": "Cololight Einrichtungsassistent", "wiz_guideyou": "Der $1 wird dich durch die Konfiguration leiten, drücke dazu einfach den Button!", "wiz_hue_blinkblue": "Lasse ID $1 blau aufleuchten", "wiz_hue_clientkey": "Clientkey:", @@ -904,6 +920,7 @@ "wiz_identify_light": "Identifiziere $1", "wiz_ids_disabled": "Deaktiviert", "wiz_ids_entire": "Ganzes Bild", + "wiz_noLights": "Es wurden keine $1s gefunden! Bitte verbinde die $1s mit dem Netzwerk oder konfiguriere sie manuell.", "wiz_pos": "Position/Status", "wiz_rgb_expl": "Der Farbpunkt ändert alle x Sekunden die Farbe (rot, grün), zur selben Zeit ändern deine LEDs die Farbe ebenfalls. Beantworte die Fragen unten, um deine RGB Byte Reihenfolge zu überprüfen/korrigieren.", "wiz_rgb_intro1": "Dieser Assistent wird dir dabei helfen die richtige Byte Reihenfolge für deine LEDs zu finden. Klicke auf Fortfahren, um zu beginnen.", @@ -916,7 +933,6 @@ "wiz_wizavail": "Assistent verfügbar", "wiz_yeelight_desc2": "Nun kannst du auswählen, welche der Lampen hinzugefügt werden sollen. Mit der Position wählst du aus, wo die jeweilige Lampe \"im Bild\" sitzen soll. Deaktivierte Lampen werden nicht hinzugefügt. Als Hilfe zur Identifizierung kannst du sie mit einem Klick auf den rechten Button kurz aufleuchten lassen.", "wiz_yeelight_intro1": "Dieser Assistent hilft dir bei der Konfiguration von Hyperion für Yeelight. Zu den Funktionen zählen ein automatisches Finden der Yeelights, die einzelnen Lampen unterschiedlichen Bereichen im Bild zuzuordnen und weitere Einstellungen von Hyperion automatisch anzupassen. Kurz gesagt: Komplette Einrichtung mit ein paar Klicks.", - "wiz_yeelight_noLights": "Es wurden keine Yeelights gefunden! Bitte verbinde die Yeelights mit dem Netzwerk oder konfiguriere sie manuell.", "wiz_yeelight_title": "Yeelight Einrichtungsassistent", "wiz_yeelight_unsupported": "Nicht unterstützt" -} +} \ No newline at end of file diff --git a/assets/webconfig/i18n/en.json b/assets/webconfig/i18n/en.json index 536bafa8..037cadd8 100644 --- a/assets/webconfig/i18n/en.json +++ b/assets/webconfig/i18n/en.json @@ -261,12 +261,14 @@ "edt_conf_enum_automatic": "Automatic", "edt_conf_enum_bbclassic": "Classic", "edt_conf_enum_bbdefault": "Default", + "edt_conf_enum_bbletterbox": "Letterbox", "edt_conf_enum_bbosd": "OSD", "edt_conf_enum_bgr": "BGR", "edt_conf_enum_bottom_up": "Bottom up", "edt_conf_enum_brg": "BRG", "edt_conf_enum_color": "Color", "edt_conf_enum_custom": "Custom", + "edt_conf_enum_decay": "Decay", "edt_conf_enum_dl_error": "Error", "edt_conf_enum_dl_informational": "Informational", "edt_conf_enum_dl_nodebug": "No Debug output", @@ -296,7 +298,6 @@ "edt_conf_fbs_heading_title": "Flatbuffers Server", "edt_conf_fbs_timeout_expl": "If no data are received for the given period, the component will be (soft) disabled.", "edt_conf_fbs_timeout_title": "Timeout", - "edt_conf_fg_device_title": "Device", "edt_conf_fg_display_expl": "Select which desktop should be captured (multi monitor setup)", "edt_conf_fg_display_title": "Display", "edt_conf_fg_frequency_Hz_expl": "How fast new pictures are captured", @@ -367,7 +368,15 @@ "edt_conf_pbs_timeout_title": "Timeout", "edt_conf_smooth_continuousOutput_expl": "Update the LEDs even there is no changed picture.", "edt_conf_smooth_continuousOutput_title": "Continuous output", + "edt_conf_smooth_decay_expl": "The speed of decay. 1 is linear, greater values are have stronger effect.", + "edt_conf_smooth_decay_title": "Decay-Power", + "edt_conf_smooth_dithering_expl": "Improve color accuracy at high output speeds by alternating between adjacent colors.", + "edt_conf_smooth_dithering_title": "Dithering", "edt_conf_smooth_heading_title": "Smoothing", + "edt_conf_smooth_interpolationRate_expl": "Speed of the calculation of smooth intermediate frames.", + "edt_conf_smooth_interpolationRate_title": "Interpolation Rate", + "edt_conf_smooth_outputRate_expl": "The output speed to your led controller.", + "edt_conf_smooth_outputRate_title": "Output Rate", "edt_conf_smooth_time_ms_expl": "How long should the smoothing gather pictures?", "edt_conf_smooth_time_ms_title": "Time", "edt_conf_smooth_type_expl": "Type of smoothing.", @@ -457,6 +466,8 @@ "edt_dev_spec_dithering_title": "Dithering", "edt_dev_spec_dmaNumber_title": "DMA channel", "edt_dev_spec_gamma_title": "Gamma", + "edt_dev_spec_globalBrightnessControlMaxLevel_title": "Max Current Level", + "edt_dev_spec_globalBrightnessControlThreshold_title": "Adaptive Current Threshold", "edt_dev_spec_gpioBcm_title": "GPIO Pin", "edt_dev_spec_gpioMap_title": "GPIO mapping", "edt_dev_spec_gpioNumber_title": "GPIO number", @@ -466,8 +477,6 @@ "edt_dev_spec_intervall_title": "Interval", "edt_dev_spec_invert_title": "Invert signal", "edt_dev_spec_latchtime_title": "Latch time", - "edt_dev_spec_globalBrightnessControlMaxLevel_title": "Max Current Level", - "edt_dev_spec_globalBrightnessControlThreshold_title": "Adaptive Current Threshold", "edt_dev_spec_ledIndex_title": "LED index", "edt_dev_spec_ledType_title": "LED Type", "edt_dev_spec_lightid_itemtitle": "ID", @@ -712,7 +721,7 @@ "general_comp_PROTOSERVER": "Protocol Buffers Server", "general_comp_SMOOTHING": "Smoothing", "general_comp_V4L": "USB Capture", - "general_country_cn": "China", + "general_country_cn": "China", "general_country_de": "Germany", "general_country_es": "Spain", "general_country_fr": "France", @@ -730,14 +739,11 @@ "general_speech_nl": "Dutch", "general_speech_pl": "Polish", "general_speech_ro": "Romanian", - "general_speech_ru": "Russian", - "general_speech_sv": "Swedish", - "general_speech_tr": "Turkish", - "general_speech_vi": "Vietnamese", - "general_speech_zh-CN": "Chinese (simplified)", + "general_speech_ru": "Russian", "general_speech_sv": "Swedish", "general_speech_tr": "Turkish", "general_speech_vi": "Vietnamese", + "general_speech_zh-CN": "Chinese (simplified)", "general_webui_title": "Hyperion - Web Configuration", "general_wiki_moreto": "More information to \"$1\" at our Wiki", "infoDialog_checklist_title": "Checklist!", @@ -855,7 +861,6 @@ "update_versreminder": "Your version: $1", "wiz_atmoorb_desc2": "Now choose which Orbs should be added. The position assigns the lamp to a specific position on your \"picture\". Disabled lamps won't be added. To identify single lamps press the button on the right.", "wiz_atmoorb_intro1": "This wizards configures Hyperion for AtmoOrbs. Features are the AtmoOrb auto detection, setting each light to a specific position on your picture or disable it and optimise the Hyperion settings automatically! So in short: All you need are some clicks and you are done!", - "wiz_atmoorb_noLights": "No AtmoOrbs found! Please get the lights connected to the network or configure them manually.", "wiz_atmoorb_title": "AtmoOrb Wizard", "wiz_cc_adjustgamma": "Gamma: What you have to do is, adjust gamma levels of each channel until you have the same perceived amount of each channel. Hint: Neutral is 1.0! For example, if your Grey is a bit reddish it means that you have to increase red gamma to reduce the amount of red (the more gamma, the less amount of color).", "wiz_cc_adjustit": "Adjust your \"$1\", until your are fine with it. Take notice: The more you adjust away from the default value the color spectrum will be limited (Also for all colors in between). Depending on TV/LED color spectrum the results will vary.", @@ -879,6 +884,10 @@ "wiz_cc_testintrok": "Push on a button below to start a test video.", "wiz_cc_testintrowok": "Check out the following link to download test videos:", "wiz_cc_title": "Colour calibration wizard", + "wiz_cololight_desc2": "Now choose which Cololights should be added. To identify single lights, press the button on the right.", + "wiz_cololight_intro1": "This wizards configures Hyperion for the Cololight system. Features are the Cololight auto detection and tune the Hyperion settings automatically! In short: All you need are some clicks and you are done!
Note: In case of Cololight Strip, you might need to manually correct the LED count and layout.", + "wiz_cololight_noprops": "Not able to get device properties - Define Hardware LED count manually", + "wiz_cololight_title": "Cololight Wizard", "wiz_guideyou": "The $1 will guide you through the settings. Just press the button!", "wiz_hue_blinkblue": "Let ID $1 light up blue", "wiz_hue_clientkey": "Clientkey:", @@ -912,6 +921,7 @@ "wiz_identify_light": "Identify $1", "wiz_ids_disabled": "Deactivated", "wiz_ids_entire": "Whole picture", + "wiz_noLights": "No $1 found! Please get the lights connected to the network or configure them manually.", "wiz_pos": "Position/State", "wiz_rgb_expl": "The color dot switches every x seconds the color (red, green), at the same time your LEDs switch the color too. Answer the questions at the bottom to check/correct your byte order.", "wiz_rgb_intro1": "This wizard will guide you through the finding process of the correct color order for your leds. Click on continue to begin.", @@ -924,7 +934,6 @@ "wiz_wizavail": "Wizard available", "wiz_yeelight_desc2": "Now choose which lamps should be added. The position assigns the lamp to a specific position on your \"picture\". Disabled lamps won't be added. To identify single lamps press the button on the right.", "wiz_yeelight_intro1": "This wizards configures Hyperion for the Yeelight system. Features are the Yeelighs' auto detection, setting each light to a specific position on your picture or disable it and tune the Hyperion settings automatically! So in short: All you need are some clicks and you are done!", - "wiz_yeelight_noLights": "No Yeelights found! Please get the lights connected to the network or configure them manually.", "wiz_yeelight_title": "Yeelight Wizard", "wiz_yeelight_unsupported": "Unsupported" -} +} \ No newline at end of file diff --git a/assets/webconfig/i18n/es.json b/assets/webconfig/i18n/es.json index f8ff7e2c..2d10ad2e 100644 --- a/assets/webconfig/i18n/es.json +++ b/assets/webconfig/i18n/es.json @@ -13,18 +13,18 @@ "about_3rd_party_licenses": "Licencias de terceros", "about_3rd_party_licenses_error": "Tuvimos problemas para recopilar información de licencias de terceros a través de la Web.
Por favor, sigue este enlace para acceder al Recurso GitHub.", "about_build": "Build", - "about_builddate": "la fecha de creación", + "about_builddate": "Fecha de creación", "about_contribute": "¡Añade más idiomas a Hyperion!", "about_credits": "¡Créditos a todos estos desarrolladores!", "about_resources": "$1 librerías", "about_translations": "Traducciones", "about_version": "Versión", "conf_colors_blackborder_intro": "Omite bordes negros dondequiera que estén. Cada modo usa otro algoritmo de detección que está ajustado para situaciones especiales. Sube el umbral si no percibes funcionamiento.", - "conf_colors_color_intro": "Cree uno o más perfiles de calibración, ajuste cada color, brillo, linealización y más.", + "conf_colors_color_intro": "Crea uno o más perfiles de calibración, ajusta cada color, brillo, linealización y más.", "conf_colors_smoothing_intro": "El suavizado aplana los cambios de color/brillo para reducir la distracción molesta.", - "conf_effect_bgeff_intro": "Definir un efecto de fondo, que se muestra durante \"inactivo\". (También temporalmente a través de Observador Kodi)", - "conf_effect_fgeff_intro": "Define un efecto de arranque o color, que se muestra durante el inicio de Hyperion durante una duración definida.", - "conf_effect_path_intro": "Define más rutas de efectos si es necesario.", + "conf_effect_bgeff_intro": "Definir un efecto/color de fondo, que se muestra durante Hyperion \"inactivo\". Comienza siempre con el canal prioritario 255.", + "conf_effect_fgeff_intro": "Define un efecto de arranque o color, que se muestra durante el inicio de Hyperion durante la duración definida.", + "conf_effect_path_intro": "Cargar los efectos de las rutas definidas. Además, puedes desactivar los efectos individuales por nombre para ocultarlos de todas las listas de efectos.", "conf_general_createInst_btn": "Crear Instalaciñón", "conf_general_impexp_expbtn": "Exportar", "conf_general_impexp_impbtn": "Importar", @@ -34,15 +34,15 @@ "conf_general_inst_actionhead": "Acción", "conf_general_inst_delreq_h": "Eliminar la instalación de hardware LED", "conf_general_inst_delreq_t": "¿Está seguro de que desea borrar la instalación \"$1\"? También se borrarán todos los ajustes.", - "conf_general_inst_desc": "Utilice hardware LED diferente al mismo tiempo. Cada instalación se ejecuta de forma independiente, lo que permite diferentes diseños de LEDs y configuraciones de calibración. Las instalaciones en ejecución están disponibles en la barra de iconos superior", + "conf_general_inst_desc": "Utiliza hardware LED diferente al mismo tiempo. Cada instalación se ejecuta de forma independiente, lo que permite diferentes diseños de LEDs y configuraciones de calibración. Las instalaciones en ejecución están disponibles en la barra de iconos superior", "conf_general_inst_name_title": "Nombre de la nueva instalación", "conf_general_inst_namehead": "Nombre de la instalación", "conf_general_inst_renreq_t": "Introduce un nuevo nombre para tu instalación en el campo de abajo.", "conf_general_inst_title": "Gestión de instalaciones de hardware LED", "conf_general_intro": "Ajustes básicos de Hyperion y WebUI que no encajan en otra categoría.", "conf_general_label_title": "Configuración General", - "conf_grabber_fg_intro": "La captura de la plataforma es la captura del sistema local como fuente de entrada, en la que Hyperion está instalado.", - "conf_grabber_v4l_intro": "La captura USB es un dispositivo (de captura) conectado a través de USB que se utiliza para introducir imágenes de origen para su procesamiento.", + "conf_grabber_fg_intro": "La plataforma de captura es la captura del sistema local como fuente de entrada, en la que Hyperion está instalado.", + "conf_grabber_v4l_intro": "La captura USB es un dispositivo (de captura) conectado a través de USB que se utiliza para introducir imágenes de origen para su procesado.", "conf_helptable_expl": "Explicación", "conf_helptable_option": "Opción", "conf_leds_contr_label_contrtype": "Tipo de controladora:", @@ -54,7 +54,7 @@ "conf_leds_layout_checkp1": "El led negro es tu primer led, el primer led es el punto donde introduces tu señal de datos.", "conf_leds_layout_checkp2": "La disposición es siempre la vista delantera de tu TV, nunca la visión posterior.", "conf_leds_layout_checkp3": "Asegúrate de que la dirección es correcta. Los leds grises indican los led número 2 y 3 para visualizar la dirección de los datos.", - "conf_leds_layout_checkp4": "Hueco: Para crear un hueco, ignóralo primero cuando defina Superior/Inferior/Izquierda/Derecha y ajusta después la longitud del hueco para eliminar una cantidad de leds. Modifica la posición del hueco hasta que coincida.", + "conf_leds_layout_checkp4": "Hueco: Para crear un hueco, ignóralo primero cuando definas Superior/Inferior/Izquierda/Derecha y ajusta después la longitud del hueco para eliminar una cantidad de leds. Modifica la posición del hueco hasta que coincida.", "conf_leds_layout_cl_bottom": "Inferior", "conf_leds_layout_cl_bottomleft": "Inferior Izquierda (Esquina)", "conf_leds_layout_cl_bottomright": "Inferior Derecha (Esquina)", @@ -80,7 +80,7 @@ "conf_leds_layout_cl_vleddepth": "Profundidad LED vertical", "conf_leds_layout_frame": "Disposición Clásica (Marco LED)", "conf_leds_layout_generatedconf": "Configuración LED Generada/Actual", - "conf_leds_layout_intro": "Necesitas también un diseño led, que refleje tus posiciones led. La disposición clásica es el marco generalmente usado de la TV, pero también apoyamos la creación de matriz led (paredes led). La vista en esta disposición es SIEMPRE del FRENTE de su TV.", + "conf_leds_layout_intro": "Necesitas también un diseño led, que refleje tus posiciones led. La disposición clásica es el marco generalmente usado de la TV, pero también apoyamos la creación de matriz led (paredes led). La vista en esta disposición es SIEMPRE del FRENTE de tu TV.", "conf_leds_layout_ma_cabling": "Cableado", "conf_leds_layout_ma_horiz": "Horizontal", "conf_leds_layout_ma_optbottomleft": "Inferior Izquierda", @@ -104,13 +104,13 @@ "conf_leds_layout_preview_originCL": "Creado a partir de: Disposición Clásica (Marco LED)", "conf_leds_layout_preview_originMA": "Creado a partir de: Disposición en Matriz (Pared LED)", "conf_leds_layout_preview_originTEXT": "Creado a partir de: Cuadro de texto", - "conf_leds_layout_preview_totalleds": "Total de LEDs:$1", + "conf_leds_layout_preview_totalleds": "Total de LEDs: $1", "conf_leds_layout_ptl": "Apunta arriba a la izquierda", "conf_leds_layout_ptlh": "Horizontal", - "conf_leds_layout_ptln": "Trapezpuntos", + "conf_leds_layout_ptln": "Triplepuntos", "conf_leds_layout_ptlv": "Vertical", "conf_leds_layout_ptr": "Apunta arriba a la derecha", - "conf_leds_layout_textf1": "Este campo de texto muestra de forma predeterminada el diseño cargado actual y se sobrescribirá si genera uno nuevo con las opciones siguientes. Opcional, puede realizar más ediciones.", + "conf_leds_layout_textf1": "Este campo de texto muestra de forma predeterminada el diseño cargado actual y se sobrescribirá si generas uno nuevo con las opciones siguientes. Opcional, puedes realizar más ediciones.", "conf_leds_nav_label_ledcontroller": "Controlador LED", "conf_leds_nav_label_ledlayout": "Disposición LED", "conf_leds_optgroup_RPiGPIO": "GPIO RPi", @@ -118,7 +118,7 @@ "conf_leds_optgroup_RPiSPI": "SPI RPi", "conf_leds_optgroup_debug": "Depurar", "conf_leds_optgroup_network": "Red", - "conf_leds_optgroup_usb": "USB", + "conf_leds_optgroup_usb": "USB/Serial", "conf_logging_btn_autoscroll": "Desplazamiento automático", "conf_logging_btn_pbupload": "Crear informe para solicitud de soporte", "conf_logging_contpolicy": "Informe de la Política de Privacidad", @@ -143,11 +143,11 @@ "conf_network_tok_diaMsg": "Aquí está tu nuevo Token que puede ser utilizado para conceder a una aplicación acceso a la API de Hyperion. Por razones de seguridad no puedes volver a verla, así que úsala/anótala ahora.", "conf_network_tok_diaTitle": "¡Nuevo Token creado!", "conf_network_tok_grantMsg": "Una aplicación solicitó un token para acceder a la API de Hyperion. ¿Quiere conceder el acceso? Por favor, ¡verifique la información proporcionada!", - "conf_network_tok_grantT": "Aplicación solicita Token", + "conf_network_tok_grantT": "Solicitudes de aplicación Token", "conf_network_tok_intro": "Aquí puedes crear y eliminar Tokens para la autenticación de la API. Los Tokens creados sólo se mostrarán una vez.", "conf_network_tok_lastuse": "Último uso", "conf_network_tok_title": "Gestión de Tokens", - "conf_webconfig_label_intro": "Ajustes de configuración web. Editar con sabiduría.", + "conf_webconfig_label_intro": "Ajustes de configuración web. Editar sabiamente.", "dashboard_active_instance": "Instalación seleccionada", "dashboard_alert_message_confedit": "Se ha modificado la configuración de Hyperion. Para aplicarlo, reinicia Hyperion.", "dashboard_alert_message_confedit_t": "Configuración modificada", @@ -171,7 +171,7 @@ "dashboard_infobox_label_watchedversionbranch": "Rama de la versión visualizada:", "dashboard_infobox_message_updatesuccess": "Ejecutas la última versión de Hyperion.", "dashboard_infobox_message_updatewarning": "¡Una versión más nueva de Hyperion está disponible! ($1)", - "dashboard_label_intro": "El cuadro de mandos te ofrece una visión general sobre el estado de Hyperion y te mostrará las últimas noticias del Blog de Hyperion.", + "dashboard_label_intro": "El cuadro de mandos te ofrece una visión general sobre el estado de Hyperion y te muestra las últimas noticias del Blog de Hyperion.", "dashboard_message_default_password": "Establecida la contraseña predeterminada para la WebUi. Recomendamos encarecidamente cambiar esto.", "dashboard_message_default_password_t": "Establecida la contraseña predeterminada de WebUi", "dashboard_message_do_not_show_again": "No mostrar este mensaje de nuevo", @@ -201,34 +201,34 @@ "edt_conf_bb_maxInconsistentCnt_title": "maxInconsistentCn", "edt_conf_bb_mode_expl": "Algoritmo para procesamiento. (Ver Wiki)", "edt_conf_bb_mode_title": "Modo", - "edt_conf_bb_threshold_expl": "Si la detección no funciona, Sube el umbral para ajustar en negro \"grisáceo\"", + "edt_conf_bb_threshold_expl": "Si la detección no funciona, sube el umbral para ajustar en negro \"grisáceo\"", "edt_conf_bb_threshold_title": "Umbral", "edt_conf_bb_unknownFrameCnt_expl": "Número de fotogramas sin detección antes de que el borde se establezca en 0.", "edt_conf_bb_unknownFrameCnt_title": "unknownFrameCnt", - "edt_conf_bge_heading_title": "Efecto de fondo/Color", + "edt_conf_bge_heading_title": "Efecto/color de fondo", "edt_conf_bobls_heading_title": "Servidor Boblight", - "edt_conf_color_backlightColored_expl": "Añada un poco de color a su retroiluminación.", + "edt_conf_color_backlightColored_expl": "Añade un poco de color a tu retroiluminación.", "edt_conf_color_backlightColored_title": "Retroiluminación colorida", - "edt_conf_color_backlightThreshold_expl": "La cantidad mínima de brillo (retroiluminación). Desactivado durante los efectos, colores y en estado \"Off\"", + "edt_conf_color_backlightThreshold_expl": "La cantidad mínima de brillo (retroiluminación). Desactivado durante los efectos, colores y en estado \"Apagado\"", "edt_conf_color_backlightThreshold_title": "Umbral de retroiluminación", "edt_conf_color_black_expl": "El valor negro calibrado.", "edt_conf_color_black_title": "negro", "edt_conf_color_blue_expl": "El valor azulcalibrado.", "edt_conf_color_blue_title": "azul", - "edt_conf_color_brightnessComp_expl": "Compensa las diferencias de brillo entre RGB, CMY y blanco. 100 significa compensación completa, 0 sin compensación", + "edt_conf_color_brightnessComp_expl": "Compensa las diferencias de brillo entre el rojo, verde, azul, amarillo y blanco. 100 significa compensación total, 0 sin compensación", "edt_conf_color_brightnessComp_title": "Compensación de brillo", "edt_conf_color_brightness_expl": "De 0,0 a 0,5 el brillo se lineariza, de 0,5 a 1,0 cian, magenta, amarillo es hasta 2x más brillante y blanco 3x.", "edt_conf_color_brightness_title": "Brillo máximo", - "edt_conf_color_channelAdjustment_header_expl": "Ajustes para color, brillo, linealización y más.", + "edt_conf_color_channelAdjustment_header_expl": "Crear perfiles de color que puedan ser asignados a un componente específico. Ajustar el color, la gama, el brillo, la compensación y más.", "edt_conf_color_channelAdjustment_header_itemtitle": "Perfil", "edt_conf_color_channelAdjustment_header_title": "Ajustes del canal de color", "edt_conf_color_cyan_expl": "El valor cian calibrado.", "edt_conf_color_cyan_title": "cyan", - "edt_conf_color_gammaBlue_expl": "El gamma del azul.", + "edt_conf_color_gammaBlue_expl": "La gamma del azul. 1.0 es neutral. Por encima de 1,0 reduce el azul, por debajo de 1,0 añade azul.", "edt_conf_color_gammaBlue_title": "azul gamma", - "edt_conf_color_gammaGreen_expl": "El gamma del verde.", + "edt_conf_color_gammaGreen_expl": "El gamma de verde. 1.0 es neutral. Por encima de 1.0 reduce el verde, por debajo de 1.0 añade verde.", "edt_conf_color_gammaGreen_title": "verde gamma", - "edt_conf_color_gammaRed_expl": "El gamma de rojo.", + "edt_conf_color_gammaRed_expl": "La gamma de rojo. 1.0 es neutral. Por encima de 1.0 reduce el rojo, por debajo de 1.0 añade rojo.", "edt_conf_color_gammaRed_title": "Rojo gamma", "edt_conf_color_green_expl": "El valor verde calibrado.", "edt_conf_color_green_title": "verde", @@ -247,31 +247,33 @@ "edt_conf_color_white_title": "blanco", "edt_conf_color_yellow_expl": "El valor amarillo calibrado.", "edt_conf_color_yellow_title": "amarillo", - "edt_conf_effp_disable_expl": "Añada nombres de efectos aquí para deshabilitar/ocultarlos de todas las listas de efectos.", + "edt_conf_effp_disable_expl": "Añade nombres de efectos aquí para deshabilitar/ocultarlos de todas las listas de efectos.", "edt_conf_effp_disable_itemtitle": "Efecto", "edt_conf_effp_disable_title": "Efectos Deshabilitados", "edt_conf_effp_heading_title": "Rutas del efecto", "edt_conf_effp_paths_expl": "Puedes definir más carpetas que contengan efectos. El configurador de efectos siempre guardará en la primera carpeta.", "edt_conf_effp_paths_itemtitle": "Ruta", "edt_conf_effp_paths_title": "Ruta(s) del efecto", - "edt_conf_enum_NO_CHANGE": "Auto", + "edt_conf_enum_NO_CHANGE": "Automático", "edt_conf_enum_NTSC": "NTSC", "edt_conf_enum_PAL": "PAL", "edt_conf_enum_SECAM": "SECAM", "edt_conf_enum_automatic": "Automático", "edt_conf_enum_bbclassic": "Clásico", "edt_conf_enum_bbdefault": "Predeterminado", + "edt_conf_enum_bbletterbox": "Letterbox", "edt_conf_enum_bbosd": "OSD", "edt_conf_enum_bgr": "BGR", "edt_conf_enum_bottom_up": "De abajo a arriba", "edt_conf_enum_brg": "BRG", "edt_conf_enum_color": "Color", "edt_conf_enum_custom": "Personalizado", + "edt_conf_enum_decay": "Degradación", "edt_conf_enum_dl_error": "Error", - "edt_conf_enum_dl_informational": "Informacional", + "edt_conf_enum_dl_informational": "Informativo", "edt_conf_enum_dl_nodebug": "No hay depuración", "edt_conf_enum_dl_statechange": "Cambio de estado", - "edt_conf_enum_dl_verbose": "Verborrea", + "edt_conf_enum_dl_verbose": "Verboso", "edt_conf_enum_dl_verbose1": "Verbosidad nivel 1", "edt_conf_enum_dl_verbose2": "Verbosidad nivel 2", "edt_conf_enum_dl_verbose3": "Verbosidad nivel 3", @@ -282,7 +284,7 @@ "edt_conf_enum_left_right": "De izquierda a derecha", "edt_conf_enum_linear": "Lineal", "edt_conf_enum_logdebug": "Depurar", - "edt_conf_enum_logsilent": "Silencio", + "edt_conf_enum_logsilent": "Silenciado", "edt_conf_enum_logverbose": "Detallado", "edt_conf_enum_logwarn": "Advertencia", "edt_conf_enum_multicolor_mean": "Multicolor", @@ -296,19 +298,18 @@ "edt_conf_fbs_heading_title": "Servidor de Flatbuffers", "edt_conf_fbs_timeout_expl": "Si no se reciben datos para el período dado, el componente se desactivará (suavemente).", "edt_conf_fbs_timeout_title": "Tiempo de espera", - "edt_conf_fg_device_title": "Dispositivo", "edt_conf_fg_display_expl": "Selecciona qué escritorio debe ser capturado (configuración de varios monitores)", "edt_conf_fg_display_title": "Visualización", "edt_conf_fg_frequency_Hz_expl": "Cómo de rápido se capturan las nuevas imágenes", "edt_conf_fg_frequency_Hz_title": "Frecuencia de captura", "edt_conf_fg_heading_title": "Captura de Plataforma", - "edt_conf_fg_height_expl": "Reducir la imagen a esta altura, como material raw necesita un montón de tiempo de CPU.", + "edt_conf_fg_height_expl": "Reducir la imagen a este alto, ya que la imagen en bruto necesita mucho tiempo de CPU.", "edt_conf_fg_height_title": "Altura", - "edt_conf_fg_pixelDecimation_expl": "Reduce el tamaño de la imagen (factor) en función del tamaño original. Un factor de 1 significa que no hay cambios", + "edt_conf_fg_pixelDecimation_expl": "Reducir el tamaño de la imagen (factor) basado en el tamaño original. Un factor de 1 significa que no hay cambios", "edt_conf_fg_pixelDecimation_title": "Diezmado de la imagen", "edt_conf_fg_type_expl": "Tipo de captura de plataforma, por defecto es 'auto'", "edt_conf_fg_type_title": "Tipo", - "edt_conf_fg_width_expl": "Encoge la imagen a este ancho, como imagen raw necesita un montón de tiempo de CPU.", + "edt_conf_fg_width_expl": "Reducir la imagen a este ancho, ya que la imagen en bruto necesita mucho tiempo de CPU.", "edt_conf_fg_width_title": "Anchura", "edt_conf_fge_color_expl": "Si el tipo es \"Color\", selecciona un color de tu elección.", "edt_conf_fge_color_title": "Color", @@ -345,29 +346,37 @@ "edt_conf_instC_v4lEnable_title": "Habilitar captura USB", "edt_conf_instCapture_heading_title": "Captura de instancias", "edt_conf_js_heading_title": "Servidor JSON", - "edt_conf_log_heading_title": "Registrando", + "edt_conf_log_heading_title": "Registro", "edt_conf_log_level_expl": "Dependiendo del nivel de registro verás menos o más mensajes en tu registro.", "edt_conf_log_level_title": "Nivel de registro", - "edt_conf_net_apiAuth_expl": "Imponer a todas las aplicaciones que utilizan la API de Hyperion a autenticarse contra Hyperion (Excepción ver \"Autenticación de la API local\"). Mayor seguridad, ya que tú controlas el acceso y lo revocas en cualquier momento.", + "edt_conf_net_apiAuth_expl": "Imponer a todas las aplicaciones que utilizan la API de Hyperion a autenticarse contra Hyperion (Excepción: \"Autenticación de la API local\"). Mayor seguridad, ya que se controla el acceso y se revoca en cualquier momento.", "edt_conf_net_apiAuth_title": "Autenticación de API", "edt_conf_net_heading_title": "Red", "edt_conf_net_internetAccessAPI_expl": "Permite el acceso a la API/interfaz web de Hyperion desde Internet, desactivado para mayor seguridad.", "edt_conf_net_internetAccessAPI_title": "Acceso a la API de Internet", - "edt_conf_net_ipWhitelist_expl": "En su lugar, puedes hacer una lista blanca de direcciones IP permitiendo que todas las conexiones de Internet se conectan a la API/interfaz web de Hyperion.", + "edt_conf_net_ipWhitelist_expl": "Puedes hacer una lista blanca de direcciones IP en vez de permitir que todas las conexiones de internet se conecten a la API/Webinterface de Hyperion.", "edt_conf_net_ipWhitelist_title": "IPs de la lista blanca", "edt_conf_net_ip_itemtitle": "IP", - "edt_conf_net_localAdminAuth_expl": "Cuando está habilitado, el acceso de administración desde tu red doméstica necesita una contraseña.", + "edt_conf_net_localAdminAuth_expl": "Cuando está habilitado, el acceso de administración desde tu red local necesita una contraseña.", "edt_conf_net_localAdminAuth_title": "Autenticación de la API de administración local", "edt_conf_net_localApiAuth_expl": "Cuando está habilitado, las conexiones de tu red doméstica también necesitan autenticarse contra Hyperion.", "edt_conf_net_localApiAuth_title": "Autenticación de API local", - "edt_conf_net_restirctedInternetAccessAPI_expl": "Puede restringir el acceso a la API a través de Internet a determinadas IP.", + "edt_conf_net_restirctedInternetAccessAPI_expl": "Puedes restringir el acceso a la API a través de Internet a determinadas IP.", "edt_conf_net_restirctedInternetAccessAPI_title": "Restringir a las IP", "edt_conf_pbs_heading_title": "Servidor de Buffers de Protocolo", "edt_conf_pbs_timeout_expl": "Si no se reciben datos para el período dado, el componente se desactivará (suavemente).", "edt_conf_pbs_timeout_title": "Tiempo de espera", - "edt_conf_smooth_continuousOutput_expl": "Actualizar los leds incluso si no hay cambio de imagen.", + "edt_conf_smooth_continuousOutput_expl": "Actualizar los LED incluso si no hay cambio de imagen.", "edt_conf_smooth_continuousOutput_title": "Salida continua", + "edt_conf_smooth_decay_expl": "La velocidad de degradación. 1 es lineal, los valores mayores tienen un efecto más fuerte.", + "edt_conf_smooth_decay_title": "Potencia de degradación", + "edt_conf_smooth_dithering_expl": "Mejorar la precisión del color a altas velocidades de salida alternando entre colores adyacentes.", + "edt_conf_smooth_dithering_title": "Tramado/Dithering", "edt_conf_smooth_heading_title": "Suavizado", + "edt_conf_smooth_interpolationRate_expl": "Velocidad de cálculo de los fotogramas intermedios suaves.", + "edt_conf_smooth_interpolationRate_title": "Tasa de interpolación", + "edt_conf_smooth_outputRate_expl": "La velocidad de salida a tu controlador de leds.", + "edt_conf_smooth_outputRate_title": "Tasa de salida", "edt_conf_smooth_time_ms_expl": "¿Cuánto tiempo debe recoger las imágenes el suavizado?", "edt_conf_smooth_time_ms_title": "Tiempo", "edt_conf_smooth_type_expl": "Tipo de suavizado", @@ -388,7 +397,7 @@ "edt_conf_v4l2_cropRight_title": "Cortar derecha", "edt_conf_v4l2_cropTop_expl": "Cuenta de píxeles en la parte superior que se quitan de la imagen.", "edt_conf_v4l2_cropTop_title": "Cortar arriba", - "edt_conf_v4l2_device_expl": "La ruta a la captura USB.", + "edt_conf_v4l2_device_expl": "La ruta a la interfaz de captura USB. Ajustado en 'Automático' para la detección automática. Ejemplo: '/dev/video0'", "edt_conf_v4l2_device_title": "Dispositivo", "edt_conf_v4l2_framerate_expl": "Los fotogramas soportados por segundo del dispositivo activo", "edt_conf_v4l2_framerate_title": "Fotogramas por segundo", @@ -413,11 +422,11 @@ "edt_conf_v4l2_signalDetection_title": "Habilitar detección de señal", "edt_conf_v4l2_sizeDecimation_expl": "El factor de diezmación del tamaño", "edt_conf_v4l2_sizeDecimation_title": "Diezmación de tamaño", - "edt_conf_v4l2_standard_expl": "Selecciona el estándar de vídeo para tu región.", + "edt_conf_v4l2_standard_expl": "Selecciona el estándar de vídeo de tu región. \"Automático\" mantiene el valor elegido por la interfaz v4l2.", "edt_conf_v4l2_standard_title": "Estándar de vídeo", "edt_conf_webc_crtPath_expl": "Ruta al archivo de certificación (el formato debe ser PEM)", "edt_conf_webc_crtPath_title": "Ruta del certificado", - "edt_conf_webc_docroot_expl": "Ruta raíz de la interfaz web local (sólo para desarrolladores webui)", + "edt_conf_webc_docroot_expl": "Ruta de la raíz de la interfaz web local (sólo para desarrolladores de webui)", "edt_conf_webc_docroot_title": "Documento raíz", "edt_conf_webc_heading_title": "Configuración web", "edt_conf_webc_keyPassPhrase_expl": "Opcional: La clave puede estar protegida con una contraseña", @@ -429,7 +438,7 @@ "edt_dev_auth_key_title": "Token de autenticación", "edt_dev_enum_sub_min_cool_adjust": "Min. Ajuste fresco", "edt_dev_enum_sub_min_warm_adjust": "Min. Ajuste caliente", - "edt_dev_enum_subtract_minimum": "Restar mínimo", + "edt_dev_enum_subtract_minimum": "Restar el mínimo", "edt_dev_enum_white_off": "Blanco apagado", "edt_dev_general_colorOrder_title": "Orden de bytes RGB", "edt_dev_general_hardwareLedCount_title": "Recuento de LEDs de hardware", @@ -457,10 +466,12 @@ "edt_dev_spec_dithering_title": "Tintineo", "edt_dev_spec_dmaNumber_title": "Canal DMA", "edt_dev_spec_gamma_title": "Gamma", + "edt_dev_spec_globalBrightnessControlMaxLevel_title": "Nivel máximo actual", + "edt_dev_spec_globalBrightnessControlThreshold_title": "Umbral de adaptación actual", "edt_dev_spec_gpioBcm_title": "Pin GPIO", "edt_dev_spec_gpioMap_title": "Mapeo GPIO", "edt_dev_spec_gpioNumber_title": "Número GPIO", - "edt_dev_spec_groupId_title": "Id de grupo", + "edt_dev_spec_groupId_title": "ID de grupo", "edt_dev_spec_header_title": "Ajustes Específicos", "edt_dev_spec_interpolation_title": "Interpolación", "edt_dev_spec_intervall_title": "Intervalo", @@ -483,13 +494,13 @@ "edt_dev_spec_order_left_right_title": "2.", "edt_dev_spec_order_top_down_title": "1.", "edt_dev_spec_outputPath_title": "Ruta de salida", - "edt_dev_spec_panel_start_position": "Panel de inicio [paneles 0-max]", + "edt_dev_spec_panel_start_position": "Panel de inicio [0-paneles max]", "edt_dev_spec_panelorganisation_title": "Secuencia de numeración del panel", "edt_dev_spec_pid_title": "PID", "edt_dev_spec_port_title": "Puerto", "edt_dev_spec_printTimeStamp_title": "Añadir marca de tiempo", "edt_dev_spec_pwmChannel_title": "Canal PWM", - "edt_dev_spec_restoreOriginalState_title": "Restaurar el estado original de las luces", + "edt_dev_spec_restoreOriginalState_title": "Restaurar el estado original de las luces cuando se desactivan", "edt_dev_spec_serial_title": "Número de serie", "edt_dev_spec_spipath_title": "Ruta SPI", "edt_dev_spec_sslHSTimeoutMax_title": "Máximo tiempo de espera para el contacto con el Streamer", @@ -537,7 +548,7 @@ "edt_eff_colorevel": "Nivel de color", "edt_eff_colorone": "Color uno", "edt_eff_colorrandom": "Color aleatório", - "edt_eff_colorshift": "Cambio de Color", + "edt_eff_colorshift": "Turno de Color", "edt_eff_colorstart": "Color de inicio", "edt_eff_colorstarttime": "Tiempo para mantener el color de fin", "edt_eff_colortwo": "Color dos", @@ -556,24 +567,24 @@ "edt_eff_fadeintime": "Tiempo de fundido", "edt_eff_fadeouttime": "Tiempo de desvanecimiento", "edt_eff_flag_header": "Banderas", - "edt_eff_flag_header_desc": "Deja que tus leds brillen en los colores de tu país. Puedes seleccionar más de una bandera, que cambiará en función del tiempo de intervalo.", + "edt_eff_flag_header_desc": "Deja que tus LEDs brillen con los colores de tu país. Puedes seleccionar más de una bandera y cambiarán según el tiempo del intervalo.", "edt_eff_fps": "Fotogramas por segundo", "edt_eff_frequency": "Frecuencia", - "edt_eff_gif_header": "Sobre GIF", + "edt_eff_gif_header": "GIFs", "edt_eff_gif_header_desc": "Este efecto reproduce archivos.gif, proporciona un vídeo simple tipo bucle como efecto.", "edt_eff_height": "Altura", "edt_eff_huechange": "Cambio de color", "edt_eff_image": "Archivo de imagen", "edt_eff_interval": "Intervalo", "edt_eff_knightrider_header": "Coche Fantástico", - "edt_eff_knightrider_header_desc": "¡K.I.T.T ha vuelto! El escáner frontal del muy conocidocoche, esta vez no sólo en rojo.", + "edt_eff_knightrider_header_desc": "¡K.I.T.T ha vuelto! El escáner frontal del muy conocido coche, esta vez no sólo en rojo.", "edt_eff_ledlist": "Lista Led", "edt_eff_ledtest_header": "Prueba de Led", "edt_eff_ledtest_header_desc": "Salida giratoria: Rojo, Azul, Verde, Blanco, Negro", "edt_eff_length": "Longitud", - "edt_eff_lightclock_header": "Reloj", - "edt_eff_lightclock_header_desc": "¡Un reloj real como la luz! Ajusta los colores de las horas, minutos, segundos. También está disponible un marcador de 3/6/9/12 en punto opcional. En caso de que el reloj esté mal, debes comprobar el reloj del sistema.", - "edt_eff_maintain_end_color": "Mantener color de fin", + "edt_eff_lightclock_header": "Reloj de luz", + "edt_eff_lightclock_header_desc": "¡Un verdadero reloj como la luz! Ajustar los colores de las horas, los minutos, los segundos. También hay disponible un marcador opcional de 3/6/9/12 en punto. En caso de que el reloj esté equivocado, debes revisar el reloj de tu sistema.", + "edt_eff_maintain_end_color": "Mantener color final", "edt_eff_margin": "Margen", "edt_eff_markerDepth": "Profundidad del marcador", "edt_eff_markerEnable": "Mostrar marcador", @@ -590,8 +601,8 @@ "edt_eff_police_header": "Policía", "edt_eff_police_header_desc": "Luces como un coche de policía en acción", "edt_eff_postcolor": "Post color", - "edt_eff_rainbowmood_header": "Ánimo Arcoiris", - "edt_eff_rainbowmood_header_desc": "Todos los leds estado arco iris", + "edt_eff_rainbowmood_header": "Atmósfera de arco iris", + "edt_eff_rainbowmood_header_desc": "Todos los LEDs de atmósfera de arco iris", "edt_eff_randomCenter": "Centro aleatorio", "edt_eff_random_header": "Aleatorio", "edt_eff_random_header_desc": "Pixel Punto, punto, punto...", @@ -609,18 +620,18 @@ "edt_eff_snake_header": "Serpiente", "edt_eff_snake_header_desc": "¿Dónde hay algo para comer?", "edt_eff_sparks_header": "Chispas", - "edt_eff_sparks_header_desc": "Estrella-Burbujeante, elege entre un color estático o aleatorio. También puedes ajustar el brillo, la saturación y la velocidad.", + "edt_eff_sparks_header_desc": "Estrella-Burbujeante, elige entre un color estático o aleatorio. También puedes ajustar el brillo, la saturación y la velocidad.", "edt_eff_speed": "Velocidad", "edt_eff_swirl_header": "Remolino del color", - "edt_eff_swirl_header_desc": "Un remolino con colores personalizado. Los colores se extienden hasta 360°, entre colores se calcularán turnos. Adicionalmente puedes añadir un segundo remolino en la parte superior, ¡ten en cuenta que necesita transparencia parcial! Sugerencia: Una reapertura del mismo color da como resultado un área de color mas \"matizada\" y un área de cambio de color reducida.", + "edt_eff_swirl_header_desc": "Un remolino con colores personalizados. Los colores se extienden incluso a 360°, entre los colores se calcularán los cambios. Adicionalmente puedes añadir un segundo remolino en la parte superior, ten en cuenta que necesitas una transparencia parcial! Sugerencia: Una repetición del mismo color resulta en un área de color \"más alta\" y un área de cambio de color reducido.", "edt_eff_systemshutdown_header": "Apagado del Sistema", "edt_eff_systemshutdown_header_desc": "Una animación corta con probablemente un apagado real del sistema", - "edt_eff_traces_header": "Rastros de Color", + "edt_eff_traces_header": "Trazas de Color", "edt_eff_traces_header_desc": "Requiere rediseño", "edt_eff_trails_header": "Caminos", "edt_eff_trails_header_desc": "Estrellas de colores que caen de arriba a abajo", "edt_eff_waves_header": "Ondas", - "edt_eff_waves_header_desc": "¡Ondas de color! Elije tus colores, el tiempo de rotación, la dirección de inversión y mucho más.", + "edt_eff_waves_header_desc": "¡Ondas de color! Elije tus colores, el tiempo de rotación, dirección inversa y más.", "edt_eff_whichleds": "Qué Leds", "edt_eff_whitelevel": "Nivel de blanco", "edt_eff_x-mas_header": "Navidad", @@ -709,11 +720,13 @@ "general_comp_PROTOSERVER": "Servidor de Buffers de Protocolo", "general_comp_SMOOTHING": "Suavizado", "general_comp_V4L": "Captura USB", + "general_country_cn": "China", "general_country_de": "Alemania", "general_country_es": "España", "general_country_fr": "Francia", "general_country_it": "Italia", "general_country_nl": "Holanda", + "general_country_ru": "Rusia", "general_country_uk": "Reino Unido", "general_country_us": "Estados Unidos", "general_speech_cs": "Czech", @@ -725,9 +738,11 @@ "general_speech_nl": "Holandés", "general_speech_pl": "Polaco", "general_speech_ro": "Rumano", + "general_speech_ru": "Ruso", "general_speech_sv": "Sueco", "general_speech_tr": "Turco", "general_speech_vi": "Vietnamita", + "general_speech_zh-CN": "Chino (simplificado)", "general_webui_title": "Hyperion - Configuración Web", "general_wiki_moreto": "Más información sobre \"$1\" en nuestra Wiki", "infoDialog_checklist_title": "¡Lista!", @@ -736,14 +751,14 @@ "infoDialog_general_error_title": "Error", "infoDialog_general_success_title": "Éxito", "infoDialog_general_warning_title": "Advertencia", - "infoDialog_import_comperror_text": ":( Tu navegador no admite una importación. Inténtalo de nuevo con otro navegador.", + "infoDialog_import_comperror_text": "¡Lástima! Tu navegador no admite una importación. Inténtalo de nuevo con otro navegador.", "infoDialog_import_confirm_text": "¿Estás seguro de importar \"$1\"? ¡Este proceso no puede revertirse!", "infoDialog_import_confirm_title": "Confirmar importación", "infoDialog_import_hyperror_text": "No se puede importar el archivo de configuración seleccionado \"$1\". ¡No es compatible con Hyperion 2.0 y superior!", "infoDialog_import_jsonerror_text": "El archivo de configuración seleccionado \"$1\" no es un archivo .json o está dañado. Mensaje de error: ($2)", "infoDialog_wizrgb_text": "Tu Orden de Bytes RGB ya está bien ajustado.", - "infoDialog_writeconf_error_text": "Error al guardar la configuración.", - "infoDialog_writeimage_error_text": "¡El archivo seleccionado \"$1\" no es un archivo de imagen o está dañado! Por favor, seleccione otro archivo de imagen.", + "infoDialog_writeconf_error_text": "Error al guardar tu configuración.", + "infoDialog_writeimage_error_text": "¡El archivo seleccionado \"$1\" no es un archivo de imagen o está dañado! Por favor, selecciona otro archivo de imagen.", "info_404": "¡La pagina que buscas no está disponible!", "info_conlost_label_autorecon": "Nos volvemos a conectar después de que Hyperion esté disponible.", "info_conlost_label_autorefresh": "Esta página se actualizará automáticamente.", @@ -756,7 +771,7 @@ "info_restart_contus": "Si todavía te quedas por aquí después de 20 segundos y no tienes idea de por qué, abre un nuevo tema en nuestro foro de soporte ...", "info_restart_contusa": "... con tus últimos pasos. ¡Gracias!", "info_restart_rightback": "¡Hyperion regresará inmediatamente!", - "info_restart_title": "Reinicia actualmente...", + "info_restart_title": "Reinicia ahora...", "main_ledsim_btn_togglelednumber": "Números LED", "main_ledsim_btn_toggleleds": "Mostrar LEDs", "main_ledsim_btn_togglelivevideo": "video en vivo", @@ -779,7 +794,7 @@ "main_menu_system_token": "Sistema", "main_menu_update_token": "Actualizar", "main_menu_webconfig_token": "Configuración web", - "remote_adjustment_intro": "Modifica el color/brillo/linealización durante el tiempo de ejecución. $1", + "remote_adjustment_intro": "Modifica el color/brillo/compensación durante el tiempo de ejecución. $1", "remote_adjustment_label": "Ajustes de color", "remote_color_button_reset": "Restablecer Color/Efecto", "remote_color_intro": "Establecer un efecto o color. También se enumeran los efectos creados uno mismo (si están disponibles). $1", @@ -802,7 +817,7 @@ "remote_input_sourceactiv_btn": "Entrada activa", "remote_input_status": "Estado/Acción", "remote_losthint": "Nota: Todos los cambios se pierden después de un reinicio.", - "remote_maptype_intro": "Cambie el tipo de mapeo durante el tiempo de ejecución. $1", + "remote_maptype_intro": "Normalmente la disposición de los leds define qué leds cubren un área específica de la imagen, puedes cambiarlo aquí: $1.", "remote_maptype_label": "Tipo de Mapeo", "remote_maptype_label_multicolor_mean": "Multicolor", "remote_maptype_label_unicolor_mean": "Unicolor", @@ -820,7 +835,7 @@ "support_label_donate": "Dona o utiliza nuestros enlaces de afiliados", "support_label_donationpp": "Donación:", "support_label_fbtext": "Comparte nuestra página de Hyperion en Facebook y obten un aviso cuando se publiquen nuevas actualizaciones", - "support_label_forumtext": "Casos de muestra, discusiones, ayuda y mucho más", + "support_label_forumtext": "Casos de ejemplo, discusiones, ayuda y mucho más", "support_label_forumtitle": "Foro", "support_label_ggtext": "¡Haznos un círculo en Google+!", "support_label_ghtext": "Visitanos en Github", @@ -837,34 +852,41 @@ "support_label_yttext": "¿Aburrido de las fotos? ¡Comprueba nuestro canal de Youtube!", "update_button_changelog": "Registro completo de cambios", "update_button_install": "Instalar", - "update_error_getting_versions": "Tuvimos problemas para determinar las versiones disponibles.", + "update_error_getting_versions": "Tuvimos problemas para determinar la última versión disponible.", "update_label_description": "Descripción:", "update_label_intro": "Descripción general de todas las versiones disponibles de Hyperion. En la parte superior puedes actualizar o degradar tu versión de Hyperion siempre que lo desees. Ordenado de nuevo a más antiguo", "update_label_type": "Tipo:", "update_no_updates_for_branch": "No hay actualizaciones para el canal de versión seleccionado.", "update_versreminder": "Tu versión: $1", - "wiz_cc_adjustgamma": "Gamma: Lo que tienes que hacer es ajustar los niveles gamma de cada canal hasta que tengas la misma cantidad percibida de cada canal. Por ejemplo, si su Gris es un poco rojizo, significa que tiene que aumentar el gamma rojo para reducir la cantidad de rojo (más gamma, menos cantidad de color).", + "wiz_atmoorb_desc2": "Ahora elige qué Orbes deben ser añadidos. La posición asigna la lámpara a una posición específica en tu \"imagen\". No se añadirán las lámparas desactivadas. Para identificar las lámparas individuales, pulsa el botón de la derecha.", + "wiz_atmoorb_intro1": "Este asistente configura Hyperion para AtmoOrbs. Las características son la detección automática de AtmoOrb, ajustando cada luz a una posición específica en su imagen o desactivarla y ¡optimizar la configuración de Hyperion automáticamente! En resumen: ¡Todo lo que necesitas son algunos clics y listo!", + "wiz_atmoorb_title": "Asistente AtmoOrb", + "wiz_cc_adjustgamma": "Gamma: Lo que tienes que hacer es ajustar los niveles de gamma de cada canal hasta que tengas la misma cantidad percibida de cada canal. Pista: ¡Neutral es 1.0! Por ejemplo, si tu gris es un poco rojizo significa que tienes que aumentar la gamma del rojo para reducir la cantidad de rojo (a mayor gamma, menor cantidad de color).", "wiz_cc_adjustit": "Ajusta tu \"$1\", hasta que estés agusto con él. Ten en cuenta: Cuanto más lo ajustes fuera del valor predeterminado, el espectro de color será limitado (también para todos los colores intermedios). Dependiendo del espectro de color de TV/LED, los resultados variarán.", - "wiz_cc_backlight": "Adicionalmente podrías definir una retroiluminación para solucionar \"malos colores\" en áreas casi oscuras o si no te gusta el cambio entre color y apagado durante la observación. Adicionalmente se podría definir si debe haber algún color en ella o simplemente blanco. Esto se desactiva durante el estado \"Off\", \"Color\" y \"Efecto\".", + "wiz_cc_backlight": "Además, puedes definir una luz de fondo para distinguir los \"colores malos\" en las zonas casi oscuras o si no te gusta el cambio entre el color y el apagado durante el visionado. Además podrías definir si debería haber algún color en él o sólo blanco. Esto se desactiva durante el estado \"Apagado\", \"Color\" y \"Efecto\".", "wiz_cc_btn_stop": "Parar vídeo", "wiz_cc_btn_switchpic": "Cambiar imagen", "wiz_cc_chooseid": "Define un nombre para éste perfil.", - "wiz_cc_intro1": "Este asistente te guiará a través de la calibración led. Si estás utilizando Kodi, las imágenes de calibración y los videos se pueden enviar directamente a kodi sin más tareas de tu lado. Si no, necesitas descargar estos archivos tú mismo y aplicarlos, si el asistente lo desea.", - "wiz_cc_kodicon": "Servidor web Kodi encontrado, proceder con el soporte de Kodi.", - "wiz_cc_kodidiscon": "No se encuentra el servidor web de Kodi, procede sin el soporte de Kodi.", + "wiz_cc_intro1": "Este asistente te guiará a través de la calibración led. Si estás usando Kodi, las fotos y videos de la calibración pueden ser enviados directamente a él. Prerrequisito: Necesitas habilitar \"Permitir el control remoto desde aplicaciones en otros sistemas\" en Kodi.
O bien, puede que quieras descargar estos archivos tú mismo y mostrarlos cuando el asistente te pida que ajustes la configuración.", + "wiz_cc_kodicon": "Kodi encontrado, proceder con el soporte de Kodi.", + "wiz_cc_kodidiscon": "No se encuentra Kodi, proceder sin el soporte de Kodi.", "wiz_cc_kodidisconlink": "Descarga enlaces de imagenes:", "wiz_cc_kodimsg_start": "Prueba con éxito - ¡tiempo de continuar!", "wiz_cc_kodishould": "Kodi debería mostrar la siguiente imagen: $1", - "wiz_cc_kwebs": "Servidor web Kodi (IP:PUERTO)", + "wiz_cc_kwebs": "Servidor web Kodi (Nombre de Host o IP)", "wiz_cc_lettvshow": "Deja que tu TV muestre la siguiente imagen: $1", "wiz_cc_lettvshowm": "Comprueba esto con las siguientes imágenes: $1", "wiz_cc_link": "¡Haz click en mi!", "wiz_cc_morethanone": "Tienes más de un perfil, elije el perfil que deseas calibrar.", - "wiz_cc_summary": "Una conclusión de su configuración. Durante la reproducción de vídeo, puede cambiar o probar valores de nuevo. Si está hecho, haga clic en guardar.", + "wiz_cc_summary": "Una conclusión de tu configuración. Durante la reproducción de vídeo, puedes cambiar o probar valores de nuevo. Si has terminado, haz clic en guardar.", "wiz_cc_testintro": "¡Tiempo para una prueba real!", "wiz_cc_testintrok": "Pulsa un botón de abajo para iniciar un vídeo de prueba.", "wiz_cc_testintrowok": "Haz el siguiente enlace para descargar videos de prueba:", "wiz_cc_title": "Asistente de calibración de color", + "wiz_cololight_desc2": "Ahora elige qué Cololights deben ser añadidos. Para identificar las luces individuales, pulsa el botón de la derecha.", + "wiz_cololight_intro1": "Este asistente configura Hyperion para el sistema Cololight. ¡Las características son la detección automática de Cololight y el ajuste automático de la configuración de Hyperion! En resumen: Todo lo que necesitas son algunos clics y listo!
Nota: En el caso de Cololight Strip, puede que necesites corregir manualmente el recuento y la disposición de los LEDs.", + "wiz_cololight_noprops": "Imposible obtener las propiedades del dispositivo - Define el conteo de LEDs de hardware manualmente", + "wiz_cololight_title": "Asistente Cololight", "wiz_guideyou": "El $1 te guiará a través de los ajustes. Simplemente ¡presiona el botón!", "wiz_hue_blinkblue": "Permite a ID $1 encender el azul", "wiz_hue_clientkey": "Llave de cliente:", @@ -876,7 +898,7 @@ "wiz_hue_e_desc1": "Busca automáticamente un puente Hue, en caso de que no pueda encontrar uno, debes proporcionar la dirección IP y pulsar el botón de recarga a la derecha. Ahora necesitas un ID de usuario y la clave de cliente, si no tienes ambas, crea una nueva.", "wiz_hue_e_desc2": "Ahora elige tu grupo de entretenimiento, que tiene todas tus luces dentro para usarlas con Hyperion.", "wiz_hue_e_desc3": "Ahora puedes elegir en qué posición la respectiva lámpara debe estar \"en la foto\". Se hizo una preselección de la posición basada en las posiciones configuradas de las luces del grupo de entretenimiento. Esto es sólo una recomendación y puede ser personalizada como se desee. Por lo tanto, puede resaltarlas brevemente haciendo clic en el botón derecho para mejorar la selección.", - "wiz_hue_e_intro1": "Este asistente configura Hyperion para el conocido sistema de entretenimiento Philips Hue. Las características son la detección automática de Hue Bridge, la creación de teclas de usuario y cliente, la selección de grupos de entretenimiento y la configuración de las luces de grupo en una posición específica en la imagen y la sintonización de la configuración de Hyperion de forma automática! En resumen: ¡Sólo necesitas algunos clics y listo!", + "wiz_hue_e_intro1": "Este asistente configura Hyperion para el conocido sistema de entretenimiento Philips Hue. Las características son: Detección automática de Hue Bridge, creación de teclas de usuario y cliente, selección de grupos de entretenimiento, ajuste de luces de grupo a una posición específica en la imagen y optimización de la configuración de Hyperion de forma automática! En resumen: ¡Sólo necesitas algunos clics y listo!", "wiz_hue_e_noapisupport": "El Asistente ha desactivado el soporte de la API de entretenimiento y continuará en el modo clásico.", "wiz_hue_e_noapisupport_hint": "La opción \"Usar API de Entretenimiento Hue\" estaba desmarcada.", "wiz_hue_e_noegrpids": "No se han definido grupos de entretenimiento en este puente de Hue.", @@ -886,7 +908,7 @@ "wiz_hue_e_use_groupid": "Usar ID de grupo $1", "wiz_hue_failure_connection": "El tiempo de conexión expiró. Por favor, pulsa el botón a tiempo.", "wiz_hue_failure_ip": "Comprueba tu dirección IP.", - "wiz_hue_failure_user": "Usuario no encontrado, crea uno nuevo debajo o introduce un ID de usuario válido", + "wiz_hue_failure_user": "Usuario no encontrado, crea uno nuevo con el botón de abajo o introduce una identificación de usuario válida y pulsa el símbolo de \"recargar\".\n", "wiz_hue_intro1": "Con este ayudante de configuración puedes obtener un nuevo usuario para tu Puente de Matiz y puedes ver tus luces con las ID para la Configuración de Hyperion.", "wiz_hue_ip": "IP Puente de Matiz:", "wiz_hue_noids": "Este puente de Matiz no tiene bombillas/tiras, por favor, emparéjalos antes con las aplicaciones de Hue", @@ -898,6 +920,7 @@ "wiz_identify_light": "Identificar $1", "wiz_ids_disabled": "Desactivado", "wiz_ids_entire": "Toda la imagen", + "wiz_noLights": "¡No se encontró $1! Por favor, conecta las luces a la red o configúralas manualmente.", "wiz_pos": "Posición/Estado", "wiz_rgb_expl": "El punto de color cambia cada x segundos el color (rojo, verde), al mismo tiempo que tus leds cambian el color también. Responde las preguntas en la parte inferior para verificar/corregir tu orden de bytes.", "wiz_rgb_intro1": "Este asistente te guiará a través del proceso de búsqueda del orden de color correcto para tus leds. Haz clic en continuar para comenzar.", @@ -910,7 +933,6 @@ "wiz_wizavail": "Asistente disponible", "wiz_yeelight_desc2": "Ahora elige qué lámparas deben añadirse. La posición asigna la lámpara a una posición específica en su \"imagen\". Las lámparas desactivadas no se añadirán. Para identificar las lámparas individuales, pulsa el botón de la derecha.", "wiz_yeelight_intro1": "Este asistente configura el Hyperion para el sistema Yeelight. Las características son la detección automática de los Yeelights, ajustando cada luz a una posición específica en su imagen o desactivarla y ¡ajustar la configuración de Hyperion automáticamente! En resumen: ¡Todo lo que necesitas son algunos clics y listo!", - "wiz_yeelight_noLights": "¡No se encontraron Yeelights! Por favor, conecta las luces a la red o configúralas manualmente.", "wiz_yeelight_title": "Asistente Yeelight", "wiz_yeelight_unsupported": "Sin soporte" } \ No newline at end of file diff --git a/assets/webconfig/i18n/fr.json b/assets/webconfig/i18n/fr.json index a50d2d4b..2e0e0a37 100644 --- a/assets/webconfig/i18n/fr.json +++ b/assets/webconfig/i18n/fr.json @@ -252,7 +252,6 @@ "edt_conf_fbs_heading_title": "Serveur Flatbuffers", "edt_conf_fbs_timeout_expl": "Si aucune donnée n'est reçue dans la période de temps donnée, le composant sera désactivé.", "edt_conf_fbs_timeout_title": "Temps écoulé", - "edt_conf_fg_device_title": "Appareil", "edt_conf_fg_frequency_Hz_title": "Fréquence de capture", "edt_conf_fg_heading_title": "Platform de capture", "edt_conf_fg_height_title": "Hauteur", diff --git a/assets/webconfig/i18n/it.json b/assets/webconfig/i18n/it.json index ff42735d..579b4937 100644 --- a/assets/webconfig/i18n/it.json +++ b/assets/webconfig/i18n/it.json @@ -295,7 +295,6 @@ "edt_conf_fbs_heading_title": "Server Flatbuffers", "edt_conf_fbs_timeout_expl": "Se nessuna informazione viene ricevuta per un dato periodo, il componente verrà disabilitato (soft).", "edt_conf_fbs_timeout_title": "Timeout", - "edt_conf_fg_device_title": "Dispositivo", "edt_conf_fg_display_expl": "Seleziona quale desktop dovrebbe essere catturato (setup multi monitor)", "edt_conf_fg_display_title": "Display", "edt_conf_fg_frequency_Hz_expl": "Quanto velocemente vengono catturare le immagini", @@ -705,11 +704,13 @@ "general_comp_PROTOSERVER": "Server Protocol Buffers", "general_comp_SMOOTHING": "Sfumatura", "general_comp_V4L": "Cattura USB", + "general_country_cn": "Cina", "general_country_de": "Germania", "general_country_es": "Spagna", "general_country_fr": "Francia", "general_country_it": "Italia", "general_country_nl": "Olanda", + "general_country_ru": "Russia", "general_country_uk": "Regno Unito", "general_country_us": "Stati Uniti", "general_speech_cs": "Czech", @@ -721,9 +722,11 @@ "general_speech_nl": "Olandese", "general_speech_pl": "Polacco", "general_speech_ro": "Rumeno", + "general_speech_ru": "Russo", "general_speech_sv": "Svedese", "general_speech_tr": "Turco", "general_speech_vi": "Vietnamita", + "general_speech_zh-CN": "Cinese (semplificanto)", "general_webui_title": "Hyperion - Configurazione Web", "general_wiki_moreto": "Più informazioni su '$1' sulla nostra Wiki", "infoDialog_checklist_title": "Lista!", @@ -903,7 +906,6 @@ "wiz_wizavail": "Assistente disponibile", "wiz_yeelight_desc2": "Scegli quale lampade devono essere aggiunte. La posizione assegna la lampada a una specifica posizione nella tua \"immagine\". Lampade disabilitate non saranno aggiunte. Per identificare una singola lampada premi il bottone sulla destra.", "wiz_yeelight_intro1": "Questi assistenti Configurano Hyperion per il sistema Yeelight. Le funzionalità sono: rilevamento automatico Yeelights, assegnazione di ciascuna luce a una specifica posizione nella tua immagine, disabilitazione luci, regolazione automatica delle impostazioni di Hyperion! In breve: tutto ciò che ti serve sono un paio di click e sei pronto!", - "wiz_yeelight_noLights": "Nessuna Yeelight trovata! Connetti le luci alla rete o configurale manualmente.", "wiz_yeelight_title": "Assistente Yeelight", "wiz_yeelight_unsupported": "Non supportato" } \ No newline at end of file diff --git a/assets/webconfig/i18n/nl.json b/assets/webconfig/i18n/nl.json index b899ae6e..71a5155e 100644 --- a/assets/webconfig/i18n/nl.json +++ b/assets/webconfig/i18n/nl.json @@ -296,7 +296,6 @@ "edt_conf_fbs_heading_title": "Flatbuffers Server", "edt_conf_fbs_timeout_expl": "Als er voor een bepaalde tijd geen data binnenkomt, zal de component worden uitgeschakeld.", "edt_conf_fbs_timeout_title": "Timeout", - "edt_conf_fg_device_title": "Apparaat", "edt_conf_fg_display_expl": "Selecteer welk scherm moet worden opgenomen (multi-scherm setup)", "edt_conf_fg_display_title": "Scherm", "edt_conf_fg_frequency_Hz_expl": "Hoe snel nieuw beeld wordt opgenomen", @@ -910,7 +909,6 @@ "wiz_wizavail": "Wizard beschikbaar", "wiz_yeelight_desc2": "Kies nu welke lampen moeten worden toegevoegd. De positie wijst de lamp toe aan een specifieke positie op uw \"foto\". Uitgeschakelde lampen worden niet toegevoegd. Druk op de knop aan de rechterkant om afzonderlijke lampen te identificeren.", "wiz_yeelight_intro1": "Deze wizards configureren Hyperion voor het Yeelight-systeem. Functies zijn de automatische detectie van de Yeelighs, waarbij elk licht op een specifieke positie op uw foto wordt ingesteld of uitgeschakeld en de Hyperion-instellingen automatisch worden afgestemd! Kortom: u heeft slechts enkele klikken nodig en u bent klaar!", - "wiz_yeelight_noLights": "Geen Yeelights gevonden! Zorg ervoor dat de lampen op het netwerk zijn aangesloten of configureer ze mannelijk.", "wiz_yeelight_title": "Yeelight Wizard", "wiz_yeelight_unsupported": "Niet ondersteund" } \ No newline at end of file diff --git a/assets/webconfig/i18n/pl.json b/assets/webconfig/i18n/pl.json index bcd7b605..bcd89cd0 100644 --- a/assets/webconfig/i18n/pl.json +++ b/assets/webconfig/i18n/pl.json @@ -159,7 +159,7 @@ "dashboard_componentbox_label_status": "Status", "dashboard_componentbox_label_title": "Status komponentów", "dashboard_infobox_label_currenthyp": "Twoja wersja Hyperion:", - "dashboard_infobox_label_disableh": "Wyłącz instancję: 1 $", + "dashboard_infobox_label_disableh": "Wyłącz instancję: $1", "dashboard_infobox_label_enableh": "Uruchom Hyperion", "dashboard_infobox_label_instance": "Instancja:", "dashboard_infobox_label_latesthyp": "Najnowsza wersja Hyperion:", @@ -296,7 +296,6 @@ "edt_conf_fbs_heading_title": "Server Flatbuffers", "edt_conf_fbs_timeout_expl": "Jeśli w danym okresie nie zostaną odebrane żadne dane, komponent zostanie (miękko) wyłączony.", "edt_conf_fbs_timeout_title": "Timeout", - "edt_conf_fg_device_title": "Urządzenie", "edt_conf_fg_display_expl": "Wybierz pulpit, który ma zostać przechwycony (konfiguracja wielu monitorów)", "edt_conf_fg_display_title": "Pulpit", "edt_conf_fg_frequency_Hz_expl": "Jak często rejestrowane są nowe zdjęcia", @@ -703,12 +702,12 @@ "general_comp_BLACKBORDER": "Usuń czarne pasy", "general_comp_BOBLIGHTSERVER": "Serwer Boblight", "general_comp_FLATBUFSERVER": "Serwer FlatBuffers", - "general_comp_FORWARDER": "Przekazywanie wejścia", - "general_comp_GRABBER": "Przechwytywanie pulpitu", + "general_comp_FORWARDER": "Przekazyw. wejścia", + "general_comp_GRABBER": "Przechwytyw. pulpitu", "general_comp_LEDDEVICE": "Urządzenie LED", "general_comp_PROTOSERVER": "Serwer protokołu buforowania", "general_comp_SMOOTHING": "Wygładzanie animacji", - "general_comp_V4L": "Przechwytywanie USB", + "general_comp_V4L": "Przechwytyw. USB", "general_country_de": "Niemcy (Germany)", "general_country_es": "Hiszpania (Spain)", "general_country_fr": "Francja (France)", @@ -843,6 +842,9 @@ "update_label_type": "Typ:", "update_no_updates_for_branch": "Brak aktualizacji dla wybranej wersji kanału.", "update_versreminder": "Twoja wersja: $1", + "wiz_atmoorb_desc2": "Teraz wybierz, które kule mają zostać dodane. Pozycja przypisuje lampę do określonej pozycji na twoim \"obrazie\". Wyłączone lampy nie zostaną dodane. Aby zidentyfikować pojedyncze lampy, naciśnij przycisk po prawej stronie.", + "wiz_atmoorb_intro1": "Ten kreator konfiguruje Hyperion dla AtmoOrbs. Funkcje obejmują automatyczne wykrywanie AtmoOrb, ustawianie każdego światła w określonej pozycji w obrazie lub wyłączanie go i automatyczne dostrajanie ustawień Hyperion! Krótko mówiąc: wystarczy kilka kliknięć i gotowe!", + "wiz_atmoorb_title": "Kreator AtmoOrb", "wiz_cc_adjustgamma": "Gamma: Musisz dostosować poziomy gamma każdego kanału, aż uzyskasz taką samą postrzeganą ilość dla każdego kanału. Wskazówka: Neutralny to 1.0! Na przykład, jeśli twoja szarość jest nieco czerwonawa, oznacza to, że musisz zwiększyć czerwoną gamma, aby zmniejszyć ilość czerwieni (im więcej gamma, tym mniej koloru).", "wiz_cc_adjustit": "Dostosuj swoje „$1”, aż będziesz z niego zadowolony. Zwróć uwagę: Im więcej zmienisz od wartości domyślnej, tym bardziej zmniejszy się dostępne spektrum kolorów (wpływa to również na kolory pomiędzy). W zależności od spektrum kolorów telewizora / LED wyniki mogą się różnić.", "wiz_cc_backlight": "Dodatkowo możesz zdefiniować podświetlenie, aby uporządkować „złe kolory” w prawie ciemnych obszarach lub jeśli nie lubisz przełączać między kolorem a wyłączaniem podczas oglądania. Możesz również zdefiniować konkretny kolor, który będzie używany jako podświetlenie. Jest to wyłączone w stanach „Wył.”, „Kolor” i „Efekt”.", @@ -910,7 +912,6 @@ "wiz_wizavail": "Kreator dostępny", "wiz_yeelight_desc2": "Teraz wybierz, które lampy chcesz dodać. Pozycja przypisuje lampę do określonej pozycji na twoim \"obrazie\". Wyłączone lampy nie zostaną dodane. Aby zidentyfikować pojedyncze lampy, naciśnij przycisk po prawej stronie.", "wiz_yeelight_intro1": "Ten kreator konfiguruje Hyperion dla systemu Yeelight. Funkcje obejmują automatyczne wykrywanie Yeelighów, ustawianie każdego światła w określonej pozycji na zdjęciu lub wyłączanie go i automatyczne dostrajanie ustawień Hyperiona! Krótko mówiąc: wystarczy kilka kliknięć i gotowe!", - "wiz_yeelight_noLights": "Nie znaleziono Yeelights! Podłącz światła do sieci lub skonfiguruj je ręcznie.", "wiz_yeelight_title": "Kreator Yeelight", "wiz_yeelight_unsupported": "Niewspierane" } \ No newline at end of file diff --git a/assets/webconfig/i18n/sv.json b/assets/webconfig/i18n/sv.json index 3143e54e..f379f177 100644 --- a/assets/webconfig/i18n/sv.json +++ b/assets/webconfig/i18n/sv.json @@ -95,6 +95,8 @@ "conf_leds_layout_ma_position": "Inmatning", "conf_leds_layout_ma_vert": "Vertikal", "conf_leds_layout_matrix": "Matrislayout (LED-vägg)", + "conf_leds_layout_pbl": "Punkt, vänster botten", + "conf_leds_layout_pbr": "Punkt, höger botten", "conf_leds_layout_peview": "Förhandsvisning av LED-layout", "conf_leds_layout_preview_l1": "Detta är din första LED (ingångsposition)", "conf_leds_layout_preview_l2": "Detta visualiserar datariktningen (andra/tredje led)", @@ -103,8 +105,11 @@ "conf_leds_layout_preview_originMA": "Skapad från Matrislayout (LED-vägg)", "conf_leds_layout_preview_originTEXT": "Skapad från: Textfält", "conf_leds_layout_preview_totalleds": "Totalt antal LEDs: $1", + "conf_leds_layout_ptl": "Punkt, vänster toppen", "conf_leds_layout_ptlh": "Horisontell", + "conf_leds_layout_ptln": "Trapespunkter", "conf_leds_layout_ptlv": "Vertikal", + "conf_leds_layout_ptr": "Punkt, höger toppen", "conf_leds_layout_textf1": "Detta textfält visar (som standard) din nuvarande laddade layout och skrivs över om du genererar en ny med alternativen ovan. Valfritt kan du utföra ytterligare redigeringar.", "conf_leds_nav_label_ledcontroller": "LED-styrenhet", "conf_leds_nav_label_ledlayout": "LED layout", @@ -169,6 +174,7 @@ "dashboard_label_intro": "Denna sida ger dig en snabb överblick över din Hyperion-installation och visar dig de senaste nyheterna från Hyperion-bloggen.", "dashboard_message_default_password": "Standardlösenordet för webbanvändargränssnittet är inställt. Vi rekommenderar starkt att ändra detta.", "dashboard_message_default_password_t": "Webbanvändargränssnittets standardlösenord är inställt", + "dashboard_message_do_not_show_again": "Visa inte detta meddelande igen", "dashboard_message_global_setting": "Inställningarna på den här sidan beror inte på en specifik instans. Ändringar lagras globalt för alla instanser.", "dashboard_message_global_setting_t": "Instansoberoende inställning", "dashboard_newsbox_label_title": "Hyperion-Blog", @@ -230,7 +236,7 @@ "edt_conf_color_id_expl": "Tilldelat användarnamn", "edt_conf_color_id_title": "ID", "edt_conf_color_imageToLedMappingType_expl": "Om inte \"flerfärgad\" kommer din LED-layout att skrivas över med en annan bildtilldelning", - "edt_conf_color_imageToLedMappingType_title": "LED-tilldelningstyp", + "edt_conf_color_imageToLedMappingType_title": "LED-områdestilldelning", "edt_conf_color_leds_expl": "Tilldelas alla (*) lysdioder eller endast till vissa LED-nummer (0-17).", "edt_conf_color_leds_title": "LED index", "edt_conf_color_magenta_expl": "Det kalibrerade magentavärdet.", @@ -257,8 +263,18 @@ "edt_conf_enum_bbdefault": "Standard", "edt_conf_enum_bbosd": "OSD", "edt_conf_enum_bgr": "BGR", + "edt_conf_enum_bottom_up": "Botten upp", "edt_conf_enum_brg": "BRG", "edt_conf_enum_color": "Färg", + "edt_conf_enum_custom": "Anpassad", + "edt_conf_enum_dl_error": "Fel", + "edt_conf_enum_dl_informational": "Informativ", + "edt_conf_enum_dl_nodebug": "Ingen felsökning", + "edt_conf_enum_dl_statechange": "Tillståndsförändring", + "edt_conf_enum_dl_verbose": "Omfattande", + "edt_conf_enum_dl_verbose1": "Riklighetsnivå 1", + "edt_conf_enum_dl_verbose2": "Riklighetsnivå 2", + "edt_conf_enum_dl_verbose3": "Riklighetsnivå 3", "edt_conf_enum_effect": "Effekt", "edt_conf_enum_gbr": "GBR", "edt_conf_enum_grb": "GRB", @@ -273,13 +289,13 @@ "edt_conf_enum_rbg": "RBG", "edt_conf_enum_rgb": "RGB", "edt_conf_enum_right_left": "Höger till vänster", + "edt_conf_enum_top_down": "Top ner", "edt_conf_enum_transeffect_smooth": "Mjuk", "edt_conf_enum_transeffect_sudden": "Plötslig", "edt_conf_enum_unicolor_mean": "Enfärg", "edt_conf_fbs_heading_title": "Flatbuffers-server", "edt_conf_fbs_timeout_expl": "Om ingen data tas emot under den angivna tiden inaktiveras komponenten (tillfälligt)", "edt_conf_fbs_timeout_title": "Timeout", - "edt_conf_fg_device_title": "Enhet", "edt_conf_fg_display_expl": "Ange vilket skrivbord du vill spela in från. (Multi Monitor Setup)", "edt_conf_fg_display_title": "Visa", "edt_conf_fg_frequency_Hz_expl": "Hur ofta nya bilder tas", @@ -430,8 +446,12 @@ "edt_dev_spec_brightnessMax_title": "Högsta ljusstyrka", "edt_dev_spec_brightnessMin_title": "Minsta ljusstyrka", "edt_dev_spec_brightnessThreshold_title": "Minsta signalstyrka för ljusstyrka", + "edt_dev_spec_chanperfixture_title": "Kanaler per fixtur", "edt_dev_spec_cid_title": "CID", + "edt_dev_spec_clientKey_title": "Klientnyckel", "edt_dev_spec_colorComponent_title": "Färgkomponent", + "edt_dev_spec_debugLevel_title": "Felsökningsnivå för Streameranslutning", + "edt_dev_spec_debugStreamer_title": "Streamerfelsökning", "edt_dev_spec_delayAfterConnect_title": "Försening efter anslutning", "edt_dev_spec_dithering_title": "Ditrering", "edt_dev_spec_dmaNumber_title": "DMA-kanal", @@ -439,6 +459,7 @@ "edt_dev_spec_gpioBcm_title": "GPIO-stift", "edt_dev_spec_gpioMap_title": "GPIO-kartläggning", "edt_dev_spec_gpioNumber_title": "GPIO-nummer", + "edt_dev_spec_groupId_title": "Grupp-ID", "edt_dev_spec_header_title": "Specifika inställningar", "edt_dev_spec_interpolation_title": "Interpolation", "edt_dev_spec_intervall_title": "Intervall", @@ -458,7 +479,11 @@ "edt_dev_spec_networkDevicePort_title": "Port", "edt_dev_spec_numberOfLeds_title": "Antal LEDs", "edt_dev_spec_orbIds_title": "Sfär-ID(n)", + "edt_dev_spec_order_left_right_title": "2.", + "edt_dev_spec_order_top_down_title": "1.", "edt_dev_spec_outputPath_title": "Sökväg", + "edt_dev_spec_panel_start_position": "Startpanel [0-max-panel]", + "edt_dev_spec_panelorganisation_title": "Panelnumreringssekvens", "edt_dev_spec_pid_title": "PID", "edt_dev_spec_port_title": "Port", "edt_dev_spec_printTimeStamp_title": "Lägg till tidsstämpel", @@ -466,13 +491,19 @@ "edt_dev_spec_restoreOriginalState_title": "Återställ lampans ursprungliga tillstånd när den inaktiveras", "edt_dev_spec_serial_title": "Serienummer", "edt_dev_spec_spipath_title": "SPI-väg", + "edt_dev_spec_sslHSTimeoutMax_title": "Maximal Streamer-handskakningstimeout", + "edt_dev_spec_sslHSTimeoutMin_title": "Streamer-handskakningstimeout minimum", + "edt_dev_spec_sslReadTimeout_title": "Streamer-avläsningstimeout", "edt_dev_spec_switchOffOnBlack_title": "Stäng av när svart", + "edt_dev_spec_switchOffOnbelowMinBrightness_title": "Stäng av, under minimum", "edt_dev_spec_targetIpHost_title": "Mål IP/värdnamn", "edt_dev_spec_targetIp_title": "Mål-IP", "edt_dev_spec_transeffect_title": "Övergångseffekt", + "edt_dev_spec_transistionTimeExtra_title": "Extra mörkertid", "edt_dev_spec_transistionTime_title": "Övergångstid", "edt_dev_spec_uid_title": "UID", "edt_dev_spec_universe_title": "Universal", + "edt_dev_spec_useEntertainmentAPI_title": "Använd Hue Entertainment API\n", "edt_dev_spec_useOrbSmoothing_title": "Sfärisk utjämning", "edt_dev_spec_useRgbwProtocol_title": "Använd RGBW-protokoll", "edt_dev_spec_username_title": "Användarnamn", @@ -688,11 +719,14 @@ "general_speech_de": "Tyska", "general_speech_en": "Engelska", "general_speech_es": "Spanska", + "general_speech_fr": "Franska", "general_speech_it": "Italienska", "general_speech_nl": "Holländska", "general_speech_pl": "Polska", "general_speech_ro": "Rumänska", "general_speech_sv": "Svenska", + "general_speech_tr": "Turkiska", + "general_speech_vi": "Vietnamesiska", "general_webui_title": "Hyperion - Webbkonfiguration", "general_wiki_moreto": "Mer information om \"$1\" finns i vår", "infoDialog_checklist_title": "Checklista!", @@ -808,6 +842,9 @@ "update_label_type": "Typ:", "update_no_updates_for_branch": "Inga uppdateringar för vald versionskanal.", "update_versreminder": "Din version: $1", + "wiz_atmoorb_desc2": "Välj vilka kulor som ska läggas till. Positionen tilldelar lampan till en specifik position på din \"bild\". Inaktiverade lampor kommer inte att läggas till. För att identifiera enstaka lampor, tryck på knappen till höger.", + "wiz_atmoorb_intro1": "Den här guiden konfigurerar Hyperion för AtmoOrbs. Funktionerna är den automatiska detekteringen av AtmoOrb, genom att ställa in varje ljus till en specifik position på din bild eller inaktivera den och ställ in Hyperion-inställningarna automatiskt! Kort sagt: Några klick och du är klar!", + "wiz_atmoorb_title": "AtmoOrb-Guide", "wiz_cc_adjustgamma": "Gamma: Du ställer in varje gammakanal så att den \"gråa lutningen\" på lysdioderna inte ser grön-/röd-/blåaktig ut. Tips: Neutral är 1,0! T.ex.: Om grå är lite rödaktig betyder det att du måste öka din gamma för röd för att minska mängden röd (mer gamma ger mindre färg).", "wiz_cc_adjustit": "Justera din \"$1\" tills du är nöjd med det. Observera: Ju mer du justerar bort från standardvärdet minskar det tillgängliga färgspektrumet (Detta påverkar också färgerna däremellan). Beroende på din TV/LED-färgspektrum kan dina resultat variera.", "wiz_cc_backlight": "Dessutom kan du ställa in en bakgrundsbelysning för att undvika \"irriterande färger\" i en nästan svart bild eller att du tycker att ändringen mellan färg och av för ansträngande. Dessutom kan det avgöras om detta ska vara färgat eller bara vitt. Inaktiveras automatiskt i läget \"Av\" såväl som \"Färg\" och \"Effekt\".", @@ -859,6 +896,11 @@ "wiz_hue_searchb": "Letar efter brygga...", "wiz_hue_title": "Philips Hue Guide", "wiz_hue_username": "Användar-ID", + "wiz_identify": "Identifiera", + "wiz_identify_light": "Identifiera $1", + "wiz_ids_disabled": "Inaktiverad", + "wiz_ids_entire": "Hela bilden", + "wiz_pos": "Position/läge", "wiz_rgb_expl": "Den färgade punkten kommer att ändra färg (röd, grön) varje x sekund, samtidigt som dina lysdioder byter till den färgen. Svara på frågorna längst ner för att kontrollera/korrigera din byte-order.", "wiz_rgb_intro1": "Den här guiden leder dig genom processen för att hitta rätt färgordning för dina lysdioder. Klicka på Fortsätt för att börja.", "wiz_rgb_intro2": "När behöver du den här guiden? Exempel: Du ställer in färgen röd, men får istället grön eller blå. Men du kan också använda den som första-gången-konfiguration.", @@ -867,5 +909,9 @@ "wiz_rgb_qrend": "...röd?", "wiz_rgb_switchevery": "Byt färg varje...", "wiz_rgb_title": "RGB-byte-ordningsguide", - "wiz_wizavail": "Guiden tillgänglig" + "wiz_wizavail": "Guiden tillgänglig", + "wiz_yeelight_desc2": "Välj nu vilka lampor som ska läggas till. Positionen tilldelar lampan en specifik position på din \"bild\". Inaktiverade lampor läggs inte till. För att identifiera enstaka lampor, tryck på knappen till höger.", + "wiz_yeelight_intro1": "De här guiderna konfigurerar Hyperion för Yeelight-systemet. Funktionerna är Yeelights automatiska detektering, ställ in varje ljus till en specifik position på din bild eller inaktivera den och ställ in Hyperion-inställningarna automatiskt! Kort sagt: Några klick och du är klar!", + "wiz_yeelight_title": "Yeelight-Guide", + "wiz_yeelight_unsupported": "Utan stöd" } \ No newline at end of file diff --git a/assets/webconfig/i18n/tr.json b/assets/webconfig/i18n/tr.json index 771b09c0..38788cf1 100644 --- a/assets/webconfig/i18n/tr.json +++ b/assets/webconfig/i18n/tr.json @@ -184,7 +184,6 @@ "edt_conf_enum_unicolor_mean": "Tek renkli", "edt_conf_fbs_heading_title": "Flatbuffers Sunucusu", "edt_conf_fbs_timeout_title": "Zaman aşımı", - "edt_conf_fg_device_title": "Aygıt", "edt_conf_fg_display_title": "Ekran", "edt_conf_fg_frequency_Hz_title": "Yakalama frekansı", "edt_conf_fg_height_title": "Yükseklik", diff --git a/assets/webconfig/i18n/vi.json b/assets/webconfig/i18n/vi.json index 465f1deb..fa0d3289 100644 --- a/assets/webconfig/i18n/vi.json +++ b/assets/webconfig/i18n/vi.json @@ -257,7 +257,6 @@ "edt_conf_fbs_heading_title": "Máy chủ Flatbuffers", "edt_conf_fbs_timeout_expl": "Nếu không có dữ liệu nào được nhận trong khoảng thời gian nhất định, thành phần sẽ bị tắt (mềm).", "edt_conf_fbs_timeout_title": "Hết giờ", - "edt_conf_fg_device_title": "Thiết bị", "edt_conf_fg_display_expl": "Chọn máy tính để bàn nào sẽ được chụp (thiết lập nhiều màn hình)", "edt_conf_fg_display_title": "Trưng bày", "edt_conf_fg_frequency_Hz_expl": "Tần suất chụp ảnh mới", diff --git a/assets/webconfig/img/cc/HGradient.png b/assets/webconfig/img/cc/HGradient.png index b681c709..a6683841 100644 Binary files a/assets/webconfig/img/cc/HGradient.png and b/assets/webconfig/img/cc/HGradient.png differ diff --git a/assets/webconfig/img/cc/VGradient.png b/assets/webconfig/img/cc/VGradient.png index 974b087e..06e13e27 100644 Binary files a/assets/webconfig/img/cc/VGradient.png and b/assets/webconfig/img/cc/VGradient.png differ diff --git a/assets/webconfig/img/cc/blue.png b/assets/webconfig/img/cc/blue.png index 727f8cf3..17fad048 100644 Binary files a/assets/webconfig/img/cc/blue.png and b/assets/webconfig/img/cc/blue.png differ diff --git a/assets/webconfig/img/cc/cyan.png b/assets/webconfig/img/cc/cyan.png index 5bf23750..29506240 100644 Binary files a/assets/webconfig/img/cc/cyan.png and b/assets/webconfig/img/cc/cyan.png differ diff --git a/assets/webconfig/img/cc/green.png b/assets/webconfig/img/cc/green.png index 01e18f12..1b9242dc 100644 Binary files a/assets/webconfig/img/cc/green.png and b/assets/webconfig/img/cc/green.png differ diff --git a/assets/webconfig/img/cc/grey_1.png b/assets/webconfig/img/cc/grey_1.png index 7c9d7e81..9e0c2465 100644 Binary files a/assets/webconfig/img/cc/grey_1.png and b/assets/webconfig/img/cc/grey_1.png differ diff --git a/assets/webconfig/img/cc/grey_2.png b/assets/webconfig/img/cc/grey_2.png index dcf7a3f6..6d64a996 100644 Binary files a/assets/webconfig/img/cc/grey_2.png and b/assets/webconfig/img/cc/grey_2.png differ diff --git a/assets/webconfig/img/cc/grey_3.png b/assets/webconfig/img/cc/grey_3.png index f61f7297..2e5d2fa1 100644 Binary files a/assets/webconfig/img/cc/grey_3.png and b/assets/webconfig/img/cc/grey_3.png differ diff --git a/assets/webconfig/img/cc/magenta.png b/assets/webconfig/img/cc/magenta.png index 891ac731..0d897b85 100644 Binary files a/assets/webconfig/img/cc/magenta.png and b/assets/webconfig/img/cc/magenta.png differ diff --git a/assets/webconfig/img/cc/red.png b/assets/webconfig/img/cc/red.png index 93d40815..e8202dd2 100644 Binary files a/assets/webconfig/img/cc/red.png and b/assets/webconfig/img/cc/red.png differ diff --git a/assets/webconfig/img/cc/white.png b/assets/webconfig/img/cc/white.png index 8e5dc2d5..75e1aca7 100644 Binary files a/assets/webconfig/img/cc/white.png and b/assets/webconfig/img/cc/white.png differ diff --git a/assets/webconfig/img/cc/yellow.png b/assets/webconfig/img/cc/yellow.png index b2eb1711..e78b49cc 100644 Binary files a/assets/webconfig/img/cc/yellow.png and b/assets/webconfig/img/cc/yellow.png differ diff --git a/assets/webconfig/img/hyperion/hyperionlogo.png b/assets/webconfig/img/hyperion/hyperionlogo.png index e9604c69..203a43b3 100644 Binary files a/assets/webconfig/img/hyperion/hyperionlogo.png and b/assets/webconfig/img/hyperion/hyperionlogo.png differ diff --git a/assets/webconfig/img/hyperion/hyperionwhitelogo.png b/assets/webconfig/img/hyperion/hyperionwhitelogo.png index 65338f68..6c29bbbe 100644 Binary files a/assets/webconfig/img/hyperion/hyperionwhitelogo.png and b/assets/webconfig/img/hyperion/hyperionwhitelogo.png differ diff --git a/assets/webconfig/img/hyperion/ssdp_icon.png b/assets/webconfig/img/hyperion/ssdp_icon.png index ebd86fbe..8dbd2897 100644 Binary files a/assets/webconfig/img/hyperion/ssdp_icon.png and b/assets/webconfig/img/hyperion/ssdp_icon.png differ diff --git a/assets/webconfig/js/content_colors.js b/assets/webconfig/js/content_colors.js index bb178b1c..87428047 100644 --- a/assets/webconfig/js/content_colors.js +++ b/assets/webconfig/js/content_colors.js @@ -35,7 +35,7 @@ $(document).ready( function() { }, true, true); editor_color.on('change',function() { - editor_color.validate().length ? $('#btn_submit_color').attr('disabled', true) : $('#btn_submit_color').attr('disabled', false); + editor_color.validate().length || window.readOnlyMode ? $('#btn_submit_color').attr('disabled', true) : $('#btn_submit_color').attr('disabled', false); }); $('#btn_submit_color').off().on('click',function() { @@ -48,7 +48,8 @@ $(document).ready( function() { }, true, true); editor_smoothing.on('change',function() { - editor_smoothing.validate().length ? $('#btn_submit_smoothing').attr('disabled', true) : $('#btn_submit_smoothing').attr('disabled', false); + editor_smoothing.validate().length || window.readOnlyMode ? $('#btn_submit_smoothing').attr('disabled', true) : $('#btn_submit_smoothing').attr('disabled', false); + }); $('#btn_submit_smoothing').off().on('click',function() { @@ -61,7 +62,7 @@ $(document).ready( function() { }, true, true); editor_blackborder.on('change',function() { - editor_blackborder.validate().length ? $('#btn_submit_blackborder').attr('disabled', true) : $('#btn_submit_blackborder').attr('disabled', false); + editor_blackborder.validate().length || window.readOnlyMode ? $('#btn_submit_blackborder').attr('disabled', true) : $('#btn_submit_blackborder').attr('disabled', false); }); $('#btn_submit_blackborder').off().on('click',function() { diff --git a/assets/webconfig/js/content_dashboard.js b/assets/webconfig/js/content_dashboard.js index ff5383a1..bcb88a60 100644 --- a/assets/webconfig/js/content_dashboard.js +++ b/assets/webconfig/js/content_dashboard.js @@ -70,7 +70,6 @@ $(document).ready( function() { }); var instancename = window.currentHyperionInstanceName; - console.log ("instancename: ",instancename); $('#dash_statush').html(hyperion_enabled ? ''+$.i18n('general_btn_on')+'' : ''+$.i18n('general_btn_off')+''); $('#btn_hsc').html(hyperion_enabled ? '' : ''); diff --git a/assets/webconfig/js/content_effects.js b/assets/webconfig/js/content_effects.js index 84102fb6..bf08f85c 100644 --- a/assets/webconfig/js/content_effects.js +++ b/assets/webconfig/js/content_effects.js @@ -43,7 +43,7 @@ $(document).ready( function() { }, true, true); effects_editor.on('change',function() { - effects_editor.validate().length ? $('#btn_submit_effects').attr('disabled', true) : $('#btn_submit_effects').attr('disabled', false); + effects_editor.validate().length || window.readOnlyMode ? $('#btn_submit_effects').attr('disabled', true) : $('#btn_submit_effects').attr('disabled', false); }); $('#btn_submit_effects').off().on('click',function() { @@ -65,11 +65,11 @@ $(document).ready( function() { }); foregroundEffect_editor.on('change',function() { - foregroundEffect_editor.validate().length ? $('#btn_submit_foregroundEffect').attr('disabled', true) : $('#btn_submit_foregroundEffect').attr('disabled', false); + foregroundEffect_editor.validate().length || window.readOnlyMode ? $('#btn_submit_foregroundEffect').attr('disabled', true) : $('#btn_submit_foregroundEffect').attr('disabled', false); }); backgroundEffect_editor.on('change',function() { - backgroundEffect_editor.validate().length ? $('#btn_submit_backgroundEffect').attr('disabled', true) : $('#btn_submit_backgroundEffect').attr('disabled', false); + backgroundEffect_editor.validate().length || window.readOnlyMode ? $('#btn_submit_backgroundEffect').attr('disabled', true) : $('#btn_submit_backgroundEffect').attr('disabled', false); }); $('#btn_submit_foregroundEffect').off().on('click',function() { diff --git a/assets/webconfig/js/content_effectsconfigurator.js b/assets/webconfig/js/content_effectsconfigurator.js index c23b4a07..feea705c 100644 --- a/assets/webconfig/js/content_effectsconfigurator.js +++ b/assets/webconfig/js/content_effectsconfigurator.js @@ -83,7 +83,8 @@ $(document).ready( function() { } if( effects_editor.validate().length == 0 && effectName != "") { - $('#btn_start_test, #btn_write').attr('disabled', false); + $('#btn_start_test').attr('disabled', false); + !window.readOnlyMode ? $('#btn_write').attr('disabled', false) : $('#btn_write').attr('disabled', true); } else { @@ -101,6 +102,7 @@ $(document).ready( function() { } else { effects_editor.enable(); $("#eff_footer").children().attr('disabled',false); + !window.readOnlyMode ? $('#btn_write').attr('disabled', false) : $('#btn_write').attr('disabled', true); } }); diff --git a/assets/webconfig/js/content_general.js b/assets/webconfig/js/content_general.js index f0d459d4..4918a874 100644 --- a/assets/webconfig/js/content_general.js +++ b/assets/webconfig/js/content_general.js @@ -18,7 +18,7 @@ $(document).ready( function() { }, true, true); conf_editor.on('change',function() { - conf_editor.validate().length ? $('#btn_submit').attr('disabled', true) : $('#btn_submit').attr('disabled', false); + conf_editor.validate().length || window.readOnlyMode ? $('#btn_submit').attr('disabled', true) : $('#btn_submit').attr('disabled', false); }); $('#btn_submit').off().on('click',function() { @@ -28,6 +28,12 @@ $(document).ready( function() { // Instance handling function handleInstanceRename(e) { + + conf_editor.on('change',function() { + window.readOnlyMode ? $('#btn_cl_save').attr('disabled', true) : $('#btn_submit').attr('disabled', false); + window.readOnlyMode ? $('#btn_ma_save').attr('disabled', true) : $('#btn_submit').attr('disabled', false); + }); + var inst = e.currentTarget.id.split("_")[1]; showInfoDialog('renInst', $.i18n('conf_general_inst_renreq_t'), getInstanceNameByIndex(inst)); @@ -77,6 +83,10 @@ $(document).ready( function() { $('#instren_'+inst[key].instance).off().on('click', handleInstanceRename); $('#inst_'+inst[key].instance).off().on('click', handleInstanceStartStop); $('#instdel_'+inst[key].instance).off().on('click', handleInstanceDelete); + + window.readOnlyMode ? $('#instren_'+inst[key].instance).attr('disabled', true) : $('#btn_submit').attr('disabled', false); + window.readOnlyMode ? $('#inst_'+inst[key].instance).attr('disabled', true) : $('#btn_submit').attr('disabled', false); + window.readOnlyMode ? $('#instdel_'+inst[key].instance).attr('disabled', true) : $('#btn_submit').attr('disabled', false); } } @@ -85,7 +95,7 @@ $(document).ready( function() { buildInstanceList(); $('#inst_name').off().on('input',function(e) { - (e.currentTarget.value.length >= 5) ? $('#btn_create_inst').attr('disabled', false) : $('#btn_create_inst').attr('disabled', true); + (e.currentTarget.value.length >= 5) && !window.readOnlyMode ? $('#btn_create_inst').attr('disabled', false) : $('#btn_create_inst').attr('disabled', true); if(5-e.currentTarget.value.length >= 1 && 5-e.currentTarget.value.length <= 4) $('#inst_chars_needed').html(5-e.currentTarget.value.length + " " + $.i18n('general_chars_needed')) else @@ -105,7 +115,7 @@ $(document).ready( function() { //import function dis_imp_btn(state) { - state ? $('#btn_import_conf').attr('disabled', true) : $('#btn_import_conf').attr('disabled', false); + state || window.readOnlyMode ? $('#btn_import_conf').attr('disabled', true) : $('#btn_import_conf').attr('disabled', false); } function readFile(evt) diff --git a/assets/webconfig/js/content_grabber.js b/assets/webconfig/js/content_grabber.js index ad94831b..246f32e9 100644 --- a/assets/webconfig/js/content_grabber.js +++ b/assets/webconfig/js/content_grabber.js @@ -1,45 +1,45 @@ -$(document).ready( function() { +$(document).ready(function () { performTranslation(); var conf_editor_v4l2 = null; var conf_editor_fg = null; var conf_editor_instCapt = null; var V4L2_AVAIL = window.serverInfo.grabbers.available.includes("v4l2"); - if(V4L2_AVAIL) { + if (V4L2_AVAIL) { // Dynamic v4l2 enum schema var v4l2_dynamic_enum_schema = { "available_devices": { "type": "string", "title": "edt_conf_v4l2_device_title", - "propertyOrder" : 1, - "required" : true + "propertyOrder": 1, + "required": true }, "device_inputs": { "type": "string", "title": "edt_conf_v4l2_input_title", - "propertyOrder" : 3, - "required" : true + "propertyOrder": 3, + "required": true }, "resolutions": { "type": "string", "title": "edt_conf_v4l2_resolution_title", - "propertyOrder" : 6, - "required" : true + "propertyOrder": 6, + "required": true }, "framerates": { "type": "string", "title": "edt_conf_v4l2_framerate_title", - "propertyOrder" : 9, - "required" : true + "propertyOrder": 9, + "required": true } }; // Build dynamic v4l2 enum schema parts - var buildSchemaPart = function(key, schema, device) { + var buildSchemaPart = function (key, schema, device) { if (schema[key]) { var enumVals = []; var enumTitelVals = []; @@ -76,31 +76,31 @@ $(document).ready( function() { "type": schema[key].type, "title": schema[key].title, "enum": [].concat(["auto"], enumVals, ["custom"]), - "options" : + "options": { - "enum_titles" : [].concat(["edt_conf_enum_automatic"], enumTitelVals, ["edt_conf_enum_custom"]), + "enum_titles": [].concat(["edt_conf_enum_automatic"], enumTitelVals, ["edt_conf_enum_custom"]), }, - "propertyOrder" : schema[key].propertyOrder, - "required" : schema[key].required + "propertyOrder": schema[key].propertyOrder, + "required": schema[key].required }; } }; // Switch between visible states function toggleOption(option, state) { - $('[data-schemapath="root.grabberV4L2.'+option+'"]').toggle(state); + $('[data-schemapath="root.grabberV4L2.' + option + '"]').toggle(state); if (state) ( - $('[data-schemapath="root.grabberV4L2.'+option+'"]').addClass('col-md-12'), - $('label[for="root_grabberV4L2_'+option+'"]').css('left','10px'), - $('[id="root_grabberV4L2_'+option+'"]').css('left','10px') + $('[data-schemapath="root.grabberV4L2.' + option + '"]').addClass('col-md-12'), + $('label[for="root_grabberV4L2_' + option + '"]').css('left', '10px'), + $('[id="root_grabberV4L2_' + option + '"]').css('left', '10px') ); } // Watch all v4l2 dynamic fields - var setWatchers = function(schema) { + var setWatchers = function (schema) { var path = 'root.grabberV4L2.'; - Object.keys(schema).forEach(function(key) { - conf_editor_v4l2.watch(path + key, function() { + Object.keys(schema).forEach(function (key) { + conf_editor_v4l2.watch(path + key, function () { var ed = conf_editor_v4l2.getEditor(path + key); var val = ed.getValue(); @@ -108,7 +108,7 @@ $(document).ready( function() { var V4L2properties = ['device_inputs', 'resolutions', 'framerates']; if (val == 'custom') { var grabberV4L2 = ed.parent; - V4L2properties.forEach(function(item) { + V4L2properties.forEach(function (item) { buildSchemaPart(item, v4l2_dynamic_enum_schema, 'none'); grabberV4L2.original_schema.properties[item] = window.schema.grabberV4L2.properties[item]; grabberV4L2.schema.properties[item] = window.schema.grabberV4L2.properties[item]; @@ -123,9 +123,8 @@ $(document).ready( function() { conf_editor_v4l2.getEditor(path + 'standard').enable(); toggleOption('device', true); - } else if (val == 'auto') { - V4L2properties.forEach(function(item) { + V4L2properties.forEach(function (item) { conf_editor_v4l2.getEditor(path + item).setValue('auto'); conf_editor_v4l2.getEditor(path + item).disable(); }); @@ -134,12 +133,11 @@ $(document).ready( function() { conf_editor_v4l2.getEditor(path + 'standard').disable(); (toggleOption('device', false), toggleOption('input', false), - toggleOption('width', false), toggleOption('height', false), - toggleOption('fps', false)); - + toggleOption('width', false), toggleOption('height', false), + toggleOption('fps', false)); } else { var grabberV4L2 = ed.parent; - V4L2properties.forEach(function(item) { + V4L2properties.forEach(function (item) { buildSchemaPart(item, v4l2_dynamic_enum_schema, val); grabberV4L2.original_schema.properties[item] = window.schema.grabberV4L2.properties[item]; grabberV4L2.schema.properties[item] = window.schema.grabberV4L2.properties[item]; @@ -176,12 +174,12 @@ $(document).ready( function() { }; // Insert dynamic v4l2 enum schema parts - Object.keys(v4l2_dynamic_enum_schema).forEach(function(key) { + Object.keys(v4l2_dynamic_enum_schema).forEach(function (key) { buildSchemaPart(key, v4l2_dynamic_enum_schema, window.serverConfig.grabberV4L2.device); }); } - if(window.showOptHelp) { + if (window.showOptHelp) { // Instance Capture $('#conf_cont').append(createRow('conf_cont_instCapt')); $('#conf_cont_instCapt').append(createOptPanel('fa-camera', $.i18n("edt_conf_instCapture_heading_title"), 'editor_container_instCapt', 'btn_submit_instCapt')); @@ -193,7 +191,7 @@ $(document).ready( function() { $('#conf_cont_fg').append(createHelpTable(window.schema.framegrabber.properties, $.i18n("edt_conf_fg_heading_title"))); // V4L2 - hide if not available - if(V4L2_AVAIL) { + if (V4L2_AVAIL) { $('#conf_cont').append(createRow('conf_cont_v4l')); $('#conf_cont_v4l').append(createOptPanel('fa-camera', $.i18n("edt_conf_v4l2_heading_title"), 'editor_container_v4l2', 'btn_submit_v4l2')); $('#conf_cont_v4l').append(createHelpTable(window.schema.grabberV4L2.properties, $.i18n("edt_conf_v4l2_heading_title"))); @@ -202,7 +200,7 @@ $(document).ready( function() { $('#conf_cont').addClass('row'); $('#conf_cont').append(createOptPanel('fa-camera', $.i18n("edt_conf_instCapture_heading_title"), 'editor_container_instCapt', 'btn_submit_instCapt')); $('#conf_cont').append(createOptPanel('fa-camera', $.i18n("edt_conf_fg_heading_title"), 'editor_container_fg', 'btn_submit_fg')); - if(V4L2_AVAIL) { + if (V4L2_AVAIL) { $('#conf_cont').append(createOptPanel('fa-camera', $.i18n("edt_conf_v4l2_heading_title"), 'editor_container_v4l2', 'btn_submit_v4l2')); } } @@ -212,11 +210,11 @@ $(document).ready( function() { instCapture: window.schema.instCapture }, true, true); - conf_editor_instCapt.on('change',function() { - conf_editor_instCapt.validate().length ? $('#btn_submit_instCapt').attr('disabled', true) : $('#btn_submit_instCapt').attr('disabled', false); + conf_editor_instCapt.on('change', function () { + conf_editor_instCapt.validate().length || window.readOnlyMode ? $('#btn_submit_instCapt').attr('disabled', true) : $('#btn_submit_instCapt').attr('disabled', false); }); - $('#btn_submit_instCapt').off().on('click',function() { + $('#btn_submit_instCapt').off().on('click', function () { requestWriteConfig(conf_editor_instCapt.getValue()); }); @@ -225,24 +223,46 @@ $(document).ready( function() { framegrabber: window.schema.framegrabber }, true, true); - conf_editor_fg.on('change',function() { - conf_editor_fg.validate().length ? $('#btn_submit_fg').attr('disabled', true) : $('#btn_submit_fg').attr('disabled', false); + conf_editor_fg.on('change', function () { + //Remove Grabbers which are not supported + var grabbers = window.serverInfo.grabbers.available; + + var selector = "root_framegrabber_type"; + var options = $("#" + selector + " option"); + + for (var i = 0; i < options.length; i++) { + var type = options[i].value; + if (grabbers.indexOf(type) === -1) { + $("#" + selector + " option[value='" + type + "']").remove(); + } + } + + if (window.serverInfo.grabbers.active) + { + var activegrabber = window.serverInfo.grabbers.active.toLowerCase(); + $("#" + selector + " option[value='" + activegrabber + "']").attr('selected', 'selected'); + } + + var selectedType = $("#root_framegrabber_type").val(); + filerFgGrabberOptions(selectedType); + + conf_editor_fg.validate().length || window.readOnlyMode ? $('#btn_submit_fg').attr('disabled', true) : $('#btn_submit_fg').attr('disabled', false); }); - $('#btn_submit_fg').off().on('click',function() { + $('#btn_submit_fg').off().on('click', function () { requestWriteConfig(conf_editor_fg.getValue()); }); - if(V4L2_AVAIL) { + if (V4L2_AVAIL) { conf_editor_v4l2 = createJsonEditor('editor_container_v4l2', { - grabberV4L2 : window.schema.grabberV4L2 + grabberV4L2: window.schema.grabberV4L2 }, true, true); - conf_editor_v4l2.on('change',function() { - conf_editor_v4l2.validate().length ? $('#btn_submit_v4l2').attr('disabled', true) : $('#btn_submit_v4l2').attr('disabled', false); + conf_editor_v4l2.on('change', function () { + conf_editor_v4l2.validate().length || window.readOnlyMode ? $('#btn_submit_v4l2').attr('disabled', true) : $('#btn_submit_v4l2').attr('disabled', false); }); - conf_editor_v4l2.on('ready', function() { + conf_editor_v4l2.on('ready', function () { setWatchers(v4l2_dynamic_enum_schema); if (window.serverConfig.grabberV4L2.available_devices == 'custom' && window.serverConfig.grabberV4L2.device != 'auto') @@ -252,7 +272,7 @@ $(document).ready( function() { conf_editor_v4l2.getEditor('root.grabberV4L2.available_devices').setValue('auto'); if (window.serverConfig.grabberV4L2.available_devices == 'auto') { - ['device_inputs', 'standard', 'resolutions', 'framerates'].forEach(function(item) { + ['device_inputs', 'standard', 'resolutions', 'framerates'].forEach(function (item) { conf_editor_v4l2.getEditor('root.grabberV4L2.' + item).setValue('auto'); conf_editor_v4l2.getEditor('root.grabberV4L2.' + item).disable(); }); @@ -266,10 +286,9 @@ $(document).ready( function() { if (window.serverConfig.grabberV4L2.framerates == 'custom' && window.serverConfig.grabberV4L2.device != 'auto') toggleOption('fps', true); - }); - $('#btn_submit_v4l2').off().on('click',function() { + $('#btn_submit_v4l2').off().on('click', function () { var v4l2Options = conf_editor_v4l2.getValue(); if (v4l2Options.grabberV4L2.available_devices != 'custom' && v4l2Options.grabberV4L2.available_devices != 'auto') @@ -304,31 +323,54 @@ $(document).ready( function() { ////////////////////////////////////////////////// //create introduction - if(window.showOptHelp) { + if (window.showOptHelp) { createHint("intro", $.i18n('conf_grabber_fg_intro'), "editor_container_fg"); - if(V4L2_AVAIL){ + if (V4L2_AVAIL) { createHint("intro", $.i18n('conf_grabber_v4l_intro'), "editor_container_v4l2"); } } - function hideEl(el) { - for(var i = 0; i -1) - hideEl(["device","pixelDecimation"]); - else if (grabbers.indexOf('x11') > -1 || grabbers.indexOf('xcb') > -1) - hideEl(["device","width","height"]); - else if (grabbers.indexOf('osx') > -1 ) - hideEl(["device","pixelDecimation"]); - else if (grabbers.indexOf('amlogic') > -1) - hideEl(["pixelDecimation"]); + var grabbers = window.serverInfo.grabbers.available; + if (grabbers.indexOf(type) > -1) { + toggleFgOptions(["width", "height", "pixelDecimation", "display"], true); + + switch (type) { + case "dispmanx": + toggleFgOptions(["pixelDecimation", "display"], false); + break; + case "x11": + case "xcb": + toggleFgOptions(["width", "height", "display"], false); + break; + case "framebuffer": + toggleFgOptions(["display"], false); + break; + case "amlogic": + toggleFgOptions(["pixelDecimation", "display"], false); + break; + case "qt": + break; + case "dx": + break; + case "osx": + break; + default: + } + } + }; + + $('#root_framegrabber_type').change(function () { + var selectedType = $("#root_framegrabber_type").val(); + filerFgGrabberOptions(selectedType); }); removeOverlay(); diff --git a/assets/webconfig/js/content_index.js b/assets/webconfig/js/content_index.js index 219070f7..c57f1626 100644 --- a/assets/webconfig/js/content_index.js +++ b/assets/webconfig/js/content_index.js @@ -26,6 +26,9 @@ $(document).ready(function () { $(window.hyperion).on("cmd-serverinfo", function (event) { window.serverInfo = event.response.info; + + window.readOnlyMode = window.sysInfo.hyperion.readOnlyMode; + // comps window.comps = event.response.info.components diff --git a/assets/webconfig/js/content_leds.js b/assets/webconfig/js/content_leds.js index e6f5c18f..5ac970fb 100644 --- a/assets/webconfig/js/content_leds.js +++ b/assets/webconfig/js/content_leds.js @@ -59,40 +59,36 @@ function createLedPreview(leds, origin){ } -function createClassicLeds(){ - //get values - var ledstop = parseInt($("#ip_cl_top").val()); - var ledsbottom = parseInt($("#ip_cl_bottom").val()); - var ledsleft = parseInt($("#ip_cl_left").val()); - var ledsright = parseInt($("#ip_cl_right").val()); - var ledsglength = parseInt($("#ip_cl_glength").val()); - var ledsgpos = parseInt($("#ip_cl_gpos").val()); - var position = parseInt($("#ip_cl_position").val()); - var reverse = $("#ip_cl_reverse").is(":checked"); +function createClassicLedLayoutSimple( ledstop,ledsleft,ledsright,ledsbottom,position,reverse ){ - //advanced values - var ledsVDepth = parseInt($("#ip_cl_vdepth").val())/100; - var ledsHDepth = parseInt($("#ip_cl_hdepth").val())/100; - var edgeVGap = parseInt($("#ip_cl_edgegap").val())/100/2; - //var cornerVGap = parseInt($("#ip_cl_cornergap").val())/100/2; - var overlap = $("#ip_cl_overlap").val()/100; + let params = { + ledstop: 0, ledsleft: 0, ledsright: 0, ledsbottom: 0, + ledsglength: 0, ledsgpos: 0, position: 0, + ledsHDepth: 0.08, ledsVDepth: 0.05, overlap: 0, + edgeVGap: 0, + ptblh: 0, ptblv: 1, ptbrh: 1, ptbrv: 1, + pttlh: 0, pttlv: 0, pttrh: 1, pttrv: 0, + reverse:false + }; + + params.ledstop = ledstop; + params.ledsleft = ledsleft; + params.ledsright = ledsright; + params.ledsbottom = ledsbottom; + params.position = position; + params.reverse = reverse; - //trapezoid values % -> float - var ptblh = parseInt($("#ip_cl_pblh").val())/100; - var ptblv = parseInt($("#ip_cl_pblv").val())/100; - var ptbrh = parseInt($("#ip_cl_pbrh").val())/100; - var ptbrv = parseInt($("#ip_cl_pbrv").val())/100; - var pttlh = parseInt($("#ip_cl_ptlh").val())/100; - var pttlv = parseInt($("#ip_cl_ptlv").val())/100; - var pttrh = parseInt($("#ip_cl_ptrh").val())/100; - var pttrv = parseInt($("#ip_cl_ptrv").val())/100; + return createClassicLedLayout( params ); +} + +function createClassicLedLayout( params ){ //helper - var edgeHGap = edgeVGap/(16/9); + var edgeHGap = params.edgeVGap/(16/9); var ledArray = []; function createFinalArray(array){ - finalLedArray = []; + var finalLedArray = []; for(var i = 0; i-1; i--){ - var hmin = ovl("-",ptblh+(steph*Number([i]))+edgeHGap); - var hmax = ovl("+",ptblh+(steph*Number([i+1]))+edgeHGap); - var vmax= ptblv+(stepv*Number([i])); - var vmin = vmax-ledsHDepth; + for (var i = params.ledsbottom-1; i>-1; i--){ + var hmin = ovl("-",params.ptblh+(steph*Number([i]))+edgeHGap); + var hmax = ovl("+",params.ptblh+(steph*Number([i+1]))+edgeHGap); + var vmax= params.ptblv+(stepv*Number([i])); + var vmin = vmax-params.ledsHDepth; createLedArray(hmin, hmax, vmin, vmax); } } function createLeftLeds(){ - var steph = (ptblh - pttlh)/ledsleft; - var stepv = (ptblv - pttlv - (2*edgeVGap))/ledsleft; + var steph = (params.ptblh - params.pttlh)/params.ledsleft; + var stepv = (params.ptblv - params.pttlv - (2*params.edgeVGap))/params.ledsleft; - for (var i = ledsleft-1; i>-1; i--){ - var hmin = pttlh+(steph*Number([i])); - var hmax = hmin+ledsVDepth; - var vmin = ovl("-",pttlv+(stepv*Number([i]))+edgeVGap); - var vmax = ovl("+",pttlv+(stepv*Number([i+1]))+edgeVGap); + for (var i = params.ledsleft-1; i>-1; i--){ + var hmin = params.pttlh+(steph*Number([i])); + var hmax = hmin+params.ledsVDepth; + var vmin = ovl("-",params.pttlv+(stepv*Number([i]))+params.edgeVGap); + var vmax = ovl("+",params.pttlv+(stepv*Number([i+1]))+params.edgeVGap); createLedArray(hmin, hmax, vmin, vmax); } @@ -202,47 +198,87 @@ function createClassicLeds(){ createRightLeds(); createBottomLeds(); createLeftLeds(); - + //check led gap pos - if (ledsgpos+ledsglength > ledArray.length) + if (params.ledsgpos+params.ledsglength > ledArray.length) { - var mpos = Math.max(0,ledArray.length-ledsglength); - $('#ip_cl_ledsgpos').val(mpos); + var mpos = Math.max(0,ledArray.length-params.ledsglength); + //$('#ip_cl_ledsgpos').val(mpos); ledsgpos = mpos; } //check led gap length - if(ledsglength >= ledArray.length) + if(params.ledsglength >= ledArray.length) { - $('#ip_cl_ledsglength').val(ledArray.length-1); - ledsglength = ledArray.length-ledsglength-1; + //$('#ip_cl_ledsglength').val(ledArray.length-1); + params.ledsglength = ledArray.length-params.ledsglength-1; } - if(ledsglength != 0){ - ledArray.splice(ledsgpos, ledsglength); + if(params.ledsglength != 0){ + ledArray.splice(params.ledsgpos, params.ledsglength); } - if (position != 0){ - rotateArray(ledArray, position); + if (params.position != 0){ + rotateArray(ledArray, params.position); } - if (reverse) + if (params.reverse) ledArray.reverse(); - createFinalArray(ledArray); + return createFinalArray(ledArray); } -function createMatrixLeds(){ +function createClassicLeds(){ + + //get values + let params = { + ledstop : parseInt($("#ip_cl_top").val()), + ledsbottom : parseInt($("#ip_cl_bottom").val()), + ledsleft : parseInt($("#ip_cl_left").val()), + ledsright : parseInt($("#ip_cl_right").val()), + ledsglength : parseInt($("#ip_cl_glength").val()), + ledsgpos : parseInt($("#ip_cl_gpos").val()), + position : parseInt($("#ip_cl_position").val()), + reverse : $("#ip_cl_reverse").is(":checked"), + + //advanced values + ledsVDepth : parseInt($("#ip_cl_vdepth").val())/100, + ledsHDepth : parseInt($("#ip_cl_hdepth").val())/100, + edgeVGap : parseInt($("#ip_cl_edgegap").val())/100/2, + //cornerVGap : parseInt($("#ip_cl_cornergap").val())/100/2, + overlap : $("#ip_cl_overlap").val()/100, + + //trapezoid values % -> float + ptblh : parseInt($("#ip_cl_pblh").val())/100, + ptblv : parseInt($("#ip_cl_pblv").val())/100, + ptbrh : parseInt($("#ip_cl_pbrh").val())/100, + ptbrv : parseInt($("#ip_cl_pbrv").val())/100, + pttlh : parseInt($("#ip_cl_ptlh").val())/100, + pttlv : parseInt($("#ip_cl_ptlv").val())/100, + pttrh : parseInt($("#ip_cl_ptrh").val())/100, + pttrv : parseInt($("#ip_cl_ptrv").val())/100, + } + + finalLedArray = createClassicLedLayout( params ); + + //check led gap pos + if (params.ledsgpos+params.ledsglength > finalLedArray.length) { + var mpos = Math.max(0,finalLedArray.length-params.ledsglength); + $('#ip_cl_ledsgpos').val(mpos); + } + //check led gap length + if(params.ledsglength >= finalLedArray.length) { + $('#ip_cl_ledsglength').val(finalLedArray.length-1); + } + + createLedPreview(finalLedArray, 'classic'); +} + + +function createMatrixLayout( ledshoriz, ledsvert, cabling, start){ // Big thank you to RanzQ (Juha Rantanen) from Github for this script // https://raw.githubusercontent.com/RanzQ/hyperion-audio-effects/master/matrix-config.js - //get values - var ledshoriz = parseInt($("#ip_ma_ledshoriz").val()); - var ledsvert = parseInt($("#ip_ma_ledsvert").val()); - var cabling = $("#ip_ma_cabling").val(); - //var order = $("#ip_ma_order").val(); - var start = $("#ip_ma_start").val(); - var parallel = false var leds = [] var hblock = 1.0 / ledshoriz @@ -298,9 +334,23 @@ function createMatrixLeds(){ endX = tmp } } - finalLedArray =[]; - finalLedArray = leds - createLedPreview(leds, 'matrix'); + + return leds; +} + + +function createMatrixLeds(){ +// Big thank you to RanzQ (Juha Rantanen) from Github for this script +// https://raw.githubusercontent.com/RanzQ/hyperion-audio-effects/master/matrix-config.js + + //get values + var ledshoriz = parseInt($("#ip_ma_ledshoriz").val()); + var ledsvert = parseInt($("#ip_ma_ledsvert").val()); + var cabling = $("#ip_ma_cabling").val(); + var start = $("#ip_ma_start").val(); + + finalLedArray = createMatrixLayout(ledshoriz,ledsvert,cabling,start); + createLedPreview(finalLedArray, 'matrix'); } function migrateLedConfig(slConfig){ @@ -309,10 +359,10 @@ function migrateLedConfig(slConfig){ //Default Classic layout newLedConfig.classic = { - "top" : 8, - "bottom" : 8, - "left" : 5, - "right" : 5, + "top" : 1, + "bottom" : 0, + "left" : 0, + "right" : 0, "glength" : 0, "gpos" : 0, "position" : 0, @@ -336,8 +386,8 @@ function migrateLedConfig(slConfig){ newLedConfig.classic.overlap = slConfig.overlap; //Default Matrix layout - newLedConfig["matrix"] = { "ledshoriz": 10, - "ledsvert" : 10, + newLedConfig["matrix"] = { "ledshoriz": 1, + "ledsvert" : 1, "cabling" : "snake", "start" : "top-left" } @@ -468,6 +518,11 @@ $(document).ready(function() { $('#leds_custom_updsim').attr("disabled", true); $('#leds_custom_save').attr("disabled", true); } + + if ( window.readOnlyMode ) + { + $('#leds_custom_save').attr('disabled', true); + } } }, window.serverConfig.leds); @@ -520,7 +575,13 @@ $(document).ready(function() { }; // change save button state based on validation result - conf_editor.validate().length ? $('#btn_submit_controller').attr('disabled', true) : $('#btn_submit_controller').attr('disabled', false); + conf_editor.validate().length || window.readOnlyMode ? $('#btn_submit_controller').attr('disabled', true) : $('#btn_submit_controller').attr('disabled', false); + + conf_editor.on('change',function() { + window.readOnlyMode ? $('#btn_cl_save').attr('disabled', true) : $('#btn_submit').attr('disabled', false); + window.readOnlyMode ? $('#btn_ma_save').attr('disabled', true) : $('#btn_submit').attr('disabled', false); + window.readOnlyMode ? $('#leds_custom_save').attr('disabled', true) : $('#btn_submit').attr('disabled', false); + }); // led controller sepecific wizards $('#btn_wiz_holder').html(""); @@ -549,6 +610,12 @@ $(document).ready(function() { var atmoorb_title = 'wiz_atmoorb_title'; changeWizard(data, atmoorb_title, startWizardAtmoOrb); } + else if(ledType == "cololight") { + var ledWizardType = (this.checked) ? "cololight" : ledType; + var data = { type: ledWizardType }; + var cololight_title = 'wiz_cololight_title'; + changeWizard(data, cololight_title, startWizardCololight); + } else if(ledType == "yeelight") { var ledWizardType = (this.checked) ? "yeelight" : ledType; var data = { type: ledWizardType }; @@ -572,10 +639,10 @@ $(document).ready(function() { var devRPiPWM = ['ws281x']; var devRPiGPIO = ['piblaster']; - var devNET = ['atmoorb', 'fadecandy', 'philipshue', 'nanoleaf', 'razer', 'tinkerforge', 'tpm2net', 'udpe131', 'udpartnet', 'udph801', 'udpraw', 'wled', 'yeelight']; + var devNET = ['atmoorb', 'cololight', 'fadecandy', 'philipshue', 'nanoleaf', 'razer', 'tinkerforge', 'tpm2net', 'udpe131', 'udpartnet', 'udph801', 'udpraw', 'wled', 'yeelight']; var devUSB = ['adalight', 'dmx', 'atmo', 'hyperionusbasp', 'lightpack', 'paintpack', 'rawhid', 'sedu', 'tpm2', 'karate']; - var optArr = [[]]; + var optArr = [[]]; optArr[1]=[]; optArr[2]=[]; optArr[3]=[]; diff --git a/assets/webconfig/js/content_logging.js b/assets/webconfig/js/content_logging.js index 6836bc7b..8ace208d 100644 --- a/assets/webconfig/js/content_logging.js +++ b/assets/webconfig/js/content_logging.js @@ -22,7 +22,7 @@ $(document).ready(function() { }, true, true); conf_editor.on('change',function() { - conf_editor.validate().length ? $('#btn_submit').attr('disabled', true) : $('#btn_submit').attr('disabled', false); + conf_editor.validate().length || window.readOnlyMode ? $('#btn_submit').attr('disabled', true) : $('#btn_submit').attr('disabled', false); }); $('#btn_submit').off().on('click',function() { @@ -73,17 +73,32 @@ $(document).ready(function() { //create general info info = "### GENERAL ### \n"; - info += 'Build: '+shy.build+'\n'; - info += 'Build time: '+shy.time+'\n'; - info += 'Version: '+shy.version+'\n'; - info += 'UI Lang: '+storedLang+' (BrowserL: '+navigator.language+')\n'; - info += 'UI Access: '+storedAccess+'\n'; - info += 'Log lvl: '+window.serverConfig.logger.level+'\n'; - info += 'Avail Capt: '+window.serverInfo.grabbers.available+'\n\n'; - info += 'Distribution:'+sys.prettyName+'\n'; - info += 'Arch: '+sys.architecture+'\n'; - info += 'Kernel: '+sys.kernelType+' ('+sys.kernelVersion+' (WS: '+sys.wordSize+'))\n'; - info += 'Browser/OS: '+navigator.userAgent+'\n\n'; + info += 'Build: '+shy.build+'\n'; + info += 'Build time: '+shy.time+'\n'; + info += 'Version: '+shy.version+'\n'; + info += 'UI Lang: '+storedLang+' (BrowserL: '+navigator.language+')\n'; + info += 'UI Access: '+storedAccess+'\n'; + info += 'Log lvl: '+window.serverConfig.logger.level+'\n'; + info += 'Avail Capt: '+window.serverInfo.grabbers.available+'\n'; + info += 'Database: '+(shy.readOnlyMode ? "ready-only" : "read/write")+'\n'; + info += '\n'; + + info += 'Distribution: '+sys.prettyName+'\n'; + info += 'Architecture: '+sys.architecture+'\n'; + + if (sys.cpuModelName) + info += 'CPU Model: ' + sys.cpuModelName + '\n'; + if (sys.cpuModelType) + info += 'CPU Type: ' + sys.cpuModelType + '\n'; + if (sys.cpuRevision) + info += 'CPU Revision: ' + sys.cpuRevision + '\n'; + if (sys.cpuHardware) + info += 'CPU Hardware: ' + sys.cpuHardware + '\n'; + + info += 'Kernel: ' + sys.kernelType+' ('+sys.kernelVersion+' (WS: '+sys.wordSize+'))' + '\n'; + info += 'Qt Version: ' + sys.qtVersion + '\n'; + info += 'Python Version: ' + sys.pyVersion + '\n'; + info += 'Browser/OS: ' + navigator.userAgent + '\n\n'; //create prios info += "### PRIORITIES ### \n"; diff --git a/assets/webconfig/js/content_network.js b/assets/webconfig/js/content_network.js index 92d51f65..44f60b41 100644 --- a/assets/webconfig/js/content_network.js +++ b/assets/webconfig/js/content_network.js @@ -62,7 +62,7 @@ $(document).ready( function() { }, true, true); conf_editor_net.on('change',function() { - conf_editor_net.validate().length ? $('#btn_submit_net').attr('disabled', true) : $('#btn_submit_net').attr('disabled', false); + conf_editor_net.validate().length || window.readOnlyMode ? $('#btn_submit_net').attr('disabled', true) : $('#btn_submit_net').attr('disabled', false); }); $('#btn_submit_net').off().on('click',function() { @@ -75,7 +75,7 @@ $(document).ready( function() { }, true, true); conf_editor_json.on('change',function() { - conf_editor_json.validate().length ? $('#btn_submit_jsonserver').attr('disabled', true) : $('#btn_submit_jsonserver').attr('disabled', false); + conf_editor_json.validate().length || window.readOnlyMode ? $('#btn_submit_jsonserver').attr('disabled', true) : $('#btn_submit_jsonserver').attr('disabled', false); }); $('#btn_submit_jsonserver').off().on('click',function() { @@ -88,7 +88,7 @@ $(document).ready( function() { }, true, true); conf_editor_fbs.on('change',function() { - conf_editor_fbs.validate().length ? $('#btn_submit_fbserver').attr('disabled', true) : $('#btn_submit_fbserver').attr('disabled', false); + conf_editor_fbs.validate().length || window.readOnlyMode ? $('#btn_submit_fbserver').attr('disabled', true) : $('#btn_submit_fbserver').attr('disabled', false); }); $('#btn_submit_fbserver').off().on('click',function() { @@ -101,7 +101,7 @@ $(document).ready( function() { }, true, true); conf_editor_proto.on('change',function() { - conf_editor_proto.validate().length ? $('#btn_submit_protoserver').attr('disabled', true) : $('#btn_submit_protoserver').attr('disabled', false); + conf_editor_proto.validate().length || window.readOnlyMode ? $('#btn_submit_protoserver').attr('disabled', true) : $('#btn_submit_protoserver').attr('disabled', false); }); $('#btn_submit_protoserver').off().on('click',function() { @@ -114,7 +114,7 @@ $(document).ready( function() { }, true, true); conf_editor_bobl.on('change',function() { - conf_editor_bobl.validate().length ? $('#btn_submit_boblightserver').attr('disabled', true) : $('#btn_submit_boblightserver').attr('disabled', false); + conf_editor_bobl.validate().length || window.readOnlyMode ? $('#btn_submit_boblightserver').attr('disabled', true) : $('#btn_submit_boblightserver').attr('disabled', false); }); $('#btn_submit_boblightserver').off().on('click',function() { @@ -129,7 +129,7 @@ $(document).ready( function() { }, true, true); conf_editor_forw.on('change',function() { - conf_editor_forw.validate().length ? $('#btn_submit_forwarder').attr('disabled', true) : $('#btn_submit_forwarder').attr('disabled', false); + conf_editor_forw.validate().length || window.readOnlyMode ? $('#btn_submit_forwarder').attr('disabled', true) : $('#btn_submit_forwarder').attr('disabled', false); }); $('#btn_submit_forwarder').off().on('click',function() { diff --git a/assets/webconfig/js/content_webconfig.js b/assets/webconfig/js/content_webconfig.js index 8942a076..ea20ba45 100644 --- a/assets/webconfig/js/content_webconfig.js +++ b/assets/webconfig/js/content_webconfig.js @@ -14,7 +14,7 @@ }, true, true); conf_editor.on('change',function() { - conf_editor.validate().length ? $('#btn_submit').attr('disabled', true) : $('#btn_submit').attr('disabled', false); + conf_editor.validate().length || window.readOnlyMode ? $('#btn_submit').attr('disabled', true) : $('#btn_submit').attr('disabled', false); }); $('#btn_submit').off().on('click',function() { diff --git a/assets/webconfig/js/hyperion.js b/assets/webconfig/js/hyperion.js index b06805e7..a3dd781f 100644 --- a/assets/webconfig/js/hyperion.js +++ b/assets/webconfig/js/hyperion.js @@ -454,9 +454,9 @@ function requestAdjustment(type, value, complete) sendToHyperion("adjustment", "", '"adjustment": {"'+type+'": '+value+'}'); } -async function requestLedDeviceDiscovery(type) +async function requestLedDeviceDiscovery(type, params) { - let data = { ledDeviceType: type }; + let data = { ledDeviceType: type, params: params }; return sendAsyncToHyperion("leddevice", "discover", data, Math.floor(Math.random() * 1000) ); } @@ -471,8 +471,5 @@ async function requestLedDeviceProperties(type, params) function requestLedDeviceIdentification(type, params) { sendToHyperion("leddevice", "identify", '"ledDeviceType": "'+type+'","params": '+JSON.stringify(params)+''); - - //let data = {ledDeviceType: type, params: params}; - //sendToHyperion("leddevice", "identify", data ); } diff --git a/assets/webconfig/js/settings.js b/assets/webconfig/js/settings.js index 8a74ba30..961ecc36 100644 --- a/assets/webconfig/js/settings.js +++ b/assets/webconfig/js/settings.js @@ -21,7 +21,7 @@ function changePassword(){ }); $('#newPw, #oldPw').off().on('input',function(e) { - ($('#oldPw').val().length >= 8 && $('#newPw').val().length >= 8) ? $('#id_btn_ok').attr('disabled', false) : $('#id_btn_ok').attr('disabled', true); + ($('#oldPw').val().length >= 8 && $('#newPw').val().length >= 8) && !window.readOnlyMode ? $('#id_btn_ok').attr('disabled', false) : $('#id_btn_ok').attr('disabled', true); }); } diff --git a/assets/webconfig/js/ui_utils.js b/assets/webconfig/js/ui_utils.js index cae6b50c..62d8a6b4 100644 --- a/assets/webconfig/js/ui_utils.js +++ b/assets/webconfig/js/ui_utils.js @@ -184,7 +184,7 @@ function initLanguageSelection() for (var i = 0; i < availLang.length; i++) { $("#language-select").append(''); - } + } var langLocale = storedLang; @@ -389,7 +389,7 @@ function showInfoDialog(type,header,message) $(document).on('click', '[data-dismiss-modal]', function () { var target = $(this).attr('data-dismiss-modal'); - $(target).modal('hide'); + $.find(target).modal.hide(); }); } @@ -533,7 +533,7 @@ function createJsonEditor(container,schema,setconfig,usePanel,arrayre) { for(var key in editor.root.editors) { - editor.getEditor("root."+key).setValue( window.serverConfig[key] ); + editor.getEditor("root."+key).setValue(Object.assign({}, editor.getEditor("root."+key).value, window.serverConfig[key] )); } } diff --git a/assets/webconfig/js/wizard.js b/assets/webconfig/js/wizard.js index 0b48b032..19c3ead4 100644 --- a/assets/webconfig/js/wizard.js +++ b/assets/webconfig/js/wizard.js @@ -109,9 +109,13 @@ function beginWizardRGB() { if (redS == "r" && greenS == "g") { $('#btn_wiz_save').toggle(false); $('#btn_wiz_checkok').toggle(true); + + window.readOnlyMode ? $('#btn_wiz_checkok').attr('disabled', true) : $('#btn_wiz_checkok').attr('disabled', false); } else { $('#btn_wiz_save').toggle(true); + window.readOnlyMode ? $('#btn_wiz_save').attr('disabled', true) : $('#btn_wiz_save').attr('disabled', false); + $('#btn_wiz_checkok').toggle(false); } new_rgb_order = rgb_order; @@ -148,9 +152,11 @@ function beginWizardRGB() { $('#btn_wizard_byteorder').off().on('click', startWizardRGB); //color calibration wizard + var kodiHost = document.location.hostname; var kodiPort = 9090; var kodiAddress = kodiHost; + var wiz_editor; var colorLength; var cobj; @@ -164,6 +170,7 @@ var picnr = 0; var availVideos = ["Sweet_Cocoon", "Caminandes_2_GranDillama", "Caminandes_3_Llamigos"]; if (getStorage("kodiAddress") != null) { + kodiAddress = getStorage("kodiAddress"); [kodiHost, kodiPort] = kodiAddress.split(":", 2); @@ -390,6 +397,7 @@ function performAction() { $('#btn_wiz_next').attr("disabled", true); $('#btn_wiz_save').toggle(true); + window.readOnlyMode ? $('#btn_wiz_save').attr('disabled', true) : $('#btn_wiz_save').attr('disabled', false); } else { $('#btn_wiz_next').attr("disabled", false); @@ -407,16 +415,16 @@ function updateWEditor(el, all) { } function startWizardCC() { + // Ensure that Kodi's default REST-API port is not used, as now the Web-Socket port is used [kodiHost, kodiPort] = kodiAddress.split(":", 2); if (kodiPort === "8080") { kodiAddress = kodiHost; kodiPort = undefined; } - //create html $('#wiz_header').html('' + $.i18n('wiz_cc_title')); - $('#wizp1_body').html('

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

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

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

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

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

'); $('#wizp1_footer').html(''); $('#wizp2_body').html('
'); $('#wizp2_footer').html(''); @@ -429,7 +437,10 @@ function startWizardCC() { }); $('#wiz_cc_kodiip').off().on('change', function () { + kodiAddress = $(this).val().trim(); + $('#wizp1_body').find("kodiAddress").val(kodiAddress); + $('#kodi_status').html(''); // Remove Kodi's default Web-Socket port (9090) from display and ensure Kodi's default REST-API port (8080) is mapped to web-socket port to ease migration @@ -579,6 +590,30 @@ function assignLightPos(id, pos, name) { return i; } +function getHostInLights(hostname) { + return lights.filter( + function (lights) { + return lights.host === hostname + } + ); +} + +function getIpInLights(ip) { + return lights.filter( + function (lights) { + return lights.ip === ip + } + ); +} + +function getIdInLights(id) { + return lights.filter( + function (lights) { + return lights.id === id + } + ); +} + //**************************** // Wizard Philips Hue //**************************** @@ -792,17 +827,8 @@ async function getProperties_hue_bridge(hostAddress, username, resourceFilter) { } function identify_hue_device(hostAddress, username, id) { - console.log("identify_hue_device"); - let params = { host: hostAddress, user: username, lightId: id }; - - const res = requestLedDeviceIdentification("philipshue", params); - // TODO: error case unhandled - // res can be: false (timeout) or res.error (not found) - if (res && !res.error) { - const r = res.info - console.log(r); - } + requestLedDeviceIdentification("philipshue", params); } function getHueIPs() { @@ -1151,7 +1177,9 @@ function get_hue_lights() { cC++; } } - (cC == 0) ? $('#btn_wiz_save').attr("disabled", true) : $('#btn_wiz_save').attr("disabled", false); + + (cC == 0 || window.readOnlyMode) ? $('#btn_wiz_save').attr("disabled", true) : $('#btn_wiz_save').attr("disabled", false); + }); } $('.hue_sel_watch').trigger('change'); @@ -1263,14 +1291,7 @@ async function getProperties_wled(hostAddress, resourceFilter) { function identify_wled(hostAddress) { let params = { host: hostAddress }; - - const res = requestLedDeviceIdentification("wled", params); - // TODO: error case unhandled - // res can be: false (timeout) or res.error (not found) - if (res && !res.error) { - const r = res.info - console.log(r); - } + requestLedDeviceIdentification("wled", params); } //**************************** @@ -1382,14 +1403,6 @@ function beginWizardYeelight() { $('#btn_wiz_abort').off().on('click', resetWizard); } -function getHostInLights(hostname) { - return lights.filter( - function (lights) { - return lights.host === hostname - } - ); -} - async function discover_yeelight_lights() { var light = {}; // Get discovered lights @@ -1406,10 +1419,9 @@ async function discover_yeelight_lights() { if (device.hostname !== "") { if (getHostInLights(device.hostname).length === 0) { - light = {}; + var light = {}; light.host = device.hostname; light.port = device.port; - light.name = device.other.name; light.model = device.other.model; lights.push(light); @@ -1427,7 +1439,7 @@ async function discover_yeelight_lights() { if (host !== "") if (getHostInLights(host).length === 0) { - light = {}; + var light = {}; light.host = host; light.port = port; light.name = configuredLights[keyConfig].name; @@ -1446,7 +1458,7 @@ function assign_yeelight_lights() { // If records are left for configuration if (Object.keys(lights).length > 0) { $('#wh_topcontainer').toggle(false); - $('#yee_ids_t,#btn_wiz_save').toggle(true); + $('#yee_ids_t, #btn_wiz_save').toggle(true); var lightOptions = [ "top", "topleft", "topright", @@ -1478,7 +1490,7 @@ function assign_yeelight_lights() { options += '>' + $.i18n(txt + val) + ''; } - var enabled = 'enabled' + var enabled = 'enabled'; if (!models.includes(lights[lightid].model)) { var enabled = 'disabled'; options = ''; @@ -1497,7 +1509,8 @@ function assign_yeelight_lights() { cC++; } } - if (cC === 0) + + if (cC === 0 || window.readOnlyMode) $('#btn_wiz_save').attr("disabled", true); else $('#btn_wiz_save').attr("disabled", false); @@ -1505,7 +1518,7 @@ function assign_yeelight_lights() { $('.yee_sel_watch').trigger('change'); } else { - var noLightsTxt = '

' + $.i18n('wiz_yeelight_noLights') + '

'; + var noLightsTxt = '

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

'; $('#wizp2_body').append(noLightsTxt); } } @@ -1527,13 +1540,7 @@ async function getProperties_yeelight(hostname, port) { function identify_yeelight_device(hostname, port) { let params = { hostname: hostname, port: port }; - - const res = requestLedDeviceIdentification("yeelight", params); - // TODO: error case unhandled - // res can be: false (timeout) or res.error (not found) - if (res && !res.error) { - //const r = res.info; - } + requestLedDeviceIdentification("yeelight", params); } //**************************** @@ -1580,7 +1587,7 @@ function beginWizardAtmoOrb() { lights = []; configuredLights = []; - configruedOrbIds = conf_editor.getEditor("root.specificOptions.orbIds").getValue().trim(); + var configruedOrbIds = conf_editor.getEditor("root.specificOptions.orbIds").getValue().trim(); if (configruedOrbIds.length !== 0) { configuredLights = configruedOrbIds.split(",").map(Number); } @@ -1640,24 +1647,19 @@ function beginWizardAtmoOrb() { $('#btn_wiz_abort').off().on('click', resetWizard); } -function getIdInLights(id) { - return lights.filter( - function (lights) { - return lights.id === id - } - ); -} - async function discover_atmoorb_lights(multiCastGroup, multiCastPort) { var light = {}; - if (multiCastGroup === "") - multiCastGroup = "239.255.255.250"; + var params = {}; + if (multiCastGroup !== "") + { + params.multiCastGroup = multiCastGroup; + } - if (multiCastPort === "") - multiCastPort = 49692; - - let params = { multiCastGroup: multiCastGroup, multiCastPort: multiCastPort }; + if (multiCastPort !== 0) + { + params.multiCastPort = multiCastPort; + } // Get discovered lights const res = await requestLedDeviceDiscovery('atmoorb', params); @@ -1665,13 +1667,13 @@ async function discover_atmoorb_lights(multiCastGroup, multiCastPort) { // TODO: error case unhandled // res can be: false (timeout) or res.error (not found) if (res && !res.error) { - const r = res.info - + const r = res.info; + // Process devices returned by discovery for (const device of r.devices) { if (device.id !== "") { if (getIdInLights(device.id).length === 0) { - light = {}; + var light = {}; light.id = device.id; light.ip = device.ip; light.host = device.hostname; @@ -1684,7 +1686,7 @@ async function discover_atmoorb_lights(multiCastGroup, multiCastPort) { for (const keyConfig in configuredLights) { if (configuredLights[keyConfig] !== "" && !isNaN(configuredLights[keyConfig])) { if (getIdInLights(configuredLights[keyConfig]).length === 0) { - light = {}; + var light = {}; light.id = configuredLights[keyConfig]; light.ip = ""; light.host = ""; @@ -1735,9 +1737,9 @@ function assign_atmoorb_lights() { options += '>' + $.i18n(txt + val) + ''; } - var enabled = 'enabled' + var enabled = 'enabled'; if (orbId < 1 || orbId > 255) { - enabled = 'disabled' + enabled = 'disabled'; options = ''; } @@ -1759,7 +1761,7 @@ function assign_atmoorb_lights() { cC++; } } - if (cC === 0) + if (cC === 0 || window.readOnlyMode) $('#btn_wiz_save').attr("disabled", true); else $('#btn_wiz_save').attr("disabled", false); @@ -1767,20 +1769,14 @@ function assign_atmoorb_lights() { $('.orb_sel_watch').trigger('change'); } else { - var noLightsTxt = '

' + $.i18n('wiz_atmoorb_noLights') + '

'; + var noLightsTxt = '

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

'; $('#wizp2_body').append(noLightsTxt); } } function identify_atmoorb_device(orbId) { let params = { id: orbId }; - - const res = requestLedDeviceIdentification("atmoorb", params); - // TODO: error case unhandled - // res can be: false (timeout) or res.error (not found) - if (res && !res.error) { - const r = res.info - } + requestLedDeviceIdentification("atmoorb", params); } //**************************** @@ -1829,16 +1825,197 @@ async function getProperties_nanoleaf(hostAddress, authToken, resourceFilter) { function identify_nanoleaf(hostAddress, authToken) { let params = { host: hostAddress, token: authToken }; + requestLedDeviceIdentification("nanoleaf", params); +} + +//**************************** +// Wizard Cololight +//**************************** +var lights = null; +var selectedLightId = null; + +function startWizardCololight(e) { + //create html + + var cololight_title = 'wiz_cololight_title'; + var cololight_intro1 = 'wiz_cololight_intro1'; + + $('#wiz_header').html('' + $.i18n(cololight_title)); + $('#wizp1_body').html('

' + $.i18n(cololight_title) + '

' + $.i18n(cololight_intro1) + '

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

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

'; + $('#wizp2_body').append(noLightsTxt); + } +} + +async function getProperties_cololight(ip) { + let params = { host: ip }; + + const res = await requestLedDeviceProperties('cololight', params); + + if (res && !res.error) { + var coloLightProperties = res.info; + + //Store properties along light with given IP-address + var id = getIpInLights(ip)[0].id; + lights[id].props = coloLightProperties; + } +} + +function identify_cololight_device(hostAddress) { + let params = { host: hostAddress }; + requestLedDeviceIdentification("cololight", params); +} + //**************************** // Wizard/Routines RS232-Devices //**************************** @@ -1860,7 +2037,6 @@ async function discover_providerRs232(rs232Type) { //**************************** async function discover_providerHid(hidType) { const res = await requestLedDeviceDiscovery(hidType); - console.log("discover_providerHid", res); // TODO: error case unhandled // res can be: false (timeout) or res.error (not found) diff --git a/assets/webconfig/mstile-144x144.png b/assets/webconfig/mstile-144x144.png index 8d7a2df2..def5042e 100644 Binary files a/assets/webconfig/mstile-144x144.png and b/assets/webconfig/mstile-144x144.png differ diff --git a/bin/compile.sh b/bin/compile.sh index 8005ad46..7843d2b9 100755 --- a/bin/compile.sh +++ b/bin/compile.sh @@ -27,6 +27,7 @@ sudo apt-get install \ libssl-dev \ libjpeg-dev \ libqt5sql5-sqlite \ + libqt5svg5-dev \ zlib1g-dev \ if [ -e /dev/vc-cma -a -e /dev/vc-mem ] diff --git a/bin/service/hyperion.systemd.RO b/bin/service/hyperion.systemd.RO deleted file mode 100644 index 179871c6..00000000 --- a/bin/service/hyperion.systemd.RO +++ /dev/null @@ -1,16 +0,0 @@ -[Unit] -Description=Hyperion ambient light systemd service for OpenELEC/LibreELEC/RasPlex -After=graphical.target -After=network.target - -[Service] -WorkingDirectory=/storage/hyperion/bin/ -Environment=LD_LIBRARY_PATH=$LD_LIBRARY_PATH:. -ExecStart=./hyperiond /storage/.config/hyperion/hyperion.config.json -TimeoutStopSec=5 -KillMode=mixed -Restart=on-failure -RestartSec=2 - -[Install] -WantedBy=default.target diff --git a/cmake/Dependencies.cmake b/cmake/Dependencies.cmake index f3f40462..caa92d83 100644 --- a/cmake/Dependencies.cmake +++ b/cmake/Dependencies.cmake @@ -311,6 +311,28 @@ macro(DeployWindows TARGET) ) endforeach() + # Download DirectX End-User Runtimes (June 2010) + set(url "https://download.microsoft.com/download/8/4/A/84A35BF1-DAFE-4AE8-82AF-AD2AE20B6B14/directx_Jun2010_redist.exe") + if(NOT EXISTS "${CMAKE_CURRENT_BINARY_DIR}/dx_redist.exe") + file(DOWNLOAD "${url}" "${CMAKE_CURRENT_BINARY_DIR}/dx_redist.exe" + STATUS result + ) + + # Check if the download is successful + list(GET result 0 result_code) + if(NOT result_code EQUAL 0) + list(GET result 1 reason) + message(FATAL_ERROR "Could not download DirectX End-User Runtimes: ${reason}") + endif() + endif() + + # Copy DirectX End-User Runtimes to 'hyperion' + install( + FILES ${CMAKE_CURRENT_BINARY_DIR}/dx_redist.exe + DESTINATION "bin" + COMPONENT "Hyperion" + ) + else() # Run CMake after target was built add_custom_command( diff --git a/cmake/FindDirectX9.cmake b/cmake/FindDirectX9.cmake new file mode 100644 index 00000000..e09049dd --- /dev/null +++ b/cmake/FindDirectX9.cmake @@ -0,0 +1,32 @@ +# Find the DirectX 9 includes and library +# This module defines: +# DIRECTX9_INCLUDE_DIRS, where to find d3d9.h, etc. +# DIRECTX9_LIBRARIES, libraries to link against to use DirectX. +# DIRECTX9_FOUND, If false, do not try to use DirectX. +# DIRECTX9_ROOT_DIR, directory where DirectX was installed. + +set(DIRECTX9_INCLUDE_PATHS + "$ENV{DXSDK_DIR}/Include" + "$ENV{DIRECTX_ROOT}/Include" + "C:/Program Files (x86)/Microsoft DirectX SDK (June 2010)/Include" + "C:/Program Files/Microsoft DirectX SDK (June 2010)/Include" +) +find_path(DIRECTX9_INCLUDE_DIRS d3dx9.h ${DIRECTX9_INCLUDE_PATHS} NO_DEFAULT_PATH) + +get_filename_component(DIRECTX9_ROOT_DIR "${DIRECTX9_INCLUDE_DIRS}/.." ABSOLUTE) + +if (CMAKE_SIZEOF_VOID_P EQUAL 8) + set(DIRECTX9_LIBRARY_PATHS "${DIRECTX9_ROOT_DIR}/Lib/x64") +else () + set(DIRECTX9_LIBRARY_PATHS "${DIRECTX9_ROOT_DIR}/Lib/x86" "${DIRECTX9_ROOT_DIR}/Lib") +endif () + +find_library(DIRECTX9_D3D9_LIBRARY d3d9 ${DIRECTX9_LIBRARY_PATHS} NO_DEFAULT_PATH) +find_library(DIRECTX9_D3DX9_LIBRARY d3dx9 ${DIRECTX9_LIBRARY_PATHS} NO_DEFAULT_PATH) +find_library(DIRECTX9_DXERR_LIBRARY DxErr ${DIRECTX9_LIBRARY_PATHS} NO_DEFAULT_PATH) +set(DIRECTX9_LIBRARIES ${DIRECTX9_D3D9_LIBRARY} ${DIRECTX9_D3DX9_LIBRARY} ${DIRECTX9_DXERR_LIBRARY}) + +# handle the QUIETLY and REQUIRED arguments and set DIRECTX9_FOUND to TRUE if all listed variables are TRUE +include(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(DirectX9 DEFAULT_MSG DIRECTX9_ROOT_DIR DIRECTX9_LIBRARIES DIRECTX9_INCLUDE_DIRS) +mark_as_advanced(DIRECTX9_INCLUDE_DIRS DIRECTX9_D3D9_LIBRARY DIRECTX9_D3DX9_LIBRARY DIRECTX9_DXERR_LIBRARY) diff --git a/cmake/debian/postinst b/cmake/debian/postinst index 0527c94d..140a6962 100644 --- a/cmake/debian/postinst +++ b/cmake/debian/postinst @@ -21,6 +21,7 @@ echo "---Hyperion ambient light postinstall ---" #check system CPU_RPI=`grep -m1 -c 'BCM2708\|BCM2709\|BCM2710\|BCM2835\|BCM2836\|BCM2837\|BCM2711' /proc/cpuinfo` CPU_X32X64=`uname -m | grep 'x86_32\|i686\|x86_64' | wc -l` +OS_HYPERBIAN=`grep ID /etc/os-release | grep -m1 -c HyperBian` #Check for a bootloader as Berryboot BOOT_BERRYBOOT=$(grep -m1 -c '\(/var/media\|/media/pi\)/berryboot' /etc/mtab) @@ -47,12 +48,19 @@ if grep -m1 systemd /proc/1/comm > /dev/null then echo "---> init deamon: systemd" # systemd - install_file /usr/share/hyperion/service/hyperion.systemd /etc/systemd/system/hyperiond@.service - # service registration just on Raspberry Pi, probably need to ask the user how we should use the service. TODO service start in user login scope eg for x11?! - if [ $ENABLE_SERVICE -eq 1 ]; then - systemctl enable hyperiond"@${FOUND_USR}".service - start_msg="--> systemctl start hyperiond for user ${FOUND_USR}" - systemctl start hyperiond"@${FOUND_USR}" + if [ $OS_HYPERBIAN -eq 1 ]; then + # start service only + echo "--> Service file already exists, skip creation" + start_msg="--> systemctl start hyperion" + systemctl start hyperion + else + install_file /usr/share/hyperion/service/hyperion.systemd /etc/systemd/system/hyperiond@.service + # service registration just on Raspberry Pi, probably need to ask the user how we should use the service. TODO service start in user login scope eg for x11?! + if [ $ENABLE_SERVICE -eq 1 ]; then + systemctl enable hyperiond"@${FOUND_USR}".service + start_msg="--> systemctl start hyperiond for user ${FOUND_USR}" + systemctl start hyperiond"@${FOUND_USR}" + fi fi elif [ -e /sbin/initctl ] @@ -92,11 +100,13 @@ ln -fs $BINSP/hyperion-xcb $BINTP/hyperion-xcb 2>/dev/null ln -fs $BINSP/hyperion-aml $BINTP/hyperion-aml 2>/dev/null ln -fs $BINSP/hyperion-qt $BINTP/hyperion-qt 2>/dev/null -# install desktop icons -echo "---> Install Hyperion desktop icons" -mkdir /usr/share/pixmaps/hyperion 2>/dev/null -cp /usr/share/hyperion/desktop/*.png /usr/share/pixmaps/hyperion 2>/dev/null -desktop-file-install /usr/share/hyperion/desktop/hyperiond.desktop 2>/dev/null +# install desktop icons / not on HyperBian +if [ $OS_HYPERBIAN -ne 1 ]; then + echo "---> Install Hyperion desktop icons" + mkdir /usr/share/pixmaps/hyperion 2>/dev/null + cp /usr/share/hyperion/desktop/*.png /usr/share/pixmaps/hyperion 2>/dev/null + desktop-file-install /usr/share/hyperion/desktop/hyperiond.desktop 2>/dev/null +fi # cleanup desktop icons rm -r /usr/share/hyperion/desktop 2>/dev/null diff --git a/cmake/debian/preinst b/cmake/debian/preinst index 5559dca8..4aa8f037 100644 --- a/cmake/debian/preinst +++ b/cmake/debian/preinst @@ -12,7 +12,7 @@ then then echo "--> stop init deamon: systemd" # systemd - systemctl stop hyperiond"@${FOUND_USR}" 2> /dev/null + systemctl stop hyperion hyperiond"@${FOUND_USR}" 2> /dev/null elif [ -e /sbin/initctl ] then diff --git a/cmake/debian/prerm b/cmake/debian/prerm index ceb8c5a4..f43561cf 100644 --- a/cmake/debian/prerm +++ b/cmake/debian/prerm @@ -13,10 +13,10 @@ if grep -m1 systemd /proc/1/comm > /dev/null then echo "---> stop init deamon: systemd" # systemd - $HYPERION_RUNNING && systemctl stop hyperiond"@${FOUND_USR}" 2> /dev/null - # disable user specific symlink + $HYPERION_RUNNING && systemctl stop hyperion hyperiond"@${FOUND_USR}" 2> /dev/null + # disable user specific symlink / not on HyperBian echo "---> Disable service and remove entry" - systemctl -q disable hyperiond"@${FOUND_USR}" + systemctl -q disable hyperiond"@${FOUND_USR}" 2> /dev/null rm -v /etc/systemd/system/hyperiond@.service 2>/dev/null elif [ -e /sbin/initctl ] diff --git a/cmake/desktop/hyperiond_128.png b/cmake/desktop/hyperiond_128.png index 8da3dd17..be7b4b16 100644 Binary files a/cmake/desktop/hyperiond_128.png and b/cmake/desktop/hyperiond_128.png differ diff --git a/cmake/nsis/template/FindProcDLL.dll b/cmake/nsis/plugins/FindProcDLL.dll similarity index 100% rename from cmake/nsis/template/FindProcDLL.dll rename to cmake/nsis/plugins/FindProcDLL.dll diff --git a/cmake/nsis/template/NSIS.template.in b/cmake/nsis/template/NSIS.template.in index e86adc24..ccb28ce5 100644 --- a/cmake/nsis/template/NSIS.template.in +++ b/cmake/nsis/template/NSIS.template.in @@ -43,6 +43,8 @@ ;Tnstaller window branding text BrandingText /TRIMLEFT "@CPACK_NSIS_BRANDING_TEXT@" +@CPACK_NSIS_EXTRA_DEFS@ + @CPACK_NSIS_DEFINES@ !include Sections.nsh @@ -787,8 +789,15 @@ Section "-Core installation" ; Goto End ;End: +; Install Visual c++ Redistributable ExecWait '"$INSTDIR\bin\vc_redist.x64.exe" /install /quiet' +; Install DirectX 9 Redistributable +ExecWait '"$INSTDIR\bin\dx_redist.exe" /q /t:"$INSTDIR\tmp"' + ExecWait '"$INSTDIR\tmp\DXSETUP.exe" /silent' + Delete '$INSTDIR\tmp\*.*' + RMDir '$INSTDIR\tmp' + SectionEnd Section "-Add to path" diff --git a/cmake/packages.cmake b/cmake/packages.cmake index 755865aa..685ac0c5 100644 --- a/cmake/packages.cmake +++ b/cmake/packages.cmake @@ -58,7 +58,7 @@ ENDIF() # https://cmake.org/Wiki/CMake:CPackPackageGenerators # .deb files for apt SET ( CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA "${CMAKE_CURRENT_SOURCE_DIR}/cmake/debian/preinst;${CMAKE_CURRENT_SOURCE_DIR}/cmake/debian/postinst;${CMAKE_CURRENT_SOURCE_DIR}/cmake/debian/prerm" ) -SET ( CPACK_DEBIAN_PACKAGE_DEPENDS "libcec4" ) +SET ( CPACK_DEBIAN_PACKAGE_DEPENDS "libcec6 | libcec4" ) SET ( CPACK_DEBIAN_PACKAGE_SECTION "Miscellaneous" ) # .rpm for rpm @@ -105,19 +105,12 @@ SET ( CPACK_NSIS_HELP_LINK "https://www.hyperion-project.org") SET ( CPACK_NSIS_URL_INFO_ABOUT "https://www.hyperion-project.org") SET ( CPACK_NSIS_MUI_FINISHPAGE_RUN "hyperiond.exe") SET ( CPACK_NSIS_BRANDING_TEXT "Hyperion-${HYPERION_VERSION}") +# custom nsis plugin directory +SET ( CPACK_NSIS_EXTRA_DEFS "!addplugindir ${CMAKE_SOURCE_DIR}/cmake/nsis/plugins") # additional hyperiond startmenu link, won't be created if the user disables startmenu links SET ( CPACK_NSIS_CREATE_ICONS_EXTRA "CreateShortCut '$SMPROGRAMS\\\\$STARTMENU_FOLDER\\\\Hyperion (Console).lnk' '$INSTDIR\\\\bin\\\\hyperiond.exe' '-d -c'") SET ( CPACK_NSIS_DELETE_ICONS_EXTRA "Delete '$SMPROGRAMS\\\\$MUI_TEMP\\\\Hyperion (Console).lnk'") - -#SET ( CPACK_NSIS_CREATE_ICONS_EXTRA "CreateShortCut '$SMPROGRAMS\\\\$STARTMENU_FOLDER\\\\Hyperion.lnk' '$INSTDIR\\\\bin\\\\hyperiond.exe'") -#SET ( CPACK_NSIS_DELETE_ICONS_EXTRA "Delete '$SMPROGRAMS\\\\$START_MENU\\\\Hyperion.lnk'") -# hyperiond desktop link -#SET ( CPACK_NSIS_CREATE_ICONS_EXTRA "CreateShortCut '$DESKTOP\\\\Hyperion.lnk' '$INSTDIR\\\\bin\\\\hyperiond.exe' ") -#SET ( CPACK_NSIS_EXTRA_UNINSTALL_COMMANDS "Delete '$DESKTOP\\\\Hyperion.lnk' ") -#SET ( CPACK_NSIS_EXTRA_INSTALL_COMMANDS "CreateShortCut \\\"$DESKTOP\\\\Hyperion.lnk\\\" \\\"$INSTDIR\\\\bin\\\\hyperiond.exe\\\" ") -#SET ( CPACK_NSIS_EXTRA_UNINSTALL_COMMANDS "Delete \\\"$DESKTOP\\\\Hyperion.lnk\\\" ") - # define the install components # See also https://gitlab.kitware.com/cmake/community/-/wikis/doc/cpack/Component-Install-With-CPack # and https://cmake.org/cmake/help/latest/module/CPackComponent.html diff --git a/config/hyperion.config.json.commented b/config/hyperion.config.json.commented index f1595970..35ab38af 100644 --- a/config/hyperion.config.json.commented +++ b/config/hyperion.config.json.commented @@ -165,10 +165,7 @@ "pixelDecimation" : 8, // valid for qt - "display" 0, - - // valid for framebuffer - "device" : "/dev/fb0" + "display" 0 }, /// The black border configuration, contains the following items: @@ -178,7 +175,7 @@ /// * borderFrameCnt : Number of frames before a consistent detected border gets set (default 50) /// * maxInconsistentCnt : Number of inconsistent frames that are ignored before a new border gets a chance to proof consistency /// * blurRemoveCnt : Number of pixels that get removed from the detected border to cut away blur (default 1) - /// * mode : Border detection mode (values=default,classic,osd) + /// * mode : Border detection mode (values=default,classic,osd,letterbox) "blackborderdetector" : { "enable" : true, diff --git a/config/hyperion.config.json.default b/config/hyperion.config.json.default index 170d55de..ac5ef2ba 100644 --- a/config/hyperion.config.json.default +++ b/config/hyperion.config.json.default @@ -48,12 +48,16 @@ "smoothing" : { - "enable" : true, - "type" : "linear", - "time_ms" : 200, - "updateFrequency" : 25.0000, - "updateDelay" : 0, - "continuousOutput" : true + "enable" : true, + "type" : "linear", + "time_ms" : 200, + "updateFrequency" : 25.0000, + "interpolationRate" : 25.0000, + "outputRate" : 25.0000, + "decay" : 1, + "dithering" : false, + "updateDelay" : 0, + "continuousOutput" : true }, "grabberV4L2" : @@ -91,7 +95,7 @@ "cropRight" : 0, "cropTop" : 0, "cropBottom" : 0, - "device" : "/dev/fb0" + "display" : 0 }, "blackborderdetector" : @@ -116,7 +120,7 @@ "backgroundEffect" : { - "enable" : true, + "enable" : false, "type" : "effect", "color" : [255,138,0], "effect" : "Warm mood blobs" @@ -193,10 +197,10 @@ { "classic": { - "top" : 8, - "bottom" : 8, - "left" : 5, - "right" : 5, + "top" : 1, + "bottom" : 0, + "left" : 0, + "right" : 0, "glength" : 0, "gpos" : 0, "position" : 0, @@ -217,8 +221,8 @@ "matrix": { - "ledshoriz": 10, - "ledsvert" : 10, + "ledshoriz": 1, + "ledsvert" : 1, "cabling" : "snake", "start" : "top-left" } @@ -227,160 +231,10 @@ "leds": [ { - "hmax": 0.125, + "hmax": 1, "hmin": 0, "vmax": 0.08, "vmin": 0 - }, - { - "hmax": 0.25, - "hmin": 0.125, - "vmax": 0.08, - "vmin": 0 - }, - { - "hmax": 0.375, - "hmin": 0.25, - "vmax": 0.08, - "vmin": 0 - }, - { - "hmax": 0.5, - "hmin": 0.375, - "vmax": 0.08, - "vmin": 0 - }, - { - "hmax": 0.625, - "hmin": 0.5, - "vmax": 0.08, - "vmin": 0 - }, - { - "hmax": 0.75, - "hmin": 0.625, - "vmax": 0.08, - "vmin": 0 - }, - { - "hmax": 0.875, - "hmin": 0.75, - "vmax": 0.08, - "vmin": 0 - }, - { - "hmax": 1, - "hmin": 0.875, - "vmax": 0.08, - "vmin": 0 - }, - { - "hmax": 1, - "hmin": 0.95, - "vmax": 0.2, - "vmin": 0 - }, - { - "hmax": 1, - "hmin": 0.95, - "vmax": 0.4, - "vmin": 0.2 - }, - { - "hmax": 1, - "hmin": 0.95, - "vmax": 0.6, - "vmin": 0.4 - }, - { - "hmax": 1, - "hmin": 0.95, - "vmax": 0.8, - "vmin": 0.6 - }, - { - "hmax": 1, - "hmin": 0.95, - "vmax": 1, - "vmin": 0.8 - }, - { - "hmax": 1, - "hmin": 0.875, - "vmax": 1, - "vmin": 0.92 - }, - { - "hmax": 0.875, - "hmin": 0.75, - "vmax": 1, - "vmin": 0.92 - }, - { - "hmax": 0.75, - "hmin": 0.625, - "vmax": 1, - "vmin": 0.92 - }, - { - "hmax": 0.625, - "hmin": 0.5, - "vmax": 1, - "vmin": 0.92 - }, - { - "hmax": 0.5, - "hmin": 0.375, - "vmax": 1, - "vmin": 0.92 - }, - { - "hmax": 0.375, - "hmin": 0.25, - "vmax": 1, - "vmin": 0.92 - }, - { - "hmax": 0.25, - "hmin": 0.125, - "vmax": 1, - "vmin": 0.92 - }, - { - "hmax": 0.125, - "hmin": 0, - "vmax": 1, - "vmin": 0.92 - }, - { - "hmax": 0.05, - "hmin": 0, - "vmax": 1, - "vmin": 0.8 - }, - { - "hmax": 0.05, - "hmin": 0, - "vmax": 0.8, - "vmin": 0.6 - }, - { - "hmax": 0.05, - "hmin": 0, - "vmax": 0.6, - "vmin": 0.4 - }, - { - "hmax": 0.05, - "hmin": 0, - "vmax": 0.4, - "vmin": 0.2 - }, - { - "hmax": 0.05, - "hmin": 0, - "vmax": 0.2, - "vmin": 0 } ] } diff --git a/doc/screenshot.png b/doc/screenshot.png index 3b1c24e7..2dfa62be 100644 Binary files a/doc/screenshot.png and b/doc/screenshot.png differ diff --git a/docs/development/LedDevice_Flows.png b/docs/development/LedDevice_Flows.png index f6ce9ee0..1c12dba0 100644 Binary files a/docs/development/LedDevice_Flows.png and b/docs/development/LedDevice_Flows.png differ diff --git a/docs/docs/.vuepress/public/hyperion-logo.png b/docs/docs/.vuepress/public/hyperion-logo.png index 139205e8..1d3036ce 100644 Binary files a/docs/docs/.vuepress/public/hyperion-logo.png and b/docs/docs/.vuepress/public/hyperion-logo.png differ diff --git a/docs/docs/.vuepress/public/icons/apple-icon-120x120.png b/docs/docs/.vuepress/public/icons/apple-icon-120x120.png index 45b700db..169bb6d5 100644 Binary files a/docs/docs/.vuepress/public/icons/apple-icon-120x120.png and b/docs/docs/.vuepress/public/icons/apple-icon-120x120.png differ diff --git a/docs/docs/.vuepress/public/icons/apple-icon-152x152.png b/docs/docs/.vuepress/public/icons/apple-icon-152x152.png index 019e24bc..ca8c49d0 100644 Binary files a/docs/docs/.vuepress/public/icons/apple-icon-152x152.png and b/docs/docs/.vuepress/public/icons/apple-icon-152x152.png differ diff --git a/docs/docs/.vuepress/public/icons/apple-icon-167x167.png b/docs/docs/.vuepress/public/icons/apple-icon-167x167.png index 86ac91bb..2f5c0dfc 100644 Binary files a/docs/docs/.vuepress/public/icons/apple-icon-167x167.png and b/docs/docs/.vuepress/public/icons/apple-icon-167x167.png differ diff --git a/docs/docs/.vuepress/public/icons/apple-icon-180x180.png b/docs/docs/.vuepress/public/icons/apple-icon-180x180.png index 19ac97d9..11007fe9 100644 Binary files a/docs/docs/.vuepress/public/icons/apple-icon-180x180.png and b/docs/docs/.vuepress/public/icons/apple-icon-180x180.png differ diff --git a/docs/docs/.vuepress/public/icons/apple-launch-1125x2436.png b/docs/docs/.vuepress/public/icons/apple-launch-1125x2436.png index f58e3a59..b9cef742 100644 Binary files a/docs/docs/.vuepress/public/icons/apple-launch-1125x2436.png and b/docs/docs/.vuepress/public/icons/apple-launch-1125x2436.png differ diff --git a/docs/docs/.vuepress/public/icons/apple-launch-1242x2208.png b/docs/docs/.vuepress/public/icons/apple-launch-1242x2208.png index 4d83a1c8..2e6e322d 100644 Binary files a/docs/docs/.vuepress/public/icons/apple-launch-1242x2208.png and b/docs/docs/.vuepress/public/icons/apple-launch-1242x2208.png differ diff --git a/docs/docs/.vuepress/public/icons/apple-launch-1242x2688.png b/docs/docs/.vuepress/public/icons/apple-launch-1242x2688.png index 3607035d..96fbd2a4 100644 Binary files a/docs/docs/.vuepress/public/icons/apple-launch-1242x2688.png and b/docs/docs/.vuepress/public/icons/apple-launch-1242x2688.png differ diff --git a/docs/docs/.vuepress/public/icons/apple-launch-1536x2048.png b/docs/docs/.vuepress/public/icons/apple-launch-1536x2048.png index e88a7776..98a9928d 100644 Binary files a/docs/docs/.vuepress/public/icons/apple-launch-1536x2048.png and b/docs/docs/.vuepress/public/icons/apple-launch-1536x2048.png differ diff --git a/docs/docs/.vuepress/public/icons/apple-launch-1668x2224.png b/docs/docs/.vuepress/public/icons/apple-launch-1668x2224.png index 0beaac44..d0549a00 100644 Binary files a/docs/docs/.vuepress/public/icons/apple-launch-1668x2224.png and b/docs/docs/.vuepress/public/icons/apple-launch-1668x2224.png differ diff --git a/docs/docs/.vuepress/public/icons/apple-launch-1668x2388.png b/docs/docs/.vuepress/public/icons/apple-launch-1668x2388.png index ded455ef..8158e92e 100644 Binary files a/docs/docs/.vuepress/public/icons/apple-launch-1668x2388.png and b/docs/docs/.vuepress/public/icons/apple-launch-1668x2388.png differ diff --git a/docs/docs/.vuepress/public/icons/apple-launch-2048x2732.png b/docs/docs/.vuepress/public/icons/apple-launch-2048x2732.png index 8a7fb064..fecd09c8 100644 Binary files a/docs/docs/.vuepress/public/icons/apple-launch-2048x2732.png and b/docs/docs/.vuepress/public/icons/apple-launch-2048x2732.png differ diff --git a/docs/docs/.vuepress/public/icons/apple-launch-640x1136.png b/docs/docs/.vuepress/public/icons/apple-launch-640x1136.png index f72c017c..b5042c27 100644 Binary files a/docs/docs/.vuepress/public/icons/apple-launch-640x1136.png and b/docs/docs/.vuepress/public/icons/apple-launch-640x1136.png differ diff --git a/docs/docs/.vuepress/public/icons/apple-launch-750x1334.png b/docs/docs/.vuepress/public/icons/apple-launch-750x1334.png index ecd2feca..9e66b4e8 100644 Binary files a/docs/docs/.vuepress/public/icons/apple-launch-750x1334.png and b/docs/docs/.vuepress/public/icons/apple-launch-750x1334.png differ diff --git a/docs/docs/.vuepress/public/icons/apple-launch-828x1792.png b/docs/docs/.vuepress/public/icons/apple-launch-828x1792.png index 36dc8276..59925e08 100644 Binary files a/docs/docs/.vuepress/public/icons/apple-launch-828x1792.png and b/docs/docs/.vuepress/public/icons/apple-launch-828x1792.png differ diff --git a/docs/docs/.vuepress/public/icons/favicon-128x128.png b/docs/docs/.vuepress/public/icons/favicon-128x128.png index 9881beb3..16b685c7 100644 Binary files a/docs/docs/.vuepress/public/icons/favicon-128x128.png and b/docs/docs/.vuepress/public/icons/favicon-128x128.png differ diff --git a/docs/docs/.vuepress/public/icons/favicon-96x96.png b/docs/docs/.vuepress/public/icons/favicon-96x96.png index 4ac3dd7c..e06ea630 100644 Binary files a/docs/docs/.vuepress/public/icons/favicon-96x96.png and b/docs/docs/.vuepress/public/icons/favicon-96x96.png differ diff --git a/docs/docs/.vuepress/public/icons/icon-128x128.png b/docs/docs/.vuepress/public/icons/icon-128x128.png index 9881beb3..16b685c7 100644 Binary files a/docs/docs/.vuepress/public/icons/icon-128x128.png and b/docs/docs/.vuepress/public/icons/icon-128x128.png differ diff --git a/docs/docs/.vuepress/public/icons/icon-192x192.png b/docs/docs/.vuepress/public/icons/icon-192x192.png index 4864d939..b6cd4aad 100644 Binary files a/docs/docs/.vuepress/public/icons/icon-192x192.png and b/docs/docs/.vuepress/public/icons/icon-192x192.png differ diff --git a/docs/docs/.vuepress/public/icons/icon-256x256.png b/docs/docs/.vuepress/public/icons/icon-256x256.png index c1799ba1..9ba11917 100644 Binary files a/docs/docs/.vuepress/public/icons/icon-256x256.png and b/docs/docs/.vuepress/public/icons/icon-256x256.png differ diff --git a/docs/docs/.vuepress/public/icons/icon-384x384.png b/docs/docs/.vuepress/public/icons/icon-384x384.png index ba6c0d03..852ae387 100644 Binary files a/docs/docs/.vuepress/public/icons/icon-384x384.png and b/docs/docs/.vuepress/public/icons/icon-384x384.png differ diff --git a/docs/docs/.vuepress/public/icons/icon-512x512.png b/docs/docs/.vuepress/public/icons/icon-512x512.png index 8d277f0e..69ac0340 100644 Binary files a/docs/docs/.vuepress/public/icons/icon-512x512.png and b/docs/docs/.vuepress/public/icons/icon-512x512.png differ diff --git a/docs/docs/.vuepress/public/icons/ms-icon-144x144.png b/docs/docs/.vuepress/public/icons/ms-icon-144x144.png index cb68c67e..1bbbaf7a 100644 Binary files a/docs/docs/.vuepress/public/icons/ms-icon-144x144.png and b/docs/docs/.vuepress/public/icons/ms-icon-144x144.png differ diff --git a/docs/docs/.vuepress/public/images/en/avahi-browse.jpg b/docs/docs/.vuepress/public/images/en/avahi-browse.jpg index 67c52081..54097bbc 100644 Binary files a/docs/docs/.vuepress/public/images/en/avahi-browse.jpg and b/docs/docs/.vuepress/public/images/en/avahi-browse.jpg differ diff --git a/docs/docs/.vuepress/public/images/en/http_jsonrpc.jpg b/docs/docs/.vuepress/public/images/en/http_jsonrpc.jpg index 8fde4256..6e1f25bf 100644 Binary files a/docs/docs/.vuepress/public/images/en/http_jsonrpc.jpg and b/docs/docs/.vuepress/public/images/en/http_jsonrpc.jpg differ diff --git a/docs/docs/.vuepress/public/images/en/owneff_1.jpg b/docs/docs/.vuepress/public/images/en/owneff_1.jpg index 57b16715..21580cf2 100644 Binary files a/docs/docs/.vuepress/public/images/en/owneff_1.jpg and b/docs/docs/.vuepress/public/images/en/owneff_1.jpg differ diff --git a/docs/docs/.vuepress/public/images/en/owneff_2.jpg b/docs/docs/.vuepress/public/images/en/owneff_2.jpg index 71dc5b18..f68d9a77 100644 Binary files a/docs/docs/.vuepress/public/images/en/owneff_2.jpg and b/docs/docs/.vuepress/public/images/en/owneff_2.jpg differ diff --git a/docs/docs/.vuepress/public/images/en/owneff_3.gif b/docs/docs/.vuepress/public/images/en/owneff_3.gif index 0bf4b159..9918f4a3 100644 Binary files a/docs/docs/.vuepress/public/images/en/owneff_3.gif and b/docs/docs/.vuepress/public/images/en/owneff_3.gif differ diff --git a/docs/docs/.vuepress/public/images/en/owneff_4.gif b/docs/docs/.vuepress/public/images/en/owneff_4.gif index 5b90ff32..49ad9e82 100644 Binary files a/docs/docs/.vuepress/public/images/en/owneff_4.gif and b/docs/docs/.vuepress/public/images/en/owneff_4.gif differ diff --git a/docs/docs/.vuepress/public/images/en/user_bbmodes.jpg b/docs/docs/.vuepress/public/images/en/user_bbmodes.jpg index 282edcd9..bb363b9c 100644 Binary files a/docs/docs/.vuepress/public/images/en/user_bbmodes.jpg and b/docs/docs/.vuepress/public/images/en/user_bbmodes.jpg differ diff --git a/docs/docs/.vuepress/public/images/en/user_config_access.jpg b/docs/docs/.vuepress/public/images/en/user_config_access.jpg index b64cb563..6d618547 100644 Binary files a/docs/docs/.vuepress/public/images/en/user_config_access.jpg and b/docs/docs/.vuepress/public/images/en/user_config_access.jpg differ diff --git a/docs/docs/.vuepress/public/images/en/user_config_lang.jpg b/docs/docs/.vuepress/public/images/en/user_config_lang.jpg index 320d6faa..5d9cc277 100644 Binary files a/docs/docs/.vuepress/public/images/en/user_config_lang.jpg and b/docs/docs/.vuepress/public/images/en/user_config_lang.jpg differ diff --git a/docs/docs/.vuepress/public/images/en/user_gammacurve.png b/docs/docs/.vuepress/public/images/en/user_gammacurve.png index c221f9f3..2a8b4127 100644 Binary files a/docs/docs/.vuepress/public/images/en/user_gammacurve.png and b/docs/docs/.vuepress/public/images/en/user_gammacurve.png differ diff --git a/docs/docs/.vuepress/public/images/en/user_hyperbian_ssh.jpg b/docs/docs/.vuepress/public/images/en/user_hyperbian_ssh.jpg index ab5d8274..641ddc97 100644 Binary files a/docs/docs/.vuepress/public/images/en/user_hyperbian_ssh.jpg and b/docs/docs/.vuepress/public/images/en/user_hyperbian_ssh.jpg differ diff --git a/docs/docs/.vuepress/public/images/en/user_hyperbian_wpa_suppli1.jpg b/docs/docs/.vuepress/public/images/en/user_hyperbian_wpa_suppli1.jpg index 90b0d7bc..aa9cfdd7 100644 Binary files a/docs/docs/.vuepress/public/images/en/user_hyperbian_wpa_suppli1.jpg and b/docs/docs/.vuepress/public/images/en/user_hyperbian_wpa_suppli1.jpg differ diff --git a/docs/docs/.vuepress/public/images/en/user_hyperbian_wpa_suppli2.jpg b/docs/docs/.vuepress/public/images/en/user_hyperbian_wpa_suppli2.jpg index aeb86e15..389a28c3 100644 Binary files a/docs/docs/.vuepress/public/images/en/user_hyperbian_wpa_suppli2.jpg and b/docs/docs/.vuepress/public/images/en/user_hyperbian_wpa_suppli2.jpg differ diff --git a/docs/docs/.vuepress/public/images/en/user_hyperbian_wpa_suppli3.jpg b/docs/docs/.vuepress/public/images/en/user_hyperbian_wpa_suppli3.jpg index e8d749de..c8ff6082 100644 Binary files a/docs/docs/.vuepress/public/images/en/user_hyperbian_wpa_suppli3.jpg and b/docs/docs/.vuepress/public/images/en/user_hyperbian_wpa_suppli3.jpg differ diff --git a/docs/docs/.vuepress/public/images/en/user_ledlayout3.jpg b/docs/docs/.vuepress/public/images/en/user_ledlayout3.jpg index e8f03505..4acbe6b7 100644 Binary files a/docs/docs/.vuepress/public/images/en/user_ledlayout3.jpg and b/docs/docs/.vuepress/public/images/en/user_ledlayout3.jpg differ diff --git a/docs/docs/en/api/Detect.md b/docs/docs/en/api/Detect.md index 510ee6ee..4b20fd89 100644 --- a/docs/docs/en/api/Detect.md +++ b/docs/docs/en/api/Detect.md @@ -1,52 +1,57 @@ # Detect Hyperion -Hyperion pronounces it's services at the network. Currently with ZeroConf and SSDP. +Hyperion announces it's services on the network, via ZeroConf and SSDP. [[toc]] ## SSDP -**S**imple**S**ervice**D**iscovery**P**rotocol ([SSDP](https://en.wikipedia.org/wiki/Simple_Service_Discovery_Protocol)), is the discovery subset of UPnP. The implementation is lighter than ZeroConf as it just needs a UdpSocket without further dependencies. +**S**imple**S**ervice**D**iscovery**P**rotocol +([SSDP](https://en.wikipedia.org/wiki/Simple_Service_Discovery_Protocol)) is the +discovery subset of UPnP. The implementation is lighter than ZeroConf as it just needs a +UDP Socket without any further dependencies. ### SSDP-Client Library -Here are some client libraries for different programming languages. You can also search for "UPnP" client libraries. This list isn't complete, nor tested but will show you how simple it actually is! +Here are some example client libraries for different programming languages (many others available): * [NodeJS](https://github.com/diversario/node-ssdp#usage---client) * [Java](https://github.com/resourcepool/ssdp-client#jarpic-client) ### Usage -If you found a ssdp-client library, all you need to do is searching for the following USN / service type: +With a given SSDP-client library, you can use the following USN / service type: `urn:hyperion-project.org:device:basic:1` -Some headers from the response. - * Location: The URL of the webserver - * USN: The unique id for this Hyperion instance, it will remain the same also after system restarts or Hyperion updates - * HYPERION-FBS-PORT: The port of the flatbuffers server - * HYPERION-JSS-PORT: The port of the JsonServer - * HYPERION-NAME: The user defined name for this server - * More may be added in future with additional data to other Hyperion network ports +Some headers from the response will include: + * **Location**: The URL of the webserver + * **USN**: The unique id for this Hyperion instance, it will remain the same after system restarts or Hyperion updates + * **HYPERION-FBS-PORT**: The port of the flatbuffers server + * **HYPERION-JSS-PORT**: The port of the JsonServer + * **HYPERION-NAME**: The user defined name for this server -You will receive further notifications when the data changes (Network adapter changed the IP Address, port change) or Hyperion shuts down. +As the data changes (e.g. network adapter IP address change), new updates will be automatically announced. ## Zeroconf Also known as [Apple Bonjour](https://en.wikipedia.org/wiki/Bonjour_(software)) or [Avahi](https://en.wikipedia.org/wiki/Avahi_(software)). Hyperion is detectable through zeroconf. **Hyperion publishes the following informations:** - * _hyperiond-http._tcp -> Hyperion Webserver (HTTP+Websocket) - * _hyperiond-json._tcp -> Hyperion JSON Server (TcpSocket) - * _hyperiond-flatbuf._tcp -> Hyperion Flatbuffers server (Google Flatbuffers) + * **_hyperiond-http._tcp**: Hyperion Webserver (HTTP+Websocket) + * **_hyperiond-json._tcp**: Hyperion JSON Server (TcpSocket) + * **_hyperiond-flatbuf._tcp**: Hyperion Flatbuffers server (Google Flatbuffers) -So you get the IP address, hostname and port of the system. Also the Hyperion instance name is part of it (before the @ for the full name). As this works realtime you have always an up2date list of available Hyperion servers right to your hand. So check your development environment if you have access to it. +You get the IP address, hostname, port and the Hyperion instance name (before the @ for +the full name). As this works realtime you can always have an up to date list of available +Hyperion servers. ### TXT RECORD -Each published entry contains at least the following informations at the txt field - * id = A static unique id to identify a hyperion instance (good value to sort between new and known instances) - * version = Hyperion version +Each published entry contains at least the following data in the txt field: + * **id**: A static unique id to identify an Hyperion instance. + * **version**: Hyperion version. ### Test Clients -There are several clients available for testing like the [avahi-browse](http://manpages.ubuntu.com/manpages/bionic/man1/avahi-browse.1.html) commandline tool for ubuntu/debian. Example command +There are several clients available for testing like the +[avahi-browse](http://manpages.ubuntu.com/manpages/bionic/man1/avahi-browse.1.html) a +commandline tool for Ubuntu/Debian. Example command ``` bash -sudo apt-get install avahi-browse && -avahi-browse -r _hyperiond-http._tcp +sudo apt-get install avahi-browse && avahi-browse -r _hyperiond-http._tcp ``` \ No newline at end of file diff --git a/docs/docs/en/api/Guidelines.md b/docs/docs/en/api/Guidelines.md index 497fe569..f37aa4a7 100644 --- a/docs/docs/en/api/Guidelines.md +++ b/docs/docs/en/api/Guidelines.md @@ -1,13 +1,14 @@ # Guidelines -Improve the experience with Hyperion by following a rule set +Improve the user experience with Hyperion by following these guidelines. [[toc]] ## Priority Guidelines -The possibilities of priorities mixing and using is endless. But where are a lot of possibilities you require also some guidelines to reduce confusion on user and developer side and to get the best matching experience possible. +Please adhere to the following priority guidelines to avoid user confusion and ensure +the best user experience possible: -The user expects, that an Effect or Color should be higher in priority than capturing as you usually run them from remotes and not all time. -While we have different capture/input methods, we follow also a specific priority chain to make sure that it fit's the most use cases out of the box. +The user expects that an effect or color should be higher in priority (lower in value) +than capturing, as colors/effects are usually run intermittently. | Type | Priority/Range | Recommended | Comment | | :---------------------: | :------------: | :---------: | :----------------------------------------: | @@ -19,5 +20,4 @@ While we have different capture/input methods, we follow also a specific priorit | USB Capture | 240 | - | | | Platform Capture | 250 | - | | | Background Effect/Color | 254 | - | | -| Reserved | 255 | - | | - +| Reserved | 255 | - | | \ No newline at end of file diff --git a/docs/docs/en/json/Authorization.md b/docs/docs/en/json/Authorization.md index 3d0dbf3f..f7e4bbce 100644 --- a/docs/docs/en/json/Authorization.md +++ b/docs/docs/en/json/Authorization.md @@ -1,21 +1,27 @@ ## Authorization -Hyperion has an authorization system where people can login via password and applications with Tokens. The user can decide how strong or weak the Hyperion API should be protected. +Hyperion has an authorization system allowing users to login via password, and +applications to login with tokens. The user can configure how strong or weak the Hyperion API +should be protected from the `Configuration` -> `Network Services` panel on the Web UI. [[toc]] ### Token System -Tokens are a simple way to authenticate an App for API access. They can be created "by hand" TODO LINK TO WEBUI TOKEN CREATION at the webconfiguration or your application can [request one](#request-a-token). +Tokens are a simple way to authenticate an App for API access. They can be created in +the UI on the `Configuration` -> `Network Services` panel (the panel appears when `API +Authentication` options is checked). Your application can also [request a +token](#request-a-token) via the API. ### Authorization Check -It's for sure useful to check if you actually need an authorization to work with the API. Ask Hyperion by sending -``` json + +Callers can check whether authorization is required to work with the API, by sending: +```json { "command" : "authorize", "subcommand" : "tokenRequired" } ``` -If the property "required" is true, you need authentication. Example response. -``` json +If the property `required` is true, authentication is required. An example response: +```json { "command" : "authorize-tokenRequired", "info" : { @@ -27,8 +33,8 @@ If the property "required" is true, you need authentication. Example response. ``` ### Login with Token -Login with your token as follows. And get a [Login response](#login-response) -``` json +Login with a token as follows -- the caller will receive a [Login response](#login-response). +```json { "command" : "authorize", "subcommand" : "login", @@ -37,14 +43,14 @@ Login with your token as follows. And get a [Login response](#login-response) ``` ### Login with Token over HTTP/S -Add the HTTP Authorization header to every request. On error, you will get a failed [Login response](#login-response) -``` http +Add the HTTP Authorization header to every request. On error, the user will get a failed [Login response](#login-response). +```http Authorization : token YourPrivateTokenHere ``` #### Login response -Login success response -``` json +A successful login response: +```json { "command" : "authorize-login", "success" : true, @@ -52,8 +58,8 @@ Login success response } ``` -Login failed response -``` json +A failed login response: +```json { "command" : "authorize-login", "error" : "No Authorization", @@ -63,15 +69,18 @@ Login failed response ``` ### Logout -You can also logout. Hyperion doesn't verify the login state, it will be always a success. -``` json +Users can also logout. Hyperion doesn't verify the login state, this call will always +return a success. + +```json { "command" : "authorize", "subcommand" : "logout" } ``` -Response -``` json + +Response: +```json { "command" : "authorize-logout", "success" : true, @@ -79,19 +88,26 @@ Response } ``` ::: warning -A Logout will stop all streaming data services and subscriptions +Logging out will stop all streaming data services and subscriptions ::: ### Request a Token -If you want to get the most comfortable way for your application to authenticate - * You ask Hyperion for a token along with a comment (A text which identifies you as the sender, meaningful informations are desired - appname + device) and a short random created id (numbers/letters) - * Wait for the response, the user needs to accept the request from the webconfiguration - * -> On success you get a UUID token which is now your personal app token - * -> On error you won't get a token, in this case the user denied the request or it timed out (180s). - * Now you are able to access the API, your access can be revoked by the user at any time, but will last for current connected sessions. -Requesting a token is easy, just send the following command, make sure to add a sufficient comment. The "id" field has 5 random chars created by yourself. And add a meaningful comment. -``` json +Here is the recommended workflow for your application to authenticate: + * Ask Hyperion for a token along with a comment (a short meaningful string that + identifies the caller is the most useful, e.g. includes an application name and + device) and a short randomly created `id` (numbers/letters). + * Wait for the response. The user will need to accept the token request from the Web UI. + * On success: The call will return a UUID token that can be repeatedly used. Note that + access could be revoked by the user at any time, but will continue to last for + currently connected sessions in this case. + * On error: The call won't get a token, which means the user either denied the request or it timed out (180s). + +Request a token using the follow command, being sure to add a comment that is +descriptive enough for the Web UI user to make a decision as to whether to grant or deny +the request. The `id` field has 5 random chars created by the caller, which will appear +in the Web UI as the user considers granting their approval. +```json { "command" : "authorize", "subcommand" : "requestToken", @@ -99,13 +115,15 @@ Requesting a token is easy, just send the following command, make sure to add a "id" : "T3c91" } ``` -Now you wait for the response, show a popup that the user should login to the webconfiguration and accept the token request. Show the comment and the id so that the user can confirm the origin properly. After 180 seconds without a user action, the request is automatically rejected. You will get a notification about the failure. + +After the call, a popup will appear in the Web UI to accept/reject the token request. +The calling application should show the comment and the id so that the user can confirm +the origin properly in the Hyperion UI. After 180 seconds without a user action, the +request is automatically rejected, and the caller will get a failure response (see below). #### Success response -If the user accepted your token request you will get the following message. - * Save the token somewhere for further use, it doesn't expire. - * Be aware that a user can revoke the access. It will last for current connected sessions. -``` json +If the user accepted the token request the caller will get the following response: +```json { "command" : "authorize-requestToken", "success" : true, @@ -116,12 +134,15 @@ If the user accepted your token request you will get the following message. } } ``` + * Save the token somewhere for further use. The token does not expire. + * Be aware that a user can revoke the token. It will continue to function for currently connected sessions. #### Failed response -A request will fail when - * Timeout - no user action for 180 seconds - * User denied the request -``` json +A request will fail when either: + * It times out (i.e. user neither approves nor rejects for 180 seconds after the request + is sent). + * User rejects the request. +```json { "command" : "authorize-requestToken", "success" : false, @@ -130,8 +151,9 @@ A request will fail when ``` #### Request abort -You can abort the request by adding an "accept" property to the original request. The request will be deleted -``` json +You can abort the token request by adding an "accept" property to the original request. +The request will be deleted: +```json { "command" : "authorize", "subcommand" : "requestToken", @@ -140,4 +162,3 @@ You can abort the request by adding an "accept" property to the original request "accept" : false } ``` - \ No newline at end of file diff --git a/docs/docs/en/json/Control.md b/docs/docs/en/json/Control.md index 29074bf8..84509c16 100644 --- a/docs/docs/en/json/Control.md +++ b/docs/docs/en/json/Control.md @@ -1,8 +1,8 @@ # Control -You can control Hyperion by sending specific JSON messages. Or get a image and led colors stream of the current active source priority. +You can control Hyperion by sending specific JSON messages. ::: tip -The `tan` property is supported, but omitted. +The `tan` property is supported in these calls, but omitted for brevity. ::: [[toc]] @@ -15,19 +15,19 @@ Set a color for all leds or provide a pattern of led colors. | Property | Type | Required | Comment | | :------: | :-----: | :------: | :--------------------------------------------------------------------------------------------------------------: | | color | Array | true | An array of R G B Integer values e.g. `[R,G,B]` | -| duration | Integer | false | Duration of color in ms. If you don't provide a duration, it's `0` -> endless | +| duration | Integer | false | Duration of color in ms. If you don't provide a duration, it's `0` -> indefinite | | priority | Integer | true | We recommend `50`, following the [Priority Guidelines](/en/api/guidelines#priority_guidelines). Min `2` Max `99` | | origin | String | true | A short name of your application like `Hyperion of App` . Max length is `20`, min `4`. | -``` json -// Example: Set color blue with endless duration at priority 50 +```json +// Example: Set a blue color with indefinite duration at priority 50 { "command":"color", "color":[0,0,255], "priority":50, "origin":"My Fancy App" } -// Example: Set color cyan for 12 seconds at priority 20 +// Example: Set a cyan color for 12 seconds at priority 20 { "command":"color", "color":[0,255,255], @@ -36,8 +36,8 @@ Set a color for all leds or provide a pattern of led colors. "origin":"My Fancy App" } -// Example: Provide a color pattern, which will be reapted until all LEDs have a color -// In this case LED 1: Red, LED 2: Red, LED 3: Blue. This repeats now +// Example: Provide a color pattern, which will be repeated until all LEDs have a color +// In this case LED 1: Red, LED 2: Red, LED 3: Blue. { "command":"color", "color":[255,0,0,255,0,0,0,0,255], // one led has 3 values (Red,Green,Blue) with range of 0-255 @@ -48,17 +48,17 @@ Set a color for all leds or provide a pattern of led colors. ``` ### Set Effect -Set an effect by name. Get a name from [Serverinfo](/en/json/ServerInfo.md#effect-list) +Set an effect by name. Available names can be found in the [serverinfo effect list](/en/json/ServerInfo.md#effect-list). | Property | Type | Required | Comment | | :------: | :-----: | :------: | :--------------------------------------------------------------------------------------------------------------: | | effect | Object | true | Object with additional properties. e.g. `"name":"EffectName"`. | -| duration | Integer | false | Duration of effect in ms. If you don't provide a duration, it's `0` -> endless | +| duration | Integer | false | Duration of effect in ms. If you don't provide a duration, it's `0` -> indefinite | | priority | Integer | true | We recommend `50`, following the [Priority Guidelines](/en/api/guidelines#priority_guidelines). Min `2` Max `99` | | origin | String | true | A short name of your application like `Hyperion of App` . Max length is `20`, min `4`. | -``` json -// Example: Set effect Warm mood blobs with endless duration +```json +// Example: Set the 'Warm mood blobs' effect with indefinite duration { "command":"effect", "effect":{ @@ -67,7 +67,7 @@ Set an effect by name. Get a name from [Serverinfo](/en/json/ServerInfo.md#effec "priority":50, "origin":"My Fancy App" } -// Example: Set effect Rainbow swirl for 5 seconds +// Example: Set 'Rainbow swirl' effect for 5 seconds { "command":"effect", "effect":{ @@ -77,9 +77,9 @@ Set an effect by name. Get a name from [Serverinfo](/en/json/ServerInfo.md#effec "priority":50, "origin":"My Fancy App" } -// Example: Set effect Rainbow swirl for 1 seconds with overwritten agrument -// Each effect has different agruments inside the args property that can be overwritten -// WARNING: We highly recommend to use the effects configurator instead. As you can send wrong values and the effect can crash/ behave strange +// Example: Set 'Rainbow swirl' effect for 1 second with overridden agrument +// Each effect has different agruments inside the args property that can be overridden. +// WARNING: We highly recommend using the effects configurator in the UI instead. Sending invalid values may cause the effect to misbehave or crash. { "command":"effect", "effect":{ @@ -94,7 +94,7 @@ Set an effect by name. Get a name from [Serverinfo](/en/json/ServerInfo.md#effec ``` ### Set Image -Set a single image. Supported are all [Qt5](https://doc.qt.io/qt-5/qimagereader.html#supportedImageFormats) image formats, including png/jpg/gif. +Set a single image. Supports all [Qt5](https://doc.qt.io/qt-5/qimagereader.html#supportedImageFormats) image formats, including png/jpg/gif. | Property | Type | Required | Comment | | :-------: | :-----: | :------: | :--------------------------------------------------------------------------------------------------------------: | @@ -105,8 +105,8 @@ Set a single image. Supported are all [Qt5](https://doc.qt.io/qt-5/qimagereader. | priority | Integer | true | We recommend `50`, following the [Priority Guidelines](/en/api/guidelines#priority_guidelines). Min `2` Max `99` | | origin | String | true | A short name of your application like `Hyperion of App` . Max length is `20`, min `4`. | -``` json -// Set a image for 5 seconds +```json +// Set an image for 5 seconds { "command":"image", "imagedata":"VGhpcyBpcyBubyBpbWFnZSEgOik=", // as base64! @@ -119,8 +119,9 @@ Set a single image. Supported are all [Qt5](https://doc.qt.io/qt-5/qimagereader. ``` ### Clear -Clear a priority, usually used to revert these: [set color](#set-color), [set effect](#set-effect), [set image](#set-image) -``` json +Clear a priority, usually used to revert [set color](#set-color), [set +effect](#set-effect) or [set image](#set-image). +```json // Clear effect/color/image with priority 50 { "command":"clear", @@ -133,11 +134,13 @@ Clear a priority, usually used to revert these: [set color](#set-color), [set ef } ``` ::: warning -When you clear all, you clear all effects and colors independent who set it! We recommend to provide a list of possible clear targets instead based on the priority list +When you clear all, you clear all effects and colors regardless of who set them! +Instead, we recommend users provide a list of possible clear targets based on a +priority list ::: ### Adjustments -Adjustments reflect the color calibration. You can modify all properties of [serverinfo adjustments](/en/json/serverinfo#adjustments) +Adjustments reflect the color calibration. You can modify all properties of [serverinfo adjustments](/en/json/serverinfo#adjustments). | Property | Type | Required | Comment | | :--------------------: | :------------: | :------: | :--------------------------------------------------------------------------------------------: | @@ -157,7 +160,7 @@ Adjustments reflect the color calibration. You can modify all properties of [ser | backlightColored | Boolean | false | If `true` the backlight is colored, `false` it's white. Disabled for effect/color/image | | id | String | false | Short identifier | -``` json +```json // Example: Set gammaRed to 1.5 { "command":"adjustment", @@ -191,8 +194,8 @@ Adjustments reflect the color calibration. You can modify all properties of [ser ``` ### LED mapping -Switch the image to led mapping mode. Available are `unicolor_mean` (led color based on whole picture color) and `multicolor_mean` (led colors based on led layout) -``` json +Switch the image to led mapping mode. Possible values are `unicolor_mean` (led color based on whole picture color) and `multicolor_mean` (led colors based on led layout) +```json // Example: Set mapping mode to multicolor_mean { "command":"processing", @@ -206,8 +209,8 @@ Switch the image to led mapping mode. Available are `unicolor_mean` (led color b ``` ### Video Mode -Switch the video mode between 2D, 3DSBS, 3DTAB. - ``` json +Switch the video mode. Possible values are: `2D`, `3DSBS` and `3DTAB`. + ```json // Example: Set video mode to 3DTAB { "command":"videomode", @@ -221,12 +224,12 @@ Switch the video mode between 2D, 3DSBS, 3DTAB. ``` ### Control Components -It is possible to enable and disable certain components during runtime. -To get the current state and available components See [serverinfo Components](/en/json/serverinfo#components). -See also: [Components/IDs explained](#components-ids-explained) +Some components can be enabled and disabled at runtime. To get the current state and +available components see [Serverinfo Components](/en/json/serverinfo#components). See +also: [Components/IDs explained](#components-ids-explained) - ``` json -// Example: disable component LEDDEVICE + ```json +// Example: disable LEDDEVICE component { "command":"componentstate", "componentstate":{ @@ -234,7 +237,7 @@ See also: [Components/IDs explained](#components-ids-explained) "state":false } } -// Example: enable component SMOOTHING +// Example: enable SMOOTHING component { "command":"componentstate", "componentstate":{ @@ -244,12 +247,13 @@ See also: [Components/IDs explained](#components-ids-explained) } ``` ::: warning -Hyperion needs to be enabled! Check the status of "ALL" at the components list before you change another component! +Hyperion itself needs to be enabled! Check the status of "ALL" in the components list before you change another component! ::: ### Components/IDs explained -Each component has a unique id. Not all of them can be enabled/disabled some of them like effect and color is used to determine the source - type you are confronted with at the priority overview. +Each component has a unique id. Not all of them can be enabled/disabled -- some of them, +such as effect and color, are used to determine the source type when examining the +[priority list](/en/json/ServerInfo.html#priorities). | ComponentID | Component | Enable/Disable | Comment | | :------------: | :------------------: | :------------: | :---------------------------------------------------------------------------: | | SMOOTHING | Smoothing | Yes | Smoothing component | @@ -268,30 +272,36 @@ Each component has a unique id. Not all of them can be enabled/disabled some of ### Source Selection -Sources are always selected automatically by priority value (lowest number first), now you could select on your own a specific priority which should be visible (shown). You need the priority value of the source you want to select. Get them from the [serverinfo Priorities](/en/json/serverinfo#priorities). -``` json +Sources are always selected automatically by priority value (lowest value is the highest +priority). You need to know the priority value of the source you want to select -- these +priority values are available in the [serverinfo +Priorities](/en/json/serverinfo#priorities). +```json // Example: Set priority 50 to visible { "command":"sourceselect", "priority":50 } ``` -If you get a success response, the `priorities_autoselect`-status will switch to false: [serverinfo Autoselection Mode](/en/json/serverinfo##priorities-selection-auto-manual). You are now in manual mode, to switch back to auto mode send: -``` json +If you get a success response, the `priorities_autoselect`-status will switch to false (see [serverinfo Autoselection Mode](/en/json/serverinfo##priorities-selection-auto-manual)). You are now in manual mode, to switch back to auto mode send: +```json { "command":"sourceselect", "auto":true } ``` -Now, the `priorities_autoselect`-status will be again true +After which, the `priorities_autoselect`-status will return to `true`. ::: warning -You can just select priorities which are `active:true`! +You can only select priorities which are `active:true`! ::: ### Control Instances -An instance represents a LED hardware instance, it runs within a own scope along with it's own plugin settings, led layout, calibration. First, you need to get informations about instances. The first shot from [serverinfo Instance](/en/json/serverinfo#instance). -``` json +An instance represents an LED hardware instance. It runs within its own scope with it's +own plugin settings, led layout and calibration. Before selecting you can instance, you +should first get information about the available instances from [serverinfo](/en/json/serverinfo#instance). + +```json // Command to start instance 1 { "command" : "instance", @@ -306,36 +316,43 @@ An instance represents a LED hardware instance, it runs within a own scope along "instance" : 1 } ``` -In both cases you get a success response, it doesn't return any error responses. ### API Instance handling -On connection to the API you will be connected to instance `0`, that means you can control just one instance at the same time within a connection. It's possible to switch to another instance with the following command. +On connection to the API you will be connected to instance `0` by default. You can +control only one instance at the same time within a single connection, and +[subscriptions](/en/json/subscribe#instance-updates) are in the context of the selected instance. -``` json -// We switch to instance 1 +It's possible to switch to another instance with the following command: + +```json +// Switch to instance 1 { "command" : "instance", "subcommand" : "switchTo", "instance" : 1 } ``` -Will return a success response, or a error response when the instance isn't available +This will return a success response or an error if the instance isn't available. ::: warning -It's possible that an instance will stop while you are connected. In this case you will be automatically reseted to instance `0`. So keep watching the instance data as subscription: [Instance updates](/en/json/subscribe#instance-updates) +It's possible that an instance will stop while you are connected to it. In this case +connections on that instance will automatically be reset to instance `0`. Keep watching +the instance data via subscription if you need to handle this case. +See: [Instance updates](/en/json/subscribe#instance-updates). ::: ### Live Image Stream -You can request a live image stream (when the current source priority can deliver). So it might be possible that there is no response or it stops and starts in between. -``` json +You can request a live image stream (if the current source priority supports it, +otherwise here may be no response). +```json { "command":"ledcolors", "subcommand":"imagestream-start" } ``` You will receive "ledcolors-imagestream-update" messages with a base64 encoded image. -Stop the stream by sending -``` json +Stop the stream by sending: +```json { "command":"ledcolors", "subcommand":"imagestream-stop" @@ -347,16 +364,17 @@ This feature is not available for HTTP/S JSON-RPC ### Live Led Color Stream -You can request a live led color stream with current color values in RGB for each single led. Update rate is 125ms. -``` json +You can request a live led color stream with current color values in RGB for each single +led. The update rate is 125ms. +```json { "command":"ledcolors", "subcommand":"ledstream-start" } ``` You will receive "ledcolors-ledstream-update" messages with an array of all led colors. -Stop the stream by sending -``` json +Stop the stream by sending: +```json { "command":"ledcolors", "subcommand":"ledstream-stop" @@ -365,25 +383,3 @@ Stop the stream by sending ::: danger HTTP/S This feature is not available for HTTP/S JSON-RPC ::: - -### Plugins -::: danger NOT IMPLEMENTED -THIS IS NOT IMPLEMENTED -::: -You can start and stop plugins. First, you need to get informations about plugins. The first short from [serverinfo plugins](/en/json/serverinfo#plugins). -You need now the plugin id. Example: `service.kodi`. -``` json -// Command to start a plugin -{ - "command":"plugin", - "subcommand":"start", - "id":"service.kodi" -} -// Command to stop a plugin -{ - "command":"plugin", - "subcommand":"stop", - "id":"service.kodi" -} -``` -You will get a response of your action. `plugin-start` or `plugin-stop` with success true/false. diff --git a/docs/docs/en/json/README.md b/docs/docs/en/json/README.md index 5af982bf..bf716b7a 100644 --- a/docs/docs/en/json/README.md +++ b/docs/docs/en/json/README.md @@ -1,21 +1,28 @@ # JSON RPC Introduction -The JSON-RPC provide lot's of possibilities to interact with Hyperion. You could get information about Hyperion and it's states and trigger certain actions based on these informations or just out of the wild. +The JSON-RPC interfaces provides many ways to interact with Hyperion. You can retrieve +information about your server, your instances and take actions (such as setting a +priority input). [[toc]] ## What is JSON? -JSON is a standardized message format [JSON.org](http://www.json.org/) and is supported by lot's of programming languages, which is perfect to transmit and process informations. While it's not the smartest in traffic size, it can be read by humans. +JSON is a standardized message format (see [JSON.org](http://www.json.org/)) and is supported +by most programming languages. It is human readable which makes for easier debugging. ### Sending JSON -Hyperion requires a special formatted JSON message to process it. `command` is always required, while `tan` is optional. The tan is a integer you could freely define. It is part of the response so you could easy filter for it in case you need it (Might be very rarely). -``` json +Hyperion requires a specially formatted JSON message. A `command` argument is always +required. A `tan` argument is optional. This is an integer you can freely choose -- it is +part of the response you will receive to allow you to filter the response from other server +messages (this functionality is likely necessary for advanced usecases only). + +```json { "command" : "YourCommand", "tan" : 1 } ``` -Depending on command, there might be an additional subcommand required -``` json +Depending on the command, there might be an additional subcommand required: +```json { "command" : "YourCommand", "subcommand" : "YourSubCommand", @@ -24,8 +31,8 @@ Depending on command, there might be an additional subcommand required ``` ### Response -Most messages you send, trigger a response of the following format: -``` json +Most messages you send will trigger a response of the following format: +```json { "command" : "YourCommand", "info":{ ...DATA... }, @@ -33,27 +40,28 @@ Most messages you send, trigger a response of the following format: "tan" : 1 } ``` -- command: The command you requested. -- tan: The tan you provided (If not, defaults to 1). -- success: true or false. In case of false you get a proper answer what's wrong within an **error** property. -- info: The data you requested (if so) +- **command**: The command you requested. +- **tan**: The tan you provided (If not, it will default to 0 in the response). +- **success**: true or false. If false, an **error** argument will contain details of the issue. +- **info**: The data you requested (if any). ## Connect -Supported are currently TCP Socket ("Json Server"), WebSocket and HTTP/S. +Hyperion currently supports multiple connection mechanisms: TCP Socket ("Json Server"), WebSocket and HTTP/S. ::: tip -You can discover Hyperion servers! Checkout [Detect Hyperion](/en/api/detect.md) +You can automatically discover Hyperion servers! See [Detect Hyperion](/en/api/detect.md) ::: ### TCP Socket -Is a "raw" connection, you send and receive json from the json-rpc (default port: 19444). Also known as "Json Server". +This is a "raw" connection, you can send and receive line-separated json from the server +(default port: 19444). This is also known as the "Json Server". ### WebSocket -Part of the webserver (default port: 8090). You send and receive json from the json-rpc. -Supported is also WSS at port 8092. We support just TEXT mode. Read more at [Websocket](https://en.wikipedia.org/wiki/WebSocket|). +This is part of the Hyperion webserver (default port: 8090). You send and receive json +commands. WSS is also supported on port 8092. Only TEXT mode is supported. Read more +about websockets at [Websocket](https://en.wikipedia.org/wiki/WebSocket|). ### HTTP/S Json -HTTP requests can be also send to the webserver (default port: 8090, for HTTPS: 8092). -Send a HTTP/S POST request along with a properly formatted json message at the body to the (example) url: `http://IpOfDevice:WebserverPort/json-rpc` +HTTP requests can also be sent to the webserver (default port: 8090, for HTTPS: 8092). Send a HTTP/S POST request along with a properly formatted json message in the body to the (example) url: `http://IpOfDevice:WebserverPort/json-rpc` Example picture with a [Firefox](https://addons.mozilla.org/de/firefox/addon/restclient/)/[Chrome](https://chrome.google.com/webstore/detail/advanced-rest-client/hgmloofddffdnphfgcellkdfbfbjeloo/related) Addon to send HTTP JSON messages @@ -61,7 +69,7 @@ Example picture with a [Firefox](https://addons.mozilla.org/de/firefox/addon/res ::: tip -If you get a "No Authorization" message back, you need to create an [Authorization Token](/en/json/Authorization.md#token-system) +If you get a "No Authorization" response, you need to create an [Authorization Token](/en/json/Authorization.md#token-system) ::: ::: warning HTTP/S Restrictions @@ -72,10 +80,10 @@ Please note that the HTTP JSON-RPC lacks of the following functions due to techn ## API ### Server Info -All kind of infos from the Server: [Server Info](/en/json/ServerInfo.md) +A large variety of data is available from the server: [Server Info](/en/json/ServerInfo.md) ### Control -Control Hyperion: [Control](/en/json/Control.md) +Control your Hyperion server: [Control](/en/json/Control.md) ### Authorization -All around the Authorization system: [Authorization](/en/json/Authorization.md) +Authorization mechanisms: [Authorization](/en/json/Authorization.md) ### Subscribe Data subscriptions: [Subscribe](/en/json/Subscribe.md) diff --git a/docs/docs/en/json/ServerInfo.md b/docs/docs/en/json/ServerInfo.md index a1e04762..2e9448f1 100644 --- a/docs/docs/en/json/ServerInfo.md +++ b/docs/docs/en/json/ServerInfo.md @@ -1,25 +1,29 @@ -# Sever Information -It provides you informations about Hyperion that are required to perform certain actions. +# Server Information +This is the primary read mechanism of the Hyperion server. This single command provides data about the live state of Hyperion, broken down into a number +of different parts (described below). [[toc]] -Grab it by sending: -``` json +You can request a `serverinfo` response by sending the following command: +```json { "command":"serverinfo", "tan":1 } ``` -## Parts +## Parts of a serverinfo response ### Components -List of Hyperion components and their current status "enabled" (on/off). You can enable or disable them during runtime. The "ALL" component reflect Hyperion as a whole and as long "ALL" is false (off) you can't enable another component. [See control components](/en/json/control#control-components) +List of Hyperion components and their current status "enabled" (on/off). You can enable +or disable them during runtime . The "ALL" component reflect Hyperion as a whole -- if +"ALL" is false (off) you can't enable any other component. [See control +components](/en/json/control#control-components) ::: tip Subscribe You can subscribe to future data updates. Read more about [Component updates](/en/json/subscribe#component-updates) ::: -``` json +```json { "components":[ { @@ -59,11 +63,12 @@ You can subscribe to future data updates. Read more about [Component updates](/e ``` ### Adjustments -Adjustments reflects the value of the last performed (non persistent) color adjustment. Read more about [control Adjustments](/en/json/control#adjustments) +Adjustments reflect the value of the last performed (non-persistent) color adjustment +(e.g. brightness). Read more about [control Adjustments](/en/json/control#adjustments) ::: tip Subscribe You can subscribe to future data updates. Read more about [Adjustment updates](/en/json/subscribe#adjustment-updates) ::: -``` json +```json { "adjustment":[ { @@ -87,12 +92,16 @@ You can subscribe to future data updates. Read more about [Adjustment updates](/ ``` ### Effect list -An array of effects where each object is one effect, usually you just use the `name` to create a list for the user. You could filter between user created effects and provided effects by checking the effect `file` string. In case it begins with `:` it's a provided effect. If the path begins with `/`, it's a user created effect. Could be used to list the user effects earlier than the provided effects and to keep the overview. +An array of effects where each object is one named effect. You can filter between user +created effects and system provided effects by checking the effect `file` string -- if +it begins with `:` it's a system provided effect, whereas if the path begins with `/`, +it's a user created effect. + See also [set Effect](/en/json/control#set-effect) ::: tip Subscribe You can subscribe to future data updates. Read more about [Effect updates](/en/json/subscribe#effects-updates) ::: -``` json +```json { "effects":[ { @@ -132,38 +141,43 @@ You can subscribe to future data updates. Read more about [Effect updates](/en/j ``` ### LED mapping -Active mode of led area mapping. [See control LED mapping](/en/json/control#led-mapping) +Active mode of the led area mapping. [See control LED mapping](/en/json/control#led-mapping) ::: tip Subscribe You can subscribe to future data updates. Read more about [LED mapping updates](/en/json/subscribe#led-mapping-updates) ::: -``` json +```json "imageToLedMappingType":"multicolor_mean" ``` ### Video mode -The current video mode of grabbers Can be switched to 3DHSBS, 3DVSBS. [See control video mode](/en/json/control#video-mode) +The current video mode of grabbers. Can be switched to 3DHSBS, 3DVSBS. [See control video mode](/en/json/control#video-mode) ::: tip Subscribe You can subscribe to future data updates. Read more about [Video mode updates](/en/json/subscribe#videomode-updates) ::: -``` json +```json "videomode" : "2D" ``` ### Priorities Overview of the registered/active sources. Each object is a source. - * active: If "true" it is selectable for manual source selection. [See also source selection](/en/json/control#source-selection) - * visible: If "true" this source is displayed and will be pushed to the led device. The `visible:true`-source is always the first entry! - * componentId: A string belongs to a specific component. [See available components](/en/json/control#components-ids-explained) - * origin: The external setter of this source "NameOfRemote@IP". If not given it's `System` (from Hyperion). - * owner: Contains additional information related to the componentId. If it's an effect, the effect name is shown here. If it's USB capture it shows the capture device. If it's platform capture you get the name of it (While we use different capture implementations on different hardware (dispmanx/x11/amlogic/...)). - * priority: The priority of this source. - * value: Just available if source is a color AND color data is available (active = false has usually no data). Outputs the color in RGB and HSL. - * duration_ms: Actual duration in ms until this priority is deleted. Just available if source is color or effect AND a specific duration higher than `0` is set (because 0 is endless). + * **active**: If "true" it is selectable for manual source selection. [See also source selection](/en/json/control#source-selection) + * **visible**: If "true" this source is displayed and pushed to the led device. The `visible:true`-source is always the first entry! + * **componentId**: A key belonging to a specific component that indicates the kind of input. [See available components](/en/json/control#components-ids-explained) + * **origin**: A named external setter of this source for reference purposes. If not given it's `System` (from Hyperion). + * **owner**: Contains additional information related to the componentId. If it's an effect, + the effect name is shown here. If it's USB capture, the capture device is shown. If + it's platform capture, you get the name of the platform capture implementation (e.g. dispmanx/x11/amlogic/...). + * **priority**: The priority of this source, an integer between 0 and 255. + * **value**: If the source is a color AND color data is available (if active is false + there's usually no datta),hen this will be the color in RGB and HSL. + * **duration_ms**: Actual duration in ms until this priority is automatically deleted. + This is shown if source is color or effect AND a specific duration higher than + `0` is set (0 means indefinite). ::: tip Subscribe You can subscribe to future data updates. Read more about [Priority updates](/en/json/subscribe#priority-updates) ::: -``` json +```json "priorities":[ { "active":true, @@ -196,17 +210,23 @@ You can subscribe to future data updates. Read more about [Priority updates](/en ] ``` -### Priorities selection Auto/Manual -If "priorities_autoselect" is "true" the visible source is determined by priority. The lowest number is selected. If someone request to set a source manual, the value switches to "false". -In case the manual selected source is cleared/stops/duration runs out OR the user requests the auto selection, it switches back to "true". [See also source selection](/en/json/control#source-selection). -This value will be updated together with the priority update. +### Priorities selection: Auto/Manual +If `priorities_autoselect` is "true" the visible source is determined by priority. The +lowest number is automatically selected. If a caller requests to set a source manually, +then `priorities_autoselect` switches to `false`. + +If the manually selected source is cleared/stops/completes-duration OR the user requests +the auto selection, `priorities_autoselect` switches back to `true`. This value is +atomically updated with the priority updates (shown above). +[See also source selection](/en/json/control#source-selection). ### Instance -Information about available instances and their state. Each instance represents a LED device. How to control them: [Control Instance](/en/json/control#control-instances). +Information about available instances and their state. Each instance represents a LED +device. Instances can be controlled, see: [Control Instance](/en/json/control#control-instances). ::: tip Subscribe You can subscribe to future data updates. Read more about [Instance Updates](/en/json/subscribe#instance-updates) ::: -``` json +```json "instance":[ { "instance": 0, @@ -218,7 +238,7 @@ You can subscribe to future data updates. Read more about [Instance Updates](/en "running" : false, "friendly_name" : "PhilipsHue LED Hardware instance" } - [ + ] ``` ### LEDs @@ -226,7 +246,7 @@ Information about led layout (image mapping positions) and led count. ::: tip Subscribe You can subscribe to future data updates. Read more about [LEDs Updates](/en/json/subscribe#leds-updates) ::: -``` json +```json { "leds":[ { @@ -247,16 +267,18 @@ You can subscribe to future data updates. Read more about [LEDs Updates](/en/jso ``` ### System & Hyperion -It's possible to gather some basic software informations Hyperion runs on. This information is static and won't change during runtime. -``` json +It's possible to retrieve basic system information about the Hyperion server and the +host it runs on. This information is static and won't change during runtime. +```json { "command" : "sysinfo", "tan" : 1 } ``` You can use the "version" (Hyperion version) string to check application compatibility. We use [Semantic Versioning 2.0.0](https://semver.org/). -If you need a specific id to re-detect known servers you can use the "id" field which provides a unique id. -``` json +If you need a specific id to re-detect known servers you can use the `id` field which +provides a unique id and will not change for a given server. +```json { "hyperion":{ "build":"webd (brindosch-38f97dc/814977d-1489698970)", @@ -278,18 +300,13 @@ If you need a specific id to re-detect known servers you can use the "id" field } ``` -Hyperion will answer your request. Below just the "info"-part of the response, splitted for better overview. -::: tip Subscribe -You can subscribe to future data updates. Read more about [Subscriptions](/en/json/subscribe) -::: - ### Sessions - "sessions" shows all Hyperion servers at the current network found via Zeroconf/avahi/bonjour. See also [detect Hyperion](/en/api/detect.md) + `sessions` shows all Hyperion servers on the current network found via Zeroconf/avahi/bonjour. See also [detect Hyperion](/en/api/detect.md) ::: tip Subscribe You can subscribe to future data updates. Read more about [Session updates](/en/json/subscribe#session-updates) ::: -``` json +```json { "sessions":[ { @@ -303,28 +320,3 @@ You can subscribe to future data updates. Read more about [Session updates](/en/ ] } ``` - -### Plugins -::: danger NOT IMPLEMENTED -THIS IS NOT IMPLEMENTED -::: -Information about installed plugins. How to control them: [Control Plugins](/en/json/control#plugins) -::: tip Subscribe -You can subscribe to future data updates. Read more about [Plugin updates](/en/json/subscribe#plugin-updates) -::: -``` json - "plugins": { - "service.kodi": { - "description": "Connect to a Kodi instance to get player state", - "name": "Kodi Connector", - "version": "0.0.2", - "running": true - }, - "service.wol": { - "description": "Send WOL packages to somewhere", - "name": "WOL Packages", - "version": "0.0.1", - "running": false - } - } -``` \ No newline at end of file diff --git a/docs/docs/en/json/Subscribe.md b/docs/docs/en/json/Subscribe.md index e9ce0d4d..ed458543 100644 --- a/docs/docs/en/json/Subscribe.md +++ b/docs/docs/en/json/Subscribe.md @@ -1,10 +1,13 @@ # Subscription -During initial serverinfo request you can subscribe to specific data updates or all of them at once, these updates will be pushed to you whenever a server side data change occur. +During a `serverinfo` request the caller can optionally subscribe to updates -- either +to specific [serverinfo parts](/en/json/ServerInfo.html#parts) or all available data. +These updates will be pushed whenever a server-side data change occurs, without the need +for the caller to poll. [[toc]] -To subscribe for specific updates modify serverinfo command to -``` json +To subscribe to specific updates, you can modify the serverinfo command to: +```json { "command":"serverinfo", "subscribe":[ @@ -16,7 +19,7 @@ To subscribe for specific updates modify serverinfo command to } ``` To subscribe for all available updates modify the severinfo command to -``` json +```json { "command":"serverinfo", "subscribe":["all"], @@ -24,8 +27,10 @@ To subscribe for all available updates modify the severinfo command to } ``` ### Base response layout -All responses will have a `-update` suffix in their command property, it's the same command you subscribed to. The new data is in the `data` property. A `tan` and `success` property does not exist. -``` json +All pushed subscription updates will have an `-update` suffix added to the relevant key +from the [serverinfo part in question](/en/json/ServerInfo.html#parts). The new data +will be in the `data` property. There is no `tan` nor `success` argument provided. +```json { "command":"XYZ-update", "data":{ @@ -34,8 +39,11 @@ All responses will have a `-update` suffix in their command property, it's the s } ``` ### Component updates -You can subscribe to updates of components. These updates are meant to update the `components` section of your initial serverinfo. Modify serverinfo command to -``` json +The caller can subscribe to component updates. These updates are meant to update the +`components` section of the caller's initial serverinfo. Relevant `serverinfo` +subscription command: + +```json { "command":"serverinfo", "subscribe":[ @@ -44,8 +52,8 @@ You can subscribe to updates of components. These updates are meant to update th "tan":1 } ``` -You will get incremental updates, here an example response -``` json +After this, the caller will receive incremental updates. An example: +```json { "command":"components-update", "data":{ @@ -56,8 +64,10 @@ You will get incremental updates, here an example response ``` ### Session updates -You can subscribe to session updates (Found with Bonjour/Zeroconf/Ahavi). These updates are meant to update the `sessions` section of your initial serverinfo. Modify serverinfo command to -``` json +The caller can subscribe to session updates (Hyperion instances found with +Bonjour/Zeroconf/Ahavi). These updates are meant to update the `sessions` section of +the caller's initial serverinfo. Relevant `serverinfo` subscription command: +```json { "command":"serverinfo", "subscribe":[ @@ -66,9 +76,9 @@ You can subscribe to session updates (Found with Bonjour/Zeroconf/Ahavi). These "tan":1 } ``` -These updates aren't incremental, so they contain always all found entries. -Example response with 2 HTTP server sessions (_hyperiond-http._tcp) -``` json +These updates aren't incremental -- they contain all found entries on each update. +Example response with 2 HTTP server sessions (`_hyperiond-http._tcp`): +```json { "command":"sessions-update", "data":[ @@ -92,16 +102,19 @@ Example response with 2 HTTP server sessions (_hyperiond-http._tcp) } ``` ### Priority updates -You can subscribe to priority updates. These updates are meant to update the `priorities` and `priorities_autoselect` section of your initial serverinfo. Modify serverinfo command to -``` json +The caller can subscribe to priority updates. These updates are meant to update the +`priorities` and `priorities_autoselect` section of the caller's initial `serverinfo`. +Relevant `serverinfo` subscription command: +```json { "command":"serverinfo", "subscribe":["priorities-update"], "tan":1 } ``` -You get the complete data, please note that if a color or effect is running with a timeout > -1 you will receive at least within a 1 second interval new data. Here an example update: -``` json +Caller will get the complete data. Please note that if a color or effect is running with +a timeout > -1, the caller will receive new data each second. An example update: +```json { "command":"priorities-update", "data":{ @@ -140,15 +153,17 @@ You get the complete data, please note that if a color or effect is running with } ``` ### LED Mapping updates -You can subscribe to LED mapping type updates. These updates are meant to update the `imageToLedMappingType` section of your initial serverinfo. Modify serverinfo command to -``` json +The caller can subscribe to LED mapping type updates. These updates are meant to update +the `imageToLedMappingType` section of the caller's initial `serverinfo`. +Relevant `serverinfo` subscription command: +```json { "command":"serverinfo", "subscribe":["imageToLedMapping-update"], "tan":1} ``` -Here an example update: -``` json +An example update: +```json { "command":"imageToLedMapping-update", "data":{ @@ -157,16 +172,18 @@ Here an example update: } ``` ### Adjustment updates -You can subscribe to adjustment updates. These updates are meant to update the `adjustment` section of your initial serverinfo. Modify serverinfo command to -``` json +The caller can subscribe to adjustment updates. These updates are meant to update the +`adjustment` section of the caller's initial `serverinfo`. Relevant `serverinfo` +subscription command: +```json { "command":"serverinfo", "subscribe":["adjustment-update"], "tan":1 } ``` -Here an example update: -``` json +An example update: +```json { "command":"adjustment-update", "data":[{ @@ -189,16 +206,18 @@ Here an example update: } ``` ### VideoMode updates -You can subscribe to videomode updates. These updates are meant to update the `videomode` section of your initial serverinfo. Modify serverinfo command to -``` json +The caller can subscribe to videomode updates. These updates are meant to update the +`videomode` section of the cakker's initial `serverinfo`. Relevant `serverinfo` +subscription command: +```json { "command":"serverinfo", "subscribe":["videomode-update"], "tan":1 } ``` -Here an example update: -``` json +An example update: +```json { "command":"videomode-update", "data":{ @@ -207,16 +226,18 @@ Here an example update: } ``` ### Effects updates -You can subscribe to effect list updates. These updates are meant to update the `effects` section of your initial serverinfo. Modify serverinfo command to -``` json +The caller can subscribe to effect list updates. These updates are meant to update the +`effects` section of the caller's initial `serverinfo`. Relevant `serverinfo` +subscription command: +```json { "command":"serverinfo", "subscribe":["effects-update"], "tan":1 } ``` -Here an example update: -``` json +An example update: +```json { "command":"effects-update", "data":{ @@ -226,16 +247,19 @@ Here an example update: ``` ### Instance updates -You can subscribe to instance updates. These updates are meant to update the `instance` section of your initial serverinfo. Modify serverinfo command to -``` json +The caller can subscribe to instance updates. These updates are meant to update the +`instance` section of the caller's initial serverinfo. Relevant `serverinfo` +subscription command: +```json { "command":"serverinfo", "subscribe":["instance-update"], "tan":1 } ``` -Here an example update, you will also get the whole section: -``` json +An example update. This is not incremental -- the caller will get the full set of +instances: +```json { "command":"instance-update", "data":[ @@ -253,16 +277,17 @@ Here an example update, you will also get the whole section: } ``` ### LEDs updates -You can subscribe to leds updates. These updates are meant to update the `leds` section of your initial serverinfo. Modify serverinfo command to -``` json +The caller can subscribe to leds updates. These updates are meant to update the `leds` +section of the caller's initial `serverinfo`. Relevant `serverinfo` subscription command: +```json { "command":"serverinfo", "subscribe":["leds-update"], "tan":1 } ``` -Here an example update, you will also get the whole section: -``` json +An example update. This is not incremental -- the caller willg et the full set of leds: +```json { "command":"leds-update", "data": { @@ -278,54 +303,3 @@ Here an example update, you will also get the whole section: } } ``` - -### Plugin updates -::: danger NOT IMPLEMENTED -THIS IS NOT IMPLEMENTED -::: -You can subscribe to plugin updates. These updates are meant to update the `plugins` section of your initial serverinfo. Modify serverinfo command to -``` json -{ - "command":"serverinfo", - "subscribe":[ - "plugins-update" - ], - "tan":1 -} -``` -Response on new plugin has been added (or plugin has been updated to a newer version) -``` json -{ - "command":"plugins-update", - "data":{ - "IdOfPlugin":{ - "name":"The name of plugin", - "description":"The description of plugin", - "version":"TheVersion", - "running":false - } - } - } -``` -Response on plugin removed, the data object contains a `removed` property / the plugin id object is empty -``` json -{ - "command":"plugins-update", - "data":{ - "ThePluginId":{ - "removed":true - } - } -} -``` -Response on plugin running state change -``` json -{ - "command":"plugins-update", - "data":{ - "ThePluginId":{ - "running":true - } - } -} -``` \ No newline at end of file diff --git a/docs/docs/en/user/advanced/Advanced.md b/docs/docs/en/user/advanced/Advanced.md index d44a7678..c8ce7119 100644 --- a/docs/docs/en/user/advanced/Advanced.md +++ b/docs/docs/en/user/advanced/Advanced.md @@ -93,6 +93,7 @@ Explain the differences between the available modes for blackbar detection. * **Default:** 3 scanlines in each direction (X Y) - fastest detection * **Classic:** The original implementation - lower cpu time (legacy for RPi 1) just scan the top one third of the picture which leads to a slow detection and trouble with TV channel logo. * **OSD:** Based on the default mode - not that effective but prevents border switching which may caused of OSD overlays (program infos and volume bar). + * **Letterbox:** Based on the default mode - only considers blackbars at the top and bottom of the picture, ignoring the sides. ## Gamma Curve diff --git a/effects/lights.gif b/effects/lights.gif index b1d83a7e..3f5f1739 100644 Binary files a/effects/lights.gif and b/effects/lights.gif differ diff --git a/include/api/API.h b/include/api/API.h index aa134ccc..04a65676 100644 --- a/include/api/API.h +++ b/include/api/API.h @@ -231,7 +231,7 @@ protected: /// @brief Save settings object. Requires ADMIN ACCESS /// @param data The data object /// - void saveSettings(const QJsonObject &data); + bool saveSettings(const QJsonObject &data); /// /// @brief Test if we are authorized to use the interface diff --git a/include/blackborder/BlackBorderDetector.h b/include/blackborder/BlackBorderDetector.h index 436800db..42da8dc2 100644 --- a/include/blackborder/BlackBorderDetector.h +++ b/include/blackborder/BlackBorderDetector.h @@ -11,7 +11,7 @@ namespace hyperion /// struct BlackBorder { - /// Falg indicating if the border is unknown + /// Flag indicating if the border is unknown bool unknown; /// The size of the detected horizontal border @@ -48,7 +48,7 @@ namespace hyperion public: /// /// Constructs a black-border detector - /// @param[in] blackborderThreshold The threshold which the blackborder detector should use + /// @param[in] threshold The threshold which the black-border detector should use /// BlackBorderDetector(double threshold); @@ -67,9 +67,9 @@ namespace hyperion template BlackBorder process(const Image & image) const { - // test center and 33%, 66% of width/heigth + // test centre and 33%, 66% of width/height // 33 and 66 will check left and top - // center will check right and bottom sids + // centre will check right and bottom sides int width = image.width(); int height = image.height(); int width33percent = width / 3; @@ -234,11 +234,54 @@ namespace hyperion } + /// + /// letterbox detection mode (5lines top-bottom only detection) + template + BlackBorder process_letterbox(const Image & image) const + { + // test center and 25%, 75% of width + // 25 and 75 will check both top and bottom + // center will only check top (minimise false detection of captions) + int width = image.width(); + int height = image.height(); + int width25percent = width / 4; + int height33percent = height / 3; + int width75percent = width25percent * 3; + int xCenter = width / 2; + + + int firstNonBlackYPixelIndex = -1; + + height--; // remove 1 pixel to get end pixel index + + // find first Y pixel of the image + for (int y = 0; y < height33percent; ++y) + { + if (!isBlack(image(xCenter, y)) + || !isBlack(image(width25percent, y)) + || !isBlack(image(width75percent, y)) + || !isBlack(image(width25percent, (height - y))) + || !isBlack(image(width75percent, (height - y)))) + { + firstNonBlackYPixelIndex = y; + break; + } + } + + // Construct result + BlackBorder detectedBorder; + detectedBorder.unknown = firstNonBlackYPixelIndex == -1; + detectedBorder.horizontalSize = firstNonBlackYPixelIndex; + detectedBorder.verticalSize = 0; + return detectedBorder; + } + + private: /// - /// Checks if a given color is considered black and therefor could be part of the border. + /// Checks if a given color is considered black and therefore could be part of the border. /// /// @param[in] color The color to check /// @@ -252,7 +295,7 @@ namespace hyperion } private: - /// Threshold for the blackborder detector [0 .. 255] + /// Threshold for the black-border detector [0 .. 255] const uint8_t _blackborderThreshold; }; diff --git a/include/blackborder/BlackBorderProcessor.h b/include/blackborder/BlackBorderProcessor.h index 752c9f9e..96db3dbf 100644 --- a/include/blackborder/BlackBorderProcessor.h +++ b/include/blackborder/BlackBorderProcessor.h @@ -44,14 +44,14 @@ namespace hyperion void setEnabled(bool enable); /// - /// Sets the _hardDisabled state, if True prevents the enable from COMP_BLACKBORDER state emit (mimiks wrong state to external!) - /// It's not possible to enable bb from this method, if the user requsted a disable! + /// Sets the _hardDisabled state, if True prevents the enable from COMP_BLACKBORDER state emit (mimics wrong state to external!) + /// It's not possible to enable black-border detection from this method, if the user requested a disable! /// @param disable The new state /// void setHardDisable(bool disable); /// - /// Processes the image. This performs detecion of black-border on the given image and + /// Processes the image. This performs detection of black-border on the given image and /// updates the current border accordingly. If the current border is updated the method call /// will return true else false /// @@ -64,10 +64,11 @@ namespace hyperion { // get the border for the single image BlackBorder imageBorder; + imageBorder.horizontalSize = 0; + imageBorder.verticalSize = 0; + if (!enabled()) { - imageBorder.horizontalSize = 0; - imageBorder.verticalSize = 0; imageBorder.unknown=true; _currentBorder = imageBorder; return true; @@ -79,6 +80,8 @@ namespace hyperion imageBorder = _detector->process_classic(image); } else if (_detectionMode == "osd") { imageBorder = _detector->process_osd(image); + } else if (_detectionMode == "letterbox") { + imageBorder = _detector->process_letterbox(image); } // add blur to the border if (imageBorder.horizontalSize > 0) @@ -96,7 +99,7 @@ namespace hyperion private slots: /// /// @brief Handle settings update from Hyperion Settingsmanager emit or this constructor - /// @param type settingyType from enum + /// @param type settingType from enum /// @param config configuration object /// void handleSettingsUpdate(settings::type type, const QJsonDocument& config); @@ -119,7 +122,7 @@ namespace hyperion /// bool updateBorder(const BlackBorder & newDetectedBorder); - /// flag for blackborder detector usage + /// flag for black-border detector usage bool _enabled; /// The number of unknown-borders detected before it becomes the current border @@ -131,13 +134,13 @@ namespace hyperion // The number of frames that are "ignored" before a new border gets set as _previousDetectedBorder unsigned _maxInconsistentCnt; - /// The number of pixels to increase a detected border for removing blury pixels + /// The number of pixels to increase a detected border for removing blurry pixels unsigned _blurRemoveCnt; /// The border detection mode QString _detectionMode; - /// The blackborder detector + /// The black-border detector BlackBorderDetector* _detector; /// The current detected border @@ -146,9 +149,9 @@ namespace hyperion /// The border detected in the previous frame BlackBorder _previousDetectedBorder; - /// The number of frame the previous detected border matched the incomming border + /// The number of frame the previous detected border matched the incoming border unsigned _consistentCnt; - /// The number of frame the previous detected border NOT matched the incomming border + /// The number of frame the previous detected border NOT matched the incoming border unsigned _inconsistentCnt; /// old threshold double _oldThreshold; diff --git a/include/bonjour/bonjourbrowserwrapper.h b/include/bonjour/bonjourbrowserwrapper.h index 527576d4..849cc981 100644 --- a/include/bonjour/bonjourbrowserwrapper.h +++ b/include/bonjour/bonjourbrowserwrapper.h @@ -19,7 +19,7 @@ private: /// @brief Browse for hyperion services in bonjour, constructed from HyperionDaemon /// Searching for hyperion http service by default /// - BonjourBrowserWrapper(QObject * parent = 0); + BonjourBrowserWrapper(QObject * parent = nullptr); public: @@ -30,36 +30,36 @@ public: /// /// @brief Get all available sessions /// - QMap getAllServices() { return _hyperionSessions; }; + QMap getAllServices() { return _hyperionSessions; } static BonjourBrowserWrapper* instance; - static BonjourBrowserWrapper* getInstance(){ return instance; }; + static BonjourBrowserWrapper *getInstance() { return instance; } signals: /// /// @brief Emits whenever a change happend /// - void browserChange(const QMap& bRegisters); + void browserChange( const QMap &bRegisters ); private: /// map of service names and browsers - QMap< QString, BonjourServiceBrowser* > _browsedServices; + QMap _browsedServices; /// Resolver - BonjourServiceResolver* _bonjourResolver; + BonjourServiceResolver *_bonjourResolver; // contains all current active service sessions - QMap _hyperionSessions; + QMap _hyperionSessions; - QString _bonjourCurrentServiceToResolve; + QString _bonjourCurrentServiceToResolve; /// timer to resolve changes - QTimer* _timerBonjourResolver; + QTimer *_timerBonjourResolver; private slots: /// /// @brief is called whenever a BonjourServiceBrowser emits change - void currentBonjourRecordsChanged(const QList &list); + void currentBonjourRecordsChanged( const QList &list ); /// @brief new record resolved - void bonjourRecordResolved(const QHostInfo &hostInfo, int port); + void bonjourRecordResolved( const QHostInfo &hostInfo, int port ); /// /// @brief timer slot which updates regularly entries diff --git a/include/commandline/Option.h b/include/commandline/Option.h index b84526fa..f5983111 100644 --- a/include/commandline/Option.h +++ b/include/commandline/Option.h @@ -33,6 +33,8 @@ public: QString value(Parser &parser) const; const char* getCString(Parser &parser) const; + virtual ~Option(); + protected: QString _error; }; diff --git a/include/commandline/Parser.h b/include/commandline/Parser.h index b315787e..5fd2ab29 100644 --- a/include/commandline/Parser.h +++ b/include/commandline/Parser.h @@ -1,7 +1,6 @@ #pragma once #include -#include #include "ColorOption.h" #include "ColorsOption.h" #include "DoubleOption.h" @@ -24,8 +23,8 @@ protected: /* No public inheritance because we need to modify a few methods */ QCommandLineParser _parser; - QStringList _getNames(const char shortOption, const QString longOption); - QString _getDescription(const QString description, const QString default_=QString()); + QStringList _getNames(const char shortOption, const QString& longOption); + QString _getDescription(const QString& description, const QString& default_=QString()); public: ~Parser() override; @@ -97,24 +96,24 @@ public: { if(description.size()) setApplicationDescription(description); - }; + } QCommandLineOption addHelpOption() { return _parser.addHelpOption(); - }; + } bool addOption(Option &option); bool addOption(Option *option); void addPositionalArgument(const QString &name, const QString &description, const QString &syntax = QString()) { _parser.addPositionalArgument(name, description, syntax); - }; + } QCommandLineOption addVersionOption() { return _parser.addVersionOption(); - }; + } QString applicationDescription() const { @@ -166,7 +165,7 @@ public: _parser.setSingleDashWordOptionMode(singleDashWordOptionMode); } - void showHelp(int exitCode = 0) + [[ noreturn ]] void showHelp(int exitCode = 0) { _parser.showHelp(exitCode); } diff --git a/include/db/AuthTable.h b/include/db/AuthTable.h index ae5918b6..c6495ea5 100644 --- a/include/db/AuthTable.h +++ b/include/db/AuthTable.h @@ -16,9 +16,10 @@ class AuthTable : public DBManager public: /// construct wrapper with auth table - AuthTable(const QString& rootPath = "", QObject* parent = nullptr) + AuthTable(const QString& rootPath = "", QObject* parent = nullptr, bool readonlyMode = false) : DBManager(parent) { + setReadonlyMode(readonlyMode); if(!rootPath.isEmpty()){ // Init Hyperion database usage setRootPath(rootPath); diff --git a/include/db/DBManager.h b/include/db/DBManager.h index 8ea6761f..4d36471d 100644 --- a/include/db/DBManager.h +++ b/include/db/DBManager.h @@ -119,6 +119,13 @@ public: /// bool deleteTable(const QString& table) const; + /// + /// @brief Sets a table in read-only mode. + /// Updates will not written to the table + /// @param[in] readOnly True read-only, false - read/write + /// + void setReadonlyMode(bool readOnly) { _readonlyMode = readOnly; }; + private: Logger* _log; @@ -127,6 +134,8 @@ private: /// table in database QString _table; + bool _readonlyMode; + /// addBindValue to query given by QVariantList void doAddBindValue(QSqlQuery& query, const QVariantList& variants) const; }; diff --git a/include/db/InstanceTable.h b/include/db/InstanceTable.h index aff7d73f..912ecbaf 100644 --- a/include/db/InstanceTable.h +++ b/include/db/InstanceTable.h @@ -14,9 +14,11 @@ class InstanceTable : public DBManager { public: - InstanceTable(const QString& rootPath, QObject* parent = nullptr) + InstanceTable(const QString& rootPath, QObject* parent = nullptr, bool readonlyMode = false) : DBManager(parent) { + + setReadonlyMode(readonlyMode); // Init Hyperion database usage setRootPath(rootPath); setDatabaseName("hyperion"); diff --git a/include/db/MetaTable.h b/include/db/MetaTable.h index acc27d03..e1861568 100644 --- a/include/db/MetaTable.h +++ b/include/db/MetaTable.h @@ -17,9 +17,11 @@ class MetaTable : public DBManager public: /// construct wrapper with plugins table and columns - MetaTable(QObject* parent = nullptr) + MetaTable(QObject* parent = nullptr, bool readonlyMode = false) : DBManager(parent) { + setReadonlyMode(readonlyMode); + setTable("meta"); createTable(QStringList()<<"uuid TEXT"<<"created_at TEXT"); }; diff --git a/include/effectengine/Effect.h b/include/effectengine/Effect.h index 229593b7..a1ecc990 100644 --- a/include/effectengine/Effect.h +++ b/include/effectengine/Effect.h @@ -1,11 +1,5 @@ #pragma once -// Python includes -// collide of qt slots macro -#undef slots -#include "Python.h" -#define slots - // Qt includes #include #include @@ -44,13 +38,13 @@ public: int getPriority() const { return _priority; } /// - /// @brief Set manual interuption to true, - /// Note: DO NOT USE QThread::interuption! + /// @brief Set manual interruption to true, + /// Note: DO NOT USE QThread::interruption! /// void requestInterruption() { _interupt = true; } /// - /// @brief Check if the interuption flag has been set + /// @brief Check if the interruption flag has been set /// @return The flag state /// bool isInterruptionRequested() { return _interupt; } @@ -88,7 +82,7 @@ private: QVector _colors; Logger *_log; - // Reflects whenever this effects should interupt (timeout or external request) + // Reflects whenever this effects should interrupt (timeout or external request) std::atomic _interupt {}; QSize _imageSize; diff --git a/include/effectengine/EffectFileHandler.h b/include/effectengine/EffectFileHandler.h index 428a8712..ea998e29 100644 --- a/include/effectengine/EffectFileHandler.h +++ b/include/effectengine/EffectFileHandler.h @@ -15,7 +15,7 @@ private: public: static EffectFileHandler* efhInstance; - static EffectFileHandler* getInstance() { return efhInstance; }; + static EffectFileHandler* getInstance() { return efhInstance; } /// /// @brief Get all available effects diff --git a/include/grabber/DirectXGrabber.h b/include/grabber/DirectXGrabber.h new file mode 100644 index 00000000..39f56d49 --- /dev/null +++ b/include/grabber/DirectXGrabber.h @@ -0,0 +1,79 @@ +#pragma once + +#include + +// DirectX 9 header +#include +#include + +// Hyperion-utils includes +#include +#include + +/// +/// @brief The DirectX9 capture implementation +/// +class DirectXGrabber : public Grabber +{ +public: + + DirectXGrabber(int cropLeft, int cropRight, int cropTop, int cropBottom, int pixelDecimation, int display); + + virtual ~DirectXGrabber(); + + /// + /// Captures a single snapshot of the display and writes the data to the given image. The + /// provided image should have the same dimensions as the configured values (_width and + /// _height) + /// + /// @param[out] image The snapped screenshot + /// + virtual int grabFrame(Image & image); + + /// + /// @brief Set a new video mode + /// + virtual void setVideoMode(VideoMode mode); + + /// + /// @brief Apply new width/height values, overwrite Grabber.h implementation + /// + virtual bool setWidthHeight(int width, int height) { return true; }; + + /// + /// @brief Apply new pixelDecimation + /// + virtual void setPixelDecimation(int pixelDecimation); + + /// + /// Set the crop values + /// @param cropLeft Left pixel crop + /// @param cropRight Right pixel crop + /// @param cropTop Top pixel crop + /// @param cropBottom Bottom pixel crop + /// + virtual void setCropping(unsigned cropLeft, unsigned cropRight, unsigned cropTop, unsigned cropBottom); + +private: + /// + /// @brief Setup a new capture display, will free the previous one + /// @return True on success, false if no display is found + /// + bool setupDisplay(); + + /// + /// @brief free the _screen pointer + /// + void freeResources(); + +private: + int _pixelDecimation; + unsigned _displayWidth; + unsigned _displayHeight; + RECT* _srcRect; + + IDirect3D9* _d3d9; + IDirect3DDevice9* _device; + IDirect3DSurface9* _surface; + IDirect3DSurface9* _surfaceDest; +}; diff --git a/include/grabber/DirectXWrapper.h b/include/grabber/DirectXWrapper.h new file mode 100644 index 00000000..d0bcb0b0 --- /dev/null +++ b/include/grabber/DirectXWrapper.h @@ -0,0 +1,36 @@ +#pragma once + +#include +#include + +class DirectXWrapper: public GrabberWrapper +{ +public: + /// + /// Constructs the DirectX grabber with a specified grab size and update rate. + /// + /// @param[in] cropLeft Remove from left [pixels] + /// @param[in] cropRight Remove from right [pixels] + /// @param[in] cropTop Remove from top [pixels] + /// @param[in] cropBottom Remove from bottom [pixels] + /// @param[in] pixelDecimation Decimation factor for image [pixels] + /// @param[in] display The display used[index] + /// @param[in] updateRate_Hz The image grab rate [Hz] + /// + DirectXWrapper(int cropLeft, int cropRight, int cropTop, int cropBottom, int pixelDecimation, int display, const unsigned updateRate_Hz); + + /// + /// Destructor of this DirectX grabber. Releases any claimed resources. + /// + virtual ~DirectXWrapper() {}; + +public slots: + /// + /// Performs a single frame grab and computes the led-colors + /// + virtual void action(); + +private: + /// The actual grabber + DirectXGrabber _grabber; +}; diff --git a/include/grabber/QtGrabber.h b/include/grabber/QtGrabber.h index b53c72a0..9c5903b6 100644 --- a/include/grabber/QtGrabber.h +++ b/include/grabber/QtGrabber.h @@ -37,7 +37,7 @@ public: /// /// @brief Apply new width/height values, overwrite Grabber.h implementation as qt doesn't use width/height, just pixelDecimation to calc dimensions /// - bool setWidthHeight(int width, int height) override { return true; }; + bool setWidthHeight(int width, int height) override { return true; } /// /// @brief Apply new pixelDecimation diff --git a/include/grabber/X11Grabber.h b/include/grabber/X11Grabber.h index 7b0b3bd0..abc347c3 100644 --- a/include/grabber/X11Grabber.h +++ b/include/grabber/X11Grabber.h @@ -45,7 +45,7 @@ public: /// /// @brief Apply new width/height values, overwrite Grabber.h implementation as X11 doesn't use width/height, just pixelDecimation to calc dimensions /// - bool setWidthHeight(int width, int height) override { return true; }; + bool setWidthHeight(int width, int height) override { return true; } /// /// @brief Apply new pixelDecimation diff --git a/include/grabber/XcbWrapper.h b/include/grabber/XcbWrapper.h index 307c7c4b..758269b3 100644 --- a/include/grabber/XcbWrapper.h +++ b/include/grabber/XcbWrapper.h @@ -15,7 +15,7 @@ public: ~XcbWrapper() override; public slots: - virtual void action(); + void action() override; private: XcbGrabber _grabber; diff --git a/include/hyperion/AuthManager.h b/include/hyperion/AuthManager.h index 1f22fb3a..3ee594c5 100644 --- a/include/hyperion/AuthManager.h +++ b/include/hyperion/AuthManager.h @@ -21,7 +21,7 @@ class AuthManager : public QObject private: friend class HyperionDaemon; /// constructor is private, can be called from HyperionDaemon - AuthManager(QObject *parent = 0); + AuthManager(QObject *parent = nullptr, bool readonlyMode = false); public: struct AuthDefinition diff --git a/include/hyperion/BGEffectHandler.h b/include/hyperion/BGEffectHandler.h index b5504212..08b67320 100644 --- a/include/hyperion/BGEffectHandler.h +++ b/include/hyperion/BGEffectHandler.h @@ -19,14 +19,14 @@ public: // listen for config changes connect(_hyperion, &Hyperion::settingsChanged, this, &BGEffectHandler::handleSettingsUpdate); - // init + // initialization handleSettingsUpdate(settings::BGEFFECT, _hyperion->getSetting(settings::BGEFFECT)); - }; + } private slots: /// /// @brief Handle settings update from Hyperion Settingsmanager emit or this constructor - /// @param type settingyType from enum + /// @param type settingType from enum /// @param config configuration object /// void handleSettingsUpdate(settings::type type, const QJsonDocument& config) @@ -36,7 +36,7 @@ private slots: const QJsonObject& BGEffectConfig = config.object(); #define BGCONFIG_ARRAY bgColorConfig.toArray() - // clear bg prioritiy + // clear background priority _hyperion->clear(254); // initial background effect/color if (BGEffectConfig["enable"].toBool(true)) @@ -48,24 +48,24 @@ private slots: { std::vector bg_color = { ColorRgb { - (uint8_t)BGCONFIG_ARRAY.at(0).toInt(0), - (uint8_t)BGCONFIG_ARRAY.at(1).toInt(0), - (uint8_t)BGCONFIG_ARRAY.at(2).toInt(0) + static_cast(BGCONFIG_ARRAY.at(0).toInt(0)), + static_cast(BGCONFIG_ARRAY.at(1).toInt(0)), + static_cast(BGCONFIG_ARRAY.at(2).toInt(0)) } }; _hyperion->setColor(254, bg_color); - Info(Logger::getInstance("HYPERION"),"Inital background color set (%d %d %d)",bg_color.at(0).red, bg_color.at(0).green, bg_color.at(0).blue); + Info(Logger::getInstance("HYPERION"),"Initial background color set (%d %d %d)",bg_color.at(0).red, bg_color.at(0).green, bg_color.at(0).blue); } else { int result = _hyperion->setEffect(bgEffectConfig, 254); - Info(Logger::getInstance("HYPERION"),"Inital background effect '%s' %s", QSTRING_CSTR(bgEffectConfig), ((result == 0) ? "started" : "failed")); + Info(Logger::getInstance("HYPERION"),"Initial background effect '%s' %s", QSTRING_CSTR(bgEffectConfig), ((result == 0) ? "started" : "failed")); } } #undef BGCONFIG_ARRAY } - }; + } private: /// Hyperion instance pointer diff --git a/include/hyperion/GrabberWrapper.h b/include/hyperion/GrabberWrapper.h index dd1880f0..00a50f6e 100644 --- a/include/hyperion/GrabberWrapper.h +++ b/include/hyperion/GrabberWrapper.h @@ -37,12 +37,12 @@ public: static GrabberWrapper* getInstance(){ return instance; } /// - /// Starts the grabber wich produces led values with the specified update rate + /// Starts the grabber which produces led values with the specified update rate /// virtual bool start(); /// - /// Starts maybe the grabber wich produces led values with the specified update rate + /// Starts maybe the grabber which produces led values with the specified update rate /// virtual void tryStart(); @@ -90,6 +90,12 @@ public: /// virtual QStringList getFramerates(const QString& devicePath) const; + /// + /// @brief Get active grabber name + /// @return Active grabber name + /// + virtual QString getActive() const; + static QStringList availableGrabbers(); public: diff --git a/include/hyperion/Hyperion.h b/include/hyperion/Hyperion.h index 90a76591..896b985a 100644 --- a/include/hyperion/Hyperion.h +++ b/include/hyperion/Hyperion.h @@ -98,6 +98,8 @@ public: /// QString getActiveDeviceType() const; + bool getReadOnlyMode() {return _readOnlyMode; }; + public slots: /// @@ -109,7 +111,7 @@ public slots: /// /// Returns the number of attached leds /// - unsigned getLedCount() const; + int getLedCount() const; /// /// @brief Register a new input by priority, the priority is not active (timeout -100 isn't muxer recognized) until you start to update the data with setInput() @@ -296,7 +298,7 @@ public slots: /// /// @return The information of the given, a not found priority will return lowest priority as fallback /// - InputInfo getPriorityInfo(int priority) const; + PriorityMuxer::InputInfo getPriorityInfo(int priority) const; /// ############# /// SETTINGSMANAGER @@ -484,7 +486,7 @@ private: /// @brief Constructs the Hyperion instance, just accessible for HyperionIManager /// @param instance The instance index /// - Hyperion(quint8 instance); + Hyperion(quint8 instance, bool readonlyMode = false); /// instance index const quint8 _instIndex; @@ -525,7 +527,7 @@ private: Logger * _log; /// count of hardware leds - unsigned _hwLedCount; + int _hwLedCount; QSize _ledGridSize; @@ -541,4 +543,6 @@ private: /// Boblight instance BoblightServer* _boblightServer; + + bool _readOnlyMode; }; diff --git a/include/hyperion/HyperionIManager.h b/include/hyperion/HyperionIManager.h index c7b1fca9..85faecab 100644 --- a/include/hyperion/HyperionIManager.h +++ b/include/hyperion/HyperionIManager.h @@ -169,7 +169,7 @@ private: /// @brief Construct the Manager /// @param The root path of all userdata /// - HyperionIManager(const QString& rootPath, QObject* parent = nullptr); + HyperionIManager(const QString& rootPath, QObject* parent = nullptr, bool readonlyMode = false); /// /// @brief Start all instances that are marked as enabled in db. Non blocking @@ -193,6 +193,9 @@ private: const QString _rootPath; QMap _runningInstances; QList _startQueue; + + bool _readonlyMode; + /// All pending requests QMap _pendingRequests; }; diff --git a/include/hyperion/ImageProcessor.h b/include/hyperion/ImageProcessor.h index 5c6f2e46..bc192668 100644 --- a/include/hyperion/ImageProcessor.h +++ b/include/hyperion/ImageProcessor.h @@ -19,9 +19,9 @@ class Hyperion; /// -/// The ImageProcessor translates an RGB-image to RGB-values for the leds. The processing is +/// The ImageProcessor translates an RGB-image to RGB-values for the LEDs. The processing is /// performed in two steps. First the average color per led-region is computed. Second a -/// color-tranform is applied based on a gamma-correction. +/// color-transform is applied based on a gamma-correction. /// class ImageProcessor : public QObject { @@ -39,7 +39,7 @@ public: ~ImageProcessor() override; /// - /// Specifies the width and height of 'incomming' images. This will resize the buffer-image to + /// Specifies the width and height of 'incoming' images. This will resize the buffer-image to /// match the given size. /// NB All earlier obtained references will be invalid. /// @@ -53,7 +53,7 @@ public: /// void setLedString(const LedString& ledString); - /// Returns starte of black border detector + /// Returns state of black border detector bool blackBorderDetectorEnabled() const; /// Returns the current _userMappingType, this may not be the current applied type! @@ -66,7 +66,7 @@ public: static QString mappingTypeToStr(int mappingType); /// - /// @brief Set the Hyperion::update() requestes led mapping type. This type is used in favour of type set with setLedMappingType. + /// @brief Set the Hyperion::update() request LED mapping type. This type is used in favour of type set with setLedMappingType. /// If you don't want to force a mapType set this to -1 (user choice will be set) /// @param mapType The new mapping type /// @@ -85,7 +85,7 @@ public slots: public: /// - /// Specifies the width and height of 'incomming' images. This will resize the buffer-image to + /// Specifies the width and height of 'incoming' images. This will resize the buffer-image to /// match the given size. /// NB All earlier obtained references will be invalid. /// @@ -117,7 +117,7 @@ public: // Check black border detection verifyBorder(image); - // Create a result vector and call the 'in place' functionl + // Create a result vector and call the 'in place' function switch (_mappingType) { case 1: colors = _imageToLeds->getUniLedColor(image); break; @@ -225,7 +225,7 @@ private: /// The processor for black border detection hyperion::BlackBorderProcessor * _borderProcessor; - /// The mapping of image-pixels to leds + /// The mapping of image-pixels to LEDs hyperion::ImageToLedsMap* _imageToLeds; /// Type of image 2 led mapping diff --git a/include/hyperion/ImageToLedsMap.h b/include/hyperion/ImageToLedsMap.h index ade94b8d..71cfc9de 100644 --- a/include/hyperion/ImageToLedsMap.h +++ b/include/hyperion/ImageToLedsMap.h @@ -175,7 +175,7 @@ namespace hyperion return ColorRgb::BLACK; } - // Accumulate the sum of each seperate color channel + // Accumulate the sum of each separate color channel uint_fast32_t cummRed = 0; uint_fast32_t cummGreen = 0; uint_fast32_t cummBlue = 0; @@ -209,7 +209,7 @@ namespace hyperion template ColorRgb calcMeanColor(const Image & image) const { - // Accumulate the sum of each seperate color channel + // Accumulate the sum of each separate color channel uint_fast32_t cummRed = 0; uint_fast32_t cummGreen = 0; uint_fast32_t cummBlue = 0; diff --git a/include/hyperion/LedString.h b/include/hyperion/LedString.h index afd41666..282b4429 100644 --- a/include/hyperion/LedString.h +++ b/include/hyperion/LedString.h @@ -107,16 +107,6 @@ struct Led class LedString { public: - /// - /// Constructs the LedString with no leds - /// - LedString(); - - /// - /// Destructor of this LedString - /// - ~LedString(); - /// /// Returns the led specifications /// diff --git a/include/hyperion/MultiColorAdjustment.h b/include/hyperion/MultiColorAdjustment.h index 3db3f47e..8ef15d25 100644 --- a/include/hyperion/MultiColorAdjustment.h +++ b/include/hyperion/MultiColorAdjustment.h @@ -11,22 +11,22 @@ /// /// The LedColorTransform is responsible for performing color transformation from 'raw' colors -/// received as input to colors mapped to match the color-properties of the leds. +/// received as input to colors mapped to match the color-properties of the LEDs. /// class MultiColorAdjustment { public: - MultiColorAdjustment(unsigned ledCnt); + MultiColorAdjustment(int ledCnt); ~MultiColorAdjustment(); /** * Adds a new ColorAdjustment to this MultiColorTransform * - * @param adjustment The new ColorAdjustment (ownership is transfered) + * @param adjustment The new ColorAdjustment (ownership is transferred) */ void addAdjustment(ColorAdjustment * adjustment); - void setAdjustmentForLed(const QString& id, unsigned startLed, unsigned endLed); + void setAdjustmentForLed(const QString& id, int startLed, int endLed); bool verifyAdjustments() const; diff --git a/include/hyperion/PriorityMuxer.h b/include/hyperion/PriorityMuxer.h index c1feedf5..7567ff60 100644 --- a/include/hyperion/PriorityMuxer.h +++ b/include/hyperion/PriorityMuxer.h @@ -58,10 +58,10 @@ public: const static int LOWEST_PRIORITY; /// - /// Constructs the PriorityMuxer for the given number of leds (used to switch to black when + /// Constructs the PriorityMuxer for the given number of LEDs (used to switch to black when /// there are no priority channels /// - /// @param ledCount The number of leds + /// @param ledCount The number of LEDs /// PriorityMuxer(int ledCount, QObject * parent); @@ -87,18 +87,18 @@ public: /// @brief Get the state of source auto selection /// @return True if enabled, else false /// - bool isSourceAutoSelectEnabled() const { return _sourceAutoSelectEnabled; }; + bool isSourceAutoSelectEnabled() const { return _sourceAutoSelectEnabled; } /// - /// @brief Overwrite current lowest piority with manual selection; On success disables aito selection + /// @brief Overwrite current lowest priority with manual selection; On success disables auto selection /// @param priority The /// @return True on success, false if priority not found /// - bool setPriority(uint8_t priority); + bool setPriority(int priority); /// - /// @brief Update all ledColos with min length of >= 1 to fit the new led length - /// @param[in] ledCount The count of leds + /// @brief Update all LED-Colors with min length of >= 1 to fit the new led length + /// @param[in] ledCount The count of LEDs /// void updateLedColorsLength(int ledCount); @@ -146,13 +146,13 @@ public: /// @param[in] priority The priority of the channel /// @param[in] component The component of the channel /// @param[in] origin Who set the channel (CustomString@IP) - /// @param[in] owner Speicifc owner string, might be empty + /// @param[in] owner Specific owner string, might be empty /// @param[in] smooth_cfg The smooth id to use /// void registerInput(int priority, hyperion::Components component, const QString& origin = "System", const QString& owner = "", unsigned smooth_cfg = SMOOTHING_MODE_DEFAULT); /// - /// @brief Update the current color of a priority (prev registered with registerInput()) + /// @brief Update the current color of a priority (previous registered with registerInput()) /// @param priority The priority to update /// @param ledColors The colors /// @param timeout_ms The new timeout (defaults to -1 endless) @@ -174,7 +174,7 @@ public: /// @param priority The priority /// @return True on success false if not found /// - bool setInputInactive(quint8 priority); + bool setInputInactive(int priority); /// /// Clears the specified priority channel and update _currentPriority on success @@ -182,7 +182,7 @@ public: /// @param[in] priority The priority of the channel to clear /// @return True if priority has been cleared else false (not found) /// - bool clearInput(uint8_t priority); + bool clearInput(int priority); /// /// Clears all priority channels @@ -190,7 +190,7 @@ public: void clearAll(bool forceClearAll=false); /// - /// @brief Queue a manual push where muxer doesn't recognize them (e.g. continous single color pushes) + /// @brief Queue a manual push where muxer doesn't recognize them (e.g. continuous single color pushes) /// void queuePush() { emit timeRunner(); } @@ -212,19 +212,6 @@ signals: /// void visibleComponentChanged(hyperion::Components comp); - /// - /// @brief Emits whenever a priority changes active state - /// @param priority The priority who changed the active state - /// @param state The new state, state true = active else false - /// - void activeStateChanged(quint8 priority, bool state); - - /// - /// @brief Emits whenever the auto selection state has been changed - /// @param state The new state of auto selection; True enabled else false - /// - void autoSelectChanged(bool state); - /// /// @brief Emits whenever something changes which influences the priorities listing /// Emits also in 1s interval when a COLOR or EFFECT is running with a timeout > -1 diff --git a/include/hyperion/SettingsManager.h b/include/hyperion/SettingsManager.h index d20e3afb..954c0eb5 100644 --- a/include/hyperion/SettingsManager.h +++ b/include/hyperion/SettingsManager.h @@ -3,14 +3,14 @@ #include #include -// qt incl +// qt includes #include class Hyperion; class SettingsTable; /// -/// @brief Manage the settings read write from/to config file, on settings changed will emit a signal to update components accordingly +/// @brief Manage the settings read write from/to configuration file, on settings changed will emit a signal to update components accordingly /// class SettingsManager : public QObject { @@ -21,10 +21,10 @@ public: /// @params instance Instance index of HyperionInstanceManager /// @params parent The parent hyperion instance /// - SettingsManager(quint8 instance, QObject* parent = nullptr); + SettingsManager(quint8 instance, QObject* parent = nullptr, bool readonlyMode = false); /// - /// @brief Save a complete json config + /// @brief Save a complete json configuration /// @param config The entire config object /// @param correct If true will correct json against schema before save /// @return True on success else false @@ -32,7 +32,7 @@ public: bool saveSettings(QJsonObject config, bool correct = false); /// - /// @brief get a single setting json from config + /// @brief get a single setting json from configuration /// @param type The settings::type from enum /// @return The requested json data as QJsonDocument /// @@ -42,11 +42,11 @@ public: /// @brief get the full settings object of this instance (with global settings) /// @return The requested json /// - const QJsonObject & getSettings() const { return _qconfig; }; + const QJsonObject & getSettings() const { return _qconfig; } signals: /// - /// @brief Emits whenever a config part changed. + /// @brief Emits whenever a configuration part changed. /// @param type The settings type from enum /// @param data The data as QJsonDocument /// @@ -54,7 +54,7 @@ signals: private: /// - /// @brief Add possile migrations steps for config here + /// @brief Add possible migrations steps for configuration here /// @param config The configuration object /// @return True when a migration has been triggered /// @@ -73,6 +73,8 @@ private: /// the schema static QJsonObject schemaJson; - /// the current config of this instance + /// the current configuration of this instance QJsonObject _qconfig; + + bool _readonlyMode; }; diff --git a/include/leddevice/LedDevice.h b/include/leddevice/LedDevice.h index 03c2ddd7..d2c1dbba 100644 --- a/include/leddevice/LedDevice.h +++ b/include/leddevice/LedDevice.h @@ -60,9 +60,9 @@ public: /// /// @brief Set the number of LEDs supported by the device. /// - /// @param[in] ledCount Number of device LEDs + /// @param[in] ledCount Number of device LEDs, 0 = unknown number /// - void setLedCount(unsigned int ledCount); + void setLedCount(int ledCount); /// /// @brief Set a device's latch time. @@ -87,9 +87,11 @@ public: /// @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 /// - virtual QJsonObject discover(); + virtual QJsonObject discover(const QJsonObject& params); /// /// @brief Discover first device of this type available (for configuration). @@ -116,7 +118,7 @@ public: /// /// @param[in] params Parameters to address device /// - virtual void identify(const QJsonObject& params) {} + virtual void identify(const QJsonObject& /*params*/) {} /// /// @brief Check, if device is properly initialised @@ -191,9 +193,9 @@ public slots: /// /// @brief Get the number of LEDs supported by the device. /// - /// @return Number of device's LEDs + /// @return Number of device's LEDs, 0 = unknown number /// - unsigned int getLedCount() const { return _ledCount; } + int getLedCount() const { return _ledCount; } /// /// @brief Get the current active LED-device type. @@ -348,7 +350,15 @@ 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 qint64 size, qint64 number = -1) const; + QString uint8_t_to_hex_string(const uint8_t * data, const int size, int number = -1) const; + + /// + /// @brief Converts a ByteArray to hex string. + /// + /// @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; /// Current device's type QString _activeDeviceType; @@ -368,17 +378,17 @@ protected: // Device configuration parameters - /// Number of hardware LEDs supported by device. - unsigned int _ledCount; - unsigned int _ledRGBCount; - unsigned int _ledRGBWCount; - /// Refresh interval in milliseconds int _refreshTimerInterval_ms; /// Time a device requires mandatorily between two writes (in milliseconds) int _latchTime_ms; + /// Number of hardware LEDs supported by device. + uint _ledCount; + uint _ledRGBCount; + uint _ledRGBWCount; + /// Does the device allow restoring the original state? bool _isRestoreOrigState; diff --git a/include/ssdp/SSDPServer.h b/include/ssdp/SSDPServer.h index 6bfe5d1c..e932d10e 100644 --- a/include/ssdp/SSDPServer.h +++ b/include/ssdp/SSDPServer.h @@ -66,65 +66,62 @@ public: /// @brief Overwrite description address /// @param addr new address /// - void setDescriptionAddress(const QString& addr) { _descAddress = addr; }; + void setDescriptionAddress( const QString &addr ) { _descAddress = addr; } /// /// @brief Set uuid /// @param uuid The uuid /// - void setUuid(const QString& uuid) { _uuid = uuid; }; + void setUuid( const QString &uuid ) { _uuid = uuid; } /// /// @brief set new flatbuffer server port /// - void setFlatBufPort(quint16 port) { _fbsPort = QString::number(port); }; + void setFlatBufPort( quint16 port ){_fbsPort = QString::number( port ); } /// /// @brief Get current flatbuffer server port /// - quint16 getFlatBufPort() const { return _fbsPort.toInt(); }; - + quint16 getFlatBufPort() const + { + return _fbsPort.toInt(); + } /// /// @brief set new protobuf server port /// - void setProtoBufPort(quint16 port) { _pbsPort = QString::number(port); }; + void setProtoBufPort( quint16 port ) { _pbsPort = QString::number( port ); } /// /// @brief Get current protobuf server port /// - quint16 getProtoBufPort() const { return _pbsPort.toInt(); }; - + quint16 getProtoBufPort() const { return _pbsPort.toInt(); } /// /// @brief set new json server port /// - void setJsonServerPort(quint16 port) { _jssPort = QString::number(port); }; - + void setJsonServerPort( quint16 port ) { _jssPort = QString::number( port ); } /// /// @brief get new json server port /// - quint16 getJsonServerPort() const { return _jssPort.toInt(); }; - + quint16 getJsonServerPort() const { return _jssPort.toInt(); } /// /// @brief set new ssl server port /// - void setSSLServerPort(quint16 port) { _sslPort = QString::number(port); }; - + void setSSLServerPort( quint16 port ) { _sslPort = QString::number( port ); } /// /// @brief get new ssl server port /// - quint16 getSSLServerPort() const { return _sslPort.toInt(); }; + quint16 getSSLServerPort() const { return _sslPort.toInt(); } /// /// @brief set new hyperion name /// - void setHyperionName(const QString &name) { _name = name; }; + void setHyperionName( const QString &name ) { _name = name; } /// /// @brief get hyperion name /// - QString getHyperionName() const { return _name; }; - - + QString getHyperionName() const { return _name; } + signals: /// /// @brief Emits whenever a new SSDP search "man : ssdp:discover" is received along with the service type @@ -133,23 +130,18 @@ signals: /// @param address The ip of the caller /// @param port The port of the caller /// - void msearchRequestReceived(const QString& target, const QString& mx, const QString address, quint16 port); + void msearchRequestReceived( const QString &target, + const QString &mx, + const QString address, + quint16 port ); private: - Logger* _log; - QUdpSocket* _udpSocket; + Logger *_log; + QUdpSocket *_udpSocket; - QString _serverHeader, - _uuid, - _fbsPort, - _pbsPort, - _jssPort, - _sslPort, - _name, - _descAddress; - bool _running; + QString _serverHeader, _uuid, _fbsPort, _pbsPort, _jssPort, _sslPort, _name, _descAddress; + bool _running; private slots: void readPendingDatagrams(); - }; diff --git a/include/utils/ColorRgb.h b/include/utils/ColorRgb.h index 780f44d0..7f11849c 100644 --- a/include/utils/ColorRgb.h +++ b/include/utils/ColorRgb.h @@ -97,17 +97,23 @@ inline bool operator!=(const ColorRgb & lhs, const ColorRgb & rhs) /// Compare operator to check if a color is 'smaller' than or 'equal' to another color inline bool operator<=(const ColorRgb & lhs, const ColorRgb & rhs) { - return lhs < rhs || lhs == rhs; + return lhs.red <= rhs.red && + lhs.green <= rhs.green && + lhs.blue <= rhs.blue; } /// Compare operator to check if a color is 'greater' to another color inline bool operator>(const ColorRgb & lhs, const ColorRgb & rhs) { - return !(lhs < rhs) && lhs != rhs; + return lhs.red > rhs.red && + lhs.green > rhs.green && + lhs.blue > rhs.blue; } /// Compare operator to check if a color is 'greater' than or 'equal' to another color inline bool operator>=(const ColorRgb & lhs, const ColorRgb & rhs) { - return lhs > rhs || lhs == rhs; + return lhs.red >= rhs.red && + lhs.green >= rhs.green && + lhs.blue >= rhs.blue; } diff --git a/include/utils/ImageData.h b/include/utils/ImageData.h index fa0a3399..e850fba3 100644 --- a/include/utils/ImageData.h +++ b/include/utils/ImageData.h @@ -38,7 +38,7 @@ public: _height(other._height), _pixels(new Pixel_T[other._width * other._height + 1]) { - memcpy(_pixels, other._pixels, (long) other._width * other._height * sizeof(Pixel_T)); + memcpy(_pixels, other._pixels, static_cast(other._width) * static_cast(other._height) * sizeof(Pixel_T)); } ImageData& operator=(ImageData rhs) @@ -150,7 +150,7 @@ public: ssize_t size() const { - return (ssize_t) _width * _height * sizeof(Pixel_T); + return static_cast(_width) * static_cast(_height) * sizeof(Pixel_T); } void clear() @@ -163,7 +163,7 @@ public: _pixels = new Pixel_T[2]; } - memset(_pixels, 0, (unsigned long) _width * _height * sizeof(Pixel_T)); + memset(_pixels, 0, static_cast(_width) * static_cast(_height) * sizeof(Pixel_T)); } private: diff --git a/include/utils/NetOrigin.h b/include/utils/NetOrigin.h index b82238fd..75796e66 100644 --- a/include/utils/NetOrigin.h +++ b/include/utils/NetOrigin.h @@ -16,7 +16,7 @@ class NetOrigin : public QObject Q_OBJECT private: friend class HyperionDaemon; - NetOrigin(QObject* parent = 0, Logger* log = Logger::getInstance("NETWORK")); + NetOrigin(QObject* parent = nullptr, Logger* log = Logger::getInstance("NETWORK")); public: /// @@ -33,8 +33,8 @@ public: /// bool isLocalAddress(const QHostAddress& address, const QHostAddress& local) const; - static NetOrigin* getInstance(){ return instance; }; - static NetOrigin* instance; + static NetOrigin *getInstance() { return instance; } + static NetOrigin *instance; private slots: /// diff --git a/include/utils/RgbChannelAdjustment.h b/include/utils/RgbChannelAdjustment.h index cf01c3e2..3ccc2d35 100644 --- a/include/utils/RgbChannelAdjustment.h +++ b/include/utils/RgbChannelAdjustment.h @@ -19,9 +19,6 @@ public: /// @param adjustB RgbChannelAdjustment(uint8_t adjustR, uint8_t adjustG, uint8_t adjustB, QString channelName=""); - /// Destructor - ~RgbChannelAdjustment(); - /// /// Transform the given array value /// diff --git a/include/utils/SysInfo.h b/include/utils/SysInfo.h index c8dca5d7..22d80c27 100644 --- a/include/utils/SysInfo.h +++ b/include/utils/SysInfo.h @@ -11,18 +11,25 @@ public: QString kernelType; QString kernelVersion; QString architecture; + QString cpuModelName; + QString cpuModelType; + QString cpuRevision; + QString cpuHardware; QString wordSize; QString productType; QString productVersion; QString prettyName; QString hostName; QString domainName; + QString qtVersion; + QString pyVersion; }; static HyperionSysInfo get(); private: SysInfo(); + void getCPUInfo(); static SysInfo* _instance; diff --git a/include/utils/hyperion.h b/include/utils/hyperion.h index af650f63..294c91b2 100644 --- a/include/utils/hyperion.h +++ b/include/utils/hyperion.h @@ -36,9 +36,9 @@ namespace hyperion { { std::vector fg_color = { ColorRgb { - (uint8_t)FGCONFIG_ARRAY.at(0).toInt(0), - (uint8_t)FGCONFIG_ARRAY.at(1).toInt(0), - (uint8_t)FGCONFIG_ARRAY.at(2).toInt(0) + static_cast(FGCONFIG_ARRAY.at(0).toInt(0)), + static_cast(FGCONFIG_ARRAY.at(1).toInt(0)), + static_cast(FGCONFIG_ARRAY.at(2).toInt(0)) } }; hyperion->setColor(FG_PRIORITY, fg_color, fg_duration_ms); @@ -62,22 +62,22 @@ namespace hyperion { { const double backlightThreshold = colorConfig["backlightThreshold"].toDouble(0.0); const bool backlightColored = colorConfig["backlightColored"].toBool(false); - const double brightness = colorConfig["brightness"].toInt(100); - const double brightnessComp = colorConfig["brightnessCompensation"].toInt(100); + const int brightness = colorConfig["brightness"].toInt(100); + const int brightnessComp = colorConfig["brightnessCompensation"].toInt(100); const double gammaR = colorConfig["gammaRed"].toDouble(1.0); const double gammaG = colorConfig["gammaGreen"].toDouble(1.0); const double gammaB = colorConfig["gammaBlue"].toDouble(1.0); - return RgbTransform(gammaR, gammaG, gammaB, backlightThreshold, backlightColored, brightness, brightnessComp); + return RgbTransform(gammaR, gammaG, gammaB, backlightThreshold, backlightColored, static_cast(brightness), static_cast(brightnessComp)); } RgbChannelAdjustment createRgbChannelAdjustment(const QJsonObject& colorConfig, const QString& channelName, int defaultR, int defaultG, int defaultB) { const QJsonArray& channelConfig = colorConfig[channelName].toArray(); return RgbChannelAdjustment( - channelConfig[0].toInt(defaultR), - channelConfig[1].toInt(defaultG), - channelConfig[2].toInt(defaultB), + static_cast(channelConfig[0].toInt(defaultR)), + static_cast(channelConfig[1].toInt(defaultG)), + static_cast(channelConfig[2].toInt(defaultB)), "ChannelAdjust_" + channelName.toUpper() ); } @@ -101,7 +101,7 @@ namespace hyperion { return adjustment; } - MultiColorAdjustment * createLedColorsAdjustment(unsigned ledCnt, const QJsonObject & colorConfig) + MultiColorAdjustment * createLedColorsAdjustment(int ledCnt, const QJsonObject & colorConfig) { // Create the result, the transforms are added to this MultiColorAdjustment * adjustment = new MultiColorAdjustment(ledCnt); @@ -233,7 +233,7 @@ namespace hyperion { std::sort(midPointsY.begin(), midPointsY.end()); midPointsY.erase(std::unique(midPointsY.begin(), midPointsY.end()), midPointsY.end()); - QSize gridSize( midPointsX.size(), midPointsY.size() ); + QSize gridSize( static_cast(midPointsX.size()), static_cast(midPointsY.size()) ); // Correct the grid in case it is malformed in width vs height // Expected is at least 50% of width <-> height diff --git a/include/utils/jsonschema/QJsonUtils.h b/include/utils/jsonschema/QJsonUtils.h index 1cd74a8a..8626ae98 100644 --- a/include/utils/jsonschema/QJsonUtils.h +++ b/include/utils/jsonschema/QJsonUtils.h @@ -48,7 +48,7 @@ public: { case QJsonValue::Array: { - for (const QJsonValue &v : value.toArray()) + for (const QJsonValueRef v : value.toArray()) { ret = getDefaultValue(v); if (!ret.isEmpty()) diff --git a/include/webserver/WebServer.h b/include/webserver/WebServer.h index abcf0409..f2537eba 100644 --- a/include/webserver/WebServer.h +++ b/include/webserver/WebServer.h @@ -33,7 +33,7 @@ class WebServer : public QObject Q_OBJECT public: - WebServer (const QJsonDocument& config, bool useSsl, QObject * parent = 0); + WebServer (const QJsonDocument& config, bool useSsl, QObject * parent = nullptr); ~WebServer () override; diff --git a/libsrc/api/API.cpp b/libsrc/api/API.cpp index 7a22d9cc..b3413318 100644 --- a/libsrc/api/API.cpp +++ b/libsrc/api/API.cpp @@ -378,11 +378,18 @@ QString API::saveEffect(const QJsonObject &data) return NO_AUTH; } -void API::saveSettings(const QJsonObject &data) +bool API::saveSettings(const QJsonObject &data) { + bool rc = true; if (!_adminAuthorized) - return; - QMetaObject::invokeMethod(_hyperion, "saveSettings", Qt::QueuedConnection, Q_ARG(QJsonObject, data), Q_ARG(bool, true)); + { + rc = false; + } + else + { + QMetaObject::invokeMethod(_hyperion, "saveSettings", Qt::DirectConnection, Q_RETURN_ARG(bool, rc), Q_ARG(QJsonObject, data), Q_ARG(bool, true)); + } + return rc; } bool API::updateHyperionPassword(const QString &password, const QString &newPassword) diff --git a/libsrc/api/JsonAPI.cpp b/libsrc/api/JsonAPI.cpp index 0bedbeac..49abfd5b 100644 --- a/libsrc/api/JsonAPI.cpp +++ b/libsrc/api/JsonAPI.cpp @@ -275,12 +275,18 @@ void JsonAPI::handleSysInfoCommand(const QJsonObject &, const QString &command, system["kernelType"] = data.kernelType; system["kernelVersion"] = data.kernelVersion; system["architecture"] = data.architecture; + system["cpuModelName"] = data.cpuModelName; + system["cpuModelType"] = data.cpuModelType; + system["cpuHardware"] = data.cpuHardware; + system["cpuRevision"] = data.cpuRevision; system["wordSize"] = data.wordSize; system["productType"] = data.productType; system["productVersion"] = data.productVersion; system["prettyName"] = data.prettyName; system["hostName"] = data.hostName; system["domainName"] = data.domainName; + system["qtVersion"] = data.qtVersion; + system["pyVersion"] = data.pyVersion; info["system"] = system; QJsonObject hyperion; @@ -289,6 +295,8 @@ void JsonAPI::handleSysInfoCommand(const QJsonObject &, const QString &command, hyperion["gitremote"] = QString(HYPERION_GIT_REMOTE); hyperion["time"] = QString(__DATE__ " " __TIME__); hyperion["id"] = _authManager->getID(); + hyperion["readOnlyMode"] = _hyperion->getReadOnlyMode(); + info["hyperion"] = hyperion; // send the result @@ -461,8 +469,12 @@ void JsonAPI::handleServerInfoCommand(const QJsonObject &message, const QString #if defined(ENABLE_DISPMANX) || defined(ENABLE_V4L2) || defined(ENABLE_FB) || defined(ENABLE_AMLOGIC) || defined(ENABLE_OSX) || defined(ENABLE_X11) || defined(ENABLE_XCB) || defined(ENABLE_QT) + if ( GrabberWrapper::getInstance() != nullptr ) + { + grabbers["active"] = GrabberWrapper::getInstance()->getActive(); + } + // get available grabbers - //grabbers["active"] = ????; for (auto grabber : GrabberWrapper::availableGrabbers()) { availableGrabbers.append(grabber); @@ -677,12 +689,13 @@ void JsonAPI::handleServerInfoCommand(const QJsonObject &message, const QString if (subsArr.contains("all")) { subsArr = QJsonArray(); - for (const auto &entry : _jsonCB->getCommands()) + for (const auto& entry : _jsonCB->getCommands()) { subsArr.append(entry); } } - for (const auto &entry : subsArr) + + for (const QJsonValueRef entry : subsArr) { // config callbacks just if auth is set if ((entry == "settings-update" || entry == "token-update") && !API::isAdminAuthorized()) @@ -870,8 +883,14 @@ void JsonAPI::handleConfigSetCommand(const QJsonObject &message, const QString & QJsonObject config = message["config"].toObject(); if (API::isHyperionEnabled()) { - API::saveSettings(config); - sendSuccessReply(command, tan); + if ( API::saveSettings(config) ) + { + sendSuccessReply(command, tan); + } + else + { + sendErrorReply("Save settings failed", command, tan); + } } else sendErrorReply("Saving configuration while Hyperion is disabled isn't possible", command, tan); @@ -1399,7 +1418,8 @@ void JsonAPI::handleLedDeviceCommand(const QJsonObject &message, const QString & if (subc == "discover") { ledDevice = LedDeviceFactory::construct(config); - const QJsonObject devicesDiscovered = ledDevice->discover(); + const QJsonObject ¶ms = message["params"].toObject(); + const QJsonObject devicesDiscovered = ledDevice->discover(params); Debug(_log, "response: [%s]", QString(QJsonDocument(devicesDiscovered).toJson(QJsonDocument::Compact)).toUtf8().constData() ); diff --git a/libsrc/api/JsonCB.cpp b/libsrc/api/JsonCB.cpp index 9c48debb..5863d1cc 100644 --- a/libsrc/api/JsonCB.cpp +++ b/libsrc/api/JsonCB.cpp @@ -72,12 +72,10 @@ bool JsonCB::subscribeFor(const QString& type, bool unsubscribe) if(type == "priorities-update") { - if(unsubscribe){ + if (unsubscribe) disconnect(_prioMuxer,0 ,0 ,0); - } else { + else connect(_prioMuxer, &PriorityMuxer::prioritiesChanged, this, &JsonCB::handlePriorityUpdate, Qt::UniqueConnection); - connect(_prioMuxer, &PriorityMuxer::autoSelectChanged, this, &JsonCB::handlePriorityUpdate, Qt::UniqueConnection); - } } if(type == "imageToLedMapping-update") diff --git a/libsrc/boblightserver/BoblightClientConnection.cpp b/libsrc/boblightserver/BoblightClientConnection.cpp index e50a6dc9..833ba94f 100644 --- a/libsrc/boblightserver/BoblightClientConnection.cpp +++ b/libsrc/boblightserver/BoblightClientConnection.cpp @@ -386,7 +386,7 @@ void BoblightClientConnection::sendLightMessage() sendMessage(QByteArray(buffer, n)); double h0, h1, v0, v1; - for (unsigned i = 0; i < _hyperion->getLedCount(); ++i) + for (int i = 0; i < _hyperion->getLedCount(); ++i) { _imageProcessor->getScanParameters(i, h0, h1, v0, v1); n = snprintf(buffer, sizeof(buffer), "light %03d scan %f %f %f %f\n", i, 100*v0, 100*v1, 100*h0, 100*h1); diff --git a/libsrc/boblightserver/BoblightClientConnection.h b/libsrc/boblightserver/BoblightClientConnection.h index 46a3b0eb..ce4f7927 100644 --- a/libsrc/boblightserver/BoblightClientConnection.h +++ b/libsrc/boblightserver/BoblightClientConnection.h @@ -17,7 +17,7 @@ class ImageProcessor; class Hyperion; /// -/// The Connection object created by \a BoblightServer when a new connection is establshed +/// The Connection object created by \a BoblightServer when a new connection is established /// class BoblightClientConnection : public QObject { diff --git a/libsrc/commandline/Option.cpp b/libsrc/commandline/Option.cpp index 21a191a9..0e802152 100644 --- a/libsrc/commandline/Option.cpp +++ b/libsrc/commandline/Option.cpp @@ -21,6 +21,11 @@ Option::Option(const QCommandLineOption &other) : QCommandLineOption(other) {} +Option::~Option() +{ + +} + QString Option::value(Parser &parser) const { return parser.value(*this); diff --git a/libsrc/commandline/Parser.cpp b/libsrc/commandline/Parser.cpp index 84efea45..eb9da4bc 100644 --- a/libsrc/commandline/Parser.cpp +++ b/libsrc/commandline/Parser.cpp @@ -1,5 +1,3 @@ -#include -#include #include "commandline/Parser.h" using namespace commandline; @@ -21,7 +19,7 @@ bool Parser::parse(const QStringList &arguments) QString value = this->value(*option); if (!option->validate(*this, value)) { const QString error = option->getError(); - if (error.size()) { + if (!error.isEmpty()) { _errorText = tr("%1 is not a valid option for %2\n%3").arg(value, option->name(), error); } else @@ -44,15 +42,14 @@ void Parser::process(const QStringList &arguments) } } -void Parser::process(const QCoreApplication &app) +void Parser::process(const QCoreApplication& /*app*/) { - Q_UNUSED(app); process(QCoreApplication::arguments()); } QString Parser::errorText() const { - return (_errorText.size()) ? _errorText : _parser.errorText(); + return (!_errorText.isEmpty()) ? _errorText : _parser.errorText(); } bool Parser::addOption(Option &option) @@ -66,27 +63,27 @@ bool Parser::addOption(Option * const option) return _parser.addOption(*option); } -QStringList Parser::_getNames(const char shortOption, const QString longOption) +QStringList Parser::_getNames(const char shortOption, const QString& longOption) { QStringList names; if (shortOption != 0x0) { names << QString(shortOption); } - if (longOption.size()) + if (longOption.size() != 0) { names << longOption; } return names; } -QString Parser::_getDescription(const QString description, const QString default_) +QString Parser::_getDescription(const QString& description, const QString& default_) { /* Add the translations if available */ QString formattedDescription(tr(qPrintable(description))); /* Fill in the default if needed */ - if (default_.size()) + if (!default_.isEmpty()) { if(!formattedDescription.contains("%1")) { diff --git a/libsrc/db/DBManager.cpp b/libsrc/db/DBManager.cpp index 8e0ea575..6336ef0e 100644 --- a/libsrc/db/DBManager.cpp +++ b/libsrc/db/DBManager.cpp @@ -19,6 +19,7 @@ static QThreadStorage _databasePool; DBManager::DBManager(QObject* parent) : QObject(parent) , _log(Logger::getInstance("DB")) + , _readonlyMode (false) { } @@ -58,6 +59,11 @@ QSqlDatabase DBManager::getDB() const bool DBManager::createRecord(const VectorPair& conditions, const QVariantMap& columns) const { + if ( _readonlyMode ) + { + return false; + } + if(recordExists(conditions)) { // if there is no column data, return @@ -144,6 +150,11 @@ bool DBManager::recordExists(const VectorPair& conditions) const bool DBManager::updateRecord(const VectorPair& conditions, const QVariantMap& columns) const { + if ( _readonlyMode ) + { + return false; + } + QSqlDatabase idb = getDB(); QSqlQuery query(idb); query.setForwardOnly(true); @@ -276,6 +287,11 @@ bool DBManager::getRecords(QVector& results, const QStringList& tCo bool DBManager::deleteRecord(const VectorPair& conditions) const { + if ( _readonlyMode ) + { + return false; + } + if(conditions.isEmpty()) { Error(_log, "Oops, a deleteRecord() call wants to delete the entire table (%s)! Denied it", QSTRING_CSTR(_table)); @@ -311,6 +327,11 @@ bool DBManager::deleteRecord(const VectorPair& conditions) const bool DBManager::createTable(QStringList& columns) const { + if ( _readonlyMode ) + { + return false; + } + if(columns.isEmpty()) { Error(_log,"Empty tables aren't supported!"); @@ -353,6 +374,11 @@ bool DBManager::createTable(QStringList& columns) const bool DBManager::createColumn(const QString& column) const { + if ( _readonlyMode ) + { + return false; + } + QSqlDatabase idb = getDB(); QSqlQuery query(idb); if(!query.exec(QString("ALTER TABLE %1 ADD COLUMN %2").arg(_table,column))) @@ -374,6 +400,11 @@ bool DBManager::tableExists(const QString& table) const bool DBManager::deleteTable(const QString& table) const { + if ( _readonlyMode ) + { + return false; + } + if(tableExists(table)) { QSqlDatabase idb = getDB(); diff --git a/libsrc/effectengine/Effect.cpp b/libsrc/effectengine/Effect.cpp index 80c85105..3f241f2f 100644 --- a/libsrc/effectengine/Effect.cpp +++ b/libsrc/effectengine/Effect.cpp @@ -56,8 +56,8 @@ void Effect::setModuleParameters() PyModule_AddObject(module, "__effectObj", PyCapsule_New((void*)this, "hyperion.__effectObj", nullptr)); // add ledCount variable to the interpreter - unsigned ledCount = 0; - QMetaObject::invokeMethod(_hyperion, "getLedCount", Qt::BlockingQueuedConnection, Q_RETURN_ARG(unsigned, ledCount)); + int ledCount = 0; + QMetaObject::invokeMethod(_hyperion, "getLedCount", Qt::BlockingQueuedConnection, Q_RETURN_ARG(int, ledCount)); PyObject_SetAttrString(module, "ledCount", Py_BuildValue("i", ledCount)); // add minimumWriteTime variable to the interpreter diff --git a/libsrc/effectengine/EffectModule.cpp b/libsrc/effectengine/EffectModule.cpp index 4e5ae414..78d6be5d 100644 --- a/libsrc/effectengine/EffectModule.cpp +++ b/libsrc/effectengine/EffectModule.cpp @@ -138,7 +138,7 @@ PyObject* EffectModule::wrapSetColor(PyObject *self, PyObject *args) int argCount = PyTuple_Size(args); if (argCount == 3) { - // three seperate arguments for red, green, and blue + // three separate arguments for red, green, and blue ColorRgb color; if (PyArg_ParseTuple(args, "bbb", &color.red, &color.green, &color.blue)) { @@ -158,7 +158,7 @@ PyObject* EffectModule::wrapSetColor(PyObject *self, PyObject *args) if (PyByteArray_Check(bytearray)) { size_t length = PyByteArray_Size(bytearray); - if (length == 3 * getEffect()->_hyperion->getLedCount()) + if (length == 3 * static_cast(getEffect()->_hyperion->getLedCount())) { char * data = PyByteArray_AS_STRING(bytearray); memcpy(getEffect()->_colors.data(), data, length); diff --git a/libsrc/grabber/CMakeLists.txt b/libsrc/grabber/CMakeLists.txt index 3faec253..302aece4 100644 --- a/libsrc/grabber/CMakeLists.txt +++ b/libsrc/grabber/CMakeLists.txt @@ -29,3 +29,7 @@ endif() if (ENABLE_QT) add_subdirectory(qt) endif() + +if (ENABLE_DX) + add_subdirectory(directx) +endif() diff --git a/libsrc/grabber/directx/CMakeLists.txt b/libsrc/grabber/directx/CMakeLists.txt new file mode 100644 index 00000000..16db7dd2 --- /dev/null +++ b/libsrc/grabber/directx/CMakeLists.txt @@ -0,0 +1,14 @@ +# Define the current source locations +SET( CURRENT_HEADER_DIR ${CMAKE_SOURCE_DIR}/include/grabber ) +SET( CURRENT_SOURCE_DIR ${CMAKE_SOURCE_DIR}/libsrc/grabber/directx ) + +include_directories(${DIRECTX9_INCLUDE_DIRS}) + +FILE ( GLOB DIRECTX_GRAB_SOURCES "${CURRENT_HEADER_DIR}/DirectX*.h" "${CURRENT_SOURCE_DIR}/*.h" "${CURRENT_SOURCE_DIR}/*.cpp" ) + +add_library( directx-grabber ${DIRECTX_GRAB_SOURCES} ) + +target_link_libraries(directx-grabber + hyperion + ${DIRECTX9_LIBRARIES} +) diff --git a/libsrc/grabber/directx/DirectXGrabber.cpp b/libsrc/grabber/directx/DirectXGrabber.cpp new file mode 100644 index 00000000..f8cd3f23 --- /dev/null +++ b/libsrc/grabber/directx/DirectXGrabber.cpp @@ -0,0 +1,181 @@ +#include +#include +#include +#pragma comment(lib, "d3d9.lib") +#pragma comment(lib,"d3dx9.lib") + +DirectXGrabber::DirectXGrabber(int cropLeft, int cropRight, int cropTop, int cropBottom, int pixelDecimation, int display) + : Grabber("DXGRABBER", 0, 0, cropLeft, cropRight, cropTop, cropBottom) + , _pixelDecimation(pixelDecimation) + , _displayWidth(0) + , _displayHeight(0) + , _srcRect(0) + , _d3d9(nullptr) + , _device(nullptr) + , _surface(nullptr) +{ + // init + setupDisplay(); +} + +DirectXGrabber::~DirectXGrabber() +{ + freeResources(); +} + +void DirectXGrabber::freeResources() +{ + if (_surface) + _surface->Release(); + + if (_device) + _device->Release(); + + if (_d3d9) + _d3d9->Release(); + + delete _srcRect; +} + +bool DirectXGrabber::setupDisplay() +{ + freeResources(); + + D3DDISPLAYMODE ddm; + D3DPRESENT_PARAMETERS d3dpp; + + if ((_d3d9 = Direct3DCreate9(D3D_SDK_VERSION)) == nullptr) + { + Error(_log, "Failed to create Direct3D"); + return false; + } + + if (FAILED(_d3d9->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &ddm))) + { + Error(_log, "Failed to get current display mode"); + return false; + } + + SecureZeroMemory(&d3dpp, sizeof(D3DPRESENT_PARAMETERS)); + + d3dpp.Windowed = TRUE; + d3dpp.Flags = D3DPRESENTFLAG_LOCKABLE_BACKBUFFER; + d3dpp.BackBufferFormat = ddm.Format; + d3dpp.BackBufferHeight = _displayHeight = ddm.Height; + d3dpp.BackBufferWidth = _displayWidth = ddm.Width; + d3dpp.MultiSampleType = D3DMULTISAMPLE_NONE; + d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD; + d3dpp.hDeviceWindow = nullptr; + d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_DEFAULT; + d3dpp.FullScreen_RefreshRateInHz = D3DPRESENT_RATE_DEFAULT; + + if (FAILED(_d3d9->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, nullptr, D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp, &_device))) + { + Error(_log, "CreateDevice failed"); + return false; + } + + if (FAILED(_device->CreateOffscreenPlainSurface(ddm.Width, ddm.Height, D3DFMT_A8R8G8B8, D3DPOOL_SCRATCH, &_surface, nullptr))) + { + Error(_log, "CreateOffscreenPlainSurface failed"); + return false; + } + + int width = _displayWidth / _pixelDecimation; + int height =_displayHeight / _pixelDecimation; + + // calculate final image dimensions and adjust top/left cropping in 3D modes + _srcRect = new RECT; + switch (_videoMode) + { + case VideoMode::VIDEO_3DSBS: + _width = width / 2; + _height = height; + _srcRect->left = _cropLeft * _pixelDecimation / 2; + _srcRect->top = _cropTop * _pixelDecimation; + _srcRect->right = (_displayWidth / 2) - (_cropRight * _pixelDecimation); + _srcRect->bottom = _displayHeight - (_cropBottom * _pixelDecimation); + break; + case VideoMode::VIDEO_3DTAB: + _width = width; + _height = height / 2; + _srcRect->left = _cropLeft * _pixelDecimation; + _srcRect->top = (_cropTop * _pixelDecimation) / 2; + _srcRect->right = _displayWidth - (_cropRight * _pixelDecimation); + _srcRect->bottom = (_displayHeight / 2) - (_cropBottom * _pixelDecimation); + break; + case VideoMode::VIDEO_2D: + default: + _width = width; + _height = height; + _srcRect->left = _cropLeft * _pixelDecimation; + _srcRect->top = _cropTop * _pixelDecimation; + _srcRect->right = _displayWidth - _cropRight * _pixelDecimation; + _srcRect->bottom = _displayHeight - _cropBottom * _pixelDecimation; + break; + } + + if (FAILED(_device->CreateOffscreenPlainSurface(_width, _height, D3DFMT_R8G8B8, D3DPOOL_SCRATCH, &_surfaceDest, nullptr))) + { + Error(_log, "CreateOffscreenPlainSurface failed"); + return false; + } + + Info(_log, "Update output image resolution to [%dx%d]", _width, _height); + return true; +} + +int DirectXGrabber::grabFrame(Image & image) +{ + if (!_enabled) + return 0; + + if (FAILED(_device->GetFrontBufferData(0, _surface))) + { + // reinit, this will disable capture on failure + Error(_log, "Unable to get Buffer Surface Data"); + setEnabled(setupDisplay()); + return -1; + } + + D3DXLoadSurfaceFromSurface(_surfaceDest, nullptr, nullptr, _surface, nullptr, _srcRect, D3DX_DEFAULT, 0); + + D3DLOCKED_RECT lockedRect; + if (FAILED(_surfaceDest->LockRect(&lockedRect, nullptr, D3DLOCK_NO_DIRTY_UPDATE | D3DLOCK_NOSYSLOCK | D3DLOCK_READONLY))) + { + Error(_log, "Unable to lock destination Front Buffer Surface"); + return 0; + } + + memcpy(image.memptr(), lockedRect.pBits, _width * _height * 3); + for (int idx = 0; idx < _width * _height; idx++) + { + const ColorRgb & color = image.memptr()[idx]; + image.memptr()[idx] = ColorRgb{color.blue, color.green, color.red}; + } + + if (FAILED(_surfaceDest->UnlockRect())) + { + Error(_log, "Unable to unlock destination Front Buffer Surface"); + return 0; + } + + return 0; +} + +void DirectXGrabber::setVideoMode(VideoMode mode) +{ + Grabber::setVideoMode(mode); + setupDisplay(); +} + +void DirectXGrabber::setPixelDecimation(int pixelDecimation) +{ + _pixelDecimation = pixelDecimation; +} + +void DirectXGrabber::setCropping(unsigned cropLeft, unsigned cropRight, unsigned cropTop, unsigned cropBottom) +{ + Grabber::setCropping(cropLeft, cropRight, cropTop, cropBottom); + setupDisplay(); +} diff --git a/libsrc/grabber/directx/DirectXWrapper.cpp b/libsrc/grabber/directx/DirectXWrapper.cpp new file mode 100644 index 00000000..26018903 --- /dev/null +++ b/libsrc/grabber/directx/DirectXWrapper.cpp @@ -0,0 +1,11 @@ +#include + +DirectXWrapper::DirectXWrapper(int cropLeft, int cropRight, int cropTop, int cropBottom, int pixelDecimation, int display, const unsigned updateRate_Hz) + : GrabberWrapper("DirectX", &_grabber, 0, 0, updateRate_Hz) + , _grabber(cropLeft, cropRight, cropTop, cropBottom, pixelDecimation, display) +{} + +void DirectXWrapper::action() +{ + transferFrame(_grabber); +} diff --git a/libsrc/hyperion/AuthManager.cpp b/libsrc/hyperion/AuthManager.cpp index 17f76706..329b387d 100644 --- a/libsrc/hyperion/AuthManager.cpp +++ b/libsrc/hyperion/AuthManager.cpp @@ -10,10 +10,10 @@ AuthManager *AuthManager::manager = nullptr; -AuthManager::AuthManager(QObject *parent) +AuthManager::AuthManager(QObject *parent, bool readonlyMode) : QObject(parent) - , _authTable(new AuthTable("", this)) - , _metaTable(new MetaTable(this)) + , _authTable(new AuthTable("", this, readonlyMode)) + , _metaTable(new MetaTable(this, readonlyMode)) , _pendingRequests() , _authRequired(true) , _timer(new QTimer(this)) diff --git a/libsrc/hyperion/ComponentRegister.cpp b/libsrc/hyperion/ComponentRegister.cpp index 56d3e430..a57dd0bd 100644 --- a/libsrc/hyperion/ComponentRegister.cpp +++ b/libsrc/hyperion/ComponentRegister.cpp @@ -7,14 +7,14 @@ using namespace hyperion; ComponentRegister::ComponentRegister(Hyperion* hyperion) : _hyperion(hyperion) - , _log(Logger::getInstance("COMPONENTREG")) + , _log(Logger::getInstance("COMPONENTREG")) { // init all comps to false QVector vect; vect << COMP_ALL << COMP_SMOOTHING << COMP_BLACKBORDER << COMP_FORWARDER << COMP_BOBLIGHTSERVER << COMP_GRABBER << COMP_V4L << COMP_LEDDEVICE; for(auto e : vect) { - _componentStates.emplace(e, ((e == COMP_ALL) ? true : false)); + _componentStates.emplace(e, (e == COMP_ALL)); } connect(_hyperion, &Hyperion::compStateChangeRequest, this, &ComponentRegister::handleCompStateChangeRequest); @@ -36,7 +36,7 @@ void ComponentRegister::setNewComponentState(hyperion::Components comp, bool act Debug( _log, "%s: %s", componentToString(comp), (activated? "enabled" : "disabled")); _componentStates[comp] = activated; // emit component has changed state - emit updatedComponentState(comp, activated); + emit updatedComponentState(comp, activated); } } @@ -48,28 +48,34 @@ void ComponentRegister::handleCompStateChangeRequest(hyperion::Components comps, if(!activated && _prevComponentStates.empty()) { Debug(_log,"Disable Hyperion, store current component states"); - for(const auto comp : _componentStates) + for(const auto &comp : _componentStates) { // save state _prevComponentStates.emplace(comp.first, comp.second); // disable if enabled if(comp.second) + { emit _hyperion->compStateChangeRequest(comp.first, false); + } } setNewComponentState(COMP_ALL, false); } - else if(activated && !_prevComponentStates.empty()) + else { - Debug(_log,"Enable Hyperion, recover previous component states"); - for(const auto comp : _prevComponentStates) + if(activated && !_prevComponentStates.empty()) { - // if comp was enabled, enable again - if(comp.second) - emit _hyperion->compStateChangeRequest(comp.first, true); - + Debug(_log,"Enable Hyperion, recover previous component states"); + for(const auto &comp : _prevComponentStates) + { + // if comp was enabled, enable again + if(comp.second) + { + emit _hyperion->compStateChangeRequest(comp.first, true); + } + } + _prevComponentStates.clear(); + setNewComponentState(COMP_ALL, true); } - _prevComponentStates.clear(); - setNewComponentState(COMP_ALL, true); } _inProgress = false; } diff --git a/libsrc/hyperion/GrabberWrapper.cpp b/libsrc/hyperion/GrabberWrapper.cpp index c91cf1bd..51f04fe3 100644 --- a/libsrc/hyperion/GrabberWrapper.cpp +++ b/libsrc/hyperion/GrabberWrapper.cpp @@ -65,6 +65,11 @@ bool GrabberWrapper::isActive() const return _timer->isActive(); } +QString GrabberWrapper::getActive() const +{ + return _grabberName; +} + QStringList GrabberWrapper::availableGrabbers() { QStringList grabbers; @@ -101,6 +106,10 @@ QStringList GrabberWrapper::availableGrabbers() grabbers << "qt"; #endif + #ifdef ENABLE_DX + grabbers << "dx"; + #endif + return grabbers; } diff --git a/libsrc/hyperion/Hyperion.cpp b/libsrc/hyperion/Hyperion.cpp index 2b5182f0..14e14132 100644 --- a/libsrc/hyperion/Hyperion.cpp +++ b/libsrc/hyperion/Hyperion.cpp @@ -18,7 +18,7 @@ #include #include -// Leddevice includes +// LedDevice includes #include #include @@ -39,21 +39,27 @@ // Boblight #include -Hyperion::Hyperion(quint8 instance) +Hyperion::Hyperion(quint8 instance, bool readonlyMode) : QObject() , _instIndex(instance) - , _settingsManager(new SettingsManager(instance, this)) + , _settingsManager(new SettingsManager(instance, this, readonlyMode)) , _componentRegister(this) , _ledString(hyperion::createLedString(getSetting(settings::LEDS).array(), hyperion::createColorOrder(getSetting(settings::DEVICE).object()))) , _imageProcessor(new ImageProcessor(_ledString, this)) - , _muxer(_ledString.leds().size(), this) - , _raw2ledAdjustment(hyperion::createLedColorsAdjustment(_ledString.leds().size(), getSetting(settings::COLOR).object())) + , _muxer(static_cast(_ledString.leds().size()), this) + , _raw2ledAdjustment(hyperion::createLedColorsAdjustment(static_cast(_ledString.leds().size()), getSetting(settings::COLOR).object())) + , _ledDeviceWrapper(nullptr) + , _deviceSmooth(nullptr) , _effectEngine(nullptr) , _messageForwarder(nullptr) , _log(Logger::getInstance("HYPERION")) , _hwLedCount() , _ledGridSize(hyperion::getLedLayoutGridSize(getSetting(settings::LEDS).array())) + , _BGEffectHandler(nullptr) + ,_captureCont(nullptr) , _ledBuffer(_ledString.leds().size(), ColorRgb::BLACK) + , _boblightServer(nullptr) + , _readOnlyMode(readonlyMode) { } @@ -77,9 +83,9 @@ void Hyperion::start() } // handle hwLedCount - _hwLedCount = qMax(unsigned(getSetting(settings::DEVICE).object()["hardwareLedCount"].toInt(getLedCount())), getLedCount()); + _hwLedCount = qMax(getSetting(settings::DEVICE).object()["hardwareLedCount"].toInt(getLedCount()), getLedCount()); - // init colororder vector + // Initialize colororder vector for (const Led& led : _ledString.leds()) { _ledStringColorOrder.push_back(led.colorOrder); @@ -96,12 +102,14 @@ void Hyperion::start() // listen for settings updates of this instance (LEDS & COLOR) connect(_settingsManager, &SettingsManager::settingsChanged, this, &Hyperion::handleSettingsUpdate); + #if 0 // set color correction activity state const QJsonObject color = getSetting(settings::COLOR).object(); + #endif - // initialize leddevices + // initialize LED-devices QJsonObject ledDevice = getSetting(settings::DEVICE).object(); - ledDevice["currentLedCount"] = int(_hwLedCount); // Inject led count info + ledDevice["currentLedCount"] = _hwLedCount; // Inject led count info _ledDeviceWrapper = new LedDeviceWrapper(this); connect(this, &Hyperion::compStateChangeRequest, _ledDeviceWrapper, &LedDeviceWrapper::handleComponentState); @@ -114,7 +122,9 @@ void Hyperion::start() // create the message forwarder only on main instance if (_instIndex == 0) + { _messageForwarder = new MessageForwarder(this); + } // create the effect engine; needs to be initialized after smoothing! _effectEngine = new EffectEngine(this); @@ -135,14 +145,14 @@ void Hyperion::start() connect(GlobalSignals::getInstance(), &GlobalSignals::setGlobalColor, this, &Hyperion::setColor); connect(GlobalSignals::getInstance(), &GlobalSignals::setGlobalImage, this, &Hyperion::setInputImage); - // if there is no startup / background eff and no sending capture interface we probably want to push once BLACK (as PrioMuxer won't emit a prioritiy change) + // if there is no startup / background effect and no sending capture interface we probably want to push once BLACK (as PrioMuxer won't emit a priority change) update(); // boblight, can't live in global scope as it depends on layout _boblightServer = new BoblightServer(this, getSetting(settings::BOBLSERVER)); connect(this, &Hyperion::settingsChanged, _boblightServer, &BoblightServer::handleSettingsUpdate); - // instance inited, enter thread event loop + // instance initiated, enter thread event loop emit started(); } @@ -177,7 +187,7 @@ void Hyperion::handleSettingsUpdate(settings::type type, const QJsonDocument& co const QJsonObject obj = config.object(); // change in color recreate ledAdjustments delete _raw2ledAdjustment; - _raw2ledAdjustment = hyperion::createLedColorsAdjustment(_ledString.leds().size(), obj); + _raw2ledAdjustment = hyperion::createLedColorsAdjustment(static_cast(_ledString.leds().size()), obj); if (!_raw2ledAdjustment->verifyAdjustments()) { @@ -188,13 +198,13 @@ void Hyperion::handleSettingsUpdate(settings::type type, const QJsonDocument& co { const QJsonArray leds = config.array(); - // stop and cache all running effects, as effects depend heavily on ledlayout + // stop and cache all running effects, as effects depend heavily on LED-layout _effectEngine->cacheRunningEffects(); - // ledstring, img processor, muxer, ledGridSize (eff engine image based effects), _ledBuffer and ByteOrder of ledstring + // ledstring, img processor, muxer, ledGridSize (effect-engine image based effects), _ledBuffer and ByteOrder of ledstring _ledString = hyperion::createLedString(leds, hyperion::createColorOrder(getSetting(settings::DEVICE).object())); _imageProcessor->setLedString(_ledString); - _muxer.updateLedColorsLength(_ledString.leds().size()); + _muxer.updateLedColorsLength(static_cast(_ledString.leds().size())); _ledGridSize = hyperion::getLedLayoutGridSize(leds); std::vector color(_ledString.leds().size(), ColorRgb{0,0,0}); @@ -207,11 +217,11 @@ void Hyperion::handleSettingsUpdate(settings::type type, const QJsonDocument& co } // handle hwLedCount update - _hwLedCount = qMax(unsigned(getSetting(settings::DEVICE).object()["hardwareLedCount"].toInt(getLedCount())), getLedCount()); + _hwLedCount = qMax(getSetting(settings::DEVICE).object()["hardwareLedCount"].toInt(getLedCount()), getLedCount()); // change in leds are also reflected in adjustment delete _raw2ledAdjustment; - _raw2ledAdjustment = hyperion::createLedColorsAdjustment(_ledString.leds().size(), getSetting(settings::COLOR).object()); + _raw2ledAdjustment = hyperion::createLedColorsAdjustment(static_cast(_ledString.leds().size()), getSetting(settings::COLOR).object()); // start cached effects _effectEngine->startCachedEffects(); @@ -221,7 +231,7 @@ void Hyperion::handleSettingsUpdate(settings::type type, const QJsonDocument& co QJsonObject dev = config.object(); // handle hwLedCount update - _hwLedCount = qMax(unsigned(dev["hardwareLedCount"].toInt(getLedCount())), getLedCount()); + _hwLedCount = qMax(dev["hardwareLedCount"].toInt(getLedCount()), getLedCount()); // force ledString update, if device ByteOrder changed if(_ledDeviceWrapper->getColorOrder() != dev["colorOrder"].toString("rgb")) @@ -237,7 +247,7 @@ void Hyperion::handleSettingsUpdate(settings::type type, const QJsonDocument& co } // do always reinit until the led devices can handle dynamic changes - dev["currentLedCount"] = int(_hwLedCount); // Inject led count info + dev["currentLedCount"] = _hwLedCount; // Inject led count info _ledDeviceWrapper->createLedDevice(dev); // TODO: Check, if framegrabber frequency is lower than latchtime..., if yes, stop @@ -276,9 +286,9 @@ unsigned Hyperion::updateSmoothingConfig(unsigned id, int settlingTime_ms, doubl return _deviceSmooth->updateConfig(id, settlingTime_ms, ledUpdateFrequency_hz, updateDelay); } -unsigned Hyperion::getLedCount() const +int Hyperion::getLedCount() const { - return _ledString.leds().size(); + return static_cast(_ledString.leds().size()); } void Hyperion::setSourceAutoSelect(bool state) @@ -322,7 +332,9 @@ bool Hyperion::setInput(int priority, const std::vector& ledColors, in { // clear effect if this call does not come from an effect if(clearEffect) + { _effectEngine->channelCleared(priority); + } // if this priority is visible, update immediately if(priority == _muxer.getCurrentPriority()) @@ -347,7 +359,9 @@ bool Hyperion::setInputImage(int priority, const Image& image, int64_t { // clear effect if this call does not come from an effect if(clearEffect) + { _effectEngine->channelCleared(priority); + } // if this priority is visible, update immediately if(priority == _muxer.getCurrentPriority()) @@ -369,7 +383,9 @@ void Hyperion::setColor(int priority, const std::vector &ledColors, in { // clear effect if this call does not come from an effect if (clearEffects) + { _effectEngine->channelCleared(priority); + } // create full led vector from single/multiple colors size_t size = _ledString.leds().size(); @@ -380,21 +396,23 @@ void Hyperion::setColor(int priority, const std::vector &ledColors, in { newLedColors.emplace_back(entry); if (newLedColors.size() == size) + { goto end; + } } } end: if (getPriorityInfo(priority).componentId != hyperion::COMP_COLOR) + { clear(priority); + } // register color registerInput(priority, hyperion::COMP_COLOR, origin); - // write color to muxer & queuePush + // write color to muxer setInput(priority, newLedColors, timeout_ms); - if (timeout_ms <= 0) - _muxer.queuePush(); } QStringList Hyperion::getAdjustmentIds() const @@ -415,13 +433,14 @@ void Hyperion::adjustmentsUpdated() bool Hyperion::clear(int priority, bool forceClearAll) { + bool isCleared = false; if (priority < 0) { _muxer.clearAll(forceClearAll); // send clearall signal to the effect engine _effectEngine->allChannelsCleared(); - return true; + isCleared = true; } else { @@ -430,9 +449,11 @@ bool Hyperion::clear(int priority, bool forceClearAll) _effectEngine->channelCleared(priority); if (_muxer.clearInput(priority)) - return true; + { + isCleared = true; + } } - return false; + return isCleared; } int Hyperion::getCurrentPriority() const @@ -533,9 +554,9 @@ void Hyperion::handleVisibleComponentChanged(hyperion::Components comp) void Hyperion::handlePriorityChangedLedDevice(const quint8& priority) { - quint8 previousPriority = _muxer.getPreviousPriority(); + int previousPriority = _muxer.getPreviousPriority(); - Debug(_log,"priority[%u], previousPriority[%u]", priority, previousPriority); + Debug(_log,"priority[%d], previousPriority[%d]", priority, previousPriority); if ( priority == PriorityMuxer::LOWEST_PRIORITY) { Debug(_log,"No source left -> switch LED-Device off"); @@ -605,8 +626,8 @@ void Hyperion::update() i++; } - // fill additional hw leds with black - if ( _hwLedCount > _ledBuffer.size() ) + // fill additional hardware LEDs with black + if ( _hwLedCount > static_cast(_ledBuffer.size()) ) { _ledBuffer.resize(_hwLedCount, ColorRgb::BLACK); } @@ -624,16 +645,18 @@ void Hyperion::update() { _deviceSmooth->selectConfig(priorityInfo.smooth_cfg); - // feed smoothing in pause mode to maintain a smooth transistion back to smooth mode + // feed smoothing in pause mode to maintain a smooth transition back to smooth mode if (_deviceSmooth->enabled() || _deviceSmooth->pause()) { _deviceSmooth->updateLedValues(_ledBuffer); } } } - //else - //{ - // /LEDDevice is disabled - // Debug(_log, "LEDDevice is disabled - no update required"); - //} + #if 0 + else + { + //LEDDevice is disabled + Debug(_log, "LEDDevice is disabled - no update required"); + } + #endif } diff --git a/libsrc/hyperion/HyperionIManager.cpp b/libsrc/hyperion/HyperionIManager.cpp index 9613af25..307bd073 100644 --- a/libsrc/hyperion/HyperionIManager.cpp +++ b/libsrc/hyperion/HyperionIManager.cpp @@ -9,11 +9,12 @@ HyperionIManager* HyperionIManager::HIMinstance; -HyperionIManager::HyperionIManager(const QString& rootPath, QObject* parent) +HyperionIManager::HyperionIManager(const QString& rootPath, QObject* parent, bool readonlyMode) : QObject(parent) , _log(Logger::getInstance("HYPERION")) - , _instanceTable( new InstanceTable(rootPath, this) ) + , _instanceTable( new InstanceTable(rootPath, this, readonlyMode) ) , _rootPath( rootPath ) + , _readonlyMode(readonlyMode) { HIMinstance = this; qRegisterMetaType("InstanceState"); @@ -75,7 +76,7 @@ bool HyperionIManager::startInstance(quint8 inst, bool block, QObject* caller, i { QThread* hyperionThread = new QThread(); hyperionThread->setObjectName("HyperionThread"); - Hyperion* hyperion = new Hyperion(inst); + Hyperion* hyperion = new Hyperion(inst, _readonlyMode); hyperion->moveToThread(hyperionThread); // setup thread management connect(hyperionThread, &QThread::started, hyperion, &Hyperion::start); diff --git a/libsrc/hyperion/LedString.cpp b/libsrc/hyperion/LedString.cpp index 6ad66cf7..bf9f7528 100644 --- a/libsrc/hyperion/LedString.cpp +++ b/libsrc/hyperion/LedString.cpp @@ -5,16 +5,6 @@ // hyperion includes #include -LedString::LedString() -{ - // empty -} - -LedString::~LedString() -{ - // empty -} - std::vector& LedString::leds() { return mLeds; diff --git a/libsrc/hyperion/LinearColorSmoothing.cpp b/libsrc/hyperion/LinearColorSmoothing.cpp index 1583f61e..0af1128f 100644 --- a/libsrc/hyperion/LinearColorSmoothing.cpp +++ b/libsrc/hyperion/LinearColorSmoothing.cpp @@ -6,27 +6,63 @@ #include #include +#include +#include + +/// The number of microseconds per millisecond = 1000. +const int64_t MS_PER_MICRO = 1000; + +#if defined(COMPILER_GCC) +#define ALWAYS_INLINE inline __attribute__((__always_inline__)) +#elif defined(COMPILER_MSVC) +#define ALWAYS_INLINE __forceinline +#else +#define ALWAYS_INLINE inline +#endif + +/// Clamps the rounded values to the byte-interval of [0, 255]. +ALWAYS_INLINE long clampRounded(const floatT x) { + return std::min(255L, std::max(0L, std::lroundf(x))); +} + +/// The number of bits that are used for shifting the fixed point values +const int FPShift = (sizeof(uint64_t)*8 - (12 + 9)); + +/// The number of bits that are reduce the shifting when converting from fixed to floating point. 8 bits = 256 values +const int SmallShiftBis = sizeof(uint8_t)*8; + +/// The number of bits that are used for shifting the fixed point values plus SmallShiftBis +const int FPShiftSmall = (sizeof(uint64_t)*8 - (12 + 9 + SmallShiftBis)); + +const char* SETTINGS_KEY_SMOOTHING_TYPE = "type"; +const char* SETTINGS_KEY_INTERPOLATION_RATE = "interpolationRate"; +const char* SETTINGS_KEY_OUTPUT_RATE = "outputRate"; +const char* SETTINGS_KEY_DITHERING = "dithering"; +const char* SETTINGS_KEY_DECAY = "decay"; using namespace hyperion; -const int64_t DEFAUL_SETTLINGTIME = 200; // settlingtime in ms -const double DEFAUL_UPDATEFREQUENCY = 25; // updatefrequncy in hz -const int64_t DEFAUL_UPDATEINTERVALL = static_cast(1000 / DEFAUL_UPDATEFREQUENCY); // updateintervall in ms -const unsigned DEFAUL_OUTPUTDEPLAY = 0; // outputdelay in ms +const int64_t DEFAUL_SETTLINGTIME = 200; // settlingtime in ms +const int DEFAUL_UPDATEFREQUENCY = 25; // updatefrequncy in hz -LinearColorSmoothing::LinearColorSmoothing(const QJsonDocument& config, Hyperion* hyperion) +constexpr std::chrono::milliseconds DEFAUL_UPDATEINTERVALL{1000/ DEFAUL_UPDATEFREQUENCY}; +const unsigned DEFAUL_OUTPUTDEPLAY = 0; // outputdelay in ms + +LinearColorSmoothing::LinearColorSmoothing(const QJsonDocument &config, Hyperion *hyperion) : QObject(hyperion) , _log(Logger::getInstance("SMOOTHING")) , _hyperion(hyperion) - , _updateInterval(DEFAUL_UPDATEINTERVALL) + , _updateInterval(DEFAUL_UPDATEINTERVALL.count()) , _settlingTime(DEFAUL_SETTLINGTIME) , _timer(new QTimer(this)) , _outputDelay(DEFAUL_OUTPUTDEPLAY) + , _smoothingType(SmoothingType::Linear) , _writeToLedsEnable(false) , _continuousOutput(false) , _pause(false) , _currentConfigId(0) , _enabled(false) + , tempValues(std::vector(0, 0L)) { // init cfg 0 (default) addConfig(DEFAUL_SETTLINGTIME, DEFAUL_UPDATEFREQUENCY, DEFAUL_OUTPUTDEPLAY); @@ -34,38 +70,56 @@ LinearColorSmoothing::LinearColorSmoothing(const QJsonDocument& config, Hyperion selectConfig(0, true); // add pause on cfg 1 - SMOOTHING_CFG cfg = {true, 0, 0, 0}; + SMOOTHING_CFG cfg = {SmoothingType::Linear, false, 0, 0, 0, 0, 0, false, 1}; _cfgList.append(cfg); // listen for comp changes connect(_hyperion, &Hyperion::compStateChangeRequest, this, &LinearColorSmoothing::componentStateChange); // timer connect(_timer, &QTimer::timeout, this, &LinearColorSmoothing::updateLeds); + + //Debug(_log, "LinearColorSmoothing sizeof floatT == %d", (sizeof(floatT))); } -void LinearColorSmoothing::handleSettingsUpdate(settings::type type, const QJsonDocument& config) +void LinearColorSmoothing::handleSettingsUpdate(settings::type type, const QJsonDocument &config) { - if(type == settings::SMOOTHING) + if (type == settings::SMOOTHING) { // std::cout << "LinearColorSmoothing::handleSettingsUpdate" << std::endl; // std::cout << config.toJson().toStdString() << std::endl; QJsonObject obj = config.object(); - if(enabled() != obj["enable"].toBool(true)) + if (enabled() != obj["enable"].toBool(true)) + { setEnable(obj["enable"].toBool(true)); + } _continuousOutput = obj["continuousOutput"].toBool(true); - SMOOTHING_CFG cfg = {false, - static_cast(obj["time_ms"].toInt(DEFAUL_SETTLINGTIME)), - static_cast(1000.0/obj["updateFrequency"].toDouble(DEFAUL_UPDATEFREQUENCY)), - static_cast(obj["updateDelay"].toInt(DEFAUL_OUTPUTDEPLAY)) - }; + SMOOTHING_CFG cfg = {SmoothingType::Linear,true, 0, 0, 0, 0, 0, false, 1}; + + const QString typeString = obj[SETTINGS_KEY_SMOOTHING_TYPE].toString(); + + if(typeString == "linear") { + cfg.smoothingType = SmoothingType::Linear; + } else if(typeString == "decay") { + cfg.smoothingType = SmoothingType::Decay; + } + + cfg.pause = false; + cfg.settlingTime = static_cast(obj["time_ms"].toInt(DEFAUL_SETTLINGTIME)); + cfg.updateInterval = static_cast(1000.0 / obj["updateFrequency"].toDouble(DEFAUL_UPDATEFREQUENCY)); + cfg.outputRate = obj[SETTINGS_KEY_OUTPUT_RATE].toDouble(DEFAUL_UPDATEFREQUENCY); + cfg.interpolationRate = obj[SETTINGS_KEY_INTERPOLATION_RATE].toDouble(DEFAUL_UPDATEFREQUENCY); + cfg.outputDelay = static_cast(obj["updateDelay"].toInt(DEFAUL_OUTPUTDEPLAY)); + cfg.dithering = obj[SETTINGS_KEY_DITHERING].toBool(false); + cfg.decay = obj[SETTINGS_KEY_DECAY].toDouble(1.0); + //Debug( _log, "smoothing cfg_id %d: pause: %d bool, settlingTime: %d ms, interval: %d ms (%u Hz), updateDelay: %u frames", _currentConfigId, cfg.pause, cfg.settlingTime, cfg.updateInterval, unsigned(1000.0/cfg.updateInterval), cfg.outputDelay ); _cfgList[0] = cfg; // if current id is 0, we need to apply the settings (forced) - if( _currentConfigId == 0) + if (_currentConfigId == 0) { //Debug( _log, "_currentConfigId == 0"); selectConfig(0, true); @@ -79,15 +133,18 @@ void LinearColorSmoothing::handleSettingsUpdate(settings::type type, const QJson int LinearColorSmoothing::write(const std::vector &ledValues) { - _targetTime = QDateTime::currentMSecsSinceEpoch() + _settlingTime; + _targetTime = micros() + (MS_PER_MICRO * _settlingTime); _targetValues = ledValues; + rememberFrame(ledValues); + // received a new target color if (_previousValues.empty()) { // not initialized yet - _previousTime = QDateTime::currentMSecsSinceEpoch(); + _previousWriteTime = micros(); _previousValues = ledValues; + _previousInterpolationTime = micros(); //Debug( _log, "Start Smoothing timer: settlingTime: %d ms, interval: %d ms (%u Hz), updateDelay: %u frames", _settlingTime, _updateInterval, unsigned(1000.0/_updateInterval), _outputDelay ); QMetaObject::invokeMethod(_timer, "start", Qt::QueuedConnection, Q_ARG(int, _updateInterval)); @@ -96,12 +153,12 @@ int LinearColorSmoothing::write(const std::vector &ledValues) return 0; } -int LinearColorSmoothing::updateLedValues(const std::vector& ledValues) +int LinearColorSmoothing::updateLedValues(const std::vector &ledValues) { int retval = 0; if (!_enabled) { - return -1; + retval = -1; } else { @@ -110,75 +167,368 @@ int LinearColorSmoothing::updateLedValues(const std::vector& ledValues return retval; } -void LinearColorSmoothing::updateLeds() +void LinearColorSmoothing::intitializeComponentVectors(const size_t ledCount) { - int64_t now = QDateTime::currentMSecsSinceEpoch(); - int64_t deltaTime = _targetTime - now; - - //Debug(_log, "elapsed Time [%d], _targetTime [%d] - now [%d], deltaTime [%d]", now -_previousTime, _targetTime, now, deltaTime); - if (deltaTime < 0) + // (Re-)Initialize the color-vectors that store the Mean-Value + if (_ledCount != ledCount) { - _previousValues = _targetValues; - _previousTime = now; + _ledCount = ledCount; - queueColors(_previousValues); - _writeToLedsEnable = _continuousOutput; + const size_t len = 3 * ledCount; + + meanValues = std::vector(len, 0.0F); + residualErrors = std::vector(len, 0.0F); + tempValues = std::vector(len, 0L); } - else + + // Zero the temp vector + std::fill(tempValues.begin(), tempValues.end(), 0L); +} + +void LinearColorSmoothing::writeDirect() +{ + const int64_t now = micros(); + _previousValues = _targetValues; + _previousWriteTime = now; + + queueColors(_previousValues); + _writeToLedsEnable = _continuousOutput; +} + + +void LinearColorSmoothing::writeFrame() +{ + const int64_t now = micros(); + _previousWriteTime = now; + queueColors(_previousValues); + _writeToLedsEnable = _continuousOutput; +} + + +ALWAYS_INLINE int64_t LinearColorSmoothing::micros() const +{ + const auto now = std::chrono::high_resolution_clock::now(); + return (std::chrono::duration_cast(now.time_since_epoch())).count(); +} + +void LinearColorSmoothing::assembleAndDitherFrame() +{ + if (meanValues.empty()) { - _writeToLedsEnable = true; + return; + } - //std::cout << "LinearColorSmoothing::updateLeds> _previousValues: "; LedDevice::printLedValues ( _previousValues ); + // The number of leds present in each frame + const size_t N = _targetValues.size(); - float k = 1.0f - 1.0f * deltaTime / (_targetTime - _previousTime); + for (size_t i = 0; i < N; ++i) + { + // Add residuals for error diffusion (temporal dithering) + const floatT fr = meanValues[3 * i + 0] + residualErrors[3 * i + 0]; + const floatT fg = meanValues[3 * i + 1] + residualErrors[3 * i + 1]; + const floatT fb = meanValues[3 * i + 2] + residualErrors[3 * i + 2]; - int reddif = 0, greendif = 0, bluedif = 0; + // Convert to to 8-bit value + const long ir = clampRounded(fr); + const long ig = clampRounded(fg); + const long ib = clampRounded(fb); - for (size_t i = 0; i < _previousValues.size(); ++i) - { - ColorRgb & prev = _previousValues[i]; - ColorRgb & target = _targetValues[i]; + // Update the colors + ColorRgb &prev = _previousValues[i]; + prev.red = static_cast(ir); + prev.green = static_cast(ig); + prev.blue = static_cast(ib); - reddif = target.red - prev.red; - greendif = target.green - prev.green; - bluedif = target.blue - prev.blue; - - prev.red += (reddif < 0 ? -1:1) * std::ceil(k * std::abs(reddif)); - prev.green += (greendif < 0 ? -1:1) * std::ceil(k * std::abs(greendif)); - prev.blue += (bluedif < 0 ? -1:1) * std::ceil(k * std::abs(bluedif)); - } - _previousTime = now; - - //std::cout << "LinearColorSmoothing::updateLeds> _targetValues: "; LedDevice::printLedValues ( _targetValues ); - - queueColors(_previousValues); + // Determine the component errors + residualErrors[3 * i + 0] = fr - ir; + residualErrors[3 * i + 1] = fg - ig; + residualErrors[3 * i + 2] = fb - ib; } } -void LinearColorSmoothing::queueColors(const std::vector & ledColors) +void LinearColorSmoothing::assembleFrame() +{ + if (meanValues.empty()) + { + return; + } + + // The number of leds present in each frame + const size_t N = _targetValues.size(); + + for (size_t i = 0; i < N; ++i) + { + // Convert to to 8-bit value + const long ir = clampRounded(meanValues[3 * i + 0]); + const long ig = clampRounded(meanValues[3 * i + 1]); + const long ib = clampRounded(meanValues[3 * i + 2]); + + // Update the colors + ColorRgb &prev = _previousValues[i]; + prev.red = static_cast(ir); + prev.green = static_cast(ig); + prev.blue = static_cast(ib); + } +} + +ALWAYS_INLINE void LinearColorSmoothing::aggregateComponents(const std::vector& colors, std::vector& weighted, const floatT weight) { + // Determine the integer-scale by converting the weight to fixed point + const uint64_t scale = (static_cast(1L)<(weight); + + const size_t N = colors.size(); + + for (size_t i = 0; i < N; ++i) + { + const ColorRgb &color = colors[i]; + + // Scale the colors + const uint64_t red = scale * color.red; + const uint64_t green = scale * color.green; + const uint64_t blue = scale * color.blue; + + // Accumulate in the vector + weighted[3 * i + 0] += red; + weighted[3 * i + 1] += green; + weighted[3 * i + 2] += blue; + } +} + +void LinearColorSmoothing::interpolateFrame() +{ + const int64_t now = micros(); + + // The number of leds present in each frame + const size_t N = _targetValues.size(); + + intitializeComponentVectors(N); + + /// Time where the frame has been shown + int64_t frameStart; + + /// Time where the frame display would have ended + int64_t frameEnd = now; + + /// Time where the current window has started + const int64_t windowStart = now - (MS_PER_MICRO * _settlingTime); + + /// The total weight of the frames that were included in our window; sum of the individual weights + floatT fs = 0.0F; + + // To calculate the mean component we iterate over all relevant frames; + // from the most recent to the oldest frame that still clips our moving-average window given by time (now) + for (auto it = _frameQueue.rbegin(); it != _frameQueue.rend() && frameEnd > windowStart; ++it) + { + // Starting time of a frame in the window is clipped to the window start + frameStart = std::max(windowStart, it->time); + + // Weight the current frame relative to the overall window based on start and end times + const floatT weight = _weightFrame(frameStart, frameEnd, windowStart); + fs += weight; + + // Aggregate the RGB components of this frame's LED colors using the individual weighting + aggregateComponents(it->colors, tempValues, weight); + + // The previous (earlier) frame display has ended when the current frame stared to show, + // so we can use this as the frame-end time for next iteration + frameEnd = frameStart; + } + + /// The inverse scaling factor for the color components, clamped to (0, 1.0]; 1.0 for fs < 1, 1 : fs otherwise + const floatT inv_fs = ((fs < 1.0F) ? 1.0F : 1.0F / fs) / (1 << SmallShiftBis); + + // Normalize the mean component values for the window (fs) + for (size_t i = 0; i < 3 * N; ++i) + { + meanValues[i] = (tempValues[i] >> FPShiftSmall) * inv_fs; + } + + _previousInterpolationTime = now; +} + +void LinearColorSmoothing::performDecay(const int64_t now) { + /// The target time when next frame interpolation should be performed + const int64_t interpolationTarget = _previousInterpolationTime + _interpolationIntervalMicros; + + /// The target time when next write operation should be performed + const int64_t writeTarget = _previousWriteTime + _outputIntervalMicros; + + /// Whether a frame interpolation is pending + const bool interpolatePending = now > interpolationTarget; + + /// Whether a write is pending + const bool writePending = now > writeTarget; + + // Check whether a new interpolation frame is due + if (interpolatePending) + { + interpolateFrame(); + ++_interpolationCounter; + + // Assemble the frame now when no dithering is applied + if(!_dithering) { + assembleFrame(); + } + } + + // Check whether to frame output is due + if (writePending) + { + // Dither the frame to diffuse rounding errors + if(_dithering) { + assembleAndDitherFrame(); + } + + writeFrame(); + ++_renderedCounter; + } + + // Check for sleep when no operation is pending. + // As our QTimer is not capable of sub 1ms timing but instead performs spinning - + // we have to do µsec-sleep to free CPU time; otherwise the thread would consume 100% CPU time. + if(_updateInterval <= 0 && !(interpolatePending || writePending)) { + const int64_t nextActionExpected = std::min(interpolationTarget, writeTarget); + const int64_t microsTillNextAction = nextActionExpected - now; + const int64_t SLEEP_MAX_MICROS = 1000L; // We want to use usleep for up to 1ms + const int64_t SLEEP_RES_MICROS = 100L; // Expected resolution is >= 100µs on stock linux + + if(microsTillNextAction > SLEEP_RES_MICROS) { + const int64_t wait = std::min(microsTillNextAction - SLEEP_RES_MICROS, SLEEP_MAX_MICROS); + //usleep(wait); + std::this_thread::sleep_for(std::chrono::microseconds(wait)); + } + } + + // Write stats every 30 sec + if ((now > (_renderedStatTime + 30 * 1000000)) && (_renderedCounter > _renderedStatCounter)) + { + Debug(_log, "decay - rendered frames [%d] (%f/s), interpolated frames [%d] (%f/s) in [%f ms]" + , _renderedCounter - _renderedStatCounter + , (1.0F * (_renderedCounter - _renderedStatCounter) / ((now - _renderedStatTime) / 1000000.0F)) + , _interpolationCounter - _interpolationStatCounter + , (1.0F * (_interpolationCounter - _interpolationStatCounter) / ((now - _renderedStatTime) / 1000000.0F)) + , (now - _renderedStatTime) / 1000.0F + ); + _renderedStatTime = now; + _renderedStatCounter = _renderedCounter; + _interpolationStatCounter = _interpolationCounter; + } +} + +void LinearColorSmoothing::performLinear(const int64_t now) { + const int64_t deltaTime = _targetTime - now; + const float k = 1.0F - 1.0F * deltaTime / (_targetTime - _previousWriteTime); + const size_t N = _previousValues.size(); + + for (size_t i = 0; i < N; ++i) + { + const ColorRgb &target = _targetValues[i]; + ColorRgb &prev = _previousValues[i]; + + const int reddif = target.red - prev.red; + const int greendif = target.green - prev.green; + const int bluedif = target.blue - prev.blue; + + prev.red += (reddif < 0 ? -1:1) * std::ceil(k * std::abs(reddif)); + prev.green += (greendif < 0 ? -1:1) * std::ceil(k * std::abs(greendif)); + prev.blue += (bluedif < 0 ? -1:1) * std::ceil(k * std::abs(bluedif)); + } + + writeFrame(); +} + +void LinearColorSmoothing::updateLeds() +{ + const int64_t now = micros(); + const int64_t deltaTime = _targetTime - now; + + //Debug(_log, "elapsed Time [%d], _targetTime [%d] - now [%d], deltaTime [%d]", now -_previousWriteTime, _targetTime, now, deltaTime); + if (deltaTime < 0) + { + writeDirect(); + return; + } + + switch (_smoothingType) + { + case Decay: + performDecay(now); + break; + + case Linear: + // Linear interpolation is default + default: + performLinear(now); + break; + } +} + +void LinearColorSmoothing::rememberFrame(const std::vector &ledColors) +{ + //Debug(_log, "rememberFrame - before _frameQueue.size() [%d]", _frameQueue.size()); + + const int64_t now = micros(); + + // Maintain the queue by removing outdated frames + const int64_t windowStart = now - (MS_PER_MICRO * _settlingTime); + + int p = -1; // Start with -1 instead of 0, so we keep the last frame at least partially clipping the window + + // As the frames are ordered chronologically we scan from the front (oldest) till we find the first fresh frame + for (auto it = _frameQueue.begin(); it != _frameQueue.end() && it->time < windowStart; ++it) + { + ++p; + } + + if (p > 0) + { + //Debug(_log, "rememberFrame - erasing %d frames", p); + _frameQueue.erase(_frameQueue.begin(), _frameQueue.begin() + p); + } + + // Append the latest frame at back of the queue + const REMEMBERED_FRAME frame = REMEMBERED_FRAME(now, ledColors); + _frameQueue.push_back(frame); + + //Debug(_log, "rememberFrame - after _frameQueue.size() [%d]", _frameQueue.size()); +} + + +void LinearColorSmoothing::clearRememberedFrames() +{ + _frameQueue.clear(); + + _ledCount = 0; + meanValues.clear(); + residualErrors.clear(); + tempValues.clear(); +} + +void LinearColorSmoothing::queueColors(const std::vector &ledColors) { //Debug(_log, "queueColors - _outputDelay[%d] _outputQueue.size() [%d], _writeToLedsEnable[%d]", _outputDelay, _outputQueue.size(), _writeToLedsEnable); if (_outputDelay == 0) { // No output delay => immediate write - if ( _writeToLedsEnable && !_pause) + if (_writeToLedsEnable && !_pause) { -// if ( ledColors.size() == 0 ) -// qFatal ("No LedValues! - in LinearColorSmoothing::queueColors() - _outputDelay == 0"); -// else + // if ( ledColors.size() == 0 ) + // qFatal ("No LedValues! - in LinearColorSmoothing::queueColors() - _outputDelay == 0"); + // else emit _hyperion->ledDeviceData(ledColors); } } else { // Push new colors in the delay-buffer - if ( _writeToLedsEnable ) + if (_writeToLedsEnable) + { _outputQueue.push_back(ledColors); + } // If the delay-buffer is filled pop the front and write to device - if (_outputQueue.size() > 0 ) + if (!_outputQueue.empty()) { - if ( _outputQueue.size() > _outputDelay || !_writeToLedsEnable ) + if (_outputQueue.size() > _outputDelay || !_writeToLedsEnable) { if (!_pause) { @@ -196,17 +546,19 @@ void LinearColorSmoothing::clearQueuedColors() _previousValues.clear(); _targetValues.clear(); + + clearRememberedFrames(); } void LinearColorSmoothing::componentStateChange(hyperion::Components component, bool state) { _writeToLedsEnable = state; - if(component == hyperion::COMP_LEDDEVICE) + if (component == hyperion::COMP_LEDDEVICE) { clearQueuedColors(); } - if(component == hyperion::COMP_SMOOTHING) + if (component == hyperion::COMP_SMOOTHING) { setEnable(state); } @@ -230,7 +582,17 @@ void LinearColorSmoothing::setPause(bool pause) unsigned LinearColorSmoothing::addConfig(int settlingTime_ms, double ledUpdateFrequency_hz, unsigned updateDelay) { - SMOOTHING_CFG cfg = {false, settlingTime_ms, int64_t(1000.0/ledUpdateFrequency_hz), updateDelay}; + SMOOTHING_CFG cfg = { + SmoothingType::Linear, + false, + settlingTime_ms, + static_cast(1000.0 / ledUpdateFrequency_hz), + ledUpdateFrequency_hz, + ledUpdateFrequency_hz, + updateDelay, + false, + 1 + }; _cfgList.append(cfg); //Debug( _log, "smoothing cfg %d: pause: %d bool, settlingTime: %d ms, interval: %d ms (%u Hz), updateDelay: %u frames", _cfgList.count()-1, cfg.pause, cfg.settlingTime, cfg.updateInterval, unsigned(1000.0/cfg.updateInterval), cfg.outputDelay ); @@ -240,17 +602,26 @@ unsigned LinearColorSmoothing::addConfig(int settlingTime_ms, double ledUpdateFr unsigned LinearColorSmoothing::updateConfig(unsigned cfgID, int settlingTime_ms, double ledUpdateFrequency_hz, unsigned updateDelay) { unsigned updatedCfgID = cfgID; - if ( cfgID < static_cast(_cfgList.count()) ) + if (cfgID < static_cast(_cfgList.count())) { - SMOOTHING_CFG cfg = {false, settlingTime_ms, int64_t(1000.0/ledUpdateFrequency_hz), updateDelay}; + SMOOTHING_CFG cfg = { + SmoothingType::Linear, + false, + settlingTime_ms, + static_cast(1000.0 / ledUpdateFrequency_hz), + ledUpdateFrequency_hz, + ledUpdateFrequency_hz, + updateDelay, + false, + 1}; _cfgList[updatedCfgID] = cfg; } else { - updatedCfgID = addConfig ( settlingTime_ms, ledUpdateFrequency_hz, updateDelay); + updatedCfgID = addConfig(settlingTime_ms, ledUpdateFrequency_hz, updateDelay); } -// Debug( _log, "smoothing updatedCfgID %u: settlingTime: %d ms, " -// "interval: %d ms (%u Hz), updateDelay: %u frames", cfgID, _settlingTime, int64_t(1000.0/ledUpdateFrequency_hz), unsigned(ledUpdateFrequency_hz), updateDelay ); + // Debug( _log, "smoothing updatedCfgID %u: settlingTime: %d ms, " + // "interval: %d ms (%u Hz), updateDelay: %u frames", cfgID, _settlingTime, int64_t(1000.0/ledUpdateFrequency_hz), unsigned(ledUpdateFrequency_hz), updateDelay ); return updatedCfgID; } @@ -264,18 +635,54 @@ bool LinearColorSmoothing::selectConfig(unsigned cfg, bool force) } //Debug( _log, "selectConfig FORCED - _currentConfigId [%u], force [%d]", cfg, force); - if ( cfg < (unsigned)_cfgList.count()) + if (cfg < static_cast(_cfgList.count()) ) { - _settlingTime = _cfgList[cfg].settlingTime; - _outputDelay = _cfgList[cfg].outputDelay; - _pause = _cfgList[cfg].pause; + _smoothingType = _cfgList[cfg].smoothingType; + _settlingTime = _cfgList[cfg].settlingTime; + _outputDelay = _cfgList[cfg].outputDelay; + _pause = _cfgList[cfg].pause; + _outputRate = _cfgList[cfg].outputRate; + _outputIntervalMicros = int64_t(1000000.0 / _outputRate); // 1s = 1e6 µs + _interpolationRate = _cfgList[cfg].interpolationRate; + _interpolationIntervalMicros = int64_t(1000000.0 / _interpolationRate); + _dithering = _cfgList[cfg].dithering; + _decay = _cfgList[cfg].decay; + _invWindow = 1.0F / (MS_PER_MICRO * _settlingTime); + + // Set _weightFrame based on the given decay + const float decay = _decay; + const floatT inv_window = _invWindow; + + // For decay != 1 use power-based approach for calculating the moving average values + if(std::abs(decay - 1.0F) > std::numeric_limits::epsilon()) { + // Exponential Decay + _weightFrame = [inv_window,decay](const int64_t fs, const int64_t fe, const int64_t ws) { + const floatT s = (fs - ws) * inv_window; + const floatT t = (fe - ws) * inv_window; + + return (decay + 1) * (std::pow(t, decay) - std::pow(s, decay)); + }; + } else { + // For decay == 1 use linear interpolation of the moving average values + // Linear Decay + _weightFrame = [inv_window](const int64_t fs, const int64_t fe, const int64_t /*ws*/) { + // Linear weighting = (end - start) * scale + return static_cast((fe - fs) * inv_window); + }; + } + + _renderedStatTime = micros(); + _renderedCounter = 0; + _renderedStatCounter = 0; + _interpolationCounter = 0; + _interpolationStatCounter = 0; if (_cfgList[cfg].updateInterval != _updateInterval) { QMetaObject::invokeMethod(_timer, "stop", Qt::QueuedConnection); _updateInterval = _cfgList[cfg].updateInterval; - if ( this->enabled() && this->_writeToLedsEnable ) + if (this->enabled() && this->_writeToLedsEnable) { //Debug( _log, "_cfgList[cfg].updateInterval != _updateInterval - Restart timer - _updateInterval [%d]", _updateInterval); QMetaObject::invokeMethod(_timer, "start", Qt::QueuedConnection, Q_ARG(int, _updateInterval)); @@ -290,6 +697,9 @@ bool LinearColorSmoothing::selectConfig(unsigned cfg, bool force) // DebugIf( enabled() && !_pause, _log, "set smoothing cfg: %u settlingTime: %d ms, interval: %d ms, updateDelay: %u frames", _currentConfigId, _settlingTime, _updateInterval, _outputDelay ); // DebugIf( _pause, _log, "set smoothing cfg: %d, pause", _currentConfigId ); + const float thalf = (1.0-std::pow(1.0/2, 1.0/_decay))*_settlingTime; + Debug( _log, "cfg [%d]: Type: %s - Time: %d ms, outputRate %f Hz, interpolationRate: %f Hz, timer: %d ms, Dithering: %d, Decay: %f -> HalfTime: %f ms", cfg, _smoothingType == SmoothingType::Decay ? "decay" : "linear", _settlingTime, _outputRate, _interpolationRate, _updateInterval, _dithering ? 1 : 0, _decay, thalf); + return true; } diff --git a/libsrc/hyperion/LinearColorSmoothing.h b/libsrc/hyperion/LinearColorSmoothing.h index c3472999..d9f2d355 100644 --- a/libsrc/hyperion/LinearColorSmoothing.h +++ b/libsrc/hyperion/LinearColorSmoothing.h @@ -2,25 +2,74 @@ // STL includes #include +#include // Qt includes #include -// hyperion incluse +// hyperion includes #include #include // settings #include +// The type of float +#define floatT float // Select double, float or __fp16 + class QTimer; class Logger; class Hyperion; -/// Linear Smooting class +/// The type of smoothing to perform +enum SmoothingType { + /// "Linear" smoothing algorithm + Linear, + + /// Decay based smoothing algorithm + Decay, +}; + +/// Linear Smoothing class /// /// This class processes the requested led values and forwards them to the device after applying -/// a linear smoothing effect. This class can be handled as a generic LedDevice. +/// a smoothing effect to LED colors. This class can be handled as a generic LedDevice. +/// +/// Currently, two types of smoothing are supported: +/// +/// - Linear: A linear smoothing effect that interpolates the previous to the target colors. +/// - Decay: A temporal smoothing effect that uses a decay based algorithm that interpolates +/// colors based on the age of previous frames and a given decay-power. +/// +/// The smoothing is performed on a history of relevant LED-color frames that are +/// incorporated in the smoothing window (given by the configured settling time). +/// +/// For each moment, all ingress frames that were received during the smoothing window +/// are reduced to the concrete color values using a weighted moving average. This is +/// done by applying a decay-controlled weighting-function to individual the colors of +/// each frame. +/// +/// Decay +/// ===== +/// The decay-power influences the weight of individual frames based on their 'age'. +/// +/// * A decay value of 1 indicates linear decay. The colors are given by the moving average +/// with a weight that is strictly proportionate to the fraction of time each frame was +/// visible during the smoothing window. As a result, equidistant frames will have an +/// equal share when calculating an intermediate frame. +/// +/// * A decay value greater than 1 indicates non-linear decay. With higher powers, the +/// decay is stronger. I.e. newer frames in the smoothing window will have more influence +/// on colors of intermediate frames than older ones. +/// +/// Dithering +/// ========= +/// A temporal dithering algorithm is used to minimize rounding errors, when downsampling +/// the average color values to the 8-bit RGB resolution of the LED-device. Effectively, +/// this performs diffusion of the residual errors across multiple egress frames. +/// +/// + class LinearColorSmoothing : public QObject { Q_OBJECT @@ -30,14 +79,14 @@ public: /// @param config The configuration document smoothing /// @param hyperion The hyperion parent instance /// - LinearColorSmoothing(const QJsonDocument& config, Hyperion* hyperion); + LinearColorSmoothing(const QJsonDocument &config, Hyperion *hyperion); /// LED values as input for the smoothing filter /// /// @param ledValues The color-value per led /// @return Zero on success else negative /// - virtual int updateLedValues(const std::vector& ledValues); + virtual int updateLedValues(const std::vector &ledValues); void setEnable(bool enable); void setPause(bool pause); @@ -45,14 +94,14 @@ public: bool enabled() const { return _enabled && !_pause; } /// - /// @brief Add a new smoothing cfg which can be used with selectConfig() + /// @brief Add a new smoothing configuration which can be used with selectConfig() /// @param settlingTime_ms The buffer time /// @param ledUpdateFrequency_hz The frequency of update /// @param updateDelay The delay /// - /// @return The index of the cfg which can be passed to selectConfig() + /// @return The index of the configuration, which can be passed to selectConfig() /// - unsigned addConfig(int settlingTime_ms, double ledUpdateFrequency_hz=25.0, unsigned updateDelay=0); + unsigned addConfig(int settlingTime_ms, double ledUpdateFrequency_hz = 25.0, unsigned updateDelay = 0); /// /// @brief Update a smoothing cfg which can be used with selectConfig() @@ -63,12 +112,12 @@ public: /// @param ledUpdateFrequency_hz The frequency of update /// @param updateDelay The delay /// - /// @return The index of the cfg which can be passed to selectConfig() + /// @return The index of the configuration, which can be passed to selectConfig() /// - unsigned updateConfig(unsigned cfgID, int settlingTime_ms, double ledUpdateFrequency_hz=25.0, unsigned updateDelay=0); + unsigned updateConfig(unsigned cfgID, int settlingTime_ms, double ledUpdateFrequency_hz = 25.0, unsigned updateDelay = 0); /// - /// @brief select a smoothing cfg given by cfg index from addConfig() + /// @brief select a smoothing configuration given by cfg index from addConfig() /// @param cfg The index to use /// @param force Overwrite in any case the current values (used for cfg 0 settings update) /// @@ -79,10 +128,10 @@ public: public slots: /// /// @brief Handle settings update from Hyperion Settingsmanager emit or this constructor - /// @param type settingyType from enum + /// @param type settingType from enum /// @param config configuration object /// - void handleSettingsUpdate(settings::type type, const QJsonDocument& config); + void handleSettingsUpdate(settings::type type, const QJsonDocument &config); private slots: /// Timer callback which writes updated led values to the led device @@ -96,13 +145,12 @@ private slots: void componentStateChange(hyperion::Components component, bool state); private: - /** * Pushes the colors into the output queue and popping the head to the led-device * * @param ledColors The colors to queue */ - void queueColors(const std::vector & ledColors); + void queueColors(const std::vector &ledColors); void clearQueuedColors(); /// write updated values as input for the smoothing filter @@ -113,19 +161,19 @@ private: virtual int write(const std::vector &ledValues); /// Logger instance - Logger* _log; + Logger *_log; /// Hyperion instance - Hyperion* _hyperion; + Hyperion *_hyperion; /// The interval at which to update the leds (msec) - int64_t _updateInterval; + int _updateInterval; /// The time after which the updated led values have been fully applied (msec) int64_t _settlingTime; /// The Qt timer object - QTimer * _timer; + QTimer *_timer; /// The timestamp at which the target data should be fully applied int64_t _targetTime; @@ -134,15 +182,45 @@ private: std::vector _targetValues; /// The timestamp of the previously written led data - int64_t _previousTime; + int64_t _previousWriteTime; + + /// The timestamp of the previously data interpolation + int64_t _previousInterpolationTime; /// The previously written led data std::vector _previousValues; /// The number of updates to keep in the output queue (delayed) before being output unsigned _outputDelay; + /// The output queue - std::list > _outputQueue; + std::deque> _outputQueue; + + /// A frame of led colors used for temporal smoothing + class REMEMBERED_FRAME + { + public: + /// The time this frame was received + int64_t time; + + /// The led colors + std::vector colors; + + REMEMBERED_FRAME ( REMEMBERED_FRAME && ) = default; + REMEMBERED_FRAME ( const REMEMBERED_FRAME & ) = default; + REMEMBERED_FRAME & operator= ( const REMEMBERED_FRAME & ) = default; + + REMEMBERED_FRAME(const int64_t time, const std::vector colors) + : time(time) + , colors(colors) + {} + }; + + /// The type of smoothing to perform + SmoothingType _smoothingType; + + /// The queue of temporarily remembered frames + std::deque _frameQueue; /// Prevent sending data to device when no intput data is sent bool _writeToLedsEnable; @@ -153,17 +231,146 @@ private: /// Flag for pausing bool _pause; + /// The rate at which color frames should be written to LED device. + double _outputRate; + + /// The interval time in microseconds for writing of LED Frames. + int64_t _outputIntervalMicros; + + /// The rate at which interpolation of LED frames should be performed. + double _interpolationRate; + + /// The interval time in microseconds for interpolation of LED Frames. + int64_t _interpolationIntervalMicros; + + /// Whether to apply temporal dithering to diffuse rounding errors when downsampling to 8-bit RGB colors. + bool _dithering; + + /// The decay power > 0. A value of exactly 1 is linear decay, higher numbers indicate a faster decay rate. + double _decay; + + /// Value of 1.0 / settlingTime; inverse of the window size used for weighting of frames. + floatT _invWindow; + struct SMOOTHING_CFG { - bool pause; - int64_t settlingTime; - int64_t updateInterval; - unsigned outputDelay; - }; + /// The type of smoothing to perform + SmoothingType smoothingType; - /// smooth config list + /// Whether to pause output + bool pause; + + /// The time of the smoothing window. + int64_t settlingTime; + + /// The interval time in milliseconds of the timer used for scheduling LED update operations. A value of 0 indicates sub-millisecond timing. + int updateInterval; + + // The rate at which color frames should be written to LED device. + double outputRate; + + /// The rate at which interpolation of LED frames should be performed. + double interpolationRate; + + /// The number of frames the output is delayed + unsigned outputDelay; + + /// Whether to apply temporal dithering to diffuse rounding errors when downsampling to 8-bit RGB colors. Improves color accuracy. + bool dithering; + + /// The decay power > 0. A value of exactly 1 is linear decay, higher numbers indicate a faster decay rate. + double decay; + }; + /// smooth configuration list QVector _cfgList; unsigned _currentConfigId; - bool _enabled; + bool _enabled; + + /// Pushes the colors into the frame queue and cleans outdated frames from memory. + /// + /// @param ledColors The next colors to queue + void rememberFrame(const std::vector &ledColors); + + /// Frees the LED frames that were queued for calculating the moving average. + void clearRememberedFrames(); + + /// (Re-)Initializes the color-component vectors with given number of values. + /// + /// @param ledCount The number of colors. + void intitializeComponentVectors(const size_t ledCount); + + /// The number of led component-values that must be held per color; i.e. size of the color vectors reds / greens / blues + size_t _ledCount = 0; + + /// The average component colors red, green, blue of the leds + std::vector meanValues; + + /// The residual component errors of the leds + std::vector residualErrors; + + /// The accumulated led color values in 64-bit fixed point domain + std::vector tempValues; + + /// Writes the target frame RGB data to the LED device without any interpolation. + void writeDirect(); + + /// Writes the assembled RGB data to the LED device. + void writeFrame(); + + /// Assembles a frame of LED colors in order to write RGB data to the LED device. + /// Temporal dithering is applied to diffuse the downsampling error for RGB color components. + void assembleAndDitherFrame(); + + /// Assembles a frame of LED colors in order to write RGB data to the LED device. + /// No dithering is applied, RGB color components are just rounded to nearest integer. + void assembleFrame(); + + /// Prepares a frame of LED colors by interpolating using the current smoothing window + void interpolateFrame(); + + /// Performs a decay-based smoothing effect. The frames are interpolated based on their age and a given decay-power. + /// + /// The ingress frames that were received during the current smoothing window are reduced using a weighted moving average + /// by applying the weighting-function to the color components of each frame. + /// + /// When downsampling the average color values to the 8-bit RGB resolution of the LED device, rounding errors are minimized + /// by temporal dithering algorithm (error diffusion of residual errors). + void performDecay(const int64_t now); + + /// Performs a linear smoothing effect + void performLinear(const int64_t now); + + /// Aggregates the RGB components of the LED colors using the given weight and updates weighted accordingly + /// + /// @param colors The LED colors to aggregate. + /// @param weighted The target vector, that accumulates the terms. + /// @param weight The weight to use. + static inline void aggregateComponents(const std::vector& colors, std::vector& weighted, const floatT weight); + + /// Gets the current time in microseconds from high precision system clock. + inline int64_t micros() const; + + /// The time, when the rendering statistics were logged previously + int64_t _renderedStatTime; + + /// The total number of frames that were rendered to the LED device + int64_t _renderedCounter; + + /// The count of frames that have been rendered to the LED device when statistics were shown previously + int64_t _renderedStatCounter; + + /// The total number of frames that were interpolated using the smoothing algorithm + int64_t _interpolationCounter; + + /// The count of frames that have been interpolated when statistics were shown previously + int64_t _interpolationStatCounter; + + /// Frame weighting function for finding the frame's integral value + /// + /// @param frameStart The start of frame time. + /// @param frameEnd The end of frame time. + /// @param windowStart The window start time. + /// @returns The frame weight. + std::function _weightFrame; }; diff --git a/libsrc/hyperion/MultiColorAdjustment.cpp b/libsrc/hyperion/MultiColorAdjustment.cpp index 2ceb370e..1eca8a1e 100644 --- a/libsrc/hyperion/MultiColorAdjustment.cpp +++ b/libsrc/hyperion/MultiColorAdjustment.cpp @@ -2,7 +2,7 @@ #include #include -MultiColorAdjustment::MultiColorAdjustment(unsigned ledCnt) +MultiColorAdjustment::MultiColorAdjustment(int ledCnt) : _ledAdjustments(ledCnt, nullptr) , _log(Logger::getInstance("ADJUSTMENT")) { @@ -25,7 +25,7 @@ void MultiColorAdjustment::addAdjustment(ColorAdjustment * adjustment) _adjustment.push_back(adjustment); } -void MultiColorAdjustment::setAdjustmentForLed(const QString& id, unsigned startLed, unsigned endLed) +void MultiColorAdjustment::setAdjustmentForLed(const QString& id, int startLed, int endLed) { // abort if(startLed > endLed) @@ -34,19 +34,19 @@ void MultiColorAdjustment::setAdjustmentForLed(const QString& id, unsigned start return; } // catch wrong values - if(endLed > _ledAdjustments.size()-1) + if(endLed > static_cast(_ledAdjustments.size()-1)) { - Warning(_log,"The color calibration 'LED index' field has leds specified which aren't part of your led layout"); - endLed = _ledAdjustments.size()-1; + Warning(_log,"The color calibration 'LED index' field has LEDs specified which aren't part of your led layout"); + endLed = static_cast(_ledAdjustments.size()-1); } // Get the identified adjustment (don't care if is nullptr) ColorAdjustment * adjustment = getAdjustment(id); - //Debug(_log,"ColorAdjustment Profile [%s], startLed[%u], endLed[%u]", QSTRING_CSTR(id), startLed, endLed); - for (unsigned iLed=startLed; iLed<=endLed; ++iLed) + //Debug(_log,"ColorAdjustment Profile [%s], startLed[%d], endLed[%d]", QSTRING_CSTR(id), startLed, endLed); + for (int iLed=startLed; iLed<=endLed; ++iLed) { - //Debug(_log,"_ledAdjustments [%u] -> [%p]", iLed, adjustment); + //Debug(_log,"_ledAdjustments [%d] -> [%p]", iLed, adjustment); _ledAdjustments[iLed] = adjustment; } } diff --git a/libsrc/hyperion/PriorityMuxer.cpp b/libsrc/hyperion/PriorityMuxer.cpp index 17cfe319..4ba0189f 100644 --- a/libsrc/hyperion/PriorityMuxer.cpp +++ b/libsrc/hyperion/PriorityMuxer.cpp @@ -5,6 +5,7 @@ // qt incl #include #include +#include // Hyperion includes #include @@ -44,7 +45,6 @@ PriorityMuxer::PriorityMuxer(int ledCount, QObject * parent) // forward timeRunner signal to prioritiesChanged signal & threading workaround connect(this, &PriorityMuxer::timeRunner, this, &PriorityMuxer::prioritiesChanged); connect(this, &PriorityMuxer::signalTimeTrigger, this, &PriorityMuxer::timeTrigger); - connect(this, &PriorityMuxer::activeStateChanged, this, &PriorityMuxer::prioritiesChanged); // start muxer timer connect(_updateTimer, &QTimer::timeout, this, &PriorityMuxer::setCurrentTime); @@ -79,13 +79,12 @@ bool PriorityMuxer::setSourceAutoSelectEnabled(bool enable, bool update) if(update) setCurrentTime(); - emit autoSelectChanged(enable); return true; } return false; } -bool PriorityMuxer::setPriority(uint8_t priority) +bool PriorityMuxer::setPriority(int priority) { if(_activeInputs.contains(priority)) { @@ -142,10 +141,11 @@ hyperion::Components PriorityMuxer::getComponentOfPriority(int priority) const void PriorityMuxer::registerInput(int priority, hyperion::Components component, const QString& origin, const QString& owner, unsigned smooth_cfg) { // detect new registers - bool newInput, reusedInput = false; + bool newInput = false; + bool reusedInput = false; if (!_activeInputs.contains(priority)) newInput = true; - else + else if(_prevVisComp == component || _activeInputs[priority].componentId == component) reusedInput = true; InputInfo& input = _activeInputs[priority]; @@ -159,12 +159,16 @@ void PriorityMuxer::registerInput(int priority, hyperion::Components component, if (newInput) { Debug(_log,"Register new input '%s/%s' with priority %d as inactive", QSTRING_CSTR(origin), hyperion::componentToIdString(component), priority); - if (!_sourceAutoSelectEnabled) // emit 'prioritiesChanged' only on when _sourceAutoSelectEnabled is false + // emit 'prioritiesChanged' only if _sourceAutoSelectEnabled is false + if (!_sourceAutoSelectEnabled) emit prioritiesChanged(); return; } - if (reusedInput) emit prioritiesChanged(); + if (reusedInput) + { + emit timeRunner(); + } } bool PriorityMuxer::setInput(int priority, const std::vector& ledColors, int64_t timeout_ms) @@ -201,9 +205,13 @@ bool PriorityMuxer::setInput(int priority, const std::vector& ledColor if(activeChange) { Debug(_log, "Priority %d is now %s", priority, active ? "active" : "inactive"); - emit activeStateChanged(priority, active); + if (_currentPriority < priority) + { + emit prioritiesChanged(); + } setCurrentTime(); } + return true; } @@ -215,7 +223,7 @@ bool PriorityMuxer::setInputImage(int priority, const Image& image, in return false; } - // calc final timeout + // calculate final timeout if(timeout_ms > 0) timeout_ms = QDateTime::currentMSecsSinceEpoch() + timeout_ms; @@ -241,26 +249,30 @@ bool PriorityMuxer::setInputImage(int priority, const Image& image, in if(activeChange) { Debug(_log, "Priority %d is now %s", priority, active ? "active" : "inactive"); - emit activeStateChanged(priority, active); + if (_currentPriority < priority) + emit prioritiesChanged(); setCurrentTime(); } + return true; } -bool PriorityMuxer::setInputInactive(quint8 priority) +bool PriorityMuxer::setInputInactive(int priority) { Image image; return setInputImage(priority, image, -100); } -bool PriorityMuxer::clearInput(uint8_t priority) +bool PriorityMuxer::clearInput(int priority) { if (priority < PriorityMuxer::LOWEST_PRIORITY && _activeInputs.remove(priority)) { Debug(_log,"Removed source priority %d",priority); // on clear success update _currentPriority setCurrentTime(); - emit prioritiesChanged(); + // emit 'prioritiesChanged' only if _sourceAutoSelectEnabled is false + if (!_sourceAutoSelectEnabled || _currentPriority < priority) + emit prioritiesChanged(); return true; } return false; @@ -298,7 +310,7 @@ void PriorityMuxer::setCurrentTime() { if (infoIt->timeoutTime_ms > 0 && infoIt->timeoutTime_ms <= now) { - quint8 tPrio = infoIt->priority; + int tPrio = infoIt->priority; infoIt = _activeInputs.erase(infoIt); Debug(_log,"Timeout clear for priority %d",tPrio); emit prioritiesChanged(); @@ -316,7 +328,7 @@ void PriorityMuxer::setCurrentTime() ++infoIt; } } - // eval if manual selected prio is still available + // evaluate, if manual selected priority is still available if(!_sourceAutoSelectEnabled) { if(_activeInputs.contains(_manualSelectedPriority)) @@ -331,19 +343,20 @@ void PriorityMuxer::setCurrentTime() } } // apply & emit on change (after apply!) - if (_currentPriority != newPriority) + hyperion::Components comp = getComponentOfPriority(newPriority); + if (_currentPriority != newPriority || comp != _prevVisComp) { _previousPriority = _currentPriority; _currentPriority = newPriority; Debug(_log, "Set visible priority to %d", newPriority); emit visiblePriorityChanged(newPriority); // check for visible comp change - hyperion::Components comp = getComponentOfPriority(newPriority); if (comp != _prevVisComp) { _prevVisComp = comp; emit visibleComponentChanged(comp); } + emit prioritiesChanged(); } } diff --git a/libsrc/hyperion/SettingsManager.cpp b/libsrc/hyperion/SettingsManager.cpp index 2f4bcc8e..8a5b4a2b 100644 --- a/libsrc/hyperion/SettingsManager.cpp +++ b/libsrc/hyperion/SettingsManager.cpp @@ -14,11 +14,13 @@ QJsonObject SettingsManager::schemaJson; -SettingsManager::SettingsManager(quint8 instance, QObject* parent) +SettingsManager::SettingsManager(quint8 instance, QObject* parent, bool readonlyMode) : QObject(parent) , _log(Logger::getInstance("SETTINGSMGR")) , _sTable(new SettingsTable(instance, this)) + , _readonlyMode(readonlyMode) { + _sTable->setReadonlyMode(_readonlyMode); // get schema if(schemaJson.isEmpty()) { @@ -99,7 +101,7 @@ SettingsManager::SettingsManager(quint8 instance, QObject* parent) for (auto & schemaError : schemaChecker.getMessages()) Warning(_log, "Config Fix: %s", QSTRING_CSTR(schemaError)); - saveSettings(dbConfig); + saveSettings(dbConfig,true); } else _qconfig = dbConfig; @@ -152,18 +154,24 @@ bool SettingsManager::saveSettings(QJsonObject config, bool correct) } } + int rc = true; // compare database data with new data to emit/save changes accordingly for(const auto & key : keyList) { QString data = newValueList.takeFirst(); if(_sTable->getSettingsRecordString(key) != data) { - _sTable->createSettingsRecord(key, data); - - emit settingsChanged(settings::stringToType(key), QJsonDocument::fromJson(data.toLocal8Bit())); + if ( ! _sTable->createSettingsRecord(key, data) ) + { + rc = false; + } + else + { + emit settingsChanged(settings::stringToType(key), QJsonDocument::fromJson(data.toLocal8Bit())); + } } } - return true; + return rc; } bool SettingsManager::handleConfigUpgrade(QJsonObject& config) diff --git a/libsrc/hyperion/schema/schema-blackborderdetector.json b/libsrc/hyperion/schema/schema-blackborderdetector.json index 2b73d019..8247c61e 100644 --- a/libsrc/hyperion/schema/schema-blackborderdetector.json +++ b/libsrc/hyperion/schema/schema-blackborderdetector.json @@ -60,10 +60,10 @@ { "type" : "string", "title": "edt_conf_bb_mode_title", - "enum" : ["default", "classic", "osd"], + "enum" : ["default", "classic", "osd", "letterbox"], "default" : "default", "options" : { - "enum_titles" : ["edt_conf_enum_bbdefault", "edt_conf_enum_bbclassic", "edt_conf_enum_bbosd"] + "enum_titles" : ["edt_conf_enum_bbdefault", "edt_conf_enum_bbclassic", "edt_conf_enum_bbosd", "edt_conf_enum_bbletterbox"] }, "propertyOrder" : 7 } diff --git a/libsrc/hyperion/schema/schema-framegrabber.json b/libsrc/hyperion/schema/schema-framegrabber.json index 4ef1cfb7..aad0a8ff 100644 --- a/libsrc/hyperion/schema/schema-framegrabber.json +++ b/libsrc/hyperion/schema/schema-framegrabber.json @@ -7,13 +7,14 @@ { "type" : "string", "title" : "edt_conf_fg_type_title", - "enum" : ["auto","dispmanx","amlogic","x11", "xcb", "framebuffer","qt"], + "enum" : ["auto","amlogic","dispmanx","dx","framebuffer","osx","qt","x11", "xcb"], "options": { - "enum_titles": ["edt_conf_enum_automatic","DispmanX","AMLogic","X11", "XCB", "Framebuffer","QT"] + "enum_titles": ["edt_conf_enum_automatic","AMLogic","DispmanX","DirectX9","Framebuffer","OSX","QT","X11","XCB"] + }, "default" : "auto", - "propertyOrder" : 2 + "propertyOrder" : 1 }, "width" : { @@ -22,7 +23,7 @@ "minimum" : 10, "default" : 80, "append" : "edt_append_pixel", - "propertyOrder" : 3 + "propertyOrder" : 2 }, "height" : { @@ -49,7 +50,7 @@ "minimum" : 0, "default" : 0, "append" : "edt_append_pixel", - "propertyOrder" : 6 + "propertyOrder" : 5 }, "cropRight" : { @@ -58,7 +59,7 @@ "minimum" : 0, "default" : 0, "append" : "edt_append_pixel", - "propertyOrder" : 7 + "propertyOrder" : 6 }, "cropTop" : { @@ -67,7 +68,7 @@ "minimum" : 0, "default" : 0, "append" : "edt_append_pixel", - "propertyOrder" : 8 + "propertyOrder" : 7 }, "cropBottom" : { @@ -76,7 +77,7 @@ "minimum" : 0, "default" : 0, "append" : "edt_append_pixel", - "propertyOrder" : 9 + "propertyOrder" : 8 }, "pixelDecimation" : { @@ -85,35 +86,15 @@ "minimum" : 1, "maximum" : 30, "default" : 8, - "propertyOrder" : 10 - }, - "device" : - { - "type" : "string", - "title" : "edt_conf_fg_device_title", - "default" : "/dev/fb0", - "propertyOrder" : 11 + "propertyOrder" : 9 }, "display" : { "type" : "integer", "title" : "edt_conf_fg_display_title", "minimum" : 0, - "propertyOrder" : 12 - }, - "amlogic_grabber" : - { - "type" : "string", - "title" : "edt_conf_fg_amlogic_grabber_title", - "default" : "amvideocap0", - "propertyOrder" : 13 - }, - "ge2d_mode" : - { - "type" : "integer", - "title" : "edt_conf_fg_ge2d_mode_title", "default" : 0, - "propertyOrder" : 14 + "propertyOrder" : 10 } }, "additionalProperties" : false diff --git a/libsrc/hyperion/schema/schema-ledConfig.json b/libsrc/hyperion/schema/schema-ledConfig.json index 050fddc8..f5f58487 100644 --- a/libsrc/hyperion/schema/schema-ledConfig.json +++ b/libsrc/hyperion/schema/schema-ledConfig.json @@ -8,22 +8,22 @@ "top": { "type": "integer", "minimum": 0, - "default": 8 + "default": 1 }, "bottom": { "type": "integer", "minimum": 0, - "default": 8 + "default": 0 }, "left": { "type": "integer", "minimum": 0, - "default": 5 + "default": 0 }, "right": { "type": "integer", "minimum": 0, - "default": 5 + "default": 0 }, "glength": { "type": "integer", diff --git a/libsrc/hyperion/schema/schema-smoothing.json b/libsrc/hyperion/schema/schema-smoothing.json index 9e3f64ad..fcf0835a 100644 --- a/libsrc/hyperion/schema/schema-smoothing.json +++ b/libsrc/hyperion/schema/schema-smoothing.json @@ -14,11 +14,10 @@ { "type" : "string", "title" : "edt_conf_smooth_type_title", - "enum" : ["linear"], + "enum" : ["linear", "decay"], "default" : "linear", "options" : { - "enum_titles" : ["edt_conf_enum_linear"], - "hidden":true + "enum_titles" : ["edt_conf_enum_linear", "edt_conf_enum_decay"] }, "propertyOrder" : 2 }, @@ -27,7 +26,7 @@ "type" : "integer", "title" : "edt_conf_smooth_time_ms_title", "minimum" : 25, - "maximum": 600, + "maximum": 5000, "default" : 200, "append" : "edt_append_ms", "propertyOrder" : 3 @@ -37,11 +36,47 @@ "type" : "number", "title" : "edt_conf_smooth_updateFrequency_title", "minimum" : 1.0, - "maximum" : 100.0, + "maximum" : 2000.0, "default" : 25.0, "append" : "edt_append_hz", "propertyOrder" : 4 }, + "interpolationRate" : + { + "type" : "number", + "title" : "edt_conf_smooth_interpolationRate_title", + "minimum" : 1.0, + "maximum": 1000.0, + "default" : 1.0, + "append" : "edt_append_hz", + "propertyOrder" : 5 + }, + "outputRate" : + { + "type" : "number", + "title" : "edt_conf_smooth_outputRate_title", + "minimum" : 1.0, + "maximum": 1000.0, + "default" : 1.0, + "append" : "edt_append_hz", + "propertyOrder" : 6 + }, + "decay" : + { + "type" : "number", + "title" : "edt_conf_smooth_decay_title", + "default" : 1.0, + "minimum" : 1.0, + "maximum": 20.0, + "propertyOrder" : 7 + }, + "dithering" : + { + "type" : "boolean", + "title" : "edt_conf_smooth_dithering_title", + "default" : true, + "propertyOrder" : 8 + }, "updateDelay" : { "type" : "integer", @@ -50,14 +85,14 @@ "maximum": 2048, "default" : 0, "append" : "edt_append_ms", - "propertyOrder" : 5 + "propertyOrder" : 9 }, "continuousOutput" : { "type" : "boolean", "title" : "edt_conf_smooth_continuousOutput_title", "default" : true, - "propertyOrder" : 6 + "propertyOrder" : 10 } }, "additionalProperties" : false diff --git a/libsrc/leddevice/LedDevice.cpp b/libsrc/leddevice/LedDevice.cpp index 3d1906e9..16a1d8da 100644 --- a/libsrc/leddevice/LedDevice.cpp +++ b/libsrc/leddevice/LedDevice.cpp @@ -24,6 +24,7 @@ LedDevice::LedDevice(const QJsonObject& deviceConfig, QObject* parent) , _refreshTimer(nullptr) , _refreshTimerInterval_ms(0) , _latchTime_ms(0) + , _ledCount(0) , _isRestoreOrigState(false) , _isEnabled(false) , _isDeviceInitialised(false) @@ -148,7 +149,7 @@ bool LedDevice::init(const QJsonObject &deviceConfig) _colorOrder = deviceConfig["colorOrder"].toString("RGB"); - setLedCount( static_cast( deviceConfig["currentLedCount"].toInt(1) ) ); // property injected to reflect real led count + setLedCount( deviceConfig["currentLedCount"].toInt(1) ); // property injected to reflect real led count setLatchTime( deviceConfig["latchTime"].toInt( _latchTime_ms ) ); setRewriteTime ( deviceConfig["rewriteTime"].toInt( _refreshTimerInterval_ms) ); @@ -177,11 +178,11 @@ int LedDevice::updateLeds(const std::vector& ledValues) if ( !_isEnabled || !_isOn || !_isDeviceReady || _isDeviceInError ) { //std::cout << "LedDevice::updateLeds(), LedDevice NOT ready! "; - return -1; + 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; @@ -355,7 +356,7 @@ bool LedDevice::restoreState() return rc; } -QJsonObject LedDevice::discover() +QJsonObject LedDevice::discover(const QJsonObject& /*params*/) { QJsonObject devicesDiscovered; @@ -391,8 +392,9 @@ QJsonObject LedDevice::getProperties(const QJsonObject& params) return properties; } -void LedDevice::setLedCount(unsigned int ledCount) +void LedDevice::setLedCount(int ledCount) { + assert(ledCount >= 0); _ledCount = ledCount; _ledRGBCount = _ledCount * sizeof(ColorRgb); _ledRGBWCount = _ledCount * sizeof(ColorRgbw); @@ -400,12 +402,14 @@ void LedDevice::setLedCount(unsigned int ledCount) void LedDevice::setLatchTime( int latchTime_ms ) { + assert(latchTime_ms >= 0); _latchTime_ms = latchTime_ms; Debug(_log, "LatchTime updated to %dms", _latchTime_ms); } void LedDevice::setRewriteTime( int rewriteTime_ms ) { + assert(rewriteTime_ms >= 0); _refreshTimerInterval_ms = rewriteTime_ms; if ( _refreshTimerInterval_ms > 0 ) @@ -440,7 +444,7 @@ void LedDevice::printLedValues(const std::vector& ledValues) std::cout << "]" << std::endl; } -QString LedDevice::uint8_t_to_hex_string(const uint8_t * data, const qint64 size, qint64 number) const +QString LedDevice::uint8_t_to_hex_string(const uint8_t * data, const int size, int number) const { if ( number <= 0 || number > size) { @@ -454,3 +458,17 @@ QString LedDevice::uint8_t_to_hex_string(const uint8_t * data, const qint64 size return bytes.toHex(); #endif } + +QString LedDevice::toHex(const QByteArray& data, int number) const +{ + if ( number <= 0 || number > data.size()) + { + number = data.size(); + } + +#if (QT_VERSION >= QT_VERSION_CHECK(5, 9, 0)) + return data.left(number).toHex(':'); +#else + return data.left(number).toHex(); +#endif +} diff --git a/libsrc/leddevice/LedDeviceFactory.cpp b/libsrc/leddevice/LedDeviceFactory.cpp index 0b0690ac..636a88ec 100644 --- a/libsrc/leddevice/LedDeviceFactory.cpp +++ b/libsrc/leddevice/LedDeviceFactory.cpp @@ -36,16 +36,17 @@ LedDevice * LedDeviceFactory::construct(const QJsonObject & deviceConfig) if (device == nullptr) { - Error(log, "Dummy device used, because configured device '%s' is unknown", QSTRING_CSTR(type) ); throw std::runtime_error("unknown device"); } } catch(std::exception& e) { + QString dummyDeviceType = "file"; + Error(log, "Dummy device type (%s) used, because configured device '%s' throws error '%s'", QSTRING_CSTR(dummyDeviceType), QSTRING_CSTR(type), e.what()); - Error(log, "Dummy device used, because configured device '%s' throws error '%s'", QSTRING_CSTR(type), e.what()); - const QJsonObject dummyDeviceConfig; - device = LedDeviceFile::construct(QJsonObject()); + QJsonObject dummyDeviceConfig; + dummyDeviceConfig.insert("type",dummyDeviceType); + device = LedDeviceFile::construct(dummyDeviceConfig); } return device; diff --git a/libsrc/leddevice/LedDeviceSchemas.qrc b/libsrc/leddevice/LedDeviceSchemas.qrc index 9ed5fc6f..2718c281 100644 --- a/libsrc/leddevice/LedDeviceSchemas.qrc +++ b/libsrc/leddevice/LedDeviceSchemas.qrc @@ -36,5 +36,6 @@ schemas/schema-wled.json schemas/schema-yeelight.json schemas/schema-razer.json + schemas/schema-cololight.json diff --git a/libsrc/leddevice/LedDeviceTemplate.cpp b/libsrc/leddevice/LedDeviceTemplate.cpp index dd8ce3f4..143b7277 100644 --- a/libsrc/leddevice/LedDeviceTemplate.cpp +++ b/libsrc/leddevice/LedDeviceTemplate.cpp @@ -1,7 +1,6 @@ #include "LedDeviceTemplate.h" -LedDeviceTemplate::LedDeviceTemplate(const QJsonObject &deviceConfig) - : LedDevice() +LedDeviceTemplate::LedDeviceTemplate(const QJsonObject & /*deviceConfig*/) { } @@ -19,7 +18,7 @@ bool LedDeviceTemplate::init(const QJsonObject &deviceConfig) { // Initialise LedDevice configuration and execution environment // ... - if ( 0 /*Error during init*/) + if ( false /*Error during init*/) { //Build an errortext, illustrative QString errortext = QString ("Error message: %1").arg("errno/text"); @@ -56,7 +55,7 @@ int LedDeviceTemplate::open() } // On error/exceptions, set LedDevice in error - if ( retval < 0 ) + if ( false /* retval < 0*/ ) { this->setInError( errortext ); } diff --git a/libsrc/leddevice/LedDeviceWrapper.cpp b/libsrc/leddevice/LedDeviceWrapper.cpp index 43a35e79..8d54e205 100644 --- a/libsrc/leddevice/LedDeviceWrapper.cpp +++ b/libsrc/leddevice/LedDeviceWrapper.cpp @@ -146,8 +146,8 @@ QString LedDeviceWrapper::getColorOrder() const unsigned int LedDeviceWrapper::getLedCount() const { - unsigned int value = 0; - QMetaObject::invokeMethod(_ledDevice, "getLedCount", Qt::BlockingQueuedConnection, Q_RETURN_ARG(unsigned int, value)); + int value = 0; + QMetaObject::invokeMethod(_ledDevice, "getLedCount", Qt::BlockingQueuedConnection, Q_RETURN_ARG(int, value)); return value; } diff --git a/libsrc/leddevice/dev_hid/ProviderHID.cpp b/libsrc/leddevice/dev_hid/ProviderHID.cpp index 503be8a8..d29c0f94 100644 --- a/libsrc/leddevice/dev_hid/ProviderHID.cpp +++ b/libsrc/leddevice/dev_hid/ProviderHID.cpp @@ -190,7 +190,7 @@ void ProviderHID::unblockAfterDelay() _blockedForDelay = false; } -QJsonObject ProviderHID::discover() +QJsonObject ProviderHID::discover(const QJsonObject& /*params*/) { QJsonObject devicesDiscovered; devicesDiscovered.insert("ledDeviceType", _activeDeviceType ); diff --git a/libsrc/leddevice/dev_hid/ProviderHID.h b/libsrc/leddevice/dev_hid/ProviderHID.h index 0183a80b..5a90503d 100644 --- a/libsrc/leddevice/dev_hid/ProviderHID.h +++ b/libsrc/leddevice/dev_hid/ProviderHID.h @@ -31,9 +31,11 @@ public: /// /// @brief Discover HIB (USB) devices available (for configuration). /// + /// @param[in] params Parameters used to overwrite discovery default behaviour + /// /// @return A JSON structure holding a list of devices found /// - QJsonObject discover() override; + QJsonObject discover(const QJsonObject& params) override; protected: diff --git a/libsrc/leddevice/dev_net/LedDeviceAtmoOrb.cpp b/libsrc/leddevice/dev_net/LedDeviceAtmoOrb.cpp index f8c1a06d..e95f408d 100644 --- a/libsrc/leddevice/dev_net/LedDeviceAtmoOrb.cpp +++ b/libsrc/leddevice/dev_net/LedDeviceAtmoOrb.cpp @@ -55,7 +55,7 @@ bool LedDeviceAtmoOrb::init(const QJsonObject &deviceConfig) QStringList orbIds = QStringUtils::split(deviceConfig["orbIds"].toString().simplified().remove(" "),",", QStringUtils::SplitBehavior::SkipEmptyParts); Debug(_log, "DeviceType : %s", QSTRING_CSTR( this->getActiveDeviceType() )); - Debug(_log, "LedCount : %u", this->getLedCount()); + 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()); @@ -89,8 +89,8 @@ bool LedDeviceAtmoOrb::init(const QJsonObject &deviceConfig) } } - uint numberOrbs = _orbIds.size(); - uint configuredLedCount = this->getLedCount(); + int numberOrbs = _orbIds.size(); + int configuredLedCount = this->getLedCount(); if ( _orbIds.empty() ) { @@ -111,7 +111,7 @@ bool LedDeviceAtmoOrb::init(const QJsonObject &deviceConfig) { if ( numberOrbs > configuredLedCount ) { - Info(_log, "%s: More Orbs [%u] than configured LEDs [%u].", QSTRING_CSTR(this->getActiveDeviceType()), numberOrbs, configuredLedCount ); + Info(_log, "%s: More Orbs [%d] than configured LEDs [%d].", QSTRING_CSTR(this->getActiveDeviceType()), numberOrbs, configuredLedCount ); } isInitOK = true; @@ -276,16 +276,21 @@ void LedDeviceAtmoOrb::sendCommand(const QByteArray &bytes) _udpSocket->writeDatagram(bytes.data(), bytes.size(), _groupAddress, _multiCastGroupPort); } -QJsonObject LedDeviceAtmoOrb::discover() +QJsonObject LedDeviceAtmoOrb::discover(const QJsonObject& params) { + //Debug(_log, "params: [%s]", QString(QJsonDocument(params).toJson(QJsonDocument::Compact)).toUtf8().constData()); + QJsonObject devicesDiscovered; devicesDiscovered.insert("ledDeviceType", _activeDeviceType ); QJsonArray deviceList; + _multicastGroup = params["multiCastGroup"].toString(MULTICAST_GROUP_DEFAULT_ADDRESS); + _multiCastGroupPort = static_cast(params["multiCastPort"].toInt(MULTICAST_GROUP_DEFAULT_PORT)); + if ( open() == 0 ) { - Debug ( _log, "Send discovery requests to all AtmoOrbs" ); + Debug ( _log, "Send discovery requests to all AtmoOrbs listening to %s:%d", QSTRING_CSTR(_multicastGroup),_multiCastGroupPort ); setColor(0, ColorRgb::BLACK, 8); if ( _udpSocket->waitForReadyRead(DEFAULT_DISCOVERY_TIMEOUT.count()) ) diff --git a/libsrc/leddevice/dev_net/LedDeviceAtmoOrb.h b/libsrc/leddevice/dev_net/LedDeviceAtmoOrb.h index ce2f966b..bfcfc39a 100644 --- a/libsrc/leddevice/dev_net/LedDeviceAtmoOrb.h +++ b/libsrc/leddevice/dev_net/LedDeviceAtmoOrb.h @@ -43,9 +43,11 @@ public: /// /// @brief Discover AtmoOrb devices available (for configuration). /// + /// @param[in] params Parameters used to overwrite discovery default behaviour + /// /// @return A JSON structure holding a list of devices found /// - virtual QJsonObject discover() override; + QJsonObject discover(const QJsonObject& params) override; /// /// @brief Send an update to the AtmoOrb device to identify it. diff --git a/libsrc/leddevice/dev_net/LedDeviceCololight.cpp b/libsrc/leddevice/dev_net/LedDeviceCololight.cpp new file mode 100644 index 00000000..98a47cef --- /dev/null +++ b/libsrc/leddevice/dev_net/LedDeviceCololight.cpp @@ -0,0 +1,725 @@ +#include "LedDeviceCololight.h" + +#include +#include +#include +#include +#include + +#include + +// Constants +namespace { +const bool verbose = false; +const bool verbose3 = false; + +// Configuration settings + +const char CONFIG_HW_LED_COUNT[] = "hardwareLedCount"; + +// 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"; +constexpr std::chrono::milliseconds DEFAULT_DISCOVERY_TIMEOUT{ 5000 }; +constexpr std::chrono::milliseconds DEFAULT_READ_TIMEOUT{ 1000 }; +constexpr std::chrono::milliseconds DEFAULT_IDENTIFY_TIME{ 2000 }; + +const char COLOLIGHT_MODEL[] = "mod"; +const char COLOLIGHT_MODEL_TYPE[] = "subkey"; +const char COLOLIGHT_MAC[] = "sn"; +const char COLOLIGHT_NAME[] = "name"; + +const char COLOLIGHT_MODEL_IDENTIFIER[] = "OD_WE_QUAN"; + +const int COLOLIGHT_BEADS_PER_MODULE = 19; +const int COLOLIGHT_MIN_STRIP_SEGMENT_SIZE = 30; + +} //End of constants + +LedDeviceCololight::LedDeviceCololight(const QJsonObject& deviceConfig) + : ProviderUdp(deviceConfig) + , _modelType(-1) + , _ledLayoutType(STRIP_LAYOUT) + , _ledBeadCount(0) + , _distance(0) + , _sequenceNumber(1) +{ + _packetFixPart.append(reinterpret_cast(PACKET_HEADER), sizeof(PACKET_HEADER)); + _packetFixPart.append(reinterpret_cast(PACKET_SECU), sizeof(PACKET_SECU)); +} + +LedDevice* LedDeviceCololight::construct(const QJsonObject& deviceConfig) +{ + return new LedDeviceCololight(deviceConfig); +} + +bool LedDeviceCololight::init(const QJsonObject& deviceConfig) +{ + bool isInitOK = false; + + _port = API_DEFAULT_PORT; + + 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; + } + } + return isInitOK; +} + +bool LedDeviceCololight::initLedsConfiguration() +{ + bool isInitOK = false; + + if (!getInfo()) + { + QString errorReason = QString("Cololight device (%1) not accessible to get additional properties!") + .arg(getAddress().toString()); + setInError(errorReason); + } + else + { + QString modelTypeText; + + switch (_modelType) { + case 0: + modelTypeText = "Strip"; + _ledLayoutType = STRIP_LAYOUT; + break; + case 1: + _ledLayoutType = MODLUE_LAYOUT; + modelTypeText = "Plus"; + break; + default: + _modelType = STRIP; + modelTypeText = "Strip"; + _ledLayoutType = STRIP_LAYOUT; + Info(_log, "Model not identified, assuming Cololight %s", QSTRING_CSTR(modelTypeText)); + break; + } + Debug(_log, "Model type : %s", QSTRING_CSTR(modelTypeText)); + + if (getLedCount() == 0) + { + setLedCount(_devConfig[CONFIG_HW_LED_COUNT].toInt(0)); + } + + if (_modelType == STRIP && (getLedCount() % COLOLIGHT_MIN_STRIP_SEGMENT_SIZE != 0)) + { + QString errorReason = QString("Hardware LED count must be multiple of %1 for Cololight Strip!") + .arg(COLOLIGHT_MIN_STRIP_SEGMENT_SIZE); + this->setInError(errorReason); + } + else + { + Debug(_log, "LedCount : %d", getLedCount()); + + int configuredLedCount = _devConfig["currentLedCount"].toInt(1); + + if (getLedCount() < configuredLedCount) + { + QString errorReason = QString("Not enough LEDs [%1] for configured LEDs in layout [%2] found!") + .arg(getLedCount()) + .arg(configuredLedCount); + this->setInError(errorReason); + } + else + { + if (getLedCount() > configuredLedCount) + { + Info(_log, "%s: More LEDs [%d] than configured LEDs in layout [%d].", QSTRING_CSTR(this->getActiveDeviceType()), getLedCount(), configuredLedCount); + } + isInitOK = true; + } + } + } + + return isInitOK; +} + +void LedDeviceCololight::initDirectColorCmdTemplate() +{ + int ledNumber = static_cast(this->getLedCount()); + + _directColorCommandTemplate.clear(); + + //Packet + _directColorCommandTemplate.append(static_cast(bufferMode::LIGHTBEAD)); // idx + + int beads = 1; + if (_ledLayoutType == MODLUE_LAYOUT) + { + beads = COLOLIGHT_BEADS_PER_MODULE; + } + + for (int i = 0; i < ledNumber; ++i) + { + _directColorCommandTemplate.append(static_cast(i * beads + 1)); + _directColorCommandTemplate.append(static_cast(i * beads + beads)); + _directColorCommandTemplate.append(3, static_cast(0x00)); + } +} + +bool LedDeviceCololight::getInfo() +{ + bool isCmdOK = false; + + QByteArray command; + + const quint8 packetSize = 2; + int fixPartsize = sizeof(TL1_CMD_FIXED_PART); + + command.resize(sizeof(TL1_CMD_FIXED_PART) + packetSize); + command.fill('\0'); + + command[fixPartsize - 3] = static_cast(SETVAR); // verb + command[fixPartsize - 2] = static_cast(_sequenceNumber); // ctag + command[fixPartsize - 1] = static_cast(packetSize); // length + + //Packet + command[fixPartsize] = static_cast(READ_INFO_FROM_STORAGE); // idx + command[fixPartsize + 1] = static_cast(0x01); // idx + + if (sendRequest(TL1_CMD, command)) + { + QByteArray response; + if (readResponse(response)) + { + DebugIf(verbose, _log, "#[0x%x], Data returned: [%s]", _sequenceNumber, QSTRING_CSTR(toHex(response))); + + quint16 ledNum = qFromBigEndian(response.data() + 1); + + if (ledNum != 0xFFFF) + { + _ledBeadCount = ledNum; + if (ledNum % COLOLIGHT_BEADS_PER_MODULE == 0) + { + _modelType = MODLUE_LAYOUT; + _distance = ledNum / COLOLIGHT_BEADS_PER_MODULE; + setLedCount(_distance); + } + } + else + { + _modelType = STRIP; + setLedCount(0); + } + + Debug(_log, "#LEDs found [0x%x], [%u], distance [%d]", _ledBeadCount, _ledBeadCount, _distance); + + isCmdOK = true; + } + } + + return isCmdOK; +} + +bool LedDeviceCololight::setEffect(const effect effect) +{ + return setColor(static_cast(effect)); +} + +bool LedDeviceCololight::setColor(const ColorRgb colorRgb) +{ + uint32_t color = colorRgb.blue | (colorRgb.green << 8) | (colorRgb.red << 16) | (0x00 << 24); + + return setColor(color); +} + +bool LedDeviceCololight::setColor(const uint32_t color) +{ + bool isCmdOK = false; + + QByteArray command; + + const quint8 packetSize = 6; + int fixPartsize = sizeof(TL1_CMD_FIXED_PART); + + command.resize(sizeof(TL1_CMD_FIXED_PART) + packetSize); + command.fill('\0'); + + command[fixPartsize - 3] = static_cast(SET); // verb + command[fixPartsize - 2] = static_cast(_sequenceNumber); // ctag + command[fixPartsize - 1] = static_cast(packetSize); // length + + //Packet + command[fixPartsize] = static_cast(0x02); // idx + command[fixPartsize + 1] = static_cast(0xff); // set color or dynamic effect + + qToBigEndian(color, command.data() + fixPartsize + 2); + + if (sendRequest(TL1_CMD, command)) + { + QByteArray response; + if (readResponse(response)) + { + DebugIf(verbose, _log, "#[0x%x], Data returned: [%s]", _sequenceNumber, QSTRING_CSTR(toHex(response))); + isCmdOK = true; + } + } + + return isCmdOK; +} + +bool LedDeviceCololight::setState(bool isOn) +{ + bool isCmdOK = false; + + quint8 type = isOn ? STATE_ON : STATE_OFF; + + QByteArray command; + + const quint8 packetSize = 3; + int fixPartsize = sizeof(TL1_CMD_FIXED_PART); + + command.resize(sizeof(TL1_CMD_FIXED_PART) + packetSize); + command.fill('\0'); + + command[fixPartsize - 3] = static_cast(SET); // verb + command[fixPartsize - 2] = static_cast(_sequenceNumber); // ctag + command[fixPartsize - 1] = static_cast(packetSize); // length + + //Packet + command[fixPartsize] = static_cast(BRIGTHNESS_CONTROL); // idx + command[fixPartsize + 1] = static_cast(type); // type + command[fixPartsize + 2] = static_cast(isOn); // value + + if (sendRequest(TL1_CMD, command)) + { + QByteArray response; + if (readResponse(response)) + { + DebugIf(verbose, _log, "#[0x%x], Data returned: [%s]", _sequenceNumber, QSTRING_CSTR(toHex(response))); + isCmdOK = true; + } + } + + return isCmdOK; +} + +bool LedDeviceCololight::setStateDirect(bool isOn) +{ + bool isCmdOK = false; + + QByteArray command; + + //Packet + command.append(static_cast(0x04)); // idx + command.append(static_cast(isOn)); // idx + command.append(static_cast(0xd7)); // idx + + if (sendRequest(DIRECT_CONTROL, command)) + { + QByteArray response; + if (readResponse(response)) + { + DebugIf(verbose, _log, "#[0x%x], Data returned: [%s]", _sequenceNumber, QSTRING_CSTR(toHex(response))); + isCmdOK = true; + } + } + + return isCmdOK; +} + +bool LedDeviceCololight::setColor(const std::vector& ledValues) +{ + int ledNumber = static_cast(ledValues.size()); + + QByteArray command = _directColorCommandTemplate; + + //Update LED values, start from offset (mode + first start/stop pair) = 3 + for (int i = 0; i < ledNumber; ++i) + { + command[3 + i * 5] = static_cast(ledValues[i].red); + command[3 + i * 5 + 1] = static_cast(ledValues[i].green); + command[3 + i * 5 + 2] = static_cast(ledValues[i].blue); + } + + bool isCmdOK = sendRequest(DIRECT_CONTROL, command); + + return isCmdOK; +} + +bool LedDeviceCololight::setTL1CommandMode(bool isOn) +{ + bool isCmdOK = false; + + quint8 type = isOn ? STATE_ON : STATE_OFF; + + QByteArray command; + + const quint8 packetSize = 2; + int fixPartsize = sizeof(TL1_CMD_FIXED_PART); + + command.resize(sizeof(TL1_CMD_FIXED_PART) + packetSize); + command.fill('\0'); + + command[fixPartsize - 3] = static_cast(SETEEPROM); // verb + command[fixPartsize - 2] = static_cast(_sequenceNumber); // ctag + command[fixPartsize - 1] = static_cast(packetSize); // length + + //Packet + command[fixPartsize] = static_cast(COLOR_CONTROL); // idx + command[fixPartsize + 1] = static_cast(type); // type + + if (sendRequest(TL1_CMD, command)) + { + QByteArray response; + if (readResponse(response)) + { + DebugIf(verbose, _log, "#[0x%x], Data returned: [%s]", _sequenceNumber, QSTRING_CSTR(toHex(response))); + isCmdOK = true; + } + } + + return isCmdOK; +} + +bool LedDeviceCololight::sendRequest(const appID appID, const QByteArray& command) +{ + bool isSendOK = true; + QByteArray packet(_packetFixPart); + packet.append(static_cast(_sequenceNumber)); + packet.append(command); + + quint32 size = static_cast(static_cast(sizeof(PACKET_SECU)) + 1 + command.size()); + + qToBigEndian(appID, packet.data() + 4); + + qToBigEndian(size, packet.data() + 6); + + ++_sequenceNumber; + + DebugIf(verbose3, _log, "packet: ([0x%x], [%u])[%s]", size, size, QSTRING_CSTR(toHex(packet, 64))); + + if (writeBytes(packet) < 0) + { + isSendOK = false; + } + + return isSendOK; +} + +bool LedDeviceCololight::readResponse() +{ + QByteArray response; + return readResponse(response); +} + +bool LedDeviceCololight::readResponse(QByteArray& response) +{ + bool isRequestOK = false; + if (_udpSocket->waitForReadyRead(DEFAULT_READ_TIMEOUT.count())) + { + while (_udpSocket->waitForReadyRead(200)) + { + QByteArray datagram; + + while (_udpSocket->hasPendingDatagrams()) + { + datagram.resize(static_cast(_udpSocket->pendingDatagramSize())); + QHostAddress senderIP; + quint16 senderPort; + + _udpSocket->readDatagram(datagram.data(), datagram.size(), &senderIP, &senderPort); + + if (datagram.size() >= 10) + { + DebugIf(verbose3, _log, "response: [%s]", QSTRING_CSTR(toHex(datagram, 64))); + + quint16 appID = qFromBigEndian(datagram.mid(4, sizeof(appID))); + + if (verbose && appID == 0x8000) + { + QString tagVersion = datagram.left(2); + quint32 packetSize = qFromBigEndian(datagram.mid(sizeof(PACKET_HEADER) - sizeof(packetSize))); + + Debug(_log, "Response HEADER: tagVersion [%s], appID: [0x%.2x][%u], packet size: [0x%.4x][%u]", QSTRING_CSTR(tagVersion), appID, appID, packetSize, packetSize); + + quint32 dictionary = qFromBigEndian(datagram.mid(sizeof(PACKET_HEADER))); + quint32 checkSum = qFromBigEndian(datagram.mid(sizeof(PACKET_HEADER) + sizeof(dictionary))); + quint32 salt = qFromBigEndian(datagram.mid(sizeof(PACKET_HEADER) + sizeof(dictionary) + sizeof(checkSum), sizeof(salt))); + quint32 sequenceNumber = qFromBigEndian(datagram.mid(sizeof(PACKET_HEADER) + sizeof(dictionary) + sizeof(checkSum) + sizeof(salt))); + + Debug(_log, "Response SECU : Dict: [0x%.4x][%u], Sum: [0x%.4x][%u], Salt: [0x%.4x][%u], SN: [0x%.4x][%u]", dictionary, dictionary, checkSum, checkSum, salt, salt, sequenceNumber, sequenceNumber); + + quint8 packetSN = static_cast(datagram.at(sizeof(PACKET_HEADER) + sizeof(PACKET_SECU))); + Debug(_log, "Response packSN: [0x%.4x][%u]", packetSN, packetSN); + } + + quint8 errorCode = static_cast(datagram.at(sizeof(PACKET_HEADER) + sizeof(PACKET_SECU) + 1)); + + int dataPartStart = sizeof(PACKET_HEADER) + sizeof(PACKET_SECU) + sizeof(TL1_CMD_FIXED_PART); + + if (errorCode != 0) + { + quint8 originalVerb = static_cast(datagram.at(dataPartStart - 2) - 0x80); + quint8 originalRequestPacketSN = static_cast(datagram.at(dataPartStart - 1)); + + if (errorCode == 16) + { + //TL1 Command failure + Error(_log, "Request [0x%x] failed =with error [%u], appID [%u], originalVerb [0x%x]", originalRequestPacketSN, errorCode, appID, originalVerb); + } + else + { + Error(_log, "Request [0x%x] failed with error [%u], appID [%u]", originalRequestPacketSN, errorCode, appID); + } + } + else + { + // TL1 Protocol + if (appID == 0x8000) + { + if (dataPartStart < datagram.size()) + { + quint8 dataLength = static_cast(datagram.at(dataPartStart)); + + response = datagram.mid(dataPartStart + 1, dataLength); + if (verbose) + { + quint8 originalVerb = static_cast(datagram.at(dataPartStart - 2) - 0x80); + Debug(_log, "Cmd [0x%x], Data returned: [%s]", originalVerb, QSTRING_CSTR(toHex(response))); + } + } + else + { + DebugIf(verbose, _log, "No additional data returned"); + } + } + isRequestOK = true; + } + } + } + } + } + return isRequestOK; +} + +int LedDeviceCololight::write(const std::vector& ledValues) +{ + int rc = -1; + + if (setColor(ledValues)) + { + rc = 0; + } + + return rc; +} + +bool LedDeviceCololight::powerOn() +{ + bool on = true; + if (_isDeviceReady) + { + if (!setState(false) || !setTL1CommandMode(false)) + { + on = false; + } + } + return on; +} + +bool LedDeviceCololight::powerOff() +{ + bool off = true; + if (_isDeviceReady) + { + writeBlack(); + off = setStateDirect(false); + setTL1CommandMode(false); + } + return off; +} + +QJsonObject LedDeviceCololight::discover(const QJsonObject& /*params*/) +{ + QJsonObject devicesDiscovered; + devicesDiscovered.insert("ledDeviceType", _activeDeviceType); + + QJsonArray deviceList; + + QUdpSocket udpSocket; + + udpSocket.writeDatagram(QString(DISCOVERY_MESSAGE).toUtf8(), QHostAddress(DISCOVERY_ADDRESS), DISCOVERY_PORT); + + if (udpSocket.waitForReadyRead(DEFAULT_DISCOVERY_TIMEOUT.count())) + { + while (udpSocket.waitForReadyRead(500)) + { + QByteArray datagram; + + while (udpSocket.hasPendingDatagrams()) + { + datagram.resize(static_cast(udpSocket.pendingDatagramSize())); + QHostAddress senderIP; + quint16 senderPort; + + udpSocket.readDatagram(datagram.data(), datagram.size(), &senderIP, &senderPort); + + QString data(datagram); + + QMap headers; + // parse request + QStringList entries = QStringUtils::split(data, "\n", QStringUtils::SplitBehavior::SkipEmptyParts); + for (auto entry : entries) + { + // split into key=value, be aware that value field may contain also a "=" + entry = entry.simplified(); + int pos = entry.indexOf("="); + if (pos == -1) + { + continue; + } + + const QString key = entry.left(pos).trimmed().toLower(); + const QString value = entry.mid(pos + 1).trimmed(); + headers[key] = value; + } + + if (headers.value("mod") == COLOLIGHT_MODEL_IDENTIFIER) + { + QString ipAddress = QHostAddress(senderIP.toIPv4Address()).toString(); + _services.insert(ipAddress, headers); + + Debug(_log, "Cololight discovered at [%s]", QSTRING_CSTR(ipAddress)); + DebugIf(verbose3, _log, "_data: [%s]", QSTRING_CSTR(data)); + } + } + } + } + + QMap>::iterator i; + for (i = _services.begin(); i != _services.end(); ++i) + { + QJsonObject obj; + + QString ipAddress = i.key(); + obj.insert("ip", ipAddress); + obj.insert("model", i.value().value(COLOLIGHT_MODEL)); + obj.insert("type", i.value().value(COLOLIGHT_MODEL_TYPE)); + obj.insert("mac", i.value().value(COLOLIGHT_MAC)); + obj.insert("name", i.value().value(COLOLIGHT_NAME)); + + QHostInfo hostInfo = QHostInfo::fromName(i.key()); + if (hostInfo.error() == QHostInfo::NoError) + { + QString hostname = hostInfo.hostName(); + if (!QHostInfo::localDomainName().isEmpty()) + { + obj.insert("hostname", hostname.remove("." + QHostInfo::localDomainName())); + obj.insert("domain", QHostInfo::localDomainName()); + } + else + { + if (hostname.startsWith(ipAddress)) + { + obj.insert("hostname", ipAddress); + + QString domain = hostname.remove(ipAddress); + if (domain.at(0) == '.') + { + domain.remove(0, 1); + } + obj.insert("domain", domain); + } + else + { + int domainPos = hostname.indexOf('.'); + obj.insert("hostname", hostname.left(domainPos)); + obj.insert("domain", hostname.mid(domainPos + 1)); + } + } + } + + deviceList << obj; + } + + devicesDiscovered.insert("devices", deviceList); + DebugIf(verbose, _log, "devicesDiscovered: [%s]", QString(QJsonDocument(devicesDiscovered).toJson(QJsonDocument::Compact)).toUtf8().constData()); + + return devicesDiscovered; +} + +QJsonObject LedDeviceCololight::getProperties(const QJsonObject& params) +{ + DebugIf(verbose, _log, "params: [%s]", QString(QJsonDocument(params).toJson(QJsonDocument::Compact)).toUtf8().constData()); + QJsonObject properties; + + QString apiHostname = params["host"].toString(""); + quint16 apiPort = static_cast(params["port"].toInt(API_DEFAULT_PORT)); + + if (!apiHostname.isEmpty()) + { + QJsonObject deviceConfig; + + deviceConfig.insert("host", apiHostname); + deviceConfig.insert("port", apiPort); + if (ProviderUdp::init(deviceConfig)) + { + if (getInfo()) + { + QString modelTypeText; + + switch (_modelType) { + case 1: + modelTypeText = "Plus"; + break; + default: + modelTypeText = "Strip"; + break; + } + properties.insert("modelType", modelTypeText); + properties.insert("ledCount", static_cast(getLedCount())); + properties.insert("ledBeadCount", _ledBeadCount); + properties.insert("distance", _distance); + } + } + } + + DebugIf(verbose, _log, "properties: [%s]", QString(QJsonDocument(properties).toJson(QJsonDocument::Compact)).toUtf8().constData()); + + return properties; +} + +void LedDeviceCololight::identify(const QJsonObject& params) +{ + DebugIf(verbose, _log, "params: [%s]", QString(QJsonDocument(params).toJson(QJsonDocument::Compact)).toUtf8().constData()); + + QString apiHostname = params["host"].toString(""); + quint16 apiPort = static_cast(params["port"].toInt(API_DEFAULT_PORT)); + + if (!apiHostname.isEmpty()) + { + QJsonObject deviceConfig; + + deviceConfig.insert("host", apiHostname); + deviceConfig.insert("port", apiPort); + if (ProviderUdp::init(deviceConfig)) + { + if (setStateDirect(false) && setState(true)) + { + setEffect(THE_CIRCUS); + + QEventLoop loop; + QTimer::singleShot(DEFAULT_IDENTIFY_TIME.count(), &loop, &QEventLoop::quit); + loop.exec(); + + setColor(ColorRgb::BLACK); + } + } + } +} diff --git a/libsrc/leddevice/dev_net/LedDeviceCololight.h b/libsrc/leddevice/dev_net/LedDeviceCololight.h new file mode 100644 index 00000000..7d9365be --- /dev/null +++ b/libsrc/leddevice/dev_net/LedDeviceCololight.h @@ -0,0 +1,310 @@ +#ifndef LEDEVICECOLOLIGHT_H +#define LEDEVICECOLOLIGHT_H + +// LedDevice includes +#include +#include "ProviderUdp.h" + +enum appID { + TL1_CMD = 0x00, + DIRECT_CONTROL = 0x01, + TRANSMIT_FILE = 0x02, + CLEAR_FILES = 0x03, + WRITE_FILE = 0x04, + READ_FILE = 0x05, + MODIFY_SECU = 0x06 +}; + +enum effect : uint32_t { + SAVANNA = 0x04970400, + SUNRISE = 0x01c10a00, + UNICORNS = 0x049a0e00, + PENSIEVE = 0x04c40600, + THE_CIRCUS = 0x04810130, + INSTASHARE = 0x03bc0190, + EIGTHIES = 0x049a0000, + CHERRY_BLOS = 0x04940800, + RAINBOW = 0x05bd0690, + TEST = 0x03af0af0, + CHRISTMAS = 0x068b0900 +}; + +enum verbs { + GET = 0x03, + SET = 0x04, + SETEEPROM = 0x07, + SETVAR = 0x0b +}; + +enum commandTypes { + STATE_OFF = 0x80, + STATE_ON = 0x81, + BRIGTHNESS = 0xCF, + SETCOLOR = 0xFF +}; + +enum idxTypes { + BRIGTHNESS_CONTROL = 0x01, + COLOR_CONTROL = 0x02, + COLOR_DIRECT_CONTROL = 0x81, + READ_INFO_FROM_STORAGE = 0x86 +}; + + enum bufferMode { + MONOCROME = 0x01, + LIGHTBEAD = 0x02, + }; + +enum ledLayout { + STRIP_LAYOUT, + MODLUE_LAYOUT +}; + +enum modelType { + STRIP, + PLUS +}; + +const uint8_t PACKET_HEADER[] = + { + 'S', 'Z', // Tag "SZ" + 0x30, 0x30, // Version "00" + 0x00, 0x00, // AppID, 0x0000 = TL1 command mode + 0x00, 0x00, 0x00, 0x00 // Size +}; + +const uint8_t PACKET_SECU[] = + { + 0x00, 0x00, 0x00, 0x00, // Dict + 0x00, 0x00, 0x00, 0x00, // Sum + 0x00, 0x00, 0x00, 0x00, // Salt + 0x00, 0x00, 0x00, 0x00 // SN +}; + +const uint8_t TL1_CMD_FIXED_PART[] = + { + 0x00, 0x00, 0x00, 0x00, // DISTID + 0x00, 0x00, 0x00, 0x00, // SRCID + 0x00, // SECU + 0x00, // VERB + 0x00, // CTAG + 0x00 // LENGTH +}; + +/// +/// Implementation of a Cololight LedDevice +/// +class LedDeviceCololight : public ProviderUdp +{ +public: + + /// + /// @brief Constructs a Cololight LED-device + /// + /// @param deviceConfig Device's configuration as JSON-Object + /// + explicit LedDeviceCololight(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); + + /// + /// @brief Discover Cololight devices available (for configuration). + /// + /// @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 a Cololight device's resource properties + /// + /// Following parameters are required + /// @code + /// { + /// "host" : "hostname or IP", + /// } + ///@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 Cololight device to identify it. + /// + /// Following parameters are required + /// @code + /// { + /// "host" : "hostname or IP", + /// } + ///@endcode + /// + /// @param[in] params Parameters to address device + /// + void identify(const QJsonObject& params) override; + +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 Writes the RGB-Color values to the LEDs. + /// + /// @param[in] ledValues The RGB-color per LED + /// @return Zero on success, else negative + /// + int write(const std::vector& ledValues) override; + + /// + /// @brief Power-/turn on the Cololight device. + /// + /// @return True if success + /// + bool powerOn() override; + + /// + /// @brief Power-/turn off the Cololight device. + /// + /// @return True if success + /// + bool powerOff() override; + +private: + + bool initLedsConfiguration(); + void initDirectColorCmdTemplate(); + + /// + /// @brief Read additional information from Cololight + /// + /// @return True if success + /// + bool getInfo(); + + /// + /// @brief Set a Cololight effect + /// + /// @param[in] effect from effect list + /// + /// @return True if success + /// + bool setEffect(const effect effect); + + /// + /// @brief Set a color + /// + /// @param[in] color in RGB + /// + /// @return True if success + /// + bool setColor(const ColorRgb colorRgb); + + /// + /// @brief Set a color (or effect) + /// + /// @param[in] color in four bytes (red, green, blue, mode) + /// + /// @return True if success + /// + bool setColor(const uint32_t color); + + /// + /// @brief Set colors per LED as per given list + /// + /// @param[in] list of color per LED + /// + /// @return True if success + /// + bool setColor(const std::vector& ledValues); + + /// + /// @brief Set the Cololight device in TL1 command mode + /// + /// @param[in] isOn, Enable TL1 command mode = true + /// + /// @return True if success + /// + bool setTL1CommandMode(bool isOn); + + /// + /// @brief Set the Cololight device's state (on/off) in TL1 mode + /// + /// @param[in] isOn, on=true + /// + /// @return True if success + /// + bool setState(bool isOn); + + /// + /// @brief Set the Cololight device's state (on/off) in Direct Mode + /// + /// @param[in] isOn, on=true + /// + /// @return True if success + /// + bool setStateDirect(bool isOn); + + /// + /// @brief Send a request to the Cololight device for execution + /// + /// @param[in] appID + /// @param[in] command + /// + /// @return True if success + /// + bool sendRequest(const appID appID, const QByteArray& command); + + /// + /// @brief Read response for a send request + /// + /// @return True if success + /// + bool readResponse(); + + /// + /// @brief Read response for a send request + /// + /// @param[out] response + /// + /// @return True if success + /// + bool readResponse(QByteArray& response); + + // Cololight model, e.g. CololightPlus, CololightStrip + int _modelType; + + // Defines how Cololight LED are organised (multiple light beads in a module or individual lights on a strip + int _ledLayoutType; + + // Count of overall LEDs across all modules + int _ledBeadCount; + + // Distance (in #modules) of the module farest away from the main controller + int _distance; + + QByteArray _packetFixPart; + QByteArray _DataPart; + + QByteArray _directColorCommandTemplate; + + quint32 _sequenceNumber; + + //Cololights discovered and their response message details + QMultiMap> _services; +}; + +#endif // LEDEVICECOLOLIGHT_H diff --git a/libsrc/leddevice/dev_net/LedDeviceFadeCandy.cpp b/libsrc/leddevice/dev_net/LedDeviceFadeCandy.cpp index 75b6a4f9..7ab59b3b 100644 --- a/libsrc/leddevice/dev_net/LedDeviceFadeCandy.cpp +++ b/libsrc/leddevice/dev_net/LedDeviceFadeCandy.cpp @@ -1,5 +1,9 @@ #include "LedDeviceFadeCandy.h" +#include + +#include + // https://docs.microsoft.com/en-us/windows/win32/winprog/windows-data-types#ssize-t #if defined(_MSC_VER) #include @@ -8,22 +12,22 @@ typedef SSIZE_T ssize_t; // Constants namespace { +constexpr std::chrono::milliseconds CONNECT_TIMEOUT{1000}; -const signed MAX_NUM_LEDS = 10000; // OPC can handle 21845 LEDs - in theory, fadecandy device should handle 10000 LEDs -const unsigned OPC_SET_PIXELS = 0; // OPC command codes -const unsigned OPC_SYS_EX = 255; // OPC command codes -const unsigned OPC_HEADER_SIZE = 4; // OPC header size - +const int MAX_NUM_LEDS = 10000; // OPC can handle 21845 LEDs - in theory, fadecandy device should handle 10000 LEDs +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 quint16 STREAM_DEFAULT_PORT = 7890; +const int STREAM_DEFAULT_PORT = 7890; -LedDeviceFadeCandy::LedDeviceFadeCandy(const QJsonObject &deviceConfig) +LedDeviceFadeCandy::LedDeviceFadeCandy(const QJsonObject& deviceConfig) : LedDevice(deviceConfig) , _client(nullptr) - ,_host() - ,_port(STREAM_DEFAULT_PORT) + , _host() + , _port(STREAM_DEFAULT_PORT) { } @@ -32,20 +36,20 @@ LedDeviceFadeCandy::~LedDeviceFadeCandy() delete _client; } -LedDevice* LedDeviceFadeCandy::construct(const QJsonObject &deviceConfig) +LedDevice* LedDeviceFadeCandy::construct(const QJsonObject& deviceConfig) { return new LedDeviceFadeCandy(deviceConfig); } -bool LedDeviceFadeCandy::init(const QJsonObject &deviceConfig) +bool LedDeviceFadeCandy::init(const QJsonObject& deviceConfig) { bool isInitOK = false; - if ( LedDevice::init(deviceConfig) ) + if (LedDevice::init(deviceConfig)) { if (getLedCount() > MAX_NUM_LEDS) { - QString errortext = QString ("More LED configured than allowed (%1)").arg(MAX_NUM_LEDS); + QString errortext = QString("More LED configured than allowed (%1)").arg(MAX_NUM_LEDS); this->setInError(errortext); isInitOK = false; } @@ -55,18 +59,18 @@ bool LedDeviceFadeCandy::init(const QJsonObject &deviceConfig) _port = deviceConfig["port"].toInt(STREAM_DEFAULT_PORT); //If host not configured the init fails - if ( _host.isEmpty() ) + if (_host.isEmpty()) { this->setInError("No target hostname nor IP defined"); } else { - _channel = deviceConfig["channel"].toInt(0); - _gamma = deviceConfig["gamma"].toDouble(1.0); - _noDither = ! deviceConfig["dither"].toBool(false); - _noInterp = ! deviceConfig["interpolation"].toBool(false); - _manualLED = deviceConfig["manualLed"].toBool(false); - _ledOnOff = deviceConfig["ledOn"].toBool(false); + _channel = deviceConfig["channel"].toInt(0); + _gamma = deviceConfig["gamma"].toDouble(1.0); + _noDither = !deviceConfig["dither"].toBool(false); + _noInterp = !deviceConfig["interpolation"].toBool(false); + _manualLED = deviceConfig["manualLed"].toBool(false); + _ledOnOff = deviceConfig["ledOn"].toBool(false); _setFcConfig = deviceConfig["setFcConfig"].toBool(false); _whitePoint_r = 1.0; @@ -74,20 +78,19 @@ bool LedDeviceFadeCandy::init(const QJsonObject &deviceConfig) _whitePoint_b = 1.0; const QJsonArray whitePointConfig = deviceConfig["whitePoint"].toArray(); - if ( !whitePointConfig.isEmpty() && whitePointConfig.size() == 3 ) + if (!whitePointConfig.isEmpty() && whitePointConfig.size() == 3) { _whitePoint_r = whitePointConfig[0].toDouble() / 255.0; _whitePoint_g = whitePointConfig[1].toDouble() / 255.0; _whitePoint_b = whitePointConfig[2].toDouble() / 255.0; } - _opc_data.resize( _ledRGBCount + OPC_HEADER_SIZE ); - _opc_data[0] = _channel; + _opc_data.resize(static_cast(_ledRGBCount) + OPC_HEADER_SIZE); + _opc_data[0] = static_cast(_channel); _opc_data[1] = OPC_SET_PIXELS; - _opc_data[2] = _ledRGBCount >> 8; - _opc_data[3] = _ledRGBCount & 0xff; + qToBigEndian(static_cast(_ledRGBCount), _opc_data.data() + 2); - if ( initNetwork() ) + if (initNetwork()) { isInitOK = true; } @@ -101,7 +104,7 @@ bool LedDeviceFadeCandy::initNetwork() { bool isInitOK = false; - if ( _client == nullptr ) + if (_client == nullptr) { _client = new QTcpSocket(this); isInitOK = true; @@ -116,10 +119,10 @@ int LedDeviceFadeCandy::open() _isDeviceReady = false; // Try to open the LedDevice - if ( !tryConnect() ) + if (!tryConnect()) { - errortext = QString ("Failed to open device."); - this->setInError( errortext ); + errortext = QString("Failed to open device."); + this->setInError(errortext); } else { @@ -136,7 +139,7 @@ int LedDeviceFadeCandy::close() _isDeviceReady = false; // LedDevice specific closing activities - if ( _client != nullptr ) + if (_client != nullptr) { _client->close(); // Everything is OK -> device is closed @@ -147,7 +150,7 @@ int LedDeviceFadeCandy::close() bool LedDeviceFadeCandy::isConnected() const { bool connected = false; - if ( _client != nullptr ) + if (_client != nullptr) { connected = _client->state() == QAbstractSocket::ConnectedState; } @@ -156,13 +159,13 @@ bool LedDeviceFadeCandy::isConnected() const bool LedDeviceFadeCandy::tryConnect() { - if ( _client != nullptr ) + if (_client != nullptr) { - if ( _client->state() == QAbstractSocket::UnconnectedState ) { - _client->connectToHost( _host, _port); - if ( _client->waitForConnected(1000) ) + if (_client->state() == QAbstractSocket::UnconnectedState) { + _client->connectToHost(_host, static_cast(_port)); + if (_client->waitForConnected(CONNECT_TIMEOUT.count())) { - Info(_log,"fadecandy/opc: connected to %s:%i on channel %i", QSTRING_CSTR(_host), _port, _channel); + Info(_log, "fadecandy/opc: connected to %s:%d on channel %d", QSTRING_CSTR(_host), _port, _channel); if (_setFcConfig) { sendFadeCandyConfiguration(); @@ -173,50 +176,48 @@ bool LedDeviceFadeCandy::tryConnect() return isConnected(); } -int LedDeviceFadeCandy::write( const std::vector & ledValues ) +int LedDeviceFadeCandy::write(const std::vector& ledValues) { uint idx = OPC_HEADER_SIZE; for (const ColorRgb& color : ledValues) { - _opc_data[idx ] = unsigned( color.red ); - _opc_data[idx+1] = unsigned( color.green ); - _opc_data[idx+2] = unsigned( color.blue ); + _opc_data[idx] = static_cast(color.red); + _opc_data[idx + 1] = static_cast(color.green); + _opc_data[idx + 2] = static_cast(color.blue); idx += 3; } - int retval = transferData()<0 ? -1 : 0; + int retval = transferData() < 0 ? -1 : 0; return retval; } -int LedDeviceFadeCandy::transferData() +qint64 LedDeviceFadeCandy::transferData() { - if ( isConnected() || tryConnect() ) + if (isConnected() || tryConnect()) { - return _client->write( _opc_data, _opc_data.size() ); + return _client->write(_opc_data); } return -2; } -int LedDeviceFadeCandy::sendSysEx(uint8_t systemId, uint8_t commandId, const QByteArray& msg) +qint64 LedDeviceFadeCandy::sendSysEx(uint8_t systemId, uint8_t commandId, const QByteArray& msg) { - if ( isConnected() ) + if (isConnected()) { QByteArray sysExData; - uint data_size = msg.size() + 4; - sysExData.resize( 4 + OPC_HEADER_SIZE ); + int data_size = msg.size() + 4; + sysExData.resize(4 + OPC_HEADER_SIZE); sysExData[0] = 0; - sysExData[1] = OPC_SYS_EX; - sysExData[2] = data_size >>8; - sysExData[3] = data_size &0xff; - sysExData[4] = systemId >>8; - sysExData[5] = systemId &0xff; - sysExData[6] = commandId >>8; - sysExData[7] = commandId &0xff; + sysExData[1] = static_cast(OPC_SYS_EX); + + qToBigEndian(static_cast(data_size), sysExData.data() + 2); + qToBigEndian(static_cast(systemId), sysExData.data() + 4); + qToBigEndian(static_cast(commandId), sysExData.data() + 6); sysExData += msg; - return _client->write( sysExData, sysExData.size() ); + return _client->write(sysExData, sysExData.size()); } return -1; } @@ -224,9 +225,9 @@ int LedDeviceFadeCandy::sendSysEx(uint8_t systemId, uint8_t commandId, const QBy void LedDeviceFadeCandy::sendFadeCandyConfiguration() { Debug(_log, "send configuration to fadecandy"); - QString data = "{\"gamma\": "+QString::number(_gamma,'g',4)+", \"whitepoint\": ["+QString::number(_whitePoint_r,'g',4)+", "+QString::number(_whitePoint_g,'g',4)+", "+QString::number(_whitePoint_b,'g',4)+"]}"; - sendSysEx(1, 1, data.toLocal8Bit() ); + QString data = "{\"gamma\": " + QString::number(_gamma, 'g', 4) + ", \"whitepoint\": [" + QString::number(_whitePoint_r, 'g', 4) + ", " + QString::number(_whitePoint_g, 'g', 4) + ", " + QString::number(_whitePoint_b, 'g', 4) + "]}"; + sendSysEx(1, 1, data.toLocal8Bit()); - char firmware_data = ((uint8_t)_noDither | ((uint8_t)_noInterp << 1) | ((uint8_t)_manualLED << 2) | ((uint8_t)_ledOnOff << 3) ); - sendSysEx(1, 2, QByteArray(1,firmware_data) ); + char firmware_data = static_cast(static_cast(_noDither) | (static_cast(_noInterp) << 1) | (static_cast(_manualLED) << 2) | (static_cast(_ledOnOff) << 3)); + sendSysEx(1, 2, QByteArray(1, firmware_data)); } diff --git a/libsrc/leddevice/dev_net/LedDeviceFadeCandy.h b/libsrc/leddevice/dev_net/LedDeviceFadeCandy.h index c4a7490a..82f61e9a 100644 --- a/libsrc/leddevice/dev_net/LedDeviceFadeCandy.h +++ b/libsrc/leddevice/dev_net/LedDeviceFadeCandy.h @@ -40,7 +40,7 @@ public: /// /// @param deviceConfig Device's configuration as JSON-Object /// - explicit LedDeviceFadeCandy(const QJsonObject &deviceConfig); + explicit LedDeviceFadeCandy(const QJsonObject& deviceConfig); /// /// @brief Destructor of the LedDevice @@ -52,7 +52,7 @@ public: /// /// @param[in] deviceConfig Device's configuration as JSON-Object /// @return LedDevice constructed - static LedDevice* construct(const QJsonObject &deviceConfig); + static LedDevice* construct(const QJsonObject& deviceConfig); protected: @@ -62,7 +62,7 @@ protected: /// @param[in] deviceConfig the JSON device configuration /// @return True, if success /// - bool init(const QJsonObject &deviceConfig) override; + bool init(const QJsonObject& deviceConfig) override; /// /// @brief Opens the output device. @@ -84,7 +84,7 @@ protected: /// @param[in] ledValues The RGB-color per LED /// @return Zero on success, else negative /// - int write(const std::vector & ledValues) override; + int write(const std::vector& ledValues) override; private: @@ -113,7 +113,7 @@ private: /// /// @return amount of transferred bytes. -1 error while transferring, -2 error while connecting /// - int transferData(); + qint64 transferData(); /// /// @brief Send system exclusive commands @@ -122,7 +122,7 @@ private: /// @param[in] commandId id of command /// @param[in] msg the sysEx message /// @return amount bytes written, -1 if failed - int sendSysEx(uint8_t systemId, uint8_t commandId, const QByteArray& msg); + qint64 sendSysEx(uint8_t systemId, uint8_t commandId, const QByteArray& msg); /// /// @brief Sends the configuration to fadecandy cserver @@ -131,8 +131,8 @@ private: QTcpSocket* _client; QString _host; - uint16_t _port; - unsigned _channel; + int _port; + int _channel; QByteArray _opc_data; // fadecandy sysEx @@ -145,7 +145,6 @@ private: bool _noInterp; bool _manualLED; bool _ledOnOff; - }; #endif // LEDEVICEFADECANDY_H diff --git a/libsrc/leddevice/dev_net/LedDeviceNanoleaf.cpp b/libsrc/leddevice/dev_net/LedDeviceNanoleaf.cpp index 2db5dcd0..f76c29b8 100644 --- a/libsrc/leddevice/dev_net/LedDeviceNanoleaf.cpp +++ b/libsrc/leddevice/dev_net/LedDeviceNanoleaf.cpp @@ -7,6 +7,7 @@ // Qt includes #include #include +#include //std includes #include @@ -14,18 +15,17 @@ // Constants namespace { - -const bool verbose = false; +const bool verbose = false; const bool verbose3 = false; // Configuration settings const char CONFIG_ADDRESS[] = "host"; //const char CONFIG_PORT[] = "port"; -const char CONFIG_AUTH_TOKEN[] ="token"; +const char CONFIG_AUTH_TOKEN[] = "token"; -const char CONFIG_PANEL_ORDER_TOP_DOWN[] ="panelOrderTopDown"; -const char CONFIG_PANEL_ORDER_LEFT_RIGHT[] ="panelOrderLeftRight"; -const char CONFIG_PANEL_START_POS[] ="panelStartPos"; +const char CONFIG_PANEL_ORDER_TOP_DOWN[] = "panelOrderTopDown"; +const char CONFIG_PANEL_ORDER_LEFT_RIGHT[] = "panelOrderLeftRight"; +const char CONFIG_PANEL_START_POS[] = "panelStartPos"; // Panel configuration settings const char PANEL_LAYOUT[] = "layout"; @@ -61,16 +61,19 @@ const char API_BASE_PATH[] = "/api/v1/%1/"; const char API_ROOT[] = ""; //const char API_EXT_MODE_STRING_V1[] = "{\"write\" : {\"command\" : \"display\", \"animType\" : \"extControl\"}}"; const char API_EXT_MODE_STRING_V2[] = "{\"write\" : {\"command\" : \"display\", \"animType\" : \"extControl\", \"extControlVersion\" : \"v2\"}}"; -const char API_STATE[] ="state"; +const char API_STATE[] = "state"; const char API_PANELLAYOUT[] = "panelLayout"; const char API_EFFECT[] = "effects"; +//Nanoleaf Control data stream +const int STREAM_FRAME_PANEL_NUM_SIZE = 2; +const int STREAM_FRAME_PANEL_INFO_SIZE = 8; + // Nanoleaf ssdp services const char SSDP_ID[] = "ssdp:all"; const char SSDP_FILTER_HEADER[] = "ST"; const char SSDP_CANVAS[] = "nanoleaf:nl29"; const char SSDP_LIGHTPANELS[] = "nanoleaf_aurora:light"; - } //End of constants // Nanoleaf Panel Shapetypes @@ -81,7 +84,7 @@ enum SHAPETYPES { CONTROL_SQUARE_PRIMARY, CONTROL_SQUARE_PASSIVE, POWER_SUPPLY, -}; + }; // Nanoleaf external control versions enum EXTCONTROLVERSIONS { @@ -89,20 +92,20 @@ enum EXTCONTROLVERSIONS { EXTCTRLVER_V2 }; -LedDeviceNanoleaf::LedDeviceNanoleaf(const QJsonObject &deviceConfig) +LedDeviceNanoleaf::LedDeviceNanoleaf(const QJsonObject& deviceConfig) : ProviderUdp(deviceConfig) - ,_restApi(nullptr) - ,_apiPort(API_DEFAULT_PORT) - ,_topDown(true) - ,_leftRight(true) - ,_startPos(0) - ,_endPos(0) - ,_extControlVersion (EXTCTRLVER_V2), + , _restApi(nullptr) + , _apiPort(API_DEFAULT_PORT) + , _topDown(true) + , _leftRight(true) + , _startPos(0) + , _endPos(0) + , _extControlVersion(EXTCTRLVER_V2), _panelLedCount(0) { } -LedDevice* LedDeviceNanoleaf::construct(const QJsonObject &deviceConfig) +LedDevice* LedDeviceNanoleaf::construct(const QJsonObject& deviceConfig) { return new LedDeviceNanoleaf(deviceConfig); } @@ -113,7 +116,7 @@ LedDeviceNanoleaf::~LedDeviceNanoleaf() _restApi = nullptr; } -bool LedDeviceNanoleaf::init(const QJsonObject &deviceConfig) +bool LedDeviceNanoleaf::init(const QJsonObject& deviceConfig) { // Overwrite non supported/required features setLatchTime(0); @@ -121,69 +124,69 @@ bool LedDeviceNanoleaf::init(const QJsonObject &deviceConfig) if (deviceConfig["rewriteTime"].toInt(0) > 0) { - Info (_log, "Device Nanoleaf does not require rewrites. Refresh time is ignored."); + Info(_log, "Device Nanoleaf does not require rewrites. Refresh time is ignored."); } - DebugIf(verbose, _log, "deviceConfig: [%s]", QString(QJsonDocument(_devConfig).toJson(QJsonDocument::Compact)).toUtf8().constData() ); + DebugIf(verbose, _log, "deviceConfig: [%s]", QString(QJsonDocument(_devConfig).toJson(QJsonDocument::Compact)).toUtf8().constData()); bool isInitOK = false; - if ( LedDevice::init(deviceConfig) ) + if (LedDevice::init(deviceConfig)) { - 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() )); + 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()); // Read panel organisation configuration - if ( deviceConfig[ CONFIG_PANEL_ORDER_TOP_DOWN ].isString() ) + if (deviceConfig[CONFIG_PANEL_ORDER_TOP_DOWN].isString()) { - _topDown = deviceConfig[ CONFIG_PANEL_ORDER_TOP_DOWN ].toString().toInt() == 0; + _topDown = deviceConfig[CONFIG_PANEL_ORDER_TOP_DOWN].toString().toInt() == 0; } else { - _topDown = deviceConfig[ CONFIG_PANEL_ORDER_TOP_DOWN ].toInt() == 0; + _topDown = deviceConfig[CONFIG_PANEL_ORDER_TOP_DOWN].toInt() == 0; } - if ( deviceConfig[ CONFIG_PANEL_ORDER_LEFT_RIGHT ].isString() ) + if (deviceConfig[CONFIG_PANEL_ORDER_LEFT_RIGHT].isString()) { - _leftRight = deviceConfig[ CONFIG_PANEL_ORDER_LEFT_RIGHT ].toString().toInt() == 0; + _leftRight = deviceConfig[CONFIG_PANEL_ORDER_LEFT_RIGHT].toString().toInt() == 0; } else { - _leftRight = deviceConfig[ CONFIG_PANEL_ORDER_LEFT_RIGHT ].toInt() == 0; + _leftRight = deviceConfig[CONFIG_PANEL_ORDER_LEFT_RIGHT].toInt() == 0; } - _startPos = static_cast( deviceConfig[ CONFIG_PANEL_START_POS ].toInt(0) ); + _startPos = deviceConfig[CONFIG_PANEL_START_POS].toInt(0); // TODO: Allow to handle port dynamically //Set hostname as per configuration and_defaultHost default port - _hostname = deviceConfig[ CONFIG_ADDRESS ].toString(); - _apiPort = API_DEFAULT_PORT; - _authToken = deviceConfig[ CONFIG_AUTH_TOKEN ].toString(); + _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() ) + if (_hostname.isEmpty()) { this->setInError("No target hostname nor IP defined"); isInitOK = false; } else { - if ( initRestAPI( _hostname, _apiPort, _authToken ) ) + if (initRestAPI(_hostname, _apiPort, _authToken)) { // Read LedDevice configuration and validate against device configuration - if ( initLedsConfiguration() ) + 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, "Hostname/IP : %s", QSTRING_CSTR(_hostname)); Debug(_log, "Port : %d", _port); } } @@ -201,9 +204,9 @@ bool LedDeviceNanoleaf::initLedsConfiguration() // Read Panel count and panel Ids _restApi->setPath(API_ROOT); httpResponse response = _restApi->get(); - if ( response.error() ) + if (response.error()) { - this->setInError ( response.getErrorReason() ); + this->setInError(response.getErrorReason()); isInitOK = false; } else @@ -215,35 +218,35 @@ bool LedDeviceNanoleaf::initLedsConfiguration() QString deviceManufacturer = jsonAllPanelInfo[DEV_DATA_MANUFACTURER].toString(); _deviceFirmwareVersion = jsonAllPanelInfo[DEV_DATA_FIRMWAREVERSION].toString(); - Debug(_log, "Name : %s", QSTRING_CSTR( deviceName )); - Debug(_log, "Model : %s", QSTRING_CSTR( _deviceModel )); - Debug(_log, "Manufacturer : %s", QSTRING_CSTR( deviceManufacturer )); - Debug(_log, "FirmwareVersion: %s", QSTRING_CSTR( _deviceFirmwareVersion)); + Debug(_log, "Name : %s", QSTRING_CSTR(deviceName)); + Debug(_log, "Model : %s", QSTRING_CSTR(_deviceModel)); + Debug(_log, "Manufacturer : %s", QSTRING_CSTR(deviceManufacturer)); + Debug(_log, "FirmwareVersion: %s", QSTRING_CSTR(_deviceFirmwareVersion)); // Get panel details from /panelLayout/layout QJsonObject jsonPanelLayout = jsonAllPanelInfo[API_PANELLAYOUT].toObject(); QJsonObject jsonLayout = jsonPanelLayout[PANEL_LAYOUT].toObject(); - uint panelNum = static_cast(jsonLayout[PANEL_NUM].toInt()); + int panelNum = jsonLayout[PANEL_NUM].toInt(); QJsonArray positionData = jsonLayout[PANEL_POSITIONDATA].toArray(); - std::map> panelMap; + std::map> panelMap; // Loop over all children. - for (const QJsonValue value : positionData) + foreach(const QJsonValue & value, positionData) { QJsonObject panelObj = value.toObject(); - uint panelId = static_cast(panelObj[PANEL_ID].toInt()); - uint panelX = static_cast(panelObj[PANEL_POS_X].toInt()); - uint panelY = static_cast(panelObj[PANEL_POS_Y].toInt()); - uint panelshapeType = static_cast(panelObj[PANEL_SHAPE_TYPE].toInt()); - //uint panelOrientation = static_cast(panelObj[PANEL_ORIENTATION].toInt()); + int panelId = panelObj[PANEL_ID].toInt(); + int panelX = panelObj[PANEL_POS_X].toInt(); + int panelY = panelObj[PANEL_POS_Y].toInt(); + int panelshapeType = panelObj[PANEL_SHAPE_TYPE].toInt(); + //int panelOrientation = panelObj[PANEL_ORIENTATION].toInt(); - DebugIf(verbose, _log, "Panel [%u] (%u,%u) - Type: [%u]", panelId, panelX, panelY, panelshapeType ); + DebugIf(verbose, _log, "Panel [%d] (%d,%d) - Type: [%d]", panelId, panelX, panelY, panelshapeType); // Skip Rhythm panels - if ( panelshapeType != RHYTM ) + if (panelshapeType != RHYTM) { panelMap[panelY][panelX] = panelId; } @@ -254,16 +257,16 @@ bool LedDeviceNanoleaf::initLedsConfiguration() } // Travers panels top down - for(auto posY = panelMap.crbegin(); posY != panelMap.crend(); ++posY) + for (auto posY = panelMap.crbegin(); posY != panelMap.crend(); ++posY) { // Sort panels left to right - if ( _leftRight ) + if (_leftRight) { - for( auto posX = posY->second.cbegin(); posX != posY->second.cend(); ++posX) + for (auto posX = posY->second.cbegin(); posX != posY->second.cend(); ++posX) { - DebugIf(verbose3, _log, "panelMap[%u][%u]=%u", posY->first, posX->first, posX->second ); + DebugIf(verbose3, _log, "panelMap[%d][%d]=%d", posY->first, posX->first, posX->second); - if ( _topDown ) + if (_topDown) { _panelIds.push_back(posX->second); } @@ -276,11 +279,11 @@ bool LedDeviceNanoleaf::initLedsConfiguration() else { // Sort panels right to left - for( auto posX = posY->second.crbegin(); posX != posY->second.crend(); ++posX) + for (auto posX = posY->second.crbegin(); posX != posY->second.crend(); ++posX) { - DebugIf(verbose3, _log, "panelMap[%u][%u]=%u", posY->first, posX->first, posX->second ); + DebugIf(verbose3, _log, "panelMap[%d][%d]=%d", posY->first, posX->first, posX->second); - if ( _topDown ) + if (_topDown) { _panelIds.push_back(posX->second); } @@ -292,22 +295,22 @@ bool LedDeviceNanoleaf::initLedsConfiguration() } } - this->_panelLedCount = static_cast(_panelIds.size()); - _devConfig["hardwareLedCount"] = static_cast(_panelLedCount); + this->_panelLedCount = _panelIds.size(); + _devConfig["hardwareLedCount"] = _panelLedCount; - Debug(_log, "PanelsNum : %u", panelNum); - Debug(_log, "PanelLedCount : %u", _panelLedCount); + Debug(_log, "PanelsNum : %d", panelNum); + Debug(_log, "PanelLedCount : %d", _panelLedCount); // Check. if enough panels were found. - uint configuredLedCount = this->getLedCount(); + int configuredLedCount = this->getLedCount(); _endPos = _startPos + configuredLedCount - 1; Debug(_log, "Sort Top>Down : %d", _topDown); Debug(_log, "Sort Left>Right: %d", _leftRight); - Debug(_log, "Start Panel Pos: %u", _startPos); - Debug(_log, "End Panel Pos : %u", _endPos); + Debug(_log, "Start Panel Pos: %d", _startPos); + Debug(_log, "End Panel Pos : %d", _endPos); - if (_panelLedCount < configuredLedCount ) + if (_panelLedCount < configuredLedCount) { QString errorReason = QString("Not enough panels [%1] for configured LEDs [%2] found!") .arg(_panelLedCount) @@ -317,16 +320,16 @@ bool LedDeviceNanoleaf::initLedsConfiguration() } else { - if ( _panelLedCount > this->getLedCount() ) + if (_panelLedCount > this->getLedCount()) { - Info(_log, "%s: More panels [%u] than configured LEDs [%u].", QSTRING_CSTR(this->getActiveDeviceType()), _panelLedCount, configuredLedCount ); + Info(_log, "%s: More panels [%d] than configured LEDs [%d].", QSTRING_CSTR(this->getActiveDeviceType()), _panelLedCount, configuredLedCount); } // Check, if start position + number of configured LEDs is greater than number of panels available - if ( _endPos >= _panelLedCount ) + if (_endPos >= _panelLedCount) { QString errorReason = QString("Start panel [%1] out of range. Start panel position can be max [%2] given [%3] panel available!") - .arg(_startPos).arg(_panelLedCount-configuredLedCount).arg(_panelLedCount); + .arg(_startPos).arg(_panelLedCount - configuredLedCount).arg(_panelLedCount); this->setInError(errorReason); isInitOK = false; @@ -336,16 +339,16 @@ bool LedDeviceNanoleaf::initLedsConfiguration() return isInitOK; } -bool LedDeviceNanoleaf::initRestAPI(const QString &hostname, int port, const QString &token ) +bool LedDeviceNanoleaf::initRestAPI(const QString& hostname, int port, const QString& token) { bool isInitOK = false; - if ( _restApi == nullptr ) + if (_restApi == nullptr) { - _restApi = new ProviderRestApi(hostname, port ); + _restApi = new ProviderRestApi(hostname, port); //Base-path is api-path + authentication token - _restApi->setBasePath( QString(API_BASE_PATH).arg(token) ); + _restApi->setBasePath(QString(API_BASE_PATH).arg(token)); isInitOK = true; } @@ -360,13 +363,13 @@ int LedDeviceNanoleaf::open() QJsonDocument responseDoc = changeToExternalControlMode(); // Resolve port for Light Panels QJsonObject jsonStreamControllInfo = responseDoc.object(); - if ( ! jsonStreamControllInfo.isEmpty() ) + if (!jsonStreamControllInfo.isEmpty()) { //Set default streaming port _port = static_cast(jsonStreamControllInfo[STREAM_CONTROL_PORT].toInt()); } - if ( ProviderUdp::open() == 0 ) + if (ProviderUdp::open() == 0) { // Everything is OK, device is ready _isDeviceReady = true; @@ -375,10 +378,10 @@ int LedDeviceNanoleaf::open() return retval; } -QJsonObject LedDeviceNanoleaf::discover() +QJsonObject LedDeviceNanoleaf::discover(const QJsonObject& /*params*/) { QJsonObject devicesDiscovered; - devicesDiscovered.insert("ledDeviceType", _activeDeviceType ); + devicesDiscovered.insert("ledDeviceType", _activeDeviceType); QJsonArray deviceList; @@ -391,41 +394,41 @@ QJsonObject LedDeviceNanoleaf::discover() discover.setSearchFilter(searchTargetFilter, SSDP_FILTER_HEADER); QString searchTarget = SSDP_ID; - if ( discover.discoverServices(searchTarget) > 0 ) + if (discover.discoverServices(searchTarget) > 0) { deviceList = discover.getServicesDiscoveredJson(); } 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; } QJsonObject LedDeviceNanoleaf::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; // Get Nanoleaf device properties QString host = params["host"].toString(""); - if ( !host.isEmpty() ) + if (!host.isEmpty()) { QString authToken = params["token"].toString(""); QString filter = params["filter"].toString(""); // Resolve hostname and port (or use default API port) - QStringList addressparts = QStringUtils::split(host,":", QStringUtils::SplitBehavior::SkipEmptyParts); + QStringList addressparts = QStringUtils::split(host, ":", QStringUtils::SplitBehavior::SkipEmptyParts); QString apiHost = addressparts[0]; int apiPort; - if ( addressparts.size() > 1) + if (addressparts.size() > 1) { apiPort = addressparts[1].toInt(); } else { - apiPort = API_DEFAULT_PORT; + apiPort = API_DEFAULT_PORT; } initRestAPI(apiHost, apiPort, authToken); @@ -433,40 +436,39 @@ QJsonObject LedDeviceNanoleaf::getProperties(const QJsonObject& params) // Perform request httpResponse response = _restApi->get(); - if ( response.error() ) + if (response.error()) { - Warning (_log, "%s get properties failed with error: '%s'", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(response.getErrorReason())); + Warning(_log, "%s get properties failed with error: '%s'", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(response.getErrorReason())); } properties.insert("properties", response.getBody().object()); - 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 LedDeviceNanoleaf::identify(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()); QString host = params["host"].toString(""); - if ( !host.isEmpty() ) + if (!host.isEmpty()) { QString authToken = params["token"].toString(""); // Resolve hostname and port (or use default API port) - QStringList addressparts = QStringUtils::split(host,":", QStringUtils::SplitBehavior::SkipEmptyParts); + QStringList addressparts = QStringUtils::split(host, ":", QStringUtils::SplitBehavior::SkipEmptyParts); QString apiHost = addressparts[0]; int apiPort; - if ( addressparts.size() > 1) + if (addressparts.size() > 1) { apiPort = addressparts[1].toInt(); } else { - apiPort = API_DEFAULT_PORT; + apiPort = API_DEFAULT_PORT; } initRestAPI(apiHost, apiPort, authToken); @@ -474,33 +476,33 @@ void LedDeviceNanoleaf::identify(const QJsonObject& params) // Perform request httpResponse response = _restApi->put(); - if ( response.error() ) + if (response.error()) { - Warning (_log, "%s identification failed with error: '%s'", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(response.getErrorReason())); + Warning(_log, "%s identification failed with error: '%s'", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(response.getErrorReason())); } } } bool LedDeviceNanoleaf::powerOn() { - if ( _isDeviceReady) + if (_isDeviceReady) { changeToExternalControlMode(); //Power-on Nanoleaf device _restApi->setPath(API_STATE); - _restApi->put( getOnOffRequest(true) ); + _restApi->put(getOnOffRequest(true)); } return true; } bool LedDeviceNanoleaf::powerOff() { - if ( _isDeviceReady) + if (_isDeviceReady) { //Power-off the Nanoleaf device physically _restApi->setPath(API_STATE); - _restApi->put( getOnOffRequest(false) ); + _restApi->put(getOnOffRequest(false)); } return true; } @@ -508,7 +510,7 @@ bool LedDeviceNanoleaf::powerOff() QString LedDeviceNanoleaf::getOnOffRequest(bool isOn) const { QString state = isOn ? STATE_VALUE_TRUE : STATE_VALUE_FALSE; - return QString( "{\"%1\":{\"%2\":%3}}" ).arg(STATE_ON, STATE_ONOFF_VALUE, state); + return QString("{\"%1\":{\"%2\":%3}}").arg(STATE_ON, STATE_ONOFF_VALUE, state); } QJsonDocument LedDeviceNanoleaf::changeToExternalControlMode() @@ -518,15 +520,14 @@ QJsonDocument LedDeviceNanoleaf::changeToExternalControlMode() //Enable UDP Mode v2 _restApi->setPath(API_EFFECT); - httpResponse response =_restApi->put(API_EXT_MODE_STRING_V2); + httpResponse response = _restApi->put(API_EXT_MODE_STRING_V2); return response.getBody(); } -int LedDeviceNanoleaf::write(const std::vector & ledValues) +int LedDeviceNanoleaf::write(const std::vector& ledValues) { int retVal = 0; - uint udpBufferSize; // // nPanels 2B @@ -537,35 +538,27 @@ int LedDeviceNanoleaf::write(const std::vector & ledValues) // // Note: Nanoleaf Light Panels (Aurora) now support External Control V2 (tested with FW 3.2.0) - udpBufferSize = _panelLedCount * 8 + 2; - std::vector udpbuffer; + int udpBufferSize = STREAM_FRAME_PANEL_NUM_SIZE + _panelLedCount * STREAM_FRAME_PANEL_INFO_SIZE; + + QByteArray udpbuffer; udpbuffer.resize(udpBufferSize); - uchar lowByte; // lower byte - uchar highByte; // upper byte - - uint i=0; + int i = 0; // Set number of panels - highByte = static_cast(_panelLedCount >>8 ); - lowByte = static_cast(_panelLedCount & 0xFF); - - udpbuffer[i++] = highByte; - udpbuffer[i++] = lowByte; + qToBigEndian(static_cast(_panelLedCount), udpbuffer.data() + i); + i += 2; ColorRgb color; //Maintain LED counter independent from PanelCounter - uint ledCounter = 0; - for ( uint panelCounter=0; panelCounter < _panelLedCount; panelCounter++ ) + int ledCounter = 0; + for (int panelCounter = 0; panelCounter < _panelLedCount; panelCounter++) { - uint panelID = _panelIds[panelCounter]; - - highByte = static_cast(panelID >>8 ); - lowByte = static_cast(panelID & 0xFF); + int panelID = _panelIds[panelCounter]; // Set panels configured - if( panelCounter >= _startPos && panelCounter <= _endPos ) { + if (panelCounter >= _startPos && panelCounter <= _endPos) { color = static_cast(ledValues.at(ledCounter)); ++ledCounter; } @@ -573,49 +566,35 @@ int LedDeviceNanoleaf::write(const std::vector & ledValues) { // Set panels not configured to black; color = ColorRgb::BLACK; - DebugIf(verbose3, _log, "[%u] >= panelLedCount [%u] => Set to BLACK", panelCounter, _panelLedCount ); + DebugIf(verbose3, _log, "[%d] >= panelLedCount [%d] => Set to BLACK", panelCounter, _panelLedCount); } // Set panelID - udpbuffer[i++] = highByte; - udpbuffer[i++] = lowByte; + qToBigEndian(static_cast(panelID), udpbuffer.data() + i); + i += 2; // Set panel's color LEDs - udpbuffer[i++] = color.red; - udpbuffer[i++] = color.green; - udpbuffer[i++] = color.blue; + udpbuffer[i++] = static_cast(color.red); + udpbuffer[i++] = static_cast(color.green); + udpbuffer[i++] = static_cast(color.blue); // Set white LED udpbuffer[i++] = 0; // W not set manually // Set transition time unsigned char tranitionTime = 1; // currently fixed at value 1 which corresponds to 100ms + qToBigEndian(static_cast(tranitionTime), udpbuffer.data() + i); + i += 2; - highByte = static_cast(tranitionTime >>8 ); - lowByte = static_cast(tranitionTime & 0xFF); - - udpbuffer[i++] = highByte; - udpbuffer[i++] = lowByte; - DebugIf(verbose3, _log, "[%u] Color: {%u,%u,%u}", panelCounter, color.red, color.green, color.blue ); - + DebugIf(verbose3, _log, "[%u] Color: {%u,%u,%u}", panelCounter, color.red, color.green, color.blue); } - DebugIf(verbose3, _log, "UDP-Address [%s], UDP-Port [%u], udpBufferSize[%u], Bytes to send [%u]", QSTRING_CSTR(_address.toString()), _port, udpBufferSize, i); - DebugIf(verbose3, _log, "[%s]", uint8_vector_to_hex_string(udpbuffer).c_str() ); - retVal &= writeBytes( i , udpbuffer.data()); - DebugIf(verbose3, _log, "writeBytes(): [%d]",retVal); + if (verbose3) + { + Debug(_log, "UDP-Address [%s], UDP-Port [%u], udpBufferSize[%d], Bytes to send [%d]", QSTRING_CSTR(_address.toString()), _port, udpBufferSize, i); + Debug( _log, "packet: [%s]", QSTRING_CSTR(toHex(udpbuffer, 64))); + } + + retVal = writeBytes(udpbuffer); return retVal; } - -std::string LedDeviceNanoleaf::uint8_vector_to_hex_string(const std::vector& buffer) const -{ - std::stringstream ss; - ss << std::hex << std::setfill('0'); - std::vector::const_iterator it; - - for (it = buffer.begin(); it != buffer.end(); ++it) - { - ss << " " << std::setw(2) << static_cast(*it); - } - return ss.str(); -} diff --git a/libsrc/leddevice/dev_net/LedDeviceNanoleaf.h b/libsrc/leddevice/dev_net/LedDeviceNanoleaf.h index 7386c96e..f6d53c08 100644 --- a/libsrc/leddevice/dev_net/LedDeviceNanoleaf.h +++ b/libsrc/leddevice/dev_net/LedDeviceNanoleaf.h @@ -32,7 +32,7 @@ public: /// /// @param deviceConfig Device's configuration as JSON-Object /// - explicit LedDeviceNanoleaf(const QJsonObject &deviceConfig); + explicit LedDeviceNanoleaf(const QJsonObject& deviceConfig); /// /// @brief Destructor of the LED-device @@ -44,14 +44,16 @@ public: /// /// @param[in] deviceConfig Device's configuration as JSON-Object /// @return LedDevice constructed - static LedDevice* construct(const QJsonObject &deviceConfig); + static LedDevice* construct(const QJsonObject& deviceConfig); /// /// @brief Discover Nanoleaf devices available (for configuration). /// + /// @param[in] params Parameters used to overwrite discovery default behaviour + /// /// @return A JSON structure holding a list of devices found /// - QJsonObject discover() override; + QJsonObject discover(const QJsonObject& params) override; /// /// @brief Get the Nanoleaf device's resource properties @@ -93,7 +95,7 @@ protected: /// @param[in] deviceConfig the JSON device configuration /// @return True, if success /// - bool init(const QJsonObject &deviceConfig) override; + bool init(const QJsonObject& deviceConfig) override; /// /// @brief Opens the output device. @@ -108,7 +110,7 @@ protected: /// @param[in] ledValues The RGB-color per LED /// @return Zero on success, else negative ////// - int write(const std::vector & ledValues) override; + int write(const std::vector& ledValues) override; /// /// @brief Power-/turn on the Nanoleaf device. @@ -135,7 +137,7 @@ private: /// /// @return True, if success /// - bool initRestAPI(const QString &hostname, int port, const QString &token ); + bool initRestAPI(const QString& hostname, int port, const QString& token); /// /// @brief Get Nanoleaf device details and configuration @@ -157,14 +159,7 @@ private: /// @param isOn True, if to switch on device /// @return Command to switch device on/off /// - QString getOnOffRequest (bool isOn ) const; - - /// - /// @brief Convert vector to hex string - /// - /// @param uint8_t vector - /// @return vector as string of hex values - std::string uint8_vector_to_hex_string( const std::vector& buffer ) const; + QString getOnOffRequest(bool isOn) const; ///REST-API wrapper ProviderRestApi* _restApi; @@ -175,8 +170,8 @@ private: bool _topDown; bool _leftRight; - uint _startPos; - uint _endPos; + int _startPos; + int _endPos; //Nanoleaf device details QString _deviceModel; @@ -184,11 +179,10 @@ private: ushort _extControlVersion; /// The number of panels with LEDs - uint _panelLedCount; + int _panelLedCount; /// Array of the panel ids. - QVector _panelIds; - + QVector _panelIds; }; #endif // LEDEVICENANOLEAF_H diff --git a/libsrc/leddevice/dev_net/LedDevicePhilipsHue.cpp b/libsrc/leddevice/dev_net/LedDevicePhilipsHue.cpp index c2fa61a5..88910f19 100644 --- a/libsrc/leddevice/dev_net/LedDevicePhilipsHue.cpp +++ b/libsrc/leddevice/dev_net/LedDevicePhilipsHue.cpp @@ -6,11 +6,11 @@ #include -bool verbose = false; - // Constants namespace { +bool verbose = false; + // Configuration settings const char CONFIG_ADDRESS[] = "output"; //const char CONFIG_PORT[] = "port"; @@ -97,31 +97,6 @@ const int STREAM_SSL_HANDSHAKE_ATTEMPTS = 5; constexpr std::chrono::milliseconds STREAM_REWRITE_TIME{20}; const int SSL_CIPHERSUITES[2] = { MBEDTLS_TLS_PSK_WITH_AES_128_GCM_SHA256, 0 }; -//Streaming message header and payload definition -const uint8_t HEADER[] = -{ - 'H', 'u', 'e', 'S', 't', 'r', 'e', 'a', 'm', //protocol - 0x01, 0x00, //version 1.0 - 0x01, //sequence number 1 - 0x00, 0x00, //Reserved write 0’s - 0x01, //xy Brightness - 0x00, // Reserved, write 0’s -}; - -const uint8_t PAYLOAD_PER_LIGHT[] = -{ - 0x01, 0x00, 0x06, //light ID - //color: 16 bpc - 0xff, 0xff, - 0xff, 0xff, - 0xff, 0xff, - /* - (message.R >> 8) & 0xff, message.R & 0xff, - (message.G >> 8) & 0xff, message.G & 0xff, - (message.B >> 8) & 0xff, message.B & 0xff - */ -}; - } //End of constants bool operator ==(const CiColor& p1, const CiColor& p2) @@ -301,7 +276,7 @@ bool LedDevicePhilipsHueBridge::init(const QJsonObject &deviceConfig) { log( "DeviceType", "%s", QSTRING_CSTR( this->getActiveDeviceType() ) ); - log( "LedCount", "%u", this->getLedCount() ); + log( "LedCount", "%d", this->getLedCount() ); log( "ColorOrder", "%s", QSTRING_CSTR( this->getColorOrder() ) ); log( "RefreshTime", "%d", _refreshTimerInterval_ms ); log( "LatchTime", "%d", this->getLatchTime() ); @@ -313,7 +288,7 @@ bool LedDevicePhilipsHueBridge::init(const QJsonObject &deviceConfig) if ( address.isEmpty() ) { this->setInError("No target hostname nor IP defined"); - return false; + isInitOK = false; } else { @@ -419,13 +394,7 @@ void LedDevicePhilipsHueBridge::log(const char* msg, const char* type, ...) cons QJsonDocument LedDevicePhilipsHueBridge::getAllBridgeInfos() { - // Read Groups/ Lights and Light-Ids - _restApi->setPath(API_ROOT); - - httpResponse response = _restApi->get(); - checkApiError(response.getBody()); - - return response.getBody(); + return get(API_ROOT); } bool LedDevicePhilipsHueBridge::initMaps() @@ -491,7 +460,7 @@ void LedDevicePhilipsHueBridge::setBridgeConfig(const QJsonDocument &doc) log( "Bridge-ID", "%s", QSTRING_CSTR( deviceBridgeID )); log( "SoftwareVersion", "%s", QSTRING_CSTR( _deviceFirmwareVersion )); log( "API-Version", "%u.%u.%u", _api_major, _api_minor, _api_patch ); - log( "EntertainmentReady", "%d", _isHueEntertainmentReady ); + log( "EntertainmentReady", "%d", static_cast(_isHueEntertainmentReady) ); } void LedDevicePhilipsHueBridge::setLightsMap(const QJsonDocument &doc) @@ -517,7 +486,7 @@ void LedDevicePhilipsHueBridge::setLightsMap(const QJsonDocument &doc) } else { - log( "Lights in Bridge found", "%u", getLedCount() ); + log( "Lights in Bridge found", "%d", getLedCount() ); } } @@ -628,6 +597,15 @@ bool LedDevicePhilipsHueBridge::checkApiError(const QJsonDocument &response) return apiError; } +QJsonDocument LedDevicePhilipsHueBridge::get(const QString& route) +{ + _restApi->setPath(route); + + httpResponse response = _restApi->get(); + checkApiError(response.getBody()); + return response.getBody(); +} + QJsonDocument LedDevicePhilipsHueBridge::post(const QString& route, const QString& content) { _restApi->setPath(route); @@ -637,6 +615,12 @@ QJsonDocument LedDevicePhilipsHueBridge::post(const QString& route, const QStrin return response.getBody(); } +QJsonDocument LedDevicePhilipsHueBridge::getLightState(unsigned int lightId) +{ + DebugIf( verbose, _log, "GetLightState [%u]", lightId ); + return get( QString("%1/%2").arg( API_LIGHTS ).arg( lightId ) ); +} + void LedDevicePhilipsHueBridge::setLightState(unsigned int lightId, const QString &state) { DebugIf( verbose, _log, "SetLightState [%u]: %s", lightId, QSTRING_CSTR(state) ); @@ -645,10 +629,8 @@ void LedDevicePhilipsHueBridge::setLightState(unsigned int lightId, const QStrin QJsonDocument LedDevicePhilipsHueBridge::getGroupState(unsigned int groupId) { - _restApi->setPath( QString("%1/%2").arg( API_GROUPS ).arg( groupId ) ); - httpResponse response = _restApi->get(); - checkApiError(response.getBody()); - return response.getBody(); + DebugIf( verbose, _log, "GetGroupState [%u]", groupId ); + return get( QString("%1/%2").arg( API_GROUPS ).arg( groupId ) ); } QJsonDocument LedDevicePhilipsHueBridge::setGroupState(unsigned int groupId, bool state) @@ -712,8 +694,6 @@ PhilipsHueLight::PhilipsHueLight(Logger* log, unsigned int id, QJsonObject value _colorBlack = {0.0, 0.0, 0.0}; } - saveOriginalState(values); - _lightname = values["name"].toString().trimmed().replace("\"", ""); Info(_log, "Light ID %d (\"%s\", LED index \"%d\") created", id, QSTRING_CSTR(_lightname), ledidx ); } @@ -806,7 +786,6 @@ LedDevicePhilipsHue::LedDevicePhilipsHue(const QJsonObject& deviceConfig) , _switchOffOnBlack(false) , _brightnessFactor(1.0) , _transitionTime(1) - , _lightStatesRestored(false) , _isInitLeds(false) , _lightsCount(0) , _groupId(0) @@ -918,7 +897,7 @@ bool LedDevicePhilipsHue::setLights() if( !lArray.empty() ) { - for (const auto id : lArray) + for (const QJsonValueRef id : lArray) { unsigned int lightId = id.toString().toUInt(); if( lightId > 0 ) @@ -1249,7 +1228,7 @@ QByteArray LedDevicePhilipsHue::prepareStreamData() const { QByteArray msg; msg.reserve(static_cast(sizeof(HEADER) + sizeof(PAYLOAD_PER_LIGHT) * _lights.size())); - msg.append((const char*)HEADER, sizeof(HEADER)); + msg.append(reinterpret_cast(HEADER), sizeof(HEADER)); for (const PhilipsHueLight& light : _lights) { @@ -1264,7 +1243,7 @@ QByteArray LedDevicePhilipsHue::prepareStreamData() const static_cast((G >> 8) & 0xff), static_cast(G & 0xff), static_cast((B >> 8) & 0xff), static_cast(B & 0xff) }; - msg.append((char*)payload, sizeof(payload)); + msg.append(reinterpret_cast(payload), sizeof(payload)); } return msg; @@ -1278,30 +1257,8 @@ void LedDevicePhilipsHue::stop() int LedDevicePhilipsHue::open() { - int retval = -1; - _isDeviceReady = false; - - if( _useHueEntertainmentAPI ) - { - if ( openStream() ) - { - // Everything is OK, device is ready - _isDeviceReady = true; - retval = 0; - } - else - { - // TODO: Stop device (or fallback to classic mode) - suggest to stop device to meet user expectation - //_useHueEntertainmentAPI = false; -to be removed, if 1 - // Everything is OK, device is ready - } - } - else - { - // Classic mode, everything is OK, device is ready - _isDeviceReady = true; - retval = 0; - } + int retval = 0; + _isDeviceReady = true; return retval; } @@ -1315,6 +1272,40 @@ int LedDevicePhilipsHue::close() return retval; } +bool LedDevicePhilipsHue::switchOn() +{ + Debug(_log, ""); + + bool rc = false; + + if ( _isOn ) + { + rc = true; + } + else + { + if ( _isEnabled && _isDeviceInitialised ) + { + storeState(); + + if ( _useHueEntertainmentAPI) + { + if ( openStream() ) + { + _isOn = true; + rc = true; + } + } + else if ( powerOn() ) + { + _isOn = true; + rc = true; + } + } + } + return rc; +} + bool LedDevicePhilipsHue::switchOff() { Debug(_log, ""); @@ -1322,7 +1313,10 @@ bool LedDevicePhilipsHue::switchOff() this->stopBlackTimeoutTimer(); stop_retry_left = 3; + if (_useHueEntertainmentAPI) + { stopStream(); + } return LedDevicePhilipsHueBridge::switchOff(); } @@ -1369,7 +1363,7 @@ void LedDevicePhilipsHue::stopBlackTimeoutTimer() bool LedDevicePhilipsHue::noSignalDetection() { - if( _allLightsBlack ) + if( _allLightsBlack && _switchOffOnBlack) { if( !_stopConnection && _isInitLeds ) { @@ -1563,11 +1557,14 @@ bool LedDevicePhilipsHue::storeState() if ( _isRestoreOrigState ) { - // Save device's original state - //_orignalStateValues = get device's state; - - // TODO: Move saveOriginalState out of the HueLight constructor, - // as the light state may have change since last close and needs to be stored again before reopen + if( !_lightIds.empty() ) + { + for ( PhilipsHueLight& light : _lights ) + { + QJsonObject values = getLightState(light.getId()).object(); + light.saveOriginalState(values); + } + } } return rc; @@ -1577,11 +1574,9 @@ bool LedDevicePhilipsHue::restoreState() { bool rc = true; - if ( _isRestoreOrigState && !_lightStatesRestored ) + if ( _isRestoreOrigState ) { // Restore device's original state - _lightStatesRestored = true; - if( !_lightIds.empty() ) { for ( PhilipsHueLight& light : _lights ) @@ -1594,7 +1589,7 @@ bool LedDevicePhilipsHue::restoreState() return rc; } -QJsonObject LedDevicePhilipsHue::discover() +QJsonObject LedDevicePhilipsHue::discover(const QJsonObject& /*params*/) { QJsonObject devicesDiscovered; devicesDiscovered.insert("ledDeviceType", _activeDeviceType ); diff --git a/libsrc/leddevice/dev_net/LedDevicePhilipsHue.h b/libsrc/leddevice/dev_net/LedDevicePhilipsHue.h index baac3cdd..de3961a0 100644 --- a/libsrc/leddevice/dev_net/LedDevicePhilipsHue.h +++ b/libsrc/leddevice/dev_net/LedDevicePhilipsHue.h @@ -17,6 +17,31 @@ #include "ProviderRestApi.h" #include "ProviderUdpSSL.h" +//Streaming message header and payload definition +const uint8_t HEADER[] = + { + 'H', 'u', 'e', 'S', 't', 'r', 'e', 'a', 'm', //protocol + 0x01, 0x00, //version 1.0 + 0x01, //sequence number 1 + 0x00, 0x00, //Reserved write 0’s + 0x01, //xy Brightness + 0x00, // Reserved, write 0’s +}; + +const uint8_t PAYLOAD_PER_LIGHT[] = + { + 0x01, 0x00, 0x06, //light ID + //color: 16 bpc + 0xff, 0xff, + 0xff, 0xff, + 0xff, 0xff, + /* + (message.R >> 8) & 0xff, message.R & 0xff, + (message.G >> 8) & 0xff, message.G & 0xff, + (message.B >> 8) & 0xff, message.B & 0xff + */ +}; + /** * A XY color point in the color space of the hue system without brightness. */ @@ -152,12 +177,11 @@ public: /// @return the color space of the light determined by the model id reported by the bridge. CiColorTriangle getColorSpace() const; + void saveOriginalState(const QJsonObject& values); QString getOriginalState() const; private: - void saveOriginalState(const QJsonObject& values); - Logger* _log; /// light id unsigned int _id; @@ -200,12 +224,23 @@ public: bool initRestAPI(const QString &hostname, int port, const QString &token ); /// - /// @param route the route of the POST request. + /// @brief Perform a REST-API GET /// + /// @param route the route of the GET request. + /// + /// @return the content of the GET request. + /// + QJsonDocument get(const QString& route); + + /// + /// @brief Perform a REST-API POST + /// + /// @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 getLightState(unsigned int lightId); void setLightState(unsigned int lightId = 0, const QString &state = ""); QMap getLightMap() const; @@ -316,7 +351,7 @@ public: /// /// @brief Destructor of the LED-device /// - ~LedDevicePhilipsHue(); + ~LedDevicePhilipsHue() override; /// /// @brief Constructs the LED-device @@ -329,9 +364,11 @@ public: /// @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() override; + QJsonObject discover(const QJsonObject& params) override; /// /// @brief Get the Hue Bridge device's resource properties @@ -421,7 +458,7 @@ protected: /// /// @return True if success /// - //bool switchOn() override; + bool switchOn() override; /// /// @brief Switch the LEDs off. @@ -525,7 +562,6 @@ private: /// The default of the Hue lights is 400 ms, but we may want it snappier. int _transitionTime; - bool _lightStatesRestored; bool _isInitLeds; /// Array of the light ids. diff --git a/libsrc/leddevice/dev_net/LedDeviceWled.cpp b/libsrc/leddevice/dev_net/LedDeviceWled.cpp index 7255d2ea..80c24680 100644 --- a/libsrc/leddevice/dev_net/LedDeviceWled.cpp +++ b/libsrc/leddevice/dev_net/LedDeviceWled.cpp @@ -17,7 +17,7 @@ const quint16 STREAM_DEFAULT_PORT = 19446; const int API_DEFAULT_PORT = -1; //Use default port per communication scheme const char API_BASE_PATH[] = "/json/"; -const char API_PATH_INFO[] = "info"; +//const char API_PATH_INFO[] = "info"; const char API_PATH_STATE[] = "state"; // List of State Information @@ -60,9 +60,9 @@ bool LedDeviceWled::init(const QJsonObject &deviceConfig) if ( LedDevice::init(deviceConfig) ) { // Initialise LedDevice configuration and execution environment - uint configuredLedCount = this->getLedCount(); + int configuredLedCount = this->getLedCount(); Debug(_log, "DeviceType : %s", QSTRING_CSTR( this->getActiveDeviceType() )); - Debug(_log, "LedCount : %u", configuredLedCount); + Debug(_log, "LedCount : %d", configuredLedCount); Debug(_log, "ColorOrder : %s", QSTRING_CSTR( this->getColorOrder() )); Debug(_log, "LatchTime : %d", this->getLatchTime()); @@ -166,7 +166,7 @@ bool LedDeviceWled::powerOff() return off; } -QJsonObject LedDeviceWled::discover() +QJsonObject LedDeviceWled::discover(const QJsonObject& /*params*/) { QJsonObject devicesDiscovered; devicesDiscovered.insert("ledDeviceType", _activeDeviceType ); diff --git a/libsrc/leddevice/dev_net/LedDeviceWled.h b/libsrc/leddevice/dev_net/LedDeviceWled.h index 3d5a65b3..114519cc 100644 --- a/libsrc/leddevice/dev_net/LedDeviceWled.h +++ b/libsrc/leddevice/dev_net/LedDeviceWled.h @@ -37,9 +37,11 @@ public: /// /// @brief Discover WLED devices available (for configuration). /// + /// @param[in] params Parameters used to overwrite discovery default behaviour + /// /// @return A JSON structure holding a list of devices found /// - QJsonObject discover() override; + QJsonObject discover(const QJsonObject& params) override; /// /// @brief Get the WLED device's resource properties diff --git a/libsrc/leddevice/dev_net/LedDeviceYeelight.cpp b/libsrc/leddevice/dev_net/LedDeviceYeelight.cpp index aca97a8f..cca03b9b 100644 --- a/libsrc/leddevice/dev_net/LedDeviceYeelight.cpp +++ b/libsrc/leddevice/dev_net/LedDeviceYeelight.cpp @@ -247,7 +247,7 @@ int YeelightLight::writeCommand( const QJsonDocument &command, QJsonArray &resul if ( elapsedTime < _waitTimeQuota ) { int waitTime = _waitTimeQuota; - log ( 1, "writeCommand():", "Wait %dms, elapsedTime: %dms < quotaTime: %dms", waitTime, elapsedTime, _waitTimeQuota); + log ( 1, "writeCommand():", "Wait %dms, elapsedTime: %dms < quotaTime: %dms", waitTime, static_cast(elapsedTime), _waitTimeQuota); // Wait time (in ms) before doing next write to not overrun Yeelight command quota std::this_thread::sleep_for(std::chrono::milliseconds(_waitTimeQuota)); @@ -452,7 +452,7 @@ YeelightResponse YeelightLight::handleResponse(int correlationID, QByteArray con // Debug output if(!yeeResponse.getResult().empty()) { - for(const auto item : yeeResponse.getResult()) + for(const QJsonValueRef item : yeeResponse.getResult()) { log ( 3, "Result:", "%s", QSTRING_CSTR( item.toString() )); } @@ -524,7 +524,7 @@ QJsonObject YeelightLight::getProperties() if( !result.empty()) { int i = 0; - for(const auto item : result) + for(const QJsonValueRef item : result) { log (1,"Property:", "%s = %s", QSTRING_CSTR( propertyList.at(i).toString() ), QSTRING_CSTR( item.toString() )); properties.insert( propertyList.at(i).toString(), item ); @@ -1008,7 +1008,7 @@ bool LedDeviceYeelight::init(const QJsonObject &deviceConfig) if ( LedDevice::init(deviceConfig) ) { Debug(_log, "DeviceType : %s", QSTRING_CSTR( this->getActiveDeviceType() )); - Debug(_log, "LedCount : %u", this->getLedCount()); + Debug(_log, "LedCount : %d", this->getLedCount()); Debug(_log, "ColorOrder : %s", QSTRING_CSTR( this->getColorOrder() )); Debug(_log, "RewriteTime : %d", this->getRewriteTime()); Debug(_log, "LatchTime : %d", this->getLatchTime()); @@ -1073,8 +1073,8 @@ bool LedDeviceYeelight::init(const QJsonObject &deviceConfig) Debug(_log, "Debuglevel : %d", _debuglevel); QJsonArray configuredYeelightLights = _devConfig[CONFIG_LIGHTS].toArray(); - uint configuredYeelightsCount = 0; - for (const QJsonValue light : configuredYeelightLights) + int configuredYeelightsCount = 0; + for (const QJsonValueRef light : configuredYeelightLights) { QString host = light.toObject().value("host").toString(); int port = light.toObject().value("port").toInt(API_DEFAULT_PORT); @@ -1085,9 +1085,9 @@ bool LedDeviceYeelight::init(const QJsonObject &deviceConfig) ++configuredYeelightsCount; } } - Debug(_log, "Light configured : %u", configuredYeelightsCount ); + Debug(_log, "Light configured : %d", configuredYeelightsCount ); - uint configuredLedCount = this->getLedCount(); + int configuredLedCount = this->getLedCount(); if (configuredYeelightsCount < configuredLedCount ) { QString errorReason = QString("Not enough Yeelights [%1] for configured LEDs [%2] found!") @@ -1101,7 +1101,7 @@ bool LedDeviceYeelight::init(const QJsonObject &deviceConfig) if ( configuredYeelightsCount > configuredLedCount ) { - Warning(_log, "More Yeelights defined [%u] than configured LEDs [%u].", configuredYeelightsCount, configuredLedCount ); + Warning(_log, "More Yeelights defined [%d] than configured LEDs [%d].", configuredYeelightsCount, configuredLedCount ); } _lightsAddressList.clear(); @@ -1347,7 +1347,7 @@ bool LedDeviceYeelight::restoreState() return rc; } -QJsonObject LedDeviceYeelight::discover() +QJsonObject LedDeviceYeelight::discover(const QJsonObject& /*params*/) { QJsonObject devicesDiscovered; devicesDiscovered.insert("ledDeviceType", _activeDeviceType ); diff --git a/libsrc/leddevice/dev_net/LedDeviceYeelight.h b/libsrc/leddevice/dev_net/LedDeviceYeelight.h index 850f3a65..ff37c5ea 100644 --- a/libsrc/leddevice/dev_net/LedDeviceYeelight.h +++ b/libsrc/leddevice/dev_net/LedDeviceYeelight.h @@ -435,12 +435,11 @@ public: /// static LedDevice* construct(const QJsonObject &deviceConfig); - /// - /// @brief Discover Yeelight devices available (for configuration). + /// @param[in] params Parameters used to overwrite discovery default behaviour /// /// @return A JSON structure holding a list of devices found /// - QJsonObject discover() override; + QJsonObject discover(const QJsonObject& params) override; /// /// @brief Get a Yeelight device's resource properties diff --git a/libsrc/leddevice/dev_net/ProviderUdp.cpp b/libsrc/leddevice/dev_net/ProviderUdp.cpp index 7ab04b8c..f424cbe1 100644 --- a/libsrc/leddevice/dev_net/ProviderUdp.cpp +++ b/libsrc/leddevice/dev_net/ProviderUdp.cpp @@ -1,4 +1,3 @@ - // STL includes #include #include @@ -16,13 +15,13 @@ const ushort MAX_PORT = 65535; -ProviderUdp::ProviderUdp(const QJsonObject &deviceConfig) +ProviderUdp::ProviderUdp(const QJsonObject& deviceConfig) : LedDevice(deviceConfig) - , _udpSocket (nullptr) + , _udpSocket(nullptr) , _port(1) , _defaultHost("127.0.0.1") { - _latchTime_ms = 1; + _latchTime_ms = 0; } ProviderUdp::~ProviderUdp() @@ -30,48 +29,48 @@ ProviderUdp::~ProviderUdp() delete _udpSocket; } -bool ProviderUdp::init(const QJsonObject &deviceConfig) +bool ProviderUdp::init(const QJsonObject& deviceConfig) { bool isInitOK = false; // Initialise sub-class - if ( LedDevice::init(deviceConfig) ) + if (LedDevice::init(deviceConfig)) { QString host = deviceConfig["host"].toString(_defaultHost); - if (_address.setAddress(host) ) + if (_address.setAddress(host)) { - Debug( _log, "Successfully parsed %s as an IP-address.", QSTRING_CSTR(_address.toString())); + Debug(_log, "Successfully parsed %s as an IP-address.", QSTRING_CSTR(_address.toString())); } else { QHostInfo hostInfo = QHostInfo::fromName(host); - if ( hostInfo.error() == QHostInfo::NoError ) + if (hostInfo.error() == QHostInfo::NoError) { _address = hostInfo.addresses().first(); - Debug( _log, "Successfully resolved IP-address (%s) for hostname (%s).", QSTRING_CSTR(_address.toString()), QSTRING_CSTR(host)); + Debug(_log, "Successfully resolved IP-address (%s) for hostname (%s).", QSTRING_CSTR(_address.toString()), QSTRING_CSTR(host)); } else { - QString errortext = QString ("Failed resolving IP-address for [%1], (%2) %3").arg(host).arg(hostInfo.error()).arg(hostInfo.errorString()); - this->setInError ( errortext ); + QString errortext = QString("Failed resolving IP-address for [%1], (%2) %3").arg(host).arg(hostInfo.error()).arg(hostInfo.errorString()); + this->setInError(errortext); isInitOK = false; } } - if ( !_isDeviceInError ) + if (!_isDeviceInError) { int config_port = deviceConfig["port"].toInt(_port); - if ( config_port <= 0 || config_port > MAX_PORT ) + if (config_port <= 0 || config_port > MAX_PORT) { - QString errortext = QString ("Invalid target port [%1]!").arg(config_port); - this->setInError ( errortext ); + QString errortext = QString("Invalid target port [%1]!").arg(config_port); + this->setInError(errortext); isInitOK = false; } else { - _port = static_cast(config_port); - Debug( _log, "UDP socket will write to %s:%u", QSTRING_CSTR(_address.toString()) , _port ); + _port = static_cast(config_port); + Debug(_log, "UDP socket will write to %s:%u", QSTRING_CSTR(_address.toString()), _port); _udpSocket = new QUdpSocket(this); @@ -88,16 +87,16 @@ int ProviderUdp::open() _isDeviceReady = false; // Try to bind the UDP-Socket - if ( _udpSocket != nullptr ) + if (_udpSocket != nullptr) { - if ( _udpSocket->state() != QAbstractSocket::BoundState ) + if (_udpSocket->state() != QAbstractSocket::BoundState) { QHostAddress localAddress = QHostAddress::Any; quint16 localPort = 0; - if ( !_udpSocket->bind(localAddress, localPort) ) + if (!_udpSocket->bind(localAddress, localPort)) { - QString warntext = QString ("Could not bind local address: %1, (%2) %3").arg(localAddress.toString()).arg(_udpSocket->error()).arg(_udpSocket->errorString()); - Warning ( _log, "%s", QSTRING_CSTR(warntext)); + QString warntext = QString("Could not bind local address: %1, (%2) %3").arg(localAddress.toString()).arg(_udpSocket->error()).arg(_udpSocket->errorString()); + Warning(_log, "%s", QSTRING_CSTR(warntext)); } } // Everything is OK, device is ready @@ -106,7 +105,7 @@ int ProviderUdp::open() } else { - this->setInError( " Open error. UDP Socket not initialised!" ); + this->setInError(" Open error. UDP Socket not initialised!"); } return retval; } @@ -116,12 +115,12 @@ int ProviderUdp::close() int retval = 0; _isDeviceReady = false; - if ( _udpSocket != nullptr ) + if (_udpSocket != nullptr) { // Test, if device requires closing - if ( _udpSocket->isOpen() ) + if (_udpSocket->isOpen()) { - Debug(_log,"Close UDP-device: %s", QSTRING_CSTR( this->getActiveDeviceType() ) ); + Debug(_log, "Close UDP-device: %s", QSTRING_CSTR(this->getActiveDeviceType())); _udpSocket->close(); // Everything is OK -> device is closed } @@ -129,12 +128,28 @@ int ProviderUdp::close() return retval; } -int ProviderUdp::writeBytes(const unsigned size, const uint8_t * data) +int ProviderUdp::writeBytes(const unsigned size, const uint8_t* data) { - qint64 retVal = _udpSocket->writeDatagram((const char *)data,size,_address,_port); + int rc = 0; + qint64 bytesWritten = _udpSocket->writeDatagram(reinterpret_cast(data), size, _address, _port); - WarningIf((retVal<0), _log, "&s", QSTRING_CSTR(QString - ("(%1:%2) Write Error: (%3) %4").arg(_address.toString()).arg(_port).arg(_udpSocket->error()).arg(_udpSocket->errorString()))); - - return retVal; + if (bytesWritten == -1 || bytesWritten != size) + { + Warning(_log, "%s", QSTRING_CSTR(QString("(%1:%2) Write Error: (%3) %4").arg(_address.toString()).arg(_port).arg(_udpSocket->error()).arg(_udpSocket->errorString()))); + rc = -1; + } + return rc; +} + +int ProviderUdp::writeBytes(const QByteArray& bytes) +{ + int rc = 0; + qint64 bytesWritten = _udpSocket->writeDatagram(bytes, _address, _port); + + if (bytesWritten == -1 || bytesWritten != bytes.size()) + { + Warning(_log, "%s", QSTRING_CSTR(QString("(%1:%2) Write Error: (%3) %4").arg(_address.toString()).arg(_port).arg(_udpSocket->error()).arg(_udpSocket->errorString()))); + rc = -1; + } + return rc; } diff --git a/libsrc/leddevice/dev_net/ProviderUdp.h b/libsrc/leddevice/dev_net/ProviderUdp.h index 3ceed9d4..7a30c570 100644 --- a/libsrc/leddevice/dev_net/ProviderUdp.h +++ b/libsrc/leddevice/dev_net/ProviderUdp.h @@ -21,13 +21,15 @@ public: /// /// @brief Constructs an UDP LED-device /// - ProviderUdp(const QJsonObject &deviceConfig); + ProviderUdp(const QJsonObject& deviceConfig); /// /// @brief Destructor of the UDP LED-device /// ~ProviderUdp() override; + QHostAddress getAddress() const { return _address; } + protected: /// @@ -36,7 +38,7 @@ protected: /// @param[in] deviceConfig the JSON device configuration /// @return True, if success /// - bool init(const QJsonObject &deviceConfig) override; + bool init(const QJsonObject& deviceConfig) override; /// /// @brief Opens the output device. @@ -53,18 +55,26 @@ protected: int close() override; /// - /// @brief Writes the given bytes/bits to the UDP-device and sleeps the latch time to ensure that the - /// values are latched. + /// @brief Writes the given bytes to the UDP-device /// /// @param[in] size The length of the data /// @param[in] data The data /// /// @return Zero on success, else negative /// - int writeBytes(const unsigned size, const uint8_t *data); + int writeBytes(const unsigned size, const uint8_t* data); /// - QUdpSocket * _udpSocket; + /// @brief Writes the given bytes to the UDP-device + /// + /// @param[in] data The data + /// + /// @return Zero on success, else negative + /// + int writeBytes(const QByteArray& bytes); + + /// + QUdpSocket* _udpSocket; QHostAddress _address; quint16 _port; QString _defaultHost; diff --git a/libsrc/leddevice/dev_other/LedDeviceFile.cpp b/libsrc/leddevice/dev_other/LedDeviceFile.cpp index 13da4246..bb26a371 100644 --- a/libsrc/leddevice/dev_other/LedDeviceFile.cpp +++ b/libsrc/leddevice/dev_other/LedDeviceFile.cpp @@ -26,6 +26,14 @@ bool LedDeviceFile::init(const QJsonObject &deviceConfig) bool initOK = LedDevice::init(deviceConfig); _fileName = deviceConfig["output"].toString("/dev/null"); + +#if _WIN32 + if (_fileName == "/dev/null" ) + { + _fileName = "NULL"; + } +#endif + _printTimeStamp = deviceConfig["printTimeStamp"].toBool(false); initFile(_fileName); diff --git a/libsrc/leddevice/dev_serial/LedDeviceAdalight.cpp b/libsrc/leddevice/dev_serial/LedDeviceAdalight.cpp index 5486ed7b..0e74e13a 100644 --- a/libsrc/leddevice/dev_serial/LedDeviceAdalight.cpp +++ b/libsrc/leddevice/dev_serial/LedDeviceAdalight.cpp @@ -1,5 +1,7 @@ #include "LedDeviceAdalight.h" +#include + #include LedDeviceAdalight::LedDeviceAdalight(const QJsonObject &deviceConfig) @@ -50,8 +52,7 @@ bool LedDeviceAdalight::init(const QJsonObject &deviceConfig) _ledBuffer[0] = 'A'; _ledBuffer[1] = 'd'; _ledBuffer[2] = 'a'; - _ledBuffer[3] = (totalLedCount >> 8) & 0xFF; // LED count high byte - _ledBuffer[4] = totalLedCount & 0xFF; // LED count low byte + qToBigEndian(static_cast(totalLedCount), &_ledBuffer[3]); _ledBuffer[5] = _ledBuffer[3] ^ _ledBuffer[4] ^ 0x55; // Checksum Debug( _log, "Adalight header for %d leds: %c%c%c 0x%02x 0x%02x 0x%02x", _ledCount, diff --git a/libsrc/leddevice/dev_serial/ProviderRs232.cpp b/libsrc/leddevice/dev_serial/ProviderRs232.cpp index cb2b5b46..efc730aa 100644 --- a/libsrc/leddevice/dev_serial/ProviderRs232.cpp +++ b/libsrc/leddevice/dev_serial/ProviderRs232.cpp @@ -34,7 +34,7 @@ bool ProviderRs232::init(const QJsonObject &deviceConfig) { Debug(_log, "DeviceType : %s", QSTRING_CSTR( this->getActiveDeviceType() )); - Debug(_log, "LedCount : %u", this->getLedCount()); + 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()); @@ -256,7 +256,7 @@ QString ProviderRs232::discoverFirst() return ""; } -QJsonObject ProviderRs232::discover() +QJsonObject ProviderRs232::discover(const QJsonObject& /*params*/) { QJsonObject devicesDiscovered; devicesDiscovered.insert("ledDeviceType", _activeDeviceType ); diff --git a/libsrc/leddevice/dev_serial/ProviderRs232.h b/libsrc/leddevice/dev_serial/ProviderRs232.h index d4fbee24..537c51ce 100644 --- a/libsrc/leddevice/dev_serial/ProviderRs232.h +++ b/libsrc/leddevice/dev_serial/ProviderRs232.h @@ -66,12 +66,11 @@ protected: /// QString discoverFirst() override; - /// - /// @brief Discover RS232 serial devices available (for configuration). + /// @param[in] params Parameters used to overwrite discovery default behaviour /// /// @return A JSON structure holding a list of devices found /// - QJsonObject discover() override; + QJsonObject discover(const QJsonObject& params) override; /// /// @brief Write the given bytes to the RS232-device diff --git a/libsrc/leddevice/dev_spi/LedDeviceSK9822.cpp b/libsrc/leddevice/dev_spi/LedDeviceSK9822.cpp index e0fcc84e..808263d1 100644 --- a/libsrc/leddevice/dev_spi/LedDeviceSK9822.cpp +++ b/libsrc/leddevice/dev_spi/LedDeviceSK9822.cpp @@ -34,14 +34,15 @@ bool LedDeviceSK9822::init(const QJsonObject &deviceConfig) Info(_log, "[SK9822] Using global brightness control with threshold of %d and max level of %d", _globalBrightnessControlThreshold, _globalBrightnessControlMaxLevel); const unsigned int startFrameSize = 4; - const unsigned int endFrameSize = qMax(((_ledCount + 15) / 16), 4); + const unsigned int endFrameSize = ((_ledCount/32) + 1)*4; const unsigned int bufferSize = (_ledCount * 4) + startFrameSize + endFrameSize; - _ledBuffer.resize(bufferSize, 0xFF); - _ledBuffer[0] = 0x00; - _ledBuffer[1] = 0x00; - _ledBuffer[2] = 0x00; - _ledBuffer[3] = 0x00; + _ledBuffer.resize(0, 0x00); + _ledBuffer.resize(bufferSize, 0x00); + //_ledBuffer[0] = 0x00; + //_ledBuffer[1] = 0x00; + //_ledBuffer[2] = 0x00; + //_ledBuffer[3] = 0x00; isInitOK = true; } diff --git a/libsrc/leddevice/schemas/schema-cololight.json b/libsrc/leddevice/schemas/schema-cololight.json new file mode 100644 index 00000000..349826d1 --- /dev/null +++ b/libsrc/leddevice/schemas/schema-cololight.json @@ -0,0 +1,22 @@ +{ + "type":"object", + "required":true, + "properties": { + "host" : { + "type": "string", + "title":"edt_dev_spec_targetIpHost_title", + "propertyOrder" : 1 + }, + "latchTime": { + "type": "integer", + "title":"edt_dev_spec_latchtime_title", + "default": 0, + "append" : "edt_append_ms", + "minimum": 0, + "maximum": 1000, + "access" : "expert", + "propertyOrder" : 2 + } + }, + "additionalProperties": true +} diff --git a/libsrc/leddevice/schemas/schema-philipshue.json b/libsrc/leddevice/schemas/schema-philipshue.json index b27e62a4..46b917e3 100644 --- a/libsrc/leddevice/schemas/schema-philipshue.json +++ b/libsrc/leddevice/schemas/schema-philipshue.json @@ -46,12 +46,7 @@ "switchOffOnBlack": { "type": "boolean", "title":"edt_dev_spec_switchOffOnBlack_title", - "default" : true, - "options": { - "dependencies": { - "useEntertainmentAPI": false - } - }, + "default" : false, "propertyOrder" : 6 }, "restoreOriginalState": { diff --git a/libsrc/protoserver/ProtoClientConnection.cpp b/libsrc/protoserver/ProtoClientConnection.cpp index d7098545..e8bf1d91 100644 --- a/libsrc/protoserver/ProtoClientConnection.cpp +++ b/libsrc/protoserver/ProtoClientConnection.cpp @@ -214,7 +214,7 @@ void ProtoClientConnection::handleNotImplemented() void ProtoClientConnection::sendMessage(const google::protobuf::Message &message) { std::string serializedReply = message.SerializeAsString(); - uint32_t size = serializedReply.size(); + uint32_t size = static_cast(serializedReply.size()); uint8_t sizeData[] = {uint8_t(size >> 24), uint8_t(size >> 16), uint8_t(size >> 8), uint8_t(size)}; _socket->write((const char *) sizeData, sizeof(sizeData)); _socket->write(serializedReply.data(), serializedReply.length()); diff --git a/libsrc/python/PythonInit.cpp b/libsrc/python/PythonInit.cpp index e053d7c8..bcac8d9c 100644 --- a/libsrc/python/PythonInit.cpp +++ b/libsrc/python/PythonInit.cpp @@ -1,7 +1,3 @@ -#undef slots -#include -#define slots - // utils #include @@ -48,7 +44,11 @@ PythonInit::PythonInit() throw std::runtime_error("Initializing Python failed!"); } +#if (PY_VERSION_HEX < 0x03090000) + // PyEval_InitThreads became deprecated in Python 3.9 and will be removed in Python 3.11 PyEval_InitThreads(); // Create the GIL +#endif + mainThreadState = PyEval_SaveThread(); } diff --git a/libsrc/python/PythonProgram.cpp b/libsrc/python/PythonProgram.cpp index e2cb925e..e39839f2 100644 --- a/libsrc/python/PythonProgram.cpp +++ b/libsrc/python/PythonProgram.cpp @@ -19,7 +19,12 @@ PythonProgram::PythonProgram(const QString & name, Logger * log) : _tstate = Py_NewInterpreter(); if(_tstate == nullptr) { +#if (PY_VERSION_HEX >= 0x03020000) + PyThreadState_Swap(mainThreadState); + PyEval_SaveThread(); +#else PyEval_ReleaseLock(); +#endif Error(_log, "Failed to get thread state for %s",QSTRING_CSTR(_name)); return; } @@ -54,7 +59,12 @@ PythonProgram::~PythonProgram() // Clean up the thread state Py_EndInterpreter(_tstate); +#if (PY_VERSION_HEX >= 0x03020000) + PyThreadState_Swap(mainThreadState); + PyEval_SaveThread(); +#else PyEval_ReleaseLock(); +#endif } void PythonProgram::execute(const QByteArray & python_code) diff --git a/libsrc/ssdp/SSDPDiscover.cpp b/libsrc/ssdp/SSDPDiscover.cpp index 712e2f32..a16ee53f 100644 --- a/libsrc/ssdp/SSDPDiscover.cpp +++ b/libsrc/ssdp/SSDPDiscover.cpp @@ -316,24 +316,40 @@ QJsonArray SSDPDiscover::getServicesDiscoveredJson() const obj.insert("usn", i.value().uniqueServiceName); QUrl url (i.value().location); - obj.insert("ip", url.host()); + QString ipAddress = url.host(); + + obj.insert("ip", ipAddress); obj.insert("port", url.port()); QHostInfo hostInfo = QHostInfo::fromName(url.host()); - if (hostInfo.error() == QHostInfo::NoError ) + if (hostInfo.error() == QHostInfo::NoError) { QString hostname = hostInfo.hostName(); - //Seems that for Windows no local domain name is resolved - if (!hostInfo.localDomainName().isEmpty() ) + + if (!QHostInfo::localDomainName().isEmpty()) { - obj.insert("hostname", hostname.remove("."+hostInfo.localDomainName())); - obj.insert("domain", hostInfo.localDomainName()); + obj.insert("hostname", hostname.remove("." + QHostInfo::localDomainName())); + obj.insert("domain", QHostInfo::localDomainName()); } else { - int domainPos = hostname.indexOf('.'); - obj.insert("hostname", hostname.left(domainPos)); - obj.insert("domain", hostname.mid(domainPos+1)); + if (hostname.startsWith(ipAddress)) + { + obj.insert("hostname", ipAddress); + + QString domain = hostname.remove(ipAddress); + if (domain.at(0) == '.') + { + domain.remove(0, 1); + } + obj.insert("domain", domain); + } + else + { + int domainPos = hostname.indexOf('.'); + obj.insert("hostname", hostname.left(domainPos)); + obj.insert("domain", hostname.mid(domainPos + 1)); + } } } diff --git a/libsrc/utils/CMakeLists.txt b/libsrc/utils/CMakeLists.txt index 6b3865ac..40e7efe6 100644 --- a/libsrc/utils/CMakeLists.txt +++ b/libsrc/utils/CMakeLists.txt @@ -1,5 +1,17 @@ # Define the current source locations +# Include the python directory. Also include the parent (which is for example /usr/include) +# which may be required when it is not includes by the (cross-) compiler by default. +if (NOT CMAKE_VERSION VERSION_LESS "3.12") + find_package(Python3 COMPONENTS Interpreter Development REQUIRED) + include_directories(${Python3_INCLUDE_DIRS} ${Python3_INCLUDE_DIRS}/..) + add_compile_definitions(PYTHON_VERSION_MAJOR_MINOR=${Python3_VERSION_MAJOR}${Python3_VERSION_MINOR}) +else() + find_package (PythonLibs ${PYTHON_VERSION_STRING} EXACT) # Maps PythonLibs to the PythonInterp version of the main cmake + include_directories(${PYTHON_INCLUDE_DIRS} ${PYTHON_INCLUDE_DIRS}/..) + add_definitions(-DPYTHON_VERSION_MAJOR_MINOR=${PYTHON_VERSION_MAJOR}${PYTHON_VERSION_MINOR}) +endif() + SET(CURRENT_HEADER_DIR ${CMAKE_SOURCE_DIR}/include/utils) SET(CURRENT_SOURCE_DIR ${CMAKE_SOURCE_DIR}/libsrc/utils) @@ -15,6 +27,7 @@ add_library(hyperion-utils target_link_libraries(hyperion-utils hyperion + python Qt5::Core Qt5::Gui Qt5::Network diff --git a/libsrc/utils/RgbChannelAdjustment.cpp b/libsrc/utils/RgbChannelAdjustment.cpp index b42616ea..50f9863e 100644 --- a/libsrc/utils/RgbChannelAdjustment.cpp +++ b/libsrc/utils/RgbChannelAdjustment.cpp @@ -15,10 +15,6 @@ RgbChannelAdjustment::RgbChannelAdjustment(uint8_t adjustR, uint8_t adjustG, uin setAdjustment(adjustR, adjustG, adjustB); } -RgbChannelAdjustment::~RgbChannelAdjustment() -{ -} - void RgbChannelAdjustment::resetInitialized() { //Debug(_log, "initialize mapping with %d,%d,%d", _adjust[RED], _adjust[GREEN], _adjust[BLUE]); diff --git a/libsrc/utils/RgbToRgbw.cpp b/libsrc/utils/RgbToRgbw.cpp index 1a4d12c5..f82fadf5 100644 --- a/libsrc/utils/RgbToRgbw.cpp +++ b/libsrc/utils/RgbToRgbw.cpp @@ -7,10 +7,22 @@ namespace RGBW { WhiteAlgorithm stringToWhiteAlgorithm(const QString& str) { - if (str == "subtract_minimum") return WhiteAlgorithm::SUBTRACT_MINIMUM; - if (str == "sub_min_warm_adjust") return WhiteAlgorithm::SUB_MIN_WARM_ADJUST; - if (str == "sub_min_cool_adjust") return WhiteAlgorithm::SUB_MIN_COOL_ADJUST; - if (str.isEmpty() || str == "white_off") return WhiteAlgorithm::WHITE_OFF; + if (str == "subtract_minimum") + { + return WhiteAlgorithm::SUBTRACT_MINIMUM; + } + if (str == "sub_min_warm_adjust") + { + return WhiteAlgorithm::SUB_MIN_WARM_ADJUST; + } + if (str == "sub_min_cool_adjust") + { + return WhiteAlgorithm::SUB_MIN_COOL_ADJUST; + } + if (str.isEmpty() || str == "white_off") + { + return WhiteAlgorithm::WHITE_OFF; + } return WhiteAlgorithm::INVALID; } @@ -20,7 +32,7 @@ void Rgb_to_Rgbw(ColorRgb input, ColorRgbw * output, WhiteAlgorithm algorithm) { case WhiteAlgorithm::SUBTRACT_MINIMUM: { - output->white = qMin(qMin(input.red, input.green), input.blue); + output->white = static_cast(qMin(qMin(input.red, input.green), input.blue)); output->red = input.red - output->white; output->green = input.green - output->white; output->blue = input.blue - output->white; @@ -31,14 +43,14 @@ void Rgb_to_Rgbw(ColorRgb input, ColorRgbw * output, WhiteAlgorithm algorithm) { // http://forum.garagecube.com/viewtopic.php?t=10178 // warm white - const float F1(0.274); - const float F2(0.454); - const float F3(2.333); + const double F1(0.274); + const double F2(0.454); + const double F3(2.333); - output->white = qMin(input.red*F1,qMin(input.green*F2,input.blue*F3)); - output->red = input.red - output->white/F1; - output->green = input.green - output->white/F2; - output->blue = input.blue - output->white/F3; + output->white = static_cast(qMin(input.red*F1,qMin(input.green*F2,input.blue*F3))); + output->red = input.red - static_cast(output->white/F1); + output->green = input.green - static_cast(output->white/F2); + output->blue = input.blue - static_cast(output->white/F3); break; } @@ -46,14 +58,14 @@ void Rgb_to_Rgbw(ColorRgb input, ColorRgbw * output, WhiteAlgorithm algorithm) { // http://forum.garagecube.com/viewtopic.php?t=10178 // cold white - const float F1(0.299); - const float F2(0.587); - const float F3(0.114); + const double F1(0.299); + const double F2(0.587); + const double F3(0.114); - output->white = qMin(input.red*F1,qMin(input.green*F2,input.blue*F3)); - output->red = input.red - output->white/F1; - output->green = input.green - output->white/F2; - output->blue = input.blue - output->white/F3; + output->white = static_cast(qMin(input.red*F1,qMin(input.green*F2,input.blue*F3))); + output->red = input.red - static_cast(output->white/F1); + output->green = input.green - static_cast(output->white/F2); + output->blue = input.blue - static_cast(output->white/F3); break; } diff --git a/libsrc/utils/SysInfo.cpp b/libsrc/utils/SysInfo.cpp index 7898ecde..3136899d 100644 --- a/libsrc/utils/SysInfo.cpp +++ b/libsrc/utils/SysInfo.cpp @@ -1,7 +1,15 @@ +// Python includes +#include + #include "utils/SysInfo.h" +#include "utils/FileUtils.h" #include #include +#include +#include + +#include SysInfo* SysInfo::_instance = nullptr; @@ -17,6 +25,9 @@ SysInfo::SysInfo() _sysinfo.prettyName = QSysInfo::prettyProductName(); _sysinfo.hostName = QHostInfo::localHostName(); _sysinfo.domainName = QHostInfo::localDomainName(); + getCPUInfo(); + _sysinfo.qtVersion = QT_VERSION_STR; + _sysinfo.pyVersion = PY_VERSION; } SysInfo::HyperionSysInfo SysInfo::get() @@ -26,3 +37,48 @@ SysInfo::HyperionSysInfo SysInfo::get() return SysInfo::_instance->_sysinfo; } + +void SysInfo::getCPUInfo() +{ + QString cpuInfo; + if( FileUtils::readFile("/proc/cpuinfo", cpuInfo, Logger::getInstance("DAEMON"), true) ) + { + QRegularExpression regEx ("^model\\s*:\\s(.*)", QRegularExpression::CaseInsensitiveOption | QRegularExpression::MultilineOption); + QRegularExpressionMatch match; + + match = regEx.match(cpuInfo); + if ( match.hasMatch() ) + { + _sysinfo.cpuModelType = match.captured(1); + } + + regEx.setPattern("^model name\\s*:\\s(.*)"); + match = regEx.match(cpuInfo); + if ( match.hasMatch() ) + { + _sysinfo.cpuModelName = match.captured(1); + } + + regEx.setPattern("^hardware\\s*:\\s(.*)"); + match = regEx.match(cpuInfo); + if ( match.hasMatch() ) + { + _sysinfo.cpuHardware = match.captured(1); + } + + regEx.setPattern("^revision\\s*:\\s(.*)"); + match = regEx.match(cpuInfo); + if ( match.hasMatch() ) + { + _sysinfo.cpuRevision = match.captured(1); + } + + regEx.setPattern("^revision\\s*:\\s(.*)"); + match = regEx.match(cpuInfo); + if ( match.hasMatch() ) + { + _sysinfo.cpuRevision = match.captured(1); + } + } +} + diff --git a/libsrc/webserver/QtHttpClientWrapper.cpp b/libsrc/webserver/QtHttpClientWrapper.cpp index 68bca8e0..f308a9f4 100644 --- a/libsrc/webserver/QtHttpClientWrapper.cpp +++ b/libsrc/webserver/QtHttpClientWrapper.cpp @@ -242,7 +242,7 @@ void QtHttpClientWrapper::onReplySendHeadersRequested (void) { QByteArray data; // HTTP Version + Status Code + Status Msg - data.append (QtHttpServer::HTTP_VERSION); + data.append (QtHttpServer::HTTP_VERSION.toUtf8()); data.append (SPACE); data.append (QByteArray::number (reply->getStatusCode ())); data.append (SPACE); diff --git a/libsrc/webserver/WebSocketClient.cpp b/libsrc/webserver/WebSocketClient.cpp index 1f865904..0e522072 100644 --- a/libsrc/webserver/WebSocketClient.cpp +++ b/libsrc/webserver/WebSocketClient.cpp @@ -223,7 +223,7 @@ void WebSocketClient::sendClose(int status, QString reason) sendBuffer.append(quint8(length)); } - sendBuffer.append(reason); + sendBuffer.append(reason.toUtf8()); _socket->write(sendBuffer); _socket->flush(); diff --git a/resources/icons/autorun.svg b/resources/icons/autorun.svg new file mode 100644 index 00000000..d71cae15 --- /dev/null +++ b/resources/icons/autorun.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/clear.svg b/resources/icons/clear.svg new file mode 100644 index 00000000..f5888f06 --- /dev/null +++ b/resources/icons/clear.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/color.svg b/resources/icons/color.svg new file mode 100644 index 00000000..0b4f6cca --- /dev/null +++ b/resources/icons/color.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/effects.svg b/resources/icons/effects.svg new file mode 100644 index 00000000..e9a4b649 --- /dev/null +++ b/resources/icons/effects.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/hyperion-icon-32px.png b/resources/icons/hyperion-icon-32px.png index 550ed558..ee60b83b 100644 Binary files a/resources/icons/hyperion-icon-32px.png and b/resources/icons/hyperion-icon-32px.png differ diff --git a/resources/icons/quit.svg b/resources/icons/quit.svg new file mode 100644 index 00000000..115603c2 --- /dev/null +++ b/resources/icons/quit.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/settings.svg b/resources/icons/settings.svg new file mode 100644 index 00000000..499a5f79 --- /dev/null +++ b/resources/icons/settings.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/hyperion-remote/hyperion-remote.cpp b/src/hyperion-remote/hyperion-remote.cpp index a1370f32..29be812a 100644 --- a/src/hyperion-remote/hyperion-remote.cpp +++ b/src/hyperion-remote/hyperion-remote.cpp @@ -27,7 +27,9 @@ int count(std::initializer_list values) int count = 0; for (bool value : values) { if (value) + { count++; + } } return count; } @@ -43,14 +45,16 @@ void showHelp(Option & option){ qWarning() << qPrintable(QString("\t%1\t%2\t%3").arg(shortOption, longOption, option.description())); } -int getInstaneIdbyName(const QJsonObject & reply, const QString name){ +int getInstaneIdbyName(const QJsonObject & reply, const QString & name){ if(reply.contains("instance")){ QJsonArray list = reply.value("instance").toArray(); - for (const auto & entry : list) { + for (const QJsonValueRef entry : list) { const QJsonObject obj = entry.toObject(); if(obj["friendly_name"] == name && obj["running"].toBool()) + { return obj["instance"].toInt(); + } } } std::cout << "Can't find a running instance with name '" << name.toStdString()<< "' at this Hyperion server, will use first instance" << std::endl; @@ -78,23 +82,23 @@ int main(int argc, char * argv[]) try { // create the option parser and initialize all parameters - Parser parser("Application to send a command to hyperion using the Json interface"); + Parser parser("Application to send a command to hyperion using the JSON interface"); /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // art variable definition append art to Parser short-, long option description, optional default value // /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// Option & argAddress = parser.add