diff --git a/debian/nymea-plugin-zigbee-tradfri.install.in b/debian/nymea-plugin-zigbee-tradfri.install.in new file mode 100644 index 00000000..1d842d82 --- /dev/null +++ b/debian/nymea-plugin-zigbee-tradfri.install.in @@ -0,0 +1 @@ +usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationpluginzigbee-tradfri.so diff --git a/zigbee-generic-lights/integrationpluginzigbeegenericlights.cpp b/zigbee-generic-lights/integrationpluginzigbeegenericlights.cpp index 42351b39..a5f3ca25 100644 --- a/zigbee-generic-lights/integrationpluginzigbeegenericlights.cpp +++ b/zigbee-generic-lights/integrationpluginzigbeegenericlights.cpp @@ -149,6 +149,10 @@ void IntegrationPluginZigbeeGenericLights::handleRemoveNode(ZigbeeNode *node, co if (m_colorTemperatureRanges.contains(thing)) { m_colorTemperatureRanges.remove(thing); } + + if (m_colorCapabilities.contains(thing)) { + m_colorCapabilities.remove(thing); + } } } @@ -609,6 +613,10 @@ void IntegrationPluginZigbeeGenericLights::thingRemoved(Thing *thing) if (m_colorTemperatureRanges.contains(thing)) { m_colorTemperatureRanges.remove(thing); } + + if (m_colorCapabilities.contains(thing)) { + m_colorCapabilities.remove(thing); + } } ZigbeeNodeEndpoint *IntegrationPluginZigbeeGenericLights::findEndpoint(Thing *thing) diff --git a/zigbee-tradfri/integrationpluginzigbee-tradfri.json b/zigbee-tradfri/integrationpluginzigbee-tradfri.json index 05d430e1..dbf79d49 100644 --- a/zigbee-tradfri/integrationpluginzigbee-tradfri.json +++ b/zigbee-tradfri/integrationpluginzigbee-tradfri.json @@ -1,7 +1,7 @@ { "name": "ZigbeeTradfri", "displayName": "Zigbee TRÅDFRI", - "id": "6a4343be-9fd6-4015-9ff5-38542651c534", + "id": "ad9480c8-05f0-4ba1-92b1-4dd8e2ae2830", "vendors": [ { "name": "ikeaTradfri", @@ -121,6 +121,236 @@ ] } ] + }, + { + "name": "remote", + "displayName": "TRÅDFRI remote", + "id": "19ee3f56-f958-4b04-8ea3-747b72d7c7d6", + "setupMethod": "JustAdd", + "createMethods": [ "Auto" ], + "interfaces": [ "longpressmultibutton", "batterylevel", "wirelessconnectable" ], + "paramTypes": [ + { + "id": "2ee18d3e-5fb8-4809-a52a-8e420b677f6e", + "name": "ieeeAddress", + "displayName": "IEEE adress", + "type": "QString", + "defaultValue": "00:00:00:00:00:00:00:00" + }, + { + "id": "7bb2e588-5e05-4cf8-bedc-a54d6cdae30e", + "name": "networkUuid", + "displayName": "Zigbee network UUID", + "type": "QString", + "defaultValue": "" + }, + { + "id": "09cbf843-0036-47b8-8c2b-a58939075203", + "name": "endpointId", + "displayName": "Endpoint ID", + "type": "uint", + "defaultValue": 1 + } + ], + "stateTypes": [ + { + "id": "9d5d116c-f742-4766-baf9-e7662f5003a9", + "name": "connected", + "displayName": "Connected", + "displayNameEvent": "Connected changed", + "type": "bool", + "cached": false, + "defaultValue": false + }, + { + "id": "b87d310a-28ba-4546-9f47-822e5182836c", + "name": "signalStrength", + "displayName": "Signal strength", + "displayNameEvent": "Signal strength changed", + "defaultValue": 0, + "maxValue": 100, + "minValue": 0, + "type": "uint", + "unit": "Percentage" + }, + { + "id": "340444fa-b158-4d03-9967-7fa45a96bd45", + "name": "version", + "displayName": "Version", + "displayNameEvent": "Version changed", + "type": "QString", + "cached": true, + "defaultValue": "" + }, + { + "id": "4a72d569-44be-4689-bda6-addc0c6351d4", + "name": "batteryLevel", + "displayName": "Battery", + "displayNameEvent": "Battery changed", + "type": "int", + "unit": "Percentage", + "defaultValue": 0, + "minValue": 0, + "maxValue": 100 + }, + { + "id": "87b60c83-d980-48f5-bc31-0daa241d727e", + "name": "batteryCritical", + "displayName": "Battery critical", + "displayNameEvent": "Battery critical changed", + "type": "bool", + "defaultValue": false + } + ], + "actionTypes": [ + + ], + "eventTypes": [ + { + "id": "4d671edd-d57c-4f72-a43a-d8e4eaa741cf", + "name": "pressed", + "displayName": "Button pressed", + "paramTypes": [ + { + "id": "2e6bc343-41a3-4634-b514-4512ae54afbc", + "name": "buttonName", + "displayName": "Button name", + "type": "QString", + "allowedValues": ["Power"] + } + ] + }, + { + "id": "3fb1bc3b-50ea-4a9f-a5b1-10b1fae2592e", + "name": "longPressed", + "displayName": "Button longpressed", + "paramTypes": [ + { + "id": "4a151723-5798-4087-a4e5-5aa08ecbb9b8", + "name": "buttonName", + "displayName": "Button name", + "type": "QString", + "allowedValues": ["Power"] + } + ] + } + ] + }, + { + "name": "motionSensor", + "displayName": "TRÅDFRI motion sensor", + "id": "30a1090e-8058-4a5b-aee3-4ba112ba3ba7", + "setupMethod": "JustAdd", + "createMethods": [ "Auto" ], + "interfaces": [ "presencesensor", "batterylevel", "wirelessconnectable" ], + "paramTypes": [ + { + "id": "a03fabac-df59-4f4c-a691-237b1c37270f", + "name": "ieeeAddress", + "displayName": "IEEE adress", + "type": "QString", + "defaultValue": "00:00:00:00:00:00:00:00" + }, + { + "id": "263a92bc-a993-4527-915c-1eccb3c2a7c5", + "name": "networkUuid", + "displayName": "Zigbee network UUID", + "type": "QString", + "defaultValue": "" + }, + { + "id": "11e97521-0108-4157-8d85-26d64ecd7723", + "name": "endpointId", + "displayName": "Endpoint ID", + "type": "uint", + "defaultValue": 1 + } + ], + "settingsTypes": [ + { + "id": "31cd2810-453b-4d86-a3ea-5556bcde4822", + "name": "timeout", + "displayName": "Time period", + "type": "uint", + "unit": "Seconds", + "defaultValue": 180, + "minValue": 180 + } + ], + "stateTypes": [ + { + "id": "6bf470bb-4756-43fa-bddf-5e6ce002caa3", + "name": "connected", + "displayName": "Connected", + "displayNameEvent": "Connected changed", + "type": "bool", + "cached": false, + "defaultValue": false + }, + { + "id": "0a54036e-ca25-40fd-8f24-3516c5141c0b", + "name": "signalStrength", + "displayName": "Signal strength", + "displayNameEvent": "Signal strength changed", + "defaultValue": 0, + "maxValue": 100, + "minValue": 0, + "type": "uint", + "unit": "Percentage" + }, + { + "id": "709a215b-5664-4cea-b841-54dd01e387b8", + "name": "version", + "displayName": "Version", + "displayNameEvent": "Version changed", + "type": "QString", + "cached": true, + "defaultValue": "" + }, + { + "id": "e2e414c1-5f21-4244-942a-ee17cb25f1c0", + "name": "batteryLevel", + "displayName": "Battery", + "displayNameEvent": "Battery changed", + "type": "int", + "unit": "Percentage", + "defaultValue": 0, + "minValue": 0, + "maxValue": 100 + }, + { + "id": "21e7c58e-595e-4b7d-bd21-c0a424c876dc", + "name": "batteryCritical", + "displayName": "Battery critical", + "displayNameEvent": "Battery critical changed", + "type": "bool", + "defaultValue": false + }, + { + "id": "16ee7fe2-1a86-40e9-aae5-610f712ca155", + "name": "isPresent", + "displayName": "Present", + "displayNameEvent": "Present changed", + "type": "bool", + "defaultValue": true, + "ioType": "digitalInput" + }, + { + "id": "12041993-c731-4cc1-8010-904b5655d920", + "name": "lastSeenTime", + "displayName": "Last seen time", + "displayNameEvent": "Last seen time changed", + "type": "int", + "unit": "UnixTime", + "defaultValue": 0 + } + ], + "actionTypes": [ + + ], + "eventTypes": [ + + ] } ] } diff --git a/zigbee-tradfri/integrationpluginzigbeetradfri.cpp b/zigbee-tradfri/integrationpluginzigbeetradfri.cpp index 5f9caa63..b06d91e2 100644 --- a/zigbee-tradfri/integrationpluginzigbeetradfri.cpp +++ b/zigbee-tradfri/integrationpluginzigbeetradfri.cpp @@ -37,16 +37,28 @@ IntegrationPluginZigbeeTradfri::IntegrationPluginZigbeeTradfri() { m_ieeeAddressParamTypeIds[onOffSwitchThingClassId] = onOffSwitchThingIeeeAddressParamTypeId; + m_ieeeAddressParamTypeIds[remoteThingClassId] = remoteThingIeeeAddressParamTypeId; + m_ieeeAddressParamTypeIds[motionSensorThingClassId] = motionSensorThingIeeeAddressParamTypeId; m_networkUuidParamTypeIds[onOffSwitchThingClassId] = onOffSwitchThingNetworkUuidParamTypeId; + m_networkUuidParamTypeIds[remoteThingClassId] = remoteThingNetworkUuidParamTypeId; + m_networkUuidParamTypeIds[motionSensorThingClassId] = motionSensorThingNetworkUuidParamTypeId; m_endpointIdParamTypeIds[onOffSwitchThingClassId] = onOffSwitchThingEndpointIdParamTypeId; + m_endpointIdParamTypeIds[remoteThingClassId] = remoteThingEndpointIdParamTypeId; + m_endpointIdParamTypeIds[motionSensorThingClassId] = motionSensorThingEndpointIdParamTypeId; m_connectedStateTypeIds[onOffSwitchThingClassId] = onOffSwitchConnectedStateTypeId; + m_connectedStateTypeIds[remoteThingClassId] = remoteConnectedStateTypeId; + m_connectedStateTypeIds[motionSensorThingClassId] = motionSensorConnectedStateTypeId; m_signalStrengthStateTypeIds[onOffSwitchThingClassId] = onOffSwitchSignalStrengthStateTypeId; + m_signalStrengthStateTypeIds[remoteThingClassId] = remoteSignalStrengthStateTypeId; + m_signalStrengthStateTypeIds[motionSensorThingClassId] = motionSensorSignalStrengthStateTypeId; m_versionStateTypeIds[onOffSwitchThingClassId] = onOffSwitchVersionStateTypeId; + m_versionStateTypeIds[remoteThingClassId] = remoteVersionStateTypeId; + m_versionStateTypeIds[motionSensorThingClassId] = motionSensorVersionStateTypeId; } QString IntegrationPluginZigbeeTradfri::name() const @@ -62,18 +74,38 @@ bool IntegrationPluginZigbeeTradfri::handleNode(ZigbeeNode *node, const QUuid &n bool handled = false; foreach (ZigbeeNodeEndpoint *endpoint, node->endpoints()) { - if (endpoint->profile() == Zigbee::ZigbeeProfileHomeAutomation && - endpoint->deviceId() == Zigbee::HomeAutomationDeviceNonColourController) { + if (endpoint->profile() == Zigbee::ZigbeeProfileHomeAutomation && endpoint->deviceId() == Zigbee::HomeAutomationDeviceNonColourController) { qCDebug(dcZigbeeTradfri()) << "Handeling TRADFRI on/off switch" << node << endpoint; - ThingDescriptor descriptor(onOffSwitchThingClassId); - QString deviceClassName = supportedThings().findById(onOffSwitchThingClassId).displayName(); - descriptor.setTitle(deviceClassName); + createThing(onOffSwitchThingClassId, networkUuid, node, endpoint); + initOnOffSwitch(node, endpoint); + handled = true; + } + + if (endpoint->profile() == Zigbee::ZigbeeProfileHomeAutomation && endpoint->deviceId() == Zigbee::HomeAutomationDeviceOnOffSensor) { + qCDebug(dcZigbeeTradfri()) << "Handeling TRADFRI motion sensor" << node << endpoint; + createThing(motionSensorThingClassId, networkUuid, node, endpoint); + handled = true; + } + + if (endpoint->profile() == Zigbee::ZigbeeProfileLightLink && endpoint->deviceId() == Zigbee::LightLinkDeviceNonColourSceneController) { + qCDebug(dcZigbeeTradfri()) << "Handeling TRADFRI remote control" << node << endpoint; + createThing(remoteThingClassId, networkUuid, node, endpoint); + + ZigbeeClusterBasic *basicCluster = endpoint->inputCluster(ZigbeeClusterLibrary::ClusterIdBasic); + if (!basicCluster) { + qCWarning(dcZigbeeTradfri()) << "Failed to find basic cluster for performing factory reset to defaults"; + initRemote(node, endpoint); + } else { + ZigbeeClusterReply *zclReply = basicCluster->resetToFactoryDefaults(); + connect(zclReply, &ZigbeeClusterReply::finished, node, [=](){ + if (zclReply->error() != ZigbeeClusterReply::ErrorNoError) { + qCWarning(dcZigbeeTradfri()) << "Failed to perform factory reset on basic cluster on" << endpoint; + } + + initRemote(node, endpoint); + }); + } - ParamList params; - params.append(Param(onOffSwitchThingNetworkUuidParamTypeId, networkUuid.toString())); - params.append(Param(onOffSwitchThingIeeeAddressParamTypeId, node->extendedAddress().toString())); - descriptor.setParams(params); - emit autoThingsAppeared({descriptor}); handled = true; } } @@ -84,13 +116,12 @@ bool IntegrationPluginZigbeeTradfri::handleNode(ZigbeeNode *node, const QUuid &n void IntegrationPluginZigbeeTradfri::handleRemoveNode(ZigbeeNode *node, const QUuid &networkUuid) { Q_UNUSED(networkUuid) - Thing *thing = m_thingNodes.key(node); - if (thing) { - qCDebug(dcZigbeeTradfri()) << node << "for" << thing << "has left the network."; - emit autoThingDisappeared(thing->id()); - // Removing it from our map to prevent a loop that would ask the zigbee network to remove this node (see thingRemoved()) + if (m_thingNodes.values().contains(node)) { + Thing *thing = m_thingNodes.key(node); + qCDebug(dcZigbeeTradfri()) << node << "for" << thing << "has left the network."; m_thingNodes.remove(thing); + emit autoThingDisappeared(thing->id()); } } @@ -136,8 +167,8 @@ void IntegrationPluginZigbeeTradfri::setupThing(ThingSetupInfo *info) // Set the version thing->setStateValue(m_versionStateTypeIds.value(thing->thingClassId()), endpoint->softwareBuildId()); + // Thing specific setup if (thing->thingClassId() == onOffSwitchThingClassId) { - // Receive on/off commands ZigbeeClusterOnOff *onOffCluster = endpoint->outputCluster(ZigbeeClusterLibrary::ClusterIdOnOff); if (!onOffCluster) { @@ -155,7 +186,7 @@ void IntegrationPluginZigbeeTradfri::setupThing(ThingSetupInfo *info) }); } - // Receive level cntrol commands + // Receive level control commands ZigbeeClusterLevelControl *levelCluster = endpoint->outputCluster(ZigbeeClusterLibrary::ClusterIdLevelControl); if (!levelCluster) { qCWarning(dcZigbeeTradfri()) << "Could not find level client cluster on" << thing << endpoint; @@ -177,6 +208,7 @@ void IntegrationPluginZigbeeTradfri::setupThing(ThingSetupInfo *info) }); } + // Get battery level changes ZigbeeClusterPowerConfiguration *powerCluster = endpoint->inputCluster(ZigbeeClusterLibrary::ClusterIdPowerConfiguration); if (!powerCluster) { qCWarning(dcZigbeeTradfri()) << "Could not find power configuration cluster on" << thing << endpoint; @@ -195,6 +227,87 @@ void IntegrationPluginZigbeeTradfri::setupThing(ThingSetupInfo *info) } } + if (thing->thingClassId() == motionSensorThingClassId) { + // Create plugintimer if required + if (!m_presenceTimer) { + m_presenceTimer = hardwareManager()->pluginTimerManager()->registerTimer(1); + } + connect(m_presenceTimer, &PluginTimer::timeout, thing, [thing](){ + if (thing->stateValue(motionSensorIsPresentStateTypeId).toBool()) { + int timeout = thing->setting(motionSensorSettingsTimeoutParamTypeId).toInt(); + QDateTime lastSeenTime = QDateTime::fromMSecsSinceEpoch(thing->stateValue(motionSensorLastSeenTimeStateTypeId).toULongLong() * 1000); + if (lastSeenTime.addSecs(timeout) < QDateTime::currentDateTime()) { + thing->setStateValue(motionSensorIsPresentStateTypeId, false); + } + } + }); + + // Receive on/off commands + ZigbeeClusterOnOff *onOffCluster = endpoint->outputCluster(ZigbeeClusterLibrary::ClusterIdOnOff); + if (!onOffCluster) { + qCWarning(dcZigbeeTradfri()) << "Could not find on/off client cluster on" << thing << endpoint; + } else { + connect(onOffCluster, &ZigbeeClusterOnOff::commandOnWithTimedOffSent, thing, [=](bool acceptOnlyWhenOn, quint16 onTime, quint16 offTime){ + qCDebug(dcZigbeeTradfri()) << thing << "command received: Accept only when on:" << acceptOnlyWhenOn << "On time:" << onTime / 10 << "s" << "Off time:" << offTime / 10 << "s"; + thing->setStateValue(motionSensorLastSeenTimeStateTypeId, QDateTime::currentDateTime().toMSecsSinceEpoch() / 1000); + thing->setStateValue(motionSensorIsPresentStateTypeId, true); + m_presenceTimer->start(); + }); + } + + // Get battery level changes + ZigbeeClusterPowerConfiguration *powerCluster = endpoint->inputCluster(ZigbeeClusterLibrary::ClusterIdPowerConfiguration); + if (!powerCluster) { + qCWarning(dcZigbeeTradfri()) << "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(motionSensorBatteryLevelStateTypeId, powerCluster->batteryPercentage()); + thing->setStateValue(motionSensorBatteryCriticalStateTypeId, (powerCluster->batteryPercentage() < 10.0)); + } + + connect(powerCluster, &ZigbeeClusterPowerConfiguration::batteryPercentageChanged, thing, [=](double percentage){ + qCDebug(dcZigbeeTradfri()) << "Battery percentage changed" << percentage << "%" << thing; + thing->setStateValue(motionSensorBatteryLevelStateTypeId, percentage); + thing->setStateValue(motionSensorBatteryCriticalStateTypeId, (percentage < 10.0)); + }); + } + } + + if (thing->thingClassId() == remoteThingClassId) { + // Receive on/off commands + ZigbeeClusterOnOff *onOffCluster = endpoint->outputCluster(ZigbeeClusterLibrary::ClusterIdOnOff); + if (!onOffCluster) { + qCWarning(dcZigbeeTradfri()) << "Could not find on/off client cluster on" << thing << endpoint; + } else { + connect(onOffCluster, &ZigbeeClusterOnOff::commandSent, thing, [=](ZigbeeClusterOnOff::Command command){ + qCDebug(dcZigbeeTradfri()) << thing << "button pressed" << command; + if (command == ZigbeeClusterOnOff::CommandToggle) { + qCDebug(dcZigbeeTradfri()) << thing << "pressed power"; + emit emitEvent(Event(remotePressedEventTypeId, thing->id(), ParamList() << Param(remotePressedEventButtonNameParamTypeId, "Power"))); + } + }); + } + + // Get battery level changes + ZigbeeClusterPowerConfiguration *powerCluster = endpoint->inputCluster(ZigbeeClusterLibrary::ClusterIdPowerConfiguration); + if (!powerCluster) { + qCWarning(dcZigbeeTradfri()) << "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(remoteBatteryLevelStateTypeId, powerCluster->batteryPercentage()); + thing->setStateValue(remoteBatteryCriticalStateTypeId, (powerCluster->batteryPercentage() < 10.0)); + } + + connect(powerCluster, &ZigbeeClusterPowerConfiguration::batteryPercentageChanged, thing, [=](double percentage){ + qCDebug(dcZigbeeTradfri()) << "Battery percentage changed" << percentage << "%" << thing; + thing->setStateValue(remoteBatteryLevelStateTypeId, percentage); + thing->setStateValue(remoteBatteryCriticalStateTypeId, (percentage < 10.0)); + }); + } + } + info->finish(Thing::ThingErrorNoError); } @@ -224,3 +337,294 @@ ZigbeeNodeEndpoint *IntegrationPluginZigbeeTradfri::findEndpoint(Thing *thing) return node->getEndpoint(endpointId); } +void IntegrationPluginZigbeeTradfri::createThing(const ThingClassId &thingClassId, const QUuid &networkUuid, ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint) +{ + ThingDescriptor descriptor(thingClassId); + QString deviceClassName = supportedThings().findById(thingClassId).displayName(); + descriptor.setTitle(deviceClassName); + + ParamList params; + params.append(Param(m_networkUuidParamTypeIds[thingClassId], networkUuid.toString())); + params.append(Param(m_ieeeAddressParamTypeIds[thingClassId], node->extendedAddress().toString())); + params.append(Param(m_endpointIdParamTypeIds[thingClassId], endpoint->endpointId())); + descriptor.setParams(params); + emit autoThingsAppeared({descriptor}); +} + +void IntegrationPluginZigbeeTradfri::initOnOffSwitch(ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint) +{ + // Get the current configured bindings for this node + ZigbeeReply *reply = node->removeAllBindings(); + connect(reply, &ZigbeeReply::finished, node, [=](){ + if (reply->error() != ZigbeeReply::ErrorNoError) { + qCWarning(dcZigbeeTradfri()) << "Failed to remove all bindings for initialization of" << node; + } + + // Read battery, bind and configure attribute reporting for battery + + if (!endpoint->hasInputCluster(ZigbeeClusterLibrary::ClusterIdPowerConfiguration)) { + qCWarning(dcZigbeeTradfri()) << "Failed to initialize the power configuration cluster because the cluster could not be found" << node << endpoint; + return; + } + + qCDebug(dcZigbeeTradfri()) << "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(dcZigbeeTradfri()) << "Failed to read power cluster attributes" << readAttributeReply->error(); + //emit nodeInitialized(node); + return; + } + + qCDebug(dcZigbeeTradfri()) << "Read power configuration cluster attributes finished successfully"; + + // Bind the cluster to the coordinator + qCDebug(dcZigbeeTradfri()) << "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(dcZigbeeTradfri()) << "Failed to bind power cluster to coordinator" << zdoReply->error(); + return; + } + qCDebug(dcZigbeeTradfri()) << "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;//300; + reportingConfig.maxReportingInterval = 120;//2700; + reportingConfig.reportableChange = ZigbeeDataType(static_cast(1)).data(); + + qCDebug(dcZigbeeTradfri()) << "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(dcZigbeeTradfri()) << "Failed to configure power cluster attribute reporting" << reportingReply->error(); + return; + } + + qCDebug(dcZigbeeTradfri()) << "Attribute reporting configuration finished for power cluster" << ZigbeeClusterLibrary::parseAttributeReportingStatusRecords(reportingReply->responseFrame().payload); + + qCDebug(dcZigbeeTradfri()) << "Bind power configuration cluster to coordinator"; + ZigbeeDeviceObjectReply * zdoReply = node->deviceObject()->requestBindShortAddress(endpoint->endpointId(), ZigbeeClusterLibrary::ClusterIdOnOff, 0x0000); + connect(zdoReply, &ZigbeeDeviceObjectReply::finished, node, [=](){ + if (zdoReply->error() != ZigbeeDeviceObjectReply::ErrorNoError) { + qCWarning(dcZigbeeTradfri()) << "Failed to bind on/off cluster to coordinator" << zdoReply->error(); + return; + } + qCDebug(dcZigbeeTradfri()) << "Bind on/off cluster to coordinator finished successfully"; + + qCDebug(dcZigbeeTradfri()) << "Bind power level cluster to coordinator"; + ZigbeeDeviceObjectReply * zdoReply = node->deviceObject()->requestBindShortAddress(endpoint->endpointId(), ZigbeeClusterLibrary::ClusterIdLevelControl, 0x0000); + connect(zdoReply, &ZigbeeDeviceObjectReply::finished, node, [=](){ + if (zdoReply->error() != ZigbeeDeviceObjectReply::ErrorNoError) { + qCWarning(dcZigbeeTradfri()) << "Failed to bind level cluster to coordinator" << zdoReply->error(); + return; + } + qCDebug(dcZigbeeTradfri()) << "Bind level cluster to coordinator finished successfully"; + + qCDebug(dcZigbeeTradfri()) << "Read binding table from node" << node; + ZigbeeReply *reply = node->readBindingTableEntries(); + connect(reply, &ZigbeeReply::finished, node, [=](){ + if (reply->error() != ZigbeeReply::ErrorNoError) { + qCWarning(dcZigbeeTradfri()) << "Failed to read binding table from" << node; + } else { + foreach (const ZigbeeDeviceProfile::BindingTableListRecord &binding, node->bindingTableRecords()) { + qCDebug(dcZigbeeTradfri()) << node << binding; + } + } + }); + }); + }); + }); + }); + }); + }); +} + +void IntegrationPluginZigbeeTradfri::initRemote(ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint) +{ + // Get the current configured bindings for this node + ZigbeeReply *reply = node->removeAllBindings(); + connect(reply, &ZigbeeReply::finished, node, [=](){ + if (reply->error() != ZigbeeReply::ErrorNoError) { + qCWarning(dcZigbeeTradfri()) << "Failed to remove all bindings for initialization of" << node; + } + + // Read battery, bind and configure attribute reporting for battery + + if (!endpoint->hasInputCluster(ZigbeeClusterLibrary::ClusterIdPowerConfiguration)) { + qCWarning(dcZigbeeTradfri()) << "Failed to initialize the power configuration cluster because the cluster could not be found" << node << endpoint; + return; + } + + qCDebug(dcZigbeeTradfri()) << "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(dcZigbeeTradfri()) << "Failed to read power cluster attributes" << readAttributeReply->error(); + //emit nodeInitialized(node); + return; + } + + qCDebug(dcZigbeeTradfri()) << "Read power configuration cluster attributes finished successfully"; + + // Bind the cluster to the coordinator + qCDebug(dcZigbeeTradfri()) << "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(dcZigbeeTradfri()) << "Failed to bind power cluster to coordinator" << zdoReply->error(); + return; + } + qCDebug(dcZigbeeTradfri()) << "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;//300; + reportingConfig.maxReportingInterval = 120;//2700; + reportingConfig.reportableChange = ZigbeeDataType(static_cast(1)).data(); + + qCDebug(dcZigbeeTradfri()) << "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(dcZigbeeTradfri()) << "Failed to configure power cluster attribute reporting" << reportingReply->error(); + return; + } + + qCDebug(dcZigbeeTradfri()) << "Attribute reporting configuration finished for power cluster" << ZigbeeClusterLibrary::parseAttributeReportingStatusRecords(reportingReply->responseFrame().payload); + + qCDebug(dcZigbeeTradfri()) << "Bind power configuration cluster to coordinator"; + ZigbeeDeviceObjectReply * zdoReply = node->deviceObject()->requestBindShortAddress(endpoint->endpointId(), ZigbeeClusterLibrary::ClusterIdOnOff, 0x0000); + connect(zdoReply, &ZigbeeDeviceObjectReply::finished, node, [=](){ + if (zdoReply->error() != ZigbeeDeviceObjectReply::ErrorNoError) { + qCWarning(dcZigbeeTradfri()) << "Failed to bind on/off cluster to coordinator" << zdoReply->error(); + return; + } + qCDebug(dcZigbeeTradfri()) << "Bind on/off cluster to coordinator finished successfully"; + + qCDebug(dcZigbeeTradfri()) << "Bind power level cluster to coordinator"; + ZigbeeDeviceObjectReply * zdoReply = node->deviceObject()->requestBindShortAddress(endpoint->endpointId(), ZigbeeClusterLibrary::ClusterIdLevelControl, 0x0000); + connect(zdoReply, &ZigbeeDeviceObjectReply::finished, node, [=](){ + if (zdoReply->error() != ZigbeeDeviceObjectReply::ErrorNoError) { + qCWarning(dcZigbeeTradfri()) << "Failed to bind level cluster to coordinator" << zdoReply->error(); + return; + } + qCDebug(dcZigbeeTradfri()) << "Bind level cluster to coordinator finished successfully"; + + qCDebug(dcZigbeeTradfri()) << "Read binding table from node" << node; + ZigbeeReply *reply = node->readBindingTableEntries(); + connect(reply, &ZigbeeReply::finished, node, [=](){ + if (reply->error() != ZigbeeReply::ErrorNoError) { + qCWarning(dcZigbeeTradfri()) << "Failed to read binding table from" << node; + } else { + foreach (const ZigbeeDeviceProfile::BindingTableListRecord &binding, node->bindingTableRecords()) { + qCDebug(dcZigbeeTradfri()) << node << binding; + } + } + }); + }); + }); + }); + }); + }); + }); +} + +void IntegrationPluginZigbeeTradfri::readBindings(ZigbeeNode *node) +{ + qCDebug(dcZigbeeTradfri()) << "Read binding table from node" << node; + ZigbeeReply *reply = node->readBindingTableEntries(); + connect(reply, &ZigbeeReply::finished, node, [=](){ + if (reply->error() != ZigbeeReply::ErrorNoError) { + qCWarning(dcZigbeeTradfri()) << "Failed to read binding table from" << node; + } else { + foreach (const ZigbeeDeviceProfile::BindingTableListRecord &binding, node->bindingTableRecords()) { + qCDebug(dcZigbeeTradfri()) << node << binding; + } + } + }); +} + +void IntegrationPluginZigbeeTradfri::initPowerConfigurationCluster(ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint) +{ + if (!endpoint->hasInputCluster(ZigbeeClusterLibrary::ClusterIdPowerConfiguration)) { + qCWarning(dcZigbeeTradfri()) << "Failed to initialize the power configuration cluster because the cluster could not be found" << node << endpoint; + return; + } + + qCDebug(dcZigbeeTradfri()) << "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(dcZigbeeTradfri()) << "Failed to read power cluster attributes" << readAttributeReply->error(); + //emit nodeInitialized(node); + return; + } + + qCDebug(dcZigbeeTradfri()) << "Read power configuration cluster attributes finished successfully"; + // Bind the cluster to the coordinator + qCDebug(dcZigbeeTradfri()) << "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(dcZigbeeTradfri()) << "Failed to bind power cluster to coordinator" << zdoReply->error(); + return; + } + qCDebug(dcZigbeeTradfri()) << "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;//300; + reportingConfig.maxReportingInterval = 120;//2700; + reportingConfig.reportableChange = ZigbeeDataType(static_cast(1)).data(); + + qCDebug(dcZigbeeTradfri()) << "Configure attribute reporting for power configuration cluster to coordinator"; + ZigbeeClusterReply *reportingReply = endpoint->getInputCluster(ZigbeeClusterLibrary::ClusterIdPowerConfiguration)->configureReporting({reportingConfig}); + connect(reportingReply, &ZigbeeClusterReply::finished, this, [reportingReply](){ + if (reportingReply->error() != ZigbeeClusterReply::ErrorNoError) { + qCWarning(dcZigbeeTradfri()) << "Failed to configure power cluster attribute reporting" << reportingReply->error(); + return; + } + + qCDebug(dcZigbeeTradfri()) << "Attribute reporting configuration finished for power cluster" << ZigbeeClusterLibrary::parseAttributeReportingStatusRecords(reportingReply->responseFrame().payload); + }); + }); + }); +} + +void IntegrationPluginZigbeeTradfri::initOnOffCluster(ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint) +{ + qCDebug(dcZigbeeTradfri()) << "Bind power configuration cluster to coordinator"; + ZigbeeDeviceObjectReply * zdoReply = node->deviceObject()->requestBindShortAddress(endpoint->endpointId(), ZigbeeClusterLibrary::ClusterIdOnOff, 0x0000); + connect(zdoReply, &ZigbeeDeviceObjectReply::finished, node, [=](){ + if (zdoReply->error() != ZigbeeDeviceObjectReply::ErrorNoError) { + qCWarning(dcZigbeeTradfri()) << "Failed to bind on/off cluster to coordinator" << zdoReply->error(); + return; + } + qCDebug(dcZigbeeTradfri()) << "Bind on/off cluster to coordinator finished successfully"; + }); +} + +void IntegrationPluginZigbeeTradfri::initLevelControlCluster(ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint) +{ + qCDebug(dcZigbeeTradfri()) << "Bind power level cluster to coordinator"; + ZigbeeDeviceObjectReply * zdoReply = node->deviceObject()->requestBindShortAddress(endpoint->endpointId(), ZigbeeClusterLibrary::ClusterIdLevelControl, 0x0000); + connect(zdoReply, &ZigbeeDeviceObjectReply::finished, node, [=](){ + if (zdoReply->error() != ZigbeeDeviceObjectReply::ErrorNoError) { + qCWarning(dcZigbeeTradfri()) << "Failed to bind level cluster to coordinator" << zdoReply->error(); + return; + } + qCDebug(dcZigbeeTradfri()) << "Bind level cluster to coordinator finished successfully"; + }); +} + diff --git a/zigbee-tradfri/integrationpluginzigbeetradfri.h b/zigbee-tradfri/integrationpluginzigbeetradfri.h index e40afb1d..8c28cef6 100644 --- a/zigbee-tradfri/integrationpluginzigbeetradfri.h +++ b/zigbee-tradfri/integrationpluginzigbeetradfri.h @@ -57,6 +57,7 @@ public: void thingRemoved(Thing *thing) override; private: + PluginTimer *m_presenceTimer = nullptr; QHash m_thingNodes; QHash m_ieeeAddressParamTypeIds; @@ -68,6 +69,17 @@ private: QHash m_versionStateTypeIds; ZigbeeNodeEndpoint *findEndpoint(Thing *thing); + void createThing(const ThingClassId &thingClassId, const QUuid &networkUuid, ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint); + + void initOnOffSwitch(ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint); + void initRemote(ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint); + + void readBindings(ZigbeeNode *node); + void initPowerConfigurationCluster(ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint); + void initOnOffCluster(ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint); + void initLevelControlCluster(ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint); + + }; #endif // INTEGRATIONPLUGINZIGBEETRADFRI_H