Merge pull request #16167 from s-hadinger/zigbee_battpercent

Zigbee include "BatteryPercentage" in all messages
This commit is contained in:
s-hadinger 2022-08-06 14:40:00 +02:00 committed by GitHub
commit f39679512b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 66 additions and 29 deletions

View File

@ -10,6 +10,7 @@ All notable changes to this project will be documented in this file.
- Support for multiple ``IRsend`` GPIOs - Support for multiple ``IRsend`` GPIOs
- Zigbee added recording of when the battery was last reported - Zigbee added recording of when the battery was last reported
- Zigbee add Battery auto-probe (can be disabled with ``SetOption143 1``) - Zigbee add Battery auto-probe (can be disabled with ``SetOption143 1``)
- Zigbee include "BatteryPercentage" in all messages
### Changed ### Changed
- ESP32 LVGL library from v8.2.0 to v8.3.0 - ESP32 LVGL library from v8.2.0 to v8.3.0

View File

@ -628,6 +628,7 @@
#define D_CMND_ZIGBEE_SAVE "Save" #define D_CMND_ZIGBEE_SAVE "Save"
#define D_CMND_ZIGBEE_LINKQUALITY "LinkQuality" #define D_CMND_ZIGBEE_LINKQUALITY "LinkQuality"
#define D_CMND_ZIGBEE_CLUSTER "Cluster" #define D_CMND_ZIGBEE_CLUSTER "Cluster"
#define D_CMND_ZIGBEE_BATTPERCENT "BatteryPercentage"
#define D_CMND_ZIGBEE_ENDPOINT "Endpoint" #define D_CMND_ZIGBEE_ENDPOINT "Endpoint"
#define D_CMND_ZIGBEE_GROUP "Group" #define D_CMND_ZIGBEE_GROUP "Group"
#define D_CMND_ZIGBEE_MANUF "Manuf" #define D_CMND_ZIGBEE_MANUF "Manuf"

View File

@ -231,6 +231,7 @@ public:
uint8_t src_ep; // source endpoint, 0xFF if unknown uint8_t src_ep; // source endpoint, 0xFF if unknown
uint8_t lqi; // linkquality, 0xFF if unknown uint8_t lqi; // linkquality, 0xFF if unknown
uint16_t group_id; // group address OxFFFF if inknown uint16_t group_id; // group address OxFFFF if inknown
uint8_t batt_percent; // battery percentage
Z_attribute_list(): Z_attribute_list():
LList<Z_attribute>(), // call superclass constructor LList<Z_attribute>(), // call superclass constructor
@ -248,11 +249,15 @@ public:
src_ep = 0xFF; src_ep = 0xFF;
lqi = 0xFF; lqi = 0xFF;
group_id = 0xFFFF; group_id = 0xFFFF;
batt_percent = 0xFF;
} }
inline bool isValidSrcEp(void) const { return 0xFF != src_ep; } inline bool validSrcEp(void) const { return 0xFF != src_ep; }
inline bool isValidLQI(void) const { return 0xFF != lqi; } inline bool validLQI(void) const { return 0xFF != lqi; }
inline bool isValidGroupId(void) const { return 0xFFFF != group_id; } 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 // the following addAttribute() compute the suffix and increments it
// Add attribute to the list, given cluster and attribute id // 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) // dump the entire structure as JSON, starting from head (as parameter)
// does not start not end with a comma // does not start not end with a comma
// do we enclosed in brackets '{' '}' // 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 // 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; 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; 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 = ""; String res = "";
if (enclose_brackets) { res += '{'; } if (enclose_brackets) { res += '{'; }
bool prefix_comma = false; bool prefix_comma = false;
@ -773,22 +778,29 @@ String Z_attribute_list::toString(bool enclose_brackets) const {
res += attr.toString(prefix_comma); res += attr.toString(prefix_comma);
prefix_comma = true; 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 // add source endpoint
if (0xFF != src_ep) { if (validSrcEp()) {
if (prefix_comma) { res += ','; } if (prefix_comma) { res += ','; }
prefix_comma = true; prefix_comma = true;
res += F("\"" D_CMND_ZIGBEE_ENDPOINT "\":"); res += F("\"" D_CMND_ZIGBEE_ENDPOINT "\":");
res += src_ep; res += src_ep;
} }
// add group address // add group address
if (0xFFFF != group_id) { if (validGroupId()) {
if (prefix_comma) { res += ','; } if (prefix_comma) { res += ','; }
prefix_comma = true; prefix_comma = true;
res += F("\"" D_CMND_ZIGBEE_GROUP "\":"); res += F("\"" D_CMND_ZIGBEE_GROUP "\":");
res += group_id; res += group_id;
} }
// add lqi // add lqi
if (0xFF != lqi) { if (validLQI()) {
if (prefix_comma) { res += ','; } if (prefix_comma) { res += ','; }
prefix_comma = true; prefix_comma = true;
res += F("\"" D_CMND_ZIGBEE_LINKQUALITY "\":"); res += F("\"" D_CMND_ZIGBEE_LINKQUALITY "\":");

View File

@ -730,14 +730,14 @@ public:
// Light information for Hue integration integration, last known values // Light information for Hue integration integration, last known values
// New version of device data handling // 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 // other status - device wide data is 8 bytes
// START OF DEVICE WIDE DATA // START OF DEVICE WIDE DATA
uint32_t last_seen; // Last seen time (epoch) 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_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 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 lqi; // lqi from last message, 0xFF means unknown
uint8_t batterypercent; // battery percentage (0..100), 0xFF means unknwon uint8_t batt_percent; // battery percentage (0..100), 0xFF means unknwon
uint16_t reserved_for_alignment; uint16_t reserved_for_alignment;
// Debounce informmation when receiving commands // Debounce informmation when receiving commands
// If we receive the same ZCL transaction number from the same device and the same endpoint within 300ms // 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_seen(0),
batt_last_probed(0), batt_last_probed(0),
lqi(0xFF), lqi(0xFF),
batterypercent(0xFF), batt_percent(0xFF),
reserved_for_alignment(0xFFFF), reserved_for_alignment(0xFFFF),
debounce_endpoint(0), debounce_endpoint(0),
debounce_transact(0) debounce_transact(0)
@ -781,7 +781,7 @@ public:
inline bool validPower(uint8_t ep =0) const; inline bool validPower(uint8_t ep =0) const;
inline bool validLqi(void) const { return 0xFF != lqi; } 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 validLastSeen(void) const { return 0x0 != last_seen; }
inline bool validBattLastSeen(void) const { return (0x0 != batt_last_seen) && (0xFFFFFFFF != batt_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 setRouter(bool router) { is_router = router; }
inline void setLQI(uint8_t _lqi) { lqi = _lqi; } inline void setLQI(uint8_t _lqi) { lqi = _lqi; }
inline void setBatteryPercent(uint8_t bp) { // set battery percentage to new value - and mark timestamp only if time is valid
batterypercent = bp; // 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) { if (Rtc.utc_time >= START_VALID_TIME) {
batt_last_seen = Rtc.utc_time; batt_last_seen = Rtc.utc_time;
} }

View File

@ -491,12 +491,12 @@ bool Z_Devices::jsonIsConflict(uint16_t shortaddr, const Z_attribute_list &attr_
} }
// compare groups // 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 if (device.attr_list.group_id != attr_list.group_id) { return true; } // groups are in conflict
} }
// compare src_ep // 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; } 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()); ResponseAppend_P(PSTR("\"" D_JSON_ZIGBEE_NAME "\":\"%s\","), EscapeJSONString(friendlyName).c_str());
} }
// Add all other attributes // 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) { if (!Settings->flag5.zb_omit_json_addr) {
ResponseAppend_P(PSTR("}")); 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 (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); 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.groupaddr = attr_list.group_id; // %zbgroup%
gZbLastMessage.endpoint = attr_list.src_ep; // %zbendpoint% 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); device.jsonPublishAttrList(PSTR(D_JSON_ZIGBEE_RECEIVED), attr_list);
attr_list.reset(); // clear the attributes 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" // Add "BatteryPercentage", "LastSeen", "LastSeenEpoch", "LinkQuality"
void Z_Device::jsonAddDeviceAttributes(Z_attribute_list & attr_list) const { void Z_Device::jsonAddDeviceAttributes(Z_attribute_list & attr_list) const {
attr_list.addAttributePMEM(PSTR("Reachable")).setBool(getReachable()); 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()) { if (validBattLastSeen()) {
attr_list.addAttributePMEM(PSTR("BatteryLastSeenEpoch")).setUInt(batt_last_seen); attr_list.addAttributePMEM(PSTR("BatteryLastSeenEpoch")).setUInt(batt_last_seen);
} }

View File

@ -46,7 +46,7 @@ int32_t hydrateDeviceWideData(class Z_Device & device, const SBuffer & buf, size
} }
device.last_seen = buf.get32(start+1); device.last_seen = buf.get32(start+1);
device.lqi = buf.get8(start + 5); device.lqi = buf.get8(start + 5);
device.batterypercent = buf.get8(start + 6); device.batt_percent = buf.get8(start + 6);
if (segment_len >= 10) { if (segment_len >= 10) {
device.batt_last_seen = buf.get32(start+7); 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.add8(10); // 10 bytes
buf.add32(device.last_seen); buf.add32(device.last_seen);
buf.add8(device.lqi); buf.add8(device.lqi);
buf.add8(device.batterypercent); buf.add8(device.batt_percent);
// now storing batt_last_seen // now storing batt_last_seen
buf.add32(device.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 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 #ifdef USE_ZIGBEE_EEPROM
void ZFS_Erase(void) { void ZFS_Erase(void) {
if (zigbee.eeprom_present) { if (zigbee.eeprom_present) {

View File

@ -1703,7 +1703,7 @@ void Z_IncomingMessage(class ZCLFrame &zcl_received) {
Z_Query_Battery(srcaddr); // do battery auto-probing when receiving commands 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) // discard the message if it was sent by us (broadcast or group loopback)
if (srcaddr == localShortAddr) { if (srcaddr == localShortAddr) {

View File

@ -2039,8 +2039,8 @@ void ZigbeeShow(bool json)
} }
snprintf_P(sbatt, sizeof(sbatt), snprintf_P(sbatt, sizeof(sbatt),
msg[ZB_WEB_BATTERY], msg[ZB_WEB_BATTERY],
device.batterypercent, dhm, device.batt_percent, dhm,
changeUIntScale(device.batterypercent, 0, 100, 0, 14), changeUIntScale(device.batt_percent, 0, 100, 0, 14),
(color & 0xFF0000) >> 16, (color & 0x00FF00) >> 8, (color & 0x0000FF) (color & 0xFF0000) >> 16, (color & 0x00FF00) >> 8, (color & 0x0000FF)
); );
} }

View File

@ -66,7 +66,7 @@ extern "C" {
return d->lqi == 255 ? -1 : d->lqi; return d->lqi == 255 ? -1 : d->lqi;
} }
int32_t zd_battery(const class Z_Device* d) { 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) { int32_t zd_battery_lastseen(const class Z_Device* d) {
return 0; // TODO not yet known return 0; // TODO not yet known