Merge PR #231: Scriptengine
This commit is contained in:
commit
d2c02d78bc
4
debian/changelog
vendored
4
debian/changelog
vendored
@ -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
20
debian/control
vendored
@ -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}),
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
255
libnymea-core/devices/scriptdeviceplugin.cpp
Normal file
255
libnymea-core/devices/scriptdeviceplugin.cpp
Normal 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();
|
||||
}
|
||||
}
|
||||
183
libnymea-core/devices/scriptdeviceplugin.h
Normal file
183
libnymea-core/devices/scriptdeviceplugin.h
Normal 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 ¶ms = QVariantList(), const QUuid &parentDeviceId = QUuid()) {
|
||||
ParamList paramList;
|
||||
for (int i = 0; i < params.count(); i++) {
|
||||
paramList << Param(params.at(i).toMap().value("paramTypeId").toUuid(), params.at(i).toMap().value("value"));
|
||||
}
|
||||
DeviceDescriptor d(deviceClassId, title, description, parentDeviceId);
|
||||
d.setParams(paramList);
|
||||
m_info->addDeviceDescriptor(d);
|
||||
}
|
||||
Q_INVOKABLE void finish(Device::DeviceError status = Device::DeviceErrorNoError, const QString &displayMessage = QString()) {
|
||||
m_info->finish(status, displayMessage);
|
||||
}
|
||||
signals:
|
||||
void aborted();
|
||||
void finished();
|
||||
private:
|
||||
DeviceDiscoveryInfo *m_info = nullptr;
|
||||
};
|
||||
|
||||
class ScriptDevice: public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
|
||||
public:
|
||||
ScriptDevice(Device *device): QObject(device), m_device(device) {
|
||||
connect(device, &Device::nameChanged, this, &ScriptDevice::nameChanged);
|
||||
}
|
||||
|
||||
QString name() const { return m_device->name(); }
|
||||
void setName(const QString &name) { m_device->setName(name); }
|
||||
|
||||
Q_INVOKABLE QVariant paramValue(const QUuid ¶mTypeId) { return m_device->paramValue(paramTypeId); }
|
||||
Q_INVOKABLE void setParamValue(const QUuid ¶mTypeId, const QVariant &value) { m_device->setParamValue(paramTypeId, value); }
|
||||
|
||||
Q_INVOKABLE QVariant stateValue(const QUuid &stateTypeId) { return m_device->stateValue(stateTypeId); }
|
||||
Q_INVOKABLE void setStateValue(const QUuid &stateTypeId, const QVariant &value) { m_device->setStateValue(stateTypeId, value); }
|
||||
|
||||
signals:
|
||||
void nameChanged();
|
||||
|
||||
private:
|
||||
Device *m_device = nullptr;
|
||||
};
|
||||
|
||||
class ScriptDeviceSetupInfo: public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(ScriptDevice* device READ device CONSTANT)
|
||||
public:
|
||||
ScriptDeviceSetupInfo(DeviceSetupInfo *info, ScriptDevice *scriptDevice): QObject(info), m_info(info), m_device(scriptDevice) {
|
||||
connect(info, &DeviceSetupInfo::aborted, this, &ScriptDeviceSetupInfo::aborted);
|
||||
connect(info, &DeviceSetupInfo::finished, this, &ScriptDeviceSetupInfo::finished);
|
||||
}
|
||||
Q_INVOKABLE void finish(Device::DeviceError status = Device::DeviceErrorNoError, const QString &displayMessage = QString()) {
|
||||
m_info->finish(status, displayMessage);
|
||||
}
|
||||
ScriptDevice* device() const { return m_device; }
|
||||
signals:
|
||||
void aborted();
|
||||
void finished();
|
||||
private:
|
||||
DeviceSetupInfo *m_info = nullptr;
|
||||
ScriptDevice *m_device = nullptr;
|
||||
};
|
||||
|
||||
class ScriptDevicePairingInfo: public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QUuid deviceClassId READ deviceClassId CONSTANT)
|
||||
Q_PROPERTY(QUuid deviceId READ deviceId CONSTANT)
|
||||
Q_PROPERTY(QString deviceName READ deviceName CONSTANT)
|
||||
Q_PROPERTY(QUuid parentDeviceId READ parentDeviceId CONSTANT)
|
||||
Q_PROPERTY(QUrl oAuthUrl READ oAuthUrl WRITE setOAuthUrl)
|
||||
public:
|
||||
ScriptDevicePairingInfo(DevicePairingInfo* info): QObject(info), m_info(info) {
|
||||
connect(info, &DevicePairingInfo::aborted, this, &ScriptDevicePairingInfo::aborted);
|
||||
connect(info, &DevicePairingInfo::finished, this, &ScriptDevicePairingInfo::finished);
|
||||
}
|
||||
Q_INVOKABLE QVariant paramValue(const QUuid ¶mTypeId) { return m_info->params().paramValue(paramTypeId); }
|
||||
Q_INVOKABLE void finish(Device::DeviceError status = Device::DeviceErrorNoError, const QString &displayMessage = QString()) {
|
||||
m_info->finish(status, displayMessage);
|
||||
}
|
||||
QUuid deviceClassId() const { return m_info->deviceClassId(); }
|
||||
QUuid deviceId() const { return m_info->deviceId(); }
|
||||
QString deviceName() const { return m_info->deviceName(); }
|
||||
QUuid parentDeviceId() const { return m_info->parentDeviceId(); }
|
||||
QUrl oAuthUrl() const { return m_info->oAuthUrl(); }
|
||||
void setOAuthUrl(const QUrl &oAuthUrl) { m_info->setOAuthUrl(oAuthUrl); }
|
||||
signals:
|
||||
void aborted();
|
||||
void finished();
|
||||
private:
|
||||
DevicePairingInfo *m_info = nullptr;
|
||||
};
|
||||
|
||||
class ScriptDeviceActionInfo: public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(ScriptDevice* device READ device CONSTANT)
|
||||
Q_PROPERTY(QUuid actionTypeId READ actionTypeId CONSTANT)
|
||||
public:
|
||||
ScriptDeviceActionInfo(DeviceActionInfo* info, ScriptDevice* scriptDevice): QObject(info), m_info(info), m_device(scriptDevice) {
|
||||
connect(info, &DeviceActionInfo::finished, this, &ScriptDeviceActionInfo::finished);
|
||||
connect(info, &DeviceActionInfo::aborted, this, &ScriptDeviceActionInfo::aborted);
|
||||
}
|
||||
ScriptDevice* device() const { return m_device; }
|
||||
QUuid actionTypeId() const { return m_info->action().actionTypeId(); }
|
||||
Q_INVOKABLE QVariant paramValue(const QUuid ¶mTypeId) { return m_info->action().params().paramValue(paramTypeId); }
|
||||
Q_INVOKABLE void finish(Device::DeviceError status = Device::DeviceErrorNoError, const QString &displayMessage = QString()) {
|
||||
m_info->finish(status, displayMessage);
|
||||
}
|
||||
|
||||
signals:
|
||||
void aborted();
|
||||
void finished();
|
||||
private:
|
||||
DeviceActionInfo* m_info = nullptr;
|
||||
ScriptDevice* m_device = nullptr;
|
||||
};
|
||||
|
||||
class ScriptDevicePlugin : public DevicePlugin
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ScriptDevicePlugin(QObject *parent = nullptr);
|
||||
|
||||
bool loadScript(const QString &fileName);
|
||||
QJsonObject metaData() const;
|
||||
|
||||
void init() override;
|
||||
void startMonitoringAutoDevices() override;
|
||||
void discoverDevices(DeviceDiscoveryInfo *info) override;
|
||||
void startPairing(DevicePairingInfo *info) override;
|
||||
void confirmPairing(DevicePairingInfo *info, const QString &username, const QString &secret) override;
|
||||
void setupDevice(DeviceSetupInfo *info) override;
|
||||
void postSetupDevice(Device *device) override;
|
||||
void deviceRemoved(Device *device) override;
|
||||
void executeAction(DeviceActionInfo *info) override;
|
||||
|
||||
private:
|
||||
QQmlEngine *m_engine = nullptr;
|
||||
QJsonObject m_metaData;
|
||||
QJSValue m_pluginImport;
|
||||
QHash<Device*, ScriptDevice*> m_devices;
|
||||
};
|
||||
|
||||
#endif // SCRIPTDEVICEPLUGIN_H
|
||||
@ -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"
|
||||
|
||||
@ -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 ¶ms = QVariantMap(), const QString &deprecationWarning = QString());
|
||||
|
||||
214
libnymea-core/jsonrpc/scriptshandler.cpp
Normal file
214
libnymea-core/jsonrpc/scriptshandler.cpp
Normal 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 ¶ms)
|
||||
{
|
||||
Q_UNUSED(params)
|
||||
|
||||
QVariantMap returns;
|
||||
returns.insert("scripts", pack(m_engine->scripts()));
|
||||
return createReply(returns);
|
||||
}
|
||||
|
||||
JsonReply *ScriptsHandler::GetScriptContent(const QVariantMap ¶ms)
|
||||
{
|
||||
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 ¶ms)
|
||||
{
|
||||
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 ¶ms)
|
||||
{
|
||||
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 ¶ms)
|
||||
{
|
||||
QUuid scriptId = params.value("id").toUuid();
|
||||
ScriptEngine::ScriptError status = m_engine->removeScript(scriptId);
|
||||
QVariantMap returns;
|
||||
returns.insert("scriptError", enumValueName(status));
|
||||
return createReply(returns);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
59
libnymea-core/jsonrpc/scriptshandler.h
Normal file
59
libnymea-core/jsonrpc/scriptshandler.h
Normal 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 ¶ms);
|
||||
JsonReply* GetScriptContent(const QVariantMap ¶ms);
|
||||
JsonReply* AddScript(const QVariantMap ¶ms);
|
||||
JsonReply* EditScript(const QVariantMap ¶ms);
|
||||
JsonReply* RemoveScript(const QVariantMap ¶ms);
|
||||
|
||||
signals:
|
||||
void ScriptAdded(const QVariantMap ¶ms);
|
||||
void ScriptRemoved(const QVariantMap ¶ms);
|
||||
void ScriptChanged(const QVariantMap ¶ms);
|
||||
void ScriptContentChanged(const QVariantMap ¶ms);
|
||||
void ScriptLogMessage(const QVariantMap ¶ms);
|
||||
|
||||
private:
|
||||
ScriptEngine *m_engine = nullptr;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // SCRIPTSHANDLER_H
|
||||
@ -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 \
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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;
|
||||
|
||||
71
libnymea-core/scriptengine/script.cpp
Normal file
71
libnymea-core/scriptengine/script.cpp
Normal 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>());
|
||||
}
|
||||
|
||||
}
|
||||
73
libnymea-core/scriptengine/script.h
Normal file
73
libnymea-core/scriptengine/script.h
Normal 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
|
||||
125
libnymea-core/scriptengine/scriptaction.cpp
Normal file
125
libnymea-core/scriptengine/scriptaction.cpp
Normal 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 ¶ms)
|
||||
{
|
||||
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 ¶mNameOrId, 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);
|
||||
}
|
||||
|
||||
}
|
||||
68
libnymea-core/scriptengine/scriptaction.h
Normal file
68
libnymea-core/scriptengine/scriptaction.h
Normal 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 ¶ms);
|
||||
|
||||
signals:
|
||||
void deviceIdChanged();
|
||||
void actionTypeIdChanged();
|
||||
void actionNameChanged();
|
||||
|
||||
public:
|
||||
DeviceManager *m_deviceManager = nullptr;
|
||||
QString m_deviceId;
|
||||
QString m_actionTypeId;
|
||||
QString m_actionName;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // SCRIPTACTION_H
|
||||
144
libnymea-core/scriptengine/scriptalarm.cpp
Normal file
144
libnymea-core/scriptengine/scriptalarm.cpp
Normal 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();
|
||||
}
|
||||
}
|
||||
90
libnymea-core/scriptengine/scriptalarm.h
Normal file
90
libnymea-core/scriptengine/scriptalarm.h
Normal 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
|
||||
466
libnymea-core/scriptengine/scriptengine.cpp
Normal file
466
libnymea-core/scriptengine/scriptengine.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
108
libnymea-core/scriptengine/scriptengine.h
Normal file
108
libnymea-core/scriptengine/scriptengine.h
Normal 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
|
||||
110
libnymea-core/scriptengine/scriptevent.cpp
Normal file
110
libnymea-core/scriptengine/scriptevent.cpp
Normal 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 ¶m, 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
77
libnymea-core/scriptengine/scriptevent.h
Normal file
77
libnymea-core/scriptengine/scriptevent.h
Normal 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 ¶ms);
|
||||
|
||||
private:
|
||||
DeviceManager *m_deviceManager = nullptr;
|
||||
|
||||
QString m_deviceId;
|
||||
QString m_eventTypeId;
|
||||
QString m_eventName;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // EVENTLISTENER_H
|
||||
205
libnymea-core/scriptengine/scriptstate.cpp
Normal file
205
libnymea-core/scriptengine/scriptstate.cpp
Normal 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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
91
libnymea-core/scriptengine/scriptstate.h
Normal file
91
libnymea-core/scriptengine/scriptstate.h
Normal 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
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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()) {
|
||||
|
||||
@ -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}. */
|
||||
|
||||
@ -38,6 +38,7 @@ class HardwareResource;
|
||||
class HardwareManager : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(PluginTimerManager* pluginTimerManager READ pluginTimerManager CONSTANT)
|
||||
|
||||
public:
|
||||
HardwareManager(QObject *parent = nullptr);
|
||||
|
||||
@ -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;
|
||||
|
||||
};
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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(); \
|
||||
} \
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -20,4 +20,5 @@ SUBDIRS = versioning \
|
||||
usermanager \
|
||||
mqttbroker \
|
||||
tags \
|
||||
scripts \
|
||||
|
||||
|
||||
@ -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());
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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.
|
||||
|
||||
11
tests/auto/scripts/scripts.pro
Normal file
11
tests/auto/scripts/scripts.pro
Normal file
@ -0,0 +1,11 @@
|
||||
include(../../../nymea.pri)
|
||||
include(../autotests.pri)
|
||||
|
||||
TARGET = scripts
|
||||
SOURCES += testscripts.cpp \
|
||||
testhelper.cpp
|
||||
|
||||
QT += qml
|
||||
|
||||
HEADERS += \
|
||||
testhelper.h
|
||||
46
tests/auto/scripts/testhelper.cpp
Normal file
46
tests/auto/scripts/testhelper.cpp
Normal 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 ¶ms)
|
||||
{
|
||||
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)
|
||||
{
|
||||
|
||||
}
|
||||
48
tests/auto/scripts/testhelper.h
Normal file
48
tests/auto/scripts/testhelper.h
Normal 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 ¶ms);
|
||||
Q_INVOKABLE void logStateChange(const QString &deviceId, const QString &stateId, const QVariant &value);
|
||||
|
||||
signals:
|
||||
void setState(const QVariant &value);
|
||||
void executeAction(const QVariantMap ¶ms);
|
||||
|
||||
void eventLogged(const DeviceId &deviceId, const QString &eventId, const QVariantMap ¶ms);
|
||||
void stateChangeLogged(const DeviceId &deviceId, const QString stateId, const QVariant &value);
|
||||
private:
|
||||
explicit TestHelper(QObject *parent = nullptr);
|
||||
static TestHelper* s_instance;
|
||||
};
|
||||
|
||||
#endif // TESTHELPER_H
|
||||
379
tests/auto/scripts/testscripts.cpp
Normal file
379
tests/auto/scripts/testscripts.cpp
Normal 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)
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
Reference in New Issue
Block a user