diff --git a/.gitignore b/.gitignore index 91a498c3..7f9c9c20 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ /build* .DS_Store +libsrc/hyperion/ImageProcessor.cpp +libsrc/hyperion/CMakeLists.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index 5b89af70..9c8e077a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,6 +37,9 @@ message(STATUS "ENABLE_WS2812BPWM = " ${ENABLE_WS2812BPWM}) option(ENABLE_X11 "Enable the X11 grabber" OFF) message(STATUS "ENABLE_X11 = " ${ENABLE_X11}) +option(ENABLE_QT5 "Enable QT5" OFF) +message(STATUS "ENABLE_QT5 = " ${ENABLE_QT5}) + if(ENABLE_V4L2 AND NOT ENABLE_PROTOBUF) message(FATAL_ERROR "V4L2 grabber requires PROTOBUF. Disable V4L2 or enable PROTOBUF") endif(ENABLE_V4L2 AND NOT ENABLE_PROTOBUF) @@ -53,6 +56,10 @@ if(ENABLE_OSX AND ENABLE_DISPMANX) message(FATAL_ERROR "dispmanx grabber and osx grabber cannot be used at the same time") endif(ENABLE_OSX AND ENABLE_DISPMANX) +#if(ENABLE_QT5) +# TODO vs ENABLE_QT4? +#endif(ENABLE_QT5) + # Createt the configuration file # configure a header file to pass some of the CMake settings # to the source code @@ -61,8 +68,14 @@ include_directories("${PROJECT_BINARY_DIR}") # Add project specific cmake modules (find, etc) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake) + +if(ENABLE_QT5) +ADD_DEFINITIONS ( -DENABLE_QT5 ) +#find_package(Qt5Widgets) +else(ENABLE_QT5) # Add specific cmake modules to find qt4 (default version finds first available QT which might not be qt4) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake/qt4) +endif(ENABLE_QT5) # Define the global output path of binaries SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib) @@ -78,22 +91,31 @@ include_directories(${CMAKE_SOURCE_DIR}/include) # Prefer static linking over dynamic #set(CMAKE_FIND_LIBRARY_SUFFIXES ".a;.so") -set(CMAKE_BUILD_TYPE "Debug") -#set(CMAKE_BUILD_TYPE "Release") - # enable C++11 set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x -Wall") #set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++ -std=c++11 -Wall") +if(ENABLE_QT5) +#find_package(Qt5Core REQUIRED) +find_package(Qt5 COMPONENTS Core Gui Widgets Network REQUIRED) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${Qt5Widgets_EXECUTABLE_COMPILE_FLAGS}") +# set(CMAKE_CXX_FLAGS "-fPIC") +else(ENABLE_QT5) # Configure the use of QT4 find_package(Qt4 COMPONENTS QtCore QtGui QtNetwork REQUIRED QUIET) +endif(ENABLE_QT5) #add libusb and pthreads find_package(libusb-1.0 REQUIRED) find_package(Threads REQUIRED) - +if(ENABLE_QT5) +#include(${QT_USE_FILE}) +add_definitions(${QT_DEFINITIONS}) +else(ENABLE_QT5) include(${QT_USE_FILE}) add_definitions(${QT_DEFINITIONS}) +endif(ENABLE_QT5) + # TODO[TvdZ]: This linking directory should only be added if we are cross compiling if(NOT APPLE) link_directories(${CMAKE_FIND_ROOT_PATH}/lib/arm-linux-gnueabihf) diff --git a/CompileHowto.txt b/CompileHowto.txt index 621cf8fd..9f419709 100644 --- a/CompileHowto.txt +++ b/CompileHowto.txt @@ -24,11 +24,11 @@ mkdir "$HYPERION_DIR/build" cd "$HYPERION_DIR/build" # run cmake to generate make files on the raspberry pi -cmake .. +cmake -DCMAKE_BUILD_TYPE=Release -Wno-dev .. # or if you are not compiling on the raspberry pi and need to disable the Dispmanx grabber and support for spi devices -cmake -DENABLE_DISPMANX=OFF -DENABLE_SPIDEV=OFF -DENABLE_X11=ON .. +cmake -DENABLE_DISPMANX=OFF -DENABLE_SPIDEV=OFF -DENABLE_X11=ON -DCMAKE_BUILD_TYPE=Release -Wno-dev .. # as an alternative for the dispmanx grabber on non-rpi devices (e.g. cubox-i) you could try the framebuffer grabber -cmake -DENABLE_DISPMANX=OFF -DENABLE_SPIDEV=OFF -DENABLE_FB=ON .. +cmake -DENABLE_DISPMANX=OFF -DENABLE_SPIDEV=OFF -DENABLE_FB=ON -DCMAKE_BUILD_TYPE=Release -Wno-dev .. # for OSX build you need XCode, qt4 libraries and cmake. You can use macport (homebrew might work too) to install them sudo port install qt4-mac sudo port install cmake @@ -36,6 +36,12 @@ cmake -DENABLE_DISPMANX=OFF -DENABLE_SPIDEV=OFF -DENABLE_V4L2=OFF -DENABLE_OSX=O # run make to build Hyperion make +# or if you have a system with more then 1 cpu core +make -j 4 +# "4" is the number of cpu cores (e.g. 4 on RPi2), this makes compile faster + +#after compile, to remove any stuff not needed for a release version. +strip bin/* # The binaries are build in "$HYPERION_DIR/build/bin". You could copy those to /usr/bin sudo cp ./bin/hyperion-remote /usr/bin/ diff --git a/Toolchain-imx6.cmake b/Toolchain-imx6.cmake index 6ca757a7..3afbeaed 100644 --- a/Toolchain-imx6.cmake +++ b/Toolchain-imx6.cmake @@ -6,8 +6,8 @@ SET(CMAKE_SYSTEM_VERSION 1) # specify the cross compiler SET(CMAKE_C_COMPILER ${CUBIXCROSS_DIR}/gcc-linaro-arm-linux-gnueabihf-4.8-2013.10_linux/bin/arm-linux-gnueabihf-gcc) SET(CMAKE_CXX_COMPILER ${CUBIXCROSS_DIR}/gcc-linaro-arm-linux-gnueabihf-4.8-2013.10_linux/bin/arm-linux-gnueabihf-g++) -SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=armv7-a -mcpu=cortex-a9 -mtune=cortex-a9 -mfpu=vfpv3 -mfloat-abi=softfp") -SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -march=armv7-a -mcpu=cortex-a9 -mtune=cortex-a9 -mfpu=vfpv3 -mfloat-abi=softfp") +SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=armv7-a -mcpu=cortex-a9 -mtune=cortex-a9 -mfpu=vfpv3 -mfloat-abi=hard") +SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -march=armv7-a -mcpu=cortex-a9 -mtune=cortex-a9 -mfpu=vfpv3 -mfloat-abi=hard") # where is the target environment SET(CMAKE_FIND_ROOT_PATH ${CUBIXCROSS_DIR}/rootfs) diff --git a/bin/create_release.sh b/bin/create_release.sh index 625ff0e1..c44fb5c5 100644 --- a/bin/create_release.sh +++ b/bin/create_release.sh @@ -24,12 +24,17 @@ tar --create --verbose --gzip --absolute-names --show-transformed-names --ignore --transform "s:$repodir/effects/:hyperion/effects/:" \ --transform "s:$repodir/config/:hyperion/config/:" \ --transform "s:$repodir/bin/hyperion.init.sh:hyperion/init.d/hyperion.init.sh:" \ + --transform "s:$repodir/bin/hyperion.systemd.sh:hyperion/init.d/hyperion.systemd.sh:" \ + --transform "s:$repodir/bin/hyperion.initctl.sh:hyperion/init.d/hyperion.initctl.sh:" \ --transform "s://:/:g" \ "$builddir/bin/hyperiond" \ "$builddir/bin/hyperion-remote" \ "$builddir/bin/hyperion-v4l2" \ + "$builddir/bin/gpio2spi" \ "$builddir/bin/dispmanx2png" \ "$repodir/effects/"* \ "$repodir/bin/hyperion.init.sh" \ + "$repodir/bin/hyperion.systemd.sh" \ + "$repodir/bin/hyperion.initctl.sh" \ "$repodir/config/hyperion.config.json" diff --git a/bin/hyperion.init.sh b/bin/hyperion.init.sh index d83bdb28..fbae0156 100644 --- a/bin/hyperion.init.sh +++ b/bin/hyperion.init.sh @@ -1,7 +1,16 @@ #!/bin/bash -# Hyperion daemon +# Hyperion daemon service # description: Hyperion daemon # processname: hyperiond +### BEGIN INIT INFO +# Provides: Hyperion +# Required-Start: $remote_fs $syslog +# Required-Stop: $remote_fs $syslog +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: Hyperion Ambilight init.d Service. +# Description: Hyperion Ambilight init.d Service. +### END INIT INFO DAEMON=hyperiond DAEMONOPTS="/etc/hyperion.config.json" diff --git a/bin/hyperion.initctl.sh b/bin/hyperion.initctl.sh new file mode 100644 index 00000000..e533b14a --- /dev/null +++ b/bin/hyperion.initctl.sh @@ -0,0 +1,17 @@ +## Hyperion daemon initctl script + +description "hyperion" +author "poljvd & tvdzwan" + +start on (runlevel [2345]) +stop on (runlevel [!2345]) + +respawn + +pre-start script +#comment out the following 2 lines for x32/64 +modprobe spidev +/usr/bin/gpio2spi +end script + +exec /usr/bin/hyperiond /etc/hyperion.config.json \ No newline at end of file diff --git a/bin/hyperion.systemd.sh b/bin/hyperion.systemd.sh new file mode 100644 index 00000000..005c0c14 --- /dev/null +++ b/bin/hyperion.systemd.sh @@ -0,0 +1,15 @@ +[Unit] +Description=Hyperion Systemd service + +[Service] +Type=simple +User=root +Group=root +UMask=007 +ExecStart=/opt/hyperion/bin/hyperiond /etc/hyperion.config.json +ExecReload=/bin/kill -HUP $MAINPID +Restart=on-failure +TimeoutStopSec=10 + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/bin/install_hyperion.sh b/bin/install_hyperion.sh index 4d3e9a2c..4c06e696 100755 --- a/bin/install_hyperion.sh +++ b/bin/install_hyperion.sh @@ -40,7 +40,7 @@ if [ $IS_OPENELEC -eq 1 ]; then if [ $IS_IMX6 -eq 1 ]; then curl -L --get https://raw.githubusercontent.com/tvdzwan/hyperion/master/deploy/hyperion_imx6.tar.gz | tar -C /storage -xz else - curl -L --get https://raw.githubusercontent.com/tvdzwan/hyperion/master/deploy/hyperion.tar.gz | tar -C /storage -xz + curl -L --get https://raw.githubusercontent.com/tvdzwan/hyperion/master/deploy/hyperion_rpi.tar.gz | tar -C /storage -xz fi curl -L --get https://raw.githubusercontent.com/tvdzwan/hyperion/master/deploy/hyperion.deps.openelec-rpi.tar.gz | tar -C /storage/hyperion/bin -xz # modify the default config to have a correct effect path @@ -49,7 +49,7 @@ else if [ $IS_IMX6 -eq 1 ]; then wget https://raw.githubusercontent.com/tvdzwan/hyperion/master/deploy/hyperion_imx6.tar.gz -O - | tar -C /opt -xz else - wget https://raw.githubusercontent.com/tvdzwan/hyperion/master/deploy/hyperion.tar.gz -O - | tar -C /opt -xz + wget https://raw.githubusercontent.com/tvdzwan/hyperion/master/deploy/hyperion_rpi.tar.gz -O - | tar -C /opt -xz fi fi diff --git a/bin/remove_hyperion.sh b/bin/remove_hyperion.sh new file mode 100644 index 00000000..2b0c2788 --- /dev/null +++ b/bin/remove_hyperion.sh @@ -0,0 +1,105 @@ +#!/bin/sh +# Script to remove Hyperion and all services + +# Make sure /sbin is on the path (for service to find sub scripts) +PATH="/sbin:$PATH" + +#Check if HyperCon is logged in as root +if [ $(id -u) != 0 ] && [ "$1" = "HyperConRemove" ]; then + echo '---> Critical Error: Please connect as user "root" through HyperCon' + echo '---> We need admin privileges to remove your Hyperion! -> abort' + exit 1 +fi + +#Check, if script is running as root +if [ $(id -u) != 0 ]; then + echo '---> Critical Error: Please run the script as root (sudo sh ./remove_hyperion.sh)' + exit 1 +fi + +#Welcome message +echo '*******************************************************************************' +echo 'This script will remove Hyperion and it´s services' +echo '-----> Please BACKUP your hyperion.config.json if necessary <-----' +echo '*******************************************************************************' + +#Skip the prompt if HyperCon Remove +if [ "$1" = "" ]; then +#Prompt for confirmation to proceed +while true +do +echo -n "---> Do you really want to remove Hyperion and it´s services? (y or n) :" +read CONFIRM +case $CONFIRM in +y|Y|YES|yes|Yes) break ;; +n|N|no|NO|No) +echo "---> Aborting - you entered \"$CONFIRM\"" +exit +;; +*) echo "-> Please enter only y or n" +esac +done +echo "---> You entered \"$CONFIRM\". Remove Hyperion!" +fi +# Find out if we are on OpenElec +OS_OPENELEC=`grep -m1 -c OpenELEC /etc/issue` + +# check which init script we should use +USE_SYSTEMD=`grep -m1 -c systemd /proc/1/comm` +USE_INITCTL=`which /sbin/initctl | wc -l` +USE_SERVICE=`which /usr/sbin/service | wc -l` + +# Stop hyperion daemon if it is running +echo '---> Stop Hyperion, if necessary' +if [ $OS_OPENELEC -eq 1 ]; then + killall hyperiond 2>/dev/null +elif [ $USE_INITCTL -eq 1 ]; then + /sbin/initctl stop hyperion 2>/dev/null +elif [ $USE_SERVICE -eq 1 ]; then + /usr/sbin/service hyperion stop 2>/dev/null +elif [ $USE_SYSTEMD -eq 1 ]; then + service hyperion stop 2>/dev/null +fi + +#Disabling and delete service files +if [ $USE_INITCTL -eq 1 ]; then + echo '---> Delete and disable Hyperion initctl script' + rm -v /etc/init/hyperion 2>/dev/null + initctl reload-configuration +elif [ $OS_OPENELEC -eq 1 ]; then + # Remove Hyperion from OpenELEC autostart.sh + echo "---> Remove Hyperion from OpenELEC autostart.sh" + sed -i "/hyperiond/d" /storage/.config/autostart.sh 2>/dev/null +elif [ $USE_SYSTEMD -eq 1 ]; then + # Delete and disable Hyperion systemd script + echo '---> Delete and disable Hyperion systemd script' + systemctl disable hyperion.service + rm -v /etc/systemd/system/hyperion.service 2>/dev/null +elif [ $USE_SERVICE -eq 1 ]; then + # Delete and disable Hyperion init.d script + echo '---> Delete and disable Hyperion init.d script' + update-rc.d -f hyperion remove + rm /etc/init.d/hyperion 2>/dev/null +fi + +# Delete Hyperion binaries +if [ $OS_OPENELEC -eq 1 ]; then + # Remove OpenELEC Hyperion binaries and configs + echo '---> Remove the OpenELEC Hyperion binaries and hyperion.config.json' + rm -rv /storage/hyperion 2>/dev/null + rm -v /storage/.config/hyperion.config.json 2>/dev/null +else + #Remove binaries on all distributions/systems (not OpenELEC) + echo "---> Remove links to the binaries" + rm -v /usr/bin/hyperiond 2>/dev/null + rm -v /usr/bin/hyperion-remote 2>/dev/null + rm -v /usr/bin/hyperion-v4l2 2>/dev/null + rm -v /etc/hyperion.config.json 2>/dev/null + echo "---> Remove binaries" + rm -rv /opt/hyperion 2>/dev/null +fi +echo '*******************************************************************************' +echo 'Hyperion successful removed!' +echo '*******************************************************************************' +exit 0 + \ No newline at end of file diff --git a/config/hyperion.config.json b/config/hyperion.config.json index 44931ac4..5bc313c3 100644 --- a/config/hyperion.config.json +++ b/config/hyperion.config.json @@ -4,22 +4,16 @@ { /// Device configuration contains the following fields: /// * 'name' : The user friendly name of the device (only used for display purposes) - /// * 'type' : The type of the device or leds (known types for now are 'ws2801', 'ldp8806', - /// 'lpd6803', 'sedu', 'adalight', 'lightpack', 'philipshue', 'test' and 'none') - /// * 'output' : The output specification depends on selected device. This can for example be the - /// device specifier, device serial number, or the output file name - /// * 'rate' : The baudrate of the output to the device + /// * 'type' : The type of the device or leds (known types for now are + /// APA102, Adalight, AmbiLed, Atmo, Hyperion-USBASP-WS2801, Hyperion-USBASP-WS2812, Lightberry, Lightpack, LPD6803, LPD8806, Multi-Lightpack, P9813, Paintpack, PhilipsHUE, PiBlaster, SEDU, Test, ThinkerForge, TPM2, WS2801, WS2812b, None) + /// * [device type specific configuration] /// * 'colorOrder' : The order of the color bytes ('rgb', 'rbg', 'bgr', etc.). - /// Specific of Philips Hue: - /// * 'username' : The name of user registred on the Philips Hue Bridge - /// * 'switchOffOnBlack' : Define if Hue light switch off when black is detected - /// * 'transitiontime' : Set the time of transition between color of Hue light "device" : { "name" : "MyPi", "type" : "ws2801", "output" : "/dev/spidev0.0", - "rate" : 250000, + "rate" : 1000000, "colorOrder" : "rgb" }, @@ -45,6 +39,7 @@ /// - 'type' The type of smoothing algorithm ('linear' or 'none') /// - 'time_ms' The time constant for smoothing algorithm in milliseconds /// - 'updateFrequency' The update frequency of the leds in Hz + /// - 'updateDelay' The delay of the output to leds (in periods of smoothing) "color" : { "transform" : @@ -82,13 +77,156 @@ ], "smoothing" : { - "type" : "none", + "type" : "linear", "time_ms" : 200, "updateFrequency" : 20.0000, "updateDelay" : 0 } }, + /// The black border configuration, contains the following items: + /// * enable : true if the detector should be activated + /// * threshold : Value below which a pixel is regarded as black (value between 0.0 and 1.0) + /// * unknownFrameCnt : Number of frames without any detection before the border is set to 0 (default 600) + /// * borderFrameCnt : Number of frames before a consistent detected border gets set (default 50) + /// * maxInconsistentCnt : Number of inconsistent frames that are ignored before a new border gets a chance to proof consistency + /// * blurRemoveCnt : Number of pixels that get removed from the detected border to cut away blur (default 1) + /// * mode : Border detection mode (values=default,classic,osd) + "blackborderdetector" : + { + "enable" : true, + "threshold" : 0.0, + "unknownFrameCnt" : 600, + "borderFrameCnt" : 50, + "maxInconsistentCnt" : 10, + "blurRemoveCnt" : 1, + "mode" : "default" + }, + + /// The configuration of the effect engine, contains the following items: + /// * paths : An array with absolute location(s) of directories with effects + /// * color : Set static color after boot -> set effect to "" (empty) and input the values [R,G,B] and set duration_ms NOT to 0 (use 1) instead + /// * effect : The effect selected as 'boot sequence' + /// * duration_ms : The duration of the selected effect (0=endless) + /// * priority : The priority of the selected effect/static color (default=0) + "effects" : + { + "paths" : + [ + "/opt/hyperion/effects" + ] + }, + + "bootsequence" : + { + "color" : [0,0,0], + "effect" : "Rainbow swirl fast", + "duration_ms" : 3000, + "priority" : 990 + }, + + /// The configuration of the Json/Proto forwarder. Forward messages to multiple instances of Hyperion on same and/or other hosts + /// 'proto' is mostly used for video streams and 'json' for effects + /// * proto : Proto server adress and port of your target. Syntax:[IP:PORT] -> ["127.0.0.1:19447"] or more instances to forward ["127.0.0.1:19447","192.168.0.24:19449"] + /// * json : Json server adress and port of your target. Syntax:[IP:PORT] -> ["127.0.0.1:19446"] or more instances to forward ["127.0.0.1:19446","192.168.0.24:19448"] + /// HINT: If you redirect to "127.0.0.1" (localhost) you could start a second hyperion with another device/led config! + /// Be sure your client(s) is/are listening on the configured ports. The second Hyperion (if used) also needs to be configured! (HyperCon -> External -> Json Server/Proto Server) +// "forwarder" : +// { +// "proto" : ["127.0.0.1:19447"], +// "json" : ["127.0.0.1:19446"] +// }, + + /// The configuration for the frame-grabber, contains the following items: + /// * width : The width of the grabbed frames [pixels] + /// * height : The height of the grabbed frames [pixels] + /// * frequency_Hz : The frequency of the frame grab [Hz] + "framegrabber" : + { + "width" : 80, + "height" : 45, + "frequency_Hz" : 10.0 + }, + + /// The configuration of the Kodi connection used to enable and disable the frame-grabber. Contains the following fields: + /// * xbmcAddress : The IP address of the Kodi-host + /// * xbmcTcpPort : The TCP-port of the Kodi-server + /// * grabVideo : Flag indicating that the frame-grabber is on(true) during video playback + /// * grabPictures : Flag indicating that the frame-grabber is on(true) during picture show + /// * grabAudio : Flag indicating that the frame-grabber is on(true) during audio playback + /// * grabMenu : Flag indicating that the frame-grabber is on(true) at the Kodi menu + /// * grabScreensaver : Flag indicating that the frame-grabber is on(true) when Kodi is on screensaver + /// * enable3DDetection : Flag indicating that the frame-grabber should switch to a 3D compatible modus if a 3D video is playing +// "xbmcVideoChecker" : +// { +// "xbmcAddress" : "127.0.0.1", +// "xbmcTcpPort" : 9090, +// "grabVideo" : true, +// "grabPictures" : true, +// "grabAudio" : true, +// "grabMenu" : false, +// "grabScreensaver" : true, +// "enable3DDetection" : true +// }, + + /// The configuration of the Json server which enables the json remote interface + /// * port : Port at which the json server is started + "jsonServer" : + { + "port" : 19444 + }, + + /// The configuration of the Proto server which enables the protobuffer remote interface + /// * port : Port at which the protobuffer server is started + "protoServer" : + { + "port" : 19445 + }, + + /// The configuration of the boblight server which enables the boblight remote interface + /// * port : Port at which the boblight server is started +// "boblightServer" : +// { +// "port" : 19333 +// }, + + /// Configuration for the embedded V4L2 grabber + /// * device : V4L2 Device to use [default="/dev/video0"] + /// * input : V4L2 input to use [default=0] + /// * standard : Video standard (no-change/PAL/NTSC) [default="no-change"] + /// * width : V4L2 width to set [default=-1] + /// * height : V4L2 height to set [default=-1] + /// * frameDecimation : Frame decimation factor [default=2] + /// * sizeDecimation : Size decimation factor [default=8] + /// * priority : Hyperion priority channel [default=800] + /// * mode : 3D mode to use 2D/3DSBS/3DTAB (note: no autodetection) [default="2D"] + /// * cropLeft : Cropping from the left [default=0] + /// * cropRight : Cropping from the right [default=0] + /// * cropTop : Cropping from the top [default=0] + /// * cropBottom : Cropping from the bottom [default=0] + /// * redSignalThreshold : Signal threshold for the red channel between 0.0 and 1.0 [default=0.0] + /// * greenSignalThreshold : Signal threshold for the green channel between 0.0 and 1.0 [default=0.0] + /// * blueSignalThreshold : Signal threshold for the blue channel between 0.0 and 1.0 [default=0.0] +// "grabber-v4l2" : +// { +// "device" : "/dev/video0", +// "input" : 0, +// "standard" : "no-change", +// "width" : -1, +// "height" : -1, +// "frameDecimation" : 2, +// "sizeDecimation" : 8, +// "priority" : 800, +// "mode" : "2D", +// "cropLeft" : 0, +// "cropRight" : 0, +// "cropTop" : 0, +// "cropBottom" : 0, +// "redSignalThreshold" : 0.0, +// "greenSignalThreshold" : 0.0, +// "blueSignalThreshold" : 0.0 +// }, + /// The configuration for each individual led. This contains the specification of the area /// averaged of an input image for each led to determine its color. Each item in the list /// contains the following fields: @@ -102,47 +240,47 @@ [ { "index" : 0, - "hscan" : { "minimum" : 0.4375, "maximum" : 0.5000 }, + "hscan" : { "minimum" : 0.5000, "maximum" : 0.5625 }, "vscan" : { "minimum" : 0.9200, "maximum" : 1.0000 } }, { "index" : 1, - "hscan" : { "minimum" : 0.3750, "maximum" : 0.4375 }, + "hscan" : { "minimum" : 0.4375, "maximum" : 0.5000 }, "vscan" : { "minimum" : 0.9200, "maximum" : 1.0000 } }, { "index" : 2, - "hscan" : { "minimum" : 0.3125, "maximum" : 0.3750 }, + "hscan" : { "minimum" : 0.3750, "maximum" : 0.4375 }, "vscan" : { "minimum" : 0.9200, "maximum" : 1.0000 } }, { "index" : 3, - "hscan" : { "minimum" : 0.2500, "maximum" : 0.3125 }, + "hscan" : { "minimum" : 0.3125, "maximum" : 0.3750 }, "vscan" : { "minimum" : 0.9200, "maximum" : 1.0000 } }, { "index" : 4, - "hscan" : { "minimum" : 0.1875, "maximum" : 0.2500 }, + "hscan" : { "minimum" : 0.2500, "maximum" : 0.3125 }, "vscan" : { "minimum" : 0.9200, "maximum" : 1.0000 } }, { "index" : 5, - "hscan" : { "minimum" : 0.1250, "maximum" : 0.1875 }, + "hscan" : { "minimum" : 0.1875, "maximum" : 0.2500 }, "vscan" : { "minimum" : 0.9200, "maximum" : 1.0000 } }, { "index" : 6, - "hscan" : { "minimum" : 0.0625, "maximum" : 0.1250 }, + "hscan" : { "minimum" : 0.1250, "maximum" : 0.1875 }, "vscan" : { "minimum" : 0.9200, "maximum" : 1.0000 } }, { "index" : 7, - "hscan" : { "minimum" : 0.0000, "maximum" : 0.0625 }, + "hscan" : { "minimum" : 0.0625, "maximum" : 0.1250 }, "vscan" : { "minimum" : 0.9200, "maximum" : 1.0000 } }, { "index" : 8, - "hscan" : { "minimum" : 0.0000, "maximum" : 0.0500 }, + "hscan" : { "minimum" : 0.0000, "maximum" : 0.0625 }, "vscan" : { "minimum" : 0.9200, "maximum" : 1.0000 } }, { @@ -182,254 +320,155 @@ }, { "index" : 16, - "hscan" : { "minimum" : 0.0000, "maximum" : 0.0500 }, - "vscan" : { "minimum" : 0.0000, "maximum" : 0.0800 } - }, - { - "index" : 17, "hscan" : { "minimum" : 0.0000, "maximum" : 0.0625 }, "vscan" : { "minimum" : 0.0000, "maximum" : 0.0800 } }, { - "index" : 18, + "index" : 17, "hscan" : { "minimum" : 0.0625, "maximum" : 0.1250 }, "vscan" : { "minimum" : 0.0000, "maximum" : 0.0800 } }, { - "index" : 19, + "index" : 18, "hscan" : { "minimum" : 0.1250, "maximum" : 0.1875 }, "vscan" : { "minimum" : 0.0000, "maximum" : 0.0800 } }, { - "index" : 20, + "index" : 19, "hscan" : { "minimum" : 0.1875, "maximum" : 0.2500 }, "vscan" : { "minimum" : 0.0000, "maximum" : 0.0800 } }, { - "index" : 21, + "index" : 20, "hscan" : { "minimum" : 0.2500, "maximum" : 0.3125 }, "vscan" : { "minimum" : 0.0000, "maximum" : 0.0800 } }, { - "index" : 22, + "index" : 21, "hscan" : { "minimum" : 0.3125, "maximum" : 0.3750 }, "vscan" : { "minimum" : 0.0000, "maximum" : 0.0800 } }, { - "index" : 23, + "index" : 22, "hscan" : { "minimum" : 0.3750, "maximum" : 0.4375 }, "vscan" : { "minimum" : 0.0000, "maximum" : 0.0800 } }, { - "index" : 24, + "index" : 23, "hscan" : { "minimum" : 0.4375, "maximum" : 0.5000 }, "vscan" : { "minimum" : 0.0000, "maximum" : 0.0800 } }, { - "index" : 25, + "index" : 24, "hscan" : { "minimum" : 0.5000, "maximum" : 0.5625 }, "vscan" : { "minimum" : 0.0000, "maximum" : 0.0800 } }, + { + "index" : 25, + "hscan" : { "minimum" : 0.5625, "maximum" : 0.6250 }, + "vscan" : { "minimum" : 0.0000, "maximum" : 0.0800 } + }, { "index" : 26, - "hscan" : { "minimum" : 0.5625, "maximum" : 0.6250 }, + "hscan" : { "minimum" : 0.6250, "maximum" : 0.6875 }, "vscan" : { "minimum" : 0.0000, "maximum" : 0.0800 } }, { "index" : 27, - "hscan" : { "minimum" : 0.6250, "maximum" : 0.6875 }, + "hscan" : { "minimum" : 0.6875, "maximum" : 0.7500 }, "vscan" : { "minimum" : 0.0000, "maximum" : 0.0800 } }, { "index" : 28, - "hscan" : { "minimum" : 0.6875, "maximum" : 0.7500 }, + "hscan" : { "minimum" : 0.7500, "maximum" : 0.8125 }, "vscan" : { "minimum" : 0.0000, "maximum" : 0.0800 } }, { "index" : 29, - "hscan" : { "minimum" : 0.7500, "maximum" : 0.8125 }, + "hscan" : { "minimum" : 0.8125, "maximum" : 0.8750 }, "vscan" : { "minimum" : 0.0000, "maximum" : 0.0800 } }, { "index" : 30, - "hscan" : { "minimum" : 0.8125, "maximum" : 0.8750 }, + "hscan" : { "minimum" : 0.8750, "maximum" : 0.9375 }, "vscan" : { "minimum" : 0.0000, "maximum" : 0.0800 } }, { "index" : 31, - "hscan" : { "minimum" : 0.8750, "maximum" : 0.9375 }, + "hscan" : { "minimum" : 0.9375, "maximum" : 1.0000 }, "vscan" : { "minimum" : 0.0000, "maximum" : 0.0800 } }, { "index" : 32, - "hscan" : { "minimum" : 0.9375, "maximum" : 1.0000 }, - "vscan" : { "minimum" : 0.0000, "maximum" : 0.0800 } - }, - { - "index" : 33, - "hscan" : { "minimum" : 0.9500, "maximum" : 1.0000 }, - "vscan" : { "minimum" : 0.0000, "maximum" : 0.0800 } - }, - { - "index" : 34, "hscan" : { "minimum" : 0.9500, "maximum" : 1.0000 }, "vscan" : { "minimum" : 0.0000, "maximum" : 0.1429 } }, { - "index" : 35, + "index" : 33, "hscan" : { "minimum" : 0.9500, "maximum" : 1.0000 }, "vscan" : { "minimum" : 0.1429, "maximum" : 0.2857 } }, { - "index" : 36, + "index" : 34, "hscan" : { "minimum" : 0.9500, "maximum" : 1.0000 }, "vscan" : { "minimum" : 0.2857, "maximum" : 0.4286 } }, { - "index" : 37, + "index" : 35, "hscan" : { "minimum" : 0.9500, "maximum" : 1.0000 }, "vscan" : { "minimum" : 0.4286, "maximum" : 0.5714 } }, { - "index" : 38, + "index" : 36, "hscan" : { "minimum" : 0.9500, "maximum" : 1.0000 }, "vscan" : { "minimum" : 0.5714, "maximum" : 0.7143 } }, { - "index" : 39, + "index" : 37, "hscan" : { "minimum" : 0.9500, "maximum" : 1.0000 }, "vscan" : { "minimum" : 0.7143, "maximum" : 0.8571 } }, { - "index" : 40, + "index" : 38, "hscan" : { "minimum" : 0.9500, "maximum" : 1.0000 }, "vscan" : { "minimum" : 0.8571, "maximum" : 1.0000 } }, { - "index" : 41, - "hscan" : { "minimum" : 0.9500, "maximum" : 1.0000 }, - "vscan" : { "minimum" : 0.9200, "maximum" : 1.0000 } - }, - { - "index" : 42, + "index" : 39, "hscan" : { "minimum" : 0.9375, "maximum" : 1.0000 }, "vscan" : { "minimum" : 0.9200, "maximum" : 1.0000 } }, { - "index" : 43, + "index" : 40, "hscan" : { "minimum" : 0.8750, "maximum" : 0.9375 }, "vscan" : { "minimum" : 0.9200, "maximum" : 1.0000 } }, { - "index" : 44, + "index" : 41, "hscan" : { "minimum" : 0.8125, "maximum" : 0.8750 }, "vscan" : { "minimum" : 0.9200, "maximum" : 1.0000 } }, { - "index" : 45, + "index" : 42, "hscan" : { "minimum" : 0.7500, "maximum" : 0.8125 }, "vscan" : { "minimum" : 0.9200, "maximum" : 1.0000 } }, { - "index" : 46, + "index" : 43, "hscan" : { "minimum" : 0.6875, "maximum" : 0.7500 }, "vscan" : { "minimum" : 0.9200, "maximum" : 1.0000 } }, { - "index" : 47, + "index" : 44, "hscan" : { "minimum" : 0.6250, "maximum" : 0.6875 }, "vscan" : { "minimum" : 0.9200, "maximum" : 1.0000 } }, { - "index" : 48, + "index" : 45, "hscan" : { "minimum" : 0.5625, "maximum" : 0.6250 }, "vscan" : { "minimum" : 0.9200, "maximum" : 1.0000 } - }, - { - "index" : 49, - "hscan" : { "minimum" : 0.5000, "maximum" : 0.5625 }, - "vscan" : { "minimum" : 0.9200, "maximum" : 1.0000 } } ], - /// The black border configuration, contains the following items: - /// * enable : true if the detector should be activated - /// * threshold : Value below which a pixel is regarded as black (value between 0.0 and 1.0) - "blackborderdetector" : - { - "enable" : true, - "threshold" : 0.01 - }, - - /// The configuration of the effect engine, contains the following items: - /// * paths : An array with absolute location(s) of directories with effects - /// * bootsequence : The effect selected as 'boot sequence' - "effects" : - { - "paths" : - [ - "/opt/hyperion/effects" - ] - }, - - "bootsequence" : - { - "effect" : "Rainbow swirl fast", - "duration_ms" : 3000 - }, - - /// The configuration for the frame-grabber, contains the following items: - /// * width : The width of the grabbed frames [pixels] - /// * height : The height of the grabbed frames [pixels] - /// * frequency_Hz : The frequency of the frame grab [Hz] - "framegrabber" : - { - "width" : 64, - "height" : 64, - "frequency_Hz" : 10.0 - }, - - /// The configuration of the XBMC connection used to enable and disable the frame-grabber. Contains the following fields: - /// * xbmcAddress : The IP address of the XBMC-host - /// * xbmcTcpPort : The TCP-port of the XBMC-server - /// * grabVideo : Flag indicating that the frame-grabber is on(true) during video playback - /// * grabPictures : Flag indicating that the frame-grabber is on(true) during picture show - /// * grabAudio : Flag indicating that the frame-grabber is on(true) during audio playback - /// * grabMenu : Flag indicating that the frame-grabber is on(true) in the XBMC menu - /// * grabScreensaver : Flag indicating that the frame-grabber is on(true) when XBMC is on screensaver - /// * enable3DDetection : Flag indicating that the frame-grabber should switch to a 3D compatible modus if a 3D video is playing - "xbmcVideoChecker" : - { - "xbmcAddress" : "127.0.0.1", - "xbmcTcpPort" : 9090, - "grabVideo" : true, - "grabPictures" : true, - "grabAudio" : true, - "grabMenu" : false, - "grabScreensaver" : true, - "enable3DDetection" : true - }, - - /// The configuration of the Json server which enables the json remote interface - /// * port : Port at which the json server is started - "jsonServer" : - { - "port" : 19444 - }, - - /// The configuration of the Proto server which enables the protobuffer remote interface - /// * port : Port at which the protobuffer server is started - "protoServer" : - { - "port" : 19445 - }, - - /// The configuration of the boblight server which enables the boblight remote interface - /// * port : Port at which the boblight server is started -// "boblightServer" : -// { -// "port" : 19333 -// }, - "endOfJson" : "endOfJson" } diff --git a/config/hyperion_x86.config.json b/config/hyperion_x86.config.json index 4abd64db..ec21a35b 100644 --- a/config/hyperion_x86.config.json +++ b/config/hyperion_x86.config.json @@ -19,6 +19,21 @@ "colorOrder" : "rgb" }, + /// Configuration for message forwarding to other hyperions + /// protobuffer and json remote interface are forwarded to configured hosts + /// 'proto' is mostly used for video streams and 'json' for effects + /// + /// ** pay attention which port you use. use correct ports for protols ** + /// + /// * 'proto' : list of host in form of : + /// * 'json' : list of host in form of : +/// "forwarder" : +/// { +/// "proto" : [ "127.0.0.1:19445","192.168.178.88:19445" ], +/// "json" : [ "127.0.0.1:19444","192.168.178.88:19444" ] +/// }, + + /// Color manipulation configuration used to tune the output colors to specific surroundings. /// The configuration contains a list of color-transforms. Each transform contains the /// following fields: @@ -359,6 +374,10 @@ /// The configuration of the effect engine, contains the following items: /// * paths : An array with absolute location(s) of directories with effects /// * bootsequence : The effect selected as 'boot sequence' + /// * effect : name of the effect you want to start. Set to empty if no effect wanted + /// * color : switch to static color after effect is done + /// * duration_ms : duration of boot effect in ms. 0 means effect stays forever + /// * priority : priority of boot effect and static color "effects" : { "paths" : @@ -369,8 +388,10 @@ "bootsequence" : { - "effect" : "Rainbow swirl fast", - "duration_ms" : 3000 + "color" : [0,0,0], + "effect" : "Rainbow swirl fast", + "duration_ms" : 3000, + "priority" : 900 }, /// The configuration for the frame-grabber, contains the following items: diff --git a/dependencies/LightberryHDUSBAPA1021.1.zip b/dependencies/LightberryHDUSBAPA1021.1.zip new file mode 100644 index 00000000..af1527ae Binary files /dev/null and b/dependencies/LightberryHDUSBAPA1021.1.zip differ diff --git a/dependencies/LightberryHDUSBAPA1021.1/LightberryHDUSBAPA1021.1.ino b/dependencies/LightberryHDUSBAPA1021.1/LightberryHDUSBAPA1021.1.ino new file mode 100644 index 00000000..603599c9 --- /dev/null +++ b/dependencies/LightberryHDUSBAPA1021.1/LightberryHDUSBAPA1021.1.ino @@ -0,0 +1,274 @@ +// Arduino "bridge" code between host computer and WS2801-based digital +// RGB LED pixels (e.g. Adafruit product ID #322). Intended for use +// with USB-native boards such as Teensy or Adafruit 32u4 Breakout; +// works on normal serial Arduinos, but throughput is severely limited. +// LED data is streamed, not buffered, making this suitable for larger +// installations (e.g. video wall, etc.) than could otherwise be held +// in the Arduino's limited RAM. + +// Some effort is put into avoiding buffer underruns (where the output +// side becomes starved of data). The WS2801 latch protocol, being +// delay-based, could be inadvertently triggered if the USB bus or CPU +// is swamped with other tasks. This code buffers incoming serial data +// and introduces intentional pauses if there's a threat of the buffer +// draining prematurely. The cost of this complexity is somewhat +// reduced throughput, the gain is that most visual glitches are +// avoided (though ultimately a function of the load on the USB bus and +// host CPU, and out of our control). + +// LED data and clock lines are connected to the Arduino's SPI output. +// On traditional Arduino boards, SPI data out is digital pin 11 and +// clock is digital pin 13. On both Teensy and the 32u4 Breakout, +// data out is pin B2, clock is B1. LEDs should be externally +// powered -- trying to run any more than just a few off the Arduino's +// 5V line is generally a Bad Idea. LED ground should also be +// connected to Arduino ground. + +// -------------------------------------------------------------------- +// This file is part of Adalight. + +// Adalight is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation, either version 3 of +// the License, or (at your option) any later version. + +// Adalight is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. + +// You should have received a copy of the GNU Lesser General Public +// License along with Adalight. If not, see +// . +// -------------------------------------------------------------------- + +#include + +// LED pin for Adafruit 32u4 Breakout Board: +//#define LED_DDR DDRE +//#define LED_PORT PORTE +//#define LED_PIN _BV(PORTE6) +// LED pin for Teensy: +//#define LED_DDR DDRD +//#define LED_PORT PORTD +//#define LED_PIN _BV(PORTD6) +// LED pin for Arduino: +#define LED_DDR DDRB +#define LED_PORT PORTB +#define LED_PIN _BV(PORTB5) + +// A 'magic word' (along with LED count & checksum) precedes each block +// of LED data; this assists the microcontroller in syncing up with the +// host-side software and properly issuing the latch (host I/O is +// likely buffered, making usleep() unreliable for latch). You may see +// an initial glitchy frame or two until the two come into alignment. +// The magic word can be whatever sequence you like, but each character +// should be unique, and frequent pixel values like 0 and 255 are +// avoided -- fewer false positives. The host software will need to +// generate a compatible header: immediately following the magic word +// are three bytes: a 16-bit count of the number of LEDs (high byte +// first) followed by a simple checksum value (high byte XOR low byte +// XOR 0x55). LED data follows, 3 bytes per LED, in order R, G, B, +// where 0 = off and 255 = max brightness. + +static const uint8_t magic[] = {'A', 'd', 'a'}; +#define MAGICSIZE sizeof(magic) +#define HEADERSIZE (MAGICSIZE + 3) + +#define MODE_HEADER 0 +#define MODE_HOLD 1 +#define MODE_DATA 2 + +#define DATA_LED A5 +#define SPI_LED A3 + +// If no serial data is received for a while, the LEDs are shut off +// automatically. This avoids the annoying "stuck pixel" look when +// quitting LED display programs on the host computer. +static const unsigned long serialTimeout = 15000; // 15 seconds + +void setup() +{ + // Dirty trick: the circular buffer for serial data is 256 bytes, + // and the "in" and "out" indices are unsigned 8-bit types -- this + // much simplifies the cases where in/out need to "wrap around" the + // beginning/end of the buffer. Otherwise there'd be a ton of bit- + // masking and/or conditional code every time one of these indices + // needs to change, slowing things down tremendously. + uint8_t + buffer[256], + indexIn = 0, + indexOut = 0, + mode = MODE_HEADER, + hi, lo, chk, i, spiFlag; + int16_t + bytesBuffered = 0, + hold = 0, + c; + int32_t + bytesRemaining; + unsigned long + startTime, + lastByteTime, + lastAckTime, + t; + bool + data_in_led = false, + spi_out_led = false; + + LED_DDR |= LED_PIN; // Enable output for LED + LED_PORT &= ~LED_PIN; // LED off + pinMode(DATA_LED, OUTPUT); //data in led + pinMode(SPI_LED, OUTPUT); //data out led + + Serial.begin(115200); // Teensy/32u4 disregards baud rate; is OK! + + SPI.begin(); + SPI.setBitOrder(MSBFIRST); + SPI.setDataMode(SPI_MODE0); + SPI.setClockDivider(SPI_CLOCK_DIV8); // 2Mhz + + // Issue test pattern to LEDs on startup. This helps verify that + // wiring between the Arduino and LEDs is correct. Not knowing the + // actual number of LEDs connected, this sets all of them (well, up + // to the first 25,000, so as not to be TOO time consuming) to red, + // green, blue, then off. Once you're confident everything is working + // end-to-end, it's OK to comment this out and reprogram the Arduino. + uint8_t testcolor[] = { 0, 0, 0, 255, 0, 0 }; + for (char n = 3; n >= 0; n--) { + for (int i = 0; i < 4; i++) { //Start Frame + for (SPDR = 0x00; !(SPSR & _BV(SPIF)); ); + } + for (c = 0; c < 25000; c++) { + for (SPDR = 0xFF; !(SPSR & _BV(SPIF)); ); //Brightness byte + for (i = 0; i < 3; i++) { + for (SPDR = testcolor[n + i]; !(SPSR & _BV(SPIF)); ); //BGR + } + } + for (int i = 0; i < 4; i++) { //Stop Frame + for (SPDR = 0xFF; !(SPSR & _BV(SPIF)); ); + } + + delay(1); // One millisecond pause = latch + } + digitalWrite(SPI_LED, spi_out_led = !spi_out_led); + + Serial.print("Ada\n"); // Send ACK string to host + + startTime = micros(); + lastByteTime = lastAckTime = millis(); + + // loop() is avoided as even that small bit of function overhead + // has a measurable impact on this code's overall throughput. + + for (;;) { + digitalWrite(DATA_LED, LOW); + digitalWrite(SPI_LED, LOW); + // Implementation is a simple finite-state machine. + // Regardless of mode, check for serial input each time: + t = millis(); + if ((bytesBuffered < 256) && ((c = Serial.read()) >= 0)) { + buffer[indexIn++] = c; + bytesBuffered++; + lastByteTime = lastAckTime = t; // Reset timeout counters + } else { + // No data received. If this persists, send an ACK packet + // to host once every second to alert it to our presence. + if ((t - lastAckTime) > 1000) { + Serial.print("Ada\n"); // Send ACK string to host + lastAckTime = t; // Reset counter + } + // If no data received for an extended time, turn off all LEDs. + if ((t - lastByteTime) > serialTimeout) { + for (i = 0; i < 4; i++) { //Start Frame + for (SPDR = 0x00; !(SPSR & _BV(SPIF)); ); + } + for (c = 0; c < 25000; c++) { + for (SPDR = 0xFF; !(SPSR & _BV(SPIF)); ); //Brightness Byte + for (i = 0; i < 3; i++) { + for (SPDR = 0x00; !(SPSR & _BV(SPIF)); ); //BGR + } + } + for (i = 0; i < 4; i++) { //Stop Frame + for (SPDR = 0xFF; !(SPSR & _BV(SPIF)); ); + } + delay(1); // One millisecond pause = latch + lastByteTime = t; // Reset counter + } + } + + switch (mode) { + + case MODE_HEADER: + + // In header-seeking mode. Is there enough data to check? + if (bytesBuffered >= HEADERSIZE) { + // Indeed. Check for a 'magic word' match. + for (i = 0; (i < MAGICSIZE) && (buffer[indexOut++] == magic[i++]);); + if (i == MAGICSIZE) { + // Magic word matches. Now how about the checksum? + hi = buffer[indexOut++]; + lo = buffer[indexOut++]; + chk = buffer[indexOut++]; + if (chk == (hi ^ lo ^ 0x55)) { + // Checksum looks valid. Get 16-bit LED count, add 1 + // (# LEDs is always > 0) and multiply by 3 for R,G,B. + bytesRemaining = 4L * (256L * (long)hi + (long)lo) + 4L + (256L * (long)hi + (long)lo + 15) / 16; + bytesBuffered -= 3; + spiFlag = 0; // No data out yet + mode = MODE_HOLD; // Proceed to latch wait mode + digitalWrite(DATA_LED, data_in_led = !data_in_led); + } else { + // Checksum didn't match; search resumes after magic word. + indexOut -= 3; // Rewind + } + } // else no header match. Resume at first mismatched byte. + bytesBuffered -= i; + } + break; + + case MODE_HOLD: + + // Ostensibly "waiting for the latch from the prior frame + // to complete" mode, but may also revert to this mode when + // underrun prevention necessitates a delay. + + if ((micros() - startTime) < hold) break; // Still holding; keep buffering + + // Latch/delay complete. Advance to data-issuing mode... + LED_PORT &= ~LED_PIN; // LED off + mode = MODE_DATA; // ...and fall through (no break): + + case MODE_DATA: + digitalWrite(SPI_LED, spi_out_led = !spi_out_led); + while (spiFlag && !(SPSR & _BV(SPIF))); // Wait for prior byte + if (bytesRemaining > 0) { + if (bytesBuffered > 0) { + SPDR = buffer[indexOut++]; // Issue next byte + bytesBuffered--; + bytesRemaining--; + spiFlag = 1; + } + // If serial buffer is threatening to underrun, start + // introducing progressively longer pauses to allow more + // data to arrive (up to a point). + if ((bytesBuffered < 32) && (bytesRemaining > bytesBuffered)) { + startTime = micros(); + hold = 100 + (32 - bytesBuffered) * 10; + mode = MODE_HOLD; + } + } else { + // End of data -- issue latch: + startTime = micros(); + hold = 1000; // Latch duration = 1000 uS + LED_PORT |= LED_PIN; // LED on + mode = MODE_HEADER; // Begin next header search + } + } // end switch + } // end for(;;) +} + +void loop() +{ + // Not used. See note in setup() function. +} diff --git a/deploy/hyperion_imx6.tar.gz.REMOVED.git-id b/deploy/hyperion_imx6.tar.gz.REMOVED.git-id index 294f2909..882af089 100644 --- a/deploy/hyperion_imx6.tar.gz.REMOVED.git-id +++ b/deploy/hyperion_imx6.tar.gz.REMOVED.git-id @@ -1 +1 @@ -3785db40d7232275fbc00b20e627280e0e057f99 \ No newline at end of file +44d552b5adb3dc5af38edd5e6fb208a0dc75d63d \ No newline at end of file diff --git a/deploy/hyperion_rpi.tar.gz.REMOVED.git-id b/deploy/hyperion_rpi.tar.gz.REMOVED.git-id index 0f6903ba..f3692ead 100644 --- a/deploy/hyperion_rpi.tar.gz.REMOVED.git-id +++ b/deploy/hyperion_rpi.tar.gz.REMOVED.git-id @@ -1 +1 @@ -0769228a95e4a0d4b2ed6649f21ddf70b43773d6 \ No newline at end of file +5b0f057a8591d76be009018b302977faeec5159a \ No newline at end of file diff --git a/deploy/hyperion_wetek.tar.gz.REMOVED.git-id b/deploy/hyperion_wetek.tar.gz.REMOVED.git-id index 4a4621a7..4f6eb81f 100644 --- a/deploy/hyperion_wetek.tar.gz.REMOVED.git-id +++ b/deploy/hyperion_wetek.tar.gz.REMOVED.git-id @@ -1 +1 @@ -7e53ee35b7258e7d58d96514701a50014a98da31 \ No newline at end of file +7f0d60e7e0c7075cae5cef69ec5408f3087c00df \ No newline at end of file diff --git a/deploy/hyperion_x32.tar.gz.REMOVED.git-id b/deploy/hyperion_x32.tar.gz.REMOVED.git-id index 24239241..4866065e 100644 --- a/deploy/hyperion_x32.tar.gz.REMOVED.git-id +++ b/deploy/hyperion_x32.tar.gz.REMOVED.git-id @@ -1 +1 @@ -36d03bb18626fa0faea8aabf6426e571eab4f719 \ No newline at end of file +511ab205cce688c5d7151087d8659905402e5015 \ No newline at end of file diff --git a/deploy/hyperion_x64.tar.gz.REMOVED.git-id b/deploy/hyperion_x64.tar.gz.REMOVED.git-id index f6368736..812ded3c 100644 --- a/deploy/hyperion_x64.tar.gz.REMOVED.git-id +++ b/deploy/hyperion_x64.tar.gz.REMOVED.git-id @@ -1 +1 @@ -6f86ceeb6650e527d179cf7153e0beada3b73889 \ No newline at end of file +e26ac1a0bf52bcd54ab00c37ff25d01a457eec9d \ No newline at end of file diff --git a/doc/UDP_led_driver.txt b/doc/UDP_led_driver.txt new file mode 100644 index 00000000..646abe21 --- /dev/null +++ b/doc/UDP_led_driver.txt @@ -0,0 +1,57 @@ +BACKGROUND +--------------------------------------------------------- +The UDP led device type can be used to send LED data over UDP packets. +It was originally designed to support an ESP8266 Wifi module based WS2812 +LED strip controller. + +I've used this to support : +- A string of 600 LEDs as xmas decorations + The effects development kit is great for these scenarios + +- a 61 LED collection of concentric circles + This has been used as a "night light" and a super lo-res + TV + +In each of these cases, the hyperion-remote iOS app is a great way to +control the effects. + + +CONFIG +--------------------------------------------------------- +Simple example for devices that support a raw binary protocol. + "device" : + { + "name" : "MyPi", + "type" : "udp", + "output" : "esp201-0.home:2391", "protocol" : 0, + "rate" : 1000000, + "colorOrder" : "grb" + }, + + + +If you are using an ESP8266/Arduino device with a long LED strip, you chould use this alternate protocol. +The ESP8266/Arduino doesnt support datagram re-assembly so will never see any udp packets greater than 1450. + "device" : + { + "name" : "MyPi", + "type" : "udp", +// "output" : "esp201-0.home:2392", "protocol" : 2, "maxpacket" : 1450, + "rate" : 1000000, + "colorOrder" : "rgb" + }, + +PROTOCOL +--------------------------------------------------------- +Simple UDP packets are sent. + +Packet Format protocol 0: +3 bytes per LED as R, G, B + +Packet Format protocol 2: +0: update number & 0xf; +1: fragment of this update +2: 1st led# of this update - high byte +3: 1st led# of this update - low byte +4..n 3 bytes per LED as R, G, B + diff --git a/doc/datasheets/fadecandy_opc_protocol.md b/doc/datasheets/fadecandy_opc_protocol.md new file mode 100644 index 00000000..53cec6a5 --- /dev/null +++ b/doc/datasheets/fadecandy_opc_protocol.md @@ -0,0 +1,79 @@ +Fadecandy: Open Pixel Control Protocol +====================================== + +The Fadecandy Server (`fcserver`) operates as a bridge between LED controllers attached over USB, and visual effects that communicate via a TCP socket. + +The primary protocol supported by `fcserver` is [Open Pixel Control](http://openpixelcontrol.org), a super simple way to send RGB values over a socket. We support the standard Open Pixel Control commands, as well as some Fadecandy extensions. + +Socket +------ + +Open Pixel Control uses a TCP socket, by default on port 7890. For the best performance, remember to set TCP_NODELAY socket option. + +Command Format +-------------- + +All OPC commands follow the same general format. All multi-byte values in Open Pixel Control are in network byte order, high byte followed by low byte. + +Channel | Command | Length (N) | Data +---------- | --------- | ---------- | -------------------------- +1 byte | 1 byte | 2 bytes | N bytes of message data + +Set Pixel Colors +---------------- + +Video data arrives in a **Set Pixel Colors** command: + +Byte | **Set Pixel Colors** command +------ | -------------------------------- +0 | Channel Number +1 | Command (0x00) +2 - 3 | Data length +4 | Pixel #0, Red +5 | Pixel #0, Green +6 | Pixel #0, Blue +7 | Pixel #1, Red +8 | Pixel #1, Green +9 | Pixel #1, Blue +… | … + +As soon as a complete Set Pixel Colors command is received, a new frame of video will be broadcast simultaneously to all attached Fadecandy devices. + +Set Global Color Correction +--------------------------- + +The color correction data (from the 'color' configuration key) can also be changed at runtime, by sending a new blob of JSON text in a Fadecandy-specific command. Fadecandy's 16-bit System ID for Open Pixel Control's System Exclusive (0xFF) command is **0x0001**. + +Byte | **Set Global Color Correction** command +------ | ------------------------------------------ +0 | Channel Number (0x00, reserved) +1 | Command (0xFF, System Exclusive) +2 - 3 | Data length (JSON Length + 4) +4 - 5 | System ID (0x0001, Fadecandy) +6 - 7 | SysEx ID (0x0001, Set Global Color Correction) +8 - … | JSON Text + +Set Firmware Configuration +-------------------------- + +The firmware supports some runtime configuration options. Any OPC client can send a new firmware configuration packet using this command. If the supplied data is shorter than the firmware's configuration buffer, only the provided bytes will be changed. + +Byte | **Set Firmware Configuration** command +------ | ------------------------------------------ +0 | Channel Number (0x00, reserved) +1 | Command (0xFF, System Exclusive) +2 - 3 | Data length (Configuration Length + 4) +4 - 5 | System ID (0x0001, Fadecandy) +6 - 7 | SysEx ID (0x0002, Set Firmware Configuration) +8 - … | Configuration Data + +Current firmwares support the following configuration options: + +Byte Offset | Bits | Description +----------- | ------ | ------------ +0 | 7 … 4 | (reserved) +0 | 3 | Manual LED control bit +0 | 2 | 0 = LED shows USB activity, 1 = LED under manual control +0 | 1 | Disable keyframe interpolation +0 | 0 | Disable dithering +1 … 62 | 7 … 0 | (reserved) diff --git a/effects/cinema-fade-in.json b/effects/cinema-fade-in.json new file mode 100644 index 00000000..2730c568 --- /dev/null +++ b/effects/cinema-fade-in.json @@ -0,0 +1,10 @@ +{ + "name" : "Cinema brighten lights", + "script" : "fade.py", + "args" : + { + "fade-time" : 5.0, + "color-start" : [ 136, 97, 7 ], + "color-end" : [ 238, 173, 47 ] + } +} diff --git a/effects/cinema-fade-off.json b/effects/cinema-fade-off.json new file mode 100644 index 00000000..7d7c20f8 --- /dev/null +++ b/effects/cinema-fade-off.json @@ -0,0 +1,10 @@ +{ + "name" : "Cinema dim lights", + "script" : "fade.py", + "args" : + { + "fade-time" : 5.0, + "color-start" : [ 238, 173, 47 ], + "color-end" : [ 136, 97, 7 ] + } +} diff --git a/effects/fade.py b/effects/fade.py new file mode 100644 index 00000000..eb233a38 --- /dev/null +++ b/effects/fade.py @@ -0,0 +1,27 @@ +import hyperion, time + +# Get the parameters +fadeTime = float(hyperion.args.get('fade-time', 5.0)) +colorStart = hyperion.args.get('color-start', (255,174,11)) +colorEnd = hyperion.args.get('color-end', (100,100,100)) + +color_step = ( + (colorEnd[0] - colorStart[0]) / 256.0, + (colorEnd[1] - colorStart[1]) / 256.0, + (colorEnd[2] - colorStart[2]) / 256.0 +) + +# fade color +calcChannel = lambda i: min(max(int(colorStart[i] + color_step[i]*step),0),255) +for step in range(256): + if hyperion.abort(): + break + + hyperion.setColor( calcChannel(0),calcChannel(1),calcChannel(2) ) + time.sleep( fadeTime / 256 ) + +# maintain color until effect end +hyperion.setColor(colorEnd[0],colorEnd[1],colorEnd[2]) +while not hyperion.abort(): + time.sleep(1) + diff --git a/effects/police-lights-single.json b/effects/police-lights-single.json new file mode 100644 index 00000000..9f171139 --- /dev/null +++ b/effects/police-lights-single.json @@ -0,0 +1,12 @@ +{ + "name" : "Police Lights Single", + "script" : "police.py", + "args" : + { + "rotation-time" : 1.5, + "color_one" : [ 255, 0, 0 ], + "color_two" : [ 0, 0, 255 ], + "colors_count" : 10, + "reverse" : false + } +} diff --git a/effects/police-lights-solid.json b/effects/police-lights-solid.json new file mode 100644 index 00000000..5eee1f58 --- /dev/null +++ b/effects/police-lights-solid.json @@ -0,0 +1,11 @@ +{ + "name" : "Police Lights Solid", + "script" : "police.py", + "args" : + { + "rotation-time" : 1.0, + "color_one" : [ 255, 0, 0 ], + "color_two" : [ 0, 0, 255 ], + "reverse" : false + } +} diff --git a/effects/police.py b/effects/police.py new file mode 100644 index 00000000..2f4133d2 --- /dev/null +++ b/effects/police.py @@ -0,0 +1,46 @@ +import hyperion +import time +import colorsys + +# Get the parameters +rotationTime = float(hyperion.args.get('rotation-time', 2.0)) +colorOne = hyperion.args.get('color_one', (255,0,0)) +colorTwo = hyperion.args.get('color_two', (0,0,255)) +colorsCount = hyperion.args.get('colors_count', hyperion.ledCount/2) +reverse = bool(hyperion.args.get('reverse', False)) + +# Check parameters +rotationTime = max(0.1, rotationTime) +colorsCount = min(hyperion.ledCount/2, colorsCount) + +# Initialize the led data +hsv1 = colorsys.rgb_to_hsv(colorOne[0]/255.0, colorOne[1]/255.0, colorOne[2]/255.0) +hsv2 = colorsys.rgb_to_hsv(colorTwo[0]/255.0, colorTwo[1]/255.0, colorTwo[2]/255.0) +colorBlack = (0,0,0) +ledData = bytearray() +for i in range(hyperion.ledCount): + if i <= colorsCount: + rgb = colorsys.hsv_to_rgb(hsv1[0], hsv1[1], hsv1[2]) + elif (i >= hyperion.ledCount/2-1) & (i < (hyperion.ledCount/2) + colorsCount): + rgb = colorsys.hsv_to_rgb(hsv2[0], hsv2[1], hsv2[2]) + else: + rgb = colorBlack + ledData += bytearray((int(255*rgb[0]), int(255*rgb[1]), int(255*rgb[2]))) + +# Calculate the sleep time and rotation increment +increment = 3 +sleepTime = rotationTime / hyperion.ledCount +while sleepTime < 0.05: + increment *= 2 + sleepTime *= 2 +increment %= hyperion.ledCount + +# Switch direction if needed +if reverse: + increment = -increment + +# Start the write data loop +while not hyperion.abort(): + hyperion.setColor(ledData) + ledData = ledData[-increment:] + ledData[:-increment] + time.sleep(sleepTime) diff --git a/effects/random.json b/effects/random.json new file mode 100644 index 00000000..4a85b133 --- /dev/null +++ b/effects/random.json @@ -0,0 +1,9 @@ +{ + "name" : "Random", + "script" : "random.py", + "args" : + { + "speed" : 1.0, + "saturation" : 1.0 + } +} diff --git a/effects/random.py b/effects/random.py new file mode 100644 index 00000000..63b56dca --- /dev/null +++ b/effects/random.py @@ -0,0 +1,21 @@ +import hyperion, time, colorsys, random + +# get args +sleepTime = float(hyperion.args.get('speed', 1.0)) +saturation = float(hyperion.args.get('saturation', 1.0)) +ledData = bytearray() + +# Initialize the led data +for i in range(hyperion.ledCount): + ledData += bytearray((0,0,0)) + +# Start the write data loop +while not hyperion.abort(): + hyperion.setColor(ledData) + for i in range(hyperion.ledCount): + if random.randrange(10) == 1: + rgb = colorsys.hsv_to_rgb(random.random(), saturation, random.random()) + ledData[i*3 ] = int(255*rgb[0]) + ledData[i*3+1] = int(255*rgb[1]) + ledData[i*3+2] = int(255*rgb[2]) + time.sleep(sleepTime) diff --git a/effects/running_dots.json b/effects/running_dots.json new file mode 100644 index 00000000..8accd451 --- /dev/null +++ b/effects/running_dots.json @@ -0,0 +1,10 @@ +{ + "name" : "Running dots", + "script" : "running_dots.py", + "args" : + { + "speed" : 1.5, + "whiteLevel" : 100, + "colorLevel" : 230 + } +} diff --git a/effects/running_dots.py b/effects/running_dots.py new file mode 100644 index 00000000..16d8d039 --- /dev/null +++ b/effects/running_dots.py @@ -0,0 +1,43 @@ +import hyperion, time, colorsys, random + +# get options from args +sleepTime = float(hyperion.args.get('speed', 1.5)) * 0.005 +whiteLevel = int(hyperion.args.get('whiteLevel', 0)) +lvl = int(hyperion.args.get('colorLevel', 220)) + +# check value +whiteLevel = min( whiteLevel, 254 ) +lvl = min( lvl, 255 ) + +if whiteLevel >= lvl: + lvl = 255 + +# Initialize the led data +ledData = bytearray() +for i in range(hyperion.ledCount): + ledData += bytearray((0,0,0)) + +runners = [ + { "pos":0, "step": 4, "lvl":lvl}, + { "pos":1, "step": 5, "lvl":lvl}, + { "pos":2, "step": 6, "lvl":lvl}, + { "pos":0, "step": 7, "lvl":lvl}, + { "pos":1, "step": 8, "lvl":lvl}, + { "pos":2, "step": 9, "lvl":lvl}, + #{ "pos":0, "step":10, "lvl":lvl}, + #{ "pos":1, "step":11, "lvl":lvl}, + #{ "pos":2, "step":12, "lvl":lvl}, +] + +# Start the write data loop +counter = 0 +while not hyperion.abort(): + counter += 1 + for r in runners: + if counter % r["step"] == 0: + ledData[r["pos"]] = whiteLevel + r["pos"] = (r["pos"]+3) % (hyperion.ledCount*3) + ledData[r["pos"]] = r["lvl"] + + hyperion.setColor(ledData) + time.sleep(sleepTime) diff --git a/effects/shutdown.json b/effects/shutdown.json new file mode 100644 index 00000000..7e09a13b --- /dev/null +++ b/effects/shutdown.json @@ -0,0 +1,11 @@ +{ + "name" : "System Shutdown", + "script" : "shutdown.py", + "args" : + { + "speed" : 1.2, + "alarm-color" : [255,0,0], + "post-color" : [255,174,11], + "shutdown-enabled" : false + } +} diff --git a/effects/shutdown.py b/effects/shutdown.py new file mode 100644 index 00000000..214af60e --- /dev/null +++ b/effects/shutdown.py @@ -0,0 +1,49 @@ +import hyperion, time, subprocess + +def setPixel(x,y,rgb): + global imageData, width + offset = y*width*3 + x*3 + if offset+2 < len(imageData): + imageData[offset] = rgb[0] + imageData[offset+1] = rgb[1] + imageData[offset+2] = rgb[2] + +# Initialize the led data and args +sleepTime = float(hyperion.args.get('speed', 1.0))*0.5 +alarmColor = hyperion.args.get('alarm-color', (255,0,0)) +postColor = hyperion.args.get('post-color', (255,174,11)) +off = bool(hyperion.args.get('shutdown-enabled', False)) +width = 12 +height = 10 + +imageData = bytearray(height * width * (0,0,0)) + +# Start the write data loop +for i in range(6): + if hyperion.abort(): + off = False + break + if i % 2: + hyperion.setColor(alarmColor[0], alarmColor[1], alarmColor[2]) + else: + hyperion.setColor(0, 0, 0) + time.sleep(sleepTime) + +for y in range(height,0,-1): + if hyperion.abort(): + off = False + break + for x in range(width): + setPixel(x, y-1, alarmColor) + hyperion.setImage(width, height, imageData) + time.sleep(sleepTime) +time.sleep(1) + +for y in range(height): + for x in range(width): + setPixel(x, y, postColor) +hyperion.setImage(width, height, imageData) +time.sleep(2) + +if off and not hyperion.abort(): + subprocess.call("halt") diff --git a/effects/snake.json b/effects/snake.json index 2c7ba395..18f2b17c 100644 --- a/effects/snake.json +++ b/effects/snake.json @@ -1,10 +1,10 @@ { - "name" : "Snake", + "name" : "Snake", "script" : "snake.py", "args" : { - "rotation-time" : 10.0, - "color" : [255, 0, 0], - "percentage" : 25 + "rotation-time" : 12.0, + "color" : [255, 0, 0], + "percentage" : 10 } } diff --git a/effects/snake.py b/effects/snake.py index 2ae5cb37..d8d16561 100644 --- a/effects/snake.py +++ b/effects/snake.py @@ -23,7 +23,7 @@ for i in range(hyperion.ledCount-snakeLeds): ledData += bytearray((0, 0, 0)) for i in range(1,snakeLeds+1): - rgb = colorsys.hsv_to_rgb(hsv[0], hsv[1], hsv[2]/i) + rgb = colorsys.hsv_to_rgb(hsv[0], hsv[1], hsv[2]*(snakeLeds-i)/snakeLeds) ledData += bytearray((int(rgb[0]*255), int(rgb[1]*255), int(rgb[2]*255))) # Calculate the sleep time and rotation increment diff --git a/effects/sparks-color.json b/effects/sparks-color.json new file mode 100644 index 00000000..e4d3649e --- /dev/null +++ b/effects/sparks-color.json @@ -0,0 +1,14 @@ +{ + "name" : "Sparks Color", + "script" : "sparks.py", + "args" : + { + "rotation-time" : 3.0, + "sleep-time" : 0.05, + "brightness" : 1.0, + "saturation" : 1.0, + "reverse" : false, + "color" : [255,255,255], + "random-color" : true + } +} diff --git a/effects/sparks.json b/effects/sparks.json new file mode 100644 index 00000000..0860987e --- /dev/null +++ b/effects/sparks.json @@ -0,0 +1,14 @@ +{ + "name" : "Sparks", + "script" : "sparks.py", + "args" : + { + "rotation-time" : 3.0, + "sleep-time" : 0.05, + "brightness" : 1.0, + "saturation" : 1.0, + "reverse" : false, + "color" : [255,255,255], + "random-color" : false + } +} diff --git a/effects/sparks.py b/effects/sparks.py new file mode 100644 index 00000000..f215ba9a --- /dev/null +++ b/effects/sparks.py @@ -0,0 +1,37 @@ +import hyperion, time, colorsys, random + +# Get the parameters +rotationTime = float(hyperion.args.get('rotation-time', 3.0)) +sleepTime = float(hyperion.args.get('sleep-time', 0.05)) +brightness = float(hyperion.args.get('brightness', 1.0)) +saturation = float(hyperion.args.get('saturation', 1.0)) +reverse = bool(hyperion.args.get('reverse', False)) +color = list(hyperion.args.get('color', (255,255,255))) +randomColor = bool(hyperion.args.get('random-color', False)) + +# Check parameters +rotationTime = max(0.1, rotationTime) +brightness = max(0.0, min(brightness, 1.0)) +saturation = max(0.0, min(saturation, 1.0)) + +# Initialize the led data +ledData = bytearray() +for i in range(hyperion.ledCount): + ledData += bytearray((0, 0, 0)) + +# Start the write data loop +while not hyperion.abort(): + ledData[:] = bytearray(3*hyperion.ledCount) + for i in range(hyperion.ledCount): + if random.random() < 0.005: + + if randomColor: + rgb = colorsys.hsv_to_rgb(random.random(), 1, 1) + for n in range(3): + color[n] = int(rgb[n]*255) + + for n in range(3): + ledData[i*3+n] = color[n] + + hyperion.setColor(ledData) + time.sleep(sleepTime) diff --git a/effects/strobe.py b/effects/strobe.py index 007e6133..39a3a558 100644 --- a/effects/strobe.py +++ b/effects/strobe.py @@ -1,6 +1,4 @@ -import hyperion -import time -import colorsys +import hyperion, time # Get the rotation time color = hyperion.args.get('color', (255,255,255)) @@ -12,13 +10,9 @@ frequency = min(100.0, frequency) # Compute the strobe interval sleepTime = 1.0 / frequency -# Initialize the led data -blackLedsData = bytearray(hyperion.ledCount * ( 0, 0, 0)) -whiteLedsData = bytearray(hyperion.ledCount * color) - # Start the write data loop while not hyperion.abort(): - hyperion.setColor(blackLedsData) + hyperion.setColor(0, 0, 0) time.sleep(sleepTime) - hyperion.setColor(whiteLedsData) + hyperion.setColor(color[0], color[1], color[2]) time.sleep(sleepTime) diff --git a/effects/traces.json b/effects/traces.json new file mode 100644 index 00000000..a2bfdabb --- /dev/null +++ b/effects/traces.json @@ -0,0 +1,8 @@ +{ + "name" : "Color traces", + "script" : "traces.py", + "args" : + { + "speed" : 1.0 + } +} diff --git a/effects/traces.py b/effects/traces.py new file mode 100644 index 00000000..7f6a8dfe --- /dev/null +++ b/effects/traces.py @@ -0,0 +1,34 @@ +import hyperion +import time +import colorsys +import random + +# Initialize the led data +ledData = bytearray() +for i in range(hyperion.ledCount): + ledData += bytearray((0,0,0)) + +sleepTime = float(hyperion.args.get('speed', 1.0)) * 0.004 + +runners = [ +{ "i":0, "pos":0, "c":0, "step":9 , "lvl":255}, +{ "i":1, "pos":0, "c":0, "step":8 , "lvl":255}, +{ "i":2, "pos":0, "c":0, "step":7 , "lvl":255}, +{ "i":0, "pos":0, "c":0, "step":6 , "lvl":100}, +{ "i":1, "pos":0, "c":0, "step":5 , "lvl":100}, +{ "i":2, "pos":0, "c":0, "step":4, "lvl":100}, +] + +# Start the write data loop +while not hyperion.abort(): + for r in runners: + if r["c"] == 0: + #ledData[r["pos"]*3+r["i"]] = 0 + r["c"] = r["step"] + r["pos"] = (r["pos"]+1)%hyperion.ledCount + ledData[r["pos"]*3+r["i"]] = int(r["lvl"]*(0.2+0.8*random.random())) + else: + r["c"] -= 1 + + hyperion.setColor(ledData) + time.sleep(sleepTime) diff --git a/effects/udp.json b/effects/udp.json new file mode 100644 index 00000000..29df0971 --- /dev/null +++ b/effects/udp.json @@ -0,0 +1,8 @@ +{ + "name" : "UDP listener", + "script" : "udp.py", + "args" : + { + "udpPort" : 2391 + } +} diff --git a/effects/udp.py b/effects/udp.py new file mode 100644 index 00000000..f8317a20 --- /dev/null +++ b/effects/udp.py @@ -0,0 +1,47 @@ +import hyperion +import time +import colorsys +import socket +import errno + +# Get the parameters +udpPort = int(hyperion.args.get('udpPort', 2812)) + +UDPSock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) +UDPSock.setblocking(False) + +listen_addr = ("",udpPort) +print "udp.py: bind socket port:",udpPort +UDPSock.bind(listen_addr) + +hyperion.setColor(hyperion.ledCount * bytearray((int(0), int(0), int(0))) ) + +# Start the write data loop +while not hyperion.abort(): + try: + data,addr = UDPSock.recvfrom(4500) +# print data.strip(),len(data),addr + if (len(data)%3 == 0): +# print "numleds ",len(data)/3 + ledData = bytearray() + for i in range(hyperion.ledCount): + if (i<(len(data)/3)): + ledData += data[i*3+0] + ledData += data[i*3+1] + ledData += data[i*3+2] + else: + ledData += bytearray((int(0), int(0), int(0))) + + hyperion.setColor(ledData) + + else: + print "not div 3" + except IOError as e: + if e.errno == errno.EWOULDBLOCK: + pass + else: + print "errno:", e.errno + +print "udp.py: closing socket" +UDPSock.close() + diff --git a/effects/x-mas.json b/effects/x-mas.json new file mode 100644 index 00000000..6c73b645 --- /dev/null +++ b/effects/x-mas.json @@ -0,0 +1,8 @@ +{ + "name" : "X-Mas", + "script" : "x-mas.py", + "args" : + { + "sleepTime" : 0.75 + } +} diff --git a/effects/x-mas.py b/effects/x-mas.py new file mode 100644 index 00000000..697d6f71 --- /dev/null +++ b/effects/x-mas.py @@ -0,0 +1,28 @@ +import hyperion +import time +import colorsys + +# Get the parameters +sleepTime = float(hyperion.args.get('sleepTime', 1.0)) + +# Initialize the led data +ledDataOdd = bytearray() +for i in range(hyperion.ledCount): + if i%2 == 0: + ledDataOdd += bytearray((int(255), int(0), int(0))) + else: + ledDataOdd += bytearray((int(255), int(255), int(255))) + +ledDataEven = bytearray() +for i in range(hyperion.ledCount): + if i%2 == 0: + ledDataEven += bytearray((int(255), int(255), int(255))) + else: + ledDataEven += bytearray((int(255), int(0), int(0))) + +# Start the write data loop +while not hyperion.abort(): + hyperion.setColor(ledDataOdd) + time.sleep(sleepTime) + hyperion.setColor(ledDataEven) + time.sleep(sleepTime) \ No newline at end of file diff --git a/include/blackborder/BlackBorderDetector.h b/include/blackborder/BlackBorderDetector.h index c5ee91e7..7323ebd1 100644 --- a/include/blackborder/BlackBorderDetector.h +++ b/include/blackborder/BlackBorderDetector.h @@ -1,3 +1,4 @@ +//#include #pragma once // Utils includes @@ -49,7 +50,7 @@ namespace hyperion /// Constructs a black-border detector /// @param[in] blackborderThreshold The threshold which the blackborder detector should use /// - BlackBorderDetector(uint8_t blackborderThreshold); + BlackBorderDetector(double threshold); /// /// Performs the actual black-border detection on the given image @@ -58,8 +59,72 @@ namespace hyperion /// /// @return The detected (or not detected) black border info /// + + uint8_t calculateThreshold(double blackborderThreshold); + + /// + /// default detection mode (3lines 4side detection) template BlackBorder process(const Image & image) + { + // test center and 33%, 66% of width/heigth + // 33 and 66 will check left and top + // center will check right and bottom sids + int width = image.width(); + int height = image.height(); + int width33percent = width / 3; + int height33percent = height / 3; + int width66percent = width33percent * 2; + int height66percent = height33percent * 2; + int xCenter = width / 2; + int yCenter = height / 2; + + + int firstNonBlackXPixelIndex = -1; + int firstNonBlackYPixelIndex = -1; + + width--; // remove 1 pixel to get end pixel index + height--; + + // find first X pixel of the image + for (int x = 0; x < width33percent; ++x) + { + const Pixel_T & color1 = image( (width - x), yCenter); // right side center line check + const Pixel_T & color2 = image(x, height33percent); + const Pixel_T & color3 = image(x, height66percent); + if (!isBlack(color1) || !isBlack(color2) || !isBlack(color3)) + { + firstNonBlackXPixelIndex = x; + break; + } + } + + // find first Y pixel of the image + for (int y = 0; y < height33percent; ++y) + { + const Pixel_T & color1 = image(xCenter, (height - y)); // bottom center line check + const Pixel_T & color2 = image(width33percent, y ); + const Pixel_T & color3 = image(width66percent, y); + if (!isBlack(color1) || !isBlack(color2) || !isBlack(color3)) + { + firstNonBlackYPixelIndex = y; + break; + } + } + + // Construct result + BlackBorder detectedBorder; + detectedBorder.unknown = firstNonBlackXPixelIndex == -1 || firstNonBlackYPixelIndex == -1; + detectedBorder.horizontalSize = firstNonBlackYPixelIndex; + detectedBorder.verticalSize = firstNonBlackXPixelIndex; + return detectedBorder; + } + + + /// + /// classic detection mode (topleft single line mode) + template + BlackBorder process_classic(const Image & image) { // only test the topleft third of the image int width = image.width() /3; @@ -112,6 +177,67 @@ namespace hyperion return detectedBorder; } + + /// + /// osd detection mode (find x then y at detected x to avoid changes by osd overlays) + template + BlackBorder process_osd(const Image & image) + { + // find X position at height33 and height66 we check from the left side, Ycenter will check from right side + // then we try to find a pixel at this X position from top and bottom and right side from top + int width = image.width(); + int height = image.height(); + int width33percent = width / 3; + int height33percent = height / 3; + int height66percent = height33percent * 2; + int yCenter = height / 2; + + + int firstNonBlackXPixelIndex = -1; + int firstNonBlackYPixelIndex = -1; + + width--; // remove 1 pixel to get end pixel index + height--; + + // find first X pixel of the image + int x; + for (x = 0; x < width33percent; ++x) + { + const Pixel_T & color1 = image( (width - x), yCenter); // right side center line check + const Pixel_T & color2 = image(x, height33percent); + const Pixel_T & color3 = image(x, height66percent); + if (!isBlack(color1) || !isBlack(color2) || !isBlack(color3)) + { + firstNonBlackXPixelIndex = x; + break; + } + } + + // find first Y pixel of the image + for (int y = 0; y < height33percent; ++y) + { + const Pixel_T & color1 = image(x, y );// left side top check + const Pixel_T & color2 = image(x, (height - y)); // left side bottom check + const Pixel_T & color3 = image( (width - x), y); // right side top check + const Pixel_T & color4 = image( (width - x), (height - y)); // right side bottom check + if (!isBlack(color1) || !isBlack(color2) || !isBlack(color3) || !isBlack(color4)) + { +// std::cout << "y " << y << " lt " << int(isBlack(color1)) << " lb " << int(isBlack(color2)) << " rt " << int(isBlack(color3)) << " rb " << int(isBlack(color4)) << std::endl; + firstNonBlackYPixelIndex = y; + break; + } + } + + // Construct result + BlackBorder detectedBorder; + detectedBorder.unknown = firstNonBlackXPixelIndex == -1 || firstNonBlackYPixelIndex == -1; + detectedBorder.horizontalSize = firstNonBlackYPixelIndex; + detectedBorder.verticalSize = firstNonBlackXPixelIndex; + return detectedBorder; + } + + + private: /// @@ -131,5 +257,6 @@ namespace hyperion private: /// Threshold for the blackborder detector [0 .. 255] const uint8_t _blackborderThreshold; + }; } // end namespace hyperion diff --git a/include/blackborder/BlackBorderProcessor.h b/include/blackborder/BlackBorderProcessor.h index 4d0c4fca..443b1789 100644 --- a/include/blackborder/BlackBorderProcessor.h +++ b/include/blackborder/BlackBorderProcessor.h @@ -1,6 +1,8 @@ #pragma once +// Jsoncpp includes +#include // Local Hyperion includes #include "BlackBorderDetector.h" @@ -23,11 +25,7 @@ namespace hyperion /// outer pixels is blurred (black and color combined due to image scaling)) /// @param[in] blackborderThreshold The threshold which the blackborder detector should use /// - BlackBorderProcessor( - const unsigned unknownFrameCnt, - const unsigned borderFrameCnt, - const unsigned blurRemoveCnt, - uint8_t blackborderThreshold); + BlackBorderProcessor(const Json::Value &blackborderConfig); /// /// Return the current (detected) border @@ -48,7 +46,14 @@ namespace hyperion bool process(const Image & image) { // get the border for the single image - BlackBorder imageBorder = _detector.process(image); + BlackBorder imageBorder; + if (_detectionMode == "default") { + imageBorder = _detector.process(image); + } else if (_detectionMode == "classic") { + imageBorder = _detector.process_classic(image); + } else if (_detectionMode == "osd") { + imageBorder = _detector.process_osd(image); + } // add blur to the border if (imageBorder.horizontalSize > 0) { @@ -80,9 +85,15 @@ namespace hyperion /// The number of horizontal/vertical borders detected before it becomes the current border const unsigned _borderSwitchCnt; + // The number of frames that are "ignored" before a new border gets set as _previousDetectedBorder + const unsigned _maxInconsistentCnt; + /// The number of pixels to increase a detected border for removing blury pixels unsigned _blurRemoveCnt; + /// The border detection mode + const std::string _detectionMode; + /// The blackborder detector BlackBorderDetector _detector; @@ -94,5 +105,8 @@ namespace hyperion /// The number of frame the previous detected border matched the incomming border unsigned _consistentCnt; + /// The number of frame the previous detected border NOT matched the incomming border + unsigned _inconsistentCnt; + }; } // end namespace hyperion diff --git a/libsrc/grabber/amlogic/AmlogicGrabber.h b/include/grabber/AmlogicGrabber.h similarity index 100% rename from libsrc/grabber/amlogic/AmlogicGrabber.h rename to include/grabber/AmlogicGrabber.h diff --git a/include/grabber/AmlogicWrapper.h b/include/grabber/AmlogicWrapper.h index 0ce70b80..c712b60e 100644 --- a/include/grabber/AmlogicWrapper.h +++ b/include/grabber/AmlogicWrapper.h @@ -68,6 +68,9 @@ public slots: /// void setVideoMode(const VideoMode videoMode); +signals: + void emitImage(int priority, const Image & image, const int timeout_ms); + private: /// The update rate [Hz] const int _updateInterval_ms; @@ -91,4 +94,7 @@ private: /// Pointer to Hyperion for writing led values Hyperion * _hyperion; + + // forwarding enabled + bool _forward; }; diff --git a/libsrc/grabber/dispmanx/DispmanxFrameGrabber.h b/include/grabber/DispmanxFrameGrabber.h similarity index 100% rename from libsrc/grabber/dispmanx/DispmanxFrameGrabber.h rename to include/grabber/DispmanxFrameGrabber.h diff --git a/include/grabber/DispmanxWrapper.h b/include/grabber/DispmanxWrapper.h index 4868c1a0..e64adf47 100644 --- a/include/grabber/DispmanxWrapper.h +++ b/include/grabber/DispmanxWrapper.h @@ -68,6 +68,9 @@ public slots: /// void setVideoMode(const VideoMode videoMode); +signals: + void emitImage(int priority, const Image & image, const int timeout_ms); + private: /// The update rate [Hz] const int _updateInterval_ms; @@ -91,4 +94,7 @@ private: /// Pointer to Hyperion for writing led values Hyperion * _hyperion; + + // forwarding enabled + bool _forward; }; diff --git a/include/grabber/FramebufferWrapper.h b/include/grabber/FramebufferWrapper.h index f633ae55..75252d24 100644 --- a/include/grabber/FramebufferWrapper.h +++ b/include/grabber/FramebufferWrapper.h @@ -68,6 +68,9 @@ public slots: /// void setVideoMode(const VideoMode videoMode); +signals: + void emitImage(int priority, const Image & image, const int timeout_ms); + private: /// The update rate [Hz] const int _updateInterval_ms; diff --git a/include/grabber/OsxWrapper.h b/include/grabber/OsxWrapper.h index 39ac1a7e..fa597c67 100644 --- a/include/grabber/OsxWrapper.h +++ b/include/grabber/OsxWrapper.h @@ -69,6 +69,9 @@ public slots: /// void setVideoMode(const VideoMode videoMode); +signals: + void emitImage(int priority, const Image & image, const int timeout_ms); + private: /// The update rate [Hz] const int _updateInterval_ms; diff --git a/include/grabber/V4L2Wrapper.h b/include/grabber/V4L2Wrapper.h index 2a9ece2c..2b8dc74a 100644 --- a/include/grabber/V4L2Wrapper.h +++ b/include/grabber/V4L2Wrapper.h @@ -44,6 +44,7 @@ public slots: signals: void emitColors(int priority, const std::vector &ledColors, const int timeout_ms); + void emitImage(int priority, const Image & image, const int timeout_ms); private slots: void newFrame(const Image & image); diff --git a/include/grabber/X11Grabber.h b/include/grabber/X11Grabber.h index ee857b01..b35031cf 100644 --- a/include/grabber/X11Grabber.h +++ b/include/grabber/X11Grabber.h @@ -7,6 +7,11 @@ // X11 includes #include +#include +#include +#include +#include + class X11Grabber { public: @@ -16,6 +21,8 @@ public: virtual ~X11Grabber(); int open(); + + bool Setup(); Image & grab(); @@ -26,14 +33,24 @@ private: int _cropRight; int _cropTop; int _cropBottom; + + XImage* _xImage; + XShmSegmentInfo _shminfo; /// Reference to the X11 display (nullptr if not opened) - Display * _x11Display; + Display* _x11Display; + Window _window; + XWindowAttributes _windowAttr; unsigned _screenWidth; unsigned _screenHeight; + unsigned _croppedWidth; + unsigned _croppedHeight; Image _image; - + + void freeResources(); + void setupResources(); + int updateScreenDimensions(); }; diff --git a/include/hyperion/ImageProcessor.h b/include/hyperion/ImageProcessor.h index 5049ac03..338c18e7 100644 --- a/include/hyperion/ImageProcessor.h +++ b/include/hyperion/ImageProcessor.h @@ -106,7 +106,7 @@ private: /// @param[in] enableBlackBorderDetector Flag indicating if the blacborder detector should be enabled /// @param[in] blackborderThreshold The threshold which the blackborder detector should use /// - ImageProcessor(const LedString &ledString, bool enableBlackBorderDetector, uint8_t blackborderThreshold); + ImageProcessor(const LedString &ledString, const Json::Value &blackborderConfig); /// /// Performs black-border detection (if enabled) on the given image diff --git a/include/hyperion/ImageProcessorFactory.h b/include/hyperion/ImageProcessorFactory.h index cde64440..e988e283 100644 --- a/include/hyperion/ImageProcessorFactory.h +++ b/include/hyperion/ImageProcessorFactory.h @@ -33,7 +33,7 @@ public: /// @param[in] enableBlackBorderDetector Flag indicating if the blacborder detector should be enabled /// @param[in] blackborderThreshold The threshold which the blackborder detector should use /// - void init(const LedString& ledString, bool enableBlackBorderDetector, double blackborderThreshold); + void init(const LedString& ledString, const Json::Value &blackborderConfig); /// /// Creates a new ImageProcessor. The onwership of the processor is transferred to the caller. @@ -46,9 +46,6 @@ private: /// The Led-string specification LedString _ledString; - /// Flag indicating if the black border detector should be used - bool _enableBlackBorderDetector; - - /// Threshold for the blackborder detector [0 .. 255] - uint8_t _blackborderThreshold; + // Reference to the blackborder json configuration values + Json::Value _blackborderConfig; }; diff --git a/include/hyperion/MessageForwarder.h b/include/hyperion/MessageForwarder.h new file mode 100644 index 00000000..a762ae41 --- /dev/null +++ b/include/hyperion/MessageForwarder.h @@ -0,0 +1,38 @@ +#pragma once + +// STL includes +#include +#include +#include +#include + +// QT includes +#include +#include +#include + +// Utils includes +#include +class MessageForwarder +{ +public: + + struct JsonSlaveAddress { + QHostAddress addr; + quint16 port; + }; + + MessageForwarder(); + ~MessageForwarder(); + + void addJsonSlave(std::string slave); + void addProtoSlave(std::string slave); + + bool protoForwardingEnabled(); + QStringList getProtoSlaves(); + QList getJsonSlaves(); + +private: + QStringList _protoSlaves; + QList _jsonSlaves; +}; diff --git a/include/protoserver/ProtoConnection.h b/include/protoserver/ProtoConnection.h index bb41e1b7..7ecd8f36 100644 --- a/include/protoserver/ProtoConnection.h +++ b/include/protoserver/ProtoConnection.h @@ -7,6 +7,7 @@ #include #include #include +#include #include // hyperion util @@ -19,8 +20,11 @@ /// /// Connection class to setup an connection to the hyperion server and execute commands /// -class ProtoConnection +class ProtoConnection : public QObject { + +Q_OBJECT + public: /// /// Constructor @@ -67,10 +71,6 @@ public: /// void clearAll(); -private: - /// Try to connect to the Hyperion host - void connectToHost(); - /// /// Send a command message and receive its reply /// @@ -78,6 +78,13 @@ private: /// void sendMessage(const proto::HyperionRequest & message); +private slots: + /// Try to connect to the Hyperion host + void connectToHost(); + + +private: + /// /// Parse a reply message /// @@ -99,4 +106,7 @@ private: /// Skip receiving reply messages from Hyperion if set bool _skipReply; + + QTimer _timer; + QAbstractSocket::SocketState _prevSocketState; }; diff --git a/include/protoserver/ProtoServer.h b/include/protoserver/ProtoServer.h index dae57481..520848a9 100644 --- a/include/protoserver/ProtoServer.h +++ b/include/protoserver/ProtoServer.h @@ -6,11 +6,23 @@ // Qt includes #include #include +#include +#include // Hyperion includes #include +// hyperion includes +#include +#include + +// forward decl class ProtoClientConnection; +class ProtoConnection; + +namespace proto { +class HyperionRequest; +} /// /// This class creates a TCP server which accepts connections wich can then send @@ -35,6 +47,9 @@ public: /// uint16_t getPort() const; +public slots: + void sendImageToProtoSlaves(int priority, const Image & image, int duration_ms); + private slots: /// /// Slot which is called when a client tries to create a new connection @@ -47,6 +62,8 @@ private slots: /// void closedConnection(ProtoClientConnection * connection); + void newMessage(const proto::HyperionRequest * message); + private: /// Hyperion instance Hyperion * _hyperion; @@ -56,4 +73,9 @@ private: /// List with open connections QSet _openConnections; + QStringList _forwardClients; + + /// Hyperion proto connection object for forwarding + QList _proxy_connections; + }; diff --git a/include/utils/Image.h b/include/utils/Image.h index 0a8ca31a..ece6155a 100644 --- a/include/utils/Image.h +++ b/include/utils/Image.h @@ -5,6 +5,8 @@ #include #include #include +#include + template class Image @@ -183,6 +185,25 @@ public: { return _pixels; } + + + /// + /// Convert image of any color order to a RGB image. + /// + /// @param[out] image The image that buffers the output + /// + void toRgb(Image& image) + { + image.resize(_width, _height); + const unsigned imageSize = _width * _height; + + for (unsigned idx=0; idx // BlackBorders includes #include +#include using namespace hyperion; -BlackBorderDetector::BlackBorderDetector(uint8_t blackborderThreshold) : - _blackborderThreshold(blackborderThreshold) +BlackBorderDetector::BlackBorderDetector(double threshold) : + _blackborderThreshold(calculateThreshold(threshold)) { // empty } + +uint8_t BlackBorderDetector::calculateThreshold(double threshold) +{ + int rgbThreshold = int(std::ceil(threshold * 255)); + if (rgbThreshold < 0) + rgbThreshold = 0; + else if (rgbThreshold > 255) + rgbThreshold = 255; + + uint8_t blackborderThreshold = uint8_t(rgbThreshold); + + std::cout << "Black border threshold set to " << threshold << " (" << int(blackborderThreshold) << ")" << std::endl; + + return blackborderThreshold; +} diff --git a/libsrc/blackborder/BlackBorderProcessor.cpp b/libsrc/blackborder/BlackBorderProcessor.cpp index ee78efb9..e7fbc205 100644 --- a/libsrc/blackborder/BlackBorderProcessor.cpp +++ b/libsrc/blackborder/BlackBorderProcessor.cpp @@ -1,21 +1,28 @@ +#include +/* +#include +using std::setw; +//*/ // Blackborder includes #include + using namespace hyperion; -BlackBorderProcessor::BlackBorderProcessor(const unsigned unknownFrameCnt, - const unsigned borderFrameCnt, - const unsigned blurRemoveCnt, - uint8_t blackborderThreshold) : - _unknownSwitchCnt(unknownFrameCnt), - _borderSwitchCnt(borderFrameCnt), - _blurRemoveCnt(blurRemoveCnt), - _detector(blackborderThreshold), +BlackBorderProcessor::BlackBorderProcessor(const Json::Value &blackborderConfig) : + _unknownSwitchCnt(blackborderConfig.get("unknownFrameCnt", 600).asUInt()), + _borderSwitchCnt(blackborderConfig.get("borderFrameCnt", 50).asUInt()), + _maxInconsistentCnt(blackborderConfig.get("maxInconsistentCnt", 10).asUInt()), + _blurRemoveCnt(blackborderConfig.get("blurRemoveCnt", 1).asUInt()), + _detectionMode(blackborderConfig.get("mode", "default").asString()), + _detector(blackborderConfig.get("threshold", 0.01).asDouble()), _currentBorder({true, -1, -1}), _previousDetectedBorder({true, -1, -1}), - _consistentCnt(0) + _consistentCnt(0), + _inconsistentCnt(10) { + std::cout << "DETECTION MODE:" << _detectionMode << std::endl; // empty } @@ -26,13 +33,35 @@ BlackBorder BlackBorderProcessor::getCurrentBorder() const bool BlackBorderProcessor::updateBorder(const BlackBorder & newDetectedBorder) { +// the new changes ignore false small borders (no reset of consistance) +// as long as the previous stable state returns within 10 frames +// and will only switch to a new border if it is realy detected stable >50 frames + +// sometimes the grabber delivers "bad" frames with a smaller black border (looks like random number every few frames and even when freezing the image) +// maybe some interferences of the power supply or bad signal causing this effect - not exactly sure what causes it but changing the power supply of the converter significantly increased that "random" effect on my system +// (you can check with the debug output below or if you want i can provide some output logs) +// this "random effect" caused the old algorithm to switch to that smaller border immediatly, resulting in a too small border being detected +// makes it look like the border detectionn is not working - since the new 3 line detection algorithm is more precise this became a problem specialy in dark scenes +// wisc + +// std::cout << "c: " << setw(2) << _currentBorder.verticalSize << " " << setw(2) << _currentBorder.horizontalSize << " p: " << setw(2) << _previousDetectedBorder.verticalSize << " " << setw(2) << _previousDetectedBorder.horizontalSize << " n: " << setw(2) << newDetectedBorder.verticalSize << " " << setw(2) << newDetectedBorder.horizontalSize << " c:i " << setw(2) << _consistentCnt << ":" << setw(2) << _inconsistentCnt << std::endl; + // set the consistency counter if (newDetectedBorder == _previousDetectedBorder) { ++_consistentCnt; + _inconsistentCnt = 0; } else { + ++_inconsistentCnt; + if (_inconsistentCnt <= _maxInconsistentCnt)// only few inconsistent frames + { + //discard the newDetectedBorder -> keep the consistent count for previousDetectedBorder + return false; + } + // the inconsistency threshold is reached + // -> give the newDetectedBorder a chance to proof that its consistent _previousDetectedBorder = newDetectedBorder; _consistentCnt = 0; } @@ -41,6 +70,7 @@ bool BlackBorderProcessor::updateBorder(const BlackBorder & newDetectedBorder) if (_currentBorder == newDetectedBorder) { // No change required + _inconsistentCnt = 0; // we have found a consistent border -> reset _inconsistentCnt return false; } @@ -62,22 +92,7 @@ bool BlackBorderProcessor::updateBorder(const BlackBorder & newDetectedBorder) _currentBorder = newDetectedBorder; borderChanged = true; } - else - { - // apply smaller borders immediately - if (newDetectedBorder.verticalSize < _currentBorder.verticalSize) - { - _currentBorder.verticalSize = newDetectedBorder.verticalSize; - borderChanged = true; - } - - if (newDetectedBorder.horizontalSize < _currentBorder.horizontalSize) - { - _currentBorder.horizontalSize = newDetectedBorder.horizontalSize; - borderChanged = true; - } - } } return borderChanged; -} +} \ No newline at end of file diff --git a/libsrc/boblightserver/BoblightClientConnection.cpp b/libsrc/boblightserver/BoblightClientConnection.cpp index f78d6784..a8e5a810 100644 --- a/libsrc/boblightserver/BoblightClientConnection.cpp +++ b/libsrc/boblightserver/BoblightClientConnection.cpp @@ -59,13 +59,16 @@ void BoblightClientConnection::readData() while(bytes > 0) { // create message string (strip the newline) +#ifdef ENABLE_QT5 + QString message = QString::fromLatin1(_receiveBuffer.data(), bytes-1); +#else QString message = QString::fromAscii(_receiveBuffer.data(), bytes-1); - +#endif // remove message data from buffer _receiveBuffer = _receiveBuffer.mid(bytes); - // handle message - handleMessage(message); + // handle trimmed message + handleMessage(message.trimmed()); // drop messages if the buffer is too full if (_receiveBuffer.size() > 100*1024) @@ -132,9 +135,15 @@ void BoblightClientConnection::handleMessage(const QString & message) { if (messageParts[3] == "rgb" && messageParts.size() == 7) { + // replace decimal comma with decimal point + messageParts[4].replace(',', '.'); + messageParts[5].replace(',', '.'); + messageParts[6].replace(',', '.'); + bool rc1, rc2, rc3; uint8_t red = qMax(0, qMin(255, int(255 * messageParts[4].toFloat(&rc1)))); + // check for correct locale should not be needed anymore - please check! if (!rc1) { // maybe a locale issue. switch to a locale with a comma instead of a dot as decimal seperator (or vice versa) diff --git a/libsrc/boblightserver/CMakeLists.txt b/libsrc/boblightserver/CMakeLists.txt index 1bb30cdb..33be4d17 100644 --- a/libsrc/boblightserver/CMakeLists.txt +++ b/libsrc/boblightserver/CMakeLists.txt @@ -17,7 +17,11 @@ set(BoblightServer_SOURCES ${CURRENT_SOURCE_DIR}/BoblightClientConnection.cpp ) +if(ENABLE_QT5) +qt5_wrap_cpp(BoblightServer_HEADERS_MOC ${BoblightServer_QT_HEADERS}) +else(ENABLE_QT5) qt4_wrap_cpp(BoblightServer_HEADERS_MOC ${BoblightServer_QT_HEADERS}) +endif(ENABLE_QT5) add_library(boblightserver ${BoblightServer_HEADERS} @@ -26,6 +30,10 @@ add_library(boblightserver ${BoblightServer_HEADERS_MOC} ) +if(ENABLE_QT5) +qt5_use_modules(boblightserver Widgets) +endif(ENABLE_QT5) + target_link_libraries(boblightserver hyperion hyperion-utils diff --git a/libsrc/effectengine/CMakeLists.txt b/libsrc/effectengine/CMakeLists.txt index c7c77236..adb430e0 100644 --- a/libsrc/effectengine/CMakeLists.txt +++ b/libsrc/effectengine/CMakeLists.txt @@ -1,5 +1,7 @@ find_package(PythonLibs REQUIRED) +#OpenElec uses 2.7, if you want to compile for OpenElec require 2.7 +#find_package(PythonLibs 2.7 REQUIRED) # Include the python directory. Also include the parent (which is for example /usr/include) # which may be required when it is not includes by the (cross-) compiler by default. @@ -27,9 +29,13 @@ SET(EffectEngineSOURCES set(EffectEngine_RESOURCES ${CURRENT_SOURCE_DIR}/EffectEngine.qrc) +if(ENABLE_QT5) +QT5_WRAP_CPP(EffectEngineHEADERS_MOC ${EffectEngineQT_HEADERS}) +qt5_add_resources(EffectEngine_RESOURCES_RCC ${EffectEngine_RESOURCES} OPTIONS "-no-compress") +else(ENABLE_QT5) QT4_WRAP_CPP(EffectEngineHEADERS_MOC ${EffectEngineQT_HEADERS}) - qt4_add_resources(EffectEngine_RESOURCES_RCC ${EffectEngine_RESOURCES} OPTIONS "-no-compress") +endif(ENABLE_QT5) add_library(effectengine ${EffectEngineHEADERS} @@ -39,6 +45,10 @@ add_library(effectengine ${EffectEngineSOURCES} ) +if(ENABLE_QT5) +qt5_use_modules(effectengine Widgets) +endif(ENABLE_QT5) + target_link_libraries(effectengine hyperion jsoncpp diff --git a/libsrc/effectengine/EffectEngine.cpp b/libsrc/effectengine/EffectEngine.cpp index f7057e21..b9379fad 100644 --- a/libsrc/effectengine/EffectEngine.cpp +++ b/libsrc/effectengine/EffectEngine.cpp @@ -75,7 +75,11 @@ const std::list &EffectEngine::getEffects() const bool EffectEngine::loadEffectDefinition(const std::string &path, const std::string &effectConfigFile, EffectDefinition & effectDefinition) { +#ifdef ENABLE_QT5 + std::string fileName = path + QDir::separator().toLatin1() + effectConfigFile; +#else std::string fileName = path + QDir::separator().toAscii() + effectConfigFile; +#endif std::ifstream file(fileName.c_str()); if (!file.is_open()) @@ -110,7 +114,11 @@ bool EffectEngine::loadEffectDefinition(const std::string &path, const std::stri // setup the definition effectDefinition.name = config["name"].asString(); +#ifdef ENABLE_QT5 + effectDefinition.script = path + QDir::separator().toLatin1() + config["script"].asString(); +#else effectDefinition.script = path + QDir::separator().toAscii() + config["script"].asString(); +#endif effectDefinition.args = config["args"]; // return succes diff --git a/libsrc/grabber/amlogic/AmlogicGrabber.cpp b/libsrc/grabber/amlogic/AmlogicGrabber.cpp index 8eaa9799..25153142 100644 --- a/libsrc/grabber/amlogic/AmlogicGrabber.cpp +++ b/libsrc/grabber/amlogic/AmlogicGrabber.cpp @@ -13,7 +13,7 @@ #include // Local includes -#include "AmlogicGrabber.h" +#include // Flags copied from 'include/linux/amlogic/amports/amvideocap.h' at https://github.com/codesnake/linux-amlogic #define AMVIDEOCAP_IOC_MAGIC 'V' diff --git a/libsrc/grabber/amlogic/AmlogicWrapper.cpp b/libsrc/grabber/amlogic/AmlogicWrapper.cpp index 1dca6764..29022afe 100644 --- a/libsrc/grabber/amlogic/AmlogicWrapper.cpp +++ b/libsrc/grabber/amlogic/AmlogicWrapper.cpp @@ -9,7 +9,7 @@ // Amlogic grabber includes #include -#include "AmlogicGrabber.h" +#include AmlogicWrapper::AmlogicWrapper(const unsigned grabWidth, const unsigned grabHeight, const unsigned updateRate_Hz, Hyperion * hyperion) : @@ -26,6 +26,7 @@ AmlogicWrapper::AmlogicWrapper(const unsigned grabWidth, const unsigned grabHeig // Configure the timer to generate events every n milliseconds _timer.setInterval(_updateInterval_ms); _timer.setSingleShot(false); + _forward = _hyperion->getForwarder()->protoForwardingEnabled(); _processor->setSize(grabWidth, grabHeight); @@ -55,8 +56,14 @@ void AmlogicWrapper::action() return; } - _processor->process(_image, _ledColors); + if ( _forward ) + { + Image image_rgb; + _image.toRgb(image_rgb); + emit emitImage(_priority, image_rgb, _timeout_ms); + } + _processor->process(_image, _ledColors); _hyperion->setColors(_priority, _ledColors, _timeout_ms); } diff --git a/libsrc/grabber/amlogic/CMakeLists.txt b/libsrc/grabber/amlogic/CMakeLists.txt index 447a873d..9ec98bfe 100644 --- a/libsrc/grabber/amlogic/CMakeLists.txt +++ b/libsrc/grabber/amlogic/CMakeLists.txt @@ -7,7 +7,7 @@ SET(CURRENT_SOURCE_DIR ${CMAKE_SOURCE_DIR}/libsrc/grabber/amlogic) SET(AmlogicQT_HEADERS ${CURRENT_HEADER_DIR}/AmlogicWrapper.h) SET(AmlogicHEADERS - ${CURRENT_SOURCE_DIR}/AmlogicGrabber.h + ${CURRENT_HEADER_DIR}/AmlogicGrabber.h ) SET(AmlogicSOURCES @@ -15,7 +15,11 @@ SET(AmlogicSOURCES ${CURRENT_SOURCE_DIR}/AmlogicGrabber.cpp ) +if(ENABLE_QT5) +QT5_WRAP_CPP(AmlogicHEADERS_MOC ${AmlogicQT_HEADERS}) +else(ENABLE_QT5) QT4_WRAP_CPP(AmlogicHEADERS_MOC ${AmlogicQT_HEADERS}) +endif(ENABLE_QT5) add_library(amlogic-grabber ${AmlogicHEADERS} diff --git a/libsrc/grabber/dispmanx/CMakeLists.txt b/libsrc/grabber/dispmanx/CMakeLists.txt index 20714dba..4eb59e64 100644 --- a/libsrc/grabber/dispmanx/CMakeLists.txt +++ b/libsrc/grabber/dispmanx/CMakeLists.txt @@ -9,28 +9,33 @@ SET(CURRENT_SOURCE_DIR ${CMAKE_SOURCE_DIR}/libsrc/grabber/dispmanx) # Group the headers that go through the MOC compiler SET(DispmanxGrabberQT_HEADERS - ${CURRENT_HEADER_DIR}/DispmanxWrapper.h + ${CURRENT_HEADER_DIR}/DispmanxWrapper.h ) SET(DispmanxGrabberHEADERS - ${CURRENT_SOURCE_DIR}/DispmanxFrameGrabber.h + ${CURRENT_HEADER_DIR}/DispmanxFrameGrabber.h ) SET(DispmanxGrabberSOURCES - ${CURRENT_SOURCE_DIR}/DispmanxWrapper.cpp - ${CURRENT_SOURCE_DIR}/DispmanxFrameGrabber.cpp + ${CURRENT_SOURCE_DIR}/DispmanxWrapper.cpp + ${CURRENT_SOURCE_DIR}/DispmanxFrameGrabber.cpp ) -QT4_WRAP_CPP(DispmanxGrabberHEADERS_MOC ${DispmanxGrabberQT_HEADERS}) +if(ENABLE_QT5) + QT5_WRAP_CPP(DispmanxGrabberHEADERS_MOC ${DispmanxGrabberQT_HEADERS}) +else(ENABLE_QT5) + QT4_WRAP_CPP(DispmanxGrabberHEADERS_MOC ${DispmanxGrabberQT_HEADERS}) +endif(ENABLE_QT5) add_library(dispmanx-grabber - ${DispmanxGrabberHEADERS} - ${DispmanxGrabberQT_HEADERS} - ${DispmanxGrabberHEADERS_MOC} - ${DispmanxGrabberSOURCES} + ${DispmanxGrabberHEADERS} + ${DispmanxGrabberQT_HEADERS} + ${DispmanxGrabberHEADERS_MOC} + ${DispmanxGrabberSOURCES} ) target_link_libraries(dispmanx-grabber - hyperion - ${QT_LIBRARIES} - ${BCM_LIBRARIES}) + hyperion + ${QT_LIBRARIES} + ${BCM_LIBRARIES} +) diff --git a/libsrc/grabber/dispmanx/DispmanxFrameGrabber.cpp b/libsrc/grabber/dispmanx/DispmanxFrameGrabber.cpp index 1f6516a4..53397acf 100644 --- a/libsrc/grabber/dispmanx/DispmanxFrameGrabber.cpp +++ b/libsrc/grabber/dispmanx/DispmanxFrameGrabber.cpp @@ -4,7 +4,7 @@ #include // Local includes -#include "DispmanxFrameGrabber.h" +#include "grabber/DispmanxFrameGrabber.h" DispmanxFrameGrabber::DispmanxFrameGrabber(const unsigned width, const unsigned height) : _vc_display(0), diff --git a/libsrc/grabber/dispmanx/DispmanxWrapper.cpp b/libsrc/grabber/dispmanx/DispmanxWrapper.cpp index c9d2b22c..7868ce34 100644 --- a/libsrc/grabber/dispmanx/DispmanxWrapper.cpp +++ b/libsrc/grabber/dispmanx/DispmanxWrapper.cpp @@ -9,7 +9,7 @@ // Dispmanx grabber includes #include -#include "DispmanxFrameGrabber.h" +#include DispmanxWrapper::DispmanxWrapper(const unsigned grabWidth, const unsigned grabHeight, const unsigned updateRate_Hz, Hyperion * hyperion) : @@ -28,6 +28,7 @@ DispmanxWrapper::DispmanxWrapper(const unsigned grabWidth, const unsigned grabHe _timer.setSingleShot(false); _processor->setSize(grabWidth, grabHeight); + _forward = _hyperion->getForwarder()->protoForwardingEnabled(); // Connect the QTimer to this QObject::connect(&_timer, SIGNAL(timeout()), this, SLOT(action())); @@ -51,10 +52,17 @@ void DispmanxWrapper::action() // Grab frame into the allocated image _frameGrabber->grabFrame(_image); - _processor->process(_image, _ledColors); + if ( _forward ) + { + Image image_rgb; + _image.toRgb(image_rgb); + emit emitImage(_priority, image_rgb, _timeout_ms); + } + _processor->process(_image, _ledColors); _hyperion->setColors(_priority, _ledColors, _timeout_ms); } + void DispmanxWrapper::stop() { // Stop the timer, effectivly stopping the process diff --git a/libsrc/grabber/framebuffer/CMakeLists.txt b/libsrc/grabber/framebuffer/CMakeLists.txt index 9a6a12be..dc585f42 100644 --- a/libsrc/grabber/framebuffer/CMakeLists.txt +++ b/libsrc/grabber/framebuffer/CMakeLists.txt @@ -21,7 +21,11 @@ SET(FramebufferGrabberSOURCES ${CURRENT_SOURCE_DIR}/FramebufferFrameGrabber.cpp ) +if(ENABLE_QT5) +QT5_WRAP_CPP(FramebufferGrabberHEADERS_MOC ${FramebufferGrabberQT_HEADERS}) +else(ENABLE_QT5) QT4_WRAP_CPP(FramebufferGrabberHEADERS_MOC ${FramebufferGrabberQT_HEADERS}) +endif(ENABLE_QT5) add_library(framebuffer-grabber ${FramebufferGrabberHEADERS} diff --git a/libsrc/grabber/framebuffer/FramebufferWrapper.cpp b/libsrc/grabber/framebuffer/FramebufferWrapper.cpp index abc6400c..96506d55 100644 --- a/libsrc/grabber/framebuffer/FramebufferWrapper.cpp +++ b/libsrc/grabber/framebuffer/FramebufferWrapper.cpp @@ -46,8 +46,9 @@ void FramebufferWrapper::action() // Grab frame into the allocated image _frameGrabber->grabFrame(_image); - _processor->process(_image, _ledColors); + emit emitImage(_priority, _image, _timeout_ms); + _processor->process(_image, _ledColors); _hyperion->setColors(_priority, _ledColors, _timeout_ms); } void FramebufferWrapper::stop() diff --git a/libsrc/grabber/osx/CMakeLists.txt b/libsrc/grabber/osx/CMakeLists.txt index 05c67b3b..03868eeb 100644 --- a/libsrc/grabber/osx/CMakeLists.txt +++ b/libsrc/grabber/osx/CMakeLists.txt @@ -16,7 +16,11 @@ SET(OsxGrabberSOURCES ${CURRENT_SOURCE_DIR}/OsxFrameGrabber.cpp ) +if(ENABLE_QT5) +QT5_WRAP_CPP(OsxGrabberHEADERS_MOC ${OsxGrabberQT_HEADERS}) +else(ENABLE_QT5) QT4_WRAP_CPP(OsxGrabberHEADERS_MOC ${OsxGrabberQT_HEADERS}) +endif(ENABLE_QT5) add_library(osx-grabber ${OsxGrabberHEADERS} diff --git a/libsrc/grabber/osx/OsxWrapper.cpp b/libsrc/grabber/osx/OsxWrapper.cpp index 6d6f9a00..da8b039e 100644 --- a/libsrc/grabber/osx/OsxWrapper.cpp +++ b/libsrc/grabber/osx/OsxWrapper.cpp @@ -46,8 +46,9 @@ void OsxWrapper::action() // Grab frame into the allocated image _frameGrabber->grabFrame(_image); - _processor->process(_image, _ledColors); + emit emitImage(_priority, _image, _timeout_ms); + _processor->process(_image, _ledColors); _hyperion->setColors(_priority, _ledColors, _timeout_ms); } void OsxWrapper::stop() diff --git a/libsrc/grabber/v4l2/CMakeLists.txt b/libsrc/grabber/v4l2/CMakeLists.txt index 5c7844e0..71de7806 100644 --- a/libsrc/grabber/v4l2/CMakeLists.txt +++ b/libsrc/grabber/v4l2/CMakeLists.txt @@ -16,7 +16,11 @@ SET(V4L2_SOURCES ${CURRENT_SOURCE_DIR}/V4L2Wrapper.cpp ) +if(ENABLE_QT5) +QT5_WRAP_CPP(V4L2_HEADERS_MOC ${V4L2_QT_HEADERS}) +else(ENABLE_QT5) QT4_WRAP_CPP(V4L2_HEADERS_MOC ${V4L2_QT_HEADERS}) +endif(ENABLE_QT5) add_library(v4l2-grabber ${V4L2_HEADERS} @@ -25,6 +29,10 @@ add_library(v4l2-grabber ${V4L2_HEADERS_MOC} ) +if(ENABLE_QT5) +qt5_use_modules(v4l2-grabber Widgets) +endif(ENABLE_QT5) + target_link_libraries(v4l2-grabber hyperion ${QT_LIBRARIES} diff --git a/libsrc/grabber/v4l2/V4L2Wrapper.cpp b/libsrc/grabber/v4l2/V4L2Wrapper.cpp index 76b16ab7..cbccf41b 100644 --- a/libsrc/grabber/v4l2/V4L2Wrapper.cpp +++ b/libsrc/grabber/v4l2/V4L2Wrapper.cpp @@ -94,6 +94,9 @@ void V4L2Wrapper::newFrame(const Image &image) // process the new image _processor->process(image, _ledColors); + // forward to other hyperions + emit emitImage(_priority, image, _timeout_ms); + // send colors to Hyperion emit emitColors(_priority, _ledColors, _timeout_ms); } diff --git a/libsrc/grabber/x11/CMakeLists.txt b/libsrc/grabber/x11/CMakeLists.txt index 869fb4bf..4d8ead6d 100644 --- a/libsrc/grabber/x11/CMakeLists.txt +++ b/libsrc/grabber/x11/CMakeLists.txt @@ -22,7 +22,11 @@ SET(X11_SOURCES ${CURRENT_SOURCE_DIR}/X11Grabber.cpp ) +if(ENABLE_QT5) +QT5_WRAP_CPP(X11_HEADERS_MOC ${X11_QT_HEADERS}) +else(ENABLE_QT5) QT4_WRAP_CPP(X11_HEADERS_MOC ${X11_QT_HEADERS}) +endif(ENABLE_QT5) add_library(x11-grabber ${X11_HEADERS} diff --git a/libsrc/grabber/x11/X11Grabber.cpp b/libsrc/grabber/x11/X11Grabber.cpp index 0787e056..b051fe82 100644 --- a/libsrc/grabber/x11/X11Grabber.cpp +++ b/libsrc/grabber/x11/X11Grabber.cpp @@ -17,82 +17,116 @@ X11Grabber::X11Grabber(int cropLeft, int cropRight, int cropTop, int cropBottom, _x11Display(nullptr), _screenWidth(0), _screenHeight(0), + _croppedWidth(0), + _croppedHeight(0), _image(0,0) { _imageResampler.setHorizontalPixelDecimation(horizontalPixelDecimation); _imageResampler.setVerticalPixelDecimation(verticalPixelDecimation); - _imageResampler.setCropping(0, 0, 0, 0); // cropping is performed by XGetImage + _imageResampler.setCropping(0, 0, 0, 0); // cropping is performed by XShmGetImage } X11Grabber::~X11Grabber() { if (_x11Display != nullptr) { + freeResources(); XCloseDisplay(_x11Display); } } -int X11Grabber::open() +void X11Grabber::freeResources() { - const char * display_name = nullptr; - _x11Display = XOpenDisplay(display_name); + // Cleanup allocated resources of the X11 grab + XShmDetach(_x11Display, &_shminfo); + XDestroyImage(_xImage); + shmdt(_shminfo.shmaddr); + shmctl(_shminfo.shmid, IPC_RMID, 0); +} +void X11Grabber::setupResources() +{ + _xImage = XShmCreateImage(_x11Display, _windowAttr.visual, + _windowAttr.depth, ZPixmap, NULL, &_shminfo, + _croppedWidth, _croppedHeight); + + _shminfo.shmid = shmget(IPC_PRIVATE, _xImage->bytes_per_line * _xImage->height, IPC_CREAT|0777); + _shminfo.shmaddr = _xImage->data = (char*)shmat(_shminfo.shmid,0,0); + _shminfo.readOnly = False; + + XShmAttach(_x11Display, &_shminfo); +} + +bool X11Grabber::Setup() +{ + _x11Display = XOpenDisplay(NULL); if (_x11Display == nullptr) { - std::cerr << "Failed to open the default X11-display" << std::endl; - return -1; - } + std::cerr << "Unable to open display"; + if (getenv("DISPLAY")) + std::cerr << " " << std::string(getenv("DISPLAY")) << std::endl; + else + std::cerr << ". DISPLAY environment variable not set" << std::endl; + return false; + } + + _window = DefaultRootWindow(_x11Display); - return 0; -} + return true; + } Image & X11Grabber::grab() { - if (_x11Display == nullptr) - { - open(); - } - updateScreenDimensions(); - - const unsigned croppedWidth = _screenWidth - _cropLeft - _cropRight; - const unsigned croppedHeight = _screenHeight - _cropTop - _cropBottom; - - // Capture the current screen - XImage * xImage = XGetImage(_x11Display, DefaultRootWindow(_x11Display), _cropLeft, _cropTop, croppedWidth, croppedHeight, AllPlanes, ZPixmap); - if (xImage == nullptr) + + XShmGetImage(_x11Display, _window, _xImage, _cropLeft, _cropTop, 0x00FFFFFF); + if (_xImage == nullptr) { std::cerr << "Grab failed" << std::endl; return _image; } - _imageResampler.processImage(reinterpret_cast(xImage->data), xImage->width, xImage->height, xImage->bytes_per_line, PIXELFORMAT_BGR32, _image); - - // Cleanup allocated resources of the X11 grab - XDestroyImage(xImage); + _imageResampler.processImage(reinterpret_cast(_xImage->data), _xImage->width, _xImage->height, _xImage->bytes_per_line, PIXELFORMAT_BGR32, _image); return _image; } int X11Grabber::updateScreenDimensions() { - XWindowAttributes window_attributes_return; - const Status status = XGetWindowAttributes(_x11Display, DefaultRootWindow(_x11Display), &window_attributes_return); + const Status status = XGetWindowAttributes(_x11Display, _window, &_windowAttr); if (status == 0) { std::cerr << "Failed to obtain window attributes" << std::endl; return -1; } - if (_screenWidth == unsigned(window_attributes_return.width) && _screenHeight == unsigned(window_attributes_return.height)) + if (_screenWidth == unsigned(_windowAttr.width) && _screenHeight == unsigned(_windowAttr.height)) { // No update required return 0; } + std::cout << "Update of screen resolution: [" << _screenWidth << "x" << _screenHeight <<"] => "; - _screenWidth = window_attributes_return.width; - _screenHeight = window_attributes_return.height; + + if (_screenWidth || _screenHeight) + freeResources(); + + _screenWidth = _windowAttr.width; + _screenHeight = _windowAttr.height; + std::cout << "[" << _screenWidth << "x" << _screenHeight <<"]" << std::endl; + + if (_screenWidth > unsigned(_cropLeft + _cropRight)) + _croppedWidth = _screenWidth - _cropLeft - _cropRight; + else + _croppedWidth = _screenWidth; + + if (_screenHeight > unsigned(_cropTop + _cropBottom)) + _croppedHeight = _screenHeight - _cropTop - _cropBottom; + else + _croppedHeight = _screenHeight; + + setupResources(); return 0; } diff --git a/libsrc/hyperion/ImageProcessor.cpp b/libsrc/hyperion/ImageProcessor.cpp index 25784d0a..f83dcc27 100644 --- a/libsrc/hyperion/ImageProcessor.cpp +++ b/libsrc/hyperion/ImageProcessor.cpp @@ -8,10 +8,11 @@ using namespace hyperion; -ImageProcessor::ImageProcessor(const LedString& ledString, bool enableBlackBorderDetector, uint8_t blackborderThreshold) : +//ImageProcessor::ImageProcessor(const LedString& ledString, bool enableBlackBorderDetector, uint8_t blackborderThreshold) : +ImageProcessor::ImageProcessor(const LedString& ledString, const Json::Value & blackborderConfig) : _ledString(ledString), - _enableBlackBorderRemoval(enableBlackBorderDetector), - _borderProcessor(new BlackBorderProcessor(600, 50, 1, blackborderThreshold)), + _enableBlackBorderRemoval(blackborderConfig.get("enable", true).asBool()), + _borderProcessor(new BlackBorderProcessor(blackborderConfig) ), _imageToLeds(nullptr) { // empty diff --git a/libsrc/hyperion/ImageProcessorFactory.cpp b/libsrc/hyperion/ImageProcessorFactory.cpp index a47e532f..e3e4f44c 100644 --- a/libsrc/hyperion/ImageProcessorFactory.cpp +++ b/libsrc/hyperion/ImageProcessorFactory.cpp @@ -1,7 +1,3 @@ - -// STL includes -#include - // Hyperion includes #include #include @@ -13,25 +9,13 @@ ImageProcessorFactory& ImageProcessorFactory::getInstance() return instance; } -void ImageProcessorFactory::init(const LedString& ledString, bool enableBlackBorderDetector, double blackborderThreshold) +void ImageProcessorFactory::init(const LedString& ledString, const Json::Value & blackborderConfig) { _ledString = ledString; - _enableBlackBorderDetector = enableBlackBorderDetector; - - int threshold = int(std::ceil(blackborderThreshold * 255)); - if (threshold < 0) - threshold = 0; - else if (threshold > 255) - threshold = 255; - _blackborderThreshold = uint8_t(threshold); - - if (_enableBlackBorderDetector) - { - std::cout << "Black border threshold set to " << blackborderThreshold << " (" << int(_blackborderThreshold) << ")" << std::endl; - } + _blackborderConfig = blackborderConfig; } ImageProcessor* ImageProcessorFactory::newImageProcessor() const { - return new ImageProcessor(_ledString, _enableBlackBorderDetector, _blackborderThreshold); + return new ImageProcessor(_ledString, _blackborderConfig); } diff --git a/libsrc/hyperion/MessageForwarder.cpp b/libsrc/hyperion/MessageForwarder.cpp new file mode 100644 index 00000000..aa6e4e0c --- /dev/null +++ b/libsrc/hyperion/MessageForwarder.cpp @@ -0,0 +1,51 @@ +// STL includes +#include + +#include + + +MessageForwarder::MessageForwarder() +{ +} + +MessageForwarder::~MessageForwarder() +{ +} + + +void MessageForwarder::addJsonSlave(std::string slave) +{ + QStringList parts = QString(slave.c_str()).split(":"); + if (parts.size() != 2) + throw std::runtime_error(QString("Wrong address: unable to parse address (%1)").arg(slave.c_str()).toStdString()); + + bool ok; + quint16 port = parts[1].toUShort(&ok); + if (!ok) + throw std::runtime_error(QString("Wrong address: Unable to parse the port number (%1)").arg(parts[1]).toStdString()); + + JsonSlaveAddress c; + c.addr = QHostAddress(parts[0]); + c.port = port; + _jsonSlaves << c; +} + +void MessageForwarder::addProtoSlave(std::string slave) +{ + _protoSlaves << QString(slave.c_str()); +} + +QStringList MessageForwarder::getProtoSlaves() +{ + return _protoSlaves; +} + +QList MessageForwarder::getJsonSlaves() +{ + return _jsonSlaves; +} + +bool MessageForwarder::protoForwardingEnabled() +{ + return ! _protoSlaves.empty(); +} diff --git a/libsrc/jsonserver/CMakeLists.txt b/libsrc/jsonserver/CMakeLists.txt index 208c1ed9..d03ed1ea 100644 --- a/libsrc/jsonserver/CMakeLists.txt +++ b/libsrc/jsonserver/CMakeLists.txt @@ -20,10 +20,13 @@ set(JsonServer_SOURCES set(JsonServer_RESOURCES ${CURRENT_SOURCE_DIR}/JsonSchemas.qrc ) - +if(ENABLE_QT5) +qt5_wrap_cpp(JsonServer_HEADERS_MOC ${JsonServer_QT_HEADERS}) +qt5_add_resources(JsonServer_RESOURCES_RCC ${JsonServer_RESOURCES} OPTIONS "-no-compress") +else(ENABLE_QT5) qt4_wrap_cpp(JsonServer_HEADERS_MOC ${JsonServer_QT_HEADERS}) - qt4_add_resources(JsonServer_RESOURCES_RCC ${JsonServer_RESOURCES} OPTIONS "-no-compress") +endif(ENABLE_QT5) add_library(jsonserver ${JsonServer_HEADERS} @@ -34,6 +37,10 @@ add_library(jsonserver ${JsonServer_RESOURCES_RCC} ) +if(ENABLE_QT5) +qt5_use_modules(jsonserver Widgets Network) +endif(ENABLE_QT5) + target_link_libraries(jsonserver hyperion hyperion-utils diff --git a/libsrc/jsonserver/JsonClientConnection.cpp b/libsrc/jsonserver/JsonClientConnection.cpp index 87fb7c1f..e4dbc096 100644 --- a/libsrc/jsonserver/JsonClientConnection.cpp +++ b/libsrc/jsonserver/JsonClientConnection.cpp @@ -16,6 +16,7 @@ // hyperion util includes #include #include +#include #include #include @@ -250,8 +251,27 @@ void JsonClientConnection::handleMessage(const std::string &messageString) handleNotImplemented(); } + +void JsonClientConnection::forwardJsonMessage(const Json::Value & message) +{ + QTcpSocket client; + QList list = _hyperion->getForwarder()->getJsonSlaves(); + + for ( int i=0; iclearall(); @@ -530,10 +558,51 @@ void JsonClientConnection::sendMessage(const Json::Value &message) response.append(serializedReply.c_str(), serializedReply.length()); - _socket->write(response.data(), response.length()); + _socket->write(response.data(), response.length()); } } + +void JsonClientConnection::sendMessage(const Json::Value & message, QTcpSocket * socket) +{ + // serialize message (FastWriter already appends a newline) + std::string serializedMessage = Json::FastWriter().write(message); + + // write message + socket->write(serializedMessage.c_str()); + if (!socket->waitForBytesWritten()) + { + //std::cout << "Error while writing data to host" << std::endl; + return; + } + + // read reply data + QByteArray serializedReply; + while (!serializedReply.contains('\n')) + { + // receive reply + if (!socket->waitForReadyRead()) + { + //std::cout << "Error while reading data from host" << std::endl; + return; + } + + serializedReply += socket->readAll(); + } + int bytes = serializedReply.indexOf('\n') + 1; // Find the end of message + + // parse reply data + Json::Reader jsonReader; + Json::Value reply; + if (!jsonReader.parse(serializedReply.constData(), serializedReply.constData() + bytes, reply)) + { + //std::cout << "Error while parsing reply: invalid json" << std::endl; + return; + } + +} + + void JsonClientConnection::sendSuccessReply() { // create reply diff --git a/libsrc/jsonserver/JsonClientConnection.h b/libsrc/jsonserver/JsonClientConnection.h index 6575388a..01db54b1 100644 --- a/libsrc/jsonserver/JsonClientConnection.h +++ b/libsrc/jsonserver/JsonClientConnection.h @@ -124,6 +124,7 @@ private: /// @param message The JSON message to send /// void sendMessage(const Json::Value & message); + void sendMessage(const Json::Value & message, QTcpSocket * socket); /// /// Send a standard reply indicating success @@ -147,6 +148,11 @@ private: /// void handleWebSocketFrame(); + /// + /// forward json message + /// + void forwardJsonMessage(const Json::Value & message); + private: /// /// Check if a JSON messag is valid according to a given JSON schema diff --git a/libsrc/jsonserver/JsonServer.cpp b/libsrc/jsonserver/JsonServer.cpp index 4efe855c..4cd7f411 100644 --- a/libsrc/jsonserver/JsonServer.cpp +++ b/libsrc/jsonserver/JsonServer.cpp @@ -16,6 +16,14 @@ JsonServer::JsonServer(Hyperion *hyperion, uint16_t port) : throw std::runtime_error("Json server could not bind to port"); } + QList list = _hyperion->getForwarder()->getJsonSlaves(); + for ( int i=0; i #include #include -#include // Linux includes #include @@ -13,30 +12,38 @@ #include "LedDeviceAdalightApa102.h" LedDeviceAdalightApa102::LedDeviceAdalightApa102(const std::string& outputDevice, const unsigned baudrate, int delayAfterConnect_ms) : - LedDeviceAdalight(outputDevice, baudrate, delayAfterConnect_ms), + LedRs232Device(outputDevice, baudrate, delayAfterConnect_ms), _ledBuffer(0), _timer() { + // setup the timer + _timer.setSingleShot(false); + _timer.setInterval(5000); + connect(&_timer, SIGNAL(timeout()), this, SLOT(rewriteLeds())); + + // start the timer + _timer.start(); } //comparing to ws2801 adalight, the following changes were needed: // 1- differnt data frame (4 bytes instead of 3) // 2 - in order to accomodate point 1 above, number of leds sent to adalight is increased by 1/3rd int LedDeviceAdalightApa102::write(const std::vector & ledValues) { + ledCount = ledValues.size(); const unsigned int startFrameSize = 4; - const unsigned int endFrameSize = std::max(((ledValues.size() + 15) / 16), 4); - const unsigned int mLedCount = (ledValues.size() * 4) + startFrameSize + endFrameSize; - if(_ledBuffer.size() != mLedCount){ - _ledBuffer.resize(mLedCount, 0xFF); + const unsigned int endFrameSize = std::max(((ledCount + 15) / 16), 4); + const unsigned int mLedCount = (ledCount * 4) + startFrameSize + endFrameSize; + if(_ledBuffer.size() != mLedCount+6){ + _ledBuffer.resize(mLedCount+6, 0x00); _ledBuffer[0] = 'A'; _ledBuffer[1] = 'd'; _ledBuffer[2] = 'a'; - _ledBuffer[3] = (((unsigned int)(ledValues.size() * 1.33) - 1) >> 8) & 0xFF; // LED count high byte - _ledBuffer[4] = ((unsigned int)(ledValues.size() * 1.33) - 1) & 0xFF; // LED count low byte + _ledBuffer[3] = (((unsigned int)(ledValues.size())) >> 8) & 0xFF; // LED count high byte + _ledBuffer[4] = ((unsigned int)(ledValues.size())) & 0xFF; // LED count low byte _ledBuffer[5] = _ledBuffer[3] ^ _ledBuffer[4] ^ 0x55; // Checksum } - - for (unsigned iLed=1; iLed<=ledValues.size(); iLed++) { + + for (unsigned iLed=1; iLed<=ledCount; iLed++) { const ColorRgb& rgb = ledValues[iLed-1]; _ledBuffer[iLed*4+6] = 0xFF; _ledBuffer[iLed*4+1+6] = rgb.red; @@ -51,4 +58,25 @@ int LedDeviceAdalightApa102::write(const std::vector & ledValues) return writeBytes(_ledBuffer.size(), _ledBuffer.data()); } +int LedDeviceAdalightApa102::switchOff() +{ + for (unsigned iLed=1; iLed<=ledCount; iLed++) { + _ledBuffer[iLed*4+6] = 0xFF; + _ledBuffer[iLed*4+1+6] = 0x00; + _ledBuffer[iLed*4+2+6] = 0x00; + _ledBuffer[iLed*4+3+6] = 0x00; + } + + // restart the timer + _timer.start(); + + // write data + return writeBytes(_ledBuffer.size(), _ledBuffer.data()); + +} + +void LedDeviceAdalightApa102::rewriteLeds() +{ + writeBytes(_ledBuffer.size(), _ledBuffer.data()); +} diff --git a/libsrc/leddevice/LedDeviceAdalightApa102.h b/libsrc/leddevice/LedDeviceAdalightApa102.h index a0a7c89c..eef37662 100644 --- a/libsrc/leddevice/LedDeviceAdalightApa102.h +++ b/libsrc/leddevice/LedDeviceAdalightApa102.h @@ -7,12 +7,12 @@ #include // hyperion incluse -#include "LedDeviceAdalight.h" +#include "LedRs232Device.h" /// /// Implementation of the LedDevice interface for writing to an Adalight led device for APA102. /// -class LedDeviceAdalightApa102 : public LedDeviceAdalight +class LedDeviceAdalightApa102 : public LedRs232Device { Q_OBJECT @@ -32,13 +32,17 @@ public: /// @return Zero on succes else negative /// virtual int write(const std::vector & ledValues); + virtual int switchOff(); - +private slots: + /// Write the last data to the leds again + void rewriteLeds(); + private: /// The buffer containing the packed RGB values std::vector _ledBuffer; - + unsigned int ledCount; /// Timer object which makes sure that led data is written at a minimum rate /// The Adalight device will switch off when it does not receive data at least /// every 15 seconds diff --git a/libsrc/leddevice/LedDeviceFactory.cpp b/libsrc/leddevice/LedDeviceFactory.cpp index 85f665a5..fed6d82d 100755 --- a/libsrc/leddevice/LedDeviceFactory.cpp +++ b/libsrc/leddevice/LedDeviceFactory.cpp @@ -30,6 +30,8 @@ #include "LedDevicePiBlaster.h" #include "LedDeviceSedu.h" #include "LedDeviceTest.h" +#include "LedDeviceFadeCandy.h" +#include "LedDeviceUdp.h" #include "LedDeviceHyperionUsbasp.h" #include "LedDevicePhilipsHue.h" #include "LedDeviceTpm2.h" @@ -127,8 +129,9 @@ LedDevice * LedDeviceFactory::construct(const Json::Value & deviceConfig) { const std::string output = deviceConfig["output"].asString(); const unsigned rate = deviceConfig["rate"].asInt(); + const unsigned latchtime = deviceConfig.get("latchtime",500000).asInt(); - LedDeviceWs2801* deviceWs2801 = new LedDeviceWs2801(output, rate); + LedDeviceWs2801* deviceWs2801 = new LedDeviceWs2801(output, rate, latchtime); deviceWs2801->open(); device = deviceWs2801; @@ -243,6 +246,21 @@ LedDevice * LedDeviceFactory::construct(const Json::Value & deviceConfig) const std::string output = deviceConfig["output"].asString(); device = new LedDeviceTest(output); } + else if (type == "fadecandy") + { + const std::string host = deviceConfig.get("output", "127.0.0.1").asString(); + const uint16_t port = deviceConfig.get("port", 7890).asInt(); + const uint16_t channel = deviceConfig.get("channel", 0).asInt(); + device = new LedDeviceFadeCandy(host, port, channel); + } + else if (type == "udp") + { + const std::string output = deviceConfig["output"].asString(); + const unsigned rate = deviceConfig["rate"].asInt(); + const unsigned protocol = deviceConfig["protocol"].asInt(); + const unsigned maxPacket = deviceConfig["maxpacket"].asInt(); + device = new LedDeviceUdp(output, rate, protocol, maxPacket); + } else if (type == "tpm2") { const std::string output = deviceConfig["output"].asString(); diff --git a/libsrc/leddevice/LedDeviceFadeCandy.cpp b/libsrc/leddevice/LedDeviceFadeCandy.cpp new file mode 100644 index 00000000..2e6a1411 --- /dev/null +++ b/libsrc/leddevice/LedDeviceFadeCandy.cpp @@ -0,0 +1,90 @@ +#include "LedDeviceFadeCandy.h" + +static const signed MAX_NUM_LEDS = 10000; // OPC can handle 21845 leds - in theory, fadecandy device should handle 10000 leds +static const unsigned OPC_SET_PIXELS = 0; // OPC command codes +static const unsigned OPC_HEADER_SIZE = 4; // OPC header size + + +LedDeviceFadeCandy::LedDeviceFadeCandy(const std::string& host, const uint16_t port, const unsigned channel) : + _host(host), _port(port), _channel(channel) +{ + _opc_data.resize( OPC_HEADER_SIZE ); + _opc_data[0] = channel; + _opc_data[1] = OPC_SET_PIXELS; + _opc_data[2] = 0; + _opc_data[3] = 0; +} + + +LedDeviceFadeCandy::~LedDeviceFadeCandy() +{ + _client.close(); +} + + +bool LedDeviceFadeCandy::isConnected() +{ + return _client.state() == QAbstractSocket::ConnectedState; +} + + +bool LedDeviceFadeCandy::tryConnect() +{ + if ( _client.state() == QAbstractSocket::UnconnectedState ) { + _client.connectToHost( _host.c_str(), _port); + if ( _client.waitForConnected(1000) ) + qDebug("fadecandy/opc: connected to %s:%i on channel %i", _host.c_str(), _port, _channel); + } + + return isConnected(); +} + + +int LedDeviceFadeCandy::write( const std::vector & ledValues ) +{ + ssize_t nrLedValues = ledValues.size(); + ssize_t led_data_size = nrLedValues * 3; // 3 color bytes + ssize_t opc_data_size = led_data_size + OPC_HEADER_SIZE; + + if (nrLedValues > MAX_NUM_LEDS) + { + std::cerr << "fadecandy/opc: Invalid attempt to write led values. Not more than " << MAX_NUM_LEDS << " leds are allowed." << std::endl; + return -1; + } + + if ( opc_data_size != _opc_data.size() ) + _opc_data.resize( opc_data_size ); + + _opc_data[2] = led_data_size >> 8; + _opc_data[3] = led_data_size & 0xff; + + uint idx = OPC_HEADER_SIZE; + for (const ColorRgb& color : ledValues) + { + _opc_data[idx ] = unsigned( color.red ); + _opc_data[idx+1] = unsigned( color.green ); + _opc_data[idx+2] = unsigned( color.blue ); + idx += 3; + } + + return ( transferData()<0 ? -1 : 0 ); +} + + +int LedDeviceFadeCandy::transferData() +{ + if ( isConnected() || tryConnect() ) + return _client.write( _opc_data, _opc_data.size() ); + + return -2; +} + + +int LedDeviceFadeCandy::switchOff() +{ + for ( int idx=OPC_HEADER_SIZE; idx < _opc_data.size(); idx++ ) + _opc_data[idx] = 0; + + return ( transferData()<0 ? -1 : 0 ); +} + diff --git a/libsrc/leddevice/LedDeviceFadeCandy.h b/libsrc/leddevice/LedDeviceFadeCandy.h new file mode 100644 index 00000000..ebcd85d9 --- /dev/null +++ b/libsrc/leddevice/LedDeviceFadeCandy.h @@ -0,0 +1,71 @@ +#pragma once + +// STL/Qt includes +#include +#include +#include + +// Leddevice includes +#include + +/// +/// Implementation of the LedDevice interface for sending to +/// fadecandy/opc-server via network by using the 'open pixel control' protocol. +/// +class LedDeviceFadeCandy : public QObject, public LedDevice +{ + Q_OBJECT + +public: + /// + /// Constructs the LedDevice for fadecandy/opc server + /// + /// @param host The ip address/host name of fadecandy/opc server + /// @param port The port to use (fadecandy default is 7890) + /// + LedDeviceFadeCandy(const std::string& host, const uint16_t port, const unsigned channel); + + /// + /// Destructor of the LedDevice; closes the tcp client + /// + virtual ~LedDeviceFadeCandy(); + + /// + /// Writes the led color values to the led-device + /// + /// @param ledValues The color-value per led + /// @return Zero on succes else negative + /// + virtual int write(const std::vector & ledValues); + + /// Switch the leds off + virtual int switchOff(); + + +private: + QTcpSocket _client; + const std::string _host; + const uint16_t _port; + const unsigned _channel; + QByteArray _opc_data; + + /// try to establish connection to opc server, if not connected yet + /// + /// @return true if connection is established + /// + bool tryConnect(); + + /// return the conenction state + /// + /// @return True if connection established + /// + bool isConnected(); + + + /// transfer current opc_data buffer to opc server + /// + /// @return amount of transfered bytes. -1 error while transfering, -2 error while connecting + /// + int transferData(); + +}; diff --git a/libsrc/leddevice/LedDevicePhilipsHue.cpp b/libsrc/leddevice/LedDevicePhilipsHue.cpp index 056e9ead..ee019e18 100755 --- a/libsrc/leddevice/LedDevicePhilipsHue.cpp +++ b/libsrc/leddevice/LedDevicePhilipsHue.cpp @@ -5,10 +5,9 @@ #include // qt includes -#include -#include -#include +#include #include +#include #include @@ -144,14 +143,14 @@ LedDevicePhilipsHue::LedDevicePhilipsHue(const std::string& output, const std::s int transitiontime, std::vector lightIds) : host(output.c_str()), username(username.c_str()), switchOffOnBlack(switchOffOnBlack), transitiontime( transitiontime), lightIds(lightIds) { - http = new QHttp(host); + manager = new QNetworkAccessManager(); timer.setInterval(3000); timer.setSingleShot(true); connect(&timer, SIGNAL(timeout()), this, SLOT(restoreStates())); } LedDevicePhilipsHue::~LedDevicePhilipsHue() { - delete http; + delete manager; } int LedDevicePhilipsHue::write(const std::vector & ledValues) { @@ -215,34 +214,30 @@ int LedDevicePhilipsHue::switchOff() { return 0; } -void LedDevicePhilipsHue::put(QString route, QString content) { - QString url = QString("/api/%1/%2").arg(username).arg(route); - QHttpRequestHeader header("PUT", url); - header.setValue("Host", host); - header.setValue("Accept-Encoding", "identity"); - header.setValue("Connection", "keep-alive"); - header.setValue("Content-Length", QString("%1").arg(content.size())); - QEventLoop loop; - // Connect requestFinished signal to quit slot of the loop. - loop.connect(http, SIGNAL(requestFinished(int, bool)), SLOT(quit())); +void LedDevicePhilipsHue::put(QString route, QString content) { + QString url = QString("http://%1/api/%2/%3").arg(host).arg(username).arg(route); // Perfrom request - http->request(header, content.toAscii()); + QNetworkRequest request(url); + QNetworkReply* reply = manager->put(request, content.toLatin1()); + // Connect finished signal to quit slot of the loop. + QEventLoop loop; + loop.connect(reply, SIGNAL(finished()), SLOT(quit())); // Go into the loop until the request is finished. loop.exec(); } -QByteArray LedDevicePhilipsHue::get(QString route) { - QString url = QString("/api/%1/%2").arg(username).arg(route); - // Event loop to block until request finished. - QEventLoop loop; - // Connect requestFinished signal to quit slot of the loop. - loop.connect(http, SIGNAL(requestFinished(int, bool)), SLOT(quit())); +QByteArray LedDevicePhilipsHue::get(QString route) { + QString url = QString("http://%1/api/%2/%3").arg(host).arg(username).arg(route); // Perfrom request - http->get(url); + QNetworkRequest request(url); + QNetworkReply* reply = manager->get(request); + // Connect requestFinished signal to quit slot of the loop. + QEventLoop loop; + loop.connect(reply, SIGNAL(finished()), SLOT(quit())); // Go into the loop until the request is finished. loop.exec(); - // Read all data of the response. - return http->readAll(); + // Read all data of the response. + return reply->readAll(); } QString LedDevicePhilipsHue::getStateRoute(unsigned int lightId) { diff --git a/libsrc/leddevice/LedDevicePhilipsHue.h b/libsrc/leddevice/LedDevicePhilipsHue.h index 59c0383d..61047062 100755 --- a/libsrc/leddevice/LedDevicePhilipsHue.h +++ b/libsrc/leddevice/LedDevicePhilipsHue.h @@ -6,9 +6,8 @@ // Qt includes #include #include -#include +#include #include - // Leddevice includes #include @@ -164,8 +163,8 @@ private: QString host; /// User name for the API ("newdeveloper") QString username; - /// Qhttp object for sending requests. - QHttp* http; + /// QNetworkAccessManager object for sending requests. + QNetworkAccessManager* manager; /// Use timer to reset lights when we got into "GRABBINGMODE_OFF". QTimer timer; /// diff --git a/libsrc/leddevice/LedDeviceUdp.cpp b/libsrc/leddevice/LedDeviceUdp.cpp new file mode 100644 index 00000000..129eb47f --- /dev/null +++ b/libsrc/leddevice/LedDeviceUdp.cpp @@ -0,0 +1,165 @@ + +// Local-Hyperion includes +#include "LedDeviceUdp.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct addrinfo hints, *servinfo, *p; +//char udpbuffer[1024]; +int sockfd; +int ledprotocol; +int leds_per_pkt; +int update_number; +int fragment_number; + +LedDeviceUdp::LedDeviceUdp(const std::string& output, const unsigned baudrate, const unsigned protocol, const unsigned maxPacket) +//LedDeviceUdp::LedDeviceUdp(const std::string& output, const unsigned baudrate) : +// _ofs(output.empty()?"/home/pi/LedDevice.out":output.c_str()) +{ + + std::string hostname; + std::string port; + ledprotocol = protocol; + leds_per_pkt = ((maxPacket-4)/3); + if (leds_per_pkt <= 0) { + leds_per_pkt = 200; + } + +//printf ("leds_per_pkt is %d\n", leds_per_pkt); + int got_colon=0; + for (unsigned int i=0; iai_next) { + if ((sockfd = socket(p->ai_family, p->ai_socktype, + p->ai_protocol)) == -1) { + perror("talker: socket"); + continue; + } + + break; + } + + if (p == NULL) { + fprintf(stderr, "talker: failed to create socket\n"); + assert(p!=NULL); + } +} + +LedDeviceUdp::~LedDeviceUdp() +{ + // empty +} + +int LedDeviceUdp::write(const std::vector & ledValues) +{ + + char udpbuffer[4096]; + int udpPtr=0; + + update_number++; + update_number &= 0xf; + + if (ledprotocol == 0) { + int i=0; + for (const ColorRgb& color : ledValues) + { + if (i<4090) { + udpbuffer[i++] = color.red; + udpbuffer[i++] = color.green; + udpbuffer[i++] = color.blue; + } + //printf ("c.red %d sz c.red %d\n", color.red, sizeof(color.red)); + } + sendto(sockfd, udpbuffer, i, 0, p->ai_addr, p->ai_addrlen); + } + if (ledprotocol == 1) { +#define MAXLEDperFRAG 450 + int mLedCount = ledValues.size(); + + for (int frag=0; frag<4; frag++) { + udpPtr=0; + udpbuffer[udpPtr++] = 0; + udpbuffer[udpPtr++] = 0; + udpbuffer[udpPtr++] = (frag*MAXLEDperFRAG)/256; // high byte + udpbuffer[udpPtr++] = (frag*MAXLEDperFRAG)%256; // low byte + int ct=0; + for (int this_led = frag*300; ((this_led 7) + sendto(sockfd, udpbuffer, udpPtr, 0, p->ai_addr, p->ai_addrlen); + } + } + if (ledprotocol == 2) { + udpPtr = 0; + unsigned int ledCtr = 0; + fragment_number = 0; + udpbuffer[udpPtr++] = update_number & 0xf; + udpbuffer[udpPtr++] = fragment_number++; + udpbuffer[udpPtr++] = ledCtr/256; // high byte + udpbuffer[udpPtr++] = ledCtr%256; // low byte + + for (const ColorRgb& color : ledValues) + { + if (udpPtr<4090) { + udpbuffer[udpPtr++] = color.red; + udpbuffer[udpPtr++] = color.green; + udpbuffer[udpPtr++] = color.blue; + } + ledCtr++; + if ( (ledCtr % leds_per_pkt == 0) || (ledCtr == ledValues.size()) ) { + sendto(sockfd, udpbuffer, udpPtr, 0, p->ai_addr, p->ai_addrlen); + memset(udpbuffer, 0, sizeof udpbuffer); + udpPtr = 0; + udpbuffer[udpPtr++] = update_number & 0xf; + udpbuffer[udpPtr++] = fragment_number++; + udpbuffer[udpPtr++] = ledCtr/256; // high byte + udpbuffer[udpPtr++] = ledCtr%256; // low byte + } + } + + } + return 0; +} + +int LedDeviceUdp::switchOff() +{ +// return write(std::vector(mLedCount, ColorRgb{0,0,0})); + return 0; +} diff --git a/libsrc/leddevice/LedDeviceUdp.h b/libsrc/leddevice/LedDeviceUdp.h new file mode 100644 index 00000000..c8e2f119 --- /dev/null +++ b/libsrc/leddevice/LedDeviceUdp.h @@ -0,0 +1,44 @@ +#pragma once + +// STL includes0 +#include + +// Leddevice includes +#include + +/// +/// Implementation of the LedDevice that write the led-colors to an +/// ASCII-textfile('/home/pi/LedDevice.out') +/// +class LedDeviceUdp : public LedDevice +{ +public: + /// + /// Constructs the test-device, which opens an output stream to the file + /// + LedDeviceUdp(const std::string& output, const unsigned baudrate, const unsigned protocol, const unsigned maxPacket); + + /// + /// Destructor of this test-device + /// + virtual ~LedDeviceUdp(); + + /// + /// Writes the given led-color values to the output stream + /// + /// @param ledValues The color-value per led + /// + /// @return Zero on success else negative + /// + virtual int write(const std::vector & ledValues); + + /// Switch the leds off + virtual int switchOff(); + +private: + /// The outputstream +// std::ofstream _ofs; + + /// the number of leds (needed when switching off) + size_t mLedCount; +}; diff --git a/libsrc/leddevice/LedDeviceWs2801.cpp b/libsrc/leddevice/LedDeviceWs2801.cpp index 31bb43cd..aaee8cf6 100644 --- a/libsrc/leddevice/LedDeviceWs2801.cpp +++ b/libsrc/leddevice/LedDeviceWs2801.cpp @@ -18,6 +18,13 @@ LedDeviceWs2801::LedDeviceWs2801(const std::string& outputDevice, const unsigned // empty } +LedDeviceWs2801::LedDeviceWs2801(const std::string& outputDevice, const unsigned baudrate, const unsigned latchTime) : + LedSpiDevice(outputDevice, baudrate, latchTime), + mLedCount(0) +{ + // empty +} + int LedDeviceWs2801::write(const std::vector &ledValues) { mLedCount = ledValues.size(); diff --git a/libsrc/leddevice/LedDeviceWs2801.h b/libsrc/leddevice/LedDeviceWs2801.h index e7ff6998..c2231cd7 100644 --- a/libsrc/leddevice/LedDeviceWs2801.h +++ b/libsrc/leddevice/LedDeviceWs2801.h @@ -21,6 +21,10 @@ public: LedDeviceWs2801(const std::string& outputDevice, const unsigned baudrate); + LedDeviceWs2801(const std::string& outputDevice, + const unsigned baudrate, + const unsigned latchTime); + /// /// Writes the led color values to the led-device /// diff --git a/libsrc/protoserver/CMakeLists.txt b/libsrc/protoserver/CMakeLists.txt index 6a9b6d1e..769cc807 100644 --- a/libsrc/protoserver/CMakeLists.txt +++ b/libsrc/protoserver/CMakeLists.txt @@ -34,7 +34,11 @@ protobuf_generate_cpp(ProtoServer_PROTO_SRCS ProtoServer_PROTO_HDRS ${ProtoServer_PROTOS} ) +if(ENABLE_QT5) +qt5_wrap_cpp(ProtoServer_HEADERS_MOC ${ProtoServer_QT_HEADERS}) +else(ENABLE_QT5) qt4_wrap_cpp(ProtoServer_HEADERS_MOC ${ProtoServer_QT_HEADERS}) +endif(ENABLE_QT5) add_library(protoserver ${ProtoServer_HEADERS} @@ -45,6 +49,9 @@ add_library(protoserver ${ProtoServer_PROTO_SRCS} ${ProtoServer_PROTO_HDRS} ) +if(ENABLE_QT5) +qt5_use_modules(protoserver Widgets) +endif(ENABLE_QT5) target_link_libraries(protoserver hyperion diff --git a/libsrc/protoserver/ProtoClientConnection.cpp b/libsrc/protoserver/ProtoClientConnection.cpp index 165315f7..d1f087cd 100644 --- a/libsrc/protoserver/ProtoClientConnection.cpp +++ b/libsrc/protoserver/ProtoClientConnection.cpp @@ -81,6 +81,9 @@ void ProtoClientConnection::socketClosed() void ProtoClientConnection::handleMessage(const proto::HyperionRequest & message) { + // forward messages + emit newMessage(&message); + switch (message.command()) { case proto::HyperionRequest::COLOR: diff --git a/libsrc/protoserver/ProtoClientConnection.h b/libsrc/protoserver/ProtoClientConnection.h index ec7d2e1c..c68f56e0 100644 --- a/libsrc/protoserver/ProtoClientConnection.h +++ b/libsrc/protoserver/ProtoClientConnection.h @@ -6,12 +6,14 @@ // Qt includes #include #include +#include // Hyperion includes #include // proto includes #include "message.pb.h" +#include "protoserver/ProtoConnection.h" class ImageProcessor; @@ -41,6 +43,7 @@ signals: /// @param connection This connection object /// void connectionClosed(ProtoClientConnection * connection); + void newMessage(const proto::HyperionRequest * message); private slots: /// diff --git a/libsrc/protoserver/ProtoConnection.cpp b/libsrc/protoserver/ProtoConnection.cpp index d7a6037e..5eb5e53c 100644 --- a/libsrc/protoserver/ProtoConnection.cpp +++ b/libsrc/protoserver/ProtoConnection.cpp @@ -9,7 +9,8 @@ ProtoConnection::ProtoConnection(const std::string & a) : _socket(), - _skipReply(false) + _skipReply(false), + _prevSocketState(QAbstractSocket::UnconnectedState) { QString address(a.c_str()); QStringList parts = address.split(":"); @@ -29,10 +30,18 @@ ProtoConnection::ProtoConnection(const std::string & a) : // try to connect to host std::cout << "Connecting to Hyperion: " << _host.toStdString() << ":" << _port << std::endl; connectToHost(); + + // start the connection timer + _timer.setInterval(5000); + _timer.setSingleShot(false); + + connect(&_timer,SIGNAL(timeout()), this, SLOT(connectToHost()) ); + _timer.start(); } ProtoConnection::~ProtoConnection() { + _timer.stop(); _socket.close(); } @@ -91,20 +100,37 @@ void ProtoConnection::clearAll() void ProtoConnection::connectToHost() { - _socket.connectToHost(_host, _port); - if (_socket.waitForConnected()) { - std::cout << "Connected to Hyperion host" << std::endl; + // try connection only when + if (_socket.state() == QAbstractSocket::UnconnectedState) + { + _socket.connectToHost(_host, _port); + //_socket.waitForConnected(1000); } } void ProtoConnection::sendMessage(const proto::HyperionRequest &message) { - if (_socket.state() == QAbstractSocket::UnconnectedState) + // print out connection message only when state is changed + if (_socket.state() != _prevSocketState ) { - std::cout << "Currently disconnected: trying to connect to host" << std::endl; - connectToHost(); + switch (_socket.state() ) + { + case QAbstractSocket::UnconnectedState: + std::cout << "No connection to Hyperion: " << _host.toStdString() << ":" << _port << std::endl; + break; + + case QAbstractSocket::ConnectedState: + std::cout << "Connected to Hyperion: " << _host.toStdString() << ":" << _port << std::endl; + break; + + default: + //std::cout << "Connecting to Hyperion: " << _host.toStdString() << ":" << _port << std::endl; + break; + } + _prevSocketState = _socket.state(); } + if (_socket.state() != QAbstractSocket::ConnectedState) { return; diff --git a/libsrc/protoserver/ProtoServer.cpp b/libsrc/protoserver/ProtoServer.cpp index a1afa30c..c4eb82ca 100644 --- a/libsrc/protoserver/ProtoServer.cpp +++ b/libsrc/protoserver/ProtoServer.cpp @@ -2,7 +2,9 @@ #include // project includes +#include #include +#include "protoserver/ProtoConnection.h" #include "ProtoClientConnection.h" ProtoServer::ProtoServer(Hyperion *hyperion, uint16_t port) : @@ -11,6 +13,20 @@ ProtoServer::ProtoServer(Hyperion *hyperion, uint16_t port) : _server(), _openConnections() { + + MessageForwarder * forwarder = hyperion->getForwarder(); + QStringList slaves = forwarder->getProtoSlaves(); + + for (int i = 0; i < slaves.size(); ++i) { + if ( QString("127.0.0.1:%1").arg(port) == slaves.at(i) ) { + throw std::runtime_error("Loop between proto server and forwarder detected. Fix your config!"); + } + + ProtoConnection* p = new ProtoConnection(slaves.at(i).toLocal8Bit().constData()); + p->setSkipReply(true); + _proxy_connections << p; + } + if (!_server.listen(QHostAddress::Any, port)) { throw std::runtime_error("Proto server could not bind to port"); @@ -25,6 +41,9 @@ ProtoServer::~ProtoServer() foreach (ProtoClientConnection * connection, _openConnections) { delete connection; } + + while (!_proxy_connections.isEmpty()) + delete _proxy_connections.takeFirst(); } uint16_t ProtoServer::getPort() const @@ -44,9 +63,23 @@ void ProtoServer::newConnection() // register slot for cleaning up after the connection closed connect(connection, SIGNAL(connectionClosed(ProtoClientConnection*)), this, SLOT(closedConnection(ProtoClientConnection*))); + connect(connection, SIGNAL(newMessage(const proto::HyperionRequest*)), this, SLOT(newMessage(const proto::HyperionRequest*))); + } } +void ProtoServer::newMessage(const proto::HyperionRequest * message) +{ + for (int i = 0; i < _proxy_connections.size(); ++i) + _proxy_connections.at(i)->sendMessage(*message); +} + +void ProtoServer::sendImageToProtoSlaves(int priority, const Image & image, int duration_ms) +{ + for (int i = 0; i < _proxy_connections.size(); ++i) + _proxy_connections.at(i)->setImage(image, priority, duration_ms); +} + void ProtoServer::closedConnection(ProtoClientConnection *connection) { std::cout << "Proto connection closed" << std::endl; diff --git a/libsrc/xbmcvideochecker/CMakeLists.txt b/libsrc/xbmcvideochecker/CMakeLists.txt index 77ee2f34..f6e71b00 100644 --- a/libsrc/xbmcvideochecker/CMakeLists.txt +++ b/libsrc/xbmcvideochecker/CMakeLists.txt @@ -15,7 +15,11 @@ SET(XBMCVideoChecker_SOURCES ${CURRENT_SOURCE_DIR}/XBMCVideoChecker.cpp ) +if(ENABLE_QT5) +QT5_WRAP_CPP(XBMCVideoChecker_HEADERS_MOC ${XBMCVideoChecker_QT_HEADERS}) +else(ENABLE_QT5) QT4_WRAP_CPP(XBMCVideoChecker_HEADERS_MOC ${XBMCVideoChecker_QT_HEADERS}) +endif(ENABLE_QT5) add_library(xbmcvideochecker ${XBMCVideoChecker_HEADERS} @@ -24,6 +28,10 @@ add_library(xbmcvideochecker ${XBMCVideoChecker_SOURCES} ) +if(ENABLE_QT5) +qt5_use_modules(xbmcvideochecker Widgets) +endif(ENABLE_QT5) + target_link_libraries(xbmcvideochecker hyperion ${QT_LIBRARIES}) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 2a291850..f55af750 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -15,4 +15,9 @@ if(ENABLE_PROTOBUF) if(ENABLE_X11) add_subdirectory(hyperion-x11) endif() + + if(ENABLE_DISPMANX) + add_subdirectory(hyperion-dispmanx) + endif() + endif() diff --git a/src/hyperion-aml/AmlogicWrapper.cpp b/src/hyperion-aml/AmlogicWrapper.cpp new file mode 100644 index 00000000..e4c1f7f1 --- /dev/null +++ b/src/hyperion-aml/AmlogicWrapper.cpp @@ -0,0 +1,38 @@ + +// Hyperion-AmLogic includes +#include "AmlogicWrapper.h" + +AmlogicWrapper::AmlogicWrapper(const unsigned grabWidth, const unsigned grabHeight, const unsigned updateRate_Hz) : + _timer(this), + _grabber(grabWidth, grabHeight) +{ + _timer.setSingleShot(false); + _timer.setInterval(updateRate_Hz); + + // Connect capturing to the timeout signal of the timer + connect(&_timer, SIGNAL(timeout()), this, SLOT(capture())); +} + +const Image & AmlogicWrapper::getScreenshot() +{ + capture(); + return _screenshot_rgb; +} + +void AmlogicWrapper::start() +{ + _timer.start(); +} + +void AmlogicWrapper::stop() +{ + _timer.stop(); +} + +void AmlogicWrapper::capture() +{ + _grabber.grabFrame(_screenshot); + _screenshot.toRgb(_screenshot_rgb); + + emit sig_screenshot(_screenshot_rgb); +} diff --git a/src/hyperion-aml/AmlogicWrapper.h b/src/hyperion-aml/AmlogicWrapper.h new file mode 100644 index 00000000..3f5b0bc8 --- /dev/null +++ b/src/hyperion-aml/AmlogicWrapper.h @@ -0,0 +1,43 @@ + +// QT includes +#include + +// Hyperion-Dispmanx includes +#include + +class AmlogicWrapper : public QObject +{ + Q_OBJECT +public: + AmlogicWrapper(const unsigned grabWidth, const unsigned grabHeight, const unsigned updateRate_Hz); + + const Image & getScreenshot(); + + /// + /// Starts the timed capturing of screenshots + /// + void start(); + + void stop(); + +signals: + void sig_screenshot(const Image & screenshot); + +private slots: + /// + /// Performs a single screenshot capture and publishes the capture screenshot on the screenshot signal. + /// + void capture(); + +private: + /// The QT timer to generate capture-publish events + QTimer _timer; + + /// The grabber for creating screenshots + AmlogicGrabber _grabber; + + // image buffers + Image _screenshot_rgb; + Image _screenshot; + +}; diff --git a/src/hyperion-aml/CMakeLists.txt b/src/hyperion-aml/CMakeLists.txt index d6f88a90..cd27c046 100644 --- a/src/hyperion-aml/CMakeLists.txt +++ b/src/hyperion-aml/CMakeLists.txt @@ -14,6 +14,7 @@ include_directories( ) set(Hyperion_AML_QT_HEADERS + AmlogicWrapper.h ) set(Hyperion_AML_HEADERS @@ -21,9 +22,14 @@ set(Hyperion_AML_HEADERS set(Hyperion_AML_SOURCES hyperion-aml.cpp + AmlogicWrapper.cpp ) -QT4_WRAP_CPP(Hyperion_AML_HEADERS_MOC ${Hyperion_AML_QT_HEADERS}) +if(ENABLE_QT5) + QT5_WRAP_CPP(Hyperion_AML_HEADERS_MOC ${Hyperion_AML_QT_HEADERS}) +else(ENABLE_QT5) + QT4_WRAP_CPP(Hyperion_AML_HEADERS_MOC ${Hyperion_AML_QT_HEADERS}) +endif(ENABLE_QT5) add_executable(hyperion-amlogic ${Hyperion_AML_HEADERS} diff --git a/src/hyperion-aml/hyperion-aml.cpp b/src/hyperion-aml/hyperion-aml.cpp index 13da6686..cca5e688 100644 --- a/src/hyperion-aml/hyperion-aml.cpp +++ b/src/hyperion-aml/hyperion-aml.cpp @@ -7,16 +7,16 @@ // getoptPlusPLus includes #include -#include "../../libsrc/grabber/amlogic/AmlogicGrabber.h" +#include +#include "AmlogicWrapper.h" using namespace vlofgren; // save the image as screenshot -void saveScreenshot(const char * filename, const Image & image) +void saveScreenshot(const char * filename, const Image & image) { // store as PNG QImage pngImage((const uint8_t *) image.memptr(), image.width(), image.height(), 3*image.width(), QImage::Format_RGB888); - pngImage = pngImage.rgbSwapped(); pngImage.save(filename); } @@ -30,16 +30,17 @@ int main(int argc, char ** argv) OptionsParser optionParser("X11 capture application for Hyperion"); ParameterSet & parameters = optionParser.getParameters(); - //IntParameter & argFps = parameters.add ('f', "framerate", "Capture frame rate [default=10]"); + IntParameter & argFps = parameters.add ('f', "framerate", "Capture frame rate [default=10]"); IntParameter & argWidth = parameters.add (0x0, "width", "Width of the captured image [default=128]"); IntParameter & argHeight = parameters.add (0x0, "height", "Height of the captured image [default=128]"); SwitchParameter<> & argScreenshot = parameters.add> (0x0, "screenshot", "Take a single screenshot, save it to file and quit"); StringParameter & argAddress = parameters.add ('a', "address", "Set the address of the hyperion server [default: 127.0.0.1:19445]"); IntParameter & argPriority = parameters.add ('p', "priority", "Use the provided priority channel (the lower the number, the higher the priority) [default: 800]"); - //SwitchParameter<> & argSkipReply = parameters.add> (0x0, "skip-reply", "Do not receive and check reply messages from Hyperion"); + SwitchParameter<> & argSkipReply = parameters.add> (0x0, "skip-reply", "Do not receive and check reply messages from Hyperion"); SwitchParameter<> & argHelp = parameters.add> ('h', "help", "Show this help message and exit"); // set defaults + argFps.setDefault(10); argWidth.setDefault(160); argHeight.setDefault(160); argAddress.setDefault("127.0.0.1:19445"); @@ -57,29 +58,36 @@ int main(int argc, char ** argv) int width = argWidth.getValue(); int height = argHeight.getValue(); - if (width < 160 || height < 60) + if (width < 160 || height < 160) { std::cout << "Minimum width and height is 160" << std::endl; width = std::max(160, width); height = std::max(160, height); } + + int grabInterval = 1000 / argFps.getValue(); + AmlogicWrapper amlWrapper(argWidth.getValue(),argHeight.getValue(),grabInterval); + if (argScreenshot.isSet()) { - - // Create the grabber - AmlogicGrabber amlGrabber(width, height); - // Capture a single screenshot and finish - Image screenshot; - amlGrabber.grabFrame(screenshot); + const Image & screenshot = amlWrapper.getScreenshot(); saveScreenshot("screenshot.png", screenshot); } else { - // TODO[TvdZ]: Implement the proto-client mechanisme - std::cerr << "The PROTO-interface has not been implemented yet" << std::endl; - } + // Create the Proto-connection with hyperiond + ProtoConnectionWrapper protoWrapper(argAddress.getValue(), argPriority.getValue(), 1000, argSkipReply.isSet()); + // Connect the screen capturing to the proto processing + QObject::connect(&amlWrapper, SIGNAL(sig_screenshot(const Image &)), &protoWrapper, SLOT(receiveImage(Image))); + + // Start the capturing + amlWrapper.start(); + + // Start the application + app.exec(); + } } catch (const std::runtime_error & e) { diff --git a/src/hyperion-dispmanx/CMakeLists.txt b/src/hyperion-dispmanx/CMakeLists.txt new file mode 100644 index 00000000..130cb661 --- /dev/null +++ b/src/hyperion-dispmanx/CMakeLists.txt @@ -0,0 +1,65 @@ +# Configure minimum CMAKE version +cmake_minimum_required(VERSION 2.8) + +# Set the project name +project(hyperion-dispmanx) + +if(ENABLE_QT5) + find_package(Qt5Widgets REQUIRED) +else(ENABLE_QT5) + # find Qt4 + find_package(Qt4 REQUIRED QtCore QtGui QtNetwork ) +endif(ENABLE_QT5) + +# Find the BCM-package (VC control) +find_package(BCM REQUIRED) + +include_directories( + ${CMAKE_CURRENT_BINARY_DIR}/../../libsrc/protoserver + ${QT_INCLUDES} + ${BCM_INCLUDE_DIRS} + ${PROTOBUF_INCLUDE_DIRS} +) + +set(Hyperion_Dispmanx_QT_HEADERS + DispmanxWrapper.h) + +set(Hyperion_Dispmanx_HEADERS +) + +set(Hyperion_Dispmanx_SOURCES + hyperion-dispmanx.cpp + DispmanxWrapper.cpp +) + +if(ENABLE_QT5) + QT5_WRAP_CPP(Hyperion_Dispmanx_HEADERS_MOC ${Hyperion_Dispmanx_QT_HEADERS}) +else(ENABLE_QT5) + QT4_WRAP_CPP(Hyperion_Dispmanx_HEADERS_MOC ${Hyperion_Dispmanx_QT_HEADERS}) +endif(ENABLE_QT5) + +add_executable( ${PROJECT_NAME} + ${Hyperion_Dispmanx_HEADERS} + ${Hyperion_Dispmanx_SOURCES} + ${Hyperion_Dispmanx_HEADERS_MOC} +) + +target_link_libraries( ${PROJECT_NAME} + getoptPlusPlus + blackborder + hyperion-utils + protoserver + dispmanx-grabber + ${Dispmanx_LIBRARIES} + pthread +) + +if(ENABLE_QT5) + qt5_use_modules(hyperion-dispmanx Widgets Core Gui Network) +else(ENABLE_QT5) + qt4_use_modules(hyperion-dispmanx + Core + Gui + Network + ) +endif(ENABLE_QT5) diff --git a/src/hyperion-dispmanx/DispmanxWrapper.cpp b/src/hyperion-dispmanx/DispmanxWrapper.cpp new file mode 100644 index 00000000..7d8702d2 --- /dev/null +++ b/src/hyperion-dispmanx/DispmanxWrapper.cpp @@ -0,0 +1,38 @@ + +// Hyperion-Dispmanx includes +#include "DispmanxWrapper.h" + +DispmanxWrapper::DispmanxWrapper(const unsigned grabWidth, const unsigned grabHeight, const unsigned updateRate_Hz) : + _timer(this), + _grabber(grabWidth, grabHeight) +{ + _timer.setSingleShot(false); + _timer.setInterval(updateRate_Hz); + + // Connect capturing to the timeout signal of the timer + connect(&_timer, SIGNAL(timeout()), this, SLOT(capture())); +} + +const Image & DispmanxWrapper::getScreenshot() +{ + capture(); + return _screenshot_rgb; +} + +void DispmanxWrapper::start() +{ + _timer.start(); +} + +void DispmanxWrapper::stop() +{ + _timer.stop(); +} + +void DispmanxWrapper::capture() +{ + _grabber.grabFrame(_screenshot); + _screenshot.toRgb(_screenshot_rgb); + + emit sig_screenshot(_screenshot_rgb); +} diff --git a/src/hyperion-dispmanx/DispmanxWrapper.h b/src/hyperion-dispmanx/DispmanxWrapper.h new file mode 100644 index 00000000..e8526b02 --- /dev/null +++ b/src/hyperion-dispmanx/DispmanxWrapper.h @@ -0,0 +1,42 @@ + +// QT includes +#include + +// Hyperion-Dispmanx includes +#include + +class DispmanxWrapper : public QObject +{ + Q_OBJECT +public: + DispmanxWrapper(const unsigned grabWidth, const unsigned grabHeight, const unsigned updateRate_Hz); + + const Image & getScreenshot(); + + /// + /// Starts the timed capturing of screenshots + /// + void start(); + + void stop(); + +signals: + void sig_screenshot(const Image & screenshot); + +private slots: + /// + /// Performs a single screenshot capture and publishes the capture screenshot on the screenshot + /// signal. + /// + void capture(); + +private: + /// The QT timer to generate capture-publish events + QTimer _timer; + + /// The grabber for creating screenshots + DispmanxFrameGrabber _grabber; + Image _screenshot_rgb; + Image _screenshot; + +}; diff --git a/src/hyperion-dispmanx/hyperion-dispmanx.cpp b/src/hyperion-dispmanx/hyperion-dispmanx.cpp new file mode 100644 index 00000000..abf78c8c --- /dev/null +++ b/src/hyperion-dispmanx/hyperion-dispmanx.cpp @@ -0,0 +1,92 @@ + +// QT includes +#include +#include + +// getoptPlusPLus includes +#include + +#include +#include "DispmanxWrapper.h" + +using namespace vlofgren; + +// save the image as screenshot +void saveScreenshot(const char * filename, const Image & image) +{ + // store as PNG + QImage pngImage((const uint8_t *) image.memptr(), image.width(), image.height(), 3*image.width(), QImage::Format_RGB888); + pngImage.save(filename); +} + +int main(int argc, char ** argv) +{ + QCoreApplication app(argc, argv); + + try + { + // create the option parser and initialize all parameters + OptionsParser optionParser("Dispmanx capture application for Hyperion"); + ParameterSet & parameters = optionParser.getParameters(); + + IntParameter & argFps = parameters.add ('f', "framerate", "Capture frame rate [default=10]"); + IntParameter & argWidth = parameters.add (0x0, "width", "The width of the grabbed frames [pixels]"); + IntParameter & argHeight = parameters.add (0x0, "height", "The height of the grabbed frames"); + + SwitchParameter<> & argScreenshot = parameters.add> (0x0, "screenshot", "Take a single screenshot, save it to file and quit"); + StringParameter & argAddress = parameters.add ('a', "address", "Set the address of the hyperion server [default: 127.0.0.1:19445]"); + IntParameter & argPriority = parameters.add ('p', "priority", "Use the provided priority channel (the lower the number, the higher the priority) [default: 800]"); + SwitchParameter<> & argSkipReply = parameters.add> (0x0, "skip-reply", "Do not receive and check reply messages from Hyperion"); + SwitchParameter<> & argHelp = parameters.add> ('h', "help", "Show this help message and exit"); + + // set defaults + argFps.setDefault(10); + argWidth.setDefault(64); + argHeight.setDefault(64); + argAddress.setDefault("127.0.0.1:19445"); + argPriority.setDefault(800); + + // parse all options + optionParser.parse(argc, const_cast(argv)); + + // check if we need to display the usage. exit if we do. + if (argHelp.isSet()) + { + optionParser.usage(); + return 0; + } + + // Create the dispmanx grabbing stuff + int grabInterval = 1000 / argFps.getValue(); + DispmanxWrapper dispmanxWrapper(argWidth.getValue(),argHeight.getValue(),grabInterval); + + if (argScreenshot.isSet()) + { + // Capture a single screenshot and finish + const Image & screenshot = dispmanxWrapper.getScreenshot(); + saveScreenshot("screenshot.png", screenshot); + } + else + { + // Create the Proto-connection with hyperiond + ProtoConnectionWrapper protoWrapper(argAddress.getValue(), argPriority.getValue(), 1000, argSkipReply.isSet()); + + // Connect the screen capturing to the proto processing + QObject::connect(&dispmanxWrapper, SIGNAL(sig_screenshot(const Image &)), &protoWrapper, SLOT(receiveImage(Image))); + + // Start the capturing + dispmanxWrapper.start(); + + // Start the application + app.exec(); + } + } + catch (const std::runtime_error & e) + { + // An error occured. Display error and quit + std::cerr << e.what() << std::endl; + return -1; + } + + return 0; +} diff --git a/src/hyperion-remote/CMakeLists.txt b/src/hyperion-remote/CMakeLists.txt index c934be21..b8c73a59 100644 --- a/src/hyperion-remote/CMakeLists.txt +++ b/src/hyperion-remote/CMakeLists.txt @@ -3,7 +3,13 @@ cmake_minimum_required(VERSION 2.8) project(hyperion-remote) # find Qt4 +if(ENABLE_QT5) +find_package(Qt5 COMPONENTS Core Gui Widgets Network REQUIRED) +# set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${Qt5Widgets_EXECUTABLE_COMPILE_FLAGS}") +# set(CMAKE_CXX_FLAGS "-fPIC") +else(ENABLE_QT5) find_package(Qt4 REQUIRED QtCore QtGui QtNetwork) +endif(ENABLE_QT5) # The following I do not undrstand completely... # libQtCore.so uses some hardcoded library path inside which are incorrect after copying the file RPi file system @@ -25,6 +31,10 @@ add_executable(hyperion-remote ${hyperion-remote_HEADERS} ${hyperion-remote_SOURCES}) +if(ENABLE_QT5) +qt5_use_modules(hyperion-remote Widgets Network) +endif(ENABLE_QT5) + target_link_libraries(hyperion-remote jsoncpp getoptPlusPlus diff --git a/src/hyperion-remote/hyperion-remote.cpp b/src/hyperion-remote/hyperion-remote.cpp index da9d4b00..3636721f 100644 --- a/src/hyperion-remote/hyperion-remote.cpp +++ b/src/hyperion-remote/hyperion-remote.cpp @@ -43,8 +43,13 @@ int main(int argc, char * argv[]) // create the option parser and initialize all parameters OptionsParser optionParser("Simple application to send a command to hyperion using the Json interface"); ParameterSet & parameters = optionParser.getParameters(); +#ifdef ENABLE_QT5 + StringParameter & argAddress = parameters.add ('a', "address" , QString("Set the address of the hyperion server [default: %1]").arg(defaultServerAddress).toLatin1().constData()); + IntParameter & argPriority = parameters.add ('p', "priority" , QString("Use to the provided priority channel (the lower the number, the higher the priority) [default: %1]").arg(defaultPriority).toLatin1().constData()); +#else StringParameter & argAddress = parameters.add ('a', "address" , QString("Set the address of the hyperion server [default: %1]").arg(defaultServerAddress).toAscii().constData()); IntParameter & argPriority = parameters.add ('p', "priority" , QString("Use to the provided priority channel (the lower the number, the higher the priority) [default: %1]").arg(defaultPriority).toAscii().constData()); +#endif IntParameter & argDuration = parameters.add ('d', "duration" , "Specify how long the leds should be switched on in millseconds [default: infinity]"); ColorParameter & argColor = parameters.add ('c', "color" , "Set all leds to a constant color (either RRGGBB hex value or a color name. The color may be repeated multiple time like: RRGGBBRRGGBB)"); ImageParameter & argImage = parameters.add ('i', "image" , "Set the leds to the colors according to the given image file"); diff --git a/src/hyperion-v4l2/CMakeLists.txt b/src/hyperion-v4l2/CMakeLists.txt index 6607c04f..ef051e2d 100644 --- a/src/hyperion-v4l2/CMakeLists.txt +++ b/src/hyperion-v4l2/CMakeLists.txt @@ -3,7 +3,14 @@ cmake_minimum_required(VERSION 2.8) project(hyperion-v4l2) # find Qt4 +if(ENABLE_QT5) +find_package(Qt5Widgets REQUIRED) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${Qt5Widgets_EXECUTABLE_COMPILE_FLAGS}") +# set(CMAKE_CXX_FLAGS "-fPIC") +else(ENABLE_QT5) find_package(Qt4 REQUIRED QtCore QtGui QtNetwork) +endif(ENABLE_QT5) + include_directories( ${CMAKE_CURRENT_BINARY_DIR}/../../libsrc/protoserver @@ -24,8 +31,11 @@ set(Hyperion_V4L2_SOURCES hyperion-v4l2.cpp ScreenshotHandler.cpp ) - +if(ENABLE_QT5) +QT5_WRAP_CPP(Hyperion_V4L2_MOC_SOURCES ${Hyperion_V4L2_QT_HEADERS}) +else(ENABLE_QT5) QT4_WRAP_CPP(Hyperion_V4L2_MOC_SOURCES ${Hyperion_V4L2_QT_HEADERS}) +endif(ENABLE_QT5) add_executable(hyperion-v4l2 ${Hyperion_V4L2_HEADERS} diff --git a/src/hyperion-x11/CMakeLists.txt b/src/hyperion-x11/CMakeLists.txt index 89912f66..626781f9 100644 --- a/src/hyperion-x11/CMakeLists.txt +++ b/src/hyperion-x11/CMakeLists.txt @@ -4,8 +4,12 @@ cmake_minimum_required(VERSION 2.8) # Set the project name project(hyperion-x11) +if(ENABLE_QT5) +find_package(Qt5Widgets REQUIRED) +else(ENABLE_QT5) # find Qt4 find_package(Qt4 REQUIRED QtCore QtGui QtNetwork) +endif(ENABLE_QT5) # Find X11 find_package(X11 REQUIRED) @@ -28,7 +32,11 @@ set(Hyperion_X11_SOURCES X11Wrapper.cpp ) +if(ENABLE_QT5) +QT5_WRAP_CPP(Hyperion_X11_HEADERS_MOC ${Hyperion_X11_QT_HEADERS}) +else(ENABLE_QT5) QT4_WRAP_CPP(Hyperion_X11_HEADERS_MOC ${Hyperion_X11_QT_HEADERS}) +endif(ENABLE_QT5) add_executable(hyperion-x11 ${Hyperion_X11_HEADERS} @@ -46,7 +54,11 @@ target_link_libraries(hyperion-x11 pthread ) +if(ENABLE_QT5) +qt5_use_modules(hyperion-x11 Widgets Core Gui Network) +else(ENABLE_QT5) qt4_use_modules(hyperion-x11 Core Gui Network) +endif(ENABLE_QT5) diff --git a/src/hyperion-x11/X11Wrapper.cpp b/src/hyperion-x11/X11Wrapper.cpp index 6f33cc61..a0489fdf 100644 --- a/src/hyperion-x11/X11Wrapper.cpp +++ b/src/hyperion-x11/X11Wrapper.cpp @@ -29,6 +29,11 @@ void X11Wrapper::stop() _timer.stop(); } +bool X11Wrapper::displayInit() +{ + return _grabber.Setup(); +} + void X11Wrapper::capture() { const Image & screenshot = _grabber.grab(); diff --git a/src/hyperion-x11/X11Wrapper.h b/src/hyperion-x11/X11Wrapper.h index 211dfe18..18fab407 100644 --- a/src/hyperion-x11/X11Wrapper.h +++ b/src/hyperion-x11/X11Wrapper.h @@ -19,6 +19,8 @@ public: void start(); void stop(); + + bool displayInit(); signals: void sig_screenshot(const Image & screenshot); diff --git a/src/hyperion-x11/hyperion-x11.cpp b/src/hyperion-x11/hyperion-x11.cpp index 449f3990..7815aa34 100644 --- a/src/hyperion-x11/hyperion-x11.cpp +++ b/src/hyperion-x11/hyperion-x11.cpp @@ -77,6 +77,9 @@ int main(int argc, char ** argv) argCropBottom.getValue(), argSizeDecimation.getValue(), // horizontal decimation argSizeDecimation.getValue()); // vertical decimation + + if (!x11Wrapper.displayInit()) + return -1; if (argScreenshot.isSet()) { diff --git a/src/hyperiond/hyperiond.cpp b/src/hyperiond/hyperiond.cpp index 7357d55f..8af26879 100644 --- a/src/hyperiond/hyperiond.cpp +++ b/src/hyperiond/hyperiond.cpp @@ -1,6 +1,7 @@ // C++ includes #include #include +#include // QT includes #include @@ -126,20 +127,34 @@ int main(int argc, char** argv) // Get the parameters for the bootsequence const std::string effectName = effectConfig["effect"].asString(); const unsigned duration_ms = effectConfig["duration_ms"].asUInt(); - const int priority = 0; + const int priority = effectConfig["priority"].asUInt(); + const int bootcolor_priority = (priority > 990) ? priority+1 : 990; - hyperion.setColor(priority+1, ColorRgb::BLACK, duration_ms, false); + if ( ! effectConfig["color"].isNull() && effectConfig["color"].isArray() && effectConfig["color"].size() == 3 ) + { + ColorRgb boot_color = { + (uint8_t)effectConfig["color"][0].asUInt(), + (uint8_t)effectConfig["color"][1].asUInt(), + (uint8_t)effectConfig["color"][2].asUInt() + }; + + hyperion.setColor(bootcolor_priority, boot_color, 0, false); + } + else + { + hyperion.setColor(bootcolor_priority, ColorRgb::BLACK, duration_ms, false); + } if (effectConfig.isMember("args")) { const Json::Value effectConfigArgs = effectConfig["args"]; if (hyperion.setEffect(effectName, effectConfigArgs, priority, duration_ms) == 0) { - std::cout << "Boot sequence(" << effectName << ") with user-defined arguments created and started" << std::endl; + std::cout << "Boot sequence(" << effectName << ") with user-defined arguments created and started" << std::endl; } else { - std::cout << "Failed to start boot sequence: " << effectName << " with user-defined arguments" << std::endl; + std::cout << "Failed to start boot sequence: " << effectName << " with user-defined arguments" << std::endl; } } else @@ -174,6 +189,39 @@ int main(int argc, char** argv) std::cout << "XBMC video checker created and started" << std::endl; } +// ---- network services ----- + + // Create Json server if configuration is present + JsonServer * jsonServer = nullptr; + if (config.isMember("jsonServer")) + { + const Json::Value & jsonServerConfig = config["jsonServer"]; + jsonServer = new JsonServer(&hyperion, jsonServerConfig["port"].asUInt()); + std::cout << "Json server created and started on port " << jsonServer->getPort() << std::endl; + } + +#ifdef ENABLE_PROTOBUF + // Create Proto server if configuration is present + ProtoServer * protoServer = nullptr; + if (config.isMember("protoServer")) + { + const Json::Value & protoServerConfig = config["protoServer"]; + protoServer = new ProtoServer(&hyperion, protoServerConfig["port"].asUInt() ); + std::cout << "Proto server created and started on port " << protoServer->getPort() << std::endl; + } +#endif + + // Create Boblight server if configuration is present + BoblightServer * boblightServer = nullptr; + if (config.isMember("boblightServer")) + { + const Json::Value & boblightServerConfig = config["boblightServer"]; + boblightServer = new BoblightServer(&hyperion, boblightServerConfig["port"].asUInt()); + std::cout << "Boblight server created and started on port " << boblightServer->getPort() << std::endl; + } + +// ---- grabber ----- + #ifdef ENABLE_DISPMANX // Construct and start the frame-grabber if the configuration is present DispmanxWrapper * dispmanx = nullptr; @@ -192,6 +240,10 @@ int main(int argc, char** argv) QObject::connect(xbmcVideoChecker, SIGNAL(videoMode(VideoMode)), dispmanx, SLOT(setVideoMode(VideoMode))); } + #ifdef ENABLE_PROTOBUF + QObject::connect(dispmanx, SIGNAL(emitImage(int, const Image&, const int)), protoServer, SLOT(sendImageToProtoSlaves(int, const Image&, const int)) ); + #endif + dispmanx->start(); std::cout << "Frame grabber created and started" << std::endl; } @@ -199,7 +251,7 @@ int main(int argc, char** argv) #if !defined(ENABLE_OSX) && !defined(ENABLE_FB) if (config.isMember("framegrabber")) { - std::cerr << "The dispmanx framegrabber can not be instantiated, becuse it has been left out from the build" << std::endl; + std::cerr << "The dispmanx framegrabber can not be instantiated, because it has been left out from the build" << std::endl; } #endif #endif @@ -231,14 +283,19 @@ int main(int argc, char** argv) grabberConfig.get("cropTop", 0).asInt(), grabberConfig.get("cropBottom", 0).asInt()); + #ifdef ENABLE_PROTOBUF + QObject::connect(v4l2Grabber, SIGNAL(emitImage(int, const Image&, const int)), protoServer, SLOT(sendImageToProtoSlaves(int, const Image&, const int)) ); + #endif + v4l2Grabber->start(); std::cout << "V4l2 grabber created and started" << std::endl; } #else if (config.isMember("grabber-v4l2")) { - std::cerr << "The v4l2 grabber can not be instantiated, becuse it has been left out from the build" << std::endl; + std::cerr << "The v4l2 grabber can not be instantiated, because it has been left out from the build" << std::endl; } + #endif #ifdef ENABLE_AMLOGIC @@ -259,6 +316,10 @@ int main(int argc, char** argv) QObject::connect(xbmcVideoChecker, SIGNAL(videoMode(VideoMode)), amlGrabber, SLOT(setVideoMode(VideoMode))); } + #ifdef ENABLE_PROTOBUF + QObject::connect(amlGrabber, SIGNAL(emitImage(int, const Image&, const int)), protoServer, SLOT(sendImageToProtoSlaves(int, const Image&, const int)) ); + #endif + amlGrabber->start(); std::cout << "AMLOGIC grabber created and started" << std::endl; } @@ -288,18 +349,22 @@ int main(int argc, char** argv) QObject::connect(xbmcVideoChecker, SIGNAL(videoMode(VideoMode)), fbGrabber, SLOT(setVideoMode(VideoMode))); } + #ifdef ENABLE_PROTOBUF + QObject::connect(fbGrabber, SIGNAL(emitImage(int, const Image&, const int)), protoServer, SLOT(sendImageToProtoSlaves(int, const Image&, const int)) ); + #endif + fbGrabber->start(); std::cout << "Framebuffer grabber created and started" << std::endl; } #else if (config.isMember("framebuffergrabber")) { - std::cerr << "The framebuffer grabber can not be instantiated, becuse it has been left out from the build" << std::endl; + std::cerr << "The framebuffer grabber can not be instantiated, because it has been left out from the build" << std::endl; } #if !defined(ENABLE_DISPMANX) && !defined(ENABLE_OSX) else if (config.isMember("framegrabber")) { - std::cerr << "The framebuffer grabber can not be instantiated, becuse it has been left out from the build" << std::endl; + std::cerr << "The framebuffer grabber can not be instantiated, because it has been left out from the build" << std::endl; } #endif #endif @@ -322,6 +387,10 @@ int main(int argc, char** argv) QObject::connect(xbmcVideoChecker, SIGNAL(grabbingMode(GrabbingMode)), osxGrabber, SLOT(setGrabbingMode(GrabbingMode))); QObject::connect(xbmcVideoChecker, SIGNAL(videoMode(VideoMode)), osxGrabber, SLOT(setVideoMode(VideoMode))); } + + #ifdef ENABLE_PROTOBUF + QObject::connect(osxGrabber, SIGNAL(emitImage(int, const Image&, const int)), protoServer, SLOT(sendImageToProtoSlaves(int, const Image&, const int)) ); + #endif osxGrabber->start(); std::cout << "OSX grabber created and started" << std::endl; @@ -329,44 +398,16 @@ int main(int argc, char** argv) #else if (config.isMember("osxgrabber")) { - std::cerr << "The osx grabber can not be instantiated, becuse it has been left out from the build" << std::endl; + std::cerr << "The osx grabber can not be instantiated, because it has been left out from the build" << std::endl; } #if !defined(ENABLE_DISPMANX) && !defined(ENABLE_FB) else if (config.isMember("framegrabber")) { - std::cerr << "The osx grabber can not be instantiated, becuse it has been left out from the build" << std::endl; + std::cerr << "The osx grabber can not be instantiated, because it has been left out from the build" << std::endl; } #endif #endif - // Create Json server if configuration is present - JsonServer * jsonServer = nullptr; - if (config.isMember("jsonServer")) - { - const Json::Value & jsonServerConfig = config["jsonServer"]; - jsonServer = new JsonServer(&hyperion, jsonServerConfig["port"].asUInt()); - std::cout << "Json server created and started on port " << jsonServer->getPort() << std::endl; - } - -#ifdef ENABLE_PROTOBUF - // Create Proto server if configuration is present - ProtoServer * protoServer = nullptr; - if (config.isMember("protoServer")) - { - const Json::Value & protoServerConfig = config["protoServer"]; - protoServer = new ProtoServer(&hyperion, protoServerConfig["port"].asUInt()); - std::cout << "Proto server created and started on port " << protoServer->getPort() << std::endl; - } -#endif - - // Create Boblight server if configuration is present - BoblightServer * boblightServer = nullptr; - if (config.isMember("boblightServer")) - { - const Json::Value & boblightServerConfig = config["boblightServer"]; - boblightServer = new BoblightServer(&hyperion, boblightServerConfig["port"].asUInt()); - std::cout << "Boblight server created and started on port " << boblightServer->getPort() << std::endl; - } // run the application int rc = app.exec(); diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index bf40cebb..76cbb5dc 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -53,10 +53,19 @@ add_executable(test_qtscreenshot TestQtScreenshot.cpp) target_link_libraries(test_qtscreenshot ${QT_LIBRARIES}) +if(ENABLE_QT5) +qt5_use_modules(test_qregexp Widgets) +qt5_use_modules(test_qtscreenshot Widgets) +endif(ENABLE_QT5) + if(ENABLE_X11) # Find X11 find_package(X11 REQUIRED) add_executable(test_x11performance TestX11Performance.cpp) target_link_libraries(test_x11performance ${X11_LIBRARIES} ${QT_LIBRARIES}) +if(ENABLE_QT5) +qt5_use_modules(test_x11performance Widgets) +endif(ENABLE_QT5) + endif(ENABLE_X11) diff --git a/test/TestBlackBorderProcessor.cpp b/test/TestBlackBorderProcessor.cpp index 4aceaa81..88879398 100644 --- a/test/TestBlackBorderProcessor.cpp +++ b/test/TestBlackBorderProcessor.cpp @@ -29,7 +29,7 @@ Image createImage(unsigned width, unsigned height, unsigned topBorder, { for (unsigned y=0; y ( height - topBorder ) || x < leftBorder || x > (width - leftBorder) ) { image(x,y) = ColorRgb::BLACK; } @@ -44,11 +44,13 @@ Image createImage(unsigned width, unsigned height, unsigned topBorder, int main() { - unsigned unknownCnt = 600; +// unsigned unknownCnt = 600; unsigned borderCnt = 50; - unsigned blurCnt = 0; +// unsigned blurCnt = 0; + Json::Value config; - BlackBorderProcessor processor(unknownCnt, borderCnt, blurCnt, 3); +// BlackBorderProcessor processor(unknownCnt, borderCnt, blurCnt, 3, config); + BlackBorderProcessor processor(config); // Start with 'no border' detection Image noBorderImage = createImage(64, 64, 0, 0); @@ -86,7 +88,7 @@ int main() for (unsigned i=0; i vertImage = createImage(64, 64, 0, borderSize); for (unsigned i=0; i +#include #include +#include +#include int main() { std::cout << "Constructing image" << std::endl; - Image image(64, 64, ColorRgb::BLACK); + int width = 64; + int height = 64; + Image image_rgb(width, height, ColorRgb::BLACK); + Image image_bgr(image_rgb.width(), image_rgb.height(), ColorBgr::BLACK); std::cout << "Writing image" << std::endl; - for (unsigned y=0; y<64; ++y) + unsigned l = width * height; + + // BGR + for (unsigned i=0; i // Dispmanx grabber includes -#include +#include using namespace vlofgren;