Merge branch 'development' of https://github.com/arendst/Tasmota into development

This commit is contained in:
Theo Arends 2020-09-05 15:01:55 +02:00
commit ea8d92ec69
8 changed files with 1923 additions and 1224 deletions

View File

@ -0,0 +1,186 @@
/*
support_light_list.ino - Lightweight Linked List for simple objects - optimized for low code size and low memory
Copyright (C) 2020 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/>.
*/
/*********************************************************************************************\
*
* private class for Linked List element
*
\*********************************************************************************************/
template <typename T>
class LList;
template <typename T>
class LList_elt {
public:
LList_elt() : _next(nullptr), _val() {}
inline T & val(void) { return _val; }
inline LList_elt<T> * next(void) { return _next; }
inline void next(LList_elt<T> * next) { _next = next; }
friend class LList<T>;
protected:
LList_elt<T> * _next;
T _val;
};
/*********************************************************************************************\
*
* Lightweight Linked List - optimized for low code size
*
\*********************************************************************************************/
template <typename T>
class LList {
public:
LList() : _head(nullptr) {}
~LList() { reset(); }
// remove elements
void removeHead(void); // remove first element
void reset(void); // remove all elements
void remove(const T * val);
// read the list
inline bool isEmpty(void) const { return (_head == nullptr) ? true : false; }
size_t length(void) const;
inline T * head(void) { return _head ? &_head->_val : nullptr; }
inline const T * head(void) const { return _head ? &_head->_val : nullptr; }
const T * at(size_t index) const ;
// non-const variants
// not very academic cast but reduces code size
inline T * at(size_t index) { return (T*) ((const LList<T>*)this)->at(index); }
// adding elements
T & addHead(void);
T & addHead(const T &val);
T & addToLast(void);
// iterator
// see https://stackoverflow.com/questions/8164567/how-to-make-my-custom-type-to-work-with-range-based-for-loops
class iterator {
public:
iterator(LList_elt<T> *_cur): cur(_cur), next(nullptr) { if (cur) { next = cur->_next; } }
iterator operator++() { cur = next; if (cur) { next = cur->_next;} return *this; }
bool operator!=(const iterator & other) const { return cur != other.cur; }
T & operator*() const { return cur->_val; }
private:
LList_elt<T> *cur;
LList_elt<T> *next; // we need to keep next pointer in case the current attribute gets deleted
};
iterator begin() { return iterator(this->_head); } // start with 'head'
iterator end() { return iterator(nullptr); } // end with null pointer
// const iterator
class const_iterator {
public:
const_iterator(const LList_elt<T> *_cur): cur(_cur), next(nullptr) { if (cur) { next = cur->_next; } }
const_iterator operator++() { cur = next; if (cur) { next = cur->_next;} return *this; }
bool operator!=(const_iterator & other) const { return cur != other.cur; }
const T & operator*() const { return cur->_val; }
private:
const LList_elt<T> *cur;
const LList_elt<T> *next; // we need to keep next pointer in case the current attribute gets deleted
};
const_iterator begin() const { return const_iterator(this->_head); } // start with 'head'
const_iterator end() const { return const_iterator(nullptr); } // end with null pointer
protected:
LList_elt<T> * _head;
};
template <typename T>
size_t LList<T>::length(void) const {
size_t count = 0;
for (auto & elt : *this) {count++; }
return count;
}
template <typename T>
const T * LList<T>::at(size_t index) const {
size_t count = 0;
for (const auto & elt : *this) {
if (index == count++) { return &elt; }
}
return nullptr;
}
template <typename T>
void LList<T>::reset(void) {
while (_head) {
LList_elt<T> * next = _head->next();
delete _head;
_head = next;
}
}
template <typename T>
void LList<T>::removeHead(void) {
if (_head) {
LList_elt<T> * next = _head->next();
delete _head;
_head = next;
}
}
template <typename T>
void LList<T>::remove(const T * val) {
if (nullptr == val) { return; }
// find element in chain and find pointer before
LList_elt<T> **curr_ptr = &_head;
while (*curr_ptr) {
LList_elt<T> * curr_elt = *curr_ptr;
if ( &(curr_elt->_val) == val) {
*curr_ptr = curr_elt->_next; // update previous pointer to next element
delete curr_elt;
break; // stop iteration now
}
curr_ptr = &((*curr_ptr)->_next); // move to next element
}
}
template <typename T>
T & LList<T>::addHead(void) {
LList_elt<T> * elt = new LList_elt<T>(); // create element
elt->next(_head); // insert at the head
_head = elt;
return elt->_val;
}
template <typename T>
T & LList<T>::addHead(const T &val) {
LList_elt<T> * elt = new LList_elt<T>(); // create element
elt->next(_head); // insert at the head
elt->_val = val;
_head = elt;
return elt->_val;
}
template <typename T>
T & LList<T>::addToLast(void) {
LList_elt<T> **curr_ptr = &_head;
while (*curr_ptr) {
curr_ptr = &((*curr_ptr)->_next);
}
LList_elt<T> * elt = new LList_elt<T>(); // create element
*curr_ptr = elt;
return elt->_val;
}

View File

@ -237,3 +237,18 @@ public:
_buf = nullptr;
}
} PreAllocatedSBuffer;
// nullptr accepted
bool equalsSBuffer(const class SBuffer * buf1, const class SBuffer * buf2) {
if (buf1 == buf2) { return true; }
if (!buf1 && (buf2->len() == 0)) { return true; }
if (!buf2 && (buf1->len() == 0)) { return true; }
if (!buf1 || !buf2) { return false; } // at least one buf is not empty
// we know that both buf1 and buf2 are non-null
if (buf1->len() != buf2->len()) { return false; }
size_t len = buf1->len();
for (uint32_t i=0; i<len; i++) {
if (buf1->get8(i) != buf2->get8(i)) { return false; }
}
return true;
}

View File

@ -0,0 +1,752 @@
/*
xdrv_23_zigbee_1z_libs.ino - zigbee support for Tasmota, JSON replacement libs
Copyright (C) 2020 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
};
class Z_attribute {
public:
// attribute key, either cluster+attribute_id or plain name
union {
struct {
uint16_t cluster;
uint16_t attr_id;
} id;
char * key;
} key;
// attribute value
union {
uint32_t uval32;
int32_t ival32;
float fval;
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)
// Constructor with all defaults
Z_attribute():
key{ .id = { 0x0000, 0x0000 } },
val{ .uval32 = 0x0000 },
type(Za_type::Za_none),
key_is_str(false),
key_is_pmem(false),
val_str_raw(false),
key_suffix(1)
{};
Z_attribute(const Z_attribute & rhs) {
deepCopy(rhs);
}
Z_attribute & operator = (const Z_attribute & rhs) {
freeKey();
freeVal();
deepCopy(rhs);
}
// Destructor, free memory that was allocated
~Z_attribute() {
freeKey();
freeVal();
}
// free any allocated memoruy for values
void 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;
}
}
// free any allocated memoruy for keys
void freeKey(void) {
if (key_is_str && key.key && !key_is_pmem) { delete[] key.key; }
key.key = nullptr;
}
// set key name
void setKeyName(const char * _key, bool pmem = false) {
freeKey();
key_is_str = true;
key_is_pmem = pmem;
if (pmem) {
key.key = (char*) _key;
} else {
setKeyName(_key, nullptr);
}
}
// provide two entries and concat
void 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.key = new char[key_len+1];
strcpy_P(key.key, _key);
if (_key2) {
strcat_P(key.key, _key2);
}
}
}
void setKeyId(uint16_t cluster, uint16_t attr_id) {
freeKey();
key_is_str = false;
key.id.cluster = cluster;
key.id.attr_id = attr_id;
}
// Setters
void setNone(void) {
freeVal(); // free any previously allocated memory
val.uval32 = 0;
type = Za_type::Za_none;
}
void setUInt(uint32_t _val) {
freeVal(); // free any previously allocated memory
val.uval32 = _val;
type = Za_type::Za_uint;
}
void setBool(bool _val) {
freeVal(); // free any previously allocated memory
val.uval32 = _val;
type = Za_type::Za_bool;
}
void setInt(int32_t _val) {
freeVal(); // free any previously allocated memory
val.ival32 = _val;
type = Za_type::Za_int;
}
void setFloat(float _val) {
freeVal(); // free any previously allocated memory
val.fval = _val;
type = Za_type::Za_float;
}
void 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;
}
// 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) {
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;
}
inline void setStrRaw(const char * _val) {
setStr(_val);
val_str_raw = true;
}
inline bool isNum(void) const { return (type >= Za_type::Za_bool) && (type <= Za_type::Za_float); }
// get num values
float 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 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 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 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 * 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 * getStr(void) const {
if (Za_type::Za_str == type) { return val.sval; }
return "";
}
bool equalsKey(const Z_attribute & attr2, bool ignore_key_suffix = false) const {
// check if keys are equal
if (key_is_str != attr2.key_is_str) { return false; }
if (key_is_str) {
if (strcmp_PP(key.key, attr2.key.key)) { return false; }
} else {
if ((key.id.cluster != attr2.key.id.cluster) ||
(key.id.attr_id != attr2.key.id.attr_id)) { return false; }
}
if (!ignore_key_suffix) {
if (key_suffix != attr2.key_suffix) { return false; }
}
return true;
}
bool equalsKey(uint16_t cluster, uint16_t attr_id, uint8_t suffix = 0) const {
if (!key_is_str) {
if ((key.id.cluster == cluster) && (key.id.attr_id == attr_id)) {
if (suffix) {
if (key_suffix == suffix) { return true; }
} else {
return true;
}
}
}
return false;
}
bool equalsKey(const char * name, uint8_t suffix = 0) const {
if (key_is_str) {
if (0 == strcmp_PP(key.key, name)) {
if (suffix) {
if (key_suffix == suffix) { return true; }
} else {
return true;
}
}
}
return false;
}
bool 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 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; }
}
return true;
}
bool equals(const Z_attribute & attr2) const {
return equalsKey(attr2) && equalsVal(attr2);
}
String toString(bool prefix_comma = false) const {
String res("");
if (prefix_comma) { res += ','; }
res += '"';
// compute the attribute name
if (key_is_str) {
if (key.key) { res += EscapeJSONString(key.key); }
else { res += F("null"); } // shouldn't happen
if (key_suffix > 1) {
res += key_suffix;
}
} else {
char attr_name[12];
snprintf_P(attr_name, sizeof(attr_name), PSTR("%04X/%04X"), key.id.cluster, key.id.attr_id);
res += attr_name;
if (key_suffix > 1) {
res += '+';
res += key_suffix;
}
}
res += F("\":");
// value part
switch (type) {
case Za_type::Za_none:
res += "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;
}
return res;
}
// copy value from one attribute to another, without changing its type
void 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;
}
protected:
void deepCopy(const Z_attribute & rhs) {
// copy key
if (!rhs.key_is_str) {
key.id.cluster = rhs.key.id.cluster;
key.id.attr_id = rhs.key.id.attr_id;
} else {
if (rhs.key_is_pmem) {
key.key = rhs.key.key; // PMEM, don't copy
} else {
key.key = nullptr;
if (rhs.key.key) {
size_t key_len = strlen_P(rhs.key.key);
if (key_len) {
key.key = new char[key_len+1];
strcpy_P(key.key, rhs.key.key);
}
}
}
}
key_is_str = rhs.key_is_str;
key_is_pmem = rhs.key_is_pmem;
key_suffix = rhs.key_suffix;
// copy value
copyVal(rhs);
// don't touch next pointer
}
};
/*********************************************************************************************\
*
* Class for attribute array of values
* This is a helper function to generate a clean list of unsigned ints
*
\*********************************************************************************************/
class Z_json_array {
public:
Z_json_array(): val("[]") {} // start with empty array
void add(uint32_t uval32) {
// remove trailing ']'
val.remove(val.length()-1);
if (val.length() > 1) { // if not empty, prefix with comma
val += ',';
}
val += uval32;
val += ']';
}
String &toString(void) {
return val;
}
private :
String val;
};
/*********************************************************************************************\
*
* 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
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;
}
inline bool isValidSrcEp(void) const { return 0xFF != src_ep; }
inline bool isValidLQI(void) const { return 0xFF != lqi; }
inline bool isValidGroupId(void) const { return 0xFFFF != group_id; }
// 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);
// 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);
}
// 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) 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 * findAttribute(const char * name, uint8_t suffix = 0) const;
const Z_attribute * findAttribute(const Z_attribute &attr) const; // suffis 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 * 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);
// 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);
};
// 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.key.id.cluster = cluster;
attr.key.id.attr_id = attr_id;
attr.key_is_str = false;
if (!suffix) { attr.key_suffix = countAttribute(attr.key.id.cluster, attr.key.id.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.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.key); }
else { attr.key_suffix = suffix; }
return attr;
}
String Z_attribute_list::toString(bool enclose_brackets) 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 source endpoint
if (0xFF != src_ep) {
if (prefix_comma) { res += ','; }
prefix_comma = true;
res += F("\"" D_CMND_ZIGBEE_ENDPOINT "\":");
res += src_ep;
}
// add group address
if (0xFFFF != group_id) {
if (prefix_comma) { res += ','; }
prefix_comma = true;
res += F("\"" D_CMND_ZIGBEE_GROUP "\":");
res += group_id;
}
// add lqi
if (0xFF != lqi) {
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.key, suffix);
} else {
return findAttribute(attr.key.id.cluster, attr.key.id.attr_id, 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.equalsKey(cluster, attr_id, suffix)) { 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.equalsKey(cluster, attr_id, 0)) { count++; }
}
return count;
}
// 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);
}
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) {
if (attr.key_is_str) {
return findOrCreateAttribute(attr.key.key, attr.key_suffix);
} else {
return findOrCreateAttribute(attr.key.id.cluster, attr.key.id.attr_id, attr.key_suffix);
}
}
// 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; }
}
if (0xFF != attr_list.lqi) {
lqi = attr_list.lqi;
}
for (auto & attr : attr_list) {
replaceOrCreate(attr);
}
return true;
}
#endif // USE_ZIGBEE

View File

@ -19,8 +19,6 @@
#ifdef USE_ZIGBEE
#include <vector>
#ifndef ZIGBEE_SAVE_DELAY_SECONDS
#define ZIGBEE_SAVE_DELAY_SECONDS 2 // wait for 2s before saving Zigbee info
#endif
@ -29,25 +27,6 @@ const uint16_t kZigbeeSaveDelaySeconds = ZIGBEE_SAVE_DELAY_SECONDS; // wait f
/*********************************************************************************************\
* Structures for Rules variables related to the last received message
\*********************************************************************************************/
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; }
/*********************************************************************************************\
* Structures for device configuration
\*********************************************************************************************/
const size_t endpoints_max = 8; // we limit to 8 endpoints
class Z_Device {
@ -58,9 +37,8 @@ public:
char * modelId;
char * friendlyName;
uint8_t endpoints[endpoints_max]; // static array to limit memory consumption, list of endpoints until 0x00 or end of array
// json buffer used for attribute reporting
DynamicJsonBuffer *json_buffer;
JsonObject *json;
// Used for attribute reporting
Z_attribute_list attr_list;
// sequence number for Zigbee frames
uint16_t shortaddr; // unique key if not null, or unspecified if null
uint8_t seqNumber;
@ -95,14 +73,13 @@ public:
int16_t mains_power; // Active power
// Constructor with all defaults
Z_Device(uint16_t _shortaddr, uint64_t _longaddr = 0x00):
Z_Device(uint16_t _shortaddr = BAD_SHORTADDR, uint64_t _longaddr = 0x00):
longaddr(_longaddr),
manufacturerId(nullptr),
modelId(nullptr),
friendlyName(nullptr),
endpoints{ 0, 0, 0, 0, 0, 0, 0, 0 },
json_buffer(nullptr),
json(nullptr),
attr_list(),
shortaddr(_shortaddr),
seqNumber(0),
// Hue support
@ -207,7 +184,7 @@ typedef struct Z_Deferred {
// - shortaddr and longaddr cannot be both null
class Z_Devices {
public:
Z_Devices() {};
Z_Devices() : _deferred() {};
// Probe the existence of device keys
// Results:
@ -218,12 +195,17 @@ public:
uint16_t isKnownIndex(uint32_t index) const;
uint16_t isKnownFriendlyName(const char * name) const;
Z_Device & findShortAddr(uint16_t shortaddr);
const Z_Device & findShortAddr(uint16_t shortaddr) const;
Z_Device & findLongAddr(uint64_t longaddr);
const Z_Device & findLongAddr(uint64_t longaddr) const;
Z_Device & getShortAddr(uint16_t shortaddr); // find Device from shortAddr, creates it if does not exist
const Z_Device & getShortAddrConst(uint16_t shortaddr) const ; // find Device from shortAddr, creates it if does not exist
Z_Device & getLongAddr(uint64_t longaddr); // find Device from shortAddr, creates it if does not exist
// check if a device was found or if it's the fallback device
inline bool foundDevice(const Z_Device & device) const {
return (&device != &device_unk);
}
int32_t findLongAddr(uint64_t longaddr) const;
int32_t findFriendlyName(const char * name) const;
uint64_t getDeviceLongAddr(uint16_t shortaddr) const;
@ -281,19 +263,22 @@ public:
void runTimer(void);
// Append or clear attributes Json structure
void jsonClear(uint16_t shortaddr);
void jsonAppend(uint16_t shortaddr, const JsonObject &values);
const JsonObject *jsonGet(uint16_t shortaddr);
void jsonAppend(uint16_t shortaddr, const Z_attribute_list &attr_list);
void jsonPublishFlush(uint16_t shortaddr); // publish the json message and clear buffer
bool jsonIsConflict(uint16_t shortaddr, const JsonObject &values);
void jsonPublishNow(uint16_t shortaddr, JsonObject &values);
bool jsonIsConflict(uint16_t shortaddr, const Z_attribute_list &attr_list) const;
void jsonPublishNow(uint16_t shortaddr, Z_attribute_list &attr_list);
// Iterator
size_t devicesSize(void) const {
return _devices.size();
return _devices.length();
}
const Z_Device &devicesAt(size_t i) const {
return *(_devices.at(i));
const Z_Device & devicesAt(size_t i) const {
const Z_Device * devp = _devices.at(i);
if (devp) {
return *devp;
} else {
return device_unk;
}
}
// Remove device from list
@ -308,8 +293,8 @@ public:
uint16_t parseDeviceParam(const char * param, bool short_must_be_known = false) const;
private:
std::vector<Z_Device*> _devices = {};
std::vector<Z_Deferred> _deferred = {}; // list of deferred calls
LList<Z_Device> _devices; // list of devices
LList<Z_Deferred> _deferred; // list of deferred calls
uint32_t _saveTimer = 0;
uint8_t _seqNumber = 0; // global seqNumber if device is unknown
@ -317,10 +302,7 @@ private:
// Any find() function will not return Null, instead it will return this instance
const Z_Device device_unk = Z_Device(BAD_SHORTADDR);
template < typename T>
static bool findInVector(const std::vector<T> & vecOfElements, const T & element);
int32_t findShortAddrIdx(uint16_t shortaddr) const;
//int32_t findShortAddrIdx(uint16_t shortaddr) const;
// Create a new entry in the devices list - must be called if it is sure it does not already exist
Z_Device & createDeviceEntry(uint16_t shortaddr, uint64_t longaddr = 0);
void freeDeviceEntry(Z_Device *device);
@ -343,31 +325,16 @@ uint16_t localShortAddr = 0;
* Implementation
\*********************************************************************************************/
// https://thispointer.com/c-how-to-find-an-element-in-vector-and-get-its-index/
template < typename T>
bool Z_Devices::findInVector(const std::vector<T> & vecOfElements, const T & element) {
// Find given element in vector
auto it = std::find(vecOfElements.begin(), vecOfElements.end(), element);
if (it != vecOfElements.end()) {
return true;
} else {
return false;
}
}
//
// Create a new Z_Device entry in _devices. Only to be called if you are sure that no
// entry with same shortaddr or longaddr exists.
//
Z_Device & Z_Devices::createDeviceEntry(uint16_t shortaddr, uint64_t longaddr) {
if ((BAD_SHORTADDR == shortaddr) && !longaddr) { return (Z_Device&) device_unk; } // it is not legal to create this entry
Z_Device * device_alloc = new Z_Device(shortaddr, longaddr);
Z_Device device(shortaddr, longaddr);
device_alloc->json_buffer = new DynamicJsonBuffer(16);
_devices.push_back(device_alloc);
dirty();
return *(_devices.back());
return _devices.addHead(device);
}
void Z_Devices::freeDeviceEntry(Z_Device *device) {
@ -383,20 +350,17 @@ void Z_Devices::freeDeviceEntry(Z_Device *device) {
// In:
// shortaddr (not BAD_SHORTADDR)
// Out:
// index in _devices of entry, -1 if not found
//
int32_t Z_Devices::findShortAddrIdx(uint16_t shortaddr) const {
if (BAD_SHORTADDR == shortaddr) { return -1; } // does not make sense to look for BAD_SHORTADDR shortaddr (broadcast)
int32_t found = 0;
for (auto &elem : _devices) {
if (elem->shortaddr == shortaddr) { return found; }
found++;
// reference to device, or to device_unk if not found
// (use foundDevice() to check if found)
Z_Device & Z_Devices::findShortAddr(uint16_t shortaddr) {
for (auto & elem : _devices) {
if (elem.shortaddr == shortaddr) { return elem; }
}
return -1;
return (Z_Device&) device_unk;
}
const Z_Device & Z_Devices::findShortAddr(uint16_t shortaddr) const {
for (auto &elem : _devices) {
if (elem->shortaddr == shortaddr) { return *elem; }
for (const auto & elem : _devices) {
if (elem.shortaddr == shortaddr) { return elem; }
}
return device_unk;
}
@ -408,14 +372,19 @@ const Z_Device & Z_Devices::findShortAddr(uint16_t shortaddr) const {
// Out:
// index in _devices of entry, -1 if not found
//
int32_t Z_Devices::findLongAddr(uint64_t longaddr) const {
if (!longaddr) { return -1; }
int32_t found = 0;
Z_Device & Z_Devices::findLongAddr(uint64_t longaddr) {
if (!longaddr) { return (Z_Device&) device_unk; }
for (auto &elem : _devices) {
if (elem->longaddr == longaddr) { return found; }
found++;
if (elem.longaddr == longaddr) { return elem; }
}
return -1;
return (Z_Device&) device_unk;
}
const Z_Device & Z_Devices::findLongAddr(uint64_t longaddr) const {
if (!longaddr) { return device_unk; }
for (const auto &elem : _devices) {
if (elem.longaddr == longaddr) { return elem; }
}
return device_unk;
}
//
// Scan all devices to find a corresponding friendlyNme
@ -431,8 +400,8 @@ int32_t Z_Devices::findFriendlyName(const char * name) const {
int32_t found = 0;
if (name_len) {
for (auto &elem : _devices) {
if (elem->friendlyName) {
if (strcasecmp(elem->friendlyName, name) == 0) { return found; }
if (elem.friendlyName) {
if (strcasecmp(elem.friendlyName, name) == 0) { return found; }
}
found++;
}
@ -441,9 +410,8 @@ int32_t Z_Devices::findFriendlyName(const char * name) const {
}
uint16_t Z_Devices::isKnownLongAddr(uint64_t longaddr) const {
int32_t found = findLongAddr(longaddr);
if (found >= 0) {
const Z_Device & device = devicesAt(found);
const Z_Device & device = findLongAddr(longaddr);
if (foundDevice(device)) {
return device.shortaddr; // can be zero, if not yet registered
} else {
return BAD_SHORTADDR;
@ -471,7 +439,7 @@ uint16_t Z_Devices::isKnownFriendlyName(const char * name) const {
}
uint64_t Z_Devices::getDeviceLongAddr(uint16_t shortaddr) const {
return getShortAddrConst(shortaddr).longaddr; // if unknown, it reverts to the Unknown device and longaddr is 0x00
return findShortAddr(shortaddr).longaddr; // if unknown, it reverts to the Unknown device and longaddr is 0x00
}
//
@ -479,38 +447,28 @@ uint64_t Z_Devices::getDeviceLongAddr(uint16_t shortaddr) const {
//
Z_Device & Z_Devices::getShortAddr(uint16_t shortaddr) {
if (BAD_SHORTADDR == shortaddr) { return (Z_Device&) device_unk; } // this is not legal
int32_t found = findShortAddrIdx(shortaddr);
if (found >= 0) {
return *(_devices[found]);
Z_Device & device = findShortAddr(shortaddr);
if (foundDevice(device)) {
return device;
}
//Serial.printf("Device entry created for shortaddr = 0x%02X, found = %d\n", shortaddr, found);
return createDeviceEntry(shortaddr, 0);
}
// Same version but Const
const Z_Device & Z_Devices::getShortAddrConst(uint16_t shortaddr) const {
int32_t found = findShortAddrIdx(shortaddr);
if (found >= 0) {
return *(_devices[found]);
}
return device_unk;
}
// find the Device object by its longaddr (unique key if not null)
Z_Device & Z_Devices::getLongAddr(uint64_t longaddr) {
if (!longaddr) { return (Z_Device&) device_unk; }
int32_t found = findLongAddr(longaddr);
if (found > 0) {
return *(_devices[found]);
Z_Device & device = findLongAddr(longaddr);
if (foundDevice(device)) {
return device;
}
return createDeviceEntry(0, longaddr);
}
// Remove device from list, return true if it was known, false if it was not recorded
bool Z_Devices::removeDevice(uint16_t shortaddr) {
int32_t found = findShortAddrIdx(shortaddr);
if (found >= 0) {
freeDeviceEntry(_devices.at(found));
_devices.erase(_devices.begin() + found);
Z_Device & device = findShortAddr(shortaddr);
if (foundDevice(device)) {
_devices.remove(&device);
dirty();
return true;
}
@ -523,27 +481,27 @@ bool Z_Devices::removeDevice(uint16_t shortaddr) {
// shortaddr
// longaddr (both can't be null at the same time)
void Z_Devices::updateDevice(uint16_t shortaddr, uint64_t longaddr) {
int32_t s_found = findShortAddrIdx(shortaddr); // is there already a shortaddr entry
int32_t l_found = findLongAddr(longaddr); // is there already a longaddr entry
Z_Device * s_found = &findShortAddr(shortaddr); // is there already a shortaddr entry
Z_Device * l_found = &findLongAddr(longaddr); // is there already a longaddr entry
if ((s_found >= 0) && (l_found >= 0)) { // both shortaddr and longaddr are already registered
if (foundDevice(*s_found) && foundDevice(*l_found)) { // both shortaddr and longaddr are already registered
if (s_found == l_found) {
} else { // they don't match
// the device with longaddr got a new shortaddr
_devices[l_found]->shortaddr = shortaddr; // update the shortaddr corresponding to the longaddr
l_found->shortaddr = shortaddr; // update the shortaddr corresponding to the longaddr
// erase the previous shortaddr
freeDeviceEntry(_devices.at(s_found));
_devices.erase(_devices.begin() + s_found);
freeDeviceEntry(s_found);
_devices.remove(s_found);
dirty();
}
} else if (s_found >= 0) {
} else if (foundDevice(*s_found)) {
// shortaddr already exists but longaddr not
// add the longaddr to the entry
_devices[s_found]->longaddr = longaddr;
s_found->longaddr = longaddr;
dirty();
} else if (l_found >= 0) {
} else if (foundDevice(*l_found)) {
// longaddr entry exists, update shortaddr
_devices[l_found]->shortaddr = shortaddr;
l_found->shortaddr = shortaddr;
dirty();
} else {
// neither short/lonf addr are found.
@ -588,9 +546,8 @@ void Z_Devices::addEndpoint(uint16_t shortaddr, uint8_t endpoint) {
//
uint32_t Z_Devices::countEndpoints(uint16_t shortaddr) const {
uint32_t count_ep = 0;
int32_t found = findShortAddrIdx(shortaddr);
if (found < 0) return 0; // avoid creating an entry if the device was never seen
const Z_Device &device = devicesAt(found);
const Z_Device & device =findShortAddr(shortaddr);
if (!foundDevice(device)) return 0;
for (uint32_t i = 0; i < endpoints_max; i++) {
if (0 != device.endpoints[i]) {
@ -666,9 +623,8 @@ void Z_Devices::setBatteryPercent(uint16_t shortaddr, uint8_t bp) {
// get the next sequance number for the device, or use the global seq number if device is unknown
uint8_t Z_Devices::getNextSeqNumber(uint16_t shortaddr) {
int32_t short_found = findShortAddrIdx(shortaddr);
if (short_found >= 0) {
Z_Device &device = getShortAddr(shortaddr);
Z_Device & device = findShortAddr(shortaddr);
if (foundDevice(device)) {
device.seqNumber += 1;
return device.seqNumber;
} else {
@ -754,9 +710,9 @@ void Z_Devices::hideHueBulb(uint16_t shortaddr, bool hidden) {
}
// true if device is not knwon or not a bulb - it wouldn't make sense to publish a non-bulb
bool Z_Devices::isHueBulbHidden(uint16_t shortaddr) const {
int32_t found = findShortAddrIdx(shortaddr);
if (found >= 0) {
uint8_t zb_profile = _devices[found]->zb_profile;
const Z_Device & device = findShortAddr(shortaddr);
if (foundDevice(device)) {
uint8_t zb_profile = device.zb_profile;
if (0x00 == (zb_profile & 0xF0)) {
// bulb type
return (zb_profile & 0x08) ? true : false;
@ -769,13 +725,10 @@ bool Z_Devices::isHueBulbHidden(uint16_t shortaddr) const {
// Parse for a specific category, of all deferred for a device if category == 0xFF
void Z_Devices::resetTimersForDevice(uint16_t shortaddr, uint16_t groupaddr, uint8_t category) {
// iterate the list of deferred, and remove any linked to the shortaddr
for (auto it = _deferred.begin(); it != _deferred.end(); it++) {
// Notice that the iterator is decremented after it is passed
// to erase() but before erase() is executed
// see https://www.techiedelight.com/remove-elements-vector-inside-loop-cpp/
if ((it->shortaddr == shortaddr) && (it->groupaddr == groupaddr)) {
if ((0xFF == category) || (it->category == category)) {
_deferred.erase(it--);
for (auto & defer : _deferred) {
if ((defer.shortaddr == shortaddr) && (defer.groupaddr == groupaddr)) {
if ((0xFF == category) || (defer.category == category)) {
_deferred.remove(&defer);
}
}
}
@ -789,7 +742,8 @@ void Z_Devices::setTimer(uint16_t shortaddr, uint16_t groupaddr, uint32_t wait_m
}
// Now create the new timer
Z_Deferred deferred = { wait_ms + millis(), // timer
Z_Deferred & deferred = _deferred.addHead();
deferred = { wait_ms + millis(), // timer
shortaddr,
groupaddr,
cluster,
@ -797,20 +751,17 @@ void Z_Devices::setTimer(uint16_t shortaddr, uint16_t groupaddr, uint32_t wait_m
category,
value,
func };
_deferred.push_back(deferred);
}
// Run timer at each tick
// WARNING: don't set a new timer within a running timer, this causes memory corruption
void Z_Devices::runTimer(void) {
// visit all timers
for (auto it = _deferred.begin(); it != _deferred.end(); it++) {
Z_Deferred &defer = *it;
for (auto & defer : _deferred) {
uint32_t timer = defer.timer;
if (TimeReached(timer)) {
(*defer.func)(defer.shortaddr, defer.groupaddr, defer.cluster, defer.endpoint, defer.value);
_deferred.erase(it--); // remove from list
_deferred.remove(&defer);
}
}
@ -821,173 +772,100 @@ void Z_Devices::runTimer(void) {
}
}
// Clear the JSON buffer for coalesced and deferred attributes
void Z_Devices::jsonClear(uint16_t shortaddr) {
Z_Device & device = getShortAddr(shortaddr);
device.json = nullptr;
device.json_buffer->clear();
}
// Copy JSON from one object to another, this helps preserving the order of attributes
void CopyJsonVariant(JsonObject &to, const String &key, const JsonVariant &val) {
// first remove the potentially existing key in the target JSON, so new adds will be at the end of the list
to.remove(key); // force remove to have metadata like LinkQuality at the end
if (val.is<char*>()) {
const char * sval = val.as<char*>(); // using char* forces a copy, and also captures 'null' values
to.set(key, (char*) sval);
} else if (val.is<JsonArray>()) {
JsonArray &nested_arr = to.createNestedArray(key);
CopyJsonArray(nested_arr, val.as<JsonArray>()); // deep copy
} else if (val.is<JsonObject>()) {
JsonObject &nested_obj = to.createNestedObject(key);
CopyJsonObject(nested_obj, val.as<JsonObject>()); // deep copy
} else {
to.set(key, val); // general case for non array, object or string
}
}
// Shallow copy of array, we skip any sub-array or sub-object. It may be added in the future
void CopyJsonArray(JsonArray &to, const JsonArray &arr) {
for (auto v : arr) {
if (v.is<char*>()) {
String sval = v.as<String>(); // force a copy of the String value
to.add(sval);
} else if (v.is<JsonArray>()) {
} else if (v.is<JsonObject>()) {
} else {
to.add(v);
}
}
}
// Deep copy of object
void CopyJsonObject(JsonObject &to, const JsonObject &from) {
for (auto kv : from) {
String key_string = kv.key;
JsonVariant &val = kv.value;
CopyJsonVariant(to, key_string, val);
}
}
// does the new payload conflicts with the existing payload, i.e. values would be overwritten
// true - one attribute (except LinkQuality) woudl be lost, there is conflict
// false - new attributes can be safely added
bool Z_Devices::jsonIsConflict(uint16_t shortaddr, const JsonObject &values) {
Z_Device & device = getShortAddr(shortaddr);
if (&values == nullptr) { return false; }
bool Z_Devices::jsonIsConflict(uint16_t shortaddr, const Z_attribute_list &attr_list) const {
const Z_Device & device = findShortAddr(shortaddr);
if (nullptr == device.json) {
if (!foundDevice(device)) { return false; }
if (attr_list.isEmpty()) {
return false; // if no previous value, no conflict
}
// compare groups
// Special case for group addresses. Group attribute is only present if the target
// address is a group address, so just comparing attributes will not work.
// Eg: if the first packet has no group attribute, and the second does, conflict would not be detected
// Here we explicitly compute the group address of both messages, and compare them. No group means group=0x0000
// (we use the property of an missing attribute returning 0)
// (note: we use .get() here which is case-sensitive. We know however that the attribute was set with the exact syntax D_CMND_ZIGBEE_GROUP, so we don't need a case-insensitive get())
uint16_t group1 = device.json->get<unsigned int>(D_CMND_ZIGBEE_GROUP);
uint16_t group2 = values.get<unsigned int>(D_CMND_ZIGBEE_GROUP);
if (group1 != group2) {
return true; // if group addresses differ, then conflict
if (device.attr_list.isValidGroupId() && attr_list.isValidGroupId()) {
if (device.attr_list.group_id != attr_list.group_id) { return true; } // groups are in conflict
}
// parse all other parameters
for (auto kv : values) {
String key_string = kv.key;
// compare src_ep
if (device.attr_list.isValidSrcEp() && attr_list.isValidSrcEp()) {
if (device.attr_list.src_ep != attr_list.src_ep) { return true; }
}
// LQI does not count as conflicting
if (0 == strcasecmp_P(kv.key, PSTR(D_CMND_ZIGBEE_GROUP))) {
// ignore group, it was handled already
} else if (0 == strcasecmp_P(kv.key, PSTR(D_CMND_ZIGBEE_ENDPOINT))) {
// attribute "Endpoint" or "Group"
if (device.json->containsKey(kv.key)) {
if (kv.value.as<unsigned int>() != device.json->get<unsigned int>(kv.key)) {
return true;
}
}
} else if (strcasecmp_P(kv.key, PSTR(D_CMND_ZIGBEE_LINKQUALITY))) { // exception = ignore duplicates for LinkQuality
if (device.json->containsKey(kv.key)) {
return true; // conflict!
// parse all other parameters
for (const auto & attr : attr_list) {
const Z_attribute * curr_attr = device.attr_list.findAttribute(attr);
if (nullptr != curr_attr) {
if (!curr_attr->equalsVal(attr)) {
return true; // the value already exists and is different - conflict!
}
}
}
return false;
}
void Z_Devices::jsonAppend(uint16_t shortaddr, const JsonObject &values) {
void Z_Devices::jsonAppend(uint16_t shortaddr, const Z_attribute_list &attr_list) {
Z_Device & device = getShortAddr(shortaddr);
if (&values == nullptr) { return; }
if (nullptr == device.json) {
device.json = &(device.json_buffer->createObject());
}
// Prepend Device, will be removed later if redundant
char sa[8];
snprintf_P(sa, sizeof(sa), PSTR("0x%04X"), shortaddr);
device.json->set(F(D_JSON_ZIGBEE_DEVICE), sa);
// Prepend Friendly Name if it has one
const char * fname = zigbee_devices.getFriendlyName(shortaddr);
if (fname) {
device.json->set(F(D_JSON_ZIGBEE_NAME), (char*) fname); // (char*) forces ArduinoJson to make a copy of the cstring
}
// copy all values from 'values' to 'json'
CopyJsonObject(*device.json, values);
}
const JsonObject *Z_Devices::jsonGet(uint16_t shortaddr) {
return getShortAddr(shortaddr).json;
device.attr_list.mergeList(attr_list);
}
void Z_Devices::jsonPublishFlush(uint16_t shortaddr) {
Z_Device & device = getShortAddr(shortaddr);
if (!device.valid()) { return; } // safeguard
JsonObject & json = *device.json;
if (&json == nullptr) { return; } // abort if nothing in buffer
Z_attribute_list &attr_list = device.attr_list;
const char * fname = zigbee_devices.getFriendlyName(shortaddr);
bool use_fname = (Settings.flag4.zigbee_use_names) && (fname); // should we replace shortaddr with friendlyname?
if (!attr_list.isEmpty()) {
const char * fname = zigbee_devices.getFriendlyName(shortaddr);
bool use_fname = (Settings.flag4.zigbee_use_names) && (fname); // should we replace shortaddr with friendlyname?
// save parameters is global variables to be used by Rules
gZbLastMessage.device = shortaddr; // %zbdevice%
gZbLastMessage.groupaddr = json[F(D_CMND_ZIGBEE_GROUP)]; // %zbgroup%
gZbLastMessage.cluster = json[F(D_CMND_ZIGBEE_CLUSTER)]; // %zbcluster%
gZbLastMessage.endpoint = json[F(D_CMND_ZIGBEE_ENDPOINT)]; // %zbendpoint%
// save parameters is global variables to be used by Rules
gZbLastMessage.device = shortaddr; // %zbdevice%
gZbLastMessage.groupaddr = attr_list.group_id; // %zbgroup%
gZbLastMessage.endpoint = attr_list.src_ep; // %zbendpoint%
// dump json in string
String msg = "";
json.printTo(msg);
zigbee_devices.jsonClear(shortaddr);
if (use_fname) {
if (Settings.flag4.remove_zbreceived) {
Response_P(PSTR("{\"%s\":%s}"), fname, msg.c_str());
} else {
Response_P(PSTR("{\"" D_JSON_ZIGBEE_RECEIVED "\":{\"%s\":%s}}"), fname, msg.c_str());
mqtt_data[0] = 0; // clear string
// Do we prefix with `ZbReceived`?
if (!Settings.flag4.remove_zbreceived) {
Response_P(PSTR("{\"" D_JSON_ZIGBEE_RECEIVED "\":"));
}
} else {
if (Settings.flag4.remove_zbreceived) {
Response_P(PSTR("{\"0x%04X\":%s}"), shortaddr, msg.c_str());
// What key do we use, shortaddr or name?
if (use_fname) {
Response_P(PSTR("%s{\"%s\":{"), mqtt_data, fname);
} else {
Response_P(PSTR("{\"" D_JSON_ZIGBEE_RECEIVED "\":{\"0x%04X\":%s}}"), shortaddr, msg.c_str());
Response_P(PSTR("%s{\"0x%04X\":{"), mqtt_data, shortaddr);
}
// Add "Device":"0x...."
Response_P(PSTR("%s\"" D_JSON_ZIGBEE_DEVICE "\":\"0x%04X\","), mqtt_data, shortaddr);
// Add "Name":"xxx" if name is present
if (fname) {
Response_P(PSTR("%s\"" D_JSON_ZIGBEE_NAME "\":\"%s\","), mqtt_data, EscapeJSONString(fname).c_str());
}
// Add all other attributes
Response_P(PSTR("%s%s}}"), mqtt_data, attr_list.toString().c_str());
if (!Settings.flag4.remove_zbreceived) {
Response_P(PSTR("%s}"), mqtt_data);
}
// AddLog_P2(LOG_LEVEL_INFO, PSTR(">>> %s"), mqtt_data); // TODO
attr_list.reset(); // clear the attributes
if (Settings.flag4.zigbee_distinct_topics) {
char subtopic[16];
snprintf_P(subtopic, sizeof(subtopic), PSTR("%04X/" D_RSLT_SENSOR), shortaddr);
MqttPublishPrefixTopic_P(TELE, subtopic, Settings.flag.mqtt_sensor_retain);
} else {
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR), Settings.flag.mqtt_sensor_retain);
}
XdrvRulesProcess(); // apply rules
}
if (Settings.flag4.zigbee_distinct_topics) {
char subtopic[16];
snprintf_P(subtopic, sizeof(subtopic), PSTR("%04X/" D_RSLT_SENSOR), shortaddr);
MqttPublishPrefixTopic_P(TELE, subtopic, Settings.flag.mqtt_sensor_retain);
} else {
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR), Settings.flag.mqtt_sensor_retain);
}
XdrvRulesProcess(); // apply rules
}
void Z_Devices::jsonPublishNow(uint16_t shortaddr, JsonObject & values) {
void Z_Devices::jsonPublishNow(uint16_t shortaddr, Z_attribute_list &attr_list) {
jsonPublishFlush(shortaddr); // flush any previous buffer
jsonAppend(shortaddr, values);
jsonAppend(shortaddr, attr_list);
jsonPublishFlush(shortaddr); // publish now
}
@ -1044,9 +922,8 @@ String Z_Devices::dumpLightState(uint16_t shortaddr) const {
JsonObject& json = jsonBuffer.createObject();
char hex[8];
int32_t found = findShortAddrIdx(shortaddr);
if (found >= 0) {
const Z_Device & device = devicesAt(found);
const Z_Device & device = findShortAddr(shortaddr);
if (foundDevice(device)) {
const char * fname = getFriendlyName(shortaddr);
bool use_fname = (Settings.flag4.zigbee_use_names) && (fname); // should we replace shortaddr with friendlyname?
@ -1090,8 +967,7 @@ String Z_Devices::dump(uint32_t dump_mode, uint16_t status_shortaddr) const {
JsonArray& json = jsonBuffer.createArray();
JsonArray& devices = json;
for (std::vector<Z_Device*>::const_iterator it = _devices.begin(); it != _devices.end(); ++it) {
const Z_Device &device = **it;
for (const auto & device : _devices) {
uint16_t shortaddr = device.shortaddr;
char hex[22];

File diff suppressed because it is too large Load Diff

View File

@ -328,13 +328,8 @@ void sendHueUpdate(uint16_t shortaddr, uint16_t groupaddr, uint16_t cluster, uin
// Parse a cluster specific command, and try to convert into human readable
void convertClusterSpecific(JsonObject& json, uint16_t cluster, uint8_t cmd, bool direction, uint16_t shortaddr, uint8_t srcendpoint, const SBuffer &payload) {
size_t hex_char_len = payload.len()*2+2;
char *hex_char = (char*) malloc(hex_char_len);
if (!hex_char) { return; }
ToHex_P((unsigned char*)payload.getBuffer(), payload.len(), hex_char, hex_char_len);
const __FlashStringHelper* command_name = nullptr;
void convertClusterSpecific(class Z_attribute_list &attr_list, uint16_t cluster, uint8_t cmd, bool direction, uint16_t shortaddr, uint8_t srcendpoint, const SBuffer &payload) {
const char * command_name = nullptr;
uint8_t conv_direction;
Z_XYZ_Var xyz;
@ -373,7 +368,7 @@ void convertClusterSpecific(JsonObject& json, uint16_t cluster, uint8_t cmd, boo
p += 2;
}
if (match) {
command_name = (const __FlashStringHelper*) (Z_strings + pgm_read_word(&conv->tasmota_cmd_offset));
command_name = Z_strings + pgm_read_word(&conv->tasmota_cmd_offset);
parseXYZ(Z_strings + pgm_read_word(&conv->param_offset), payload, &xyz);
if (0xFF == conv_cmd) {
// shift all values
@ -396,112 +391,105 @@ void convertClusterSpecific(JsonObject& json, uint16_t cluster, uint8_t cmd, boo
// Format: "0004<00": "00" = "<cluster><<cmd>": "<payload>" for commands to devices
char attrid_str[12];
snprintf_P(attrid_str, sizeof(attrid_str), PSTR("%04X%c%02X"), cluster, direction ? '<' : '!', cmd);
json[attrid_str] = hex_char;
free(hex_char);
attr_list.addAttribute(attrid_str).setBuf(payload, 0, payload.len());
if (command_name) {
// Now try to transform into a human readable format
String command_name2 = String(command_name);
// if (direction & 0x80) then specific transform
if (conv_direction & 0x80) {
// TODO need to create a specific command
uint32_t cccc00mm = (cluster << 16) | cmd; // format = cccc00mm, cccc = cluster, mm = command
// IAS
if ((cluster == 0x0500) && (cmd == 0x00)) {
// "ZoneStatusChange"
json[command_name] = xyz.x;
switch (cccc00mm) {
case 0x05000000: // "ZoneStatusChange"
attr_list.addAttribute(command_name, true).setUInt(xyz.x);
if (0 != xyz.y) {
json[command_name2 + F("Ext")] = xyz.y;
attr_list.addAttribute(command_name, PSTR("Ext")).setUInt(xyz.y);
}
if ((0 != xyz.z) && (0xFF != xyz.z)) {
json[command_name2 + F("Zone")] = xyz.z;
attr_list.addAttribute(command_name, PSTR("Zone")).setUInt(xyz.z);
}
} else if ((cluster == 0x0004) && ((cmd == 0x00) || (cmd == 0x01) || (cmd == 0x03))) {
// AddGroupResp or ViewGroupResp (group name ignored) or RemoveGroup
json[command_name] = xyz.y;
json[command_name2 + F("Status")] = xyz.x;
json[command_name2 + F("StatusMsg")] = getZigbeeStatusMessage(xyz.x);
} else if ((cluster == 0x0004) && (cmd == 0x02)) {
// GetGroupResp
json[command_name2 + F("Capacity")] = xyz.x;
json[command_name2 + F("Count")] = xyz.y;
JsonArray &arr = json.createNestedArray(command_name);
for (uint32_t i = 0; i < xyz.y; i++) {
arr.add(payload.get16(2 + 2*i));
break;
case 0x00040000:
case 0x00040001:
case 0x00040003: // AddGroupResp or ViewGroupResp (group name ignored) or RemoveGroup
attr_list.addAttribute(command_name, true).setUInt(xyz.y);
attr_list.addAttribute(command_name, PSTR("Status")).setUInt(xyz.x);
attr_list.addAttribute(command_name, PSTR("StatusMsg")).setStr(getZigbeeStatusMessage(xyz.x).c_str());
break;
case 0x00040002: // GetGroupResp
attr_list.addAttribute(command_name, PSTR("Capacity")).setUInt(xyz.x);
attr_list.addAttribute(command_name, PSTR("Count")).setUInt(xyz.y);
{
Z_json_array group_list;
for (uint32_t i = 0; i < xyz.y; i++) {
group_list.add(payload.get16(2 + 2*i));
}
attr_list.addAttribute(command_name, true).setStrRaw(group_list.toString().c_str());
}
} else if ((cluster == 0x0005) && ((cmd == 0x00) || (cmd == 0x02) || (cmd == 0x03))) {
// AddScene or RemoveScene or StoreScene
json[command_name2 + F("Status")] = xyz.x;
json[command_name2 + F("StatusMsg")] = getZigbeeStatusMessage(xyz.x);
json[F("GroupId")] = xyz.y;
json[F("SceneId")] = xyz.z;
} else if ((cluster == 0x0005) && (cmd == 0x01)) {
// ViewScene
json[command_name2 + F("Status")] = xyz.x;
json[command_name2 + F("StatusMsg")] = getZigbeeStatusMessage(xyz.x);
json[F("GroupId")] = xyz.y;
json[F("SceneId")] = xyz.z;
String scene_payload = json[attrid_str];
json[F("ScenePayload")] = scene_payload.substring(8); // remove first 8 characters
} else if ((cluster == 0x0005) && (cmd == 0x03)) {
// RemoveAllScenes
json[command_name2 + F("Status")] = xyz.x;
json[command_name2 + F("StatusMsg")] = getZigbeeStatusMessage(xyz.x);
json[F("GroupId")] = xyz.y;
} else if ((cluster == 0x0005) && (cmd == 0x06)) {
// GetSceneMembership
json[command_name2 + F("Status")] = xyz.x;
json[command_name2 + F("StatusMsg")] = getZigbeeStatusMessage(xyz.x);
json[F("Capacity")] = xyz.y;
json[F("GroupId")] = xyz.z;
String scene_payload = json[attrid_str];
json[F("ScenePayload")] = scene_payload.substring(8); // remove first 8 characters
} else if ((cluster == 0x0006) && (cmd == 0x40)) {
// Power Off With Effect
json[F("Power")] = 0; // always "Power":0
json[F("PowerEffect")] = xyz.x;
json[F("PowerEffectVariant")] = xyz.y;
} else if ((cluster == 0x0006) && (cmd == 0x41)) {
// Power On With Recall Global Scene
json[F("Power")] = 1; // always "Power":1
json[F("PowerRecallGlobalScene")] = true;
} else if ((cluster == 0x0006) && (cmd == 0x42)) {
// Power On With Timed Off Command
json[F("Power")] = 1; // always "Power":1
json[F("PowerOnlyWhenOn")] = xyz.x;
json[F("PowerOnTime")] = xyz.y / 10.0f;
json[F("PowerOffWait")] = xyz.z / 10.0f;
break;
case 0x00050000:
case 0x00050001: // ViewScene
case 0x00050002:
case 0x00050004: // AddScene or RemoveScene or StoreScene
attr_list.addAttribute(command_name, PSTR("Status")).setUInt(xyz.x);
attr_list.addAttribute(command_name, PSTR("StatusMsg")).setStr(getZigbeeStatusMessage(xyz.x).c_str());
attr_list.addAttribute(PSTR("GroupId"), true).setUInt(xyz.y);
attr_list.addAttribute(PSTR("SceneId"), true).setUInt(xyz.z);
if (0x00050001 == cccc00mm) { // ViewScene specific
attr_list.addAttribute(PSTR("ScenePayload"), true).setBuf(payload, 4, payload.len()-4); // remove first 4 bytes
}
break;
case 0x00050003: // RemoveAllScenes
attr_list.addAttribute(command_name, PSTR("Status")).setUInt(xyz.x);
attr_list.addAttribute(command_name, PSTR("StatusMsg")).setStr(getZigbeeStatusMessage(xyz.x).c_str());
attr_list.addAttribute(PSTR("GroupId"), true).setUInt(xyz.y);
break;
case 0x00050006: // GetSceneMembership
attr_list.addAttribute(command_name, PSTR("Status")).setUInt(xyz.x);
attr_list.addAttribute(command_name, PSTR("StatusMsg")).setStr(getZigbeeStatusMessage(xyz.x).c_str());
attr_list.addAttribute(PSTR("Capacity"), true).setUInt(xyz.y);
attr_list.addAttribute(PSTR("GroupId"), true).setUInt(xyz.z);
attr_list.addAttribute(PSTR("ScenePayload"), true).setBuf(payload, 4, payload.len()-4); // remove first 4 bytes
break;
case 0x00060040: // Power Off With Effect
attr_list.addAttribute(PSTR("Power"), true).setUInt(0);
attr_list.addAttribute(PSTR("PowerEffect"), true).setUInt(xyz.x);
attr_list.addAttribute(PSTR("PowerEffectVariant"), true).setUInt(xyz.y);
break;
case 0x00060041: // Power On With Recall Global Scene
attr_list.addAttribute(PSTR("Power"), true).setUInt(1);
attr_list.addAttribute(PSTR("PowerRecallGlobalScene"), true).setBool(true);
break;
case 0x00060042: // Power On With Timed Off Command
attr_list.addAttribute(PSTR("Power"), true).setUInt(1);
attr_list.addAttribute(PSTR("PowerOnlyWhenOn"), true).setUInt(xyz.x);
attr_list.addAttribute(PSTR("PowerOnTime"), true).setFloat(xyz.y / 10.0f);
attr_list.addAttribute(PSTR("PowerOffWait"), true).setFloat(xyz.z / 10.0f);
break;
}
} else { // general case
bool extended_command = false; // do we send command with endpoint suffix
// do we send command with endpoint suffix
char command_suffix[4] = { 0x00 }; // empty string by default
// if SO101 and multiple endpoints, append endpoint number
if (Settings.flag4.zb_index_ep) {
if (zigbee_devices.countEndpoints(shortaddr) > 0) {
command_name2 += srcendpoint;
extended_command = true;
snprintf_P(command_suffix, sizeof(command_suffix), PSTR("%d"), srcendpoint);
}
}
if (0 == xyz.x_type) {
json[command_name] = true; // no parameter
if (extended_command) { json[command_name2] = true; }
attr_list.addAttribute(command_name, command_suffix).setBool(true);
} else if (0 == xyz.y_type) {
json[command_name] = xyz.x; // 1 parameter
if (extended_command) { json[command_name2] = xyz.x; }
attr_list.addAttribute(command_name, command_suffix).setUInt(xyz.x);
} else {
// multiple answers, create an array
JsonArray &arr = json.createNestedArray(command_name);
Z_json_array arr;
arr.add(xyz.x);
arr.add(xyz.y);
if (xyz.z_type) {
arr.add(xyz.z);
}
if (extended_command) {
JsonArray &arr = json.createNestedArray(command_name2);
arr.add(xyz.x);
arr.add(xyz.y);
if (xyz.z_type) {
arr.add(xyz.z);
}
}
attr_list.addAttribute(command_name, command_suffix).setStrRaw(arr.toString().c_str());
}
}
}

View File

@ -1011,53 +1011,53 @@ int32_t EZ_ReceiveTCJoinHandler(int32_t res, const class SBuffer &buf) {
// Parse incoming ZCL message.
//
// This code is common to ZNP and EZSP
void Z_IncomingMessage(ZCLFrame &zcl_received) {
void Z_IncomingMessage(class ZCLFrame &zcl_received) {
uint16_t srcaddr = zcl_received.getSrcAddr();
uint16_t groupid = zcl_received.getGroupAddr();
uint16_t clusterid = zcl_received.getClusterId();
uint8_t linkquality = zcl_received.getLinkQuality();
uint8_t srcendpoint = zcl_received.getSrcEndpoint();
linkquality = linkquality != 0xFF ? linkquality : 0xFE; // avoid 0xFF (reserved for unknown)
bool defer_attributes = false; // do we defer attributes reporting to coalesce
// log the packet details
zcl_received.log();
zigbee_devices.setLQI(srcaddr, linkquality != 0xFF ? linkquality : 0xFE); // EFR32 has a different scale for LQI
zigbee_devices.setLQI(srcaddr, linkquality); // EFR32 has a different scale for LQI
char shortaddr[8];
snprintf_P(shortaddr, sizeof(shortaddr), PSTR("0x%04X"), srcaddr);
DynamicJsonBuffer jsonBuffer;
JsonObject& json = jsonBuffer.createObject();
Z_attribute_list attr_list;
attr_list.lqi = linkquality;
attr_list.src_ep = srcendpoint;
if (groupid) { // TODO we miss the group_id == 0 here
attr_list.group_id = groupid;
}
if ( (!zcl_received.isClusterSpecificCommand()) && (ZCL_DEFAULT_RESPONSE == zcl_received.getCmdId())) {
zcl_received.parseResponse(); // Zigbee general "Degault Response", publish ZbResponse message
zcl_received.parseResponse(); // Zigbee general "Default Response", publish ZbResponse message
} else {
// Build the ZbReceive json
// Build the ZbReceive list
if ( (!zcl_received.isClusterSpecificCommand()) && (ZCL_REPORT_ATTRIBUTES == zcl_received.getCmdId())) {
zcl_received.parseReportAttributes(json); // Zigbee report attributes from sensors
zcl_received.parseReportAttributes(attr_list); // Zigbee report attributes from sensors
if (clusterid) { defer_attributes = true; } // don't defer system Cluster=0 messages
} else if ( (!zcl_received.isClusterSpecificCommand()) && (ZCL_READ_ATTRIBUTES_RESPONSE == zcl_received.getCmdId())) {
zcl_received.parseReadAttributesResponse(json);
zcl_received.parseReadAttributesResponse(attr_list);
if (clusterid) { defer_attributes = true; } // don't defer system Cluster=0 messages
} else if ( (!zcl_received.isClusterSpecificCommand()) && (ZCL_READ_ATTRIBUTES == zcl_received.getCmdId())) {
zcl_received.parseReadAttributes(json);
zcl_received.parseReadAttributes(attr_list);
// never defer read_attributes, so the auto-responder can send response back on a per cluster basis
} else if ( (!zcl_received.isClusterSpecificCommand()) && (ZCL_READ_REPORTING_CONFIGURATION_RESPONSE == zcl_received.getCmdId())) {
zcl_received.parseReadConfigAttributes(json);
zcl_received.parseReadConfigAttributes(attr_list);
} else if ( (!zcl_received.isClusterSpecificCommand()) && (ZCL_CONFIGURE_REPORTING_RESPONSE == zcl_received.getCmdId())) {
zcl_received.parseConfigAttributes(json);
zcl_received.parseConfigAttributes(attr_list);
} else if (zcl_received.isClusterSpecificCommand()) {
zcl_received.parseClusterSpecificCommand(json);
zcl_received.parseClusterSpecificCommand(attr_list);
}
{ // fence to force early de-allocation of msg
String msg("");
msg.reserve(100);
json.printTo(msg);
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_ZIGBEE D_JSON_ZIGBEEZCL_RAW_RECEIVED ": {\"0x%04X\":%s}"), srcaddr, msg.c_str());
}
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_ZIGBEE D_JSON_ZIGBEEZCL_RAW_RECEIVED ": {\"0x%04X\":{%s}}"), srcaddr, attr_list.toString().c_str());
// discard the message if it was sent by us (broadcast or group loopback)
if (srcaddr == localShortAddr) {
@ -1065,37 +1065,25 @@ void Z_IncomingMessage(ZCLFrame &zcl_received) {
return; // abort the rest of message management
}
zcl_received.postProcessAttributes(srcaddr, json);
// Add Endpoint
json[F(D_CMND_ZIGBEE_ENDPOINT)] = srcendpoint;
// Add Group if non-zero
if (groupid) {
json[F(D_CMND_ZIGBEE_GROUP)] = groupid;
}
// Add linkquality
json[F(D_CMND_ZIGBEE_LINKQUALITY)] = linkquality;
zcl_received.generateSyntheticAttributes(attr_list);
zcl_received.generateCallBacks(attr_list); // set deferred callbacks, ex: Occupancy
zcl_received.postProcessAttributes(srcaddr, attr_list);
// since we just receveived data from the device, it is reachable
zigbee_devices.resetTimersForDevice(srcaddr, 0 /* groupaddr */, Z_CAT_REACHABILITY); // remove any reachability timer already there
zigbee_devices.setReachable(srcaddr, true); // mark device as reachable
// Post-provess for Aqara Presence Senson
Z_AqaraOccupancy(srcaddr, clusterid, srcendpoint, json);
if (defer_attributes) {
// Prepare for publish
if (zigbee_devices.jsonIsConflict(srcaddr, json)) {
if (zigbee_devices.jsonIsConflict(srcaddr, attr_list)) {
// there is conflicting values, force a publish of the previous message now and don't coalesce
zigbee_devices.jsonPublishFlush(srcaddr);
}
zigbee_devices.jsonAppend(srcaddr, json);
zigbee_devices.jsonAppend(srcaddr, attr_list);
zigbee_devices.setTimer(srcaddr, 0 /* groupaddr */, USE_ZIGBEE_COALESCE_ATTR_TIMER, clusterid, srcendpoint, Z_CAT_READ_ATTR, 0, &Z_PublishAttributes);
} else {
// Publish immediately
zigbee_devices.jsonPublishNow(srcaddr, json);
// Add auto-responder here
Z_AutoResponder(srcaddr, clusterid, srcendpoint, json[F("ReadNames")]);
zigbee_devices.jsonPublishNow(srcaddr, attr_list);
}
}
}
@ -1281,30 +1269,8 @@ int32_t EZ_Recv_Default(int32_t res, const class SBuffer &buf) {
* Callbacks
\*********************************************************************************************/
// Aqara Occupancy behavior: the Aqara device only sends Occupancy: true events every 60 seconds.
// Here we add a timer so if we don't receive a Occupancy event for 90 seconds, we send Occupancy:false
void Z_AqaraOccupancy(uint16_t shortaddr, uint16_t cluster, uint8_t endpoint, const JsonObject &json) {
static const uint32_t OCCUPANCY_TIMEOUT = 90 * 1000; // 90 s
// Read OCCUPANCY value if any
const JsonVariant &val_endpoint = GetCaseInsensitive(json, PSTR(OCCUPANCY));
if (nullptr != &val_endpoint) {
uint32_t occupancy = strToUInt(val_endpoint);
if (occupancy) {
zigbee_devices.setTimer(shortaddr, 0 /* groupaddr */, OCCUPANCY_TIMEOUT, cluster, endpoint, Z_CAT_VIRTUAL_OCCUPANCY, 0, &Z_OccupancyCallback);
} else {
zigbee_devices.resetTimersForDevice(shortaddr, 0 /* groupaddr */, Z_CAT_VIRTUAL_OCCUPANCY);
}
}
}
// Publish the received values once they have been coalesced
int32_t Z_PublishAttributes(uint16_t shortaddr, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint, uint32_t value) {
const JsonObject *json = zigbee_devices.jsonGet(shortaddr);
if (json == nullptr) { return 0; } // don't crash if not found
zigbee_devices.jsonPublishFlush(shortaddr);
return 1;
}
@ -1476,49 +1442,61 @@ 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 JsonObject &json) {
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();
// responder
switch (cluster) {
case 0x0000:
if (HasKeyCaseInsensitive(json, PSTR("ModelId"))) { json_out[F("ModelId")] = F(USE_ZIGBEE_MODELID); }
if (HasKeyCaseInsensitive(json, PSTR("Manufacturer"))) { json_out[F("Manufacturer")] = F(USE_ZIGBEE_MANUFACTURER); }
break;
for (uint32_t i=0; i<attr_len; i++) {
uint16_t attr = attr_list[i];
uint32_t ccccaaaa = (cluster << 16) || attr;
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
#ifdef USE_LIGHT
case 0x0006:
if (HasKeyCaseInsensitive(json, PSTR("Power"))) { json_out[F("Power")] = Light.power ? 1 : 0; }
break;
case 0x0008:
if (HasKeyCaseInsensitive(json, PSTR("Dimmer"))) { json_out[F("Dimmer")] = LightGetDimmer(0); }
break;
case 0x0300:
{
uint16_t hue;
uint8_t sat;
float XY[2];
LightGetHSB(&hue, &sat, nullptr);
LightGetXY(&XY[0], &XY[1]);
uint16_t uxy[2];
for (uint32_t i = 0; i < ARRAY_SIZE(XY); i++) {
uxy[i] = XY[i] * 65536.0f;
uxy[i] = (uxy[i] > 0xFEFF) ? uxy[i] : 0xFEFF;
}
if (HasKeyCaseInsensitive(json, PSTR("Hue"))) { json_out[F("Hue")] = changeUIntScale(hue, 0, 360, 0, 254); }
if (HasKeyCaseInsensitive(json, PSTR("Sat"))) { json_out[F("Sat")] = changeUIntScale(sat, 0, 255, 0, 254); }
if (HasKeyCaseInsensitive(json, PSTR("CT"))) { json_out[F("CT")] = LightGetColorTemp(); }
if (HasKeyCaseInsensitive(json, PSTR("X"))) { json_out[F("X")] = uxy[0]; }
if (HasKeyCaseInsensitive(json, PSTR("Y"))) { json_out[F("Y")] = uxy[1]; }
}
break;
case 0x00060000: json_out[F("Power")] = Light.power ? 1 : 0; break; // Power
case 0x00080000: json_out[F("Dimmer")] = LightGetDimmer(0); break; // Dimmer
case 0x03000000: // Hue
case 0x03000001: // Sat
case 0x03000003: // X
case 0x03000004: // Y
case 0x03000007: // CT
{
uint16_t hue;
uint8_t sat;
float XY[2];
LightGetHSB(&hue, &sat, nullptr);
LightGetXY(&XY[0], &XY[1]);
uint16_t uxy[2];
for (uint32_t i = 0; i < ARRAY_SIZE(XY); i++) {
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(); }
}
break;
#endif
case 0x000A: // Time
if (HasKeyCaseInsensitive(json, PSTR("Time"))) { json_out[F("Time")] = (Rtc.utc_time > (60 * 60 * 24 * 365 * 10)) ? Rtc.utc_time - 946684800 : Rtc.utc_time; }
if (HasKeyCaseInsensitive(json, PSTR("TimeEpoch"))) { json_out[F("TimeEpoch")] = Rtc.utc_time; }
if (HasKeyCaseInsensitive(json, PSTR("TimeStatus"))) { json_out[F("TimeStatus")] = (Rtc.utc_time > (60 * 60 * 24 * 365 * 10)) ? 0x02 : 0x00; } // if time is beyond 2010 then we are synchronized
if (HasKeyCaseInsensitive(json, PSTR("TimeZone"))) { json_out[F("TimeZone")] = Settings.toffset[0] * 60; } // seconds
break;
case 0x000A0000: // Time
json_out[F("Time")] = (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;
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
break;
case 0x000A0002: // TimeZone
json_out[F("TimeZone")] = 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);
break;
}
}
if (json_out.size() > 0) {

View File

@ -795,7 +795,7 @@ void ZbBindUnbind(bool unbind) { // false = bind, true = unbind
if (nullptr != &val_cluster) {
cluster = strToUInt(val_cluster); // first convert as number
if (0 == cluster) {
zigbeeFindAttributeByName(val_cluster.as<const char*>(), &cluster, nullptr, nullptr, nullptr);
zigbeeFindAttributeByName(val_cluster.as<const char*>(), &cluster, nullptr, nullptr);
}
}