From 1e197fb4ce440d7e31c38e180dd5a6d54615024e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Sun, 16 May 2021 09:54:19 +0200 Subject: [PATCH 1/3] Add zigbee develco air quality sensor --- .../integrationpluginzigbeedevelco.cpp | 276 +++++++++++++++++- .../integrationpluginzigbeedevelco.h | 33 ++- .../integrationpluginzigbeedevelco.json | 93 ++++++ 3 files changed, 382 insertions(+), 20 deletions(-) diff --git a/zigbeedevelco/integrationpluginzigbeedevelco.cpp b/zigbeedevelco/integrationpluginzigbeedevelco.cpp index 2e91b338..ed36694c 100644 --- a/zigbeedevelco/integrationpluginzigbeedevelco.cpp +++ b/zigbeedevelco/integrationpluginzigbeedevelco.cpp @@ -40,12 +40,16 @@ IntegrationPluginZigbeeDevelco::IntegrationPluginZigbeeDevelco() { m_ieeeAddressParamTypeIds[ioModuleThingClassId] = ioModuleThingIeeeAddressParamTypeId; + m_ieeeAddressParamTypeIds[airQualitySensorThingClassId] = airQualitySensorThingIeeeAddressParamTypeId; m_networkUuidParamTypeIds[ioModuleThingClassId] = ioModuleThingNetworkUuidParamTypeId; + m_networkUuidParamTypeIds[airQualitySensorThingClassId] = airQualitySensorThingNetworkUuidParamTypeId; m_connectedStateTypeIds[ioModuleThingClassId] = ioModuleConnectedStateTypeId; + m_connectedStateTypeIds[airQualitySensorThingClassId] = airQualitySensorConnectedStateTypeId; m_signalStrengthStateTypeIds[ioModuleThingClassId] = ioModuleSignalStrengthStateTypeId; + m_signalStrengthStateTypeIds[airQualitySensorThingClassId] = airQualitySensorSignalStrengthStateTypeId; } QString IntegrationPluginZigbeeDevelco::name() const @@ -69,6 +73,13 @@ bool IntegrationPluginZigbeeDevelco::handleNode(ZigbeeNode *node, const QUuid &n createThing(ioModuleThingClassId, networkUuid, node); handled = true; } + } else if (node->modelName() == "AQSZB-110") { + if (node->hasEndpoint(AIR_QUALITY_SENSOR_EP_SENSOR)) { + qCDebug(dcZigbeeDevelco()) << "Found air quality sensor" << node << networkUuid.toString(); + initAirQualitySensor(node); + createThing(airQualitySensorThingClassId, networkUuid, node); + handled = true; + } } return handled; @@ -81,7 +92,6 @@ void IntegrationPluginZigbeeDevelco::handleRemoveNode(ZigbeeNode *node, const QU if (thing) { qCDebug(dcZigbeeDevelco()) << 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_thingNodes.remove(thing); } @@ -128,16 +138,17 @@ void IntegrationPluginZigbeeDevelco::setupThing(ThingSetupInfo *info) info->finish(Thing::ThingErrorSetupFailed); return; } + if (primaryEndpoint->hasInputCluster(ZigbeeClusterLibrary::ClusterIdBasic)) { ZigbeeCluster *basicCluster = primaryEndpoint->getInputCluster(ZigbeeClusterLibrary::ClusterIdBasic); - if (basicCluster->hasAttribute(DEVELCO_ATTRIBUTE_SW_VERSION)) { + if (basicCluster->hasAttribute(DEVELCO_BASIC_ATTRIBUTE_SW_VERSION)) { thing->setStateValue(ioModuleVersionStateTypeId, parseDevelcoVersionString(primaryEndpoint)); } else { readDevelcoFirmwareVersion(node, primaryEndpoint); } connect(basicCluster, &ZigbeeCluster::attributeChanged, this, [=](const ZigbeeClusterAttribute &attribute){ - if (attribute.id() == DEVELCO_ATTRIBUTE_SW_VERSION) { + if (attribute.id() == DEVELCO_BASIC_ATTRIBUTE_SW_VERSION) { thing->setStateValue(ioModuleVersionStateTypeId, parseDevelcoVersionString(primaryEndpoint)); } }); @@ -268,6 +279,114 @@ void IntegrationPluginZigbeeDevelco::setupThing(ThingSetupInfo *info) }); } } + } else if (thing->thingClassId() == airQualitySensorThingClassId) { + ZigbeeNodeEndpoint *sensorEndpoint = node->getEndpoint(AIR_QUALITY_SENSOR_EP_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; + } + + // Handle reachable state from node + thing->setStateValue(m_connectedStateTypeIds.value(thing->thingClassId()), node->reachable()); + connect(node, &ZigbeeNode::reachableChanged, thing, [=](bool reachable){ + thing->setStateValue(m_connectedStateTypeIds.value(thing->thingClassId()), reachable); + }); + + // Version state + if (sensorEndpoint->hasInputCluster(ZigbeeClusterLibrary::ClusterIdBasic)) { + ZigbeeCluster *basicCluster = sensorEndpoint->getInputCluster(ZigbeeClusterLibrary::ClusterIdBasic); + if (basicCluster->hasAttribute(DEVELCO_BASIC_ATTRIBUTE_SW_VERSION)) { + thing->setStateValue(airQualitySensorVersionStateTypeId, parseDevelcoVersionString(sensorEndpoint)); + } + + connect(basicCluster, &ZigbeeCluster::attributeChanged, this, [=](const ZigbeeClusterAttribute &attribute){ + if (attribute.id() == DEVELCO_BASIC_ATTRIBUTE_SW_VERSION) { + thing->setStateValue(airQualitySensorVersionStateTypeId, parseDevelcoVersionString(sensorEndpoint)); + } + }); + } + + // Temperature + ZigbeeClusterTemperatureMeasurement *temperatureCluster = sensorEndpoint->inputCluster(ZigbeeClusterLibrary::ClusterIdTemperatureMeasurement); + if (!temperatureCluster) { + qCWarning(dcZigbeeDevelco()) << "Could not find the temperature measurement server cluster on" << thing << sensorEndpoint; + } else { + // Only set the state if the cluster actually has the attribute + if (temperatureCluster->hasAttribute(ZigbeeClusterTemperatureMeasurement::AttributeMeasuredValue)) { + thing->setStateValue(airQualitySensorTemperatureStateTypeId, temperatureCluster->temperature()); + } + connect(temperatureCluster, &ZigbeeClusterTemperatureMeasurement::temperatureChanged, thing, [thing](double temperature){ + qCDebug(dcZigbeeDevelco()) << thing << "temperature changed" << temperature << "°C"; + thing->setStateValue(airQualitySensorTemperatureStateTypeId, temperature); + }); + } + + // Humidity + ZigbeeClusterRelativeHumidityMeasurement *humidityCluster = sensorEndpoint->inputCluster(ZigbeeClusterLibrary::ClusterIdRelativeHumidityMeasurement); + if (!humidityCluster) { + qCWarning(dcZigbeeDevelco()) << "Could not find the humidity measurement server cluster on" << thing << sensorEndpoint; + } else { + // Only set the state if the cluster actually has the attribute + if (humidityCluster->hasAttribute(ZigbeeClusterRelativeHumidityMeasurement::AttributeMeasuredValue)) { + thing->setStateValue(airQualitySensorHumidityStateTypeId, humidityCluster->humidity()); + } + connect(humidityCluster, &ZigbeeClusterRelativeHumidityMeasurement::humidityChanged, thing, [thing](double humidity){ + qCDebug(dcZigbeeDevelco()) << thing << "humidity changed" << humidity << "%"; + thing->setStateValue(airQualitySensorHumidityStateTypeId, humidity); + }); + } + + // Battery voltage + ZigbeeClusterPowerConfiguration *powerConfigurationCluster = sensorEndpoint->inputCluster(ZigbeeClusterLibrary::ClusterIdPowerConfiguration); + if (!powerConfigurationCluster) { + qCWarning(dcZigbeeDevelco()) << "Could not find the power configuration server cluster on" << thing << sensorEndpoint; + } else { + // Only set the state if the cluster actually has the attribute + if (powerConfigurationCluster->hasAttribute(ZigbeeClusterPowerConfiguration::AttributeBatteryVoltage)) { + int batteryVoltage = powerConfigurationCluster->attribute(ZigbeeClusterPowerConfiguration::AttributeBatteryVoltage).dataType().toUInt8() * 100; + qCDebug(dcZigbeeDevelco()) << thing << "battery voltage" << batteryVoltage << "mV"; + thing->setStateValue(airQualitySensorBatteryCriticalStateTypeId, batteryVoltage < 2500); + } + connect(powerConfigurationCluster, &ZigbeeClusterPowerConfiguration::attributeChanged, thing, [=](const ZigbeeClusterAttribute &attribute){ + if (attribute.id() == ZigbeeClusterPowerConfiguration::AttributeBatteryVoltage) { + int batteryVoltage = attribute.dataType().toUInt8() * 100; + qCDebug(dcZigbeeDevelco()) << thing << "battery voltage" << batteryVoltage << "mV"; + thing->setStateValue(airQualitySensorBatteryCriticalStateTypeId, batteryVoltage < 2500); + } + }); + } + + // VOC + ZigbeeCluster *vocCluster = sensorEndpoint->getInputCluster(static_cast(AIR_QUALITY_SENSOR_VOC_MEASUREMENT_CLUSTER_ID)); + if (!vocCluster) { + qCWarning(dcZigbeeDevelco()) << "Could not find the VOC measurement server cluster on" << thing << sensorEndpoint; + } else { + // Only set the state if the cluster actually has the attribute + if (vocCluster->hasAttribute(AIR_QUALITY_SENSOR_VOC_MEASUREMENT_ATTRIBUTE_MEASURED_VALUE)) { + ZigbeeClusterAttribute measuredValueAttribute = vocCluster->attribute(AIR_QUALITY_SENSOR_VOC_MEASUREMENT_ATTRIBUTE_MEASURED_VALUE); + bool valueOk = false; + quint16 value = measuredValueAttribute.dataType().toUInt16(&valueOk); + if (valueOk) { + thing->setStateValue(airQualitySensorVocStateTypeId, value); + } else { + qCWarning(dcZigbeeDevelco()) << "Failed to convert VOC measurment value" << measuredValueAttribute; + } + } + + connect(vocCluster, &ZigbeeCluster::attributeChanged, thing, [=](const ZigbeeClusterAttribute &attribute){ + if (attribute.id() == AIR_QUALITY_SENSOR_VOC_MEASUREMENT_ATTRIBUTE_MEASURED_VALUE) { + bool valueOk = false; + quint16 value = attribute.dataType().toUInt16(&valueOk); + if (valueOk) { + qCDebug(dcZigbeeDevelco()) << thing << "VOC changed" << value << "ppm"; + thing->setStateValue(airQualitySensorVocStateTypeId, value); + } else { + qCWarning(dcZigbeeDevelco()) << "Failed to convert VOC measurment value" << attribute; + } + } + }); + } } info->finish(Thing::ThingErrorNoError); @@ -473,12 +592,12 @@ QString IntegrationPluginZigbeeDevelco::parseDevelcoVersionString(ZigbeeNodeEndp return versionString; } - if (!basicCluster->hasAttribute(DEVELCO_ATTRIBUTE_SW_VERSION)) { + if (!basicCluster->hasAttribute(DEVELCO_BASIC_ATTRIBUTE_SW_VERSION)) { qCWarning(dcZigbeeDevelco()) << "Could not find manufacturer specific develco software version attribute in basic cluster on" << endpoint; return versionString; } - ZigbeeClusterAttribute versionAttribute = basicCluster->attribute(DEVELCO_ATTRIBUTE_SW_VERSION); + ZigbeeClusterAttribute versionAttribute = basicCluster->attribute(DEVELCO_BASIC_ATTRIBUTE_SW_VERSION); // 1 Byte octet string length, 3 byte version infromation if (versionAttribute.dataType().data().length() < 4 || versionAttribute.dataType().data().at(0) != 3) { qCWarning(dcZigbeeDevelco()) << "Failed to parse version string from manufacturer specific develco software version attribute" << versionAttribute; @@ -510,6 +629,17 @@ void IntegrationPluginZigbeeDevelco::initIoModule(ZigbeeNode *node) configureBinaryInputReporting(node, node->getEndpoint(IO_MODULE_EP_INPUT4)); } +void IntegrationPluginZigbeeDevelco::initAirQualitySensor(ZigbeeNode *node) +{ + qCDebug(dcZigbeeDevelco()) << "Start initializing air quality sensor" << node; + readDevelcoFirmwareVersion(node, node->getEndpoint(AIR_QUALITY_SENSOR_EP_SENSOR)); + + configureTemperatureReporting(node, node->getEndpoint(AIR_QUALITY_SENSOR_EP_SENSOR)); + configureHumidityReporting(node, node->getEndpoint(AIR_QUALITY_SENSOR_EP_SENSOR)); + configureBattryVoltageReporting(node, node->getEndpoint(AIR_QUALITY_SENSOR_EP_SENSOR)); + configureVocReporting(node, node->getEndpoint(AIR_QUALITY_SENSOR_EP_SENSOR)); +} + void IntegrationPluginZigbeeDevelco::configureOnOffPowerReporting(ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint) { qCDebug(dcZigbeeDevelco()) << "Bind on/off cluster to coordinator IEEE address" << node << endpoint; @@ -521,7 +651,7 @@ void IntegrationPluginZigbeeDevelco::configureOnOffPowerReporting(ZigbeeNode *no qCDebug(dcZigbeeDevelco()) << "Bind on/off cluster to coordinator finished successfully"; } - // Configure attribute reporting for lock state + // Configure attribute reporting ZigbeeClusterLibrary::AttributeReportingConfiguration reportingConfig; reportingConfig.attributeId = ZigbeeClusterOnOff::AttributeOnOff; reportingConfig.minReportingInterval = 0; @@ -551,7 +681,7 @@ void IntegrationPluginZigbeeDevelco::configureBinaryInputReporting(ZigbeeNode *n qCDebug(dcZigbeeDevelco()) << "Bind binary input cluster to coordinator finished successfully"; } - // Configure attribute reporting for lock state + // Configure attribute reporting ZigbeeClusterLibrary::AttributeReportingConfiguration reportingConfig; reportingConfig.attributeId = ZigbeeClusterBinaryInput::AttributePresentValue; reportingConfig.minReportingInterval = 0; @@ -570,6 +700,136 @@ void IntegrationPluginZigbeeDevelco::configureBinaryInputReporting(ZigbeeNode *n }); } +void IntegrationPluginZigbeeDevelco::configureTemperatureReporting(ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint) +{ + // Note: don not read initial value since it returns an invalid masurement. The device needs some time for initial measuring + + qCDebug(dcZigbeeDevelco()) << "Bind temperature measurement cluster to coordinator IEEE address" << node << endpoint; + ZigbeeDeviceObjectReply * zdoReply = node->deviceObject()->requestBindIeeeAddress(endpoint->endpointId(), ZigbeeClusterLibrary::ClusterIdTemperatureMeasurement, hardwareManager()->zigbeeResource()->coordinatorAddress(node->networkUuid()), 0x01); + connect(zdoReply, &ZigbeeDeviceObjectReply::finished, node, [=](){ + if (zdoReply->error() != ZigbeeDeviceObjectReply::ErrorNoError) { + qCWarning(dcZigbeeDevelco()) << "Failed to bind temperature measurement cluster to coordinator" << zdoReply->error(); + } else { + qCDebug(dcZigbeeDevelco()) << "Bind temperature measurement cluster to coordinator finished successfully"; + } + + // Configure attribute reporting + ZigbeeClusterLibrary::AttributeReportingConfiguration reportingConfig; + reportingConfig.attributeId = ZigbeeClusterTemperatureMeasurement::AttributeMeasuredValue; + reportingConfig.dataType = Zigbee::Int16; + reportingConfig.minReportingInterval = 60; + reportingConfig.maxReportingInterval = 300; + reportingConfig.reportableChange = ZigbeeDataType(static_cast(10)).data(); + + qCDebug(dcZigbeeDevelco()) << "Configure attribute reporting for temperature measurement cluster" << node << endpoint; + ZigbeeClusterReply *reportingReply = endpoint->getInputCluster(ZigbeeClusterLibrary::ClusterIdTemperatureMeasurement)->configureReporting({reportingConfig}); + connect(reportingReply, &ZigbeeClusterReply::finished, this, [=](){ + if (reportingReply->error() != ZigbeeClusterReply::ErrorNoError) { + qCWarning(dcZigbeeDevelco()) << "Failed configure attribute reporting on temperature measurement cluster" << reportingReply->error(); + } else { + qCDebug(dcZigbeeDevelco()) << "Attribute reporting configuration finished for on temperature measurement cluster" << ZigbeeClusterLibrary::parseAttributeReportingStatusRecords(reportingReply->responseFrame().payload); + } + }); + }); +} + +void IntegrationPluginZigbeeDevelco::configureHumidityReporting(ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint) +{ + // Note: don not read initial value since it returns an invalid masurement. The device needs some time for initial measuring + qCDebug(dcZigbeeDevelco()) << "Bind humidity measurement cluster to coordinator IEEE address" << node << endpoint; + ZigbeeDeviceObjectReply * zdoReply = node->deviceObject()->requestBindIeeeAddress(endpoint->endpointId(), ZigbeeClusterLibrary::ClusterIdRelativeHumidityMeasurement, hardwareManager()->zigbeeResource()->coordinatorAddress(node->networkUuid()), 0x01); + connect(zdoReply, &ZigbeeDeviceObjectReply::finished, node, [=](){ + if (zdoReply->error() != ZigbeeDeviceObjectReply::ErrorNoError) { + qCWarning(dcZigbeeDevelco()) << "Failed to bind humidity measurement cluster to coordinator" << zdoReply->error(); + } else { + qCDebug(dcZigbeeDevelco()) << "Bind humidity measurement cluster to coordinator finished successfully"; + } + + // Configure attribute reporting + ZigbeeClusterLibrary::AttributeReportingConfiguration reportingConfig; + reportingConfig.attributeId = ZigbeeClusterRelativeHumidityMeasurement::AttributeMeasuredValue; + reportingConfig.dataType = Zigbee::Uint16; + reportingConfig.minReportingInterval = 60; + reportingConfig.maxReportingInterval = 300; + reportingConfig.reportableChange = ZigbeeDataType(static_cast(10)).data(); + + qCDebug(dcZigbeeDevelco()) << "Configure attribute reporting for humidity measurement cluster" << node << endpoint; + ZigbeeClusterReply *reportingReply = endpoint->getInputCluster(ZigbeeClusterLibrary::ClusterIdRelativeHumidityMeasurement)->configureReporting({reportingConfig}); + connect(reportingReply, &ZigbeeClusterReply::finished, this, [=](){ + if (reportingReply->error() != ZigbeeClusterReply::ErrorNoError) { + qCWarning(dcZigbeeDevelco()) << "Failed configure attribute reporting on humidity measurement cluster" << reportingReply->error(); + } else { + qCDebug(dcZigbeeDevelco()) << "Attribute reporting configuration finished for on humidity measurement cluster" << ZigbeeClusterLibrary::parseAttributeReportingStatusRecords(reportingReply->responseFrame().payload); + } + }); + }); +} + +void IntegrationPluginZigbeeDevelco::configureBattryVoltageReporting(ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint) +{ + // Note: don not read initial value since it returns an invalid masurement. The device needs some time for initial measuring + qCDebug(dcZigbeeDevelco()) << "Bind power configuration cluster to coordinator IEEE address" << node << endpoint; + 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(dcZigbeeDevelco()) << "Failed to bind power configuration cluster to coordinator" << zdoReply->error(); + } else { + qCDebug(dcZigbeeDevelco()) << "Bind power configuration cluster to coordinator finished successfully"; + } + + // Configure attribute reporting + ZigbeeClusterLibrary::AttributeReportingConfiguration reportingConfig; + reportingConfig.attributeId = ZigbeeClusterPowerConfiguration::AttributeBatteryVoltage; + reportingConfig.dataType = Zigbee::Uint8; + reportingConfig.minReportingInterval = 60; + reportingConfig.maxReportingInterval = 300; + reportingConfig.reportableChange = ZigbeeDataType(static_cast(1)).data(); + + qCDebug(dcZigbeeDevelco()) << "Configure attribute reporting for power configuration cluster" << node << endpoint; + ZigbeeClusterReply *reportingReply = endpoint->getInputCluster(ZigbeeClusterLibrary::ClusterIdPowerConfiguration)->configureReporting({reportingConfig}); + connect(reportingReply, &ZigbeeClusterReply::finished, this, [=](){ + if (reportingReply->error() != ZigbeeClusterReply::ErrorNoError) { + qCWarning(dcZigbeeDevelco()) << "Failed configure attribute reporting on power configuration cluster" << reportingReply->error(); + } else { + qCDebug(dcZigbeeDevelco()) << "Attribute reporting configuration finished for on power configuration cluster" << ZigbeeClusterLibrary::parseAttributeReportingStatusRecords(reportingReply->responseFrame().payload); + } + }); + }); +} + +void IntegrationPluginZigbeeDevelco::configureVocReporting(ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint) +{ + // Note: don not read initial value since it returns an invalid masurement. The device needs some time for initial measuring + + qCDebug(dcZigbeeDevelco()) << "Bind VOC measurement cluster to coordinator IEEE address" << node << endpoint; + ZigbeeDeviceObjectReply * zdoReply = node->deviceObject()->requestBindIeeeAddress(endpoint->endpointId(), AIR_QUALITY_SENSOR_VOC_MEASUREMENT_CLUSTER_ID, hardwareManager()->zigbeeResource()->coordinatorAddress(node->networkUuid()), 0x01); + connect(zdoReply, &ZigbeeDeviceObjectReply::finished, node, [=](){ + if (zdoReply->error() != ZigbeeDeviceObjectReply::ErrorNoError) { + qCWarning(dcZigbeeDevelco()) << "Failed to bind VOC measurement cluster to coordinator" << zdoReply->error(); + } else { + qCDebug(dcZigbeeDevelco()) << "Bind VOC measurement cluster to coordinator finished successfully"; + } + + // Configure attribute reporting + ZigbeeClusterLibrary::AttributeReportingConfiguration reportingConfig; + reportingConfig.attributeId = AIR_QUALITY_SENSOR_VOC_MEASUREMENT_ATTRIBUTE_MEASURED_VALUE; + reportingConfig.dataType = Zigbee::Uint16; + reportingConfig.minReportingInterval = 60; + reportingConfig.maxReportingInterval = 300; + reportingConfig.reportableChange = ZigbeeDataType(static_cast(10)).data(); + + qCDebug(dcZigbeeDevelco()) << "Configure attribute reporting for VOC measurement cluster" << node << endpoint; + ZigbeeClusterReply *reportingReply = endpoint->getInputCluster(static_cast(AIR_QUALITY_SENSOR_VOC_MEASUREMENT_CLUSTER_ID))->configureReporting({reportingConfig}, Zigbee::Develco); + connect(reportingReply, &ZigbeeClusterReply::finished, this, [=](){ + if (reportingReply->error() != ZigbeeClusterReply::ErrorNoError) { + qCWarning(dcZigbeeDevelco()) << "Failed configure attribute reporting on VOC measurement cluster" << reportingReply->error(); + } else { + qCDebug(dcZigbeeDevelco()) << "Attribute reporting configuration finished for on VOC measurement cluster" << ZigbeeClusterLibrary::parseAttributeReportingStatusRecords(reportingReply->responseFrame().payload); + } + }); + }); +} + void IntegrationPluginZigbeeDevelco::readDevelcoFirmwareVersion(ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint) { // Read manufacturer specific basic cluster attribute 0x8000 @@ -580,7 +840,7 @@ void IntegrationPluginZigbeeDevelco::readDevelcoFirmwareVersion(ZigbeeNode *node } // We have to read the color capabilities - ZigbeeClusterReply *reply = basicCluster->readAttributes({DEVELCO_ATTRIBUTE_SW_VERSION}, Zigbee::Develco); + ZigbeeClusterReply *reply = basicCluster->readAttributes({DEVELCO_BASIC_ATTRIBUTE_SW_VERSION}, Zigbee::Develco); connect(reply, &ZigbeeClusterReply::finished, node, [=](){ if (reply->error() != ZigbeeClusterReply::ErrorNoError) { qCWarning(dcZigbeeDevelco()) << "Failed to read manufacturer specific version attribute on" << node << endpoint << basicCluster; diff --git a/zigbeedevelco/integrationpluginzigbeedevelco.h b/zigbeedevelco/integrationpluginzigbeedevelco.h index b7b0093a..53b78014 100644 --- a/zigbeedevelco/integrationpluginzigbeedevelco.h +++ b/zigbeedevelco/integrationpluginzigbeedevelco.h @@ -59,19 +59,21 @@ #define IO_MODULE_EP_OUTPUT1 0x74 #define IO_MODULE_EP_OUTPUT2 0x75 - -/* Develco manufacturer specific Basic cluster attributes - * 0x8000 Primary SW version - * 0x8010 Primary Bootloader SW version - * 0x8020 Primary HW version - * 0x8030 Primary HW name - * 0x8050 Primary SW version 3rd party +/* Air quality sensor - manufacturer specific clustr + * https://www.develcoproducts.com/media/1674/aqszb-110-technical-manual-air-quality-sensor-04-08-20.pdf */ -#define DEVELCO_ATTRIBUTE_SW_VERSION 0x8000 -#define DEVELCO_ATTRIBUTE_BOOTLOADER_VERSION 0x8010 -#define DEVELCO_ATTRIBUTE_HARDWARE_VERSION 0x8020 -#define DEVELCO_ATTRIBUTE_HARDWARE_NAME 0x8030 -#define DEVELCO_ATTRIBUTE_3RD_PARTY_SW_VERSION 0x8050 +#define AIR_QUALITY_SENSOR_EP_SENSOR 0x26 +#define AIR_QUALITY_SENSOR_VOC_MEASUREMENT_CLUSTER_ID 0xfc03 +#define AIR_QUALITY_SENSOR_VOC_MEASUREMENT_ATTRIBUTE_MEASURED_VALUE 0x0000 +#define AIR_QUALITY_SENSOR_VOC_MEASUREMENT_ATTRIBUTE_MIN_MEASURED_VALUE 0x0001 +#define AIR_QUALITY_SENSOR_VOC_MEASUREMENT_ATTRIBUTE_RESOLUTION 0x0003 + +/* Develco manufacturer specific Basic cluster attributes */ +#define DEVELCO_BASIC_ATTRIBUTE_SW_VERSION 0x8000 +#define DEVELCO_BASIC_ATTRIBUTE_BOOTLOADER_VERSION 0x8010 +#define DEVELCO_BASIC_ATTRIBUTE_HARDWARE_VERSION 0x8020 +#define DEVELCO_BASIC_ATTRIBUTE_HARDWARE_NAME 0x8030 +#define DEVELCO_BASIC_ATTRIBUTE_3RD_PARTY_SW_VERSION 0x8050 class IntegrationPluginZigbeeDevelco: public IntegrationPlugin, public ZigbeeHandler @@ -108,9 +110,16 @@ private: QString parseDevelcoVersionString(ZigbeeNodeEndpoint *endpoint); void initIoModule(ZigbeeNode *node); + void initAirQualitySensor(ZigbeeNode *node); + void configureOnOffPowerReporting(ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint); void configureBinaryInputReporting(ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint); + void configureTemperatureReporting(ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint); + void configureHumidityReporting(ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint); + void configureBattryVoltageReporting(ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint); + void configureVocReporting(ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint); + void readDevelcoFirmwareVersion(ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint); void readOnOffPowerAttribute(ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint); void readBinaryInputPresentValueAttribute(ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint); diff --git a/zigbeedevelco/integrationpluginzigbeedevelco.json b/zigbeedevelco/integrationpluginzigbeedevelco.json index bbf58f73..3cc2e74d 100644 --- a/zigbeedevelco/integrationpluginzigbeedevelco.json +++ b/zigbeedevelco/integrationpluginzigbeedevelco.json @@ -147,6 +147,99 @@ "displayName": "Impulse output 2" } ] + }, + { + "id": "9f966405-4d63-4ae8-8472-a3ab7bbcadaa", + "name": "airQualitySensor", + "displayName": "Air qualiy sensor", + "createMethods": ["auto"], + "interfaces": ["temperaturesensor", "humiditysensor", "battery", "wirelessconnectable"], + "paramTypes": [ + { + "id": "dbad9e63-1adc-45ef-8bfc-4947060f19f4", + "name": "ieeeAddress", + "displayName": "IEEE adress", + "type": "QString", + "defaultValue": "00:00:00:00:00:00:00:00" + }, + { + "id": "7c042425-f95d-479b-a6af-cee7e8ce9c38", + "name": "networkUuid", + "displayName": "Zigbee network UUID", + "type": "QString", + "defaultValue": "" + } + ], + "stateTypes": [ + { + "id": "c6f65bb8-1234-40f2-8855-84ca28f48150", + "name": "connected", + "displayName": "Connected", + "displayNameEvent": "Connected changed", + "type": "bool", + "cached": false, + "defaultValue": false + }, + { + "id": "19cfb35b-6a2e-4efe-9366-287c2f13c027", + "name": "signalStrength", + "displayName": "Signal strength", + "displayNameEvent": "Signal strength changed", + "defaultValue": 0, + "maxValue": 100, + "minValue": 0, + "type": "uint", + "unit": "Percentage" + }, + { + "id": "5d1c46ca-9306-473a-a721-165831ff53cc", + "name": "version", + "displayName": "Version", + "displayNameEvent": "Version changed", + "type": "QString", + "cached": true, + "defaultValue": "" + }, + { + "id": "2d31d98e-dcd7-4700-a90e-a0d051ad6b40", + "name": "temperature", + "displayName": "Temperature", + "displayNameEvent": "Temperature changed", + "type": "double", + "unit": "DegreeCelsius", + "defaultValue": 0.0 + }, + { + "id": "5bc2825c-666f-4013-9583-3a88ceebecc2", + "name": "humidity", + "displayName": "Humidity", + "displayNameEvent": "Humidity changed", + "maxValue": 100, + "minValue": 0, + "unit": "Percentage", + "type": "double", + "defaultValue": 0.0 + }, + { + "id": "048ed452-785f-4361-b7a1-f955f4a622b2", + "name": "batteryCritical", + "displayName": "Battery critical", + "displayNameEvent": "Battery critical changed", + "type": "bool", + "defaultValue": false + }, + { + "id": "1ba14107-4241-46d0-b65f-eb6e8c6f8e08", + "name": "voc", + "displayName": "VOC", + "displayNameEvent": "VOC changed", + "type": "uint", + "unit": "PartsPerMillion", + "suggestLogging": true, + "defaultValue": 0 + } + ], + "actionTypes": [ ] } ] } From 492b59e9d1eb2a50622d0759871f380a4a7d6891 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Fri, 21 May 2021 08:46:39 +0200 Subject: [PATCH 2/3] Add indoor air quality interface and catch invalid VOC measurement value --- .../integrationpluginzigbeedevelco.cpp | 32 +++++++++++++++++-- .../integrationpluginzigbeedevelco.h | 1 + .../integrationpluginzigbeedevelco.json | 17 +++++++++- 3 files changed, 46 insertions(+), 4 deletions(-) diff --git a/zigbeedevelco/integrationpluginzigbeedevelco.cpp b/zigbeedevelco/integrationpluginzigbeedevelco.cpp index ed36694c..4bb38134 100644 --- a/zigbeedevelco/integrationpluginzigbeedevelco.cpp +++ b/zigbeedevelco/integrationpluginzigbeedevelco.cpp @@ -368,7 +368,13 @@ void IntegrationPluginZigbeeDevelco::setupThing(ThingSetupInfo *info) bool valueOk = false; quint16 value = measuredValueAttribute.dataType().toUInt16(&valueOk); if (valueOk) { - thing->setStateValue(airQualitySensorVocStateTypeId, value); + if (value == 0xFFFF) { + qCWarning(dcZigbeeDevelco()) << "Received invalid VOC measurment. The sensor is not ready yet."; + } else { + qCDebug(dcZigbeeDevelco()) << thing << "VOC changed" << value << "ppm"; + thing->setStateValue(airQualitySensorVocStateTypeId, value); + updateIndoorAirQuality(thing, value); + } } else { qCWarning(dcZigbeeDevelco()) << "Failed to convert VOC measurment value" << measuredValueAttribute; } @@ -379,8 +385,13 @@ void IntegrationPluginZigbeeDevelco::setupThing(ThingSetupInfo *info) bool valueOk = false; quint16 value = attribute.dataType().toUInt16(&valueOk); if (valueOk) { - qCDebug(dcZigbeeDevelco()) << thing << "VOC changed" << value << "ppm"; - thing->setStateValue(airQualitySensorVocStateTypeId, value); + if (value == 0xFFFF) { + qCWarning(dcZigbeeDevelco()) << "Received invalid VOC measurment. The sensor is not ready yet."; + } else { + qCDebug(dcZigbeeDevelco()) << thing << "VOC changed" << value << "ppm"; + thing->setStateValue(airQualitySensorVocStateTypeId, value); + updateIndoorAirQuality(thing, value); + } } else { qCWarning(dcZigbeeDevelco()) << "Failed to convert VOC measurment value" << attribute; } @@ -830,6 +841,21 @@ void IntegrationPluginZigbeeDevelco::configureVocReporting(ZigbeeNode *node, Zig }); } +void IntegrationPluginZigbeeDevelco::updateIndoorAirQuality(Thing *thing, uint voc) +{ + if (voc <= 65) { + thing->setStateValue(airQualitySensorIndoorAirQualityStateTypeId, "Excellent"); + } else if (voc <= 220) { + thing->setStateValue(airQualitySensorIndoorAirQualityStateTypeId, "Good"); + } else if (voc <= 660) { + thing->setStateValue(airQualitySensorIndoorAirQualityStateTypeId, "Moderate"); + } else if (voc <= 2200) { + thing->setStateValue(airQualitySensorIndoorAirQualityStateTypeId, "Poor"); + } else { + thing->setStateValue(airQualitySensorIndoorAirQualityStateTypeId, "Unhealthy"); + } +} + void IntegrationPluginZigbeeDevelco::readDevelcoFirmwareVersion(ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint) { // Read manufacturer specific basic cluster attribute 0x8000 diff --git a/zigbeedevelco/integrationpluginzigbeedevelco.h b/zigbeedevelco/integrationpluginzigbeedevelco.h index 53b78014..05b40aed 100644 --- a/zigbeedevelco/integrationpluginzigbeedevelco.h +++ b/zigbeedevelco/integrationpluginzigbeedevelco.h @@ -119,6 +119,7 @@ private: void configureHumidityReporting(ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint); void configureBattryVoltageReporting(ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint); void configureVocReporting(ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint); + void updateIndoorAirQuality(Thing *thing, uint voc); void readDevelcoFirmwareVersion(ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint); void readOnOffPowerAttribute(ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint); diff --git a/zigbeedevelco/integrationpluginzigbeedevelco.json b/zigbeedevelco/integrationpluginzigbeedevelco.json index 3cc2e74d..f9fa2893 100644 --- a/zigbeedevelco/integrationpluginzigbeedevelco.json +++ b/zigbeedevelco/integrationpluginzigbeedevelco.json @@ -153,7 +153,7 @@ "name": "airQualitySensor", "displayName": "Air qualiy sensor", "createMethods": ["auto"], - "interfaces": ["temperaturesensor", "humiditysensor", "battery", "wirelessconnectable"], + "interfaces": ["indoorairquality", "temperaturesensor", "humiditysensor", "battery", "wirelessconnectable"], "paramTypes": [ { "id": "dbad9e63-1adc-45ef-8bfc-4947060f19f4", @@ -228,6 +228,21 @@ "type": "bool", "defaultValue": false }, + { + "id": "8396ec7e-54d4-452f-9558-c366e0904b2f", + "name": "indoorAirQuality", + "displayName": "Air quality", + "displayNameEvent": "Air quality changed", + "type": "QString", + "defaultValue": "Good", + "possibleValues": [ + "Excellent", + "Good", + "Moderate", + "Poor", + "Unhealthy" + ] + }, { "id": "1ba14107-4241-46d0-b65f-eb6e8c6f8e08", "name": "voc", From be20a5499edbb59754785e6044f851b05e0f4b44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Mon, 28 Jun 2021 09:36:20 +0200 Subject: [PATCH 3/3] Update translations for the new develco device --- ...e976d-e842-40e1-9fad-87d71a69c721-en_US.ts | 212 ++++++++++++++---- 1 file changed, 163 insertions(+), 49 deletions(-) diff --git a/zigbeedevelco/translations/1b9e976d-e842-40e1-9fad-87d71a69c721-en_US.ts b/zigbeedevelco/translations/1b9e976d-e842-40e1-9fad-87d71a69c721-en_US.ts index e5e1e220..45ec6629 100644 --- a/zigbeedevelco/translations/1b9e976d-e842-40e1-9fad-87d71a69c721-en_US.ts +++ b/zigbeedevelco/translations/1b9e976d-e842-40e1-9fad-87d71a69c721-en_US.ts @@ -4,65 +4,128 @@ ZigbeeDevelco - - + + + Air quality + The name of the ParamType (ThingClass: airQualitySensor, EventType: indoorAirQuality, ID: {8396ec7e-54d4-452f-9558-c366e0904b2f}) +---------- +The name of the StateType ({8396ec7e-54d4-452f-9558-c366e0904b2f}) of ThingClass airQualitySensor + + + + + Air quality changed + The name of the EventType ({8396ec7e-54d4-452f-9558-c366e0904b2f}) of ThingClass airQualitySensor + + + + + Air qualiy sensor + The name of the ThingClass ({9f966405-4d63-4ae8-8472-a3ab7bbcadaa}) + + + + + + Battery critical + The name of the ParamType (ThingClass: airQualitySensor, EventType: batteryCritical, ID: {048ed452-785f-4361-b7a1-f955f4a622b2}) +---------- +The name of the StateType ({048ed452-785f-4361-b7a1-f955f4a622b2}) of ThingClass airQualitySensor + + + + + Battery critical changed + The name of the EventType ({048ed452-785f-4361-b7a1-f955f4a622b2}) of ThingClass airQualitySensor + + + + + + + Connected - The name of the ParamType (ThingClass: ioModule, EventType: connected, ID: {16d5ebb7-21d1-4294-98a6-ca06eea8e3a3}) + The name of the ParamType (ThingClass: airQualitySensor, EventType: connected, ID: {c6f65bb8-1234-40f2-8855-84ca28f48150}) +---------- +The name of the StateType ({c6f65bb8-1234-40f2-8855-84ca28f48150}) of ThingClass airQualitySensor +---------- +The name of the ParamType (ThingClass: ioModule, EventType: connected, ID: {16d5ebb7-21d1-4294-98a6-ca06eea8e3a3}) ---------- The name of the StateType ({16d5ebb7-21d1-4294-98a6-ca06eea8e3a3}) of ThingClass ioModule - + + Connected changed - The name of the EventType ({16d5ebb7-21d1-4294-98a6-ca06eea8e3a3}) of ThingClass ioModule + The name of the EventType ({c6f65bb8-1234-40f2-8855-84ca28f48150}) of ThingClass airQualitySensor +---------- +The name of the EventType ({16d5ebb7-21d1-4294-98a6-ca06eea8e3a3}) of ThingClass ioModule - + Develco The name of the vendor ({e4b36f3e-ccdc-4a88-8968-39025c3ec742}) - - IEEE adress - The name of the ParamType (ThingClass: ioModule, Type: thing, ID: {d9a3afa7-c460-43e7-bc84-c8b5ff1adf44}) + + + Humidity + The name of the ParamType (ThingClass: airQualitySensor, EventType: humidity, ID: {5bc2825c-666f-4013-9583-3a88ceebecc2}) +---------- +The name of the StateType ({5bc2825c-666f-4013-9583-3a88ceebecc2}) of ThingClass airQualitySensor - + + Humidity changed + The name of the EventType ({5bc2825c-666f-4013-9583-3a88ceebecc2}) of ThingClass airQualitySensor + + + + + + IEEE adress + The name of the ParamType (ThingClass: airQualitySensor, Type: thing, ID: {dbad9e63-1adc-45ef-8bfc-4947060f19f4}) +---------- +The name of the ParamType (ThingClass: ioModule, Type: thing, ID: {d9a3afa7-c460-43e7-bc84-c8b5ff1adf44}) + + + + IO Module The name of the ThingClass ({3fb419ef-795d-4f4d-b801-e7eaff16cdb0}) - + Identify The name of the ActionType ({62c5562a-8bdf-49b4-8e1d-f27442b2b360}) of ThingClass ioModule - + Impulse duration The name of the ParamType (ThingClass: ioModule, Type: settings, ID: {c2806b97-bf94-4ad2-ae22-5b5d7d1eaf5a}) - + Impulse output 1 The name of the ActionType ({45ec5c65-0719-4148-82ea-3e69b34be939}) of ThingClass ioModule - + Impulse output 2 The name of the ActionType ({d780946c-4ddf-4b59-a669-dbf6ecfda5d6}) of ThingClass ioModule - - + + Input 1 The name of the ParamType (ThingClass: ioModule, EventType: input1, ID: {bc23c7e8-f4ad-47c4-b938-f9af2dbd3393}) ---------- @@ -70,14 +133,14 @@ The name of the StateType ({bc23c7e8-f4ad-47c4-b938-f9af2dbd3393}) of ThingClass - + Input 1 changed The name of the EventType ({bc23c7e8-f4ad-47c4-b938-f9af2dbd3393}) of ThingClass ioModule - - + + Input 2 The name of the ParamType (ThingClass: ioModule, EventType: input2, ID: {065b0dc0-6d31-40ec-b356-02fea57e2fe1}) ---------- @@ -85,14 +148,14 @@ The name of the StateType ({065b0dc0-6d31-40ec-b356-02fea57e2fe1}) of ThingClass - + Input 2 changed The name of the EventType ({065b0dc0-6d31-40ec-b356-02fea57e2fe1}) of ThingClass ioModule - - + + Input 3 The name of the ParamType (ThingClass: ioModule, EventType: input3, ID: {8f2b052a-30b7-49aa-a8d4-503cb0b9b66b}) ---------- @@ -100,14 +163,14 @@ The name of the StateType ({8f2b052a-30b7-49aa-a8d4-503cb0b9b66b}) of ThingClass - + Input 3 changed The name of the EventType ({8f2b052a-30b7-49aa-a8d4-503cb0b9b66b}) of ThingClass ioModule - - + + Input 4 The name of the ParamType (ThingClass: ioModule, EventType: input4, ID: {caef986c-da13-4ef3-ab81-316244c7be1e}) ---------- @@ -115,15 +178,15 @@ The name of the StateType ({caef986c-da13-4ef3-ab81-316244c7be1e}) of ThingClass - + Input 4 changed The name of the EventType ({caef986c-da13-4ef3-ab81-316244c7be1e}) of ThingClass ioModule - - - + + + Output 1 The name of the ParamType (ThingClass: ioModule, ActionType: output1, ID: {aaeda2c6-439a-452a-b829-45d6249bdee6}) ---------- @@ -133,15 +196,15 @@ The name of the StateType ({aaeda2c6-439a-452a-b829-45d6249bdee6}) of ThingClass - + Output 1 changed The name of the EventType ({aaeda2c6-439a-452a-b829-45d6249bdee6}) of ThingClass ioModule - - - + + + Output 2 The name of the ParamType (ThingClass: ioModule, ActionType: output2, ID: {105cf314-35b5-4a8b-8e6d-d011009f97ff}) ---------- @@ -151,63 +214,114 @@ The name of the StateType ({105cf314-35b5-4a8b-8e6d-d011009f97ff}) of ThingClass - + Output 2 changed The name of the EventType ({105cf314-35b5-4a8b-8e6d-d011009f97ff}) of ThingClass ioModule - + Set output 1 The name of the ActionType ({aaeda2c6-439a-452a-b829-45d6249bdee6}) of ThingClass ioModule - + Set output 2 The name of the ActionType ({105cf314-35b5-4a8b-8e6d-d011009f97ff}) of ThingClass ioModule - - + + + + Signal strength - The name of the ParamType (ThingClass: ioModule, EventType: signalStrength, ID: {0e09e6a6-8b3f-4b63-acb1-ac04ab31957a}) + The name of the ParamType (ThingClass: airQualitySensor, EventType: signalStrength, ID: {19cfb35b-6a2e-4efe-9366-287c2f13c027}) +---------- +The name of the StateType ({19cfb35b-6a2e-4efe-9366-287c2f13c027}) of ThingClass airQualitySensor +---------- +The name of the ParamType (ThingClass: ioModule, EventType: signalStrength, ID: {0e09e6a6-8b3f-4b63-acb1-ac04ab31957a}) ---------- The name of the StateType ({0e09e6a6-8b3f-4b63-acb1-ac04ab31957a}) of ThingClass ioModule - + + Signal strength changed - The name of the EventType ({0e09e6a6-8b3f-4b63-acb1-ac04ab31957a}) of ThingClass ioModule + The name of the EventType ({19cfb35b-6a2e-4efe-9366-287c2f13c027}) of ThingClass airQualitySensor +---------- +The name of the EventType ({0e09e6a6-8b3f-4b63-acb1-ac04ab31957a}) of ThingClass ioModule - - + + + Temperature + The name of the ParamType (ThingClass: airQualitySensor, EventType: temperature, ID: {2d31d98e-dcd7-4700-a90e-a0d051ad6b40}) +---------- +The name of the StateType ({2d31d98e-dcd7-4700-a90e-a0d051ad6b40}) of ThingClass airQualitySensor + + + + + Temperature changed + The name of the EventType ({2d31d98e-dcd7-4700-a90e-a0d051ad6b40}) of ThingClass airQualitySensor + + + + + + VOC + The name of the ParamType (ThingClass: airQualitySensor, EventType: voc, ID: {1ba14107-4241-46d0-b65f-eb6e8c6f8e08}) +---------- +The name of the StateType ({1ba14107-4241-46d0-b65f-eb6e8c6f8e08}) of ThingClass airQualitySensor + + + + + VOC changed + The name of the EventType ({1ba14107-4241-46d0-b65f-eb6e8c6f8e08}) of ThingClass airQualitySensor + + + + + + + Version - The name of the ParamType (ThingClass: ioModule, EventType: version, ID: {f28c9a61-b8ab-419c-bd85-58692df26ac7}) + The name of the ParamType (ThingClass: airQualitySensor, EventType: version, ID: {5d1c46ca-9306-473a-a721-165831ff53cc}) +---------- +The name of the StateType ({5d1c46ca-9306-473a-a721-165831ff53cc}) of ThingClass airQualitySensor +---------- +The name of the ParamType (ThingClass: ioModule, EventType: version, ID: {f28c9a61-b8ab-419c-bd85-58692df26ac7}) ---------- The name of the StateType ({f28c9a61-b8ab-419c-bd85-58692df26ac7}) of ThingClass ioModule - + + Version changed - The name of the EventType ({f28c9a61-b8ab-419c-bd85-58692df26ac7}) of ThingClass ioModule + The name of the EventType ({5d1c46ca-9306-473a-a721-165831ff53cc}) of ThingClass airQualitySensor +---------- +The name of the EventType ({f28c9a61-b8ab-419c-bd85-58692df26ac7}) of ThingClass ioModule - + Zigbee Develco The name of the plugin ZigbeeDevelco ({1b9e976d-e842-40e1-9fad-87d71a69c721}) - + + Zigbee network UUID - The name of the ParamType (ThingClass: ioModule, Type: thing, ID: {f4c83d2b-06e1-4206-98b7-e3ace79ff447}) + The name of the ParamType (ThingClass: airQualitySensor, Type: thing, ID: {7c042425-f95d-479b-a6af-cee7e8ce9c38}) +---------- +The name of the ParamType (ThingClass: ioModule, Type: thing, ID: {f4c83d2b-06e1-4206-98b7-e3ace79ff447})