524 lines
18 KiB
QML
524 lines
18 KiB
QML
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
|
*
|
|
* 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 <https://www.gnu.org/licenses/>.
|
|
*
|
|
* 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("Network settings")
|
|
onClicked: pageStack.push(zigbeeHelpPage)
|
|
}
|
|
|
|
HeaderButton {
|
|
imageSource: "/ui/images/configure.svg"
|
|
text: qsTr("Network settings")
|
|
onClicked: pageStack.push(Qt.resolvedUrl("ZigbeeNetworkSettingsPage.qml"), { zigbeeManager: zigbeeManager, network: network })
|
|
}
|
|
}
|
|
|
|
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: app.margins
|
|
Layout.leftMargin: app.margins
|
|
Layout.rightMargin: app.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("ZigBee nodes")
|
|
}
|
|
|
|
Repeater {
|
|
id: zigbeeNodeRepeater
|
|
model: ZigbeeNodesProxy {
|
|
id: zigbeeNodesProxy
|
|
zigbeeNodes: root.network.nodes
|
|
}
|
|
|
|
delegate: BigTile {
|
|
Layout.fillWidth: true
|
|
Layout.leftMargin: Style.smallMargins
|
|
Layout.rightMargin: Style.smallMargins
|
|
interactive: false
|
|
|
|
readonly property ZigbeeNode node: zigbeeNodesProxy.get(index)
|
|
|
|
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"
|
|
}
|
|
}
|
|
}
|
|
|
|
contentItem: ColumnLayout {
|
|
Label {
|
|
Layout.fillWidth: true
|
|
elide: Text.ElideRight
|
|
visible: node && node.type !== ZigbeeNode.ZigbeeNodeTypeCoordinator
|
|
text: node.manufacturer
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
Label {
|
|
Layout.fillWidth: true
|
|
text: qsTr("IEEE address") + ": " + node.ieeeAddress
|
|
elide: Text.ElideRight
|
|
}
|
|
|
|
Label {
|
|
Layout.fillWidth: true
|
|
text: qsTr("Network address") + ": 0x" + (node.networkAddress + Math.pow(16, 4)).toString(16).slice(-4).toUpperCase();
|
|
elide: Text.ElideRight
|
|
}
|
|
|
|
Button {
|
|
// size: Style.iconSize
|
|
visible: node && node.type !== ZigbeeNode.ZigbeeNodeTypeCoordinator
|
|
// imageSource: "/ui/images/delete.svg"
|
|
text: qsTr("Remove")
|
|
Layout.alignment: Qt.AlignRight
|
|
onClicked: {
|
|
var dialog = removeZigbeeNodeDialogComponent.createObject(app, {zigbeeNode: node})
|
|
dialog.open()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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: zigbeeHelpPage
|
|
|
|
SettingsPageBase {
|
|
id: root
|
|
title: qsTr("ZigBee network help")
|
|
|
|
header: NymeaHeader {
|
|
text: qsTr("ZigBee network help")
|
|
backButtonVisible: true
|
|
onBackPressed: pageStack.pop()
|
|
}
|
|
|
|
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: app.margins
|
|
ColorIcon {
|
|
Layout.preferredHeight: Style.iconSize
|
|
Layout.preferredWidth: Style.iconSize
|
|
name: "/ui/images/zigbee-router.svg"
|
|
}
|
|
|
|
Label {
|
|
text: qsTr("ZigBee router")
|
|
}
|
|
}
|
|
|
|
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")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|