diff --git a/debian/changelog b/debian/changelog index 3d9d6ef1..cad447e4 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,7 @@ +nymea (0.18.0) UNRELEASED; urgency=medium + + -- Michael Zanetti Wed, 27 Nov 2019 15:24:26 +0100 + nymea (0.17.0) xenial; urgency=medium [ Michael Zanetti ] diff --git a/debian/control b/debian/control index e10207b4..82db62d1 100644 --- a/debian/control +++ b/debian/control @@ -6,15 +6,9 @@ Standards-Version: 3.9.7 Homepage: https://nymea.io Vcs-Git: https://github.com/guh/guh.git Build-Depends: debhelper (>= 9.0.0), + dbus-test-runner, dh-systemd, dpkg-dev (>= 1.16.1~), - rsync, - qtchooser, - qt5-default, - qt5-qmake:native, - qtbase5-dev, - qttools5-dev-tools, - qtconnectivity5-dev, libnymea-remoteproxyclient-dev, libqt5websockets5-dev, libqt5bluetooth5, @@ -22,7 +16,15 @@ Build-Depends: debhelper (>= 9.0.0), libqt5dbus5, libssl-dev, libnymea-mqtt-dev (>= 0.1.2), - dbus-test-runner, + rsync, + qml-module-qtquick2, + qtchooser, + qt5-default, + qt5-qmake:native, + qtbase5-dev, + qttools5-dev-tools, + qtconnectivity5-dev, + qtdeclarative5-dev, Package: nymea Architecture: any @@ -30,6 +32,7 @@ Section: metapackages Multi-Arch: same Depends: nymead (= ${binary:Version}), ${misc:Depends} +Recommends: qml-module-qtquick2 Suggests: nymea-doc Replaces: guh Description: An open source IoT server - meta package @@ -59,6 +62,7 @@ Depends: libqt5network5, tar, iputils-tracepath, iputils-ping, + qml-module-qtquick2, dnsutils, nymea-translations, libnymea1 (= ${binary:Version}), diff --git a/libnymea-core/debugserverhandler.cpp b/libnymea-core/debugserverhandler.cpp index 2b71eb95..103b3a78 100644 --- a/libnymea-core/debugserverhandler.cpp +++ b/libnymea-core/debugserverhandler.cpp @@ -41,7 +41,6 @@ namespace nymeaserver { -QtMessageHandler DebugServerHandler::s_oldLogMessageHandler = nullptr; QList DebugServerHandler::s_websocketClients; DebugServerHandler::DebugServerHandler(QObject *parent) : @@ -514,8 +513,6 @@ HttpReply *DebugServerHandler::processDebugRequest(const QString &requestPath, c void DebugServerHandler::logMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &message) { - s_oldLogMessageHandler(type, context, message); - QString finalMessage; switch (type) { case QtDebugMsg: @@ -616,7 +613,7 @@ void DebugServerHandler::onWebsocketClientConnected() if (s_websocketClients.isEmpty()) { qCDebug(dcDebugServer()) << "Install debug message handler for live logs."; //QLoggingCategory::setFilterRules("*.debug=true"); - s_oldLogMessageHandler = qInstallMessageHandler(&logMessageHandler); + nymeaInstallMessageHandler(&logMessageHandler); } s_websocketClients.append(client); @@ -634,9 +631,8 @@ void DebugServerHandler::onWebsocketClientDisconnected() client->deleteLater(); if (s_websocketClients.isEmpty()) { - qCDebug(dcDebugServer()) << "Uninstall debug message handler for live logs and restore default message handler"; - qInstallMessageHandler(s_oldLogMessageHandler); - s_oldLogMessageHandler = nullptr; + qCDebug(dcDebugServer()) << "Uninstalling debug message handler for live logs."; + nymeaUninstallMessageHandler(&logMessageHandler); } } diff --git a/libnymea-core/debugserverhandler.h b/libnymea-core/debugserverhandler.h index 9a701d4f..034865b7 100644 --- a/libnymea-core/debugserverhandler.h +++ b/libnymea-core/debugserverhandler.h @@ -41,7 +41,6 @@ public: HttpReply *processDebugRequest(const QString &requestPath, const QUrlQuery &requestQuery); private: - static QtMessageHandler s_oldLogMessageHandler; static QList s_websocketClients; static void logMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &message); diff --git a/libnymea-core/devices/devicemanagerimplementation.cpp b/libnymea-core/devices/devicemanagerimplementation.cpp index 48c365e5..3c90abda 100644 --- a/libnymea-core/devices/devicemanagerimplementation.cpp +++ b/libnymea-core/devices/devicemanagerimplementation.cpp @@ -23,6 +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" @@ -268,6 +271,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); } }); @@ -297,6 +304,7 @@ DeviceSetupInfo *DeviceManagerImplementation::addConfiguredDevice(const DeviceDe { 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; @@ -304,11 +312,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; @@ -674,12 +684,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; @@ -693,6 +705,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; @@ -702,6 +715,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; @@ -1104,6 +1118,43 @@ void DeviceManagerImplementation::loadPlugins() loadPlugin(pluginIface, metaData); } } + +#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(); + 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(jsFi.absoluteFilePath()); + 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); + } + } +#endif } 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..7e710c2f --- /dev/null +++ b/libnymea-core/devices/scriptdeviceplugin.cpp @@ -0,0 +1,255 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2019 Michael Zanetti * + * * + * This file is part of nymea. * + * * + * nymea is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, version 2 of the License. * + * * + * nymea 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 nymea. If not, see . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "scriptdeviceplugin.h" + +#include +#include +#include + +#include "loggingcategories.h" +#include + +ScriptDevicePlugin::ScriptDevicePlugin(QObject *parent) : DevicePlugin(parent) +{ + +} + +bool ScriptDevicePlugin::loadScript(const QString &fileName) +{ + + QFileInfo fi(fileName); + QString metaDataFileName = fi.absoluteDir().path() + '/' + fi.baseName() + ".json"; + + 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()); + + m_engine = new QQmlEngine(this); + m_engine->installExtensions(QJSEngine::AllExtensions); + + QJSValue deviceMetaObject = m_engine->newQMetaObject(&Device::staticMetaObject); + m_engine->globalObject().setProperty("Device", deviceMetaObject); + + m_pluginImport = m_engine->importModule(fileName); + if (m_pluginImport.isError()) { + qCWarning(dcDeviceManager()) << "Error loading plugin module" << m_pluginImport.errorType() << m_pluginImport.toString(); + return false; + } + + return true; +} + +QJsonObject ScriptDevicePlugin::metaData() const +{ + return m_metaData; +} + +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); + + 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 new file mode 100644 index 00000000..b9082f63 --- /dev/null +++ b/libnymea-core/devices/scriptdeviceplugin.h @@ -0,0 +1,183 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2019 Michael Zanetti * + * * + * This file is part of nymea. * + * * + * nymea is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, version 2 of the License. * + * * + * nymea 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 nymea. If not, see . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef SCRIPTDEVICEPLUGIN_H +#define SCRIPTDEVICEPLUGIN_H + +#include "devices/deviceplugin.h" + +#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 +public: + explicit ScriptDevicePlugin(QObject *parent = nullptr); + + bool loadScript(const QString &fileName); + QJsonObject metaData() const; + + void init() override; + 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; + +private: + QQmlEngine *m_engine = nullptr; + QJsonObject m_metaData; + QJSValue m_pluginImport; + QHash m_devices; +}; + +#endif // SCRIPTDEVICEPLUGIN_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..2687d8dc --- /dev/null +++ b/libnymea-core/jsonrpc/scriptshandler.cpp @@ -0,0 +1,214 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2019 Michael Zanetti * + * * + * This file is part of nymea. * + * * + * nymea is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, version 2 of the License. * + * * + * nymea 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 nymea. If not, see . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "scriptshandler.h" + +#include "loggingcategories.h" + +#include "scriptengine/scriptengine.h" + +namespace nymeaserver { + +ScriptsHandler::ScriptsHandler(ScriptEngine *scriptEngine, QObject *parent): + JsonHandler(parent), + m_engine(scriptEngine) +{ + registerEnum(); + 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 = "Get a scripts content."; + params.insert("id", enumValueName(Uuid)); + returns.insert("scriptError", enumRef()); + returns.insert("o:content", enumValueName(String)); + registerMethod("GetScriptContent", 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