From ea85b706853957b94dc73d29da161c3b6f23309f Mon Sep 17 00:00:00 2001 From: Stephan Hadinger Date: Mon, 14 Sep 2020 22:06:19 +0200 Subject: [PATCH] Zigbee minor improvements --- tasmota/support.ino | 24 ++++ tasmota/xdrv_23_zigbee_1_headers.ino | 1 + tasmota/xdrv_23_zigbee_1z_libs.ino | 19 ++- tasmota/xdrv_23_zigbee_2_devices.ino | 63 +++++---- tasmota/xdrv_23_zigbee_5_converters.ino | 74 +++++++++++ tasmota/xdrv_23_zigbee_8_parsers.ino | 73 ++++++---- tasmota/xdrv_23_zigbee_9_serial.ino | 2 + tasmota/xdrv_23_zigbee_A_impl.ino | 170 +++++++++++------------- 8 files changed, 266 insertions(+), 160 deletions(-) diff --git a/tasmota/support.ino b/tasmota/support.ino index 8d4044906..eddffeab6 100644 --- a/tasmota/support.ino +++ b/tasmota/support.ino @@ -2006,3 +2006,27 @@ String Decompress(const char * compressed, size_t uncompressed_size) { } #endif // USE_UNISHOX_COMPRESSION + +/*********************************************************************************************\ + * High entropy hardware random generator + * Thanks to DigitalAlchemist +\*********************************************************************************************/ +// Based on code from https://raw.githubusercontent.com/espressif/esp-idf/master/components/esp32/hw_random.c +uint32_t HwRandom(void) { +#if ESP8266 + // https://web.archive.org/web/20160922031242/http://esp8266-re.foogod.com/wiki/Random_Number_Generator + #define _RAND_ADDR 0x3FF20E44UL +#else // ESP32 + #define _RAND_ADDR 0x3FF75144UL +#endif + static uint32_t last_ccount = 0; + uint32_t ccount; + uint32_t result = 0; + do { + ccount = ESP.getCycleCount(); + result ^= *(volatile uint32_t *)_RAND_ADDR; + } while (ccount - last_ccount < 64); + last_ccount = ccount; + return result ^ *(volatile uint32_t *)_RAND_ADDR; +#undef _RAND_ADDR +} diff --git a/tasmota/xdrv_23_zigbee_1_headers.ino b/tasmota/xdrv_23_zigbee_1_headers.ino index 604c53835..c7e3093e2 100644 --- a/tasmota/xdrv_23_zigbee_1_headers.ino +++ b/tasmota/xdrv_23_zigbee_1_headers.ino @@ -37,6 +37,7 @@ public: }; void ZigbeeZCLSend_Raw(const ZigbeeZCLSendMessage &zcl); +bool ZbAppendWriteBuf(SBuffer & buf, const Z_attribute & attr, bool prepend_status_ok = false); // get the result as a string (const char*) and nullptr if there is no field or the string is empty const char * getCaseInsensitiveConstCharNull(const JsonObject &json, const char *needle) { diff --git a/tasmota/xdrv_23_zigbee_1z_libs.ino b/tasmota/xdrv_23_zigbee_1z_libs.ino index 0590cff81..1f9012152 100644 --- a/tasmota/xdrv_23_zigbee_1z_libs.ino +++ b/tasmota/xdrv_23_zigbee_1z_libs.ino @@ -101,11 +101,13 @@ public: SBuffer* bval; char* sval; } val; - Za_type type; // uint8_t in size, type of attribute, see above - bool key_is_str; // is the key a string? - bool key_is_pmem; // is the string in progmem, so we don't need to make a copy - bool val_str_raw; // if val is String, it is raw JSON and should not be escaped - uint8_t key_suffix; // append a suffix to key (default is 1, explicitly output if >1) + Za_type type; // uint8_t in size, type of attribute, see above + bool key_is_str; // is the key a string? + bool key_is_pmem; // is the string in progmem, so we don't need to make a copy + bool val_str_raw; // if val is String, it is raw JSON and should not be escaped + uint8_t key_suffix; // append a suffix to key (default is 1, explicitly output if >1) + uint8_t attr_type; // [opt] type of the attribute, default to Zunk (0xFF) + uint8_t attr_multiplier; // [opt] multiplier for attribute, defaults to 0x01 (no change) // Constructor with all defaults Z_attribute(): @@ -115,7 +117,9 @@ public: key_is_str(false), key_is_pmem(false), val_str_raw(false), - key_suffix(1) + key_suffix(1), + attr_type(0xFF), + attr_multiplier(1) {}; Z_attribute(const Z_attribute & rhs) { @@ -247,6 +251,7 @@ public: } 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);} // get num values float getFloat(void) const { switch (type) { @@ -483,6 +488,8 @@ protected: key_is_str = rhs.key_is_str; key_is_pmem = rhs.key_is_pmem; key_suffix = rhs.key_suffix; + attr_type = rhs.attr_type; + attr_multiplier = rhs.attr_multiplier; // copy value copyVal(rhs); // don't touch next pointer diff --git a/tasmota/xdrv_23_zigbee_2_devices.ino b/tasmota/xdrv_23_zigbee_2_devices.ino index 1e2c29704..b84db3e2e 100644 --- a/tasmota/xdrv_23_zigbee_2_devices.ino +++ b/tasmota/xdrv_23_zigbee_2_devices.ino @@ -951,44 +951,43 @@ uint16_t Z_Devices::parseDeviceParam(const char * param, bool short_must_be_know // Display the tracked status for a light String Z_Devices::dumpLightState(uint16_t shortaddr) const { - DynamicJsonBuffer jsonBuffer; - JsonObject& json = jsonBuffer.createObject(); + Z_attribute_list attr_list; char hex[8]; const Z_Device & device = findShortAddr(shortaddr); - if (foundDevice(device)) { - const char * fname = getFriendlyName(shortaddr); + const char * fname = getFriendlyName(shortaddr); + bool use_fname = (Settings.flag4.zigbee_use_names) && (fname); // should we replace shortaddr with friendlyname? + snprintf_P(hex, sizeof(hex), PSTR("0x%04X"), shortaddr); - bool use_fname = (Settings.flag4.zigbee_use_names) && (fname); // should we replace shortaddr with friendlyname? - - snprintf_P(hex, sizeof(hex), PSTR("0x%04X"), shortaddr); - - JsonObject& dev = use_fname ? json.createNestedObject((char*) fname) // casting (char*) forces a copy - : json.createNestedObject(hex); - if (use_fname) { - dev[F(D_JSON_ZIGBEE_DEVICE)] = hex; - } else if (fname) { - dev[F(D_JSON_ZIGBEE_NAME)] = (char*) fname; - } - - // expose the last known status of the bulb, for Hue integration - dev[F(D_JSON_ZIGBEE_LIGHT)] = getHueBulbtype(shortaddr); // sign extend, 0xFF changed as -1 - // dump all known values - dev[F("Reachable")] = device.getReachable(); // TODO TODO - if (device.validPower()) { dev[F("Power")] = device.getPower(); } - if (device.validDimmer()) { dev[F("Dimmer")] = device.dimmer; } - if (device.validColormode()) { dev[F("Colormode")] = device.colormode; } - if (device.validCT()) { dev[F("CT")] = device.ct; } - if (device.validSat()) { dev[F("Sat")] = device.sat; } - if (device.validHue()) { dev[F("Hue")] = device.hue; } - if (device.validX()) { dev[F("X")] = device.x; } - if (device.validY()) { dev[F("Y")] = device.y; } + attr_list.addAttribute(F(D_JSON_ZIGBEE_DEVICE)).setStr(hex); + if (fname) { + attr_list.addAttribute(F(D_JSON_ZIGBEE_NAME)).setStr(fname); } - String payload = ""; - payload.reserve(200); - json.printTo(payload); - return payload; + if (foundDevice(device)) { + // expose the last known status of the bulb, for Hue integration + attr_list.addAttribute(F(D_JSON_ZIGBEE_LIGHT)).setInt(getHueBulbtype(shortaddr)); // sign extend, 0xFF changed as -1 + // dump all known values + attr_list.addAttribute(F("Reachable")).setBool(device.getReachable()); + if (device.validPower()) { attr_list.addAttribute(F("Power")).setUInt(device.getPower()); } + if (device.validDimmer()) { attr_list.addAttribute(F("Dimmer")).setUInt(device.dimmer); } + if (device.validColormode()) { attr_list.addAttribute(F("Colormode")).setUInt(device.colormode); } + if (device.validCT()) { attr_list.addAttribute(F("CT")).setUInt(device.ct); } + if (device.validSat()) { attr_list.addAttribute(F("Sat")).setUInt(device.sat); } + if (device.validHue()) { attr_list.addAttribute(F("Hue")).setUInt(device.hue); } + if (device.validX()) { attr_list.addAttribute(F("X")).setUInt(device.x); } + if (device.validY()) { attr_list.addAttribute(F("Y")).setUInt(device.y); } + } + + Z_attribute_list attr_list_root; + Z_attribute * attr_root; + if (use_fname) { + attr_root = &attr_list_root.addAttribute(fname); + } else { + attr_root = &attr_list_root.addAttribute(hex); + } + attr_root->setStrRaw(attr_list.toString(true).c_str()); + return attr_list_root.toString(true); } // Dump the internal memory of Zigbee devices diff --git a/tasmota/xdrv_23_zigbee_5_converters.ino b/tasmota/xdrv_23_zigbee_5_converters.ino index edcdb3135..f29c514d5 100644 --- a/tasmota/xdrv_23_zigbee_5_converters.ino +++ b/tasmota/xdrv_23_zigbee_5_converters.ino @@ -1664,4 +1664,78 @@ void ZCLFrame::postProcessAttributes(uint16_t shortaddr, Z_attribute_list& attr_ } } +// +// Given an attribute string, retrieve all attribute details. +// Input: the attribute has a key name, either: / or /% or "" +// Ex: "0008/0000", "0008/0000%20", "Dimmer" +// Use: +// Z_attribute attr; +// attr.setKeyName("0008/0000%20") +// if (Z_parseAttributeKey(attr)) { +// // ok +// } +// +// Output: +// The `attr` attribute has the correct cluster, attr_id, attr_type, attr_multiplier +// Note: the attribute value is unchanged and unparsed +// +// Note: if the type is specified in the key, the multiplier is not applied, no conversion happens +bool Z_parseAttributeKey(class Z_attribute & attr) { + // check if the name has the format "XXXX/YYYY" where XXXX is the cluster, YYYY the attribute id + // alternative "XXXX/YYYY%ZZ" where ZZ is the type (for unregistered attributes) + if (attr.key_is_str) { + const char * key = attr.key.key; + char * delimiter = strchr(key, '/'); + char * delimiter2 = strchr(key, '%'); + if (delimiter) { + uint16_t attr_id = 0xFFFF; + uint16_t cluster_id = 0xFFFF; + uint8_t type_id = Zunk; + + cluster_id = strtoul(key, &delimiter, 16); + if (!delimiter2) { + attr_id = strtoul(delimiter+1, nullptr, 16); + } else { + attr_id = strtoul(delimiter+1, &delimiter2, 16); + type_id = strtoul(delimiter2+1, nullptr, 16); + } + attr.setKeyId(cluster_id, attr_id); + attr.attr_type = type_id; + } + } + // AddLog_P2(LOG_LEVEL_DEBUG, PSTR("cluster_id = 0x%04X, attr_id = 0x%04X"), cluster_id, attr_id); + + // do we already know the type, i.e. attribute and cluster are also known + if (Zunk == attr.attr_type) { + // scan attributes to find by name, and retrieve type + for (uint32_t i = 0; i < ARRAY_SIZE(Z_PostProcess); i++) { + const Z_AttributeConverter *converter = &Z_PostProcess[i]; + bool match = false; + uint16_t local_attr_id = pgm_read_word(&converter->attribute); + uint16_t local_cluster_id = CxToCluster(pgm_read_byte(&converter->cluster_short)); + uint8_t local_type_id = pgm_read_byte(&converter->type); + int8_t local_multiplier = pgm_read_byte(&converter->multiplier); + // AddLog_P2(LOG_LEVEL_DEBUG, PSTR("Try cluster = 0x%04X, attr = 0x%04X, type_id = 0x%02X"), local_cluster_id, local_attr_id, local_type_id); + + if (!attr.key_is_str) { + if ((attr.key.id.cluster == local_cluster_id) && (attr.key.id.attr_id == local_attr_id)) { + attr.attr_type = local_type_id; + break; + } + } else if (pgm_read_word(&converter->name_offset)) { + const char * key = attr.key.key; + // AddLog_P2(LOG_LEVEL_DEBUG, PSTR("Comparing '%s' with '%s'"), attr_name, converter->name); + if (0 == strcasecmp_P(key, Z_strings + pgm_read_word(&converter->name_offset))) { + // match + attr.setKeyId(local_cluster_id, local_attr_id); + attr.attr_type = local_type_id; + attr.attr_multiplier = local_multiplier; + break; + } + } + } + } + return (Zunk != attr.attr_type) ? true : false; +} + #endif // USE_ZIGBEE diff --git a/tasmota/xdrv_23_zigbee_8_parsers.ino b/tasmota/xdrv_23_zigbee_8_parsers.ino index c7585134b..c761cabb6 100644 --- a/tasmota/xdrv_23_zigbee_8_parsers.ino +++ b/tasmota/xdrv_23_zigbee_8_parsers.ino @@ -1768,20 +1768,21 @@ int32_t Z_State_Ready(uint8_t value) { // // Mostly used for routers/end-devices // json: holds the attributes in JSON format -void Z_AutoResponder(uint16_t srcaddr, uint16_t cluster, uint8_t endpoint, const uint16_t *attr_list, size_t attr_len) { - DynamicJsonBuffer jsonBuffer; - JsonObject& json_out = jsonBuffer.createObject(); +void Z_AutoResponder(uint16_t srcaddr, uint16_t cluster, uint8_t endpoint, const uint16_t *attr_list_ids, size_t attr_len) { + Z_attribute_list attr_list; for (uint32_t i=0; i 0xFEFF) ? uxy[i] : 0xFEFF; } - if (0x0000 == attr) { json_out[F("Hue")] = changeUIntScale(hue, 0, 360, 0, 254); } - if (0x0001 == attr) { json_out[F("Sat")] = changeUIntScale(sat, 0, 255, 0, 254); } - if (0x0003 == attr) { json_out[F("X")] = uxy[0]; } - if (0x0004 == attr) { json_out[F("Y")] = uxy[1]; } - if (0x0007 == attr) { json_out[F("CT")] = LightGetColorTemp(); } + if (0x0000 == attr_id) { attr.setUInt(changeUIntScale(hue, 0, 360, 0, 254)); } + if (0x0001 == attr_id) { attr.setUInt(changeUIntScale(sat, 0, 255, 0, 254)); } + if (0x0003 == attr_id) { attr.setUInt(uxy[0]); } + if (0x0004 == attr_id) { attr.setUInt(uxy[1]); } + if (0x0007 == attr_id) { attr.setUInt(LightGetColorTemp()); } } break; #endif case 0x000A0000: // Time - json_out[F("Time")] = (Rtc.utc_time > (60 * 60 * 24 * 365 * 10)) ? Rtc.utc_time - 946684800 : Rtc.utc_time; + attr.setUInt((Rtc.utc_time > (60 * 60 * 24 * 365 * 10)) ? Rtc.utc_time - 946684800 : Rtc.utc_time); break; case 0x000AFF00: // TimeEpoch - Tasmota specific - json_out[F("TimeEpoch")] = Rtc.utc_time; + attr.setUInt(Rtc.utc_time); break; case 0x000A0001: // TimeStatus - json_out[F("TimeStatus")] = (Rtc.utc_time > (60 * 60 * 24 * 365 * 10)) ? 0x02 : 0x00; // if time is beyond 2010 then we are synchronized + attr.setUInt((Rtc.utc_time > (60 * 60 * 24 * 365 * 10)) ? 0x02 : 0x00); // if time is beyond 2010 then we are synchronized break; case 0x000A0002: // TimeZone - json_out[F("TimeZone")] = Settings.toffset[0] * 60; + attr.setUInt(Settings.toffset[0] * 60); break; case 0x000A0007: // LocalTime // TODO take DST - json_out[F("LocalTime")] = Settings.toffset[0] * 60 + ((Rtc.utc_time > (60 * 60 * 24 * 365 * 10)) ? Rtc.utc_time - 946684800 : Rtc.utc_time); + attr.setUInt(Settings.toffset[0] * 60 + ((Rtc.utc_time > (60 * 60 * 24 * 365 * 10)) ? Rtc.utc_time - 946684800 : Rtc.utc_time)); break; } + if (!attr.isNone()) { + Z_parseAttributeKey(attr); + attr_list.addAttribute(cluster, attr_id) = attr; + } } - if (json_out.size() > 0) { + SBuffer buf(200); + for (const auto & attr : attr_list) { + if (!ZbAppendWriteBuf(buf, attr, true)) { // true = need status indicator in Read Attribute Responses + return; // error + } + } + + if (buf.len() > 0) { // we have a non-empty output // log first - String msg(""); - msg.reserve(100); - json_out.printTo(msg); AddLog_P2(LOG_LEVEL_INFO, PSTR("ZIG: Auto-responder: ZbSend {\"Device\":\"0x%04X\"" ",\"Cluster\":\"0x%04X\"" ",\"Endpoint\":%d" ",\"Response\":%s}" ), srcaddr, cluster, endpoint, - msg.c_str()); + attr_list.toString().c_str()); // send - const JsonVariant &json_out_v = json_out; - ZbSendReportWrite(json_out_v, srcaddr, 0 /* group */,cluster, endpoint, 0 /* manuf */, ZCL_READ_ATTRIBUTES_RESPONSE); + // all good, send the packet + uint8_t seq = zigbee_devices.getNextSeqNumber(srcaddr); + ZigbeeZCLSend_Raw(ZigbeeZCLSendMessage({ + srcaddr, + 0x0000, + cluster /*cluster*/, + endpoint, + ZCL_READ_ATTRIBUTES_RESPONSE, + 0x0000, /* manuf */ + false /* not cluster specific */, + false /* no response */, + seq, /* zcl transaction id */ + buf.getBuffer(), buf.len() + })); } } diff --git a/tasmota/xdrv_23_zigbee_9_serial.ino b/tasmota/xdrv_23_zigbee_9_serial.ino index fd69ee16a..c14c7f248 100644 --- a/tasmota/xdrv_23_zigbee_9_serial.ino +++ b/tasmota/xdrv_23_zigbee_9_serial.ino @@ -595,7 +595,9 @@ int32_t ZigbeeProcessInputEZSP(class SBuffer &buf) { case EZSP_getNetworkParameters: // 2800 case EZSP_sendUnicast: // 3400 case EZSP_sendBroadcast: // 3600 + case EZSP_sendMulticast: // 3800 case EZSP_messageSentHandler: // 3F00 + case EZSP_incomingMessageHandler: // 4500 case EZSP_setConfigurationValue: // 5300 case EZSP_setPolicy: // 5500 case 0x0059: // 5900 - supposedly removed by still happening diff --git a/tasmota/xdrv_23_zigbee_A_impl.ino b/tasmota/xdrv_23_zigbee_A_impl.ino index 495db0e1b..5ca3b555e 100644 --- a/tasmota/xdrv_23_zigbee_A_impl.ino +++ b/tasmota/xdrv_23_zigbee_A_impl.ino @@ -212,6 +212,34 @@ void ZbApplyMultiplier(double &val_d, int8_t multiplier) { } } +// +// Send Attribute Write, apply mutlipliers before +// +bool ZbAppendWriteBuf(SBuffer & buf, const Z_attribute & attr, bool prepend_status_ok) { + double val_d = attr.getFloat(); + const char * val_str = attr.getStr(); + + if (attr.key_is_str) { return false; } + if (attr.isNum() && (1 != attr.attr_multiplier)) { + ZbApplyMultiplier(val_d, attr.attr_multiplier); + } + + // push the value in the buffer + buf.add16(attr.key.id.attr_id); // prepend with attribute identifier + if (prepend_status_ok) { + buf.add8(Z_SUCCESS); // status OK = 0x00 + } + buf.add8(attr.attr_type); // prepend with attribute type + int32_t res = encodeSingleAttribute(buf, val_d, val_str, attr.attr_type); + if (res < 0) { + // remove the attribute type we just added + // buf.setLen(buf.len() - (operation == ZCL_READ_ATTRIBUTES_RESPONSE ? 4 : 3)); + AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "Unsupported attribute type %04X/%04X '0x%02X'"), attr.key.id.cluster, attr.key.id.attr_id, attr.attr_type); + return false; + } + return true; +} + // Parse "Report", "Write", "Response" or "Condig" attribute // Operation is one of: ZCL_REPORT_ATTRIBUTES (0x0A), ZCL_WRITE_ATTRIBUTES (0x02) or ZCL_READ_ATTRIBUTES_RESPONSE (0x01) void ZbSendReportWrite(const JsonObject &val_pubwrite, uint16_t device, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint, uint16_t manuf, uint8_t operation) { @@ -226,99 +254,42 @@ void ZbSendReportWrite(const JsonObject &val_pubwrite, uint16_t device, uint16_t const char *key = it->key; const JsonVariant &value = it->value; - uint16_t attr_id = 0xFFFF; - uint16_t cluster_id = 0xFFFF; - uint8_t type_id = Znodata; - int8_t multiplier = 1; // multiplier to adjust the key value - double val_d = 0; // I try to avoid `double` but this type capture both float and (u)int32_t without prevision loss - const char* val_str = ""; // variant as string - - // check if the name has the format "XXXX/YYYY" where XXXX is the cluster, YYYY the attribute id - // alternative "XXXX/YYYY%ZZ" where ZZ is the type (for unregistered attributes) - char * delimiter = strchr(key, '/'); - char * delimiter2 = strchr(key, '%'); - if (delimiter) { - cluster_id = strtoul(key, &delimiter, 16); - if (!delimiter2) { - attr_id = strtoul(delimiter+1, nullptr, 16); - } else { - attr_id = strtoul(delimiter+1, &delimiter2, 16); - type_id = strtoul(delimiter2+1, nullptr, 16); - } - } - // AddLog_P2(LOG_LEVEL_DEBUG, PSTR("cluster_id = 0x%04X, attr_id = 0x%04X"), cluster_id, attr_id); - - // do we already know the type, i.e. attribute and cluster are also known - if (Znodata == type_id) { - // scan attributes to find by name, and retrieve type - for (uint32_t i = 0; i < ARRAY_SIZE(Z_PostProcess); i++) { - const Z_AttributeConverter *converter = &Z_PostProcess[i]; - bool match = false; - uint16_t local_attr_id = pgm_read_word(&converter->attribute); - uint16_t local_cluster_id = CxToCluster(pgm_read_byte(&converter->cluster_short)); - uint8_t local_type_id = pgm_read_byte(&converter->type); - int8_t local_multiplier = pgm_read_byte(&converter->multiplier); - // AddLog_P2(LOG_LEVEL_DEBUG, PSTR("Try cluster = 0x%04X, attr = 0x%04X, type_id = 0x%02X"), local_cluster_id, local_attr_id, local_type_id); - - if (delimiter) { - if ((cluster_id == local_cluster_id) && (attr_id == local_attr_id)) { - type_id = local_type_id; - break; - } - } else if (pgm_read_word(&converter->name_offset)) { - // AddLog_P2(LOG_LEVEL_DEBUG, PSTR("Comparing '%s' with '%s'"), attr_name, converter->name); - if (0 == strcasecmp_P(key, Z_strings + pgm_read_word(&converter->name_offset))) { - // match - cluster_id = local_cluster_id; - attr_id = local_attr_id; - type_id = local_type_id; - multiplier = local_multiplier; - break; - } - } - } - } - - // Buffer ready, do some sanity checks - // AddLog_P2(LOG_LEVEL_DEBUG, PSTR("cluster_id = 0x%04X, attr_id = 0x%04X, type_id = 0x%02X"), cluster_id, attr_id, type_id); - if ((0xFFFF == attr_id) || (0xFFFF == cluster_id)) { - Response_P(PSTR("{\"%s\":\"%s'%s'\"}"), XdrvMailbox.command, PSTR("Unknown attribute "), key); - return; - } - if (Znodata == type_id) { - Response_P(PSTR("{\"%s\":\"%s'%s'\"}"), XdrvMailbox.command, PSTR("Unknown attribute type for attribute "), key); - return; - } - - if (0xFFFF == cluster) { - cluster = cluster_id; // set the cluster for this packet - } else if (cluster != cluster_id) { - ResponseCmndChar_P(PSTR("No more than one cluster id per command")); - return; - } - - // //////////////////////////////////////////////////////////////////////////////// - // Split encoding depending on message - if (operation != ZCL_CONFIGURE_REPORTING) { - // apply multiplier if needed - val_d = value.as(); - val_str = value.as(); - ZbApplyMultiplier(val_d, multiplier); - - // push the value in the buffer - buf.add16(attr_id); // prepend with attribute identifier - if (operation == ZCL_READ_ATTRIBUTES_RESPONSE) { - buf.add8(Z_SUCCESS); // status OK = 0x00 - } - buf.add8(type_id); // prepend with attribute type - int32_t res = encodeSingleAttribute(buf, val_d, val_str, type_id); - if (res < 0) { - // remove the attribute type we just added - // buf.setLen(buf.len() - (operation == ZCL_READ_ATTRIBUTES_RESPONSE ? 4 : 3)); - Response_P(PSTR("{\"%s\":\"%s'%s' 0x%02X\"}"), XdrvMailbox.command, PSTR("Unsupported attribute type "), key, type_id); + Z_attribute attr; + attr.setKeyName(key); + if (Z_parseAttributeKey(attr)) { + // Buffer ready, do some sanity checks + if (0xFFFF == cluster) { + cluster = attr.key.id.cluster; // set the cluster for this packet + } else if (cluster != attr.key.id.cluster) { + ResponseCmndChar_P(PSTR("No more than one cluster id per command")); return; } + } else { + if (attr.key_is_str) { + Response_P(PSTR("{\"%s\":\"%s'%s'\"}"), XdrvMailbox.command, PSTR("Unknown attribute "), key); + return; + } + if (Zunk == attr.attr_type) { + Response_P(PSTR("{\"%s\":\"%s'%s'\"}"), XdrvMailbox.command, PSTR("Unknown attribute type for attribute "), key); + return; + } + } + + if (value.is()) { + attr.setStr(value.as()); + } else if (value.is()) { + attr.setFloat(value.as()); + } + + double val_d = 0; // I try to avoid `double` but this type capture both float and (u)int32_t without prevision loss + const char* val_str = ""; // variant as string + //////////////////////////////////////////////////////////////////////////////// + // Split encoding depending on message + if (operation != ZCL_CONFIGURE_REPORTING) { + if (!ZbAppendWriteBuf(buf, attr, operation == ZCL_READ_ATTRIBUTES_RESPONSE)) { + return; // error + } } else { // //////////////////////////////////////////////////////////////////////////////// // ZCL_CONFIGURE_REPORTING @@ -350,7 +321,7 @@ void ZbSendReportWrite(const JsonObject &val_pubwrite, uint16_t device, uint16_t if (nullptr != &val_attr_rc) { val_d = val_attr_rc.as(); val_str = val_attr_rc.as(); - ZbApplyMultiplier(val_d, multiplier); + ZbApplyMultiplier(val_d, attr.attr_multiplier); } // read TimeoutPeriod @@ -358,22 +329,22 @@ void ZbSendReportWrite(const JsonObject &val_pubwrite, uint16_t device, uint16_t const JsonVariant &val_attr_timeout = GetCaseInsensitive(attr_config, PSTR("TimeoutPeriod")); if (nullptr != &val_attr_timeout) { attr_timeout = strToUInt(val_attr_timeout); } - bool attr_discrete = Z_isDiscreteDataType(type_id); + bool attr_discrete = Z_isDiscreteDataType(attr.attr_type); // all fields are gathered, output the butes into the buffer, ZCL 2.5.7.1 // common bytes buf.add8(attr_direction ? 0x01 : 0x00); - buf.add16(attr_id); + buf.add16(attr.key.id.attr_id); if (attr_direction) { buf.add16(attr_timeout); } else { - buf.add8(type_id); + buf.add8(attr.attr_type); buf.add16(attr_min_interval); buf.add16(attr_max_interval); if (!attr_discrete) { - int32_t res = encodeSingleAttribute(buf, val_d, val_str, type_id); + int32_t res = encodeSingleAttribute(buf, val_d, val_str, attr.attr_type); if (res < 0) { - Response_P(PSTR("{\"%s\":\"%s'%s' 0x%02X\"}"), XdrvMailbox.command, PSTR("Unsupported attribute type "), key, type_id); + Response_P(PSTR("{\"%s\":\"%s'%s' 0x%02X\"}"), XdrvMailbox.command, PSTR("Unsupported attribute type "), key, attr.attr_type); return; } } @@ -1312,6 +1283,13 @@ void CmndZbConfig(void) { const JsonVariant &val_txradio = GetCaseInsensitive(json, PSTR("TxRadio")); if (nullptr != &val_txradio) { zb_txradio_dbm = strToUInt(val_txradio); } + // if network key is zero, we generate a truly random key with a hardware generator from ESP + if ((0 == zb_precfgkey_l) && (0 == zb_precfgkey_h)) { + AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "generating random Zigbee network key")); + zb_precfgkey_l = (uint64_t)HwRandom() << 32 | HwRandom(); + zb_precfgkey_h = (uint64_t)HwRandom() << 32 | HwRandom(); + } + // Check if a parameter was changed after all if ( (zb_channel != Settings.zb_channel) || (zb_pan_id != Settings.zb_pan_id) ||