diff --git a/CMakeLists.txt b/CMakeLists.txt index 05ba74a8..251bf2da 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,6 +13,9 @@ message(STATUS "ENABLE_DISPMANX = " ${ENABLE_DISPMANX}) option(ENABLE_SPIDEV "Enable the SPIDEV device" ON) message(STATUS "ENABLE_SPIDEV = " ${ENABLE_SPIDEV}) +option(ENABLE_WS2812BPWM "Enable the WS2812b-PWM device" OFF) +message(STATUS "ENABLE_WS2812BPWM = " ${ENABLE_WS2812BPWM}) + option(ENABLE_V4L2 "Enable the V4L2 grabber" ON) message(STATUS "ENABLE_V4L2 = " ${ENABLE_V4L2}) @@ -22,6 +25,13 @@ message(STATUS "ENABLE_X11 = " ${ENABLE_X11}) option(ENABLE_TINKERFORGE "Enable the TINKERFORGE device" ON) message(STATUS "ENABLE_TINKERFORGE = " ${ENABLE_TINKERFORGE}) +option(ENABLE_PROTOBUF "Enable PROTOBUF server" ON) +message(STATUS "ENABLE_PROTOBUF = " ${ENABLE_PROTOBUF}) + +if(ENABLE_V4L2 AND NOT ENABLE_PROTOBUF) + message(FATAL_ERROR "V4L2 grabber requires PROTOBUF. Disable V4L2 or enable PROTOBUF") +endif(ENABLE_V4L2 AND NOT ENABLE_PROTOBUF) + # Createt the configuration file # configure a header file to pass some of the CMake settings # to the source code @@ -57,12 +67,14 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x -Wall") # Configure the use of QT4 find_package(Qt4 COMPONENTS QtCore QtGui QtNetwork REQUIRED QUIET) -# add protocol buffers (make sure to find the static version) -set(CMAKE_FIND_LIBRARY_SUFFIXES_OLD ${CMAKE_FIND_LIBRARY_SUFFIXES}) -set(CMAKE_FIND_LIBRARY_SUFFIXES ".a") -find_package(Protobuf REQUIRED) -set(CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_FIND_LIBRARY_SUFFIXES_OLD}) -set(CMAKE_FIND_LIBRARY_SUFFIXES_OLD) +if (ENABLE_PROTOBUF) + # add protocol buffers (make sure to find the static version) + set(CMAKE_FIND_LIBRARY_SUFFIXES_OLD ${CMAKE_FIND_LIBRARY_SUFFIXES}) + set(CMAKE_FIND_LIBRARY_SUFFIXES ".a") + find_package(Protobuf REQUIRED) + set(CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_FIND_LIBRARY_SUFFIXES_OLD}) + set(CMAKE_FIND_LIBRARY_SUFFIXES_OLD) +endif (ENABLE_PROTOBUF) #add libusb and pthreads find_package(libusb-1.0 REQUIRED) diff --git a/HyperionConfig.h.in b/HyperionConfig.h.in index 17f040c0..3739bba8 100644 --- a/HyperionConfig.h.in +++ b/HyperionConfig.h.in @@ -9,5 +9,11 @@ // Define to enable the spi-device #cmakedefine ENABLE_SPIDEV +// Define to enable the ws2812b-pwm-device +#cmakedefine ENABLE_WS2812BPWM + // Define to enable the spi-device #cmakedefine ENABLE_TINKERFORGE + +// Define to enable PROTOBUF server +#cmakedefine ENABLE_PROTOBUF diff --git a/bin/install_hyperion.sh b/bin/install_hyperion.sh index 53dd28e6..f697c2f2 100755 --- a/bin/install_hyperion.sh +++ b/bin/install_hyperion.sh @@ -34,13 +34,13 @@ fi echo 'Downloading hyperion' if [ $IS_OPENELEC -eq 1 ]; then # OpenELEC has a readonly file system. Use alternative location - curl --get https://raw.github.com/tvdzwan/hyperion/master/deploy/hyperion.tar.gz | tar -C /storage -xz - curl --get https://raw.github.com/tvdzwan/hyperion/master/deploy/hyperion.deps.openelec-rpi.tar.gz | tar -C /storage/hyperion/bin -xz + curl -L --get https://raw.githubusercontent.com/tvdzwan/hyperion/master/deploy/hyperion.tar.gz | tar -C /storage -xz + curl -L --get https://raw.githubusercontent.com/tvdzwan/hyperion/master/deploy/hyperion.deps.openelec-rpi.tar.gz | tar -C /storage/hyperion/bin -xz # modify the default config to have a correct effect path sed -i 's:/opt:/storage:g' /storage/hyperion/config/hyperion.config.json else - wget https://raw.github.com/tvdzwan/hyperion/master/deploy/hyperion.tar.gz -O - | tar -C /opt -xz + wget https://raw.githubusercontent.com/tvdzwan/hyperion/master/deploy/hyperion.tar.gz -O - | tar -C /opt -xz fi # create links to the binaries @@ -68,9 +68,9 @@ fi if [ $USE_INITCTL -eq 1 ]; then echo 'Installing initctl script' if [ $IS_RASPBMC -eq 1 ]; then - wget -N https://raw.github.com/tvdzwan/hyperion/master/deploy/hyperion.conf -P /etc/init/ + wget -N https://raw.githubusercontent.com/tvdzwan/hyperion/master/deploy/hyperion.conf -P /etc/init/ else - wget -N https://raw.github.com/tvdzwan/hyperion/master/deploy/hyperion.xbian.conf -O /etc/init/hyperion.conf + wget -N https://raw.githubusercontent.com/tvdzwan/hyperion/master/deploy/hyperion.xbian.conf -O /etc/init/hyperion.conf fi elif [ $USE_SERVICE -eq 1 ]; then echo 'Installing startup script in init.d' diff --git a/cmake/Findlibusb-1.0.cmake b/cmake/Findlibusb-1.0.cmake index 26a82c4f..77474e9a 100644 --- a/cmake/Findlibusb-1.0.cmake +++ b/cmake/Findlibusb-1.0.cmake @@ -87,6 +87,7 @@ else (LIBUSB_1_LIBRARIES AND LIBUSB_1_INCLUDE_DIRS) message(STATUS " - Libraries: ${LIBUSB_1_LIBRARIES}") endif (NOT libusb_1_FIND_QUIETLY) else (LIBUSB_1_FOUND) + unset(LIBUSB_1_LIBRARY CACHE) if (libusb_1_FIND_REQUIRED) message(FATAL_ERROR "Could not find libusb") endif (libusb_1_FIND_REQUIRED) diff --git a/cmake/qt4/Qt4ConfigDependentSettings.cmake b/cmake/qt4/Qt4ConfigDependentSettings.cmake index 6db5da18..6df28f47 100644 --- a/cmake/qt4/Qt4ConfigDependentSettings.cmake +++ b/cmake/qt4/Qt4ConfigDependentSettings.cmake @@ -270,6 +270,16 @@ if(Q_WS_X11) endif() +if(Q_WS_QWS) + set(CMAKE_THREAD_PREFER_PTHREADS 1) + find_package(Threads) + if(CMAKE_USE_PTHREADS_INIT) + set(QT_QTCORE_LIB_DEPENDENCIES ${QT_QTCORE_LIB_DEPENDENCIES} ${CMAKE_THREAD_LIBS_INIT}) + endif() + + set (QT_QTCORE_LIB_DEPENDENCIES ${QT_QTCORE_LIB_DEPENDENCIES} ${CMAKE_DL_LIBS}) + +endif() if(Q_WS_WIN) set(QT_QTGUI_LIB_DEPENDENCIES ${QT_QTGUI_LIB_DEPENDENCIES} imm32 winmm) diff --git a/config/hyperion.config.json b/config/hyperion.config.json index fa08d80d..8e361ed0 100644 --- a/config/hyperion.config.json +++ b/config/hyperion.config.json @@ -80,7 +80,8 @@ { "type" : "none", "time_ms" : 200, - "updateFrequency" : 20.0000 + "updateFrequency" : 20.0000, + "updateDelay" : 0 } }, diff --git a/deploy/hummingboard_prerelease.tar.gz.REMOVED.git-id b/deploy/hummingboard_prerelease.tar.gz.REMOVED.git-id new file mode 100644 index 00000000..25e27e13 --- /dev/null +++ b/deploy/hummingboard_prerelease.tar.gz.REMOVED.git-id @@ -0,0 +1 @@ +63b2cc4bf190a0c0c996be6db30fcf03109c9625 \ No newline at end of file diff --git a/deploy/hyperion.tar.gz.REMOVED.git-id b/deploy/hyperion.tar.gz.REMOVED.git-id index a3a391be..874b3ca0 100644 --- a/deploy/hyperion.tar.gz.REMOVED.git-id +++ b/deploy/hyperion.tar.gz.REMOVED.git-id @@ -1 +1 @@ -d61b685eca164580cb39eb5bc3cf65b89afad410 \ No newline at end of file +c2332ae026dcd9b5ededbbe2db493ae13e5208c5 \ No newline at end of file diff --git a/effects/knight-rider.json b/effects/knight-rider.json index bf83f897..b4644387 100644 --- a/effects/knight-rider.json +++ b/effects/knight-rider.json @@ -4,6 +4,7 @@ "args" : { "speed" : 1.0, - "fadeFactor" : 0.7 + "fadeFactor" : 0.7, + "color" : [255,0,0] } } diff --git a/effects/knight-rider.py b/effects/knight-rider.py index d2ff3ecc..38c8aabd 100644 --- a/effects/knight-rider.py +++ b/effects/knight-rider.py @@ -2,9 +2,10 @@ import hyperion import time import colorsys -# Get the rotation time +# Get the parameters speed = float(hyperion.args.get('speed', 1.0)) fadeFactor = float(hyperion.args.get('fadeFactor', 0.7)) +color = hyperion.args.get('color', (255,0,0)) # Check parameters speed = max(0.0001, speed) @@ -13,7 +14,9 @@ fadeFactor = max(0.0, min(fadeFactor, 1.0)) # Initialize the led data width = 25 imageData = bytearray(width * (0,0,0)) -imageData[0] = 255 +imageData[0] = color[0] +imageData[1] = color[1] +imageData[2] = color[2] # Calculate the sleep time and rotation increment increment = 1 @@ -41,9 +44,13 @@ while not hyperion.abort(): # Fade the old data for j in range(width): imageData[3*j] = int(fadeFactor * imageData[3*j]) + imageData[3*j+1] = int(fadeFactor * imageData[3*j+1]) + imageData[3*j+2] = int(fadeFactor * imageData[3*j+2]) # Insert new data - imageData[3*position] = 255 + imageData[3*position] = color[0] + imageData[3*position+1] = color[1] + imageData[3*position+2] = color[2] # Sleep for a while time.sleep(sleepTime) diff --git a/effects/mood-blobs-cold.json b/effects/mood-blobs-cold.json new file mode 100644 index 00000000..d259b9f1 --- /dev/null +++ b/effects/mood-blobs-cold.json @@ -0,0 +1,16 @@ +{ + "name" : "Cold mood blobs", + "script" : "mood-blobs.py", + "args" : + { + "rotationTime" : 60.0, + "color" : [0,0,255], + "hueChange" : 30.0, + "blobs" : 5, + "reverse" : false, + "baseChange" : true, + "baseColorRangeLeft" : 160, + "baseColorRangeRight" : 320, + "baseColorChangeRate" : 2.0 + } +} diff --git a/effects/mood-blobs-full.json b/effects/mood-blobs-full.json new file mode 100644 index 00000000..8b230010 --- /dev/null +++ b/effects/mood-blobs-full.json @@ -0,0 +1,16 @@ +{ + "name" : "Full color mood blobs", + "script" : "mood-blobs.py", + "args" : + { + "rotationTime" : 60.0, + "color" : [0,0,255], + "hueChange" : 30.0, + "blobs" : 5, + "reverse" : false, + "baseChange" : true, + "baseColorRangeLeft" : 0, + "baseColorRangeRight" : 360, + "baseColorChangeRate" : 0.2 + } +} diff --git a/effects/mood-blobs-warm.json b/effects/mood-blobs-warm.json new file mode 100644 index 00000000..39443092 --- /dev/null +++ b/effects/mood-blobs-warm.json @@ -0,0 +1,16 @@ +{ + "name" : "Warm mood blobs", + "script" : "mood-blobs.py", + "args" : + { + "rotationTime" : 60.0, + "color" : [255,0,0], + "hueChange" : 30.0, + "blobs" : 5, + "reverse" : false, + "baseChange" : true, + "baseColorRangeLeft" : 333, + "baseColorRangeRight" : 151, + "baseColorChangeRate" : 2.0 + } +} diff --git a/effects/mood-blobs.py b/effects/mood-blobs.py index c0ce3d15..b638f3d3 100644 --- a/effects/mood-blobs.py +++ b/effects/mood-blobs.py @@ -6,14 +6,31 @@ 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 +hueChange = float(hyperion.args.get('hueChange', 60.0)) blobs = int(hyperion.args.get('blobs', 5)) reverse = bool(hyperion.args.get('reverse', False)) +baseColorChange = bool(hyperion.args.get('baseChange', False)) +baseColorRangeLeft = float(hyperion.args.get('baseColorRangeLeft',0.0)) # Degree +baseColorRangeRight = float(hyperion.args.get('baseColorRangeRight',360.0)) # Degree +baseColorChangeRate = float(hyperion.args.get('baseColorChangeRate',10.0)) # Seconds for one Degree + +# switch baseColor change off if left and right are too close together to see a difference in color +if (baseColorRangeRight > baseColorRangeLeft and (baseColorRangeRight - baseColorRangeLeft) < 10) or \ + (baseColorRangeLeft > baseColorRangeRight and ((baseColorRangeRight + 360) - baseColorRangeLeft) < 10): + baseColorChange = False + +# 360 -> 1 +fullColorWheelAvailable = (baseColorRangeRight % 360) == (baseColorRangeLeft % 360) +baseColorChangeIncreaseValue = 1.0 / 360.0 # 1 degree +hueChange /= 360.0 +baseColorRangeLeft = (baseColorRangeLeft / 360.0) +baseColorRangeRight = (baseColorRangeRight / 360.0) # Check parameters rotationTime = max(0.1, rotationTime) hueChange = max(0.0, min(abs(hueChange), .5)) blobs = max(1, blobs) +baseColorChangeRate = max(0, baseColorChangeRate) # > 0 # Calculate the color data baseHsv = colorsys.rgb_to_hsv(color[0]/255.0, color[1]/255.0, color[2]/255.0) @@ -27,6 +44,7 @@ for i in range(hyperion.ledCount): sleepTime = 0.1 amplitudePhaseIncrement = blobs * math.pi * sleepTime / rotationTime colorDataIncrement = 3 +baseColorChangeRate /= sleepTime # Switch direction if needed if reverse: @@ -39,23 +57,58 @@ 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) +baseColorChangeStepCount = 0 +baseHSVValue = baseHsv[0] +numberOfRotates = 0 - # 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) +while not hyperion.abort(): + + # move the basecolor + if baseColorChange: + # every baseColorChangeRate seconds + if baseColorChangeStepCount >= baseColorChangeRate: + baseColorChangeStepCount = 0 + # cyclic increment when the full colorwheel is available, move up and down otherwise + if fullColorWheelAvailable: + baseHSVValue = (baseHSVValue + baseColorChangeIncreaseValue) % baseColorRangeRight + else: + # switch increment direction if baseHSV <= left or baseHSV >= right + if baseColorChangeIncreaseValue < 0 and baseHSVValue > baseColorRangeLeft and (baseHSVValue + baseColorChangeIncreaseValue) <= baseColorRangeLeft: + baseColorChangeIncreaseValue = abs(baseColorChangeIncreaseValue) + elif baseColorChangeIncreaseValue > 0 and baseHSVValue < baseColorRangeRight and (baseHSVValue + baseColorChangeIncreaseValue) >= baseColorRangeRight : + baseColorChangeIncreaseValue = -abs(baseColorChangeIncreaseValue) + + baseHSVValue = (baseHSVValue + baseColorChangeIncreaseValue) % 1.0 + + # update color values + colorData = bytearray() + for i in range(hyperion.ledCount): + hue = (baseHSVValue + 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]))) + + # set correct rotation after reinitialisation of the array + colorData = colorData[-colorDataIncrement*numberOfRotates:] + colorData[:-colorDataIncrement*numberOfRotates] + + baseColorChangeStepCount += 1 + + # 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] + numberOfRotates = (numberOfRotates + 1) % hyperion.ledCount + rotateColors = not rotateColors + + # sleep for a while + time.sleep(sleepTime) diff --git a/include/hyperion/ImageProcessor.h b/include/hyperion/ImageProcessor.h index deb40df1..5049ac03 100644 --- a/include/hyperion/ImageProcessor.h +++ b/include/hyperion/ImageProcessor.h @@ -37,6 +37,9 @@ public: /// void setSize(const unsigned width, const unsigned height); + /// Enable or disable the black border detector + void enableBalckBorderDetector(bool enable); + /// /// Processes the image to a list of led colors. This will update the size of the buffer-image /// if required and call the image-to-leds mapping to determine the mean color per led. @@ -142,7 +145,7 @@ private: const LedString _ledString; /// Flag the enables(true)/disabled(false) blackborder detector - const bool _enableBlackBorderRemoval; + bool _enableBlackBorderRemoval; /// The processor for black border detection hyperion::BlackBorderProcessor * _borderProcessor; diff --git a/include/utils/Sleep.h b/include/utils/Sleep.h new file mode 100644 index 00000000..a2c55815 --- /dev/null +++ b/include/utils/Sleep.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +class Sleep : protected QThread { +public: + static inline void msleep(unsigned long msecs) { + QThread::msleep(msecs); + } +}; diff --git a/include/utils/VideoMode.h b/include/utils/VideoMode.h index f5cae94f..ae727657 100644 --- a/include/utils/VideoMode.h +++ b/include/utils/VideoMode.h @@ -18,7 +18,7 @@ inline VideoMode parse3DMode(std::string videoMode) // convert to lower case std::transform(videoMode.begin(), videoMode.end(), videoMode.begin(), ::tolower); - if (videoMode == "23DTAB") + if (videoMode == "3DTAB") { return VIDEO_3DTAB; } diff --git a/include/xbmcvideochecker/XBMCVideoChecker.h b/include/xbmcvideochecker/XBMCVideoChecker.h index 08fc4236..01401093 100644 --- a/include/xbmcvideochecker/XBMCVideoChecker.h +++ b/include/xbmcvideochecker/XBMCVideoChecker.h @@ -38,7 +38,7 @@ public: /// @param grabPhoto Whether or not to grab when the XBMC photo player is playing /// @param grabAudio Whether or not to grab when the XBMC audio player is playing /// @param grabMenu Whether or not to grab when nothing is playing (in XBMC menu) - /// @param grabScreensaver Whether or not to grab when the XBMC screensaver is activated + /// @param grabScreensaver Whether or not to grab when the XBMC screensaver is activated /// @param enable3DDetection Wheter or not to enable the detection of 3D movies playing /// XBMCVideoChecker(const std::string & address, uint16_t port, bool grabVideo, bool grabPhoto, bool grabAudio, bool grabMenu, bool grabScreensaver, bool enable3DDetection); @@ -96,6 +96,12 @@ private: /// The JSON-RPC message to check the screensaver const QString _checkScreensaverRequest; + /// The JSON-RPC message to check the active stereoscopicmode + const QString _getStereoscopicMode; + + /// The JSON-RPC message to check the xbmc version + const QString _getXbmcVersion; + /// The QT TCP Socket with connection to XBMC QTcpSocket _socket; @@ -111,7 +117,7 @@ private: /// Flag indicating whether or not to grab when XBMC is playing nothing (in menu) const bool _grabMenu; - /// Flag inidcating whether or not to grab when the XBMC screensaver is activated + /// Flag indicating whether or not to grab when the XBMC screensaver is activated const bool _grabScreensaver; /// Flag indicating wheter or not to enable the detection of 3D movies playing @@ -125,4 +131,7 @@ private: /// Previous emitted video mode VideoMode _previousVideoMode; + + /// XBMC version number + int _xbmcVersion; }; diff --git a/libsrc/CMakeLists.txt b/libsrc/CMakeLists.txt index 62911dd7..680e7a53 100644 --- a/libsrc/CMakeLists.txt +++ b/libsrc/CMakeLists.txt @@ -1,15 +1,19 @@ - -# Define the current source locations -SET(CURRENT_HEADER_DIR ${CMAKE_SOURCE_DIR}/include) -SET(CURRENT_SOURCE_DIR ${CMAKE_SOURCE_DIR}/libsrc) - -add_subdirectory(hyperion) -add_subdirectory(blackborder) -add_subdirectory(jsonserver) -add_subdirectory(protoserver) -add_subdirectory(boblightserver) -add_subdirectory(leddevice) -add_subdirectory(utils) -add_subdirectory(xbmcvideochecker) -add_subdirectory(effectengine) -add_subdirectory(grabber) + +# Define the current source locations +SET(CURRENT_HEADER_DIR ${CMAKE_SOURCE_DIR}/include) +SET(CURRENT_SOURCE_DIR ${CMAKE_SOURCE_DIR}/libsrc) + +add_subdirectory(hyperion) +add_subdirectory(blackborder) +add_subdirectory(jsonserver) + +if (ENABLE_PROTOBUF) + add_subdirectory(protoserver) +endif (ENABLE_PROTOBUF) + +add_subdirectory(boblightserver) +add_subdirectory(leddevice) +add_subdirectory(utils) +add_subdirectory(xbmcvideochecker) +add_subdirectory(effectengine) +add_subdirectory(grabber) diff --git a/libsrc/effectengine/Effect.cpp b/libsrc/effectengine/Effect.cpp index 09411c87..3b9c67e7 100644 --- a/libsrc/effectengine/Effect.cpp +++ b/libsrc/effectengine/Effect.cpp @@ -64,6 +64,9 @@ Effect::Effect(PyThreadState * mainThreadState, int priority, int timeout, const { _colors.resize(_imageProcessor->getLedCount(), ColorRgb::BLACK); + // disable the black border detector for effects + _imageProcessor->enableBalckBorderDetector(false); + // connect the finished signal connect(this, SIGNAL(finished()), this, SLOT(effectFinished())); } @@ -310,7 +313,7 @@ PyObject* Effect::wrapSetImage(PyObject *self, PyObject *args) } else { - PyErr_SetString(PyExc_RuntimeError, "Length of bytearray argument should be 3*ledCount"); + PyErr_SetString(PyExc_RuntimeError, "Length of bytearray argument should be 3*width*height"); return nullptr; } } diff --git a/libsrc/hyperion/Hyperion.cpp b/libsrc/hyperion/Hyperion.cpp index a81e4f3c..5e76d8e8 100644 --- a/libsrc/hyperion/Hyperion.cpp +++ b/libsrc/hyperion/Hyperion.cpp @@ -243,8 +243,13 @@ LedDevice * Hyperion::createColorSmoothing(const Json::Value & smoothingConfig, } else { + const unsigned updateDelay = smoothingConfig.get("updateDelay", Json::Value(0u)).asUInt(); std::cout << "Creating linear smoothing" << std::endl; - return new LinearColorSmoothing(ledDevice, smoothingConfig["updateFrequency"].asDouble(), smoothingConfig["time_ms"].asInt()); + return new LinearColorSmoothing( + ledDevice, + smoothingConfig["updateFrequency"].asDouble(), + smoothingConfig["time_ms"].asInt(), + updateDelay); } } else diff --git a/libsrc/hyperion/ImageProcessor.cpp b/libsrc/hyperion/ImageProcessor.cpp index 66b02e74..25784d0a 100644 --- a/libsrc/hyperion/ImageProcessor.cpp +++ b/libsrc/hyperion/ImageProcessor.cpp @@ -43,6 +43,11 @@ void ImageProcessor::setSize(const unsigned width, const unsigned height) _imageToLeds = new ImageToLedsMap(width, height, 0, 0, _ledString.leds()); } +void ImageProcessor::enableBalckBorderDetector(bool enable) +{ + _enableBlackBorderRemoval = enable; +} + bool ImageProcessor::getScanParameters(size_t led, double &hscanBegin, double &hscanEnd, double &vscanBegin, double &vscanEnd) const { if (led < _ledString.leds().size()) diff --git a/libsrc/hyperion/ImageToLedsMap.cpp b/libsrc/hyperion/ImageToLedsMap.cpp index 202f22ff..aa309768 100644 --- a/libsrc/hyperion/ImageToLedsMap.cpp +++ b/libsrc/hyperion/ImageToLedsMap.cpp @@ -35,15 +35,16 @@ ImageToLedsMap::ImageToLedsMap( // skip leds without area if ((led.maxX_frac-led.minX_frac) < 1e-6 || (led.maxY_frac-led.minY_frac) < 1e-6) { + mColorsMap.emplace_back(); continue; } - + // Compute the index boundaries for this led unsigned minX_idx = xOffset + unsigned(std::round(actualWidth * led.minX_frac)); unsigned maxX_idx = xOffset + unsigned(std::round(actualWidth * led.maxX_frac)); unsigned minY_idx = yOffset + unsigned(std::round(actualHeight * led.minY_frac)); unsigned maxY_idx = yOffset + unsigned(std::round(actualHeight * led.maxY_frac)); - + // make sure that the area is at least a single led large minX_idx = std::min(minX_idx, xOffset + actualWidth - 1); if (minX_idx == maxX_idx) diff --git a/libsrc/hyperion/LinearColorSmoothing.cpp b/libsrc/hyperion/LinearColorSmoothing.cpp index 0fe3e208..eef5569d 100644 --- a/libsrc/hyperion/LinearColorSmoothing.cpp +++ b/libsrc/hyperion/LinearColorSmoothing.cpp @@ -3,29 +3,38 @@ #include "LinearColorSmoothing.h" -LinearColorSmoothing::LinearColorSmoothing(LedDevice *ledDevice, double ledUpdateFrequency_hz, int settlingTime_ms) : +LinearColorSmoothing::LinearColorSmoothing( + LedDevice * ledDevice, + double ledUpdateFrequency_hz, + int settlingTime_ms, + unsigned updateDelay) : QObject(), LedDevice(), _ledDevice(ledDevice), _updateInterval(1000 / ledUpdateFrequency_hz), _settlingTime(settlingTime_ms), - _timer() + _timer(), + _outputDelay(updateDelay) { _timer.setSingleShot(false); _timer.setInterval(_updateInterval); connect(&_timer, SIGNAL(timeout()), this, SLOT(updateLeds())); + + std::cout << "Created linear-smoothing(interval_ms=" << _updateInterval << ";settlingTime_ms=" << settlingTime_ms << ";updateDelay=" << _outputDelay << std::endl; } LinearColorSmoothing::~LinearColorSmoothing() { + // Make sure to switch off the underlying led-device (because switchOff is no longer forwarded) + _ledDevice->switchOff(); delete _ledDevice; } int LinearColorSmoothing::write(const std::vector &ledValues) { // received a new target color - if (_previousValues.size() == 0) + if (_previousValues.empty()) { // not initialized yet _targetTime = QDateTime::currentMSecsSinceEpoch() + _settlingTime; @@ -46,17 +55,20 @@ int LinearColorSmoothing::write(const std::vector &ledValues) int LinearColorSmoothing::switchOff() { - // stop smoothing filter - _timer.stop(); + // We will keep updating the leds (but with pure-black) - // return to uninitialized state - _previousValues.clear(); - _previousTime = 0; - _targetValues.clear(); + // Clear the smoothing parameters + std::fill(_targetValues.begin(), _targetValues.end(), ColorRgb::BLACK); _targetTime = 0; - // finally switch off all leds - return _ledDevice->switchOff(); + // Erase the output-queue + for (unsigned i=0; i<_outputQueue.size(); ++i) + { + _outputQueue.push_back(_targetValues); + _outputQueue.pop_front(); + } + + return 0; } void LinearColorSmoothing::updateLeds() @@ -69,7 +81,7 @@ void LinearColorSmoothing::updateLeds() memcpy(_previousValues.data(), _targetValues.data(), _targetValues.size() * sizeof(ColorRgb)); _previousTime = now; - _ledDevice->write(_previousValues); + queueColors(_previousValues); } else { @@ -86,6 +98,26 @@ void LinearColorSmoothing::updateLeds() } _previousTime = now; - _ledDevice->write(_previousValues); + queueColors(_previousValues); + } +} + +void LinearColorSmoothing::queueColors(const std::vector & ledColors) +{ + if (_outputDelay == 0) + { + // No output delay => immediate write + _ledDevice->write(ledColors); + } + else + { + // Push the new colors in the delay-buffer + _outputQueue.push_back(ledColors); + // If the delay-buffer is filled pop the front and write to device + if (_outputQueue.size() > _outputDelay) + { + _ledDevice->write(_outputQueue.front()); + _outputQueue.pop_front(); + } } } diff --git a/libsrc/hyperion/LinearColorSmoothing.h b/libsrc/hyperion/LinearColorSmoothing.h index 5c27862d..2b75059e 100644 --- a/libsrc/hyperion/LinearColorSmoothing.h +++ b/libsrc/hyperion/LinearColorSmoothing.h @@ -23,7 +23,8 @@ public: /// @param LedDevice the led device /// @param LedUpdatFrequency The frequency at which the leds will be updated (Hz) /// @param settingTime The time after which the updated led values have been fully applied (sec) - LinearColorSmoothing(LedDevice *ledDevice, double ledUpdateFrequency, int settlingTime); + /// @param updateDelay The number of frames to delay outgoing led updates + LinearColorSmoothing(LedDevice *ledDevice, double ledUpdateFrequency, int settlingTime, unsigned updateDelay); /// Destructor virtual ~LinearColorSmoothing(); @@ -43,6 +44,13 @@ private slots: void updateLeds(); private: + /** + * Pushes the colors into the output queue and popping the head to the led-device + * + * @param ledColors The colors to queue + */ + void queueColors(const std::vector & ledColors); + /// The led device LedDevice * _ledDevice; @@ -66,4 +74,10 @@ private: /// The previously written led data std::vector _previousValues; + + /** The number of updates to keep in the output queue (delayed) before being output */ + const unsigned _outputDelay; + /** The output queue */ + std::list > _outputQueue; + }; diff --git a/libsrc/jsonserver/JsonClientConnection.cpp b/libsrc/jsonserver/JsonClientConnection.cpp index d478a4d2..87fb7c1f 100644 --- a/libsrc/jsonserver/JsonClientConnection.cpp +++ b/libsrc/jsonserver/JsonClientConnection.cpp @@ -10,6 +10,8 @@ // Qt includes #include #include +#include +#include // hyperion util includes #include @@ -25,7 +27,8 @@ JsonClientConnection::JsonClientConnection(QTcpSocket *socket, Hyperion * hyperi _socket(socket), _imageProcessor(ImageProcessorFactory::getInstance().newImageProcessor()), _hyperion(hyperion), - _receiveBuffer() + _receiveBuffer(), + _webSocketHandshakeDone(false) { // connect internal signals and slots connect(_socket, SIGNAL(disconnected()), this, SLOT(socketClosed())); @@ -41,26 +44,164 @@ JsonClientConnection::~JsonClientConnection() void JsonClientConnection::readData() { _receiveBuffer += _socket->readAll(); - - int bytes = _receiveBuffer.indexOf('\n') + 1; - while(bytes > 0) + + if (_webSocketHandshakeDone) { - // create message string - std::string message(_receiveBuffer.data(), bytes); + // websocket mode, data frame + handleWebSocketFrame(); + } else + { + // might be a handshake request or raw socket data + if(_receiveBuffer.contains("Upgrade: websocket")) + { + doWebSocketHandshake(); + } else + { + // raw socket data, handling as usual + int bytes = _receiveBuffer.indexOf('\n') + 1; + while(bytes > 0) + { + // create message string + std::string message(_receiveBuffer.data(), bytes); - // remove message data from buffer - _receiveBuffer = _receiveBuffer.mid(bytes); + // remove message data from buffer + _receiveBuffer = _receiveBuffer.mid(bytes); - // handle message - handleMessage(message); + // handle message + handleMessage(message); - // try too look up '\n' again - bytes = _receiveBuffer.indexOf('\n') + 1; + // try too look up '\n' again + bytes = _receiveBuffer.indexOf('\n') + 1; + } + } } } +void JsonClientConnection::handleWebSocketFrame() +{ + if ((_receiveBuffer.at(0) & 0x80) == 0x80) + { + // final bit found, frame complete + quint8 * maskKey = NULL; + quint8 opCode = _receiveBuffer.at(0) & 0x0F; + bool isMasked = (_receiveBuffer.at(1) & 0x80) == 0x80; + quint64 payloadLength = _receiveBuffer.at(1) & 0x7F; + quint32 index = 2; + + switch (payloadLength) + { + case 126: + payloadLength = ((_receiveBuffer.at(2) << 8) & 0xFF00) | (_receiveBuffer.at(3) & 0xFF); + index += 2; + break; + case 127: + payloadLength = 0; + for (uint i=0; i < 8; i++) { + payloadLength |= ((quint64)(_receiveBuffer.at(index+i) & 0xFF)) << (8*(7-i)); + } + index += 8; + break; + default: + break; + } + + if (isMasked) + { + // if the data is masked we need to get the key for unmasking + maskKey = new quint8[4]; + for (uint i=0; i < 4; i++) + { + maskKey[i] = _receiveBuffer.at(index + i); + } + index += 4; + } + + // check the type of data frame + switch (opCode) + { + case 0x01: + { + // frame contains text, extract it + QByteArray result = _receiveBuffer.mid(index, payloadLength); + _receiveBuffer.clear(); + + // unmask data if necessary + if (isMasked) + { + for (uint i=0; i < payloadLength; i++) + { + result[i] = (result[i] ^ maskKey[i % 4]); + } + if (maskKey != NULL) + { + delete[] maskKey; + maskKey = NULL; + } + } + + handleMessage(QString(result).toStdString()); + } + break; + case 0x08: + { + // close request, confirm + quint8 close[] = {0x88, 0}; + _socket->write((const char*)close, 2); + _socket->flush(); + _socket->close(); + } + break; + case 0x09: + { + // ping received, send pong + quint8 pong[] = {0x0A, 0}; + _socket->write((const char*)pong, 2); + _socket->flush(); + } + break; + } + } else + { + std::cout << "Someone is sending very big messages over several frames... it's not supported yet" << std::endl; + quint8 close[] = {0x88, 0}; + _socket->write((const char*)close, 2); + _socket->flush(); + _socket->close(); + } +} + +void JsonClientConnection::doWebSocketHandshake() +{ + // http header, might not be a very reliable check... + std::cout << "Websocket handshake" << std::endl; + + // get the key to prepare an answer + int start = _receiveBuffer.indexOf("Sec-WebSocket-Key") + 19; + std::string value(_receiveBuffer.mid(start, _receiveBuffer.indexOf("\r\n", start) - start).data()); + _receiveBuffer.clear(); + + // must be always appended + value += "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + + // generate sha1 hash + QByteArray hash = QCryptographicHash::hash(value.c_str(), QCryptographicHash::Sha1); + + // prepare an answer + std::ostringstream h; + h << "HTTP/1.1 101 Switching Protocols\r\n" << + "Upgrade: websocket\r\n" << + "Connection: Upgrade\r\n" << + "Sec-WebSocket-Accept: " << QString(hash.toBase64()).toStdString() << "\r\n\r\n"; + + _socket->write(h.str().c_str()); + _socket->flush(); + // we are in WebSocket mode, data frames should follow next + _webSocketHandshakeDone = true; +} + void JsonClientConnection::socketClosed() { + _webSocketHandshakeDone = false; emit connectionClosed(this); } @@ -205,6 +346,9 @@ void JsonClientConnection::handleServerInfoCommand(const Json::Value &) Json::Value result; result["success"] = true; Json::Value & info = result["info"]; + + // add host name for remote clients + info["hostname"] = QHostInfo::localHostName().toStdString(); // collect priority information Json::Value & priorities = info["priorities"] = Json::Value(Json::arrayValue); @@ -362,7 +506,32 @@ void JsonClientConnection::sendMessage(const Json::Value &message) { Json::FastWriter writer; std::string serializedReply = writer.write(message); - _socket->write(serializedReply.data(), serializedReply.length()); + + if (!_webSocketHandshakeDone) + { + // raw tcp socket mode + _socket->write(serializedReply.data(), serializedReply.length()); + } else + { + // websocket mode + quint32 size = serializedReply.length(); + + // prepare data frame + QByteArray response; + response.append(0x81); + if (size > 125) + { + response.append(0x7E); + response.append((size >> 8) & 0xFF); + response.append(size & 0xFF); + } else { + response.append(size); + } + + response.append(serializedReply.c_str(), serializedReply.length()); + + _socket->write(response.data(), response.length()); + } } void JsonClientConnection::sendSuccessReply() diff --git a/libsrc/jsonserver/JsonClientConnection.h b/libsrc/jsonserver/JsonClientConnection.h index dc80deba..6575388a 100644 --- a/libsrc/jsonserver/JsonClientConnection.h +++ b/libsrc/jsonserver/JsonClientConnection.h @@ -136,6 +136,16 @@ private: /// @param error String describing the error /// void sendErrorReply(const std::string & error); + + /// + /// Do handshake for a websocket connection + /// + void doWebSocketHandshake(); + + /// + /// Handle incoming websocket data frame + /// + void handleWebSocketFrame(); private: /// @@ -161,4 +171,7 @@ private: /// The buffer used for reading data from the socket QByteArray _receiveBuffer; + + /// used for WebSocket detection and connection handling + bool _webSocketHandshakeDone; }; diff --git a/libsrc/jsonserver/schema/schema-transform.json b/libsrc/jsonserver/schema/schema-transform.json index a135e12f..7b939839 100644 --- a/libsrc/jsonserver/schema/schema-transform.json +++ b/libsrc/jsonserver/schema/schema-transform.json @@ -16,12 +16,12 @@ "required" : false }, "saturationGain" : { - "type" : "double", + "type" : "number", "required" : false, "minimum" : 0.0 }, "valueGain" : { - "type" : "double", + "type" : "number", "required" : false, "minimum" : 0.0 }, @@ -29,7 +29,7 @@ "type": "array", "required": false, "items" : { - "type": "double", + "type": "number", "minimum": 0.0, "maximum": 1.0 }, @@ -40,7 +40,7 @@ "type": "array", "required": false, "items" : { - "type": "double", + "type": "number", "minimum": 0.0 }, "minItems": 3, @@ -50,7 +50,7 @@ "type": "array", "required": false, "items" : { - "type": "double" + "type": "number" }, "minItems": 3, "maxItems": 3 @@ -59,7 +59,7 @@ "type": "array", "required": false, "items" : { - "type": "double" + "type": "number" }, "minItems": 3, "maxItems": 3 diff --git a/libsrc/leddevice/CMakeLists.txt b/libsrc/leddevice/CMakeLists.txt old mode 100644 new mode 100755 index 443e845d..4304c874 --- a/libsrc/leddevice/CMakeLists.txt +++ b/libsrc/leddevice/CMakeLists.txt @@ -13,15 +13,15 @@ include_directories( # Group the headers that go through the MOC compiler SET(Leddevice_QT_HEADERS + ${CURRENT_SOURCE_DIR}/LedRs232Device.h ${CURRENT_SOURCE_DIR}/LedDeviceAdalight.h + ${CURRENT_SOURCE_DIR}/LedDevicePhilipsHue.h ) SET(Leddevice_HEADERS ${CURRENT_HEADER_DIR}/LedDevice.h ${CURRENT_HEADER_DIR}/LedDeviceFactory.h - ${CURRENT_SOURCE_DIR}/LedRs232Device.h - ${CURRENT_SOURCE_DIR}/LedDeviceLightpack.h ${CURRENT_SOURCE_DIR}/LedDeviceMultiLightpack.h ${CURRENT_SOURCE_DIR}/LedDevicePaintpack.h @@ -29,6 +29,7 @@ SET(Leddevice_HEADERS ${CURRENT_SOURCE_DIR}/LedDeviceSedu.h ${CURRENT_SOURCE_DIR}/LedDeviceTest.h ${CURRENT_SOURCE_DIR}/LedDeviceHyperionUsbasp.h + ${CURRENT_SOURCE_DIR}/LedDeviceTpm2.h ) SET(Leddevice_SOURCES @@ -44,6 +45,8 @@ SET(Leddevice_SOURCES ${CURRENT_SOURCE_DIR}/LedDeviceSedu.cpp ${CURRENT_SOURCE_DIR}/LedDeviceTest.cpp ${CURRENT_SOURCE_DIR}/LedDeviceHyperionUsbasp.cpp + ${CURRENT_SOURCE_DIR}/LedDevicePhilipsHue.cpp + ${CURRENT_SOURCE_DIR}/LedDeviceTpm2.cpp ) if(ENABLE_SPIDEV) @@ -65,6 +68,17 @@ if(ENABLE_SPIDEV) ) endif(ENABLE_SPIDEV) +if(ENABLE_WS2812BPWM) +SET(Leddevice_HEADERS + ${Leddevice_HEADERS} + ${CURRENT_SOURCE_DIR}/LedDeviceWS2812b.h + ) +SET(Leddevice_SOURCES + ${Leddevice_SOURCES} + ${CURRENT_SOURCE_DIR}/LedDeviceWS2812b.cpp +) +endif(ENABLE_WS2812BPWM) + if(ENABLE_TINKERFORGE) SET(Leddevice_HEADERS ${Leddevice_HEADERS} diff --git a/libsrc/leddevice/LedDeviceAdalight.cpp b/libsrc/leddevice/LedDeviceAdalight.cpp index f19bb4be..47c09278 100644 --- a/libsrc/leddevice/LedDeviceAdalight.cpp +++ b/libsrc/leddevice/LedDeviceAdalight.cpp @@ -11,8 +11,8 @@ // hyperion local includes #include "LedDeviceAdalight.h" -LedDeviceAdalight::LedDeviceAdalight(const std::string& outputDevice, const unsigned baudrate) : - LedRs232Device(outputDevice, baudrate), +LedDeviceAdalight::LedDeviceAdalight(const std::string& outputDevice, const unsigned baudrate, int delayAfterConnect_ms) : + LedRs232Device(outputDevice, baudrate, delayAfterConnect_ms), _ledBuffer(0), _timer() { diff --git a/libsrc/leddevice/LedDeviceAdalight.h b/libsrc/leddevice/LedDeviceAdalight.h index 30e7bfb7..bc08200b 100644 --- a/libsrc/leddevice/LedDeviceAdalight.h +++ b/libsrc/leddevice/LedDeviceAdalight.h @@ -12,7 +12,7 @@ /// /// Implementation of the LedDevice interface for writing to an Adalight led device. /// -class LedDeviceAdalight : public QObject, public LedRs232Device +class LedDeviceAdalight : public LedRs232Device { Q_OBJECT @@ -23,7 +23,7 @@ public: /// @param outputDevice The name of the output device (eg '/dev/ttyS0') /// @param baudrate The used baudrate for writing to the output device /// - LedDeviceAdalight(const std::string& outputDevice, const unsigned baudrate); + LedDeviceAdalight(const std::string& outputDevice, const unsigned baudrate, int delayAfterConnect_ms); /// /// Writes the led color values to the led-device diff --git a/libsrc/leddevice/LedDeviceFactory.cpp b/libsrc/leddevice/LedDeviceFactory.cpp old mode 100644 new mode 100755 index b5bc6f5e..fb602b02 --- a/libsrc/leddevice/LedDeviceFactory.cpp +++ b/libsrc/leddevice/LedDeviceFactory.cpp @@ -28,6 +28,12 @@ #include "LedDeviceSedu.h" #include "LedDeviceTest.h" #include "LedDeviceHyperionUsbasp.h" +#include "LedDevicePhilipsHue.h" +#include "LedDeviceTpm2.h" + +#ifdef ENABLE_WS2812BPWM + #include "LedDeviceWS2812b.h" +#endif LedDevice * LedDeviceFactory::construct(const Json::Value & deviceConfig) { @@ -42,8 +48,9 @@ LedDevice * LedDeviceFactory::construct(const Json::Value & deviceConfig) { const std::string output = deviceConfig["output"].asString(); const unsigned rate = deviceConfig["rate"].asInt(); + const int delay_ms = deviceConfig["delayAfterConnect"].asInt(); - LedDeviceAdalight* deviceAdalight = new LedDeviceAdalight(output, rate); + LedDeviceAdalight* deviceAdalight = new LedDeviceAdalight(output, rate, delay_ms); deviceAdalight->open(); device = deviceAdalight; @@ -159,11 +166,33 @@ LedDevice * LedDeviceFactory::construct(const Json::Value & deviceConfig) deviceHyperionUsbasp->open(); device = deviceHyperionUsbasp; } + else if (type == "philipshue") + { + const std::string output = deviceConfig["output"].asString(); + const bool switchOffOnBlack = deviceConfig.get("switchOffOnBlack", true).asBool(); + device = new LedDevicePhilipsHue(output, switchOffOnBlack); + } else if (type == "test") { const std::string output = deviceConfig["output"].asString(); device = new LedDeviceTest(output); } + else if (type == "tpm2") + { + const std::string output = deviceConfig["output"].asString(); + const unsigned rate = deviceConfig["rate"].asInt(); + + LedDeviceTpm2 * deviceTpm2 = new LedDeviceTpm2(output, rate); + deviceTpm2->open(); + device = deviceTpm2; + } +#ifdef ENABLE_WS2812BPWM + else if (type == "ws2812b") + { + LedDeviceWS2812b * ledDeviceWS2812b = new LedDeviceWS2812b(); + device = ledDeviceWS2812b; + } +#endif else { std::cout << "Unable to create device " << type << std::endl; diff --git a/libsrc/leddevice/LedDevicePhilipsHue.cpp b/libsrc/leddevice/LedDevicePhilipsHue.cpp new file mode 100755 index 00000000..35607871 --- /dev/null +++ b/libsrc/leddevice/LedDevicePhilipsHue.cpp @@ -0,0 +1,291 @@ +// Local-Hyperion includes +#include "LedDevicePhilipsHue.h" + +// jsoncpp includes +#include + +// qt includes +#include +#include +#include +#include + +#include + +bool operator ==(CiColor p1, CiColor p2) { + return (p1.x == p2.x) && (p1.y == p2.y) && (p1.bri == p2.bri); +} + +bool operator !=(CiColor p1, CiColor p2) { + return !(p1 == p2); +} + +PhilipsHueLamp::PhilipsHueLamp(unsigned int id, QString originalState, QString modelId) : + id(id), originalState(originalState) { + // Hue system model ids. + const std::set HUE_BULBS_MODEL_IDS = { "LCT001", "LCT002", "LCT003" }; + const std::set LIVING_COLORS_MODEL_IDS = { "LLC001", "LLC005", "LLC006", "LLC007", "LLC011", "LLC012", + "LLC013", "LST001" }; + // Find id in the sets and set the appropiate color space. + if (HUE_BULBS_MODEL_IDS.find(modelId) != HUE_BULBS_MODEL_IDS.end()) { + colorSpace.red = {0.675f, 0.322f}; + colorSpace.green = {0.4091f, 0.518f}; + colorSpace.blue = {0.167f, 0.04f}; + } else if (LIVING_COLORS_MODEL_IDS.find(modelId) != LIVING_COLORS_MODEL_IDS.end()) { + colorSpace.red = {0.703f, 0.296f}; + colorSpace.green = {0.214f, 0.709f}; + colorSpace.blue = {0.139f, 0.081f}; + } else { + colorSpace.red = {1.0f, 0.0f}; + colorSpace.green = {0.0f, 1.0f}; + colorSpace.blue = {0.0f, 0.0f}; + } + // Initialize black color. + black = rgbToCiColor(0.0f, 0.0f, 0.0f); + // Initialize color with black + color = {black.x, black.y, black.bri}; +} + +float PhilipsHueLamp::crossProduct(CiColor p1, CiColor p2) { + return p1.x * p2.y - p1.y * p2.x; +} + +bool PhilipsHueLamp::isPointInLampsReach(CiColor p) { + CiColor v1 = { colorSpace.green.x - colorSpace.red.x, colorSpace.green.y - colorSpace.red.y }; + CiColor v2 = { colorSpace.blue.x - colorSpace.red.x, colorSpace.blue.y - colorSpace.red.y }; + CiColor q = { p.x - colorSpace.red.x, p.y - colorSpace.red.y }; + float s = crossProduct(q, v2) / crossProduct(v1, v2); + float t = crossProduct(v1, q) / crossProduct(v1, v2); + if ((s >= 0.0f) && (t >= 0.0f) && (s + t <= 1.0f)) { + return true; + } + return false; +} + +CiColor PhilipsHueLamp::getClosestPointToPoint(CiColor a, CiColor b, CiColor p) { + CiColor AP = { p.x - a.x, p.y - a.y }; + CiColor AB = { b.x - a.x, b.y - a.y }; + float ab2 = AB.x * AB.x + AB.y * AB.y; + float ap_ab = AP.x * AB.x + AP.y * AB.y; + float t = ap_ab / ab2; + if (t < 0.0f) { + t = 0.0f; + } else if (t > 1.0f) { + t = 1.0f; + } + return {a.x + AB.x * t, a.y + AB.y * t}; +} + +float PhilipsHueLamp::getDistanceBetweenTwoPoints(CiColor p1, CiColor p2) { + // Horizontal difference. + float dx = p1.x - p2.x; + // Vertical difference. + float dy = p1.y - p2.y; + // Absolute value. + return sqrt(dx * dx + dy * dy); +} + +CiColor PhilipsHueLamp::rgbToCiColor(float red, float green, float blue) { + // Apply gamma correction. + float r = (red > 0.04045f) ? powf((red + 0.055f) / (1.0f + 0.055f), 2.4f) : (red / 12.92f); + float g = (green > 0.04045f) ? powf((green + 0.055f) / (1.0f + 0.055f), 2.4f) : (green / 12.92f); + float b = (blue > 0.04045f) ? powf((blue + 0.055f) / (1.0f + 0.055f), 2.4f) : (blue / 12.92f); + // Convert to XYZ space. + float X = r * 0.649926f + g * 0.103455f + b * 0.197109f; + float Y = r * 0.234327f + g * 0.743075f + b * 0.022598f; + float Z = r * 0.0000000f + g * 0.053077f + b * 1.035763f; + // Convert to x,y space. + float cx = X / (X + Y + Z); + float cy = Y / (X + Y + Z); + if (isnan(cx)) { + cx = 0.0f; + } + if (isnan(cy)) { + cy = 0.0f; + } + // Brightness is simply Y in the XYZ space. + CiColor xy = { cx, cy, Y }; + // Check if the given XY value is within the color reach of our lamps. + if (!isPointInLampsReach(xy)) { + // It seems the color is out of reach let's find the closes color we can produce with our lamp and send this XY value out. + CiColor pAB = getClosestPointToPoint(colorSpace.red, colorSpace.green, xy); + CiColor pAC = getClosestPointToPoint(colorSpace.blue, colorSpace.red, xy); + CiColor pBC = getClosestPointToPoint(colorSpace.green, colorSpace.blue, xy); + // Get the distances per point and see which point is closer to our Point. + float dAB = getDistanceBetweenTwoPoints(xy, pAB); + float dAC = getDistanceBetweenTwoPoints(xy, pAC); + float dBC = getDistanceBetweenTwoPoints(xy, pBC); + float lowest = dAB; + CiColor closestPoint = pAB; + if (dAC < lowest) { + lowest = dAC; + closestPoint = pAC; + } + if (dBC < lowest) { + lowest = dBC; + closestPoint = pBC; + } + // Change the xy value to a value which is within the reach of the lamp. + xy.x = closestPoint.x; + xy.y = closestPoint.y; + } + return xy; +} + +LedDevicePhilipsHue::LedDevicePhilipsHue(const std::string& output, bool switchOffOnBlack) : + host(output.c_str()), username("newdeveloper"), switchOffOnBlack(switchOffOnBlack) { + http = new QHttp(host); + timer.setInterval(3000); + timer.setSingleShot(true); + connect(&timer, SIGNAL(timeout()), this, SLOT(restoreStates())); +} + +LedDevicePhilipsHue::~LedDevicePhilipsHue() { + delete http; +} + +int LedDevicePhilipsHue::write(const std::vector & ledValues) { + // Save light states if not done before. + if (!areStatesSaved()) { + saveStates((unsigned int) ledValues.size()); + switchOn((unsigned int) ledValues.size()); + } + // If there are less states saved than colors given, then maybe something went wrong before. + if (lamps.size() != ledValues.size()) { + restoreStates(); + return 0; + } + // Iterate through colors and set light states. + unsigned int idx = 0; + for (const ColorRgb& color : ledValues) { + // Get lamp. + PhilipsHueLamp& lamp = lamps.at(idx); + // Scale colors from [0, 255] to [0, 1] and convert to xy space. + CiColor xy = lamp.rgbToCiColor(color.red / 255.0f, color.green / 255.0f, color.blue / 255.0f); + // Write color if color has been changed. + if (xy != lamp.color) { + // Switch on if the lamp has been previously switched off. + if (switchOffOnBlack && lamp.color == lamp.black) { + + } + // Send adjust color and brightness command in JSON format. + put(getStateRoute(lamp.id), + QString("{\"xy\": [%1, %2], \"bri\": %3}").arg(xy.x).arg(xy.y).arg(qRound(xy.bri * 255.0f))); + + } + // Switch lamp off if switchOffOnBlack is enabled and the lamp is currently on. + if (switchOffOnBlack) { + // From black to a color. + if (lamp.color == lamp.black && xy != lamp.black) { + put(getStateRoute(lamp.id), QString("{\"on\": true}")); + } + // From a color to black. + else if (lamp.color != lamp.black && xy == lamp.black) { + put(getStateRoute(lamp.id), QString("{\"on\": false}")); + } + } + // Remember last color. + lamp.color = xy; + // Next light id. + idx++; + } + timer.start(); + return 0; +} + +int LedDevicePhilipsHue::switchOff() { + timer.stop(); + // If light states have been saved before, ... + if (areStatesSaved()) { + // ... restore them. + restoreStates(); + } + return 0; +} + +void LedDevicePhilipsHue::put(QString route, QString content) { + QString url = QString("/api/%1/%2").arg(username).arg(route); + QHttpRequestHeader header("PUT", url); + header.setValue("Host", host); + header.setValue("Accept-Encoding", "identity"); + header.setValue("Connection", "keep-alive"); + header.setValue("Content-Length", QString("%1").arg(content.size())); + QEventLoop loop; + // Connect requestFinished signal to quit slot of the loop. + loop.connect(http, SIGNAL(requestFinished(int, bool)), SLOT(quit())); + // Perfrom request + http->request(header, content.toAscii()); + // Go into the loop until the request is finished. + loop.exec(); +} + +QByteArray LedDevicePhilipsHue::get(QString route) { + QString url = QString("/api/%1/%2").arg(username).arg(route); + // Event loop to block until request finished. + QEventLoop loop; + // Connect requestFinished signal to quit slot of the loop. + loop.connect(http, SIGNAL(requestFinished(int, bool)), SLOT(quit())); + // Perfrom request + http->get(url); + // Go into the loop until the request is finished. + loop.exec(); + // Read all data of the response. + return http->readAll(); +} + +QString LedDevicePhilipsHue::getStateRoute(unsigned int lightId) { + return QString("lights/%1/state").arg(lightId); +} + +QString LedDevicePhilipsHue::getRoute(unsigned int lightId) { + return QString("lights/%1").arg(lightId); +} + +void LedDevicePhilipsHue::saveStates(unsigned int nLights) { + // Clear saved lamps. + lamps.clear(); + // Use json parser to parse reponse. + Json::Reader reader; + Json::FastWriter writer; + // Iterate lights. + for (unsigned int i = 0; i < nLights; i++) { + // Read the response. + QByteArray response = get(getRoute(i + 1)); + // Parse JSON. + Json::Value json; + if (!reader.parse(QString(response).toStdString(), json)) { + // Error occured, break loop. + break; + } + // Get state object values which are subject to change. + Json::Value state(Json::objectValue); + state["on"] = json["state"]["on"]; + if (json["state"]["on"] == true) { + state["xy"] = json["state"]["xy"]; + state["bri"] = json["state"]["bri"]; + } + // Determine the model id. + QString modelId = QString(writer.write(json["modelid"]).c_str()).trimmed().replace("\"", ""); + QString originalState = QString(writer.write(state).c_str()).trimmed(); + // Save state object. + lamps.push_back(PhilipsHueLamp(i + 1, originalState, modelId)); + } +} + +void LedDevicePhilipsHue::switchOn(unsigned int nLights) { + for (PhilipsHueLamp lamp : lamps) { + put(getStateRoute(lamp.id), "{\"on\": true}"); + } +} + +void LedDevicePhilipsHue::restoreStates() { + for (PhilipsHueLamp lamp : lamps) { + put(getStateRoute(lamp.id), lamp.originalState); + } + // Clear saved light states. + lamps.clear(); +} + +bool LedDevicePhilipsHue::areStatesSaved() { + return !lamps.empty(); +} diff --git a/libsrc/leddevice/LedDevicePhilipsHue.h b/libsrc/leddevice/LedDevicePhilipsHue.h new file mode 100755 index 00000000..085defb0 --- /dev/null +++ b/libsrc/leddevice/LedDevicePhilipsHue.h @@ -0,0 +1,218 @@ +#pragma once + +// STL includes +#include + +// Qt includes +#include +#include +#include +#include + +// Leddevice includes +#include + +/** + * A color point in the color space of the hue system. + */ +struct CiColor { + /// X component. + float x; + /// Y component. + float y; + /// The brightness. + float bri; +}; + +bool operator==(CiColor p1, CiColor p2); +bool operator!=(CiColor p1, CiColor p2); + +/** + * Color triangle to define an available color space for the hue lamps. + */ +struct CiColorTriangle { + CiColor red, green, blue; +}; + +/** + * Simple class to hold the id, the latest color, the color space and the original state. + */ +class PhilipsHueLamp { +public: + unsigned int id; + CiColor black; + CiColor color; + CiColorTriangle colorSpace; + QString originalState; + + /// + /// Constructs the lamp. + /// + /// @param id the light id + /// + /// @param originalState the json string of the original state + /// + /// @param modelId the model id of the hue lamp which is used to determine the color space + /// + PhilipsHueLamp(unsigned int id, QString originalState, QString modelId); + + /// + /// Converts an RGB color to the Hue xy color space and brightness. + /// https://github.com/PhilipsHue/PhilipsHueSDK-iOS-OSX/blob/master/ApplicationDesignNotes/RGB%20to%20xy%20Color%20conversion.md + /// + /// @param red the red component in [0, 1] + /// + /// @param green the green component in [0, 1] + /// + /// @param blue the blue component in [0, 1] + /// + /// @return color point + /// + CiColor rgbToCiColor(float red, float green, float blue); + + /// + /// @param p the color point to check + /// + /// @return true if the color point is covered by the lamp color space + /// + bool isPointInLampsReach(CiColor p); + + /// + /// @param p1 point one + /// + /// @param p2 point tow + /// + /// @return the cross product between p1 and p2 + /// + float crossProduct(CiColor p1, CiColor p2); + + /// + /// @param a reference point one + /// + /// @param b reference point two + /// + /// @param p the point to which the closest point is to be found + /// + /// @return the closest color point of p to a and b + /// + CiColor getClosestPointToPoint(CiColor a, CiColor b, CiColor p); + + /// + /// @param p1 point one + /// + /// @param p2 point tow + /// + /// @return the distance between the two points + /// + float getDistanceBetweenTwoPoints(CiColor p1, CiColor p2); +}; + +/** + * Implementation for the Philips Hue system. + * + * To use set the device to "philipshue". + * Uses the official Philips Hue API (http://developers.meethue.com). + * Framegrabber must be limited to 10 Hz / numer of lights to avoid rate limitation by the hue bridge. + * Create a new API user name "newdeveloper" on the bridge (http://developers.meethue.com/gettingstarted.html) + * + * @author ntim (github), bimsarck (github) + */ +class LedDevicePhilipsHue: public QObject, public LedDevice { +Q_OBJECT +public: + /// + /// Constructs the device. + /// + /// @param output the ip address of the bridge + /// + /// @param switchOffOnBlack kill lights for black + /// + LedDevicePhilipsHue(const std::string& output, bool switchOffOnBlack); + + /// + /// Destructor of this device + /// + virtual ~LedDevicePhilipsHue(); + + /// + /// Sends the given led-color values via put request to the hue system + /// + /// @param ledValues The color-value per led + /// + /// @return Zero on success else negative + /// + virtual int write(const std::vector & ledValues); + + /// Restores the original state of the leds. + virtual int switchOff(); + +private slots: + /// Restores the status of all lights. + void restoreStates(); + +private: + /// Array to save the lamps. + std::vector lamps; + /// Ip address of the bridge + QString host; + /// User name for the API ("newdeveloper") + QString username; + /// Qhttp object for sending requests. + QHttp* http; + /// Use timer to reset lights when we got into "GRABBINGMODE_OFF". + QTimer timer; + /// + bool switchOffOnBlack; + + /// + /// Sends a HTTP GET request (blocking). + /// + /// @param route the URI of the request + /// + /// @return response of the request + /// + QByteArray get(QString route); + + /// + /// Sends a HTTP PUT request (non-blocking). + /// + /// @param route the URI of the request + /// + /// @param content content of the request + /// + void put(QString route, QString content); + + /// + /// @param lightId the id of the hue light (starting from 1) + /// + /// @return the URI of the light state for PUT requests. + /// + QString getStateRoute(unsigned int lightId); + + /// + /// @param lightId the id of the hue light (starting from 1) + /// + /// @return the URI of the light for GET requests. + /// + QString getRoute(unsigned int lightId); + + /// + /// Queries the status of all lights and saves it. + /// + /// @param nLights the number of lights + /// + void saveStates(unsigned int nLights); + + /// + /// Switches the leds on. + /// + /// @param nLights the number of lights + /// + void switchOn(unsigned int nLights); + + /// + /// @return true if light states have been saved. + /// + bool areStatesSaved(); + +}; diff --git a/libsrc/leddevice/LedDeviceTpm2.cpp b/libsrc/leddevice/LedDeviceTpm2.cpp new file mode 100644 index 00000000..a0fcf869 --- /dev/null +++ b/libsrc/leddevice/LedDeviceTpm2.cpp @@ -0,0 +1,42 @@ + +// STL includes +#include +#include +#include + +// Linux includes +#include +#include + +// hyperion local includes +#include "LedDeviceTpm2.h" + +LedDeviceTpm2::LedDeviceTpm2(const std::string& outputDevice, const unsigned baudrate) : + LedRs232Device(outputDevice, baudrate), + _ledBuffer(0) +{ + // empty +} + +int LedDeviceTpm2::write(const std::vector &ledValues) +{ + if (_ledBuffer.size() == 0) + { + _ledBuffer.resize(5 + 3*ledValues.size()); + _ledBuffer[0] = 0xC9; // block-start byte + _ledBuffer[1] = 0xDA; // DATA frame + _ledBuffer[2] = ((3 * ledValues.size()) >> 8) & 0xFF; // frame size high byte + _ledBuffer[3] = (3 * ledValues.size()) & 0xFF; // frame size low byte + _ledBuffer.back() = 0x36; // block-end byte + } + + // write data + memcpy(4 + _ledBuffer.data(), ledValues.data(), ledValues.size() * 3); + return writeBytes(_ledBuffer.size(), _ledBuffer.data()); +} + +int LedDeviceTpm2::switchOff() +{ + memset(4 + _ledBuffer.data(), 0, _ledBuffer.size() - 5); + return writeBytes(_ledBuffer.size(), _ledBuffer.data()); +} diff --git a/libsrc/leddevice/LedDeviceTpm2.h b/libsrc/leddevice/LedDeviceTpm2.h new file mode 100644 index 00000000..f7ca8680 --- /dev/null +++ b/libsrc/leddevice/LedDeviceTpm2.h @@ -0,0 +1,38 @@ +#pragma once + +// STL includes +#include + +// hyperion incluse +#include "LedRs232Device.h" + +/// +/// Implementation of the LedDevice interface for writing to serial device using tpm2 protocol. +/// +class LedDeviceTpm2 : public LedRs232Device +{ +public: + /// + /// Constructs the LedDevice for attached serial device using supporting tpm2 protocol + /// All LEDs in the stripe are handled as one frame + /// + /// @param outputDevice The name of the output device (eg '/dev/ttyAMA0') + /// @param baudrate The used baudrate for writing to the output device + /// + LedDeviceTpm2(const std::string& outputDevice, const unsigned baudrate); + + /// + /// Writes the led color values to the led-device + /// + /// @param ledValues The color-value per led + /// @return Zero on succes else negative + /// + virtual int write(const std::vector &ledValues); + + /// Switch the leds off + virtual int switchOff(); + +private: + /// The buffer containing the packed RGB values + std::vector _ledBuffer; +}; diff --git a/libsrc/leddevice/LedDeviceWS2812b.cpp b/libsrc/leddevice/LedDeviceWS2812b.cpp new file mode 100644 index 00000000..dc6bbe70 --- /dev/null +++ b/libsrc/leddevice/LedDeviceWS2812b.cpp @@ -0,0 +1,883 @@ +// For license and other informations see LedDeviceWS2812b.h +// To activate: use led device "ws2812s" in the hyperion configuration + +// STL includes +#include +#include +#include +#include + +// Linux includes +#include +#include +#include +#include +//#include +//#include + +#ifdef BENCHMARK + #include +#endif + +// hyperion local includes +#include "LedDeviceWS2812b.h" + +// ==== Defines and Vars ==== + +// Base addresses for GPIO, PWM, PWM clock, and DMA controllers (physical, not bus!) +// These will be "memory mapped" into virtual RAM so that they can be written and read directly. +// ------------------------------------------------------------------------------------------------- +#define DMA_BASE 0x20007000 +#define DMA_LEN 0x24 +#define PWM_BASE 0x2020C000 +#define PWM_LEN 0x28 +#define CLK_BASE 0x20101000 +#define CLK_LEN 0xA8 +#define GPIO_BASE 0x20200000 +#define GPIO_LEN 0xB4 + +// GPIO +// ------------------------------------------------------------------------------------------------- +#define GPFSEL0 0x20200000 // GPIO function select, pins 0-9 (bits 30-31 reserved) +#define GPFSEL1 0x20200004 // Pins 10-19 +#define GPFSEL2 0x20200008 // Pins 20-29 +#define GPFSEL3 0x2020000C // Pins 30-39 +#define GPFSEL4 0x20200010 // Pins 40-49 +#define GPFSEL5 0x20200014 // Pins 50-53 +#define GPSET0 0x2020001C // Set (turn on) pin +#define GPCLR0 0x20200028 // Clear (turn off) pin +#define GPPUD 0x20200094 // Internal pullup/pulldown resistor control +#define GPPUDCLK0 0x20200098 // PUD clock for pins 0-31 +#define GPPUDCLK1 0x2020009C // PUD clock for pins 32-53 + +// Memory offsets for the PWM clock register, which is undocumented! Please fix that, Broadcom! +// ------------------------------------------------------------------------------------------------- +#define PWM_CLK_CNTL 40 // Control (on/off) +#define PWM_CLK_DIV 41 // Divisor (bits 11:0 are *quantized* floating part, 31:12 integer part) + +// PWM Register Addresses (page 141) +// These are divided by 4 because the register offsets in the guide are in bytes (8 bits) but +// the pointers we use in this program are in words (32 bits). Buss' original defines are in +// word offsets, e.g. PWM_RNG1 was 4 and PWM_DAT1 was 5. This is functionally the same, but it +// matches the numbers supplied in the guide. +// ------------------------------------------------------------------------------------------------- +#define PWM_CTL 0x00 // Control Register +#define PWM_STA (0x04 / 4) // Status Register +#define PWM_DMAC (0x08 / 4) // DMA Control Register +#define PWM_RNG1 (0x10 / 4) // Channel 1 Range +#define PWM_DAT1 (0x14 / 4) // Channel 1 Data +#define PWM_FIF1 (0x18 / 4) // FIFO (for both channels - bytes are interleaved if both active) +#define PWM_RNG2 (0x20 / 4) // Channel 2 Range +#define PWM_DAT2 (0x24 / 4) // Channel 2 Data + +// PWM_CTL register bit offsets +// Note: Don't use MSEN1/2 for this purpose. It will screw things up. +// ------------------------------------------------------------------------------------------------- +#define PWM_CTL_MSEN2 15 // Channel 2 - 0: Use PWM algorithm. 1: Use M/S (serial) algorithm. +#define PWM_CTL_USEF2 13 // Channel 2 - 0: Use PWM_DAT2. 1: Use FIFO. +#define PWM_CTL_POLA2 12 // Channel 2 - Invert output polarity (if set, 0=high and 1=low) +#define PWM_CTL_SBIT2 11 // Channel 2 - Silence bit (default line state when not transmitting) +#define PWM_CTL_RPTL2 10 // Channel 2 - Repeat last data in FIFO +#define PWM_CTL_MODE2 9 // Channel 2 - Mode. 0=PWM, 1=Serializer +#define PWM_CTL_PWEN2 8 // Channel 2 - Enable PWM +#define PWM_CTL_CLRF1 6 // Clear FIFO +#define PWM_CTL_MSEN1 7 // Channel 1 - 0: Use PWM algorithm. 1: Use M/S (serial) algorithm. +#define PWM_CTL_USEF1 5 // Channel 1 - 0: Use PWM_DAT1. 1: Use FIFO. +#define PWM_CTL_POLA1 4 // Channel 1 - Invert output polarity (if set, 0=high and 1=low) +#define PWM_CTL_SBIT1 3 // Channel 1 - Silence bit (default line state when not transmitting) +#define PWM_CTL_RPTL1 2 // Channel 1 - Repeat last data in FIFO +#define PWM_CTL_MODE1 1 // Channel 1 - Mode. 0=PWM, 1=Serializer +#define PWM_CTL_PWEN1 0 // Channel 1 - Enable PWM + +// PWM_STA register bit offsets +// ------------------------------------------------------------------------------------------------- +#define PWM_STA_STA4 12 // Channel 4 State +#define PWM_STA_STA3 11 // Channel 3 State +#define PWM_STA_STA2 10 // Channel 2 State +#define PWM_STA_STA1 9 // Channel 1 State +#define PWM_STA_BERR 8 // Bus Error +#define PWM_STA_GAPO4 7 // Gap Occurred on Channel 4 +#define PWM_STA_GAPO3 6 // Gap Occurred on Channel 3 +#define PWM_STA_GAPO2 5 // Gap Occurred on Channel 2 +#define PWM_STA_GAPO1 4 // Gap Occurred on Channel 1 +#define PWM_STA_RERR1 3 // FIFO Read Error +#define PWM_STA_WERR1 2 // FIFO Write Error +#define PWM_STA_EMPT1 1 // FIFO Empty +#define PWM_STA_FULL1 0 // FIFO Full + +// PWM_DMAC bit offsets +// ------------------------------------------------------------------------------------------------- +#define PWM_DMAC_ENAB 31 // 0: DMA Disabled. 1: DMA Enabled. +#define PWM_DMAC_PANIC 8 // Bits 15:8. Threshold for PANIC signal. Default 7. +#define PWM_DMAC_DREQ 0 // Bits 7:0. Threshold for DREQ signal. Default 7. + +// PWM_RNG1, PWM_RNG2 +// -------------------------------------------------------------------------------------------------- +// Defines the transmission range. In PWM mode, evenly spaced pulses are sent within a period +// of length defined in these registers. In serial mode, serialized data is sent within the +// same period. The value is normally 32. If less, data will be truncated. If more, data will +// be padded with zeros. + +// DAT1, DAT2 +// -------------------------------------------------------------------------------------------------- +// NOTE: These registers are not useful for our purposes - we will use the FIFO instead! +// Stores 32 bits of data to be sent when USEF1/USEF2 is 0. In PWM mode, defines how many +// pulses will be sent within the period specified in PWM_RNG1/PWM_RNG2. In serializer mode, +// defines a 32-bit word to be transmitted. + +// FIF1 +// -------------------------------------------------------------------------------------------------- +// 32-bit-wide register used to "stuff" the FIFO, which has 16 32-bit words. (So, if you write +// it 16 times, it will fill the FIFO.) +// See also: PWM_STA_EMPT1 (FIFO empty) +// PWM_STA_FULL1 (FIFO full) +// PWM_CTL_CLRF1 (Clear FIFO) + +// DMA +// -------------------------------------------------------------------------------------------------- +// DMA registers (divided by four to convert form word to byte offsets, as with the PWM registers) +#define DMA_CS (0x00 / 4) // Control & Status register +#define DMA_CONBLK_AD (0x04 / 4) // Address of Control Block (must be 256-BYTE ALIGNED!!!) +#define DMA_TI (0x08 / 4) // Transfer Information (populated from CB) +#define DMA_SOURCE_AD (0x0C / 4) // Source address, populated from CB. Physical address. +#define DMA_DEST_AD (0x10 / 4) // Destination address, populated from CB. Bus address. +#define DMA_TXFR_LEN (0x14 / 4) // Transfer length, populated from CB +#define DMA_STRIDE (0x18 / 4) // Stride, populated from CB +#define DMA_NEXTCONBK (0x1C / 4) // Next control block address, populated from CB +#define DMA_DEBUG (0x20 / 4) // Debug settings + +// DMA Control & Status register bit offsets +#define DMA_CS_RESET 31 // Reset the controller for this channel +#define DMA_CS_ABORT 30 // Set to abort transfer +#define DMA_CS_DISDEBUG 29 // Disable debug pause signal +#define DMA_CS_WAIT_FOR 28 // Wait for outstanding writes +#define DMA_CS_PANIC_PRI 20 // Panic priority (bits 23:20), default 7 +#define DMA_CS_PRIORITY 16 // AXI priority level (bits 19:16), default 7 +#define DMA_CS_ERROR 8 // Set when there's been an error +#define DMA_CS_WAITING_FOR 6 // Set when the channel's waiting for a write to be accepted +#define DMA_CS_DREQ_STOPS_DMA 5 // Set when the DMA is paused because DREQ is inactive +#define DMA_CS_PAUSED 4 // Set when the DMA is paused (active bit cleared, etc.) +#define DMA_CS_DREQ 3 // Set when DREQ line is high +#define DMA_CS_INT 2 // If INTEN is set, this will be set on CB transfer end +#define DMA_CS_END 1 // Set when the current control block is finished +#define DMA_CS_ACTIVE 0 // Enable DMA (CB_ADDR must not be 0) +// Default CS word +#define DMA_CS_CONFIGWORD (8 << DMA_CS_PANIC_PRI) | \ + (8 << DMA_CS_PRIORITY) | \ + (1 << DMA_CS_WAIT_FOR) + +// DREQ lines (page 61, most DREQs omitted) +#define DMA_DREQ_ALWAYS 0 +#define DMA_DREQ_PCM_TX 2 +#define DMA_DREQ_PCM_RX 3 +#define DMA_DREQ_PWM 5 +#define DMA_DREQ_SPI_TX 6 +#define DMA_DREQ_SPI_RX 7 +#define DMA_DREQ_BSC_TX 8 +#define DMA_DREQ_BSC_RX 9 + +// DMA Transfer Information register bit offsets +// We don't write DMA_TI directly. It's populated from the TI field in a control block. +#define DMA_TI_NO_WIDE_BURSTS 26 // Don't do wide writes in 2-beat bursts +#define DMA_TI_WAITS 21 // Wait this many cycles after end of each read/write +#define DMA_TI_PERMAP 16 // Peripheral # whose ready signal controls xfer rate (pwm=5) +#define DMA_TI_BURST_LENGTH 12 // Length of burst in words (bits 15:12) +#define DMA_TI_SRC_IGNORE 11 // Don't perform source reads (for fast cache fill) +#define DMA_TI_SRC_DREQ 10 // Peripheral in PERMAP gates source reads +#define DMA_TI_SRC_WIDTH 9 // Source transfer width - 0=32 bits, 1=128 bits +#define DMA_TI_SRC_INC 8 // Source address += SRC_WITH after each read +#define DMA_TI_DEST_IGNORE 7 // Don't perform destination writes +#define DMA_TI_DEST_DREQ 6 // Peripheral in PERMAP gates destination writes +#define DMA_TI_DEST_WIDTH 5 // Destination transfer width - 0=32 bits, 1=128 bits +#define DMA_TI_DEST_INC 4 // Dest address += DEST_WIDTH after each read +#define DMA_TI_WAIT_RESP 3 // Wait for write response +#define DMA_TI_TDMODE 1 // 2D striding mode +#define DMA_TI_INTEN 0 // Interrupt enable +// Default TI word +#define DMA_TI_CONFIGWORD (1 << DMA_TI_NO_WIDE_BURSTS) | \ + (1 << DMA_TI_SRC_INC) | \ + (1 << DMA_TI_DEST_DREQ) | \ + (1 << DMA_TI_WAIT_RESP) | \ + (1 << DMA_TI_INTEN) | \ + (DMA_DREQ_PWM << DMA_TI_PERMAP) + +// DMA Debug register bit offsets +#define DMA_DEBUG_LITE 28 // Whether the controller is "Lite" +#define DMA_DEBUG_VERSION 25 // DMA Version (bits 27:25) +#define DMA_DEBUG_DMA_STATE 16 // DMA State (bits 24:16) +#define DMA_DEBUG_DMA_ID 8 // DMA controller's AXI bus ID (bits 15:8) +#define DMA_DEBUG_OUTSTANDING_WRITES 4 // Outstanding writes (bits 7:4) +#define DMA_DEBUG_READ_ERROR 2 // Slave read response error (clear by setting) +#define DMA_DEBUG_FIFO_ERROR 1 // Operational read FIFO error (clear by setting) +#define DMA_DEBUG_READ_LAST_NOT_SET 0 // AXI bus read last signal not set (clear by setting) + + + +#define PAGE_SIZE 4096 // Size of a RAM page to be allocated +#define PAGE_SHIFT 12 // This is used for address translation +#define NUM_PAGES ((sizeof(struct control_data_s) + PAGE_SIZE - 1) >> PAGE_SHIFT) + +#define SETBIT(word, bit) word |= 1< &ledValues) +{ +#ifdef BENCHMARK + timespec timeStart; + timespec timeEnd; + clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &timeStart); +#endif + + mLedCount = ledValues.size(); + + // Read data from LEDBuffer[], translate it into wire format, and write to PWMWaveform + unsigned int colorBits = 0; // Holds the GRB color before conversion to wire bit pattern + unsigned int wireBit = 1; // Holds the current bit we will set in PWMWaveform, start with 1 and skip the other two for speed + + // Copy PWM waveform to DMA's data buffer + //printf("Copying %d words to DMA data buffer\n", NUM_DATA_WORDS); + struct control_data_s *ctl = (struct control_data_s *)virtbase; + dma_cb_t *cbp = ctl->cb; + + // 72 bits per pixel / 32 bits per word = 2.25 words per pixel + // Add 1 to make sure the PWM FIFO gets the message: "we're sending zeroes" + // Times 4 because DMA works in bytes, not words + cbp->length = ((mLedCount * 2.25) + 1) * 4; + if(cbp->length > NUM_DATA_WORDS * 4) + { + cbp->length = NUM_DATA_WORDS * 4; + mLedCount = (NUM_DATA_WORDS - 1) / 2.25; + } + +#ifdef WS2812_ASM_OPTI + unsigned int startbitPattern = 0x40000000; // = 0100 0000 0000 0000 0000 0000 0000 0000 pattern +#endif + + + for(size_t i=0; i=0; j--) { +#ifdef WS2812_ASM_OPTI + // Fetch word the bit is in + unsigned int wordOffset = (int)(wireBit / 32); + wireBit +=3; + + if (colorBits & (1 << j)) { + PWMWaveform[wordOffset] |= startbitPattern; + } else { + PWMWaveform[wordOffset] = arm_Bit_Clear_imm(PWMWaveform[wordOffset], startbitPattern); + } + + startbitPattern = arm_ror_imm(startbitPattern, 3); +#else + unsigned char colorBit = (colorBits & (1 << j)) ? 1 : 0; // Holds current bit out of colorBits to be processed + setPWMBit(wireBit, colorBit); + wireBit +=3; +#endif + /* old code for better understanding + switch(colorBit) { + case 1: + //wireBits = 0b110; // High, High, Low + setPWMBit(wireBit++, 1); + setPWMBit(wireBit++, 1); + setPWMBit(wireBit++, 0); + break; + case 0: + //wireBits = 0b100; // High, Low, Low + setPWMBit(wireBit++, 1); + setPWMBit(wireBit++, 0); + setPWMBit(wireBit++, 0); + break; + }*/ + } + } + +#ifdef WS2812_ASM_OPTI + // calculate the bits manually since it is not needed with asm + //wireBit += mLedCount * 24 *3; + //printf(" %d\n", wireBit); +#endif + //remove one to undo optimization + wireBit --; + +#ifdef WS2812_ASM_OPTI + int rest = 32 - wireBit % 32; // 64: 32 - used Bits + startbitPattern = (1 << (rest-1)); // set new bitpattern to start at the benigining of one bit (3 bit in wave form) + rest += 32; // add one int extra for pwm + +// printBinary(startbitPattern, 32); +// printf(" startbit\n"); + + unsigned int oldwireBitValue = wireBit; + unsigned int oldbitPattern = startbitPattern; + + // zero rest of the 4 bytes / int so that output is 0 (no data is send) + for (int i = 0; i < rest; i += 3) + { + unsigned int wordOffset = (int)(wireBit / 32); + wireBit += 3; + PWMWaveform[wordOffset] = arm_Bit_Clear_imm(PWMWaveform[wordOffset], startbitPattern); + startbitPattern = arm_ror_imm(startbitPattern, 3); + } + +#else + // fill up the bytes + int rest = 32 - wireBit % 32 + 32; // 64: 32 - used Bits + 32 (one int extra for pwm) + unsigned int oldwireBitValue = wireBit; + + // zero rest of the 4 bytes / int so that output is 0 (no data is send) + for (int i = 0; i < rest; i += 3) + { + setPWMBit(wireBit, 0); + wireBit += 3; + } +#endif + + memcpy ( ctl->sample, PWMWaveform, cbp->length ); + + // Enable DMA and PWM engines, which should now send the data + startTransfer(); + + // restore bit pattern + wireBit = oldwireBitValue; + +#ifdef WS2812_ASM_OPTI + startbitPattern = oldbitPattern; + for (int i = 0; i < rest; i += 3) + { + unsigned int wordOffset = (int)(wireBit / 32); + wireBit += 3; + PWMWaveform[wordOffset] |= startbitPattern; + startbitPattern = arm_ror_imm(startbitPattern, 3); + } +#else + for (int i = 0; i < rest; i += 3) + { + setPWMBit(wireBit, 1); + wireBit += 3; + } +#endif + +#ifdef BENCHMARK + clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &timeEnd); + timespec result; + + result.tv_sec = timeEnd.tv_sec - timeStart.tv_sec; + result.tv_nsec = timeEnd.tv_nsec - timeStart.tv_nsec; + if (result.tv_nsec < 0) + { + result.tv_nsec = 1e9 - result.tv_nsec; + result.tv_sec -= 1; + } + runCount ++; + combinedNseconds += result.tv_nsec; + shortestNseconds = result.tv_nsec < shortestNseconds ? result.tv_nsec : shortestNseconds; +#endif + return 0; +} + +int LedDeviceWS2812b::switchOff() +{ + return write(std::vector(mLedCount, ColorRgb{0,0,0})); +} + +LedDeviceWS2812b::~LedDeviceWS2812b() +{ + // Exit cleanly, freeing memory and stopping the DMA & PWM engines + terminate(0); +#ifdef BENCHMARK + printf("WS2812b Benchmark results: Runs %d - Avarage %lu (n) - Minimum %ld (n)\n", + runCount, (runCount > 0 ? combinedNseconds / runCount : 0), shortestNseconds); +#endif +} + + +// ================================================================================================= +// ________ .__ +// / _____/ ____ ____ ________________ | | +// / \ ____/ __ \ / \_/ __ \_ __ \__ \ | | +// \ \_\ \ ___/| | \ ___/| | \// __ \| |__ +// \______ /\___ >___| /\___ >__| (____ /____/ +// \/ \/ \/ \/ \/ +// ================================================================================================= + +// Convenience functions +// -------------------------------------------------------------------------------------------------- +// Print some bits of a binary number (2nd arg is how many bits) +void LedDeviceWS2812b::printBinary(unsigned int i, unsigned int bits) +{ + int x; + for(x=bits-1; x>=0; x--) + { + printf("%d", (i & (1 << x)) ? 1 : 0); + if(x % 16 == 0 && x > 0) + { + printf(" "); + } + else if(x % 4 == 0 && x > 0) + { + printf(":"); + } + } +} + +// Reverse the bits in a word +unsigned int reverseWord(unsigned int word) +{ + unsigned int output = 0; + //unsigned char bit; + int i; + for(i=0; i<32; i++) + { + output |= word & (1 << i) ? 1 : 0; + if(i<31) + { + output <<= 1; + } + } + return output; +} + +// Shutdown functions +// -------------------------------------------------------------------------------------------------- +void LedDeviceWS2812b::terminate(int dummy) { + // Shut down the DMA controller + if(dma_reg) + { + CLRBIT(dma_reg[DMA_CS], DMA_CS_ACTIVE); + usleep(100); + SETBIT(dma_reg[DMA_CS], DMA_CS_RESET); + usleep(100); + } + + // Shut down PWM + if(pwm_reg) + { + CLRBIT(pwm_reg[PWM_CTL], PWM_CTL_PWEN1); + usleep(100); + pwm_reg[PWM_CTL] = (1 << PWM_CTL_CLRF1); + } + + // Free the allocated memory + if(page_map != 0) + { + free(page_map); + } +} + +void LedDeviceWS2812b::fatal(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + terminate(0); +} + + +// Memory management +// -------------------------------------------------------------------------------------------------- +// Translate from virtual address to physical +unsigned int LedDeviceWS2812b::mem_virt_to_phys(void *virt) +{ + unsigned int offset = (uint8_t *)virt - virtbase; + return page_map[offset >> PAGE_SHIFT].physaddr + (offset % PAGE_SIZE); +} + +// Translate from physical address to virtual +unsigned int LedDeviceWS2812b::mem_phys_to_virt(uint32_t phys) +{ + unsigned int pg_offset = phys & (PAGE_SIZE - 1); + unsigned int pg_addr = phys - pg_offset; + + for (unsigned int i = 0; i < NUM_PAGES; i++) + { + if (page_map[i].physaddr == pg_addr) + { + return (uint32_t)virtbase + i * PAGE_SIZE + pg_offset; + } + } + fatal("Failed to reverse map phys addr %08x\n", phys); + + return 0; +} + +// Map a peripheral's IO memory into our virtual memory, so we can read/write it directly +void * LedDeviceWS2812b::map_peripheral(uint32_t base, uint32_t len) +{ + int fd = open("/dev/mem", O_RDWR); + void * vaddr; + + if (fd < 0) + { + fatal("Failed to open /dev/mem: %m\n"); + } + vaddr = mmap(NULL, len, PROT_READ|PROT_WRITE, MAP_SHARED, fd, base); + if (vaddr == MAP_FAILED) + { + fatal("Failed to map peripheral at 0x%08x: %m\n", base); + } + close(fd); + + return vaddr; +} + +// Zero out the PWM waveform buffer +void LedDeviceWS2812b::clearPWMBuffer() +{ + memset(PWMWaveform, 0, NUM_DATA_WORDS * 4); // Times four because memset deals in bytes. +} + +// Set an individual bit in the PWM output array, accounting for word boundaries +// The (31 - bitIdx) is so that we write the data backwards, correcting its endianness +// This means getPWMBit will return something other than what was written, so it would be nice +// if the logic that calls this function would figure it out instead. (However, that's trickier) +void LedDeviceWS2812b::setPWMBit(unsigned int bitPos, unsigned char bit) +{ + // Fetch word the bit is in + unsigned int wordOffset = (int)(bitPos / 32); + unsigned int bitIdx = bitPos - (wordOffset * 32); + + switch(bit) + { + case 1: + PWMWaveform[wordOffset] |= (1 << (31 - bitIdx)); + break; + case 0: + PWMWaveform[wordOffset] &= ~(1 << (31 - bitIdx)); + break; + } +} + +// ==== Init Hardware ==== +void LedDeviceWS2812b::initHardware() +{ + int pid; + int fd; + char pagemap_fn[64]; + + // Clear the PWM buffer + // --------------------------------------------------------------- + clearPWMBuffer(); + + // Set up peripheral access + // --------------------------------------------------------------- + dma_reg = (unsigned int *) map_peripheral(DMA_BASE, DMA_LEN); + dma_reg += 0x000; + pwm_reg = (unsigned int *) map_peripheral(PWM_BASE, PWM_LEN); + clk_reg = (unsigned int *) map_peripheral(CLK_BASE, CLK_LEN); + gpio_reg = (unsigned int *) map_peripheral(GPIO_BASE, GPIO_LEN); + + + // Set PWM alternate function for GPIO18 + // --------------------------------------------------------------- + //gpio_reg[1] &= ~(7 << 24); + //usleep(100); + //gpio_reg[1] |= (2 << 24); + //usleep(100); + SET_GPIO_ALT(18, 5); + + + // Allocate memory for the DMA control block & data to be sent + // --------------------------------------------------------------- + virtbase = (uint8_t *) mmap( + NULL, // Address + NUM_PAGES * PAGE_SIZE, // Length + PROT_READ | PROT_WRITE, // Protection + MAP_SHARED | // Shared + MAP_ANONYMOUS | // Not file-based, init contents to 0 + MAP_NORESERVE | // Don't reserve swap space + MAP_LOCKED, // Lock in RAM (don't swap) + -1, // File descriptor + 0); // Offset + + if (virtbase == MAP_FAILED) + { + fatal("Failed to mmap physical pages: %m\n"); + return; + } + + if ((unsigned long)virtbase & (PAGE_SIZE-1)) + { + fatal("Virtual address is not page aligned\n"); + return; + } + + // Allocate page map (pointers to the control block(s) and data for each CB + page_map = (page_map_t *) malloc(NUM_PAGES * sizeof(*page_map)); + if (page_map == 0) + { + fatal("Failed to malloc page_map: %m\n"); + return; + } + + // Use /proc/self/pagemap to figure out the mapping between virtual and physical addresses + pid = getpid(); + sprintf(pagemap_fn, "/proc/%d/pagemap", pid); + fd = open(pagemap_fn, O_RDONLY); + + if (fd < 0) + { + fatal("Failed to open %s: %m\n", pagemap_fn); + } + + off_t newOffset = (unsigned long)virtbase >> 9; + if (lseek(fd, newOffset, SEEK_SET) != newOffset) + { + fatal("Failed to seek on %s: %m\n", pagemap_fn); + } + + printf("Page map: %d pages\n", NUM_PAGES); + for (unsigned int i = 0; i < NUM_PAGES; i++) + { + uint64_t pfn; + page_map[i].virtaddr = virtbase + i * PAGE_SIZE; + + // Following line forces page to be allocated + // (Note: Copied directly from Hirst's code... page_map[i].virtaddr[0] was just set...?) + page_map[i].virtaddr[0] = 0; + + if (read(fd, &pfn, sizeof(pfn)) != sizeof(pfn)) { + fatal("Failed to read %s: %m\n", pagemap_fn); + } + + if (((pfn >> 55) & 0xfbf) != 0x10c) { // pagemap bits: https://www.kernel.org/doc/Documentation/vm/pagemap.txt + fatal("Page %d not present (pfn 0x%016llx)\n", i, pfn); + } + + page_map[i].physaddr = (unsigned int)pfn << PAGE_SHIFT | 0x40000000; + //printf("Page map #%2d: virtual %8p ==> physical 0x%08x [0x%016llx]\n", i, page_map[i].virtaddr, page_map[i].physaddr, pfn); + } + + + // Set up control block + // --------------------------------------------------------------- + struct control_data_s *ctl = (struct control_data_s *)virtbase; + dma_cb_t *cbp = ctl->cb; + // FIXME: Change this to use DEFINEs + unsigned int phys_pwm_fifo_addr = 0x7e20c000 + 0x18; + + // No wide bursts, source increment, dest DREQ on line 5, wait for response, enable interrupt + cbp->info = DMA_TI_CONFIGWORD; + + // Source is our allocated memory + cbp->src = mem_virt_to_phys(ctl->sample); + + // Destination is the PWM controller + cbp->dst = phys_pwm_fifo_addr; + + // 72 bits per pixel / 32 bits per word = 2.25 words per pixel + // Add 1 to make sure the PWM FIFO gets the message: "we're sending zeroes" + // Times 4 because DMA works in bytes, not words + cbp->length = ((mLedCount * 2.25) + 1) * 4; + if(cbp->length > NUM_DATA_WORDS * 4) + { + cbp->length = NUM_DATA_WORDS * 4; + } + + // We don't use striding + cbp->stride = 0; + + // These are reserved + cbp->pad[0] = 0; + cbp->pad[1] = 0; + + // Pointer to next block - 0 shuts down the DMA channel when transfer is complete + cbp->next = 0; + + // Stop any existing DMA transfers + // --------------------------------------------------------------- + dma_reg[DMA_CS] |= (1 << DMA_CS_ABORT); + usleep(100); + dma_reg[DMA_CS] = (1 << DMA_CS_RESET); + usleep(100); + + + // PWM Clock + // --------------------------------------------------------------- + // Kill the clock + // FIXME: Change this to use a DEFINE + clk_reg[PWM_CLK_CNTL] = 0x5A000000 | (1 << 5); + usleep(100); + + // Disable DMA requests + CLRBIT(pwm_reg[PWM_DMAC], PWM_DMAC_ENAB); + usleep(100); + + // The fractional part is quantized to a range of 0-1024, so multiply the decimal part by 1024. + // E.g., 0.25 * 1024 = 256. + // So, if you want a divisor of 400.5, set idiv to 400 and fdiv to 512. + unsigned int idiv = 400; + unsigned short fdiv = 0; // Should be 16 bits, but the value must be <= 1024 + clk_reg[PWM_CLK_DIV] = 0x5A000000 | (idiv << 12) | fdiv; // Set clock multiplier + usleep(100); + + // Enable the clock. Next-to-last digit means "enable clock". Last digit is 1 (oscillator), + // 4 (PLLA), 5 (PLLC), or 6 (PLLD) (according to the docs) although PLLA doesn't seem to work. + // FIXME: Change this to use a DEFINE + clk_reg[PWM_CLK_CNTL] = 0x5A000015; + usleep(100); + + + // PWM + // --------------------------------------------------------------- + // Clear any preexisting crap from the control & status register + pwm_reg[PWM_CTL] = 0; + + // Set transmission range (32 bytes, or 1 word) + // <32: Truncate. >32: Pad with SBIT1. As it happens, 32 is perfect. + pwm_reg[PWM_RNG1] = 32; + usleep(100); + + // Send DMA requests to fill the FIFO + pwm_reg[PWM_DMAC] = + (1 << PWM_DMAC_ENAB) | + (8 << PWM_DMAC_PANIC) | + (8 << PWM_DMAC_DREQ); + usleep(1000); + + // Clear the FIFO + SETBIT(pwm_reg[PWM_CTL], PWM_CTL_CLRF1); + usleep(100); + + // Don't repeat last FIFO contents if it runs dry + CLRBIT(pwm_reg[PWM_CTL], PWM_CTL_RPTL1); + usleep(100); + + // Silence (default) bit is 0 + CLRBIT(pwm_reg[PWM_CTL], PWM_CTL_SBIT1); + usleep(100); + + // Polarity = default (low = 0, high = 1) + CLRBIT(pwm_reg[PWM_CTL], PWM_CTL_POLA1); + usleep(100); + + // Enable serializer mode + SETBIT(pwm_reg[PWM_CTL], PWM_CTL_MODE1); + usleep(100); + + // Use FIFO rather than DAT1 + SETBIT(pwm_reg[PWM_CTL], PWM_CTL_USEF1); + usleep(100); + + // Disable MSEN1 + CLRBIT(pwm_reg[PWM_CTL], PWM_CTL_MSEN1); + usleep(100); + + + // DMA + // --------------------------------------------------------------- + // Raise an interrupt when transfer is complete, which will set the INT flag in the CS register + SETBIT(dma_reg[DMA_CS], DMA_CS_INT); + usleep(100); + + // Clear the END flag (by setting it - this is a "write 1 to clear", or W1C, bit) + SETBIT(dma_reg[DMA_CS], DMA_CS_END); + usleep(100); + + // Send the physical address of the control block into the DMA controller + dma_reg[DMA_CONBLK_AD] = mem_virt_to_phys(ctl->cb); + usleep(100); + + // Clear error flags, if any (these are also W1C bits) + // FIXME: Use a define instead of this + dma_reg[DMA_DEBUG] = 7; + usleep(100); +} + +// Begin the transfer +void LedDeviceWS2812b::startTransfer() +{ + // Enable DMA + dma_reg[DMA_CONBLK_AD] = mem_virt_to_phys(((struct control_data_s *) virtbase)->cb); + dma_reg[DMA_CS] = DMA_CS_CONFIGWORD | (1 << DMA_CS_ACTIVE); + usleep(100); + + // Enable PWM + SETBIT(pwm_reg[PWM_CTL], PWM_CTL_PWEN1); +} diff --git a/libsrc/leddevice/LedDeviceWS2812b.h b/libsrc/leddevice/LedDeviceWS2812b.h new file mode 100644 index 00000000..d945e20d --- /dev/null +++ b/libsrc/leddevice/LedDeviceWS2812b.h @@ -0,0 +1,218 @@ +#ifndef LEDDEVICEWS2812B_H_ +#define LEDDEVICEWS2812B_H_ + +#pragma once + + +// Set tabs to 4 spaces. + +// ================================================================================================= +// +// __ __ _________________ ______ ____________ ____________________.__ +// / \ / \/ _____/\_____ \ / __ \/_ \_____ \ \______ \______ \__| +// \ \/\/ /\_____ \ / ____/ > < | |/ ____/ | _/| ___/ | +// \ / / \/ \/ -- \| / \ | | \| | | | +// \__/\ / /_______ /\_______ \______ /|___\_______ \ |____|_ /|____| |__| +// \/ \/ \/ \/ \/ \/ +// +// WS2812 NeoPixel driver +// Based on code by Richard G. Hirst and others +// Adapted for the WS2812 by 626Pilot, April/May 2014 +// See: https://github.com/626Pilot/RaspberryPi-NeoPixel-WS2812 +// Version: https://github.com/626Pilot/RaspberryPi-NeoPixel-WS2812/blob/1d43407d9e6eba19bff24330bc09a27963b55751/ws2812-RPi.c +// Huge ASCII art section labels are from http://patorjk.com/software/taag/ +// +// LED driver adaptation by Kammerjaeger () +// mostly code removed that was not needed +// +// License: GPL +// +// You are using this at your OWN RISK. I believe this software is reasonably safe to use (aside +// from the intrinsic risk to those who are photosensitive - see below), although I can't be certain +// that it won't trash your hardware or cause property damage. +// +// Speaking of risk, WS2812 pixels are bright enough to cause eye pain and (for all I know) possibly +// retina damage when run at full strength. It's a good idea to set the brightness at 0.2 or so for +// direct viewing (whether you're looking directly at the pixels or not), or to put some diffuse +// material between you and the LEDs. +// +// PHOTOSENSITIVITY WARNING: +// Patterns of light and darkness (stationary or moving), flashing lights, patterns and backgrounds +// on screens, and the like, may cause epilleptic seizures in some people. This is a danger EVEN IF +// THE PERSON (WHICH MAY BE *YOU*) HAS NEVER KNOWINGLY HAD A PHOTOSENSITIVE EPISODE BEFORE. It's up +// to you to learn the warning signs, but symptoms may include dizziness, nausea, vision changes, +// convlusions, disorientation, involuntary movements, and eye twitching. (This list is not +// necessarily exhaustive.) +// +// NEOPIXEL BEST PRACTICES: https://learn.adafruit.com/adafruit-neopixel-uberguide/best-practices +// +// Connections: +// Positive to Raspberry Pi's 3.3v, for better separation connect only ground and data directly +// (5v can be used then without a problem, at least it worked for me, Kammerjaeger) +// Negative to Raspberry Pi's ground +// Data to GPIO18 (Pin 12) (through a resistor, which you should know from the Best +// Practices guide!) +// +// Buy WS2812-based stuff from: http://adafruit.com +// +// To activate: use led device "ws2812s" in the hyperion configuration +// (it needs to be root so it can map the peripherals' registers) +// +// ================================================================================================= + +// This is for the WS2812 LEDs. It won't work with the older WS2811s, although it could be modified +// for that without too much trouble. Preliminary driver used Frank Buss' servo driver, but I moved +// to Richard Hirst's memory mapping/access model because his code already works with DMA, and has +// what I think is a slightly cleaner way of accessing the registers: register[name] rather than +// *(register + name). + +// At the time of writing, there's a lot of confusing "PWM DMA" code revolving around simulating +// an FM signal. Usually this is done without properly initializing certain registers, which is +// OK for their purpose, but I needed to be able to transfer actual coherent data and have it wind +// up in a proper state once it was transferred. This has proven to be a somewhat painful task. +// The PWM controller likes to ignore the RPTL1 bit when the data is in a regular, repeating +// pattern. I'M NOT MAKING IT UP! It really does that. It's bizarre. There are lots of other +// strange irregularities as well, which had to be figured out through trial and error. It doesn't +// help that the BCM2835 ARM Peripherals manual contains outright errors and omissions! + +// Many examples of this kind of code have magic numbers in them. If you don't know, a magic number +// is one that either lacks an obvious structure (e.g. 0x2020C000) or purpose. Please don't use +// that stuff in any code you release! All magic numbers found in reference code have been changed +// to DEFINEs. That way, instead of seeing some inscrutable number, you see (e.g.) PWM_CTL. + +// References - BCM2835 ARM Peripherals: +// http://www.raspberrypi.org/wp-content/uploads/2012/02/BCM2835-ARM-Peripherals.pdf +// +// Raspberry Pi low-level peripherals: +// http://elinux.org/RPi_Low-level_peripherals +// +// Richard Hirst's nice, clean code: +// https://github.com/richardghirst/PiBits/blob/master/PiFmDma/PiFmDma.c +// +// PWM clock register: +// http://www.raspberrypi.org/forums/viewtopic.php?t=8467&p=124620 +// +// Simple (because it's in assembly) PWM+DMA setup: +// https://github.com/mikedurso/rpi-projects/blob/master/asm-nyancat/rpi-nyancat.s +// +// Adafruit's NeoPixel driver: +// https://github.com/adafruit/Adafruit_NeoPixel/blob/master/Adafruit_NeoPixel.cpp + +// Hyperion includes +#include + +//#define BENCHMARK +#define WS2812_ASM_OPTI + +// The page map contains pointers to memory that we will allocate below. It uses two pointers +// per address. This is because the software (this program) deals only in virtual addresses, +// whereas the DMA controller can only access RAM via physical address. (If that's not confusing +// enough, it writes to peripherals by their bus addresses.) +struct page_map_t +{ + uint8_t *virtaddr; + uint32_t physaddr; +}; + +// Control Block (CB) - this tells the DMA controller what to do. +struct dma_cb_t +{ + unsigned info; // Transfer Information (TI) + unsigned src; // Source address (physical) + unsigned dst; // Destination address (bus) + unsigned length; // Length in bytes (not words!) + unsigned stride; // We don't care about this + unsigned next; // Pointer to next control block + unsigned pad[2]; // These are "reserved" (unused) +}; + +/// +/// Implementation of the LedDevice interface for writing to Ws2801 led device. +/// +class LedDeviceWS2812b : public LedDevice +{ +public: + /// + /// Constructs the LedDevice for a string containing leds of the type WS2812 + LedDeviceWS2812b(); + + ~LedDeviceWS2812b(); + /// + /// Writes the led color values to the led-device + /// + /// @param ledValues The color-value per led + /// @return Zero on succes else negative + /// + virtual int write(const std::vector &ledValues); + + /// Switch the leds off + virtual int switchOff(); + +private: + + /// the number of leds (needed when switching off) + size_t mLedCount; + + page_map_t *page_map; // This will hold the page map, which we'll allocate + uint8_t *virtbase; // Pointer to some virtual memory that will be allocated + + volatile unsigned int *pwm_reg; // PWM controller register set + volatile unsigned int *clk_reg; // PWM clock manager register set + volatile unsigned int *dma_reg; // DMA controller register set + volatile unsigned int *gpio_reg; // GPIO pin controller register set + + // Contains arrays of control blocks and their related samples. + // One pixel needs 72 bits (24 bits for the color * 3 to represent them on the wire). + // 768 words = 341.3 pixels + // 1024 words = 455.1 pixels + // The highest I can make this number is 1016. Any higher, and it will start copying garbage to the + // PWM controller. I think it might be because of the virtual->physical memory mapping not being + // contiguous, so *pointer+1016 isn't "next door" to *pointer+1017 for some weird reason. + // However, that's still enough for 451.5 color instructions! If someone has more pixels than that + // to control, they can figure it out. I tried Hirst's message of having one CB per word, which + // seems like it might fix that, but I couldn't figure it out. + #define NUM_DATA_WORDS 1016 + struct control_data_s { + dma_cb_t cb[1]; + uint32_t sample[NUM_DATA_WORDS]; + }; + + //struct control_data_s *ctl; + + // PWM waveform buffer (in words), 16 32-bit words are enough to hold 170 wire bits. + // That's OK if we only transmit from the FIFO, but for DMA, we will use a much larger size. + // 1024 (4096 bytes) should be enough for over 400 elements. It can be bumped up if you need more! + unsigned int PWMWaveform[NUM_DATA_WORDS]; + + void initHardware(); + void startTransfer(); + + void clearPWMBuffer(); + void setPWMBit(unsigned int bitPos, unsigned char bit); + + unsigned int mem_phys_to_virt(uint32_t phys); + unsigned int mem_virt_to_phys(void *virt); + void terminate(int dummy); + void fatal(const char *fmt, ...); + void * map_peripheral(uint32_t base, uint32_t len); + void printBinary(unsigned int i, unsigned int bits); + +#ifdef BENCHMARK + unsigned int runCount; + long combinedNseconds; + long shortestNseconds; +#endif +}; + + + + + + + + + + + + +#endif /* LEDDEVICEWS2812B_H_ */ diff --git a/libsrc/leddevice/LedRs232Device.cpp b/libsrc/leddevice/LedRs232Device.cpp index 6bc3560b..a4b058d2 100644 --- a/libsrc/leddevice/LedRs232Device.cpp +++ b/libsrc/leddevice/LedRs232Device.cpp @@ -4,17 +4,21 @@ #include #include +// Qt includes +#include + // Serial includes #include // Local Hyperion includes #include "LedRs232Device.h" - -LedRs232Device::LedRs232Device(const std::string& outputDevice, const unsigned baudrate) : - mDeviceName(outputDevice), - mBaudRate_Hz(baudrate), - _rs232Port() +LedRs232Device::LedRs232Device(const std::string& outputDevice, const unsigned baudrate, int delayAfterConnect_ms) : + _deviceName(outputDevice), + _baudRate_Hz(baudrate), + _delayAfterConnect_ms(delayAfterConnect_ms), + _rs232Port(), + _blockedForDelay(false) { // empty } @@ -31,10 +35,17 @@ int LedRs232Device::open() { try { - std::cout << "Opening UART: " << mDeviceName << std::endl; - _rs232Port.setPort(mDeviceName); - _rs232Port.setBaudrate(mBaudRate_Hz); + std::cout << "Opening UART: " << _deviceName << std::endl; + _rs232Port.setPort(_deviceName); + _rs232Port.setBaudrate(_baudRate_Hz); _rs232Port.open(); + + if (_delayAfterConnect_ms > 0) + { + _blockedForDelay = true; + QTimer::singleShot(_delayAfterConnect_ms, this, SLOT(unblockAfterDelay())); + std::cout << "Device blocked for " << _delayAfterConnect_ms << " ms" << std::endl; + } } catch (const std::exception& e) { @@ -47,6 +58,11 @@ int LedRs232Device::open() int LedRs232Device::writeBytes(const unsigned size, const uint8_t * data) { + if (_blockedForDelay) + { + return 0; + } + if (!_rs232Port.isOpen()) { return -1; @@ -95,3 +111,9 @@ int LedRs232Device::writeBytes(const unsigned size, const uint8_t * data) return 0; } + +void LedRs232Device::unblockAfterDelay() +{ + std::cout << "Device unblocked" << std::endl; + _blockedForDelay = false; +} diff --git a/libsrc/leddevice/LedRs232Device.h b/libsrc/leddevice/LedRs232Device.h index 856a80fe..e11d0a8a 100644 --- a/libsrc/leddevice/LedRs232Device.h +++ b/libsrc/leddevice/LedRs232Device.h @@ -1,5 +1,7 @@ #pragma once +#include + // Serial includes #include @@ -9,8 +11,10 @@ /// /// The LedRs232Device implements an abstract base-class for LedDevices using a RS232-device. /// -class LedRs232Device : public LedDevice +class LedRs232Device : public QObject, public LedDevice { + Q_OBJECT + public: /// /// Constructs the LedDevice attached to a RS232-device @@ -18,7 +22,7 @@ public: /// @param[in] outputDevice The name of the output device (eg '/etc/ttyS0') /// @param[in] baudrate The used baudrate for writing to the output device /// - LedRs232Device(const std::string& outputDevice, const unsigned baudrate); + LedRs232Device(const std::string& outputDevice, const unsigned baudrate, int delayAfterConnect_ms = 0); /// /// Destructor of the LedDevice; closes the output device if it is open @@ -43,12 +47,22 @@ protected: */ int writeBytes(const unsigned size, const uint8_t *data); +private slots: + /// Unblock the device after a connection delay + void unblockAfterDelay(); + private: /// The name of the output device - const std::string mDeviceName; + const std::string _deviceName; + /// The used baudrate of the output device - const int mBaudRate_Hz; + const int _baudRate_Hz; + + /// Sleep after the connect before continuing + const int _delayAfterConnect_ms; /// The RS232 serial-device serial::Serial _rs232Port; + + bool _blockedForDelay; }; diff --git a/libsrc/protoserver/message.proto b/libsrc/protoserver/message.proto index ebc7cba2..33c995b3 100644 --- a/libsrc/protoserver/message.proto +++ b/libsrc/protoserver/message.proto @@ -17,7 +17,7 @@ message HyperionRequest { message ColorRequest { extend HyperionRequest { - required ColorRequest colorRequest = 10; + optional ColorRequest colorRequest = 10; } // priority to use when setting the color @@ -32,7 +32,7 @@ message ColorRequest { message ImageRequest { extend HyperionRequest { - required ImageRequest imageRequest = 11; + optional ImageRequest imageRequest = 11; } // priority to use when setting the image @@ -53,7 +53,7 @@ message ImageRequest { message ClearRequest { extend HyperionRequest { - required ClearRequest clearRequest = 12; + optional ClearRequest clearRequest = 12; } // priority which need to be cleared diff --git a/libsrc/utils/CMakeLists.txt b/libsrc/utils/CMakeLists.txt index 52094a61..b1f0709e 100644 --- a/libsrc/utils/CMakeLists.txt +++ b/libsrc/utils/CMakeLists.txt @@ -11,6 +11,7 @@ add_library(hyperion-utils ${CURRENT_HEADER_DIR}/ColorRgba.h ${CURRENT_SOURCE_DIR}/ColorRgba.cpp ${CURRENT_HEADER_DIR}/Image.h + ${CURRENT_HEADER_DIR}/Sleep.h ${CURRENT_HEADER_DIR}/HsvTransform.h ${CURRENT_SOURCE_DIR}/HsvTransform.cpp diff --git a/libsrc/xbmcvideochecker/XBMCVideoChecker.cpp b/libsrc/xbmcvideochecker/XBMCVideoChecker.cpp index 0c396693..a540c08b 100644 --- a/libsrc/xbmcvideochecker/XBMCVideoChecker.cpp +++ b/libsrc/xbmcvideochecker/XBMCVideoChecker.cpp @@ -17,6 +17,10 @@ // {"id":668,"jsonrpc":"2.0","method":"XBMC.GetInfoBooleans","params":{"booleans":["System.ScreenSaverActive"]}} // {"id":668,"jsonrpc":"2.0","result":{"System.ScreenSaverActive":false}} +// Request stereoscopicmode example: +// {"jsonrpc":"2.0","method":"GUI.GetProperties","params":{"properties":["stereoscopicmode"]},"id":669} +// {"id":669,"jsonrpc":"2.0","result":{"stereoscopicmode":{"label":"Nebeneinander","mode":"split_vertical"}}} + XBMCVideoChecker::XBMCVideoChecker(const std::string & address, uint16_t port, bool grabVideo, bool grabPhoto, bool grabAudio, bool grabMenu, bool grabScreensaver, bool enable3DDetection) : QObject(), _address(QString::fromStdString(address)), @@ -24,6 +28,8 @@ XBMCVideoChecker::XBMCVideoChecker(const std::string & address, uint16_t port, b _activePlayerRequest(R"({"id":666,"jsonrpc":"2.0","method":"Player.GetActivePlayers"})"), _currentPlayingItemRequest(R"({"id":667,"jsonrpc":"2.0","method":"Player.GetItem","params":{"playerid":%1,"properties":["file"]}})"), _checkScreensaverRequest(R"({"id":668,"jsonrpc":"2.0","method":"XBMC.GetInfoBooleans","params":{"booleans":["System.ScreenSaverActive"]}})"), + _getStereoscopicMode(R"({"jsonrpc":"2.0","method":"GUI.GetProperties","params":{"properties":["stereoscopicmode"]},"id":669})"), + _getXbmcVersion(R"({"jsonrpc":"2.0","method":"Application.GetProperties","params":{"properties":["version"]},"id":670})"), _socket(), _grabVideo(grabVideo), _grabPhoto(grabPhoto), @@ -33,7 +39,8 @@ XBMCVideoChecker::XBMCVideoChecker(const std::string & address, uint16_t port, b _enable3DDetection(enable3DDetection), _previousScreensaverMode(false), _previousGrabbingMode(GRABBINGMODE_INVALID), - _previousVideoMode(VIDEO_2D) + _previousVideoMode(VIDEO_2D), + _xbmcVersion(0) { // setup socket connect(&_socket, SIGNAL(readyRead()), this, SLOT(receiveReply())); @@ -116,24 +123,32 @@ void XBMCVideoChecker::receiveReply() } else if (reply.contains("\"id\":667")) { - // result of Player.GetItem - // TODO: what if the filename contains a '"'. In Json this should have been escaped - QRegExp regex("\"file\":\"((?!\").)*\""); - int pos = regex.indexIn(reply); - if (pos > 0) + if (_xbmcVersion >= 13) { - QStringRef filename = QStringRef(&reply, pos+8, regex.matchedLength()-9); - if (filename.contains("3DSBS", Qt::CaseInsensitive) || filename.contains("HSBS", Qt::CaseInsensitive)) + // check of active stereoscopicmode + _socket.write(_getStereoscopicMode.toUtf8()); + } + else + { + // result of Player.GetItem + // TODO: what if the filename contains a '"'. In Json this should have been escaped + QRegExp regex("\"file\":\"((?!\").)*\""); + int pos = regex.indexIn(reply); + if (pos > 0) { - setVideoMode(VIDEO_3DSBS); - } - else if (filename.contains("3DTAB", Qt::CaseInsensitive) || filename.contains("HTAB", Qt::CaseInsensitive)) - { - setVideoMode(VIDEO_3DTAB); - } - else - { - setVideoMode(VIDEO_2D); + QStringRef filename = QStringRef(&reply, pos+8, regex.matchedLength()-9); + if (filename.contains("3DSBS", Qt::CaseInsensitive) || filename.contains("HSBS", Qt::CaseInsensitive)) + { + setVideoMode(VIDEO_3DSBS); + } + else if (filename.contains("3DTAB", Qt::CaseInsensitive) || filename.contains("HTAB", Qt::CaseInsensitive)) + { + setVideoMode(VIDEO_3DTAB); + } + else + { + setVideoMode(VIDEO_2D); + } } } } @@ -142,6 +157,41 @@ void XBMCVideoChecker::receiveReply() // result of System.ScreenSaverActive bool active = reply.contains("\"System.ScreenSaverActive\":true"); setScreensaverMode(!_grabScreensaver && active); + + // check here xbmc version + if (_socket.state() == QTcpSocket::ConnectedState) + { + if (_xbmcVersion == 0) + { + _socket.write(_getXbmcVersion.toUtf8()); + } + } + } + else if (reply.contains("\"id\":669")) + { + QRegExp regex("\"mode\":\"(split_vertical|split_horizontal)\""); + int pos = regex.indexIn(reply); + if (pos > 0) + { + QString sMode = regex.cap(1); + if (sMode == "split_vertical") + { + setVideoMode(VIDEO_3DSBS); + } + else if (sMode == "split_horizontal") + { + setVideoMode(VIDEO_3DTAB); + } + } + } + else if (reply.contains("\"id\":670")) + { + QRegExp regex("\"major\":(\\d+)"); + int pos = regex.indexIn(reply); + if (pos > 0) + { + _xbmcVersion = regex.cap(1).toInt(); + } } } diff --git a/src/hyperion-remote/CMakeLists.txt b/src/hyperion-remote/CMakeLists.txt index 676dc7d3..c934be21 100644 --- a/src/hyperion-remote/CMakeLists.txt +++ b/src/hyperion-remote/CMakeLists.txt @@ -2,9 +2,6 @@ cmake_minimum_required(VERSION 2.8) project(hyperion-remote) -# add protocol buffers -find_package(Protobuf REQUIRED) - # find Qt4 find_package(Qt4 REQUIRED QtCore QtGui QtNetwork) diff --git a/src/hyperiond/CMakeLists.txt b/src/hyperiond/CMakeLists.txt index f843b15f..49df4bfe 100644 --- a/src/hyperiond/CMakeLists.txt +++ b/src/hyperiond/CMakeLists.txt @@ -1,20 +1,23 @@ - -add_executable(hyperiond - hyperiond.cpp) - -target_link_libraries(hyperiond - hyperion - xbmcvideochecker - effectengine - jsonserver - protoserver - boblightserver -) - -if (ENABLE_DISPMANX) - target_link_libraries(hyperiond dispmanx-grabber) -endif (ENABLE_DISPMANX) - -if (ENABLE_V4L2) - target_link_libraries(hyperiond v4l2-grabber) -endif (ENABLE_V4L2) + +add_executable(hyperiond + hyperiond.cpp) + +target_link_libraries(hyperiond + hyperion + xbmcvideochecker + effectengine + jsonserver + boblightserver +) + +if (ENABLE_DISPMANX) + target_link_libraries(hyperiond dispmanx-grabber) +endif (ENABLE_DISPMANX) + +if (ENABLE_V4L2) + target_link_libraries(hyperiond v4l2-grabber) +endif (ENABLE_V4L2) + +if (ENABLE_PROTOBUF) + target_link_libraries(hyperiond protoserver) +endif (ENABLE_PROTOBUF) diff --git a/src/hyperiond/hyperiond.cpp b/src/hyperiond/hyperiond.cpp index c6174fa9..4c21b39b 100644 --- a/src/hyperiond/hyperiond.cpp +++ b/src/hyperiond/hyperiond.cpp @@ -36,8 +36,10 @@ // JsonServer includes #include +#ifdef ENABLE_PROTOBUF // ProtoServer includes #include +#endif // BoblightServer includes #include @@ -112,14 +114,31 @@ int main(int argc, char** argv) const std::string effectName = effectConfig["effect"].asString(); const unsigned duration_ms = effectConfig["duration_ms"].asUInt(); const int priority = 0; + + hyperion.setColor(priority+1, ColorRgb::BLACK, duration_ms, false); - if (hyperion.setEffect(effectName, priority, duration_ms) == 0) + if (effectConfig.isMember("args")) { - std::cout << "Boot sequence(" << effectName << ") created and started" << std::endl; + const Json::Value effectConfigArgs = effectConfig["args"]; + if (hyperion.setEffect(effectName, effectConfigArgs, priority, duration_ms) == 0) + { + std::cout << "Boot sequence(" << effectName << ") with user-defined arguments created and started" << std::endl; + } + else + { + std::cout << "Failed to start boot sequence: " << effectName << " with user-defined arguments" << std::endl; + } } else { - std::cout << "Failed to start boot sequence: " << effectName << std::endl; + if (hyperion.setEffect(effectName, priority, duration_ms) == 0) + { + std::cout << "Boot sequence(" << effectName << ") created and started" << std::endl; + } + else + { + std::cout << "Failed to start boot sequence: " << effectName << std::endl; + } } } @@ -216,6 +235,7 @@ int main(int argc, char** argv) std::cout << "Json server created and started on port " << jsonServer->getPort() << std::endl; } +#ifdef ENABLE_PROTOBUF // Create Proto server if configuration is present ProtoServer * protoServer = nullptr; if (config.isMember("protoServer")) @@ -224,6 +244,7 @@ int main(int argc, char** argv) protoServer = new ProtoServer(&hyperion, protoServerConfig["port"].asUInt()); std::cout << "Proto server created and started on port " << protoServer->getPort() << std::endl; } +#endif // Create Boblight server if configuration is present BoblightServer * boblightServer = nullptr; @@ -247,7 +268,9 @@ int main(int argc, char** argv) #endif delete xbmcVideoChecker; delete jsonServer; +#ifdef ENABLE_PROTOBUF delete protoServer; +#endif delete boblightServer; // leave application