Cleanup and fixes for Zigbee settings

pull/903/head
Michael Zanetti 2022-10-19 23:14:53 +02:00
parent a1f25f3a2a
commit 4f6e6b9dea
10 changed files with 212 additions and 89 deletions

View File

@ -170,6 +170,19 @@ int ZigbeeManager::createBinding(const QUuid &networkUuid, const QString &source
return m_engine->jsonRpcClient()->sendCommand("Zigbee.CreateBinding", params, this, "createBindingResponse");
}
int ZigbeeManager::createGroupBinding(const QUuid &networkUuid, const QString &sourceAddress, quint8 sourceEndpointId, quint16 clusterId, quint16 destinationGroupAddress)
{
QVariantMap params = {
{"networkUuid", networkUuid},
{"sourceAddress", sourceAddress},
{"sourceEndpointId", sourceEndpointId},
{"clusterId", clusterId},
{"destinationGroupAddress", destinationGroupAddress}
};
qCDebug(dcZigbee()) << "Creating binding for:" << qUtf8Printable(QJsonDocument::fromVariant(params).toJson());
return m_engine->jsonRpcClient()->sendCommand("Zigbee.CreateBinding", params, this, "createBindingResponse");
}
int ZigbeeManager::removeBinding(const QUuid &networkUuid, ZigbeeNodeBinding *binding)
{
QVariantMap params = {
@ -256,6 +269,9 @@ void ZigbeeManager::addNetworkResponse(int commandId, const QVariantMap &params)
void ZigbeeManager::removeNetworkResponse(int commandId, const QVariantMap &params)
{
qCDebug(dcZigbee()) << "Zigbee remove network response" << commandId << params;
QMetaEnum errorEnum = QMetaEnum::fromType<ZigbeeError>();
ZigbeeError error = static_cast<ZigbeeError>(errorEnum.keyToValue(params.value("zigbeeError").toByteArray()));
emit removeNetworkReply(commandId, error);
}
void ZigbeeManager::setPermitJoinResponse(int commandId, const QVariantMap &params)
@ -266,6 +282,9 @@ void ZigbeeManager::setPermitJoinResponse(int commandId, const QVariantMap &para
void ZigbeeManager::factoryResetNetworkResponse(int commandId, const QVariantMap &params)
{
qCDebug(dcZigbee()) << "Zigbee factory reset network response" << commandId << params;
QMetaEnum errorEnum = QMetaEnum::fromType<ZigbeeError>();
ZigbeeError error = static_cast<ZigbeeError>(errorEnum.keyToValue(params.value("zigbeeError").toByteArray()));
emit factoryResetNetworkReply(commandId, error);
}
void ZigbeeManager::getNodesResponse(int commandId, const QVariantMap &params)

View File

@ -114,6 +114,7 @@ public:
Q_INVOKABLE int removeNode(const QUuid &networkUuid, const QString &ieeeAddress);
Q_INVOKABLE void refreshNeighborTables(const QUuid &networkUuid);
Q_INVOKABLE int createBinding(const QUuid &networkUuid, const QString &sourceAddress, quint8 sourceEndpointId, quint16 clusterId, const QString &destinationAddress, quint8 destinationEndpointId);
Q_INVOKABLE int createGroupBinding(const QUuid &networkUuid, const QString &sourceAddress, quint8 sourceEndpointId, quint16 clusterId, quint16 destinationGroupAddress);
Q_INVOKABLE int removeBinding(const QUuid &networkUuid, ZigbeeNodeBinding *binding);
signals:
@ -121,6 +122,8 @@ signals:
void fetchingDataChanged();
void availableBackendsChanged();
void addNetworkReply(int commandId, ZigbeeError error, const QUuid &networkUuid);
void removeNetworkReply(int commandId, ZigbeeError error);
void factoryResetNetworkReply(int commandId, ZigbeeError error);
void removeNodeReply(int commandId, ZigbeeError error);
void createBindingReply(int commandId, ZigbeeError error);
void removeBindingReply(int commandId, ZigbeeError error);

View File

@ -67,7 +67,8 @@ void ZigbeeNodesProxy::setZigbeeNodes(ZigbeeNodes *zigbeeNodes)
emit countChanged();
});
connect(m_zigbeeNodes, &ZigbeeNodes::dataChanged, this, [this](const QModelIndex &/*topLeft*/, const QModelIndex &/*bottomRight*/, const QVector<int> &roles = QVector<int>()){
if (roles.contains(ZigbeeNodes::RoleReachable) && (!m_showOffline || !m_showOnline)) {
if ((roles.contains(ZigbeeNodes::RoleReachable) && (!m_showOffline || !m_showOnline))
|| (roles.contains(ZigbeeNodes::RoleType) && !m_showCoordinator)) {
invalidateFilter();
emit countChanged();
}

View File

@ -56,7 +56,10 @@ Dialog {
parent: app.overlay
anchors.fill: parent
z: -1
onPressed: mouse.accepted = true
onPressed: {
print("Dialog: eating mouse press")
mouse.accepted = true
}
}
header: Item {

View File

@ -41,6 +41,8 @@ SettingsPageBase {
property ZigbeeManager zigbeeManager: null
property ZigbeeNetwork network: null
signal exit()
header: NymeaHeader {
text: qsTr("ZigBee network")
backButtonVisible: true
@ -61,8 +63,7 @@ SettingsPageBase {
onClicked: {
var page = pageStack.push(Qt.resolvedUrl("ZigbeeNetworkSettingsPage.qml"), { zigbeeManager: zigbeeManager, network: network })
page.exit.connect(function() {
pageStack.pop(root, StackView.Immediate)
pageStack.pop()
root.exit()
})
}
}

View File

@ -43,6 +43,15 @@ SettingsPageBase {
signal exit()
Connections {
target: zigbeeManager
onFactoryResetNetworkReply: {
busy = false;
// if (error != ZigbeeManager.ZigbeeErrorNoError) {
// }
}
}
header: NymeaHeader {
text: qsTr("ZigBee network settings")
backButtonVisible: true
@ -165,8 +174,9 @@ SettingsPageBase {
});
popup.open();
popup.accepted.connect(function() {
root.zigbeeManager.removeNetwork(root.network.networkUuid)
popup.destroy();
root.exit()
root.zigbeeManager.removeNetwork(root.network.networkUuid)
})
}
}
@ -189,6 +199,7 @@ SettingsPageBase {
popup.open();
popup.accepted.connect(function() {
root.zigbeeManager.factoryResetNetwork(root.network.networkUuid)
root.busy = true;
})
}
}

View File

@ -180,12 +180,12 @@ SettingsPageBase {
subText: qsTr("Version")
}
// NymeaItemDelegate {
// Layout.fillWidth: true
// text: qsTr("Device endpoints")
// subText: qsTr("Show detailed information about the node")
// onClicked: pageStack.push(endpointsPageComponent)
// }
NymeaItemDelegate {
Layout.fillWidth: true
text: qsTr("Device endpoints")
subText: qsTr("Show detailed information about the node")
onClicked: pageStack.push(endpointsPageComponent)
}
SettingsPageSectionHeader {
text: qsTr("Associated things")
@ -338,43 +338,67 @@ SettingsPageBase {
Component {
id: endpointsPageComponent
SettingsPageBase {
title: qsTr("Device endpoints")
title: qsTr("Node descriptor")
Repeater {
model: root.node.endpoints
delegate: ColumnLayout {
id: endpointDelegate
property ZigbeeNodeEndpoint endpoint: root.node.endpoints[index]
Label {
Layout.fillWidth: true
text: "- " + qsTr("Endpoint %1").arg(endpointDelegate.endpoint.endpointId)
header: NymeaHeader {
text: qsTr("ZigBee node descriptor")
backButtonVisible: true
onBackPressed: pageStack.pop()
HeaderButton {
imageSource: "/ui/images/edit-copy.svg"
text: qsTr("Copy")
onClicked: {
PlatformHelper.toClipBoard(descriptorText.text)
ToolTip.show(qsTr("Copied to clipboard"), 1000);
}
Label {
Layout.fillWidth: true
text: " " + qsTr("Input clusters")
}
}
TextArea {
id: descriptorText
readOnly: true
Layout.fillWidth: true
leftPadding: Style.margins
rightPadding: Style.margins
topPadding: Style.margins
bottomPadding: Style.margins
font.family: "Monospace"
text: {
var ret = root.node.manufacturer + "\n"
ret += root.node.model + "\n"
ret += "RxOnWhileIdle: " + root.node.rxOnWhenIdle + "\n"
ret += "Basic cluster version: " + root.node.version + "\n"
if (root.node.endpoints.length > 0) {
ret += "Endpoints\n";
}
Repeater {
model: endpointDelegate.endpoint.inputClusters
delegate: Label {
Layout.fillWidth: true
property ZigbeeCluster cluster: endpointDelegate.endpoint.inputClusters[index]
text: " - " + cluster.clusterName() + " (" + (cluster.direction == ZigbeeCluster.ZigbeeClusterDirectionClient ? qsTr("Client") : qsTr("Server")) + ")"
for (var i = 0; i < root.node.endpoints.length; i++) {
var endpoint = root.node.endpoints[i]
var isLastEp = i == root.node.endpoints.length - 1;
var hasInputClusters = endpoint.inputClusters.length > 0
var hasOutputClusters = endpoint.outputClusters.length > 0
ret += (isLastEp ? "└" : "├") + (hasInputClusters || hasOutputClusters ? "┬" : "─") + " " + endpoint.endpointId + "\n"
ret += (isLastEp ? " " : "│") + (hasOutputClusters ? "├" : "└") + (hasInputClusters ? "┬" : "─") + " Input clusters\n"
for (var j = 0; j < endpoint.inputClusters.length; j++) {
var cluster = endpoint.inputClusters[j]
var isLast = j == endpoint.inputClusters.length - 1
ret += (isLastEp ? " " : "│") + (hasOutputClusters ? "│" : " ") + (isLast ? "└" : "├") + "─ 0x" + NymeaUtils.pad(cluster.clusterId, 4, 16) + " " + cluster.clusterName() + " (" + (cluster.direction == ZigbeeCluster.ZigbeeClusterDirectionClient ? qsTr("Client") : qsTr("Server")) + ")\n"
}
}
Label {
Layout.fillWidth: true
text: " " + qsTr("Output clusters")
}
Repeater {
model: endpointDelegate.endpoint.outputClusters
delegate: Label {
Layout.fillWidth: true
property ZigbeeCluster cluster: endpointDelegate.endpoint.outputClusters[index]
text: " - " + cluster.clusterName() + " (" + (cluster.direction == ZigbeeCluster.ZigbeeClusterDirectionClient ? qsTr("Client") : qsTr("Server")) + ")"
if (hasOutputClusters) {
ret += (isLastEp ? " " : "│") + "└" + (hasOutputClusters ? "┬" : "─") +" Output clusters\n"
for (var j = 0; j < endpoint.outputClusters.length; j++) {
var cluster = endpoint.outputClusters[j]
var isLast = j == endpoint.outputClusters.length - 1
ret += (isLastEp ? " " : "│") + " " + (isLast ? "└" : "├") + "─ 0x" + NymeaUtils.pad(cluster.clusterId, 4, 16) + " " + cluster.clusterName() + " (" + (cluster.direction == ZigbeeCluster.ZigbeeClusterDirectionClient ? qsTr("Client") : qsTr("Server")) + ")\n"
}
}
}
return ret;
}
}
}
@ -400,14 +424,32 @@ SettingsPageBase {
onCurrentEndpointChanged: print("source endpoint changed", currentEndpoint.endpointId)
}
Label {
text: qsTr("Target node")
text: qsTr("Target")
Layout.fillWidth: true
}
RowLayout {
// Groups not properly implemented yet. hiding it for now
visible: false
RadioButton {
id: nodeRadioButton
text: qsTr("Node")
checked: true
Layout.fillWidth: true
}
RadioButton {
id: groupRadioButton
text: qsTr("Group")
Layout.fillWidth: true
}
}
ComboBox {
id: destinationNodeComboBox
Layout.fillWidth: true
Layout.preferredHeight: Style.delegateHeight
model: network.nodes
visible: nodeRadioButton.checked
property ZigbeeNode currentNode: network.nodes.get(currentIndex)
property Thing currentNodeThing: currentNode && currentDestinationNodeThings.count > 0 ? currentDestinationNodeThings.get(0) : null
ThingsProxy {
@ -483,11 +525,13 @@ SettingsPageBase {
}
}
Label {
visible: nodeRadioButton.checked
text: qsTr("Destination endpoint")
Layout.fillWidth: true
}
ComboBox {
id: destinationEndpointComboBox
visible: nodeRadioButton.checked
Layout.fillWidth: true
model: destinationNodeComboBox.currentNode.endpoints
textRole: "endpointId"
@ -495,6 +539,18 @@ SettingsPageBase {
property ZigbeeNodeEndpoint currentEndpoint: destinationNodeComboBox.currentNode.endpoints[currentIndex]
}
Label {
text: qsTr("Group address")
visible: groupRadioButton.checked
}
TextField {
id: groupAddressTextField
visible: groupRadioButton.checked
Layout.fillWidth: true
text: "0"
}
Label {
text: qsTr("Cluster")
Layout.fillWidth: true
@ -509,44 +565,51 @@ SettingsPageBase {
}
model: {
var ret = []
print("updating clusters", sourceEndpointComboBox.currentEndpoint, destinationNodeComboBox.currentNode, destinationEndpointComboBox.currentEndpoint)
if (!sourceEndpointComboBox.currentEndpoint || !destinationNodeComboBox.currentNode || !destinationEndpointComboBox.currentEndpoint) {
return ret;
}
if (destinationNodeComboBox.currentNode == root.coordinatorNode) {
for (var i = 0; i < sourceEndpointComboBox.currentEndpoint.outputClusters.length; i++) {
var outputCluster = sourceEndpointComboBox.currentEndpoint.outputClusters[i]
ret.push(outputCluster)
if (nodeRadioButton.checked) {
print("updating clusters", sourceEndpointComboBox.currentEndpoint, destinationNodeComboBox.currentNode, destinationEndpointComboBox.currentEndpoint)
if (!sourceEndpointComboBox.currentEndpoint || !destinationNodeComboBox.currentNode || !destinationEndpointComboBox.currentEndpoint) {
return ret;
}
for (var i = 0; i < sourceEndpointComboBox.currentEndpoint.inputClusters.length; i++) {
var inputCluster = sourceEndpointComboBox.currentEndpoint.inputClusters[i]
ret.push(inputCluster)
if (destinationNodeComboBox.currentNode == root.coordinatorNode) {
for (var i = 0; i < sourceEndpointComboBox.currentEndpoint.outputClusters.length; i++) {
var outputCluster = sourceEndpointComboBox.currentEndpoint.outputClusters[i]
ret.push(outputCluster)
}
for (var i = 0; i < sourceEndpointComboBox.currentEndpoint.inputClusters.length; i++) {
var inputCluster = sourceEndpointComboBox.currentEndpoint.inputClusters[i]
ret.push(inputCluster)
}
} else {
for (var i = 0; i < sourceEndpointComboBox.currentEndpoint.outputClusters.length; i++) {
var outputCluster = sourceEndpointComboBox.currentEndpoint.outputClusters[i]
print("source has cluster", outputCluster.clusterId)
for (var j = 0; j < destinationEndpointComboBox.currentEndpoint.inputClusters.length; j++) {
var inputCluster = destinationEndpointComboBox.currentEndpoint.inputClusters[j]
print("destination has cluster", inputCluster.clusterId)
if (inputCluster.clusterId === outputCluster.clusterId && inputCluster.direction !== outputCluster.direction) {
ret.push(outputCluster);
break;
}
}
}
for (var i = 0; i < sourceEndpointComboBox.currentEndpoint.inputClusters.length; i++) {
var inputCluster = sourceEndpointComboBox.currentEndpoint.inputClusters[i]
print("source has cluster", inputCluster.clusterId)
for (var j = 0; j < destinationEndpointComboBox.currentEndpoint.outputClusters.length; j++) {
var outputCluster = destinationEndpointComboBox.currentEndpoint.outputClusters[j]
print("destination has cluster", outputCluster.clusterId)
if (inputCluster.clusterId === outputCluster.clusterId && inputCluster.direction !== outputCluster.direction) {
ret.push(inputCluster);
break;
}
}
}
}
} else {
for (var i = 0; i < sourceEndpointComboBox.currentEndpoint.outputClusters.length; i++) {
var outputCluster = sourceEndpointComboBox.currentEndpoint.outputClusters[i]
print("source has cluster", outputCluster.clusterId)
for (var j = 0; j < destinationEndpointComboBox.currentEndpoint.inputClusters.length; j++) {
var inputCluster = destinationEndpointComboBox.currentEndpoint.inputClusters[j]
print("destination has cluster", inputCluster.clusterId)
if (inputCluster.clusterId === outputCluster.clusterId && inputCluster.direction !== outputCluster.direction) {
ret.push(outputCluster);
break;
}
}
}
for (var i = 0; i < sourceEndpointComboBox.currentEndpoint.inputClusters.length; i++) {
var inputCluster = sourceEndpointComboBox.currentEndpoint.inputClusters[i]
print("source has cluster", inputCluster.clusterId)
for (var j = 0; j < destinationEndpointComboBox.currentEndpoint.outputClusters.length; j++) {
var outputCluster = destinationEndpointComboBox.currentEndpoint.outputClusters[j]
print("destination has cluster", outputCluster.clusterId)
if (inputCluster.clusterId === outputCluster.clusterId && inputCluster.direction !== outputCluster.direction) {
ret.push(inputCluster);
break;
}
}
ret.push(inputCluster);
}
}
@ -556,15 +619,23 @@ SettingsPageBase {
displayText: currentValue.clusterName()
}
onAccepted: {
d.pendingCommandId = root.zigbeeManager.createBinding(
root.network.networkUuid,
root.node.ieeeAddress,
sourceEndpointComboBox.currentEndpoint.endpointId,
clusterComboBox.currentCluster.clusterId,
destinationNodeComboBox.currentNode.ieeeAddress,
destinationEndpointComboBox.currentEndpoint.endpointId)
if (nodeRadioButton.checked) {
d.pendingCommandId = root.zigbeeManager.createBinding(
root.network.networkUuid,
root.node.ieeeAddress,
sourceEndpointComboBox.currentEndpoint.endpointId,
clusterComboBox.currentCluster.clusterId,
destinationNodeComboBox.currentNode.ieeeAddress,
destinationEndpointComboBox.currentEndpoint.endpointId)
} else {
d.pendingCommandId = root.zigbeeManager.createGroupBinding(
root.network.networkUuid,
root.node.ieeeAddress,
sourceEndpointComboBox.currentEndpoint.endpointId,
clusterComboBox.currentCluster.clusterId,
groupAddressTextField.text)
}
if (!root.node.rxOnWhenIdle) {
d.wakeupDialog = wakeupDialogComponent.createObject(root)

View File

@ -56,6 +56,10 @@ SettingsPageBase {
addPage.done.connect(function() {pageStack.pop(root)})
}
function returnToMain() {
pageStack.pop(root)
}
ZigbeeManager {
id: zigbeeManager
engine: _engine
@ -98,7 +102,13 @@ SettingsPageBase {
interactive: false
property ZigbeeNetwork network: zigbeeManager.networks.get(index)
onClicked: pageStack.push(Qt.resolvedUrl("ZigbeeNetworkPage.qml"), { zigbeeManager: zigbeeManager, network: networkDelegate.network })
onClicked: {
var page = pageStack.push(Qt.resolvedUrl("ZigbeeNetworkPage.qml"), { zigbeeManager: zigbeeManager, network: networkDelegate.network })
page.exit.connect(function() {
print("exiting")
root.returnToMain()
})
}
header: RowLayout {
ColorIcon {

View File

@ -144,7 +144,7 @@ SettingsPageBase {
progressive: false
onClicked: {
PlatformHelper.toClipBoard(root.thing.id.toString().replace(/[{}]/g, ""));
ToolTip.show(qsTr("ID copied to clipboard"), 500);
ToolTip.show(qsTr("ID copied to clipboard"), 1000);
}
}

View File

@ -6,14 +6,18 @@ import QtCharts 2.2
Item {
id: root
function pad(num, size) {
function pad(num, size, base) {
if (base == undefined) {
base = 10
}
var trimmedNum = Math.floor(num)
var decimals = num - trimmedNum
var trimmedStr = "" + trimmedNum
var str = "000000000" + trimmedNum;
var trimmedStr = "" + trimmedNum.toString(16)
var str = "000000000" + trimmedStr
str = str.substr(str.length - Math.max(size, trimmedStr.length));
if (decimals !== 0) {
str += "." + (num - trimmedNum);
str += "." + decimals.toString(base);
}
return str;
}