mirror of https://github.com/arendst/Tasmota.git
Zigbee tuya phase 2
This commit is contained in:
parent
95c0274d59
commit
80d7922977
|
@ -79,6 +79,13 @@ public:
|
||||||
}
|
}
|
||||||
return _buf->len;
|
return _buf->len;
|
||||||
}
|
}
|
||||||
|
size_t add16BigEndian(const uint16_t data) { // append 16 bits value
|
||||||
|
if (_buf->len < _buf->size - 1) { // do we have room for 2 bytes
|
||||||
|
_buf->buf[_buf->len++] = data >> 8;
|
||||||
|
_buf->buf[_buf->len++] = data;
|
||||||
|
}
|
||||||
|
return _buf->len;
|
||||||
|
}
|
||||||
size_t add32(const uint32_t data) { // append 32 bits value
|
size_t add32(const uint32_t data) { // append 32 bits value
|
||||||
if (_buf->len < _buf->size - 3) { // do we have room for 4 bytes
|
if (_buf->len < _buf->size - 3) { // do we have room for 4 bytes
|
||||||
_buf->buf[_buf->len++] = data;
|
_buf->buf[_buf->len++] = data;
|
||||||
|
@ -88,6 +95,15 @@ public:
|
||||||
}
|
}
|
||||||
return _buf->len;
|
return _buf->len;
|
||||||
}
|
}
|
||||||
|
size_t add32BigEndian(const uint32_t data) { // append 32 bits value
|
||||||
|
if (_buf->len < _buf->size - 3) { // do we have room for 4 bytes
|
||||||
|
_buf->buf[_buf->len++] = data >> 24;
|
||||||
|
_buf->buf[_buf->len++] = data >> 16;
|
||||||
|
_buf->buf[_buf->len++] = data >> 8;
|
||||||
|
_buf->buf[_buf->len++] = data;
|
||||||
|
}
|
||||||
|
return _buf->len;
|
||||||
|
}
|
||||||
size_t add64(const uint64_t data) { // append 64 bits value
|
size_t add64(const uint64_t data) { // append 64 bits value
|
||||||
if (_buf->len < _buf->size - 7) { // do we have room for 8 bytes
|
if (_buf->len < _buf->size - 7) { // do we have room for 8 bytes
|
||||||
_buf->buf[_buf->len++] = data;
|
_buf->buf[_buf->len++] = data;
|
||||||
|
|
|
@ -25,9 +25,9 @@ class ZigbeeZCLSendMessage {
|
||||||
public:
|
public:
|
||||||
uint16_t shortaddr;
|
uint16_t shortaddr;
|
||||||
uint16_t groupaddr;
|
uint16_t groupaddr;
|
||||||
uint16_t clusterId;
|
uint16_t cluster;
|
||||||
uint8_t endpoint;
|
uint8_t endpoint;
|
||||||
uint8_t cmdId;
|
uint8_t cmd;
|
||||||
uint16_t manuf;
|
uint16_t manuf;
|
||||||
bool clusterSpecific;
|
bool clusterSpecific;
|
||||||
bool needResponse;
|
bool needResponse;
|
||||||
|
|
|
@ -40,7 +40,11 @@ enum Z_DataTypes {
|
||||||
ZToD = 0xE0, Zdate = 0xE1, ZUTC = 0xE2,
|
ZToD = 0xE0, Zdate = 0xE1, ZUTC = 0xE2,
|
||||||
ZclusterId = 0xE8, ZattribId = 0xE9, ZbacOID = 0xEA,
|
ZclusterId = 0xE8, ZattribId = 0xE9, ZbacOID = 0xEA,
|
||||||
ZEUI64 = 0xF0, Zkey128 = 0xF1,
|
ZEUI64 = 0xF0, Zkey128 = 0xF1,
|
||||||
Zunk = 0xFF
|
Zunk = 0xFF,
|
||||||
|
// adding fake type for Tuya specific encodings
|
||||||
|
Ztuya1 = 0x80, // 1 byte unsigned int, Big Endian when output (input is taken care of)
|
||||||
|
Ztuya2 = 0x81, // 2 bytes unsigned, Big Endian when output (input is taken care of)
|
||||||
|
Ztuya4 = 0x82, // 4 bytes signed, Big Endian when output (input is taken care of)
|
||||||
};
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
|
@ -61,13 +65,18 @@ uint8_t Z_getDatatypeLen(uint8_t t) {
|
||||||
case Zsemi:
|
case Zsemi:
|
||||||
case ZclusterId:
|
case ZclusterId:
|
||||||
case ZattribId:
|
case ZattribId:
|
||||||
|
case Ztuya1:
|
||||||
return 2;
|
return 2;
|
||||||
|
case Ztuya2:
|
||||||
|
return 3;
|
||||||
case Zsingle:
|
case Zsingle:
|
||||||
case ZToD:
|
case ZToD:
|
||||||
case Zdate:
|
case Zdate:
|
||||||
case ZUTC:
|
case ZUTC:
|
||||||
case ZbacOID:
|
case ZbacOID:
|
||||||
return 4;
|
return 4;
|
||||||
|
case Ztuya4:
|
||||||
|
return 5;
|
||||||
case Zdouble:
|
case Zdouble:
|
||||||
case ZEUI64:
|
case ZEUI64:
|
||||||
return 8;
|
return 8;
|
||||||
|
@ -583,25 +592,25 @@ const Z_AttributeConverter Z_PostProcess[] PROGMEM = {
|
||||||
// Tuya Moes specific - 0xEF00
|
// Tuya Moes specific - 0xEF00
|
||||||
{ Zoctstr, CxEF00, 0x0070, Z_(TuyaScheduleWorkdays), Cm1, 0 },
|
{ Zoctstr, CxEF00, 0x0070, Z_(TuyaScheduleWorkdays), Cm1, 0 },
|
||||||
{ Zoctstr, CxEF00, 0x0071, Z_(TuyaScheduleHolidays), Cm1, 0 },
|
{ Zoctstr, CxEF00, 0x0071, Z_(TuyaScheduleHolidays), Cm1, 0 },
|
||||||
{ Zuint8, CxEF00, 0x0107, Z_(TuyaChildLock), Cm1, 0 },
|
{ Ztuya1, CxEF00, 0x0107, Z_(TuyaChildLock), Cm1, 0 },
|
||||||
{ Zuint8, CxEF00, 0x0112, Z_(TuyaWindowDetection), Cm1, 0 },
|
{ Ztuya1, CxEF00, 0x0112, Z_(TuyaWindowDetection), Cm1, 0 },
|
||||||
{ Zuint8, CxEF00, 0x0114, Z_(TuyaValveDetection), Cm1, 0 },
|
{ Ztuya1, CxEF00, 0x0114, Z_(TuyaValveDetection), Cm1, 0 },
|
||||||
{ Zuint8, CxEF00, 0x0174, Z_(TuyaAutoLock), Cm1, 0 },
|
{ Ztuya1, CxEF00, 0x0174, Z_(TuyaAutoLock), Cm1, 0 },
|
||||||
{ Zint16, CxEF00, 0x0202, Z_(TuyaTempTarget), Cm_10, 0 },
|
{ Ztuya4, CxEF00, 0x0202, Z_(TuyaTempTarget), Cm_10, 0 },
|
||||||
{ Zint16, CxEF00, 0x0203, Z_(LocalTemperature), Cm_10, 0 }, // will be overwritten by actual LocalTemperature
|
{ Ztuya4, CxEF00, 0x0203, Z_(LocalTemperature), Cm_10, 0 }, // will be overwritten by actual LocalTemperature
|
||||||
{ Zuint8, CxEF00, 0x0215, Z_(TuyaBattery), Cm1, 0 }, // TODO check equivalent?
|
{ Ztuya1, CxEF00, 0x0215, Z_(TuyaBattery), Cm1, 0 }, // TODO check equivalent?
|
||||||
{ Zint32, CxEF00, 0x0266, Z_(TuyaMinTemp), Cm1, 0 },
|
{ Ztuya4, CxEF00, 0x0266, Z_(TuyaMinTemp), Cm1, 0 },
|
||||||
{ Zint32, CxEF00, 0x0267, Z_(TuyaMaxTemp), Cm1, 0 },
|
{ Ztuya4, CxEF00, 0x0267, Z_(TuyaMaxTemp), Cm1, 0 },
|
||||||
{ Zint32, CxEF00, 0x0269, Z_(TuyaBoostTime), Cm1, 0 },
|
{ Ztuya4, CxEF00, 0x0269, Z_(TuyaBoostTime), Cm1, 0 },
|
||||||
{ Zint32, CxEF00, 0x026B, Z_(TuyaComfortTemp), Cm1, 0 },
|
{ Ztuya4, CxEF00, 0x026B, Z_(TuyaComfortTemp), Cm1, 0 },
|
||||||
{ Zint32, CxEF00, 0x026C, Z_(TuyaEcoTemp), Cm1, 0 },
|
{ Ztuya4, CxEF00, 0x026C, Z_(TuyaEcoTemp), Cm1, 0 },
|
||||||
{ Zuint8, CxEF00, 0x026D, Z_(TuyaValvePosition), Cm1, 0 },
|
{ Ztuya1, CxEF00, 0x026D, Z_(TuyaValvePosition), Cm1, 0 },
|
||||||
{ Zint32, CxEF00, 0x0272, Z_(TuyaAwayTemp), Cm1, 0 },
|
{ Ztuya4, CxEF00, 0x0272, Z_(TuyaAwayTemp), Cm1, 0 },
|
||||||
{ Zint32, CxEF00, 0x0275, Z_(TuyaAwayDays), Cm1, 0 },
|
{ Ztuya4, CxEF00, 0x0275, Z_(TuyaAwayDays), Cm1, 0 },
|
||||||
{ Zuint8, CxEF00, 0x0404, Z_(TuyaPreset), Cm1, 0 },
|
{ Ztuya1, CxEF00, 0x0404, Z_(TuyaPreset), Cm1, 0 },
|
||||||
{ Zuint8, CxEF00, 0x0405, Z_(TuyaFanMode), Cm1, 0 },
|
{ Ztuya1, CxEF00, 0x0405, Z_(TuyaFanMode), Cm1, 0 },
|
||||||
{ Zuint8, CxEF00, 0x046A, Z_(TuyaForceMode), Cm1, 0 },
|
{ Ztuya1, CxEF00, 0x046A, Z_(TuyaForceMode), Cm1, 0 },
|
||||||
{ Zuint8, CxEF00, 0x046F, Z_(TuyaWeekSelect), Cm1, 0 },
|
{ Ztuya1, CxEF00, 0x046F, Z_(TuyaWeekSelect), Cm1, 0 },
|
||||||
};
|
};
|
||||||
#pragma GCC diagnostic pop
|
#pragma GCC diagnostic pop
|
||||||
|
|
||||||
|
@ -816,7 +825,7 @@ uint8_t toPercentageCR2032(uint32_t voltage) {
|
||||||
// - n bytes: value (typically between 1 and 4 bytes, or bigger for strings)
|
// - n bytes: value (typically between 1 and 4 bytes, or bigger for strings)
|
||||||
// returns number of bytes of attribute, or <0 if error
|
// returns number of bytes of attribute, or <0 if error
|
||||||
int32_t encodeSingleAttribute(class SBuffer &buf, double val_d, const char *val_str, uint8_t attrtype) {
|
int32_t encodeSingleAttribute(class SBuffer &buf, double val_d, const char *val_str, uint8_t attrtype) {
|
||||||
uint32_t len = Z_getDatatypeLen(attrtype); // pre-compute lenght, overloaded for variable length attributes
|
uint32_t len = Z_getDatatypeLen(attrtype); // pre-compute length, overloaded for variable length attributes
|
||||||
uint32_t u32 = val_d;
|
uint32_t u32 = val_d;
|
||||||
int32_t i32 = val_d;
|
int32_t i32 = val_d;
|
||||||
float f32 = val_d;
|
float f32 = val_d;
|
||||||
|
@ -830,6 +839,10 @@ int32_t encodeSingleAttribute(class SBuffer &buf, double val_d, const char *val_
|
||||||
case Zmap8: // map8
|
case Zmap8: // map8
|
||||||
buf.add8(u32);
|
buf.add8(u32);
|
||||||
break;
|
break;
|
||||||
|
case Ztuya1: // tuya specific 1 byte
|
||||||
|
buf.add8(1); // len
|
||||||
|
buf.add8(u32);
|
||||||
|
break;
|
||||||
// unsigned 16
|
// unsigned 16
|
||||||
case Zuint16: // uint16
|
case Zuint16: // uint16
|
||||||
case Zenum16: // enum16
|
case Zenum16: // enum16
|
||||||
|
@ -837,6 +850,9 @@ int32_t encodeSingleAttribute(class SBuffer &buf, double val_d, const char *val_
|
||||||
case Zmap16: // map16
|
case Zmap16: // map16
|
||||||
buf.add16(u32);
|
buf.add16(u32);
|
||||||
break;
|
break;
|
||||||
|
case Ztuya2:
|
||||||
|
buf.add8(2); // len
|
||||||
|
buf.add16BigEndian(u32);
|
||||||
// unisgned 32
|
// unisgned 32
|
||||||
case Zuint32: // uint32
|
case Zuint32: // uint32
|
||||||
case Zdata32: // data32
|
case Zdata32: // data32
|
||||||
|
@ -855,6 +871,10 @@ int32_t encodeSingleAttribute(class SBuffer &buf, double val_d, const char *val_
|
||||||
case Zint32: // int32
|
case Zint32: // int32
|
||||||
buf.add32(i32);
|
buf.add32(i32);
|
||||||
break;
|
break;
|
||||||
|
case Ztuya4:
|
||||||
|
buf.add8(4); // len
|
||||||
|
buf.add32BigEndian(i32);
|
||||||
|
break;
|
||||||
|
|
||||||
case Zsingle: // float
|
case Zsingle: // float
|
||||||
uint32_t *f_ptr;
|
uint32_t *f_ptr;
|
||||||
|
@ -944,6 +964,15 @@ uint32_t parseSingleAttribute(Z_attribute & attr, const SBuffer &buf,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case Ztuya1: // uint8 Big Endian
|
||||||
|
attr.setUInt(buf.get8(i+1));
|
||||||
|
break;
|
||||||
|
case Ztuya2: // uint16 Big Endian
|
||||||
|
attr.setUInt(buf.get16BigEndian(i+1));
|
||||||
|
break;
|
||||||
|
case Ztuya4:
|
||||||
|
attr.setInt(buf.get32IBigEndian(i+1));
|
||||||
|
break;
|
||||||
// Note: uint40, uint48, uint56, uint64 are displayed as Hex
|
// Note: uint40, uint48, uint56, uint64 are displayed as Hex
|
||||||
// Note: int40, int48, int56, int64 are displayed as Hex
|
// Note: int40, int48, int56, int64 are displayed as Hex
|
||||||
case Zuint40: // uint40
|
case Zuint40: // uint40
|
||||||
|
@ -1001,14 +1030,14 @@ uint32_t parseSingleAttribute(Z_attribute & attr, const SBuffer &buf,
|
||||||
// For strings, default is to try to do a real string, but reverts to octet stream if null char is present or on some exceptions
|
// For strings, default is to try to do a real string, but reverts to octet stream if null char is present or on some exceptions
|
||||||
{
|
{
|
||||||
bool parse_as_string = true;
|
bool parse_as_string = true;
|
||||||
len = (attrtype <= 0x42) ? buf.get8(i) : buf.get16(i); // len is 8 or 16 bits
|
len = (attrtype <= Zstring) ? buf.get8(i) : buf.get16(i); // len is 8 or 16 bits
|
||||||
i += (attrtype <= 0x42) ? 1 : 2; // increment pointer
|
i += (attrtype <= Zstring) ? 1 : 2; // increment pointer
|
||||||
if (i + len > buf.len()) { // make sure we don't get past the buffer
|
if (i + len > buf.len()) { // make sure we don't get past the buffer
|
||||||
len = buf.len() - i;
|
len = buf.len() - i;
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if we can safely use a string
|
// check if we can safely use a string
|
||||||
if ((0x41 == attrtype) || (0x43 == attrtype)) { parse_as_string = false; }
|
if ((Zoctstr == attrtype) || (Zoctstr16 == attrtype)) { parse_as_string = false; }
|
||||||
|
|
||||||
if (parse_as_string) {
|
if (parse_as_string) {
|
||||||
char str[len+1];
|
char str[len+1];
|
||||||
|
|
|
@ -461,34 +461,14 @@ bool convertTuyaSpecificCluster(class Z_attribute_list &attr_list, uint16_t clus
|
||||||
if ((1 == cmd) || (2 == cmd)) { // attribute report or attribute response
|
if ((1 == cmd) || (2 == cmd)) { // attribute report or attribute response
|
||||||
// create a synthetic attribute with id 'dp'
|
// create a synthetic attribute with id 'dp'
|
||||||
Z_attribute & attr = attr_list.addAttribute(cluster, dp);
|
Z_attribute & attr = attr_list.addAttribute(cluster, dp);
|
||||||
int32_t ival32 = -0x80000000;
|
uint8_t attr_type;
|
||||||
if (1 == len) {
|
switch (len) {
|
||||||
// 1 byte, convert as uint8_t
|
case 1: attr_type = Ztuya1; break;
|
||||||
ival32 = buf.get8(6);
|
case 2: attr_type = Ztuya2; break;
|
||||||
} else if (2 == len) {
|
case 4: attr_type = Ztuya4; break;
|
||||||
ival32 = buf.get16BigEndian(6);
|
default: attr_type = Zoctstr; break;
|
||||||
} else if (4 == len) {
|
|
||||||
// 4 bytes, convert as int32_t
|
|
||||||
ival32 = buf.get32IBigEndian(6);
|
|
||||||
}
|
|
||||||
if (ival32 != -0x80000000) {
|
|
||||||
// fix temperature coefficient
|
|
||||||
switch (dp) {
|
|
||||||
case 0x0202:
|
|
||||||
attr_list.addAttribute(0x0201, 0x0012).setInt(ival32 * 10); // OccupiedHeatingSetpoint
|
|
||||||
break;
|
|
||||||
case 0x0203:
|
|
||||||
attr_list.addAttribute(0x0201, 0x0000).setInt(ival32 * 10); // LocalTemperature
|
|
||||||
break;
|
|
||||||
case 0x026D:
|
|
||||||
attr_list.addAttribute(0x0201, 0x0008).setUInt(ival32); // PIHeatingDemand
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
attr.setInt(ival32);
|
|
||||||
} else {
|
|
||||||
// add as raw buffer
|
|
||||||
attr.setBuf(buf, 6, len);
|
|
||||||
}
|
}
|
||||||
|
parseSingleAttribute(attr, buf, 5, attr_type);
|
||||||
return true; // true = remove the original Tuya attribute
|
return true; // true = remove the original Tuya attribute
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -761,7 +761,7 @@ void CmndZbEZSPSend(void)
|
||||||
// - groupaddr: 16-bits group address, or 0x0000 if unicast using shortaddr
|
// - groupaddr: 16-bits group address, or 0x0000 if unicast using shortaddr
|
||||||
// - clusterIf: 16-bits cluster number
|
// - clusterIf: 16-bits cluster number
|
||||||
// - endpoint: 8-bits target endpoint (source is always 0x01), unused for group addresses. Should not be 0x00 except when sending to group address.
|
// - endpoint: 8-bits target endpoint (source is always 0x01), unused for group addresses. Should not be 0x00 except when sending to group address.
|
||||||
// - cmdId: 8-bits ZCL command number
|
// - cmd: 8-bits ZCL command number
|
||||||
// - clusterSpecific: boolean, is the message general cluster or cluster specific, used to create the FC byte of ZCL
|
// - clusterSpecific: boolean, is the message general cluster or cluster specific, used to create the FC byte of ZCL
|
||||||
// - msg: pointer to byte array, payload of ZCL message (len is following), ignored if nullptr
|
// - msg: pointer to byte array, payload of ZCL message (len is following), ignored if nullptr
|
||||||
// - len: length of the 'msg' payload
|
// - len: length of the 'msg' payload
|
||||||
|
@ -786,7 +786,7 @@ void ZigbeeZCLSend_Raw(const ZigbeeZCLSendMessage &zcl) {
|
||||||
}
|
}
|
||||||
buf.add16(0x0000); // dest Pan ID, 0x0000 = intra-pan
|
buf.add16(0x0000); // dest Pan ID, 0x0000 = intra-pan
|
||||||
buf.add8(0x01); // source endpoint
|
buf.add8(0x01); // source endpoint
|
||||||
buf.add16(zcl.clusterId);
|
buf.add16(zcl.cluster);
|
||||||
buf.add8(zcl.transacId); // transacId
|
buf.add8(zcl.transacId); // transacId
|
||||||
buf.add8(0x30); // 30 options
|
buf.add8(0x30); // 30 options
|
||||||
buf.add8(0x1E); // 1E radius
|
buf.add8(0x1E); // 1E radius
|
||||||
|
@ -797,7 +797,7 @@ void ZigbeeZCLSend_Raw(const ZigbeeZCLSendMessage &zcl) {
|
||||||
buf.add16(zcl.manuf); // add Manuf Id if not null
|
buf.add16(zcl.manuf); // add Manuf Id if not null
|
||||||
}
|
}
|
||||||
buf.add8(zcl.transacId); // Transaction Sequence Number
|
buf.add8(zcl.transacId); // Transaction Sequence Number
|
||||||
buf.add8(zcl.cmdId);
|
buf.add8(zcl.cmd);
|
||||||
if (zcl.len > 0) {
|
if (zcl.len > 0) {
|
||||||
buf.addBuffer(zcl.msg, zcl.len); // add the payload
|
buf.addBuffer(zcl.msg, zcl.len); // add the payload
|
||||||
}
|
}
|
||||||
|
@ -815,7 +815,7 @@ void ZigbeeZCLSend_Raw(const ZigbeeZCLSendMessage &zcl) {
|
||||||
buf.add16(zcl.shortaddr); // dest addr
|
buf.add16(zcl.shortaddr); // dest addr
|
||||||
// ApsFrame
|
// ApsFrame
|
||||||
buf.add16(Z_PROF_HA); // Home Automation profile
|
buf.add16(Z_PROF_HA); // Home Automation profile
|
||||||
buf.add16(zcl.clusterId); // cluster
|
buf.add16(zcl.cluster); // cluster
|
||||||
buf.add8(0x01); // srcEp
|
buf.add8(0x01); // srcEp
|
||||||
buf.add8(zcl.endpoint); // dstEp
|
buf.add8(zcl.endpoint); // dstEp
|
||||||
buf.add16(EMBER_APS_OPTION_ENABLE_ROUTE_DISCOVERY | EMBER_APS_OPTION_RETRY); // APS frame
|
buf.add16(EMBER_APS_OPTION_ENABLE_ROUTE_DISCOVERY | EMBER_APS_OPTION_RETRY); // APS frame
|
||||||
|
@ -830,7 +830,7 @@ void ZigbeeZCLSend_Raw(const ZigbeeZCLSendMessage &zcl) {
|
||||||
buf.add16(zcl.manuf); // add Manuf Id if not null
|
buf.add16(zcl.manuf); // add Manuf Id if not null
|
||||||
}
|
}
|
||||||
buf.add8(zcl.transacId); // Transaction Sequance Number
|
buf.add8(zcl.transacId); // Transaction Sequance Number
|
||||||
buf.add8(zcl.cmdId);
|
buf.add8(zcl.cmd);
|
||||||
if (zcl.len > 0) {
|
if (zcl.len > 0) {
|
||||||
buf.addBuffer(zcl.msg, zcl.len); // add the payload
|
buf.addBuffer(zcl.msg, zcl.len); // add the payload
|
||||||
}
|
}
|
||||||
|
@ -839,7 +839,7 @@ void ZigbeeZCLSend_Raw(const ZigbeeZCLSendMessage &zcl) {
|
||||||
buf.add16(EZSP_sendMulticast); // 3800
|
buf.add16(EZSP_sendMulticast); // 3800
|
||||||
// ApsFrame
|
// ApsFrame
|
||||||
buf.add16(Z_PROF_HA); // Home Automation profile
|
buf.add16(Z_PROF_HA); // Home Automation profile
|
||||||
buf.add16(zcl.clusterId); // cluster
|
buf.add16(zcl.cluster); // cluster
|
||||||
buf.add8(0x01); // srcEp
|
buf.add8(0x01); // srcEp
|
||||||
buf.add8(zcl.endpoint); // broadcast endpoint for groupcast
|
buf.add8(zcl.endpoint); // broadcast endpoint for groupcast
|
||||||
buf.add16(EMBER_APS_OPTION_ENABLE_ROUTE_DISCOVERY | EMBER_APS_OPTION_RETRY); // APS frame
|
buf.add16(EMBER_APS_OPTION_ENABLE_ROUTE_DISCOVERY | EMBER_APS_OPTION_RETRY); // APS frame
|
||||||
|
@ -856,7 +856,7 @@ void ZigbeeZCLSend_Raw(const ZigbeeZCLSendMessage &zcl) {
|
||||||
buf.add16(zcl.manuf); // add Manuf Id if not null
|
buf.add16(zcl.manuf); // add Manuf Id if not null
|
||||||
}
|
}
|
||||||
buf.add8(zcl.transacId); // Transaction Sequance Number
|
buf.add8(zcl.transacId); // Transaction Sequance Number
|
||||||
buf.add8(zcl.cmdId);
|
buf.add8(zcl.cmd);
|
||||||
if (zcl.len > 0) {
|
if (zcl.len > 0) {
|
||||||
buf.addBuffer(zcl.msg, zcl.len); // add the payload
|
buf.addBuffer(zcl.msg, zcl.len); // add the payload
|
||||||
}
|
}
|
||||||
|
|
|
@ -218,6 +218,29 @@ void ZbApplyMultiplier(double &val_d, int8_t multiplier) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Write Tuya-Moes attribute
|
||||||
|
//
|
||||||
|
bool ZbTuyaWrite(SBuffer & buf, const Z_attribute & attr, uint8_t transid) {
|
||||||
|
double val_d = attr.getFloat();
|
||||||
|
const char * val_str = attr.getStr();
|
||||||
|
|
||||||
|
if (attr.key_is_str) { return false; } // couldn't find attr if so skip
|
||||||
|
if (attr.isNum() && (1 != attr.attr_multiplier)) {
|
||||||
|
ZbApplyMultiplier(val_d, attr.attr_multiplier);
|
||||||
|
}
|
||||||
|
buf.add8(0); // status
|
||||||
|
buf.add8(transid); // transid
|
||||||
|
buf.add16(attr.key.id.attr_id); // dp
|
||||||
|
buf.add8(0); // fn
|
||||||
|
int32_t res = encodeSingleAttribute(buf, val_d, val_str, attr.attr_type);
|
||||||
|
if (res < 0) {
|
||||||
|
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "Error for Tuya attribute type %04X/%04X '0x%02X'"), attr.key.id.cluster, attr.key.id.attr_id, attr.attr_type);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Send Attribute Write, apply mutlipliers before
|
// Send Attribute Write, apply mutlipliers before
|
||||||
//
|
//
|
||||||
|
@ -225,7 +248,7 @@ bool ZbAppendWriteBuf(SBuffer & buf, const Z_attribute & attr, bool prepend_stat
|
||||||
double val_d = attr.getFloat();
|
double val_d = attr.getFloat();
|
||||||
const char * val_str = attr.getStr();
|
const char * val_str = attr.getStr();
|
||||||
|
|
||||||
if (attr.key_is_str) { return false; }
|
if (attr.key_is_str) { return false; } // couldn't find attr if so skip
|
||||||
if (attr.isNum() && (1 != attr.attr_multiplier)) {
|
if (attr.isNum() && (1 != attr.attr_multiplier)) {
|
||||||
ZbApplyMultiplier(val_d, attr.attr_multiplier);
|
ZbApplyMultiplier(val_d, attr.attr_multiplier);
|
||||||
}
|
}
|
||||||
|
@ -246,9 +269,11 @@ bool ZbAppendWriteBuf(SBuffer & buf, const Z_attribute & attr, bool prepend_stat
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse "Report", "Write", "Response" or "Condig" attribute
|
//
|
||||||
|
// Parse "Report", "Write", "Response" or "Config" attribute
|
||||||
// Operation is one of: ZCL_REPORT_ATTRIBUTES (0x0A), ZCL_WRITE_ATTRIBUTES (0x02) or ZCL_READ_ATTRIBUTES_RESPONSE (0x01)
|
// Operation is one of: ZCL_REPORT_ATTRIBUTES (0x0A), ZCL_WRITE_ATTRIBUTES (0x02) or ZCL_READ_ATTRIBUTES_RESPONSE (0x01)
|
||||||
void ZbSendReportWrite(class JsonParserToken val_pubwrite, uint16_t device, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint, uint16_t manuf, uint8_t operation) {
|
//
|
||||||
|
void ZbSendReportWrite(class JsonParserToken val_pubwrite, class ZigbeeZCLSendMessage & packet) {
|
||||||
SBuffer buf(200); // buffer to store the binary output of attibutes
|
SBuffer buf(200); // buffer to store the binary output of attibutes
|
||||||
|
|
||||||
if (nullptr == XdrvMailbox.command) {
|
if (nullptr == XdrvMailbox.command) {
|
||||||
|
@ -263,9 +288,11 @@ void ZbSendReportWrite(class JsonParserToken val_pubwrite, uint16_t device, uint
|
||||||
attr.setKeyName(key.getStr());
|
attr.setKeyName(key.getStr());
|
||||||
if (Z_parseAttributeKey(attr)) {
|
if (Z_parseAttributeKey(attr)) {
|
||||||
// Buffer ready, do some sanity checks
|
// Buffer ready, do some sanity checks
|
||||||
if (0xFFFF == cluster) {
|
|
||||||
cluster = attr.key.id.cluster; // set the cluster for this packet
|
// all attributes must use the same cluster
|
||||||
} else if (cluster != attr.key.id.cluster) {
|
if (0xFFFF == packet.cluster) {
|
||||||
|
packet.cluster = attr.key.id.cluster; // set the cluster for this packet
|
||||||
|
} else if (packet.cluster != attr.key.id.cluster) {
|
||||||
ResponseCmndChar_P(PSTR("No more than one cluster id per command"));
|
ResponseCmndChar_P(PSTR("No more than one cluster id per command"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -281,6 +308,7 @@ void ZbSendReportWrite(class JsonParserToken val_pubwrite, uint16_t device, uint
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// copy value from input to attribute, in numerical or string format
|
||||||
if (value.isStr()) {
|
if (value.isStr()) {
|
||||||
attr.setStr(value.getStr());
|
attr.setStr(value.getStr());
|
||||||
} else if (value.isNum()) {
|
} else if (value.isNum()) {
|
||||||
|
@ -291,8 +319,19 @@ void ZbSendReportWrite(class JsonParserToken val_pubwrite, uint16_t device, uint
|
||||||
const char* val_str = ""; // variant as string
|
const char* val_str = ""; // variant as string
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
// Split encoding depending on message
|
// Split encoding depending on message
|
||||||
if (operation != ZCL_CONFIGURE_REPORTING) {
|
if (packet.cmd != ZCL_CONFIGURE_REPORTING) {
|
||||||
if (!ZbAppendWriteBuf(buf, attr, operation == ZCL_READ_ATTRIBUTES_RESPONSE)) {
|
if ((packet.cluster == 0XEF00) && (packet.cmd == ZCL_WRITE_ATTRIBUTES)) {
|
||||||
|
// special case of Tuya / Moes attributes
|
||||||
|
if (buf.len() > 0) {
|
||||||
|
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "Only 1 attribute allowed for Tuya"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
packet.clusterSpecific = true;
|
||||||
|
packet.cmd = 0x00;
|
||||||
|
if (!ZbTuyaWrite(buf, attr, zigbee_devices.getNextSeqNumber(packet.shortaddr))) {
|
||||||
|
return; // error
|
||||||
|
}
|
||||||
|
} else if (!ZbAppendWriteBuf(buf, attr, packet.cmd == ZCL_READ_ATTRIBUTES_RESPONSE)) { // general case
|
||||||
return; // error
|
return; // error
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -353,19 +392,10 @@ void ZbSendReportWrite(class JsonParserToken val_pubwrite, uint16_t device, uint
|
||||||
}
|
}
|
||||||
|
|
||||||
// all good, send the packet
|
// all good, send the packet
|
||||||
uint8_t seq = zigbee_devices.getNextSeqNumber(device);
|
packet.transacId = zigbee_devices.getNextSeqNumber(packet.shortaddr);
|
||||||
ZigbeeZCLSend_Raw(ZigbeeZCLSendMessage({
|
packet.msg = buf.getBuffer();
|
||||||
device,
|
packet.len = buf.len();
|
||||||
groupaddr,
|
ZigbeeZCLSend_Raw(packet);
|
||||||
cluster /*cluster*/,
|
|
||||||
endpoint,
|
|
||||||
operation,
|
|
||||||
manuf, /* manuf */
|
|
||||||
false /* not cluster specific */,
|
|
||||||
false /* no response */,
|
|
||||||
seq, /* zcl transaction id */
|
|
||||||
buf.getBuffer(), buf.len()
|
|
||||||
}));
|
|
||||||
ResponseCmndDone();
|
ResponseCmndDone();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -501,7 +531,7 @@ void ZbSendSend(class JsonParserToken val_cmd, uint16_t device, uint16_t groupad
|
||||||
|
|
||||||
|
|
||||||
// Parse the "Send" attribute and send the command
|
// Parse the "Send" attribute and send the command
|
||||||
void ZbSendRead(JsonParserToken val_attr, uint16_t device, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint, uint16_t manuf, uint8_t operation) {
|
void ZbSendRead(JsonParserToken val_attr, ZigbeeZCLSendMessage & packet) {
|
||||||
// ZbSend {"Device":"0xF289","Cluster":0,"Endpoint":3,"Read":5}
|
// ZbSend {"Device":"0xF289","Cluster":0,"Endpoint":3,"Read":5}
|
||||||
// ZbSend {"Device":"0xF289","Cluster":"0x0000","Endpoint":"0x0003","Read":"0x0005"}
|
// ZbSend {"Device":"0xF289","Cluster":"0x0000","Endpoint":"0x0003","Read":"0x0005"}
|
||||||
// ZbSend {"Device":"0xF289","Cluster":0,"Endpoint":3,"Read":[5,6,7,4]}
|
// ZbSend {"Device":"0xF289","Cluster":0,"Endpoint":3,"Read":[5,6,7,4]}
|
||||||
|
@ -516,7 +546,7 @@ void ZbSendRead(JsonParserToken val_attr, uint16_t device, uint16_t groupaddr, u
|
||||||
uint8_t* attrs = nullptr; // empty string is valid
|
uint8_t* attrs = nullptr; // empty string is valid
|
||||||
size_t attr_item_len = 2; // how many bytes per attribute, standard for "Read"
|
size_t attr_item_len = 2; // how many bytes per attribute, standard for "Read"
|
||||||
size_t attr_item_offset = 0; // how many bytes do we offset to store attribute
|
size_t attr_item_offset = 0; // how many bytes do we offset to store attribute
|
||||||
if (ZCL_READ_REPORTING_CONFIGURATION == operation) {
|
if (ZCL_READ_REPORTING_CONFIGURATION == packet.cmd) {
|
||||||
attr_item_len = 3;
|
attr_item_len = 3;
|
||||||
attr_item_offset = 1;
|
attr_item_offset = 1;
|
||||||
}
|
}
|
||||||
|
@ -569,9 +599,9 @@ void ZbSendRead(JsonParserToken val_attr, uint16_t device, uint16_t groupaddr, u
|
||||||
actual_attr_len += attr_item_len - 2 - attr_item_offset; // normally 0
|
actual_attr_len += attr_item_len - 2 - attr_item_offset; // normally 0
|
||||||
found = true;
|
found = true;
|
||||||
// check cluster
|
// check cluster
|
||||||
if (0xFFFF == cluster) {
|
if (0xFFFF == packet.cluster) {
|
||||||
cluster = local_cluster_id;
|
packet.cluster = local_cluster_id;
|
||||||
} else if (cluster != local_cluster_id) {
|
} else if (packet.cluster != local_cluster_id) {
|
||||||
ResponseCmndChar_P(PSTR("No more than one cluster id per command"));
|
ResponseCmndChar_P(PSTR("No more than one cluster id per command"));
|
||||||
if (attrs) { free(attrs); }
|
if (attrs) { free(attrs); }
|
||||||
return;
|
return;
|
||||||
|
@ -587,7 +617,7 @@ void ZbSendRead(JsonParserToken val_attr, uint16_t device, uint16_t groupaddr, u
|
||||||
attrs_len = actual_attr_len;
|
attrs_len = actual_attr_len;
|
||||||
} else {
|
} else {
|
||||||
// value is a literal
|
// value is a literal
|
||||||
if (0xFFFF != cluster) {
|
if (0xFFFF != packet.cluster) {
|
||||||
uint16_t val = val_attr.getUInt();
|
uint16_t val = val_attr.getUInt();
|
||||||
attrs_len = attr_item_len;
|
attrs_len = attr_item_len;
|
||||||
attrs = (uint8_t*) calloc(attrs_len, 1);
|
attrs = (uint8_t*) calloc(attrs_len, 1);
|
||||||
|
@ -597,19 +627,11 @@ void ZbSendRead(JsonParserToken val_attr, uint16_t device, uint16_t groupaddr, u
|
||||||
}
|
}
|
||||||
|
|
||||||
if (attrs_len > 0) {
|
if (attrs_len > 0) {
|
||||||
uint8_t seq = zigbee_devices.getNextSeqNumber(device);
|
// all good, send the packet
|
||||||
ZigbeeZCLSend_Raw(ZigbeeZCLSendMessage({
|
packet.transacId = zigbee_devices.getNextSeqNumber(packet.shortaddr);
|
||||||
device,
|
packet.msg = attrs;
|
||||||
groupaddr,
|
packet.len = attrs_len;
|
||||||
cluster /*cluster*/,
|
ZigbeeZCLSend_Raw(packet);
|
||||||
endpoint,
|
|
||||||
operation,
|
|
||||||
manuf, /* manuf */
|
|
||||||
false /* not cluster specific */,
|
|
||||||
true /* response */,
|
|
||||||
seq, /* zcl transaction id */
|
|
||||||
attrs, attrs_len
|
|
||||||
}));
|
|
||||||
ResponseCmndDone();
|
ResponseCmndDone();
|
||||||
} else {
|
} else {
|
||||||
ResponseCmndChar_P(PSTR("Missing parameters"));
|
ResponseCmndChar_P(PSTR("Missing parameters"));
|
||||||
|
@ -707,6 +729,20 @@ void CmndZbSend(void) {
|
||||||
}
|
}
|
||||||
// from here we have one and only one command
|
// from here we have one and only one command
|
||||||
|
|
||||||
|
// collate information in a ready to send packet
|
||||||
|
ZigbeeZCLSendMessage packet({
|
||||||
|
device,
|
||||||
|
groupaddr,
|
||||||
|
cluster /*cluster*/,
|
||||||
|
endpoint,
|
||||||
|
ZCL_READ_ATTRIBUTES,
|
||||||
|
manuf, /* manuf */
|
||||||
|
false /* not cluster specific */,
|
||||||
|
false /* no response */,
|
||||||
|
0, /* zcl transaction id */
|
||||||
|
nullptr, 0
|
||||||
|
});
|
||||||
|
|
||||||
if (val_cmd) {
|
if (val_cmd) {
|
||||||
// "Send":{...commands...}
|
// "Send":{...commands...}
|
||||||
// we accept either a string or a JSON object
|
// we accept either a string or a JSON object
|
||||||
|
@ -714,7 +750,8 @@ void CmndZbSend(void) {
|
||||||
} else if (val_read) {
|
} else if (val_read) {
|
||||||
// "Read":{...attributes...}, "Read":attribute or "Read":[...attributes...]
|
// "Read":{...attributes...}, "Read":attribute or "Read":[...attributes...]
|
||||||
// we accept eitehr a number, a string, an array of numbers/strings, or a JSON object
|
// we accept eitehr a number, a string, an array of numbers/strings, or a JSON object
|
||||||
ZbSendRead(val_read, device, groupaddr, cluster, endpoint, manuf, ZCL_READ_ATTRIBUTES);
|
packet.cmd = ZCL_READ_ATTRIBUTES;
|
||||||
|
ZbSendRead(val_read, packet);
|
||||||
} else if (val_write) {
|
} else if (val_write) {
|
||||||
// only KSON object
|
// only KSON object
|
||||||
if (!val_write.isObject()) {
|
if (!val_write.isObject()) {
|
||||||
|
@ -722,7 +759,8 @@ void CmndZbSend(void) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// "Write":{...attributes...}
|
// "Write":{...attributes...}
|
||||||
ZbSendReportWrite(val_write, device, groupaddr, cluster, endpoint, manuf, ZCL_WRITE_ATTRIBUTES);
|
packet.cmd = ZCL_WRITE_ATTRIBUTES;
|
||||||
|
ZbSendReportWrite(val_write, packet);
|
||||||
} else if (val_publish) {
|
} else if (val_publish) {
|
||||||
// "Publish":{...attributes...}
|
// "Publish":{...attributes...}
|
||||||
// only KSON object
|
// only KSON object
|
||||||
|
@ -730,7 +768,8 @@ void CmndZbSend(void) {
|
||||||
ResponseCmndChar_P(PSTR("Missing parameters"));
|
ResponseCmndChar_P(PSTR("Missing parameters"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ZbSendReportWrite(val_publish, device, groupaddr, cluster, endpoint, manuf, ZCL_REPORT_ATTRIBUTES);
|
packet.cmd = ZCL_REPORT_ATTRIBUTES;
|
||||||
|
ZbSendReportWrite(val_publish, packet);
|
||||||
} else if (val_response) {
|
} else if (val_response) {
|
||||||
// "Report":{...attributes...}
|
// "Report":{...attributes...}
|
||||||
// only KSON object
|
// only KSON object
|
||||||
|
@ -738,11 +777,13 @@ void CmndZbSend(void) {
|
||||||
ResponseCmndChar_P(PSTR("Missing parameters"));
|
ResponseCmndChar_P(PSTR("Missing parameters"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ZbSendReportWrite(val_response, device, groupaddr, cluster, endpoint, manuf, ZCL_READ_ATTRIBUTES_RESPONSE);
|
packet.cmd = ZCL_READ_ATTRIBUTES_RESPONSE;
|
||||||
|
ZbSendReportWrite(val_response, packet);
|
||||||
} else if (val_read_config) {
|
} else if (val_read_config) {
|
||||||
// "ReadConfg":{...attributes...}, "ReadConfg":attribute or "ReadConfg":[...attributes...]
|
// "ReadConfg":{...attributes...}, "ReadConfg":attribute or "ReadConfg":[...attributes...]
|
||||||
// we accept eitehr a number, a string, an array of numbers/strings, or a JSON object
|
// we accept eitehr a number, a string, an array of numbers/strings, or a JSON object
|
||||||
ZbSendRead(val_read_config, device, groupaddr, cluster, endpoint, manuf, ZCL_READ_REPORTING_CONFIGURATION);
|
packet.cmd = ZCL_READ_REPORTING_CONFIGURATION;
|
||||||
|
ZbSendRead(val_read_config, packet);
|
||||||
} else if (val_config) {
|
} else if (val_config) {
|
||||||
// "Config":{...attributes...}
|
// "Config":{...attributes...}
|
||||||
// only JSON object
|
// only JSON object
|
||||||
|
@ -750,7 +791,8 @@ void CmndZbSend(void) {
|
||||||
ResponseCmndChar_P(PSTR("Missing parameters"));
|
ResponseCmndChar_P(PSTR("Missing parameters"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ZbSendReportWrite(val_config, device, groupaddr, cluster, endpoint, manuf, ZCL_CONFIGURE_REPORTING);
|
packet.cmd = ZCL_CONFIGURE_REPORTING;
|
||||||
|
ZbSendReportWrite(val_config, packet);
|
||||||
} else {
|
} else {
|
||||||
Response_P(PSTR("Missing zigbee 'Send', 'Write', 'Report' or 'Response'"));
|
Response_P(PSTR("Missing zigbee 'Send', 'Write', 'Report' or 'Response'"));
|
||||||
return;
|
return;
|
||||||
|
@ -1388,9 +1430,13 @@ void CmndZbData(void) {
|
||||||
Z_attribute_list inner_attr;
|
Z_attribute_list inner_attr;
|
||||||
char key[4];
|
char key[4];
|
||||||
snprintf_P(key, sizeof(key), "?%02X", data_elt.getEndpoint());
|
snprintf_P(key, sizeof(key), "?%02X", data_elt.getEndpoint());
|
||||||
// The key is in the form "L-01", where 'L' is the type and '01' the endpoint in hex format
|
// The key is in the form "L01", where 'L' is the type and '01' the endpoint in hex format
|
||||||
// 'L' = Light
|
|
||||||
// 'P' = Power
|
// 'P' = Power
|
||||||
|
// 'L' = Light
|
||||||
|
// 'O' = OnOff
|
||||||
|
// 'T' = Thermo & sensors
|
||||||
|
// 'A' = Alarm
|
||||||
|
// '?' = Device wide
|
||||||
//
|
//
|
||||||
Z_Data_Type data_type = data_elt.getType();
|
Z_Data_Type data_type = data_elt.getType();
|
||||||
switch (data_type) {
|
switch (data_type) {
|
||||||
|
|
Loading…
Reference in New Issue