Skip to content

Commit

Permalink
Merge PR #72: Don't destroy things when removing ZigBee nodes from th…
Browse files Browse the repository at this point in the history
…e network
  • Loading branch information
jenkins committed Oct 21, 2023
2 parents a347c99 + 431c981 commit a1faaf5
Show file tree
Hide file tree
Showing 24 changed files with 280 additions and 185 deletions.
126 changes: 88 additions & 38 deletions common/zigbeeintegrationplugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
#include <zcl/measurement/zigbeeclusteroccupancysensing.h>
#include <zcl/ota/zigbeeclusterota.h>
#include <zcl/closures/zigbeeclusterwindowcovering.h>
#include <zcl/closures/zigbeeclusterdoorlock.h>

#include <QColor>
#include <QNetworkRequest>
Expand Down Expand Up @@ -83,10 +84,16 @@ void ZigbeeIntegrationPlugin::handleRemoveNode(ZigbeeNode *node, const QUuid &ne
{
Q_UNUSED(networkUuid)
foreach (Thing *thing, m_thingNodes.keys(node)) {
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_thingNodes.remove(thing);
// Not removing thing as this destroys magic too easily if a device decides to leave and rejoin for whatever reason
// While in theory that's not a use case, in practice it turns out that this is a common thing to do when fiddling with
// ZigBee network instabilites. Instead we're just marking things as disconnected.

// 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_thingNodes.remove(thing);

thing->setStateValue("connected", false);
}
}

Expand All @@ -113,39 +120,7 @@ ZigbeeNode* ZigbeeIntegrationPlugin::manageNode(Thing *thing)
return nullptr;
}

m_thingNodes.insert(thing, node);

// Update connected state
thing->setStateValue("connected", node->reachable());
connect(node, &ZigbeeNode::reachableChanged, thing, [thing](bool reachable){
thing->setStateValue("connected", reachable);
});

// Update signal strength
thing->setStateValue("signalStrength", qRound(node->lqi() * 100.0 / 255.0));
connect(node, &ZigbeeNode::lqiChanged, thing, [thing](quint8 lqi){
uint signalStrength = qRound(lqi * 100.0 / 255.0);
thing->setStateValue("signalStrength", signalStrength);
});

connect(node, &ZigbeeNode::lastSeenChanged, this, [=](){
while (!m_delayedWriteRequests.value(node).isEmpty()) {
DelayedAttributeWriteRequest request = m_delayedWriteRequests[node].takeFirst();
ZigbeeClusterReply *reply = request.cluster->writeAttributes(request.records, request.manufacturerCode);
connect(reply, &ZigbeeClusterReply::finished, this, [=](){
if (reply->error() != ZigbeeClusterReply::ErrorNoError) {
qCWarning(m_dc) << "Error writing attributes on" << thing->name();
}
});
}
while (!m_delayedReadRequests.value(node).isEmpty()) {
DelayedAttributeReadRequest request = m_delayedReadRequests[node].takeFirst();
ZigbeeClusterReply *reply = request.cluster->readAttributes(request.attributes, request.manufacturerCode);
if (reply->error() != ZigbeeClusterReply::ErrorNoError) {
qCWarning(m_dc) << "Error writing attributes on" << thing->name();
}
}
});
setupNode(node, thing);

return node;
}
Expand All @@ -160,7 +135,7 @@ ZigbeeNode *ZigbeeIntegrationPlugin::nodeForThing(Thing *thing)
return m_thingNodes.value(thing);
}

void ZigbeeIntegrationPlugin::createThing(const ThingClassId &thingClassId, ZigbeeNode *node, const ParamList &additionalParams)
Thing *ZigbeeIntegrationPlugin::createThing(const ThingClassId &thingClassId, ZigbeeNode *node, const ParamList &additionalParams)
{
ThingDescriptor descriptor(thingClassId);
QString deviceClassName = supportedThings().findById(thingClassId).displayName();
Expand All @@ -172,7 +147,15 @@ void ZigbeeIntegrationPlugin::createThing(const ThingClassId &thingClassId, Zigb
params.append(Param(tc.paramTypes().findByName("ieeeAddress").id(), node->extendedAddress().toString()));
params.append(additionalParams);
descriptor.setParams(params);
emit autoThingsAppeared({descriptor});

Thing *existingThing = myThings().findByParams(params);
if (!existingThing) {
emit autoThingsAppeared({descriptor});
} else {
qCInfo(m_dc) << "Thing for node" << node << "already existing. Not recreating.";
setupNode(node, existingThing);
}
return existingThing;
}

void ZigbeeIntegrationPlugin::bindCluster(ZigbeeNodeEndpoint *endpoint, ZigbeeClusterLibrary::ClusterId clusterId, int retries)
Expand Down Expand Up @@ -605,6 +588,27 @@ void ZigbeeIntegrationPlugin::configureWindowCoveringInputClusterLiftPercentageA
});
}

void ZigbeeIntegrationPlugin::configureDoorLockInputClusterAttributeReporting(ZigbeeNodeEndpoint *endpoint)
{
ZigbeeClusterLibrary::AttributeReportingConfiguration reportingConfig;
reportingConfig.attributeId = ZigbeeClusterDoorLock::AttributeLockState;
reportingConfig.dataType = Zigbee::Enum8;
reportingConfig.minReportingInterval = 60;
reportingConfig.maxReportingInterval = 120;
reportingConfig.reportableChange = ZigbeeDataType(static_cast<quint8>(1)).data();

qCDebug(m_dc()) << "Configuring attribute reporting for door lock cluster lock state";
ZigbeeClusterReply *reportingReply = endpoint->getInputCluster(ZigbeeClusterLibrary::ClusterIdDoorLock)->configureReporting({reportingConfig});
connect(reportingReply, &ZigbeeClusterReply::finished, this, [=](){
if (reportingReply->error() != ZigbeeClusterReply::ErrorNoError) {
qCWarning(m_dc()) << "Failed to door lock cluster door state attribute reporting" << reportingReply->error();
} else {
qCDebug(m_dc()) << "Attribute reporting configuration finished for door lock cluster lock state" << ZigbeeClusterLibrary::parseAttributeReportingStatusRecords(reportingReply->responseFrame().payload);
}
});

}

void ZigbeeIntegrationPlugin::connectToPowerConfigurationInputCluster(Thing *thing, ZigbeeNodeEndpoint *endpoint, qreal maxVoltage, qreal minVoltage)
{
ZigbeeClusterPowerConfiguration *powerCluster = endpoint->inputCluster<ZigbeeClusterPowerConfiguration>(ZigbeeClusterLibrary::ClusterIdPowerConfiguration);
Expand Down Expand Up @@ -1083,6 +1087,10 @@ void ZigbeeIntegrationPlugin::connectToOtaOutputCluster(Thing *thing, ZigbeeNode
otaCluster->setProperty("lastFirmwareCheck", QDateTime::currentDateTime());

ZigbeeNode *node = nodeForThing(thing);
if (!node) {
qCWarning(m_dc) << "Node for thing" << thing << "not found. Cannot continue with OTA";
return;
}
FirmwareIndexEntry newInfo = checkFirmwareAvailability(m_firmwareIndex, manufacturerCode, imageType, currentFileVersion, node->modelName());
ZigbeeClusterOta::FileVersion currentParsed = ZigbeeClusterOta::parseFileVersion(currentFileVersion);
thing->setStateValue("currentVersion", QString("%0.%1.%2.%3")
Expand Down Expand Up @@ -1664,6 +1672,48 @@ void ZigbeeIntegrationPlugin::updateFirmwareIndex()
});
}

void ZigbeeIntegrationPlugin::setupNode(ZigbeeNode *node, Thing *thing)
{
m_thingNodes.insert(thing, node);

// Delaying the connection setup to only set state values after the thing setup finished
QTimer::singleShot(0, thing, [=](){
// Update connected state
thing->setStateValue("connected", node->reachable());
connect(node, &ZigbeeNode::reachableChanged, thing, [thing](bool reachable){
thing->setStateValue("connected", reachable);
});

// Update signal strength
thing->setStateValue("signalStrength", qRound(node->lqi() * 100.0 / 255.0));
connect(node, &ZigbeeNode::lqiChanged, thing, [thing](quint8 lqi){
uint signalStrength = qRound(lqi * 100.0 / 255.0);
thing->setStateValue("signalStrength", signalStrength);
});

connect(node, &ZigbeeNode::lastSeenChanged, this, [=](){
while (!m_delayedWriteRequests.value(node).isEmpty()) {
DelayedAttributeWriteRequest request = m_delayedWriteRequests[node].takeFirst();
ZigbeeClusterReply *reply = request.cluster->writeAttributes(request.records, request.manufacturerCode);
connect(reply, &ZigbeeClusterReply::finished, this, [=](){
if (reply->error() != ZigbeeClusterReply::ErrorNoError) {
qCWarning(m_dc) << "Error writing attributes on" << thing->name();
}
});
}
while (!m_delayedReadRequests.value(node).isEmpty()) {
DelayedAttributeReadRequest request = m_delayedReadRequests[node].takeFirst();
ZigbeeClusterReply *reply = request.cluster->readAttributes(request.attributes, request.manufacturerCode);
if (reply->error() != ZigbeeClusterReply::ErrorNoError) {
qCWarning(m_dc) << "Error writing attributes on" << thing->name();
}
}
});

createConnections(thing);
});
}

ZigbeeIntegrationPlugin::FirmwareIndexEntry ZigbeeIntegrationPlugin::firmwareInfo(quint16 manufacturerId, quint16 imageType, quint32 fileVersion) const
{
foreach (const FirmwareIndexEntry &entry, m_firmwareIndex) {
Expand Down
6 changes: 5 additions & 1 deletion common/zigbeeintegrationplugin.h
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,9 @@ class ZigbeeIntegrationPlugin: public IntegrationPlugin, public ZigbeeHandler
Thing *thingForNode(ZigbeeNode *node);
ZigbeeNode *nodeForThing(Thing *thing);

virtual void createThing(const ThingClassId &thingClassId, ZigbeeNode *node, const ParamList &additionalParams = ParamList());
virtual Thing *createThing(const ThingClassId &thingClassId, ZigbeeNode *node, const ParamList &additionalParams = ParamList());
virtual void createConnections(Thing *thing) = 0;


void bindCluster(ZigbeeNodeEndpoint *endpoint, ZigbeeClusterLibrary::ClusterId clusterId, int retries = 3);

Expand All @@ -94,6 +96,7 @@ class ZigbeeIntegrationPlugin: public IntegrationPlugin, public ZigbeeHandler
void configureFanControlInputClusterAttributeReporting(ZigbeeNodeEndpoint *endpoint);
void configureIasZoneInputClusterAttributeReporting(ZigbeeNodeEndpoint *endpoint);
void configureWindowCoveringInputClusterLiftPercentageAttributeReporting(ZigbeeNodeEndpoint *endpoint);
void configureDoorLockInputClusterAttributeReporting(ZigbeeNodeEndpoint *endpoint);

void connectToPowerConfigurationInputCluster(Thing *thing, ZigbeeNodeEndpoint *endpoint, qreal maxVoltage = 0, qreal minVoltage = 0);
void connectToThermostatCluster(Thing *thing, ZigbeeNodeEndpoint *endpoint);
Expand Down Expand Up @@ -145,6 +148,7 @@ private slots:
virtual void updateFirmwareIndex();

private:
void setupNode(ZigbeeNode *node, Thing *thing);
FirmwareIndexEntry firmwareInfo(quint16 manufacturerId, quint16 imageType, quint32 fileVersion) const;
QString firmwareFileName(const FirmwareIndexEntry &info) const;
FetchFirmwareReply *fetchFirmware(const FirmwareIndexEntry &info);
Expand Down
22 changes: 17 additions & 5 deletions zigbeedevelco/integrationpluginzigbeedevelco.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -173,14 +173,23 @@ void IntegrationPluginZigbeeDevelco::setupThing(ThingSetupInfo *info)
info->finish(Thing::ThingErrorHardwareNotAvailable);
return;
}

info->finish(Thing::ThingErrorNoError);
}

void IntegrationPluginZigbeeDevelco::createConnections(Thing *thing)
{
ZigbeeNode *node = nodeForThing(thing);
if (!node) {
qCWarning(dcZigbeeDevelco()) << "Node for thing" << thing << "not found.";
return;
}

if (thing->thingClassId() == ioModuleThingClassId) {
// Set the version from the manufacturer specific attribute in base cluster
ZigbeeNodeEndpoint *primaryEndpoint = node->getEndpoint(IO_MODULE_EP_INPUT1);
if (!primaryEndpoint) {
qCWarning(dcZigbeeDevelco()) << "Failed to set up IO module" << thing << ". Could not find endpoint for version parsing.";
info->finish(Thing::ThingErrorSetupFailed);
return;
}

Expand Down Expand Up @@ -326,7 +335,6 @@ void IntegrationPluginZigbeeDevelco::setupThing(ThingSetupInfo *info)
ZigbeeNodeEndpoint *sensorEndpoint = node->getEndpoint(DEVELCO_EP_TEMPERATURE_SENSOR);
if (!sensorEndpoint) {
qCWarning(dcZigbeeDevelco()) << "Failed to set up air quality sensor" << thing << ". Could not find endpoint for version parsing.";
info->finish(Thing::ThingErrorSetupFailed);
return;
}

Expand Down Expand Up @@ -430,14 +438,13 @@ void IntegrationPluginZigbeeDevelco::setupThing(ThingSetupInfo *info)
connectToTemperatureMeasurementInputCluster(thing, temperatureSensorEndpoint);
connectToIlluminanceMeasurementInputCluster(thing, illuminanceSensorEndpoint);
}

info->finish(Thing::ThingErrorNoError);
}

void IntegrationPluginZigbeeDevelco::postSetupThing(Thing *thing)
{
if (thing->thingClassId() == ioModuleThingClassId) {
if (nodeForThing(thing)->reachable()) {
ZigbeeNode *node = nodeForThing(thing);
if (node && node->reachable()) {
readIoModuleOutputPowerStates(thing);
readIoModuleInputPowerStates(thing);
}
Expand All @@ -453,6 +460,11 @@ void IntegrationPluginZigbeeDevelco::executeAction(ThingActionInfo *info)

Thing *thing = info->thing();
ZigbeeNode *node = nodeForThing(info->thing());
if (!node) {
qCWarning(dcZigbeeDevelco()) << "Node for thing" << thing << "not found.";
info->finish(Thing::ThingErrorHardwareNotAvailable);
return;
}

if (thing->thingClassId() == ioModuleThingClassId) {
// Identify
Expand Down
2 changes: 2 additions & 0 deletions zigbeedevelco/integrationpluginzigbeedevelco.h
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ class IntegrationPluginZigbeeDevelco: public ZigbeeIntegrationPlugin
void executeAction(ThingActionInfo *info) override;

private:
void createConnections(Thing *thing) override;

QString parseDevelcoVersionString(ZigbeeNodeEndpoint *endpoint);

void initIoModule(ZigbeeNode *node);
Expand Down
18 changes: 15 additions & 3 deletions zigbeeeurotronic/integrationpluginzigbeeeurotronic.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,17 @@ void IntegrationPluginZigbeeEurotronic::setupThing(ThingSetupInfo *info)
return;
}

info->finish(Thing::ThingErrorNoError);
}

void IntegrationPluginZigbeeEurotronic::createConnections(Thing *thing)
{
ZigbeeNode *node = nodeForThing(thing);
if (!node) {
qCWarning(dcZigbeeEurotronic()) << "Node for thing" << thing << "not found.";
return;
}

ZigbeeNodeEndpoint *endpoint = node->getEndpoint(0x01);
thing->setStateValue("currentVersion", endpoint->deviceVersion());

Expand All @@ -89,7 +99,6 @@ void IntegrationPluginZigbeeEurotronic::setupThing(ThingSetupInfo *info)
ZigbeeClusterThermostat *thermostatCluster = endpoint->inputCluster<ZigbeeClusterThermostat>(ZigbeeClusterLibrary::ClusterIdThermostat);
if (!thermostatCluster) {
qCWarning(dcZigbeeEurotronic()) << "Failed to read thermostat cluster";
info->finish(Thing::ThingErrorHardwareFailure);
return;
}

Expand All @@ -107,8 +116,6 @@ void IntegrationPluginZigbeeEurotronic::setupThing(ThingSetupInfo *info)
}
});
thermostatCluster->readAttributes({0x4008}, 0x1037);

info->finish(Thing::ThingErrorNoError);
}

void IntegrationPluginZigbeeEurotronic::executeAction(ThingActionInfo *info)
Expand Down Expand Up @@ -143,6 +150,11 @@ void IntegrationPluginZigbeeEurotronic::sendNextAction()
}

ZigbeeNode *node = nodeForThing(info->thing());
if (!node) {
qCWarning(dcZigbeeEurotronic()) << "Node for thing" << info->thing() << "not found.";
info->finish(Thing::ThingErrorHardwareNotAvailable, QT_TR_NOOP("ZigBee node not found in network."));
return;
}
ZigbeeNodeEndpoint *endpoint = node->getEndpoint(0x01);

ZigbeeClusterThermostat *thermostatCluster = endpoint->inputCluster<ZigbeeClusterThermostat>(ZigbeeClusterLibrary::ClusterIdThermostat);
Expand Down
2 changes: 2 additions & 0 deletions zigbeeeurotronic/integrationpluginzigbeeeurotronic.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ class IntegrationPluginZigbeeEurotronic: public ZigbeeIntegrationPlugin
void executeAction(ThingActionInfo *info) override;

private:
void createConnections(Thing *thing) override;

void sendNextAction();

private:
Expand Down
Loading

0 comments on commit a1faaf5

Please sign in to comment.