Skip to content

Commit

Permalink
Merge remote-tracking branch 'swoopx/xiaomitvoc'
Browse files Browse the repository at this point in the history
  • Loading branch information
manup committed Nov 10, 2021
2 parents a2c5cd6 + 2cddf96 commit 2a0a914
Show file tree
Hide file tree
Showing 4 changed files with 74 additions and 4 deletions.
2 changes: 2 additions & 0 deletions bindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1889,6 +1889,7 @@ bool DeRestPluginPrivate::sendConfigureReportingRequest(BindingTask &bt)
modelId == QLatin1String("TS0202") || // Tuya sensor
modelId == QLatin1String("3AFE14010402000D") || // Konke presence sensor
modelId == QLatin1String("3AFE28010402000D") || // Konke presence sensor
modelId == QLatin1String("lumi.airmonitor.acn01") || // Xiaomi Aqara TVOC Air Quality Monitor
modelId.startsWith(QLatin1String("GZ-PIR02")) || // Sercomm motion sensor
modelId.startsWith(QLatin1String("SZ-WTD02N_CAR")) || // Sercomm water sensor
modelId.startsWith(QLatin1String("3300")) || // Centralite contatc sensor
Expand Down Expand Up @@ -3078,6 +3079,7 @@ bool DeRestPluginPrivate::checkSensorBindingsForAttributeReporting(Sensor *senso
sensor->modelId().startsWith(QLatin1String("lumi.plug.maeu01")) ||
sensor->modelId().startsWith(QLatin1String("lumi.sen_ill.mgl01")) ||
sensor->modelId().startsWith(QLatin1String("lumi.switch.b1naus01")) ||
sensor->modelId() == QLatin1String("lumi.airmonitor.acn01") ||
sensor->modelId() == QLatin1String("lumi.sensor_magnet.agl02") ||
sensor->modelId() == QLatin1String("lumi.motion.agl04") ||
sensor->modelId() == QLatin1String("lumi.flood.agl02") ||
Expand Down
45 changes: 41 additions & 4 deletions de_web_plugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,7 @@ static const SupportedDevice supportedDevices[] = {
{ VENDOR_XIAOMI, "lumi.remote.b686opcn01", xiaomiMacPrefix }, // Xiaomi Aqara Opple WXCJKG13LM
{ VENDOR_XIAOMI, "lumi.sen_ill.mgl01", xiaomiMacPrefix }, // Xiaomi ZB3.0 light sensor
{ VENDOR_XIAOMI2, "lumi.sen_ill.mgl01", lumiMacPrefix }, // Mi light detection sensor GZCGQ01LM
{ VENDOR_XIAOMI, "lumi.airmonitor.acn01", lumiMacPrefix}, // Xiaomi Aqara TVOC Air Quality Monitor VOCKQJK11LM
{ VENDOR_XIAOMI, "lumi.plug", xiaomiMacPrefix }, // Xiaomi smart plugs (router)
{ VENDOR_XIAOMI, "lumi.switch.b1naus01", xiaomiMacPrefix }, // Xiaomi Aqara ZB3.0 Smart Wall Switch Single Rocker WS-USC03
// { VENDOR_XIAOMI, "lumi.curtain", jennicMacPrefix}, // Xiaomi curtain controller (router) - exposed only as light
Expand Down Expand Up @@ -5749,6 +5750,7 @@ void DeRestPluginPrivate::addSensorNode(const deCONZ::Node *node, const deCONZ::
fpWaterSensor.inClusters.push_back(ci->id());
fpDoorLockSensor.inClusters.push_back(ci->id());
fpAncillaryControlSensor.inClusters.push_back(ci->id());
fpAirQualitySensor.inClusters.push_back(ci->id());
}
break;

Expand Down Expand Up @@ -6133,6 +6135,10 @@ void DeRestPluginPrivate::addSensorNode(const deCONZ::Node *node, const deCONZ::
{
fpConsumptionSensor.inClusters.push_back(ci->id());
}
else if (modelId == QLatin1String("lumi.airmonitor.acn01"))
{
fpAirQualitySensor.inClusters.push_back(ci->id());
}
}
break;

Expand Down Expand Up @@ -6983,8 +6989,9 @@ void DeRestPluginPrivate::addSensorNode(const deCONZ::Node *node, const deCONZ::
}

// ZHAAirQuality
if (fpAirQualitySensor.hasInCluster(DEVELCO_AIR_QUALITY_CLUSTER_ID) // Develco specific -> VOC Management
|| fpAirQualitySensor.hasInCluster(BOSCH_AIR_QUALITY_CLUSTER_ID)) // Bosch Air quality sensor
if (fpAirQualitySensor.hasInCluster(DEVELCO_AIR_QUALITY_CLUSTER_ID) || // Develco specific -> VOC Management
fpAirQualitySensor.hasInCluster(ANALOG_INPUT_CLUSTER_ID) || // Xiaomi Aqara TVOC Air Quality Monitor
fpAirQualitySensor.hasInCluster(BOSCH_AIR_QUALITY_CLUSTER_ID)) // Bosch Air quality sensor
{
fpAirQualitySensor.endpoint = i->endpoint();
fpAirQualitySensor.deviceId = i->deviceId();
Expand Down Expand Up @@ -7710,9 +7717,14 @@ void DeRestPluginPrivate::addSensorNode(const deCONZ::Node *node, const SensorFi
item = sensorNode.addItem(DataTypeUInt16, RConfigPending);
item->setValue(item->toNumber() | R_PENDING_WRITE_POLL_CHECKIN_INTERVAL | R_PENDING_SET_LONG_POLL_INTERVAL);
}
else if (sensorNode.fingerPrint().hasInCluster(ANALOG_INPUT_CLUSTER_ID))
{
clusterId = ANALOG_INPUT_CLUSTER_ID;
}

if (modelId == QLatin1String("AQSZB-110") // Develco air quality sensor
|| (node->nodeDescriptor().manufacturerCode() == VENDOR_BOSCH2 && modelId == QLatin1String("AIR"))) // Bosch air quality sensor
if (modelId == QLatin1String("AQSZB-110") || // Develco air quality sensor
modelId == QLatin1String("lumi.airmonitor.acn01") || // Xiaomi Aqara TVOC Air Quality Monitor
(node->nodeDescriptor().manufacturerCode() == VENDOR_BOSCH2 && modelId == QLatin1String("AIR"))) // Bosch air quality sensor
{
item = sensorNode.addItem(DataTypeString, RStateAirQuality);
item = sensorNode.addItem(DataTypeUInt16, RStateAirQualityPpb);
Expand Down Expand Up @@ -9487,6 +9499,31 @@ void DeRestPluginPrivate::updateSensorNode(const deCONZ::NodeEvent &event)
updateSensorEtag(&*i);
}
}
else if (i->modelId() == QLatin1String("lumi.airmonitor.acn01"))
{
quint32 levelPpb = static_cast<quint32>(ia->numericValue().real);
ResourceItem *item = i->item(RStateAirQualityPpb);

if (item && item->toNumber() != levelPpb)
{
item->setValue(levelPpb);
QString airQuality = getAirQualityString(levelPpb);
ResourceItem *item = i->item(RStateAirQuality);

if (item && item->toString() != airQuality)
{
item->setValue(airQuality);
enqueueEvent(Event(RSensors, RStateAirQuality, i->id(), item));
}

i->updateStateTimestamp();
i->setNeedSaveDatabase(true);
enqueueEvent(Event(RSensors, RStateAirQualityPpb, i->id(), item));
enqueueEvent(Event(RSensors, RStateLastUpdated, i->id()));
}

updateSensorEtag(&*i);
}
}
}
}
Expand Down
29 changes: 29 additions & 0 deletions utils/utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -342,3 +342,32 @@ bool copyString(char *dst, size_t dstSize, const char *src, ssize_t srcSize)

return true;
}

QString getAirQualityString(quint32 levelPpb)
{
QString airquality;

if (levelPpb <= 65) { airquality = QLatin1String("excellent"); }
if (levelPpb > 65 && levelPpb <= 220) { airquality = QLatin1String("good"); }
if (levelPpb > 220 && levelPpb <= 660) { airquality = QLatin1String("moderate"); }
if (levelPpb > 660 && levelPpb <= 2200) { airquality = QLatin1String("poor"); }
if (levelPpb > 2200 && levelPpb <= 5500) { airquality = QLatin1String("unhealthy"); }
if (levelPpb > 5500 ) { airquality = QLatin1String("out of scale"); }

return airquality;
}

quint8 calculateBatteryPercentageRemaining(const quint8 batteryVoltage, const float vmin, const float vmax)
{
float batteryPercentage = batteryVoltage;

if (batteryPercentage > vmax) { batteryPercentage = vmax; }
else if (batteryPercentage < vmin) { batteryPercentage = vmin; }

batteryPercentage = ((batteryPercentage - vmin) / (vmax - vmin)) * 100;

if (batteryPercentage > 100) { batteryPercentage = 100; }
else if (batteryPercentage <= 0) { batteryPercentage = 1; } // ?

return static_cast<quint8>(batteryPercentage);
}
2 changes: 2 additions & 0 deletions utils/utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ int indexOf(QLatin1String haystack, QLatin1String needle);
bool contains(QLatin1String haystack, QLatin1String needle);
RestData verifyRestData(const ResourceItemDescriptor &rid, const QVariant &val);
bool isSameAddress(const deCONZ::Address &a, const deCONZ::Address &b);
QString getAirQualityString(quint32 levelPpb);
quint8 calculateBatteryPercentageRemaining(const quint8 batteryVoltage, const float vmin, const float vmax);

inline bool isValid(const KeyMap &entry) { return entry.key.size() != 0; }
inline bool isValid(const KeyValMap &entry) { return entry.key.size() != 0; }
Expand Down

0 comments on commit 2a0a914

Please sign in to comment.