diff --git a/zigbee-generic-lights/integrationpluginzigbeegenericlights.cpp b/zigbee-generic-lights/integrationpluginzigbeegenericlights.cpp index 6105b550..42351b39 100644 --- a/zigbee-generic-lights/integrationpluginzigbeegenericlights.cpp +++ b/zigbee-generic-lights/integrationpluginzigbeegenericlights.cpp @@ -90,6 +90,7 @@ QString IntegrationPluginZigbeeGenericLights::name() const bool IntegrationPluginZigbeeGenericLights::handleNode(ZigbeeNode *node, const QUuid &networkUuid) { + bool handled = false; foreach (ZigbeeNodeEndpoint *endpoint, node->endpoints()) { if ((endpoint->profile() == Zigbee::ZigbeeProfile::ZigbeeProfileLightLink && endpoint->deviceId() == Zigbee::LightLinkDevice::LightLinkDeviceOnOffLight) || @@ -98,7 +99,7 @@ bool IntegrationPluginZigbeeGenericLights::handleNode(ZigbeeNode *node, const QU qCDebug(dcZigbeeGenericLights()) << "Handeling on/off light for" << node << endpoint; createLightThing(onOffLightThingClassId, networkUuid, node, endpoint); - return true; + handled = true; } if ((endpoint->profile() == Zigbee::ZigbeeProfile::ZigbeeProfileLightLink && @@ -108,7 +109,7 @@ bool IntegrationPluginZigbeeGenericLights::handleNode(ZigbeeNode *node, const QU qCDebug(dcZigbeeGenericLights()) << "Handeling dimmable light for" << node << endpoint; createLightThing(dimmableLightThingClassId, networkUuid, node, endpoint); - return true; + handled = true; } @@ -119,7 +120,7 @@ bool IntegrationPluginZigbeeGenericLights::handleNode(ZigbeeNode *node, const QU qCDebug(dcZigbeeGenericLights()) << "Handeling color temperature light for" << node << endpoint; createLightThing(colorTemperatureLightThingClassId, networkUuid, node, endpoint); - return true; + handled = true; } if ((endpoint->profile() == Zigbee::ZigbeeProfileLightLink && endpoint->deviceId() == Zigbee::LightLinkDeviceColourLight) || @@ -128,11 +129,11 @@ bool IntegrationPluginZigbeeGenericLights::handleNode(ZigbeeNode *node, const QU qCDebug(dcZigbeeGenericLights()) << "Handeling color light for" << node << endpoint; createLightThing(colorLightThingClassId, networkUuid, node, endpoint); - return true; + handled = true; } } - return false; + return handled; } void IntegrationPluginZigbeeGenericLights::handleRemoveNode(ZigbeeNode *node, const QUuid &networkUuid) diff --git a/zigbee-generic/integrationpluginzigbee-generic.json b/zigbee-generic/integrationpluginzigbee-generic.json index 6e97d53b..4793cfee 100644 --- a/zigbee-generic/integrationpluginzigbee-generic.json +++ b/zigbee-generic/integrationpluginzigbee-generic.json @@ -15,20 +15,41 @@ "createMethods": ["auto"], "interfaces": ["thermostat", "temperaturesensor", "wirelessconnectable"], "paramTypes": [ - { - "id": "f38746d8-0084-43a3-b645-3ec743ea5fbc", - "name": "ieeeAddress", - "displayName": "IEEE adress", - "type": "QString", - "defaultValue": "00:00:00:00:00:00:00:00" - }, - { - "id": "4a92b536-de4c-4701-8117-9bb26dd51c3e", - "name": "networkUuid", - "displayName": "Zigbee network UUID", - "type": "QString", - "defaultValue": "" - } + { + "id": "f38746d8-0084-43a3-b645-3ec743ea5fbc", + "name": "ieeeAddress", + "displayName": "IEEE adress", + "type": "QString", + "defaultValue": "00:00:00:00:00:00:00:00" + }, + { + "id": "4a92b536-de4c-4701-8117-9bb26dd51c3e", + "name": "networkUuid", + "displayName": "Zigbee network UUID", + "type": "QString", + "defaultValue": "" + }, + { + "id": "138a529d-1d85-410a-9d83-93ebc9a5f8ca", + "name": "endpointId", + "displayName": "Endpoint ID", + "type": "uint", + "defaultValue": 1 + }, + { + "id": "ae76acb0-40f4-440b-8ae7-16969fe6c91a", + "name": "manufacturer", + "displayName": "Manufacturer", + "type": "QString", + "defaultValue": "" + }, + { + "id": "4262cfc6-4b04-49fd-af2b-765afca53b0b", + "name": "model", + "displayName": "Model", + "type": "QString", + "defaultValue": "" + } ], "stateTypes": [ { @@ -70,8 +91,114 @@ "minValue": 0, "type": "uint", "unit": "Percentage" + }, + { + "id": "c8bd12d2-b425-4422-9e0e-a65542a47b70", + "name": "version", + "displayName": "Version", + "displayNameEvent": "Version changed", + "type": "QString", + "cached": true, + "defaultValue": "" } ] + }, + { + "name": "powerSocket", + "displayName": "Power socket", + "id": "800a8df8-06cb-4d93-8334-944fcce9651a", + "setupMethod": "JustAdd", + "createMethods": [ "Auto" ], + "interfaces": [ "powersocket", "alert", "wirelessconnectable" ], + "paramTypes": [ + { + "id": "1db22159-08a3-43d0-b515-7b34211b1d04", + "name": "ieeeAddress", + "displayName": "IEEE adress", + "type": "QString", + "defaultValue": "00:00:00:00:00:00:00:00" + }, + { + "id": "26e92d1d-bc2f-4ccd-ad1f-d6d6189d2609", + "name": "networkUuid", + "displayName": "Zigbee network UUID", + "type": "QString", + "defaultValue": "" + }, + { + "id": "c7cbda61-2b84-4f2f-a40d-bcbe0efad08c", + "name": "endpointId", + "displayName": "Endpoint ID", + "type": "uint", + "defaultValue": 1 + }, + { + "id": "8daa4709-1dc1-4717-a529-b78e32002b59", + "name": "manufacturer", + "displayName": "Manufacturer", + "type": "QString", + "defaultValue": "" + }, + { + "id": "ad03bc04-857a-43a9-8bea-a3c9db24461a", + "name": "model", + "displayName": "Model", + "type": "QString", + "defaultValue": "" + } + ], + "stateTypes": [ + { + "id": "b5abd47e-95f1-4e35-94fa-be87c396073a", + "name": "connected", + "displayName": "Connected", + "displayNameEvent": "Connected changed", + "type": "bool", + "cached": false, + "defaultValue": false + }, + { + "id": "21889797-32c0-4f1b-903d-8af71981882b", + "name": "signalStrength", + "displayName": "Signal strength", + "displayNameEvent": "Signal strength changed", + "defaultValue": 0, + "maxValue": 100, + "minValue": 0, + "type": "uint", + "unit": "Percentage" + }, + { + "id": "41c74445-4d2d-46dc-8a63-c98f1df8e742", + "name": "version", + "displayName": "Version", + "displayNameEvent": "Version changed", + "type": "QString", + "cached": true, + "defaultValue": "" + }, + { + "id": "2c8268b9-d76b-415a-b2e9-6bfeabd98a76", + "name": "power", + "displayName": "Power", + "displayNameEvent": "Power changed", + "displayNameAction": "Set power", + "type": "bool", + "defaultValue": false, + "writable": true, + "ioType": "digitalOutput" + } + ], + "actionTypes": [ + { + "id": "4e3b1430-d98e-4f05-bae6-301dac34bd4b", + "name": "alert", + "displayName": "Identify" + } + ], + "eventTypes": [ + + ] } ] } diff --git a/zigbee-generic/integrationpluginzigbeegeneric.cpp b/zigbee-generic/integrationpluginzigbeegeneric.cpp index 4a80dcd6..4c894c5d 100644 --- a/zigbee-generic/integrationpluginzigbeegeneric.cpp +++ b/zigbee-generic/integrationpluginzigbeegeneric.cpp @@ -40,12 +40,28 @@ IntegrationPluginZigbeeGeneric::IntegrationPluginZigbeeGeneric() { m_ieeeAddressParamTypeIds[thermostatThingClassId] = thermostatThingIeeeAddressParamTypeId; + m_ieeeAddressParamTypeIds[powerSocketThingClassId] = powerSocketThingIeeeAddressParamTypeId; m_networkUuidParamTypeIds[thermostatThingClassId] = thermostatThingNetworkUuidParamTypeId; + m_networkUuidParamTypeIds[powerSocketThingClassId] = powerSocketThingNetworkUuidParamTypeId; + + m_endpointIdParamTypeIds[thermostatThingClassId] = thermostatThingEndpointIdParamTypeId; + m_endpointIdParamTypeIds[powerSocketThingClassId] = powerSocketThingEndpointIdParamTypeId; + + m_manufacturerIdParamTypeIds[thermostatThingClassId] = thermostatThingManufacturerParamTypeId; + m_manufacturerIdParamTypeIds[powerSocketThingClassId] = powerSocketThingManufacturerParamTypeId; + + m_modelIdParamTypeIds[thermostatThingClassId] = thermostatThingModelParamTypeId; + m_modelIdParamTypeIds[powerSocketThingClassId] = powerSocketThingModelParamTypeId; m_connectedStateTypeIds[thermostatThingClassId] = thermostatConnectedStateTypeId; + m_connectedStateTypeIds[powerSocketThingClassId] = powerSocketConnectedStateTypeId; m_signalStrengthStateTypeIds[thermostatThingClassId] = thermostatSignalStrengthStateTypeId; + m_signalStrengthStateTypeIds[powerSocketThingClassId] = powerSocketSignalStrengthStateTypeId; + + m_versionStateTypeIds[thermostatThingClassId] = thermostatVersionStateTypeId; + m_versionStateTypeIds[powerSocketThingClassId] = powerSocketVersionStateTypeId; } QString IntegrationPluginZigbeeGeneric::name() const @@ -55,25 +71,23 @@ QString IntegrationPluginZigbeeGeneric::name() const bool IntegrationPluginZigbeeGeneric::handleNode(ZigbeeNode *node, const QUuid &networkUuid) { - qCDebug(dcZigbeeGeneric()) << "handleNode called for:" << node; - - QHash devicesThingClassIdsMap; - devicesThingClassIdsMap.insert(Zigbee::HomeAutomationDeviceThermostat, thermostatThingClassId); - bool handled = false; - foreach (ZigbeeNodeEndpoint *endpoint, node->endpoints()) { - qCDebug(dcZigbeeGeneric()) << "Checking ndoe endpoint:" << endpoint->endpointId() << endpoint->deviceId(); + qCDebug(dcZigbeeGeneric()) << "Checking node endpoint:" << endpoint->endpointId() << endpoint->deviceId(); + if (endpoint->profile() == Zigbee::ZigbeeProfile::ZigbeeProfileHomeAutomation && + endpoint->deviceId() == Zigbee::HomeAutomationDeviceThermostat) { + qCDebug(dcZigbeeGeneric()) << "Handeling thermostat endpoint for" << node << endpoint; + createThing(thermostatThingClassId, networkUuid, node, endpoint); + handled = true; + } - if (devicesThingClassIdsMap.contains(endpoint->deviceId())) { - ThingClassId thingClassId = devicesThingClassIdsMap.value(endpoint->deviceId()); - ThingDescriptor descriptor(thingClassId, endpoint->modelIdentifier(), endpoint->manufacturerName()); - ParamList params; - params << Param(m_ieeeAddressParamTypeIds.value(thingClassId), node->extendedAddress().toString()); - params << Param(m_networkUuidParamTypeIds.value(thingClassId), networkUuid.toString()); - descriptor.setParams(params); - emit autoThingsAppeared({descriptor}); + if ((endpoint->profile() == Zigbee::ZigbeeProfile::ZigbeeProfileLightLink && + endpoint->deviceId() == Zigbee::LightLinkDevice::LightLinkDeviceOnOffPlugin) || + (endpoint->profile() == Zigbee::ZigbeeProfile::ZigbeeProfileHomeAutomation && + endpoint->deviceId() == Zigbee::HomeAutomationDeviceOnOffPlugin)) { + qCDebug(dcZigbeeGeneric()) << "Handeling power socket endpoint for" << node << endpoint; + createThing(powerSocketThingClassId, networkUuid, node, endpoint); handled = true; } } @@ -84,13 +98,13 @@ bool IntegrationPluginZigbeeGeneric::handleNode(ZigbeeNode *node, const QUuid &n void IntegrationPluginZigbeeGeneric::handleRemoveNode(ZigbeeNode *node, const QUuid &networkUuid) { Q_UNUSED(networkUuid) - Thing *thing = m_zigbeeNodes.key(node); + Thing *thing = m_thingNodes.key(node); if (thing) { qCDebug(dcZigbeeGeneric()) << 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()) - m_zigbeeNodes.remove(thing); + m_thingNodes.remove(thing); } } @@ -105,21 +119,21 @@ void IntegrationPluginZigbeeGeneric::setupThing(ThingSetupInfo *info) QUuid networkUuid = thing->paramValue(m_networkUuidParamTypeIds.value(thing->thingClassId())).toUuid(); qCDebug(dcZigbeeGeneric()) << "Nework uuid:" << networkUuid; ZigbeeAddress zigbeeAddress = ZigbeeAddress(thing->paramValue(m_ieeeAddressParamTypeIds.value(thing->thingClassId())).toString()); - ZigbeeNode *node = m_zigbeeNodes.value(thing); + ZigbeeNode *node = m_thingNodes.value(thing); if (!node) { node = hardwareManager()->zigbeeResource()->claimNode(this, networkUuid, zigbeeAddress); } + if (!node) { qCWarning(dcZigbeeGeneric()) << "Zigbee node for" << info->thing()->name() << "not found.ยด"; info->finish(Thing::ThingErrorHardwareNotAvailable); return; } + m_thingNodes.insert(thing, node); - m_zigbeeNodes.insert(thing, node); - - ZigbeeNodeEndpoint *endpoint = node->getEndpoint(0x01); + ZigbeeNodeEndpoint *endpoint = findEndpoint(thing); if (!endpoint) { - qCWarning(dcZigbeeGeneric()) << "Zigbee endpoint 1 not found on" << thing->name(); + qCWarning(dcZigbeeGeneric()) << "Could not find endpoint for" << thing; info->finish(Thing::ThingErrorSetupFailed); return; } @@ -138,6 +152,9 @@ void IntegrationPluginZigbeeGeneric::setupThing(ThingSetupInfo *info) thing->setStateValue(m_signalStrengthStateTypeIds.value(thing->thingClassId()), signalStrength); }); + // Set the version + thing->setStateValue(m_versionStateTypeIds.value(thing->thingClassId()), endpoint->softwareBuildId()); + // Type specific setup if (thing->thingClassId() == thermostatThingClassId) { ZigbeeClusterThermostat *thermostatCluster = endpoint->inputCluster(ZigbeeClusterLibrary::ClusterIdThermostat); @@ -184,19 +201,144 @@ void IntegrationPluginZigbeeGeneric::setupThing(ThingSetupInfo *info) }); } + if (thing->thingClassId() == powerSocketThingClassId) { + ZigbeeClusterOnOff *onOffCluster = endpoint->inputCluster(ZigbeeClusterLibrary::ClusterIdOnOff); + if (onOffCluster) { + if (onOffCluster->hasAttribute(ZigbeeClusterOnOff::AttributeOnOff)) { + thing->setStateValue(powerSocketPowerStateTypeId, onOffCluster->power()); + } + + connect(onOffCluster, &ZigbeeClusterOnOff::powerChanged, thing, [thing](bool power){ + qCDebug(dcZigbeeGeneric()) << thing << "power changed" << power; + thing->setStateValue(powerSocketPowerStateTypeId, power); + }); + + connect(node, &ZigbeeNode::reachableChanged, thing, [=](bool reachable){ + if (reachable) { + ZigbeeClusterReply *reply = onOffCluster->readAttributes({ZigbeeClusterOnOff::AttributeOnOff}); + connect(reply, &ZigbeeClusterReply::finished, thing, [=](){ + if (reply->error() != ZigbeeClusterReply::ErrorNoError) { + qCWarning(dcZigbeeGeneric()) << "Reading attribute from" << thing << "finished with error" << reply->error(); + } + // Note: the state will be updated using the power changed signal from the cluster + }); + } + }); + } else { + qCWarning(dcZigbeeGeneric()) << "Could not find the OnOff input cluster on" << thing << endpoint; + } + } + info->finish(Thing::ThingErrorNoError); } void IntegrationPluginZigbeeGeneric::executeAction(ThingActionInfo *info) { + if (!hardwareManager()->zigbeeResource()->available()) { + info->finish(Thing::ThingErrorHardwareNotAvailable); + return; + } + + // Get the node + Thing *thing = info->thing(); + ZigbeeNode *node = m_thingNodes.value(thing); + if (!node->reachable()) { + info->finish(Thing::ThingErrorHardwareNotAvailable); + return; + } + + // Get the endpoint + ZigbeeNodeEndpoint *endpoint = findEndpoint(thing); + if (!endpoint) { + info->finish(Thing::ThingErrorHardwareNotAvailable); + return; + } + + if (thing->thingClassId() == powerSocketThingClassId) { + if (info->action().actionTypeId() == powerSocketAlertActionTypeId) { + ZigbeeClusterIdentify *identifyCluster = endpoint->inputCluster(ZigbeeClusterLibrary::ClusterIdIdentify); + if (!identifyCluster) { + qCWarning(dcZigbeeGeneric()) << "Could not find identify cluster for" << thing << "in" << m_thingNodes.value(thing); + info->finish(Thing::ThingErrorHardwareFailure); + return; + } + + // Send the command trough the network + ZigbeeClusterReply *reply = identifyCluster->identify(2); + 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() == powerSocketPowerActionTypeId) { + ZigbeeClusterOnOff *onOffCluster = endpoint->inputCluster(ZigbeeClusterLibrary::ClusterIdOnOff); + if (!onOffCluster) { + qCWarning(dcZigbeeGeneric()) << "Could not find on/off cluster for" << thing << "in" << endpoint; + info->finish(Thing::ThingErrorHardwareFailure); + return; + } + + // Send the command trough the network + bool power = info->action().param(powerSocketPowerActionPowerParamTypeId).value().toBool(); + qCDebug(dcZigbeeGeneric()) << "Set power for" << thing << "to" << power; + ZigbeeClusterReply *reply = (power ? onOffCluster->commandOn() : onOffCluster->commandOff()); + connect(reply, &ZigbeeClusterReply::finished, info, [=](){ + // Note: reply will be deleted automatically + if (reply->error() != ZigbeeClusterReply::ErrorNoError) { + qCWarning(dcZigbeeGeneric()) << "Failed to set power on" << thing << reply->error(); + info->finish(Thing::ThingErrorHardwareFailure); + } else { + info->finish(Thing::ThingErrorNoError); + qCDebug(dcZigbeeGeneric()) << "Set power finished successfully for" << thing; + thing->setStateValue(powerSocketPowerStateTypeId, power); + } + }); + return; + } + } + info->finish(Thing::ThingErrorUnsupportedFeature); } void IntegrationPluginZigbeeGeneric::thingRemoved(Thing *thing) { - ZigbeeNode *node = m_zigbeeNodes.take(thing); + ZigbeeNode *node = m_thingNodes.take(thing); if (node) { QUuid networkUuid = thing->paramValue(m_networkUuidParamTypeIds.value(thing->thingClassId())).toUuid(); hardwareManager()->zigbeeResource()->removeNodeFromNetwork(networkUuid, node); } } + +ZigbeeNodeEndpoint *IntegrationPluginZigbeeGeneric::findEndpoint(Thing *thing) +{ + ZigbeeNode *node = m_thingNodes.value(thing); + if (!node) { + qCWarning(dcZigbeeGeneric()) << "Could not find the node for" << thing; + return nullptr; + } + + quint8 endpointId = thing->paramValue(m_endpointIdParamTypeIds.value(thing->thingClassId())).toUInt(); + return node->getEndpoint(endpointId); +} + +void IntegrationPluginZigbeeGeneric::createThing(const ThingClassId &thingClassId, const QUuid &networkUuid, ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint) +{ + ThingDescriptor descriptor(thingClassId); + QString deviceClassName = supportedThings().findById(thingClassId).displayName(); + descriptor.setTitle(QString("%1 (%2 - %3)").arg(deviceClassName).arg(endpoint->manufacturerName()).arg(endpoint->modelIdentifier())); + + 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())); + params.append(Param(m_modelIdParamTypeIds[thingClassId], endpoint->modelIdentifier())); + params.append(Param(m_manufacturerIdParamTypeIds[thingClassId], endpoint->manufacturerName())); + descriptor.setParams(params); + emit autoThingsAppeared({descriptor}); +} diff --git a/zigbee-generic/integrationpluginzigbeegeneric.h b/zigbee-generic/integrationpluginzigbeegeneric.h index 39f755eb..11d50d22 100644 --- a/zigbee-generic/integrationpluginzigbeegeneric.h +++ b/zigbee-generic/integrationpluginzigbeegeneric.h @@ -57,13 +57,22 @@ public: void thingRemoved(Thing *thing) override; private: - QHash m_zigbeeNodes; + QHash m_thingNodes; QHash m_ieeeAddressParamTypeIds; QHash m_networkUuidParamTypeIds; + QHash m_endpointIdParamTypeIds; + QHash m_modelIdParamTypeIds; + QHash m_manufacturerIdParamTypeIds; QHash m_connectedStateTypeIds; QHash m_signalStrengthStateTypeIds; + QHash m_versionStateTypeIds; + + // Get the endpoint for the given thing + ZigbeeNodeEndpoint *findEndpoint(Thing *thing); + void createThing(const ThingClassId &thingClassId, const QUuid &networkUuid, ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint); + }; #endif // INTEGRATIONPLUGINZIGBEEGENERIC_H