diff --git a/libnymea-app-core/jsonrpc/jsonrpcclient.cpp b/libnymea-app-core/jsonrpc/jsonrpcclient.cpp
index 7fddca5a..9bcd7c76 100644
--- a/libnymea-app-core/jsonrpc/jsonrpcclient.cpp
+++ b/libnymea-app-core/jsonrpc/jsonrpcclient.cpp
@@ -355,6 +355,7 @@ void JsonRpcClient::dataReceived(const QByteArray &data)
// check if this is a notification
if (dataMap.contains("notification")) {
+// qDebug() << "Incoming notification:" << jsonDoc.toJson();
QStringList notification = dataMap.value("notification").toString().split(".");
QString nameSpace = notification.first();
JsonHandler *handler = m_notificationHandlers.value(nameSpace).first;
diff --git a/libnymea-app-core/jsonrpc/jsontypes.cpp b/libnymea-app-core/jsonrpc/jsontypes.cpp
index ef405dd3..26c46400 100644
--- a/libnymea-app-core/jsonrpc/jsontypes.cpp
+++ b/libnymea-app-core/jsonrpc/jsontypes.cpp
@@ -449,7 +449,7 @@ QVariantMap JsonTypes::packTimeEventItem(TimeEventItem *timeEventItem)
ret.insert("time", timeEventItem->time().toString("hh:mm"));
}
if (!timeEventItem->dateTime().isNull()) {
- ret.insert("dateTime", timeEventItem->dateTime().toSecsSinceEpoch());
+ ret.insert("datetime", timeEventItem->dateTime().toSecsSinceEpoch());
}
ret.insert("repeating", packRepeatingOption(timeEventItem->repeatingOption()));
return ret;
diff --git a/libnymea-app-core/rulemanager.cpp b/libnymea-app-core/rulemanager.cpp
index 885232cd..3c3a495b 100644
--- a/libnymea-app-core/rulemanager.cpp
+++ b/libnymea-app-core/rulemanager.cpp
@@ -62,6 +62,7 @@ void RuleManager::addRule(const QVariantMap params)
void RuleManager::addRule(Rule *rule)
{
QVariantMap params = JsonTypes::packRule(rule);
+ qDebug() << "packed rule:" << qUtf8Printable(QJsonDocument::fromVariant(params).toJson(QJsonDocument::Indented));
m_jsonClient->sendCommand("Rules.AddRule", params, this, "onAddRuleReply");
}
diff --git a/libnymea-app-core/ruletemplates/ruleactiontemplate.h b/libnymea-app-core/ruletemplates/ruleactiontemplate.h
index 95e857c5..960c53a0 100644
--- a/libnymea-app-core/ruletemplates/ruleactiontemplate.h
+++ b/libnymea-app-core/ruletemplates/ruleactiontemplate.h
@@ -18,6 +18,7 @@ public:
enum SelectionMode {
SelectionModeAny,
SelectionModeDevice,
+ SelectionModeDevices,
SelectionModeInterface
};
Q_ENUM(SelectionMode)
diff --git a/nymea-app/resources.qrc b/nymea-app/resources.qrc
index f49a7e82..7a3e5967 100644
--- a/nymea-app/resources.qrc
+++ b/nymea-app/resources.qrc
@@ -196,5 +196,6 @@
ui/devicepages/DoorbellDevicePage.qml
ui/magic/NewMagicPage.qml
ui/components/WebViewWrapper.qml
+ ui/magic/NewScenePage.qml
diff --git a/nymea-app/ruletemplates/buttontemplates.json b/nymea-app/ruletemplates/buttontemplates.json
index 16a147d7..ff36b868 100644
--- a/nymea-app/ruletemplates/buttontemplates.json
+++ b/nymea-app/ruletemplates/buttontemplates.json
@@ -1,7 +1,7 @@
{
"templates": [
{
- "description": "Turn on a light",
+ "description": "Turn on lights",
"ruleNameTemplate": "%0 turns on %1",
"eventDescriptorTemplates": [
{
@@ -15,7 +15,7 @@
"interfaceName": "light",
"interfaceAction": "power",
"selectionId": 1,
- "selectionMode": "SelectionModeDevice",
+ "selectionMode": "SelectionModeDevices",
"params": [
{
"name": "power",
@@ -26,7 +26,7 @@
]
},
{
- "description": "Turn off a light",
+ "description": "Turn off lights",
"ruleNameTemplate": "%0 turns off %1",
"eventDescriptorTemplates": [
{
@@ -40,7 +40,7 @@
"interfaceName": "light",
"interfaceAction": "power",
"selectionId": 1,
- "selectionMode": "SelectionModeDevice",
+ "selectionMode": "SelectionModeDevices",
"params": [
{
"name": "power",
diff --git a/nymea-app/ruletemplates/notificationtemplates.json b/nymea-app/ruletemplates/notificationtemplates.json
index 6feacebb..1a7e0e62 100644
--- a/nymea-app/ruletemplates/notificationtemplates.json
+++ b/nymea-app/ruletemplates/notificationtemplates.json
@@ -39,6 +39,7 @@
"interfaceName": "moisturesensor",
"interfaceState": "moisture",
"operator": "ValueOperatorLess",
+ "selectionMode": "SelectionModeDevice",
"selectionId": 0,
"value": 20
}
diff --git a/nymea-app/ui/MainPage.qml b/nymea-app/ui/MainPage.qml
index f9dc09cf..d44d6ac6 100644
--- a/nymea-app/ui/MainPage.qml
+++ b/nymea-app/ui/MainPage.qml
@@ -275,8 +275,8 @@ Page {
if (engine.deviceManager.devices.count === 0) {
pageStack.push(Qt.resolvedUrl("thingconfiguration/NewThingPage.qml"))
} else {
- var page = pageStack.push(Qt.resolvedUrl("MagicPage.qml"))
- page.addRule()
+ var page = pageStack.push(Qt.resolvedUrl("magic/NewScenePage.qml"))
+// page.addRule()
}
}
}
diff --git a/nymea-app/ui/Nymea.qml b/nymea-app/ui/Nymea.qml
index b334503d..1d5365d7 100644
--- a/nymea-app/ui/Nymea.qml
+++ b/nymea-app/ui/Nymea.qml
@@ -286,39 +286,57 @@ ApplicationWindow {
case "dimmablelight":
case "colorlight":
case "colortemperaturelight":
+ //: Select ...
return qsTr("light")
- case "button":
- return "button";
case "sensor":
+ //: Select ...
return qsTr("sensor")
case "battery":
+ //: Select ...
return qsTr("battery powered thing")
case "connectable":
+ //: Select ...
return qsTr("connectable thing")
case "power":
+ //: Select ...
return qsTr("switchable thing")
case "daylightsensor":
+ //: Select ...
return qsTr("daylight sensor")
case "presencesensor":
+ //: Select ...
return qsTr("presence sensor")
case "doorbell":
+ //: Select ...
return qsTr("doorbell")
case "alert":
+ //: Select ...
return qsTr("alert")
case "simplemultibutton":
case "simplebutton":
+ case "button":
+ //: Select ...
return qsTr("button")
case "accesscotrol":
+ //: Select ...
return qsTr("access control")
case "smartmeter":
case "smartmeterproducer":
case "smartmeterconsumer":
case "extendedsmartmeterproducer":
case "extendedsmartmeterconsumer":
+ //: Select ...
return qsTr("smart meter");
case "media":
case "mediaplayer":
- return qsTr("media player")
+ //: Select ...
+ return qsTr("media player");
+ case "moisturesensor":
+ //: Select ...
+ return qsTr("moisture sensor");
+ case "notifications":
+ //: Select ...
+ return qsTr("thing to notify")
default:
console.warn("Unhandled interfaceToDisplayName:", name)
}
diff --git a/nymea-app/ui/magic/NewScenePage.qml b/nymea-app/ui/magic/NewScenePage.qml
new file mode 100644
index 00000000..cdd0b77c
--- /dev/null
+++ b/nymea-app/ui/magic/NewScenePage.qml
@@ -0,0 +1,33 @@
+import QtQuick 2.9
+import QtQuick.Controls 2.2
+import QtQuick.Layouts 1.3
+import Nymea 1.0
+import "../components"
+
+Page {
+ id: root
+
+ header: NymeaHeader {
+ text: qsTr("New scene")
+ onBackPressed: pageStack.pop()
+ }
+
+ signal done()
+
+
+ ListView {
+ anchors.fill: parent
+ model: RuleTemplatesFilterModel {
+ ruleTemplates: RuleTemplates {}
+ filterByDevices: DevicesProxy {
+ engine: _engine
+ }
+
+ }
+
+ delegate: NymeaListItemDelegate {
+ text: model.description
+ }
+ }
+
+}
diff --git a/nymea-app/ui/magic/NewThingMagicPage.qml b/nymea-app/ui/magic/NewThingMagicPage.qml
index 02fdbf41..44f8f79d 100644
--- a/nymea-app/ui/magic/NewThingMagicPage.qml
+++ b/nymea-app/ui/magic/NewThingMagicPage.qml
@@ -24,6 +24,7 @@ Page {
QtObject {
id: d
property var selectedThings: ({})
+ property var selectedThingNames: ({})
property var selectedInterfaces: ({})
function fillRuleFromTemplate(rule, ruleTemplate) {
@@ -44,6 +45,7 @@ Page {
if (root.device && !deviceIsUsed(root.device.id) && root.deviceClass.interfaces.indexOf(eventDescriptorTemplate.interfaceName) >= 0) {
print("Root device is matching and not used. Using for selectionId", eventDescriptorTemplate.selectionId, ":", root.device.name)
selectedThings[eventDescriptorTemplate.selectionId] = root.device.id;
+ selectedThingNames[eventDescriptorTemplate.selectionId] = root.device.name;
createEventDescriptor(rule, ruleTemplate, root.device, eventDescriptorTemplate)
return;
}
@@ -53,6 +55,7 @@ Page {
var page = pageStack.push(Qt.resolvedUrl("SelectThingPage.qml"), {shownInterfaces: [eventDescriptorTemplate.interfaceName]});
page.thingSelected.connect(function(device) {
selectedThings[eventDescriptorTemplate.selectionId] = device.id;
+ selectedThingNames[eventDescriptorTemplate.selectionId] = device.name;
createEventDescriptor(rule, ruleTemplate, device, eventDescriptorTemplate)
return;
})
@@ -98,7 +101,7 @@ Page {
if (ruleActionTemplate.selectionId in selectedThings) {
var device = engine.deviceManager.devices.getDevice(selectedThings[ruleActionTemplate.selectionId]);
print("Already have a device for selectionId", ruleActionTemplate.selectionId, ":", device.name)
- createRuleAction(rule, ruleTemplate, rule.actions, device, ruleActionTemplate)
+ createRuleAction(rule, ruleTemplate, rule.actions, [device], ruleActionTemplate)
return;
}
@@ -106,18 +109,29 @@ Page {
if (root.device && !deviceIsUsed(root.device.id) && root.deviceClass.interfaces.indexOf(ruleActionTemplate.interfaceName) >= 0) {
print("Root device is matching and not used. Using for selectionId", ruleActionTemplate.selectionId, ":", root.device.name)
selectedThings[ruleActionTemplate.selectionId] = root.device.id;
- createRuleAction(rule, ruleTemplate, rule.actions, root.device, ruleActionTemplate)
+ selectedThingNames[ruleActionTemplate.selectionId] = root.device.name;
+ createRuleAction(rule, ruleTemplate, rule.actions, [root.device], ruleActionTemplate)
return;
}
// Ok, we need to pick a thing
-// print("Need to select a thing.")
- var page = pageStack.push(Qt.resolvedUrl("SelectThingPage.qml"), {shownInterfaces: [ruleActionTemplate.interfaceName]});
+ var multipleSelection = ruleActionTemplate.selectionMode === RuleActionTemplate.SelectionModeDevices;
+ var page = pageStack.push(Qt.resolvedUrl("SelectThingPage.qml"), {shownInterfaces: [ruleActionTemplate.interfaceName], multipleSelection: multipleSelection});
page.thingSelected.connect(function(device) {
selectedThings[ruleActionTemplate.selectionId] = device.id;
- createRuleAction(rule, ruleTemplate, rule.actions, device, ruleActionTemplate)
- return;
+ selectedThingNames[ruleActionTemplate.selectionId] = device.name;
+ createRuleAction(rule, ruleTemplate, rule.actions, [device], ruleActionTemplate)
})
+ page.thingsSelected.connect(function(devices) {
+ var names = []
+ for (var i = 0; i < devices.length; i++) {
+ names.push(devices[i].name)
+ }
+ selectedThingNames[ruleActionTemplate.selectionId] = names.join(", ");
+
+ createRuleAction(rule, ruleTemplate, rule.actions, devices, ruleActionTemplate)
+ });
+
page.backPressed.connect(function() {rule.destroy(); root.done();})
return;
}
@@ -144,35 +158,46 @@ Page {
// Did we pick a thing for this index before?
if (ruleExitActionTemplate.selectionId in selectedThings) {
var device = engine.deviceManager.devices.getDevice(selectedThings[ruleExitActionTemplate.selectionId]);
- createRuleAction(rule, ruleTemplate, rule.exitActions, device, ruleExitActionTemplate);
+ createRuleAction(rule, ruleTemplate, rule.exitActions, [device], ruleExitActionTemplate);
return;
}
// Did we already use the thing we opened this page from?
if (root.device && !deviceIsUsed(root.device.id) && root.deviceClass.interfaces.indexOf(ruleExitActionTemplate.interfaceName) >= 0) {
selectedThings[ruleExitActionTemplate.selectionId] = root.device.id;
- createRuleAction(rule, ruleTemplate, rule.exitActions, root.device, ruleExitActionTemplate);
+ selectedThingNames[ruleExitActionTemplate.selectionId] = root.device.name;
+ createRuleAction(rule, ruleTemplate, rule.exitActions, [root.device], ruleExitActionTemplate);
return;
}
// Ok, we need to pick a thing
- var page = pageStack.push(Qt.resolvedUrl("SelectThingPage.qml"), {shownInterfaces: [ruleExitActionTemplate.interfaceName]});
+ var multipleSelection = ruleActionTemplate.selectionMode === RuleActionTemplate.SelectionModeDevices;
+ var page = pageStack.push(Qt.resolvedUrl("SelectThingPage.qml"), {shownInterfaces: [ruleExitActionTemplate.interfaceName], multipleSelection: multipleSelection});
page.thingSelected.connect(function(device) {
selectedThings[ruleExitActionTemplate.selectionId] = device.id;
- createRuleAction(rule, ruleTemplate, rule.exitActions, device, ruleExitActionTemplate);
+ selectedThingNames[ruleExitActionTemplate.selectionId] = device.name;
+ createRuleAction(rule, ruleTemplate, rule.exitActions, [device], ruleExitActionTemplate);
return;
})
+ page.thingsSelected.connect(function(devices) {
+ var names = []
+ for (var i = 0; i < devices.length; i++) {
+ names.push(devices[i].name)
+ }
+ selectedThingNames[ruleExitActionTemplate.selectionId] = devices.join(", ");
+ createRuleAction(rule, ruleTemplate, rule.exitActions, [device], ruleExitActionTemplate);
+ });
page.backPressed.connect(function() {rule.destroy(); root.done();})
return;
}
- // Now replace %i in title and action params with selectedThings[i].name
+ // Now replace %i in title and action params with selectedThingNames[id]
rule.name = ruleTemplate.ruleNameTemplate;
- for (var selectionId in selectedThings) {
- print("Replacing", selectionId, "with", selectedThings[selectionId], selectedInterfaces[selectionId])
- var device = engine.deviceManager.devices.getDevice(selectedThings[selectionId]);
- rule.name = rule.name.replace("%" + selectionId, device.name)
+ for (var selectionId in selectedThingNames) {
+ print("Replacing", selectionId, "with", selectedThingNames[selectionId], selectedInterfaces[selectionId])
+ var thingName = selectedThingNames[selectionId];
+ rule.name = rule.name.replace("%" + selectionId, thingName)
for (var j = 0; j < rule.actions.count; j++) {
var action = rule.actions.get(j);
@@ -180,7 +205,7 @@ Page {
var actionParam = action.ruleActionParams.get(k);
print("replacing args", typeof actionParam.value)
if (typeof actionParam.value === "string") {
- actionParam.value = actionParam.value.replace("%" + selectionId, device.name);
+ actionParam.value = actionParam.value.replace("%" + selectionId, thingName);
}
}
}
@@ -189,7 +214,7 @@ Page {
for(var k = 0; k < action.ruleActionParams.count; k++) {
var actionParam = action.ruleActionParams.get(k);
if (typeof actionParam.value === "string") {
- actionParam.value = actionParam.value.replace("%" + selectionId, device.name);
+ actionParam.value = actionParam.value.replace("%" + selectionId, thingName);
}
}
}
@@ -238,6 +263,7 @@ Page {
stateEvaluator.stateDescriptor.valueOperator = stateEvaluatorTemplate.stateDescriptorTemplate.valueOperator;
stateEvaluator.stateDescriptor.value = stateEvaluatorTemplate.stateDescriptorTemplate.value;
selectedThings[stateEvaluatorTemplate.stateDescriptorTemplate.selectionId] = root.device.id;
+ selectedThingNames[stateEvaluatorTemplate.stateDescriptorTemplate.selectionId] = root.device.name;
fillRuleFromTemplate(rule, ruleTemplate);
return true;
}
@@ -251,6 +277,7 @@ Page {
stateEvaluator.stateDescriptor.valueOperator = stateEvaluatorTemplate.stateDescriptorTemplate.valueOperator;
stateEvaluator.stateDescriptor.value = stateEvaluatorTemplate.stateDescriptorTemplate.value;
selectedThings[stateEvaluatorTemplate.stateDescriptorTemplate.selectionId] = device.id;
+ selectedThingNames[stateEvaluatorTemplate.stateDescriptorTemplate.selectionId] = device.name;
fillRuleFromTemplate(rule, ruleTemplate)
})
page.onAnySelected.connect(function() {
@@ -314,7 +341,13 @@ Page {
fillRuleFromTemplate(rule, ruleTemplate);
}
- function createRuleAction(rule, ruleTemplate, ruleActions, device, ruleActionTemplate) {
+ function createRuleAction(rule, ruleTemplate, ruleActions, devices, ruleActionTemplate) {
+
+ var device = devices.shift();
+ // device param -> devices
+ // take first, run code
+ // at end, if devices is empty, continue with fillRuleFromTemplate otherwise run again
+
var ruleAction = ruleActions.createNewRuleAction();
var deviceClass = engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId);
@@ -369,14 +402,22 @@ Page {
paramsPage.onCompleted.connect(function() {
pageStack.pop();
ruleActions.addRuleAction(ruleAction);
- fillRuleFromTemplate(rule, ruleTemplate);
+ if (devices.length === 0) {
+ fillRuleFromTemplate(rule, ruleTemplate);
+ } else {
+ createRuleAction(rule, ruleTemplate, rule.actions, devices, ruleActionTemplate)
+ }
})
return;
}
}
ruleActions.addRuleAction(ruleAction);
- fillRuleFromTemplate(rule, ruleTemplate);
+ if (devices.length === 0) {
+ fillRuleFromTemplate(rule, ruleTemplate);
+ } else {
+ createRuleAction(rule, ruleTemplate, rule.actions, devices, ruleActionTemplate)
+ }
}
function deviceIsUsed(deviceId) {
diff --git a/nymea-app/ui/magic/SelectThingPage.qml b/nymea-app/ui/magic/SelectThingPage.qml
index ee45c3ba..808ef0e4 100644
--- a/nymea-app/ui/magic/SelectThingPage.qml
+++ b/nymea-app/ui/magic/SelectThingPage.qml
@@ -14,16 +14,18 @@ Page {
property alias showStates: interfacesProxy.showStates
property alias shownInterfaces: devicesProxy.shownInterfaces
property bool allowSelectAny: false
+ property bool multipleSelection: false
signal backPressed();
signal thingSelected(var device);
+ signal thingsSelected(var devices);
signal interfaceSelected(string interfaceName);
signal anySelected();
header: NymeaHeader {
text: root.selectInterface ?
- qsTr("Select a kind of things") :
- root.shownInterfaces.length > 0 ? qsTr("Select a %1").arg(app.interfaceToDisplayName(root.shownInterfaces[0])) : qsTr("Select a thing")
+ qsTr("Select kind of things") :
+ root.shownInterfaces.length > 0 ? qsTr("Select %1").arg(app.interfaceToDisplayName(root.shownInterfaces[0])) : qsTr("Select thing")
onBackPressed: root.backPressed()
HeaderButton {
@@ -66,12 +68,24 @@ Page {
}
ThinDivider { visible: root.allowSelectAny }
-
GroupedListView {
+ id: listView
Layout.fillWidth: true
Layout.fillHeight: true
model: root.selectInterface ? interfacesProxy : devicesProxy
clip: true
+ property var checkBoxCache: ({})
+ function toggleCheckBoxCache(deviceId) {
+ var newCache = listView.checkBoxCache;
+ if (!newCache.hasOwnProperty(deviceId) || !newCache[deviceId]) {
+ newCache[deviceId] = true
+ } else {
+ newCache[deviceId] = false
+ }
+ listView.checkBoxCache = newCache;
+ print("new checked state;", newCache[deviceId])
+ }
+
delegate: NymeaListItemDelegate {
width: parent.width
text: root.selectInterface ? model.displayName : model.name
@@ -79,10 +93,39 @@ Page {
onClicked: {
if (root.selectInterface) {
root.interfaceSelected(interfacesProxy.get(index).name)
- } else {
+ } else if (!root.multipleSelection) {
root.thingSelected(devicesProxy.get(index))
+ } else {
+ listView.toggleCheckBoxCache(model.id)
}
}
+ progressive: !root.multipleSelection
+
+ additionalItem: root.multipleSelection ? entryCheckBox : null
+ CheckBox {
+ id: entryCheckBox
+ height: parent.height
+ visible: root.multipleSelection
+ checked: listView.checkBoxCache.hasOwnProperty(model.id) && listView.checkBoxCache[model.id]
+ onClicked: listView.toggleCheckBoxCache(model.id)
+ }
+ }
+ }
+
+ Button {
+ Layout.fillWidth: true
+ Layout.margins: app.margins
+ text: qsTr("OK")
+ visible: root.multipleSelection
+ onClicked: {
+ var devices = []
+ for (var i = 0; i < devicesProxy.count; i++) {
+ var device = devicesProxy.get(i);
+ if (listView.checkBoxCache.hasOwnProperty(device.id) && listView.checkBoxCache[device.id]) {
+ devices.push(device)
+ }
+ }
+ root.thingsSelected(devices)
}
}
}