mirror of
https://github.com/hyperion-project/hyperion.ng.git
synced 2025-03-01 10:33:28 +00:00
Leddevices source tree refactoring (#461)
* rework structure of leddevice source tree * fix data type vor v4l sig detection value in webui * automate leddevicefactory.cpp
This commit is contained in:
171
libsrc/leddevice/dev_net/LedDeviceAtmoOrb.cpp
Normal file
171
libsrc/leddevice/dev_net/LedDeviceAtmoOrb.cpp
Normal file
@@ -0,0 +1,171 @@
|
||||
// Local-Hyperion includes
|
||||
#include "LedDeviceAtmoOrb.h"
|
||||
|
||||
// qt includes
|
||||
#include <QtCore/qmath.h>
|
||||
#include <QEventLoop>
|
||||
#include <QtNetwork>
|
||||
#include <QNetworkReply>
|
||||
#include <QStringList>
|
||||
|
||||
AtmoOrbLight::AtmoOrbLight(unsigned int id)
|
||||
{
|
||||
// Not implemented
|
||||
}
|
||||
|
||||
LedDeviceAtmoOrb::LedDeviceAtmoOrb(const QJsonObject &deviceConfig)
|
||||
: LedDevice()
|
||||
{
|
||||
init(deviceConfig);
|
||||
_manager = new QNetworkAccessManager();
|
||||
_groupAddress = QHostAddress(_multicastGroup);
|
||||
|
||||
_udpSocket = new QUdpSocket(this);
|
||||
_udpSocket->bind(QHostAddress::AnyIPv4, _multiCastGroupPort, QUdpSocket::ShareAddress | QUdpSocket::ReuseAddressHint);
|
||||
|
||||
joinedMulticastgroup = _udpSocket->joinMulticastGroup(_groupAddress);
|
||||
}
|
||||
|
||||
bool LedDeviceAtmoOrb::init(const QJsonObject &deviceConfig)
|
||||
{
|
||||
_multicastGroup = deviceConfig["output"].toString().toStdString().c_str();
|
||||
_useOrbSmoothing = deviceConfig["useOrbSmoothing"].toBool(false);
|
||||
_transitiontime = deviceConfig["transitiontime"].toInt(0);
|
||||
_skipSmoothingDiff = deviceConfig["skipSmoothingDiff"].toInt(0);
|
||||
_multiCastGroupPort = deviceConfig["port"].toInt(49692);
|
||||
_numLeds = deviceConfig["numLeds"].toInt(24);
|
||||
|
||||
const QStringList orbIds = deviceConfig["orbIds"].toString().simplified().remove(" ").split(",", QString::SkipEmptyParts);
|
||||
_orbIds.clear();
|
||||
|
||||
foreach(auto & id_str, orbIds)
|
||||
{
|
||||
bool ok;
|
||||
int id = id_str.toInt(&ok);
|
||||
if (ok)
|
||||
_orbIds.append(id);
|
||||
else
|
||||
Error(_log, "orb id '%s' is not a number", QSTRING_CSTR(id_str));
|
||||
}
|
||||
|
||||
return _orbIds.size() > 0;
|
||||
}
|
||||
|
||||
LedDevice* LedDeviceAtmoOrb::construct(const QJsonObject &deviceConfig)
|
||||
{
|
||||
return new LedDeviceAtmoOrb(deviceConfig);
|
||||
}
|
||||
|
||||
int LedDeviceAtmoOrb::write(const std::vector <ColorRgb> &ledValues)
|
||||
{
|
||||
// If not in multicast group return
|
||||
if (!joinedMulticastgroup)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Command options:
|
||||
//
|
||||
// 1 = force off
|
||||
// 2 = use lamp smoothing and validate by Orb ID
|
||||
// 4 = validate by Orb ID
|
||||
|
||||
// When setting _useOrbSmoothing = true it's recommended to disable Hyperion's own smoothing as it will conflict (double smoothing)
|
||||
int commandType = 4;
|
||||
if(_useOrbSmoothing)
|
||||
{
|
||||
commandType = 2;
|
||||
}
|
||||
|
||||
// Iterate through colors and set Orb color
|
||||
// Start off with idx 1 as 0 is reserved for controlling all orbs at once
|
||||
unsigned int idx = 1;
|
||||
|
||||
for (const ColorRgb &color : ledValues)
|
||||
{
|
||||
// Retrieve last send colors
|
||||
int lastRed = lastColorRedMap[idx];
|
||||
int lastGreen = lastColorGreenMap[idx];
|
||||
int lastBlue = lastColorBlueMap[idx];
|
||||
|
||||
// If color difference is higher than _skipSmoothingDiff than we skip Orb smoothing (if enabled) and send it right away
|
||||
if ((_skipSmoothingDiff != 0 && _useOrbSmoothing) && (abs(color.red - lastRed) >= _skipSmoothingDiff || abs(color.blue - lastBlue) >= _skipSmoothingDiff ||
|
||||
abs(color.green - lastGreen) >= _skipSmoothingDiff))
|
||||
{
|
||||
// Skip Orb smoothing when using (command type 4)
|
||||
for (int i = 0; i < _orbIds.size(); i++)
|
||||
{
|
||||
if (_orbIds[i] == idx)
|
||||
{
|
||||
setColor(idx, color, 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Send color
|
||||
for (int i = 0; i < _orbIds.size(); i++)
|
||||
{
|
||||
if (_orbIds[i] == idx)
|
||||
{
|
||||
setColor(idx, color, commandType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Store last colors send for light id
|
||||
lastColorRedMap[idx] = color.red;
|
||||
lastColorGreenMap[idx] = color.green;
|
||||
lastColorBlueMap[idx] = color.blue;
|
||||
|
||||
// Next light id.
|
||||
idx++;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void LedDeviceAtmoOrb::setColor(unsigned int orbId, const ColorRgb &color, int commandType)
|
||||
{
|
||||
QByteArray bytes;
|
||||
bytes.resize(5 + _numLeds * 3);
|
||||
bytes.fill('\0');
|
||||
|
||||
// Command identifier: C0FFEE
|
||||
bytes[0] = 0xC0;
|
||||
bytes[1] = 0xFF;
|
||||
bytes[2] = 0xEE;
|
||||
|
||||
// Command type
|
||||
bytes[3] = commandType;
|
||||
|
||||
// Orb ID
|
||||
bytes[4] = orbId;
|
||||
|
||||
// RED / GREEN / BLUE
|
||||
bytes[5] = color.red;
|
||||
bytes[6] = color.green;
|
||||
bytes[7] = color.blue;
|
||||
|
||||
sendCommand(bytes);
|
||||
}
|
||||
|
||||
void LedDeviceAtmoOrb::sendCommand(const QByteArray &bytes)
|
||||
{
|
||||
QByteArray datagram = bytes;
|
||||
_udpSocket->writeDatagram(datagram.data(), datagram.size(), _groupAddress, _multiCastGroupPort);
|
||||
}
|
||||
|
||||
int LedDeviceAtmoOrb::switchOff()
|
||||
{
|
||||
for (auto orbId : _orbIds)
|
||||
{
|
||||
setColor(orbId, ColorRgb::BLACK, 1);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
LedDeviceAtmoOrb::~LedDeviceAtmoOrb()
|
||||
{
|
||||
delete _manager;
|
||||
}
|
123
libsrc/leddevice/dev_net/LedDeviceAtmoOrb.h
Normal file
123
libsrc/leddevice/dev_net/LedDeviceAtmoOrb.h
Normal file
@@ -0,0 +1,123 @@
|
||||
#pragma once
|
||||
|
||||
// Qt includes
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QHostAddress>
|
||||
#include <QMap>
|
||||
#include <QVector>
|
||||
|
||||
// Leddevice includes
|
||||
#include <leddevice/LedDevice.h>
|
||||
|
||||
class QUdpSocket;
|
||||
|
||||
class AtmoOrbLight {
|
||||
public:
|
||||
unsigned int id;
|
||||
|
||||
///
|
||||
/// Constructs the light.
|
||||
///
|
||||
/// @param id the orb id
|
||||
AtmoOrbLight(unsigned int id);
|
||||
};
|
||||
|
||||
/**
|
||||
* Implementation for the AtmoOrb
|
||||
*
|
||||
* To use set the device to "atmoorb".
|
||||
*
|
||||
* @author RickDB (github)
|
||||
*/
|
||||
class LedDeviceAtmoOrb : public LedDevice
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
// Last send color map
|
||||
QMap<int, int> lastColorRedMap;
|
||||
QMap<int, int> lastColorGreenMap;
|
||||
QMap<int, int> lastColorBlueMap;
|
||||
|
||||
// Multicast status
|
||||
bool joinedMulticastgroup;
|
||||
|
||||
///
|
||||
/// Constructs specific LedDevice
|
||||
///
|
||||
/// @param deviceConfig json device config
|
||||
///
|
||||
LedDeviceAtmoOrb(const QJsonObject &deviceConfig);
|
||||
|
||||
///
|
||||
/// Sets configuration
|
||||
///
|
||||
/// @param deviceConfig the json device config
|
||||
/// @return true if success
|
||||
bool init(const QJsonObject &deviceConfig);
|
||||
|
||||
/// constructs leddevice
|
||||
static LedDevice* construct(const QJsonObject &deviceConfig);
|
||||
///
|
||||
/// Destructor of this device
|
||||
///
|
||||
virtual ~LedDeviceAtmoOrb();
|
||||
|
||||
virtual int switchOff();
|
||||
|
||||
private:
|
||||
///
|
||||
/// Sends the given led-color values to the Orbs
|
||||
///
|
||||
/// @param ledValues The color-value per led
|
||||
/// @return Zero on success else negative
|
||||
///
|
||||
virtual int write(const std::vector <ColorRgb> &ledValues);
|
||||
|
||||
/// QNetworkAccessManager object for sending requests.
|
||||
QNetworkAccessManager *_manager;
|
||||
|
||||
/// String containing multicast group IP address
|
||||
QString _multicastGroup;
|
||||
|
||||
/// use Orbs own (external) smoothing algorithm
|
||||
bool _useOrbSmoothing;
|
||||
|
||||
/// Transition time between colors (not implemented)
|
||||
int _transitiontime;
|
||||
|
||||
// Maximum allowed color difference, will skip Orb (external) smoothing once reached
|
||||
int _skipSmoothingDiff;
|
||||
|
||||
/// Multicast port to send data to
|
||||
int _multiCastGroupPort;
|
||||
|
||||
/// Number of leds in Orb, used to determine buffer size
|
||||
int _numLeds;
|
||||
|
||||
/// QHostAddress object of multicast group IP address
|
||||
QHostAddress _groupAddress;
|
||||
|
||||
/// QUdpSocket object used to send data over
|
||||
QUdpSocket * _udpSocket;
|
||||
|
||||
/// Array of the orb ids.
|
||||
QVector<unsigned int> _orbIds;
|
||||
|
||||
///
|
||||
/// Set Orbcolor
|
||||
///
|
||||
/// @param orbId the orb id
|
||||
/// @param color which color to set
|
||||
/// @param commandType which type of command to send (off / smoothing / etc..)
|
||||
///
|
||||
void setColor(unsigned int orbId, const ColorRgb &color, int commandType);
|
||||
|
||||
///
|
||||
/// Send Orb command
|
||||
///
|
||||
/// @param bytes the byte array containing command to send over multicast
|
||||
///
|
||||
void sendCommand(const QByteArray &bytes);
|
||||
};
|
147
libsrc/leddevice/dev_net/LedDeviceFadeCandy.cpp
Normal file
147
libsrc/leddevice/dev_net/LedDeviceFadeCandy.cpp
Normal file
@@ -0,0 +1,147 @@
|
||||
#include "LedDeviceFadeCandy.h"
|
||||
|
||||
static const signed MAX_NUM_LEDS = 10000; // OPC can handle 21845 leds - in theory, fadecandy device should handle 10000 leds
|
||||
static const unsigned OPC_SET_PIXELS = 0; // OPC command codes
|
||||
static const unsigned OPC_SYS_EX = 255; // OPC command codes
|
||||
static const unsigned OPC_HEADER_SIZE = 4; // OPC header size
|
||||
|
||||
|
||||
LedDeviceFadeCandy::LedDeviceFadeCandy(const QJsonObject &deviceConfig)
|
||||
: LedDevice()
|
||||
{
|
||||
_deviceReady = init(deviceConfig);
|
||||
}
|
||||
|
||||
|
||||
LedDeviceFadeCandy::~LedDeviceFadeCandy()
|
||||
{
|
||||
_client.close();
|
||||
}
|
||||
|
||||
LedDevice* LedDeviceFadeCandy::construct(const QJsonObject &deviceConfig)
|
||||
{
|
||||
return new LedDeviceFadeCandy(deviceConfig);
|
||||
}
|
||||
|
||||
|
||||
bool LedDeviceFadeCandy::init(const QJsonObject &deviceConfig)
|
||||
{
|
||||
_client.close();
|
||||
|
||||
if (_ledCount > MAX_NUM_LEDS)
|
||||
{
|
||||
Error(_log, "fadecandy/opc: Invalid attempt to write led values. Not more than %d leds are allowed.", MAX_NUM_LEDS);
|
||||
return false;
|
||||
}
|
||||
|
||||
_host = deviceConfig["output"].toString("127.0.0.1");
|
||||
_port = deviceConfig["port"].toInt(7890);
|
||||
_channel = deviceConfig["channel"].toInt(0);
|
||||
_gamma = deviceConfig["gamma"].toDouble(1.0);
|
||||
_noDither = ! deviceConfig["dither"].toBool(false);
|
||||
_noInterp = ! deviceConfig["interpolation"].toBool(false);
|
||||
_manualLED = deviceConfig["manualLed"].toBool(false);
|
||||
_ledOnOff = deviceConfig["ledOn"].toBool(false);
|
||||
_setFcConfig = deviceConfig["setFcConfig"].toBool(false);
|
||||
|
||||
_whitePoint_r = 1.0;
|
||||
_whitePoint_g = 1.0;
|
||||
_whitePoint_b = 1.0;
|
||||
|
||||
const QJsonArray whitePointConfig = deviceConfig["whitePoint"].toArray();
|
||||
if ( !whitePointConfig.isEmpty() && whitePointConfig.size() == 3 )
|
||||
{
|
||||
_whitePoint_r = whitePointConfig[0].toDouble() / 255.0;
|
||||
_whitePoint_g = whitePointConfig[1].toDouble() / 255.0;
|
||||
_whitePoint_b = whitePointConfig[2].toDouble() / 255.0;
|
||||
}
|
||||
|
||||
_opc_data.resize( _ledRGBCount + OPC_HEADER_SIZE );
|
||||
_opc_data[0] = _channel;
|
||||
_opc_data[1] = OPC_SET_PIXELS;
|
||||
_opc_data[2] = _ledRGBCount >> 8;
|
||||
_opc_data[3] = _ledRGBCount & 0xff;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LedDeviceFadeCandy::isConnected()
|
||||
{
|
||||
return _client.state() == QAbstractSocket::ConnectedState;
|
||||
}
|
||||
|
||||
|
||||
bool LedDeviceFadeCandy::tryConnect()
|
||||
{
|
||||
if ( _client.state() == QAbstractSocket::UnconnectedState ) {
|
||||
_client.connectToHost( _host, _port);
|
||||
if ( _client.waitForConnected(1000) )
|
||||
{
|
||||
Info(_log,"fadecandy/opc: connected to %s:%i on channel %i", QSTRING_CSTR(_host), _port, _channel);
|
||||
if (_setFcConfig)
|
||||
{
|
||||
sendFadeCandyConfiguration();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return isConnected();
|
||||
}
|
||||
|
||||
|
||||
int LedDeviceFadeCandy::write( const std::vector<ColorRgb> & ledValues )
|
||||
{
|
||||
uint idx = OPC_HEADER_SIZE;
|
||||
for (const ColorRgb& color : ledValues)
|
||||
{
|
||||
_opc_data[idx ] = unsigned( color.red );
|
||||
_opc_data[idx+1] = unsigned( color.green );
|
||||
_opc_data[idx+2] = unsigned( color.blue );
|
||||
idx += 3;
|
||||
}
|
||||
|
||||
return ( transferData()<0 ? -1 : 0 );
|
||||
}
|
||||
|
||||
|
||||
int LedDeviceFadeCandy::transferData()
|
||||
{
|
||||
if ( isConnected() || tryConnect() )
|
||||
return _client.write( _opc_data, _opc_data.size() );
|
||||
|
||||
return -2;
|
||||
}
|
||||
|
||||
int LedDeviceFadeCandy::sendSysEx(uint8_t systemId, uint8_t commandId, QByteArray msg)
|
||||
{
|
||||
if ( isConnected() )
|
||||
{
|
||||
QByteArray sysExData;
|
||||
ssize_t data_size = msg.size() + 4;
|
||||
sysExData.resize( 4 + OPC_HEADER_SIZE );
|
||||
|
||||
sysExData[0] = 0;
|
||||
sysExData[1] = OPC_SYS_EX;
|
||||
sysExData[2] = data_size >>8;
|
||||
sysExData[3] = data_size &0xff;
|
||||
sysExData[4] = systemId >>8;
|
||||
sysExData[5] = systemId &0xff;
|
||||
sysExData[6] = commandId >>8;
|
||||
sysExData[7] = commandId &0xff;
|
||||
|
||||
sysExData += msg;
|
||||
|
||||
return _client.write( sysExData, sysExData.size() );
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void LedDeviceFadeCandy::sendFadeCandyConfiguration()
|
||||
{
|
||||
Debug(_log, "send configuration to fadecandy");
|
||||
QString data = "{\"gamma\": "+QString::number(_gamma,'g',4)+", \"whitepoint\": ["+QString::number(_whitePoint_r,'g',4)+", "+QString::number(_whitePoint_g,'g',4)+", "+QString::number(_whitePoint_b,'g',4)+"]}";
|
||||
sendSysEx(1, 1, data.toLocal8Bit() );
|
||||
|
||||
char firmware_data = ((uint8_t)_noDither | ((uint8_t)_noInterp << 1) | ((uint8_t)_manualLED << 2) | ((uint8_t)_ledOnOff << 3) );
|
||||
sendSysEx(1, 2, QByteArray(1,firmware_data) );
|
||||
}
|
114
libsrc/leddevice/dev_net/LedDeviceFadeCandy.h
Normal file
114
libsrc/leddevice/dev_net/LedDeviceFadeCandy.h
Normal file
@@ -0,0 +1,114 @@
|
||||
#pragma once
|
||||
|
||||
// STL/Qt includes
|
||||
#include <QTcpSocket>
|
||||
#include <QString>
|
||||
|
||||
// Leddevice includes
|
||||
#include <leddevice/LedDevice.h>
|
||||
|
||||
///
|
||||
/// Implementation of the LedDevice interface for sending to
|
||||
/// fadecandy/opc-server via network by using the 'open pixel control' protocol.
|
||||
///
|
||||
class LedDeviceFadeCandy : public LedDevice
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
///
|
||||
/// Constructs the LedDevice for fadecandy/opc server
|
||||
///
|
||||
/// following code shows all config options
|
||||
/// @code
|
||||
/// "device" :
|
||||
/// {
|
||||
/// "name" : "MyPi",
|
||||
/// "type" : "fadecandy",
|
||||
/// "output" : "localhost",
|
||||
/// "colorOrder" : "rgb",
|
||||
/// "setFcConfig" : false,
|
||||
/// "gamma" : 1.0,
|
||||
/// "whitepoint" : [1.0, 1.0, 1.0],
|
||||
/// "dither" : false,
|
||||
/// "interpolation" : false,
|
||||
/// "manualLed" : false,
|
||||
/// "ledOn" : false
|
||||
/// },
|
||||
///@endcode
|
||||
///
|
||||
/// @param deviceConfig json config for fadecandy
|
||||
///
|
||||
LedDeviceFadeCandy(const QJsonObject &deviceConfig);
|
||||
|
||||
///
|
||||
/// Destructor of the LedDevice; closes the tcp client
|
||||
///
|
||||
virtual ~LedDeviceFadeCandy();
|
||||
|
||||
/// constructs leddevice
|
||||
static LedDevice* construct(const QJsonObject &deviceConfig);
|
||||
|
||||
///
|
||||
/// Sets configuration
|
||||
///
|
||||
/// @param deviceConfig the json device config
|
||||
/// @return true if success
|
||||
bool init(const QJsonObject &deviceConfig);
|
||||
|
||||
///
|
||||
/// 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);
|
||||
|
||||
private:
|
||||
QTcpSocket _client;
|
||||
QString _host;
|
||||
uint16_t _port;
|
||||
unsigned _channel;
|
||||
QByteArray _opc_data;
|
||||
|
||||
// fadecandy sysEx
|
||||
bool _setFcConfig;
|
||||
double _gamma;
|
||||
double _whitePoint_r;
|
||||
double _whitePoint_g;
|
||||
double _whitePoint_b;
|
||||
bool _noDither;
|
||||
bool _noInterp;
|
||||
bool _manualLED;
|
||||
bool _ledOnOff;
|
||||
|
||||
/// try to establish connection to opc server, if not connected yet
|
||||
///
|
||||
/// @return true if connection is established
|
||||
///
|
||||
bool tryConnect();
|
||||
|
||||
/// return the conenction state
|
||||
///
|
||||
/// @return True if connection established
|
||||
///
|
||||
bool isConnected();
|
||||
|
||||
/// transfer current opc_data buffer to opc server
|
||||
///
|
||||
/// @return amount of transfered bytes. -1 error while transfering, -2 error while connecting
|
||||
///
|
||||
int transferData();
|
||||
|
||||
/// send system exclusive commands
|
||||
///
|
||||
/// @param systemId fadecandy device identifier (for standard fadecandy always: 1)
|
||||
/// @param commandId id of command
|
||||
/// @param msg the sysEx message
|
||||
/// @return amount bytes written, -1 if fail
|
||||
int sendSysEx(uint8_t systemId, uint8_t commandId, QByteArray msg);
|
||||
|
||||
/// sends the configuration to fcserver
|
||||
void sendFadeCandyConfiguration();
|
||||
|
||||
};
|
457
libsrc/leddevice/dev_net/LedDevicePhilipsHue.cpp
Executable file
457
libsrc/leddevice/dev_net/LedDevicePhilipsHue.cpp
Executable file
@@ -0,0 +1,457 @@
|
||||
// Local-Hyperion includes
|
||||
#include "LedDevicePhilipsHue.h"
|
||||
|
||||
// qt includes
|
||||
#include <QtCore/qmath.h>
|
||||
#include <QEventLoop>
|
||||
#include <QNetworkReply>
|
||||
|
||||
const CiColor CiColor::BLACK =
|
||||
{ 0, 0, 0 };
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
CiColor CiColor::rgbToCiColor(float red, float green, float blue, CiColorTriangle colorSpace)
|
||||
{
|
||||
// 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.664511f + g * 0.154324f + b * 0.162028f;
|
||||
float Y = r * 0.283881f + g * 0.668433f + b * 0.047685f;
|
||||
float Z = r * 0.000088f + g * 0.072310f + b * 0.986039f;
|
||||
// Convert to x,y space.
|
||||
float cx = X / (X + Y + Z);
|
||||
float cy = Y / (X + Y + Z);
|
||||
if (std::isnan(cx))
|
||||
{
|
||||
cx = 0.0f;
|
||||
}
|
||||
if (std::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, colorSpace))
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
|
||||
float CiColor::crossProduct(CiColor p1, CiColor p2)
|
||||
{
|
||||
return p1.x * p2.y - p1.y * p2.x;
|
||||
}
|
||||
|
||||
bool CiColor::isPointInLampsReach(CiColor p, CiColorTriangle colorSpace)
|
||||
{
|
||||
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 CiColor::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 CiColor::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);
|
||||
}
|
||||
|
||||
QByteArray PhilipsHueBridge::get(QString route)
|
||||
{
|
||||
QString url = QString("http://%1/api/%2/%3").arg(host).arg(username).arg(route);
|
||||
Debug(log, "Get %s", url.toStdString().c_str());
|
||||
// Perfrom request
|
||||
QNetworkRequest request(url);
|
||||
QNetworkReply* reply = manager->get(request);
|
||||
// Connect requestFinished signal to quit slot of the loop.
|
||||
QEventLoop loop;
|
||||
loop.connect(reply, SIGNAL(finished()), SLOT(quit()));
|
||||
// Go into the loop until the request is finished.
|
||||
loop.exec();
|
||||
// Read all data of the response.
|
||||
QByteArray response = reply->readAll();
|
||||
// Free space.
|
||||
reply->deleteLater();
|
||||
// Return response;
|
||||
return response;
|
||||
}
|
||||
|
||||
void PhilipsHueBridge::post(QString route, QString content)
|
||||
{
|
||||
QString url = QString("http://%1/api/%2/%3").arg(host).arg(username).arg(route);
|
||||
Debug(log, "Post %s: %s", url.toStdString().c_str(), content.toStdString().c_str());
|
||||
// Perfrom request
|
||||
QNetworkRequest request(url);
|
||||
QNetworkReply* reply = manager->put(request, content.toLatin1());
|
||||
// Connect finished signal to quit slot of the loop.
|
||||
QEventLoop loop;
|
||||
loop.connect(reply, SIGNAL(finished()), SLOT(quit()));
|
||||
// Go into the loop until the request is finished.
|
||||
loop.exec();
|
||||
// Free space.
|
||||
reply->deleteLater();
|
||||
}
|
||||
|
||||
const std::set<QString> PhilipsHueLight::GAMUT_A_MODEL_IDS =
|
||||
{ "LLC001", "LLC005", "LLC006", "LLC007", "LLC010", "LLC011", "LLC012", "LLC013", "LLC014", "LST001" };
|
||||
const std::set<QString> PhilipsHueLight::GAMUT_B_MODEL_IDS =
|
||||
{ "LCT001", "LCT002", "LCT003", "LCT007", "LLM001" };
|
||||
const std::set<QString> PhilipsHueLight::GAMUT_C_MODEL_IDS =
|
||||
{ "LLC020", "LST002" };
|
||||
|
||||
PhilipsHueLight::PhilipsHueLight(Logger* log, PhilipsHueBridge& bridge, unsigned int id) :
|
||||
log(log), bridge(bridge), id(id)
|
||||
{
|
||||
|
||||
// Get model id and original state.
|
||||
QByteArray response = bridge.get(QString("lights/%1").arg(id));
|
||||
// Use JSON parser to parse response.
|
||||
QJsonParseError error;
|
||||
QJsonDocument reader = QJsonDocument::fromJson(response, &error);
|
||||
;
|
||||
// Parse response.
|
||||
if (error.error != QJsonParseError::NoError)
|
||||
{
|
||||
Error(log, "Got invalid response from light %d", id);
|
||||
}
|
||||
// Get state object values which are subject to change.
|
||||
QJsonObject json = reader.object();
|
||||
if (!json["state"].toObject().contains("on"))
|
||||
{
|
||||
Error(log, "Got no state object from light %d", id);
|
||||
}
|
||||
if (!json["state"].toObject().contains("on"))
|
||||
{
|
||||
Error(log, "Got invalid state object from light %d", id);
|
||||
}
|
||||
QJsonObject state;
|
||||
state["on"] = json["state"].toObject()["on"];
|
||||
on = false;
|
||||
if (json["state"].toObject()["on"].toBool() == true)
|
||||
{
|
||||
state["xy"] = json["state"].toObject()["xy"];
|
||||
state["bri"] = json["state"].toObject()["bri"];
|
||||
on = true;
|
||||
|
||||
color =
|
||||
{ (float) state["xy"].toArray()[0].toDouble(),(float) state["xy"].toArray()[1].toDouble(), (float) state["bri"].toDouble() / 255.0f};
|
||||
transitionTime = json["state"].toObject()["transitiontime"].toInt();
|
||||
}
|
||||
// Determine the model id.
|
||||
modelId = json["modelid"].toString().trimmed().replace("\"", "");
|
||||
// Determine the original state.
|
||||
originalState = QJsonDocument(state).toJson(QJsonDocument::JsonFormat::Compact).trimmed();
|
||||
// Find id in the sets and set the appropriate color space.
|
||||
if (GAMUT_A_MODEL_IDS.find(modelId) != GAMUT_A_MODEL_IDS.end())
|
||||
{
|
||||
Debug(log, "Recognized model id %s as gamut A", modelId.toStdString().c_str());
|
||||
colorSpace.red =
|
||||
{ 0.703f, 0.296f};
|
||||
colorSpace.green =
|
||||
{ 0.2151f, 0.7106f};
|
||||
colorSpace.blue =
|
||||
{ 0.138f, 0.08f};
|
||||
}
|
||||
else if (GAMUT_B_MODEL_IDS.find(modelId) != GAMUT_B_MODEL_IDS.end())
|
||||
{
|
||||
Debug(log, "Recognized model id %s as gamut B", modelId.toStdString().c_str());
|
||||
colorSpace.red =
|
||||
{ 0.675f, 0.322f};
|
||||
colorSpace.green =
|
||||
{ 0.4091f, 0.518f};
|
||||
colorSpace.blue =
|
||||
{ 0.167f, 0.04f};
|
||||
}
|
||||
else if (GAMUT_C_MODEL_IDS.find(modelId) != GAMUT_C_MODEL_IDS.end())
|
||||
{
|
||||
Debug(log, "Recognized model id %s as gamut C", modelId.toStdString().c_str());
|
||||
colorSpace.red =
|
||||
{ 0.675f, 0.322f};
|
||||
colorSpace.green =
|
||||
{ 0.2151f, 0.7106f};
|
||||
colorSpace.blue =
|
||||
{ 0.167f, 0.04f};
|
||||
}
|
||||
else
|
||||
{
|
||||
Warning(log, "Did not recognize model id %s", modelId.toStdString().c_str());
|
||||
colorSpace.red =
|
||||
{ 1.0f, 0.0f};
|
||||
colorSpace.green =
|
||||
{ 0.0f, 1.0f};
|
||||
colorSpace.blue =
|
||||
{ 0.0f, 0.0f};
|
||||
}
|
||||
}
|
||||
|
||||
PhilipsHueLight::~PhilipsHueLight()
|
||||
{
|
||||
// Restore the original state.
|
||||
set(originalState);
|
||||
}
|
||||
|
||||
void PhilipsHueLight::set(QString state)
|
||||
{
|
||||
bridge.post(QString("lights/%1/state").arg(id), state);
|
||||
}
|
||||
|
||||
void PhilipsHueLight::setOn(bool on)
|
||||
{
|
||||
if (this->on != on)
|
||||
{
|
||||
QString arg = on ? "true" : "false";
|
||||
set(QString("{ \"on\": %1 }").arg(arg));
|
||||
}
|
||||
this->on = on;
|
||||
}
|
||||
|
||||
void PhilipsHueLight::setTransitionTime(unsigned int transitionTime)
|
||||
{
|
||||
if (this->transitionTime != transitionTime)
|
||||
{
|
||||
set(QString("{ \"transitiontime\": %1 }").arg(transitionTime));
|
||||
}
|
||||
this->transitionTime = transitionTime;
|
||||
}
|
||||
|
||||
void PhilipsHueLight::setColor(CiColor color, float brightnessFactor)
|
||||
{
|
||||
if (this->color != color)
|
||||
{
|
||||
const int bri = qRound(qMin(254.0f, brightnessFactor * qMax(1.0f, color.bri * 254.0f)));
|
||||
set(QString("{ \"xy\": [%1, %2], \"bri\": %3 }").arg(color.x, 0, 'f', 4).arg(color.y, 0, 'f', 4).arg(bri));
|
||||
}
|
||||
this->color = color;
|
||||
}
|
||||
|
||||
CiColor PhilipsHueLight::getColor() const
|
||||
{
|
||||
return color;
|
||||
}
|
||||
|
||||
CiColorTriangle PhilipsHueLight::getColorSpace() const
|
||||
{
|
||||
return colorSpace;
|
||||
}
|
||||
|
||||
LedDevicePhilipsHue::LedDevicePhilipsHue(const QJsonObject &deviceConfig) :
|
||||
LedDevice()
|
||||
{
|
||||
manager = new QNetworkAccessManager();
|
||||
_deviceReady = init(deviceConfig);
|
||||
|
||||
timer.setInterval(3000);
|
||||
timer.setSingleShot(true);
|
||||
connect(&timer, SIGNAL(timeout()), this, SLOT(restoreStates()));
|
||||
}
|
||||
|
||||
LedDevicePhilipsHue::~LedDevicePhilipsHue()
|
||||
{
|
||||
// Switch off.
|
||||
switchOff();
|
||||
}
|
||||
|
||||
bool LedDevicePhilipsHue::init(const QJsonObject &deviceConfig)
|
||||
{
|
||||
LedDevice::init(deviceConfig);
|
||||
|
||||
bridge =
|
||||
{ _log, manager, deviceConfig["output"].toString(), deviceConfig["username"].toString("newdeveloper")};
|
||||
switchOffOnBlack = deviceConfig["switchOffOnBlack"].toBool(true);
|
||||
brightnessFactor = (float) deviceConfig["brightnessFactor"].toDouble(1.0);
|
||||
transitionTime = deviceConfig["transitiontime"].toInt(1);
|
||||
lightIds.clear();
|
||||
QJsonArray lArray = deviceConfig["lightIds"].toArray();
|
||||
for (int i = 0; i < lArray.size(); i++)
|
||||
{
|
||||
lightIds.push_back(lArray[i].toInt());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
LedDevice* LedDevicePhilipsHue::construct(const QJsonObject &deviceConfig)
|
||||
{
|
||||
return new LedDevicePhilipsHue(deviceConfig);
|
||||
}
|
||||
|
||||
int LedDevicePhilipsHue::write(const std::vector<ColorRgb> & ledValues)
|
||||
{
|
||||
// Save light states if not done before.
|
||||
if (!areStatesSaved())
|
||||
{
|
||||
saveStates((unsigned int) ledValues.size());
|
||||
}
|
||||
// If there are less states saved than colors given, then maybe something went wrong before.
|
||||
if (lights.size() != ledValues.size())
|
||||
{
|
||||
restoreStates();
|
||||
return 0;
|
||||
}
|
||||
// Iterate through colors and set light states.
|
||||
unsigned int idx = 0;
|
||||
for (const ColorRgb& color : ledValues)
|
||||
{
|
||||
// Get lamp.
|
||||
PhilipsHueLight& light = lights.at(idx);
|
||||
// Scale colors from [0, 255] to [0, 1] and convert to xy space.
|
||||
CiColor xy = CiColor::rgbToCiColor(color.red / 255.0f, color.green / 255.0f, color.blue / 255.0f,
|
||||
light.getColorSpace());
|
||||
// Write color if color has been changed.
|
||||
if (switchOffOnBlack && light.getColor() != CiColor::BLACK && xy == CiColor::BLACK)
|
||||
{
|
||||
light.setOn(false);
|
||||
}
|
||||
else if (switchOffOnBlack && light.getColor() == CiColor::BLACK && xy != CiColor::BLACK)
|
||||
{
|
||||
light.setOn(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
light.setOn(true);
|
||||
}
|
||||
light.setTransitionTime(transitionTime);
|
||||
light.setColor(xy, brightnessFactor);
|
||||
// Next light id.
|
||||
idx++;
|
||||
}
|
||||
// Reset timer.
|
||||
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::saveStates(unsigned int nLights)
|
||||
{
|
||||
|
||||
// Clear saved lamps.
|
||||
lights.clear();
|
||||
//
|
||||
if (nLights == 0) {
|
||||
return;
|
||||
}
|
||||
// Read light ids if none have been supplied by the user.
|
||||
if (lightIds.size() != nLights)
|
||||
{
|
||||
lightIds.clear();
|
||||
// Retrieve lights from bridge.
|
||||
QByteArray response = bridge.get("lights");
|
||||
// Use QJsonDocument to parse reponse.
|
||||
QJsonParseError error;
|
||||
QJsonDocument reader = QJsonDocument::fromJson(response, &error);
|
||||
if (error.error != QJsonParseError::NoError)
|
||||
{
|
||||
Error(_log, "No lights found.");
|
||||
}
|
||||
// Loop over all children.
|
||||
QJsonObject json = reader.object();
|
||||
for (QJsonObject::iterator it = json.begin(); it != json.end() && lightIds.size() < nLights; it++)
|
||||
{
|
||||
int lightId = atoi(it.key().toStdString().c_str());
|
||||
lightIds.push_back(lightId);
|
||||
Debug(_log, "nLights=%d: found light with id %d.", nLights, lightId);
|
||||
}
|
||||
// Check if we found enough lights.
|
||||
if (lightIds.size() != nLights)
|
||||
{
|
||||
Error(_log, "Not enough lights found");
|
||||
}
|
||||
}
|
||||
// Iterate lights.
|
||||
for (unsigned int i = 0; i < nLights; i++)
|
||||
{
|
||||
lights.push_back(PhilipsHueLight(_log, bridge, lightIds.at(i)));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void LedDevicePhilipsHue::restoreStates()
|
||||
{
|
||||
lights.clear();
|
||||
}
|
||||
|
||||
bool LedDevicePhilipsHue::areStatesSaved()
|
||||
{
|
||||
return !lights.empty();
|
||||
}
|
272
libsrc/leddevice/dev_net/LedDevicePhilipsHue.h
Normal file
272
libsrc/leddevice/dev_net/LedDevicePhilipsHue.h
Normal file
@@ -0,0 +1,272 @@
|
||||
#pragma once
|
||||
|
||||
// STL includes
|
||||
#include <set>
|
||||
|
||||
// Qt includes
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QTimer>
|
||||
|
||||
// Leddevice includes
|
||||
#include <leddevice/LedDevice.h>
|
||||
|
||||
// Forward declaration
|
||||
struct CiColorTriangle;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
/// Black color constant.
|
||||
static const CiColor BLACK;
|
||||
|
||||
///
|
||||
/// 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
|
||||
///
|
||||
static CiColor rgbToCiColor(float red, float green, float blue, CiColorTriangle colorSpace);
|
||||
|
||||
///
|
||||
/// @param p the color point to check
|
||||
///
|
||||
/// @return true if the color point is covered by the lamp color space
|
||||
///
|
||||
static bool isPointInLampsReach(CiColor p, CiColorTriangle colorSpace);
|
||||
|
||||
///
|
||||
/// @param p1 point one
|
||||
///
|
||||
/// @param p2 point tow
|
||||
///
|
||||
/// @return the cross product between p1 and p2
|
||||
///
|
||||
static 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
|
||||
///
|
||||
static CiColor getClosestPointToPoint(CiColor a, CiColor b, CiColor p);
|
||||
|
||||
///
|
||||
/// @param p1 point one
|
||||
///
|
||||
/// @param p2 point tow
|
||||
///
|
||||
/// @return the distance between the two points
|
||||
///
|
||||
static float getDistanceBetweenTwoPoints(CiColor p1, CiColor p2);
|
||||
};
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
class PhilipsHueBridge
|
||||
{
|
||||
private:
|
||||
Logger* log;
|
||||
/// QNetworkAccessManager object for sending requests.
|
||||
QNetworkAccessManager* manager;
|
||||
/// Ip address of the bridge
|
||||
QString host;
|
||||
/// User name for the API ("newdeveloper")
|
||||
QString username;
|
||||
|
||||
public:
|
||||
PhilipsHueBridge(Logger* log, QNetworkAccessManager* manager, QString host, QString username) :
|
||||
log(log), manager(manager), host(host), username(username)
|
||||
{
|
||||
}
|
||||
PhilipsHueBridge()
|
||||
{
|
||||
log = NULL;
|
||||
manager = NULL;
|
||||
}
|
||||
|
||||
///
|
||||
/// @param route the route of the GET request.
|
||||
///
|
||||
/// @return the response of the GET request.
|
||||
///
|
||||
QByteArray get(QString route);
|
||||
|
||||
///
|
||||
/// @param route the route of the POST request.
|
||||
///
|
||||
/// @param content the content of the POST request.
|
||||
///
|
||||
void post(QString route, QString content);
|
||||
};
|
||||
|
||||
/**
|
||||
* Simple class to hold the id, the latest color, the color space and the original state.
|
||||
*/
|
||||
class PhilipsHueLight
|
||||
{
|
||||
private:
|
||||
Logger * log;
|
||||
PhilipsHueBridge& bridge;
|
||||
unsigned int id;
|
||||
bool on;
|
||||
unsigned int transitionTime;
|
||||
CiColor color;
|
||||
/// The model id of the hue lamp which is used to determine the color space.
|
||||
QString modelId;
|
||||
CiColorTriangle colorSpace;
|
||||
/// The json string of the original state.
|
||||
QString originalState;
|
||||
|
||||
///
|
||||
/// @param state the state as json object to set
|
||||
///
|
||||
void set(QString state);
|
||||
|
||||
public:
|
||||
// Hue system model ids (http://www.developers.meethue.com/documentation/supported-lights).
|
||||
// Light strips, color iris, ...
|
||||
static const std::set<QString> GAMUT_A_MODEL_IDS;
|
||||
// Hue bulbs, spots, ...
|
||||
static const std::set<QString> GAMUT_B_MODEL_IDS;
|
||||
// Hue Lightstrip plus, go ...
|
||||
static const std::set<QString> GAMUT_C_MODEL_IDS;
|
||||
|
||||
///
|
||||
/// Constructs the light.
|
||||
///
|
||||
/// @param log the logger
|
||||
/// @param bridge the bridge
|
||||
/// @param id the light id
|
||||
///
|
||||
PhilipsHueLight(Logger* log, PhilipsHueBridge& bridge, unsigned int id);
|
||||
~PhilipsHueLight();
|
||||
|
||||
///
|
||||
/// @param on
|
||||
///
|
||||
void setOn(bool on);
|
||||
|
||||
///
|
||||
/// @param transitionTime the transition time between colors in multiples of 100 ms
|
||||
///
|
||||
void setTransitionTime(unsigned int transitionTime);
|
||||
|
||||
///
|
||||
/// @param color the color to set
|
||||
/// @param brightnessFactor the factor to apply to the CiColor#bri value
|
||||
///
|
||||
void setColor(CiColor color, float brightnessFactor = 1.0f);
|
||||
CiColor getColor() const;
|
||||
|
||||
///
|
||||
/// @return the color space of the light determined by the model id reported by the bridge.
|
||||
CiColorTriangle getColorSpace() const;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* 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 LedDevice
|
||||
{
|
||||
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
///
|
||||
/// Constructs specific LedDevice
|
||||
///
|
||||
/// @param deviceConfig json device config
|
||||
///
|
||||
LedDevicePhilipsHue(const QJsonObject &deviceConfig);
|
||||
|
||||
///
|
||||
/// Destructor of this device
|
||||
///
|
||||
virtual ~LedDevicePhilipsHue();
|
||||
|
||||
/// constructs leddevice
|
||||
static LedDevice* construct(const QJsonObject &deviceConfig);
|
||||
|
||||
/// Restores the original state of the leds.
|
||||
virtual int switchOff();
|
||||
|
||||
private slots:
|
||||
/// Restores the status of all lights.
|
||||
void restoreStates();
|
||||
|
||||
protected:
|
||||
///
|
||||
/// Writes the RGB-Color values to the leds.
|
||||
///
|
||||
/// @param[in] ledValues The RGB-color per led
|
||||
///
|
||||
/// @return Zero on success else negative
|
||||
///
|
||||
virtual int write(const std::vector<ColorRgb> & ledValues);
|
||||
bool init(const QJsonObject &deviceConfig);
|
||||
|
||||
private:
|
||||
QNetworkAccessManager* manager;
|
||||
PhilipsHueBridge bridge;
|
||||
/// Use timer to reset lights when we got into "GRABBINGMODE_OFF".
|
||||
QTimer timer;
|
||||
///
|
||||
bool switchOffOnBlack;
|
||||
/// The brightness factor to multiply on color change.
|
||||
float brightnessFactor;
|
||||
/// Transition time in multiples of 100 ms.
|
||||
/// The default of the Hue lights is 400 ms, but we may want it snapier.
|
||||
int transitionTime;
|
||||
/// Array of the light ids.
|
||||
std::vector<unsigned int> lightIds;
|
||||
/// Array to save the lamps.
|
||||
std::vector<PhilipsHueLight> lights;
|
||||
|
||||
///
|
||||
/// Queries the status of all lights and saves it.
|
||||
///
|
||||
/// @param nLights the number of lights
|
||||
///
|
||||
void saveStates(unsigned int nLights);
|
||||
|
||||
///
|
||||
/// @return true if light states have been saved.
|
||||
///
|
||||
bool areStatesSaved();
|
||||
|
||||
};
|
65
libsrc/leddevice/dev_net/LedDeviceTpm2net.cpp
Normal file
65
libsrc/leddevice/dev_net/LedDeviceTpm2net.cpp
Normal file
@@ -0,0 +1,65 @@
|
||||
#include "LedDeviceTpm2net.h"
|
||||
|
||||
LedDeviceTpm2net::LedDeviceTpm2net(const QJsonObject &deviceConfig)
|
||||
: ProviderUdp()
|
||||
{
|
||||
_deviceReady = init(deviceConfig);
|
||||
}
|
||||
|
||||
bool LedDeviceTpm2net::init(const QJsonObject &deviceConfig)
|
||||
{
|
||||
_port = TPM2_DEFAULT_PORT;
|
||||
ProviderUdp::init(deviceConfig);
|
||||
_tpm2_max = deviceConfig["max-packet"].toInt(170);
|
||||
_tpm2ByteCount = 3 * _ledCount;
|
||||
_tpm2TotalPackets = 1 + _tpm2ByteCount / _tpm2_max;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
LedDevice* LedDeviceTpm2net::construct(const QJsonObject &deviceConfig)
|
||||
{
|
||||
return new LedDeviceTpm2net(deviceConfig);
|
||||
}
|
||||
|
||||
|
||||
// populates the headers
|
||||
|
||||
int LedDeviceTpm2net::write(const std::vector<ColorRgb> &ledValues)
|
||||
{
|
||||
uint8_t * tpm2_buffer = (uint8_t*) malloc(_tpm2_max+7);
|
||||
|
||||
int retVal = 0;
|
||||
|
||||
int _thisPacketBytes = 0;
|
||||
_tpm2ThisPacket = 1;
|
||||
|
||||
const uint8_t * rawdata = reinterpret_cast<const uint8_t *>(ledValues.data());
|
||||
|
||||
for (int rawIdx = 0; rawIdx < _tpm2ByteCount; rawIdx++)
|
||||
{
|
||||
if (rawIdx % _tpm2_max == 0) // start of new packet
|
||||
{
|
||||
_thisPacketBytes = (_tpm2ByteCount - rawIdx < _tpm2_max) ? _tpm2ByteCount % _tpm2_max : _tpm2_max;
|
||||
// is this the last packet? ? ^^ last packet : ^^ earlier packets
|
||||
|
||||
tpm2_buffer[0] = 0x9c; // Packet start byte
|
||||
tpm2_buffer[1] = 0xda; // Packet type Data frame
|
||||
tpm2_buffer[2] = (_thisPacketBytes >> 8) & 0xff; // Frame size high
|
||||
tpm2_buffer[3] = _thisPacketBytes & 0xff; // Frame size low
|
||||
tpm2_buffer[4] = _tpm2ThisPacket++; // Packet Number
|
||||
tpm2_buffer[5] = _tpm2TotalPackets; // Number of packets
|
||||
}
|
||||
|
||||
tpm2_buffer [6 + rawIdx%_tpm2_max] = rawdata[rawIdx];
|
||||
|
||||
// is this the last byte of last packet || last byte of other packets
|
||||
if ( (rawIdx == _tpm2ByteCount-1) || (rawIdx %_tpm2_max == _tpm2_max-1) )
|
||||
{
|
||||
tpm2_buffer [6 + rawIdx%_tpm2_max +1] = 0x36; // Packet end byte
|
||||
retVal &= writeBytes(_thisPacketBytes+7, tpm2_buffer);
|
||||
}
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
44
libsrc/leddevice/dev_net/LedDeviceTpm2net.h
Normal file
44
libsrc/leddevice/dev_net/LedDeviceTpm2net.h
Normal file
@@ -0,0 +1,44 @@
|
||||
#pragma once
|
||||
|
||||
// hyperion includes
|
||||
#include "ProviderUdp.h"
|
||||
|
||||
#define TPM2_DEFAULT_PORT 65506
|
||||
|
||||
///
|
||||
/// Implementation of the LedDevice interface for sending led colors via udp tpm2.net packets
|
||||
///
|
||||
class LedDeviceTpm2net : public ProviderUdp
|
||||
{
|
||||
public:
|
||||
///
|
||||
/// Constructs specific LedDevice
|
||||
///
|
||||
/// @param deviceConfig json device config
|
||||
///
|
||||
LedDeviceTpm2net(const QJsonObject &deviceConfig);
|
||||
|
||||
///
|
||||
/// Sets configuration
|
||||
///
|
||||
/// @param deviceConfig the json device config
|
||||
/// @return true if success
|
||||
virtual bool init(const QJsonObject &deviceConfig);
|
||||
|
||||
/// constructs leddevice
|
||||
static LedDevice* construct(const QJsonObject &deviceConfig);
|
||||
|
||||
private:
|
||||
///
|
||||
/// 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);
|
||||
|
||||
int _tpm2_max;
|
||||
int _tpm2ByteCount;
|
||||
int _tpm2TotalPackets;
|
||||
int _tpm2ThisPacket;
|
||||
};
|
93
libsrc/leddevice/dev_net/LedDeviceUdpArtNet.cpp
Normal file
93
libsrc/leddevice/dev_net/LedDeviceUdpArtNet.cpp
Normal file
@@ -0,0 +1,93 @@
|
||||
#include <arpa/inet.h>
|
||||
#include <QHostInfo>
|
||||
|
||||
// hyperion local includes
|
||||
#include "LedDeviceUdpArtNet.h"
|
||||
|
||||
LedDeviceUdpArtNet::LedDeviceUdpArtNet(const QJsonObject &deviceConfig)
|
||||
: ProviderUdp()
|
||||
{
|
||||
_deviceReady = init(deviceConfig);
|
||||
}
|
||||
|
||||
bool LedDeviceUdpArtNet::init(const QJsonObject &deviceConfig)
|
||||
{
|
||||
_port = 6454;
|
||||
ProviderUdp::init(deviceConfig);
|
||||
_artnet_universe = deviceConfig["universe"].toInt(1);
|
||||
_artnet_channelsPerFixture = deviceConfig["channelsPerFixture"].toInt(3);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
LedDevice* LedDeviceUdpArtNet::construct(const QJsonObject &deviceConfig)
|
||||
{
|
||||
return new LedDeviceUdpArtNet(deviceConfig);
|
||||
}
|
||||
|
||||
|
||||
// populates the headers
|
||||
void LedDeviceUdpArtNet::prepare(const unsigned this_universe, const unsigned this_sequence, unsigned this_dmxChannelCount)
|
||||
{
|
||||
// WTF? why do the specs say:
|
||||
// "This value should be an even number in the range 2 – 512. "
|
||||
if (this_dmxChannelCount & 0x1)
|
||||
{
|
||||
this_dmxChannelCount++;
|
||||
}
|
||||
|
||||
memcpy (artnet_packet.ID, "Art-Net\0", 8);
|
||||
|
||||
artnet_packet.OpCode = htons(0x0050); // OpOutput / OpDmx
|
||||
artnet_packet.ProtVer = htons(0x000e);
|
||||
artnet_packet.Sequence = this_sequence;
|
||||
artnet_packet.Physical = 0;
|
||||
artnet_packet.SubUni = this_universe & 0xff ;
|
||||
artnet_packet.Net = (this_universe >> 8) & 0x7f;
|
||||
artnet_packet.Length = htons(this_dmxChannelCount);
|
||||
|
||||
}
|
||||
|
||||
int LedDeviceUdpArtNet::write(const std::vector<ColorRgb> &ledValues)
|
||||
{
|
||||
int retVal = 0;
|
||||
int thisUniverse = _artnet_universe;
|
||||
const uint8_t * rawdata = reinterpret_cast<const uint8_t *>(ledValues.data());
|
||||
|
||||
/*
|
||||
This field is incremented in the range 0x01 to 0xff to allow the receiving node to resequence packets.
|
||||
The Sequence field is set to 0x00 to disable this feature.
|
||||
*/
|
||||
if (_artnet_seq++ == 0)
|
||||
{
|
||||
_artnet_seq = 1;
|
||||
}
|
||||
|
||||
int dmxIdx = 0; // offset into the current dmx packet
|
||||
|
||||
memset(artnet_packet.raw, 0, sizeof(artnet_packet.raw));
|
||||
for (int ledIdx = 0; ledIdx < _ledRGBCount; ledIdx++)
|
||||
{
|
||||
|
||||
artnet_packet.Data[dmxIdx++] = rawdata[ledIdx];
|
||||
if ( (ledIdx % 3 == 2) && (ledIdx > 0) )
|
||||
{
|
||||
dmxIdx += (_artnet_channelsPerFixture-3);
|
||||
}
|
||||
|
||||
// is this the last byte of last packet || last byte of other packets
|
||||
if ( (ledIdx == _ledRGBCount-1) || (dmxIdx >= DMX_MAX) )
|
||||
{
|
||||
prepare(thisUniverse, _artnet_seq, dmxIdx);
|
||||
retVal &= writeBytes(18 + qMin(dmxIdx, DMX_MAX), artnet_packet.raw);
|
||||
|
||||
memset(artnet_packet.raw, 0, sizeof(artnet_packet.raw));
|
||||
thisUniverse ++;
|
||||
dmxIdx = 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
79
libsrc/leddevice/dev_net/LedDeviceUdpArtNet.h
Normal file
79
libsrc/leddevice/dev_net/LedDeviceUdpArtNet.h
Normal file
@@ -0,0 +1,79 @@
|
||||
#pragma once
|
||||
|
||||
// hyperion includes
|
||||
#include "ProviderUdp.h"
|
||||
|
||||
#include <QUuid>
|
||||
|
||||
/**
|
||||
*
|
||||
* This program is provided free for you to use in any way that you wish,
|
||||
* subject to the laws and regulations where you are using it. Due diligence
|
||||
* is strongly suggested before using this code. Please give credit where due.
|
||||
*
|
||||
**/
|
||||
|
||||
#define ArtNet_DEFAULT_PORT 5568
|
||||
|
||||
#define DMX_MAX 512 // 512 usable slots
|
||||
|
||||
// http://stackoverflow.com/questions/16396013/artnet-packet-structure
|
||||
typedef union
|
||||
{
|
||||
struct {
|
||||
char ID[8]; // "Art-Net"
|
||||
uint16_t OpCode; // See Doc. Table 1 - OpCodes eg. 0x5000 OpOutput / OpDmx
|
||||
uint16_t ProtVer; // 0x0e00 (aka 14)
|
||||
uint8_t Sequence; // monotonic counter
|
||||
uint8_t Physical; // 0x00
|
||||
uint8_t SubUni; // low universe (0-255)
|
||||
uint8_t Net; // high universe (not used)
|
||||
uint16_t Length; // data length (2 - 512)
|
||||
uint8_t Data[ DMX_MAX ]; // universe data
|
||||
} __attribute__((packed));
|
||||
|
||||
uint8_t raw[ 18 + DMX_MAX ];
|
||||
|
||||
} artnet_packet_t;
|
||||
|
||||
///
|
||||
/// Implementation of the LedDevice interface for sending led colors via udp/E1.31 packets
|
||||
///
|
||||
class LedDeviceUdpArtNet : public ProviderUdp
|
||||
{
|
||||
public:
|
||||
///
|
||||
/// Constructs specific LedDevice
|
||||
///
|
||||
/// @param deviceConfig json device config
|
||||
///
|
||||
LedDeviceUdpArtNet(const QJsonObject &deviceConfig);
|
||||
|
||||
///
|
||||
/// Sets configuration
|
||||
///
|
||||
/// @param deviceConfig the json device config
|
||||
/// @return true if success
|
||||
bool init(const QJsonObject &deviceConfig);
|
||||
|
||||
/// constructs leddevice
|
||||
static LedDevice* construct(const QJsonObject &deviceConfig);
|
||||
|
||||
|
||||
private:
|
||||
///
|
||||
/// 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);
|
||||
|
||||
void prepare(const unsigned this_universe, const unsigned this_sequence, const unsigned this_dmxChannelCount);
|
||||
|
||||
|
||||
artnet_packet_t artnet_packet;
|
||||
uint8_t _artnet_seq = 1;
|
||||
uint8_t _artnet_channelsPerFixture = 3;
|
||||
unsigned _artnet_universe = 1;
|
||||
};
|
114
libsrc/leddevice/dev_net/LedDeviceUdpE131.cpp
Normal file
114
libsrc/leddevice/dev_net/LedDeviceUdpE131.cpp
Normal file
@@ -0,0 +1,114 @@
|
||||
#include <arpa/inet.h>
|
||||
#include <QHostInfo>
|
||||
|
||||
// hyperion local includes
|
||||
#include "LedDeviceUdpE131.h"
|
||||
|
||||
LedDeviceUdpE131::LedDeviceUdpE131(const QJsonObject &deviceConfig)
|
||||
: ProviderUdp()
|
||||
{
|
||||
_deviceReady = init(deviceConfig);
|
||||
}
|
||||
|
||||
bool LedDeviceUdpE131::init(const QJsonObject &deviceConfig)
|
||||
{
|
||||
_port = 5568;
|
||||
ProviderUdp::init(deviceConfig);
|
||||
_e131_universe = deviceConfig["universe"].toInt(1);
|
||||
_e131_source_name = deviceConfig["source-name"].toString("hyperion on "+QHostInfo::localHostName());
|
||||
QString _json_cid = deviceConfig["cid"].toString("");
|
||||
|
||||
if (_json_cid.isEmpty())
|
||||
{
|
||||
_e131_cid = QUuid::createUuid();
|
||||
Debug( _log, "e131 no cid found, generated %s", QSTRING_CSTR(_e131_cid.toString()));
|
||||
} else {
|
||||
_e131_cid = QUuid(_json_cid);
|
||||
Debug( _log, "e131 cid found, using %s", QSTRING_CSTR(_e131_cid.toString()));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
LedDevice* LedDeviceUdpE131::construct(const QJsonObject &deviceConfig)
|
||||
{
|
||||
return new LedDeviceUdpE131(deviceConfig);
|
||||
}
|
||||
|
||||
|
||||
// populates the headers
|
||||
void LedDeviceUdpE131::prepare(const unsigned this_universe, const unsigned this_dmxChannelCount)
|
||||
{
|
||||
memset(e131_packet.raw, 0, sizeof(e131_packet.raw));
|
||||
|
||||
/* Root Layer */
|
||||
e131_packet.preamble_size = htons(16);
|
||||
e131_packet.postamble_size = 0;
|
||||
memcpy (e131_packet.acn_id, _acn_id, 12);
|
||||
e131_packet.root_flength = htons(0x7000 | (110+this_dmxChannelCount) );
|
||||
e131_packet.root_vector = htonl(VECTOR_ROOT_E131_DATA);
|
||||
memcpy (e131_packet.cid, _e131_cid.toRfc4122().constData() , sizeof(e131_packet.cid) );
|
||||
|
||||
/* Frame Layer */
|
||||
e131_packet.frame_flength = htons(0x7000 | (88+this_dmxChannelCount));
|
||||
e131_packet.frame_vector = htonl(VECTOR_E131_DATA_PACKET);
|
||||
snprintf (e131_packet.source_name, sizeof(e131_packet.source_name), "%s", QSTRING_CSTR(_e131_source_name) );
|
||||
e131_packet.priority = 100;
|
||||
e131_packet.reserved = htons(0);
|
||||
e131_packet.options = 0; // Bit 7 = Preview_Data
|
||||
// Bit 6 = Stream_Terminated
|
||||
// Bit 5 = Force_Synchronization
|
||||
e131_packet.universe = htons(this_universe);
|
||||
|
||||
/* DMX Layer */
|
||||
e131_packet.dmp_flength = htons(0x7000 | (11+this_dmxChannelCount));
|
||||
e131_packet.dmp_vector = VECTOR_DMP_SET_PROPERTY;
|
||||
e131_packet.type = 0xa1;
|
||||
e131_packet.first_address = htons(0);
|
||||
e131_packet.address_increment = htons(1);
|
||||
e131_packet.property_value_count = htons(1+this_dmxChannelCount);
|
||||
|
||||
e131_packet.property_values[0] = 0; // start code
|
||||
}
|
||||
|
||||
int LedDeviceUdpE131::write(const std::vector<ColorRgb> &ledValues)
|
||||
{
|
||||
int retVal = 0;
|
||||
int thisChannelCount = 0;
|
||||
int dmxChannelCount = _ledRGBCount;
|
||||
const uint8_t * rawdata = reinterpret_cast<const uint8_t *>(ledValues.data());
|
||||
|
||||
_e131_seq++;
|
||||
|
||||
for (int rawIdx = 0; rawIdx < dmxChannelCount; rawIdx++)
|
||||
{
|
||||
if (rawIdx % DMX_MAX == 0) // start of new packet
|
||||
{
|
||||
thisChannelCount = (dmxChannelCount - rawIdx < DMX_MAX) ? dmxChannelCount % DMX_MAX : DMX_MAX;
|
||||
// is this the last packet? ? ^^ last packet : ^^ earlier packets
|
||||
|
||||
prepare(_e131_universe + rawIdx / DMX_MAX, thisChannelCount);
|
||||
e131_packet.sequence_number = _e131_seq;
|
||||
}
|
||||
|
||||
e131_packet.property_values[1 + rawIdx%DMX_MAX] = rawdata[rawIdx];
|
||||
|
||||
// is this the last byte of last packet || last byte of other packets
|
||||
if ( (rawIdx == dmxChannelCount-1) || (rawIdx %DMX_MAX == DMX_MAX-1) )
|
||||
{
|
||||
#undef e131debug
|
||||
#if e131debug
|
||||
Debug (_log, "send packet: rawidx %d dmxchannelcount %d universe: %d, packetsz %d"
|
||||
, rawIdx
|
||||
, dmxChannelCount
|
||||
, _e131_universe + rawIdx / DMX_MAX
|
||||
, E131_DMP_DATA + 1 + thisChannelCount
|
||||
);
|
||||
#endif
|
||||
retVal &= writeBytes(E131_DMP_DATA + 1 + thisChannelCount, e131_packet.raw);
|
||||
}
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
138
libsrc/leddevice/dev_net/LedDeviceUdpE131.h
Normal file
138
libsrc/leddevice/dev_net/LedDeviceUdpE131.h
Normal file
@@ -0,0 +1,138 @@
|
||||
#pragma once
|
||||
|
||||
// hyperion includes
|
||||
#include "ProviderUdp.h"
|
||||
|
||||
#include <QUuid>
|
||||
|
||||
/**
|
||||
*
|
||||
* https://raw.githubusercontent.com/forkineye/ESPixelStick/master/_E131.h
|
||||
* Project: E131 - E.131 (sACN) library for Arduino
|
||||
* Copyright (c) 2015 Shelby Merrick
|
||||
* http://www.forkineye.com
|
||||
*
|
||||
* This program is provided free for you to use in any way that you wish,
|
||||
* subject to the laws and regulations where you are using it. Due diligence
|
||||
* is strongly suggested before using this code. Please give credit where due.
|
||||
*
|
||||
**/
|
||||
|
||||
#define E131_DEFAULT_PORT 5568
|
||||
|
||||
/* E1.31 Packet Offsets */
|
||||
#define E131_ROOT_PREAMBLE_SIZE 0
|
||||
#define E131_ROOT_POSTAMBLE_SIZE 2
|
||||
#define E131_ROOT_ID 4
|
||||
#define E131_ROOT_FLENGTH 16
|
||||
#define E131_ROOT_VECTOR 18
|
||||
#define E131_ROOT_CID 22
|
||||
|
||||
#define E131_FRAME_FLENGTH 38
|
||||
#define E131_FRAME_VECTOR 40
|
||||
#define E131_FRAME_SOURCE 44
|
||||
#define E131_FRAME_PRIORITY 108
|
||||
#define E131_FRAME_RESERVED 109
|
||||
#define E131_FRAME_SEQ 111
|
||||
#define E131_FRAME_OPT 112
|
||||
#define E131_FRAME_UNIVERSE 113
|
||||
|
||||
#define E131_DMP_FLENGTH 115
|
||||
#define E131_DMP_VECTOR 117
|
||||
#define E131_DMP_TYPE 118
|
||||
#define E131_DMP_ADDR_FIRST 119
|
||||
#define E131_DMP_ADDR_INC 121
|
||||
#define E131_DMP_COUNT 123
|
||||
#define E131_DMP_DATA 125
|
||||
|
||||
/* E1.31 Packet Structure */
|
||||
typedef union
|
||||
{
|
||||
struct
|
||||
{
|
||||
/* Root Layer */
|
||||
uint16_t preamble_size;
|
||||
uint16_t postamble_size;
|
||||
uint8_t acn_id[12];
|
||||
uint16_t root_flength;
|
||||
uint32_t root_vector;
|
||||
char cid[16];
|
||||
|
||||
/* Frame Layer */
|
||||
uint16_t frame_flength;
|
||||
uint32_t frame_vector;
|
||||
char source_name[64];
|
||||
uint8_t priority;
|
||||
uint16_t reserved;
|
||||
uint8_t sequence_number;
|
||||
uint8_t options;
|
||||
uint16_t universe;
|
||||
|
||||
/* DMP Layer */
|
||||
uint16_t dmp_flength;
|
||||
uint8_t dmp_vector;
|
||||
uint8_t type;
|
||||
uint16_t first_address;
|
||||
uint16_t address_increment;
|
||||
uint16_t property_value_count;
|
||||
uint8_t property_values[513];
|
||||
} __attribute__((packed));
|
||||
|
||||
uint8_t raw[638];
|
||||
} e131_packet_t;
|
||||
|
||||
/* defined parameters from http://tsp.esta.org/tsp/documents/docs/BSR_E1-31-20xx_CP-2014-1009r2.pdf */
|
||||
#define VECTOR_ROOT_E131_DATA 0x00000004
|
||||
#define VECTOR_ROOT_E131_EXTENDED 0x00000008
|
||||
#define VECTOR_DMP_SET_PROPERTY 0x02
|
||||
#define VECTOR_E131_DATA_PACKET 0x00000002
|
||||
#define VECTOR_E131_EXTENDED_SYNCHRONIZATION 0x00000001
|
||||
#define VECTOR_E131_EXTENDED_DISCOVERY 0x00000002
|
||||
#define VECTOR_UNIVERSE_DISCOVERY_UNIVERSE_LIST 0x00000001
|
||||
#define E131_E131_UNIVERSE_DISCOVERY_INTERVAL 10 // seconds
|
||||
#define E131_NETWORK_DATA_LOSS_TIMEOUT 2500 // milli econds
|
||||
#define E131_DISCOVERY_UNIVERSE 64214
|
||||
#define DMX_MAX 512 // 512 usable slots
|
||||
|
||||
///
|
||||
/// Implementation of the LedDevice interface for sending led colors via udp/E1.31 packets
|
||||
///
|
||||
class LedDeviceUdpE131 : public ProviderUdp
|
||||
{
|
||||
public:
|
||||
///
|
||||
/// Constructs specific LedDevice
|
||||
///
|
||||
/// @param deviceConfig json device config
|
||||
///
|
||||
LedDeviceUdpE131(const QJsonObject &deviceConfig);
|
||||
|
||||
///
|
||||
/// Sets configuration
|
||||
///
|
||||
/// @param deviceConfig the json device config
|
||||
/// @return true if success
|
||||
bool init(const QJsonObject &deviceConfig);
|
||||
|
||||
/// constructs leddevice
|
||||
static LedDevice* construct(const QJsonObject &deviceConfig);
|
||||
|
||||
|
||||
private:
|
||||
///
|
||||
/// 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);
|
||||
|
||||
void prepare(const unsigned this_universe, const unsigned this_dmxChannelCount);
|
||||
|
||||
e131_packet_t e131_packet;
|
||||
uint8_t _e131_seq = 0;
|
||||
uint8_t _e131_universe = 1;
|
||||
uint8_t _acn_id[12] = {0x41, 0x53, 0x43, 0x2d, 0x45, 0x31, 0x2e, 0x31, 0x37, 0x00, 0x00, 0x00 };
|
||||
QString _e131_source_name;
|
||||
QUuid _e131_cid;
|
||||
};
|
53
libsrc/leddevice/dev_net/LedDeviceUdpH801.cpp
Normal file
53
libsrc/leddevice/dev_net/LedDeviceUdpH801.cpp
Normal file
@@ -0,0 +1,53 @@
|
||||
#include "LedDeviceUdpH801.h"
|
||||
|
||||
LedDeviceUdpH801::LedDeviceUdpH801(const QJsonObject &deviceConfig)
|
||||
: ProviderUdp()
|
||||
{
|
||||
_deviceReady = init(deviceConfig);
|
||||
}
|
||||
|
||||
bool LedDeviceUdpH801::init(const QJsonObject &deviceConfig)
|
||||
{
|
||||
/* The H801 port is fixed */
|
||||
_latchTime_ms = 10;
|
||||
_port = 30977;
|
||||
_defaultHost = "255.255.255.255";
|
||||
ProviderUdp::init(deviceConfig);
|
||||
|
||||
_ids.clear();
|
||||
QJsonArray lArray = deviceConfig["lightIds"].toArray();
|
||||
for (int i = 0; i < lArray.size(); i++)
|
||||
{
|
||||
QString id = lArray[i].toString();
|
||||
_ids.push_back(id.toInt(nullptr, 16));
|
||||
}
|
||||
|
||||
_message = QByteArray(_prefix_size + _colors + _id_size * _ids.size() + _suffix_size, 0x00);
|
||||
_message[0] = 0xFB;
|
||||
_message[1] = 0xEB;
|
||||
|
||||
for (int i = 0; i < _ids.length(); i++) {
|
||||
_message[_prefix_size + _colors + i * _id_size + 0] = (_ids[i] >> 0x00) & 0xFF;
|
||||
_message[_prefix_size + _colors + i * _id_size + 1] = (_ids[i] >> 0x08) & 0xFF;
|
||||
_message[_prefix_size + _colors + i * _id_size + 2] = (_ids[i] >> 0x10) & 0xFF;
|
||||
}
|
||||
|
||||
Debug(_log, "H801 using %s:%d", _address.toString().toStdString().c_str(), _port);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
LedDevice* LedDeviceUdpH801::construct(const QJsonObject &deviceConfig)
|
||||
{
|
||||
return new LedDeviceUdpH801(deviceConfig);
|
||||
}
|
||||
|
||||
int LedDeviceUdpH801::write(const std::vector<ColorRgb> &ledValues)
|
||||
{
|
||||
ColorRgb color = ledValues[0];
|
||||
_message[_prefix_size + 0] = color.red;
|
||||
_message[_prefix_size + 1] = color.green;
|
||||
_message[_prefix_size + 2] = color.blue;
|
||||
|
||||
return writeBytes(_message.size(), reinterpret_cast<const uint8_t*>(_message.data()));
|
||||
}
|
45
libsrc/leddevice/dev_net/LedDeviceUdpH801.h
Normal file
45
libsrc/leddevice/dev_net/LedDeviceUdpH801.h
Normal file
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
|
||||
// hyperion includes
|
||||
#include "ProviderUdp.h"
|
||||
|
||||
///
|
||||
/// Implementation of the LedDevice interface for sending led colors via udp.
|
||||
///
|
||||
class LedDeviceUdpH801: public ProviderUdp
|
||||
{
|
||||
protected:
|
||||
QList<int> _ids;
|
||||
QByteArray _message;
|
||||
const int _prefix_size = 2;
|
||||
const int _colors = 5;
|
||||
const int _id_size = 3;
|
||||
const int _suffix_size = 1;
|
||||
|
||||
public:
|
||||
///
|
||||
/// Constructs specific LedDevice
|
||||
///
|
||||
/// @param deviceConfig json device config
|
||||
///
|
||||
LedDeviceUdpH801(const QJsonObject &deviceConfig);
|
||||
|
||||
///
|
||||
/// Sets configuration
|
||||
///
|
||||
/// @param deviceConfig the json device config
|
||||
/// @return true if success
|
||||
bool init(const QJsonObject &deviceConfig);
|
||||
|
||||
/// constructs leddevice
|
||||
static LedDevice* construct(const QJsonObject &deviceConfig);
|
||||
|
||||
private:
|
||||
///
|
||||
/// 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);
|
||||
};
|
20
libsrc/leddevice/dev_net/LedDeviceUdpRaw.cpp
Normal file
20
libsrc/leddevice/dev_net/LedDeviceUdpRaw.cpp
Normal file
@@ -0,0 +1,20 @@
|
||||
#include "LedDeviceUdpRaw.h"
|
||||
|
||||
LedDeviceUdpRaw::LedDeviceUdpRaw(const QJsonObject &deviceConfig)
|
||||
: ProviderUdp()
|
||||
{
|
||||
_port = 5568;
|
||||
init(deviceConfig);
|
||||
}
|
||||
|
||||
LedDevice* LedDeviceUdpRaw::construct(const QJsonObject &deviceConfig)
|
||||
{
|
||||
return new LedDeviceUdpRaw(deviceConfig);
|
||||
}
|
||||
|
||||
int LedDeviceUdpRaw::write(const std::vector<ColorRgb> &ledValues)
|
||||
{
|
||||
const uint8_t * dataPtr = reinterpret_cast<const uint8_t *>(ledValues.data());
|
||||
|
||||
return writeBytes((unsigned)_ledRGBCount, dataPtr);
|
||||
}
|
29
libsrc/leddevice/dev_net/LedDeviceUdpRaw.h
Normal file
29
libsrc/leddevice/dev_net/LedDeviceUdpRaw.h
Normal file
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
// hyperion incluse
|
||||
#include "ProviderUdp.h"
|
||||
|
||||
///
|
||||
/// Implementation of the LedDevice interface for sending led colors via udp.
|
||||
///
|
||||
class LedDeviceUdpRaw : public ProviderUdp
|
||||
{
|
||||
public:
|
||||
///
|
||||
/// Constructs specific LedDevice
|
||||
///
|
||||
/// @param deviceConfig json device config
|
||||
///
|
||||
LedDeviceUdpRaw(const QJsonObject &deviceConfig);
|
||||
|
||||
/// constructs leddevice
|
||||
static LedDevice* construct(const QJsonObject &deviceConfig);
|
||||
|
||||
///
|
||||
/// 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);
|
||||
};
|
83
libsrc/leddevice/dev_net/ProviderUdp.cpp
Normal file
83
libsrc/leddevice/dev_net/ProviderUdp.cpp
Normal file
@@ -0,0 +1,83 @@
|
||||
|
||||
// STL includes
|
||||
#include <cstring>
|
||||
#include <cstdio>
|
||||
#include <iostream>
|
||||
#include <exception>
|
||||
// Linux includes
|
||||
#include <fcntl.h>
|
||||
#include <sys/ioctl.h>
|
||||
|
||||
#include <QStringList>
|
||||
#include <QUdpSocket>
|
||||
#include <QHostInfo>
|
||||
|
||||
// Local Hyperion includes
|
||||
#include "ProviderUdp.h"
|
||||
|
||||
ProviderUdp::ProviderUdp()
|
||||
: LedDevice()
|
||||
, _port(1)
|
||||
, _defaultHost("127.0.0.1")
|
||||
{
|
||||
_latchTime_ms = 1;
|
||||
_udpSocket = new QUdpSocket();
|
||||
}
|
||||
|
||||
ProviderUdp::~ProviderUdp()
|
||||
{
|
||||
_udpSocket->close();
|
||||
}
|
||||
|
||||
bool ProviderUdp::init(const QJsonObject &deviceConfig)
|
||||
{
|
||||
LedDevice::init(deviceConfig);
|
||||
|
||||
QString host = deviceConfig["host"].toString(_defaultHost);
|
||||
|
||||
if (_address.setAddress(host) )
|
||||
{
|
||||
Debug( _log, "Successfully parsed %s as an ip address.", deviceConfig["host"].toString().toStdString().c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug( _log, "Failed to parse %s as an ip address.", deviceConfig["host"].toString().toStdString().c_str());
|
||||
QHostInfo info = QHostInfo::fromName(host);
|
||||
if (info.addresses().isEmpty())
|
||||
{
|
||||
Debug( _log, "Failed to parse %s as a hostname.", deviceConfig["host"].toString().toStdString().c_str());
|
||||
throw std::runtime_error("invalid target address");
|
||||
}
|
||||
Debug( _log, "Successfully parsed %s as a hostname.", deviceConfig["host"].toString().toStdString().c_str());
|
||||
_address = info.addresses().first();
|
||||
}
|
||||
|
||||
_port = deviceConfig["port"].toInt(_port);
|
||||
if ( _port<=0 || _port > 65535)
|
||||
{
|
||||
throw std::runtime_error("invalid target port");
|
||||
}
|
||||
|
||||
Debug( _log, "UDP using %s:%d", _address.toString().toStdString().c_str() , _port );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int ProviderUdp::open()
|
||||
{
|
||||
QHostAddress localAddress = QHostAddress::Any;
|
||||
quint16 localPort = 0;
|
||||
|
||||
WarningIf( !_udpSocket->bind(localAddress, localPort), _log, "Could not bind local address: %s", strerror(errno));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ProviderUdp::writeBytes(const unsigned size, const uint8_t * data)
|
||||
{
|
||||
|
||||
qint64 retVal = _udpSocket->writeDatagram((const char *)data,size,_address,_port);
|
||||
WarningIf((retVal<0), _log, "Error sending: %s", strerror(errno));
|
||||
|
||||
return retVal;
|
||||
}
|
56
libsrc/leddevice/dev_net/ProviderUdp.h
Normal file
56
libsrc/leddevice/dev_net/ProviderUdp.h
Normal file
@@ -0,0 +1,56 @@
|
||||
#pragma once
|
||||
|
||||
#include <QUdpSocket>
|
||||
|
||||
// Hyperion includes
|
||||
#include <leddevice/LedDevice.h>
|
||||
#include <utils/Logger.h>
|
||||
|
||||
///
|
||||
/// The ProviderUdp implements an abstract base-class for LedDevices using UDP packets.
|
||||
///
|
||||
class ProviderUdp : public LedDevice
|
||||
{
|
||||
public:
|
||||
///
|
||||
/// Constructs specific LedDevice
|
||||
///
|
||||
ProviderUdp();
|
||||
|
||||
///
|
||||
/// Destructor of the LedDevice; closes the output device if it is open
|
||||
///
|
||||
virtual ~ProviderUdp();
|
||||
|
||||
///
|
||||
/// Sets configuration
|
||||
///
|
||||
/// @param deviceConfig the json device config
|
||||
/// @return true if success
|
||||
virtual bool init(const QJsonObject &deviceConfig);
|
||||
|
||||
///
|
||||
/// Opens and configures the output device
|
||||
///
|
||||
/// @return Zero on succes else negative
|
||||
///
|
||||
int open();
|
||||
|
||||
protected:
|
||||
///
|
||||
/// Writes the given bytes/bits to the SPI-device and sleeps the latch time to ensure that the
|
||||
/// values are latched.
|
||||
///
|
||||
/// @param[in] size The length of the data
|
||||
/// @param[in] data The data
|
||||
///
|
||||
/// @return Zero on succes else negative
|
||||
///
|
||||
int writeBytes(const unsigned size, const uint8_t *data);
|
||||
|
||||
///
|
||||
QUdpSocket * _udpSocket;
|
||||
QHostAddress _address;
|
||||
quint16 _port;
|
||||
QString _defaultHost;
|
||||
};
|
Reference in New Issue
Block a user