From 9322cc79e9e8782620d2d16139b95f11cd11043f Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Thu, 7 Nov 2019 11:32:58 +0100 Subject: [PATCH 01/28] Initial attempt to script a plugin --- .../devices/devicemanagerimplementation.cpp | 5 ++ libnymea-core/devices/scriptdeviceplugin.cpp | 84 +++++++++++++++++++ libnymea-core/devices/scriptdeviceplugin.h | 28 +++++++ libnymea-core/libnymea-core.pro | 4 +- 4 files changed, 120 insertions(+), 1 deletion(-) create mode 100644 libnymea-core/devices/scriptdeviceplugin.cpp create mode 100644 libnymea-core/devices/scriptdeviceplugin.h diff --git a/libnymea-core/devices/devicemanagerimplementation.cpp b/libnymea-core/devices/devicemanagerimplementation.cpp index 48c365e5..39bbae23 100644 --- a/libnymea-core/devices/devicemanagerimplementation.cpp +++ b/libnymea-core/devices/devicemanagerimplementation.cpp @@ -23,6 +23,7 @@ #include "devicemanagerimplementation.h" #include "translator.h" +#include "scriptdeviceplugin.h" #include "loggingcategories.h" #include "typeutils.h" @@ -1104,6 +1105,10 @@ void DeviceManagerImplementation::loadPlugins() loadPlugin(pluginIface, metaData); } } + + ScriptDevicePlugin *plugin = new ScriptDevicePlugin(this); + plugin->loadScript("/home/micha/Develop/nymea-plugin-jstest/devicepluginjstest.js"); + loadPlugin(plugin, plugin->metaData()); } void DeviceManagerImplementation::loadPlugin(DevicePlugin *pluginIface, const PluginMetadata &metaData) diff --git a/libnymea-core/devices/scriptdeviceplugin.cpp b/libnymea-core/devices/scriptdeviceplugin.cpp new file mode 100644 index 00000000..89fa90c2 --- /dev/null +++ b/libnymea-core/devices/scriptdeviceplugin.cpp @@ -0,0 +1,84 @@ +#include "scriptdeviceplugin.h" + +#include +#include +#include + +#include "loggingcategories.h" + +ScriptDevicePlugin::ScriptDevicePlugin(QObject *parent) : DevicePlugin(parent) +{ + +} + +bool ScriptDevicePlugin::loadScript(const QString &fileName) +{ + + QFileInfo fi(fileName); + QString metaDataFileName = fileName + "on"; + + QFile metaDataFile(metaDataFileName); + if (!metaDataFile.open(QFile::ReadOnly)) { + qCWarning(dcDeviceManager()) << "Failed to open plugin metadata at" << metaDataFileName; + return false; + } + QJsonParseError error; + QByteArray data = metaDataFile.readAll(); + QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error); + metaDataFile.close(); + + if (error.error != QJsonParseError::NoError) { + int errorOffset = error.offset; + int newLineIndex = data.indexOf("\n"); + int lineIndex = 1; + while (newLineIndex > 0 && errorOffset > newLineIndex) { + data.remove(0, newLineIndex + 2); + errorOffset -= (newLineIndex + 2); + newLineIndex = data.indexOf("\n"); + lineIndex++; + } + if (newLineIndex >= 0) { + data = data.left(newLineIndex); + } + QString spacer; + for (int i = 0; i < errorOffset; i++) { + spacer += ' '; + } + QDebug dbg = qWarning(dcDeviceManager()).nospace().noquote(); + dbg << metaDataFileName << ":" << lineIndex << ":" << errorOffset + 2 << ": error: JSON parsing failed: " << error.errorString() << ": " << data.trimmed() << endl; + dbg << data << endl; + dbg << spacer << "^"; + return false; + } + m_metaData = QJsonObject::fromVariantMap(jsonDoc.toVariant().toMap()); + + + QFile scriptFile(fileName); + if (!scriptFile.open(QIODevice::ReadOnly)) { + return false; + } + + m_engine = new QJSEngine(this); + m_engine->installExtensions(QJSEngine::ConsoleExtension); + + QTextStream stream(&scriptFile); + QString contents = stream.readAll(); + scriptFile.close(); + QJSValue result = m_engine->evaluate(contents, fileName); + if (result.isError()) { + qCWarning(dcDeviceManager()) << "Error evaluating script" << fileName << result.toString(); + return false; + } + + return true; +} + +QJsonObject ScriptDevicePlugin::metaData() const +{ + return m_metaData; +} + +void ScriptDevicePlugin::init() +{ + +} diff --git a/libnymea-core/devices/scriptdeviceplugin.h b/libnymea-core/devices/scriptdeviceplugin.h new file mode 100644 index 00000000..c28c07a5 --- /dev/null +++ b/libnymea-core/devices/scriptdeviceplugin.h @@ -0,0 +1,28 @@ +#ifndef SCRIPTDEVICEPLUGIN_H +#define SCRIPTDEVICEPLUGIN_H + +#include "devices/deviceplugin.h" + +#include +#include + +class ScriptDevicePlugin : public DevicePlugin +{ + Q_OBJECT +public: + explicit ScriptDevicePlugin(QObject *parent = nullptr); + + bool loadScript(const QString &fileName); + QJsonObject metaData() const; + + void init() override; +signals: + +public slots: + +private: + QJSEngine *m_engine = nullptr; + QJsonObject m_metaData; +}; + +#endif // SCRIPTDEVICEPLUGIN_H diff --git a/libnymea-core/libnymea-core.pro b/libnymea-core/libnymea-core.pro index 5e5fbf32..5d4ceceb 100644 --- a/libnymea-core/libnymea-core.pro +++ b/libnymea-core/libnymea-core.pro @@ -3,7 +3,7 @@ TARGET = nymea-core include(../nymea.pri) -QT += sql +QT += sql qml INCLUDEPATH += $$top_srcdir/libnymea LIBS += -L$$top_builddir/libnymea/ -lnymea -lssl -lcrypto -lnymea-mqtt @@ -17,6 +17,7 @@ RESOURCES += $$top_srcdir/icons.qrc \ HEADERS += nymeacore.h \ devices/devicemanagerimplementation.h \ + devices/scriptdeviceplugin.h \ devices/translator.h \ experiences/experiencemanager.h \ jsonrpc/jsonrpcserverimplementation.h \ @@ -91,6 +92,7 @@ HEADERS += nymeacore.h \ SOURCES += nymeacore.cpp \ devices/devicemanagerimplementation.cpp \ + devices/scriptdeviceplugin.cpp \ devices/translator.cpp \ experiences/experiencemanager.cpp \ jsonrpc/jsonrpcserverimplementation.cpp \ From b97e4e5b0cd75940c3c3fb9fb48d437e66bfac7c Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Tue, 12 Nov 2019 15:43:39 +0100 Subject: [PATCH 02/28] Add support for JS device plugins --- .../devices/devicemanagerimplementation.cpp | 28 ++- libnymea-core/devices/scriptdeviceplugin.cpp | 176 +++++++++++++++++- libnymea-core/devices/scriptdeviceplugin.h | 146 ++++++++++++++- libnymea-core/jsonrpc/devicehandler.cpp | 2 + libnymea/devices/devicesetupinfo.h | 1 + libnymea/devices/deviceutils.cpp | 10 +- libnymea/hardwaremanager.cpp | 1 - libnymea/hardwaremanager.h | 1 + libnymea/plugintimer.h | 4 +- libnymea/typeutils.h | 17 +- tests/auto/logging/testlogging.cpp | 2 +- tests/auto/rules/testrules.cpp | 6 +- 12 files changed, 361 insertions(+), 33 deletions(-) diff --git a/libnymea-core/devices/devicemanagerimplementation.cpp b/libnymea-core/devices/devicemanagerimplementation.cpp index 39bbae23..5a3ba256 100644 --- a/libnymea-core/devices/devicemanagerimplementation.cpp +++ b/libnymea-core/devices/devicemanagerimplementation.cpp @@ -269,6 +269,10 @@ DeviceDiscoveryInfo* DeviceManagerImplementation::discoverDevices(const DeviceCl } qCDebug(dcDeviceManager()) << "Discovery finished. Found devices:" << discoveryInfo->deviceDescriptors().count(); foreach (const DeviceDescriptor &descriptor, discoveryInfo->deviceDescriptors()) { + if (!descriptor.isValid()) { + qCWarning(dcDeviceManager()) << "Descriptor is invalid. Not adding to results"; + continue; + } m_discoveredDevices.insert(descriptor.id(), descriptor); } }); @@ -296,8 +300,10 @@ DeviceSetupInfo* DeviceManagerImplementation::addConfiguredDevice(const DeviceCl * Returns \l{DeviceError} to inform about the result. */ DeviceSetupInfo *DeviceManagerImplementation::addConfiguredDevice(const DeviceDescriptorId &deviceDescriptorId, const ParamList ¶ms, const QString &name) { + qWarning() << "Have descriptors" << m_discoveredDevices.keys(); DeviceDescriptor descriptor = m_discoveredDevices.value(deviceDescriptorId); if (!descriptor.isValid()) { + qCWarning(dcDeviceManager()) << "Cannot add device. DeviceDescriptor" << deviceDescriptorId << "not found."; DeviceSetupInfo *info = new DeviceSetupInfo(nullptr, this); info->finish(Device::DeviceErrorDeviceDescriptorNotFound); return info; @@ -305,11 +311,13 @@ DeviceSetupInfo *DeviceManagerImplementation::addConfiguredDevice(const DeviceDe DeviceClass deviceClass = findDeviceClass(descriptor.deviceClassId()); if (!deviceClass.isValid()) { + qCWarning(dcDeviceManager()) << "Cannot add device. DeviceClass" << descriptor.deviceClassId() << "not found."; DeviceSetupInfo *info = new DeviceSetupInfo(nullptr, this); info->finish(Device::DeviceErrorDeviceClassNotFound); return info; } if (!deviceClass.createMethods().testFlag(DeviceClass::CreateMethodDiscovery)) { + qCWarning(dcDeviceManager()) << "Cannot add device. This device cannot be added via discovery."; DeviceSetupInfo *info = new DeviceSetupInfo(nullptr, this); info->finish(Device::DeviceErrorCreationMethodNotSupported); return info; @@ -675,12 +683,14 @@ DeviceSetupInfo* DeviceManagerImplementation::addConfiguredDeviceInternal(const { DeviceClass deviceClass = findDeviceClass(deviceClassId); if (deviceClass.id().isNull()) { + qCWarning(dcDeviceManager()) << "Cannot add device. DeviceClass" << deviceClassId << "not found."; DeviceSetupInfo *info = new DeviceSetupInfo(nullptr, this); info->finish(Device::DeviceErrorDeviceClassNotFound); return info; } if (deviceClass.setupMethod() != DeviceClass::SetupMethodJustAdd) { + qCWarning(dcDeviceManager()) << "Cannot add device. This device cannot be added this way. (SetupMethodJustAdd)"; DeviceSetupInfo *info = new DeviceSetupInfo(nullptr, this); info->finish(Device::DeviceErrorCreationMethodNotSupported); return info; @@ -694,6 +704,7 @@ DeviceSetupInfo* DeviceManagerImplementation::addConfiguredDeviceInternal(const DevicePlugin *plugin = m_devicePlugins.value(deviceClass.pluginId()); if (!plugin) { + qCWarning(dcDeviceManager()) << "Cannot add device. Plugin for device class" << deviceClass.name() << "not found."; DeviceSetupInfo *info = new DeviceSetupInfo(nullptr, this); info->finish(Device::DeviceErrorPluginNotFound); return info; @@ -703,6 +714,7 @@ DeviceSetupInfo* DeviceManagerImplementation::addConfiguredDeviceInternal(const ParamList effectiveParams = buildParams(deviceClass.paramTypes(), params); Device::DeviceError paramsResult = DeviceUtils::verifyParams(deviceClass.paramTypes(), effectiveParams); if (paramsResult != Device::DeviceErrorNoError) { + qCWarning(dcDeviceManager()) << "Cannot add device. Parameter verification failed."; DeviceSetupInfo *info = new DeviceSetupInfo(nullptr, this); info->finish(paramsResult); return info; @@ -1107,8 +1119,20 @@ void DeviceManagerImplementation::loadPlugins() } ScriptDevicePlugin *plugin = new ScriptDevicePlugin(this); - plugin->loadScript("/home/micha/Develop/nymea-plugin-jstest/devicepluginjstest.js"); - loadPlugin(plugin, plugin->metaData()); + bool ret = plugin->loadScript("/home/micha/Develop/nymea-plugin-jstest/devicepluginjstest.js"); + if (!ret) { + delete plugin; + qCWarning(dcDeviceManager()) << "JS plugin failed to load"; + return; + } + PluginMetadata metaData(plugin->metaData()); + if (!metaData.isValid()) { + qCWarning(dcDeviceManager()) << "Not loading JS plugin. Invalid metadata."; + foreach (const QString &error, metaData.validationErrors()) { + qCWarning(dcDeviceManager()) << error; + } + } + loadPlugin(plugin, metaData); } void DeviceManagerImplementation::loadPlugin(DevicePlugin *pluginIface, const PluginMetadata &metaData) diff --git a/libnymea-core/devices/scriptdeviceplugin.cpp b/libnymea-core/devices/scriptdeviceplugin.cpp index 89fa90c2..a0654e3c 100644 --- a/libnymea-core/devices/scriptdeviceplugin.cpp +++ b/libnymea-core/devices/scriptdeviceplugin.cpp @@ -5,6 +5,7 @@ #include #include "loggingcategories.h" +#include ScriptDevicePlugin::ScriptDevicePlugin(QObject *parent) : DevicePlugin(parent) { @@ -15,7 +16,7 @@ bool ScriptDevicePlugin::loadScript(const QString &fileName) { QFileInfo fi(fileName); - QString metaDataFileName = fileName + "on"; + QString metaDataFileName = fi.absoluteDir().path() + '/' + fi.baseName() + ".json"; QFile metaDataFile(metaDataFileName); if (!metaDataFile.open(QFile::ReadOnly)) { @@ -58,18 +59,18 @@ bool ScriptDevicePlugin::loadScript(const QString &fileName) return false; } - m_engine = new QJSEngine(this); - m_engine->installExtensions(QJSEngine::ConsoleExtension); + m_engine = new QQmlEngine(this); + m_engine->installExtensions(QJSEngine::AllExtensions); + m_engine->addImportPath(fi.absoluteDir().path() + "/node_modules/"); + qCWarning(dcDeviceManager()) << "Engine import path list" << m_engine->importPathList(); - QTextStream stream(&scriptFile); - QString contents = stream.readAll(); - scriptFile.close(); - QJSValue result = m_engine->evaluate(contents, fileName); - if (result.isError()) { - qCWarning(dcDeviceManager()) << "Error evaluating script" << fileName << result.toString(); + m_pluginImport = m_engine->importModule(fileName); + if (m_pluginImport.isError()) { + qCWarning(dcDeviceManager()) << "Error loading plugin module" << m_pluginImport.errorType() << m_pluginImport.toString(); return false; } + qCDebug(dcDeviceManager()) << "Loaded JS plugin" << fileName; return true; } @@ -80,5 +81,162 @@ QJsonObject ScriptDevicePlugin::metaData() const void ScriptDevicePlugin::init() { + qmlRegisterType(); + qmlRegisterType(); + QJSValue hardwareManagerObject = m_engine->newQObject(hardwareManager()); + m_engine->globalObject().setProperty("hardwareManager", hardwareManagerObject); + + if (!m_pluginImport.hasOwnProperty("init")) { + DevicePlugin::init(); + return; + } + QJSValue initFunction = m_pluginImport.property("init"); + QJSValue result = initFunction.call(); + if (result.isError()) { + qCWarning(dcDeviceManager()) << "Error calling init in JS plugin:" << result.toString(); + return; + } +} + +void ScriptDevicePlugin::discoverDevices(DeviceDiscoveryInfo *info) +{ + if (!m_pluginImport.hasOwnProperty("discoverDevices")) { + DevicePlugin::discoverDevices(info); + return; + } + + ScriptDeviceDiscoveryInfo *scriptInfo = new ScriptDeviceDiscoveryInfo(info); + QJSValue jsInfo = m_engine->newQObject(scriptInfo); + + QJSValue discoverFunction = m_pluginImport.property("discoverDevices"); + QJSValue ret = discoverFunction.call({jsInfo}); + if (ret.isError()) { + qCWarning(dcDeviceManager()) << "discoverDevices script failed to execute:\n" << ret.toString(); + } +} + +void ScriptDevicePlugin::startPairing(DevicePairingInfo *info) +{ + if (!m_pluginImport.hasOwnProperty("startPairing")) { + DevicePlugin::startPairing(info); + return; + } + + ScriptDevicePairingInfo *scriptInfo = new ScriptDevicePairingInfo(info); + QJSValue jsInfo = m_engine->newQObject(scriptInfo); + + QJSValue startPairingFunction = m_pluginImport.property("startPairing"); + QJSValue ret = startPairingFunction.call({jsInfo}); + if (ret.isError()) { + qCWarning(dcDeviceManager()) << "startPairing script failed to execute:\n" << ret.toString(); + } +} + +void ScriptDevicePlugin::confirmPairing(DevicePairingInfo *info, const QString &username, const QString &secret) +{ + if (!m_pluginImport.hasOwnProperty("confirmPairing")) { + DevicePlugin::confirmPairing(info, username, secret); + return; + } + + ScriptDevicePairingInfo *scriptInfo = new ScriptDevicePairingInfo(info); + QJSValue jsInfo = m_engine->newQObject(scriptInfo); + + QJSValue confirmPairingFunction = m_pluginImport.property("confirmPairing"); + QJSValue ret = confirmPairingFunction.call({jsInfo, username, secret}); + if (ret.isError()) { + qCWarning(dcDeviceManager()) << "confirmPairing script failed to execute:\n" << ret.toString(); + } +} + +void ScriptDevicePlugin::startMonitoringAutoDevices() +{ + if (!m_pluginImport.hasOwnProperty("startMonitoringAutoDevices")) { + DevicePlugin::startMonitoringAutoDevices(); + return; + } + + QJSValue monitorFunction = m_pluginImport.property("startMonitoringAutoDevices"); + QJSValue ret = monitorFunction.call(); + if (ret.isError()) { + qCWarning(dcDeviceManager()) << "startMonitoringAutoDevices failed to execute:\n" << ret.toString(); + } +} + +void ScriptDevicePlugin::setupDevice(DeviceSetupInfo *info) +{ + if (!m_pluginImport.hasOwnProperty("setupDevice")) { + DevicePlugin::setupDevice(info); + return; + } + QJSValue setupFunction = m_pluginImport.property("setupDevice"); + + Device *device = info->device(); + ScriptDevice *scriptDevice = new ScriptDevice(device); + m_devices.insert(device, scriptDevice); + connect(device, &Device::destroyed, this, [this, device](){ + m_devices.remove(device); + }); + + ScriptDeviceSetupInfo *scriptInfo = new ScriptDeviceSetupInfo(info, scriptDevice); + + qWarning() << "Setup params" << info->device()->params(); + QJSValue jsInfo = m_engine->newQObject(scriptInfo); + QJSValue ret = setupFunction.call({jsInfo}); + + if (ret.errorType() != QJSValue::NoError) { + qCWarning(dcDeviceManager()) << "setupDevice script failed to execute:\n" << ret.toString(); + } +} + +void ScriptDevicePlugin::postSetupDevice(Device *device) +{ + if (!m_pluginImport.hasOwnProperty("postSetupDevice")) { + DevicePlugin::postSetupDevice(device); + return; + } + QJSValue postSetupFunction = m_pluginImport.property("postSetupDevice"); + + QJSValue jsDevice = m_engine->newQObject(m_devices.value(device)); + QJSValue ret = postSetupFunction.call({jsDevice}); + if (ret.errorType() != QJSValue::NoError) { + qCWarning(dcDeviceManager()) << "setupDevice script failed to execute:\n" << ret.toString(); + } +} + +void ScriptDevicePlugin::deviceRemoved(Device *device) +{ + if (!m_pluginImport.hasOwnProperty("deviceRemoved")) { + DevicePlugin::deviceRemoved(device); + return; + } + + QJSValue jsDevice = m_engine->newQObject(m_devices.value(device)); + + QJSValue deviceRemovedFunction = m_pluginImport.property("deviceRemoved"); + QJSValue ret = deviceRemovedFunction.call({jsDevice}); + if (ret.isError()) { + qCWarning(dcDeviceManager()) << "deviceRemoved script failed to execute:\n" << ret.toString(); + } +} + +void ScriptDevicePlugin::executeAction(DeviceActionInfo *info) +{ + if (!m_pluginImport.hasOwnProperty("executeAction")) { + DevicePlugin::executeAction(info); + return; + } + + ScriptDevice *scriptDevice = m_devices.value(info->device()); + QJSValue jsDevice = m_engine->newQObject(scriptDevice); + + ScriptDeviceActionInfo *scriptInfo = new ScriptDeviceActionInfo(info, scriptDevice); + QJSValue jsInfo = m_engine->newQObject(scriptInfo); + + QJSValue executeActionFunction = m_pluginImport.property("executeAction"); + QJSValue ret = executeActionFunction.call({jsInfo}); + if (ret.isError()) { + qCWarning(dcDeviceManager()) << "executeAction script failed to execute:\n" << ret.toString(); + } } diff --git a/libnymea-core/devices/scriptdeviceplugin.h b/libnymea-core/devices/scriptdeviceplugin.h index c28c07a5..9f5194dd 100644 --- a/libnymea-core/devices/scriptdeviceplugin.h +++ b/libnymea-core/devices/scriptdeviceplugin.h @@ -3,9 +3,137 @@ #include "devices/deviceplugin.h" -#include +#include #include +class ScriptDeviceDiscoveryInfo: public QObject +{ + Q_OBJECT +public: + ScriptDeviceDiscoveryInfo(DeviceDiscoveryInfo *info): QObject(info), m_info(info) { + connect(info, &DeviceDiscoveryInfo::aborted, this, &ScriptDeviceDiscoveryInfo::aborted); + connect(info, &DeviceDiscoveryInfo::finished, this, &ScriptDeviceDiscoveryInfo::finished); + } + Q_INVOKABLE void addDeviceDescriptor(const QUuid &deviceClassId, const QString &title, const QString &description = QString(), const QVariantList ¶ms = QVariantList(), const QUuid &parentDeviceId = QUuid()) { + ParamList paramList; + for (int i = 0; i < params.count(); i++) { + paramList << Param(params.at(i).toMap().value("paramTypeId").toUuid(), params.at(i).toMap().value("value")); + } + DeviceDescriptor d(deviceClassId, title, description, parentDeviceId); + d.setParams(paramList); + m_info->addDeviceDescriptor(d); + } + Q_INVOKABLE void finish(Device::DeviceError status = Device::DeviceErrorNoError, const QString &displayMessage = QString()) { + m_info->finish(status, displayMessage); + } +signals: + void aborted(); + void finished(); +private: + DeviceDiscoveryInfo *m_info = nullptr; +}; + +class ScriptDevice: public QObject +{ + Q_OBJECT + Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged) +public: + ScriptDevice(Device *device): QObject(device), m_device(device) { + connect(device, &Device::nameChanged, this, &ScriptDevice::nameChanged); + } + + QString name() const { return m_device->name(); } + void setName(const QString &name) { m_device->setName(name); } + + Q_INVOKABLE QVariant paramValue(const QUuid ¶mTypeId) { return m_device->paramValue(paramTypeId); } + Q_INVOKABLE void setParamValue(const QUuid ¶mTypeId, const QVariant &value) { m_device->setParamValue(paramTypeId, value); } + + Q_INVOKABLE QVariant stateValue(const QUuid &stateTypeId) { return m_device->stateValue(stateTypeId); } + Q_INVOKABLE void setStateValue(const QUuid &stateTypeId, const QVariant &value) { m_device->setStateValue(stateTypeId, value); } + +signals: + void nameChanged(); + +private: + Device *m_device = nullptr; +}; + +class ScriptDeviceSetupInfo: public QObject +{ + Q_OBJECT + Q_PROPERTY(ScriptDevice* device READ device CONSTANT) +public: + ScriptDeviceSetupInfo(DeviceSetupInfo *info, ScriptDevice *scriptDevice): QObject(info), m_info(info), m_device(scriptDevice) { + connect(info, &DeviceSetupInfo::aborted, this, &ScriptDeviceSetupInfo::aborted); + connect(info, &DeviceSetupInfo::finished, this, &ScriptDeviceSetupInfo::finished); + } + Q_INVOKABLE void finish(Device::DeviceError status = Device::DeviceErrorNoError, const QString &displayMessage = QString()) { + m_info->finish(status, displayMessage); + } + ScriptDevice* device() const { return m_device; } +signals: + void aborted(); + void finished(); +private: + DeviceSetupInfo *m_info = nullptr; + ScriptDevice* m_device = nullptr; +}; + +class ScriptDevicePairingInfo: public QObject +{ + Q_OBJECT + Q_PROPERTY(QUuid deviceClassId READ deviceClassId CONSTANT) + Q_PROPERTY(QUuid deviceId READ deviceId CONSTANT) + Q_PROPERTY(QString deviceName READ deviceName CONSTANT) + Q_PROPERTY(QUuid parentDeviceId READ parentDeviceId CONSTANT) + Q_PROPERTY(QUrl oAuthUrl READ oAuthUrl WRITE setOAuthUrl) +public: + ScriptDevicePairingInfo(DevicePairingInfo* info): QObject(info), m_info(info) { + connect(info, &DevicePairingInfo::aborted, this, &ScriptDevicePairingInfo::aborted); + connect(info, &DevicePairingInfo::finished, this, &ScriptDevicePairingInfo::finished); + } + Q_INVOKABLE QVariant paramValue(const QUuid ¶mTypeId) { return m_info->params().paramValue(paramTypeId); } + Q_INVOKABLE void finish(Device::DeviceError status = Device::DeviceErrorNoError, const QString &displayMessage = QString()) { + m_info->finish(status, displayMessage); + } + QUuid deviceClassId() const { return m_info->deviceClassId(); } + QUuid deviceId() const { return m_info->deviceId(); } + QString deviceName() const { return m_info->deviceName(); } + QUuid parentDeviceId() const { return m_info->parentDeviceId(); } + QUrl oAuthUrl() const { return m_info->oAuthUrl(); } + void setOAuthUrl(const QUrl &oAuthUrl) { m_info->setOAuthUrl(oAuthUrl); } +signals: + void aborted(); + void finished(); +private: + DevicePairingInfo *m_info = nullptr; +}; + +class ScriptDeviceActionInfo: public QObject +{ + Q_OBJECT + Q_PROPERTY(ScriptDevice* device READ device CONSTANT) + Q_PROPERTY(QUuid actionTypeId READ actionTypeId CONSTANT) +public: + ScriptDeviceActionInfo(DeviceActionInfo* info, ScriptDevice* scriptDevice): QObject(info), m_info(info), m_device(scriptDevice) { + connect(info, &DeviceActionInfo::finished, this, &ScriptDeviceActionInfo::finished); + connect(info, &DeviceActionInfo::aborted, this, &ScriptDeviceActionInfo::aborted); + } + ScriptDevice* device() const { return m_device; } + QUuid actionTypeId() const { return m_info->action().actionTypeId(); } + Q_INVOKABLE QVariant paramValue(const QUuid ¶mTypeId) { return m_info->action().params().paramValue(paramTypeId); } + Q_INVOKABLE void finish(Device::DeviceError status = Device::DeviceErrorNoError, const QString &displayMessage = QString()) { + m_info->finish(status, displayMessage); + } + +signals: + void aborted(); + void finished(); +private: + DeviceActionInfo* m_info = nullptr; + ScriptDevice* m_device = nullptr; +}; + class ScriptDevicePlugin : public DevicePlugin { Q_OBJECT @@ -16,13 +144,21 @@ public: QJsonObject metaData() const; void init() override; -signals: - -public slots: + void startMonitoringAutoDevices() override; + void discoverDevices(DeviceDiscoveryInfo *info) override; + void startPairing(DevicePairingInfo *info) override; + void confirmPairing(DevicePairingInfo *info, const QString &username, const QString &secret) override; + void setupDevice(DeviceSetupInfo *info) override; + void postSetupDevice(Device *device) override; + void deviceRemoved(Device *device) override; + void executeAction(DeviceActionInfo *info) override; + void browseDevice(BrowseResult *result) override; private: - QJSEngine *m_engine = nullptr; + QQmlEngine *m_engine = nullptr; QJsonObject m_metaData; + QJSValue m_pluginImport; + QHash m_devices; }; #endif // SCRIPTDEVICEPLUGIN_H diff --git a/libnymea-core/jsonrpc/devicehandler.cpp b/libnymea-core/jsonrpc/devicehandler.cpp index 367b0c88..1de7c8dc 100644 --- a/libnymea-core/jsonrpc/devicehandler.cpp +++ b/libnymea-core/jsonrpc/devicehandler.cpp @@ -455,6 +455,8 @@ JsonReply *DeviceHandler::GetDiscoveredDevices(const QVariantMap ¶ms) const QVariantList deviceDescriptorList; foreach (const DeviceDescriptor &deviceDescriptor, info->deviceDescriptors()) { deviceDescriptorList.append(pack(deviceDescriptor)); + qWarning() << "*** Discovery result:" << deviceDescriptor.params(); + qWarning() << "Packed:" << pack(deviceDescriptor); } returns.insert("deviceDescriptors", deviceDescriptorList); } diff --git a/libnymea/devices/devicesetupinfo.h b/libnymea/devices/devicesetupinfo.h index 614b291b..8bdbb445 100644 --- a/libnymea/devices/devicesetupinfo.h +++ b/libnymea/devices/devicesetupinfo.h @@ -32,6 +32,7 @@ class DeviceManager; class LIBNYMEA_EXPORT DeviceSetupInfo : public QObject { Q_OBJECT + Q_PROPERTY(Device* device READ device CONSTANT) public: explicit DeviceSetupInfo(Device *device, DeviceManager *deviceManager, quint32 timeout = 0); diff --git a/libnymea/devices/deviceutils.cpp b/libnymea/devices/deviceutils.cpp index 07126c91..b2d81572 100644 --- a/libnymea/devices/deviceutils.cpp +++ b/libnymea/devices/deviceutils.cpp @@ -185,7 +185,7 @@ Interface DeviceUtils::loadInterface(const QString &name) ActionTypes actionTypes; EventTypes eventTypes; foreach (const QVariant &stateVariant, content.value("states").toList()) { - StateType stateType(StateTypeId::fromUuid(QUuid())); + StateType stateType; stateType.setName(stateVariant.toMap().value("name").toString()); stateType.setType(QVariant::nameToType(stateVariant.toMap().value("type").toByteArray())); stateType.setPossibleValues(stateVariant.toMap().value("allowedValues").toList()); @@ -193,7 +193,7 @@ Interface DeviceUtils::loadInterface(const QString &name) stateType.setMaxValue(stateVariant.toMap().value("maxValue")); stateTypes.append(stateType); - EventType stateChangeEventType(EventTypeId::fromUuid(QUuid())); + EventType stateChangeEventType; stateChangeEventType.setName(stateType.name()); ParamType stateChangeEventParamType; stateChangeEventParamType.setName(stateType.name()); @@ -205,7 +205,7 @@ Interface DeviceUtils::loadInterface(const QString &name) eventTypes.append(stateChangeEventType); if (stateVariant.toMap().value("writable", false).toBool()) { - ActionType stateChangeActionType(ActionTypeId::fromUuid(QUuid())); + ActionType stateChangeActionType; stateChangeActionType.setName(stateType.name()); stateChangeActionType.setParamTypes(ParamTypes() << stateChangeEventParamType); actionTypes.append(stateChangeActionType); @@ -213,7 +213,7 @@ Interface DeviceUtils::loadInterface(const QString &name) } foreach (const QVariant &actionVariant, content.value("actions").toList()) { - ActionType actionType(ActionTypeId::fromUuid(QUuid())); + ActionType actionType; actionType.setName(actionVariant.toMap().value("name").toString()); ParamTypes paramTypes; foreach (const QVariant &actionParamVariant, actionVariant.toMap().value("params").toList()) { @@ -229,7 +229,7 @@ Interface DeviceUtils::loadInterface(const QString &name) } foreach (const QVariant &eventVariant, content.value("events").toList()) { - EventType eventType(EventTypeId::fromUuid(QUuid())); + EventType eventType; eventType.setName(eventVariant.toMap().value("name").toString()); ParamTypes paramTypes; foreach (const QVariant &eventParamVariant, eventVariant.toMap().value("params").toList()) { diff --git a/libnymea/hardwaremanager.cpp b/libnymea/hardwaremanager.cpp index 21f9e3dd..9cb8d553 100644 --- a/libnymea/hardwaremanager.cpp +++ b/libnymea/hardwaremanager.cpp @@ -69,7 +69,6 @@ HardwareManager::HardwareManager(QObject *parent) : QObject(parent) { - } /*! Sets the given \a resource to \a enabled. This allows to enable/disable individual \l{HardwareResource}{HardwareResources}. */ diff --git a/libnymea/hardwaremanager.h b/libnymea/hardwaremanager.h index a88ab580..9f30580c 100644 --- a/libnymea/hardwaremanager.h +++ b/libnymea/hardwaremanager.h @@ -38,6 +38,7 @@ class HardwareResource; class HardwareManager : public QObject { Q_OBJECT + Q_PROPERTY(PluginTimerManager* pluginTimerManager READ pluginTimerManager CONSTANT) public: HardwareManager(QObject *parent = nullptr); diff --git a/libnymea/plugintimer.h b/libnymea/plugintimer.h index ffda5446..a08c85b7 100644 --- a/libnymea/plugintimer.h +++ b/libnymea/plugintimer.h @@ -66,8 +66,8 @@ public: PluginTimerManager(QObject *parent = nullptr); virtual ~PluginTimerManager() = default; - virtual PluginTimer *registerTimer(int seconds = 60) = 0; - virtual void unregisterTimer(PluginTimer *timer = nullptr) = 0; + Q_INVOKABLE virtual PluginTimer *registerTimer(int seconds = 60) = 0; + Q_INVOKABLE virtual void unregisterTimer(PluginTimer *timer = nullptr) = 0; }; #endif // PLUGINTIMER_H diff --git a/libnymea/typeutils.h b/libnymea/typeutils.h index a8439a69..37690efe 100644 --- a/libnymea/typeutils.h +++ b/libnymea/typeutils.h @@ -29,13 +29,20 @@ #include "libnymea.h" -#define DECLARE_TYPE_ID(type) class type##Id: public QUuid \ +class GadgetUuid: public QUuid +{ + Q_GADGET +public: + GadgetUuid() {} + GadgetUuid(const QUuid &uuid): QUuid(uuid) {} +}; + +#define DECLARE_TYPE_ID(type) class type##Id: public GadgetUuid \ { \ public: \ - type##Id(const QUuid &uuid): QUuid(uuid) {} \ - type##Id(): QUuid() {} \ - static type##Id create##type##Id() { return type##Id(QUuid::createUuid().toString()); } \ - static type##Id fromUuid(const QUuid &uuid) { return type##Id(uuid.toString()); } \ + type##Id(const QUuid &uuid): GadgetUuid(uuid) {} \ + type##Id(): GadgetUuid() {} \ + static type##Id create##type##Id() { return type##Id(QUuid::createUuid()); } \ bool operator==(const type##Id &other) const { \ return toString() == other.toString(); \ } \ diff --git a/tests/auto/logging/testlogging.cpp b/tests/auto/logging/testlogging.cpp index 98e40a75..ba6fda7f 100644 --- a/tests/auto/logging/testlogging.cpp +++ b/tests/auto/logging/testlogging.cpp @@ -599,7 +599,7 @@ void TestLogging::testHouseKeeping() deviceParams.append(httpParam); params.insert("deviceParams", deviceParams); QVariant response = injectAndWait("Devices.AddConfiguredDevice", params); - DeviceId deviceId = DeviceId::fromUuid(response.toMap().value("params").toMap().value("deviceId").toUuid()); + DeviceId deviceId = DeviceId(response.toMap().value("params").toMap().value("deviceId").toUuid()); QVERIFY2(!deviceId.isNull(), "Something went wrong creating the device for testing."); // Trigger something that creates a logging entry diff --git a/tests/auto/rules/testrules.cpp b/tests/auto/rules/testrules.cpp index e390457e..a7d7407c 100644 --- a/tests/auto/rules/testrules.cpp +++ b/tests/auto/rules/testrules.cpp @@ -2766,7 +2766,7 @@ void TestRules::testInitStatesActive() params.insert("exitActions", exitActions); QVariant response = injectAndWait("Rules.AddRule", params); - RuleId ruleId = RuleId::fromUuid(response.toMap().value("params").toMap().value("ruleId").toUuid()); + RuleId ruleId = RuleId(response.toMap().value("params").toMap().value("ruleId").toUuid()); QVERIFY2(!ruleId.isNull(), "Error adding rule"); // Get the current state value, make sure it's false @@ -3140,7 +3140,7 @@ void TestRules::testHousekeeping() deviceParams.append(httpParam); params.insert("deviceParams", deviceParams); QVariant response = injectAndWait("Devices.AddConfiguredDevice", params); - DeviceId deviceId = DeviceId::fromUuid(response.toMap().value("params").toMap().value("deviceId").toUuid()); + DeviceId deviceId = DeviceId(response.toMap().value("params").toMap().value("deviceId").toUuid()); QVERIFY2(!deviceId.isNull(), "Something went wrong creating the device for testing."); // Create a rule with this device @@ -3181,7 +3181,7 @@ void TestRules::testHousekeeping() } response = injectAndWait("Rules.AddRule", params); - RuleId ruleId = RuleId::fromUuid(response.toMap().value("params").toMap().value("ruleId").toUuid()); + RuleId ruleId = RuleId(response.toMap().value("params").toMap().value("ruleId").toUuid()); // Verfy that the rule has been created successfully and our device is in there. From e47a4062ee7d977497911c18c61c93d0e59f1757 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Tue, 12 Nov 2019 18:05:14 +0100 Subject: [PATCH 03/28] Fix loading of JS plugins --- .../devices/devicemanagerimplementation.cpp | 45 +++++++++++++------ libnymea-core/devices/scriptdeviceplugin.cpp | 6 +-- libnymea-core/devices/scriptdeviceplugin.h | 1 - 3 files changed, 35 insertions(+), 17 deletions(-) diff --git a/libnymea-core/devices/devicemanagerimplementation.cpp b/libnymea-core/devices/devicemanagerimplementation.cpp index 5a3ba256..2ca37902 100644 --- a/libnymea-core/devices/devicemanagerimplementation.cpp +++ b/libnymea-core/devices/devicemanagerimplementation.cpp @@ -1118,21 +1118,40 @@ void DeviceManagerImplementation::loadPlugins() } } - ScriptDevicePlugin *plugin = new ScriptDevicePlugin(this); - bool ret = plugin->loadScript("/home/micha/Develop/nymea-plugin-jstest/devicepluginjstest.js"); - if (!ret) { - delete plugin; - qCWarning(dcDeviceManager()) << "JS plugin failed to load"; - return; - } - PluginMetadata metaData(plugin->metaData()); - if (!metaData.isValid()) { - qCWarning(dcDeviceManager()) << "Not loading JS plugin. Invalid metadata."; - foreach (const QString &error, metaData.validationErrors()) { - qCWarning(dcDeviceManager()) << error; + foreach (const QString &path, pluginSearchDirs()) { + QDir dir(path); + qCDebug(dcDeviceManager) << "Loading JS plugins from:" << dir.absolutePath(); + foreach (const QString &entry, dir.entryList()) { + QFileInfo jsFi; + QFileInfo jsonFi; + + if (entry.endsWith(".js")) { + jsFi.setFile(path + "/" + entry); + } else { + jsFi.setFile(path + "/" + entry + "/" + entry + ".js"); + } + + if (!jsFi.exists()) { + continue; + } + + ScriptDevicePlugin *plugin = new ScriptDevicePlugin(this); + bool ret = plugin->loadScript("/home/micha/Develop/nymea-plugin-jstest/devicepluginjstest.js"); + if (!ret) { + delete plugin; + qCWarning(dcDeviceManager()) << "JS plugin failed to load"; + continue; + } + PluginMetadata metaData(plugin->metaData()); + if (!metaData.isValid()) { + qCWarning(dcDeviceManager()) << "Not loading JS plugin. Invalid metadata."; + foreach (const QString &error, metaData.validationErrors()) { + qCWarning(dcDeviceManager()) << error; + } + } + loadPlugin(plugin, metaData); } } - loadPlugin(plugin, metaData); } void DeviceManagerImplementation::loadPlugin(DevicePlugin *pluginIface, const PluginMetadata &metaData) diff --git a/libnymea-core/devices/scriptdeviceplugin.cpp b/libnymea-core/devices/scriptdeviceplugin.cpp index a0654e3c..aa6955ec 100644 --- a/libnymea-core/devices/scriptdeviceplugin.cpp +++ b/libnymea-core/devices/scriptdeviceplugin.cpp @@ -61,8 +61,9 @@ bool ScriptDevicePlugin::loadScript(const QString &fileName) m_engine = new QQmlEngine(this); m_engine->installExtensions(QJSEngine::AllExtensions); - m_engine->addImportPath(fi.absoluteDir().path() + "/node_modules/"); - qCWarning(dcDeviceManager()) << "Engine import path list" << m_engine->importPathList(); + + QJSValue deviceMetaObject = m_engine->newQMetaObject(&Device::staticMetaObject); + m_engine->globalObject().setProperty("Device", deviceMetaObject); m_pluginImport = m_engine->importModule(fileName); if (m_pluginImport.isError()) { @@ -70,7 +71,6 @@ bool ScriptDevicePlugin::loadScript(const QString &fileName) return false; } - qCDebug(dcDeviceManager()) << "Loaded JS plugin" << fileName; return true; } diff --git a/libnymea-core/devices/scriptdeviceplugin.h b/libnymea-core/devices/scriptdeviceplugin.h index 9f5194dd..5ded8fb7 100644 --- a/libnymea-core/devices/scriptdeviceplugin.h +++ b/libnymea-core/devices/scriptdeviceplugin.h @@ -152,7 +152,6 @@ public: void postSetupDevice(Device *device) override; void deviceRemoved(Device *device) override; void executeAction(DeviceActionInfo *info) override; - void browseDevice(BrowseResult *result) override; private: QQmlEngine *m_engine = nullptr; From fd401a850aacad6539abe69de7e146c2fd9c7b37 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Tue, 12 Nov 2019 18:11:15 +0100 Subject: [PATCH 04/28] cleanup --- libnymea-core/devices/scriptdeviceplugin.cpp | 7 ------- libnymea-core/jsonrpc/devicehandler.cpp | 2 -- libnymea/typeutils.h | 14 +++----------- 3 files changed, 3 insertions(+), 20 deletions(-) diff --git a/libnymea-core/devices/scriptdeviceplugin.cpp b/libnymea-core/devices/scriptdeviceplugin.cpp index aa6955ec..0e492c8c 100644 --- a/libnymea-core/devices/scriptdeviceplugin.cpp +++ b/libnymea-core/devices/scriptdeviceplugin.cpp @@ -53,12 +53,6 @@ bool ScriptDevicePlugin::loadScript(const QString &fileName) } m_metaData = QJsonObject::fromVariantMap(jsonDoc.toVariant().toMap()); - - QFile scriptFile(fileName); - if (!scriptFile.open(QIODevice::ReadOnly)) { - return false; - } - m_engine = new QQmlEngine(this); m_engine->installExtensions(QJSEngine::AllExtensions); @@ -181,7 +175,6 @@ void ScriptDevicePlugin::setupDevice(DeviceSetupInfo *info) ScriptDeviceSetupInfo *scriptInfo = new ScriptDeviceSetupInfo(info, scriptDevice); - qWarning() << "Setup params" << info->device()->params(); QJSValue jsInfo = m_engine->newQObject(scriptInfo); QJSValue ret = setupFunction.call({jsInfo}); diff --git a/libnymea-core/jsonrpc/devicehandler.cpp b/libnymea-core/jsonrpc/devicehandler.cpp index 1de7c8dc..367b0c88 100644 --- a/libnymea-core/jsonrpc/devicehandler.cpp +++ b/libnymea-core/jsonrpc/devicehandler.cpp @@ -455,8 +455,6 @@ JsonReply *DeviceHandler::GetDiscoveredDevices(const QVariantMap ¶ms) const QVariantList deviceDescriptorList; foreach (const DeviceDescriptor &deviceDescriptor, info->deviceDescriptors()) { deviceDescriptorList.append(pack(deviceDescriptor)); - qWarning() << "*** Discovery result:" << deviceDescriptor.params(); - qWarning() << "Packed:" << pack(deviceDescriptor); } returns.insert("deviceDescriptors", deviceDescriptorList); } diff --git a/libnymea/typeutils.h b/libnymea/typeutils.h index 37690efe..4ad88095 100644 --- a/libnymea/typeutils.h +++ b/libnymea/typeutils.h @@ -29,19 +29,11 @@ #include "libnymea.h" -class GadgetUuid: public QUuid -{ - Q_GADGET -public: - GadgetUuid() {} - GadgetUuid(const QUuid &uuid): QUuid(uuid) {} -}; - -#define DECLARE_TYPE_ID(type) class type##Id: public GadgetUuid \ +#define DECLARE_TYPE_ID(type) class type##Id: public QUuid \ { \ public: \ - type##Id(const QUuid &uuid): GadgetUuid(uuid) {} \ - type##Id(): GadgetUuid() {} \ + type##Id(const QUuid &uuid): QUuid(uuid) {} \ + type##Id(): QUuid() {} \ static type##Id create##type##Id() { return type##Id(QUuid::createUuid()); } \ bool operator==(const type##Id &other) const { \ return toString() == other.toString(); \ From f562503d6df567106045af722ca0f8e0156aec4f Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Tue, 12 Nov 2019 19:29:32 +0100 Subject: [PATCH 05/28] Add missing dpkg dependency --- debian/control | 1 + 1 file changed, 1 insertion(+) diff --git a/debian/control b/debian/control index e10207b4..f76b9ff3 100644 --- a/debian/control +++ b/debian/control @@ -15,6 +15,7 @@ Build-Depends: debhelper (>= 9.0.0), qtbase5-dev, qttools5-dev-tools, qtconnectivity5-dev, + qtdeclarative5-dev, libnymea-remoteproxyclient-dev, libqt5websockets5-dev, libqt5bluetooth5, From 4c0c95b1a80e30f9fe5bd916065d6b76a9dfc76b Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Tue, 12 Nov 2019 22:31:54 +0100 Subject: [PATCH 06/28] Add a ScriptEngine --- libnymea-core/libnymea-core.pro | 2 ++ libnymea-core/nymeacore.cpp | 3 +++ libnymea-core/scriptengine/scriptengine.cpp | 22 +++++++++++++++++ libnymea-core/scriptengine/scriptengine.h | 27 +++++++++++++++++++++ 4 files changed, 54 insertions(+) create mode 100644 libnymea-core/scriptengine/scriptengine.cpp create mode 100644 libnymea-core/scriptengine/scriptengine.h diff --git a/libnymea-core/libnymea-core.pro b/libnymea-core/libnymea-core.pro index 5d4ceceb..b1f07e6e 100644 --- a/libnymea-core/libnymea-core.pro +++ b/libnymea-core/libnymea-core.pro @@ -26,6 +26,7 @@ HEADERS += nymeacore.h \ ruleengine/stateevaluator.h \ ruleengine/ruleaction.h \ ruleengine/ruleactionparam.h \ + scriptengine/scriptengine.h \ transportinterface.h \ nymeaconfiguration.h \ servermanager.h \ @@ -101,6 +102,7 @@ SOURCES += nymeacore.cpp \ ruleengine/stateevaluator.cpp \ ruleengine/ruleaction.cpp \ ruleengine/ruleactionparam.cpp \ + scriptengine/scriptengine.cpp \ transportinterface.cpp \ nymeaconfiguration.cpp \ servermanager.cpp \ diff --git a/libnymea-core/nymeacore.cpp b/libnymea-core/nymeacore.cpp index 6c98bf9c..264b40d1 100644 --- a/libnymea-core/nymeacore.cpp +++ b/libnymea-core/nymeacore.cpp @@ -98,6 +98,7 @@ #include "tagging/tagsstorage.h" #include "platform/platform.h" #include "experiences/experiencemanager.h" +#include "scriptengine/scriptengine.h" #include "devices/devicemanagerimplementation.h" #include "devices/device.h" @@ -160,6 +161,8 @@ void NymeaCore::init() { qCDebug(dcApplication) << "Creating Rule Engine"; m_ruleEngine = new RuleEngine(this); + new ScriptEngine(m_deviceManager, this); + qCDebug(dcApplication()) << "Creating Tags Storage"; m_tagsStorage = new TagsStorage(m_deviceManager, m_ruleEngine, this); diff --git a/libnymea-core/scriptengine/scriptengine.cpp b/libnymea-core/scriptengine/scriptengine.cpp new file mode 100644 index 00000000..5dd19745 --- /dev/null +++ b/libnymea-core/scriptengine/scriptengine.cpp @@ -0,0 +1,22 @@ +#include "scriptengine.h" +#include "devices/devicemanager.h" + +#include + +namespace nymeaserver { + +ScriptEngine::ScriptEngine(DeviceManager *deviceManager, QObject *parent) : QObject(parent), + m_deviceManager(deviceManager) +{ + loadScripts(); +} + +void ScriptEngine::loadScripts() +{ + QString fileName = "/home/micha/Develop/nymea/tests/script.qml"; + QQmlApplicationEngine *engine = new QQmlApplicationEngine(this); + + engine->load(fileName); +} + +} diff --git a/libnymea-core/scriptengine/scriptengine.h b/libnymea-core/scriptengine/scriptengine.h new file mode 100644 index 00000000..a5f72844 --- /dev/null +++ b/libnymea-core/scriptengine/scriptengine.h @@ -0,0 +1,27 @@ +#ifndef SCRIPTENGINE_H +#define SCRIPTENGINE_H + +#include + +class DeviceManager; + +namespace nymeaserver { + +class ScriptEngine : public QObject +{ + Q_OBJECT +public: + explicit ScriptEngine(DeviceManager *deviceManager, QObject *parent = nullptr); + +signals: + +private: + void loadScripts(); + +private: + DeviceManager *m_deviceManager = nullptr; +}; + +} + +#endif // SCRIPTENGINE_H From 0f75be0fa4c2bbf9c37c554cdd3145e1a5fd14a7 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Tue, 12 Nov 2019 23:52:24 +0100 Subject: [PATCH 07/28] Require Qt 5.12 for ScriptDevicePlugin --- libnymea-core/devices/devicemanagerimplementation.cpp | 4 ++++ libnymea-core/libnymea-core.pro | 10 ++++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/libnymea-core/devices/devicemanagerimplementation.cpp b/libnymea-core/devices/devicemanagerimplementation.cpp index 2ca37902..f7f9f8d8 100644 --- a/libnymea-core/devices/devicemanagerimplementation.cpp +++ b/libnymea-core/devices/devicemanagerimplementation.cpp @@ -23,7 +23,9 @@ #include "devicemanagerimplementation.h" #include "translator.h" +#if QT_VERSION >= QT_VERSION_CHECK(5,12,0) #include "scriptdeviceplugin.h" +#endif #include "loggingcategories.h" #include "typeutils.h" @@ -1118,6 +1120,7 @@ void DeviceManagerImplementation::loadPlugins() } } +#if QT_VERSION >= QT_VERSION_CHECK(5,12,0) foreach (const QString &path, pluginSearchDirs()) { QDir dir(path); qCDebug(dcDeviceManager) << "Loading JS plugins from:" << dir.absolutePath(); @@ -1152,6 +1155,7 @@ void DeviceManagerImplementation::loadPlugins() loadPlugin(plugin, metaData); } } +#endif } void DeviceManagerImplementation::loadPlugin(DevicePlugin *pluginIface, const PluginMetadata &metaData) diff --git a/libnymea-core/libnymea-core.pro b/libnymea-core/libnymea-core.pro index b1f07e6e..f915cba1 100644 --- a/libnymea-core/libnymea-core.pro +++ b/libnymea-core/libnymea-core.pro @@ -17,7 +17,6 @@ RESOURCES += $$top_srcdir/icons.qrc \ HEADERS += nymeacore.h \ devices/devicemanagerimplementation.h \ - devices/scriptdeviceplugin.h \ devices/translator.h \ experiences/experiencemanager.h \ jsonrpc/jsonrpcserverimplementation.h \ @@ -93,7 +92,6 @@ HEADERS += nymeacore.h \ SOURCES += nymeacore.cpp \ devices/devicemanagerimplementation.cpp \ - devices/scriptdeviceplugin.cpp \ devices/translator.cpp \ experiences/experiencemanager.cpp \ jsonrpc/jsonrpcserverimplementation.cpp \ @@ -164,3 +162,11 @@ SOURCES += nymeacore.cpp \ debugreportgenerator.cpp \ platform/platform.cpp \ jsonrpc/systemhandler.cpp + +versionAtLeast(QT_VERSION, 5.12.0) { +HEADERS += \ + devices/scriptdeviceplugin.h \ + +SOURCES += \ + devices/scriptdeviceplugin.cpp \ +} From d4081195d4de7b3318c5dc4849ce527642966802 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Fri, 15 Nov 2019 10:59:37 +0100 Subject: [PATCH 08/28] Some more work on the script engine --- libnymea-core/libnymea-core.pro | 6 + libnymea-core/nymeacore.cpp | 1 + libnymea-core/scriptengine/scriptaction.cpp | 64 +++++++++++ libnymea-core/scriptengine/scriptaction.h | 42 +++++++ libnymea-core/scriptengine/scriptengine.cpp | 10 ++ libnymea-core/scriptengine/scriptengine.h | 4 +- libnymea-core/scriptengine/scriptevent.cpp | 78 +++++++++++++ libnymea-core/scriptengine/scriptevent.h | 54 +++++++++ libnymea-core/scriptengine/scriptstate.cpp | 116 ++++++++++++++++++++ libnymea-core/scriptengine/scriptstate.h | 63 +++++++++++ libnymea/loggingcategories.cpp | 1 + libnymea/loggingcategories.h | 1 + tests/scripts/getconfigureddevices.sh | 4 +- tests/scripts/getstatetypes.sh | 8 +- 14 files changed, 447 insertions(+), 5 deletions(-) create mode 100644 libnymea-core/scriptengine/scriptaction.cpp create mode 100644 libnymea-core/scriptengine/scriptaction.h create mode 100644 libnymea-core/scriptengine/scriptevent.cpp create mode 100644 libnymea-core/scriptengine/scriptevent.h create mode 100644 libnymea-core/scriptengine/scriptstate.cpp create mode 100644 libnymea-core/scriptengine/scriptstate.h diff --git a/libnymea-core/libnymea-core.pro b/libnymea-core/libnymea-core.pro index f915cba1..c28efe27 100644 --- a/libnymea-core/libnymea-core.pro +++ b/libnymea-core/libnymea-core.pro @@ -25,7 +25,10 @@ HEADERS += nymeacore.h \ ruleengine/stateevaluator.h \ ruleengine/ruleaction.h \ ruleengine/ruleactionparam.h \ + scriptengine/scriptaction.h \ scriptengine/scriptengine.h \ + scriptengine/scriptevent.h \ + scriptengine/scriptstate.h \ transportinterface.h \ nymeaconfiguration.h \ servermanager.h \ @@ -100,7 +103,10 @@ SOURCES += nymeacore.cpp \ ruleengine/stateevaluator.cpp \ ruleengine/ruleaction.cpp \ ruleengine/ruleactionparam.cpp \ + scriptengine/scriptaction.cpp \ scriptengine/scriptengine.cpp \ + scriptengine/scriptevent.cpp \ + scriptengine/scriptstate.cpp \ transportinterface.cpp \ nymeaconfiguration.cpp \ servermanager.cpp \ diff --git a/libnymea-core/nymeacore.cpp b/libnymea-core/nymeacore.cpp index 264b40d1..bf289dc4 100644 --- a/libnymea-core/nymeacore.cpp +++ b/libnymea-core/nymeacore.cpp @@ -663,6 +663,7 @@ QStringList NymeaCore::loggingFilters() "DeviceManager", "RuleEngine", "RuleEngineDebug", + "ScriptEngine", "Hardware", "Bluetooth", "LogEngine", diff --git a/libnymea-core/scriptengine/scriptaction.cpp b/libnymea-core/scriptengine/scriptaction.cpp new file mode 100644 index 00000000..da88df82 --- /dev/null +++ b/libnymea-core/scriptengine/scriptaction.cpp @@ -0,0 +1,64 @@ +#include "scriptaction.h" + +#include "devices/devicemanager.h" +#include "types/action.h" + +#include + +namespace nymeaserver { + +ScriptAction::ScriptAction(QObject *parent) : QObject(parent) +{ + +} + +void ScriptAction::classBegin() +{ + m_deviceManager = reinterpret_cast(qmlEngine(this)->property("deviceManager").toULongLong()); +} + +void ScriptAction::componentComplete() +{ + +} + +QString ScriptAction::deviceId() const +{ + return m_deviceId; +} + +void ScriptAction::setDeviceId(const QString &deviceId) +{ + if (m_deviceId != deviceId) { + m_deviceId = deviceId; + emit deviceIdChanged(); + } +} + +QString ScriptAction::actionTypeId() const +{ + return m_actionTypeId; +} + +void ScriptAction::setActionTypeId(const QString &actionTypeId) +{ + if (m_actionTypeId != actionTypeId) { + m_actionTypeId = actionTypeId; + emit actionTypeIdChanged(); + } +} + +void ScriptAction::execute(const QVariantList ¶ms) +{ + Action action; + action.setActionTypeId(ActionTypeId(m_actionTypeId)); + action.setDeviceId(DeviceId(m_deviceId)); + ParamList paramList; + foreach (const QVariant &p, params) { + paramList << Param(ParamTypeId(p.toMap().value("paramTypeId").toUuid()), p.toMap().value("value")); + } + action.setParams(paramList); + m_deviceManager->executeAction(action); +} + +} diff --git a/libnymea-core/scriptengine/scriptaction.h b/libnymea-core/scriptengine/scriptaction.h new file mode 100644 index 00000000..98baab7e --- /dev/null +++ b/libnymea-core/scriptengine/scriptaction.h @@ -0,0 +1,42 @@ +#ifndef SCRIPTACTION_H +#define SCRIPTACTION_H + +#include +#include + +class DeviceManager; + +namespace nymeaserver { + +class ScriptAction : public QObject, public QQmlParserStatus +{ + Q_OBJECT + Q_PROPERTY(QString deviceId READ deviceId WRITE setDeviceId NOTIFY deviceIdChanged) + Q_PROPERTY(QString actionTypeId READ actionTypeId WRITE setActionTypeId NOTIFY actionTypeIdChanged) +public: + explicit ScriptAction(QObject *parent = nullptr); + void classBegin() override; + void componentComplete() override; + + QString deviceId() const; + void setDeviceId(const QString &deviceId); + + QString actionTypeId() const; + void setActionTypeId(const QString &actionTypeId); + +public slots: + void execute(const QVariantList ¶ms); + +signals: + void deviceIdChanged(); + void actionTypeIdChanged(); + +public: + DeviceManager *m_deviceManager = nullptr; + QString m_deviceId; + QString m_actionTypeId; +}; + +} + +#endif // SCRIPTACTION_H diff --git a/libnymea-core/scriptengine/scriptengine.cpp b/libnymea-core/scriptengine/scriptengine.cpp index 5dd19745..4f994a98 100644 --- a/libnymea-core/scriptengine/scriptengine.cpp +++ b/libnymea-core/scriptengine/scriptengine.cpp @@ -1,6 +1,10 @@ #include "scriptengine.h" #include "devices/devicemanager.h" +#include "scriptaction.h" +#include "scriptevent.h" +#include "scriptstate.h" + #include namespace nymeaserver { @@ -8,13 +12,19 @@ namespace nymeaserver { ScriptEngine::ScriptEngine(DeviceManager *deviceManager, QObject *parent) : QObject(parent), m_deviceManager(deviceManager) { + qmlRegisterType("nymea", 1, 0, "Event"); + qmlRegisterType("nymea", 1, 0, "Action"); + qmlRegisterType("nymea", 1, 0, "State"); + loadScripts(); } void ScriptEngine::loadScripts() { QString fileName = "/home/micha/Develop/nymea/tests/script.qml"; + QQmlApplicationEngine *engine = new QQmlApplicationEngine(this); + engine->setProperty("deviceManager", reinterpret_cast(m_deviceManager)); engine->load(fileName); } diff --git a/libnymea-core/scriptengine/scriptengine.h b/libnymea-core/scriptengine/scriptengine.h index a5f72844..806eb4aa 100644 --- a/libnymea-core/scriptengine/scriptengine.h +++ b/libnymea-core/scriptengine/scriptengine.h @@ -2,8 +2,10 @@ #define SCRIPTENGINE_H #include +#include +#include -class DeviceManager; +#include "devices/devicemanager.h" namespace nymeaserver { diff --git a/libnymea-core/scriptengine/scriptevent.cpp b/libnymea-core/scriptengine/scriptevent.cpp new file mode 100644 index 00000000..cb9a8cb0 --- /dev/null +++ b/libnymea-core/scriptengine/scriptevent.cpp @@ -0,0 +1,78 @@ +#include "scriptevent.h" + +namespace nymeaserver { + +ScriptEvent::ScriptEvent(QObject *parent) : QObject(parent) +{ +} + +void ScriptEvent::classBegin() +{ + m_deviceManager = reinterpret_cast(qmlEngine(this)->property("deviceManager").toULongLong()); + connect(m_deviceManager, &DeviceManager::eventTriggered, this, &ScriptEvent::onEventTriggered); +} + +void ScriptEvent::componentComplete() +{ + +} + +QString ScriptEvent::deviceId() const +{ + return m_deviceId; +} + +void ScriptEvent::setDeviceId(const QString &deviceId) +{ + if (m_deviceId != deviceId) { + m_deviceId = deviceId; + emit deviceIdChanged(); + } +} + +QString ScriptEvent::eventTypeId() const +{ + return m_eventTypeId; +} + +void ScriptEvent::setEventTypeId(const QString &eventTypeId) +{ + if (m_eventTypeId != eventTypeId) { + m_eventTypeId = eventTypeId; + emit eventTypeIdChanged(); + } +} + +QString ScriptEvent::eventName() const +{ + return m_eventName; +} + +void ScriptEvent::setEventName(const QString &eventName) +{ + if (m_eventName != eventName) { + m_eventName = eventName; + emit eventNameChanged(); + } +} + +void ScriptEvent::onEventTriggered(const Event &event) +{ + if (QUuid(m_deviceId) != event.deviceId()) { + return; + } + + if (!m_eventTypeId.isEmpty() && event.eventTypeId() != m_eventTypeId) { + return; + } + + Device *device = m_deviceManager->findConfiguredDevice(event.deviceId()); + if (!m_eventName.isEmpty() && device->deviceClass().eventTypes().findByName(m_eventName).id() != event.eventTypeId()) { + return; + } + + emit triggered(); +} + +} + diff --git a/libnymea-core/scriptengine/scriptevent.h b/libnymea-core/scriptengine/scriptevent.h new file mode 100644 index 00000000..92266c9c --- /dev/null +++ b/libnymea-core/scriptengine/scriptevent.h @@ -0,0 +1,54 @@ +#ifndef EVENTLISTENER_H +#define EVENTLISTENER_H + +#include +#include +#include + +#include "types/event.h" +#include "devices/devicemanager.h" + +namespace nymeaserver { + +class ScriptEvent: public QObject, public QQmlParserStatus +{ + Q_OBJECT + Q_INTERFACES(QQmlParserStatus) + Q_PROPERTY(QString deviceId READ deviceId WRITE setDeviceId NOTIFY deviceIdChanged) + Q_PROPERTY(QString eventTypeId READ eventTypeId WRITE setEventTypeId NOTIFY eventTypeIdChanged) + Q_PROPERTY(QString eventName READ eventName WRITE setEventName NOTIFY eventNameChanged) +public: + ScriptEvent(QObject *parent = nullptr); + void classBegin() override; + void componentComplete() override; + + QString deviceId() const; + void setDeviceId(const QString &deviceId); + + QString eventTypeId() const; + void setEventTypeId(const QString &eventTypeId); + + QString eventName() const; + void setEventName(const QString &eventName); + +private slots: + void onEventTriggered(const Event &event); + +signals: + void deviceIdChanged(); + void eventTypeIdChanged(); + void eventNameChanged(); + + void triggered(); + +private: + DeviceManager *m_deviceManager = nullptr; + + QString m_deviceId; + QString m_eventTypeId; + QString m_eventName; +}; + +} + +#endif // EVENTLISTENER_H diff --git a/libnymea-core/scriptengine/scriptstate.cpp b/libnymea-core/scriptengine/scriptstate.cpp new file mode 100644 index 00000000..b161ffd8 --- /dev/null +++ b/libnymea-core/scriptengine/scriptstate.cpp @@ -0,0 +1,116 @@ +#include "scriptstate.h" + +#include "loggingcategories.h" + +#include + +namespace nymeaserver { + +ScriptState::ScriptState(QObject *parent) : QObject(parent) +{ + +} + +void ScriptState::classBegin() +{ + m_deviceManager = reinterpret_cast(qmlEngine(this)->property("deviceManager").toULongLong()); + connect(m_deviceManager, &DeviceManager::deviceStateChanged, this, &ScriptState::onDeviceStateChanged); +} + +void ScriptState::componentComplete() +{ + +} + +QString ScriptState::deviceId() const +{ + return m_deviceId; +} + +void ScriptState::setDeviceId(const QString &deviceId) +{ + if (m_deviceId != deviceId) { + m_deviceId = deviceId; + emit deviceIdChanged(); + store(); + } +} + +QString ScriptState::stateTypeId() const +{ + return m_stateTypeId; +} + +void ScriptState::setStateTypeId(const QString &stateTypeId) +{ + if (m_stateTypeId != stateTypeId) { + m_stateTypeId = stateTypeId; + emit stateTypeIdChanged(); + store(); + } +} + +QVariant ScriptState::value() const +{ + Device* device = m_deviceManager->findConfiguredDevice(DeviceId(m_deviceId)); + if (!device) { + return QVariant(); + } + return device->stateValue(StateTypeId(m_stateTypeId)); +} + +void ScriptState::setValue(const QVariant &value) +{ + qCDebug(dcScriptEngine()) << "setValueCalled1" << value; + if (m_pendingActionInfo) { + m_valueCache = value; + return; + } + + Device* device = m_deviceManager->findConfiguredDevice(DeviceId(m_deviceId)); + if (!device) { + qCWarning(dcScriptEngine()) << "No device with id" << m_deviceId << "found."; + return; + } + + if (device->deviceClass().stateTypes().findById(StateTypeId(m_stateTypeId)).id().isNull()) { + qCWarning(dcScriptEngine) << "Device" << device->name() << "does not have a state with type id" << m_stateTypeId; + return; + } + Action action; + action.setDeviceId(DeviceId(m_deviceId)); + action.setActionTypeId(ActionTypeId(m_stateTypeId)); + ParamList params = ParamList() << Param(ParamTypeId(m_stateTypeId), value); + action.setParams(params); + + qCDebug(dcScriptEngine()) << "setValueCalled2" << value; + m_valueCache = QVariant(); + m_pendingActionInfo = m_deviceManager->executeAction(action); + connect(m_pendingActionInfo, &DeviceActionInfo::finished, this, [this](){ + m_pendingActionInfo = nullptr; + if (!m_valueCache.isNull()) { + setValue(m_valueCache); + } + }); +} + +void ScriptState::store() +{ + m_valueStore = value(); + qCDebug(dcScriptEngine()) << "Storing value:" << m_valueStore; +} + +void ScriptState::restore() +{ + qCDebug(dcScriptEngine()) << "Restoring value:" << m_valueStore << m_valueStore.value().toRgb(); + setValue(m_valueStore); +} + +void nymeaserver::ScriptState::onDeviceStateChanged(Device *device, const StateTypeId &stateTypeId) +{ + if (device->id() == DeviceId(m_deviceId) && stateTypeId == StateTypeId(m_stateTypeId)) { + emit valueChanged(); + } +} + +} diff --git a/libnymea-core/scriptengine/scriptstate.h b/libnymea-core/scriptengine/scriptstate.h new file mode 100644 index 00000000..30bd28a6 --- /dev/null +++ b/libnymea-core/scriptengine/scriptstate.h @@ -0,0 +1,63 @@ +#ifndef SCRIPTSTATE_H +#define SCRIPTSTATE_H + +#include +#include +#include + +#include "devices/devicemanager.h" +#include "devices/deviceactioninfo.h" + +namespace nymeaserver { + +class ScriptState : public QObject, public QQmlParserStatus +{ + Q_OBJECT + Q_INTERFACES(QQmlParserStatus) + Q_PROPERTY(QString deviceId READ deviceId WRITE setDeviceId NOTIFY deviceIdChanged) + Q_PROPERTY(QString stateTypeId READ stateTypeId WRITE setStateTypeId NOTIFY stateTypeIdChanged) + Q_PROPERTY(QString stateName READ stateName WRITE setStateName NOTIFY stateNameChanged) + Q_PROPERTY(QVariant value READ value WRITE setValue NOTIFY valueChanged) + +public: + explicit ScriptState(QObject *parent = nullptr); + void classBegin() override; + void componentComplete() override; + + QString deviceId() const; + void setDeviceId(const QString &deviceId); + + QString stateTypeId() const; + void setStateTypeId(const QString &stateTypeId); + + QVariant value() const; + void setValue(const QVariant &value); + +public slots: + void store(); + void restore(); + +signals: + void deviceIdChanged(); + void stateTypeIdChanged(); + void stateNameChanged(); + void valueChanged(); + +private slots: + void onDeviceStateChanged(Device *device, const StateTypeId &stateTypeId); + +private: + DeviceManager *m_deviceManager = nullptr; + + QString m_deviceId; + QString m_stateTypeId; + + DeviceActionInfo *m_pendingActionInfo = nullptr; + QVariant m_valueCache; + + QVariant m_valueStore; +}; + +} + +#endif // SCRIPTSTATE_H diff --git a/libnymea/loggingcategories.cpp b/libnymea/loggingcategories.cpp index 4b8cc66e..e34f511f 100644 --- a/libnymea/loggingcategories.cpp +++ b/libnymea/loggingcategories.cpp @@ -34,6 +34,7 @@ Q_LOGGING_CATEGORY(dcExperiences, "Experiences") Q_LOGGING_CATEGORY(dcTimeManager, "TimeManager") Q_LOGGING_CATEGORY(dcRuleEngine, "RuleEngine") Q_LOGGING_CATEGORY(dcRuleEngineDebug, "RuleEngineDebug") +Q_LOGGING_CATEGORY(dcScriptEngine, "ScriptEngine") Q_LOGGING_CATEGORY(dcHardware, "Hardware") Q_LOGGING_CATEGORY(dcLogEngine, "LogEngine") Q_LOGGING_CATEGORY(dcServerManager, "ServerManager") diff --git a/libnymea/loggingcategories.h b/libnymea/loggingcategories.h index fec359bc..a06ed85f 100644 --- a/libnymea/loggingcategories.h +++ b/libnymea/loggingcategories.h @@ -39,6 +39,7 @@ 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) diff --git a/tests/scripts/getconfigureddevices.sh b/tests/scripts/getconfigureddevices.sh index 1d6a39dd..cbd8d221 100755 --- a/tests/scripts/getconfigureddevices.sh +++ b/tests/scripts/getconfigureddevices.sh @@ -6,14 +6,14 @@ if [ -z $1 ]; then fi if [ -z $2 ]; then -cat < Date: Mon, 18 Nov 2019 21:50:58 +0100 Subject: [PATCH 09/28] tune script types a bit --- libnymea-core/scriptengine/scriptaction.cpp | 30 +++++++- libnymea-core/scriptengine/scriptaction.h | 6 ++ libnymea-core/scriptengine/scriptengine.cpp | 3 + libnymea-core/scriptengine/scriptevent.cpp | 2 +- libnymea-core/scriptengine/scriptstate.cpp | 85 +++++++++++++++++++-- libnymea-core/scriptengine/scriptstate.h | 16 +++- 6 files changed, 128 insertions(+), 14 deletions(-) diff --git a/libnymea-core/scriptengine/scriptaction.cpp b/libnymea-core/scriptengine/scriptaction.cpp index da88df82..7c65d081 100644 --- a/libnymea-core/scriptengine/scriptaction.cpp +++ b/libnymea-core/scriptengine/scriptaction.cpp @@ -5,6 +5,8 @@ #include +#include "loggingcategories.h" + namespace nymeaserver { ScriptAction::ScriptAction(QObject *parent) : QObject(parent) @@ -48,10 +50,36 @@ void ScriptAction::setActionTypeId(const QString &actionTypeId) } } +QString ScriptAction::actionName() const +{ + return m_actionName; +} + +void ScriptAction::setActionName(const QString &actionName) +{ + if (m_actionName != actionName) { + m_actionName = actionName; + emit actionNameChanged(); + } +} + void ScriptAction::execute(const QVariantList ¶ms) { + Device *device = m_deviceManager->configuredDevices().findById(DeviceId(m_deviceId)); + if (!device) { + qCWarning(dcScriptEngine) << "No device with id" << m_deviceId; + return; + } + ActionTypeId actionTypeId = ActionTypeId(m_actionTypeId); + if (actionTypeId.isNull()) { + actionTypeId = device->deviceClass().actionTypes().findByName(m_actionName).id(); + } + if (actionTypeId.isNull()) { + qCWarning(dcScriptEngine()) << "Either a valid actionTypeId or actionName is required"; + return; + } Action action; - action.setActionTypeId(ActionTypeId(m_actionTypeId)); + action.setActionTypeId(actionTypeId); action.setDeviceId(DeviceId(m_deviceId)); ParamList paramList; foreach (const QVariant &p, params) { diff --git a/libnymea-core/scriptengine/scriptaction.h b/libnymea-core/scriptengine/scriptaction.h index 98baab7e..36a455fe 100644 --- a/libnymea-core/scriptengine/scriptaction.h +++ b/libnymea-core/scriptengine/scriptaction.h @@ -13,6 +13,7 @@ class ScriptAction : public QObject, public QQmlParserStatus Q_OBJECT Q_PROPERTY(QString deviceId READ deviceId WRITE setDeviceId NOTIFY deviceIdChanged) Q_PROPERTY(QString actionTypeId READ actionTypeId WRITE setActionTypeId NOTIFY actionTypeIdChanged) + Q_PROPERTY(QString actionName READ actionName WRITE setActionName NOTIFY actionNameChanged) public: explicit ScriptAction(QObject *parent = nullptr); void classBegin() override; @@ -24,17 +25,22 @@ public: QString actionTypeId() const; void setActionTypeId(const QString &actionTypeId); + QString actionName() const; + void setActionName(const QString &actionName); + public slots: void execute(const QVariantList ¶ms); signals: void deviceIdChanged(); void actionTypeIdChanged(); + void actionNameChanged(); public: DeviceManager *m_deviceManager = nullptr; QString m_deviceId; QString m_actionTypeId; + QString m_actionName; }; } diff --git a/libnymea-core/scriptengine/scriptengine.cpp b/libnymea-core/scriptengine/scriptengine.cpp index 4f994a98..dbe1ddc7 100644 --- a/libnymea-core/scriptengine/scriptengine.cpp +++ b/libnymea-core/scriptengine/scriptengine.cpp @@ -7,6 +7,8 @@ #include +#include "loggingcategories.h" + namespace nymeaserver { ScriptEngine::ScriptEngine(DeviceManager *deviceManager, QObject *parent) : QObject(parent), @@ -26,6 +28,7 @@ void ScriptEngine::loadScripts() QQmlApplicationEngine *engine = new QQmlApplicationEngine(this); engine->setProperty("deviceManager", reinterpret_cast(m_deviceManager)); + qCWarning(dcScriptEngine()) << "Loading script"; engine->load(fileName); } diff --git a/libnymea-core/scriptengine/scriptevent.cpp b/libnymea-core/scriptengine/scriptevent.cpp index cb9a8cb0..5665197c 100644 --- a/libnymea-core/scriptengine/scriptevent.cpp +++ b/libnymea-core/scriptengine/scriptevent.cpp @@ -58,7 +58,7 @@ void ScriptEvent::setEventName(const QString &eventName) void ScriptEvent::onEventTriggered(const Event &event) { - if (QUuid(m_deviceId) != event.deviceId()) { + if (DeviceId(m_deviceId) != event.deviceId()) { return; } diff --git a/libnymea-core/scriptengine/scriptstate.cpp b/libnymea-core/scriptengine/scriptstate.cpp index b161ffd8..dbf1e5f8 100644 --- a/libnymea-core/scriptengine/scriptstate.cpp +++ b/libnymea-core/scriptengine/scriptstate.cpp @@ -45,7 +45,21 @@ void ScriptState::setStateTypeId(const QString &stateTypeId) { if (m_stateTypeId != stateTypeId) { m_stateTypeId = stateTypeId; - emit stateTypeIdChanged(); + emit stateTypeChanged(); + store(); + } +} + +QString ScriptState::stateName() const +{ + return m_stateName; +} + +void ScriptState::setStateName(const QString &stateName) +{ + if (m_stateName != stateName) { + m_stateName = stateName; + emit stateTypeChanged(); store(); } } @@ -56,7 +70,12 @@ QVariant ScriptState::value() const if (!device) { return QVariant(); } - return device->stateValue(StateTypeId(m_stateTypeId)); + StateTypeId stateTypeId = StateTypeId(m_stateTypeId); + if (stateTypeId.isNull()) { + stateTypeId = device->deviceClass().stateTypes().findByName(m_stateName).id(); + } + + return device->stateValue(stateTypeId); } void ScriptState::setValue(const QVariant &value) @@ -73,17 +92,31 @@ void ScriptState::setValue(const QVariant &value) return; } - if (device->deviceClass().stateTypes().findById(StateTypeId(m_stateTypeId)).id().isNull()) { - qCWarning(dcScriptEngine) << "Device" << device->name() << "does not have a state with type id" << m_stateTypeId; + ActionTypeId actionTypeId; + if (!m_stateTypeId.isNull()) { + actionTypeId = device->deviceClass().stateTypes().findById(StateTypeId(m_stateTypeId)).id(); + if (actionTypeId.isNull()) { + qCWarning(dcScriptEngine) << "Device" << device->name() << "does not have a state with type id" << m_stateTypeId; + } + } + if (actionTypeId.isNull()) { + actionTypeId = device->deviceClass().stateTypes().findByName(stateName()).id(); + if (actionTypeId.isNull()) { + qCWarning(dcScriptEngine) << "Device" << device->name() << "does not have a state named" << m_stateName; + } + } + + if (actionTypeId.isNull()) { + qCWarning(dcScriptEngine()) << "Either stateTypeId or stateName is required to be valid."; return; } + Action action; action.setDeviceId(DeviceId(m_deviceId)); - action.setActionTypeId(ActionTypeId(m_stateTypeId)); - ParamList params = ParamList() << Param(ParamTypeId(m_stateTypeId), value); + action.setActionTypeId(ActionTypeId(actionTypeId)); + ParamList params = ParamList() << Param(ParamTypeId(actionTypeId), value); action.setParams(params); - qCDebug(dcScriptEngine()) << "setValueCalled2" << value; m_valueCache = QVariant(); m_pendingActionInfo = m_deviceManager->executeAction(action); connect(m_pendingActionInfo, &DeviceActionInfo::finished, this, [this](){ @@ -94,6 +127,32 @@ void ScriptState::setValue(const QVariant &value) }); } +QVariant ScriptState::minimumValue() const +{ + Device *device = m_deviceManager->configuredDevices().findById(DeviceId(m_deviceId)); + if (!device) { + return QVariant(); + } + StateType stateType = device->deviceClass().stateTypes().findById(StateTypeId(m_stateTypeId)); + if (stateType.id().isNull()) { + stateType = device->deviceClass().stateTypes().findByName(m_stateName); + } + return stateType.minValue(); +} + +QVariant ScriptState::maximumValue() const +{ + Device *device = m_deviceManager->configuredDevices().findById(DeviceId(m_deviceId)); + if (!device) { + return QVariant(); + } + StateType stateType = device->deviceClass().stateTypes().findById(StateTypeId(m_stateTypeId)); + if (stateType.id().isNull()) { + stateType = device->deviceClass().stateTypes().findByName(m_stateName); + } + return stateType.minValue(); +} + void ScriptState::store() { m_valueStore = value(); @@ -108,7 +167,17 @@ void ScriptState::restore() void nymeaserver::ScriptState::onDeviceStateChanged(Device *device, const StateTypeId &stateTypeId) { - if (device->id() == DeviceId(m_deviceId) && stateTypeId == StateTypeId(m_stateTypeId)) { + if (device->id() != DeviceId(m_deviceId)) { + return; + } + StateTypeId localStateTypeId = StateTypeId(m_stateTypeId); + if (localStateTypeId.isNull()) { + localStateTypeId = device->deviceClass().stateTypes().findByName(m_stateName).id(); + } + if (localStateTypeId.isNull()) { + return; + } + if (stateTypeId == localStateTypeId) { emit valueChanged(); } } diff --git a/libnymea-core/scriptengine/scriptstate.h b/libnymea-core/scriptengine/scriptstate.h index 30bd28a6..9ba75d9d 100644 --- a/libnymea-core/scriptengine/scriptstate.h +++ b/libnymea-core/scriptengine/scriptstate.h @@ -15,9 +15,11 @@ class ScriptState : public QObject, public QQmlParserStatus Q_OBJECT Q_INTERFACES(QQmlParserStatus) Q_PROPERTY(QString deviceId READ deviceId WRITE setDeviceId NOTIFY deviceIdChanged) - Q_PROPERTY(QString stateTypeId READ stateTypeId WRITE setStateTypeId NOTIFY stateTypeIdChanged) - Q_PROPERTY(QString stateName READ stateName WRITE setStateName NOTIFY stateNameChanged) + Q_PROPERTY(QString stateTypeId READ stateTypeId WRITE setStateTypeId NOTIFY stateTypeChanged) + Q_PROPERTY(QString stateName READ stateName WRITE setStateName NOTIFY stateTypeChanged) Q_PROPERTY(QVariant value READ value WRITE setValue NOTIFY valueChanged) + Q_PROPERTY(QVariant minimumValue READ minimumValue NOTIFY stateTypeChanged) + Q_PROPERTY(QVariant maximumValue READ maximumValue NOTIFY stateTypeChanged) public: explicit ScriptState(QObject *parent = nullptr); @@ -30,17 +32,22 @@ public: QString stateTypeId() const; void setStateTypeId(const QString &stateTypeId); + QString stateName() const; + void setStateName(const QString &stateName); + QVariant value() const; void setValue(const QVariant &value); + QVariant minimumValue() const; + QVariant maximumValue() const; + public slots: void store(); void restore(); signals: void deviceIdChanged(); - void stateTypeIdChanged(); - void stateNameChanged(); + void stateTypeChanged(); void valueChanged(); private slots: @@ -51,6 +58,7 @@ private: QString m_deviceId; QString m_stateTypeId; + QString m_stateName; DeviceActionInfo *m_pendingActionInfo = nullptr; QVariant m_valueCache; From 3a9a0a0abc2c7b1f40300fdcd6c4b422cfb848a4 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Tue, 19 Nov 2019 20:03:48 +0100 Subject: [PATCH 10/28] Add scripts api namespace --- .../jsonrpc/jsonrpcserverimplementation.cpp | 1 + .../jsonrpc/jsonrpcserverimplementation.h | 2 +- libnymea-core/jsonrpc/scriptshandler.cpp | 106 +++++++++ libnymea-core/jsonrpc/scriptshandler.h | 31 +++ libnymea-core/libnymea-core.pro | 4 + libnymea-core/nymeacore.cpp | 6 +- libnymea-core/nymeacore.h | 2 + libnymea-core/scriptengine/script.cpp | 51 +++++ libnymea-core/scriptengine/script.h | 53 +++++ libnymea-core/scriptengine/scriptengine.cpp | 206 +++++++++++++++++- libnymea-core/scriptengine/scriptengine.h | 32 ++- libnymea/jsonrpc/jsonrpcserver.h | 1 + 12 files changed, 488 insertions(+), 7 deletions(-) create mode 100644 libnymea-core/jsonrpc/scriptshandler.cpp create mode 100644 libnymea-core/jsonrpc/scriptshandler.h create mode 100644 libnymea-core/scriptengine/script.cpp create mode 100644 libnymea-core/scriptengine/script.h diff --git a/libnymea-core/jsonrpc/jsonrpcserverimplementation.cpp b/libnymea-core/jsonrpc/jsonrpcserverimplementation.cpp index 8aded8dc..47f5bcb9 100644 --- a/libnymea-core/jsonrpc/jsonrpcserverimplementation.cpp +++ b/libnymea-core/jsonrpc/jsonrpcserverimplementation.cpp @@ -52,6 +52,7 @@ #include "devicehandler.h" #include "actionhandler.h" #include "ruleshandler.h" +#include "scriptshandler.h" #include "eventhandler.h" #include "logginghandler.h" #include "statehandler.h" diff --git a/libnymea-core/jsonrpc/jsonrpcserverimplementation.h b/libnymea-core/jsonrpc/jsonrpcserverimplementation.h index 097066e7..d8bcf3c4 100644 --- a/libnymea-core/jsonrpc/jsonrpcserverimplementation.h +++ b/libnymea-core/jsonrpc/jsonrpcserverimplementation.h @@ -72,10 +72,10 @@ public: void registerTransportInterface(TransportInterface *interface, bool authenticationRequired); void unregisterTransportInterface(TransportInterface *interface); + bool registerHandler(JsonHandler *handler) override; bool registerExperienceHandler(JsonHandler *handler, int majorVersion, int minorVersion) override; private: - bool registerHandler(JsonHandler *handler); QHash handlers() const; void sendResponse(TransportInterface *interface, const QUuid &clientId, int commandId, const QVariantMap ¶ms = QVariantMap(), const QString &deprecationWarning = QString()); diff --git a/libnymea-core/jsonrpc/scriptshandler.cpp b/libnymea-core/jsonrpc/scriptshandler.cpp new file mode 100644 index 00000000..17f03d25 --- /dev/null +++ b/libnymea-core/jsonrpc/scriptshandler.cpp @@ -0,0 +1,106 @@ +#include "scriptshandler.h" + +#include "loggingcategories.h" + +#include "scriptengine/scriptengine.h" + +namespace nymeaserver { + +ScriptsHandler::ScriptsHandler(ScriptEngine *scriptEngine, QObject *parent): + JsonHandler(parent), + m_engine(scriptEngine) +{ + registerEnum(); + + registerObject(); + + QVariantMap params, returns; + QString description; + + params.clear(); returns.clear(); + description = "Get all script, that is, their names and properties, but no content."; + returns.insert("scripts", objectRef()); + registerMethod("GetScripts", description, params, returns); + + params.clear(); returns.clear(); + description = "Add a script"; + params.insert("name", enumValueName(String)); + params.insert("content", enumValueName(String)); + returns.insert("scriptError", enumRef()); + returns.insert("o:script", objectRef