From fab0c208fece67da8d27ed706206128e28021c9d Mon Sep 17 00:00:00 2001 From: Paulchen Panther Date: Mon, 24 Oct 2016 23:52:53 +0200 Subject: [PATCH] Create Effect configuration files (.json) with JSON RPC (#277) * Add getEffectSchemas and loadEffectSchema function * Add getEffectSchemas function * add effect schema files to internal resources * Add loadEffectSchema and getEffectSchemas function * add effect schema resources * add getEffectSchemas function * extend handleSchemaGetCommand to get ... ... all available effect schemas add handleCreateEffectCommand function * add handleCreateEffectCommand function * include schema-create-effect.json file * add create-effect schema * Add schema-create-effect.json file * Add createEffect to hyperion-remote * Add createEffect function * add createEffect function * Create fade.schema.json * Add files via upload * Add files via upload * Update police.schema.json * Update EffectEngine.qrc.in * Update CMakeLists.txt --- effects/schema/fade.schema.json | 42 +++++++ effects/schema/knight-rider.schema.json | 36 ++++++ effects/schema/light-clock.schema.json | 15 +++ effects/schema/mood-blobs.schema.json | 87 +++++++++++++++ effects/schema/police.schema.json | 54 +++++++++ effects/schema/rainbow-mood.schema.json | 29 +++++ effects/schema/rainbow-swirl.schema.json | 36 ++++++ effects/schema/random.schema.json | 23 ++++ effects/schema/running_dots.schema.json | 32 ++++++ effects/schema/shutdown.schema.json | 48 ++++++++ effects/schema/snake.schema.json | 35 ++++++ effects/schema/sparks.schema.json | 62 +++++++++++ effects/schema/strobe.schema.json | 30 +++++ effects/schema/traces.schema.json | 16 +++ effects/schema/udp.schema.json | 22 ++++ effects/schema/x-mas.schema.json | 16 +++ include/effectengine/EffectEngine.h | 7 ++ include/effectengine/EffectSchema.h | 11 ++ include/hyperion/Hyperion.h | 5 + libsrc/effectengine/EffectEngine.cpp | 103 +++++++++++++++++- libsrc/hyperion/Hyperion.cpp | 5 + libsrc/jsonserver/JsonClientConnection.cpp | 99 ++++++++++++++++- libsrc/jsonserver/JsonClientConnection.h | 7 ++ libsrc/jsonserver/JsonSchemas.qrc | 1 + .../schema/schema-create-effect.json | 30 +++++ libsrc/jsonserver/schema/schema.json | 2 +- src/hyperion-remote/JsonConnection.cpp | 48 +++++++- src/hyperion-remote/JsonConnection.h | 9 ++ src/hyperion-remote/hyperion-remote.cpp | 17 ++- 29 files changed, 915 insertions(+), 12 deletions(-) create mode 100644 effects/schema/fade.schema.json create mode 100644 effects/schema/knight-rider.schema.json create mode 100644 effects/schema/light-clock.schema.json create mode 100644 effects/schema/mood-blobs.schema.json create mode 100644 effects/schema/police.schema.json create mode 100644 effects/schema/rainbow-mood.schema.json create mode 100644 effects/schema/rainbow-swirl.schema.json create mode 100644 effects/schema/random.schema.json create mode 100644 effects/schema/running_dots.schema.json create mode 100644 effects/schema/shutdown.schema.json create mode 100644 effects/schema/snake.schema.json create mode 100644 effects/schema/sparks.schema.json create mode 100644 effects/schema/strobe.schema.json create mode 100644 effects/schema/traces.schema.json create mode 100644 effects/schema/udp.schema.json create mode 100644 effects/schema/x-mas.schema.json create mode 100644 include/effectengine/EffectSchema.h create mode 100644 libsrc/jsonserver/schema/schema-create-effect.json diff --git a/effects/schema/fade.schema.json b/effects/schema/fade.schema.json new file mode 100644 index 00000000..5b0fa5f1 --- /dev/null +++ b/effects/schema/fade.schema.json @@ -0,0 +1,42 @@ +{ + "type":"object", + "script" : "fade.py", + "title":"Fade", + "required":true, + "properties":{ + "fade-time": { + "type": "number", + "title":"Fade Time", + "default": 5.0, + "minimum" : 0.1, + "propertyOrder" : 1 + }, + "color-start": { + "type": "array", + "title":"Color Start", + "default": "255,174,11", + "items" : { + "type": "integer", + "minimum": 0, + "maximum": 255 + }, + "minItems": 3, + "maxItems": 3, + "propertyOrder" : 2 + }, + "color-end": { + "type": "array", + "title":"Color End", + "default": "100,100,100", + "items" : { + "type": "integer", + "minimum": 0, + "maximum": 255 + }, + "minItems": 3, + "maxItems": 3, + "propertyOrder" : 3 + } + }, + "additionalProperties": false +} diff --git a/effects/schema/knight-rider.schema.json b/effects/schema/knight-rider.schema.json new file mode 100644 index 00000000..ed32250c --- /dev/null +++ b/effects/schema/knight-rider.schema.json @@ -0,0 +1,36 @@ +{ + "type":"object", + "script" : "knight-rider.py", + "title":"Knight Rider", + "required":true, + "properties":{ + "speed": { + "type": "number", + "title":"Speed", + "default": 1.0, + "minimum": 0.1, + "propertyOrder" : 1 + }, + "fadeFactor": { + "type": "number", + "title":"Fade Factor", + "default": 0.7, + "minimum" : 0.0, + "propertyOrder" : 1 + }, + "color": { + "type": "array", + "title":"Color", + "default": "255,0,0", + "items" : { + "type": "integer", + "minimum": 0, + "maximum": 255 + }, + "minItems": 3, + "maxItems": 3, + "propertyOrder" : 3 + } + }, + "additionalProperties": false +} diff --git a/effects/schema/light-clock.schema.json b/effects/schema/light-clock.schema.json new file mode 100644 index 00000000..a18481d4 --- /dev/null +++ b/effects/schema/light-clock.schema.json @@ -0,0 +1,15 @@ +{ + "type":"object", + "script" : "light-clock.py", + "title":"Light clock", + "required":true, + "properties":{ + "show_seconds": { + "type": "boolean", + "title":"Show seconds", + "default": true, + "propertyOrder" : 1 + } + }, + "additionalProperties": false +} diff --git a/effects/schema/mood-blobs.schema.json b/effects/schema/mood-blobs.schema.json new file mode 100644 index 00000000..44b699ff --- /dev/null +++ b/effects/schema/mood-blobs.schema.json @@ -0,0 +1,87 @@ +{ + "type":"object", + "script" : "mood-blobs.py", + "title":"Mood Blobs", + "required":true, + "properties":{ + "color": { + "type": "array", + "title":"Color", + "default": "255,0,0", + "items" : { + "type": "integer", + "minimum": 0, + "maximum": 255 + }, + "minItems": 3, + "maxItems": 3, + "propertyOrder" : 1 + }, + "blobs": { + "type": "integer", + "title":"Blob count", + "default": 5, + "minimum" : 1, + "propertyOrder" : 2 + }, + "rotationTime": { + "type": "number", + "title":"Rotation time", + "default": 20.0, + "minimum" : 1.0, + "propertyOrder" : 3 + }, + "hueChange": { + "type": "number", + "title":"Hue change", + "default": 60.0, + "minimum" : 1.0, + "propertyOrder" : 4 + }, + "reverse": { + "type": "boolean", + "title":"Reverse direction", + "default": false, + "propertyOrder" : 5 + }, + "colorRandom": { + "type": "boolean", + "title":"Random color", + "default": false, + "propertyOrder" : 6 + }, + "baseChange": { + "type": "boolean", + "title":"Base color change", + "default": false, + "propertyOrder" : 7 + }, + "baseColorRangeLeft": { + "type": "number", + "title":"baseColorRangeLeft", + "default": 0.0, + "minimum" : 0.0, + "maximum" : 360.0, + "append" : "° (Degree)", + "propertyOrder" : 8 + }, + "baseColorRangeRight": { + "type": "number", + "title":"baseColorRangeRight", + "default": 360.0, + "minimum" : 0.0, + "maximum" : 360.0, + "append" : "° (Degree)", + "propertyOrder" : 9 + }, + "baseColorChangeRate": { + "type": "number", + "title":"baseColorChangeRate", + "default": 2.0, + "minimum" : 0.0, + "append" : "Seconds for one degree", + "propertyOrder" : 10 + } + }, + "additionalProperties": false +} diff --git a/effects/schema/police.schema.json b/effects/schema/police.schema.json new file mode 100644 index 00000000..fc250fcf --- /dev/null +++ b/effects/schema/police.schema.json @@ -0,0 +1,54 @@ +{ + "type":"object", + "script" : "police.py", + "title":"Police", + "required":true, + "properties":{ + "color_one": { + "type": "array", + "title":"Color one", + "default": "255,0,0", + "items" : { + "type": "integer", + "minimum": 0, + "maximum": 255 + }, + "minItems": 3, + "maxItems": 3, + "propertyOrder" : 1 + }, + "color_two": { + "type": "array", + "title":"Color two", + "default": "0,0,255", + "items" : { + "type": "integer", + "minimum": 0, + "maximum": 255 + }, + "minItems": 3, + "maxItems": 3, + "propertyOrder" : 2 + }, + "colors_count": { + "type": "integer", + "title":"Colors count", + "default": "10", + "propertyOrder" : 3 + }, + "rotation-time": { + "type": "number", + "title":"Rotation time", + "default": 2.0, + "minimum" : 0.1, + "propertyOrder" : 4 + }, + "reverse": { + "type": "boolean", + "title":"Reverse direction", + "default": false, + "propertyOrder" : 5 + } + }, + "additionalProperties": false +} diff --git a/effects/schema/rainbow-mood.schema.json b/effects/schema/rainbow-mood.schema.json new file mode 100644 index 00000000..eb17ad9f --- /dev/null +++ b/effects/schema/rainbow-mood.schema.json @@ -0,0 +1,29 @@ +{ + "type":"object", + "script" : "rainbow-mood.py", + "title":"Rainbow mood", + "required":true, + "properties":{ + "rotation-time": { + "type": "number", + "title":"Rotation time", + "default": 60.0, + "minimum" : 0.1, + "propertyOrder" : 1 + }, + "brightness": { + "type": "number", + "title":"Brightness", + "default": 1.0, + "minimum" : 0.0, + "propertyOrder" : 2 + }, + "reverse": { + "type": "boolean", + "title":"Reverse", + "default": false, + "propertyOrder" : 3 + } + }, + "additionalProperties": false +} diff --git a/effects/schema/rainbow-swirl.schema.json b/effects/schema/rainbow-swirl.schema.json new file mode 100644 index 00000000..43a94442 --- /dev/null +++ b/effects/schema/rainbow-swirl.schema.json @@ -0,0 +1,36 @@ +{ + "type":"object", + "script" : "rainbow-swirl.py", + "title":"Rainbow swirl", + "required":true, + "properties":{ + "rotation-time": { + "type": "number", + "title":"Rotation Time", + "default": 20.0, + "minimum" : 0.1, + "propertyOrder" : 1 + }, + "center_x": { + "type": "number", + "title":"Center X", + "default": 0.5, + "minimum" : 0.0, + "propertyOrder" : 2 + }, + "center_y": { + "type": "number", + "title":"Center Y", + "default": 0.5, + "minimum" : 0.0, + "propertyOrder" : 3 + }, + "reverse": { + "type": "boolean", + "title":"Reverse", + "default": false, + "propertyOrder" : 4 + } + }, + "additionalProperties": false +} diff --git a/effects/schema/random.schema.json b/effects/schema/random.schema.json new file mode 100644 index 00000000..1138644c --- /dev/null +++ b/effects/schema/random.schema.json @@ -0,0 +1,23 @@ +{ + "type":"object", + "script" : "random.py", + "title":"Random", + "required":true, + "properties":{ + "speed": { + "type": "number", + "title":"Speed", + "default": 1.0, + "minimum" : 0.0, + "propertyOrder" : 1 + }, + "saturation": { + "type": "number", + "title":"Saturation", + "default": 1.0, + "minimum" : 0.0, + "propertyOrder" : 2 + } + }, + "additionalProperties": false +} diff --git a/effects/schema/running_dots.schema.json b/effects/schema/running_dots.schema.json new file mode 100644 index 00000000..f7043c8e --- /dev/null +++ b/effects/schema/running_dots.schema.json @@ -0,0 +1,32 @@ +{ + "type":"object", + "script" : "running_dots.py", + "title":"Running dots", + "required":true, + "properties":{ + "speed": { + "type": "number", + "title":"Runner speed", + "default": 1.5, + "minimum" : 0.1, + "propertyOrder" : 1 + }, + "colorLevel": { + "type": "integer", + "title":"Color", + "default": 220, + "minimium" : 0, + "maximum" : 255, + "propertyOrder" : 2 + }, + "whiteLevel": { + "type": "integer", + "title":"White", + "default": 0, + "minimium" : 0, + "maximum" : 254, + "propertyOrder" : 3 + } + }, + "additionalProperties": false +} diff --git a/effects/schema/shutdown.schema.json b/effects/schema/shutdown.schema.json new file mode 100644 index 00000000..ab13366d --- /dev/null +++ b/effects/schema/shutdown.schema.json @@ -0,0 +1,48 @@ +{ + "type":"object", + "script" : "shutdown.py", + "title":"System Shutdown", + "required":true, + "properties":{ + "speed": { + "type": "number", + "title":"Speed", + "default": 1.0, + "minimum" : 0.1, + "propertyOrder" : 1 + }, + "alarm-color": { + "type": "array", + "title":"Alarm color", + "default": "255,0,0", + "items" : { + "type": "integer", + "minimum": 0, + "maximum": 255 + }, + "minItems": 3, + "maxItems": 3, + "propertyOrder" : 2 + }, + "post-color": { + "type": "array", + "title":"Post color", + "default": "255,174,11", + "items" : { + "type": "integer", + "minimum": 0, + "maximum": 255 + }, + "minItems": 3, + "maxItems": 3, + "propertyOrder" : 3 + }, + "shutdown-enabled": { + "type": "boolean", + "title":"Shutdown enabled", + "default": false, + "propertyOrder" : 4 + } + }, + "additionalProperties": false +} diff --git a/effects/schema/snake.schema.json b/effects/schema/snake.schema.json new file mode 100644 index 00000000..53f855c2 --- /dev/null +++ b/effects/schema/snake.schema.json @@ -0,0 +1,35 @@ +{ + "type":"object", + "script" : "snake.py", + "title":"Snake", + "required":true, + "properties":{ + "color": { + "type": "array", + "title":"Color", + "default": "255,0,0", + "items" : { + "type": "integer", + "minimum": 0, + "maximum": 255 + }, + "minItems": 3, + "maxItems": 3, + "propertyOrder" : 1 + }, + "rotation-time": { + "type": "number", + "title":"Rotation time", + "default": 12.0, + "minimum" : 0.1, + "propertyOrder" : 2 + }, + "percentage": { + "type": "integer", + "title":"Percentage", + "default": 10, + "propertyOrder" : 3 + } + }, + "additionalProperties": false +} diff --git a/effects/schema/sparks.schema.json b/effects/schema/sparks.schema.json new file mode 100644 index 00000000..38bade22 --- /dev/null +++ b/effects/schema/sparks.schema.json @@ -0,0 +1,62 @@ +{ + "type":"object", + "script" : "sparks.py", + "title":"Sparks", + "required":true, + "properties":{ + "color_one": { + "type": "array", + "title":"Color", + "default": "255,0,0", + "items" : { + "type": "integer", + "minimum": 0, + "maximum": 255 + }, + "minItems": 3, + "maxItems": 3, + "propertyOrder" : 1 + }, + "rotation-time": { + "type": "number", + "title":"Rotation time", + "default": 2.0, + "minimum" : 0.1, + "propertyOrder" : 2 + }, + "sleep-time": { + "type": "number", + "title":"Rotation time", + "default": 0.05, + "minimum" : 0.01, + "propertyOrder" : 3 + }, + "brightness": { + "type": "number", + "title":"Rotation time", + "default": 1.0, + "minimum" : 0.01, + "propertyOrder" : 4 + }, + "saturation": { + "type": "number", + "title":"Rotation time", + "default": 1.0, + "minimum" : 0.01, + "propertyOrder" : 5 + }, + "reverse": { + "type": "boolean", + "title":"Reverse direction", + "default": false, + "propertyOrder" : 6 + }, + "random-color": { + "type": "boolean", + "title":"Random color", + "default": false, + "propertyOrder" : 7 + } + }, + "additionalProperties": false +} diff --git a/effects/schema/strobe.schema.json b/effects/schema/strobe.schema.json new file mode 100644 index 00000000..943066c1 --- /dev/null +++ b/effects/schema/strobe.schema.json @@ -0,0 +1,30 @@ +{ + "type":"object", + "script" : "strobe.py", + "title":"Strobe", + "required":true, + "properties":{ + "color": { + "type": "array", + "title":"Color", + "default": "255,0,0", + "items" : { + "type": "integer", + "minimum": 0, + "maximum": 255 + }, + "minItems": 3, + "maxItems": 3, + "propertyOrder" : 1 + }, + "frequency": { + "type": "number", + "title":"Frequency", + "default": 10.0, + "minimum" : 0.1, + "append" : "Hz", + "propertyOrder" : 2 + } + }, + "additionalProperties": false +} diff --git a/effects/schema/traces.schema.json b/effects/schema/traces.schema.json new file mode 100644 index 00000000..984bfda7 --- /dev/null +++ b/effects/schema/traces.schema.json @@ -0,0 +1,16 @@ +{ + "type":"object", + "script" : "traces.py", + "title":"Color traces", + "required":true, + "properties":{ + "speed": { + "type": "number", + "title":"Speed", + "default": 1.0, + "minimum" : 0.1, + "propertyOrder" : 1 + } + }, + "additionalProperties": false +} diff --git a/effects/schema/udp.schema.json b/effects/schema/udp.schema.json new file mode 100644 index 00000000..9feb0337 --- /dev/null +++ b/effects/schema/udp.schema.json @@ -0,0 +1,22 @@ +{ + "type":"object", + "script" : "udp.py", + "title":"UDP listener", + "required":true, + "properties":{ + "ListenPort": { + "type": "Integer", + "title":"Listen Port", + "default": 2801, + "minimum" : 1, + "propertyOrder" : 1 + }, + "ListenIP": { + "type": "string", + "title":"Listen IP", + "default": "", + "propertyOrder" : 2 + } + }, + "additionalProperties": false +} diff --git a/effects/schema/x-mas.schema.json b/effects/schema/x-mas.schema.json new file mode 100644 index 00000000..73e7cc21 --- /dev/null +++ b/effects/schema/x-mas.schema.json @@ -0,0 +1,16 @@ +{ + "type":"object", + "script" : "x-mas.py", + "title":"X-Mas", + "required":true, + "properties":{ + "sleepTime": { + "type": "number", + "title":"Sleep Time", + "default": 1.0, + "minimum" : 0.1, + "propertyOrder" : 1 + } + }, + "additionalProperties": false +} diff --git a/include/effectengine/EffectEngine.h b/include/effectengine/EffectEngine.h index a7393637..01215e16 100644 --- a/include/effectengine/EffectEngine.h +++ b/include/effectengine/EffectEngine.h @@ -14,6 +14,7 @@ // Effect engine includes #include #include +#include #include // pre-declarioation @@ -31,8 +32,12 @@ public: const std::list & getEffects() const; const std::list & getActiveEffects(); + + const std::list & getEffectSchemas(); static bool loadEffectDefinition(const QString & path, const QString & effectConfigFile, EffectDefinition &effectDefinition); + + static bool loadEffectSchema(const QString & path, const QString & effectSchemaFile, EffectSchema &effectSchema); public slots: /// Run the specified effect on the given priority channel and optionally specify a timeout @@ -62,6 +67,8 @@ private: std::list _activeEffects; std::list _availableActiveEffects; + + std::list _effectSchemas; PyThreadState * _mainThreadState; diff --git a/include/effectengine/EffectSchema.h b/include/effectengine/EffectSchema.h new file mode 100644 index 00000000..0dda8f77 --- /dev/null +++ b/include/effectengine/EffectSchema.h @@ -0,0 +1,11 @@ +#pragma once + +// QT include +#include +#include + +struct EffectSchema +{ + QString pyFile, schemaFile; + QJsonObject pySchema; +}; \ No newline at end of file diff --git a/include/hyperion/Hyperion.h b/include/hyperion/Hyperion.h index b04daa05..0499ab1e 100644 --- a/include/hyperion/Hyperion.h +++ b/include/hyperion/Hyperion.h @@ -29,6 +29,7 @@ // Effect engine includes #include #include +#include // KodiVideoChecker includes #include @@ -126,6 +127,10 @@ public: /// @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; }; diff --git a/libsrc/effectengine/EffectEngine.cpp b/libsrc/effectengine/EffectEngine.cpp index 70a1d814..0497080a 100644 --- a/libsrc/effectengine/EffectEngine.cpp +++ b/libsrc/effectengine/EffectEngine.cpp @@ -82,6 +82,22 @@ EffectEngine::EffectEngine(Hyperion * hyperion, const QJsonObject & jsonEffectCo } } Info(_log, "%d effects loaded from directory %s", efxCount, path.toUtf8().constData()); + + // collect effect schemas + efxCount = 0; + directory = path + "schema/"; + QStringList pynames = directory.entryList(QStringList() << "*.json", QDir::Files, QDir::Name | QDir::IgnoreCase); + foreach (const QString & pyname, pynames) + { + EffectSchema pyEffect; + if (loadEffectSchema(path, pyname, pyEffect)) + { + _effectSchemas.push_back(pyEffect); + efxCount++; + } + } + if (efxCount > 0) + Info(_log, "%d effect schemas loaded from directory %s", efxCount, (path + "schema/").toUtf8().constData()); } else { @@ -138,6 +154,11 @@ const std::list &EffectEngine::getActiveEffects() return _availableActiveEffects; } +const std::list &EffectEngine::getEffectSchemas() +{ + return _effectSchemas; +} + bool EffectEngine::loadEffectDefinition(const QString &path, const QString &effectConfigFile, EffectDefinition & effectDefinition) { Logger * log = Logger::getInstance("EFFECTENGINE"); @@ -232,17 +253,93 @@ bool EffectEngine::loadEffectDefinition(const QString &path, const QString &effe effectDefinition.name = config["name"].toString(); if (scriptName.isEmpty()) return false; + + QFile fileInfo(scriptName); if (scriptName.mid(0, 1) == ":" ) - effectDefinition.script = ":/effects/"+scriptName.mid(1); - else - effectDefinition.script = path + QDir::separator().toLatin1() + scriptName; + { + (!fileInfo.exists()) + ? effectDefinition.script = ":/effects/"+scriptName.mid(1) + : effectDefinition.script = scriptName; + } else + { + (!fileInfo.exists()) + ? effectDefinition.script = path + QDir::separator().toLatin1() + scriptName + : effectDefinition.script = scriptName; + } effectDefinition.args = config["args"].toObject(); return true; } +bool EffectEngine::loadEffectSchema(const QString &path, const QString &effectSchemaFile, EffectSchema & effectSchema) +{ + Logger * log = Logger::getInstance("EFFECTENGINE"); + + QString fileName = path + "schema/" + QDir::separator() + effectSchemaFile; + QJsonParseError error; + + // ---------- Read the effect schema file ---------- + + QFile file(fileName); + if (!file.open(QIODevice::ReadOnly)) + { + Error( log, "Effect schema '%s' could not be loaded", fileName.toUtf8().constData()); + return false; + } + + QByteArray fileContent = file.readAll(); + QJsonDocument schemaEffect = QJsonDocument::fromJson(fileContent, &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,fileContent.size()); i & Hyperion::getActiveEffects() return _effectEngine->getActiveEffects(); } +const std::list & Hyperion::getEffectSchemas() +{ + return _effectEngine->getEffectSchemas(); +} + int Hyperion::setEffect(const QString &effectName, int priority, int timeout) { return _effectEngine->runEffect(effectName, priority, timeout); diff --git a/libsrc/jsonserver/JsonClientConnection.cpp b/libsrc/jsonserver/JsonClientConnection.cpp index ed710eed..ca5bde60 100644 --- a/libsrc/jsonserver/JsonClientConnection.cpp +++ b/libsrc/jsonserver/JsonClientConnection.cpp @@ -20,6 +20,7 @@ #include #include #include +#include // hyperion util includes #include @@ -282,6 +283,8 @@ void JsonClientConnection::handleMessage(const QString& messageString) handleImageCommand(message, command, tan); else if (command == "effect") handleEffectCommand(message, command, tan); + else if (command == "create-effect") + handleCreateEffectCommand(message, command, tan); else if (command == "serverinfo") handleServerInfoCommand(message, command, tan); else if (command == "clear") @@ -434,6 +437,65 @@ void JsonClientConnection::handleEffectCommand(const QJsonObject& message, const sendSuccessReply(command, tan); } +void JsonClientConnection::handleCreateEffectCommand(const QJsonObject& message, const QString &command, const int tan) +{ + struct find_schema: std::unary_function + { + QString pyFile; + find_schema(QString pyFile):pyFile(pyFile) { } + bool operator()(EffectSchema const& schema) const + { + return schema.pyFile == pyFile; + } + }; + + 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) + { + effectJson["name"] = message["name"].toString(); + effectJson["script"] = message["script"].toString(); + effectJson["args"] = message["args"].toObject(); + QJsonFactory::writeJson(effectArray[0].toString() + QDir::separator() + message["name"].toString().replace(QString(" "), QString("")) + QString(".json"), effectJson); + } else + { + sendErrorReply("Can't save new effect. Effect path empty", command, tan); + return; + } + + sendSuccessReply(command, tan); + } 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::handleServerInfoCommand(const QJsonObject&, const QString& command, const int tan) { // create result @@ -939,7 +1001,7 @@ void JsonClientConnection::handleSchemaGetCommand(const QJsonObject& message, co Q_INIT_RESOURCE(resource); QJsonParseError error; - // read the json schema from the resource + // read the hyperion json schema from the resource QFile schemaData(":/hyperion-schema"); if (!schemaData.open(QIODevice::ReadOnly)) @@ -976,10 +1038,43 @@ void JsonClientConnection::handleSchemaGetCommand(const QJsonObject& message, co schemaJson = doc.object(); - //QJsonObject.insert can not merge. See details: http://doc.qt.io/qt-5/qjsonobject.html#insert + // collect all LED Devices properties = schemaJson["properties"].toObject(); alldevices = LedDevice::getLedDeviceSchemas(); properties.insert("alldevices", alldevices); + + // collect all available effect schemas + QJsonObject pyEffectSchemas, pyEffectSchema; + QJsonArray in, ex; + const std::list & effectsSchemas = _hyperion->getEffectSchemas(); + for (const EffectSchema & effectSchema : effectsSchemas) + { + if (effectSchema.pyFile.mid(0, 1) == ":") + { + QJsonObject internal; + internal.insert("script", effectSchema.pyFile); + internal.insert("schema-location", effectSchema.schemaFile); + internal.insert("schema-content", effectSchema.pySchema); + in.append(internal); + } + else + { + QJsonObject external; + external.insert("script", effectSchema.pyFile); + external.insert("schema-location", effectSchema.schemaFile); + external.insert("schema-content", 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; diff --git a/libsrc/jsonserver/JsonClientConnection.h b/libsrc/jsonserver/JsonClientConnection.h index ba892d82..52698cf7 100644 --- a/libsrc/jsonserver/JsonClientConnection.h +++ b/libsrc/jsonserver/JsonClientConnection.h @@ -89,6 +89,13 @@ private: /// 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 Server info message /// diff --git a/libsrc/jsonserver/JsonSchemas.qrc b/libsrc/jsonserver/JsonSchemas.qrc index 05059608..513fc305 100644 --- a/libsrc/jsonserver/JsonSchemas.qrc +++ b/libsrc/jsonserver/JsonSchemas.qrc @@ -11,6 +11,7 @@ schema/schema-temperature.json schema/schema-adjustment.json schema/schema-effect.json + schema/schema-create-effect.json schema/schema-sourceselect.json schema/schema-config.json schema/schema-componentstate.json diff --git a/libsrc/jsonserver/schema/schema-create-effect.json b/libsrc/jsonserver/schema/schema-create-effect.json new file mode 100644 index 00000000..2e29e533 --- /dev/null +++ b/libsrc/jsonserver/schema/schema-create-effect.json @@ -0,0 +1,30 @@ +{ + "type":"object", + "required":true, + "properties":{ + "command": { + "type" : "string", + "required" : true, + "enum" : ["create-effect"] + }, + "tan" : { + "type" : "integer" + }, + "name" : + { + "type" : "string", + "required" : true + }, + "script" : + { + "type" : "string", + "required" : true + }, + "args" : + { + "type" : "object", + "required" : true + } + }, + "additionalProperties": false +} diff --git a/libsrc/jsonserver/schema/schema.json b/libsrc/jsonserver/schema/schema.json index a20f101e..353e13ca 100644 --- a/libsrc/jsonserver/schema/schema.json +++ b/libsrc/jsonserver/schema/schema.json @@ -5,7 +5,7 @@ "command": { "type" : "string", "required" : true, - "enum" : ["color", "image", "effect", "serverinfo", "clear", "clearall", "transform", "correction", "temperature", "adjustment", "sourceselect", "config", "componentstate", "ledcolors"] + "enum" : ["color", "image", "effect", "create-effect", "serverinfo", "clear", "clearall", "transform", "correction", "temperature", "adjustment", "sourceselect", "config", "componentstate", "ledcolors"] } } } diff --git a/src/hyperion-remote/JsonConnection.cpp b/src/hyperion-remote/JsonConnection.cpp index 229098ad..50ba7eb7 100644 --- a/src/hyperion-remote/JsonConnection.cpp +++ b/src/hyperion-remote/JsonConnection.cpp @@ -148,9 +148,10 @@ void JsonConnection::setEffect(const QString &effectName, const QString & effect } effect["args"] = doc.object(); - command["effect"] = effect; } + command["effect"] = effect; + if (duration > 0) { command["duration"] = duration; @@ -163,6 +164,51 @@ void JsonConnection::setEffect(const QString &effectName, const QString & effect parseReply(reply); } +void JsonConnection::createEffect(const QString &effectName, const QString &effectScript, const QString & effectArgs) +{ + qDebug() << "Create effect " << effectName; + + // create command + QJsonObject effect; + effect["command"] = QString("create-effect"); + effect["name"] = effectName; + effect["script"] = effectScript; + + if (effectArgs.size() > 0) + { + QJsonParseError error; + QJsonDocument doc = QJsonDocument::fromJson(effectArgs.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,effectArgs.size()); i ('a', "address" , "Set the address of the hyperion server [default: %1]", "localhost:19444"); + Option & argAddress = parser.add