Merge remote-tracking branch 'refs/remotes/tvdzwan/master'

# Conflicts:
#	include/hyperion/Hyperion.h
#	libsrc/hyperion/CMakeLists.txt
#	libsrc/hyperion/Hyperion.cpp
This commit is contained in:
AEtHeLsYn 2016-03-09 19:25:04 +01:00
commit 1144520581
127 changed files with 3079 additions and 438 deletions

2
.gitignore vendored
View File

@ -2,3 +2,5 @@
/build*
.DS_Store
libsrc/hyperion/ImageProcessor.cpp
libsrc/hyperion/CMakeLists.txt

View File

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

View File

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

View File

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

View File

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

View File

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

17
bin/hyperion.initctl.sh Normal file
View File

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

15
bin/hyperion.systemd.sh Normal file
View File

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

View File

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

105
bin/remove_hyperion.sh Normal file
View File

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

View File

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

View File

@ -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 <ip>:<port>
/// * 'json' : list of host in form of <ip>:<port>
/// "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:

Binary file not shown.

View File

@ -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
// <http://www.gnu.org/licenses/>.
// --------------------------------------------------------------------
#include <SPI.h>
// 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.
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

57
doc/UDP_led_driver.txt Normal file
View File

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

View File

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

View File

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

View File

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

27
effects/fade.py Normal file
View File

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

View File

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

View File

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

46
effects/police.py Normal file
View File

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

9
effects/random.json Normal file
View File

@ -0,0 +1,9 @@
{
"name" : "Random",
"script" : "random.py",
"args" :
{
"speed" : 1.0,
"saturation" : 1.0
}
}

21
effects/random.py Normal file
View File

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

10
effects/running_dots.json Normal file
View File

@ -0,0 +1,10 @@
{
"name" : "Running dots",
"script" : "running_dots.py",
"args" :
{
"speed" : 1.5,
"whiteLevel" : 100,
"colorLevel" : 230
}
}

43
effects/running_dots.py Normal file
View File

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

11
effects/shutdown.json Normal file
View File

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

49
effects/shutdown.py Normal file
View File

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

View File

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

View File

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

14
effects/sparks-color.json Normal file
View File

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

14
effects/sparks.json Normal file
View File

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

37
effects/sparks.py Normal file
View File

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

View File

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

8
effects/traces.json Normal file
View File

@ -0,0 +1,8 @@
{
"name" : "Color traces",
"script" : "traces.py",
"args" :
{
"speed" : 1.0
}
}

34
effects/traces.py Normal file
View File

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

8
effects/udp.json Normal file
View File

@ -0,0 +1,8 @@
{
"name" : "UDP listener",
"script" : "udp.py",
"args" :
{
"udpPort" : 2391
}
}

47
effects/udp.py Normal file
View File

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

8
effects/x-mas.json Normal file
View File

@ -0,0 +1,8 @@
{
"name" : "X-Mas",
"script" : "x-mas.py",
"args" :
{
"sleepTime" : 0.75
}
}

28
effects/x-mas.py Normal file
View File

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

View File

@ -1,3 +1,4 @@
//#include <iostream>
#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 <typename Pixel_T>
BlackBorder process(const Image<Pixel_T> & 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 <typename Pixel_T>
BlackBorder process_classic(const Image<Pixel_T> & 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 <typename Pixel_T>
BlackBorder process_osd(const Image<Pixel_T> & 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

View File

@ -1,6 +1,8 @@
#pragma once
// Jsoncpp includes
#include <json/json.h>
// 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<Pixel_T> & 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

View File

@ -68,6 +68,9 @@ public slots:
///
void setVideoMode(const VideoMode videoMode);
signals:
void emitImage(int priority, const Image<ColorRgb> & 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;
};

View File

@ -68,6 +68,9 @@ public slots:
///
void setVideoMode(const VideoMode videoMode);
signals:
void emitImage(int priority, const Image<ColorRgb> & 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;
};

View File

@ -68,6 +68,9 @@ public slots:
///
void setVideoMode(const VideoMode videoMode);
signals:
void emitImage(int priority, const Image<ColorRgb> & image, const int timeout_ms);
private:
/// The update rate [Hz]
const int _updateInterval_ms;

View File

@ -69,6 +69,9 @@ public slots:
///
void setVideoMode(const VideoMode videoMode);
signals:
void emitImage(int priority, const Image<ColorRgb> & image, const int timeout_ms);
private:
/// The update rate [Hz]
const int _updateInterval_ms;

View File

@ -44,6 +44,7 @@ public slots:
signals:
void emitColors(int priority, const std::vector<ColorRgb> &ledColors, const int timeout_ms);
void emitImage(int priority, const Image<ColorRgb> & image, const int timeout_ms);
private slots:
void newFrame(const Image<ColorRgb> & image);

View File

@ -7,6 +7,11 @@
// X11 includes
#include <X11/Xlib.h>
#include <X11/extensions/Xrender.h>
#include <X11/extensions/XShm.h>
#include <sys/ipc.h>
#include <sys/shm.h>
class X11Grabber
{
public:
@ -16,6 +21,8 @@ public:
virtual ~X11Grabber();
int open();
bool Setup();
Image<ColorRgb> & 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<ColorRgb> _image;
void freeResources();
void setupResources();
int updateScreenDimensions();
};

View File

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

View File

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

View File

@ -0,0 +1,38 @@
#pragma once
// STL includes
#include <vector>
#include <map>
#include <cstdint>
#include <limits>
// QT includes
#include <QList>
#include <QStringList>
#include <QHostAddress>
// Utils includes
#include <utils/ColorRgb.h>
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<MessageForwarder::JsonSlaveAddress> getJsonSlaves();
private:
QStringList _protoSlaves;
QList<MessageForwarder::JsonSlaveAddress> _jsonSlaves;
};

View File

@ -7,6 +7,7 @@
#include <QColor>
#include <QImage>
#include <QTcpSocket>
#include <QTimer>
#include <QMap>
// 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;
};

View File

@ -6,11 +6,23 @@
// Qt includes
#include <QTcpServer>
#include <QSet>
#include <QList>
#include <QStringList>
// Hyperion includes
#include <hyperion/Hyperion.h>
// hyperion includes
#include <utils/Image.h>
#include <utils/ColorRgb.h>
// 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<ColorRgb> & 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<ProtoClientConnection *> _openConnections;
QStringList _forwardClients;
/// Hyperion proto connection object for forwarding
QList<ProtoConnection*> _proxy_connections;
};

View File

@ -5,6 +5,8 @@
#include <cstdint>
#include <cstring>
#include <algorithm>
#include <utils/ColorRgb.h>
template <typename Pixel_T>
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<ColorRgb>& image)
{
image.resize(_width, _height);
const unsigned imageSize = _width * _height;
for (unsigned idx=0; idx<imageSize; idx++)
{
const Pixel_T color = memptr()[idx];
image.memptr()[idx] = ColorRgb{color.red, color.green, color.blue};
}
}
private:
///

View File

@ -1,11 +1,27 @@
#include <iostream>
// BlackBorders includes
#include <blackborder/BlackBorderDetector.h>
#include <cmath>
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;
}

View File

@ -1,21 +1,28 @@
#include <iostream>
/*
#include <iomanip>
using std::setw;
//*/
// Blackborder includes
#include <blackborder/BlackBorderProcessor.h>
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;
}
}

View File

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

View File

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

View File

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

View File

@ -75,7 +75,11 @@ const std::list<EffectDefinition> &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

View File

@ -13,7 +13,7 @@
#include <sys/types.h>
// Local includes
#include "AmlogicGrabber.h"
#include <grabber/AmlogicGrabber.h>
// Flags copied from 'include/linux/amlogic/amports/amvideocap.h' at https://github.com/codesnake/linux-amlogic
#define AMVIDEOCAP_IOC_MAGIC 'V'

View File

@ -9,7 +9,7 @@
// Amlogic grabber includes
#include <grabber/AmlogicWrapper.h>
#include "AmlogicGrabber.h"
#include <grabber/AmlogicGrabber.h>
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<ColorRgb> image_rgb;
_image.toRgb(image_rgb);
emit emitImage(_priority, image_rgb, _timeout_ms);
}
_processor->process(_image, _ledColors);
_hyperion->setColors(_priority, _ledColors, _timeout_ms);
}

View File

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

View File

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

View File

@ -4,7 +4,7 @@
#include <iostream>
// Local includes
#include "DispmanxFrameGrabber.h"
#include "grabber/DispmanxFrameGrabber.h"
DispmanxFrameGrabber::DispmanxFrameGrabber(const unsigned width, const unsigned height) :
_vc_display(0),

View File

@ -9,7 +9,7 @@
// Dispmanx grabber includes
#include <grabber/DispmanxWrapper.h>
#include "DispmanxFrameGrabber.h"
#include <grabber/DispmanxFrameGrabber.h>
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<ColorRgb> 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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -94,6 +94,9 @@ void V4L2Wrapper::newFrame(const Image<ColorRgb> &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);
}

View File

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

View File

@ -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<ColorRgb> & 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<const uint8_t *>(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<const uint8_t *>(_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;
}

View File

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

View File

@ -1,7 +1,3 @@
// STL includes
#include <cmath>
// Hyperion includes
#include <hyperion/ImageProcessorFactory.h>
#include <hyperion/ImageProcessor.h>
@ -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);
}

View File

@ -0,0 +1,51 @@
// STL includes
#include <stdexcept>
#include <hyperion/MessageForwarder.h>
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::JsonSlaveAddress> MessageForwarder::getJsonSlaves()
{
return _jsonSlaves;
}
bool MessageForwarder::protoForwardingEnabled()
{
return ! _protoSlaves.empty();
}

View File

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

View File

@ -16,6 +16,7 @@
// hyperion util includes
#include <hyperion/ImageProcessorFactory.h>
#include <hyperion/ImageProcessor.h>
#include <hyperion/MessageForwarder.h>
#include <hyperion/ColorTransform.h>
#include <utils/ColorRgb.h>
@ -250,8 +251,27 @@ void JsonClientConnection::handleMessage(const std::string &messageString)
handleNotImplemented();
}
void JsonClientConnection::forwardJsonMessage(const Json::Value & message)
{
QTcpSocket client;
QList<MessageForwarder::JsonSlaveAddress> list = _hyperion->getForwarder()->getJsonSlaves();
for ( int i=0; i<list.size(); i++ )
{
client.connectToHost(list.at(i).addr, list.at(i).port);
if ( client.waitForConnected(500) )
{
sendMessage(message,&client);
client.close();
}
}
}
void JsonClientConnection::handleColorCommand(const Json::Value &message)
{
forwardJsonMessage(message);
// extract parameters
int priority = message["priority"].asInt();
int duration = message.get("duration", -1).asInt();
@ -289,6 +309,8 @@ void JsonClientConnection::handleColorCommand(const Json::Value &message)
void JsonClientConnection::handleImageCommand(const Json::Value &message)
{
forwardJsonMessage(message);
// extract parameters
int priority = message["priority"].asInt();
int duration = message.get("duration", -1).asInt();
@ -320,6 +342,8 @@ void JsonClientConnection::handleImageCommand(const Json::Value &message)
void JsonClientConnection::handleEffectCommand(const Json::Value &message)
{
forwardJsonMessage(message);
// extract parameters
int priority = message["priority"].asInt();
int duration = message.get("duration", -1).asInt();
@ -418,6 +442,8 @@ void JsonClientConnection::handleServerInfoCommand(const Json::Value &)
void JsonClientConnection::handleClearCommand(const Json::Value &message)
{
forwardJsonMessage(message);
// extract parameters
int priority = message["priority"].asInt();
@ -428,8 +454,10 @@ void JsonClientConnection::handleClearCommand(const Json::Value &message)
sendSuccessReply();
}
void JsonClientConnection::handleClearallCommand(const Json::Value &)
void JsonClientConnection::handleClearallCommand(const Json::Value & message)
{
forwardJsonMessage(message);
// clear priority
_hyperion->clearall();
@ -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

View File

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

View File

@ -16,6 +16,14 @@ JsonServer::JsonServer(Hyperion *hyperion, uint16_t port) :
throw std::runtime_error("Json server could not bind to port");
}
QList<MessageForwarder::JsonSlaveAddress> list = _hyperion->getForwarder()->getJsonSlaves();
for ( int i=0; i<list.size(); i++ )
{
if ( list.at(i).addr == QHostAddress::LocalHost && list.at(i).port == port ) {
throw std::runtime_error("Loop between proto server and forwarder detected. Fix your config!");
}
}
// Set trigger for incoming connections
connect(&_server, SIGNAL(newConnection()), this, SLOT(newConnection()));

View File

@ -20,6 +20,8 @@ SET(Leddevice_QT_HEADERS
${CURRENT_SOURCE_DIR}/LedDevicePhilipsHue.h
${CURRENT_SOURCE_DIR}/LedHIDDevice.h
${CURRENT_SOURCE_DIR}/LedDeviceRawHID.h
${CURRENT_SOURCE_DIR}/LedDeviceTest.h
${CURRENT_SOURCE_DIR}/LedDeviceFadeCandy.h
)
SET(Leddevice_HEADERS
@ -31,7 +33,9 @@ SET(Leddevice_HEADERS
${CURRENT_SOURCE_DIR}/LedDevicePaintpack.h
${CURRENT_SOURCE_DIR}/LedDevicePiBlaster.h
${CURRENT_SOURCE_DIR}/LedDeviceSedu.h
${CURRENT_SOURCE_DIR}/LedDeviceTest.h
${CURRENT_SOURCE_DIR}/LedDeviceTest.h
${CURRENT_SOURCE_DIR}/LedDeviceFadeCandy.h
${CURRENT_SOURCE_DIR}/LedDeviceUdp.h
${CURRENT_SOURCE_DIR}/LedDeviceHyperionUsbasp.h
${CURRENT_SOURCE_DIR}/LedDeviceTpm2.h
${CURRENT_SOURCE_DIR}/LedDeviceAtmo.h
@ -52,7 +56,9 @@ SET(Leddevice_SOURCES
${CURRENT_SOURCE_DIR}/LedDevicePaintpack.cpp
${CURRENT_SOURCE_DIR}/LedDevicePiBlaster.cpp
${CURRENT_SOURCE_DIR}/LedDeviceSedu.cpp
${CURRENT_SOURCE_DIR}/LedDeviceTest.cpp
${CURRENT_SOURCE_DIR}/LedDeviceTest.cpp
${CURRENT_SOURCE_DIR}/LedDeviceFadeCandy.cpp
${CURRENT_SOURCE_DIR}/LedDeviceUdp.cpp
${CURRENT_SOURCE_DIR}/LedDeviceHyperionUsbasp.cpp
${CURRENT_SOURCE_DIR}/LedDevicePhilipsHue.cpp
${CURRENT_SOURCE_DIR}/LedDeviceTpm2.cpp
@ -102,8 +108,12 @@ if(ENABLE_TINKERFORGE)
)
endif(ENABLE_TINKERFORGE)
if(ENABLE_QT5)
QT5_WRAP_CPP(Leddevice_HEADERS_MOC ${Leddevice_QT_HEADERS})
else(ENABLE_QT5)
QT4_WRAP_CPP(Leddevice_HEADERS_MOC ${Leddevice_QT_HEADERS})
endif(ENABLE_QT5)
add_library(leddevice
${Leddevice_HEADERS}
@ -112,9 +122,13 @@ add_library(leddevice
${Leddevice_SOURCES}
)
if(ENABLE_QT5)
qt5_use_modules(leddevice Widgets Network)
endif(ENABLE_QT5)
target_link_libraries(leddevice
hyperion-utils
serialport
serialport
${LIBUSB_1_LIBRARIES} #apt-get install libusb-1.0-0-dev
${CMAKE_THREAD_LIBS_INIT}
${QT_LIBRARIES}

View File

@ -3,7 +3,6 @@
#include <cstring>
#include <cstdio>
#include <iostream>
#include <algorithm>
// Linux includes
#include <fcntl.h>
@ -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<ColorRgb> & ledValues)
{
ledCount = ledValues.size();
const unsigned int startFrameSize = 4;
const unsigned int endFrameSize = std::max<unsigned int>(((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<unsigned int>(((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<ColorRgb> & 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());
}

View File

@ -7,12 +7,12 @@
#include <QTimer>
// 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<ColorRgb> & 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<uint8_t> _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

View File

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

View File

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

View File

@ -0,0 +1,71 @@
#pragma once
// STL/Qt includes
#include <fstream>
#include <QObject>
#include <QTcpSocket>
// Leddevice includes
#include <leddevice/LedDevice.h>
///
/// 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<ColorRgb> & 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();
};

View File

@ -5,10 +5,9 @@
#include <json/json.h>
// qt includes
#include <QtCore/qmath.h>
#include <QUrl>
#include <QHttpRequestHeader>
#include <QtCore/qmath.h>
#include <QEventLoop>
#include <QNetworkReply>
#include <set>
@ -144,14 +143,14 @@ LedDevicePhilipsHue::LedDevicePhilipsHue(const std::string& output, const std::s
int transitiontime, std::vector<unsigned int> 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<ColorRgb> & 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) {

View File

@ -6,9 +6,8 @@
// Qt includes
#include <QObject>
#include <QString>
#include <QHttp>
#include <QNetworkAccessManager>
#include <QTimer>
// Leddevice includes
#include <leddevice/LedDevice.h>
@ -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;
///

View File

@ -0,0 +1,165 @@
// Local-Hyperion includes
#include "LedDeviceUdp.h"
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <assert.h>
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; i<output.length(); i++) {
if (output[i] == ':') {
got_colon++;
} else if (got_colon == 0) {
hostname+=output[i];
} else {
port+=output[i];
}
}
//std::cout << "output " << output << " hostname " << hostname << " port " << port <<std::endl;
assert(got_colon==1);
int rv;
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_DGRAM;
if ((rv = getaddrinfo(hostname.c_str() , port.c_str(), &hints, &servinfo)) != 0) {
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
assert(rv==0);
}
// loop through all the results and make a socket
for(p = servinfo; p != NULL; p = p->ai_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<ColorRgb> & 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<mLedCount) && (ct++<MAXLEDperFRAG)); this_led++) {
const ColorRgb& color = ledValues[this_led];
if (udpPtr<4090) {
udpbuffer[udpPtr++] = color.red;
udpbuffer[udpPtr++] = color.green;
udpbuffer[udpPtr++] = color.blue;
}
}
if (udpPtr > 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<ColorRgb>(mLedCount, ColorRgb{0,0,0}));
return 0;
}

View File

@ -0,0 +1,44 @@
#pragma once
// STL includes0
#include <fstream>
// Leddevice includes
#include <leddevice/LedDevice.h>
///
/// 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<ColorRgb> & 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;
};

View File

@ -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<ColorRgb> &ledValues)
{
mLedCount = ledValues.size();

View File

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

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