diff --git a/CrossCompileHowto.txt b/CrossCompileHowto.txt index aed7c627..ac959f10 100644 --- a/CrossCompileHowto.txt +++ b/CrossCompileHowto.txt @@ -1,6 +1,6 @@ ON RASPBERRY -------------- -sudo apt-get install libprotobuf-dev libQt4-dev libusb-1.0-0-dev libudev-dev rsync +sudo apt-get install libprotobuf-dev libQt4-dev libusb-1.0-0-dev python-dev rsync ON HOST --------- diff --git a/bin/copy_binaries_to_deploy.sh b/bin/copy_binaries_to_deploy.sh index 26595acd..f81ebbed 100755 --- a/bin/copy_binaries_to_deploy.sh +++ b/bin/copy_binaries_to_deploy.sh @@ -10,7 +10,18 @@ repodir="$2" echo build directory = $builddir echo repository root dirrectory = $repodir -echo Copying binaries -cp -v "$builddir"/bin/hyperiond "$repodir"/deploy -cp -v "$builddir"/bin/hyperion-remote "$repodir"/deploy -cp -v "$builddir"/bin/gpio2spi "$repodir"/deploy +outfile="$repodir/deploy/hyperion.tar.gz" +echo create $outfile + +tar --create --verbose --gzip --absolute-names --show-transformed-names \ + --file "$outfile" \ + --transform "s:$builddir/bin/:hyperion/bin/:" \ + --transform "s:$repodir/effects/:hyperion/effects/:" \ + --transform "s:$repodir/config/:hyperion/config/:" \ + --transform "s://:/:g" \ + "$builddir/bin/hyperiond" \ + "$builddir/bin/hyperion-remote" \ + "$builddir/bin/gpio2spi" \ + "$builddir/bin/dispmanx2png" \ + "$repodir/effects/"* \ + "$repodir/config/hyperion.config.json" diff --git a/bin/install_hyperion.sh b/bin/install_hyperion.sh index a0d6bd5c..09ecb6c5 100755 --- a/bin/install_hyperion.sh +++ b/bin/install_hyperion.sh @@ -16,28 +16,26 @@ fi # Stop hyperion daemon if it is running /sbin/initctl stop hyperion -# Get the Hyperion executable -wget -N github.com/tvdzwan/hyperion/raw/master/deploy/hyperiond -P /usr/bin/ -chmod +x /usr/bin/hyperiond +# Get and extract the Hyperion binaries and effects to /opt +wget https://raw.github.com/tvdzwan/hyperion/master/deploy/hyperion.tar.gz -O - | tar -C /opt -xz -# Get the Hyperion command line utility -wget -N github.com/tvdzwan/hyperion/raw/master/deploy/hyperion-remote -P /usr/bin/ -chmod +x /usr/bin/hyperion-remote +# create links to the binaries +ln -fs /opt/hyperion/bin/hyperiond /usr/bin/hyperiond +ln -fs /opt/hyperion/bin/hyperion-remote /usr/bin/hyperion-remote -# Copy the gpio changer (gpio->spi) to the /usr/bin +# create link to the gpio changer (gpio->spi) if [ $IS_XBIAN -eq 0 ]; then - wget -N github.com/tvdzwan/hyperion/raw/master/deploy/gpio2spi -P /usr/bin/ - chmod +x /usr/bin/gpio2spi + ln -fs /opt/hyperion/bin/gpio2spi /usr/bin/gpio2spi fi -# Copy the hyperion configuration file to /etc -wget -N github.com/tvdzwan/hyperion/raw/master/config/hyperion.config.json -P /etc/ +# Copy a link to the hyperion configuration file to /etc +ln -s /opt/hyperion/config/hyperion.config.json /etc/hyperion.config.json # Copy the service control configuration to /etc/int if [ $IS_XBIAN -eq 0 ]; then - wget -N github.com/tvdzwan/hyperion/raw/master/deploy/hyperion.conf -P /etc/init/ + wget -N https://raw.github.com/tvdzwan/hyperion/master/deploy/hyperion.conf -P /etc/init/ else - wget -N github.com/tvdzwan/hyperion/raw/master/deploy/hyperion.xbian.conf -O /etc/init/hyperion.conf + wget -N https://raw.github.com/tvdzwan/hyperion/master/deploy/hyperion.xbian.conf -O /etc/init/hyperion.conf fi # Start the hyperion daemon diff --git a/bin/update_hyperion.sh b/bin/update_hyperion.sh deleted file mode 100755 index 8e1996b5..00000000 --- a/bin/update_hyperion.sh +++ /dev/null @@ -1,41 +0,0 @@ -#!/bin/sh - -# Script for downloading and installing the latest Hyperion release - -# Find out if we are on XBian -IS_XBIAN=`cat /etc/issue | grep XBian | wc -l` - -# Make sure that the boblight daemon is no longer running -BOBLIGHT_PROCNR=$(ps -e | grep "boblight" | wc -l) -if [ $BOBLIGHT_PROCNR -eq 1 ]; -then - echo 'Found running instance of boblight. Please stop boblight via XBMC menu before installing hyperion' - exit -fi - -# Stop hyperion daemon if it is running -/sbin/initctl stop hyperion - -# Get the Hyperion executable -wget -N github.com/tvdzwan/hyperion/raw/master/deploy/hyperiond -P /usr/bin/ -chmod +x /usr/bin/hyperiond - -# Get the Hyperion command line utility -wget -N github.com/tvdzwan/hyperion/raw/master/deploy/hyperion-remote -P /usr/bin/ -chmod +x /usr/bin/hyperion-remote - -# Copy the gpio changer (gpio->spi) to the /usr/bin -if [ $IS_XBIAN -eq 0 ]; then - wget -N github.com/tvdzwan/hyperion/raw/master/deploy/gpio2spi -P /usr/bin/ - chmod +x /usr/bin/gpio2spi -fi - -# Copy the service control configuration to /etc/int -if [ $IS_XBIAN -eq 0 ]; then - wget -N github.com/tvdzwan/hyperion/raw/master/deploy/hyperion.conf -P /etc/init/ -else - wget -N github.com/tvdzwan/hyperion/raw/master/deploy/hyperion.xbian.conf -P /etc/init/ -O hyperion.conf -fi - -# Start the hyperion daemon -/sbin/initctl start hyperion diff --git a/config/hyperion.config.json b/config/hyperion.config.json index 5f569a43..2c90592e 100644 --- a/config/hyperion.config.json +++ b/config/hyperion.config.json @@ -2,7 +2,7 @@ // Generated by: HyperCon (The Hyperion deamon configuration file builder { - /// Device configuration contains the following fields: + /// 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', 'test' and 'none') @@ -15,51 +15,69 @@ "name" : "MyPi", "type" : "ws2801", "output" : "/dev/spidev0.0", - "rate" : 500000, + "rate" : 250000, "colorOrder" : "rgb" }, - /// Color manipulation configuration used to tune the output colors to specific surroundings. Contains the following fields: - /// * 'hsv' : The manipulation in the Hue-Saturation-Value color domain with the following tuning parameters: + /// 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: + /// * 'id' : The unique identifier of the color transformation (eg 'device_1') + /// * 'leds' : The indices (or index ranges) of the leds to which this color transform applies + /// (eg '0-5, 9, 11, 12-17'). The indices are zero based. + /// * 'hsv' : The manipulation in the Hue-Saturation-Value color domain with the following + /// tuning parameters: /// - 'saturationGain' The gain adjustement of the saturation /// - 'valueGain' The gain adjustement of the value - /// * 'red'/'green'/'blue' : The manipulation in the Red-Green-Blue color domain with the following tuning parameters for each channel: - /// - 'threshold' The minimum required input value for the channel to be on (else zero) + /// * 'red'/'green'/'blue' : The manipulation in the Red-Green-Blue color domain with the + /// following tuning parameters for each channel: + /// - 'threshold' The minimum required input value for the channel to be on + /// (else zero) /// - 'gamma' The gamma-curve correction factor /// - 'blacklevel' The lowest possible value (when the channel is black) /// - 'whitelevel' The highest possible value (when the channel is white) - /// * 'smoothing' : Smoothing of the colors in the time-domain with the following tuning parameters: + /// + /// Next to the list with color transforms there is also a smoothing option. + /// * 'smoothing' : Smoothing of the colors in the time-domain with the following tuning + /// parameters: /// - '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 "color" : { - "hsv" : - { - "saturationGain" : 1.0000, - "valueGain" : 1.5000 - }, - "red" : - { - "threshold" : 0.1000, - "gamma" : 2.0000, - "blacklevel" : 0.0000, - "whitelevel" : 0.8000 - }, - "green" : - { - "threshold" : 0.1000, - "gamma" : 2.0000, - "blacklevel" : 0.0000, - "whitelevel" : 1.0000 - }, - "blue" : - { - "threshold" : 0.1000, - "gamma" : 2.0000, - "blacklevel" : 0.0000, - "whitelevel" : 1.0000 - }, + "transform" : + [ + { + "id" : "default", + "leds" : "*", + "hsv" : + { + "saturationGain" : 1.0000, + "valueGain" : 1.0000 + }, + "red" : + { + "threshold" : 0.0000, + "gamma" : 1.0000, + "blacklevel" : 0.0000, + "whitelevel" : 1.0000 + }, + "green" : + { + "threshold" : 0.0000, + "gamma" : 1.0000, + "blacklevel" : 0.0000, + "whitelevel" : 1.0000 + }, + "blue" : + { + "threshold" : 0.0000, + "gamma" : 1.0000, + "blacklevel" : 0.0000, + "whitelevel" : 1.0000 + } + } + ], "smoothing" : { "type" : "none", @@ -68,16 +86,16 @@ } }, - /// 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: - /// * index: The index of the led. This determines its location in the string of leds; zero - /// being the first led. - /// * hscan: The fractional part of the image along the horizontal used for the averaging - /// (minimum and maximum inclusive) - /// * vscan: The fractional part of the image along the vertical used for the averaging - /// (minimum and maximum inclusive) - "leds" : + /// 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: + /// * index: The index of the led. This determines its location in the string of leds; zero + /// being the first led. + /// * hscan: The fractional part of the image along the horizontal used for the averaging + /// (minimum and maximum inclusive) + /// * vscan: The fractional part of the image along the vertical used for the averaging + /// (minimum and maximum inclusive) + "leds" : [ { "index" : 0, @@ -331,34 +349,42 @@ } ], - /// The black border configuration, contains the following items: + /// The black border configuration, contains the following items: /// * enable : true if the detector should be activated "blackborderdetector" : { "enable" : true }, - /// The boot-sequence configuration, contains the following items: - /// * type : The type of the boot-sequence ('rainbow', 'knightrider', 'none') - /// * duration_ms : The length of the boot-sequence [ms] + /// The configuration of the effect engine, contains the following items: + /// * paths : An array with absolute location(s) of directories with effects + "effects" : + { + "paths" : + [ + "/opt/hyperion/effects" + ] + }, + "bootsequence" : { - "type" : "Rainbow", + "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] + /// 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, + "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: + /// 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 @@ -367,12 +393,12 @@ /// * grabMenu : Flag indicating that the frame-grabber is on(true) in the XBMC menu "xbmcVideoChecker" : { - "xbmcAddress" : "127.0.0.1", - "xbmcTcpPort" : 9090, - "grabVideo" : true, + "xbmcAddress" : "127.0.0.1", + "xbmcTcpPort" : 9090, + "grabVideo" : true, "grabPictures" : true, - "grabAudio" : true, - "grabMenu" : false + "grabAudio" : true, + "grabMenu" : false }, /// The configuration of the Json server which enables the json remote interface @@ -391,10 +417,10 @@ /// The configuration of the boblight server which enables the boblight remote interface /// * port : Port at which the boblight server is started -// "boblightServer" : -// { -// "port" : 19333 -// }, +// "boblightServer" : +// { +// "port" : 19333 +// }, - "end-of-json" : "end-of-json" + "endOfJson" : "endOfJson" } diff --git a/config/hyperion_multicolor.config.json b/config/hyperion_test.config.json similarity index 90% rename from config/hyperion_multicolor.config.json rename to config/hyperion_test.config.json index 886ae288..dc00c2e9 100644 --- a/config/hyperion_multicolor.config.json +++ b/config/hyperion_test.config.json @@ -37,8 +37,38 @@ "transform" : [ { - "id" : "MyPi device", - "leds" : "0-49", + "id" : "MyPi red", + "leds" : "0-11,37-49", + "hsv" : + { + "saturationGain" : 1.0000, + "valueGain" : 1.5000 + }, + "red" : + { + "threshold" : 0.1000, + "gamma" : 2.0000, + "blacklevel" : 0.3000, + "whitelevel" : 0.8000 + }, + "green" : + { + "threshold" : 0.1000, + "gamma" : 2.0000, + "blacklevel" : 0.0000, + "whitelevel" : 1.0000 + }, + "blue" : + { + "threshold" : 0.1000, + "gamma" : 2.0000, + "blacklevel" : 0.0000, + "whitelevel" : 1.0000 + } + }, + { + "id" : "MyPi blue", + "leds" : "12-36", "hsv" : { "saturationGain" : 1.0000, @@ -63,7 +93,7 @@ "threshold" : 0.1000, "gamma" : 2.0000, "blacklevel" : 0.0000, - "whitelevel" : 1.0000 + "whitelevel" : 0.3000 } } ], @@ -344,6 +374,40 @@ { "enable" : true }, + + + "effects" : + { + "Rainbow swirl" : + { + "script" : "/home/pi/hyperion/effects/rainbow-swirl.py", + "args" : + { + "rotation-time" : 10.0, + "brightness" : 1.0, + "reverse" : false + } + }, + "Rainbow mood" : + { + "script" : "/home/pi/hyperion/effects/rainbow-mood.py", + "args" : + { + "rotation-time" : 10.0, + "brightness" : 1.0, + "reverse" : false + } + }, + "Knight rider" : + { + "script" : "/home/pi/hyperion/effects/knight-rider.py", + "args" : + { + "speed" : 1.0, + "fadeFactor" : 0.7 + } + } + }, /// The boot-sequence configuration, contains the following items: /// * type : The type of the boot-sequence ('rainbow', 'knightrider', 'none') @@ -352,7 +416,13 @@ /// * duration_ms : The length of the boot-sequence [ms] "bootsequence" : { - "type" : "Rainbow", + "script" : "/home/pi/hyperion/effects/rainbow-swirl.py", + "args" : + { + "rotation-time" : 3.0, + "brightness" : 1.0, + "reverse" : false + }, "duration_ms" : 3000 }, diff --git a/deploy/HyperCon.jar.REMOVED.git-id b/deploy/HyperCon.jar.REMOVED.git-id index 90278ffd..0ae7510f 100644 --- a/deploy/HyperCon.jar.REMOVED.git-id +++ b/deploy/HyperCon.jar.REMOVED.git-id @@ -1 +1 @@ -a63791e2794fa0c76a2a5da3d0ce7b3e4f4089ff \ No newline at end of file +0e33a5994203b58676a56efa2addf362a276c219 \ No newline at end of file diff --git a/deploy/dispmanx2png b/deploy/dispmanx2png deleted file mode 100755 index deff3974..00000000 Binary files a/deploy/dispmanx2png and /dev/null differ diff --git a/deploy/gpio2spi b/deploy/gpio2spi deleted file mode 100755 index f999003f..00000000 Binary files a/deploy/gpio2spi and /dev/null differ diff --git a/deploy/hyperion-remote b/deploy/hyperion-remote deleted file mode 100755 index 2d4a01e9..00000000 Binary files a/deploy/hyperion-remote and /dev/null differ diff --git a/deploy/hyperion.tar.gz b/deploy/hyperion.tar.gz new file mode 100644 index 00000000..12dfa7e5 Binary files /dev/null and b/deploy/hyperion.tar.gz differ diff --git a/deploy/hyperiond.REMOVED.git-id b/deploy/hyperiond.REMOVED.git-id deleted file mode 100644 index b969c543..00000000 --- a/deploy/hyperiond.REMOVED.git-id +++ /dev/null @@ -1 +0,0 @@ -cb10b0acda414c8d57cd1cc1bf49c48c142ec6b8 \ No newline at end of file diff --git a/effects/knight-rider.json b/effects/knight-rider.json new file mode 100755 index 00000000..bf83f897 --- /dev/null +++ b/effects/knight-rider.json @@ -0,0 +1,9 @@ +{ + "name" : "Knight rider", + "script" : "knight-rider.py", + "args" : + { + "speed" : 1.0, + "fadeFactor" : 0.7 + } +} diff --git a/effects/knight-rider.py b/effects/knight-rider.py new file mode 100644 index 00000000..d2ff3ecc --- /dev/null +++ b/effects/knight-rider.py @@ -0,0 +1,49 @@ +import hyperion +import time +import colorsys + +# Get the rotation time +speed = float(hyperion.args.get('speed', 1.0)) +fadeFactor = float(hyperion.args.get('fadeFactor', 0.7)) + +# Check parameters +speed = max(0.0001, speed) +fadeFactor = max(0.0, min(fadeFactor, 1.0)) + +# Initialize the led data +width = 25 +imageData = bytearray(width * (0,0,0)) +imageData[0] = 255 + +# Calculate the sleep time and rotation increment +increment = 1 +sleepTime = 1.0 / (speed * width) +while sleepTime < 0.05: + increment *= 2 + sleepTime *= 2 + +# Start the write data loop +position = 0 +direction = 1 +while not hyperion.abort(): + hyperion.setImage(width, 1, imageData) + + # Move data into next state + for i in range(increment): + position += direction + if position == -1: + position = 1 + direction = 1 + elif position == width: + position = width-2 + direction = -1 + + # Fade the old data + for j in range(width): + imageData[3*j] = int(fadeFactor * imageData[3*j]) + + # Insert new data + imageData[3*position] = 255 + + # Sleep for a while + time.sleep(sleepTime) diff --git a/effects/mood-blobs-blue.json b/effects/mood-blobs-blue.json new file mode 100755 index 00000000..1aa188ab --- /dev/null +++ b/effects/mood-blobs-blue.json @@ -0,0 +1,12 @@ +{ + "name" : "Blue mood blobs", + "script" : "mood-blobs.py", + "args" : + { + "rotationTime" : 60.0, + "color" : [0,0,255], + "hueChange" : 60.0, + "blobs" : 5, + "reverse" : false + } +} diff --git a/effects/mood-blobs-green.json b/effects/mood-blobs-green.json new file mode 100755 index 00000000..c0c104fe --- /dev/null +++ b/effects/mood-blobs-green.json @@ -0,0 +1,12 @@ +{ + "name" : "Green mood blobs", + "script" : "mood-blobs.py", + "args" : + { + "rotationTime" : 60.0, + "color" : [0,255,0], + "hueChange" : 60.0, + "blobs" : 5, + "reverse" : false + } +} diff --git a/effects/mood-blobs-red.json b/effects/mood-blobs-red.json new file mode 100755 index 00000000..3272dded --- /dev/null +++ b/effects/mood-blobs-red.json @@ -0,0 +1,12 @@ +{ + "name" : "Red mood blobs", + "script" : "mood-blobs.py", + "args" : + { + "rotationTime" : 60.0, + "color" : [255,0,0], + "hueChange" : 60.0, + "blobs" : 5, + "reverse" : false + } +} diff --git a/effects/mood-blobs.py b/effects/mood-blobs.py new file mode 100644 index 00000000..c0ce3d15 --- /dev/null +++ b/effects/mood-blobs.py @@ -0,0 +1,61 @@ +import hyperion +import time +import colorsys +import math + +# Get the parameters +rotationTime = float(hyperion.args.get('rotationTime', 20.0)) +color = hyperion.args.get('color', (0,0,255)) +hueChange = float(hyperion.args.get('hueChange', 60.0)) / 360.0 +blobs = int(hyperion.args.get('blobs', 5)) +reverse = bool(hyperion.args.get('reverse', False)) + +# Check parameters +rotationTime = max(0.1, rotationTime) +hueChange = max(0.0, min(abs(hueChange), .5)) +blobs = max(1, blobs) + +# Calculate the color data +baseHsv = colorsys.rgb_to_hsv(color[0]/255.0, color[1]/255.0, color[2]/255.0) +colorData = bytearray() +for i in range(hyperion.ledCount): + hue = (baseHsv[0] + hueChange * math.sin(2*math.pi * i / hyperion.ledCount)) % 1.0 + rgb = colorsys.hsv_to_rgb(hue, baseHsv[1], baseHsv[2]) + colorData += bytearray((int(255*rgb[0]), int(255*rgb[1]), int(255*rgb[2]))) + +# Calculate the increments +sleepTime = 0.1 +amplitudePhaseIncrement = blobs * math.pi * sleepTime / rotationTime +colorDataIncrement = 3 + +# Switch direction if needed +if reverse: + amplitudePhaseIncrement = -amplitudePhaseIncrement + colorDataIncrement = -colorDataIncrement + +# create a Array for the colors +colors = bytearray(hyperion.ledCount * (0,0,0)) + +# Start the write data loop +amplitudePhase = 0.0 +rotateColors = False +while not hyperion.abort(): + # Calculate new colors + for i in range(hyperion.ledCount): + amplitude = max(0.0, math.sin(-amplitudePhase + 2*math.pi * blobs * i / hyperion.ledCount)) + colors[3*i+0] = int(colorData[3*i+0] * amplitude) + colors[3*i+1] = int(colorData[3*i+1] * amplitude) + colors[3*i+2] = int(colorData[3*i+2] * amplitude) + + # set colors + hyperion.setColor(colors) + + # increment the phase + amplitudePhase = (amplitudePhase + amplitudePhaseIncrement) % (2*math.pi) + + if rotateColors: + colorData = colorData[-colorDataIncrement:] + colorData[:-colorDataIncrement] + rotateColors = not rotateColors + + # sleep for a while + time.sleep(sleepTime) diff --git a/effects/rainbow-mood.json b/effects/rainbow-mood.json new file mode 100755 index 00000000..fe754287 --- /dev/null +++ b/effects/rainbow-mood.json @@ -0,0 +1,10 @@ +{ + "name" : "Rainbow mood", + "script" : "rainbow-mood.py", + "args" : + { + "rotation-time" : 60.0, + "brightness" : 1.0, + "reverse" : false + } +} diff --git a/effects/rainbow-mood.py b/effects/rainbow-mood.py new file mode 100644 index 00000000..2ce08edd --- /dev/null +++ b/effects/rainbow-mood.py @@ -0,0 +1,30 @@ +import hyperion +import time +import colorsys + +# Get the parameters +rotationTime = float(hyperion.args.get('rotation-time', 30.0)) +brightness = float(hyperion.args.get('brightness', 1.0)) +saturation = float(hyperion.args.get('saturation', 1.0)) +reverse = bool(hyperion.args.get('reverse', False)) + +# Check parameters +rotationTime = max(0.1, rotationTime) +brightness = max(0.0, min(brightness, 1.0)) +saturation = max(0.0, min(saturation, 1.0)) + +# Calculate the sleep time and hue increment +sleepTime = 0.1 +hueIncrement = sleepTime / rotationTime + +# Switch direction if needed +if reverse: + increment = -increment + +# Start the write data loop +hue = 0.0 +while not hyperion.abort(): + rgb = colorsys.hsv_to_rgb(hue, saturation, brightness) + hyperion.setColor(int(255*rgb[0]), int(255*rgb[1]), int(255*rgb[2])) + hue = (hue + hueIncrement) % 1.0 + time.sleep(sleepTime) diff --git a/effects/rainbow-swirl-fast.json b/effects/rainbow-swirl-fast.json new file mode 100755 index 00000000..19fec89c --- /dev/null +++ b/effects/rainbow-swirl-fast.json @@ -0,0 +1,10 @@ +{ + "name" : "Rainbow swirl fast", + "script" : "rainbow-swirl.py", + "args" : + { + "rotation-time" : 3.0, + "brightness" : 1.0, + "reverse" : false + } +} diff --git a/effects/rainbow-swirl.json b/effects/rainbow-swirl.json new file mode 100755 index 00000000..3f7b7243 --- /dev/null +++ b/effects/rainbow-swirl.json @@ -0,0 +1,10 @@ +{ + "name" : "Rainbow swirl", + "script" : "rainbow-swirl.py", + "args" : + { + "rotation-time" : 20.0, + "brightness" : 1.0, + "reverse" : false + } +} diff --git a/effects/rainbow-swirl.py b/effects/rainbow-swirl.py new file mode 100644 index 00000000..ec623aa1 --- /dev/null +++ b/effects/rainbow-swirl.py @@ -0,0 +1,39 @@ +import hyperion +import time +import colorsys + +# Get the parameters +rotationTime = float(hyperion.args.get('rotation-time', 3.0)) +brightness = float(hyperion.args.get('brightness', 1.0)) +saturation = float(hyperion.args.get('saturation', 1.0)) +reverse = bool(hyperion.args.get('reverse', 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): + hue = float(i)/hyperion.ledCount + rgb = colorsys.hsv_to_rgb(hue, saturation, brightness) + ledData += bytearray((int(255*rgb[0]), int(255*rgb[1]), int(255*rgb[2]))) + +# Calculate the sleep time and rotation increment +increment = 3 +sleepTime = rotationTime / hyperion.ledCount +while sleepTime < 0.05: + increment *= 2 + sleepTime *= 2 +increment %= hyperion.ledCount + +# Switch direction if needed +if reverse: + increment = -increment + +# Start the write data loop +while not hyperion.abort(): + hyperion.setColor(ledData) + ledData = ledData[-increment:] + ledData[:-increment] + time.sleep(sleepTime) diff --git a/effects/strobe.json b/effects/strobe.json new file mode 100644 index 00000000..1af14afc --- /dev/null +++ b/effects/strobe.json @@ -0,0 +1,8 @@ +{ + "name" : "Stroboscope", + "script" : "strobe.py", + "args" : + { + "frequency" : 10.0 + } +} diff --git a/effects/strobe.py b/effects/strobe.py new file mode 100644 index 00000000..c35cbab6 --- /dev/null +++ b/effects/strobe.py @@ -0,0 +1,23 @@ +import hyperion +import time +import colorsys + +# Get the rotation time +frequency = float(hyperion.args.get('frequency', 10.0)) + +# Check parameters +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 * (255,255,255)) + +# Start the write data loop +while not hyperion.abort(): + hyperion.setColor(blackLedsData) + time.sleep(sleepTime) + hyperion.setColor(whiteLedsData) + time.sleep(sleepTime) diff --git a/include/bootsequence/BootSequence.h b/include/bootsequence/BootSequence.h deleted file mode 100644 index 17606d30..00000000 --- a/include/bootsequence/BootSequence.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -/// -/// Pure virtual base class (or interface) for boot sequences. A BootSequence is started after the -/// Hyperion deamon is started to demonstrate the proper functioninf of the attached leds (and lets -/// face it because it is cool) -/// -class BootSequence -{ -public: - - /// - /// Empty virtual destructor for abstract base class - /// - virtual ~BootSequence() - { - // empty - } - - /// - /// Starts the boot sequence writing one or more colors to the attached leds - /// - virtual void start() = 0; -}; diff --git a/include/bootsequence/BootSequenceFactory.h b/include/bootsequence/BootSequenceFactory.h deleted file mode 100644 index f6ba7d67..00000000 --- a/include/bootsequence/BootSequenceFactory.h +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once - -// Jsoncpp includes -#include - -// Bootsequence includes -#include - -// Hyperion includes -#include - -/// -/// Factory for settings based construction of a boot-sequence -/// -class BootSequenceFactory -{ -public: - - /// - /// Creates a BootSequence using the given configuration (and Hyperion connection). Ownship of - /// the returned instance is transferred - /// - /// @param[in] hyperion The Hyperion controlling the leds - /// @param[in] jsonConfig The boot-sequence configuration - /// - /// @return The bootsequence (ownership is transferred to the caller - /// - static BootSequence * createBootSequence(Hyperion * hyperion, const Json::Value & jsonConfig); -}; diff --git a/include/effectengine/EffectDefinition.h b/include/effectengine/EffectDefinition.h new file mode 100644 index 00000000..d8c1ce27 --- /dev/null +++ b/include/effectengine/EffectDefinition.h @@ -0,0 +1,14 @@ +#pragma once + +// stl include +#include + +// json include +#include + +struct EffectDefinition +{ + std::string name; + std::string script; + Json::Value args; +}; diff --git a/include/effectengine/EffectEngine.h b/include/effectengine/EffectEngine.h new file mode 100644 index 00000000..bea45576 --- /dev/null +++ b/include/effectengine/EffectEngine.h @@ -0,0 +1,59 @@ +#pragma once + +// Qt includes +#include + +// Json includes +#include + +// Hyperion includes +#include + +// Effect engine includes +#include + +// pre-declarioation +class Effect; +typedef struct _ts PyThreadState; + +class EffectEngine : public QObject +{ + Q_OBJECT + +public: + EffectEngine(Hyperion * hyperion, const Json::Value & jsonEffectConfig); + virtual ~EffectEngine(); + + const std::list & getEffects() const; + + static bool loadEffectDefinition(const std::string & path, const std::string & effectConfigFile, EffectDefinition &effectDefinition); + +public slots: + /// Run the specified effect on the given priority channel and optionally specify a timeout + int runEffect(const std::string &effectName, int priority, int timeout = -1); + + /// Run the specified effect on the given priority channel and optionally specify a timeout + int runEffect(const std::string &effectName, const Json::Value & args, int priority, int timeout = -1); + + /// Clear any effect running on the provided channel + void channelCleared(int priority); + + /// Clear all effects + void allChannelsCleared(); + +private slots: + void effectFinished(Effect * effect); + +private: + /// Run the specified effect on the given priority channel and optionally specify a timeout + int runEffectScript(const std::string &script, const Json::Value & args, int priority, int timeout = -1); + +private: + Hyperion * _hyperion; + + std::list _availableEffects; + + std::list _activeEffects; + + PyThreadState * _mainThreadState; +}; diff --git a/include/hyperion/Hyperion.h b/include/hyperion/Hyperion.h index 2514cdcf..cb092c2e 100644 --- a/include/hyperion/Hyperion.h +++ b/include/hyperion/Hyperion.h @@ -15,8 +15,12 @@ #include #include +// Effect engine includes +#include + // Forward class declaration class ColorTransform; +class EffectEngine; class HsvTransform; class RgbChannelTransform; class MultiColorTransform; @@ -71,6 +75,29 @@ public: /// unsigned getLedCount() const; + /// + /// Returns a list of active priorities + /// + /// @return The list with priorities + /// + QList getActivePriorities() const; + + /// + /// Returns the information of a specific priorrity channel + /// + /// @param[in] priority The priority channel + /// + /// @return The information of the given + /// + /// @throw std::runtime_error when the priority channel does not exist + /// + const InputInfo& getPriorityInfo(const int priority) const; + + /// Get the list of available effects + /// @return The list of available effects + const std::list &getEffects() const; + +public slots: /// /// Writes a single color to all the leds for the given time and priority /// @@ -78,7 +105,7 @@ public: /// @param[in] ledColor The color to write to the leds /// @param[in] timeout_ms The time the leds are set to the given color [ms] /// - void setColor(int priority, const ColorRgb &ledColor, const int timeout_ms); + void setColor(int priority, const ColorRgb &ledColor, const int timeout_ms, bool clearEffects = true); /// /// Writes the given colors to all leds for the given time and priority @@ -87,7 +114,7 @@ public: /// @param[in] ledColors The colors to write to the leds /// @param[in] timeout_ms The time the leds are set to the given colors [ms] /// - void setColors(int priority, const std::vector &ledColors, const int timeout_ms); + void setColors(int priority, const std::vector &ledColors, const int timeout_ms, bool clearEffects = true); /// /// Returns the list with unique transform identifiers @@ -117,24 +144,20 @@ public: /// void clearall(); - /// - /// Returns a list of active priorities - /// - /// @return The list with priorities - /// - QList getActivePriorities() const; + /// Run the specified effect on the given priority channel and optionally specify a timeout + /// @param effectName Name of the effec to run + /// @param priority The priority channel of the effect + /// @param timout The timeout of the effect (after the timout, the effect will be cleared) + int setEffect(const std::string & effectName, int priority, int timeout = -1); - /// - /// Returns the information of a specific priorrity channel - /// - /// @param[in] priority The priority channel - /// - /// @return The information of the given - /// - /// @throw std::runtime_error when the priority channel does not exist - /// - const InputInfo& getPriorityInfo(const int priority) const; + /// Run the specified effect on the given priority channel and optionally specify a timeout + /// @param effectName Name of the effec to run + /// @param args arguments of the effect script + /// @param priority The priority channel of the effect + /// @param timout The timeout of the effect (after the timout, the effect will be cleared) + int setEffect(const std::string & effectName, const Json::Value & args, int priority, int timeout = -1); +public: static LedDevice * createDevice(const Json::Value & deviceConfig); static ColorOrder createColorOrder(const Json::Value & deviceConfig); static LedString createLedString(const Json::Value & ledsConfig); @@ -146,6 +169,15 @@ public: static LedDevice * createColorSmoothing(const Json::Value & smoothingConfig, LedDevice * ledDevice); +signals: + /// Signal which is emitted when a priority channel is actively cleared + /// This signal will not be emitted when a priority channel time out + void channelCleared(int priority); + + /// Signal which is emitted when all priority channels are actively cleared + /// This signal will not be emitted when a priority channel time out + void allChannelsCleared(); + private slots: /// /// Updates the priority muxer with the current time and (re)writes the led color with applied @@ -169,6 +201,9 @@ private: /// The actual LedDevice LedDevice * _device; + /// Effect engine + EffectEngine * _effectEngine; + /// The timer for handling priority channel timeouts QTimer _timer; }; diff --git a/include/hyperion/ImageProcessor.h b/include/hyperion/ImageProcessor.h index be87982e..6c4b4739 100644 --- a/include/hyperion/ImageProcessor.h +++ b/include/hyperion/ImageProcessor.h @@ -20,6 +20,11 @@ class ImageProcessor public: ~ImageProcessor(); + /// + /// Returns the number of attached leds + /// + unsigned getLedCount() const; + /// /// Specifies the width and height of 'incomming' images. This will resize the buffer-image to /// match the given size. diff --git a/libsrc/CMakeLists.txt b/libsrc/CMakeLists.txt index d97e6368..ee82011c 100644 --- a/libsrc/CMakeLists.txt +++ b/libsrc/CMakeLists.txt @@ -3,13 +3,13 @@ SET(CURRENT_HEADER_DIR ${CMAKE_SOURCE_DIR}/include) SET(CURRENT_SOURCE_DIR ${CMAKE_SOURCE_DIR}/libsrc) -add_subdirectory(bootsequence) add_subdirectory(hyperion) add_subdirectory(jsonserver) add_subdirectory(protoserver) add_subdirectory(boblightserver) add_subdirectory(utils) add_subdirectory(xbmcvideochecker) +add_subdirectory(effectengine) if (ENABLE_DISPMANX) add_subdirectory(dispmanx-grabber) diff --git a/libsrc/boblightserver/BoblightClientConnection.cpp b/libsrc/boblightserver/BoblightClientConnection.cpp index 75e61451..de254803 100644 --- a/libsrc/boblightserver/BoblightClientConnection.cpp +++ b/libsrc/boblightserver/BoblightClientConnection.cpp @@ -133,7 +133,8 @@ void BoblightClientConnection::handleMessage(const QString & message) if (messageParts[3] == "rgb" && messageParts.size() == 7) { bool rc1, rc2, rc3; - uint8_t red = 255 * messageParts[4].toFloat(&rc1); + uint8_t red = qMax(0, qMin(255, int(255 * messageParts[4].toFloat(&rc1)))); + if (!rc1) { // maybe a locale issue. switch to a locale with a comma instead of a dot as decimal seperator (or vice versa) @@ -141,11 +142,11 @@ void BoblightClientConnection::handleMessage(const QString & message) _locale.setNumberOptions(QLocale::OmitGroupSeparator | QLocale::RejectGroupSeparator); // try again - red = 255 * messageParts[4].toFloat(&rc1); + red = qMax(0, qMin(255, int(255 * messageParts[4].toFloat(&rc1)))); } - uint8_t green = 255 * messageParts[5].toFloat(&rc2); - uint8_t blue = 255 * messageParts[6].toFloat(&rc3); + uint8_t green = qMax(0, qMin(255, 255 * int(messageParts[5].toFloat(&rc2)))); + uint8_t blue = qMax(0, qMin(255, 255 * int(messageParts[6].toFloat(&rc3)))); if (rc1 && rc2 && rc3) { @@ -153,6 +154,13 @@ void BoblightClientConnection::handleMessage(const QString & message) rgb.red = red; rgb.green = green; rgb.blue = blue; + + // send current color values to hyperion if this is the last led assuming leds values are send in order of id + if ((ledIndex == _ledColors.size() -1) && _priority < 255) + { + _hyperion->setColors(_priority, _ledColors, -1); + } + return; } } diff --git a/libsrc/bootsequence/AbstractBootSequence.cpp b/libsrc/bootsequence/AbstractBootSequence.cpp deleted file mode 100644 index 2342eda5..00000000 --- a/libsrc/bootsequence/AbstractBootSequence.cpp +++ /dev/null @@ -1,35 +0,0 @@ -#include "AbstractBootSequence.h" - -AbstractBootSequence::AbstractBootSequence(Hyperion * hyperion, const int64_t interval, const unsigned iterationCnt) : - _timer(), - _hyperion(hyperion), - _priority(0), - _iterationCounter(iterationCnt) -{ - _timer.setInterval(interval); - _timer.setSingleShot(false); - QObject::connect(&_timer, SIGNAL(timeout()), this, SLOT(update())); -} - -void AbstractBootSequence::start() -{ - _timer.start(); -} - -void AbstractBootSequence::update() -{ - if (_iterationCounter == 0) - { - _timer.stop(); - _hyperion->clear(_priority); - return; - } - - // Obtain the next led-colors from the child-class - const std::vector& colors = nextColors(); - // Write the colors to hyperion - _hyperion->setColors(_priority, colors, -1); - - // Decrease the loop count - --_iterationCounter; -} diff --git a/libsrc/bootsequence/AbstractBootSequence.h b/libsrc/bootsequence/AbstractBootSequence.h deleted file mode 100644 index 224619b9..00000000 --- a/libsrc/bootsequence/AbstractBootSequence.h +++ /dev/null @@ -1,62 +0,0 @@ -#pragma once - -// QT includes -#include - -// Bootsequence includes -#include - -// Hyperion includes -#include - -/// -/// The AbstractBootSequence is an 'abstract' implementation of the BootSequence that handles the -/// event generation and Hyperion connection. Subclasses only need to specify the interval and -/// return the colors for the leds for each iteration. -/// -class AbstractBootSequence : public QObject, public BootSequence -{ -Q_OBJECT - -public: - /// - /// Constructs the AbstractBootSequence with the given parameters - /// - /// @param hyperion The Hyperion instance - /// @param interval The interval between new led colors - /// @param iterationCnt The number of iteration performed by the boot sequence - /// - AbstractBootSequence(Hyperion * hyperion, const int64_t interval, const unsigned iterationCnt); - - /// - /// Starts the boot-sequence - /// - virtual void start(); - -protected slots: - /// - /// Timer slot for handling each interval of the boot-sequence - /// - void update(); - -protected: - /// - /// Child-classes must implement this by returning the next led colors in the sequence - /// - /// @return The next led colors in the boot sequence - /// - virtual const std::vector& nextColors() = 0; - -private: - /// The timer used to generate an 'update' signal every interval - QTimer _timer; - - /// The Hyperion instance - Hyperion * _hyperion; - /// The priority of the boot sequence - int _priority; - - /// The counter of the number of iterations left - int _iterationCounter; -}; - diff --git a/libsrc/bootsequence/BootSequenceFactory.cpp b/libsrc/bootsequence/BootSequenceFactory.cpp deleted file mode 100644 index 7ad5ae6b..00000000 --- a/libsrc/bootsequence/BootSequenceFactory.cpp +++ /dev/null @@ -1,36 +0,0 @@ -// stl includes -#include -#include - -// Bootsequence includes -#include - -// Local Bootsequence includes -#include "RainbowBootSequence.h" -#include "KittBootSequence.h" - -BootSequence * BootSequenceFactory::createBootSequence(Hyperion * hyperion, const Json::Value & jsonConfig) -{ - std::string type = jsonConfig["type"].asString(); - std::transform(type.begin(), type.end(), type.begin(), ::tolower); - - if (type == "none") - { - return nullptr; - } - else if (type == "rainbow") - { - std::cout << "SELECTED BOOT SEQUENCE: " << "RAINBOW" << std::endl; - const unsigned duration_ms = jsonConfig["duration_ms"].asUInt(); - return new RainbowBootSequence(hyperion, duration_ms); - } - else if (type == "knightrider" || type == "knight rider" || "knight_rider") - { - std::cout << "SELECTED BOOT SEQUENCE: " << "KITT" << std::endl; - const unsigned duration_ms = jsonConfig["duration_ms"].asUInt(); - return new KittBootSequence(hyperion, duration_ms); - } - - std::cerr << "Unknown boot-sequence selected; boot-sequence disabled." << std::endl; - return nullptr; -} diff --git a/libsrc/bootsequence/CMakeLists.txt b/libsrc/bootsequence/CMakeLists.txt deleted file mode 100644 index f10e37a4..00000000 --- a/libsrc/bootsequence/CMakeLists.txt +++ /dev/null @@ -1,36 +0,0 @@ - -# Define the current source locations -SET(CURRENT_HEADER_DIR ${CMAKE_SOURCE_DIR}/include/bootsequence) -SET(CURRENT_SOURCE_DIR ${CMAKE_SOURCE_DIR}/libsrc/bootsequence) - -# Group the headers that go through the MOC compiler -SET(BootsequenceQT_HEADERS - ${CURRENT_SOURCE_DIR}/AbstractBootSequence.h -) - -SET(BootsequenceHEADERS - ${CURRENT_HEADER_DIR}/BootSequence.h - ${CURRENT_HEADER_DIR}/BootSequenceFactory.h - ${CURRENT_SOURCE_DIR}/RainbowBootSequence.h - ${CURRENT_SOURCE_DIR}/KittBootSequence.h -) - -SET(BootsequenceSOURCES - ${CURRENT_SOURCE_DIR}/AbstractBootSequence.cpp - ${CURRENT_SOURCE_DIR}/BootSequenceFactory.cpp - ${CURRENT_SOURCE_DIR}/RainbowBootSequence.cpp - ${CURRENT_SOURCE_DIR}/KittBootSequence.cpp -) - -QT4_WRAP_CPP(BootsequenceHEADERS_MOC ${BootsequenceQT_HEADERS}) - -add_library(bootsequence - ${BootsequenceHEADERS} - ${BootsequenceQT_HEADERS} - ${BootsequenceHEADERS_MOC} - ${BootsequenceSOURCES} -) - -target_link_libraries(bootsequence - hyperion - ${QT_LIBRARIES}) diff --git a/libsrc/bootsequence/KittBootSequence.cpp b/libsrc/bootsequence/KittBootSequence.cpp deleted file mode 100644 index d72e9d31..00000000 --- a/libsrc/bootsequence/KittBootSequence.cpp +++ /dev/null @@ -1,67 +0,0 @@ - -// Hyperion includes -#include - -// Local-Bootsequence includes -#include "KittBootSequence.h" - -KittBootSequence::KittBootSequence(Hyperion * hyperion, const unsigned duration_ms) : - AbstractBootSequence(hyperion, 100, duration_ms/100), - _processor(ImageProcessorFactory::getInstance().newImageProcessor()), - _image(9, 1, ColorRgb{0,0,0}), - _ledColors(hyperion->getLedCount(), ColorRgb{0,0,0}), - _forwardMove(false), - _currentLight(0) -{ - // empty -} - -KittBootSequence::~KittBootSequence() -{ - delete _processor; -} - -const std::vector& KittBootSequence::nextColors() -{ - - // Switch the previous light 'off' - _image(_currentLight, 0) = ColorRgb{0,0,0}; - - // Move the current to the next light - moveNextLight(); - - // Switch the current light 'on' - _image(_currentLight, 0) = ColorRgb{255,0,0}; - - - // Translate the 'image' to led colors - _processor->process(_image, _ledColors); - - // Return the colors - return _ledColors; -} - -void KittBootSequence::moveNextLight() -{ - // Increase/Decrease the current light - if (_forwardMove) - { - ++_currentLight; - if (_currentLight == _image.width()) - { - _forwardMove = false; - --_currentLight; - } - } - else - { - if (_currentLight == 0) - { - _forwardMove = true; - } - else - { - --_currentLight; - } - } -} diff --git a/libsrc/bootsequence/KittBootSequence.h b/libsrc/bootsequence/KittBootSequence.h deleted file mode 100644 index d5ea8c25..00000000 --- a/libsrc/bootsequence/KittBootSequence.h +++ /dev/null @@ -1,56 +0,0 @@ - -#pragma once - -// Bootsequence includes -#include "AbstractBootSequence.h" - -// Hyperion includes -#include -#include - -/// -/// The KITT BootSequence is a boot sequence inspired by the Knight Rider car: Knight Industries Two -/// Thousand (aka KITT) -/// -class KittBootSequence : public AbstractBootSequence -{ -public: - /// - /// Constructs the KITT BootSequence - /// - /// @param[in] hyperion The Hyperion instance - /// @param[in] duration_ms The length of the sequence [ms] - /// - KittBootSequence(Hyperion * hyperion, const unsigned duration_ms); - - /// - /// Destructor, deletes the processor - /// - virtual ~KittBootSequence(); - - /// - /// Returns the next led color sequence - /// - /// @return The next colors for the leds - /// - virtual const std::vector& nextColors(); - -private: - /// Image processor to compute led-colors from the image - ImageProcessor * _processor; - - /// 1D-Image of the KITT-grill contains a single red pixel and the rest black - Image _image; - - /// The vector with led-colors - std::vector _ledColors; - - /// Direction the red-light is currently moving - bool _forwardMove; - /// The location of the current red-light - unsigned _currentLight; - - /// Moves the current light to the next (increase or decrease depending on direction) - void moveNextLight(); -}; - diff --git a/libsrc/bootsequence/RainbowBootSequence.cpp b/libsrc/bootsequence/RainbowBootSequence.cpp deleted file mode 100644 index d04a14ab..00000000 --- a/libsrc/bootsequence/RainbowBootSequence.cpp +++ /dev/null @@ -1,30 +0,0 @@ - -// Utils includes -#include - -// Local-Bootsequence include -#include "RainbowBootSequence.h" - -RainbowBootSequence::RainbowBootSequence(Hyperion * hyperion, const unsigned duration_ms) : - AbstractBootSequence(hyperion, duration_ms/hyperion->getLedCount(), hyperion->getLedCount()), - _ledColors(hyperion->getLedCount()) -{ - for (unsigned iLed=0; iLedgetLedCount(); ++iLed) - { - ColorRgb& color = _ledColors[iLed]; - HsvTransform::hsv2rgb(iLed*360/hyperion->getLedCount(), 255, 255, color.red, color.green, color.blue); - } -} - -const std::vector& RainbowBootSequence::nextColors() -{ - // Rotate the colors left - const ColorRgb headColor = _ledColors.front(); - for (unsigned i=1; i<_ledColors.size(); ++i) - { - _ledColors[i-1] = _ledColors[i]; - } - _ledColors.back() = headColor; - - return _ledColors; -} diff --git a/libsrc/bootsequence/RainbowBootSequence.h b/libsrc/bootsequence/RainbowBootSequence.h deleted file mode 100644 index 8b60a9b6..00000000 --- a/libsrc/bootsequence/RainbowBootSequence.h +++ /dev/null @@ -1,38 +0,0 @@ - -#pragma once - -// QT includes -#include - -// Bootsequence include -#include "AbstractBootSequence.h" - -/// -/// The RainborBootSequence shows a 'rainbow' (all lights have a different color). The rainbow is -/// rotated over each led during the length of the sequence. -/// -class RainbowBootSequence : public AbstractBootSequence -{ -public: - /// - /// Constructs the rainbow boot-sequence. Hyperion is used for writing the led colors. The given - /// duration is the length of the sequence. - /// - /// @param[in] hyperion The Hyperion instance - /// @param[in] duration_ms The length of the sequence [ms] - /// - RainbowBootSequence(Hyperion * hyperion, const unsigned duration_ms); - -protected: - /// - /// Moves the rainbow one led further - /// - const std::vector& nextColors(); - -private: - /// The current color of the boot sequence (the rainbow) - std::vector _ledColors; - /// The counter of the number of iterations left - int _iterationCounter; -}; - diff --git a/libsrc/effectengine/CMakeLists.txt b/libsrc/effectengine/CMakeLists.txt new file mode 100644 index 00000000..98edfa5c --- /dev/null +++ b/libsrc/effectengine/CMakeLists.txt @@ -0,0 +1,44 @@ + +find_package(PythonLibs REQUIRED) + +include_directories(${PYTHON_INCLUDE_DIRS}) + +# Define the current source locations +SET(CURRENT_HEADER_DIR ${CMAKE_SOURCE_DIR}/include/effectengine) +SET(CURRENT_SOURCE_DIR ${CMAKE_SOURCE_DIR}/libsrc/effectengine) + +# Group the headers that go through the MOC compiler +SET(EffectEngineQT_HEADERS + ${CURRENT_HEADER_DIR}/EffectEngine.h + ${CURRENT_SOURCE_DIR}/Effect.h +) + +SET(EffectEngineHEADERS + ${CURRENT_HEADER_DIR}/EffectDefinition.h +) + +SET(EffectEngineSOURCES + ${CURRENT_SOURCE_DIR}/EffectEngine.cpp + ${CURRENT_SOURCE_DIR}/Effect.cpp +) + + +set(EffectEngine_RESOURCES ${CURRENT_SOURCE_DIR}/EffectEngine.qrc) + +QT4_WRAP_CPP(EffectEngineHEADERS_MOC ${EffectEngineQT_HEADERS}) + +qt4_add_resources(EffectEngine_RESOURCES_RCC ${EffectEngine_RESOURCES} OPTIONS "-no-compress") + +add_library(effectengine + ${EffectEngineHEADERS} + ${EffectEngineQT_HEADERS} + ${EffectEngineHEADERS_MOC} + ${EffectEngine_RESOURCES_RCC} + ${EffectEngineSOURCES} +) + +target_link_libraries(effectengine + hyperion + jsoncpp + ${QT_LIBRARIES} + ${PYTHON_LIBRARIES}) diff --git a/libsrc/effectengine/Effect.cpp b/libsrc/effectengine/Effect.cpp new file mode 100644 index 00000000..e09f8979 --- /dev/null +++ b/libsrc/effectengine/Effect.cpp @@ -0,0 +1,310 @@ +// Python include +#include + +// stl includes +#include +#include + +// Qt includes +#include + +// effect engin eincludes +#include "Effect.h" + +// Python method table +PyMethodDef Effect::effectMethods[] = { + {"setColor", Effect::wrapSetColor, METH_VARARGS, "Set a new color for the leds."}, + {"setImage", Effect::wrapSetImage, METH_VARARGS, "Set a new image to process and determine new led colors."}, + {"abort", Effect::wrapAbort, METH_NOARGS, "Check if the effect should abort execution."}, + {NULL, NULL, 0, NULL} +}; + + +Effect::Effect(int priority, int timeout, const std::string & script, const Json::Value & args) : + QThread(), + _priority(priority), + _timeout(timeout), + _script(script), + _args(args), + _endTime(-1), + _interpreterThreadState(nullptr), + _abortRequested(false), + _imageProcessor(ImageProcessorFactory::getInstance().newImageProcessor()), + _colors() +{ + _colors.resize(_imageProcessor->getLedCount(), ColorRgb::BLACK); + + // connect the finished signal + connect(this, SIGNAL(finished()), this, SLOT(effectFinished())); +} + +Effect::~Effect() +{ +} + +void Effect::run() +{ + // Initialize a new thread state + PyEval_AcquireLock(); // Get the GIL + _interpreterThreadState = Py_NewInterpreter(); + + // add methods extra builtin methods to the interpreter + PyObject * thisCapsule = PyCapsule_New(this, nullptr, nullptr); + PyObject * module = Py_InitModule4("hyperion", effectMethods, nullptr, thisCapsule, PYTHON_API_VERSION); + + // add ledCount variable to the interpreter + PyObject_SetAttrString(module, "ledCount", Py_BuildValue("i", _imageProcessor->getLedCount())); + + // add a args variable to the interpreter + PyObject_SetAttrString(module, "args", json2python(_args)); + //PyObject_SetAttrString(module, "args", Py_BuildValue("s", _args.c_str())); + + // Set the end time if applicable + if (_timeout > 0) + { + _endTime = QDateTime::currentMSecsSinceEpoch() + _timeout; + } + + // Run the effect script + FILE* file = fopen(_script.c_str(), "r"); + if (file != nullptr) + { + PyRun_SimpleFile(file, _script.c_str()); + } + else + { + std::cerr << "Unable to open script file " << _script << std::endl; + } + + // Clean up the thread state + Py_EndInterpreter(_interpreterThreadState); + _interpreterThreadState = nullptr; + PyEval_ReleaseLock(); +} + +int Effect::getPriority() const +{ + return _priority; +} + +bool Effect::isAbortRequested() const +{ + return _abortRequested; +} + +void Effect::abort() +{ + _abortRequested = true; +} + +void Effect::effectFinished() +{ + emit effectFinished(this); +} + +PyObject *Effect::json2python(const Json::Value &json) const +{ + switch (json.type()) + { + case Json::nullValue: + return Py_BuildValue(""); + case Json::realValue: + return Py_BuildValue("d", json.asDouble()); + case Json::intValue: + case Json::uintValue: + return Py_BuildValue("i", json.asInt()); + case Json::booleanValue: + return Py_BuildValue("i", json.asBool() ? 1 : 0); + case Json::stringValue: + return Py_BuildValue("s", json.asCString()); + case Json::objectValue: + { + PyObject * obj = PyDict_New(); + for (Json::Value::iterator i = json.begin(); i != json.end(); ++i) + { + PyDict_SetItemString(obj, i.memberName(), json2python(*i)); + } + return obj; + } + case Json::arrayValue: + { + PyObject * list = PyList_New(json.size()); + for (Json::Value::iterator i = json.begin(); i != json.end(); ++i) + { + PyList_SetItem(list, i.index(), json2python(*i)); + } + return list; + } + } + + assert(false); + return nullptr; +} + +PyObject* Effect::wrapSetColor(PyObject *self, PyObject *args) +{ + // get the effect + Effect * effect = getEffect(self); + + // check if we have aborted already + if (effect->_abortRequested) + { + return Py_BuildValue(""); + } + + // determine the timeout + int timeout = effect->_timeout; + if (timeout > 0) + { + timeout = effect->_endTime - QDateTime::currentMSecsSinceEpoch(); + + // we are done if the time has passed + if (timeout <= 0) + { + return Py_BuildValue(""); + } + } + + // check the number of arguments + int argCount = PyTuple_Size(args); + if (argCount == 3) + { + // three seperate arguments for red, green, and blue + ColorRgb color; + if (PyArg_ParseTuple(args, "bbb", &color.red, &color.green, &color.blue)) + { + std::fill(effect->_colors.begin(), effect->_colors.end(), color); + effect->setColors(effect->_priority, effect->_colors, timeout, false); + return Py_BuildValue(""); + } + else + { + return nullptr; + } + } + else if (argCount == 1) + { + // bytearray of values + PyObject * bytearray = nullptr; + if (PyArg_ParseTuple(args, "O", &bytearray)) + { + if (PyByteArray_Check(bytearray)) + { + size_t length = PyByteArray_Size(bytearray); + if (length == 3 * effect->_imageProcessor->getLedCount()) + { + char * data = PyByteArray_AS_STRING(bytearray); + memcpy(effect->_colors.data(), data, length); + effect->setColors(effect->_priority, effect->_colors, timeout, false); + return Py_BuildValue(""); + } + else + { + PyErr_SetString(PyExc_RuntimeError, "Length of bytearray argument should be 3*ledCount"); + return nullptr; + } + } + else + { + PyErr_SetString(PyExc_RuntimeError, "Argument is not a bytearray"); + return nullptr; + } + } + else + { + return nullptr; + } + } + else + { + PyErr_SetString(PyExc_RuntimeError, "Function expect 1 or 3 arguments"); + return nullptr; + } + + // error + PyErr_SetString(PyExc_RuntimeError, "Unknown error"); + return nullptr; +} + +PyObject* Effect::wrapSetImage(PyObject *self, PyObject *args) +{ + // get the effect + Effect * effect = getEffect(self); + + // check if we have aborted already + if (effect->_abortRequested) + { + return Py_BuildValue(""); + } + + // determine the timeout + int timeout = effect->_timeout; + if (timeout > 0) + { + timeout = effect->_endTime - QDateTime::currentMSecsSinceEpoch(); + + // we are done if the time has passed + if (timeout <= 0) + { + return Py_BuildValue(""); + } + } + + // bytearray of values + int width, height; + PyObject * bytearray = nullptr; + if (PyArg_ParseTuple(args, "iiO", &width, &height, &bytearray)) + { + if (PyByteArray_Check(bytearray)) + { + int length = PyByteArray_Size(bytearray); + if (length == 3 * width * height) + { + Image image(width, height); + char * data = PyByteArray_AS_STRING(bytearray); + memcpy(image.memptr(), data, length); + + effect->_imageProcessor->process(image, effect->_colors); + effect->setColors(effect->_priority, effect->_colors, timeout, false); + return Py_BuildValue(""); + } + else + { + PyErr_SetString(PyExc_RuntimeError, "Length of bytearray argument should be 3*ledCount"); + return nullptr; + } + } + else + { + PyErr_SetString(PyExc_RuntimeError, "Argument 3 is not a bytearray"); + return nullptr; + } + } + else + { + return nullptr; + } + + // error + PyErr_SetString(PyExc_RuntimeError, "Unknown error"); + return nullptr; +} + +PyObject* Effect::wrapAbort(PyObject *self, PyObject *) +{ + Effect * effect = getEffect(self); + + // Test if the effect has reached it end time + if (effect->_timeout > 0 && QDateTime::currentMSecsSinceEpoch() > effect->_endTime) + { + effect->_abortRequested = true; + } + + return Py_BuildValue("i", effect->_abortRequested ? 1 : 0); +} + +Effect * Effect::getEffect(PyObject *self) +{ + // Get the effect from the capsule in the self pointer + return reinterpret_cast(PyCapsule_GetPointer(self, nullptr)); +} diff --git a/libsrc/effectengine/Effect.h b/libsrc/effectengine/Effect.h new file mode 100644 index 00000000..62c2ed75 --- /dev/null +++ b/libsrc/effectengine/Effect.h @@ -0,0 +1,67 @@ +#pragma once + +// Python includes +#include + +// Qt includes +#include + +// Hyperion includes +#include + +class Effect : public QThread +{ + Q_OBJECT + +public: + Effect(int priority, int timeout, const std::string & script, const Json::Value & args = Json::Value()); + virtual ~Effect(); + + virtual void run(); + + int getPriority() const; + + bool isAbortRequested() const; + +public slots: + void abort(); + +signals: + void effectFinished(Effect * effect); + + void setColors(int priority, const std::vector &ledColors, const int timeout_ms, bool clearEffects); + +private slots: + void effectFinished(); + +private: + PyObject * json2python(const Json::Value & json) const; + + // Wrapper methods for Python interpreter extra buildin methods + static PyMethodDef effectMethods[]; + static PyObject* wrapSetColor(PyObject *self, PyObject *args); + static PyObject* wrapSetImage(PyObject *self, PyObject *args); + static PyObject* wrapAbort(PyObject *self, PyObject *args); + static Effect * getEffect(PyObject *self); + +private: + const int _priority; + + const int _timeout; + + const std::string _script; + + const Json::Value _args; + + int64_t _endTime; + + PyThreadState * _interpreterThreadState; + + bool _abortRequested; + + /// The processor for translating images to led-values + ImageProcessor * _imageProcessor; + + /// Buffer for colorData + std::vector _colors; +}; diff --git a/libsrc/effectengine/EffectDefinition.schema.json b/libsrc/effectengine/EffectDefinition.schema.json new file mode 100644 index 00000000..44733252 --- /dev/null +++ b/libsrc/effectengine/EffectDefinition.schema.json @@ -0,0 +1,342 @@ +{ + "type" : "object", + "required" : true, + "properties" : { + "device" : { + "type" : "object", + "required" : true, + "properties" : { + "name" : { + "type" : "string", + "required" : true + }, + "type" : { + "type" : "string", + "required" : true + }, + "output" : { + "type" : "string", + "required" : true + }, + "rate" : { + "type" : "integer", + "required" : true, + "minimum" : 0 + }, + "colorOrder" : { + "type" : "string", + "required" : false + }, + "bgr-output" : { // deprecated + "type" : "boolean", + "required" : false + } + }, + "additionalProperties" : false + }, + "color": { + "type":"object", + "required":false, + "properties": { + "hsv" : { + "type" : "object", + "required" : false, + "properties" : { + "saturationGain" : { + "type" : "number", + "required" : false, + "minimum" : 0.0 + }, + "valueGain" : { + "type" : "number", + "required" : false, + "minimum" : 0.0 + } + }, + "additionalProperties" : false + }, + "red": { + "type":"object", + "required":false, + "properties":{ + "gamma": { + "type":"number", + "required":false + }, + "blacklevel": { + "type":"number", + "required":false + }, + "whitelevel": { + "type":"number", + "required":false + }, + "threshold": { + "type":"number", + "required":false, + "minimum" : 0.0, + "maximum" : 1.0 + } + }, + "additionalProperties" : false + }, + "green": { + "type":"object", + "required":false, + "properties":{ + "gamma": { + "type":"number", + "required":false + }, + "blacklevel": { + "type":"number", + "required":false + }, + "whitelevel": { + "type":"number", + "required":false + }, + "threshold": { + "type":"number", + "required":false, + "minimum" : 0.0, + "maximum" : 1.0 + } + }, + "additionalProperties" : false + }, + "blue": { + "type":"object", + "required":false, + "properties":{ + "gamma": { + "type":"number", + "required":false + }, + "whitelevel": { + "type":"number", + "required":false + }, + "blacklevel": { + "type":"number", + "required":false + }, + "threshold": { + "type":"number", + "required":false, + "minimum" : 0.0, + "maximum" : 1.0 + } + }, + "additionalProperties" : false + }, + "smoothing" : { + "type" : "object", + "required" : false, + "properties" : { + "type" : { + "type" : "enum", + "required" : true, + "values" : ["none", "linear"] + }, + "time_ms" : { + "type" : "integer", + "required" : false, + "minimum" : 10 + }, + "updateFrequency" : { + "type" : "number", + "required" : false, + "minimum" : 0.001 + } + }, + "additionalProperties" : false + } + + }, + "additionalProperties" : false + }, + "leds": { + "type":"array", + "required":true, + "items": { + "type":"object", + "properties": { + "index": { + "type":"integer", + "required":true + }, + "hscan": { + "type":"object", + "required":true, + "properties": { + "minimum": { + "type":"number", + "required":true + }, + "maximum": { + "type":"number", + "required":true + } + }, + "additionalProperties" : false + }, + "vscan": { + "type":"object", + "required":true, + "properties": { + "minimum": { + "type":"number", + "required":true + }, + "maximum": { + "type":"number", + "required":true + } + }, + "additionalProperties" : false + } + }, + "additionalProperties" : false + } + }, + "effects" : + { + "type" : "object", + "required" : false, + "properties" : { + "paths" : { + "type" : "array", + "required" : false, + "items" : { + "type" : "string" + } + } + }, + "additionalProperties" : false + }, + "blackborderdetector" : + { + "type" : "object", + "required" : false, + "properties" : { + "enable" : { + "type" : "boolean", + "required" : true + } + }, + "additionalProperties" : false + }, + "xbmcVideoChecker" : + { + "type" : "object", + "required" : false, + "properties" : { + "xbmcAddress" : { + "type" : "string", + "required" : true + }, + "xbmcTcpPort" : { + "type" : "integer", + "required" : true + }, + "grabVideo" : { + "type" : "boolean", + "required" : true + }, + "grabPictures" : { + "type" : "boolean", + "required" : true + }, + "grabAudio" : { + "type" : "boolean", + "required" : true + }, + "grabMenu" : { + "type" : "boolean", + "required" : true + } + }, + "additionalProperties" : false + }, + "bootsequence" : + { + "type" : "object", + "required" : false, + "properties" : { + "path" : { + "type" : "string", + "required" : true + }, + "effect" : { + "type" : "string", + "required" : true + } + }, + "additionalProperties" : false + }, + "framegrabber" : + { + "type" : "object", + "required" : false, + "properties" : { + "width" : { + "type" : "integer", + "required" : true + }, + "height" : { + "type" : "integer", + "required" : true + }, + "frequency_Hz" : { + "type" : "integer", + "required" : true + } + }, + "additionalProperties" : false + }, + "jsonServer" : + { + "type" : "object", + "required" : false, + "properties" : { + "port" : { + "type" : "integer", + "required" : true, + "minimum" : 0, + "maximum" : 65535 + } + }, + "additionalProperties" : false + }, + "protoServer" : + { + "type" : "object", + "required" : false, + "properties" : { + "port" : { + "type" : "integer", + "required" : true, + "minimum" : 0, + "maximum" : 65535 + } + }, + "additionalProperties" : false + }, + "boblightServer" : + { + "type" : "object", + "required" : false, + "properties" : { + "port" : { + "type" : "integer", + "required" : true, + "minimum" : 0, + "maximum" : 65535 + } + }, + "additionalProperties" : false + } + }, + "additionalProperties" : false +} diff --git a/libsrc/effectengine/EffectEngine.cpp b/libsrc/effectengine/EffectEngine.cpp new file mode 100644 index 00000000..a343ae4e --- /dev/null +++ b/libsrc/effectengine/EffectEngine.cpp @@ -0,0 +1,204 @@ +// Python includes +#include + +// Stl includes +#include + +// Qt includes +#include +#include +#include +#include + +// hyperion util includes +#include + +// effect engine includes +#include +#include "Effect.h" + +EffectEngine::EffectEngine(Hyperion * hyperion, const Json::Value & jsonEffectConfig) : + _hyperion(hyperion), + _availableEffects(), + _activeEffects(), + _mainThreadState(nullptr) +{ + qRegisterMetaType>("std::vector"); + + // connect the Hyperion channel clear feedback + connect(_hyperion, SIGNAL(channelCleared(int)), this, SLOT(channelCleared(int))); + connect(_hyperion, SIGNAL(allChannelsCleared()), this, SLOT(allChannelsCleared())); + + // read all effects + const Json::Value & paths = jsonEffectConfig["paths"]; + for (Json::UInt i = 0; i < paths.size(); ++i) + { + const std::string & path = paths[i].asString(); + QDir directory(QString::fromStdString(path)); + if (!directory.exists()) + { + std::cerr << "Effect directory can not be loaded: " << path << std::endl; + continue; + } + + QStringList filenames = directory.entryList(QStringList() << "*.json", QDir::Files, QDir::Name | QDir::IgnoreCase); + foreach (const QString & filename, filenames) + { + EffectDefinition def; + if (loadEffectDefinition(path, filename.toStdString(), def)) + { + _availableEffects.push_back(def); + } + } + } + + // initialize the python interpreter + std::cout << "Initializing Python interpreter" << std::endl; + Py_InitializeEx(0); + PyEval_InitThreads(); // Create the GIL + _mainThreadState = PyEval_SaveThread(); +} + +EffectEngine::~EffectEngine() +{ + // clean up the Python interpreter + std::cout << "Cleaning up Python interpreter" << std::endl; + PyEval_RestoreThread(_mainThreadState); + Py_Finalize(); +} + +const std::list &EffectEngine::getEffects() const +{ + return _availableEffects; +} + +bool EffectEngine::loadEffectDefinition(const std::string &path, const std::string &effectConfigFile, EffectDefinition & effectDefinition) +{ + std::string fileName = path + QDir::separator().toAscii() + effectConfigFile; + std::ifstream file(fileName.c_str()); + + if (!file.is_open()) + { + std::cerr << "Effect file '" << fileName << "' could not be loaded" << std::endl; + return false; + } + + // Read the json config file + Json::Reader jsonReader; + Json::Value config; + if (!jsonReader.parse(file, config, false)) + { + std::cerr << "Error while reading effect '" << fileName << "': " << jsonReader.getFormattedErrorMessages() << std::endl; + return false; + } + + // Read the json schema file + QResource schemaData(":effect-schema"); + JsonSchemaChecker schemaChecker; + Json::Value schema; + Json::Reader().parse(reinterpret_cast(schemaData.data()), reinterpret_cast(schemaData.data()) + schemaData.size(), schema, false); + schemaChecker.setSchema(schema); + if (!schemaChecker.validate(config)) + { + const std::list & errors = schemaChecker.getMessages(); + foreach (const std::string & error, errors) { + std::cerr << "Error while checking '" << fileName << "':" << error << std::endl; + } + return false; + } + + // setup the definition + effectDefinition.name = config["name"].asString(); + effectDefinition.script = path + QDir::separator().toAscii() + config["script"].asString(); + effectDefinition.args = config["args"]; + + // return succes + std::cout << "Effect loaded: " + effectDefinition.name << std::endl; + return true; +} + +int EffectEngine::runEffect(const std::string &effectName, int priority, int timeout) +{ + return runEffect(effectName, Json::Value(Json::nullValue), priority, timeout); +} + +int EffectEngine::runEffect(const std::string &effectName, const Json::Value &args, int priority, int timeout) +{ + std::cout << "run effect " << effectName << " on channel " << priority << std::endl; + + const EffectDefinition * effectDefinition = nullptr; + for (const EffectDefinition & e : _availableEffects) + { + if (e.name == effectName) + { + effectDefinition = &e; + break; + } + } + if (effectDefinition == nullptr) + { + // no such effect + std::cerr << "effect " << effectName << " not found" << std::endl; + return -1; + } + + return runEffectScript(effectDefinition->script, args.isNull() ? effectDefinition->args : args, priority, timeout); +} + +int EffectEngine::runEffectScript(const std::string &script, const Json::Value &args, int priority, int timeout) +{ + // clear current effect on the channel + channelCleared(priority); + + // create the effect + Effect * effect = new Effect(priority, timeout, script, args); + connect(effect, SIGNAL(setColors(int,std::vector,int,bool)), _hyperion, SLOT(setColors(int,std::vector,int,bool)), Qt::QueuedConnection); + connect(effect, SIGNAL(effectFinished(Effect*)), this, SLOT(effectFinished(Effect*))); + _activeEffects.push_back(effect); + + // start the effect + effect->start(); + + return 0; +} + +void EffectEngine::channelCleared(int priority) +{ + for (Effect * effect : _activeEffects) + { + if (effect->getPriority() == priority) + { + effect->abort(); + } + } +} + +void EffectEngine::allChannelsCleared() +{ + for (Effect * effect : _activeEffects) + { + effect->abort(); + } +} + +void EffectEngine::effectFinished(Effect *effect) +{ + if (!effect->isAbortRequested()) + { + // effect stopped by itself. Clear the channel + _hyperion->clear(effect->getPriority()); + } + + std::cout << "effect finished" << std::endl; + for (auto effectIt = _activeEffects.begin(); effectIt != _activeEffects.end(); ++effectIt) + { + if (*effectIt == effect) + { + _activeEffects.erase(effectIt); + break; + } + } + + // cleanup the effect + effect->deleteLater(); +} diff --git a/libsrc/effectengine/EffectEngine.qrc b/libsrc/effectengine/EffectEngine.qrc new file mode 100644 index 00000000..2f4ef03e --- /dev/null +++ b/libsrc/effectengine/EffectEngine.qrc @@ -0,0 +1,5 @@ + + + EffectDefinition.schema.json + + diff --git a/libsrc/hyperion/CMakeLists.txt b/libsrc/hyperion/CMakeLists.txt index 8a70c46a..b1ccc93e 100644 --- a/libsrc/hyperion/CMakeLists.txt +++ b/libsrc/hyperion/CMakeLists.txt @@ -11,15 +11,6 @@ include_directories( ../../include/hidapi ${LIBUSB_1_INCLUDE_DIRS}) # for Lightpack device -# set the build options -option (LIBUDEV_REQUIRED "Add libudev to the build (needed on Ubuntu 13.10, but gives errors when added on the RPi) :-S" OFF) -message(STATUS "LIBUDEV_REQUIRED = " ${LIBUDEV_REQUIRED}) - -if (LIBUDEV_REQUIRED) - find_package(UDev REQUIRED) - include_directories(${UDEV_INCLUDE_DIR}) -endif (LIBUDEV_REQUIRED) - # Group the headers that go through the MOC compiler SET(Hyperion_QT_HEADERS ${CURRENT_HEADER_DIR}/Hyperion.h @@ -49,6 +40,7 @@ SET(Hyperion_HEADERS ${CURRENT_SOURCE_DIR}/device/LedDeviceLpd6803.h ${CURRENT_SOURCE_DIR}/device/LedDeviceLpd8806.h ${CURRENT_SOURCE_DIR}/device/LedDeviceLightpack.h + ${CURRENT_SOURCE_DIR}/device/LedDevicePaintpack.h ${CURRENT_SOURCE_DIR}/device/LedDeviceMultiLightpack.h ) @@ -74,6 +66,7 @@ SET(Hyperion_SOURCES ${CURRENT_SOURCE_DIR}/device/LedDeviceLpd8806.cpp ${CURRENT_SOURCE_DIR}/device/LedDeviceAdalight.cpp ${CURRENT_SOURCE_DIR}/device/LedDeviceLightpack.cpp + ${CURRENT_SOURCE_DIR}/device/LedDevicePaintpack.cpp ${CURRENT_SOURCE_DIR}/device/LedDeviceMultiLightpack.cpp ) @@ -95,14 +88,10 @@ add_library(hyperion target_link_libraries(hyperion hyperion-utils + effectengine + hidapi-libusb serialport ${LIBUSB_1_LIBRARIES} #apt-get install libusb-1.0-0-dev ${CMAKE_THREAD_LIBS_INIT} ${QT_LIBRARIES} ) - -if (LIBUDEV_REQUIRED) - target_link_libraries(hyperion - ${UDEV_LIBRARIES} # apt-get install libudev-dev - ) -endif (LIBUDEV_REQUIRED) diff --git a/libsrc/hyperion/Hyperion.cpp b/libsrc/hyperion/Hyperion.cpp index 8c746ef9..1f245781 100644 --- a/libsrc/hyperion/Hyperion.cpp +++ b/libsrc/hyperion/Hyperion.cpp @@ -4,6 +4,7 @@ // QT includes #include +#include #include #include #include @@ -22,12 +23,16 @@ #include "device/LedDeviceTest.h" #include "device/LedDeviceWs2801.h" #include "device/LedDeviceAdalight.h" +#include "device/LedDevicePaintpack.h" #include "device/LedDeviceLightpack.h" #include "device/LedDeviceMultiLightpack.h" #include "MultiColorTransform.h" #include "LinearColorSmoothing.h" +// effect engine includes +#include + LedDevice* Hyperion::createDevice(const Json::Value& deviceConfig) { std::cout << "Device configuration: " << deviceConfig << std::endl; @@ -36,7 +41,7 @@ LedDevice* Hyperion::createDevice(const Json::Value& deviceConfig) std::transform(type.begin(), type.end(), type.begin(), ::tolower); LedDevice* device = nullptr; - if (type == "ws2801") + if (type == "ws2801" || type == "lightberry") { const std::string output = deviceConfig["output"].asString(); const unsigned rate = deviceConfig["rate"].asInt(); @@ -95,6 +100,13 @@ LedDevice* Hyperion::createDevice(const Json::Value& deviceConfig) device = deviceLightpack; } + else if (type == "paintpack") + { + LedDevicePaintpack * devicePainLightpack = new LedDevicePaintpack(); + devicePainLightpack->open(); + + device = devicePainLightpack; + } else if (type == "multi-lightpack") { LedDeviceMultiLightpack* deviceLightpack = new LedDeviceMultiLightpack(); @@ -211,7 +223,15 @@ MultiColorTransform * Hyperion::createLedColorsTransform(const unsigned ledCnt, ColorTransform * colorTransform = createColorTransform(config); transform->addTransform(colorTransform); - const QString ledIndicesStr = config.get("leds", "").asCString(); + const QString ledIndicesStr = QString(config.get("leds", "").asCString()).trimmed(); + if (ledIndicesStr.compare("*") == 0) + { + // Special case for indices '*' => all leds + transform->setTransformForLed(colorTransform->_id, 0, ledCnt-1); + std::cout << "ColorTransform '" << colorTransform->_id << "' => [0; "<< ledCnt-1 << "]" << std::endl; + continue; + } + if (!overallExp.exactMatch(ledIndicesStr)) { std::cerr << "Given led indices " << i << " not correct format: " << ledIndicesStr.toStdString() << std::endl; @@ -342,6 +362,7 @@ Hyperion::Hyperion(const Json::Value &jsonConfig) : _raw2ledTransform(createLedColorsTransform(_ledString.leds().size(), jsonConfig["color"])), _colorOrder(createColorOrder(jsonConfig["device"])), _device(createDevice(jsonConfig["device"])), + _effectEngine(nullptr), _timer() { if (!_raw2ledTransform->verifyTransforms()) @@ -358,6 +379,9 @@ Hyperion::Hyperion(const Json::Value &jsonConfig) : _timer.setSingleShot(true); QObject::connect(&_timer, SIGNAL(timeout()), this, SLOT(update())); + // create the effect engine + _effectEngine = new EffectEngine(this, jsonConfig["effects"]); + // initialize the leds update(); } @@ -369,6 +393,9 @@ Hyperion::~Hyperion() clearall(); _device->switchOff(); + // delete the effect engine + delete _effectEngine; + // Delete the Led-String delete _device; @@ -381,17 +408,23 @@ unsigned Hyperion::getLedCount() const return _ledString.leds().size(); } -void Hyperion::setColor(int priority, const ColorRgb &color, const int timeout_ms) +void Hyperion::setColor(int priority, const ColorRgb &color, const int timeout_ms, bool clearEffects) { // create led output std::vector ledColors(_ledString.leds().size(), color); // set colors - setColors(priority, ledColors, timeout_ms); + setColors(priority, ledColors, timeout_ms, clearEffects); } -void Hyperion::setColors(int priority, const std::vector& ledColors, const int timeout_ms) +void Hyperion::setColors(int priority, const std::vector& ledColors, const int timeout_ms, bool clearEffects) { + // clear effects if this call does not come from an effect + if (clearEffects) + { + _effectEngine->channelCleared(priority); + } + if (timeout_ms > 0) { const uint64_t timeoutTime = QDateTime::currentMSecsSinceEpoch() + timeout_ms; @@ -435,6 +468,10 @@ void Hyperion::clear(int priority) update(); } } + + // send clear signal to the effect engine + // (outside the check so the effect gets cleared even when the effect is not sending colors) + _effectEngine->channelCleared(priority); } void Hyperion::clearall() @@ -443,6 +480,9 @@ void Hyperion::clearall() // update leds update(); + + // send clearall signal to the effect engine + _effectEngine->allChannelsCleared(); } QList Hyperion::getActivePriorities() const @@ -455,6 +495,21 @@ const Hyperion::InputInfo &Hyperion::getPriorityInfo(const int priority) const return _muxer.getInputInfo(priority); } +const std::list & Hyperion::getEffects() const +{ + return _effectEngine->getEffects(); +} + +int Hyperion::setEffect(const std::string &effectName, int priority, int timeout) +{ + return _effectEngine->runEffect(effectName, priority, timeout); +} + +int Hyperion::setEffect(const std::string &effectName, const Json::Value &args, int priority, int timeout) +{ + return _effectEngine->runEffect(effectName, args, priority, timeout); +} + void Hyperion::update() { // Update the muxer, cleaning obsolete priorities diff --git a/libsrc/hyperion/ImageProcessor.cpp b/libsrc/hyperion/ImageProcessor.cpp index a0af2077..ec10f52c 100644 --- a/libsrc/hyperion/ImageProcessor.cpp +++ b/libsrc/hyperion/ImageProcessor.cpp @@ -21,6 +21,11 @@ ImageProcessor::~ImageProcessor() delete _borderProcessor; } +unsigned ImageProcessor::getLedCount() const +{ + return mLedString.leds().size(); +} + void ImageProcessor::setSize(const unsigned width, const unsigned height) { // Check if the existing buffer-image is already the correct dimensions diff --git a/libsrc/hyperion/device/LedDevicePaintpack.cpp b/libsrc/hyperion/device/LedDevicePaintpack.cpp new file mode 100644 index 00000000..7ed0c847 --- /dev/null +++ b/libsrc/hyperion/device/LedDevicePaintpack.cpp @@ -0,0 +1,77 @@ + +// Hyperion includes +#include "LedDevicePaintpack.h" + +LedDevicePaintpack::LedDevicePaintpack() : + LedDevice(), + _deviceHandle(nullptr) +{ + // empty +} + +int LedDevicePaintpack::open() +{ + // initialize the usb context + int error = hid_init(); + if (error != 0) + { + std::cerr << "Error while initializing the hidapi context" << std::endl; + return -1; + } + std::cout << "Hidapi initialized" << std::endl; + + // Initialise the paintpack device + const unsigned short Paintpack_VendorId = 0x0ebf; + const unsigned short Paintpack_ProductId = 0x0025; + _deviceHandle = hid_open(Paintpack_VendorId, Paintpack_ProductId, nullptr); + if (_deviceHandle == nullptr) + { + // Failed to open the device + std::cerr << "Failed to open HID Paintpakc device " << std::endl; + return -1; + } + + return 0; +} + +LedDevicePaintpack::~LedDevicePaintpack() +{ + if (_deviceHandle != nullptr) + { + hid_close(_deviceHandle); + _deviceHandle = nullptr; + } + + hid_exit(); +} + +int LedDevicePaintpack::write(const std::vector& ledValues) +{ + if (_ledBuffer.size() < 3 + ledValues.size()*3) + { + _ledBuffer.resize(3 + ledValues.size()*3, uint8_t(0)); + + _ledBuffer[0] = 0; + _ledBuffer[1] = 3; + _ledBuffer[2] = 0; + } + + auto bufIt = _ledBuffer.begin()+3; + for (const ColorRgb & ledValue : ledValues) + { + *bufIt = ledValue.red; + ++bufIt; + *bufIt = ledValue.green; + ++bufIt; + *bufIt = ledValue.blue; + ++bufIt; + } + + return hid_write(_deviceHandle, _ledBuffer.data(), _ledBuffer.size()); +} + +int LedDevicePaintpack::switchOff() +{ + std::fill(_ledBuffer.begin()+3, _ledBuffer.end(), uint8_t(0)); + return hid_write(_deviceHandle, _ledBuffer.data(), _ledBuffer.size()); +} diff --git a/libsrc/hyperion/device/LedDevicePaintpack.h b/libsrc/hyperion/device/LedDevicePaintpack.h new file mode 100644 index 00000000..8247bfb6 --- /dev/null +++ b/libsrc/hyperion/device/LedDevicePaintpack.h @@ -0,0 +1,59 @@ +#pragma once + +// STL includes +#include + +// libusb include +#include + +// Hyperion includes +#include + +/// +/// LedDevice implementation for a paintpack device () +/// +class LedDevicePaintpack : public LedDevice +{ +public: + /** + * Constructs the paintpack device + */ + LedDevicePaintpack(); + + /** + * Destructs the paintpack device, closes USB connection if open + */ + virtual ~LedDevicePaintpack(); + + /** + * Opens the Paintpack device + * + * @return Zero on succes else negative + */ + int open(); + + /// + /// Writes the RGB-Color values to the leds. + /// + /// @param[in] ledValues The RGB-color per led + /// + /// @return Zero on success else negative + /// + virtual int write(const std::vector& ledValues); + + /// + /// Switch the leds off + /// + /// @return Zero on success else negative + /// + virtual int switchOff(); + +private: + /// libusb device handle + hid_device * _deviceHandle; + + /// buffer for led data + std::vector _ledBuffer; + + +}; diff --git a/libsrc/hyperion/hyperion.schema.json b/libsrc/hyperion/hyperion.schema.json index d6f7c7bf..44733252 100644 --- a/libsrc/hyperion/hyperion.schema.json +++ b/libsrc/hyperion/hyperion.schema.json @@ -200,6 +200,21 @@ "additionalProperties" : false } }, + "effects" : + { + "type" : "object", + "required" : false, + "properties" : { + "paths" : { + "type" : "array", + "required" : false, + "items" : { + "type" : "string" + } + } + }, + "additionalProperties" : false + }, "blackborderdetector" : { "type" : "object", @@ -249,12 +264,12 @@ "type" : "object", "required" : false, "properties" : { - "type" : { + "path" : { "type" : "string", "required" : true }, - "duration_ms" : { - "type" : "integer", + "effect" : { + "type" : "string", "required" : true } }, diff --git a/libsrc/jsonserver/JsonClientConnection.cpp b/libsrc/jsonserver/JsonClientConnection.cpp index 44bcec3c..d478a4d2 100644 --- a/libsrc/jsonserver/JsonClientConnection.cpp +++ b/libsrc/jsonserver/JsonClientConnection.cpp @@ -95,6 +95,8 @@ void JsonClientConnection::handleMessage(const std::string &messageString) handleColorCommand(message); else if (command == "image") handleImageCommand(message); + else if (command == "effect") + handleEffectCommand(message); else if (command == "serverinfo") handleServerInfoCommand(message); else if (command == "clear") @@ -112,10 +114,33 @@ void JsonClientConnection::handleColorCommand(const Json::Value &message) // extract parameters int priority = message["priority"].asInt(); int duration = message.get("duration", -1).asInt(); - ColorRgb color = {uint8_t(message["color"][0u].asInt()), uint8_t(message["color"][1u].asInt()), uint8_t(message["color"][2u].asInt())}; + + std::vector colorData(_hyperion->getLedCount()); + const Json::Value & jsonColor = message["color"]; + Json::UInt i = 0; + for (; i < jsonColor.size()/3 && i < _hyperion->getLedCount(); ++i) + { + colorData[i].red = uint8_t(message["color"][3u*i].asInt()); + colorData[i].green = uint8_t(message["color"][3u*i+1u].asInt()); + colorData[i].blue = uint8_t(message["color"][3u*i+2u].asInt()); + } + + // copy full blocks of led colors + unsigned size = i; + while (i + size < _hyperion->getLedCount()) + { + memcpy(&(colorData[i]), colorData.data(), size * sizeof(ColorRgb)); + i += size; + } + + // copy remaining block of led colors + if (i < _hyperion->getLedCount()) + { + memcpy(&(colorData[i]), colorData.data(), (_hyperion->getLedCount()-i) * sizeof(ColorRgb)); + } // set output - _hyperion->setColor(priority, color, duration); + _hyperion->setColors(priority, colorData, duration); // send reply sendSuccessReply(); @@ -152,7 +177,29 @@ void JsonClientConnection::handleImageCommand(const Json::Value &message) sendSuccessReply(); } -void JsonClientConnection::handleServerInfoCommand(const Json::Value &message) +void JsonClientConnection::handleEffectCommand(const Json::Value &message) +{ + // extract parameters + int priority = message["priority"].asInt(); + int duration = message.get("duration", -1).asInt(); + const Json::Value & effect = message["effect"]; + const std::string & effectName = effect["name"].asString(); + + // set output + if (effect.isMember("args")) + { + _hyperion->setEffect(effectName, effect["args"], priority, duration); + } + else + { + _hyperion->setEffect(effectName, priority, duration); + } + + // send reply + sendSuccessReply(); +} + +void JsonClientConnection::handleServerInfoCommand(const Json::Value &) { // create result Json::Value result; @@ -160,7 +207,7 @@ void JsonClientConnection::handleServerInfoCommand(const Json::Value &message) Json::Value & info = result["info"]; // collect priority information - Json::Value & priorities = info["priorities"]; + Json::Value & priorities = info["priorities"] = Json::Value(Json::arrayValue); uint64_t now = QDateTime::currentMSecsSinceEpoch(); QList activePriorities = _hyperion->getActivePriorities(); foreach (int priority, activePriorities) { @@ -208,6 +255,19 @@ void JsonClientConnection::handleServerInfoCommand(const Json::Value &message) whitelevel.append(colorTransform->_rgbBlueTransform.getWhitelevel()); } + // collect effect info + Json::Value & effects = info["effects"] = Json::Value(Json::arrayValue); + const std::list & effectsDefinitions = _hyperion->getEffects(); + for (const EffectDefinition & effectDefinition : effectsDefinitions) + { + Json::Value effect; + effect["name"] = effectDefinition.name; + effect["script"] = effectDefinition.script; + effect["args"] = effectDefinition.args; + + effects.append(effect); + } + // send the result sendMessage(result); } diff --git a/libsrc/jsonserver/JsonClientConnection.h b/libsrc/jsonserver/JsonClientConnection.h index 5381baff..dc80deba 100644 --- a/libsrc/jsonserver/JsonClientConnection.h +++ b/libsrc/jsonserver/JsonClientConnection.h @@ -78,6 +78,13 @@ private: /// void handleImageCommand(const Json::Value & message); + /// + /// Handle an incoming JSON Effect message + /// + /// @param message the incoming message + /// + void handleEffectCommand(const Json::Value & message); + /// /// Handle an incoming JSON Server info message /// diff --git a/libsrc/jsonserver/JsonSchemas.qrc b/libsrc/jsonserver/JsonSchemas.qrc index 5c2f1afb..7736e530 100644 --- a/libsrc/jsonserver/JsonSchemas.qrc +++ b/libsrc/jsonserver/JsonSchemas.qrc @@ -1,11 +1,12 @@ - - schema/schema.json - schema/schema-color.json - schema/schema-image.json - schema/schema-serverinfo.json - schema/schema-clear.json - schema/schema-clearall.json - schema/schema-transform.json - + + schema/schema.json + schema/schema-color.json + schema/schema-image.json + schema/schema-serverinfo.json + schema/schema-clear.json + schema/schema-clearall.json + schema/schema-transform.json + schema/schema-effect.json + diff --git a/libsrc/jsonserver/schema/schema-color.json b/libsrc/jsonserver/schema/schema-color.json index c825698d..1cd98bd2 100644 --- a/libsrc/jsonserver/schema/schema-color.json +++ b/libsrc/jsonserver/schema/schema-color.json @@ -21,8 +21,7 @@ "items" :{ "type" : "integer" }, - "minItems": 3, - "maxItems": 3 + "minItems": 3 } }, "additionalProperties": false diff --git a/libsrc/jsonserver/schema/schema-effect.json b/libsrc/jsonserver/schema/schema-effect.json new file mode 100644 index 00000000..3a690d7d --- /dev/null +++ b/libsrc/jsonserver/schema/schema-effect.json @@ -0,0 +1,35 @@ +{ + "type":"object", + "required":true, + "properties":{ + "command": { + "type" : "string", + "required" : true, + "enum" : ["effect"] + }, + "priority": { + "type": "integer", + "required": true + }, + "duration": { + "type": "integer", + "required": false + }, + "effect": { + "type": "object", + "required": true, + "properties" :{ + "name" : { + "type" : "string", + "required" : true + }, + "args" : { + "type" : "object", + "required" : false + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false +} diff --git a/libsrc/jsonserver/schema/schema.json b/libsrc/jsonserver/schema/schema.json index 1cbbbb06..e931f7b7 100644 --- a/libsrc/jsonserver/schema/schema.json +++ b/libsrc/jsonserver/schema/schema.json @@ -5,7 +5,7 @@ "command": { "type" : "string", "required" : true, - "enum" : ["color", "image", "serverinfo", "clear", "clearall", "transform"] + "enum" : ["color", "image", "effect", "serverinfo", "clear", "clearall", "transform"] } } } diff --git a/libsrc/utils/jsonschema/JsonSchemaChecker.cpp b/libsrc/utils/jsonschema/JsonSchemaChecker.cpp index da0c51d8..3411d84d 100644 --- a/libsrc/utils/jsonschema/JsonSchemaChecker.cpp +++ b/libsrc/utils/jsonschema/JsonSchemaChecker.cpp @@ -168,8 +168,8 @@ void JsonSchemaChecker::checkType(const Json::Value & value, const Json::Value & wrongType = !value.isString(); else if (type == "any") wrongType = false; - else - assert(false); +// else +// assert(false); if (wrongType) { diff --git a/src/config-tool/ConfigTool/src/org/hyperion/hypercon/ConfigurationFile.java b/src/config-tool/ConfigTool/src/org/hyperion/hypercon/ConfigurationFile.java index 1abd46c1..d4986b99 100644 --- a/src/config-tool/ConfigTool/src/org/hyperion/hypercon/ConfigurationFile.java +++ b/src/config-tool/ConfigTool/src/org/hyperion/hypercon/ConfigurationFile.java @@ -1,5 +1,6 @@ package org.hyperion.hypercon; +import java.awt.Color; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; @@ -12,9 +13,19 @@ import java.lang.reflect.ParameterizedType; import java.util.Properties; import java.util.Vector; +/** + * Class for supporting the serialisation and deserialisation of HyperCon settings. + */ public class ConfigurationFile { + + /** Temporary storage of the HyperCon configuration */ private final Properties mProps = new Properties(); + /** + * Loads the configuration of HyperCon from the given filename into this {@link ConfigurationFile} + * + * @param pFilename The absolute filename containing the configuration + */ public void load(String pFilename) { mProps.clear(); // try (InputStream in = new InflaterInputStream(new FileInputStream(pFilename))){ @@ -27,6 +38,11 @@ public class ConfigurationFile { } } + /** + * Saves the configuration of this {@link ConfigurationFile} to the given filename + * + * @param pFilename The absolute filename to which to save the HyperCon configuration + */ public void save(String pFilename) { // try (OutputStream out = new DeflaterOutputStream(new FileOutputStream(pFilename))) { // try (OutputStream out = new GZIPOutputStream(new FileOutputStream(pFilename))) { @@ -37,9 +53,22 @@ public class ConfigurationFile { } } + /** + * Stores the given object to the local properties object + * + * @param pObj The object to store + */ public void store(Object pObj) { store(pObj, pObj.getClass().getSimpleName(), ""); } + + /** + * Stores the given object to the local properties object (with given preamble and postamble) + * + * @param pObj The object to store + * @param preamble The preamble prepended to the key of the object members + * @param postamble The postamble appended to the key of the object members + */ public void store(Object pObj, String preamble, String postamble) { String className = pObj.getClass().getSimpleName(); // Retrieve the member variables @@ -55,37 +84,80 @@ public class ConfigurationFile { try { Object value = field.get(pObj); - if (value.getClass().isEnum()) { + if (field.getType() == boolean.class) { + mProps.setProperty(key, Boolean.toString((boolean) value)); + } else if (field.getType() == int.class) { + mProps.setProperty(key, Integer.toString((int) value)); + } else if (field.getType() == double.class) { + mProps.setProperty(key, Double.toString((double) value)); + } else if (field.getType() == String.class) { + mProps.setProperty(key, (String)value); + } else if (field.getType() == Color.class) { + Color color = (Color)value; + mProps.setProperty(key, String.format("[%d; %d; %d]", color.getRed(), color.getGreen(), color.getBlue())); + } else if (value.getClass().isEnum()) { mProps.setProperty(key, ((Enum)value).name()); - } else if (value.getClass().isAssignableFrom(Vector.class)) { + } else if (value instanceof Vector) { @SuppressWarnings("unchecked") Vector v = (Vector) value; for (int i=0; i vector; try { @@ -157,19 +229,56 @@ public class ConfigurationFile { field.set(pObj, Integer.parseInt(value)); } else if (field.getType() == double.class) { field.set(pObj, Double.parseDouble(value)); + } else if (field.getType() == Color.class) { + String[] channelValues = value.substring(1, value.length()-1).split(";"); + field.set(pObj, new Color(Integer.parseInt(channelValues[0].trim()), Integer.parseInt(channelValues[1].trim()), Integer.parseInt(channelValues[2].trim()))); + } else if (field.getType() == String.class) { + field.set(pObj, value); } else if (field.getType().isEnum()) { Method valMet = field.getType().getMethod("valueOf", String.class); Object enumVal = valMet.invoke(null, value); field.set(pObj, enumVal); - } else { - field.set(pObj, value); + } else if (field.getType() == Object.class) { + // We can not infer from the type of the field, let's try the actual stored value + if (value.isEmpty()) { + // We will never known ... + } else if (value.startsWith("[") && value.endsWith("]")) { + String[] channelValues = value.substring(1, value.length()-1).split(";"); + field.set(pObj, new Color(Integer.parseInt(channelValues[0].trim()), Integer.parseInt(channelValues[1].trim()), Integer.parseInt(channelValues[2].trim()))); + } else if (value.startsWith("\"") && value.endsWith("\"")) { + field.set(pObj, value.substring(1, value.length()-1)); + } else { + try { + int i = Integer.parseInt(value); + field.set(pObj, i); + } catch (Throwable t1) { + try { + double d = Double.parseDouble(value); + field.set(pObj, d); + } catch (Throwable t2) { + try { + boolean bool = Boolean.parseBoolean(value); + field.set(pObj, bool); + } catch (Throwable t3) { + + } + } + } + } } } catch (Throwable t) { System.out.println("Failed to parse value(" + value + ") for " + key); + t.printStackTrace(); } } } + /** + * Returns a String representation of this ConfigurationFile, which is the {@link #toString()} + * of the underlying {@link Properties} + * + * @return The String representation of this ConfigurationFile + */ @Override public String toString() { return mProps.toString(); diff --git a/src/config-tool/ConfigTool/src/org/hyperion/hypercon/HyperConConfig.java b/src/config-tool/ConfigTool/src/org/hyperion/hypercon/HyperConConfig.java new file mode 100644 index 00000000..25025f89 --- /dev/null +++ b/src/config-tool/ConfigTool/src/org/hyperion/hypercon/HyperConConfig.java @@ -0,0 +1,6 @@ +package org.hyperion.hypercon; + +public class HyperConConfig { + + public boolean loadDefaultEffect = true; +} diff --git a/src/config-tool/ConfigTool/src/org/hyperion/hypercon/JsonStringBuffer.java b/src/config-tool/ConfigTool/src/org/hyperion/hypercon/JsonStringBuffer.java new file mode 100644 index 00000000..853423d2 --- /dev/null +++ b/src/config-tool/ConfigTool/src/org/hyperion/hypercon/JsonStringBuffer.java @@ -0,0 +1,226 @@ +package org.hyperion.hypercon; + +public class JsonStringBuffer { + + private final StringBuffer mStrBuf = new StringBuffer(); + + private final int mStartIndentLevel; + private int mIndentLevel = 0; + + /** Flag indicating that the parts written are 'commented-out' */ + private boolean mComment = false; + + public JsonStringBuffer() { + this(0); + + mStrBuf.append("{\n"); + ++mIndentLevel; + } + + public JsonStringBuffer(int pIndentLevel) { + mStartIndentLevel = pIndentLevel; + mIndentLevel = pIndentLevel; + } + + public void newLine() { + mStrBuf.append('\n'); + } + + public void finish() { + + for (int i=0; i leds; @@ -55,14 +54,23 @@ public class LedString { String colorJson = mColorConfig.toJsonString(); fw.write(colorJson + ",\n\n"); - String ledJson = ledToJsonString(); - fw.write(ledJson + ",\n\n"); + JsonStringBuffer jsonBuf = new JsonStringBuffer(1); + + ledsAppendTo(jsonBuf); - String blackBorderJson = mProcessConfig.getBlackborderJson(); - fw.write(blackBorderJson + ",\n\n"); + jsonBuf.newLine(); - String miscJson = mMiscConfig.toJsonString(); - fw.write(miscJson + "\n"); + mProcessConfig.appendTo(jsonBuf); + + jsonBuf.newLine(); + + mMiscConfig.appendTo(jsonBuf); + + jsonBuf.newLine(); + + jsonBuf.addValue("endOfJson", "endOfJson", true); + + fw.write(jsonBuf.toString()); fw.write("}\n"); } catch (IOException e) { @@ -70,42 +78,28 @@ public class LedString { } } - /** - * Converts the list with leds specifications to a JSON string as used by the Hyperion Deamon - * - * @return The JSON string with led-specifications - */ - String ledToJsonString() { - StringBuffer strBuf = new StringBuffer(); - - strBuf.append("\t/// The configuration for each individual led. This contains the specification of the area \n"); - strBuf.append("\t/// averaged of an input image for each led to determine its color. Each item in the list \n"); - strBuf.append("\t/// contains the following fields:\n"); - strBuf.append("\t/// * index: The index of the led. This determines its location in the string of leds; zero \n"); - strBuf.append("\t/// being the first led.\n"); - strBuf.append("\t/// * hscan: The fractional part of the image along the horizontal used for the averaging \n"); - strBuf.append("\t/// (minimum and maximum inclusive)\n"); - strBuf.append("\t/// * vscan: The fractional part of the image along the vertical used for the averaging \n"); - strBuf.append("\t/// (minimum and maximum inclusive)\n"); - - strBuf.append("\t\"leds\" : \n"); - strBuf.append("\t[\n"); + void ledsAppendTo(JsonStringBuffer pJsonBuf) { + String ledComment = + " The configuration for each individual led. This contains the specification of the area \n" + + " averaged of an input image for each led to determine its color. Each item in the list \n" + + " contains the following fields:\n" + + " * index: The index of the led. This determines its location in the string of leds; zero \n" + + " being the first led.\n" + + " * hscan: The fractional part of the image along the horizontal used for the averaging \n" + + " (minimum and maximum inclusive)\n" + + " * vscan: The fractional part of the image along the vertical used for the averaging \n" + + " (minimum and maximum inclusive)\n"; + pJsonBuf.writeComment(ledComment); + pJsonBuf.startArray("leds"); for (Led led : leds) { - strBuf.append("\t\t{\n"); - strBuf.append(String.format(Locale.ROOT, "\t\t\t\"index\" : %d,\n", led.mLedSeqNr)); - strBuf.append(String.format(Locale.ROOT, "\t\t\t\"hscan\" : { \"minimum\" : %.4f, \"maximum\" : %.4f },\n", led.mImageRectangle.getMinX(), led.mImageRectangle.getMaxX())); - strBuf.append(String.format(Locale.ROOT, "\t\t\t\"vscan\" : { \"minimum\" : %.4f, \"maximum\" : %.4f }\n", led.mImageRectangle.getMinY(), led.mImageRectangle.getMaxY())); - if (led != leds.lastElement()) { - strBuf.append("\t\t},\n"); - } else { - strBuf.append("\t\t}\n"); - } + pJsonBuf.startObject(""); + pJsonBuf.addValue("index", led.mLedSeqNr, false); + pJsonBuf.addRawValue("hscan", String.format(Locale.ENGLISH, "{ %1$cminimum%1$c : %2$.4f, %1$cmaximum%1$c : %3$.4f }", '"', led.mImageRectangle.getMinX(), led.mImageRectangle.getMaxX()), false); + pJsonBuf.addRawValue("vscan", String.format(Locale.ENGLISH, "{ %1$cminimum%1$c : %2$.4f, %1$cmaximum%1$c : %3$.4f }", '"', led.mImageRectangle.getMinY(), led.mImageRectangle.getMaxY()), true); + pJsonBuf.stopObject(led.equals(leds.get(leds.size()-1))); } - - strBuf.append("\t]"); - - return strBuf.toString(); + pJsonBuf.stopArray(false); } } diff --git a/src/config-tool/ConfigTool/src/org/hyperion/hypercon/Main.java b/src/config-tool/ConfigTool/src/org/hyperion/hypercon/Main.java index e0fdc7e8..c2f0681d 100644 --- a/src/config-tool/ConfigTool/src/org/hyperion/hypercon/Main.java +++ b/src/config-tool/ConfigTool/src/org/hyperion/hypercon/Main.java @@ -16,6 +16,9 @@ import org.hyperion.hypercon.gui.ConfigPanel; */ public class Main { public static final String configFilename = "hypercon.dat"; + + /** Some application settings (for easy/dirty access) */ + public static final HyperConConfig HyperConConfig = new HyperConConfig(); /** * Entry point to start HyperCon @@ -42,6 +45,7 @@ public class Main { @Override public void windowClosing(WindowEvent e) { ConfigurationFile configFile = new ConfigurationFile(); + configFile.store(Main.HyperConConfig); configFile.store(ledString.mDeviceConfig); configFile.store(ledString.mLedFrameConfig); configFile.store(ledString.mProcessConfig); @@ -54,6 +58,7 @@ public class Main { if (new File(configFilename).exists()) { ConfigurationFile configFile = new ConfigurationFile(); configFile.load(configFilename); + configFile.restore(Main.HyperConConfig); configFile.restore(ledString.mDeviceConfig); configFile.restore(ledString.mLedFrameConfig); configFile.restore(ledString.mProcessConfig); diff --git a/src/config-tool/ConfigTool/src/org/hyperion/hypercon/gui/BootSequencePanel.java b/src/config-tool/ConfigTool/src/org/hyperion/hypercon/gui/BootSequencePanel.java deleted file mode 100644 index 606570cd..00000000 --- a/src/config-tool/ConfigTool/src/org/hyperion/hypercon/gui/BootSequencePanel.java +++ /dev/null @@ -1,126 +0,0 @@ -package org.hyperion.hypercon.gui; - -import java.awt.Dimension; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.beans.Transient; - -import javax.swing.BorderFactory; -import javax.swing.GroupLayout; -import javax.swing.JCheckBox; -import javax.swing.JComboBox; -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.JSpinner; -import javax.swing.SpinnerNumberModel; -import javax.swing.event.ChangeEvent; -import javax.swing.event.ChangeListener; - -import org.hyperion.hypercon.spec.BootSequence; -import org.hyperion.hypercon.spec.MiscConfig; - -public class BootSequencePanel extends JPanel { - - private final MiscConfig mMiscConfig; - - private JCheckBox mBootSequenceCheck; - private JLabel mBootSequenceLabel; - private JComboBox mBootSequenceCombo; - private JLabel mBootSequenceLengthLabel; - private JSpinner mBootSequenceLengthSpinner; - - public BootSequencePanel(final MiscConfig pMiscconfig) { - super(); - - mMiscConfig = pMiscconfig; - - initialise(); - } - - @Override - @Transient - public Dimension getMaximumSize() { - Dimension maxSize = super.getMaximumSize(); - Dimension prefSize = super.getPreferredSize(); - return new Dimension(maxSize.width, prefSize.height); - } - - private void initialise() { - setBorder(BorderFactory.createTitledBorder("Boot Sequence")); - - mBootSequenceCheck = new JCheckBox("Enabled"); - mBootSequenceCheck.setSelected(mMiscConfig.mBootsequenceEnabled); - mBootSequenceCheck.addActionListener(mActionListener); - add(mBootSequenceCheck); - - mBootSequenceLabel = new JLabel("Type:"); - mBootSequenceLabel.setEnabled(mMiscConfig.mBootsequenceEnabled); - add(mBootSequenceLabel); - - mBootSequenceCombo = new JComboBox<>(BootSequence.values()); - mBootSequenceCombo.setSelectedItem(mMiscConfig.mBootSequence); - mBootSequenceCombo.setToolTipText("The sequence used on startup to verify proper working of all the leds"); - mBootSequenceCombo.addActionListener(mActionListener); - add(mBootSequenceCombo); - - mBootSequenceLengthLabel = new JLabel("Length [ms]"); - add(mBootSequenceLengthLabel); - - mBootSequenceLengthSpinner = new JSpinner(new SpinnerNumberModel(mMiscConfig.mBootSequenceLength_ms, 500, 3600000, 1000)); - mBootSequenceLengthSpinner.addChangeListener(mChangeListener); - add(mBootSequenceLengthSpinner); - - - GroupLayout layout = new GroupLayout(this); - layout.setAutoCreateGaps(true); - setLayout(layout); - - layout.setHorizontalGroup(layout.createSequentialGroup() - .addGroup(layout.createParallelGroup() - .addComponent(mBootSequenceCheck) - .addComponent(mBootSequenceLabel) - .addComponent(mBootSequenceLengthLabel) - ) - .addGroup(layout.createParallelGroup() - .addComponent(mBootSequenceCheck) - .addComponent(mBootSequenceCombo) - .addComponent(mBootSequenceLengthSpinner) - )); - layout.setVerticalGroup(layout.createSequentialGroup() - .addComponent(mBootSequenceCheck) - .addGroup(layout.createParallelGroup() - .addComponent(mBootSequenceLabel) - .addComponent(mBootSequenceCombo) - ) - .addGroup(layout.createParallelGroup() - .addComponent(mBootSequenceLengthLabel) - .addComponent(mBootSequenceLengthSpinner) - )); - - toggleEnabled(mMiscConfig.mBootsequenceEnabled); - } - - private void toggleEnabled(boolean pEnabled) { - mBootSequenceLabel.setEnabled(pEnabled); - mBootSequenceCombo.setEnabled(pEnabled); - mBootSequenceLengthLabel.setEnabled(pEnabled); - mBootSequenceLengthSpinner.setEnabled(pEnabled); - } - - private final ActionListener mActionListener = new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - mMiscConfig.mBootsequenceEnabled = mBootSequenceCheck.isSelected(); - mMiscConfig.mBootSequence = (BootSequence) mBootSequenceCombo.getSelectedItem(); - - toggleEnabled(mMiscConfig.mBootsequenceEnabled); - } - }; - - private final ChangeListener mChangeListener = new ChangeListener() { - @Override - public void stateChanged(ChangeEvent e) { - mMiscConfig.mBootSequenceLength_ms = (Integer)mBootSequenceLengthSpinner.getValue(); - } - }; -} diff --git a/src/config-tool/ConfigTool/src/org/hyperion/hypercon/gui/ColorTransformPanel.java b/src/config-tool/ConfigTool/src/org/hyperion/hypercon/gui/ColorTransformPanel.java index e7dd3278..e3c011f1 100644 --- a/src/config-tool/ConfigTool/src/org/hyperion/hypercon/gui/ColorTransformPanel.java +++ b/src/config-tool/ConfigTool/src/org/hyperion/hypercon/gui/ColorTransformPanel.java @@ -28,6 +28,8 @@ import org.hyperion.hypercon.spec.TransformConfig; */ public class ColorTransformPanel extends JPanel { + private final Dimension maxDim = new Dimension(1024, 20); + private final TransformConfig mColorConfig; private JPanel mIndexPanel; @@ -99,7 +101,7 @@ public class ColorTransformPanel extends JPanel { mIndexPanel.add(mIndexLabel, BorderLayout.WEST); mIndexField = new JTextField(mColorConfig.mLedIndexString); - mIndexField.setToolTipText("Comma seperated indices or index ranges (eg '1-10, 13, 14, 17-19')"); + mIndexField.setToolTipText("Comma seperated indices or index ranges (eg '1-10, 13, 14, 17-19'); Special case '*', which means all leds"); mIndexField.getDocument().addDocumentListener(mDocumentListener); mIndexPanel.add(mIndexField, BorderLayout.CENTER); } @@ -131,45 +133,57 @@ public class ColorTransformPanel extends JPanel { mRedTransformLabel = new JLabel("RED"); mRgbTransformPanel.add(mRedTransformLabel); mRedThresholdSpinner = new JSpinner(new SpinnerNumberModel(mColorConfig.mRedThreshold, 0.0, 1.0, 0.1)); + mRedThresholdSpinner.setMaximumSize(maxDim); mRedThresholdSpinner.addChangeListener(mChangeListener); mRgbTransformPanel.add(mRedThresholdSpinner); mRedGammaSpinner = new JSpinner(new SpinnerNumberModel(mColorConfig.mRedGamma, 0.0, 100.0, 0.1)); - mRedThresholdSpinner.addChangeListener(mChangeListener); + mRedGammaSpinner.setMaximumSize(maxDim); + mRedGammaSpinner.addChangeListener(mChangeListener); mRgbTransformPanel.add(mRedGammaSpinner); mRedBlacklevelSpinner = new JSpinner(new SpinnerNumberModel(mColorConfig.mRedBlacklevel, 0.0, 1.0, 0.1)); - mRedThresholdSpinner.addChangeListener(mChangeListener); + mRedBlacklevelSpinner.setMaximumSize(maxDim); + mRedBlacklevelSpinner.addChangeListener(mChangeListener); mRgbTransformPanel.add(mRedBlacklevelSpinner); mRedWhitelevelSpinner = new JSpinner(new SpinnerNumberModel(mColorConfig.mRedWhitelevel, 0.0, 1.0, 0.1)); - mRedThresholdSpinner.addChangeListener(mChangeListener); + mRedWhitelevelSpinner.setMaximumSize(maxDim); + mRedWhitelevelSpinner.addChangeListener(mChangeListener); mRgbTransformPanel.add(mRedWhitelevelSpinner); mGreenTransformLabel = new JLabel("GREEN"); mRgbTransformPanel.add(mGreenTransformLabel); mGreenThresholdSpinner = new JSpinner(new SpinnerNumberModel(mColorConfig.mGreenThreshold, 0.0, 1.0, 0.1)); + mGreenThresholdSpinner.setMaximumSize(maxDim); mGreenThresholdSpinner.addChangeListener(mChangeListener); mRgbTransformPanel.add(mGreenThresholdSpinner); mGreenGammaSpinner = new JSpinner(new SpinnerNumberModel(mColorConfig.mGreenGamma, 0.0, 100.0, 0.1)); + mGreenGammaSpinner.setMaximumSize(maxDim); mGreenGammaSpinner.addChangeListener(mChangeListener); mRgbTransformPanel.add(mGreenGammaSpinner); mGreenBlacklevelSpinner = new JSpinner(new SpinnerNumberModel(mColorConfig.mGreenBlacklevel, 0.0, 1.0, 0.1)); + mGreenBlacklevelSpinner.setMaximumSize(maxDim); mGreenBlacklevelSpinner.addChangeListener(mChangeListener); mRgbTransformPanel.add(mGreenBlacklevelSpinner); mGreenWhitelevelSpinner = new JSpinner(new SpinnerNumberModel(mColorConfig.mGreenWhitelevel, 0.0, 1.0, 0.1)); + mGreenWhitelevelSpinner.setMaximumSize(maxDim); mGreenWhitelevelSpinner.addChangeListener(mChangeListener); mRgbTransformPanel.add(mGreenWhitelevelSpinner); mBlueTransformLabel = new JLabel("BLUE"); mRgbTransformPanel.add(mBlueTransformLabel); mBlueThresholdSpinner = new JSpinner(new SpinnerNumberModel(mColorConfig.mBlueThreshold, 0.0, 1.0, 0.1)); + mBlueThresholdSpinner.setMaximumSize(maxDim); mBlueThresholdSpinner.addChangeListener(mChangeListener); mRgbTransformPanel.add(mBlueThresholdSpinner); mBlueGammaSpinner = new JSpinner(new SpinnerNumberModel(mColorConfig.mBlueGamma, 0.0, 100.0, 0.1)); + mBlueGammaSpinner.setMaximumSize(maxDim); mBlueGammaSpinner.addChangeListener(mChangeListener); mRgbTransformPanel.add(mBlueGammaSpinner); mBlueBlacklevelSpinner = new JSpinner(new SpinnerNumberModel(mColorConfig.mBlueBlacklevel, 0.0, 1.0, 0.1)); + mBlueBlacklevelSpinner.setMaximumSize(maxDim); mBlueBlacklevelSpinner.addChangeListener(mChangeListener); mRgbTransformPanel.add(mBlueBlacklevelSpinner); mBlueWhitelevelSpinner = new JSpinner(new SpinnerNumberModel(mColorConfig.mBlueWhitelevel, 0.0, 1.0, 0.1)); + mBlueWhitelevelSpinner.setMaximumSize(maxDim); mBlueWhitelevelSpinner.addChangeListener(mChangeListener); mRgbTransformPanel.add(mBlueWhitelevelSpinner); } @@ -187,6 +201,7 @@ public class ColorTransformPanel extends JPanel { mHsvTransformPanel.add(mSaturationAdjustLabel); mSaturationAdjustSpinner = new JSpinner(new SpinnerNumberModel(mColorConfig.mSaturationGain, 0.0, 1024.0, 0.01)); + mSaturationAdjustSpinner.setMaximumSize(maxDim); mSaturationAdjustSpinner.addChangeListener(mChangeListener); mHsvTransformPanel.add(mSaturationAdjustSpinner); @@ -194,6 +209,7 @@ public class ColorTransformPanel extends JPanel { mHsvTransformPanel.add(mValueAdjustLabel); mValueAdjustSpinner = new JSpinner(new SpinnerNumberModel(mColorConfig.mValueGain, 0.0, 1024.0, 0.01)); + mValueAdjustSpinner.setMaximumSize(maxDim); mValueAdjustSpinner.addChangeListener(mChangeListener); mHsvTransformPanel.add(mValueAdjustSpinner); diff --git a/src/config-tool/ConfigTool/src/org/hyperion/hypercon/gui/ConfigPanel.java b/src/config-tool/ConfigTool/src/org/hyperion/hypercon/gui/ConfigPanel.java index 84e1636d..a7d06cf8 100644 --- a/src/config-tool/ConfigTool/src/org/hyperion/hypercon/gui/ConfigPanel.java +++ b/src/config-tool/ConfigTool/src/org/hyperion/hypercon/gui/ConfigPanel.java @@ -171,7 +171,6 @@ public class ConfigPanel extends JPanel { mProcessPanel = new JPanel(); mProcessPanel.setLayout(new BoxLayout(mProcessPanel, BoxLayout.Y_AXIS)); - mProcessPanel.add(new BootSequencePanel(ledString.mMiscConfig)); mProcessPanel.add(new FrameGrabberPanel(ledString.mMiscConfig)); mProcessPanel.add(new ColorSmoothingPanel(ledString.mColorConfig)); mProcessPanel.add(new ColorsPanel(ledString.mColorConfig)); @@ -187,6 +186,7 @@ public class ConfigPanel extends JPanel { mExternalPanel.add(new XbmcPanel(ledString.mMiscConfig)); mExternalPanel.add(new InterfacePanel(ledString.mMiscConfig)); + mExternalPanel.add(new EffectEnginePanel(ledString.mMiscConfig)); mExternalPanel.add(Box.createVerticalGlue()); } return mExternalPanel; diff --git a/src/config-tool/ConfigTool/src/org/hyperion/hypercon/gui/DevicePanel.java b/src/config-tool/ConfigTool/src/org/hyperion/hypercon/gui/DevicePanel.java index 721fced0..90219e28 100644 --- a/src/config-tool/ConfigTool/src/org/hyperion/hypercon/gui/DevicePanel.java +++ b/src/config-tool/ConfigTool/src/org/hyperion/hypercon/gui/DevicePanel.java @@ -1,5 +1,6 @@ package org.hyperion.hypercon.gui; +import java.awt.BorderLayout; import java.awt.Dimension; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; @@ -10,11 +11,8 @@ import javax.swing.GroupLayout; import javax.swing.JComboBox; import javax.swing.JLabel; import javax.swing.JPanel; -import javax.swing.JSpinner; -import javax.swing.SpinnerNumberModel; -import javax.swing.event.ChangeEvent; -import javax.swing.event.ChangeListener; +import org.hyperion.hypercon.gui.device.DeviceTypePanel; import org.hyperion.hypercon.spec.ColorByteOrder; import org.hyperion.hypercon.spec.DeviceConfig; import org.hyperion.hypercon.spec.DeviceType; @@ -25,15 +23,10 @@ public class DevicePanel extends JPanel { private final DeviceConfig mDeviceConfig; - private JLabel mTypeLabel; private JComboBox mTypeCombo; - private JLabel mOutputLabel; - private JComboBox mOutputCombo; - - private JLabel mBaudrateLabel; - private JSpinner mBaudrateSpinner; + private JPanel mDevicePanel; private JLabel mRgbLabel; private JComboBox mRgbCombo; @@ -57,16 +50,8 @@ public class DevicePanel extends JPanel { private void initialise() { setBorder(BorderFactory.createTitledBorder("Device")); - mOutputLabel = new JLabel("Output"); - add(mOutputLabel); - - mOutputCombo = new JComboBox<>(KnownOutputs); - mOutputCombo.setEditable(true); - mOutputCombo.setSelectedItem(mDeviceConfig.mOutput); - mOutputCombo.addActionListener(mActionListener); - add(mOutputCombo); - - mTypeLabel = new JLabel("LED Type:"); + mTypeLabel = new JLabel("Type: "); + mTypeLabel.setMinimumSize(new Dimension(80, 10)); add(mTypeLabel); mTypeCombo = new JComboBox<>(DeviceType.values()); @@ -74,14 +59,17 @@ public class DevicePanel extends JPanel { mTypeCombo.addActionListener(mActionListener); add(mTypeCombo); - mBaudrateLabel = new JLabel("Baudrate"); - add(mBaudrateLabel); + mDevicePanel = new JPanel(); + mDevicePanel.setBorder(BorderFactory.createEmptyBorder(5, 0, 5, 0)); + mDevicePanel.setLayout(new BorderLayout()); + DeviceTypePanel typePanel = mDeviceConfig.mType.getConfigPanel(mDeviceConfig); + if (typePanel != null) { + mDevicePanel.add(typePanel, BorderLayout.CENTER); + } + add(mDevicePanel); - mBaudrateSpinner = new JSpinner(new SpinnerNumberModel(mDeviceConfig.mBaudrate, 1, 1000000, 128)); - mBaudrateSpinner.addChangeListener(mChangeListener); - add(mBaudrateSpinner); - - mRgbLabel = new JLabel("RGB Byte Order"); + mRgbLabel = new JLabel("RGB Byte Order: "); + mRgbLabel.setMinimumSize(new Dimension(80, 10)); add(mRgbLabel); mRgbCombo = new JComboBox<>(ColorByteOrder.values()); @@ -89,52 +77,41 @@ public class DevicePanel extends JPanel { mRgbCombo.addActionListener(mActionListener); add(mRgbCombo); - GroupLayout layout = new GroupLayout(this); layout.setAutoCreateGaps(true); setLayout(layout); - layout.setHorizontalGroup(layout.createSequentialGroup() - .addGroup(layout.createParallelGroup() - .addComponent(mOutputLabel) - .addComponent(mTypeLabel) - .addComponent(mBaudrateLabel) - .addComponent(mRgbLabel)) - .addGroup(layout.createParallelGroup() - .addComponent(mOutputCombo) - .addComponent(mTypeCombo) - .addComponent(mBaudrateSpinner) - .addComponent(mRgbCombo)) - ); - layout.setVerticalGroup(layout.createSequentialGroup() - .addGroup(layout.createParallelGroup() - .addComponent(mOutputLabel) - .addComponent(mOutputCombo)) - .addGroup(layout.createParallelGroup() + layout.setHorizontalGroup(layout.createParallelGroup() + .addGroup(layout.createSequentialGroup() .addComponent(mTypeLabel) .addComponent(mTypeCombo)) - .addGroup(layout.createParallelGroup() - .addComponent(mBaudrateLabel) - .addComponent(mBaudrateSpinner)) - .addGroup(layout.createParallelGroup() + .addComponent(mDevicePanel) + .addGroup(layout.createSequentialGroup() .addComponent(mRgbLabel) .addComponent(mRgbCombo))); + layout.setVerticalGroup(layout.createParallelGroup() + .addGroup(layout.createSequentialGroup() + .addComponent(mTypeLabel) + .addComponent(mDevicePanel) + .addComponent(mRgbLabel)) + .addGroup(layout.createSequentialGroup() + .addComponent(mTypeCombo) + .addComponent(mDevicePanel) + .addComponent(mRgbCombo))); } private final ActionListener mActionListener = new ActionListener() { @Override public void actionPerformed(ActionEvent e) { mDeviceConfig.mType = (DeviceType)mTypeCombo.getSelectedItem(); - mDeviceConfig.mOutput = (String)mOutputCombo.getSelectedItem(); - mDeviceConfig.mBaudrate = (Integer)mBaudrateSpinner.getValue(); mDeviceConfig.mColorByteOrder = (ColorByteOrder)mRgbCombo.getSelectedItem(); - } - }; - - private final ChangeListener mChangeListener = new ChangeListener() { - @Override - public void stateChanged(ChangeEvent e) { - mDeviceConfig.mBaudrate = (Integer)mBaudrateSpinner.getValue(); + + mDevicePanel.removeAll(); + DeviceTypePanel typePanel = mDeviceConfig.mType.getConfigPanel(mDeviceConfig); + if (typePanel != null) { + mDevicePanel.add(typePanel, BorderLayout.CENTER); + } + revalidate(); } }; } diff --git a/src/config-tool/ConfigTool/src/org/hyperion/hypercon/gui/EffectEnginePanel.java b/src/config-tool/ConfigTool/src/org/hyperion/hypercon/gui/EffectEnginePanel.java new file mode 100644 index 00000000..26f01025 --- /dev/null +++ b/src/config-tool/ConfigTool/src/org/hyperion/hypercon/gui/EffectEnginePanel.java @@ -0,0 +1,175 @@ +package org.hyperion.hypercon.gui; + +import java.awt.Dimension; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import javax.swing.BorderFactory; +import javax.swing.GroupLayout; +import javax.swing.JCheckBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JSpinner; +import javax.swing.JTextField; +import javax.swing.SpinnerNumberModel; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; + +import org.hyperion.hypercon.spec.MiscConfig; + +/** + * THe EffectEnginePanel contains the components for configuring the parameters of the Effect Engine + */ +public class EffectEnginePanel extends JPanel { + + /** The MISC config contains the effect engine settings */ + private final MiscConfig mMiscConfig; + + private JLabel mPathLabel; + private JTextField mPathField; + + private JPanel mBootSequencePanel; + private JCheckBox mBootSequenceCheck; + private JLabel mBootSequenceLabel; + private JTextField mBootSequenceField; + private JLabel mBootSequenceLengthLabel; + private JSpinner mBootSequenceLengthSpinner; + + public EffectEnginePanel(final MiscConfig pMiscConfig) { + super(); + + mMiscConfig = pMiscConfig; + + initialise(); + } + + private void initialise() { + setBorder(BorderFactory.createTitledBorder("Effect Engine")); + + mPathLabel = new JLabel("Directory: "); + mPathLabel.setMinimumSize(new Dimension(80, 10)); + add(mPathLabel); + + mPathField = new JTextField(); + mPathField.setMaximumSize(new Dimension(1024, 20)); + mPathField.setText(mMiscConfig.mEffectEnginePath); + mPathField.getDocument().addDocumentListener(new DocumentListener() { + @Override + public void removeUpdate(DocumentEvent e) { + mMiscConfig.mEffectEnginePath = mPathField.getText(); + } + @Override + public void insertUpdate(DocumentEvent e) { + mMiscConfig.mEffectEnginePath = mPathField.getText(); + } + @Override + public void changedUpdate(DocumentEvent e) { + mMiscConfig.mEffectEnginePath = mPathField.getText(); + } + }); + add(mPathField); + + add(getBootSequencePanel()); + + GroupLayout layout = new GroupLayout(this); + setLayout(layout); + + layout.setVerticalGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup() + .addComponent(mPathLabel) + .addComponent(mPathField)) + .addComponent(getBootSequencePanel())); + + layout.setHorizontalGroup(layout.createParallelGroup() + .addGroup(layout.createSequentialGroup() + .addComponent(mPathLabel) + .addComponent(mPathField)) + .addComponent(getBootSequencePanel())); + } + + private JPanel getBootSequencePanel() { + if (mBootSequencePanel == null) { + mBootSequencePanel = new JPanel(); + mBootSequencePanel.setBorder(BorderFactory.createTitledBorder("Bootsequence")); + + mBootSequenceCheck = new JCheckBox("Enabled"); + mBootSequenceCheck.setSelected(mMiscConfig.mBootSequenceEnabled); + mBootSequenceCheck.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + mMiscConfig.mBootSequenceEnabled = mBootSequenceCheck.isSelected(); + mBootSequenceLabel.setEnabled(mMiscConfig.mBootSequenceEnabled); + mBootSequenceField.setEnabled(mMiscConfig.mBootSequenceEnabled); + } + }); + mBootSequencePanel.add(mBootSequenceCheck); + + mBootSequenceLabel = new JLabel("Type:"); + mBootSequenceLabel.setMinimumSize(new Dimension(75, 10)); + mBootSequenceLabel.setEnabled(mMiscConfig.mBootSequenceEnabled); + mBootSequencePanel.add(mBootSequenceLabel); + + mBootSequenceField = new JTextField(); + mBootSequenceField.setMaximumSize(new Dimension(1024, 20)); + mBootSequenceField.setText(mMiscConfig.mBootSequenceEffect); + mBootSequenceField.setEnabled(mMiscConfig.mBootSequenceEnabled); + mBootSequenceField.getDocument().addDocumentListener(new DocumentListener() { + @Override + public void removeUpdate(DocumentEvent e) { + mMiscConfig.mBootSequenceEffect = mBootSequenceField.getText(); + } + @Override + public void insertUpdate(DocumentEvent e) { + mMiscConfig.mBootSequenceEffect = mBootSequenceField.getText(); + } + @Override + public void changedUpdate(DocumentEvent e) { + mMiscConfig.mBootSequenceEffect = mBootSequenceField.getText(); + } + }); + mBootSequencePanel.add(mBootSequenceField); + + mBootSequenceLengthLabel = new JLabel("Length[ms]: "); + mBootSequenceLengthLabel.setMinimumSize(new Dimension(75, 10)); + mBootSequenceLengthLabel.setEnabled(mMiscConfig.mBootSequenceEnabled); + mBootSequencePanel.add(mBootSequenceLengthLabel); + + mBootSequenceLengthSpinner = new JSpinner(new SpinnerNumberModel(mMiscConfig.mBootSequenceLength_ms, 100, 1500000, 500)); + mBootSequenceLengthSpinner.setMaximumSize(new Dimension(1024, 20)); + mBootSequenceLengthSpinner.setEnabled(mMiscConfig.mBootSequenceEnabled); + mBootSequenceLengthSpinner.addChangeListener(new ChangeListener() { + @Override + public void stateChanged(ChangeEvent e) { + mMiscConfig.mBootSequenceLength_ms = (Integer)mBootSequenceLengthSpinner.getValue(); + } + }); + mBootSequencePanel.add(mBootSequenceLengthSpinner); + + GroupLayout layout = new GroupLayout(mBootSequencePanel); + mBootSequencePanel.setLayout(layout); + + layout.setVerticalGroup(layout.createSequentialGroup() + .addComponent(mBootSequenceCheck) + .addGroup(layout.createParallelGroup() + .addComponent(mBootSequenceLabel) + .addComponent(mBootSequenceField)) + .addGroup(layout.createParallelGroup() + .addComponent(mBootSequenceLengthLabel) + .addComponent(mBootSequenceLengthSpinner)) + ); + + layout.setHorizontalGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup() + .addComponent(mBootSequenceCheck) + .addComponent(mBootSequenceLabel) + .addComponent(mBootSequenceLengthLabel)) + .addGroup(layout.createParallelGroup() + .addComponent(mBootSequenceCheck) + .addComponent(mBootSequenceField) + .addComponent(mBootSequenceLengthSpinner))); + } + return mBootSequencePanel; + } +} diff --git a/src/config-tool/ConfigTool/src/org/hyperion/hypercon/gui/device/DeviceTypePanel.java b/src/config-tool/ConfigTool/src/org/hyperion/hypercon/gui/device/DeviceTypePanel.java new file mode 100644 index 00000000..09e75dd2 --- /dev/null +++ b/src/config-tool/ConfigTool/src/org/hyperion/hypercon/gui/device/DeviceTypePanel.java @@ -0,0 +1,24 @@ +package org.hyperion.hypercon.gui.device; + +import java.awt.Dimension; + +import javax.swing.JPanel; + +import org.hyperion.hypercon.spec.DeviceConfig; + +public abstract class DeviceTypePanel extends JPanel { + + protected final Dimension firstColMinDim = new Dimension(80, 10); + protected final Dimension maxDim = new Dimension(1024, 20); + + protected DeviceConfig mDeviceConfig = null; + + public DeviceTypePanel() { + super(); + } + + public void setDeviceConfig(DeviceConfig pDeviceConfig) { + mDeviceConfig = pDeviceConfig; + } + +} diff --git a/src/config-tool/ConfigTool/src/org/hyperion/hypercon/gui/device/LightPackPanel.java b/src/config-tool/ConfigTool/src/org/hyperion/hypercon/gui/device/LightPackPanel.java new file mode 100644 index 00000000..05b015ab --- /dev/null +++ b/src/config-tool/ConfigTool/src/org/hyperion/hypercon/gui/device/LightPackPanel.java @@ -0,0 +1,63 @@ +package org.hyperion.hypercon.gui.device; + +import javax.swing.GroupLayout; +import javax.swing.JLabel; +import javax.swing.JTextField; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; + +import org.hyperion.hypercon.spec.DeviceConfig; + +public class LightPackPanel extends DeviceTypePanel { + + private JLabel mSerialNoLabel; + private JTextField mSerialNoField; + + public LightPackPanel() { + super(); + + initialise(); + } + + @Override + public void setDeviceConfig(DeviceConfig pDeviceConfig) { + super.setDeviceConfig(pDeviceConfig); + + mSerialNoField.setText(mDeviceConfig.mOutput); + } + + private void initialise() { + mSerialNoLabel = new JLabel("Serial #: "); + mSerialNoLabel.setMinimumSize(firstColMinDim); + add(mSerialNoLabel); + + mSerialNoField = new JTextField(); + mSerialNoField.setMaximumSize(maxDim); + mSerialNoField.getDocument().addDocumentListener(new DocumentListener() { + @Override + public void removeUpdate(DocumentEvent e) { + mDeviceConfig.mOutput = mSerialNoField.getText(); + } + @Override + public void insertUpdate(DocumentEvent e) { + mDeviceConfig.mOutput = mSerialNoField.getText(); + } + @Override + public void changedUpdate(DocumentEvent e) { + mDeviceConfig.mOutput = mSerialNoField.getText(); + } + }); + add(mSerialNoField); + + GroupLayout layout = new GroupLayout(this); + setLayout(layout); + + layout.setHorizontalGroup(layout.createSequentialGroup() + .addComponent(mSerialNoLabel) + .addComponent(mSerialNoField)); + layout.setVerticalGroup(layout.createParallelGroup() + .addComponent(mSerialNoLabel) + .addComponent(mSerialNoField)); + } + +} diff --git a/src/config-tool/ConfigTool/src/org/hyperion/hypercon/gui/device/SerialPanel.java b/src/config-tool/ConfigTool/src/org/hyperion/hypercon/gui/device/SerialPanel.java new file mode 100644 index 00000000..204a6bf9 --- /dev/null +++ b/src/config-tool/ConfigTool/src/org/hyperion/hypercon/gui/device/SerialPanel.java @@ -0,0 +1,104 @@ +package org.hyperion.hypercon.gui.device; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import javax.swing.GroupLayout; +import javax.swing.JComboBox; +import javax.swing.JLabel; +import javax.swing.JSpinner; +import javax.swing.SpinnerNumberModel; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import org.hyperion.hypercon.spec.DeviceConfig; + +/** + * Panel for configuring Ws2801 specific settings + */ +public class SerialPanel extends DeviceTypePanel { + + public static final String[] KnownOutputs = { "/dev/ttyS0", "/dev/ttyUSB0", "/dev/ttyAMA0", "/dev/null"}; + + private JLabel mOutputLabel; + private JComboBox mOutputCombo; + + private JLabel mBaudrateLabel; + private JSpinner mBaudrateSpinner; + + + public SerialPanel() { + super(); + + initialise(); + } + + @Override + public void setDeviceConfig(DeviceConfig pDeviceConfig) { + super.setDeviceConfig(pDeviceConfig); + + mOutputCombo.setSelectedItem(mDeviceConfig.mOutput); + ((SpinnerNumberModel)mBaudrateSpinner.getModel()).setValue(mDeviceConfig.mBaudrate); + } + + private void initialise() { + mOutputLabel = new JLabel("Output: "); + mOutputLabel.setMinimumSize(firstColMinDim); + add(mOutputLabel); + + mOutputCombo = new JComboBox<>(KnownOutputs); + mOutputCombo.setMaximumSize(maxDim); + mOutputCombo.setEditable(true); + mOutputCombo.addActionListener(mActionListener); + add(mOutputCombo); + + mBaudrateLabel = new JLabel("Baudrate: "); + mBaudrateLabel.setMinimumSize(firstColMinDim); + add(mBaudrateLabel); + + mBaudrateSpinner = new JSpinner(new SpinnerNumberModel(1, 1, 1000000, 128)); + mBaudrateSpinner .setMaximumSize(maxDim); + mBaudrateSpinner.addChangeListener(mChangeListener); + add(mBaudrateSpinner); + + + GroupLayout layout = new GroupLayout(this); + layout.setAutoCreateGaps(true); + setLayout(layout); + + layout.setHorizontalGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup() + .addComponent(mOutputLabel) + .addComponent(mBaudrateLabel)) + .addGroup(layout.createParallelGroup() + .addComponent(mOutputCombo) + .addComponent(mBaudrateSpinner)) + ); + layout.setVerticalGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup() + .addComponent(mOutputLabel) + .addComponent(mOutputCombo)) + .addGroup(layout.createParallelGroup() + .addComponent(mBaudrateLabel) + .addComponent(mBaudrateSpinner)) + ); + } + + private ActionListener mActionListener = new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + if (e.getSource() == mOutputCombo) { + mDeviceConfig.mOutput = (String)mOutputCombo.getSelectedItem(); + } else if (e.getSource() == mBaudrateSpinner) { + mDeviceConfig.mBaudrate = (Integer)mBaudrateSpinner.getValue(); + } + } + }; + + private ChangeListener mChangeListener = new ChangeListener() { + @Override + public void stateChanged(ChangeEvent e) { + mDeviceConfig.mBaudrate = (Integer)mBaudrateSpinner.getValue(); + } + }; +} diff --git a/src/config-tool/ConfigTool/src/org/hyperion/hypercon/gui/device/TestDevicePanel.java b/src/config-tool/ConfigTool/src/org/hyperion/hypercon/gui/device/TestDevicePanel.java new file mode 100644 index 00000000..edf2487e --- /dev/null +++ b/src/config-tool/ConfigTool/src/org/hyperion/hypercon/gui/device/TestDevicePanel.java @@ -0,0 +1,63 @@ +package org.hyperion.hypercon.gui.device; + +import javax.swing.GroupLayout; +import javax.swing.JLabel; +import javax.swing.JTextField; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; + +import org.hyperion.hypercon.spec.DeviceConfig; + +public class TestDevicePanel extends DeviceTypePanel { + + private JLabel mFilenameLabel; + private JTextField mFilenameField; + + public TestDevicePanel() { + super(); + + initialise(); + } + + @Override + public void setDeviceConfig(DeviceConfig pDeviceConfig) { + super.setDeviceConfig(pDeviceConfig); + + mFilenameField.setText(mDeviceConfig.mOutput); + } + + private void initialise() { + mFilenameLabel = new JLabel("Filename: "); + mFilenameLabel.setMinimumSize(firstColMinDim); + add(mFilenameLabel); + + mFilenameField = new JTextField(); + mFilenameField.setMaximumSize(maxDim); + mFilenameField.getDocument().addDocumentListener(new DocumentListener() { + @Override + public void removeUpdate(DocumentEvent e) { + mDeviceConfig.mOutput = mFilenameField.getText(); + } + @Override + public void insertUpdate(DocumentEvent e) { + mDeviceConfig.mOutput = mFilenameField.getText(); + } + @Override + public void changedUpdate(DocumentEvent e) { + mDeviceConfig.mOutput = mFilenameField.getText(); + } + }); + add(mFilenameField); + + GroupLayout layout = new GroupLayout(this); + setLayout(layout); + + layout.setHorizontalGroup(layout.createSequentialGroup() + .addComponent(mFilenameLabel) + .addComponent(mFilenameField)); + layout.setVerticalGroup(layout.createParallelGroup() + .addComponent(mFilenameLabel) + .addComponent(mFilenameField)); + } + +} diff --git a/src/config-tool/ConfigTool/src/org/hyperion/hypercon/gui/device/Ws2801Panel.java b/src/config-tool/ConfigTool/src/org/hyperion/hypercon/gui/device/Ws2801Panel.java new file mode 100644 index 00000000..ffea3a46 --- /dev/null +++ b/src/config-tool/ConfigTool/src/org/hyperion/hypercon/gui/device/Ws2801Panel.java @@ -0,0 +1,104 @@ +package org.hyperion.hypercon.gui.device; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import javax.swing.GroupLayout; +import javax.swing.JComboBox; +import javax.swing.JLabel; +import javax.swing.JSpinner; +import javax.swing.SpinnerNumberModel; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import org.hyperion.hypercon.spec.DeviceConfig; + +/** + * Panel for configuring Ws2801 specific settings + */ +public class Ws2801Panel extends DeviceTypePanel { + + public static final String[] KnownOutputs = {"/dev/spidev0.0", "/dev/spidev0.1", "/dev/null"}; + + private JLabel mOutputLabel; + private JComboBox mOutputCombo; + + private JLabel mBaudrateLabel; + private JSpinner mBaudrateSpinner; + + + public Ws2801Panel() { + super(); + + initialise(); + } + + @Override + public void setDeviceConfig(DeviceConfig pDeviceConfig) { + super.setDeviceConfig(pDeviceConfig); + + mOutputCombo.setSelectedItem(mDeviceConfig.mOutput); + ((SpinnerNumberModel)mBaudrateSpinner.getModel()).setValue(mDeviceConfig.mBaudrate); + } + + private void initialise() { + mOutputLabel = new JLabel("Output: "); + mOutputLabel.setMinimumSize(firstColMinDim); + add(mOutputLabel); + + mOutputCombo = new JComboBox<>(KnownOutputs); + mOutputCombo.setMaximumSize(maxDim); + mOutputCombo.setEditable(true); + mOutputCombo.addActionListener(mActionListener); + add(mOutputCombo); + + mBaudrateLabel = new JLabel("Baudrate: "); + mBaudrateLabel.setMinimumSize(firstColMinDim); + add(mBaudrateLabel); + + mBaudrateSpinner = new JSpinner(new SpinnerNumberModel(1, 1, 1000000, 128)); + mBaudrateSpinner.setMaximumSize(maxDim); + mBaudrateSpinner.addChangeListener(mChangeListener); + add(mBaudrateSpinner); + + + GroupLayout layout = new GroupLayout(this); + layout.setAutoCreateGaps(true); + setLayout(layout); + + layout.setHorizontalGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup() + .addComponent(mOutputLabel) + .addComponent(mBaudrateLabel)) + .addGroup(layout.createParallelGroup() + .addComponent(mOutputCombo) + .addComponent(mBaudrateSpinner)) + ); + layout.setVerticalGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup() + .addComponent(mOutputLabel) + .addComponent(mOutputCombo)) + .addGroup(layout.createParallelGroup() + .addComponent(mBaudrateLabel) + .addComponent(mBaudrateSpinner)) + ); + } + + private ActionListener mActionListener = new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + if (e.getSource() == mOutputCombo) { + mDeviceConfig.mOutput = (String)mOutputCombo.getSelectedItem(); + } else if (e.getSource() == mBaudrateSpinner) { + mDeviceConfig.mBaudrate = (Integer)mBaudrateSpinner.getValue(); + } + } + }; + + private ChangeListener mChangeListener = new ChangeListener() { + @Override + public void stateChanged(ChangeEvent e) { + mDeviceConfig.mBaudrate = (Integer)mBaudrateSpinner.getValue(); + } + }; +} diff --git a/src/config-tool/ConfigTool/src/org/hyperion/hypercon/spec/BootSequence.java b/src/config-tool/ConfigTool/src/org/hyperion/hypercon/spec/BootSequence.java deleted file mode 100644 index 0b7d9921..00000000 --- a/src/config-tool/ConfigTool/src/org/hyperion/hypercon/spec/BootSequence.java +++ /dev/null @@ -1,27 +0,0 @@ -package org.hyperion.hypercon.spec; - -/** - * Enumeration of possible boot sequences - */ -public enum BootSequence { - /** The rainbow boot sequence */ - rainbow, - /** The Knight Rider (or KITT) boot sequence */ - knightrider; - - /** - * Returns a string representation of the BootSequence - * - * @return String representation of this boot-sequence - */ - @Override - public String toString() { - switch(this) { - case rainbow: - return "Rainbow"; - case knightrider: - return "Knight Rider"; - } - return "None"; - } -} diff --git a/src/config-tool/ConfigTool/src/org/hyperion/hypercon/spec/ColorConfig.java b/src/config-tool/ConfigTool/src/org/hyperion/hypercon/spec/ColorConfig.java index 824c3f1d..e55a76cf 100644 --- a/src/config-tool/ConfigTool/src/org/hyperion/hypercon/spec/ColorConfig.java +++ b/src/config-tool/ConfigTool/src/org/hyperion/hypercon/spec/ColorConfig.java @@ -47,8 +47,8 @@ public class ColorConfig { strBuf.append("\t/// - 'gamma' The gamma-curve correction factor\n"); strBuf.append("\t/// - 'blacklevel' The lowest possible value (when the channel is black)\n"); strBuf.append("\t/// - 'whitelevel' The highest possible value (when the channel is white)\n"); - strBuf.append("\t///"); - strBuf.append("\t/// Next to the list with color transforms there is also a smoothing option."); + strBuf.append("\t///\n"); + strBuf.append("\t/// Next to the list with color transforms there is also a smoothing option.\n"); strBuf.append("\t/// * 'smoothing' : Smoothing of the colors in the time-domain with the following tuning \n"); strBuf.append("\t/// parameters:\n"); strBuf.append("\t/// - 'type' The type of smoothing algorithm ('linear' or 'none')\n"); diff --git a/src/config-tool/ConfigTool/src/org/hyperion/hypercon/spec/DeviceConfig.java b/src/config-tool/ConfigTool/src/org/hyperion/hypercon/spec/DeviceConfig.java index 7be938f7..ec07096b 100644 --- a/src/config-tool/ConfigTool/src/org/hyperion/hypercon/spec/DeviceConfig.java +++ b/src/config-tool/ConfigTool/src/org/hyperion/hypercon/spec/DeviceConfig.java @@ -12,7 +12,7 @@ public class DeviceConfig { /** The device 'file' name */ public String mOutput = "/dev/spidev0.0"; /** The baudrate of the device */ - public int mBaudrate = 1000000; + public int mBaudrate = 250000; /** The order of the color bytes */ public ColorByteOrder mColorByteOrder = ColorByteOrder.RGB; diff --git a/src/config-tool/ConfigTool/src/org/hyperion/hypercon/spec/DeviceType.java b/src/config-tool/ConfigTool/src/org/hyperion/hypercon/spec/DeviceType.java index 674c883c..ce94b32a 100644 --- a/src/config-tool/ConfigTool/src/org/hyperion/hypercon/spec/DeviceType.java +++ b/src/config-tool/ConfigTool/src/org/hyperion/hypercon/spec/DeviceType.java @@ -1,5 +1,11 @@ package org.hyperion.hypercon.spec; +import org.hyperion.hypercon.gui.device.DeviceTypePanel; +import org.hyperion.hypercon.gui.device.LightPackPanel; +import org.hyperion.hypercon.gui.device.SerialPanel; +import org.hyperion.hypercon.gui.device.TestDevicePanel; +import org.hyperion.hypercon.gui.device.Ws2801Panel; + /** * Enumeration of known device types */ @@ -12,19 +18,68 @@ public enum DeviceType { lpd6803("LPD6803"), /** SEDU LED device */ sedu("SEDU"), + /** Lightberry device */ + lightberry("Lightberry"), /** Adalight device */ adalight("Adalight"), /** Lightpack USB led device */ lightpack("Lightpack"), + /** Paintpack USB led device */ + paintpack("Paintpack"), /** Test device for writing color values to file-output */ test("Test"), /** No device, no output is generated */ none("None"); + /** The 'pretty' name of the device type */ private final String mName; - private DeviceType(String name) { - mName = name; + /** The device specific configuration panel */ + private DeviceTypePanel mConfigPanel; + + /** + * Constructs the DeviceType + * + * @param name The 'pretty' name of the device type + * @param pConfigPanel The panel for device type specific configuration + */ + private DeviceType(final String name) { + mName = name; + } + + /** + * Returns the configuration panel for the this device-type (or null if no configuration is required) + * + * @return The panel for configuring this device type + */ + public DeviceTypePanel getConfigPanel(DeviceConfig pDeviceConfig) { + if (mConfigPanel == null) { + switch (this) { + case ws2801: + case lightberry: + case lpd6803: + case lpd8806: + mConfigPanel = new Ws2801Panel(); + break; + case test: + mConfigPanel = new TestDevicePanel(); + break; + case adalight: + case sedu: + mConfigPanel = new SerialPanel(); + break; + case lightpack: + mConfigPanel = new LightPackPanel(); + break; + case paintpack: + case none: + break; + } + } + if (mConfigPanel != null) { + mConfigPanel.setDeviceConfig(pDeviceConfig); + } + return mConfigPanel; } @Override diff --git a/src/config-tool/ConfigTool/src/org/hyperion/hypercon/spec/ImageProcessConfig.java b/src/config-tool/ConfigTool/src/org/hyperion/hypercon/spec/ImageProcessConfig.java index 21302be6..ba6bf312 100644 --- a/src/config-tool/ConfigTool/src/org/hyperion/hypercon/spec/ImageProcessConfig.java +++ b/src/config-tool/ConfigTool/src/org/hyperion/hypercon/spec/ImageProcessConfig.java @@ -1,8 +1,8 @@ package org.hyperion.hypercon.spec; -import java.util.Locale; import java.util.Observable; +import org.hyperion.hypercon.JsonStringBuffer; import org.hyperion.hypercon.LedFrameFactory; /** @@ -161,17 +161,15 @@ public class ImageProcessConfig extends Observable { } } - public String getBlackborderJson() { - StringBuffer strBuf = new StringBuffer(); - - strBuf.append("\t/// The black border configuration, contains the following items: \n"); - strBuf.append("\t/// * enable : true if the detector should be activated\n"); + public void appendTo(JsonStringBuffer pJsonBuf) { + String comment = + "The black border configuration, contains the following items: \n" + + " * enable : true if the detector should be activated\n"; + pJsonBuf.writeComment(comment); - strBuf.append("\t\"blackborderdetector\" :\n"); - strBuf.append("\t{\n"); - strBuf.append(String.format(Locale.ROOT, "\t\t\"enable\" : %s\n", mBlackBorderRemoval ? "true" : "false")); - strBuf.append("\t}"); - - return strBuf.toString(); + pJsonBuf.startObject("blackborderdetector"); + pJsonBuf.addValue("enable", mBlackBorderRemoval, true); + pJsonBuf.stopObject(); } + } diff --git a/src/config-tool/ConfigTool/src/org/hyperion/hypercon/spec/MiscConfig.java b/src/config-tool/ConfigTool/src/org/hyperion/hypercon/spec/MiscConfig.java index cadfd7d6..b5236a0c 100644 --- a/src/config-tool/ConfigTool/src/org/hyperion/hypercon/spec/MiscConfig.java +++ b/src/config-tool/ConfigTool/src/org/hyperion/hypercon/spec/MiscConfig.java @@ -1,17 +1,20 @@ package org.hyperion.hypercon.spec; -import java.util.Locale; +import org.hyperion.hypercon.JsonStringBuffer; /** * Miscellaneous configuration items for the Hyperion daemon. */ public class MiscConfig { + + /** The absolute location(s) of the effects */ + public String mEffectEnginePath = "/opt/hyperion/effects"; - /** Flag indicating that the boot sequence is enabled */ - public boolean mBootsequenceEnabled = true; - /** The selected boot sequence */ - public BootSequence mBootSequence = BootSequence.rainbow; - /** The length of the boot sequence [ms] */ + /** Flag indicating that the boot sequence is enabled(true) or not(false) */ + public boolean mBootSequenceEnabled = true; + /** The effect selected for the boot sequence */ + public String mBootSequenceEffect = "Rainbow swirl fast"; + /** The (maximum) length of the boot-sequence */ public int mBootSequenceLength_ms = 3000; /** Flag indicating that the Frame Grabber is enabled */ @@ -53,89 +56,128 @@ public class MiscConfig { /** The TCP port at which the Protobuf server is listening for incoming connections */ public int mBoblightPort = 19333; + public void appendTo(JsonStringBuffer strBuf) { + String effectEngineComment = + "The configuration of the effect engine, contains the following items: \n" + + " * paths : An array with absolute location(s) of directories with effects \n" + + " * bootsequence : The effect selected as 'boot sequence'"; + strBuf.writeComment(effectEngineComment); + + String[] effectPaths = mEffectEnginePath.split(":"); + + strBuf.startObject("effects"); + strBuf.startArray("paths"); + for (String effectPath : effectPaths) { + strBuf.addArrayElement(effectPath, effectPath == effectPaths[effectPaths.length-1]); + } + strBuf.stopArray(true); + strBuf.stopObject(); + + strBuf.newLine(); + + strBuf.toggleComment(!mBootSequenceEnabled); + strBuf.startObject("bootsequence"); + strBuf.addValue("effect", mBootSequenceEffect, false); + strBuf.addValue("duration_ms", mBootSequenceLength_ms, true); + strBuf.stopObject(); + strBuf.toggleComment(false); + + strBuf.newLine(); + + String grabComment = + " The configuration for the frame-grabber, contains the following items: \n" + + " * width : The width of the grabbed frames [pixels]\n" + + " * height : The height of the grabbed frames [pixels]\n" + + " * frequency_Hz : The frequency of the frame grab [Hz]\n"; + strBuf.writeComment(grabComment); + + strBuf.toggleComment(!mFrameGrabberEnabled); + strBuf.startObject("framegrabber"); + strBuf.addValue("width", mFrameGrabberWidth, false); + strBuf.addValue("height", mFrameGrabberHeight, false); + strBuf.addValue("frequency_Hz", 1000.0/mFrameGrabberInterval_ms, true); + strBuf.stopObject(); + strBuf.toggleComment(false); + + strBuf.newLine(); + + String xbmcComment = + "The configuration of the XBMC connection used to enable and disable the frame-grabber. Contains the following fields: \n" + + " * xbmcAddress : The IP address of the XBMC-host\n" + + " * xbmcTcpPort : The TCP-port of the XBMC-server\n" + + " * grabVideo : Flag indicating that the frame-grabber is on(true) during video playback\n" + + " * grabPictures : Flag indicating that the frame-grabber is on(true) during picture show\n" + + " * grabAudio : Flag indicating that the frame-grabber is on(true) during audio playback\n" + + " * grabMenu : Flag indicating that the frame-grabber is on(true) in the XBMC menu\n"; + strBuf.writeComment(xbmcComment); + + strBuf.toggleComment(!mXbmcCheckerEnabled); + strBuf.startObject("xbmcVideoChecker"); + strBuf.addValue("xbmcAddress", mXbmcAddress, false); + strBuf.addValue("xbmcTcpPort", mXbmcTcpPort, false); + strBuf.addValue("grabVideo", mVideoOn, false); + strBuf.addValue("grabPictures", mPictureOn, false); + strBuf.addValue("grabAudio", mAudioOn, false); + strBuf.addValue("grabMenu", mMenuOn, true); + strBuf.stopObject(); + strBuf.toggleComment(false); + + strBuf.newLine(); + + String jsonComment = + "The configuration of the Json server which enables the json remote interface\n" + + " * port : Port at which the json server is started\n"; + strBuf.writeComment(jsonComment); + + strBuf.toggleComment(!mJsonInterfaceEnabled); + strBuf.startObject("jsonServer"); + strBuf.addValue("port", mJsonPort, true); + strBuf.stopObject(); + strBuf.toggleComment(false); + + strBuf.newLine(); + + String protoComment = + "The configuration of the Proto server which enables the protobuffer remote interface\n" + + " * port : Port at which the protobuffer server is started\n"; + strBuf.writeComment(protoComment); + + strBuf.toggleComment(!mProtoInterfaceEnabled); + strBuf.startObject("protoServer"); + strBuf.addValue("port", mProtoPort, true); + strBuf.stopObject(); + strBuf.toggleComment(false); + + strBuf.newLine(); + + String boblightComment = + "The configuration of the boblight server which enables the boblight remote interface\n" + + " * port : Port at which the boblight server is started\n"; + strBuf.writeComment(boblightComment); + + strBuf.toggleComment(!mBoblightInterfaceEnabled); + strBuf.startObject("boblightServer"); + strBuf.addValue("port", mBoblightPort, true); + strBuf.stopObject(); + strBuf.toggleComment(false); + } /** * Creates the JSON string of the configuration as used in the Hyperion daemon configfile * * @return The JSON string of this MiscConfig */ public String toJsonString() { - StringBuffer strBuf = new StringBuffer(); - - strBuf.append("\t/// The boot-sequence configuration, contains the following items: \n"); - strBuf.append("\t/// * type : The type of the boot-sequence ('rainbow', 'knightrider', 'none') \n"); - strBuf.append("\t/// * duration_ms : The length of the boot-sequence [ms]\n"); + JsonStringBuffer jsonBuf = new JsonStringBuffer(1); + appendTo(jsonBuf); + return jsonBuf.toString(); + } + + public static void main(String[] pArgs) { + MiscConfig miscConfig = new MiscConfig(); - String bootPreamble = mBootsequenceEnabled? "\t" : "//\t"; - strBuf.append(bootPreamble).append("\"bootsequence\" :\n"); - strBuf.append(bootPreamble).append("{\n"); - strBuf.append(bootPreamble).append(String.format(Locale.ROOT, "\t\"type\" : \"%s\",\n", mBootSequence.name())); - strBuf.append(bootPreamble).append(String.format(Locale.ROOT, "\t\"duration_ms\" : %d\n", mBootSequenceLength_ms)); - strBuf.append(bootPreamble).append("},\n\n"); - - strBuf.append("\t/// The configuration for the frame-grabber, contains the following items: \n"); - strBuf.append("\t/// * width : The width of the grabbed frames [pixels]\n"); - strBuf.append("\t/// * height : The height of the grabbed frames [pixels]\n"); - strBuf.append("\t/// * frequency_Hz : The frequency of the frame grab [Hz]\n"); + JsonStringBuffer jsonBuf = new JsonStringBuffer(1); + miscConfig.appendTo(jsonBuf); - String grabPreamble = mFrameGrabberEnabled? "\t" : "//\t"; - strBuf.append(grabPreamble).append("\"framegrabber\" :\n"); - strBuf.append(grabPreamble).append("{\n"); - strBuf.append(grabPreamble).append(String.format(Locale.ROOT, "\t\"width\" : %d,\n", mFrameGrabberWidth)); - strBuf.append(grabPreamble).append(String.format(Locale.ROOT, "\t\"height\" : %d,\n", mFrameGrabberHeight)); - strBuf.append(grabPreamble).append(String.format(Locale.ROOT, "\t\"frequency_Hz\" : %.1f\n", 1000.0/mFrameGrabberInterval_ms)); - strBuf.append(grabPreamble).append("},\n\n"); - - strBuf.append("\t/// The configuration of the XBMC connection used to enable and disable the frame-grabber. Contains the following fields: \n"); - strBuf.append("\t/// * xbmcAddress : The IP address of the XBMC-host\n"); - strBuf.append("\t/// * xbmcTcpPort : The TCP-port of the XBMC-server\n"); - strBuf.append("\t/// * grabVideo : Flag indicating that the frame-grabber is on(true) during video playback\n"); - strBuf.append("\t/// * grabPictures : Flag indicating that the frame-grabber is on(true) during picture show\n"); - strBuf.append("\t/// * grabAudio : Flag indicating that the frame-grabber is on(true) during audio playback\n"); - strBuf.append("\t/// * grabMenu : Flag indicating that the frame-grabber is on(true) in the XBMC menu\n"); - - String xbmcPreamble = mXbmcCheckerEnabled? "\t" : "//\t"; - strBuf.append(xbmcPreamble).append("\"xbmcVideoChecker\" :\n"); - strBuf.append(xbmcPreamble).append("{\n"); - strBuf.append(xbmcPreamble).append(String.format(Locale.ROOT, "\t\"xbmcAddress\" : \"%s\",\n", mXbmcAddress)); - strBuf.append(xbmcPreamble).append(String.format(Locale.ROOT, "\t\"xbmcTcpPort\" : %d,\n", mXbmcTcpPort)); - strBuf.append(xbmcPreamble).append(String.format(Locale.ROOT, "\t\"grabVideo\" : %s,\n", mVideoOn)); - strBuf.append(xbmcPreamble).append(String.format(Locale.ROOT, "\t\"grabPictures\" : %s,\n", mPictureOn)); - strBuf.append(xbmcPreamble).append(String.format(Locale.ROOT, "\t\"grabAudio\" : %s,\n", mAudioOn)); - strBuf.append(xbmcPreamble).append(String.format(Locale.ROOT, "\t\"grabMenu\" : %s\n", mMenuOn)); - strBuf.append(xbmcPreamble).append("},\n\n"); - - - strBuf.append("\t/// The configuration of the Json server which enables the json remote interface\n"); - strBuf.append("\t/// * port : Port at which the json server is started\n"); - - String jsonPreamble = mJsonInterfaceEnabled? "\t" : "//\t"; - strBuf.append(jsonPreamble).append("\"jsonServer\" :\n"); - strBuf.append(jsonPreamble).append("{\n"); - strBuf.append(jsonPreamble).append(String.format(Locale.ROOT, "\t\"port\" : %d\n", mJsonPort)); - strBuf.append(jsonPreamble).append("},\n\n"); - - - strBuf.append("\t/// The configuration of the Proto server which enables the protobuffer remote interface\n"); - strBuf.append("\t/// * port : Port at which the protobuffer server is started\n"); - - String protoPreamble = mProtoInterfaceEnabled? "\t" : "//\t"; - strBuf.append(protoPreamble).append("\"protoServer\" :\n"); - strBuf.append(protoPreamble).append("{\n"); - strBuf.append(protoPreamble).append(String.format(Locale.ROOT, "\t\"port\" : %d\n", mProtoPort)); - strBuf.append(protoPreamble).append("},\n\n"); - - - strBuf.append("\t/// The configuration of the boblight server which enables the boblight remote interface\n"); - strBuf.append("\t/// * port : Port at which the boblight server is started\n"); - - String bobligthPreamble = mBoblightInterfaceEnabled? "\t" : "//\t"; - strBuf.append(bobligthPreamble).append("\"boblightServer\" :\n"); - strBuf.append(bobligthPreamble).append("{\n"); - strBuf.append(bobligthPreamble).append(String.format(Locale.ROOT, "\t\"port\" : %d\n", mBoblightPort)); - strBuf.append(bobligthPreamble).append("},\n\n"); - - strBuf.append("\t\"end-of-json\" : \"end-of-json\""); - - return strBuf.toString(); + System.out.println(jsonBuf.toString()); } } diff --git a/src/config-tool/ConfigTool/src/org/hyperion/hypercon/spec/TransformConfig.java b/src/config-tool/ConfigTool/src/org/hyperion/hypercon/spec/TransformConfig.java index a5893210..9e42a4bc 100644 --- a/src/config-tool/ConfigTool/src/org/hyperion/hypercon/spec/TransformConfig.java +++ b/src/config-tool/ConfigTool/src/org/hyperion/hypercon/spec/TransformConfig.java @@ -7,35 +7,35 @@ public class TransformConfig { public String mId = "default"; /** The indices to which this transform applies */ - public String mLedIndexString = "0-49"; + public String mLedIndexString = "*"; /** The saturation gain (in HSV space) */ public double mSaturationGain = 1.0; /** The value gain (in HSV space) */ - public double mValueGain = 1.5; + public double mValueGain = 1.0; /** The minimum required RED-value (in RGB space) */ - public double mRedThreshold = 0.1; + public double mRedThreshold = 0.0; /** The gamma-curve correct for the RED-value (in RGB space) */ - public double mRedGamma = 2.0; + public double mRedGamma = 1.0; /** The black-level of the RED-value (in RGB space) */ public double mRedBlacklevel = 0.0; /** The white-level of the RED-value (in RGB space) */ - public double mRedWhitelevel = 0.8; + public double mRedWhitelevel = 1.0; /** The minimum required GREEN-value (in RGB space) */ - public double mGreenThreshold = 0.1; + public double mGreenThreshold = 0.0; /** The gamma-curve correct for the GREEN-value (in RGB space) */ - public double mGreenGamma = 2.0; + public double mGreenGamma = 1.0; /** The black-level of the GREEN-value (in RGB space) */ public double mGreenBlacklevel = 0.0; /** The white-level of the GREEN-value (in RGB space) */ public double mGreenWhitelevel = 1.0; /** The minimum required BLUE-value (in RGB space) */ - public double mBlueThreshold = 0.1; + public double mBlueThreshold = 0.0; /** The gamma-curve correct for the BLUE-value (in RGB space) */ - public double mBlueGamma = 2.0; + public double mBlueGamma = 1.0; /** The black-level of the BLUE-value (in RGB space) */ public double mBlueBlacklevel = 0.0; /** The white-level of the BLUE-value (in RGB space) */ diff --git a/src/hyperion-remote/CustomParameter.h b/src/hyperion-remote/CustomParameter.h index c1ff96d4..91c3a614 100644 --- a/src/hyperion-remote/CustomParameter.h +++ b/src/hyperion-remote/CustomParameter.h @@ -11,7 +11,7 @@ #include "ColorTransformValues.h" /// Data parameter for a color -typedef vlofgren::PODParameter ColorParameter; +typedef vlofgren::PODParameter> ColorParameter; /// Data parameter for an image typedef vlofgren::PODParameter ImageParameter; @@ -21,40 +21,54 @@ typedef vlofgren::PODParameter TransformParameter; namespace vlofgren { /// - /// Translates a string (as passed on the commandline) to a color + /// Translates a string (as passed on the commandline) to a vector of colors /// /// @param[in] s The string (as passed on the commandline) /// - /// @return The translated color + /// @return The translated colors /// /// @throws Parameter::ParameterRejected If the string did not result in a color /// template<> - QColor ColorParameter::validate(const std::string& s) throw (Parameter::ParameterRejected) + std::vector ColorParameter::validate(const std::string& s) throw (Parameter::ParameterRejected) { // Check if we can create the color by name QColor color(s.c_str()); if (color.isValid()) { - return color; + return std::vector{color}; } // check if we can create the color by hex RRGGBB value - if (s.length() == 6 && isxdigit(s[0]) && isxdigit(s[1]) && isxdigit(s[2]) && isxdigit(s[3]) && isxdigit(s[4]) && isxdigit(s[5])) + if (s.length() >= 6u && (s.length()%6) == 0u && std::count_if(s.begin(), s.end(), isxdigit) == s.length()) { bool ok = true; - int rgb[3]; - for (int i = 0; i < 3 && ok; ++i) + std::vector colors; + + for (size_t j = 0; j < s.length()/6; ++j) { - QString colorComponent(s.substr(2*i, 2).c_str()); - rgb[i] = colorComponent.toInt(&ok, 16); + int rgb[3]; + for (int i = 0; i < 3 && ok; ++i) + { + QString colorComponent(s.substr(6*j+2*i, 2).c_str()); + rgb[i] = colorComponent.toInt(&ok, 16); + } + + if (ok) + { + color.setRgb(rgb[0], rgb[1], rgb[2]); + colors.push_back(color); + } + else + { + break; + } } // check if all components parsed succesfully if (ok) { - color.setRgb(rgb[0], rgb[1], rgb[2]); - return color; + return colors; } } @@ -65,7 +79,7 @@ namespace vlofgren { } throw Parameter::ParameterRejected(errorMessage.str()); - return color; + return std::vector{color}; } template<> diff --git a/src/hyperion-remote/JsonConnection.cpp b/src/hyperion-remote/JsonConnection.cpp index 641c7681..652a3dfa 100644 --- a/src/hyperion-remote/JsonConnection.cpp +++ b/src/hyperion-remote/JsonConnection.cpp @@ -39,18 +39,21 @@ JsonConnection::~JsonConnection() _socket.close(); } -void JsonConnection::setColor(QColor color, int priority, int duration) +void JsonConnection::setColor(std::vector colors, int priority, int duration) { - std::cout << "Set color to " << color.red() << " " << color.green() << " " << color.blue() << std::endl; + std::cout << "Set color to " << colors[0].red() << " " << colors[0].green() << " " << colors[0].blue() << (colors.size() > 1 ? " + ..." : "") << std::endl; // create command Json::Value command; command["command"] = "color"; command["priority"] = priority; Json::Value & rgbValue = command["color"]; - rgbValue[0] = color.red(); - rgbValue[1] = color.green(); - rgbValue[2] = color.blue(); + for (const QColor & color : colors) + { + rgbValue.append(color.red()); + rgbValue.append(color.green()); + rgbValue.append(color.blue()); + } if (duration > 0) { command["duration"] = duration; @@ -102,6 +105,35 @@ void JsonConnection::setImage(QImage image, int priority, int duration) parseReply(reply); } +void JsonConnection::setEffect(const std::string &effectName, const std::string & effectArgs, int priority, int duration) +{ + std::cout << "Start effect " << effectName << std::endl; + + // create command + Json::Value command; + command["command"] = "effect"; + command["priority"] = priority; + Json::Value & effect = command["effect"]; + effect["name"] = effectName; + if (effectArgs.size() > 0) + { + Json::Reader reader; + if (!reader.parse(effectArgs, effect["args"], false)) + { + throw std::runtime_error("Error in effect arguments: " + reader.getFormattedErrorMessages()); + } + } + if (duration > 0) + { + command["duration"] = duration; + } + + // send command message + Json::Value reply = sendMessage(command); + + // parse reply message + parseReply(reply); +} QString JsonConnection::getServerInfo() { diff --git a/src/hyperion-remote/JsonConnection.h b/src/hyperion-remote/JsonConnection.h index 6bd358b5..472c5565 100644 --- a/src/hyperion-remote/JsonConnection.h +++ b/src/hyperion-remote/JsonConnection.h @@ -41,7 +41,7 @@ public: /// @param priority The priority /// @param duration The duration in milliseconds /// - void setColor(QColor color, int priority, int duration); + void setColor(std::vector color, int priority, int duration); /// /// Set the leds according to the given image (assume the image is stretched to the display size) @@ -52,6 +52,16 @@ public: /// void setImage(QImage image, int priority, int duration); + /// + /// Start the given effect + /// + /// @param effect The name of the effect + /// @param effectArgs The arguments to use instead of the default ones + /// @param priority The priority + /// @param duration The duration in milliseconds + /// + void setEffect(const std::string & effectName, const std::string &effectArgs, int priority, int duration); + /// /// Retrieve a list of all occupied priority channels /// diff --git a/src/hyperion-remote/hyperion-remote.cpp b/src/hyperion-remote/hyperion-remote.cpp index b290b4f2..f7dfffe6 100644 --- a/src/hyperion-remote/hyperion-remote.cpp +++ b/src/hyperion-remote/hyperion-remote.cpp @@ -40,8 +40,10 @@ int main(int argc, char * argv[]) StringParameter & argAddress = parameters.add ('a', "address" , QString("Set the address of the hyperion server [default: %1]").arg(defaultServerAddress).toAscii().constData()); IntParameter & argPriority = parameters.add ('p', "priority" , QString("Use to the provided priority channel (the lower the number, the higher the priority) [default: %1]").arg(defaultPriority).toAscii().constData()); IntParameter & argDuration = parameters.add ('d', "duration" , "Specify how long the leds should be switched on in millseconds [default: infinity]"); - ColorParameter & argColor = parameters.add ('c', "color" , "Set all leds to a constant color (either RRGGBB hex value or a color name)"); + ColorParameter & argColor = parameters.add ('c', "color" , "Set all leds to a constant color (either RRGGBB hex value or a color name. The color may be repeated multiple time like: RRGGBBRRGGBB)"); ImageParameter & argImage = parameters.add ('i', "image" , "Set the leds to the colors according to the given image file"); + StringParameter & argEffect = parameters.add ('e', "effect" , "Enable the effect with the given name"); + StringParameter & argEffectArgs = parameters.add (0x0, "effectArgs", "Arguments to use in combination with the specified effect. Should be a Json object string."); SwitchParameter<> & argServerInfo = parameters.add >('l', "list" , "List server info"); SwitchParameter<> & argClear = parameters.add >('x', "clear" , "Clear data for the priority channel provided by the -p option"); SwitchParameter<> & argClearAll = parameters.add >(0x0, "clearall" , "Clear data for all active priority channels"); @@ -59,6 +61,7 @@ int main(int argc, char * argv[]) argAddress.setDefault(defaultServerAddress.toStdString()); argPriority.setDefault(defaultPriority); argDuration.setDefault(-1); + argEffectArgs.setDefault(""); // parse all options optionParser.parse(argc, const_cast(argv)); @@ -74,12 +77,13 @@ int main(int argc, char * argv[]) bool colorTransform = argSaturation.isSet() || argValue.isSet() || argThreshold.isSet() || argGamma.isSet() || argBlacklevel.isSet() || argWhitelevel.isSet(); // check that exactly one command was given - int commandCount = count({argColor.isSet(), argImage.isSet(), argServerInfo.isSet(), argClear.isSet(), argClearAll.isSet(), colorTransform}); + int commandCount = count({argColor.isSet(), argImage.isSet(), argEffect.isSet(), argServerInfo.isSet(), argClear.isSet(), argClearAll.isSet(), colorTransform}); if (commandCount != 1) { std::cerr << (commandCount == 0 ? "No command found." : "Multiple commands found.") << " Provide exactly one of the following options:" << std::endl; std::cerr << " " << argColor.usageLine() << std::endl; std::cerr << " " << argImage.usageLine() << std::endl; + std::cerr << " " << argEffect.usageLine() << std::endl; std::cerr << " " << argServerInfo.usageLine() << std::endl; std::cerr << " " << argClear.usageLine() << std::endl; std::cerr << " " << argClearAll.usageLine() << std::endl; @@ -106,6 +110,10 @@ int main(int argc, char * argv[]) { connection.setImage(argImage.getValue(), argPriority.getValue(), argDuration.getValue()); } + else if (argEffect.isSet()) + { + connection.setEffect(argEffect.getValue(), argEffectArgs.getValue(), argPriority.getValue(), argDuration.getValue()); + } else if (argServerInfo.isSet()) { QString info = connection.getServerInfo(); diff --git a/src/hyperiond/CMakeLists.txt b/src/hyperiond/CMakeLists.txt index 646a8575..1a255b17 100644 --- a/src/hyperiond/CMakeLists.txt +++ b/src/hyperiond/CMakeLists.txt @@ -3,9 +3,9 @@ add_executable(hyperiond hyperiond.cpp) target_link_libraries(hyperiond - bootsequence hyperion xbmcvideochecker + effectengine jsonserver protoserver boblightserver) diff --git a/src/hyperiond/hyperiond.cpp b/src/hyperiond/hyperiond.cpp index bc789691..3f46c712 100644 --- a/src/hyperiond/hyperiond.cpp +++ b/src/hyperiond/hyperiond.cpp @@ -15,9 +15,6 @@ // Hyperion includes #include -// Bootsequence includes -#include - #ifdef ENABLE_DISPMANX // Dispmanx grabber includes #include @@ -26,6 +23,9 @@ // XBMC Video checker includes #include +// Effect engine includes +#include + // JsonServer includes #include @@ -93,15 +93,25 @@ int main(int argc, char** argv) std::cout << "Hyperion created and initialised" << std::endl; // create boot sequence if the configuration is present - BootSequence * bootSequence = nullptr; if (config.isMember("bootsequence")) { - bootSequence = BootSequenceFactory::createBootSequence(&hyperion, config["bootsequence"]); + const Json::Value effectConfig = config["bootsequence"]; - if (bootSequence != nullptr) + // Get the parameters for the bootsequence + const std::string effectName = effectConfig["effect"].asString(); + const unsigned duration_ms = effectConfig["duration_ms"].asUInt(); + const int priority = 0; + +// int retVal = -1; +// QMetaObject::invokeMethod(hyperion, "setEffect", Q_RETURN_ARG(int, retVal), Q_ARG(std::string, effectName), Q_ARG(Json::Value, Json::Value()), Q_ARG(int, priority), Q_ARG(int, duration_ms)); +// if (retVal == 0) + if (hyperion.setEffect(effectName, priority, duration_ms) == 0) { - bootSequence->start(); - std::cout << "Boot sequence created and started" << std::endl; + std::cout << "Boot sequence(" << effectName << ") created and started" << std::endl; + } + else + { + std::cout << "Failed to start boot sequence: " << effectName << std::endl; } } @@ -182,13 +192,13 @@ int main(int argc, char** argv) std::cout << "Application closed with code " << rc << std::endl; // Delete all component - delete bootSequence; #ifdef ENABLE_DISPMANX delete dispmanx; #endif delete xbmcVideoChecker; delete jsonServer; delete protoServer; + delete boblightServer; // leave application return rc;