mirror of
				https://github.com/hyperion-project/hyperion.ng.git
				synced 2025-03-01 10:33:28 +00:00 
			
		
		
		
	Merge branch 'hyperion-project:master' into temperture
This commit is contained in:
		
							
								
								
									
										9
									
								
								.github/workflows/codeql.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										9
									
								
								.github/workflows/codeql.yml
									
									
									
									
										vendored
									
									
								
							@@ -36,7 +36,12 @@ jobs:
 | 
			
		||||
        if: ${{ matrix.language == 'cpp' }}
 | 
			
		||||
        run: |
 | 
			
		||||
          sudo apt-get update
 | 
			
		||||
          sudo apt-get install --yes 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 libasound2-dev libturbojpeg0-dev libjpeg-dev libssl-dev
 | 
			
		||||
          sudo apt-get install --yes git 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 libasound2-dev libturbojpeg0-dev libjpeg-dev libssl-dev libftdi1-dev 
 | 
			
		||||
 | 
			
		||||
      - name: Temporarily downgrade CMake to 3.28.3 # Please remove if GitHub has updated Cmake (greater than 3.30.0)
 | 
			
		||||
        uses: jwlawson/actions-setup-cmake@v2
 | 
			
		||||
        with:
 | 
			
		||||
          cmake-version: '3.28.3'          
 | 
			
		||||
 | 
			
		||||
      - name: 🔁 Initialize CodeQL
 | 
			
		||||
        uses: github/codeql-action/init@v3
 | 
			
		||||
@@ -44,7 +49,7 @@ jobs:
 | 
			
		||||
          languages: ${{ matrix.language }}
 | 
			
		||||
          queries: +security-and-quality
 | 
			
		||||
          config-file: ./.github/config/codeql.yml
 | 
			
		||||
 | 
			
		||||
          
 | 
			
		||||
      - name: 👷 Autobuild
 | 
			
		||||
        uses: github/codeql-action/autobuild@v3
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										36
									
								
								.github/workflows/qt5_6.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										36
									
								
								.github/workflows/qt5_6.yml
									
									
									
									
										vendored
									
									
								
							@@ -117,9 +117,14 @@ jobs:
 | 
			
		||||
          echo '::group::Update/Install dependencies'
 | 
			
		||||
            brew untap --force homebrew/core homebrew/cask
 | 
			
		||||
            brew update || true
 | 
			
		||||
            brew install qt@${{ inputs.qt_version }} vulkan-headers ninja || true
 | 
			
		||||
            brew install qt@${{ inputs.qt_version }} vulkan-headers ninja libftdi || true
 | 
			
		||||
          echo '::endgroup::'
 | 
			
		||||
 | 
			
		||||
      - name: Temporarily downgrade CMake to 3.28.3 # Please remove if GitHub has updated Cmake (greater than 3.30.0)
 | 
			
		||||
        uses: jwlawson/actions-setup-cmake@v2
 | 
			
		||||
        with:
 | 
			
		||||
          cmake-version: '3.28.3'
 | 
			
		||||
 | 
			
		||||
      - name: 👷 Build
 | 
			
		||||
        shell: bash
 | 
			
		||||
        run: ./.github/scripts/build.sh
 | 
			
		||||
@@ -163,26 +168,32 @@ jobs:
 | 
			
		||||
        uses: actions/cache@v4
 | 
			
		||||
        with:
 | 
			
		||||
          path: C:\Users\runneradmin\AppData\Local\Temp\chocolatey
 | 
			
		||||
          key: ${{ runner.os }}${{ inputs.qt_version == '6' && '-chocolatey-qt6' || '-chocolatey' }}
 | 
			
		||||
          key: ${{ runner.os }}${{ '-chocolatey' }}
 | 
			
		||||
 | 
			
		||||
      - name: 📥 Install DirectX SDK, OpenSSL, libjpeg-turbo ${{ inputs.qt_version == '6' && 'and Vulkan-SDK' || '' }}
 | 
			
		||||
      - name: 📥 Install DirectX SDK, OpenSSL, libjpeg-turbo
 | 
			
		||||
        shell: powershell
 | 
			
		||||
        run: |
 | 
			
		||||
          choco install --no-progress directx-sdk ${{env.VULKAN_SDK}} -y
 | 
			
		||||
          choco install --no-progress directx-sdk -y
 | 
			
		||||
          choco install --no-progress ${{env.OPENSSL}} -y
 | 
			
		||||
          Invoke-WebRequest https://netcologne.dl.sourceforge.net/project/libjpeg-turbo/3.0.1/libjpeg-turbo-3.0.1-vc64.exe -OutFile libjpeg-turbo.exe -UserAgent NativeHost
 | 
			
		||||
          .\libjpeg-turbo /S
 | 
			
		||||
        env:
 | 
			
		||||
          VULKAN_SDK: ${{ inputs.qt_version == '6' && 'vulkan-sdk' || '' }}
 | 
			
		||||
          OPENSSL: ${{ inputs.qt_version == '6' && 'openssl' || 'openssl --version=1.1.1.2100' }}
 | 
			
		||||
 | 
			
		||||
      - name: 📥 Install Qt
 | 
			
		||||
        uses: jurplel/install-qt-action@v3
 | 
			
		||||
      - name: Install Vulkan SDK
 | 
			
		||||
        if: ${{ inputs.qt_version == '6' }}
 | 
			
		||||
        uses: jakoch/install-vulkan-sdk-action@v1.0.4
 | 
			
		||||
        with:
 | 
			
		||||
          version: ${{ inputs.qt_version == '6' && '6.5.2' || '5.15.2' }}
 | 
			
		||||
          install_runtime: false
 | 
			
		||||
          cache: true
 | 
			
		||||
          stripdown: true
 | 
			
		||||
 | 
			
		||||
      - name: 📥 Install Qt
 | 
			
		||||
        uses: jurplel/install-qt-action@v4
 | 
			
		||||
        with:
 | 
			
		||||
          version: ${{ inputs.qt_version == '6' && '6.7' || '5.15.*' }}
 | 
			
		||||
          target: 'desktop'
 | 
			
		||||
          modules: ${{ inputs.qt_version == '6' && 'qtserialport' || '' }}
 | 
			
		||||
          arch: 'win64_msvc2019_64'
 | 
			
		||||
          cache: 'true'
 | 
			
		||||
          cache-key-prefix: 'cache-qt-windows'
 | 
			
		||||
 | 
			
		||||
@@ -190,6 +201,11 @@ jobs:
 | 
			
		||||
        shell: cmd
 | 
			
		||||
        run: call "${{env.VCINSTALLDIR}}\Auxiliary\Build\vcvars64.bat"
 | 
			
		||||
 | 
			
		||||
      - name: Temporarily downgrade CMake to 3.28.3 # Please remove if GitHub has updated Cmake (greater than 3.30.0)
 | 
			
		||||
        uses: jwlawson/actions-setup-cmake@v2
 | 
			
		||||
        with:
 | 
			
		||||
          cmake-version: '3.28.3'
 | 
			
		||||
 | 
			
		||||
      - name: 👷 Build
 | 
			
		||||
        shell: bash
 | 
			
		||||
        run: ./.github/scripts/build.sh
 | 
			
		||||
@@ -226,7 +242,7 @@ jobs:
 | 
			
		||||
          echo '::endgroup::'
 | 
			
		||||
 | 
			
		||||
      - name: 💾 Artifact download
 | 
			
		||||
        uses: actions/download-artifact@v4.1.7
 | 
			
		||||
        uses: actions/download-artifact@v4.1.8
 | 
			
		||||
        with:
 | 
			
		||||
          pattern: artifact-*
 | 
			
		||||
          path: all-artifacts
 | 
			
		||||
 
 | 
			
		||||
@@ -13,6 +13,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 | 
			
		||||
 | 
			
		||||
### Added
 | 
			
		||||
 | 
			
		||||
- Support for ftdi chip based LED-devices with ws2812, sk6812 apa102 LED types (Many thanks to @nurikk) (#1746)
 | 
			
		||||
- Support for Skydimo devices (being an Adalight variant)
 | 
			
		||||
- Support gaps on Matrix Layout (#1696)
 | 
			
		||||
- Windows: Added a new grabber that uses the DXGI DDA (Desktop Duplication API). This has much better performance than the DX grabber as it does more of its work on the GPU.
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -94,6 +94,7 @@ set(DEFAULT_DEV_SPI                     OFF)
 | 
			
		||||
set(DEFAULT_DEV_TINKERFORGE             OFF)
 | 
			
		||||
set(DEFAULT_DEV_USB_HID                 OFF)
 | 
			
		||||
set(DEFAULT_DEV_WS281XPWM               OFF)
 | 
			
		||||
set(DEFAULT_DEV_FTDI                    ON )
 | 
			
		||||
 | 
			
		||||
# Services
 | 
			
		||||
set(DEFAULT_EFFECTENGINE                ON )
 | 
			
		||||
@@ -121,9 +122,10 @@ if(${CMAKE_SYSTEM} MATCHES "Linux")
 | 
			
		||||
	set(DEFAULT_DEV_USB_HID     ON)
 | 
			
		||||
	set(DEFAULT_CEC             ON)
 | 
			
		||||
elseif (WIN32)
 | 
			
		||||
	set(DEFAULT_DX ON)
 | 
			
		||||
	set(DEFAULT_DDA ON)
 | 
			
		||||
	set(DEFAULT_MF ON)
 | 
			
		||||
	set(DEFAULT_DX              ON )
 | 
			
		||||
  set(DEFAULT_DDA             ON )
 | 
			
		||||
	set(DEFAULT_MF              ON )
 | 
			
		||||
	set(DEFAULT_DEV_FTDI        OFF)
 | 
			
		||||
else()
 | 
			
		||||
	set(DEFAULT_FB              OFF)
 | 
			
		||||
	set(DEFAULT_V4L2            OFF)
 | 
			
		||||
@@ -364,6 +366,9 @@ message(STATUS "ENABLE_DEV_USB_HID = ${ENABLE_DEV_USB_HID}")
 | 
			
		||||
option(ENABLE_DEV_WS281XPWM "Enable the WS281x-PWM device" ${DEFAULT_DEV_WS281XPWM})
 | 
			
		||||
message(STATUS "ENABLE_DEV_WS281XPWM = ${ENABLE_DEV_WS281XPWM}")
 | 
			
		||||
 | 
			
		||||
option(ENABLE_DEV_FTDI "Enable the FTDI devices" ${DEFAULT_DEV_FTDI} )
 | 
			
		||||
message(STATUS "ENABLE_DEV_FTDI = ${ENABLE_DEV_FTDI}")
 | 
			
		||||
 | 
			
		||||
removeIndent()
 | 
			
		||||
 | 
			
		||||
message(STATUS "Services options:")
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										14
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								README.md
									
									
									
									
									
								
							@@ -24,14 +24,14 @@
 | 
			
		||||
* Low CPU load makes it perfect for SoCs like Raspberry Pi
 | 
			
		||||
* Json interface which allows easy integration into scripts
 | 
			
		||||
* A command line utility for testing and integration in automated environment
 | 
			
		||||
* Priority channels are not coupled to a specific led data provider which means that a provider can post led data and leave without the need to maintain a connection to Hyperion. This is ideal for a remote application (like our [Android app](https://play.google.com/store/apps/details?id=nl.hyperion.hyperionpro)).
 | 
			
		||||
* Priority channels are not coupled to a specific led data provider which means that a provider can post led data and leave without the need to maintain a connection to Hyperion. This is ideal for a remote application (like our former [Android app](https://play.google.com/store/apps/details?id=nl.hyperion.hyperionpro), which is no longer available).
 | 
			
		||||
* Black border detector and processor
 | 
			
		||||
* A scriptable (Python) effect engine with 39 build-in effects for your inspiration
 | 
			
		||||
* A multi language web interface to configure and remote control hyperion
 | 
			
		||||
 | 
			
		||||
### Supported Hardware
 | 
			
		||||
 | 
			
		||||
You can find a list of supported hardware [here](https://docs.hyperion-project.org/en/user/leddevices/).
 | 
			
		||||
You can find a list of supported hardware [here](https://docs.hyperion-project.org/user/leddevices/Overview.html).
 | 
			
		||||
 | 
			
		||||
If you need further support please open a topic at the forum!<br>
 | 
			
		||||
[](https://www.hyperion-project.org)
 | 
			
		||||
@@ -50,10 +50,10 @@ Find here more details on [supported platforms and configuration sets](doc/devel
 | 
			
		||||
 | 
			
		||||
## Documentation
 | 
			
		||||
Covers these topics:
 | 
			
		||||
- [Installation](https://docs.hyperion-project.org/en/user/Installation.html)
 | 
			
		||||
- [Configuration](https://docs.hyperion-project.org/en/user/Configuration.html)
 | 
			
		||||
- [Effect development](https://docs.hyperion-project.org/en/effects/#effect-files)
 | 
			
		||||
- [JSON API](https://docs.hyperion-project.org/en/json/)
 | 
			
		||||
- [Getting Started and Installation](https://docs.hyperion-project.org/user/GettingStarted.html)
 | 
			
		||||
- [Configuration](https://docs.hyperion-project.org/user/Configuration.html)
 | 
			
		||||
- [Effect development](https://docs.hyperion-project.org/effects/Effects.html)
 | 
			
		||||
- [JSON API](https://docs.hyperion-project.org/json/JSON.html)
 | 
			
		||||
 | 
			
		||||
[](https://docs.hyperion-project.org)
 | 
			
		||||
 | 
			
		||||
@@ -64,7 +64,7 @@ Released and unreleased changes at [CHANGELOG.md](CHANGELOG.md).
 | 
			
		||||
See [CompileHowto.md](doc/development/CompileHowto.md).
 | 
			
		||||
 | 
			
		||||
## Installation
 | 
			
		||||
See [Documentation](https://docs.hyperion-project.org/en/user/Installation.html) or on the [Release Repository](https://releases.hyperion-project.org).
 | 
			
		||||
See [Getting Started](https://docs.hyperion-project.org/user/GettingStarted.html) or on the [Release Repository](https://releases.hyperion-project.org).
 | 
			
		||||
 | 
			
		||||
## Download
 | 
			
		||||
GitHub Releases are available on the [Hyperion release page](https://github.com/hyperion-project/hyperion.ng/releases).
 | 
			
		||||
 
 | 
			
		||||
@@ -159,8 +159,9 @@
 | 
			
		||||
    "conf_leds_note_layout_overwrite": "Achtung: Überschreiben erzeugt ein Standardlayout für {{plural:$1| eine LED| alle $1 LEDs}} gemäß der gegebenen Hardware LED-Anzahl",
 | 
			
		||||
    "conf_leds_optgroup_RPiGPIO": "RPi GPIO",
 | 
			
		||||
    "conf_leds_optgroup_RPiPWM": "RPi PWM",
 | 
			
		||||
    "conf_leds_optgroup_RPiSPI": "RPi SPI",
 | 
			
		||||
    "conf_leds_optgroup_SPI": "SPI",
 | 
			
		||||
    "conf_leds_optgroup_debug": "Debug",
 | 
			
		||||
    "conf_leds_optgroup_ftdi": "USB/Ftdi",
 | 
			
		||||
    "conf_leds_optgroup_network": "Netzwerk",
 | 
			
		||||
    "conf_leds_optgroup_other": "Andere",
 | 
			
		||||
    "conf_leds_optgroup_usb": "USB/Seriell",
 | 
			
		||||
@@ -191,6 +192,7 @@
 | 
			
		||||
    "conf_network_tok_diaTitle": "Neues Token erstellt!",
 | 
			
		||||
    "conf_network_tok_grantMsg": "Eine App fordert Zugriff auf die Hyperion API durch ein Token. Möchtest du dies zulassen? Bitte überprüfe die angegebenen Informationen!",
 | 
			
		||||
    "conf_network_tok_grantT": "App Token angefordert",
 | 
			
		||||
    "conf_network_tok_idhead": "ID",
 | 
			
		||||
    "conf_network_tok_intro": "Hier kannst du Token zur API-Authentifizierung erstellen oder löschen. Neu erstellte Token werden einmalig angezeigt.",
 | 
			
		||||
    "conf_network_tok_lastuse": "Zuletzt genutzt",
 | 
			
		||||
    "conf_network_tok_title": "Token Management",
 | 
			
		||||
@@ -611,6 +613,11 @@
 | 
			
		||||
    "edt_conf_webc_sslport_title": "HTTPS Port",
 | 
			
		||||
    "edt_dev_auth_key_title": "Autorisierungs-Token",
 | 
			
		||||
    "edt_dev_auth_key_title_info": "Autorisierungs-Token für den Zugriff auf das Gerät erforderlich",
 | 
			
		||||
    "edt_dev_enum_auto": "Auto",
 | 
			
		||||
    "edt_dev_enum_auto_accurate": "Auto genau",
 | 
			
		||||
    "edt_dev_enum_auto_max": "Auto maximal",
 | 
			
		||||
    "edt_dev_enum_cold_white": "Kaltweiß",
 | 
			
		||||
    "edt_dev_enum_neutral_white": "Neutralweiß",
 | 
			
		||||
    "edt_dev_enum_sub_min_cool_adjust": "Minimale Anpassung: cool",
 | 
			
		||||
    "edt_dev_enum_sub_min_warm_adjust": "Minimale Anpassung: warm",
 | 
			
		||||
    "edt_dev_enum_subtract_minimum": "Subtrahiere Minimum",
 | 
			
		||||
@@ -735,7 +742,7 @@
 | 
			
		||||
    "edt_dev_spec_username_title": "Benutzername",
 | 
			
		||||
    "edt_dev_spec_verbose_title": "Protokollierung der HUE-Kommandos",
 | 
			
		||||
    "edt_dev_spec_vid_title": "VID",
 | 
			
		||||
    "edt_dev_spec_whiteLedAlgor_title": "Weiß Algorithmus",
 | 
			
		||||
    "edt_dev_spec_whiteLedAlgor_title": "Weißabgleich Algorithmus",
 | 
			
		||||
    "edt_dev_spec_whitepoint_title": "Weißpunkt",
 | 
			
		||||
    "edt_eff_alarmcolor": "Alarm Farbe",
 | 
			
		||||
    "edt_eff_backgroundColor": "Hintergrundfarbe",
 | 
			
		||||
@@ -962,6 +969,7 @@
 | 
			
		||||
    "general_country_us": "Amerika",
 | 
			
		||||
    "general_disabled": "deaktiviert",
 | 
			
		||||
    "general_enabled": "aktiviert",
 | 
			
		||||
    "general_speech_bg": "Bulgarisch",
 | 
			
		||||
    "general_speech_ca": "Katalanisch",
 | 
			
		||||
    "general_speech_cs": "Tschechisch",
 | 
			
		||||
    "general_speech_da": "Dänisch",
 | 
			
		||||
 
 | 
			
		||||
@@ -85,6 +85,7 @@
 | 
			
		||||
    "conf_leds_layout_cl_bottomleft": "Bottom Left (Corner)",
 | 
			
		||||
    "conf_leds_layout_cl_bottomright": "Bottom Right (Corner)",
 | 
			
		||||
    "conf_leds_layout_cl_cornergap": "Corner Gap",
 | 
			
		||||
    "conf_leds_layout_cl_disabled": "Deactivated",
 | 
			
		||||
    "conf_leds_layout_cl_edgegap": "Edge Gap",
 | 
			
		||||
    "conf_leds_layout_cl_entertainment": "Entertainment Area",
 | 
			
		||||
    "conf_leds_layout_cl_entertainment_center": "Entertainment Area Center",
 | 
			
		||||
@@ -103,6 +104,7 @@
 | 
			
		||||
    "conf_leds_layout_cl_lightPosBottomLeft112": "Bottom: 0  - 50%  from Left",
 | 
			
		||||
    "conf_leds_layout_cl_lightPosBottomLeft121": "Bottom: 50 - 100% from Left",
 | 
			
		||||
    "conf_leds_layout_cl_lightPosBottomLeftNewMid": "Bottom: 25  - 75%  from Left",
 | 
			
		||||
    "conf_leds_layout_cl_lightPosEntire": "Whole picture",
 | 
			
		||||
    "conf_leds_layout_cl_lightPosTopLeft112": "Top: 0  - 50%  from Left",
 | 
			
		||||
    "conf_leds_layout_cl_lightPosTopLeft121": "Top: 50 - 100% from Left",
 | 
			
		||||
    "conf_leds_layout_cl_lightPosTopLeftNewMid": "Top: 25  - 75%  from Left",
 | 
			
		||||
@@ -161,11 +163,12 @@
 | 
			
		||||
    "conf_leds_note_layout_overwrite": "Note: Overwrite creates a default layout for {{plural:$1| one LED| all $1 LEDs}} given by the hardware LED count",
 | 
			
		||||
    "conf_leds_optgroup_RPiGPIO": "RPi GPIO",
 | 
			
		||||
    "conf_leds_optgroup_RPiPWM": "RPi PWM",
 | 
			
		||||
    "conf_leds_optgroup_RPiSPI": "RPi SPI",
 | 
			
		||||
    "conf_leds_optgroup_SPI": "SPI",
 | 
			
		||||
    "conf_leds_optgroup_debug": "Debug",
 | 
			
		||||
    "conf_leds_optgroup_network": "Network",
 | 
			
		||||
    "conf_leds_optgroup_other": "Other",
 | 
			
		||||
    "conf_leds_optgroup_usb": "USB/Serial",
 | 
			
		||||
    "conf_leds_optgroup_ftdi": "USB/Ftdi",
 | 
			
		||||
    "conf_logging_btn_autoscroll": "Auto scrolling",
 | 
			
		||||
    "conf_logging_btn_clipboard": "Copy Log to Clipboard",
 | 
			
		||||
    "conf_logging_btn_pbupload": "Upload a report for support requests",
 | 
			
		||||
@@ -624,6 +627,11 @@
 | 
			
		||||
    "edt_dev_enum_sub_min_cool_adjust": "Subtract cool white",
 | 
			
		||||
    "edt_dev_enum_sub_min_warm_adjust": "Subtract warm white",
 | 
			
		||||
    "edt_dev_enum_subtract_minimum": "Subtract minimum",
 | 
			
		||||
    "edt_dev_enum_cold_white": "Cold white",
 | 
			
		||||
    "edt_dev_enum_neutral_white": "Neutral white",
 | 
			
		||||
    "edt_dev_enum_auto": "Auto",
 | 
			
		||||
    "edt_dev_enum_auto_max": "Auto max",
 | 
			
		||||
    "edt_dev_enum_auto_accurate": "Auto accurate",
 | 
			
		||||
    "edt_dev_enum_white_off": "White off",
 | 
			
		||||
    "edt_dev_general_autostart_title": "Autostart",
 | 
			
		||||
    "edt_dev_general_autostart_title_info": "The LED device is switched-on during startup or not",
 | 
			
		||||
@@ -660,13 +668,14 @@
 | 
			
		||||
    "edt_dev_spec_colorComponent_title": "Colour component",
 | 
			
		||||
    "edt_dev_spec_debugLevel_title": "Debug Level",
 | 
			
		||||
    "edt_dev_spec_delayAfterConnect_title": "Delay after connect",
 | 
			
		||||
    "edt_dev_spec_devices_discovered_none": "No Devices Discovered",
 | 
			
		||||
    "edt_dev_spec_devices_discovered_title": "Devices Discovered",
 | 
			
		||||
    "edt_dev_spec_devices_discovered_none": "No Devices discovered",
 | 
			
		||||
    "edt_dev_spec_devices_discovered_title": "Devices discovered",
 | 
			
		||||
    "edt_dev_spec_devices_discovered_title_info": "Select your LED-Device discovered",
 | 
			
		||||
    "edt_dev_spec_devices_discovered_title_info_custom": "Select your LED-Device discovered or configure a custome one",
 | 
			
		||||
    "edt_dev_spec_devices_discovery_inprogress": "Discovery in progress",
 | 
			
		||||
    "edt_dev_spec_dithering_title": "Dithering",
 | 
			
		||||
    "edt_dev_spec_dmaNumber_title": "DMA channel",
 | 
			
		||||
    "edt_dev_spec_fullBrightnessAtStart_title": "Full brightness at start",    
 | 
			
		||||
    "edt_dev_spec_gamma_title": "Gamma",
 | 
			
		||||
    "edt_dev_spec_globalBrightnessControlMaxLevel_title": "Max Current Level",
 | 
			
		||||
    "edt_dev_spec_globalBrightnessControlThreshold_title": "Adaptive Current Threshold",
 | 
			
		||||
@@ -684,6 +693,7 @@
 | 
			
		||||
    "edt_dev_spec_ledType_title": "LED Type",
 | 
			
		||||
    "edt_dev_spec_lightid_itemtitle": "ID",
 | 
			
		||||
    "edt_dev_spec_lightid_title": "Light ID(s)",
 | 
			
		||||
    "edt_dev_spec_lights_discovered_none": "No Lights discovered",
 | 
			
		||||
    "edt_dev_spec_lights_itemtitle": "Light",
 | 
			
		||||
    "edt_dev_spec_lights_name": "Name",
 | 
			
		||||
    "edt_dev_spec_lights_title": "Light(s)",
 | 
			
		||||
@@ -705,6 +715,7 @@
 | 
			
		||||
    "edt_dev_spec_port_expl": "Service Port [1-65535]",
 | 
			
		||||
    "edt_dev_spec_port_title": "Port",
 | 
			
		||||
    "edt_dev_spec_printTimeStamp_title": "Add timestamp",
 | 
			
		||||
    "edt_dev_spec_skydimo_mode_title": "Skydimo Mode",
 | 
			
		||||
    "edt_dev_spec_stream_protocol_title": "Streaming protocol",
 | 
			
		||||
    "edt_dev_spec_pwmChannel_title": "PWM channel",
 | 
			
		||||
    "edt_dev_spec_razer_device_title": "Razer Chroma Device",
 | 
			
		||||
@@ -1182,9 +1193,10 @@
 | 
			
		||||
    "wiz_identify_tip": "Identify configured device by lighting it up",
 | 
			
		||||
    "wiz_identify_light": "Identify $1",
 | 
			
		||||
    "wiz_layout": "Generate Layout",
 | 
			
		||||
    "wiz_layout_led_position_title": "LED position",
 | 
			
		||||
    "wiz_layout_led_positions_title": "LED position layout wizard",
 | 
			
		||||
    "wiz_layout_led_positions_expl": "Select the LED position for the $1 controller lights.",
 | 
			
		||||
    "wiz_layout_tip": "Generate a layout for the configured device",
 | 
			
		||||
    "wiz_ids_disabled": "Deactivated",
 | 
			
		||||
    "wiz_ids_entire": "Whole picture",
 | 
			
		||||
    "wiz_nanoleaf_failure_auth_token": "Please press the Nanoleaf Power On/Off button within 30 seconds",
 | 
			
		||||
    "wiz_nanoleaf_failure_auth_token_t": "User authorization token generating timeout",
 | 
			
		||||
    "wiz_nanoleaf_press_onoff_button": "Please press the Power On/Off button on your Nanoleaf device for 5-7 seconds",
 | 
			
		||||
 
 | 
			
		||||
@@ -159,8 +159,9 @@
 | 
			
		||||
    "conf_leds_note_layout_overwrite": "Varning: Åsidosättande skapar en standardlayout för {{plural:$1| en LED| varje $1 lysdioder}} enligt det givna antalet lysdioder för hårdvara",
 | 
			
		||||
    "conf_leds_optgroup_RPiGPIO": "RPi GPIO",
 | 
			
		||||
    "conf_leds_optgroup_RPiPWM": "RPi PWM",
 | 
			
		||||
    "conf_leds_optgroup_RPiSPI": "RPi SPI",
 | 
			
		||||
    "conf_leds_optgroup_SPI": "SPI",
 | 
			
		||||
    "conf_leds_optgroup_debug": "Felsöka",
 | 
			
		||||
    "conf_leds_optgroup_ftdi": "USB/Ftdi",
 | 
			
		||||
    "conf_leds_optgroup_network": "Nätverk",
 | 
			
		||||
    "conf_leds_optgroup_other": "Annat",
 | 
			
		||||
    "conf_leds_optgroup_usb": "USB/Seriell",
 | 
			
		||||
@@ -612,6 +613,11 @@
 | 
			
		||||
    "edt_conf_webc_sslport_title": "HTTPS-Port",
 | 
			
		||||
    "edt_dev_auth_key_title": "Auktorisationsnyckel",
 | 
			
		||||
    "edt_dev_auth_key_title_info": "Auktorisationsnyckel krävs för att få åtkomst till enheten",
 | 
			
		||||
    "edt_dev_enum_auto": "Auto",
 | 
			
		||||
    "edt_dev_enum_auto_accurate": "Auto noggrann",
 | 
			
		||||
    "edt_dev_enum_auto_max": "Auto max",
 | 
			
		||||
    "edt_dev_enum_cold_white": "Kallvitt",
 | 
			
		||||
    "edt_dev_enum_neutral_white": "Neutralvitt",
 | 
			
		||||
    "edt_dev_enum_sub_min_cool_adjust": "Minsta justering: kall",
 | 
			
		||||
    "edt_dev_enum_sub_min_warm_adjust": "Minsta justering: varm",
 | 
			
		||||
    "edt_dev_enum_subtract_minimum": "Subtrahera minimum",
 | 
			
		||||
@@ -963,6 +969,7 @@
 | 
			
		||||
    "general_country_us": "USA",
 | 
			
		||||
    "general_disabled": "Inaktiverad",
 | 
			
		||||
    "general_enabled": "Aktiverad",
 | 
			
		||||
    "general_speech_bg": "Bulgariska",
 | 
			
		||||
    "general_speech_ca": "Katalanska",
 | 
			
		||||
    "general_speech_cs": "Tjeckiska",
 | 
			
		||||
    "general_speech_da": "Danska",
 | 
			
		||||
 
 | 
			
		||||
@@ -197,7 +197,7 @@ $(document).ready(function () {
 | 
			
		||||
      removeStorage("loginToken");
 | 
			
		||||
      requestRequiresDefaultPasswortChange();
 | 
			
		||||
    }
 | 
			
		||||
    else if (event.reason == "Selected Hyperion instance isn't running") {
 | 
			
		||||
    else if (event.reason == "Selected Hyperion instance is not running") {
 | 
			
		||||
      //Switch to default instance
 | 
			
		||||
      instanceSwitch(0);
 | 
			
		||||
    } else {
 | 
			
		||||
 
 | 
			
		||||
@@ -18,10 +18,11 @@ var bottomRight2bottomLeft = null;
 | 
			
		||||
var bottomLeft2topLeft = null;
 | 
			
		||||
var toggleKeystoneCorrectionArea = false;
 | 
			
		||||
 | 
			
		||||
var devRPiSPI = ['apa102', 'apa104', 'ws2801', 'lpd6803', 'lpd8806', 'p9813', 'sk6812spi', 'sk6822spi', 'sk9822', 'ws2812spi'];
 | 
			
		||||
var devSPI = ['apa102', 'apa104', 'ws2801', 'lpd6803', 'lpd8806', 'p9813', 'sk6812spi', 'sk6822spi', 'sk9822', 'ws2812spi'];
 | 
			
		||||
var devFTDI = ['apa102_ftdi', 'sk6812_ftdi', 'ws2812_ftdi'];
 | 
			
		||||
var devRPiPWM = ['ws281x'];
 | 
			
		||||
var devRPiGPIO = ['piblaster'];
 | 
			
		||||
var devNET = ['atmoorb', 'cololight', 'fadecandy', 'philipshue', 'nanoleaf', 'razer', 'tinkerforge', 'tpm2net', 'udpe131', 'udpartnet', 'udpddp', 'udph801', 'udpraw', 'wled', 'yeelight'];
 | 
			
		||||
var devNET = ['atmoorb', 'cololight', 'fadecandy', 'homeassistant', 'philipshue', 'nanoleaf', 'razer', 'tinkerforge', 'tpm2net', 'udpe131', 'udpartnet', 'udpddp', 'udph801', 'udpraw', 'wled', 'yeelight'];
 | 
			
		||||
var devSerial = ['adalight', 'dmx', 'atmo', 'sedu', 'tpm2', 'karate'];
 | 
			
		||||
var devHID = ['hyperionusbasp', 'lightpack', 'paintpack', 'rawhid'];
 | 
			
		||||
 | 
			
		||||
@@ -1099,6 +1100,7 @@ $(document).ready(function () {
 | 
			
		||||
      switch (ledType) {
 | 
			
		||||
        case "wled":
 | 
			
		||||
        case "cololight":
 | 
			
		||||
        case "homeassistant":
 | 
			
		||||
        case "nanoleaf":
 | 
			
		||||
          showAllDeviceInputOptions("hostList", false);
 | 
			
		||||
        case "apa102":
 | 
			
		||||
@@ -1121,6 +1123,12 @@ $(document).ready(function () {
 | 
			
		||||
        case "karate":
 | 
			
		||||
        case "sedu":
 | 
			
		||||
        case "tpm2":
 | 
			
		||||
 | 
			
		||||
        //FTDI devices
 | 
			
		||||
        case "apa102_ftdi":
 | 
			
		||||
        case "sk6812_ftdi":
 | 
			
		||||
        case "ws2812_ftdi":
 | 
			
		||||
 | 
			
		||||
          if (storedAccess === 'expert') {
 | 
			
		||||
            filter.discoverAll = true;
 | 
			
		||||
          }
 | 
			
		||||
@@ -1139,6 +1147,7 @@ $(document).ready(function () {
 | 
			
		||||
            .catch(error => {
 | 
			
		||||
              showNotification('danger', "Device discovery for " + ledType + " failed with error:" + error);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
          break;
 | 
			
		||||
 | 
			
		||||
        case "philipshue": {
 | 
			
		||||
@@ -1271,7 +1280,21 @@ $(document).ready(function () {
 | 
			
		||||
          if (hostList !== "SELECT") {
 | 
			
		||||
            const host = conf_editor.getEditor("root.specificOptions.host").getValue();
 | 
			
		||||
            const token = conf_editor.getEditor("root.specificOptions.token").getValue();
 | 
			
		||||
            if (host !== "" && token !== "") {
 | 
			
		||||
            if (host !== "" && token !== "" && entityIds) {
 | 
			
		||||
              canIdentify = true;
 | 
			
		||||
              canSave = true;
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
          break;
 | 
			
		||||
 | 
			
		||||
        case "homeassistant": {
 | 
			
		||||
          const hostList = conf_editor.getEditor("root.specificOptions.hostList").getValue();
 | 
			
		||||
          if (hostList !== "SELECT") {
 | 
			
		||||
            const host = conf_editor.getEditor("root.specificOptions.host").getValue();
 | 
			
		||||
            const token = conf_editor.getEditor("root.specificOptions.token").getValue();
 | 
			
		||||
            const entityIds = conf_editor.getEditor("root.specificOptions.entityIds").getValue();
 | 
			
		||||
            if (host !== "" && token !== "" && entityIds) {
 | 
			
		||||
              canIdentify = true;
 | 
			
		||||
              canSave = true;
 | 
			
		||||
            }
 | 
			
		||||
@@ -1379,6 +1402,16 @@ $(document).ready(function () {
 | 
			
		||||
            getProperties_device(ledType, host, params);
 | 
			
		||||
            break;
 | 
			
		||||
 | 
			
		||||
          case "homeassistant":
 | 
			
		||||
            var token = conf_editor.getEditor("root.specificOptions.token").getValue();
 | 
			
		||||
            if (token === "") {
 | 
			
		||||
              return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            params = { host: host, token: token, filter: "states" };
 | 
			
		||||
            getProperties_device(ledType, host, params);
 | 
			
		||||
            break;
 | 
			
		||||
 | 
			
		||||
          case "nanoleaf":
 | 
			
		||||
            $('#btn_wiz_holder').show();
 | 
			
		||||
 | 
			
		||||
@@ -1441,6 +1474,9 @@ $(document).ready(function () {
 | 
			
		||||
          case "sk9822":
 | 
			
		||||
          case "ws2812spi":
 | 
			
		||||
          case "piblaster":
 | 
			
		||||
          case "apa102_ftdi":
 | 
			
		||||
          case "sk6812_ftdi":
 | 
			
		||||
          case "ws2812_ftdi":
 | 
			
		||||
          default:
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -1541,6 +1577,14 @@ $(document).ready(function () {
 | 
			
		||||
 | 
			
		||||
        var host = "";
 | 
			
		||||
        switch (ledType) {
 | 
			
		||||
          case "homeassistant":
 | 
			
		||||
            host = conf_editor.getEditor("root.specificOptions.host").getValue();
 | 
			
		||||
            if (host === "") {
 | 
			
		||||
              return
 | 
			
		||||
            }
 | 
			
		||||
            params = { host: host, token: token, filter: "states" };
 | 
			
		||||
            break;
 | 
			
		||||
 | 
			
		||||
          case "nanoleaf":
 | 
			
		||||
            host = conf_editor.getEditor("root.specificOptions.host").getValue();
 | 
			
		||||
            if (host === "") {
 | 
			
		||||
@@ -1643,6 +1687,16 @@ $(document).ready(function () {
 | 
			
		||||
        default:
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    conf_editor.watch('root.specificOptions.entityIds', () => {
 | 
			
		||||
      var entityIds = conf_editor.getEditor("root.specificOptions.entityIds").getValue();
 | 
			
		||||
      if (entityIds.length > 0) {
 | 
			
		||||
        $('#btn_test_controller').prop('disabled', false);
 | 
			
		||||
      } else {
 | 
			
		||||
        $('#btn_test_controller').prop('disabled', true);
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  //philipshueentertainment backward fix
 | 
			
		||||
@@ -1657,9 +1711,10 @@ $(document).ready(function () {
 | 
			
		||||
  optArr[3] = [];
 | 
			
		||||
  optArr[4] = [];
 | 
			
		||||
  optArr[5] = [];
 | 
			
		||||
  optArr[6] = [];
 | 
			
		||||
 | 
			
		||||
  for (var idx = 0; idx < ledDevices.length; idx++) {
 | 
			
		||||
    if ($.inArray(ledDevices[idx], devRPiSPI) != -1)
 | 
			
		||||
    if ($.inArray(ledDevices[idx], devSPI) != -1)
 | 
			
		||||
      optArr[0].push(ledDevices[idx]);
 | 
			
		||||
    else if ($.inArray(ledDevices[idx], devRPiPWM) != -1)
 | 
			
		||||
      optArr[1].push(ledDevices[idx]);
 | 
			
		||||
@@ -1671,8 +1726,12 @@ $(document).ready(function () {
 | 
			
		||||
      optArr[4].push(ledDevices[idx]);
 | 
			
		||||
    else if ($.inArray(ledDevices[idx], devHID) != -1)
 | 
			
		||||
      optArr[4].push(ledDevices[idx]);
 | 
			
		||||
    else if (ledDevices[idx].endsWith("_ftdi")) {
 | 
			
		||||
      var title = ledDevices[idx].replace('_ftdi', '');
 | 
			
		||||
      optArr[5].push(ledDevices[idx] + ":" + title);
 | 
			
		||||
    }
 | 
			
		||||
    else
 | 
			
		||||
      optArr[5].push(ledDevices[idx]);
 | 
			
		||||
      optArr[6].push(ledDevices[idx]);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  $("#leddevices").append(createSel(optArr[0], $.i18n('conf_leds_optgroup_RPiSPI')));
 | 
			
		||||
@@ -1680,9 +1739,10 @@ $(document).ready(function () {
 | 
			
		||||
  $("#leddevices").append(createSel(optArr[2], $.i18n('conf_leds_optgroup_RPiGPIO')));
 | 
			
		||||
  $("#leddevices").append(createSel(optArr[3], $.i18n('conf_leds_optgroup_network')));
 | 
			
		||||
  $("#leddevices").append(createSel(optArr[4], $.i18n('conf_leds_optgroup_usb')));
 | 
			
		||||
  $("#leddevices").append(createSel(optArr[5], $.i18n('conf_leds_optgroup_ftdi'), true));
 | 
			
		||||
 | 
			
		||||
  if (storedAccess === 'expert' || window.serverConfig.device.type === "file") {
 | 
			
		||||
    $("#leddevices").append(createSel(optArr[5], $.i18n('conf_leds_optgroup_other')));
 | 
			
		||||
    $("#leddevices").append(createSel(optArr[6], $.i18n('conf_leds_optgroup_other')));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  $("#leddevices").val(window.serverConfig.device.type);
 | 
			
		||||
@@ -1727,6 +1787,13 @@ $(document).ready(function () {
 | 
			
		||||
        params = { host: host };
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
      case "homeassistant":
 | 
			
		||||
        var host = conf_editor.getEditor("root.specificOptions.host").getValue();
 | 
			
		||||
        var token = conf_editor.getEditor("root.specificOptions.token").getValue();
 | 
			
		||||
        const entityIds = conf_editor.getEditor("root.specificOptions.entityIds").getValue();
 | 
			
		||||
        params = { host: host, token: token, entity_id: entityIds };
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
      case "nanoleaf":
 | 
			
		||||
        var host = conf_editor.getEditor("root.specificOptions.host").getValue();
 | 
			
		||||
        var token = conf_editor.getEditor("root.specificOptions.token").getValue();
 | 
			
		||||
@@ -1861,6 +1928,7 @@ function saveLedConfig(genDefLayout = false) {
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
 | 
			
		||||
    case "homeassistant":
 | 
			
		||||
    case "nanoleaf":
 | 
			
		||||
    case "wled":
 | 
			
		||||
    case "yeelight":
 | 
			
		||||
@@ -1886,6 +1954,9 @@ function saveLedConfig(genDefLayout = false) {
 | 
			
		||||
    case "sk9822":
 | 
			
		||||
    case "ws2812spi":
 | 
			
		||||
    case "piblaster":
 | 
			
		||||
    case "apa102_ftdi":
 | 
			
		||||
    case "sk6812_ftdi":
 | 
			
		||||
    case "ws2812_ftdi":
 | 
			
		||||
    default:
 | 
			
		||||
      if (genDefLayout === true) {
 | 
			
		||||
        ledConfig = {
 | 
			
		||||
@@ -1938,8 +2009,10 @@ var updateOutputSelectList = function (ledType, discoveryInfo) {
 | 
			
		||||
    ledTypeGroup = "devNET";
 | 
			
		||||
  } else if ($.inArray(ledType, devSerial) != -1) {
 | 
			
		||||
    ledTypeGroup = "devSerial";
 | 
			
		||||
  } else if ($.inArray(ledType, devRPiSPI) != -1) {
 | 
			
		||||
    ledTypeGroup = "devRPiSPI";
 | 
			
		||||
  } else if ($.inArray(ledType, devSPI) != -1) {
 | 
			
		||||
    ledTypeGroup = "devSPI";
 | 
			
		||||
  } else if ($.inArray(ledType, devFTDI) != -1) {
 | 
			
		||||
    ledTypeGroup = "devFTDI";
 | 
			
		||||
  } else if ($.inArray(ledType, devRPiGPIO) != -1) {
 | 
			
		||||
    ledTypeGroup = "devRPiGPIO";
 | 
			
		||||
  } else if ($.inArray(ledType, devRPiPWM) != -1) {
 | 
			
		||||
@@ -2062,7 +2135,63 @@ var updateOutputSelectList = function (ledType, discoveryInfo) {
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
    case "devRPiSPI":
 | 
			
		||||
 | 
			
		||||
    case "devFTDI":
 | 
			
		||||
      key = "output";
 | 
			
		||||
 | 
			
		||||
      if (discoveryInfo.devices.length == 0) {
 | 
			
		||||
        enumVals.push("NONE");
 | 
			
		||||
        enumTitleVals.push($.i18n('edt_dev_spec_devices_discovered_none'));
 | 
			
		||||
        $('#btn_submit_controller').prop('disabled', true);
 | 
			
		||||
        showAllDeviceInputOptions(key, false);
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        switch (ledType) {
 | 
			
		||||
          case "ws2812_ftdi":
 | 
			
		||||
          case "sk6812_ftdi":
 | 
			
		||||
          case "apa102_ftdi":
 | 
			
		||||
            for (const device of discoveryInfo.devices) {
 | 
			
		||||
              enumVals.push(device.ftdiOpenString);
 | 
			
		||||
 | 
			
		||||
              var title = "FTDI";
 | 
			
		||||
              if (device.manufacturer) {
 | 
			
		||||
                title = device.manufacturer;
 | 
			
		||||
              }
 | 
			
		||||
 | 
			
		||||
              if (device.serialNumber) {
 | 
			
		||||
                title += " - " + device.serialNumber;
 | 
			
		||||
              }
 | 
			
		||||
              title += " (" + device.vendorIdentifier + "|" + device.productIdentifier + ")";
 | 
			
		||||
 | 
			
		||||
              if (device.description) {
 | 
			
		||||
                title += " " + device.description;
 | 
			
		||||
              }
 | 
			
		||||
 | 
			
		||||
              enumTitleVals.push(title);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Select configured device
 | 
			
		||||
            var configuredDeviceType = window.serverConfig.device.type;
 | 
			
		||||
            var configuredOutput = window.serverConfig.device.output;
 | 
			
		||||
            if (ledType === configuredDeviceType) {
 | 
			
		||||
              if ($.inArray(configuredOutput, enumVals) != -1) {
 | 
			
		||||
                enumDefaultVal = configuredOutput;
 | 
			
		||||
              } else {
 | 
			
		||||
                enumVals.push(window.serverConfig.device.output);
 | 
			
		||||
                enumDefaultVal = configuredOutput;
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
            else {
 | 
			
		||||
              addSelect = true;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            break;
 | 
			
		||||
          default:
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
 | 
			
		||||
    case "devSPI":
 | 
			
		||||
    case "devRPiGPIO":
 | 
			
		||||
      key = "output";
 | 
			
		||||
 | 
			
		||||
@@ -2128,7 +2257,6 @@ var updateOutputSelectList = function (ledType, discoveryInfo) {
 | 
			
		||||
async function discover_device(ledType, params) {
 | 
			
		||||
 | 
			
		||||
  const result = await requestLedDeviceDiscovery(ledType, params);
 | 
			
		||||
 | 
			
		||||
  var discoveryResult = {};
 | 
			
		||||
  if (result) {
 | 
			
		||||
    if (result.error) {
 | 
			
		||||
@@ -2234,6 +2362,12 @@ function updateElements(ledType, key) {
 | 
			
		||||
        }
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
      case "homeassistant":
 | 
			
		||||
        updateElementsHomeAssistant(ledType, key);
 | 
			
		||||
        hardwareLedCount = 1;
 | 
			
		||||
        conf_editor.getEditor("root.generalOptions.hardwareLedCount").setValue(hardwareLedCount);
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
      case "atmo":
 | 
			
		||||
      case "karate":
 | 
			
		||||
        var ledProperties = devicesProperties[ledType][key];
 | 
			
		||||
@@ -2361,6 +2495,63 @@ function validateWledLedCount(hardwareLedCount) {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function updateElementsHomeAssistant(ledType, key) {
 | 
			
		||||
 | 
			
		||||
  // Get configured device's details
 | 
			
		||||
  var configuredDeviceType = window.serverConfig.device.type;
 | 
			
		||||
  var configuredHost = window.serverConfig.device.host;
 | 
			
		||||
  var host = conf_editor.getEditor("root.specificOptions.host").getValue();
 | 
			
		||||
 | 
			
		||||
  // New light selection list values
 | 
			
		||||
  var enumVals = [];
 | 
			
		||||
  var enumTitleVals = [];
 | 
			
		||||
  var enumDefaultVal = [];
 | 
			
		||||
 | 
			
		||||
  if (devicesProperties[ledType] && devicesProperties[ledType][key]) {
 | 
			
		||||
    var ledDeviceProperties = devicesProperties[ledType][key];
 | 
			
		||||
 | 
			
		||||
    if (!jQuery.isEmptyObject(ledDeviceProperties)) {
 | 
			
		||||
      if (ledDeviceProperties && ledDeviceProperties.lightEntities) {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        for (const light of ledDeviceProperties.lightEntities) {
 | 
			
		||||
          enumVals.push(light.entity_id);
 | 
			
		||||
          enumTitleVals.push(light.attributes.friendly_name);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Select configured device
 | 
			
		||||
  if (configuredDeviceType == ledType && configuredHost == host) {
 | 
			
		||||
    let configuredEntityIds = window.serverConfig.device.entityIds;
 | 
			
		||||
    for (const light of configuredEntityIds) {
 | 
			
		||||
      if ($.inArray(enumVals, light) != -1) {
 | 
			
		||||
        enumVals.push(light);
 | 
			
		||||
      }
 | 
			
		||||
      enumDefaultVal.push(light);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (enumVals.length < 1) {
 | 
			
		||||
    enumVals.push("NONE");
 | 
			
		||||
    enumTitleVals.push($.i18n('edt_dev_spec_lights_discovered_none'));
 | 
			
		||||
  }
 | 
			
		||||
  else {
 | 
			
		||||
    $('#btn_wiz_holder').show();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  let addSchemaElements = {
 | 
			
		||||
    "uniqueItems": true,
 | 
			
		||||
    "minItems": 1,
 | 
			
		||||
    "required": true
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  updateJsonEditorMultiSelection(conf_editor, 'root.specificOptions', 'entityIds', addSchemaElements, enumVals, enumTitleVals, enumDefaultVal);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function updateElementsWled(ledType, key) {
 | 
			
		||||
 | 
			
		||||
  // Get configured device's details
 | 
			
		||||
@@ -2456,6 +2647,7 @@ function updateElementsWled(ledType, key) {
 | 
			
		||||
  }
 | 
			
		||||
  showInputOptionForItem(conf_editor, "root.specificOptions.segments", "switchOffOtherSegments", showAdditionalOptions);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function sortByPanelCoordinates(arr, topToBottom, leftToRight) {
 | 
			
		||||
  arr.sort((a, b) => {
 | 
			
		||||
    //Nanoleaf corodinates start at bottom left, therefore reverse topToBottom
 | 
			
		||||
@@ -2514,7 +2706,7 @@ function nanoleafGeneratelayout(panelLayout, panelOrderTopDown, panelOrderLeftRi
 | 
			
		||||
    29: { name: "4DLightstrip", led: true, sideLengthX: 50, sideLengthY: 50 },
 | 
			
		||||
    30: { name: "Skylight Panel", led: true, sideLengthX: 180, sideLengthY: 180 },
 | 
			
		||||
    31: { name: "SkylightControllerPrimary", led: true, sideLengthX: 180, sideLengthY: 180 },
 | 
			
		||||
    32: { name: "SkylightControllerPassive", led: true, sideLengthX: 180, sideLengthY: 180 },            
 | 
			
		||||
    32: { name: "SkylightControllerPassive", led: true, sideLengthX: 180, sideLengthY: 180 },
 | 
			
		||||
    999: { name: "Unknown", led: true, sideLengthX: 100, sideLengthY: 100 }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -321,7 +321,7 @@ function showInfoDialog(type, header, message) {
 | 
			
		||||
  $(document).on('click', '[data-dismiss-modal]', function () {
 | 
			
		||||
    var target = $(this).data('dismiss-modal');
 | 
			
		||||
    $($.find(target)).modal('hide');
 | 
			
		||||
});
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function createHintH(type, text, container) {
 | 
			
		||||
@@ -478,7 +478,7 @@ function createJsonEditor(container, schema, setconfig, usePanel, arrayre) {
 | 
			
		||||
  return editor;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function updateJsonEditorSelection(rootEditor, path, key, addElements, newEnumVals, newTitelVals, newDefaultVal, addSelect, addCustom, addCustomAsFirst, customText) {
 | 
			
		||||
function updateJsonEditorSelection(rootEditor, path, key, addElements, newEnumVals, newTitleVals, newDefaultVal, addSelect, addCustom, addCustomAsFirst, customText) {
 | 
			
		||||
  var editor = rootEditor.getEditor(path);
 | 
			
		||||
  var orginalProperties = editor.schema.properties[key];
 | 
			
		||||
 | 
			
		||||
@@ -516,8 +516,8 @@ function updateJsonEditorSelection(rootEditor, path, key, addElements, newEnumVa
 | 
			
		||||
 | 
			
		||||
  if (addCustom) {
 | 
			
		||||
 | 
			
		||||
    if (newTitelVals.length === 0) {
 | 
			
		||||
      newTitelVals = [...newEnumVals];
 | 
			
		||||
    if (newTitleVals.length === 0) {
 | 
			
		||||
      newTitleVals = [...newEnumVals];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!!!customText) {
 | 
			
		||||
@@ -526,10 +526,10 @@ function updateJsonEditorSelection(rootEditor, path, key, addElements, newEnumVa
 | 
			
		||||
 | 
			
		||||
    if (addCustomAsFirst) {
 | 
			
		||||
      newEnumVals.unshift("CUSTOM");
 | 
			
		||||
      newTitelVals.unshift(customText);
 | 
			
		||||
      newTitleVals.unshift(customText);
 | 
			
		||||
    } else {
 | 
			
		||||
      newEnumVals.push("CUSTOM");
 | 
			
		||||
      newTitelVals.push(customText);
 | 
			
		||||
      newTitleVals.push(customText);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (newSchema[key].options.infoText) {
 | 
			
		||||
@@ -540,7 +540,7 @@ function updateJsonEditorSelection(rootEditor, path, key, addElements, newEnumVa
 | 
			
		||||
 | 
			
		||||
  if (addSelect) {
 | 
			
		||||
    newEnumVals.unshift("SELECT");
 | 
			
		||||
    newTitelVals.unshift("edt_conf_enum_please_select");
 | 
			
		||||
    newTitleVals.unshift("edt_conf_enum_please_select");
 | 
			
		||||
    newDefaultVal = "SELECT";
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -548,8 +548,8 @@ function updateJsonEditorSelection(rootEditor, path, key, addElements, newEnumVa
 | 
			
		||||
    newSchema[key]["enum"] = newEnumVals;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (newTitelVals) {
 | 
			
		||||
    newSchema[key]["options"]["enum_titles"] = newTitelVals;
 | 
			
		||||
  if (newTitleVals) {
 | 
			
		||||
    newSchema[key]["options"]["enum_titles"] = newTitleVals;
 | 
			
		||||
  }
 | 
			
		||||
  if (newDefaultVal) {
 | 
			
		||||
    newSchema[key]["default"] = newDefaultVal;
 | 
			
		||||
@@ -572,7 +572,7 @@ function updateJsonEditorSelection(rootEditor, path, key, addElements, newEnumVa
 | 
			
		||||
  rootEditor.notifyWatchers(path + "." + key);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function updateJsonEditorMultiSelection(rootEditor, path, key, addElements, newEnumVals, newTitelVals, newDefaultVal) {
 | 
			
		||||
function updateJsonEditorMultiSelection(rootEditor, path, key, addElements, newEnumVals, newTitleVals, newDefaultVal) {
 | 
			
		||||
  var editor = rootEditor.getEditor(path);
 | 
			
		||||
  var orginalProperties = editor.schema.properties[key];
 | 
			
		||||
 | 
			
		||||
@@ -617,8 +617,8 @@ function updateJsonEditorMultiSelection(rootEditor, path, key, addElements, newE
 | 
			
		||||
    newSchema[key]["items"]["enum"] = newEnumVals;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (newTitelVals) {
 | 
			
		||||
    newSchema[key]["items"]["options"]["enum_titles"] = newTitelVals;
 | 
			
		||||
  if (newTitleVals) {
 | 
			
		||||
    newSchema[key]["items"]["options"]["enum_titles"] = newTitleVals;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (newDefaultVal) {
 | 
			
		||||
@@ -923,8 +923,8 @@ function createTableRow(list, head, align) {
 | 
			
		||||
      el.style.verticalAlign = "middle";
 | 
			
		||||
 | 
			
		||||
    var purifyConfig = {
 | 
			
		||||
            ADD_TAGS: ['button'],
 | 
			
		||||
            ADD_ATTR: ['onclick']
 | 
			
		||||
      ADD_TAGS: ['button'],
 | 
			
		||||
      ADD_ATTR: ['onclick']
 | 
			
		||||
    };
 | 
			
		||||
    el.innerHTML = DOMPurify.sanitize(list[i], purifyConfig);
 | 
			
		||||
    row.appendChild(el);
 | 
			
		||||
@@ -1403,7 +1403,7 @@ function loadScript(src, callback, ...params) {
 | 
			
		||||
  if (isScriptLoaded(src)) {
 | 
			
		||||
    debugMessage('Script ' + src + ' already loaded');
 | 
			
		||||
    if (callback && typeof callback === 'function') {
 | 
			
		||||
      callback( ...params);
 | 
			
		||||
      callback(...params);
 | 
			
		||||
    }
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -37,27 +37,37 @@ function createLedDeviceWizards(ledType) {
 | 
			
		||||
  $('#btn_led_device_wiz').off();
 | 
			
		||||
  if (ledType == "philipshue") {
 | 
			
		||||
    $('#btn_wiz_holder').show();
 | 
			
		||||
    data = { ledType };
 | 
			
		||||
    wizardName = ledType;
 | 
			
		||||
    data = { wizardName };
 | 
			
		||||
    title = 'wiz_hue_title';
 | 
			
		||||
  }
 | 
			
		||||
  else if (ledType == "nanoleaf") {
 | 
			
		||||
    $('#btn_wiz_holder').hide();
 | 
			
		||||
    data = { ledType };
 | 
			
		||||
    wizardName = ledType;
 | 
			
		||||
    data = { wizardName };
 | 
			
		||||
    title = 'wiz_nanoleaf_user_auth_title';
 | 
			
		||||
  }
 | 
			
		||||
  else if (ledType == "homeassistant") {
 | 
			
		||||
    $('#btn_wiz_holder').hide();
 | 
			
		||||
    wizardName = "layoutLedPositions";
 | 
			
		||||
    data = { wizardName, ledType };
 | 
			
		||||
    title = 'wiz_layout_led_positions_title';
 | 
			
		||||
  }
 | 
			
		||||
  else if (ledType == "atmoorb") {
 | 
			
		||||
    $('#btn_wiz_holder').show();
 | 
			
		||||
    data = { ledType };
 | 
			
		||||
    wizardName = ledType;
 | 
			
		||||
    data = { wizardName };
 | 
			
		||||
    title = 'wiz_atmoorb_title';
 | 
			
		||||
  }
 | 
			
		||||
  else if (ledType == "yeelight") {
 | 
			
		||||
    $('#btn_wiz_holder').show();
 | 
			
		||||
    data = { ledType };
 | 
			
		||||
    wizardName = ledType;
 | 
			
		||||
    data = { wizardName };
 | 
			
		||||
    title = 'wiz_yeelight_title';
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (Object.keys(data).length !== 0) {
 | 
			
		||||
    startLedDeviceWizard(data, title, ledType + "Wizard");
 | 
			
		||||
    startLedDeviceWizard(data, title, wizardName + "Wizard");
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -66,8 +76,7 @@ function startLedDeviceWizard(data, hint, wizardName) {
 | 
			
		||||
  createHint("wizard", $.i18n(hint), "btn_wiz_holder", "btn_led_device_wiz");
 | 
			
		||||
  $('#btn_led_device_wiz').off();
 | 
			
		||||
  $('#btn_led_device_wiz').on('click', async (e) => {
 | 
			
		||||
    const { [wizardName]: winzardObject } = await import('./wizards/LedDevice_' + data.ledType + '.js');
 | 
			
		||||
    winzardObject.start(e);
 | 
			
		||||
    const { [wizardName]: winzardObject } = await import('./wizards/LedDevice_' + data.wizardName + '.js');
 | 
			
		||||
    winzardObject.start(e, data);
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -151,17 +151,7 @@ const atmoorbWizard = (() => {
 | 
			
		||||
      $('#wh_topcontainer').toggle(false);
 | 
			
		||||
      $('#orb_ids_t, #btn_wiz_save').toggle(true);
 | 
			
		||||
 | 
			
		||||
      const lightOptions = [
 | 
			
		||||
        "top", "topleft", "topright",
 | 
			
		||||
        "bottom", "bottomleft", "bottomright",
 | 
			
		||||
        "left", "lefttop", "leftmiddle", "leftbottom",
 | 
			
		||||
        "right", "righttop", "rightmiddle", "rightbottom",
 | 
			
		||||
        "entire",
 | 
			
		||||
        "lightPosTopLeft112", "lightPosTopLeftNewMid", "lightPosTopLeft121",
 | 
			
		||||
        "lightPosBottomLeft14", "lightPosBottomLeft12", "lightPosBottomLeft34", "lightPosBottomLeft11",
 | 
			
		||||
        "lightPosBottomLeft112", "lightPosBottomLeftNewMid", "lightPosBottomLeft121"
 | 
			
		||||
      ];
 | 
			
		||||
 | 
			
		||||
      const lightOptions = utils.getLayoutPositions();
 | 
			
		||||
      lightOptions.unshift("disabled");
 | 
			
		||||
 | 
			
		||||
      $('.lidsb').html("");
 | 
			
		||||
@@ -178,10 +168,9 @@ const atmoorbWizard = (() => {
 | 
			
		||||
        let options = "";
 | 
			
		||||
        for (const opt in lightOptions) {
 | 
			
		||||
          const val = lightOptions[opt];
 | 
			
		||||
          const txt = (val !== 'entire' && val !== 'disabled') ? 'conf_leds_layout_cl_' : 'wiz_ids_';
 | 
			
		||||
          options += '<option value="' + val + '"';
 | 
			
		||||
          if (pos === val) options += ' selected="selected"';
 | 
			
		||||
          options += '>' + $.i18n(txt + val) + '</option>';
 | 
			
		||||
          options += '>' + $.i18n('conf_leds_layout_cl_' + val) + '</option>';
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let enabled = 'enabled';
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										74
									
								
								assets/webconfig/js/wizards/LedDevice_layoutLedPositions.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								assets/webconfig/js/wizards/LedDevice_layoutLedPositions.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,74 @@
 | 
			
		||||
//****************************
 | 
			
		||||
// Wizard LED Layout
 | 
			
		||||
//****************************
 | 
			
		||||
 | 
			
		||||
import { ledDeviceWizardUtils as utils } from './LedDevice_utils.js';
 | 
			
		||||
 | 
			
		||||
const layoutLedPositionsWizard = (() => {
 | 
			
		||||
 | 
			
		||||
  let wiz_editor;
 | 
			
		||||
 | 
			
		||||
  function createEditor() {
 | 
			
		||||
    wiz_editor = createJsonEditor('editor_container_wiz', {
 | 
			
		||||
      layoutPosition: {
 | 
			
		||||
        "type": "string",
 | 
			
		||||
        "title": "wiz_layout_led_position_title",
 | 
			
		||||
        "enum": utils.getLayoutPositions(),
 | 
			
		||||
        "options": {
 | 
			
		||||
          "enum_titles": utils.getLayoutPositionsTitles()
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }, true, true);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function stopWizardLedLayout(reload) {
 | 
			
		||||
    resetWizard(reload);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function beginWizardLayoutLedPositions() {
 | 
			
		||||
    createEditor();
 | 
			
		||||
    setStorage("wizardactive", true);
 | 
			
		||||
 | 
			
		||||
    $('#btn_wiz_abort').off().on('click', function () {
 | 
			
		||||
      stopWizardLedLayout(true);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    $('#btn_wiz_ok').off().on('click', function () {
 | 
			
		||||
      const layoutPosition = wiz_editor.getEditor("root.layoutPosition").getValue();
 | 
			
		||||
      const layoutObject = utils.assignLightPos(layoutPosition);
 | 
			
		||||
 | 
			
		||||
      var layoutObjects = [];
 | 
			
		||||
      layoutObjects.push(JSON.parse(JSON.stringify(layoutObject)));
 | 
			
		||||
      aceEdt.set(layoutObjects);
 | 
			
		||||
 | 
			
		||||
      stopWizardLedLayout(true);
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    start: function (e, data) {
 | 
			
		||||
      $('#wiz_header').html('<i class="fa fa-magic fa-fw"></i>' + $.i18n('wiz_layout_led_positions_title'));
 | 
			
		||||
      $('#wizp1_body').html('<div <p style="font-weight:bold">' + $.i18n('wiz_layout_led_positions_expl', data.ledType) + '</p></div>' +
 | 
			
		||||
        '<div id="editor_container_wiz"></div>'
 | 
			
		||||
      );
 | 
			
		||||
      $('#wizp1_footer').html('<button type="button" class="btn btn-primary" id="btn_wiz_ok"><i class="fa fa-fw fa-check"></i>' + $.i18n('general_btn_ok') + 
 | 
			
		||||
      '</button><button type="button" class="btn btn-danger" id="btn_wiz_abort"><i class="fa fa-fw fa-close"></i>' + $.i18n('general_btn_cancel') + '</button>'
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
      if (getStorage("darkMode") == "on")
 | 
			
		||||
        $('#wizard_logo').attr("src", 'img/hyperion/logo_negativ.png');
 | 
			
		||||
 | 
			
		||||
      //open modal
 | 
			
		||||
      $("#wizard_modal").modal({
 | 
			
		||||
        backdrop: "static",
 | 
			
		||||
        keyboard: false,
 | 
			
		||||
        show: true
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      beginWizardLayoutLedPositions();
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
})();
 | 
			
		||||
 | 
			
		||||
export { layoutLedPositionsWizard };
 | 
			
		||||
 | 
			
		||||
@@ -794,17 +794,7 @@ const philipshueWizard = (() => {
 | 
			
		||||
        }
 | 
			
		||||
        $('#hue_ids_t, #btn_wiz_save').toggle(true);
 | 
			
		||||
 | 
			
		||||
        const lightOptions = [
 | 
			
		||||
          "top", "topleft", "topright",
 | 
			
		||||
          "bottom", "bottomleft", "bottomright",
 | 
			
		||||
          "left", "lefttop", "leftmiddle", "leftbottom",
 | 
			
		||||
          "right", "righttop", "rightmiddle", "rightbottom",
 | 
			
		||||
          "entire",
 | 
			
		||||
          "lightPosTopLeft112", "lightPosTopLeftNewMid", "lightPosTopLeft121",
 | 
			
		||||
          "lightPosBottomLeft14", "lightPosBottomLeft12", "lightPosBottomLeft34", "lightPosBottomLeft11",
 | 
			
		||||
          "lightPosBottomLeft112", "lightPosBottomLeftNewMid", "lightPosBottomLeft121"
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        const lightOptions = utils.getLayoutPositions();
 | 
			
		||||
        if (isEntertainmentReady && hueEntertainmentConfigs.length > 0) {
 | 
			
		||||
          lightOptions.unshift("entertainment_center");
 | 
			
		||||
          lightOptions.unshift("entertainment");
 | 
			
		||||
@@ -866,10 +856,9 @@ const philipshueWizard = (() => {
 | 
			
		||||
          let options = "";
 | 
			
		||||
          for (const opt in lightOptions) {
 | 
			
		||||
            const val = lightOptions[opt];
 | 
			
		||||
            const txt = (val != 'entire' && val != 'disabled') ? 'conf_leds_layout_cl_' : 'wiz_ids_';
 | 
			
		||||
            options += '<option value="' + val + '"';
 | 
			
		||||
            if (pos == val) options += ' selected="selected"';
 | 
			
		||||
            options += '>' + $.i18n(txt + val) + '</option>';
 | 
			
		||||
            options += '>' + $.i18n('conf_leds_layout_cl_' + val) + '</option>';
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          $('.lidsb').append(createTableRow([id + ' (' + lightName + ')',
 | 
			
		||||
 
 | 
			
		||||
@@ -52,6 +52,17 @@ const ledDeviceWizardUtils = (() => {
 | 
			
		||||
      const i = positionMap[pos] || positionMap["lightPosEntire"];
 | 
			
		||||
      i.name = name;
 | 
			
		||||
      return i;
 | 
			
		||||
    },
 | 
			
		||||
    getLayoutPositions: function () {
 | 
			
		||||
      return Object.keys(positionMap);
 | 
			
		||||
    },
 | 
			
		||||
    getLayoutPositionsTitles: function () {
 | 
			
		||||
 | 
			
		||||
      let layoutPositionTitles = [];
 | 
			
		||||
      for (const layoutPosition of Object.keys(positionMap)) {
 | 
			
		||||
        layoutPositionTitles.push($.i18n('conf_leds_layout_cl_' + layoutPosition));
 | 
			
		||||
      }
 | 
			
		||||
      return layoutPositionTitles;
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -173,17 +173,7 @@ const yeelightWizard = (() => {
 | 
			
		||||
      $('#wh_topcontainer').toggle(false);
 | 
			
		||||
      $('#yee_ids_t, #btn_wiz_save').toggle(true);
 | 
			
		||||
 | 
			
		||||
      const lightOptions = [
 | 
			
		||||
        "top", "topleft", "topright",
 | 
			
		||||
        "bottom", "bottomleft", "bottomright",
 | 
			
		||||
        "left", "lefttop", "leftmiddle", "leftbottom",
 | 
			
		||||
        "right", "righttop", "rightmiddle", "rightbottom",
 | 
			
		||||
        "entire",
 | 
			
		||||
        "lightPosTopLeft112", "lightPosTopLeftNewMid", "lightPosTopLeft121",
 | 
			
		||||
        "lightPosBottomLeft14", "lightPosBottomLeft12", "lightPosBottomLeft34", "lightPosBottomLeft11",
 | 
			
		||||
        "lightPosBottomLeft112", "lightPosBottomLeftNewMid", "lightPosBottomLeft121"
 | 
			
		||||
      ];
 | 
			
		||||
 | 
			
		||||
      const lightOptions = utils.getLayoutPositions();
 | 
			
		||||
      lightOptions.unshift("disabled");
 | 
			
		||||
 | 
			
		||||
      $('.lidsb').html("");
 | 
			
		||||
@@ -200,10 +190,9 @@ const yeelightWizard = (() => {
 | 
			
		||||
        let options = "";
 | 
			
		||||
        for (const opt in lightOptions) {
 | 
			
		||||
          const val = lightOptions[opt];
 | 
			
		||||
          const txt = (val !== 'entire' && val !== 'disabled') ? 'conf_leds_layout_cl_' : 'wiz_ids_';
 | 
			
		||||
          options += '<option value="' + val + '"';
 | 
			
		||||
          if (pos === val) options += ' selected="selected"';
 | 
			
		||||
          options += '>' + $.i18n(txt + val) + '</option>';
 | 
			
		||||
          options += '>' + $.i18n('conf_leds_layout_cl_' + val) + '</option>';
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let enabled = 'enabled';
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										42
									
								
								dependencies/CMakeLists.txt
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										42
									
								
								dependencies/CMakeLists.txt
									
									
									
									
										vendored
									
									
								
							@@ -24,36 +24,19 @@ if (ENABLE_MDNS)
 | 
			
		||||
	if(USE_SYSTEM_QMDNS_LIBS)
 | 
			
		||||
		find_package(qmdnsengine REQUIRED)
 | 
			
		||||
	else()
 | 
			
		||||
		include(ExternalProject)
 | 
			
		||||
		ExternalProject_Add(qmdns
 | 
			
		||||
			PREFIX				${CMAKE_CURRENT_BINARY_DIR}/external/qmdnsengine
 | 
			
		||||
			BUILD_ALWAYS		OFF
 | 
			
		||||
			DOWNLOAD_COMMAND	""
 | 
			
		||||
			SOURCE_DIR			${CMAKE_CURRENT_SOURCE_DIR}/external/qmdnsengine
 | 
			
		||||
			BINARY_DIR			${CMAKE_CURRENT_BINARY_DIR}/external/qmdnsengine/bin
 | 
			
		||||
			CMAKE_ARGS			-DBUILD_SHARED_LIBS:BOOL=OFF
 | 
			
		||||
								-DCMAKE_INSTALL_PREFIX:PATH=${CMAKE_BINARY_DIR}
 | 
			
		||||
								-DBIN_INSTALL_DIR:STRING=lib
 | 
			
		||||
								-DLIB_INSTALL_DIR:STRING=lib
 | 
			
		||||
								-DINCLUDE_INSTALL_DIR:STRING=include
 | 
			
		||||
								-DCMAKE_PREFIX_PATH:PATH=${CMAKE_PREFIX_PATH}
 | 
			
		||||
								-DCMAKE_C_COMPILER:FILEPATH=${CMAKE_C_COMPILER}
 | 
			
		||||
								-DCMAKE_CXX_COMPILER:FILEPATH=${CMAKE_CXX_COMPILER}
 | 
			
		||||
								-DCMAKE_C_FLAGS:STRING=${CMAKE_C_FLAGS}
 | 
			
		||||
								-DCMAKE_CXX_FLAGS:STRING=${CMAKE_CXX_FLAGS}
 | 
			
		||||
								-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}
 | 
			
		||||
								-Wno-dev # We don't want to be warned over unused variables
 | 
			
		||||
			INSTALL_DIR			${CMAKE_BINARY_DIR}
 | 
			
		||||
			BUILD_BYPRODUCTS    <INSTALL_DIR>/lib/${CMAKE_STATIC_LIBRARY_PREFIX}qmdnsengine${CMAKE_STATIC_LIBRARY_SUFFIX}
 | 
			
		||||
		)
 | 
			
		||||
 		# Build QMdnsEngine as static library
 | 
			
		||||
		set(BUILD_SHARED_LIBS OFF CACHE BOOL "Build statically version of QMdnsEngine")
 | 
			
		||||
 | 
			
		||||
		add_library(qmdnsengine STATIC IMPORTED GLOBAL)
 | 
			
		||||
		add_dependencies(qmdnsengine qmdns)
 | 
			
		||||
		ExternalProject_Get_Property(qmdns INSTALL_DIR)
 | 
			
		||||
		set_target_properties(qmdnsengine PROPERTIES
 | 
			
		||||
			IMPORTED_LOCATION "${INSTALL_DIR}/lib/${CMAKE_STATIC_LIBRARY_PREFIX}qmdnsengine${CMAKE_STATIC_LIBRARY_SUFFIX}"
 | 
			
		||||
			INTERFACE_INCLUDE_DIRECTORIES "${INSTALL_DIR}/include"
 | 
			
		||||
		)
 | 
			
		||||
		# Suppress warnings about "Compatibility with CMake < 3.5 will be removed from a future version of CMake"
 | 
			
		||||
		set(CMAKE_WARN_DEPRECATED OFF CACHE BOOL "" FORCE)
 | 
			
		||||
 | 
			
		||||
		# Add QMdnsEngine directory to the build
 | 
			
		||||
		add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/external/qmdnsengine")
 | 
			
		||||
	endif()
 | 
			
		||||
 | 
			
		||||
	if(TARGET qmdnsengine AND NOT TARGET qmdns)
 | 
			
		||||
		add_library(qmdns INTERFACE IMPORTED GLOBAL)
 | 
			
		||||
		target_link_libraries(qmdns INTERFACE qmdnsengine)
 | 
			
		||||
	endif()
 | 
			
		||||
endif()
 | 
			
		||||
 | 
			
		||||
@@ -149,6 +132,7 @@ if(ENABLE_PROTOBUF_SERVER)
 | 
			
		||||
		set(protobuf_BUILD_TESTS OFF CACHE BOOL "Build protobuf with tests")
 | 
			
		||||
		set(protobuf_BUILD_SHARED_LIBS OFF CACHE BOOL "Build protobuf shared")
 | 
			
		||||
		set(protobuf_WITH_ZLIB OFF CACHE BOOL "Build protobuf with zlib support")
 | 
			
		||||
		set(protobuf_BUILD_LIBUPB OFF CACHE BOOL "Build libupb")
 | 
			
		||||
 | 
			
		||||
		if (WIN32)
 | 
			
		||||
			set(protobuf_MSVC_STATIC_RUNTIME OFF CACHE BOOL "Build protobuf static")
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								dependencies/external/flatbuffers
									
									
									
									
										vendored
									
									
								
							
							
								
								
								
								
								
							
						
						
									
										2
									
								
								dependencies/external/flatbuffers
									
									
									
									
										vendored
									
									
								
							 Submodule dependencies/external/flatbuffers updated: 0100f6a577...595bf0007a
									
								
							
							
								
								
									
										2
									
								
								dependencies/external/mbedtls
									
									
									
									
										vendored
									
									
								
							
							
								
								
								
								
								
							
						
						
									
										2
									
								
								dependencies/external/mbedtls
									
									
									
									
										vendored
									
									
								
							 Submodule dependencies/external/mbedtls updated: edb8fec988...2ca6c285a0
									
								
							
							
								
								
									
										2
									
								
								dependencies/external/protobuf
									
									
									
									
										vendored
									
									
								
							
							
								
								
								
								
								
							
						
						
									
										2
									
								
								dependencies/external/protobuf
									
									
									
									
										vendored
									
									
								
							 Submodule dependencies/external/protobuf updated: 7f94235e55...3d9f7c430a
									
								
							@@ -61,14 +61,14 @@ cd $HYPERION_HOME
 | 
			
		||||
 | 
			
		||||
```console
 | 
			
		||||
sudo apt-get update
 | 
			
		||||
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 libasound2-dev libturbojpeg0-dev libjpeg-dev libssl-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 libasound2-dev libturbojpeg0-dev libjpeg-dev libssl-dev libftdi1-dev
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
**Ubuntu (22.04+) - Qt6 based**
 | 
			
		||||
 | 
			
		||||
```console
 | 
			
		||||
sudo apt-get update
 | 
			
		||||
sudo apt-get install git cmake build-essential qt6-base-dev libqt6serialport6-dev libxkbcommon-dev libvulkan-dev libgl1-mesa-dev libusb-1.0-0-dev python3-dev libasound2-dev libturbojpeg0-dev libjpeg-dev libssl-dev pkg-config
 | 
			
		||||
sudo apt-get install git cmake build-essential qt6-base-dev libqt6serialport6-dev libxkbcommon-dev libvulkan-dev libgl1-mesa-dev libusb-1.0-0-dev python3-dev libasound2-dev libturbojpeg0-dev libjpeg-dev libssl-dev pkg-config libftdi1-dev
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
**For Linux X11/XCB grabber support**
 | 
			
		||||
@@ -110,16 +110,21 @@ See [AUR](https://aur.archlinux.org/packages/?O=0&SeB=nd&K=hyperion&outdated=&SB
 | 
			
		||||
The following dependencies are needed to build hyperion.ng on fedora.
 | 
			
		||||
```console
 | 
			
		||||
sudo dnf -y groupinstall "Development Tools"
 | 
			
		||||
sudo dnf install python3-devel qt-devel qt5-qtbase-devel qt5-qtserialport-devel xrandr xcb-util-image-devel qt5-qtx11extras-devel alsa-lib-devel turbojpeg-devel libusb-devel xcb-util-devel dbus-devel openssl-devel fedora-packager rpmdevtools gcc libcec-devel
 | 
			
		||||
sudo dnf install python3-devel qt-devel qt5-qtbase-devel qt5-qtserialport-devel xrandr xcb-util-image-devel qt5-qtx11extras-devel alsa-lib-devel turbojpeg-devel libusb-devel xcb-util-devel dbus-devel openssl-devel fedora-packager rpmdevtools gcc libcec-devel libftdi1-dev
 | 
			
		||||
```
 | 
			
		||||
After installing the dependencies, you can continue with the compile instructions later on this page (the more detailed way..).
 | 
			
		||||
 | 
			
		||||
## OSX
 | 
			
		||||
## macOS
 | 
			
		||||
To install on OS X you either need [Homebrew](https://brew.sh/) or [Macport](https://www.macports.org/) but Homebrew is the recommended way to install the packages. To use Homebrew, XCode is required as well, use `brew doctor` to check your install.
 | 
			
		||||
 | 
			
		||||
First you need to install the dependencies:
 | 
			
		||||
First you need to install the dependencies for either the QT5 or QT6 build:
 | 
			
		||||
####QT5
 | 
			
		||||
```console
 | 
			
		||||
brew install git qt@5 python3 cmake libusb openssl@1.1
 | 
			
		||||
brew install git qt@5 python3 cmake libusb openssl@1.1 libftdi pkg-config
 | 
			
		||||
```
 | 
			
		||||
####QT6
 | 
			
		||||
```console
 | 
			
		||||
brew install git qt python3 cmake libusb openssl@1.1 libftdi pkg-config
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Windows
 | 
			
		||||
@@ -147,7 +152,7 @@ We assume a 64bit Windows 10. Install the following;
 | 
			
		||||
 | 
			
		||||
## The general quick way (without big comments)
 | 
			
		||||
 | 
			
		||||
**complete automated process for Mac/Linux:**
 | 
			
		||||
**complete automated process (Linux only):**
 | 
			
		||||
```console
 | 
			
		||||
wget -qO- https://raw.githubusercontent.com/hyperion-project/hyperion.ng/master/bin/compile.sh | sh
 | 
			
		||||
```
 | 
			
		||||
 
 | 
			
		||||
@@ -46,6 +46,7 @@ public:
 | 
			
		||||
		int numerator	= 0;
 | 
			
		||||
		int denominator = 0;
 | 
			
		||||
		PixelFormat pf	= PixelFormat::NO_CHANGE;
 | 
			
		||||
		long defstride  = 0;
 | 
			
		||||
		GUID guid		= GUID_NULL;
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -22,6 +22,7 @@ const MdnsServiceMap mDnsServiceMap = {
 | 
			
		||||
 | 
			
		||||
	//LED Devices
 | 
			
		||||
	{"cololight"    , {"_hap._tcp.local.", "ColoLight.*"}},
 | 
			
		||||
	{"homeassistant", {"_home-assistant._tcp.local.", ".*"}},
 | 
			
		||||
	{"nanoleaf"	    , {"_nanoleafapi._tcp.local.", ".*"}},
 | 
			
		||||
	{"philipshue"   , {"_hue._tcp.local.", ".*"}},
 | 
			
		||||
	{"wled"		    , {"_wled._tcp.local.", ".*"}},
 | 
			
		||||
 
 | 
			
		||||
@@ -10,6 +10,7 @@ enum class PixelFormat {
 | 
			
		||||
	YUYV,
 | 
			
		||||
	UYVY,
 | 
			
		||||
	BGR16,
 | 
			
		||||
	RGB24,
 | 
			
		||||
	BGR24,
 | 
			
		||||
	RGB32,
 | 
			
		||||
	BGR32,
 | 
			
		||||
@@ -36,6 +37,10 @@ inline PixelFormat parsePixelFormat(const QString& pixelFormat)
 | 
			
		||||
	{
 | 
			
		||||
		return PixelFormat::BGR16;
 | 
			
		||||
	}
 | 
			
		||||
	else if (format.compare("rgb24")  == 0)
 | 
			
		||||
	{
 | 
			
		||||
		return PixelFormat::RGB24;
 | 
			
		||||
	}
 | 
			
		||||
	else if (format.compare("bgr24")  == 0)
 | 
			
		||||
	{
 | 
			
		||||
		return PixelFormat::BGR24;
 | 
			
		||||
@@ -80,6 +85,10 @@ inline QString pixelFormatToString(const PixelFormat& pixelFormat)
 | 
			
		||||
	{
 | 
			
		||||
		return "BGR16";
 | 
			
		||||
	}
 | 
			
		||||
	else if (pixelFormat == PixelFormat::RGB24)
 | 
			
		||||
	{
 | 
			
		||||
		return "RGB24";
 | 
			
		||||
	}
 | 
			
		||||
	else if (pixelFormat == PixelFormat::BGR24)
 | 
			
		||||
	{
 | 
			
		||||
		return "BGR24";
 | 
			
		||||
@@ -115,10 +124,10 @@ inline QString pixelFormatToString(const PixelFormat& pixelFormat)
 | 
			
		||||
 | 
			
		||||
enum class FlipMode
 | 
			
		||||
{
 | 
			
		||||
	NO_CHANGE,
 | 
			
		||||
	HORIZONTAL,
 | 
			
		||||
	VERTICAL,
 | 
			
		||||
	BOTH,
 | 
			
		||||
	NO_CHANGE
 | 
			
		||||
	BOTH
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
inline FlipMode parseFlipMode(const QString& flipMode)
 | 
			
		||||
 
 | 
			
		||||
@@ -11,7 +11,12 @@ namespace RGBW {
 | 
			
		||||
		SUBTRACT_MINIMUM,
 | 
			
		||||
		SUB_MIN_WARM_ADJUST,
 | 
			
		||||
		SUB_MIN_COOL_ADJUST,
 | 
			
		||||
		WHITE_OFF
 | 
			
		||||
		WHITE_OFF,
 | 
			
		||||
        COLD_WHITE,
 | 
			
		||||
        NEUTRAL_WHITE,
 | 
			
		||||
        AUTO,
 | 
			
		||||
        AUTO_MAX,
 | 
			
		||||
        AUTO_ACCURATE
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	WhiteAlgorithm stringToWhiteAlgorithm(const QString& str);
 | 
			
		||||
 
 | 
			
		||||
@@ -745,7 +745,7 @@ void JsonAPI::handleConfigSetCommand(const QJsonObject &message, const JsonApiCo
 | 
			
		||||
		}
 | 
			
		||||
		else
 | 
			
		||||
		{
 | 
			
		||||
			sendErrorReply("Saving configuration while Hyperion is disabled isn't possible", cmd);
 | 
			
		||||
			sendErrorReply("It is not possible saving a configuration while Hyperion is disabled", cmd);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -174,7 +174,7 @@ int DDAGrabber::grabFrame(Image<ColorRgb> &image)
 | 
			
		||||
	// Acquire the next frame.
 | 
			
		||||
	CComPtr<IDXGIResource> desktopResource;
 | 
			
		||||
	DXGI_OUTDUPL_FRAME_INFO frameInfo;
 | 
			
		||||
	hr = d->desktopDuplication->AcquireNextFrame(INFINITE, &frameInfo, &desktopResource);
 | 
			
		||||
	hr = d->desktopDuplication->AcquireNextFrame(500, &frameInfo, &desktopResource);
 | 
			
		||||
	if (hr == DXGI_ERROR_ACCESS_LOST || hr == DXGI_ERROR_INVALID_CALL)
 | 
			
		||||
	{
 | 
			
		||||
		if (!restartCapture())
 | 
			
		||||
@@ -185,7 +185,7 @@ int DDAGrabber::grabFrame(Image<ColorRgb> &image)
 | 
			
		||||
	}
 | 
			
		||||
	if (hr == DXGI_ERROR_WAIT_TIMEOUT)
 | 
			
		||||
	{
 | 
			
		||||
		// This shouldn't happen since we specified an INFINITE timeout.
 | 
			
		||||
		// Nothing changed on the screen in the 500ms we waited.
 | 
			
		||||
		return 0;
 | 
			
		||||
	}
 | 
			
		||||
	RETURN_IF_ERROR(hr, "Failed to acquire next frame", 0);
 | 
			
		||||
 
 | 
			
		||||
@@ -131,7 +131,7 @@ void EncoderThread::process()
 | 
			
		||||
#if defined(ENABLE_V4L2)
 | 
			
		||||
				_pixelFormat,
 | 
			
		||||
#else
 | 
			
		||||
				PixelFormat::BGR24,
 | 
			
		||||
				PixelFormat::BGR24,  // MF-Grabber always sends RGB24, but memory layout is RGBTRIPLE (b,g,r) -> process as BGR24
 | 
			
		||||
#endif
 | 
			
		||||
				image
 | 
			
		||||
			);
 | 
			
		||||
 
 | 
			
		||||
@@ -363,6 +363,18 @@ done:
 | 
			
		||||
		_height = props.height;
 | 
			
		||||
		_frameByteSize = _width * _height * 3;
 | 
			
		||||
		_lineLength = _width * 3;
 | 
			
		||||
		// adjust flipMode for bottom-up images
 | 
			
		||||
		if (props.defstride < 0)
 | 
			
		||||
		{
 | 
			
		||||
			if (_flipMode == FlipMode::NO_CHANGE)
 | 
			
		||||
				_flipMode = FlipMode::HORIZONTAL;
 | 
			
		||||
			else if (_flipMode == FlipMode::HORIZONTAL)
 | 
			
		||||
				_flipMode = FlipMode::NO_CHANGE;
 | 
			
		||||
			else if (_flipMode == FlipMode::VERTICAL)
 | 
			
		||||
				_flipMode = FlipMode::BOTH;
 | 
			
		||||
			else if (_flipMode == FlipMode::BOTH)
 | 
			
		||||
				_flipMode = FlipMode::VERTICAL;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Cleanup
 | 
			
		||||
@@ -436,6 +448,14 @@ void MFGrabber::enumVideoCaptureDevices()
 | 
			
		||||
												properties.denominator = denominator;
 | 
			
		||||
												properties.pf = pixelformat;
 | 
			
		||||
												properties.guid = format;
 | 
			
		||||
 | 
			
		||||
												HRESULT hr = pType->GetUINT32(MF_MT_DEFAULT_STRIDE, (UINT32*)&properties.defstride);
 | 
			
		||||
												if (FAILED(hr))
 | 
			
		||||
												{
 | 
			
		||||
													hr = MFGetStrideForBitmapInfoHeader(format.Data1, width, &properties.defstride);
 | 
			
		||||
													if (FAILED(hr))
 | 
			
		||||
														DebugIf (verbose, _log, "failed to get default stride");
 | 
			
		||||
												}
 | 
			
		||||
												devicePropertyList.append(properties);
 | 
			
		||||
 | 
			
		||||
												DebugIf (verbose, _log, "%s %d x %d @ %d fps (%s)", QSTRING_CSTR(dev), properties.width, properties.height, properties.fps, QSTRING_CSTR(pixelFormatToString(properties.pf)));
 | 
			
		||||
@@ -797,7 +817,7 @@ QJsonArray MFGrabber::discover(const QJsonObject& params)
 | 
			
		||||
		resolution_default["width"] = 640;
 | 
			
		||||
		resolution_default["height"] = 480;
 | 
			
		||||
		resolution_default["fps"] = 25;
 | 
			
		||||
		format_default["format"] = "bgr24";
 | 
			
		||||
		format_default["format"] = "rgb24";
 | 
			
		||||
		format_default["resolution"] = resolution_default;
 | 
			
		||||
		video_inputs_default["inputIdx"] = 0;
 | 
			
		||||
		video_inputs_default["standards"] = "PAL";
 | 
			
		||||
 
 | 
			
		||||
@@ -27,7 +27,7 @@
 | 
			
		||||
static PixelFormat GetPixelFormatForGuid(const GUID guid)
 | 
			
		||||
{
 | 
			
		||||
	if (IsEqualGUID(guid, MFVideoFormat_RGB32)) return PixelFormat::RGB32;
 | 
			
		||||
	if (IsEqualGUID(guid, MFVideoFormat_RGB24)) return PixelFormat::BGR24;
 | 
			
		||||
	if (IsEqualGUID(guid, MFVideoFormat_RGB24)) return PixelFormat::RGB24;
 | 
			
		||||
	if (IsEqualGUID(guid, MFVideoFormat_YUY2)) return PixelFormat::YUYV;
 | 
			
		||||
	if (IsEqualGUID(guid, MFVideoFormat_UYVY)) return PixelFormat::UYVY;
 | 
			
		||||
#ifdef HAVE_TURBO_JPEG
 | 
			
		||||
@@ -145,11 +145,11 @@ public:
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
#ifdef HAVE_TURBO_JPEG
 | 
			
		||||
		if (_pixelformat != PixelFormat::MJPEG && _pixelformat != PixelFormat::BGR24 && _pixelformat != PixelFormat::NO_CHANGE)
 | 
			
		||||
		if (_pixelformat != PixelFormat::MJPEG && _pixelformat != PixelFormat::RGB24 && _pixelformat != PixelFormat::NO_CHANGE)
 | 
			
		||||
#else
 | 
			
		||||
		if (_pixelformat != PixelFormat::BGR24 && _pixelformat != PixelFormat::NO_CHANGE)
 | 
			
		||||
		if (_pixelformat != PixelFormat::RGB24 && _pixelformat != PixelFormat::NO_CHANGE)
 | 
			
		||||
#endif
 | 
			
		||||
			pSample = TransformSample(_transform, pSample);
 | 
			
		||||
			pSample = TransformSample(_transform, pSample); // forced conversion to RGB24, but memory layout is RGBTRIPLE (b,g,r) -> process as BGR24
 | 
			
		||||
 | 
			
		||||
		_hrStatus = pSample->ConvertToContiguousBuffer(&buffer);
 | 
			
		||||
		if (FAILED(_hrStatus))
 | 
			
		||||
@@ -181,9 +181,9 @@ public:
 | 
			
		||||
			_bEOS = TRUE; // Reached the end of the stream.
 | 
			
		||||
 | 
			
		||||
#ifdef HAVE_TURBO_JPEG
 | 
			
		||||
		if (_pixelformat != PixelFormat::MJPEG && _pixelformat != PixelFormat::BGR24 && _pixelformat != PixelFormat::NO_CHANGE)
 | 
			
		||||
		if (_pixelformat != PixelFormat::MJPEG && _pixelformat != PixelFormat::RGB24 && _pixelformat != PixelFormat::NO_CHANGE)
 | 
			
		||||
#else
 | 
			
		||||
		if (_pixelformat != PixelFormat::BGR24 && _pixelformat != PixelFormat::NO_CHANGE)
 | 
			
		||||
		if (_pixelformat != PixelFormat::RGB24 && _pixelformat != PixelFormat::NO_CHANGE)
 | 
			
		||||
#endif
 | 
			
		||||
			SAFE_RELEASE(pSample);
 | 
			
		||||
 | 
			
		||||
@@ -196,9 +196,9 @@ public:
 | 
			
		||||
	{
 | 
			
		||||
		_pixelformat = format;
 | 
			
		||||
#ifdef HAVE_TURBO_JPEG
 | 
			
		||||
		if (format == PixelFormat::MJPEG || format == PixelFormat::BGR24 || format == PixelFormat::NO_CHANGE)
 | 
			
		||||
		if (format == PixelFormat::MJPEG || format == PixelFormat::RGB24 || format == PixelFormat::NO_CHANGE)
 | 
			
		||||
#else
 | 
			
		||||
		if (format == PixelFormat::BGR24 || format == PixelFormat::NO_CHANGE)
 | 
			
		||||
		if (format == PixelFormat::RGB24 || format == PixelFormat::NO_CHANGE)
 | 
			
		||||
#endif
 | 
			
		||||
			return S_OK;
 | 
			
		||||
 | 
			
		||||
@@ -392,10 +392,10 @@ private:
 | 
			
		||||
private:
 | 
			
		||||
	long				_nRefCount;
 | 
			
		||||
	CRITICAL_SECTION	_critsec;
 | 
			
		||||
	MFGrabber*			_grabber;
 | 
			
		||||
	MFGrabber* _grabber;
 | 
			
		||||
	BOOL				_bEOS;
 | 
			
		||||
	HRESULT				_hrStatus;
 | 
			
		||||
	IMFTransform*		_transform;
 | 
			
		||||
	IMFTransform* _transform;
 | 
			
		||||
	PixelFormat			_pixelformat;
 | 
			
		||||
	std::atomic<bool>	_isBusy;
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -54,7 +54,9 @@ Q_GLOBAL_STATIC_WITH_ARGS(ControlIDPropertyMap, _controlIDPropertyMap, (initCont
 | 
			
		||||
static PixelFormat GetPixelFormat(const unsigned int format)
 | 
			
		||||
{
 | 
			
		||||
	if (format == V4L2_PIX_FMT_RGB32) return PixelFormat::RGB32;
 | 
			
		||||
	if (format == V4L2_PIX_FMT_RGB24) return PixelFormat::BGR24;
 | 
			
		||||
	if (format == V4L2_PIX_FMT_BGR32) return PixelFormat::BGR32;
 | 
			
		||||
	if (format == V4L2_PIX_FMT_RGB24) return PixelFormat::RGB24;
 | 
			
		||||
	if (format == V4L2_PIX_FMT_BGR24) return PixelFormat::BGR24;
 | 
			
		||||
	if (format == V4L2_PIX_FMT_YUYV) return PixelFormat::YUYV;
 | 
			
		||||
	if (format == V4L2_PIX_FMT_UYVY) return PixelFormat::UYVY;
 | 
			
		||||
	if (format == V4L2_PIX_FMT_NV12) return  PixelFormat::NV12;
 | 
			
		||||
@@ -557,10 +559,18 @@ void V4L2Grabber::init_device(VideoStandard videoStandard)
 | 
			
		||||
			fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_RGB32;
 | 
			
		||||
		break;
 | 
			
		||||
 | 
			
		||||
		case PixelFormat::BGR24:
 | 
			
		||||
		case PixelFormat::BGR32:
 | 
			
		||||
			fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_BGR32;
 | 
			
		||||
			break;
 | 
			
		||||
 | 
			
		||||
		case PixelFormat::RGB24:
 | 
			
		||||
			fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_RGB24;
 | 
			
		||||
		break;
 | 
			
		||||
 | 
			
		||||
		case PixelFormat::BGR24:
 | 
			
		||||
			fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_BGR24;
 | 
			
		||||
		break;
 | 
			
		||||
 | 
			
		||||
		case PixelFormat::YUYV:
 | 
			
		||||
			fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
 | 
			
		||||
		break;
 | 
			
		||||
@@ -691,7 +701,23 @@ void V4L2Grabber::init_device(VideoStandard videoStandard)
 | 
			
		||||
		}
 | 
			
		||||
		break;
 | 
			
		||||
 | 
			
		||||
		case V4L2_PIX_FMT_BGR32:
 | 
			
		||||
		{
 | 
			
		||||
			_pixelFormat = PixelFormat::BGR32;
 | 
			
		||||
			_frameByteSize = _width * _height * 4;
 | 
			
		||||
			Debug(_log, "Pixel format=BGR32");
 | 
			
		||||
		}
 | 
			
		||||
		break;
 | 
			
		||||
 | 
			
		||||
		case V4L2_PIX_FMT_RGB24:
 | 
			
		||||
		{
 | 
			
		||||
			_pixelFormat = PixelFormat::RGB24;
 | 
			
		||||
			_frameByteSize = _width * _height * 3;
 | 
			
		||||
			Debug(_log, "Pixel format=RGB24");
 | 
			
		||||
		}
 | 
			
		||||
		break;
 | 
			
		||||
 | 
			
		||||
		case V4L2_PIX_FMT_BGR24:
 | 
			
		||||
		{
 | 
			
		||||
			_pixelFormat = PixelFormat::BGR24;
 | 
			
		||||
			_frameByteSize = _width * _height * 3;
 | 
			
		||||
@@ -699,7 +725,6 @@ void V4L2Grabber::init_device(VideoStandard videoStandard)
 | 
			
		||||
		}
 | 
			
		||||
		break;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		case V4L2_PIX_FMT_YUYV:
 | 
			
		||||
		{
 | 
			
		||||
			_pixelFormat = PixelFormat::YUYV;
 | 
			
		||||
@@ -743,9 +768,9 @@ void V4L2Grabber::init_device(VideoStandard videoStandard)
 | 
			
		||||
 | 
			
		||||
		default:
 | 
			
		||||
#ifdef HAVE_TURBO_JPEG
 | 
			
		||||
			throw_exception("Only pixel formats RGB32, BGR24, YUYV, UYVY, NV12, I420 and MJPEG are supported");
 | 
			
		||||
			throw_exception("Only pixel formats RGB32, BGR32, RGB24, BGR24, YUYV, UYVY, NV12, I420 and MJPEG are supported");
 | 
			
		||||
#else
 | 
			
		||||
			throw_exception("Only pixel formats RGB32, BGR24, YUYV, UYVY, NV12 and I420 are supported");
 | 
			
		||||
			throw_exception("Only pixel formats RGB32, BGR32, RGB24, BGR24, YUYV, UYVY, NV12 and I420 are supported");
 | 
			
		||||
#endif
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -19,6 +19,7 @@ include_directories(
 | 
			
		||||
	dev_spi
 | 
			
		||||
	dev_rpi_pwm
 | 
			
		||||
	dev_tinker
 | 
			
		||||
	dev_ftdi
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
file (GLOB Leddevice_SOURCES
 | 
			
		||||
@@ -63,7 +64,11 @@ if(ENABLE_DEV_WS281XPWM)
 | 
			
		||||
	file (GLOB Leddevice_PWM_SOURCES "${CURRENT_SOURCE_DIR}/dev_rpi_pwm/*.h" "${CURRENT_SOURCE_DIR}/dev_rpi_pwm/*.cpp")
 | 
			
		||||
endif()
 | 
			
		||||
 | 
			
		||||
set(LedDevice_RESOURCES ${CURRENT_SOURCE_DIR}/LedDeviceSchemas.qrc)
 | 
			
		||||
if (ENABLE_DEV_FTDI)
 | 
			
		||||
	FILE ( GLOB Leddevice_FTDI_SOURCES "${CURRENT_SOURCE_DIR}/dev_ftdi/*.h" "${CURRENT_SOURCE_DIR}/dev_ftdi/*.cpp")
 | 
			
		||||
endif()
 | 
			
		||||
 | 
			
		||||
set(LedDevice_RESOURCES ${CURRENT_SOURCE_DIR}/LedDeviceSchemas.qrc )
 | 
			
		||||
 | 
			
		||||
set(Leddevice_SOURCES
 | 
			
		||||
	${Leddevice_SOURCES}
 | 
			
		||||
@@ -74,6 +79,7 @@ set(Leddevice_SOURCES
 | 
			
		||||
	${Leddevice_SPI_SOURCES}
 | 
			
		||||
	${Leddevice_TINKER_SOURCES}
 | 
			
		||||
	${Leddevice_USB_HID_SOURCES}
 | 
			
		||||
	${Leddevice_FTDI_SOURCES}
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
# auto generate header file that include all available leddevice headers
 | 
			
		||||
@@ -165,3 +171,10 @@ if(ENABLE_MDNS)
 | 
			
		||||
 	target_link_libraries(leddevice mdns)
 | 
			
		||||
endif()
 | 
			
		||||
 | 
			
		||||
if( ENABLE_DEV_FTDI )
 | 
			
		||||
	find_package(PkgConfig REQUIRED)
 | 
			
		||||
	pkg_check_modules(LIB_FTDI REQUIRED IMPORTED_TARGET libftdi1 )
 | 
			
		||||
	target_include_directories(leddevice PRIVATE PkgConfig::LIB_FTDI)
 | 
			
		||||
	target_link_libraries(leddevice PkgConfig::LIB_FTDI)
 | 
			
		||||
endif()
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -7,6 +7,7 @@
 | 
			
		||||
		<file alias="schema-dmx">schemas/schema-dmx.json</file>
 | 
			
		||||
		<file alias="schema-fadecandy">schemas/schema-fadecandy.json</file>
 | 
			
		||||
		<file alias="schema-file">schemas/schema-file.json</file>
 | 
			
		||||
		<file alias="schema-homeassistant">schemas/schema-homeassistant.json</file>
 | 
			
		||||
		<file alias="schema-hyperionusbasp">schemas/schema-hyperionusbasp.json</file>
 | 
			
		||||
		<file alias="schema-lightpack">schemas/schema-lightpack.json</file>
 | 
			
		||||
		<file alias="schema-lpd6803">schemas/schema-lpd6803.json</file>
 | 
			
		||||
@@ -38,5 +39,8 @@
 | 
			
		||||
		<file alias="schema-yeelight">schemas/schema-yeelight.json</file>
 | 
			
		||||
		<file alias="schema-razer">schemas/schema-razer.json</file>		
 | 
			
		||||
		<file alias="schema-cololight">schemas/schema-cololight.json</file>
 | 
			
		||||
		<file alias="schema-ws2812_ftdi">schemas/schema-ws2812_ftdi.json</file>
 | 
			
		||||
		<file alias="schema-apa102_ftdi">schemas/schema-apa102_ftdi.json</file>
 | 
			
		||||
		<file alias="schema-sk6812_ftdi">schemas/schema-sk6812_ftdi.json</file>
 | 
			
		||||
	</qresource>
 | 
			
		||||
</RCC>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										52
									
								
								libsrc/leddevice/dev_ftdi/LedDeviceAPA102_ftdi.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								libsrc/leddevice/dev_ftdi/LedDeviceAPA102_ftdi.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,52 @@
 | 
			
		||||
#include "LedDeviceAPA102_ftdi.h"
 | 
			
		||||
 | 
			
		||||
#define LED_HEADER 0b11100000
 | 
			
		||||
#define LED_BRIGHTNESS_FULL 31
 | 
			
		||||
 | 
			
		||||
LedDeviceAPA102_ftdi::LedDeviceAPA102_ftdi(const QJsonObject &deviceConfig) : ProviderFtdi(deviceConfig)
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
LedDevice *LedDeviceAPA102_ftdi::construct(const QJsonObject &deviceConfig)
 | 
			
		||||
{
 | 
			
		||||
	return new LedDeviceAPA102_ftdi(deviceConfig);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool LedDeviceAPA102_ftdi::init(const QJsonObject &deviceConfig)
 | 
			
		||||
{
 | 
			
		||||
	bool isInitOK = false;
 | 
			
		||||
	// Initialise sub-class
 | 
			
		||||
	if (ProviderFtdi::init(deviceConfig))
 | 
			
		||||
	{
 | 
			
		||||
        _brightnessControlMaxLevel = deviceConfig["brightnessControlMaxLevel"].toInt(LED_BRIGHTNESS_FULL);
 | 
			
		||||
        Info(_log, "[%s] Setting maximum brightness to [%d] = %d%%", QSTRING_CSTR(_activeDeviceType), _brightnessControlMaxLevel, _brightnessControlMaxLevel * 100 / LED_BRIGHTNESS_FULL);
 | 
			
		||||
 | 
			
		||||
        CreateHeader();
 | 
			
		||||
		isInitOK = true;
 | 
			
		||||
	}
 | 
			
		||||
	return isInitOK;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void LedDeviceAPA102_ftdi::CreateHeader()
 | 
			
		||||
{
 | 
			
		||||
	const unsigned int startFrameSize = 4;
 | 
			
		||||
	// Endframe, add additional 4 bytes to cover SK9922 Reset frame (in case SK9922 were sold as AP102) -  has no effect on APA102
 | 
			
		||||
	const unsigned int endFrameSize = (_ledCount / 32) * 4 + 4;
 | 
			
		||||
	const unsigned int APAbufferSize = (_ledCount * 4) + startFrameSize + endFrameSize;
 | 
			
		||||
	_ledBuffer.resize(APAbufferSize, 0);
 | 
			
		||||
	Debug(_log, "APA102 buffer created for %d LEDs", _ledCount);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int LedDeviceAPA102_ftdi::write(const std::vector<ColorRgb> &ledValues)
 | 
			
		||||
{
 | 
			
		||||
	for (signed iLed = 0; iLed < static_cast<int>(_ledCount); ++iLed)
 | 
			
		||||
	{
 | 
			
		||||
		const ColorRgb &rgb = ledValues[iLed];
 | 
			
		||||
		_ledBuffer[4 + iLed * 4 + 0] = LED_HEADER | _brightnessControlMaxLevel;
 | 
			
		||||
		_ledBuffer[4 + iLed * 4 + 1] = rgb.red;
 | 
			
		||||
		_ledBuffer[4 + iLed * 4 + 2] = rgb.green;
 | 
			
		||||
		_ledBuffer[4 + iLed * 4 + 3] = rgb.blue;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return writeBytes(_ledBuffer.size(), _ledBuffer.data());
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										50
									
								
								libsrc/leddevice/dev_ftdi/LedDeviceAPA102_ftdi.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								libsrc/leddevice/dev_ftdi/LedDeviceAPA102_ftdi.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,50 @@
 | 
			
		||||
#ifndef LEDEVICET_APA102_H
 | 
			
		||||
#define LEDEVICET_APA102_H
 | 
			
		||||
#include "ProviderFtdi.h"
 | 
			
		||||
 | 
			
		||||
class LedDeviceAPA102_ftdi : public ProviderFtdi
 | 
			
		||||
{
 | 
			
		||||
	Q_OBJECT
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
 | 
			
		||||
	///
 | 
			
		||||
	/// @brief Constructs an APA102 LED-device
 | 
			
		||||
	///
 | 
			
		||||
	/// @param deviceConfig Device's configuration as JSON-Object
 | 
			
		||||
	///
 | 
			
		||||
	explicit LedDeviceAPA102_ftdi(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);
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
 | 
			
		||||
	///
 | 
			
		||||
	/// @brief Initialise the device's configuration
 | 
			
		||||
	///
 | 
			
		||||
	/// @param[in] deviceConfig the JSON device configuration
 | 
			
		||||
	/// @return True, if success
 | 
			
		||||
	///
 | 
			
		||||
	bool init(const QJsonObject& deviceConfig) override;
 | 
			
		||||
 | 
			
		||||
	void CreateHeader();
 | 
			
		||||
 | 
			
		||||
	///
 | 
			
		||||
	/// @brief Writes the RGB-Color values to the LEDs.
 | 
			
		||||
	///
 | 
			
		||||
	/// @param[in] ledValues The RGB-color per LED
 | 
			
		||||
	/// @return Zero on success, else negative
 | 
			
		||||
	///
 | 
			
		||||
	int write(const std::vector<ColorRgb>& ledValues) override;
 | 
			
		||||
 | 
			
		||||
	/// The brighness level. Possibile values 1 .. 31.
 | 
			
		||||
	int _brightnessControlMaxLevel;
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif // LEDEVICET_APA102_H
 | 
			
		||||
							
								
								
									
										96
									
								
								libsrc/leddevice/dev_ftdi/LedDeviceSk6812_ftdi.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								libsrc/leddevice/dev_ftdi/LedDeviceSk6812_ftdi.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,96 @@
 | 
			
		||||
#include "LedDeviceSk6812_ftdi.h"
 | 
			
		||||
 | 
			
		||||
LedDeviceSk6812_ftdi::LedDeviceSk6812_ftdi(const QJsonObject &deviceConfig)
 | 
			
		||||
	: ProviderFtdi(deviceConfig),
 | 
			
		||||
	  _whiteAlgorithm(RGBW::WhiteAlgorithm::INVALID),
 | 
			
		||||
	  SPI_BYTES_PER_COLOUR(4),
 | 
			
		||||
	  bitpair_to_byte{
 | 
			
		||||
		  0b10001000,
 | 
			
		||||
		  0b10001100,
 | 
			
		||||
		  0b11001000,
 | 
			
		||||
		  0b11001100}
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
LedDevice *LedDeviceSk6812_ftdi::construct(const QJsonObject &deviceConfig)
 | 
			
		||||
{
 | 
			
		||||
	return new LedDeviceSk6812_ftdi(deviceConfig);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool LedDeviceSk6812_ftdi::init(const QJsonObject &deviceConfig)
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
	bool isInitOK = false;
 | 
			
		||||
 | 
			
		||||
	// Initialise sub-class
 | 
			
		||||
	if (ProviderFtdi::init(deviceConfig))
 | 
			
		||||
	{
 | 
			
		||||
		_brightnessControlMaxLevel = deviceConfig["brightnessControlMaxLevel"].toInt(255);
 | 
			
		||||
		Info(_log, "[%s] Setting maximum brightness to [%d]", QSTRING_CSTR(_activeDeviceType), _brightnessControlMaxLevel);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		QString whiteAlgorithm = deviceConfig["whiteAlgorithm"].toString("white_off");
 | 
			
		||||
 | 
			
		||||
		_whiteAlgorithm = RGBW::stringToWhiteAlgorithm(whiteAlgorithm);
 | 
			
		||||
		if (_whiteAlgorithm == RGBW::WhiteAlgorithm::INVALID)
 | 
			
		||||
		{
 | 
			
		||||
			QString errortext = QString ("unknown whiteAlgorithm: %1").arg(whiteAlgorithm);
 | 
			
		||||
			this->setInError(errortext);
 | 
			
		||||
			isInitOK = false;
 | 
			
		||||
		}
 | 
			
		||||
		else
 | 
			
		||||
		{
 | 
			
		||||
 | 
			
		||||
			Debug(_log, "whiteAlgorithm : %s", QSTRING_CSTR(whiteAlgorithm));
 | 
			
		||||
 | 
			
		||||
			WarningIf((_baudRate_Hz < 2050000 || _baudRate_Hz > 3750000), _log, "Baud rate %d outside recommended range (2050000 -> 3750000)", _baudRate_Hz);
 | 
			
		||||
 | 
			
		||||
			const int SPI_FRAME_END_LATCH_BYTES = 3;
 | 
			
		||||
			_ledBuffer.resize(_ledRGBWCount * SPI_BYTES_PER_COLOUR + SPI_FRAME_END_LATCH_BYTES, 0x00);
 | 
			
		||||
 | 
			
		||||
			isInitOK = true;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return isInitOK;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
inline __attribute__((always_inline)) uint8_t LedDeviceSk6812_ftdi::scale(uint8_t i, uint8_t scale) {
 | 
			
		||||
	return (((uint16_t)i) * (1+(uint16_t)(scale))) >> 8;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int LedDeviceSk6812_ftdi::write(const std::vector<ColorRgb> &ledValues)
 | 
			
		||||
{
 | 
			
		||||
	unsigned spi_ptr = 0;
 | 
			
		||||
	const int SPI_BYTES_PER_LED = sizeof(ColorRgbw) * SPI_BYTES_PER_COLOUR;
 | 
			
		||||
 | 
			
		||||
	ColorRgbw temp_rgbw;
 | 
			
		||||
	ColorRgb scaled_color;
 | 
			
		||||
	for (const ColorRgb &color : ledValues)
 | 
			
		||||
	{
 | 
			
		||||
		scaled_color.red = scale(color.red, _brightnessControlMaxLevel);
 | 
			
		||||
		scaled_color.green = scale(color.green, _brightnessControlMaxLevel);
 | 
			
		||||
		scaled_color.blue = scale(color.blue, _brightnessControlMaxLevel);
 | 
			
		||||
 | 
			
		||||
        RGBW::Rgb_to_Rgbw(scaled_color, &temp_rgbw, _whiteAlgorithm);
 | 
			
		||||
 | 
			
		||||
		uint32_t colorBits =
 | 
			
		||||
			((uint32_t)temp_rgbw.red << 24) +
 | 
			
		||||
			((uint32_t)temp_rgbw.green << 16) +
 | 
			
		||||
			((uint32_t)temp_rgbw.blue << 8) +
 | 
			
		||||
			temp_rgbw.white;
 | 
			
		||||
 | 
			
		||||
		for (int j = SPI_BYTES_PER_LED - 1; j >= 0; j--)
 | 
			
		||||
		{
 | 
			
		||||
			_ledBuffer[spi_ptr + j] = bitpair_to_byte[colorBits & 0x3];
 | 
			
		||||
			colorBits >>= 2;
 | 
			
		||||
		}
 | 
			
		||||
		spi_ptr += SPI_BYTES_PER_LED;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_ledBuffer[spi_ptr++] = 0;
 | 
			
		||||
	_ledBuffer[spi_ptr++] = 0;
 | 
			
		||||
	_ledBuffer[spi_ptr++] = 0;
 | 
			
		||||
 | 
			
		||||
	return writeBytes(_ledBuffer.size(), _ledBuffer.data());
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										52
									
								
								libsrc/leddevice/dev_ftdi/LedDeviceSk6812_ftdi.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								libsrc/leddevice/dev_ftdi/LedDeviceSk6812_ftdi.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,52 @@
 | 
			
		||||
#ifndef LEDEVICESK6812ftdi_H
 | 
			
		||||
#define LEDEVICESK6812ftdi_H
 | 
			
		||||
 | 
			
		||||
#include "ProviderFtdi.h"
 | 
			
		||||
 | 
			
		||||
class LedDeviceSk6812_ftdi : public ProviderFtdi
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
 | 
			
		||||
	///
 | 
			
		||||
	/// @brief Constructs a Sk6801 LED-device
 | 
			
		||||
	///
 | 
			
		||||
	/// @param deviceConfig Device's configuration as JSON-Object
 | 
			
		||||
	///
 | 
			
		||||
	explicit LedDeviceSk6812_ftdi(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);
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
 | 
			
		||||
	///
 | 
			
		||||
	/// @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<ColorRgb>& ledValues) override;
 | 
			
		||||
 | 
			
		||||
	inline __attribute__((always_inline)) uint8_t scale(uint8_t i, uint8_t scale);
 | 
			
		||||
 | 
			
		||||
	RGBW::WhiteAlgorithm _whiteAlgorithm;
 | 
			
		||||
 | 
			
		||||
	const int SPI_BYTES_PER_COLOUR;
 | 
			
		||||
	uint8_t bitpair_to_byte[4];
 | 
			
		||||
 | 
			
		||||
	int _brightnessControlMaxLevel;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif // LEDEVICESK6812ftdi_H
 | 
			
		||||
							
								
								
									
										93
									
								
								libsrc/leddevice/dev_ftdi/LedDeviceWs2812_ftdi.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								libsrc/leddevice/dev_ftdi/LedDeviceWs2812_ftdi.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,93 @@
 | 
			
		||||
#include "LedDeviceWs2812_ftdi.h"
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
From the data sheet:
 | 
			
		||||
 | 
			
		||||
(TH+TL=1.25μs±600ns)
 | 
			
		||||
 | 
			
		||||
T0H,	 0 code, high level time,	 0.40µs ±0.150ns
 | 
			
		||||
T0L,	 0 code, low level time,	 0.85µs ±0.150ns
 | 
			
		||||
T1H,	 1 code, high level time,	 0.80µs ±0.150ns
 | 
			
		||||
T1L,	 1 code, low level time,	 0.45µs ±0.150ns
 | 
			
		||||
WT,	 Wait for the processing time,	 NA
 | 
			
		||||
Trst,	 Reset code,low level time,	 50µs (not anymore... need 300uS for latest revision)
 | 
			
		||||
 | 
			
		||||
To normalise the pulse times so they fit in 4 SPI bits:
 | 
			
		||||
 | 
			
		||||
On the assumption that the "low" time doesnt matter much
 | 
			
		||||
 | 
			
		||||
A SPI bit time of 0.40uS = 2.5 Mbit/sec
 | 
			
		||||
T0 is sent as 1000
 | 
			
		||||
T1 is sent as 1100
 | 
			
		||||
 | 
			
		||||
With a bit of excel testing, we can work out the maximum and minimum speeds:
 | 
			
		||||
2106000 MIN
 | 
			
		||||
2590500 AVG
 | 
			
		||||
3075000 MAX
 | 
			
		||||
 | 
			
		||||
Wait time:
 | 
			
		||||
Not Applicable for WS2812
 | 
			
		||||
 | 
			
		||||
Reset time:
 | 
			
		||||
using the max of 3075000, the bit time is 0.325
 | 
			
		||||
Reset time is 300uS = 923 bits = 116 bytes
 | 
			
		||||
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
LedDeviceWs2812_ftdi::LedDeviceWs2812_ftdi(const QJsonObject &deviceConfig)
 | 
			
		||||
	: ProviderFtdi(deviceConfig),
 | 
			
		||||
	  SPI_BYTES_PER_COLOUR(4),
 | 
			
		||||
	  SPI_FRAME_END_LATCH_BYTES(116),
 | 
			
		||||
	  bitpair_to_byte{
 | 
			
		||||
		  0b10001000,
 | 
			
		||||
		  0b10001100,
 | 
			
		||||
		  0b11001000,
 | 
			
		||||
		  0b11001100,
 | 
			
		||||
	  }
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
LedDevice *LedDeviceWs2812_ftdi::construct(const QJsonObject &deviceConfig)
 | 
			
		||||
{
 | 
			
		||||
	return new LedDeviceWs2812_ftdi(deviceConfig);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool LedDeviceWs2812_ftdi::init(const QJsonObject &deviceConfig)
 | 
			
		||||
{
 | 
			
		||||
	bool isInitOK = false;
 | 
			
		||||
 | 
			
		||||
	// Initialise sub-class
 | 
			
		||||
	if (ProviderFtdi::init(deviceConfig))
 | 
			
		||||
	{
 | 
			
		||||
		WarningIf((_baudRate_Hz < 2106000 || _baudRate_Hz > 3075000), _log, "Baud rate %d outside recommended range (2106000 -> 3075000)", _baudRate_Hz);
 | 
			
		||||
		_ledBuffer.resize(_ledRGBCount * SPI_BYTES_PER_COLOUR + SPI_FRAME_END_LATCH_BYTES, 0x00);
 | 
			
		||||
		isInitOK = true;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return isInitOK;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int LedDeviceWs2812_ftdi::write(const std::vector<ColorRgb> &ledValues)
 | 
			
		||||
{
 | 
			
		||||
	unsigned spi_ptr = 0;
 | 
			
		||||
	const int SPI_BYTES_PER_LED = sizeof(ColorRgb) * SPI_BYTES_PER_COLOUR;
 | 
			
		||||
 | 
			
		||||
	for (const ColorRgb &color : ledValues)
 | 
			
		||||
	{
 | 
			
		||||
		uint32_t colorBits = ((unsigned int)color.red << 16) | ((unsigned int)color.green << 8) | color.blue;
 | 
			
		||||
 | 
			
		||||
		for (int j = SPI_BYTES_PER_LED - 1; j >= 0; j--)
 | 
			
		||||
		{
 | 
			
		||||
			_ledBuffer[spi_ptr + j] = bitpair_to_byte[colorBits & 0x3];
 | 
			
		||||
			colorBits >>= 2;
 | 
			
		||||
		}
 | 
			
		||||
		spi_ptr += SPI_BYTES_PER_LED;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for (int j = 0; j < SPI_FRAME_END_LATCH_BYTES; j++)
 | 
			
		||||
	{
 | 
			
		||||
		_ledBuffer[spi_ptr++] = 0;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return writeBytes(_ledBuffer.size(), _ledBuffer.data());
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										49
									
								
								libsrc/leddevice/dev_ftdi/LedDeviceWs2812_ftdi.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								libsrc/leddevice/dev_ftdi/LedDeviceWs2812_ftdi.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,49 @@
 | 
			
		||||
#ifndef LEDEVICEWS2812_ftdi_H
 | 
			
		||||
#define LEDEVICEWS2812_ftdi_H
 | 
			
		||||
 | 
			
		||||
#include "ProviderFtdi.h"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class LedDeviceWs2812_ftdi : public ProviderFtdi
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
 | 
			
		||||
	///
 | 
			
		||||
	/// @brief Constructs a Ws2812 LED-device
 | 
			
		||||
	///
 | 
			
		||||
	/// @param deviceConfig Device's configuration as JSON-Object
 | 
			
		||||
	///
 | 
			
		||||
	explicit LedDeviceWs2812_ftdi(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);
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
 | 
			
		||||
	///
 | 
			
		||||
	/// @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<ColorRgb>& ledValues) override;
 | 
			
		||||
 | 
			
		||||
	const int SPI_BYTES_PER_COLOUR;
 | 
			
		||||
	const int SPI_FRAME_END_LATCH_BYTES;
 | 
			
		||||
 | 
			
		||||
	uint8_t bitpair_to_byte[4];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif // LEDEVICEWS2812_ftdi_H
 | 
			
		||||
							
								
								
									
										208
									
								
								libsrc/leddevice/dev_ftdi/ProviderFtdi.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										208
									
								
								libsrc/leddevice/dev_ftdi/ProviderFtdi.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,208 @@
 | 
			
		||||
// LedDevice includes
 | 
			
		||||
#include <leddevice/LedDevice.h>
 | 
			
		||||
#include "ProviderFtdi.h"
 | 
			
		||||
#include <utils/WaitTime.h>
 | 
			
		||||
 | 
			
		||||
#include <ftdi.h>
 | 
			
		||||
#include <libusb.h>
 | 
			
		||||
 | 
			
		||||
#define ANY_FTDI_VENDOR 0x0
 | 
			
		||||
#define ANY_FTDI_PRODUCT 0x0
 | 
			
		||||
 | 
			
		||||
#define FTDI_CHECK_RESULT(statement) if (statement) {setInError(ftdi_get_error_string(_ftdic)); return rc;}
 | 
			
		||||
 | 
			
		||||
namespace Pin
 | 
			
		||||
{
 | 
			
		||||
// enumerate the AD bus for convenience.
 | 
			
		||||
enum bus_t
 | 
			
		||||
{
 | 
			
		||||
	SK = 0x01, // ADBUS0, SPI data clock
 | 
			
		||||
	DO = 0x02, // ADBUS1, SPI data out
 | 
			
		||||
	CS = 0x08, // ADBUS3, SPI chip select, active low
 | 
			
		||||
};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const uint8_t pinInitialState = Pin::CS;
 | 
			
		||||
// Use these pins as outputs
 | 
			
		||||
const uint8_t pinDirection = Pin::SK | Pin::DO | Pin::CS;
 | 
			
		||||
 | 
			
		||||
const QString ProviderFtdi::AUTO_SETTING = QString("auto");
 | 
			
		||||
 | 
			
		||||
ProviderFtdi::ProviderFtdi(const QJsonObject &deviceConfig)
 | 
			
		||||
	: LedDevice(deviceConfig),
 | 
			
		||||
	  _ftdic(nullptr),
 | 
			
		||||
	  _baudRate_Hz(1000000)
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool ProviderFtdi::init(const QJsonObject &deviceConfig)
 | 
			
		||||
{
 | 
			
		||||
	bool isInitOK = false;
 | 
			
		||||
 | 
			
		||||
	if (LedDevice::init(deviceConfig))
 | 
			
		||||
	{
 | 
			
		||||
		_baudRate_Hz = deviceConfig["rate"].toInt(_baudRate_Hz);
 | 
			
		||||
		_deviceName = deviceConfig["output"].toString(AUTO_SETTING);
 | 
			
		||||
 | 
			
		||||
		Debug(_log, "_baudRate_Hz [%d]", _baudRate_Hz);
 | 
			
		||||
		Debug(_log, "_deviceName [%s]", QSTRING_CSTR(_deviceName));
 | 
			
		||||
 | 
			
		||||
		isInitOK = true;
 | 
			
		||||
	}
 | 
			
		||||
	return isInitOK;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int ProviderFtdi::open()
 | 
			
		||||
{
 | 
			
		||||
	int rc = 0;
 | 
			
		||||
 | 
			
		||||
	_ftdic = ftdi_new();
 | 
			
		||||
 | 
			
		||||
	if (ftdi_init(_ftdic) < 0)
 | 
			
		||||
	{
 | 
			
		||||
		_ftdic = nullptr;
 | 
			
		||||
		setInError("Could not initialize the ftdi library");
 | 
			
		||||
		return -1;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Debug(_log, "Opening FTDI device=%s", QSTRING_CSTR(_deviceName));
 | 
			
		||||
 | 
			
		||||
	FTDI_CHECK_RESULT((rc = ftdi_usb_open_string(_ftdic, QSTRING_CSTR(_deviceName))) < 0);
 | 
			
		||||
	/* doing this disable resets things if they were in a bad state */
 | 
			
		||||
	FTDI_CHECK_RESULT((rc = ftdi_disable_bitbang(_ftdic)) < 0);
 | 
			
		||||
	FTDI_CHECK_RESULT((rc = ftdi_setflowctrl(_ftdic, SIO_DISABLE_FLOW_CTRL)) < 0);
 | 
			
		||||
	FTDI_CHECK_RESULT((rc = ftdi_set_bitmode(_ftdic, 0x00, BITMODE_RESET)) < 0);
 | 
			
		||||
	FTDI_CHECK_RESULT((rc = ftdi_set_bitmode(_ftdic, 0xff, BITMODE_MPSSE)) < 0);
 | 
			
		||||
 | 
			
		||||
	double reference_clock = 60e6;
 | 
			
		||||
	int divisor = (reference_clock / 2 / _baudRate_Hz) - 1;
 | 
			
		||||
	std::vector<uint8_t> buf = {
 | 
			
		||||
		DIS_DIV_5,
 | 
			
		||||
		TCK_DIVISOR,
 | 
			
		||||
		static_cast<unsigned char>(divisor),
 | 
			
		||||
		static_cast<unsigned char>(divisor >> 8),
 | 
			
		||||
		SET_BITS_LOW,    // opcode: set low bits (ADBUS[0-7]
 | 
			
		||||
		pinInitialState, // argument: inital pin state
 | 
			
		||||
		pinDirection
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	FTDI_CHECK_RESULT((rc = ftdi_write_data(_ftdic, buf.data(), buf.size())) != buf.size());
 | 
			
		||||
 | 
			
		||||
	_isDeviceReady = true;
 | 
			
		||||
	return rc;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int ProviderFtdi::close()
 | 
			
		||||
{
 | 
			
		||||
	LedDevice::close();
 | 
			
		||||
	if (_ftdic != nullptr) {
 | 
			
		||||
		Debug(_log, "Closing FTDI device");
 | 
			
		||||
		// Delay to give time to push color black from writeBlack() into the led,
 | 
			
		||||
		// otherwise frame transmission will be terminated half way through
 | 
			
		||||
		wait(30);
 | 
			
		||||
		ftdi_set_bitmode(_ftdic, 0x00, BITMODE_RESET);
 | 
			
		||||
		ftdi_usb_close(_ftdic);
 | 
			
		||||
		ftdi_free(_ftdic);
 | 
			
		||||
		_ftdic = nullptr;
 | 
			
		||||
	}
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ProviderFtdi::setInError(const QString &errorMsg, bool isRecoverable)
 | 
			
		||||
{
 | 
			
		||||
	close();
 | 
			
		||||
 | 
			
		||||
	LedDevice::setInError(errorMsg, isRecoverable);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int ProviderFtdi::writeBytes(const qint64 size, const uint8_t *data)
 | 
			
		||||
{
 | 
			
		||||
	int rc;
 | 
			
		||||
	int count_arg = size - 1;
 | 
			
		||||
	std::vector<uint8_t> buf = {
 | 
			
		||||
		SET_BITS_LOW,
 | 
			
		||||
		pinInitialState & ~Pin::CS,
 | 
			
		||||
		pinDirection,
 | 
			
		||||
		MPSSE_DO_WRITE | MPSSE_WRITE_NEG,
 | 
			
		||||
		static_cast<unsigned char>(count_arg),
 | 
			
		||||
		static_cast<unsigned char>(count_arg >> 8),
 | 
			
		||||
		SET_BITS_LOW,
 | 
			
		||||
		pinInitialState | Pin::CS,
 | 
			
		||||
		pinDirection
 | 
			
		||||
	};
 | 
			
		||||
	// insert before last SET_BITS_LOW command
 | 
			
		||||
	// SET_BITS_LOW takes 2 arguments, so we're inserting data in -3 position from the end
 | 
			
		||||
	buf.insert(buf.end() - 3, &data[0], &data[size]);
 | 
			
		||||
 | 
			
		||||
	FTDI_CHECK_RESULT((rc = ftdi_write_data(_ftdic, buf.data(), buf.size())) != buf.size());
 | 
			
		||||
	return rc;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
QJsonObject ProviderFtdi::discover(const QJsonObject & /*params*/)
 | 
			
		||||
{
 | 
			
		||||
	QJsonObject devicesDiscovered;
 | 
			
		||||
	QJsonArray deviceList;
 | 
			
		||||
	struct ftdi_device_list *devlist;
 | 
			
		||||
	struct ftdi_context *ftdic;
 | 
			
		||||
 | 
			
		||||
	ftdic = ftdi_new();
 | 
			
		||||
 | 
			
		||||
	if (ftdi_usb_find_all(ftdic, &devlist, ANY_FTDI_VENDOR, ANY_FTDI_PRODUCT) > 0)
 | 
			
		||||
	{
 | 
			
		||||
		struct ftdi_device_list *curdev = devlist;
 | 
			
		||||
		QMap<QString, uint8_t> deviceIndexes;
 | 
			
		||||
 | 
			
		||||
		while (curdev)
 | 
			
		||||
		{
 | 
			
		||||
			libusb_device_descriptor desc;
 | 
			
		||||
			int rc = libusb_get_device_descriptor(curdev->dev, &desc);
 | 
			
		||||
			if (rc == 0)
 | 
			
		||||
			{
 | 
			
		||||
				QString vendorIdentifier =  QString("0x%1").arg(desc.idVendor, 4, 16, QChar{'0'});
 | 
			
		||||
				QString productIdentifier = QString("0x%1").arg(desc.idProduct, 4, 16, QChar{'0'});
 | 
			
		||||
				QString vendorAndProduct = QString("%1:%2")
 | 
			
		||||
										   .arg(vendorIdentifier)
 | 
			
		||||
										   .arg(productIdentifier);
 | 
			
		||||
				uint8_t deviceIndex = deviceIndexes.value(vendorAndProduct, 0);
 | 
			
		||||
 | 
			
		||||
				char serial_string[128] = {0};
 | 
			
		||||
				char manufacturer_string[128] = {0};
 | 
			
		||||
				char description_string[128] = {0};
 | 
			
		||||
				ftdi_usb_get_strings2(ftdic, curdev->dev, manufacturer_string, 128, description_string, 128, serial_string, 128);
 | 
			
		||||
 | 
			
		||||
				QString serialNumber {serial_string};
 | 
			
		||||
				QString ftdiOpenString;
 | 
			
		||||
				if(!serialNumber.isEmpty())
 | 
			
		||||
				{
 | 
			
		||||
					ftdiOpenString = QString("s:%1:%2").arg(vendorAndProduct).arg(serialNumber);
 | 
			
		||||
				}
 | 
			
		||||
				else
 | 
			
		||||
				{
 | 
			
		||||
					ftdiOpenString = QString("i:%1:%2").arg(vendorAndProduct).arg(deviceIndex);
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				deviceList.push_back(QJsonObject{
 | 
			
		||||
										 {"ftdiOpenString", ftdiOpenString},
 | 
			
		||||
										 {"vendorIdentifier", vendorIdentifier},
 | 
			
		||||
										 {"productIdentifier", productIdentifier},
 | 
			
		||||
										 {"deviceIndex", deviceIndex},
 | 
			
		||||
										 {"serialNumber", serialNumber},
 | 
			
		||||
										 {"manufacturer", manufacturer_string},
 | 
			
		||||
										 {"description", description_string}
 | 
			
		||||
									 });
 | 
			
		||||
				deviceIndexes.insert(vendorAndProduct, deviceIndex + 1);
 | 
			
		||||
			}
 | 
			
		||||
			curdev = curdev->next;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ftdi_list_free(&devlist);
 | 
			
		||||
	ftdi_free(ftdic);
 | 
			
		||||
 | 
			
		||||
	devicesDiscovered.insert("ledDeviceType", _activeDeviceType);
 | 
			
		||||
	devicesDiscovered.insert("devices", deviceList);
 | 
			
		||||
 | 
			
		||||
	Debug(_log, "FTDI devices discovered: [%s]", QString(QJsonDocument(devicesDiscovered).toJson(QJsonDocument::Compact)).toUtf8().constData());
 | 
			
		||||
 | 
			
		||||
	return devicesDiscovered;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										76
									
								
								libsrc/leddevice/dev_ftdi/ProviderFtdi.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								libsrc/leddevice/dev_ftdi/ProviderFtdi.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,76 @@
 | 
			
		||||
#ifndef PROVIDERFtdi_H
 | 
			
		||||
#define PROVIDERFtdi_H
 | 
			
		||||
 | 
			
		||||
// LedDevice includes
 | 
			
		||||
#include <leddevice/LedDevice.h>
 | 
			
		||||
 | 
			
		||||
#include <ftdi.h>
 | 
			
		||||
 | 
			
		||||
///
 | 
			
		||||
/// The ProviderFtdi implements an abstract base-class for LedDevices using a Ftdi-device.
 | 
			
		||||
///
 | 
			
		||||
class ProviderFtdi : public LedDevice
 | 
			
		||||
{
 | 
			
		||||
	Q_OBJECT
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
 | 
			
		||||
	///
 | 
			
		||||
	/// @brief Constructs a Ftdi LED-device
 | 
			
		||||
	///
 | 
			
		||||
	ProviderFtdi(const QJsonObject& deviceConfig);
 | 
			
		||||
 | 
			
		||||
	static const QString AUTO_SETTING;
 | 
			
		||||
 | 
			
		||||
protected:
 | 
			
		||||
	///
 | 
			
		||||
	/// @brief Opens the output device.
 | 
			
		||||
	///
 | 
			
		||||
	/// @return Zero on success (i.e. device is ready), else negative
 | 
			
		||||
	///
 | 
			
		||||
	int open() override;
 | 
			
		||||
 | 
			
		||||
	///
 | 
			
		||||
	/// Sets configuration
 | 
			
		||||
	///
 | 
			
		||||
	/// @param deviceConfig the json device config
 | 
			
		||||
	/// @return true if success
 | 
			
		||||
	bool init(const QJsonObject& deviceConfig) override;
 | 
			
		||||
 | 
			
		||||
	///
 | 
			
		||||
	/// @brief Closes the UDP device.
 | 
			
		||||
	///
 | 
			
		||||
	/// @return Zero on success (i.e. device is closed), else negative
 | 
			
		||||
	///
 | 
			
		||||
	int close() override;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	/// @brief Write the given bytes to the Ftdi-device
 | 
			
		||||
	///
 | 
			
		||||
	/// @param[in[ size The length of the data
 | 
			
		||||
	/// @param[in] data The data
 | 
			
		||||
	/// @return Zero on success, else negative
 | 
			
		||||
	///
 | 
			
		||||
	int writeBytes(const qint64 size, const uint8_t* data);
 | 
			
		||||
 | 
			
		||||
	
 | 
			
		||||
	QJsonObject discover(const QJsonObject& params) override;
 | 
			
		||||
 | 
			
		||||
	/// The Ftdi serial-device
 | 
			
		||||
	struct ftdi_context *_ftdic;
 | 
			
		||||
 | 
			
		||||
	/// The used baud-rate of the output device
 | 
			
		||||
	qint32 _baudRate_Hz;
 | 
			
		||||
	QString _deviceName;
 | 
			
		||||
 | 
			
		||||
protected slots:
 | 
			
		||||
 | 
			
		||||
	///
 | 
			
		||||
	/// @brief Set device in error state
 | 
			
		||||
	///
 | 
			
		||||
	/// @param errorMsg The error message to be logged
 | 
			
		||||
	///
 | 
			
		||||
	void setInError(const QString& errorMsg, bool isRecoverable=true) override;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif // PROVIDERFtdi_H
 | 
			
		||||
							
								
								
									
										446
									
								
								libsrc/leddevice/dev_net/LedDeviceHomeAssistant.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										446
									
								
								libsrc/leddevice/dev_net/LedDeviceHomeAssistant.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,446 @@
 | 
			
		||||
// Local-Hyperion includes
 | 
			
		||||
#include "LedDeviceHomeAssistant.h"
 | 
			
		||||
 | 
			
		||||
#include <ssdp/SSDPDiscover.h>
 | 
			
		||||
// mDNS discover
 | 
			
		||||
#ifdef ENABLE_MDNS
 | 
			
		||||
#include <mdns/MdnsBrowser.h>
 | 
			
		||||
#include <mdns/MdnsServiceRegister.h>
 | 
			
		||||
#endif
 | 
			
		||||
#include <utils/NetUtils.h>
 | 
			
		||||
#include <utils/ColorRgb.h>
 | 
			
		||||
 | 
			
		||||
#include <algorithm>
 | 
			
		||||
 | 
			
		||||
// Constants
 | 
			
		||||
namespace {
 | 
			
		||||
const bool verbose = false;
 | 
			
		||||
 | 
			
		||||
// Configuration settings
 | 
			
		||||
const char CONFIG_HOST[] = "host";
 | 
			
		||||
const char CONFIG_PORT[] = "port";
 | 
			
		||||
const char CONFIG_AUTH_TOKEN[] = "token";
 | 
			
		||||
const char CONFIG_ENITYIDS[] = "entityIds";
 | 
			
		||||
const char CONFIG_BRIGHTNESS[] = "brightness";
 | 
			
		||||
const char CONFIG_BRIGHTNESS_OVERWRITE[] = "overwriteBrightness";
 | 
			
		||||
const char CONFIG_FULL_BRIGHTNESS_AT_START[] = "fullBrightnessAtStart";
 | 
			
		||||
const char CONFIG_ON_OFF_BLACK[] = "switchOffOnBlack";
 | 
			
		||||
const char CONFIG_TRANSITIONTIME[] = "transitionTime";
 | 
			
		||||
 | 
			
		||||
const bool DEFAULT_IS_BRIGHTNESS_OVERWRITE = true;
 | 
			
		||||
const bool DEFAULT_IS_FULL_BRIGHTNESS_AT_START = true;
 | 
			
		||||
const int  BRI_MAX = 255;
 | 
			
		||||
const bool DEFAULT_IS_SWITCH_OFF_ON_BLACK = false;
 | 
			
		||||
 | 
			
		||||
// Home Assistant API
 | 
			
		||||
const int  API_DEFAULT_PORT = 8123;
 | 
			
		||||
const char API_BASE_PATH[] = "/api/";
 | 
			
		||||
const char API_STATES[] = "states";
 | 
			
		||||
const char API_LIGHT_TURN_ON[] = "services/light/turn_on";
 | 
			
		||||
const char API_LIGHT_TURN_OFF[] = "services/light/turn_off";
 | 
			
		||||
 | 
			
		||||
const char ENTITY_ID[] = "entity_id";
 | 
			
		||||
const char RGB_COLOR[] = "rgb_color";
 | 
			
		||||
const char BRIGHTNESS[] = "brightness";
 | 
			
		||||
const char TRANSITION[] = "transition";
 | 
			
		||||
const char FLASH[] = "flash";
 | 
			
		||||
 | 
			
		||||
// // Home Assistant ssdp services
 | 
			
		||||
const char SSDP_ID[] = "ssdp:all";
 | 
			
		||||
const char SSDP_FILTER_HEADER[] = "ST";
 | 
			
		||||
const char SSDP_FILTER[] = "(.*)home-assistant.io(.*)";
 | 
			
		||||
 | 
			
		||||
} //End of constants
 | 
			
		||||
 | 
			
		||||
LedDeviceHomeAssistant::LedDeviceHomeAssistant(const QJsonObject& deviceConfig)
 | 
			
		||||
	: LedDevice(deviceConfig)
 | 
			
		||||
	, _restApi(nullptr)
 | 
			
		||||
	, _apiPort(API_DEFAULT_PORT)
 | 
			
		||||
	, _isBrightnessOverwrite(DEFAULT_IS_BRIGHTNESS_OVERWRITE)
 | 
			
		||||
	, _isFullBrightnessAtStart(DEFAULT_IS_FULL_BRIGHTNESS_AT_START)
 | 
			
		||||
	, _brightness (BRI_MAX)
 | 
			
		||||
{
 | 
			
		||||
#ifdef ENABLE_MDNS
 | 
			
		||||
	QMetaObject::invokeMethod(MdnsBrowser::getInstance().data(), "browseForServiceType",
 | 
			
		||||
							  Qt::QueuedConnection, Q_ARG(QByteArray, MdnsServiceRegister::getServiceType(_activeDeviceType)));
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
LedDevice* LedDeviceHomeAssistant::construct(const QJsonObject& deviceConfig)
 | 
			
		||||
{
 | 
			
		||||
	return new LedDeviceHomeAssistant(deviceConfig);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
LedDeviceHomeAssistant::~LedDeviceHomeAssistant()
 | 
			
		||||
{
 | 
			
		||||
	delete _restApi;
 | 
			
		||||
	_restApi = nullptr;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool LedDeviceHomeAssistant::init(const QJsonObject& deviceConfig)
 | 
			
		||||
{
 | 
			
		||||
	bool isInitOK{ false };
 | 
			
		||||
 | 
			
		||||
	if ( LedDevice::init(deviceConfig) )
 | 
			
		||||
	{
 | 
			
		||||
		// Overwrite non supported/required features
 | 
			
		||||
		if (deviceConfig["rewriteTime"].toInt(0) > 0)
 | 
			
		||||
		{
 | 
			
		||||
			Info(_log, "Home Assistant lights do not require rewrites. Refresh time is ignored.");
 | 
			
		||||
			setRewriteTime(0);
 | 
			
		||||
		}
 | 
			
		||||
		DebugIf(verbose, _log, "deviceConfig: [%s]", QString(QJsonDocument(_devConfig).toJson(QJsonDocument::Compact)).toUtf8().constData());
 | 
			
		||||
 | 
			
		||||
		//Set hostname as per configuration and default port
 | 
			
		||||
		_hostName = deviceConfig[CONFIG_HOST].toString();
 | 
			
		||||
		_apiPort = deviceConfig[CONFIG_PORT].toInt(API_DEFAULT_PORT);
 | 
			
		||||
		_bearerToken = deviceConfig[CONFIG_AUTH_TOKEN].toString();
 | 
			
		||||
 | 
			
		||||
		_isBrightnessOverwrite = _devConfig[CONFIG_BRIGHTNESS_OVERWRITE].toBool(DEFAULT_IS_BRIGHTNESS_OVERWRITE);
 | 
			
		||||
		_isFullBrightnessAtStart = _devConfig[CONFIG_FULL_BRIGHTNESS_AT_START].toBool(DEFAULT_IS_FULL_BRIGHTNESS_AT_START);
 | 
			
		||||
		_brightness = _devConfig[CONFIG_BRIGHTNESS].toInt(BRI_MAX);
 | 
			
		||||
		_switchOffOnBlack       = _devConfig[CONFIG_ON_OFF_BLACK].toBool(DEFAULT_IS_SWITCH_OFF_ON_BLACK);
 | 
			
		||||
		int transitionTimeMs = _devConfig[CONFIG_TRANSITIONTIME].toInt(0);
 | 
			
		||||
		_transitionTime = transitionTimeMs / 1000.0;
 | 
			
		||||
 | 
			
		||||
		Debug(_log, "Hostname/IP       : %s", QSTRING_CSTR(_hostName));
 | 
			
		||||
		Debug(_log, "Port              : %d", _apiPort );
 | 
			
		||||
 | 
			
		||||
		Debug(_log, "Overwrite Brightn.: %s", _isBrightnessOverwrite ? "Yes" : "No" );
 | 
			
		||||
		Debug(_log, "Set Brightness to : %d", _brightness);
 | 
			
		||||
		Debug(_log, "Full Bri. at start: %s", _isFullBrightnessAtStart  ? "Yes" : "No" );
 | 
			
		||||
		Debug(_log, "Off on Black      : %s", _switchOffOnBlack ? "Yes" : "No" );
 | 
			
		||||
		Debug(_log, "Transition Time   : %d ms", transitionTimeMs );
 | 
			
		||||
 | 
			
		||||
		_lightEntityIds = _devConfig[ CONFIG_ENITYIDS ].toVariant().toStringList();
 | 
			
		||||
		int configuredLightsCount = _lightEntityIds.size();
 | 
			
		||||
 | 
			
		||||
		if ( configuredLightsCount == 0 )
 | 
			
		||||
		{
 | 
			
		||||
			this->setInError( "No light entity-ids configured" );
 | 
			
		||||
			isInitOK = false;
 | 
			
		||||
		}
 | 
			
		||||
		else
 | 
			
		||||
		{
 | 
			
		||||
			Debug(_log, "Lights configured : %d", configuredLightsCount );
 | 
			
		||||
			isInitOK = true;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return isInitOK;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool LedDeviceHomeAssistant::initLedsConfiguration()
 | 
			
		||||
{
 | 
			
		||||
	bool isInitOK = false;
 | 
			
		||||
 | 
			
		||||
	//Currently on one light is supported
 | 
			
		||||
	QString lightEntityId = _lightEntityIds[0];
 | 
			
		||||
 | 
			
		||||
	//Get properties for configured light entitiy to check availability
 | 
			
		||||
	_restApi->setPath({ API_STATES, lightEntityId});
 | 
			
		||||
	httpResponse response = _restApi->get();
 | 
			
		||||
	if (response.error())
 | 
			
		||||
	{
 | 
			
		||||
		QString errorReason = QString("%1 get properties failed with error: '%2'").arg(_activeDeviceType,response.getErrorReason());
 | 
			
		||||
		this->setInError(errorReason);
 | 
			
		||||
	}
 | 
			
		||||
	else
 | 
			
		||||
	{
 | 
			
		||||
		QJsonObject propertiesDetails = response.getBody().object();
 | 
			
		||||
		if (propertiesDetails.isEmpty())
 | 
			
		||||
		{
 | 
			
		||||
			QString errorReason = QString("Light [%1] does not exist").arg(lightEntityId);
 | 
			
		||||
			this->setInError(errorReason);
 | 
			
		||||
		}
 | 
			
		||||
		else
 | 
			
		||||
		{
 | 
			
		||||
			if (propertiesDetails.value("state").toString().compare("unavailable") == 0)
 | 
			
		||||
			{
 | 
			
		||||
				Warning(_log, "Light [%s] is currently unavailable", QSTRING_CSTR(lightEntityId));
 | 
			
		||||
			}
 | 
			
		||||
			isInitOK = true;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return isInitOK;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool LedDeviceHomeAssistant::openRestAPI()
 | 
			
		||||
{
 | 
			
		||||
	bool isInitOK{ true };
 | 
			
		||||
 | 
			
		||||
	if (_restApi == nullptr)
 | 
			
		||||
	{
 | 
			
		||||
		if (_apiPort == 0)
 | 
			
		||||
		{
 | 
			
		||||
			_apiPort = API_DEFAULT_PORT;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		_restApi = new ProviderRestApi(_address.toString(), _apiPort);
 | 
			
		||||
		_restApi->setLogger(_log);
 | 
			
		||||
 | 
			
		||||
		_restApi->setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
 | 
			
		||||
		_restApi->setHeader("Authorization", QByteArrayLiteral("Bearer ") + _bearerToken.toUtf8());
 | 
			
		||||
 | 
			
		||||
		//Base-path is api-path
 | 
			
		||||
		_restApi->setBasePath(API_BASE_PATH);
 | 
			
		||||
	}
 | 
			
		||||
	return isInitOK;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int LedDeviceHomeAssistant::open()
 | 
			
		||||
{
 | 
			
		||||
	int retval = -1;
 | 
			
		||||
	_isDeviceReady = false;
 | 
			
		||||
 | 
			
		||||
	if (NetUtils::resolveHostToAddress(_log, _hostName, _address, _apiPort))
 | 
			
		||||
	{
 | 
			
		||||
		if (openRestAPI())
 | 
			
		||||
		{
 | 
			
		||||
			// Read LedDevice configuration and validate against device configuration
 | 
			
		||||
			if (initLedsConfiguration())
 | 
			
		||||
			{
 | 
			
		||||
				// Everything is OK, device is ready
 | 
			
		||||
				_isDeviceReady = true;
 | 
			
		||||
				retval = 0;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		else
 | 
			
		||||
		{
 | 
			
		||||
			_restApi->setHost(_address.toString());
 | 
			
		||||
			_restApi->setPort(_apiPort);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return retval;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
QJsonArray LedDeviceHomeAssistant::discoverSsdp() const
 | 
			
		||||
{
 | 
			
		||||
	QJsonArray deviceList;
 | 
			
		||||
	SSDPDiscover ssdpDiscover;
 | 
			
		||||
	ssdpDiscover.skipDuplicateKeys(true);
 | 
			
		||||
	ssdpDiscover.setSearchFilter(SSDP_FILTER, SSDP_FILTER_HEADER);
 | 
			
		||||
	QString searchTarget = SSDP_ID;
 | 
			
		||||
 | 
			
		||||
	if (ssdpDiscover.discoverServices(searchTarget) > 0)
 | 
			
		||||
	{
 | 
			
		||||
		deviceList = ssdpDiscover.getServicesDiscoveredJson();
 | 
			
		||||
	}
 | 
			
		||||
	return deviceList;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
QJsonObject LedDeviceHomeAssistant::discover(const QJsonObject& /*params*/)
 | 
			
		||||
{
 | 
			
		||||
	QJsonObject devicesDiscovered;
 | 
			
		||||
	devicesDiscovered.insert("ledDeviceType", _activeDeviceType);
 | 
			
		||||
 | 
			
		||||
	QJsonArray deviceList;
 | 
			
		||||
 | 
			
		||||
#ifdef ENABLE_MDNS
 | 
			
		||||
	QString discoveryMethod("mDNS");
 | 
			
		||||
	deviceList = MdnsBrowser::getInstance().data()->getServicesDiscoveredJson(
 | 
			
		||||
					 MdnsServiceRegister::getServiceType(_activeDeviceType),
 | 
			
		||||
					 MdnsServiceRegister::getServiceNameFilter(_activeDeviceType),
 | 
			
		||||
					 DEFAULT_DISCOVER_TIMEOUT
 | 
			
		||||
					 );
 | 
			
		||||
#else
 | 
			
		||||
	QString discoveryMethod("ssdp");
 | 
			
		||||
	deviceList = discoverSsdp();
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
	devicesDiscovered.insert("discoveryMethod", discoveryMethod);
 | 
			
		||||
	devicesDiscovered.insert("devices", deviceList);
 | 
			
		||||
 | 
			
		||||
	DebugIf(verbose, _log, "devicesDiscovered: [%s]", QString(QJsonDocument(devicesDiscovered).toJson(QJsonDocument::Compact)).toUtf8().constData());
 | 
			
		||||
 | 
			
		||||
	return devicesDiscovered;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
QJsonObject LedDeviceHomeAssistant::getProperties(const QJsonObject& params)
 | 
			
		||||
{
 | 
			
		||||
	DebugIf(verbose, _log, "params: [%s]", QString(QJsonDocument(params).toJson(QJsonDocument::Compact)).toUtf8().constData());
 | 
			
		||||
	QJsonObject properties;
 | 
			
		||||
 | 
			
		||||
	_hostName = params[CONFIG_HOST].toString("");
 | 
			
		||||
	_apiPort = API_DEFAULT_PORT;
 | 
			
		||||
	_bearerToken = params[CONFIG_AUTH_TOKEN].toString("");
 | 
			
		||||
 | 
			
		||||
	Info(_log, "Get properties for %s, hostname (%s)", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(_hostName));
 | 
			
		||||
 | 
			
		||||
	if (NetUtils::resolveHostToAddress(_log, _hostName, _address, _apiPort))
 | 
			
		||||
	{
 | 
			
		||||
		if (openRestAPI())
 | 
			
		||||
		{
 | 
			
		||||
			QString filter = params["filter"].toString("");
 | 
			
		||||
			_restApi->setPath(filter);
 | 
			
		||||
 | 
			
		||||
			// Perform request
 | 
			
		||||
			httpResponse response = _restApi->get();
 | 
			
		||||
			if (response.error())
 | 
			
		||||
			{
 | 
			
		||||
				Warning(_log, "%s get properties failed with error: '%s'", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(response.getErrorReason()));
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			QJsonObject propertiesDetails;
 | 
			
		||||
			const QJsonDocument jsonDoc = response.getBody();
 | 
			
		||||
			if (jsonDoc.isArray()) {
 | 
			
		||||
				const QJsonArray jsonArray = jsonDoc.array();
 | 
			
		||||
				QVector<QJsonValue> filteredVector;
 | 
			
		||||
 | 
			
		||||
				// Iterate over the array and filter objects with entity_id starting with "light."
 | 
			
		||||
				for (const QJsonValue &value : jsonArray)
 | 
			
		||||
				{
 | 
			
		||||
					QJsonObject obj = value.toObject();
 | 
			
		||||
					QString entityId = obj[ENTITY_ID].toString();
 | 
			
		||||
 | 
			
		||||
					if (entityId.startsWith("light."))
 | 
			
		||||
					{
 | 
			
		||||
						filteredVector.append(obj);
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				// Sort the filtered vector by "friendly_name" in ascending order
 | 
			
		||||
				std::sort(filteredVector.begin(), filteredVector.end(), [](const QJsonValue &a, const QJsonValue &b) {
 | 
			
		||||
					QString nameA = a.toObject()["attributes"].toObject()["friendly_name"].toString();
 | 
			
		||||
					QString nameB = b.toObject()["attributes"].toObject()["friendly_name"].toString();
 | 
			
		||||
					return nameA < nameB;  // Ascending order
 | 
			
		||||
				});
 | 
			
		||||
				// Convert the sorted vector back to a QJsonArray
 | 
			
		||||
				QJsonArray sortedArray;
 | 
			
		||||
				for (const QJsonValue &value : filteredVector) {
 | 
			
		||||
					sortedArray.append(value);
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				propertiesDetails.insert("lightEntities", sortedArray);
 | 
			
		||||
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if (!propertiesDetails.isEmpty())
 | 
			
		||||
			{
 | 
			
		||||
				propertiesDetails.insert("ledCount", 1);
 | 
			
		||||
			}
 | 
			
		||||
			properties.insert("properties", propertiesDetails);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		DebugIf(verbose, _log, "properties: [%s]", QString(QJsonDocument(properties).toJson(QJsonDocument::Compact)).toUtf8().constData());
 | 
			
		||||
	}
 | 
			
		||||
	return properties;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void LedDeviceHomeAssistant::identify(const QJsonObject& params)
 | 
			
		||||
{
 | 
			
		||||
	DebugIf(verbose, _log, "params: [%s]", QString(QJsonDocument(params).toJson(QJsonDocument::Compact)).toUtf8().constData());
 | 
			
		||||
 | 
			
		||||
	_hostName = params[CONFIG_HOST].toString("");
 | 
			
		||||
	_apiPort = API_DEFAULT_PORT;
 | 
			
		||||
	_bearerToken = params[CONFIG_AUTH_TOKEN].toString("");
 | 
			
		||||
 | 
			
		||||
	Info(_log, "Identify %s, hostname (%s)", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(_hostName));
 | 
			
		||||
 | 
			
		||||
	if (NetUtils::resolveHostToAddress(_log, _hostName, _address, _apiPort))
 | 
			
		||||
	{
 | 
			
		||||
		if (openRestAPI())
 | 
			
		||||
		{
 | 
			
		||||
			QJsonArray lightEntityIds = params[ ENTITY_ID ].toArray();
 | 
			
		||||
 | 
			
		||||
			_restApi->setPath(API_LIGHT_TURN_ON);
 | 
			
		||||
			QJsonObject serviceAttributes{{ENTITY_ID, lightEntityIds}};
 | 
			
		||||
			serviceAttributes.insert(FLASH, "short");
 | 
			
		||||
 | 
			
		||||
			httpResponse response = _restApi->post(serviceAttributes);
 | 
			
		||||
			if (response.error())
 | 
			
		||||
			{
 | 
			
		||||
				Warning(_log, "%s identification failed with error: '%s'", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(response.getErrorReason()));
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool LedDeviceHomeAssistant::powerOn()
 | 
			
		||||
{
 | 
			
		||||
	bool isOn = false;
 | 
			
		||||
	if (_isDeviceReady)
 | 
			
		||||
	{
 | 
			
		||||
		_restApi->setPath(API_LIGHT_TURN_ON);
 | 
			
		||||
		QJsonObject serviceAttributes {{ENTITY_ID, QJsonArray::fromStringList(_lightEntityIds)}};
 | 
			
		||||
 | 
			
		||||
		if (_isFullBrightnessAtStart)
 | 
			
		||||
		{
 | 
			
		||||
			serviceAttributes.insert(BRIGHTNESS, BRI_MAX);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		httpResponse response = _restApi->post(serviceAttributes);
 | 
			
		||||
		if (response.error())
 | 
			
		||||
		{
 | 
			
		||||
			QString errorReason = QString("Power-on request failed with error: '%1'").arg(response.getErrorReason());
 | 
			
		||||
			this->setInError(errorReason);
 | 
			
		||||
			isOn = false;
 | 
			
		||||
		}
 | 
			
		||||
		else {
 | 
			
		||||
			isOn = true;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return isOn;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool LedDeviceHomeAssistant::powerOff()
 | 
			
		||||
{
 | 
			
		||||
	bool isOff = true;
 | 
			
		||||
	if (_isDeviceReady)
 | 
			
		||||
	{
 | 
			
		||||
		_restApi->setPath(API_LIGHT_TURN_OFF);
 | 
			
		||||
		QJsonObject serviceAttributes {{ENTITY_ID, QJsonArray::fromStringList(_lightEntityIds)}};
 | 
			
		||||
		httpResponse response = _restApi->post(serviceAttributes);
 | 
			
		||||
		if (response.error())
 | 
			
		||||
		{
 | 
			
		||||
			QString errorReason = QString("Power-off request failed with error: '%1'").arg(response.getErrorReason());
 | 
			
		||||
			this->setInError(errorReason);
 | 
			
		||||
			isOff = false;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return isOff;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int LedDeviceHomeAssistant::write(const std::vector<ColorRgb>& ledValues)
 | 
			
		||||
{
 | 
			
		||||
	int retVal = 0;
 | 
			
		||||
 | 
			
		||||
	QJsonObject serviceAttributes {{ENTITY_ID, QJsonArray::fromStringList(_lightEntityIds)}};
 | 
			
		||||
	ColorRgb ledValue = ledValues.at(0);
 | 
			
		||||
 | 
			
		||||
	if (_switchOffOnBlack && ledValue == ColorRgb::BLACK)
 | 
			
		||||
	{
 | 
			
		||||
		_restApi->setPath(API_LIGHT_TURN_OFF);
 | 
			
		||||
	}
 | 
			
		||||
	else
 | 
			
		||||
	{
 | 
			
		||||
		//    http://hostname:port/api/services/light/turn_on
 | 
			
		||||
		//    {
 | 
			
		||||
		//      "entity_id": [ entity-IDs ],
 | 
			
		||||
		//      "rgb_color": [R,G,B]
 | 
			
		||||
		//    }
 | 
			
		||||
 | 
			
		||||
		_restApi->setPath(API_LIGHT_TURN_ON);
 | 
			
		||||
		QJsonArray rgbColor {ledValue.red, ledValue.green, ledValue.blue};
 | 
			
		||||
		serviceAttributes.insert(RGB_COLOR, rgbColor);
 | 
			
		||||
 | 
			
		||||
		if (_isBrightnessOverwrite)
 | 
			
		||||
		{
 | 
			
		||||
			serviceAttributes.insert(BRIGHTNESS, _brightness);
 | 
			
		||||
		}
 | 
			
		||||
		if (_transitionTime > 0)
 | 
			
		||||
		{
 | 
			
		||||
			// Transition time in seconds
 | 
			
		||||
			serviceAttributes.insert(TRANSITION, _transitionTime);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	httpResponse response = _restApi->post(serviceAttributes);
 | 
			
		||||
	if (response.error())
 | 
			
		||||
	{
 | 
			
		||||
		Warning(_log,"Updating lights failed with error: '%s'", QSTRING_CSTR(response.getErrorReason()) );
 | 
			
		||||
		retVal = -1;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return retVal;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										181
									
								
								libsrc/leddevice/dev_net/LedDeviceHomeAssistant.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										181
									
								
								libsrc/leddevice/dev_net/LedDeviceHomeAssistant.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,181 @@
 | 
			
		||||
#ifndef LEDEVICEHOMEASSISTANT_H
 | 
			
		||||
#define LEDEVICEHOMEASSISTANT_H
 | 
			
		||||
 | 
			
		||||
// LedDevice includes
 | 
			
		||||
#include <leddevice/LedDevice.h>
 | 
			
		||||
#include "ProviderRestApi.h"
 | 
			
		||||
 | 
			
		||||
// Qt includes
 | 
			
		||||
#include <QString>
 | 
			
		||||
#include <QHostAddress>
 | 
			
		||||
#include <QNetworkAccessManager>
 | 
			
		||||
 | 
			
		||||
///
 | 
			
		||||
/// Implementation of the LedDevice interface for sending to
 | 
			
		||||
/// lights made available via the Home Assistant platform.
 | 
			
		||||
///
 | 
			
		||||
class LedDeviceHomeAssistant : LedDevice
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
	///
 | 
			
		||||
	/// @brief Constructs LED-device for Home Assistant Lights
 | 
			
		||||
	///
 | 
			
		||||
	/// following code shows all configuration options
 | 
			
		||||
	/// @code
 | 
			
		||||
	/// "device" :
 | 
			
		||||
	/// {
 | 
			
		||||
	///     "type" : "homeassistant"
 | 
			
		||||
	///     "host" : "hostname or IP",
 | 
			
		||||
	///     "port"  : port
 | 
			
		||||
	///     "token": "bearer token",
 | 
			
		||||
	/// },
 | 
			
		||||
	///@endcode
 | 
			
		||||
	///
 | 
			
		||||
	/// @param deviceConfig Device's configuration as JSON-Object
 | 
			
		||||
	///
 | 
			
		||||
	explicit LedDeviceHomeAssistant(const QJsonObject& deviceConfig);
 | 
			
		||||
 | 
			
		||||
	///
 | 
			
		||||
	/// @brief Destructor of the LED-device
 | 
			
		||||
	///
 | 
			
		||||
	~LedDeviceHomeAssistant() override;
 | 
			
		||||
 | 
			
		||||
	///
 | 
			
		||||
	/// @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 Home Assistant lights 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 the Home Assistant light's resource properties
 | 
			
		||||
	///
 | 
			
		||||
	/// Following parameters are required
 | 
			
		||||
	/// @code
 | 
			
		||||
	/// {
 | 
			
		||||
	///     "host"  : "hostname or IP",
 | 
			
		||||
	///     "port"  : port
 | 
			
		||||
	///     "token" : "bearer token",
 | 
			
		||||
	///     "filter": "resource to query", root "/" is used, if empty
 | 
			
		||||
	/// }
 | 
			
		||||
	///@endcode
 | 
			
		||||
	///
 | 
			
		||||
	/// @param[in] params Parameters to query device
 | 
			
		||||
	/// @return A JSON structure holding the device's properties
 | 
			
		||||
	///
 | 
			
		||||
	QJsonObject getProperties(const QJsonObject& params) override;
 | 
			
		||||
 | 
			
		||||
	///
 | 
			
		||||
	/// @brief Send an update to the Nanoleaf device to identify it.
 | 
			
		||||
	///
 | 
			
		||||
	/// Following parameters are required
 | 
			
		||||
	/// @code
 | 
			
		||||
	/// {
 | 
			
		||||
	///     "host"  : "hostname or IP",
 | 
			
		||||
	///     "port"  : port
 | 
			
		||||
	///     "token" : "bearer token",
 | 
			
		||||
	///     "entity_id": array of lightIds
 | 
			
		||||
	/// }
 | 
			
		||||
	///@endcode
 | 
			
		||||
	///
 | 
			
		||||
	/// @param[in] params Parameters to address device
 | 
			
		||||
	///
 | 
			
		||||
	void identify(const QJsonObject& params) override;
 | 
			
		||||
 | 
			
		||||
protected:
 | 
			
		||||
 | 
			
		||||
	///
 | 
			
		||||
	/// @brief Initialise the Home Assistant light's configuration and network address details
 | 
			
		||||
	///
 | 
			
		||||
	/// @param[in] deviceConfig the JSON device configuration
 | 
			
		||||
	/// @return True, if success
 | 
			
		||||
	///
 | 
			
		||||
	bool init(const QJsonObject& deviceConfig) override;
 | 
			
		||||
 | 
			
		||||
	///
 | 
			
		||||
	/// @brief Opens the output device.
 | 
			
		||||
	///
 | 
			
		||||
	/// @return Zero on success (i.e. device is ready), else negative
 | 
			
		||||
	///
 | 
			
		||||
	int open() override;
 | 
			
		||||
 | 
			
		||||
	///
 | 
			
		||||
	/// @brief Writes the RGB-Color values to the Home Assistant light.
 | 
			
		||||
	///
 | 
			
		||||
	/// @param[in] ledValues The RGB-color
 | 
			
		||||
	/// @return Zero on success, else negative
 | 
			
		||||
	//////
 | 
			
		||||
	int write(const std::vector<ColorRgb>& ledValues) override;
 | 
			
		||||
 | 
			
		||||
	///
 | 
			
		||||
	/// @brief Power-/turn on the Home Assistant light.
 | 
			
		||||
	///
 | 
			
		||||
	/// @brief Store the device's original state.
 | 
			
		||||
	///
 | 
			
		||||
	bool powerOn() override;
 | 
			
		||||
 | 
			
		||||
	///
 | 
			
		||||
	/// @brief Power-/turn off the Home Assistant light.
 | 
			
		||||
	///
 | 
			
		||||
	/// @return True if success
 | 
			
		||||
	///
 | 
			
		||||
	bool powerOff() override;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
 | 
			
		||||
	///
 | 
			
		||||
	/// @brief Initialise the access to the REST-API wrapper
 | 
			
		||||
	///
 | 
			
		||||
	/// @return True, if success
 | 
			
		||||
	///
 | 
			
		||||
	bool openRestAPI();
 | 
			
		||||
 | 
			
		||||
	///
 | 
			
		||||
	/// @brief Get Nanoleaf device details and configuration
 | 
			
		||||
	///
 | 
			
		||||
	/// @return True, if Nanoleaf device capabilities fit configuration
 | 
			
		||||
	///
 | 
			
		||||
	bool initLedsConfiguration();
 | 
			
		||||
 | 
			
		||||
	///
 | 
			
		||||
	/// @brief Discover Home Assistant lights available (for configuration).
 | 
			
		||||
	///
 | 
			
		||||
	/// @return A JSON structure holding a list of devices found
 | 
			
		||||
	///
 | 
			
		||||
	QJsonArray discoverSsdp() const;
 | 
			
		||||
 | 
			
		||||
	// ///
 | 
			
		||||
	// /// @brief Get number of panels that can be used as LEds.
 | 
			
		||||
	// ///
 | 
			
		||||
	// /// @return Number of usable LED panels
 | 
			
		||||
	// ///
 | 
			
		||||
	// int getHwLedCount(const QJsonObject& jsonLayout) const;
 | 
			
		||||
 | 
			
		||||
	QString      _hostName;
 | 
			
		||||
	QHostAddress _address;
 | 
			
		||||
	ProviderRestApi* _restApi;
 | 
			
		||||
	int	_apiPort;
 | 
			
		||||
	QString _bearerToken;
 | 
			
		||||
 | 
			
		||||
	/// List of the HA light entity_ids.
 | 
			
		||||
	QStringList _lightEntityIds;
 | 
			
		||||
 | 
			
		||||
	bool _isBrightnessOverwrite;
 | 
			
		||||
	bool _isFullBrightnessAtStart;
 | 
			
		||||
	int _brightness;
 | 
			
		||||
	bool _switchOffOnBlack;
 | 
			
		||||
	/// Transition time in seconds
 | 
			
		||||
	double _transitionTime;
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif // LEDEVICEHOMEASSISTANT_H
 | 
			
		||||
@@ -31,7 +31,7 @@ const char CONFIG_TRANSITIONTIME[] = "transitiontime";
 | 
			
		||||
const char CONFIG_BLACK_LIGHTS_TIMEOUT[] = "blackLightsTimeout";
 | 
			
		||||
const char CONFIG_ON_OFF_BLACK[] = "switchOffOnBlack";
 | 
			
		||||
const char CONFIG_RESTORE_STATE[] = "restoreOriginalState";
 | 
			
		||||
const char CONFIG_lightIdS[] = "lightIds";
 | 
			
		||||
const char CONFIG_LIGHTIDS[] = "lightIds";
 | 
			
		||||
const char CONFIG_USE_HUE_API_V2[] = "useAPIv2";
 | 
			
		||||
const char CONFIG_USE_HUE_ENTERTAINMENT_API[] = "useEntertainmentAPI";
 | 
			
		||||
const char CONFIG_groupId[] = "groupId";
 | 
			
		||||
@@ -1849,7 +1849,7 @@ bool LedDevicePhilipsHue::setLights()
 | 
			
		||||
			_useEntertainmentAPI = false;
 | 
			
		||||
			Error(_log, "Group-ID [%s] is not usable - Entertainment API usage was disabled!", QSTRING_CSTR(_groupId)  );
 | 
			
		||||
		}
 | 
			
		||||
		lights = _devConfig[ CONFIG_lightIdS ].toVariant().toStringList();
 | 
			
		||||
		lights = _devConfig[ CONFIG_LIGHTIDS ].toVariant().toStringList();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_lightIds = lights;
 | 
			
		||||
 
 | 
			
		||||
@@ -23,7 +23,7 @@ namespace {
 | 
			
		||||
	const char CONFIG_RAZER_DEVICE_TYPE[] = "subType";
 | 
			
		||||
	const char CONFIG_SINGLE_COLOR[] = "singleColor";
 | 
			
		||||
 | 
			
		||||
	// WLED JSON-API elements
 | 
			
		||||
	// API elements
 | 
			
		||||
	const char API_DEFAULT_HOST[] = "localhost";
 | 
			
		||||
	const int API_DEFAULT_PORT = 54235;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -58,6 +58,11 @@ bool LedDeviceAdalight::init(const QJsonObject &deviceConfig)
 | 
			
		||||
		case Adalight::ADA:
 | 
			
		||||
			Debug( _log, "Adalight driver uses standard Adalight protocol");
 | 
			
		||||
			break;
 | 
			
		||||
 | 
			
		||||
		case Adalight::SKYDIMO:
 | 
			
		||||
			Debug( _log, "Adalight driver uses Skydimo protocol");
 | 
			
		||||
			break;
 | 
			
		||||
 | 
			
		||||
		default:
 | 
			
		||||
			Error( _log, "Adalight driver - unsupported protocol");
 | 
			
		||||
			return false;
 | 
			
		||||
@@ -71,10 +76,6 @@ bool LedDeviceAdalight::init(const QJsonObject &deviceConfig)
 | 
			
		||||
 | 
			
		||||
void LedDeviceAdalight::prepareHeader()
 | 
			
		||||
{
 | 
			
		||||
	// create ledBuffer
 | 
			
		||||
	uint totalLedCount = _ledCount;
 | 
			
		||||
	_bufferLength = static_cast<qint64>(HEADER_SIZE + _ledRGBCount);
 | 
			
		||||
 | 
			
		||||
	switch (_streamProtocol) {
 | 
			
		||||
	case Adalight::LBAPA:
 | 
			
		||||
	{
 | 
			
		||||
@@ -82,7 +83,6 @@ void LedDeviceAdalight::prepareHeader()
 | 
			
		||||
		const unsigned int bytesPerRGBLed = 4;
 | 
			
		||||
		const unsigned int endFrameSize = qMax<unsigned int>(((_ledCount + 15) / 16), bytesPerRGBLed);
 | 
			
		||||
		_bufferLength = HEADER_SIZE + (_ledCount * bytesPerRGBLed) + startFrameSize + endFrameSize;
 | 
			
		||||
 | 
			
		||||
		_ledBuffer.resize(static_cast<size_t>(_bufferLength), 0x00);
 | 
			
		||||
 | 
			
		||||
		// init constant data values
 | 
			
		||||
@@ -91,39 +91,47 @@ void LedDeviceAdalight::prepareHeader()
 | 
			
		||||
			_ledBuffer[iLed*4+HEADER_SIZE] = 0xFF;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
		break;
 | 
			
		||||
 | 
			
		||||
	break;
 | 
			
		||||
	case Adalight::SKYDIMO:
 | 
			
		||||
	{
 | 
			
		||||
		_bufferLength = static_cast<qint64>(HEADER_SIZE + _ledRGBCount);
 | 
			
		||||
		_ledBuffer.resize(static_cast<size_t>(_bufferLength), 0x00);
 | 
			
		||||
		_ledBuffer[0] = 'A';
 | 
			
		||||
		_ledBuffer[1] = 'd';
 | 
			
		||||
		_ledBuffer[2] = 'a';
 | 
			
		||||
		_ledBuffer[3] = 0;
 | 
			
		||||
		_ledBuffer[4] = 0;
 | 
			
		||||
		_ledBuffer[5] = static_cast<quint8>(_ledCount);
 | 
			
		||||
	}
 | 
			
		||||
	break;
 | 
			
		||||
	case Adalight::AWA:
 | 
			
		||||
		_bufferLength += 8;
 | 
			
		||||
		[[fallthrough]];
 | 
			
		||||
	{
 | 
			
		||||
		_bufferLength = static_cast<qint64>(HEADER_SIZE + _ledRGBCount + 8);
 | 
			
		||||
		_ledBuffer.resize(static_cast<size_t>(_bufferLength), 0x00);
 | 
			
		||||
		_ledBuffer[0] = 'A';
 | 
			
		||||
		_ledBuffer[1] = 'w';
 | 
			
		||||
		_ledBuffer[2] = _white_channel_calibration ? 'A' : 'a';
 | 
			
		||||
		qToBigEndian<quint16>(static_cast<quint16>(_ledCount-1), &_ledBuffer[3]);
 | 
			
		||||
		_ledBuffer[5] = _ledBuffer[3] ^ _ledBuffer[4] ^ 0x55; // Checksum
 | 
			
		||||
	}
 | 
			
		||||
	break;
 | 
			
		||||
	case Adalight::ADA:
 | 
			
		||||
		[[fallthrough]];
 | 
			
		||||
	default:
 | 
			
		||||
		totalLedCount -= 1;
 | 
			
		||||
		_bufferLength = static_cast<qint64>(HEADER_SIZE + _ledRGBCount);
 | 
			
		||||
		_ledBuffer.resize(static_cast<size_t>(_bufferLength), 0x00);
 | 
			
		||||
		break;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_ledBuffer[0] = 'A';
 | 
			
		||||
	if (_streamProtocol == Adalight::AWA )
 | 
			
		||||
	{
 | 
			
		||||
		_ledBuffer[1] = 'w';
 | 
			
		||||
		_ledBuffer[2] = _white_channel_calibration ? 'A' : 'a';
 | 
			
		||||
	}
 | 
			
		||||
	else
 | 
			
		||||
	{
 | 
			
		||||
		_ledBuffer[0] = 'A';
 | 
			
		||||
		_ledBuffer[1] = 'd';
 | 
			
		||||
		_ledBuffer[2] = 'a';
 | 
			
		||||
		qToBigEndian<quint16>(static_cast<quint16>(_ledCount-1), &_ledBuffer[3]);
 | 
			
		||||
		_ledBuffer[5] = _ledBuffer[3] ^ _ledBuffer[4] ^ 0x55; // Checksum
 | 
			
		||||
		break;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	qToBigEndian<quint16>(static_cast<quint16>(totalLedCount), &_ledBuffer[3]);
 | 
			
		||||
	_ledBuffer[5] = _ledBuffer[3] ^ _ledBuffer[4] ^ 0x55; // Checksum
 | 
			
		||||
 | 
			
		||||
	Debug( _log, "Adalight header for %d leds (size: %d): %c%c%c 0x%02x 0x%02x 0x%02x", _ledCount, _ledBuffer.size(),
 | 
			
		||||
		   _ledBuffer[0], _ledBuffer[1], _ledBuffer[2], _ledBuffer[3], _ledBuffer[4], _ledBuffer[5] );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
int LedDeviceAdalight::write(const std::vector<ColorRgb> & ledValues)
 | 
			
		||||
{
 | 
			
		||||
	if (_ledCount != ledValues.size())
 | 
			
		||||
 
 | 
			
		||||
@@ -10,7 +10,8 @@ typedef enum ProtocolType
 | 
			
		||||
{
 | 
			
		||||
	ADA = 0,
 | 
			
		||||
	LBAPA,
 | 
			
		||||
	AWA
 | 
			
		||||
	AWA,
 | 
			
		||||
	SKYDIMO
 | 
			
		||||
} PROTOCOLTYPE;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -11,10 +11,10 @@
 | 
			
		||||
		"streamProtocol": {
 | 
			
		||||
		  "type": "string",
 | 
			
		||||
		  "title": "edt_dev_spec_stream_protocol_title",
 | 
			
		||||
		  "enum": [ "0", "1", "2" ],
 | 
			
		||||
		  "enum": [ "0", "1", "2", "3" ],
 | 
			
		||||
		  "default": "0",
 | 
			
		||||
		  "options": {
 | 
			
		||||
		    "enum_titles": [ "edt_dev_spec_ada_mode_title", "edt_dev_spec_LBap102Mode_title","edt_dev_spec_awa_mode_title" ]
 | 
			
		||||
		    "enum_titles": [ "edt_dev_spec_ada_mode_title", "edt_dev_spec_LBap102Mode_title","edt_dev_spec_awa_mode_title", "edt_dev_spec_skydimo_mode_title" ]
 | 
			
		||||
		  },
 | 
			
		||||
		  "propertyOrder": 2
 | 
			
		||||
		},
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										27
									
								
								libsrc/leddevice/schemas/schema-apa102_ftdi.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								libsrc/leddevice/schemas/schema-apa102_ftdi.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,27 @@
 | 
			
		||||
{
 | 
			
		||||
	"type": "object",
 | 
			
		||||
	"required": true,
 | 
			
		||||
	"properties": {
 | 
			
		||||
		"output": {
 | 
			
		||||
			"type": "string",
 | 
			
		||||
			"title":"edt_dev_spec_outputPath_title",
 | 
			
		||||
			"propertyOrder": 1
 | 
			
		||||
		},
 | 
			
		||||
		"rate": {
 | 
			
		||||
			"type": "integer",
 | 
			
		||||
			"title": "edt_dev_spec_baudrate_title",
 | 
			
		||||
			"default": 5000000,
 | 
			
		||||
			"propertyOrder": 2
 | 
			
		||||
		},
 | 
			
		||||
		"brightnessControlMaxLevel": {
 | 
			
		||||
			"type": "integer",
 | 
			
		||||
			"title": "edt_conf_color_brightness_title",
 | 
			
		||||
			"default": 31,
 | 
			
		||||
			"minimum": 1,
 | 
			
		||||
			"maximum": 31,
 | 
			
		||||
			"propertyOrder": 3
 | 
			
		||||
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
	"additionalProperties": true
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										135
									
								
								libsrc/leddevice/schemas/schema-homeassistant.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										135
									
								
								libsrc/leddevice/schemas/schema-homeassistant.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,135 @@
 | 
			
		||||
{
 | 
			
		||||
  "type": "object",
 | 
			
		||||
  "required": true,
 | 
			
		||||
  "properties": {
 | 
			
		||||
    "hostList": {
 | 
			
		||||
      "type": "string",
 | 
			
		||||
      "title": "edt_dev_spec_devices_discovered_title",
 | 
			
		||||
      "enum": [ "NONE" ],
 | 
			
		||||
      "options": {
 | 
			
		||||
        "enum_titles": [ "edt_dev_spec_devices_discovery_inprogress" ],
 | 
			
		||||
        "infoText": "edt_dev_spec_devices_discovered_title_info"
 | 
			
		||||
      },
 | 
			
		||||
      "required": true,
 | 
			
		||||
      "propertyOrder": 1
 | 
			
		||||
    },
 | 
			
		||||
    "host": {
 | 
			
		||||
      "type": "string",
 | 
			
		||||
      "format": "hostname_or_ip",
 | 
			
		||||
      "title": "edt_dev_spec_targetIpHost_title",
 | 
			
		||||
      "options": {
 | 
			
		||||
        "infoText": "edt_dev_spec_targetIpHost_title_info"
 | 
			
		||||
      },
 | 
			
		||||
      "required": true,
 | 
			
		||||
      "propertyOrder": 2
 | 
			
		||||
    },
 | 
			
		||||
    "port": {
 | 
			
		||||
      "type": "integer",
 | 
			
		||||
      "title": "edt_dev_spec_port_title",
 | 
			
		||||
      "default": 8123,
 | 
			
		||||
      "minimum": 0,
 | 
			
		||||
      "maximum": 65535,
 | 
			
		||||
      "access": "expert",
 | 
			
		||||
      "propertyOrder": 3
 | 
			
		||||
    },
 | 
			
		||||
    "token": {
 | 
			
		||||
      "type": "string",
 | 
			
		||||
      "title": "edt_dev_auth_key_title",
 | 
			
		||||
      "options": {
 | 
			
		||||
        "infoText": "edt_dev_auth_key_title_info"
 | 
			
		||||
      },
 | 
			
		||||
      "propertyOrder": 4
 | 
			
		||||
    },
 | 
			
		||||
    "restoreOriginalState": {
 | 
			
		||||
      "type": "boolean",
 | 
			
		||||
      "format": "checkbox",
 | 
			
		||||
      "title": "edt_dev_spec_restoreOriginalState_title",
 | 
			
		||||
      "default": true,
 | 
			
		||||
      "required": true,
 | 
			
		||||
      "options": {
 | 
			
		||||
        "hidden": true,
 | 
			
		||||
        "infoText": "edt_dev_spec_restoreOriginalState_title_info"
 | 
			
		||||
      },
 | 
			
		||||
      "propertyOrder": 5
 | 
			
		||||
    },
 | 
			
		||||
    "overwriteBrightness": {
 | 
			
		||||
      "type": "boolean",
 | 
			
		||||
      "format": "checkbox",
 | 
			
		||||
      "title": "edt_dev_spec_brightnessOverwrite_title",
 | 
			
		||||
      "default": true,
 | 
			
		||||
      "required": true,
 | 
			
		||||
      "access": "advanced",
 | 
			
		||||
      "propertyOrder": 5
 | 
			
		||||
    },
 | 
			
		||||
    "brightness": {
 | 
			
		||||
      "type": "integer",
 | 
			
		||||
      "title": "edt_dev_spec_brightness_title",
 | 
			
		||||
      "default": 255,
 | 
			
		||||
      "minimum": 1,
 | 
			
		||||
      "maximum": 255,
 | 
			
		||||
      "options": {
 | 
			
		||||
        "dependencies": {
 | 
			
		||||
          "overwriteBrightness": true
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "access": "advanced",
 | 
			
		||||
      "propertyOrder": 6
 | 
			
		||||
    },
 | 
			
		||||
    "fullBrightnessAtStart": {
 | 
			
		||||
      "type": "boolean",
 | 
			
		||||
      "format": "checkbox",
 | 
			
		||||
      "title": "edt_dev_spec_fullBrightnessAtStart_title",
 | 
			
		||||
      "default": true,
 | 
			
		||||
      "required": true,
 | 
			
		||||
      "access": "advanced",
 | 
			
		||||
      "propertyOrder": 7
 | 
			
		||||
    },
 | 
			
		||||
    "switchOffOnBlack": {
 | 
			
		||||
      "type": "boolean",
 | 
			
		||||
      "format": "checkbox",
 | 
			
		||||
      "title": "edt_dev_spec_switchOffOnBlack_title",
 | 
			
		||||
      "default": false,
 | 
			
		||||
      "access": "advanced",
 | 
			
		||||
      "propertyOrder": 8
 | 
			
		||||
    },
 | 
			
		||||
    "transitionTime": {
 | 
			
		||||
      "type": "integer",
 | 
			
		||||
      "title": "edt_dev_spec_transistionTime_title",
 | 
			
		||||
      "default": 0,
 | 
			
		||||
      "append": "ms",
 | 
			
		||||
      "minimum": 0,
 | 
			
		||||
      "maximum": 2000,
 | 
			
		||||
      "required": false,
 | 
			
		||||
      "access": "advanced",
 | 
			
		||||
      "propertyOrder": 9
 | 
			
		||||
    },
 | 
			
		||||
    "entityIds": {
 | 
			
		||||
      "title": "edt_dev_spec_lightid_title",
 | 
			
		||||
      "type": "array",
 | 
			
		||||
      "required": true,
 | 
			
		||||
      "format": "select",
 | 
			
		||||
      "options": {
 | 
			
		||||
        "hidden": true
 | 
			
		||||
      },
 | 
			
		||||
      "items": {
 | 
			
		||||
        "type": "string",
 | 
			
		||||
        "title": "edt_dev_spec_lights_itemtitle"
 | 
			
		||||
      },
 | 
			
		||||
      "propertyOrder": 10
 | 
			
		||||
    },
 | 
			
		||||
    "latchTime": {
 | 
			
		||||
      "type": "integer",
 | 
			
		||||
      "title": "edt_dev_spec_latchtime_title",
 | 
			
		||||
      "default": 250,
 | 
			
		||||
      "append": "edt_append_ms",
 | 
			
		||||
      "minimum": 100,
 | 
			
		||||
      "maximum": 2000,
 | 
			
		||||
      "access": "expert",
 | 
			
		||||
      "options": {
 | 
			
		||||
        "infoText": "edt_dev_spec_latchtime_title_info"
 | 
			
		||||
      },
 | 
			
		||||
      "propertyOrder": 11
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  "additionalProperties": true
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										60
									
								
								libsrc/leddevice/schemas/schema-sk6812_ftdi.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								libsrc/leddevice/schemas/schema-sk6812_ftdi.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,60 @@
 | 
			
		||||
{
 | 
			
		||||
	"type": "object",
 | 
			
		||||
	"required": true,
 | 
			
		||||
	"properties": {
 | 
			
		||||
		"output": {
 | 
			
		||||
			"type": "string",
 | 
			
		||||
			"title": "edt_dev_spec_outputPath_title",
 | 
			
		||||
			"required": true,
 | 
			
		||||
			"propertyOrder": 1
 | 
			
		||||
		},
 | 
			
		||||
		"rate": {
 | 
			
		||||
			"type": "integer",
 | 
			
		||||
			"step": 100000,
 | 
			
		||||
			"title": "edt_dev_spec_baudrate_title",
 | 
			
		||||
			"default": 3200000,
 | 
			
		||||
			"minimum": 2050000,
 | 
			
		||||
			"maximum": 3750000,
 | 
			
		||||
			"propertyOrder": 2
 | 
			
		||||
		},
 | 
			
		||||
		"brightnessControlMaxLevel": {
 | 
			
		||||
			"type": "integer",
 | 
			
		||||
			"title": "edt_conf_color_brightness_title",
 | 
			
		||||
			"default": 255,
 | 
			
		||||
			"minimum": 1,
 | 
			
		||||
			"maximum": 255,
 | 
			
		||||
			"propertyOrder": 3
 | 
			
		||||
		},
 | 
			
		||||
		"whiteAlgorithm": {
 | 
			
		||||
			"type": "string",
 | 
			
		||||
			"title": "edt_dev_spec_whiteLedAlgor_title",
 | 
			
		||||
			"enum": [
 | 
			
		||||
				"subtract_minimum",
 | 
			
		||||
				"sub_min_cool_adjust",
 | 
			
		||||
				"sub_min_warm_adjust",
 | 
			
		||||
				"cold_white",
 | 
			
		||||
				"neutral_white",
 | 
			
		||||
				"auto",
 | 
			
		||||
				"auto_max",
 | 
			
		||||
				"auto_accurate",
 | 
			
		||||
				"white_off"				
 | 
			
		||||
			],
 | 
			
		||||
			"default": "white_off",
 | 
			
		||||
			"options": {
 | 
			
		||||
				"enum_titles": [
 | 
			
		||||
					"edt_dev_enum_subtract_minimum",
 | 
			
		||||
					"edt_dev_enum_sub_min_cool_adjust",
 | 
			
		||||
					"edt_dev_enum_sub_min_warm_adjust",
 | 
			
		||||
					"edt_dev_enum_cold_white",
 | 
			
		||||
					"edt_dev_enum_neutral_white",
 | 
			
		||||
					"edt_dev_enum_auto",
 | 
			
		||||
					"edt_dev_enum_auto_max",
 | 
			
		||||
					"edt_dev_enum_auto_accurate",
 | 
			
		||||
					"edt_dev_enum_white_off"
 | 
			
		||||
				]
 | 
			
		||||
			},
 | 
			
		||||
			"propertyOrder": 4
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
	"additionalProperties": true
 | 
			
		||||
}
 | 
			
		||||
@@ -22,10 +22,30 @@
 | 
			
		||||
		"whiteAlgorithm": {
 | 
			
		||||
			"type": "string",
 | 
			
		||||
			"title":"edt_dev_spec_whiteLedAlgor_title",
 | 
			
		||||
			"enum" : ["subtract_minimum","sub_min_cool_adjust","sub_min_warm_adjust","white_off"],
 | 
			
		||||
			"enum" : [
 | 
			
		||||
				"subtract_minimum",
 | 
			
		||||
				"sub_min_cool_adjust",
 | 
			
		||||
				"sub_min_warm_adjust",
 | 
			
		||||
				"cold_white",
 | 
			
		||||
				"neutral_white",
 | 
			
		||||
				"auto",
 | 
			
		||||
				"auto_max",
 | 
			
		||||
				"auto_accurate",
 | 
			
		||||
				"white_off"
 | 
			
		||||
			],
 | 
			
		||||
			"default": "subtract_minimum",
 | 
			
		||||
			"options" : {
 | 
			
		||||
				"enum_titles" : ["edt_dev_enum_subtract_minimum", "edt_dev_enum_sub_min_cool_adjust","edt_dev_enum_sub_min_warm_adjust", "edt_dev_enum_white_off"]
 | 
			
		||||
				"enum_titles" : [
 | 
			
		||||
					"edt_dev_enum_subtract_minimum",
 | 
			
		||||
					"edt_dev_enum_sub_min_cool_adjust",
 | 
			
		||||
					"edt_dev_enum_sub_min_warm_adjust",
 | 
			
		||||
					"edt_dev_enum_cold_white",
 | 
			
		||||
					"edt_dev_enum_neutral_white",
 | 
			
		||||
					"edt_dev_enum_auto",
 | 
			
		||||
					"edt_dev_enum_auto_max",
 | 
			
		||||
					"edt_dev_enum_auto_accurate",
 | 
			
		||||
					"edt_dev_enum_white_off"
 | 
			
		||||
				]
 | 
			
		||||
			},
 | 
			
		||||
			"propertyOrder" : 4
 | 
			
		||||
		},
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										20
									
								
								libsrc/leddevice/schemas/schema-ws2812_ftdi.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								libsrc/leddevice/schemas/schema-ws2812_ftdi.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,20 @@
 | 
			
		||||
{
 | 
			
		||||
	"type": "object",
 | 
			
		||||
	"required": true,
 | 
			
		||||
	"properties": {
 | 
			
		||||
		"output": {
 | 
			
		||||
			"type": "string",
 | 
			
		||||
			"title": "edt_dev_spec_outputPath_title",
 | 
			
		||||
			"propertyOrder": 1
 | 
			
		||||
		},
 | 
			
		||||
		"rate": {
 | 
			
		||||
			"type": "integer",
 | 
			
		||||
			"title": "edt_dev_spec_baudrate_title",
 | 
			
		||||
			"default": 3075000,
 | 
			
		||||
			"minimum": 2106000,
 | 
			
		||||
			"maximum": 3075000,
 | 
			
		||||
			"propertyOrder": 2
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
	"additionalProperties": true
 | 
			
		||||
}
 | 
			
		||||
@@ -43,10 +43,30 @@
 | 
			
		||||
		"whiteAlgorithm": {
 | 
			
		||||
			"type": "string",
 | 
			
		||||
			"title":"edt_dev_spec_whiteLedAlgor_title",
 | 
			
		||||
			"enum" : ["subtract_minimum","sub_min_cool_adjust","sub_min_warm_adjust","white_off"],
 | 
			
		||||
			"enum" : [
 | 
			
		||||
				"subtract_minimum",
 | 
			
		||||
				"sub_min_cool_adjust",
 | 
			
		||||
				"sub_min_warm_adjust",
 | 
			
		||||
				"cold_white",
 | 
			
		||||
				"neutral_white",
 | 
			
		||||
				"auto",
 | 
			
		||||
				"auto_max",
 | 
			
		||||
				"auto_accurate",
 | 
			
		||||
				"white_off"
 | 
			
		||||
			],
 | 
			
		||||
			"default": "subtract_minimum",
 | 
			
		||||
			"options" : {
 | 
			
		||||
				"enum_titles" : ["edt_dev_enum_subtract_minimum", "edt_dev_enum_sub_min_cool_adjust","edt_dev_enum_sub_min_warm_adjust", "edt_dev_enum_white_off"]
 | 
			
		||||
				"enum_titles" : [
 | 
			
		||||
					"edt_dev_enum_subtract_minimum",
 | 
			
		||||
					"edt_dev_enum_sub_min_cool_adjust",
 | 
			
		||||
					"edt_dev_enum_sub_min_warm_adjust",
 | 
			
		||||
					"edt_dev_enum_cold_white",
 | 
			
		||||
					"edt_dev_enum_neutral_white",
 | 
			
		||||
					"edt_dev_enum_auto",
 | 
			
		||||
					"edt_dev_enum_auto_max",
 | 
			
		||||
					"edt_dev_enum_auto_accurate",
 | 
			
		||||
					"edt_dev_enum_white_off"
 | 
			
		||||
				]
 | 
			
		||||
			},
 | 
			
		||||
			"propertyOrder" : 7
 | 
			
		||||
		},
 | 
			
		||||
 
 | 
			
		||||
@@ -133,6 +133,22 @@ void ImageResampler::processImage(const uint8_t * data, int width, int height, i
 | 
			
		||||
			break;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		case PixelFormat::RGB24:
 | 
			
		||||
		{
 | 
			
		||||
			for (int yDest = yDestStart, ySource = cropTop + (_verticalDecimation >> 1); yDest <= yDestEnd; ySource += _verticalDecimation, ++yDest)
 | 
			
		||||
			{
 | 
			
		||||
				for (int xDest = xDestStart, xSource = cropLeft + (_horizontalDecimation >> 1); xDest <= xDestEnd; xSource += _horizontalDecimation, ++xDest)
 | 
			
		||||
				{
 | 
			
		||||
					ColorRgb & rgb = outputImage(abs(xDest), abs(yDest));
 | 
			
		||||
					int index = lineLength * ySource + (xSource << 1) + xSource;
 | 
			
		||||
					rgb.red   = data[index  ];
 | 
			
		||||
					rgb.green = data[index+1];
 | 
			
		||||
					rgb.blue  = data[index+2];
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			break;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		case PixelFormat::BGR24:
 | 
			
		||||
		{
 | 
			
		||||
			for (int yDest = yDestStart, ySource = cropTop + (_verticalDecimation >> 1); yDest <= yDestEnd; ySource += _verticalDecimation, ++yDest)
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,8 @@
 | 
			
		||||
#include <utils/RgbToRgbw.h>
 | 
			
		||||
#include <utils/Logger.h>
 | 
			
		||||
 | 
			
		||||
#define ROUND_DIVIDE(number, denom) (((number) + (denom) / 2) / (denom))
 | 
			
		||||
 | 
			
		||||
namespace RGBW {
 | 
			
		||||
 | 
			
		||||
WhiteAlgorithm stringToWhiteAlgorithm(const QString& str)
 | 
			
		||||
@@ -19,7 +21,27 @@ WhiteAlgorithm stringToWhiteAlgorithm(const QString& str)
 | 
			
		||||
	{
 | 
			
		||||
		return WhiteAlgorithm::SUB_MIN_COOL_ADJUST;
 | 
			
		||||
	}
 | 
			
		||||
	if (str.isEmpty() || str == "white_off")
 | 
			
		||||
    if (str == "cold_white")
 | 
			
		||||
    {
 | 
			
		||||
        return WhiteAlgorithm::COLD_WHITE;
 | 
			
		||||
    }
 | 
			
		||||
    if (str == "neutral_white")
 | 
			
		||||
    {
 | 
			
		||||
        return WhiteAlgorithm::NEUTRAL_WHITE;
 | 
			
		||||
    }
 | 
			
		||||
    if (str == "auto")
 | 
			
		||||
    {
 | 
			
		||||
        return WhiteAlgorithm::AUTO;
 | 
			
		||||
    }
 | 
			
		||||
    if (str == "auto_max")
 | 
			
		||||
    {
 | 
			
		||||
        return WhiteAlgorithm::AUTO_MAX;
 | 
			
		||||
    }
 | 
			
		||||
    if (str == "auto_accurate")
 | 
			
		||||
    {
 | 
			
		||||
        return WhiteAlgorithm::AUTO_ACCURATE;
 | 
			
		||||
    }
 | 
			
		||||
    if (str.isEmpty() || str == "white_off")
 | 
			
		||||
	{
 | 
			
		||||
		return WhiteAlgorithm::WHITE_OFF;
 | 
			
		||||
	}
 | 
			
		||||
@@ -77,6 +99,63 @@ void Rgb_to_Rgbw(ColorRgb input, ColorRgbw * output, WhiteAlgorithm algorithm)
 | 
			
		||||
			output->white = 0;
 | 
			
		||||
			break;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
        case WhiteAlgorithm::AUTO_MAX:
 | 
			
		||||
        {
 | 
			
		||||
            output->red = input.red;
 | 
			
		||||
            output->green = input.green;
 | 
			
		||||
            output->blue = input.blue;
 | 
			
		||||
            output->white = input.red > input.green ? (input.red > input.blue ? input.red : input.blue) : (input.green > input.blue ? input.green : input.blue);
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        case WhiteAlgorithm::AUTO_ACCURATE:
 | 
			
		||||
        {
 | 
			
		||||
            output->white = input.red < input.green ? (input.red < input.blue ? input.red : input.blue) : (input.green < input.blue ? input.green : input.blue);
 | 
			
		||||
            output->red = input.red - output->white;
 | 
			
		||||
            output->green = input.green - output->white;
 | 
			
		||||
            output->blue = input.blue - output->white;
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        case WhiteAlgorithm::AUTO:
 | 
			
		||||
        {
 | 
			
		||||
 | 
			
		||||
            output->red = input.red;
 | 
			
		||||
            output->green = input.green;
 | 
			
		||||
            output->blue = input.blue;
 | 
			
		||||
            output->white = input.red < input.green ? (input.red < input.blue ? input.red : input.blue) : (input.green < input.blue ? input.green : input.blue);
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        case WhiteAlgorithm::NEUTRAL_WHITE:
 | 
			
		||||
        case WhiteAlgorithm::COLD_WHITE:
 | 
			
		||||
        {
 | 
			
		||||
            //cold white config
 | 
			
		||||
            uint8_t gain = 0xFF;
 | 
			
		||||
            uint8_t red = 0xA0;
 | 
			
		||||
            uint8_t green = 0xA0;
 | 
			
		||||
            uint8_t blue = 0xA0;
 | 
			
		||||
 | 
			
		||||
            if (algorithm == WhiteAlgorithm::NEUTRAL_WHITE) {
 | 
			
		||||
                gain = 0xFF;
 | 
			
		||||
                red = 0xB0;
 | 
			
		||||
                green = 0xB0;
 | 
			
		||||
                blue = 0x70;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            uint8_t _r = qMin((uint32_t)(ROUND_DIVIDE(red * input.red,  0xFF)), (uint32_t)0xFF);
 | 
			
		||||
            uint8_t _g = qMin((uint32_t)(ROUND_DIVIDE(green * input.green,  0xFF)), (uint32_t)0xFF);
 | 
			
		||||
            uint8_t _b = qMin((uint32_t)(ROUND_DIVIDE(blue * input.blue,  0xFF)), (uint32_t)0xFF);
 | 
			
		||||
 | 
			
		||||
            output->white = qMin(_r, qMin(_g, _b));
 | 
			
		||||
            output->red = input.red - _r;
 | 
			
		||||
            output->green = input.green - _g;
 | 
			
		||||
            output->blue = input.blue - _b;
 | 
			
		||||
 | 
			
		||||
            uint8_t _w = qMin((uint32_t)(ROUND_DIVIDE(gain * output->white,  0xFF)), (uint32_t)0xFF);
 | 
			
		||||
            output->white = _w;
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
		default:
 | 
			
		||||
			break;
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user