diff --git a/zigbee-generic/integrationpluginzigbee-generic.json b/zigbee-generic/integrationpluginzigbee-generic.json index 4793cfee..5c9a13b3 100644 --- a/zigbee-generic/integrationpluginzigbee-generic.json +++ b/zigbee-generic/integrationpluginzigbee-generic.json @@ -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": [ + ] } ] diff --git a/zigbee-generic/integrationpluginzigbeegeneric.cpp b/zigbee-generic/integrationpluginzigbeegeneric.cpp index 4c894c5d..c2fac542 100644 --- a/zigbee-generic/integrationpluginzigbeegeneric.cpp +++ b/zigbee-generic/integrationpluginzigbeegeneric.cpp @@ -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(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(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(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(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(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(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); + } + }); + }); + }); + }); + }); + }); +} diff --git a/zigbee-generic/integrationpluginzigbeegeneric.h b/zigbee-generic/integrationpluginzigbeegeneric.h index 11d50d22..102fbba8 100644 --- a/zigbee-generic/integrationpluginzigbeegeneric.h +++ b/zigbee-generic/integrationpluginzigbeegeneric.h @@ -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