From e3f4cc94f6b4ec3632a9396dc77b13caccc36ba5 Mon Sep 17 00:00:00 2001 From: Stephan Hadinger Date: Sat, 31 Oct 2020 17:48:40 +0100 Subject: [PATCH] Zigbee added ``ZbOccupancy`` command to configure the time-out for PIR --- CHANGELOG.md | 1 + tasmota/i18n.h | 2 + tasmota/xdrv_23_zigbee_2_devices.ino | 38 +++++++++++++- tasmota/xdrv_23_zigbee_2a_devices_impl.ino | 24 +++++---- tasmota/xdrv_23_zigbee_4_persistence.ino | 2 +- tasmota/xdrv_23_zigbee_5_converters.ino | 7 ++- tasmota/xdrv_23_zigbee_A_impl.ino | 58 +++++++++++++++++++++- 7 files changed, 117 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e8e9ce778..b428f58eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ All notable changes to this project will be documented in this file. - Zigbee reduce battery drain (#9642) - Zigbee added ``ZbMap`` command to describe Zigbee topology (#9651) - Command ``Gpios 255`` to show all possible GPIO configurations +- Zigbee added ``ZbOccupancy`` command to configure the time-out for PIR ### Changed - PlatformIO library structure redesigned for compilation speed by Jason2866 diff --git a/tasmota/i18n.h b/tasmota/i18n.h index 50287a783..d19bf3144 100644 --- a/tasmota/i18n.h +++ b/tasmota/i18n.h @@ -577,6 +577,8 @@ #define D_JSON_ZIGBEE_STATUS_MSG "StatusMessage" #define D_CMND_ZIGBEE_LIGHT "Light" #define D_JSON_ZIGBEE_LIGHT "Light" +#define D_CMND_ZIGBEE_OCCUPANCY "Occupancy" + #define D_JSON_ZIGBEE_OCCUPANCY "Occupancy" #define D_CMND_ZIGBEE_RESTORE "Restore" #define D_CMND_ZIGBEE_CONFIG "Config" #define D_JSON_ZIGBEE_CONFIG "Config" diff --git a/tasmota/xdrv_23_zigbee_2_devices.ino b/tasmota/xdrv_23_zigbee_2_devices.ino index 1d2d2ca52..443d33cf1 100644 --- a/tasmota/xdrv_23_zigbee_2_devices.ino +++ b/tasmota/xdrv_23_zigbee_2_devices.ino @@ -172,7 +172,7 @@ void Z_Data_OnOff::setPower(bool val, uint32_t relay) { } /*********************************************************************************************\ - * Device specific: Light device + * Device specific: Plug device \*********************************************************************************************/ class Z_Data_Plug : public Z_Data { public: @@ -249,6 +249,18 @@ public: /*********************************************************************************************\ * Device specific: PIR + * + // List of occupancy time-outs: + // 0xF = default (90 s) + // 0x0 = no time-out + // 0x1 = 15 s + // 0x2 = 30 s + // 0x3 = 45 s + // 0x4 = 60 s + // 0x5 = 75 s + // 0x6 = 90 s -- default + // 0x7 = 105 s + // 0x8 = 120 s \*********************************************************************************************/ class Z_Data_PIR : public Z_Data { public: @@ -265,7 +277,10 @@ public: inline uint16_t getIlluminance(void) const { return illuminance; } inline void setOccupancy(uint8_t _occupancy) { occupancy = _occupancy; } - inline void setilluminance(uint16_t _illuminance) { illuminance = _illuminance; } + inline void setIlluminance(uint16_t _illuminance) { illuminance = _illuminance; } + + uint32_t getTimeoutSeconds(void) const; + void setTimeoutSeconds(int32_t value); static const Z_Data_Type type = Z_Data_Type::Z_PIR; // PIR @@ -273,6 +288,24 @@ public: uint16_t illuminance; // illuminance }; +uint32_t Z_Data_PIR::getTimeoutSeconds(void) const { + if (_config != 0xF) { + return _config * 15; + } else { + return 90; + } +} + +void Z_Data_PIR::setTimeoutSeconds(int32_t value) { + if (value < 0) { + _config = 0xF; + } else { + uint32_t val_15 = (value + 14)/ 15; // always round up + if (val_15 > 8) { val_15 = 8; } + _config = val_15; + } +} + /*********************************************************************************************\ * Device specific: Sensors: temp, humidity, pressure... \*********************************************************************************************/ @@ -510,6 +543,7 @@ public: inline bool getReachable(void) const { return reachable; } inline bool getPower(uint8_t ep =0) const; + bool addEndpoint(uint8_t endpoint); // dump device attributes to ZbData void toAttributes(Z_attribute_list & attr_list) const; diff --git a/tasmota/xdrv_23_zigbee_2a_devices_impl.ino b/tasmota/xdrv_23_zigbee_2a_devices_impl.ino index 14a56f17d..c5ef7dcb1 100644 --- a/tasmota/xdrv_23_zigbee_2a_devices_impl.ino +++ b/tasmota/xdrv_23_zigbee_2a_devices_impl.ino @@ -228,21 +228,27 @@ void Z_Devices::clearEndpoints(uint16_t shortaddr) { // // Add an endpoint to a shortaddr +// return true if a change was made // -void Z_Devices::addEndpoint(uint16_t shortaddr, uint8_t endpoint) { - if ((0x00 == endpoint) || (endpoint > 240)) { return; } - Z_Device &device = getShortAddr(shortaddr); +bool Z_Device::addEndpoint(uint8_t endpoint) { + if ((0x00 == endpoint) || (endpoint > 240)) { return false; } for (uint32_t i = 0; i < endpoints_max; i++) { - if (endpoint == device.endpoints[i]) { - return; // endpoint already there + if (endpoint == endpoints[i]) { + return false; // endpoint already there } - if (0 == device.endpoints[i]) { - device.endpoints[i] = endpoint; - dirty(); - return; + if (0 == endpoints[i]) { + endpoints[i] = endpoint; + return true; } } + return false; +} + +void Z_Devices::addEndpoint(uint16_t shortaddr, uint8_t endpoint) { + if (getShortAddr(shortaddr).addEndpoint(endpoint)) { + dirty(); + } } // diff --git a/tasmota/xdrv_23_zigbee_4_persistence.ino b/tasmota/xdrv_23_zigbee_4_persistence.ino index b6d252d59..fc7af0c67 100644 --- a/tasmota/xdrv_23_zigbee_4_persistence.ino +++ b/tasmota/xdrv_23_zigbee_4_persistence.ino @@ -265,7 +265,7 @@ void hydrateSingleDevice(const SBuffer & buf_d, uint32_t version) { uint8_t ep = buf_d.get8(d++); if (0xFF == ep) { break; } // ep 0xFF marks the end of the endpoints if (ep > 240) { ep = 0xFF; } // ep == 0xFF means ignore - if ((ep > 0) && (ep != 0xFF)) { zigbee_devices.addEndpoint(shortaddr, ep); } // don't add endpoint if it is 0x00 + device.addEndpoint(ep); // it will ignore invalid endpoints while (d < buf_len) { uint8_t config_type = buf_d.get8(d++); if (0xFF == config_type) { break; } // 0xFF marks the end of congiguration diff --git a/tasmota/xdrv_23_zigbee_5_converters.ino b/tasmota/xdrv_23_zigbee_5_converters.ino index ca6180ed3..126614ac0 100644 --- a/tasmota/xdrv_23_zigbee_5_converters.ino +++ b/tasmota/xdrv_23_zigbee_5_converters.ino @@ -1285,7 +1285,12 @@ void ZCLFrame::generateCallBacks(Z_attribute_list& attr_list) { case 0x04060000: // Occupancy uint32_t occupancy = attr.getUInt(); if (occupancy) { - zigbee_devices.setTimer(_srcaddr, 0 /* groupaddr */, OCCUPANCY_TIMEOUT, _cluster_id, _srcendpoint, Z_CAT_VIRTUAL_OCCUPANCY, 0, &Z_OccupancyCallback); + uint32_t pir_timer = OCCUPANCY_TIMEOUT; + const Z_Data_PIR & pir_found = (const Z_Data_PIR&) zigbee_devices.getShortAddr(_srcaddr).data.find(Z_Data_Type::Z_PIR, _srcendpoint); + if (&pir_found != nullptr) { + pir_timer = pir_found.getTimeoutSeconds() * 1000; + } + zigbee_devices.setTimer(_srcaddr, 0 /* groupaddr */, pir_timer, _cluster_id, _srcendpoint, Z_CAT_VIRTUAL_OCCUPANCY, 0, &Z_OccupancyCallback); } else { zigbee_devices.resetTimersForDevice(_srcaddr, 0 /* groupaddr */, Z_CAT_VIRTUAL_OCCUPANCY); } diff --git a/tasmota/xdrv_23_zigbee_A_impl.ino b/tasmota/xdrv_23_zigbee_A_impl.ino index ebc0ddb9f..26d5b977c 100644 --- a/tasmota/xdrv_23_zigbee_A_impl.ino +++ b/tasmota/xdrv_23_zigbee_A_impl.ino @@ -32,7 +32,8 @@ const char kZbCommands[] PROGMEM = D_PRFX_ZB "|" // prefix D_CMND_ZIGBEE_STATUS "|" D_CMND_ZIGBEE_RESET "|" D_CMND_ZIGBEE_SEND "|" D_CMND_ZIGBEE_PROBE "|" D_CMND_ZIGBEE_FORGET "|" D_CMND_ZIGBEE_SAVE "|" D_CMND_ZIGBEE_NAME "|" D_CMND_ZIGBEE_BIND "|" D_CMND_ZIGBEE_UNBIND "|" D_CMND_ZIGBEE_PING "|" D_CMND_ZIGBEE_MODELID "|" - D_CMND_ZIGBEE_LIGHT "|" D_CMND_ZIGBEE_RESTORE "|" D_CMND_ZIGBEE_BIND_STATE "|" D_CMND_ZIGBEE_MAP "|" + D_CMND_ZIGBEE_LIGHT "|" D_CMND_ZIGBEE_OCCUPANCY "|" + D_CMND_ZIGBEE_RESTORE "|" D_CMND_ZIGBEE_BIND_STATE "|" D_CMND_ZIGBEE_MAP "|" D_CMND_ZIGBEE_CONFIG "|" D_CMND_ZIGBEE_DATA ; @@ -47,7 +48,8 @@ void (* const ZigbeeCommand[])(void) PROGMEM = { &CmndZbStatus, &CmndZbReset, &CmndZbSend, &CmndZbProbe, &CmndZbForget, &CmndZbSave, &CmndZbName, &CmndZbBind, &CmndZbUnbind, &CmndZbPing, &CmndZbModelId, - &CmndZbLight, &CmndZbRestore, &CmndZbBindState, &CmndZbMap, + &CmndZbLight, &CmndZbOccupancy, + &CmndZbRestore, &CmndZbBindState, &CmndZbMap, &CmndZbConfig, CmndZbData, }; @@ -1111,6 +1113,56 @@ void CmndZbLight(void) { MqttPublishPrefixTopicRulesProcess_P(RESULT_OR_STAT, PSTR(D_PRFX_ZB D_CMND_ZIGBEE_LIGHT)); ResponseCmndDone(); } +// +// Command `ZbOccupancy` +// Specify, read or erase the Occupancy detector configuration +void CmndZbOccupancy(void) { + // Syntax is: + // ZbOccupancy , - set the occupancy time-out + // ZbOccupancy - display the configuration + // + // List of occupancy time-outs: + // 0xF = default (90 s) + // 0x0 = no time-out + // 0x1 = 15 s + // 0x2 = 30 s + // 0x3 = 45 s + // 0x4 = 60 s + // 0x5 = 75 s + // 0x6 = 90 s -- default + // 0x7 = 105 s + // 0x8 = 120 s + // Where can be: short_addr, long_addr, device_index, friendly_name + + if (zigbee.init_phase) { ResponseCmndChar_P(PSTR(D_ZIGBEE_NOT_STARTED)); return; } + + // check if parameters contain a comma ',' + char *p; + char *str = strtok_r(XdrvMailbox.data, ", ", &p); + + // parse first part, + uint16_t shortaddr = zigbee_devices.parseDeviceParam(XdrvMailbox.data, true); // in case of short_addr, it must be already registered + if (BAD_SHORTADDR == shortaddr) { ResponseCmndChar_P(PSTR("Unknown device")); return; } + + Z_Device & device = zigbee_devices.getShortAddr(shortaddr); + + int8_t occupancy_time = -1; + if (p) { + Z_Data_PIR & pir = (Z_Data_PIR&) device.data.getByType(Z_Data_Type::Z_PIR); + occupancy_time = strtol(p, nullptr, 10); + pir.setTimeoutSeconds(occupancy_time); + zigbee_devices.dirty(); + } else { + const Z_Data_PIR & pir_found = (const Z_Data_PIR&) device.data.find(Z_Data_Type::Z_PIR); + if (&pir_found != nullptr) { + occupancy_time = pir_found.getTimeoutSeconds(); + } + } + Response_P(PSTR("{\"" D_PRFX_ZB D_CMND_ZIGBEE_OCCUPANCY "\":%d}"), occupancy_time); + + MqttPublishPrefixTopicRulesProcess_P(RESULT_OR_STAT, PSTR(D_PRFX_ZB D_CMND_ZIGBEE_LIGHT)); + ResponseCmndDone(); +} // // Command `ZbForget` @@ -1357,6 +1409,7 @@ bool parseDeviceInnerData(class Z_Device & device, JsonParserObject root) { if (val = data_values[PSTR("LastSeen")]) { device.last_seen = val.getUInt(); } } else { // Import generic attributes first + device.addEndpoint(endpoint); Z_Data & data = device.data.getByType(data_type, endpoint); // scan through attributes @@ -1459,6 +1512,7 @@ void CmndZbData(void) { } } } + zigbee_devices.dirty(); // save to flash ResponseCmndDone(); } else { // non-JSON, export current data