mirror of https://github.com/arendst/Tasmota.git
Merge pull request #10074 from s-hadinger/zigbee_tuya_write
Zigbee better support for Tuya Protocol
This commit is contained in:
commit
9051240121
|
@ -177,6 +177,13 @@ public:
|
|||
}
|
||||
return 0;
|
||||
}
|
||||
uint32_t get32BigEndian(const size_t offset) const {
|
||||
if (offset < len() - 3) {
|
||||
return _buf->buf[offset+3] | (_buf->buf[offset+2] << 8) |
|
||||
(_buf->buf[offset+1] << 16) | (_buf->buf[offset] << 24);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
int32_t get32IBigEndian(const size_t offset) const {
|
||||
if (offset < len() - 3) {
|
||||
return _buf->buf[offset+3] | (_buf->buf[offset+2] << 8) |
|
||||
|
|
|
@ -32,6 +32,7 @@ enum class Z_Data_Type : uint8_t {
|
|||
Z_Alarm = 4,
|
||||
Z_Thermo = 5, // Thermostat and sensor for home environment (temp, himudity, pressure)
|
||||
Z_OnOff = 6, // OnOff, Buttons and Relays (always complements Lights and Plugs)
|
||||
Z_Mode = 0xE, // Encode special modes for communication, like Tuya Zigbee protocol
|
||||
Z_Ext = 0xF, // extended for other values
|
||||
Z_Device = 0xFF // special value when parsing Device level attributes
|
||||
};
|
||||
|
@ -43,8 +44,7 @@ const uint8_t Z_Data_Type_char[] PROGMEM = {
|
|||
'I', // 0x03 Z_Data_Type::Z_PIR
|
||||
'A', // 0x04 Z_Data_Type::Z_Alarm
|
||||
'T', // 0x05 Z_Data_Type::Z_Thermo
|
||||
'O', // 0x05 Z_Data_Type::Z_OnOff
|
||||
'\0', // 0x06
|
||||
'O', // 0x06 Z_Data_Type::Z_OnOff
|
||||
'\0', // 0x07
|
||||
'\0', // 0x08
|
||||
'\0', // 0x09
|
||||
|
@ -52,7 +52,7 @@ const uint8_t Z_Data_Type_char[] PROGMEM = {
|
|||
'\0', // 0x0B
|
||||
'\0', // 0x0C
|
||||
'\0', // 0x0D
|
||||
'\0', // 0x0E
|
||||
'~', // 0x0E
|
||||
'E', // 0x0F Z_Data_Type::Z_Ext
|
||||
// '_' maps to 0xFF Z_Data_Type::Z_Device
|
||||
};
|
||||
|
@ -309,16 +309,16 @@ void Z_Data_PIR::setTimeoutSeconds(int32_t value) {
|
|||
* Device specific: Sensors: temp, humidity, pressure...
|
||||
\*********************************************************************************************/
|
||||
enum Z_Alarm_Type {
|
||||
ZA_CIE = 0x0,
|
||||
ZA_PIR = 0x1,
|
||||
ZA_Contact = 0x2,
|
||||
ZA_Fire = 0x3,
|
||||
ZA_CIE = 0x0,
|
||||
ZA_PIR = 0x1,
|
||||
ZA_Contact = 0x2,
|
||||
ZA_Fire = 0x3,
|
||||
ZA_Water = 0x4,
|
||||
ZA_CO = 0x5,
|
||||
ZA_Personal = 0x6,
|
||||
ZA_Movement = 0x7,
|
||||
ZA_Panic = 0x8,
|
||||
ZA_GlassBreak =0x9,
|
||||
ZA_CO = 0x5,
|
||||
ZA_Personal = 0x6,
|
||||
ZA_Movement = 0x7,
|
||||
ZA_Panic = 0x8,
|
||||
ZA_GlassBreak = 0x9,
|
||||
};
|
||||
|
||||
class Z_Data_Thermo : public Z_Data {
|
||||
|
@ -500,6 +500,32 @@ void Z_Data_Alarm::convertZoneStatus(Z_attribute_list & attr_list, uint16_t val)
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/*********************************************************************************************\
|
||||
* Mode
|
||||
*
|
||||
// List of modes
|
||||
// 0x1 = Tuya Zigbee mode
|
||||
// 0xF (default) = ZCL standard mode
|
||||
\*********************************************************************************************/
|
||||
enum Z_Mode_Type {
|
||||
ZM_Tuya = 0x1,
|
||||
};
|
||||
|
||||
class Z_Data_Mode : public Z_Data {
|
||||
public:
|
||||
Z_Data_Mode(uint8_t endpoint = 0) :
|
||||
Z_Data(Z_Data_Type::Z_Mode, endpoint)
|
||||
{}
|
||||
|
||||
inline bool isTuyaProtocol(void) const { return _config == 1; }
|
||||
|
||||
static const Z_Data_Type type = Z_Data_Type::Z_Mode;
|
||||
};
|
||||
|
||||
/*********************************************************************************************\
|
||||
*
|
||||
\*********************************************************************************************/
|
||||
const uint8_t Z_Data_Type_len[] PROGMEM = {
|
||||
0, // 0x00 Z_Data_Type::Z_Unknown
|
||||
sizeof(Z_Data_Light), // 0x01 Z_Data_Type::Z_Light
|
||||
|
@ -507,8 +533,7 @@ const uint8_t Z_Data_Type_len[] PROGMEM = {
|
|||
sizeof(Z_Data_PIR), // 0x03 Z_Data_Type::Z_PIR
|
||||
sizeof(Z_Data_Alarm), // 0x04 Z_Data_Type::Z_Alarm
|
||||
sizeof(Z_Data_Thermo), // 0x05 Z_Data_Type::Z_Thermo
|
||||
sizeof(Z_Data_OnOff), // 0x05 Z_Data_Type::Z_OnOff
|
||||
0, // 0x06
|
||||
sizeof(Z_Data_OnOff), // 0x06 Z_Data_Type::Z_OnOff
|
||||
0, // 0x07
|
||||
0, // 0x08
|
||||
0, // 0x09
|
||||
|
@ -516,7 +541,7 @@ const uint8_t Z_Data_Type_len[] PROGMEM = {
|
|||
0, // 0x0B
|
||||
0, // 0x0C
|
||||
0, // 0x0D
|
||||
0, // 0x0E
|
||||
sizeof(Z_Data_Mode), // 0x0E
|
||||
0, // 0x0F Z_Data_Type::Z_Ext
|
||||
// '_' maps to 0xFF Z_Data_Type::Z_Device
|
||||
};
|
||||
|
@ -549,6 +574,8 @@ public:
|
|||
|
||||
template <class M>
|
||||
M & get(uint8_t ep = 0);
|
||||
template <class M>
|
||||
const M & getConst(uint8_t ep = 0) const;
|
||||
|
||||
template <class M>
|
||||
const M & find(uint8_t ep = 0) const;
|
||||
|
@ -569,6 +596,7 @@ bool Z_Data_Set::updateData(Z_Data & elt) {
|
|||
case Z_Data_Type::Z_Thermo: return ((Z_Data_Thermo&) elt).update(); break;
|
||||
case Z_Data_Type::Z_OnOff: return ((Z_Data_OnOff&) elt).update(); break;
|
||||
case Z_Data_Type::Z_PIR: return ((Z_Data_PIR&) elt).update(); break;
|
||||
case Z_Data_Type::Z_Mode: return ((Z_Data_Mode&) elt).update(); break;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
@ -587,6 +615,8 @@ Z_Data & Z_Data_Set::getByType(Z_Data_Type type, uint8_t ep) {
|
|||
return get<Z_Data_OnOff>(ep);
|
||||
case Z_Data_Type::Z_PIR:
|
||||
return get<Z_Data_PIR>(ep);
|
||||
case Z_Data_Type::Z_Mode:
|
||||
return get<Z_Data_Mode>(ep);
|
||||
default:
|
||||
return *(Z_Data*)nullptr;
|
||||
}
|
||||
|
@ -626,6 +656,11 @@ M & Z_Data_Set::get(uint8_t ep) {
|
|||
M & m = (M&) find(M::type, ep);
|
||||
return addIfNull<M>(m, ep);
|
||||
}
|
||||
template <class M>
|
||||
const M & Z_Data_Set::getConst(uint8_t ep) const {
|
||||
const M & m = (M&) find(M::type, ep);
|
||||
return m;
|
||||
}
|
||||
|
||||
template <class M>
|
||||
const M & Z_Data_Set::find(uint8_t ep) const {
|
||||
|
@ -917,6 +952,7 @@ public:
|
|||
// Find device by name, can be short_addr, long_addr, number_in_array or name
|
||||
Z_Device & parseDeviceFromName(const char * param, bool short_must_be_known = false);
|
||||
|
||||
bool isTuyaProtocol(uint16_t shortaddr, uint8_t ep = 0) const;
|
||||
|
||||
private:
|
||||
LList<Z_Device> _devices; // list of devices
|
||||
|
|
|
@ -830,13 +830,16 @@ int32_t Z_Devices::deviceRestore(JsonParserObject json) {
|
|||
for (auto config_elt : arr_config) {
|
||||
const char * conf_str = config_elt.getStr();
|
||||
Z_Data_Type data_type;
|
||||
uint8_t ep, config;
|
||||
uint8_t ep = 0;
|
||||
uint8_t config = 0xF; // default = no config
|
||||
|
||||
if (Z_Data::ConfigToZData(conf_str, &data_type, &ep, &config)) {
|
||||
Z_Data & data = device.data.getByType(data_type, ep);
|
||||
if (&data != nullptr) {
|
||||
data.setConfig(config);
|
||||
}
|
||||
} else {
|
||||
AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "Ignoring config '%s'"), conf_str);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -848,6 +851,17 @@ Z_Data_Light & Z_Devices::getLight(uint16_t shortaddr) {
|
|||
return getShortAddr(shortaddr).data.get<Z_Data_Light>();
|
||||
}
|
||||
|
||||
bool Z_Devices::isTuyaProtocol(uint16_t shortaddr, uint8_t ep) const {
|
||||
const Z_Device & device = findShortAddr(shortaddr);
|
||||
if (device.valid()) {
|
||||
const Z_Data_Mode & mode = device.data.getConst<Z_Data_Mode>(ep);
|
||||
if (&mode != nullptr) {
|
||||
return mode.isTuyaProtocol();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/*********************************************************************************************\
|
||||
* Device specific data handlers
|
||||
\*********************************************************************************************/
|
||||
|
|
|
@ -42,9 +42,12 @@ enum Z_DataTypes {
|
|||
ZEUI64 = 0xF0, Zkey128 = 0xF1,
|
||||
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)
|
||||
Ztuya0 = Zoctstr,
|
||||
Ztuya1 = Zbool,
|
||||
Ztuya2 = Zint32,
|
||||
Ztuya3 = Zstring,
|
||||
Ztuya4 = Zuint8,
|
||||
Ztuya5 = Zuint32
|
||||
};
|
||||
|
||||
//
|
||||
|
@ -65,18 +68,13 @@ uint8_t Z_getDatatypeLen(uint8_t t) {
|
|||
case Zsemi:
|
||||
case ZclusterId:
|
||||
case ZattribId:
|
||||
case Ztuya1:
|
||||
return 2;
|
||||
case Ztuya2:
|
||||
return 3;
|
||||
case Zsingle:
|
||||
case ZToD:
|
||||
case Zdate:
|
||||
case ZUTC:
|
||||
case ZbacOID:
|
||||
return 4;
|
||||
case Ztuya4:
|
||||
return 5;
|
||||
case Zdouble:
|
||||
case ZEUI64:
|
||||
return 8;
|
||||
|
@ -603,8 +601,15 @@ const Z_AttributeConverter Z_PostProcess[] PROGMEM = {
|
|||
{ Zuint8, Cx0B05, 0x011D, Z_(LastMessageRSSI), Cm1, 0 },
|
||||
|
||||
// Tuya Moes specific - 0xEF00
|
||||
{ Zoctstr, CxEF00, 0x0070, Z_(TuyaScheduleWorkdays), Cm1, 0 },
|
||||
{ Zoctstr, CxEF00, 0x0071, Z_(TuyaScheduleHolidays), Cm1, 0 },
|
||||
// Mapping of Tuya type with internal mapping
|
||||
// 0x00 - Zoctstr (len N)
|
||||
// 0x01 - Ztuya1 (len 1) - equivalent to Zuint8 without invalid value handling
|
||||
// 0x02 - Ztuya4 (len 4) - equivalent to Zint32 in big endian and without invalid value handling
|
||||
// 0x03 - Zstr (len N)
|
||||
// 0x04 - Ztuya1 (len 1)
|
||||
// 0x05 - Ztuya4u (len 1/2/4) - equivalent to Zuint32
|
||||
{ Ztuya0, CxEF00, 0x0070, Z_(TuyaScheduleWorkdays), Cm1, 0 },
|
||||
{ Ztuya0, CxEF00, 0x0071, Z_(TuyaScheduleHolidays), Cm1, 0 },
|
||||
{ Ztuya1, CxEF00, 0x0101, Z_(Power), Cm1, 0 },
|
||||
{ Ztuya1, CxEF00, 0x0102, Z_(Power2), Cm1, 0 },
|
||||
{ Ztuya1, CxEF00, 0x0103, Z_(Power3), Cm1, 0 },
|
||||
|
@ -613,21 +618,21 @@ const Z_AttributeConverter Z_PostProcess[] PROGMEM = {
|
|||
{ Ztuya1, CxEF00, 0x0112, Z_(TuyaWindowDetection), Cm1, 0 },
|
||||
{ Ztuya1, CxEF00, 0x0114, Z_(TuyaValveDetection), Cm1, 0 },
|
||||
{ Ztuya1, CxEF00, 0x0174, Z_(TuyaAutoLock), Cm1, 0 },
|
||||
{ Ztuya4, CxEF00, 0x0202, Z_(TuyaTempTarget), Cm_10, 0 },
|
||||
{ Ztuya4, CxEF00, 0x0203, Z_(LocalTemperature), Cm_10, 0 }, // will be overwritten by actual LocalTemperature
|
||||
{ Ztuya1, CxEF00, 0x0215, Z_(TuyaBattery), Cm1, 0 }, // TODO check equivalent?
|
||||
{ Ztuya4, CxEF00, 0x0266, Z_(TuyaMinTemp), Cm1, 0 },
|
||||
{ Ztuya4, CxEF00, 0x0267, Z_(TuyaMaxTemp), Cm1, 0 },
|
||||
{ Ztuya4, CxEF00, 0x0269, Z_(TuyaBoostTime), Cm1, 0 },
|
||||
{ Ztuya4, CxEF00, 0x026B, Z_(TuyaComfortTemp), Cm1, 0 },
|
||||
{ Ztuya4, CxEF00, 0x026C, Z_(TuyaEcoTemp), Cm1, 0 },
|
||||
{ Ztuya1, CxEF00, 0x026D, Z_(TuyaValvePosition), Cm1, 0 },
|
||||
{ Ztuya4, CxEF00, 0x0272, Z_(TuyaAwayTemp), Cm1, 0 },
|
||||
{ Ztuya4, CxEF00, 0x0275, Z_(TuyaAwayDays), Cm1, 0 },
|
||||
{ Ztuya1, CxEF00, 0x0404, Z_(TuyaPreset), Cm1, 0 },
|
||||
{ Ztuya1, CxEF00, 0x0405, Z_(TuyaFanMode), Cm1, 0 },
|
||||
{ Ztuya1, CxEF00, 0x046A, Z_(TuyaForceMode), Cm1, 0 },
|
||||
{ Ztuya1, CxEF00, 0x046F, Z_(TuyaWeekSelect), Cm1, 0 },
|
||||
{ Ztuya2, CxEF00, 0x0202, Z_(TuyaTempTarget), Cm_10, 0 },
|
||||
{ Ztuya2, CxEF00, 0x0203, Z_(LocalTemperature), Cm_10, 0 }, // will be overwritten by actual LocalTemperature
|
||||
{ Ztuya2, CxEF00, 0x0215, Z_(TuyaBattery), Cm1, 0 }, // TODO check equivalent?
|
||||
{ Ztuya2, CxEF00, 0x0266, Z_(TuyaMinTemp), Cm1, 0 },
|
||||
{ Ztuya2, CxEF00, 0x0267, Z_(TuyaMaxTemp), Cm1, 0 },
|
||||
{ Ztuya2, CxEF00, 0x0269, Z_(TuyaBoostTime), Cm1, 0 },
|
||||
{ Ztuya2, CxEF00, 0x026B, Z_(TuyaComfortTemp), Cm1, 0 },
|
||||
{ Ztuya2, CxEF00, 0x026C, Z_(TuyaEcoTemp), Cm1, 0 },
|
||||
{ Ztuya2, CxEF00, 0x026D, Z_(TuyaValvePosition), Cm1, 0 },
|
||||
{ Ztuya2, CxEF00, 0x0272, Z_(TuyaAwayTemp), Cm1, 0 },
|
||||
{ Ztuya2, CxEF00, 0x0275, Z_(TuyaAwayDays), Cm1, 0 },
|
||||
{ Ztuya4, CxEF00, 0x0404, Z_(TuyaPreset), Cm1, 0 },
|
||||
{ Ztuya4, CxEF00, 0x0405, Z_(TuyaFanMode), Cm1, 0 },
|
||||
{ Ztuya4, CxEF00, 0x046A, Z_(TuyaForceMode), Cm1, 0 },
|
||||
{ Ztuya4, CxEF00, 0x046F, Z_(TuyaWeekSelect), Cm1, 0 },
|
||||
|
||||
// Terncy specific - 0xFCCC
|
||||
{ Zuint16, CxFCCC, 0x001A, Z_(TerncyDuration), Cm1, 0 },
|
||||
|
@ -863,10 +868,6 @@ int32_t encodeSingleAttribute(class SBuffer &buf, double val_d, const char *val_
|
|||
case Zmap8: // map8
|
||||
buf.add8(u32);
|
||||
break;
|
||||
case Ztuya1: // tuya specific 1 byte
|
||||
buf.add8(1); // len
|
||||
buf.add8(u32);
|
||||
break;
|
||||
// unsigned 16
|
||||
case Zuint16: // uint16
|
||||
case Zenum16: // enum16
|
||||
|
@ -874,9 +875,6 @@ int32_t encodeSingleAttribute(class SBuffer &buf, double val_d, const char *val_
|
|||
case Zmap16: // map16
|
||||
buf.add16(u32);
|
||||
break;
|
||||
case Ztuya2:
|
||||
buf.add8(2); // len
|
||||
buf.add16BigEndian(u32);
|
||||
// unisgned 32
|
||||
case Zuint32: // uint32
|
||||
case Zdata32: // data32
|
||||
|
@ -895,10 +893,6 @@ int32_t encodeSingleAttribute(class SBuffer &buf, double val_d, const char *val_
|
|||
case Zint32: // int32
|
||||
buf.add32(i32);
|
||||
break;
|
||||
case Ztuya4:
|
||||
buf.add8(4); // len
|
||||
buf.add32BigEndian(i32);
|
||||
break;
|
||||
|
||||
case Zsingle: // float
|
||||
buf.add32( *((uint32_t*)&f32) ); // cast float as uint32_t
|
||||
|
@ -987,15 +981,6 @@ uint32_t parseSingleAttribute(Z_attribute & attr, const SBuffer &buf,
|
|||
}
|
||||
}
|
||||
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: int40, int48, int56, int64 are displayed as Hex
|
||||
case Zuint40: // uint40
|
||||
|
@ -1960,6 +1945,39 @@ void Z_postProcessAttributes(uint16_t shortaddr, uint16_t src_ep, class Z_attrib
|
|||
}
|
||||
}
|
||||
|
||||
// Internal search function
|
||||
void Z_parseAttributeKey_inner(class Z_attribute & attr, uint16_t preferred_cluster) {
|
||||
// scan attributes to find by name, and retrieve type
|
||||
for (uint32_t i = 0; i < ARRAY_SIZE(Z_PostProcess); i++) {
|
||||
const Z_AttributeConverter *converter = &Z_PostProcess[i];
|
||||
uint16_t local_attr_id = pgm_read_word(&converter->attribute);
|
||||
uint16_t local_cluster_id = CxToCluster(pgm_read_byte(&converter->cluster_short));
|
||||
uint8_t local_type_id = pgm_read_byte(&converter->type);
|
||||
int8_t local_multiplier = CmToMultiplier(pgm_read_byte(&converter->multiplier_idx));
|
||||
// AddLog_P(LOG_LEVEL_DEBUG, PSTR("Try cluster = 0x%04X, attr = 0x%04X, type_id = 0x%02X"), local_cluster_id, local_attr_id, local_type_id);
|
||||
|
||||
if (!attr.key_is_str) {
|
||||
if ((attr.key.id.cluster == local_cluster_id) && (attr.key.id.attr_id == local_attr_id)) {
|
||||
attr.attr_type = local_type_id;
|
||||
break;
|
||||
}
|
||||
} else if (pgm_read_word(&converter->name_offset)) {
|
||||
const char * key = attr.key.key;
|
||||
// AddLog_P(LOG_LEVEL_DEBUG, PSTR("Comparing '%s' with '%s'"), attr_name, converter->name);
|
||||
if (0 == strcasecmp_P(key, Z_strings + pgm_read_word(&converter->name_offset))) {
|
||||
if ((preferred_cluster == 0xFFFF) || // any cluster
|
||||
(local_cluster_id == preferred_cluster)) {
|
||||
// match
|
||||
attr.setKeyId(local_cluster_id, local_attr_id);
|
||||
attr.attr_type = local_type_id;
|
||||
attr.attr_multiplier = local_multiplier;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Given an attribute string, retrieve all attribute details.
|
||||
// Input: the attribute has a key name, either: <cluster>/<attr> or <cluster>/<attr>%<type> or "<attribute_name>"
|
||||
|
@ -1976,7 +1994,7 @@ void Z_postProcessAttributes(uint16_t shortaddr, uint16_t src_ep, class Z_attrib
|
|||
// Note: the attribute value is unchanged and unparsed
|
||||
//
|
||||
// Note: if the type is specified in the key, the multiplier is not applied, no conversion happens
|
||||
bool Z_parseAttributeKey(class Z_attribute & attr) {
|
||||
bool Z_parseAttributeKey(class Z_attribute & attr, uint16_t preferred_cluster) {
|
||||
// check if the name has the format "XXXX/YYYY" where XXXX is the cluster, YYYY the attribute id
|
||||
// alternative "XXXX/YYYY%ZZ" where ZZ is the type (for unregistered attributes)
|
||||
if (attr.key_is_str) {
|
||||
|
@ -2002,33 +2020,11 @@ bool Z_parseAttributeKey(class Z_attribute & attr) {
|
|||
// AddLog_P(LOG_LEVEL_DEBUG, PSTR("cluster_id = 0x%04X, attr_id = 0x%04X"), cluster_id, attr_id);
|
||||
|
||||
// do we already know the type, i.e. attribute and cluster are also known
|
||||
if ((Zunk == attr.attr_type) && (preferred_cluster != 0xFFFF)) {
|
||||
Z_parseAttributeKey_inner(attr, preferred_cluster); // try to find with the selected cluster
|
||||
}
|
||||
if (Zunk == attr.attr_type) {
|
||||
// scan attributes to find by name, and retrieve type
|
||||
for (uint32_t i = 0; i < ARRAY_SIZE(Z_PostProcess); i++) {
|
||||
const Z_AttributeConverter *converter = &Z_PostProcess[i];
|
||||
uint16_t local_attr_id = pgm_read_word(&converter->attribute);
|
||||
uint16_t local_cluster_id = CxToCluster(pgm_read_byte(&converter->cluster_short));
|
||||
uint8_t local_type_id = pgm_read_byte(&converter->type);
|
||||
int8_t local_multiplier = CmToMultiplier(pgm_read_byte(&converter->multiplier_idx));
|
||||
// AddLog_P(LOG_LEVEL_DEBUG, PSTR("Try cluster = 0x%04X, attr = 0x%04X, type_id = 0x%02X"), local_cluster_id, local_attr_id, local_type_id);
|
||||
|
||||
if (!attr.key_is_str) {
|
||||
if ((attr.key.id.cluster == local_cluster_id) && (attr.key.id.attr_id == local_attr_id)) {
|
||||
attr.attr_type = local_type_id;
|
||||
break;
|
||||
}
|
||||
} else if (pgm_read_word(&converter->name_offset)) {
|
||||
const char * key = attr.key.key;
|
||||
// AddLog_P(LOG_LEVEL_DEBUG, PSTR("Comparing '%s' with '%s'"), attr_name, converter->name);
|
||||
if (0 == strcasecmp_P(key, Z_strings + pgm_read_word(&converter->name_offset))) {
|
||||
// match
|
||||
attr.setKeyId(local_cluster_id, local_attr_id);
|
||||
attr.attr_type = local_type_id;
|
||||
attr.attr_multiplier = local_multiplier;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
Z_parseAttributeKey_inner(attr, 0xFFFF); // try again with any cluster
|
||||
}
|
||||
return (Zunk != attr.attr_type) ? true : false;
|
||||
}
|
||||
|
|
|
@ -460,6 +460,41 @@ void convertClusterSpecific(class Z_attribute_list &attr_list, uint16_t cluster,
|
|||
}
|
||||
}
|
||||
|
||||
void parseSingleTuyaAttribute(Z_attribute & attr, const SBuffer &buf,
|
||||
uint32_t i, uint32_t len, int32_t attrtype) {
|
||||
|
||||
// fallback - enter a null value
|
||||
attr.setNone(); // set to null by default
|
||||
|
||||
// now parse accordingly to attr type
|
||||
switch (attrtype) {
|
||||
case 0x00: // RAW octstr
|
||||
attr.setBuf(buf, i, len);
|
||||
break;
|
||||
case 0x01: // Bool, values 0/1, len = 1
|
||||
case 0x04: // enum 8 bits
|
||||
attr.setUInt(buf.get8(i));
|
||||
break;
|
||||
case 0x02: // 4 bytes value (signed?)
|
||||
attr.setUInt(buf.get32BigEndian(i));
|
||||
break;
|
||||
case 0x03: // String (we expect it is not ended with \00)
|
||||
char str[len+1];
|
||||
strncpy(str, buf.charptr(i), len);
|
||||
str[len] = 0x00;
|
||||
attr.setStr(str);
|
||||
break;
|
||||
case 0x05: // enum in 1/2/4 bytes, Big Endian
|
||||
if (1 == len) {
|
||||
attr.setUInt(buf.get8(i));
|
||||
} else if (2 == len) {
|
||||
attr.setUInt(buf.get16BigEndian(i));
|
||||
} else if (4 == len) {
|
||||
attr.setUInt(buf.get32BigEndian(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Tuya - MOES specifc cluster 0xEF00
|
||||
// See https://medium.com/@dzegarra/zigbee2mqtt-how-to-add-support-for-a-new-tuya-based-device-part-2-5492707e882d
|
||||
|
@ -468,21 +503,14 @@ void convertClusterSpecific(class Z_attribute_list &attr_list, uint16_t cluster,
|
|||
bool convertTuyaSpecificCluster(class Z_attribute_list &attr_list, uint16_t cluster, uint8_t cmd, bool direction, uint16_t shortaddr, uint8_t srcendpoint, const SBuffer &buf) {
|
||||
// uint8_t status = buf.get8(0);
|
||||
// uint8_t transid = buf.get8(1);
|
||||
uint16_t dp = buf.get16(2); // decode dp as 16 bits little endian - which is not big endian as mentioned in documentation
|
||||
// uint8_t fn = buf.get8(4);
|
||||
uint8_t len = buf.get8(5); // len of payload
|
||||
uint8_t dp = buf.get8(2); // dpid from Tuya documentation
|
||||
uint8_t attr_type = buf.get8(3); // data type from Tuya documentation
|
||||
uint16_t len = buf.get16BigEndian(4);
|
||||
|
||||
if ((1 == cmd) || (2 == cmd)) { // attribute report or attribute response
|
||||
// create a synthetic attribute with id 'dp'
|
||||
Z_attribute & attr = attr_list.addAttribute(cluster, dp);
|
||||
uint8_t attr_type;
|
||||
switch (len) {
|
||||
case 1: attr_type = Ztuya1; break;
|
||||
case 2: attr_type = Ztuya2; break;
|
||||
case 4: attr_type = Ztuya4; break;
|
||||
default: attr_type = Zoctstr; break;
|
||||
}
|
||||
parseSingleAttribute(attr, buf, 5, attr_type);
|
||||
Z_attribute & attr = attr_list.addAttribute(cluster, (attr_type << 8) | dp);
|
||||
parseSingleTuyaAttribute(attr, buf, 6, len, attr_type);
|
||||
return true; // true = remove the original Tuya attribute
|
||||
}
|
||||
return false;
|
||||
|
|
|
@ -700,6 +700,7 @@ int32_t Z_ReceiveSimpleDesc(int32_t res, const class SBuffer &buf) {
|
|||
const size_t numInIndex = 11;
|
||||
const size_t numOutIndex = 12 + numInCluster*2;
|
||||
#endif
|
||||
bool is_tuya_protocol = false;
|
||||
|
||||
if (0 == status) {
|
||||
// device is reachable
|
||||
|
@ -710,14 +711,15 @@ int32_t Z_ReceiveSimpleDesc(int32_t res, const class SBuffer &buf) {
|
|||
}
|
||||
|
||||
Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATE "\":{"
|
||||
"\"Status\":%d,\"Endpoint\":\"0x%02X\""
|
||||
"\"Status\":%d,\"Device\":\"0x%04X\",\"Endpoint\":\"0x%02X\""
|
||||
",\"ProfileId\":\"0x%04X\",\"DeviceId\":\"0x%04X\",\"DeviceVersion\":%d"
|
||||
",\"InClusters\":["),
|
||||
ZIGBEE_STATUS_SIMPLE_DESC, endpoint,
|
||||
ZIGBEE_STATUS_SIMPLE_DESC, nwkAddr, endpoint,
|
||||
profileId, deviceId, deviceVersion);
|
||||
for (uint32_t i = 0; i < numInCluster; i++) {
|
||||
if (i > 0) { ResponseAppend_P(PSTR(",")); }
|
||||
ResponseAppend_P(PSTR("\"0x%04X\""), buf.get16(numInIndex + i*2));
|
||||
if (buf.get16(numInIndex + i*2) == 0xEF00) { is_tuya_protocol = true; } // tuya protocol
|
||||
}
|
||||
ResponseAppend_P(PSTR("],\"OutClusters\":["));
|
||||
for (uint32_t i = 0; i < numOutCluster; i++) {
|
||||
|
@ -729,6 +731,14 @@ int32_t Z_ReceiveSimpleDesc(int32_t res, const class SBuffer &buf) {
|
|||
XdrvRulesProcess();
|
||||
}
|
||||
|
||||
// If tuya protocol, change the model information
|
||||
if (is_tuya_protocol) {
|
||||
Z_Device & device = zigbee_devices.getShortAddr(nwkAddr);
|
||||
device.addEndpoint(endpoint);
|
||||
device.data.get<Z_Data_Mode>(endpoint).setConfig(ZM_Tuya);
|
||||
zigbee_devices.dirty();
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
@ -2036,7 +2046,7 @@ void ZCLFrame::autoResponder(const uint16_t *attr_list_ids, size_t attr_len) {
|
|||
break;
|
||||
}
|
||||
if (!attr.isNone()) {
|
||||
Z_parseAttributeKey(attr);
|
||||
Z_parseAttributeKey(attr, _cluster_id);
|
||||
attr_list.addAttribute(_cluster_id, attr_id) = attr;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -237,7 +237,7 @@ void ZbApplyMultiplier(double &val_d, int8_t multiplier) {
|
|||
//
|
||||
// Write Tuya-Moes attribute
|
||||
//
|
||||
bool ZbTuyaWrite(SBuffer & buf, const Z_attribute & attr, uint8_t transid) {
|
||||
bool ZbTuyaWrite(SBuffer & buf, const Z_attribute & attr) {
|
||||
double val_d = attr.getFloat();
|
||||
const char * val_str = attr.getStr();
|
||||
|
||||
|
@ -245,14 +245,50 @@ bool ZbTuyaWrite(SBuffer & buf, const Z_attribute & attr, uint8_t transid) {
|
|||
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_P(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;
|
||||
uint32_t u32 = val_d;
|
||||
int32_t i32 = val_d;
|
||||
|
||||
uint8_t tuyatype = (attr.key.id.attr_id >> 8);
|
||||
uint8_t dpid = (attr.key.id.attr_id & 0xFF);
|
||||
buf.add8(tuyatype);
|
||||
buf.add8(dpid);
|
||||
|
||||
// the next attribute is length 16 bits in big endian
|
||||
// high byte is always 0x00
|
||||
buf.add8(0);
|
||||
|
||||
switch (tuyatype) {
|
||||
case 0x00: // raw
|
||||
{
|
||||
SBuffer buf_raw = SBuffer::SBufferFromHex(val_str, strlen(val_str));
|
||||
if (buf_raw.len() > 255) { return false; }
|
||||
buf.add8(buf_raw.len());
|
||||
buf.addBuffer(buf_raw);
|
||||
}
|
||||
break;
|
||||
case 0x01: // Boolean = uint8
|
||||
case 0x04: // enum uint8
|
||||
buf.add8(1);
|
||||
buf.add8(u32);
|
||||
break;
|
||||
case 0x02: // int32
|
||||
buf.add8(4);
|
||||
buf.add32BigEndian(i32);
|
||||
break;
|
||||
case 0x03: // String
|
||||
{
|
||||
uint32_t s_len = strlen(val_str);
|
||||
if (s_len > 255) { return false; }
|
||||
buf.add8(s_len);
|
||||
buf.addBuffer(val_str, s_len);
|
||||
}
|
||||
break;
|
||||
case 0x05: // bitmap 1/2/4 so we use 4 bytes
|
||||
buf.add8(4);
|
||||
buf.add32BigEndian(u32);
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -296,13 +332,15 @@ void ZbSendReportWrite(class JsonParserToken val_pubwrite, class ZigbeeZCLSendMe
|
|||
XdrvMailbox.command = (char*) ""; // prevent a crash when calling ReponseCmndChar and there was no previous command
|
||||
}
|
||||
|
||||
bool tuya_protocol = zigbee_devices.isTuyaProtocol(packet.shortaddr, packet.endpoint);
|
||||
|
||||
// iterate on keys
|
||||
for (auto key : val_pubwrite.getObject()) {
|
||||
JsonParserToken value = key.getValue();
|
||||
|
||||
Z_attribute attr;
|
||||
attr.setKeyName(key.getStr());
|
||||
if (Z_parseAttributeKey(attr)) {
|
||||
if (Z_parseAttributeKey(attr, tuya_protocol ? 0xEF00 : 0xFFFF)) { // favor tuya protocol if needed
|
||||
// Buffer ready, do some sanity checks
|
||||
|
||||
// all attributes must use the same cluster
|
||||
|
@ -337,14 +375,15 @@ void ZbSendReportWrite(class JsonParserToken val_pubwrite, class ZigbeeZCLSendMe
|
|||
// Split encoding depending on message
|
||||
if (packet.cmd != ZCL_CONFIGURE_REPORTING) {
|
||||
if ((packet.cluster == 0XEF00) && (packet.cmd == ZCL_WRITE_ATTRIBUTES)) {
|
||||
// special case of Tuya / Moes attributes
|
||||
if (buf.len() > 0) {
|
||||
AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "Only 1 attribute allowed for Tuya"));
|
||||
return;
|
||||
// special case of Tuya / Moes / Lidl attributes
|
||||
if (buf.len() == 0) {
|
||||
// add the prefix to data
|
||||
buf.add8(0); // status
|
||||
buf.add8(zigbee_devices.getNextSeqNumber(packet.shortaddr));
|
||||
}
|
||||
packet.clusterSpecific = true;
|
||||
packet.cmd = 0x00;
|
||||
if (!ZbTuyaWrite(buf, attr, zigbee_devices.getNextSeqNumber(packet.shortaddr))) {
|
||||
if (!ZbTuyaWrite(buf, attr)) {
|
||||
return; // error
|
||||
}
|
||||
} else if (!ZbAppendWriteBuf(buf, attr, packet.cmd == ZCL_READ_ATTRIBUTES_RESPONSE)) { // general case
|
||||
|
|
Loading…
Reference in New Issue