Add Thing and Things types to script engine

This commit is contained in:
Michael Zanetti 2022-09-25 00:58:24 +02:00
parent 84163288b0
commit 22e75b08df
37 changed files with 904 additions and 25 deletions

View File

@ -1235,12 +1235,7 @@ Vendor ThingManagerImplementation::translateVendor(const Vendor &vendor, const Q
Thing *ThingManagerImplementation::findConfiguredThing(const ThingId &id) const
{
foreach (Thing *thing, m_configuredThings) {
if (thing->id() == id) {
return thing;
}
}
return nullptr;
return m_configuredThings.value(id);
}
Things ThingManagerImplementation::configuredThings() const

View File

@ -36,6 +36,8 @@
namespace nymeaserver {
using namespace scriptengine;
ScriptsHandler::ScriptsHandler(ScriptEngine *scriptEngine, QObject *parent):
JsonHandler(parent),
m_engine(scriptEngine)
@ -43,7 +45,7 @@ ScriptsHandler::ScriptsHandler(ScriptEngine *scriptEngine, QObject *parent):
registerEnum<ScriptEngine::ScriptError>();
registerEnum<ScriptEngine::ScriptMessageType>();
registerObject<Script, Scripts>();
registerObject<scriptengine::Script, Scripts>();
QVariantMap params, returns;
QString description;

View File

@ -42,7 +42,7 @@ class ScriptsHandler : public JsonHandler
{
Q_OBJECT
public:
explicit ScriptsHandler(ScriptEngine *scriptEngine, QObject *parent = nullptr);
explicit ScriptsHandler(scriptengine::ScriptEngine *scriptEngine, QObject *parent = nullptr);
QString name() const override;
@ -61,7 +61,7 @@ signals:
void ScriptLogMessage(const QVariantMap &params);
private:
ScriptEngine *m_engine = nullptr;
scriptengine::ScriptEngine *m_engine = nullptr;
};
}

View File

@ -61,6 +61,8 @@ HEADERS += nymeacore.h \
hardware/network/macaddressdatabasereplyimpl.h \
hardware/serialport/serialportmonitor.h \
hardware/zwave/zwavehardwareresourceimplementation.h \
scriptengine/scriptthing.h \
scriptengine/scriptthings.h \
zwave/zwavedevicedatabase.h \
zwave/zwavemanagerreply.h \
zwave/zwavenodeimplementation.h \
@ -174,6 +176,8 @@ SOURCES += nymeacore.cpp \
hardware/network/macaddressdatabasereplyimpl.cpp \
hardware/serialport/serialportmonitor.cpp \
hardware/zwave/zwavehardwareresourceimplementation.cpp \
scriptengine/scriptthing.cpp \
scriptengine/scriptthings.cpp \
zwave/zwavedevicedatabase.cpp \
zwave/zwavemanagerreply.cpp \
zwave/zwavenodeimplementation.cpp \

View File

@ -135,7 +135,7 @@ void NymeaCore::init(const QStringList &additionalInterfaces) {
m_logger->setThingManager(m_thingManager);
qCDebug(dcCore()) << "Creating Script Engine";
m_scriptEngine = new ScriptEngine(m_thingManager, this);
m_scriptEngine = new scriptengine::ScriptEngine(m_thingManager, this);
m_serverManager->jsonServer()->registerHandler(new ScriptsHandler(m_scriptEngine, m_scriptEngine));
qCDebug(dcCore()) << "Creating Tags Storage";

View File

@ -64,13 +64,17 @@ class UserManager;
class Platform;
class System;
class ExperienceManager;
class ScriptEngine;
class CloudManager;
class ZigbeeManager;
class ZWaveManager;
class ModbusRtuManager;
class SerialPortMonitor;
namespace scriptengine {
class ScriptEngine;
}
using namespace scriptengine;
class NymeaCore : public QObject
{
Q_OBJECT

View File

@ -30,7 +30,11 @@
#include "script.h"
#include <QLoggingCategory>
Q_DECLARE_LOGGING_CATEGORY(dcScriptEngine)
namespace nymeaserver {
namespace scriptengine {
Script::Script()
{
@ -79,3 +83,4 @@ void Scripts::put(const QVariant &value)
}
}
}

View File

@ -38,6 +38,7 @@
#include <QObject>
namespace nymeaserver {
namespace scriptengine {
class Script
{
@ -75,9 +76,9 @@ public:
Q_INVOKABLE QVariant get(int index);
Q_INVOKABLE void put(const QVariant &value);
};
}
Q_DECLARE_METATYPE(nymeaserver::Script)
Q_DECLARE_METATYPE(nymeaserver::Scripts)
}
Q_DECLARE_METATYPE(nymeaserver::scriptengine::Script)
Q_DECLARE_METATYPE(nymeaserver::scriptengine::Scripts)
#endif // SCRIPT_H

View File

@ -36,9 +36,11 @@
#include <QQmlEngine>
#include <qqml.h>
#include "loggingcategories.h"
#include <QLoggingCategory>
Q_DECLARE_LOGGING_CATEGORY(dcScriptEngine)
namespace nymeaserver {
namespace scriptengine {
ScriptAction::ScriptAction(QObject *parent) : QObject(parent)
{
@ -159,3 +161,4 @@ void ScriptAction::execute(const QVariantMap &params)
}
}
}

View File

@ -38,6 +38,7 @@
class ThingManager;
namespace nymeaserver {
namespace scriptengine {
class ScriptAction : public QObject, public QQmlParserStatus
{
@ -82,6 +83,7 @@ public:
QString m_actionName;
};
}
}
#endif // SCRIPTACTION_H

View File

@ -29,9 +29,13 @@
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "scriptalarm.h"
#include "loggingcategories.h"
#include <QTimer>
#include <QLoggingCategory>
Q_DECLARE_LOGGING_CATEGORY(dcScriptEngine)
namespace nymeaserver {
namespace scriptengine {
ScriptAlarm::ScriptAlarm(QObject *parent) : QObject(parent)
{
@ -151,3 +155,6 @@ void ScriptAlarm::updateActive()
emit activeChanged();
}
}
}
}

View File

@ -34,6 +34,8 @@
#include <QObject>
#include <QDateTime>
#include <QTimer>
namespace nymeaserver {
namespace scriptengine {
class ScriptAlarm : public QObject
{
@ -97,4 +99,7 @@ private:
int m_timerId = 0;
};
}
}
#endif // SCRIPTALARM_H

View File

@ -38,6 +38,8 @@
#include "scriptinterfaceaction.h"
#include "scriptinterfacestate.h"
#include "scriptinterfaceevent.h"
#include "scriptthing.h"
#include "scriptthings.h"
#include "nymeasettings.h"
@ -51,7 +53,10 @@
#include <QDir>
NYMEA_LOGGING_CATEGORY(dcScriptEngine, "ScriptEngine")
namespace nymeaserver {
namespace scriptengine {
QList<ScriptEngine*> ScriptEngine::s_engines;
QtMessageHandler ScriptEngine::s_upstreamMessageHandler;
@ -68,6 +73,8 @@ ScriptEngine::ScriptEngine(ThingManager *thingManager, QObject *parent) : QObjec
qmlRegisterType<ScriptInterfaceState>("nymea", 1, 0, "InterfaceState");
qmlRegisterType<ScriptInterfaceEvent>("nymea", 1, 0, "InterfaceEvent");
qmlRegisterType<ScriptAlarm>("nymea", 1, 0, "Alarm");
qmlRegisterType<ScriptThing>("nymea", 1, 0, "Thing");
qmlRegisterType<ScriptThings>("nymea", 1, 0, "Things");
m_engine = new QQmlEngine(this);
m_engine->setProperty("thingManager", reinterpret_cast<quint64>(m_thingManager));
@ -486,3 +493,4 @@ void ScriptEngine::logCategoryFilter(QLoggingCategory *category)
}
}
}

View File

@ -42,6 +42,7 @@
#include "script.h"
namespace nymeaserver {
namespace scriptengine {
class ScriptEngine : public QObject
{
@ -115,6 +116,7 @@ private:
static QMutex s_loggerMutex;
};
}
}
#endif // SCRIPTENGINE_H

View File

@ -35,6 +35,7 @@
#include <QJsonDocument>
namespace nymeaserver {
namespace scriptengine {
ScriptEvent::ScriptEvent(QObject *parent) : QObject(parent)
{
@ -117,4 +118,4 @@ void ScriptEvent::onEventTriggered(const Event &event)
}
}
}

View File

@ -39,6 +39,7 @@
#include "integrations/thingmanager.h"
namespace nymeaserver {
namespace scriptengine {
class ScriptParams;
@ -82,6 +83,7 @@ private:
QString m_eventName;
};
}
}
#endif // SCRIPTEVENT_H

View File

@ -36,9 +36,11 @@
#include <QQmlEngine>
#include <qqml.h>
#include "loggingcategories.h"
#include <QLoggingCategory>
Q_DECLARE_LOGGING_CATEGORY(dcScriptEngine)
namespace nymeaserver {
namespace scriptengine {
ScriptInterfaceAction::ScriptInterfaceAction(QObject *parent) : QObject(parent)
{
@ -124,3 +126,4 @@ void ScriptInterfaceAction::execute(const QVariantMap &params)
}
}
}

View File

@ -37,6 +37,7 @@
class ThingManager;
namespace nymeaserver {
namespace scriptengine {
class ScriptInterfaceAction : public QObject, public QQmlParserStatus
{
@ -68,6 +69,7 @@ public:
QString m_actionName;
};
}
}
#endif // SCRIPTINTERFACEACTION_H

View File

@ -35,6 +35,7 @@
#include <QJsonDocument>
namespace nymeaserver {
namespace scriptengine {
ScriptInterfaceEvent::ScriptInterfaceEvent(QObject *parent) : QObject(parent)
{
@ -100,4 +101,4 @@ void ScriptInterfaceEvent::onEventTriggered(const Event &event)
}
}
}

View File

@ -39,6 +39,7 @@
#include "integrations/thingmanager.h"
namespace nymeaserver {
namespace scriptengine {
class ScriptParams;
@ -75,6 +76,7 @@ private:
QString m_eventName;
};
}
}
#endif // SCRIPTINTERFACEEVENT_H

View File

@ -35,6 +35,7 @@
#include <QJsonDocument>
namespace nymeaserver {
namespace scriptengine {
ScriptInterfaceState::ScriptInterfaceState(QObject *parent) : QObject(parent)
{
@ -91,4 +92,4 @@ void ScriptInterfaceState::onStateChanged(Thing *thing, const StateTypeId &state
}
}
}

View File

@ -39,6 +39,7 @@
#include "integrations/thingmanager.h"
namespace nymeaserver {
namespace scriptengine {
class ScriptParams;
@ -75,6 +76,7 @@ private:
QString m_stateName;
};
}
}
#endif // SCRIPTINTERFACESTATE_H

View File

@ -30,13 +30,15 @@
#include "scriptstate.h"
#include "loggingcategories.h"
#include <QColor>
#include <qqml.h>
#include <QQmlEngine>
#include <QLoggingCategory>
Q_DECLARE_LOGGING_CATEGORY(dcScriptEngine)
namespace nymeaserver {
namespace scriptengine {
ScriptState::ScriptState(QObject *parent) : QObject(parent)
{
@ -238,6 +240,9 @@ void ScriptState::onThingStateChanged(Thing *thing, const StateTypeId &stateType
void ScriptState::connectToThing()
{
if (m_connection) {
disconnect(m_connection);
}
Thing *thing = m_thingManager->findConfiguredThing(ThingId(m_thingId));
if (!thing) {
qCDebug(dcScriptEngine()) << "Can't find thing with id" << m_thingId << "(yet)";
@ -252,7 +257,7 @@ void ScriptState::connectToThing()
qCDebug(dcScriptEngine()) << "Thing setup for" << thing->name() << "not complete yet";
}
connect(thing, &Thing::setupStatusChanged, this, [this, thing](){
m_connection = connect(thing, &Thing::setupStatusChanged, this, [this, thing](){
if (thing->setupStatus() == Thing::ThingSetupStatusComplete) {
qCDebug(dcScriptEngine()) << "Thing setup for" << thing->name() << "completed";
if (!m_valueCache.isNull()) {
@ -263,3 +268,4 @@ void ScriptState::connectToThing()
}
}
}

View File

@ -39,6 +39,7 @@
#include "integrations/thingactioninfo.h"
namespace nymeaserver {
namespace scriptengine {
class ScriptState : public QObject, public QQmlParserStatus
{
@ -98,8 +99,11 @@ private:
QVariant m_valueCache;
QVariant m_valueStore;
QMetaObject::Connection m_connection;
};
}
}
#endif // SCRIPTSTATE_H

View File

@ -0,0 +1,195 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2022, nymea GmbH
* Contact: contact@nymea.io
*
* This file is part of nymea.
* This project including source code and documentation is protected by
* copyright law, and remains the property of nymea GmbH. All rights, including
* reproduction, publication, editing and translation, are reserved. The use of
* this project is subject to the terms of a license agreement to be concluded
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
* under https://nymea.io/license
*
* GNU General Public License Usage
* Alternatively, this project may be redistributed and/or modified under the
* terms of the GNU General Public License as published by the Free Software
* Foundation, GNU version 3. This project is distributed in the hope that it
* will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with
* this project. If not, see <https://www.gnu.org/licenses/>.
*
* For any further details and any questions please contact us under
* contact@nymea.io or see our FAQ/Licensing Information on
* https://nymea.io/license/faq
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "scriptthing.h"
#include <qqml.h>
#include <QQmlEngine>
#include <QJsonDocument>
#include <QLoggingCategory>
Q_DECLARE_LOGGING_CATEGORY(dcScriptEngine)
namespace nymeaserver {
namespace scriptengine {
ScriptThing::ScriptThing(QObject *parent)
: QObject{parent}
{
}
ScriptThing::ScriptThing(ThingManager *thingManager, QObject *parent)
: QObject{parent}
{
init(thingManager);
}
void ScriptThing::classBegin()
{
init(reinterpret_cast<ThingManager*>(qmlEngine(this)->property("thingManager").toULongLong()));
}
void ScriptThing::componentComplete()
{
}
QString ScriptThing::thingId() const
{
return m_thingId.toString();
}
void ScriptThing::setThingId(const QString &thingId)
{
if (m_thingId != ThingId(thingId)) {
m_thingId = ThingId(thingId);
emit thingIdChanged();
emit nameChanged();
connectToThing();
}
}
QString ScriptThing::name() const
{
Thing *thing = m_thingManager->findConfiguredThing(m_thingId);
if (!thing) {
return QString();
}
return thing->name();
}
QVariant ScriptThing::stateValue(const QString &stateName) const
{
Thing *thing = m_thingManager->findConfiguredThing(m_thingId);
if (!thing) {
return QVariant();
}
return thing->stateValue(stateName);
}
void ScriptThing::setStateValue(const QString &stateName, const QVariant &value)
{
executeAction(stateName, {{stateName, value}});
}
void ScriptThing::executeAction(const QString &actionName, const QVariantMap &params)
{
Thing *thing = m_thingManager->findConfiguredThing(m_thingId);
if (!thing) {
return;
}
ActionType actionType = thing->thingClass().actionTypes().findByName(actionName);
if (actionType.id().isNull()) { // Try to find by id for now, for compatiblity sake
actionType = thing->thingClass().actionTypes().findById(QUuid(actionName));
}
if (actionType.id().isNull()) {
qCWarning(dcScriptEngine()) << "Thing" << thing->name() << "does not have action" << actionName;
return;
}
Action action(actionType.id(), thing->id(), Action::TriggeredByScript);
ParamList paramList;
foreach (const QString &paramNameOrId, params.keys()) {
ParamType paramType;
if (!ParamTypeId(paramNameOrId).isNull()) {
paramType = actionType.paramTypes().findById(ParamTypeId(paramNameOrId));
} else {
paramType = actionType.paramTypes().findByName(paramNameOrId);
}
if (paramType.id().isNull()) {
qCWarning(dcScriptEngine()) << "Invalid param id or name";
continue;
}
paramList << Param(paramType.id(), params.value(paramNameOrId));
}
action.setParams(paramList);
qCDebug(dcScriptEngine()) << "Executing action:" << action.thingId().toString() << action.actionTypeId().toString() << action.params();
m_thingManager->executeAction(action);
}
void ScriptThing::init(ThingManager *thingManager)
{
m_thingManager = thingManager;
connect(m_thingManager, &ThingManager::thingAdded, this, [this](Thing *newThing){
if (newThing->id() == m_thingId) {
qCDebug(dcScriptEngine()) << "Thing" << newThing->name() << "appeared in system";
connectToThing();
}
});
connect(m_thingManager, &ThingManager::thingStateChanged, this, [=](Thing *thing, const StateTypeId &stateTypeId, const QVariant &value, const QVariant &minValue, const QVariant &maxValue){
Q_UNUSED(minValue)
Q_UNUSED(maxValue)
if (m_thingId != thing->id()) {
return;
}
emit stateValueChanged(thing->thingClass().getStateType(stateTypeId).name(), value);
});
connect(m_thingManager, &ThingManager::eventTriggered, this, [=](const Event &event){
if (m_thingId != event.thingId()) {
return;
}
Thing *thing = m_thingManager->findConfiguredThing(event.thingId());
QVariantMap params;
foreach (const Param &param, event.params()) {
params.insert(param.paramTypeId().toString().remove(QRegExp("[{}]")), param.value().toByteArray());
QString paramName = thing->thingClass().eventTypes().findById(event.eventTypeId()).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 eventTriggered(thing->thingClass().eventTypes().findById(event.eventTypeId()).name(), QJsonDocument::fromVariant(params).toVariant().toMap());
});
}
void ScriptThing::connectToThing()
{
disconnect(m_nameConnection);
Thing *thing = m_thingManager->findConfiguredThing(m_thingId);
if (!thing) {
qCDebug(dcScriptEngine()) << "Can't find thing with id" << m_thingId.toString() << "(yet)";
return;
}
m_nameConnection = connect(thing, &Thing::nameChanged, this, [this, thing](){
if (thing->setupStatus() == Thing::ThingSetupStatusComplete) {
qCDebug(dcScriptEngine()) << "Thing setup for" << thing->name() << "completed";
emit nameChanged();
}
});
}
}
}

View File

@ -0,0 +1,84 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2022, nymea GmbH
* Contact: contact@nymea.io
*
* This file is part of nymea.
* This project including source code and documentation is protected by
* copyright law, and remains the property of nymea GmbH. All rights, including
* reproduction, publication, editing and translation, are reserved. The use of
* this project is subject to the terms of a license agreement to be concluded
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
* under https://nymea.io/license
*
* GNU General Public License Usage
* Alternatively, this project may be redistributed and/or modified under the
* terms of the GNU General Public License as published by the Free Software
* Foundation, GNU version 3. This project is distributed in the hope that it
* will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with
* this project. If not, see <https://www.gnu.org/licenses/>.
*
* For any further details and any questions please contact us under
* contact@nymea.io or see our FAQ/Licensing Information on
* https://nymea.io/license/faq
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#ifndef SCRIPTTHING_H
#define SCRIPTTHING_H
#include <QObject>
#include <QQmlParserStatus>
#include <QUuid>
#include "integrations/thingmanager.h"
namespace nymeaserver {
namespace scriptengine {
class ScriptThing : public QObject, public QQmlParserStatus
{
Q_OBJECT
Q_PROPERTY(QString thingId READ thingId WRITE setThingId NOTIFY thingIdChanged)
Q_PROPERTY(QString name READ name NOTIFY nameChanged)
public:
explicit ScriptThing(QObject *parent = nullptr);
explicit ScriptThing(ThingManager *thingManager, QObject *parent = nullptr);
void classBegin() override;
void componentComplete() override;
QString thingId() const;
void setThingId(const QString &thingId);
QString name() const;
Q_INVOKABLE QVariant stateValue(const QString &stateName) const;
Q_INVOKABLE void setStateValue(const QString &stateName, const QVariant &value);
Q_INVOKABLE void executeAction(const QString &actionName, const QVariantMap &params);
signals:
void thingIdChanged();
void nameChanged();
void stateValueChanged(const QString &stateName, const QVariant &value);
void eventTriggered(const QString &eventName, const QVariantMap &params);
private slots:
void init(ThingManager *thingManager);
void connectToThing();
private:
ThingId m_thingId;
ThingManager *m_thingManager = nullptr;
QMetaObject::Connection m_nameConnection;
};
}
}
#endif // SCRIPTTHING_H

View File

@ -0,0 +1,22 @@
#include "scriptthingmanager.h"
#include <qqml.h>
#include <QQmlEngine>
namespace nymeaserver {
namespace scriptengine {
ScriptThingManager::ScriptThingManager(QObject *parent)
: QObject{parent}
{
}
void ScriptThingManager::classBegin()
{
m_thingManager = reinterpret_cast<ThingManager*>(qmlEngine(this)->property("thingManager").toULongLong());
}
}
}

View File

@ -0,0 +1,31 @@
#ifndef SCRIPTTHINGMANAGER_H
#define SCRIPTTHINGMANAGER_H
#include "integrations/thingmanager.h"
#include <QObject>
#include <QQmlParserStatus>
namespace nymeaserver {
namespace scriptengine {
class ScriptThingManager : public QObject, public QQmlParserStatus
{
Q_OBJECT
public:
explicit ScriptThingManager(QObject *parent = nullptr);
void classBegin() override;
void componentComplete() override;
signals:
private:
ThingManager *m_thingManager = nullptr;
};
}
}
#endif // SCRIPTTHINGMANAGER_H

View File

@ -0,0 +1,162 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2022, nymea GmbH
* Contact: contact@nymea.io
*
* This file is part of nymea.
* This project including source code and documentation is protected by
* copyright law, and remains the property of nymea GmbH. All rights, including
* reproduction, publication, editing and translation, are reserved. The use of
* this project is subject to the terms of a license agreement to be concluded
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
* under https://nymea.io/license
*
* GNU General Public License Usage
* Alternatively, this project may be redistributed and/or modified under the
* terms of the GNU General Public License as published by the Free Software
* Foundation, GNU version 3. This project is distributed in the hope that it
* will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with
* this project. If not, see <https://www.gnu.org/licenses/>.
*
* For any further details and any questions please contact us under
* contact@nymea.io or see our FAQ/Licensing Information on
* https://nymea.io/license/faq
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "scriptthings.h"
#include "scriptthing.h"
#include <qqml.h>
#include <QQmlEngine>
#include <QLoggingCategory>
Q_DECLARE_LOGGING_CATEGORY(dcScriptEngine)
namespace nymeaserver {
namespace scriptengine {
ScriptThings::ScriptThings(QObject *parent)
: QSortFilterProxyModel{parent}
{
}
void ScriptThings::classBegin()
{
m_thingManager = reinterpret_cast<ThingManager*>(qmlEngine(this)->property("thingManager").toULongLong());
m_model = new ThingsModel(m_thingManager, this);
setSourceModel(m_model);
connect(m_thingManager, &ThingManager::thingAdded, this, [this](Thing *newThing){
emit thingAdded(newThing->id().toString());
emit countChanged();
});
connect(m_thingManager, &ThingManager::thingRemoved, this, [this](const ThingId &thingId){
emit thingRemoved(thingId.toString());
emit countChanged();
});
}
void ScriptThings::componentComplete()
{
}
QString ScriptThings::filterInterface() const
{
return m_filterInterface;
}
void ScriptThings::setFilterInterface(const QString &filterInterface)
{
if (m_filterInterface != filterInterface) {
m_filterInterface = filterInterface;
emit filterInterfaceChanged();
invalidateFilter();
}
}
ScriptThing *ScriptThings::get(int index) const
{
Thing *thing = m_model->get(mapToSource(this->index(index, 0)).row());
if (!thing) {
return nullptr;
}
ScriptThing *scriptThing = new ScriptThing(m_thingManager);
QQmlEngine::setObjectOwnership(scriptThing, QQmlEngine::JavaScriptOwnership);
scriptThing->setThingId(thing->id().toString());
return scriptThing;
}
ScriptThing *ScriptThings::getThing(const QUuid &thingId) const
{
Thing *thing = m_model->getThing(thingId);
if (!thing) {
return nullptr;
}
ScriptThing *scriptThing = new ScriptThing(m_thingManager);
QQmlEngine::setObjectOwnership(scriptThing, QQmlEngine::JavaScriptOwnership);
scriptThing->setThingId(thing->id().toString());
return scriptThing;
}
bool ScriptThings::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
{
Q_UNUSED(sourceParent)
Thing *thing = m_model->get(sourceRow);
if (!m_filterInterface.isEmpty() && !thing->thingClass().interfaces().contains(m_filterInterface)) {
return false;
}
return true;
}
ThingsModel::ThingsModel(ThingManager *thingManager, QObject *parent):
QAbstractListModel(parent),
m_thingManager(thingManager)
{
}
int ThingsModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
return m_thingManager->configuredThings().count();
}
QVariant ThingsModel::data(const QModelIndex &index, int role) const
{
switch (role) {
case RoleId:
return m_thingManager->configuredThings().at(index.row())->id();
case RoleName:
return m_thingManager->configuredThings().at(index.row())->name();
}
return QVariant();
}
QHash<int, QByteArray> ThingsModel::roleNames() const
{
return {
{RoleId, "thingId"},
{RoleName, "thingName"}
};
}
Thing *ThingsModel::get(int index) const
{
return m_thingManager->configuredThings().at(index);
}
Thing *ThingsModel::getThing(const QUuid &thingId) const
{
return m_thingManager->findConfiguredThing(thingId);
}
}
}

View File

@ -0,0 +1,110 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2022, nymea GmbH
* Contact: contact@nymea.io
*
* This file is part of nymea.
* This project including source code and documentation is protected by
* copyright law, and remains the property of nymea GmbH. All rights, including
* reproduction, publication, editing and translation, are reserved. The use of
* this project is subject to the terms of a license agreement to be concluded
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
* under https://nymea.io/license
*
* GNU General Public License Usage
* Alternatively, this project may be redistributed and/or modified under the
* terms of the GNU General Public License as published by the Free Software
* Foundation, GNU version 3. This project is distributed in the hope that it
* will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with
* this project. If not, see <https://www.gnu.org/licenses/>.
*
* For any further details and any questions please contact us under
* contact@nymea.io or see our FAQ/Licensing Information on
* https://nymea.io/license/faq
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#ifndef SCRIPTTHINGS_H
#define SCRIPTTHINGS_H
#include "integrations/thingmanager.h"
#include <QObject>
#include <QAbstractListModel>
#include <QSortFilterProxyModel>
#include <QQmlParserStatus>
namespace nymeaserver {
namespace scriptengine {
class ScriptThing;
class ThingsModel;
class ScriptThings : public QSortFilterProxyModel, public QQmlParserStatus
{
Q_OBJECT
Q_PROPERTY(int count READ rowCount NOTIFY countChanged)
Q_PROPERTY(QString filterInterface READ filterInterface WRITE setFilterInterface NOTIFY filterInterfaceChanged)
public:
explicit ScriptThings(QObject *parent = nullptr);
void classBegin() override;
void componentComplete() override;
QString filterInterface() const;
void setFilterInterface(const QString &filterInterface);
Q_INVOKABLE nymeaserver::scriptengine::ScriptThing *get(int index) const;
Q_INVOKABLE nymeaserver::scriptengine::ScriptThing *getThing(const QUuid &thingId) const;
signals:
void countChanged();
void filterInterfaceChanged();
void thingAdded(const QString &thingId);
void thingRemoved(const QString &thingId);
protected:
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
private:
ThingManager *m_thingManager = nullptr;
ThingsModel *m_model = nullptr;
QString m_filterInterface;
};
class ThingsModel: public QAbstractListModel
{
Q_OBJECT
public:
enum Roles {
RoleId,
RoleName
};
ThingsModel(ThingManager *thingManager, QObject *parent = nullptr);
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role) const override;
QHash<int, QByteArray> roleNames() const override;
Thing *get(int index) const;
Thing *getThing(const QUuid &thingId) const;
signals:
void countChanged();
private:
ThingManager *m_thingManager = nullptr;
};
}
}
#endif // SCRIPTTHINGS_H

View File

@ -0,0 +1,7 @@
#include "scriptthingsfilter.h"
ScriptThingsFilter::ScriptThingsFilter(QObject *parent)
: QObject{parent}
{
}

View File

@ -0,0 +1,17 @@
#ifndef SCRIPTTHINGSFILTER_H
#define SCRIPTTHINGSFILTER_H
#include <QSortFilterProxyModel>
class ScriptThingsFilter : public QSortFilterProxyModel
{
Q_OBJECT
Q_PROPERTY()
public:
explicit ScriptThingsFilter(QObject *parent = nullptr);
signals:
};
#endif // SCRIPTTHINGSFILTER_H

View File

@ -50,7 +50,6 @@ NYMEA_LOGGING_CATEGORY(dcExperiences, "Experiences")
NYMEA_LOGGING_CATEGORY(dcTimeManager, "TimeManager")
NYMEA_LOGGING_CATEGORY(dcRuleEngine, "RuleEngine")
NYMEA_LOGGING_CATEGORY(dcRuleEngineDebug, "RuleEngineDebug")
NYMEA_LOGGING_CATEGORY(dcScriptEngine, "ScriptEngine")
NYMEA_LOGGING_CATEGORY(dcHardware, "Hardware")
NYMEA_LOGGING_CATEGORY(dcLogEngine, "LogEngine")
NYMEA_LOGGING_CATEGORY(dcServerManager, "ServerManager")

View File

@ -58,7 +58,6 @@ Q_DECLARE_LOGGING_CATEGORY(dcExperiences)
Q_DECLARE_LOGGING_CATEGORY(dcTimeManager)
Q_DECLARE_LOGGING_CATEGORY(dcRuleEngine)
Q_DECLARE_LOGGING_CATEGORY(dcRuleEngineDebug)
Q_DECLARE_LOGGING_CATEGORY(dcScriptEngine)
Q_DECLARE_LOGGING_CATEGORY(dcHardware)
Q_DECLARE_LOGGING_CATEGORY(dcLogEngine)
Q_DECLARE_LOGGING_CATEGORY(dcServerManager)

View File

@ -50,6 +50,11 @@ void TestHelper::logStateChange(const QString &thingId, const QString &stateId,
emit stateChangeLogged(ThingId(thingId), stateId, value);
}
void TestHelper::setTestResult(bool success)
{
emit testResult(success);
}
TestHelper::TestHelper(QObject *parent) : QObject(parent)
{

View File

@ -34,12 +34,17 @@ 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 setTestResult(bool success);
signals:
void setState(const QVariant &value);
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 testResult(bool success);
private:
explicit TestHelper(QObject *parent = nullptr);
static TestHelper* s_instance;

View File

@ -74,6 +74,13 @@ private slots:
void testInterfaceEvent();
void testInterfaceState();
void testInterfaceAction();
void testScriptThingAction();
void testScriptThingReadState();
void testScriptThingWriteState();
void testScriptThingEvent();
void testThingsFindThing();
};
@ -518,6 +525,180 @@ void TestScripts::testInterfaceAction()
}
void TestScripts::testScriptThingAction()
{
QString script = QString("import QtQuick 2.0\n"
"import nymea 1.0\n"
"Item {\n"
" Thing {\n"
" id: thing\n"
" thingId: \"%1\"\n"
" }\n"
" Connections {\n"
" target: TestHelper\n"
" onExecuteAction: {\n"
" thing.executeAction(\"%2\", params)\n"
" }\n"
" }\n"
"}\n").arg(m_mockThingId.toString()).arg("power");
qCDebug(dcTests()) << "Adding script:\n" << qUtf8Printable(script);
ScriptEngine::AddScriptReply reply = NymeaCore::instance()->scriptEngine()->addScript("TestAction", script.toUtf8());
QCOMPARE(reply.scriptError, ScriptEngine::ScriptErrorNoError);
QSignalSpy spy(NymeaCore::instance()->thingManager(), &ThingManager::thingStateChanged);
QVariantMap params;
params.insert("power", true);
TestHelper::instance()->executeAction(params);
spy.wait(1);
QCOMPARE(spy.count(), 1);
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);
}
void TestScripts::testScriptThingReadState()
{
QString script = QString("import QtQuick 2.0\n"
"import nymea 1.0\n"
"Item {\n"
" Thing {\n"
" thingId: \"%1\"\n"
" onStateValueChanged: {\n"
" TestHelper.logStateChange(thingId, stateName, value);\n"
" }\n"
" }\n"
"}\n").arg(m_mockThingId.toString()).arg("power");
qCDebug(dcTests()) << "Adding script:\n" << qUtf8Printable(script);
ScriptEngine::AddScriptReply reply = NymeaCore::instance()->scriptEngine()->addScript("TestState", script.toUtf8());
QCOMPARE(reply.scriptError, ScriptEngine::ScriptErrorNoError);
QSignalSpy spy(TestHelper::instance(), &TestHelper::stateChangeLogged);
// Generate state change
Action action(mockPowerActionTypeId, m_mockThingId);
action.setParams(ParamList() << Param(mockPowerActionPowerParamTypeId, true));
NymeaCore::instance()->thingManager()->executeAction(action);
spy.wait(1);
QCOMPARE(spy.count(), 1);
QCOMPARE(spy.first().at(0).value<ThingId>(), m_mockThingId);
QCOMPARE(spy.first().at(1).toString(), QString("power"));
QCOMPARE(spy.first().at(2).toBool(), true);
}
void TestScripts::testScriptThingWriteState()
{
QString script = QString("import QtQuick 2.0\n"
"import nymea 1.0\n"
"Item {\n"
" Thing {\n"
" id: thing\n"
" thingId: \"%1\"\n"
" }\n"
" Connections {\n"
" target: TestHelper\n"
" onSetState: {\n"
" thing.setStateValue(\"%2\", value)\n"
" }\n"
" }\n"
"}\n").arg(m_mockThingId.toString()).arg("power");
qCDebug(dcTests()) << "Adding script:\n" << qUtf8Printable(script);
ScriptEngine::AddScriptReply reply = NymeaCore::instance()->scriptEngine()->addScript("TestState", script.toUtf8());
QCOMPARE(reply.scriptError, ScriptEngine::ScriptErrorNoError);
QSignalSpy spy(NymeaCore::instance()->thingManager(), &ThingManager::thingStateChanged);
TestHelper::instance()->setState(true);
spy.wait(1);
QCOMPARE(spy.count(), 1);
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);
}
void TestScripts::testScriptThingEvent()
{
QString script = QString("import QtQuick 2.0\n"
"import nymea 1.0\n"
"Item {\n"
" Thing {\n"
" thingId: \"%1\"\n"
" onEventTriggered: {\n"
" TestHelper.logEvent(thingId, eventName, params);\n"
" }\n"
" }\n"
"}\n").arg(m_mockThingId.toString());
qCDebug(dcTests()) << "Adding script:\n" << qUtf8Printable(script);
ScriptEngine::AddScriptReply reply = NymeaCore::instance()->scriptEngine()->addScript("TestEvent", script.toUtf8());
QCOMPARE(reply.scriptError, ScriptEngine::ScriptErrorNoError);
QSignalSpy spy(TestHelper::instance(), &TestHelper::eventLogged);
// trigger event in mock device
Thing* thing = NymeaCore::instance()->thingManager()->findConfiguredThing(m_mockThingId);
int port = thing->paramValue(mockThingHttpportParamTypeId).toInt();
QNetworkRequest request(QUrl(QString("http://localhost:%1/generateevent?eventtypeid=%2&%3=%4")
.arg(port)
.arg(mockEvent2EventTypeId.toString())
.arg(mockEvent2EventIntParamParamTypeId.toString())
.arg(10)));
QNetworkAccessManager nam;
QNetworkReply *r = nam.get(request);
connect(r, &QNetworkReply::finished, r, &QNetworkReply::deleteLater);
spy.wait();
QCOMPARE(spy.count(), 1);
QCOMPARE(spy.first().at(0).value<ThingId>(), m_mockThingId);
QCOMPARE(spy.first().at(1).toString(), QString("event2"));
QVariantMap expectedParams;
expectedParams.insert(mockEvent2EventIntParamParamTypeId.toString().remove(QRegExp("[{}]")), 10);
expectedParams.insert("intParam", 10);
QCOMPARE(spy.first().at(2).toMap(), expectedParams);
}
void TestScripts::testThingsFindThing()
{
QSignalSpy spy(TestHelper::instance(), &TestHelper::testResult);
QString script = QString("import QtQuick 2.0\n"
"import nymea 1.0\n"
"Item {\n"
" id: root\n"
" property string thingId: \"%1\"\n"
" Things {\n"
" id: things\n"
" }\n"
" Component.onCompleted: {\n"
" var thing = things.getThing(root.thingId)\n"
" TestHelper.setTestResult(thing.thingId == root.thingId);\n"
" }\n"
"}\n").arg(m_mockThingId.toString());
qCDebug(dcTests()) << "Adding script:\n" << qUtf8Printable(script);
ScriptEngine::AddScriptReply reply = NymeaCore::instance()->scriptEngine()->addScript("TestEvent", script.toUtf8());
QCOMPARE(reply.scriptError, ScriptEngine::ScriptErrorNoError);
QCOMPARE(spy.count(), 1);
QCOMPARE(spy.first().at(0).toBool(), true);
}
#include "testscripts.moc"
QTEST_MAIN(TestScripts)