Tasmota/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_1z_libs.ino

990 lines
32 KiB
C++

/*
xdrv_23_zigbee_1z_libs.ino - zigbee support for Tasmota, JSON replacement libs
Copyright (C) 2021 Theo Arends and Stephan Hadinger
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifdef USE_ZIGBEE
/*********************************************************************************************\
* Replacement libs for JSON to output a list of attributes
\*********************************************************************************************/
// simplified version of strcmp accepting both arguments to be in PMEM, and accepting nullptr arguments
// inspired from https://code.woboq.org/userspace/glibc/string/strcmp.c.html
int strcmp_PP(const char *p1, const char *p2) {
if (p1 == p2) { return 0; } // equality
if (!p1) { return -1; } // first string is null
if (!p2) { return 1; } // second string is null
const unsigned char *s1 = (const unsigned char *) p1;
const unsigned char *s2 = (const unsigned char *) p2;
unsigned char c1, c2;
do {
c1 = (unsigned char) pgm_read_byte(s1);
s1++;
c2 = (unsigned char) pgm_read_byte(s2);
s2++;
if (c1 == '\0')
return c1 - c2;
}
while (c1 == c2);
return c1 - c2;
}
/*********************************************************************************************\
*
* Variables for Rules from last Zigbee message received
*
\*********************************************************************************************/
typedef struct Z_LastMessageVars {
uint16_t device; // device short address
uint16_t groupaddr; // group address
uint16_t cluster; // cluster id
uint8_t endpoint; // source endpoint
} Z_LastMessageVars;
Z_LastMessageVars gZbLastMessage;
uint16_t Z_GetLastDevice(void) { return gZbLastMessage.device; }
uint16_t Z_GetLastGroup(void) { return gZbLastMessage.groupaddr; }
uint16_t Z_GetLastCluster(void) { return gZbLastMessage.cluster; }
uint8_t Z_GetLastEndpoint(void) { return gZbLastMessage.endpoint; }
/*********************************************************************************************\
*
* Class for single attribute
*
\*********************************************************************************************/
enum class Za_type : uint8_t {
Za_none, // empty, translates into null in JSON
// numericals
Za_bool, // boolean, translates to true/false, uses uval32 to store
Za_uint, // unsigned 32 int, uses uval32
Za_int, // signed 32 int, uses ival32
Za_float, // float 32, uses fval
// non-nummericals
Za_raw, // bytes buffer, uses bval
Za_str, // string, uses sval
// sub_objects
Za_obj, // json sub-object
Za_arr, // array sub-object (string add-only)
};
class Z_attribute {
public:
// cluster+attribute_id and/or plain name
uint16_t cluster; // only valid if both cluster and attr_id are not 0xFFFF
uint16_t attr_id; // only valid if both cluster and attr_id are not 0xFFFF
char* key; // only valid if `key_is_str` is true. Should not be freed if `key_is_pmem` is true
// attribute value
union {
uint32_t uval32;
int32_t ival32;
float fval;
SBuffer* bval;
char* sval; // string should be freed
class Z_attribute_list * objval; // contains a list of attributes, should be printed as a map (although it's just a list internally)
class JsonGeneratorArray * arrval; // contains a JSON string representing an array of values
} 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
bool key_is_cmd; // if command, cmd_id is the low 8 bits of attr_id.
// Bit #8 is `0` command sent to device or `1` command received from device
// Bit #9 is `0` command is cluster specific, or `1` general_command
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)
uint16_t manuf; // manufacturer id (0 if none)
uint32_t attr_multiplier; // [opt] multiplier for attribute, defaults to 0x01 (no change)
uint32_t attr_divider; // [opt] divider
int32_t attr_base; // [opt] base for conversion
// Constructor with all defaults
Z_attribute():
cluster(0xFFFF),
attr_id(0xFFFF),
key(nullptr),
val{ .uval32 = 0x0000 },
type(Za_type::Za_none),
key_is_str(false),
key_is_pmem(false),
val_str_raw(false),
key_is_cmd(false),
key_suffix(1),
attr_type(0xFF),
attr_multiplier(1),
attr_divider(1),
attr_base(0),
manuf(0)
{};
Z_attribute(const Z_attribute & rhs) {
deepCopy(rhs);
}
Z_attribute & operator = (const Z_attribute & rhs) {
freeKey();
freeVal();
deepCopy(rhs);
return *this;
}
// Destructor, free memory that was allocated
~Z_attribute() {
freeKey();
freeVal();
}
// free any allocated memoruy for values
void freeVal(void);
// free any allocated memoruy for keys
void freeKey(void);
// set key name
void setKeyName(const char * _key, bool pmem = false);
// provide two entries and concat
void setKeyName(const char * _key, const char * _key2);
void setKeyId(uint16_t cluster, uint16_t attr_id);
void setCmdId(uint16_t cluster, uint8_t cmd_id, bool direction, bool cmd_general);
// Setters
void setNone(void);
void setUInt(uint32_t _val);
void setBool(bool _val);
void setInt(int32_t _val);
void setFloat(float _val);
void setRaw(const void* buf, size_t len);
void setBuf(const SBuffer &buf, size_t index, size_t len);
// specific formatters
void setHex32(uint32_t _val);
void setHex64(uint64_t _val);
// set the string value
// PMEM argument is allowed
// string will be copied, so it can be changed later
// nullptr is allowed and considered as empty string
// Note: memory is allocated only if string is non-empty
void setStr(const char * _val);
inline void setStrRaw(const char * _val) {
setStr(_val);
val_str_raw = true;
}
Z_attribute_list & newAttrList(void);
JsonGeneratorArray & newJsonArray(void);
inline bool isNum(void) const { return (type >= Za_type::Za_bool) && (type <= Za_type::Za_float); }
inline bool isNone(void) const { return (type == Za_type::Za_none);}
// get num values
float getFloat(void) const;
int32_t getInt(void) const;
uint32_t getUInt(void) const;
bool getBool(void) const;
// optimistically try to get any value as literal or in string - double to not lose precision for 32 bits
double getOptimisticDouble(void) const;
//
const SBuffer * getRaw(void) const;
// always return a point to a string, if not defined then empty string.
// Never returns nullptr
const char * getStr(void) const;
bool equalsKey(const Z_attribute & attr2, bool ignore_key_suffix = false) const;
bool equalsId(uint16_t cluster, uint16_t attr_id, uint8_t suffix = 0) const;
bool equalsCmd(uint16_t cluster, uint8_t cmd_id, bool direction, bool cmd_general, uint8_t suffix = 0) const;
bool equalsKey(const char * name, uint8_t suffix = 0) const;
bool equalsVal(const Z_attribute & attr2) const;
bool equals(const Z_attribute & attr2) const;
String toString(bool prefix_comma = false) const;
// copy value from one attribute to another, without changing its type
void copyVal(const Z_attribute & rhs);
protected:
void deepCopy(const Z_attribute & rhs);
};
/*********************************************************************************************\
*
* Class for attribute ordered list
*
\*********************************************************************************************/
// Attribute list
// Contains meta-information:
// - source endpoint (is conflicting)
// - LQI (if not conflicting)
class Z_attribute_list : public LList<Z_attribute> {
public:
uint8_t src_ep; // source endpoint, 0xFF if unknown
uint8_t lqi; // linkquality, 0xFF if unknown
uint16_t group_id; // group address OxFFFF if inknown
uint8_t batt_percent; // battery percentage
Z_attribute_list():
LList<Z_attribute>(), // call superclass constructor
src_ep(0xFF),
lqi(0xFF),
group_id(0xFFFF)
{};
// we don't define any destructor, the superclass destructor is automatically called
// reset object to its initial state
// free all allocated memory
void reset(void) {
LList<Z_attribute>::reset();
src_ep = 0xFF;
lqi = 0xFF;
group_id = 0xFFFF;
batt_percent = 0xFF;
}
inline bool validSrcEp(void) const { return 0xFF != src_ep; }
inline bool validLQI(void) const { return 0xFF != lqi; }
inline bool validGroupId(void) const { return 0xFFFF != group_id; }
inline bool validBattPercent(void) const { return 0xFF != batt_percent; }
inline void setBattPercent(uint8_t batt) { batt_percent = batt; }
// the following addAttribute() compute the suffix and increments it
// Add attribute to the list, given cluster and attribute id
Z_attribute & addAttribute(uint16_t cluster, uint16_t attr_id, uint8_t suffix = 0);
// ZCL command
Z_attribute & addAttributeCmd(uint16_t cluster, uint8_t cmd_id, bool direction, bool cmd_general, uint8_t suffix = 0);
// Add attribute to the list, given name
Z_attribute & addAttribute(const char * name, bool pmem = false, uint8_t suffix = 0);
Z_attribute & addAttribute(const char * name, const char * name2, uint8_t suffix = 0);
inline Z_attribute & addAttribute(const __FlashStringHelper * name, uint8_t suffix = 0) {
return addAttribute((const char*) name, true, suffix);
}
// smaller version called often to reduce code size
Z_attribute & addAttributePMEM(const char * name);
// Remove from list by reference, if null or not found, then do nothing
inline void removeAttribute(const Z_attribute * attr) { remove(attr); }
// dump the entire structure as JSON, starting from head (as parameter)
// does not start not end with a comma
// do we enclosed in brackets '{' '}'
String toString(bool enclose_brackets = false, bool include_battery = false) const;
// find if attribute with same key already exists, return null if not found
const Z_attribute * findAttribute(uint16_t cluster, uint16_t attr_id, uint8_t suffix = 0) const;
const Z_attribute * findAttributeCmd(uint16_t cluster, uint8_t cmd_id, bool direction, bool cmd_general, uint8_t suffix = 0) const;
const Z_attribute * findAttribute(const char * name, uint8_t suffix = 0) const;
const Z_attribute * findAttribute(const Z_attribute &attr) const; // suffix always count here
// non-const variants
inline Z_attribute * findAttribute(uint16_t cluster, uint16_t attr_id, uint8_t suffix = 0) {
return (Z_attribute*) ((const Z_attribute_list*)this)->findAttribute(cluster, attr_id, suffix);
}
inline Z_attribute * findAttributeCmd(uint16_t cluster, uint8_t cmd_id, bool direction, bool cmd_general, uint8_t suffix = 0) {
return (Z_attribute*) ((const Z_attribute_list*)this)->findAttributeCmd(cluster, cmd_id, direction, cmd_general, suffix);
}
inline Z_attribute * findAttribute(const char * name, uint8_t suffix = 0) {
return (Z_attribute*) (((const Z_attribute_list*)this)->findAttribute(name, suffix));
}
inline Z_attribute * findAttribute(const Z_attribute &attr) {
return (Z_attribute*) ((const Z_attribute_list*)this)->findAttribute(attr);
}
// count matching attributes, ignoring suffix
size_t countAttribute(uint16_t cluster, uint16_t attr_id) const ;
size_t countAttribute(const char * name) const ;
// if suffix == 0, we don't care and find the first match
Z_attribute & findOrCreateAttribute(uint16_t cluster, uint16_t attr_id, uint8_t suffix = 0);
Z_attribute & findOrCreateAttribute(const char * name, uint8_t suffix = 0);
Z_attribute & findOrCreateCmd(uint16_t cluster, uint8_t cmd_id, bool direction, bool cmd_general, uint8_t suffix = 0);
// always care about suffix
Z_attribute & findOrCreateAttribute(const Z_attribute &attr);
// replace attribute with new value, suffix does care
Z_attribute & replaceOrCreate(const Z_attribute &attr);
// merge with secondary list, return true if ok, false if conflict
bool mergeList(const Z_attribute_list &list2);
};
Z_attribute & Z_attribute_list::addAttributePMEM(const char * name) {
return addAttribute(name, true, 0);
}
/*********************************************************************************************\
*
* Implementation for Z_attribute
*
\*********************************************************************************************/
// free any allocated memoruy for keys
void Z_attribute::freeKey(void) {
if (key_is_str && key && !key_is_pmem) { delete[] key; }
key = nullptr;
}
// set key name
void Z_attribute::setKeyName(const char * _key, bool pmem) {
freeKey();
key_is_str = true;
key_is_pmem = pmem;
if (pmem) {
key = (char*) _key;
} else {
setKeyName(_key, nullptr);
}
}
// provide two entries and concat
void Z_attribute::setKeyName(const char * _key, const char * _key2) {
freeKey();
key_is_str = true;
key_is_pmem = false;
if (_key) {
size_t key_len = strlen_P(_key);
if (_key2) {
key_len += strlen_P(_key2);
}
key = new char[key_len+1];
strcpy_P(key, _key);
if (_key2) {
strcat_P(key, _key2);
}
}
}
void Z_attribute::setKeyId(uint16_t _cluster, uint16_t _attr_id) {
freeKey();
key_is_str = false;
cluster = _cluster;
attr_id = _attr_id;
key_is_cmd = false;
}
void Z_attribute::setCmdId(uint16_t _cluster, uint8_t _cmd_id, bool direction, bool cmd_general) {
freeKey();
key_is_str = false;
cluster = _cluster;
attr_id = _cmd_id | (direction ? 0x100 : 0x000) | (cmd_general ? 0x200 : 0x000);
key_is_cmd = true;
}
// Setters
void Z_attribute::setNone(void) {
freeVal(); // free any previously allocated memory
val.uval32 = 0;
type = Za_type::Za_none;
}
void Z_attribute::setUInt(uint32_t _val) {
freeVal(); // free any previously allocated memory
val.uval32 = _val;
type = Za_type::Za_uint;
}
void Z_attribute::setBool(bool _val) {
freeVal(); // free any previously allocated memory
val.uval32 = _val;
type = Za_type::Za_bool;
}
void Z_attribute::setInt(int32_t _val) {
freeVal(); // free any previously allocated memory
val.ival32 = _val;
type = Za_type::Za_int;
}
void Z_attribute::setFloat(float _val) {
freeVal(); // free any previously allocated memory
val.fval = _val;
type = Za_type::Za_float;
}
void Z_attribute::setRaw(const void* buf, size_t len) {
freeVal();
if (len) {
val.bval = new SBuffer(len);
val.bval->addBuffer((const uint8_t*)buf, len);
}
type = Za_type::Za_raw;
}
void Z_attribute::setBuf(const SBuffer &buf, size_t index, size_t len) {
freeVal();
if (len) {
val.bval = new SBuffer(len);
val.bval->addBuffer(buf.buf(index), len);
}
type = Za_type::Za_raw;
}
void Z_attribute::setHex32(uint32_t _val) {
char hex[8];
snprintf_P(hex, sizeof(hex), PSTR("0x%04X"), _val);
setStr(hex);
}
void Z_attribute::setHex64(uint64_t _val) {
char hex[22];
ext_snprintf_P(hex, sizeof(hex), PSTR("0x%_X"), &_val);
setStr(hex);
}
// set the string value
// PMEM argument is allowed
// string will be copied, so it can be changed later
// nullptr is allowed and considered as empty string
// Note: memory is allocated only if string is non-empty
void Z_attribute::setStr(const char * _val) {
freeVal(); // free any previously allocated memory
val_str_raw = false;
// val.sval is always nullptr after freeVal()
if (_val) {
size_t len = strlen_P(_val);
if (len) {
val.sval = new char[len+1];
strcpy_P(val.sval, _val);
}
}
type = Za_type::Za_str;
}
Z_attribute_list & Z_attribute::newAttrList(void) {
freeVal();
val.objval = new Z_attribute_list();
type = Za_type::Za_obj;
return *val.objval;
}
JsonGeneratorArray & Z_attribute::newJsonArray(void) {
freeVal();
val.arrval = new JsonGeneratorArray();
type = Za_type::Za_arr;
return *val.arrval;
}
// get num values
double Z_attribute::getOptimisticDouble(void) const {
switch (type) {
case Za_type::Za_bool:
case Za_type::Za_uint: return (double) val.uval32;
case Za_type::Za_int: return (double) val.ival32;
case Za_type::Za_float: return (double) val.fval;
case Za_type::Za_str: return JsonParserToken::json_strtof(val.sval);
default: return 0.0;
}
}
float Z_attribute::getFloat(void) const {
switch (type) {
case Za_type::Za_bool:
case Za_type::Za_uint: return (float) val.uval32;
case Za_type::Za_int: return (float) val.ival32;
case Za_type::Za_float: return val.fval;
default: return 0.0f;
}
}
int32_t Z_attribute::getInt(void) const {
switch (type) {
case Za_type::Za_bool:
case Za_type::Za_uint: return (int32_t) val.uval32;
case Za_type::Za_int: return val.ival32;
case Za_type::Za_float: return (int32_t) val.fval;
default: return 0;
}
}
uint32_t Z_attribute::getUInt(void) const {
switch (type) {
case Za_type::Za_bool:
case Za_type::Za_uint: return val.uval32;
case Za_type::Za_int: return (uint32_t) val.ival32;
case Za_type::Za_float: return (uint32_t) val.fval;
default: return 0;
}
}
bool Z_attribute::getBool(void) const {
switch (type) {
case Za_type::Za_bool:
case Za_type::Za_uint: return val.uval32 ? true : false;
case Za_type::Za_int: return val.ival32 ? true : false;
case Za_type::Za_float: return val.fval ? true : false;
default: return false;
}
}
const SBuffer * Z_attribute::getRaw(void) const {
if (Za_type::Za_raw == type) { return val.bval; }
return nullptr;
}
// always return a point to a string, if not defined then empty string.
// Never returns nullptr
const char * Z_attribute::getStr(void) const {
if (Za_type::Za_str == type) { return val.sval; }
return "";
}
bool Z_attribute::equalsKey(const Z_attribute & attr2, bool ignore_key_suffix) const {
// check if keys are equal
if (key_is_str != attr2.key_is_str) { return false; }
if (key_is_str) {
if (strcmp_PP(key, attr2.key)) { return false; }
} else {
if ((this->cluster != attr2.cluster) ||
(this->attr_id != attr2.attr_id) ||
(this->key_is_cmd != attr2.key_is_cmd)) { return false; }
}
if (!ignore_key_suffix) {
if (key_suffix != attr2.key_suffix) { return false; }
}
return true;
}
bool Z_attribute::equalsId(uint16_t _cluster, uint16_t _attr_id, uint8_t suffix) const {
if (key_is_cmd || key_is_str) { return false; }
if ((this->cluster == _cluster) && (this->attr_id == _attr_id)) {
if (suffix) {
if (key_suffix == suffix) { return true; }
} else {
return true;
}
}
return false;
}
bool Z_attribute::equalsCmd(uint16_t _cluster, uint8_t _cmd_id, bool _direction, bool cmd_general, uint8_t suffix) const {
if (!key_is_cmd || key_is_str) { return false; }
uint16_t _attr_id = _cmd_id | (_direction ? 0x100 : 0x000) | (cmd_general ? 0x200 : 0x000);
if ((this->cluster == _cluster) && (this->attr_id == _attr_id)) {
if (suffix) {
if (key_suffix == suffix) { return true; }
} else {
return true;
}
}
return false;
}
bool Z_attribute::equalsKey(const char * name, uint8_t suffix) const {
if (key_is_str) {
if (0 == strcmp_PP(key, name)) {
if (suffix) {
if (key_suffix == suffix) { return true; }
} else {
return true;
}
}
}
return false;
}
bool Z_attribute::equalsVal(const Z_attribute & attr2) const {
if (type != attr2.type) { return false; }
if ((type >= Za_type::Za_bool) && (type <= Za_type::Za_float)) {
// numerical value
if (val.uval32 != attr2.val.uval32) { return false; }
} else if (type == Za_type::Za_raw) {
// compare 2 Static buffers
return SBuffer::equalsSBuffer(val.bval, attr2.val.bval);
} else if (type == Za_type::Za_str) {
// if (val_str_raw != attr2.val_str_raw) { return false; }
if (strcmp_PP(val.sval, attr2.val.sval)) { return false; }
} else if (type == Za_type::Za_obj) {
return false; // TODO for now we'll assume sub-objects are always different
} else if (type == Za_type::Za_arr) {
return false; // TODO for now we'll assume sub-objects are always different
}
return true;
}
bool Z_attribute::equals(const Z_attribute & attr2) const {
return equalsKey(attr2) && equalsVal(attr2);
}
String Z_attribute::toString(bool prefix_comma) const {
String res("");
if (prefix_comma) { res += ','; }
res += '"';
// compute the attribute name
if (key_is_str) {
if (key) { res += EscapeJSONString(key); }
else { res += F("null"); } // shouldn't happen
if (key_suffix > 1) {
res += key_suffix;
}
} else {
char attr_name[12];
if (!key_is_cmd) { // regular attribute
snprintf_P(attr_name, sizeof(attr_name), PSTR("%04X/%04X"), this->cluster, this->attr_id);
} else { // cmd
bool direction = (this->attr_id & 0x100);
bool cmd_general = (this->attr_id & 0x200);
uint8_t cmd_id = this->attr_id & 0xFF;
char cmd_char = cmd_general ? (direction ? '^' : '_') : (direction ? '?' : '!');
snprintf_P(attr_name, sizeof(attr_name), PSTR("%04X%c%02X"), this->cluster, cmd_char, cmd_id);
}
res += attr_name;
if (key_suffix > 1) {
res += '+';
res += key_suffix;
}
}
res += F("\":");
// value part
switch (type) {
case Za_type::Za_none:
res += F("null");
break;
case Za_type::Za_bool:
res += val.uval32 ? F("true") : F("false");
break;
case Za_type::Za_uint:
res += val.uval32;
break;
case Za_type::Za_int:
res += val.ival32;
break;
case Za_type::Za_float:
{
String fstr(val.fval, 2);
size_t last = fstr.length() - 1;
// remove trailing zeros
while (fstr[last] == '0') {
fstr.remove(last--);
}
// remove trailing dot
if (fstr[last] == '.') {
fstr.remove(last);
}
res += fstr;
}
break;
case Za_type::Za_raw:
res += '"';
if (val.bval) {
size_t blen = val.bval->len();
// print as HEX
char hex[2*blen+1];
ToHex_P(val.bval->getBuffer(), blen, hex, sizeof(hex));
res += hex;
}
res += '"';
break;
case Za_type::Za_str:
if (val_str_raw) {
if (val.sval) { res += val.sval; }
} else {
res += '"';
if (val.sval) {
res += EscapeJSONString(val.sval); // escape JSON chars
}
res += '"';
}
break;
case Za_type::Za_obj:
res += '{';
if (val.objval) {
res += val.objval->toString();
}
res += '}';
break;
case Za_type::Za_arr:
if (val.arrval) {
res += val.arrval->toString();
} else {
// res += '[';
// res += ']';
res += F("[]");
}
break;
}
return res;
}
// copy value from one attribute to another, without changing its type
void Z_attribute::copyVal(const Z_attribute & rhs) {
freeVal();
// copy value
val.uval32 = 0x00000000;
type = rhs.type;
if (rhs.isNum()) {
val.uval32 = rhs.val.uval32;
} else if (rhs.type == Za_type::Za_raw) {
if (rhs.val.bval) {
val.bval = new SBuffer(rhs.val.bval->len());
val.bval->addBuffer(*(rhs.val.bval));
}
} else if (rhs.type == Za_type::Za_str) {
if (rhs.val.sval) {
size_t s_len = strlen_P(rhs.val.sval);
val.sval = new char[s_len+1];
strcpy_P(val.sval, rhs.val.sval);
}
}
val_str_raw = rhs.val_str_raw;
}
// free any allocated memoruy for values
void Z_attribute::freeVal(void) {
switch (type) {
case Za_type::Za_raw:
if (val.bval) { delete val.bval; val.bval = nullptr; }
break;
case Za_type::Za_str:
if (val.sval) { delete[] val.sval; val.sval = nullptr; }
break;
case Za_type::Za_obj:
if (val.objval) { delete val.objval; val.objval = nullptr; }
break;
case Za_type::Za_arr:
if (val.arrval) { delete val.arrval; val.arrval = nullptr; }
break;
default:
break;
}
}
void Z_attribute::deepCopy(const Z_attribute & rhs) {
// copy cluster anymays
cluster = rhs.cluster;
attr_id = rhs.attr_id;
if (rhs.key_is_str) {
if (rhs.key_is_pmem) {
key = rhs.key; // PMEM, don't copy
} else {
key = nullptr;
if (rhs.key) {
size_t key_len = strlen_P(rhs.key);
if (key_len) {
key = new char[key_len+1];
strcpy_P(key, rhs.key);
}
}
}
}
key_is_str = rhs.key_is_str;
key_is_pmem = rhs.key_is_pmem;
key_is_cmd = rhs.key_is_cmd;
key_suffix = rhs.key_suffix;
attr_type = rhs.attr_type;
attr_multiplier = rhs.attr_multiplier;
attr_divider = rhs.attr_divider;
attr_base = rhs.attr_base;
// copy value
copyVal(rhs);
// don't touch next pointer
}
/*********************************************************************************************\
*
* Implementation for Z_attribute_list
*
\*********************************************************************************************/
// add a cluster/attr_id attribute at the end of the list
Z_attribute & Z_attribute_list::addAttribute(uint16_t cluster, uint16_t attr_id, uint8_t suffix) {
Z_attribute & attr = addToLast();
attr.cluster = cluster;
attr.attr_id = attr_id;
attr.key_is_str = false;
if (!suffix) { attr.key_suffix = countAttribute(attr.cluster, attr.attr_id); }
else { attr.key_suffix = suffix; }
return attr;
}
// add a cluster/cmd_id attribute at the end of the list
Z_attribute & Z_attribute_list::addAttributeCmd(uint16_t cluster, uint8_t cmd_id, bool direction, bool cmd_general, uint8_t suffix) {
Z_attribute & attr = addToLast();
attr.cluster = cluster;
attr.attr_id = cmd_id | (direction ? 0x100 : 0) | (cmd_general ? 0x200 : 0x000);
attr.key_is_cmd = true;
if (!suffix) { attr.key_suffix = countAttribute(attr.cluster, attr.attr_id); }
else { attr.key_suffix = suffix; }
return attr;
}
// add a cluster/attr_id attribute at the end of the list
Z_attribute & Z_attribute_list::addAttribute(const char * name, bool pmem, uint8_t suffix) {
Z_attribute & attr = addToLast();
attr.setKeyName(name, pmem);
if (!suffix) { attr.key_suffix = countAttribute(attr.key); }
else { attr.key_suffix = suffix; }
return attr;
}
Z_attribute & Z_attribute_list::addAttribute(const char * name, const char * name2, uint8_t suffix) {
Z_attribute & attr = addToLast();
attr.setKeyName(name, name2);
if (!suffix) { attr.key_suffix = countAttribute(attr.key); }
else { attr.key_suffix = suffix; }
return attr;
}
String Z_attribute_list::toString(bool enclose_brackets, bool include_battery) const {
String res = "";
if (enclose_brackets) { res += '{'; }
bool prefix_comma = false;
for (const auto & attr : *this) {
res += attr.toString(prefix_comma);
prefix_comma = true;
}
// add battery percentage if available, and if BatteryPercentage is not already present
if (include_battery && validBattPercent() && countAttribute(PSTR(D_CMND_ZIGBEE_BATTPERCENT)) == 0) {
if (prefix_comma) { res += ','; }
prefix_comma = true;
res += F("\"" D_CMND_ZIGBEE_BATTPERCENT "\":");
res += batt_percent;
}
// add source endpoint
if (validSrcEp()) {
if (prefix_comma) { res += ','; }
prefix_comma = true;
res += F("\"" D_CMND_ZIGBEE_ENDPOINT "\":");
res += src_ep;
}
// add group address
if (validGroupId()) {
if (prefix_comma) { res += ','; }
prefix_comma = true;
res += F("\"" D_CMND_ZIGBEE_GROUP "\":");
res += group_id;
}
// add lqi
if (validLQI()) {
if (prefix_comma) { res += ','; }
prefix_comma = true;
res += F("\"" D_CMND_ZIGBEE_LINKQUALITY "\":");
res += lqi;
}
if (enclose_brackets) { res += '}'; }
// done
return res;
}
// suffis always count here
const Z_attribute * Z_attribute_list::findAttribute(const Z_attribute &attr) const {
uint8_t suffix = attr.key_suffix;
if (attr.key_is_str) {
return findAttribute(attr.key, suffix);
} else if (!attr.key_is_cmd) {
return findAttribute(attr.cluster, attr.attr_id, suffix);
} else {
return findAttributeCmd(attr.cluster, attr.attr_id & 0xFF, attr.attr_id & 0x100 ? true : false, attr.attr_id & 0x200 ? true : false, suffix);
}
}
const Z_attribute * Z_attribute_list::findAttribute(uint16_t cluster, uint16_t attr_id, uint8_t suffix) const {
for (const auto & attr : *this) {
if (attr.equalsId(cluster, attr_id, suffix) && !attr.key_is_cmd) { return &attr; }
}
return nullptr;
}
size_t Z_attribute_list::countAttribute(uint16_t cluster, uint16_t attr_id) const {
size_t count = 0;
for (const auto & attr : *this) {
if (attr.equalsId(cluster, attr_id, 0)) { count++; }
}
return count;
}
const Z_attribute * Z_attribute_list::findAttributeCmd(uint16_t cluster, uint8_t cmd_id, bool direction, bool cmd_general, uint8_t suffix) const {
for (const auto & attr : *this) {
if (attr.equalsCmd(cluster, cmd_id, direction, cmd_general, suffix)) { return &attr; }
}
return nullptr;
}
// return the existing attribute or create a new one
Z_attribute & Z_attribute_list::findOrCreateAttribute(uint16_t cluster, uint16_t attr_id, uint8_t suffix) {
Z_attribute * found = findAttribute(cluster, attr_id, suffix);
return found ? *found : addAttribute(cluster, attr_id, suffix);
}
Z_attribute & Z_attribute_list::findOrCreateCmd(uint16_t cluster, uint8_t cmd_id, bool direction, bool cmd_general, uint8_t suffix) {
Z_attribute * found = findAttributeCmd(cluster, cmd_id, direction, cmd_general, suffix);
return found ? *found : addAttributeCmd(cluster, cmd_id, direction, cmd_general, suffix);
}
const Z_attribute * Z_attribute_list::findAttribute(const char * name, uint8_t suffix) const {
for (const auto & attr : *this) {
if (attr.equalsKey(name, suffix)) { return &attr; }
}
return nullptr;
}
size_t Z_attribute_list::countAttribute(const char * name) const {
size_t count = 0;
for (const auto & attr : *this) {
if (attr.equalsKey(name, 0)) { count++; }
}
return count;
}
// return the existing attribute or create a new one
Z_attribute & Z_attribute_list::findOrCreateAttribute(const char * name, uint8_t suffix) {
Z_attribute * found = findAttribute(name, suffix);
return found ? *found : addAttribute(name, suffix);
}
// same but passing a Z_attribute as key
Z_attribute & Z_attribute_list::findOrCreateAttribute(const Z_attribute &attr) {
Z_attribute & ret = attr.key_is_str ? findOrCreateAttribute(attr.key, attr.key_suffix)
: attr.key_is_cmd ?
findOrCreateCmd(attr.cluster, attr.attr_id & 0xFF, attr.attr_id & 0x100 ? true : false, attr.attr_id & 0x200 ? true : false, attr.key_suffix)
: findOrCreateAttribute(attr.cluster, attr.attr_id, attr.key_suffix);
ret.key_suffix = attr.key_suffix;
return ret;
}
// replace the entire content with new attribute or create
Z_attribute & Z_attribute_list::replaceOrCreate(const Z_attribute &attr) {
Z_attribute &new_attr = findOrCreateAttribute(attr);
new_attr.copyVal(attr);
return new_attr;
}
bool Z_attribute_list::mergeList(const Z_attribute_list &attr_list) {
// Check source endpoint
if (0xFF == src_ep) {
src_ep = attr_list.src_ep;
} else if (0xFF != attr_list.src_ep) {
if (src_ep != attr_list.src_ep) { return false; }
}
// Check group address
if (0xFFFF == group_id) {
group_id = attr_list.group_id;
} else if (0xFFFF != attr_list.group_id) {
if (group_id != attr_list.group_id) { return false; }
}
// copy LQI
if (0xFF != attr_list.lqi) {
lqi = attr_list.lqi;
}
// merge attributes
for (auto & attr : attr_list) {
replaceOrCreate(attr);
}
return true;
}
#endif // USE_ZIGBEE