diff --git a/libnymea-app/thingsproxy.cpp b/libnymea-app/thingsproxy.cpp index f826ec90..a047645d 100644 --- a/libnymea-app/thingsproxy.cpp +++ b/libnymea-app/thingsproxy.cpp @@ -391,6 +391,22 @@ void ThingsProxy::setFilterUpdates(bool filterUpdates) } } +QVariantMap ThingsProxy::paramsFilter() const +{ + return m_paramsFilter; +} + +void ThingsProxy::setParamsFilter(const QVariantMap ¶msFilter) +{ + if (m_paramsFilter != paramsFilter) { + m_paramsFilter = paramsFilter; + emit paramsFilterChanged(); + + invalidateFilter(); + emit countChanged(); + } +} + bool ThingsProxy::groupByInterface() const { return m_groupByInterface; @@ -590,5 +606,14 @@ bool ThingsProxy::filterAcceptsRow(int source_row, const QModelIndex &source_par } } + if (!m_paramsFilter.isEmpty()) { + foreach (const QString ¶mName, m_paramsFilter.keys()) { + Param *param = thing->paramByName(paramName); + if (!param || param->value() != m_paramsFilter.value(paramName)) { + return false; + } + } + } + return QSortFilterProxyModel::filterAcceptsRow(source_row, source_parent); } diff --git a/libnymea-app/thingsproxy.h b/libnymea-app/thingsproxy.h index 1fb32467..591d8411 100644 --- a/libnymea-app/thingsproxy.h +++ b/libnymea-app/thingsproxy.h @@ -75,6 +75,9 @@ class ThingsProxy : public QSortFilterProxyModel Q_PROPERTY(bool filterUpdates READ filterUpdates WRITE setFilterUpdates NOTIFY filterUpdatesChanged) + // A map of paramName:value pairs, all given need to match + Q_PROPERTY(QVariantMap paramsFilter READ paramsFilter WRITE setParamsFilter NOTIFY paramsFilterChanged) + Q_PROPERTY(bool groupByInterface READ groupByInterface WRITE setGroupByInterface NOTIFY groupByInterfaceChanged) public: @@ -143,6 +146,9 @@ public: bool filterUpdates() const; void setFilterUpdates(bool filterUpdates); + QVariantMap paramsFilter() const; + void setParamsFilter(const QVariantMap ¶msFilter); + bool groupByInterface() const; void setGroupByInterface(bool groupByInterface); @@ -172,6 +178,7 @@ signals: void filterDisconnectedChanged(); void filterSetupFailedChanged(); void filterUpdatesChanged(); + void paramsFilterChanged(); void groupByInterfaceChanged(); void countChanged(); @@ -203,6 +210,8 @@ private: bool m_filterSetupFailed = false; bool m_filterUpdates = false; + QVariantMap m_paramsFilter; + bool m_groupByInterface = false; protected: diff --git a/libnymea-app/zigbee/zigbeemanager.cpp b/libnymea-app/zigbee/zigbeemanager.cpp index 1766df15..4429ccb1 100644 --- a/libnymea-app/zigbee/zigbeemanager.cpp +++ b/libnymea-app/zigbee/zigbeemanager.cpp @@ -93,12 +93,16 @@ ZigbeeNetworks *ZigbeeManager::networks() const return m_networks; } -int ZigbeeManager::addNetwork(const QString &serialPort, uint baudRate, const QString &backend) +int ZigbeeManager::addNetwork(const QString &serialPort, uint baudRate, const QString &backend, ZigbeeChannels channels) { QVariantMap params; params.insert("serialPort", serialPort); params.insert("baudRate", baudRate); params.insert("backend", backend); + qWarning() << "************ channel mask!" << channels; + if (m_engine->jsonRpcClient()->ensureServerVersion("5.8")) { + params.insert("channelMask", static_cast(channels)); + } qCDebug(dcZigbee()) << "Add zigbee network" << params; return m_engine->jsonRpcClient()->sendCommand("Zigbee.AddNetwork", params, this, "addNetworkResponse"); diff --git a/libnymea-app/zigbee/zigbeemanager.h b/libnymea-app/zigbee/zigbeemanager.h index cfa8bf73..680049a8 100644 --- a/libnymea-app/zigbee/zigbeemanager.h +++ b/libnymea-app/zigbee/zigbeemanager.h @@ -52,6 +52,29 @@ class ZigbeeManager : public QObject Q_PROPERTY(ZigbeeNetworks *networks READ networks CONSTANT) public: + enum ZigbeeChannel { + ZigbeeChannel11 = 0x00000800, + ZigbeeChannel12 = 0x00001000, + ZigbeeChannel13 = 0x00002000, + ZigbeeChannel14 = 0x00004000, + ZigbeeChannel15 = 0x00008000, + ZigbeeChannel16 = 0x00010000, + ZigbeeChannel17 = 0x00020000, + ZigbeeChannel18 = 0x00040000, + ZigbeeChannel19 = 0x00080000, + ZigbeeChannel20 = 0x00100000, + ZigbeeChannel21 = 0x00200000, + ZigbeeChannel22 = 0x00400000, + ZigbeeChannel23 = 0x00800000, + ZigbeeChannel24 = 0x01000000, + ZigbeeChannel25 = 0x02000000, + ZigbeeChannel26 = 0x04000000, + ZigbeeChannelPrimaryLightLink = 0x02108800, + ZigbeeChannelAll = 0x07fff800 + }; + Q_DECLARE_FLAGS(ZigbeeChannels, ZigbeeChannel) + Q_FLAG(ZigbeeChannels) + explicit ZigbeeManager(QObject *parent = nullptr); ~ZigbeeManager(); @@ -63,7 +86,7 @@ public: ZigbeeNetworks *networks() const; // Network - Q_INVOKABLE int addNetwork(const QString &serialPort, uint baudRate, const QString &backend); + Q_INVOKABLE int addNetwork(const QString &serialPort, uint baudRate, const QString &backend, ZigbeeChannels channels); Q_INVOKABLE void removeNetwork(const QUuid &networkUuid); Q_INVOKABLE void setPermitJoin(const QUuid &networkUuid, uint duration = 120); Q_INVOKABLE void factoryResetNetwork(const QUuid &networkUuid); diff --git a/libnymea-app/zigbee/zigbeenodesproxy.cpp b/libnymea-app/zigbee/zigbeenodesproxy.cpp index f145529b..2149b448 100644 --- a/libnymea-app/zigbee/zigbeenodesproxy.cpp +++ b/libnymea-app/zigbee/zigbeenodesproxy.cpp @@ -37,7 +37,7 @@ ZigbeeNodesProxy::ZigbeeNodesProxy(QObject *parent) : QSortFilterProxyModel(parent) { - + sort(0); } ZigbeeNodes *ZigbeeNodesProxy::zigbeeNodes() const @@ -59,6 +59,12 @@ void ZigbeeNodesProxy::setZigbeeNodes(ZigbeeNodes *zigbeeNodes) sort(0, Qt::AscendingOrder); emit countChanged(); }); + connect(m_zigbeeNodes, &ZigbeeNodes::rowsInserted, this, [this](const QModelIndex &parent, int first, int last){ + Q_UNUSED(parent) + for (int i = first; i <= last; i++) { + m_newNodes.insert(m_zigbeeNodes->get(i), QDateTime::currentDateTime()); + } + }); setSourceModel(m_zigbeeNodes); @@ -69,6 +75,36 @@ void ZigbeeNodesProxy::setZigbeeNodes(ZigbeeNodes *zigbeeNodes) emit countChanged(); } +bool ZigbeeNodesProxy::showCoordinator() const +{ + return m_showCoordinator; +} + +void ZigbeeNodesProxy::setShowCoordinator(bool showCoordinator) +{ + if (m_showCoordinator != showCoordinator) { + m_showCoordinator = showCoordinator; + emit showCoordinatorChanged(); + + invalidateFilter(); + emit countChanged(); + } +} + +bool ZigbeeNodesProxy::newOnTop() const +{ + return m_newOnTop; +} + +void ZigbeeNodesProxy::setNewOnTop(bool newOnTop) +{ + if (m_newOnTop != newOnTop) { + m_newOnTop = newOnTop; + emit newOnTopChanged(); + invalidate(); + } +} + ZigbeeNode *ZigbeeNodesProxy::get(int index) const { if (index >= 0 && index < m_zigbeeNodes->rowCount()) { @@ -76,3 +112,31 @@ ZigbeeNode *ZigbeeNodesProxy::get(int index) const } return nullptr; } + +bool ZigbeeNodesProxy::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const +{ + Q_UNUSED(source_parent) + ZigbeeNode *node = m_zigbeeNodes->get(source_row); + if (!m_showCoordinator && node->type() == ZigbeeNode::ZigbeeNodeTypeCoordinator) { + return false; + } + return true; +} + +bool ZigbeeNodesProxy::lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const +{ + if (m_newOnTop) { + ZigbeeNode *left = m_zigbeeNodes->get(source_left.row()); + ZigbeeNode *right = m_zigbeeNodes->get(source_right.row()); + if (m_newNodes.contains(left) && !m_newNodes.contains(right)) { + return true; + } + if (!m_newNodes.contains(left) && !m_newNodes.contains(right)) { + return false; + } + if (m_newNodes.contains(left) && m_newNodes.contains(right)) { + return m_newNodes.value(left) > m_newNodes.value(right); + } + } + return QSortFilterProxyModel::lessThan(source_left, source_right); +} diff --git a/libnymea-app/zigbee/zigbeenodesproxy.h b/libnymea-app/zigbee/zigbeenodesproxy.h index 11afab4c..cc103659 100644 --- a/libnymea-app/zigbee/zigbeenodesproxy.h +++ b/libnymea-app/zigbee/zigbeenodesproxy.h @@ -34,7 +34,7 @@ #include #include -class ZigbeeNode; +#include "zigbeenode.h" class ZigbeeNodes; class ZigbeeNodesProxy : public QSortFilterProxyModel @@ -42,6 +42,9 @@ class ZigbeeNodesProxy : public QSortFilterProxyModel Q_OBJECT Q_PROPERTY(int count READ rowCount NOTIFY countChanged) Q_PROPERTY(ZigbeeNodes *zigbeeNodes READ zigbeeNodes WRITE setZigbeeNodes NOTIFY zigbeeNodesChanged) + Q_PROPERTY(bool showCoordinator READ showCoordinator WRITE setShowCoordinator NOTIFY showCoordinatorChanged) + + Q_PROPERTY(bool newOnTop READ newOnTop WRITE setNewOnTop NOTIFY newOnTopChanged) public: explicit ZigbeeNodesProxy(QObject *parent = nullptr); @@ -49,15 +52,32 @@ public: ZigbeeNodes *zigbeeNodes() const; void setZigbeeNodes(ZigbeeNodes *zigbeeNodes); + bool showCoordinator() const; + void setShowCoordinator(bool showCoordinator); + + bool newOnTop() const; + void setNewOnTop(bool newOnTop); + Q_INVOKABLE ZigbeeNode *get(int index) const; signals: void countChanged(); void zigbeeNodesChanged(ZigbeeNodes *zigbeeNodes); + void showCoordinatorChanged(); + void newOnTopChanged(); + +protected: + bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override; + bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const override; private: ZigbeeNodes *m_zigbeeNodes = nullptr; + bool m_showCoordinator = true; + + bool m_newOnTop = false; + + QHash m_newNodes; }; #endif // ZIGBEENODESPROXY_H diff --git a/nymea-app/ui/components/NymeaSwipeDelegate.qml b/nymea-app/ui/components/NymeaSwipeDelegate.qml index 581fa968..afaf3130 100644 --- a/nymea-app/ui/components/NymeaSwipeDelegate.qml +++ b/nymea-app/ui/components/NymeaSwipeDelegate.qml @@ -120,6 +120,8 @@ SwipeDelegate { BusyIndicator { id: busyIndicator anchors.centerIn: parent + width: Style.bigIconSize + height: width visible: running running: false } diff --git a/nymea-app/ui/system/ZigbeeAddNetworkPage.qml b/nymea-app/ui/system/ZigbeeAddNetworkPage.qml index 5ab8a972..536e41af 100644 --- a/nymea-app/ui/system/ZigbeeAddNetworkPage.qml +++ b/nymea-app/ui/system/ZigbeeAddNetworkPage.qml @@ -38,45 +38,10 @@ import "../components" SettingsPageBase { id: root title: qsTr("Add a new ZigBee network") - busy: d.pendingCommandId != -1 property ZigbeeManager zigbeeManager: null - - QtObject { - id: d - property int pendingCommandId: -1 - - function addNetwork(serialPort, baudRate, backend) { - d.pendingCommandId = root.zigbeeManager.addNetwork(serialPort, baudRate, backend) - } - } - - Connections { - target: root.zigbeeManager - onAddNetworkReply: { - if (commandId == d.pendingCommandId) { - d.pendingCommandId = -1 - var props = {}; - switch (error) { - case "ZigbeeErrorNoError": - pageStack.pop(); - return; - case "ZigbeeErrorAdapterNotAvailable": - props.text = qsTr("The selected adapter is not available or the selected serial port configration is incorrect."); - break; - case "ZigbeeErrorAdapterAlreadyInUse": - props.text = qsTr("The selected adapter is already in use."); - break; - default: - props.errorCode = error; - } - var comp = Qt.createComponent("../components/ErrorDialog.qml") - var popup = comp.createObject(app, props) - popup.open(); - } - } - } + signal done(); SettingsPageSectionHeader { text: qsTr("Hardware not available") @@ -84,10 +49,10 @@ SettingsPageBase { } RowLayout { - Layout.leftMargin: app.margins - Layout.rightMargin: app.margins + Layout.leftMargin: Style.margins + Layout.rightMargin: Style.margins visible: root.zigbeeManager.adapters.count == 0 - spacing: app.margins + spacing: Style.margins ColorIcon { Layout.preferredHeight: Style.iconSize Layout.preferredWidth: Style.iconSize @@ -107,7 +72,7 @@ SettingsPageBase { Label { Layout.fillWidth: true - Layout.leftMargin: app.margins; Layout.rightMargin: app.margins + Layout.leftMargin: Style.margins; Layout.rightMargin: Style.margins text: qsTr("Please select the ZigBee adapter on which the new network will be created.") font.pixelSize: app.smallFont wrapMode: Text.WordWrap @@ -128,7 +93,9 @@ SettingsPageBase { Layout.fillWidth: true iconName: "../images/zigbee.svg" text: model.backend + " - " + model.description + " - " + model.serialPort - onClicked: d.addNetwork(model.serialPort, model.baudRate, model.backend) + onClicked: { + pageStack.push(addSettingsPageComponent, {serialPort: model.serialPort, baudRate: model.baudRate, backend: model.backend, allowSerialPortSettings: false}) + } } } @@ -138,7 +105,7 @@ SettingsPageBase { } Label { - Layout.fillWidth: true; Layout.leftMargin: app.margins; Layout.rightMargin: app.margins + Layout.fillWidth: true; Layout.leftMargin: Style.margins; Layout.rightMargin: Style.margins wrapMode: Text.WordWrap font.pixelSize: app.smallFont text: qsTr("Please verify that the ZigBee adapter is properly connected to a serial port and select the appropriate port.") @@ -160,27 +127,69 @@ SettingsPageBase { iconName: "../images/stock_usb.svg" text: model.description + " - " + model.serialPort onClicked: { - var dialog = serialPortOptionsDialogComponent.createObject(app, {serialPort: model.serialPort, baudRate: model.baudRate, backend: model.backend}) - dialog.open() + pageStack.push(addSettingsPageComponent, {serialPort: model.serialPort, baudRate: model.baudRate, backend: model.backend, allowSerialPortSettings: true}) } } } Component { - id: serialPortOptionsDialogComponent - MeaDialog { - id: serialPortOptionsDialog + id: addSettingsPageComponent + SettingsPageBase { + id: addSettingsPage + title: qsTr("Add ZigBee network") + busy: d.pendingCommandId != -1 + + property bool allowSerialPortSettings: false property string serialPort property int baudRate property string backend - headerIcon: "../images/stock_usb.svg" - title: qsTr("Serial port options") - text: qsTr("Please select the serial port options for using the ZigBee adapter") - standardButtons: Dialog.Ok | Dialog.Cancel + QtObject { + id: d + property int pendingCommandId: -1 + } + + Connections { + target: root.zigbeeManager + onAddNetworkReply: { + if (commandId == d.pendingCommandId) { + d.pendingCommandId = -1 + var props = {}; + switch (error) { + case "ZigbeeErrorNoError": + root.done() + return; + case "ZigbeeErrorAdapterNotAvailable": + props.text = qsTr("The selected adapter is not available or the selected serial port configration is incorrect."); + break; + case "ZigbeeErrorAdapterAlreadyInUse": + props.text = qsTr("The selected adapter is already in use."); + break; + default: + props.errorCode = error; + } + var comp = Qt.createComponent("../components/ErrorDialog.qml") + var popup = comp.createObject(app, props) + popup.open(); + } + } + } + + SettingsPageSectionHeader { + text: qsTr("Serial port options") + visible: addSettingsPage.allowSerialPortSettings + } + Label { + visible: addSettingsPage.allowSerialPortSettings + Layout.fillWidth: true; Layout.leftMargin: Style.margins; Layout.rightMargin: Style.margins + wrapMode: Text.WordWrap + text: qsTr("Please select the serial port options for using the ZigBee adapter") + } RowLayout { + Layout.fillWidth: true; Layout.leftMargin: Style.margins; Layout.rightMargin: Style.margins + visible: addSettingsPage.allowSerialPortSettings Label { text: qsTr("Adapter") Layout.fillWidth: true @@ -189,12 +198,14 @@ SettingsPageBase { id: backendComboBox model: root.zigbeeManager.availableBackends Component.onCompleted: { - currentIndex = backendComboBox.find(serialPortOptionsDialog.backend) + currentIndex = backendComboBox.find(addSettingsPage.backend) } } } RowLayout { + Layout.fillWidth: true; Layout.leftMargin: Style.margins; Layout.rightMargin: Style.margins + visible: addSettingsPage.allowSerialPortSettings Label { text: qsTr("Baud rate") Layout.fillWidth: true @@ -203,13 +214,55 @@ SettingsPageBase { id: baudRateComboBox model: ["9600", "14400", "19200", "38400", "57600", "115200", "128000", "230400", "256000"] Component.onCompleted: { - currentIndex = baudRateComboBox.find(serialPortOptionsDialog.baudRate) + currentIndex = baudRateComboBox.find(addSettingsPage.baudRate) } } } - onAccepted: { - d.addNetwork(serialPortOptionsDialog.serialPort, baudRateComboBox.currentText, backendComboBox.currentText) + SettingsPageSectionHeader { + text: qsTr("Zigbee network settings") + } + + RowLayout { + Layout.fillWidth: true; Layout.leftMargin: Style.margins; Layout.rightMargin: Style.margins + Label { + text: qsTr("Channel") + Layout.fillWidth: true + } + ComboBox { + id: channelCombobox + model: ListModel { + id: channelsModel + ListElement { modelData: qsTr("Auto"); channel: ZigbeeManager.ZigbeeChannelAll } + ListElement { modelData: qsTr("Auto light link"); channel: ZigbeeManager.ZigbeeChannelPrimaryLightLink } + ListElement { modelData: "11"; channel: ZigbeeManager.ZigbeeChannel11 } + ListElement { modelData: "12"; channel: ZigbeeManager.ZigbeeChannel12 } + ListElement { modelData: "13"; channel: ZigbeeManager.ZigbeeChannel13 } + ListElement { modelData: "14"; channel: ZigbeeManager.ZigbeeChannel14 } + ListElement { modelData: "15"; channel: ZigbeeManager.ZigbeeChannel15 } + ListElement { modelData: "16"; channel: ZigbeeManager.ZigbeeChannel16 } + ListElement { modelData: "17"; channel: ZigbeeManager.ZigbeeChannel17 } + ListElement { modelData: "18"; channel: ZigbeeManager.ZigbeeChannel18 } + ListElement { modelData: "19"; channel: ZigbeeManager.ZigbeeChannel19 } + ListElement { modelData: "20"; channel: ZigbeeManager.ZigbeeChannel20 } + ListElement { modelData: "21"; channel: ZigbeeManager.ZigbeeChannel21 } + ListElement { modelData: "22"; channel: ZigbeeManager.ZigbeeChannel22 } + ListElement { modelData: "23"; channel: ZigbeeManager.ZigbeeChannel23 } + ListElement { modelData: "24"; channel: ZigbeeManager.ZigbeeChannel24 } + ListElement { modelData: "25"; channel: ZigbeeManager.ZigbeeChannel25 } + ListElement { modelData: "26"; channel: ZigbeeManager.ZigbeeChannel26 } + } + currentIndex: 0 + } + } + + Button { + Layout.fillWidth: true; Layout.leftMargin: Style.margins; Layout.rightMargin: Style.margins + text: qsTr("OK") + onClicked: { + print("adding ---", channelCombobox.currentIndex, channelsModel.get(channelCombobox.currentIndex).modelData, channelsModel.get(channelCombobox.currentIndex).channel) + d.pendingCommandId = root.zigbeeManager.addNetwork(addSettingsPage.serialPort, baudRateComboBox.currentText, backendComboBox.currentText, channelsModel.get(channelCombobox.currentIndex).channel) + } } } } diff --git a/nymea-app/ui/system/ZigbeeNetworkPage.qml b/nymea-app/ui/system/ZigbeeNetworkPage.qml index a3418222..92be316f 100644 --- a/nymea-app/ui/system/ZigbeeNetworkPage.qml +++ b/nymea-app/ui/system/ZigbeeNetworkPage.qml @@ -48,14 +48,23 @@ SettingsPageBase { HeaderButton { imageSource: "/ui/images/help.svg" - text: qsTr("Network settings") - onClicked: pageStack.push(zigbeeHelpPage) + text: qsTr("Help") + onClicked: { + var popup = zigbeeHelpDialog.createObject(app) + popup.open() + } } HeaderButton { imageSource: "/ui/images/configure.svg" text: qsTr("Network settings") - onClicked: pageStack.push(Qt.resolvedUrl("ZigbeeNetworkSettingsPage.qml"), { zigbeeManager: zigbeeManager, network: network }) + onClicked: { + var page = pageStack.push(Qt.resolvedUrl("ZigbeeNetworkSettingsPage.qml"), { zigbeeManager: zigbeeManager, network: network }) + page.exit.connect(function() { + pageStack.pop(root, StackView.Immediate) + pageStack.pop() + }) + } } } @@ -99,9 +108,9 @@ SettingsPageBase { } ColumnLayout { - spacing: app.margins - Layout.leftMargin: app.margins - Layout.rightMargin: app.margins + spacing: Style.margins + Layout.leftMargin: Style.margins + Layout.rightMargin: Style.margins RowLayout { Layout.fillWidth: true @@ -229,193 +238,175 @@ SettingsPageBase { enabled: network.networkState === ZigbeeNetwork.ZigbeeNetworkStateOnline onClicked: zigbeeManager.setPermitJoin(network.networkUuid) } - - } SettingsPageSectionHeader { - text: qsTr("ZigBee nodes") + text: qsTr("Connected devices") + } + + Label { + Layout.fillWidth: true + Layout.margins: Style.margins + horizontalAlignment: Text.AlignHCenter + text: qsTr("There are no ZigBee devices connectd yet. Open the network for new devices to join and start the pairing procedure from the ZigBee device. Please refer to the devices manual for more information on how to start the pairing.") + wrapMode: Text.WordWrap + visible: nodesModel.count === 0 } Repeater { - id: zigbeeNodeRepeater model: ZigbeeNodesProxy { - id: zigbeeNodesProxy + id: nodesModel zigbeeNodes: root.network.nodes + showCoordinator: false + newOnTop: true } + delegate: NymeaSwipeDelegate { + id: nodeDelegate + readonly property ZigbeeNode node: nodesModel.get(index) + + ThingsProxy { + id: nodeThings + engine: _engine + paramsFilter: {"ieeeAddress": nodeDelegate.node.ieeeAddress} + } + readonly property Thing nodeThing: nodeThings.count === 1 ? nodeThings.get(0) : null + property int signalStrength: node ? Math.round(node.lqi * 100.0 / 255.0) : 0 - delegate: BigTile { Layout.fillWidth: true - Layout.leftMargin: Style.smallMargins - Layout.rightMargin: Style.smallMargins - interactive: false + text: nodeThing ? nodeThing.name : node.model + subText: node.manufacturer || node.ieeeAddress + iconName: nodeThing ? app.interfacesToIcon(nodeThing.thingClass.interfaces) : "/ui/images/zigbee.svg" + iconColor: busy + ? Style.tileOverlayColor + : nodeThing != null ? Style.accentColor : Style.iconColor + progressive: false - readonly property ZigbeeNode node: zigbeeNodesProxy.get(index) + busy: node.state !== ZigbeeNode.ZigbeeNodeStateInitialized - header: RowLayout { - width: parent.width - ColorIcon { - Layout.preferredHeight: Style.smallIconSize - Layout.preferredWidth: Style.smallIconSize - name: !node || node.type === ZigbeeNode.ZigbeeNodeTypeCoordinator - ? "/ui/images/zigbee.svg" - : node.type === ZigbeeNode.ZigbeeNodeTypeRouter - ? "/ui/images/zigbee-router.svg" - : "/ui/images/zigbee-enddevice.svg" - } - - Led { - Layout.preferredHeight: Style.smallIconSize - Layout.preferredWidth: Style.smallIconSize - state: { - if (!node) { - return "off" - } - - if (node.type === ZigbeeNode.ZigbeeNodeTypeCoordinator) { - switch (network.networkState) { - case ZigbeeNetwork.ZigbeeNetworkStateOnline: - return "on" - case ZigbeeNetwork.ZigbeeNetworkStateOffline: - return "off" - case ZigbeeNetwork.ZigbeeNetworkStateStarting: - return "orange" - case ZigbeeNetwork.ZigbeeNetworkStateUpdating: - return "orange" - case ZigbeeNetwork.ZigbeeNetworkStateError: - return "red" - } - } - - if (node.state !== ZigbeeNode.ZigbeeNodeStateInitialized) { - return "orange" - } - - if (node.reachable) { - return "on" - } else { - return "red" - } - } - } - - - Label { - Layout.fillWidth: true - text: node.type === ZigbeeNode.ZigbeeNodeTypeCoordinator - ? network.backend + " " + qsTr("network coordinator") - : node ? node.model : "" - elide: Text.ElideRight - } - - BusyIndicator { - Layout.preferredHeight: Style.smallIconSize - Layout.preferredWidth: Style.smallIconSize - running: visible - visible: node && node.state !== ZigbeeNode.ZigbeeNodeStateInitialized - } - - Label { - text: signalStrengthIcon.signalStrength + "%" - font: Style.smallFont - visible: node && node.type !== ZigbeeNode.ZigbeeNodeTypeCoordinator - } - - ColorIcon { - id: signalStrengthIcon - Layout.preferredHeight: Style.smallIconSize - Layout.preferredWidth: Style.smallIconSize - visible: node && node.type !== ZigbeeNode.ZigbeeNodeTypeCoordinator - - property int signalStrength: node ? Math.round(node.lqi * 100.0 / 255.0) : 0 - - name: { - if (!node || !node.reachable) - return "/ui/images/connections/nm-signal-00.svg" - - if (signalStrength <= 25) - return "/ui/images/connections/nm-signal-25.svg" - - if (signalStrength <= 50) - return "/ui/images/connections/nm-signal-50.svg" - - if (signalStrength <= 75) - return "/ui/images/connections/nm-signal-75.svg" - - if (signalStrength <= 100) - return "/ui/images/connections/nm-signal-100.svg" - } - } - - ColorIcon { - id: sleepyIconLoader - Layout.preferredHeight: Style.smallIconSize - Layout.preferredWidth: Style.smallIconSize - visible: node && !node.rxOnWhenIdle - name: "/ui/images/system-suspend.svg" - } - - Led { - id: communicationIndicatorLed - Layout.preferredWidth: Style.smallIconSize - Layout.preferredHeight: Style.smallIconSize - state: "off" - - Connections { - target: node - onLastSeenChanged: { - communicationIndicatorLed.state = "on" - communicationIndicatorLedTimer.start() - } - } - - Timer { - id: communicationIndicatorLedTimer - interval: 200 - repeat: false - onTriggered: communicationIndicatorLed.state = "off" - } - } + canDelete: true + onDeleteClicked: { + var dialog = removeZigbeeNodeDialogComponent.createObject(app, {zigbeeNode: node}) + dialog.open() } - contentItem: ColumnLayout { - Label { - Layout.fillWidth: true - elide: Text.ElideRight - visible: node && node.type !== ZigbeeNode.ZigbeeNodeTypeCoordinator - text: node.manufacturer - } + secondaryIconName: node && !node.rxOnWhenIdle ? "/ui/images/system-suspend.svg" : "" - Label { - Layout.fillWidth: true - elide: Text.ElideRight - visible: node && (node.type === ZigbeeNode.ZigbeeNodeTypeCoordinator || node.version !== "") - text: qsTr("Version") + ": " + (node.type === ZigbeeNode.ZigbeeNodeTypeCoordinator ? network.firmwareVersion : node.version) - } + tertiaryIconName: { + if (!node || !node.reachable) + return "/ui/images/connections/nm-signal-00.svg" - Label { - Layout.fillWidth: true - text: qsTr("IEEE address") + ": " + node.ieeeAddress - elide: Text.ElideRight - } + if (signalStrength <= 25) + return "/ui/images/connections/nm-signal-25.svg" - Label { - Layout.fillWidth: true - text: qsTr("Network address") + ": 0x" + (node.networkAddress + Math.pow(16, 4)).toString(16).slice(-4).toUpperCase(); - elide: Text.ElideRight - } + if (signalStrength <= 50) + return "/ui/images/connections/nm-signal-50.svg" + + if (signalStrength <= 75) + return "/ui/images/connections/nm-signal-75.svg" + + if (signalStrength <= 100) + return "/ui/images/connections/nm-signal-100.svg" + } + + + tertiaryIconColor: node.reachable ? Style.iconColor : "red" + + Connections { + target: node + onLastSeenChanged: communicationIndicatorLedTimer.start() + } + + Timer { + id: communicationIndicatorLedTimer + interval: 200 + } + additionalItem: ColorIcon { + size: Style.smallIconSize + visible: node && !node.rxOnWhenIdle + anchors.verticalCenter: parent.verticalCenter + name: node.type === ZigbeeNode.ZigbeeNodeTypeRouter + ? "/ui/images/zigbee-router.svg" + : "/ui/images/zigbee-enddevice.svg" + color: communicationIndicatorLedTimer.running ? Style.accentColor : Style.iconColor + } + + onClicked: { + var popup = nodeInfoComponent.createObject(app, {node: node, nodeThing: nodeThing}) + popup.open() + } + } + } + + Component { + id: nodeInfoComponent + MeaDialog { + id: nodeInfoDialog + property ZigbeeNode node: null + property Thing nodeThing: null + headerIcon: nodeThing ? app.interfacesToIcon(nodeThing.thingClass.interfaces) : "/ui/images/zigbee.svg" + title: nodeThing ? nodeThing.name : node.model + standardButtons: Dialog.NoButton + + NymeaItemDelegate { + text: qsTr("Model") + Layout.fillWidth: true + progressive: false + subText: nodeInfoDialog.node.model + } + NymeaItemDelegate { + text: qsTr("Manufacturer") + Layout.fillWidth: true + progressive: false + subText: nodeInfoDialog.node.manufacturer + } + NymeaItemDelegate { + text: qsTr("IEEE address") + Layout.fillWidth: true + progressive: false + subText: nodeInfoDialog.node.ieeeAddress + } + NymeaItemDelegate { + text: qsTr("Network address") + Layout.fillWidth: true + progressive: false + subText: nodeInfoDialog.node.networkAddress + } + NymeaItemDelegate { + text: qsTr("Version") + Layout.fillWidth: true + progressive: false + subText: nodeInfoDialog.node.version + } + NymeaItemDelegate { + text: qsTr("Signal strength") + Layout.fillWidth: true + progressive: false + subText: (nodeInfoDialog.node.lqi * 100.0 / 255.0) + " %" + } + RowLayout { + Layout.fillWidth: true Button { -// size: Style.iconSize + // size: Style.iconSize visible: node && node.type !== ZigbeeNode.ZigbeeNodeTypeCoordinator -// imageSource: "/ui/images/delete.svg" + // imageSource: "/ui/images/delete.svg" text: qsTr("Remove") - Layout.alignment: Qt.AlignRight + Layout.alignment: Qt.AlignLeft onClicked: { var dialog = removeZigbeeNodeDialogComponent.createObject(app, {zigbeeNode: node}) dialog.open() + nodeInfoDialog.close() } } + Item { + Layout.fillWidth: true + } + + Button { + text: qsTr("OK") + onClicked: nodeInfoDialog.close() + Layout.alignment: Qt.AlignRight + } } } } @@ -446,76 +437,48 @@ SettingsPageBase { } Component { - id: zigbeeHelpPage + id: zigbeeHelpDialog - SettingsPageBase { - id: root + MeaDialog { + id: dialog title: qsTr("ZigBee network help") - header: NymeaHeader { - text: qsTr("ZigBee network help") - backButtonVisible: true - onBackPressed: pageStack.pop() + RowLayout { + spacing: Style.margins + ColorIcon { + Layout.preferredHeight: Style.iconSize + Layout.preferredWidth: Style.iconSize + name: "/ui/images/zigbee-router.svg" + } + + Label { + text: qsTr("ZigBee router") + } } - ColumnLayout { - Layout.fillWidth: true - Layout.topMargin: 2 * app.margins - Layout.leftMargin: app.margins - Layout.rightMargin: app.margins - - - RowLayout { - spacing: app.margins - ColorIcon { - Layout.preferredHeight: Style.iconSize - Layout.preferredWidth: Style.iconSize - name: "/ui/images/zigbee.svg" - } - - Label { - text: qsTr("ZigBee network coordinator") - } + RowLayout { + spacing: Style.margins + ColorIcon { + Layout.preferredHeight: Style.iconSize + Layout.preferredWidth: Style.iconSize + name: "/ui/images/zigbee-enddevice.svg" } + Label { + text: qsTr("ZigBee end device") + } + } - RowLayout { - spacing: app.margins - ColorIcon { - Layout.preferredHeight: Style.iconSize - Layout.preferredWidth: Style.iconSize - name: "/ui/images/zigbee-router.svg" - } - - Label { - text: qsTr("ZigBee router") - } + RowLayout { + spacing: Style.margins + ColorIcon { + Layout.preferredHeight: Style.iconSize + Layout.preferredWidth: Style.iconSize + name: "/ui/images/system-suspend.svg" } - RowLayout { - spacing: app.margins - ColorIcon { - Layout.preferredHeight: Style.iconSize - Layout.preferredWidth: Style.iconSize - name: "/ui/images/zigbee-enddevice.svg" - } - - Label { - text: qsTr("ZigBee end device") - } - } - - RowLayout { - spacing: app.margins - ColorIcon { - Layout.preferredHeight: Style.iconSize - Layout.preferredWidth: Style.iconSize - name: "/ui/images/system-suspend.svg" - } - - Label { - text: qsTr("Sleepy device") - } + Label { + text: qsTr("Sleepy device") } } } diff --git a/nymea-app/ui/system/ZigbeeNetworkPage.qml.autosave b/nymea-app/ui/system/ZigbeeNetworkPage.qml.autosave new file mode 100644 index 00000000..a1bfd8a1 --- /dev/null +++ b/nymea-app/ui/system/ZigbeeNetworkPage.qml.autosave @@ -0,0 +1,486 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2021, 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.8 +import QtQuick.Controls 2.2 +import QtQuick.Controls.Material 2.1 +import QtQuick.Layouts 1.3 +import "../components" +import Nymea 1.0 + +SettingsPageBase { + id: root + + property ZigbeeManager zigbeeManager: null + property ZigbeeNetwork network: null + + header: NymeaHeader { + text: qsTr("ZigBee network") + backButtonVisible: true + onBackPressed: pageStack.pop() + + HeaderButton { + imageSource: "/ui/images/help.svg" + text: qsTr("Help") + onClicked: { + var popup = zigbeeHelpDialog.createObject(app) + popup.open() + } + } + + HeaderButton { + imageSource: "/ui/images/configure.svg" + text: qsTr("Network settings") + onClicked: { + var page = pageStack.push(Qt.resolvedUrl("ZigbeeNetworkSettingsPage.qml"), { zigbeeManager: zigbeeManager, network: network }) + page.exit.connect(function() { + pageStack.pop(root, StackView.Immediate) + pageStack.pop() + }) + } + } + } + + busy: d.pendingCommandId != -1 + + QtObject { + id: d + property int pendingCommandId: -1 + function removeNode(networkUuid, ieeeAddress) { + d.pendingCommandId = root.zigbeeManager.removeNode(networkUuid, ieeeAddress) + } + } + + Connections { + target: root.zigbeeManager + onRemoveNodeReply: { + if (commandId == d.pendingCommandId) { + d.pendingCommandId = -1 + var props = {}; + switch (error) { + case "ZigbeeErrorNoError": + return; + case "ZigbeeErrorAdapterNotAvailable": + props.text = qsTr("The selected adapter is not available or the selected serial port configration is incorrect."); + break; + case "ZigbeeErrorAdapterAlreadyInUse": + props.text = qsTr("The selected adapter is already in use."); + break; + default: + props.errorCode = error; + } + var comp = Qt.createComponent("../components/ErrorDialog.qml") + var popup = comp.createObject(app, props) + popup.open(); + } + } + } + + SettingsPageSectionHeader { + text: qsTr("Network") + } + + ColumnLayout { + spacing: Style.margins + Layout.leftMargin: Style.margins + Layout.rightMargin: Style.margins + + RowLayout { + Layout.fillWidth: true + + Label { + Layout.fillWidth: true + text: qsTr("Network state:") + } + + Label { + text: { + switch (network.networkState) { + case ZigbeeNetwork.ZigbeeNetworkStateOnline: + return qsTr("Online") + case ZigbeeNetwork.ZigbeeNetworkStateOffline: + return qsTr("Offline") + case ZigbeeNetwork.ZigbeeNetworkStateStarting: + return qsTr("Starting") + case ZigbeeNetwork.ZigbeeNetworkStateUpdating: + return qsTr("Updating") + case ZigbeeNetwork.ZigbeeNetworkStateError: + return qsTr("Error") + } + } + } + + Led { + Layout.preferredHeight: Style.iconSize + Layout.preferredWidth: Style.iconSize + state: { + switch (network.networkState) { + case ZigbeeNetwork.ZigbeeNetworkStateOnline: + return "on" + case ZigbeeNetwork.ZigbeeNetworkStateOffline: + return "off" + case ZigbeeNetwork.ZigbeeNetworkStateStarting: + return "orange" + case ZigbeeNetwork.ZigbeeNetworkStateUpdating: + return "orange" + case ZigbeeNetwork.ZigbeeNetworkStateError: + return "red" + } + } + } + } + + RowLayout { + Layout.fillWidth: true + + Label { + Layout.fillWidth: true + text: qsTr("Channel") + } + + Label { + text: network.channel + } + } + + RowLayout { + Layout.fillWidth: true + + Label { + Layout.fillWidth: true + text: qsTr("Permit new devices:") + } + + Label { + text: network.permitJoiningEnabled ? qsTr("Open for %0 s").arg(network.permitJoiningRemaining) : qsTr("Closed") + } + + ColorIcon { + Layout.preferredHeight: Style.iconSize + Layout.preferredWidth: Style.iconSize + name: network.permitJoiningEnabled ? "/ui/images/lock-open.svg" : "/ui/images/lock-closed.svg" + visible: !network.permitJoiningEnabled + } + + Canvas { + id: canvas + Layout.preferredHeight: Style.iconSize + Layout.preferredWidth: Style.iconSize + rotation: -90 + visible: network.permitJoiningEnabled + + property real progress: network.permitJoiningRemaining / network.permitJoiningDuration + onProgressChanged: { + canvas.requestPaint() + } + + onPaint: { + var ctx = canvas.getContext("2d"); + ctx.save(); + ctx.reset(); + var data = [1 - progress, progress]; + var myTotal = 0; + + for(var e = 0; e < data.length; e++) { + myTotal += data[e]; + } + + ctx.fillStyle = Style.accentColor + ctx.strokeStyle = Style.accentColor + ctx.lineWidth = 1; + + ctx.beginPath(); + ctx.moveTo(canvas.width/2,canvas.height/2); + ctx.arc(canvas.width/2,canvas.height/2,canvas.height/2,0,(Math.PI*2*((1-progress)/myTotal)),false); + ctx.lineTo(canvas.width/2,canvas.height/2); + ctx.fill(); + ctx.closePath(); + ctx.beginPath(); + ctx.arc(canvas.width/2,canvas.height/2,canvas.height/2 - 1,0,Math.PI*2,false); + ctx.closePath(); + ctx.stroke(); + + ctx.restore(); + } + } + } + + Button { + Layout.fillWidth: true + text: network.permitJoiningEnabled ? qsTr("Extend open duration") : qsTr("Open for new devices") + enabled: network.networkState === ZigbeeNetwork.ZigbeeNetworkStateOnline + onClicked: zigbeeManager.setPermitJoin(network.networkUuid) + } + } + + SettingsPageSectionHeader { + text: qsTr("Connected devices") + } + + Label { + Layout.fillWidth: true + Layout.margins: Style.margins + horizontalAlignment: Text.AlignHCenter + text: qsTr("There are no ZigBee devices connectd yet. Open the network for new devices to join and start the pairing procedure from the ZigBee device. Please refer to the devices manual for more information on how to start the pairing.") + wrapMode: Text.WordWrap + visible: nodesModel.count === 0 + } + + Repeater { + model: ZigbeeNodesProxy { + id: nodesModel + zigbeeNodes: root.network.nodes + showCoordinator: false + newOnTop: true + } + delegate: NymeaSwipeDelegate { + id: nodeDelegate + readonly property ZigbeeNode node: nodesModel.get(index) + + ThingsProxy { + id: nodeThings + engine: _engine + paramsFilter: {"ieeeAddress": nodeDelegate.node.ieeeAddress} + } + readonly property Thing nodeThing: nodeThings.count === 1 ? nodeThings.get(0) : null + property int signalStrength: node ? Math.round(node.lqi * 100.0 / 255.0) : 0 + + Layout.fillWidth: true + text: nodeThing ? nodeThing.name : node.model + subText: node.manufacturer || node.ieeeAddress + iconName: nodeThing ? app.interfacesToIcon(nodeThing.thingClass.interfaces) : "/ui/images/zigbee.svg" + iconColor: busy + ? Style.tileOverlayColor + : nodeThing != null ? Style.accentColor : Style.iconColor + progressive: false + + busy: node.state !== ZigbeeNode.ZigbeeNodeStateInitialized + + canDelete: true + onDeleteClicked: { + var dialog = removeZigbeeNodeDialogComponent.createObject(app, {zigbeeNode: node}) + dialog.open() + } + + secondaryIconName: node && !node.rxOnWhenIdle ? "/ui/images/system-suspend.svg" : "" + + tertiaryIconName: { + if (!node || !node.reachable) + return "/ui/images/connections/nm-signal-00.svg" + + if (signalStrength <= 25) + return "/ui/images/connections/nm-signal-25.svg" + + if (signalStrength <= 50) + return "/ui/images/connections/nm-signal-50.svg" + + if (signalStrength <= 75) + return "/ui/images/connections/nm-signal-75.svg" + + if (signalStrength <= 100) + return "/ui/images/connections/nm-signal-100.svg" + } + + + tertiaryIconColor: node.reachable ? Style.iconColor : "red" + + Connections { + target: node + onLastSeenChanged: communicationIndicatorLedTimer.start() + } + + Timer { + id: communicationIndicatorLedTimer + interval: 200 + } + additionalItem: ColorIcon { + size: Style.smallIconSize + visible: node && !node.rxOnWhenIdle + anchors.verticalCenter: parent.verticalCenter + name: node.type === ZigbeeNode.ZigbeeNodeTypeRouter + ? "/ui/images/zigbee-router.svg" + : "/ui/images/zigbee-enddevice.svg" + color: communicationIndicatorLedTimer.running ? Style.accentColor : Style.iconColor + } + + onClicked: { + var popup = nodeInfoComponent.createObject(app, {node: node, nodeThing: nodeThing}) + popup.open() + } + } + } + + Component { + id: nodeInfoComponent + MeaDialog { + id: nodeInfoDialog + property ZigbeeNode node: null + property Thing nodeThing: null + headerIcon: nodeThing ? app.interfacesToIcon(nodeThing.thingClass.interfaces) : "/ui/images/zigbee.svg" + title: nodeThing ? nodeThing.name : node.model + standardButtons: Dialog.NoButton + + NymeaItemDelegate { + text: qsTr("Model") + Layout.fillWidth: true + progressive: false + subText: nodeInfoDialog.node.model + } + NymeaItemDelegate { + text: qsTr("Manufacturer") + Layout.fillWidth: true + progressive: false + subText: nodeInfoDialog.node.manufacturer + } + NymeaItemDelegate { + text: qsTr("IEEE address") + Layout.fillWidth: true + progressive: false + subText: nodeInfoDialog.node.ieeeAddress + } + NymeaItemDelegate { + text: qsTr("Network address") + Layout.fillWidth: true + progressive: false + subText: nodeInfoDialog.node.networkAddress + } + NymeaItemDelegate { + text: qsTr("Signal strength") + Layout.fillWidth: true + progressive: false + subText: (nodeInfoDialog.node.lqi * 100.0 / 255.0) + " %" + } + NymeaItemDelegate { + text: qsTr("Version") + Layout.fillWidth: true + progressive: false + subText: nodeInfoDialog.node.version + } + RowLayout { + Layout.fillWidth: true + + Button { + // size: Style.iconSize + visible: node && node.type !== ZigbeeNode.ZigbeeNodeTypeCoordinator + // imageSource: "/ui/images/delete.svg" + text: qsTr("Remove") + Layout.alignment: Qt.AlignLeft + onClicked: { + var dialog = removeZigbeeNodeDialogComponent.createObject(app, {zigbeeNode: node}) + dialog.open() + nodeInfoDialog.close() + } + } + Item { + Layout.fillWidth: true + } + + Button { + text: qsTr("OK") + onClicked: nodeInfoDialog.close() + Layout.alignment: Qt.AlignRight + } + } + } + } + + Component { + id: removeZigbeeNodeDialogComponent + + MeaDialog { + id: removeZigbeeNodeDialog + + property ZigbeeNode zigbeeNode + + headerIcon: "/ui/images/zigbee.svg" + title: qsTr("Remove ZigBee node") + " " + (zigbeeNode ? zigbeeNode.model : "") + text: qsTr("Are you sure you want to remove this node from the network?") + standardButtons: Dialog.Ok | Dialog.Cancel + + Label { + text: qsTr("Please note that if this node has been assigned to a thing, it will also be removed from the system.") + Layout.fillWidth: true + wrapMode: Text.WordWrap + } + + onAccepted: { + d.removeNode(zigbeeNode.networkUuid, zigbeeNode.ieeeAddress) + } + } + } + + Component { + id: zigbeeHelpDialog + + MeaDialog { + id: dialog + title: qsTr("ZigBee network help") + + RowLayout { + spacing: Style.margins + ColorIcon { + Layout.preferredHeight: Style.iconSize + Layout.preferredWidth: Style.iconSize + name: "/ui/images/zigbee-router.svg" + } + + Label { + text: qsTr("ZigBee router") + } + } + + RowLayout { + spacing: Style.margins + ColorIcon { + Layout.preferredHeight: Style.iconSize + Layout.preferredWidth: Style.iconSize + name: "/ui/images/zigbee-enddevice.svg" + } + + Label { + text: qsTr("ZigBee end device") + } + } + + RowLayout { + spacing: Style.margins + ColorIcon { + Layout.preferredHeight: Style.iconSize + Layout.preferredWidth: Style.iconSize + name: "/ui/images/system-suspend.svg" + } + + Label { + text: qsTr("Sleepy device") + } + } + } + } +} diff --git a/nymea-app/ui/system/ZigbeeNetworkSettingsPage.qml b/nymea-app/ui/system/ZigbeeNetworkSettingsPage.qml index cbb51b25..b3d75c18 100644 --- a/nymea-app/ui/system/ZigbeeNetworkSettingsPage.qml +++ b/nymea-app/ui/system/ZigbeeNetworkSettingsPage.qml @@ -41,6 +41,8 @@ SettingsPageBase { property ZigbeeManager zigbeeManager: null property ZigbeeNetwork network: null + signal exit() + header: NymeaHeader { text: qsTr("ZigBee network settings") backButtonVisible: true @@ -144,7 +146,7 @@ SettingsPageBase { text: qsTr("Remove network") onClicked: { root.zigbeeManager.removeNetwork(root.network.networkUuid) - pageStack.pop() + root.exit() } } diff --git a/nymea-app/ui/system/ZigbeeSettingsPage.qml b/nymea-app/ui/system/ZigbeeSettingsPage.qml index bff3a62a..bca47b1b 100644 --- a/nymea-app/ui/system/ZigbeeSettingsPage.qml +++ b/nymea-app/ui/system/ZigbeeSettingsPage.qml @@ -45,10 +45,17 @@ SettingsPageBase { HeaderButton { imageSource: "../images/add.svg" text: qsTr("Add ZigBee network") - onClicked: pageStack.push(Qt.resolvedUrl("ZigbeeAddNetworkPage.qml"), {zigbeeManager: zigbeeManager}) + onClicked: { + addNetwork() + } } } + function addNetwork() { + var addPage = pageStack.push(Qt.resolvedUrl("ZigbeeAddNetworkPage.qml"), {zigbeeManager: zigbeeManager}) + addPage.done.connect(function() {pageStack.pop(root)}) + } + ZigbeeManager { id: zigbeeManager engine: _engine @@ -67,7 +74,7 @@ SettingsPageBase { imageSource: "/ui/images/zigbee.svg" buttonText: qsTr("Add network") onButtonClicked: { - pageStack.push(Qt.resolvedUrl("ZigbeeAddNetworkPage.qml"), {zigbeeManager: zigbeeManager}) + addNetwork() } } } diff --git a/nymea-app/ui/system/ZigbeeSettingsPage.qml.autosave b/nymea-app/ui/system/ZigbeeSettingsPage.qml.autosave new file mode 100644 index 00000000..bca47b1b --- /dev/null +++ b/nymea-app/ui/system/ZigbeeSettingsPage.qml.autosave @@ -0,0 +1,177 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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.8 +import QtQuick.Controls 2.2 +import QtQuick.Controls.Material 2.1 +import QtQuick.Layouts 1.3 +import "../components" +import Nymea 1.0 + +SettingsPageBase { + id: root + header: NymeaHeader { + text: qsTr("ZigBee") + backButtonVisible: true + onBackPressed: pageStack.pop() + + HeaderButton { + imageSource: "../images/add.svg" + text: qsTr("Add ZigBee network") + onClicked: { + addNetwork() + } + } + } + + function addNetwork() { + var addPage = pageStack.push(Qt.resolvedUrl("ZigbeeAddNetworkPage.qml"), {zigbeeManager: zigbeeManager}) + addPage.done.connect(function() {pageStack.pop(root)}) + } + + ZigbeeManager { + id: zigbeeManager + engine: _engine + } + + Item { + Layout.fillWidth: true + Layout.preferredHeight: root.height + visible: zigbeeManager.networks.count == 0 + + EmptyViewPlaceholder { + width: parent.width - app.margins * 2 + anchors.centerIn: parent + title: qsTr("ZigBee") + text: qsTr("There are no ZigBee networks set up yet. In order to use ZigBee, create a ZigBee network.") + imageSource: "/ui/images/zigbee.svg" + buttonText: qsTr("Add network") + onButtonClicked: { + addNetwork() + } + } + } + + + ColumnLayout { + Layout.margins: app.margins / 2 + Repeater { + model: zigbeeManager.networks + delegate: BigTile { + Layout.fillWidth: true + interactive: false + + onClicked: pageStack.push(Qt.resolvedUrl("ZigbeeNetworkPage.qml"), { zigbeeManager: zigbeeManager, network: zigbeeManager.networks.get(index) }) + + header: RowLayout { + ColorIcon { + name: "/ui/images/zigbee/" + model.backend + ".svg" + Layout.preferredWidth: Style.iconSize + Layout.preferredHeight: Style.iconSize + } + + Label { + Layout.fillWidth: true + text: model.backend + font.pixelSize: app.largeFont + } + } + + contentItem: ColumnLayout { + spacing: app.margins + + + RowLayout { + Label { + Layout.fillWidth: true + text: qsTr("Network state:") + } + Label { + text: { + switch (model.networkState) { + case ZigbeeNetwork.ZigbeeNetworkStateOnline: + return qsTr("Online") + case ZigbeeNetwork.ZigbeeNetworkStateOffline: + return qsTr("Offline") + case ZigbeeNetwork.ZigbeeNetworkStateStarting: + return qsTr("Starting") + case ZigbeeNetwork.ZigbeeNetworkStateUpdating: + return qsTr("Updating") + case ZigbeeNetwork.ZigbeeNetworkStateError: + return qsTr("Error") + } + } + } + + Led { + Layout.preferredHeight: Style.iconSize + Layout.preferredWidth: Style.iconSize + state: { + switch (model.networkState) { + case ZigbeeNetwork.ZigbeeNetworkStateOnline: + return "on" + case ZigbeeNetwork.ZigbeeNetworkStateOffline: + return "off" + case ZigbeeNetwork.ZigbeeNetworkStateStarting: + return "orange" + case ZigbeeNetwork.ZigbeeNetworkStateUpdating: + return "orange" + case ZigbeeNetwork.ZigbeeNetworkStateError: + return "red" + } + } + } + } + + RowLayout { + Label { + Layout.fillWidth: true + text: qsTr("MAC address:") + } + Label { + text: model.macAddress + } + } + + RowLayout { + Label { + Layout.fillWidth: true + text: qsTr("Firmware version:") + } + Label { + text: model.firmwareVersion + } + } + } + } + } + } +} +