diff --git a/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_1z_libs.ino b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_1z_libs.ino index 4cf5fae4e..0e76c79f1 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_1z_libs.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_1z_libs.ino @@ -113,6 +113,7 @@ public: uint8_t key_suffix; // append a suffix to key (default is 1, explicitly output if >1) uint8_t attr_type; // [opt] type of the attribute, default to Zunk (0xFF) uint8_t attr_multiplier; // [opt] multiplier for attribute, defaults to 0x01 (no change) + uint16_t manuf; // manufacturer id (0 if none) // Constructor with all defaults Z_attribute(): @@ -124,7 +125,8 @@ public: val_str_raw(false), key_suffix(1), attr_type(0xFF), - attr_multiplier(1) + attr_multiplier(1), + manuf(0) {}; Z_attribute(const Z_attribute & rhs) { diff --git a/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_5_1_attributes.ino b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_5_1_attributes.ino index 7f091f243..37d2897de 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_5_1_attributes.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_5_1_attributes.ino @@ -271,7 +271,7 @@ class Z_plugin_attribute { public: Z_plugin_attribute(void) : - type(Zunk), multiplier(1), cluster(0xFFFF), attribute(0xFFFF) + type(Zunk), multiplier(1), cluster(0xFFFF), attribute(0xFFFF), manuf(0) {}; void set(uint16_t cluster, uint16_t attribute, const char *name, uint8_t type = Zunk) { @@ -285,6 +285,7 @@ public: int8_t multiplier; // multiplier, values 0, 1, 2, 5, 10, 100, -2, -5, -10, -100, uint16_t cluster; // cluster number uint16_t attribute; // attribute number + uint16_t manuf; // manufacturer code, 0 if none String name; // name of attribute once converted }; @@ -456,6 +457,7 @@ public: int8_t multiplier = 1; uint8_t map_offset = 0; Z_Data_Type map_type = Z_Data_Type::Z_Unknown; + uint16_t manuf = 0x0000; // manuf code (if any) }; Z_attribute_match Z_plugin_matchAttributeById(const char *model, const char *manufacturer, uint16_t cluster, uint16_t attribute); @@ -472,8 +474,6 @@ const Z_AttributeConverter Z_PostProcess[] PROGMEM = { { Zuint8, Cx0000, 0x0003, Z_(HWVersion), Cm1, 0 }, { Zstring, Cx0000, 0x0004, Z_(Manufacturer), Cm1, 0 }, // record Manufacturer { Zstring, Cx0000, 0x0005, Z_(ModelId), Cm1, 0 }, // record Model - // { Zstring, Cx0000, 0x0004, Z_(Manufacturer), Cm1, Z_ManufKeep, 0 }, // record Manufacturer - // { Zstring, Cx0000, 0x0005, Z_(ModelId), Cm1, Z_ModelKeep, 0 }, // record Model { Zstring, Cx0000, 0x0006, Z_(DateCode), Cm1, 0 }, { Zenum8, Cx0000, 0x0007, Z_(PowerSource), Cm1, 0 }, { Zenum8, Cx0000, 0x0008, Z_(GenericDeviceClass), Cm1, 0 }, @@ -524,8 +524,8 @@ const Z_AttributeConverter Z_PostProcess[] PROGMEM = { // Level Control cluster { Zuint8, Cx0008, 0x0000, Z_(Dimmer), Cm1 + Z_EXPORT_DATA, Z_MAPPING(Z_Data_Light, dimmer) }, - { Zmap8, Cx0008, 0x000F, Z_(DimmerOptions), Cm1, 0 }, { Zuint16, Cx0008, 0x0001, Z_(DimmerRemainingTime), Cm1, 0 }, + { Zmap8, Cx0008, 0x000F, Z_(DimmerOptions), Cm1, 0 }, { Zuint16, Cx0008, 0x0010, Z_(OnOffTransitionTime), Cm1, 0 }, // { Zuint8, Cx0008, 0x0011, (OnLevel), Cm1, 0 }, // { Zuint16, Cx0008, 0x0012, (OnTransitionTime), Cm1, 0 }, diff --git a/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_5_2_converters.ino b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_5_2_converters.ino index 85faa6d85..5a7331b9e 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_5_2_converters.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_5_2_converters.ino @@ -1509,6 +1509,7 @@ void Z_parseAttributeKey_inner(uint16_t shortaddr, class Z_attribute & attr, uin attr.setKeyId(matched_attr.cluster, matched_attr.attribute); attr.attr_type = matched_attr.zigbee_type; attr.attr_multiplier = matched_attr.multiplier; + attr.manuf = matched_attr.manuf; } } } else { @@ -1516,6 +1517,7 @@ void Z_parseAttributeKey_inner(uint16_t shortaddr, class Z_attribute & attr, uin Z_attribute_match matched_attr = Z_findAttributeMatcherById(shortaddr, attr.key.id.cluster, attr.key.id.attr_id, false); if (matched_attr.found()) { attr.attr_type = matched_attr.zigbee_type; + attr.manuf = matched_attr.manuf; } } } diff --git a/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_7_6_plugin.ino b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_7_6_plugin.ino index 7db2c99fb..094ec3248 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_7_6_plugin.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_7_6_plugin.ino @@ -19,6 +19,18 @@ #ifdef USE_ZIGBEE +const char Z_MUL[] PROGMEM = "mul:"; +const char Z_DIV[] PROGMEM = "div:"; +const char Z_MANUF[] PROGMEM = "manuf:"; + +char * Z_subtoken(char * token, const char * prefix) { + size_t prefix_len = strlen_P(prefix); + if (!strncmp_P(token, prefix, prefix_len)) { + return token + prefix_len; + } + return nullptr; +} + // global singleton Z_plugin_templates g_plugin_templates; @@ -48,6 +60,7 @@ Z_attribute_match Z_plugin_matchAttributeById(const char *model, const char *man attr.name = attr_tmpl->name.c_str(); attr.zigbee_type = attr_tmpl->type; attr.multiplier = attr_tmpl->multiplier; + attr.manuf = attr_tmpl->manuf; } return attr; } @@ -67,6 +80,7 @@ Z_attribute_match Z_plugin_matchAttributeByName(const char *model, const char *m attr.name = attr_tmpl->name.c_str(); attr.zigbee_type = attr_tmpl->type; attr.multiplier = attr_tmpl->multiplier; + attr.manuf = attr_tmpl->manuf; } } return attr; @@ -205,6 +219,7 @@ bool ZbLoad(const char *filename_raw) { char * delimiter_equal = strchr(token, '='); if (delimiter_equal == nullptr) { + // NORMAL ATTRIBUTE // token is of from '0000/0000' or '0000/0000%00' char * delimiter_slash = strchr(token, '/'); char * delimiter_percent = strchr(token, '%'); @@ -215,6 +230,9 @@ bool ZbLoad(const char *filename_raw) { uint16_t attr_id = 0xFFFF; uint16_t cluster_id = 0xFFFF; uint8_t type_id = Zunk; + int8_t multiplier = 1; + char * name = nullptr; + uint16_t manuf = 0; cluster_id = strtoul(token, &delimiter_slash, 16); if (!delimiter_percent) { @@ -223,19 +241,44 @@ bool ZbLoad(const char *filename_raw) { attr_id = strtoul(delimiter_slash+1, &delimiter_percent, 16); type_id = Z_getTypeByName(delimiter_percent+1); } - // name of the attribute + // NAME of the attribute token = strtok_r(rest, ",", &rest); if (token == nullptr) { AddLog(LOG_LEVEL_INFO, "ZIG: ZbLoad '%s' ignore missing name '%s'", filename_raw, buf_line); continue; } + name = token; + // ADDITIONAL ELEMENTS? + // Ex: `manuf:1037` + while (token = strtok_r(rest, ",", &rest)) { + char * sub_token; + // look for multiplier + if (sub_token = Z_subtoken(token, Z_MUL)) { + multiplier = strtoul(sub_token, nullptr, 10); + } + // look for divider + else if (sub_token = Z_subtoken(token, Z_DIV)) { + multiplier = - strtoul(sub_token, nullptr, 10); // negative to indicate divider + } + // look for `manuf:HHHH` + else if (sub_token = Z_subtoken(token, Z_MANUF)) { + manuf = strtoul(sub_token, nullptr, 16); + } + else { + AddLog(LOG_LEVEL_DEBUG, "ZIG: ZbLoad unrecognized modifier '%s'", token); + } + } + // token contains the name of the attribute Z_plugin_attribute & plugin_attr = tmpl->attributes.addToLast(); plugin_attr.cluster = cluster_id; plugin_attr.attribute = attr_id; plugin_attr.type = type_id; - plugin_attr.name = token; + plugin_attr.name = name; + plugin_attr.multiplier = multiplier; + plugin_attr.manuf = manuf; } else { + // ATTRIBUTE SYNONYM // token is of from '0000/0000=0000/0000,1' char * rest2 = token; char * tok2 = strtok_r(rest2, "=", &rest2); @@ -246,11 +289,22 @@ bool ZbLoad(const char *filename_raw) { char * delimiter_slash2 = strchr(tok2, '/'); uint16_t new_cluster_id = strtoul(tok2, &delimiter_slash2, 16); uint16_t new_attr_id = strtoul(delimiter_slash2+1, nullptr, 16); - // multiplier - token = strtok_r(rest, ",", &rest); int8_t multiplier = 1; - if (token != nullptr) { - multiplier = strtol(token, nullptr, 10); + + // ADDITIONAL ELEMENTS? + while (token = strtok_r(rest, ",", &rest)) { + char * sub_token; + // look for multiplier + if (sub_token = Z_subtoken(token, Z_MUL)) { + multiplier = strtoul(sub_token, nullptr, 10); + } + // look for divider + else if (sub_token = Z_subtoken(token, Z_DIV)) { + multiplier = - strtoul(sub_token, nullptr, 10); // negative to indicate divider + } + else { + AddLog(LOG_LEVEL_DEBUG, "ZIG: ZbLoad unrecognized modifier '%s'", token); + } } // create the synonym Z_attribute_synonym & syn = tmpl->synonyms.addToLast(); @@ -305,21 +359,35 @@ bool ZbUnload(const char *filename_raw) { return false; } +// append modifiers like mul/div/manuf +void Z_AppendModifiers(char * buf, size_t buf_len, int8_t multiplier, uint16_t manuf) { + if (multiplier != 0 && multiplier != 1) { + ext_snprintf_P(buf, buf_len, "%s,%s%i", buf, multiplier > 0 ? Z_MUL : Z_DIV, multiplier > 0 ? multiplier : -multiplier); + } + if (manuf) { + ext_snprintf_P(buf, buf_len, "%s,%s%04X", buf, Z_MANUF, manuf); + } +} + // Dump the ZbLoad structure in a format compatible with ZbLoad void ZbLoadDump(void) { + char buf[96]; AddLog(LOG_LEVEL_INFO, "ZIG: ZbLoad dump all current information"); AddLog(LOG_LEVEL_INFO, "====> START"); for (const Z_plugin_template & tmpl : g_plugin_templates) { if (tmpl.filename != nullptr) { - AddLog(LOG_LEVEL_INFO, "# imported from '%s'", tmpl.filename); + ext_snprintf_P(buf, sizeof(buf), "# imported from '%s'", tmpl.filename); + AddLog(LOG_LEVEL_INFO, PSTR("%s"), buf); } // marchers if (tmpl.matchers.length() == 0) { - AddLog(LOG_LEVEL_INFO, ": # no matcher"); + ext_snprintf_P(buf, sizeof(buf), ": # no matcher"); + AddLog(LOG_LEVEL_INFO, PSTR("%s"), buf); } else { for (const Z_plugin_matcher & matcher : tmpl.matchers) { - AddLog(LOG_LEVEL_INFO, ":%s,%s", matcher.model.c_str(), matcher.manufacturer.c_str()); + ext_snprintf_P(buf, sizeof(buf), ":%s,%s", matcher.model.c_str(), matcher.manufacturer.c_str()); + AddLog(LOG_LEVEL_INFO, PSTR("%s"), buf); } } // attributes @@ -328,16 +396,20 @@ void ZbLoadDump(void) { AddLog(LOG_LEVEL_INFO, ""); } else { for (const Z_plugin_attribute & attr : tmpl.attributes) { - if (attr.type == Zunk) { - AddLog(LOG_LEVEL_INFO, "%04X/%04X,%s", attr.cluster, attr.attribute, attr.name.c_str()); - } else { - char type[16]; - Z_getTypeByNumber(type, sizeof(type), attr.type); - AddLog(LOG_LEVEL_INFO, "%04X/%04X%%%s,%s", attr.cluster, attr.attribute, type, attr.name.c_str()); + ext_snprintf_P(buf, sizeof(buf), "%04X/%04X", attr.cluster, attr.attribute); + // add type if known + if (attr.type != Zunk) { + char type_str[16]; + Z_getTypeByNumber(type_str, sizeof(type_str), attr.type); + ext_snprintf_P(buf, sizeof(buf), "%s%%%s", buf, type_str); } + Z_AppendModifiers(buf, sizeof(buf), attr.multiplier, attr.manuf); + AddLog(LOG_LEVEL_INFO, PSTR("%s"), buf); } for (const Z_attribute_synonym & syn : tmpl.synonyms) { - AddLog(LOG_LEVEL_INFO, "%04X/%04X=%04X/%04X,%i", syn.cluster, syn.attribute, syn.new_cluster, syn.new_attribute, syn.multiplier); + ext_snprintf_P(buf, sizeof(buf), "%04X/%04X=%04X/%04X", syn.cluster, syn.attribute, syn.new_cluster, syn.new_attribute); + Z_AppendModifiers(buf, sizeof(buf), syn.multiplier, 0); + AddLog(LOG_LEVEL_INFO, PSTR("%s"), buf); } } } diff --git a/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_A_impl.ino b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_A_impl.ino index a5a3735be..378062153 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_A_impl.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_A_impl.ino @@ -352,6 +352,15 @@ void ZbSendReportWrite(class JsonParserToken val_pubwrite, class ZCLFrame & zcl) return; } + // check for manuf code + if (attr.manuf) { + if (zcl.manuf != 0 && zcl.manuf != attr.manuf) { + AddLog(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "conflicting manuf code 0x%04X (was 0x%04X)"), attr.manuf, zcl.manuf); + } else { + zcl.manuf = attr.manuf; + } + } + } else { if (attr.key_is_str) { Response_P(PSTR("{\"%s\":\"%s'%s'\"}"), XdrvMailbox.command, PSTR(D_ZIGBEE_UNKNOWN_ATTRIBUTE " "), key.getStr()); @@ -646,6 +655,14 @@ void ZbSendRead(JsonParserToken val_attr, ZCLFrame & zcl) { if (attrs) { free(attrs); } return; } + // check for manuf code + if (matched_attr.manuf) { + if (zcl.manuf != 0 && zcl.manuf != matched_attr.manuf) { + AddLog(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "conflicting manuf code 0x%04X (was 0x%04X)"), matched_attr.manuf, zcl.manuf); + } else { + zcl.manuf = matched_attr.manuf; + } + } } if (!found) { AddLog(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE D_ZIGBEE_UNKNWON_ATTRIBUTE), key.getStr()); diff --git a/tasmota/zigbee/giex_water.zb b/tasmota/zigbee/giex_water.zb index fbc5f2fd9..b36ffd6f9 100644 --- a/tasmota/zigbee/giex_water.zb +++ b/tasmota/zigbee/giex_water.zb @@ -9,6 +9,6 @@ EF00/0267,CycleIrrigationNumTimes # number of cycle irrigation times, set to 0 EF00/0268,IrrigationTarget # duration in minutes or capacity in Liters (depending on mode) EF00/0269,CycleIrrigationInterval # cycle irrigation interval (minutes, max 1440) EF00/026A,CurrentTemperature # (value ignored because isn't a valid tempurature reading. Misdocumented and usage unclear) -EF00/026C=0001/0021,2 # match to BatteryPercentage +EF00/026C=0001/0021,mul:2 # match to BatteryPercentage EF00/026F,WaterConsumed # water consumed (Litres) EF00/0372,LastIrrigationDuration # (string) Ex: "00:01:10,0" diff --git a/tasmota/zigbee/legacy_tuya.zb b/tasmota/zigbee/legacy_tuya.zb new file mode 100644 index 000000000..06490e363 --- /dev/null +++ b/tasmota/zigbee/legacy_tuya.zb @@ -0,0 +1,31 @@ +#Z2Tv1 +# DEPRECATED +# Legacy Tuya attributes from before Zigbee Device plugins +# Use only if you need to legacy behavior, prefer device specific +: # apply to all devices +EF00/0070,TuyaScheduleWorkdays +EF00/0071,TuyaScheduleHolidays +EF00/0101,Power +EF00/0102,Power2 +EF00/0103,Power3 +EF00/0104,Power4 +EF00/0107,TuyaChildLock +EF00/0112,TuyaWindowDetection +EF00/0114,TuyaValveDetection +EF00/0174,TuyaAutoLock +EF00/0202,TuyaTempTarget +EF00/0202=0201/FFF1,mul:10 # TempTarget +EF00/0203=0402/0000,mul:10 # Temperature +EF00/0215,TuyaBattery +EF00/0266,TuyaMinTemp +EF00/0267,TuyaMaxTemp +EF00/0269,TuyaBoostTime +EF00/026B,TuyaComfortTemp +EF00/026C,TuyaEcoTemp +EF00/026D=0201/FFF0 +EF00/0272,TuyaAwayTemp +EF00/0275,TuyaAwayDays +EF00/0404,TuyaPreset +EF00/0405,TuyaFanMode +EF00/046A,TuyaForceMode +EF00/046F,TuyaWeekSelect