mirror of https://github.com/arendst/Tasmota.git
Merge pull request #16545 from s-hadinger/zigbee_muldiv_plug
Zigbee support for decimal Voltage/Current/Power on power metering plugs
This commit is contained in:
commit
088e44e1e3
|
@ -9,6 +9,7 @@ All notable changes to this project will be documented in this file.
|
|||
- Berry automated solidification of code
|
||||
- Support of optional file calib.dat on ADE7953 based energy monitors like Shelly EM (#16486)
|
||||
- Command ``SetOption46 0..255`` to add 0..255 * 10 milliseconds power on delay before initializing I/O (#15438)
|
||||
- Zigbee support for decimal Voltage/Current/Power on power metering plugs
|
||||
|
||||
### Changed
|
||||
- ESP32 Increase number of button GPIOs from 8 to 28 (#16518)
|
||||
|
|
|
@ -24,6 +24,91 @@
|
|||
#endif
|
||||
const uint16_t kZigbeeSaveDelaySeconds = ZIGBEE_SAVE_DELAY_SECONDS; // wait for x seconds
|
||||
|
||||
// Convert a multiplier or divisor initially on 2 bytes, to a single byte
|
||||
// We use a property that values are usually powers of 10 or 2/5/25/50...
|
||||
// Values can range from 0 to 31e7
|
||||
typedef struct uint8log_t {
|
||||
uint8_t mantissa : 6; // 0..63
|
||||
uint8_t exponent10 : 2; // 0..3
|
||||
} uint8log_t;
|
||||
|
||||
// convert uint8log to uint, lossless conversion, but can create an overflow with high exponent
|
||||
uint16_t uint8log_to_uint16(uint8_t v8) {
|
||||
uint8log_t * v_log = (uint8log_t*) &v8;
|
||||
uint32_t val = v_log->mantissa;
|
||||
for (uint32_t i = 0; i < v_log->exponent10; i++) {
|
||||
val = val * 10;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
// convert uint16_t to uint8log, ther is potential rounding happening above 63, except when a multiple of 10
|
||||
uint8_t uint16_to_uint8log(uint16_t val) {
|
||||
uint8log_t v_log;
|
||||
uint32_t mantissa = val; // mantissa must be 0..63
|
||||
uint32_t expo10 = 0; // exponent in base 10
|
||||
|
||||
while (mantissa > 63) {
|
||||
expo10++;
|
||||
mantissa = mantissa / 10;
|
||||
}
|
||||
// test overflow
|
||||
if (expo10 > 3) {
|
||||
expo10 = 3;
|
||||
mantissa = 63; // max value is 63000
|
||||
}
|
||||
v_log.mantissa = mantissa;
|
||||
v_log.exponent10 = expo10;
|
||||
uint8_t * v8 = (uint8_t*) &v_log;
|
||||
return *v8;
|
||||
}
|
||||
|
||||
const uint16_t uint8log_test_vectors[] = {
|
||||
0,1,2,5,10,20,33,50,66,75,100,150,200,300,500,1000,2500,3000,5000,10000,20000,25000,30000,50000,60000,65535
|
||||
};
|
||||
|
||||
// uint8log unit tests (normally not called)
|
||||
void uint8log_tests(void) {
|
||||
AddLog(LOG_LEVEL_INFO, "ZIG: sizeof(uint8log_t)=%i", sizeof(uint8log_t));
|
||||
|
||||
for (uint32_t i = 0; i < sizeof(uint8log_test_vectors)/2; i++) {
|
||||
uint16_t v16 = uint8log_test_vectors[i];
|
||||
uint8_t v = uint16_to_uint8log(v16);
|
||||
uint16_t v16_out = uint8log_to_uint16(v);
|
||||
AddLog(LOG_LEVEL_INFO, ">>>: v16=%5i out=%5i %s hex=0x%02X", v16, v16_out, v16 != v16_out ? "<>" : "", v);
|
||||
}
|
||||
}
|
||||
/*
|
||||
Output:
|
||||
00:00:00.128 ZIG: sizeof(uint8log_t)=1
|
||||
00:00:00.129 >>>: v16= 0 out= 0 hex=0x00
|
||||
00:00:00.129 >>>: v16= 1 out= 1 hex=0x01
|
||||
00:00:00.130 >>>: v16= 2 out= 2 hex=0x02
|
||||
00:00:00.140 >>>: v16= 5 out= 5 hex=0x05
|
||||
00:00:00.140 >>>: v16= 10 out= 10 hex=0x0A
|
||||
00:00:00.151 >>>: v16= 20 out= 20 hex=0x14
|
||||
00:00:00.151 >>>: v16= 33 out= 33 hex=0x21
|
||||
00:00:00.152 >>>: v16= 50 out= 50 hex=0x32
|
||||
00:00:00.162 >>>: v16= 66 out= 60 <> hex=0x46
|
||||
00:00:00.163 >>>: v16= 75 out= 70 <> hex=0x47
|
||||
00:00:00.173 >>>: v16= 100 out= 100 hex=0x4A
|
||||
00:00:00.174 >>>: v16= 150 out= 150 hex=0x4F
|
||||
00:00:00.174 >>>: v16= 200 out= 200 hex=0x54
|
||||
00:00:00.185 >>>: v16= 300 out= 300 hex=0x5E
|
||||
00:00:00.185 >>>: v16= 500 out= 500 hex=0x72
|
||||
00:00:00.185 >>>: v16= 1000 out= 1000 hex=0x8A
|
||||
00:00:00.196 >>>: v16= 2500 out= 2500 hex=0x99
|
||||
00:00:00.196 >>>: v16= 3000 out= 3000 hex=0x9E
|
||||
00:00:00.207 >>>: v16= 5000 out= 5000 hex=0xB2
|
||||
00:00:00.207 >>>: v16=10000 out=10000 hex=0xCA
|
||||
00:00:00.208 >>>: v16=20000 out=20000 hex=0xD4
|
||||
00:00:00.219 >>>: v16=25000 out=25000 hex=0xD9
|
||||
00:00:00.219 >>>: v16=30000 out=30000 hex=0xDE
|
||||
00:00:00.219 >>>: v16=50000 out=50000 hex=0xF2
|
||||
00:00:00.230 >>>: v16=60000 out=60000 hex=0xFC
|
||||
00:00:00.231 >>>: v16=65535 out=63000 <> hex=0xFF
|
||||
*/
|
||||
|
||||
enum class Z_Data_Type : uint8_t {
|
||||
Z_Unknown = 0x00,
|
||||
Z_Light = 1, // Lights 1-5 channels
|
||||
|
@ -180,22 +265,50 @@ public:
|
|||
Z_Data_Plug(uint8_t endpoint = 0) :
|
||||
Z_Data(Z_Data_Type::Z_Plug, endpoint),
|
||||
mains_voltage(0xFFFF),
|
||||
mains_power(-0x8000)
|
||||
mains_power(-0x8000),
|
||||
ac_voltage_div(1),
|
||||
ac_voltage_mul(1),
|
||||
ac_current_div(1),
|
||||
ac_current_mul(1),
|
||||
ac_power_div(1),
|
||||
ac_power_mul(1)
|
||||
{}
|
||||
|
||||
inline bool validMainsVoltage(void) const { return 0xFFFF != mains_voltage; }
|
||||
inline bool validMainsPower(void) const { return -0x8000 != mains_power; }
|
||||
|
||||
inline uint16_t getMainsVoltage(void) const { return mains_voltage; }
|
||||
inline int16_t getMainsPower(void) const { return mains_power; }
|
||||
inline uint16_t getMainsVoltageRaw(void) const { return mains_voltage; }
|
||||
inline int16_t getMainsPowerRaw(void) const { return mains_power; }
|
||||
inline float getMainsVoltage(void) const { return (float)mains_voltage * getACVoltageMul() / getACVoltageDiv(); }
|
||||
inline float getMainsPower(void) const { return (float)mains_power * getACPowerMul() / getACPowerDiv(); }
|
||||
|
||||
inline void setMainsVoltage(uint16_t _mains_voltage) { mains_voltage = _mains_voltage; }
|
||||
inline void setMainsPower(int16_t _mains_power) { mains_power = _mains_power; }
|
||||
inline void setMainsVoltageRaw(uint16_t _mains_voltage) { mains_voltage = _mains_voltage; }
|
||||
inline void setMainsPowerRaw(int16_t _mains_power) { mains_power = _mains_power; }
|
||||
|
||||
inline uint16_t getACVoltageDiv(void) const { return uint8log_to_uint16(ac_voltage_div); }
|
||||
inline uint16_t getACVoltageMul(void) const { return uint8log_to_uint16(ac_voltage_mul); }
|
||||
inline uint16_t getACCurrentDiv(void) const { return uint8log_to_uint16(ac_current_div); }
|
||||
inline uint16_t getACCurrentMul(void) const { return uint8log_to_uint16(ac_current_mul); }
|
||||
inline uint16_t getACPowerDiv(void) const { return uint8log_to_uint16(ac_power_div); }
|
||||
inline uint16_t getACPowerMul(void) const { return uint8log_to_uint16(ac_power_mul); }
|
||||
|
||||
inline void setACVoltageDiv(uint16_t v) { ac_voltage_div = uint16_to_uint8log(v); }
|
||||
inline void setACVoltageMul(uint16_t v) { ac_voltage_mul = uint16_to_uint8log(v); }
|
||||
inline void setACCurrentDiv(uint16_t v) { ac_current_div = uint16_to_uint8log(v); }
|
||||
inline void setACCurrentMul(uint16_t v) { ac_current_mul = uint16_to_uint8log(v); }
|
||||
inline void setACPowerDiv(uint16_t v) { ac_power_div = uint16_to_uint8log(v); }
|
||||
inline void setACPowerMul(uint16_t v) { ac_power_mul = uint16_to_uint8log(v); }
|
||||
|
||||
static const Z_Data_Type type = Z_Data_Type::Z_Plug;
|
||||
// 4 bytes
|
||||
uint16_t mains_voltage; // AC voltage
|
||||
int16_t mains_power; // Active power
|
||||
uint8_t ac_voltage_div;
|
||||
uint8_t ac_voltage_mul;
|
||||
uint8_t ac_current_div;
|
||||
uint8_t ac_current_mul;
|
||||
uint8_t ac_power_div;
|
||||
uint8_t ac_power_mul;
|
||||
};
|
||||
|
||||
/*********************************************************************************************\
|
||||
|
|
|
@ -703,7 +703,7 @@ void ZCLFrame::removeInvalidAttributes(Z_attribute_list& attr_list) {
|
|||
// Note: both function are now split to compute on extracted attributes
|
||||
//
|
||||
void ZCLFrame::computeSyntheticAttributes(Z_attribute_list& attr_list) {
|
||||
const Z_Device & device = zigbee_devices.findShortAddr(shortaddr);
|
||||
Z_Device & device = zigbee_devices.findShortAddr(shortaddr);
|
||||
|
||||
String modelId((char*) device.modelId);
|
||||
// scan through attributes and apply specific converters
|
||||
|
@ -812,9 +812,29 @@ void ZCLFrame::computeSyntheticAttributes(Z_attribute_list& attr_list) {
|
|||
}
|
||||
break;
|
||||
case 0x05000002: // ZoneStatus
|
||||
const Z_Data_Alarm & alarm = (const Z_Data_Alarm&) zigbee_devices.getShortAddr(shortaddr).data.find(Z_Data_Type::Z_Alarm, srcendpoint);
|
||||
if (&alarm != nullptr) {
|
||||
alarm.convertZoneStatus(attr_list, attr.getUInt());
|
||||
{
|
||||
const Z_Data_Alarm & alarm = (const Z_Data_Alarm&) zigbee_devices.getShortAddr(shortaddr).data.find(Z_Data_Type::Z_Alarm, srcendpoint);
|
||||
if (&alarm != nullptr) {
|
||||
alarm.convertZoneStatus(attr_list, attr.getUInt());
|
||||
}
|
||||
}
|
||||
break;
|
||||
// convert AC multipliers/dividers
|
||||
case 0x0B040600 ... 0x0B040605: // cluser 0x0B04 - attr 0x0600..0x0605
|
||||
{
|
||||
uint16_t val = attr.getUInt();
|
||||
Z_Data_Plug & plug = device.data.get<Z_Data_Plug>();
|
||||
if (&plug != &z_data_unk) {
|
||||
switch (ccccaaaa) {
|
||||
case 0x0B040600: plug.setACVoltageMul(val); break;
|
||||
case 0x0B040601: plug.setACVoltageDiv(val); break;
|
||||
case 0x0B040602: plug.setACCurrentMul(val); break;
|
||||
case 0x0B040603: plug.setACCurrentDiv(val); break;
|
||||
case 0x0B040604: plug.setACPowerMul(val); break;
|
||||
case 0x0B040605: plug.setACPowerDiv(val); break;
|
||||
}
|
||||
}
|
||||
// AddLog(LOG_LEVEL_INFO, ">>>: cluster=0x%04X attr=0x%04X v=%i", attr.cluster, attr.attr_id, attr.getUInt());
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -1452,14 +1472,14 @@ void Z_postProcessAttributes(uint16_t shortaddr, uint16_t src_ep, class Z_attrib
|
|||
float fval = attr.getFloat();
|
||||
if (found && (matched_attr.map_type != Z_Data_Type::Z_Unknown)) {
|
||||
// We apply an automatic mapping to Z_Data_XXX object
|
||||
// First we find or instantiate the correct Z_Data_XXX accorfing to the endpoint
|
||||
// First we find or instantiate the correct Z_Data_XXX according to the endpoint
|
||||
// Then store the attribute at the attribute addres (via offset) and according to size 8/16/32 bits
|
||||
|
||||
// add the endpoint if it was not already known
|
||||
device.addEndpoint(src_ep);
|
||||
// we don't apply the multiplier, but instead store in Z_Data_XXX object
|
||||
Z_Data & data = device.data.getByType(matched_attr.map_type, src_ep);
|
||||
uint8_t *attr_address = ((uint8_t*)&data) + sizeof(Z_Data) + matched_attr.map_offset;
|
||||
uint8_t * attr_address = ((uint8_t*)&data) + sizeof(Z_Data) + matched_attr.map_offset;
|
||||
uint32_t uval32 = attr.getUInt(); // call converter to uint only once
|
||||
int32_t ival32 = attr.getInt(); // call converter to int only once
|
||||
// AddLog(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_ZIGBEE "Mapping type=%d offset=%d zigbee_type=%02X value=%d\n"), (uint8_t) matched_attr.matched_attr, matched_attr.map_offset, matched_attr.zigbee_type, ival32);
|
||||
|
@ -1512,6 +1532,22 @@ void Z_postProcessAttributes(uint16_t shortaddr, uint16_t src_ep, class Z_attrib
|
|||
break;
|
||||
case 0x00060000:
|
||||
case 0x00068000: device.setPower(attr.getBool(), src_ep); break;
|
||||
// apply multiplier/divisor to AC values
|
||||
case 0x0B040505: // RMSVoltage
|
||||
case 0x0B040508: // RMSCurrent
|
||||
case 0x0B04050B: // ActivePower
|
||||
{
|
||||
const Z_Data_Plug & plug = device.data.find<Z_Data_Plug>();
|
||||
if (&plug != &z_data_unk) {
|
||||
switch (ccccaaaa) {
|
||||
case 0x0B040505: fval = fval * plug.getACVoltageMul() / plug.getACVoltageDiv(); break;
|
||||
case 0x0B040508: fval = fval * plug.getACCurrentMul() / plug.getACCurrentDiv(); break;
|
||||
case 0x0B04050B: fval = fval * plug.getACPowerMul() / plug.getACPowerDiv(); break;
|
||||
}
|
||||
attr.setFloat(fval);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// now apply the multiplier to make it human readable
|
||||
|
@ -1681,7 +1717,19 @@ void Z_Data::toAttributes(Z_attribute_list & attr_list) const {
|
|||
fval = fval / divider;
|
||||
}
|
||||
}
|
||||
// special case for plugs, with parametric multiplier/divisor
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Winvalid-offsetof" // avoid warnings since we're using offsetof() in a risky way
|
||||
const Z_Data_Plug * plug = (Z_Data_Plug*) this;
|
||||
if (map_type == Z_Data_Type::Z_Plug) {
|
||||
if (map_offset == Z_OFFSET(Z_Data_Plug, mains_voltage)) {
|
||||
fval = fval * plug->getACVoltageMul() / plug->getACVoltageDiv();
|
||||
} else if (map_offset == Z_OFFSET(Z_Data_Plug, mains_power)) {
|
||||
fval = fval * plug->getACPowerMul() / plug->getACPowerDiv();
|
||||
}
|
||||
}
|
||||
attr.setFloat(fval);
|
||||
#pragma GCC diagnostic pop
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -666,7 +666,7 @@ int32_t Z_ReceiveActiveEp(int32_t res, const SBuffer &buf) {
|
|||
const uint8_t Z_bindings[] PROGMEM = {
|
||||
Cx0001, Cx0006, Cx0008, Cx0102, Cx0201, Cx0300,
|
||||
Cx0400, Cx0402, Cx0403, Cx0405, Cx0406,
|
||||
Cx0500,
|
||||
Cx0500, Cx0B04,
|
||||
};
|
||||
|
||||
int32_t Z_ClusterToCxBinding(uint16_t cluster) {
|
||||
|
@ -714,6 +714,11 @@ void Z_AutoBindDefer(uint16_t shortaddr, uint8_t endpoint, const SBuffer &buf,
|
|||
zigbee_devices.queueTimer(shortaddr, 0 /* groupaddr */, 2000, 0x0500, endpoint, Z_CAT_CIE_ENROLL, 1 /* zone */, &Z_SendCIEZoneEnrollResponse);
|
||||
}
|
||||
|
||||
// if Plug, request the multipliers and divisors for Voltage, Current and Power
|
||||
if (bitRead(cluster_in_map, Z_ClusterToCxBinding(0x0B04))) {
|
||||
zigbee_devices.queueTimer(shortaddr, 0 /* groupaddr */, 2000, 0x0B04, endpoint, Z_CAT_READ_ATTRIBUTE, 0 /* ignore */, &Z_SendSinglePlugMulDivAttributesRead);
|
||||
}
|
||||
|
||||
// enqueue bind requests
|
||||
for (uint32_t i=0; i<nitems(Z_bindings); i++) {
|
||||
if (bitRead(cluster_map, i)) {
|
||||
|
@ -1418,6 +1423,27 @@ void Z_SendSingleAttributeRead(uint16_t shortaddr, uint16_t groupaddr, uint16_t
|
|||
zigbeeZCLSendCmd(zcl);
|
||||
}
|
||||
|
||||
//
|
||||
// Send single attribute read request in Timer
|
||||
//
|
||||
void Z_SendSinglePlugMulDivAttributesRead(uint16_t shortaddr, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint, uint32_t value) {
|
||||
ZCLFrame zcl(12); // message is 12 bytes
|
||||
zcl.shortaddr = shortaddr;
|
||||
zcl.cluster = 0x0B04;
|
||||
zcl.dstendpoint = endpoint;
|
||||
zcl.cmd = ZCL_READ_ATTRIBUTES;
|
||||
zcl.clusterSpecific = false;
|
||||
zcl.needResponse = true;
|
||||
zcl.direct = false; // discover route
|
||||
zcl.payload.add16(0x0600);
|
||||
zcl.payload.add16(0x0601);
|
||||
zcl.payload.add16(0x0602);
|
||||
zcl.payload.add16(0x0603);
|
||||
zcl.payload.add16(0x0604);
|
||||
zcl.payload.add16(0x0605);
|
||||
zigbeeZCLSendCmd(zcl);
|
||||
}
|
||||
|
||||
//
|
||||
// Write CIE address
|
||||
//
|
||||
|
|
|
@ -2260,10 +2260,12 @@ void ZigbeeShow(bool json)
|
|||
if (plug_voltage || plug_power) {
|
||||
WSContentSend_P(PSTR(" ⚡ "));
|
||||
if (plug_voltage) {
|
||||
WSContentSend_P(PSTR(" %dV"), plug.getMainsVoltage());
|
||||
float mains_voltage = plug.getMainsVoltage();
|
||||
WSContentSend_P(PSTR(" %-1_fV"), &mains_voltage);
|
||||
}
|
||||
if (plug_power) {
|
||||
WSContentSend_P(PSTR(" %dW"), plug.getMainsPower());
|
||||
float mains_power = plug.getMainsPower();
|
||||
WSContentSend_P(PSTR(" %-1_fW"), &mains_power);
|
||||
}
|
||||
}
|
||||
WSContentSend_P(PSTR("{e}"));
|
||||
|
|
Loading…
Reference in New Issue