mirror of
https://github.com/arendst/Tasmota.git
synced 2025-07-24 19:26:37 +00:00
Zigbee minor improvements
This commit is contained in:
parent
297dedc774
commit
ea85b70685
@ -2006,3 +2006,27 @@ String Decompress(const char * compressed, size_t uncompressed_size) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#endif // USE_UNISHOX_COMPRESSION
|
#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);
|
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
|
// 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) {
|
const char * getCaseInsensitiveConstCharNull(const JsonObject &json, const char *needle) {
|
||||||
|
@ -101,11 +101,13 @@ public:
|
|||||||
SBuffer* bval;
|
SBuffer* bval;
|
||||||
char* sval;
|
char* sval;
|
||||||
} val;
|
} val;
|
||||||
Za_type type; // uint8_t in size, type of attribute, see above
|
Za_type type; // uint8_t in size, type of attribute, see above
|
||||||
bool key_is_str; // is the key a string?
|
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 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
|
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 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
|
// Constructor with all defaults
|
||||||
Z_attribute():
|
Z_attribute():
|
||||||
@ -115,7 +117,9 @@ public:
|
|||||||
key_is_str(false),
|
key_is_str(false),
|
||||||
key_is_pmem(false),
|
key_is_pmem(false),
|
||||||
val_str_raw(false),
|
val_str_raw(false),
|
||||||
key_suffix(1)
|
key_suffix(1),
|
||||||
|
attr_type(0xFF),
|
||||||
|
attr_multiplier(1)
|
||||||
{};
|
{};
|
||||||
|
|
||||||
Z_attribute(const Z_attribute & rhs) {
|
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 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
|
// get num values
|
||||||
float getFloat(void) const {
|
float getFloat(void) const {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
@ -483,6 +488,8 @@ protected:
|
|||||||
key_is_str = rhs.key_is_str;
|
key_is_str = rhs.key_is_str;
|
||||||
key_is_pmem = rhs.key_is_pmem;
|
key_is_pmem = rhs.key_is_pmem;
|
||||||
key_suffix = rhs.key_suffix;
|
key_suffix = rhs.key_suffix;
|
||||||
|
attr_type = rhs.attr_type;
|
||||||
|
attr_multiplier = rhs.attr_multiplier;
|
||||||
// copy value
|
// copy value
|
||||||
copyVal(rhs);
|
copyVal(rhs);
|
||||||
// don't touch next pointer
|
// 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
|
// Display the tracked status for a light
|
||||||
String Z_Devices::dumpLightState(uint16_t shortaddr) const {
|
String Z_Devices::dumpLightState(uint16_t shortaddr) const {
|
||||||
DynamicJsonBuffer jsonBuffer;
|
Z_attribute_list attr_list;
|
||||||
JsonObject& json = jsonBuffer.createObject();
|
|
||||||
char hex[8];
|
char hex[8];
|
||||||
|
|
||||||
const Z_Device & device = findShortAddr(shortaddr);
|
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?
|
attr_list.addAttribute(F(D_JSON_ZIGBEE_DEVICE)).setStr(hex);
|
||||||
|
if (fname) {
|
||||||
snprintf_P(hex, sizeof(hex), PSTR("0x%04X"), shortaddr);
|
attr_list.addAttribute(F(D_JSON_ZIGBEE_NAME)).setStr(fname);
|
||||||
|
|
||||||
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; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String payload = "";
|
if (foundDevice(device)) {
|
||||||
payload.reserve(200);
|
// expose the last known status of the bulb, for Hue integration
|
||||||
json.printTo(payload);
|
attr_list.addAttribute(F(D_JSON_ZIGBEE_LIGHT)).setInt(getHueBulbtype(shortaddr)); // sign extend, 0xFF changed as -1
|
||||||
return payload;
|
// 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
|
// 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
|
#endif // USE_ZIGBEE
|
||||||
|
@ -1768,20 +1768,21 @@ int32_t Z_State_Ready(uint8_t value) {
|
|||||||
//
|
//
|
||||||
// Mostly used for routers/end-devices
|
// Mostly used for routers/end-devices
|
||||||
// json: holds the attributes in JSON format
|
// 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) {
|
void Z_AutoResponder(uint16_t srcaddr, uint16_t cluster, uint8_t endpoint, const uint16_t *attr_list_ids, size_t attr_len) {
|
||||||
DynamicJsonBuffer jsonBuffer;
|
Z_attribute_list attr_list;
|
||||||
JsonObject& json_out = jsonBuffer.createObject();
|
|
||||||
|
|
||||||
for (uint32_t i=0; i<attr_len; i++) {
|
for (uint32_t i=0; i<attr_len; i++) {
|
||||||
uint16_t attr = attr_list[i];
|
uint16_t attr_id = attr_list_ids[i];
|
||||||
uint32_t ccccaaaa = (cluster << 16) | attr;
|
uint32_t ccccaaaa = (cluster << 16) | attr_id;
|
||||||
|
Z_attribute attr;
|
||||||
|
attr.setKeyId(cluster, attr_id);
|
||||||
|
|
||||||
switch (ccccaaaa) {
|
switch (ccccaaaa) {
|
||||||
case 0x00000004: json_out[F("Manufacturer")] = F(USE_ZIGBEE_MANUFACTURER); break; // Manufacturer
|
case 0x00000004: attr.setStr(PSTR(USE_ZIGBEE_MANUFACTURER)); break; // Manufacturer
|
||||||
case 0x00000005: json_out[F("ModelId")] = F(USE_ZIGBEE_MODELID); break; // ModelId
|
case 0x00000005: attr.setStr(PSTR(USE_ZIGBEE_MODELID)); break; // ModelId
|
||||||
#ifdef USE_LIGHT
|
#ifdef USE_LIGHT
|
||||||
case 0x00060000: json_out[F("Power")] = Light.power ? 1 : 0; break; // Power
|
case 0x00060000: attr.setUInt(Light.power ? 1 : 0); break; // Power
|
||||||
case 0x00080000: json_out[F("Dimmer")] = LightGetDimmer(0); break; // Dimmer
|
case 0x00080000: attr.setUInt(LightGetDimmer(0)); break; // Dimmer
|
||||||
|
|
||||||
case 0x03000000: // Hue
|
case 0x03000000: // Hue
|
||||||
case 0x03000001: // Sat
|
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] = XY[i] * 65536.0f;
|
||||||
uxy[i] = (uxy[i] > 0xFEFF) ? uxy[i] : 0xFEFF;
|
uxy[i] = (uxy[i] > 0xFEFF) ? uxy[i] : 0xFEFF;
|
||||||
}
|
}
|
||||||
if (0x0000 == attr) { json_out[F("Hue")] = changeUIntScale(hue, 0, 360, 0, 254); }
|
if (0x0000 == attr_id) { attr.setUInt(changeUIntScale(hue, 0, 360, 0, 254)); }
|
||||||
if (0x0001 == attr) { json_out[F("Sat")] = changeUIntScale(sat, 0, 255, 0, 254); }
|
if (0x0001 == attr_id) { attr.setUInt(changeUIntScale(sat, 0, 255, 0, 254)); }
|
||||||
if (0x0003 == attr) { json_out[F("X")] = uxy[0]; }
|
if (0x0003 == attr_id) { attr.setUInt(uxy[0]); }
|
||||||
if (0x0004 == attr) { json_out[F("Y")] = uxy[1]; }
|
if (0x0004 == attr_id) { attr.setUInt(uxy[1]); }
|
||||||
if (0x0007 == attr) { json_out[F("CT")] = LightGetColorTemp(); }
|
if (0x0007 == attr_id) { attr.setUInt(LightGetColorTemp()); }
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
#endif
|
#endif
|
||||||
case 0x000A0000: // Time
|
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;
|
break;
|
||||||
case 0x000AFF00: // TimeEpoch - Tasmota specific
|
case 0x000AFF00: // TimeEpoch - Tasmota specific
|
||||||
json_out[F("TimeEpoch")] = Rtc.utc_time;
|
attr.setUInt(Rtc.utc_time);
|
||||||
break;
|
break;
|
||||||
case 0x000A0001: // TimeStatus
|
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;
|
break;
|
||||||
case 0x000A0002: // TimeZone
|
case 0x000A0002: // TimeZone
|
||||||
json_out[F("TimeZone")] = Settings.toffset[0] * 60;
|
attr.setUInt(Settings.toffset[0] * 60);
|
||||||
break;
|
break;
|
||||||
case 0x000A0007: // LocalTime // TODO take DST
|
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;
|
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
|
// we have a non-empty output
|
||||||
|
|
||||||
// log first
|
// log first
|
||||||
String msg("");
|
|
||||||
msg.reserve(100);
|
|
||||||
json_out.printTo(msg);
|
|
||||||
AddLog_P2(LOG_LEVEL_INFO, PSTR("ZIG: Auto-responder: ZbSend {\"Device\":\"0x%04X\""
|
AddLog_P2(LOG_LEVEL_INFO, PSTR("ZIG: Auto-responder: ZbSend {\"Device\":\"0x%04X\""
|
||||||
",\"Cluster\":\"0x%04X\""
|
",\"Cluster\":\"0x%04X\""
|
||||||
",\"Endpoint\":%d"
|
",\"Endpoint\":%d"
|
||||||
",\"Response\":%s}"
|
",\"Response\":%s}"
|
||||||
),
|
),
|
||||||
srcaddr, cluster, endpoint,
|
srcaddr, cluster, endpoint,
|
||||||
msg.c_str());
|
attr_list.toString().c_str());
|
||||||
|
|
||||||
// send
|
// send
|
||||||
const JsonVariant &json_out_v = json_out;
|
// all good, send the packet
|
||||||
ZbSendReportWrite(json_out_v, srcaddr, 0 /* group */,cluster, endpoint, 0 /* manuf */, ZCL_READ_ATTRIBUTES_RESPONSE);
|
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_getNetworkParameters: // 2800
|
||||||
case EZSP_sendUnicast: // 3400
|
case EZSP_sendUnicast: // 3400
|
||||||
case EZSP_sendBroadcast: // 3600
|
case EZSP_sendBroadcast: // 3600
|
||||||
|
case EZSP_sendMulticast: // 3800
|
||||||
case EZSP_messageSentHandler: // 3F00
|
case EZSP_messageSentHandler: // 3F00
|
||||||
|
case EZSP_incomingMessageHandler: // 4500
|
||||||
case EZSP_setConfigurationValue: // 5300
|
case EZSP_setConfigurationValue: // 5300
|
||||||
case EZSP_setPolicy: // 5500
|
case EZSP_setPolicy: // 5500
|
||||||
case 0x0059: // 5900 - supposedly removed by still happening
|
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
|
// 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)
|
// 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) {
|
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 char *key = it->key;
|
||||||
const JsonVariant &value = it->value;
|
const JsonVariant &value = it->value;
|
||||||
|
|
||||||
uint16_t attr_id = 0xFFFF;
|
Z_attribute attr;
|
||||||
uint16_t cluster_id = 0xFFFF;
|
attr.setKeyName(key);
|
||||||
uint8_t type_id = Znodata;
|
if (Z_parseAttributeKey(attr)) {
|
||||||
int8_t multiplier = 1; // multiplier to adjust the key value
|
// Buffer ready, do some sanity checks
|
||||||
double val_d = 0; // I try to avoid `double` but this type capture both float and (u)int32_t without prevision loss
|
if (0xFFFF == cluster) {
|
||||||
const char* val_str = ""; // variant as string
|
cluster = attr.key.id.cluster; // set the cluster for this packet
|
||||||
|
} else if (cluster != attr.key.id.cluster) {
|
||||||
// check if the name has the format "XXXX/YYYY" where XXXX is the cluster, YYYY the attribute id
|
ResponseCmndChar_P(PSTR("No more than one cluster id per command"));
|
||||||
// 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);
|
|
||||||
return;
|
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 {
|
} else {
|
||||||
// ////////////////////////////////////////////////////////////////////////////////
|
// ////////////////////////////////////////////////////////////////////////////////
|
||||||
// ZCL_CONFIGURE_REPORTING
|
// ZCL_CONFIGURE_REPORTING
|
||||||
@ -350,7 +321,7 @@ void ZbSendReportWrite(const JsonObject &val_pubwrite, uint16_t device, uint16_t
|
|||||||
if (nullptr != &val_attr_rc) {
|
if (nullptr != &val_attr_rc) {
|
||||||
val_d = val_attr_rc.as<double>();
|
val_d = val_attr_rc.as<double>();
|
||||||
val_str = val_attr_rc.as<const char*>();
|
val_str = val_attr_rc.as<const char*>();
|
||||||
ZbApplyMultiplier(val_d, multiplier);
|
ZbApplyMultiplier(val_d, attr.attr_multiplier);
|
||||||
}
|
}
|
||||||
|
|
||||||
// read TimeoutPeriod
|
// 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"));
|
const JsonVariant &val_attr_timeout = GetCaseInsensitive(attr_config, PSTR("TimeoutPeriod"));
|
||||||
if (nullptr != &val_attr_timeout) { attr_timeout = strToUInt(val_attr_timeout); }
|
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
|
// all fields are gathered, output the butes into the buffer, ZCL 2.5.7.1
|
||||||
// common bytes
|
// common bytes
|
||||||
buf.add8(attr_direction ? 0x01 : 0x00);
|
buf.add8(attr_direction ? 0x01 : 0x00);
|
||||||
buf.add16(attr_id);
|
buf.add16(attr.key.id.attr_id);
|
||||||
if (attr_direction) {
|
if (attr_direction) {
|
||||||
buf.add16(attr_timeout);
|
buf.add16(attr_timeout);
|
||||||
} else {
|
} else {
|
||||||
buf.add8(type_id);
|
buf.add8(attr.attr_type);
|
||||||
buf.add16(attr_min_interval);
|
buf.add16(attr_min_interval);
|
||||||
buf.add16(attr_max_interval);
|
buf.add16(attr_max_interval);
|
||||||
if (!attr_discrete) {
|
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) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1312,6 +1283,13 @@ void CmndZbConfig(void) {
|
|||||||
const JsonVariant &val_txradio = GetCaseInsensitive(json, PSTR("TxRadio"));
|
const JsonVariant &val_txradio = GetCaseInsensitive(json, PSTR("TxRadio"));
|
||||||
if (nullptr != &val_txradio) { zb_txradio_dbm = strToUInt(val_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
|
// Check if a parameter was changed after all
|
||||||
if ( (zb_channel != Settings.zb_channel) ||
|
if ( (zb_channel != Settings.zb_channel) ||
|
||||||
(zb_pan_id != Settings.zb_pan_id) ||
|
(zb_pan_id != Settings.zb_pan_id) ||
|
||||||
|
Loading…
x
Reference in New Issue
Block a user