Zigbee device plugin mechanism with commands ``ZbLoad``, ``ZbUnload`` and ``ZbLoadDump``

This commit is contained in:
Stephan Hadinger 2022-08-18 19:23:11 +02:00
parent 94c88df93b
commit ff07d0608b
11 changed files with 1707 additions and 996 deletions

View File

@ -5,6 +5,7 @@ All notable changes to this project will be documented in this file.
## [12.1.0.1] ## [12.1.0.1]
### Added ### Added
- Zigbee device plugin mechanism with commands ``ZbLoad``, ``ZbUnload`` and ``ZbLoadDump``
### Changed ### Changed

View File

@ -672,6 +672,9 @@
#define D_JSON_ZIGBEE_SCAN "ZbScan" #define D_JSON_ZIGBEE_SCAN "ZbScan"
#define D_CMND_ZIGBEE_CIE "CIE" #define D_CMND_ZIGBEE_CIE "CIE"
#define D_CMND_ZIGBEE_ENROLL "Enroll" #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 // Commands xdrv_25_A4988_Stepper.ino
#define D_CMND_MOTOR "MOTOR" #define D_CMND_MOTOR "MOTOR"

View File

@ -356,9 +356,6 @@ const char Z_strings[] PROGMEM =
"PhysicalClosedLimitLift" "\x00" "PhysicalClosedLimitLift" "\x00"
"PhysicalClosedLimitTilt" "\x00" "PhysicalClosedLimitTilt" "\x00"
"Power" "\x00" "Power" "\x00"
"Power2" "\x00"
"Power3" "\x00"
"Power4" "\x00"
"PowerOffEffect" "\x00" "PowerOffEffect" "\x00"
"PowerOnRecall" "\x00" "PowerOnRecall" "\x00"
"PowerOnTimer" "\x00" "PowerOnTimer" "\x00"
@ -440,32 +437,12 @@ const char Z_strings[] PROGMEM =
"TimeStatus" "\x00" "TimeStatus" "\x00"
"TimeZone" "\x00" "TimeZone" "\x00"
"TotalProfileNum" "\x00" "TotalProfileNum" "\x00"
"TuyaAutoLock" "\x00"
"TuyaAwayDays" "\x00"
"TuyaAwayTemp" "\x00"
"TuyaBattery" "\x00"
"TuyaBoostTime" "\x00"
"TuyaCalibration" "\x00" "TuyaCalibration" "\x00"
"TuyaCalibrationTime" "\x00" "TuyaCalibrationTime" "\x00"
"TuyaChildLock" "\x00"
"TuyaComfortTemp" "\x00"
"TuyaEcoTemp" "\x00"
"TuyaFanMode" "\x00"
"TuyaForceMode" "\x00"
"TuyaMCUVersion" "\x00" "TuyaMCUVersion" "\x00"
"TuyaMaxTemp" "\x00"
"TuyaMinTemp" "\x00"
"TuyaMotorReversal" "\x00" "TuyaMotorReversal" "\x00"
"TuyaMovingState" "\x00" "TuyaMovingState" "\x00"
"TuyaPreset" "\x00"
"TuyaQuery" "\x00" "TuyaQuery" "\x00"
"TuyaScheduleHolidays" "\x00"
"TuyaScheduleWorkdays" "\x00"
"TuyaTempTarget" "\x00"
"TuyaValveDetection" "\x00"
"TuyaValvePosition" "\x00"
"TuyaWeekSelect" "\x00"
"TuyaWindowDetection" "\x00"
"UnoccupiedCoolingSetpoint" "\x00" "UnoccupiedCoolingSetpoint" "\x00"
"UnoccupiedHeatingSetpoint" "\x00" "UnoccupiedHeatingSetpoint" "\x00"
"UtilityName" "\x00" "UtilityName" "\x00"
@ -792,158 +769,135 @@ enum Z_offsets {
Zo_PhysicalClosedLimitLift = 4561, Zo_PhysicalClosedLimitLift = 4561,
Zo_PhysicalClosedLimitTilt = 4585, Zo_PhysicalClosedLimitTilt = 4585,
Zo_Power = 4609, Zo_Power = 4609,
Zo_Power2 = 4615, Zo_PowerOffEffect = 4615,
Zo_Power3 = 4622, Zo_PowerOnRecall = 4630,
Zo_Power4 = 4629, Zo_PowerOnTimer = 4644,
Zo_PowerOffEffect = 4636, Zo_PowerSource = 4657,
Zo_PowerOnRecall = 4651, Zo_PowerThreshold = 4669,
Zo_PowerOnTimer = 4665, Zo_Pressure = 4684,
Zo_PowerSource = 4678, Zo_PressureMaxMeasuredValue = 4693,
Zo_PowerThreshold = 4690, Zo_PressureMaxScaledValue = 4718,
Zo_Pressure = 4705, Zo_PressureMinMeasuredValue = 4741,
Zo_PressureMaxMeasuredValue = 4714, Zo_PressureMinScaledValue = 4766,
Zo_PressureMaxScaledValue = 4739, Zo_PressureScale = 4789,
Zo_PressureMinMeasuredValue = 4762, Zo_PressureScaledTolerance = 4803,
Zo_PressureMinScaledValue = 4787, Zo_PressureScaledValue = 4827,
Zo_PressureScale = 4810, Zo_PressureTolerance = 4847,
Zo_PressureScaledTolerance = 4824, Zo_Primary1Intensity = 4865,
Zo_PressureScaledValue = 4848, Zo_Primary1X = 4883,
Zo_PressureTolerance = 4868, Zo_Primary1Y = 4893,
Zo_Primary1Intensity = 4886, Zo_Primary2Intensity = 4903,
Zo_Primary1X = 4904, Zo_Primary2X = 4921,
Zo_Primary1Y = 4914, Zo_Primary2Y = 4931,
Zo_Primary2Intensity = 4924, Zo_Primary3Intensity = 4941,
Zo_Primary2X = 4942, Zo_Primary3X = 4959,
Zo_Primary2Y = 4952, Zo_Primary3Y = 4969,
Zo_Primary3Intensity = 4962, Zo_ProductCode = 4979,
Zo_Primary3X = 4980, Zo_ProductRevision = 4991,
Zo_Primary3Y = 4990, Zo_ProductURL = 5007,
Zo_ProductCode = 5000, Zo_QualityMeasure = 5018,
Zo_ProductRevision = 5012, Zo_RGB = 5033,
Zo_ProductURL = 5028, Zo_RMSCurrent = 5037,
Zo_QualityMeasure = 5039, Zo_RMSVoltage = 5048,
Zo_RGB = 5054, Zo_ReactivePower = 5059,
Zo_RMSCurrent = 5058, Zo_RecallScene = 5073,
Zo_RMSVoltage = 5069, Zo_RemainingTime = 5085,
Zo_ReactivePower = 5080, Zo_RemoteSensing = 5099,
Zo_RecallScene = 5094, Zo_RemoveAllGroups = 5113,
Zo_RemainingTime = 5106, Zo_RemoveAllScenes = 5129,
Zo_RemoteSensing = 5120, Zo_RemoveGroup = 5145,
Zo_RemoveAllGroups = 5134, Zo_RemoveScene = 5157,
Zo_RemoveAllScenes = 5150, Zo_ResetAlarm = 5169,
Zo_RemoveGroup = 5166, Zo_ResetAllAlarms = 5180,
Zo_RemoveScene = 5178, Zo_SWBuildID = 5195,
Zo_ResetAlarm = 5190, Zo_Sat = 5205,
Zo_ResetAllAlarms = 5201, Zo_SatMove = 5209,
Zo_SWBuildID = 5216, Zo_SatStep = 5217,
Zo_Sat = 5226, Zo_SceneCount = 5225,
Zo_SatMove = 5230, Zo_SceneValid = 5236,
Zo_SatStep = 5238, Zo_ScheduleMode = 5247,
Zo_SceneCount = 5246, Zo_SeaPressure = 5260,
Zo_SceneValid = 5257, Zo_ShortPollInterval = 5272,
Zo_ScheduleMode = 5268, Zo_Shutter = 5290,
Zo_SeaPressure = 5281, Zo_ShutterClose = 5298,
Zo_ShortPollInterval = 5293, Zo_ShutterLift = 5311,
Zo_Shutter = 5311, Zo_ShutterOpen = 5323,
Zo_ShutterClose = 5319, Zo_ShutterStop = 5335,
Zo_ShutterLift = 5332, Zo_ShutterTilt = 5347,
Zo_ShutterOpen = 5344, Zo_SoftwareRevision = 5359,
Zo_ShutterStop = 5356, Zo_StackVersion = 5376,
Zo_ShutterTilt = 5368, Zo_StandardTime = 5389,
Zo_SoftwareRevision = 5380, Zo_StartUpOnOff = 5402,
Zo_StackVersion = 5397, Zo_Status = 5415,
Zo_StandardTime = 5410, Zo_StoreScene = 5422,
Zo_StartUpOnOff = 5423, Zo_SwitchType = 5433,
Zo_Status = 5436, Zo_SystemMode = 5444,
Zo_StoreScene = 5443, Zo_TRVBoost = 5455,
Zo_SwitchType = 5454, Zo_TRVChildProtection = 5464,
Zo_SystemMode = 5465, Zo_TRVMirrorDisplay = 5483,
Zo_TRVBoost = 5476, Zo_TRVMode = 5500,
Zo_TRVChildProtection = 5485, Zo_TRVWindowOpen = 5508,
Zo_TRVMirrorDisplay = 5504, Zo_TempTarget = 5522,
Zo_TRVMode = 5521, Zo_Temperature = 5533,
Zo_TRVWindowOpen = 5529, Zo_TemperatureMaxMeasuredValue = 5545,
Zo_TempTarget = 5543, Zo_TemperatureMinMeasuredValue = 5573,
Zo_Temperature = 5554, Zo_TemperatureTolerance = 5601,
Zo_TemperatureMaxMeasuredValue = 5566, Zo_TerncyDuration = 5622,
Zo_TemperatureMinMeasuredValue = 5594, Zo_TerncyRotate = 5637,
Zo_TemperatureTolerance = 5622, Zo_ThSetpoint = 5650,
Zo_TerncyDuration = 5643, Zo_Time = 5661,
Zo_TerncyRotate = 5658, Zo_TimeEpoch = 5666,
Zo_ThSetpoint = 5671, Zo_TimeStatus = 5676,
Zo_Time = 5682, Zo_TimeZone = 5687,
Zo_TimeEpoch = 5687, Zo_TotalProfileNum = 5696,
Zo_TimeStatus = 5697, Zo_TuyaCalibration = 5712,
Zo_TimeZone = 5708, Zo_TuyaCalibrationTime = 5728,
Zo_TotalProfileNum = 5717, Zo_TuyaMCUVersion = 5748,
Zo_TuyaAutoLock = 5733, Zo_TuyaMotorReversal = 5763,
Zo_TuyaAwayDays = 5746, Zo_TuyaMovingState = 5781,
Zo_TuyaAwayTemp = 5759, Zo_TuyaQuery = 5797,
Zo_TuyaBattery = 5772, Zo_UnoccupiedCoolingSetpoint = 5807,
Zo_TuyaBoostTime = 5784, Zo_UnoccupiedHeatingSetpoint = 5833,
Zo_TuyaCalibration = 5798, Zo_UtilityName = 5859,
Zo_TuyaCalibrationTime = 5814, Zo_ValidUntilTime = 5871,
Zo_TuyaChildLock = 5834, Zo_ValvePosition = 5886,
Zo_TuyaComfortTemp = 5848, Zo_VelocityLift = 5900,
Zo_TuyaEcoTemp = 5864, Zo_ViewGroup = 5913,
Zo_TuyaFanMode = 5876, Zo_ViewScene = 5923,
Zo_TuyaForceMode = 5888, Zo_Water = 5933,
Zo_TuyaMCUVersion = 5902, Zo_WhitePointX = 5939,
Zo_TuyaMaxTemp = 5917, Zo_WhitePointY = 5951,
Zo_TuyaMinTemp = 5929, Zo_WindowCoveringType = 5963,
Zo_TuyaMotorReversal = 5941, Zo_X = 5982,
Zo_TuyaMovingState = 5959, Zo_Y = 5984,
Zo_TuyaPreset = 5975, Zo_ZCLVersion = 5986,
Zo_TuyaQuery = 5986, Zo_ZoneState = 5997,
Zo_TuyaScheduleHolidays = 5996, Zo_ZoneStatus = 6007,
Zo_TuyaScheduleWorkdays = 6017, Zo_ZoneStatusChange = 6018,
Zo_TuyaTempTarget = 6038, Zo_ZoneType = 6035,
Zo_TuyaValveDetection = 6053, Zo__ = 6044,
Zo_TuyaValvePosition = 6072, Zo_xx = 6046,
Zo_TuyaWeekSelect = 6090, Zo_xx000A00 = 6049,
Zo_TuyaWindowDetection = 6105, Zo_xx0A = 6058,
Zo_UnoccupiedCoolingSetpoint = 6125, Zo_xx0A00 = 6063,
Zo_UnoccupiedHeatingSetpoint = 6151, Zo_xx19 = 6070,
Zo_UtilityName = 6177, Zo_xx190A = 6075,
Zo_ValidUntilTime = 6189, Zo_xx190A00 = 6082,
Zo_ValvePosition = 6204, Zo_xxxx = 6091,
Zo_VelocityLift = 6218, Zo_xxxx00 = 6096,
Zo_ViewGroup = 6231, Zo_xxxx0A00 = 6103,
Zo_ViewScene = 6241, Zo_xxxxyy = 6112,
Zo_Water = 6251, Zo_xxxxyyyy = 6119,
Zo_WhitePointX = 6257, Zo_xxxxyyyy0A00 = 6128,
Zo_WhitePointY = 6269, Zo_xxxxyyzz = 6141,
Zo_WindowCoveringType = 6281, Zo_xxyy = 6150,
Zo_X = 6300, Zo_xxyy0A00 = 6155,
Zo_Y = 6302, Zo_xxyyyy = 6164,
Zo_ZCLVersion = 6304, Zo_xxyyyy000000000000 = 6171,
Zo_ZoneState = 6315, Zo_xxyyyy0A0000000000 = 6190,
Zo_ZoneStatus = 6325, Zo_xxyyyyzz = 6209,
Zo_ZoneStatusChange = 6336, Zo_xxyyyyzzzz = 6218,
Zo_ZoneType = 6353, Zo_xxyyzzzz = 6229,
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,
}; };

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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; i<nitems(Cx_cluster); i++) {
if (pgm_read_word(&Cx_cluster[i]) == cluster) {
return i;
}
}
return 0xFF;
}
// Multiplier contains only a limited set of values, so instead of storing the value
// we store an index in a table, and reduce it to 4 bits
enum Cm_multiplier_nibble {
Cm0 = 0, Cm1 = 1, Cm2, Cm5, Cm10, Cm100,
// negative numbers
Cm_2, Cm_5, Cm_10, Cm_100
};
const int8_t Cm_multiplier[] PROGMEM = {
0, 1, 2, 5, 10, 100,
-2, -5, -10, -100,
};
int8_t CmToMultiplier(uint8_t cm) {
cm = cm & 0x0F; // get only low nibble
if (cm < nitems(Cm_multiplier)) {
return pgm_read_byte(&Cm_multiplier[cm]);
}
return 1;
}
//
// Plug-in mechanism for device specific attributes and commands
//
/* Example:
# GiEX garden watering https://www.aliexpress.com/item/1005004222098040.html
:TS0601,_TZE200_sh1btabb
EF00_0101,water_mode
EF00_0102,water_state
EF00_0365,irrigation_start_time
EF00_0366,irrigation_target
EF00_0267,cycle_irrigation_num_times
EF00_0268,irrigation_target
EF00_0269,cycle_irrigation_interval
EF00_026A,current_temperature
EF00_026C,battery
EF00_026F,water_consumed
EF00_0372,last_irrigation_duration
*/
//
//
// Class for a single attribute from a plugin
//
//
class Z_plugin_attribute {
public:
Z_plugin_attribute(void) :
type(Zunk), multiplier(1), cluster(0xFFFF), attribute(0xFFFF)
{};
void set(uint16_t cluster, uint16_t attribute, const char *name, uint8_t type = Zunk) {
this->cluster = 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<Z_plugin_matcher> matchers;
LList<Z_attribute_synonym> synonyms;
LList<Z_plugin_attribute> 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<Z_plugin_template> {
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<Z_plugin_matcher> & matchers = tmpl.matchers; // get synonyms
const LList<Z_attribute_synonym> & 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<Z_plugin_matcher> & matchers = tmpl.matchers; // get synonyms
const LList<Z_plugin_attribute> & 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<Z_plugin_matcher> & matchers = tmpl.matchers; // get synonyms
const LList<Z_plugin_attribute> & 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

View File

@ -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 zigbee_devices.getShortAddr(shortaddr).setReachable(false); // mark device as reachable
Z_attribute_list attr_list; Z_attribute_list attr_list;
attr_list.addAttributePMEM(PSTR("Reachable")).setBool(false); // "Reachable":false 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); zigbee_devices.jsonPublishNow(shortaddr, attr_list);
} }
} }

View File

@ -529,6 +529,7 @@ static const Zigbee_Instruction zb_prog[] PROGMEM = {
ZI_CALL(&Z_Load_Devices, 0) ZI_CALL(&Z_Load_Devices, 0)
ZI_CALL(&Z_Load_Data, 0) ZI_CALL(&Z_Load_Data, 0)
ZI_CALL(&Z_Set_Save_Data_Timer, 0) ZI_CALL(&Z_Set_Save_Data_Timer, 0)
ZI_CALL(&Z_ZbAutoload, 0)
ZI_CALL(&Z_Query_Bulbs, 0) ZI_CALL(&Z_Query_Bulbs, 0)
ZI_LABEL(ZIGBEE_LABEL_MAIN_LOOP) 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_Devices, 0)
ZI_CALL(&Z_Load_Data, 0) ZI_CALL(&Z_Load_Data, 0)
ZI_CALL(&Z_Set_Save_Data_Timer, 0) ZI_CALL(&Z_Set_Save_Data_Timer, 0)
ZI_CALL(&Z_ZbAutoload, 0)
ZI_CALL(&Z_Query_Bulbs, 0) ZI_CALL(&Z_Query_Bulbs, 0)
ZI_LABEL(ZIGBEE_LABEL_MAIN_LOOP) ZI_LABEL(ZIGBEE_LABEL_MAIN_LOOP)

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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(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

View File

@ -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)); uint16_t max_interval = pgm_read_word(&(Z_autoAttributeReporting[i].max_interval));
float report_change_raw = Z_autoAttributeReporting[i].report_change; float report_change_raw = Z_autoAttributeReporting[i].report_change;
double report_change = report_change_raw; double report_change = report_change_raw;
uint8_t attr_type; // uint8_t attr_type;
int8_t multiplier; // int8_t multiplier;
const __FlashStringHelper* attr_name = zigbeeFindAttributeById(cluster, attr_id, &attr_type, &multiplier); Z_attribute_match attr_matched = Z_findAttributeMatcherById(shortaddr, cluster, attr_id, false);
if (attr_matched.found()) {
if (attr_name) {
if (comma) { ResponseAppend_P(PSTR(",")); } if (comma) { ResponseAppend_P(PSTR(",")); }
comma = true; 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.add8(0); // direction, always 0
buf.add16(attr_id); buf.add16(attr_id);
buf.add8(attr_type); buf.add8(attr_matched.zigbee_type);
buf.add16(min_interval); buf.add16(min_interval);
buf.add16(max_interval); buf.add16(max_interval);
if (!Z_isDiscreteDataType(attr_type)) { // report_change is only valid for non-discrete data types (numbers) if (!Z_isDiscreteDataType(attr_matched.zigbee_type)) { // report_change is only valid for non-discrete data types (numbers)
ZbApplyMultiplier(report_change, multiplier); ZbApplyMultiplier(report_change, attr_matched.multiplier);
// encode value // 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) { if (res < 0) {
AddLog(LOG_LEVEL_ERROR, PSTR(D_LOG_ZIGBEE "internal error, unsupported attribute type")); AddLog(LOG_LEVEL_ERROR, PSTR(D_LOG_ZIGBEE "internal error, unsupported attribute type"));
} else { } else {
@ -1690,12 +1689,12 @@ void Z_IncomingMessage(class ZCLFrame &zcl_received) {
zcl_received.parseReadAttributesResponse(attr_list); zcl_received.parseReadAttributesResponse(attr_list);
if (clusterid) { defer_attributes = true; } // don't defer system Cluster=0 messages if (clusterid) { defer_attributes = true; } // don't defer system Cluster=0 messages
} else if ( (!zcl_received.isClusterSpecificCommand()) && (ZCL_READ_ATTRIBUTES == zcl_received.getCmdId())) { } 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 // 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())) { } 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())) { } 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())) { } else if ( (!zcl_received.isClusterSpecificCommand()) && (ZCL_WRITE_ATTRIBUTES_RESPONSE == zcl_received.getCmdId())) {
zcl_received.parseWriteAttributesResponse(attr_list); zcl_received.parseWriteAttributesResponse(attr_list);
} else if (zcl_received.isClusterSpecificCommand()) { } else if (zcl_received.isClusterSpecificCommand()) {
@ -2123,6 +2122,15 @@ int32_t Z_Query_Bulbs(uint8_t value) {
return 0; // continue 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 // 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; break;
} }
if (!attr.isNone()) { if (!attr.isNone()) {
Z_parseAttributeKey(attr, cluster); Z_parseAttributeKey(shortaddr, attr, cluster);
attr_list.addAttribute(cluster, attr_id) = attr; attr_list.addAttribute(cluster, attr_id) = attr;
} }
} }

View File

@ -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_BIND "|" D_CMND_ZIGBEE_UNBIND "|" D_CMND_ZIGBEE_PING "|" D_CMND_ZIGBEE_MODELID "|"
D_CMND_ZIGBEE_LIGHT "|" D_CMND_ZIGBEE_OCCUPANCY "|" 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_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, SO_SYNONYMS(kZbSynonyms,
@ -62,6 +63,7 @@ void (* const ZigbeeCommand[])(void) PROGMEM = {
&CmndZbRestore, &CmndZbBindState, &CmndZbMap, &CmndZbLeave, &CmndZbRestore, &CmndZbBindState, &CmndZbMap, &CmndZbLeave,
&CmndZbConfig, &CmndZbData, &CmndZbScan, &CmndZbConfig, &CmndZbData, &CmndZbScan,
&CmndZbenroll, &CmndZbcie, &CmndZbenroll, &CmndZbcie,
&CmndZbLoad, &CmndZbUnload, &CmndZbLoadDump,
}; };
/********************************************************************************************/ /********************************************************************************************/
@ -339,7 +341,7 @@ void ZbSendReportWrite(class JsonParserToken val_pubwrite, class ZCLFrame & zcl)
Z_attribute attr; Z_attribute attr;
attr.setKeyName(key.getStr()); 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 // Buffer ready, do some sanity checks
// all attributes must use the same cluster // all attributes must use the same cluster
@ -352,11 +354,11 @@ void ZbSendReportWrite(class JsonParserToken val_pubwrite, class ZCLFrame & zcl)
} else { } else {
if (attr.key_is_str) { 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; return;
} }
if (Zunk == attr.attr_type) { 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; return;
} }
} }
@ -623,35 +625,27 @@ void ZbSendRead(JsonParserToken val_attr, ZCLFrame & zcl) {
bool found = false; bool found = false;
// scan attributes to find by name, and retrieve type // scan attributes to find by name, and retrieve type
for (uint32_t i = 0; i < nitems(Z_PostProcess); i++) { Z_attribute_match matched_attr = Z_findAttributeMatcherByName(zcl.shortaddr, key.getStr());
const Z_AttributeConverter *converter = &Z_PostProcess[i]; if (matched_attr.found()) {
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 // match name
// check if there is a conflict with cluster // check if there is a conflict with cluster
// TODO
if (!(value.getBool()) && attr_item_offset) { if (!(value.getBool()) && attr_item_offset) {
// If value is false (non-default) then set direction to 1 (for ReadConfig) // If value is false (non-default) then set direction to 1 (for ReadConfig)
attrs[actual_attr_len] = 0x01; attrs[actual_attr_len] = 0x01;
} }
actual_attr_len += attr_item_offset; actual_attr_len += attr_item_offset;
attrs[actual_attr_len++] = local_attr_id & 0xFF; attrs[actual_attr_len++] = matched_attr.attribute & 0xFF;
attrs[actual_attr_len++] = local_attr_id >> 8; attrs[actual_attr_len++] = matched_attr.attribute >> 8;
actual_attr_len += attr_item_len - 2 - attr_item_offset; // normally 0 actual_attr_len += attr_item_len - 2 - attr_item_offset; // normally 0
found = true; found = true;
// check cluster // check cluster
if (!zcl.validCluster()) { if (!zcl.validCluster()) {
zcl.cluster = local_cluster_id; zcl.cluster = matched_attr.cluster;
} else if (zcl.cluster != local_cluster_id) { } else if (zcl.cluster != matched_attr.cluster) {
ResponseCmndChar_P(PSTR(D_ZIGBEE_TOO_MANY_CLUSTERS)); ResponseCmndChar_P(PSTR(D_ZIGBEE_TOO_MANY_CLUSTERS));
if (attrs) { free(attrs); } if (attrs) { free(attrs); }
return; return;
} }
break; // found, exit loop
}
} }
if (!found) { if (!found) {
AddLog(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE D_ZIGBEE_UNKNWON_ATTRIBUTE), key.getStr()); AddLog(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE D_ZIGBEE_UNKNWON_ATTRIBUTE), key.getStr());
@ -863,7 +857,10 @@ void ZbBindUnbind(bool unbind) { // false = bind, true = unbind
if (val_cluster) { if (val_cluster) {
cluster = val_cluster.getUInt(cluster); // first convert as number cluster = val_cluster.getUInt(cluster); // first convert as number
if (0 == cluster) { 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(); 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` // Command `ZbScan`
// Run an energy scan // Run an energy scan

View File

@ -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"