hyperion.ng/libsrc/api/JsonAPI.cpp

1558 lines
46 KiB
C++
Raw Normal View History

// project includes
#include <api/JsonAPI.h>
// stl includes
#include <iostream>
#include <iterator>
// Qt includes
#include <QResource>
#include <QDateTime>
#include <QCryptographicHash>
#include <QImage>
#include <QBuffer>
#include <QByteArray>
#include <QDateTime>
2019-01-27 13:41:21 +01:00
#include <QHostInfo>
#include <QMutexLocker>
// hyperion includes
#include <utils/jsonschema/QJsonFactory.h>
#include <utils/SysInfo.h>
#include <HyperionConfig.h>
#include <utils/ColorSys.h>
#include <leddevice/LedDeviceWrapper.h>
#include <hyperion/GrabberWrapper.h>
#include <utils/Process.h>
#include <utils/JsonUtils.h>
// bonjour wrapper
#include <bonjour/bonjourbrowserwrapper.h>
// ledmapping int <> string transform methods
#include <hyperion/ImageProcessor.h>
// api includes
#include <api/JsonCB.h>
// auth manager
#include <hyperion/AuthManager.h>
using namespace hyperion;
JsonAPI::JsonAPI(QString peerAddress, Logger* log, const bool& localConnection, QObject* parent, bool noListener)
: QObject(parent)
, _authManager(AuthManager::getInstance())
, _authorized(false)
, _userAuthorized(false)
, _apiAuthRequired(_authManager->isAuthRequired())
, _noListener(noListener)
, _peerAddress(peerAddress)
, _log(log)
, _instanceManager(HyperionIManager::getInstance())
, _hyperion(nullptr)
, _jsonCB(nullptr)
, _streaming_logging_activated(false)
, _image_stream_timeout(0)
2018-12-31 15:48:29 +01:00
, _led_stream_timeout(0)
{
Q_INIT_RESOURCE(JSONRPC_schemas);
// if this is localConnection and network allows unauth locals, set authorized flag
if(_apiAuthRequired && localConnection)
_authorized = !_authManager->isLocalAuthRequired();
// setup auth interface
connect(_authManager, &AuthManager::newPendingTokenRequest, this, &JsonAPI::handlePendingTokenRequest);
connect(_authManager, &AuthManager::tokenResponse, this, &JsonAPI::handleTokenResponse);
// listen for killed instances
connect(_instanceManager, &HyperionIManager::instanceStateChanged, this, &JsonAPI::handleInstanceStateChange);
2018-12-30 22:07:53 +01:00
// init Hyperion pointer
handleInstanceSwitch(0);
// notify hyperion about a jsonMessageForward
connect(this, &JsonAPI::forwardJsonMessage, _hyperion, &Hyperion::forwardJsonMessage);
}
bool JsonAPI::handleInstanceSwitch(const quint8& inst, const bool& forced)
{
// check if we are already on the requested instance
if(_hyperion != nullptr && _hyperion->getInstanceIndex() == inst)
return true;
if(_instanceManager->IsInstanceRunning(inst))
{
Debug(_log,"Client '%s' switch to Hyperion instance %d", QSTRING_CSTR(_peerAddress), inst);
// cut all connections between hyperion / plugins and this
if(_hyperion != nullptr)
disconnect(_hyperion, 0, this, 0);
// get new Hyperion pointer
_hyperion = _instanceManager->getHyperionInstance(inst);
// the JsonCB creates json messages you can subscribe to e.g. data change events; forward them to the parent client
QStringList cbCmds;
if(_jsonCB != nullptr)
{
cbCmds = _jsonCB->getSubscribedCommands();
delete _jsonCB;
}
_jsonCB = new JsonCB(_hyperion, this);
connect(_jsonCB, &JsonCB::newCallback, this, &JsonAPI::callbackMessage);
// read subs
for(const auto & entry : cbCmds)
{
_jsonCB->subscribeFor(entry);
}
// // imageStream last state
// if(_ledcolorsImageActive)
// connect(_hyperion, &Hyperion::currentImage, this, &JsonAPI::setImage, Qt::UniqueConnection);
//
// //ledColor stream last state
// if(_ledcolorsLedsActive)
// connect(_hyperion, &Hyperion::rawLedColors, this, &JsonAPI::streamLedcolorsUpdate, Qt::UniqueConnection);
return true;
}
return false;
}
void JsonAPI::handleMessage(const QString& messageString, const QString& httpAuthHeader)
{
const QString ident = "JsonRpc@"+_peerAddress;
QJsonObject message;
// parse the message
if(!JsonUtils::parse(ident, messageString, message, _log))
{
sendErrorReply("Errors during message parsing, please consult the Hyperion Log.");
return;
}
// check basic message
if(!JsonUtils::validate(ident, message, ":schema", _log))
{
sendErrorReply("Errors during message validation, please consult the Hyperion Log.");
return;
}
// check specific message
const QString command = message["command"].toString();
if(!JsonUtils::validate(ident, message, QString(":schema-%1").arg(command), _log))
{
sendErrorReply("Errors during specific message validation, please consult the Hyperion Log");
return;
}
int tan = message["tan"].toInt();
// client auth before everything else but not for http
if (!_noListener && command == "authorize")
{
handleAuthorizeCommand(message, command, tan);
return;
}
// on the fly auth available for http from http Auth header, on failure we return and auth handler sends a failure
if(_noListener && _apiAuthRequired && !_authorized)
{
// extract token from http header
QString cToken = httpAuthHeader.mid(5).trimmed();
if(!handleHTTPAuth(command, tan, cToken))
return;
}
// on strong api auth you need a auth for all cmds
if(_apiAuthRequired && !_authorized)
{
sendErrorReply("No Authorization", command, tan);
return;
}
// 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 == "create-effect") handleCreateEffectCommand (message, command, tan);
else if (command == "delete-effect") handleDeleteEffectCommand (message, command, tan);
else if (command == "sysinfo") handleSysInfoCommand (message, command, tan);
else if (command == "serverinfo") handleServerInfoCommand (message, command, tan);
else if (command == "clear") handleClearCommand (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 if (command == "logging") handleLoggingCommand (message, command, tan);
else if (command == "processing") handleProcessingCommand (message, command, tan);
else if (command == "videomode") handleVideoModeCommand (message, command, tan);
else if (command == "instance") handleInstanceCommand (message, command, tan);
2019-01-27 13:41:21 +01:00
// BEGIN | The following commands are derecated but used to ensure backward compatibility with hyperion Classic remote control
else if (command == "clearall")
handleClearallCommand(message, command, tan);
2019-01-27 13:41:21 +01:00
else if (command == "transform" || command == "correction" || command == "temperature")
sendErrorReply("The command " + command + "is deprecated, please use the Hyperion Web Interface to configure");
// END
// handle not implemented commands
else handleNotImplemented();
}
void JsonAPI::handleColorCommand(const QJsonObject& message, const QString& command, const int tan)
{
emit forwardJsonMessage(message);
// extract parameters
int priority = message["priority"].toInt();
int duration = message["duration"].toInt(-1);
const QString origin = message["origin"].toString("JsonRpc") + "@"+_peerAddress;
const QJsonArray & jsonColor = message["color"].toArray();
const ColorRgb color = {uint8_t(jsonColor.at(0).toInt()),uint8_t(jsonColor.at(1).toInt()),uint8_t(jsonColor.at(2).toInt())};
// set color
_hyperion->setColor(priority, color, duration, origin);
// send reply
sendSuccessReply(command, tan);
}
void JsonAPI::handleImageCommand(const QJsonObject& message, const QString& command, const int tan)
{
emit forwardJsonMessage(message);
// extract parameters
int priority = message["priority"].toInt();
const QString origin = message["origin"].toString("JsonRpc") + "@"+_peerAddress;
int duration = message["duration"].toInt(-1);
int width = message["imagewidth"].toInt();
int height = message["imageheight"].toInt();
int scale = message["scale"].toInt(-1);
QString format = message["format"].toString();
QString imgName = message["name"].toString("");
QByteArray data = QByteArray::fromBase64(QByteArray(message["imagedata"].toString().toUtf8()));
// truncate name length
imgName.truncate(16);
if(format == "auto")
{
QImage img = QImage::fromData(data);
if(img.isNull())
{
sendErrorReply("Failed to parse picture, the file might be corrupted", command, tan);
return;
}
// check for requested scale
if(scale > 24)
{
if(img.height() > scale)
{
img = img.scaledToHeight(scale);
}
if(img.width() > scale)
{
img = img.scaledToWidth(scale);
}
}
// check if we need to force a scale
if(img.width() > 2000 || img.height() > 2000)
{
scale = 2000;
if(img.height() > scale)
{
img = img.scaledToHeight(scale);
}
if(img.width() > scale)
{
img = img.scaledToWidth(scale);
}
}
width = img.width();
height = img.height();
// extract image
img = img.convertToFormat(QImage::Format_ARGB32_Premultiplied);
data.clear();
data.reserve(img.width() * img.height() * 3);
for (int i = 0; i < img.height(); ++i)
{
const QRgb * scanline = reinterpret_cast<const QRgb *>(img.scanLine(i));
for (int j = 0; j < img.width(); ++j)
{
data.append((char) qRed(scanline[j]));
data.append((char) qGreen(scanline[j]));
data.append((char) qBlue(scanline[j]));
}
}
}
else
{
// 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;
}
}
// copy image
Image<ColorRgb> image(width, height);
memcpy(image.memptr(), data.data(), data.size());
_hyperion->registerInput(priority, hyperion::COMP_IMAGE, origin, imgName);
_hyperion->setInputImage(priority, image, duration);
// send reply
sendSuccessReply(command, tan);
}
void JsonAPI::handleEffectCommand(const QJsonObject &message, const QString &command, const int tan)
{
emit forwardJsonMessage(message);
// extract parameters
int priority = message["priority"].toInt();
int duration = message["duration"].toInt(-1);
QString pythonScript = message["pythonScript"].toString();
QString origin = message["origin"].toString("JsonRpc") + "@"+_peerAddress;
const QJsonObject & effect = message["effect"].toObject();
const QString & effectName = effect["name"].toString();
const QString & data = message["imageData"].toString("").toUtf8();
// set output
(effect.contains("args"))
? _hyperion->setEffect(effectName, effect["args"].toObject(), priority, duration, pythonScript, origin, data)
: _hyperion->setEffect(effectName, priority, duration, origin);
// send reply
sendSuccessReply(command, tan);
}
void JsonAPI::handleCreateEffectCommand(const QJsonObject& message, const QString &command, const int tan)
{
2018-12-31 15:48:29 +01:00
QString resultMsg;
if(_hyperion->saveEffect(message, resultMsg))
sendSuccessReply(command, tan);
else
sendErrorReply(resultMsg, command, tan);
}
void JsonAPI::handleDeleteEffectCommand(const QJsonObject& message, const QString& command, const int tan)
{
2018-12-31 15:48:29 +01:00
QString resultMsg;
if(_hyperion->deleteEffect(message["name"].toString(), resultMsg))
sendSuccessReply(command, tan);
else
sendErrorReply(resultMsg, command, tan);
}
void JsonAPI::handleSysInfoCommand(const QJsonObject&, const QString& command, const int tan)
{
// create result
QJsonObject result;
QJsonObject info;
result["success"] = true;
result["command"] = command;
result["tan"] = tan;
SysInfo::HyperionSysInfo data = SysInfo::get();
QJsonObject system;
system["kernelType" ] = data.kernelType;
system["kernelVersion" ] = data.kernelVersion;
system["architecture" ] = data.architecture;
system["wordSize" ] = data.wordSize;
system["productType" ] = data.productType;
system["productVersion"] = data.productVersion;
system["prettyName" ] = data.prettyName;
system["hostName" ] = data.hostName;
system["domainName" ] = data.domainName;
info["system"] = system;
QJsonObject hyperion;
hyperion["jsonrpc_version" ] = QString(HYPERION_JSON_VERSION);
hyperion["version" ] = QString(HYPERION_VERSION);
hyperion["channel" ] = QString(HYPERION_VERSION_CHANNEL);
hyperion["build" ] = QString(HYPERION_BUILD_ID);
hyperion["time" ] = QString(__DATE__ " " __TIME__);
hyperion["id" ] = _authManager->getID();
info["hyperion"] = hyperion;
// send the result
result["info" ] = info;
emit callbackMessage(result);
}
void JsonAPI::handleServerInfoCommand(const QJsonObject& message, const QString& command, const int tan)
{
QJsonObject info;
// collect priority information
QJsonArray priorities;
uint64_t now = QDateTime::currentMSecsSinceEpoch();
QList<int> activePriorities = _hyperion->getActivePriorities();
activePriorities.removeAll(255);
int currentPriority = _hyperion->getCurrentPriority();
foreach (int priority, activePriorities) {
const Hyperion::InputInfo & priorityInfo = _hyperion->getPriorityInfo(priority);
QJsonObject item;
item["priority"] = priority;
2018-12-31 15:48:29 +01:00
if (priorityInfo.timeoutTime_ms > 0 )
item["duration_ms"] = int(priorityInfo.timeoutTime_ms - now);
2018-12-31 15:48:29 +01:00
// owner has optional informations to the component
if(!priorityInfo.owner.isEmpty())
item["owner"] = priorityInfo.owner;
item["componentId"] = QString(hyperion::componentToIdString(priorityInfo.componentId));
item["origin"] = priorityInfo.origin;
item["active"] = (priorityInfo.timeoutTime_ms >= -1);
item["visible"] = (priority == currentPriority);
if(priorityInfo.componentId == hyperion::COMP_COLOR && !priorityInfo.ledColors.empty())
{
QJsonObject LEDcolor;
// add RGB Value to Array
QJsonArray RGBValue;
RGBValue.append(priorityInfo.ledColors.begin()->red);
RGBValue.append(priorityInfo.ledColors.begin()->green);
RGBValue.append(priorityInfo.ledColors.begin()->blue);
LEDcolor.insert("RGB", RGBValue);
uint16_t Hue;
float Saturation, Luminace;
// add HSL Value to Array
QJsonArray HSLValue;
ColorSys::rgb2hsl(priorityInfo.ledColors.begin()->red,
priorityInfo.ledColors.begin()->green,
priorityInfo.ledColors.begin()->blue,
Hue, Saturation, Luminace);
HSLValue.append(Hue);
HSLValue.append(Saturation);
HSLValue.append(Luminace);
LEDcolor.insert("HSL", HSLValue);
item["value"] = LEDcolor;
}
// priorities[priorities.size()] = item;
priorities.append(item);
}
info["priorities"] = priorities;
info["priorities_autoselect"] = _hyperion->sourceAutoSelectEnabled();
// collect adjustment information
QJsonArray adjustmentArray;
for (const QString& adjustmentId : _hyperion->getAdjustmentIds())
{
const ColorAdjustment * colorAdjustment = _hyperion->getAdjustment(adjustmentId);
if (colorAdjustment == nullptr)
{
Error(_log, "Incorrect color adjustment id: %s", QSTRING_CSTR(adjustmentId));
continue;
}
QJsonObject adjustment;
adjustment["id"] = adjustmentId;
QJsonArray whiteAdjust;
whiteAdjust.append(colorAdjustment->_rgbWhiteAdjustment.getAdjustmentR());
whiteAdjust.append(colorAdjustment->_rgbWhiteAdjustment.getAdjustmentG());
whiteAdjust.append(colorAdjustment->_rgbWhiteAdjustment.getAdjustmentB());
adjustment.insert("white", whiteAdjust);
QJsonArray redAdjust;
redAdjust.append(colorAdjustment->_rgbRedAdjustment.getAdjustmentR());
redAdjust.append(colorAdjustment->_rgbRedAdjustment.getAdjustmentG());
redAdjust.append(colorAdjustment->_rgbRedAdjustment.getAdjustmentB());
adjustment.insert("red", redAdjust);
QJsonArray greenAdjust;
greenAdjust.append(colorAdjustment->_rgbGreenAdjustment.getAdjustmentR());
greenAdjust.append(colorAdjustment->_rgbGreenAdjustment.getAdjustmentG());
greenAdjust.append(colorAdjustment->_rgbGreenAdjustment.getAdjustmentB());
adjustment.insert("green", greenAdjust);
QJsonArray blueAdjust;
blueAdjust.append(colorAdjustment->_rgbBlueAdjustment.getAdjustmentR());
blueAdjust.append(colorAdjustment->_rgbBlueAdjustment.getAdjustmentG());
blueAdjust.append(colorAdjustment->_rgbBlueAdjustment.getAdjustmentB());
adjustment.insert("blue", blueAdjust);
QJsonArray cyanAdjust;
cyanAdjust.append(colorAdjustment->_rgbCyanAdjustment.getAdjustmentR());
cyanAdjust.append(colorAdjustment->_rgbCyanAdjustment.getAdjustmentG());
cyanAdjust.append(colorAdjustment->_rgbCyanAdjustment.getAdjustmentB());
adjustment.insert("cyan", cyanAdjust);
QJsonArray magentaAdjust;
magentaAdjust.append(colorAdjustment->_rgbMagentaAdjustment.getAdjustmentR());
magentaAdjust.append(colorAdjustment->_rgbMagentaAdjustment.getAdjustmentG());
magentaAdjust.append(colorAdjustment->_rgbMagentaAdjustment.getAdjustmentB());
adjustment.insert("magenta", magentaAdjust);
QJsonArray yellowAdjust;
yellowAdjust.append(colorAdjustment->_rgbYellowAdjustment.getAdjustmentR());
yellowAdjust.append(colorAdjustment->_rgbYellowAdjustment.getAdjustmentG());
yellowAdjust.append(colorAdjustment->_rgbYellowAdjustment.getAdjustmentB());
adjustment.insert("yellow", yellowAdjust);
adjustment["backlightThreshold"] = colorAdjustment->_rgbTransform.getBacklightThreshold();
adjustment["backlightColored"] = colorAdjustment->_rgbTransform.getBacklightColored();
adjustment["brightness"] = colorAdjustment->_rgbTransform.getBrightness();
adjustment["brightnessCompensation"] = colorAdjustment->_rgbTransform.getBrightnessCompensation();
adjustment["gammaRed"] = colorAdjustment->_rgbTransform.getGammaR();
adjustment["gammaGreen"] = colorAdjustment->_rgbTransform.getGammaG();
adjustment["gammaBlue"] = colorAdjustment->_rgbTransform.getGammaB();
adjustmentArray.append(adjustment);
}
info["adjustment"] = adjustmentArray;
// collect effect info
QJsonArray effects;
const std::list<EffectDefinition> & effectsDefinitions = _hyperion->getEffects();
for (const EffectDefinition & effectDefinition : effectsDefinitions)
{
QJsonObject effect;
effect["name"] = effectDefinition.name;
effect["file"] = effectDefinition.file;
effect["script"] = effectDefinition.script;
effect["args"] = effectDefinition.args;
effects.append(effect);
}
info["effects"] = effects;
// get available led devices
QJsonObject ledDevices;
ledDevices["active"] = _hyperion->getActiveDevice();
QJsonArray availableLedDevices;
for (auto dev: LedDeviceWrapper::getDeviceMap())
{
availableLedDevices.append(dev.first);
}
ledDevices["available"] = availableLedDevices;
info["ledDevices"] = ledDevices;
QJsonObject grabbers;
QJsonArray availableGrabbers;
#if defined(ENABLE_DISPMANX) || defined(ENABLE_V4L2) || defined(ENABLE_FB) || defined(ENABLE_AMLOGIC) || defined(ENABLE_OSX) || defined(ENABLE_X11)
// get available grabbers
//grabbers["active"] = ????;
for (auto grabber: GrabberWrapper::availableGrabbers())
{
availableGrabbers.append(grabber);
}
#endif
grabbers["available"] = availableGrabbers;
info["videomode"] = QString(videoMode2String(_hyperion->getCurrentVideoMode()));
info["grabbers"] = grabbers;
// get available components
QJsonArray component;
std::map<hyperion::Components, bool> components = _hyperion->getComponentRegister().getRegister();
for(auto comp : components)
{
QJsonObject item;
item["name"] = QString::fromStdString(hyperion::componentToIdString(comp.first));
item["enabled"] = comp.second;
component.append(item);
}
info["components"] = component;
info["imageToLedMappingType"] = ImageProcessor::mappingTypeToStr(_hyperion->getLedMappingType());
// add sessions
QJsonArray sessions;
for (auto session: BonjourBrowserWrapper::getInstance()->getAllServices())
{
if (session.port<0) continue;
QJsonObject item;
item["name"] = session.serviceName;
item["type"] = session.registeredType;
item["domain"] = session.replyDomain;
item["host"] = session.hostName;
item["address"]= session.address;
item["port"] = session.port;
sessions.append(item);
}
info["sessions"] = sessions;
// add instance info
QJsonArray instanceInfo;
for(const auto & entry : _instanceManager->getInstanceData())
{
QJsonObject obj;
obj.insert("friendly_name", entry["friendly_name"].toString());
obj.insert("instance", entry["instance"].toInt());
//obj.insert("last_use", entry["last_use"].toString());
obj.insert("running", entry["running"].toBool());
instanceInfo.append(obj);
}
info["instance"] = instanceInfo;
// add leds configs
info["leds"] = _hyperion->getSetting(settings::LEDS).array();
2019-01-27 13:41:21 +01:00
// BEGIN | The following entries are derecated but used to ensure backward compatibility with hyperion Classic remote control
// TODO Output the real transformation information instead of default
// HOST NAME
2019-01-27 13:41:21 +01:00
info["hostname"] = QHostInfo::localHostName();
// TRANSFORM INFORMATION (DEFAULT VALUES)
2019-01-27 13:41:21 +01:00
QJsonArray transformArray;
for (const QString& transformId : _hyperion->getAdjustmentIds())
{
QJsonObject transform;
QJsonArray blacklevel, whitelevel, gamma, threshold;
transform["id"] = transformId;
transform["saturationGain"] = 1.0;
transform["valueGain"] = 1.0;
transform["saturationLGain"] = 1.0;
transform["luminanceGain"] = 1.0;
transform["luminanceMinimum"] = 0.0;
for (int i = 0; i < 3; i++ )
{
blacklevel.append(0.0);
whitelevel.append(1.0);
gamma.append(2.50);
threshold.append(0.0);
}
transform.insert("blacklevel", blacklevel);
transform.insert("whitelevel", whitelevel);
transform.insert("gamma", gamma);
transform.insert("threshold", threshold);
transformArray.append(transform);
}
info["transform"] = transformArray;
// ACTIVE EFFECT INFO
QJsonArray activeEffects;
const std::list<ActiveEffectDefinition> & activeEffectsDefinitions = _hyperion->getActiveEffects();
for (const ActiveEffectDefinition & activeEffectDefinition : activeEffectsDefinitions)
{
if (activeEffectDefinition.priority != PriorityMuxer::LOWEST_PRIORITY -1)
{
QJsonObject activeEffect;
activeEffect["script"] = activeEffectDefinition.script;
activeEffect["name"] = activeEffectDefinition.name;
activeEffect["priority"] = activeEffectDefinition.priority;
activeEffect["timeout"] = activeEffectDefinition.timeout;
activeEffect["args"] = activeEffectDefinition.args;
activeEffects.append(activeEffect);
}
}
info["activeEffects"] = activeEffects;
// ACTIVE STATIC LED COLOR
QJsonArray activeLedColors;
const Hyperion::InputInfo & priorityInfo = _hyperion->getPriorityInfo(_hyperion->getCurrentPriority());
if(priorityInfo.componentId == hyperion::COMP_COLOR && !priorityInfo.ledColors.empty())
{
QJsonObject LEDcolor;
// check if LED Color not Black (0,0,0)
if ((priorityInfo.ledColors.begin()->red +
priorityInfo.ledColors.begin()->green +
priorityInfo.ledColors.begin()->blue != 0))
{
QJsonObject LEDcolor;
// add RGB Value to Array
QJsonArray RGBValue;
RGBValue.append(priorityInfo.ledColors.begin()->red);
RGBValue.append(priorityInfo.ledColors.begin()->green);
RGBValue.append(priorityInfo.ledColors.begin()->blue);
LEDcolor.insert("RGB Value", RGBValue);
uint16_t Hue;
float Saturation, Luminace;
// add HSL Value to Array
QJsonArray HSLValue;
ColorSys::rgb2hsl(priorityInfo.ledColors.begin()->red,
priorityInfo.ledColors.begin()->green,
priorityInfo.ledColors.begin()->blue,
Hue, Saturation, Luminace);
HSLValue.append(Hue);
HSLValue.append(Saturation);
HSLValue.append(Luminace);
LEDcolor.insert("HSL Value", HSLValue);
activeLedColors.append(LEDcolor);
}
}
info["activeLedColor"] = activeLedColors;
2019-01-27 13:41:21 +01:00
// END
sendSuccessDataReply(QJsonDocument(info), command, tan);
// AFTER we send the info, the client might want to subscribe to future updates
if(message.contains("subscribe"))
{
// check if listeners are allowed
if(_noListener)
return;
QJsonArray subsArr = message["subscribe"].toArray();
// catch the all keyword and build a list of all cmds
if(subsArr.contains("all"))
{
subsArr = QJsonArray();
for(const auto & entry : _jsonCB->getCommands())
{
subsArr.append(entry);
}
}
for(const auto & entry : subsArr)
{
// config callbacks just if auth is set
if(entry == "settings-update" && !_authorized)
continue;
if(!_jsonCB->subscribeFor(entry.toString()))
sendErrorReply(QString("Subscription for '%1' not found. Possible values: %2").arg(entry.toString(), _jsonCB->getCommands().join(", ")), command, tan);
}
}
}
void JsonAPI::handleClearCommand(const QJsonObject& message, const QString& command, const int tan)
{
emit forwardJsonMessage(message);
int priority = message["priority"].toInt();
if(priority > 0)
_hyperion->clear(priority);
else if(priority < 0)
_hyperion->clearall();
else
{
sendErrorReply("Priority 0 is not allowed", command, tan);
return;
}
// send reply
sendSuccessReply(command, tan);
}
void JsonAPI::handleClearallCommand(const QJsonObject& message, const QString& command, const int tan)
{
emit forwardJsonMessage(message);
// clear priority
_hyperion->clearall();
// send reply
sendSuccessReply(command, tan);
}
void JsonAPI::handleAdjustmentCommand(const QJsonObject& message, const QString& command, const int tan)
{
const QJsonObject & adjustment = message["adjustment"].toObject();
const QString adjustmentId = adjustment["id"].toString(_hyperion->getAdjustmentIds().first());
ColorAdjustment * colorAdjustment = _hyperion->getAdjustment(adjustmentId);
if (colorAdjustment == nullptr)
{
Warning(_log, "Incorrect adjustment identifier: %s", adjustmentId.toStdString().c_str());
return;
}
if (adjustment.contains("red"))
{
const QJsonArray & values = adjustment["red"].toArray();
colorAdjustment->_rgbRedAdjustment.setAdjustment(values[0u].toInt(), values[1u].toInt(), values[2u].toInt());
}
if (adjustment.contains("green"))
{
const QJsonArray & values = adjustment["green"].toArray();
colorAdjustment->_rgbGreenAdjustment.setAdjustment(values[0u].toInt(), values[1u].toInt(), values[2u].toInt());
}
if (adjustment.contains("blue"))
{
const QJsonArray & values = adjustment["blue"].toArray();
colorAdjustment->_rgbBlueAdjustment.setAdjustment(values[0u].toInt(), values[1u].toInt(), values[2u].toInt());
}
if (adjustment.contains("cyan"))
{
const QJsonArray & values = adjustment["cyan"].toArray();
colorAdjustment->_rgbCyanAdjustment.setAdjustment(values[0u].toInt(), values[1u].toInt(), values[2u].toInt());
}
if (adjustment.contains("magenta"))
{
const QJsonArray & values = adjustment["magenta"].toArray();
colorAdjustment->_rgbMagentaAdjustment.setAdjustment(values[0u].toInt(), values[1u].toInt(), values[2u].toInt());
}
if (adjustment.contains("yellow"))
{
const QJsonArray & values = adjustment["yellow"].toArray();
colorAdjustment->_rgbYellowAdjustment.setAdjustment(values[0u].toInt(), values[1u].toInt(), values[2u].toInt());
}
if (adjustment.contains("white"))
{
const QJsonArray & values = adjustment["white"].toArray();
colorAdjustment->_rgbWhiteAdjustment.setAdjustment(values[0u].toInt(), values[1u].toInt(), values[2u].toInt());
}
if (adjustment.contains("gammaRed"))
{
colorAdjustment->_rgbTransform.setGamma(adjustment["gammaRed"].toDouble(), colorAdjustment->_rgbTransform.getGammaG(), colorAdjustment->_rgbTransform.getGammaB());
}
if (adjustment.contains("gammaGreen"))
{
colorAdjustment->_rgbTransform.setGamma(colorAdjustment->_rgbTransform.getGammaR(), adjustment["gammaGreen"].toDouble(), colorAdjustment->_rgbTransform.getGammaB());
}
if (adjustment.contains("gammaBlue"))
{
colorAdjustment->_rgbTransform.setGamma(colorAdjustment->_rgbTransform.getGammaR(), colorAdjustment->_rgbTransform.getGammaG(), adjustment["gammaBlue"].toDouble());
}
if (adjustment.contains("backlightThreshold"))
{
colorAdjustment->_rgbTransform.setBacklightThreshold(adjustment["backlightThreshold"].toDouble());
}
if (adjustment.contains("backlightColored"))
{
colorAdjustment->_rgbTransform.setBacklightColored(adjustment["backlightColored"].toBool());
}
if (adjustment.contains("brightness"))
{
colorAdjustment->_rgbTransform.setBrightness(adjustment["brightness"].toInt());
}
if (adjustment.contains("brightnessCompensation"))
{
colorAdjustment->_rgbTransform.setBrightnessCompensation(adjustment["brightnessCompensation"].toInt());
}
// commit the changes
_hyperion->adjustmentsUpdated();
sendSuccessReply(command, tan);
}
void JsonAPI::handleSourceSelectCommand(const QJsonObject& message, const QString& command, const int tan)
{
bool success = false;
if (message["auto"].toBool(false))
{
_hyperion->setSourceAutoSelectEnabled(true);
success = true;
}
else if (message.contains("priority"))
{
success = _hyperion->setCurrentSourcePriority(message["priority"].toInt());
}
if (success)
{
sendSuccessReply(command, tan);
}
else
{
sendErrorReply("setting current priority failed", command, tan);
}
}
void JsonAPI::handleConfigCommand(const QJsonObject& message, const QString& command, const int tan)
{
QString subcommand = message["subcommand"].toString("");
QString full_command = command + "-" + subcommand;
if (subcommand == "getschema")
{
handleSchemaGetCommand(message, full_command, tan);
}
else if (subcommand == "setconfig")
{
handleConfigSetCommand(message, full_command, tan);
}
else if (subcommand == "getconfig")
{
sendSuccessDataReply(QJsonDocument(_hyperion->getQJsonConfig()), full_command, tan);
}
else if (subcommand == "reload")
{
_hyperion->freeObjects(true);
Process::restartHyperion();
sendErrorReply("failed to restart hyperion", full_command, tan);
}
else
{
sendErrorReply("unknown or missing subcommand", full_command, tan);
}
}
void JsonAPI::handleConfigSetCommand(const QJsonObject& message, const QString &command, const int tan)
{
if (message.contains("config"))
{
QJsonObject config = message["config"].toObject();
if(_hyperion->getComponentRegister().isComponentEnabled(hyperion::COMP_ALL))
{
if(_hyperion->saveSettings(config, true))
sendSuccessReply(command,tan);
else
sendErrorReply("Failed to save configuration, more information at the Hyperion log", command, tan);
}
else
sendErrorReply("Saving configuration while Hyperion is disabled isn't possible", command, tan);
}
}
void JsonAPI::handleSchemaGetCommand(const QJsonObject& message, const QString& command, const int tan)
{
// create result
QJsonObject schemaJson, alldevices, properties;
// make sure the resources are loaded (they may be left out after static linking)
Q_INIT_RESOURCE(resource);
// read the hyperion json schema from the resource
QString schemaFile = ":/hyperion-schema";
try
{
schemaJson = QJsonFactory::readSchema(schemaFile);
}
catch(const std::runtime_error& error)
{
throw std::runtime_error(error.what());
}
// collect all LED Devices
properties = schemaJson["properties"].toObject();
alldevices = LedDeviceWrapper::getLedDeviceSchemas();
properties.insert("alldevices", alldevices);
// collect all available effect schemas
QJsonObject pyEffectSchemas, pyEffectSchema;
QJsonArray in, ex;
const std::list<EffectSchema> & effectsSchemas = _hyperion->getEffectSchemas();
for (const EffectSchema & effectSchema : effectsSchemas)
{
if (effectSchema.pyFile.mid(0, 1) == ":")
{
QJsonObject internal;
internal.insert("script", effectSchema.pyFile);
internal.insert("schemaLocation", effectSchema.schemaFile);
internal.insert("schemaContent", effectSchema.pySchema);
in.append(internal);
}
else
{
QJsonObject external;
external.insert("script", effectSchema.pyFile);
external.insert("schemaLocation", effectSchema.schemaFile);
external.insert("schemaContent", effectSchema.pySchema);
ex.append(external);
}
}
if (!in.empty())
pyEffectSchema.insert("internal", in);
if (!ex.empty())
pyEffectSchema.insert("external", ex);
pyEffectSchemas = pyEffectSchema;
properties.insert("effectSchemas", pyEffectSchemas);
schemaJson.insert("properties", properties);
// send the result
sendSuccessDataReply(QJsonDocument(schemaJson), command, tan);
}
void JsonAPI::handleComponentStateCommand(const QJsonObject& message, const QString &command, const int tan)
{
const QJsonObject & componentState = message["componentstate"].toObject();
QString compStr = componentState["component"].toString("invalid");
bool compState = componentState["state"].toBool(true);
Components component = stringToComponent(compStr);
if (compStr == "ALL" )
{
if(_hyperion->getComponentRegister().setHyperionEnable(compState))
sendSuccessReply(command, tan);
return;
}
else if (component != COMP_INVALID)
{
// send result before apply
sendSuccessReply(command, tan);
_hyperion->setComponentState(component, compState);
return;
}
sendErrorReply("invalid component name", command, tan);
}
void JsonAPI::handleLedColorsCommand(const QJsonObject& message, const QString &command, const int tan)
{
// create result
QString subcommand = message["subcommand"].toString("");
if (subcommand == "ledstream-start")
{
_streaming_leds_reply["success"] = true;
_streaming_leds_reply["command"] = command+"-ledstream-update";
2018-12-30 22:07:53 +01:00
_streaming_leds_reply["tan"] = tan;
2018-12-31 15:48:29 +01:00
connect(_hyperion, &Hyperion::rawLedColors, this, &JsonAPI::streamLedcolorsUpdate, Qt::UniqueConnection);
}
else if (subcommand == "ledstream-stop")
{
2018-12-31 15:48:29 +01:00
disconnect(_hyperion, &Hyperion::rawLedColors, this, &JsonAPI::streamLedcolorsUpdate);
}
else if (subcommand == "imagestream-start")
{
_streaming_image_reply["success"] = true;
_streaming_image_reply["command"] = command+"-imagestream-update";
2018-12-30 22:07:53 +01:00
_streaming_image_reply["tan"] = tan;
connect(_hyperion, &Hyperion::currentImage, this, &JsonAPI::setImage, Qt::UniqueConnection);
_hyperion->update();
}
else if (subcommand == "imagestream-stop")
{
disconnect(_hyperion, &Hyperion::currentImage, this, &JsonAPI::setImage);
}
else
{
return;
}
sendSuccessReply(command+"-"+subcommand,tan);
}
void JsonAPI::handleLoggingCommand(const QJsonObject& message, const QString &command, const int tan)
{
// create result
QString subcommand = message["subcommand"].toString("");
_streaming_logging_reply["success"] = true;
_streaming_logging_reply["command"] = command;
_streaming_logging_reply["tan"] = tan;
if (subcommand == "start")
{
if (!_streaming_logging_activated)
{
_streaming_logging_reply["command"] = command+"-update";
connect(LoggerManager::getInstance(),SIGNAL(newLogMessage(Logger::T_LOG_MESSAGE)), this, SLOT(incommingLogMessage(Logger::T_LOG_MESSAGE)));
Debug(_log, "log streaming activated for client %s",_peerAddress.toStdString().c_str()); // needed to trigger log sending
}
}
else if (subcommand == "stop")
{
if (_streaming_logging_activated)
{
disconnect(LoggerManager::getInstance(), SIGNAL(newLogMessage(Logger::T_LOG_MESSAGE)), this, 0);
_streaming_logging_activated = false;
Debug(_log, "log streaming deactivated for client %s",_peerAddress.toStdString().c_str());
}
}
else
{
return;
}
sendSuccessReply(command+"-"+subcommand,tan);
}
void JsonAPI::handleProcessingCommand(const QJsonObject& message, const QString &command, const int tan)
{
_hyperion->setLedMappingType(ImageProcessor::mappingTypeToInt( message["mappingType"].toString("multicolor_mean")) );
sendSuccessReply(command, tan);
}
void JsonAPI::handleVideoModeCommand(const QJsonObject& message, const QString &command, const int tan)
{
_hyperion->setVideoMode(parse3DMode(message["videoMode"].toString("2D")));
sendSuccessReply(command, tan);
}
void JsonAPI::handleAuthorizeCommand(const QJsonObject & message, const QString &command, const int tan)
{
const QString& subc = message["subcommand"].toString().trimmed();
// catch test if auth is required
if(subc == "required")
{
QJsonObject req;
req["required"] = _apiAuthRequired;
sendSuccessDataReply(QJsonDocument(req), command+"-"+subc, tan);
return;
}
// catch logout
if(subc == "logout")
{
_authorized = false;
_userAuthorized = false;
sendSuccessReply(command+"-"+subc, tan);
return;
}
// token created from ui
if(subc == "createToken")
{
const QString& c = message["comment"].toString().trimmed();
// for user authorized sessions
if(_userAuthorized)
{
AuthManager::AuthDefinition def = _authManager->createToken(c);
QJsonObject newTok;
newTok["comment"] = def.comment;
newTok["id"] = def.id;
newTok["token"] = def.token;
sendSuccessDataReply(QJsonDocument(newTok), command+"-"+subc, tan);
return;
}
sendErrorReply("No Authorization",command+"-"+subc, tan);
return;
}
// delete token
if(subc == "deleteToken")
{
const QString& did = message["id"].toString().trimmed();
// for user authorized sessions
if(_userAuthorized)
{
_authManager->deleteToken(did);
sendSuccessReply(command+"-"+subc, tan);
return;
}
sendErrorReply("No Authorization",command+"-"+subc, tan);
return;
}
// catch token request
if(subc == "requestToken")
{
const QString& comment = message["comment"].toString().trimmed();
const QString& id = message["id"].toString().trimmed();
_authManager->setNewTokenRequest(this, comment, id);
// client should wait for answer
return;
}
// get pending token requests
if(subc == "getPendingRequests")
{
if(_userAuthorized)
{
QMap<QString, AuthManager::AuthDefinition> map = _authManager->getPendingRequests();
QJsonArray arr;
for(const auto& entry : map)
{
QJsonObject obj;
obj["comment"] = entry.comment;
obj["id"] = entry.id;
obj["timeout"] = int(entry.timeoutTime - QDateTime::currentMSecsSinceEpoch());
arr.append(obj);
}
sendSuccessDataReply(QJsonDocument(arr),command+"-"+subc, tan);
}
else
sendErrorReply("No Authorization", command+"-"+subc, tan);
return;
}
// accept/deny token request
if(subc == "answerRequest")
{
const QString& id = message["id"].toString().trimmed();
const bool& accept = message["accept"].toBool(false);
if(_userAuthorized)
{
if(accept)
_authManager->acceptTokenRequest(id);
else
_authManager->denyTokenRequest(id);
}
else
sendErrorReply("No Authorization", command+"-"+subc, tan);
return;
}
// deny token request
if(subc == "acceptRequest")
{
const QString& id = message["id"].toString().trimmed();
if(_userAuthorized)
{
_authManager->acceptTokenRequest(id);
}
else
sendErrorReply("No Authorization", command+"-"+subc, tan);
return;
}
// cath get token list
if(subc == "getTokenList")
{
if(_userAuthorized)
{
QVector<AuthManager::AuthDefinition> defVect = _authManager->getTokenList();
QJsonArray tArr;
for(const auto& entry : defVect)
{
QJsonObject subO;
subO["comment"] = entry.comment;
subO["id"] = entry.id;
subO["last_use"] = entry.lastUse;
tArr.append(subO);
}
sendSuccessDataReply(QJsonDocument(tArr),command+"-"+subc, tan);
return;
}
sendErrorReply("No Authorization",command+"-"+subc, tan);
return;
}
// login
if(subc == "login")
{
// catch token auth
const QString& token = message["token"].toString().trimmed();
if(!token.isEmpty())
{
if(token.count() >= 36)
{
if(_authManager->isTokenAuthorized(token))
{
_authorized = true;
sendSuccessReply(command+"-"+subc, tan);
}
else
sendErrorReply("No Authorization", command+"-"+subc, tan);
}
else
sendErrorReply("Token is too short", command+"-"+subc, tan);
return;
}
// user & password
const QString& user = message["username"].toString().trimmed();
const QString& password = message["password"].toString().trimmed();
if(user.count() >= 3 && password.count() >= 8)
{
if(_authManager->isUserAuthorized(user, password))
{
_authorized = true;
_userAuthorized = true;
sendSuccessReply(command+"-"+subc, tan);
}
else
sendErrorReply("No Authorization", command+"-"+subc, tan);
}
else
sendErrorReply("User or password string too short", command+"-"+subc, tan);
}
}
bool JsonAPI::handleHTTPAuth(const QString& command, const int& tan, const QString& token)
{
if(_authManager->isTokenAuthorized(token))
{
_authorized = true;
return true;
}
sendErrorReply("No Authorization", command, tan);
return false;
}
void JsonAPI::handleInstanceCommand(const QJsonObject & message, const QString &command, const int tan)
2019-01-27 13:41:21 +01:00
{
const QString & subc = message["subcommand"].toString();
const quint8 & inst = message["instance"].toInt();
const QString & name = message["name"].toString();
2019-01-27 13:41:21 +01:00
if(subc == "switchTo")
{
if(handleInstanceSwitch(inst))
sendSuccessReply(command+"-"+subc, tan);
else
sendErrorReply("Selected Hyperion instance isn't running",command+"-"+subc, tan);
return;
}
2019-01-27 13:41:21 +01:00
if(subc == "startInstance")
{
// silent fail
_instanceManager->startInstance(inst);
sendSuccessReply(command+"-"+subc, tan);
return;
}
if(subc == "stopInstance")
{
// silent fail
_instanceManager->stopInstance(inst);
sendSuccessReply(command+"-"+subc, tan);
return;
}
if(subc == "deleteInstance")
{
if(_userAuthorized)
{
if(_instanceManager->deleteInstance(inst))
sendSuccessReply(command+"-"+subc, tan);
else
sendErrorReply(QString("Failed to delete instance '%1'").arg(inst), command+"-"+subc, tan);
}
else
sendErrorReply("No Authorization",command+"-"+subc, tan);
return;
}
// create and save name requires name
if(name.isEmpty())
sendErrorReply("Name string required for this command",command+"-"+subc, tan);
if(subc == "createInstance")
{
if(_userAuthorized)
{
if(_instanceManager->createInstance(name))
sendSuccessReply(command+"-"+subc, tan);
else
sendErrorReply(QString("The instance name '%1' is already in use").arg(name), command+"-"+subc, tan);
}
else
sendErrorReply("No Authorization",command+"-"+subc, tan);
return;
}
if(subc == "saveName")
{
if(_userAuthorized)
{
// silent fail
if(_instanceManager->saveName(inst,name))
sendSuccessReply(command+"-"+subc, tan);
else
sendErrorReply(QString("The instance name '%1' is already in use").arg(name), command+"-"+subc, tan);
}
else
sendErrorReply("No Authorization",command+"-"+subc, tan);
return;
}
2019-01-27 13:41:21 +01:00
}
void JsonAPI::handleNotImplemented()
{
sendErrorReply("Command not implemented");
}
void JsonAPI::sendSuccessReply(const QString &command, const int tan)
{
// create reply
QJsonObject reply;
reply["success"] = true;
reply["command"] = command;
reply["tan"] = tan;
// send reply
emit callbackMessage(reply);
}
void JsonAPI::sendSuccessDataReply(const QJsonDocument &doc, const QString &command, const int &tan)
{
QJsonObject reply;
reply["success"] = true;
reply["command"] = command;
reply["tan"] = tan;
if(doc.isArray())
reply["info"] = doc.array();
else
reply["info"] = doc.object();
emit callbackMessage(reply);
}
void JsonAPI::sendErrorReply(const QString &error, const QString &command, const int tan)
{
// create reply
QJsonObject reply;
reply["success"] = false;
reply["error"] = error;
reply["command"] = command;
reply["tan"] = tan;
// send reply
emit callbackMessage(reply);
}
2018-12-31 15:48:29 +01:00
void JsonAPI::streamLedcolorsUpdate(const std::vector<ColorRgb>& ledColors)
{
QMutexLocker lock(&_led_stream_mutex);
if ( (_led_stream_timeout+100) < QDateTime::currentMSecsSinceEpoch() )
{
2018-12-31 15:48:29 +01:00
_led_stream_timeout = QDateTime::currentMSecsSinceEpoch();
QJsonObject result;
QJsonArray leds;
for(auto color = ledColors.begin(); color != ledColors.end(); ++color)
{
QJsonObject item;
item["index"] = int(color - ledColors.begin());
item["red"] = color->red;
item["green"] = color->green;
item["blue"] = color->blue;
leds.append(item);
}
2018-12-31 15:48:29 +01:00
result["leds"] = leds;
_streaming_leds_reply["result"] = result;
2018-12-31 15:48:29 +01:00
// send the result
emit callbackMessage(_streaming_leds_reply);
}
}
void JsonAPI::setImage(const Image<ColorRgb> & image)
{
QMutexLocker lock(&_image_stream_mutex);
if ( (_image_stream_timeout+100) < QDateTime::currentMSecsSinceEpoch() )
{
_image_stream_timeout = QDateTime::currentMSecsSinceEpoch();
QImage jpgImage((const uint8_t *) image.memptr(), image.width(), image.height(), 3*image.width(), QImage::Format_RGB888);
QByteArray ba;
QBuffer buffer(&ba);
buffer.open(QIODevice::WriteOnly);
jpgImage.save(&buffer, "jpg");
QJsonObject result;
result["image"] = "data:image/jpg;base64,"+QString(ba.toBase64());
_streaming_image_reply["result"] = result;
emit callbackMessage(_streaming_image_reply);
}
}
void JsonAPI::incommingLogMessage(const Logger::T_LOG_MESSAGE &msg)
{
QJsonObject result, message;
QJsonArray messageArray;
if (!_streaming_logging_activated)
{
_streaming_logging_activated = true;
QVector<Logger::T_LOG_MESSAGE>* logBuffer = LoggerManager::getInstance()->getLogMessageBuffer();
for(int i=0; i<logBuffer->length(); i++)
{
message["appName"] = logBuffer->at(i).appName;
message["loggerName"] = logBuffer->at(i).loggerName;
message["function"] = logBuffer->at(i).function;
message["line"] = QString::number(logBuffer->at(i).line);
message["fileName"] = logBuffer->at(i).fileName;
message["message"] = logBuffer->at(i).message;
message["levelString"] = logBuffer->at(i).levelString;
messageArray.append(message);
}
}
else
{
message["appName"] = msg.appName;
message["loggerName"] = msg.loggerName;
message["function"] = msg.function;
message["line"] = QString::number(msg.line);
message["fileName"] = msg.fileName;
message["message"] = msg.message;
message["levelString"] = msg.levelString;
messageArray.append(message);
}
result.insert("messages", messageArray);
_streaming_logging_reply["result"] = result;
// send the result
emit callbackMessage(_streaming_logging_reply);
}
void JsonAPI::handlePendingTokenRequest(const QString& id, const QString& comment)
{
// just user sessions are allowed to react on this, to prevent that token authorized instances authorize new tokens on their own
if(_userAuthorized)
{
QJsonObject obj;
obj["command"] = "authorize-event";
obj["comment"] = comment;
obj["id"] = id;
emit callbackMessage(obj);
}
}
void JsonAPI::handleTokenResponse(const bool& success, QObject* caller, const QString& token, const QString& comment, const QString& id)
{
// if this is the requester, we send the reply
if(this == caller)
{
const QString cmd = "authorize-requestToken";
QJsonObject result;
result["token"] = token;
result["comment"] = comment;
result["id"] = id;
if(success)
sendSuccessDataReply(QJsonDocument(result), cmd);
else
sendErrorReply("Token request timeout or denied", cmd);
}
}
void JsonAPI::handleInstanceStateChange(const instanceState& state, const quint8& instance, const QString& name)
{
switch(state){
case H_ON_STOP:
if(_hyperion->getInstanceIndex() == instance)
{
handleInstanceSwitch();
}
break;
default:
break;
}
}