/* 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 - testing _TZE204_hwyydvqm (device is now handled in the 'Tuya Zigbee Dimmer' driver!)
* revision 1.3.3 - 2024-10-23 - kkossev - added TS0601 _TZE204_lmgrbuwf (NOVADIGITAL)
* revision 1.3.4 - 2025-04-07 - kkossev - added TS0601 _TZE204_g4au0afs (NOVADIGITAL); added TS0601 _TZE200_mwvfvw8g _TZE200_cduqh1l0 _TZE200_emxxanvi ; added TS0601 _TZE200_kyfqmmyl _TZE200_2hf7x9n3 _TZE204_atpwqgml _TZE200_bynnczcb
* revision 1.3.5 - 2025-06-07 - kkossev - added TS0601 _TZE204_cduqh1l0
* revision 1.4.0 - 2025-08-30 - kkossev - added Moes Star Feather Smart Switches SFL02-Z (1,2,3,4 gangs); added Nova Digital Topazio Smart Switches (without the 'scene mode')
* revision 1.4.1 - 2025-10-04 - kkossev - added Mercator Ikuu 5-gang switch TS0601 _TZE200_leaqthtqq @giovanni.mausisa
* revision 1.4.2 - 2025-10-08 - kkossev/nickjamesdavis - added Eyzee 6-gang fingerprints _TZE204_jgujzjwh (app 4A) & _TZE200_8eazvzo6 (app 41) with 6-button detection
* revision 1.4.3 - 2025-11-29 - kkossev - Moes Star Feather Smart Switches processing bug fixes, including date/time sync
* revision 1.4.4 - 2026-01-05 - kkossev - added TS0601 _TZE284_ms97nkyy
* revision 1.4.5 - 2026-02-04 - kkossev - adding TS0601 _TZE204_nvxorhcj @bhnoronha
* revision 1.5.0 - 2026-04-15 - kkossev - fixed Moes Star Feather on()/off() commands; added all known Moes Star Feather Zigbee Wall Switches (1-4 gangs) with support for all their features, including _TZE200_z3u99qxt @smc.cross
* revision 1.5.1 - 2026-06-03 - kkossev - added Quero automatização 1-gang _TZE284_nzns7udm, 2-gang _TZE284_1aqlsquf, 3-gang _TZE284_pgxndxp4; renamed _TZE284_ms97nkyy to 'Quero automatização 6-gang switch'; each Quero N-gang device creates N physical + N virtual child switches;
* revision 1.5.2 - 2026-06-21 - Nathanael-Shermett - added setPreference command
* revision 1.5.3 - 2026-09-30 - kkossev - added Zemismart _TZE284_xibaabmu 4-gang control and screen labels @marcusvrsilva
*
*
*/
static String version() { '1.5.3' }
static String timeStamp() { '2026/09/30 08:57 AM' }
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'
command 'setPreference', [[name:'preference*', type:'STRING', description:'Preference name, e.g. lightIndicatorMode'], [name:'value*', type:'STRING', description:'New value, e.g. none']]
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
fingerprint profileId:'0104', model:'TS0601', manufacturer:'_TZE204_g4au0afs', endpointId:'01', inClusters:'0004,0005,EF00,0000', outClusters:'0019,000A', application:'42', deviceJoinName: 'NOVADIGITAL 6-gang switch' // https://community.hubitat.com/t/novadigital-tuya-6-gang-wall-switch-not-recognized-by-hubitat/152353?u=kkossev
fingerprint profileId:'0104', model:'TS0601', manufacturer:'_TZE200_mwvfvw8g', endpointId:'01', inClusters:'0004,0005,EF00,0000', outClusters:'0019,000A', application:'42', deviceJoinName: 'Tuya 6-gang switch'
fingerprint profileId:'0104', model:'TS0601', manufacturer:'_TZE200_cduqh1l0', endpointId:'01', inClusters:'0004,0005,EF00,0000', outClusters:'0019,000A', application:'42', deviceJoinName: 'Tuya 6-gang switch'
fingerprint profileId:'0104', model:'TS0601', manufacturer:'_TZE204_cduqh1l0', endpointId:'01', inClusters:'0004,0005,EF00,0000', outClusters:'0019,000A', application:'42', deviceJoinName: 'Tuya 6-gang switch' // https://community.hubitat.com/t/driver-zigbee-smart-switch-6-gang-tuya/154041?u=kkossev
fingerprint profileId:'0104', model:'TS0601', manufacturer:'_TZE200_emxxanvi', endpointId:'01', inClusters:'0004,0005,EF00,0000', outClusters:'0019,000A', application:'42', deviceJoinName: 'Tuya 6-gang switch'
fingerprint profileId:'0104', model:'TS0601', manufacturer:'_TZE200_kyfqmmyl', endpointId:'01', inClusters:'0000,0004,0005,EF00', outClusters:'0019,000A', application:'46', deviceJoinName: 'Interruptor touch Zigbee 3 Teclas'
fingerprint profileId:'0104', model:'TS0601', manufacturer:'_TZE200_2hf7x9n3', endpointId:'01', inClusters:'0000,0004,0005,EF00', outClusters:'0019,000A', application:'46', deviceJoinName: 'Interruptor touch Zigbee 3 Teclas'
fingerprint profileId:'0104', model:'TS0601', manufacturer:'_TZE204_atpwqgml', endpointId:'01', inClusters:'0000,0004,0005,EF00', outClusters:'0019,000A', application:'46', deviceJoinName: 'Interruptor touch Zigbee 3 Teclas'
fingerprint profileId:'0104', model:'TS0601', manufacturer:'_TZE200_bynnczcb', endpointId:'01', inClusters:'0000,0004,0005,EF00', outClusters:'0019,000A', application:'46', deviceJoinName: 'Interruptor touch Zigbee 3 Teclas'
fingerprint profileId:'0104', model:'TS0601', manufacturer:'_TZE200_leaqthtqq', endpointId:'01', inClusters:'0000,0004,0005,EF00', outClusters:'0019,000A', application:'42', deviceJoinName: 'Mercator Ikuü 5-gang switch' // https://community.hubitat.com/t/driver-needed-for-a-6-gang-mercator-ikuu-zigbee-switch/142341/10?u=kkossev
// Moes Star Feather Smart Switches
fingerprint profileId:'0104', model:'TS0601', manufacturer:'_TZE200_ydkqbmpt', endpointId:'01', inClusters:'0004,0005,EF00,0000', outClusters:'0019,000A', application:'42', deviceJoinName: 'Moes Star Feather Smart Switch 1 Gang'
fingerprint profileId:'0104', model:'TS0601', manufacturer:'_TZE200_z3u99qxt', endpointId:'01', inClusters:'0004,0005,EF00,0000', outClusters:'0019,000A', application:'41', deviceJoinName: 'Moes Star Feather Smart Switch 1 Gang' // https://community.hubitat.com/t/re-release-moes-zigbee-wall-switch-1-2-3-gang-tuya-ts0601-switches/138120/43?u=kkossev
fingerprint profileId:'0104', model:'TS0601', manufacturer:'_TZE200_stvgmdjz', endpointId:'01', inClusters:'0004,0005,EF00,0000', outClusters:'0019,000A', application:'42', deviceJoinName: 'Moes Star Feather Smart Switch 1 Gang'
fingerprint profileId:'0104', model:'TS0601', manufacturer:'_TZE200_uenof8jd', endpointId:'01', inClusters:'0004,0005,EF00,0000', outClusters:'0019,000A', application:'42', deviceJoinName: 'Moes Star Feather Smart Switch 2 Gang'
fingerprint profileId:'0104', model:'TS0601', manufacturer:'_TZE200_tzyy0rtq', endpointId:'01', inClusters:'0004,0005,EF00,0000', outClusters:'0019,000A', application:'42', deviceJoinName: 'Moes Star Feather Smart Switch 2 Gang'
fingerprint profileId:'0104', model:'TS0601', manufacturer:'_TZE200_hktk6hze', endpointId:'01', inClusters:'0004,0005,EF00,0000', outClusters:'0019,000A', application:'42', deviceJoinName: 'Moes Star Feather Smart Switch 2 Gang'
fingerprint profileId:'0104', model:'TS0601', manufacturer:'_TZE200_wv9ukqca', endpointId:'01', inClusters:'0004,0005,EF00,0000', outClusters:'0019,000A', application:'42', deviceJoinName: 'Moes Star Feather Smart Switch 3 Gang'
fingerprint profileId:'0104', model:'TS0601', manufacturer:'_TZE200_zo0cfekv', endpointId:'01', inClusters:'0004,0005,EF00,0000', outClusters:'0019,000A', application:'42', deviceJoinName: 'Moes Star Feather Smart Switch 3 Gang'
fingerprint profileId:'0104', model:'TS0601', manufacturer:'_TZE200_rd8cdssd', endpointId:'01', inClusters:'0004,0005,EF00,0000', outClusters:'0019,000A', application:'42', deviceJoinName: 'Moes Star Feather Smart Switch 3 Gang'
fingerprint profileId:'0104', model:'TS0601', manufacturer:'_TZE200_dq8bu0pt', endpointId:'01', inClusters:'0004,0005,EF00,0000', outClusters:'0019,000A', application:'42', deviceJoinName: 'Moes Star Feather Smart Switch 4 Gang'
fingerprint profileId:'0104', model:'TS0601', manufacturer:'_TZE200_9dhenr94', endpointId:'01', inClusters:'0004,0005,EF00,0000', outClusters:'0019,000A', application:'42', deviceJoinName: 'Moes Star Feather Smart Switch 4 Gang'
// Nova Digital Topazio Smart Switches
fingerprint profileId:'0104', model:'TS0601', manufacturer:'_TZE200_hmabvy81', endpointId:'01', inClusters:'0004,0005,EF00,0000', outClusters:'0019,000A', application:'42', deviceJoinName: 'Nova Digital Topazio Smart Switch 4 Gang'
fingerprint profileId:'0104', model:'TS0601', manufacturer:'_TZE204_nvxorhcj', endpointId:'01', inClusters:'0004,0005,EF00,0000', outClusters:'0019,000A', application:'4A', deviceJoinName: 'Nova Digital Topazio Smart Switch 8 Gang' // https://community.hubitat.com/t/re-release-moes-zigbee-wall-switch-1-2-3-gang-tuya-ts0601-switches/138120/28?u=kkossev
//
fingerprint profileId:'0104', model:'TS0601', manufacturer:'_TZE204_jgujzjwh', endpointId:'01', inClusters:'0000,0004,0005,EF00', outClusters:'0019,000A', application:'4A', deviceJoinName: 'Eyzee 6-gang switch' // https://community.hubitat.com/t/re-release-moes-zigbee-wall-switch-1-2-3-gang-tuya-ts0601-switches/138120/12?u=kkossev
fingerprint profileId:'0104', model:'TS0601', manufacturer:'_TZE200_8eazvzo6', endpointId:'01', inClusters:'0000,0004,0005,EF00', outClusters:'0019,000A', application:'41', deviceJoinName: 'Eyzee 6-gang switch' // https://community.hubitat.com/t/re-release-moes-zigbee-wall-switch-1-2-3-gang-tuya-ts0601-switches/138120/12?u=kkossev
// Quero automatização Switches
fingerprint profileId:"0104", model:"TS0601", manufacturer:"_TZE284_ms97nkyy", endpointId:'01', inClusters:"0000,0004,0005,EF00,0000,ED00", outClusters:"0019,000A", controllerType: "ZGB", deviceJoinName: 'Quero automatização 6-gang switch'
fingerprint profileId:"0104", model:"TS0601", manufacturer:"_TZE284_nzns7udm", endpointId:'01', inClusters:"0000,0004,0005,EF00,0000,ED00", outClusters:"0019,000A", controllerType: "ZGB", deviceJoinName: 'Quero automatização 1-gang switch'
fingerprint profileId:"0104", model:"TS0601", manufacturer:"_TZE284_1aqlsquf", endpointId:'01', inClusters:"0000,0004,0005,EF00,0000,ED00", outClusters:"0019,000A", controllerType: "ZGB", deviceJoinName: 'Quero automatização 2-gang switch'
fingerprint profileId:"0104", model:"TS0601", manufacturer:"_TZE284_pgxndxp4", endpointId:'01', inClusters:"0000,0004,0005,EF00,0000,ED00", outClusters:"0019,000A", controllerType: "ZGB", deviceJoinName: 'Quero automatização 3-gang switch'
// marcusvrsilva: https://community.hubitat.com/t/138120/56; cluster list confirmed by supplied pairing screenshot.
fingerprint profileId:'0104', model:'TS0601', manufacturer:'_TZE284_xibaabmu', endpointId:'01', inClusters:'0004,0005,EF00,0000,ED00', outClusters:'0019,000A', controllerType:'ZGB', deviceJoinName:'Zemismart 4-gang screen switch'
}
attribute 'switchLightMode', 'enum', ['off', 'on', 'position']
attribute 'powerOnBehavior', 'enum', ['off', 'on', 'last state']
attribute 'lastCheckin', 'string'
attribute 'screenLabel1', 'string'
attribute 'screenLabel2', 'string'
attribute 'screenLabel3', 'string'
attribute 'screenLabel4', 'string'
attribute 'screenMasterSwitch', 'enum', ['off', 'on']
attribute 'screenIndicatorMode', 'enum', ['off', 'on_off_status', 'switch_position']
attribute 'delayColor', 'enum', ['Red', 'Blue', 'Green', 'White', 'Yellow', 'Magenta', 'Cyan', 'WarmWhite', 'WarmYellow']
attribute 'radarConfig', 'enum', ['none', '10s', '20s', '30s', '45s', '60s']
attribute 'powerOnBehavior_l1', 'enum', ['off', 'on', 'last state']
attribute 'powerOnBehavior_l2', 'enum', ['off', 'on', 'last state']
attribute 'powerOnBehavior_l3', 'enum', ['off', 'on', 'last state']
attribute 'powerOnBehavior_l4', 'enum', ['off', 'on', 'last state']
attribute 'backlightLevel', 'number'
attribute 'childLock', 'enum', ['off', 'on']
attribute 'backlightMode', 'enum', ['off', 'on']
attribute 'lightIndicatorMode', 'enum', ['none', 'lit when on', 'lit when off']
attribute 'vibrationMode', 'enum', ['Gear 0', 'Gear 1', 'Gear 2', 'Gear 3']
attribute 'switchMode_l1', 'enum', ['switch', 'scene']
attribute 'switchMode_l2', 'enum', ['switch', 'scene']
attribute 'switchMode_l3', 'enum', ['switch', 'scene']
attribute 'switchMode_l4', 'enum', ['switch', 'scene']
attribute 'inductionMode', 'enum', ['off', 'on']
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 (isZemismartScreenSwitch()) {
input(name: 'screenIndicatorMode', type: 'enum', title: 'Indicator mode', description: 'Unset leaves the device setting unchanged.', options: [off:'Off', on_off_status:'On/off status', switch_position:'Switch position'], required: false)
input(name: 'backlightMode', type: 'enum', title: 'Backlight', description: 'Unset leaves the device setting unchanged.', options: ['off', 'on'], required: false)
input(name: 'backlightLevel', type: 'number', title: 'Backlight brightness (%)', description: 'Whole number 0-100. Unset leaves the device setting unchanged.', range: '0..100', required: false)
input(name: 'childLlock', type: 'enum', title: 'Child lock', description: 'Unset leaves the device setting unchanged.', options: ['off', 'on'], required: false)
input(name: 'onColor', type: 'enum', title: 'Color when on', description: 'Unset leaves the device setting unchanged.', options: ['Red', 'Blue', 'Green', 'White', 'Yellow', 'Magenta', 'Cyan', 'WarmWhite', 'WarmYellow'], required: false)
input(name: 'offColor', type: 'enum', title: 'Color when off', description: 'Unset leaves the device setting unchanged.', options: ['Red', 'Blue', 'Green', 'White', 'Yellow', 'Magenta', 'Cyan', 'WarmWhite', 'WarmYellow'], required: false)
input(name: 'delayColor', type: 'enum', title: 'Color while delayed', description: 'Unset leaves the device setting unchanged. Does not start a timer.', options: ['Red', 'Blue', 'Green', 'White', 'Yellow', 'Magenta', 'Cyan', 'WarmWhite', 'WarmYellow'], required: false)
input(name: 'radarConfig', type: 'enum', title: 'Radar configuration', description: 'Unset leaves the device setting unchanged.', options: ['none', '10s', '20s', '30s', '45s', '60s'], required: false)
for (int gang = 1; gang <= 4; gang++) {
input(name: "powerOnBehavior_l${gang}", type: 'enum', title: "Switch ${gang} power-on behavior", description: 'Unset leaves the device setting unchanged.', options: ['off', 'on', 'last state'], required: false)
input(name: "screenLabel${gang}", type: 'text', title: "Display label ${gang}", description: 'Maximum 12 characters (some symbols count as two). Save Preferences to apply; removing a previously set label clears it.', defaultValue: '', required: false)
}
}
else 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 if (isMoesStarFeatherSwitch()) {
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: 'vibrationMode', type: 'enum', title: ("Vibration Mode"), description: ("Select vibration intensity for tactile feedback (default: Gear 1)"), options: ['Gear 0', 'Gear 1', 'Gear 2', 'Gear 3'], defaultValue: 'Gear 1')
input(name: 'inductionMode', type: 'enum', title: ("Induction Mode"), description: ("Enable/disable induction sensing (default: off)"), options: ['off', 'on'], defaultValue: 'off')
int sfGangs = getStarFeatherGangCount()
input(name: 'switchMode_l1', type: 'enum', title: ("Switch 1 Mode"), description: ("Switch or scene mode for gang 1 (default: switch)"), options: ['switch', 'scene'], defaultValue: 'switch')
input(name: 'countdown_l1', type: 'number', title: ("Switch 1 Countdown Timer (s)"), description: ("Auto-off timer for gang 1, seconds, 0 = disabled (max 86400)"), defaultValue: 0, range: '0..86400')
input(name: 'momentary_l1', type: 'number', title: ("Switch 1 Momentary Timer (s)"), description: ("Momentary on timer for gang 1, seconds, 0 = disabled (max 3600)"), defaultValue: 0, range: '0..3600')
if (sfGangs >= 2) {
input(name: 'switchMode_l2', type: 'enum', title: ("Switch 2 Mode"), description: ("Switch or scene mode for gang 2 (default: switch)"), options: ['switch', 'scene'], defaultValue: 'switch')
input(name: 'countdown_l2', type: 'number', title: ("Switch 2 Countdown Timer (s)"), description: ("Auto-off timer for gang 2, seconds, 0 = disabled"), defaultValue: 0, range: '0..86400')
input(name: 'momentary_l2', type: 'number', title: ("Switch 2 Momentary Timer (s)"), description: ("Momentary on timer for gang 2, seconds, 0 = disabled"), defaultValue: 0, range: '0..3600')
}
if (sfGangs >= 3) {
input(name: 'switchMode_l3', type: 'enum', title: ("Switch 3 Mode"), description: ("Switch or scene mode for gang 3 (default: switch)"), options: ['switch', 'scene'], defaultValue: 'switch')
input(name: 'countdown_l3', type: 'number', title: ("Switch 3 Countdown Timer (s)"), description: ("Auto-off timer for gang 3, seconds, 0 = disabled"), defaultValue: 0, range: '0..86400')
input(name: 'momentary_l3', type: 'number', title: ("Switch 3 Momentary Timer (s)"), description: ("Momentary on timer for gang 3, seconds, 0 = disabled"), defaultValue: 0, range: '0..3600')
}
if (sfGangs >= 4) {
input(name: 'switchMode_l4', type: 'enum', title: ("Switch 4 Mode"), description: ("Switch or scene mode for gang 4 (default: switch)"), options: ['switch', 'scene'], defaultValue: 'switch')
input(name: 'countdown_l4', type: 'number', title: ("Switch 4 Countdown Timer (s)"), description: ("Auto-off timer for gang 4, seconds, 0 = disabled"), defaultValue: 0, range: '0..86400')
input(name: 'momentary_l4', type: 'number', title: ("Switch 4 Momentary Timer (s)"), description: ("Momentary on timer for gang 4, seconds, 0 = disabled"), defaultValue: 0, range: '0..3600')
}
}
else if (isQueroSwitch()) {
input(name: 'backlightLevel', type: 'number', title: ("Backlight Brightness"), description: ("Set backlight brightness 0-99% (default: 50)"), defaultValue: 50, range: '0..99')
}
else {
input(name: 'switchLightMode', type: 'enum', title: ("Switch Backlight Mode"), description: ("Select type of backlight indicator (default: Position)"), options: ['off', 'on', 'position'], defaultValue: 'position')
}
if (!isZemismartScreenSwitch()) {
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']
}
boolean isZemismartScreenSwitch() {
return device.getDataValue('model') == 'TS0601' && device.getDataValue('manufacturer') == '_TZE284_xibaabmu'
}
boolean isMoesStarFeatherSwitch() { //return true
return device.getDataValue('manufacturer') in ['_TZE200_ydkqbmpt', '_TZE200_z3u99qxt', '_TZE200_stvgmdjz', '_TZE200_uenof8jd', '_TZE200_tzyy0rtq', '_TZE200_hktk6hze', '_TZE200_wv9ukqca', '_TZE200_zo0cfekv', '_TZE200_rd8cdssd', '_TZE200_dq8bu0pt', '_TZE200_9dhenr94', '_TZE200_hmabvy81']
}
int getStarFeatherGangCount() {
String mfr = device.getDataValue('manufacturer')
if (mfr in ['_TZE200_ydkqbmpt', '_TZE200_z3u99qxt', '_TZE200_stvgmdjz']) { return 1 }
if (mfr in ['_TZE200_uenof8jd', '_TZE200_tzyy0rtq', '_TZE200_hktk6hze']) { return 2 }
if (mfr in ['_TZE200_wv9ukqca', '_TZE200_zo0cfekv', '_TZE200_rd8cdssd']) { return 3 }
if (mfr in ['_TZE200_dq8bu0pt', '_TZE200_9dhenr94', '_TZE200_hmabvy81']) { return 4 }
return 1
}
boolean isQueroSwitch() {
return device.getDataValue('manufacturer') in ['_TZE284_ms97nkyy', '_TZE284_nzns7udm', '_TZE284_1aqlsquf', '_TZE284_pgxndxp4']
}
boolean isQuero12GangSwitch() {
return device.getDataValue('manufacturer') == '_TZE284_ms97nkyy'
}
int getQueroGangCount() {
// Returns total child device count (physical + virtual)
switch (device.getDataValue('manufacturer')) {
case '_TZE284_ms97nkyy': return 12
case '_TZE284_nzns7udm': return 2
case '_TZE284_1aqlsquf': return 4
case '_TZE284_pgxndxp4': return 6
default: return 1
}
}
int getQueroPhysicalCount() {
// Returns number of physical relay channels only
switch (device.getDataValue('manufacturer')) {
case '_TZE284_ms97nkyy': return 6
case '_TZE284_nzns7udm': return 1
case '_TZE284_1aqlsquf': return 2
case '_TZE284_pgxndxp4': return 3
default: return 1
}
}
boolean is8GangNvxorhcj() {
return device.getDataValue('manufacturer') == '_TZE204_nvxorhcj'
}
void initializeVars() {
if (isZemismartScreenSwitch()) {
// Optional screen configuration stays unset until selected; never impose device defaults.
for (int gang = 1; gang <= 4; gang++) {
if (settings["screenLabel${gang}"] == null) { device.updateSetting("screenLabel${gang}", [type:'text', value:'']) }
}
}
else if (isTratoZemismartKeypad()) {
device.updateSetting('backlightMode', [type:'enum', value:'on'])
device.updateSetting('lightIndicatorMode', [type:'enum', value:'lit when on'])
}
else if (isMoesStarFeatherSwitch()) {
device.updateSetting('backlightMode', [type:'enum', value:'on'])
device.updateSetting('lightIndicatorMode', [type:'enum', value:'lit when on'])
device.updateSetting('vibrationMode', [type:'enum', value:'Gear 1'])
device.updateSetting('inductionMode', [type:'enum', value:'off'])
device.updateSetting('switchMode_l1', [type:'enum', value:'switch'])
device.updateSetting('switchMode_l2', [type:'enum', value:'switch'])
device.updateSetting('switchMode_l3', [type:'enum', value:'switch'])
device.updateSetting('switchMode_l4', [type:'enum', value:'switch'])
device.updateSetting('countdown_l1', [type:'number', value:0])
device.updateSetting('countdown_l2', [type:'number', value:0])
device.updateSetting('countdown_l3', [type:'number', value:0])
device.updateSetting('countdown_l4', [type:'number', value:0])
device.updateSetting('momentary_l1', [type:'number', value:0])
device.updateSetting('momentary_l2', [type:'number', value:0])
device.updateSetting('momentary_l3', [type:'number', value:0])
device.updateSetting('momentary_l4', [type:'number', value:0])
}
else if (isQueroSwitch()) {
device.updateSetting('backlightLevel', [type:'number', value:50])
}
else {
device.updateSetting('switchLightMode', [type:'enum', value:'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 (isZemismartScreenSwitch()) {
cmds += zemismartScreenConfig() + screenLabelConfig()
}
if (isMoesStarFeatherSwitch()) {
cmds += vibrationModeConfig() + inductionModeConfig() + switchModeConfig() + countdownConfig() + momentaryConfig()
}
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 (isQueroSwitch()) {
// Quero automatização switches: backlight brightness 0-99%
int brightness = settings?.backlightLevel != null ? settings.backlightLevel as int : 50
cmds += getTuyaCommand('65', DP_TYPE_VALUE, zigbee.convertToHexString(brightness, 8))
}
if (infoLogging) { log.info 'Updated...' }
sendZigbeeCommands(cmds)
}
def setPreference(String preference, String value) {
String key = preference?.trim()
String val = value?.trim() ?: ''
if (!key) {
log.warn 'setPreference: missing preference name'
return
}
String type = val.isNumber() ? 'number' : (val.toLowerCase() in ['true', 'false'] ? 'bool' : 'enum')
if (isZemismartScreenSwitch()) {
if (key ==~ /screenLabel[1-4]/) {
type = 'text'
val = value ?: '' // Preserve numeric-looking text and surrounding spaces.
} else if (key in ['debugLogging', 'infoLogging']) {
type = 'bool'
val = val.toLowerCase()
} else { type = key == 'backlightLevel' ? 'number' : 'enum' }
if (!validScreenPreference(key, val)) { return }
}
if (infoLogging) { log.info "setPreference: ${key} = ${val} (${type})" }
device.updateSetting(key, [value: val, type: type])
settings[key] = isZemismartScreenSwitch() && type == 'bool' ? val == 'true' : val
updated()
}
private static int getCLUSTER_TUYA() { 0xEF00 }
private static int getSETDATA() { 0x00 }
private static int getSETTIME() { 0x24 }
private static String getDP_TYPE_RAW() { '00' } // [ 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}" }
// Check if this is a Star Feather device - if so, skip traditional switch logic
if (isMoesStarFeatherSwitch()) {
parseMoesStarFeatherSwitch(descMap) // handle Star Feather specific parsing
return [:]
}
if (isQueroSwitch()) {
parse12GangSwitch(descMap) // handle Quero automatização switch parsing
return [:]
}
if (isZemismartScreenSwitch()) {
parseZemismartScreenSwitch(descMap)
return [:]
}
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'
// _TZE204_nvxorhcj (8-gang) uses DP 0x65/0x66 for gangs 7/8
String childId = switchFunc
boolean is8GangSwitchDp = is8GangNvxorhcj() && (switchFunc in ['65', '66'])
if (is8GangSwitchDp) {
childId = switchFunc == '65' ? '07' : '08'
}
if ((switchFunc in ['01', '02', '03', '04', '05', '06'] || is8GangSwitchDp) && switchAttr == '01') {
// Traditional switch logic for Moes and Trato devices
def cd = getChildDevice("${device.id}-${childId}")
if (cd == null) {
return createEvent(name: 'switch', value: switchState)
}
if (descMap?.command == '00') {
// switch toggled
cd.parse([[name: 'switch', value:switchState, descriptionText: "Child switch ${childId} turned $switchState"]])
}
else if (descMap?.command in ['01', '02']) {
// report switch status
cd.parse([[name: 'switch', value:switchState, descriptionText: "Child switch ${childId} 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) != childId && 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()
}
}
}
}
// Protocol reference: zigbee-herdsman-converters, zemismart.ts ZMS-206US-4
// at 8c6213a285159cb8ebb63436db2aef7c39565772. Scope is the exact fingerprint above.
void parseZemismartScreenSwitch(Map descMap) {
String command = descMap.command?.toUpperCase()
if (descMap.sourceEndpoint != null && descMap.sourceEndpoint != '01') { return }
// Global ZCL default responses (0B) must never be interpreted as Tuya data.
if (descMap.isClusterSpecific == false || command == '0B') { return }
if (command == '24') {
syncTuyaDateTime()
return
}
if (command == '25') {
// ZCL UINT16 payloadSize is little-endian, followed by gateway-connected = 1.
sendZigbeeCommands(zigbee.command(CLUSTER_TUYA, 0x25, [destEndpoint:1], 200, '010001'))
return
}
if (!(command in ['00', '01', '02', '05', '06'])) { return }
List data = descMap.data
if (data == null || data.size() < 2 || data.any { !(it instanceof String) || !(it ==~ /[0-9a-fA-F]{2}/) }) {
if (debugLogging) { log.debug 'Screen switch: invalid Tuya payload' }
return
}
// Decode the complete frame first: a truncated trailing record cannot publish partial state.
List