mirror of
https://github.com/arendst/Tasmota.git
synced 2025-07-25 19:56:30 +00:00
Merge branch 'development' into pre-release-9.2.0
This commit is contained in:
commit
ae793c8f2d
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
@ -6,7 +6,7 @@
|
|||||||
- [ ] The pull request is done against the latest dev branch
|
- [ ] The pull request is done against the latest dev branch
|
||||||
- [ ] Only relevant files were touched
|
- [ ] Only relevant files were touched
|
||||||
- [ ] Only one feature/fix was added per PR and the code change compiles without warnings
|
- [ ] Only one feature/fix was added per PR and the code change compiles without warnings
|
||||||
- [ ] The code change is tested and works on Tasmota core ESP8266 V.2.7.4.7
|
- [ ] The code change is tested and works on Tasmota core ESP8266 V.2.7.4.9
|
||||||
- [ ] The code change is tested and works on Tasmota core ESP32 V.1.0.4.2
|
- [ ] The code change is tested and works on Tasmota core ESP32 V.1.0.4.2
|
||||||
- [ ] I accept the [CLA](https://github.com/arendst/Tasmota/blob/development/CONTRIBUTING.md#contributor-license-agreement-cla).
|
- [ ] I accept the [CLA](https://github.com/arendst/Tasmota/blob/development/CONTRIBUTING.md#contributor-license-agreement-cla).
|
||||||
|
|
||||||
|
@ -144,11 +144,14 @@ build_flags = -DUSE_IR_REMOTE_FULL
|
|||||||
|
|
||||||
|
|
||||||
[core]
|
[core]
|
||||||
; *** Esp8266 Tasmota modified Arduino core based on core 2.7.4
|
; *** Esp8266 Tasmota modified Arduino core based on core 2.7.4. Added Backport for PWM selection
|
||||||
platform = espressif8266 @ 2.6.2
|
platform = espressif8266 @ 2.6.2
|
||||||
platform_packages = tasmota/framework-arduinoespressif8266 @ 3.20704.7
|
platform_packages = tasmota/framework-arduinoespressif8266 @ ~2.7.4
|
||||||
platformio/toolchain-xtensa @ 2.40802.200502
|
platformio/toolchain-xtensa @ 2.40802.200502
|
||||||
platformio/tool-esptool @ 1.413.0
|
platformio/tool-esptool @ 1.413.0
|
||||||
platformio/tool-esptoolpy @ ~1.30000.0
|
platformio/tool-esptoolpy @ ~1.30000.0
|
||||||
build_unflags = ${esp_defaults.build_unflags}
|
build_unflags = ${esp_defaults.build_unflags}
|
||||||
build_flags = ${esp82xx_defaults.build_flags}
|
build_flags = ${esp82xx_defaults.build_flags}
|
||||||
|
; *** Use ONE of the two PWM variants. Tasmota default is Locked PWM
|
||||||
|
;-DWAVEFORM_LOCKED_PHASE
|
||||||
|
-DWAVEFORM_LOCKED_PWM
|
||||||
|
@ -43,7 +43,7 @@ uint32_t stack_thunk_light_refcnt = 0;
|
|||||||
//#define _stackSize (5600/4)
|
//#define _stackSize (5600/4)
|
||||||
#if defined(USE_MQTT_AWS_IOT)
|
#if defined(USE_MQTT_AWS_IOT)
|
||||||
#define _stackSize (5300/4) // using a light version of bearssl we can save 300 bytes
|
#define _stackSize (5300/4) // using a light version of bearssl we can save 300 bytes
|
||||||
#elif defined(USE_MQTT_TLS_FORCE_EC_CIPHER)
|
#elif defined(USE_MQTT_TLS_FORCE_EC_CIPHER) || defined(USE_4K_RSA)
|
||||||
#define _stackSize (4800/4) // no private key, we can reduce a little, max observed 4300
|
#define _stackSize (4800/4) // no private key, we can reduce a little, max observed 4300
|
||||||
#else
|
#else
|
||||||
#define _stackSize (3600/4) // using a light version of bearssl we can save 2k
|
#define _stackSize (3600/4) // using a light version of bearssl we can save 2k
|
||||||
|
@ -177,6 +177,13 @@ public:
|
|||||||
}
|
}
|
||||||
return 0;
|
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 {
|
int32_t get32IBigEndian(const size_t offset) const {
|
||||||
if (offset < len() - 3) {
|
if (offset < len() - 3) {
|
||||||
return _buf->buf[offset+3] | (_buf->buf[offset+2] << 8) |
|
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_Alarm = 4,
|
||||||
Z_Thermo = 5, // Thermostat and sensor for home environment (temp, himudity, pressure)
|
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_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_Ext = 0xF, // extended for other values
|
||||||
Z_Device = 0xFF // special value when parsing Device level attributes
|
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
|
'I', // 0x03 Z_Data_Type::Z_PIR
|
||||||
'A', // 0x04 Z_Data_Type::Z_Alarm
|
'A', // 0x04 Z_Data_Type::Z_Alarm
|
||||||
'T', // 0x05 Z_Data_Type::Z_Thermo
|
'T', // 0x05 Z_Data_Type::Z_Thermo
|
||||||
'O', // 0x05 Z_Data_Type::Z_OnOff
|
'O', // 0x06 Z_Data_Type::Z_OnOff
|
||||||
'\0', // 0x06
|
|
||||||
'\0', // 0x07
|
'\0', // 0x07
|
||||||
'\0', // 0x08
|
'\0', // 0x08
|
||||||
'\0', // 0x09
|
'\0', // 0x09
|
||||||
@ -52,7 +52,7 @@ const uint8_t Z_Data_Type_char[] PROGMEM = {
|
|||||||
'\0', // 0x0B
|
'\0', // 0x0B
|
||||||
'\0', // 0x0C
|
'\0', // 0x0C
|
||||||
'\0', // 0x0D
|
'\0', // 0x0D
|
||||||
'\0', // 0x0E
|
'~', // 0x0E
|
||||||
'E', // 0x0F Z_Data_Type::Z_Ext
|
'E', // 0x0F Z_Data_Type::Z_Ext
|
||||||
// '_' maps to 0xFF Z_Data_Type::Z_Device
|
// '_' 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...
|
* Device specific: Sensors: temp, humidity, pressure...
|
||||||
\*********************************************************************************************/
|
\*********************************************************************************************/
|
||||||
enum Z_Alarm_Type {
|
enum Z_Alarm_Type {
|
||||||
ZA_CIE = 0x0,
|
ZA_CIE = 0x0,
|
||||||
ZA_PIR = 0x1,
|
ZA_PIR = 0x1,
|
||||||
ZA_Contact = 0x2,
|
ZA_Contact = 0x2,
|
||||||
ZA_Fire = 0x3,
|
ZA_Fire = 0x3,
|
||||||
ZA_Water = 0x4,
|
ZA_Water = 0x4,
|
||||||
ZA_CO = 0x5,
|
ZA_CO = 0x5,
|
||||||
ZA_Personal = 0x6,
|
ZA_Personal = 0x6,
|
||||||
ZA_Movement = 0x7,
|
ZA_Movement = 0x7,
|
||||||
ZA_Panic = 0x8,
|
ZA_Panic = 0x8,
|
||||||
ZA_GlassBreak =0x9,
|
ZA_GlassBreak = 0x9,
|
||||||
};
|
};
|
||||||
|
|
||||||
class Z_Data_Thermo : public Z_Data {
|
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 = {
|
const uint8_t Z_Data_Type_len[] PROGMEM = {
|
||||||
0, // 0x00 Z_Data_Type::Z_Unknown
|
0, // 0x00 Z_Data_Type::Z_Unknown
|
||||||
sizeof(Z_Data_Light), // 0x01 Z_Data_Type::Z_Light
|
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_PIR), // 0x03 Z_Data_Type::Z_PIR
|
||||||
sizeof(Z_Data_Alarm), // 0x04 Z_Data_Type::Z_Alarm
|
sizeof(Z_Data_Alarm), // 0x04 Z_Data_Type::Z_Alarm
|
||||||
sizeof(Z_Data_Thermo), // 0x05 Z_Data_Type::Z_Thermo
|
sizeof(Z_Data_Thermo), // 0x05 Z_Data_Type::Z_Thermo
|
||||||
sizeof(Z_Data_OnOff), // 0x05 Z_Data_Type::Z_OnOff
|
sizeof(Z_Data_OnOff), // 0x06 Z_Data_Type::Z_OnOff
|
||||||
0, // 0x06
|
|
||||||
0, // 0x07
|
0, // 0x07
|
||||||
0, // 0x08
|
0, // 0x08
|
||||||
0, // 0x09
|
0, // 0x09
|
||||||
@ -516,7 +541,7 @@ const uint8_t Z_Data_Type_len[] PROGMEM = {
|
|||||||
0, // 0x0B
|
0, // 0x0B
|
||||||
0, // 0x0C
|
0, // 0x0C
|
||||||
0, // 0x0D
|
0, // 0x0D
|
||||||
0, // 0x0E
|
sizeof(Z_Data_Mode), // 0x0E
|
||||||
0, // 0x0F Z_Data_Type::Z_Ext
|
0, // 0x0F Z_Data_Type::Z_Ext
|
||||||
// '_' maps to 0xFF Z_Data_Type::Z_Device
|
// '_' maps to 0xFF Z_Data_Type::Z_Device
|
||||||
};
|
};
|
||||||
@ -549,6 +574,8 @@ public:
|
|||||||
|
|
||||||
template <class M>
|
template <class M>
|
||||||
M & get(uint8_t ep = 0);
|
M & get(uint8_t ep = 0);
|
||||||
|
template <class M>
|
||||||
|
const M & getConst(uint8_t ep = 0) const;
|
||||||
|
|
||||||
template <class M>
|
template <class M>
|
||||||
const M & find(uint8_t ep = 0) const;
|
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_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_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_PIR: return ((Z_Data_PIR&) elt).update(); break;
|
||||||
|
case Z_Data_Type::Z_Mode: return ((Z_Data_Mode&) elt).update(); break;
|
||||||
default: return false;
|
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);
|
return get<Z_Data_OnOff>(ep);
|
||||||
case Z_Data_Type::Z_PIR:
|
case Z_Data_Type::Z_PIR:
|
||||||
return get<Z_Data_PIR>(ep);
|
return get<Z_Data_PIR>(ep);
|
||||||
|
case Z_Data_Type::Z_Mode:
|
||||||
|
return get<Z_Data_Mode>(ep);
|
||||||
default:
|
default:
|
||||||
return *(Z_Data*)nullptr;
|
return *(Z_Data*)nullptr;
|
||||||
}
|
}
|
||||||
@ -626,6 +656,11 @@ M & Z_Data_Set::get(uint8_t ep) {
|
|||||||
M & m = (M&) find(M::type, ep);
|
M & m = (M&) find(M::type, ep);
|
||||||
return addIfNull<M>(m, 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>
|
template <class M>
|
||||||
const M & Z_Data_Set::find(uint8_t ep) const {
|
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
|
// 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);
|
Z_Device & parseDeviceFromName(const char * param, bool short_must_be_known = false);
|
||||||
|
|
||||||
|
bool isTuyaProtocol(uint16_t shortaddr, uint8_t ep = 0) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
LList<Z_Device> _devices; // list of devices
|
LList<Z_Device> _devices; // list of devices
|
||||||
|
@ -830,13 +830,16 @@ int32_t Z_Devices::deviceRestore(JsonParserObject json) {
|
|||||||
for (auto config_elt : arr_config) {
|
for (auto config_elt : arr_config) {
|
||||||
const char * conf_str = config_elt.getStr();
|
const char * conf_str = config_elt.getStr();
|
||||||
Z_Data_Type data_type;
|
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)) {
|
if (Z_Data::ConfigToZData(conf_str, &data_type, &ep, &config)) {
|
||||||
Z_Data & data = device.data.getByType(data_type, ep);
|
Z_Data & data = device.data.getByType(data_type, ep);
|
||||||
if (&data != nullptr) {
|
if (&data != nullptr) {
|
||||||
data.setConfig(config);
|
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>();
|
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
|
* Device specific data handlers
|
||||||
\*********************************************************************************************/
|
\*********************************************************************************************/
|
||||||
|
@ -42,9 +42,12 @@ enum Z_DataTypes {
|
|||||||
ZEUI64 = 0xF0, Zkey128 = 0xF1,
|
ZEUI64 = 0xF0, Zkey128 = 0xF1,
|
||||||
Zunk = 0xFF,
|
Zunk = 0xFF,
|
||||||
// adding fake type for Tuya specific encodings
|
// adding fake type for Tuya specific encodings
|
||||||
Ztuya1 = 0x80, // 1 byte unsigned int, Big Endian when output (input is taken care of)
|
Ztuya0 = Zoctstr,
|
||||||
Ztuya2 = 0x81, // 2 bytes unsigned, Big Endian when output (input is taken care of)
|
Ztuya1 = Zbool,
|
||||||
Ztuya4 = 0x82, // 4 bytes signed, Big Endian when output (input is taken care of)
|
Ztuya2 = Zint32,
|
||||||
|
Ztuya3 = Zstring,
|
||||||
|
Ztuya4 = Zuint8,
|
||||||
|
Ztuya5 = Zuint32
|
||||||
};
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
@ -65,18 +68,13 @@ 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;
|
||||||
@ -603,8 +601,15 @@ const Z_AttributeConverter Z_PostProcess[] PROGMEM = {
|
|||||||
{ Zuint8, Cx0B05, 0x011D, Z_(LastMessageRSSI), Cm1, 0 },
|
{ Zuint8, Cx0B05, 0x011D, Z_(LastMessageRSSI), Cm1, 0 },
|
||||||
|
|
||||||
// Tuya Moes specific - 0xEF00
|
// Tuya Moes specific - 0xEF00
|
||||||
{ Zoctstr, CxEF00, 0x0070, Z_(TuyaScheduleWorkdays), Cm1, 0 },
|
// Mapping of Tuya type with internal mapping
|
||||||
{ Zoctstr, CxEF00, 0x0071, Z_(TuyaScheduleHolidays), Cm1, 0 },
|
// 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, 0x0101, Z_(Power), Cm1, 0 },
|
||||||
{ Ztuya1, CxEF00, 0x0102, Z_(Power2), Cm1, 0 },
|
{ Ztuya1, CxEF00, 0x0102, Z_(Power2), Cm1, 0 },
|
||||||
{ Ztuya1, CxEF00, 0x0103, Z_(Power3), 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, 0x0112, Z_(TuyaWindowDetection), Cm1, 0 },
|
||||||
{ Ztuya1, CxEF00, 0x0114, Z_(TuyaValveDetection), Cm1, 0 },
|
{ Ztuya1, CxEF00, 0x0114, Z_(TuyaValveDetection), Cm1, 0 },
|
||||||
{ Ztuya1, CxEF00, 0x0174, Z_(TuyaAutoLock), Cm1, 0 },
|
{ Ztuya1, CxEF00, 0x0174, Z_(TuyaAutoLock), Cm1, 0 },
|
||||||
{ Ztuya4, CxEF00, 0x0202, Z_(TuyaTempTarget), Cm_10, 0 },
|
{ Ztuya2, CxEF00, 0x0202, Z_(TuyaTempTarget), Cm_10, 0 },
|
||||||
{ Ztuya4, CxEF00, 0x0203, Z_(LocalTemperature), Cm_10, 0 }, // will be overwritten by actual LocalTemperature
|
{ Ztuya2, CxEF00, 0x0203, Z_(LocalTemperature), Cm_10, 0 }, // will be overwritten by actual LocalTemperature
|
||||||
{ Ztuya1, CxEF00, 0x0215, Z_(TuyaBattery), Cm1, 0 }, // TODO check equivalent?
|
{ Ztuya2, CxEF00, 0x0215, Z_(TuyaBattery), Cm1, 0 }, // TODO check equivalent?
|
||||||
{ Ztuya4, CxEF00, 0x0266, Z_(TuyaMinTemp), Cm1, 0 },
|
{ Ztuya2, CxEF00, 0x0266, Z_(TuyaMinTemp), Cm1, 0 },
|
||||||
{ Ztuya4, CxEF00, 0x0267, Z_(TuyaMaxTemp), Cm1, 0 },
|
{ Ztuya2, CxEF00, 0x0267, Z_(TuyaMaxTemp), Cm1, 0 },
|
||||||
{ Ztuya4, CxEF00, 0x0269, Z_(TuyaBoostTime), Cm1, 0 },
|
{ Ztuya2, CxEF00, 0x0269, Z_(TuyaBoostTime), Cm1, 0 },
|
||||||
{ Ztuya4, CxEF00, 0x026B, Z_(TuyaComfortTemp), Cm1, 0 },
|
{ Ztuya2, CxEF00, 0x026B, Z_(TuyaComfortTemp), Cm1, 0 },
|
||||||
{ Ztuya4, CxEF00, 0x026C, Z_(TuyaEcoTemp), Cm1, 0 },
|
{ Ztuya2, CxEF00, 0x026C, Z_(TuyaEcoTemp), Cm1, 0 },
|
||||||
{ Ztuya1, CxEF00, 0x026D, Z_(TuyaValvePosition), Cm1, 0 },
|
{ Ztuya2, CxEF00, 0x026D, Z_(TuyaValvePosition), Cm1, 0 },
|
||||||
{ Ztuya4, CxEF00, 0x0272, Z_(TuyaAwayTemp), Cm1, 0 },
|
{ Ztuya2, CxEF00, 0x0272, Z_(TuyaAwayTemp), Cm1, 0 },
|
||||||
{ Ztuya4, CxEF00, 0x0275, Z_(TuyaAwayDays), Cm1, 0 },
|
{ Ztuya2, CxEF00, 0x0275, Z_(TuyaAwayDays), Cm1, 0 },
|
||||||
{ Ztuya1, CxEF00, 0x0404, Z_(TuyaPreset), Cm1, 0 },
|
{ Ztuya4, CxEF00, 0x0404, Z_(TuyaPreset), Cm1, 0 },
|
||||||
{ Ztuya1, CxEF00, 0x0405, Z_(TuyaFanMode), Cm1, 0 },
|
{ Ztuya4, CxEF00, 0x0405, Z_(TuyaFanMode), Cm1, 0 },
|
||||||
{ Ztuya1, CxEF00, 0x046A, Z_(TuyaForceMode), Cm1, 0 },
|
{ Ztuya4, CxEF00, 0x046A, Z_(TuyaForceMode), Cm1, 0 },
|
||||||
{ Ztuya1, CxEF00, 0x046F, Z_(TuyaWeekSelect), Cm1, 0 },
|
{ Ztuya4, CxEF00, 0x046F, Z_(TuyaWeekSelect), Cm1, 0 },
|
||||||
|
|
||||||
// Terncy specific - 0xFCCC
|
// Terncy specific - 0xFCCC
|
||||||
{ Zuint16, CxFCCC, 0x001A, Z_(TerncyDuration), Cm1, 0 },
|
{ 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
|
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
|
||||||
@ -874,9 +875,6 @@ 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
|
||||||
@ -895,10 +893,6 @@ 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
|
||||||
buf.add32( *((uint32_t*)&f32) ); // cast float as uint32_t
|
buf.add32( *((uint32_t*)&f32) ); // cast float as uint32_t
|
||||||
@ -987,15 +981,6 @@ 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
|
||||||
@ -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.
|
// 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>"
|
// 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: the attribute value is unchanged and unparsed
|
||||||
//
|
//
|
||||||
// Note: if the type is specified in the key, the multiplier is not applied, no conversion happens
|
// 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
|
// 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)
|
// alternative "XXXX/YYYY%ZZ" where ZZ is the type (for unregistered attributes)
|
||||||
if (attr.key_is_str) {
|
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);
|
// 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
|
// 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) {
|
if (Zunk == attr.attr_type) {
|
||||||
// scan attributes to find by name, and retrieve type
|
Z_parseAttributeKey_inner(attr, 0xFFFF); // try again with any cluster
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return (Zunk != attr.attr_type) ? true : false;
|
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
|
// Tuya - MOES specifc cluster 0xEF00
|
||||||
// See https://medium.com/@dzegarra/zigbee2mqtt-how-to-add-support-for-a-new-tuya-based-device-part-2-5492707e882d
|
// 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) {
|
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 status = buf.get8(0);
|
||||||
// uint8_t transid = buf.get8(1);
|
// 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 dp = buf.get8(2); // dpid from Tuya documentation
|
||||||
// uint8_t fn = buf.get8(4);
|
uint8_t attr_type = buf.get8(3); // data type from Tuya documentation
|
||||||
uint8_t len = buf.get8(5); // len of payload
|
uint16_t len = buf.get16BigEndian(4);
|
||||||
|
|
||||||
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, (attr_type << 8) | dp);
|
||||||
uint8_t attr_type;
|
parseSingleTuyaAttribute(attr, buf, 6, len, 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);
|
|
||||||
return true; // true = remove the original Tuya attribute
|
return true; // true = remove the original Tuya attribute
|
||||||
}
|
}
|
||||||
return false;
|
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 numInIndex = 11;
|
||||||
const size_t numOutIndex = 12 + numInCluster*2;
|
const size_t numOutIndex = 12 + numInCluster*2;
|
||||||
#endif
|
#endif
|
||||||
|
bool is_tuya_protocol = false;
|
||||||
|
|
||||||
if (0 == status) {
|
if (0 == status) {
|
||||||
// device is reachable
|
// 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 "\":{"
|
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"
|
",\"ProfileId\":\"0x%04X\",\"DeviceId\":\"0x%04X\",\"DeviceVersion\":%d"
|
||||||
",\"InClusters\":["),
|
",\"InClusters\":["),
|
||||||
ZIGBEE_STATUS_SIMPLE_DESC, endpoint,
|
ZIGBEE_STATUS_SIMPLE_DESC, nwkAddr, endpoint,
|
||||||
profileId, deviceId, deviceVersion);
|
profileId, deviceId, deviceVersion);
|
||||||
for (uint32_t i = 0; i < numInCluster; i++) {
|
for (uint32_t i = 0; i < numInCluster; i++) {
|
||||||
if (i > 0) { ResponseAppend_P(PSTR(",")); }
|
if (i > 0) { ResponseAppend_P(PSTR(",")); }
|
||||||
ResponseAppend_P(PSTR("\"0x%04X\""), buf.get16(numInIndex + i*2));
|
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\":["));
|
ResponseAppend_P(PSTR("],\"OutClusters\":["));
|
||||||
for (uint32_t i = 0; i < numOutCluster; i++) {
|
for (uint32_t i = 0; i < numOutCluster; i++) {
|
||||||
@ -729,6 +731,14 @@ int32_t Z_ReceiveSimpleDesc(int32_t res, const class SBuffer &buf) {
|
|||||||
XdrvRulesProcess();
|
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;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2036,7 +2046,7 @@ void ZCLFrame::autoResponder(const uint16_t *attr_list_ids, size_t attr_len) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (!attr.isNone()) {
|
if (!attr.isNone()) {
|
||||||
Z_parseAttributeKey(attr);
|
Z_parseAttributeKey(attr, _cluster_id);
|
||||||
attr_list.addAttribute(_cluster_id, attr_id) = attr;
|
attr_list.addAttribute(_cluster_id, attr_id) = attr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -237,7 +237,7 @@ void ZbApplyMultiplier(double &val_d, int8_t multiplier) {
|
|||||||
//
|
//
|
||||||
// Write Tuya-Moes attribute
|
// 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();
|
double val_d = attr.getFloat();
|
||||||
const char * val_str = attr.getStr();
|
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)) {
|
if (attr.isNum() && (1 != attr.attr_multiplier)) {
|
||||||
ZbApplyMultiplier(val_d, attr.attr_multiplier);
|
ZbApplyMultiplier(val_d, attr.attr_multiplier);
|
||||||
}
|
}
|
||||||
buf.add8(0); // status
|
uint32_t u32 = val_d;
|
||||||
buf.add8(transid); // transid
|
int32_t i32 = val_d;
|
||||||
buf.add16(attr.key.id.attr_id); // dp
|
|
||||||
buf.add8(0); // fn
|
uint8_t tuyatype = (attr.key.id.attr_id >> 8);
|
||||||
int32_t res = encodeSingleAttribute(buf, val_d, val_str, attr.attr_type);
|
uint8_t dpid = (attr.key.id.attr_id & 0xFF);
|
||||||
if (res < 0) {
|
buf.add8(tuyatype);
|
||||||
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);
|
buf.add8(dpid);
|
||||||
return false;
|
|
||||||
|
// 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;
|
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
|
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
|
// iterate on keys
|
||||||
for (auto key : val_pubwrite.getObject()) {
|
for (auto key : val_pubwrite.getObject()) {
|
||||||
JsonParserToken value = key.getValue();
|
JsonParserToken value = key.getValue();
|
||||||
|
|
||||||
Z_attribute attr;
|
Z_attribute attr;
|
||||||
attr.setKeyName(key.getStr());
|
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
|
// Buffer ready, do some sanity checks
|
||||||
|
|
||||||
// all attributes must use the same cluster
|
// all attributes must use the same cluster
|
||||||
@ -337,14 +375,15 @@ void ZbSendReportWrite(class JsonParserToken val_pubwrite, class ZigbeeZCLSendMe
|
|||||||
// Split encoding depending on message
|
// Split encoding depending on message
|
||||||
if (packet.cmd != ZCL_CONFIGURE_REPORTING) {
|
if (packet.cmd != ZCL_CONFIGURE_REPORTING) {
|
||||||
if ((packet.cluster == 0XEF00) && (packet.cmd == ZCL_WRITE_ATTRIBUTES)) {
|
if ((packet.cluster == 0XEF00) && (packet.cmd == ZCL_WRITE_ATTRIBUTES)) {
|
||||||
// special case of Tuya / Moes attributes
|
// special case of Tuya / Moes / Lidl attributes
|
||||||
if (buf.len() > 0) {
|
if (buf.len() == 0) {
|
||||||
AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "Only 1 attribute allowed for Tuya"));
|
// add the prefix to data
|
||||||
return;
|
buf.add8(0); // status
|
||||||
|
buf.add8(zigbee_devices.getNextSeqNumber(packet.shortaddr));
|
||||||
}
|
}
|
||||||
packet.clusterSpecific = true;
|
packet.clusterSpecific = true;
|
||||||
packet.cmd = 0x00;
|
packet.cmd = 0x00;
|
||||||
if (!ZbTuyaWrite(buf, attr, zigbee_devices.getNextSeqNumber(packet.shortaddr))) {
|
if (!ZbTuyaWrite(buf, attr)) {
|
||||||
return; // error
|
return; // error
|
||||||
}
|
}
|
||||||
} else if (!ZbAppendWriteBuf(buf, attr, packet.cmd == ZCL_READ_ATTRIBUTES_RESPONSE)) { // general case
|
} else if (!ZbAppendWriteBuf(buf, attr, packet.cmd == ZCL_READ_ATTRIBUTES_RESPONSE)) { // general case
|
||||||
|
@ -19,6 +19,11 @@
|
|||||||
|
|
||||||
#ifdef USE_I2C
|
#ifdef USE_I2C
|
||||||
#ifdef USE_IAQ
|
#ifdef USE_IAQ
|
||||||
|
/*********************************************************************************************\
|
||||||
|
* iAQ-Core - Indoor Air Quality Sensor
|
||||||
|
*
|
||||||
|
* I2C Address: 0x5A
|
||||||
|
\*********************************************************************************************/
|
||||||
|
|
||||||
#define XSNS_66 66
|
#define XSNS_66 66
|
||||||
#define XI2C_46 46 // See I2CDEVICES.md
|
#define XI2C_46 46 // See I2CDEVICES.md
|
||||||
@ -35,22 +40,32 @@ struct {
|
|||||||
int32_t resistance;
|
int32_t resistance;
|
||||||
uint16_t pred;
|
uint16_t pred;
|
||||||
uint16_t Tvoc;
|
uint16_t Tvoc;
|
||||||
|
uint8_t i2c_address;
|
||||||
uint8_t status;
|
uint8_t status;
|
||||||
bool ready;
|
bool ready;
|
||||||
} iAQ;
|
} iAQ;
|
||||||
|
|
||||||
void IAQ_Init(void)
|
void IAQ_Init(void) {
|
||||||
{
|
|
||||||
if (!I2cSetDevice(I2_ADR_IAQ)) { return; }
|
if (!I2cSetDevice(I2_ADR_IAQ)) { return; }
|
||||||
I2cSetActiveFound(I2_ADR_IAQ, "IAQ");
|
I2cSetActiveFound(I2_ADR_IAQ, "IAQ");
|
||||||
|
iAQ.i2c_address = I2_ADR_IAQ;
|
||||||
iAQ.ready = true;
|
iAQ.ready = true;
|
||||||
|
/*
|
||||||
|
for (iAQ.i2c_address = I2_ADR_IAQ; iAQ.i2c_address < I2_ADR_IAQ +5; iAQ.i2c_address++) {
|
||||||
|
if (I2cActive(iAQ.i2c_address)) { continue; }
|
||||||
|
if (I2cSetDevice(iAQ.i2c_address)) {
|
||||||
|
I2cSetActiveFound(iAQ.i2c_address, "IAQ");
|
||||||
|
iAQ.ready = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
void IAQ_Read(void)
|
void IAQ_Read(void) {
|
||||||
{
|
|
||||||
uint8_t buf[9];
|
uint8_t buf[9];
|
||||||
buf[2] = IAQ_STATUS_I2C_ERR; // populate entry with error code
|
buf[2] = IAQ_STATUS_I2C_ERR; // populate entry with error code
|
||||||
Wire.requestFrom((uint8_t)I2_ADR_IAQ,sizeof(buf));
|
Wire.requestFrom(iAQ.i2c_address, sizeof(buf));
|
||||||
for( uint32_t i=0; i<9; i++ ) {
|
for( uint32_t i=0; i<9; i++ ) {
|
||||||
buf[i]= Wire.read();
|
buf[i]= Wire.read();
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user