Add support for connection to actionExecuted in scripts

This commit is contained in:
Michael Zanetti 2023-05-24 14:31:42 +02:00 committed by Simon Stürz
parent 1e75df2782
commit 0c09e07112
9 changed files with 104 additions and 2 deletions

View File

@ -37,6 +37,7 @@
#include <QQmlEngine>
#include <qqml.h>
#include <QQmlContext>
#include <QJsonDocument>
#include <QLoggingCategory>
Q_DECLARE_LOGGING_CATEGORY(dcScriptEngine)
@ -56,6 +57,34 @@ void ScriptAction::classBegin()
m_scriptId = qmlEngine(this)->contextForObject(this)->contextProperty("scriptId").toUuid();
m_logger = qmlEngine(this)->contextForObject(this)->contextProperty("logger").value<Logger*>();
connect(m_thingManager, &ThingManager::actionExecuted, this, [=](const Action &action, Thing::ThingError status){
if (ThingId(m_thingId) != action.thingId()) {
return;
}
Thing *thing = m_thingManager->findConfiguredThing(ThingId(m_thingId));
if (!thing) {
return;
}
ActionTypeId ourActionTypeId = ActionTypeId(m_actionTypeId);
if (ourActionTypeId.isNull()) {
ourActionTypeId = thing->thingClass().actionTypes().findByName(m_actionName).id();
}
if (ourActionTypeId.isNull() || action.actionTypeId() != ourActionTypeId) {
return;
}
QVariantMap params;
foreach (const Param &param, action.params()) {
params.insert(param.paramTypeId().toString().remove(QRegExp("[{}]")), param.value().toByteArray());
QString paramName = thing->thingClass().actionTypes().findById(action.actionTypeId()).paramTypes().findById(param.paramTypeId()).name();
params.insert(paramName, param.value().toByteArray());
}
// Note: Explicitly convert the params to a Json document because auto-casting from QVariantMap to the JS engine might drop some values.
emit executed(QJsonDocument::fromVariant(params).toVariant().toMap(), status, action.triggeredBy());
});
}
void ScriptAction::componentComplete()

View File

@ -36,6 +36,9 @@
#include <QVariantMap>
#include <QUuid>
#include "integrations/thing.h"
#include "types/action.h"
class Logger;
class ThingManager;
@ -77,6 +80,8 @@ signals:
void actionTypeIdChanged();
void actionNameChanged();
void executed(const QVariantMap &params, Thing::ThingError status, Action::TriggeredBy triggeredBy);
public:
ThingManager *m_thingManager = nullptr;
Logger *m_logger = nullptr;

View File

@ -40,6 +40,7 @@
#include "scriptinterfaceevent.h"
#include "scriptthing.h"
#include "scriptthings.h"
#include "types/action.h"
#include "nymeasettings.h"
#include "logging/logengine.h"
@ -76,6 +77,8 @@ ScriptEngine::ScriptEngine(ThingManager *thingManager, LogEngine *logEngine, QOb
qmlRegisterType<ScriptAlarm>("nymea", 1, 0, "Alarm");
qmlRegisterType<ScriptThing>("nymea", 1, 0, "Thing");
qmlRegisterType<ScriptThings>("nymea", 1, 0, "Things");
qmlRegisterUncreatableType<Action>("nymea", 1, 0, "Action", "Cannot create Actions. Use ThingAction instead.");
m_logger = logEngine->registerLogSource("scripts", {"id", "event"});

View File

@ -170,6 +170,22 @@ void ScriptThing::init(ThingManager *thingManager)
// Note: Explicitly convert the params to a Json document because auto-casting from QVariantMap to the JS engine might drop some values.
emit eventTriggered(thing->thingClass().eventTypes().findById(event.eventTypeId()).name(), QJsonDocument::fromVariant(params).toVariant().toMap());
});
connect(m_thingManager, &ThingManager::actionExecuted, this, [=](const Action &action, Thing::ThingError status){
if (m_thingId != action.thingId()) {
return;
}
Thing *thing = m_thingManager->findConfiguredThing(action.thingId());
QVariantMap params;
foreach (const Param &param, action.params()) {
params.insert(param.paramTypeId().toString().remove(QRegExp("[{}]")), param.value().toByteArray());
QString paramName = thing->thingClass().actionTypes().findById(action.actionTypeId()).paramTypes().findById(param.paramTypeId()).name();
params.insert(paramName, param.value().toByteArray());
}
// Note: Explicitly convert the params to a Json document because auto-casting from QVariantMap to the JS engine might drop some values.
emit actionExecuted(thing->thingClass().actionTypes().findById(action.actionTypeId()).name(), QJsonDocument::fromVariant(params).toVariant().toMap(), status, action.triggeredBy());
});
}
void ScriptThing::connectToThing()

View File

@ -45,6 +45,8 @@ class ScriptThing : public QObject, public QQmlParserStatus
Q_PROPERTY(QString thingId READ thingId WRITE setThingId NOTIFY thingIdChanged)
Q_PROPERTY(QString name READ name NOTIFY nameChanged)
public:
Q_ENUM(Thing::ThingError)
explicit ScriptThing(QObject *parent = nullptr);
explicit ScriptThing(ThingManager *thingManager, QObject *parent = nullptr);
void classBegin() override;
@ -66,6 +68,7 @@ signals:
void stateValueChanged(const QString &stateName, const QVariant &value);
void eventTriggered(const QString &eventName, const QVariantMap &params);
void actionExecuted(const QString &actionName, const QVariantMap &params, Thing::ThingError status, Action::TriggeredBy triggeredBy);
private slots:
void init(ThingManager *thingManager);

View File

@ -77,4 +77,6 @@ private:
TriggeredBy m_triggeredBy = TriggeredByUser;
};
Q_DECLARE_METATYPE(Action::TriggeredBy)
#endif // ACTION_H

View File

@ -50,6 +50,11 @@ void TestHelper::logStateChange(const QString &thingId, const QString &stateId,
emit stateChangeLogged(ThingId(thingId), stateId, value);
}
void TestHelper::logActionExecuted(const QString &thingId, const QString &actionId, const QVariantMap &params, Thing::ThingError status, Action::TriggeredBy triggeredBy)
{
emit actionExecutionLogged(ThingId(thingId), actionId, params, status, triggeredBy);
}
void TestHelper::setTestResult(bool success)
{
emit testResult(success);

View File

@ -24,6 +24,8 @@
#include <QObject>
#include "typeutils.h"
#include "integrations/thing.h"
#include "types/action.h"
class TestHelper : public QObject
{
@ -33,6 +35,7 @@ public:
Q_INVOKABLE void logEvent(const QString &thingId, const QString &eventId, const QVariantMap &params);
Q_INVOKABLE void logStateChange(const QString &thingId, const QString &stateId, const QVariant &value);
Q_INVOKABLE void logActionExecuted(const QString &thingId, const QString &actionId, const QVariantMap &params, Thing::ThingError status, Action::TriggeredBy triggeredBy);
Q_INVOKABLE void setTestResult(bool success);
@ -41,7 +44,8 @@ signals:
void executeAction(const QVariantMap &params);
void eventLogged(const ThingId &thingId, const QString &eventId, const QVariantMap &params);
void stateChangeLogged(const ThingId &thingId, const QString stateId, const QVariant &value);
void stateChangeLogged(const ThingId &thingId, const QString &stateId, const QVariant &value);
void actionExecutionLogged(const ThingId &thingId, const QString &actionId, const QVariantMap &params, Thing::ThingError status, Action::TriggeredBy triggeredBy);
void testResult(bool success);

View File

@ -101,7 +101,9 @@ void TestScripts::init()
// Set initial state values of mock device
Action action(mockPowerActionTypeId, m_mockThingId);
action.setParams(ParamList() << Param(mockPowerActionPowerParamTypeId, false));
NymeaCore::instance()->thingManager()->executeAction(action);
ThingActionInfo *info = NymeaCore::instance()->thingManager()->executeAction(action);
QSignalSpy spy(info, &ThingActionInfo::finished);
spy.wait();
}
void TestScripts::testScriptEventById()
@ -336,6 +338,9 @@ void TestScripts::testScriptActionById()
" id: thingAction\n"
" thingId: \"%1\"\n"
" actionTypeId: \"%2\"\n"
" onExecuted: {\n"
" TestHelper.logActionExecuted(\"%1\", \"%2\", params, status, triggeredBy)\n"
" }\n"
" }\n"
" Connections {\n"
" target: TestHelper\n"
@ -350,6 +355,7 @@ void TestScripts::testScriptActionById()
QCOMPARE(reply.scriptError, ScriptEngine::ScriptErrorNoError);
QSignalSpy spy(NymeaCore::instance()->thingManager(), &ThingManager::thingStateChanged);
QSignalSpy actionExecutedSpy(TestHelper::instance(), &TestHelper::actionExecutionLogged);
QVariantMap params;
params.insert(mockPowerActionPowerParamTypeId.toString(), true);
@ -361,6 +367,13 @@ void TestScripts::testScriptActionById()
QCOMPARE(spy.first().at(0).value<Thing*>()->id(), m_mockThingId);
QCOMPARE(spy.first().at(1).value<StateTypeId>(), mockPowerStateTypeId);
QCOMPARE(spy.first().at(2).toBool(), true);
QCOMPARE(actionExecutedSpy.count(), 1);
QCOMPARE(actionExecutedSpy.first().at(0).value<ThingId>(), m_mockThingId);
QCOMPARE(ActionTypeId(actionExecutedSpy.first().at(1).toString()), mockPowerActionTypeId);
QCOMPARE(actionExecutedSpy.first().at(2).toMap().value(mockPowerActionTypeId.toString().remove(QRegExp("[{}]"))).toBool(), true);
QCOMPARE(actionExecutedSpy.first().at(3).value<Thing::ThingError>(), Thing::ThingErrorNoError);
QCOMPARE(actionExecutedSpy.first().at(4).value<Action::TriggeredBy>(), Action::TriggeredByScript);
}
void TestScripts::testScriptActionByName()
@ -372,6 +385,9 @@ void TestScripts::testScriptActionByName()
" id: thingAction\n"
" thingId: \"%1\"\n"
" actionName: \"%2\"\n"
" onExecuted: {\n"
" TestHelper.logActionExecuted(\"%1\", \"%2\", params, status, triggeredBy)\n"
" }\n"
" }\n"
" Connections {\n"
" target: TestHelper\n"
@ -386,6 +402,7 @@ void TestScripts::testScriptActionByName()
QCOMPARE(reply.scriptError, ScriptEngine::ScriptErrorNoError);
QSignalSpy spy(NymeaCore::instance()->thingManager(), &ThingManager::thingStateChanged);
QSignalSpy actionExecutedSpy(TestHelper::instance(), &TestHelper::actionExecutionLogged);
QVariantMap params;
params.insert("power", true);
@ -397,6 +414,13 @@ void TestScripts::testScriptActionByName()
QCOMPARE(spy.first().at(0).value<Thing*>()->id(), m_mockThingId);
QCOMPARE(spy.first().at(1).value<StateTypeId>(), mockPowerStateTypeId);
QCOMPARE(spy.first().at(2).toBool(), true);
QCOMPARE(actionExecutedSpy.count(), 1);
QCOMPARE(actionExecutedSpy.first().at(0).value<ThingId>(), m_mockThingId);
QCOMPARE(actionExecutedSpy.first().at(1).toString(), "power");
QCOMPARE(actionExecutedSpy.first().at(2).toMap().value("power").toBool(), true);
QCOMPARE(actionExecutedSpy.first().at(3).value<Thing::ThingError>(), Thing::ThingErrorNoError);
QCOMPARE(actionExecutedSpy.first().at(4).value<Action::TriggeredBy>(), Action::TriggeredByScript);
}
void TestScripts::testScriptAlarm_data()
@ -535,6 +559,9 @@ void TestScripts::testScriptThingAction()
" Thing {\n"
" id: thing\n"
" thingId: \"%1\"\n"
" onActionExecuted: {\n"
" TestHelper.logActionExecuted(\"%1\", actionName, params, status, triggeredBy)\n"
" }\n"
" }\n"
" Connections {\n"
" target: TestHelper\n"
@ -549,6 +576,7 @@ void TestScripts::testScriptThingAction()
QCOMPARE(reply.scriptError, ScriptEngine::ScriptErrorNoError);
QSignalSpy spy(NymeaCore::instance()->thingManager(), &ThingManager::thingStateChanged);
QSignalSpy actionExecutedSpy(TestHelper::instance(), &TestHelper::actionExecutionLogged);
QVariantMap params;
params.insert("power", true);
@ -561,6 +589,13 @@ void TestScripts::testScriptThingAction()
QCOMPARE(spy.first().at(1).value<StateTypeId>(), mockPowerStateTypeId);
QCOMPARE(spy.first().at(2).toBool(), true);
QCOMPARE(actionExecutedSpy.count(), 1);
QCOMPARE(actionExecutedSpy.first().at(0).value<ThingId>(), m_mockThingId);
QCOMPARE(actionExecutedSpy.first().at(1).toString(), "power");
QCOMPARE(actionExecutedSpy.first().at(2).toMap().value("power").toBool(), true);
QCOMPARE(actionExecutedSpy.first().at(3).value<Thing::ThingError>(), Thing::ThingErrorNoError);
QCOMPARE(actionExecutedSpy.first().at(4).value<Action::TriggeredBy>(), Action::TriggeredByScript);
}
void TestScripts::testScriptThingReadState()