From 5da871dc9fd12295375076e86869fc81f3bf3cd5 Mon Sep 17 00:00:00 2001 From: brindosch Date: Sat, 24 Jun 2017 11:52:22 +0200 Subject: [PATCH] Move JsonProcessing from JsonClientConnection to own class (#444) * update * Tell me why... --- include/hyperion/Hyperion.h | 58 +- include/jsonserver/JsonServer.h | 26 +- include/utils/JsonProcessor.h | 268 ++++ libsrc/hyperion/Hyperion.cpp | 54 +- libsrc/jsonserver/JsonClientConnection.cpp | 1339 +------------------- libsrc/jsonserver/JsonClientConnection.h | 246 +--- libsrc/jsonserver/JsonServer.cpp | 100 +- libsrc/utils/CMakeLists.txt | 2 + libsrc/utils/JsonProcessor.cpp | 1242 ++++++++++++++++++ libsrc/utils/Stats.cpp | 4 +- 10 files changed, 1709 insertions(+), 1630 deletions(-) create mode 100644 include/utils/JsonProcessor.h create mode 100644 libsrc/utils/JsonProcessor.cpp diff --git a/include/hyperion/Hyperion.h b/include/hyperion/Hyperion.h index bf4c8c18..e1d5bb54 100644 --- a/include/hyperion/Hyperion.h +++ b/include/hyperion/Hyperion.h @@ -92,7 +92,7 @@ public: /// @return The current priority /// int getCurrentPriority() const; - + /// /// Returns a list of active priorities /// @@ -117,15 +117,15 @@ public: /// Get the list of available effects /// @return The list of available effects const std::list &getEffects() const; - + /// Get the list of active effects /// @return The list of active effects const std::list &getActiveEffects(); - + /// Get the list of available effect schema files /// @return The list of available effect schema files const std::list &getEffectSchemas(); - + /// gets the current json config object /// @return json config const QJsonObject& getQJsonConfig() { return _qjsonConfig; }; @@ -139,28 +139,28 @@ public: /// @param origin External setter /// @param priority priority channel void registerPriority(const QString &name, const int priority); - + /// unregister a input source to a priority channel /// @param name uniq name of input source void unRegisterPriority(const QString &name); - + /// gets current priority register /// @return the priority register const PriorityRegister& getPriorityRegister() { return _priorityRegister; } - + /// enable/disable automatic/priorized source selection /// @param enabled the state void setSourceAutoSelectEnabled(bool enabled); - + /// set current input source to visible /// @param priority the priority channel which should be vidible /// @return true if success, false on error bool setCurrentSourcePriority(int priority ); - + /// gets current state of automatic/priorized source selection /// @return the state bool sourceAutoSelectEnabled() { return _sourceAutoSelectEnabled; }; - + /// /// Enable/Disable components during runtime /// @@ -177,12 +177,10 @@ public: /// gets the methode how image is maped to leds int getLedMappingType() { return _ledMAppingType; }; - + int getConfigVersionId() { return _configVersionId; }; - + QJsonObject getConfig() { return _qjsonConfig; }; - - QString getConfigFile() { return _configFile; }; /// unique id per instance QString id; @@ -273,6 +271,9 @@ public slots: /// Hyperion::BonjourRegister getHyperionSessions(); + /// Slot which is called, when state of hyperion has been changed + void hyperionStateChanged(); + public: static Hyperion *_hyperion; @@ -311,8 +312,11 @@ signals: void emitImage(int priority, const Image & image, const int timeout_ms); void closing(); - /// Signal which is emitted, when state of config or bonjour or priorityMuxer changed - void hyperionStateChanged(); + /// Signal which is emitted, when a new json message should be forwarded + void forwardJsonMessage(QJsonObject); + + /// Signal which is emitted, after the hyperionStateChanged has been processed with a emit count blocker (250ms interval) + void sendServerInfo(); private slots: /// @@ -329,7 +333,7 @@ private slots: void checkConfigState(); private: - + /// /// Constructs the Hyperion instance based on the given Json configuration /// @@ -344,13 +348,13 @@ private: LedString _ledStringClone; std::vector _ledStringColorOrder; - + /// The priority muxer PriorityMuxer _muxer; /// The adjustment from raw colors to led colors MultiColorAdjustment * _raw2ledAdjustment; - + /// The actual LedDevice LedDevice * _device; @@ -359,7 +363,7 @@ private: /// Effect engine EffectEngine * _effectEngine; - + // proto and json Message forwarder MessageForwarder * _messageForwarder; @@ -383,24 +387,24 @@ private: unsigned _hwLedCount; ComponentRegister _componentRegister; - + /// register of input sources and it's prio channel PriorityRegister _priorityRegister; /// flag indicates state for autoselection of input source bool _sourceAutoSelectEnabled; - + /// holds the current priority channel that is manualy selected int _currentSourcePriority; QByteArray _configHash; QSize _ledGridSize; - + int _ledMAppingType; - + int _configVersionId; - + hyperion::Components _prevCompId; BonjourServiceBrowser _bonjourBrowser; BonjourServiceResolver _bonjourResolver; @@ -417,4 +421,8 @@ private: /// holds the current states of configWriteable and modified bool _configMod = false; bool _configWrite = true; + + /// timers to handle severinfo blocking + QTimer _fsi_timer; + QTimer _fsi_blockTimer; }; diff --git a/include/jsonserver/JsonServer.h b/include/jsonserver/JsonServer.h index 5ee71c18..935a8f05 100644 --- a/include/jsonserver/JsonServer.h +++ b/include/jsonserver/JsonServer.h @@ -6,7 +6,6 @@ // Qt includes #include #include -#include // Hyperion includes #include @@ -37,15 +36,12 @@ public: /// uint16_t getPort() const; + private slots: /// /// Slot which is called when a client tries to create a new connection /// void newConnection(); - /// - /// Slot which is called when a new forced serverinfo should be pushed - /// - void pushReq(); /// /// Slot which is called when a client closes a connection @@ -53,11 +49,25 @@ private slots: /// void closedConnection(JsonClientConnection * connection); + /// forward message to all json slaves + void forwardJsonMessage(const QJsonObject &message); + +public slots: + /// process current forwarder state + void componentStateChanged(const hyperion::Components component, bool enable); + + /// + /// forward message to a single json slaves + /// + /// @param message The JSON message to send + /// + void sendMessage(const QJsonObject & message, QTcpSocket * socket); + private: /// The TCP server object QTcpServer _server; - /// Link to Hyperion to get hyperion state emiter + /// Link to Hyperion to get config state emiter Hyperion * _hyperion; /// List with open connections @@ -66,6 +76,6 @@ private: /// the logger instance Logger * _log; - QTimer _timer; - QTimer _blockTimer; + /// Flag if forwarder is enabled + bool _forwarder_enabled = true; }; diff --git a/include/utils/JsonProcessor.h b/include/utils/JsonProcessor.h new file mode 100644 index 00000000..9c395356 --- /dev/null +++ b/include/utils/JsonProcessor.h @@ -0,0 +1,268 @@ +#pragma once + +// hyperion includes +#include +#include +#include +#include + +// qt includess +#include +#include +#include +#include + +// createEffect helper +struct find_schema: std::unary_function +{ + QString pyFile; + find_schema(QString pyFile):pyFile(pyFile) { } + bool operator()(EffectSchema const& schema) const + { + return schema.pyFile == pyFile; + } +}; + +// deleteEffect helper +struct find_effect: std::unary_function +{ + QString effectName; + find_effect(QString effectName) :effectName(effectName) { } + bool operator()(EffectDefinition const& effectDefinition) const + { + return effectDefinition.name == effectName; + } +}; + +class ImageProcessor; + +class JsonProcessor : public QObject +{ + Q_OBJECT + +public: + JsonProcessor(QString peerAddress); + ~JsonProcessor(); + + /// + /// Handle an incoming JSON message + /// + /// @param message the incoming message as string + /// + void handleMessage(const QString & message); + + /// + /// send a forced serverinfo to a client + /// + void forceServerInfo(); + +public slots: + /// _timer_ledcolors requests ledcolor updates (if enabled) + void streamLedcolorsUpdate(); + + /// push images whenever hyperion emits (if enabled) + void setImage(int priority, const Image & image, int duration_ms); + + /// process and push new log messages from logger (if enabled) + void incommingLogMessage(Logger::T_LOG_MESSAGE); + +signals: + /// + /// Signal which is emitted when a sendSuccessReply() has been executed + /// + void pushReq(); + /// + /// Signal emits with the reply message provided with handleMessage() + /// + void callbackMessage(QJsonObject); + + /// + /// Signal emits whenever a jsonmessage should be forwarded + /// + void forwardJsonMessage(QJsonObject); + +private: + /// The peer address of the client + QString _peerAddress; + + /// Log instance + Logger* _log; + + /// Hyperion instance + Hyperion* _hyperion; + + /// The processor for translating images to led-values + ImageProcessor * _imageProcessor; + + /// holds the state before off state + static std::map _componentsPrevState; + + /// returns if hyperion is on or off + inline bool hyperionIsActive() { return JsonProcessor::_componentsPrevState.empty(); }; + + /// timer for ledcolors streaming + QTimer _timer_ledcolors; + + // streaming buffers + QJsonObject _streaming_leds_reply; + QJsonObject _streaming_image_reply; + QJsonObject _streaming_logging_reply; + + /// flag to determine state of log streaming + bool _streaming_logging_activated; + + /// mutex to determine state of image streaming + QMutex _image_stream_mutex; + + /// timeout for live video refresh + volatile qint64 _image_stream_timeout; + + /// + /// Handle an incoming JSON Color message + /// + /// @param message the incoming message + /// + void handleColorCommand(const QJsonObject & message, const QString &command, const int tan); + + /// + /// Handle an incoming JSON Image message + /// + /// @param message the incoming message + /// + void handleImageCommand(const QJsonObject & message, const QString &command, const int tan); + + /// + /// Handle an incoming JSON Effect message + /// + /// @param message the incoming message + /// + void handleEffectCommand(const QJsonObject & message, const QString &command, const int tan); + + /// + /// Handle an incoming JSON Effect message (Write JSON Effect) + /// + /// @param message the incoming message + /// + void handleCreateEffectCommand(const QJsonObject & message, const QString &command, const int tan); + + /// + /// Handle an incoming JSON Effect message (Delete JSON Effect) + /// + /// @param message the incoming message + /// + void handleDeleteEffectCommand(const QJsonObject & message, const QString &command, const int tan); + + /// + /// Handle an incoming JSON System info message + /// + /// @param message the incoming message + /// + void handleSysInfoCommand(const QJsonObject & message, const QString &command, const int tan); + + /// + /// Handle an incoming JSON Server info message + /// + /// @param message the incoming message + /// + void handleServerInfoCommand(const QJsonObject & message, const QString &command, const int tan); + + /// + /// Handle an incoming JSON Clear message + /// + /// @param message the incoming message + /// + void handleClearCommand(const QJsonObject & message, const QString &command, const int tan); + + /// + /// Handle an incoming JSON Clearall message + /// + /// @param message the incoming message + /// + void handleClearallCommand(const QJsonObject & message, const QString &command, const int tan); + + /// + /// Handle an incoming JSON Adjustment message + /// + /// @param message the incoming message + /// + void handleAdjustmentCommand(const QJsonObject & message, const QString &command, const int tan); + + /// + /// Handle an incoming JSON SourceSelect message + /// + /// @param message the incoming message + /// + void handleSourceSelectCommand(const QJsonObject & message, const QString &command, const int tan); + + /// Handle an incoming JSON GetConfig message and check subcommand + /// + /// @param message the incoming message + /// + void handleConfigCommand(const QJsonObject & message, const QString &command, const int tan); + + /// Handle an incoming JSON GetConfig message from handleConfigCommand() + /// + /// @param message the incoming message + /// + void handleSchemaGetCommand(const QJsonObject & message, const QString &command, const int tan); + + /// Handle an incoming JSON GetConfig message from handleConfigCommand() + /// + /// @param message the incoming message + /// + void handleConfigGetCommand(const QJsonObject & message, const QString &command, const int tan); + + /// + /// Handle an incoming JSON Component State message + /// + /// @param message the incoming message + /// + void handleComponentStateCommand(const QJsonObject & message, const QString &command, const int tan); + + /// Handle an incoming JSON Led Colors message + /// + /// @param message the incoming message + /// + void handleLedColorsCommand(const QJsonObject & message, const QString &command, const int tan); + + /// Handle an incoming JSON Logging message + /// + /// @param message the incoming message + /// + void handleLoggingCommand(const QJsonObject & message, const QString &command, const int tan); + + /// Handle an incoming JSON Proccessing message + /// + /// @param message the incoming message + /// + void handleProcessingCommand(const QJsonObject & message, const QString &command, const int tan); + + /// + /// Handle an incoming JSON message of unknown type + /// + void handleNotImplemented(); + + /// + /// Send a standard reply indicating success + /// + void sendSuccessReply(const QString &command="", const int tan=0); + + /// + /// Send an error message back to the client + /// + /// @param error String describing the error + /// + void sendErrorReply(const QString & error, const QString &command="", const int tan=0); + + /// + /// Check if a JSON messag is valid according to a given JSON schema + /// + /// @param message JSON message which need to be checked + /// @param schemaResource Qt Resource identifier with the JSON schema + /// @param errors Output error message + /// @param ignoreRequired ignore the required value in JSON schema + /// + /// @return true if message conforms the given JSON schema + /// + bool checkJson(const QJsonObject & message, const QString &schemaResource, QString & errors, bool ignoreRequired = false); +}; diff --git a/libsrc/hyperion/Hyperion.cpp b/libsrc/hyperion/Hyperion.cpp index 689d603f..6b2dfc21 100644 --- a/libsrc/hyperion/Hyperion.cpp +++ b/libsrc/hyperion/Hyperion.cpp @@ -50,7 +50,7 @@ Hyperion* Hyperion::getInstance() { if ( Hyperion::_hyperion == nullptr ) throw std::runtime_error("Hyperion::getInstance used without call of Hyperion::initInstance before"); - + return Hyperion::_hyperion; } @@ -153,7 +153,7 @@ MultiColorAdjustment * Hyperion::createLedColorsAdjustment(const unsigned ledCnt ss << index; } } - Info(CORE_LOGGER, "ColorAdjustment '%s' => [%s]", QSTRING_CSTR(colorAdjustment->_id), ss.str().c_str()); + Info(CORE_LOGGER, "ColorAdjustment '%s' => [%s]", QSTRING_CSTR(colorAdjustment->_id), ss.str().c_str()); } return adjustment; @@ -190,7 +190,7 @@ LedString Hyperion::createLedString(const QJsonValue& ledsConfig, const ColorOrd const QString deviceOrderStr = colorOrderToString(deviceOrder); const QJsonArray & ledConfigArray = ledsConfig.toArray(); int maxLedId = ledConfigArray.size(); - + for (signed i = 0; i < ledConfigArray.size(); ++i) { const QJsonObject& index = ledConfigArray[i].toObject(); @@ -325,7 +325,7 @@ LinearColorSmoothing * Hyperion::createColorSmoothing(const QJsonObject & smooth QString type = smoothingConfig["type"].toString("linear").toLower(); LinearColorSmoothing * device = nullptr; type = "linear"; // TODO currently hardcoded type, delete it if we have more types - + if (type == "linear") { Info( CORE_LOGGER, "Creating linear smoothing"); @@ -341,7 +341,7 @@ LinearColorSmoothing * Hyperion::createColorSmoothing(const QJsonObject & smooth { Error(CORE_LOGGER, "Smoothing disabled, because of unknown type '%s'.", QSTRING_CSTR(type)); } - + device->setEnable(smoothingConfig["enable"].toBool(true)); InfoIf(!device->enabled(), CORE_LOGGER,"Smoothing disabled"); @@ -414,11 +414,11 @@ Hyperion::Hyperion(const QJsonObject &qjsonConfig, const QString configFile) _bonjourBrowser.browseForServiceType(QLatin1String("_hyperiond-http._tcp")); connect(&_bonjourBrowser, SIGNAL(currentBonjourRecordsChanged(const QList&)),this, SLOT(currentBonjourRecordsChanged(const QList &))); connect(&_bonjourResolver, SIGNAL(bonjourRecordResolved(const QHostInfo &, int)), this, SLOT(bonjourRecordResolved(const QHostInfo &, int))); - + // initialize the image processor factory _ledMAppingType = ImageProcessor::mappingTypeToInt(color["imageToLedMappingType"].toString()); ImageProcessorFactory::getInstance().init(_ledString, qjsonConfig["blackborderdetector"].toObject(),_ledMAppingType ); - + getComponentRegister().componentStateChanged(hyperion::COMP_FORWARDER, _messageForwarder->forwardingEnabled()); // initialize leddevices @@ -438,7 +438,7 @@ Hyperion::Hyperion(const QJsonObject &qjsonConfig, const QString configFile) // create the effect engine _effectEngine = new EffectEngine(this,qjsonConfig["effects"].toObject() ); - + const QJsonObject& device = qjsonConfig["device"].toObject(); unsigned int hwLedCount = device["ledCount"].toInt(getLedCount()); _hwLedCount = std::max(hwLedCount, getLedCount()); @@ -450,9 +450,14 @@ Hyperion::Hyperion(const QJsonObject &qjsonConfig, const QString configFile) QObject::connect(&_cTimer, SIGNAL(timeout()), this, SLOT(checkConfigState())); _cTimer.start(2000); - // pipe muxer signal for effect/color timerunner to hyperionStateChanged signal + // pipe muxer signal for effect/color timerunner to hyperionStateChanged slot QObject::connect(&_muxer, &PriorityMuxer::timerunner, this, &Hyperion::hyperionStateChanged); + // prepare processing of hyperionStateChanged for forced serverinfo + connect(&_fsi_timer, SIGNAL(timeout()), this, SLOT(hyperionStateChanged())); + _fsi_timer.setSingleShot(true); + _fsi_blockTimer.setSingleShot(true); + const QJsonObject & generalConfig = qjsonConfig["general"].toObject(); _configVersionId = generalConfig["configVersion"].toInt(-1); @@ -542,7 +547,7 @@ Hyperion::BonjourRegister Hyperion::getHyperionSessions() void Hyperion::checkConfigState() { - // Check config modifications + // Check config modifications QFile f(_configFile); if (f.open(QFile::ReadOnly)) { @@ -568,9 +573,9 @@ void Hyperion::checkConfigState() QFile file(_configFile); QFileInfo fileInfo(file); _configWrite = fileInfo.isWritable() && fileInfo.isReadable() ? true : false; - + if(_prevConfigWrite != _configWrite) - { + { emit hyperionStateChanged(); _prevConfigWrite = _configWrite; } @@ -579,7 +584,7 @@ void Hyperion::checkConfigState() void Hyperion::registerPriority(const QString &name, const int priority/*, const QString &origin*/) { Info(_log, "Register new input source named '%s' for priority channel '%d'", QSTRING_CSTR(name), priority ); - + for(auto key : _priorityRegister.keys()) { WarningIf( ( key != name && _priorityRegister.value(key) == priority), _log, @@ -734,7 +739,7 @@ void Hyperion::clearall() int Hyperion::getCurrentPriority() const { - + return _sourceAutoSelectEnabled || !_muxer.hasPriority(_currentSourcePriority) ? _muxer.getCurrentPriority() : _currentSourcePriority; } @@ -784,6 +789,19 @@ void Hyperion::setLedMappingType(int mappingType) emit imageToLedsMappingChanged(mappingType); } +void Hyperion::hyperionStateChanged() +{ + if(_fsi_blockTimer.isActive()) + { + _fsi_timer.start(300); + } + else + { + emit sendServerInfo(); + _fsi_blockTimer.start(250); + } +} + void Hyperion::update() { // Update the muxer, cleaning obsolete priorities @@ -817,13 +835,13 @@ void Hyperion::update() _ledStringColorOrder.insert(_ledStringColorOrder.begin() + led.index, led.colorOrder); } } - + // insert cloned leds into buffer for (Led& led : _ledStringClone.leds()) { _ledBuffer.insert(_ledBuffer.begin() + led.index, _ledBuffer.at(led.clone)); } - + int i = 0; for (ColorRgb& color : _ledBuffer) { @@ -861,7 +879,7 @@ void Hyperion::update() { _ledBuffer.resize(_hwLedCount, ColorRgb::BLACK); } - + // Write the data to the device if (_device->enabled()) { @@ -869,7 +887,7 @@ void Hyperion::update() // feed smoothing in pause mode to maintain a smooth transistion back to smoth mode if (_deviceSmooth->enabled() || _deviceSmooth->pause()) _deviceSmooth->setLedValues(_ledBuffer); - + if (! _deviceSmooth->enabled()) _device->setLedValues(_ledBuffer); } diff --git a/libsrc/jsonserver/JsonClientConnection.cpp b/libsrc/jsonserver/JsonClientConnection.cpp index 0af414cc..4afa8962 100644 --- a/libsrc/jsonserver/JsonClientConnection.cpp +++ b/libsrc/jsonserver/JsonClientConnection.cpp @@ -1,82 +1,35 @@ -// system includes -#include -#include -#include -#include - // stl includes -#include #include -#include // Qt includes -#include -#include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -// hyperion util includes -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include // project includes #include "JsonClientConnection.h" -using namespace hyperion; - -std::map JsonClientConnection::_componentsPrevState; - JsonClientConnection::JsonClientConnection(QTcpSocket *socket) : QObject() , _socket(socket) - , _imageProcessor(ImageProcessorFactory::getInstance().newImageProcessor()) , _hyperion(Hyperion::getInstance()) , _receiveBuffer() , _webSocketHandshakeDone(false) , _log(Logger::getInstance("JSONCLIENTCONNECTION")) - , _forwarder_enabled(true) - , _streaming_logging_activated(false) - , _image_stream_timeout(0) , _clientAddress(socket->peerAddress()) { // 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())); - _image_stream_mutex.unlock(); + // create a new instance of JsonProcessor + _jsonProcessor = new JsonProcessor(_clientAddress.toString()); + // get the callback messages from JsonProcessor and send it to the client + connect(_jsonProcessor,SIGNAL(callbackMessage(QJsonObject)),this,SLOT(sendMessage(QJsonObject))); } - JsonClientConnection::~JsonClientConnection() { delete _socket; + delete _jsonProcessor; } void JsonClientConnection::readData() @@ -94,7 +47,7 @@ void JsonClientConnection::readData() if(_receiveBuffer.contains("Upgrade: websocket")) { doWebSocketHandshake(); - } else + } else { // raw socket data, handling as usual int bytes = _receiveBuffer.indexOf('\n') + 1; @@ -107,11 +60,11 @@ void JsonClientConnection::readData() _receiveBuffer = _receiveBuffer.mid(bytes); // handle message - handleMessage(message); + _jsonProcessor->handleMessage(message); // try too look up '\n' again bytes = _receiveBuffer.indexOf('\n') + 1; - } + } } } } @@ -126,7 +79,7 @@ void JsonClientConnection::handleWebSocketFrame() bool isMasked = (_receiveBuffer.at(1) & BHB0_FIN) == BHB0_FIN; quint64 payloadLength = _receiveBuffer.at(1) & BHB1_PAYLOAD; quint32 index = 2; - + switch (payloadLength) { case payload_size_code_16bit: @@ -144,18 +97,18 @@ void JsonClientConnection::handleWebSocketFrame() 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); + maskKey[i] = _receiveBuffer.at(index + i); } index += 4; } - + // check the type of data frame switch (opCode) { @@ -164,7 +117,7 @@ void JsonClientConnection::handleWebSocketFrame() // frame contains text, extract it QByteArray result = _receiveBuffer.mid(index, payloadLength); _receiveBuffer.clear(); - + // unmask data if necessary if (isMasked) { @@ -178,8 +131,8 @@ void JsonClientConnection::handleWebSocketFrame() maskKey = NULL; } } - - handleMessage(QString(result)); + + _jsonProcessor->handleMessage(QString(result)); } break; case OPCODE::CLOSE: @@ -204,7 +157,7 @@ void JsonClientConnection::handleWebSocketFrame() else { Error(_log, "Someone is sending very big messages over several frames... it's not supported yet"); - quint8 close[] = {0x88, 0}; + quint8 close[] = {0x88, 0}; _socket->write((const char*)close, 2); _socket->flush(); _socket->close(); @@ -231,7 +184,7 @@ void JsonClientConnection::doWebSocketHandshake() std::ostringstream h; h << "HTTP/1.1 101 Switching Protocols\r\n" << "Upgrade: websocket\r\n" << - "Connection: Upgrade\r\n" << + "Connection: Upgrade\r\n" << "Sec-WebSocket-Accept: " << QString(hash.toBase64()).toStdString() << "\r\n\r\n"; _socket->write(h.str().c_str()); @@ -247,1075 +200,11 @@ void JsonClientConnection::socketClosed() emit connectionClosed(this); } -void JsonClientConnection::handleMessage(const QString& messageString) -{ - QString errors; - - try - { - QJsonParseError error; - QJsonDocument doc = QJsonDocument::fromJson(messageString.toUtf8(), &error); - - if (error.error != QJsonParseError::NoError) - { - // report to the user the failure and their locations in the document. - int errorLine(0), errorColumn(0); - - for( int i=0, count=qMin( error.offset,messageString.size()); i list = _hyperion->getForwarder()->getJsonSlaves(); - - for ( int i=0; i colorData(_hyperion->getLedCount()); - const QJsonArray & jsonColor = message["color"].toArray(); - unsigned int i = 0; - for (; i < unsigned(jsonColor.size()/3) && i < _hyperion->getLedCount(); ++i) - { - colorData[i].red = uint8_t(jsonColor.at(3u*i).toInt()); - colorData[i].green = uint8_t(jsonColor.at(3u*i+1u).toInt()); - colorData[i].blue = uint8_t(jsonColor.at(3u*i+2u).toInt()); - } - - // 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, true, hyperion::COMP_COLOR, origin); - - // send reply - sendSuccessReply(command, tan); -} - -void JsonClientConnection::handleImageCommand(const QJsonObject& message, const QString& command, const int tan) -{ - forwardJsonMessage(message); - - // extract parameters - int priority = message["priority"].toInt(); - int duration = message["duration"].toInt(-1); - int width = message["imagewidth"].toInt(); - int height = message["imageheight"].toInt(); - QByteArray data = QByteArray::fromBase64(QByteArray(message["imagedata"].toString().toUtf8())); - - // 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 image(width, height); - memcpy(image.memptr(), data.data(), data.size()); - - // process the image - std::vector ledColors = _imageProcessor->process(image); - _hyperion->setColors(priority, ledColors, duration); - - // send reply - sendSuccessReply(command, tan); -} - -void JsonClientConnection::handleEffectCommand(const QJsonObject& message, const QString& command, const int tan) -{ - 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() + "@"+_clientAddress.toString(); - const QJsonObject & effect = message["effect"].toObject(); - const QString & effectName = effect["name"].toString(); - - // set output - if (effect.contains("args")) - { - _hyperion->setEffect(effectName, effect["args"].toObject(), priority, duration, pythonScript, origin); - } - else - { - _hyperion->setEffect(effectName, priority, duration, origin); - } - - // send reply - sendSuccessReply(command, tan); -} - -void JsonClientConnection::handleCreateEffectCommand(const QJsonObject& message, const QString &command, const int tan) -{ - if(message.size() > 0) - { - if (!message["args"].toObject().isEmpty()) - { - QString scriptName; - (message["script"].toString().mid(0, 1) == ":" ) - ? scriptName = ":/effects//" + message["script"].toString().mid(1) - : scriptName = message["script"].toString(); - - std::list effectsSchemas = _hyperion->getEffectSchemas(); - std::list::iterator it = std::find_if(effectsSchemas.begin(), effectsSchemas.end(), find_schema(scriptName)); - - if (it != effectsSchemas.end()) - { - QString errors; - - if (!checkJson(message["args"].toObject(), it->schemaFile, errors)) - { - sendErrorReply("Error while validating json: " + errors, command, tan); - return; - } - - QJsonObject effectJson; - QJsonArray effectArray; - effectArray = _hyperion->getQJsonConfig()["effects"].toObject()["paths"].toArray(); - - if (effectArray.size() > 0) - { - if (message["name"].toString().trimmed().isEmpty() || message["name"].toString().trimmed().startsWith(".")) - { - sendErrorReply("Can't save new effect. Effect name is empty or begins with a dot.", command, tan); - return; - } - - effectJson["name"] = message["name"].toString(); - effectJson["script"] = message["script"].toString(); - effectJson["args"] = message["args"].toObject(); - - std::list availableEffects = _hyperion->getEffects(); - std::list::iterator iter = std::find_if(availableEffects.begin(), availableEffects.end(), find_effect(message["name"].toString())); - - QFileInfo newFileName; - if (iter != availableEffects.end()) - { - newFileName.setFile(iter->file); - if (newFileName.absoluteFilePath().mid(0, 1) == ":") - { - sendErrorReply("The effect name '" + message["name"].toString() + "' is assigned to an internal effect. Please rename your effekt.", command, tan); - return; - } - } else - { - newFileName.setFile(effectArray[0].toString() + QDir::separator() + message["name"].toString().replace(QString(" "), QString("")) + QString(".json")); - - while(newFileName.exists()) - { - newFileName.setFile(effectArray[0].toString() + QDir::separator() + newFileName.baseName() + QString::number(qrand() % ((10) - 0) + 0) + QString(".json")); - } - } - - QJsonFactory::writeJson(newFileName.absoluteFilePath(), effectJson); - Info(_log, "Reload effect list"); - _hyperion->reloadEffects(); - sendSuccessReply(command, tan); - } else - { - sendErrorReply("Can't save new effect. Effect path empty", command, tan); - return; - } - } else - sendErrorReply("Missing schema file for Python script " + message["script"].toString(), command, tan); - } else - sendErrorReply("Missing or empty Object 'args'", command, tan); - } else - sendErrorReply("Error while parsing json: Message size " + QString(message.size()), command, tan); -} - -void JsonClientConnection::handleDeleteEffectCommand(const QJsonObject& message, const QString& command, const int tan) -{ - if(message.size() > 0) - { - QString effectName = message["name"].toString(); - std::list effectsDefinition = _hyperion->getEffects(); - std::list::iterator it = std::find_if(effectsDefinition.begin(), effectsDefinition.end(), find_effect(effectName)); - - if (it != effectsDefinition.end()) - { - QFileInfo effectConfigurationFile(it->file); - if (effectConfigurationFile.absoluteFilePath().mid(0, 1) != ":" ) - { - if (effectConfigurationFile.exists()) - { - bool result = QFile::remove(effectConfigurationFile.absoluteFilePath()); - if (result) - { - Info(_log, "Reload effect list"); - _hyperion->reloadEffects(); - sendSuccessReply(command, tan); - } else - sendErrorReply("Can't delete effect configuration file: " + effectConfigurationFile.absoluteFilePath() + ". Please check permissions", command, tan); - } else - sendErrorReply("Can't find effect configuration file: " + effectConfigurationFile.absoluteFilePath(), command, tan); - } else - sendErrorReply("Can't delete internal effect: " + message["name"].toString(), command, tan); - } else - sendErrorReply("Effect " + message["name"].toString() + " not found", command, tan); - } else - sendErrorReply("Error while parsing json: Message size " + QString(message.size()), command, tan); -} - - -void JsonClientConnection::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["build" ] = QString(HYPERION_BUILD_ID); - hyperion["time" ] = QString(__DATE__ " " __TIME__); - hyperion["id" ] = _hyperion->id; - info["hyperion"] = hyperion; - - // send the result - result["info" ] = info; - sendMessage(result); -} - - -void JsonClientConnection::handleServerInfoCommand(const QJsonObject&, const QString& command, const int tan) -{ - // create result - QJsonObject result; - result["success"] = true; - result["command"] = command; - result["tan"] = tan; - - QJsonObject info; - - // collect priority information - QJsonArray priorities; - uint64_t now = QDateTime::currentMSecsSinceEpoch(); - QList activePriorities = _hyperion->getActivePriorities(); - Hyperion::PriorityRegister priorityRegister = _hyperion->getPriorityRegister(); - int currentPriority = _hyperion->getCurrentPriority(); - - foreach (int priority, activePriorities) { - const Hyperion::InputInfo & priorityInfo = _hyperion->getPriorityInfo(priority); - QJsonObject item; - item["priority"] = priority; - if (priorityInfo.timeoutTime_ms != -1 ) - { - item["duration_ms"] = int(priorityInfo.timeoutTime_ms - now); - } - - item["owner"] = QString(hyperion::componentToIdString(priorityInfo.componentId)); - item["componentId"] = QString(hyperion::componentToIdString(priorityInfo.componentId)); - item["origin"] = priorityInfo.origin; - item["active"] = true; - item["visible"] = (priority == currentPriority); - - // remove item from prio register, because we have more valuable information via active priority - QList prios = priorityRegister.keys(priority); - if (! prios.empty()) - { - item["owner"] = prios[0]; - priorityRegister.remove(prios[0]); - } - - if(priorityInfo.componentId == hyperion::COMP_COLOR) - { - 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); - - // add HEX Value to Array ["HEX Value"] - QJsonArray HEXValue; - 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); - - HEXValue.append(QString::fromStdString(hex.str())); - LEDcolor.insert("HEX", HEXValue); - - item["value"] = LEDcolor; - } - // priorities[priorities.size()] = item; - priorities.append(item); - } - - // append left over priorities - for(auto key : priorityRegister.keys()) - { - QJsonObject item; - item["priority"] = priorityRegister[key]; - item["active"] = false; - item["visible"] = false; - item["owner"] = key; - 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 blackAdjust; - blackAdjust.append(colorAdjustment->_rgbBlackAdjustment.getAdjustmentR()); - blackAdjust.append(colorAdjustment->_rgbBlackAdjustment.getAdjustmentG()); - blackAdjust.append(colorAdjustment->_rgbBlackAdjustment.getAdjustmentB()); - adjustment.insert("black", blackAdjust); - - 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 & 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"] =LedDevice::activeDevice(); - QJsonArray availableLedDevices; - for (auto dev: LedDevice::getDeviceMap()) - { - availableLedDevices.append(dev.first); - } - - ledDevices["available"] = availableLedDevices; - info["ledDevices"] = ledDevices; - - // get available grabbers - QJsonObject grabbers; - //grabbers["active"] = ????; - QJsonArray availableGrabbers; - for (auto grabber: GrabberWrapper::availableGrabbers()) - { - availableGrabbers.append(grabber); - } - - grabbers["available"] = availableGrabbers; - info["grabbers"] = grabbers; - - // get available components - QJsonArray component; - std::map 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["ledMAppingType"] = ImageProcessor::mappingTypeToStr(_hyperion->getLedMappingType()); - - // Add Hyperion - QJsonObject hyperion; - hyperion["config_modified" ] = _hyperion->configModified(); - hyperion["config_writeable"] = _hyperion->configWriteable(); - hyperion["off"] = hyperionIsActive()? false : true; - - // sessions - QJsonArray sessions; - for (auto session: _hyperion->getHyperionSessions()) - { - 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); - } - hyperion["sessions"] = sessions; - - info["hyperion"] = hyperion; - - // send the result - result["info"] = info; - sendMessage(result); -} - -void JsonClientConnection::handleClearCommand(const QJsonObject& message, const QString& command, const int tan) -{ - forwardJsonMessage(message); - - // extract parameters - int priority = message["priority"].toInt(); - - // clear priority - _hyperion->clear(priority); - - // send reply - sendSuccessReply(command, tan); -} - -void JsonClientConnection::handleClearallCommand(const QJsonObject& message, const QString& command, const int tan) -{ - forwardJsonMessage(message); - - // clear priority - _hyperion->clearall(); - - // send reply - sendSuccessReply(command, tan); -} - -void JsonClientConnection::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("black")) - { - const QJsonArray & values = adjustment["black"].toArray(); - colorAdjustment->_rgbBlackAdjustment.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 JsonClientConnection::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 JsonClientConnection::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 == "getconfig") - { - handleConfigGetCommand(message, 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 JsonClientConnection::handleConfigGetCommand(const QJsonObject& message, const QString& command, const int tan) -{ - // create result - QJsonObject result; - result["success"] = true; - result["command"] = command; - result["tan"] = tan; - - try - { - result["result"] = QJsonFactory::readConfig(_hyperion->getConfigFileName()); - } - catch(...) - { - result["result"] = _hyperion->getQJsonConfig(); - } - - // send the result - sendMessage(result); -} - -void JsonClientConnection::handleSchemaGetCommand(const QJsonObject& message, const QString& command, const int tan) -{ - // create result - QJsonObject result, schemaJson, alldevices, properties; - result["success"] = true; - result["command"] = command; - result["tan"] = tan; - - // make sure the resources are loaded (they may be left out after static linking) - Q_INIT_RESOURCE(resource); - QJsonParseError error; - - // read the hyperion json schema from the resource - QFile schemaData(":/hyperion-schema-"+QString::number(_hyperion->getConfigVersionId())); - - if (!schemaData.open(QIODevice::ReadOnly)) - { - std::stringstream error; - error << "Schema not found: " << schemaData.errorString().toStdString(); - throw std::runtime_error(error.str()); - } - - QByteArray schema = schemaData.readAll(); - QJsonDocument doc = QJsonDocument::fromJson(schema, &error); - schemaData.close(); - - if (error.error != QJsonParseError::NoError) - { - // report to the user the failure and their locations in the document. - int errorLine(0), errorColumn(0); - - for( int i=0, count=qMin( error.offset,schema.size()); i & 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); - - result["result"] = schemaJson; - - // send the result - sendMessage(result); -} - -void JsonClientConnection::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); - - if (compStr == "ALL" ) - { - if (hyperionIsActive() != compState) - { - std::map components = _hyperion->getComponentRegister().getRegister(); - - if (!compState) - { - JsonClientConnection::_componentsPrevState = components; - } - - for(auto comp : components) - { - _hyperion->setComponentState(comp.first, compState ? JsonClientConnection::_componentsPrevState[comp.first] : false); - } - - if (compState) - { - JsonClientConnection::_componentsPrevState.clear(); - } - } - - sendSuccessReply(command, tan); - return; - - } - else - { - Components component = stringToComponent(compStr); - - if (hyperionIsActive()) - { - if (component != COMP_INVALID) - { - _hyperion->setComponentState(component, compState); - sendSuccessReply(command, tan); - return; - } - sendErrorReply("invalid component name", command, tan); - return; - } - sendErrorReply("can't change component state when hyperion is off", command, tan); - } -} - -void JsonClientConnection::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"; - _streaming_leds_reply["tan"] = tan; - _timer_ledcolors.start(125); - } - else if (subcommand == "ledstream-stop") - { - _timer_ledcolors.stop(); - } - else if (subcommand == "imagestream-start") - { - _streaming_image_reply["success"] = true; - _streaming_image_reply["command"] = command+"-imagestream-update"; - _streaming_image_reply["tan"] = tan; - connect(_hyperion, SIGNAL(emitImage(int, const Image&, const int)), this, SLOT(setImage(int, const Image&, const int)) ); - } - else if (subcommand == "imagestream-stop") - { - disconnect(_hyperion, SIGNAL(emitImage(int, const Image&, const int)), this, 0 ); - } - else - { - sendErrorReply("unknown subcommand \""+subcommand+"\"",command,tan); - return; - } - - sendSuccessReply(command+"-"+subcommand,tan); -} - -void JsonClientConnection::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"); // 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"); - - } - } - else - { - sendErrorReply("unknown subcommand",command,tan); - return; - } - - sendSuccessReply(command+"-"+subcommand,tan); -} - -void JsonClientConnection::handleProcessingCommand(const QJsonObject& message, const QString &command, const int tan) -{ - _hyperion->setLedMappingType(ImageProcessor::mappingTypeToInt( message["mappingType"].toString("multicolor_mean")) ); - - sendSuccessReply(command, tan); -} - -void JsonClientConnection::incommingLogMessage(Logger::T_LOG_MESSAGE msg) -{ - QJsonObject result, message; - QJsonArray messageArray; - - if (!_streaming_logging_activated) - { - _streaming_logging_activated = true; - QVector* logBuffer = LoggerManager::getInstance()->getLogMessageBuffer(); - for(int i=0; ilength(); 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 - sendMessage(_streaming_logging_reply); -} - -void JsonClientConnection::handleNotImplemented() -{ - sendErrorReply("Command not implemented"); -} - -void JsonClientConnection::sendMessage(const QJsonObject &message) +void JsonClientConnection::sendMessage(QJsonObject message) { QJsonDocument writer(message); QByteArray serializedReply = writer.toJson(QJsonDocument::Compact) + "\n"; - + if (!_webSocketHandshakeDone) { // raw tcp socket mode @@ -1324,7 +213,7 @@ void JsonClientConnection::sendMessage(const QJsonObject &message) { // websocket mode quint32 size = serializedReply.length(); - + // prepare data frame QByteArray response; response.append(0x81); @@ -1336,193 +225,9 @@ void JsonClientConnection::sendMessage(const QJsonObject &message) } else { response.append(size); } - + response.append(serializedReply, serializedReply.length()); - + _socket->write(response.data(), response.length()); } } - - -void JsonClientConnection::sendMessage(const QJsonObject & message, QTcpSocket * socket) -{ - // serialize message - QJsonDocument writer(message); - QByteArray serializedMessage = writer.toJson(QJsonDocument::Compact) + "\n"; - - // write message - socket->write(serializedMessage); - 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(); - } - - // parse reply data - QJsonParseError error; - QJsonDocument reply = QJsonDocument::fromJson(serializedReply ,&error); - - if (error.error != QJsonParseError::NoError) - { - Error(_log, "Error while parsing reply: invalid json"); - return; - } - -} - -void JsonClientConnection::sendSuccessReply(const QString &command, const int tan) -{ - // create reply - QJsonObject reply; - reply["success"] = true; - reply["command"] = command; - reply["tan"] = tan; - - // send reply - sendMessage(reply); - - // blacklisted commands for emitter - QVector vector; - vector << "ledcolors-imagestream-stop" << "ledcolors-imagestream-start" << "ledcolors-ledstream-stop" << "ledcolors-ledstream-start" << "logging-start" << "logging-stop"; - if(vector.indexOf(command) == -1) - { - emit pushReq(); - } -} - -void JsonClientConnection::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 - sendMessage(reply); -} - -bool JsonClientConnection::checkJson(const QJsonObject& message, const QString& schemaResource, QString& errorMessage, bool ignoreRequired) -{ - // make sure the resources are loaded (they may be left out after static linking) - Q_INIT_RESOURCE(JsonSchemas); - QJsonParseError error; - - // read the json schema from the resource - QFile schemaData(schemaResource); - if (!schemaData.open(QIODevice::ReadOnly)) - { - errorMessage = "Schema error: " + schemaData.errorString(); - return false; - } - - // create schema checker - QByteArray schema = schemaData.readAll(); - QJsonDocument schemaJson = QJsonDocument::fromJson(schema, &error); - schemaData.close(); - - if (error.error != QJsonParseError::NoError) - { - // report to the user the failure and their locations in the document. - int errorLine(0), errorColumn(0); - - for( int i=0, count=qMin( error.offset,schema.size()); igetPriorityInfo(_hyperion->getCurrentPriority()); - for(auto color = priorityInfo.ledColors.begin(); color != priorityInfo.ledColors.end(); ++color) - { - QJsonObject item; - item["index"] = int(color - priorityInfo.ledColors.begin()); - item["red"] = color->red; - item["green"] = color->green; - item["blue"] = color->blue; - leds.append(item); - } - - result["leds"] = leds; - _streaming_leds_reply["result"] = result; - - // send the result - sendMessage(_streaming_leds_reply); -} - -void JsonClientConnection::setImage(int priority, const Image & image, int duration_ms) -{ - if ( (_image_stream_timeout+250) < QDateTime::currentMSecsSinceEpoch() && _image_stream_mutex.tryLock(0) ) - { - _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; - sendMessage(_streaming_image_reply); - - _image_stream_mutex.unlock(); - } -} - -void JsonClientConnection::forceServerInfo() -{ - const QString command("serverinfo"); - const int tan = 1; - const QJsonObject obj; - handleServerInfoCommand(obj,command,tan); -} - diff --git a/libsrc/jsonserver/JsonClientConnection.h b/libsrc/jsonserver/JsonClientConnection.h index f925fa6e..405a5464 100644 --- a/libsrc/jsonserver/JsonClientConnection.h +++ b/libsrc/jsonserver/JsonClientConnection.h @@ -5,7 +5,6 @@ // Qt includes #include #include -#include #include #include @@ -13,12 +12,8 @@ #include // util includes -#include #include -#include - -class ImageProcessor; - +#include /// Constants and utility functions related to WebSocket opcodes /** @@ -74,26 +69,6 @@ namespace OPCODE { } } -struct find_schema: std::unary_function -{ - QString pyFile; - find_schema(QString pyFile):pyFile(pyFile) { } - bool operator()(EffectSchema const& schema) const - { - return schema.pyFile == pyFile; - } -}; - -struct find_effect: std::unary_function -{ - QString effectName; - find_effect(QString effectName) :effectName(effectName) { } - bool operator()(EffectDefinition const& effectDefinition) const - { - return effectDefinition.name == effectName; - } -}; - /// /// The Connection object created by \a JsonServer when a new connection is establshed /// @@ -105,7 +80,6 @@ public: /// /// Constructor /// @param socket The Socket object for this connection - /// @param hyperion The Hyperion server /// JsonClientConnection(QTcpSocket * socket); @@ -114,16 +88,8 @@ public: /// ~JsonClientConnection(); - /// - /// send a forced serverinfo to a client - /// - void forceServerInfo(); - public slots: - void componentStateChanged(const hyperion::Components component, bool enable); - void streamLedcolorsUpdate(); - void incommingLogMessage(Logger::T_LOG_MESSAGE); - void setImage(int priority, const Image & image, int duration_ms); + void sendMessage(QJsonObject); signals: /// @@ -132,11 +98,6 @@ signals: /// void connectionClosed(JsonClientConnection * connection); - /// - /// Signal which is emitted when a sendSuccessReply() has been executed - /// - void pushReq(); - private slots: /// /// Slot called when new data has arrived @@ -150,232 +111,37 @@ private slots: private: - /// - /// Handle an incoming JSON message - /// - /// @param message the incoming message as string - /// - void handleMessage(const QString & message); + /// new instance of JsonProcessor + JsonProcessor * _jsonProcessor; - /// - /// Handle an incoming JSON Color message - /// - /// @param message the incoming message - /// - void handleColorCommand(const QJsonObject & message, const QString &command, const int tan); - - /// - /// Handle an incoming JSON Image message - /// - /// @param message the incoming message - /// - void handleImageCommand(const QJsonObject & message, const QString &command, const int tan); - - /// - /// Handle an incoming JSON Effect message - /// - /// @param message the incoming message - /// - void handleEffectCommand(const QJsonObject & message, const QString &command, const int tan); - - /// - /// Handle an incoming JSON Effect message (Write JSON Effect) - /// - /// @param message the incoming message - /// - void handleCreateEffectCommand(const QJsonObject & message, const QString &command, const int tan); - - /// - /// Handle an incoming JSON Effect message (Delete JSON Effect) - /// - /// @param message the incoming message - /// - void handleDeleteEffectCommand(const QJsonObject & message, const QString &command, const int tan); - - /// - /// Handle an incoming JSON Server info message - /// - /// @param message the incoming message - /// - void handleServerInfoCommand(const QJsonObject & message, const QString &command, const int tan); - - /// - /// Handle an incoming JSON System info message - /// - /// @param message the incoming message - /// - void handleSysInfoCommand(const QJsonObject & message, const QString &command, const int tan); - - /// - /// Handle an incoming JSON Clear message - /// - /// @param message the incoming message - /// - void handleClearCommand(const QJsonObject & message, const QString &command, const int tan); - - /// - /// Handle an incoming JSON Clearall message - /// - /// @param message the incoming message - /// - void handleClearallCommand(const QJsonObject & message, const QString &command, const int tan); - - /// - /// Handle an incoming JSON Adjustment message - /// - /// @param message the incoming message - /// - void handleAdjustmentCommand(const QJsonObject & message, const QString &command, const int tan); - - /// - /// Handle an incoming JSON SourceSelect message - /// - /// @param message the incoming message - /// - void handleSourceSelectCommand(const QJsonObject & message, const QString &command, const int tan); - - /// Handle an incoming JSON GetConfig message - /// - /// @param message the incoming message - /// - void handleConfigCommand(const QJsonObject & message, const QString &command, const int tan); - - /// Handle an incoming JSON GetConfig message - /// - /// @param message the incoming message - /// - void handleSchemaGetCommand(const QJsonObject & message, const QString &command, const int tan); - - /// Handle an incoming JSON GetConfig message - /// - /// @param message the incoming message - /// - void handleConfigGetCommand(const QJsonObject & message, const QString &command, const int tan); - - /// - /// Handle an incoming JSON Component State message - /// - /// @param message the incoming message - /// - void handleComponentStateCommand(const QJsonObject & message, const QString &command, const int tan); - - /// Handle an incoming JSON Led Colors message - /// - /// @param message the incoming message - /// - void handleLedColorsCommand(const QJsonObject & message, const QString &command, const int tan); - - /// Handle an incoming JSON Logging message - /// - /// @param message the incoming message - /// - void handleLoggingCommand(const QJsonObject & message, const QString &command, const int tan); - - /// Handle an incoming JSON Proccessing message - /// - /// @param message the incoming message - /// - void handleProcessingCommand(const QJsonObject & message, const QString &command, const int tan); - - /// - /// Handle an incoming JSON message of unknown type - /// - void handleNotImplemented(); - - /// - /// Send a message to the connected client - /// - /// @param message The JSON message to send - /// - void sendMessage(const QJsonObject & message); - void sendMessage(const QJsonObject & message, QTcpSocket * socket); - - /// - /// Send a standard reply indicating success - /// - void sendSuccessReply(const QString &command="", const int tan=0); - - /// - /// Send an error message back to the client - /// - /// @param error String describing the error - /// - void sendErrorReply(const QString & error, const QString &command="", const int tan=0); - /// /// Do handshake for a websocket connection /// void doWebSocketHandshake(); - + /// /// Handle incoming websocket data frame /// void handleWebSocketFrame(); - /// - /// forward json message - /// - void forwardJsonMessage(const QJsonObject & message); - - /// - /// Check if a JSON messag is valid according to a given JSON schema - /// - /// @param message JSON message which need to be checked - /// @param schemaResource Qt Resource identifier with the JSON schema - /// @param errors Output error message - /// @param ignoreRequired ignore the required value in JSON schema - /// - /// @return true if message conforms the given JSON schema - /// - bool checkJson(const QJsonObject & message, const QString &schemaResource, QString & errors, bool ignoreRequired = false); - - /// returns if hyperion is on or off - inline bool hyperionIsActive() { return JsonClientConnection::_componentsPrevState.empty(); }; - /// The TCP-Socket that is connected tot the Json-client QTcpSocket * _socket; - /// The processor for translating images to led-values - ImageProcessor * _imageProcessor; - /// Link to Hyperion for writing led-values to a priority channel Hyperion * _hyperion; /// The buffer used for reading data from the socket QByteArray _receiveBuffer; - + /// used for WebSocket detection and connection handling bool _webSocketHandshakeDone; /// The logger instance Logger * _log; - /// Flag if forwarder is enabled - bool _forwarder_enabled; - - /// timer for ledcolors streaming - QTimer _timer_ledcolors; - - // streaming buffers - QJsonObject _streaming_leds_reply; - QJsonObject _streaming_image_reply; - QJsonObject _streaming_logging_reply; - - /// flag to determine state of log streaming - bool _streaming_logging_activated; - - /// mutex to determine state of image streaming - QMutex _image_stream_mutex; - - /// timeout for live video refresh - volatile qint64 _image_stream_timeout; - /// address of client QHostAddress _clientAddress; - /// holds the state before off state - static std::map _componentsPrevState; - // masks for fields in the basic header static uint8_t const BHB0_OPCODE = 0x0F; static uint8_t const BHB0_RSV3 = 0x10; diff --git a/libsrc/jsonserver/JsonServer.cpp b/libsrc/jsonserver/JsonServer.cpp index 56e4bc9c..ac30f0ff 100644 --- a/libsrc/jsonserver/JsonServer.cpp +++ b/libsrc/jsonserver/JsonServer.cpp @@ -5,6 +5,14 @@ #include #include "JsonClientConnection.h" +// hyperion include +#include + +// qt includes +#include +#include +#include + JsonServer::JsonServer(uint16_t port) : QObject() , _server() @@ -28,17 +36,14 @@ JsonServer::JsonServer(uint16_t port) // Set trigger for incoming connections connect(&_server, SIGNAL(newConnection()), this, SLOT(newConnection())); - // connect delay timer and setup - connect(&_timer, SIGNAL(timeout()), this, SLOT(pushReq())); - _timer.setSingleShot(true); - _blockTimer.setSingleShot(true); - - // register for hyprion state changes (bonjour, config, prioritymuxer, register/unregister source) - connect(_hyperion, SIGNAL(hyperionStateChanged()), this, SLOT(pushReq())); - // make sure the resources are loaded (they may be left out after static linking Q_INIT_RESOURCE(JsonSchemas); + // receive state of forwarder + connect(_hyperion, &Hyperion::componentStateChanged, this, &JsonServer::componentStateChanged); + + // initial connect TODO get initial state from config to stop messing + connect(_hyperion, &Hyperion::forwardJsonMessage, this, &JsonServer::forwardJsonMessage); } JsonServer::~JsonServer() @@ -63,9 +68,6 @@ void JsonServer::newConnection() JsonClientConnection * connection = new JsonClientConnection(socket); _openConnections.insert(connection); - // register for JSONClientConnection events - connect(connection, SIGNAL(pushReq()), this, SLOT(pushReq())); - // register slot for cleaning up after the connection closed connect(connection, SIGNAL(connectionClosed(JsonClientConnection*)), this, SLOT(closedConnection(JsonClientConnection*))); } @@ -80,17 +82,75 @@ void JsonServer::closedConnection(JsonClientConnection *connection) connection->deleteLater(); } -void JsonServer::pushReq() +void JsonServer::componentStateChanged(const hyperion::Components component, bool enable) { - if(_blockTimer.isActive()) + if (component == hyperion::COMP_FORWARDER && _forwarder_enabled != enable) { - _timer.start(250); - } - else - { - foreach (JsonClientConnection * connection, _openConnections) { - connection->forceServerInfo(); + _forwarder_enabled = enable; + Info(_log, "forwarder change state to %s", (enable ? "enabled" : "disabled") ); + if(_forwarder_enabled) + { + connect(_hyperion, &Hyperion::forwardJsonMessage, this, &JsonServer::forwardJsonMessage); + } + else + { + disconnect(_hyperion, &Hyperion::forwardJsonMessage, this, &JsonServer::forwardJsonMessage); } - _blockTimer.start(250); } } + +void JsonServer::forwardJsonMessage(const QJsonObject &message) +{ + QTcpSocket client; + QList list = _hyperion->getForwarder()->getJsonSlaves(); + + for ( int i=0; iwrite(serializedMessage); + 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(); + } + + // parse reply data + QJsonParseError error; + QJsonDocument reply = QJsonDocument::fromJson(serializedReply ,&error); + + if (error.error != QJsonParseError::NoError) + { + Error(_log, "Error while parsing reply: invalid json"); + return; + } + +} diff --git a/libsrc/utils/CMakeLists.txt b/libsrc/utils/CMakeLists.txt index d1a4b365..a996d390 100644 --- a/libsrc/utils/CMakeLists.txt +++ b/libsrc/utils/CMakeLists.txt @@ -6,6 +6,7 @@ SET(CURRENT_SOURCE_DIR ${CMAKE_SOURCE_DIR}/libsrc/utils) SET(Utils_QT_HEADERS ${CURRENT_HEADER_DIR}/Logger.h ${CURRENT_HEADER_DIR}/Stats.h + ${CURRENT_HEADER_DIR}/JsonProcessor.h ) SET(Utils_HEADERS @@ -46,6 +47,7 @@ SET(Utils_SOURCES ${CURRENT_SOURCE_DIR}/RgbToRgbw.cpp ${CURRENT_SOURCE_DIR}/SysInfo.cpp ${CURRENT_SOURCE_DIR}/Stats.cpp + ${CURRENT_SOURCE_DIR}/JsonProcessor.cpp ${CURRENT_SOURCE_DIR}/jsonschema/QJsonSchemaChecker.cpp ) diff --git a/libsrc/utils/JsonProcessor.cpp b/libsrc/utils/JsonProcessor.cpp new file mode 100644 index 00000000..f0b1d15e --- /dev/null +++ b/libsrc/utils/JsonProcessor.cpp @@ -0,0 +1,1242 @@ + +// project includes +#include + +// stl includes +#include +#include +#include +#include + +// Qt includes +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// hyperion includes +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace hyperion; + +std::map JsonProcessor::_componentsPrevState; + +JsonProcessor::JsonProcessor(QString peerAddress) + : QObject() + , _peerAddress(peerAddress) + , _log(Logger::getInstance("JSONRPCPROCESSOR")) + , _hyperion(Hyperion::getInstance()) + , _imageProcessor(ImageProcessorFactory::getInstance().newImageProcessor()) + , _streaming_logging_activated(false) + , _image_stream_timeout(0) +{ + // notify hyperion about a jsonMessageForward + connect(this, &JsonProcessor::forwardJsonMessage, _hyperion, &Hyperion::forwardJsonMessage); + // notify hyperion about a push emit + connect(this, &JsonProcessor::pushReq, _hyperion, &Hyperion::hyperionStateChanged); + // listen for sendServerInfo pushes from hyperion + connect(_hyperion, &Hyperion::sendServerInfo, this, &JsonProcessor::forceServerInfo); + + // led color stream update timer + _timer_ledcolors.setSingleShot(false); + connect(&_timer_ledcolors, SIGNAL(timeout()), this, SLOT(streamLedcolorsUpdate())); + _image_stream_mutex.unlock(); +} + +JsonProcessor::~JsonProcessor() +{ + +} + +void JsonProcessor::handleMessage(const QString& messageString) +{ + QString errors; + + try + { + QJsonParseError error; + QJsonDocument doc = QJsonDocument::fromJson(messageString.toUtf8(), &error); + + if (error.error != QJsonParseError::NoError) + { + // report to the user the failure and their locations in the document. + int errorLine(0), errorColumn(0); + + for( int i=0, count=qMin( error.offset,messageString.size()); i colorData(_hyperion->getLedCount()); + const QJsonArray & jsonColor = message["color"].toArray(); + unsigned int i = 0; + for (; i < unsigned(jsonColor.size()/3) && i < _hyperion->getLedCount(); ++i) + { + colorData[i].red = uint8_t(jsonColor.at(3u*i).toInt()); + colorData[i].green = uint8_t(jsonColor.at(3u*i+1u).toInt()); + colorData[i].blue = uint8_t(jsonColor.at(3u*i+2u).toInt()); + } + + // 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, true, hyperion::COMP_COLOR, origin); + + // send reply + sendSuccessReply(command, tan); +} + +void JsonProcessor::handleImageCommand(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); + int width = message["imagewidth"].toInt(); + int height = message["imageheight"].toInt(); + QByteArray data = QByteArray::fromBase64(QByteArray(message["imagedata"].toString().toUtf8())); + + // 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 image(width, height); + memcpy(image.memptr(), data.data(), data.size()); + + // process the image + std::vector ledColors = _imageProcessor->process(image); + _hyperion->setColors(priority, ledColors, duration); + + // send reply + sendSuccessReply(command, tan); +} + +void JsonProcessor::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() + "@"+_peerAddress; + const QJsonObject & effect = message["effect"].toObject(); + const QString & effectName = effect["name"].toString(); + + // set output + if (effect.contains("args")) + { + _hyperion->setEffect(effectName, effect["args"].toObject(), priority, duration, pythonScript, origin); + } + else + { + _hyperion->setEffect(effectName, priority, duration, origin); + } + + // send reply + sendSuccessReply(command, tan); +} + +void JsonProcessor::handleCreateEffectCommand(const QJsonObject& message, const QString &command, const int tan) +{ + if(message.size() > 0) + { + if (!message["args"].toObject().isEmpty()) + { + QString scriptName; + (message["script"].toString().mid(0, 1) == ":" ) + ? scriptName = ":/effects//" + message["script"].toString().mid(1) + : scriptName = message["script"].toString(); + + std::list effectsSchemas = _hyperion->getEffectSchemas(); + std::list::iterator it = std::find_if(effectsSchemas.begin(), effectsSchemas.end(), find_schema(scriptName)); + + if (it != effectsSchemas.end()) + { + QString errors; + + if (!checkJson(message["args"].toObject(), it->schemaFile, errors)) + { + sendErrorReply("Error while validating json: " + errors, command, tan); + return; + } + + QJsonObject effectJson; + QJsonArray effectArray; + effectArray = _hyperion->getQJsonConfig()["effects"].toObject()["paths"].toArray(); + + if (effectArray.size() > 0) + { + if (message["name"].toString().trimmed().isEmpty() || message["name"].toString().trimmed().startsWith(".")) + { + sendErrorReply("Can't save new effect. Effect name is empty or begins with a dot.", command, tan); + return; + } + + effectJson["name"] = message["name"].toString(); + effectJson["script"] = message["script"].toString(); + effectJson["args"] = message["args"].toObject(); + + std::list availableEffects = _hyperion->getEffects(); + std::list::iterator iter = std::find_if(availableEffects.begin(), availableEffects.end(), find_effect(message["name"].toString())); + + QFileInfo newFileName; + if (iter != availableEffects.end()) + { + newFileName.setFile(iter->file); + if (newFileName.absoluteFilePath().mid(0, 1) == ":") + { + sendErrorReply("The effect name '" + message["name"].toString() + "' is assigned to an internal effect. Please rename your effekt.", command, tan); + return; + } + } else + { + newFileName.setFile(effectArray[0].toString() + QDir::separator() + message["name"].toString().replace(QString(" "), QString("")) + QString(".json")); + + while(newFileName.exists()) + { + newFileName.setFile(effectArray[0].toString() + QDir::separator() + newFileName.baseName() + QString::number(qrand() % ((10) - 0) + 0) + QString(".json")); + } + } + + QJsonFactory::writeJson(newFileName.absoluteFilePath(), effectJson); + Info(_log, "Reload effect list"); + _hyperion->reloadEffects(); + sendSuccessReply(command, tan); + } else + { + sendErrorReply("Can't save new effect. Effect path empty", command, tan); + return; + } + } else + sendErrorReply("Missing schema file for Python script " + message["script"].toString(), command, tan); + } else + sendErrorReply("Missing or empty Object 'args'", command, tan); + } else + sendErrorReply("Error while parsing json: Message size " + QString(message.size()), command, tan); +} + +void JsonProcessor::handleDeleteEffectCommand(const QJsonObject& message, const QString& command, const int tan) +{ + if(message.size() > 0) + { + QString effectName = message["name"].toString(); + std::list effectsDefinition = _hyperion->getEffects(); + std::list::iterator it = std::find_if(effectsDefinition.begin(), effectsDefinition.end(), find_effect(effectName)); + + if (it != effectsDefinition.end()) + { + QFileInfo effectConfigurationFile(it->file); + if (effectConfigurationFile.absoluteFilePath().mid(0, 1) != ":" ) + { + if (effectConfigurationFile.exists()) + { + bool result = QFile::remove(effectConfigurationFile.absoluteFilePath()); + if (result) + { + Info(_log, "Reload effect list"); + _hyperion->reloadEffects(); + sendSuccessReply(command, tan); + } else + sendErrorReply("Can't delete effect configuration file: " + effectConfigurationFile.absoluteFilePath() + ". Please check permissions", command, tan); + } else + sendErrorReply("Can't find effect configuration file: " + effectConfigurationFile.absoluteFilePath(), command, tan); + } else + sendErrorReply("Can't delete internal effect: " + message["name"].toString(), command, tan); + } else + sendErrorReply("Effect " + message["name"].toString() + " not found", command, tan); + } else + sendErrorReply("Error while parsing json: Message size " + QString(message.size()), command, tan); +} + +void JsonProcessor::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["build" ] = QString(HYPERION_BUILD_ID); + hyperion["time" ] = QString(__DATE__ " " __TIME__); + hyperion["id" ] = _hyperion->id; + info["hyperion"] = hyperion; + + // send the result + result["info" ] = info; + emit callbackMessage(result); +} + +void JsonProcessor::handleServerInfoCommand(const QJsonObject&, const QString& command, const int tan) +{ + // create result + QJsonObject result; + result["success"] = true; + result["command"] = command; + result["tan"] = tan; + + QJsonObject info; + + // collect priority information + QJsonArray priorities; + uint64_t now = QDateTime::currentMSecsSinceEpoch(); + QList activePriorities = _hyperion->getActivePriorities(); + Hyperion::PriorityRegister priorityRegister = _hyperion->getPriorityRegister(); + int currentPriority = _hyperion->getCurrentPriority(); + + foreach (int priority, activePriorities) { + const Hyperion::InputInfo & priorityInfo = _hyperion->getPriorityInfo(priority); + QJsonObject item; + item["priority"] = priority; + if (priorityInfo.timeoutTime_ms != -1 ) + { + item["duration_ms"] = int(priorityInfo.timeoutTime_ms - now); + } + + item["owner"] = QString(hyperion::componentToIdString(priorityInfo.componentId)); + item["componentId"] = QString(hyperion::componentToIdString(priorityInfo.componentId)); + item["origin"] = priorityInfo.origin; + item["active"] = true; + item["visible"] = (priority == currentPriority); + + // remove item from prio register, because we have more valuable information via active priority + QList prios = priorityRegister.keys(priority); + if (! prios.empty()) + { + item["owner"] = prios[0]; + priorityRegister.remove(prios[0]); + } + + if(priorityInfo.componentId == hyperion::COMP_COLOR) + { + 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); + + // add HEX Value to Array ["HEX Value"] + QJsonArray HEXValue; + 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); + + HEXValue.append(QString::fromStdString(hex.str())); + LEDcolor.insert("HEX", HEXValue); + + item["value"] = LEDcolor; + } + // priorities[priorities.size()] = item; + priorities.append(item); + } + + // append left over priorities + for(auto key : priorityRegister.keys()) + { + QJsonObject item; + item["priority"] = priorityRegister[key]; + item["active"] = false; + item["visible"] = false; + item["owner"] = key; + 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 blackAdjust; + blackAdjust.append(colorAdjustment->_rgbBlackAdjustment.getAdjustmentR()); + blackAdjust.append(colorAdjustment->_rgbBlackAdjustment.getAdjustmentG()); + blackAdjust.append(colorAdjustment->_rgbBlackAdjustment.getAdjustmentB()); + adjustment.insert("black", blackAdjust); + + 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 & 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"] =LedDevice::activeDevice(); + QJsonArray availableLedDevices; + for (auto dev: LedDevice::getDeviceMap()) + { + availableLedDevices.append(dev.first); + } + + ledDevices["available"] = availableLedDevices; + info["ledDevices"] = ledDevices; + + // get available grabbers + QJsonObject grabbers; + //grabbers["active"] = ????; + QJsonArray availableGrabbers; + for (auto grabber: GrabberWrapper::availableGrabbers()) + { + availableGrabbers.append(grabber); + } + + grabbers["available"] = availableGrabbers; + info["grabbers"] = grabbers; + + // get available components + QJsonArray component; + std::map 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["ledMAppingType"] = ImageProcessor::mappingTypeToStr(_hyperion->getLedMappingType()); + + // Add Hyperion + QJsonObject hyperion; + hyperion["config_modified" ] = _hyperion->configModified(); + hyperion["config_writeable"] = _hyperion->configWriteable(); + hyperion["off"] = hyperionIsActive()? false : true; + + // sessions + QJsonArray sessions; + for (auto session: _hyperion->getHyperionSessions()) + { + 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); + } + hyperion["sessions"] = sessions; + + info["hyperion"] = hyperion; + + // send the result + result["info"] = info; + emit callbackMessage(result); +} + +void JsonProcessor::handleClearCommand(const QJsonObject& message, const QString& command, const int tan) +{ + emit forwardJsonMessage(message); + + // extract parameters + int priority = message["priority"].toInt(); + + // clear priority + _hyperion->clear(priority); + + // send reply + sendSuccessReply(command, tan); +} + +void JsonProcessor::handleClearallCommand(const QJsonObject& message, const QString& command, const int tan) +{ + emit forwardJsonMessage(message); + + // clear priority + _hyperion->clearall(); + + // send reply + sendSuccessReply(command, tan); +} + +void JsonProcessor::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("black")) + { + const QJsonArray & values = adjustment["black"].toArray(); + colorAdjustment->_rgbBlackAdjustment.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 JsonProcessor::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 JsonProcessor::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 == "getconfig") + { + handleConfigGetCommand(message, 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 JsonProcessor::handleConfigGetCommand(const QJsonObject& message, const QString& command, const int tan) +{ + // create result + QJsonObject result; + result["success"] = true; + result["command"] = command; + result["tan"] = tan; + + try + { + result["result"] = QJsonFactory::readConfig(_hyperion->getConfigFileName()); + } + catch(...) + { + result["result"] = _hyperion->getQJsonConfig(); + } + + // send the result + emit callbackMessage(result); +} + +void JsonProcessor::handleSchemaGetCommand(const QJsonObject& message, const QString& command, const int tan) +{ + // create result + QJsonObject result, schemaJson, alldevices, properties; + result["success"] = true; + result["command"] = command; + result["tan"] = tan; + + // make sure the resources are loaded (they may be left out after static linking) + Q_INIT_RESOURCE(resource); + QJsonParseError error; + + // read the hyperion json schema from the resource + QFile schemaData(":/hyperion-schema-"+QString::number(_hyperion->getConfigVersionId())); + + if (!schemaData.open(QIODevice::ReadOnly)) + { + std::stringstream error; + error << "Schema not found: " << schemaData.errorString().toStdString(); + throw std::runtime_error(error.str()); + } + + QByteArray schema = schemaData.readAll(); + QJsonDocument doc = QJsonDocument::fromJson(schema, &error); + schemaData.close(); + + if (error.error != QJsonParseError::NoError) + { + // report to the user the failure and their locations in the document. + int errorLine(0), errorColumn(0); + + for( int i=0, count=qMin( error.offset,schema.size()); i & 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); + + result["result"] = schemaJson; + + // send the result + emit callbackMessage(result); +} + +void JsonProcessor::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); + + if (compStr == "ALL" ) + { + if (hyperionIsActive() != compState) + { + std::map components = _hyperion->getComponentRegister().getRegister(); + + if (!compState) + { + JsonProcessor::_componentsPrevState = components; + } + + for(auto comp : components) + { + _hyperion->setComponentState(comp.first, compState ? JsonProcessor::_componentsPrevState[comp.first] : false); + } + + if (compState) + { + JsonProcessor::_componentsPrevState.clear(); + } + } + + sendSuccessReply(command, tan); + return; + + } + else + { + Components component = stringToComponent(compStr); + + if (hyperionIsActive()) + { + if (component != COMP_INVALID) + { + _hyperion->setComponentState(component, compState); + sendSuccessReply(command, tan); + return; + } + sendErrorReply("invalid component name", command, tan); + return; + } + sendErrorReply("can't change component state when hyperion is off", command, tan); + } +} + +void JsonProcessor::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"; + _streaming_leds_reply["tan"] = tan; + _timer_ledcolors.start(125); + } + else if (subcommand == "ledstream-stop") + { + _timer_ledcolors.stop(); + } + else if (subcommand == "imagestream-start") + { + _streaming_image_reply["success"] = true; + _streaming_image_reply["command"] = command+"-imagestream-update"; + _streaming_image_reply["tan"] = tan; + connect(_hyperion, SIGNAL(emitImage(int, const Image&, const int)), this, SLOT(setImage(int, const Image&, const int)) ); + } + else if (subcommand == "imagestream-stop") + { + disconnect(_hyperion, SIGNAL(emitImage(int, const Image&, const int)), this, 0 ); + } + else + { + sendErrorReply("unknown subcommand \""+subcommand+"\"",command,tan); + return; + } + + sendSuccessReply(command+"-"+subcommand,tan); +} + +void JsonProcessor::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 + { + sendErrorReply("unknown subcommand",command,tan); + return; + } + + sendSuccessReply(command+"-"+subcommand,tan); +} + +void JsonProcessor::handleProcessingCommand(const QJsonObject& message, const QString &command, const int tan) +{ + _hyperion->setLedMappingType(ImageProcessor::mappingTypeToInt( message["mappingType"].toString("multicolor_mean")) ); + + sendSuccessReply(command, tan); +} + +void JsonProcessor::handleNotImplemented() +{ + sendErrorReply("Command not implemented"); +} + +void JsonProcessor::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); + + // blacklisted commands for emitter + QVector vector; + vector << "ledcolors-imagestream-stop" << "ledcolors-imagestream-start" << "ledcolors-ledstream-stop" << "ledcolors-ledstream-start" << "logging-start" << "logging-stop"; + if(vector.indexOf(command) == -1) + { + emit pushReq(); + } +} + +void JsonProcessor::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); +} + +bool JsonProcessor::checkJson(const QJsonObject& message, const QString& schemaResource, QString& errorMessage, bool ignoreRequired) +{ + // make sure the resources are loaded (they may be left out after static linking) + Q_INIT_RESOURCE(JsonSchemas); + QJsonParseError error; + + // read the json schema from the resource + QFile schemaData(schemaResource); + if (!schemaData.open(QIODevice::ReadOnly)) + { + errorMessage = "Schema error: " + schemaData.errorString(); + return false; + } + + // create schema checker + QByteArray schema = schemaData.readAll(); + QJsonDocument schemaJson = QJsonDocument::fromJson(schema, &error); + schemaData.close(); + + if (error.error != QJsonParseError::NoError) + { + // report to the user the failure and their locations in the document. + int errorLine(0), errorColumn(0); + + for( int i=0, count=qMin( error.offset,schema.size()); igetPriorityInfo(_hyperion->getCurrentPriority()); + for(auto color = priorityInfo.ledColors.begin(); color != priorityInfo.ledColors.end(); ++color) + { + QJsonObject item; + item["index"] = int(color - priorityInfo.ledColors.begin()); + item["red"] = color->red; + item["green"] = color->green; + item["blue"] = color->blue; + leds.append(item); + } + + result["leds"] = leds; + _streaming_leds_reply["result"] = result; + + // send the result + emit callbackMessage(_streaming_leds_reply); +} + +void JsonProcessor::setImage(int priority, const Image & image, int duration_ms) +{ + if ( (_image_stream_timeout+250) < QDateTime::currentMSecsSinceEpoch() && _image_stream_mutex.tryLock(0) ) + { + _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); + + _image_stream_mutex.unlock(); + } +} + +void JsonProcessor::incommingLogMessage(Logger::T_LOG_MESSAGE msg) +{ + QJsonObject result, message; + QJsonArray messageArray; + + if (!_streaming_logging_activated) + { + _streaming_logging_activated = true; + QVector* logBuffer = LoggerManager::getInstance()->getLogMessageBuffer(); + for(int i=0; ilength(); 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 JsonProcessor::forceServerInfo() +{ + const QString command("serverinfo"); + const int tan = 1; + const QJsonObject obj; + handleServerInfoCommand(obj,command,tan); +} diff --git a/libsrc/utils/Stats.cpp b/libsrc/utils/Stats.cpp index 8f3562ed..4cb22c53 100644 --- a/libsrc/utils/Stats.cpp +++ b/libsrc/utils/Stats.cpp @@ -23,7 +23,7 @@ Stats::Stats() { if (!(interface.flags() & QNetworkInterface::IsLoopBack)) { - _hyperion->id = QString(QCryptographicHash::hash(interface.hardwareAddress().toLocal8Bit().append(_hyperion->getConfigFile().toLocal8Bit()),QCryptographicHash::Sha1).toHex()); + _hyperion->id = QString(QCryptographicHash::hash(interface.hardwareAddress().toLocal8Bit().append(_hyperion->getConfigFileName().toLocal8Bit()),QCryptographicHash::Sha1).toHex()); _hash = QString(QCryptographicHash::hash(interface.hardwareAddress().toLocal8Bit(),QCryptographicHash::Sha1).toHex()); break; } @@ -34,7 +34,7 @@ Stats::Stats() { Warning(_log, "No interface found, abort"); // fallback id - _hyperion->id = QString(QCryptographicHash::hash(_hyperion->getConfigFile().toLocal8Bit(),QCryptographicHash::Sha1).toHex()); + _hyperion->id = QString(QCryptographicHash::hash(_hyperion->getConfigFileName().toLocal8Bit(),QCryptographicHash::Sha1).toHex()); return; }