diff --git a/CHANGELOG.md b/CHANGELOG.md
index 02547ddaa..d56b21c5a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,7 @@ All notable changes to this project will be documented in this file.
## [12.1.0.1]
### Added
+- Zigbee device plugin mechanism with commands ``ZbLoad``, ``ZbUnload`` and ``ZbLoadDump``
### Changed
diff --git a/tasmota/include/i18n.h b/tasmota/include/i18n.h
index 54711b451..560baa0dc 100644
--- a/tasmota/include/i18n.h
+++ b/tasmota/include/i18n.h
@@ -672,6 +672,9 @@
#define D_JSON_ZIGBEE_SCAN "ZbScan"
#define D_CMND_ZIGBEE_CIE "CIE"
#define D_CMND_ZIGBEE_ENROLL "Enroll"
+#define D_CMND_ZIGBEE_LOAD "Load"
+#define D_CMND_ZIGBEE_LOADDUMP "LoadDump"
+#define D_CMND_ZIGBEE_UNLOAD "Unload"
// Commands xdrv_25_A4988_Stepper.ino
#define D_CMND_MOTOR "MOTOR"
diff --git a/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_5__constants.ino b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_5_0_constants.ino
similarity index 82%
rename from tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_5__constants.ino
rename to tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_5_0_constants.ino
index df2d3c0be..cb395097f 100644
--- a/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_5__constants.ino
+++ b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_5_0_constants.ino
@@ -356,9 +356,6 @@ const char Z_strings[] PROGMEM =
"PhysicalClosedLimitLift" "\x00"
"PhysicalClosedLimitTilt" "\x00"
"Power" "\x00"
- "Power2" "\x00"
- "Power3" "\x00"
- "Power4" "\x00"
"PowerOffEffect" "\x00"
"PowerOnRecall" "\x00"
"PowerOnTimer" "\x00"
@@ -440,32 +437,12 @@ const char Z_strings[] PROGMEM =
"TimeStatus" "\x00"
"TimeZone" "\x00"
"TotalProfileNum" "\x00"
- "TuyaAutoLock" "\x00"
- "TuyaAwayDays" "\x00"
- "TuyaAwayTemp" "\x00"
- "TuyaBattery" "\x00"
- "TuyaBoostTime" "\x00"
"TuyaCalibration" "\x00"
"TuyaCalibrationTime" "\x00"
- "TuyaChildLock" "\x00"
- "TuyaComfortTemp" "\x00"
- "TuyaEcoTemp" "\x00"
- "TuyaFanMode" "\x00"
- "TuyaForceMode" "\x00"
"TuyaMCUVersion" "\x00"
- "TuyaMaxTemp" "\x00"
- "TuyaMinTemp" "\x00"
"TuyaMotorReversal" "\x00"
"TuyaMovingState" "\x00"
- "TuyaPreset" "\x00"
"TuyaQuery" "\x00"
- "TuyaScheduleHolidays" "\x00"
- "TuyaScheduleWorkdays" "\x00"
- "TuyaTempTarget" "\x00"
- "TuyaValveDetection" "\x00"
- "TuyaValvePosition" "\x00"
- "TuyaWeekSelect" "\x00"
- "TuyaWindowDetection" "\x00"
"UnoccupiedCoolingSetpoint" "\x00"
"UnoccupiedHeatingSetpoint" "\x00"
"UtilityName" "\x00"
@@ -792,158 +769,135 @@ enum Z_offsets {
Zo_PhysicalClosedLimitLift = 4561,
Zo_PhysicalClosedLimitTilt = 4585,
Zo_Power = 4609,
- Zo_Power2 = 4615,
- Zo_Power3 = 4622,
- Zo_Power4 = 4629,
- Zo_PowerOffEffect = 4636,
- Zo_PowerOnRecall = 4651,
- Zo_PowerOnTimer = 4665,
- Zo_PowerSource = 4678,
- Zo_PowerThreshold = 4690,
- Zo_Pressure = 4705,
- Zo_PressureMaxMeasuredValue = 4714,
- Zo_PressureMaxScaledValue = 4739,
- Zo_PressureMinMeasuredValue = 4762,
- Zo_PressureMinScaledValue = 4787,
- Zo_PressureScale = 4810,
- Zo_PressureScaledTolerance = 4824,
- Zo_PressureScaledValue = 4848,
- Zo_PressureTolerance = 4868,
- Zo_Primary1Intensity = 4886,
- Zo_Primary1X = 4904,
- Zo_Primary1Y = 4914,
- Zo_Primary2Intensity = 4924,
- Zo_Primary2X = 4942,
- Zo_Primary2Y = 4952,
- Zo_Primary3Intensity = 4962,
- Zo_Primary3X = 4980,
- Zo_Primary3Y = 4990,
- Zo_ProductCode = 5000,
- Zo_ProductRevision = 5012,
- Zo_ProductURL = 5028,
- Zo_QualityMeasure = 5039,
- Zo_RGB = 5054,
- Zo_RMSCurrent = 5058,
- Zo_RMSVoltage = 5069,
- Zo_ReactivePower = 5080,
- Zo_RecallScene = 5094,
- Zo_RemainingTime = 5106,
- Zo_RemoteSensing = 5120,
- Zo_RemoveAllGroups = 5134,
- Zo_RemoveAllScenes = 5150,
- Zo_RemoveGroup = 5166,
- Zo_RemoveScene = 5178,
- Zo_ResetAlarm = 5190,
- Zo_ResetAllAlarms = 5201,
- Zo_SWBuildID = 5216,
- Zo_Sat = 5226,
- Zo_SatMove = 5230,
- Zo_SatStep = 5238,
- Zo_SceneCount = 5246,
- Zo_SceneValid = 5257,
- Zo_ScheduleMode = 5268,
- Zo_SeaPressure = 5281,
- Zo_ShortPollInterval = 5293,
- Zo_Shutter = 5311,
- Zo_ShutterClose = 5319,
- Zo_ShutterLift = 5332,
- Zo_ShutterOpen = 5344,
- Zo_ShutterStop = 5356,
- Zo_ShutterTilt = 5368,
- Zo_SoftwareRevision = 5380,
- Zo_StackVersion = 5397,
- Zo_StandardTime = 5410,
- Zo_StartUpOnOff = 5423,
- Zo_Status = 5436,
- Zo_StoreScene = 5443,
- Zo_SwitchType = 5454,
- Zo_SystemMode = 5465,
- Zo_TRVBoost = 5476,
- Zo_TRVChildProtection = 5485,
- Zo_TRVMirrorDisplay = 5504,
- Zo_TRVMode = 5521,
- Zo_TRVWindowOpen = 5529,
- Zo_TempTarget = 5543,
- Zo_Temperature = 5554,
- Zo_TemperatureMaxMeasuredValue = 5566,
- Zo_TemperatureMinMeasuredValue = 5594,
- Zo_TemperatureTolerance = 5622,
- Zo_TerncyDuration = 5643,
- Zo_TerncyRotate = 5658,
- Zo_ThSetpoint = 5671,
- Zo_Time = 5682,
- Zo_TimeEpoch = 5687,
- Zo_TimeStatus = 5697,
- Zo_TimeZone = 5708,
- Zo_TotalProfileNum = 5717,
- Zo_TuyaAutoLock = 5733,
- Zo_TuyaAwayDays = 5746,
- Zo_TuyaAwayTemp = 5759,
- Zo_TuyaBattery = 5772,
- Zo_TuyaBoostTime = 5784,
- Zo_TuyaCalibration = 5798,
- Zo_TuyaCalibrationTime = 5814,
- Zo_TuyaChildLock = 5834,
- Zo_TuyaComfortTemp = 5848,
- Zo_TuyaEcoTemp = 5864,
- Zo_TuyaFanMode = 5876,
- Zo_TuyaForceMode = 5888,
- Zo_TuyaMCUVersion = 5902,
- Zo_TuyaMaxTemp = 5917,
- Zo_TuyaMinTemp = 5929,
- Zo_TuyaMotorReversal = 5941,
- Zo_TuyaMovingState = 5959,
- Zo_TuyaPreset = 5975,
- Zo_TuyaQuery = 5986,
- Zo_TuyaScheduleHolidays = 5996,
- Zo_TuyaScheduleWorkdays = 6017,
- Zo_TuyaTempTarget = 6038,
- Zo_TuyaValveDetection = 6053,
- Zo_TuyaValvePosition = 6072,
- Zo_TuyaWeekSelect = 6090,
- Zo_TuyaWindowDetection = 6105,
- Zo_UnoccupiedCoolingSetpoint = 6125,
- Zo_UnoccupiedHeatingSetpoint = 6151,
- Zo_UtilityName = 6177,
- Zo_ValidUntilTime = 6189,
- Zo_ValvePosition = 6204,
- Zo_VelocityLift = 6218,
- Zo_ViewGroup = 6231,
- Zo_ViewScene = 6241,
- Zo_Water = 6251,
- Zo_WhitePointX = 6257,
- Zo_WhitePointY = 6269,
- Zo_WindowCoveringType = 6281,
- Zo_X = 6300,
- Zo_Y = 6302,
- Zo_ZCLVersion = 6304,
- Zo_ZoneState = 6315,
- Zo_ZoneStatus = 6325,
- Zo_ZoneStatusChange = 6336,
- Zo_ZoneType = 6353,
- Zo__ = 6362,
- Zo_xx = 6364,
- Zo_xx000A00 = 6367,
- Zo_xx0A = 6376,
- Zo_xx0A00 = 6381,
- Zo_xx19 = 6388,
- Zo_xx190A = 6393,
- Zo_xx190A00 = 6400,
- Zo_xxxx = 6409,
- Zo_xxxx00 = 6414,
- Zo_xxxx0A00 = 6421,
- Zo_xxxxyy = 6430,
- Zo_xxxxyyyy = 6437,
- Zo_xxxxyyyy0A00 = 6446,
- Zo_xxxxyyzz = 6459,
- Zo_xxyy = 6468,
- Zo_xxyy0A00 = 6473,
- Zo_xxyyyy = 6482,
- Zo_xxyyyy000000000000 = 6489,
- Zo_xxyyyy0A0000000000 = 6508,
- Zo_xxyyyyzz = 6527,
- Zo_xxyyyyzzzz = 6536,
- Zo_xxyyzzzz = 6547,
+ Zo_PowerOffEffect = 4615,
+ Zo_PowerOnRecall = 4630,
+ Zo_PowerOnTimer = 4644,
+ Zo_PowerSource = 4657,
+ Zo_PowerThreshold = 4669,
+ Zo_Pressure = 4684,
+ Zo_PressureMaxMeasuredValue = 4693,
+ Zo_PressureMaxScaledValue = 4718,
+ Zo_PressureMinMeasuredValue = 4741,
+ Zo_PressureMinScaledValue = 4766,
+ Zo_PressureScale = 4789,
+ Zo_PressureScaledTolerance = 4803,
+ Zo_PressureScaledValue = 4827,
+ Zo_PressureTolerance = 4847,
+ Zo_Primary1Intensity = 4865,
+ Zo_Primary1X = 4883,
+ Zo_Primary1Y = 4893,
+ Zo_Primary2Intensity = 4903,
+ Zo_Primary2X = 4921,
+ Zo_Primary2Y = 4931,
+ Zo_Primary3Intensity = 4941,
+ Zo_Primary3X = 4959,
+ Zo_Primary3Y = 4969,
+ Zo_ProductCode = 4979,
+ Zo_ProductRevision = 4991,
+ Zo_ProductURL = 5007,
+ Zo_QualityMeasure = 5018,
+ Zo_RGB = 5033,
+ Zo_RMSCurrent = 5037,
+ Zo_RMSVoltage = 5048,
+ Zo_ReactivePower = 5059,
+ Zo_RecallScene = 5073,
+ Zo_RemainingTime = 5085,
+ Zo_RemoteSensing = 5099,
+ Zo_RemoveAllGroups = 5113,
+ Zo_RemoveAllScenes = 5129,
+ Zo_RemoveGroup = 5145,
+ Zo_RemoveScene = 5157,
+ Zo_ResetAlarm = 5169,
+ Zo_ResetAllAlarms = 5180,
+ Zo_SWBuildID = 5195,
+ Zo_Sat = 5205,
+ Zo_SatMove = 5209,
+ Zo_SatStep = 5217,
+ Zo_SceneCount = 5225,
+ Zo_SceneValid = 5236,
+ Zo_ScheduleMode = 5247,
+ Zo_SeaPressure = 5260,
+ Zo_ShortPollInterval = 5272,
+ Zo_Shutter = 5290,
+ Zo_ShutterClose = 5298,
+ Zo_ShutterLift = 5311,
+ Zo_ShutterOpen = 5323,
+ Zo_ShutterStop = 5335,
+ Zo_ShutterTilt = 5347,
+ Zo_SoftwareRevision = 5359,
+ Zo_StackVersion = 5376,
+ Zo_StandardTime = 5389,
+ Zo_StartUpOnOff = 5402,
+ Zo_Status = 5415,
+ Zo_StoreScene = 5422,
+ Zo_SwitchType = 5433,
+ Zo_SystemMode = 5444,
+ Zo_TRVBoost = 5455,
+ Zo_TRVChildProtection = 5464,
+ Zo_TRVMirrorDisplay = 5483,
+ Zo_TRVMode = 5500,
+ Zo_TRVWindowOpen = 5508,
+ Zo_TempTarget = 5522,
+ Zo_Temperature = 5533,
+ Zo_TemperatureMaxMeasuredValue = 5545,
+ Zo_TemperatureMinMeasuredValue = 5573,
+ Zo_TemperatureTolerance = 5601,
+ Zo_TerncyDuration = 5622,
+ Zo_TerncyRotate = 5637,
+ Zo_ThSetpoint = 5650,
+ Zo_Time = 5661,
+ Zo_TimeEpoch = 5666,
+ Zo_TimeStatus = 5676,
+ Zo_TimeZone = 5687,
+ Zo_TotalProfileNum = 5696,
+ Zo_TuyaCalibration = 5712,
+ Zo_TuyaCalibrationTime = 5728,
+ Zo_TuyaMCUVersion = 5748,
+ Zo_TuyaMotorReversal = 5763,
+ Zo_TuyaMovingState = 5781,
+ Zo_TuyaQuery = 5797,
+ Zo_UnoccupiedCoolingSetpoint = 5807,
+ Zo_UnoccupiedHeatingSetpoint = 5833,
+ Zo_UtilityName = 5859,
+ Zo_ValidUntilTime = 5871,
+ Zo_ValvePosition = 5886,
+ Zo_VelocityLift = 5900,
+ Zo_ViewGroup = 5913,
+ Zo_ViewScene = 5923,
+ Zo_Water = 5933,
+ Zo_WhitePointX = 5939,
+ Zo_WhitePointY = 5951,
+ Zo_WindowCoveringType = 5963,
+ Zo_X = 5982,
+ Zo_Y = 5984,
+ Zo_ZCLVersion = 5986,
+ Zo_ZoneState = 5997,
+ Zo_ZoneStatus = 6007,
+ Zo_ZoneStatusChange = 6018,
+ Zo_ZoneType = 6035,
+ Zo__ = 6044,
+ Zo_xx = 6046,
+ Zo_xx000A00 = 6049,
+ Zo_xx0A = 6058,
+ Zo_xx0A00 = 6063,
+ Zo_xx19 = 6070,
+ Zo_xx190A = 6075,
+ Zo_xx190A00 = 6082,
+ Zo_xxxx = 6091,
+ Zo_xxxx00 = 6096,
+ Zo_xxxx0A00 = 6103,
+ Zo_xxxxyy = 6112,
+ Zo_xxxxyyyy = 6119,
+ Zo_xxxxyyyy0A00 = 6128,
+ Zo_xxxxyyzz = 6141,
+ Zo_xxyy = 6150,
+ Zo_xxyy0A00 = 6155,
+ Zo_xxyyyy = 6164,
+ Zo_xxyyyy000000000000 = 6171,
+ Zo_xxyyyy0A0000000000 = 6190,
+ Zo_xxyyyyzz = 6209,
+ Zo_xxyyyyzzzz = 6218,
+ Zo_xxyyzzzz = 6229,
};
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
new file mode 100644
index 000000000..fcb0686da
--- /dev/null
+++ b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_5_1_attributes.ino
@@ -0,0 +1,955 @@
+/*
+ xdrv_23_zigbee_converters.ino - zigbee support for Tasmota
+
+ Copyright (C) 2021 Theo Arends and Stephan Hadinger
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+*/
+
+#ifdef USE_ZIGBEE
+
+/*********************************************************************************************\
+ * ZCL
+\*********************************************************************************************/
+
+
+enum Z_DataTypes {
+ Znodata = 0x00,
+ Zdata8 = 0x08, Zdata16, Zdata24, Zdata32, Zdata40, Zdata48, Zdata56, Zdata64,
+ Zbool = 0x10,
+ Zmap8 = 0x18, Zmap16, Zmap24, Zmap32, Zmap40, Zmap48, Zmap56, Zmap64,
+ Zuint8 = 0x20, Zuint16, Zuint24, Zuint32, Zuint40, Zuint48, Zuint56, Zuint64,
+ Zint8 = 0x28, Zint16, Zint24, Zint32, Zint40, Zint48, Zint56, Zint64,
+ Zenum8 = 0x30, Zenum16 = 0x31,
+ Zsemi = 0x38, Zsingle = 0x39, Zdouble = 0x3A,
+ Zoctstr = 0x41, Zstring = 0x42, Zoctstr16 = 0x43, Zstring16 = 0x44,
+ Zarrray = 0x48,
+ Zstruct = 0x4C,
+ Zset = 0x50, Zbag = 0x51,
+ ZToD = 0xE0, Zdate = 0xE1, ZUTC = 0xE2,
+ ZclusterId = 0xE8, ZattribId = 0xE9, ZbacOID = 0xEA,
+ ZEUI64 = 0xF0, Zkey128 = 0xF1,
+ Zunk = 0xFF,
+ // adding fake type for Tuya specific encodings
+ Ztuya0 = Zoctstr,
+ Ztuya1 = Zbool,
+ Ztuya2 = Zint32,
+ Ztuya3 = Zstring,
+ Ztuya4 = Zuint8,
+ Ztuya5 = Zuint32
+};
+
+const char Z_DATATYPES[] PROGMEM =
+ "nodata|"
+ "data8|data16|data24|data32|data40|data48|data56|data64|"
+ "bool|"
+ "map8|map16|map24|map32|map40|map48|map56|map64|"
+ "uint8|uint16|uint24|uint32|uint40|uint48|uint56|uint64|"
+ "int8|int16|int24|int32|int40|int48|int56|int64|"
+ "enum8|enum16|"
+ "semi|single|double|"
+ "octstr|string|octstr16|string16|"
+ "array|struct|set|bag|"
+ "ToD|date|UTC|"
+ "clusterId|attribId|bacOID|"
+ "EUI64|key128|"
+ "unk"
+;
+
+const uint8_t Z_DATA_TYPES_CODE[] PROGMEM = {
+ Znodata,
+ Zdata8, Zdata16, Zdata24, Zdata32, Zdata40, Zdata48, Zdata56, Zdata64,
+ Zbool,
+ Zmap8, Zmap16, Zmap24, Zmap32, Zmap40, Zmap48, Zmap56, Zmap64,
+ Zuint8, Zuint16, Zuint24, Zuint32, Zuint40, Zuint48, Zuint56, Zuint64,
+ Zint8, Zint16, Zint24, Zint32, Zint40, Zint48, Zint56, Zint64,
+ Zenum8, Zenum16,
+ Zsemi, Zsingle, Zdouble,
+ Zoctstr, Zstring, Zoctstr16, Zstring16,
+ Zarrray, Zstruct, Zset, Zbag,
+ ZToD, Zdate, ZUTC,
+ ZclusterId, ZattribId, ZbacOID,
+ ZEUI64, Zkey128,
+ Zunk,
+};
+
+// convert a type into a name, or HEX if none found
+void Z_getTypeByNumber(char *destination, size_t destination_size, uint8_t type) {
+ for (uint32_t i = 0; i < ARRAY_SIZE(Z_DATA_TYPES_CODE); i++) {
+ if (type == pgm_read_byte(&Z_DATA_TYPES_CODE[i])) {
+ GetTextIndexed(destination, destination_size, i, Z_DATATYPES);
+ return;
+ }
+ }
+ snprintf(destination, destination_size, "%02X", type);
+}
+
+// convert a string to a type, or Zunk if no match
+uint8_t Z_getTypeByName(const char *type) {
+ if (type == nullptr) { return Zunk; }
+ char type_found[16];
+ int32_t ret = GetCommandCode(type_found, sizeof(type_found), type, Z_DATATYPES);
+ if (ret < 0) {
+ // try to decode hex
+ size_t type_len = strlen_P(type);
+ if (type_len > 0 && type_len <= 2) {
+ char *type_end;
+ ret = strtoul(type, &type_end, 16);
+ if (type_end == type) { ret = Zunk; } // could not decode
+ }
+ return ret;
+ } else {
+ return pgm_read_byte(&Z_DATA_TYPES_CODE[ret]);
+ }
+}
+//
+// get the lenth in bytes for a data-type
+// return 0 if unknown of type specific
+//
+// Note: this code is smaller than a static array
+uint8_t Z_getDatatypeLen(uint8_t t) {
+ if ( ((t >= 0x08) && (t <= 0x0F)) || // data8 - data64
+ ((t >= 0x18) && (t <= 0x2F)) ) { // map/uint/int
+ return (t & 0x07) + 1;
+ }
+ switch (t) {
+ case Zbool:
+ case Zenum8:
+ return 1;
+ case Zenum16:
+ case Zsemi:
+ case ZclusterId:
+ case ZattribId:
+ return 2;
+ case Zsingle:
+ case ZToD:
+ case Zdate:
+ case ZUTC:
+ case ZbacOID:
+ return 4;
+ case Zdouble:
+ case ZEUI64:
+ return 8;
+ case Zkey128:
+ return 16;
+ case Znodata:
+ default:
+ return 0;
+ }
+}
+
+// is the type a discrete type, cf. section 2.6.2 of ZCL spec
+bool Z_isDiscreteDataType(uint8_t t) {
+ if ( ((t >= 0x20) && (t <= 0x2F)) || // uint8 - int64
+ ((t >= 0x38) && (t <= 0x3A)) || // semi - double
+ ((t >= 0xE0) && (t <= 0xE2)) ) { // ToD - UTC
+ return false;
+ } else {
+ return true;
+ }
+}
+
+typedef struct Z_AttributeConverter {
+ uint8_t type;
+ uint8_t cluster_short;
+ uint16_t attribute;
+ uint16_t name_offset;
+ uint8_t multiplier_idx; // multiplier index for numerical value, use CmToMultiplier(), (if > 0 multiply by x, if <0 device by x)
+ // the high 4 bits are used to encode flags
+ // currently: 0x80 = this parameter needs to be exported to ZbData
+ uint8_t mapping; // high 4 bits = type, low 4 bits = offset in bytes from header
+ // still room for a byte
+} Z_AttributeConverter;
+
+// Get offset in bytes of attributes, starting after the header (skipping first 4 bytes)
+#define Z_OFFSET(c,a) (offsetof(class c, a) - sizeof(Z_Data))
+#define Z_CLASS(c) c // necessary to get a valid token without concatenation (which wouldn't work)
+#define Z_MAPPING(c,a) (((((uint8_t)Z_CLASS(c)::type) & 0x0F) << 4) | Z_OFFSET(c,a))
+
+// lines with this marker, will be used to export automatically data to `ZbData`
+// at the condition Z_MAPPING() is also used
+const uint8_t Z_EXPORT_DATA = 0x80;
+
+// Cluster numbers are store in 8 bits format to save space,
+// the following tables allows the conversion from 8 bits index Cx...
+// to the 16 bits actual cluster number
+enum Cx_cluster_short {
+ Cx0000, Cx0001, Cx0002, Cx0003, Cx0004, Cx0005, Cx0006, Cx0007,
+ Cx0008, Cx0009, Cx000A, Cx000B, Cx000C, Cx000D, Cx000E, Cx000F,
+ Cx0010, Cx0011, Cx0012, Cx0013, Cx0014, Cx001A, Cx0020, Cx0100,
+ Cx0101, Cx0102, Cx0201, Cx0300, Cx0400, Cx0401, Cx0402, Cx0403,
+ Cx0404, Cx0405, Cx0406, Cx0500, Cx0702, Cx0B01, Cx0B04, Cx0B05,
+ CxEF00, CxFC01, CxFC40, CxFCC0, CxFCCC,
+};
+
+const uint16_t Cx_cluster[] PROGMEM = {
+ 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
+ 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
+ 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x001A, 0x0020, 0x0100,
+ 0x0101, 0x0102, 0x0201, 0x0300, 0x0400, 0x0401, 0x0402, 0x0403,
+ 0x0404, 0x0405, 0x0406, 0x0500, 0x0702, 0x0B01, 0x0B04, 0x0B05,
+ 0xEF00, 0xFC01, 0xFC40, 0xFCC0, 0xFCCC,
+};
+
+uint16_t CxToCluster(uint8_t cx) {
+ if (cx < nitems(Cx_cluster)) {
+ return pgm_read_word(&Cx_cluster[cx]);
+ }
+ return 0xFFFF;
+}
+
+uint8_t ClusterToCx(uint16_t cluster) {
+ for (uint32_t i=0; icluster = cluster;
+ this->attribute = attribute;
+ this->name = name;
+ this->type = type;
+ }
+
+ uint8_t type; // zigbee type, Zunk by default
+ 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
+ String name; // name of attribute once converted
+};
+
+//
+// Class for an attribute synonym
+//
+class Z_attribute_synonym {
+public:
+ Z_attribute_synonym(void) :
+ cluster(0xFFFF), attribute(0xFFFF), new_cluster(0xFFFF), new_attribute(0xFFFF),
+ multiplier(1)
+ {};
+
+ void set(uint16_t cluster, uint16_t attribute, uint16_t new_cluster, uint16_t new_attribute, int8_t multiplier = 1) {
+ this->cluster = cluster;
+ this->attribute = attribute;
+ this->new_cluster = new_cluster;
+ this->new_attribute = new_attribute;
+ this->multiplier = multiplier;
+ }
+
+ inline bool found(void) const { return cluster != 0xFFFF && attribute != 0xFFFF; }
+
+ uint16_t cluster; // cluster to match
+ uint16_t attribute; // attribute to match
+ uint16_t new_cluster; // replace with this cluster
+ uint16_t new_attribute; // replace with this attribute
+ int8_t multiplier; // mutliplier if different than 1 (otherwise don't change value)
+};
+
+//
+//
+// matcher for a device, based on ModelID and Manufacturer
+//
+//
+class Z_plugin_matcher {
+public:
+
+ Z_plugin_matcher(void) {};
+
+ inline void setModelManuf(const char *_model, const char *_manuf) {
+ model = (const char*)_model;
+ manufacturer = (const char*)_manuf;
+ }
+
+ bool match(const char *match_model, const char *match_manuf) const {
+ bool match = true;
+ if (model.length() > 0) {
+ if (!model.equals(match_model)) {
+ match = false;
+ }
+ }
+ if (manufacturer.length() > 0) {
+ if (!manufacturer.equals(match_manuf)) {
+ match = false;
+ }
+ }
+ return match;
+ }
+
+ String model;
+ String manufacturer;
+};
+
+//
+//
+// Z_plugin_template : template for a group of devices
+//
+//
+class Z_plugin_template {
+public:
+
+ LList matchers;
+ LList synonyms;
+ LList attributes;
+ char * filename = nullptr; // the filename from which it was imported
+ // needs to be freed by ZbUnload only (to allow reuse between mutliple instances)
+};
+
+//
+//
+// Z_plugin_templates
+//
+//
+class Z_plugin_templates : public LList {
+public:
+ // match an attribute from the plug-in in-memory database
+ // returns `nullptr` if none found
+ // or a pointer to `Z_plugin_attribute`
+ const Z_plugin_attribute * matchAttributeById(const char *model, const char *manufacturer, uint16_t cluster, uint16_t attribute);
+ const Z_plugin_attribute * matchAttributeByName(const char *model, const char *manufacturer, const char *name);
+ const Z_attribute_synonym * matchAttributeSynonym(const char *model, const char *manufacturer, uint16_t cluster, uint16_t attribute);
+};
+
+const Z_attribute_synonym * Z_plugin_templates::matchAttributeSynonym(const char *model, const char *manufacturer, uint16_t cluster, uint16_t attribute) {
+ // scan through all templates
+ for (const Z_plugin_template & tmpl : *this) {
+ const LList & matchers = tmpl.matchers; // get synonyms
+ const LList & synonyms = tmpl.synonyms; // get synonyms
+
+ for (const Z_plugin_matcher & mtch : matchers) {
+ if (mtch.match(model, manufacturer)) {
+ // got a match, apply template
+ for (const Z_attribute_synonym & syn : synonyms) {
+ if (syn.cluster == cluster && syn.attribute == attribute) {
+ return &syn;
+ }
+ }
+ }
+ }
+ }
+ // no match
+ return nullptr;
+}
+
+const Z_plugin_attribute * Z_plugin_templates::matchAttributeById(const char *model, const char *manufacturer, uint16_t cluster, uint16_t attribute) {
+ // scan through all templates
+ for (const Z_plugin_template & tmpl : *this) {
+ const LList & matchers = tmpl.matchers; // get synonyms
+ const LList & attributes = tmpl.attributes; // get synonyms
+
+ for (const Z_plugin_matcher & mtch : matchers) {
+ if (mtch.match(model, manufacturer)) {
+ // got a match, apply template
+ for (const Z_plugin_attribute & attr : attributes) {
+ if (attr.cluster == cluster && attr.attribute == attribute) {
+ return &attr;
+ }
+ }
+ }
+ }
+ }
+ // no match
+ return nullptr;
+}
+
+const Z_plugin_attribute * Z_plugin_templates::matchAttributeByName(const char *model, const char *manufacturer, const char * name) {
+ // scan through all templates
+ for (const Z_plugin_template & tmpl : *this) {
+ const LList & matchers = tmpl.matchers; // get synonyms
+ const LList & attributes = tmpl.attributes; // get synonyms
+
+ for (const Z_plugin_matcher & mtch : matchers) {
+ if (mtch.match(model, manufacturer)) {
+ // got a match, apply template
+ for (const Z_plugin_attribute & attr : attributes) {
+ if (attr.name.equals(name)) {
+ return &attr;
+ }
+ }
+ }
+ }
+ }
+ // no match
+ return nullptr;
+}
+
+//
+// Attribute match result
+//
+class Z_attribute_match {
+public:
+ inline bool found(void) const { return (cluster != 0xFFFF && attribute != 0xFFFF); }
+
+ uint16_t cluster = 0xFFFF;
+ uint16_t attribute = 0xFFFF;
+ const char * name = nullptr;
+ uint8_t zigbee_type = Znodata;
+ int8_t multiplier = 1;
+ uint8_t map_offset = 0;
+ Z_Data_Type map_type = Z_Data_Type::Z_Unknown;
+};
+
+Z_attribute_match Z_plugin_matchAttributeById(const char *model, const char *manufacturer, uint16_t cluster, uint16_t attribute);
+Z_attribute_match Z_plugin_matchAttributeByName(const char *model, const char *manufacturer, const char *name);
+
+
+// list of post-processing directives
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Winvalid-offsetof" // avoid warnings since we're using offsetof() in a risky way
+const Z_AttributeConverter Z_PostProcess[] PROGMEM = {
+ { Zuint8, Cx0000, 0x0000, Z_(ZCLVersion), Cm1, 0 },
+ { Zuint8, Cx0000, 0x0001, Z_(AppVersion), Cm1, 0 },
+ { Zuint8, Cx0000, 0x0002, Z_(StackVersion), Cm1, 0 },
+ { 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 },
+ { Zenum8, Cx0000, 0x0009, Z_(GenericDeviceType), Cm1, 0 },
+ { Zoctstr, Cx0000, 0x000A, Z_(ProductCode), Cm1, 0 },
+ { Zstring, Cx0000, 0x000B, Z_(ProductURL), Cm1, 0 },
+ { Zstring, Cx0000, 0x4000, Z_(SWBuildID), Cm1, 0 },
+ { Zuint8, Cx0000, 0x4005, Z_(MullerLightMode), Cm1, 0 },
+ // { Zunk, Cx0000, 0xFFFF, nullptr, Cm0, 0 }, // Remove all other values
+ // Cmd 0x0A - Cluster 0x0000, attribute 0xFF01 - proprietary
+ { Zmap8, Cx0000, 0xFF01, Z_(), Cm0, 0 },
+ { Zmap8, Cx0000, 0xFF02, Z_(), Cm0, 0 },
+ // { Zmap8, Cx0000, 0xFF01, Z_(), Cm0, Z_AqaraSensor, 0 },
+ // { Zmap8, Cx0000, 0xFF02, Z_(), Cm0, Z_AqaraSensor2, 0 },
+
+ // Power Configuration cluster
+ { Zuint16, Cx0001, 0x0000, Z_(MainsVoltage), Cm1, 0 },
+ { Zuint8, Cx0001, 0x0001, Z_(MainsFrequency), Cm1, 0 },
+ { Zuint8, Cx0001, 0x0020, Z_(BatteryVoltage), Cm_10, 0 }, // divide by 10
+ { Zuint8, Cx0001, 0x0021, Z_(BatteryPercentage), Cm_2, 0 }, // divide by 2
+ // { Zuint8, Cx0001, 0x0021, Z_(BatteryPercentage), Cm_2, Z_BatteryPercentage, 0 }, // divide by 2
+
+ // Device Temperature Configuration cluster
+ { Zint16, Cx0002, 0x0000, Z_(CurrentTemperature), Cm1, 0 },
+ { Zint16, Cx0002, 0x0001, Z_(MinTempExperienced), Cm1, 0 },
+ { Zint16, Cx0002, 0x0002, Z_(MaxTempExperienced), Cm1, 0 },
+ { Zuint16, Cx0002, 0x0003, Z_(OverTempTotalDwell), Cm1, 0 },
+
+ // Identify cluster
+ { Zuint16, Cx0003, 0x0000, Z_(IdentifyTime), Cm1, 0 },
+
+ // Groups cluster
+ { Zmap8, Cx0004, 0x0000, Z_(GroupNameSupport), Cm1, 0 },
+
+ // Scenes cluster
+ { Zuint8, Cx0005, 0x0000, Z_(SceneCount), Cm1, 0 },
+ { Zuint8, Cx0005, 0x0001, Z_(CurrentScene), Cm1, 0 },
+ { Zuint16, Cx0005, 0x0002, Z_(CurrentGroup), Cm1, 0 },
+ { Zbool, Cx0005, 0x0003, Z_(SceneValid), Cm1, 0 },
+ //{ Zmap8, Cx0005, 0x0004, (NameSupport), Cm1, 0 },
+
+ // On/off cluster
+ { Zbool, Cx0006, 0x0000, Z_(Power), Cm1 + Z_EXPORT_DATA, Z_MAPPING(Z_Data_OnOff, power) },
+ { Zenum8, Cx0006, 0x4003, Z_(StartUpOnOff), Cm1, 0 },
+ { Zbool, Cx0006, 0x8000, Z_(Power), Cm1, 0 }, // See 7280
+
+ // On/Off Switch Configuration cluster
+ { Zenum8, Cx0007, 0x0000, Z_(SwitchType), Cm1, 0 },
+
+ // 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 },
+ { Zuint16, Cx0008, 0x0010, Z_(OnOffTransitionTime), Cm1, 0 },
+ // { Zuint8, Cx0008, 0x0011, (OnLevel), Cm1, 0 },
+ // { Zuint16, Cx0008, 0x0012, (OnTransitionTime), Cm1, 0 },
+ // { Zuint16, Cx0008, 0x0013, (OffTransitionTime), Cm1, 0 },
+ // { Zuint16, Cx0008, 0x0014, (DefaultMoveRate), Cm1, 0 },
+
+ // Alarms cluster
+ { Zuint16, Cx0009, 0x0000, Z_(AlarmCount), Cm1, 0 },
+
+ // Time cluster
+ { ZUTC, Cx000A, 0x0000, Z_(Time), Cm1, 0 },
+ { Zmap8, Cx000A, 0x0001, Z_(TimeStatus), Cm1, 0 },
+ { Zint32, Cx000A, 0x0002, Z_(TimeZone), Cm1, 0 },
+ { Zuint32, Cx000A, 0x0003, Z_(DstStart), Cm1, 0 },
+ { Zuint32, Cx000A, 0x0004, Z_(DstEnd), Cm1, 0 },
+ { Zint32, Cx000A, 0x0005, Z_(DstShift), Cm1, 0 },
+ { Zuint32, Cx000A, 0x0006, Z_(StandardTime), Cm1, 0 },
+ { Zuint32, Cx000A, 0x0007, Z_(LocalTime), Cm1, 0 },
+ { ZUTC, Cx000A, 0x0008, Z_(LastSetTime), Cm1, 0 },
+ { ZUTC, Cx000A, 0x0009, Z_(ValidUntilTime), Cm1, 0 },
+ { ZUTC, Cx000A, 0xFF00, Z_(TimeEpoch), Cm1, 0 }, // Tasmota specific, epoch
+
+ // RSSI Location cluster
+ { Zdata8, Cx000B, 0x0000, Z_(LocationType), Cm1, 0 },
+ { Zenum8, Cx000B, 0x0001, Z_(LocationMethod), Cm1, 0 },
+ { Zuint16, Cx000B, 0x0002, Z_(LocationAge), Cm1, 0 },
+ { Zuint8, Cx000B, 0x0003, Z_(QualityMeasure), Cm1, 0 },
+ { Zuint8, Cx000B, 0x0004, Z_(NumberOfDevices), Cm1, 0 },
+
+ // Analog Input cluster
+ // { 0xFF, Cx000C, 0x0004, (AnalogInActiveText), Cm1, 0 },
+ { Zstring, Cx000C, 0x001C, Z_(AnalogInDescription), Cm1, 0 },
+ // { 0xFF, Cx000C, 0x002E, (AnalogInInactiveText), Cm1, 0 },
+ { Zsingle, Cx000C, 0x0041, Z_(AnalogInMaxValue), Cm1, 0 },
+ { Zsingle, Cx000C, 0x0045, Z_(AnalogInMinValue), Cm1, 0 },
+ { Zbool, Cx000C, 0x0051, Z_(AnalogInOutOfService), Cm1, 0 },
+ { Zsingle, Cx000C, 0x0055, Z_(AnalogValue), Cm1, 0 },
+ // { 0xFF, Cx000C, 0x0057, (AnalogInPriorityArray),Cm1, 0 },
+ { Zenum8, Cx000C, 0x0067, Z_(AnalogInReliability), Cm1, 0 },
+ // { 0xFF, Cx000C, 0x0068, (AnalogInRelinquishDefault),Cm1, 0 },
+ { Zsingle, Cx000C, 0x006A, Z_(AnalogInResolution), Cm1, 0 },
+ { Zmap8, Cx000C, 0x006F, Z_(AnalogInStatusFlags), Cm1, 0 },
+ { Zenum16, Cx000C, 0x0075, Z_(AnalogInEngineeringUnits),Cm1, 0 },
+ { Zuint32, Cx000C, 0x0100, Z_(AnalogInApplicationType),Cm1, 0 },
+ { Zuint16, Cx000C, 0xFF55, Z_(AqaraRotate), Cm1, 0 },
+ { Zuint16, Cx000C, 0xFF05, Z_(Aqara_FF05), Cm1, 0 },
+
+ // Analog Output cluster
+ { Zstring, Cx000D, 0x001C, Z_(AnalogOutDescription), Cm1, 0 },
+ { Zsingle, Cx000D, 0x0041, Z_(AnalogOutMaxValue), Cm1, 0 },
+ { Zsingle, Cx000D, 0x0045, Z_(AnalogOutMinValue), Cm1, 0 },
+ { Zbool, Cx000D, 0x0051, Z_(AnalogOutOutOfService),Cm1, 0 },
+ { Zsingle, Cx000D, 0x0055, Z_(AnalogOutValue), Cm1, 0 },
+ // { Zunk, Cx000D, 0x0057, (AnalogOutPriorityArray),Cm1, 0 },
+ { Zenum8, Cx000D, 0x0067, Z_(AnalogOutReliability), Cm1, 0 },
+ { Zsingle, Cx000D, 0x0068, Z_(AnalogOutRelinquishDefault), Cm1, 0 },
+ { Zsingle, Cx000D, 0x006A, Z_(AnalogOutResolution), Cm1, 0 },
+ { Zmap8, Cx000D, 0x006F, Z_(AnalogOutStatusFlags), Cm1, 0 },
+ { Zenum16, Cx000D, 0x0075, Z_(AnalogOutEngineeringUnits), Cm1, 0 },
+ { Zuint32, Cx000D, 0x0100, Z_(AnalogOutApplicationType), Cm1, 0 },
+
+ // Analog Value cluster
+ { Zstring, Cx000E, 0x001C, Z_(AnalogDescription), Cm1, 0 },
+ { Zbool, Cx000E, 0x0051, Z_(AnalogOutOfService), Cm1, 0 },
+ { Zsingle, Cx000E, 0x0055, Z_(AnalogValue), Cm1, 0 },
+ { Zunk, Cx000E, 0x0057, Z_(AnalogPriorityArray), Cm1, 0 },
+ { Zenum8, Cx000E, 0x0067, Z_(AnalogReliability), Cm1, 0 },
+ { Zsingle, Cx000E, 0x0068, Z_(AnalogRelinquishDefault),Cm1, 0 },
+ { Zmap8, Cx000E, 0x006F, Z_(AnalogStatusFlags), Cm1, 0 },
+ { Zenum16, Cx000E, 0x0075, Z_(AnalogEngineeringUnits),Cm1, 0 },
+ { Zuint32, Cx000E, 0x0100, Z_(AnalogApplicationType),Cm1, 0 },
+
+ // Binary Input cluster
+ { Zstring, Cx000F, 0x0004, Z_(BinaryInActiveText), Cm1, 0 },
+ { Zstring, Cx000F, 0x001C, Z_(BinaryInDescription), Cm1, 0 },
+ { Zstring, Cx000F, 0x002E, Z_(BinaryInInactiveText),Cm1, 0 },
+ { Zbool, Cx000F, 0x0051, Z_(BinaryInOutOfService),Cm1, 0 },
+ { Zenum8, Cx000F, 0x0054, Z_(BinaryInPolarity), Cm1, 0 },
+ { Zstring, Cx000F, 0x0055, Z_(BinaryInValue), Cm1, 0 },
+ // { 0xFF, Cx000F, 0x0057, (BinaryInPriorityArray),Cm1, 0 },
+ { Zenum8, Cx000F, 0x0067, Z_(BinaryInReliability), Cm1, 0 },
+ { Zmap8, Cx000F, 0x006F, Z_(BinaryInStatusFlags), Cm1, 0 },
+ { Zuint32, Cx000F, 0x0100, Z_(BinaryInApplicationType),Cm1, 0 },
+
+ // Binary Output cluster
+ { Zstring, Cx0010, 0x0004, Z_(BinaryOutActiveText), Cm1, 0 },
+ { Zstring, Cx0010, 0x001C, Z_(BinaryOutDescription), Cm1, 0 },
+ { Zstring, Cx0010, 0x002E, Z_(BinaryOutInactiveText),Cm1, 0 },
+ { Zuint32, Cx0010, 0x0042, Z_(BinaryOutMinimumOffTime),Cm1, 0 },
+ { Zuint32, Cx0010, 0x0043, Z_(BinaryOutMinimumOnTime),Cm1, 0 },
+ { Zbool, Cx0010, 0x0051, Z_(BinaryOutOutOfService),Cm1, 0 },
+ { Zenum8, Cx0010, 0x0054, Z_(BinaryOutPolarity), Cm1, 0 },
+ { Zbool, Cx0010, 0x0055, Z_(BinaryOutValue), Cm1, 0 },
+ // { Zunk, Cx0010, 0x0057, (BinaryOutPriorityArray),Cm1, 0 },
+ { Zenum8, Cx0010, 0x0067, Z_(BinaryOutReliability), Cm1, 0 },
+ { Zbool, Cx0010, 0x0068, Z_(BinaryOutRelinquishDefault),Cm1, 0 },
+ { Zmap8, Cx0010, 0x006F, Z_(BinaryOutStatusFlags), Cm1, 0 },
+ { Zuint32, Cx0010, 0x0100, Z_(BinaryOutApplicationType),Cm1, 0 },
+
+ // Binary Value cluster
+ { Zstring, Cx0011, 0x0004, Z_(BinaryActiveText), Cm1, 0 },
+ { Zstring, Cx0011, 0x001C, Z_(BinaryDescription), Cm1, 0 },
+ { Zstring, Cx0011, 0x002E, Z_(BinaryInactiveText), Cm1, 0 },
+ { Zuint32, Cx0011, 0x0042, Z_(BinaryMinimumOffTime), Cm1, 0 },
+ { Zuint32, Cx0011, 0x0043, Z_(BinaryMinimumOnTime), Cm1, 0 },
+ { Zbool, Cx0011, 0x0051, Z_(BinaryOutOfService), Cm1, 0 },
+ { Zbool, Cx0011, 0x0055, Z_(BinaryValue), Cm1, 0 },
+ // { Zunk, Cx0011, 0x0057, (BinaryPriorityArray), Cm1, 0 },
+ { Zenum8, Cx0011, 0x0067, Z_(BinaryReliability), Cm1, 0 },
+ { Zbool, Cx0011, 0x0068, Z_(BinaryRelinquishDefault),Cm1, 0 },
+ { Zmap8, Cx0011, 0x006F, Z_(BinaryStatusFlags), Cm1, 0 },
+ { Zuint32, Cx0011, 0x0100, Z_(BinaryApplicationType),Cm1, 0 },
+
+ // Multistate Input cluster
+ // { Zunk, Cx0012, 0x000E, (MultiInStateText), Cm1, 0 },
+ { Zstring, Cx0012, 0x001C, Z_(MultiInDescription), Cm1, 0 },
+ { Zuint16, Cx0012, 0x004A, Z_(MultiInNumberOfStates),Cm1, 0 },
+ { Zbool, Cx0012, 0x0051, Z_(MultiInOutOfService), Cm1, 0 },
+ { Zuint16, Cx0012, 0x0055, Z_(MultiInValue), Cm1, 0 },
+ // { Zuint16, Cx0012, 0x0055, Z_(MultiInValue), Cm0, Z_AqaraCube, 0 },
+ // { Zuint16, Cx0012, 0x0055, Z_(MultiInValue), Cm0, Z_AqaraButton, 0 },
+ { Zenum8, Cx0012, 0x0067, Z_(MultiInReliability), Cm1, 0 },
+ { Zmap8, Cx0012, 0x006F, Z_(MultiInStatusFlags), Cm1, 0 },
+ { Zuint32, Cx0012, 0x0100, Z_(MultiInApplicationType),Cm1, 0 },
+
+ // Multistate output
+ // { Zunk, Cx0013, 0x000E, (MultiOutStateText), Cm1, 0 },
+ { Zstring, Cx0013, 0x001C, Z_(MultiOutDescription), Cm1, 0 },
+ { Zuint16, Cx0013, 0x004A, Z_(MultiOutNumberOfStates),Cm1, 0 },
+ { Zbool, Cx0013, 0x0051, Z_(MultiOutOutOfService), Cm1, 0 },
+ { Zuint16, Cx0013, 0x0055, Z_(MultiOutValue), Cm1, 0 },
+ // { Zunk, Cx0013, 0x0057, (MultiOutPriorityArray),Cm1, 0 },
+ { Zenum8, Cx0013, 0x0067, Z_(MultiOutReliability), Cm1, 0 },
+ { Zuint16, Cx0013, 0x0068, Z_(MultiOutRelinquishDefault),Cm1, 0 },
+ { Zmap8, Cx0013, 0x006F, Z_(MultiOutStatusFlags), Cm1, 0 },
+ { Zuint32, Cx0013, 0x0100, Z_(MultiOutApplicationType),Cm1, 0 },
+
+ // Multistate Value cluster
+ // { Zunk, Cx0014, 0x000E, (MultiStateText), Cm1, 0 },
+ { Zstring, Cx0014, 0x001C, Z_(MultiDescription), Cm1, 0 },
+ { Zuint16, Cx0014, 0x004A, Z_(MultiNumberOfStates), Cm1, 0 },
+ { Zbool, Cx0014, 0x0051, Z_(MultiOutOfService), Cm1, 0 },
+ { Zuint16, Cx0014, 0x0055, Z_(MultiValue), Cm1, 0 },
+ { Zenum8, Cx0014, 0x0067, Z_(MultiReliability), Cm1, 0 },
+ { Zuint16, Cx0014, 0x0068, Z_(MultiRelinquishDefault),Cm1, 0 },
+ { Zmap8, Cx0014, 0x006F, Z_(MultiStatusFlags), Cm1, 0 },
+ { Zuint32, Cx0014, 0x0100, Z_(MultiApplicationType), Cm1, 0 },
+
+ // Power Profile cluster
+ { Zuint8, Cx001A, 0x0000, Z_(TotalProfileNum), Cm1, 0 },
+ { Zbool, Cx001A, 0x0001, Z_(MultipleScheduling), Cm1, 0 },
+ { Zmap8, Cx001A, 0x0002, Z_(EnergyFormatting), Cm1, 0 },
+ { Zbool, Cx001A, 0x0003, Z_(EnergyRemote), Cm1, 0 },
+ { Zmap8, Cx001A, 0x0004, Z_(ScheduleMode), Cm1, 0 },
+
+ // Poll Control cluster
+ { Zuint32, Cx0020, 0x0000, Z_(CheckinInterval), Cm1, 0 },
+ { Zuint32, Cx0020, 0x0001, Z_(LongPollInterval), Cm1, 0 },
+ { Zuint16, Cx0020, 0x0002, Z_(ShortPollInterval), Cm1, 0 },
+ { Zuint16, Cx0020, 0x0003, Z_(FastPollTimeout), Cm1, 0 },
+ { Zuint32, Cx0020, 0x0004, Z_(CheckinIntervalMin), Cm1, 0 },
+ { Zuint32, Cx0020, 0x0005, Z_(LongPollIntervalMin), Cm1, 0 },
+ { Zuint16, Cx0020, 0x0006, Z_(FastPollTimeoutMax), Cm1, 0 },
+
+ // Shade Configuration cluster
+ { Zuint16, Cx0100, 0x0000, Z_(PhysicalClosedLimit), Cm1, 0 },
+ { Zuint8, Cx0100, 0x0001, Z_(MotorStepSize), Cm1, 0 },
+ { Zmap8, Cx0100, 0x0002, Z_(Status), Cm1, 0 },
+ { Zuint16, Cx0100, 0x0010, Z_(ClosedLimit), Cm1, 0 },
+ { Zenum8, Cx0100, 0x0011, Z_(Mode), Cm1, 0 },
+
+ // Door Lock cluster
+ { Zenum8, Cx0101, 0x0000, Z_(LockState), Cm1, 0 },
+ { Zenum8, Cx0101, 0x0001, Z_(LockType), Cm1, 0 },
+ { Zbool, Cx0101, 0x0002, Z_(ActuatorEnabled), Cm1, 0 },
+ { Zenum8, Cx0101, 0x0003, Z_(DoorState), Cm1, 0 },
+ { Zuint32, Cx0101, 0x0004, Z_(DoorOpenEvents), Cm1, 0 },
+ { Zuint32, Cx0101, 0x0005, Z_(DoorClosedEvents), Cm1, 0 },
+ { Zuint16, Cx0101, 0x0006, Z_(OpenPeriod), Cm1, 0 },
+
+ // Aqara Lumi Vibration Sensor
+ { Zuint16, Cx0101, 0x0055, Z_(AqaraVibrationMode), Cm1, 0 },
+ { Zuint16, Cx0101, 0x0503, Z_(AqaraVibrationsOrAngle), Cm1, 0 },
+ { Zuint32, Cx0101, 0x0505, Z_(AqaraVibration505), Cm1, 0 },
+ { Zuint48, Cx0101, 0x0508, Z_(AqaraAccelerometer), Cm1, 0 },
+
+ // Window Covering cluster
+ { Zenum8, Cx0102, 0x0000, Z_(WindowCoveringType), Cm1, 0 },
+ { Zuint16, Cx0102, 0x0001, Z_(PhysicalClosedLimitLift),Cm1, 0 },
+ { Zuint16, Cx0102, 0x0002, Z_(PhysicalClosedLimitTilt),Cm1, 0 },
+ { Zuint16, Cx0102, 0x0003, Z_(CurrentPositionLift), Cm1, 0 },
+ { Zuint16, Cx0102, 0x0004, Z_(CurrentPositionTilt), Cm1, 0 },
+ { Zuint16, Cx0102, 0x0005, Z_(NumberofActuationsLift),Cm1, 0 },
+ { Zuint16, Cx0102, 0x0006, Z_(NumberofActuationsTilt),Cm1, 0 },
+ { Zmap8, Cx0102, 0x0007, Z_(ConfigStatus), Cm1, 0 },
+ { Zuint8, Cx0102, 0x0008, Z_(CurrentPositionLiftPercentage),Cm1, 0 },
+ { Zuint8, Cx0102, 0x0009, Z_(CurrentPositionTiltPercentage),Cm1, 0 },
+ { Zuint16, Cx0102, 0x0010, Z_(InstalledOpenLimitLift),Cm1, 0 },
+ { Zuint16, Cx0102, 0x0011, Z_(InstalledClosedLimitLift),Cm1, 0 },
+ { Zuint16, Cx0102, 0x0012, Z_(InstalledOpenLimitTilt),Cm1, 0 },
+ { Zuint16, Cx0102, 0x0013, Z_(InstalledClosedLimitTilt),Cm1, 0 },
+ { Zuint16, Cx0102, 0x0014, Z_(VelocityLift), Cm1, 0 },
+ { Zuint16, Cx0102, 0x0015, Z_(AccelerationTimeLift),Cm1, 0 },
+ { Zuint16, Cx0102, 0x0016, Z_(DecelerationTimeLift), Cm1, 0 },
+ { Zmap8, Cx0102, 0x0017, Z_(Mode), Cm1, 0 },
+ { Zoctstr, Cx0102, 0x0018, Z_(IntermediateSetpointsLift),Cm1, 0 },
+ { Zoctstr, Cx0102, 0x0019, Z_(IntermediateSetpointsTilt),Cm1, 0 },
+ // Tuya specific, from Zigbee2MQTT https://github.com/Koenkk/zigbee-herdsman/blob/4fed7310d1e1e9d81e5148cf5b4d8ec98d03c687/src/zcl/definition/cluster.ts#L1772
+ { Zenum8, Cx0102, 0xF000, Z_(TuyaMovingState),Cm1, 0 },
+ { Zenum8, Cx0102, 0xF001, Z_(TuyaCalibration),Cm1, 0 },
+ { Zenum8, Cx0102, 0xF002, Z_(TuyaMotorReversal),Cm1, 0 },
+ { Zuint16, Cx0102, 0xF003, Z_(TuyaCalibrationTime),Cm1, 0 },
+
+ // Thermostat
+ { Zint16, Cx0201, 0x0000, Z_(LocalTemperature), Cm_100, Z_MAPPING(Z_Data_Thermo, temperature) },
+ { Zint16, Cx0201, 0x0001, Z_(OutdoorTemperature),Cm_100, 0 },
+ { Zuint8, Cx0201, 0x0007, Z_(PICoolingDemand), Cm1, Z_MAPPING(Z_Data_Thermo, th_setpoint) },
+ { Zuint8, Cx0201, 0x0008, Z_(PIHeatingDemand), Cm1, Z_MAPPING(Z_Data_Thermo, th_setpoint) },
+ { Zint8, Cx0201, 0x0010, Z_(LocalTemperatureCalibration), Cm_10, 0 },
+ { Zint16, Cx0201, 0x0011, Z_(OccupiedCoolingSetpoint), Cm_100, Z_MAPPING(Z_Data_Thermo, temperature_target) },
+ { Zint16, Cx0201, 0x0012, Z_(OccupiedHeatingSetpoint), Cm_100, Z_MAPPING(Z_Data_Thermo, temperature_target) },
+ { Zint16, Cx0201, 0x0013, Z_(UnoccupiedCoolingSetpoint), Cm_100, 0 },
+ { Zint16, Cx0201, 0x0014, Z_(UnoccupiedHeatingSetpoint), Cm_100, 0 },
+ { Zmap8, Cx0201, 0x001A, Z_(RemoteSensing), Cm1, 0 },
+ { Zenum8, Cx0201, 0x001B, Z_(ControlSequenceOfOperation), Cm1, 0 },
+ { Zenum8, Cx0201, 0x001C, Z_(SystemMode), Cm1, 0 },
+ // below is Eurotronic specific
+ { Zenum8, Cx0201, 0x4000, Z_(TRVMode), Cm1, 0 },
+ { Zuint8, Cx0201, 0x4001, Z_(ValvePosition), Cm1, 0 },
+ { Zuint8, Cx0201, 0x4002, Z_(EurotronicErrors), Cm1, 0 },
+ { Zint16, Cx0201, 0x4003, Z_(CurrentTemperatureSetPoint), Cm_100, 0 },
+ { Zuint24, Cx0201, 0x4008, Z_(EurotronicHostFlags), Cm1, 0 },
+ // below are synthetic virtual attributes used to decode EurotronicHostFlags
+ // Last byte acts as a field mask for the lowest byte value
+ { Zbool, Cx0201, 0xF002, Z_(TRVMirrorDisplay), Cm1, 0 },
+ { Zbool, Cx0201, 0xF004, Z_(TRVBoost), Cm1, 0 },
+ { Zbool, Cx0201, 0xF010, Z_(TRVWindowOpen), Cm1, 0 },
+ { Zbool, Cx0201, 0xF080, Z_(TRVChildProtection), Cm1, 0 },
+ // below are virtual attributes to simplify ZbData import/export
+ { Zuint8, Cx0201, 0xFFF0, Z_(ThSetpoint), Cm1 + Z_EXPORT_DATA, Z_MAPPING(Z_Data_Thermo, th_setpoint) },
+ { Zint16, Cx0201, 0xFFF1, Z_(TempTarget), Cm_100 + Z_EXPORT_DATA, Z_MAPPING(Z_Data_Thermo, temperature_target) },
+
+ // Color Control cluster
+ { Zuint8, Cx0300, 0x0000, Z_(Hue), Cm1 + Z_EXPORT_DATA, Z_MAPPING(Z_Data_Light, hue) },
+ { Zuint8, Cx0300, 0x0001, Z_(Sat), Cm1 + Z_EXPORT_DATA, Z_MAPPING(Z_Data_Light, sat) },
+ { Zuint16, Cx0300, 0x0002, Z_(RemainingTime), Cm1, 0 },
+ { Zuint16, Cx0300, 0x0003, Z_(X), Cm1 + Z_EXPORT_DATA, Z_MAPPING(Z_Data_Light, x) },
+ { Zuint16, Cx0300, 0x0004, Z_(Y), Cm1 + Z_EXPORT_DATA, Z_MAPPING(Z_Data_Light, y) },
+ { Zenum8, Cx0300, 0x0005, Z_(DriftCompensation), Cm1, 0 },
+ { Zstring, Cx0300, 0x0006, Z_(CompensationText), Cm1, 0 },
+ { Zuint16, Cx0300, 0x0007, Z_(CT), Cm1 + Z_EXPORT_DATA, Z_MAPPING(Z_Data_Light, ct) },
+ { Zenum8, Cx0300, 0x0008, Z_(ColorMode), Cm1 + Z_EXPORT_DATA, Z_MAPPING(Z_Data_Light, colormode) },
+ { Zuint8, Cx0300, 0x0010, Z_(NumberOfPrimaries), Cm1, 0 },
+ { Zuint16, Cx0300, 0x0011, Z_(Primary1X), Cm1, 0 },
+ { Zuint16, Cx0300, 0x0012, Z_(Primary1Y), Cm1, 0 },
+ { Zuint8, Cx0300, 0x0013, Z_(Primary1Intensity), Cm1, 0 },
+ { Zuint16, Cx0300, 0x0015, Z_(Primary2X), Cm1, 0 },
+ { Zuint16, Cx0300, 0x0016, Z_(Primary2Y), Cm1, 0 },
+ { Zuint8, Cx0300, 0x0017, Z_(Primary2Intensity), Cm1, 0 },
+ { Zuint16, Cx0300, 0x0019, Z_(Primary3X), Cm1, 0 },
+ { Zuint16, Cx0300, 0x001A, Z_(Primary3Y), Cm1, 0 },
+ { Zuint8, Cx0300, 0x001B, Z_(Primary3Intensity), Cm1, 0 },
+ { Zuint16, Cx0300, 0x0030, Z_(WhitePointX), Cm1, 0 },
+ { Zuint16, Cx0300, 0x0031, Z_(WhitePointY), Cm1, 0 },
+ { Zuint16, Cx0300, 0x0032, Z_(ColorPointRX), Cm1, 0 },
+ { Zuint16, Cx0300, 0x0033, Z_(ColorPointRY), Cm1, 0 },
+ { Zuint8, Cx0300, 0x0034, Z_(ColorPointRIntensity), Cm1, 0 },
+ { Zuint16, Cx0300, 0x0036, Z_(ColorPointGX), Cm1, 0 },
+ { Zuint16, Cx0300, 0x0037, Z_(ColorPointGY), Cm1, 0 },
+ { Zuint8, Cx0300, 0x0038, Z_(ColorPointGIntensity), Cm1, 0 },
+ { Zuint16, Cx0300, 0x003A, Z_(ColorPointBX), Cm1, 0 },
+ { Zuint16, Cx0300, 0x003B, Z_(ColorPointBY), Cm1, 0 },
+ { Zuint8, Cx0300, 0x003C, Z_(ColorPointBIntensity), Cm1, 0 },
+
+ // Illuminance Measurement cluster
+ { Zuint16, Cx0400, 0x0000, Z_(Illuminance), Cm1 + Z_EXPORT_DATA, Z_MAPPING(Z_Data_PIR, illuminance) }, // Illuminance (in Lux)
+ { Zuint16, Cx0400, 0x0001, Z_(IlluminanceMinMeasuredValue), Cm1, 0 }, //
+ { Zuint16, Cx0400, 0x0002, Z_(IlluminanceMaxMeasuredValue), Cm1, 0 }, //
+ { Zuint16, Cx0400, 0x0003, Z_(IlluminanceTolerance), Cm1, 0 }, //
+ { Zenum8, Cx0400, 0x0004, Z_(IlluminanceLightSensorType), Cm1, 0 }, //
+ { Zunk, Cx0400, 0xFFFF, Z_(), Cm0, 0 }, // Remove all other values
+
+ // Illuminance Level Sensing cluster
+ { Zenum8, Cx0401, 0x0000, Z_(IlluminanceLevelStatus), Cm1, 0 }, // Illuminance (in Lux)
+ { Zenum8, Cx0401, 0x0001, Z_(IlluminanceLightSensorType), Cm1, 0 }, // LightSensorType
+ { Zuint16, Cx0401, 0x0010, Z_(IlluminanceTargetLevel), Cm1, 0 }, //
+ { Zunk, Cx0401, 0xFFFF, Z_(), Cm0, 0 }, // Remove all other values
+
+ // Temperature Measurement cluster
+ { Zint16, Cx0402, 0x0000, Z_(Temperature), Cm_100 + Z_EXPORT_DATA, Z_MAPPING(Z_Data_Thermo, temperature) },
+ { Zint16, Cx0402, 0x0001, Z_(TemperatureMinMeasuredValue), Cm_100, 0 }, //
+ { Zint16, Cx0402, 0x0002, Z_(TemperatureMaxMeasuredValue), Cm_100, 0 }, //
+ { Zuint16, Cx0402, 0x0003, Z_(TemperatureTolerance), Cm_100, 0 }, //
+ { Zunk, Cx0402, 0xFFFF, Z_(), Cm0, 0 }, // Remove all other values
+
+ // Pressure Measurement cluster
+ { Zint16, Cx0403, 0x0000, Z_(Pressure), Cm1 + Z_EXPORT_DATA, Z_MAPPING(Z_Data_Thermo, pressure) }, // Pressure
+ { Zint16, Cx0403, 0x0001, Z_(PressureMinMeasuredValue), Cm1, 0 }, //
+ { Zint16, Cx0403, 0x0002, Z_(PressureMaxMeasuredValue), Cm1, 0 }, //
+ { Zuint16, Cx0403, 0x0003, Z_(PressureTolerance), Cm1, 0 }, //
+ { Zint16, Cx0403, 0x0010, Z_(PressureScaledValue), Cm1, 0 }, //
+ { Zint16, Cx0403, 0x0011, Z_(PressureMinScaledValue), Cm1, 0 }, //
+ { Zint16, Cx0403, 0x0012, Z_(PressureMaxScaledValue), Cm1, 0 }, //
+ { Zuint16, Cx0403, 0x0013, Z_(PressureScaledTolerance), Cm1, 0 }, //
+ { Zint8, Cx0403, 0x0014, Z_(PressureScale), Cm1, 0 }, //
+ { Zint16, Cx0403, 0xFFF0, Z_(SeaPressure), Cm1, Z_MAPPING(Z_Data_Thermo, pressure) }, // Pressure at Sea Level, Tasmota specific
+ { Zunk, Cx0403, 0xFFFF, Z_(), Cm0, 0 }, // Remove all other Pressure values
+
+ // Flow Measurement cluster
+ { Zuint16, Cx0404, 0x0000, Z_(FlowRate), Cm_10, 0 }, // Flow (in m3/h)
+ { Zuint16, Cx0404, 0x0001, Z_(FlowMinMeasuredValue), Cm1, 0 }, //
+ { Zuint16, Cx0404, 0x0002, Z_(FlowMaxMeasuredValue), Cm1, 0 }, //
+ { Zuint16, Cx0404, 0x0003, Z_(FlowTolerance), Cm1, 0 }, //
+ { Zunk, Cx0404, 0xFFFF, Z_(), Cm0, 0 }, // Remove all other values
+
+ // Relative Humidity Measurement cluster
+ { Zuint16, Cx0405, 0x0000, Z_(Humidity), Cm_100 + Z_EXPORT_DATA, Z_MAPPING(Z_Data_Thermo, humidity) }, // Humidity
+ { Zuint16, Cx0405, 0x0001, Z_(HumidityMinMeasuredValue), Cm1, 0 }, //
+ { Zuint16, Cx0405, 0x0002, Z_(HumidityMaxMeasuredValue), Cm1, 0 }, //
+ { Zuint16, Cx0405, 0x0003, Z_(HumidityTolerance), Cm1, 0 }, //
+ { Zunk, Cx0405, 0xFFFF, Z_(), Cm0, 0 }, // Remove all other values
+
+ // Occupancy Sensing cluster
+ { Zmap8, Cx0406, 0x0000, Z_(Occupancy), Cm1 + Z_EXPORT_DATA, Z_MAPPING(Z_Data_PIR, occupancy) }, // Occupancy (map8)
+ { Zenum8, Cx0406, 0x0001, Z_(OccupancySensorType), Cm1, 0 }, // OccupancySensorType
+ { Zunk, Cx0406, 0xFFFF, Z_(), Cm0, 0 }, // Remove all other values
+
+ // IAS Cluster (Intruder Alarm System)
+ { Zenum8, Cx0500, 0x0000, Z_(ZoneState), Cm1, 0 }, // Occupancy (map8)
+ { Zenum16, Cx0500, 0x0001, Z_(ZoneType), Cm1 + Z_EXPORT_DATA, Z_MAPPING(Z_Data_Alarm, zone_type) }, // Zone type for sensor
+ { Zmap16, Cx0500, 0x0002, Z_(ZoneStatus), Cm1 + Z_EXPORT_DATA, Z_MAPPING(Z_Data_Alarm, zone_status) }, // Zone status for sensor
+ { Zuint8, Cx0500, 0xFFF0 + ZA_CIE, Z_(CIE), Cm1, 0 },
+ { Zuint8, Cx0500, 0xFFF0 + ZA_PIR, Z_(Occupancy), Cm1, 0 }, // normally converted to the actual Occupancy 0406/0000
+ { Zuint8, Cx0500, 0xFFF0 + ZA_Contact, Z_(Contact), Cm1, Z_MAPPING(Z_Data_Alarm, zone_status) }, // We fit the first bit in the LSB
+ { Zuint8, Cx0500, 0xFFF0 + ZA_Fire, Z_(Fire), Cm1, 0 },
+ { Zuint8, Cx0500, 0xFFF0 + ZA_Water, Z_(Water), Cm1, 0 },
+ { Zuint8, Cx0500, 0xFFF0 + ZA_CO, Z_(CO), Cm1, 0 },
+ { Zuint8, Cx0500, 0xFFF0 + ZA_Personal, Z_(PersonalAlarm),Cm1, 0 },
+ { Zuint8, Cx0500, 0xFFF0 + ZA_Movement, Z_(Movement), Cm1, 0 },
+ { Zuint8, Cx0500, 0xFFF0 + ZA_Panic, Z_(Panic), Cm1, 0 },
+ { Zuint8, Cx0500, 0xFFF0 + ZA_GlassBreak, Z_(GlassBreak),Cm1, 0 },
+
+ // Metering (Smart Energy) cluster
+ { Zuint48, Cx0702, 0x0000, Z_(EnergyTotal), Cm1, 0 },
+
+ // Meter Identification cluster
+ { Zstring, Cx0B01, 0x0000, Z_(CompanyName), Cm1, 0 },
+ { Zuint16, Cx0B01, 0x0001, Z_(MeterTypeID), Cm1, 0 },
+ { Zuint16, Cx0B01, 0x0004, Z_(DataQualityID), Cm1, 0 },
+ { Zstring, Cx0B01, 0x0005, Z_(CustomerName), Cm1, 0 },
+ { Zoctstr, Cx0B01, 0x0006, Z_(Model), Cm1, 0 },
+ { Zoctstr, Cx0B01, 0x0007, Z_(PartNumber), Cm1, 0 },
+ { Zoctstr, Cx0B01, 0x0008, Z_(ProductRevision), Cm1, 0 },
+ { Zoctstr, Cx0B01, 0x000A, Z_(SoftwareRevision), Cm1, 0 },
+ { Zstring, Cx0B01, 0x000B, Z_(UtilityName), Cm1, 0 },
+ { Zstring, Cx0B01, 0x000C, Z_(POD), Cm1, 0 },
+ { Zint24, Cx0B01, 0x000D, Z_(AvailablePower), Cm1, 0 },
+ { Zint24, Cx0B01, 0x000E, Z_(PowerThreshold), Cm1, 0 },
+
+ // Electrical Measurement cluster
+ { Zuint16, Cx0B04, 0x0505, Z_(RMSVoltage), Cm1 + Z_EXPORT_DATA, Z_MAPPING(Z_Data_Plug, mains_voltage) },
+ { Zuint16, Cx0B04, 0x0508, Z_(RMSCurrent), Cm1, 0 },
+ { Zint16, Cx0B04, 0x050B, Z_(ActivePower), Cm1 + Z_EXPORT_DATA, Z_MAPPING(Z_Data_Plug, mains_power) },
+ { Zint16, Cx0B04, 0x050E, Z_(ReactivePower), Cm1, 0 },
+ { Zint16, Cx0B04, 0x050F, Z_(ApparentPower), Cm1, 0 },
+
+ // Diagnostics cluster
+ { Zuint16, Cx0B05, 0x0000, Z_(NumberOfResets), Cm1, 0 },
+ { Zuint16, Cx0B05, 0x0001, Z_(PersistentMemoryWrites),Cm1, 0 },
+ { Zuint8, Cx0B05, 0x011C, Z_(LastMessageLQI), Cm1, 0 },
+ { Zuint8, Cx0B05, 0x011D, Z_(LastMessageRSSI), Cm1, 0 },
+
+ // Tuya Moes specific - 0xEF00
+ // 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 },
+ // { Ztuya1, CxEF00, 0x0104, Z_(Power4), Cm1, 0 },
+ // { Ztuya1, CxEF00, 0x0107, Z_(TuyaChildLock), Cm1, 0 },
+ // { Ztuya1, CxEF00, 0x0112, Z_(TuyaWindowDetection), Cm1, 0 },
+ // { Ztuya1, CxEF00, 0x0114, Z_(TuyaValveDetection), Cm1, 0 },
+ // { Ztuya1, CxEF00, 0x0174, Z_(TuyaAutoLock), Cm1, 0 },
+ // { Zint16, CxEF00, 0x0202, Z_(TuyaTempTarget), Cm_10, Z_MAPPING(Z_Data_Thermo, temperature_target) },
+ // { Zint16, CxEF00, 0x0203, Z_(LocalTemperature), Cm_10, Z_MAPPING(Z_Data_Thermo, temperature) }, // will be overwritten by actual LocalTemperature
+ // { Zuint8, CxEF00, 0x0203, Z_(Dimmer), Cm1, Z_MAPPING(Z_Data_Light, dimmer) }, // will be overwritten by actual LocalTemperature
+ // { Zmap8, CxEF00, 0x0203, Z_(Occupancy), Cm1, Z_MAPPING(Z_Data_PIR, occupancy) }, // 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 },
+ // { Zuint8, CxEF00, 0x026D, Z_(TuyaValvePosition), Cm1, Z_MAPPING(Z_Data_Thermo, th_setpoint) },
+ // { 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 },
+
+ // Legrand BTicino - Manuf code 0x1021
+ { Zdata16, CxFC01, 0x0000, Z_(LegrandOpt1), Cm1, 0 },
+ { Zbool, CxFC01, 0x0001, Z_(LegrandOpt2), Cm1, 0 },
+ { Zbool, CxFC01, 0x0002, Z_(LegrandOpt3), Cm1, 0 },
+
+ // Legrand - Manuf code 0x1021
+ { Zenum8, CxFC40, 0x0000, Z_(LegrandHeatingMode), Cm1, 0 },
+
+ // Aqara Opple spacific
+ { Zuint8, CxFCC0, 0x0009, Z_(OppleMode), Cm1, 0 },
+
+ // Terncy specific - 0xFCCC
+ { Zuint16, CxFCCC, 0x001A, Z_(TerncyDuration), Cm1, 0 },
+ { Zint16, CxFCCC, 0x001B, Z_(TerncyRotate), Cm1, 0 },
+};
+#pragma GCC diagnostic pop
+
+#endif // USE_ZIGBEE
diff --git a/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_5_converters.ino b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_5_2_converters.ino
similarity index 58%
rename from tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_5_converters.ino
rename to tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_5_2_converters.ino
index 94095188d..85faa6d85 100644
--- a/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_5_converters.ino
+++ b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_5_2_converters.ino
@@ -19,653 +19,6 @@
#ifdef USE_ZIGBEE
-/*********************************************************************************************\
- * ZCL
-\*********************************************************************************************/
-
-
-enum Z_DataTypes {
- Znodata = 0x00,
- Zdata8 = 0x08, Zdata16, Zdata24, Zdata32, Zdata40, Zdata48, Zdata56, Zdata64,
- Zbool = 0x10,
- Zmap8 = 0x18, Zmap16, Zmap24, Zmap32, Zmap40, Zmap48, Zmap56, Zmap64,
- Zuint8 = 0x20, Zuint16, Zuint24, Zuint32, Zuint40, Zuint48, Zuint56, Zuint64,
- Zint8 = 0x28, Zint16, Zint24, Zint32, Zint40, Zint48, Zint56, Zint64,
- Zenum8 = 0x30, Zenum16 = 0x31,
- Zsemi = 0x38, Zsingle = 0x39, Zdouble = 0x3A,
- Zoctstr = 0x41, Zstring = 0x42, Zoctstr16 = 0x43, Zstring16 = 0x44,
- Arrray = 0x48,
- Zstruct = 0x4C,
- Zset = 0x50, Zbag = 0x51,
- ZToD = 0xE0, Zdate = 0xE1, ZUTC = 0xE2,
- ZclusterId = 0xE8, ZattribId = 0xE9, ZbacOID = 0xEA,
- ZEUI64 = 0xF0, Zkey128 = 0xF1,
- Zunk = 0xFF,
- // adding fake type for Tuya specific encodings
- Ztuya0 = Zoctstr,
- Ztuya1 = Zbool,
- Ztuya2 = Zint32,
- Ztuya3 = Zstring,
- Ztuya4 = Zuint8,
- Ztuya5 = Zuint32
-};
-
-//
-// get the lenth in bytes for a data-type
-// return 0 if unknown of type specific
-//
-// Note: this code is smaller than a static array
-uint8_t Z_getDatatypeLen(uint8_t t) {
- if ( ((t >= 0x08) && (t <= 0x0F)) || // data8 - data64
- ((t >= 0x18) && (t <= 0x2F)) ) { // map/uint/int
- return (t & 0x07) + 1;
- }
- switch (t) {
- case Zbool:
- case Zenum8:
- return 1;
- case Zenum16:
- case Zsemi:
- case ZclusterId:
- case ZattribId:
- return 2;
- case Zsingle:
- case ZToD:
- case Zdate:
- case ZUTC:
- case ZbacOID:
- return 4;
- case Zdouble:
- case ZEUI64:
- return 8;
- case Zkey128:
- return 16;
- case Znodata:
- default:
- return 0;
- }
-}
-
-// is the type a discrete type, cf. section 2.6.2 of ZCL spec
-bool Z_isDiscreteDataType(uint8_t t) {
- if ( ((t >= 0x20) && (t <= 0x2F)) || // uint8 - int64
- ((t >= 0x38) && (t <= 0x3A)) || // semi - double
- ((t >= 0xE0) && (t <= 0xE2)) ) { // ToD - UTC
- return false;
- } else {
- return true;
- }
-}
-
-typedef struct Z_AttributeConverter {
- uint8_t type;
- uint8_t cluster_short;
- uint16_t attribute;
- uint16_t name_offset;
- uint8_t multiplier_idx; // multiplier index for numerical value, use CmToMultiplier(), (if > 0 multiply by x, if <0 device by x)
- // the high 4 bits are used to encode flags
- // currently: 0x80 = this parameter needs to be exported to ZbData
- uint8_t mapping; // high 4 bits = type, low 4 bits = offset in bytes from header
- // still room for a byte
-} Z_AttributeConverter;
-
-// Get offset in bytes of attributes, starting after the header (skipping first 4 bytes)
-#define Z_OFFSET(c,a) (offsetof(class c, a) - sizeof(Z_Data))
-#define Z_CLASS(c) c // necessary to get a valid token without concatenation (which wouldn't work)
-#define Z_MAPPING(c,a) (((((uint8_t)Z_CLASS(c)::type) & 0x0F) << 4) | Z_OFFSET(c,a))
-
-// lines with this marker, will be used to export automatically data to `ZbData`
-// at the condition Z_MAPPING() is also used
-const uint8_t Z_EXPORT_DATA = 0x80;
-
-// Cluster numbers are store in 8 bits format to save space,
-// the following tables allows the conversion from 8 bits index Cx...
-// to the 16 bits actual cluster number
-enum Cx_cluster_short {
- Cx0000, Cx0001, Cx0002, Cx0003, Cx0004, Cx0005, Cx0006, Cx0007,
- Cx0008, Cx0009, Cx000A, Cx000B, Cx000C, Cx000D, Cx000E, Cx000F,
- Cx0010, Cx0011, Cx0012, Cx0013, Cx0014, Cx001A, Cx0020, Cx0100,
- Cx0101, Cx0102, Cx0201, Cx0300, Cx0400, Cx0401, Cx0402, Cx0403,
- Cx0404, Cx0405, Cx0406, Cx0500, Cx0702, Cx0B01, Cx0B04, Cx0B05,
- CxEF00, CxFC01, CxFC40, CxFCC0, CxFCCC,
-};
-
-const uint16_t Cx_cluster[] PROGMEM = {
- 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
- 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
- 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x001A, 0x0020, 0x0100,
- 0x0101, 0x0102, 0x0201, 0x0300, 0x0400, 0x0401, 0x0402, 0x0403,
- 0x0404, 0x0405, 0x0406, 0x0500, 0x0702, 0x0B01, 0x0B04, 0x0B05,
- 0xEF00, 0xFC01, 0xFC40, 0xFCC0, 0xFCCC,
-};
-
-uint16_t CxToCluster(uint8_t cx) {
- if (cx < nitems(Cx_cluster)) {
- return pgm_read_word(&Cx_cluster[cx]);
- }
- return 0xFFFF;
-}
-
-uint8_t ClusterToCx(uint16_t cluster) {
- for (uint32_t i=0; iname_offset)) { continue; } // avoid strcasecmp_P() from crashing
- if (0 == strcasecmp_P(command, Z_strings + pgm_read_word(&converter->name_offset))) {
- if (cluster) { *cluster = CxToCluster(pgm_read_byte(&converter->cluster_short)); }
- if (attribute) { *attribute = pgm_read_word(&converter->attribute); }
- if (multiplier) { *multiplier = CmToMultiplier(pgm_read_byte(&converter->multiplier_idx)); }
- if (zigbee_type) { *zigbee_type = pgm_read_byte(&converter->type); }
- uint8_t conv_mapping = pgm_read_byte(&converter->mapping);
- if (data_type) { *data_type = (Z_Data_Type) ((conv_mapping & 0xF0)>>4); }
- if (map_offset) { *map_offset = (conv_mapping & 0x0F); }
- return (const __FlashStringHelper*) (Z_strings + pgm_read_word(&converter->name_offset));
+//
+// Find attribute matcher by name
+//
+class Z_attribute_match Z_findAttributeMatcherByName(uint16_t shortaddr, const char *name) {
+ const Z_Device & device = zigbee_devices.findShortAddr(shortaddr);
+ // works even if the device is unknown
+
+ Z_attribute_match matched_attr = Z_plugin_matchAttributeByName(device.modelId, device.manufacturerId, name);
+ if (!matched_attr.found()) {
+ for (uint32_t i = 0; i < nitems(Z_PostProcess); i++) {
+ const Z_AttributeConverter *converter = &Z_PostProcess[i];
+ if (0 == pgm_read_word(&converter->name_offset)) { continue; } // avoid strcasecmp_P() from crashing
+ if (0 == strcasecmp_P(name, Z_strings + pgm_read_word(&converter->name_offset))) {
+ matched_attr.cluster = CxToCluster(pgm_read_byte(&converter->cluster_short));
+ matched_attr.attribute = pgm_read_word(&converter->attribute);
+ matched_attr.name = (Z_strings + pgm_read_word(&converter->name_offset));
+ matched_attr.multiplier = CmToMultiplier(pgm_read_byte(&converter->multiplier_idx));
+ matched_attr.zigbee_type = pgm_read_byte(&converter->type);
+ uint8_t conv_mapping = pgm_read_byte(&converter->mapping);
+ matched_attr.map_type = (Z_Data_Type) ((conv_mapping & 0xF0)>>4);
+ matched_attr.map_offset = (conv_mapping & 0x0F);
+ break;
+ }
}
}
- return nullptr;
+ return matched_attr;
}
//
-// Find attribute details: Name, Type, Multiplier by cuslter/attr_id
+// Find attribute matcher by name
//
-const __FlashStringHelper* zigbeeFindAttributeById(uint16_t cluster, uint16_t attr_id,
- uint8_t *attr_type, int8_t *multiplier) {
- for (uint32_t i = 0; i < nitems(Z_PostProcess); i++) {
- const Z_AttributeConverter *converter = &Z_PostProcess[i];
- uint16_t conv_cluster = CxToCluster(pgm_read_byte(&converter->cluster_short));
- uint16_t conv_attr_id = pgm_read_word(&converter->attribute);
+// attr_glob: do Attr marked as 0xFFFF match any attribute
+class Z_attribute_match Z_findAttributeMatcherById(uint16_t shortaddr, uint16_t cluster, uint16_t attr_id, bool attr_glob) {
+ const Z_Device & device = zigbee_devices.findShortAddr(shortaddr);
+ // works even if the device is unknown
- if ((conv_cluster == cluster) && (conv_attr_id == attr_id)) {
- if (multiplier) { *multiplier = CmToMultiplier(pgm_read_byte(&converter->multiplier_idx)); }
- if (attr_type) { *attr_type = pgm_read_byte(&converter->type); }
- return (const __FlashStringHelper*) (Z_strings + pgm_read_word(&converter->name_offset));
+ Z_attribute_match matched_attr = Z_plugin_matchAttributeById(device.modelId, device.manufacturerId, cluster, attr_id);
+ if (!matched_attr.found()) {
+ for (uint32_t i = 0; i < nitems(Z_PostProcess); i++) {
+ const Z_AttributeConverter *converter = &Z_PostProcess[i];
+ uint16_t conv_cluster = CxToCluster(pgm_read_byte(&converter->cluster_short));
+ uint16_t conv_attr_id = pgm_read_word(&converter->attribute);
+
+ if ((conv_cluster == cluster) && (conv_attr_id == attr_id || (attr_glob && conv_attr_id == 0xFFFF))) {
+ matched_attr.cluster = cluster;
+ matched_attr.attribute = attr_id;
+ matched_attr.name = (Z_strings + pgm_read_word(&converter->name_offset));
+ matched_attr.multiplier = CmToMultiplier(pgm_read_byte(&converter->multiplier_idx));
+ matched_attr.zigbee_type = pgm_read_byte(&converter->type);
+ uint8_t conv_mapping = pgm_read_byte(&converter->mapping);
+ matched_attr.map_type = (Z_Data_Type) ((conv_mapping & 0xF0)>>4);
+ matched_attr.map_offset = (conv_mapping & 0x0F);
+ break;
+ }
}
}
- return nullptr;
+ return matched_attr;
}
class ZCLFrame {
@@ -801,10 +171,10 @@ public:
void removeInvalidAttributes(Z_attribute_list& attr_list);
void computeSyntheticAttributes(Z_attribute_list& attr_list);
void generateCallBacks(Z_attribute_list& attr_list);
- void parseReadAttributes(Z_attribute_list& attr_list);
+ void parseReadAttributes(uint16_t shortaddr, Z_attribute_list& attr_list);
void parseReadAttributesResponse(Z_attribute_list& attr_list);
- void parseReadConfigAttributes(Z_attribute_list& attr_list);
- void parseConfigAttributes(Z_attribute_list& attr_list);
+ void parseReadConfigAttributes(uint16_t shortaddr, Z_attribute_list& attr_list);
+ void parseConfigAttributes(uint16_t shortaddr, Z_attribute_list& attr_list);
void parseWriteAttributesResponse(Z_attribute_list& attr_list);
void parseResponse(void);
void parseResponse_inner(uint8_t cmd, bool cluster_specific, uint8_t status);
@@ -1316,11 +686,26 @@ void ZCLFrame::removeInvalidAttributes(Z_attribute_list& attr_list) {
//
void ZCLFrame::computeSyntheticAttributes(Z_attribute_list& attr_list) {
const Z_Device & device = zigbee_devices.findShortAddr(shortaddr);
- const char * model_c = zigbee_devices.getModelId(shortaddr); // null if unknown
- String modelId((char*) model_c);
+
+ String modelId((char*) device.modelId);
// scan through attributes and apply specific converters
for (auto &attr : attr_list) {
if (attr.key_is_str) { continue; } // pass if key is a name
+
+ // first apply synonyms from plugins
+ Z_attribute_synonym syn = Z_plugin_matchAttributeSynonym(device.modelId, device.manufacturerId,
+ attr.key.id.cluster, attr.key.id.attr_id);
+ if (syn.found()) {
+ attr.setKeyId(syn.new_cluster, syn.new_attribute);
+ if (syn.multiplier != 1 && syn.multiplier != 0) {
+ // we need to change the value
+ float fval = attr.getFloat();
+ if (syn.multiplier > 0) { fval = fval * syn.multiplier; }
+ else { fval = fval / (-syn.multiplier); }
+ attr.setFloat(fval);
+ }
+ }
+
uint32_t ccccaaaa = (attr.key.id.cluster << 16) | attr.key.id.attr_id;
switch (ccccaaaa) { // 0xccccaaaa . c=cluster, a=attribute
@@ -1478,7 +863,7 @@ void sendHueUpdate(uint16_t shortaddr, uint16_t groupaddr, uint16_t cluster, uin
}
// ZCL_READ_ATTRIBUTES
-void ZCLFrame::parseReadAttributes(Z_attribute_list& attr_list) {
+void ZCLFrame::parseReadAttributes(uint16_t shortaddr, Z_attribute_list& attr_list) {
uint32_t i = 0;
uint32_t len = payload.len();
@@ -1494,15 +879,9 @@ void ZCLFrame::parseReadAttributes(Z_attribute_list& attr_list) {
read_attr_ids[i/2] = attrid;
// find the attribute name
- for (uint32_t i = 0; i < nitems(Z_PostProcess); i++) {
- const Z_AttributeConverter *converter = &Z_PostProcess[i];
- uint16_t conv_cluster = CxToCluster(pgm_read_byte(&converter->cluster_short));
- uint16_t conv_attribute = pgm_read_word(&converter->attribute);
-
- if ((conv_cluster == cluster) && (conv_attribute == attrid)) {
- attr_names.addAttribute(Z_strings + pgm_read_word(&converter->name_offset), true).setBool(true);
- break;
- }
+ Z_attribute_match matched_attr = Z_findAttributeMatcherById(shortaddr, cluster, attrid, false);
+ if (matched_attr.found()) {
+ attr_names.addAttribute(matched_attr.name, true).setBool(true);
}
i += 2;
}
@@ -1516,7 +895,7 @@ void ZCLFrame::parseReadAttributes(Z_attribute_list& attr_list) {
}
// ZCL_CONFIGURE_REPORTING_RESPONSE
-void ZCLFrame::parseConfigAttributes(Z_attribute_list& attr_list) {
+void ZCLFrame::parseConfigAttributes(uint16_t shortaddr, Z_attribute_list& attr_list) {
uint32_t len = payload.len();
Z_attribute_list attr_config_list;
@@ -1528,8 +907,9 @@ void ZCLFrame::parseConfigAttributes(Z_attribute_list& attr_list) {
attr_config_response.addAttributePMEM(PSTR("Status")).setUInt(status);
attr_config_response.addAttributePMEM(PSTR("StatusMsg")).setStr(getZigbeeStatusMessage(status).c_str());
- const __FlashStringHelper* attr_name = zigbeeFindAttributeById(cluster, attr_id, nullptr, nullptr);
- if (attr_name) {
+ Z_attribute_match attr_matched = Z_findAttributeMatcherById(shortaddr, cluster, attr_id, false);
+ if (attr_matched.found()) {
+ const __FlashStringHelper* attr_name = (const __FlashStringHelper*) attr_matched.name;
attr_config_list.addAttribute(attr_name).setStrRaw(attr_config_response.toString(true).c_str());
} else {
attr_config_list.addAttribute(cluster, attr_id).setStrRaw(attr_config_response.toString(true).c_str());
@@ -1546,7 +926,7 @@ void ZCLFrame::parseWriteAttributesResponse(Z_attribute_list& attr_list) {
}
// ZCL_READ_REPORTING_CONFIGURATION_RESPONSE
-void ZCLFrame::parseReadConfigAttributes(Z_attribute_list& attr_list) {
+void ZCLFrame::parseReadConfigAttributes(uint16_t shortaddr, Z_attribute_list& attr_list) {
uint32_t i = 0;
uint32_t len = payload.len();
@@ -1563,19 +943,12 @@ void ZCLFrame::parseReadConfigAttributes(Z_attribute_list& attr_list) {
attr_2.addAttributePMEM(PSTR("DirectionReceived")).setBool(true);
}
- // find the attribute name
+ // find the multiplier
int8_t multiplier = 1;
- for (uint32_t i = 0; i < nitems(Z_PostProcess); i++) {
- const Z_AttributeConverter *converter = &Z_PostProcess[i];
- uint16_t conv_cluster = CxToCluster(pgm_read_byte(&converter->cluster_short));
- uint16_t conv_attribute = pgm_read_word(&converter->attribute);
-
- if ((conv_cluster == cluster) && (conv_attribute == attrid)) {
- const char * attr_name = Z_strings + pgm_read_word(&converter->name_offset);
- attr_2.addAttribute(attr_name, true).setBool(true);
- multiplier = CmToMultiplier(pgm_read_byte(&converter->multiplier_idx));
- break;
- }
+ Z_attribute_match matched_attr = Z_findAttributeMatcherById(shortaddr, cluster, attrid, false);
+ if (matched_attr.found()) {
+ attr_2.addAttribute(matched_attr.name, true).setBool(true);
+ multiplier = matched_attr.multiplier;
}
i += 4;
if (0 != status) {
@@ -2034,31 +1407,13 @@ void Z_postProcessAttributes(uint16_t shortaddr, uint16_t src_ep, class Z_attrib
// Look for an entry in the converter table
bool found = false;
- const char * conv_name;
- Z_Data_Type map_type = Z_Data_Type::Z_Unknown;
- uint8_t map_offset = 0;
- uint8_t zigbee_type = Znodata;
- int8_t conv_multiplier;
- for (uint32_t i = 0; i < nitems(Z_PostProcess); i++) {
- const Z_AttributeConverter *converter = &Z_PostProcess[i];
- uint16_t conv_cluster = CxToCluster(pgm_read_byte(&converter->cluster_short));
- uint16_t conv_attribute = pgm_read_word(&converter->attribute);
- if ((conv_cluster == cluster) &&
- ((conv_attribute == attribute) || (conv_attribute == 0xFFFF)) ) {
- conv_multiplier = CmToMultiplier(pgm_read_byte(&converter->multiplier_idx));
- zigbee_type = pgm_read_byte(&converter->type);
- uint8_t mapping = pgm_read_byte(&converter->mapping);
- map_type = (Z_Data_Type) ((mapping & 0xF0)>>4);
- map_offset = (mapping & 0x0F);
- conv_name = Z_strings + pgm_read_word(&converter->name_offset);
- found = true;
- break;
- }
- }
+ // first search in device plug-ins
+ const Z_attribute_match matched_attr = Z_findAttributeMatcherById(shortaddr, cluster, attribute, true);
+ found = matched_attr.found();
float fval = attr.getFloat();
- if (found && (map_type != Z_Data_Type::Z_Unknown)) {
+ if (found && (matched_attr.map_type != Z_Data_Type::Z_Unknown)) {
// We apply an automatic mapping to Z_Data_XXX object
// First we find or instantiate the correct Z_Data_XXX accorfing to the endpoint
// Then store the attribute at the attribute addres (via offset) and according to size 8/16/32 bits
@@ -2066,11 +1421,11 @@ void Z_postProcessAttributes(uint16_t shortaddr, uint16_t src_ep, class Z_attrib
// add the endpoint if it was not already known
device.addEndpoint(src_ep);
// we don't apply the multiplier, but instead store in Z_Data_XXX object
- Z_Data & data = device.data.getByType(map_type, src_ep);
- uint8_t *attr_address = ((uint8_t*)&data) + sizeof(Z_Data) + map_offset;
+ Z_Data & data = device.data.getByType(matched_attr.map_type, src_ep);
+ uint8_t *attr_address = ((uint8_t*)&data) + sizeof(Z_Data) + matched_attr.map_offset;
uint32_t uval32 = attr.getUInt(); // call converter to uint only once
int32_t ival32 = attr.getInt(); // call converter to int only once
- // AddLog(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_ZIGBEE "Mapping type=%d offset=%d zigbee_type=%02X value=%d\n"), (uint8_t) map_type, map_offset, zigbee_type, ival32);
+ // AddLog(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_ZIGBEE "Mapping type=%d offset=%d zigbee_type=%02X value=%d\n"), (uint8_t) matched_attr.matched_attr, matched_attr.map_offset, matched_attr.zigbee_type, ival32);
switch (ccccaaaa) {
case 0xEF000202:
case 0xEF000203: // need to convert Tuya temperatures from 1/10 to 1/00 °C
@@ -2078,7 +1433,7 @@ void Z_postProcessAttributes(uint16_t shortaddr, uint16_t src_ep, class Z_attrib
break;
}
- switch (zigbee_type) {
+ switch (matched_attr.zigbee_type) {
case Zenum8:
case Zmap8:
case Zbool:
@@ -2124,18 +1479,18 @@ void Z_postProcessAttributes(uint16_t shortaddr, uint16_t src_ep, class Z_attrib
// now apply the multiplier to make it human readable
if (found) {
- if (0 == conv_multiplier) { attr_list.removeAttribute(&attr); continue; } // remove attribute if multiplier is zero
- if (1 != conv_multiplier) {
- if (conv_multiplier > 0) { fval = fval * conv_multiplier; }
- else { fval = fval / (-conv_multiplier); }
+ if (0 == matched_attr.multiplier) { attr_list.removeAttribute(&attr); continue; } // remove attribute if multiplier is zero
+ if (1 != matched_attr.multiplier) {
+ if (matched_attr.multiplier > 0) { fval = fval * matched_attr.multiplier; }
+ else { fval = fval / (-matched_attr.multiplier); }
attr.setFloat(fval);
}
}
// Replace cluster/attribute with name
if (found) {
- if (0x00 != pgm_read_byte(conv_name)) {// if name is not null, replace it
- attr.setKeyName(conv_name, true); // PMEM string so no need to copy
+ if (0x00 != pgm_read_byte(matched_attr.name)) {// if name is not null, replace it
+ attr.setKeyName(matched_attr.name, true); // PMEM string so no need to copy
}
}
}
@@ -2143,35 +1498,25 @@ 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 < nitems(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(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(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;
- }
+void Z_parseAttributeKey_inner(uint16_t shortaddr, class Z_attribute & attr, uint16_t preferred_cluster) {
+ if (attr.key_is_str) {
+ // find the attribute name
+ Z_attribute_match matched_attr = Z_findAttributeMatcherByName(shortaddr, attr.key.key);
+ if (matched_attr.found()) {
+ if ((preferred_cluster == 0xFFFF) || // any cluster
+ (matched_attr.cluster == preferred_cluster)) {
+ // match
+ attr.setKeyId(matched_attr.cluster, matched_attr.attribute);
+ attr.attr_type = matched_attr.zigbee_type;
+ attr.attr_multiplier = matched_attr.multiplier;
}
}
+ } else {
+ // find by cluster/attribute
+ 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;
+ }
}
}
@@ -2191,7 +1536,7 @@ void Z_parseAttributeKey_inner(class Z_attribute & attr, uint16_t preferred_clus
// 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, uint16_t preferred_cluster) {
+bool Z_parseAttributeKey(uint16_t shortaddr, 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) {
@@ -2208,7 +1553,7 @@ bool Z_parseAttributeKey(class Z_attribute & attr, uint16_t preferred_cluster) {
attr_id = strtoul(delimiter+1, nullptr, 16);
} else {
attr_id = strtoul(delimiter+1, &delimiter2, 16);
- type_id = strtoul(delimiter2+1, nullptr, 16);
+ type_id = Z_getTypeByName(delimiter2+1);
}
attr.setKeyId(cluster_id, attr_id);
attr.attr_type = type_id;
@@ -2218,12 +1563,19 @@ bool Z_parseAttributeKey(class Z_attribute & attr, uint16_t preferred_cluster) {
// 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
+ Z_parseAttributeKey_inner(shortaddr, attr, preferred_cluster); // try to find with the selected cluster
}
if (Zunk == attr.attr_type) {
- Z_parseAttributeKey_inner(attr, 0xFFFF); // try again with any cluster
+ Z_parseAttributeKey_inner(shortaddr, attr, 0xFFFF); // try again with any cluster
}
- return (Zunk != attr.attr_type) ? true : false;
+ // special case for Tuya attributes, where Zunk is allowed
+ if (Zunk == attr.attr_type) {
+ if (!attr.key_is_str && attr.key.id.cluster == 0xEF00) {
+ return true;
+ }
+ return false; // couldn't find any match
+ }
+ return true;
}
// generic toAttributes() based on declaration in the attribute array
diff --git a/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_6_commands.ino b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_6_commands.ino
index f951e09e9..6990c60db 100644
--- a/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_6_commands.ino
+++ b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_6_commands.ino
@@ -212,7 +212,6 @@ void Z_Unreachable(uint16_t shortaddr, uint16_t groupaddr, uint16_t cluster, uin
zigbee_devices.getShortAddr(shortaddr).setReachable(false); // mark device as reachable
Z_attribute_list attr_list;
attr_list.addAttributePMEM(PSTR("Reachable")).setBool(false); // "Reachable":false
- // Z_postProcessAttributes(shortaddr, endpoint, attr_list); // make sure all is updated accordingly
zigbee_devices.jsonPublishNow(shortaddr, attr_list);
}
}
diff --git a/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_7_0_statemachine.ino b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_7_0_statemachine.ino
index 13f301b29..eb7e2074a 100644
--- a/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_7_0_statemachine.ino
+++ b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_7_0_statemachine.ino
@@ -529,6 +529,7 @@ static const Zigbee_Instruction zb_prog[] PROGMEM = {
ZI_CALL(&Z_Load_Devices, 0)
ZI_CALL(&Z_Load_Data, 0)
ZI_CALL(&Z_Set_Save_Data_Timer, 0)
+ ZI_CALL(&Z_ZbAutoload, 0)
ZI_CALL(&Z_Query_Bulbs, 0)
ZI_LABEL(ZIGBEE_LABEL_MAIN_LOOP)
@@ -966,6 +967,7 @@ static const Zigbee_Instruction zb_prog[] PROGMEM = {
ZI_CALL(&Z_Load_Devices, 0)
ZI_CALL(&Z_Load_Data, 0)
ZI_CALL(&Z_Set_Save_Data_Timer, 0)
+ ZI_CALL(&Z_ZbAutoload, 0)
ZI_CALL(&Z_Query_Bulbs, 0)
ZI_LABEL(ZIGBEE_LABEL_MAIN_LOOP)
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
new file mode 100644
index 000000000..7db2c99fb
--- /dev/null
+++ b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_7_6_plugin.ino
@@ -0,0 +1,379 @@
+/*
+ xdrv_23_zigbee.ino - zigbee support for Tasmota
+
+ Copyright (C) 2021 Theo Arends and Stephan Hadinger
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+*/
+
+#ifdef USE_ZIGBEE
+
+// global singleton
+Z_plugin_templates g_plugin_templates;
+
+class Z_attribute_synonym Z_plugin_matchAttributeSynonym(const char *model, const char *manufacturer, uint16_t cluster, uint16_t attribute) {
+ const Z_attribute_synonym * attr_syn;
+ attr_syn = g_plugin_templates.matchAttributeSynonym(model == nullptr ? "" : model,
+ manufacturer == nullptr ? "" : manufacturer,
+ cluster, attribute);
+
+ Z_attribute_synonym syn;
+ if (attr_syn != nullptr) {
+ syn = *attr_syn;
+ }
+ return syn;
+}
+
+Z_attribute_match Z_plugin_matchAttributeById(const char *model, const char *manufacturer, uint16_t cluster, uint16_t attribute) {
+ const Z_plugin_attribute * attr_tmpl;
+ attr_tmpl = g_plugin_templates.matchAttributeById(model == nullptr ? "" : model,
+ manufacturer == nullptr ? "" : manufacturer,
+ cluster, attribute);
+
+ Z_attribute_match attr;
+ if (attr_tmpl != nullptr) {
+ attr.cluster = attr_tmpl->cluster;
+ attr.attribute = attr_tmpl->attribute;
+ attr.name = attr_tmpl->name.c_str();
+ attr.zigbee_type = attr_tmpl->type;
+ attr.multiplier = attr_tmpl->multiplier;
+ }
+ return attr;
+}
+
+Z_attribute_match Z_plugin_matchAttributeByName(const char *model, const char *manufacturer, const char * name) {
+ const Z_plugin_attribute * attr_tmpl;
+ Z_attribute_match attr;
+
+ if (name != nullptr) {
+ attr_tmpl = g_plugin_templates.matchAttributeByName(model == nullptr ? "" : model,
+ manufacturer == nullptr ? "" : manufacturer,
+ name);
+
+ if (attr_tmpl != nullptr) {
+ attr.cluster = attr_tmpl->cluster;
+ attr.attribute = attr_tmpl->attribute;
+ attr.name = attr_tmpl->name.c_str();
+ attr.zigbee_type = attr_tmpl->type;
+ attr.multiplier = attr_tmpl->multiplier;
+ }
+ }
+ return attr;
+}
+
+bool Zb_readline(class File *f, char* buf, size_t size) {
+ bool eof = 0;
+ while (1) {
+ // read line
+ bool comment = false; // did we encounter '#', if so ignore anything until '\n'
+ char * p = buf;
+ while (1) {
+ int c = f->read();
+ if (c == -1) { eof = true; break; } // EOF reached
+ if (c == '#') { comment = true; } // rest of line is ignored
+ if (c == '\n') { break; } // end of line
+ if (!comment) {
+ if (p < buf + size - 1) {
+ *p++ = c; // append character
+ } else {
+ AddLog(LOG_LEVEL_INFO, "ZIG: ZbLoad line exceeds 96 bytes, aborting");
+ return false;
+ }
+ }
+ }
+ int32_t ret = p - buf; // len in bytes
+ if (eof && ret == 0) { return false; } // nothing more to read
+ // found something, don't add the \n since we don't need it
+ buf[ret] = 0; // add string terminator
+ RemoveSpace(buf); // remove anything that looks like a space, tab, crlf...
+ // AddLog(LOG_LEVEL_INFO, "ZIG: ZbRead>'%s'", buf);
+ return true;
+ }
+}
+
+#ifdef USE_UFILESYS
+extern FS *ffsp;
+#endif
+
+// load a file from filesystem
+// returns `true` if success
+bool ZbLoad(const char *filename_raw) {
+
+#ifdef USE_UFILESYS
+ if (ffsp) {
+ // first unload previsou definitions
+ ZbUnload(filename_raw);
+
+ String filename = filename_raw;
+ if (filename_raw[0] != '/') {
+ filename = "/";
+ filename += filename_raw;
+ }
+ File fp;
+ fp = ffsp->open(filename.c_str(), "r");
+
+ if (fp <= 0) {
+ AddLog(LOG_LEVEL_INFO, "ZIG: unable to load file '%s'", filename.c_str());
+ return false;
+ }
+
+ char * filename_imported = nullptr;
+ Z_plugin_template * tmpl = nullptr; // current template with matchers and attributes
+ bool new_matchers = false; // indicates that we have finished the current matchers
+ char buf_line[96]; // max line is 96 bytes (comments don't count)
+
+ // read the first 6 chars
+ bool invalid_header = false;
+ static const char Z2T_HEADER_V1[] PROGMEM = "#Z2Tv1";
+ for (uint32_t i = 0; i < 6; i++) {
+ int c = fp.read();
+ if (c < 0) {
+ invalid_header = true;
+ break;
+ }
+ buf_line[i] = c;
+ buf_line[i+1] = 0;
+ }
+ if (!invalid_header) {
+ if (strcmp_P(buf_line, Z2T_HEADER_V1) != 0) {
+ invalid_header = true;
+ }
+ }
+
+ if (invalid_header) {
+ AddLog(LOG_LEVEL_INFO, "ZIG: ZbLoad '%s' invalid header", filename_raw);
+ return false;
+ }
+
+ // parse line by line
+ while (1) {
+ if (!Zb_readline(&fp, buf_line, sizeof(buf_line))) {
+ // EOF
+ break;
+ }
+
+ // at first valid line, we instanciate a new plug-in instance and assign a filemane
+ if (filename_imported == nullptr) {
+ // allocate only once the filename for multiple entries
+ // freed only by `ZbUnload`
+ filename_imported = (char*) malloc(strlen(filename.c_str())+1);
+ strcpy(filename_imported, filename.c_str());
+ }
+
+ // there is a non-empty line, containing no space/tab/crlf
+ // depending on the first char, parse either device name or cluster/attribute+name
+ if (buf_line[0] == ':') {
+ if (tmpl == nullptr || new_matchers) {
+ tmpl = &g_plugin_templates.addToLast();
+ tmpl->filename = filename_imported;
+ new_matchers = false;
+ }
+ // parse device name
+ char *rest = buf_line + 1; // skip first char ':'
+ char *token = strtok_r(rest, ",", &rest);
+ Z_plugin_matcher & matcher = tmpl->matchers.addToLast();
+ if (token != nullptr) {
+ matcher.model = token;
+ }
+ token = strtok_r(rest, ",", &rest);
+ if (token != nullptr) {
+ matcher.manufacturer = token;
+ }
+ } else {
+ if (tmpl == nullptr) {
+ continue;
+ }
+ new_matchers = true;
+ char *rest = buf_line;
+ char *token = strtok_r(rest, ",", &rest);
+ if (token == nullptr) {
+ continue;
+ }
+
+ // detect if token contains '=', if yes it is a synonym
+ char * delimiter_equal = strchr(token, '=');
+
+ if (delimiter_equal == nullptr) {
+ // token is of from '0000/0000' or '0000/0000%00'
+ char * delimiter_slash = strchr(token, '/');
+ char * delimiter_percent = strchr(token, '%');
+ if (delimiter_slash == nullptr || (delimiter_percent != nullptr && delimiter_slash > delimiter_percent)) {
+ AddLog(LOG_LEVEL_INFO, "ZIG: ZbLoad '%s' wrong delimiter '%s'", filename_raw, token);
+ }
+
+ uint16_t attr_id = 0xFFFF;
+ uint16_t cluster_id = 0xFFFF;
+ uint8_t type_id = Zunk;
+
+ cluster_id = strtoul(token, &delimiter_slash, 16);
+ if (!delimiter_percent) {
+ attr_id = strtoul(delimiter_slash+1, nullptr, 16);
+ } else {
+ attr_id = strtoul(delimiter_slash+1, &delimiter_percent, 16);
+ type_id = Z_getTypeByName(delimiter_percent+1);
+ }
+ // 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;
+ }
+ // 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;
+ } else {
+ // token is of from '0000/0000=0000/0000,1'
+ char * rest2 = token;
+ char * tok2 = strtok_r(rest2, "=", &rest2);
+ char * delimiter_slash = strchr(tok2, '/');
+ uint16_t cluster_id = strtoul(tok2, &delimiter_slash, 16);
+ uint16_t attr_id = strtoul(delimiter_slash+1, nullptr, 16);
+ tok2 = strtok_r(rest2, "=", &rest2);
+ 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);
+ }
+ // create the synonym
+ Z_attribute_synonym & syn = tmpl->synonyms.addToLast();
+ syn.cluster = cluster_id;
+ syn.attribute = attr_id;
+ syn.new_cluster = new_cluster_id;
+ syn.new_attribute = new_attr_id;
+ syn.multiplier = multiplier;
+ }
+ }
+ }
+ } else {
+ AddLog(LOG_LEVEL_ERROR, "ZIG: filesystem not enabled");
+ }
+#else
+ AddLog(LOG_LEVEL_INFO, "ZIG: ZbLoad requires file system");
+#endif
+ return true;
+}
+
+// Unlaod previously loaded definitions
+bool ZbUnload(const char *filename_raw) {
+ String filename = filename_raw;
+ if (filename_raw[0] != '/') {
+ filename = "/";
+ filename += filename_raw;
+ }
+
+ char * filename_registered = nullptr; // internal allocation for filename
+ for (const Z_plugin_template & tmpl : g_plugin_templates) {
+ bool to_be_freed = false;
+ if (filename_registered) {
+ // if filename_registered is not NULL, compare pointers
+ if (tmpl.filename == filename_registered) { to_be_freed = true; }
+ } else {
+ if (strcmp(tmpl.filename, filename.c_str()) == 0) {
+ filename_registered = tmpl.filename;
+ to_be_freed = true;
+ }
+ }
+ // check if we remove this node
+ if (to_be_freed) {
+ g_plugin_templates.remove(&tmpl);
+ }
+ }
+ // free memory for filename
+ if (filename_registered) {
+ free(filename_registered);
+ AddLog(LOG_LEVEL_INFO, "ZIG: ZbUnload '%s' sucessful", filename_raw);
+ return true;
+ }
+ return false;
+}
+
+// Dump the ZbLoad structure in a format compatible with ZbLoad
+void ZbLoadDump(void) {
+ 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);
+ }
+ // marchers
+ if (tmpl.matchers.length() == 0) {
+ AddLog(LOG_LEVEL_INFO, ": # no matcher");
+ } else {
+ for (const Z_plugin_matcher & matcher : tmpl.matchers) {
+ AddLog(LOG_LEVEL_INFO, ":%s,%s", matcher.model.c_str(), matcher.manufacturer.c_str());
+ }
+ }
+ // attributes
+ if (tmpl.attributes.length() == 0 && tmpl.synonyms.length() == 0) {
+ // no content, output an empty line
+ 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());
+ }
+ }
+ 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);
+ }
+ }
+ }
+
+ AddLog(LOG_LEVEL_INFO, "<==== END");
+}
+
+// Auto-load all files ending with '.zb'
+void ZbAutoload(void) {
+#ifdef USE_UFILESYS
+ if (ffsp) {
+ File dir = ffsp->open("/", "r");
+ if (dir) {
+ dir.rewindDirectory();
+ while (1) {
+ File entry = dir.openNextFile();
+ if (!entry) { break; }
+ const char * fn = entry.name();
+ if (strcmp(fn, ".") && strcmp(fn, "..")) {
+ // check suffix
+ size_t l = strlen(fn);
+ if (l > 3) {
+ if (fn[l-3] == '.' && fn[l-2] == 'z' && fn[l-1] == 'b') {
+ bool ret = ZbLoad(fn);
+ if (ret) {
+ AddLog(LOG_LEVEL_INFO, "ZIG: ZbLoad '%s' loaded successfully", fn);
+ }
+ }
+ }
+ }
+
+ }
+
+ }
+ }
+#endif // USE_UFILESYS
+}
+
+#endif // USE_ZIGBEE
diff --git a/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_8_parsers.ino b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_8_parsers.ino
index 14b64109a..3cc1eacb7 100644
--- a/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_8_parsers.ino
+++ b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_8_parsers.ino
@@ -1553,25 +1553,24 @@ void Z_AutoConfigReportingForCluster(uint16_t shortaddr, uint16_t groupaddr, uin
uint16_t max_interval = pgm_read_word(&(Z_autoAttributeReporting[i].max_interval));
float report_change_raw = Z_autoAttributeReporting[i].report_change;
double report_change = report_change_raw;
- uint8_t attr_type;
- int8_t multiplier;
+ // uint8_t attr_type;
+ // int8_t multiplier;
- const __FlashStringHelper* attr_name = zigbeeFindAttributeById(cluster, attr_id, &attr_type, &multiplier);
-
- if (attr_name) {
+ Z_attribute_match attr_matched = Z_findAttributeMatcherById(shortaddr, cluster, attr_id, false);
+ if (attr_matched.found()) {
if (comma) { ResponseAppend_P(PSTR(",")); }
comma = true;
- ResponseAppend_P(PSTR("\"%s\":{\"MinInterval\":%d,\"MaxInterval\":%d"), attr_name, min_interval, max_interval);
+ ResponseAppend_P(PSTR("\"%s\":{\"MinInterval\":%d,\"MaxInterval\":%d"), attr_matched.name, min_interval, max_interval);
buf.add8(0); // direction, always 0
buf.add16(attr_id);
- buf.add8(attr_type);
+ buf.add8(attr_matched.zigbee_type);
buf.add16(min_interval);
buf.add16(max_interval);
- if (!Z_isDiscreteDataType(attr_type)) { // report_change is only valid for non-discrete data types (numbers)
- ZbApplyMultiplier(report_change, multiplier);
+ if (!Z_isDiscreteDataType(attr_matched.zigbee_type)) { // report_change is only valid for non-discrete data types (numbers)
+ ZbApplyMultiplier(report_change, attr_matched.multiplier);
// encode value
- int32_t res = encodeSingleAttribute(buf, report_change, "", attr_type);
+ int32_t res = encodeSingleAttribute(buf, report_change, "", attr_matched.zigbee_type);
if (res < 0) {
AddLog(LOG_LEVEL_ERROR, PSTR(D_LOG_ZIGBEE "internal error, unsupported attribute type"));
} else {
@@ -1690,12 +1689,12 @@ void Z_IncomingMessage(class ZCLFrame &zcl_received) {
zcl_received.parseReadAttributesResponse(attr_list);
if (clusterid) { defer_attributes = true; } // don't defer system Cluster=0 messages
} else if ( (!zcl_received.isClusterSpecificCommand()) && (ZCL_READ_ATTRIBUTES == zcl_received.getCmdId())) {
- zcl_received.parseReadAttributes(attr_list);
+ zcl_received.parseReadAttributes(srcaddr, attr_list);
// never defer read_attributes, so the auto-responder can send response back on a per cluster basis
} else if ( (!zcl_received.isClusterSpecificCommand()) && (ZCL_READ_REPORTING_CONFIGURATION_RESPONSE == zcl_received.getCmdId())) {
- zcl_received.parseReadConfigAttributes(attr_list);
+ zcl_received.parseReadConfigAttributes(srcaddr, attr_list);
} else if ( (!zcl_received.isClusterSpecificCommand()) && (ZCL_CONFIGURE_REPORTING_RESPONSE == zcl_received.getCmdId())) {
- zcl_received.parseConfigAttributes(attr_list);
+ zcl_received.parseConfigAttributes(srcaddr, attr_list);
} else if ( (!zcl_received.isClusterSpecificCommand()) && (ZCL_WRITE_ATTRIBUTES_RESPONSE == zcl_received.getCmdId())) {
zcl_received.parseWriteAttributesResponse(attr_list);
} else if (zcl_received.isClusterSpecificCommand()) {
@@ -2123,6 +2122,15 @@ int32_t Z_Query_Bulbs(uint8_t value) {
return 0; // continue
}
+//
+// Z_ZbAutoload - autoload all definitions from filesystem
+// files with ending '.zb' suffix
+//
+int32_t Z_ZbAutoload(uint8_t value) {
+ ZbAutoload();
+ return 0;
+}
+
//
// Zigbee initialization is complete, let the party begin
//
@@ -2193,7 +2201,7 @@ void ZCLFrame::autoResponder(const uint16_t *attr_list_ids, size_t attr_len) {
break;
}
if (!attr.isNone()) {
- Z_parseAttributeKey(attr, cluster);
+ Z_parseAttributeKey(shortaddr, attr, cluster);
attr_list.addAttribute(cluster, attr_id) = attr;
}
}
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 c7182d609..a5a3735be 100644
--- a/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_A_impl.ino
+++ b/tasmota/tasmota_xdrv_driver/xdrv_23_zigbee_A_impl.ino
@@ -39,7 +39,8 @@ const char kZbCommands[] PROGMEM = D_PRFX_ZB "|" // prefix
D_CMND_ZIGBEE_BIND "|" D_CMND_ZIGBEE_UNBIND "|" D_CMND_ZIGBEE_PING "|" D_CMND_ZIGBEE_MODELID "|"
D_CMND_ZIGBEE_LIGHT "|" D_CMND_ZIGBEE_OCCUPANCY "|"
D_CMND_ZIGBEE_RESTORE "|" D_CMND_ZIGBEE_BIND_STATE "|" D_CMND_ZIGBEE_MAP "|" D_CMND_ZIGBEE_LEAVE "|"
- D_CMND_ZIGBEE_CONFIG "|" D_CMND_ZIGBEE_DATA "|" D_CMND_ZIGBEE_SCAN "|" D_CMND_ZIGBEE_ENROLL "|" D_CMND_ZIGBEE_CIE
+ D_CMND_ZIGBEE_CONFIG "|" D_CMND_ZIGBEE_DATA "|" D_CMND_ZIGBEE_SCAN "|" D_CMND_ZIGBEE_ENROLL "|" D_CMND_ZIGBEE_CIE "|"
+ D_CMND_ZIGBEE_LOAD "|" D_CMND_ZIGBEE_UNLOAD "|" D_CMND_ZIGBEE_LOADDUMP
;
SO_SYNONYMS(kZbSynonyms,
@@ -62,6 +63,7 @@ void (* const ZigbeeCommand[])(void) PROGMEM = {
&CmndZbRestore, &CmndZbBindState, &CmndZbMap, &CmndZbLeave,
&CmndZbConfig, &CmndZbData, &CmndZbScan,
&CmndZbenroll, &CmndZbcie,
+ &CmndZbLoad, &CmndZbUnload, &CmndZbLoadDump,
};
/********************************************************************************************/
@@ -339,7 +341,7 @@ void ZbSendReportWrite(class JsonParserToken val_pubwrite, class ZCLFrame & zcl)
Z_attribute attr;
attr.setKeyName(key.getStr());
- if (Z_parseAttributeKey(attr, tuya_protocol ? 0xEF00 : 0xFFFF)) { // favor tuya protocol if needed
+ if (Z_parseAttributeKey(zcl.shortaddr, attr, tuya_protocol ? 0xEF00 : 0xFFFF)) { // favor tuya protocol if needed
// Buffer ready, do some sanity checks
// all attributes must use the same cluster
@@ -352,11 +354,11 @@ void ZbSendReportWrite(class JsonParserToken val_pubwrite, class ZCLFrame & zcl)
} else {
if (attr.key_is_str) {
- Response_P(PSTR("{\"%s\":\"%s'%s'\"}"), XdrvMailbox.command, PSTR(D_ZIGBEE_UNKNOWN_ATTRIBUTE " "), key);
+ Response_P(PSTR("{\"%s\":\"%s'%s'\"}"), XdrvMailbox.command, PSTR(D_ZIGBEE_UNKNOWN_ATTRIBUTE " "), key.getStr());
return;
}
if (Zunk == attr.attr_type) {
- Response_P(PSTR("{\"%s\":\"%s'%s'\"}"), XdrvMailbox.command, PSTR(D_ZIGBEE_UNSUPPORTED_ATTRIBUTE_TYPE " "), key);
+ Response_P(PSTR("{\"%s\":\"%s'%s'\"}"), XdrvMailbox.command, PSTR(D_ZIGBEE_UNSUPPORTED_ATTRIBUTE_TYPE " "), key.getStr());
return;
}
}
@@ -623,34 +625,26 @@ void ZbSendRead(JsonParserToken val_attr, ZCLFrame & zcl) {
bool found = false;
// scan attributes to find by name, and retrieve type
- for (uint32_t i = 0; i < nitems(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);
-
- if ((pgm_read_word(&converter->name_offset)) && (0 == strcasecmp_P(key.getStr(), Z_strings + pgm_read_word(&converter->name_offset)))) {
- // match name
- // check if there is a conflict with cluster
- // TODO
- if (!(value.getBool()) && attr_item_offset) {
- // If value is false (non-default) then set direction to 1 (for ReadConfig)
- attrs[actual_attr_len] = 0x01;
- }
- actual_attr_len += attr_item_offset;
- attrs[actual_attr_len++] = local_attr_id & 0xFF;
- attrs[actual_attr_len++] = local_attr_id >> 8;
- actual_attr_len += attr_item_len - 2 - attr_item_offset; // normally 0
- found = true;
- // check cluster
- if (!zcl.validCluster()) {
- zcl.cluster = local_cluster_id;
- } else if (zcl.cluster != local_cluster_id) {
- ResponseCmndChar_P(PSTR(D_ZIGBEE_TOO_MANY_CLUSTERS));
- if (attrs) { free(attrs); }
- return;
- }
- break; // found, exit loop
+ Z_attribute_match matched_attr = Z_findAttributeMatcherByName(zcl.shortaddr, key.getStr());
+ if (matched_attr.found()) {
+ // match name
+ // check if there is a conflict with cluster
+ if (!(value.getBool()) && attr_item_offset) {
+ // If value is false (non-default) then set direction to 1 (for ReadConfig)
+ attrs[actual_attr_len] = 0x01;
+ }
+ actual_attr_len += attr_item_offset;
+ attrs[actual_attr_len++] = matched_attr.attribute & 0xFF;
+ attrs[actual_attr_len++] = matched_attr.attribute >> 8;
+ actual_attr_len += attr_item_len - 2 - attr_item_offset; // normally 0
+ found = true;
+ // check cluster
+ if (!zcl.validCluster()) {
+ zcl.cluster = matched_attr.cluster;
+ } else if (zcl.cluster != matched_attr.cluster) {
+ ResponseCmndChar_P(PSTR(D_ZIGBEE_TOO_MANY_CLUSTERS));
+ if (attrs) { free(attrs); }
+ return;
}
}
if (!found) {
@@ -863,7 +857,10 @@ void ZbBindUnbind(bool unbind) { // false = bind, true = unbind
if (val_cluster) {
cluster = val_cluster.getUInt(cluster); // first convert as number
if (0 == cluster) {
- zigbeeFindAttributeByName(val_cluster.getStr(), &cluster, nullptr, nullptr);
+ Z_attribute_match attr_matched = Z_findAttributeMatcherByName(BAD_SHORTADDR, val_cluster.getStr());
+ if (attr_matched.found()) {
+ cluster = attr_matched.cluster;
+ }
}
}
@@ -1306,6 +1303,53 @@ void CmndZbSave(void) {
ResponseCmndDone();
}
+//
+// Command `ZbLoad`
+// Load a device specific zigbee template
+//
+void CmndZbLoad(void) {
+ // can be called before Zigbee is initialized
+ RemoveSpace(XdrvMailbox.data);
+
+ bool ret = true;;
+ if (strcmp(XdrvMailbox.data, "*") == 0) {
+ ZbAutoload();
+ } else {
+ ret = ZbLoad(XdrvMailbox.data);
+ }
+ if (ret) {
+ ResponseCmndDone();
+ } else {
+ ResponseCmndError();
+ }
+}
+
+//
+// Command `ZbUnload`
+// Unload a template previously loaded
+//
+void CmndZbUnload(void) {
+ // can be called before Zigbee is initialized
+ RemoveSpace(XdrvMailbox.data);
+
+ bool ret = ZbUnload(XdrvMailbox.data);
+ if (ret) {
+ ResponseCmndDone();
+ } else {
+ ResponseCmndError();
+ }
+}
+
+//
+// Command `ZbLoadDump`
+// Load a device specific zigbee template
+//
+void CmndZbLoadDump(void) {
+ // can be called before Zigbee is initialized
+ ZbLoadDump();
+ ResponseCmndDone();
+}
+
//
// Command `ZbScan`
// Run an energy scan
diff --git a/tasmota/zigbee/giex_water.zb b/tasmota/zigbee/giex_water.zb
new file mode 100644
index 000000000..fbc5f2fd9
--- /dev/null
+++ b/tasmota/zigbee/giex_water.zb
@@ -0,0 +1,14 @@
+#Z2Tv1
+# GiEX garden watering https://www.aliexpress.com/item/1005004222098040.html
+:TS0601,_TZE200_sh1btabb
+EF00/0101,WaterMode # duration=0 / capacity=1
+EF00/0102,WaterState # off=0 / on=1
+EF00/0365,IrrigationStartTime # (string) ex: "08:12:26"
+EF00/0366,IrrigationTarget # (string) ex: "08:13:36"
+EF00/0267,CycleIrrigationNumTimes # number of cycle irrigation times, set to 0 for single cycle
+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/026F,WaterConsumed # water consumed (Litres)
+EF00/0372,LastIrrigationDuration # (string) Ex: "00:01:10,0"