Merge branch 'master' into add_x11

Former-commit-id: 08655bb606c5aae04fc0011a3ec1f0c16e59e7b7
This commit is contained in:
poljvd 2014-11-18 20:35:05 +01:00
commit 01854a471e
46 changed files with 2409 additions and 158 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -80,7 +80,8 @@
{
"type" : "none",
"time_ms" : 200,
"updateFrequency" : 20.0000
"updateFrequency" : 20.0000,
"updateDelay" : 0
}
},

View File

@ -0,0 +1 @@
63b2cc4bf190a0c0c996be6db30fcf03109c9625

View File

@ -1 +1 @@
d61b685eca164580cb39eb5bc3cf65b89afad410
c2332ae026dcd9b5ededbbe2db493ae13e5208c5

View File

@ -4,6 +4,7 @@
"args" :
{
"speed" : 1.0,
"fadeFactor" : 0.7
"fadeFactor" : 0.7,
"color" : [255,0,0]
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

10
include/utils/Sleep.h Normal file
View File

@ -0,0 +1,10 @@
#pragma once
#include <QThread>
class Sleep : protected QThread {
public:
static inline void msleep(unsigned long msecs) {
QThread::msleep(msecs);
}
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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<ColorRgb> & ledColors);
/// The led device
LedDevice * _ledDevice;
@ -66,4 +74,10 @@ private:
/// The previously written led data
std::vector<ColorRgb> _previousValues;
/** The number of updates to keep in the output queue (delayed) before being output */
const unsigned _outputDelay;
/** The output queue */
std::list<std::vector<ColorRgb> > _outputQueue;
};

View File

@ -10,6 +10,8 @@
// Qt includes
#include <QResource>
#include <QDateTime>
#include <QCryptographicHash>
#include <QHostInfo>
// hyperion util includes
#include <hyperion/ImageProcessorFactory.h>
@ -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()

View File

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

View File

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

18
libsrc/leddevice/CMakeLists.txt Normal file → Executable file
View File

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

View File

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

View File

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

31
libsrc/leddevice/LedDeviceFactory.cpp Normal file → Executable file
View File

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

View File

@ -0,0 +1,291 @@
// Local-Hyperion includes
#include "LedDevicePhilipsHue.h"
// jsoncpp includes
#include <json/json.h>
// qt includes
#include <QtCore/qmath.h>
#include <QUrl>
#include <QHttpRequestHeader>
#include <QEventLoop>
#include <set>
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<QString> HUE_BULBS_MODEL_IDS = { "LCT001", "LCT002", "LCT003" };
const std::set<QString> 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<ColorRgb> & 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();
}

View File

@ -0,0 +1,218 @@
#pragma once
// STL includes
#include <string>
// Qt includes
#include <QObject>
#include <QString>
#include <QHttp>
#include <QTimer>
// Leddevice includes
#include <leddevice/LedDevice.h>
/**
* 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<ColorRgb> & 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<PhilipsHueLamp> 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();
};

View File

@ -0,0 +1,42 @@
// STL includes
#include <cstring>
#include <cstdio>
#include <iostream>
// Linux includes
#include <fcntl.h>
#include <sys/ioctl.h>
// 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<ColorRgb> &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());
}

View File

@ -0,0 +1,38 @@
#pragma once
// STL includes
#include <string>
// 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<ColorRgb> &ledValues);
/// Switch the leds off
virtual int switchOff();
private:
/// The buffer containing the packed RGB values
std::vector<uint8_t> _ledBuffer;
};

View File

@ -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 <cstring>
#include <cstdio>
#include <iostream>
#include <vector>
// Linux includes
#include <fcntl.h>
#include <stdarg.h>
#include <sys/mman.h>
#include <unistd.h>
//#include <sys/types.h>
//#include <sys/ioctl.h>
#ifdef BENCHMARK
#include <time.h>
#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<<bit
#define CLRBIT(word, bit) word &= ~(1<<bit)
#define GETBIT(word, bit) word & (1 << bit) ? 1 : 0
#define true 1
#define false 0
// GPIO
#define INP_GPIO(g) *(gpio_reg+((g)/10)) &= ~(7<<(((g)%10)*3))
#define OUT_GPIO(g) *(gpio_reg+((g)/10)) |= (1<<(((g)%10)*3))
#define SET_GPIO_ALT(g,a) *(gpio_reg+(((g)/10))) |= (((a)<=3?(a)+4:(a)==4?3:2)<<(((g)%10)*3))
#define GPIO_SET *(gpio_reg+7) // sets bits which are 1 ignores bits which are 0
#define GPIO_CLR *(gpio_reg+10) // clears bits which are 1 ignores bits which are 0
LedDeviceWS2812b::LedDeviceWS2812b() :
LedDevice(),
mLedCount(0)
#ifdef BENCHMARK
,
runCount(0),
combinedNseconds(0),
shortestNseconds(2147483647)
#endif
{
//shortestNseconds = 2147483647;
// Init PWM generator and clear LED buffer
initHardware();
//clearLEDBuffer();
// init bit pattern, it is always 1X0
unsigned int wireBit = 0;
while ((wireBit + 3) < ((NUM_DATA_WORDS) * 4 * 8))
{
setPWMBit(wireBit++, 1);
setPWMBit(wireBit++, 0); // just init it with 0
setPWMBit(wireBit++, 0);
}
printf("WS2812b init finished \n");
}
#ifdef WS2812_ASM_OPTI
// rotate register, used to move the 1 around :-)
static inline __attribute__((always_inline)) uint32_t arm_ror_imm(uint32_t v, uint32_t sh)
{
uint32_t d;
asm ("ROR %[Rd], %[Rm], %[Is]" : [Rd] "=r" (d) : [Rm] "r" (v), [Is] "i" (sh));
return d;
}
// rotate register, used to move the 1 around, add 1 to int counter on carry
static inline __attribute__((always_inline)) uint32_t arm_ror_imm_add_on_carry(uint32_t v, uint32_t sh, uint32_t inc)
{
uint32_t d;
asm ("RORS %[Rd], %[Rm], %[Is]\n\t"
"ADDCS %[Rd1], %[Rd1], #1"
: [Rd] "=r" (d), [Rd1] "+r" (inc): [Rm] "r" (v), [Is] "i" (sh));
return d;
}
static inline __attribute__((always_inline)) uint32_t arm_ror(uint32_t v, uint32_t sh)
{
uint32_t d;
asm ("ROR %[Rd], %[Rm], %[Rs]" : [Rd] "=r" (d) : [Rm] "r" (v), [Rs] "r" (sh));
return d;
}
static inline __attribute__((always_inline)) uint32_t arm_Bit_Clear_imm(uint32_t v, uint32_t v2)
{
uint32_t d;
asm ("BIC %[Rd], %[Rm], %[Rs]" : [Rd] "=r" (d) : [Rm] "r" (v), [Rs] "r" (v2));
return d;
}
#endif
int LedDeviceWS2812b::write(const std::vector<ColorRgb> &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<mLedCount; i++)
{
// Create bits necessary to represent one color triplet (in GRB, not RGB, order)
//printf("RGB: %d, %d, %d\n", ledValues[i].red, ledValues[i].green, ledValues[i].blue);
colorBits = ((unsigned int)ledValues[i].red << 8) | ((unsigned int)ledValues[i].green << 16) | ledValues[i].blue;
//printBinary(colorBits, 24);
//printf(" (binary, GRB order)\n");
// Iterate through color bits to get wire bits
for(int j=23; j>=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<ColorRgb>(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);
}

View File

@ -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 <leddevice/LedDevice.h>
//#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<ColorRgb> &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_ */

View File

@ -4,17 +4,21 @@
#include <cstdio>
#include <iostream>
// Qt includes
#include <QTimer>
// Serial includes
#include <serial/serial.h>
// 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;
}

View File

@ -1,5 +1,7 @@
#pragma once
#include <QObject>
// Serial includes
#include <serial/serial.h>
@ -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;
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -36,8 +36,10 @@
// JsonServer includes
#include <jsonserver/JsonServer.h>
#ifdef ENABLE_PROTOBUF
// ProtoServer includes
#include <protoserver/ProtoServer.h>
#endif
// BoblightServer includes
#include <boblightserver/BoblightServer.h>
@ -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