diff --git a/libnymea-app/devicemanager.cpp b/libnymea-app/devicemanager.cpp index e0edc404..31e80dea 100644 --- a/libnymea-app/devicemanager.cpp +++ b/libnymea-app/devicemanager.cpp @@ -263,7 +263,7 @@ void DeviceManager::getVendorsResponse(const QVariantMap ¶ms) void DeviceManager::getSupportedDevicesResponse(const QVariantMap ¶ms) { -// qDebug() << "DeviceClass received:" << qUtf8Printable(QJsonDocument::fromVariant(params).toJson(QJsonDocument::Indented)); + qDebug() << "DeviceClass received:" << qUtf8Printable(QJsonDocument::fromVariant(params).toJson(QJsonDocument::Indented)); if (params.value("params").toMap().keys().contains("deviceClasses")) { QVariantList deviceClassList = params.value("params").toMap().value("deviceClasses").toList(); foreach (QVariant deviceClassVariant, deviceClassList) { diff --git a/libnymea-app/engine.cpp b/libnymea-app/engine.cpp index 533504ee..fbfa0a11 100644 --- a/libnymea-app/engine.cpp +++ b/libnymea-app/engine.cpp @@ -42,7 +42,7 @@ Engine::Engine(QObject *parent) : QObject(parent), m_jsonRpcClient(new JsonRpcClient(this)), - m_deviceManager(new DeviceManager(m_jsonRpcClient, this)), + m_thingManager(new DeviceManager(m_jsonRpcClient, this)), m_ruleManager(new RuleManager(m_jsonRpcClient, this)), m_scriptManager(new ScriptManager(m_jsonRpcClient, this)), m_logManager(new LogManager(m_jsonRpcClient, this)), @@ -53,7 +53,7 @@ Engine::Engine(QObject *parent) : connect(m_jsonRpcClient, &JsonRpcClient::connectedChanged, this, &Engine::onConnectedChanged); - connect(m_deviceManager, &DeviceManager::fetchingDataChanged, this, &Engine::onDeviceManagerFetchingChanged); + connect(m_thingManager, &DeviceManager::fetchingDataChanged, this, &Engine::onDeviceManagerFetchingChanged); connect(AWSClient::instance(), &AWSClient::devicesFetched, this, [this]() { if (m_jsonRpcClient->connected() && m_jsonRpcClient->cloudConnectionState() == JsonRpcClient::CloudConnectionStateConnected) { @@ -74,7 +74,12 @@ Engine::Engine(QObject *parent) : DeviceManager *Engine::deviceManager() const { - return m_deviceManager; + return m_thingManager; +} + +DeviceManager *Engine::thingManager() const +{ + return m_thingManager; } RuleManager *Engine::ruleManager() const @@ -131,20 +136,20 @@ void Engine::deployCertificate() void Engine::onConnectedChanged() { qDebug() << "Engine: connected changed:" << m_jsonRpcClient->connected(); - m_deviceManager->clear(); + m_thingManager->clear(); m_ruleManager->clear(); m_tagsManager->clear(); if (m_jsonRpcClient->connected()) { qDebug() << "Engine: inital setup required:" << m_jsonRpcClient->initialSetupRequired() << "auth required:" << m_jsonRpcClient->authenticationRequired(); if (!m_jsonRpcClient->initialSetupRequired() && !m_jsonRpcClient->authenticationRequired()) { - m_deviceManager->init(); + m_thingManager->init(); } } } void Engine::onDeviceManagerFetchingChanged() { - if (!m_deviceManager->fetchingData()) { + if (!m_thingManager->fetchingData()) { m_ruleManager->init(); m_scriptManager->init(); m_nymeaConfiguration->init(); diff --git a/libnymea-app/engine.h b/libnymea-app/engine.h index 1f707854..300b7fb4 100644 --- a/libnymea-app/engine.h +++ b/libnymea-app/engine.h @@ -50,6 +50,7 @@ class Engine : public QObject { Q_OBJECT Q_PROPERTY(DeviceManager* deviceManager READ deviceManager CONSTANT) + Q_PROPERTY(DeviceManager* thingManager READ thingManager CONSTANT) Q_PROPERTY(RuleManager* ruleManager READ ruleManager CONSTANT) Q_PROPERTY(ScriptManager* scriptManager READ scriptManager CONSTANT) Q_PROPERTY(TagsManager* tagsManager READ tagsManager CONSTANT) @@ -64,6 +65,7 @@ public: QString connectedHost() const; DeviceManager *deviceManager() const; + DeviceManager *thingManager() const; RuleManager *ruleManager() const; ScriptManager *scriptManager() const; TagsManager *tagsManager() const; @@ -76,7 +78,7 @@ public: private: JsonRpcClient *m_jsonRpcClient; - DeviceManager *m_deviceManager; + DeviceManager *m_thingManager; RuleManager *m_ruleManager; ScriptManager *m_scriptManager; LogManager *m_logManager; diff --git a/libnymea-app/types/device.cpp b/libnymea-app/types/device.cpp index 13b4de90..eb3f845c 100644 --- a/libnymea-app/types/device.cpp +++ b/libnymea-app/types/device.cpp @@ -68,6 +68,11 @@ QUuid Device::deviceClassId() const return m_deviceClass->id(); } +QUuid Device::thingClassId() const +{ + return m_deviceClass->id(); +} + QUuid Device::parentDeviceId() const { return m_parentDeviceId; @@ -153,6 +158,11 @@ DeviceClass *Device::deviceClass() const return m_deviceClass; } +DeviceClass *Device::thingClass() const +{ + return m_deviceClass; +} + bool Device::hasState(const QUuid &stateTypeId) { foreach (State *state, states()->states()) { diff --git a/libnymea-app/types/device.h b/libnymea-app/types/device.h index bdd7dcbd..8eb97ab4 100644 --- a/libnymea-app/types/device.h +++ b/libnymea-app/types/device.h @@ -46,6 +46,7 @@ class Device : public QObject Q_OBJECT Q_PROPERTY(QUuid id READ id CONSTANT) Q_PROPERTY(QUuid deviceClassId READ deviceClassId CONSTANT) + Q_PROPERTY(QUuid thingClassId READ thingClassId CONSTANT) Q_PROPERTY(QUuid parentDeviceId READ parentDeviceId CONSTANT) Q_PROPERTY(bool isChild READ isChild CONSTANT) Q_PROPERTY(QString name READ name NOTIFY nameChanged) @@ -55,6 +56,7 @@ class Device : public QObject Q_PROPERTY(Params *settings READ settings NOTIFY settingsChanged) Q_PROPERTY(States *states READ states NOTIFY statesChanged) Q_PROPERTY(DeviceClass *deviceClass READ deviceClass CONSTANT) + Q_PROPERTY(DeviceClass *thingClass READ thingClass CONSTANT) public: enum DeviceSetupStatus { @@ -74,6 +76,7 @@ public: void setName(const QString &name); QUuid deviceClassId() const; + QUuid thingClassId() const; QUuid parentDeviceId() const; bool isChild() const; @@ -91,6 +94,7 @@ public: void setStates(States *states); DeviceClass *deviceClass() const; + DeviceClass *thingClass() const; Q_INVOKABLE bool hasState(const QUuid &stateTypeId); diff --git a/libnymea-app/types/deviceclass.cpp b/libnymea-app/types/deviceclass.cpp index d23bf06a..adecb674 100644 --- a/libnymea-app/types/deviceclass.cpp +++ b/libnymea-app/types/deviceclass.cpp @@ -129,8 +129,8 @@ QString DeviceClass::baseInterface() const if (interface == "blind") { return "blind"; } - if (interface == "garagegate") { - return "garagegate"; + if (interface == "garagedoor") { + return "garagedoor"; } if (interface == "inputtrigger") { return "inputtrigger"; diff --git a/libnymea-app/types/interfaces.cpp b/libnymea-app/types/interfaces.cpp index 9132ae15..c2bf41eb 100644 --- a/libnymea-app/types/interfaces.cpp +++ b/libnymea-app/types/interfaces.cpp @@ -199,7 +199,20 @@ Interfaces::Interfaces(QObject *parent) : QAbstractListModel(parent) pts = createParamTypes("user", tr("User"), QVariant::String); addActionType("useraccesscontrol", "removeUser", tr("Remove user"), pts); - addInterface("garagegate", tr("Garage doors"), {"closable"}); + addInterface("garagedoor", tr("Garage doors")); + + addInterface("impulsegaragedoor", tr("Garage doors"), {"garagedoor"}); + addActionType("impulsegaragedoor", "triggerImpulse", tr("Operate"), new ParamTypes()); + + addInterface("simplegaragedoor", tr("Garage doors"), {"garagedoor", "closable"}); + + addInterface("statefulgaragedoor", tr("Garage doors"), {"garagedoor", "closable"}); + addStateType("statefulgaragedoor", "state", QVariant::String, false, tr("State"), tr("State changed")); + + addInterface("extendedstatfulgaragedoor", tr("Garage doors"), {"statefulgaragedoor", "extendedclosable"}); + + // Deprecated garagegate + addInterface("garagegate", tr("Garage doors"), {"garagedoor", "closable"}); addStateType("garagegate", "state", QVariant::String, false, tr("State"), tr("State changed")); addStateType("garagegate", "intermediatePosition", QVariant::Bool, false, tr("Intermediate position"), tr("Intermediate position changed")); diff --git a/nymea-app/images.qrc b/nymea-app/images.qrc index 25d7aef3..3bdf89fb 100644 --- a/nymea-app/images.qrc +++ b/nymea-app/images.qrc @@ -131,7 +131,7 @@ ui/images/settings.svg ui/images/share.svg ui/images/slideshow.svg - ui/images/sort-listitem.svg + ui/images/closable-move.svg ui/images/starred.svg ui/images/state-interface.svg ui/images/state.svg diff --git a/nymea-app/resources.qrc b/nymea-app/resources.qrc index b9ca0f42..e5bcd139 100644 --- a/nymea-app/resources.qrc +++ b/nymea-app/resources.qrc @@ -67,7 +67,7 @@ ui/devicepages/InputTriggerDevicePage.qml ui/devicepages/StateLogPage.qml ui/devicepages/ShutterDevicePage.qml - ui/devicepages/GarageGateDevicePage.qml + ui/devicepages/GarageThingPage.qml ui/devicepages/AwningDevicePage.qml ui/devicepages/NotificationsDevicePage.qml ui/devicepages/LightDevicePage.qml @@ -75,7 +75,7 @@ ui/devicepages/DeviceLogPage.qml ui/devicelistpages/GenericDeviceListPage.qml ui/devicelistpages/ClosablesDeviceListPage.qml - ui/devicelistpages/GarageDeviceListPage.qml + ui/devicelistpages/GarageThingListPage.qml ui/devicelistpages/AwningDeviceListPage.qml ui/devicelistpages/ShutterDeviceListPage.qml ui/devicelistpages/BlindDeviceListPage.qml diff --git a/nymea-app/ui/Nymea.qml b/nymea-app/ui/Nymea.qml index 7d13099f..39026392 100644 --- a/nymea-app/ui/Nymea.qml +++ b/nymea-app/ui/Nymea.qml @@ -110,7 +110,7 @@ ApplicationWindow { "light", "weather", "media", - "garagegate", + "garagedoor", "awning", "shutter", "blind", @@ -172,7 +172,7 @@ ApplicationWindow { case "awning": case "extendedawning": return qsTr("Awnings"); - case "garagegate": + case "garagedoor": return qsTr("Garage doors"); case "accesscontrol": return qsTr("Access control"); @@ -209,6 +209,7 @@ ApplicationWindow { } function interfacesToIcon(interfaces) { + print("finding icon for interfaces:", interfaces) for (var i = 0; i < interfaces.length; i++) { var icon = interfaceToIcon(interfaces[i]); if (icon !== "") { @@ -219,6 +220,7 @@ ApplicationWindow { } function interfaceToIcon(name) { + print("finding icon for interface:", name) switch (name) { case "light": case "colorlight": @@ -279,7 +281,7 @@ ApplicationWindow { case "blind": case "extendedblind": return Qt.resolvedUrl("images/shutter/shutter-060.svg") - case "garagegate": + case "garagedoor": return Qt.resolvedUrl("images/garage/garage-100.svg") case "awning": case "extendedawning": @@ -289,7 +291,7 @@ ApplicationWindow { case "uncategorized": return Qt.resolvedUrl("images/select-none.svg") case "simpleclosable": - return Qt.resolvedUrl("images/sort-listitem.svg") + return Qt.resolvedUrl("images/closable-move.svg") case "fingerprintreader": return Qt.resolvedUrl("images/fingerprint.svg") case "accesscontrol": @@ -451,8 +453,8 @@ ApplicationWindow { page = "SensorDevicePage.qml"; } else if (interfaceList.indexOf("inputtrigger") >= 0) { page = "InputTriggerDevicePage.qml"; - } else if (interfaceList.indexOf("garagegate") >= 0 ) { - page = "GarageGateDevicePage.qml"; + } else if (interfaceList.indexOf("garagedoor") >= 0 ) { + page = "GarageThingPage.qml"; } else if (interfaceList.indexOf("light") >= 0) { page = "LightDevicePage.qml"; } else if (interfaceList.indexOf("shutter") >= 0 || interfaceList.indexOf("blind") >= 0) { diff --git a/nymea-app/ui/devicelistpages/GarageDeviceListPage.qml b/nymea-app/ui/devicelistpages/GarageDeviceListPage.qml deleted file mode 100644 index 09eef6c0..00000000 --- a/nymea-app/ui/devicelistpages/GarageDeviceListPage.qml +++ /dev/null @@ -1,36 +0,0 @@ -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -* -* Copyright 2013 - 2020, nymea GmbH -* Contact: contact@nymea.io -* -* This file is part of nymea. -* This project including source code and documentation is protected by -* copyright law, and remains the property of nymea GmbH. All rights, including -* reproduction, publication, editing and translation, are reserved. The use of -* this project is subject to the terms of a license agreement to be concluded -* with nymea GmbH in accordance with the terms of use of nymea GmbH, available -* under https://nymea.io/license -* -* GNU General Public License Usage -* Alternatively, this project may be redistributed and/or modified under the -* terms of the GNU General Public License as published by the Free Software -* Foundation, GNU version 3. This project 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 -* this project. If not, see . -* -* For any further details and any questions please contact us under -* contact@nymea.io or see our FAQ/Licensing Information on -* https://nymea.io/license/faq -* -* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ - -import QtQuick 2.9 - -ClosablesDeviceListPage { - title: qsTr("Garage gates") - iconBasename: "../images/shutter/shutter" -} diff --git a/nymea-app/ui/devicelistpages/GarageThingListPage.qml b/nymea-app/ui/devicelistpages/GarageThingListPage.qml new file mode 100644 index 00000000..694f5946 --- /dev/null +++ b/nymea-app/ui/devicelistpages/GarageThingListPage.qml @@ -0,0 +1,287 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2020, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU General Public License as published by the Free Software +* Foundation, GNU version 3. This project 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 +* this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +import QtQuick 2.5 +import QtQuick.Controls 2.1 +import QtQuick.Controls.Material 2.1 +import QtQuick.Layouts 1.1 +import QtGraphicalEffects 1.0 +import Nymea 1.0 +import "../components" + +DeviceListPageBase { + id: root + + property string iconBasename: "../images/garage/garage" + + header: NymeaHeader { + id: header + onBackPressed: pageStack.pop() + text: root.title + } + + ListView { + anchors.fill: parent + model: devicesProxy + spacing: app.margins + + delegate: Pane { + id: itemDelegate + width: parent.width + + + property bool inline: width > 500 + + property Device device: devicesProxy.getDevice(model.id) + property Device thing: device + property DeviceClass deviceClass: device.deviceClass + + readonly property bool isImpulseBased: device.deviceClass.interfaces.indexOf("impulsegaragedoor") >= 0 + readonly property bool isStateful: device.deviceClass.interfaces.indexOf("statefulgaragedoor") >= 0 + || device.deviceClass.interfaces.indexOf("garagegate") >= 0 // garagegate did not inherit garagedoor before 0.23 + readonly property bool isExtended: device.deviceClass.interfaces.indexOf("extendedstatefulgaragedoor") >= 0 + + property var connectedStateType: deviceClass.stateTypes.findByName("connected"); + property var connectedState: connectedStateType ? device.states.getState(connectedStateType.id) : null + + + property StateType movingStateType: deviceClass.stateTypes.findByName("moving"); + property ActionType movingActionType: deviceClass.actionTypes.findByName("moving"); + property State movingState: movingStateType ? device.states.getState(movingStateType.id) : null + + Material.elevation: 1 + topPadding: 0 + bottomPadding: 0 + leftPadding: 0 + rightPadding: 0 + contentItem: ItemDelegate { + id: contentItem + implicitHeight: contentLoader.item.implicitHeight + + topPadding: 0 + + contentItem: Loader { + id: contentLoader + enabled: itemDelegate.connectedState === null || itemDelegate.connectedState.value === true + sourceComponent: isImpulseBased ? impulseGaragedoor + : isExtended ? extendedStatefulGaragedoor + : isStateful ? garagedoor + : simpleGaragedoor + Binding { target: contentLoader.item; property: "device"; value: itemDelegate.device } + } + onClicked: { + enterPage(index, false) + } + } + } + } + + Component { + id: impulseGaragedoor + + RowLayout { + id: contentItem + property Device device: null + + spacing: app.margins + Item { + Layout.preferredHeight: app.iconSize + Layout.preferredWidth: height + Layout.alignment: Qt.AlignVCenter + + ColorIcon { + id: icon + anchors.fill: parent + name: root.iconBasename + "-100.svg" + } + } + + Label { + Layout.fillWidth: true + text: contentItem.device.name + elide: Text.ElideRight + verticalAlignment: Text.AlignVCenter + } + + ItemDelegate { + Layout.preferredHeight: app.iconSize * 2 + Layout.preferredWidth: height + ColorIcon { + anchors.centerIn: parent + height: app.iconSize + width: height + name: "../images/closable-move.svg" + } + onClicked: { + var actionTypeId = device.thingClass.actionTypes.findByName("triggerImpulse").id + print("Triggering impulse", actionTypeId) + engine.thingManager.executeAction(device.id, actionTypeId) + } + } + } + } + + Component { + id: simpleGaragedoor + + RowLayout { + id: contentItem + spacing: app.margins + property Device device: null + + Item { + Layout.preferredHeight: app.iconSize + Layout.preferredWidth: height + Layout.alignment: Qt.AlignVCenter + + ColorIcon { + id: icon + anchors.fill: parent + color: keyColor + name: root.iconBasename + "-100.svg" + } + } + + Label { + Layout.fillWidth: true + text: contentItem.device.name + elide: Text.ElideRight + verticalAlignment: Text.AlignVCenter + } + + Item { + Layout.preferredWidth: shutterControls.implicitWidth + Layout.preferredHeight: app.iconSize * 2 + ShutterControls { + id: shutterControls + height: parent.height + device: contentItem.device + } + } + } + } + + + Component { + id: garagedoor + + RowLayout { + id: contentItem + spacing: app.margins + property Device device: null + + property StateType stateStateType: device.deviceClass.stateTypes.findByName("state") + property State stateState: stateStateType ? device.states.getState(stateStateType.id) : null + Item { + Layout.preferredHeight: app.iconSize + Layout.preferredWidth: height + Layout.alignment: Qt.AlignVCenter + + ColorIcon { + id: icon + anchors.fill: parent + color: ["opening", "closing"].indexOf(contentItem.stateState.value) >= 0 + ? app.accentColor + : keyColor + name: root.iconBasename + "-100.svg" + } + } + + Label { + Layout.fillWidth: true + text: contentItem.device.name + elide: Text.ElideRight + verticalAlignment: Text.AlignVCenter + } + + Item { + Layout.preferredWidth: shutterControls.implicitWidth + Layout.preferredHeight: app.iconSize * 2 + ShutterControls { + id: shutterControls + height: parent.height + device: contentItem.device + } + } + } + } + + + Component { + id: extendedStatefulGaragedoor + + RowLayout { + id: contentItem + spacing: app.margins + property Device device: null + + property StateType stateStateType: device.deviceClass.stateTypes.findByName("state") + property State stateState: stateStateType ? device.states.getState(stateStateType.id) : null + + property StateType percentageStateType: device.deviceClass.stateTypes.findByName("percentage"); + property ActionType percentageActionType: device.deviceClass.actionTypes.findByName("percentage"); + property State percentageState: percentageStateType ? device.states.getState(percentageStateType.id) : null + + Item { + Layout.preferredHeight: app.iconSize + Layout.preferredWidth: height + Layout.alignment: Qt.AlignVCenter + + ColorIcon { + id: icon + anchors.fill: parent + color: ["opening", "closing"].indexOf(contentItem.stateState.value) >= 0 + ? app.accentColor + : keyColor + name: contentItem.percentageStateType + ? root.iconBasename + "-" + app.pad(Math.round(contentItem.percentageState.value / 10) * 10, 3) + ".svg" + : root.iconBasename + "-050.svg" + } + } + + Label { + Layout.fillWidth: true + text: contentItem.device.name + elide: Text.ElideRight + verticalAlignment: Text.AlignVCenter + } + + Item { + Layout.preferredWidth: shutterControls.implicitWidth + Layout.preferredHeight: app.iconSize * 2 + ShutterControls { + id: shutterControls + height: parent.height + device: contentItem.device + } + } + } + } +} diff --git a/nymea-app/ui/devicepages/DevicePageBase.qml b/nymea-app/ui/devicepages/DevicePageBase.qml index 8d099d56..b6be9119 100644 --- a/nymea-app/ui/devicepages/DevicePageBase.qml +++ b/nymea-app/ui/devicepages/DevicePageBase.qml @@ -39,6 +39,8 @@ Page { property Device device: null readonly property DeviceClass deviceClass: device.deviceClass + readonly property Device thing: device + property bool showLogsButton: true property bool showDetailsButton: true property bool showBrowserButton: true diff --git a/nymea-app/ui/devicepages/GarageGateDevicePage.qml b/nymea-app/ui/devicepages/GarageThingPage.qml similarity index 63% rename from nymea-app/ui/devicepages/GarageGateDevicePage.qml rename to nymea-app/ui/devicepages/GarageThingPage.qml index 7aa21047..e12e34cf 100644 --- a/nymea-app/ui/devicepages/GarageGateDevicePage.qml +++ b/nymea-app/ui/devicepages/GarageThingPage.qml @@ -40,10 +40,31 @@ DevicePageBase { id: root readonly property bool landscape: width > height - readonly property var openState: device.states.getState(deviceClass.stateTypes.findByName("state").id) - readonly property var intermediatePositionState: device.states.getState(deviceClass.stateTypes.findByName("intermediatePosition").id) - readonly property var lightStateType: deviceClass.stateTypes.findByName("power") - readonly property var lightState: lightStateType ? device.states.getState(lightStateType.id) : null + + readonly property bool isImpulseBased: thing.thingClass.interfaces.indexOf("impulsegaragedoor") >= 0 + readonly property bool isStateful: thing.thingClass.interfaces.indexOf("statefulgaragedoor") >= 0 + readonly property bool isExtended: thing.thingClass.interfaces.indexOf("extendedstatefulgaragedoor") >= 0 + + // Stateful garagedoor + readonly property StateType stateStateType: thing.thingClass.stateTypes.findByName("state") + readonly property State stateState: stateStateType ? thing.states.getState(stateStateType.id) : null + + // Extended stateful garagedoor + readonly property StateType percentageStateType: thing.thingClass.stateTypes.findByName("percentage") + readonly property State percentageState: percentageStateType ? thing.states.getState(percentageStateType.id) : null + + + // Backward compatiblity with old garagegate interface + readonly property StateType intermediatePositionStateType: thing.thingClass.stateTypes.findByName("intermediatePosition") + readonly property var intermediatePositionState: intermediatePositionStateType ? device.states.getState(intermediatePositionStateType.id) : null + + // Some garages may also implement the light interface + readonly property var lightStateType: thing.thingClass.stateTypes.findByName("power") + readonly property var lightState: lightStateType ? thing.states.getState(lightStateType.id) : null + + Component.onCompleted: { + print("Creating garage page. Impulse based:", isImpulseBased, "stateful:", isStateful, "extended:", isExtended, "legacy:", intermediatePositionState !== null) + } GridLayout { anchors.fill: parent @@ -56,8 +77,16 @@ DevicePageBase { : Math.min(Math.min(parent.width, 500), parent.height - shutterControlsContainer.minimumHeight) Layout.preferredHeight: width Layout.alignment: Qt.AlignHCenter - property string currentImage: root.openState.value === "closed" ? "100" : - root.openState.value === "open" && root.intermediatePositionState.value === false ? "000" : "050" + property string currentImage: { + if (root.isExtended) { + return app.pad(Math.round(root.percentageState.value / 10), 2) + "0" + } + if (root.intermediatePositionStateType) { + return root.stateState.value === "closed" ? "100" + : root.intermediatePositionState.value === false ? "000" : "050" + } + return "100" + } name: "../images/garage/garage-" + currentImage + ".svg" Item { @@ -66,8 +95,8 @@ DevicePageBase { width: app.iconSize * 2 height: parent.height * .6 clip: true - visible: root.openState.value === "opening" || root.openState.value === "closing" - property bool up: root.openState.value === "opening" + visible: root.stateStateType && (root.stateState.value === "opening" || root.stateState.value === "closing") + property bool up: root.stateState && root.stateState.value === "opening" // NumberAnimation doesn't reload to/from while it's running. If we switch from closing to opening or vice versa // we need to somehow stop and start the animation @@ -76,7 +105,7 @@ DevicePageBase { if (!animationHack) hackTimer.start(); } Timer { id: hackTimer; interval: 1; onTriggered: arrows.animationHack = true } - Connections { target: root.openState; onValueChanged: arrows.animationHack = false } + Connections { target: root.stateState; onValueChanged: arrows.animationHack = false } NumberAnimation { target: arrowColumn @@ -86,7 +115,7 @@ DevicePageBase { from: arrows.up ? app.iconSize : -app.iconSize to: arrows.up ? -app.iconSize : app.iconSize loops: Animation.Infinite - running: arrows.animationHack && (root.openState.value === "opening" || root.openState.value === "closing") + running: arrows.animationHack && root.stateState && (root.stateState.value === "opening" || root.stateState.value === "closing") } Column { @@ -114,11 +143,29 @@ DevicePageBase { property int minimumWidth: app.iconSize * 2.5 * (root.lightState ? 4 : 3) property int minimumHeight: app.iconSize * 2.5 + ItemDelegate { + height: app.iconSize * 2 + width: height + anchors.centerIn: parent + visible: root.isImpulseBased + ColorIcon { + anchors.fill: parent + name: "../images/closable-move.svg" + anchors.margins: app.margins + } + onClicked: { + var actionTypeId = root.thing.thingClass.actionTypes.findByName("triggerImpulse").id + print("Triggering impulse", actionTypeId) + engine.thingManager.executeAction(root.thing.id, actionTypeId) + } + } + ShutterControls { id: shutterControls device: root.device anchors.centerIn: parent spacing: (parent.width - app.iconSize*2*children.length) / (children.length - 1) + visible: !root.isImpulseBased ItemDelegate { width: app.iconSize * 2 diff --git a/nymea-app/ui/experiences/garagegates/Main.qml b/nymea-app/ui/experiences/garagegates/Main.qml index 2d7c19ba..11e91684 100644 --- a/nymea-app/ui/experiences/garagegates/Main.qml +++ b/nymea-app/ui/experiences/garagegates/Main.qml @@ -36,19 +36,19 @@ import Nymea 1.0 Item { id: root - readonly property string title: qsTr("Garage gates") + readonly property string title: qsTr("Garage doors") readonly property string icon: Qt.resolvedUrl("qrc:/ui/images/shutter/shutter-050.svg") DevicesProxy { id: garagesFilterModel engine: _engine - shownInterfaces: ["garagegate"] + shownInterfaces: ["garagedoors"] } EmptyViewPlaceholder { anchors.centerIn: parent width: parent.width - app.margins * 2 - text: qsTr("There are no garage gates set up yet.") + text: qsTr("There are no garage doors set up yet.") imageSource: "qrc:/ui/images/shutter/shutter-050.svg" buttonText: qsTr("Set up now") visible: garagesFilterModel.count === 0 diff --git a/nymea-app/ui/images/sort-listitem.svg b/nymea-app/ui/images/closable-move.svg similarity index 100% rename from nymea-app/ui/images/sort-listitem.svg rename to nymea-app/ui/images/closable-move.svg diff --git a/nymea-app/ui/mainviews/DevicesPageDelegate.qml b/nymea-app/ui/mainviews/DevicesPageDelegate.qml index 74369b97..82356c93 100644 --- a/nymea-app/ui/mainviews/DevicesPageDelegate.qml +++ b/nymea-app/ui/mainviews/DevicesPageDelegate.qml @@ -70,8 +70,9 @@ MainPageTile { case "smartmeter": page ="SmartMeterDeviceListPage.qml"; break; - case "garagegate": - page = "GarageDeviceListPage.qml"; + case "garagegate": // Deprecated, might not inherit garagedoor in old versions + case "garagedoor": + page = "GarageThingListPage.qml"; break; case "awning": case "extendedAwning": diff --git a/nymea-app/ui/system/AboutNymeaPage.qml b/nymea-app/ui/system/AboutNymeaPage.qml index be19fc76..cbbc9757 100644 --- a/nymea-app/ui/system/AboutNymeaPage.qml +++ b/nymea-app/ui/system/AboutNymeaPage.qml @@ -77,7 +77,7 @@ SettingsPageBase { Layout.fillWidth: true text: qsTr("Qt version:") visible: engine.jsonRpcClient.ensureServerVersion("4.1") - subText: engine.jsonRpcClient.serverQtVersion + (engine.jsonRpcClient.serverQtVersion !== engine.jsonRpcClient.serverQtBuildVersion ? + " (" + qsTr("Built with %1").arg(engine.jsonRpcClient.serverQtBuildVersion) + ")" : "") + subText: engine.jsonRpcClient.serverQtVersion + (engine.jsonRpcClient.serverQtVersion !== engine.jsonRpcClient.serverQtBuildVersion ? " (" + qsTr("Built with %1").arg(engine.jsonRpcClient.serverQtBuildVersion) + ")" : "") progressive: false prominentSubText: false }