// 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 // hyperion util includes #include #include #include #include #include #include #include #include #include #include // project includes #include "JsonClientConnection.h" using namespace hyperion; JsonClientConnection::JsonClientConnection(QTcpSocket *socket) : QObject() , _socket(socket) , _imageProcessor(ImageProcessorFactory::getInstance().newImageProcessor()) , _hyperion(Hyperion::getInstance()) , _receiveBuffer() , _webSocketHandshakeDone(false) , _log(Logger::getInstance("JSONCLIENTCONNECTION")) , _forwarder_enabled(true) , _streaming_logging_activated(false) , _image_stream_timeout(0) { // 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(); } JsonClientConnection::~JsonClientConnection() { delete _socket; } void JsonClientConnection::readData() { _receiveBuffer += _socket->readAll(); if (_webSocketHandshakeDone) { // websocket mode, data frame handleWebSocketFrame(); } else { // might be a handshake request or raw socket data if(_receiveBuffer.contains("Upgrade: websocket")) { doWebSocketHandshake(); } else { // raw socket data, handling as usual int bytes = _receiveBuffer.indexOf('\n') + 1; while(bytes > 0) { // create message string std::string message(_receiveBuffer.data(), bytes); // remove message data from buffer _receiveBuffer = _receiveBuffer.mid(bytes); // handle message handleMessage(QString::fromStdString(message)); // try too look up '\n' again bytes = _receiveBuffer.indexOf('\n') + 1; } } } } void JsonClientConnection::handleWebSocketFrame() { if ((_receiveBuffer.at(0) & BHB0_FIN) == BHB0_FIN) { // final bit found, frame complete quint8 * maskKey = NULL; quint8 opCode = _receiveBuffer.at(0) & BHB0_OPCODE; 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: payloadLength = ((_receiveBuffer.at(2) << 8) & 0xFF00) | (_receiveBuffer.at(3) & 0xFF); index += 2; break; case payload_size_code_64bit: payloadLength = 0; for (uint i=0; i < 8; i++) { payloadLength |= ((quint64)(_receiveBuffer.at(index+i) & 0xFF)) << (8*(7-i)); } index += 8; break; default: break; } if (isMasked) { // if the data is masked we need to get the key for unmasking maskKey = new quint8[4]; for (uint i=0; i < 4; i++) { maskKey[i] = _receiveBuffer.at(index + i); } index += 4; } // check the type of data frame switch (opCode) { case OPCODE::TEXT: { // frame contains text, extract it QByteArray result = _receiveBuffer.mid(index, payloadLength); _receiveBuffer.clear(); // unmask data if necessary if (isMasked) { for (uint i=0; i < payloadLength; i++) { result[i] = (result[i] ^ maskKey[i % 4]); } if (maskKey != NULL) { delete[] maskKey; maskKey = NULL; } } handleMessage(QString(result)); } break; case OPCODE::CLOSE: { // close request, confirm quint8 close[] = {0x88, 0}; _socket->write((const char*)close, 2); _socket->flush(); _socket->close(); } break; case OPCODE::PING: { // ping received, send pong quint8 pong[] = {OPCODE::PONG, 0}; _socket->write((const char*)pong, 2); _socket->flush(); } break; } } else { Error(_log, "Someone is sending very big messages over several frames... it's not supported yet"); quint8 close[] = {0x88, 0}; _socket->write((const char*)close, 2); _socket->flush(); _socket->close(); } } void JsonClientConnection::doWebSocketHandshake() { // http header, might not be a very reliable check... Debug(_log, "Websocket handshake"); // get the key to prepare an answer int start = _receiveBuffer.indexOf("Sec-WebSocket-Key") + 19; std::string value(_receiveBuffer.mid(start, _receiveBuffer.indexOf("\r\n", start) - start).data()); _receiveBuffer.clear(); // must be always appended value += "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; // generate sha1 hash QByteArray hash = QCryptographicHash::hash(value.c_str(), QCryptographicHash::Sha1); // prepare an answer std::ostringstream h; h << "HTTP/1.1 101 Switching Protocols\r\n" << "Upgrade: websocket\r\n" << "Connection: Upgrade\r\n" << "Sec-WebSocket-Accept: " << QString(hash.toBase64()).toStdString() << "\r\n\r\n"; _socket->write(h.str().c_str()); _socket->flush(); // we are in WebSocket mode, data frames should follow next _webSocketHandshakeDone = true; } void JsonClientConnection::socketClosed() { _webSocketHandshakeDone = false; emit connectionClosed(this); } void JsonClientConnection::handleMessage(const 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); // 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(""); 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); } else { _hyperion->setEffect(effectName, priority, duration); } // 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::handleServerInfoCommand(const QJsonObject&, const QString& command, const int tan) { // create result QJsonObject result; result["success"] = true; result["command"] = command; result["tan"] = tan; QJsonObject info; // add host name for remote clients info["hostname"] = QHostInfo::localHostName(); // 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"] = priorityInfo.componentId; item["component"] = QString(hyperion::componentToString(priorityInfo.componentId)); item["active"] = true; item["visible"] = (priority == currentPriority); foreach(auto const &entry, priorityRegister) { if (entry.second == priority) { item["owner"] = QString::fromStdString(entry.first); priorityRegister.erase(entry.first); break; } } if(priorityInfo.componentId == 9) { 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); } foreach(auto const &entry, priorityRegister) { QJsonObject item; item["priority"] = entry.second; item["active"] = false; item["visible"] = false; item["owner"] = QString::fromStdString(entry.first); priorities.append(item); } info["priorities"] = priorities; info["priorities_autoselect"] = _hyperion->sourceAutoSelectEnabled(); // 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"] = QString::fromStdString(LedDevice::activeDevice()); QJsonArray available; for (auto dev: LedDevice::getDeviceMap()) { available.append(QString::fromStdString(dev.first)); } ledDevices["available"] = available; info["ledDevices"] = ledDevices; // get available components QJsonArray component; std::map components = _hyperion->getComponentRegister().getRegister(); for(auto comp : components) { QJsonObject item; item["id"] = comp.first; item["name"] = QString::fromStdString(hyperion::componentToIdString(comp.first)); item["title"] = QString::fromStdString(hyperion::componentToString(comp.first)); item["enabled"] = comp.second; component.append(item); } info["components"] = component; info["ledMAppingType"] = ImageProcessor::mappingTypeToStr(_hyperion->getLedMappingType()); // Add Hyperion Version, build time QJsonArray hyperion; QJsonObject ver; ver["jsonrpc_version"] = QString(HYPERION_JSON_VERSION); ver["version"] = QString(HYPERION_VERSION); ver["build"] = QString(HYPERION_BUILD_ID); ver["time"] = QString(__DATE__ " " __TIME__); ver["config_modified"] = _hyperion->configModified(); ver["config_writeable"] = _hyperion->configWriteable(); hyperion.append(ver); 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(QString::fromStdString(_hyperion->getAdjustmentIds().front())); ColorAdjustment * colorAdjustment = _hyperion->getAdjustment(adjustmentId.toStdString()); 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.setAdjustmentR(values[0u].toInt()); colorAdjustment->_rgbRedAdjustment.setAdjustmentG(values[1u].toInt()); colorAdjustment->_rgbRedAdjustment.setAdjustmentB(values[2u].toInt()); } if (adjustment.contains("green")) { const QJsonArray & values = adjustment["green"].toArray(); colorAdjustment->_rgbGreenAdjustment.setAdjustmentR(values[0u].toInt()); colorAdjustment->_rgbGreenAdjustment.setAdjustmentG(values[1u].toInt()); colorAdjustment->_rgbGreenAdjustment.setAdjustmentB(values[2u].toInt()); } if (adjustment.contains("blue")) { const QJsonArray & values = adjustment["blue"].toArray(); colorAdjustment->_rgbBlueAdjustment.setAdjustmentR(values[0u].toInt()); colorAdjustment->_rgbBlueAdjustment.setAdjustmentG(values[1u].toInt()); colorAdjustment->_rgbBlueAdjustment.setAdjustmentB(values[2u].toInt()); } if (adjustment.contains("cyan")) { const QJsonArray & values = adjustment["cyan"].toArray(); colorAdjustment->_rgbCyanAdjustment.setAdjustmentR(values[0u].toInt()); colorAdjustment->_rgbCyanAdjustment.setAdjustmentG(values[1u].toInt()); colorAdjustment->_rgbCyanAdjustment.setAdjustmentB(values[2u].toInt()); } if (adjustment.contains("magenta")) { const QJsonArray & values = adjustment["magenta"].toArray(); colorAdjustment->_rgbMagentaAdjustment.setAdjustmentR(values[0u].toInt()); colorAdjustment->_rgbMagentaAdjustment.setAdjustmentG(values[1u].toInt()); colorAdjustment->_rgbMagentaAdjustment.setAdjustmentB(values[2u].toInt()); } if (adjustment.contains("yellow")) { const QJsonArray & values = adjustment["yellow"].toArray(); colorAdjustment->_rgbYellowAdjustment.setAdjustmentR(values[0u].toInt()); colorAdjustment->_rgbYellowAdjustment.setAdjustmentG(values[1u].toInt()); colorAdjustment->_rgbYellowAdjustment.setAdjustmentB(values[2u].toInt()); } if (adjustment.contains("black")) { const QJsonArray & values = adjustment["black"].toArray(); colorAdjustment->_rgbBlackAdjustment.setAdjustmentR(values[0u].toInt()); colorAdjustment->_rgbBlackAdjustment.setAdjustmentG(values[1u].toInt()); colorAdjustment->_rgbBlackAdjustment.setAdjustmentB(values[2u].toInt()); } if (adjustment.contains("white")) { const QJsonArray & values = adjustment["white"].toArray(); colorAdjustment->_rgbWhiteAdjustment.setAdjustmentR(values[0u].toInt()); colorAdjustment->_rgbWhiteAdjustment.setAdjustmentG(values[1u].toInt()); colorAdjustment->_rgbWhiteAdjustment.setAdjustmentB(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("brightnessMin")) { colorAdjustment->_rgbTransform.setBrightnessMin(adjustment["brightnessMin"].toDouble()); } if (adjustment.contains("brightness")) { colorAdjustment->_rgbTransform.setBrightness(adjustment["brightness"].toDouble()); } // 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(QString::fromStdString(_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(); Components component = stringToComponent(componentState["component"].toString("invalid")); if (component != COMP_INVALID) { _hyperion->setComponentState(component, componentState["state"].toBool(true)); sendSuccessReply(command, tan); } else { sendErrorReply("invalid component name", 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) { QJsonDocument writer(message); QByteArray serializedReply = writer.toJson(QJsonDocument::Compact) + "\n"; if (!_webSocketHandshakeDone) { // raw tcp socket mode _socket->write(serializedReply.data(), serializedReply.length()); } else { // websocket mode quint32 size = serializedReply.length(); // prepare data frame QByteArray response; response.append(0x81); if (size > 125) { response.append(0x7E); response.append((size >> 8) & 0xFF); response.append(size & 0xFF); } else { response.append(size); } response.append(serializedReply, 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); } 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()); i & errors = schemaChecker.getMessages(); std::stringstream ss; ss << "{"; foreach (const std::string & error, errors) { ss << error << " "; } ss << "}"; errorMessage = QString::fromStdString(ss.str()); return false; } return true; } void JsonClientConnection::streamLedcolorsUpdate() { QJsonObject result; QJsonArray leds; const PriorityMuxer::InputInfo & priorityInfo = _hyperion->getPriorityInfo(_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(); } }