diff --git a/doc/images/Rules_definition.svg b/doc/images/Rules_definition.svg
new file mode 100644
index 00000000..4a652ef6
--- /dev/null
+++ b/doc/images/Rules_definition.svg
@@ -0,0 +1,692 @@
+
+
+
+
diff --git a/doc/rules.qdoc b/doc/rules.qdoc
index 010027e2..c63f24f8 100644
--- a/doc/rules.qdoc
+++ b/doc/rules.qdoc
@@ -3,5 +3,92 @@
\title Rules
\annotatedlist rules
+
+ \section1 Introduction
+
+ Rules define all the logic in guh.
+ A rule is composed by a set of \l{Event}{Events}, \l{State}{States} and \l{Action}{Actions}.
+
+ This diagram shows how rules are composed and interpreted by
+ the \l{RuleEngine}.
+
+ \image Rules_definition.svg "Rules definition"
+
+ A basic concept of a \l{Rule} is the trigger path. This is the path that defines when a \l{Rule}
+ is triggered for evaluation. Each element added on a trigger path (either red or blue) has the possibility to trigger the
+ \l{RuleEngine} to evaluate the complete rule and ultimately execute the defined \l{Action}{Actions} if the
+ whole rule evaluates to true. Once the trigger path has been left (i.e. a black arrow is followed),
+ subsequent elements will still be evaluated when the rule is triggered, but they won't trigger the
+ evaluation themselves.
+
+ There are two mechanisms that can trigger a rule, described by the two trigger paths in the Rules definition figure.
+ \list
+ \li State bindings based
+ \li Event based
+ \endlist
+
+ \section2 Event based rules
+
+ Event based rules (trigger path 1) contain either one or more \l{Event}{Events} or are triggered by a \l{State} change (disregarding what
+ the changed State's value actually is). Such rules may still evaluate \l{States}{State} for a certain value (leaving
+ Trigger path 1 and re-entering Trigger path 2), however, unless
+ otherwise explicitly defined, the Rule is not evaluated when such a State change happens (given the Trigger path has been
+ left by crossing a black arrow). Those rules are only executed for items on trigger path 1.
+
+ \section3 Examples
+
+ \list
+ \li This rule will be evaluated and executed whenever Remote_button1 is pressed:
+
+ \tt {\span {id="color-blue"} {Event}} \tt {\span {id="color-black"} {-> Action}}
+
+ \li This rule will be evaluated and executed whenever Remote_button1 is pressed or or Motion sensor 1 triggers.
+
+ \tt {\span {id="color-blue"} {Event | Event}} \tt {\span {id="color-black"} {-> Action}}
+
+ \li This rule will be evaluated and executed whenever Remote_button1 is pressed or the temperature changes.
+
+ \tt {\span {id="color-blue"} {Event | State}} \tt {\span {id="color-black"} {-> Action}}
+ \endlist
+
+ \section2 State bindings based rules
+
+ State binding rules (Trigger path 2) are rules which only contains \l{State}{States} and \l{Action}{Actions}. Each time
+ a \l{State} on that path changes, the rule is evaluated. That means, all the States in the Rule are examined. If all
+ evaluations are fulfilled, the Rule's \l{Actions}{Action} are executed. Please note, that such Rules may only check
+ if states are equal to, unequal, less than or greater then some value.
+
+ \section3 Examples
+
+ \list
+ \li This rule will be evaluated whenever the temperature changes and executed when the temperature equals 10.
+
+ \tt {\span {id="color-red"} {State==10}} \tt {\span {id="color-black"} {-> Action}}
+
+ \li This rule will be evaluated whenever the temperature changes or the Light sensor changes and executed when the temperature is greater 20 and the light sensor reports values greater 10.
+
+ \tt {\span {id="color-blue"} {State >20 & State >10}} \tt {\span {id="color-black"} {-> Action}}
+
+ \li This rule will be evaluated whenever the temperature changes or the Light sensor changes and executed when the temperature is greater 20 or the light sensor reports values greater 10..
+
+ \tt {\span {id="color-blue"} {State >20 | State >10}} \tt {\span {id="color-black"} {-> Action}}
+ \endlist
+
+ \section2 Mixing rules types
+
+ Having both types of triggers in a single rule is only possible by leaving the trigger path 1, crossing a black arrow and
+ re-entering trigger path 2. This however, converts the rule to an Event based rule which means, only the elements
+ on trigger path 1 will be able to trigger the rule evaluation.
+
+ \section3 Examples
+
+ \list
+ \li This rule will be evaluated whenever the remote button1 is pressed and executed only if the temperature is greater 10.
+
+ \tt {\span {id="color-blue"} {Event==10}} \tt {\span {id="color-black"} {->}} \tt {\span {id="color-red"} {State >10}} \tt {\span {id="color-black"} {-> Action}}
+
+ \endlist
+
+
*/
diff --git a/doc/style.css b/doc/style.css
index 18e37f2a..01562ff2 100644
--- a/doc/style.css
+++ b/doc/style.css
@@ -673,3 +673,6 @@
}
}
/* end of print media */
+
+#color-blue { color: blue }
+#color-red { color: red }
diff --git a/libguh/device.cpp b/libguh/device.cpp
index 4a421fc6..3534c057 100644
--- a/libguh/device.cpp
+++ b/libguh/device.cpp
@@ -124,6 +124,7 @@ void Device::setStateValue(const QUuid &stateTypeId, const QVariant &value)
State newState(stateTypeId, m_id);
newState.setValue(value);
m_states[i] = newState;
+ emit stateValueChanged(stateTypeId, value);
return;
}
}
diff --git a/libguh/device.h b/libguh/device.h
index 662f6f4a..9c822e80 100644
--- a/libguh/device.h
+++ b/libguh/device.h
@@ -48,6 +48,9 @@ public:
QVariant stateValue(const QUuid &stateTypeId) const;
void setStateValue(const QUuid &stateTypeId, const QVariant &value);
+signals:
+ void stateValueChanged(const QUuid &stateTypeId, const QVariant &value);
+
private:
Device(const QUuid &pluginId, const QUuid &id, const QUuid &deviceClassId, QObject *parent = 0);
Device(const QUuid &pluginId, const QUuid &deviceClassId, QObject *parent = 0);
diff --git a/libguh/devicemanager.cpp b/libguh/devicemanager.cpp
index 174ea396..a085f1f1 100644
--- a/libguh/devicemanager.cpp
+++ b/libguh/devicemanager.cpp
@@ -289,6 +289,15 @@ void DeviceManager::storeConfiguredDevices()
}
}
+void DeviceManager::slotDeviceStateValueChanged(const QUuid &stateTypeId, const QVariant &value)
+{
+ Device *device = qobject_cast(sender());
+ if (!device) {
+ return;
+ }
+ emit deviceStateChanged(device, stateTypeId, value);
+}
+
void DeviceManager::radio433SignalReceived(QList rawData)
{
foreach (Device *device, m_configuredDevices) {
@@ -344,5 +353,6 @@ bool DeviceManager::setupDevice(Device *device)
return false;
}
+ connect(device, SIGNAL(stateValueChanged(QUuid,QVariant)), this, SLOT(slotDeviceStateValueChanged(QUuid,QVariant)));
return true;
}
diff --git a/libguh/devicemanager.h b/libguh/devicemanager.h
index 9e080159..e72bbe17 100644
--- a/libguh/devicemanager.h
+++ b/libguh/devicemanager.h
@@ -69,6 +69,7 @@ public:
signals:
void loaded();
void emitEvent(const Event &event);
+ void deviceStateChanged(Device *device, const QUuid &stateTypeId, const QVariant &value);
public slots:
DeviceError executeAction(const Action &action);
@@ -78,6 +79,9 @@ private slots:
void loadConfiguredDevices();
void storeConfiguredDevices();
+ // Only connect this to Devices. It will query the sender()
+ void slotDeviceStateValueChanged(const QUuid &stateTypeId, const QVariant &value);
+
void radio433SignalReceived(QList rawData);
void timerEvent();
diff --git a/plugins/deviceplugins/mock/devicepluginmock.cpp b/plugins/deviceplugins/mock/devicepluginmock.cpp
index f8c07020..4986eb06 100644
--- a/plugins/deviceplugins/mock/devicepluginmock.cpp
+++ b/plugins/deviceplugins/mock/devicepluginmock.cpp
@@ -137,7 +137,13 @@ void DevicePluginMock::executeAction(Device *device, const Action &action)
void DevicePluginMock::setState(const QUuid &stateTypeId, const QVariant &value)
{
- qDebug() << "should set state" << stateTypeId << value;
+ HttpDaemon *daemon = qobject_cast(sender());
+ if (!daemon) {
+ return;
+ }
+
+ Device *device = m_daemons.key(daemon);
+ device->setStateValue(stateTypeId, value);
}
void DevicePluginMock::triggerEvent(const QUuid &id)
diff --git a/server/guhcore.cpp b/server/guhcore.cpp
index 635f454a..3edc328d 100644
--- a/server/guhcore.cpp
+++ b/server/guhcore.cpp
@@ -31,6 +31,7 @@
#include "jsonrpcserver.h"
#include "devicemanager.h"
#include "ruleengine.h"
+#include "device.h"
#include
@@ -78,15 +79,21 @@ GuhCore::GuhCore(QObject *parent) :
qDebug() << "*****************************************";
m_jsonServer = new JsonRPCServer(this);
- connect(m_deviceManager, &DeviceManager::emitEvent, this, &GuhCore::gotSignal);
+ connect(m_deviceManager, &DeviceManager::emitEvent, this, &GuhCore::gotEvent);
+ connect(m_deviceManager, &DeviceManager::deviceStateChanged, this, &GuhCore::deviceStateChanged);
}
/*! Connected to the DeviceManager's emitEvent signal. Events received in
here will be evaluated by the \l{RuleEngine} and the according \l{Action}{Actions} are executed.*/
-void GuhCore::gotSignal(const Event &event)
+void GuhCore::gotEvent(const Event &event)
{
foreach (const Action &action, m_ruleEngine->evaluateEvent(event)) {
m_deviceManager->executeAction(action);
}
}
+
+void GuhCore::deviceStateChanged(Device *device, const QUuid &stateTypeId, const QVariant &value)
+{
+ m_jsonServer->emitStateChangeNotification(device, stateTypeId, value);
+}
diff --git a/server/guhcore.h b/server/guhcore.h
index 3a86c932..669e5702 100644
--- a/server/guhcore.h
+++ b/server/guhcore.h
@@ -27,6 +27,7 @@
class JsonRPCServer;
class DeviceManager;
class RuleEngine;
+class Device;
class GuhCore : public QObject
{
@@ -46,7 +47,8 @@ private:
RuleEngine *m_ruleEngine;
private slots:
- void gotSignal(const Event &event);
+ void gotEvent(const Event &event);
+ void deviceStateChanged(Device *device, const QUuid &stateTypeId, const QVariant &value);
};
diff --git a/server/jsonrpc/jsonrpcserver.cpp b/server/jsonrpc/jsonrpcserver.cpp
index 573a46c3..740a12fe 100644
--- a/server/jsonrpc/jsonrpcserver.cpp
+++ b/server/jsonrpc/jsonrpcserver.cpp
@@ -85,6 +85,23 @@ JsonRPCServer::JsonRPCServer(QObject *parent):
registerHandler(new RulesHandler(this));
}
+void JsonRPCServer::emitStateChangeNotification(Device *device, const QUuid &stateTypeId, const QVariant &value)
+{
+ QVariantMap notification;
+ notification.insert("notification", "Device.StateChanged");
+
+ QVariantMap params;
+ params.insert("deviceId", device->id());
+ params.insert("stateTypeId", stateTypeId);
+ params.insert("value", value);
+
+ notification.insert("params", params);
+
+ QJsonDocument jsonDoc = QJsonDocument::fromVariant(notification);
+
+ m_tcpServer->sendData(m_clients.keys(true), jsonDoc.toJson());
+}
+
QString JsonRPCServer::name() const
{
return QStringLiteral("JSONRPC");
@@ -92,6 +109,8 @@ QString JsonRPCServer::name() const
QVariantMap JsonRPCServer::Introspect(const QVariantMap ¶ms) const
{
+ Q_UNUSED(params)
+
QVariantMap data;
data.insert("types", JsonTypes::allTypes());
QVariantMap methods;
@@ -105,6 +124,8 @@ QVariantMap JsonRPCServer::Introspect(const QVariantMap ¶ms) const
QVariantMap JsonRPCServer::Version(const QVariantMap ¶ms) const
{
+ Q_UNUSED(params)
+
QVariantMap data;
data.insert("version", "0.0.0");
return data;
@@ -137,12 +158,14 @@ void JsonRPCServer::processData(const QUuid &clientId, const QByteArray &jsonDat
int commandId = message.value("id").toInt(&success);
if (!success) {
qWarning() << "Error parsing command. Missing \"id\":" << jsonData;
+ sendErrorResponse(clientId, commandId, "Error parsing command. Missing 'id'");
return;
}
QStringList commandList = message.value("method").toString().split('.');
if (commandList.count() != 2) {
qWarning() << "Error parsing method.\nGot:" << message.value("method").toString() << "\nExpected: \"Namespace.method\"";
+ sendErrorResponse(clientId, commandId, QString("Error parsing method. Got: '%1'', Expected: 'Namespace.method'").arg(message.value("method").toString()));
return;
}
diff --git a/server/jsonrpc/jsonrpcserver.h b/server/jsonrpc/jsonrpcserver.h
index ef0974fe..af2b6206 100644
--- a/server/jsonrpc/jsonrpcserver.h
+++ b/server/jsonrpc/jsonrpcserver.h
@@ -41,6 +41,8 @@ class JsonRPCServer: public JsonHandler
public:
JsonRPCServer(QObject *parent = 0);
+ void emitStateChangeNotification(Device *device, const QUuid &stateTypeId, const QVariant &value);
+
// JsonHandler API implementation
QString name() const;
Q_INVOKABLE QVariantMap Introspect(const QVariantMap ¶ms) const;
diff --git a/tests/auto/mocktcpserver.cpp b/tests/auto/mocktcpserver.cpp
index afdda221..172b4be3 100644
--- a/tests/auto/mocktcpserver.cpp
+++ b/tests/auto/mocktcpserver.cpp
@@ -18,6 +18,8 @@
#include "mocktcpserver.h"
+#include
+
QList MockTcpServer::s_allServers;
MockTcpServer::MockTcpServer(QObject *parent):
@@ -36,6 +38,13 @@ void MockTcpServer::sendData(const QUuid &clientId, const QByteArray &data)
emit outgoingData(clientId, data);
}
+void MockTcpServer::sendData(const QList &clients, const QByteArray &data)
+{
+ foreach (const QUuid &clientId, clients) {
+ sendData(clientId, data);
+ }
+}
+
QList MockTcpServer::servers()
{
return s_allServers;
diff --git a/tests/auto/mocktcpserver.h b/tests/auto/mocktcpserver.h
index 1f85aba5..cd68c27a 100644
--- a/tests/auto/mocktcpserver.h
+++ b/tests/auto/mocktcpserver.h
@@ -31,6 +31,7 @@ public:
~MockTcpServer();
void sendData(const QUuid &clientId, const QByteArray &data);
+ void sendData(const QList &clients, const QByteArray &data);
/************** Used for testing **************************/
static QList servers();
diff --git a/tests/scripts/addrule.sh b/tests/scripts/addrule.sh
index 7d6aab7e..3ed30ae4 100755
--- a/tests/scripts/addrule.sh
+++ b/tests/scripts/addrule.sh
@@ -3,6 +3,7 @@
if test -z $5; then
echo "usage: $0 host sourceDevice eventTypeId targetDeviceId actionTypeId"
else
- (echo '{"id":1, "method":"Rules.AddRule", "params":{"event": {"eventTypeId": "'$3'", "deviceId":"'$2'"}, "actions": [ { "deviceId":"'$4'", "actionTypeId":"'$5'", "params":{"power":"true"}}]}}'; sleep 1) | nc $1 1234
+ (echo '{"id":1, "method":"Rules.AddRule", "params":{"event": {"eventTypeId": "$3", "deviceId":"'$2'"}, "actions": [ { "deviceId":"'$4'", "actionTypeId":"'$5'", "params":{"power":"true"}}]}}'; sleep 1) | nc $1 1234
+# (echo '{"id":1, "method":"Rules.AddRule", "params":{"event": {"eventTypeId": "'$3'", "deviceId":"'$2'"}, "actions": [ { "deviceId":"'$4'", "actionTypeId":"'$5'", "params":{"power":"true"}}]}}'; sleep 1) | nc $1 1234
# (echo '{"id":1, "method":"Rules.AddRule", "params":{"event": {"eventTypeId": "'$2'", "deviceId":"'$3'", "params":{"power":"false"}}, "actions": [ { "deviceId":"'$4'", "name":"rule 1", "params":{"power":"false"}},{ "deviceId":"'$5'", "name":"rule 1", "params":{"power":"true"}}]}}'; sleep 1) | nc $1 1234
fi