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
- 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

View File

@ -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"

View File

@ -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<Z_attribute>(), // 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 "\":");

View File

@ -737,7 +737,7 @@ public:
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
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;
}

View File

@ -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);
}

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.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) {

View File

@ -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) {

View File

@ -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)
);
}

View File

@ -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