Merge PR #231: Scriptengine

This commit is contained in:
Jenkins nymea 2020-01-29 21:44:33 +01:00
commit d2c02d78bc
49 changed files with 3187 additions and 103 deletions

4
debian/changelog vendored
View File

@ -1,3 +1,7 @@
nymea (0.18.0) UNRELEASED; urgency=medium
-- Michael Zanetti <michael.zanetti@guh.io> Wed, 27 Nov 2019 15:24:26 +0100
nymea (0.17.0) xenial; urgency=medium
[ Michael Zanetti ]

20
debian/control vendored
View File

@ -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}),

View File

@ -41,7 +41,6 @@
namespace nymeaserver {
QtMessageHandler DebugServerHandler::s_oldLogMessageHandler = nullptr;
QList<QWebSocket*> 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);
}
}

View File

@ -41,7 +41,6 @@ public:
HttpReply *processDebugRequest(const QString &requestPath, const QUrlQuery &requestQuery);
private:
static QtMessageHandler s_oldLogMessageHandler;
static QList<QWebSocket*> s_websocketClients;
static void logMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &message);

View File

@ -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)

View File

@ -0,0 +1,255 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2019 Michael Zanetti <michael.zanetti@nymea.io> *
* *
* 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 <http://www.gnu.org/licenses/>. *
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "scriptdeviceplugin.h"
#include <QQmlEngine>
#include <QDir>
#include <QJsonDocument>
#include "loggingcategories.h"
#include <plugintimer.h>
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<PluginTimerManager>();
qmlRegisterType<PluginTimer>();
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();
}
}

View File

@ -0,0 +1,183 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2019 Michael Zanetti <michael.zanetti@nymea.io> *
* *
* 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 <http://www.gnu.org/licenses/>. *
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#ifndef SCRIPTDEVICEPLUGIN_H
#define SCRIPTDEVICEPLUGIN_H
#include "devices/deviceplugin.h"
#include <QQmlEngine>
#include <QJsonObject>
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 &params = 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 &paramTypeId) { return m_device->paramValue(paramTypeId); }
Q_INVOKABLE void setParamValue(const QUuid &paramTypeId, 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 &paramTypeId) { 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 &paramTypeId) { 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<Device*, ScriptDevice*> m_devices;
};
#endif // SCRIPTDEVICEPLUGIN_H

View File

@ -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"

View File

@ -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<QString, JsonHandler *> handlers() const;
void sendResponse(TransportInterface *interface, const QUuid &clientId, int commandId, const QVariantMap &params = QVariantMap(), const QString &deprecationWarning = QString());

View File

@ -0,0 +1,214 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2019 Michael Zanetti <michael.zanetti@nymea.io> *
* *
* 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 <http://www.gnu.org/licenses/>. *
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "scriptshandler.h"
#include "loggingcategories.h"
#include "scriptengine/scriptengine.h"
namespace nymeaserver {
ScriptsHandler::ScriptsHandler(ScriptEngine *scriptEngine, QObject *parent):
JsonHandler(parent),
m_engine(scriptEngine)
{
registerEnum<ScriptEngine::ScriptError>();
registerEnum<ScriptEngine::ScriptMessageType>();
registerObject<Script, Scripts>();
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<Scripts>());
registerMethod("GetScripts", description, params, returns);
params.clear(); returns.clear();
description = "Get a scripts content.";
params.insert("id", enumValueName(Uuid));
returns.insert("scriptError", enumRef<ScriptEngine::ScriptError>());
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<ScriptEngine::ScriptError>());
returns.insert("o:script", objectRef<Script>());
returns.insert("o:errors", enumValueName(StringList));
registerMethod("AddScript", description, params, returns);
params.clear(); returns.clear();
description = "Edit a script";
params.insert("id", enumValueName(Uuid));
params.insert("o:name", enumValueName(String));
params.insert("o:content", enumValueName(String));
returns.insert("scriptError", enumRef<ScriptEngine::ScriptError>());
returns.insert("o:errors", enumValueName(StringList));
registerMethod("EditScript", description, params, returns);
params.clear(); returns.clear();
description = "remove a script";
params.insert("id", enumValueName(Uuid));
returns.insert("scriptError", enumRef<ScriptEngine::ScriptError>());
registerMethod("RemoveScript", description, params, returns);
params.clear();
description = "Emitted when a script has been added to the system.";
params.insert("script", objectRef<Script>());
registerNotification("ScriptAdded", description, params);
params.clear();
description = "Emitted when a script has been removed from the system.";
params.insert("id", enumValueName(Uuid));
registerNotification("ScriptRemoved", description, params);
params.clear();
description = "Emitted when a script has been changed in the system (e.g. renamed).";
params.insert("scriptId", enumValueName(Uuid));
params.insert("name", enumValueName(String));
registerNotification("ScriptChanged", description, params);
params.clear();
description = "Emitted when a script's content has been changed in the system.";
params.insert("scriptId", enumValueName(Uuid));
registerNotification("ScriptContentChanged", description, params);
params.clear();
description = "Emitted when a script produces a console message.";
params.insert("scriptId", enumValueName(Uuid));
params.insert("type", enumRef<ScriptEngine::ScriptMessageType>());
params.insert("message", enumValueName(String));
registerNotification("ScriptLogMessage", description, params);
connect(m_engine, &ScriptEngine::scriptAdded, this, [this](const Script &script){
QVariantMap params;
params.insert("script", pack(script));
emit ScriptAdded(params);
});
connect(m_engine, &ScriptEngine::scriptRemoved, this, [this](const QUuid &scriptId){
QVariantMap params;
params.insert("id", scriptId);
emit ScriptRemoved(params);
});
connect(m_engine, &ScriptEngine::scriptRenamed, this, [this](const Script &script){
QVariantMap params;
params.insert("scriptId", script.id());
params.insert("name", script.name());
emit ScriptChanged(params);
});
connect(m_engine, &ScriptEngine::scriptChanged, this, [this](const Script &script){
QVariantMap params;
params.insert("scriptId", script.id());
emit ScriptContentChanged(params);
});
connect(m_engine, &ScriptEngine::scriptConsoleMessage, this, [this](const QUuid &scriptId, ScriptEngine::ScriptMessageType type, const QString &message){
QVariantMap params;
params.insert("scriptId", scriptId);
params.insert("type", enumValueName(type));
params.insert("message", message);
emit ScriptLogMessage(params);
});
}
QString ScriptsHandler::name() const
{
return "Scripts";
}
JsonReply *ScriptsHandler::GetScripts(const QVariantMap &params)
{
Q_UNUSED(params)
QVariantMap returns;
returns.insert("scripts", pack(m_engine->scripts()));
return createReply(returns);
}
JsonReply *ScriptsHandler::GetScriptContent(const QVariantMap &params)
{
QUuid scriptId = params.value("id").toUuid();
ScriptEngine::GetScriptReply reply = m_engine->scriptContent(scriptId);
QVariantMap returns;
returns.insert("scriptError", enumValueName(reply.scriptError));
if (reply.scriptError == ScriptEngine::ScriptErrorNoError) {
returns.insert("content", reply.content);
}
return createReply(returns);
}
JsonReply* ScriptsHandler::AddScript(const QVariantMap &params)
{
qWarning() << "Script:" << params.value("content").toString();
QVariantMap returns;
ScriptEngine::AddScriptReply scriptReply = m_engine->addScript(params.value("name").toString(), params.value("content").toByteArray());
returns.insert("scriptError", enumValueName(scriptReply.scriptError));
if (scriptReply.scriptError != ScriptEngine::ScriptErrorNoError) {
returns.insert("errors", scriptReply.errors);
} else {
returns.insert("script", pack(scriptReply.script));
}
return createReply(returns);
}
JsonReply *ScriptsHandler::EditScript(const QVariantMap &params)
{
QUuid scriptId = params.value("id").toUuid();
QVariantMap returns;
if (params.contains("name")) {
QString name = params.value("name").toString();
ScriptEngine::ScriptError result = m_engine->renameScript(scriptId, name);
if (result != ScriptEngine::ScriptErrorNoError) {
returns.insert("scriptError", enumValueName(result));
return createReply(returns);
}
}
if (params.contains("content")) {
QByteArray content = params.value("content").toByteArray();
ScriptEngine::EditScriptReply reply = m_engine->editScript(scriptId, content);
if (reply.scriptError != ScriptEngine::ScriptErrorNoError) {
returns.insert("scriptError", enumValueName(reply.scriptError));
returns.insert("errors", reply.errors);
return createReply(returns);
}
}
returns.insert("scriptError", enumValueName(ScriptEngine::ScriptErrorNoError));
return createReply(returns);
}
JsonReply *ScriptsHandler::RemoveScript(const QVariantMap &params)
{
QUuid scriptId = params.value("id").toUuid();
ScriptEngine::ScriptError status = m_engine->removeScript(scriptId);
QVariantMap returns;
returns.insert("scriptError", enumValueName(status));
return createReply(returns);
}
}

View File

@ -0,0 +1,59 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2019 Michael Zanetti <michael.zanetti@nymea.io> *
* *
* 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 <http://www.gnu.org/licenses/>. *
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#ifndef SCRIPTSHANDLER_H
#define SCRIPTSHANDLER_H
#include "jsonrpc/jsonhandler.h"
#include "scriptengine/scriptengine.h"
namespace nymeaserver {
class ScriptsHandler : public JsonHandler
{
Q_OBJECT
public:
explicit ScriptsHandler(ScriptEngine *scriptEngine, QObject *parent = nullptr);
QString name() const override;
public slots:
JsonReply* GetScripts(const QVariantMap &params);
JsonReply* GetScriptContent(const QVariantMap &params);
JsonReply* AddScript(const QVariantMap &params);
JsonReply* EditScript(const QVariantMap &params);
JsonReply* RemoveScript(const QVariantMap &params);
signals:
void ScriptAdded(const QVariantMap &params);
void ScriptRemoved(const QVariantMap &params);
void ScriptChanged(const QVariantMap &params);
void ScriptContentChanged(const QVariantMap &params);
void ScriptLogMessage(const QVariantMap &params);
private:
ScriptEngine *m_engine = nullptr;
};
}
#endif // SCRIPTSHANDLER_H

View File

@ -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
@ -20,11 +20,18 @@ HEADERS += nymeacore.h \
devices/translator.h \
experiences/experiencemanager.h \
jsonrpc/jsonrpcserverimplementation.h \
jsonrpc/scriptshandler.h \
ruleengine/ruleengine.h \
ruleengine/rule.h \
ruleengine/stateevaluator.h \
ruleengine/ruleaction.h \
ruleengine/ruleactionparam.h \
scriptengine/script.h \
scriptengine/scriptaction.h \
scriptengine/scriptalarm.h \
scriptengine/scriptengine.h \
scriptengine/scriptevent.h \
scriptengine/scriptstate.h \
transportinterface.h \
nymeaconfiguration.h \
servermanager.h \
@ -94,11 +101,18 @@ SOURCES += nymeacore.cpp \
devices/translator.cpp \
experiences/experiencemanager.cpp \
jsonrpc/jsonrpcserverimplementation.cpp \
jsonrpc/scriptshandler.cpp \
ruleengine/ruleengine.cpp \
ruleengine/rule.cpp \
ruleengine/stateevaluator.cpp \
ruleengine/ruleaction.cpp \
ruleengine/ruleactionparam.cpp \
scriptengine/script.cpp \
scriptengine/scriptaction.cpp \
scriptengine/scriptalarm.cpp \
scriptengine/scriptengine.cpp \
scriptengine/scriptevent.cpp \
scriptengine/scriptstate.cpp \
transportinterface.cpp \
nymeaconfiguration.cpp \
servermanager.cpp \
@ -160,3 +174,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 \
}

View File

@ -267,7 +267,7 @@ DevicesFetchJob *LogEngine::fetchDevices()
}
foreach (const QSqlRecord &result, job->results()) {
fetchJob->m_results.append(DeviceId::fromUuid(result.value("deviceId").toUuid()));
fetchJob->m_results.append(DeviceId(result.value("deviceId").toUuid()));
}
fetchJob->finished();
});
@ -476,7 +476,7 @@ void LogEngine::appendLogEntry(const LogEntry &entry)
connect(job, &DatabaseJob::finished, this, [this, job, entry](){
if (job->error().type() != QSqlError::NoError) {
qCWarning(dcLogEngine) << "Error writing log entry. Driver error:" << job->error().driverText() << "Database error:" << job->error().number() << job->error().databaseText();
qCWarning(dcLogEngine) << "Error writing log entry. Driver error:" << job->error().driverText() << "Database error:" << job->error().databaseText();
qCWarning(dcLogEngine) << entry;
m_dbMalformed = true;
return;

View File

@ -99,6 +99,9 @@
#include "platform/platform.h"
#include "experiences/experiencemanager.h"
#include "scriptengine/scriptengine.h"
#include "jsonrpc/scriptshandler.h"
#include "devices/devicemanagerimplementation.h"
#include "devices/device.h"
#include "devices/deviceactioninfo.h"
@ -160,6 +163,10 @@ void NymeaCore::init() {
qCDebug(dcApplication) << "Creating Rule Engine";
m_ruleEngine = new RuleEngine(this);
qCDebug(dcApplication()) << "Creating Script Engine";
m_scriptEngine = new ScriptEngine(m_deviceManager, this);
m_serverManager->jsonServer()->registerHandler(new ScriptsHandler(m_scriptEngine, m_scriptEngine));
qCDebug(dcApplication()) << "Creating Tags Storage";
m_tagsStorage = new TagsStorage(m_deviceManager, m_ruleEngine, this);
@ -600,6 +607,12 @@ RuleEngine *NymeaCore::ruleEngine() const
return m_ruleEngine;
}
/*! Returns a pointer to the \l{ScriptEngine} instance owned by NymeaCore.*/
ScriptEngine *NymeaCore::scriptEngine() const
{
return m_scriptEngine;
}
/*! Returns a pointer to the \l{TimeManager} instance owned by NymeaCore.*/
TimeManager *NymeaCore::timeManager() const
{
@ -660,6 +673,7 @@ QStringList NymeaCore::loggingFilters()
"DeviceManager",
"RuleEngine",
"RuleEngineDebug",
"ScriptEngine",
"Hardware",
"Bluetooth",
"LogEngine",

View File

@ -55,6 +55,7 @@ class UserManager;
class Platform;
class System;
class ExperienceManager;
class ScriptEngine;
class NymeaCore : public QObject
{
@ -85,6 +86,7 @@ public:
JsonRPCServerImplementation *jsonRPCServer() const;
DeviceManager *deviceManager() const;
RuleEngine *ruleEngine() const;
ScriptEngine *scriptEngine() const;
TimeManager *timeManager() const;
ServerManager *serverManager() const;
BluetoothServer *bluetoothServer() const;
@ -125,6 +127,7 @@ private:
ServerManager *m_serverManager;
DeviceManagerImplementation *m_deviceManager;
RuleEngine *m_ruleEngine;
ScriptEngine *m_scriptEngine;
LogEngine *m_logger;
TimeManager *m_timeManager;
CloudManager *m_cloudManager;

View File

@ -0,0 +1,71 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2019 Michael Zanetti <michael.zanetti@nymea.io> *
* *
* 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 <http://www.gnu.org/licenses/>. *
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "script.h"
namespace nymeaserver {
Script::Script()
{
}
QUuid Script::id() const
{
return m_id;
}
void Script::setId(const QUuid &id)
{
m_id = id;
}
QString Script::name() const
{
return m_name;
}
void Script::setName(const QString &name)
{
m_name = name;
}
Scripts::Scripts()
{
}
Scripts::Scripts(const QList<Script> &other):
QList<Script>(other)
{
}
QVariant Scripts::get(int index)
{
return QVariant::fromValue(at(index));
}
void Scripts::put(const QVariant &value)
{
append(value.value<Script>());
}
}

View File

@ -0,0 +1,73 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2019 Michael Zanetti <michael.zanetti@nymea.io> *
* *
* 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 <http://www.gnu.org/licenses/>. *
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#ifndef SCRIPT_H
#define SCRIPT_H
#include <QMetaObject>
#include <QUuid>
#include <QQmlContext>
#include <QQmlComponent>
#include <QObject>
namespace nymeaserver {
class Script
{
Q_GADGET
Q_PROPERTY(QUuid id READ id)
Q_PROPERTY(QString name READ name WRITE setName)
public:
Script();
QUuid id() const;
void setId(const QUuid &id);
QString name() const;
void setName(const QString &name);
QStringList errors;
private:
QUuid m_id;
QString m_name;
friend class ScriptEngine;
QQmlContext *context = nullptr;
QQmlComponent *component = nullptr;
QObject *object = nullptr;
};
class Scripts: public QList<Script>
{
Q_GADGET
Q_PROPERTY(int count READ count)
public:
Scripts();
Scripts(const QList<Script> &other);
Q_INVOKABLE QVariant get(int index);
Q_INVOKABLE void put(const QVariant &value);
};
}
Q_DECLARE_METATYPE(nymeaserver::Script)
Q_DECLARE_METATYPE(nymeaserver::Scripts)
#endif // SCRIPT_H

View File

@ -0,0 +1,125 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2019 Michael Zanetti <michael.zanetti@nymea.io> *
* *
* 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 <http://www.gnu.org/licenses/>. *
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "scriptaction.h"
#include "devices/devicemanager.h"
#include "types/action.h"
#include <QQmlEngine>
#include <qqml.h>
#include "loggingcategories.h"
namespace nymeaserver {
ScriptAction::ScriptAction(QObject *parent) : QObject(parent)
{
}
void ScriptAction::classBegin()
{
m_deviceManager = reinterpret_cast<DeviceManager*>(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();
}
}
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 QVariantMap &params)
{
Device *device = m_deviceManager->configuredDevices().findById(DeviceId(m_deviceId));
if (!device) {
qCWarning(dcScriptEngine) << "No device with id" << m_deviceId;
return;
}
ActionType actionType;
if (!ActionTypeId(m_actionTypeId).isNull()) {
actionType = device->deviceClass().actionTypes().findById(ActionTypeId(m_actionTypeId));
} else {
actionType = device->deviceClass().actionTypes().findByName(m_actionName);
}
if (actionType.id().isNull()) {
qCWarning(dcScriptEngine()) << "Either a valid actionTypeId or actionName is required";
return;
}
Action action;
action.setActionTypeId(actionType.id());
action.setDeviceId(DeviceId(m_deviceId));
ParamList paramList;
foreach (const QString &paramNameOrId, params.keys()) {
ParamType paramType;
if (!ParamTypeId(paramNameOrId).isNull()) {
paramType = actionType.paramTypes().findById(ParamTypeId(paramNameOrId));
} else {
paramType = actionType.paramTypes().findByName(paramNameOrId);
}
if (paramType.id().isNull()) {
qCWarning(dcScriptEngine()) << "Invalid param id or name";
continue;
}
paramList << Param(paramType.id(), params.value(paramNameOrId));
}
action.setParams(paramList);
m_deviceManager->executeAction(action);
}
}

View File

@ -0,0 +1,68 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2019 Michael Zanetti <michael.zanetti@nymea.io> *
* *
* 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 <http://www.gnu.org/licenses/>. *
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#ifndef SCRIPTACTION_H
#define SCRIPTACTION_H
#include <QObject>
#include <QQmlParserStatus>
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)
Q_PROPERTY(QString actionName READ actionName WRITE setActionName NOTIFY actionNameChanged)
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);
QString actionName() const;
void setActionName(const QString &actionName);
public slots:
void execute(const QVariantMap &params);
signals:
void deviceIdChanged();
void actionTypeIdChanged();
void actionNameChanged();
public:
DeviceManager *m_deviceManager = nullptr;
QString m_deviceId;
QString m_actionTypeId;
QString m_actionName;
};
}
#endif // SCRIPTACTION_H

View File

@ -0,0 +1,144 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2019 Michael Zanetti <michael.zanetti@nymea.io> *
* *
* 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 <http://www.gnu.org/licenses/>. *
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "scriptalarm.h"
#include "loggingcategories.h"
#include <QTimer>
ScriptAlarm::ScriptAlarm(QObject *parent) : QObject(parent)
{
}
QTime ScriptAlarm::time() const
{
return m_time;
}
void ScriptAlarm::setTime(const QTime &time)
{
qCDebug(dcScriptEngine()) << "Blablabla" << time;
if (m_time != time) {
m_time = time;
emit timeChanged();
if (!time.isValid()) {
qCWarning(dcScriptEngine()) << "Invalid time:" << time;
}
if (time.isValid() && m_timerId == 0) {
m_timerId = startTimer(1000, Qt::VeryCoarseTimer);
} else if (!time.isValid() && m_timerId != 0) {
killTimer(m_timerId);
}
updateActive();
}
}
QTime ScriptAlarm::endTime() const
{
return m_endTime;
}
void ScriptAlarm::setEndTime(const QTime &endTime)
{
if (m_endTime != endTime) {
m_endTime = endTime;
emit endTimeChanged();
updateActive();
}
}
ScriptAlarm::WeekDays ScriptAlarm::weekDays() const
{
return m_weekDays;
}
void ScriptAlarm::setWeekDays(const WeekDays &weekDays)
{
if (m_weekDays != weekDays) {
m_weekDays = weekDays;
emit weekDaysChanged();
updateActive();
}
}
bool ScriptAlarm::active() const
{
return m_active;
}
void ScriptAlarm::timerEvent(QTimerEvent *event)
{
Q_UNUSED(event)
QTime now = QTime::currentTime();
updateActive();
if (!m_weekDays.testFlag(today())) {
return;
}
if (!m_weekDays.testFlag(today())) {
return;
}
if (m_time.hour() != now.hour()) {
return;
}
if (m_time.minute() != now.minute()) {
return;
}
if (m_time.second() != now.second()) {
return;
}
emit triggered();
}
ScriptAlarm::WeekDay ScriptAlarm::today() const
{
QList<WeekDay> allDays = {Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday};
return allDays.at(QDateTime::currentDateTime().date().dayOfWeek() - 1);
}
void ScriptAlarm::updateActive()
{
QTime now = QTime::currentTime();
bool active = m_endTime.isValid() && m_weekDays.testFlag(today());
if (active) {
bool beforeStart = now < m_time;
bool afterEnd = now > m_endTime;
if (m_time < m_endTime) {
active = !beforeStart && !afterEnd;
} else {
active = beforeStart || afterEnd;
}
}
if (active != m_active) {
m_active = active;
emit activeChanged();
}
}

View File

@ -0,0 +1,90 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2019 Michael Zanetti <michael.zanetti@nymea.io> *
* *
* 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 <http://www.gnu.org/licenses/>. *
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#ifndef SCRIPTALARM_H
#define SCRIPTALARM_H
#include <QObject>
#include <QDateTime>
#include <QTimer>
class ScriptAlarm : public QObject
{
Q_OBJECT
Q_PROPERTY(QTime time READ time WRITE setTime NOTIFY timeChanged)
Q_PROPERTY(QTime endTime READ endTime WRITE setEndTime NOTIFY endTimeChanged)
Q_PROPERTY(WeekDays weekDays READ weekDays WRITE setWeekDays NOTIFY weekDaysChanged)
Q_PROPERTY(bool active READ active NOTIFY activeChanged)
public:
enum WeekDay {
Monday = 0x01,
Tuesday = 0x02,
Wednesday = 0x04,
Thursday = 0x08,
Friday = 0x10,
Saturday = 0x20,
Sunday = 0x40,
AllDays = 0xFF
};
Q_ENUM(WeekDay)
Q_DECLARE_FLAGS(WeekDays, WeekDay)
Q_FLAG(WeekDays)
explicit ScriptAlarm(QObject *parent = nullptr);
QTime time() const;
void setTime(const QTime &time);
QTime endTime() const;
void setEndTime(const QTime &endTime);
WeekDays weekDays() const;
void setWeekDays(const WeekDays &weekDays);
bool active() const;
signals:
void timeChanged();
void endTimeChanged();
void weekDaysChanged();
void triggered();
void activeChanged();
protected:
void timerEvent(QTimerEvent *event) override;
private:
WeekDay today() const;
void updateActive();
private:
QTime m_time;
QTime m_endTime;
WeekDays m_weekDays = AllDays;
bool m_active = false;
int m_timerId = 0;
};
#endif // SCRIPTALARM_H

View File

@ -0,0 +1,466 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2019 Michael Zanetti <michael.zanetti@nymea.io> *
* *
* 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 <http://www.gnu.org/licenses/>. *
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "scriptengine.h"
#include "devices/devicemanager.h"
#include "scriptaction.h"
#include "scriptevent.h"
#include "scriptstate.h"
#include "scriptalarm.h"
#include "nymeasettings.h"
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QQmlComponent>
#include <QJsonParseError>
#include <QJsonDocument>
#include "loggingcategories.h"
#include <QDir>
namespace nymeaserver {
QList<ScriptEngine*> ScriptEngine::s_engines;
QtMessageHandler ScriptEngine::s_upstreamMessageHandler;
QLoggingCategory::CategoryFilter ScriptEngine::s_oldCategoryFilter = nullptr;
ScriptEngine::ScriptEngine(DeviceManager *deviceManager, QObject *parent) : QObject(parent),
m_deviceManager(deviceManager)
{
qmlRegisterType<ScriptEvent>("nymea", 1, 0, "DeviceEvent");
qmlRegisterType<ScriptAction>("nymea", 1, 0, "DeviceAction");
qmlRegisterType<ScriptState>("nymea", 1, 0, "DeviceState");
qmlRegisterType<ScriptAlarm>("nymea", 1, 0, "Alarm");
m_engine = new QQmlEngine(this);
m_engine->setProperty("deviceManager", reinterpret_cast<quint64>(m_deviceManager));
// Don't automatically print script warnings (that is, runtime errors, *not* console.warn() messages)
// to stdout as they'd end up on the "default" logging category.
// We collect them ourselves through the warnings() signal and print them to the dcScriptEngine category.
m_engine->setOutputWarningsToStandardError(false);
connect(m_engine, &QQmlEngine::warnings, this, [this](const QList<QQmlError> &warnings){
foreach (const QQmlError &warning, warnings) {
QMessageLogContext ctx(warning.url().toString().toUtf8(), warning.line(), "", "ScriptEngine");
// Send to script logs
onScriptMessage(
#if QT_VERSION >= QT_VERSION_CHECK(5,9,0)
warning.messageType(),
#else
QtMsgType::QtWarningMsg,
#endif
ctx, warning.description());
// and to logging system
qCWarning(dcScriptEngine()) << warning.toString();
}
});
// console.log()/warn() messages instead are printed to the "qml" category. We install our own
// filter to *always* get them, regardless of the configured logging categories
if (!s_oldCategoryFilter) {
s_oldCategoryFilter = QLoggingCategory::installFilter(&logCategoryFilter);
}
// and our own handler to redirect them to the ScriptEngine category
if (s_engines.isEmpty()) {
s_upstreamMessageHandler = qInstallMessageHandler(&logMessageHandler);
}
s_engines.append(this);
QDir dir;
if (!dir.exists(NymeaSettings::storagePath() + "/scripts/")) {
dir.mkpath(NymeaSettings::storagePath() + "/scripts/");
}
loadScripts();
}
ScriptEngine::~ScriptEngine()
{
s_engines.removeAll(this);
if (s_engines.isEmpty()) {
qInstallMessageHandler(s_upstreamMessageHandler);
}
}
Scripts ScriptEngine::scripts()
{
Scripts ret;
foreach (Script *script, m_scripts) {
ret.append(*script);
}
return ret;
}
ScriptEngine::GetScriptReply ScriptEngine::scriptContent(const QUuid &id)
{
GetScriptReply reply;
if (!m_scripts.contains(id)) {
reply.scriptError = ScriptErrorScriptNotFound;
return reply;
}
QFile scriptFile(baseName(id) + ".qml");
if (!scriptFile.open(QFile::ReadOnly)) {
reply.scriptError = ScriptErrorHardwareFailure;
return reply;
}
reply.content = scriptFile.readAll();
reply.scriptError = ScriptErrorNoError;
scriptFile.close();
return reply;
}
ScriptEngine::AddScriptReply ScriptEngine::addScript(const QString &name, const QByteArray &content)
{
QUuid id = QUuid::createUuid();
QString fileName = baseName(id) + ".qml";
QString jsonFileName = baseName(id) + ".json";
AddScriptReply reply;
QFile jsonFile(jsonFileName);
if (!jsonFile.open(QFile::ReadWrite)) {
qCWarning(dcScriptEngine()) << "Error opening script metadata" << jsonFileName;
reply.scriptError = ScriptErrorHardwareFailure;
return reply;
}
QVariantMap metadata;
metadata.insert("name", name);
jsonFile.write(QJsonDocument::fromVariant(metadata).toJson());
jsonFile.close();
QFile scriptFile(fileName);
if (!scriptFile.open(QFile::WriteOnly)) {
qCWarning(dcScriptEngine()) << "Error opening script file:" << fileName;
reply.scriptError = ScriptErrorHardwareFailure;
return reply;
}
qint64 len = scriptFile.write(content);
if (len != content.length()) {
qCWarning(dcScriptEngine()) << "Error writing script content";
reply.scriptError = ScriptErrorHardwareFailure;
return reply;
}
scriptFile.close();
Script *script = new Script();
script->setId(id);
script->setName(name);
bool loaded = loadScript(script);
if (!loaded) {
reply.scriptError = ScriptErrorInvalidScript;
reply.errors = script->errors;
delete script;
QFile::remove(jsonFileName);
QFile::remove(fileName);
return reply;
}
m_scripts.insert(script->id(), script);
reply.scriptError = ScriptErrorNoError;
reply.script = *m_scripts.value(id);
emit scriptAdded(reply.script);
return reply;
}
ScriptEngine::ScriptError ScriptEngine::renameScript(const QUuid &id, const QString &name)
{
if (!m_scripts.contains(id)) {
qCWarning(dcScriptEngine()) << "No script with id" << id;
return ScriptErrorScriptNotFound;
}
QString jsonFileName = baseName(id) + ".json";
QFile jsonFile(jsonFileName);
if (!jsonFile.open(QFile::ReadWrite)) {
qCWarning(dcJsonRpc()) << "Erorr opening script json file" << jsonFileName;
return ScriptErrorHardwareFailure;
}
QJsonParseError error;
QJsonDocument jsonDocument = QJsonDocument::fromJson(jsonFile.readAll(), &error);
QVariantMap jsonData = jsonDocument.toVariant().toMap();
if (error.error != QJsonParseError::NoError) {
qCWarning(dcScriptEngine()) << "Error parsing json file. Recreating it...";
// This is non-critical as we could open it. We can recreate it now.
}
jsonData["name"] = name;
QByteArray jsonString = QJsonDocument::fromVariant(jsonData).toJson();
if (!jsonFile.resize(0) || jsonFile.write(jsonString) != jsonString.length()) {
qCWarning(dcScriptEngine()) << "Error writing json metadata" << jsonFileName;
return ScriptErrorHardwareFailure;
}
jsonFile.close();
m_scripts[id]->setName(name);
qCDebug(dcScriptEngine()) << "Script" << id << "renamed to" << name;
emit scriptRenamed(*m_scripts.value(id));
return ScriptErrorNoError;
}
ScriptEngine::EditScriptReply ScriptEngine::editScript(const QUuid &id, const QByteArray &content)
{
EditScriptReply reply;
if (!m_scripts.contains(id)) {
qCWarning(dcScriptEngine()) << "No script with id" << id;
reply.scriptError = ScriptErrorScriptNotFound;
return reply;
}
Script *script = m_scripts.value(id);
unloadScript(script);
// Deleted compiled qml file to make sure we're reloading the new one
QString compiledScriptFileName = baseName(id) + ".qmlc";
QFile::remove(compiledScriptFileName);
QString scriptFileName = baseName(id) + ".qml";
QFile scriptFile(scriptFileName);
if (!scriptFile.open(QFile::ReadWrite)) {
qCWarning(dcScriptEngine()) << "Error opening script" << id;
reply.scriptError = ScriptErrorHardwareFailure;
return reply;
}
QByteArray oldContent = scriptFile.readAll();
scriptFile.close();
scriptFile.open(QFile::WriteOnly | QFile::Truncate);
qint64 bytesWritten = scriptFile.write(content);
scriptFile.flush();
scriptFile.close();
if (bytesWritten != content.length()) {
qCWarning(dcScriptEngine()) << "Error writing script content";
reply.scriptError = ScriptErrorHardwareFailure;
return reply;
}
bool loaded = loadScript(script);
if (!loaded) {
qCDebug(dcScriptEngine()) << "Restoring old content";
reply.scriptError = ScriptErrorInvalidScript;
reply.errors = script->errors;
// Restore old content
scriptFile.open(QFile::WriteOnly | QFile::Truncate);
scriptFile.write(oldContent);
scriptFile.flush();
scriptFile.close();
loadScript(script);
return reply;
}
qCDebug(dcScriptEngine()) << "Script updated" << script->name();
reply.scriptError = ScriptErrorNoError;
emit scriptChanged(*script);
return reply;
}
ScriptEngine::ScriptError ScriptEngine::removeScript(const QUuid &id)
{
Script *script = m_scripts.take(id);
if (!script) {
return ScriptErrorScriptNotFound;
}
unloadScript(script);
QString jsonFileName = baseName(id) + ".json";
QString scriptFileName = baseName(id) + ".qml";
QString compiledScriptFileName = baseName(id) + ".qmlc";
QFile::remove(scriptFileName);
QFile::remove(jsonFileName);
QFile::remove(compiledScriptFileName);
emit scriptRemoved(script->id());
delete script;
return ScriptErrorNoError;
}
void ScriptEngine::loadScripts()
{
QDir dir(NymeaSettings::storagePath() + "/scripts/");
foreach (const QString &entry, dir.entryList({"*.json"})) {
qCDebug(dcScriptEngine()) << "Have script:" << entry;
QFileInfo jsonFileInfo(NymeaSettings::storagePath() + "/scripts/" + entry);
QString jsonFileName = jsonFileInfo.absoluteFilePath();
QString scriptFileName = jsonFileInfo.absolutePath() + "/" + jsonFileInfo.baseName() + ".qml";
if (!QFile::exists(scriptFileName)) {
qCWarning(dcScriptEngine()) << "Missing script" << scriptFileName;
continue;
}
QFile jsonFile(jsonFileName);
if (!jsonFile.open(QFile::ReadOnly)) {
qCWarning(dcScriptEngine()) << "Failed to open script metadata" << jsonFileName;
continue;
}
QJsonParseError error;
QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonFile.readAll(), &error);
jsonFile.close();
if (error.error != QJsonParseError::NoError) {
qCWarning(dcScriptEngine()) << "Error parsing script metadata" << jsonFileName;
continue;
}
Script *script = new Script();
script->setId(jsonFileInfo.baseName());
script->setName(jsonDoc.toVariant().toMap().value("name").toString());
bool loaded = loadScript(script);
if (!loaded) {
qCWarning(dcScriptEngine()) << "Script failed to load:";
delete script;
continue;
}
m_scripts.insert(script->id(), script);
qCDebug(dcScriptEngine()) << "Script loaded" << scriptFileName;
}
}
bool ScriptEngine::loadScript(Script *script)
{
qCDebug(dcScriptEngine()) << "Loading script" << script->name();
QString fileName = baseName(script->id()) + ".qml";
QString jsonFileName = baseName(script->id()) + ".json";
QFile jsonFile(jsonFileName);
if (!jsonFile.open(QFile::ReadOnly)) {
qCWarning(dcScriptEngine()) << "Failed to open script metadata";
return false;
}
QJsonParseError error;
QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonFile.readAll(), &error);
jsonFile.close();
if (error.error != QJsonParseError::NoError) {
qCWarning(dcScriptEngine()) << "Failed to parse script metadata";
return false;
}
QString name = jsonDoc.toVariant().toMap().value("name").toString();
script->errors.clear();
script->component = new QQmlComponent(m_engine, QUrl::fromLocalFile(fileName), this);
script->context = new QQmlContext(m_engine, this);
script->object = script->component->create(script->context);
if (!script->object) {
qCWarning(dcScriptEngine()) << "Script failed to load:";
foreach (const QQmlError &error, script->component->errors()) {
qCWarning(dcScriptEngine()) << error.toString();
script->errors.append(QString("%1:%2: %3").arg(error.line()).arg(error.column()).arg(error.description()));
}
delete script->context;
delete script->component;
m_engine->clearComponentCache();
return false;
}
return true;
}
void ScriptEngine::unloadScript(Script *script)
{
if (!script->object || !script->component || !script->context) {
qCWarning(dcScriptEngine()) << "Script seems not to be loaded. Cannot unload.";
return;
}
delete script->object;
script->object = nullptr;
delete script->component;
script->component = nullptr;
delete script->context;
script->context = nullptr;
m_engine->clearComponentCache();
qCDebug(dcScriptEngine()) << "Unloading script" << script->name();
}
QString ScriptEngine::baseName(const QUuid &id)
{
QString path = NymeaSettings::storagePath() + "/scripts/";
QString basename = id.toString().remove(QRegExp("[{}]"));
return path + basename;
}
void ScriptEngine::onScriptMessage(QtMsgType type, const QMessageLogContext &context, const QString &message)
{
QFileInfo fi(context.file);
QUuid scriptId = fi.baseName();
if (!m_scripts.contains(scriptId)) {
return;
}
emit scriptConsoleMessage(scriptId, type == QtDebugMsg ? ScriptMessageTypeLog : ScriptMessageTypeWarning, QString::number(context.line) + ": " + message);
}
void ScriptEngine::logMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &message)
{
if (strcmp(context.category, "qml") != 0) {
s_upstreamMessageHandler(type, context, message);
return;
}
// Copy the message to the script engine
foreach (ScriptEngine *engine, s_engines) {
engine->onScriptMessage(type, context, message);
}
if (!s_oldCategoryFilter) {
return;
}
// Redirect qml messages to the ScriptEngine handler
QMessageLogContext newContext(context.file, context.line, context.function, "ScriptEngine");
QLoggingCategory *category = new QLoggingCategory("ScriptEngine", type);
s_oldCategoryFilter(category);
if (category->isEnabled(type)) {
QFileInfo fi(context.file);
s_upstreamMessageHandler(type, newContext, fi.fileName() + ":" + QString::number(context.line) + ": " + message);
}
}
void ScriptEngine::logCategoryFilter(QLoggingCategory *category)
{
// always enable qml logs, regardless what the filters are
if (qstrcmp(category->categoryName(), "qml") == 0) {
category->setEnabled(QtDebugMsg, true);
category->setEnabled(QtWarningMsg, true);
} else if (s_oldCategoryFilter) {
s_oldCategoryFilter(category);
}
}
}

View File

@ -0,0 +1,108 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2019 Michael Zanetti <michael.zanetti@nymea.io> *
* *
* 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 <http://www.gnu.org/licenses/>. *
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#ifndef SCRIPTENGINE_H
#define SCRIPTENGINE_H
#include <QObject>
#include <QUuid>
#include <QQmlEngine>
#include <QJsonValue>
#include <QLoggingCategory>
#include "devices/devicemanager.h"
#include "script.h"
namespace nymeaserver {
class ScriptEngine : public QObject
{
Q_OBJECT
public:
enum ScriptError {
ScriptErrorNoError,
ScriptErrorScriptNotFound,
ScriptErrorInvalidScript,
ScriptErrorHardwareFailure
};
Q_ENUM(ScriptError)
enum ScriptMessageType {
ScriptMessageTypeLog,
ScriptMessageTypeWarning
};
Q_ENUM(ScriptMessageType)
struct AddScriptReply {
ScriptError scriptError;
QStringList errors;
Script script;
};
struct EditScriptReply {
ScriptError scriptError;
QStringList errors;
};
struct GetScriptReply {
ScriptError scriptError;
QByteArray content;
};
explicit ScriptEngine(DeviceManager *deviceManager, QObject *parent = nullptr);
~ScriptEngine();
Scripts scripts();
GetScriptReply scriptContent(const QUuid &id);
AddScriptReply addScript(const QString &name, const QByteArray &content);
ScriptError renameScript(const QUuid &id, const QString &name);
EditScriptReply editScript(const QUuid &id, const QByteArray &content);
ScriptError removeScript(const QUuid &id);
signals:
void scriptAdded(const Script &script);
void scriptRemoved(const QUuid &id);
void scriptChanged(const Script &script);
void scriptRenamed(const Script &script);
void scriptConsoleMessage(const QUuid &scriptId, ScriptMessageType type, const QString &message);
private:
void loadScripts();
bool loadScript(Script *script);
void unloadScript(Script *script);
QString baseName(const QUuid &id);
void onScriptMessage(QtMsgType type, const QMessageLogContext &context, const QString &message);
private:
DeviceManager *m_deviceManager = nullptr;
QQmlEngine *m_engine = nullptr;
QHash<QUuid, Script*> m_scripts;
static QList<ScriptEngine*> s_engines;
static QtMessageHandler s_upstreamMessageHandler;
static QLoggingCategory::CategoryFilter s_oldCategoryFilter;
static void logMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &message);
static void logCategoryFilter(QLoggingCategory *category);
};
}
#endif // SCRIPTENGINE_H

View File

@ -0,0 +1,110 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2019 Michael Zanetti <michael.zanetti@nymea.io> *
* *
* 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 <http://www.gnu.org/licenses/>. *
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "scriptevent.h"
#include <qqml.h>
#include <QQmlEngine>
namespace nymeaserver {
ScriptEvent::ScriptEvent(QObject *parent) : QObject(parent)
{
}
void ScriptEvent::classBegin()
{
m_deviceManager = reinterpret_cast<DeviceManager*>(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 (DeviceId(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;
}
// ScriptParams *params = new ScriptParams(event.params());
// qmlEngine(this)->setObjectOwnership(params, QQmlEngine::JavaScriptOwnership);
QVariantMap params;
foreach (const Param &param, event.params()) {
params.insert(param.paramTypeId().toString().remove(QRegExp("[{}]")), param.value());
QString paramName = device->deviceClass().eventTypes().findById(event.eventTypeId()).paramTypes().findById(param.paramTypeId()).name();
params.insert(paramName, param.value());
}
emit triggered(params);
}
}

View File

@ -0,0 +1,77 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2019 Michael Zanetti <michael.zanetti@nymea.io> *
* *
* 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 <http://www.gnu.org/licenses/>. *
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#ifndef EVENTLISTENER_H
#define EVENTLISTENER_H
#include <QObject>
#include <QUuid>
#include <QQmlParserStatus>
#include "types/event.h"
#include "devices/devicemanager.h"
namespace nymeaserver {
class ScriptParams;
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(ScriptParams *params);
void triggered(const QVariantMap &params);
private:
DeviceManager *m_deviceManager = nullptr;
QString m_deviceId;
QString m_eventTypeId;
QString m_eventName;
};
}
#endif // EVENTLISTENER_H

View File

@ -0,0 +1,205 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2019 Michael Zanetti <michael.zanetti@nymea.io> *
* *
* 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 <http://www.gnu.org/licenses/>. *
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "scriptstate.h"
#include "loggingcategories.h"
#include <QColor>
#include <qqml.h>
#include <QQmlEngine>
namespace nymeaserver {
ScriptState::ScriptState(QObject *parent) : QObject(parent)
{
}
void ScriptState::classBegin()
{
m_deviceManager = reinterpret_cast<DeviceManager*>(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 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();
}
}
QVariant ScriptState::value() const
{
Device* device = m_deviceManager->findConfiguredDevice(DeviceId(m_deviceId));
if (!device) {
return QVariant();
}
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)
{
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;
}
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(actionTypeId));
ParamList params = ParamList() << Param(ParamTypeId(actionTypeId), value);
action.setParams(params);
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);
}
});
}
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();
}
void ScriptState::restore()
{
setValue(m_valueStore);
}
void nymeaserver::ScriptState::onDeviceStateChanged(Device *device, const StateTypeId &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();
}
}
}

View File

@ -0,0 +1,91 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2019 Michael Zanetti <michael.zanetti@nymea.io> *
* *
* 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 <http://www.gnu.org/licenses/>. *
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#ifndef SCRIPTSTATE_H
#define SCRIPTSTATE_H
#include <QObject>
#include <QQmlParserStatus>
#include <QPointer>
#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 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);
void classBegin() override;
void componentComplete() override;
QString deviceId() const;
void setDeviceId(const QString &deviceId);
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 stateTypeChanged();
void valueChanged();
private slots:
void onDeviceStateChanged(Device *device, const StateTypeId &stateTypeId);
private:
DeviceManager *m_deviceManager = nullptr;
QString m_deviceId;
QString m_stateTypeId;
QString m_stateName;
DeviceActionInfo *m_pendingActionInfo = nullptr;
QVariant m_valueCache;
QVariant m_valueStore;
};
}
#endif // SCRIPTSTATE_H

View File

@ -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);

View File

@ -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()) {

View File

@ -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}. */

View File

@ -38,6 +38,7 @@ class HardwareResource;
class HardwareManager : public QObject
{
Q_OBJECT
Q_PROPERTY(PluginTimerManager* pluginTimerManager READ pluginTimerManager CONSTANT)
public:
HardwareManager(QObject *parent = nullptr);

View File

@ -29,6 +29,7 @@ public:
explicit JsonRPCServer() = default;
virtual ~JsonRPCServer() = default;
virtual bool registerHandler(JsonHandler *handler) = 0;
virtual bool registerExperienceHandler(JsonHandler *handler, int majorVersion, int minorVersion) = 0;
};

View File

@ -21,6 +21,9 @@
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "loggingcategories.h"
#include <QFileInfo>
#include <QDir>
#include <QDateTime>
Q_LOGGING_CATEGORY(dcApplication, "Application")
Q_LOGGING_CATEGORY(dcPluginMetadata, "PluginMetadata")
@ -34,6 +37,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")
@ -60,3 +64,92 @@ Q_LOGGING_CATEGORY(dcBluetoothServer, "BluetoothServer")
Q_LOGGING_CATEGORY(dcBluetoothServerTraffic, "BluetoothServerTraffic")
Q_LOGGING_CATEGORY(dcMqtt, "Mqtt")
Q_LOGGING_CATEGORY(dcTranslations, "Translations")
static QFile s_logFile;
static bool s_useColors;
static QList<QtMessageHandler> s_handlers;
static const char *const normal = "\033[0m";
static const char *const warning = "\033[33m";
static const char *const error = "\033[31m";
void nymeaInstallMessageHandler(QtMessageHandler handler)
{
s_handlers.append(handler);
}
void nymeaUninstallMessageHandler(QtMessageHandler handler)
{
s_handlers.removeAll(handler);
}
void nymeaLogMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &message)
{
// Copy message to all installed nymea handlers
foreach (QtMessageHandler handler, s_handlers) {
handler(type, context, message);
}
QString messageString;
QString timeString = QDateTime::currentDateTime().toString("yyyy.MM.dd hh:mm:ss.zzz");
switch (type) {
#if (QT_VERSION >= QT_VERSION_CHECK(5, 5, 0))
case QtInfoMsg:
messageString = QString(" I %1 | %2: %3").arg(timeString).arg(context.category).arg(message);
fprintf(stdout, " I | %s: %s\n", context.category, message.toUtf8().data());
break;
#endif
case QtDebugMsg:
messageString = QString(" I %1 | %2: %3").arg(timeString).arg(context.category).arg(message);
fprintf(stdout, " I | %s: %s\n", context.category, message.toUtf8().data());
break;
case QtWarningMsg:
messageString = QString(" W %1 | %2: %3").arg(timeString).arg(context.category).arg(message);
fprintf(stdout, "%s W | %s: %s%s\n", s_useColors ? warning : "", context.category, message.toUtf8().data(), s_useColors ? normal : "");
break;
case QtCriticalMsg:
messageString = QString(" C %1 | %2: %3").arg(timeString).arg(context.category).arg(message);
fprintf(stdout, "%s C | %s: %s%s\n", s_useColors ? error : "", context.category, message.toUtf8().data(), s_useColors ? error : "");
break;
case QtFatalMsg:
messageString = QString(" F %1 | %2: %3").arg(timeString).arg(context.category).arg(message);
fprintf(stdout, "%s F | %s: %s%s\n", s_useColors ? error : "", context.category, message.toUtf8().data(), s_useColors ? error: "");
break;
}
fflush(stdout);
if (s_logFile.isOpen()) {
QTextStream textStream(&s_logFile);
textStream << messageString << endl;
}
}
bool initLogging(const QString &fileName, bool useColors)
{
s_useColors = useColors;
qInstallMessageHandler(nymeaLogMessageHandler);
if (!fileName.isEmpty()) {
QFileInfo fi(fileName);
QDir dir(fi.absolutePath());
if (!dir.exists() && !dir.mkpath(dir.absolutePath())) {
qWarning() << "Error logfile path:" << fileName;
return false;
}
s_logFile.setFileName(fileName);
if (!s_logFile.open(QFile::WriteOnly | QFile::Append)) {
qWarning() << "Error opening log file:" << fileName;
return false;
}
}
return true;
}
void closeLogFile()
{
if (s_logFile.isOpen()) {
s_logFile.close();
}
}

View File

@ -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)
@ -67,4 +68,23 @@ Q_DECLARE_LOGGING_CATEGORY(dcMqtt)
Q_DECLARE_LOGGING_CATEGORY(dcTranslations)
Q_DECLARE_LOGGING_CATEGORY(dcCoap)
/*
Installs a nymea log message handler in the system.
This is different to the qLogMessageHandler which works like a chain.
In nymea we have the use case that we need to copy log messages
to different places and also dynamically install/uninstall such
handlers.
If you need to copy log messages, use this, if you need to modify log messages
for the entire system (e.g. redirect to a different logging category, the Qt's
mechanism of qInstallMessageHandler() is still available and will always be called
*before* distributing the message to every nymea message handler.
*/
void nymeaInstallMessageHandler(QtMessageHandler handler);
void nymeaUninstallMessageHandler(QtMessageHandler handler);
bool initLogging(const QString &fileName, bool useColors);
void closeLogFile();
#endif // LOGGINGCATEGORYS_H

View File

@ -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

View File

@ -34,8 +34,7 @@
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()); } \
static type##Id create##type##Id() { return type##Id(QUuid::createUuid()); } \
bool operator==(const type##Id &other) const { \
return toString() == other.toString(); \
} \

View File

@ -3,7 +3,7 @@ NYMEA_VERSION_STRING=$$system('dpkg-parsechangelog | sed -n -e "s/^Version: //p"
# define protocol versions
JSON_PROTOCOL_VERSION_MAJOR=4
JSON_PROTOCOL_VERSION_MINOR=0
JSON_PROTOCOL_VERSION_MINOR=1
LIBNYMEA_API_VERSION_MAJOR=4
LIBNYMEA_API_VERSION_MINOR=0
LIBNYMEA_API_VERSION_PATCH=0

View File

@ -44,54 +44,12 @@
#include "nymeaapplication.h"
#include "loggingcategories.h"
static QFile s_logFile;
static const char *const normal = "\033[0m";
static const char *const warning = "\e[33m";
static const char *const error = "\e[31m";
using namespace nymeaserver;
static void consoleLogHandler(QtMsgType type, const QMessageLogContext& context, const QString& message)
{
QString messageString;
QString timeString = QDateTime::currentDateTime().toString("yyyy.MM.dd hh:mm:ss.zzz");
switch (type) {
#if (QT_VERSION >= QT_VERSION_CHECK(5, 5, 0))
case QtInfoMsg:
messageString = QString(" I %1 | %2: %3").arg(timeString).arg(context.category).arg(message);
fprintf(stdout, " I | %s: %s\n", context.category, message.toUtf8().data());
break;
#endif
case QtDebugMsg:
messageString = QString(" I %1 | %2: %3").arg(timeString).arg(context.category).arg(message);
fprintf(stdout, " I | %s: %s\n", context.category, message.toUtf8().data());
break;
case QtWarningMsg:
messageString = QString(" W %1 | %2: %3").arg(timeString).arg(context.category).arg(message);
fprintf(stdout, "%s W | %s: %s%s\n", warning, context.category, message.toUtf8().data(), normal);
break;
case QtCriticalMsg:
messageString = QString(" C %1 | %2: %3").arg(timeString).arg(context.category).arg(message);
fprintf(stdout, "%s C | %s: %s%s\n", error, context.category, message.toUtf8().data(), normal);
break;
case QtFatalMsg:
messageString = QString(" F %1 | %2: %3").arg(timeString).arg(context.category).arg(message);
fprintf(stdout, "%s F | %s: %s%s\n", error, context.category, message.toUtf8().data(), normal);
break;
}
fflush(stdout);
if (s_logFile.isOpen()) {
QTextStream textStream(&s_logFile);
textStream << messageString << endl;
}
}
int main(int argc, char *argv[])
{
qInstallMessageHandler(consoleLogHandler);
NymeaApplication application(argc, argv);
application.setOrganizationName("nymea");
application.setApplicationName("nymead");
@ -145,9 +103,12 @@ int main(int argc, char *argv[])
QCommandLineOption allOption(QStringList() << "p" << "print-all", QCoreApplication::translate("nymea", "Enables all debug categories except *Traffic and *Debug categories. Single debug categories can be disabled again with -d parameter."));
parser.addOption(allOption);
QCommandLineOption logOption({"l", "log"}, QCoreApplication::translate("nymea", "Specify a log file to write to, if this option is not specified, logs will be printed to the standard output."), "logfile", "/var/log/nymead.log");
QCommandLineOption logOption({"l", "log"}, QCoreApplication::translate("nymea", "Specify a log file to write to, if this option is not specified, logs will be printed to the standard output."), "logfile");
parser.addOption(logOption);
QCommandLineOption noColorOption({"c", "no-colors"}, QCoreApplication::translate("nymea", "Log output is colorized by default. Use this option to disable colors."));
parser.addOption(noColorOption);
QCommandLineOption dbusOption(QStringList() << "session", QCoreApplication::translate("nymea", "If specified, all D-Bus interfaces will be bound to the session bus instead of the system bus."));
parser.addOption(dbusOption);
@ -157,21 +118,11 @@ int main(int argc, char *argv[])
parser.process(application);
// Open the logfile, if any specified
if (parser.isSet(logOption)) {
QFileInfo fi(parser.value(logOption));
QDir dir(fi.absolutePath());
if (!dir.exists() && !dir.mkpath(dir.absolutePath())) {
qWarning() << "Error opening log file" << parser.value(logOption);
return 1;
}
s_logFile.setFileName(parser.value(logOption));
if (!s_logFile.open(QFile::WriteOnly | QFile::Append)) {
qWarning() << "Error opening log file" << parser.value(logOption);
return 1;
}
if (!initLogging(parser.value(logOption), !parser.isSet(noColorOption))) {
qWarning() << "Error opening log file" << parser.value(logOption);
return 1;
}
/* The logging rules will be evaluated sequentially
* 1. All debug categories off
* 2. Enable all debug categories if requested from command line (-p)
@ -256,16 +207,12 @@ int main(int argc, char *argv[])
// create core instance
NymeaCore::instance()->init();
int ret = application.exec();
if (s_logFile.isOpen()) {
s_logFile.close();
}
closeLogFile();
return ret;
}
NymeaService service(argc, argv);
int ret = service.exec();
if (s_logFile.isOpen()) {
s_logFile.close();
}
closeLogFile();
return ret;
}

View File

@ -1,4 +1,4 @@
4.0
4.1
{
"enums": {
"BasicType": [
@ -207,6 +207,16 @@
"RuleErrorNoExitActions",
"RuleErrorInterfaceNotFound"
],
"ScriptError": [
"ScriptErrorNoError",
"ScriptErrorScriptNotFound",
"ScriptErrorInvalidScript",
"ScriptErrorHardwareFailure"
],
"ScriptMessageType": [
"ScriptMessageTypeLog",
"ScriptMessageTypeWarning"
],
"SetupMethod": [
"SetupMethodJustAdd",
"SetupMethodDisplayPin",
@ -1223,6 +1233,57 @@
"ruleError": "$ref:RuleError"
}
},
"Scripts.AddScript": {
"description": "Add a script",
"params": {
"content": "String",
"name": "String"
},
"returns": {
"o:errors": "StringList",
"o:script": "$ref:Script",
"scriptError": "$ref:ScriptError"
}
},
"Scripts.EditScript": {
"description": "Edit a script",
"params": {
"id": "Uuid",
"o:content": "String",
"o:name": "String"
},
"returns": {
"o:errors": "StringList",
"scriptError": "$ref:ScriptError"
}
},
"Scripts.GetScriptContent": {
"description": "Get a scripts content.",
"params": {
"id": "Uuid"
},
"returns": {
"o:content": "String",
"scriptError": "$ref:ScriptError"
}
},
"Scripts.GetScripts": {
"description": "Get all script, that is, their names and properties, but no content.",
"params": {
},
"returns": {
"scripts": "$ref:Scripts"
}
},
"Scripts.RemoveScript": {
"description": "remove a script",
"params": {
"id": "Uuid"
},
"returns": {
"scriptError": "$ref:ScriptError"
}
},
"States.GetStateType": {
"deprecated": "Please use the Devices namespace instead.",
"description": "Get the StateType for the given stateTypeId.",
@ -1606,6 +1667,39 @@
"ruleId": "Uuid"
}
},
"Scripts.ScriptAdded": {
"description": "Emitted when a script has been added to the system.",
"params": {
"script": "$ref:Script"
}
},
"Scripts.ScriptChanged": {
"description": "Emitted when a script has been changed in the system (e.g. renamed).",
"params": {
"name": "String",
"scriptId": "Uuid"
}
},
"Scripts.ScriptContentChanged": {
"description": "Emitted when a script's content has been changed in the system.",
"params": {
"scriptId": "Uuid"
}
},
"Scripts.ScriptLogMessage": {
"description": "Emitted when a script produces a console message.",
"params": {
"message": "String",
"scriptId": "Uuid",
"type": "$ref:ScriptMessageType"
}
},
"Scripts.ScriptRemoved": {
"description": "Emitted when a script has been removed from the system.",
"params": {
"id": "Uuid"
}
},
"System.CapabilitiesChanged": {
"description": "Emitted whenever the system capabilities change.",
"params": {
@ -1928,6 +2022,13 @@
"Rules": [
"$ref:Rule"
],
"Script": {
"name": "String",
"r:id": "Uuid"
},
"Scripts": [
"$ref:Script"
],
"ServerConfiguration": {
"address": "String",
"authenticationEnabled": "Bool",

View File

@ -20,4 +20,5 @@ SUBDIRS = versioning \
usermanager \
mqttbroker \
tags \
scripts \

View File

@ -665,7 +665,7 @@ void TestJSONRPC::enableDisableNotifications_legacy()
QStringList expectedNamespaces;
if (enabled == "true") {
expectedNamespaces << "Actions" << "NetworkManager" << "Devices" << "System" << "Rules" << "States" << "Logging" << "Tags" << "JSONRPC" << "Configuration" << "Events";
expectedNamespaces << "Actions" << "NetworkManager" << "Devices" << "System" << "Rules" << "States" << "Logging" << "Tags" << "JSONRPC" << "Configuration" << "Events" << "Scripts";
}
std::sort(expectedNamespaces.begin(), expectedNamespaces.end());

View File

@ -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

View File

@ -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.

View File

@ -0,0 +1,11 @@
include(../../../nymea.pri)
include(../autotests.pri)
TARGET = scripts
SOURCES += testscripts.cpp \
testhelper.cpp
QT += qml
HEADERS += \
testhelper.h

View File

@ -0,0 +1,46 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2019 Michael Zanetti <michael.zanetti@nymea.io> *
* *
* 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 <http://www.gnu.org/licenses/>. *
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "testhelper.h"
TestHelper* TestHelper::s_instance = nullptr;
TestHelper *TestHelper::instance()
{
if (!s_instance) {
s_instance = new TestHelper();
}
return s_instance;
}
void TestHelper::logEvent(const QString &deviceId, const QString &eventId, const QVariantMap &params)
{
emit eventLogged(DeviceId(deviceId), eventId, params);
}
void TestHelper::logStateChange(const QString &deviceId, const QString &stateId, const QVariant &value)
{
emit stateChangeLogged(DeviceId(deviceId), stateId, value);
}
TestHelper::TestHelper(QObject *parent) : QObject(parent)
{
}

View File

@ -0,0 +1,48 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2019 Michael Zanetti <michael.zanetti@nymea.io> *
* *
* 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 <http://www.gnu.org/licenses/>. *
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#ifndef TESTHELPER_H
#define TESTHELPER_H
#include <QObject>
#include "typeutils.h"
class TestHelper : public QObject
{
Q_OBJECT
public:
static TestHelper* instance();
Q_INVOKABLE void logEvent(const QString &deviceId, const QString &eventId, const QVariantMap &params);
Q_INVOKABLE void logStateChange(const QString &deviceId, const QString &stateId, const QVariant &value);
signals:
void setState(const QVariant &value);
void executeAction(const QVariantMap &params);
void eventLogged(const DeviceId &deviceId, const QString &eventId, const QVariantMap &params);
void stateChangeLogged(const DeviceId &deviceId, const QString stateId, const QVariant &value);
private:
explicit TestHelper(QObject *parent = nullptr);
static TestHelper* s_instance;
};
#endif // TESTHELPER_H

View File

@ -0,0 +1,379 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2015 Simon Stürz <simon.stuerz@guh.io> *
* Copyright (C) 2014 Michael Zanetti <michael_zanetti@gmx.net> *
* *
* 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. *
* *
**
* 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 <http://www.gnu.org/licenses/>. *
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "nymeatestbase.h"
#include "testhelper.h"
#include "nymeasettings.h"
#include "nymeacore.h"
#include "scriptengine/scriptengine.h"
#include <QtQml/qqml.h>
using namespace nymeaserver;
static QObject* helperProvider(QQmlEngine *engine, QJSEngine *scriptEngine)
{
Q_UNUSED(engine)
Q_UNUSED(scriptEngine)
return TestHelper::instance();
}
class TestScripts: public NymeaTestBase
{
Q_OBJECT
public:
TestScripts();
private:
private slots:
void init();
void testScriptEventById();
void testScriptEventByName();
void testReadScriptStateById();
void testReadScriptStateByNyme();
void testWriteScriptStateById();
void testWriteScriptStateByName();
void testScriptActionById();
void testScriptActionByName();
void testScriptAlarm_data();
void testScriptAlarm();
};
TestScripts::TestScripts()
{
qmlRegisterSingletonType<TestHelper>("nymea", 1, 0, "TestHelper", &helperProvider);
}
void TestScripts::init()
{
// Make sure no scripts are in the engine when we start a test
foreach (const Script &script, NymeaCore::instance()->scriptEngine()->scripts()) {
NymeaCore::instance()->scriptEngine()->removeScript(script.id());
}
// Set initial state values of mock device
Action action(mockPowerActionTypeId, m_mockDeviceId);
action.setParams(ParamList() << Param(mockPowerActionPowerParamTypeId, false));
NymeaCore::instance()->deviceManager()->executeAction(action);
}
void TestScripts::testScriptEventById()
{
QString script = QString("import QtQuick 2.0\n"
"import nymea 1.0\n"
"Item {\n"
" DeviceEvent {\n"
" deviceId: \"%1\"\n"
" eventTypeId: \"%2\"\n"
" onTriggered: {\n"
" TestHelper.logEvent(deviceId, eventTypeId, params);\n"
" }\n"
" }\n"
"}\n").arg(m_mockDeviceId.toString()).arg(mockPowerEventTypeId.toString());
qCDebug(dcTests()) << "Adding script:\n" << qUtf8Printable(script);
ScriptEngine::AddScriptReply reply = NymeaCore::instance()->scriptEngine()->addScript("TestEvent", script.toUtf8());
QCOMPARE(reply.scriptError, ScriptEngine::ScriptErrorNoError);
QSignalSpy spy(TestHelper::instance(), &TestHelper::eventLogged);
// Generate event by setting state value of powerState
Action action(mockPowerActionTypeId, m_mockDeviceId);
action.setParams(ParamList() << Param(mockPowerActionPowerParamTypeId, true));
NymeaCore::instance()->deviceManager()->executeAction(action);
spy.wait(1);
QCOMPARE(spy.count(), 1);
QCOMPARE(spy.first().at(0).value<DeviceId>(), m_mockDeviceId);
QCOMPARE(EventTypeId(spy.first().at(1).toUuid()), mockPowerEventTypeId);
QVariantMap expectedParams;
expectedParams.insert(mockPowerEventTypeId.toString().remove(QRegExp("[{}]")), true);
expectedParams.insert("power", true);
QCOMPARE(spy.first().at(2).toMap(), expectedParams);
}
void TestScripts::testScriptEventByName()
{
QString script = QString("import QtQuick 2.0\n"
"import nymea 1.0\n"
"Item {\n"
" DeviceEvent {\n"
" deviceId: \"%1\"\n"
" eventName: \"%2\"\n"
" onTriggered: {\n"
" TestHelper.logEvent(deviceId, eventName, params);\n"
" }\n"
" }\n"
"}\n").arg(m_mockDeviceId.toString()).arg("power");
qCDebug(dcTests()) << "Adding script:\n" << qUtf8Printable(script);
ScriptEngine::AddScriptReply reply = NymeaCore::instance()->scriptEngine()->addScript("TestEvent", script.toUtf8());
QCOMPARE(reply.scriptError, ScriptEngine::ScriptErrorNoError);
QSignalSpy spy(TestHelper::instance(), &TestHelper::eventLogged);
// Generate event by setting state value of powerState
Action action(mockPowerActionTypeId, m_mockDeviceId);
action.setParams(ParamList() << Param(mockPowerActionPowerParamTypeId, true));
NymeaCore::instance()->deviceManager()->executeAction(action);
spy.wait(1);
QCOMPARE(spy.count(), 1);
QCOMPARE(spy.first().at(0).value<DeviceId>(), m_mockDeviceId);
QCOMPARE(spy.first().at(1).toString(), QString("power"));
QVariantMap expectedParams;
expectedParams.insert(mockPowerEventTypeId.toString().remove(QRegExp("[{}]")), true);
expectedParams.insert("power", true);
QCOMPARE(spy.first().at(2).toMap(), expectedParams);
}
void TestScripts::testReadScriptStateById()
{
QString script = QString("import QtQuick 2.0\n"
"import nymea 1.0\n"
"Item {\n"
" DeviceState {\n"
" deviceId: \"%1\"\n"
" stateTypeId: \"%2\"\n"
" onValueChanged: {\n"
" TestHelper.logStateChange(deviceId, stateTypeId, value);\n"
" }\n"
" }\n"
"}\n").arg(m_mockDeviceId.toString()).arg(mockPowerStateTypeId.toString());
qCDebug(dcTests()) << "Adding script:\n" << qUtf8Printable(script);
ScriptEngine::AddScriptReply reply = NymeaCore::instance()->scriptEngine()->addScript("TestState", script.toUtf8());
QCOMPARE(reply.scriptError, ScriptEngine::ScriptErrorNoError);
QSignalSpy spy(TestHelper::instance(), &TestHelper::stateChangeLogged);
// Generate state change
Action action(mockPowerActionTypeId, m_mockDeviceId);
action.setParams(ParamList() << Param(mockPowerActionPowerParamTypeId, true));
NymeaCore::instance()->deviceManager()->executeAction(action);
spy.wait(1);
QCOMPARE(spy.count(), 1);
QCOMPARE(spy.first().at(0).value<DeviceId>(), m_mockDeviceId);
QCOMPARE(StateTypeId(spy.first().at(1).toString()), mockPowerStateTypeId);
QCOMPARE(spy.first().at(2).toBool(), true);
}
void TestScripts::testReadScriptStateByNyme()
{
QString script = QString("import QtQuick 2.0\n"
"import nymea 1.0\n"
"Item {\n"
" DeviceState {\n"
" deviceId: \"%1\"\n"
" stateName: \"%2\"\n"
" onValueChanged: {\n"
" TestHelper.logStateChange(deviceId, stateName, value);\n"
" }\n"
" }\n"
"}\n").arg(m_mockDeviceId.toString()).arg("power");
qCDebug(dcTests()) << "Adding script:\n" << qUtf8Printable(script);
ScriptEngine::AddScriptReply reply = NymeaCore::instance()->scriptEngine()->addScript("TestState", script.toUtf8());
QCOMPARE(reply.scriptError, ScriptEngine::ScriptErrorNoError);
QSignalSpy spy(TestHelper::instance(), &TestHelper::stateChangeLogged);
// Generate state change
Action action(mockPowerActionTypeId, m_mockDeviceId);
action.setParams(ParamList() << Param(mockPowerActionPowerParamTypeId, true));
NymeaCore::instance()->deviceManager()->executeAction(action);
spy.wait(1);
QCOMPARE(spy.count(), 1);
QCOMPARE(spy.first().at(0).value<DeviceId>(), m_mockDeviceId);
QCOMPARE(spy.first().at(1).toString(), QString("power"));
QCOMPARE(spy.first().at(2).toBool(), true);
}
void TestScripts::testWriteScriptStateById()
{
QString script = QString("import QtQuick 2.0\n"
"import nymea 1.0\n"
"Item {\n"
" DeviceState {\n"
" id: deviceState\n"
" deviceId: \"%1\"\n"
" stateTypeId: \"%2\"\n"
" }\n"
" Connections {\n"
" target: TestHelper\n"
" onSetState: {\n"
" deviceState.value = value\n"
" }\n"
" }\n"
"}\n").arg(m_mockDeviceId.toString()).arg(mockPowerStateTypeId.toString());
qCDebug(dcTests()) << "Adding script:\n" << qUtf8Printable(script);
ScriptEngine::AddScriptReply reply = NymeaCore::instance()->scriptEngine()->addScript("TestState", script.toUtf8());
QCOMPARE(reply.scriptError, ScriptEngine::ScriptErrorNoError);
QSignalSpy spy(NymeaCore::instance()->deviceManager(), &DeviceManager::deviceStateChanged);
TestHelper::instance()->setState(true);
spy.wait(1);
QCOMPARE(spy.count(), 1);
QCOMPARE(spy.first().at(0).value<Device*>()->id(), m_mockDeviceId);
QCOMPARE(spy.first().at(1).value<StateTypeId>(), mockPowerStateTypeId);
QCOMPARE(spy.first().at(2).toBool(), true);
}
void TestScripts::testWriteScriptStateByName()
{
QString script = QString("import QtQuick 2.0\n"
"import nymea 1.0\n"
"Item {\n"
" DeviceState {\n"
" id: deviceState\n"
" deviceId: \"%1\"\n"
" stateName: \"%2\"\n"
" }\n"
" Connections {\n"
" target: TestHelper\n"
" onSetState: {\n"
" deviceState.value = value\n"
" }\n"
" }\n"
"}\n").arg(m_mockDeviceId.toString()).arg("power");
qCDebug(dcTests()) << "Adding script:\n" << qUtf8Printable(script);
ScriptEngine::AddScriptReply reply = NymeaCore::instance()->scriptEngine()->addScript("TestState", script.toUtf8());
QCOMPARE(reply.scriptError, ScriptEngine::ScriptErrorNoError);
QSignalSpy spy(NymeaCore::instance()->deviceManager(), &DeviceManager::deviceStateChanged);
TestHelper::instance()->setState(true);
spy.wait(1);
QCOMPARE(spy.count(), 1);
QCOMPARE(spy.first().at(0).value<Device*>()->id(), m_mockDeviceId);
QCOMPARE(spy.first().at(1).value<StateTypeId>(), mockPowerStateTypeId);
QCOMPARE(spy.first().at(2).toBool(), true);
}
void TestScripts::testScriptActionById()
{
QString script = QString("import QtQuick 2.0\n"
"import nymea 1.0\n"
"Item {\n"
" DeviceAction {\n"
" id: deviceAction\n"
" deviceId: \"%1\"\n"
" actionTypeId: \"%2\"\n"
" }\n"
" Connections {\n"
" target: TestHelper\n"
" onExecuteAction: {\n"
" deviceAction.execute(params)\n"
" }\n"
" }\n"
"}\n").arg(m_mockDeviceId.toString()).arg(mockPowerActionTypeId.toString());
qCDebug(dcTests()) << "Adding script:\n" << qUtf8Printable(script);
ScriptEngine::AddScriptReply reply = NymeaCore::instance()->scriptEngine()->addScript("TestAction", script.toUtf8());
QCOMPARE(reply.scriptError, ScriptEngine::ScriptErrorNoError);
QSignalSpy spy(NymeaCore::instance()->deviceManager(), &DeviceManager::deviceStateChanged);
QVariantMap params;
params.insert(mockPowerActionPowerParamTypeId.toString(), true);
TestHelper::instance()->executeAction(params);
spy.wait(1);
QCOMPARE(spy.count(), 1);
QCOMPARE(spy.first().at(0).value<Device*>()->id(), m_mockDeviceId);
QCOMPARE(spy.first().at(1).value<StateTypeId>(), mockPowerStateTypeId);
QCOMPARE(spy.first().at(2).toBool(), true);
}
void TestScripts::testScriptActionByName()
{
QString script = QString("import QtQuick 2.0\n"
"import nymea 1.0\n"
"Item {\n"
" DeviceAction {\n"
" id: deviceAction\n"
" deviceId: \"%1\"\n"
" actionName: \"%2\"\n"
" }\n"
" Connections {\n"
" target: TestHelper\n"
" onExecuteAction: {\n"
" deviceAction.execute(params)\n"
" }\n"
" }\n"
"}\n").arg(m_mockDeviceId.toString()).arg("power");
qCDebug(dcTests()) << "Adding script:\n" << qUtf8Printable(script);
ScriptEngine::AddScriptReply reply = NymeaCore::instance()->scriptEngine()->addScript("TestAction", script.toUtf8());
QCOMPARE(reply.scriptError, ScriptEngine::ScriptErrorNoError);
QSignalSpy spy(NymeaCore::instance()->deviceManager(), &DeviceManager::deviceStateChanged);
QVariantMap params;
params.insert("power", true);
TestHelper::instance()->executeAction(params);
spy.wait(1);
QCOMPARE(spy.count(), 1);
QCOMPARE(spy.first().at(0).value<Device*>()->id(), m_mockDeviceId);
QCOMPARE(spy.first().at(1).value<StateTypeId>(), mockPowerStateTypeId);
QCOMPARE(spy.first().at(2).toBool(), true);
}
void TestScripts::testScriptAlarm_data()
{
QTest::addColumn<QTime>("time");
QTest::addColumn<QTime>("endTime");
QTest::addColumn<bool>("active");
QTest::newRow("active, regular") << QTime(12, 05);
}
void TestScripts::testScriptAlarm()
{
}
#include "testscripts.moc"
QTEST_MAIN(TestScripts)

View File

@ -6,14 +6,14 @@ if [ -z $1 ]; then
fi
if [ -z $2 ]; then
cat <<EOD | nc $1 2222 | jq
cat <<EOD | nc $1 2222
{"id":0, "method":"JSONRPC.Hello"}
{"id":1, "method":"Devices.GetConfiguredDevices"}
EOD
exit 0
fi
cat <<EOD | nc $1 2222 | jq
cat <<EOD | nc $1 2222
{"id":0, "method":"JSONRPC.Hello"}
{"id":1, "token": "$2", "method":"Devices.GetConfiguredDevices"}
EOD

View File

@ -2,6 +2,10 @@
if [ -z $2 ]; then
echo "usage: $0 host deviceClassId"
else
(echo '{"id":1, "method":"Devices.GetStateTypes", "params":{"deviceClassId":"'$2'"}}'; sleep 1) | nc $1 2222
exit 1
fi
cat << EOD | nc $1 2222
{"id":0, "method":"JSONRPC.Hello"}
{"id":1, "method":"Devices.GetStateTypes", "params":{"deviceClassId":"$2"}}
EOD