Add API to interact with ZigBee bindings

This commit is contained in:
Michael Zanetti 2022-09-14 23:46:02 +02:00
parent 1fc4c7f2d7
commit b4eca2a94f
5 changed files with 347 additions and 2 deletions

View File

@ -49,6 +49,7 @@ ZigbeeHandler::ZigbeeHandler(ZigbeeManager *zigbeeManager, QObject *parent) :
registerObject<ZigbeeAdapter, ZigbeeAdapters>();
registerEnum<ZigbeeNodeRelationship>();
registerEnum<ZigbeeNodeRouteStatus>();
registerEnum<ZigbeeClusterDirection>();
// Network object describing a network instance
QVariantMap zigbeeNetworkDescription;
@ -68,6 +69,15 @@ ZigbeeHandler::ZigbeeHandler(ZigbeeManager *zigbeeManager, QObject *parent) :
zigbeeNetworkDescription.insert("networkState", enumRef<ZigbeeManager::ZigbeeNetworkState>());
registerObject("ZigbeeNetwork", zigbeeNetworkDescription);
QVariantMap zigbeeBindingTableRecordDescription;
zigbeeBindingTableRecordDescription.insert("sourceAddress", enumValueName(String));
zigbeeBindingTableRecordDescription.insert("sourceEndpointId", enumValueName(Uint));
zigbeeBindingTableRecordDescription.insert("clusterId", enumValueName(Uint));
zigbeeBindingTableRecordDescription.insert("o:destinationGroupAddress", enumValueName(Uint));
zigbeeBindingTableRecordDescription.insert("o:destinationAddress", enumValueName(String));
zigbeeBindingTableRecordDescription.insert("o:destinationEndpointId", enumValueName(Uint));
registerObject("ZigbeeBindingTableRecord", zigbeeBindingTableRecordDescription);
QVariantMap zigbeeNeighborTableRecordDescription;
zigbeeNeighborTableRecordDescription.insert("networkAddress", enumValueName(Uint));
zigbeeNeighborTableRecordDescription.insert("relationship", enumRef<ZigbeeNodeRelationship>());
@ -84,6 +94,19 @@ ZigbeeHandler::ZigbeeHandler(ZigbeeManager *zigbeeManager, QObject *parent) :
zigbeeRoutingTableRecordDescription.insert("memoryConstrained", enumValueName(Bool));
registerObject("ZigbeeRoutingTableRecord", zigbeeRoutingTableRecordDescription);
QVariantMap zigbeeClusterDescription;
zigbeeClusterDescription.insert("clusterId", enumValueName(Uint));
zigbeeClusterDescription.insert("direction", enumRef<ZigbeeClusterDirection>());
registerObject("ZigbeeCluster", zigbeeClusterDescription);
QVariantMap zigbeeNodeEndpointDescription;
zigbeeNodeEndpointDescription.insert("endpointId", enumValueName(Uint));
zigbeeNodeEndpointDescription.insert("inputClusters", QVariantList() << objectRef("ZigbeeCluster"));
zigbeeNodeEndpointDescription.insert("outputClusters", QVariantList() << objectRef("ZigbeeCluster"));
registerObject("ZigbeeNodeEndpoint", zigbeeNodeEndpointDescription);
// Zigbee node description
QVariantMap zigbeeNodeDescription;
zigbeeNodeDescription.insert("networkUuid", enumValueName(Uuid));
@ -98,8 +121,10 @@ ZigbeeHandler::ZigbeeHandler(ZigbeeManager *zigbeeManager, QObject *parent) :
zigbeeNodeDescription.insert("reachable", enumValueName(Bool));
zigbeeNodeDescription.insert("lqi", enumValueName(Uint));
zigbeeNodeDescription.insert("lastSeen", enumValueName(Uint));
zigbeeNodeDescription.insert("endpoints", QVariantList() << objectRef("ZigbeeNodeEndpoint"));
zigbeeNodeDescription.insert("neighborTableRecords", QVariantList() << objectRef("ZigbeeNeighborTableRecord"));
zigbeeNodeDescription.insert("routingTableRecords", QVariantList() << objectRef("ZigbeeRoutingTableRecord"));
zigbeeNodeDescription.insert("bindingTableRecords", QVariantList() << objectRef("ZigbeeBindingTableRecord"));
registerObject("ZigbeeNode", zigbeeNodeDescription);
QVariantMap params, returns;
@ -216,6 +241,41 @@ ZigbeeHandler::ZigbeeHandler(ZigbeeManager *zigbeeManager, QObject *parent) :
returns.insert("o:zigbeeNodes", QVariantList() << objectRef("ZigbeeNode"));
registerMethod("GetNodes", description, params, returns);
// RefreshBindings
params.clear(); returns.clear();
description = "Refresh the binding table for the given node.";
params.insert("networkUuid", enumValueName(Uuid));
params.insert("ieeeAddress", enumValueName(String));
returns.insert("zigbeeError", enumRef<ZigbeeManager::ZigbeeError>());
registerMethod("RefreshBindings", description, params, returns);
// CreateBinding
params.clear(); returns.clear();
description = "Create a binding. Use destinationAddress and destinationEndpointId to create a node to node binding, or use destinationGroupAddress to create a group binding.";
params.insert("networkUuid", enumValueName(Uuid));
params.insert("sourceAddress", enumValueName(String));
params.insert("sourceEndpointId", enumValueName(Uint));
params.insert("clusterId", enumValueName(Uint));
params.insert("o:destinationAddress", enumValueName(String));
params.insert("o:destinationEndpointId", enumValueName(Uint));
params.insert("o:destinationGroupAddress", enumValueName(Uint));
returns.insert("zigbeeError", enumRef<ZigbeeManager::ZigbeeError>());
registerMethod("CreateBinding", description, params, returns);
// RemoveBinding
params.clear(); returns.clear();
description = "Remove a binding.";
params.insert("networkUuid", enumValueName(Uuid));
params.insert("sourceAddress", enumValueName(String));
params.insert("sourceEndpointId", enumValueName(Uint));
params.insert("clusterId", enumValueName(Uint));
params.insert("o:destinationAddress", enumValueName(String));
params.insert("o:destinationEndpointId", enumValueName(Uint));
params.insert("o:destinationGroupAddress", enumValueName(Uint));
returns.insert("zigbeeError", enumRef<ZigbeeManager::ZigbeeError>());
registerMethod("RemoveBinding", description, params, returns);
// RemoveNode
params.clear(); returns.clear();
description = "Remove a ZigBee node with the given \'ieeeAddress\' from the network with the given \'networkUuid\'. "
@ -429,6 +489,153 @@ JsonReply *ZigbeeHandler::RefreshNeighborTables(const QVariantMap &params)
return createReply({{"zigbeeError", enumValueName(error)}});
}
JsonReply *ZigbeeHandler::RefreshBindings(const QVariantMap &params)
{
QUuid networkUuid = params.value("networkUuid").toUuid();
QString ieeeAddress = params.value("ieeeAddress").toString();
ZigbeeNetwork *network = m_zigbeeManager->zigbeeNetworks().value(networkUuid);
if (!network) {
return createReply({{"zigbeeError", enumValueName(ZigbeeManager::ZigbeeErrorNetworkUuidNotFound)}});
}
ZigbeeNode *node = m_zigbeeManager->zigbeeNetworks().value(networkUuid)->getZigbeeNode(ZigbeeAddress(ieeeAddress));
if (!node) {
return createReply({{"zigbeeError", enumValueName(ZigbeeManager::ZigbeeErrorNodeNotFound)}});
}
JsonReply *jsonReply = createAsyncReply("RefreshBindings");
ZigbeeReply *reply = node->readBindingTableEntries();
connect(reply, &ZigbeeReply::finished, jsonReply, [reply, jsonReply](){
jsonReply->setData({{"zigbeeError", enumValueName(reply->error())}});
jsonReply->finished();
});
return jsonReply;
}
JsonReply *ZigbeeHandler::CreateBinding(const QVariantMap &params)
{
QUuid networkUuid = params.value("networkUuid").toUuid();
ZigbeeNetwork *network = m_zigbeeManager->zigbeeNetworks().value(networkUuid);
if (!network) {
return createReply({{"zigbeeError", enumValueName(ZigbeeManager::ZigbeeErrorNetworkUuidNotFound)}});
}
QString sourceAddress = params.value("sourceAddress").toString();
ZigbeeNode *node = network->getZigbeeNode(ZigbeeAddress(sourceAddress));
if (!node) {
qCWarning(dcJsonRpc()) << "No Zigbee node for sourceAddress" << sourceAddress;
return createReply({{"zigbeeError", enumValueName(ZigbeeManager::ZigbeeErrorNodeNotFound)}});
}
quint8 sourceEndpointId = params.value("sourceEndpointId").toUInt();
quint16 clusterId = params.value("clusterId").toUInt();
if (params.contains("destinationAddress") && params.contains("destinationEndpointId")) {
QString destinationAddress = params.value("destinationAddress").toString();
quint8 destinationEndpointId = params.value("destinationEndpointId").toUInt();
ZigbeeReply *reply = node->addBinding(sourceEndpointId, clusterId, ZigbeeAddress(destinationAddress), destinationEndpointId);
JsonReply *jsonReply = createAsyncReply("CreateBinding");
connect(reply, &ZigbeeReply::finished, jsonReply, [reply, jsonReply](){
ZigbeeManager::ZigbeeError error = ZigbeeManager::ZigbeeErrorNoError;
switch (reply->error()) {
case ZigbeeReply::ErrorNoError:
break;
case ZigbeeReply::ErrorTimeout:
error = ZigbeeManager::ZigbeeErrorTimeoutError;
break;
default:
error = ZigbeeManager::ZigbeeErrorNetworkError;
break;
}
jsonReply->setData({{"zigbeeError", enumValueName(static_cast<ZigbeeManager::ZigbeeError>(error))}});
emit jsonReply->finished();
});
return jsonReply;
} else if (params.contains("destinationGroupAddress")) {
quint16 destinationGroupAddress = params.value("destinationGroupAddress").toUInt();
ZigbeeReply *reply = node->addBinding(sourceEndpointId, clusterId, destinationGroupAddress);
JsonReply *jsonReply = createAsyncReply("CreateBinding");
connect(reply, &ZigbeeReply::finished, jsonReply, [reply, jsonReply](){
ZigbeeManager::ZigbeeError error = ZigbeeManager::ZigbeeErrorNoError;
switch (reply->error()) {
case ZigbeeReply::ErrorNoError:
break;
case ZigbeeReply::ErrorTimeout:
error = ZigbeeManager::ZigbeeErrorTimeoutError;
break;
default:
error = ZigbeeManager::ZigbeeErrorNetworkError;
break;
}
jsonReply->setData({{"zigbeeError", enumValueName(static_cast<ZigbeeManager::ZigbeeError>(error))}});
emit jsonReply->finished();
});
return jsonReply;
}
return createReply({{"zigbeeError", enumValueName(ZigbeeManager::ZigbeeErrorNodeNotFound)}});
}
JsonReply *ZigbeeHandler::RemoveBinding(const QVariantMap &params)
{
QUuid networkUuid = params.value("networkUuid").toUuid();
ZigbeeNetwork *network = m_zigbeeManager->zigbeeNetworks().value(networkUuid);
if (!network) {
return createReply({{"zigbeeError", enumValueName(ZigbeeManager::ZigbeeErrorNetworkUuidNotFound)}});
}
ZigbeeAddress sourceAddress = ZigbeeAddress(params.value("sourceAddress").toString());
ZigbeeNode *node = network->getZigbeeNode(sourceAddress);
if (!node) {
qCWarning(dcJsonRpc()) << "No Zigbee node for sourceAddress" << sourceAddress;
return createReply({{"zigbeeError", enumValueName(ZigbeeManager::ZigbeeErrorNodeNotFound)}});
}
quint8 sourceEndpointId = params.value("sourceEndpointId").toUInt();
quint16 clusterId = params.value("clusterId").toUInt();
quint16 destinationGroupAddress = params.value("destinationGroupAddress").toUInt();
ZigbeeAddress destinationAddress = ZigbeeAddress(params.value("destinationAddress").toString());
quint8 destinationEndpointId = params.value("destinationEndpointId").toUInt();
bool isGroup = params.contains("destinationGroupAddress");
foreach (const ZigbeeDeviceProfile::BindingTableListRecord &binding, node->bindingTableRecords()) {
bool found = false;
if (isGroup) {
if (binding.sourceAddress == sourceAddress
&& binding.sourceEndpoint == sourceEndpointId
&& binding.clusterId == clusterId
&& binding.destinationShortAddress == destinationGroupAddress) {
found = true;
}
} else {
if (binding.sourceAddress == sourceAddress
&& binding.sourceEndpoint == sourceEndpointId
&& binding.clusterId == clusterId
&& binding.destinationIeeeAddress == destinationAddress
&& binding.destinationEndpoint == destinationEndpointId) {
found = true;
}
}
if (found) {
ZigbeeReply *reply = node->removeBinding(binding);
JsonReply *jsonReply = createAsyncReply("RemoveBinding");
connect(reply, &ZigbeeReply::finished, jsonReply, [reply, jsonReply](){
ZigbeeManager::ZigbeeError error = ZigbeeManager::ZigbeeErrorNoError;
switch (reply->error()) {
case ZigbeeReply::ErrorNoError:
break;
case ZigbeeReply::ErrorTimeout:
error = ZigbeeManager::ZigbeeErrorTimeoutError;
break;
default:
error = ZigbeeManager::ZigbeeErrorNetworkError;
break;
}
jsonReply->setData({{"zigbeeError", enumValueName(static_cast<ZigbeeManager::ZigbeeError>(error))}});
emit jsonReply->finished();
});
return jsonReply;
}
}
return createReply({{"zigbeeError", enumValueName(ZigbeeManager::ZigbeeErrorNodeNotFound)}});
}
JsonReply *ZigbeeHandler::GetNetworks(const QVariantMap &params)
{
Q_UNUSED(params)
@ -551,6 +758,44 @@ QVariantMap ZigbeeHandler::packNode(ZigbeeNode *node)
routingTableRecords.append(recordMap);
}
nodeMap.insert("routingTableRecords", routingTableRecords);
QVariantList bindingTableRecords;
foreach (const ZigbeeDeviceProfile::BindingTableListRecord &record, node->bindingTableRecords()) {
QVariantMap recordMap;
recordMap.insert("sourceAddress", record.sourceAddress.toString());
recordMap.insert("sourceEndpointId", record.sourceEndpoint);
recordMap.insert("clusterId", record.clusterId);
if (record.destinationAddressMode == Zigbee::DestinationAddressModeGroup) {
recordMap.insert("destinationGroupAddress", record.destinationShortAddress);
} else if (record.destinationAddressMode == Zigbee::DestinationAddressModeIeeeAddress) {
recordMap.insert("destinationAddress", record.destinationIeeeAddress.toString());
recordMap.insert("destinationEndpointId", record.destinationEndpoint);
}
bindingTableRecords.append(recordMap);
}
nodeMap.insert("bindingTableRecords", bindingTableRecords);
QVariantList endpoints;
foreach (ZigbeeNodeEndpoint *endpoint, node->endpoints()) {
QVariantMap endpointMap;
endpointMap.insert("endpointId", endpoint->endpointId());
QVariantList inputClusters;
foreach (ZigbeeCluster *cluster, endpoint->inputClusters()) {
QVariantMap clusterMap;
clusterMap.insert("clusterId", cluster->clusterId());
clusterMap.insert("direction", enumValueName(static_cast<ZigbeeClusterDirection>(cluster->direction())));
inputClusters.append(clusterMap);
}
endpointMap.insert("inputClusters", inputClusters);
QVariantList outputClusters;
foreach (ZigbeeCluster *cluster, endpoint->outputClusters()) {
QVariantMap clusterMap;
clusterMap.insert("clusterId", cluster->clusterId());
clusterMap.insert("direction", enumValueName(static_cast<ZigbeeClusterDirection>(cluster->direction())));
outputClusters.append(clusterMap);
}
endpointMap.insert("outputClusters", outputClusters);
endpoints.append(endpointMap);
}
nodeMap.insert("endpoints", endpoints);
return nodeMap;
}

View File

@ -63,6 +63,13 @@ public:
};
Q_ENUM(ZigbeeNodeRouteStatus)
enum ZigbeeClusterDirection {
ZigbeeClusterDirectionServer,
ZigbeeClusterDirectionClient
};
Q_ENUM(ZigbeeClusterDirection)
explicit ZigbeeHandler(ZigbeeManager *zigbeeManager, QObject *parent = nullptr);
QString name() const override;
@ -81,6 +88,10 @@ public:
Q_INVOKABLE JsonReply *RefreshNeighborTables(const QVariantMap &params);
Q_INVOKABLE JsonReply *RefreshBindings(const QVariantMap &params);
Q_INVOKABLE JsonReply *CreateBinding(const QVariantMap &params);
Q_INVOKABLE JsonReply *RemoveBinding(const QVariantMap &params);
QVariantMap packNetwork(ZigbeeNetwork *network);
QVariantMap packNode(ZigbeeNode *node);

View File

@ -254,7 +254,7 @@ ZigbeeManager::ZigbeeError ZigbeeManager::refreshNeighborTables(const QUuid &net
qCWarning(dcZigbee()) << "No network with uuid" << networkUuid.toString();
return ZigbeeManager::ZigbeeErrorNetworkUuidNotFound;
}
m_zigbeeNetworks.value(networkUuid)->refreshNeighborTable();
m_zigbeeNetworks.value(networkUuid)->refreshNeighborTables();
return ZigbeeManager::ZigbeeErrorNoError;
}
@ -704,6 +704,14 @@ void ZigbeeManager::setupNodeSignals(ZigbeeNode *node)
connect(node, &ZigbeeNode::neighborTableRecordsChanged, this, [=](){
emit nodeChanged(node->networkUuid(), node);
});
connect(node, &ZigbeeNode::routingTableRecordsChanged, this, [=](){
emit nodeChanged(node->networkUuid(), node);
});
connect(node, &ZigbeeNode::bindingTableRecordsChanged, this, [=](){
emit nodeChanged(node->networkUuid(), node);
});
}

View File

@ -64,6 +64,9 @@ public:
ZigbeeErrorNodeNotFound,
ZigbeeErrorForbidden,
ZigbeeErrorInvalidChannel,
ZigbeeErrorNetworkError,
ZigbeeErrorTimeoutError,
ZigbeeErrorNotSupported
};
Q_ENUM(ZigbeeError)
@ -96,6 +99,7 @@ public:
ZigbeeError setZigbeeNetworkPermitJoin(const QUuid &networkUuid, quint16 shortAddress = Zigbee::BroadcastAddressAllRouters, uint duration = 120);
ZigbeeError factoryResetNetwork(const QUuid &networkUuid);
ZigbeeError refreshNeighborTables(const QUuid &networkUuid);
ZigbeeReply *createBinding(const QUuid &networkUuid, const ZigbeeAddress &sourceAddress, quint8 sourceEndpointId, quint16 clusterId, const ZigbeeAddress &destinationAddress, quint8 destinationEndpointId);
private:
ZigbeeAdapters m_adapters;

View File

@ -490,6 +490,10 @@
"ZWaveNodeTypeSlave",
"ZWaveNodeTypeRoutingSlave"
],
"ZigbeeClusterDirection": [
"ZigbeeClusterDirectionServer",
"ZigbeeClusterDirectionClient"
],
"ZigbeeError": [
"ZigbeeErrorNoError",
"ZigbeeErrorAdapterNotAvailable",
@ -500,7 +504,10 @@
"ZigbeeErrorUnknownBackend",
"ZigbeeErrorNodeNotFound",
"ZigbeeErrorForbidden",
"ZigbeeErrorInvalidChannel"
"ZigbeeErrorInvalidChannel",
"ZigbeeErrorNetworkError",
"ZigbeeErrorTimeoutError",
"ZigbeeErrorNotSupported"
],
"ZigbeeNetworkState": [
"ZigbeeNetworkStateOffline",
@ -2158,6 +2165,22 @@
"zigbeeError": "$ref:ZigbeeError"
}
},
"Zigbee.CreateBinding": {
"description": "Create a binding. Use destinationAddress and destinationEndpointId to create a node to node binding, or use destinationGroupAddress to create a group binding.",
"params": {
"clusterId": "Uint",
"networkUuid": "Uuid",
"o:destinationAddress": "String",
"o:destinationEndpointId": "Uint",
"o:destinationGroupAddress": "Uint",
"sourceAddress": "String",
"sourceEndpointId": "Uint"
},
"permissionScope": "PermissionScopeAdmin",
"returns": {
"zigbeeError": "$ref:ZigbeeError"
}
},
"Zigbee.FactoryResetNetwork": {
"description": "Factory reset the network with the given 'networkUuid'. The network does not have to be online for this procedure, and all associated nodes and things will be removed permanently.",
"params": {
@ -2212,6 +2235,17 @@
"zigbeeError": "$ref:ZigbeeError"
}
},
"Zigbee.RefreshBindings": {
"description": "Refresh the binding table for the given node.",
"params": {
"ieeeAddress": "String",
"networkUuid": "Uuid"
},
"permissionScope": "PermissionScopeAdmin",
"returns": {
"zigbeeError": "$ref:ZigbeeError"
}
},
"Zigbee.RefreshNeighborTables": {
"description": "Refresh the neighbor table for all nodes. Note that calling this may cause a lot of traffic in the ZigBee network.",
"params": {
@ -2222,6 +2256,22 @@
"zigbeeError": "$ref:ZigbeeError"
}
},
"Zigbee.RemoveBinding": {
"description": "Remove a binding.",
"params": {
"clusterId": "Uint",
"networkUuid": "Uuid",
"o:destinationAddress": "String",
"o:destinationEndpointId": "Uint",
"o:destinationGroupAddress": "Uint",
"sourceAddress": "String",
"sourceEndpointId": "Uint"
},
"permissionScope": "PermissionScopeAdmin",
"returns": {
"zigbeeError": "$ref:ZigbeeError"
}
},
"Zigbee.RemoveNetwork": {
"description": "Remove the ZigBee network with the given network uuid.",
"params": {
@ -3276,6 +3326,18 @@
"ZigbeeAdapters": [
"$ref:ZigbeeAdapter"
],
"ZigbeeBindingTableRecord": {
"clusterId": "Uint",
"o:destinationAddress": "String",
"o:destinationEndpointId": "Uint",
"o:destinationGroupAddress": "Uint",
"sourceAddress": "String",
"sourceEndpointId": "Uint"
},
"ZigbeeCluster": {
"clusterId": "Uint",
"direction": "$ref:ZigbeeClusterDirection"
},
"ZigbeeNeighborTableRecord": {
"depth": "Uint",
"lqi": "Uint",
@ -3300,6 +3362,12 @@
"serialPort": "String"
},
"ZigbeeNode": {
"bindingTableRecords": [
"$ref:ZigbeeBindingTableRecord"
],
"endpoints": [
"$ref:ZigbeeNodeEndpoint"
],
"ieeeAddress": "String",
"lastSeen": "Uint",
"lqi": "Uint",
@ -3319,6 +3387,15 @@
"type": "$ref:ZigbeeNodeType",
"version": "String"
},
"ZigbeeNodeEndpoint": {
"endpointId": "Uint",
"inputClusters": [
"$ref:ZigbeeCluster"
],
"outputClusters": [
"$ref:ZigbeeCluster"
]
},
"ZigbeeRoutingTableRecord": {
"destinationAddress": "Uint",
"manyToOne": "Bool",