diff --git a/CHANGELOG.md b/CHANGELOG.md index d3428a49b..7c1e62f8f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ All notable changes to this project will be documented in this file. - Support for Modbus bridge adding commands ``ModbusSend``, ``ModbusBaudrate`` and ``ModbusSerialConfig`` (#16013) - 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``) ### Changed - ESP32 LVGL library from v8.2.0 to v8.3.0 diff --git a/tasmota/include/tasmota_types.h b/tasmota/include/tasmota_types.h index e95aa8736..020203940 100644 --- a/tasmota/include/tasmota_types.h +++ b/tasmota/include/tasmota_types.h @@ -170,7 +170,7 @@ typedef union { // Restricted by MISRA-C Rule 18.4 bu uint32_t mqtt_persistent : 1; // bit 26 (v11.1.0.1) - SetOption140 - (MQTT) MQTT clean session (0 = default) or persistent session (1) uint32_t gui_module_name : 1; // bit 27 (v11.1.0.3) - SetOption141 - (GUI) Disable display of GUI module name (1) uint32_t wait_for_wifi_result : 1; // bit 28 (v11.1.0.4) - SetOption142 - (Wifi) Wait 1 second for wifi connection solving some FRITZ!Box modem issues (1) - uint32_t spare29 : 1; // bit 29 + uint32_t zigbee_no_batt_autoprobe : 1; // bit 29 (v12.0.2.4) - SetOption143 - (Zigbee) Disable Battery auto-probe and using auto-binding uint32_t spare30 : 1; // bit 30 uint32_t spare31 : 1; // bit 31 }; diff --git a/tasmota/my_user_config.h b/tasmota/my_user_config.h index eeaa50aba..39d9875c4 100644 --- a/tasmota/my_user_config.h +++ b/tasmota/my_user_config.h @@ -898,6 +898,8 @@ #define USE_ZIGBEE_DEBOUNCE_COMMANDS 200 // if commands are received from the same device/endpoint with same ZCL transaction number, discard packet in this time window (ms) #define USE_ZIGBEE_MODELID "Tasmota Z2T" // reported "ModelId" (cluster 0000 / attribute 0005) #define USE_ZIGBEE_MANUFACTURER "Tasmota" // reported "Manufacturer" (cluster 0000 / attribute 0004) + #define USE_ZIGBEE_BATT_REPROBE (24*3600) // Period in seconds during which we don't ask again for battery, default 1 day + #define USE_ZIGBEE_BATT_REPROBE_PAUSE (3600) // Min wait period when sending an autoprobe, default: wait at least 1 hour #define USE_ZBBRIDGE_TLS // TLS support for zbbridge #define USE_ZIGBEE_ZBBRIDGE_EEPROM 0x50 // I2C id for the ZBBridge EEPROM // #define USE_ZIGBEE_FORCE_NO_CHILDREN // This feature forces `CONFIG_MAX_END_DEVICE_CHILDREN` to zero which means that the coordinator does not accept any direct child. End-devices must pair through a router. 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 f5830f2ea..441cc9165 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_2_devices.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_2_devices.ino @@ -801,7 +801,7 @@ public: } } inline void setHasNoBattery(void) { batt_last_seen = 0xFFFFFFFF; } - inline bool hasNoBattery(void) const { return 0xFFFFFFFF != batt_last_seen; } + inline bool hasNoBattery(void) const { return 0xFFFFFFFF == batt_last_seen; } // Add an endpoint to a device bool addEndpoint(uint8_t endpoint); @@ -959,6 +959,9 @@ public: // device is reachable void deviceWasReached(uint16_t shortaddr); + // device has no battery + void deviceHasNoBattery(uint16_t shortaddr); + // Timers void resetTimersForDevice(uint16_t shortaddr, uint16_t groupaddr, uint8_t category, uint16_t cluster = 0xFFFF, uint8_t endpoint = 0xFF); void setTimer(uint16_t shortaddr, uint16_t groupaddr, uint32_t wait_ms, uint16_t cluster, uint8_t endpoint, uint8_t category, uint32_t value, Z_DeviceTimer func); 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 a8f077e72..7a4989dff 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 @@ -338,6 +338,13 @@ void Z_Devices::deviceWasReached(uint16_t shortaddr) { } } +void Z_Devices::deviceHasNoBattery(uint16_t shortaddr) { + Z_Device & device = findShortAddr(shortaddr); + if (device.valid()) { + device.setHasNoBattery(); // mark device as reachable + } +} + // get the next sequance number for the device, or use the global seq number if device is unknown uint8_t Z_Devices::getNextSeqNumber(uint16_t shortaddr) { Z_Device & device = findShortAddr(shortaddr); diff --git a/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_6_commands.ino b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_6_commands.ino index 3921a9aef..5c4ad28f8 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_6_commands.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_6_commands.ino @@ -150,6 +150,7 @@ const Z_CommandConverter Z_Commands[] PROGMEM = { #define ZLE(x) ((x) & 0xFF), ((x) >> 8) // Little Endian // Below are the attributes we wand to read from each cluster +const uint8_t CLUSTER_0001[] = { ZLE(0x0020), ZLE(0x0021) }; // BatteryVoltage, BatteryPercentage const uint8_t CLUSTER_0006[] = { ZLE(0x0000) }; // Power const uint8_t CLUSTER_0008[] = { ZLE(0x0000) }; // CurrentLevel const uint8_t CLUSTER_0009[] = { ZLE(0x0000) }; // AlarmCount @@ -161,6 +162,10 @@ void Z_ReadAttrCallback(uint16_t shortaddr, uint16_t groupaddr, uint16_t cluster const uint8_t* attrs = nullptr; switch (cluster) { + case 0x0001: + attrs = CLUSTER_0001; + attrs_len = sizeof(CLUSTER_0001); + break; case 0x0006: // for On/Off attrs = CLUSTER_0006; attrs_len = sizeof(CLUSTER_0006); 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 382cf1377..a4c47f42c 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_8_parsers.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_8_parsers.ino @@ -727,7 +727,8 @@ void Z_AutoBindDefer(uint16_t shortaddr, uint8_t endpoint, const SBuffer &buf, for (uint32_t i=0; iflag5.zigbee_no_batt_autoprobe) || !Z_BatteryReportingDeviceSpecific(shortaddr)) { continue; } zigbee_devices.queueTimer(shortaddr, 0 /* groupaddr */, 2000, cluster, endpoint, Z_CAT_CONFIG_ATTR, 0 /* value */, &Z_AutoConfigReportingForCluster); } } @@ -951,11 +952,16 @@ int32_t Z_ReceiveEndDeviceAnnonce(int32_t res, const SBuffer &buf) { // device is reachable zigbee_devices.deviceWasReached(nwkAddr); + bool power_source = (capabilities & 0x04); + if (power_source) { + zigbee_devices.deviceHasNoBattery(nwkAddr); + } + Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATE "\":{" "\"Status\":%d,\"IEEEAddr\":\"0x%_X\",\"ShortAddr\":\"0x%04X\"" ",\"PowerSource\":%s,\"ReceiveWhenIdle\":%s,\"Security\":%s}}"), ZIGBEE_STATUS_DEVICE_ANNOUNCE, &ieeeAddr, nwkAddr, - (capabilities & 0x04) ? PSTR("true") : PSTR("false"), + power_source ? PSTR("true") : PSTR("false"), (capabilities & 0x08) ? PSTR("true") : PSTR("false"), (capabilities & 0x40) ? PSTR("true") : PSTR("false") ); @@ -1673,6 +1679,12 @@ void Z_IncomingMessage(class ZCLFrame &zcl_received) { // Build the ZbReceive list if ( (!zcl_received.isClusterSpecificCommand()) && (ZCL_REPORT_ATTRIBUTES == zcl_received.getCmdId() || ZCL_WRITE_ATTRIBUTES == zcl_received.getCmdId())) { zcl_received.parseReportAttributes(attr_list); // Zigbee report attributes from sensors + + // since we receive a sensor value, and the device is still awake, + // try to read the battery value + if (clusterid != 0x0001) { // avoid sending Battery probe if we already received info from cluster 0x0001 + Z_Query_Battery(srcaddr); + } if (clusterid && (ZCL_REPORT_ATTRIBUTES == zcl_received.getCmdId())) { defer_attributes = true; } // don't defer system Cluster=0 messages or Write Attribute } else if ( (!zcl_received.isClusterSpecificCommand()) && (ZCL_READ_ATTRIBUTES_RESPONSE == zcl_received.getCmdId())) { zcl_received.parseReadAttributesResponse(attr_list); @@ -2071,6 +2083,30 @@ void Z_Query_Bulb(uint16_t shortaddr, uint32_t &wait_ms) { } } +// +// Query the status of the battery (auto-probe) +// +void Z_Query_Battery(uint16_t shortaddr) { + if (Settings->flag5.zigbee_no_batt_autoprobe) { return; } // don't do auto-probe if `SetOption143 1` + if (0 == shortaddr) { return; } + Z_Device & device = zigbee_devices.findShortAddr(shortaddr); + if (device.valid()) { + uint32_t now = Rtc.utc_time; + if (now < START_VALID_TIME) { return; } // internal time is not valid + if (device.hasNoBattery()) { return; } // device is known to have no battery + if (device.batt_last_seen + USE_ZIGBEE_BATT_REPROBE > now) { return; } // battery status is fresh enough + if (device.batt_last_probed + USE_ZIGBEE_BATT_REPROBE_PAUSE > now) { return; } // battery has been probed soon enough + + uint8_t endpoint = zigbee_devices.findFirstEndpoint(shortaddr); + if (endpoint) { // send only if we know the endpoint + device.batt_last_probed = now; // we are probing now + zigbee_devices.setTimer(shortaddr, 0 /* groupaddr */, 0 /* now */, 0x0001, endpoint, Z_CAT_READ_CLUSTER, 0 /* value */, &Z_ReadAttrCallback); + AddLog(LOG_LEVEL_INFO, PSTR("ZIG: Battery auto-probe " + "`ZbSend {\"Device\":\"0x%04X\",\"Read\":{\"BatteryPercentage\":true,\"BatteryVoltage\":true}}`"), shortaddr); + } + } +} + // // Send messages to query the state of each Hue emulated light // 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 9679fa44e..9f1fdd0a4 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_A_impl.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_A_impl.ino @@ -2024,7 +2024,7 @@ void ZigbeeShow(bool json) name = sdevice; } - char sbatt[64]; + char sbatt[96]; char dhm[48]; snprintf_P(sbatt, sizeof(sbatt), PSTR(" ")); if (device.validBatteryPercent()) {