From a412c34e6893f618f4c1d9d5824b2dc16bd15d9d Mon Sep 17 00:00:00 2001 From: Paulchen-Panther Date: Sun, 27 Jan 2019 13:41:21 +0100 Subject: [PATCH] Backwards compatibility ensured --- assets/webconfig/index.html | 5 +- assets/webconfig/server_scripts/demo.py | 5 -- include/api/JsonAPI.h | 7 +++ include/hyperion/Hyperion.h | 2 +- .../webserver/WebSocketClient.h | 12 ++-- .../webserver/WebSocketUtils.h | 0 .../api/JSONRPC_schema/schema-clearall.json | 15 +++++ libsrc/api/JSONRPC_schema/schema-color.json | 2 +- libsrc/api/JSONRPC_schema/schema-effect.json | 2 +- .../schema-hyperion-classic.json | 12 ++++ libsrc/api/JSONRPC_schema/schema.json | 2 +- libsrc/api/JSONRPC_schemas.qrc | 5 ++ libsrc/api/JsonAPI.cpp | 63 ++++++++++++++++++- libsrc/jsonserver/JsonClientConnection.cpp | 42 +++++++++---- libsrc/jsonserver/JsonClientConnection.h | 4 +- libsrc/webserver/QtHttpClientWrapper.cpp | 4 +- libsrc/webserver/WebSocketClient.cpp | 24 +++---- src/hyperion-remote/JsonConnection.cpp | 4 +- 18 files changed, 164 insertions(+), 46 deletions(-) delete mode 100644 assets/webconfig/server_scripts/demo.py rename {libsrc => include}/webserver/WebSocketClient.h (88%) rename {libsrc => include}/webserver/WebSocketUtils.h (100%) create mode 100644 libsrc/api/JSONRPC_schema/schema-clearall.json create mode 100644 libsrc/api/JSONRPC_schema/schema-hyperion-classic.json diff --git a/assets/webconfig/index.html b/assets/webconfig/index.html index c2f61a19..650d5036 100644 --- a/assets/webconfig/index.html +++ b/assets/webconfig/index.html @@ -179,7 +179,10 @@ diff --git a/assets/webconfig/server_scripts/demo.py b/assets/webconfig/server_scripts/demo.py deleted file mode 100644 index a7d90ed8..00000000 --- a/assets/webconfig/server_scripts/demo.py +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env python - -print ("hello world"); - - diff --git a/include/api/JsonAPI.h b/include/api/JsonAPI.h index ed239eca..c1aa19e4 100644 --- a/include/api/JsonAPI.h +++ b/include/api/JsonAPI.h @@ -214,6 +214,13 @@ private: /// void handleVideoModeCommand(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 message of unknown type /// diff --git a/include/hyperion/Hyperion.h b/include/hyperion/Hyperion.h index d9510067..413e01b5 100644 --- a/include/hyperion/Hyperion.h +++ b/include/hyperion/Hyperion.h @@ -208,7 +208,7 @@ public: /// @param[in] priority The priority of the channel /// @param[in] component The component of the channel /// @param[in] origin Who set the channel (CustomString@IP) - /// @param[in] owner Speicifc owner string, might be empty + /// @param[in] owner Specific owner string, might be empty /// @param[in] smooth_cfg The smooth id to use /// void registerInput(const int priority, const hyperion::Components& component, const QString& origin = "System", const QString& owner = "", unsigned smooth_cfg = 0); diff --git a/libsrc/webserver/WebSocketClient.h b/include/webserver/WebSocketClient.h similarity index 88% rename from libsrc/webserver/WebSocketClient.h rename to include/webserver/WebSocketClient.h index 05bfb1c7..b7a273d9 100644 --- a/libsrc/webserver/WebSocketClient.h +++ b/include/webserver/WebSocketClient.h @@ -1,18 +1,16 @@ #pragma once #include -#include "WebSocketUtils.h" +#include "webserver/WebSocketUtils.h" +#include class QTcpSocket; - -class QtHttpRequest; -class Hyperion; class JsonAPI; class WebSocketClient : public QObject { Q_OBJECT public: - WebSocketClient(QtHttpRequest* request, QTcpSocket* sock, QObject* parent); + WebSocketClient(QByteArray socketKey, QTcpSocket* sock, QObject* parent); struct WebSocketHeader { @@ -25,13 +23,13 @@ public: private: QTcpSocket* _socket; + QByteArray _secWebSocketKey; Logger* _log; - Hyperion* _hyperion; JsonAPI* _jsonAPI; void getWsFrameHeader(WebSocketHeader* header); void sendClose(int status, QString reason = ""); - void handleBinaryMessage(QByteArray &data); +// void handleBinaryMessage(QByteArray &data); qint64 sendMessage_Raw(const char* data, quint64 size); qint64 sendMessage_Raw(QByteArray &data); QByteArray makeFrameHeader(quint8 opCode, quint64 payloadLength, bool lastFrame); diff --git a/libsrc/webserver/WebSocketUtils.h b/include/webserver/WebSocketUtils.h similarity index 100% rename from libsrc/webserver/WebSocketUtils.h rename to include/webserver/WebSocketUtils.h diff --git a/libsrc/api/JSONRPC_schema/schema-clearall.json b/libsrc/api/JSONRPC_schema/schema-clearall.json new file mode 100644 index 00000000..8c88cc6c --- /dev/null +++ b/libsrc/api/JSONRPC_schema/schema-clearall.json @@ -0,0 +1,15 @@ +{ + "type":"object", + "required":true, + "properties":{ + "command": { + "type" : "string", + "required" : true, + "enum" : ["clearall"] + }, + "tan" : { + "type" : "integer" + } + }, + "additionalProperties": false +} diff --git a/libsrc/api/JSONRPC_schema/schema-color.json b/libsrc/api/JSONRPC_schema/schema-color.json index 316cbe80..754c5ad3 100644 --- a/libsrc/api/JSONRPC_schema/schema-color.json +++ b/libsrc/api/JSONRPC_schema/schema-color.json @@ -24,7 +24,7 @@ "type": "string", "minLength" : 4, "maxLength" : 20, - "required": true + "required": false }, "color": { "type": "array", diff --git a/libsrc/api/JSONRPC_schema/schema-effect.json b/libsrc/api/JSONRPC_schema/schema-effect.json index 80a10847..876173f1 100644 --- a/libsrc/api/JSONRPC_schema/schema-effect.json +++ b/libsrc/api/JSONRPC_schema/schema-effect.json @@ -24,7 +24,7 @@ "type": "string", "minLength" : 4, "maxLength" : 20, - "required": true + "required": false }, "effect": { "type": "object", diff --git a/libsrc/api/JSONRPC_schema/schema-hyperion-classic.json b/libsrc/api/JSONRPC_schema/schema-hyperion-classic.json new file mode 100644 index 00000000..5421b3aa --- /dev/null +++ b/libsrc/api/JSONRPC_schema/schema-hyperion-classic.json @@ -0,0 +1,12 @@ +{ + "type":"object", + "required":true, + "properties":{ + "command": { + "title" : "This schema is used to ensure backward compatibility with hyperion classic", + "type" : "string", + "required" : false + } + }, + "additionalProperties": true +} diff --git a/libsrc/api/JSONRPC_schema/schema.json b/libsrc/api/JSONRPC_schema/schema.json index 9aca8d98..26d77bf9 100644 --- a/libsrc/api/JSONRPC_schema/schema.json +++ b/libsrc/api/JSONRPC_schema/schema.json @@ -5,7 +5,7 @@ "command": { "type" : "string", "required" : true, - "enum" : ["color", "image", "effect", "create-effect", "delete-effect", "serverinfo", "clear", "clearall", "adjustment", "sourceselect", "config", "componentstate", "ledcolors", "logging", "processing", "sysinfo", "videomode"] + "enum" : ["color", "image", "effect", "create-effect", "delete-effect", "serverinfo", "clear", "clearall", "adjustment", "sourceselect", "config", "componentstate", "ledcolors", "logging", "processing", "sysinfo", "videomode", "transform", "correction" , "temperature"] } } } diff --git a/libsrc/api/JSONRPC_schemas.qrc b/libsrc/api/JSONRPC_schemas.qrc index 925566a1..7595d1ee 100644 --- a/libsrc/api/JSONRPC_schemas.qrc +++ b/libsrc/api/JSONRPC_schemas.qrc @@ -6,6 +6,7 @@ JSONRPC_schema/schema-serverinfo.json JSONRPC_schema/schema-sysinfo.json JSONRPC_schema/schema-clear.json + JSONRPC_schema/schema-clearall.json JSONRPC_schema/schema-adjustment.json JSONRPC_schema/schema-effect.json JSONRPC_schema/schema-create-effect.json @@ -17,5 +18,9 @@ JSONRPC_schema/schema-logging.json JSONRPC_schema/schema-processing.json JSONRPC_schema/schema-videomode.json + + JSONRPC_schema/schema-hyperion-classic.json + JSONRPC_schema/schema-hyperion-classic.json + JSONRPC_schema/schema-hyperion-classic.json diff --git a/libsrc/api/JsonAPI.cpp b/libsrc/api/JsonAPI.cpp index 3bb10240..0a3b1393 100644 --- a/libsrc/api/JsonAPI.cpp +++ b/libsrc/api/JsonAPI.cpp @@ -13,6 +13,7 @@ #include #include #include +#include // hyperion includes #include @@ -104,6 +105,14 @@ void JsonAPI::handleMessage(const QString& messageString) else if (command == "logging") handleLoggingCommand (message, command, tan); else if (command == "processing") handleProcessingCommand (message, command, tan); else if (command == "videomode") handleVideoModeCommand (message, command, tan); + + // BEGIN | The following commands are derecated but used to ensure backward compatibility with hyperion Classic remote control + else if (command == "clearall") handleClearallCommand(message, command, tan); + else if (command == "transform" || command == "correction" || command == "temperature") + sendErrorReply("The command " + command + "is deprecated, please use the Hyperion Web Interface to configure"); + // END + + // handle not implemented commands else handleNotImplemented (); } @@ -114,7 +123,7 @@ void JsonAPI::handleColorCommand(const QJsonObject& message, const QString& comm // extract parameters int priority = message["priority"].toInt(); int duration = message["duration"].toInt(-1); - QString origin = message["origin"].toString() + "@"+_peerAddress; + QString origin = message["origin"].toString("Empty") + "@"+_peerAddress; std::vector colorData(_hyperion->getLedCount()); const QJsonArray & jsonColor = message["color"].toArray(); @@ -185,7 +194,7 @@ void JsonAPI::handleEffectCommand(const QJsonObject &message, const QString &com int priority = message["priority"].toInt(); int duration = message["duration"].toInt(-1); QString pythonScript = message["pythonScript"].toString(); - QString origin = message["origin"].toString() + "@"+_peerAddress; + QString origin = message["origin"].toString("Empty") + "@"+_peerAddress; const QJsonObject & effect = message["effect"].toObject(); const QString & effectName = effect["name"].toString(); const QString & data = message["imageData"].toString("").toUtf8(); @@ -463,6 +472,46 @@ void JsonAPI::handleServerInfoCommand(const QJsonObject& message, const QString& } info["sessions"] = sessions; + // BEGIN | The following entries are derecated but used to ensure backward compatibility with hyperion Classic remote control + // TODO Output the real transformation information instead of default + + // host name + info["hostname"] = QHostInfo::localHostName(); + + // transform information (default values) + QJsonArray transformArray; + for (const QString& transformId : _hyperion->getAdjustmentIds()) + { + QJsonObject transform; + QJsonArray blacklevel, whitelevel, gamma, threshold; + + transform["id"] = transformId; + transform["saturationGain"] = 1.0; + transform["valueGain"] = 1.0; + transform["saturationLGain"] = 1.0; + transform["luminanceGain"] = 1.0; + transform["luminanceMinimum"] = 0.0; + + for (int i = 0; i < 3; i++ ) + { + blacklevel.append(0.0); + whitelevel.append(1.0); + gamma.append(2.50); + threshold.append(0.0); + } + + transform.insert("blacklevel", blacklevel); + transform.insert("whitelevel", whitelevel); + transform.insert("gamma", gamma); + transform.insert("threshold", threshold); + + transformArray.append(transform); + } + + info["transform"] = transformArray; + + // END + sendSuccessDataReply(QJsonDocument(info), command, tan); // AFTER we send the info, the client might want to subscribe to future updates @@ -845,6 +894,16 @@ void JsonAPI::handleVideoModeCommand(const QJsonObject& message, const QString & sendSuccessReply(command, tan); } +void JsonAPI::handleClearallCommand(const QJsonObject& message, const QString& command, const int tan) +{ + emit forwardJsonMessage(message); + + // clear priority + _hyperion->clearall(); + + // send reply + sendSuccessReply(command, tan); +} void JsonAPI::handleNotImplemented() { diff --git a/libsrc/jsonserver/JsonClientConnection.cpp b/libsrc/jsonserver/JsonClientConnection.cpp index 8fd2b630..692de9a9 100644 --- a/libsrc/jsonserver/JsonClientConnection.cpp +++ b/libsrc/jsonserver/JsonClientConnection.cpp @@ -6,9 +6,13 @@ #include #include +// websocket includes +#include "webserver/WebSocketClient.h" + JsonClientConnection::JsonClientConnection(QTcpSocket *socket) : QObject() , _socket(socket) + , _websocketClient(nullptr) , _receiveBuffer() , _log(Logger::getInstance("JSONCLIENTCONNECTION")) { @@ -23,21 +27,37 @@ JsonClientConnection::JsonClientConnection(QTcpSocket *socket) void JsonClientConnection::readRequest() { _receiveBuffer += _socket->readAll(); - // raw socket data, handling as usual - int bytes = _receiveBuffer.indexOf('\n') + 1; - while(bytes > 0) + + // might be an old hyperion classic handshake request or raw socket data + if(_receiveBuffer.contains("Upgrade: websocket")) { - // create message string - QString message(QByteArray(_receiveBuffer.data(), bytes)); + if(_websocketClient == Q_NULLPTR) + { + // disconnect this slot from socket for further requests + disconnect(_socket, &QTcpSocket::readyRead, this, &JsonClientConnection::readRequest); + int start = _receiveBuffer.indexOf("Sec-WebSocket-Key") + 19; + QByteArray header(_receiveBuffer.mid(start, _receiveBuffer.indexOf("\r\n", start) - start).data()); + _websocketClient = new WebSocketClient(header, _socket, this); + } + } + else + { + // raw socket data, handling as usual + int bytes = _receiveBuffer.indexOf('\n') + 1; + while(bytes > 0) + { + // create message string + QString message(QByteArray(_receiveBuffer.data(), bytes)); - // remove message data from buffer - _receiveBuffer = _receiveBuffer.mid(bytes); + // remove message data from buffer + _receiveBuffer = _receiveBuffer.mid(bytes); - // handle message - _jsonAPI->handleMessage(message); + // handle message + _jsonAPI->handleMessage(message); - // try too look up '\n' again - bytes = _receiveBuffer.indexOf('\n') + 1; + // try too look up '\n' again + bytes = _receiveBuffer.indexOf('\n') + 1; + } } } diff --git a/libsrc/jsonserver/JsonClientConnection.h b/libsrc/jsonserver/JsonClientConnection.h index 57944275..0434bc5d 100644 --- a/libsrc/jsonserver/JsonClientConnection.h +++ b/libsrc/jsonserver/JsonClientConnection.h @@ -10,9 +10,10 @@ class JsonAPI; class QTcpSocket; +class WebSocketClient; /// -/// The Connection object created by \a JsonServer when a new connection is establshed +/// The Connection object created by \a JsonServer when a new connection is established /// class JsonClientConnection : public QObject { @@ -41,6 +42,7 @@ private slots: private: QTcpSocket* _socket; + WebSocketClient* _websocketClient; /// new instance of JsonAPI JsonAPI * _jsonAPI; diff --git a/libsrc/webserver/QtHttpClientWrapper.cpp b/libsrc/webserver/QtHttpClientWrapper.cpp index e162e014..04e22be3 100644 --- a/libsrc/webserver/QtHttpClientWrapper.cpp +++ b/libsrc/webserver/QtHttpClientWrapper.cpp @@ -4,8 +4,8 @@ #include "QtHttpReply.h" #include "QtHttpServer.h" #include "QtHttpHeader.h" -#include "WebSocketClient.h" #include "WebJsonRpc.h" +#include "webserver/WebSocketClient.h" #include #include @@ -120,7 +120,7 @@ void QtHttpClientWrapper::onClientDataReceived (void) { { // disconnect this slot from socket for further requests disconnect(m_sockClient, &QTcpSocket::readyRead, this, &QtHttpClientWrapper::onClientDataReceived); - m_websocketClient = new WebSocketClient(m_currentRequest, m_sockClient, this); + m_websocketClient = new WebSocketClient(m_currentRequest->getHeader(QtHttpHeader::SecWebSocketKey), m_sockClient, this); } break; } diff --git a/libsrc/webserver/WebSocketClient.cpp b/libsrc/webserver/WebSocketClient.cpp index c45b1882..ebbfda52 100644 --- a/libsrc/webserver/WebSocketClient.cpp +++ b/libsrc/webserver/WebSocketClient.cpp @@ -1,4 +1,4 @@ -#include "WebSocketClient.h" +#include "webserver/WebSocketClient.h" // hyperion includes #include @@ -7,25 +7,23 @@ #include // qt includes -#include "QtHttpRequest.h" -#include "QtHttpHeader.h" #include #include #include #include +#include -WebSocketClient::WebSocketClient(QtHttpRequest* request, QTcpSocket* sock, QObject* parent) + +WebSocketClient::WebSocketClient(QByteArray socketKey, QTcpSocket* sock, QObject* parent) : QObject(parent) , _socket(sock) + , _secWebSocketKey(socketKey) , _log(Logger::getInstance("WEBSOCKET")) - , _hyperion(Hyperion::getInstance()) { // connect socket; disconnect handled from QtHttpServer connect(_socket, &QTcpSocket::readyRead , this, &WebSocketClient::handleWebSocketFrame); - // QtHttpRequest contains all headers for handshake - QByteArray secWebSocketKey = request->getHeader(QtHttpHeader::SecWebSocketKey); - const QString client = request->getClientInfo().clientAddress.toString(); + const QString client = sock->peerAddress().toString(); // Json processor _jsonAPI = new JsonAPI(client, _log, this); @@ -34,8 +32,8 @@ WebSocketClient::WebSocketClient(QtHttpRequest* request, QTcpSocket* sock, QObje Debug(_log, "New connection from %s", QSTRING_CSTR(client)); // do handshake - secWebSocketKey += "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; - QByteArray hash = QCryptographicHash::hash(secWebSocketKey, QCryptographicHash::Sha1).toBase64(); + _secWebSocketKey += "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + QByteArray hash = QCryptographicHash::hash(_secWebSocketKey, QCryptographicHash::Sha1).toBase64(); QString data = QString("HTTP/1.1 101 Switching Protocols\r\n") @@ -225,6 +223,7 @@ void WebSocketClient::sendClose(int status, QString reason) _socket->close(); } +/* void WebSocketClient::handleBinaryMessage(QByteArray &data) { //uint8_t priority = data.at(0); @@ -243,9 +242,10 @@ void WebSocketClient::handleBinaryMessage(QByteArray &data) image.resize(width, height); memcpy(image.memptr(), data.data()+4, imgSize); - //_hyperion->registerInput(); - //_hyperion->setInputImage(priority, image, duration_s*1000); + _hyperion->registerInput(); + _hyperion->setInputImage(priority, image, duration_s*1000); } +*/ qint64 WebSocketClient::sendMessage(QJsonObject obj) { diff --git a/src/hyperion-remote/JsonConnection.cpp b/src/hyperion-remote/JsonConnection.cpp index d9e81d24..0d0abdfd 100644 --- a/src/hyperion-remote/JsonConnection.cpp +++ b/src/hyperion-remote/JsonConnection.cpp @@ -587,7 +587,9 @@ QJsonObject JsonConnection::sendMessage(const QJsonObject & message) QJsonDocument reply = QJsonDocument::fromJson(serializedReply ,&error); if (error.error != QJsonParseError::NoError) { - throw std::runtime_error("Error while parsing reply: invalid json"); + throw std::runtime_error( + std::string("Error while parsing json reply: ") + + error.errorString().toStdString() ); } return reply.object();