mirror of
https://github.com/hyperion-project/hyperion.ng.git
synced 2023-10-10 13:36:59 +02:00
* implement effects included in hyperiond binary * cleanup * remove install of effects dir. People who wants to develop effects has to copy them from github effect params for initial effects can be changed in config permanently and other effect params can be changed via json (currently only temporarily) * fix schema of fadecandy webui fix display of specific led options * add leddevice write support * cleanup * webui: tune hue code * when use json effect definition from putsiede hyperiond but want to use py script from inside hyperiond use ad a : e.g. fade.py needs a fade.py near the json file, but :fade.py is taken from resource inside hyperiond * add ability to di * add abiloty to diable effcts via hyperion config * use effect name instead of script in active effects and prio register * finally solve open file handle during effect is playing. Now script is read before, then file closed and then t is run by python * fix some webui things - led config tabs - inital loading screen optimize qrc file generation fix compile warning in hyperion.cpp * cleanup * more cleanup
1185 lines
35 KiB
C++
1185 lines
35 KiB
C++
// system includes
|
|
#include <stdexcept>
|
|
#include <cassert>
|
|
#include <iomanip>
|
|
|
|
// stl includes
|
|
#include <iostream>
|
|
#include <sstream>
|
|
#include <iterator>
|
|
|
|
// Qt includes
|
|
#include <QResource>
|
|
#include <QDateTime>
|
|
#include <QCryptographicHash>
|
|
#include <QHostInfo>
|
|
#include <QString>
|
|
#include <QFile>
|
|
|
|
// hyperion util includes
|
|
#include <hyperion/ImageProcessorFactory.h>
|
|
#include <hyperion/ImageProcessor.h>
|
|
#include <hyperion/MessageForwarder.h>
|
|
#include <hyperion/ColorTransform.h>
|
|
#include <hyperion/ColorCorrection.h>
|
|
#include <hyperion/ColorAdjustment.h>
|
|
#include <utils/ColorRgb.h>
|
|
#include <leddevice/LedDevice.h>
|
|
#include <HyperionConfig.h>
|
|
#include <utils/jsonschema/JsonFactory.h>
|
|
|
|
// project includes
|
|
#include "JsonClientConnection.h"
|
|
|
|
using namespace hyperion;
|
|
|
|
JsonClientConnection::JsonClientConnection(QTcpSocket *socket)
|
|
: QObject()
|
|
, _socket(socket)
|
|
, _imageProcessor(ImageProcessorFactory::getInstance().newImageProcessor())
|
|
, _hyperion(Hyperion::getInstance())
|
|
, _receiveBuffer()
|
|
, _webSocketHandshakeDone(false)
|
|
, _log(Logger::getInstance("JSONCLIENTCONNECTION"))
|
|
, _forwarder_enabled(true)
|
|
{
|
|
// connect internal signals and slots
|
|
connect(_socket, SIGNAL(disconnected()), this, SLOT(socketClosed()));
|
|
connect(_socket, SIGNAL(readyRead()), this, SLOT(readData()));
|
|
connect(_hyperion, SIGNAL(componentStateChanged(hyperion::Components,bool)), this, SLOT(componentStateChanged(hyperion::Components,bool)));
|
|
|
|
_timer_ledcolors.setSingleShot(false);
|
|
connect(&_timer_ledcolors, SIGNAL(timeout()), this, SLOT(streamLedcolorsUpdate()));
|
|
}
|
|
|
|
|
|
JsonClientConnection::~JsonClientConnection()
|
|
{
|
|
delete _socket;
|
|
}
|
|
|
|
void JsonClientConnection::readData()
|
|
{
|
|
_receiveBuffer += _socket->readAll();
|
|
|
|
if (_webSocketHandshakeDone)
|
|
{
|
|
// 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);
|
|
|
|
// handle message
|
|
handleMessage(message);
|
|
|
|
// 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
|
|
{
|
|
Error(_log, "Someone is sending very big messages over several frames... it's not supported yet");
|
|
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...
|
|
Debug(_log, "Websocket handshake");
|
|
|
|
// 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);
|
|
}
|
|
|
|
void JsonClientConnection::handleMessage(const std::string &messageString)
|
|
{
|
|
Json::Reader reader;
|
|
Json::Value message;
|
|
std::string errors;
|
|
try
|
|
{
|
|
if (!reader.parse(messageString, message, false))
|
|
{
|
|
sendErrorReply("Error while parsing json: " + reader.getFormattedErrorMessages());
|
|
return;
|
|
}
|
|
|
|
// check basic message
|
|
if (!checkJson(message, ":schema", errors))
|
|
{
|
|
sendErrorReply("Error while validating json: " + errors);
|
|
return;
|
|
}
|
|
|
|
// check specific message
|
|
const std::string command = message["command"].asString();
|
|
if (!checkJson(message, QString(":schema-%1").arg(QString::fromStdString(command)), errors))
|
|
{
|
|
sendErrorReply("Error while validating json: " + errors);
|
|
return;
|
|
}
|
|
|
|
int tan = message.get("tan",0).asInt();
|
|
// switch over all possible commands and handle them
|
|
if (command == "color")
|
|
handleColorCommand(message, command, tan);
|
|
else if (command == "image")
|
|
handleImageCommand(message, command, tan);
|
|
else if (command == "effect")
|
|
handleEffectCommand(message, command, tan);
|
|
else if (command == "serverinfo")
|
|
handleServerInfoCommand(message, command, tan);
|
|
else if (command == "clear")
|
|
handleClearCommand(message, command, tan);
|
|
else if (command == "clearall")
|
|
handleClearallCommand(message, command, tan);
|
|
else if (command == "transform")
|
|
handleTransformCommand(message, command, tan);
|
|
else if (command == "temperature")
|
|
handleTemperatureCommand(message, command, tan);
|
|
else if (command == "adjustment")
|
|
handleAdjustmentCommand(message, command, tan);
|
|
else if (command == "sourceselect")
|
|
handleSourceSelectCommand(message, command, tan);
|
|
else if (command == "config")
|
|
handleConfigCommand(message, command, tan);
|
|
else if (command == "componentstate")
|
|
handleComponentStateCommand(message, command, tan);
|
|
else if (command == "ledcolors")
|
|
handleLedColorsCommand(message, command, tan);
|
|
else
|
|
handleNotImplemented();
|
|
}
|
|
catch (std::exception& e)
|
|
{
|
|
sendErrorReply("Error while processing incoming json message: " + std::string(e.what()) + " " + errors );
|
|
Warning(_log, "Error while processing incoming json message: %s (%s)", e.what(), errors.c_str());
|
|
}
|
|
|
|
}
|
|
|
|
void JsonClientConnection::componentStateChanged(const hyperion::Components component, bool enable)
|
|
{
|
|
if (component == COMP_FORWARDER && _forwarder_enabled != enable)
|
|
{
|
|
_forwarder_enabled = enable;
|
|
Info(_log, "forwarder change state to %s", (enable ? "enabled" : "disabled") );
|
|
}
|
|
}
|
|
|
|
void JsonClientConnection::forwardJsonMessage(const Json::Value & message)
|
|
{
|
|
if (_forwarder_enabled)
|
|
{
|
|
QTcpSocket client;
|
|
QList<MessageForwarder::JsonSlaveAddress> list = _hyperion->getForwarder()->getJsonSlaves();
|
|
|
|
for ( int i=0; i<list.size(); i++ )
|
|
{
|
|
client.connectToHost(list.at(i).addr, list.at(i).port);
|
|
if ( client.waitForConnected(500) )
|
|
{
|
|
sendMessage(message,&client);
|
|
client.close();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void JsonClientConnection::handleColorCommand(const Json::Value &message, const std::string &command, const int tan)
|
|
{
|
|
forwardJsonMessage(message);
|
|
|
|
// extract parameters
|
|
int priority = message["priority"].asInt();
|
|
int duration = message.get("duration", -1).asInt();
|
|
|
|
std::vector<ColorRgb> colorData(_hyperion->getLedCount());
|
|
const Json::Value & jsonColor = message["color"];
|
|
Json::UInt i = 0;
|
|
for (; i < jsonColor.size()/3 && i < _hyperion->getLedCount(); ++i)
|
|
{
|
|
colorData[i].red = uint8_t(message["color"][3u*i].asInt());
|
|
colorData[i].green = uint8_t(message["color"][3u*i+1u].asInt());
|
|
colorData[i].blue = uint8_t(message["color"][3u*i+2u].asInt());
|
|
}
|
|
|
|
// copy full blocks of led colors
|
|
unsigned size = i;
|
|
while (i + size < _hyperion->getLedCount())
|
|
{
|
|
memcpy(&(colorData[i]), colorData.data(), size * sizeof(ColorRgb));
|
|
i += size;
|
|
}
|
|
|
|
// copy remaining block of led colors
|
|
if (i < _hyperion->getLedCount())
|
|
{
|
|
memcpy(&(colorData[i]), colorData.data(), (_hyperion->getLedCount()-i) * sizeof(ColorRgb));
|
|
}
|
|
|
|
// set output
|
|
_hyperion->setColors(priority, colorData, duration);
|
|
|
|
// send reply
|
|
sendSuccessReply(command, tan);
|
|
}
|
|
|
|
void JsonClientConnection::handleImageCommand(const Json::Value &message, const std::string &command, const int tan)
|
|
{
|
|
forwardJsonMessage(message);
|
|
|
|
// extract parameters
|
|
int priority = message["priority"].asInt();
|
|
int duration = message.get("duration", -1).asInt();
|
|
int width = message["imagewidth"].asInt();
|
|
int height = message["imageheight"].asInt();
|
|
QByteArray data = QByteArray::fromBase64(QByteArray(message["imagedata"].asCString()));
|
|
|
|
// check consistency of the size of the received data
|
|
if (data.size() != width*height*3)
|
|
{
|
|
sendErrorReply("Size of image data does not match with the width and height", command, tan);
|
|
return;
|
|
}
|
|
|
|
// set width and height of the image processor
|
|
_imageProcessor->setSize(width, height);
|
|
|
|
// create ImageRgb
|
|
Image<ColorRgb> image(width, height);
|
|
memcpy(image.memptr(), data.data(), data.size());
|
|
|
|
// process the image
|
|
std::vector<ColorRgb> ledColors = _imageProcessor->process(image);
|
|
_hyperion->setColors(priority, ledColors, duration);
|
|
|
|
// send reply
|
|
sendSuccessReply(command, tan);
|
|
}
|
|
|
|
void JsonClientConnection::handleEffectCommand(const Json::Value &message, const std::string &command, const int tan)
|
|
{
|
|
forwardJsonMessage(message);
|
|
|
|
// extract parameters
|
|
int priority = message["priority"].asInt();
|
|
int duration = message.get("duration", -1).asInt();
|
|
const Json::Value & effect = message["effect"];
|
|
const std::string & effectName = effect["name"].asString();
|
|
|
|
// set output
|
|
if (effect.isMember("args"))
|
|
{
|
|
_hyperion->setEffect(effectName, effect["args"], priority, duration);
|
|
}
|
|
else
|
|
{
|
|
_hyperion->setEffect(effectName, priority, duration);
|
|
}
|
|
|
|
// send reply
|
|
sendSuccessReply(command, tan);
|
|
}
|
|
|
|
void JsonClientConnection::handleServerInfoCommand(const Json::Value &, const std::string &command, const int tan)
|
|
{
|
|
// create result
|
|
Json::Value result;
|
|
result["success"] = true;
|
|
result["command"] = command;
|
|
result["tan"] = tan;
|
|
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);
|
|
uint64_t now = QDateTime::currentMSecsSinceEpoch();
|
|
QList<int> activePriorities = _hyperion->getActivePriorities();
|
|
Hyperion::PriorityRegister priorityRegister = _hyperion->getPriorityRegister();
|
|
int currentPriority = _hyperion->getCurrentPriority();
|
|
foreach (int priority, activePriorities) {
|
|
const Hyperion::InputInfo & priorityInfo = _hyperion->getPriorityInfo(priority);
|
|
Json::Value & item = priorities[priorities.size()];
|
|
item["priority"] = priority;
|
|
if (priorityInfo.timeoutTime_ms != -1)
|
|
{
|
|
item["duration_ms"] = Json::Value::UInt(priorityInfo.timeoutTime_ms - now);
|
|
}
|
|
|
|
item["owner"] = "unknown";
|
|
item["active"] = true;
|
|
item["visible"] = (priority == currentPriority);
|
|
foreach(auto const &entry, priorityRegister)
|
|
{
|
|
if (entry.second == priority)
|
|
{
|
|
item["owner"] = entry.first;
|
|
priorityRegister.erase(entry.first);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
foreach(auto const &entry, priorityRegister)
|
|
{
|
|
Json::Value & item = priorities[priorities.size()];
|
|
item["priority"] = entry.second;
|
|
item["active"] = false;
|
|
item["visible"] = false;
|
|
item["owner"] = entry.first;
|
|
}
|
|
|
|
// collect temperature correction information
|
|
Json::Value & temperatureArray = info["temperature"];
|
|
for (const std::string& tempId : _hyperion->getTemperatureIds())
|
|
{
|
|
const ColorCorrection * colorTemp = _hyperion->getTemperature(tempId);
|
|
if (colorTemp == nullptr)
|
|
{
|
|
Error(_log, "Incorrect color temperature correction id: %s", tempId.c_str());
|
|
continue;
|
|
}
|
|
|
|
Json::Value & temperature = temperatureArray.append(Json::Value());
|
|
temperature["id"] = tempId;
|
|
|
|
Json::Value & tempValues = temperature["correctionValues"];
|
|
tempValues.append(colorTemp->_rgbCorrection.getAdjustmentR());
|
|
tempValues.append(colorTemp->_rgbCorrection.getAdjustmentG());
|
|
tempValues.append(colorTemp->_rgbCorrection.getAdjustmentB());
|
|
}
|
|
|
|
|
|
// collect transform information
|
|
Json::Value & transformArray = info["transform"];
|
|
for (const std::string& transformId : _hyperion->getTransformIds())
|
|
{
|
|
const ColorTransform * colorTransform = _hyperion->getTransform(transformId);
|
|
if (colorTransform == nullptr)
|
|
{
|
|
Error(_log, "Incorrect color transform id: %s", transformId.c_str());
|
|
continue;
|
|
}
|
|
|
|
Json::Value & transform = transformArray.append(Json::Value());
|
|
transform["id"] = transformId;
|
|
|
|
transform["saturationGain"] = colorTransform->_hsvTransform.getSaturationGain();
|
|
transform["valueGain"] = colorTransform->_hsvTransform.getValueGain();
|
|
transform["saturationLGain"] = colorTransform->_hslTransform.getSaturationGain();
|
|
transform["luminanceGain"] = colorTransform->_hslTransform.getLuminanceGain();
|
|
transform["luminanceMinimum"] = colorTransform->_hslTransform.getLuminanceMinimum();
|
|
|
|
Json::Value & threshold = transform["threshold"];
|
|
threshold.append(colorTransform->_rgbRedTransform.getThreshold());
|
|
threshold.append(colorTransform->_rgbGreenTransform.getThreshold());
|
|
threshold.append(colorTransform->_rgbBlueTransform.getThreshold());
|
|
Json::Value & gamma = transform["gamma"];
|
|
gamma.append(colorTransform->_rgbRedTransform.getGamma());
|
|
gamma.append(colorTransform->_rgbGreenTransform.getGamma());
|
|
gamma.append(colorTransform->_rgbBlueTransform.getGamma());
|
|
Json::Value & blacklevel = transform["blacklevel"];
|
|
blacklevel.append(colorTransform->_rgbRedTransform.getBlacklevel());
|
|
blacklevel.append(colorTransform->_rgbGreenTransform.getBlacklevel());
|
|
blacklevel.append(colorTransform->_rgbBlueTransform.getBlacklevel());
|
|
Json::Value & whitelevel = transform["whitelevel"];
|
|
whitelevel.append(colorTransform->_rgbRedTransform.getWhitelevel());
|
|
whitelevel.append(colorTransform->_rgbGreenTransform.getWhitelevel());
|
|
whitelevel.append(colorTransform->_rgbBlueTransform.getWhitelevel());
|
|
}
|
|
|
|
// collect adjustment information
|
|
Json::Value & adjustmentArray = info["adjustment"];
|
|
for (const std::string& adjustmentId : _hyperion->getAdjustmentIds())
|
|
{
|
|
const ColorAdjustment * colorAdjustment = _hyperion->getAdjustment(adjustmentId);
|
|
if (colorAdjustment == nullptr)
|
|
{
|
|
Error(_log, "Incorrect color adjustment id: %s", adjustmentId.c_str());
|
|
continue;
|
|
}
|
|
|
|
Json::Value & adjustment = adjustmentArray.append(Json::Value());
|
|
adjustment["id"] = adjustmentId;
|
|
|
|
Json::Value & redAdjust = adjustment["redAdjust"];
|
|
redAdjust.append(colorAdjustment->_rgbRedAdjustment.getAdjustmentR());
|
|
redAdjust.append(colorAdjustment->_rgbRedAdjustment.getAdjustmentG());
|
|
redAdjust.append(colorAdjustment->_rgbRedAdjustment.getAdjustmentB());
|
|
Json::Value & greenAdjust = adjustment["greenAdjust"];
|
|
greenAdjust.append(colorAdjustment->_rgbGreenAdjustment.getAdjustmentR());
|
|
greenAdjust.append(colorAdjustment->_rgbGreenAdjustment.getAdjustmentG());
|
|
greenAdjust.append(colorAdjustment->_rgbGreenAdjustment.getAdjustmentB());
|
|
Json::Value & blueAdjust = adjustment["blueAdjust"];
|
|
blueAdjust.append(colorAdjustment->_rgbBlueAdjustment.getAdjustmentR());
|
|
blueAdjust.append(colorAdjustment->_rgbBlueAdjustment.getAdjustmentG());
|
|
blueAdjust.append(colorAdjustment->_rgbBlueAdjustment.getAdjustmentB());
|
|
}
|
|
|
|
// collect effect info
|
|
Json::Value & effects = info["effects"] = Json::Value(Json::arrayValue);
|
|
const std::list<EffectDefinition> & effectsDefinitions = _hyperion->getEffects();
|
|
for (const EffectDefinition & effectDefinition : effectsDefinitions)
|
|
{
|
|
Json::Value effect;
|
|
effect["name"] = effectDefinition.name;
|
|
effect["script"] = effectDefinition.script;
|
|
effect["args"] = effectDefinition.args;
|
|
|
|
effects.append(effect);
|
|
}
|
|
|
|
// collect active effect info
|
|
Json::Value & activeEffects = info["activeEffects"] = Json::Value(Json::arrayValue);
|
|
const std::list<ActiveEffectDefinition> & activeEffectsDefinitions = _hyperion->getActiveEffects();
|
|
for (const ActiveEffectDefinition & activeEffectDefinition : activeEffectsDefinitions)
|
|
{
|
|
if (activeEffectDefinition.priority != PriorityMuxer::LOWEST_PRIORITY -1)
|
|
{
|
|
Json::Value activeEffect;
|
|
activeEffect["script"] = activeEffectDefinition.script;
|
|
activeEffect["name"] = activeEffectDefinition.name;
|
|
activeEffect["priority"] = activeEffectDefinition.priority;
|
|
activeEffect["timeout"] = activeEffectDefinition.timeout;
|
|
activeEffect["args"] = activeEffectDefinition.args;
|
|
|
|
activeEffects.append(activeEffect);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////
|
|
// collect active static led color//
|
|
////////////////////////////////////
|
|
|
|
// create New JSON Array Value "activeLEDColor"
|
|
Json::Value & activeLedColors = info["activeLedColor"] = Json::Value(Json::arrayValue);
|
|
// get current Priority from Hyperion Muxer
|
|
const Hyperion::InputInfo & priorityInfo = _hyperion->getPriorityInfo(_hyperion->getCurrentPriority());
|
|
// check if current Priority exist
|
|
if (priorityInfo.priority != std::numeric_limits<int>::max())
|
|
{
|
|
Json::Value LEDcolor;
|
|
// check if all LEDs has the same Color
|
|
if (std::all_of(priorityInfo.ledColors.begin(), priorityInfo.ledColors.end(), [&](ColorRgb color)
|
|
{
|
|
return ((color.red == priorityInfo.ledColors.begin()->red) &&
|
|
(color.green == priorityInfo.ledColors.begin()->green) &&
|
|
(color.blue == priorityInfo.ledColors.begin()->blue));
|
|
} ))
|
|
{
|
|
// check if LED Color not Black (0,0,0)
|
|
if ((priorityInfo.ledColors.begin()->red +
|
|
priorityInfo.ledColors.begin()->green +
|
|
priorityInfo.ledColors.begin()->blue != 0))
|
|
{
|
|
// add RGB Value to Array
|
|
LEDcolor["RGB Value"].append(priorityInfo.ledColors.begin()->red);
|
|
LEDcolor["RGB Value"].append(priorityInfo.ledColors.begin()->green);
|
|
LEDcolor["RGB Value"].append(priorityInfo.ledColors.begin()->blue);
|
|
|
|
uint16_t Hue;
|
|
float Saturation, Luminace;
|
|
|
|
// add HSL Value to Array
|
|
HslTransform::rgb2hsl(priorityInfo.ledColors.begin()->red,
|
|
priorityInfo.ledColors.begin()->green,
|
|
priorityInfo.ledColors.begin()->blue,
|
|
Hue, Saturation, Luminace);
|
|
|
|
LEDcolor["HSL Value"].append(Hue);
|
|
LEDcolor["HSL Value"].append(Saturation);
|
|
LEDcolor["HSL Value"].append(Luminace);
|
|
|
|
// add HEX Value to Array
|
|
std::stringstream hex;
|
|
hex << "0x"
|
|
<< std::uppercase << std::setw(2) << std::setfill('0')
|
|
<< std::hex << unsigned(priorityInfo.ledColors.begin()->red)
|
|
<< std::uppercase << std::setw(2) << std::setfill('0')
|
|
<< std::hex << unsigned(priorityInfo.ledColors.begin()->green)
|
|
<< std::uppercase << std::setw(2) << std::setfill('0')
|
|
<< std::hex << unsigned(priorityInfo.ledColors.begin()->blue);
|
|
|
|
LEDcolor["HEX Value"].append(hex.str());
|
|
|
|
activeLedColors.append(LEDcolor);
|
|
}
|
|
}
|
|
}
|
|
|
|
// get available led devices
|
|
info["ledDevices"]["active"] = LedDevice::activeDevice();
|
|
info["ledDevices"]["available"] = Json::Value(Json::arrayValue);
|
|
for ( auto dev: LedDevice::getDeviceMap())
|
|
{
|
|
info["ledDevices"]["available"].append(dev.first);
|
|
}
|
|
|
|
// get components
|
|
info["components"] = Json::Value(Json::arrayValue);
|
|
std::map<hyperion::Components, bool> components = _hyperion->getComponentRegister().getRegister();
|
|
for(auto comp : components)
|
|
{
|
|
Json::Value item;
|
|
item["id"] = comp.first;
|
|
item["name"] = hyperion::componentToIdString(comp.first);
|
|
item["title"] = hyperion::componentToString(comp.first);
|
|
item["enabled"] = comp.second;
|
|
info["components"].append(item);
|
|
}
|
|
|
|
// Add Hyperion Version, build time
|
|
//Json::Value & version =
|
|
info["hyperion"] = Json::Value(Json::arrayValue);
|
|
Json::Value ver;
|
|
ver["jsonrpc_version"] = HYPERION_JSON_VERSION;
|
|
ver["version"] = HYPERION_VERSION;
|
|
ver["build"] = HYPERION_BUILD_ID;
|
|
ver["time"] = __DATE__ " " __TIME__;
|
|
|
|
info["hyperion"].append(ver);
|
|
|
|
// send the result
|
|
sendMessage(result);
|
|
}
|
|
|
|
void JsonClientConnection::handleClearCommand(const Json::Value &message, const std::string &command, const int tan)
|
|
{
|
|
forwardJsonMessage(message);
|
|
|
|
// extract parameters
|
|
int priority = message["priority"].asInt();
|
|
|
|
// clear priority
|
|
_hyperion->clear(priority);
|
|
|
|
// send reply
|
|
sendSuccessReply(command, tan);
|
|
}
|
|
|
|
void JsonClientConnection::handleClearallCommand(const Json::Value & message, const std::string &command, const int tan)
|
|
{
|
|
forwardJsonMessage(message);
|
|
|
|
// clear priority
|
|
_hyperion->clearall();
|
|
|
|
// send reply
|
|
sendSuccessReply(command, tan);
|
|
}
|
|
|
|
void JsonClientConnection::handleTransformCommand(const Json::Value &message, const std::string &command, const int tan)
|
|
{
|
|
const Json::Value & transform = message["transform"];
|
|
|
|
const std::string transformId = transform.get("id", _hyperion->getTransformIds().front()).asString();
|
|
ColorTransform * colorTransform = _hyperion->getTransform(transformId);
|
|
if (colorTransform == nullptr)
|
|
{
|
|
//sendErrorReply(std::string("Incorrect transform identifier: ") + transformId);
|
|
return;
|
|
}
|
|
|
|
if (transform.isMember("saturationGain"))
|
|
{
|
|
colorTransform->_hsvTransform.setSaturationGain(transform["saturationGain"].asDouble());
|
|
}
|
|
|
|
if (transform.isMember("valueGain"))
|
|
{
|
|
colorTransform->_hsvTransform.setValueGain(transform["valueGain"].asDouble());
|
|
}
|
|
|
|
if (transform.isMember("saturationLGain"))
|
|
{
|
|
colorTransform->_hslTransform.setSaturationGain(transform["saturationLGain"].asDouble());
|
|
}
|
|
|
|
if (transform.isMember("luminanceGain"))
|
|
{
|
|
colorTransform->_hslTransform.setLuminanceGain(transform["luminanceGain"].asDouble());
|
|
}
|
|
|
|
if (transform.isMember("luminanceMinimum"))
|
|
{
|
|
colorTransform->_hslTransform.setLuminanceMinimum(transform["luminanceMinimum"].asDouble());
|
|
}
|
|
|
|
if (transform.isMember("threshold"))
|
|
{
|
|
const Json::Value & values = transform["threshold"];
|
|
colorTransform->_rgbRedTransform .setThreshold(values[0u].asDouble());
|
|
colorTransform->_rgbGreenTransform.setThreshold(values[1u].asDouble());
|
|
colorTransform->_rgbBlueTransform .setThreshold(values[2u].asDouble());
|
|
}
|
|
|
|
if (transform.isMember("gamma"))
|
|
{
|
|
const Json::Value & values = transform["gamma"];
|
|
colorTransform->_rgbRedTransform .setGamma(values[0u].asDouble());
|
|
colorTransform->_rgbGreenTransform.setGamma(values[1u].asDouble());
|
|
colorTransform->_rgbBlueTransform .setGamma(values[2u].asDouble());
|
|
}
|
|
|
|
if (transform.isMember("blacklevel"))
|
|
{
|
|
const Json::Value & values = transform["blacklevel"];
|
|
colorTransform->_rgbRedTransform .setBlacklevel(values[0u].asDouble());
|
|
colorTransform->_rgbGreenTransform.setBlacklevel(values[1u].asDouble());
|
|
colorTransform->_rgbBlueTransform .setBlacklevel(values[2u].asDouble());
|
|
}
|
|
|
|
if (transform.isMember("whitelevel"))
|
|
{
|
|
const Json::Value & values = transform["whitelevel"];
|
|
colorTransform->_rgbRedTransform .setWhitelevel(values[0u].asDouble());
|
|
colorTransform->_rgbGreenTransform.setWhitelevel(values[1u].asDouble());
|
|
colorTransform->_rgbBlueTransform .setWhitelevel(values[2u].asDouble());
|
|
}
|
|
|
|
// commit the changes
|
|
_hyperion->transformsUpdated();
|
|
|
|
sendSuccessReply(command, tan);
|
|
}
|
|
|
|
|
|
void JsonClientConnection::handleTemperatureCommand(const Json::Value &message, const std::string &command, const int tan)
|
|
{
|
|
const Json::Value & temperature = message["temperature"];
|
|
|
|
const std::string tempId = temperature.get("id", _hyperion->getTemperatureIds().front()).asString();
|
|
ColorCorrection * colorTemperature = _hyperion->getTemperature(tempId);
|
|
if (colorTemperature == nullptr)
|
|
{
|
|
//sendErrorReply(std::string("Incorrect temperature identifier: ") + tempId);
|
|
return;
|
|
}
|
|
|
|
if (temperature.isMember("correctionValues"))
|
|
{
|
|
const Json::Value & values = temperature["correctionValues"];
|
|
colorTemperature->_rgbCorrection.setAdjustmentR(values[0u].asInt());
|
|
colorTemperature->_rgbCorrection.setAdjustmentG(values[1u].asInt());
|
|
colorTemperature->_rgbCorrection.setAdjustmentB(values[2u].asInt());
|
|
}
|
|
|
|
// commit the changes
|
|
_hyperion->temperaturesUpdated();
|
|
|
|
sendSuccessReply(command, tan);
|
|
}
|
|
|
|
void JsonClientConnection::handleAdjustmentCommand(const Json::Value &message, const std::string &command, const int tan)
|
|
{
|
|
const Json::Value & adjustment = message["adjustment"];
|
|
|
|
const std::string adjustmentId = adjustment.get("id", _hyperion->getAdjustmentIds().front()).asString();
|
|
ColorAdjustment * colorAdjustment = _hyperion->getAdjustment(adjustmentId);
|
|
if (colorAdjustment == nullptr)
|
|
{
|
|
//sendErrorReply(std::string("Incorrect transform identifier: ") + transformId);
|
|
return;
|
|
}
|
|
|
|
if (adjustment.isMember("redAdjust"))
|
|
{
|
|
const Json::Value & values = adjustment["redAdjust"];
|
|
colorAdjustment->_rgbRedAdjustment.setAdjustmentR(values[0u].asInt());
|
|
colorAdjustment->_rgbRedAdjustment.setAdjustmentG(values[1u].asInt());
|
|
colorAdjustment->_rgbRedAdjustment.setAdjustmentB(values[2u].asInt());
|
|
}
|
|
|
|
if (adjustment.isMember("greenAdjust"))
|
|
{
|
|
const Json::Value & values = adjustment["greenAdjust"];
|
|
colorAdjustment->_rgbGreenAdjustment.setAdjustmentR(values[0u].asInt());
|
|
colorAdjustment->_rgbGreenAdjustment.setAdjustmentG(values[1u].asInt());
|
|
colorAdjustment->_rgbGreenAdjustment.setAdjustmentB(values[2u].asInt());
|
|
}
|
|
|
|
if (adjustment.isMember("blueAdjust"))
|
|
{
|
|
const Json::Value & values = adjustment["blueAdjust"];
|
|
colorAdjustment->_rgbBlueAdjustment.setAdjustmentR(values[0u].asInt());
|
|
colorAdjustment->_rgbBlueAdjustment.setAdjustmentG(values[1u].asInt());
|
|
colorAdjustment->_rgbBlueAdjustment.setAdjustmentB(values[2u].asInt());
|
|
}
|
|
// commit the changes
|
|
_hyperion->adjustmentsUpdated();
|
|
|
|
sendSuccessReply(command, tan);
|
|
}
|
|
|
|
void JsonClientConnection::handleSourceSelectCommand(const Json::Value & message, const std::string &command, const int tan)
|
|
{
|
|
bool success = false;
|
|
if (message.get("auto",false).asBool())
|
|
{
|
|
_hyperion->setSourceAutoSelectEnabled(true);
|
|
success = true;
|
|
}
|
|
else if (message.isMember("priority"))
|
|
{
|
|
success = _hyperion->setCurrentSourcePriority(message["priority"].asInt());
|
|
}
|
|
|
|
if (success)
|
|
{
|
|
sendSuccessReply(command, tan);
|
|
}
|
|
else
|
|
{
|
|
sendErrorReply("setting current priority failed", command, tan);
|
|
}
|
|
}
|
|
|
|
void JsonClientConnection::handleConfigCommand(const Json::Value & message, const std::string &command, const int tan)
|
|
{
|
|
std::string subcommand = message.get("subcommand","").asString();
|
|
std::string full_command = command + "-" + subcommand;
|
|
if (subcommand == "getschema")
|
|
{
|
|
handleSchemaGetCommand(message, full_command, tan);
|
|
}
|
|
else if (subcommand == "getconfig")
|
|
{
|
|
handleConfigGetCommand(message, full_command, tan);
|
|
}
|
|
else if (subcommand == "setconfig")
|
|
{
|
|
handleConfigSetCommand(message, full_command, tan);
|
|
}
|
|
else
|
|
{
|
|
sendErrorReply("unknown or missing subcommand", full_command, tan);
|
|
}
|
|
}
|
|
|
|
void JsonClientConnection::handleConfigGetCommand(const Json::Value & message, const std::string &command, const int tan)
|
|
{
|
|
// create result
|
|
Json::Value result;
|
|
result["success"] = true;
|
|
result["command"] = command;
|
|
result["tan"] = tan;
|
|
Json::Value & config = result["result"];
|
|
config = _hyperion->getJsonConfig();
|
|
|
|
// send the result
|
|
sendMessage(result);
|
|
}
|
|
|
|
void JsonClientConnection::handleSchemaGetCommand(const Json::Value & message, const std::string &command, const int tan)
|
|
{
|
|
// create result
|
|
Json::Value result;
|
|
result["success"] = true;
|
|
result["command"] = command;
|
|
result["tan"] = tan;
|
|
Json::Value & schemaJson = result["result"];
|
|
|
|
|
|
|
|
// make sure the resources are loaded (they may be left out after static linking)
|
|
Q_INIT_RESOURCE(resource);
|
|
|
|
// read the json schema from the resource
|
|
QResource schemaData(":/hyperion-schema");
|
|
assert(schemaData.isValid());
|
|
|
|
Json::Reader jsonReader;
|
|
if (!jsonReader.parse(reinterpret_cast<const char *>(schemaData.data()), reinterpret_cast<const char *>(schemaData.data()) + schemaData.size(), schemaJson, false))
|
|
{
|
|
throw std::runtime_error("ERROR: Json schema wrong: " + jsonReader.getFormattedErrorMessages()) ;
|
|
}
|
|
result["result"]["properties"]["alldevices"] = LedDevice::getLedDeviceSchemas();
|
|
|
|
// send the result
|
|
sendMessage(result);
|
|
}
|
|
|
|
void JsonClientConnection::handleConfigSetCommand(const Json::Value &message, const std::string &command, const int tan)
|
|
{
|
|
struct nested
|
|
{
|
|
static void configSetCommand(const Json::Value& message, Json::Value& config, bool& create)
|
|
{
|
|
if (!config.isObject() || !message.isObject())
|
|
return;
|
|
|
|
for (const auto& key : message.getMemberNames()) {
|
|
if ((config.isObject() && config.isMember(key)) || create)
|
|
{
|
|
if (config[key].type() == Json::objectValue && message[key].type() == Json::objectValue)
|
|
{
|
|
configSetCommand(message[key], config[key], create);
|
|
}
|
|
else
|
|
if ( !config[key].empty() || create)
|
|
config[key] = message[key];
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
if(message.size() > 0)
|
|
{
|
|
if (message.isObject() && message.isMember("config"))
|
|
{
|
|
std::string errors;
|
|
if (!checkJson(message["config"], ":/hyperion-schema", errors, true))
|
|
{
|
|
sendErrorReply("Error while validating json: " + errors, command, tan);
|
|
return;
|
|
}
|
|
|
|
bool createKey = message["create"].asBool();
|
|
Json::Value hyperionConfig;
|
|
message["overwrite"].asBool() ? createKey = true : hyperionConfig = _hyperion->getJsonConfig();
|
|
nested::configSetCommand(message["config"], hyperionConfig, createKey);
|
|
|
|
JsonFactory::writeJson(_hyperion->getConfigFileName(), hyperionConfig);
|
|
|
|
sendSuccessReply(command, tan);
|
|
}
|
|
} else
|
|
sendErrorReply("Error while parsing json: Message size " + std::to_string(message.size()), command, tan);
|
|
}
|
|
|
|
void JsonClientConnection::handleComponentStateCommand(const Json::Value& message, const std::string &command, const int tan)
|
|
{
|
|
const Json::Value & componentState = message["componentstate"];
|
|
Components component = stringToComponent(QString::fromStdString(componentState.get("component", "invalid").asString()));
|
|
|
|
if (component != COMP_INVALID)
|
|
{
|
|
_hyperion->setComponentState(component, componentState.get("state", true).asBool());
|
|
sendSuccessReply(command, tan);
|
|
}
|
|
else
|
|
{
|
|
sendErrorReply("invalid component name", command, tan);
|
|
}
|
|
}
|
|
|
|
void JsonClientConnection::handleLedColorsCommand(const Json::Value& message, const std::string &command, const int tan)
|
|
{
|
|
// create result
|
|
std::string subcommand = message.get("subcommand","").asString();
|
|
_streaming_leds_reply["success"] = true;
|
|
_streaming_leds_reply["command"] = command;
|
|
_streaming_leds_reply["tan"] = tan;
|
|
|
|
if (subcommand == "ledstream-start")
|
|
{
|
|
_streaming_leds_reply["command"] = command+"-ledstream-update";
|
|
_timer_ledcolors.start(125);
|
|
}
|
|
else if (subcommand == "ledstream-stop")
|
|
{
|
|
_timer_ledcolors.stop();
|
|
}
|
|
else
|
|
{
|
|
sendErrorReply("unknown subcommand",command,tan);
|
|
return;
|
|
}
|
|
|
|
sendSuccessReply(command+"-"+subcommand,tan);
|
|
}
|
|
|
|
void JsonClientConnection::handleNotImplemented()
|
|
{
|
|
sendErrorReply("Command not implemented");
|
|
}
|
|
|
|
void JsonClientConnection::sendMessage(const Json::Value &message)
|
|
{
|
|
Json::FastWriter writer;
|
|
std::string serializedReply = writer.write(message);
|
|
|
|
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::sendMessage(const Json::Value & message, QTcpSocket * socket)
|
|
{
|
|
// serialize message (FastWriter already appends a newline)
|
|
std::string serializedMessage = Json::FastWriter().write(message);
|
|
|
|
// write message
|
|
socket->write(serializedMessage.c_str());
|
|
if (!socket->waitForBytesWritten())
|
|
{
|
|
Debug(_log, "Error while writing data to host");
|
|
return;
|
|
}
|
|
|
|
// read reply data
|
|
QByteArray serializedReply;
|
|
while (!serializedReply.contains('\n'))
|
|
{
|
|
// receive reply
|
|
if (!socket->waitForReadyRead())
|
|
{
|
|
Debug(_log, "Error while writing data from host");
|
|
return;
|
|
}
|
|
|
|
serializedReply += socket->readAll();
|
|
}
|
|
int bytes = serializedReply.indexOf('\n') + 1; // Find the end of message
|
|
|
|
// parse reply data
|
|
Json::Reader jsonReader;
|
|
Json::Value reply;
|
|
if (!jsonReader.parse(serializedReply.constData(), serializedReply.constData() + bytes, reply))
|
|
{
|
|
Error(_log, "Error while parsing reply: invalid json");
|
|
return;
|
|
}
|
|
|
|
}
|
|
|
|
void JsonClientConnection::sendSuccessReply(const std::string &command, const int tan)
|
|
{
|
|
// create reply
|
|
Json::Value reply;
|
|
reply["success"] = true;
|
|
reply["command"] = command;
|
|
reply["tan"] = tan;
|
|
|
|
// send reply
|
|
sendMessage(reply);
|
|
}
|
|
|
|
void JsonClientConnection::sendErrorReply(const std::string &error, const std::string &command, const int tan)
|
|
{
|
|
// create reply
|
|
Json::Value reply;
|
|
reply["success"] = false;
|
|
reply["error"] = error;
|
|
reply["command"] = command;
|
|
reply["tan"] = tan;
|
|
|
|
// send reply
|
|
sendMessage(reply);
|
|
}
|
|
|
|
bool JsonClientConnection::checkJson(const Json::Value & message, const QString & schemaResource, std::string & errorMessage, bool ignoreRequired)
|
|
{
|
|
// read the json schema from the resource
|
|
QResource schemaData(schemaResource);
|
|
assert(schemaData.isValid());
|
|
Json::Reader jsonReader;
|
|
Json::Value schemaJson;
|
|
if (!jsonReader.parse(reinterpret_cast<const char *>(schemaData.data()), reinterpret_cast<const char *>(schemaData.data()) + schemaData.size(), schemaJson, false))
|
|
{
|
|
errorMessage = "Schema error: " + jsonReader.getFormattedErrorMessages();
|
|
return false;
|
|
}
|
|
|
|
// create schema checker
|
|
JsonSchemaChecker schema;
|
|
schema.setSchema(schemaJson);
|
|
|
|
// check the message
|
|
if (!schema.validate(message, ignoreRequired))
|
|
{
|
|
const std::list<std::string> & errors = schema.getMessages();
|
|
std::stringstream ss;
|
|
ss << "{";
|
|
foreach (const std::string & error, errors) {
|
|
ss << error << " ";
|
|
}
|
|
ss << "}";
|
|
errorMessage = ss.str();
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void JsonClientConnection::streamLedcolorsUpdate()
|
|
{
|
|
Json::Value & leds = _streaming_leds_reply["result"]["leds"] = Json::Value(Json::arrayValue);
|
|
//QImage pngImage((const uint8_t *) image.memptr(), image.width(), image.height(), 3*image.width(), QImage::Format_RGB888);
|
|
|
|
const PriorityMuxer::InputInfo & priorityInfo = _hyperion->getPriorityInfo(_hyperion->getCurrentPriority());
|
|
std::vector<ColorRgb> ledBuffer = priorityInfo.ledColors;
|
|
|
|
for (ColorRgb& color : ledBuffer)
|
|
{
|
|
int idx = leds.size();
|
|
Json::Value & item = leds[idx];
|
|
item["index"] = idx;
|
|
item["red"] = color.red;
|
|
item["green"] = color.green;
|
|
item["blue"] = color.blue;
|
|
}
|
|
|
|
// send the result
|
|
sendMessage(_streaming_leds_reply);
|
|
|
|
}
|
|
|