/* 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)
}