Add generic power socket for light link and home automation

This commit is contained in:
Simon Stürz 2020-11-19 10:39:09 +01:00
parent a58ee272e1
commit 6c9276755a
4 changed files with 322 additions and 43 deletions

View File

@ -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)

View File

@ -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": [
]
}
]
}

View File

@ -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<quint16, ThingClassId> 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<ZigbeeClusterThermostat>(ZigbeeClusterLibrary::ClusterIdThermostat);
@ -184,19 +201,144 @@ void IntegrationPluginZigbeeGeneric::setupThing(ThingSetupInfo *info)
});
}
if (thing->thingClassId() == powerSocketThingClassId) {
ZigbeeClusterOnOff *onOffCluster = endpoint->inputCluster<ZigbeeClusterOnOff>(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<ZigbeeClusterIdentify>(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<ZigbeeClusterOnOff>(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});
}

View File

@ -57,13 +57,22 @@ public:
void thingRemoved(Thing *thing) override;
private:
QHash<Thing*, ZigbeeNode*> m_zigbeeNodes;
QHash<Thing*, ZigbeeNode*> m_thingNodes;
QHash<ThingClassId, ParamTypeId> m_ieeeAddressParamTypeIds;
QHash<ThingClassId, ParamTypeId> m_networkUuidParamTypeIds;
QHash<ThingClassId, ParamTypeId> m_endpointIdParamTypeIds;
QHash<ThingClassId, ParamTypeId> m_modelIdParamTypeIds;
QHash<ThingClassId, ParamTypeId> m_manufacturerIdParamTypeIds;
QHash<ThingClassId, StateTypeId> m_connectedStateTypeIds;
QHash<ThingClassId, StateTypeId> m_signalStrengthStateTypeIds;
QHash<ThingClassId, StateTypeId> 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