Merge pull request #9313 from s-hadinger/zigbee_sep_14

Zigbee minor improvements
This commit is contained in:
s-hadinger 2020-09-14 22:21:15 +02:00 committed by GitHub
commit 690e443b79
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 266 additions and 160 deletions

View File

@ -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
}

View File

@ -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) {

View File

@ -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

View File

@ -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

View File

@ -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: <cluster>/<attr> or <cluster>/<attr>%<type> or "<attribute_name>"
// 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

View File

@ -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<attr_len; i++) {
uint16_t attr = attr_list[i];
uint32_t ccccaaaa = (cluster << 16) | attr;
uint16_t attr_id = attr_list_ids[i];
uint32_t ccccaaaa = (cluster << 16) | attr_id;
Z_attribute attr;
attr.setKeyId(cluster, attr_id);
switch (ccccaaaa) {
case 0x00000004: json_out[F("Manufacturer")] = F(USE_ZIGBEE_MANUFACTURER); break; // Manufacturer
case 0x00000005: json_out[F("ModelId")] = F(USE_ZIGBEE_MODELID); break; // ModelId
case 0x00000004: attr.setStr(PSTR(USE_ZIGBEE_MANUFACTURER)); break; // Manufacturer
case 0x00000005: attr.setStr(PSTR(USE_ZIGBEE_MODELID)); break; // ModelId
#ifdef USE_LIGHT
case 0x00060000: json_out[F("Power")] = Light.power ? 1 : 0; break; // Power
case 0x00080000: json_out[F("Dimmer")] = LightGetDimmer(0); break; // Dimmer
case 0x00060000: attr.setUInt(Light.power ? 1 : 0); break; // Power
case 0x00080000: attr.setUInt(LightGetDimmer(0)); break; // Dimmer
case 0x03000000: // Hue
case 0x03000001: // Sat
@ -1799,50 +1800,70 @@ void Z_AutoResponder(uint16_t srcaddr, uint16_t cluster, uint8_t endpoint, const
uxy[i] = XY[i] * 65536.0f;
uxy[i] = (uxy[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()
}));
}
}

View File

@ -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

View File

@ -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<double>();
val_str = value.as<const char*>();
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<const char*>()) {
attr.setStr(value.as<const char*>());
} else if (value.is<double>()) {
attr.setFloat(value.as<float>());
}
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<double>();
val_str = val_attr_rc.as<const char*>();
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) ||