/* groovylint-disable BuilderMethodWithSideEffects, CompileStatic, DuplicateListLiteral, DuplicateMapLiteral, DuplicateNumberLiteral, DuplicateStringLiteral, FactoryMethodName, ImplicitClosureParameter, ImplicitReturnStatement, InsecureRandom, LineLength, MethodCount, MethodParameterTypeRequired, MethodReturnTypeRequired, NestedBlockDepth, NoDef, NoJavaUtilDate, ParameterName, PublicMethodsBeforeNonPublicMethods, UnnecessaryGetter, UnnecessaryPackageReference, VariableTypeRequired */ /* * revision 1.0.0 - 2021-05-25 - martinkura - latest original driver version update * revision 1.0.1 - 2022-02-22 - kkossev - added Moes 4-Gang Switch / ZTS-EU4 * revision 1.0.2 - 2022-02-27 - kkossev - added more Tuya fingerprints for 1,2,3 and 4 gangs TS0601 wall switches * revision 1.0.3 - 2022-09-26 - kkossev - added Zemismart 6 Gangs Wall Light Switch * revision 1.0.4 - 2022-10-12 - kkossev - _TZE200_tz32mtza bug fix; code cleanup * revision 1.0.5 - 2023-03-16 - kkossev - added OZ Smart 1-2-3-4 gang switches _TZE200_gbagoilo _TZE200_nh9m9emk _TZE200_go3tvswy _TZE200_mexisfik * revision 1.0.6 - 2023-04-24 - kkossev - added importUrl; _TZE200_aqnazj70 _TZE200_wunufsil _TZE200_oisqyl4o _TZE200_atpwqgml * revision 1.0.7 - 2023-07-18 - kkossev - added _TZE200_7deq70b8 (@pabutterworth) * revision 1.0.8 - 2023-11-20 - kkossev - added TS0601 _TZE204_dqolcpcp (@alex1) (only the first 6 relays should be working) * revision 1.0.9 - 2024-01-02 - kkossev - added TS0601 _TZE200_r731zlxk * revision 1.1.0 - 2024-03-08 - kkossev - Groovy linting; addeed Zemismart 4-gang switch _TZE200_1n2kyphz; _TZE200_shkxsgis; _TZE204_shkxsgis; added testParse; Tuya cluster 0xEF00 data size check; * revision 1.1.1 - 2024-04-03 - kkossev - added TS0601 _TZE200_m*******j @Joao; * revision 1.1.2 - 2024-04-28 - kkossev - removed the T3E fingerprint; added _TZE204_aagrxlbd; * revision 1.1.3 - 2024-05-06 - kkossev - added _TZE204_xjknlqz8 * revision 1.1.4 - 2024-05-17 - hhorigian - added _TZE204_4cl0dzt4 * revision 1.1.5 - 2024-06-28 - kkossev - added TS0006 _TYZB01_ltundz9m _TZ3000_jyupj3fw * revision 1.1.6 - 2024-07-22 - hhorigian - added _TZE204_rkbxtclc, _TZE204_zpvusbtv, _TZE204_rzdkn5rxc * revision 1.1.7 - 2024-08-20 - kkossev - added _TZE204_dvosyycn (limited to 6 relays only); * revision 1.2.0 - 2024-08-20 - kkossev - added syncTuyaDateTime() * revision 1.3.0 - 2024-08-30 - kkossev/hhorigian - TratoZemismartKeypad configuration options * revision 1.3.1 - 2024-09-07 - kkossev - added Mercator Ikuü six switch _TZE200_wnp4d4va @hpgurgel * revision 1.3.2 - 2024-09-29 - kkossev/hhorigian - adding _TZE204_hwyydvqm (only thhe first switch is working!) * revision 1.3.3 - 2024-10-23 - kkossev - adding TS0601 _TZE204_lmgrbuwf (NOVADIGITAL) * */ static String version() { '1.3.3' } static String timeStamp() { '2024/10/23 9:39 PM' } import hubitat.device.HubAction import hubitat.device.Protocol import groovy.transform.Field @Field static final Boolean _DEBUG = false metadata { definition(name: 'Moes ZigBee Wall Switch 1/2/3-Gang', namespace: 'Moes 1.31', author: 'Martin Kura', importUrl: 'https://raw.githubusercontent.com/martinkura-svk/Hubitat/main/Moes%20ZigBee%20Wall%20Switch') { capability 'Initialize' capability 'Actuator' capability 'Refresh' capability 'Switch' fingerprint profileId:'0104', model:'TS0601', manufacturer:'_TZE200_amp6tsvy', endpointId:'01', inClusters:'0000,0004,0005,EF00', outClusters:'0019,000A', application:'42', deviceJoinName: 'Moes 1-Gang Switch / ZTS-EU1' fingerprint profileId:'0104', model:'TS0601', manufacturer:'_TZE200_oisqyl4o', endpointId:'01', inClusters:'0000,0004,0005,EF00', outClusters:'0019,000A', application:'42', deviceJoinName: 'No Neutral Push Button Light Switch 1 Gang' fingerprint profileId:'0104', model:'TS0601', manufacturer:'_TZE200_g1ib5ldv', endpointId:'01', inClusters:'0000,0004,0005,EF00', outClusters:'0019,000A', application:'42', deviceJoinName: 'Moes 2-Gang Switch / ZTS-EU2' fingerprint profileId:'0104', model:'TS0601', manufacturer:'_TZE200_wunufsil', endpointId:'01', inClusters:'0000,0004,0005,EF00', outClusters:'0019,000A', application:'42', deviceJoinName: 'No Neutral Push Button Light Switch 2 Gang' fingerprint profileId:'0104', model:'TS0601', manufacturer:'_TZE200_tz32mtza', endpointId:'01', inClusters:'0000,0004,0005,EF00', outClusters:'0019,000A', application:'42', deviceJoinName: 'Moes 3-Gang Switch / ZTS-EU3' fingerprint profileId:'0104', model:'TS0601', manufacturer:'_TZE200_atpwqgml', endpointId:'01', inClusters:'0000,0004,0005,EF00', outClusters:'0019,000A', application:'42', deviceJoinName: 'No Neutral Push Button Light Switch 3 Gang' fingerprint profileId:'0104', model:'TS0601', manufacturer:'_TZE200_k6jhsr0q', endpointId:'01', inClusters:'0000,0004,0005,EF00', outClusters:'0019,000A', application:'42', deviceJoinName: 'Moes 4-Gang Switch / ZTS-EU4' fingerprint profileId:'0104', model:'TS0601', manufacturer:'_TZE200_aqnazj70', endpointId:'01', inClusters:'0000,0004,0005,EF00', outClusters:'0019,000A', application:'42', deviceJoinName: 'Touch Switch 4 Gang No Neutral' fingerprint profileId:'0104', model:'TS0601', manufacturer:'_TZE200_9mahtqtg', endpointId:'01', inClusters:'0004,0005,EF00,0000', outClusters:'0019,000A', application:'42', deviceJoinName: 'Zemismart 6 Gangs Wall Light Switch' fingerprint profileId:'0104', model:'TS0601', manufacturer:'_TZE200_wnp4d4va', endpointId:'01', inClusters:'0000,0004,0005,EF00', outClusters:'0019,000A', application:'42', deviceJoinName: 'Mercator Ikuü Six Switch' // https://community.hubitat.com/t/driver-needed-for-a-6-gang-mercator-ikuu-zigbee-switch/142341/3?u=kkossev fingerprint profileId:'0104', model:'TS0601', manufacturer:'_TZE200_r731zlxk', endpointId:'01', inClusters:'0004,0005,EF00,0000', outClusters:'0019,000A', application:'42', deviceJoinName: 'Zemismart 6 Gangs Wall Light Switch' fingerprint profileId:'0104', model:'TS0601', manufacturer:'_TZE200_gbagoilo', endpointId:'01', inClusters:'0000,0004,0005,EF00', outClusters:'0019,000A', application:'46', deviceJoinName: 'OZ Smart Single Light Switch' fingerprint profileId:'0104', model:'TS0601', manufacturer:'_TZE200_nh9m9emk', endpointId:'01', inClusters:'0000,0004,0005,EF00', outClusters:'0019,000A', application:'46', deviceJoinName: 'OZ Smart Double Light Switch' fingerprint profileId:'0104', model:'TS0601', manufacturer:'_TZE200_go3tvswy', endpointId:'01', inClusters:'0000,0004,0005,EF00', outClusters:'0019,000A', application:'46', deviceJoinName: 'OZ Smart Triple Light Switch' fingerprint profileId:'0104', model:'TS0601', manufacturer:'_TZE200_mexisfik', endpointId:'01', inClusters:'0000,0004,0005,EF00', outClusters:'0019,000A', application:'46', deviceJoinName: 'OZ Smart Quad Light Switch' fingerprint profileId:'0104', model:'TS0601', manufacturer:'_TZE200_7deq70b8', endpointId:'01', inClusters:'0000,0004,0005,EF00', outClusters:'0019,000A', application:'42', deviceJoinName: 'Moes 2-gang switch' fingerprint profileId:'0104', model:'TS0601', manufacturer:'_TZE204_dqolcpcp', endpointId:'01', inClusters:'0004,0005,EF00,0000', outClusters:'0019,000A', application:'42', deviceJoinName: 'Tuya 12-way Relay Moduleh' fingerprint profileId:'0104', model:'TS0601', manufacturer:'_TZE200_1n2kyphz', endpointId:'01', inClusters:'0004,0005,EF00,0000', outClusters:'0019,000A', application:'42', deviceJoinName: 'Zemismart 4-gang switch' // https://www.aliexpress.com/item/1005003972289459.html fingerprint profileId:'0104', model:'TS0601', manufacturer:'_TZE200_shkxsgis', endpointId:'01', inClusters:'0004,0005,EF00,0000', outClusters:'0019,000A', application:'42', deviceJoinName: 'Zemismart 4-gang switch' fingerprint profileId:'0104', model:'TS0601', manufacturer:'_TZE204_shkxsgis', endpointId:'01', inClusters:'0004,0005,EF00,0000', outClusters:'0019,000A', application:'42', deviceJoinName: 'Zemismart 4-gang switch' fingerprint profileId:'0104', model:'TS0601', manufacturer:'_TZE204_aagrxlbd', endpointId:'01', inClusters:'0004,0005,EF00,0000', outClusters:'0019,000A', application:'42', deviceJoinName: 'Tuya 4-gang switch' // @Gabriel fingerprint profileId:'0104', model:'TS0601', manufacturer:'_TZE204_xjknlqz8', endpointId:'01', inClusters:'0004,0005,EF00,0000', outClusters:'0019,000A', application:'42', deviceJoinName: 'Tuya 6-gang switch' // @Vartan BR - TrTRON fingerprint profileId:'0104', model:'TS0601', manufacturer:'_TZE204_4cl0dzt4', endpointId:'01', inClusters:'0004,0005,EF00,0000', outClusters:'0019,000A', application:'42', deviceJoinName: 'Zemismart 6 Gangs Wall Light Switch' // @Vartan BR - QA fingerprint profileId:'0104', model:'TS0601', manufacturer:'_TZE204_rkbxtclc', endpointId:'01', inClusters:'0004,0005,EF00,0000', outClusters:'0019,000A', application:'42', deviceJoinName: 'Keypad 3-gang switch' // @Vartan BR - TR fingerprint profileId:'0104', model:'TS0601', manufacturer:'_TZE204_zpvusbtv', endpointId:'01', inClusters:'0004,0005,EF00,0000', outClusters:'0019,000A', application:'42', deviceJoinName: 'Zemismart 2-gang switch' // @Vartan BR - QA fingerprint profileId:'0104', model:'TS0601', manufacturer:'_TZE204_rzdkn5rx', endpointId:'01', inClusters:'0004,0005,EF00,0000', outClusters:'0019,000A', application:'42', deviceJoinName: 'Zemismart 1-gang switch' // @Vartan BR - QA fingerprint profileId:'0104', model:'TS0601', manufacturer:'_TZE204_dvosyycn', endpointId:'01', inClusters:'0004,0005,EF00,0000', outClusters:'0019,000A', application:'42', deviceJoinName: 'Tuya ZXYH 8IN/8OUT board' // https://community.hubitat.com/t/device-labels-changing-on-reboot/141754/5?u=kkossev fingerprint profileId:'0104', model:'TS0601', manufacturer:'_TZE204_lmgrbuwf', endpointId:'01', inClusters:'0004,0005,EF00,0000', outClusters:'0019,000A', application:'42', deviceJoinName: 'NOVADIGITAL 1 socket 2 switches' // https://www.novadigitalsmart.com.br/produtos/interruptor-tecla-fisica-tomada-4x2-ntzb-01-02-w-b } attribute 'switchLightMode', 'enum', ['off', 'on', 'position'] attribute 'powerOnBehavior', 'enum', ['off', 'on', 'last state'] attribute 'lastCheckin', 'string' attribute 'backlightLevel', 'number' attribute 'childLock', 'enum', ['off', 'on'] attribute 'backlightMode', 'enum', ['off', 'on'] attribute 'lightIndicatorMode', 'enum', ['none', 'lit when on', 'lit when off'] attribute 'onColor', 'enum', ['Red', 'Blue', 'Green', 'White', 'Yellow', 'Magenta', 'Cyan', 'WarmWhite', 'WarmYellow'] attribute 'offColor', 'enum', ['Red', 'Blue', 'Green', 'White', 'Yellow', 'Magenta', 'Cyan', 'WarmWhite', 'WarmYellow'] if (_DEBUG == true) { command 'testParse', [[name:'val', type: 'STRING', description: 'description', constraints: ['STRING']]] command 'queryAllTuyaDP' command 'test' command 'tuyaTest', [[name:'dpCommand', type: 'STRING', description: 'Tuya DP Command', constraints: ['STRING']], [name:'dpValue', type: 'STRING', description: 'Tuya DP value', constraints: ['STRING']], [name:'dpType', type: 'ENUM', constraints: ['DP_TYPE_VALUE', 'DP_TYPE_BOOL', 'DP_TYPE_ENUM'], description: 'DP data type']] } preferences { input(name: 'infoLogging', type: 'bool', title: ("Enable info logging"), description: '', defaultValue: true) input(name: 'debugLogging', type: 'bool', title: ("Enable debug logging"), description: '', defaultValue: true) if (device) { if (isTratoZemismartKeypad()) { input(name: 'backlightLevel', type: 'number', title: ("Led: Backlight Level"), description: ("Select led backlight level 0-100 (default: 50)"), defaultValue: 50, range: [0..100]) input(name: 'backlightMode', type: 'enum', title: ("Led: Backlight Mode"), description: ("Select backlight mode (default: on)"), options: ['off', 'on'], defaultValue: 'on') input(name: 'lightIndicatorMode', type: 'enum', title: ("Led: Indicator Mode"), description: ("Select led indicator mode (default: lit when on)"), options: ['none', 'lit when on', 'lit when off'], defaultValue: 'lit when on') input(name: 'onColor', type: 'enum', title: ("Led: On Color"), description: ("Select led color when switch is ON(default: Blue)"), options: ['Red', 'Blue', 'Green', 'White', 'Yellow', 'Magenta', 'Cyan', 'WarmWhite', 'WarmYellow'], defaultValue: 'Blue') input(name: 'offColor', type: 'enum', title: ("Led: Off Color"), description: ("Select led color when switch is OFF(default: White)"), options: ['Red', 'Blue', 'Green', 'White', 'Yellow', 'Magenta', 'Cyan', 'WarmWhite', 'WarmYellow'], defaultValue: 'White') input(name: 'childLlock', type: 'enum', title: ("Child Lock"), description: ("Select child lock mode (default: off)"), options: ['off', 'on'], defaultValue: 'off') } else { input(name: 'switchLightMode', type: 'enum', title: ("Switch Backlight Mode"), description: ("Select type of backlight indicator (default: Position)"), options: ['off', 'on', 'position'], defaultValue: 'position') } input(name: 'powerOnBehavior', type: 'enum', title: ("Switch Power On Behavior"), description: ("Select relay renew state after AC failed (default: OFF)"), options: ['off', 'on', 'last state'], defaultValue: 'off') } } } boolean isTratoZemismartKeypad() { return device.getDataValue('manufacturer') in ['_TZE204_rzdkn5rx', '_TZE204_zpvusbtv', '_TZE204_rkbxtclc'/*, '_TZ3000_excgg5kb'*/] // _TZ3000_excgg5kb for tests only - TOBEDEL! } void initializeVars() { device.updateSetting('switchLightMode', [type:'enum', value: isTratoZemismartKeypad() ? 'on' : 'position']) device.updateSetting('powerOnBehavior', [type:'enum', value:'off']) device.updateSetting('debugLogging', [type:'bool', value:'true']) device.updateSetting('infoLogging', [type:'bool', value:'true']) } def initialize() { if (infoLogging) { log.info 'Initializing...' } log.warn 'Debug logging will be automatically disabled after 24 hours!' initializeVars() setupChildDevices() if (debugLogging) { runIn(86400, logsOff) } refresh() } void logsOff() { log.warn 'Debug logging disabled...' device.updateSetting('debugLogging', [value:'false' ,type:'bool']) } def installed() { log.info 'Installing...' log.warn 'Debug logging will be automatically disabled after 24 hours!' setupChildDevices() initializeVars() if (debugLogging) { runIn(86400, logsOff) } refresh() } def updated() { List cmds = [] String dpValHex = '' int key = 0 log.warn "debug logging is: ${debugLogging == true}" log.warn "description logging is: ${infoLogging == true}" if (debugLogging) { runIn(86400, logsOff) } if (debugLogging) { log.debug 'Parent updated' } cmds = switchLightModeConfig() + powerOnBehaviorConfig() /*+ refresh()*/ if (isTratoZemismartKeypad()) { cmds += getTuyaCommand('66', DP_TYPE_VALUE, zigbee.convertToHexString(settings?.backlightLevel as int, 8)) key = BacklightSwitchOptions.find { it.value == settings?.backlightMode }?.key cmds += getTuyaCommand('10', DP_TYPE_BOOL, zigbee.convertToHexString(key as int, 2)) key = ChildLockOptions.find { it.value == settings?.childLlock }?.key cmds += getTuyaCommand('65', DP_TYPE_BOOL, zigbee.convertToHexString(key as int, 2)) key = LightIndicatorOptions.find { it.value == settings?.lightIndicatorMode }?.key cmds += getTuyaCommand('0F', DP_TYPE_ENUM, zigbee.convertToHexString(key as int, 2)) key = ColorOptions.find { it.value == settings?.onColor }?.key cmds += getTuyaCommand('67', DP_TYPE_ENUM, zigbee.convertToHexString(key as int, 2)) key = ColorOptions.find { it.value == settings?.offColor }?.key cmds += getTuyaCommand('68', DP_TYPE_ENUM, zigbee.convertToHexString(key as int, 2)) key = PowerOnBehaviorOptions.find { it.value == settings?.powerOnBehavior }?.key cmds += getTuyaCommand('0E', DP_TYPE_ENUM, zigbee.convertToHexString(key as int, 2)) } if (infoLogging) { log.info 'Updated...' } sendZigbeeCommands(cmds) } private static int getCLUSTER_TUYA() { 0xEF00 } private static int getSETDATA() { 0x00 } private static int getSETTIME() { 0x24 } private static String getDP_TYPE_RAW() { '01' } // [ bytes ] private static String getDP_TYPE_BOOL() { '01' } // [ 0/1 ] private static String getDP_TYPE_VALUE() { '02' } // [ 4 byte value ] private static String getDP_TYPE_STRING() { '03' } // [ N byte string ] private static String getDP_TYPE_ENUM() { '04' } // [ 0-255 ] private static String getDP_TYPE_BITMAP() { '05' } // [ 1,2,4 bytes ] as bits // Parse incoming device messages to generate events def parse(String description) { if (debugLogging) { log.debug "description: ${description}" } if (description?.startsWith('catchall:') || description?.startsWith('read attr -')) { Map descMap = zigbee.parseDescriptionAsMap(description) if (descMap?.clusterInt == CLUSTER_TUYA) { if (debugLogging) { log.debug "descMap: ${descMap}" } if ((descMap?.command in ['00', '01', '02']) && descMap?.data?.size() >= 7) { def switchFunc = (descMap?.data[2]) def switchAttr = (descMap?.data[3]) def switchState = (descMap?.data[6]) == '01' ? 'on' : 'off' if (switchFunc in ['01', '02', '03', '04', '05', '06'] && switchAttr == '01') { def cd = getChildDevice("${device.id}-${switchFunc}") if (cd == null) { return createEvent(name: 'switch', value: switchState) } if (descMap?.command == '00') { // switch toggled cd.parse([[name: 'switch', value:switchState, descriptionText: "Child switch ${switchFunc} turned $switchState"]]) } else if (descMap?.command in ['01', '02']) { // report switch status cd.parse([[name: 'switch', value:switchState, descriptionText: "Child switch ${switchFunc} is $switchState"]]) } if (switchState == 'on') { if (debugLogging) { log.debug 'Parent Switch ON' } return createEvent(name: 'switch', value: 'on') } else if (switchState == 'off') { def cdsOn = 0 // cound number of switches on getChildDevices().each { child -> if (getChildId(child) != switchFunc && child.currentValue('switch') == 'on') { cdsOn++ } } if (cdsOn == 0) { if (debugLogging) { log.debug 'Parent Switch OFF' } return createEvent(name: 'switch', value: 'off') } } } else if (isTratoZemismartKeypad()) { parseTratoZemismartKeypad(descMap) // andy event will be generated there return [:] } else { if (debugLogging) { log.debug "unprocessed Tuya command switchFunc: 0x${switchFunc} " } } } else if (descMap?.command == '24') { syncTuyaDateTime() } } } } void parseTratoZemismartKeypad(Map descMap) { if (descMap?.data?.size() < 7) { if (debugLogging) { log.warn "parseTratoZemismartKeypad: unprocessed descMap = ${descMap}" } return } int dataLen = descMap?.data.size() //log.warn "dataLen=${dataLen}" //def transid = zigbee.convertHexToInt(descMap?.data[1]) // "transid" is just a "counter", a response will have the same transid as the command if (dataLen <= 5) { if (debugLogging) { log.warn "unprocessed short Tuya command response: dp_id=${descMap?.data[3]} dp=${descMap?.data[2]} fncmd_len=${fncmd_len} data=${descMap?.data})" } return } for (int i = 0; i < (dataLen - 4); ) { int dp = zigbee.convertHexToInt(descMap?.data[2 + i]) // "dp" field describes the action/message of a command frame int dp_id = zigbee.convertHexToInt(descMap?.data[3 + i]) // "dp_identifier" is device dependant int fncmd_len = zigbee.convertHexToInt(descMap?.data[5 + i]) int fncmd = getTuyaAttributeValue(descMap?.data, i) // processTuyaDP(descMap, dp, dp_id, fncmd) i = i + fncmd_len + 4 } } @Field static final Map SwitchStateOptions = [0: 'off', 1: 'on'] @Field static final Map ChildLockOptions = [0: 'off', 1: 'on'] // 0x65 @Field static final Map PowerOnBehaviorOptions = [0: 'off', 1: 'on', 2: 'last state'] // 0x0E 'Turn power Off', 'Turn power On', 'Restore previous state'], @Field static final Map BacklightSwitchOptions = [0: 'off', 1: 'on'] // 0x10 @Field static final Map LightIndicatorOptions = [0: 'none', 1: 'lit when on', 2: 'lit when off'] // 0x0F @Field static final Map ColorOptions = [0: 'Red', 1: 'Blue', 2: 'Green', 3: 'White', 4: 'Yellow', 5: 'Magenta', 6: 'Cyan', 7: 'WarmWhite', 8: 'WarmYellow'] void processTuyaDP(final Map descMap, final int dp, final int dp_id, final int fncmd, final int dp_len=0) { if (debugLogging) { log.trace "processTuyaDP: dp=${dp} dp_id=${dp_id} fncmd=${fncmd} dp_len=${dp_len}" } Map event = [:] String description = '' String strValue = '' switch (dp) { case 0x01 : // (1) switch // handled in the main parse method case 0x02 : // (2) switch Boolean "{true,false}" case 0x03 : // (3) switch case 0x04 : // (4) switch case 0x05 : // (5) switch case 0x06 : // (6) switch if (debugLogging) { log.debug "Switch ${dp} is ${fncmd}" } break case 0x07 : // timer 1 countdown_1 Integer { "unit": "s", "min": 0, "max": 43200, "scale": 0, "step": 1 } case 0x08 : // timer 2 case 0x09 : // timer 3 int gang = dp - 0x07 + 1 description = "Gang #${gang} auto timer is ${fncmd}" break case 0x0D : // (13) - Master Switch switch_all Boolean "{true,false}" strValue = SwitchStateOptions[fncmd] description = "Master switch is ${strValue}" // event = createEvent(name: 'switch', value: strValue, descriptionText: description) // already handled ? break case 0x0E : // (14) Power ON Behavior (Restart Status) relay_status Enum { "range": ["off", "on", "memory" ]} [0: 'off', 1: 'on', 2: 'last state'] strValue = PowerOnBehaviorOptions[fncmd] description = "Power ON Behavior is ${strValue}" event = createEvent(name: 'powerOnBehavior', value: strValue, descriptionText: description) break case 0x0F : // (15) indicator status light_mode Enum { "range": ["none", "relay", "pos"] } strValue = LightIndicatorOptions[fncmd] description = "Light indicator status is ${strValue}" event = createEvent(name: 'lightIndicatorMode', value: strValue, descriptionText: description) break case 0x10 : // (16) "Backlight Switch", 0x10 bool backlight_switch Boolean "{true,false}" strValue = BacklightSwitchOptions[fncmd] description = "Backlight mode is ${strValue}" event = createEvent(name: 'backlightMode', value: strValue, descriptionText: description) break case 0x13 : // (19) delay-off schedule strValue = fncmd description = "delay-off schedule is ${strValue}" break case 0x1F : // (31) restart status 3 case 0x1E : // (30) restart status 2 case 0x1D : // (29) restart status 1 int gang = dp - 0x1D + 1 description = "Restart status for gang #${gang} is ${fncmd}" break case 0x20 : // (32) unknown0x20 strValue = fncmd description = "unknown0x20 is ${strValue}" event = createEvent(name: 'unknown0x20', value: strValue, descriptionText: description) break case 0x65 : // (101) child lock strValue = ChildLockOptions[fncmd] description = "Child lock is ${strValue}" event = createEvent(name: 'childLock', value: strValue, descriptionText: description) break case 0x66 : // (102) backlight "Backlight", 0x66, value, OK! backlight Integer { "unit": "%", "min": 0, "max": 100, "scale": 0, "step": 1 } strValue = fncmd description = "Backlight level is ${strValue}" event = createEvent(name: 'backlightLevel', value: strValue, descriptionText: description) break case 0x67 : // (103) onColor night_light_color Enum strValue = ColorOptions[fncmd] description = "onColor is ${strValue}" event = createEvent(name: 'onColor', value: strValue, descriptionText: description) break case 0x68 : // (104) offColor night_light_mode ? Enum strValue = ColorOptions[fncmd] description = "offColor is ${strValue}" event = createEvent(name: 'offColor', value: strValue, descriptionText: description) break case 0xD1 : // (209) cycle schedule strValue = fncmd description = "cycle schedule is ${strValue}" break case 0xD2 : // (210) random schedule strValue = fncmd description = "random schedule is ${strValue}" break default: if (debugLogging) { log.warn "NOT PROCESSED Tuya cmd: dp=${dp} value=${fncmd} descMap.data = ${descMap?.data}" } break } if (event != [:]) { sendEvent(event) if (infoLogging) { log.info description } } else if (description != '') { if (infoLogging) { log.info description } } } private int getTuyaAttributeValue(final List _data, final int index) { int retValue = 0 if (_data.size() >= 6) { int dataLength = zigbee.convertHexToInt(_data[5 + index]) if (dataLength == 0) { return 0 } int power = 1 for (i in dataLength..1) { retValue = retValue + power * zigbee.convertHexToInt(_data[index + i + 5]) power = power * 256 } } return retValue } String getPACKET_ID() { return zigbee.convertToHexString(new Random().nextInt(65536), 4) } List getTuyaCommand(String dp, String dp_type, String fncmd, int tuyaCmdDefault = SETDATA) { return sendTuyaCommand(dp, dp_type, fncmd, tuyaCmdDefault) } List sendTuyaCommand(String dp, String dp_type, String fncmd, int tuyaCmdDefault = SETDATA) { List cmds = [] int tuyaCmd = tuyaCmdDefault cmds = zigbee.command(CLUSTER_TUYA, tuyaCmd, [:], delay = 201, PACKET_ID + dp + dp_type + zigbee.convertToHexString((int)(fncmd.length() / 2), 4) + fncmd ) if (debugLogging) { "${device.displayName} getTuyaCommand (dp=$dp fncmd=$fncmd dp_type=$dp_type) = ${cmds}" } return cmds } public void tuyaTest(String dpCommand, String dpValue, String dpTypeString ) { String dpType = dpTypeString == 'DP_TYPE_VALUE' ? DP_TYPE_VALUE : dpTypeString == 'DP_TYPE_BOOL' ? DP_TYPE_BOOL : dpTypeString == 'DP_TYPE_ENUM' ? DP_TYPE_ENUM : null String dpValHex = dpTypeString == 'DP_TYPE_VALUE' ? zigbee.convertToHexString(dpValue as int, 8) : dpValue if (settings?.logEnable) { log.warn "${device.displayName} sending TEST command=${dpCommand} value=${dpValue} ($dpValHex) type=${dpType}" } sendZigbeeCommands( sendTuyaCommand(dpCommand, dpType, dpValHex) ) } void syncTuyaDateTime() { long offset = 0 int offsetHours = 0 Calendar cal = Calendar.getInstance() //it return same time as new Date() int hour = cal.get(Calendar.HOUR_OF_DAY) try { offset = location.getTimeZone().getOffset(new Date().getTime()) offsetHours = (offset / 3600000) as int if (debugLogging) { log.debug "${device.displayName} timezone offset of current location is ${offset} (${offsetHours} hours), current hour is ${hour} h" } } catch (e) { log.error "${device.displayName} cannot resolve current location. please set location in Hubitat location setting. Setting timezone offset to zero" } // List cmds = zigbee.command(0xEF00, 0x24, '0008' + zigbee.convertToHexString((int)(now() / 1000), 8) + zigbee.convertToHexString((int)((now() + offset) / 1000), 8)) sendZigbeeCommands(cmds) if (debugLogging) { log.debug "${device.displayName} Tuya device time synchronized" } } void sendZigbeeCommands(List cmd) { if (debugLogging) { log.debug "${device.displayName} sendZigbeeCommands: ${cmd}" } hubitat.device.HubMultiAction allActions = new hubitat.device.HubMultiAction() cmd.each { allActions.add(new hubitat.device.HubAction(it, hubitat.device.Protocol.ZIGBEE)) } sendHubCommand(allActions) } def lastCheckin() { // send event for heartbeat def now = new Date() sendEvent(name: 'lastCheckin', value: now) } def off() { if (infoLogging) { log.info 'Turn all switches OFF' } return [ "he cmd 0x${device.deviceNetworkId} 0x${device.endpointId} 0xEF00 0x00 {0001010100010002010001000301000100}", 'delay 200', ] } def on() { if (infoLogging) { log.info 'Turn all switches ON' } return [ "he cmd 0x${device.deviceNetworkId} 0x${device.endpointId} 0xEF00 0x00 {0001010100010102010001010301000101}", 'delay 200', ] } def refresh() { if (infoLogging) { log.info 'Refreshing...' } if (isTratoZemismartKeypad()) { return [queryAllTuyaDP()] } return [ lastCheckin() ] } private String getChildId(childDevice) { return childDevice.deviceNetworkId.substring(childDevice.deviceNetworkId.length() - 2) } void componentOn(childDevice) { if (debugLogging) { log.debug "component state is ON - ${childDevice} {${childDevice.deviceNetworkId}}" } if (infoLogging) { log.info "${childDevice} is ON" } String fullDataOn = '0001' + getChildId(childDevice) + '01000101' sendHubCommand(new HubAction("he cmd 0x${device.deviceNetworkId} 0x${device.endpointId} 0xEF00 0x00 {${fullDataOn}}", Protocol.ZIGBEE)) if (debugLogging) { log.debug { "{executed} 0x${device.deviceNetworkId} 0x${device.endpointId} 0xEF00 0x00 {${fullDataOn}}" } } } void componentOff(childDevice) { if (debugLogging) { log.debug "component state is OFF - ${childDevice} {${childDevice.deviceNetworkId}}" } if (infoLogging) { log.info "${childDevice} is OFF" } String fullDataOff = '0001' + getChildId(childDevice) + '01000100' sendHubCommand(new HubAction("he cmd 0x${device.deviceNetworkId} 0x${device.endpointId} 0xEF00 0x00 {${fullDataOff}}", Protocol.ZIGBEE)) if (debugLogging) { log.debug "{executed} 0x${device.deviceNetworkId} 0x${device.endpointId} 0xEF00 0x00 {${fullDataOff}}" } } void componentRefresh(childDevice) { if (debugLogging) { log.debug "component refresh ${childDevice.deviceNetworkId} ${childDevice}" } sendHubCommand(new HubAction("he rattr 0x${device.deviceNetworkId} 0x${device.endpointId} 0xEF00 0x00", Protocol.ZIGBEE)) if (debugLogging) { log.debug "{executed} 0x${device.deviceNetworkId} 0x${device.endpointId} 0xEF00 0x00" } } void setupChildDevices() { if (debugLogging) { log.debug 'Parent setupChildDevices' } deleteObsoleteChildren() int buttons = 0 switch (device.data.manufacturer) { case '_TZE200_amp6tsvy' : case '_TZE200_oisqyl4o' : case '_TZE200_wfxuhoea' : case '_TZE200_gbagoilo' : case '_TZE204_rzdkn5rx' : buttons = 1 break case '_TZE200_g1ib5ldv' : case '_TZE200_wunufsil' : case '_TZE200_nh9m9emk' : case '_TZE200_7deq70b8' : case '_TZE204_zpvusbtv' : buttons = 2 break case '_TZE200_tz32mtza' : case '_TZE200_kyfqmmyl' : case '_TZE200_go3tvswy' : case '_TZE200_atpwqgml' : case '_TZE204_rkbxtclc' : buttons = 3 break case '_TZE200_k6jhsr0q' : case '_TZE200_aqnazj70' : case '_TZE200_1ozguk6x' : case '_TZE200_mexisfik' : case '_TZE200_1n2kyphz' : case '_TZE200_shkxsgis' : case '_TZE204_shkxsgis' : case '_TZE200_mua6ucdj' : case '_TZE204_aagrxlbd' : buttons = 4 break case '_TZE200_9mahtqtg' : case '_TZE200_r731zlxk' : case '_TZE204_xjknlqz8' : case '_TZE204_4cl0dzt4' : case '_TYZB01_ltundz9m' : case '_TZ3000_jyupj3fw' : case '_TZE200_wnp4d4va' : case '_TZE204_lmgrbuwf' : // NOVADIGITAL buttons = 6 break case '_TZE204_dvosyycn' : buttons = 6 // limited to 6 relays only, actually has 8 relays and 8 inputs break case '_TZE204_dqolcpcp' : buttons = 12 break case '_TZE200_vhy3iakz' : case '_TZ3000_uim07oem' : default : // assume 4 buttons also for any unknown manufacturers codes! buttons = 4 break } if (infoLogging) { log.info "model: ${device.data.manufacturer} buttons: $buttons" } createChildDevices((int)buttons) } void createChildDevices(int buttons) { if (debugLogging) { log.debug 'Parent createChildDevices' } if (buttons <= 1) { if (debugLogging) { log.debug "This device have only: $buttons button, Child devices not needed." } return } /* groovylint-disable-next-line UnnecessaryElseStatement */ else { for (i in 1..buttons) { def childId = "${device.id}-0${i}" def existingChild = getChildDevices()?.find { it.deviceNetworkId == childId } if (existingChild) { if (infoLogging) { log.info "Child device ${childId} already exists (${existingChild})" } } else { if (infoLogging) { log.info "Creating device ${childId}" } addChildDevice('hubitat', 'Generic Component Switch', childId, [isComponent: true, name: "Switch EP0${i}", label: "${device.displayName} EP0${i}"]) } } } } void deleteObsoleteChildren() { if (debugLogging) { log.debug 'Parent deleteChildren' } getChildDevices().each { child -> if (!child.deviceNetworkId.startsWith(device.id) || child.deviceNetworkId == "${device.id}-00") { if (infoLogging) { log.info "Deleting ${child.deviceNetworkId}" } deleteChildDevice(child.deviceNetworkId) } } } List switchLightModeConfig() { List cmds = [] if (isTratoZemismartKeypad()) { int bl = settings?.switchLightMode == 'on' ? 1 : 0 String dpValHex = zigbee.convertToHexString(bl as int, 2) cmds = getTuyaCommand('10', DP_TYPE_BOOL, dpValHex) } else { switch (switchLightMode) { case 'off': if (infoLogging) { log.info 'Backlight - OFF' } cmds = zigbee.command(0xEF00, 0x0, '00010f04000100') break case 'on': if (infoLogging) { log.info 'Backlight - ON' } cmds = zigbee.command(0xEF00, 0x0, '00010f04000101') break case 'position': if (infoLogging) { log.info 'Backlight - position' } cmds = zigbee.command(0xEF00, 0x0, '00010f04000102') break } } return cmds } List powerOnBehaviorConfig() { List cmds = [] if (isTratoZemismartKeypad()) { int rl = settings?.powerOnBehavior == 'off' ? 0 : settings?.powerOnBehavior == 'on' ? 1 : 2 if (debugLogging) { log.debug "Relay mode: ${rl} (${settings?.powerOnBehavior})" } String dpValHex = zigbee.convertToHexString(rl, 2) cmds = getTuyaCommand('0E', DP_TYPE_ENUM, dpValHex) } else { switch (powerOnBehavior) { case 'off': if (infoLogging) { log.info 'Relay state - OFF' } cmds = zigbee.command(0xEF00, 0x0, '00010e04000100') break case 'on': if (infoLogging) { log.info 'Relay state - ON' } cmds = zigbee.command(0xEF00, 0x0, '00010e04000101') break case 'last state': if (infoLogging) { log.info 'Relay state - last state' } cmds = zigbee.command(0xEF00, 0x0, '00010e04000102') break } } return cmds } void queryAllTuyaDP() { log.trace 'queryAllTuyaDP()' List cmds = zigbee.command(0xEF00, 0x03) sendZigbeeCommands(cmds) } void testParse(final String description) { log.warn "testParse: ${description}" parse(description) log.trace '---end of testParse---' } void test() { log.trace 'test()' List cmds = childModeConfig() sendZigbeeCommands(cmds) }