From f18091c53b6a48b67eb26fe04d124972badaddbb Mon Sep 17 00:00:00 2001 From: Stephan Hadinger Date: Sat, 6 Aug 2022 14:27:58 +0200 Subject: [PATCH] Zigbee include "BatteryPercentage" in all messages --- CHANGELOG.md | 1 + tasmota/include/i18n.h | 1 + .../xdrv_23_zigbee_1z_libs.ino | 28 +++++++++++++------ .../xdrv_23_zigbee_2_devices.ino | 25 ++++++++++------- .../xdrv_23_zigbee_2a_devices_impl.ino | 15 ++++++---- .../xdrv_23_zigbee_4b_data.ino | 17 +++++++++-- .../xdrv_23_zigbee_8_parsers.ino | 2 +- .../xdrv_23_zigbee_A_impl.ino | 4 +-- .../xdrv_52_3_berry_zigbee.ino | 2 +- 9 files changed, 66 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 58350e672..827b7bc0a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ All notable changes to this project will be documented in this file. - Support for multiple ``IRsend`` GPIOs - Zigbee added recording of when the battery was last reported - Zigbee add Battery auto-probe (can be disabled with ``SetOption143 1``) +- Zigbee include "BatteryPercentage" in all messages ### Changed - ESP32 LVGL library from v8.2.0 to v8.3.0 diff --git a/tasmota/include/i18n.h b/tasmota/include/i18n.h index bb8e27f3d..54711b451 100644 --- a/tasmota/include/i18n.h +++ b/tasmota/include/i18n.h @@ -628,6 +628,7 @@ #define D_CMND_ZIGBEE_SAVE "Save" #define D_CMND_ZIGBEE_LINKQUALITY "LinkQuality" #define D_CMND_ZIGBEE_CLUSTER "Cluster" + #define D_CMND_ZIGBEE_BATTPERCENT "BatteryPercentage" #define D_CMND_ZIGBEE_ENDPOINT "Endpoint" #define D_CMND_ZIGBEE_GROUP "Group" #define D_CMND_ZIGBEE_MANUF "Manuf" diff --git a/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_1z_libs.ino b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_1z_libs.ino index 98a9f3a7d..4cf5fae4e 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_1z_libs.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_1z_libs.ino @@ -231,6 +231,7 @@ public: uint8_t src_ep; // source endpoint, 0xFF if unknown uint8_t lqi; // linkquality, 0xFF if unknown uint16_t group_id; // group address OxFFFF if inknown + uint8_t batt_percent; // battery percentage Z_attribute_list(): LList(), // call superclass constructor @@ -248,11 +249,15 @@ public: src_ep = 0xFF; lqi = 0xFF; group_id = 0xFFFF; + batt_percent = 0xFF; } - inline bool isValidSrcEp(void) const { return 0xFF != src_ep; } - inline bool isValidLQI(void) const { return 0xFF != lqi; } - inline bool isValidGroupId(void) const { return 0xFFFF != group_id; } + inline bool validSrcEp(void) const { return 0xFF != src_ep; } + inline bool validLQI(void) const { return 0xFF != lqi; } + inline bool validGroupId(void) const { return 0xFFFF != group_id; } + inline bool validBattPercent(void) const { return 0xFF != batt_percent; } + + inline void setBattPercent(uint8_t batt) { batt_percent = batt; } // the following addAttribute() compute the suffix and increments it // Add attribute to the list, given cluster and attribute id @@ -273,7 +278,7 @@ public: // dump the entire structure as JSON, starting from head (as parameter) // does not start not end with a comma // do we enclosed in brackets '{' '}' - String toString(bool enclose_brackets = false) const; + String toString(bool enclose_brackets = false, bool include_battery = false) const; // find if attribute with same key already exists, return null if not found const Z_attribute * findAttribute(uint16_t cluster, uint16_t attr_id, uint8_t suffix = 0) const; @@ -765,7 +770,7 @@ Z_attribute & Z_attribute_list::addAttribute(const char * name, const char * nam return attr; } -String Z_attribute_list::toString(bool enclose_brackets) const { +String Z_attribute_list::toString(bool enclose_brackets, bool include_battery) const { String res = ""; if (enclose_brackets) { res += '{'; } bool prefix_comma = false; @@ -773,22 +778,29 @@ String Z_attribute_list::toString(bool enclose_brackets) const { res += attr.toString(prefix_comma); prefix_comma = true; } + // add battery percentage if available, and if BatteryPercentage is not already present + if (include_battery && validBattPercent() && countAttribute(PSTR(D_CMND_ZIGBEE_BATTPERCENT)) == 0) { + if (prefix_comma) { res += ','; } + prefix_comma = true; + res += F("\"" D_CMND_ZIGBEE_BATTPERCENT "\":"); + res += batt_percent; + } // add source endpoint - if (0xFF != src_ep) { + if (validSrcEp()) { if (prefix_comma) { res += ','; } prefix_comma = true; res += F("\"" D_CMND_ZIGBEE_ENDPOINT "\":"); res += src_ep; } // add group address - if (0xFFFF != group_id) { + if (validGroupId()) { if (prefix_comma) { res += ','; } prefix_comma = true; res += F("\"" D_CMND_ZIGBEE_GROUP "\":"); res += group_id; } // add lqi - if (0xFF != lqi) { + if (validLQI()) { if (prefix_comma) { res += ','; } prefix_comma = true; res += F("\"" D_CMND_ZIGBEE_LINKQUALITY "\":"); diff --git a/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_2_devices.ino b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_2_devices.ino index 441cc9165..befa1e22f 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_2_devices.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_2_devices.ino @@ -730,14 +730,14 @@ public: // Light information for Hue integration integration, last known values // New version of device data handling - Z_Data_Set data; // Linkedlist of device data per endpoint + Z_Data_Set data; // Linkedlist of device data per endpoint // other status - device wide data is 8 bytes // START OF DEVICE WIDE DATA - uint32_t last_seen; // Last seen time (epoch) - uint32_t batt_last_seen; // Time when we last received battery status (epoch), 0 means unknown, 0xFFFFFFFF means that the device has no battery - uint32_t batt_last_probed; // Time when the device was last probed for batteyr values - uint8_t lqi; // lqi from last message, 0xFF means unknown - uint8_t batterypercent; // battery percentage (0..100), 0xFF means unknwon + uint32_t last_seen; // Last seen time (epoch) + uint32_t batt_last_seen; // Time when we last received battery status (epoch), 0 means unknown, 0xFFFFFFFF means that the device has no battery + uint32_t batt_last_probed; // Time when the device was last probed for batteyr values + uint8_t lqi; // lqi from last message, 0xFF means unknown + uint8_t batt_percent; // battery percentage (0..100), 0xFF means unknwon uint16_t reserved_for_alignment; // Debounce informmation when receiving commands // If we receive the same ZCL transaction number from the same device and the same endpoint within 300ms @@ -765,7 +765,7 @@ public: batt_last_seen(0), batt_last_probed(0), lqi(0xFF), - batterypercent(0xFF), + batt_percent(0xFF), reserved_for_alignment(0xFFFF), debounce_endpoint(0), debounce_transact(0) @@ -781,7 +781,7 @@ public: inline bool validPower(uint8_t ep =0) const; inline bool validLqi(void) const { return 0xFF != lqi; } - inline bool validBatteryPercent(void) const { return 0xFF != batterypercent; } + inline bool validBatteryPercent(void) const { return 0xFF != batt_percent; } inline bool validLastSeen(void) const { return 0x0 != last_seen; } inline bool validBattLastSeen(void) const { return (0x0 != batt_last_seen) && (0xFFFFFFFF != batt_last_seen); } @@ -794,8 +794,13 @@ public: inline void setRouter(bool router) { is_router = router; } inline void setLQI(uint8_t _lqi) { lqi = _lqi; } - inline void setBatteryPercent(uint8_t bp) { - batterypercent = bp; + // set battery percentage to new value - and mark timestamp only if time is valid + // trigger an immediate `ZbSave` since it's important information to keep + void setBatteryPercent(uint8_t bp) { + if (batt_percent != bp) { + batt_percent = bp; + Z_Set_Save_Data_Timer_Once(0); + } if (Rtc.utc_time >= START_VALID_TIME) { batt_last_seen = Rtc.utc_time; } diff --git a/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_2a_devices_impl.ino b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_2a_devices_impl.ino index 7a4989dff..2bb0a231a 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_2a_devices_impl.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_2a_devices_impl.ino @@ -491,12 +491,12 @@ bool Z_Devices::jsonIsConflict(uint16_t shortaddr, const Z_attribute_list &attr_ } // compare groups - if (device.attr_list.isValidGroupId() && attr_list.isValidGroupId()) { + if (device.attr_list.validGroupId() && attr_list.validGroupId()) { if (device.attr_list.group_id != attr_list.group_id) { return true; } // groups are in conflict } // compare src_ep - if (device.attr_list.isValidSrcEp() && attr_list.isValidSrcEp()) { + if (device.attr_list.validSrcEp() && attr_list.validSrcEp()) { if (device.attr_list.src_ep != attr_list.src_ep) { return true; } } @@ -547,7 +547,7 @@ void Z_Device::jsonPublishAttrList(const char * json_prefix, const Z_attribute_l ResponseAppend_P(PSTR("\"" D_JSON_ZIGBEE_NAME "\":\"%s\","), EscapeJSONString(friendlyName).c_str()); } // Add all other attributes - ResponseAppend_P(PSTR("%s}"), attr_list.toString(false).c_str()); + ResponseAppend_P(PSTR("%s}"), attr_list.toString(false, true).c_str()); // (false, true) - include battery if (!Settings->flag5.zb_omit_json_addr) { ResponseAppend_P(PSTR("}")); @@ -577,7 +577,7 @@ void Z_Device::jsonPublishAttrList(const char * json_prefix, const Z_attribute_l } } if (Settings->flag5.zb_topic_endpoint) { - if (attr_list.isValidSrcEp()) { + if (attr_list.validSrcEp()) { snprintf_P(subtopic, sizeof(subtopic), PSTR("%s_%d"), subtopic, attr_list.src_ep); } } @@ -604,6 +604,11 @@ void Z_Devices::jsonPublishFlush(uint16_t shortaddr) { gZbLastMessage.groupaddr = attr_list.group_id; // %zbgroup% gZbLastMessage.endpoint = attr_list.src_ep; // %zbendpoint% + // add battery percentage from last known value + if (device.validBatteryPercent()) { + attr_list.setBattPercent(device.batt_percent); + } + device.jsonPublishAttrList(PSTR(D_JSON_ZIGBEE_RECEIVED), attr_list); attr_list.reset(); // clear the attributes } @@ -731,7 +736,7 @@ void Z_Device::jsonAddDataAttributes(Z_attribute_list & attr_list) const { // Add "BatteryPercentage", "LastSeen", "LastSeenEpoch", "LinkQuality" void Z_Device::jsonAddDeviceAttributes(Z_attribute_list & attr_list) const { attr_list.addAttributePMEM(PSTR("Reachable")).setBool(getReachable()); - if (validBatteryPercent()) { attr_list.addAttributePMEM(PSTR("BatteryPercentage")).setUInt(batterypercent); } + if (validBatteryPercent()) { attr_list.addAttributePMEM(PSTR("BatteryPercentage")).setUInt(batt_percent); } if (validBattLastSeen()) { attr_list.addAttributePMEM(PSTR("BatteryLastSeenEpoch")).setUInt(batt_last_seen); } diff --git a/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_4b_data.ino b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_4b_data.ino index 76fb96a96..62f2d7557 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_4b_data.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_4b_data.ino @@ -46,7 +46,7 @@ int32_t hydrateDeviceWideData(class Z_Device & device, const SBuffer & buf, size } device.last_seen = buf.get32(start+1); device.lqi = buf.get8(start + 5); - device.batterypercent = buf.get8(start + 6); + device.batt_percent = buf.get8(start + 6); if (segment_len >= 10) { device.batt_last_seen = buf.get32(start+7); } @@ -124,7 +124,7 @@ SBuffer hibernateDeviceData(const struct Z_Device & device) { buf.add8(10); // 10 bytes buf.add32(device.last_seen); buf.add8(device.lqi); - buf.add8(device.batterypercent); + buf.add8(device.batt_percent); // now storing batt_last_seen buf.add32(device.batt_last_seen); @@ -280,6 +280,19 @@ void Z_SaveDataTimer(uint16_t shortaddr, uint16_t groupaddr, uint16_t cluster, u Z_Set_Save_Data_Timer(0); // set a new timer } +// +// Callback for saving all data once, used after receiving important events +// +int32_t Z_Set_Save_Data_Timer_Once(uint8_t value) { + zigbee_devices.setTimer(0x0000, 0, 0 /* now */, 0, 0, Z_CAT_ALWAYS, 0 /* value */, &Z_SaveDataTimerOnce); + return 0; // continue +} + +void Z_SaveDataTimerOnce(uint16_t shortaddr, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint, uint32_t value) { + hibernateAllData(); +} + + #ifdef USE_ZIGBEE_EEPROM void ZFS_Erase(void) { if (zigbee.eeprom_present) { diff --git a/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_8_parsers.ino b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_8_parsers.ino index fb7f8cdb7..14b64109a 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_8_parsers.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_8_parsers.ino @@ -1703,7 +1703,7 @@ void Z_IncomingMessage(class ZCLFrame &zcl_received) { Z_Query_Battery(srcaddr); // do battery auto-probing when receiving commands } - AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_ZIGBEE D_JSON_ZIGBEEZCL_RAW_RECEIVED ": {\"0x%04X\":{%s}}"), srcaddr, attr_list.toString().c_str()); + AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_ZIGBEE D_JSON_ZIGBEEZCL_RAW_RECEIVED ": {\"0x%04X\":{%s}}"), srcaddr, attr_list.toString(false, false).c_str()); // don't include battery // discard the message if it was sent by us (broadcast or group loopback) if (srcaddr == localShortAddr) { diff --git a/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_A_impl.ino b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_A_impl.ino index 9f1fdd0a4..631f77f40 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_A_impl.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_A_impl.ino @@ -2039,8 +2039,8 @@ void ZigbeeShow(bool json) } snprintf_P(sbatt, sizeof(sbatt), msg[ZB_WEB_BATTERY], - device.batterypercent, dhm, - changeUIntScale(device.batterypercent, 0, 100, 0, 14), + device.batt_percent, dhm, + changeUIntScale(device.batt_percent, 0, 100, 0, 14), (color & 0xFF0000) >> 16, (color & 0x00FF00) >> 8, (color & 0x0000FF) ); } diff --git a/tasmota/tasmota_xdrv_driver/xdrv_52_3_berry_zigbee.ino b/tasmota/tasmota_xdrv_driver/xdrv_52_3_berry_zigbee.ino index e0ff79ab2..37f506c0d 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_52_3_berry_zigbee.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_52_3_berry_zigbee.ino @@ -66,7 +66,7 @@ extern "C" { return d->lqi == 255 ? -1 : d->lqi; } int32_t zd_battery(const class Z_Device* d) { - return d->batterypercent == 255 ? -1 : d->batterypercent; + return d->batt_percent == 255 ? -1 : d->batt_percent; } int32_t zd_battery_lastseen(const class Z_Device* d) { return 0; // TODO not yet known