diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 0e218ec71..748f02248 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -6,7 +6,7 @@ - [ ] The pull request is done against the latest dev branch - [ ] Only relevant files were touched - [ ] 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 - [ ] I accept the [CLA](https://github.com/arendst/Tasmota/blob/development/CONTRIBUTING.md#contributor-license-agreement-cla). diff --git a/platformio.ini b/platformio.ini index 7645a668e..5dfb5a6c5 100644 --- a/platformio.ini +++ b/platformio.ini @@ -144,11 +144,14 @@ build_flags = -DUSE_IR_REMOTE_FULL [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_packages = tasmota/framework-arduinoespressif8266 @ 3.20704.7 +platform_packages = tasmota/framework-arduinoespressif8266 @ ~2.7.4 platformio/toolchain-xtensa @ 2.40802.200502 platformio/tool-esptool @ 1.413.0 platformio/tool-esptoolpy @ ~1.30000.0 build_unflags = ${esp_defaults.build_unflags} build_flags = ${esp82xx_defaults.build_flags} +; *** Use ONE of the two PWM variants. Tasmota default is Locked PWM + ;-DWAVEFORM_LOCKED_PHASE + -DWAVEFORM_LOCKED_PWM diff --git a/tasmota/StackThunk_light.cpp b/tasmota/StackThunk_light.cpp index bbd60fb50..1e5fd445a 100644 --- a/tasmota/StackThunk_light.cpp +++ b/tasmota/StackThunk_light.cpp @@ -43,7 +43,7 @@ uint32_t stack_thunk_light_refcnt = 0; //#define _stackSize (5600/4) #if defined(USE_MQTT_AWS_IOT) #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 #else #define _stackSize (3600/4) // using a light version of bearssl we can save 2k diff --git a/tasmota/support_static_buffer.ino b/tasmota/support_static_buffer.ino index 5fa0e603c..9504ba3dd 100644 --- a/tasmota/support_static_buffer.ino +++ b/tasmota/support_static_buffer.ino @@ -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) | diff --git a/tasmota/xdrv_23_zigbee_2_devices.ino b/tasmota/xdrv_23_zigbee_2_devices.ino index e5783eaed..4271c0d42 100644 --- a/tasmota/xdrv_23_zigbee_2_devices.ino +++ b/tasmota/xdrv_23_zigbee_2_devices.ino @@ -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 M & get(uint8_t ep = 0); + template + const M & getConst(uint8_t ep = 0) const; template 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(ep); case Z_Data_Type::Z_PIR: return get(ep); + case Z_Data_Type::Z_Mode: + return get(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, ep); } +template +const M & Z_Data_Set::getConst(uint8_t ep) const { + const M & m = (M&) find(M::type, ep); + return m; +} template 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 _devices; // list of devices diff --git a/tasmota/xdrv_23_zigbee_2a_devices_impl.ino b/tasmota/xdrv_23_zigbee_2a_devices_impl.ino index d9a437db8..ae5e7e469 100644 --- a/tasmota/xdrv_23_zigbee_2a_devices_impl.ino +++ b/tasmota/xdrv_23_zigbee_2a_devices_impl.ino @@ -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(); } +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(ep); + if (&mode != nullptr) { + return mode.isTuyaProtocol(); + } + } + return false; +} + /*********************************************************************************************\ * Device specific data handlers \*********************************************************************************************/ diff --git a/tasmota/xdrv_23_zigbee_5_converters.ino b/tasmota/xdrv_23_zigbee_5_converters.ino index cd388d16c..ec4901fe4 100644 --- a/tasmota/xdrv_23_zigbee_5_converters.ino +++ b/tasmota/xdrv_23_zigbee_5_converters.ino @@ -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: / or /% or "" @@ -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; } diff --git a/tasmota/xdrv_23_zigbee_6_commands.ino b/tasmota/xdrv_23_zigbee_6_commands.ino index bf8bfdd6f..1e4675e22 100644 --- a/tasmota/xdrv_23_zigbee_6_commands.ino +++ b/tasmota/xdrv_23_zigbee_6_commands.ino @@ -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; diff --git a/tasmota/xdrv_23_zigbee_8_parsers.ino b/tasmota/xdrv_23_zigbee_8_parsers.ino index f3ee1b291..f9c8019b9 100644 --- a/tasmota/xdrv_23_zigbee_8_parsers.ino +++ b/tasmota/xdrv_23_zigbee_8_parsers.ino @@ -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(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; } } diff --git a/tasmota/xdrv_23_zigbee_A_impl.ino b/tasmota/xdrv_23_zigbee_A_impl.ino index c7c21eb90..a3525746e 100644 --- a/tasmota/xdrv_23_zigbee_A_impl.ino +++ b/tasmota/xdrv_23_zigbee_A_impl.ino @@ -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 diff --git a/tasmota/xsns_66_iAQ.ino b/tasmota/xsns_66_iAQ.ino index c647aa13b..962480748 100644 --- a/tasmota/xsns_66_iAQ.ino +++ b/tasmota/xsns_66_iAQ.ino @@ -19,6 +19,11 @@ #ifdef USE_I2C #ifdef USE_IAQ +/*********************************************************************************************\ + * iAQ-Core - Indoor Air Quality Sensor + * + * I2C Address: 0x5A +\*********************************************************************************************/ #define XSNS_66 66 #define XI2C_46 46 // See I2CDEVICES.md @@ -35,22 +40,32 @@ struct { int32_t resistance; uint16_t pred; uint16_t Tvoc; + uint8_t i2c_address; uint8_t status; bool ready; } iAQ; -void IAQ_Init(void) -{ +void IAQ_Init(void) { if (!I2cSetDevice(I2_ADR_IAQ)) { return; } I2cSetActiveFound(I2_ADR_IAQ, "IAQ"); + iAQ.i2c_address = I2_ADR_IAQ; 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]; 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++ ) { buf[i]= Wire.read(); }