mirror of https://github.com/arendst/Tasmota.git
Merge branch 'development' of https://github.com/arendst/Tasmota into development
This commit is contained in:
commit
ea8d92ec69
|
@ -0,0 +1,186 @@
|
|||
/*
|
||||
support_light_list.ino - Lightweight Linked List for simple objects - optimized for low code size and low memory
|
||||
|
||||
Copyright (C) 2020 Theo Arends and Stephan Hadinger
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*********************************************************************************************\
|
||||
*
|
||||
* private class for Linked List element
|
||||
*
|
||||
\*********************************************************************************************/
|
||||
template <typename T>
|
||||
class LList;
|
||||
|
||||
template <typename T>
|
||||
class LList_elt {
|
||||
public:
|
||||
|
||||
LList_elt() : _next(nullptr), _val() {}
|
||||
|
||||
inline T & val(void) { return _val; }
|
||||
inline LList_elt<T> * next(void) { return _next; }
|
||||
inline void next(LList_elt<T> * next) { _next = next; }
|
||||
|
||||
friend class LList<T>;
|
||||
|
||||
protected:
|
||||
LList_elt<T> * _next;
|
||||
T _val;
|
||||
};
|
||||
|
||||
/*********************************************************************************************\
|
||||
*
|
||||
* Lightweight Linked List - optimized for low code size
|
||||
*
|
||||
\*********************************************************************************************/
|
||||
template <typename T>
|
||||
class LList {
|
||||
public:
|
||||
LList() : _head(nullptr) {}
|
||||
~LList() { reset(); }
|
||||
|
||||
// remove elements
|
||||
void removeHead(void); // remove first element
|
||||
void reset(void); // remove all elements
|
||||
void remove(const T * val);
|
||||
|
||||
// read the list
|
||||
inline bool isEmpty(void) const { return (_head == nullptr) ? true : false; }
|
||||
size_t length(void) const;
|
||||
inline T * head(void) { return _head ? &_head->_val : nullptr; }
|
||||
inline const T * head(void) const { return _head ? &_head->_val : nullptr; }
|
||||
const T * at(size_t index) const ;
|
||||
// non-const variants
|
||||
// not very academic cast but reduces code size
|
||||
inline T * at(size_t index) { return (T*) ((const LList<T>*)this)->at(index); }
|
||||
|
||||
// adding elements
|
||||
T & addHead(void);
|
||||
T & addHead(const T &val);
|
||||
T & addToLast(void);
|
||||
|
||||
// iterator
|
||||
// see https://stackoverflow.com/questions/8164567/how-to-make-my-custom-type-to-work-with-range-based-for-loops
|
||||
class iterator {
|
||||
public:
|
||||
iterator(LList_elt<T> *_cur): cur(_cur), next(nullptr) { if (cur) { next = cur->_next; } }
|
||||
iterator operator++() { cur = next; if (cur) { next = cur->_next;} return *this; }
|
||||
bool operator!=(const iterator & other) const { return cur != other.cur; }
|
||||
T & operator*() const { return cur->_val; }
|
||||
private:
|
||||
LList_elt<T> *cur;
|
||||
LList_elt<T> *next; // we need to keep next pointer in case the current attribute gets deleted
|
||||
};
|
||||
iterator begin() { return iterator(this->_head); } // start with 'head'
|
||||
iterator end() { return iterator(nullptr); } // end with null pointer
|
||||
|
||||
// const iterator
|
||||
class const_iterator {
|
||||
public:
|
||||
const_iterator(const LList_elt<T> *_cur): cur(_cur), next(nullptr) { if (cur) { next = cur->_next; } }
|
||||
const_iterator operator++() { cur = next; if (cur) { next = cur->_next;} return *this; }
|
||||
bool operator!=(const_iterator & other) const { return cur != other.cur; }
|
||||
const T & operator*() const { return cur->_val; }
|
||||
private:
|
||||
const LList_elt<T> *cur;
|
||||
const LList_elt<T> *next; // we need to keep next pointer in case the current attribute gets deleted
|
||||
};
|
||||
const_iterator begin() const { return const_iterator(this->_head); } // start with 'head'
|
||||
const_iterator end() const { return const_iterator(nullptr); } // end with null pointer
|
||||
|
||||
protected:
|
||||
LList_elt<T> * _head;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
size_t LList<T>::length(void) const {
|
||||
size_t count = 0;
|
||||
for (auto & elt : *this) {count++; }
|
||||
return count;
|
||||
}
|
||||
|
||||
|
||||
template <typename T>
|
||||
const T * LList<T>::at(size_t index) const {
|
||||
size_t count = 0;
|
||||
for (const auto & elt : *this) {
|
||||
if (index == count++) { return &elt; }
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void LList<T>::reset(void) {
|
||||
while (_head) {
|
||||
LList_elt<T> * next = _head->next();
|
||||
delete _head;
|
||||
_head = next;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void LList<T>::removeHead(void) {
|
||||
if (_head) {
|
||||
LList_elt<T> * next = _head->next();
|
||||
delete _head;
|
||||
_head = next;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void LList<T>::remove(const T * val) {
|
||||
if (nullptr == val) { return; }
|
||||
// find element in chain and find pointer before
|
||||
LList_elt<T> **curr_ptr = &_head;
|
||||
while (*curr_ptr) {
|
||||
LList_elt<T> * curr_elt = *curr_ptr;
|
||||
if ( &(curr_elt->_val) == val) {
|
||||
*curr_ptr = curr_elt->_next; // update previous pointer to next element
|
||||
delete curr_elt;
|
||||
break; // stop iteration now
|
||||
}
|
||||
curr_ptr = &((*curr_ptr)->_next); // move to next element
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T & LList<T>::addHead(void) {
|
||||
LList_elt<T> * elt = new LList_elt<T>(); // create element
|
||||
elt->next(_head); // insert at the head
|
||||
_head = elt;
|
||||
return elt->_val;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T & LList<T>::addHead(const T &val) {
|
||||
LList_elt<T> * elt = new LList_elt<T>(); // create element
|
||||
elt->next(_head); // insert at the head
|
||||
elt->_val = val;
|
||||
_head = elt;
|
||||
return elt->_val;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T & LList<T>::addToLast(void) {
|
||||
LList_elt<T> **curr_ptr = &_head;
|
||||
while (*curr_ptr) {
|
||||
curr_ptr = &((*curr_ptr)->_next);
|
||||
}
|
||||
LList_elt<T> * elt = new LList_elt<T>(); // create element
|
||||
*curr_ptr = elt;
|
||||
return elt->_val;
|
||||
}
|
|
@ -237,3 +237,18 @@ public:
|
|||
_buf = nullptr;
|
||||
}
|
||||
} PreAllocatedSBuffer;
|
||||
|
||||
// nullptr accepted
|
||||
bool equalsSBuffer(const class SBuffer * buf1, const class SBuffer * buf2) {
|
||||
if (buf1 == buf2) { return true; }
|
||||
if (!buf1 && (buf2->len() == 0)) { return true; }
|
||||
if (!buf2 && (buf1->len() == 0)) { return true; }
|
||||
if (!buf1 || !buf2) { return false; } // at least one buf is not empty
|
||||
// we know that both buf1 and buf2 are non-null
|
||||
if (buf1->len() != buf2->len()) { return false; }
|
||||
size_t len = buf1->len();
|
||||
for (uint32_t i=0; i<len; i++) {
|
||||
if (buf1->get8(i) != buf2->get8(i)) { return false; }
|
||||
}
|
||||
return true;
|
||||
}
|
|
@ -0,0 +1,752 @@
|
|||
/*
|
||||
xdrv_23_zigbee_1z_libs.ino - zigbee support for Tasmota, JSON replacement libs
|
||||
|
||||
Copyright (C) 2020 Theo Arends and Stephan Hadinger
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifdef USE_ZIGBEE
|
||||
|
||||
/*********************************************************************************************\
|
||||
* Replacement libs for JSON to output a list of attributes
|
||||
\*********************************************************************************************/
|
||||
|
||||
// simplified version of strcmp accepting both arguments to be in PMEM, and accepting nullptr arguments
|
||||
// inspired from https://code.woboq.org/userspace/glibc/string/strcmp.c.html
|
||||
int strcmp_PP(const char *p1, const char *p2) {
|
||||
if (p1 == p2) { return 0; } // equality
|
||||
if (!p1) { return -1; } // first string is null
|
||||
if (!p2) { return 1; } // second string is null
|
||||
const unsigned char *s1 = (const unsigned char *) p1;
|
||||
const unsigned char *s2 = (const unsigned char *) p2;
|
||||
unsigned char c1, c2;
|
||||
do {
|
||||
c1 = (unsigned char) pgm_read_byte(s1);
|
||||
s1++;
|
||||
c2 = (unsigned char) pgm_read_byte(s2);
|
||||
s2++;
|
||||
if (c1 == '\0')
|
||||
return c1 - c2;
|
||||
}
|
||||
while (c1 == c2);
|
||||
return c1 - c2;
|
||||
}
|
||||
|
||||
/*********************************************************************************************\
|
||||
*
|
||||
* Variables for Rules from last Zigbee message received
|
||||
*
|
||||
\*********************************************************************************************/
|
||||
|
||||
typedef struct Z_LastMessageVars {
|
||||
uint16_t device; // device short address
|
||||
uint16_t groupaddr; // group address
|
||||
uint16_t cluster; // cluster id
|
||||
uint8_t endpoint; // source endpoint
|
||||
} Z_LastMessageVars;
|
||||
|
||||
Z_LastMessageVars gZbLastMessage;
|
||||
|
||||
uint16_t Z_GetLastDevice(void) { return gZbLastMessage.device; }
|
||||
uint16_t Z_GetLastGroup(void) { return gZbLastMessage.groupaddr; }
|
||||
uint16_t Z_GetLastCluster(void) { return gZbLastMessage.cluster; }
|
||||
uint8_t Z_GetLastEndpoint(void) { return gZbLastMessage.endpoint; }
|
||||
|
||||
/*********************************************************************************************\
|
||||
*
|
||||
* Class for single attribute
|
||||
*
|
||||
\*********************************************************************************************/
|
||||
|
||||
enum class Za_type : uint8_t {
|
||||
Za_none, // empty, translates into null in JSON
|
||||
// numericals
|
||||
Za_bool, // boolean, translates to true/false, uses uval32 to store
|
||||
Za_uint, // unsigned 32 int, uses uval32
|
||||
Za_int, // signed 32 int, uses ival32
|
||||
Za_float, // float 32, uses fval
|
||||
// non-nummericals
|
||||
Za_raw, // bytes buffer, uses bval
|
||||
Za_str // string, uses sval
|
||||
};
|
||||
|
||||
class Z_attribute {
|
||||
public:
|
||||
|
||||
// attribute key, either cluster+attribute_id or plain name
|
||||
union {
|
||||
struct {
|
||||
uint16_t cluster;
|
||||
uint16_t attr_id;
|
||||
} id;
|
||||
char * key;
|
||||
} key;
|
||||
// attribute value
|
||||
union {
|
||||
uint32_t uval32;
|
||||
int32_t ival32;
|
||||
float fval;
|
||||
SBuffer* bval;
|
||||
char* sval;
|
||||
} val;
|
||||
Za_type type; // uint8_t in size, type of attribute, see above
|
||||
bool key_is_str; // is the key a string?
|
||||
bool key_is_pmem; // is the string in progmem, so we don't need to make a copy
|
||||
bool val_str_raw; // if val is String, it is raw JSON and should not be escaped
|
||||
uint8_t key_suffix; // append a suffix to key (default is 1, explicitly output if >1)
|
||||
|
||||
// Constructor with all defaults
|
||||
Z_attribute():
|
||||
key{ .id = { 0x0000, 0x0000 } },
|
||||
val{ .uval32 = 0x0000 },
|
||||
type(Za_type::Za_none),
|
||||
key_is_str(false),
|
||||
key_is_pmem(false),
|
||||
val_str_raw(false),
|
||||
key_suffix(1)
|
||||
{};
|
||||
|
||||
Z_attribute(const Z_attribute & rhs) {
|
||||
deepCopy(rhs);
|
||||
}
|
||||
|
||||
Z_attribute & operator = (const Z_attribute & rhs) {
|
||||
freeKey();
|
||||
freeVal();
|
||||
deepCopy(rhs);
|
||||
}
|
||||
|
||||
// Destructor, free memory that was allocated
|
||||
~Z_attribute() {
|
||||
freeKey();
|
||||
freeVal();
|
||||
}
|
||||
|
||||
// free any allocated memoruy for values
|
||||
void freeVal(void) {
|
||||
switch (type) {
|
||||
case Za_type::Za_raw:
|
||||
if (val.bval) { delete val.bval; val.bval = nullptr; }
|
||||
break;
|
||||
case Za_type::Za_str:
|
||||
if (val.sval) { delete[] val.sval; val.sval = nullptr; }
|
||||
break;
|
||||
}
|
||||
}
|
||||
// free any allocated memoruy for keys
|
||||
void freeKey(void) {
|
||||
if (key_is_str && key.key && !key_is_pmem) { delete[] key.key; }
|
||||
key.key = nullptr;
|
||||
}
|
||||
|
||||
// set key name
|
||||
void setKeyName(const char * _key, bool pmem = false) {
|
||||
freeKey();
|
||||
key_is_str = true;
|
||||
key_is_pmem = pmem;
|
||||
if (pmem) {
|
||||
key.key = (char*) _key;
|
||||
} else {
|
||||
setKeyName(_key, nullptr);
|
||||
}
|
||||
}
|
||||
// provide two entries and concat
|
||||
void setKeyName(const char * _key, const char * _key2) {
|
||||
freeKey();
|
||||
key_is_str = true;
|
||||
key_is_pmem = false;
|
||||
if (_key) {
|
||||
size_t key_len = strlen_P(_key);
|
||||
if (_key2) {
|
||||
key_len += strlen_P(_key2);
|
||||
}
|
||||
key.key = new char[key_len+1];
|
||||
strcpy_P(key.key, _key);
|
||||
if (_key2) {
|
||||
strcat_P(key.key, _key2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void setKeyId(uint16_t cluster, uint16_t attr_id) {
|
||||
freeKey();
|
||||
key_is_str = false;
|
||||
key.id.cluster = cluster;
|
||||
key.id.attr_id = attr_id;
|
||||
}
|
||||
|
||||
// Setters
|
||||
void setNone(void) {
|
||||
freeVal(); // free any previously allocated memory
|
||||
val.uval32 = 0;
|
||||
type = Za_type::Za_none;
|
||||
}
|
||||
void setUInt(uint32_t _val) {
|
||||
freeVal(); // free any previously allocated memory
|
||||
val.uval32 = _val;
|
||||
type = Za_type::Za_uint;
|
||||
}
|
||||
void setBool(bool _val) {
|
||||
freeVal(); // free any previously allocated memory
|
||||
val.uval32 = _val;
|
||||
type = Za_type::Za_bool;
|
||||
}
|
||||
void setInt(int32_t _val) {
|
||||
freeVal(); // free any previously allocated memory
|
||||
val.ival32 = _val;
|
||||
type = Za_type::Za_int;
|
||||
}
|
||||
void setFloat(float _val) {
|
||||
freeVal(); // free any previously allocated memory
|
||||
val.fval = _val;
|
||||
type = Za_type::Za_float;
|
||||
}
|
||||
|
||||
void setBuf(const SBuffer &buf, size_t index, size_t len) {
|
||||
freeVal();
|
||||
if (len) {
|
||||
val.bval = new SBuffer(len);
|
||||
val.bval->addBuffer(buf.buf(index), len);
|
||||
}
|
||||
type = Za_type::Za_raw;
|
||||
}
|
||||
|
||||
// set the string value
|
||||
// PMEM argument is allowed
|
||||
// string will be copied, so it can be changed later
|
||||
// nullptr is allowed and considered as empty string
|
||||
// Note: memory is allocated only if string is non-empty
|
||||
void setStr(const char * _val) {
|
||||
freeVal(); // free any previously allocated memory
|
||||
val_str_raw = false;
|
||||
// val.sval is always nullptr after freeVal()
|
||||
if (_val) {
|
||||
size_t len = strlen_P(_val);
|
||||
if (len) {
|
||||
val.sval = new char[len+1];
|
||||
strcpy_P(val.sval, _val);
|
||||
}
|
||||
}
|
||||
type = Za_type::Za_str;
|
||||
}
|
||||
inline void setStrRaw(const char * _val) {
|
||||
setStr(_val);
|
||||
val_str_raw = true;
|
||||
}
|
||||
|
||||
inline bool isNum(void) const { return (type >= Za_type::Za_bool) && (type <= Za_type::Za_float); }
|
||||
// get num values
|
||||
float getFloat(void) const {
|
||||
switch (type) {
|
||||
case Za_type::Za_bool:
|
||||
case Za_type::Za_uint: return (float) val.uval32;
|
||||
case Za_type::Za_int: return (float) val.ival32;
|
||||
case Za_type::Za_float: return val.fval;
|
||||
default: return 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
int32_t getInt(void) const {
|
||||
switch (type) {
|
||||
case Za_type::Za_bool:
|
||||
case Za_type::Za_uint: return (int32_t) val.uval32;
|
||||
case Za_type::Za_int: return val.ival32;
|
||||
case Za_type::Za_float: return (int32_t) val.fval;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t getUInt(void) const {
|
||||
switch (type) {
|
||||
case Za_type::Za_bool:
|
||||
case Za_type::Za_uint: return val.uval32;
|
||||
case Za_type::Za_int: return (uint32_t) val.ival32;
|
||||
case Za_type::Za_float: return (uint32_t) val.fval;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool getBool(void) const {
|
||||
switch (type) {
|
||||
case Za_type::Za_bool:
|
||||
case Za_type::Za_uint: return val.uval32 ? true : false;
|
||||
case Za_type::Za_int: return val.ival32 ? true : false;
|
||||
case Za_type::Za_float: return val.fval ? true : false;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
const SBuffer * getRaw(void) const {
|
||||
if (Za_type::Za_raw == type) { return val.bval; }
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// always return a point to a string, if not defined then empty string.
|
||||
// Never returns nullptr
|
||||
const char * getStr(void) const {
|
||||
if (Za_type::Za_str == type) { return val.sval; }
|
||||
return "";
|
||||
}
|
||||
|
||||
bool equalsKey(const Z_attribute & attr2, bool ignore_key_suffix = false) const {
|
||||
// check if keys are equal
|
||||
if (key_is_str != attr2.key_is_str) { return false; }
|
||||
if (key_is_str) {
|
||||
if (strcmp_PP(key.key, attr2.key.key)) { return false; }
|
||||
} else {
|
||||
if ((key.id.cluster != attr2.key.id.cluster) ||
|
||||
(key.id.attr_id != attr2.key.id.attr_id)) { return false; }
|
||||
}
|
||||
if (!ignore_key_suffix) {
|
||||
if (key_suffix != attr2.key_suffix) { return false; }
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool equalsKey(uint16_t cluster, uint16_t attr_id, uint8_t suffix = 0) const {
|
||||
if (!key_is_str) {
|
||||
if ((key.id.cluster == cluster) && (key.id.attr_id == attr_id)) {
|
||||
if (suffix) {
|
||||
if (key_suffix == suffix) { return true; }
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool equalsKey(const char * name, uint8_t suffix = 0) const {
|
||||
if (key_is_str) {
|
||||
if (0 == strcmp_PP(key.key, name)) {
|
||||
if (suffix) {
|
||||
if (key_suffix == suffix) { return true; }
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool equalsVal(const Z_attribute & attr2) const {
|
||||
if (type != attr2.type) { return false; }
|
||||
if ((type >= Za_type::Za_bool) && (type <= Za_type::Za_float)) {
|
||||
// numerical value
|
||||
if (val.uval32 != attr2.val.uval32) { return false; }
|
||||
} else if (type == Za_type::Za_raw) {
|
||||
// compare 2 Static buffers
|
||||
return equalsSBuffer(val.bval, attr2.val.bval);
|
||||
} else if (type == Za_type::Za_str) {
|
||||
// if (val_str_raw != attr2.val_str_raw) { return false; }
|
||||
if (strcmp_PP(val.sval, attr2.val.sval)) { return false; }
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool equals(const Z_attribute & attr2) const {
|
||||
return equalsKey(attr2) && equalsVal(attr2);
|
||||
}
|
||||
|
||||
String toString(bool prefix_comma = false) const {
|
||||
String res("");
|
||||
if (prefix_comma) { res += ','; }
|
||||
res += '"';
|
||||
// compute the attribute name
|
||||
if (key_is_str) {
|
||||
if (key.key) { res += EscapeJSONString(key.key); }
|
||||
else { res += F("null"); } // shouldn't happen
|
||||
if (key_suffix > 1) {
|
||||
res += key_suffix;
|
||||
}
|
||||
} else {
|
||||
char attr_name[12];
|
||||
snprintf_P(attr_name, sizeof(attr_name), PSTR("%04X/%04X"), key.id.cluster, key.id.attr_id);
|
||||
res += attr_name;
|
||||
if (key_suffix > 1) {
|
||||
res += '+';
|
||||
res += key_suffix;
|
||||
}
|
||||
}
|
||||
res += F("\":");
|
||||
// value part
|
||||
switch (type) {
|
||||
case Za_type::Za_none:
|
||||
res += "null";
|
||||
break;
|
||||
case Za_type::Za_bool:
|
||||
res += val.uval32 ? F("true") : F("false");
|
||||
break;
|
||||
case Za_type::Za_uint:
|
||||
res += val.uval32;
|
||||
break;
|
||||
case Za_type::Za_int:
|
||||
res += val.ival32;
|
||||
break;
|
||||
case Za_type::Za_float:
|
||||
{
|
||||
String fstr(val.fval, 2);
|
||||
size_t last = fstr.length() - 1;
|
||||
// remove trailing zeros
|
||||
while (fstr[last] == '0') {
|
||||
fstr.remove(last--);
|
||||
}
|
||||
// remove trailing dot
|
||||
if (fstr[last] == '.') {
|
||||
fstr.remove(last);
|
||||
}
|
||||
res += fstr;
|
||||
}
|
||||
break;
|
||||
case Za_type::Za_raw:
|
||||
res += '"';
|
||||
if (val.bval) {
|
||||
size_t blen = val.bval->len();
|
||||
// print as HEX
|
||||
char hex[2*blen+1];
|
||||
ToHex_P(val.bval->getBuffer(), blen, hex, sizeof(hex));
|
||||
res += hex;
|
||||
}
|
||||
res += '"';
|
||||
break;
|
||||
case Za_type::Za_str:
|
||||
if (val_str_raw) {
|
||||
if (val.sval) { res += val.sval; }
|
||||
} else {
|
||||
res += '"';
|
||||
if (val.sval) {
|
||||
res += EscapeJSONString(val.sval); // escape JSON chars
|
||||
}
|
||||
res += '"';
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
// copy value from one attribute to another, without changing its type
|
||||
void copyVal(const Z_attribute & rhs) {
|
||||
freeVal();
|
||||
// copy value
|
||||
val.uval32 = 0x00000000;
|
||||
type = rhs.type;
|
||||
if (rhs.isNum()) {
|
||||
val.uval32 = rhs.val.uval32;
|
||||
} else if (rhs.type == Za_type::Za_raw) {
|
||||
if (rhs.val.bval) {
|
||||
val.bval = new SBuffer(rhs.val.bval->len());
|
||||
val.bval->addBuffer(*(rhs.val.bval));
|
||||
}
|
||||
} else if (rhs.type == Za_type::Za_str) {
|
||||
if (rhs.val.sval) {
|
||||
size_t s_len = strlen_P(rhs.val.sval);
|
||||
val.sval = new char[s_len+1];
|
||||
strcpy_P(val.sval, rhs.val.sval);
|
||||
}
|
||||
}
|
||||
val_str_raw = rhs.val_str_raw;
|
||||
}
|
||||
|
||||
protected:
|
||||
void deepCopy(const Z_attribute & rhs) {
|
||||
// copy key
|
||||
if (!rhs.key_is_str) {
|
||||
key.id.cluster = rhs.key.id.cluster;
|
||||
key.id.attr_id = rhs.key.id.attr_id;
|
||||
} else {
|
||||
if (rhs.key_is_pmem) {
|
||||
key.key = rhs.key.key; // PMEM, don't copy
|
||||
} else {
|
||||
key.key = nullptr;
|
||||
if (rhs.key.key) {
|
||||
size_t key_len = strlen_P(rhs.key.key);
|
||||
if (key_len) {
|
||||
key.key = new char[key_len+1];
|
||||
strcpy_P(key.key, rhs.key.key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
key_is_str = rhs.key_is_str;
|
||||
key_is_pmem = rhs.key_is_pmem;
|
||||
key_suffix = rhs.key_suffix;
|
||||
// copy value
|
||||
copyVal(rhs);
|
||||
// don't touch next pointer
|
||||
}
|
||||
};
|
||||
|
||||
/*********************************************************************************************\
|
||||
*
|
||||
* Class for attribute array of values
|
||||
* This is a helper function to generate a clean list of unsigned ints
|
||||
*
|
||||
\*********************************************************************************************/
|
||||
|
||||
class Z_json_array {
|
||||
public:
|
||||
|
||||
Z_json_array(): val("[]") {} // start with empty array
|
||||
void add(uint32_t uval32) {
|
||||
// remove trailing ']'
|
||||
val.remove(val.length()-1);
|
||||
if (val.length() > 1) { // if not empty, prefix with comma
|
||||
val += ',';
|
||||
}
|
||||
val += uval32;
|
||||
val += ']';
|
||||
}
|
||||
String &toString(void) {
|
||||
return val;
|
||||
}
|
||||
|
||||
private :
|
||||
String val;
|
||||
};
|
||||
|
||||
/*********************************************************************************************\
|
||||
*
|
||||
* Class for attribute ordered list
|
||||
*
|
||||
\*********************************************************************************************/
|
||||
|
||||
|
||||
// Attribute list
|
||||
// Contains meta-information:
|
||||
// - source endpoint (is conflicting)
|
||||
// - LQI (if not conflicting)
|
||||
class Z_attribute_list : public LList<Z_attribute> {
|
||||
public:
|
||||
uint8_t src_ep; // source endpoint, 0xFF if unknown
|
||||
uint8_t lqi; // linkquality, 0xFF if unknown
|
||||
uint16_t group_id; // group address OxFFFF if inknown
|
||||
|
||||
Z_attribute_list():
|
||||
LList<Z_attribute>(), // call superclass constructor
|
||||
src_ep(0xFF),
|
||||
lqi(0xFF),
|
||||
group_id(0xFFFF)
|
||||
{};
|
||||
|
||||
// we don't define any destructor, the superclass destructor is automatically called
|
||||
|
||||
// reset object to its initial state
|
||||
// free all allocated memory
|
||||
void reset(void) {
|
||||
LList<Z_attribute>::reset();
|
||||
src_ep = 0xFF;
|
||||
lqi = 0xFF;
|
||||
group_id = 0xFFFF;
|
||||
}
|
||||
|
||||
inline bool isValidSrcEp(void) const { return 0xFF != src_ep; }
|
||||
inline bool isValidLQI(void) const { return 0xFF != lqi; }
|
||||
inline bool isValidGroupId(void) const { return 0xFFFF != group_id; }
|
||||
|
||||
// the following addAttribute() compute the suffix and increments it
|
||||
// Add attribute to the list, given cluster and attribute id
|
||||
Z_attribute & addAttribute(uint16_t cluster, uint16_t attr_id, uint8_t suffix = 0);
|
||||
|
||||
// Add attribute to the list, given name
|
||||
Z_attribute & addAttribute(const char * name, bool pmem = false, uint8_t suffix = 0);
|
||||
Z_attribute & addAttribute(const char * name, const char * name2, uint8_t suffix = 0);
|
||||
inline Z_attribute & addAttribute(const __FlashStringHelper * name, uint8_t suffix = 0) {
|
||||
return addAttribute((const char*) name, true, suffix);
|
||||
}
|
||||
|
||||
// Remove from list by reference, if null or not found, then do nothing
|
||||
inline void removeAttribute(const Z_attribute * attr) { remove(attr); }
|
||||
|
||||
// dump the entire structure as JSON, starting from head (as parameter)
|
||||
// does not start not end with a comma
|
||||
// do we enclosed in brackets '{' '}'
|
||||
String toString(bool enclose_brackets = false) const;
|
||||
|
||||
// find if attribute with same key already exists, return null if not found
|
||||
const Z_attribute * findAttribute(uint16_t cluster, uint16_t attr_id, uint8_t suffix = 0) const;
|
||||
const Z_attribute * findAttribute(const char * name, uint8_t suffix = 0) const;
|
||||
const Z_attribute * findAttribute(const Z_attribute &attr) const; // suffis always count here
|
||||
// non-const variants
|
||||
inline Z_attribute * findAttribute(uint16_t cluster, uint16_t attr_id, uint8_t suffix = 0) {
|
||||
return (Z_attribute*) ((const Z_attribute_list*)this)->findAttribute(cluster, attr_id, suffix);
|
||||
}
|
||||
inline Z_attribute * findAttribute(const char * name, uint8_t suffix = 0) {
|
||||
return (Z_attribute*) (((const Z_attribute_list*)this)->findAttribute(name, suffix));
|
||||
}
|
||||
inline Z_attribute * findAttribute(const Z_attribute &attr) {
|
||||
return (Z_attribute*) ((const Z_attribute_list*)this)->findAttribute(attr);
|
||||
}
|
||||
|
||||
// count matching attributes, ignoring suffix
|
||||
size_t countAttribute(uint16_t cluster, uint16_t attr_id) const ;
|
||||
size_t countAttribute(const char * name) const ;
|
||||
|
||||
// if suffix == 0, we don't care and find the first match
|
||||
Z_attribute & findOrCreateAttribute(uint16_t cluster, uint16_t attr_id, uint8_t suffix = 0);
|
||||
Z_attribute & findOrCreateAttribute(const char * name, uint8_t suffix = 0);
|
||||
// always care about suffix
|
||||
Z_attribute & findOrCreateAttribute(const Z_attribute &attr);
|
||||
// replace attribute with new value, suffix does care
|
||||
Z_attribute & replaceOrCreate(const Z_attribute &attr);
|
||||
|
||||
// merge with secondary list, return true if ok, false if conflict
|
||||
bool mergeList(const Z_attribute_list &list2);
|
||||
};
|
||||
|
||||
// add a cluster/attr_id attribute at the end of the list
|
||||
Z_attribute & Z_attribute_list::addAttribute(uint16_t cluster, uint16_t attr_id, uint8_t suffix) {
|
||||
Z_attribute & attr = addToLast();
|
||||
attr.key.id.cluster = cluster;
|
||||
attr.key.id.attr_id = attr_id;
|
||||
attr.key_is_str = false;
|
||||
if (!suffix) { attr.key_suffix = countAttribute(attr.key.id.cluster, attr.key.id.attr_id); }
|
||||
else { attr.key_suffix = suffix; }
|
||||
return attr;
|
||||
}
|
||||
|
||||
// add a cluster/attr_id attribute at the end of the list
|
||||
Z_attribute & Z_attribute_list::addAttribute(const char * name, bool pmem, uint8_t suffix) {
|
||||
Z_attribute & attr = addToLast();
|
||||
attr.setKeyName(name, pmem);
|
||||
if (!suffix) { attr.key_suffix = countAttribute(attr.key.key); }
|
||||
else { attr.key_suffix = suffix; }
|
||||
return attr;
|
||||
}
|
||||
|
||||
Z_attribute & Z_attribute_list::addAttribute(const char * name, const char * name2, uint8_t suffix) {
|
||||
Z_attribute & attr = addToLast();
|
||||
attr.setKeyName(name, name2);
|
||||
if (!suffix) { attr.key_suffix = countAttribute(attr.key.key); }
|
||||
else { attr.key_suffix = suffix; }
|
||||
return attr;
|
||||
}
|
||||
|
||||
String Z_attribute_list::toString(bool enclose_brackets) const {
|
||||
String res = "";
|
||||
if (enclose_brackets) { res += '{'; }
|
||||
bool prefix_comma = false;
|
||||
for (const auto & attr : *this) {
|
||||
res += attr.toString(prefix_comma);
|
||||
prefix_comma = true;
|
||||
}
|
||||
// add source endpoint
|
||||
if (0xFF != src_ep) {
|
||||
if (prefix_comma) { res += ','; }
|
||||
prefix_comma = true;
|
||||
res += F("\"" D_CMND_ZIGBEE_ENDPOINT "\":");
|
||||
res += src_ep;
|
||||
}
|
||||
// add group address
|
||||
if (0xFFFF != group_id) {
|
||||
if (prefix_comma) { res += ','; }
|
||||
prefix_comma = true;
|
||||
res += F("\"" D_CMND_ZIGBEE_GROUP "\":");
|
||||
res += group_id;
|
||||
}
|
||||
// add lqi
|
||||
if (0xFF != lqi) {
|
||||
if (prefix_comma) { res += ','; }
|
||||
prefix_comma = true;
|
||||
res += F("\"" D_CMND_ZIGBEE_LINKQUALITY "\":");
|
||||
res += lqi;
|
||||
}
|
||||
if (enclose_brackets) { res += '}'; }
|
||||
// done
|
||||
return res;
|
||||
}
|
||||
|
||||
// suffis always count here
|
||||
const Z_attribute * Z_attribute_list::findAttribute(const Z_attribute &attr) const {
|
||||
uint8_t suffix = attr.key_suffix;
|
||||
if (attr.key_is_str) {
|
||||
return findAttribute(attr.key.key, suffix);
|
||||
} else {
|
||||
return findAttribute(attr.key.id.cluster, attr.key.id.attr_id, suffix);
|
||||
}
|
||||
}
|
||||
|
||||
const Z_attribute * Z_attribute_list::findAttribute(uint16_t cluster, uint16_t attr_id, uint8_t suffix) const {
|
||||
for (const auto & attr : *this) {
|
||||
if (attr.equalsKey(cluster, attr_id, suffix)) { return &attr; }
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
size_t Z_attribute_list::countAttribute(uint16_t cluster, uint16_t attr_id) const {
|
||||
size_t count = 0;
|
||||
for (const auto & attr : *this) {
|
||||
if (attr.equalsKey(cluster, attr_id, 0)) { count++; }
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
// return the existing attribute or create a new one
|
||||
Z_attribute & Z_attribute_list::findOrCreateAttribute(uint16_t cluster, uint16_t attr_id, uint8_t suffix) {
|
||||
Z_attribute * found = findAttribute(cluster, attr_id, suffix);
|
||||
return found ? *found : addAttribute(cluster, attr_id, suffix);
|
||||
}
|
||||
|
||||
const Z_attribute * Z_attribute_list::findAttribute(const char * name, uint8_t suffix) const {
|
||||
for (const auto & attr : *this) {
|
||||
if (attr.equalsKey(name, suffix)) { return &attr; }
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
size_t Z_attribute_list::countAttribute(const char * name) const {
|
||||
size_t count = 0;
|
||||
for (const auto & attr : *this) {
|
||||
if (attr.equalsKey(name, 0)) { count++; }
|
||||
}
|
||||
return count;
|
||||
}
|
||||
// return the existing attribute or create a new one
|
||||
Z_attribute & Z_attribute_list::findOrCreateAttribute(const char * name, uint8_t suffix) {
|
||||
Z_attribute * found = findAttribute(name, suffix);
|
||||
return found ? *found : addAttribute(name, suffix);
|
||||
}
|
||||
|
||||
// same but passing a Z_attribute as key
|
||||
Z_attribute & Z_attribute_list::findOrCreateAttribute(const Z_attribute &attr) {
|
||||
if (attr.key_is_str) {
|
||||
return findOrCreateAttribute(attr.key.key, attr.key_suffix);
|
||||
} else {
|
||||
return findOrCreateAttribute(attr.key.id.cluster, attr.key.id.attr_id, attr.key_suffix);
|
||||
}
|
||||
}
|
||||
// replace the entire content with new attribute or create
|
||||
Z_attribute & Z_attribute_list::replaceOrCreate(const Z_attribute &attr) {
|
||||
Z_attribute &new_attr = findOrCreateAttribute(attr);
|
||||
new_attr.copyVal(attr);
|
||||
return new_attr;
|
||||
}
|
||||
|
||||
|
||||
bool Z_attribute_list::mergeList(const Z_attribute_list &attr_list) {
|
||||
// Check source endpoint
|
||||
if (0xFF == src_ep) {
|
||||
src_ep = attr_list.src_ep;
|
||||
} else if (0xFF != attr_list.src_ep) {
|
||||
if (src_ep != attr_list.src_ep) { return false; }
|
||||
}
|
||||
if (0xFF != attr_list.lqi) {
|
||||
lqi = attr_list.lqi;
|
||||
}
|
||||
for (auto & attr : attr_list) {
|
||||
replaceOrCreate(attr);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif // USE_ZIGBEE
|
|
@ -19,8 +19,6 @@
|
|||
|
||||
#ifdef USE_ZIGBEE
|
||||
|
||||
#include <vector>
|
||||
|
||||
#ifndef ZIGBEE_SAVE_DELAY_SECONDS
|
||||
#define ZIGBEE_SAVE_DELAY_SECONDS 2 // wait for 2s before saving Zigbee info
|
||||
#endif
|
||||
|
@ -29,25 +27,6 @@ const uint16_t kZigbeeSaveDelaySeconds = ZIGBEE_SAVE_DELAY_SECONDS; // wait f
|
|||
/*********************************************************************************************\
|
||||
* Structures for Rules variables related to the last received message
|
||||
\*********************************************************************************************/
|
||||
|
||||
typedef struct Z_LastMessageVars {
|
||||
uint16_t device; // device short address
|
||||
uint16_t groupaddr; // group address
|
||||
uint16_t cluster; // cluster id
|
||||
uint8_t endpoint; // source endpoint
|
||||
} Z_LastMessageVars;
|
||||
|
||||
Z_LastMessageVars gZbLastMessage;
|
||||
|
||||
uint16_t Z_GetLastDevice(void) { return gZbLastMessage.device; }
|
||||
uint16_t Z_GetLastGroup(void) { return gZbLastMessage.groupaddr; }
|
||||
uint16_t Z_GetLastCluster(void) { return gZbLastMessage.cluster; }
|
||||
uint8_t Z_GetLastEndpoint(void) { return gZbLastMessage.endpoint; }
|
||||
|
||||
/*********************************************************************************************\
|
||||
* Structures for device configuration
|
||||
\*********************************************************************************************/
|
||||
|
||||
const size_t endpoints_max = 8; // we limit to 8 endpoints
|
||||
|
||||
class Z_Device {
|
||||
|
@ -58,9 +37,8 @@ public:
|
|||
char * modelId;
|
||||
char * friendlyName;
|
||||
uint8_t endpoints[endpoints_max]; // static array to limit memory consumption, list of endpoints until 0x00 or end of array
|
||||
// json buffer used for attribute reporting
|
||||
DynamicJsonBuffer *json_buffer;
|
||||
JsonObject *json;
|
||||
// Used for attribute reporting
|
||||
Z_attribute_list attr_list;
|
||||
// sequence number for Zigbee frames
|
||||
uint16_t shortaddr; // unique key if not null, or unspecified if null
|
||||
uint8_t seqNumber;
|
||||
|
@ -95,14 +73,13 @@ public:
|
|||
int16_t mains_power; // Active power
|
||||
|
||||
// Constructor with all defaults
|
||||
Z_Device(uint16_t _shortaddr, uint64_t _longaddr = 0x00):
|
||||
Z_Device(uint16_t _shortaddr = BAD_SHORTADDR, uint64_t _longaddr = 0x00):
|
||||
longaddr(_longaddr),
|
||||
manufacturerId(nullptr),
|
||||
modelId(nullptr),
|
||||
friendlyName(nullptr),
|
||||
endpoints{ 0, 0, 0, 0, 0, 0, 0, 0 },
|
||||
json_buffer(nullptr),
|
||||
json(nullptr),
|
||||
attr_list(),
|
||||
shortaddr(_shortaddr),
|
||||
seqNumber(0),
|
||||
// Hue support
|
||||
|
@ -207,7 +184,7 @@ typedef struct Z_Deferred {
|
|||
// - shortaddr and longaddr cannot be both null
|
||||
class Z_Devices {
|
||||
public:
|
||||
Z_Devices() {};
|
||||
Z_Devices() : _deferred() {};
|
||||
|
||||
// Probe the existence of device keys
|
||||
// Results:
|
||||
|
@ -218,12 +195,17 @@ public:
|
|||
uint16_t isKnownIndex(uint32_t index) const;
|
||||
uint16_t isKnownFriendlyName(const char * name) const;
|
||||
|
||||
Z_Device & findShortAddr(uint16_t shortaddr);
|
||||
const Z_Device & findShortAddr(uint16_t shortaddr) const;
|
||||
Z_Device & findLongAddr(uint64_t longaddr);
|
||||
const Z_Device & findLongAddr(uint64_t longaddr) const;
|
||||
Z_Device & getShortAddr(uint16_t shortaddr); // find Device from shortAddr, creates it if does not exist
|
||||
const Z_Device & getShortAddrConst(uint16_t shortaddr) const ; // find Device from shortAddr, creates it if does not exist
|
||||
Z_Device & getLongAddr(uint64_t longaddr); // find Device from shortAddr, creates it if does not exist
|
||||
// check if a device was found or if it's the fallback device
|
||||
inline bool foundDevice(const Z_Device & device) const {
|
||||
return (&device != &device_unk);
|
||||
}
|
||||
|
||||
int32_t findLongAddr(uint64_t longaddr) const;
|
||||
int32_t findFriendlyName(const char * name) const;
|
||||
uint64_t getDeviceLongAddr(uint16_t shortaddr) const;
|
||||
|
||||
|
@ -281,19 +263,22 @@ public:
|
|||
void runTimer(void);
|
||||
|
||||
// Append or clear attributes Json structure
|
||||
void jsonClear(uint16_t shortaddr);
|
||||
void jsonAppend(uint16_t shortaddr, const JsonObject &values);
|
||||
const JsonObject *jsonGet(uint16_t shortaddr);
|
||||
void jsonAppend(uint16_t shortaddr, const Z_attribute_list &attr_list);
|
||||
void jsonPublishFlush(uint16_t shortaddr); // publish the json message and clear buffer
|
||||
bool jsonIsConflict(uint16_t shortaddr, const JsonObject &values);
|
||||
void jsonPublishNow(uint16_t shortaddr, JsonObject &values);
|
||||
bool jsonIsConflict(uint16_t shortaddr, const Z_attribute_list &attr_list) const;
|
||||
void jsonPublishNow(uint16_t shortaddr, Z_attribute_list &attr_list);
|
||||
|
||||
// Iterator
|
||||
size_t devicesSize(void) const {
|
||||
return _devices.size();
|
||||
return _devices.length();
|
||||
}
|
||||
const Z_Device &devicesAt(size_t i) const {
|
||||
return *(_devices.at(i));
|
||||
const Z_Device & devicesAt(size_t i) const {
|
||||
const Z_Device * devp = _devices.at(i);
|
||||
if (devp) {
|
||||
return *devp;
|
||||
} else {
|
||||
return device_unk;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove device from list
|
||||
|
@ -308,8 +293,8 @@ public:
|
|||
uint16_t parseDeviceParam(const char * param, bool short_must_be_known = false) const;
|
||||
|
||||
private:
|
||||
std::vector<Z_Device*> _devices = {};
|
||||
std::vector<Z_Deferred> _deferred = {}; // list of deferred calls
|
||||
LList<Z_Device> _devices; // list of devices
|
||||
LList<Z_Deferred> _deferred; // list of deferred calls
|
||||
uint32_t _saveTimer = 0;
|
||||
uint8_t _seqNumber = 0; // global seqNumber if device is unknown
|
||||
|
||||
|
@ -317,10 +302,7 @@ private:
|
|||
// Any find() function will not return Null, instead it will return this instance
|
||||
const Z_Device device_unk = Z_Device(BAD_SHORTADDR);
|
||||
|
||||
template < typename T>
|
||||
static bool findInVector(const std::vector<T> & vecOfElements, const T & element);
|
||||
|
||||
int32_t findShortAddrIdx(uint16_t shortaddr) const;
|
||||
//int32_t findShortAddrIdx(uint16_t shortaddr) const;
|
||||
// Create a new entry in the devices list - must be called if it is sure it does not already exist
|
||||
Z_Device & createDeviceEntry(uint16_t shortaddr, uint64_t longaddr = 0);
|
||||
void freeDeviceEntry(Z_Device *device);
|
||||
|
@ -343,31 +325,16 @@ uint16_t localShortAddr = 0;
|
|||
* Implementation
|
||||
\*********************************************************************************************/
|
||||
|
||||
// https://thispointer.com/c-how-to-find-an-element-in-vector-and-get-its-index/
|
||||
template < typename T>
|
||||
bool Z_Devices::findInVector(const std::vector<T> & vecOfElements, const T & element) {
|
||||
// Find given element in vector
|
||||
auto it = std::find(vecOfElements.begin(), vecOfElements.end(), element);
|
||||
|
||||
if (it != vecOfElements.end()) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Create a new Z_Device entry in _devices. Only to be called if you are sure that no
|
||||
// entry with same shortaddr or longaddr exists.
|
||||
//
|
||||
Z_Device & Z_Devices::createDeviceEntry(uint16_t shortaddr, uint64_t longaddr) {
|
||||
if ((BAD_SHORTADDR == shortaddr) && !longaddr) { return (Z_Device&) device_unk; } // it is not legal to create this entry
|
||||
Z_Device * device_alloc = new Z_Device(shortaddr, longaddr);
|
||||
Z_Device device(shortaddr, longaddr);
|
||||
|
||||
device_alloc->json_buffer = new DynamicJsonBuffer(16);
|
||||
_devices.push_back(device_alloc);
|
||||
dirty();
|
||||
return *(_devices.back());
|
||||
return _devices.addHead(device);
|
||||
}
|
||||
|
||||
void Z_Devices::freeDeviceEntry(Z_Device *device) {
|
||||
|
@ -383,20 +350,17 @@ void Z_Devices::freeDeviceEntry(Z_Device *device) {
|
|||
// In:
|
||||
// shortaddr (not BAD_SHORTADDR)
|
||||
// Out:
|
||||
// index in _devices of entry, -1 if not found
|
||||
//
|
||||
int32_t Z_Devices::findShortAddrIdx(uint16_t shortaddr) const {
|
||||
if (BAD_SHORTADDR == shortaddr) { return -1; } // does not make sense to look for BAD_SHORTADDR shortaddr (broadcast)
|
||||
int32_t found = 0;
|
||||
for (auto &elem : _devices) {
|
||||
if (elem->shortaddr == shortaddr) { return found; }
|
||||
found++;
|
||||
// reference to device, or to device_unk if not found
|
||||
// (use foundDevice() to check if found)
|
||||
Z_Device & Z_Devices::findShortAddr(uint16_t shortaddr) {
|
||||
for (auto & elem : _devices) {
|
||||
if (elem.shortaddr == shortaddr) { return elem; }
|
||||
}
|
||||
return -1;
|
||||
return (Z_Device&) device_unk;
|
||||
}
|
||||
const Z_Device & Z_Devices::findShortAddr(uint16_t shortaddr) const {
|
||||
for (auto &elem : _devices) {
|
||||
if (elem->shortaddr == shortaddr) { return *elem; }
|
||||
for (const auto & elem : _devices) {
|
||||
if (elem.shortaddr == shortaddr) { return elem; }
|
||||
}
|
||||
return device_unk;
|
||||
}
|
||||
|
@ -408,14 +372,19 @@ const Z_Device & Z_Devices::findShortAddr(uint16_t shortaddr) const {
|
|||
// Out:
|
||||
// index in _devices of entry, -1 if not found
|
||||
//
|
||||
int32_t Z_Devices::findLongAddr(uint64_t longaddr) const {
|
||||
if (!longaddr) { return -1; }
|
||||
int32_t found = 0;
|
||||
Z_Device & Z_Devices::findLongAddr(uint64_t longaddr) {
|
||||
if (!longaddr) { return (Z_Device&) device_unk; }
|
||||
for (auto &elem : _devices) {
|
||||
if (elem->longaddr == longaddr) { return found; }
|
||||
found++;
|
||||
if (elem.longaddr == longaddr) { return elem; }
|
||||
}
|
||||
return -1;
|
||||
return (Z_Device&) device_unk;
|
||||
}
|
||||
const Z_Device & Z_Devices::findLongAddr(uint64_t longaddr) const {
|
||||
if (!longaddr) { return device_unk; }
|
||||
for (const auto &elem : _devices) {
|
||||
if (elem.longaddr == longaddr) { return elem; }
|
||||
}
|
||||
return device_unk;
|
||||
}
|
||||
//
|
||||
// Scan all devices to find a corresponding friendlyNme
|
||||
|
@ -431,8 +400,8 @@ int32_t Z_Devices::findFriendlyName(const char * name) const {
|
|||
int32_t found = 0;
|
||||
if (name_len) {
|
||||
for (auto &elem : _devices) {
|
||||
if (elem->friendlyName) {
|
||||
if (strcasecmp(elem->friendlyName, name) == 0) { return found; }
|
||||
if (elem.friendlyName) {
|
||||
if (strcasecmp(elem.friendlyName, name) == 0) { return found; }
|
||||
}
|
||||
found++;
|
||||
}
|
||||
|
@ -441,9 +410,8 @@ int32_t Z_Devices::findFriendlyName(const char * name) const {
|
|||
}
|
||||
|
||||
uint16_t Z_Devices::isKnownLongAddr(uint64_t longaddr) const {
|
||||
int32_t found = findLongAddr(longaddr);
|
||||
if (found >= 0) {
|
||||
const Z_Device & device = devicesAt(found);
|
||||
const Z_Device & device = findLongAddr(longaddr);
|
||||
if (foundDevice(device)) {
|
||||
return device.shortaddr; // can be zero, if not yet registered
|
||||
} else {
|
||||
return BAD_SHORTADDR;
|
||||
|
@ -471,7 +439,7 @@ uint16_t Z_Devices::isKnownFriendlyName(const char * name) const {
|
|||
}
|
||||
|
||||
uint64_t Z_Devices::getDeviceLongAddr(uint16_t shortaddr) const {
|
||||
return getShortAddrConst(shortaddr).longaddr; // if unknown, it reverts to the Unknown device and longaddr is 0x00
|
||||
return findShortAddr(shortaddr).longaddr; // if unknown, it reverts to the Unknown device and longaddr is 0x00
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -479,38 +447,28 @@ uint64_t Z_Devices::getDeviceLongAddr(uint16_t shortaddr) const {
|
|||
//
|
||||
Z_Device & Z_Devices::getShortAddr(uint16_t shortaddr) {
|
||||
if (BAD_SHORTADDR == shortaddr) { return (Z_Device&) device_unk; } // this is not legal
|
||||
int32_t found = findShortAddrIdx(shortaddr);
|
||||
if (found >= 0) {
|
||||
return *(_devices[found]);
|
||||
Z_Device & device = findShortAddr(shortaddr);
|
||||
if (foundDevice(device)) {
|
||||
return device;
|
||||
}
|
||||
//Serial.printf("Device entry created for shortaddr = 0x%02X, found = %d\n", shortaddr, found);
|
||||
return createDeviceEntry(shortaddr, 0);
|
||||
}
|
||||
// Same version but Const
|
||||
const Z_Device & Z_Devices::getShortAddrConst(uint16_t shortaddr) const {
|
||||
int32_t found = findShortAddrIdx(shortaddr);
|
||||
if (found >= 0) {
|
||||
return *(_devices[found]);
|
||||
}
|
||||
return device_unk;
|
||||
}
|
||||
|
||||
// find the Device object by its longaddr (unique key if not null)
|
||||
Z_Device & Z_Devices::getLongAddr(uint64_t longaddr) {
|
||||
if (!longaddr) { return (Z_Device&) device_unk; }
|
||||
int32_t found = findLongAddr(longaddr);
|
||||
if (found > 0) {
|
||||
return *(_devices[found]);
|
||||
Z_Device & device = findLongAddr(longaddr);
|
||||
if (foundDevice(device)) {
|
||||
return device;
|
||||
}
|
||||
return createDeviceEntry(0, longaddr);
|
||||
}
|
||||
|
||||
// Remove device from list, return true if it was known, false if it was not recorded
|
||||
bool Z_Devices::removeDevice(uint16_t shortaddr) {
|
||||
int32_t found = findShortAddrIdx(shortaddr);
|
||||
if (found >= 0) {
|
||||
freeDeviceEntry(_devices.at(found));
|
||||
_devices.erase(_devices.begin() + found);
|
||||
Z_Device & device = findShortAddr(shortaddr);
|
||||
if (foundDevice(device)) {
|
||||
_devices.remove(&device);
|
||||
dirty();
|
||||
return true;
|
||||
}
|
||||
|
@ -523,27 +481,27 @@ bool Z_Devices::removeDevice(uint16_t shortaddr) {
|
|||
// shortaddr
|
||||
// longaddr (both can't be null at the same time)
|
||||
void Z_Devices::updateDevice(uint16_t shortaddr, uint64_t longaddr) {
|
||||
int32_t s_found = findShortAddrIdx(shortaddr); // is there already a shortaddr entry
|
||||
int32_t l_found = findLongAddr(longaddr); // is there already a longaddr entry
|
||||
Z_Device * s_found = &findShortAddr(shortaddr); // is there already a shortaddr entry
|
||||
Z_Device * l_found = &findLongAddr(longaddr); // is there already a longaddr entry
|
||||
|
||||
if ((s_found >= 0) && (l_found >= 0)) { // both shortaddr and longaddr are already registered
|
||||
if (foundDevice(*s_found) && foundDevice(*l_found)) { // both shortaddr and longaddr are already registered
|
||||
if (s_found == l_found) {
|
||||
} else { // they don't match
|
||||
// the device with longaddr got a new shortaddr
|
||||
_devices[l_found]->shortaddr = shortaddr; // update the shortaddr corresponding to the longaddr
|
||||
l_found->shortaddr = shortaddr; // update the shortaddr corresponding to the longaddr
|
||||
// erase the previous shortaddr
|
||||
freeDeviceEntry(_devices.at(s_found));
|
||||
_devices.erase(_devices.begin() + s_found);
|
||||
freeDeviceEntry(s_found);
|
||||
_devices.remove(s_found);
|
||||
dirty();
|
||||
}
|
||||
} else if (s_found >= 0) {
|
||||
} else if (foundDevice(*s_found)) {
|
||||
// shortaddr already exists but longaddr not
|
||||
// add the longaddr to the entry
|
||||
_devices[s_found]->longaddr = longaddr;
|
||||
s_found->longaddr = longaddr;
|
||||
dirty();
|
||||
} else if (l_found >= 0) {
|
||||
} else if (foundDevice(*l_found)) {
|
||||
// longaddr entry exists, update shortaddr
|
||||
_devices[l_found]->shortaddr = shortaddr;
|
||||
l_found->shortaddr = shortaddr;
|
||||
dirty();
|
||||
} else {
|
||||
// neither short/lonf addr are found.
|
||||
|
@ -588,9 +546,8 @@ void Z_Devices::addEndpoint(uint16_t shortaddr, uint8_t endpoint) {
|
|||
//
|
||||
uint32_t Z_Devices::countEndpoints(uint16_t shortaddr) const {
|
||||
uint32_t count_ep = 0;
|
||||
int32_t found = findShortAddrIdx(shortaddr);
|
||||
if (found < 0) return 0; // avoid creating an entry if the device was never seen
|
||||
const Z_Device &device = devicesAt(found);
|
||||
const Z_Device & device =findShortAddr(shortaddr);
|
||||
if (!foundDevice(device)) return 0;
|
||||
|
||||
for (uint32_t i = 0; i < endpoints_max; i++) {
|
||||
if (0 != device.endpoints[i]) {
|
||||
|
@ -666,9 +623,8 @@ void Z_Devices::setBatteryPercent(uint16_t shortaddr, uint8_t bp) {
|
|||
|
||||
// get the next sequance number for the device, or use the global seq number if device is unknown
|
||||
uint8_t Z_Devices::getNextSeqNumber(uint16_t shortaddr) {
|
||||
int32_t short_found = findShortAddrIdx(shortaddr);
|
||||
if (short_found >= 0) {
|
||||
Z_Device &device = getShortAddr(shortaddr);
|
||||
Z_Device & device = findShortAddr(shortaddr);
|
||||
if (foundDevice(device)) {
|
||||
device.seqNumber += 1;
|
||||
return device.seqNumber;
|
||||
} else {
|
||||
|
@ -754,9 +710,9 @@ void Z_Devices::hideHueBulb(uint16_t shortaddr, bool hidden) {
|
|||
}
|
||||
// true if device is not knwon or not a bulb - it wouldn't make sense to publish a non-bulb
|
||||
bool Z_Devices::isHueBulbHidden(uint16_t shortaddr) const {
|
||||
int32_t found = findShortAddrIdx(shortaddr);
|
||||
if (found >= 0) {
|
||||
uint8_t zb_profile = _devices[found]->zb_profile;
|
||||
const Z_Device & device = findShortAddr(shortaddr);
|
||||
if (foundDevice(device)) {
|
||||
uint8_t zb_profile = device.zb_profile;
|
||||
if (0x00 == (zb_profile & 0xF0)) {
|
||||
// bulb type
|
||||
return (zb_profile & 0x08) ? true : false;
|
||||
|
@ -769,13 +725,10 @@ bool Z_Devices::isHueBulbHidden(uint16_t shortaddr) const {
|
|||
// Parse for a specific category, of all deferred for a device if category == 0xFF
|
||||
void Z_Devices::resetTimersForDevice(uint16_t shortaddr, uint16_t groupaddr, uint8_t category) {
|
||||
// iterate the list of deferred, and remove any linked to the shortaddr
|
||||
for (auto it = _deferred.begin(); it != _deferred.end(); it++) {
|
||||
// Notice that the iterator is decremented after it is passed
|
||||
// to erase() but before erase() is executed
|
||||
// see https://www.techiedelight.com/remove-elements-vector-inside-loop-cpp/
|
||||
if ((it->shortaddr == shortaddr) && (it->groupaddr == groupaddr)) {
|
||||
if ((0xFF == category) || (it->category == category)) {
|
||||
_deferred.erase(it--);
|
||||
for (auto & defer : _deferred) {
|
||||
if ((defer.shortaddr == shortaddr) && (defer.groupaddr == groupaddr)) {
|
||||
if ((0xFF == category) || (defer.category == category)) {
|
||||
_deferred.remove(&defer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -789,7 +742,8 @@ void Z_Devices::setTimer(uint16_t shortaddr, uint16_t groupaddr, uint32_t wait_m
|
|||
}
|
||||
|
||||
// Now create the new timer
|
||||
Z_Deferred deferred = { wait_ms + millis(), // timer
|
||||
Z_Deferred & deferred = _deferred.addHead();
|
||||
deferred = { wait_ms + millis(), // timer
|
||||
shortaddr,
|
||||
groupaddr,
|
||||
cluster,
|
||||
|
@ -797,20 +751,17 @@ void Z_Devices::setTimer(uint16_t shortaddr, uint16_t groupaddr, uint32_t wait_m
|
|||
category,
|
||||
value,
|
||||
func };
|
||||
_deferred.push_back(deferred);
|
||||
}
|
||||
|
||||
// Run timer at each tick
|
||||
// WARNING: don't set a new timer within a running timer, this causes memory corruption
|
||||
void Z_Devices::runTimer(void) {
|
||||
// visit all timers
|
||||
for (auto it = _deferred.begin(); it != _deferred.end(); it++) {
|
||||
Z_Deferred &defer = *it;
|
||||
|
||||
for (auto & defer : _deferred) {
|
||||
uint32_t timer = defer.timer;
|
||||
if (TimeReached(timer)) {
|
||||
(*defer.func)(defer.shortaddr, defer.groupaddr, defer.cluster, defer.endpoint, defer.value);
|
||||
_deferred.erase(it--); // remove from list
|
||||
_deferred.remove(&defer);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -821,173 +772,100 @@ void Z_Devices::runTimer(void) {
|
|||
}
|
||||
}
|
||||
|
||||
// Clear the JSON buffer for coalesced and deferred attributes
|
||||
void Z_Devices::jsonClear(uint16_t shortaddr) {
|
||||
Z_Device & device = getShortAddr(shortaddr);
|
||||
device.json = nullptr;
|
||||
device.json_buffer->clear();
|
||||
}
|
||||
|
||||
// Copy JSON from one object to another, this helps preserving the order of attributes
|
||||
void CopyJsonVariant(JsonObject &to, const String &key, const JsonVariant &val) {
|
||||
// first remove the potentially existing key in the target JSON, so new adds will be at the end of the list
|
||||
to.remove(key); // force remove to have metadata like LinkQuality at the end
|
||||
|
||||
if (val.is<char*>()) {
|
||||
const char * sval = val.as<char*>(); // using char* forces a copy, and also captures 'null' values
|
||||
to.set(key, (char*) sval);
|
||||
} else if (val.is<JsonArray>()) {
|
||||
JsonArray &nested_arr = to.createNestedArray(key);
|
||||
CopyJsonArray(nested_arr, val.as<JsonArray>()); // deep copy
|
||||
} else if (val.is<JsonObject>()) {
|
||||
JsonObject &nested_obj = to.createNestedObject(key);
|
||||
CopyJsonObject(nested_obj, val.as<JsonObject>()); // deep copy
|
||||
} else {
|
||||
to.set(key, val); // general case for non array, object or string
|
||||
}
|
||||
}
|
||||
|
||||
// Shallow copy of array, we skip any sub-array or sub-object. It may be added in the future
|
||||
void CopyJsonArray(JsonArray &to, const JsonArray &arr) {
|
||||
for (auto v : arr) {
|
||||
if (v.is<char*>()) {
|
||||
String sval = v.as<String>(); // force a copy of the String value
|
||||
to.add(sval);
|
||||
} else if (v.is<JsonArray>()) {
|
||||
} else if (v.is<JsonObject>()) {
|
||||
} else {
|
||||
to.add(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Deep copy of object
|
||||
void CopyJsonObject(JsonObject &to, const JsonObject &from) {
|
||||
for (auto kv : from) {
|
||||
String key_string = kv.key;
|
||||
JsonVariant &val = kv.value;
|
||||
|
||||
CopyJsonVariant(to, key_string, val);
|
||||
}
|
||||
}
|
||||
|
||||
// does the new payload conflicts with the existing payload, i.e. values would be overwritten
|
||||
// true - one attribute (except LinkQuality) woudl be lost, there is conflict
|
||||
// false - new attributes can be safely added
|
||||
bool Z_Devices::jsonIsConflict(uint16_t shortaddr, const JsonObject &values) {
|
||||
Z_Device & device = getShortAddr(shortaddr);
|
||||
if (&values == nullptr) { return false; }
|
||||
bool Z_Devices::jsonIsConflict(uint16_t shortaddr, const Z_attribute_list &attr_list) const {
|
||||
const Z_Device & device = findShortAddr(shortaddr);
|
||||
|
||||
if (nullptr == device.json) {
|
||||
if (!foundDevice(device)) { return false; }
|
||||
if (attr_list.isEmpty()) {
|
||||
return false; // if no previous value, no conflict
|
||||
}
|
||||
|
||||
// compare groups
|
||||
// Special case for group addresses. Group attribute is only present if the target
|
||||
// address is a group address, so just comparing attributes will not work.
|
||||
// Eg: if the first packet has no group attribute, and the second does, conflict would not be detected
|
||||
// Here we explicitly compute the group address of both messages, and compare them. No group means group=0x0000
|
||||
// (we use the property of an missing attribute returning 0)
|
||||
// (note: we use .get() here which is case-sensitive. We know however that the attribute was set with the exact syntax D_CMND_ZIGBEE_GROUP, so we don't need a case-insensitive get())
|
||||
uint16_t group1 = device.json->get<unsigned int>(D_CMND_ZIGBEE_GROUP);
|
||||
uint16_t group2 = values.get<unsigned int>(D_CMND_ZIGBEE_GROUP);
|
||||
if (group1 != group2) {
|
||||
return true; // if group addresses differ, then conflict
|
||||
if (device.attr_list.isValidGroupId() && attr_list.isValidGroupId()) {
|
||||
if (device.attr_list.group_id != attr_list.group_id) { return true; } // groups are in conflict
|
||||
}
|
||||
|
||||
// parse all other parameters
|
||||
for (auto kv : values) {
|
||||
String key_string = kv.key;
|
||||
// compare src_ep
|
||||
if (device.attr_list.isValidSrcEp() && attr_list.isValidSrcEp()) {
|
||||
if (device.attr_list.src_ep != attr_list.src_ep) { return true; }
|
||||
}
|
||||
|
||||
// LQI does not count as conflicting
|
||||
|
||||
if (0 == strcasecmp_P(kv.key, PSTR(D_CMND_ZIGBEE_GROUP))) {
|
||||
// ignore group, it was handled already
|
||||
} else if (0 == strcasecmp_P(kv.key, PSTR(D_CMND_ZIGBEE_ENDPOINT))) {
|
||||
// attribute "Endpoint" or "Group"
|
||||
if (device.json->containsKey(kv.key)) {
|
||||
if (kv.value.as<unsigned int>() != device.json->get<unsigned int>(kv.key)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else if (strcasecmp_P(kv.key, PSTR(D_CMND_ZIGBEE_LINKQUALITY))) { // exception = ignore duplicates for LinkQuality
|
||||
if (device.json->containsKey(kv.key)) {
|
||||
return true; // conflict!
|
||||
// parse all other parameters
|
||||
for (const auto & attr : attr_list) {
|
||||
const Z_attribute * curr_attr = device.attr_list.findAttribute(attr);
|
||||
if (nullptr != curr_attr) {
|
||||
if (!curr_attr->equalsVal(attr)) {
|
||||
return true; // the value already exists and is different - conflict!
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Z_Devices::jsonAppend(uint16_t shortaddr, const JsonObject &values) {
|
||||
void Z_Devices::jsonAppend(uint16_t shortaddr, const Z_attribute_list &attr_list) {
|
||||
Z_Device & device = getShortAddr(shortaddr);
|
||||
if (&values == nullptr) { return; }
|
||||
|
||||
if (nullptr == device.json) {
|
||||
device.json = &(device.json_buffer->createObject());
|
||||
}
|
||||
// Prepend Device, will be removed later if redundant
|
||||
char sa[8];
|
||||
snprintf_P(sa, sizeof(sa), PSTR("0x%04X"), shortaddr);
|
||||
device.json->set(F(D_JSON_ZIGBEE_DEVICE), sa);
|
||||
// Prepend Friendly Name if it has one
|
||||
const char * fname = zigbee_devices.getFriendlyName(shortaddr);
|
||||
if (fname) {
|
||||
device.json->set(F(D_JSON_ZIGBEE_NAME), (char*) fname); // (char*) forces ArduinoJson to make a copy of the cstring
|
||||
}
|
||||
|
||||
// copy all values from 'values' to 'json'
|
||||
CopyJsonObject(*device.json, values);
|
||||
}
|
||||
|
||||
const JsonObject *Z_Devices::jsonGet(uint16_t shortaddr) {
|
||||
return getShortAddr(shortaddr).json;
|
||||
device.attr_list.mergeList(attr_list);
|
||||
}
|
||||
|
||||
void Z_Devices::jsonPublishFlush(uint16_t shortaddr) {
|
||||
Z_Device & device = getShortAddr(shortaddr);
|
||||
if (!device.valid()) { return; } // safeguard
|
||||
JsonObject & json = *device.json;
|
||||
if (&json == nullptr) { return; } // abort if nothing in buffer
|
||||
Z_attribute_list &attr_list = device.attr_list;
|
||||
|
||||
const char * fname = zigbee_devices.getFriendlyName(shortaddr);
|
||||
bool use_fname = (Settings.flag4.zigbee_use_names) && (fname); // should we replace shortaddr with friendlyname?
|
||||
if (!attr_list.isEmpty()) {
|
||||
const char * fname = zigbee_devices.getFriendlyName(shortaddr);
|
||||
bool use_fname = (Settings.flag4.zigbee_use_names) && (fname); // should we replace shortaddr with friendlyname?
|
||||
|
||||
// save parameters is global variables to be used by Rules
|
||||
gZbLastMessage.device = shortaddr; // %zbdevice%
|
||||
gZbLastMessage.groupaddr = json[F(D_CMND_ZIGBEE_GROUP)]; // %zbgroup%
|
||||
gZbLastMessage.cluster = json[F(D_CMND_ZIGBEE_CLUSTER)]; // %zbcluster%
|
||||
gZbLastMessage.endpoint = json[F(D_CMND_ZIGBEE_ENDPOINT)]; // %zbendpoint%
|
||||
// save parameters is global variables to be used by Rules
|
||||
gZbLastMessage.device = shortaddr; // %zbdevice%
|
||||
gZbLastMessage.groupaddr = attr_list.group_id; // %zbgroup%
|
||||
gZbLastMessage.endpoint = attr_list.src_ep; // %zbendpoint%
|
||||
|
||||
// dump json in string
|
||||
String msg = "";
|
||||
json.printTo(msg);
|
||||
zigbee_devices.jsonClear(shortaddr);
|
||||
|
||||
if (use_fname) {
|
||||
if (Settings.flag4.remove_zbreceived) {
|
||||
Response_P(PSTR("{\"%s\":%s}"), fname, msg.c_str());
|
||||
} else {
|
||||
Response_P(PSTR("{\"" D_JSON_ZIGBEE_RECEIVED "\":{\"%s\":%s}}"), fname, msg.c_str());
|
||||
mqtt_data[0] = 0; // clear string
|
||||
// Do we prefix with `ZbReceived`?
|
||||
if (!Settings.flag4.remove_zbreceived) {
|
||||
Response_P(PSTR("{\"" D_JSON_ZIGBEE_RECEIVED "\":"));
|
||||
}
|
||||
} else {
|
||||
if (Settings.flag4.remove_zbreceived) {
|
||||
Response_P(PSTR("{\"0x%04X\":%s}"), shortaddr, msg.c_str());
|
||||
// What key do we use, shortaddr or name?
|
||||
if (use_fname) {
|
||||
Response_P(PSTR("%s{\"%s\":{"), mqtt_data, fname);
|
||||
} else {
|
||||
Response_P(PSTR("{\"" D_JSON_ZIGBEE_RECEIVED "\":{\"0x%04X\":%s}}"), shortaddr, msg.c_str());
|
||||
Response_P(PSTR("%s{\"0x%04X\":{"), mqtt_data, shortaddr);
|
||||
}
|
||||
// Add "Device":"0x...."
|
||||
Response_P(PSTR("%s\"" D_JSON_ZIGBEE_DEVICE "\":\"0x%04X\","), mqtt_data, shortaddr);
|
||||
// Add "Name":"xxx" if name is present
|
||||
if (fname) {
|
||||
Response_P(PSTR("%s\"" D_JSON_ZIGBEE_NAME "\":\"%s\","), mqtt_data, EscapeJSONString(fname).c_str());
|
||||
}
|
||||
// Add all other attributes
|
||||
Response_P(PSTR("%s%s}}"), mqtt_data, attr_list.toString().c_str());
|
||||
|
||||
if (!Settings.flag4.remove_zbreceived) {
|
||||
Response_P(PSTR("%s}"), mqtt_data);
|
||||
}
|
||||
// AddLog_P2(LOG_LEVEL_INFO, PSTR(">>> %s"), mqtt_data); // TODO
|
||||
attr_list.reset(); // clear the attributes
|
||||
|
||||
if (Settings.flag4.zigbee_distinct_topics) {
|
||||
char subtopic[16];
|
||||
snprintf_P(subtopic, sizeof(subtopic), PSTR("%04X/" D_RSLT_SENSOR), shortaddr);
|
||||
MqttPublishPrefixTopic_P(TELE, subtopic, Settings.flag.mqtt_sensor_retain);
|
||||
} else {
|
||||
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR), Settings.flag.mqtt_sensor_retain);
|
||||
}
|
||||
XdrvRulesProcess(); // apply rules
|
||||
}
|
||||
if (Settings.flag4.zigbee_distinct_topics) {
|
||||
char subtopic[16];
|
||||
snprintf_P(subtopic, sizeof(subtopic), PSTR("%04X/" D_RSLT_SENSOR), shortaddr);
|
||||
MqttPublishPrefixTopic_P(TELE, subtopic, Settings.flag.mqtt_sensor_retain);
|
||||
} else {
|
||||
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR), Settings.flag.mqtt_sensor_retain);
|
||||
}
|
||||
XdrvRulesProcess(); // apply rules
|
||||
}
|
||||
|
||||
void Z_Devices::jsonPublishNow(uint16_t shortaddr, JsonObject & values) {
|
||||
void Z_Devices::jsonPublishNow(uint16_t shortaddr, Z_attribute_list &attr_list) {
|
||||
jsonPublishFlush(shortaddr); // flush any previous buffer
|
||||
jsonAppend(shortaddr, values);
|
||||
jsonAppend(shortaddr, attr_list);
|
||||
jsonPublishFlush(shortaddr); // publish now
|
||||
}
|
||||
|
||||
|
@ -1044,9 +922,8 @@ String Z_Devices::dumpLightState(uint16_t shortaddr) const {
|
|||
JsonObject& json = jsonBuffer.createObject();
|
||||
char hex[8];
|
||||
|
||||
int32_t found = findShortAddrIdx(shortaddr);
|
||||
if (found >= 0) {
|
||||
const Z_Device & device = devicesAt(found);
|
||||
const Z_Device & device = findShortAddr(shortaddr);
|
||||
if (foundDevice(device)) {
|
||||
const char * fname = getFriendlyName(shortaddr);
|
||||
|
||||
bool use_fname = (Settings.flag4.zigbee_use_names) && (fname); // should we replace shortaddr with friendlyname?
|
||||
|
@ -1090,8 +967,7 @@ String Z_Devices::dump(uint32_t dump_mode, uint16_t status_shortaddr) const {
|
|||
JsonArray& json = jsonBuffer.createArray();
|
||||
JsonArray& devices = json;
|
||||
|
||||
for (std::vector<Z_Device*>::const_iterator it = _devices.begin(); it != _devices.end(); ++it) {
|
||||
const Z_Device &device = **it;
|
||||
for (const auto & device : _devices) {
|
||||
uint16_t shortaddr = device.shortaddr;
|
||||
char hex[22];
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -328,13 +328,8 @@ void sendHueUpdate(uint16_t shortaddr, uint16_t groupaddr, uint16_t cluster, uin
|
|||
|
||||
|
||||
// Parse a cluster specific command, and try to convert into human readable
|
||||
void convertClusterSpecific(JsonObject& json, uint16_t cluster, uint8_t cmd, bool direction, uint16_t shortaddr, uint8_t srcendpoint, const SBuffer &payload) {
|
||||
size_t hex_char_len = payload.len()*2+2;
|
||||
char *hex_char = (char*) malloc(hex_char_len);
|
||||
if (!hex_char) { return; }
|
||||
ToHex_P((unsigned char*)payload.getBuffer(), payload.len(), hex_char, hex_char_len);
|
||||
|
||||
const __FlashStringHelper* command_name = nullptr;
|
||||
void convertClusterSpecific(class Z_attribute_list &attr_list, uint16_t cluster, uint8_t cmd, bool direction, uint16_t shortaddr, uint8_t srcendpoint, const SBuffer &payload) {
|
||||
const char * command_name = nullptr;
|
||||
uint8_t conv_direction;
|
||||
Z_XYZ_Var xyz;
|
||||
|
||||
|
@ -373,7 +368,7 @@ void convertClusterSpecific(JsonObject& json, uint16_t cluster, uint8_t cmd, boo
|
|||
p += 2;
|
||||
}
|
||||
if (match) {
|
||||
command_name = (const __FlashStringHelper*) (Z_strings + pgm_read_word(&conv->tasmota_cmd_offset));
|
||||
command_name = Z_strings + pgm_read_word(&conv->tasmota_cmd_offset);
|
||||
parseXYZ(Z_strings + pgm_read_word(&conv->param_offset), payload, &xyz);
|
||||
if (0xFF == conv_cmd) {
|
||||
// shift all values
|
||||
|
@ -396,112 +391,105 @@ void convertClusterSpecific(JsonObject& json, uint16_t cluster, uint8_t cmd, boo
|
|||
// Format: "0004<00": "00" = "<cluster><<cmd>": "<payload>" for commands to devices
|
||||
char attrid_str[12];
|
||||
snprintf_P(attrid_str, sizeof(attrid_str), PSTR("%04X%c%02X"), cluster, direction ? '<' : '!', cmd);
|
||||
json[attrid_str] = hex_char;
|
||||
free(hex_char);
|
||||
attr_list.addAttribute(attrid_str).setBuf(payload, 0, payload.len());
|
||||
|
||||
if (command_name) {
|
||||
// Now try to transform into a human readable format
|
||||
String command_name2 = String(command_name);
|
||||
// if (direction & 0x80) then specific transform
|
||||
if (conv_direction & 0x80) {
|
||||
// TODO need to create a specific command
|
||||
uint32_t cccc00mm = (cluster << 16) | cmd; // format = cccc00mm, cccc = cluster, mm = command
|
||||
// IAS
|
||||
if ((cluster == 0x0500) && (cmd == 0x00)) {
|
||||
// "ZoneStatusChange"
|
||||
json[command_name] = xyz.x;
|
||||
switch (cccc00mm) {
|
||||
case 0x05000000: // "ZoneStatusChange"
|
||||
attr_list.addAttribute(command_name, true).setUInt(xyz.x);
|
||||
if (0 != xyz.y) {
|
||||
json[command_name2 + F("Ext")] = xyz.y;
|
||||
attr_list.addAttribute(command_name, PSTR("Ext")).setUInt(xyz.y);
|
||||
}
|
||||
if ((0 != xyz.z) && (0xFF != xyz.z)) {
|
||||
json[command_name2 + F("Zone")] = xyz.z;
|
||||
attr_list.addAttribute(command_name, PSTR("Zone")).setUInt(xyz.z);
|
||||
}
|
||||
} else if ((cluster == 0x0004) && ((cmd == 0x00) || (cmd == 0x01) || (cmd == 0x03))) {
|
||||
// AddGroupResp or ViewGroupResp (group name ignored) or RemoveGroup
|
||||
json[command_name] = xyz.y;
|
||||
json[command_name2 + F("Status")] = xyz.x;
|
||||
json[command_name2 + F("StatusMsg")] = getZigbeeStatusMessage(xyz.x);
|
||||
} else if ((cluster == 0x0004) && (cmd == 0x02)) {
|
||||
// GetGroupResp
|
||||
json[command_name2 + F("Capacity")] = xyz.x;
|
||||
json[command_name2 + F("Count")] = xyz.y;
|
||||
JsonArray &arr = json.createNestedArray(command_name);
|
||||
for (uint32_t i = 0; i < xyz.y; i++) {
|
||||
arr.add(payload.get16(2 + 2*i));
|
||||
break;
|
||||
case 0x00040000:
|
||||
case 0x00040001:
|
||||
case 0x00040003: // AddGroupResp or ViewGroupResp (group name ignored) or RemoveGroup
|
||||
attr_list.addAttribute(command_name, true).setUInt(xyz.y);
|
||||
attr_list.addAttribute(command_name, PSTR("Status")).setUInt(xyz.x);
|
||||
attr_list.addAttribute(command_name, PSTR("StatusMsg")).setStr(getZigbeeStatusMessage(xyz.x).c_str());
|
||||
break;
|
||||
case 0x00040002: // GetGroupResp
|
||||
attr_list.addAttribute(command_name, PSTR("Capacity")).setUInt(xyz.x);
|
||||
attr_list.addAttribute(command_name, PSTR("Count")).setUInt(xyz.y);
|
||||
{
|
||||
|
||||
Z_json_array group_list;
|
||||
for (uint32_t i = 0; i < xyz.y; i++) {
|
||||
group_list.add(payload.get16(2 + 2*i));
|
||||
}
|
||||
attr_list.addAttribute(command_name, true).setStrRaw(group_list.toString().c_str());
|
||||
}
|
||||
} else if ((cluster == 0x0005) && ((cmd == 0x00) || (cmd == 0x02) || (cmd == 0x03))) {
|
||||
// AddScene or RemoveScene or StoreScene
|
||||
json[command_name2 + F("Status")] = xyz.x;
|
||||
json[command_name2 + F("StatusMsg")] = getZigbeeStatusMessage(xyz.x);
|
||||
json[F("GroupId")] = xyz.y;
|
||||
json[F("SceneId")] = xyz.z;
|
||||
} else if ((cluster == 0x0005) && (cmd == 0x01)) {
|
||||
// ViewScene
|
||||
json[command_name2 + F("Status")] = xyz.x;
|
||||
json[command_name2 + F("StatusMsg")] = getZigbeeStatusMessage(xyz.x);
|
||||
json[F("GroupId")] = xyz.y;
|
||||
json[F("SceneId")] = xyz.z;
|
||||
String scene_payload = json[attrid_str];
|
||||
json[F("ScenePayload")] = scene_payload.substring(8); // remove first 8 characters
|
||||
} else if ((cluster == 0x0005) && (cmd == 0x03)) {
|
||||
// RemoveAllScenes
|
||||
json[command_name2 + F("Status")] = xyz.x;
|
||||
json[command_name2 + F("StatusMsg")] = getZigbeeStatusMessage(xyz.x);
|
||||
json[F("GroupId")] = xyz.y;
|
||||
} else if ((cluster == 0x0005) && (cmd == 0x06)) {
|
||||
// GetSceneMembership
|
||||
json[command_name2 + F("Status")] = xyz.x;
|
||||
json[command_name2 + F("StatusMsg")] = getZigbeeStatusMessage(xyz.x);
|
||||
json[F("Capacity")] = xyz.y;
|
||||
json[F("GroupId")] = xyz.z;
|
||||
String scene_payload = json[attrid_str];
|
||||
json[F("ScenePayload")] = scene_payload.substring(8); // remove first 8 characters
|
||||
} else if ((cluster == 0x0006) && (cmd == 0x40)) {
|
||||
// Power Off With Effect
|
||||
json[F("Power")] = 0; // always "Power":0
|
||||
json[F("PowerEffect")] = xyz.x;
|
||||
json[F("PowerEffectVariant")] = xyz.y;
|
||||
} else if ((cluster == 0x0006) && (cmd == 0x41)) {
|
||||
// Power On With Recall Global Scene
|
||||
json[F("Power")] = 1; // always "Power":1
|
||||
json[F("PowerRecallGlobalScene")] = true;
|
||||
} else if ((cluster == 0x0006) && (cmd == 0x42)) {
|
||||
// Power On With Timed Off Command
|
||||
json[F("Power")] = 1; // always "Power":1
|
||||
json[F("PowerOnlyWhenOn")] = xyz.x;
|
||||
json[F("PowerOnTime")] = xyz.y / 10.0f;
|
||||
json[F("PowerOffWait")] = xyz.z / 10.0f;
|
||||
break;
|
||||
case 0x00050000:
|
||||
case 0x00050001: // ViewScene
|
||||
case 0x00050002:
|
||||
case 0x00050004: // AddScene or RemoveScene or StoreScene
|
||||
attr_list.addAttribute(command_name, PSTR("Status")).setUInt(xyz.x);
|
||||
attr_list.addAttribute(command_name, PSTR("StatusMsg")).setStr(getZigbeeStatusMessage(xyz.x).c_str());
|
||||
attr_list.addAttribute(PSTR("GroupId"), true).setUInt(xyz.y);
|
||||
attr_list.addAttribute(PSTR("SceneId"), true).setUInt(xyz.z);
|
||||
if (0x00050001 == cccc00mm) { // ViewScene specific
|
||||
attr_list.addAttribute(PSTR("ScenePayload"), true).setBuf(payload, 4, payload.len()-4); // remove first 4 bytes
|
||||
}
|
||||
break;
|
||||
case 0x00050003: // RemoveAllScenes
|
||||
attr_list.addAttribute(command_name, PSTR("Status")).setUInt(xyz.x);
|
||||
attr_list.addAttribute(command_name, PSTR("StatusMsg")).setStr(getZigbeeStatusMessage(xyz.x).c_str());
|
||||
attr_list.addAttribute(PSTR("GroupId"), true).setUInt(xyz.y);
|
||||
break;
|
||||
case 0x00050006: // GetSceneMembership
|
||||
attr_list.addAttribute(command_name, PSTR("Status")).setUInt(xyz.x);
|
||||
attr_list.addAttribute(command_name, PSTR("StatusMsg")).setStr(getZigbeeStatusMessage(xyz.x).c_str());
|
||||
attr_list.addAttribute(PSTR("Capacity"), true).setUInt(xyz.y);
|
||||
attr_list.addAttribute(PSTR("GroupId"), true).setUInt(xyz.z);
|
||||
attr_list.addAttribute(PSTR("ScenePayload"), true).setBuf(payload, 4, payload.len()-4); // remove first 4 bytes
|
||||
break;
|
||||
case 0x00060040: // Power Off With Effect
|
||||
attr_list.addAttribute(PSTR("Power"), true).setUInt(0);
|
||||
attr_list.addAttribute(PSTR("PowerEffect"), true).setUInt(xyz.x);
|
||||
attr_list.addAttribute(PSTR("PowerEffectVariant"), true).setUInt(xyz.y);
|
||||
break;
|
||||
case 0x00060041: // Power On With Recall Global Scene
|
||||
attr_list.addAttribute(PSTR("Power"), true).setUInt(1);
|
||||
attr_list.addAttribute(PSTR("PowerRecallGlobalScene"), true).setBool(true);
|
||||
break;
|
||||
case 0x00060042: // Power On With Timed Off Command
|
||||
attr_list.addAttribute(PSTR("Power"), true).setUInt(1);
|
||||
attr_list.addAttribute(PSTR("PowerOnlyWhenOn"), true).setUInt(xyz.x);
|
||||
attr_list.addAttribute(PSTR("PowerOnTime"), true).setFloat(xyz.y / 10.0f);
|
||||
attr_list.addAttribute(PSTR("PowerOffWait"), true).setFloat(xyz.z / 10.0f);
|
||||
break;
|
||||
}
|
||||
} else { // general case
|
||||
bool extended_command = false; // do we send command with endpoint suffix
|
||||
// do we send command with endpoint suffix
|
||||
char command_suffix[4] = { 0x00 }; // empty string by default
|
||||
// if SO101 and multiple endpoints, append endpoint number
|
||||
if (Settings.flag4.zb_index_ep) {
|
||||
if (zigbee_devices.countEndpoints(shortaddr) > 0) {
|
||||
command_name2 += srcendpoint;
|
||||
extended_command = true;
|
||||
snprintf_P(command_suffix, sizeof(command_suffix), PSTR("%d"), srcendpoint);
|
||||
}
|
||||
}
|
||||
if (0 == xyz.x_type) {
|
||||
json[command_name] = true; // no parameter
|
||||
if (extended_command) { json[command_name2] = true; }
|
||||
attr_list.addAttribute(command_name, command_suffix).setBool(true);
|
||||
} else if (0 == xyz.y_type) {
|
||||
json[command_name] = xyz.x; // 1 parameter
|
||||
if (extended_command) { json[command_name2] = xyz.x; }
|
||||
attr_list.addAttribute(command_name, command_suffix).setUInt(xyz.x);
|
||||
} else {
|
||||
// multiple answers, create an array
|
||||
JsonArray &arr = json.createNestedArray(command_name);
|
||||
Z_json_array arr;
|
||||
arr.add(xyz.x);
|
||||
arr.add(xyz.y);
|
||||
if (xyz.z_type) {
|
||||
arr.add(xyz.z);
|
||||
}
|
||||
if (extended_command) {
|
||||
JsonArray &arr = json.createNestedArray(command_name2);
|
||||
arr.add(xyz.x);
|
||||
arr.add(xyz.y);
|
||||
if (xyz.z_type) {
|
||||
arr.add(xyz.z);
|
||||
}
|
||||
}
|
||||
attr_list.addAttribute(command_name, command_suffix).setStrRaw(arr.toString().c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1011,53 +1011,53 @@ int32_t EZ_ReceiveTCJoinHandler(int32_t res, const class SBuffer &buf) {
|
|||
// Parse incoming ZCL message.
|
||||
//
|
||||
// This code is common to ZNP and EZSP
|
||||
void Z_IncomingMessage(ZCLFrame &zcl_received) {
|
||||
void Z_IncomingMessage(class ZCLFrame &zcl_received) {
|
||||
uint16_t srcaddr = zcl_received.getSrcAddr();
|
||||
uint16_t groupid = zcl_received.getGroupAddr();
|
||||
uint16_t clusterid = zcl_received.getClusterId();
|
||||
uint8_t linkquality = zcl_received.getLinkQuality();
|
||||
uint8_t srcendpoint = zcl_received.getSrcEndpoint();
|
||||
linkquality = linkquality != 0xFF ? linkquality : 0xFE; // avoid 0xFF (reserved for unknown)
|
||||
|
||||
bool defer_attributes = false; // do we defer attributes reporting to coalesce
|
||||
|
||||
// log the packet details
|
||||
zcl_received.log();
|
||||
|
||||
zigbee_devices.setLQI(srcaddr, linkquality != 0xFF ? linkquality : 0xFE); // EFR32 has a different scale for LQI
|
||||
zigbee_devices.setLQI(srcaddr, linkquality); // EFR32 has a different scale for LQI
|
||||
|
||||
char shortaddr[8];
|
||||
snprintf_P(shortaddr, sizeof(shortaddr), PSTR("0x%04X"), srcaddr);
|
||||
|
||||
DynamicJsonBuffer jsonBuffer;
|
||||
JsonObject& json = jsonBuffer.createObject();
|
||||
Z_attribute_list attr_list;
|
||||
attr_list.lqi = linkquality;
|
||||
attr_list.src_ep = srcendpoint;
|
||||
if (groupid) { // TODO we miss the group_id == 0 here
|
||||
attr_list.group_id = groupid;
|
||||
}
|
||||
|
||||
if ( (!zcl_received.isClusterSpecificCommand()) && (ZCL_DEFAULT_RESPONSE == zcl_received.getCmdId())) {
|
||||
zcl_received.parseResponse(); // Zigbee general "Degault Response", publish ZbResponse message
|
||||
zcl_received.parseResponse(); // Zigbee general "Default Response", publish ZbResponse message
|
||||
} else {
|
||||
// Build the ZbReceive json
|
||||
// Build the ZbReceive list
|
||||
if ( (!zcl_received.isClusterSpecificCommand()) && (ZCL_REPORT_ATTRIBUTES == zcl_received.getCmdId())) {
|
||||
zcl_received.parseReportAttributes(json); // Zigbee report attributes from sensors
|
||||
zcl_received.parseReportAttributes(attr_list); // Zigbee report attributes from sensors
|
||||
if (clusterid) { defer_attributes = true; } // don't defer system Cluster=0 messages
|
||||
} else if ( (!zcl_received.isClusterSpecificCommand()) && (ZCL_READ_ATTRIBUTES_RESPONSE == zcl_received.getCmdId())) {
|
||||
zcl_received.parseReadAttributesResponse(json);
|
||||
zcl_received.parseReadAttributesResponse(attr_list);
|
||||
if (clusterid) { defer_attributes = true; } // don't defer system Cluster=0 messages
|
||||
} else if ( (!zcl_received.isClusterSpecificCommand()) && (ZCL_READ_ATTRIBUTES == zcl_received.getCmdId())) {
|
||||
zcl_received.parseReadAttributes(json);
|
||||
zcl_received.parseReadAttributes(attr_list);
|
||||
// never defer read_attributes, so the auto-responder can send response back on a per cluster basis
|
||||
} else if ( (!zcl_received.isClusterSpecificCommand()) && (ZCL_READ_REPORTING_CONFIGURATION_RESPONSE == zcl_received.getCmdId())) {
|
||||
zcl_received.parseReadConfigAttributes(json);
|
||||
zcl_received.parseReadConfigAttributes(attr_list);
|
||||
} else if ( (!zcl_received.isClusterSpecificCommand()) && (ZCL_CONFIGURE_REPORTING_RESPONSE == zcl_received.getCmdId())) {
|
||||
zcl_received.parseConfigAttributes(json);
|
||||
zcl_received.parseConfigAttributes(attr_list);
|
||||
} else if (zcl_received.isClusterSpecificCommand()) {
|
||||
zcl_received.parseClusterSpecificCommand(json);
|
||||
zcl_received.parseClusterSpecificCommand(attr_list);
|
||||
}
|
||||
|
||||
{ // fence to force early de-allocation of msg
|
||||
String msg("");
|
||||
msg.reserve(100);
|
||||
json.printTo(msg);
|
||||
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_ZIGBEE D_JSON_ZIGBEEZCL_RAW_RECEIVED ": {\"0x%04X\":%s}"), srcaddr, msg.c_str());
|
||||
}
|
||||
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_ZIGBEE D_JSON_ZIGBEEZCL_RAW_RECEIVED ": {\"0x%04X\":{%s}}"), srcaddr, attr_list.toString().c_str());
|
||||
|
||||
// discard the message if it was sent by us (broadcast or group loopback)
|
||||
if (srcaddr == localShortAddr) {
|
||||
|
@ -1065,37 +1065,25 @@ void Z_IncomingMessage(ZCLFrame &zcl_received) {
|
|||
return; // abort the rest of message management
|
||||
}
|
||||
|
||||
zcl_received.postProcessAttributes(srcaddr, json);
|
||||
// Add Endpoint
|
||||
json[F(D_CMND_ZIGBEE_ENDPOINT)] = srcendpoint;
|
||||
// Add Group if non-zero
|
||||
if (groupid) {
|
||||
json[F(D_CMND_ZIGBEE_GROUP)] = groupid;
|
||||
}
|
||||
// Add linkquality
|
||||
json[F(D_CMND_ZIGBEE_LINKQUALITY)] = linkquality;
|
||||
zcl_received.generateSyntheticAttributes(attr_list);
|
||||
zcl_received.generateCallBacks(attr_list); // set deferred callbacks, ex: Occupancy
|
||||
zcl_received.postProcessAttributes(srcaddr, attr_list);
|
||||
|
||||
// since we just receveived data from the device, it is reachable
|
||||
zigbee_devices.resetTimersForDevice(srcaddr, 0 /* groupaddr */, Z_CAT_REACHABILITY); // remove any reachability timer already there
|
||||
zigbee_devices.setReachable(srcaddr, true); // mark device as reachable
|
||||
|
||||
// Post-provess for Aqara Presence Senson
|
||||
Z_AqaraOccupancy(srcaddr, clusterid, srcendpoint, json);
|
||||
|
||||
if (defer_attributes) {
|
||||
// Prepare for publish
|
||||
if (zigbee_devices.jsonIsConflict(srcaddr, json)) {
|
||||
if (zigbee_devices.jsonIsConflict(srcaddr, attr_list)) {
|
||||
// there is conflicting values, force a publish of the previous message now and don't coalesce
|
||||
zigbee_devices.jsonPublishFlush(srcaddr);
|
||||
}
|
||||
zigbee_devices.jsonAppend(srcaddr, json);
|
||||
zigbee_devices.jsonAppend(srcaddr, attr_list);
|
||||
zigbee_devices.setTimer(srcaddr, 0 /* groupaddr */, USE_ZIGBEE_COALESCE_ATTR_TIMER, clusterid, srcendpoint, Z_CAT_READ_ATTR, 0, &Z_PublishAttributes);
|
||||
} else {
|
||||
// Publish immediately
|
||||
zigbee_devices.jsonPublishNow(srcaddr, json);
|
||||
|
||||
// Add auto-responder here
|
||||
Z_AutoResponder(srcaddr, clusterid, srcendpoint, json[F("ReadNames")]);
|
||||
zigbee_devices.jsonPublishNow(srcaddr, attr_list);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1281,30 +1269,8 @@ int32_t EZ_Recv_Default(int32_t res, const class SBuffer &buf) {
|
|||
* Callbacks
|
||||
\*********************************************************************************************/
|
||||
|
||||
|
||||
// Aqara Occupancy behavior: the Aqara device only sends Occupancy: true events every 60 seconds.
|
||||
// Here we add a timer so if we don't receive a Occupancy event for 90 seconds, we send Occupancy:false
|
||||
void Z_AqaraOccupancy(uint16_t shortaddr, uint16_t cluster, uint8_t endpoint, const JsonObject &json) {
|
||||
static const uint32_t OCCUPANCY_TIMEOUT = 90 * 1000; // 90 s
|
||||
// Read OCCUPANCY value if any
|
||||
const JsonVariant &val_endpoint = GetCaseInsensitive(json, PSTR(OCCUPANCY));
|
||||
if (nullptr != &val_endpoint) {
|
||||
uint32_t occupancy = strToUInt(val_endpoint);
|
||||
|
||||
if (occupancy) {
|
||||
zigbee_devices.setTimer(shortaddr, 0 /* groupaddr */, OCCUPANCY_TIMEOUT, cluster, endpoint, Z_CAT_VIRTUAL_OCCUPANCY, 0, &Z_OccupancyCallback);
|
||||
} else {
|
||||
zigbee_devices.resetTimersForDevice(shortaddr, 0 /* groupaddr */, Z_CAT_VIRTUAL_OCCUPANCY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Publish the received values once they have been coalesced
|
||||
int32_t Z_PublishAttributes(uint16_t shortaddr, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint, uint32_t value) {
|
||||
const JsonObject *json = zigbee_devices.jsonGet(shortaddr);
|
||||
if (json == nullptr) { return 0; } // don't crash if not found
|
||||
|
||||
zigbee_devices.jsonPublishFlush(shortaddr);
|
||||
return 1;
|
||||
}
|
||||
|
@ -1476,49 +1442,61 @@ int32_t Z_State_Ready(uint8_t value) {
|
|||
//
|
||||
// Mostly used for routers/end-devices
|
||||
// json: holds the attributes in JSON format
|
||||
void Z_AutoResponder(uint16_t srcaddr, uint16_t cluster, uint8_t endpoint, const JsonObject &json) {
|
||||
void Z_AutoResponder(uint16_t srcaddr, uint16_t cluster, uint8_t endpoint, const uint16_t *attr_list, size_t attr_len) {
|
||||
DynamicJsonBuffer jsonBuffer;
|
||||
JsonObject& json_out = jsonBuffer.createObject();
|
||||
|
||||
// responder
|
||||
switch (cluster) {
|
||||
case 0x0000:
|
||||
if (HasKeyCaseInsensitive(json, PSTR("ModelId"))) { json_out[F("ModelId")] = F(USE_ZIGBEE_MODELID); }
|
||||
if (HasKeyCaseInsensitive(json, PSTR("Manufacturer"))) { json_out[F("Manufacturer")] = F(USE_ZIGBEE_MANUFACTURER); }
|
||||
break;
|
||||
for (uint32_t i=0; i<attr_len; i++) {
|
||||
uint16_t attr = attr_list[i];
|
||||
uint32_t ccccaaaa = (cluster << 16) || attr;
|
||||
|
||||
switch (ccccaaaa) {
|
||||
case 0x00000004: json_out[F("Manufacturer")] = F(USE_ZIGBEE_MANUFACTURER); break; // Manufacturer
|
||||
case 0x00000005: json_out[F("ModelId")] = F(USE_ZIGBEE_MODELID); break; // ModelId
|
||||
#ifdef USE_LIGHT
|
||||
case 0x0006:
|
||||
if (HasKeyCaseInsensitive(json, PSTR("Power"))) { json_out[F("Power")] = Light.power ? 1 : 0; }
|
||||
break;
|
||||
case 0x0008:
|
||||
if (HasKeyCaseInsensitive(json, PSTR("Dimmer"))) { json_out[F("Dimmer")] = LightGetDimmer(0); }
|
||||
break;
|
||||
case 0x0300:
|
||||
{
|
||||
uint16_t hue;
|
||||
uint8_t sat;
|
||||
float XY[2];
|
||||
LightGetHSB(&hue, &sat, nullptr);
|
||||
LightGetXY(&XY[0], &XY[1]);
|
||||
uint16_t uxy[2];
|
||||
for (uint32_t i = 0; i < ARRAY_SIZE(XY); i++) {
|
||||
uxy[i] = XY[i] * 65536.0f;
|
||||
uxy[i] = (uxy[i] > 0xFEFF) ? uxy[i] : 0xFEFF;
|
||||
}
|
||||
if (HasKeyCaseInsensitive(json, PSTR("Hue"))) { json_out[F("Hue")] = changeUIntScale(hue, 0, 360, 0, 254); }
|
||||
if (HasKeyCaseInsensitive(json, PSTR("Sat"))) { json_out[F("Sat")] = changeUIntScale(sat, 0, 255, 0, 254); }
|
||||
if (HasKeyCaseInsensitive(json, PSTR("CT"))) { json_out[F("CT")] = LightGetColorTemp(); }
|
||||
if (HasKeyCaseInsensitive(json, PSTR("X"))) { json_out[F("X")] = uxy[0]; }
|
||||
if (HasKeyCaseInsensitive(json, PSTR("Y"))) { json_out[F("Y")] = uxy[1]; }
|
||||
}
|
||||
break;
|
||||
case 0x00060000: json_out[F("Power")] = Light.power ? 1 : 0; break; // Power
|
||||
case 0x00080000: json_out[F("Dimmer")] = LightGetDimmer(0); break; // Dimmer
|
||||
|
||||
case 0x03000000: // Hue
|
||||
case 0x03000001: // Sat
|
||||
case 0x03000003: // X
|
||||
case 0x03000004: // Y
|
||||
case 0x03000007: // CT
|
||||
{
|
||||
uint16_t hue;
|
||||
uint8_t sat;
|
||||
float XY[2];
|
||||
LightGetHSB(&hue, &sat, nullptr);
|
||||
LightGetXY(&XY[0], &XY[1]);
|
||||
uint16_t uxy[2];
|
||||
for (uint32_t i = 0; i < ARRAY_SIZE(XY); i++) {
|
||||
uxy[i] = XY[i] * 65536.0f;
|
||||
uxy[i] = (uxy[i] > 0xFEFF) ? uxy[i] : 0xFEFF;
|
||||
}
|
||||
if (0x0000 == attr) { json_out[F("Hue")] = changeUIntScale(hue, 0, 360, 0, 254); }
|
||||
if (0x0001 == attr) { json_out[F("Sat")] = changeUIntScale(sat, 0, 255, 0, 254); }
|
||||
if (0x0003 == attr) { json_out[F("X")] = uxy[0]; }
|
||||
if (0x0004 == attr) { json_out[F("Y")] = uxy[1]; }
|
||||
if (0x0007 == attr) { json_out[F("CT")] = LightGetColorTemp(); }
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
case 0x000A: // Time
|
||||
if (HasKeyCaseInsensitive(json, PSTR("Time"))) { json_out[F("Time")] = (Rtc.utc_time > (60 * 60 * 24 * 365 * 10)) ? Rtc.utc_time - 946684800 : Rtc.utc_time; }
|
||||
if (HasKeyCaseInsensitive(json, PSTR("TimeEpoch"))) { json_out[F("TimeEpoch")] = Rtc.utc_time; }
|
||||
if (HasKeyCaseInsensitive(json, PSTR("TimeStatus"))) { json_out[F("TimeStatus")] = (Rtc.utc_time > (60 * 60 * 24 * 365 * 10)) ? 0x02 : 0x00; } // if time is beyond 2010 then we are synchronized
|
||||
if (HasKeyCaseInsensitive(json, PSTR("TimeZone"))) { json_out[F("TimeZone")] = Settings.toffset[0] * 60; } // seconds
|
||||
break;
|
||||
case 0x000A0000: // Time
|
||||
json_out[F("Time")] = (Rtc.utc_time > (60 * 60 * 24 * 365 * 10)) ? Rtc.utc_time - 946684800 : Rtc.utc_time;
|
||||
break;
|
||||
case 0x000AFF00: // TimeEpoch - Tasmota specific
|
||||
json_out[F("TimeEpoch")] = Rtc.utc_time;
|
||||
break;
|
||||
case 0x000A0001: // TimeStatus
|
||||
json_out[F("TimeStatus")] = (Rtc.utc_time > (60 * 60 * 24 * 365 * 10)) ? 0x02 : 0x00; // if time is beyond 2010 then we are synchronized
|
||||
break;
|
||||
case 0x000A0002: // TimeZone
|
||||
json_out[F("TimeZone")] = Settings.toffset[0] * 60;
|
||||
break;
|
||||
case 0x000A0007: // LocalTime // TODO take DST
|
||||
json_out[F("LocalTime")] = Settings.toffset[0] * 60 + ((Rtc.utc_time > (60 * 60 * 24 * 365 * 10)) ? Rtc.utc_time - 946684800 : Rtc.utc_time);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (json_out.size() > 0) {
|
||||
|
|
|
@ -795,7 +795,7 @@ void ZbBindUnbind(bool unbind) { // false = bind, true = unbind
|
|||
if (nullptr != &val_cluster) {
|
||||
cluster = strToUInt(val_cluster); // first convert as number
|
||||
if (0 == cluster) {
|
||||
zigbeeFindAttributeByName(val_cluster.as<const char*>(), &cluster, nullptr, nullptr, nullptr);
|
||||
zigbeeFindAttributeByName(val_cluster.as<const char*>(), &cluster, nullptr, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue