diff --git a/tasmota/support_light_list.ino b/tasmota/support_light_list.ino
new file mode 100644
index 000000000..28d2af595
--- /dev/null
+++ b/tasmota/support_light_list.ino
@@ -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 .
+*/
+
+/*********************************************************************************************\
+ *
+ * private class for Linked List element
+ *
+\*********************************************************************************************/
+template
+class LList;
+
+template
+class LList_elt {
+public:
+
+ LList_elt() : _next(nullptr), _val() {}
+
+ inline T & val(void) { return _val; }
+ inline LList_elt * next(void) { return _next; }
+ inline void next(LList_elt * next) { _next = next; }
+
+ friend class LList;
+
+protected:
+ LList_elt * _next;
+ T _val;
+};
+
+/*********************************************************************************************\
+ *
+ * Lightweight Linked List - optimized for low code size
+ *
+\*********************************************************************************************/
+template
+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*)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 *_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 *cur;
+ LList_elt *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 *_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 *cur;
+ const LList_elt *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 * _head;
+};
+
+template
+size_t LList::length(void) const {
+ size_t count = 0;
+ for (auto & elt : *this) {count++; }
+ return count;
+}
+
+
+template
+const T * LList::at(size_t index) const {
+ size_t count = 0;
+ for (const auto & elt : *this) {
+ if (index == count++) { return &elt; }
+ }
+ return nullptr;
+}
+
+template
+void LList::reset(void) {
+ while (_head) {
+ LList_elt * next = _head->next();
+ delete _head;
+ _head = next;
+ }
+}
+
+template
+void LList::removeHead(void) {
+ if (_head) {
+ LList_elt * next = _head->next();
+ delete _head;
+ _head = next;
+ }
+}
+
+template
+void LList::remove(const T * val) {
+ if (nullptr == val) { return; }
+ // find element in chain and find pointer before
+ LList_elt **curr_ptr = &_head;
+ while (*curr_ptr) {
+ LList_elt * 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
+T & LList::addHead(void) {
+ LList_elt * elt = new LList_elt(); // create element
+ elt->next(_head); // insert at the head
+ _head = elt;
+ return elt->_val;
+}
+
+template
+T & LList::addHead(const T &val) {
+ LList_elt * elt = new LList_elt(); // create element
+ elt->next(_head); // insert at the head
+ elt->_val = val;
+ _head = elt;
+ return elt->_val;
+}
+
+template
+T & LList::addToLast(void) {
+ LList_elt **curr_ptr = &_head;
+ while (*curr_ptr) {
+ curr_ptr = &((*curr_ptr)->_next);
+ }
+ LList_elt * elt = new LList_elt(); // create element
+ *curr_ptr = elt;
+ return elt->_val;
+}
\ No newline at end of file
diff --git a/tasmota/support_static_buffer.ino b/tasmota/support_static_buffer.ino
index ea21b2805..81deb083b 100644
--- a/tasmota/support_static_buffer.ino
+++ b/tasmota/support_static_buffer.ino
@@ -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; iget8(i) != buf2->get8(i)) { return false; }
+ }
+ return true;
+}
\ No newline at end of file
diff --git a/tasmota/xdrv_23_zigbee_1z_libs.ino b/tasmota/xdrv_23_zigbee_1z_libs.ino
new file mode 100644
index 000000000..0590cff81
--- /dev/null
+++ b/tasmota/xdrv_23_zigbee_1z_libs.ino
@@ -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 .
+*/
+
+#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 {
+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(), // 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::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
diff --git a/tasmota/xdrv_23_zigbee_2_devices.ino b/tasmota/xdrv_23_zigbee_2_devices.ino
index aa7ea9d2c..8ad36f861 100644
--- a/tasmota/xdrv_23_zigbee_2_devices.ino
+++ b/tasmota/xdrv_23_zigbee_2_devices.ino
@@ -19,8 +19,6 @@
#ifdef USE_ZIGBEE
-#include
-
#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 _devices = {};
- std::vector _deferred = {}; // list of deferred calls
+ LList _devices; // list of devices
+ LList _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 & 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 & 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()) {
- const char * sval = val.as(); // using char* forces a copy, and also captures 'null' values
- to.set(key, (char*) sval);
- } else if (val.is()) {
- JsonArray &nested_arr = to.createNestedArray(key);
- CopyJsonArray(nested_arr, val.as()); // deep copy
- } else if (val.is()) {
- JsonObject &nested_obj = to.createNestedObject(key);
- CopyJsonObject(nested_obj, val.as()); // 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()) {
- String sval = v.as(); // force a copy of the String value
- to.add(sval);
- } else if (v.is()) {
- } else if (v.is()) {
- } 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(D_CMND_ZIGBEE_GROUP);
- uint16_t group2 = values.get(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() != device.json->get(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::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];
diff --git a/tasmota/xdrv_23_zigbee_5_converters.ino b/tasmota/xdrv_23_zigbee_5_converters.ino
index 5f7c0786b..5655244d7 100644
--- a/tasmota/xdrv_23_zigbee_5_converters.ino
+++ b/tasmota/xdrv_23_zigbee_5_converters.ino
@@ -90,18 +90,13 @@ bool Z_isDiscreteDataType(uint8_t t) {
}
}
-// return value:
-// 0 = keep initial value
-// 1 = remove initial value
-typedef int32_t (*Z_AttrConverter)(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr);
typedef struct Z_AttributeConverter {
uint8_t type;
uint8_t cluster_short;
uint16_t attribute;
uint16_t name_offset;
int8_t multiplier; // multiplier for numerical value, (if > 0 multiply by x, if <0 device by x)
- uint8_t cb; // callback func from Z_ConvOperators
- // Z_AttrConverter func;
+ // still room for a byte
} Z_AttributeConverter;
// Cluster numbers are store in 8 bits format to save space,
@@ -129,400 +124,391 @@ uint16_t CxToCluster(uint8_t cx) {
}
return 0xFFFF;
}
-
-enum Z_ConvOperators {
- Z_Nop, // copy value
- Z_AddPressureUnit, // add pressure unit attribute (non numerical)
- Z_ManufKeep, // copy and record Manufacturer attribute
- Z_ModelKeep, // copy and record ModelId attribute
- Z_AqaraSensor, // decode prioprietary Aqara Sensor message
- Z_AqaraSensor2, // decode prioprietary Aqara Sensor message V2
- Z_AqaraVibration, // decode Aqara vibration modes
- Z_AqaraCube, // decode Aqara cube
- Z_AqaraButton, // decode Aqara button
- Z_BatteryPercentage, // memorize Battery Percentage in RAM
-};
-
// list of post-processing directives
const Z_AttributeConverter Z_PostProcess[] PROGMEM = {
- { Zuint8, Cx0000, 0x0000, Z_(ZCLVersion), 1, Z_Nop },
- { Zuint8, Cx0000, 0x0001, Z_(AppVersion), 1, Z_Nop },
- { Zuint8, Cx0000, 0x0002, Z_(StackVersion), 1, Z_Nop },
- { Zuint8, Cx0000, 0x0003, Z_(HWVersion), 1, Z_Nop },
- { Zstring, Cx0000, 0x0004, Z_(Manufacturer), 1, Z_ManufKeep }, // record Manufacturer
- { Zstring, Cx0000, 0x0005, Z_(ModelId), 1, Z_ModelKeep }, // record Model
- { Zstring, Cx0000, 0x0006, Z_(DateCode), 1, Z_Nop },
- { Zenum8, Cx0000, 0x0007, Z_(PowerSource), 1, Z_Nop },
- { Zenum8, Cx0000, 0x0008, Z_(GenericDeviceClass), 1, Z_Nop },
- { Zenum8, Cx0000, 0x0009, Z_(GenericDeviceType), 1, Z_Nop },
- { Zoctstr, Cx0000, 0x000A, Z_(ProductCode), 1, Z_Nop },
- { Zstring, Cx0000, 0x000B, Z_(ProductURL), 1, Z_Nop },
- { Zstring, Cx0000, 0x4000, Z_(SWBuildID), 1, Z_Nop },
- // { Zunk, Cx0000, 0xFFFF, nullptr, 0, Z_Nop }, // Remove all other values
+ { Zuint8, Cx0000, 0x0000, Z_(ZCLVersion), 1 },
+ { Zuint8, Cx0000, 0x0001, Z_(AppVersion), 1 },
+ { Zuint8, Cx0000, 0x0002, Z_(StackVersion), 1 },
+ { Zuint8, Cx0000, 0x0003, Z_(HWVersion), 1 },
+ { Zstring, Cx0000, 0x0004, Z_(Manufacturer), 1 }, // record Manufacturer
+ { Zstring, Cx0000, 0x0005, Z_(ModelId), 1 }, // record Model
+ // { Zstring, Cx0000, 0x0004, Z_(Manufacturer), 1, Z_ManufKeep }, // record Manufacturer
+ // { Zstring, Cx0000, 0x0005, Z_(ModelId), 1, Z_ModelKeep }, // record Model
+ { Zstring, Cx0000, 0x0006, Z_(DateCode), 1 },
+ { Zenum8, Cx0000, 0x0007, Z_(PowerSource), 1 },
+ { Zenum8, Cx0000, 0x0008, Z_(GenericDeviceClass), 1 },
+ { Zenum8, Cx0000, 0x0009, Z_(GenericDeviceType), 1 },
+ { Zoctstr, Cx0000, 0x000A, Z_(ProductCode), 1 },
+ { Zstring, Cx0000, 0x000B, Z_(ProductURL), 1 },
+ { Zstring, Cx0000, 0x4000, Z_(SWBuildID), 1 },
+ // { Zunk, Cx0000, 0xFFFF, nullptr, 0 }, // Remove all other values
// Cmd 0x0A - Cluster 0x0000, attribute 0xFF01 - proprietary
- { Zmap8, Cx0000, 0xFF01, Z_(), 0, Z_AqaraSensor },
- { Zmap8, Cx0000, 0xFF02, Z_(), 0, Z_AqaraSensor2 },
+ { Zmap8, Cx0000, 0xFF01, Z_(), 0 },
+ { Zmap8, Cx0000, 0xFF02, Z_(), 0 },
+ // { Zmap8, Cx0000, 0xFF01, Z_(), 0, Z_AqaraSensor },
+ // { Zmap8, Cx0000, 0xFF02, Z_(), 0, Z_AqaraSensor2 },
// Power Configuration cluster
- { Zuint16, Cx0001, 0x0000, Z_(MainsVoltage), 1, Z_Nop },
- { Zuint8, Cx0001, 0x0001, Z_(MainsFrequency), 1, Z_Nop },
- { Zuint8, Cx0001, 0x0020, Z_(BatteryVoltage), -10,Z_Nop }, // divide by 10
- { Zuint8, Cx0001, 0x0021, Z_(BatteryPercentage), -2, Z_BatteryPercentage }, // divide by 2
+ { Zuint16, Cx0001, 0x0000, Z_(MainsVoltage), 1 },
+ { Zuint8, Cx0001, 0x0001, Z_(MainsFrequency), 1 },
+ { Zuint8, Cx0001, 0x0020, Z_(BatteryVoltage), -10 }, // divide by 10
+ { Zuint8, Cx0001, 0x0021, Z_(BatteryPercentage), -2 }, // divide by 2
+ // { Zuint8, Cx0001, 0x0021, Z_(BatteryPercentage), -2, Z_BatteryPercentage }, // divide by 2
// Device Temperature Configuration cluster
- { Zint16, Cx0002, 0x0000, Z_(CurrentTemperature), 1, Z_Nop },
- { Zint16, Cx0002, 0x0001, Z_(MinTempExperienced), 1, Z_Nop },
- { Zint16, Cx0002, 0x0002, Z_(MaxTempExperienced), 1, Z_Nop },
- { Zuint16, Cx0002, 0x0003, Z_(OverTempTotalDwell), 1, Z_Nop },
+ { Zint16, Cx0002, 0x0000, Z_(CurrentTemperature), 1 },
+ { Zint16, Cx0002, 0x0001, Z_(MinTempExperienced), 1 },
+ { Zint16, Cx0002, 0x0002, Z_(MaxTempExperienced), 1 },
+ { Zuint16, Cx0002, 0x0003, Z_(OverTempTotalDwell), 1 },
// Identify cluster
- { Zuint16, Cx0003, 0x0000, Z_(IdentifyTime), 1, Z_Nop },
+ { Zuint16, Cx0003, 0x0000, Z_(IdentifyTime), 1 },
// Groups cluster
- { Zmap8, Cx0004, 0x0000, Z_(GroupNameSupport), 1, Z_Nop },
+ { Zmap8, Cx0004, 0x0000, Z_(GroupNameSupport), 1 },
// Scenes cluster
- { Zuint8, Cx0005, 0x0000, Z_(SceneCount), 1, Z_Nop },
- { Zuint8, Cx0005, 0x0001, Z_(CurrentScene), 1, Z_Nop },
- { Zuint16, Cx0005, 0x0002, Z_(CurrentGroup), 1, Z_Nop },
- { Zbool, Cx0005, 0x0003, Z_(SceneValid), 1, Z_Nop },
- //{ Zmap8, Cx0005, 0x0004, (NameSupport), 1, Z_Nop },
+ { Zuint8, Cx0005, 0x0000, Z_(SceneCount), 1 },
+ { Zuint8, Cx0005, 0x0001, Z_(CurrentScene), 1 },
+ { Zuint16, Cx0005, 0x0002, Z_(CurrentGroup), 1 },
+ { Zbool, Cx0005, 0x0003, Z_(SceneValid), 1 },
+ //{ Zmap8, Cx0005, 0x0004, (NameSupport), 1 },
// On/off cluster
- { Zbool, Cx0006, 0x0000, Z_(Power), 1, Z_Nop },
- { Zenum8, Cx0006, 0x4003, Z_(StartUpOnOff), 1, Z_Nop },
- { Zbool, Cx0006, 0x8000, Z_(Power), 1, Z_Nop }, // See 7280
+ { Zbool, Cx0006, 0x0000, Z_(Power), 1 },
+ { Zenum8, Cx0006, 0x4003, Z_(StartUpOnOff), 1 },
+ { Zbool, Cx0006, 0x8000, Z_(Power), 1 }, // See 7280
// On/Off Switch Configuration cluster
- { Zenum8, Cx0007, 0x0000, Z_(SwitchType), 1, Z_Nop },
+ { Zenum8, Cx0007, 0x0000, Z_(SwitchType), 1 },
// Level Control cluster
- { Zuint8, Cx0008, 0x0000, Z_(Dimmer), 1, Z_Nop },
- { Zmap8, Cx0008, 0x000F, Z_(DimmerOptions), 1, Z_Nop },
- { Zuint16, Cx0008, 0x0001, Z_(DimmerRemainingTime), 1, Z_Nop },
- { Zuint16, Cx0008, 0x0010, Z_(OnOffTransitionTime), 1, Z_Nop },
- // { Zuint8, Cx0008, 0x0011, (OnLevel), 1, Z_Nop },
- // { Zuint16, Cx0008, 0x0012, (OnTransitionTime), 1, Z_Nop },
- // { Zuint16, Cx0008, 0x0013, (OffTransitionTime), 1, Z_Nop },
- // { Zuint16, Cx0008, 0x0014, (DefaultMoveRate), 1, Z_Nop },
+ { Zuint8, Cx0008, 0x0000, Z_(Dimmer), 1 },
+ { Zmap8, Cx0008, 0x000F, Z_(DimmerOptions), 1 },
+ { Zuint16, Cx0008, 0x0001, Z_(DimmerRemainingTime), 1 },
+ { Zuint16, Cx0008, 0x0010, Z_(OnOffTransitionTime), 1 },
+ // { Zuint8, Cx0008, 0x0011, (OnLevel), 1 },
+ // { Zuint16, Cx0008, 0x0012, (OnTransitionTime), 1 },
+ // { Zuint16, Cx0008, 0x0013, (OffTransitionTime), 1 },
+ // { Zuint16, Cx0008, 0x0014, (DefaultMoveRate), 1 },
// Alarms cluster
- { Zuint16, Cx0009, 0x0000, Z_(AlarmCount), 1, Z_Nop },
+ { Zuint16, Cx0009, 0x0000, Z_(AlarmCount), 1 },
// Time cluster
- { ZUTC, Cx000A, 0x0000, Z_(Time), 1, Z_Nop },
- { Zmap8, Cx000A, 0x0001, Z_(TimeStatus), 1, Z_Nop },
- { Zint32, Cx000A, 0x0002, Z_(TimeZone), 1, Z_Nop },
- { Zuint32, Cx000A, 0x0003, Z_(DstStart), 1, Z_Nop },
- { Zuint32, Cx000A, 0x0004, Z_(DstEnd), 1, Z_Nop },
- { Zint32, Cx000A, 0x0005, Z_(DstShift), 1, Z_Nop },
- { Zuint32, Cx000A, 0x0006, Z_(StandardTime), 1, Z_Nop },
- { Zuint32, Cx000A, 0x0007, Z_(LocalTime), 1, Z_Nop },
- { ZUTC, Cx000A, 0x0008, Z_(LastSetTime), 1, Z_Nop },
- { ZUTC, Cx000A, 0x0009, Z_(ValidUntilTime), 1, Z_Nop },
- { ZUTC, Cx000A, 0xFF00, Z_(TimeEpoch), 1, Z_Nop }, // Tasmota specific, epoch
+ { ZUTC, Cx000A, 0x0000, Z_(Time), 1 },
+ { Zmap8, Cx000A, 0x0001, Z_(TimeStatus), 1 },
+ { Zint32, Cx000A, 0x0002, Z_(TimeZone), 1 },
+ { Zuint32, Cx000A, 0x0003, Z_(DstStart), 1 },
+ { Zuint32, Cx000A, 0x0004, Z_(DstEnd), 1 },
+ { Zint32, Cx000A, 0x0005, Z_(DstShift), 1 },
+ { Zuint32, Cx000A, 0x0006, Z_(StandardTime), 1 },
+ { Zuint32, Cx000A, 0x0007, Z_(LocalTime), 1 },
+ { ZUTC, Cx000A, 0x0008, Z_(LastSetTime), 1 },
+ { ZUTC, Cx000A, 0x0009, Z_(ValidUntilTime), 1 },
+ { ZUTC, Cx000A, 0xFF00, Z_(TimeEpoch), 1 }, // Tasmota specific, epoch
// RSSI Location cluster
- { Zdata8, Cx000B, 0x0000, Z_(LocationType), 1, Z_Nop },
- { Zenum8, Cx000B, 0x0001, Z_(LocationMethod), 1, Z_Nop },
- { Zuint16, Cx000B, 0x0002, Z_(LocationAge), 1, Z_Nop },
- { Zuint8, Cx000B, 0x0003, Z_(QualityMeasure), 1, Z_Nop },
- { Zuint8, Cx000B, 0x0004, Z_(NumberOfDevices), 1, Z_Nop },
+ { Zdata8, Cx000B, 0x0000, Z_(LocationType), 1 },
+ { Zenum8, Cx000B, 0x0001, Z_(LocationMethod), 1 },
+ { Zuint16, Cx000B, 0x0002, Z_(LocationAge), 1 },
+ { Zuint8, Cx000B, 0x0003, Z_(QualityMeasure), 1 },
+ { Zuint8, Cx000B, 0x0004, Z_(NumberOfDevices), 1 },
// Analog Input cluster
- // { 0xFF, Cx000C, 0x0004, (AnalogInActiveText), 1, Z_Nop },
- { Zstring, Cx000C, 0x001C, Z_(AnalogInDescription), 1, Z_Nop },
- // { 0xFF, Cx000C, 0x002E, (AnalogInInactiveText), 1, Z_Nop },
- { Zsingle, Cx000C, 0x0041, Z_(AnalogInMaxValue), 1, Z_Nop },
- { Zsingle, Cx000C, 0x0045, Z_(AnalogInMinValue), 1, Z_Nop },
- { Zbool, Cx000C, 0x0051, Z_(AnalogInOutOfService), 1, Z_Nop },
- { Zsingle, Cx000C, 0x0055, Z_(AqaraRotate), 1, Z_Nop },
- // { 0xFF, Cx000C, 0x0057, (AnalogInPriorityArray),1, Z_Nop },
- { Zenum8, Cx000C, 0x0067, Z_(AnalogInReliability), 1, Z_Nop },
- // { 0xFF, Cx000C, 0x0068, (AnalogInRelinquishDefault),1, Z_Nop },
- { Zsingle, Cx000C, 0x006A, Z_(AnalogInResolution), 1, Z_Nop },
- { Zmap8, Cx000C, 0x006F, Z_(AnalogInStatusFlags), 1, Z_Nop },
- { Zenum16, Cx000C, 0x0075, Z_(AnalogInEngineeringUnits),1, Z_Nop },
- { Zuint32, Cx000C, 0x0100, Z_(AnalogInApplicationType),1, Z_Nop },
- { Zuint16, Cx000C, 0xFF05, Z_(Aqara_FF05), 1, Z_Nop },
+ // { 0xFF, Cx000C, 0x0004, (AnalogInActiveText), 1 },
+ { Zstring, Cx000C, 0x001C, Z_(AnalogInDescription), 1 },
+ // { 0xFF, Cx000C, 0x002E, (AnalogInInactiveText), 1 },
+ { Zsingle, Cx000C, 0x0041, Z_(AnalogInMaxValue), 1 },
+ { Zsingle, Cx000C, 0x0045, Z_(AnalogInMinValue), 1 },
+ { Zbool, Cx000C, 0x0051, Z_(AnalogInOutOfService), 1 },
+ { Zsingle, Cx000C, 0x0055, Z_(AqaraRotate), 1 },
+ // { 0xFF, Cx000C, 0x0057, (AnalogInPriorityArray),1 },
+ { Zenum8, Cx000C, 0x0067, Z_(AnalogInReliability), 1 },
+ // { 0xFF, Cx000C, 0x0068, (AnalogInRelinquishDefault),1 },
+ { Zsingle, Cx000C, 0x006A, Z_(AnalogInResolution), 1 },
+ { Zmap8, Cx000C, 0x006F, Z_(AnalogInStatusFlags), 1 },
+ { Zenum16, Cx000C, 0x0075, Z_(AnalogInEngineeringUnits),1 },
+ { Zuint32, Cx000C, 0x0100, Z_(AnalogInApplicationType),1 },
+ { Zuint16, Cx000C, 0xFF05, Z_(Aqara_FF05), 1 },
// Analog Output cluster
- { Zstring, Cx000D, 0x001C, Z_(AnalogOutDescription), 1, Z_Nop },
- { Zsingle, Cx000D, 0x0041, Z_(AnalogOutMaxValue), 1, Z_Nop },
- { Zsingle, Cx000D, 0x0045, Z_(AnalogOutMinValue), 1, Z_Nop },
- { Zbool, Cx000D, 0x0051, Z_(AnalogOutOutOfService),1, Z_Nop },
- { Zsingle, Cx000D, 0x0055, Z_(AnalogOutValue), 1, Z_Nop },
- // { Zunk, Cx000D, 0x0057, (AnalogOutPriorityArray),1, Z_Nop },
- { Zenum8, Cx000D, 0x0067, Z_(AnalogOutReliability), 1, Z_Nop },
- { Zsingle, Cx000D, 0x0068, Z_(AnalogOutRelinquishDefault),1, Z_Nop },
- { Zsingle, Cx000D, 0x006A, Z_(AnalogOutResolution), 1, Z_Nop },
- { Zmap8, Cx000D, 0x006F, Z_(AnalogOutStatusFlags), 1, Z_Nop },
- { Zenum16, Cx000D, 0x0075, Z_(AnalogOutEngineeringUnits),1, Z_Nop },
- { Zuint32, Cx000D, 0x0100, Z_(AnalogOutApplicationType),1, Z_Nop },
+ { Zstring, Cx000D, 0x001C, Z_(AnalogOutDescription), 1 },
+ { Zsingle, Cx000D, 0x0041, Z_(AnalogOutMaxValue), 1 },
+ { Zsingle, Cx000D, 0x0045, Z_(AnalogOutMinValue), 1 },
+ { Zbool, Cx000D, 0x0051, Z_(AnalogOutOutOfService),1 },
+ { Zsingle, Cx000D, 0x0055, Z_(AnalogOutValue), 1 },
+ // { Zunk, Cx000D, 0x0057, (AnalogOutPriorityArray),1 },
+ { Zenum8, Cx000D, 0x0067, Z_(AnalogOutReliability), 1 },
+ { Zsingle, Cx000D, 0x0068, Z_(AnalogOutRelinquishDefault),1 },
+ { Zsingle, Cx000D, 0x006A, Z_(AnalogOutResolution), 1 },
+ { Zmap8, Cx000D, 0x006F, Z_(AnalogOutStatusFlags), 1 },
+ { Zenum16, Cx000D, 0x0075, Z_(AnalogOutEngineeringUnits),1 },
+ { Zuint32, Cx000D, 0x0100, Z_(AnalogOutApplicationType),1 },
// Analog Value cluster
- { Zstring, Cx000E, 0x001C, Z_(AnalogDescription), 1, Z_Nop },
- { Zbool, Cx000E, 0x0051, Z_(AnalogOutOfService), 1, Z_Nop },
- { Zsingle, Cx000E, 0x0055, Z_(AnalogValue), 1, Z_Nop },
- { Zunk, Cx000E, 0x0057, Z_(AnalogPriorityArray), 1, Z_Nop },
- { Zenum8, Cx000E, 0x0067, Z_(AnalogReliability), 1, Z_Nop },
- { Zsingle, Cx000E, 0x0068, Z_(AnalogRelinquishDefault),1, Z_Nop },
- { Zmap8, Cx000E, 0x006F, Z_(AnalogStatusFlags), 1, Z_Nop },
- { Zenum16, Cx000E, 0x0075, Z_(AnalogEngineeringUnits),1, Z_Nop },
- { Zuint32, Cx000E, 0x0100, Z_(AnalogApplicationType),1, Z_Nop },
+ { Zstring, Cx000E, 0x001C, Z_(AnalogDescription), 1 },
+ { Zbool, Cx000E, 0x0051, Z_(AnalogOutOfService), 1 },
+ { Zsingle, Cx000E, 0x0055, Z_(AnalogValue), 1 },
+ { Zunk, Cx000E, 0x0057, Z_(AnalogPriorityArray), 1 },
+ { Zenum8, Cx000E, 0x0067, Z_(AnalogReliability), 1 },
+ { Zsingle, Cx000E, 0x0068, Z_(AnalogRelinquishDefault),1 },
+ { Zmap8, Cx000E, 0x006F, Z_(AnalogStatusFlags), 1 },
+ { Zenum16, Cx000E, 0x0075, Z_(AnalogEngineeringUnits),1 },
+ { Zuint32, Cx000E, 0x0100, Z_(AnalogApplicationType),1 },
// Binary Input cluster
- { Zstring, Cx000F, 0x0004, Z_(BinaryInActiveText), 1, Z_Nop },
- { Zstring, Cx000F, 0x001C, Z_(BinaryInDescription), 1, Z_Nop },
- { Zstring, Cx000F, 0x002E, Z_(BinaryInInactiveText),1, Z_Nop },
- { Zbool, Cx000F, 0x0051, Z_(BinaryInOutOfService),1, Z_Nop },
- { Zenum8, Cx000F, 0x0054, Z_(BinaryInPolarity), 1, Z_Nop },
- { Zstring, Cx000F, 0x0055, Z_(BinaryInValue), 1, Z_Nop },
- // { 0xFF, Cx000F, 0x0057, (BinaryInPriorityArray),1, Z_Nop },
- { Zenum8, Cx000F, 0x0067, Z_(BinaryInReliability), 1, Z_Nop },
- { Zmap8, Cx000F, 0x006F, Z_(BinaryInStatusFlags), 1, Z_Nop },
- { Zuint32, Cx000F, 0x0100, Z_(BinaryInApplicationType),1, Z_Nop },
+ { Zstring, Cx000F, 0x0004, Z_(BinaryInActiveText), 1 },
+ { Zstring, Cx000F, 0x001C, Z_(BinaryInDescription), 1 },
+ { Zstring, Cx000F, 0x002E, Z_(BinaryInInactiveText),1 },
+ { Zbool, Cx000F, 0x0051, Z_(BinaryInOutOfService),1 },
+ { Zenum8, Cx000F, 0x0054, Z_(BinaryInPolarity), 1 },
+ { Zstring, Cx000F, 0x0055, Z_(BinaryInValue), 1 },
+ // { 0xFF, Cx000F, 0x0057, (BinaryInPriorityArray),1 },
+ { Zenum8, Cx000F, 0x0067, Z_(BinaryInReliability), 1 },
+ { Zmap8, Cx000F, 0x006F, Z_(BinaryInStatusFlags), 1 },
+ { Zuint32, Cx000F, 0x0100, Z_(BinaryInApplicationType),1 },
// Binary Output cluster
- { Zstring, Cx0010, 0x0004, Z_(BinaryOutActiveText), 1, Z_Nop },
- { Zstring, Cx0010, 0x001C, Z_(BinaryOutDescription), 1, Z_Nop },
- { Zstring, Cx0010, 0x002E, Z_(BinaryOutInactiveText),1, Z_Nop },
- { Zuint32, Cx0010, 0x0042, Z_(BinaryOutMinimumOffTime),1, Z_Nop },
- { Zuint32, Cx0010, 0x0043, Z_(BinaryOutMinimumOnTime),1, Z_Nop },
- { Zbool, Cx0010, 0x0051, Z_(BinaryOutOutOfService),1, Z_Nop },
- { Zenum8, Cx0010, 0x0054, Z_(BinaryOutPolarity), 1, Z_Nop },
- { Zbool, Cx0010, 0x0055, Z_(BinaryOutValue), 1, Z_Nop },
- // { Zunk, Cx0010, 0x0057, (BinaryOutPriorityArray),1, Z_Nop },
- { Zenum8, Cx0010, 0x0067, Z_(BinaryOutReliability), 1, Z_Nop },
- { Zbool, Cx0010, 0x0068, Z_(BinaryOutRelinquishDefault),1, Z_Nop },
- { Zmap8, Cx0010, 0x006F, Z_(BinaryOutStatusFlags), 1, Z_Nop },
- { Zuint32, Cx0010, 0x0100, Z_(BinaryOutApplicationType),1, Z_Nop },
+ { Zstring, Cx0010, 0x0004, Z_(BinaryOutActiveText), 1 },
+ { Zstring, Cx0010, 0x001C, Z_(BinaryOutDescription), 1 },
+ { Zstring, Cx0010, 0x002E, Z_(BinaryOutInactiveText),1 },
+ { Zuint32, Cx0010, 0x0042, Z_(BinaryOutMinimumOffTime),1 },
+ { Zuint32, Cx0010, 0x0043, Z_(BinaryOutMinimumOnTime),1 },
+ { Zbool, Cx0010, 0x0051, Z_(BinaryOutOutOfService),1 },
+ { Zenum8, Cx0010, 0x0054, Z_(BinaryOutPolarity), 1 },
+ { Zbool, Cx0010, 0x0055, Z_(BinaryOutValue), 1 },
+ // { Zunk, Cx0010, 0x0057, (BinaryOutPriorityArray),1 },
+ { Zenum8, Cx0010, 0x0067, Z_(BinaryOutReliability), 1 },
+ { Zbool, Cx0010, 0x0068, Z_(BinaryOutRelinquishDefault),1 },
+ { Zmap8, Cx0010, 0x006F, Z_(BinaryOutStatusFlags), 1 },
+ { Zuint32, Cx0010, 0x0100, Z_(BinaryOutApplicationType),1 },
// Binary Value cluster
- { Zstring, Cx0011, 0x0004, Z_(BinaryActiveText), 1, Z_Nop },
- { Zstring, Cx0011, 0x001C, Z_(BinaryDescription), 1, Z_Nop },
- { Zstring, Cx0011, 0x002E, Z_(BinaryInactiveText), 1, Z_Nop },
- { Zuint32, Cx0011, 0x0042, Z_(BinaryMinimumOffTime), 1, Z_Nop },
- { Zuint32, Cx0011, 0x0043, Z_(BinaryMinimumOnTime), 1, Z_Nop },
- { Zbool, Cx0011, 0x0051, Z_(BinaryOutOfService), 1, Z_Nop },
- { Zbool, Cx0011, 0x0055, Z_(BinaryValue), 1, Z_Nop },
- // { Zunk, Cx0011, 0x0057, (BinaryPriorityArray), 1, Z_Nop },
- { Zenum8, Cx0011, 0x0067, Z_(BinaryReliability), 1, Z_Nop },
- { Zbool, Cx0011, 0x0068, Z_(BinaryRelinquishDefault),1, Z_Nop },
- { Zmap8, Cx0011, 0x006F, Z_(BinaryStatusFlags), 1, Z_Nop },
- { Zuint32, Cx0011, 0x0100, Z_(BinaryApplicationType),1, Z_Nop },
+ { Zstring, Cx0011, 0x0004, Z_(BinaryActiveText), 1 },
+ { Zstring, Cx0011, 0x001C, Z_(BinaryDescription), 1 },
+ { Zstring, Cx0011, 0x002E, Z_(BinaryInactiveText), 1 },
+ { Zuint32, Cx0011, 0x0042, Z_(BinaryMinimumOffTime), 1 },
+ { Zuint32, Cx0011, 0x0043, Z_(BinaryMinimumOnTime), 1 },
+ { Zbool, Cx0011, 0x0051, Z_(BinaryOutOfService), 1 },
+ { Zbool, Cx0011, 0x0055, Z_(BinaryValue), 1 },
+ // { Zunk, Cx0011, 0x0057, (BinaryPriorityArray), 1 },
+ { Zenum8, Cx0011, 0x0067, Z_(BinaryReliability), 1 },
+ { Zbool, Cx0011, 0x0068, Z_(BinaryRelinquishDefault),1 },
+ { Zmap8, Cx0011, 0x006F, Z_(BinaryStatusFlags), 1 },
+ { Zuint32, Cx0011, 0x0100, Z_(BinaryApplicationType),1 },
// Multistate Input cluster
- // { Zunk, Cx0012, 0x000E, (MultiInStateText), 1, Z_Nop },
- { Zstring, Cx0012, 0x001C, Z_(MultiInDescription), 1, Z_Nop },
- { Zuint16, Cx0012, 0x004A, Z_(MultiInNumberOfStates),1, Z_Nop },
- { Zbool, Cx0012, 0x0051, Z_(MultiInOutOfService), 1, Z_Nop },
- { Zuint16, Cx0012, 0x0055, Z_(MultiInValue), 0, Z_AqaraCube },
- { Zuint16, Cx0012, 0x0055, Z_(MultiInValue), 0, Z_AqaraButton },
- { Zenum8, Cx0012, 0x0067, Z_(MultiInReliability), 1, Z_Nop },
- { Zmap8, Cx0012, 0x006F, Z_(MultiInStatusFlags), 1, Z_Nop },
- { Zuint32, Cx0012, 0x0100, Z_(MultiInApplicationType),1, Z_Nop },
+ // { Zunk, Cx0012, 0x000E, (MultiInStateText), 1 },
+ { Zstring, Cx0012, 0x001C, Z_(MultiInDescription), 1 },
+ { Zuint16, Cx0012, 0x004A, Z_(MultiInNumberOfStates),1 },
+ { Zbool, Cx0012, 0x0051, Z_(MultiInOutOfService), 1 },
+ { Zuint16, Cx0012, 0x0055, Z_(MultiInValue), 1 },
+ // { Zuint16, Cx0012, 0x0055, Z_(MultiInValue), 0, Z_AqaraCube },
+ // { Zuint16, Cx0012, 0x0055, Z_(MultiInValue), 0, Z_AqaraButton },
+ { Zenum8, Cx0012, 0x0067, Z_(MultiInReliability), 1 },
+ { Zmap8, Cx0012, 0x006F, Z_(MultiInStatusFlags), 1 },
+ { Zuint32, Cx0012, 0x0100, Z_(MultiInApplicationType),1 },
// Multistate output
- // { Zunk, Cx0013, 0x000E, (MultiOutStateText), 1, Z_Nop },
- { Zstring, Cx0013, 0x001C, Z_(MultiOutDescription), 1, Z_Nop },
- { Zuint16, Cx0013, 0x004A, Z_(MultiOutNumberOfStates),1, Z_Nop },
- { Zbool, Cx0013, 0x0051, Z_(MultiOutOutOfService), 1, Z_Nop },
- { Zuint16, Cx0013, 0x0055, Z_(MultiOutValue), 1, Z_Nop },
- // { Zunk, Cx0013, 0x0057, (MultiOutPriorityArray),1, Z_Nop },
- { Zenum8, Cx0013, 0x0067, Z_(MultiOutReliability), 1, Z_Nop },
- { Zuint16, Cx0013, 0x0068, Z_(MultiOutRelinquishDefault),1, Z_Nop },
- { Zmap8, Cx0013, 0x006F, Z_(MultiOutStatusFlags), 1, Z_Nop },
- { Zuint32, Cx0013, 0x0100, Z_(MultiOutApplicationType),1, Z_Nop },
+ // { Zunk, Cx0013, 0x000E, (MultiOutStateText), 1 },
+ { Zstring, Cx0013, 0x001C, Z_(MultiOutDescription), 1 },
+ { Zuint16, Cx0013, 0x004A, Z_(MultiOutNumberOfStates),1 },
+ { Zbool, Cx0013, 0x0051, Z_(MultiOutOutOfService), 1 },
+ { Zuint16, Cx0013, 0x0055, Z_(MultiOutValue), 1 },
+ // { Zunk, Cx0013, 0x0057, (MultiOutPriorityArray),1 },
+ { Zenum8, Cx0013, 0x0067, Z_(MultiOutReliability), 1 },
+ { Zuint16, Cx0013, 0x0068, Z_(MultiOutRelinquishDefault),1 },
+ { Zmap8, Cx0013, 0x006F, Z_(MultiOutStatusFlags), 1 },
+ { Zuint32, Cx0013, 0x0100, Z_(MultiOutApplicationType),1 },
// Multistate Value cluster
- // { Zunk, Cx0014, 0x000E, (MultiStateText), 1, Z_Nop },
- { Zstring, Cx0014, 0x001C, Z_(MultiDescription), 1, Z_Nop },
- { Zuint16, Cx0014, 0x004A, Z_(MultiNumberOfStates), 1, Z_Nop },
- { Zbool, Cx0014, 0x0051, Z_(MultiOutOfService), 1, Z_Nop },
- { Zuint16, Cx0014, 0x0055, Z_(MultiValue), 1, Z_Nop },
- { Zenum8, Cx0014, 0x0067, Z_(MultiReliability), 1, Z_Nop },
- { Zuint16, Cx0014, 0x0068, Z_(MultiRelinquishDefault),1, Z_Nop },
- { Zmap8, Cx0014, 0x006F, Z_(MultiStatusFlags), 1, Z_Nop },
- { Zuint32, Cx0014, 0x0100, Z_(MultiApplicationType), 1, Z_Nop },
+ // { Zunk, Cx0014, 0x000E, (MultiStateText), 1 },
+ { Zstring, Cx0014, 0x001C, Z_(MultiDescription), 1 },
+ { Zuint16, Cx0014, 0x004A, Z_(MultiNumberOfStates), 1 },
+ { Zbool, Cx0014, 0x0051, Z_(MultiOutOfService), 1 },
+ { Zuint16, Cx0014, 0x0055, Z_(MultiValue), 1 },
+ { Zenum8, Cx0014, 0x0067, Z_(MultiReliability), 1 },
+ { Zuint16, Cx0014, 0x0068, Z_(MultiRelinquishDefault),1 },
+ { Zmap8, Cx0014, 0x006F, Z_(MultiStatusFlags), 1 },
+ { Zuint32, Cx0014, 0x0100, Z_(MultiApplicationType), 1 },
// Power Profile cluster
- { Zuint8, Cx001A, 0x0000, Z_(TotalProfileNum), 1, Z_Nop },
- { Zbool, Cx001A, 0x0001, Z_(MultipleScheduling), 1, Z_Nop },
- { Zmap8, Cx001A, 0x0002, Z_(EnergyFormatting), 1, Z_Nop },
- { Zbool, Cx001A, 0x0003, Z_(EnergyRemote), 1, Z_Nop },
- { Zmap8, Cx001A, 0x0004, Z_(ScheduleMode), 1, Z_Nop },
+ { Zuint8, Cx001A, 0x0000, Z_(TotalProfileNum), 1 },
+ { Zbool, Cx001A, 0x0001, Z_(MultipleScheduling), 1 },
+ { Zmap8, Cx001A, 0x0002, Z_(EnergyFormatting), 1 },
+ { Zbool, Cx001A, 0x0003, Z_(EnergyRemote), 1 },
+ { Zmap8, Cx001A, 0x0004, Z_(ScheduleMode), 1 },
// Poll Control cluster
- { Zuint32, Cx0020, 0x0000, Z_(CheckinInterval), 1, Z_Nop },
- { Zuint32, Cx0020, 0x0001, Z_(LongPollInterval), 1, Z_Nop },
- { Zuint16, Cx0020, 0x0002, Z_(ShortPollInterval), 1, Z_Nop },
- { Zuint16, Cx0020, 0x0003, Z_(FastPollTimeout), 1, Z_Nop },
- { Zuint32, Cx0020, 0x0004, Z_(CheckinIntervalMin), 1, Z_Nop },
- { Zuint32, Cx0020, 0x0005, Z_(LongPollIntervalMin), 1, Z_Nop },
- { Zuint16, Cx0020, 0x0006, Z_(FastPollTimeoutMax), 1, Z_Nop },
+ { Zuint32, Cx0020, 0x0000, Z_(CheckinInterval), 1 },
+ { Zuint32, Cx0020, 0x0001, Z_(LongPollInterval), 1 },
+ { Zuint16, Cx0020, 0x0002, Z_(ShortPollInterval), 1 },
+ { Zuint16, Cx0020, 0x0003, Z_(FastPollTimeout), 1 },
+ { Zuint32, Cx0020, 0x0004, Z_(CheckinIntervalMin), 1 },
+ { Zuint32, Cx0020, 0x0005, Z_(LongPollIntervalMin), 1 },
+ { Zuint16, Cx0020, 0x0006, Z_(FastPollTimeoutMax), 1 },
// Shade Configuration cluster
- { Zuint16, Cx0100, 0x0000, Z_(PhysicalClosedLimit), 1, Z_Nop },
- { Zuint8, Cx0100, 0x0001, Z_(MotorStepSize), 1, Z_Nop },
- { Zmap8, Cx0100, 0x0002, Z_(Status), 1, Z_Nop },
- { Zuint16, Cx0100, 0x0010, Z_(ClosedLimit), 1, Z_Nop },
- { Zenum8, Cx0100, 0x0011, Z_(Mode), 1, Z_Nop },
+ { Zuint16, Cx0100, 0x0000, Z_(PhysicalClosedLimit), 1 },
+ { Zuint8, Cx0100, 0x0001, Z_(MotorStepSize), 1 },
+ { Zmap8, Cx0100, 0x0002, Z_(Status), 1 },
+ { Zuint16, Cx0100, 0x0010, Z_(ClosedLimit), 1 },
+ { Zenum8, Cx0100, 0x0011, Z_(Mode), 1 },
// Door Lock cluster
- { Zenum8, Cx0101, 0x0000, Z_(LockState), 1, Z_Nop },
- { Zenum8, Cx0101, 0x0001, Z_(LockType), 1, Z_Nop },
- { Zbool, Cx0101, 0x0002, Z_(ActuatorEnabled), 1, Z_Nop },
- { Zenum8, Cx0101, 0x0003, Z_(DoorState), 1, Z_Nop },
- { Zuint32, Cx0101, 0x0004, Z_(DoorOpenEvents), 1, Z_Nop },
- { Zuint32, Cx0101, 0x0005, Z_(DoorClosedEvents), 1, Z_Nop },
- { Zuint16, Cx0101, 0x0006, Z_(OpenPeriod), 1, Z_Nop },
+ { Zenum8, Cx0101, 0x0000, Z_(LockState), 1 },
+ { Zenum8, Cx0101, 0x0001, Z_(LockType), 1 },
+ { Zbool, Cx0101, 0x0002, Z_(ActuatorEnabled), 1 },
+ { Zenum8, Cx0101, 0x0003, Z_(DoorState), 1 },
+ { Zuint32, Cx0101, 0x0004, Z_(DoorOpenEvents), 1 },
+ { Zuint32, Cx0101, 0x0005, Z_(DoorClosedEvents), 1 },
+ { Zuint16, Cx0101, 0x0006, Z_(OpenPeriod), 1 },
// Aqara Lumi Vibration Sensor
- { Zuint16, Cx0101, 0x0055, Z_(AqaraVibrationMode), 0, Z_AqaraVibration },
- { Zuint16, Cx0101, 0x0503, Z_(AqaraVibrationsOrAngle), 1, Z_Nop },
- { Zuint32, Cx0101, 0x0505, Z_(AqaraVibration505), 1, Z_Nop },
- { Zuint48, Cx0101, 0x0508, Z_(AqaraAccelerometer), 0, Z_AqaraVibration },
+ { Zuint16, Cx0101, 0x0055, Z_(AqaraVibrationMode), 1 },
+ { Zuint16, Cx0101, 0x0503, Z_(AqaraVibrationsOrAngle), 1 },
+ { Zuint32, Cx0101, 0x0505, Z_(AqaraVibration505), 1 },
+ { Zuint48, Cx0101, 0x0508, Z_(AqaraAccelerometer), 1 },
// Window Covering cluster
- { Zenum8, Cx0102, 0x0000, Z_(WindowCoveringType), 1, Z_Nop },
- { Zuint16, Cx0102, 0x0001, Z_(PhysicalClosedLimitLift),1, Z_Nop },
- { Zuint16, Cx0102, 0x0002, Z_(PhysicalClosedLimitTilt),1, Z_Nop },
- { Zuint16, Cx0102, 0x0003, Z_(CurrentPositionLift), 1, Z_Nop },
- { Zuint16, Cx0102, 0x0004, Z_(CurrentPositionTilt), 1, Z_Nop },
- { Zuint16, Cx0102, 0x0005, Z_(NumberofActuationsLift),1, Z_Nop },
- { Zuint16, Cx0102, 0x0006, Z_(NumberofActuationsTilt),1, Z_Nop },
- { Zmap8, Cx0102, 0x0007, Z_(ConfigStatus), 1, Z_Nop },
- { Zuint8, Cx0102, 0x0008, Z_(CurrentPositionLiftPercentage),1, Z_Nop },
- { Zuint8, Cx0102, 0x0009, Z_(CurrentPositionTiltPercentage),1, Z_Nop },
- { Zuint16, Cx0102, 0x0010, Z_(InstalledOpenLimitLift),1, Z_Nop },
- { Zuint16, Cx0102, 0x0011, Z_(InstalledClosedLimitLift),1, Z_Nop },
- { Zuint16, Cx0102, 0x0012, Z_(InstalledOpenLimitTilt),1, Z_Nop },
- { Zuint16, Cx0102, 0x0013, Z_(InstalledClosedLimitTilt),1, Z_Nop },
- { Zuint16, Cx0102, 0x0014, Z_(VelocityLift), 1, Z_Nop },
- { Zuint16, Cx0102, 0x0015, Z_(AccelerationTimeLift),1, Z_Nop },
- { Zuint16, Cx0102, 0x0016, Z_(DecelerationTimeLift), 1, Z_Nop },
- { Zmap8, Cx0102, 0x0017, Z_(Mode), 1, Z_Nop },
- { Zoctstr, Cx0102, 0x0018, Z_(IntermediateSetpointsLift),1, Z_Nop },
- { Zoctstr, Cx0102, 0x0019, Z_(IntermediateSetpointsTilt),1, Z_Nop },
+ { Zenum8, Cx0102, 0x0000, Z_(WindowCoveringType), 1 },
+ { Zuint16, Cx0102, 0x0001, Z_(PhysicalClosedLimitLift),1 },
+ { Zuint16, Cx0102, 0x0002, Z_(PhysicalClosedLimitTilt),1 },
+ { Zuint16, Cx0102, 0x0003, Z_(CurrentPositionLift), 1 },
+ { Zuint16, Cx0102, 0x0004, Z_(CurrentPositionTilt), 1 },
+ { Zuint16, Cx0102, 0x0005, Z_(NumberofActuationsLift),1 },
+ { Zuint16, Cx0102, 0x0006, Z_(NumberofActuationsTilt),1 },
+ { Zmap8, Cx0102, 0x0007, Z_(ConfigStatus), 1 },
+ { Zuint8, Cx0102, 0x0008, Z_(CurrentPositionLiftPercentage),1 },
+ { Zuint8, Cx0102, 0x0009, Z_(CurrentPositionTiltPercentage),1 },
+ { Zuint16, Cx0102, 0x0010, Z_(InstalledOpenLimitLift),1 },
+ { Zuint16, Cx0102, 0x0011, Z_(InstalledClosedLimitLift),1 },
+ { Zuint16, Cx0102, 0x0012, Z_(InstalledOpenLimitTilt),1 },
+ { Zuint16, Cx0102, 0x0013, Z_(InstalledClosedLimitTilt),1 },
+ { Zuint16, Cx0102, 0x0014, Z_(VelocityLift), 1 },
+ { Zuint16, Cx0102, 0x0015, Z_(AccelerationTimeLift),1 },
+ { Zuint16, Cx0102, 0x0016, Z_(DecelerationTimeLift), 1 },
+ { Zmap8, Cx0102, 0x0017, Z_(Mode), 1 },
+ { Zoctstr, Cx0102, 0x0018, Z_(IntermediateSetpointsLift),1 },
+ { Zoctstr, Cx0102, 0x0019, Z_(IntermediateSetpointsTilt),1 },
// Color Control cluster
- { Zuint8, Cx0300, 0x0000, Z_(Hue), 1, Z_Nop },
- { Zuint8, Cx0300, 0x0001, Z_(Sat), 1, Z_Nop },
- { Zuint16, Cx0300, 0x0002, Z_(RemainingTime), 1, Z_Nop },
- { Zuint16, Cx0300, 0x0003, Z_(X), 1, Z_Nop },
- { Zuint16, Cx0300, 0x0004, Z_(Y), 1, Z_Nop },
- { Zenum8, Cx0300, 0x0005, Z_(DriftCompensation), 1, Z_Nop },
- { Zstring, Cx0300, 0x0006, Z_(CompensationText), 1, Z_Nop },
- { Zuint16, Cx0300, 0x0007, Z_(CT), 1, Z_Nop },
- { Zenum8, Cx0300, 0x0008, Z_(ColorMode), 1, Z_Nop },
- { Zuint8, Cx0300, 0x0010, Z_(NumberOfPrimaries), 1, Z_Nop },
- { Zuint16, Cx0300, 0x0011, Z_(Primary1X), 1, Z_Nop },
- { Zuint16, Cx0300, 0x0012, Z_(Primary1Y), 1, Z_Nop },
- { Zuint8, Cx0300, 0x0013, Z_(Primary1Intensity), 1, Z_Nop },
- { Zuint16, Cx0300, 0x0015, Z_(Primary2X), 1, Z_Nop },
- { Zuint16, Cx0300, 0x0016, Z_(Primary2Y), 1, Z_Nop },
- { Zuint8, Cx0300, 0x0017, Z_(Primary2Intensity), 1, Z_Nop },
- { Zuint16, Cx0300, 0x0019, Z_(Primary3X), 1, Z_Nop },
- { Zuint16, Cx0300, 0x001A, Z_(Primary3Y), 1, Z_Nop },
- { Zuint8, Cx0300, 0x001B, Z_(Primary3Intensity), 1, Z_Nop },
- { Zuint16, Cx0300, 0x0030, Z_(WhitePointX), 1, Z_Nop },
- { Zuint16, Cx0300, 0x0031, Z_(WhitePointY), 1, Z_Nop },
- { Zuint16, Cx0300, 0x0032, Z_(ColorPointRX), 1, Z_Nop },
- { Zuint16, Cx0300, 0x0033, Z_(ColorPointRY), 1, Z_Nop },
- { Zuint8, Cx0300, 0x0034, Z_(ColorPointRIntensity), 1, Z_Nop },
- { Zuint16, Cx0300, 0x0036, Z_(ColorPointGX), 1, Z_Nop },
- { Zuint16, Cx0300, 0x0037, Z_(ColorPointGY), 1, Z_Nop },
- { Zuint8, Cx0300, 0x0038, Z_(ColorPointGIntensity), 1, Z_Nop },
- { Zuint16, Cx0300, 0x003A, Z_(ColorPointBX), 1, Z_Nop },
- { Zuint16, Cx0300, 0x003B, Z_(ColorPointBY), 1, Z_Nop },
- { Zuint8, Cx0300, 0x003C, Z_(ColorPointBIntensity), 1, Z_Nop },
+ { Zuint8, Cx0300, 0x0000, Z_(Hue), 1 },
+ { Zuint8, Cx0300, 0x0001, Z_(Sat), 1 },
+ { Zuint16, Cx0300, 0x0002, Z_(RemainingTime), 1 },
+ { Zuint16, Cx0300, 0x0003, Z_(X), 1 },
+ { Zuint16, Cx0300, 0x0004, Z_(Y), 1 },
+ { Zenum8, Cx0300, 0x0005, Z_(DriftCompensation), 1 },
+ { Zstring, Cx0300, 0x0006, Z_(CompensationText), 1 },
+ { Zuint16, Cx0300, 0x0007, Z_(CT), 1 },
+ { Zenum8, Cx0300, 0x0008, Z_(ColorMode), 1 },
+ { Zuint8, Cx0300, 0x0010, Z_(NumberOfPrimaries), 1 },
+ { Zuint16, Cx0300, 0x0011, Z_(Primary1X), 1 },
+ { Zuint16, Cx0300, 0x0012, Z_(Primary1Y), 1 },
+ { Zuint8, Cx0300, 0x0013, Z_(Primary1Intensity), 1 },
+ { Zuint16, Cx0300, 0x0015, Z_(Primary2X), 1 },
+ { Zuint16, Cx0300, 0x0016, Z_(Primary2Y), 1 },
+ { Zuint8, Cx0300, 0x0017, Z_(Primary2Intensity), 1 },
+ { Zuint16, Cx0300, 0x0019, Z_(Primary3X), 1 },
+ { Zuint16, Cx0300, 0x001A, Z_(Primary3Y), 1 },
+ { Zuint8, Cx0300, 0x001B, Z_(Primary3Intensity), 1 },
+ { Zuint16, Cx0300, 0x0030, Z_(WhitePointX), 1 },
+ { Zuint16, Cx0300, 0x0031, Z_(WhitePointY), 1 },
+ { Zuint16, Cx0300, 0x0032, Z_(ColorPointRX), 1 },
+ { Zuint16, Cx0300, 0x0033, Z_(ColorPointRY), 1 },
+ { Zuint8, Cx0300, 0x0034, Z_(ColorPointRIntensity), 1 },
+ { Zuint16, Cx0300, 0x0036, Z_(ColorPointGX), 1 },
+ { Zuint16, Cx0300, 0x0037, Z_(ColorPointGY), 1 },
+ { Zuint8, Cx0300, 0x0038, Z_(ColorPointGIntensity), 1 },
+ { Zuint16, Cx0300, 0x003A, Z_(ColorPointBX), 1 },
+ { Zuint16, Cx0300, 0x003B, Z_(ColorPointBY), 1 },
+ { Zuint8, Cx0300, 0x003C, Z_(ColorPointBIntensity), 1 },
// Illuminance Measurement cluster
- { Zuint16, Cx0400, 0x0000, Z_(Illuminance), 1, Z_Nop }, // Illuminance (in Lux)
- { Zuint16, Cx0400, 0x0001, Z_(IlluminanceMinMeasuredValue), 1, Z_Nop }, //
- { Zuint16, Cx0400, 0x0002, Z_(IlluminanceMaxMeasuredValue), 1, Z_Nop }, //
- { Zuint16, Cx0400, 0x0003, Z_(IlluminanceTolerance), 1, Z_Nop }, //
- { Zenum8, Cx0400, 0x0004, Z_(IlluminanceLightSensorType), 1, Z_Nop }, //
- { Zunk, Cx0400, 0xFFFF, Z_(), 0, Z_Nop }, // Remove all other values
+ { Zuint16, Cx0400, 0x0000, Z_(Illuminance), 1 }, // Illuminance (in Lux)
+ { Zuint16, Cx0400, 0x0001, Z_(IlluminanceMinMeasuredValue), 1 }, //
+ { Zuint16, Cx0400, 0x0002, Z_(IlluminanceMaxMeasuredValue), 1 }, //
+ { Zuint16, Cx0400, 0x0003, Z_(IlluminanceTolerance), 1 }, //
+ { Zenum8, Cx0400, 0x0004, Z_(IlluminanceLightSensorType), 1 }, //
+ { Zunk, Cx0400, 0xFFFF, Z_(), 0 }, // Remove all other values
// Illuminance Level Sensing cluster
- { Zenum8, Cx0401, 0x0000, Z_(IlluminanceLevelStatus), 1, Z_Nop }, // Illuminance (in Lux)
- { Zenum8, Cx0401, 0x0001, Z_(IlluminanceLightSensorType), 1, Z_Nop }, // LightSensorType
- { Zuint16, Cx0401, 0x0010, Z_(IlluminanceTargetLevel), 1, Z_Nop }, //
- { Zunk, Cx0401, 0xFFFF, Z_(), 0, Z_Nop }, // Remove all other values
+ { Zenum8, Cx0401, 0x0000, Z_(IlluminanceLevelStatus), 1 }, // Illuminance (in Lux)
+ { Zenum8, Cx0401, 0x0001, Z_(IlluminanceLightSensorType), 1 }, // LightSensorType
+ { Zuint16, Cx0401, 0x0010, Z_(IlluminanceTargetLevel), 1 }, //
+ { Zunk, Cx0401, 0xFFFF, Z_(), 0 }, // Remove all other values
// Temperature Measurement cluster
- { Zint16, Cx0402, 0x0000, Z_(Temperature), -100, Z_Nop }, // divide by 100
- { Zint16, Cx0402, 0x0001, Z_(TemperatureMinMeasuredValue), -100, Z_Nop }, //
- { Zint16, Cx0402, 0x0002, Z_(TemperatureMaxMeasuredValue), -100, Z_Nop }, //
- { Zuint16, Cx0402, 0x0003, Z_(TemperatureTolerance), -100, Z_Nop }, //
- { Zunk, Cx0402, 0xFFFF, Z_(), 0, Z_Nop }, // Remove all other values
+ { Zint16, Cx0402, 0x0000, Z_(Temperature), -100 }, // divide by 100
+ { Zint16, Cx0402, 0x0001, Z_(TemperatureMinMeasuredValue), -100 }, //
+ { Zint16, Cx0402, 0x0002, Z_(TemperatureMaxMeasuredValue), -100 }, //
+ { Zuint16, Cx0402, 0x0003, Z_(TemperatureTolerance), -100 }, //
+ { Zunk, Cx0402, 0xFFFF, Z_(), 0 }, // Remove all other values
// Pressure Measurement cluster
- { Zunk, Cx0403, 0x0000, Z_(PressureUnit), 0, Z_AddPressureUnit }, // Pressure Unit
- { Zint16, Cx0403, 0x0000, Z_(Pressure), 1, Z_Nop }, // Pressure
- { Zint16, Cx0403, 0x0001, Z_(PressureMinMeasuredValue), 1, Z_Nop }, //
- { Zint16, Cx0403, 0x0002, Z_(PressureMaxMeasuredValue), 1, Z_Nop }, //
- { Zuint16, Cx0403, 0x0003, Z_(PressureTolerance), 1, Z_Nop }, //
- { Zint16, Cx0403, 0x0010, Z_(PressureScaledValue), 1, Z_Nop }, //
- { Zint16, Cx0403, 0x0011, Z_(PressureMinScaledValue), 1, Z_Nop }, //
- { Zint16, Cx0403, 0x0012, Z_(PressureMaxScaledValue), 1, Z_Nop }, //
- { Zuint16, Cx0403, 0x0013, Z_(PressureScaledTolerance), 1, Z_Nop }, //
- { Zint8, Cx0403, 0x0014, Z_(PressureScale), 1, Z_Nop }, //
- { Zunk, Cx0403, 0xFFFF, Z_(), 0, Z_Nop }, // Remove all other Pressure values
+ { Zint16, Cx0403, 0x0000, Z_(Pressure), 1 }, // Pressure
+ { Zint16, Cx0403, 0x0001, Z_(PressureMinMeasuredValue), 1 }, //
+ { Zint16, Cx0403, 0x0002, Z_(PressureMaxMeasuredValue), 1 }, //
+ { Zuint16, Cx0403, 0x0003, Z_(PressureTolerance), 1 }, //
+ { Zint16, Cx0403, 0x0010, Z_(PressureScaledValue), 1 }, //
+ { Zint16, Cx0403, 0x0011, Z_(PressureMinScaledValue), 1 }, //
+ { Zint16, Cx0403, 0x0012, Z_(PressureMaxScaledValue), 1 }, //
+ { Zuint16, Cx0403, 0x0013, Z_(PressureScaledTolerance), 1 }, //
+ { Zint8, Cx0403, 0x0014, Z_(PressureScale), 1 }, //
+ { Zunk, Cx0403, 0xFFFF, Z_(), 0 }, // Remove all other Pressure values
// Flow Measurement cluster
- { Zuint16, Cx0404, 0x0000, Z_(FlowRate), -10, Z_Nop }, // Flow (in m3/h)
- { Zuint16, Cx0404, 0x0001, Z_(FlowMinMeasuredValue), 1, Z_Nop }, //
- { Zuint16, Cx0404, 0x0002, Z_(FlowMaxMeasuredValue), 1, Z_Nop }, //
- { Zuint16, Cx0404, 0x0003, Z_(FlowTolerance), 1, Z_Nop }, //
- { Zunk, Cx0404, 0xFFFF, Z_(), 0, Z_Nop }, // Remove all other values
+ { Zuint16, Cx0404, 0x0000, Z_(FlowRate), -10 }, // Flow (in m3/h)
+ { Zuint16, Cx0404, 0x0001, Z_(FlowMinMeasuredValue), 1 }, //
+ { Zuint16, Cx0404, 0x0002, Z_(FlowMaxMeasuredValue), 1 }, //
+ { Zuint16, Cx0404, 0x0003, Z_(FlowTolerance), 1 }, //
+ { Zunk, Cx0404, 0xFFFF, Z_(), 0 }, // Remove all other values
// Relative Humidity Measurement cluster
- { Zuint16, Cx0405, 0x0000, Z_(Humidity), -100, Z_Nop }, // Humidity
- { Zuint16, Cx0405, 0x0001, Z_(HumidityMinMeasuredValue), 1, Z_Nop }, //
- { Zuint16, Cx0405, 0x0002, Z_(HumidityMaxMeasuredValue), 1, Z_Nop }, //
- { Zuint16, Cx0405, 0x0003, Z_(HumidityTolerance), 1, Z_Nop }, //
- { Zunk, Cx0405, 0xFFFF, Z_(), 0, Z_Nop }, // Remove all other values
+ { Zuint16, Cx0405, 0x0000, Z_(Humidity), -100 }, // Humidity
+ { Zuint16, Cx0405, 0x0001, Z_(HumidityMinMeasuredValue), 1 }, //
+ { Zuint16, Cx0405, 0x0002, Z_(HumidityMaxMeasuredValue), 1 }, //
+ { Zuint16, Cx0405, 0x0003, Z_(HumidityTolerance), 1 }, //
+ { Zunk, Cx0405, 0xFFFF, Z_(), 0 }, // Remove all other values
// Occupancy Sensing cluster
- { Zmap8, Cx0406, 0x0000, Z_(Occupancy), 1, Z_Nop }, // Occupancy (map8)
- { Zenum8, Cx0406, 0x0001, Z_(OccupancySensorType), 1, Z_Nop }, // OccupancySensorType
- { Zunk, Cx0406, 0xFFFF, Z_(), 0, Z_Nop }, // Remove all other values
+ { Zmap8, Cx0406, 0x0000, Z_(Occupancy), 1 }, // Occupancy (map8)
+ { Zenum8, Cx0406, 0x0001, Z_(OccupancySensorType), 1 }, // OccupancySensorType
+ { Zunk, Cx0406, 0xFFFF, Z_(), 0 }, // Remove all other values
// IAS Cluster (Intruder Alarm System)
- { Zenum8, Cx0500, 0x0000, Z_(ZoneState), 1, Z_Nop }, // Occupancy (map8)
- { Zenum16, Cx0500, 0x0001, Z_(ZoneType), 1, Z_Nop }, // Occupancy (map8)
- { Zmap16, Cx0500, 0x0002, Z_(ZoneStatus), 1, Z_Nop }, // Occupancy (map8)
+ { Zenum8, Cx0500, 0x0000, Z_(ZoneState), 1 }, // Occupancy (map8)
+ { Zenum16, Cx0500, 0x0001, Z_(ZoneType), 1 }, // Occupancy (map8)
+ { Zmap16, Cx0500, 0x0002, Z_(ZoneStatus), 1 }, // Occupancy (map8)
// Metering (Smart Energy) cluster
- { Zuint48, Cx0702, 0x0000, Z_(CurrentSummDelivered), 1, Z_Nop },
+ { Zuint48, Cx0702, 0x0000, Z_(CurrentSummDelivered), 1 },
// Meter Identification cluster
- { Zstring, Cx0B01, 0x0000, Z_(CompanyName), 1, Z_Nop },
- { Zuint16, Cx0B01, 0x0001, Z_(MeterTypeID), 1, Z_Nop },
- { Zuint16, Cx0B01, 0x0004, Z_(DataQualityID), 1, Z_Nop },
- { Zstring, Cx0B01, 0x0005, Z_(CustomerName), 1, Z_Nop },
- { Zoctstr, Cx0B01, 0x0006, Z_(Model), 1, Z_Nop },
- { Zoctstr, Cx0B01, 0x0007, Z_(PartNumber), 1, Z_Nop },
- { Zoctstr, Cx0B01, 0x0008, Z_(ProductRevision), 1, Z_Nop },
- { Zoctstr, Cx0B01, 0x000A, Z_(SoftwareRevision), 1, Z_Nop },
- { Zstring, Cx0B01, 0x000B, Z_(UtilityName), 1, Z_Nop },
- { Zstring, Cx0B01, 0x000C, Z_(POD), 1, Z_Nop },
- { Zint24, Cx0B01, 0x000D, Z_(AvailablePower), 1, Z_Nop },
- { Zint24, Cx0B01, 0x000E, Z_(PowerThreshold), 1, Z_Nop },
+ { Zstring, Cx0B01, 0x0000, Z_(CompanyName), 1 },
+ { Zuint16, Cx0B01, 0x0001, Z_(MeterTypeID), 1 },
+ { Zuint16, Cx0B01, 0x0004, Z_(DataQualityID), 1 },
+ { Zstring, Cx0B01, 0x0005, Z_(CustomerName), 1 },
+ { Zoctstr, Cx0B01, 0x0006, Z_(Model), 1 },
+ { Zoctstr, Cx0B01, 0x0007, Z_(PartNumber), 1 },
+ { Zoctstr, Cx0B01, 0x0008, Z_(ProductRevision), 1 },
+ { Zoctstr, Cx0B01, 0x000A, Z_(SoftwareRevision), 1 },
+ { Zstring, Cx0B01, 0x000B, Z_(UtilityName), 1 },
+ { Zstring, Cx0B01, 0x000C, Z_(POD), 1 },
+ { Zint24, Cx0B01, 0x000D, Z_(AvailablePower), 1 },
+ { Zint24, Cx0B01, 0x000E, Z_(PowerThreshold), 1 },
// Electrical Measurement cluster
- { Zuint16, Cx0B04, 0x0505, Z_(RMSVoltage), 1, Z_Nop },
- { Zuint16, Cx0B04, 0x0508, Z_(RMSCurrent), 1, Z_Nop },
- { Zint16, Cx0B04, 0x050B, Z_(ActivePower), 1, Z_Nop },
+ { Zuint16, Cx0B04, 0x0505, Z_(RMSVoltage), 1 },
+ { Zuint16, Cx0B04, 0x0508, Z_(RMSCurrent), 1 },
+ { Zint16, Cx0B04, 0x050B, Z_(ActivePower), 1 },
// Diagnostics cluster
- { Zuint16, Cx0B05, 0x0000, Z_(NumberOfResets), 1, Z_Nop },
- { Zuint16, Cx0B05, 0x0001, Z_(PersistentMemoryWrites),1, Z_Nop },
- { Zuint8, Cx0B05, 0x011C, Z_(LastMessageLQI), 1, Z_Nop },
- { Zuint8, Cx0B05, 0x011D, Z_(LastMessageRSSI), 1, Z_Nop },
+ { Zuint16, Cx0B05, 0x0000, Z_(NumberOfResets), 1 },
+ { Zuint16, Cx0B05, 0x0001, Z_(PersistentMemoryWrites),1 },
+ { Zuint8, Cx0B05, 0x011C, Z_(LastMessageLQI), 1 },
+ { Zuint8, Cx0B05, 0x011D, Z_(LastMessageRSSI), 1 },
};
@@ -544,8 +530,7 @@ typedef union ZCLHeaderFrameControl_t {
// If not found:
// - returns nullptr
const __FlashStringHelper* zigbeeFindAttributeByName(const char *command,
- uint16_t *cluster, uint16_t *attribute, int8_t *multiplier,
- uint8_t *cb) {
+ uint16_t *cluster, uint16_t *attribute, int8_t *multiplier) {
for (uint32_t i = 0; i < ARRAY_SIZE(Z_PostProcess); i++) {
const Z_AttributeConverter *converter = &Z_PostProcess[i];
if (0 == pgm_read_word(&converter->name_offset)) { continue; } // avoid strcasecmp_P() from crashing
@@ -553,7 +538,6 @@ const __FlashStringHelper* zigbeeFindAttributeByName(const char *command,
if (cluster) { *cluster = CxToCluster(pgm_read_byte(&converter->cluster_short)); }
if (attribute) { *attribute = pgm_read_word(&converter->attribute); }
if (multiplier) { *multiplier = pgm_read_byte(&converter->multiplier); }
- if (cb) { *cb = pgm_read_byte(&converter->cb); }
return (const __FlashStringHelper*) (Z_strings + pgm_read_word(&converter->name_offset));
}
}
@@ -627,16 +611,24 @@ public:
return _frame_control.b.frame_type & 1;
}
- static void generateAttributeName(const JsonObject& json, uint16_t cluster, uint16_t attr, char *key, size_t key_len);
- void parseReportAttributes(JsonObject& json, uint8_t offset = 0);
- void parseReadAttributes(JsonObject& json, uint8_t offset = 0);
- void parseReadAttributesResponse(JsonObject& json, uint8_t offset = 0);
- void parseReadConfigAttributes(JsonObject& json, uint8_t offset = 0);
- void parseConfigAttributes(JsonObject& json, uint8_t offset = 0);
+ void parseReportAttributes(Z_attribute_list& attr_list);
+ void generateSyntheticAttributes(Z_attribute_list& attr_list);
+ void generateCallBacks(Z_attribute_list& attr_list);
+ void parseReadAttributes(Z_attribute_list& attr_list);
+ void parseReadAttributesResponse(Z_attribute_list& attr_list);
+ void parseReadConfigAttributes(Z_attribute_list& attr_list);
+ void parseConfigAttributes(Z_attribute_list& attr_list);
void parseResponse(void);
- void parseClusterSpecificCommand(JsonObject& json, uint8_t offset = 0);
- void postProcessAttributes(uint16_t shortaddr, JsonObject& json);
- void updateInternalAttributes(uint16_t shortaddr, JsonObject& json);
+ void parseResponseOld(void);
+ void parseClusterSpecificCommand(Z_attribute_list& attr_list);
+ void postProcessAttributes(uint16_t shortaddr, Z_attribute_list& attr_list);
+
+ // synthetic attributes converters
+ void syntheticAqaraSensor(Z_attribute_list &attr_list, class Z_attribute &attr);
+ void syntheticAqaraSensor2(Z_attribute_list &attr_list, class Z_attribute &attr);
+ void syntheticAqaraCubeOrButton(Z_attribute_list &attr_list, class Z_attribute &attr);
+ void syntheticAqaraVibration(Z_attribute_list &attr_list, class Z_attribute &attr);
+
inline void setGroupId(uint16_t groupid) {
_groupaddr = groupid;
@@ -782,14 +774,13 @@ int32_t encodeSingleAttribute(class SBuffer &buf, double val_d, const char *val_
// parse a single attribute
//
// Input:
-// json: json Object where to add the attribute
-// attrid_str: the key for the attribute
+// attr: attribute object to store to
// buf: the buffer to read from
// offset: location in the buffer to read from
// attrtype: type of attribute (byte) or -1 to read from the stream as first byte
// Output:
// return: the length in bytes of the attribute
-uint32_t parseSingleAttribute(JsonObject& json, char *attrid_str, class SBuffer &buf,
+uint32_t parseSingleAttribute(Z_attribute & attr, const SBuffer &buf,
uint32_t offset, int32_t attrtype = -1) {
uint32_t i = offset;
@@ -798,7 +789,7 @@ uint32_t parseSingleAttribute(JsonObject& json, char *attrid_str, class SBuffer
}
// fallback - enter a null value
- json[attrid_str] = (char*) nullptr;
+ attr.setNone(); // set to null by default
uint32_t len = Z_getDatatypeLen(attrtype); // pre-compute lenght, overloaded for variable length attributes
@@ -814,7 +805,7 @@ uint32_t parseSingleAttribute(JsonObject& json, char *attrid_str, class SBuffer
uint8_t uint8_val = buf.get8(i);
// i += 1;
if (0xFF != uint8_val) {
- json[attrid_str] = uint8_val;
+ attr.setUInt(uint8_val);
}
}
break;
@@ -824,7 +815,7 @@ uint32_t parseSingleAttribute(JsonObject& json, char *attrid_str, class SBuffer
uint16_t uint16_val = buf.get16(i);
// i += 2;
if (0xFFFF != uint16_val) {
- json[attrid_str] = uint16_val;
+ attr.setUInt(uint16_val);
}
}
break;
@@ -834,7 +825,7 @@ uint32_t parseSingleAttribute(JsonObject& json, char *attrid_str, class SBuffer
uint32_t uint32_val = buf.get32(i);
// i += 4;
if (0xFFFFFFFF != uint32_val) {
- json[attrid_str] = uint32_val;
+ attr.setUInt(uint32_val);
}
}
break;
@@ -856,7 +847,7 @@ uint32_t parseSingleAttribute(JsonObject& json, char *attrid_str, class SBuffer
for (uint32_t j=0; j= i + 3) {
uint16_t attrid = _payload.get16(i);
i += 2;
- char key[16];
- generateAttributeName(json, _cluster_id, attrid, key, sizeof(key));
-
// exception for Xiaomi lumi.weather - specific field to be treated as octet and not char
if ((0x0000 == _cluster_id) && (0xFF01 == attrid)) {
if (0x42 == _payload.get8(i)) {
_payload.set8(i, 0x41); // change type from 0x42 to 0x41
}
}
- i += parseSingleAttribute(json, key, _payload, i);
+
+ // TODO look for suffix
+ Z_attribute & attr = attr_list.addAttribute(_cluster_id, attrid);
+
+ i += parseSingleAttribute(attr, _payload, i);
}
// Issue Philips outdoor motion sensor SML002, see https://github.com/Koenkk/zigbee2mqtt/issues/897
@@ -1061,19 +1032,67 @@ void ZCLFrame::parseReportAttributes(JsonObject& json, uint8_t offset) {
}
}
+void ZCLFrame::generateSyntheticAttributes(Z_attribute_list& attr_list) {
+ // scan through attributes and apply specific converters
+ for (auto &attr : attr_list) {
+ if (attr.key_is_str) { continue; } // pass if key is a name
+ uint32_t ccccaaaa = (attr.key.id.cluster << 16) | attr.key.id.attr_id;
+
+ switch (ccccaaaa) { // 0xccccaaaa . c=cluster, a=attribute
+ case 0x0000FF01:
+ syntheticAqaraSensor(attr_list, attr);
+ break;
+ case 0x0000FF02:
+ syntheticAqaraSensor2(attr_list, attr);
+ break;
+ case 0x00120055:
+ syntheticAqaraCubeOrButton(attr_list, attr);
+ break;
+ case 0x01010055:
+ case 0x01010508:
+ syntheticAqaraVibration(attr_list, attr);
+ break;
+ }
+ }
+}
+
+// Set deferred callbacks for Occupancy
+// TODO make delay a parameter
+void ZCLFrame::generateCallBacks(Z_attribute_list& attr_list) {
+ static const uint32_t OCCUPANCY_TIMEOUT = 90 * 1000; // 90 s
+ // scan through attributes and apply specific converters
+ for (auto &attr : attr_list) {
+ if (attr.key_is_str) { continue; } // pass if key is a name
+ uint32_t ccccaaaa = (attr.key.id.cluster << 16) | attr.key.id.attr_id;
+
+ switch (ccccaaaa) { // 0xccccaaaa . c=cluster, a=attribute
+ case 0x04060000: // Occupancy
+ uint32_t occupancy = attr.getUInt();
+ if (occupancy) {
+ zigbee_devices.setTimer(_srcaddr, 0 /* groupaddr */, OCCUPANCY_TIMEOUT, _cluster_id, _srcendpoint, Z_CAT_VIRTUAL_OCCUPANCY, 0, &Z_OccupancyCallback);
+ } else {
+ zigbee_devices.resetTimersForDevice(_srcaddr, 0 /* groupaddr */, Z_CAT_VIRTUAL_OCCUPANCY);
+ }
+ break;
+ }
+ }
+}
+
// ZCL_READ_ATTRIBUTES
-// TODO
-void ZCLFrame::parseReadAttributes(JsonObject& json, uint8_t offset) {
- uint32_t i = offset;
+void ZCLFrame::parseReadAttributes(Z_attribute_list& attr_list) {
+ uint32_t i = 0;
uint32_t len = _payload.len();
- json[F(D_CMND_ZIGBEE_CLUSTER)] = _cluster_id;
+ uint16_t read_attr_ids[len/2];
- JsonArray &attr_list = json.createNestedArray(F("Read"));
- JsonObject &attr_names = json.createNestedObject(F("ReadNames"));
+ attr_list.addAttribute(F(D_CMND_ZIGBEE_CLUSTER)).setUInt(_cluster_id);
+
+ Z_json_array attr_numbers;
+ Z_attribute_list attr_names;
while (len >= 2 + i) {
uint16_t attrid = _payload.get16(i);
- attr_list.add(attrid);
+ attr_numbers.add(attrid);
+ read_attr_ids[i/2] = attrid;
// find the attribute name
for (uint32_t i = 0; i < ARRAY_SIZE(Z_PostProcess); i++) {
@@ -1082,43 +1101,49 @@ void ZCLFrame::parseReadAttributes(JsonObject& json, uint8_t offset) {
uint16_t conv_attribute = pgm_read_word(&converter->attribute);
if ((conv_cluster == _cluster_id) && (conv_attribute == attrid)) {
- attr_names[(const __FlashStringHelper*) (Z_strings + pgm_read_word(&converter->name_offset))] = true;
+ attr_names.addAttribute(Z_strings + pgm_read_word(&converter->name_offset), true).setBool(true);
break;
}
}
i += 2;
}
+ attr_list.addAttribute(F("Read")).setStrRaw(attr_numbers.toString().c_str());
+ attr_list.addAttribute(F("ReadNames")).setStrRaw(attr_names.toString(true).c_str());
+
+ // call auto-responder
+ Z_AutoResponder(_srcaddr, _cluster_id, _srcendpoint, read_attr_ids, len/2);
}
// ZCL_CONFIGURE_REPORTING_RESPONSE
-void ZCLFrame::parseConfigAttributes(JsonObject& json, uint8_t offset) {
- uint32_t i = offset;
+void ZCLFrame::parseConfigAttributes(Z_attribute_list& attr_list) {
+ uint32_t i = 0;
uint32_t len = _payload.len();
-
- JsonObject &config_rsp = json.createNestedObject(F("ConfigResponse"));
uint8_t status = _payload.get8(i);
- config_rsp[F("Status")] = status;
- config_rsp[F("StatusMsg")] = getZigbeeStatusMessage(status);
+
+ Z_attribute_list attr_config_response;
+ attr_config_response.addAttribute(F("Status")).setUInt(status);
+ attr_config_response.addAttribute(F("StatusMsg")).setStr(getZigbeeStatusMessage(status).c_str());
+
+ Z_attribute &attr_1 = attr_list.addAttribute(F("ConfigResponse"));
+ attr_1.setStrRaw(attr_config_response.toString(true).c_str());
}
// ZCL_READ_REPORTING_CONFIGURATION_RESPONSE
-void ZCLFrame::parseReadConfigAttributes(JsonObject& json, uint8_t offset) {
- uint32_t i = offset;
+void ZCLFrame::parseReadConfigAttributes(Z_attribute_list& attr_list) {
+ uint32_t i = 0;
uint32_t len = _payload.len();
- // json[F(D_CMND_ZIGBEE_CLUSTER)] = _cluster_id; // TODO is it necessary?
+ Z_attribute &attr_root = attr_list.addAttribute(F("ReadConfig"));
+ Z_attribute_list attr_1;
- JsonObject &attr_names = json.createNestedObject(F("ReadConfig"));
while (len >= i + 4) {
uint8_t status = _payload.get8(i);
uint8_t direction = _payload.get8(i+1);
uint16_t attrid = _payload.get16(i+2);
- char attr_hex[12];
- snprintf_P(attr_hex, sizeof(attr_hex), "%04X/%04X", _cluster_id, attrid);
- JsonObject &attr_details = attr_names.createNestedObject(attr_hex);
+ Z_attribute_list attr_2;
if (direction) {
- attr_details[F("DirectionReceived")] = true;
+ attr_2.addAttribute(F("DirectionReceived")).setBool(true);
}
// find the attribute name
@@ -1128,21 +1153,22 @@ void ZCLFrame::parseReadConfigAttributes(JsonObject& json, uint8_t offset) {
uint16_t conv_attribute = pgm_read_word(&converter->attribute);
if ((conv_cluster == _cluster_id) && (conv_attribute == attrid)) {
- attr_details[(const __FlashStringHelper*) (Z_strings + pgm_read_word(&converter->name_offset))] = true;
+ const char * attr_name = Z_strings + pgm_read_word(&converter->name_offset);
+ attr_2.addAttribute(attr_name, true).setBool(true);
break;
}
}
i += 4;
if (0 != status) {
- attr_details[F("Status")] = status;
- attr_details[F("StatusMsg")] = getZigbeeStatusMessage(status);
+ attr_2.addAttribute(F("Status")).setUInt(status);
+ attr_2.addAttribute(F("StatusMsg")).setStr(getZigbeeStatusMessage(status).c_str());
} else {
// no error, decode data
if (direction) {
// only Timeout period is present
uint16_t attr_timeout = _payload.get16(i);
i += 2;
- attr_details[F("TimeoutPeriod")] = (0xFFFF == attr_timeout) ? -1 : attr_timeout;
+ attr_2.addAttribute(F("TimeoutPeriod")).setUInt((0xFFFF == attr_timeout) ? -1 : attr_timeout);
} else {
// direction == 0, we have a data type
uint8_t attr_type = _payload.get8(i);
@@ -1150,22 +1176,23 @@ void ZCLFrame::parseReadConfigAttributes(JsonObject& json, uint8_t offset) {
uint16_t attr_min_interval = _payload.get16(i+1);
uint16_t attr_max_interval = _payload.get16(i+3);
i += 5;
- attr_details[F("MinInterval")] = (0xFFFF == attr_min_interval) ? -1 : attr_min_interval;
- attr_details[F("MaxInterval")] = (0xFFFF == attr_max_interval) ? -1 : attr_max_interval;
+ attr_2.addAttribute(F("MinInterval")).setUInt((0xFFFF == attr_min_interval) ? -1 : attr_min_interval);
+ attr_2.addAttribute(F("MaxInterval")).setUInt((0xFFFF == attr_max_interval) ? -1 : attr_max_interval);
if (!attr_discrete) {
// decode Reportable Change
- char attr_name[20];
- strcpy_P(attr_name, PSTR("ReportableChange"));
- i += parseSingleAttribute(attr_details, attr_name, _payload, i, attr_type);
+ Z_attribute &attr_change = attr_2.addAttribute(F("ReportableChange"));
+ i += parseSingleAttribute(attr_change, _payload, i, attr_type);
}
}
}
+ attr_1.addAttribute(_cluster_id, attrid).setStrRaw(attr_2.toString(true).c_str());
}
+ attr_root.setStrRaw(attr_1.toString(true).c_str());
}
// ZCL_READ_ATTRIBUTES_RESPONSE
-void ZCLFrame::parseReadAttributesResponse(JsonObject& json, uint8_t offset) {
- uint32_t i = offset;
+void ZCLFrame::parseReadAttributesResponse(Z_attribute_list& attr_list) {
+ uint32_t i = 0;
uint32_t len = _payload.len();
while (len >= i + 4) {
@@ -1174,10 +1201,8 @@ void ZCLFrame::parseReadAttributesResponse(JsonObject& json, uint8_t offset) {
uint8_t status = _payload.get8(i++);
if (0 == status) {
- char key[16];
- generateAttributeName(json, _cluster_id, attrid, key, sizeof(key));
-
- i += parseSingleAttribute(json, key, _payload, i);
+ Z_attribute & attr = attr_list.addAttribute(_cluster_id, attrid);
+ i += parseSingleAttribute(attr, _payload, i);
}
}
}
@@ -1188,185 +1213,224 @@ void ZCLFrame::parseResponse(void) {
uint8_t cmd = _payload.get8(0);
uint8_t status = _payload.get8(1);
- DynamicJsonBuffer jsonBuffer;
- JsonObject& json = jsonBuffer.createObject();
+ Z_attribute_list attr_list;
// "Device"
char s[12];
snprintf_P(s, sizeof(s), PSTR("0x%04X"), _srcaddr);
- json[F(D_JSON_ZIGBEE_DEVICE)] = s;
+ attr_list.addAttribute(F(D_JSON_ZIGBEE_DEVICE)).setStr(s);
// "Name"
const char * friendlyName = zigbee_devices.getFriendlyName(_srcaddr);
if (friendlyName) {
- json[F(D_JSON_ZIGBEE_NAME)] = (char*) friendlyName;
+ attr_list.addAttribute(F(D_JSON_ZIGBEE_NAME)).setStr(friendlyName);
}
// "Command"
snprintf_P(s, sizeof(s), PSTR("%04X!%02X"), _cluster_id, cmd);
- json[F(D_JSON_ZIGBEE_CMD)] = s;
+ attr_list.addAttribute(F(D_JSON_ZIGBEE_CMD)).setStr(s);
// "Status"
- json[F(D_JSON_ZIGBEE_STATUS)] = status;
+ attr_list.addAttribute(F(D_JSON_ZIGBEE_STATUS)).setUInt(status);
// "StatusMessage"
- json[F(D_JSON_ZIGBEE_STATUS_MSG)] = getZigbeeStatusMessage(status);
+ attr_list.addAttribute(F(D_JSON_ZIGBEE_STATUS_MSG)).setStr(getZigbeeStatusMessage(status).c_str());
// Add Endpoint
- json[F(D_CMND_ZIGBEE_ENDPOINT)] = _srcendpoint;
+ attr_list.addAttribute(F(D_CMND_ZIGBEE_ENDPOINT)).setUInt(_srcendpoint);
// Add Group if non-zero
- if (_groupaddr) {
- json[F(D_CMND_ZIGBEE_GROUP)] = _groupaddr;
+ if (_groupaddr) { // TODO what about group zero
+ attr_list.group_id = _groupaddr;
}
// Add linkquality
- json[F(D_CMND_ZIGBEE_LINKQUALITY)] = _linkquality;
+ attr_list.lqi = _linkquality;
- String msg("");
- msg.reserve(100);
- json.printTo(msg);
- Response_P(PSTR("{\"" D_JSON_ZIGBEE_RESPONSE "\":%s}"), msg.c_str());
+ Response_P(PSTR("{\"" D_JSON_ZIGBEE_RESPONSE "\":%s}"), attr_list.toString(true).c_str());
MqttPublishPrefixTopicRulesProcess_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCL_RECEIVED));
}
-
// Parse non-normalized attributes
-void ZCLFrame::parseClusterSpecificCommand(JsonObject& json, uint8_t offset) {
- convertClusterSpecific(json, _cluster_id, _cmd_id, _frame_control.b.direction, _srcaddr, _srcendpoint, _payload);
+void ZCLFrame::parseClusterSpecificCommand(Z_attribute_list& attr_list) {
+ convertClusterSpecific(attr_list, _cluster_id, _cmd_id, _frame_control.b.direction, _srcaddr, _srcendpoint, _payload);
#ifndef USE_ZIGBEE_NO_READ_ATTRIBUTES // read attributes unless disabled
sendHueUpdate(_srcaddr, _groupaddr, _cluster_id, _cmd_id, _frame_control.b.direction);
#endif
}
// ======================================================================
-// Record Manuf
-int32_t Z_ManufKeepFunc(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr) {
- zigbee_devices.setManufId(shortaddr, value.as());
- return 1;
-}
-// Record ModelId
-int32_t Z_ModelKeepFunc(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr) {
- zigbee_devices.setModelId(shortaddr, value.as());
- return 1;
-}
-// Record BatteryPercentage
-int32_t Z_BatteryPercentageKeepFunc(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr) {
- zigbee_devices.setBatteryPercent(shortaddr, json[new_name]);
- return 1;
+// New version of synthetic attribute generation
+void ZCLFrame::syntheticAqaraSensor(Z_attribute_list &attr_list, class Z_attribute &attr) {
+ const SBuffer * buf = attr.getRaw();
+ if (buf) {
+ const SBuffer & buf2 = *buf;
+ uint32_t i = 0;
+ uint32_t len = buf2.len();
+
+ const char * modelId_c = zigbee_devices.getModelId(_srcaddr); // null if unknown
+ String modelId((char*) modelId_c);
+
+ while (len >= 2 + i) {
+ uint8_t attrid = buf2.get8(i++);
+
+ Z_attribute attr; // temporary attribute
+ i += parseSingleAttribute(attr, buf2, i);
+ int32_t ival32 = attr.getInt();
+ float fval = attr.getFloat();
+ bool translated = false; // were we able to translate to a known format?
+ if (0x01 == attrid) {
+ float batteryvoltage = fval / 100;
+ attr_list.addAttribute(0x0001, 0x0020).setFloat(batteryvoltage);
+ uint8_t batterypercentage = toPercentageCR2032(fval);
+ attr_list.addAttribute(0x0001, 0x0021).setUInt(batterypercentage * 2);
+ } else if ((nullptr != modelId) && (0 == getManufCode())) {
+ translated = true;
+ if (modelId.startsWith(F("lumi.sensor_ht")) ||
+ modelId.startsWith(F("lumi.weather"))) { // Temp sensor
+ // Filter according to prefix of model name
+ // onla Aqara Temp/Humidity has manuf_code of zero. If non-zero we skip the parameters
+ if (0x64 == attrid) {
+ attr_list.addAttribute(0x0402, 0x0000).setInt(ival32); // Temperature
+ } else if (0x65 == attrid) {
+ attr_list.addAttribute(0x0405, 0x0000).setFloat(fval); // Humidity * 100
+ } else if (0x66 == attrid) {
+ attr_list.addAttribute(0x0403, 0x0000).setUInt((ival32 + 50) / 100); // Pressure
+ }
+ } else if (modelId.startsWith(F("lumi.sensor_smoke"))) { // gas leak
+ if (0x64 == attrid) {
+ attr_list.addAttribute(F("SmokeDensity")).copyVal(attr);
+ }
+ } else if (modelId.startsWith(F("lumi.sensor_natgas"))) { // gas leak
+ if (0x64 == attrid) {
+ attr_list.addAttribute(F("GasDensity")).copyVal(attr);
+ }
+ } else {
+ translated = false; // we didn't find a match
+ }
+ // } else if (0x115F == zcl->getManufCode()) { // Aqara Motion Sensor, still unknown field
+ }
+ if (!translated) {
+ if (attrid >= 100) { // payload is always above 0x64 or 100
+ char attr_name[12];
+ snprintf_P(attr_name, sizeof(attr_name), PSTR("Xiaomi_%02X"), attrid);
+ attr_list.addAttribute(attr_name).copyVal(attr);
+ }
+ }
+ }
+ }
}
-// Add pressure unit
-int32_t Z_AddPressureUnitFunc(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr) {
- json[new_name] = F(D_UNIT_PRESSURE);
- return 0; // keep original key
+void ZCLFrame::syntheticAqaraSensor2(class Z_attribute_list &attr_list, class Z_attribute &attr) {
+ const SBuffer * buf = attr.getRaw();
+ if (buf) {
+ const SBuffer & buf2 = *buf;
+ uint32_t len = buf2.len();
+
+ // Look for battery value which is the first attribute of type 0x21
+ uint16_t struct_size = buf2.get16(0);
+ size_t struct_len = 2;
+ if (0xFFFF != struct_size) {
+ if (struct_size > 16) { struct_size = 16; }
+ for (uint32_t j = 0; (j < struct_size) && (struct_len < len); j++) {
+ uint8_t attr_type = buf2.get8(struct_len);
+ if (0x21 == attr_type) {
+ uint16_t val = buf2.get16(struct_len+1);
+ float batteryvoltage = (float)val / 100;
+ attr_list.addAttribute(0x0001, 0x0020).setFloat(batteryvoltage);
+ uint8_t batterypercentage = toPercentageCR2032(val);
+ attr_list.addAttribute(0x0001, 0x0021).setUInt(batterypercentage * 2);
+ break;
+ }
+ struct_len += Z_getDatatypeLen(attr_type) + 1;
+ }
+ }
+ }
+ attr_list.removeAttribute(&attr);
}
-// Publish a message for `"Occupancy":0` when the timer expired
-int32_t Z_OccupancyCallback(uint16_t shortaddr, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint, uint32_t value) {
- DynamicJsonBuffer jsonBuffer;
- JsonObject& json = jsonBuffer.createObject();
- json[F(OCCUPANCY)] = 0;
- zigbee_devices.jsonPublishNow(shortaddr, json);
- return 0; // Fix GCC 10.1 warning
-}
-
-// Aqara Cube
-int32_t Z_AqaraCubeFunc(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr) {
- const char * modelId_c = zigbee_devices.findShortAddr(shortaddr).modelId; // null if unknown
+// Aqara Cube and Button
+void ZCLFrame::syntheticAqaraCubeOrButton(class Z_attribute_list &attr_list, class Z_attribute &attr) {
+ const char * modelId_c = zigbee_devices.findShortAddr(_srcaddr).modelId; // null if unknown
String modelId((char*) modelId_c);
if (modelId.startsWith(F("lumi.sensor_cube"))) { // only for Aqara cube
- int32_t val = value;
+ int32_t val = attr.getInt();
const __FlashStringHelper *aqara_cube = F("AqaraCube");
const __FlashStringHelper *aqara_cube_side = F("AqaraCubeSide");
const __FlashStringHelper *aqara_cube_from_side = F("AqaraCubeFromSide");
switch (val) {
case 0:
- json[aqara_cube] = F("shake");
+ attr_list.addAttribute(aqara_cube).setStr(PSTR("shake"));
break;
case 2:
- json[aqara_cube] = F("wakeup");
+ attr_list.addAttribute(aqara_cube).setStr(PSTR("wakeup"));
break;
case 3:
- json[aqara_cube] = F("fall");
+ attr_list.addAttribute(aqara_cube).setStr(PSTR("fall"));
break;
case 64 ... 127:
- json[aqara_cube] = F("flip90");
- json[aqara_cube_side] = val % 8;
- json[aqara_cube_from_side] = (val - 64) / 8;
+ attr_list.addAttribute(aqara_cube).setStr(PSTR("flip90"));
+ attr_list.addAttribute(aqara_cube_side).setInt(val % 8);
+ attr_list.addAttribute(aqara_cube_from_side).setInt((val - 64) / 8);
break;
case 128 ... 132:
- json[aqara_cube] = F("flip180");
- json[aqara_cube_side] = val - 128;
+ attr_list.addAttribute(aqara_cube).setStr(PSTR("flip180"));
+ attr_list.addAttribute(aqara_cube_side).setInt(val - 128);
break;
case 256 ... 261:
- json[aqara_cube] = F("slide");
- json[aqara_cube_side] = val - 256;
+ attr_list.addAttribute(aqara_cube).setStr(PSTR("slide"));
+ attr_list.addAttribute(aqara_cube_side).setInt(val - 256);
break;
case 512 ... 517:
- json[aqara_cube] = F("tap");
- json[aqara_cube_side] = val - 512;
+ attr_list.addAttribute(aqara_cube).setStr(PSTR("tap"));
+ attr_list.addAttribute(aqara_cube_side).setInt(val - 512);
break;
}
- return 1;
- }
-
- // Source: https://github.com/kirovilya/ioBroker.zigbee
- // +---+
- // | 2 |
- // +---+---+---+
- // | 4 | 0 | 1 |
- // +---+---+---+
- // |M5I|
- // +---+
- // | 3 |
- // +---+
- // Side 5 is with the MI logo, side 3 contains the battery door.
- // presentValue = 0 = shake
- // presentValue = 2 = wakeup
- // presentValue = 3 = fly/fall
- // presentValue = y + x * 8 + 64 = 90º Flip from side x on top to side y on top
- // presentValue = x + 128 = 180º flip to side x on top
- // presentValue = x + 256 = push/slide cube while side x is on top
- // presentValue = x + 512 = double tap while side x is on top
- return 0;
-}
-
-// Aqara Button
-int32_t Z_AqaraButtonFunc(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr) {
- const char * modelId_c = zigbee_devices.getModelId(shortaddr); // null if unknown
- String modelId((char*) modelId_c);
-
- if (modelId.startsWith(F("lumi.remote"))) { // only for Aqara button
- int32_t val = value;
+ attr_list.removeAttribute(&attr);
+ // Source: https://github.com/kirovilya/ioBroker.zigbee
+ // +---+
+ // | 2 |
+ // +---+---+---+
+ // | 4 | 0 | 1 |
+ // +---+---+---+
+ // |M5I|
+ // +---+
+ // | 3 |
+ // +---+
+ // Side 5 is with the MI logo, side 3 contains the battery door.
+ // presentValue = 0 = shake
+ // presentValue = 2 = wakeup
+ // presentValue = 3 = fly/fall
+ // presentValue = y + x * 8 + 64 = 90º Flip from side x on top to side y on top
+ // presentValue = x + 128 = 180º flip to side x on top
+ // presentValue = x + 256 = push/slide cube while side x is on top
+ // presentValue = x + 512 = double tap while side x is on top
+ } else if (modelId.startsWith(F("lumi.remote"))) { // only for Aqara button
+ int32_t val = attr.getInt();
const __FlashStringHelper *aqara_click = F("click");
const __FlashStringHelper *aqara_action = F("action");
switch (val) {
case 0:
- json[aqara_action] = F("hold");
+ attr_list.addAttribute(aqara_action).setStr(PSTR("hold"));
break;
case 1:
- json[aqara_click] = F("single");
+ attr_list.addAttribute(aqara_click).setStr(PSTR("single"));
break;
case 2:
- json[aqara_click] = F("double");
+ attr_list.addAttribute(aqara_click).setStr(PSTR("double"));
break;
case 255:
- json[aqara_action] = F("release");
+ attr_list.addAttribute(aqara_click).setStr(PSTR("release"));
break;
default:
- json[aqara_action] = val;
+ attr_list.addAttribute(aqara_click).setUInt(val);
break;
}
- return 1;
}
-
- return 0;
}
-// Aqara Vibration Sensor - special proprietary attributes
-int32_t Z_AqaraVibrationFunc(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr) {
- //json[new_name] = value;
- switch (attr) {
+// Aqara vibration device
+void ZCLFrame::syntheticAqaraVibration(class Z_attribute_list &attr_list, class Z_attribute &attr) {
+ switch (attr.key.id.attr_id) {
case 0x0055:
{
- int32_t ivalue = value;
+ int32_t ivalue = attr.getInt();
const __FlashStringHelper * svalue;
switch (ivalue) {
case 1: svalue = F("vibrate"); break;
@@ -1374,283 +1438,123 @@ int32_t Z_AqaraVibrationFunc(const class ZCLFrame *zcl, uint16_t shortaddr, Json
case 3: svalue = F("drop"); break;
default: svalue = F("unknown"); break;
}
- json[new_name] = svalue;
+ attr.setStr((const char*)svalue);
}
break;
- // case 0x0503:
- // break;
- // case 0x0505:
- // break;
+ case 0x0503:
+ break;
+ case 0x0505:
+ break;
case 0x0508:
{
// see https://github.com/Koenkk/zigbee2mqtt/issues/295 and http://faire-ca-soi-meme.fr/domotique/2018/09/03/test-xiaomi-aqara-vibration-sensor/
// report accelerometer measures
- String hex = value;
- SBuffer buf2 = SBuffer::SBufferFromHex(hex.c_str(), hex.length());
- int16_t x, y, z;
- z = buf2.get16(0);
- y = buf2.get16(2);
- x = buf2.get16(4);
- JsonArray& xyz = json.createNestedArray(new_name);
- xyz.add(x);
- xyz.add(y);
- xyz.add(z);
- // calculate angles
- float X = x;
- float Y = y;
- float Z = z;
- int32_t Angle_X = 0.5f + atanf(X/sqrtf(z*z+y*y)) * f_180pi;
- int32_t Angle_Y = 0.5f + atanf(Y/sqrtf(x*x+z*z)) * f_180pi;
- int32_t Angle_Z = 0.5f + atanf(Z/sqrtf(x*x+y*y)) * f_180pi;
- JsonArray& angles = json.createNestedArray(F("AqaraAngles"));
- angles.add(Angle_X);
- angles.add(Angle_Y);
- angles.add(Angle_Z);
+ const SBuffer * buf = attr.getRaw();
+ if (buf) {
+ const SBuffer & buf2 = *buf;
+ int16_t x, y, z;
+ z = buf2.get16(0);
+ y = buf2.get16(2);
+ x = buf2.get16(4);
+ char temp[32];
+ snprintf_P(temp, sizeof(temp), "[%i,%i,%i]", x, y, z);
+ attr.setStrRaw(temp);
+ // calculate angles
+ float X = x;
+ float Y = y;
+ float Z = z;
+ int32_t Angle_X = 0.5f + atanf(X/sqrtf(z*z+y*y)) * f_180pi;
+ int32_t Angle_Y = 0.5f + atanf(Y/sqrtf(x*x+z*z)) * f_180pi;
+ int32_t Angle_Z = 0.5f + atanf(Z/sqrtf(x*x+y*y)) * f_180pi;
+ snprintf_P(temp, sizeof(temp), "[%i,%i,%i]", Angle_X, Angle_Y, Angle_Z);
+ attr_list.addAttribute(F("AqaraAngles")).setStrRaw(temp);
+ }
}
break;
}
- return 1; // remove original key
}
-int32_t Z_AqaraSensorFunc(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr) {
- String hex = value;
- SBuffer buf2 = SBuffer::SBufferFromHex(hex.c_str(), hex.length());
- uint32_t i = 0;
- uint32_t len = buf2.len();
- char tmp[] = "tmp"; // for obscure reasons, it must be converted from const char* to char*, otherwise ArduinoJson gets confused
-
- const char * modelId_c = zigbee_devices.getModelId(shortaddr); // null if unknown
- String modelId((char*) modelId_c);
-
- while (len >= 2 + i) {
- uint8_t attrid = buf2.get8(i++);
-
- i += parseSingleAttribute(json, tmp, buf2, i);
- float val = json[tmp];
- json.remove(tmp);
- bool translated = false; // were we able to translate to a known format?
- if (0x01 == attrid) {
- float batteryvoltage = val / 1000.0f;
- json[F("BatteryVoltage")] = batteryvoltage;
- uint8_t batterypercentage = toPercentageCR2032(val);
- json[F("BatteryPercentage")] = batterypercentage;
- zigbee_devices.setBatteryPercent(shortaddr, batterypercentage);
- // deprecated
- json[F(D_JSON_VOLTAGE)] = batteryvoltage;
- json[F("Battery")] = toPercentageCR2032(val);
- } else if ((nullptr != modelId) && (0 == zcl->getManufCode())) {
- translated = true;
- if (modelId.startsWith(F("lumi.sensor_ht")) ||
- modelId.startsWith(F("lumi.weather"))) { // Temp sensor
- // Filter according to prefix of model name
- // onla Aqara Temp/Humidity has manuf_code of zero. If non-zero we skip the parameters
- if (0x64 == attrid) {
- json[F(D_JSON_TEMPERATURE)] = val / 100.0f;
- } else if (0x65 == attrid) {
- json[F(D_JSON_HUMIDITY)] = val / 100.0f;
- } else if (0x66 == attrid) {
- json[F(D_JSON_PRESSURE)] = val / 100.0f;
- json[F(D_JSON_PRESSURE_UNIT)] = F(D_UNIT_PRESSURE); // hPa
- }
- } else if (modelId.startsWith(F("lumi.sensor_smoke"))) { // gas leak
- if (0x64 == attrid) {
- json[F("SmokeDensity")] = val;
- }
- } else if (modelId.startsWith(F("lumi.sensor_natgas"))) { // gas leak
- if (0x64 == attrid) {
- json[F("GasDensity")] = val;
- }
- } else {
- translated = false; // we didn't find a match
- }
- // } else if (0x115F == zcl->getManufCode()) { // Aqara Motion Sensor, still unknown field
- }
- if (!translated) {
- if (attrid >= 100) { // payload is always above 0x64 or 100
- char attr_name[12];
- snprintf_P(attr_name, sizeof(attr_name), PSTR("Xiaomi_%02X"), attrid);
- json[attr_name] = val;
- }
- }
- }
- return 1; // remove original key
+/// Publish a message for `"Occupancy":0` when the timer expired
+int32_t Z_OccupancyCallback(uint16_t shortaddr, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint, uint32_t value) {
+ Z_attribute_list attr_list;
+ attr_list.addAttribute(F(OCCUPANCY)).setUInt(0);
+ zigbee_devices.jsonPublishNow(shortaddr, attr_list);
+ return 0; // Fix GCC 10.1 warning
}
-int32_t Z_AqaraSensorFunc2(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr) {
- String hex = value;
- SBuffer buf2 = SBuffer::SBufferFromHex(hex.c_str(), hex.length());
- uint32_t i = 0;
- uint32_t len = buf2.len();
-
- // Look for battery value which is the first attribute of type 0x21
- uint16_t struct_size = buf2.get16(0);
- size_t struct_len = 2;
- if (0xFFFF != struct_size) {
- if (struct_size > 16) { struct_size = 16; }
- for (uint32_t j = 0; (j < struct_size) && (struct_len < len); j++) {
- uint8_t attr_type = buf2.get8(struct_len);
- if (0x21 == attr_type) {
- uint16_t val = buf2.get16(struct_len+1);
- float batteryvoltage = val / 1000.0f;
- json[F("BatteryVoltage")] = batteryvoltage;
- uint8_t batterypercentage = toPercentageCR2032(val);
- json[F("BatteryPercentage")] = batterypercentage;
- zigbee_devices.setBatteryPercent(shortaddr, batterypercentage);
- break;
- }
- struct_len += Z_getDatatypeLen(attr_type) + 1;
- }
- }
-
- return 0; // remove original key
-}
// ======================================================================
-
-// apply the transformation from the converter
-int32_t Z_ApplyConverter(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name,
- uint16_t cluster, uint16_t attr, int8_t multiplier, uint8_t cb) {
- // apply multiplier if needed
- if (1 == multiplier) { // copy unchanged
- json[new_name] = value;
- } else if (0 != multiplier) {
- if (multiplier > 0) {
- json[new_name] = ((float)value) * multiplier;
- } else {
- json[new_name] = ((float)value) / (-multiplier);
- }
- }
-
- // apply callback if needed
- Z_AttrConverter func = nullptr;
- switch (cb) {
- case Z_Nop:
- return 1; // drop original key
- case Z_AddPressureUnit:
- func = &Z_AddPressureUnitFunc;
- break;
- case Z_ManufKeep:
- func = &Z_ManufKeepFunc;
- break;
- case Z_ModelKeep:
- func = &Z_ModelKeepFunc;
- break;
- case Z_AqaraSensor:
- func = &Z_AqaraSensorFunc;
- break;
- case Z_AqaraSensor2:
- func = &Z_AqaraSensorFunc2;
- break;
- case Z_AqaraVibration:
- func = &Z_AqaraVibrationFunc;
- break;
- case Z_AqaraCube:
- func = &Z_AqaraCubeFunc;
- break;
- case Z_AqaraButton:
- func = &Z_AqaraButtonFunc;
- break;
- case Z_BatteryPercentage:
- func = &Z_BatteryPercentageKeepFunc;
- break;
- };
-
- if (func) {
- return (*func)(zcl, shortaddr, json, name, value, new_name, cluster, attr);
- }
- return 1; // Fix GCC 10.1 warning
-}
-
-// Scan all the final attributes and update any internal representation like sensors
-void ZCLFrame::updateInternalAttributes(uint16_t shortaddr, JsonObject& json) {
- Z_Device & device = zigbee_devices.getShortAddr(shortaddr);
- for (auto kv : json) {
- String key_string = kv.key;
- const char * key = key_string.c_str();
- JsonVariant& value = kv.value;
-
- if (key_string.equalsIgnoreCase(F("Temperature"))) {
- device.temperature = value.as() * 10 + 0.5f;
- } else if (key_string.equalsIgnoreCase(F("Humidity"))) {
- device.humidity = value.as() + 0.5f;
- } else if (key_string.equalsIgnoreCase(F("Pressure"))) {
- device.pressure = value.as() + 0.5f;
- }
- }
-}
-
-void ZCLFrame::postProcessAttributes(uint16_t shortaddr, JsonObject& json) {
+void ZCLFrame::postProcessAttributes(uint16_t shortaddr, Z_attribute_list& attr_list) {
// source endpoint
uint8_t src_ep = _srcendpoint;
- // iterate on json elements
- for (auto kv : json) {
- String key_string = kv.key;
- const char * key = key_string.c_str();
- JsonVariant& value = kv.value;
- // Check that format looks like "CCCC/AAAA" or "CCCC/AAAA+d"
- char * delimiter = strchr(key, '/');
- char * delimiter2 = strchr(key, '+');
- if (delimiter) {
- uint16_t attribute;
- uint16_t suffix = 1;
- uint16_t cluster = strtoul(key, &delimiter, 16);
- if (!delimiter2) {
- attribute = strtoul(delimiter+1, nullptr, 16);
- } else {
- attribute = strtoul(delimiter+1, &delimiter2, 16);
- suffix = strtoul(delimiter2+1, nullptr, 10);
- }
-
+
+ for (auto &attr : attr_list) {
+ // attr is Z_attribute&
+ if (!attr.key_is_str) {
+ uint16_t cluster = attr.key.id.cluster;
+ uint16_t attribute = attr.key.id.attr_id;
+ uint32_t ccccaaaa = (attr.key.id.cluster << 16) | attr.key.id.attr_id;
Z_Device & device = zigbee_devices.getShortAddr(shortaddr);
- uint16_t uval16 = value; // call converter from JSonVariant to int only once
- int16_t ival16 = value; // call converter from JSonVariant to int only once
- // see if we need to update the Hue bulb status
- if ((cluster == 0x0006) && ((attribute == 0x0000) || (attribute == 0x8000))) {
- bool power = value;
- device.setPower(power);
- } else if ((cluster == 0x0008) && (attribute == 0x0000)) {
- device.dimmer = uval16;
- } else if ((cluster == 0x0300) && (attribute == 0x0000)) {
- device.hue = changeUIntScale(uval16, 0, 254, 0, 360); // change range from 0..254 to 0..360
- } else if ((cluster == 0x0300) && (attribute == 0x0001)) {
- device.sat = uval16;
- } else if ((cluster == 0x0300) && (attribute == 0x0003)) {
- device.x = uval16;
- } else if ((cluster == 0x0300) && (attribute == 0x0004)) {
- device.y = uval16;
- } else if ((cluster == 0x0300) && (attribute == 0x0007)) {
- device.ct = uval16;
- } else if ((cluster == 0x0300) && (attribute == 0x0008)) {
- device.colormode = uval16;
- } else if ((cluster == 0x0B04) && (attribute == 0x0505)) {
- device.mains_voltage = uval16;
- } else if ((cluster == 0x0B04) && (attribute == 0x050B)) {
- device.mains_power = ival16;
- }
- // Iterate on filter
+ // Look for an entry in the converter table
+ bool found = false;
+ int8_t conv_multiplier;
+ const char * conv_name;
for (uint32_t i = 0; i < ARRAY_SIZE(Z_PostProcess); i++) {
const Z_AttributeConverter *converter = &Z_PostProcess[i];
uint16_t conv_cluster = CxToCluster(pgm_read_byte(&converter->cluster_short));
uint16_t conv_attribute = pgm_read_word(&converter->attribute);
- int8_t conv_multiplier = pgm_read_byte(&converter->multiplier);
- uint8_t conv_cb = pgm_read_byte(&converter->cb); // callback id
if ((conv_cluster == cluster) &&
((conv_attribute == attribute) || (conv_attribute == 0xFFFF)) ) {
- String new_name_str = (const __FlashStringHelper*) (Z_strings + pgm_read_word(&converter->name_offset));
- if (suffix > 1) { new_name_str += suffix; } // append suffix number
- // apply the transformation
- int32_t drop = Z_ApplyConverter(this, shortaddr, json, key, value, new_name_str, conv_cluster, conv_attribute, conv_multiplier, conv_cb);
- if (drop) {
- json.remove(key);
- }
+ conv_multiplier = pgm_read_byte(&converter->multiplier);
+ conv_name = Z_strings + pgm_read_word(&converter->name_offset);
+ found = true;
+ break;
+ }
+ }
+ // apply multiplier if needed
+ float fval = attr.getFloat();
+ if (found) {
+ if (0 == conv_multiplier) { attr_list.removeAttribute(&attr); continue; } // remove attribute if multiplier is zero
+ if (1 != conv_multiplier) {
+ if (conv_multiplier > 0) { fval = fval * conv_multiplier; }
+ else { fval = fval / (-conv_multiplier); }
+ attr.setFloat(fval);
+ }
+ }
+
+ uint16_t uval16 = attr.getUInt(); // call converter to uint only once
+ int16_t ival16 = attr.getInt(); // call converter to int only once
+ // update any internal structure
+ switch (ccccaaaa) {
+ case 0x00000004: zigbee_devices.setManufId(shortaddr, attr.getStr()); break;
+ case 0x00000005: zigbee_devices.setModelId(shortaddr, attr.getStr()); break;
+ case 0x00010021: zigbee_devices.setBatteryPercent(shortaddr, uval16); break;
+ case 0x00060000:
+ case 0x00068000: device.setPower(attr.getBool()); break;
+ case 0x00080000: device.dimmer = uval16; break;
+ case 0x03000000: device.hue = changeUIntScale(uval16, 0, 254, 0, 360); break;
+ case 0x03000001: device.sat = uval16; break;
+ case 0x03000003: device.x = uval16; break;
+ case 0x03000004: device.y = uval16; break;
+ case 0x03000007: device.ct = uval16; break;
+ case 0x03000008: device.colormode = uval16; break;
+ case 0x04020000: device.temperature = fval * 10 + 0.5f; break;
+ case 0x04030000: device.pressure = fval + 0.5f; break;
+ case 0x04050000: device.humidity = fval + 0.5f; break;
+ case 0x0B040505: device.mains_voltage = uval16; break;
+ case 0x0B04050B: device.mains_power = ival16; break;
+ }
+
+ // Replace cluster/attribute with name
+ if (found) {
+ if (0x00 != pgm_read_byte(conv_name)) {// if name is not null, replace it
+ attr.setKeyName(conv_name, true); // PMEM string so no need to copy
}
}
}
}
-
- updateInternalAttributes(shortaddr, json);
}
#endif // USE_ZIGBEE
diff --git a/tasmota/xdrv_23_zigbee_6_commands.ino b/tasmota/xdrv_23_zigbee_6_commands.ino
index 1a3e5b103..c533eb8ed 100644
--- a/tasmota/xdrv_23_zigbee_6_commands.ino
+++ b/tasmota/xdrv_23_zigbee_6_commands.ino
@@ -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" = "<": "" 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());
}
}
}
diff --git a/tasmota/xdrv_23_zigbee_8_parsers.ino b/tasmota/xdrv_23_zigbee_8_parsers.ino
index 67eafb8b0..1f5d33a8f 100644
--- a/tasmota/xdrv_23_zigbee_8_parsers.ino
+++ b/tasmota/xdrv_23_zigbee_8_parsers.ino
@@ -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 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) {
diff --git a/tasmota/xdrv_23_zigbee_A_impl.ino b/tasmota/xdrv_23_zigbee_A_impl.ino
index a6386e491..4dd7e99ad 100644
--- a/tasmota/xdrv_23_zigbee_A_impl.ino
+++ b/tasmota/xdrv_23_zigbee_A_impl.ino
@@ -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(), &cluster, nullptr, nullptr, nullptr);
+ zigbeeFindAttributeByName(val_cluster.as(), &cluster, nullptr, nullptr);
}
}