mirror of https://github.com/arendst/Tasmota.git
Merge pull request #9313 from s-hadinger/zigbee_sep_14
Zigbee minor improvements
This commit is contained in:
commit
690e443b79
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) ||
|
||||
|
|
Loading…
Reference in New Issue