Add door lock to generic zigbee things

This commit is contained in:
Simon Stürz 2020-11-26 10:33:06 +01:00
parent f906769f60
commit c7ffbfda15
3 changed files with 301 additions and 0 deletions

View File

@ -198,6 +198,116 @@
],
"eventTypes": [
]
},
{
"name": "doorLock",
"displayName": "Door lock",
"id": "34cb3d09-dd9f-4b95-95d0-30a1cd94adac",
"setupMethod": "JustAdd",
"createMethods": [ "Auto" ],
"interfaces": [ "simpleclosable", "batterylevel", "wirelessconnectable" ],
"paramTypes": [
{
"id": "484bf80f-5d20-4029-b05e-6b4c9fbefd69",
"name": "ieeeAddress",
"displayName": "IEEE adress",
"type": "QString",
"defaultValue": "00:00:00:00:00:00:00:00"
},
{
"id": "4d50cbd3-f297-421c-9938-65ec0bb4bd34",
"name": "networkUuid",
"displayName": "Zigbee network UUID",
"type": "QString",
"defaultValue": ""
},
{
"id": "ebf17460-4a37-461f-aa24-e0a9a4238c63",
"name": "endpointId",
"displayName": "Endpoint ID",
"type": "uint",
"defaultValue": 1
},
{
"id": "1b177eb8-a13f-4975-92a7-a6bf54670c8f",
"name": "manufacturer",
"displayName": "Manufacturer",
"type": "QString",
"defaultValue": ""
},
{
"id": "b13f86a7-dc65-4ed6-860c-cdb099f90916",
"name": "model",
"displayName": "Model",
"type": "QString",
"defaultValue": ""
}
],
"stateTypes": [
{
"id": "206b0508-b477-4aa2-b420-aba2259fb6e6",
"name": "connected",
"displayName": "Connected",
"displayNameEvent": "Connected changed",
"type": "bool",
"cached": false,
"defaultValue": false
},
{
"id": "6c8f8db5-464c-408a-9c65-4e8096663019",
"name": "signalStrength",
"displayName": "Signal strength",
"displayNameEvent": "Signal strength changed",
"defaultValue": 0,
"maxValue": 100,
"minValue": 0,
"type": "uint",
"unit": "Percentage"
},
{
"id": "9e27850b-99d8-40df-9bc7-4b3c7c01faf8",
"name": "version",
"displayName": "Version",
"displayNameEvent": "Version changed",
"type": "QString",
"cached": true,
"defaultValue": ""
},
{
"id": "568e5bdd-47f3-4ccb-a1d8-ff3a5ea87ad8",
"name": "batteryLevel",
"displayName": "Battery",
"displayNameEvent": "Battery changed",
"type": "int",
"unit": "Percentage",
"defaultValue": 0,
"minValue": 0,
"maxValue": 100
},
{
"id": "89abea26-b772-4258-9b56-e026b80c2028",
"name": "batteryCritical",
"displayName": "Battery critical",
"displayNameEvent": "Battery critical changed",
"type": "bool",
"defaultValue": false
}
],
"actionTypes": [
{
"id": "6e112e3b-080f-47e4-8e2b-453029f0eacb",
"name": "open",
"displayName": "Unlock door"
},
{
"id": "c26e1908-25d0-4475-8f82-5aaf034640f1",
"name": "close",
"displayName": "Lock door"
}
],
"eventTypes": [
]
}
]

View File

@ -41,27 +41,35 @@ IntegrationPluginZigbeeGeneric::IntegrationPluginZigbeeGeneric()
{
m_ieeeAddressParamTypeIds[thermostatThingClassId] = thermostatThingIeeeAddressParamTypeId;
m_ieeeAddressParamTypeIds[powerSocketThingClassId] = powerSocketThingIeeeAddressParamTypeId;
m_ieeeAddressParamTypeIds[doorLockThingClassId] = doorLockThingIeeeAddressParamTypeId;
m_networkUuidParamTypeIds[thermostatThingClassId] = thermostatThingNetworkUuidParamTypeId;
m_networkUuidParamTypeIds[powerSocketThingClassId] = powerSocketThingNetworkUuidParamTypeId;
m_networkUuidParamTypeIds[doorLockThingClassId] = doorLockThingNetworkUuidParamTypeId;
m_endpointIdParamTypeIds[thermostatThingClassId] = thermostatThingEndpointIdParamTypeId;
m_endpointIdParamTypeIds[powerSocketThingClassId] = powerSocketThingEndpointIdParamTypeId;
m_endpointIdParamTypeIds[doorLockThingClassId] = doorLockThingEndpointIdParamTypeId;
m_manufacturerIdParamTypeIds[thermostatThingClassId] = thermostatThingManufacturerParamTypeId;
m_manufacturerIdParamTypeIds[powerSocketThingClassId] = powerSocketThingManufacturerParamTypeId;
m_manufacturerIdParamTypeIds[doorLockThingClassId] = doorLockThingManufacturerParamTypeId;
m_modelIdParamTypeIds[thermostatThingClassId] = thermostatThingModelParamTypeId;
m_modelIdParamTypeIds[powerSocketThingClassId] = powerSocketThingModelParamTypeId;
m_modelIdParamTypeIds[doorLockThingClassId] = doorLockThingModelParamTypeId;
m_connectedStateTypeIds[thermostatThingClassId] = thermostatConnectedStateTypeId;
m_connectedStateTypeIds[powerSocketThingClassId] = powerSocketConnectedStateTypeId;
m_connectedStateTypeIds[doorLockThingClassId] = doorLockConnectedStateTypeId;
m_signalStrengthStateTypeIds[thermostatThingClassId] = thermostatSignalStrengthStateTypeId;
m_signalStrengthStateTypeIds[powerSocketThingClassId] = powerSocketSignalStrengthStateTypeId;
m_signalStrengthStateTypeIds[doorLockThingClassId] = doorLockSignalStrengthStateTypeId;
m_versionStateTypeIds[thermostatThingClassId] = thermostatVersionStateTypeId;
m_versionStateTypeIds[powerSocketThingClassId] = powerSocketVersionStateTypeId;
m_versionStateTypeIds[doorLockThingClassId] = doorLockVersionStateTypeId;
}
QString IntegrationPluginZigbeeGeneric::name() const
@ -74,6 +82,8 @@ bool IntegrationPluginZigbeeGeneric::handleNode(ZigbeeNode *node, const QUuid &n
bool handled = false;
foreach (ZigbeeNodeEndpoint *endpoint, node->endpoints()) {
qCDebug(dcZigbeeGeneric()) << "Checking node endpoint:" << endpoint->endpointId() << endpoint->deviceId();
// Check thermostat
if (endpoint->profile() == Zigbee::ZigbeeProfile::ZigbeeProfileHomeAutomation &&
endpoint->deviceId() == Zigbee::HomeAutomationDeviceThermostat) {
qCDebug(dcZigbeeGeneric()) << "Handeling thermostat endpoint for" << node << endpoint;
@ -81,6 +91,7 @@ bool IntegrationPluginZigbeeGeneric::handleNode(ZigbeeNode *node, const QUuid &n
handled = true;
}
// Check on/off plug
if ((endpoint->profile() == Zigbee::ZigbeeProfile::ZigbeeProfileLightLink &&
endpoint->deviceId() == Zigbee::LightLinkDevice::LightLinkDeviceOnOffPlugin) ||
(endpoint->profile() == Zigbee::ZigbeeProfile::ZigbeeProfileHomeAutomation &&
@ -90,6 +101,20 @@ bool IntegrationPluginZigbeeGeneric::handleNode(ZigbeeNode *node, const QUuid &n
createThing(powerSocketThingClassId, networkUuid, node, endpoint);
handled = true;
}
// Check door lock
if (endpoint->profile() == Zigbee::ZigbeeProfile::ZigbeeProfileHomeAutomation && endpoint->deviceId() == Zigbee::HomeAutomationDeviceDoorLock) {
if (!endpoint->hasInputCluster(ZigbeeClusterLibrary::ClusterIdPowerConfiguration) ||
!endpoint->hasInputCluster(ZigbeeClusterLibrary::ClusterIdDoorLock)) {
qCWarning(dcZigbeeGeneric()) << "Endpoint claims to be a door lock, but the appropriate input clusters could not be found" << node << endpoint;
} else {
qCDebug(dcZigbeeGeneric()) << "Handeling door lock endpoint for" << node << endpoint;
createThing(doorLockThingClassId, networkUuid, node, endpoint);
// Initialize bindings and cluster attributes
initializeDoorLock(node, endpoint);
handled = true;
}
}
}
return handled;
@ -229,6 +254,44 @@ void IntegrationPluginZigbeeGeneric::setupThing(ThingSetupInfo *info)
}
}
if (thing->thingClassId() == doorLockThingClassId) {
// Get battery level changes
ZigbeeClusterPowerConfiguration *powerCluster = endpoint->inputCluster<ZigbeeClusterPowerConfiguration>(ZigbeeClusterLibrary::ClusterIdPowerConfiguration);
if (!powerCluster) {
qCWarning(dcZigbeeGeneric()) << "Could not find power configuration cluster on" << thing << endpoint;
} else {
// Only set the initial state if the attribute already exists
if (powerCluster->hasAttribute(ZigbeeClusterPowerConfiguration::AttributeBatteryPercentageRemaining)) {
thing->setStateValue(doorLockBatteryLevelStateTypeId, powerCluster->batteryPercentage());
thing->setStateValue(doorLockBatteryCriticalStateTypeId, (powerCluster->batteryPercentage() < 10.0));
}
connect(powerCluster, &ZigbeeClusterPowerConfiguration::batteryPercentageChanged, thing, [=](double percentage){
qCDebug(dcZigbeeGeneric()) << "Battery percentage changed" << percentage << "%" << thing;
thing->setStateValue(doorLockBatteryLevelStateTypeId, percentage);
thing->setStateValue(doorLockBatteryCriticalStateTypeId, (percentage < 10.0));
});
}
// Get door state changes
ZigbeeClusterDoorLock *doorLockCluster = endpoint->inputCluster<ZigbeeClusterDoorLock>(ZigbeeClusterLibrary::ClusterIdDoorLock);
if (!doorLockCluster) {
qCWarning(dcZigbeeGeneric()) << "Could not find door lock cluster on" << thing << endpoint;
} else {
// Only set the initial state if the attribute already exists
if (doorLockCluster->hasAttribute(ZigbeeClusterDoorLock::AttributeDoorState)) {
qCDebug(dcZigbeeGeneric()) << thing << doorLockCluster->doorState();
// TODO: check if we can use smart lock and set appropriate state
}
connect(doorLockCluster, &ZigbeeClusterDoorLock::lockStateChanged, thing, [=](ZigbeeClusterDoorLock::LockState lockState){
qCDebug(dcZigbeeGeneric()) << thing << "lock state changed" << lockState;
// TODO: check if we can use smart lock and set appropriate state
});
}
}
info->finish(Thing::ThingErrorNoError);
}
@ -303,6 +366,50 @@ void IntegrationPluginZigbeeGeneric::executeAction(ThingActionInfo *info)
}
}
if (thing->thingClassId() == doorLockThingClassId) {
if (info->action().actionTypeId() == doorLockOpenActionTypeId) {
ZigbeeClusterDoorLock *doorLockCluster = endpoint->inputCluster<ZigbeeClusterDoorLock>(ZigbeeClusterLibrary::ClusterIdDoorLock);
if (!doorLockCluster) {
qCWarning(dcZigbeeGeneric()) << "Could not find door lock cluster for" << thing << "in" << m_thingNodes.value(thing);
info->finish(Thing::ThingErrorHardwareFailure);
return;
}
// Send the command trough the network
ZigbeeClusterReply *reply = doorLockCluster->unlockDoor();
connect(reply, &ZigbeeClusterReply::finished, this, [reply, info](){
// Note: reply will be deleted automatically
if (reply->error() != ZigbeeClusterReply::ErrorNoError) {
info->finish(Thing::ThingErrorHardwareFailure);
} else {
info->finish(Thing::ThingErrorNoError);
}
});
return;
}
if (info->action().actionTypeId() == doorLockCloseActionTypeId) {
ZigbeeClusterDoorLock *doorLockCluster = endpoint->inputCluster<ZigbeeClusterDoorLock>(ZigbeeClusterLibrary::ClusterIdDoorLock);
if (!doorLockCluster) {
qCWarning(dcZigbeeGeneric()) << "Could not find door lock cluster for" << thing << "in" << m_thingNodes.value(thing);
info->finish(Thing::ThingErrorHardwareFailure);
return;
}
// Send the command trough the network
ZigbeeClusterReply *reply = doorLockCluster->lockDoor();
connect(reply, &ZigbeeClusterReply::finished, this, [reply, info](){
// Note: reply will be deleted automatically
if (reply->error() != ZigbeeClusterReply::ErrorNoError) {
info->finish(Thing::ThingErrorHardwareFailure);
} else {
info->finish(Thing::ThingErrorNoError);
}
});
return;
}
}
info->finish(Thing::ThingErrorUnsupportedFeature);
}
@ -342,3 +449,85 @@ void IntegrationPluginZigbeeGeneric::createThing(const ThingClassId &thingClassI
descriptor.setParams(params);
emit autoThingsAppeared({descriptor});
}
void IntegrationPluginZigbeeGeneric::initializeDoorLock(ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint)
{
qCDebug(dcZigbeeGeneric()) << "Read power configuration cluster attributes" << node;
ZigbeeClusterReply *readAttributeReply = endpoint->getInputCluster(ZigbeeClusterLibrary::ClusterIdPowerConfiguration)->readAttributes({ZigbeeClusterPowerConfiguration::AttributeBatteryPercentageRemaining});
connect(readAttributeReply, &ZigbeeClusterReply::finished, node, [=](){
if (readAttributeReply->error() != ZigbeeClusterReply::ErrorNoError) {
qCWarning(dcZigbeeGeneric()) << "Failed to read power configuration cluster attributes" << readAttributeReply->error();
} else {
qCDebug(dcZigbeeGeneric()) << "Read power configuration cluster attributes finished successfully";
}
// Bind the cluster to the coordinator
qCDebug(dcZigbeeGeneric()) << "Bind power configuration cluster to coordinator IEEE address";
ZigbeeDeviceObjectReply * zdoReply = node->deviceObject()->requestBindIeeeAddress(endpoint->endpointId(), ZigbeeClusterLibrary::ClusterIdPowerConfiguration, hardwareManager()->zigbeeResource()->coordinatorAddress(node->networkUuid()), 0x01);
connect(zdoReply, &ZigbeeDeviceObjectReply::finished, node, [=](){
if (zdoReply->error() != ZigbeeDeviceObjectReply::ErrorNoError) {
qCWarning(dcZigbeeGeneric()) << "Failed to bind power cluster to coordinator" << zdoReply->error();
} else {
qCDebug(dcZigbeeGeneric()) << "Bind power configuration cluster to coordinator finished successfully";
}
// Configure attribute rporting for battery remaining (0.5 % changes = 1)
ZigbeeClusterLibrary::AttributeReportingConfiguration reportingConfig;
reportingConfig.attributeId = ZigbeeClusterPowerConfiguration::AttributeBatteryPercentageRemaining;
reportingConfig.dataType = Zigbee::Uint8;
reportingConfig.minReportingInterval = 60; // for production use 300;
reportingConfig.maxReportingInterval = 120; // for production use 2700;
reportingConfig.reportableChange = ZigbeeDataType(static_cast<quint8>(1)).data();
qCDebug(dcZigbeeGeneric()) << "Configure attribute reporting for power configuration cluster to coordinator";
ZigbeeClusterReply *reportingReply = endpoint->getInputCluster(ZigbeeClusterLibrary::ClusterIdPowerConfiguration)->configureReporting({reportingConfig});
connect(reportingReply, &ZigbeeClusterReply::finished, this, [=](){
if (reportingReply->error() != ZigbeeClusterReply::ErrorNoError) {
qCWarning(dcZigbeeGeneric()) << "Failed to configure power cluster attribute reporting" << reportingReply->error();
} else {
qCDebug(dcZigbeeGeneric()) << "Attribute reporting configuration finished for power cluster" << ZigbeeClusterLibrary::parseAttributeReportingStatusRecords(reportingReply->responseFrame().payload);
}
// Configure door lock attribute reporting and read initial values
qCDebug(dcZigbeeGeneric()) << "Read door lock cluster attributes" << node;
ZigbeeClusterReply *readAttributeReply = endpoint->getInputCluster(ZigbeeClusterLibrary::ClusterIdDoorLock)->readAttributes({ZigbeeClusterDoorLock::AttributeDoorState, ZigbeeClusterDoorLock::AttributeLockType});
connect(readAttributeReply, &ZigbeeClusterReply::finished, node, [=](){
if (readAttributeReply->error() != ZigbeeClusterReply::ErrorNoError) {
qCWarning(dcZigbeeGeneric()) << "Failed to read door lock attributes" << readAttributeReply->error();
} else {
qCDebug(dcZigbeeGeneric()) << "Read door lock cluster attributes finished successfully";
}
// Bind the cluster to the coordinator
qCDebug(dcZigbeeGeneric()) << "Bind door lock cluster to coordinator IEEE address";
ZigbeeDeviceObjectReply * zdoReply = node->deviceObject()->requestBindIeeeAddress(endpoint->endpointId(), ZigbeeClusterLibrary::ClusterIdDoorLock, hardwareManager()->zigbeeResource()->coordinatorAddress(node->networkUuid()), 0x01);
connect(zdoReply, &ZigbeeDeviceObjectReply::finished, node, [=](){
if (zdoReply->error() != ZigbeeDeviceObjectReply::ErrorNoError) {
qCWarning(dcZigbeeGeneric()) << "Failed to door lock cluster to coordinator" << zdoReply->error();
} else {
qCDebug(dcZigbeeGeneric()) << "Bind door lock cluster to coordinator finished successfully";
}
// Configure attribute reporting for lock state
ZigbeeClusterLibrary::AttributeReportingConfiguration reportingConfig;
reportingConfig.attributeId = ZigbeeClusterDoorLock::AttributeLockState;
reportingConfig.dataType = Zigbee::Enum8;
reportingConfig.minReportingInterval = 60;
reportingConfig.maxReportingInterval = 120;
reportingConfig.reportableChange = ZigbeeDataType(static_cast<quint8>(1)).data();
qCDebug(dcZigbeeGeneric()) << "Configure attribute reporting for door lock cluster to coordinator";
ZigbeeClusterReply *reportingReply = endpoint->getInputCluster(ZigbeeClusterLibrary::ClusterIdDoorLock)->configureReporting({reportingConfig});
connect(reportingReply, &ZigbeeClusterReply::finished, this, [=](){
if (reportingReply->error() != ZigbeeClusterReply::ErrorNoError) {
qCWarning(dcZigbeeGeneric()) << "Failed to door lock cluster attribute reporting" << reportingReply->error();
} else {
qCDebug(dcZigbeeGeneric()) << "Attribute reporting configuration finished for door lock cluster" << ZigbeeClusterLibrary::parseAttributeReportingStatusRecords(reportingReply->responseFrame().payload);
}
});
});
});
});
});
});
}

View File

@ -73,6 +73,8 @@ private:
ZigbeeNodeEndpoint *findEndpoint(Thing *thing);
void createThing(const ThingClassId &thingClassId, const QUuid &networkUuid, ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint);
void initializeDoorLock(ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint);
};
#endif // INTEGRATIONPLUGINZIGBEEGENERIC_H