From afb5839b6cd9555bf77daf83be27d4bb1e5814f2 Mon Sep 17 00:00:00 2001 From: Stephan Hadinger Date: Wed, 28 Oct 2020 10:08:15 +0100 Subject: [PATCH] Zigbee flash storage refactor --- tasmota/support_static_buffer.ino | 12 +- tasmota/xdrv_23_zigbee_1z_libs.ino | 50 +--- tasmota/xdrv_23_zigbee_2_devices.ino | 99 ++++++-- tasmota/xdrv_23_zigbee_2a_devices_impl.ino | 78 ++++-- tasmota/xdrv_23_zigbee_4_persistence.ino | 262 +++++++++++++-------- tasmota/xdrv_23_zigbee_5_converters.ino | 4 +- tasmota/xdrv_23_zigbee_6_commands.ino | 4 +- tasmota/xdrv_23_zigbee_A_impl.ino | 190 +++++++-------- 8 files changed, 407 insertions(+), 292 deletions(-) diff --git a/tasmota/support_static_buffer.ino b/tasmota/support_static_buffer.ino index 9f4a8456b..5fa0e603c 100644 --- a/tasmota/support_static_buffer.ino +++ b/tasmota/support_static_buffer.ino @@ -194,14 +194,10 @@ public: return 0; } - // if no NULL is found, returns length until the end of the buffer - inline size_t strlen(const size_t offset) const { - return strnlen((const char*) &_buf->buf[offset], len() - offset); - } - - size_t strlen_s(const size_t offset) const { - size_t slen = this->strlen(offset); - if (slen == len() - offset) { + size_t strlen(const size_t offset) const { + if (offset >= len()) { return 0; } + size_t slen = strnlen((const char*) &_buf->buf[offset], len() - offset); + if (slen == (len() - offset)) { return 0; // we didn't find a NULL char } else { return slen; diff --git a/tasmota/xdrv_23_zigbee_1z_libs.ino b/tasmota/xdrv_23_zigbee_1z_libs.ino index 8b64baa5b..cfcb76990 100644 --- a/tasmota/xdrv_23_zigbee_1z_libs.ino +++ b/tasmota/xdrv_23_zigbee_1z_libs.ino @@ -64,46 +64,6 @@ uint16_t Z_GetLastGroup(void) { return gZbLastMessage.groupaddr; } uint16_t Z_GetLastCluster(void) { return gZbLastMessage.cluster; } uint8_t Z_GetLastEndpoint(void) { return gZbLastMessage.endpoint; } -/*********************************************************************************************\ - * - * Class for attribute array of values - * This is a helper function to generate a clean list of unsigned ints - * -\*********************************************************************************************/ - -class Z_json_array { -public: - - Z_json_array(): val("[]") {} // start with empty array - void add(uint32_t uval32) { - // remove trailing ']' - val.remove(val.length()-1); - if (val.length() > 1) { // if not empty, prefix with comma - val += ','; - } - val += uval32; - val += ']'; - } - void addStrRaw(const char * sval) { - // remove trailing ']' - val.remove(val.length()-1); - if (val.length() > 1) { // if not empty, prefix with comma - val += ','; - } - val += sval; - val += ']'; - } - void addStr(const char * sval) { - addStrRaw(EscapeJSONString(sval).c_str()); - } - String &toString(void) { - return val; - } - -private : - String val; -}; - /*********************************************************************************************\ * * Class for single attribute @@ -143,8 +103,8 @@ public: float fval; SBuffer* bval; char* sval; - class Z_attribute_list * objval; - class Z_json_array * arrval; + class Z_attribute_list * objval; + class JsonGeneratorArray * arrval; } val; Za_type type; // uint8_t in size, type of attribute, see above bool key_is_str; // is the key a string? @@ -217,7 +177,7 @@ public: } Z_attribute_list & newAttrList(void); - Z_json_array & newJsonArray(void); + JsonGeneratorArray & newJsonArray(void); inline bool isNum(void) const { return (type >= Za_type::Za_bool) && (type <= Za_type::Za_float); } inline bool isNone(void) const { return (type == Za_type::Za_none);} @@ -446,9 +406,9 @@ Z_attribute_list & Z_attribute::newAttrList(void) { return *val.objval; } -Z_json_array & Z_attribute::newJsonArray(void) { +JsonGeneratorArray & Z_attribute::newJsonArray(void) { freeVal(); - val.arrval = new Z_json_array(); + val.arrval = new JsonGeneratorArray(); type = Za_type::Za_arr; return *val.arrval; } diff --git a/tasmota/xdrv_23_zigbee_2_devices.ino b/tasmota/xdrv_23_zigbee_2_devices.ino index d46debf02..3edb0ac93 100644 --- a/tasmota/xdrv_23_zigbee_2_devices.ino +++ b/tasmota/xdrv_23_zigbee_2_devices.ino @@ -36,31 +36,110 @@ enum class Z_Data_Type : uint8_t { Z_Device = 0xFF // special value when parsing Device level attributes }; +const uint8_t Z_Data_Type_char[] PROGMEM = { + '?', // 0x00 Z_Data_Type::Z_Unknown + 'L', // 0x01 Z_Data_Type::Z_Light + 'P', // 0x02 Z_Data_Type::Z_Plug + 'I', // 0x03 Z_Data_Type::Z_PIR + 'A', // 0x04 Z_Data_Type::Z_Alarm + 'T', // 0x05 Z_Data_Type::Z_Thermo + 'O', // 0x05 Z_Data_Type::Z_OnOff + '\0', // 0x06 + '\0', // 0x07 + '\0', // 0x08 + '\0', // 0x09 + '\0', // 0x0A + '\0', // 0x0B + '\0', // 0x0C + '\0', // 0x0D + '\0', // 0x0E + 'E', // 0x05 Z_Data_Type::Z_Ext + // '_' maps to 0xFF Z_Data_Type::Z_Device +}; + class Z_Data_Set; /*********************************************************************************************\ * Device specific data, sensors... \*********************************************************************************************/ class Z_Data { public: - Z_Data(Z_Data_Type type = Z_Data_Type::Z_Unknown, uint8_t endpoint = 0) : _type(type), _endpoint(endpoint), _config(-1), _power(0) {} + Z_Data(Z_Data_Type type = Z_Data_Type::Z_Unknown, uint8_t endpoint = 0) : _type(type), _endpoint(endpoint), _config(0xF), _power(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); } inline uint8_t getEndpoint(void) const { return _endpoint; } void toAttributes(Z_attribute_list & attr_list, Z_Data_Type type) const; static const Z_Data_Type type = Z_Data_Type::Z_Unknown; + static bool ConfigToZData(const char * config_str, Z_Data_Type * type, uint8_t * ep, uint8_t * config); + + static Z_Data_Type CharToDataType(char c); + static char DataTypeToChar(Z_Data_Type t); 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 - int8_t _config; // encoded on 4 bits, customize behavior + uint8_t _config : 4; // encoded on 4 bits, customize behavior uint8_t _power; // power state if the type supports it }; +Z_Data_Type Z_Data::CharToDataType(char c) { + if (c) { + if (c == '_') { + return Z_Data_Type::Z_Device; + } else { + for (uint32_t i=0; ilongaddr = longaddr; dirty(); + return *s_found; } else if (foundDevice(*l_found)) { // longaddr entry exists, update shortaddr l_found->shortaddr = shortaddr; dirty(); + return *l_found; } else { // neither short/lonf addr are found. if ((BAD_SHORTADDR != shortaddr) || longaddr) { - createDeviceEntry(shortaddr, longaddr); + return createDeviceEntry(shortaddr, longaddr); } + return (Z_Device&) device_unk; } } @@ -224,7 +230,7 @@ void Z_Devices::clearEndpoints(uint16_t shortaddr) { // Add an endpoint to a shortaddr // void Z_Devices::addEndpoint(uint16_t shortaddr, uint8_t endpoint) { - if (0x00 == endpoint) { return; } + if ((0x00 == endpoint) || (endpoint > 240)) { return; } Z_Device &device = getShortAddr(shortaddr); for (uint32_t i = 0; i < endpoints_max; i++) { @@ -278,6 +284,7 @@ void Z_Devices::setStringAttribute(char*& attr, const char * str) { } } if (str_len) { + if (str_len > 31) { str_len = 31; } attr = (char*) malloc(str_len + 1); strlcpy(attr, str, str_len + 1); } @@ -652,7 +659,7 @@ String Z_Devices::dumpLightState(uint16_t shortaddr) const { // Mode = 1: simple dump of devices addresses // Mode = 2: simple dump of devices addresses and names, endpoints, light String Z_Devices::dump(uint32_t dump_mode, uint16_t status_shortaddr) const { - Z_json_array json_arr; + JsonGeneratorArray json_arr; for (const auto & device : _devices) { uint16_t shortaddr = device.shortaddr; @@ -678,20 +685,30 @@ String Z_Devices::dump(uint32_t dump_mode, uint16_t status_shortaddr) const { if (device.modelId) { attr_list.addAttribute(F(D_JSON_MODEL D_JSON_ID)).setStr(device.modelId); } - int8_t bulbtype = getHueBulbtype(shortaddr); - if (bulbtype >= 0) { - attr_list.addAttribute(F(D_JSON_ZIGBEE_LIGHT)).setInt(bulbtype); // sign extend, 0xFF changed as -1 - } if (device.manufacturerId) { attr_list.addAttribute(F("Manufacturer")).setStr(device.manufacturerId); } - Z_json_array arr_ep; + + JsonGeneratorArray arr_ep; for (uint32_t i = 0; i < endpoints_max; i++) { uint8_t endpoint = device.endpoints[i]; if (0x00 == endpoint) { break; } arr_ep.add(endpoint); } attr_list.addAttribute(F("Endpoints")).setStrRaw(arr_ep.toString().c_str()); + + JsonGeneratorArray arr_data; + for (auto & data_elt : device.data) { + 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()); + } + key[0] = Z_Data::DataTypeToChar(data_elt.getType()); + arr_data.addStr(key); + } + attr_list.addAttribute(F("Config")).setStrRaw(arr_data.toString().c_str()); } json_arr.addStrRaw(attr_list.toString(true).c_str()); } @@ -710,18 +727,17 @@ String Z_Devices::dump(uint32_t dump_mode, uint16_t status_shortaddr) const { int32_t Z_Devices::deviceRestore(JsonParserObject json) { // params - uint16_t device = 0x0000; // 0x0000 is coordinator so considered invalid + uint16_t shortaddr = 0x0000; // 0x0000 is coordinator so considered invalid uint64_t ieeeaddr = 0x0000000000000000LL; // 0 means unknown const char * modelid = nullptr; const char * manufid = nullptr; const char * friendlyname = nullptr; - int8_t bulbtype = -1; size_t endpoints_len = 0; // read mandatory "Device" JsonParserToken val_device = json[PSTR("Device")]; if (val_device) { - device = (uint32_t) val_device.getUInt(device); + shortaddr = (uint32_t) val_device.getUInt(shortaddr); } else { return -1; // missing "Device" attribute } @@ -730,23 +746,41 @@ int32_t Z_Devices::deviceRestore(JsonParserObject json) { friendlyname = json.getStr(PSTR("Name"), nullptr); // read "Name" modelid = json.getStr(PSTR("ModelId"), nullptr); manufid = json.getStr(PSTR("Manufacturer"), nullptr); - JsonParserToken tok_bulbtype = json[PSTR(D_JSON_ZIGBEE_LIGHT)]; // update internal device information - updateDevice(device, ieeeaddr); - if (modelid) { setModelId(device, modelid); } - if (manufid) { setManufId(device, manufid); } - if (friendlyname) { setFriendlyName(device, friendlyname); } - if (tok_bulbtype) { setLightProfile(device, tok_bulbtype.getInt()); } + updateDevice(shortaddr, ieeeaddr); + if (modelid) { setModelId(shortaddr, modelid); } + if (manufid) { setManufId(shortaddr, manufid); } + if (friendlyname) { setFriendlyName(shortaddr, friendlyname); } // read "Endpoints" JsonParserToken val_endpoints = json[PSTR("Endpoints")]; if (val_endpoints.isArray()) { JsonParserArray arr_ep = JsonParserArray(val_endpoints); - clearEndpoints(device); // clear even if array is empty + clearEndpoints(shortaddr); // clear even if array is empty for (auto ep_elt : arr_ep) { uint8_t ep = ep_elt.getUInt(); - if (ep) { addEndpoint(device, ep); } + if (ep) { addEndpoint(shortaddr, ep); } + } + } + + // read "Config" + JsonParserToken val_config = json[PSTR("Config")]; + Z_Device & device = getShortAddr(shortaddr); + if (val_config.isArray()) { + JsonParserArray arr_config = JsonParserArray(val_config); + device.data.reset(); // remove existing configuration + for (auto config_elt : arr_config) { + const char * conf_str = config_elt.getStr(); + Z_Data_Type data_type; + uint8_t ep, config; + + if (Z_Data::ConfigToZData(conf_str, &data_type, &ep, &config)) { + Z_Data & data = device.data.getByType(data_type, ep); + if (&data != nullptr) { + data.setConfig(config); + } + } } } diff --git a/tasmota/xdrv_23_zigbee_4_persistence.ino b/tasmota/xdrv_23_zigbee_4_persistence.ino index 0e688c960..b6d252d59 100644 --- a/tasmota/xdrv_23_zigbee_4_persistence.ino +++ b/tasmota/xdrv_23_zigbee_4_persistence.ino @@ -26,6 +26,14 @@ // uint16 - start address in Flash (offset) // uint16 - length in bytes (makes sure parsing stops) // +// First byte: +// 0x00 - Empty or V3 format +// 0x01-0xFE - Legacy format +// 0xFF - invalid +// +// +// V1 Legacy +// ========= // File structure: // uint8 - number of devices, 0=none, 0xFF=invalid entry (probably Flash was erased) // @@ -47,6 +55,29 @@ // reserved for extensions // -- V2 -- // int8_t - zigbee profile of the device +// +// ======================= +// v3 with version number +// 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 +// uint16 - short address +// uint64 - long IEEE address +// +// str - ModelID (null terminated C string, 32 chars max) +// str - Manuf (null terminated C string, 32 chars max) +// str - FriendlyName (null terminated C string, 32 chars max) +// +// [Array of endpoints] +// uint8 - endpoint number, 0xFF marks the end of endpoints +// uint8[] - list of configuration bytes, 0xFF marks the end +// i.e. 0xFF-0xFF marks the end of the array of endpoints +// + // Memory footprint #ifdef ESP8266 @@ -63,71 +94,81 @@ const static size_t z_block_offset = 0x0000; // No offset needed const static size_t z_block_len = 0x1000; // 4kb #endif -class z_flashdata_t { +// Each entry consumes 8 bytes +class Z_Flashentry { public: + uint32_t name; // simple 4 letters name. Currently 'zig1', 'zig2'. 0xFFFFFFFF if not entry + uint16_t len; // len of object in bytes, 0xFFFF if no entry + uint16_t start; // address of start, 0xFFFF if empty, must be aligned on 128 bytes boundaries +}; + +class Z_Flashdirectory { +public: + // 8 bytes header + uint32_t magic; // magic value 'Tsmt' to check that the block is initialized + uint32_t clock; // clock vector to discard entries that are made before this one. This should be incremented by 1 for each new entry (future anti-weavering) + // entries, 14*8 = 112 bytes + Z_Flashentry entries[14]; uint32_t name; // simple 4 letters name. Currently 'skey', 'crt ', 'crt1', 'crt2' uint16_t len; // len of object uint16_t reserved; // align on 4 bytes boundary + // link to next entry, none for now, but may be used for anti-weavering + uint16_t next_dir; // 0xFFFF if none + uint16_t reserved1; // must be 0xFFFF + uint32_t reserved2; // must be 0xFFFFFFFF }; -const static uint32_t ZIGB_NAME = 0x3167697A; // 'zig1' little endian -const static size_t Z_MAX_FLASH = z_block_len - sizeof(z_flashdata_t); // 2040 +const static uint32_t ZIGB_NAME1 = 0x3167697A; // 'zig1' little endian +const static uint32_t ZIGB_NAME2 = 0x3267697A; // 'zig2' little endian, v2 +const static size_t Z_MAX_FLASH = z_block_len - sizeof(Z_Flashentry); // 2040 -class SBuffer hibernateDevice(const struct Z_Device &device) { +bool hibernateDeviceConfiguration(SBuffer & buf, const class Z_Data_Set & data, uint8_t endpoint) { + bool found = false; + for (auto & elt : data) { + if (endpoint == elt.getEndpoint()) { + buf.add8(elt.getConfigByte()); + found = true; + } + } + return found; +} + +class SBuffer hibernateDevicev2(const struct Z_Device &device) { SBuffer buf(128); buf.add8(0x00); // overall length, will be updated later buf.add16(device.shortaddr); buf.add64(device.longaddr); - uint32_t endpoints_count = 0; - for (endpoints_count = 0; endpoints_count < endpoints_max; endpoints_count++) { - if (0x00 == device.endpoints[endpoints_count]) { break; } + char *names[3] = { device.modelId, device.manufacturerId, device.friendlyName }; + + for (uint32_t i=0; i 32) { len = 32; } // max 32 chars + buf.addBuffer(p, len); + } + buf.add8(0x00); // end of string marker } - buf.add8(endpoints_count); - // iterate on endpoints - for (uint32_t i = 0; i < endpoints_max; i++) { + // check if we need to write fake endpoint 0x00 + buf.add8(0x00); + if (hibernateDeviceConfiguration(buf, device.data, 0)) { + buf.add8(0xFF); // end of configuration + } else { + buf.setLen(buf.len()-1); // remove 1 byte header + } + // scan endpoints + for (uint32_t i=0; i 32) { model_len = 32; } // max 32 chars - buf.addBuffer(device.modelId, model_len); - } - buf.add8(0x00); // end of string marker - - // ManufID - if (device.manufacturerId) { - size_t manuf_len = strlen(device.manufacturerId); - if (manuf_len > 32) { manuf_len = 32; } // max 32 chars - buf.addBuffer(device.manufacturerId, manuf_len); - } - buf.add8(0x00); // end of string marker - - // FriendlyName - if (device.friendlyName) { - size_t frname_len = strlen(device.friendlyName); - if (frname_len > 32) {frname_len = 32; } // max 32 chars - buf.addBuffer(device.friendlyName, frname_len); - } - buf.add8(0x00); // end of string marker - - // Zigbee Profile - buf.add8(device.getLightChannels()); + buf.add8(0xFF); // end of endpoints // update overall length buf.set8(0, buf.len()); @@ -144,7 +185,7 @@ class SBuffer hibernateDevices(void) { for (uint32_t i = 0; i < devices_size; i++) { const Z_Device & device = zigbee_devices.devicesAt(i); - const SBuffer buf_device = hibernateDevice(device); + const SBuffer buf_device = hibernateDevicev2(device); buf.addBuffer(buf_device); } @@ -164,22 +205,24 @@ class SBuffer hibernateDevices(void) { return buf; } -void hydrateDevices(const SBuffer &buf) { - uint32_t buf_len = buf.len(); - if (buf_len <= 10) { return; } +// parse a single string from the saved data +// if something wrong happens, returns nullptr to ignore the string +// Index d is incremented to just after the string +const char * hydrateSingleString(const SBuffer & buf, uint32_t *d) { + size_t s_len = buf.strlen(*d); + const char * ptr = s_len ? buf.charptr(*d) : ""; + *d += s_len + 1; + return ptr; +} - uint32_t k = 0; - uint32_t num_devices = buf.get8(k++); - for (uint32_t i = 0; (i < num_devices) && (k < buf_len); i++) { - uint32_t dev_record_len = buf.get8(k); - - SBuffer buf_d = buf.subBuffer(k, dev_record_len); - - uint32_t d = 1; // index in device buffer - uint16_t shortaddr = buf_d.get16(d); d += 2; - uint64_t longaddr = buf_d.get64(d); d += 8; - zigbee_devices.updateDevice(shortaddr, longaddr); // update device's addresses +void hydrateSingleDevice(const SBuffer & buf_d, uint32_t version) { + uint32_t d = 1; // index in device buffer + uint16_t shortaddr = buf_d.get16(d); d += 2; + uint64_t longaddr = buf_d.get64(d); d += 8; + size_t buf_len = buf_d.len(); + Z_Device & device = zigbee_devices.updateDevice(shortaddr, longaddr); // update device's addresses + if (1 == version) { uint32_t endpoints = buf_d.get8(d++); for (uint32_t j = 0; j < endpoints; j++) { uint8_t ep = buf_d.get8(d++); @@ -187,51 +230,77 @@ void hydrateDevices(const SBuffer &buf) { zigbee_devices.addEndpoint(shortaddr, ep); // in clusters - while (d < dev_record_len) { // safe guard against overflow + while (d < buf_len) { // safe guard against overflow uint8_t ep_cluster = buf_d.get8(d++); if (0xFF == ep_cluster) { break; } // end of block // ignore } // out clusters - while (d < dev_record_len) { // safe guard against overflow + while (d < buf_len) { // safe guard against overflow uint8_t ep_cluster = buf_d.get8(d++); if (0xFF == ep_cluster) { break; } // end of block // ignore } } + } - // parse 3 strings - char empty[] = ""; + // ModelId + zigbee_devices.setModelId(shortaddr, hydrateSingleString(buf_d, &d)); - // ManufID - uint32_t s_len = buf_d.strlen_s(d); - char *ptr = s_len ? buf_d.charptr(d) : empty; - zigbee_devices.setModelId(shortaddr, ptr); - d += s_len + 1; + // ManufID + zigbee_devices.setManufId(shortaddr, hydrateSingleString(buf_d, &d)); - // ManufID - s_len = buf_d.strlen_s(d); - ptr = s_len ? buf_d.charptr(d) : empty; - zigbee_devices.setManufId(shortaddr, ptr); - d += s_len + 1; + // FriendlyName + zigbee_devices.setFriendlyName(shortaddr, hydrateSingleString(buf_d, &d)); - // FriendlyName - s_len = buf_d.strlen_s(d); - ptr = s_len ? buf_d.charptr(d) : empty; - zigbee_devices.setFriendlyName(shortaddr, ptr); - d += s_len + 1; + if (d >= buf_len) { return; } - // Hue bulbtype - if present - if (d < dev_record_len) { - zigbee_devices.setLightProfile(shortaddr, buf_d.get8(d)); - d++; + // Hue bulbtype - if present + if (1 == version) { + zigbee_devices.setLightProfile(shortaddr, buf_d.get8(d)); + d++; + } else if (2 == version) { + // v2 parser + while (d < buf_len) { + 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 + while (d < buf_len) { + uint8_t config_type = buf_d.get8(d++); + if (0xFF == config_type) { break; } // 0xFF marks the end of congiguration + uint8_t config = config_type & 0x0F; + Z_Data_Type type = (Z_Data_Type) (config_type >> 4); + // set the configuration + if (ep != 0xFF) { + Z_Data & z_data = device.data.getByType(type, ep); + if (&z_data != nullptr) { + z_data.setConfig(config); + } + } + } } + } +} + +void hydrateDevices(const SBuffer &buf, uint32_t version) { + uint32_t buf_len = buf.len(); + if (buf_len <= 10) { return; } + + uint32_t k = 0; // byte index in global buffer + uint32_t num_devices = buf.get8(k++); + for (uint32_t i = 0; (i < num_devices) && (k < buf_len); i++) { + uint32_t dev_record_len = buf.get8(k); + + SBuffer buf_d = buf.subBuffer(k, dev_record_len); + hydrateSingleDevice(buf_d, version); // next iteration k += dev_record_len; } } + void loadZigbeeDevices(void) { #ifdef ESP32 // first copy SPI buffer into ram @@ -243,19 +312,24 @@ void loadZigbeeDevices(void) { ZigbeeRead(&spi_buffer, z_spi_len); z_dev_start = spi_buffer; #endif // ESP32 - z_flashdata_t flashdata; - memcpy_P(&flashdata, z_dev_start, sizeof(z_flashdata_t)); + Z_Flashentry flashdata; + memcpy_P(&flashdata, z_dev_start, sizeof(Z_Flashentry)); // AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_ZIGBEE "Memory %d"), ESP_getFreeHeap()); AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_ZIGBEE "Zigbee signature in Flash: %08X - %d"), flashdata.name, flashdata.len); // Check the signature - if ((flashdata.name == ZIGB_NAME) && (flashdata.len > 0)) { + if ( ((flashdata.name == ZIGB_NAME1) || (flashdata.name == ZIGB_NAME2)) + && (flashdata.len > 0)) { uint16_t buf_len = flashdata.len; + uint32_t version = (flashdata.name == ZIGB_NAME2) ? 2 : 1; // parse what seems to be a valid entry SBuffer buf(buf_len); - buf.addBuffer(z_dev_start + sizeof(z_flashdata_t), buf_len); - AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "Zigbee devices data in Flash (%d bytes)"), buf_len); - hydrateDevices(buf); + buf.addBuffer(z_dev_start + sizeof(Z_Flashentry), buf_len); + AddLog_P2(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; iname = ZIGB_NAME; + Z_Flashentry *flashdata = (Z_Flashentry*)(spi_buffer + z_block_offset); + flashdata->name = ZIGB_NAME2; // v2 flashdata->len = buf_len; - flashdata->reserved = 0; + flashdata->start = 0; - memcpy(spi_buffer + z_block_offset + sizeof(z_flashdata_t), buf.getBuffer(), buf_len); + memcpy(spi_buffer + z_block_offset + sizeof(Z_Flashentry), buf.getBuffer(), buf_len); // buffer is now ready, write it back #ifdef ESP8266 diff --git a/tasmota/xdrv_23_zigbee_5_converters.ino b/tasmota/xdrv_23_zigbee_5_converters.ino index 5d96d9c70..d3cf81caa 100644 --- a/tasmota/xdrv_23_zigbee_5_converters.ino +++ b/tasmota/xdrv_23_zigbee_5_converters.ino @@ -1332,7 +1332,7 @@ void ZCLFrame::parseReadAttributes(Z_attribute_list& attr_list) { attr_list.addAttribute(F(D_CMND_ZIGBEE_CLUSTER)).setUInt(_cluster_id); - Z_json_array attr_numbers; + JsonGeneratorArray attr_numbers; Z_attribute_list attr_names; while (len >= 2 + i) { uint16_t attrid = _payload.get16(i); @@ -1803,6 +1803,8 @@ void ZCLFrame::postProcessAttributes(uint16_t shortaddr, Z_attribute_list& attr_ // First we find or instantiate the correct Z_Data_XXX accorfing to the endpoint // Then store the attribute at the attribute addres (via offset) and according to size 8/16/32 bits + // add the endpoint if it was not already known + zigbee_devices.addEndpoint(shortaddr, src_ep); // we don't apply the multiplier, but instead store in Z_Data_XXX object Z_Data & data = device.data.getByType(map_type, src_ep); uint8_t *attr_address = ((uint8_t*)&data) + sizeof(Z_Data) + map_offset; diff --git a/tasmota/xdrv_23_zigbee_6_commands.ino b/tasmota/xdrv_23_zigbee_6_commands.ino index b409dcf86..e3597fcc8 100644 --- a/tasmota/xdrv_23_zigbee_6_commands.ino +++ b/tasmota/xdrv_23_zigbee_6_commands.ino @@ -370,7 +370,7 @@ void convertClusterSpecific(class Z_attribute_list &attr_list, uint16_t cluster, attr_list.addAttribute(command_name, PSTR("Count")).setUInt(xyz.y); { - Z_json_array group_list; + JsonGeneratorArray group_list; for (uint32_t i = 0; i < xyz.y; i++) { group_list.add(payload.get16(2 + 2*i)); } @@ -441,7 +441,7 @@ void convertClusterSpecific(class Z_attribute_list &attr_list, uint16_t cluster, attr_list.addAttribute(command_name, command_suffix).setUInt(xyz.x); } else { // multiple answers, create an array - Z_json_array arr; + JsonGeneratorArray arr; arr.add(xyz.x); arr.add(xyz.y); if (xyz.z_type) { diff --git a/tasmota/xdrv_23_zigbee_A_impl.ino b/tasmota/xdrv_23_zigbee_A_impl.ino index 3e68b20cc..5dc2a4af1 100644 --- a/tasmota/xdrv_23_zigbee_A_impl.ino +++ b/tasmota/xdrv_23_zigbee_A_impl.ino @@ -1314,17 +1314,12 @@ bool parseDeviceInnerData(class Z_Device & device, JsonParserObject 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; } - switch (data_type_str[0]) { - case 'P': data_type = Z_Data_Type::Z_Plug; break; - case 'L': data_type = Z_Data_Type::Z_Light; break; - case 'O': data_type = Z_Data_Type::Z_OnOff; break; - case 'T': data_type = Z_Data_Type::Z_Thermo; break; - case 'A': data_type = Z_Data_Type::Z_Alarm; break; - case '_': data_type = Z_Data_Type::Z_Device; break; - default: data_type = Z_Data_Type::Z_Unknown; break; - } - // The format should be a valid Code Lette followed by '-' if (data_type == Z_Data_Type::Z_Unknown) { Response_P(PSTR("{\"%s\":\"%s \"%s\"\"}"), XdrvMailbox.command, PSTR("Invalid Parameters"), data_type_str); return false; @@ -1333,79 +1328,81 @@ bool parseDeviceInnerData(class Z_Device & device, JsonParserObject root) { JsonParserObject data_values = data_elt.getValue().getObject(); if (!data_values) { return false; } - // Decode the endpoint number - uint8_t endpoint = strtoul(&data_type_str[1], nullptr, 16); // hex base 16 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 + Z_Data & data = device.data.getByType(data_type, endpoint); - // Import generic attributes first - Z_Data & data = device.data.getByType(data_type, endpoint); + // scan through attributes + if (&data != nullptr) { + if (config != 0xFF) { + data.setConfig(config); + } - // scan through attributes - 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' + 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; + 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_P2(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); + } } - } else if (conv_data_type != Z_Data_Type::Z_Unknown) { - AddLog_P2(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; + // 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); } + 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; } - 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; - case 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(); } - } - break; } } return true; @@ -1454,56 +1451,39 @@ void CmndZbData(void) { { // scope to force object deallocation Z_attribute_list device_attr; device.toAttributes(device_attr); - attr_data.addAttribute(F("_00")).setStrRaw(device_attr.toString(true).c_str()); + 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[4]; - snprintf_P(key, sizeof(key), "?%02X", data_elt.getEndpoint()); - // The key is in the form "L01", where 'L' is the type and '01' the endpoint in hex format - // 'P' = Power - // 'L' = Light - // 'O' = OnOff - // 'T' = Thermo & sensors - // 'A' = Alarm - // '?' = Device wide - // + 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: - { - key[0] = 'P'; - ((Z_Data_Plug&)data_elt).toAttributes(inner_attr, data_type); - } + ((Z_Data_Plug&)data_elt).toAttributes(inner_attr, data_type); break; case Z_Data_Type::Z_Light: - { - key[0] = 'L'; - ((Z_Data_Light&)data_elt).toAttributes(inner_attr, data_type); - } + ((Z_Data_Light&)data_elt).toAttributes(inner_attr, data_type); break; case Z_Data_Type::Z_OnOff: - { - key[0] = 'O'; - ((Z_Data_OnOff&)data_elt).toAttributes(inner_attr, data_type); - } + ((Z_Data_OnOff&)data_elt).toAttributes(inner_attr, data_type); break; case Z_Data_Type::Z_Thermo: - { - key[0] = 'T'; - ((Z_Data_Thermo&)data_elt).toAttributes(inner_attr, data_type); - } + ((Z_Data_Thermo&)data_elt).toAttributes(inner_attr, data_type); break; case Z_Data_Type::Z_Alarm: - { - key[0] = 'A'; - ((Z_Data_Alarm&)data_elt).toAttributes(inner_attr, data_type); - } + ((Z_Data_Alarm&)data_elt).toAttributes(inner_attr, data_type); break; } - if (key[0] != '?') { + if ((key[0] != '\0') && (key[0] != '?')) { attr_data.addAttribute(key).setStrRaw(inner_attr.toString(true).c_str()); } }