diff --git a/CHANGELOG.md b/CHANGELOG.md index 98c45ddfc..954727ff0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ All notable changes to this project will be documented in this file. - Zigbee alarm persistence (#9785) - Support for EZO PMP sensors by Christopher Tremblay (#9760) - Commands ``TuyaRGB``, ``TuyaEnum`` and ``TuyaEnumList`` (#9769) +- Zigbee command ``ZbInfo`` and prepare support for EEPROM ### Changed - Core library from v2.7.4.5 to v2.7.4.7 diff --git a/lib/default/AT24C256_512/Eeprom24C512.cpp b/lib/default/AT24C256_512/Eeprom24C512.cpp index 06aedd9cf..86e618aee 100644 --- a/lib/default/AT24C256_512/Eeprom24C512.cpp +++ b/lib/default/AT24C256_512/Eeprom24C512.cpp @@ -227,7 +227,9 @@ Eeprom24C512::readBytes byte remainingBytes = length % EEPROM__RD_BUFFER_SIZE; word offset = length - remainingBytes; - readBuffer(address + offset, remainingBytes, p_data + offset); + if (remainingBytes > 0) { + readBuffer(address + offset, remainingBytes, p_data + offset); + } } /****************************************************************************** diff --git a/tasmota/i18n.h b/tasmota/i18n.h index 31595fe93..bf6bbcb06 100644 --- a/tasmota/i18n.h +++ b/tasmota/i18n.h @@ -547,6 +547,7 @@ #define D_JSON_ZIGBEE_MODELID "ModelId" #define D_CMND_ZIGBEE_PROBE "Probe" #define D_CMND_ZIGBEE_FORGET "Forget" +#define D_CMND_ZIGBEE_INFO "Info" #define D_CMND_ZIGBEE_SAVE "Save" #define D_CMND_ZIGBEE_LINKQUALITY "LinkQuality" #define D_CMND_ZIGBEE_CLUSTER "Cluster" diff --git a/tasmota/my_user_config.h b/tasmota/my_user_config.h index 185614f89..742d24f6c 100644 --- a/tasmota/my_user_config.h +++ b/tasmota/my_user_config.h @@ -729,7 +729,8 @@ #define USE_ZIGBEE_COALESCE_ATTR_TIMER 350 // timer to coalesce attribute values (in 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_ZBBRIDGE_TLS // TLS support for zbbridge + #define USE_ZBBRIDGE_TLS // TLS support for zbbridge + #define USE_ZIGBEE_ZBBRIDGE_EEPROM 0x50 // I2C id for the ZBBridge EEPROM // -- Other sensors/drivers ----------------------- diff --git a/tasmota/xdrv_23_zigbee_1_headers.ino b/tasmota/xdrv_23_zigbee_1_headers.ino index c4283599e..8ed35fb4b 100644 --- a/tasmota/xdrv_23_zigbee_1_headers.ino +++ b/tasmota/xdrv_23_zigbee_1_headers.ino @@ -19,6 +19,10 @@ #ifdef USE_ZIGBEE +#ifdef USE_ZIGBEE_EZSP +#include "Eeprom24C512.h" +#endif // USE_ZIGBEE_EZSP + // contains some definitions for functions used before their declarations // @@ -69,7 +73,14 @@ const uint8_t ZIGBEE_LABEL_CONFIGURE_EZSP = 53; // main loop const uint8_t ZIGBEE_LABEL_ABORT = 99; // goto label 99 in case of fatal error const uint8_t ZIGBEE_LABEL_UNSUPPORTED_VERSION = 98; // Unsupported ZNP version -struct ZigbeeStatus { +class ZigbeeStatus { +public: + ZigbeeStatus() +#ifdef USE_ZIGBEE_EZSP + : eeprom(USE_ZIGBEE_ZBBRIDGE_EEPROM) +#endif // USE_ZIGBEE_EZSP + {} + bool active = true; // is Zigbee active for this device, i.e. GPIOs configured bool state_machine = false; // the state machine is running bool state_waiting = false; // the state machine is waiting for external event or timeout @@ -77,6 +88,7 @@ struct ZigbeeStatus { bool ready = false; // cc2530 initialization is complet, ready to operate bool init_phase = true; // initialization phase, before accepting zigbee traffic bool recv_until = false; // ignore all messages until the received frame fully matches + bool eeprom_present = false; // is the ZBBridge EEPROM present? uint8_t on_error_goto = ZIGBEE_LABEL_ABORT; // on error goto label, 99 default to abort uint8_t on_timeout_goto = ZIGBEE_LABEL_ABORT; // on timeout goto label, 99 default to abort @@ -89,6 +101,10 @@ struct ZigbeeStatus { ZB_RecvMsgFunc recv_unexpected = nullptr; // function called when unexpected message is received uint32_t permit_end_time = 0; // timestamp when permit join ends + +#ifdef USE_ZIGBEE_EZSP + Eeprom24C512 eeprom; // takes only 1 bytes of RAM +#endif // USE_ZIGBEE_EZSP }; struct ZigbeeStatus zigbee; SBuffer *zigbee_buffer = nullptr; diff --git a/tasmota/xdrv_23_zigbee_2_devices.ino b/tasmota/xdrv_23_zigbee_2_devices.ino index d4b2fdacb..a72c15232 100644 --- a/tasmota/xdrv_23_zigbee_2_devices.ino +++ b/tasmota/xdrv_23_zigbee_2_devices.ino @@ -53,7 +53,7 @@ const uint8_t Z_Data_Type_char[] PROGMEM = { '\0', // 0x0C '\0', // 0x0D '\0', // 0x0E - 'E', // 0x05 Z_Data_Type::Z_Ext + 'E', // 0x0F Z_Data_Type::Z_Ext // '_' maps to 0xFF Z_Data_Type::Z_Device }; @@ -63,12 +63,13 @@ class Z_Data_Set; \*********************************************************************************************/ class Z_Data { public: - Z_Data(Z_Data_Type type = Z_Data_Type::Z_Unknown, uint8_t endpoint = 0) : _type(type), _endpoint(endpoint), _config(0xF), _power(0) {} + Z_Data(Z_Data_Type type = Z_Data_Type::Z_Unknown, uint8_t endpoint = 0) : _type(type), _endpoint(endpoint), _config(0xF), _align_1(0), _reserved(0) {} inline Z_Data_Type getType(void) const { return _type; } inline int8_t getConfig(void) const { return _config; } inline bool validConfig(void) const { return _config != 0xF; } inline void setConfig(int8_t config) { _config = config; } uint8_t getConfigByte(void) const { return ( ((uint8_t)_type) << 4) | ((_config & 0xF) & 0x0F); } + uint8_t getReserved(void) const { return _reserved; } inline uint8_t getEndpoint(void) const { return _endpoint; } @@ -79,6 +80,7 @@ public: inline bool update(void) { return false; } static const Z_Data_Type type = Z_Data_Type::Z_Unknown; + static size_t DataTypeToLength(Z_Data_Type t); static bool ConfigToZData(const char * config_str, Z_Data_Type * type, uint8_t * ep, uint8_t * config); static Z_Data_Type CharToDataType(char c); @@ -87,9 +89,10 @@ public: friend class Z_Data_Set; protected: Z_Data_Type _type; // encoded on 4 bits, type of the device - uint8_t _endpoint; // source endpoint, or 0x00 if any endpoint + uint8_t _endpoint; // source endpoint, or 0x00 if any endpoint uint8_t _config : 4; // encoded on 4 bits, customize behavior - uint8_t _power; // power state if the type supports it + uint8_t _align_1 : 4; // force aligned to bytes, and fill with zero + uint8_t _reserved; // power state if the type supports it }; Z_Data_Type Z_Data::CharToDataType(char c) { @@ -153,28 +156,20 @@ bool Z_Data::ConfigToZData(const char * config_str, Z_Data_Type * type, uint8_t class Z_Data_OnOff : public Z_Data { public: Z_Data_OnOff(uint8_t endpoint = 0) : - Z_Data(Z_Data_Type::Z_OnOff, endpoint) - { - _config = 1; // at least 1 OnOff - } + Z_Data(Z_Data_Type::Z_OnOff, endpoint), + power(0xFF) + {} - inline bool validPower(uint32_t relay = 0) const { return (_config > relay); } // power is declared - inline bool getPower(uint32_t relay = 0) const { return bitRead(_power, relay); } - void setPower(bool val, uint32_t relay = 0); - - void toAttributes(Z_attribute_list & attr_list, Z_Data_Type type) const; + inline bool validPower(void) const { return 0xFF != power; } // power is declared + inline bool getPower(void) const { return validPower() ? (power != 0) : false; } // default to false if undefined + inline void setPower(bool val) { power = val ? 1 : 0; } static const Z_Data_Type type = Z_Data_Type::Z_OnOff; + + // 1 byte + uint8_t power; }; - -void Z_Data_OnOff::setPower(bool val, uint32_t relay) { - if (relay < 8) { - if (_config < relay) { _config = relay; } // we update the number of valid relays - bitWrite(_power, relay, val); - } -} - /*********************************************************************************************\ * Device specific: Plug device \*********************************************************************************************/ @@ -430,6 +425,37 @@ public: // 0x0226 Glass break sensor // 0x0229 Security repeater* }; + +const uint8_t Z_Data_Type_len[] PROGMEM = { + 0, // 0x00 Z_Data_Type::Z_Unknown + sizeof(Z_Data_Light), // 0x01 Z_Data_Type::Z_Light + sizeof(Z_Data_Plug), // 0x02 Z_Data_Type::Z_Plug + sizeof(Z_Data_PIR), // 0x03 Z_Data_Type::Z_PIR + sizeof(Z_Data_Alarm), // 0x04 Z_Data_Type::Z_Alarm + sizeof(Z_Data_Thermo), // 0x05 Z_Data_Type::Z_Thermo + sizeof(Z_Data_OnOff), // 0x05 Z_Data_Type::Z_OnOff + 0, // 0x06 + 0, // 0x07 + 0, // 0x08 + 0, // 0x09 + 0, // 0x0A + 0, // 0x0B + 0, // 0x0C + 0, // 0x0D + 0, // 0x0E + 0, // 0x0F Z_Data_Type::Z_Ext + // '_' maps to 0xFF Z_Data_Type::Z_Device +}; + +size_t Z_Data::DataTypeToLength(Z_Data_Type t) { + uint32_t tt = (uint32_t) t; + if (tt < ARRAY_SIZE(Z_Data_Type_len)) { + return pgm_read_byte(&Z_Data_Type_len[tt]); + } + return 0; +} + + /*********************************************************************************************\ * * Device specific Linked List @@ -453,7 +479,10 @@ public: template const M & find(uint8_t ep = 0) const; - // check if the point is null, if so create a new object with the right sub-class + // create a new data object from a 4 bytes buffer + Z_Data & createFromBuffer(const SBuffer & buf, uint32_t start, uint32_t len); + + // check if the pointer is null, if so create a new object with the right sub-class template M & addIfNull(M & cur, uint8_t ep = 0); }; @@ -489,6 +518,35 @@ Z_Data & Z_Data_Set::getByType(Z_Data_Type type, uint8_t ep) { } } +// Instanciate with either: +// (04)04010100 - without data except minimal Z_Data +// (08)04010100.B06DFFFF - with complete data - in this case must not exceed the structure len +// +// Byte 0: type +// Byte 1: endpoint +// Byte 2: config +// Byte 3: Power +Z_Data & Z_Data_Set::createFromBuffer(const SBuffer & buf, uint32_t start, uint32_t len) { + if (len < sizeof(Z_Data)) { + AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "Invalid len (<4) %d"), len); + return *(Z_Data*)nullptr; + } + + Z_Data_Type data_type = (Z_Data_Type) buf.get8(start); + uint8_t expected_len = Z_Data::DataTypeToLength(data_type); + uint8_t endpoint = buf.get8(start + 1); + + Z_Data & elt = getByType(data_type, endpoint); + if (&elt == nullptr) { return *(Z_Data*)nullptr; } + if (len <= expected_len) { + memcpy(&elt, buf.buf(start), len); + } else { + memcpy(&elt, buf.buf(start), sizeof(Z_Data)); + AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "buffer len overflow %d > %d"), len, expected_len); + } + return elt; +} + template M & Z_Data_Set::get(uint8_t ep) { M & m = (M&) find(M::type, ep); @@ -528,11 +586,6 @@ const Z_Data & Z_Data_Set::find(Z_Data_Type type, uint8_t ep) const { return *(Z_Data*)nullptr; } -void Z_Data_OnOff::toAttributes(Z_attribute_list & attr_list, Z_Data_Type type) const { - if (validPower()) { attr_list.addAttribute(PSTR("Power")).setUInt(getPower() ? 1 : 0); } -} - - /*********************************************************************************************\ * Structures for Rules variables related to the last received message \*********************************************************************************************/ @@ -561,11 +614,13 @@ public: // New version of device data handling Z_Data_Set data; // Linkedlist of device data per endpoint - // other status + // other status - device wide data is 8 bytes + // START OF DEVICE WIDE DATA + uint32_t last_seen; // Last seen time (epoch) uint8_t lqi; // lqi from last message, 0xFF means unknown uint8_t batterypercent; // battery percentage (0..100), 0xFF means unknwon - // power plug data- - uint32_t last_seen; // Last seen time (epoch) + uint16_t reserved_for_alignment; + // END OF DEVICE WIDE DATA // Constructor with all defaults Z_Device(uint16_t _shortaddr = BAD_SHORTADDR, uint64_t _longaddr = 0x00): @@ -736,7 +791,7 @@ public: // Dump json static String dumpLightState(const Z_Device & device); String dumpDevice(uint32_t dump_mode, const Z_Device & device) const; - static String dumpSingleDevice(uint32_t dump_mode, const Z_Device & device); + static String dumpSingleDevice(uint32_t dump_mode, const class Z_Device & device, bool add_device_name = true, bool add_brackets = true); int32_t deviceRestore(JsonParserObject json); // Hue support @@ -753,11 +808,13 @@ public: // Append or clear attributes Json structure void jsonAppend(uint16_t shortaddr, const Z_attribute_list &attr_list); + static void jsonPublishFlushAttrList(const Z_Device & device, const String & attr_list_string); void jsonPublishFlush(uint16_t shortaddr); // publish the json message and clear buffer bool jsonIsConflict(uint16_t shortaddr, const Z_attribute_list &attr_list) const; void jsonPublishNow(uint16_t shortaddr, Z_attribute_list &attr_list); // Iterator + inline const LList & getDevices(void) const { return _devices; } size_t devicesSize(void) const { return _devices.length(); } diff --git a/tasmota/xdrv_23_zigbee_2a_devices_impl.ino b/tasmota/xdrv_23_zigbee_2a_devices_impl.ino index 20abe848a..b6591be2b 100644 --- a/tasmota/xdrv_23_zigbee_2a_devices_impl.ino +++ b/tasmota/xdrv_23_zigbee_2a_devices_impl.ino @@ -492,64 +492,71 @@ void Z_Devices::jsonAppend(uint16_t shortaddr, const Z_attribute_list &attr_list device.attr_list.mergeList(attr_list); } +// +// internal function to publish device information with respect to all `SetOption`s +// +void Z_Devices::jsonPublishFlushAttrList(const Z_Device & device, const String & attr_list_string) { + const char * fname = zigbee_devices.getFriendlyName(device.shortaddr); + bool use_fname = (Settings.flag4.zigbee_use_names) && (fname); // should we replace shortaddr with friendlyname? + + TasmotaGlobal.mqtt_data[0] = 0; // clear string + // Do we prefix with `ZbReceived`? + if (!Settings.flag4.remove_zbreceived) { + Response_P(PSTR("{\"" D_JSON_ZIGBEE_RECEIVED "\":")); + } + // What key do we use, shortaddr or name? + if (use_fname) { + Response_P(PSTR("%s{\"%s\":{"), TasmotaGlobal.mqtt_data, fname); + } else { + Response_P(PSTR("%s{\"0x%04X\":{"), TasmotaGlobal.mqtt_data, device.shortaddr); + } + // Add "Device":"0x...." + ResponseAppend_P(PSTR("\"" D_JSON_ZIGBEE_DEVICE "\":\"0x%04X\","), device.shortaddr); + // Add "Name":"xxx" if name is present + if (fname) { + ResponseAppend_P(PSTR("\"" D_JSON_ZIGBEE_NAME "\":\"%s\","), EscapeJSONString(fname).c_str()); + } + // Add all other attributes + ResponseAppend_P(PSTR("%s}}"), attr_list_string.c_str()); + + if (!Settings.flag4.remove_zbreceived) { + ResponseAppend_P(PSTR("}")); + } + + if (Settings.flag4.zigbee_distinct_topics) { + if (Settings.flag4.zb_topic_fname && fname) { + //Clean special characters and check size of friendly name + char stemp[TOPSZ]; + strlcpy(stemp, (!strlen(fname)) ? MQTT_TOPIC : fname, sizeof(stemp)); + MakeValidMqtt(0, stemp); + //Create topic with Prefix3 and cleaned up friendly name + char frtopic[TOPSZ]; + snprintf_P(frtopic, sizeof(frtopic), PSTR("%s/%s/" D_RSLT_SENSOR), SettingsText(SET_MQTTPREFIX3), stemp); + MqttPublish(frtopic, Settings.flag.mqtt_sensor_retain); + } else { + char subtopic[16]; + snprintf_P(subtopic, sizeof(subtopic), PSTR("%04X/" D_RSLT_SENSOR), device.shortaddr); + MqttPublishPrefixTopic_P(TELE, subtopic, Settings.flag.mqtt_sensor_retain); + } + } else { + MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR), Settings.flag.mqtt_sensor_retain); + } + XdrvRulesProcess(); // apply rules +} + void Z_Devices::jsonPublishFlush(uint16_t shortaddr) { Z_Device & device = getShortAddr(shortaddr); if (!device.valid()) { return; } // safeguard Z_attribute_list &attr_list = device.attr_list; if (!attr_list.isEmpty()) { - const char * fname = zigbee_devices.getFriendlyName(shortaddr); - bool use_fname = (Settings.flag4.zigbee_use_names) && (fname); // should we replace shortaddr with friendlyname? - // save parameters is global variables to be used by Rules gZbLastMessage.device = shortaddr; // %zbdevice% gZbLastMessage.groupaddr = attr_list.group_id; // %zbgroup% gZbLastMessage.endpoint = attr_list.src_ep; // %zbendpoint% - TasmotaGlobal.mqtt_data[0] = 0; // clear string - // Do we prefix with `ZbReceived`? - if (!Settings.flag4.remove_zbreceived) { - Response_P(PSTR("{\"" D_JSON_ZIGBEE_RECEIVED "\":")); - } - // What key do we use, shortaddr or name? - if (use_fname) { - Response_P(PSTR("%s{\"%s\":{"), TasmotaGlobal.mqtt_data, fname); - } else { - Response_P(PSTR("%s{\"0x%04X\":{"), TasmotaGlobal.mqtt_data, shortaddr); - } - // Add "Device":"0x...." - ResponseAppend_P(PSTR("\"" D_JSON_ZIGBEE_DEVICE "\":\"0x%04X\","), shortaddr); - // Add "Name":"xxx" if name is present - if (fname) { - ResponseAppend_P(PSTR("\"" D_JSON_ZIGBEE_NAME "\":\"%s\","), EscapeJSONString(fname).c_str()); - } - // Add all other attributes - ResponseAppend_P(PSTR("%s}}"), attr_list.toString().c_str()); - - if (!Settings.flag4.remove_zbreceived) { - ResponseAppend_P(PSTR("}")); - } + jsonPublishFlushAttrList(device, attr_list.toString()); attr_list.reset(); // clear the attributes - - if (Settings.flag4.zigbee_distinct_topics) { - if (Settings.flag4.zb_topic_fname && fname) { - //Clean special characters and check size of friendly name - char stemp[TOPSZ]; - strlcpy(stemp, (!strlen(fname)) ? MQTT_TOPIC : fname, sizeof(stemp)); - MakeValidMqtt(0, stemp); - //Create topic with Prefix3 and cleaned up friendly name - char frtopic[TOPSZ]; - snprintf_P(frtopic, sizeof(frtopic), PSTR("%s/%s/" D_RSLT_SENSOR), SettingsText(SET_MQTTPREFIX3), stemp); - MqttPublish(frtopic, Settings.flag.mqtt_sensor_retain); - } else { - char subtopic[16]; - snprintf_P(subtopic, sizeof(subtopic), PSTR("%04X/" D_RSLT_SENSOR), shortaddr); - MqttPublishPrefixTopic_P(TELE, subtopic, Settings.flag.mqtt_sensor_retain); - } - } else { - MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR), Settings.flag.mqtt_sensor_retain); - } - XdrvRulesProcess(); // apply rules } } @@ -649,20 +656,24 @@ String Z_Devices::dumpLightState(const Z_Device & device) { // Dump the internal memory of Zigbee devices // Mode = 1: simple dump of devices addresses // Mode = 2: simple dump of devices addresses and names, endpoints, light -String Z_Devices::dumpSingleDevice(uint32_t dump_mode, const class Z_Device & device) { +// Mode = 3: dump last known data attributes +// add_device_name : do we add shortaddr/name ? +String Z_Devices::dumpSingleDevice(uint32_t dump_mode, const class Z_Device & device, bool add_device_name, bool add_brackets) { uint16_t shortaddr = device.shortaddr; char hex[22]; Z_attribute_list attr_list; - snprintf_P(hex, sizeof(hex), PSTR("0x%04X"), shortaddr); - attr_list.addAttribute(F(D_JSON_ZIGBEE_DEVICE)).setStr(hex); + if (add_device_name) { + snprintf_P(hex, sizeof(hex), PSTR("0x%04X"), shortaddr); + attr_list.addAttribute(F(D_JSON_ZIGBEE_DEVICE)).setStr(hex); - if (device.friendlyName > 0) { - attr_list.addAttribute(F(D_JSON_ZIGBEE_NAME)).setStr(device.friendlyName); + if (device.friendlyName > 0) { + attr_list.addAttribute(F(D_JSON_ZIGBEE_NAME)).setStr(device.friendlyName); + } } - if (2 <= dump_mode) { + if (dump_mode >= 2) { hex[0] = '0'; // prefix with '0x' hex[1] = 'x'; Uint64toHex(device.longaddr, &hex[2], 64); @@ -695,7 +706,17 @@ String Z_Devices::dumpSingleDevice(uint32_t dump_mode, const class Z_Device & de } attr_list.addAttribute(F("Config")).setStrRaw(arr_data.toString().c_str()); } - return attr_list.toString(true); + if (dump_mode >= 3) { + // show internal data - mostly last known values + for (auto & data_elt : device.data) { + Z_Data_Type data_type = data_elt.getType(); + + data_elt.toAttributes(attr_list, data_type); + } + // add device wide attributes + device.toAttributes(attr_list); + } + return attr_list.toString(add_brackets); } // If &device == nullptr, then dump all @@ -796,9 +817,14 @@ Z_Data_Light & Z_Devices::getLight(uint16_t shortaddr) { * Export device specific attributes to ZbData \*********************************************************************************************/ void Z_Device::toAttributes(Z_attribute_list & attr_list) const { - if (validLqi()) { attr_list.addAttribute(PSTR(D_CMND_ZIGBEE_LINKQUALITY)).setUInt(lqi); } if (validBatteryPercent()) { attr_list.addAttribute(PSTR("BatteryPercentage")).setUInt(batterypercent); } - if (validLastSeen()) { attr_list.addAttribute(PSTR("LastSeen")).setUInt(last_seen); } + if (validLastSeen()) { + if (Rtc.utc_time >= last_seen) { + attr_list.addAttribute(PSTR("LastSeen")).setUInt(Rtc.utc_time - last_seen); + } + attr_list.addAttribute(PSTR("LastSeenEpoch")).setUInt(last_seen); + } + if (validLqi()) { attr_list.addAttribute(PSTR(D_CMND_ZIGBEE_LINKQUALITY)).setUInt(lqi); } } /*********************************************************************************************\ diff --git a/tasmota/xdrv_23_zigbee_4_persistence.ino b/tasmota/xdrv_23_zigbee_4_persistence.ino index 9a8a095c6..ecaf150f9 100644 --- a/tasmota/xdrv_23_zigbee_4_persistence.ino +++ b/tasmota/xdrv_23_zigbee_4_persistence.ino @@ -194,14 +194,6 @@ class SBuffer hibernateDevices(void) { AddLog_P(LOG_LEVEL_ERROR, PSTR(D_LOG_ZIGBEE "Devices list too big to fit in Flash (%d)"), buf_len); } - // Log - char *hex_char = (char*) malloc((buf_len * 2) + 2); - if (hex_char) { - AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_ZIGBEE "ZbFlashStore %s"), - ToHex_P(buf.getBuffer(), buf_len, hex_char, (buf_len * 2) + 2)); - free(hex_char); - } - return buf; } @@ -301,8 +293,8 @@ void hydrateDevices(const SBuffer &buf, uint32_t version) { } } - -void loadZigbeeDevices(void) { +// dump = true, only dump to logs, don't actually load +void loadZigbeeDevices(bool dump_only = false) { #ifdef ESP32 // first copy SPI buffer into ram uint8_t *spi_buffer = (uint8_t*) malloc(z_spi_len); @@ -316,7 +308,7 @@ void loadZigbeeDevices(void) { Z_Flashentry flashdata; memcpy_P(&flashdata, z_dev_start, sizeof(Z_Flashentry)); // AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_ZIGBEE "Memory %d"), ESP_getFreeHeap()); - AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_ZIGBEE "Zigbee signature in Flash: %08X - %d"), flashdata.name, flashdata.len); + // AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_ZIGBEE "Zigbee signature in Flash: %08X - %d"), flashdata.name, flashdata.len); // Check the signature if ( ((flashdata.name == ZIGB_NAME1) || (flashdata.name == ZIGB_NAME2)) @@ -327,15 +319,21 @@ void loadZigbeeDevices(void) { SBuffer buf(buf_len); buf.addBuffer(z_dev_start + sizeof(Z_Flashentry), buf_len); AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "Zigbee devices data in Flash v%d (%d bytes)"), version, buf_len); - // Serial.printf(">> Buffer="); - // for (uint32_t i=0; i 192) { buf_len = 192; } + AddLogBuffer(LOG_LEVEL_INFO, buf.getBuffer(), buf_len); + // Serial.printf(">> Buffer="); + // for (uint32_t i=0; i. +*/ + +#ifdef USE_ZIGBEE + + +// ======================= +// ZbData v1 +// File structure: +// +// uint8 - number of devices, 0=none, 0xFF=invalid entry (probably Flash was erased) +// +// [Array of devices] +// [Offset = 2] +// uint8 - length of device record (excluding the length byte) +// uint16 - short address +// +// [Device specific data first] +// uint8 - length of structure (excluding the length byte) +// uint8[] - device wide data +// +// [Array of data structures] +// uint8 - length of structure +// uint8[] - list of data +// + +void dumpZigbeeDevicesData(void) { +#ifdef USE_ZIGBEE_EZSP + if (zigbee.eeprom_present) { + SBuffer buf(192); + + zigbee.eeprom.readBytes(64, 192, buf.getBuffer()); + AddLogBuffer(LOG_LEVEL_INFO, buf.getBuffer(), 192); + } +#endif // USE_ZIGBEE_EZSP +} + +// returns the lenght of consumed buffer, or -1 if error +int32_t hydrateDeviceWideData(class Z_Device & device, const SBuffer & buf, size_t start, size_t len) { + size_t segment_len = buf.get8(start); + if ((segment_len < 6) || (segment_len > len)) { + AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "invalid device wide data length=%d"), segment_len); + return -1; + } + device.last_seen = buf.get32(start+1); + device.lqi = buf.get8(start + 5); + device.batterypercent = buf.get8(start + 6); + return segment_len + 1; +} + +// return true if success +bool hydrateDeviceData(class Z_Device & device, const SBuffer & buf, size_t start, size_t len) { + // First hydrate device wide data + int32_t ret = hydrateDeviceWideData(device, buf, start, len); + if (ret < 0) { return false; } + + size_t offset = 0 + ret; + while (offset + 5 <= len) { // each entry is at least 5 bytes + uint8_t data_len = buf.get8(start + offset); + Z_Data & data_elt = device.data.createFromBuffer(buf, offset + 1, data_len); + offset += data_len + 1; + } + return true; +} + +// negative means error +// positive is the segment length +int32_t hydrateSingleDevice(const class SBuffer & buf, size_t start, size_t len) { + uint8_t segment_len = buf.get8(start); + if ((segment_len < 4) || (start + segment_len > len)) { + AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "invalid segment_len=%d"), segment_len); + return -1; + } + // read shortaddr + uint16_t shortaddr = buf.get16(start + 1); + if (shortaddr >= 0xFFF0) { + AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "invalid shortaddr=0x%04X"), shortaddr); + return -1; + } + + // check if the device exists, if not skip the record + Z_Device & device = zigbee_devices.findShortAddr(shortaddr); + if (&device != nullptr) { + + // parse the rest + bool ret = hydrateDeviceData(device, buf, start + 3, segment_len - 3); + + if (!ret) { return -1; } + } + return segment_len + 1; +} + +// Parse the entire blob +// return true if ok +bool hydrateDevicesDataBlob(const class SBuffer & buf, size_t start, size_t len) { + // read number of devices + uint8_t num_devices = buf.get8(start); + if (num_devices > 0x80) { + AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "wrong number of devices=%d"), num_devices); + return false; + } + + size_t offset = 0; + for (uint32_t cur_dev_num = 0; (cur_dev_num < num_devices) && (offset + 4 <= len); cur_dev_num++) { + int32_t segment_len = hydrateSingleDevice(buf, offset, len); + + // advance buffer + if (segment_len <= 0) { return false; } + offset += segment_len; + } +} + +class SBuffer hibernateDeviceData(const struct Z_Device & device, bool log = false) { + SBuffer buf(192); + + // If we have zero information about the device, just skip ir + if (device.validLqi() || + device.validBatteryPercent() || + device.validLastSeen() || + !device.data.isEmpty()) { + + buf.add8(0x00); // overall length, will be updated later + buf.add16(device.shortaddr); + + // device wide data + buf.add8(6); // 6 bytes + buf.add32(device.last_seen); + buf.add8(device.lqi); + buf.add8(device.batterypercent); + + for (const auto & data_elt : device.data) { + size_t item_len = data_elt.DataTypeToLength(data_elt.getType()); + buf.add8(item_len); // place-holder for length + buf.addBuffer((uint8_t*) &data_elt, item_len); + } + + // update overall length + buf.set8(0, buf.len() - 1); + + if (log) { + size_t buf_len = buf.len() - 3; + char hex[2*buf_len + 1]; + // skip first 3 bytes + ToHex_P(buf.buf(3), buf_len, hex, sizeof(hex)); + + Response_P(PSTR("{\"" D_PRFX_ZB D_CMND_ZIGBEE_DATA "\":\"ZbData 0x%04X,%s\"}"), device.shortaddr, hex); + MqttPublishPrefixTopicRulesProcess_P(RESULT_OR_STAT, PSTR(D_PRFX_ZB D_CMND_ZIGBEE_DATA)); + } + } + + return buf; +} + +void hibernateAllData(void) { + + // first prefix is number of devices + uint8_t device_num = zigbee_devices.devicesSize(); + + for (const auto & device : zigbee_devices.getDevices()) { + // allocte a buffer for a single device + SBuffer buf = hibernateDeviceData(device, true); // log + if (buf.len() > 0) { + // TODO store in EEPROM + } + } + +} + +#endif // USE_ZIGBEE diff --git a/tasmota/xdrv_23_zigbee_5_converters.ino b/tasmota/xdrv_23_zigbee_5_converters.ino index bf3cc46c8..b736cb544 100644 --- a/tasmota/xdrv_23_zigbee_5_converters.ino +++ b/tasmota/xdrv_23_zigbee_5_converters.ino @@ -231,7 +231,7 @@ const Z_AttributeConverter Z_PostProcess[] PROGMEM = { //{ Zmap8, Cx0005, 0x0004, (NameSupport), Cm1, 0 }, // On/off cluster - { Zbool, Cx0006, 0x0000, Z_(Power), Cm1, 0 }, + { Zbool, Cx0006, 0x0000, Z_(Power), Cm1 + Z_EXPORT_DATA, Z_MAPPING(Z_Data_OnOff, power) }, { Zenum8, Cx0006, 0x4003, Z_(StartUpOnOff), Cm1, 0 }, { Zbool, Cx0006, 0x8000, Z_(Power), Cm1, 0 }, // See 7280 @@ -558,8 +558,8 @@ const Z_AttributeConverter Z_PostProcess[] PROGMEM = { // IAS Cluster (Intruder Alarm System) { Zenum8, Cx0500, 0x0000, Z_(ZoneState), Cm1, 0 }, // Occupancy (map8) - { Zenum16, Cx0500, 0x0001, Z_(ZoneType), Cm1, Z_MAPPING(Z_Data_Alarm, zone_type) }, // Zone type for sensor - { Zmap16, Cx0500, 0x0002, Z_(ZoneStatus), Cm1, Z_MAPPING(Z_Data_Alarm, zone_status) }, // Zone status for sensor + { Zenum16, Cx0500, 0x0001, Z_(ZoneType), Cm1 + Z_EXPORT_DATA, Z_MAPPING(Z_Data_Alarm, zone_type) }, // Zone type for sensor + { Zmap16, Cx0500, 0x0002, Z_(ZoneStatus), Cm1 + Z_EXPORT_DATA, Z_MAPPING(Z_Data_Alarm, zone_status) }, // Zone status for sensor { Zbool, Cx0500, 0xFFF0, Z_(Contact), Cm1, Z_MAPPING(Z_Data_Alarm, zone_status) }, // We fit the first bit in the LSB // Metering (Smart Energy) cluster @@ -1844,6 +1844,7 @@ void Z_postProcessAttributes(uint16_t shortaddr, uint16_t src_ep, class Z_attrib switch (zigbee_type) { case Zenum8: case Zmap8: + case Zbool: case Zuint8: *(uint8_t*)attr_address = uval32; break; case Zenum16: case Zmap16: @@ -1975,6 +1976,7 @@ void Z_Data::toAttributes(Z_attribute_list & attr_list, Z_Data_Type type) const const Z_AttributeConverter *converter = &Z_PostProcess[i]; uint8_t conv_export = pgm_read_byte(&converter->multiplier_idx) & Z_EXPORT_DATA; uint8_t conv_mapping = pgm_read_byte(&converter->mapping); + int8_t multiplier = CmToMultiplier(pgm_read_byte(&converter->multiplier_idx)); Z_Data_Type map_type = (Z_Data_Type) ((conv_mapping & 0xF0)>>4); uint8_t map_offset = (conv_mapping & 0x0F); @@ -1982,7 +1984,7 @@ void Z_Data::toAttributes(Z_attribute_list & attr_list, Z_Data_Type type) const // we need to export this attribute const char * conv_name = Z_strings + pgm_read_word(&converter->name_offset); uint8_t zigbee_type = pgm_read_byte(&converter->type); // zigbee type to select right size 8/16/32 bits - uint8_t *attr_address = ((uint8_t*)this) + sizeof(Z_Data) + map_offset; // address of attribute in memory + uint8_t * attr_address = ((uint8_t*)this) + sizeof(Z_Data) + map_offset; // address of attribute in memory int32_t data_size = 0; int32_t ival32; @@ -1990,6 +1992,7 @@ void Z_Data::toAttributes(Z_attribute_list & attr_list, Z_Data_Type type) const switch (zigbee_type) { case Zenum8: case Zmap8: + case Zbool: case Zuint8: uval32 = *(uint8_t*)attr_address; if (uval32 != 0xFF) data_size = 8; break; case Zmap16: case Zenum16: @@ -2002,8 +2005,12 @@ void Z_Data::toAttributes(Z_attribute_list & attr_list, Z_Data_Type type) const if (data_size != 0) { Z_attribute & attr = attr_list.addAttribute(conv_name); - if (data_size > 0) { attr.setUInt(uval32); } - else { attr.setInt(ival32); } + float fval = (data_size > 0) ? uval32 : ival32; + if ((1 != multiplier) && (0 != multiplier)) { + if (multiplier > 0) { fval = fval * multiplier; } + else { fval = fval / (-multiplier); } + } + attr.setFloat(fval); } } } diff --git a/tasmota/xdrv_23_zigbee_A_impl.ino b/tasmota/xdrv_23_zigbee_A_impl.ino index c6b12a1f5..ea6869778 100644 --- a/tasmota/xdrv_23_zigbee_A_impl.ino +++ b/tasmota/xdrv_23_zigbee_A_impl.ino @@ -30,7 +30,7 @@ const char kZbCommands[] PROGMEM = D_PRFX_ZB "|" // prefix #endif // USE_ZIGBEE_EZSP D_CMND_ZIGBEE_PERMITJOIN "|" 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_INFO "|" 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_OCCUPANCY "|" D_CMND_ZIGBEE_RESTORE "|" D_CMND_ZIGBEE_BIND_STATE "|" D_CMND_ZIGBEE_MAP "|" @@ -46,7 +46,7 @@ void (* const ZigbeeCommand[])(void) PROGMEM = { #endif // USE_ZIGBEE_EZSP &CmndZbPermitJoin, &CmndZbStatus, &CmndZbReset, &CmndZbSend, &CmndZbProbe, - &CmndZbForget, &CmndZbSave, &CmndZbName, + &CmndZbInfo, &CmndZbForget, &CmndZbSave, &CmndZbName, &CmndZbBind, &CmndZbUnbind, &CmndZbPing, &CmndZbModelId, &CmndZbLight, &CmndZbOccupancy, &CmndZbRestore, &CmndZbBindState, &CmndZbMap, @@ -94,6 +94,16 @@ void ZigbeeInit(void) #endif SettingsSave(2); } + +#ifdef USE_ZIGBEE_EZSP + // Check the I2C EEprom + Wire.beginTransmission(USE_ZIGBEE_ZBBRIDGE_EEPROM); + uint8_t error = Wire.endTransmission(); + if (0 == error) { + AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "ZBBridge EEPROM found at address 0x%02X"), USE_ZIGBEE_ZBBRIDGE_EEPROM); + zigbee.eeprom_present = true; + } +#endif } // update commands with the current settings @@ -1175,13 +1185,43 @@ void CmndZbForget(void) { } } +// +// Command `ZbInfo` +// Display all information known about a device, this equivalent to `2bStatus3` with a simpler JSON output +// +void CmndZbInfo(void) { + if (zigbee.init_phase) { ResponseCmndChar_P(PSTR(D_ZIGBEE_NOT_STARTED)); return; } + Z_Device & device = zigbee_devices.parseDeviceFromName(XdrvMailbox.data, true); // in case of short_addr, it must be already registered + if (!device.valid()) { ResponseCmndChar_P(PSTR("Unknown device")); return; } + + // everything is good, we can send the command + + String device_info = Z_Devices::dumpSingleDevice(3, device, false, false); + Z_Devices::jsonPublishFlushAttrList(device, device_info); + + ResponseCmndDone(); +} + // // Command `ZbSave` // Save Zigbee information to flash // void CmndZbSave(void) { if (zigbee.init_phase) { ResponseCmndChar_P(PSTR(D_ZIGBEE_NOT_STARTED)); return; } - saveZigbeeDevices(); + switch (XdrvMailbox.payload) { + case 2: // save only data + hibernateAllData(); + break; + case -1: // dump configuration + loadZigbeeDevices(true); // dump only + break; + case -2: // dump data + dumpZigbeeDevicesData(); + break; + default: + saveZigbeeDevices(); + break; + } ResponseCmndDone(); } @@ -1378,196 +1418,32 @@ void CmndZbStatus(void) { } } -// -// Innder part of ZbData parsing -// -// {"L02":{"Dimmer":10,"Sat":254}} -bool parseDeviceInnerData(class Z_Device & device, JsonParserObject root) { - for (auto data_elt : root) { - // Parse key in format "L02":.... - const char * data_type_str = data_elt.getStr(); - Z_Data_Type data_type; - uint8_t endpoint; - uint8_t config = 0xFF; // unspecified - - // parse key in the form "L01.5" - if (!Z_Data::ConfigToZData(data_type_str, &data_type, &endpoint, &config)) { data_type = Z_Data_Type::Z_Unknown; } - - if (data_type == Z_Data_Type::Z_Unknown) { - Response_P(PSTR("{\"%s\":\"%s \"%s\"\"}"), XdrvMailbox.command, PSTR("Invalid Parameters"), data_type_str); - return false; - } - - JsonParserObject data_values = data_elt.getValue().getObject(); - if (!data_values) { return false; } - - JsonParserToken val; - if (data_type == Z_Data_Type::Z_Device) { - if (val = data_values[PSTR(D_CMND_ZIGBEE_LINKQUALITY)]) { device.lqi = val.getUInt(); } - if (val = data_values[PSTR("BatteryPercentage")]) { device.batterypercent = val.getUInt(); } - 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 - if (&data != nullptr) { - if (config != 0xFF) { - data.setConfig(config); - } - - for (auto attr : data_values) { - JsonParserToken attr_value = attr.getValue(); - uint8_t conv_zigbee_type; - Z_Data_Type conv_data_type; - uint8_t conv_map_offset; - if (zigbeeFindAttributeByName(attr.getStr(), nullptr, nullptr, nullptr, &conv_zigbee_type, &conv_data_type, &conv_map_offset) != nullptr) { - // found an attribute matching the name, does is fit the type? - if (conv_data_type == data_type) { - // we got a match. Bear in mind that a zero value is not a valid 'data_type' - - uint8_t *attr_address = ((uint8_t*)&data) + sizeof(Z_Data) + conv_map_offset; - uint32_t uval32 = attr_value.getUInt(); // call converter to uint only once - int32_t ival32 = attr_value.getInt(); // call converter to int only once - switch (conv_zigbee_type) { - case Zenum8: - case Zuint8: *(uint8_t*)attr_address = uval32; break; - case Zenum16: - case Zuint16: *(uint16_t*)attr_address = uval32; break; - case Zuint32: *(uint32_t*)attr_address = uval32; break; - case Zint8: *(int8_t*)attr_address = ival32; break; - case Zint16: *(int16_t*)attr_address = ival32; break; - case Zint32: *(int32_t*)attr_address = ival32; break; - } - } else if (conv_data_type != Z_Data_Type::Z_Unknown) { - AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_ZIGBEE "attribute %s is wrong type %d (expected %d)"), attr.getStr(), (uint8_t)data_type, (uint8_t)conv_data_type); - } - } - } - } - - // Import specific attributes that are not handled with the generic method - switch (data_type) { - // case Z_Data_Type::Z_Plug: - // { - // Z_Data_Plug & plug = (Z_Data_Plug&) data; - // } - // break; - // case Z_Data_Type::Z_Light: - // { - // Z_Data_Light & light = (Z_Data_Light&) data; - // } - // break; - case Z_Data_Type::Z_OnOff: - { - Z_Data_OnOff & onoff = (Z_Data_OnOff&) data; - - if (val = data_values[PSTR("Power")]) { onoff.setPower(val.getUInt() ? true : false); } - } - break; - // case Z_Data_Type::Z_Thermo: - // { - // Z_Data_Thermo & thermo = (Z_Data_Thermo&) data; - // } - // break; - // case Z_Data_Type::Z_Alarm: - // { - // Z_Data_Alarm & alarm = (Z_Data_Alarm&) data; - // } - // break; - } - } - } - return true; -} - // // Command `ZbData` // void CmndZbData(void) { if (zigbee.init_phase) { ResponseCmndChar_P(PSTR(D_ZIGBEE_NOT_STARTED)); return; } - RemoveSpace(XdrvMailbox.data); - if (XdrvMailbox.data[0] == '{') { - // JSON input, enter saved data into memory -- essentially for debugging - JsonParser parser(XdrvMailbox.data); - JsonParserObject root = parser.getRootObject(); - if (!root) { ResponseCmndChar_P(PSTR(D_JSON_INVALID_JSON)); return; } - // Skip `ZbData` if present - JsonParserToken zbdata = root.getObject().findStartsWith(PSTR("ZbData")); - if (zbdata) { - root = zbdata; - } + // check if parameters contain a comma ',' + char *p; + char *str = strtok_r(XdrvMailbox.data, ",", &p); - for (auto device_name : root) { - Z_Device & device = zigbee_devices.parseDeviceFromName(device_name.getStr(), true); - if (!device.valid()) { ResponseCmndChar_P(PSTR("Unknown device")); return; } - JsonParserObject inner_data = device_name.getValue().getObject(); - if (inner_data) { - if (!parseDeviceInnerData(device, inner_data)) { - return; - } - } - } - zigbee_devices.dirty(); // save to flash - ResponseCmndDone(); + // parse first part, + Z_Device & device = zigbee_devices.parseDeviceFromName(XdrvMailbox.data, true); // in case of short_addr, it must be already registered + if (!device.valid()) { ResponseCmndChar_P(PSTR("Unknown device")); return; } + + if (p) { + // set ZbData + const SBuffer buf = SBuffer::SBufferFromHex(p, strlen(p)); + hydrateDeviceData(device, buf, 0, buf.len()); } else { // non-JSON, export current data // ZbData 0x1234 // ZbData Device_Name - Z_Device & device = zigbee_devices.parseDeviceFromName(XdrvMailbox.data, true); - if (!device.valid()) { ResponseCmndChar_P(PSTR("Unknown device")); return; } - - Z_attribute_list attr_data; - - { // scope to force object deallocation - Z_attribute_list device_attr; - device.toAttributes(device_attr); - attr_data.addAttribute(F("_")).setStrRaw(device_attr.toString(true).c_str()); - } - - // Iterate on data objects - for (auto & data_elt : device.data) { - Z_attribute_list inner_attr; - char key[8]; - if (data_elt.validConfig()) { - snprintf_P(key, sizeof(key), "?%02X.%1X", data_elt.getEndpoint(), data_elt.getConfig()); - } else { - snprintf_P(key, sizeof(key), "?%02X", data_elt.getEndpoint()); - } - - Z_Data_Type data_type = data_elt.getType(); - key[0] = Z_Data::DataTypeToChar(data_type); - switch (data_type) { - case Z_Data_Type::Z_Plug: - ((Z_Data_Plug&)data_elt).toAttributes(inner_attr, data_type); - break; - case Z_Data_Type::Z_Light: - ((Z_Data_Light&)data_elt).toAttributes(inner_attr, data_type); - break; - case Z_Data_Type::Z_OnOff: - ((Z_Data_OnOff&)data_elt).toAttributes(inner_attr, data_type); - break; - case Z_Data_Type::Z_Thermo: - ((Z_Data_Thermo&)data_elt).toAttributes(inner_attr, data_type); - break; - case Z_Data_Type::Z_Alarm: - ((Z_Data_Alarm&)data_elt).toAttributes(inner_attr, data_type); - break; - case Z_Data_Type::Z_PIR: - ((Z_Data_PIR&)data_elt).toAttributes(inner_attr, data_type); - break; - } - if ((key[0] != '\0') && (key[0] != '?')) { - attr_data.addAttribute(key).setStrRaw(inner_attr.toString(true).c_str()); - } - } - - char hex[8]; - snprintf_P(hex, sizeof(hex), PSTR("0x%04X"), device.shortaddr); - Response_P(PSTR("{\"%s\":{\"%s\":%s}}"), XdrvMailbox.command, hex, attr_data.toString(true).c_str()); + hibernateDeviceData(device, true); // log } + + ResponseCmndDone(); } // @@ -1652,6 +1528,8 @@ void CmndZbConfig(void) { \*********************************************************************************************/ extern "C" { + // comparator function used to sort Zigbee devices by alphabetical order (if friendlyname) + // then by shortaddr if they don't have friendlyname int device_cmp(const void * a, const void * b) { const Z_Device &dev_a = zigbee_devices.devicesAt(*(uint8_t*)a); const Z_Device &dev_b = zigbee_devices.devicesAt(*(uint8_t*)b); @@ -1664,7 +1542,7 @@ extern "C" { return (int32_t)dev_a.shortaddr - (int32_t)dev_b.shortaddr; } else { if (fn_a) return -1; - return 1; + else return 1; } } @@ -1829,15 +1707,16 @@ void ZigbeeShow(bool json) // Light, switches and plugs const Z_Data_OnOff & onoff = device.data.find(); + bool onoff_display = (&onoff != nullptr) ? onoff.validPower() : false; const Z_Data_Light & light = device.data.find(); bool light_display = (&light != nullptr) ? light.validDimmer() : false; const Z_Data_Plug & plug = device.data.find(); - if ((&onoff != nullptr) || light_display || (&plug != nullptr)) { + if (onoff_display || light_display || (&plug != nullptr)) { int8_t channels = device.getLightChannels(); if (channels < 0) { channels = 5; } // if number of channel is unknown, display all known attributes WSContentSend_P(PSTR("┆")); - if (&onoff != nullptr) { - WSContentSend_P(PSTR(" %s"), device.getPower() ? PSTR(D_ON) : PSTR(D_OFF)); + if (onoff_display) { + WSContentSend_P(PSTR(" %s"), onoff.getPower() ? PSTR(D_ON) : PSTR(D_OFF)); } if (&light != nullptr) { if (light.validDimmer() && (channels >= 1)) {