#include <math.h>
#include "de_web_plugin.h"
#include "de_web_plugin_private.h"
#include "device_descriptions.h"
#include "simple_metering.h"
const std::array<KeyValMapInt, 5> RConfigInterfaceModeValuesEMIZB = { { {1, NORWEGIAN_HAN}, {2, NORWEGIAN_HAN_EXTRA_LOAD}, {3, AIDON_METER},
{4, KAIFA_KAMSTRUP_METERS}, {5, AUTO_DETECT} } };
\param ind the APS level data indication containing the ZCL packet
\param zclFrame the actual ZCL frame which holds the simple metering cluster command or attribute
*/
void DeRestPluginPrivate::handleSimpleMeteringClusterIndication(const deCONZ::ApsDataIndication &ind, const deCONZ::ZclFrame &zclFrame)
{
if (zclFrame.isDefaultResponse())
{
return;
}
Sensor *sensor = getSensorNodeForAddressAndEndpoint(ind.srcAddress(), ind.srcEndpoint(), QLatin1String("ZHAConsumption"));
if (!sensor)
{
DBG_Printf(DBG_INFO, "No consumption sensor found for 0x%016llX, endpoint: 0x%02X\n", ind.srcAddress().ext(), ind.srcEndpoint());
return;
}
QDataStream stream(zclFrame.payload());
stream.setByteOrder(QDataStream::LittleEndian);
bool isReadAttr = false;
bool isReporting = false;
if (zclFrame.isProfileWideCommand() && zclFrame.commandId() == deCONZ::ZclReadAttributesResponseId)
{
isReadAttr = true;
}
if (zclFrame.isProfileWideCommand() && zclFrame.commandId() == deCONZ::ZclReportAttributesId)
{
isReporting = true;
}
if (isReadAttr || isReporting)
{
const auto modelId = sensor->modelId();
const NodeValue::UpdateType updateType = isReadAttr ? NodeValue::UpdateByZclRead : NodeValue::UpdateByZclReport;
bool configUpdated = false;
bool stateUpdated = false;
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;
}
ResourceItem *item = nullptr;
switch (attrId)
{
case METERING_ATTRID_CURRENT_SUMMATION_DELIVERED:
{
quint64 consumption = attr.numericValue().u64;
item = sensor->item(RStateConsumption);
if (modelId == QLatin1String("SmartPlug") ||
modelId.startsWith(QLatin1String("PSMP5_")) ||
modelId.startsWith(QLatin1String("SKHMP30")) ||
modelId.startsWith(QLatin1String("E13-")) ||
modelId.startsWith(QLatin1String("Z01-A19")))
{
consumption = static_cast<quint64>(round((double)consumption / 10.0));
DDF_AnnoteZclParse(sensor, item, ind.srcEndpoint(), ind.clusterId(), attrId, "Item.val = Math.round(Attr.val / 10)");
}
else if (modelId == QLatin1String("Smart plug Zigbee PE") ||
modelId == QLatin1String("TS011F") ||
modelId == QLatin1String("TS0121"))
{
consumption *= 10;
DDF_AnnoteZclParse(sensor, item, ind.srcEndpoint(), ind.clusterId(), attrId, "Item.val = Attr.val * 10");
}
else if (modelId.startsWith(QLatin1String("SZ-ESW01")))
{
consumption = static_cast<quint64>(round((double)consumption / 1000.0));
DDF_AnnoteZclParse(sensor, item, ind.srcEndpoint(), ind.clusterId(), attrId, "Item.val = Math.round(Attr.val / 1000)");
}
else if (modelId.startsWith(QLatin1String("ROB_200")) ||
modelId.startsWith(QLatin1String("Micro Smart Dimmer")) ||
modelId.startsWith(QLatin1String("SPW35Z")))
{
consumption = static_cast<quint64>(round((double)consumption / 3600.0));
DDF_AnnoteZclParse(sensor, item, ind.srcEndpoint(), ind.clusterId(), attrId, "Item.val = Math.round(Attr.val / 3600)");
}
else
{
DDF_AnnoteZclParse(sensor, item, ind.srcEndpoint(), ind.clusterId(), attrId, "Item.val = Attr.val");
}
if (item && item->toNumber() != static_cast<qint64>(consumption))
{
item->setValue(consumption);
enqueueEvent(Event(RSensors, RStateConsumption, sensor->id(), item));
}
sensor->setZclValue(updateType, ind.srcEndpoint(), METERING_CLUSTER_ID, attrId, attr.numericValue());
stateUpdated = true;
}
break;
case METERING_ATTRID_INTERFACE_MODE:
{
if (zclFrame.manufacturerCode() == VENDOR_DEVELCO)
{
const quint16 interfaceMode = attr.numericValue().u16;
item = sensor->item(RConfigInterfaceMode);
quint8 mode = 0;
if (modelId.startsWith(QLatin1String("EMIZB-1")))
{
if (interfaceMode == NORWEGIAN_HAN) { mode = 1; }
else if (interfaceMode == NORWEGIAN_HAN_EXTRA_LOAD) { mode = 2; }
else if (interfaceMode == AIDON_METER) { mode = 3; }
else if (interfaceMode == KAIFA_KAMSTRUP_METERS) { mode = 4; }
else if (interfaceMode == AUTO_DETECT) { mode = 5; }
}
DDF_AnnoteZclParse(sensor, item, ind.srcEndpoint(), ind.clusterId(), attrId, "console.log('todo script')");
if (item && mode != 0 && item->toNumber() != mode)
{
item->setValue(mode);
enqueueEvent(Event(RSensors, RConfigInterfaceMode, sensor->id(), item));
configUpdated = true;
}
}
sensor->setZclValue(updateType, ind.srcEndpoint(), METERING_CLUSTER_ID, attrId, attr.numericValue());
}
break;
case METERING_ATTRID_INSTANTANEOUS_DEMAND:
{
qint32 power = attr.numericValue().s32;
item = sensor->item(RStatePower);
if (modelId == QLatin1String("SmartPlug") ||
modelId == QLatin1String("902010/25") ||
modelId.startsWith(QLatin1String("Z01-A19")) ||
modelId.startsWith(QLatin1String("PSMP5_")) ||
modelId.startsWith(QLatin1String("SKHMP30")) ||
modelId.startsWith(QLatin1String("160-01")))
{
power = static_cast<qint32>(round((double)power / 10.0));
DDF_AnnoteZclParse(sensor, item, ind.srcEndpoint(), ind.clusterId(), attrId, "Item.val = Math.round(Attr.val / 10)");
}
else if (modelId.startsWith(QLatin1String("SZ-ESW01")))
{
power = static_cast<qint32>(round((double)power / 1000.0));
DDF_AnnoteZclParse(sensor, item, ind.srcEndpoint(), ind.clusterId(), attrId, "Item.val = Math.round(Attr.val / 1000)");
}
if (item && item->toNumber() != power)
{
item->setValue(power);
enqueueEvent(Event(RSensors, RStatePower, sensor->id(), item));
}
sensor->setZclValue(updateType, ind.srcEndpoint(), METERING_CLUSTER_ID, attrId, attr.numericValue());
stateUpdated = true;
}
break;
default:
break;
}
}
if (stateUpdated)
{
sensor->updateStateTimestamp();
enqueueEvent(Event(RSensors, RStateLastUpdated, sensor->id()));
}
if (configUpdated || stateUpdated)
{
updateSensorEtag(&*sensor);
sensor->setNeedSaveDatabase(true);
queSaveDb(DB_SENSORS, DB_SHORT_SAVE_DELAY);
}
}
}
* Iterate over every day and get schedule for each day.
\param task - the task item
\param attrId
\param attrType
\param attrValue
\return true - on success
false - on error
*/
bool DeRestPluginPrivate::addTaskSimpleMeteringReadWriteAttribute(TaskItem &task, uint8_t readOrWriteCmd, uint16_t attrId, uint8_t attrType, uint32_t attrValue, uint16_t mfrCode)
{
if (readOrWriteCmd != deCONZ::ZclReadAttributesId && readOrWriteCmd != deCONZ::ZclWriteAttributesId)
{
DBG_Printf(DBG_INFO, "Invalid command for simple metering cluster %d\n", readOrWriteCmd);
return false;
}
task.taskType = TaskSimpleMetering;
task.req.setClusterId(METERING_CLUSTER_ID);
task.req.setProfileId(HA_PROFILE_ID);
task.zclFrame.payload().clear();
task.zclFrame.setSequenceNumber(zclSeq++);
task.zclFrame.setCommandId(readOrWriteCmd);
task.zclFrame.setFrameControl(deCONZ::ZclFCProfileCommand |
deCONZ::ZclFCDirectionClientToServer |
deCONZ::ZclFCDisableDefaultResponse);
if (mfrCode != 0x0000)
{
task.zclFrame.setFrameControl(task.zclFrame.frameControl() | deCONZ::ZclFCManufacturerSpecific);
task.zclFrame.setManufacturerCode(mfrCode);
}
QDataStream stream(&task.zclFrame.payload(), QIODevice::WriteOnly);
stream.setByteOrder(QDataStream::LittleEndian);
if (readOrWriteCmd == deCONZ::ZclWriteAttributesId)
{
stream << attrId;
stream << attrType;
deCONZ::ZclAttribute attr(attrId, attrType, QLatin1String(""), deCONZ::ZclWrite, true);
attr.setValue(QVariant(attrValue));
if (!attr.writeToStream(stream))
{
return false;
}
}
{
task.req.asdu().clear();
QDataStream stream(&task.req.asdu(), QIODevice::WriteOnly);
stream.setByteOrder(QDataStream::LittleEndian);
task.zclFrame.writeToStream(stream);
}
return addTask(task);
}