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
}