diff --git a/libnymea-app-core/connection/nymeaconnection.cpp b/libnymea-app-core/connection/nymeaconnection.cpp index 120bffc8..ba051543 100644 --- a/libnymea-app-core/connection/nymeaconnection.cpp +++ b/libnymea-app-core/connection/nymeaconnection.cpp @@ -372,7 +372,11 @@ void NymeaConnection::onDisconnected() // Try to reconnect, only if we're not waiting for SSL certs to be trusted. if (m_connectionStatus != ConnectionStatusSslUntrusted) { - connectInternal(m_currentHost); + QTimer::singleShot(1000, this, [this](){ + if (m_currentHost) { + connectInternal(m_currentHost); + } + }); } } diff --git a/libnymea-app-core/jsonrpc/jsonrpcclient.cpp b/libnymea-app-core/jsonrpc/jsonrpcclient.cpp index c77de29e..bd8ec6c2 100644 --- a/libnymea-app-core/jsonrpc/jsonrpcclient.cpp +++ b/libnymea-app-core/jsonrpc/jsonrpcclient.cpp @@ -318,14 +318,8 @@ void JsonRpcClient::onInterfaceConnectedChanged(bool connected) emit connectedChanged(false); } } else { - QVariantMap request; - request.insert("id", 0); - qDebug() << "Connected. Starting JSONRPC Handshake"; - request.insert("method", "JSONRPC.Hello"); QVariantMap params; params.insert("locale", QLocale().name()); - request.insert("params", params); -// sendRequest(request); sendCommand("JSONRPC.Hello", params, this, "helloReply"); } } diff --git a/libnymea-app-core/jsonrpc/jsontypes.cpp b/libnymea-app-core/jsonrpc/jsontypes.cpp index 7ab41211..55d3461a 100644 --- a/libnymea-app-core/jsonrpc/jsontypes.cpp +++ b/libnymea-app-core/jsonrpc/jsontypes.cpp @@ -304,11 +304,14 @@ QVariantList JsonTypes::packRuleActions(RuleActions *ruleActions) } else { ruleActionParam.insert("paramName", rap->paramName()); } - if (!rap->eventTypeId().isEmpty() && !rap->eventParamTypeId().isEmpty()) { + if (rap->isValueBased()) { + ruleActionParam.insert("value", rap->value()); + } else if (rap->isEventParamBased()) { ruleActionParam.insert("eventTypeId", rap->eventTypeId()); ruleActionParam.insert("eventParamTypeId", rap->eventParamTypeId()); } else { - ruleActionParam.insert("value", rap->value()); + ruleActionParam.insert("stateDeviceId", rap->stateDeviceId()); + ruleActionParam.insert("stateTypeId", rap->stateTypeId()); } ruleActionParams.append(ruleActionParam); } diff --git a/libnymea-app-core/rulemanager.cpp b/libnymea-app-core/rulemanager.cpp index 1e67667d..8ea580ca 100644 --- a/libnymea-app-core/rulemanager.cpp +++ b/libnymea-app-core/rulemanager.cpp @@ -297,6 +297,8 @@ RuleAction *RuleManager::parseRuleAction(const QVariantMap &ruleAction) param->setValue(ruleActionParamVariant.toMap().value("value")); param->setEventTypeId(ruleActionParamVariant.toMap().value("eventTypeId").toString()); param->setEventParamTypeId(ruleActionParamVariant.toMap().value("eventParamTypeId").toString()); + param->setStateDeviceId(ruleActionParamVariant.toMap().value("stateDeviceId").toString()); + param->setStateTypeId(ruleActionParamVariant.toMap().value("stateTypeId").toString()); ret->ruleActionParams()->addRuleActionParam(param); } return ret; diff --git a/libnymea-common/types/ruleactionparam.cpp b/libnymea-common/types/ruleactionparam.cpp index e9c6bd92..0ff29372 100644 --- a/libnymea-common/types/ruleactionparam.cpp +++ b/libnymea-common/types/ruleactionparam.cpp @@ -7,6 +7,8 @@ RuleActionParam::RuleActionParam(const QString ¶mName, const QVariant &value m_paramName(paramName) { setValue(value); + + connect(this, &Param::valueChanged, this, &RuleActionParam::isValueBasedChanged); } RuleActionParam::RuleActionParam(QObject *parent) : Param(parent) @@ -37,6 +39,7 @@ void RuleActionParam::setEventTypeId(const QString &eventTypeId) if (m_eventTypeId != eventTypeId) { m_eventTypeId = eventTypeId; emit eventTypeIdChanged(); + emit isEventParamBasedChanged(); } } @@ -50,9 +53,53 @@ void RuleActionParam::setEventParamTypeId(const QString &eventParamTypeId) if (m_eventParamTypeId != eventParamTypeId) { m_eventParamTypeId = eventParamTypeId; emit eventParamTypeIdChanged(); + emit isEventParamBasedChanged(); } } +QString RuleActionParam::stateDeviceId() const +{ + return m_stateDeviceId; +} + +void RuleActionParam::setStateDeviceId(const QString &stateDeviceId) +{ + if (m_stateDeviceId != stateDeviceId) { + m_stateDeviceId = stateDeviceId; + emit stateDeviceIdChanged(); + emit isStateValueBasedChanged(); + } +} + +QString RuleActionParam::stateTypeId() const +{ + return m_stateTypeId; +} + +void RuleActionParam::setStateTypeId(const QString &stateTypeId) +{ + if (m_stateTypeId != stateTypeId) { + m_stateTypeId = stateTypeId; + emit stateTypeIdChanged(); + emit isStateValueBasedChanged(); + } +} + +bool RuleActionParam::isValueBased() const +{ + return !m_value.isNull(); +} + +bool RuleActionParam::isEventParamBased() const +{ + return !m_eventTypeId.isNull() && !m_eventParamTypeId.isNull(); +} + +bool RuleActionParam::isStateValueBased() const +{ + return !m_stateDeviceId.isNull() && !m_stateTypeId.isNull(); +} + RuleActionParam *RuleActionParam::clone() const { RuleActionParam *ret = new RuleActionParam(); @@ -61,6 +108,8 @@ RuleActionParam *RuleActionParam::clone() const ret->setValue(value()); ret->setEventTypeId(eventTypeId()); ret->setEventParamTypeId(eventParamTypeId()); + ret->setStateDeviceId(stateDeviceId()); + ret->setStateTypeId(stateTypeId()); return ret; } @@ -72,6 +121,8 @@ bool RuleActionParam::operator==(RuleActionParam *other) const COMPARE(m_paramName, other->paramName()); COMPARE(m_eventTypeId, other->eventTypeId()); COMPARE(m_eventParamTypeId, other->eventParamTypeId()); + COMPARE(m_stateDeviceId, other->stateDeviceId()); + COMPARE(m_stateTypeId, other->stateTypeId()); COMPARE(m_value, other->value()); return true; } diff --git a/libnymea-common/types/ruleactionparam.h b/libnymea-common/types/ruleactionparam.h index 6d79bc9a..cfd7f8b6 100644 --- a/libnymea-common/types/ruleactionparam.h +++ b/libnymea-common/types/ruleactionparam.h @@ -13,6 +13,13 @@ class RuleActionParam : public Param Q_PROPERTY(QString paramName READ paramName WRITE setParamName NOTIFY paramNameChanged) Q_PROPERTY(QString eventTypeId READ eventTypeId WRITE setEventTypeId NOTIFY eventTypeIdChanged) Q_PROPERTY(QString eventParamTypeId READ eventParamTypeId WRITE setEventParamTypeId NOTIFY eventParamTypeIdChanged) + Q_PROPERTY(QString stateDeviceId READ stateDeviceId WRITE setStateDeviceId NOTIFY stateDeviceIdChanged) + Q_PROPERTY(QString stateTypeId READ stateTypeId WRITE setStateTypeId NOTIFY stateTypeIdChanged) + + Q_PROPERTY(bool isValueBased READ isValueBased NOTIFY isValueBasedChanged) + Q_PROPERTY(bool isEventParamBased READ isEventParamBased NOTIFY isEventParamBasedChanged) + Q_PROPERTY(bool isStateValueBased READ isStateValueBased NOTIFY isStateValueBasedChanged) + public: explicit RuleActionParam(const QString ¶mName, const QVariant &value, QObject *parent = nullptr); explicit RuleActionParam(QObject *parent = nullptr); @@ -26,17 +33,35 @@ public: QString eventParamTypeId() const; void setEventParamTypeId(const QString &eventParamTypeId); + QString stateDeviceId() const; + void setStateDeviceId(const QString &stateDeviceId); + + QString stateTypeId() const; + void setStateTypeId(const QString &stateTypeId); + + bool isValueBased() const; + bool isEventParamBased() const; + bool isStateValueBased() const; + RuleActionParam* clone() const; bool operator==(RuleActionParam *other) const; signals: void paramNameChanged(); void eventTypeIdChanged(); void eventParamTypeIdChanged(); + void stateDeviceIdChanged(); + void stateTypeIdChanged(); + + void isValueBasedChanged(); + void isEventParamBasedChanged(); + void isStateValueBasedChanged(); protected: QString m_paramName; QString m_eventTypeId; QString m_eventParamTypeId; + QString m_stateDeviceId; + QString m_stateTypeId; }; #endif // RULEACTIONPARAM_H diff --git a/libnymea-common/types/ruleactionparams.cpp b/libnymea-common/types/ruleactionparams.cpp index 0e40495e..c4f4ae48 100644 --- a/libnymea-common/types/ruleactionparams.cpp +++ b/libnymea-common/types/ruleactionparams.cpp @@ -94,6 +94,22 @@ void RuleActionParams::setRuleActionParamEvent(const QString ¶mTypeId, const addRuleActionParam(rap); } +void RuleActionParams::setRuleActionParamState(const QString ¶mTypeId, const QString &stateDeviceId, const QString &stateTypeId) +{ + foreach (RuleActionParam *rap, m_list) { + if (rap->paramTypeId() == paramTypeId) { + rap->setStateDeviceId(stateDeviceId); + rap->setStateTypeId(stateTypeId); + return; + } + } + RuleActionParam *rap = new RuleActionParam(this); + rap->setParamTypeId(paramTypeId); + rap->setStateDeviceId(stateDeviceId); + rap->setStateTypeId(stateTypeId); + addRuleActionParam(rap); +} + RuleActionParam *RuleActionParams::get(int index) const { return m_list.at(index); diff --git a/libnymea-common/types/ruleactionparams.h b/libnymea-common/types/ruleactionparams.h index 89444ecd..846e3263 100644 --- a/libnymea-common/types/ruleactionparams.h +++ b/libnymea-common/types/ruleactionparams.h @@ -29,6 +29,8 @@ public: Q_INVOKABLE void setRuleActionParam(const QString ¶mTypeId, const QVariant &value); Q_INVOKABLE void setRuleActionParamByName(const QString ¶mName, const QVariant &value); Q_INVOKABLE void setRuleActionParamEvent(const QString ¶mTypeId, const QString &eventTypeId, const QString &eventParamTypeId); + Q_INVOKABLE void setRuleActionParamState(const QString ¶mTypeId, const QString &stateDeviceId, const QString &stateTypeId); + Q_INVOKABLE RuleActionParam* get(int index) const; bool operator==(RuleActionParams *other) const; diff --git a/nymea-app/resources.qrc b/nymea-app/resources.qrc index e08babd0..a2a18a9b 100644 --- a/nymea-app/resources.qrc +++ b/nymea-app/resources.qrc @@ -179,5 +179,6 @@ ui/components/Imprint.qml ui/appsettings/LookAndFeelSettingsPage.qml ui/appsettings/AppLogPage.qml + ui/magic/SelectStatePage.qml diff --git a/nymea-app/ui/components/RemoveDeviceMethodDialog.qml b/nymea-app/ui/components/RemoveDeviceMethodDialog.qml index d7fe0eb4..08f894ee 100644 --- a/nymea-app/ui/components/RemoveDeviceMethodDialog.qml +++ b/nymea-app/ui/components/RemoveDeviceMethodDialog.qml @@ -38,27 +38,32 @@ Dialog { } ThinDivider {} - Button { - text: qsTr("Remove all those rules") + MeaListItemDelegate { Layout.fillWidth: true + text: qsTr("Remove all those rules") + progressive: false onClicked: { engine.deviceManager.removeDevice(root.device.id, DeviceManager.RemovePolicyCascade) root.close() root.destroy(); } } - Button { + + MeaListItemDelegate { text: qsTr("Update rules, removing this thing") Layout.fillWidth: true + progressive: false onClicked: { engine.deviceManager.removeDevice(root.device.id, DeviceManager.RemovePolicyUpdate) root.close() root.destroy(); } } - Button { + + MeaListItemDelegate { text: qsTr("Don't remove this thing") Layout.fillWidth: true + progressive: false onClicked: { root.close() root.destroy(); diff --git a/nymea-app/ui/delegates/ParamDelegate.qml b/nymea-app/ui/delegates/ParamDelegate.qml index 674a82cd..a7c3f43f 100644 --- a/nymea-app/ui/delegates/ParamDelegate.qml +++ b/nymea-app/ui/delegates/ParamDelegate.qml @@ -16,6 +16,8 @@ ItemDelegate { value: paramType.defaultValue } property bool writable: true + property alias nameVisible: nameLabel.visible + property string placeholderText: "" topPadding: 0 bottomPadding: 0 @@ -23,6 +25,7 @@ ItemDelegate { id: contentItemColumn RowLayout { Label { + id: nameLabel Layout.fillWidth: true Layout.minimumWidth: parent.width / 2 text: root.paramType.displayName @@ -90,7 +93,6 @@ ItemDelegate { } return root.param.value; } - } } Component { @@ -188,6 +190,7 @@ ItemDelegate { root.param.value = text; } } + placeholderText: root.placeholderText } } diff --git a/nymea-app/ui/magic/RuleActionDelegate.qml b/nymea-app/ui/magic/RuleActionDelegate.qml index a5f78cbc..68fcf558 100644 --- a/nymea-app/ui/magic/RuleActionDelegate.qml +++ b/nymea-app/ui/magic/RuleActionDelegate.qml @@ -28,10 +28,20 @@ MeaListItemDelegate { var ret = []; for (var i = 0; i < root.ruleAction.ruleActionParams.count; i++) { var ruleActionParam = root.ruleAction.ruleActionParams.get(i) - print("populating subtext:", ruleActionParam.eventTypeId, ruleActionParam.eventParamTypeId) - var paramString = qsTr("%1: %2") - .arg(root.actionType.paramTypes.getParamType(ruleActionParam.paramTypeId).displayName) - .arg(ruleActionParam.eventParamTypeId.length > 0 ? qsTr("value from event") : ruleActionParam.value) + print("populating subtext:", ruleActionParam.eventTypeId, ruleActionParam.eventParamTypeId, ruleActionParam.stateDeviceId, ruleActionParam.stateTypeId, ruleActionParam.isValueBased, ruleActionParam.isEventParamBased, ruleActionParam.isStateValueBased) + + var paramString = qsTr("%1: %2").arg(root.actionType.paramTypes.getParamType(ruleActionParam.paramTypeId).displayName) + if (ruleActionParam.isValueBased) { + paramString = paramString.arg(ruleActionParam.value) + } else if (ruleActionParam.isEventParamBased) { + paramString = paramString.arg(qsTr("value from event")) + } else if (ruleActionParam.isStateValueBased) { + var stateDevice = engine.deviceManager.devices.getDevice(ruleActionParam.stateDeviceId) + var stateType = stateDevice.deviceClass.stateTypes.getStateType(ruleActionParam.stateTypeId) + print("have state value based param:", stateDevice.name) + paramString = paramString.arg("{" + stateDevice.name + " - " + stateType.displayName + "}") + } + ret.push(paramString) } return ret.join(', ') diff --git a/nymea-app/ui/magic/SelectRuleActionParamsPage.qml b/nymea-app/ui/magic/SelectRuleActionParamsPage.qml index 8fd4e543..c192cf3c 100644 --- a/nymea-app/ui/magic/SelectRuleActionParamsPage.qml +++ b/nymea-app/ui/magic/SelectRuleActionParamsPage.qml @@ -22,7 +22,7 @@ Page { signal completed(); header: GuhHeader { - text: "params" + text: actionType.displayName onBackPressed: root.backPressed(); } @@ -46,6 +46,9 @@ Page { if (eventParamRadioButton.checked) { return "event" } + if (stateValueRadioButton.checked) { + return "state" + } return "" } @@ -53,54 +56,122 @@ Page { property alias value: paramDelegate.value property alias eventType: eventParamsComboBox.eventType property alias eventParamTypeId: eventParamsComboBox.currentParamTypeId + property alias stateDeviceId: statePickerDelegate.deviceId + property alias stateTypeId: statePickerDelegate.stateTypeId - RadioButton { - id: staticParamRadioButton - text: qsTr("Use static value as parameter") - checked: true - } - ParamDelegate { - id: paramDelegate - Layout.fillWidth: true - paramType: root.actionType.paramTypes.get(index) - enabled: staticParamRadioButton.checked - } - - RadioButton { - id: eventParamRadioButton - text: qsTr("Use event parameter") - visible: eventParamsComboBox.count > 0 - } - ComboBox { - id: eventParamsComboBox + GroupBox { Layout.fillWidth: true Layout.margins: app.margins - enabled: eventParamRadioButton.checked - visible: count > 0 - Component.onCompleted: currentIndex = 0; - property var eventDescriptor: root.rule.eventDescriptors.count === 1 ? root.rule.eventDescriptors.get(0) : null - property var device: eventDescriptor ? engine.deviceManager.devices.getDevice(eventDescriptor.deviceId) : null - property var deviceClass: device ? engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId) : null - property var eventType: deviceClass ? deviceClass.eventTypes.getEventType(eventDescriptor.eventTypeId) : null - - property var currentParamDescriptor: eventType.paramTypes.get(eventParamsComboBox.currentIndex) - property var currentParamTypeId: currentParamDescriptor.id - - model: eventType.paramTypes - delegate: ItemDelegate { - width: parent.width - text: eventParamsComboBox.device.name + " - " + eventParamsComboBox.eventType.displayName + " - " + eventParamsComboBox.eventType.paramTypes.getParamType(model.id).displayName - } - contentItem: Label { - id: eventParamsComboBoxContentItem + title: paramType.displayName + ColumnLayout { anchors.fill: parent - anchors.margins: app.margins - text: eventParamsComboBox.device.name + " - " + eventParamsComboBox.eventType.displayName + " - " + eventParamsComboBox.currentParamDescriptor.displayName - elide: Text.ElideRight + RadioButton { + id: staticParamRadioButton + text: qsTr("Use static value as parameter") + checked: true + font.pixelSize: app.smallFont + visible: eventParamRadioButton.visible || stateValueRadioButton.visible + } + RadioButton { + id: eventParamRadioButton + text: qsTr("Use event parameter") + visible: eventParamsComboBox.count > 0 + font.pixelSize: app.smallFont + } + RadioButton { + id: stateValueRadioButton + text: qsTr("Use a thing's state value") + font.pixelSize: app.smallFont + visible: engine.jsonRpcClient.ensureServerVersion("2.0") + } + + ThinDivider { + visible: staticParamRadioButton.visible + } + + ParamDelegate { + id: paramDelegate + Layout.fillWidth: true + paramType: root.actionType.paramTypes.get(index) + enabled: staticParamRadioButton.checked + nameVisible: false + visible: staticParamRadioButton.checked + placeholderText: qsTr("Insert value here") + } + + ComboBox { + id: eventParamsComboBox + Layout.fillWidth: true + visible: eventParamRadioButton.checked && count > 0 + Component.onCompleted: currentIndex = 0; + property var eventDescriptor: root.rule.eventDescriptors.count === 1 ? root.rule.eventDescriptors.get(0) : null + property var device: eventDescriptor ? engine.deviceManager.devices.getDevice(eventDescriptor.deviceId) : null + property var deviceClass: device ? engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId) : null + property var eventType: deviceClass ? deviceClass.eventTypes.getEventType(eventDescriptor.eventTypeId) : null + + property var currentParamDescriptor: eventType.paramTypes.get(eventParamsComboBox.currentIndex) + property var currentParamTypeId: currentParamDescriptor.id + + model: eventType.paramTypes + delegate: ItemDelegate { + width: parent.width + text: eventParamsComboBox.device.name + " - " + eventParamsComboBox.eventType.displayName + " - " + eventParamsComboBox.eventType.paramTypes.getParamType(model.id).displayName + } + contentItem: Label { + id: eventParamsComboBoxContentItem + anchors.fill: parent + anchors.margins: app.margins + text: eventParamsComboBox.device.name + " - " + eventParamsComboBox.eventType.displayName + " - " + eventParamsComboBox.currentParamDescriptor.displayName + elide: Text.ElideRight + } + } + + MeaListItemDelegate { + id: statePickerDelegate + Layout.fillWidth: true + text: deviceId === null || stateTypeId === null + ? qsTr("Select a state") + : dev.name + " - " + dev.deviceClass.stateTypes.getStateType(stateTypeId).displayName + visible: stateValueRadioButton.checked + + property var deviceId: null + property var stateTypeId: null + + readonly property Device dev: engine.deviceManager.devices.getDevice(deviceId) + + onClicked: { + var page = pageStack.push(Qt.resolvedUrl("SelectThingPage.qml"), {showStates: true, showEvents: false, showActions: false }); + page.thingSelected.connect(function(device) { + print("Thing selected", device.name); + statePickerDelegate.deviceId = device.id + var selectStatePage = pageStack.replace(Qt.resolvedUrl("SelectStatePage.qml"), {device: device}) + selectStatePage.stateSelected.connect(function(stateTypeId) { + print("State selected", stateTypeId) + pageStack.pop(); + statePickerDelegate.stateTypeId = stateTypeId; + }) + }) + page.backPressed.connect(function() { + pageStack.pop(); + }) + } + } } } - ThinDivider {} +// Label { +// id: paramNameLabel +// Layout.fillWidth: true +// Layout.leftMargin: app.margins +// Layout.rightMargin: app.margins +// Layout.topMargin: app.margins +// elide: Text.ElideRight +// text: paramType.displayName +// font.pixelSize: app.largeFont +// } + + +// ThinDivider {} } } Item { @@ -124,6 +195,9 @@ Page { } else if (paramDelegate.type === "event") { print("adding event based rule action param", paramDelegate.paramType.id, paramDelegate.eventType.id, paramDelegate.eventParamTypeId) root.ruleAction.ruleActionParams.setRuleActionParamEvent(paramDelegate.paramType.id, paramDelegate.eventType.id, paramDelegate.eventParamTypeId) + } else if (paramDelegate.type === "state") { + print("adding state value based rule action param", paramDelegate.paramType.id, paramDelegate.stateDeviceId, paramDelegate.stateTypeId) + root.ruleAction.ruleActionParams.setRuleActionParamState(paramDelegate.paramType.id, paramDelegate.stateDeviceId, paramDelegate.stateTypeId) } } root.completed() diff --git a/nymea-app/ui/magic/SelectStatePage.qml b/nymea-app/ui/magic/SelectStatePage.qml new file mode 100644 index 00000000..8cf605bf --- /dev/null +++ b/nymea-app/ui/magic/SelectStatePage.qml @@ -0,0 +1,35 @@ +import QtQuick 2.8 +import QtQuick.Controls 2.1 +import QtQuick.Layouts 1.2 +import "../components" +import "../delegates" +import Nymea 1.0 + +Page { + id: root + header: GuhHeader { + text: qsTr("Select state") + onBackPressed: pageStack.pop() + } + + property Device device: null + + signal stateSelected(var stateTypeId); + + ListView { + anchors.fill: parent + + model: device.deviceClass.stateTypes + + delegate: MeaListItemDelegate { + width: parent.width + iconName: "../images/state.svg" + text: model.displayName + subText: root.device.states.getState(model.id).value + prominentSubText: false + onClicked: { + root.stateSelected(model.id) + } + } + } +}