diff --git a/zigbee-philipshue/integrationpluginzigbee-philipshue.json b/zigbee-philipshue/integrationpluginzigbee-philipshue.json index c64dbaad..fa21f314 100644 --- a/zigbee-philipshue/integrationpluginzigbee-philipshue.json +++ b/zigbee-philipshue/integrationpluginzigbee-philipshue.json @@ -100,6 +100,122 @@ ] } ] + }, + { + "name": "outdoorSensor", + "displayName": "Hue outdoor sensor", + "id": "031c45d1-e782-4f91-9f05-259d6b81c7ef", + "setupMethod": "JustAdd", + "createMethods": [ "Auto" ], + "interfaces": [ "presencesensor", "lightsensor", "temperaturesensor", "batterylevel", "wirelessconnectable" ], + "paramTypes": [ + { + "id": "a866df99-7e2a-40e1-b82e-529c03846d5e", + "name": "ieeeAddress", + "displayName": "IEEE adress", + "type": "QString", + "defaultValue": "00:00:00:00:00:00:00:00" + }, + { + "id": "99f4ff28-6d61-4e96-bfc4-c32aa05bb256", + "name": "networkUuid", + "displayName": "Zigbee network UUID", + "type": "QString", + "defaultValue": "" + } + ], + "stateTypes": [ + { + "id": "0d0a5ee1-41f2-4b5a-a3c0-7c8cca1c064d", + "name": "connected", + "displayName": "Connected", + "displayNameEvent": "Connected changed", + "type": "bool", + "cached": false, + "defaultValue": false + }, + { + "id": "d4f85c34-896a-4bab-af71-8b80d9889815", + "name": "signalStrength", + "displayName": "Signal strength", + "displayNameEvent": "Signal strength changed", + "defaultValue": 0, + "maxValue": 100, + "minValue": 0, + "type": "uint", + "unit": "Percentage" + }, + { + "id": "18cb67e1-220f-41d7-9145-1816fc5d38d0", + "name": "version", + "displayName": "Version", + "displayNameEvent": "Version changed", + "type": "QString", + "cached": true, + "defaultValue": "" + }, + { + "id": "00a4450b-d2fe-4e2b-92c8-1ab3a98d1628", + "name": "batteryLevel", + "displayName": "Battery", + "displayNameEvent": "Battery changed", + "type": "int", + "unit": "Percentage", + "defaultValue": 0, + "minValue": 0, + "maxValue": 100 + }, + { + "id": "9341f65a-b0aa-4648-82eb-e8400779f817", + "name": "batteryCritical", + "displayName": "Battery critical", + "displayNameEvent": "Battery critical changed", + "type": "bool", + "defaultValue": false + }, + { + "id": "66e3b840-4f4c-4f4a-a71a-69fb751f2a3b", + "name": "isPresent", + "displayName": "Present", + "displayNameEvent": "Present changed", + "type": "bool", + "defaultValue": true, + "ioType": "digitalInput" + }, + { + "id": "128777a9-75e7-4cc6-b196-691d2ffddbc9", + "name": "lastSeenTime", + "displayName": "Last seen time", + "displayNameEvent": "Last seen time changed", + "type": "int", + "unit": "UnixTime", + "defaultValue": 0 + }, + { + "id": "8077e335-1d18-4e3f-9b5e-6f71af4a33b1", + "name": "lightIntensity", + "displayName": "Light intensity", + "displayNameEvent": "Light intensity changed", + "type": "double", + "unit": "Lux", + "defaultValue": 0 + }, + { + "id": "978f069c-57c0-4e73-a81b-c593bc2e7624", + "name": "temperature", + "displayName": "Temperature", + "displayNameEvent": "Temperature changed", + "type": "double", + "unit": "DegreeCelsius", + "defaultValue": 0.0 + } + ], + "actionTypes": [ + + ], + "eventTypes": [ + + ] } ] } diff --git a/zigbee-philipshue/integrationpluginzigbeephilipshue.cpp b/zigbee-philipshue/integrationpluginzigbeephilipshue.cpp index 5364546a..fdf91ec6 100644 --- a/zigbee-philipshue/integrationpluginzigbeephilipshue.cpp +++ b/zigbee-philipshue/integrationpluginzigbeephilipshue.cpp @@ -36,15 +36,19 @@ IntegrationPluginZigbeePhilipsHue::IntegrationPluginZigbeePhilipsHue() { m_ieeeAddressParamTypeIds[dimmerSwitchThingClassId] = dimmerSwitchThingIeeeAddressParamTypeId; + m_ieeeAddressParamTypeIds[outdoorSensorThingClassId] = outdoorSensorThingIeeeAddressParamTypeId; m_networkUuidParamTypeIds[dimmerSwitchThingClassId] = dimmerSwitchThingNetworkUuidParamTypeId; + m_networkUuidParamTypeIds[outdoorSensorThingClassId] = outdoorSensorThingNetworkUuidParamTypeId; m_connectedStateTypeIds[dimmerSwitchThingClassId] = dimmerSwitchConnectedStateTypeId; + m_connectedStateTypeIds[outdoorSensorThingClassId] = outdoorSensorConnectedStateTypeId; m_signalStrengthStateTypeIds[dimmerSwitchThingClassId] = dimmerSwitchSignalStrengthStateTypeId; + m_signalStrengthStateTypeIds[outdoorSensorThingClassId] = outdoorSensorSignalStrengthStateTypeId; m_versionStateTypeIds[dimmerSwitchThingClassId] = dimmerSwitchVersionStateTypeId; - + m_versionStateTypeIds[outdoorSensorThingClassId] = outdoorSensorVersionStateTypeId; } QString IntegrationPluginZigbeePhilipsHue::name() const @@ -66,8 +70,8 @@ bool IntegrationPluginZigbeePhilipsHue::handleNode(ZigbeeNode *node, const QUuid // Dimmer switch if (endpointOne->profile() == Zigbee::ZigbeeProfileLightLink && - endpointOne->deviceId() == Zigbee::LightLinkDeviceNonColourSceneController - && endpoinTwo->profile() == Zigbee::ZigbeeProfileHomeAutomation && + endpointOne->deviceId() == Zigbee::LightLinkDeviceNonColourSceneController && + endpoinTwo->profile() == Zigbee::ZigbeeProfileHomeAutomation && endpoinTwo->deviceId() == Zigbee::HomeAutomationDeviceSimpleSensor) { qCDebug(dcZigbeePhilipsHue()) << "Handeling Hue dimmer switch" << node << endpointOne << endpoinTwo; @@ -75,6 +79,18 @@ bool IntegrationPluginZigbeePhilipsHue::handleNode(ZigbeeNode *node, const QUuid initDimmerSwitch(node); return true; } + + // Outdoor sensor + if (endpointOne->profile() == Zigbee::ZigbeeProfileLightLink && + endpointOne->deviceId() == Zigbee::LightLinkDeviceOnOffSensor && + endpoinTwo->profile() == Zigbee::ZigbeeProfileHomeAutomation && + endpoinTwo->deviceId() == Zigbee::HomeAutomationDeviceOccupacySensor) { + + qCDebug(dcZigbeePhilipsHue()) << "Handeling Hue outdoor sensor" << node << endpointOne << endpoinTwo; + createThing(outdoorSensorThingClassId, networkUuid, node); + initOutdoorSensor(node); + return true; + } } return handled; @@ -124,7 +140,6 @@ void IntegrationPluginZigbeePhilipsHue::setupThing(ThingSetupInfo *info) thing->setStateValue(m_signalStrengthStateTypeIds.value(thing->thingClassId()), signalStrength); }); - // Thing specific setup if (thing->thingClassId() == dimmerSwitchThingClassId) { ZigbeeNodeEndpoint *endpointZll = node->getEndpoint(0x01); @@ -133,7 +148,6 @@ void IntegrationPluginZigbeePhilipsHue::setupThing(ThingSetupInfo *info) // Set the version thing->setStateValue(m_versionStateTypeIds.value(thing->thingClassId()), endpointZll->softwareBuildId()); - // Receive on/off commands ZigbeeClusterOnOff *onOffCluster = endpointZll->outputCluster(ZigbeeClusterLibrary::ClusterIdOnOff); if (!onOffCluster) { @@ -191,6 +205,81 @@ void IntegrationPluginZigbeePhilipsHue::setupThing(ThingSetupInfo *info) thing->setStateValue(dimmerSwitchBatteryCriticalStateTypeId, (percentage < 10.0)); }); } + } + + if (thing->thingClassId() == outdoorSensorThingClassId) { + ZigbeeNodeEndpoint *endpointHa = node->getEndpoint(0x02); + + // Set the version + thing->setStateValue(m_versionStateTypeIds.value(thing->thingClassId()), endpointHa->softwareBuildId()); + + // Get battery level changes + ZigbeeClusterPowerConfiguration *powerCluster = endpointHa->inputCluster(ZigbeeClusterLibrary::ClusterIdPowerConfiguration); + if (!powerCluster) { + qCWarning(dcZigbeePhilipsHue()) << "Could not find power configuration cluster on" << thing << endpointHa; + } else { + // Only set the initial state if the attribute already exists + if (powerCluster->hasAttribute(ZigbeeClusterPowerConfiguration::AttributeBatteryPercentageRemaining)) { + thing->setStateValue(outdoorSensorBatteryLevelStateTypeId, powerCluster->batteryPercentage()); + thing->setStateValue(outdoorSensorBatteryCriticalStateTypeId, (powerCluster->batteryPercentage() < 10.0)); + } + + connect(powerCluster, &ZigbeeClusterPowerConfiguration::batteryPercentageChanged, thing, [=](double percentage){ + qCDebug(dcZigbeePhilipsHue()) << "Battery percentage changed" << percentage << "%" << thing; + thing->setStateValue(outdoorSensorBatteryLevelStateTypeId, percentage); + thing->setStateValue(outdoorSensorBatteryCriticalStateTypeId, (percentage < 10.0)); + }); + } + + ZigbeeClusterOccupancySensing *occupancyCluster = endpointHa->inputCluster(ZigbeeClusterLibrary::ClusterIdOccupancySensing); + if (!occupancyCluster) { + qCWarning(dcZigbeePhilipsHue()) << "Occupancy cluster not found on" << thing; + } else { + if (occupancyCluster->hasAttribute(ZigbeeClusterOccupancySensing::AttributeOccupancy)) { + thing->setStateValue(outdoorSensorIsPresentStateTypeId, occupancyCluster->occupied()); + thing->setStateValue(outdoorSensorLastSeenTimeStateTypeId, QDateTime::currentMSecsSinceEpoch() / 1000); + } + + connect(occupancyCluster, &ZigbeeClusterOccupancySensing::occupancyChanged, thing, [thing](bool occupancy){ + qCDebug(dcZigbeePhilipsHue()) << "occupancy changed" << occupancy; + // Only change the state if the it changed to true, it will be disabled by the timer + if (occupancy) { + thing->setStateValue(outdoorSensorIsPresentStateTypeId, occupancy); + //m_presenceTimer->start(); + } + thing->setStateValue(outdoorSensorLastSeenTimeStateTypeId, QDateTime::currentMSecsSinceEpoch() / 1000); + }); + + // if (!m_presenceTimer) { + // m_presenceTimer = hardwareManager()->pluginTimerManager()->registerTimer(1); + // } + + // connect(m_presenceTimer, &PluginTimer::timeout, thing, [thing](){ + // if (thing->stateValue(outdoorSensorIsPresentStateTypeId).toBool()) { + // int timeout = thing->setting(outdoorSensorSettingsTimeoutParamTypeId).toInt(); + // QDateTime lastSeenTime = QDateTime::fromMSecsSinceEpoch(thing->stateValue(lumiMotionSensorLastSeenTimeStateTypeId).toULongLong() * 1000); + // if (lastSeenTime.addSecs(timeout) < QDateTime::currentDateTime()) { + // thing->setStateValue(lumiMotionSensorIsPresentStateTypeId, false); + // } + // } + // }); + } + + ZigbeeClusterTemperatureMeasurement *temperatureCluster = endpointHa->inputCluster(ZigbeeClusterLibrary::ClusterIdTemperatureMeasurement); + if (!temperatureCluster) { + qCWarning(dcZigbeePhilipsHue()) << "Could not find the temperature measurement server cluster on" << thing << endpointHa; + } else { + // Only set the state if the cluster actually has the attribute + if (temperatureCluster->hasAttribute(ZigbeeClusterTemperatureMeasurement::AttributeMeasuredValue)) { + thing->setStateValue(outdoorSensorTemperatureStateTypeId, temperatureCluster->temperature()); + } + + connect(temperatureCluster, &ZigbeeClusterTemperatureMeasurement::temperatureChanged, thing, [thing](double temperature){ + qCDebug(dcZigbeePhilipsHue()) << thing << "temperature changed" << temperature << "°C"; + thing->setStateValue(outdoorSensorTemperatureStateTypeId, temperature); + }); + } + } @@ -319,3 +408,149 @@ void IntegrationPluginZigbeePhilipsHue::initDimmerSwitch(ZigbeeNode *node) }); } +void IntegrationPluginZigbeePhilipsHue::initOutdoorSensor(ZigbeeNode *node) +{ + //ZigbeeNodeEndpoint *endpointZll = node->getEndpoint(0x01); + ZigbeeNodeEndpoint *endpointHa = node->getEndpoint(0x02); + + // Get the current configured bindings for this node + ZigbeeReply *reply = node->removeAllBindings(); + connect(reply, &ZigbeeReply::finished, node, [=](){ + if (reply->error() != ZigbeeReply::ErrorNoError) { + qCWarning(dcZigbeePhilipsHue()) << "Failed to remove all bindings for initialization of" << node; + } + + // Read battery, bind and configure attribute reporting for battery + if (!endpointHa->hasInputCluster(ZigbeeClusterLibrary::ClusterIdPowerConfiguration)) { + qCWarning(dcZigbeePhilipsHue()) << "Failed to initialize the power configuration cluster because the cluster could not be found" << node << endpointHa; + return; + } + + qCDebug(dcZigbeePhilipsHue()) << "Read power configuration cluster attributes" << node; + ZigbeeClusterReply *readAttributeReply = endpointHa->getInputCluster(ZigbeeClusterLibrary::ClusterIdPowerConfiguration)->readAttributes({ZigbeeClusterPowerConfiguration::AttributeBatteryPercentageRemaining}); + connect(readAttributeReply, &ZigbeeClusterReply::finished, node, [=](){ + if (readAttributeReply->error() != ZigbeeClusterReply::ErrorNoError) { + qCWarning(dcZigbeePhilipsHue()) << "Failed to read power cluster attributes" << readAttributeReply->error(); + } else { + qCDebug(dcZigbeePhilipsHue()) << "Read power configuration cluster attributes finished successfully"; + } + + + // Bind the cluster to the coordinator + qCDebug(dcZigbeePhilipsHue()) << "Bind power configuration cluster to coordinator IEEE address"; + ZigbeeDeviceObjectReply * zdoReply = node->deviceObject()->requestBindIeeeAddress(endpointHa->endpointId(), ZigbeeClusterLibrary::ClusterIdPowerConfiguration, + hardwareManager()->zigbeeResource()->coordinatorAddress(node->networkUuid()), 0x01); + connect(zdoReply, &ZigbeeDeviceObjectReply::finished, node, [=](){ + if (zdoReply->error() != ZigbeeDeviceObjectReply::ErrorNoError) { + qCWarning(dcZigbeePhilipsHue()) << "Failed to bind power cluster to coordinator" << zdoReply->error(); + } else { + qCDebug(dcZigbeePhilipsHue()) << "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 = 300; + reportingConfig.maxReportingInterval = 2700; + reportingConfig.reportableChange = ZigbeeDataType(static_cast(1)).data(); + + qCDebug(dcZigbeePhilipsHue()) << "Configure attribute reporting for power configuration cluster to coordinator"; + ZigbeeClusterReply *reportingReply = endpointHa->getInputCluster(ZigbeeClusterLibrary::ClusterIdPowerConfiguration)->configureReporting({reportingConfig}); + connect(reportingReply, &ZigbeeClusterReply::finished, this, [=](){ + if (reportingReply->error() != ZigbeeClusterReply::ErrorNoError) { + qCWarning(dcZigbeePhilipsHue()) << "Failed to configure power cluster attribute reporting" << reportingReply->error(); + } else { + qCDebug(dcZigbeePhilipsHue()) << "Attribute reporting configuration finished for power cluster" << ZigbeeClusterLibrary::parseAttributeReportingStatusRecords(reportingReply->responseFrame().payload); + } + + + // Init occupancy sensing cluster + qCDebug(dcZigbeePhilipsHue()) << "Bind occupancy cluster to coordinator IEEE address"; + ZigbeeDeviceObjectReply * zdoReply = node->deviceObject()->requestBindIeeeAddress(endpointHa->endpointId(), ZigbeeClusterLibrary::ClusterIdOccupancySensing, + hardwareManager()->zigbeeResource()->coordinatorAddress(node->networkUuid()), 0x01); + connect(zdoReply, &ZigbeeDeviceObjectReply::finished, node, [=](){ + if (zdoReply->error() != ZigbeeDeviceObjectReply::ErrorNoError) { + qCWarning(dcZigbeePhilipsHue()) << "Failed to bind occupancy cluster to coordinator" << zdoReply->error(); + } else { + qCDebug(dcZigbeePhilipsHue()) << "Bind occupency cluster to coordinator finished successfully"; + } + + + // Configure attribute reporting for temperature + ZigbeeClusterLibrary::AttributeReportingConfiguration reportingConfig; + reportingConfig.attributeId = ZigbeeClusterTemperatureMeasurement::AttributeMeasuredValue; + reportingConfig.dataType = Zigbee::BitMap8; + reportingConfig.minReportingInterval = 1; + reportingConfig.maxReportingInterval = 10; + reportingConfig.reportableChange = ZigbeeDataType(static_cast(1)).data(); + + qCDebug(dcZigbeePhilipsHue()) << "Configure attribute reporting for occupancy cluster to coordinator"; + ZigbeeClusterReply *reportingReply = endpointHa->getInputCluster(ZigbeeClusterLibrary::ClusterIdOccupancySensing)->configureReporting({reportingConfig}); + connect(reportingReply, &ZigbeeClusterReply::finished, this, [=](){ + if (reportingReply->error() != ZigbeeClusterReply::ErrorNoError) { + qCWarning(dcZigbeePhilipsHue()) << "Failed to configure occupancy cluster attribute reporting" << reportingReply->error(); + } else { + qCDebug(dcZigbeePhilipsHue()) << "Attribute reporting configuration finished for occupancy cluster" << ZigbeeClusterLibrary::parseAttributeReportingStatusRecords(reportingReply->responseFrame().payload); + } + + + // Init temperature measurment cluster + qCDebug(dcZigbeePhilipsHue()) << "Bind temperature cluster to coordinator IEEE address"; + ZigbeeDeviceObjectReply * zdoReply = node->deviceObject()->requestBindIeeeAddress(endpointHa->endpointId(), ZigbeeClusterLibrary::ClusterIdTemperatureMeasurement, + hardwareManager()->zigbeeResource()->coordinatorAddress(node->networkUuid()), 0x01); + connect(zdoReply, &ZigbeeDeviceObjectReply::finished, node, [=](){ + if (zdoReply->error() != ZigbeeDeviceObjectReply::ErrorNoError) { + qCWarning(dcZigbeePhilipsHue()) << "Failed to bind temperature cluster to coordinator" << zdoReply->error(); + } else { + qCDebug(dcZigbeePhilipsHue()) << "Bind temperature cluster to coordinator finished successfully"; + } + + // Read current temperature + if (!endpointHa->hasInputCluster(ZigbeeClusterLibrary::ClusterIdTemperatureMeasurement)) { + qCWarning(dcZigbeePhilipsHue()) << "Failed to initialize the temperature cluster because the cluster could not be found" << node << endpointHa; + return; + } + + qCDebug(dcZigbeePhilipsHue()) << "Read temperature cluster attributes" << node; + ZigbeeClusterReply *readAttributeReply = endpointHa->getInputCluster(ZigbeeClusterLibrary::ClusterIdTemperatureMeasurement)->readAttributes({ZigbeeClusterTemperatureMeasurement::AttributeMeasuredValue}); + connect(readAttributeReply, &ZigbeeClusterReply::finished, node, [=](){ + if (readAttributeReply->error() != ZigbeeClusterReply::ErrorNoError) { + qCWarning(dcZigbeePhilipsHue()) << "Failed to read temperature cluster attributes" << readAttributeReply->error(); + } else { + qCDebug(dcZigbeePhilipsHue()) << "Read temperature cluster attributes finished successfully"; + } + + // Configure attribute reporting + + // Configure attribute reporting for temperature + ZigbeeClusterLibrary::AttributeReportingConfiguration reportingConfig; + reportingConfig.attributeId = ZigbeeClusterTemperatureMeasurement::AttributeMeasuredValue; + reportingConfig.dataType = Zigbee::Int16; + reportingConfig.minReportingInterval = 300; + reportingConfig.maxReportingInterval = 600; + reportingConfig.reportableChange = ZigbeeDataType(static_cast(10)).data(); + + qCDebug(dcZigbeePhilipsHue()) << "Configure attribute reporting for temperature cluster to coordinator"; + ZigbeeClusterReply *reportingReply = endpointHa->getInputCluster(ZigbeeClusterLibrary::ClusterIdTemperatureMeasurement)->configureReporting({reportingConfig}); + connect(reportingReply, &ZigbeeClusterReply::finished, this, [=](){ + if (reportingReply->error() != ZigbeeClusterReply::ErrorNoError) { + qCWarning(dcZigbeePhilipsHue()) << "Failed to configure temperature cluster attribute reporting" << reportingReply->error(); + } else { + qCDebug(dcZigbeePhilipsHue()) << "Attribute reporting configuration finished for temperature cluster" << ZigbeeClusterLibrary::parseAttributeReportingStatusRecords(reportingReply->responseFrame().payload); + } + + + + + }); + }); + }); + }); + }); + }); + }); + }); + }); +} + diff --git a/zigbee-philipshue/integrationpluginzigbeephilipshue.h b/zigbee-philipshue/integrationpluginzigbeephilipshue.h index 974b6128..07756382 100644 --- a/zigbee-philipshue/integrationpluginzigbeephilipshue.h +++ b/zigbee-philipshue/integrationpluginzigbeephilipshue.h @@ -70,6 +70,7 @@ private: void createThing(const ThingClassId &thingClassId, const QUuid &networkUuid, ZigbeeNode *node); void initDimmerSwitch(ZigbeeNode *node); + void initOutdoorSensor(ZigbeeNode *node); }; diff --git a/zigbee-tradfri/integrationpluginzigbee-tradfri.json b/zigbee-tradfri/integrationpluginzigbee-tradfri.json index dbf79d49..c51ebedc 100644 --- a/zigbee-tradfri/integrationpluginzigbee-tradfri.json +++ b/zigbee-tradfri/integrationpluginzigbee-tradfri.json @@ -350,6 +350,74 @@ ], "eventTypes": [ + ] + }, + { + "name": "signalRepeater", + "displayName": "TRÅDFRI signal repeater", + "id": "ac51f426-195e-472a-837f-4ba7e4666e6e", + "setupMethod": "JustAdd", + "createMethods": [ "Auto" ], + "interfaces": [ "wirelessconnectable" ], + "paramTypes": [ + { + "id": "fcaf0646-ef18-4e16-afde-39823437b7f4", + "name": "ieeeAddress", + "displayName": "IEEE adress", + "type": "QString", + "defaultValue": "00:00:00:00:00:00:00:00" + }, + { + "id": "5ce9a369-545b-4848-a6d7-6d3bc40779a2", + "name": "networkUuid", + "displayName": "Zigbee network UUID", + "type": "QString", + "defaultValue": "" + }, + { + "id": "3ff01f62-83dc-4856-a7f0-5605091845cc", + "name": "endpointId", + "displayName": "Endpoint ID", + "type": "uint", + "defaultValue": 1 + } + ], + "stateTypes": [ + { + "id": "7415ea93-9b72-4740-b3b1-a2ebb73d1db5", + "name": "connected", + "displayName": "Connected", + "displayNameEvent": "Connected changed", + "type": "bool", + "cached": false, + "defaultValue": false + }, + { + "id": "05758f08-33a8-485b-8572-f87021e3af36", + "name": "signalStrength", + "displayName": "Signal strength", + "displayNameEvent": "Signal strength changed", + "defaultValue": 0, + "maxValue": 100, + "minValue": 0, + "type": "uint", + "unit": "Percentage" + }, + { + "id": "a7ad66f1-7b4c-4458-8a13-e1bb9467bc17", + "name": "version", + "displayName": "Version", + "displayNameEvent": "Version changed", + "type": "QString", + "cached": true, + "defaultValue": "" + } + ], + "actionTypes": [ + + ], + "eventTypes": [ + ] } ] diff --git a/zigbee-tradfri/integrationpluginzigbeetradfri.cpp b/zigbee-tradfri/integrationpluginzigbeetradfri.cpp index d49633af..0c81be17 100644 --- a/zigbee-tradfri/integrationpluginzigbeetradfri.cpp +++ b/zigbee-tradfri/integrationpluginzigbeetradfri.cpp @@ -39,26 +39,32 @@ IntegrationPluginZigbeeTradfri::IntegrationPluginZigbeeTradfri() m_ieeeAddressParamTypeIds[onOffSwitchThingClassId] = onOffSwitchThingIeeeAddressParamTypeId; m_ieeeAddressParamTypeIds[remoteThingClassId] = remoteThingIeeeAddressParamTypeId; m_ieeeAddressParamTypeIds[motionSensorThingClassId] = motionSensorThingIeeeAddressParamTypeId; + m_ieeeAddressParamTypeIds[signalRepeaterThingClassId] = signalRepeaterThingIeeeAddressParamTypeId; m_networkUuidParamTypeIds[onOffSwitchThingClassId] = onOffSwitchThingNetworkUuidParamTypeId; m_networkUuidParamTypeIds[remoteThingClassId] = remoteThingNetworkUuidParamTypeId; m_networkUuidParamTypeIds[motionSensorThingClassId] = motionSensorThingNetworkUuidParamTypeId; + m_networkUuidParamTypeIds[signalRepeaterThingClassId] = signalRepeaterThingNetworkUuidParamTypeId; m_endpointIdParamTypeIds[onOffSwitchThingClassId] = onOffSwitchThingEndpointIdParamTypeId; m_endpointIdParamTypeIds[remoteThingClassId] = remoteThingEndpointIdParamTypeId; m_endpointIdParamTypeIds[motionSensorThingClassId] = motionSensorThingEndpointIdParamTypeId; + m_endpointIdParamTypeIds[signalRepeaterThingClassId] = signalRepeaterThingEndpointIdParamTypeId; m_connectedStateTypeIds[onOffSwitchThingClassId] = onOffSwitchConnectedStateTypeId; m_connectedStateTypeIds[remoteThingClassId] = remoteConnectedStateTypeId; m_connectedStateTypeIds[motionSensorThingClassId] = motionSensorConnectedStateTypeId; + m_connectedStateTypeIds[signalRepeaterThingClassId] = signalRepeaterConnectedStateTypeId; m_signalStrengthStateTypeIds[onOffSwitchThingClassId] = onOffSwitchSignalStrengthStateTypeId; m_signalStrengthStateTypeIds[remoteThingClassId] = remoteSignalStrengthStateTypeId; m_signalStrengthStateTypeIds[motionSensorThingClassId] = motionSensorSignalStrengthStateTypeId; + m_signalStrengthStateTypeIds[signalRepeaterThingClassId] = signalRepeaterSignalStrengthStateTypeId; m_versionStateTypeIds[onOffSwitchThingClassId] = onOffSwitchVersionStateTypeId; m_versionStateTypeIds[remoteThingClassId] = remoteVersionStateTypeId; m_versionStateTypeIds[motionSensorThingClassId] = motionSensorVersionStateTypeId; + m_versionStateTypeIds[signalRepeaterThingClassId] = signalRepeaterVersionStateTypeId; } QString IntegrationPluginZigbeeTradfri::name() const @@ -84,29 +90,20 @@ bool IntegrationPluginZigbeeTradfri::handleNode(ZigbeeNode *node, const QUuid &n if (endpoint->profile() == Zigbee::ZigbeeProfileHomeAutomation && endpoint->deviceId() == Zigbee::HomeAutomationDeviceOnOffSensor) { qCDebug(dcZigbeeTradfri()) << "Handeling TRADFRI motion sensor" << node << endpoint; createThing(motionSensorThingClassId, networkUuid, node, endpoint); + initMotionSensor(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); -// }); -// } initRemote(node, endpoint); + handled = true; + } + if (endpoint->profile() == Zigbee::ZigbeeProfileHomeAutomation && endpoint->deviceId() == Zigbee::HomeAutomationDeviceRangeExtender) { + qCDebug(dcZigbeeTradfri()) << "Handeling TRADFRI signal repeater" << node << endpoint; + createThing(signalRepeaterThingClassId, networkUuid, node, endpoint); handled = true; } } @@ -170,6 +167,24 @@ void IntegrationPluginZigbeeTradfri::setupThing(ThingSetupInfo *info) // Thing specific setup if (thing->thingClassId() == onOffSwitchThingClassId) { + // 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(onOffSwitchBatteryLevelStateTypeId, powerCluster->batteryPercentage()); + thing->setStateValue(onOffSwitchBatteryCriticalStateTypeId, (powerCluster->batteryPercentage() < 10.0)); + } + + connect(powerCluster, &ZigbeeClusterPowerConfiguration::batteryPercentageChanged, thing, [=](double percentage){ + qCDebug(dcZigbeeTradfri()) << "Battery percentage changed" << percentage << "%" << thing; + thing->setStateValue(onOffSwitchBatteryLevelStateTypeId, percentage); + thing->setStateValue(onOffSwitchBatteryCriticalStateTypeId, (percentage < 10.0)); + }); + } + // Receive on/off commands ZigbeeClusterOnOff *onOffCluster = endpoint->outputCluster(ZigbeeClusterLibrary::ClusterIdOnOff); if (!onOffCluster) { @@ -208,7 +223,9 @@ void IntegrationPluginZigbeeTradfri::setupThing(ThingSetupInfo *info) } }); } + } + if (thing->thingClassId() == motionSensorThingClassId) { // Get battery level changes ZigbeeClusterPowerConfiguration *powerCluster = endpoint->inputCluster(ZigbeeClusterLibrary::ClusterIdPowerConfiguration); if (!powerCluster) { @@ -216,19 +233,17 @@ void IntegrationPluginZigbeeTradfri::setupThing(ThingSetupInfo *info) } else { // Only set the initial state if the attribute already exists if (powerCluster->hasAttribute(ZigbeeClusterPowerConfiguration::AttributeBatteryPercentageRemaining)) { - thing->setStateValue(onOffSwitchBatteryLevelStateTypeId, powerCluster->batteryPercentage()); - thing->setStateValue(onOffSwitchBatteryCriticalStateTypeId, (powerCluster->batteryPercentage() < 10.0)); + 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(onOffSwitchBatteryLevelStateTypeId, percentage); - thing->setStateValue(onOffSwitchBatteryCriticalStateTypeId, (percentage < 10.0)); + thing->setStateValue(motionSensorBatteryLevelStateTypeId, percentage); + thing->setStateValue(motionSensorBatteryCriticalStateTypeId, (percentage < 10.0)); }); } - } - if (thing->thingClassId() == motionSensorThingClassId) { // Create plugintimer if required if (!m_presenceTimer) { m_presenceTimer = hardwareManager()->pluginTimerManager()->registerTimer(1); @@ -255,41 +270,9 @@ void IntegrationPluginZigbeeTradfri::setupThing(ThingSetupInfo *info) 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) { @@ -307,6 +290,20 @@ void IntegrationPluginZigbeeTradfri::setupThing(ThingSetupInfo *info) thing->setStateValue(remoteBatteryCriticalStateTypeId, (percentage < 10.0)); }); } + + // 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"))); + } + }); + } } info->finish(Thing::ThingErrorNoError); @@ -538,94 +535,74 @@ void IntegrationPluginZigbeeTradfri::initRemote(ZigbeeNode *node, ZigbeeNodeEndp }); } -void IntegrationPluginZigbeeTradfri::readBindings(ZigbeeNode *node) +void IntegrationPluginZigbeeTradfri::initMotionSensor(ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint) { - qCDebug(dcZigbeeTradfri()) << "Read binding table from node" << node; - ZigbeeReply *reply = node->readBindingTableEntries(); + // 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 read binding table from" << node; - } else { - foreach (const ZigbeeDeviceProfile::BindingTableListRecord &binding, node->bindingTableRecords()) { - qCDebug(dcZigbeeTradfri()) << node << binding; - } - } - }); -} + if (reply->error() != ZigbeeReply::ErrorNoError) { + qCWarning(dcZigbeeTradfri()) << "Failed to remove all bindings for initialization of" << node; + } -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; - } + // Read battery, bind and configure attribute reporting for battery - 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); + 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 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(); + 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()) << "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 = 300; - reportingConfig.maxReportingInterval = 2700; - reportingConfig.reportableChange = ZigbeeDataType(static_cast(1)).data(); + qCDebug(dcZigbeeTradfri()) << "Read power configuration cluster attributes finished successfully"; - 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(); + // 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"; - qCDebug(dcZigbeeTradfri()) << "Attribute reporting configuration finished for power cluster" << ZigbeeClusterLibrary::parseAttributeReportingStatusRecords(reportingReply->responseFrame().payload); + // Configure attribute rporting for battery remaining (0.5 % changes = 1) + ZigbeeClusterLibrary::AttributeReportingConfiguration reportingConfig; + reportingConfig.attributeId = ZigbeeClusterPowerConfiguration::AttributeBatteryPercentageRemaining; + reportingConfig.dataType = Zigbee::Uint8; + reportingConfig.minReportingInterval = 300; + reportingConfig.maxReportingInterval = 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"; + }); + }); }); }); }); } -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 8c28cef6..706fe547 100644 --- a/zigbee-tradfri/integrationpluginzigbeetradfri.h +++ b/zigbee-tradfri/integrationpluginzigbeetradfri.h @@ -73,12 +73,7 @@ private: 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); - + void initMotionSensor(ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint); };