#include "device_descriptions.h"
#include "de_web_plugin.h"
#include "de_web_plugin_private.h"
#include "utils/utils.h"
#define POWER_CONFIG_ATTRID_BATTERY_VOLTAGE 0x0020
#define POWER_CONFIG_ATTRID_BATTERY_PERCENTAGE_REMAINING 0x0021
#define POWER_CONFIG_ATTRID_BATTERY_ALARM_MASK 0x0035
static quint8 calculateBatteryPercentageRemaining(const Resource *r, ResourceItem *item, 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; }
if (r && item)
{
const int maxSize = 384;
auto jsEval = std::make_unique<char[]>(maxSize);
int ret = snprintf(jsEval.get(), maxSize,
"const vmin = %u;"
" const vmax = %u;"
" let bat = Attr.val;"
" if (bat > vmax) { bat = vmax; }"
" else if (bat < vmin) { bat = vmin; }"
" bat = ((bat - vmin) / (vmax - vmin)) * 100;"
" if (bat > 100) { bat = 100; }"
" else if (bat <= 0) { bat = 1; }"
" Item.val = bat;", unsigned(vmin), unsigned(vmax));
DBG_Assert(ret < maxSize);
if (ret > 0 && ret < maxSize && jsEval[ret] == '\0')
{
DDF_AnnoteZclParse(r, item, 255, POWER_CONFIGURATION_CLUSTER_ID, POWER_CONFIG_ATTRID_BATTERY_VOLTAGE, jsEval.get());
}
}
return static_cast<quint8>(batteryPercentage);
}
\param ind the APS level data indication containing the ZCL packet
\param zclFrame the actual ZCL frame which holds the power configuration cluster command or attribute
*/
void DeRestPluginPrivate::handlePowerConfigurationClusterIndication(const deCONZ::ApsDataIndication &ind, const deCONZ::ZclFrame &zclFrame)
{
if (zclFrame.isDefaultResponse())
{
return;
}
QDataStream stream(zclFrame.payload());
stream.setByteOrder(QDataStream::LittleEndian);
bool isReadAttr = false;
if (zclFrame.isProfileWideCommand() && zclFrame.commandId() == deCONZ::ZclReadAttributesResponseId)
{
isReadAttr = true;
}
else if (zclFrame.isProfileWideCommand() && zclFrame.commandId() == deCONZ::ZclReportAttributesId)
{
}
else
{
return;
}
const NodeValue::UpdateType updateType = isReadAttr ? NodeValue::UpdateByZclRead : NodeValue::UpdateByZclReport;
while (!stream.atEnd())
{
quint16 attrId;
quint8 attrTypeId;
stream >> attrId;
if (isReadAttr)
{
quint8 status;
stream >> status;
if (status != deCONZ::ZclSuccessStatus)
{
continue;
}
}
stream >> attrTypeId;
deCONZ::ZclAttribute attr(attrId, attrTypeId, QLatin1String(""), deCONZ::ZclRead, false);
if (!attr.readFromStream(stream))
{
continue;
}
for (Sensor &sensor: sensors)
{
if (sensor.deletedState() != Sensor::StateNormal || !sensor.node()) { continue; }
if (!isSameAddress(sensor.address(), ind.srcAddress())) { continue; }
if (sensor.mustRead(READ_BATTERY))
{
sensor.clearRead(READ_BATTERY);
}
switch (attrId)
{
case POWER_CONFIG_ATTRID_BATTERY_PERCENTAGE_REMAINING:
{
uint divider = 2;
if (sensor.modelId().startsWith(QLatin1String("TRADFRI")) ||
sensor.modelId().startsWith(QLatin1String("SYMFONISK")) ||
sensor.modelId().startsWith(QLatin1String("ICZB-")) ||
sensor.modelId().startsWith(QLatin1String("ZGR904-S")) ||
sensor.modelId().startsWith(QLatin1String("ZGRC-KEY")) ||
sensor.modelId().startsWith(QLatin1String("SV01-")) ||
sensor.modelId().startsWith(QLatin1String("SV02-")) ||
sensor.modelId() == QLatin1String("4512705") ||
sensor.modelId() == QLatin1String("4512726") ||
sensor.modelId().startsWith(QLatin1String("S57003")) ||
sensor.modelId().startsWith(QLatin1String("RGBgenie ZB-5")) ||
sensor.modelId().startsWith(QLatin1String("VOC_Sensor")) ||
sensor.modelId().startsWith(QLatin1String("TY0203")) ||
sensor.modelId().startsWith(QLatin1String("TY0202")) ||
sensor.modelId().startsWith(QLatin1String("ZG2835")))
{
divider = 1;
}
int bat = attr.numericValue().u8 / divider;
if (sensor.modelId() == QLatin1String("0x8020") ||
sensor.modelId() == QLatin1String("0x8021") ||
sensor.modelId() == QLatin1String("0x8030") ||
sensor.modelId() == QLatin1String("0x8031") ||
sensor.modelId() == QLatin1String("0x8034") ||
sensor.modelId() == QLatin1String("0x8035"))
{
if (ind.srcEndpoint() != sensor.fingerPrint().endpoint)
{
continue;
}
}
ResourceItem *item = nullptr;
if (sensor.type().endsWith(QLatin1String("Battery")))
{
item = sensor.item(RStateBattery);
if (item)
{
item->setValue(bat);
sensor.updateStateTimestamp();
sensor.setNeedSaveDatabase(true);
queSaveDb(DB_SENSORS, DB_HUGE_SAVE_DELAY);
enqueueEvent(Event(RSensors, RStateBattery, sensor.id(), item));
enqueueEvent(Event(RSensors, RStateLastUpdated, sensor.id()));
updateSensorEtag(&sensor);
}
}
else
{
item = sensor.item(RConfigBattery);
if (!item && attr.numericValue().u8 > 0)
{
item = sensor.addItem(DataTypeUInt8, RConfigBattery);
}
if (item)
{
item->setValue(bat);
enqueueEvent(Event(RSensors, RConfigBattery, sensor.id(), item));
updateSensorEtag(&sensor);
sensor.setNeedSaveDatabase(true);
queSaveDb(DB_SENSORS, DB_SHORT_SAVE_DELAY);
}
}
if (item)
{
if (divider == 1)
{
DDF_AnnoteZclParse(&sensor, item, ind.srcEndpoint(), ind.clusterId(), attrId, "Item.val = Attr.val");
}
else if (divider == 2)
{
DDF_AnnoteZclParse(&sensor, item, ind.srcEndpoint(), ind.clusterId(), attrId, "Item.val = Attr.val / 2");
}
}
if (!sensor.fingerPrint().hasInCluster(POWER_CONFIGURATION_CLUSTER_ID))
{
sensor.fingerPrint().inClusters.push_back(POWER_CONFIGURATION_CLUSTER_ID);
}
sensor.setZclValue(updateType, ind.srcEndpoint(), POWER_CONFIGURATION_CLUSTER_ID, POWER_CONFIG_ATTRID_BATTERY_PERCENTAGE_REMAINING, attr.numericValue());
}
break;
case POWER_CONFIG_ATTRID_BATTERY_VOLTAGE:
{
if (sensor.modelId().startsWith(QLatin1String("tagv4")) ||
sensor.modelId().startsWith(QLatin1String("motionv4")) ||
sensor.modelId().startsWith(QLatin1String("moisturev4")) ||
sensor.modelId().startsWith(QLatin1String("multiv4")) ||
sensor.modelId().startsWith(QLatin1String("3305-S")) ||
sensor.modelId() == QLatin1String("Remote switch") ||
sensor.modelId() == QLatin1String("Pocket remote") ||
sensor.modelId() == QLatin1String("Double gangs remote switch") ||
sensor.modelId() == QLatin1String("Shutters central remote switch") ||
sensor.modelId() == QLatin1String("Remote toggle switch") ||
sensor.modelId() == QLatin1String("Remote motion sensor") ||
sensor.modelId() == QLatin1String("lumi.flood.agl02") ||
sensor.modelId() == QLatin1String("lumi.motion.agl04") ||
sensor.modelId() == QLatin1String("Zen-01") ||
sensor.modelId() == QLatin1String("Thermostat") ||
sensor.modelId() == QLatin1String("Bell") ||
sensor.modelId() == QLatin1String("ISW-ZPR1-WP13") ||
sensor.modelId() == QLatin1String("3AFE14010402000D") ||
sensor.modelId() == QLatin1String("3AFE28010402000D") ||
sensor.modelId() == QLatin1String("FB56-DOS06HM1.3") ||
sensor.modelId().endsWith(QLatin1String("86opcn01")) ||
sensor.modelId().startsWith(QLatin1String("FLSZB-1")) ||
sensor.modelId().startsWith(QLatin1String("SIRZB-1")) ||
sensor.modelId().startsWith(QLatin1String("ZHMS101")) ||
sensor.modelId().startsWith(QLatin1String("MotionSensor51AU")) ||
sensor.modelId().startsWith(QLatin1String("RFDL-ZB-MS")) ||
sensor.modelId().startsWith(QLatin1String("1116-S")) ||
sensor.modelId().startsWith(QLatin1String("1117-S")) ||
sensor.modelId().startsWith(QLatin1String("3326-L")) ||
sensor.modelId().startsWith(QLatin1String("3300")) ||
sensor.modelId().startsWith(QLatin1String("3320-L")) ||
sensor.modelId().startsWith(QLatin1String("3323")) ||
sensor.modelId().startsWith(QLatin1String("3315")) ||
sensor.modelId().startsWith(QLatin1String("3157100")) ||
sensor.modelId().startsWith(QLatin1String("4655BC0")) ||
sensor.modelId().startsWith(QLatin1String("SZ-DWS04")) ||
sensor.modelId().startsWith(QLatin1String("SZ-WTD02N_CAR")) ||
sensor.modelId().startsWith(QLatin1String("GZ-PIR02")) ||
sensor.modelId() == QLatin1String("URC4450BC0-X-R") ||
sensor.modelId() == QLatin1String("3405-L") ||
sensor.modelId().startsWith(QLatin1String("Tripper")) ||
sensor.modelId().startsWith(QLatin1String("Lightify Switch Mini")) ||
sensor.modelId().startsWith(QLatin1String("Switch 4x EU-LIGHTIFY")) ||
sensor.modelId().startsWith(QLatin1String("Switch 4x-LIGHTIFY")) ||
sensor.modelId().startsWith(QLatin1String("Switch-LIGHTIFY")) )
{ }
else
{
continue;
}
ResourceItem *item = sensor.item(RConfigBattery);
if (!item && attr.numericValue().u8 > 0)
{
sensor.addItem(DataTypeUInt8, RConfigBattery);
}
if (!sensor.fingerPrint().hasInCluster(POWER_CONFIGURATION_CLUSTER_ID))
{
sensor.fingerPrint().inClusters.push_back(POWER_CONFIGURATION_CLUSTER_ID);
}
quint8 battery = attr.numericValue().u8;
float vmin = 20;
float vmax = 30;
if (sensor.modelId() == QLatin1String("Zen-01") ||
sensor.modelId() == QLatin1String("URC4450BC0-X-R"))
{
vmin = 36;
vmax = 60;
}
battery = calculateBatteryPercentageRemaining(&sensor, item, battery, vmin, vmax);
if (item)
{
item->setValue(battery);
enqueueEvent(Event(RSensors, RConfigBattery, sensor.id(), item));
updateSensorEtag(&sensor);
sensor.setNeedSaveDatabase(true);
queSaveDb(DB_SENSORS, DB_SHORT_SAVE_DELAY);
}
sensor.setZclValue(updateType, ind.srcEndpoint(), POWER_CONFIGURATION_CLUSTER_ID, POWER_CONFIG_ATTRID_BATTERY_VOLTAGE, attr.numericValue());
}
break;
case POWER_CONFIG_ATTRID_BATTERY_ALARM_MASK:
{
ResourceItem *item = sensor.item(RStateLowBattery);
if (!item)
{
item = sensor.addItem(DataTypeBool, RStateLowBattery);
}
bool lowBat = (attr.numericValue().u8 & 0x01);
sensor.setZclValue(updateType, ind.srcEndpoint(), POWER_CONFIGURATION_CLUSTER_ID, POWER_CONFIG_ATTRID_BATTERY_ALARM_MASK, attr.numericValue());
if (item)
{
item->setValue(lowBat);
enqueueEvent(Event(RSensors, RConfigBattery, sensor.id(), item));
updateSensorEtag(&sensor);
sensor.setNeedSaveDatabase(true);
queSaveDb(DB_SENSORS, DB_SHORT_SAVE_DELAY);
DDF_AnnoteZclParse(&sensor, item, ind.srcEndpoint(), ind.clusterId(), attrId, "Item.val = (Attr.val & 1) != 0");
}
}
break;
default:
break;
}
}
}
}