/* groovylint-disable NglParseError, ImplicitReturnStatement, InsecureRandom, MethodReturnTypeRequired, MethodSize, ParameterName, PublicMethodsBeforeNonPublicMethods, StaticMethodsBeforeInstanceMethods, UnnecessaryGroovyImport, UnnecessaryObjectReferences, UnusedImport, VariableName */
/**
* Tuya Multi Sensor 4 In 1 driver for Hubitat
*
* https://community.hubitat.com/t/dynamic-capabilities-commands-and-attributes-for-drivers/98342
* https://community.hubitat.com/t/release-tuya-zigbee-multi-sensor-4-in-1-pir-motion-sensors-w-healthstatus/92441
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*
* This driver is inspired by @w35l3y work on Tuya device driver (Edge project).
* For a big portions of code all credits go to Jonathan Bradshaw.
*
* ver. 3.2.0 2024-05-26 kkossev - first version, based on the mmWave radar driver code : depricated Linptech; added TS0202 add _TYZB01_vwqnz1sn;
* ver. 3.2.1 2024-05-31 kkossev - commonLib ver 3.2.1 allignment; tested 2In1 _TZE200_3towulqd ; new device profile group 'RH3040_TUYATEC'; SiHAS;
* ver. 3.2.2 2024-07-05 kkossev - created motionLib; restored 'all' attribute
* ver. 3.2.3 2024-07-27 kkossev - added Sonoff SNZB-03P
* ver. 3.3.0 2024-08-30 kkossev - main branch release.
* ver. 3.3.1 2024-10-26 kkossev - added TS0601 _TZE200_f1pvdgoh into a new device profile group 'TS0601_2IN1_MYQ_ZMS03'
* ver. 3.3.2 2024-11-30 kkossev - added Azoula Zigbee 4 in 1 Multi Sensor model:'HK-SENSOR-4IN1-A', manufacturer:'Sunricher' into SIHAS group
* ver. 3.3.3 2025-01-29 kkossev - TS0601 _TZE200_ppuj1vem moved to 'TS0601_2IN1_MYQ_ZMS03' deviceProfile @ltdonjohnson
* ver. 3.4.0 2025-03-03 kkossev - added customConfigureDevice(); SNZB-03 configuration bug fixes; added SNZB-03P device profile;
* ver. 3.4.1 2025-03-29 kkossev - added custom configuration function for Espressif @ilkeraktuna
* ver. 3.5.0 2025-04-08 kkossev - urgent fix for java.lang.CloneNotSupportedException
* ver. 3.5.1 2025-04-25 kkossev - HE platfrom version 2.4.1.x decimal preferences range patch/workaround.
* ver. 3.5.2 2025-07-14 kkossev - bug fix: 'sendDelayedBatteryEvent' exception
* ver. 3.5.3 2025-09-15 kkossev - alligned with commonLib 4.0.0
* ver. 3.5.4 2025-10-03 kkossev - added model:'ZG-204ZL' (note the 'ZL' sufix!), manufacturer:'HOBEIAN' 2-in-1 sensor into TS0601_2IN1' device profile group
* ver. 3.5.5 2025-10-20 kkossev - added IMOU Motion Sensor ZP1 model:'ZP2-EN', manufacturer:'MultIR'
*
* TODO: show Temperature Offset and Humidity Offset only when the device profile supports TemperatureMeasurement and RelativeHumidityMeasurement capabilities
* TODO: check why no preferences : updateAllPreferences: no preferences defined for device profile SIHAS_USM-300Z_4_IN_1
* TODO: update documentation : https://github.com/kkossev/Hubitat/wiki/Tuya-Multi-Sensor-4-In-1
*/
static String version() { "3.5.5" }
static String timeStamp() {"2025/10/20 10:45 PM"}
@Field static final Boolean _DEBUG = false
@Field static final Boolean _TRACE_ALL = false // trace all messages, including the spammy ones
@Field static final Boolean DEFAULT_DEBUG_LOGGING = false // disable it for the production release !
import groovy.transform.Field
import hubitat.device.HubMultiAction
import hubitat.device.Protocol
import hubitat.helper.HexUtils
import hubitat.zigbee.zcl.DataType
import java.util.concurrent.ConcurrentHashMap
import groovy.json.JsonOutput
import groovy.transform.CompileStatic
deviceType = "MultiSensor4in1"
@Field static final String DEVICE_TYPE = "MultiSensor4in1"
metadata {
definition (
name: 'Tuya Multi Sensor 4 In 1',
importUrl: 'https://raw.githubusercontent.com/kkossev/Hubitat/development/Drivers/Tuya%20Multi%20Sensor%204%20In%201/Tuya%20Multi%20Sensor%204%20In%201.groovy',
namespace: 'kkossev', author: 'Krassimir Kossev', singleThreaded: true )
{
capability 'MotionSensor'
//capability 'TamperAlert'
attribute 'all', 'string' // all attributes in one string
attribute 'distance', 'number' // Tuya Radar, obsolete
attribute 'unacknowledgedTime', 'number' // AIR models
attribute 'keepTime', 'enum', ['5 seconds', '10 seconds', '15 seconds','30 seconds', '45 seconds','60 seconds', '120 seconds']
attribute 'motionDetectionDistance', 'decimal' // changed 05/11/2024 - was 'number'
attribute 'sensitivity', 'number'
attribute 'fadingTime', 'decimal'
attribute 'humanMotionState', 'enum', ['none', 'moving', 'small_move', 'stationary', 'static', 'presence', 'peaceful', 'large_move'] // in use by the obsolete radars
attribute 'illumState', 'enum', ['dark', 'light', 'unknown']
attribute 'ledIndicator', 'number'
attribute 'reportingTime4in1', 'number'
attribute 'ledEnable', 'enum', ['disabled', 'enabled']
attribute 'WARNING', 'string'
// command 'setMotion' is defined in motionLib
// version 3.3.0
command 'sendCommand', [
[name:'command', type: 'STRING', description: 'command name', constraints: ['STRING']],
[name:'val', type: 'STRING', description: 'command parameter value', constraints: ['STRING']]
]
command 'setPar', [
[name:'par', type: 'STRING', description: 'preference parameter name', constraints: ['STRING']],
[name:'val', type: 'STRING', description: 'preference parameter value', constraints: ['STRING']]
]
// itterate through all the figerprints and add them on the fly
deviceProfilesV3.each { profileName, profileMap ->
if (profileMap.fingerprints != null) {
if (profileMap.device?.isDepricated != true) {
profileMap.fingerprints.each {
fingerprint it
}
}
}
}
}
preferences {
if (device) {
if (DEVICE?.device?.isDepricated == true) {
input(name: 'depricated', type: 'hidden', title: "$ttStyleStrThis is the right driver
for use with ${state.deviceProfile} device!
Please change the driver to 'Tuya Zigbee mmWave Sensor' as per the instructions in this link!")
}
else {
input(name: 'info', type: 'hidden', title: "For more info, click on this link to visit the WiKi page")
}
}
input name: 'txtEnable', type: 'bool', title: 'Enable descriptionText logging', defaultValue: true, description: 'Enables events logging.'
input name: 'logEnable', type: 'bool', title: 'Enable debug logging', defaultValue: DEFAULT_DEBUG_LOGGING, description: 'Turns on debug logging for 24 hours.'
if (device) {
if (('DistanceMeasurement' in DEVICE?.capabilities)) { // keep it because of the depricated mmWave sensors
input(name: 'ignoreDistance', type: 'bool', title: 'Ignore distance reports', description: 'If not used, ignore the distance reports received every 1 second!', defaultValue: true)
}
}
input(name: 'allStatusTextEnable', type: 'bool', title: "Enable 'all' Status Attribute Creation?", description: 'Status attribute for Devices/Rooms', defaultValue: false)
// the rest of the preferences are inputIt from the deviceProfileLib and from the included libraries
}
}
@Field static String ttStyleStr = ''
boolean is4in1() { return getDeviceProfile().contains('TS0202_4IN1') }
//boolean is2in1() { return getDeviceProfile().contains('TS0601_2IN1') } defined in the deviceProfileLib, because of a patch!
// based on 'Tuya Multi Sensor 4 In 1' version '1.9.0' '2024/05/06 10:39 AM'
@Field static final Map deviceProfilesV3 = [
// is4in1() // tested _TZ3210_zmy9hjay - OK
'TS0202_4IN1' : [
description : 'Tuya 4in1 (motion/temp/humi/lux) sensor',
models : ['TS0202'], // model: 'ZB003-X' vendor: 'Fantem'
device : [type: 'PIR', isIAS:true, powerSource: 'dc', isSleepy:false], // check powerSource
capabilities : ['MotionSensor': true, 'TemperatureMeasurement': true, 'RelativeHumidityMeasurement': true, 'IlluminanceMeasurement': true, 'tamper': true, 'Battery': true],
preferences : ['motionReset':true, 'illuminanceThreshold':true, 'reportingTime4in1':'102', 'ledEnable':'111', 'keepTime':'0x0500:0xF001', 'sensitivity':'0x0500:0x0013'],
commands : ['reportingTime4in1':'reportingTime4in1', 'resetStats':'resetStats', 'refresh':'refresh', 'initialize':'initialize', 'updateAllPreferences': 'updateAllPreferences', 'resetPreferencesToDefaults':'resetPreferencesToDefaults', 'validateAndFixPreferences':'validateAndFixPreferences', 'printFingerprints':'printFingerprints', 'printPreferences':'printPreferences'],
fingerprints : [
[profileId:'0104', endpointId:'01', inClusters:'0000,0001,0500,EF00', outClusters:'0019,000A', model:'TS0202', manufacturer:'_TZ3210_zmy9hjay', deviceJoinName: 'Tuya TS0202 Multi Sensor 4 In 1'], // pairing: double click!
[profileId:'0104', endpointId:'01', inClusters:'0000,0001,0500,EF00', outClusters:'0019,000A', model:'5j6ifxj', manufacturer:'_TYST11_i5j6ifxj', deviceJoinName: 'Tuya TS0202 Multi Sensor 4 In 1'],
[profileId:'0104', endpointId:'01', inClusters:'0000,0001,0500,EF00', outClusters:'0019,000A', model:'hfcudw5', manufacturer:'_TYST11_7hfcudw5', deviceJoinName: 'Tuya TS0202 Multi Sensor 4 In 1'],
[profileId:'0104', endpointId:'01', inClusters:'0000,0001,0500,EF00', outClusters:'0019,000A', model:'TS0202', manufacturer:'_TZ3210_rxqls8v0', deviceJoinName: 'Tuya TS0202 Multi Sensor 4 In 1'], // not tested
[profileId:'0104', endpointId:'01', inClusters:'0000,0001,0500,EF00', outClusters:'0019,000A', model:'TS0202', manufacturer:'_TZ3210_wuhzzfqg', deviceJoinName: 'Tuya TS0202 Multi Sensor 4 In 1'], // https://community.hubitat.com/t/release-tuya-zigbee-multi-sensor-4-in-1-pir-motion-sensors-and-mmwave-presence-radars/92441/282?u=kkossev
[profileId:'0104', endpointId:'01', inClusters:'0000,0001,0500,EF00', outClusters:'0019,000A', model:'TS0202', manufacturer:'_TZ3210_0aqbrnts', deviceJoinName: 'Tuya TS0202 Multi Sensor 4 In 1 is-thpl-zb']
],
tuyaDPs: [
[dp:1, name:'motion', type:'enum', rw: 'ro', min:0, max:1 , defVal:'0', scale:1, map:[0:'inactive', 1:'active'] , unit:'', description:'Motion'],
[dp:5, name:'tamper', type:'enum', rw: 'ro', min:0, max:1 , defVal:'0', scale:1, map:[0:'clear', 1:'detected'] , unit:'', description:'Tamper detection'],
[dp:25, name:'battery2', type:'number', rw: 'ro', min:0, max:100, defVal:100, scale:1, unit:'%', description:'Remaining battery 2 in %'],
[dp:102, name:'reportingTime4in1', dt:'02', tuyaCmd:04, type:'number', rw: 'rw', min:0, max:240, defVal:10, step:5, scale:1, unit:'minutes', title:'Reporting Interval', description:'Reporting interval in minutes'],
[dp:104, name:'tempCalibration', type:'decimal', rw: 'ro', min:-2.0, max:2.0, defVal:0.0, scale:10, unit:'deg.', title:'Temperature Calibration', description:'Temperature calibration (-2.0...2.0)'],
[dp:105, name:'humiCalibration', type:'number', rw: 'ro', min:-15, max:15, defVal:0, scale:1, unit:'%RH', title:'Huidity Calibration', description:'Humidity Calibration'],
[dp:106, name:'illumCalibration', type:'number', rw: 'ro', min:-20, max:20, defVal:0, scale:1, unit:'Lx', title:'Illuminance Calibration', description:'Illuminance calibration in lux'],
[dp:107, name:'temperature', type:'decimal', rw: 'ro', min:-20.0, max:80.0, defVal:0.0, scale:10, unit:'deg.', description:'Temperature'],
[dp:108, name:'humidity', type:'number', rw: 'ro', min:1, max:100, defVal:100, scale:1, unit:'%RH', description:'Humidity'],
[dp:109, name:'pirSensorEnable', type:'enum', rw: 'ro', min:0, max:1 , defVal:'1', scale:1, map:[0:'disabled', 1:'enabled'] , unit:'', title:'MoPIR Sensor Enable', description:'Enable PIR sensor'],
[dp:110, name:'battery', type:'number', rw: 'ro', min:0, max:100, defVal:100, scale:1, unit:'%', description:'Battery level'],
[dp:111, name:'ledEnable', dt:'01', tuyaCmd:04, type:'enum', rw: 'rw', min:0, max:1 , defVal:'0', scale:1, map:[0:'disabled', 1:'enabled'] , unit:'', title:'LED Enable', description:'Enable LED'],
[dp:112, name:'reportingEnable', type:'enum', rw: 'ro', min:0, max:1 , defVal:'0', scale:1, map:[0:'disabled', 1:'enabled'] , unit:'', title:'Reporting Enable', description:'Enable reporting'],
],
attributes: [
[at:'0x0500:0x0013', name:'sensitivity', type:'enum', rw: 'rw', min:0, max:2, defVal:'2', unit:'', map:[0:'low', 1:'medium', 2:'high'], title:'Sensitivity', description:'PIR sensor sensitivity (update at the time motion is activated)'],
[at:'0x0500:0xF001', name:'keepTime', type:'enum', rw: 'rw', min:0, max:5, defVal:'0', unit:'seconds', map:[0:'0 seconds', 1:'30 seconds', 2:'60 seconds', 3:'120 seconds', 4:'240 seconds', 5:'480 seconds'], title:'Keep Time', description:'PIR keep time in seconds (update at the time motion is activated)']
],
refresh: ['refreshAllIas','sensitivity', 'keepTime', 'refreshFantem'],
configuration : ['battery': false],
],
// tested TS0601 _TZE200_7hfcudw5 - OK
'TS0601_3IN1' : [ // https://szneo.com/en/products/show.php?id=239 // https://www.banggood.com/Tuya-Smart-Linkage-ZB-Motion-Sensor-Human-Infrared-Detector-Mobile-Phone-Remote-Monitoring-PIR-Sensor-p-1858413.html?cur_warehouse=CN
description : 'Tuya 3in1 (Motion/Temp/Humi) sensor',
models : ['TS0601'],
device : [type: 'PIR', powerSource: 'dc', isSleepy: false], // powerSource changes batt/DC dynamically!
capabilities : ['MotionSensor': true, 'TemperatureMeasurement': true, 'RelativeHumidityMeasurement': true, 'tamper': true, 'Battery': true],
preferences : ['motionReset':true],
commands : ['resetStats':'resetStats', 'refresh':'refresh', 'initialize':'initialize', 'updateAllPreferences': 'updateAllPreferences', 'resetPreferencesToDefaults':'resetPreferencesToDefaults', 'validateAndFixPreferences':'validateAndFixPreferences'],
fingerprints : [
[profileId:'0104', endpointId:'01', inClusters:'0000,0004,0005,EF00', outClusters:'0019,000A', model:'TS0601', manufacturer:'_TZE200_7hfcudw5', deviceJoinName: 'Tuya NAS-PD07 Multi Sensor 3 In 1'],
],
tuyaDPs: [
[dp:101, name:'motion', type:'enum', rw: 'ro', min:0, max:1 , defVal:'0', scale:1, map:[0:'inactive', 1:'active'], description:'Motion'],
[dp:102, name:'battery', preProc:'tuyaToBatteryLevel', type:'number', rw: 'ro', min:0, max:100, defVal:100, scale:1, unit:'%', description:'Battery level'],
[dp:103, name:'tamper', type:'enum', rw: 'ro', min:0, max:1 , defVal:'0', scale:1, map:[0:'clear', 1:'detected'] , unit:'', description:'Tamper detection'],
[dp:104, name:'temperature', type:'decimal', rw: 'ro', min:-20.0, max:80.0, defVal:0.0, scale:10, unit:'deg.', description:'Temperature'],
[dp:105, name:'humidity', type:'number', rw: 'ro', min:1, max:100, defVal:100, scale:1, unit:'%RH', description:'Humidity'],
[dp:106, name:'tempScale', type:'enum', rw: 'ro', min:0, max:1 , defVal:'0', scale:1, map:[0:'Celsius', 1:'Fahrenheit'] , unit:'', description:'Temperature scale'],
[dp:107, name:'minTemp', type:'number', rw: 'ro', min:-20, max:80, defVal:0, scale:1, unit:'deg.', description:'Minimal temperature'],
[dp:108, name:'maxTemp', type:'number', rw: 'ro', min:-20, max:80, defVal:0, scale:1, unit:'deg.', description:'Maximal temperature'],
[dp:109, name:'minHumidity', type:'number', rw: 'ro', min:0, max:100, defVal:0, scale:1, unit:'%RH', description:'Minimal humidity'],
[dp:110, name:'maxHumidity', type:'number', rw: 'ro', min:0, max:100, defVal:0, scale:1, unit:'%RH', description:'Maximal humidity'],
[dp:111, name:'tempAlarm', type:'enum', rw: 'ro', min:0, max:1 , defVal:'0', scale:1, map:[0:'inactive', 1:'active'] , unit:'', description:'Temperature alarm'],
[dp:112, name:'humidityAlarm', type:'enum', rw: 'ro', min:0, max:1 , defVal:'0', scale:1, map:[0:'inactive', 1:'active'] , unit:'', description:'Humidity alarm'],
[dp:113, name:'alarmType', type:'enum', rw: 'ro', min:0, max:1 , defVal:'0', scale:1, map:[0:'type0', 1:'type1'] , unit:'', description:'Alarm type'],
],
configuration : ['battery': false]
],
// is2in1() // tested _TZE200_3towulqd - OK (w/ patch!)
'TS0601_2IN1' : [ // https://github.com/Koenkk/zigbee-herdsman-converters/blob/bf32ce2b74689328048b407e56ca936dc7a54a0b/src/devices/tuya.ts#L4568
description : 'Tuya 2in1 (Motion and Illuminance) sensor',
models : ['TS0601'],
device : [type: 'PIR', isIAS:false, powerSource: 'battery', isSleepy:true],
capabilities : ['MotionSensor': true, 'IlluminanceMeasurement': true, 'Battery': true],
preferences : ['motionReset':true, 'invertMotion':true, 'keepTime':'10', 'sensitivity':'9'],
commands : ['resetStats':'resetStats', 'refresh':'refresh', 'initialize':'initialize', 'updateAllPreferences': 'updateAllPreferences', 'resetPreferencesToDefaults':'resetPreferencesToDefaults', 'validateAndFixPreferences':'validateAndFixPreferences'],
fingerprints : [
[profileId:'0104', endpointId:'01', inClusters:'0001,0500,0000', outClusters:'0019,000A', model:'TS0601', manufacturer:'_TZE200_3towulqd', deviceJoinName: 'Tuya 2 in 1 Zigbee Mini PIR Motion Detector + Bright Lux ZG-204ZL'], // https://www.aliexpress.com/item/1005004095233195.html
[profileId:'0104', endpointId:'01', inClusters:'0000,0500,0001,0400', outClusters:'0019,000A', model:'TS0601', manufacturer:'_TZE200_3towulqd', deviceJoinName: 'Tuya 2 in 1 Zigbee Mini PIR Motion Detector + Bright Lux ZG-204ZL'], // https://community.hubitat.com/t/release-tuya-zigbee-multi-sensor-4-in-1-pir-motion-sensors-and-mmwave-presence-radars-w-healthstatus/92441/934?u=kkossev
[profileId:'0104', endpointId:'01', inClusters:'0001,0500,0000', outClusters:'0019,000A', model:'TS0601', manufacturer:'_TZE200_bh3n6gk8', deviceJoinName: 'Tuya 2 in 1 Zigbee Mini PIR Motion Detector + Bright Lux ZG-204ZL'], // https://community.hubitat.com/t/tze200-bh3n6gk8-motion-sensor-not-working/123213?u=kkossev
[profileId:'0104', endpointId:'01', inClusters:'0001,0500,0000', outClusters:'0019,000A', model:'TS0601', manufacturer:'_TZE200_1ibpyhdc', deviceJoinName: 'Tuya 2 in 1 Zigbee Mini PIR Motion Detector + Bright Lux ZG-204ZL'], //
[profileId:'0104', endpointId:'01', inClusters:'0000,0003,0500,EF00,0001,0400', outClusters:'', model:'ZG-204ZL', manufacturer:'HOBEIAN', deviceJoinName: 'HOBEIAN ZG-204ZL 2 in 1 PIR Motion Detector and Lux sensor'], // https://community.hubitat.com/t/release-tuya-zigbee-multi-sensor-4-in-1-pir-motion-sensors-w-healthstatus/92441/1153?u=kkossev
[profileId:'0104', endpointId:'01', inClusters:'0000,0003,0500,0001,0400', outClusters:'', model:'ZG-204ZL', manufacturer:'HOBEIAN', deviceJoinName: 'HOBEIAN ZG-204ZL 2 in 1 PIR Motion Detector and Lux sensor'] // https://github.com/JohanBendz/com.tuya.zigbee/issues/1267
],
tuyaDPs: [
[dp:1, name:'motion', /*preProc:'invert',*/ type:'enum', rw: 'ro', min:0, max:1 , defVal:'0', scale:1, map:[0:'inactive', 1:'active'] , unit:'', description:'Motion'],
[dp:4, name:'battery', type:'number', rw: 'ro', min:0, max:100, defVal:100, scale:1, unit:'%', title:'Battery level', description:'Battery level'],
[dp:9, name:'sensitivity', type:'enum', rw: 'rw', min:0, max:2, defVal:'2', unit:'', map:[0:'0 - low', 1:'1 - medium', 2:'2 - high'], title:'Sensitivity', description:'PIR sensor sensitivity (update at the time motion is activated)'],
[dp:10, name:'keepTime', type:'enum', rw: 'rw', min:0, max:3, defVal:'0', unit:'seconds', map:[0:'10 seconds', 1:'30 seconds', 2:'60 seconds', 3:'120 seconds'], title:'Keep Time', description:'PIR keep time in seconds (update at the time motion is activated)'],
[dp:12, name:'illuminance', type:'number', rw: 'ro', min:0, max:1000, defVal:0, scale:1, unit:'lx', title:'illuminance', description:'illuminance'],
[dp:102, name:'illuminance_interval', type:'number', rw: 'rw', min:1, max:720, defVal:1, scale:1, unit:'minutes', title:'Illuminance Interval', description:'Brightness acquisition interval (update at the time motion is activated)'],
],
configuration : ['battery': false]
],
// https://community.hubitat.com/t/release-tuya-zigbee-multi-sensor-4-in-1-pir-motion-sensors-w-healthstatus/92441/1080?u=kkossev
'TS0601_2IN1_MYQ_ZMS03' : [ //https://github.com/protyposis/zigbee-herdsman-converters/blob/c9b8f3172cb11ea0ca36440f8956eda582182df7/src/devices/tuya.ts#L4750
description : 'Tuya 2in1 (Motion and Illuminance) MYQ_ZMS03 sensor',
models : ['TS0601'],
device : [type: 'PIR', isIAS:false, powerSource: 'dc', isSleepy:false],
capabilities : ['MotionSensor': true, 'IlluminanceMeasurement': true, 'Battery': true],
preferences : ['motionReset':true, 'invertMotion':true],
commands : ['resetStats':'resetStats', 'refresh':'refresh'],
fingerprints : [
[profileId:'0104', endpointId:'01', inClusters:'0004,0005,EF00,0000', outClusters:'0019,000A', model:'TS0601', manufacturer:'_TZE200_f1pvdgoh', deviceJoinName: 'Tuya MYQ_ZMS03 Multi Sensor 2 in 1'], // https://s.click.aliexpress.com/e/_DdNVVZx
[profileId:'0104', endpointId:'01', inClusters:'0004,0005,EF00,0000', outClusters:'0019,000A', model:'TS0601', manufacturer:'_TZE200_ppuj1vem', deviceJoinName: 'Treatlife human presence sensor 2 in 1'] // https://github.com/benedicttobias/zigbee-herdsman-converters/blob/ddaad14c80cadb2bff8d314ed8128139958ee02a/src/devices/tuya.ts#L12117-L12133
// ^^^^ this return the opposite value. presence is 'false' when motion is detected!
],
tuyaDPs: [
[dp:1, name:'motion', type:'enum', rw: 'ro', min:0, max:1 , defVal:'0', scale:1, map:[0:'inactive', 1:'active'] , unit:'', description:'Motion'],
[dp:4, name:'battery', type:'number', rw: 'ro', min:0, max:100, defVal:100, scale:1, unit:'%', title:'Battery level', description:'Battery level'],
[dp:101, name:'illuminance', type:'number', rw: 'ro', min:0, max:1000, defVal:0, scale:1, unit:'lx', title:'illuminance', description:'illuminance'],
],
refresh: ['queryAllTuyaDP'],
],
'RH3040_TUYATEC' : [ // testing TUYATEC-53o41joc // non-configurable
description : 'TuyaTec RH3040 Motion sensor (IAS)',
models : ['RH3040'],
device : [type: 'PIR', isIAS:true, powerSource: 'battery', isSleepy:true],
capabilities : ['MotionSensor': true, 'Battery': true],
preferences : ['motionReset':false, 'keepTime':false, 'sensitivity':false],
fingerprints : [
[profileId:'0104', endpointId:'01', inClusters:'0000,0001,0003,0500', model:'RH3040', manufacturer:'TUYATEC-53o41joc', deviceJoinName: 'TUYATEC RH3040 Motion Sensor'], // KK - 60 seconds reset period
[profileId:'0104', endpointId:'01', inClusters:'0000,0001,0003,0500', model:'RH3040', manufacturer:'TUYATEC-b5g40alm', deviceJoinName: 'TUYATEC RH3040 Motion Sensor'],
[profileId:'0104', endpointId:'01', inClusters:'0000,0001,0003,0500', model:'RH3040', manufacturer:'TUYATEC-deetibst', deviceJoinName: 'TUYATEC RH3040 Motion Sensor'],
[profileId:'0104', endpointId:'01', inClusters:'0000,0001,0003,0500', model:'RH3040', manufacturer:'TUYATEC-bd5faf9p', deviceJoinName: 'Nedis/Samotech RH3040 Motion Sensor'],
[profileId:'0104', endpointId:'01', inClusters:'0000,0001,0003,0500', model:'RH3040', manufacturer:'TUYATEC-zn9wyqtr', deviceJoinName: 'Samotech RH3040 Motion Sensor'], // vendor: 'Samotech', model: 'SM301Z'
[profileId:'0104', endpointId:'01', inClusters:'0000,0001,0003,0500', model:'RH3040', manufacturer:'TUYATEC-b3ov3nor', deviceJoinName: 'Zemismart RH3040 Motion Sensor'], // vendor: 'Nedis', model: 'ZBSM10WT'
[profileId:'0104', endpointId:'01', inClusters:'0000,0001,0003,0500', model:'RH3040', manufacturer:'TUYATEC-2gn2zf9e', deviceJoinName: 'TUYATEC RH3040 Motion Sensor']
],
deviceJoinName: 'TuyaTec RH3040 Motion sensor (IAS)',
configuration : ['battery': false]
],
'TS0202_MOTION_IAS' : [ // non-configurable
description : 'Tuya TS0202 Motion sensor (IAS)',
models : ['TS0202', 'RH3040'],
device : [type: 'PIR', isIAS:true, powerSource: 'battery', isSleepy:true],
capabilities : ['MotionSensor': true, 'Battery': true],
preferences : ['motionReset':true, 'keepTime':false, 'sensitivity':false],
fingerprints : [
[profileId:'0104', endpointId:'01', inClusters:'0000,0003,0001,0500', outClusters:'0000,0003,0001,0500', model:'TS0202', manufacturer:'_TYZB01_dl7cejts', deviceJoinName: 'Tuya TS0202 Motion Sensor'], // KK model: 'ZM-RT201'// 5 seconds (!) reset period for testing
[profileId:'0104', endpointId:'01', inClusters:'0001,0500,0003,0000', outClusters:'1000,0006,0019,000A', model:'TS0202', manufacturer:'_TZ3000_mmtwjmaq', deviceJoinName: 'Tuya TS0202 Motion Sensor'],
[profileId:'0104', endpointId:'01', inClusters:'0001,0500,0003,0000', outClusters:'1000,0006,0019,000A', model:'TS0202', manufacturer:'_TZ3000_otvn3lne', deviceJoinName: 'Tuya TS0202 Motion Sensor'],
[profileId:'0104', endpointId:'01', inClusters:'0001,0500,0003,0000', outClusters:'1000,0006,0019,000A', model:'TS0202', manufacturer:'_TYZB01_jytabjkb', deviceJoinName: 'Tuya TS0202 Motion Sensor'],
[profileId:'0104', endpointId:'01', inClusters:'0001,0500,0003,0000', outClusters:'1000,0006,0019,000A', model:'TS0202', manufacturer:'_TYZB01_ef5xlc9q', deviceJoinName: 'Tuya TS0202 Motion Sensor'],
[profileId:'0104', endpointId:'01', inClusters:'0001,0500,0003,0000', outClusters:'1000,0006,0019,000A', model:'TS0202', manufacturer:'_TYZB01_vwqnz1sn', deviceJoinName: 'Tuya TS0202 Motion Sensor'],
[profileId:'0104', endpointId:'01', inClusters:'0001,0500,0003,0000', outClusters:'1000,0006,0019,000A', model:'TS0202', manufacturer:'_TYZB01_2b8f6cio', deviceJoinName: 'Tuya TS0202 Motion Sensor'],
[profileId:'0104', endpointId:'01', inClusters:'0001,0500,0003,0000', outClusters:'1000,0006,0019,000A', model:'TS0202', manufacturer:'_TYZB01_vwqnz1sn', deviceJoinName: 'Tuya TS0202 Motion Sensor'], // https://community.hubitat.com/t/moes-tuya-motion-sensor-distance-issue-ts0202-have-to-be-ridiculously-close-to-detect-movement/109917/8?u=kkossev
[profileId:'0104', endpointId:'01', inClusters:'0001,0500,0003,0000', outClusters:'1000,0006,0019,000A', model:'TS0202', manufacturer:'_TZE200_bq5c8xfe', deviceJoinName: 'Tuya TS0202 Motion Sensor'],
[profileId:'0104', endpointId:'01', inClusters:'0001,0500,0003,0000', outClusters:'1000,0006,0019,000A', model:'TS0202', manufacturer:'_TYZB01_qjqgmqxr', deviceJoinName: 'Tuya TS0202 Motion Sensor'],
[profileId:'0104', endpointId:'01', inClusters:'0001,0500,0003,0000', outClusters:'1000,0006,0019,000A', model:'TS0202', manufacturer:'_TYZB01_zwvaj5wy', deviceJoinName: 'Tuya TS0202 Motion Sensor'],
[profileId:'0104', endpointId:'01', inClusters:'0001,0500,0003,0000', outClusters:'1000,0006,0019,000A', model:'TS0202', manufacturer:'_TZ3000_bsvqrxru', deviceJoinName: 'Tuya TS0202 Motion Sensor'],
[profileId:'0104', endpointId:'01', inClusters:'0001,0500,0003,0000', outClusters:'1000,0006,0019,000A', model:'TS0202', manufacturer:'_TYZB01_tv3wxhcz', deviceJoinName: 'Tuya TS0202 Motion Sensor'],
[profileId:'0104', endpointId:'01', inClusters:'0001,0500,0003,0000', outClusters:'1000,0006,0019,000A', model:'TS0202', manufacturer:'_TYZB01_hqbdru35', deviceJoinName: 'Tuya TS0202 Motion Sensor'],
[profileId:'0104', endpointId:'01', inClusters:'0001,0500,0003,0000', outClusters:'1000,0006,0019,000A', model:'TS0202', manufacturer:'_TZ3000_tiwq83wk', deviceJoinName: 'Tuya TS0202 Motion Sensor'],
[profileId:'0104', endpointId:'01', inClusters:'0001,0500,0003,0000', outClusters:'1000,0006,0019,000A', model:'TS0202', manufacturer:'_TZ3000_ykwcwxmz', deviceJoinName: 'Tuya TS0202 Motion Sensor'],
[profileId:'0104', endpointId:'01', inClusters:'0001,0500,0003,0000', outClusters:'1000,0006,0019,000A', model:'TS0202', manufacturer:'_TZ3000_hgu1dlak', deviceJoinName: 'Tuya TS0202 Motion Sensor'],
[profileId:'0104', endpointId:'01', inClusters:'0001,0500,0003,0000', outClusters:'1000,0006,0019,000A', model:'TS0202', manufacturer:'_TZ3000_hktqahrq', deviceJoinName: 'Tuya TS0202 Motion Sensor'],
[profileId:'0104', endpointId:'01', inClusters:'0001,0500,0003,0000', outClusters:'1000,0006,0019,000A', model:'TS0202', manufacturer:'_TZ3000_jmrgyl7o', deviceJoinName: 'Tuya TS0202 Motion Sensor'], // not tested! //https://zigbee.blakadder.com/Luminea_ZX-5311.html
[profileId:'0104', endpointId:'01', inClusters:'0001,0500,0003,0000', outClusters:'1000,0006,0019,000A', model:'WHD02', manufacturer:'_TZ3000_kmh5qpmb', deviceJoinName: 'Tuya TS0202 Motion Sensor'],
[profileId:'0104', endpointId:'01', inClusters:'0001,0500,0003,0000', outClusters:'1000,0006,0019,000A', model:'TS0202', manufacturer:'_TZ3000_kmh5qpmb', deviceJoinName: 'Tuya TS0202 Motion Sensor'], // https://community.hubitat.com/t/release-tuya-zigbee-multi-sensor-4-in-1-pir-motion-sensors-w-healthstatus/92441/1059?u=kkossev
[profileId:'0104', endpointId:'01', inClusters:'0001,0500,0003,0000', outClusters:'1000,0006,0019,000A', model:'TS0202', manufacturer:'_TZ3040_usvkzkyn', deviceJoinName: 'Tuya TS0202 Motion Sensor'], // not tested // https://www.amazon.ae/Rechargeable-Detector-Security-Devices-Required/dp/B0BKKJ48QH
[profileId:'0104', endpointId:'01', inClusters:'0000,0001,0003,0500,0B05', outClusters:'0019', model:'TY0202', manufacturer:'_TZ1800_fcdjzz3s', deviceJoinName: 'Lidl TY0202 Motion Sensor'],
[profileId:'0104', endpointId:'01', inClusters:'0000,0001,0003,0500,0B05,FCC0', outClusters:'0019,FCC0', model:'TY0202', manufacturer:'_TZ3000_4ggd8ezp', deviceJoinName: 'Bond motion sensor ZX-BS-J11W'], // https://community.hubitat.com/t/what-driver-to-use-for-this-motion-sensor-zx-bs-j11w-or-ty0202/103953/4
[profileId:'0104', endpointId:'01', inClusters:'0001,0003,0004,0500,0000', outClusters:'0004,0006,1000,0019,000A', model:'TS0202', manufacturer:'_TZ3040_bb6xaihh', deviceJoinName: 'Tuya TS0202 Motion Sensor'], // https://github.com/Koenkk/zigbee2mqtt/issues/17364
[profileId:'0104', endpointId:'01', inClusters:'0001,0003,0004,0500,0000', outClusters:'0004,0006,1000,0019,000A', model:'TS0202', manufacturer:'_TZ3040_wqmtjsyk', deviceJoinName: 'Tuya TS0202 Motion Sensor'], // not tested
[profileId:'0104', endpointId:'01', inClusters:'0001,0003,0004,0500,0000', outClusters:'0004,0006,1000,0019,000A', model:'TS0202', manufacturer:'_TZ3000_h4wnrtck', deviceJoinName: 'Tuya TS0202 Motion Sensor'] // not tested
],
configuration : ['battery': false]
],
'TS0202_MOTION_IAS_CONFIGURABLE' : [
description : 'Tuya TS0202 Motion sensor (IAS) configurable',
models : ['TS0202'],
device : [type: 'PIR', isIAS:true, powerSource: 'battery', isSleepy:true],
capabilities : ['MotionSensor': true, 'Battery': true],
preferences : ['motionReset':true, 'keepTime':'0x0500:0xF001', 'sensitivity':'0x0500:0x0013'],
fingerprints : [
[profileId:'0104', endpointId:'01', inClusters:'0001,0500,0003,0000', outClusters:'1000,0006,0019,000A', model:'TS0202', manufacturer:'_TZ3000_mcxw5ehu', deviceJoinName: 'Tuya TS0202 ZM-35H-Q Motion Sensor'], // TODO: PIR sensor sensitivity and PIR keep time in seconds ['30', '60', '120']
[profileId:'0104', endpointId:'01', inClusters:'0001,0500,0003,0000', outClusters:'1000,0006,0019,000A', model:'TS0202', manufacturer:'_TZ3000_msl6wxk9', deviceJoinName: 'Tuya TS0202 ZM-35H-Q Motion Sensor'], // TODO: fz.ZM35HQ_attr ['30', '60', '120']
[profileId:'0104', endpointId:'01', inClusters:'0001,0500,0003,0000', outClusters:'1000,0006,0019,000A', model:'TS0202', manufacturer:'_TZ3040_msl6wxk9', deviceJoinName: 'Tuya TS0202 ZM-35H-Q Motion Sensor'],
[profileId:'0104', endpointId:'01', inClusters:'0001,0500,0003,0000', outClusters:'1000,0006,0019,000A', model:'TS0202', manufacturer:'_TZ3040_fwxuzcf4', deviceJoinName: 'Tuya TS0202 ZM-35H-Q Motion Sensor'],
[profileId:'0104', endpointId:'01', inClusters:'0001,0500,0003,0000', outClusters:'1000,0006,0019,000A', model:'TS0202', manufacturer:'_TZ3000_6ygjfyll', deviceJoinName: 'Tuya TS0202 Motion Sensor'], // https://community.hubitat.com/t/release-tuya-zigbee-multi-sensor-4-in-1-pir-motion-sensors-and-mmwave-presence-radars/92441/289?u=kkossev
[profileId:'0104', endpointId:'01', inClusters:'0001,0500,0003,0000', outClusters:'1000,0006,0019,000A', model:'TS0202', manufacturer:'_TZ3040_6ygjfyll', deviceJoinName: 'Tuya TS0202 Motion Sensor'], // https://community.hubitat.com/t/tuya-motion-sensor-driver/72000/54?u=kkossev
],
attributes: [
[at:'0x0500:0x0013', name:'sensitivity', type:'enum', rw: 'rw', min:0, max:2, defVal:'2', unit:'', map:[0:'low', 1:'medium', 2:'high'], title:'Sensitivity', description:'PIR sensor sensitivity (update at the time motion is activated)'],
[at:'0x0500:0xF001', name:'keepTime', type:'enum', rw: 'rw', min:0, max:2, defVal:'0', unit:'seconds', map:[0:'30 seconds', 1:'60 seconds', 2:'120 seconds'], title:'Keep Time', description:'PIR keep time in seconds (update at the time motion is activated)'],
],
configuration : ['battery': false]
],
// isMotionSwitch()
'TS0202_MOTION_SWITCH': [
description : 'Tuya Motion Sensor and Scene Switch',
models : ['TS0202'],
device : [type: 'PIR', isIAS:true, powerSource: 'battery', isSleepy:true],
capabilities : ['MotionSensor':true, 'IlluminanceMeasurement':true, 'switch':true, 'Battery':true],
preferences : ['motionReset':true, 'keepTime':false, 'sensitivity':false, 'luxThreshold':false], // keepTime is hardcoded 60 seconds, no sensitivity configuration
fingerprints : [
[profileId:'0104', endpointId:'01', inClusters:'0001,0500,EF00,0000', outClusters:'0019,000A', model:'TS0202', manufacturer:'_TZ3210_cwamkvua', deviceJoinName: 'Tuya Motion Sensor and Scene Switch'] // vendor: 'Linkoze', model: 'LKMSZ001'
],
tuyaDPs: [
[dp:101, name:'pushed', type:'enum', rw: 'ro', min:0, max:2, defVal:'0', scale:1, map:[0:'pushed', 1:'doubleTapped', 2:'held'] , unit:'', title:'Presence state', description:'Presence state'],
[dp:102, name:'illuminance', type:'number', rw: 'ro', min:0, max:1, defVal:0, scale:1, unit:'lx', title:'illuminance', description:'illuminance'],
],
configuration : ['battery': false]
],
'TS0601_PIR_PRESENCE' : [ // isBlackPIRsensor() // https://github.com/zigpy/zha-device-handlers/issues/1618
description : 'Tuya PIR Human Motion Sensor (Black)',
models : ['TS0601'],
device : [type: 'radar', powerSource: 'dc', isSleepy:false],
capabilities : ['MotionSensor': true, 'Battery': true],
preferences : ['fadingTime':'102', 'distance':'105'],
commands : ['resetStats':'resetStats', 'resetPreferencesToDefaults':'resetPreferencesToDefaults'],
fingerprints : [
[profileId:'0104', endpointId:'01', inClusters:'0004,0005,EF00,0000', outClusters:'0019,000A', model:'TS0601', manufacturer:'_TZE200_9qayzqa8', deviceJoinName: 'Smart PIR Human Motion Sensor (Black)'] // https://www.aliexpress.com/item/1005004296422003.html
],
tuyaDPs: [ // TODO - defaults !!
[dp:102, name:'fadingTime', type:'number', rw: 'rw', min:24, max:300 , defVal:24, scale:1, unit:'seconds', title:'Fading time', description:'Fading(Induction) time'],
[dp:105, name:'distance', type:'enum', rw: 'rw', min:0, max:9 , defVal:'6', scale:1, map:[0:'0.5 m', 1:'1.0 m', 2:'1.5 m', 3:'2.0 m', 4:'2.5 m', 5:'3.0 m', 6:'3.5 m', 7:'4.0 m', 8:'4.5 m', 9:'5.0 m'] , unit:'meters', title:'Target Distance', description:'Target Distance'],
[dp:119, name:'motion', type:'enum', rw: 'ro', min:0, max:1 , defVal:'0', scale:1, map:[0:'inactive', 1:'active'] , unit:'', title:'Presence state', description:'Presence state'],
[dp:141, name:'humanMotionState', type:'enum', rw: 'ro', min:0, max:4 , defVal:'0', scale:1, map:[0:'none', 1:'presence', 2:'peaceful', 3:'small_move', 4:'large_move'] , unit:'', title:'Presence state', description:'Presence state'],
],
configuration : ['battery': false]
],
'TS0601_PIR_AIR' : [ // isHumanPresenceSensorAIR() - Human presence sensor AIR (PIR sensor!) - o_sensitivity, v_sensitivity, led_status, vacancy_delay, light_on_luminance_prefer, light_off_luminance_prefer, mode, luminance_level, reference_luminance, vacant_confirm_time
description : 'Tuya PIR Human Motion Sensor AIR',
models : ['TS0601'],
device : [type: 'radar', powerSource: 'dc', isSleepy:false],
capabilities : ['MotionSensor': true, 'IlluminanceMeasurement': true, 'Battery': true], // TODO - check if battery powered?
preferences : ['vacancyDelay':'103', 'ledStatusAIR':'110', 'detectionMode':'104', 'vSensitivity':'101', 'oSensitivity':'102', 'lightOnLuminance':'107', 'lightOffLuminance':'108' ],
commands : ['resetStats':'resetStats'],
fingerprints : [
[profileId:'0104', endpointId:'01', inClusters:'0000,0004,0005,EF00', outClusters:'0019,000A', model:'TS0601', manufacturer:'_TZE200_auin8mzr', deviceJoinName: 'Tuya PIR Human Motion Sensor AIR'] // Tuya LY-TAD-K616S-ZB
],
tuyaDPs: [ // TODO - defaults !!
[dp:101, name:'vSensitivity', type:'enum', rw: 'rw', min:0, max:1, defVal:'0', scale:1, map:[0:'Speed Priority', 1:'Standard', 2:'Accuracy Priority'] , unit:'-', title:'vSensitivity options', description:'V-Sensitivity mode'],
[dp:102, name:'oSensitivity', type:'enum', rw: 'rw', min:0, max:1, defVal:'0', scale:1, map:[0:'Sensitive', 1:'Normal', 2:'Cautious'] , unit:'', title:'oSensitivity options', description:'O-Sensitivity mode'],
[dp:103, name:'vacancyDelay', type:'number', rw: 'rw', min:0, max:1000, defVal:10, scale:1, unit:'seconds', title:'Vacancy Delay', description:'Vacancy Delay'],
[dp:104, name:'detectionMode', type:'enum', rw: 'rw', min:0, max:1 , defVal:'0', scale:1, map:[0:'General Model', 1:'Temporary Stay', 2:'Basic Detecton', 3:'PIR Sensor Test'] , unit:'', title:'Detection Mode', description:'Detection Mode'],
[dp:105, name:'unacknowledgedTime', type:'number', rw: 'ro', min:0, max:9 , defVal:7, scale:1, unit:'seconds', description:'unacknowledgedTime'],
[dp:106, name:'illuminance', type:'number', rw: 'ro', min:0, max:2000, defVal:0, scale:1, unit:'lx', title:'illuminance', description:'illuminance'],
[dp:107, name:'lightOnLuminance', type:'number', rw: 'rw', min:0, max:2000, defVal:0, scale:1, unit:'lx', title:'lightOnLuminance', description:'lightOnLuminance'], // Ligter, Medium, ... ?// TODO =- check range 0 - 10000 ?
[dp:108, name:'lightOffLuminance', type:'number', rw: 'rw', min:0, max:2000, defVal:0, scale:1, unit:'lx', title:'lightOffLuminance', description:'lightOffLuminance'],
[dp:109, name:'luminanceLevel', type:'number', rw: 'ro', min:0, max:2000, defVal:0, scale:1, unit:'lx', title:'luminanceLevel', description:'luminanceLevel'], // Ligter, Medium, ... ?
[dp:110, name:'ledStatusAIR', type:'enum', rw: 'rw', min:0, max:1 , defVal:'0', scale:1, map:[0: 'Switch On', 1:'Switch Off', 2: 'Default'] , unit:'', title:'LED status', description:'Led status switch'],
],
configuration : ['battery': false]
],
'SONOFF_MOTION_IAS' : [
description : 'Sonoff/eWeLink Motion sensor',
models : ['SNZB-03', 'MS01', 'msO1', 'SQ510A', 'RHK09', '66666'],
device : [type: 'PIR', isIAS:true, powerSource: 'battery', isSleepy:true], // very sleepy !!
capabilities : ['MotionSensor': true, 'Battery': true],
preferences : ['motionReset':false/*, 'keepTime':false, 'sensitivity':false*/], // just enable or disable showing the motionReset preference, no link to tuyaDPs or attributes map!
fingerprints : [
[profileId:'0104', endpointId:'01', inClusters:'0000,0003,0500,0001', outClusters:'0003', model:'SNZB-03', manufacturer:'eWeLink', deviceJoinName: 'eWeLink Motion Sensor'], // SNZB-O3 OUVOPO Wireless Motion Sensor (2023)
[profileId:'0104', endpointId:'01', inClusters:'0000,0003,0500,0001,0020', outClusters:'0003', model:'SNZB-03', manufacturer:'eWeLink', deviceJoinName: 'eWeLink Motion Sensor'], //
[profileId:'0104', endpointId:'01', inClusters:'0000,0003,0500,0001', outClusters:'0003', model:'RHK09', manufacturer:'eWeLink', deviceJoinName: 'eWeLink Motion Sensor'], //
[profileId:'0104', endpointId:'01', inClusters:'0000,0003,0500,0001,0020', outClusters:'0003', model:'RHK09', manufacturer:'eWeLink', deviceJoinName: 'eWeLink Motion Sensor'], //
[profileId:'0104', endpointId:'01', inClusters:'0000,0003,0500,0001', outClusters:'0003', model:'SQ510A', manufacturer:'eWeLink', deviceJoinName: 'eWeLink Motion Sensor'], //
[profileId:'0104', endpointId:'01', inClusters:'0000,0003,0500,0001,0020', outClusters:'0003', model:'SQ510A', manufacturer:'eWeLink', deviceJoinName: 'eWeLink Motion Sensor'], //
[profileId:'0104', endpointId:'01', inClusters:'0000,0003,0500,0001', outClusters:'0003', model:'ms01', manufacturer:'eWeLink', deviceJoinName: 'eWeLink Motion Sensor'], // for testL 60 seconds re-triggering period!
[profileId:'0104', endpointId:'01', inClusters:'0000,0003,0500,0001', outClusters:'0003', model:'msO1', manufacturer:'eWeLink', deviceJoinName: 'eWeLink Motion Sensor'], // second variant
[profileId:'0104', endpointId:'01', inClusters:'0000,0003,0500,0001', outClusters:'0003', model:'MS01', manufacturer:'eWeLink', deviceJoinName: 'eWeLink Motion Sensor'], // third variant
[profileId:'0104', endpointId:'01', inClusters:'0000,0003,0500,0001', outClusters:'0003', model:'66666', manufacturer:'eWeLink', deviceJoinName: 'eWeLink Motion Sensor'], // fourth variant
],
refresh : [ 'batteryRefresh'],
configuration : [
'0x0001':['bind':true, 'voltageReporting':[3600, 7200, 0x02], 'batteryReporting':[3600, 7200, 0x02] ],
'0x0500':['bind':false, 'sensitivity':false, 'keepTime':false], // TODO - use in update function
], // battery percentage, min 3600, max 7200, UINT8, delta 2
],
'SONOFF_SNZB_03P' : [ // https://github.com/Koenkk/zigbee-herdsman-converters/blob/master/src/devices/sonoff.ts#L1131-L1157
description : 'Sonoff SNZB-03P Motion sensor', // https://community.hubitat.com/t/new-sonoff-snzb-03p-motion-sensors-not-detecting/141138/17?u=kkossev
models : ['SNZB-03P'], // https://community.hubitat.com/t/sonoff-ewelink-snzb-03p-motion-sensor-doesnt-reporting-battery-level/150726/3?u=kkossev
device : [type: 'PIR', isIAS:true, powerSource: 'battery', isSleepy:true],
capabilities : ['MotionSensor': true, 'Battery': true],
preferences : ['motionReset':true, 'sensitivity':false, 'keepTime':'0x0406:0x0020', 'refreshOnSave':true],
fingerprints : [
[profileId:'0104', endpointId:'01', inClusters:'0000,0001,0003,0020,0406,0500,FC57', outClusters:'0003,0019', model:'SNZB-03P', manufacturer:'eWeLink', deviceJoinName: 'SONOFF SNZB-03P Motion Sensor'] // https://community.hubitat.com/t/new-sonoff-snzb-03p-motion-sensors-not-detecting/141138?u=kkossev
],
attributes: [
[at:'0x0406:0x0020', name:'keepTime', type:'enum', dt: '0x21', rw: 'rw', min:5, max:60, defVal:'30', scale:1, unit:'seconds', map:[5:'5 seconds', 10:'10 seconds', 15:'15 seconds', 30:'30 seconds', 45:'45 seconds', 60:'60 seconds'], title:'Fading Time', description:'Fading time in seconds'],
[at:'0xFC11:0x2001', name:'illumState', type:'enum', dt: '0x20', mfgCode: '0x1286', rw: 'ro', min:0, max:2, defVal:2, scale:1, unit:'', map:[0:'dark', 1:'light', 2:'unknown'], title:'Illuminance State', description:'Illuminance State'] // manufacturerCode: 0x1286
],
refresh : [ 'batteryRefresh', 'keepTime', 'illumState'],
configuration : [
'0x0001':['bind':true, 'voltageReporting':[3600, 7200, 0x02], 'batteryReporting':[3600, 7200, 0x02] ],
'0x0500':['bind':false, 'sensitivity':false, 'keepTime':false], // TODO - use in update function
],
],
// isSiHAS() and Sunricher
'SIHAS_USM-300Z_4_IN_1' : [
description : 'SiHAS USM-300Z 4-in-1',
models : ['ShinaSystem'],
device : [type: 'PIR', powerSource: 'battery', isIAS:false, isSleepy:false],
capabilities : ['MotionSensor': true, 'TemperatureMeasurement': true, 'RelativeHumidityMeasurement': true, 'IlluminanceMeasurement': true, 'Battery': true],
preferences : [:],
fingerprints : [
[profileId:'0104', endpointId:'01', inClusters:'0000,0400,0003,0406,0402,0001,0405,0500', outClusters:'0004,0003,0019', model:'USM-300Z', manufacturer:'ShinaSystem', deviceJoinName: 'SiHAS MultiPurpose Sensor'],
[profileId:'0104', endpointId:'01', inClusters:'0000,0001,0003,0009,0400,0402,0405,0406,0500', outClusters:'0019', model:'HK-SENSOR-4IN1-A', manufacturer:'Sunricher', deviceJoinName: 'Azoula Zigbee 4 in 1 Multi Sensor'] // https://community.hubitat.com/t/what-driver-for-this-4-1/145847?u=kkossev
],
commands : ['resetStats':'resetStats', 'refresh':'refresh', 'initialize':'initialize', 'updateAllPreferences': 'updateAllPreferences', 'resetPreferencesToDefaults':'resetPreferencesToDefaults', 'validateAndFixPreferences':'validateAndFixPreferences'],
//tuyaDPs : [:],
attributes : [
[at:'0x0406:0x0000', name:'motion', type:'enum', rw: 'ro', min:0, max:1, defVal:'0', scale:1, map:[0:'inactive', 1:'active'], title:'Presence', description:'Presence state']
],
refresh : [ 'batteryRefresh', 'illuminanceRefresh', 'temperatureRefresh', 'humidityRefresh', 'motion'],
//configuration : ["0x0406":"bind"] // TODO !!
configuration : [:],
],
'ESRESSIF_PIR_TEMP' : [
description : 'Espressif motion and temp sensor',
models : ['ZigbeeOccupancyPIRSensor'],
device : [type: 'PIR', powerSource: 'dc', isIAS:false, isSleepy:false],
capabilities : ['MotionSensor': true, 'TemperatureMeasurement': true, 'Battery': false],
preferences : [:],
fingerprints : [
[profileId:'0104', endpointId:'0A', inClusters:'0000,0003,0406', outClusters:'0019,000A', model:'ZigbeeOccupancyPIRSensor', manufacturer:'Espressif', deviceJoinName: 'Espressif ZigbeeOccupancyPIRSensor'],
// endpoint 0B: inClusters: 0000,0003,0402,000A, outClusters: 0003,000A
],
commands : ['resetStats':'resetStats', 'refresh':'refresh', 'initialize':'initialize', 'updateAllPreferences': 'updateAllPreferences', 'resetPreferencesToDefaults':'resetPreferencesToDefaults'],
attributes : [
[at:'0x0406:0x0000', name:'motion', type:'enum', rw: 'ro', min:0, max:1, defVal:'0', scale:1, map:[0:'inactive', 1:'active'], title:'Motion'],
[at:'0x0402:0x0000', name:'temperature', type:'decimal', rw: 'ro', min:-20.0, max:80.0, defVal:0.0, scale:10, unit:'deg.', description:'Temperature']
],
refresh : ['motion', 'temperatureRefresh'],
configuration : ['custom':'configureEspressif'],
],
'IMOU_MOTION_ZP1' : [
description : 'IMOU Motion Sensor ZP1',
models : ['ZP2-EN', 'ZP1-EN'],
device : [type: 'PIR', isIAS:true, powerSource: 'battery', isSleepy:true],
capabilities : ['MotionSensor': true, 'TemperatureMeasurement': false, 'RelativeHumidityMeasurement': false, 'IlluminanceMeasurement': false, 'Battery': true],
preferences : ['motionReset':true, /*'keepTime':'0x0500:0xF001', 'sensitivity':'0x0500:0x0013'*/],
fingerprints : [
[profileId:'0104', endpointId:'01', inClusters:'0000,0001,0003,0500,0B05', outClusters:'0003', model:'ZP2-EN', manufacturer:'MultIR', deviceJoinName: 'IMOU Motion Sensor ZP1'],
[profileId:'0104', endpointId:'01', inClusters:'0000,0001,0003,0500,0B05', outClusters:'0003', model:'ZP1-EN', manufacturer:'MultIR', deviceJoinName: 'IMOU Motion Sensor ZP1']
],
attributes: [/*
[at:'0x0500:0x0013', name:'sensitivity', type:'enum', rw: 'rw', min:0, max:2, defVal:'2', unit:'', map:[0:'low', 1:'medium', 2:'high'], title:'Sensitivity', description:'PIR sensor sensitivity (update at the time motion is activated)'],
[at:'0x0500:0xF001', name:'keepTime', type:'enum', rw: 'rw', min:0, max:2, defVal:'0', unit:'seconds', map:[0:'30 seconds', 1:'60 seconds', 2:'120 seconds'], title:'Keep Time', description:'PIR keep time in seconds (update at the time motion is activated)'],
*/
],
configuration : ['battery': false]
// https://github.com/Koenkk/zigbee-herdsman-converters/blob/master/src/devices/imou.ts
],
'NONTUYA_MOTION_IAS' : [
description : 'Other OEM Motion sensors (IAS)',
models : ['MOT003', 'XXX'],
device : [type: 'PIR', isIAS:true, powerSource: 'battery', isSleepy:true],
capabilities : ['MotionSensor': true, 'Battery': true],
preferences : ['motionReset':true, 'keepTime':'0x0500:0xF001', 'sensitivity':'0x0500:0x0013'],
fingerprints : [
[profileId:'0104', endpointId:'01', inClusters:'0000,0001,0003,0020,0400,0402,0500', outClusters:'0019', model:'MOT003', manufacturer:'HiveHome.com', deviceJoinName: 'Hive Motion Sensor'] // https://community.hubitat.com/t/hive-motion-sensors-can-we-get-custom-driver-sorted/108177?u=kkossev
],
attributes: [
[at:'0x0500:0x0013', name:'sensitivity', type:'enum', rw: 'rw', min:0, max:2, defVal:'2', unit:'', map:[0:'low', 1:'medium', 2:'high'], title:'Sensitivity', description:'PIR sensor sensitivity (update at the time motion is activated)'],
[at:'0x0500:0xF001', name:'keepTime', type:'enum', rw: 'rw', min:0, max:2, defVal:'0', unit:'seconds', map:[0:'30 seconds', 1:'60 seconds', 2:'120 seconds'], title:'Keep Time', description:'PIR keep time in seconds (update at the time motion is activated)'],
],
configuration : ['battery': false]
],
'---' : [
description : '--------------------------------------',
models : [],
fingerprints : [],
],
// ------------------------------------------- mmWave Radars - OBSOLETE ! => use the mmWave driver instead! ------------------------------------------------//
'TS0601_TUYA_RADAR' : [
description : 'Tuya Human Presence mmWave Radar ZY-M100', models : ['TS0601'], device : [isDepricated: true, type: 'radar', powerSource: 'dc', isSleepy:false], capabilities : ['MotionSensor': true, 'IlluminanceMeasurement': true, 'DistanceMeasurement':true],
fingerprints : [
[manufacturer:'_TZE200_ztc6ggyl'], [manufacturer:'_TZE204_ztc6ggyl'], [manufacturer:'_TZE200_ikvncluo'], [manufacturer:'_TZE200_lyetpprm'], [manufacturer:'_TZE200_wukb7rhc'],
[manufacturer:'_TZE200_jva8ink8'], [manufacturer:'_TZE200_mrf6vtua'], [manufacturer:'_TZE200_ar0slwnd'], [manufacturer:'_TZE200_sfiy5tfs'], [manufacturer:'_TZE200_holel4dk'],
[manufacturer:'_TZE200_xpq2rzhq'], [manufacturer:'_TZE204_qasjif9e'], [manufacturer:'_TZE204_xsm7l9xa']
],
tuyaDPs: [
[dp:1, name:'motion', type:'enum', rw: 'ro', map:[0:'inactive', 1:'active'], description:'Presence state'],
[dp:9, name:'distance', type:'decimal', rw: 'ro', scale:100, unit:'meters', description:'detected distance'],
[dp:104, name:'illuminance', type:'number', rw: 'ro', unit:'lx', description:'illuminance'],
], spammyDPsToIgnore : [9], spammyDPsToNotTrace : [9, 103]
],
'TS0601_KAPVNNLK_RADAR' : [
description : 'Tuya TS0601_KAPVNNLK 24GHz Radar', models : ['TS0601'], device : [isDepricated: true, type: 'radar', powerSource: 'dc', isSleepy:false], capabilities : ['MotionSensor': true, 'DistanceMeasurement':true],
fingerprints : [[manufacturer:'_TZE204_kapvnnlk'], [manufacturer:'_TZE204_kyhbrfyl']],
tuyaDPs: [
[dp:1, name:'motion', type:'enum', rw: 'ro', map:[0:'inactive', 1:'active'], description:'Presence state'],
[dp:19, name:'distance', type:'decimal', rw: 'ro', scale:100, unit:'meters', description:'detected distance']
], spammyDPsToIgnore : [19], spammyDPsToNotTrace : [19]
],
'TS0601_RADAR_MIR-HE200-TY' : [
description : 'Tuya Human Presence Sensor MIR-HE200-TY', models : ['TS0601'], device : [isDepricated:true, type: 'radar', powerSource: 'dc', isSleepy:false], capabilities : ['MotionSensor': true, 'IlluminanceMeasurement': true],
fingerprints : [[manufacturer:'_TZE200_vrfecyku'], [manufacturer:'_TZE200_lu01t0zl'], [manufacturer:'_TZE200_ypprdwsl']],
tuyaDPs: [
[dp:1, name:'motion', type:'enum', rw: 'ro', map:[0:'inactive', 1:'active'], description:'Presence state'],
[dp:102, name:'motionState', type:'enum', rw: 'ro', map:[0:'inactive', 1:'active'], description:'Motion state (occupancy)'],
]
],
'TS0601_BLACK_SQUARE_RADAR' : [
description : 'Tuya Black Square Radar', models : ['TS0601'], device : [isDepricated:true, type: 'radar', powerSource: 'dc', isSleepy:false], capabilities : ['MotionSensor':true],
fingerprints : [[manufacturer:'_TZE200_0u3bj3rc'], [manufacturer:'_TZE200_v6ossqfy'], [manufacturer:'_TZE200_mx6u6l4y']],
tuyaDPs: [[dp:1, name:'motion', type:'enum', rw: 'ro', map:[0:'inactive', 1:'active'], description:'Presence']], spammyDPsToIgnore : [103], spammyDPsToNotTrace : [1, 101, 102, 103]
],
'TS0601_YXZBRB58_RADAR' : [
description : 'Tuya YXZBRB58 Radar', models : ['TS0601'], device : [isDepricated: true, type: 'radar', powerSource: 'dc', isSleepy: false], capabilities : ['MotionSensor': true, 'IlluminanceMeasurement': true, 'DistanceMeasurement':true],
fingerprints : [[manufacturer:'_TZE204_sooucan5']],
tuyaDPs: [
[dp:1, name:'motion', type:'enum', rw: 'ro', map:[0:'inactive', 1:'active'], description:'Presence state'],
[dp:101, name:'illuminance', type:'number', rw: 'ro', unit:'lx', description:'Illuminance'],
[dp:105, name:'distance', type:'decimal', rw: 'ro', scale:100, unit:'meters', description:'Distance']
], spammyDPsToIgnore : [105], spammyDPsToNotTrace : [105]
],
'TS0601_SXM7L9XA_RADAR' : [
description : 'Tuya Human Presence Detector SXM7L9XA', models : ['TS0601'], device : [isDepricated: true, type: 'radar', powerSource: 'dc', isSleepy:false], capabilities : ['MotionSensor': true, 'IlluminanceMeasurement': true, 'DistanceMeasurement':true],
fingerprints : [[manufacturer:'_TZE204_sxm7l9xa'], [manufacturer:'_TZE204_e5m9c5hl']],
tuyaDPs: [
[dp:104, name:'illuminance', type:'number', rw: 'ro', unit:'lx', description:'illuminance'],
[dp:105, name:'motion', type:'enum', rw: 'ro', map:[0:'inactive', 1:'active'], description:'Presence state'],
[dp:109, name:'distance', type:'decimal', rw: 'ro', scale:100, unit:'meters', description:'Distance']
], spammyDPsToIgnore : [109], spammyDPsToNotTrace : [109]
],
'TS0601_IJXVKHD0_RADAR' : [
description : 'Tuya Human Presence Detector IJXVKHD0',
models : ['TS0601'],
device : [isDepricated: true, type: 'radar', powerSource: 'dc', isSleepy:false],
capabilities : ['MotionSensor': true, 'IlluminanceMeasurement': true, 'DistanceMeasurement':true],
fingerprints : [[manufacturer:'_TZE204_ijxvkhd0']],
tuyaDPs: [
[dp:104, name:'illuminance', type:'number', rw: 'ro', unit:'lx', description:'illuminance'],
[dp:105, name:'humanMotionState', type:'enum', rw: 'ro', map:[0:'none', 1:'present', 2:'moving'], description:'Presence state'],
[dp:109, name:'distance', type:'decimal', rw: 'ro', unit:'meters', description:'Target distance'],
[dp:112, name:'motion', type:'enum', rw: 'ro', map:[0:'inactive', 1:'active'], description:'Presence state'],
[dp:123, name:'presence', type:'enum', rw: 'ro', map:[0:'none', 1:'presence'], description:'Presence']
], spammyDPsToIgnore : [109, 9], spammyDPsToNotTrace : [109, 104]
],
'TS0601_YENSYA2C_RADAR' : [
description : 'Tuya Human Presence Detector YENSYA2C', models : ['TS0601'], device : [isDepricated: true, type: 'radar', powerSource: 'dc', isSleepy: false], capabilities : ['MotionSensor': true, 'IlluminanceMeasurement': true, 'DistanceMeasurement':true],
fingerprints : [[manufacturer:'_TZE204_yensya2c'], [manufacturer:'_TZE204_mhxn2jso']],
tuyaDPs: [
[dp:1, name:'motion', type:'enum', rw: 'ro', map:[0:'inactive', 1:'active'] , unit:'', title:'Presence state', description:'Presence state'],
[dp:19, name:'distance', type:'decimal', rw: 'ro', scale:100, unit:'meters', description:'Distance'],
[dp:20, name:'illuminance', type:'number', rw: 'ro', unit:'lx', description:'illuminance']
], spammyDPsToIgnore : [19], spammyDPsToNotTrace : [19]
],
'TS0225_HL0SS9OA_RADAR' : [
description : 'Tuya TS0225_HL0SS9OA Radar', models : ['TS0225'], device : [isDepricated: true, type: 'radar', powerSource: 'dc', isSleepy:false], capabilities : ['MotionSensor': true, 'IlluminanceMeasurement': true, 'HumanMotionState':true],
fingerprints : [[manufacturer:'_TZE200_hl0ss9oa']],
tuyaDPs: [
[dp:1, name:'motion', type:'enum', rw: 'ro', map:[0:'inactive', 1:'active'] , unit:'', title:'Presence state', description:'Presence state'],
[dp:11, name:'humanMotionState', type:'enum', rw: 'ro', map:[0:'none', 1:'large', 2:'small', 3:'static'], description:'Human motion state'],
[dp:20, name:'illuminance', type:'number', rw: 'ro', scale:10, unit:'lx', description:'Illuminance']
], spammyDPsToIgnore : [], spammyDPsToNotTrace : [11]
],
'TS0225_2AAELWXK_RADAR' : [
description : 'Tuya TS0225_2AAELWXK 5.8 GHz Radar', models : ['TS0225'], device : [isDepricated: true, type: 'radar', powerSource: 'dc', isSleepy:false], capabilities : ['MotionSensor': true, 'IlluminanceMeasurement': true, 'HumanMotionState':true],
fingerprints : [[manufacturer:'_TZE200_2aaelwxk']],
tuyaDPs: [
[dp:1, name:'motion', type:'enum', rw: 'ro', map:[0:'inactive', 1:'active'] , unit:'', title:'Presence state', description:'Presence state'],
[dp:101, name:'humanMotionState', type:'enum', rw: 'ro', map:[0:'none', 1:'large', 2:'small', 3:'static'], description:'Human motion state'],
[dp:106, name:'illuminance', type:'number', rw: 'ro', scale:10, unit:'lx', description:'Illuminance']
]
],
'TS0601_SBYX0LM6_RADAR' : [
description : 'Tuya Human Presence Detector SBYX0LM6', models : ['TS0601'], device : [isDepricated: true, type: 'radar', powerSource: 'dc', isSleepy:false], capabilities : ['MotionSensor': true, 'IlluminanceMeasurement': true, 'DistanceMeasurement':true],
fingerprints : [
[manufacturer:'_TZE204_sbyx0lm6'], [manufacturer:'_TZE200_sbyx0lm6'], [manufacturer:'_TZE204_dtzziy1e'], [manufacturer:'_TZE200_dtzziy1e'], [manufacturer:'_TZE204_clrdrnya'], [manufacturer:'_TZE200_clrdrnya'],
[manufacturer:'_TZE204_cfcznfbz'], [manufacturer:'_TZE204_iaeejhvf'], [manufacturer:'_TZE204_mtoaryre'], [manufacturer:'_TZE204_8s6jtscb'], [manufacturer:'_TZE204_rktkuel1'], [manufacturer:'_TZE204_mp902om5'],
[manufacturer:'_TZE200_w5y5slkq'], [manufacturer:'_TZE204_w5y5slkq'], [manufacturer:'_TZE200_xnaqu2pc'], [manufacturer:'_TZE204_xnaqu2pc'], [manufacturer:'_TZE200_wk7seszg'], [manufacturer:'_TZE204_wk7seszg'],
[manufacturer:'_TZE200_0wfzahlw'], [manufacturer:'_TZE204_0wfzahlw'], [manufacturer:'_TZE200_pfayrzcw'], [manufacturer:'_TZE204_pfayrzcw'], [manufacturer:'_TZE200_z4tzr0rg'], [manufacturer:'_TZE204_z4tzr0rg']
],
tuyaDPs: [
[dp:1, name:'motion', type:'enum', rw: 'ro', map:[0:'inactive', 1:'active'] , unit:'', title:'Presence state', description:'Presence state'],
[dp:9, name:'distance', type:'decimal', rw: 'ro', scale:100, unit:'meters', description:'detected distance'],
[dp:104, name:'illuminance', type:'number', rw: 'ro', scale:10, unit:'lx', description:'illuminance']
], spammyDPsToIgnore : [9], spammyDPsToNotTrace : [9]
],
'TS0225_LINPTECH_RADAR' : [
description : 'Tuya TS0225_LINPTECH 24GHz Radar', models : ['TS0225'], device : [isDepricated: true, type: 'radar', powerSource: 'dc', isSleepy:false], capabilities : ['MotionSensor': true, 'IlluminanceMeasurement': true, 'DistanceMeasurement':true],
fingerprints : [[manufacturer:'_TZ3218_awarhusb']],
tuyaDPs: [ [dp:101, name:'fadingTime', type:'number', rw: 'rw', min:1, max:9999, defVal:10, scale:1, unit:'seconds', title: 'Fading time', description:'Presence inactivity timer, seconds']]
],
'TS0225_EGNGMRZH_RADAR' : [
description : 'Tuya TS0225_EGNGMRZH 24GHz Radar', models : ['TS0225'], device : [isDepricated: true, type: 'radar', powerSource: 'dc', isSleepy:false], capabilities : ['MotionSensor': true, 'IlluminanceMeasurement': true, 'DistanceMeasurement':true],
fingerprints : [[manufacturer:'_TZFED8_egngmrzh']], // uses IAS for occupancy!
tuyaDPs: [
[dp:101, name:'illuminance', type:'number', rw: 'ro', unit:'lx'],
[dp:103, name:'distance', type:'decimal', rw: 'ro', scale:10, unit:'meters']
], spammyDPsToIgnore : [103], spammyDPsToNotTrace : [103]
],
'TS0225_O7OE4N9A_RADAR' : [
description : 'Tuya Human Presence Detector YENSYA2C', models : ['TS0225'], device : [isDepricated: true, type: 'radar', powerSource: 'dc', isSleepy:false], capabilities : ['MotionSensor': true, 'IlluminanceMeasurement': true, 'DistanceMeasurement':true],
fingerprints : [[manufacturer:'_TZFED8_o7oe4n9a']],
tuyaDPs: [
[dp:1, name:'motion', type:'enum', rw: 'ro', map:[0:'inactive', 1:'active'], description:'Presence state'],
[dp:181, name:'illuminance', type:'number', rw: 'ro', unit:'lx', description:'illuminance'],
[dp:182, name:'distance', type:'decimal', rw: 'ro', unit:'meters', description:'Distance to target']
], spammyDPsToIgnore : [182], spammyDPsToNotTrace : [182]
],
'OWON_OCP305_RADAR' : [
description : 'OWON OCP305 Radar', models : ['OCP305'], device: [isDepricated:true, type: 'radar', powerSource: 'dc', isSleepy:false], capabilities : ['MotionSensor': true, 'Battery': true],
fingerprints : [[manufacturer:'OWON']]
],
'SONOFF_SNZB-06P_RADAR' : [ // Depricated
description : 'SONOFF SNZB-06P RADAR', models : ['SNZB-06P'], device : [isDepricated:true, type: 'radar', powerSource: 'dc', isIAS:false, isSleepy:false], capabilities : ['MotionSensor': true],
fingerprints : [[manufacturer:'SONOFF']]
],
'UNKNOWN' : [ // the Device Profile key (shown in the State Variables)
description : 'Unknown device', // the Device Profile description (shown in the Preferences)
models : ['UNKNOWN'], // used to match a Device profile if the individuak fingerprints do not match
device : [
type: 'PIR', // 'PIR' or 'radar'
isIAS:true, // define it for PIR sensors only!
powerSource: 'dc', // determines the powerSource value - can be 'battery', 'dc', 'mains'
isSleepy:false // determines the update and ping behaviour
],
capabilities : ['MotionSensor': true, 'IlluminanceMeasurement': true, 'Battery': true],
preferences : ['motionReset':true],
commands : ['resetSettings':'resetSettings', 'resetStats':'resetStats', 'initialize':'initialize', 'updateAllPreferences': 'updateAllPreferences', 'resetPreferencesToDefaults':'resetPreferencesToDefaults', 'validateAndFixPreferences':'validateAndFixPreferences' \
],
//fingerprints : [
// [profileId:"0104", endpointId:"01", inClusters:"0000,0003,0406", outClusters:"0003", model:"model", manufacturer:"manufacturer"]
//],
tuyaDPs: [
[
dp:1,
name:'motion',
type:'enum',
rw: 'ro',
min:0,
max:1,
map:[0:'inactive', 1:'active'],
description:'Motion state'
]
],
configuration : ['battery': true],
]
]
// this is a motion driver -> IAS events represent motion/occupancy
public void customParseIasMessage(final String description) {
Map zs = zigbee.parseZoneStatusChange(description)
if (zs.alarm1Set == true) {
logDebug "customParseIasMessage: Alarm 1 is set"
handleMotion(true) // motionLib.groovy
}
else {
logDebug "customParseIasMessage: Alarm 1 is cleared"
handleMotion(false) // motionLib.groovy
}
}
void customParseOccupancyCluster(final Map descMap) {
//final Integer value = safeToInt(hexStrToUnsignedInt(descMap.value))
logTrace "customParseOccupancyCluster: zigbee received cluster 0x0406 attribute 0x${descMap.attrId} value ${value} (raw ${descMap.value})"
boolean result = processClusterAttributeFromDeviceProfile(descMap) // deviceProfileLib
if (result == false) {
if (descMap.attrId == '0000') {
int raw = Integer.parseInt(descMap.value, 16)
handleMotion(raw ? true : false)
}
// TODO - should be processed in the processClusterAttributeFromDeviceProfile method!
else if (descMap.attrId == '0020') { // OWON and SONOFF
int value = zigbee.convertHexToInt(descMap.value)
sendEvent('name': 'fadingTime', 'value': value, 'unit': 'seconds', 'type': 'physical', 'descriptionText': "fading time is ${value} seconds")
logDebug "Cluster ${descMap.cluster} Attribute ${descMap.attrId} (fadingTime) value is ${value} (0x${descMap.value} seconds)"
}
else if (descMap.attrId == '0022') {
int value = zigbee.convertHexToInt(descMap.value)
sendEvent('name': 'radarSensitivity', 'value': value, 'unit': '', 'type': 'physical', 'descriptionText': "radar sensitivity is ${value}")
logDebug "Cluster ${descMap.cluster} Attribute ${descMap.attrId} (radarSensitivity) value is ${value} (0x${descMap.value})"
}
else {
logDebug "UNPROCESSED Cluster ${descMap.cluster} Attribute ${descMap.attrId} value is ${descMap.value} (0x${descMap.value})"
}
}
}
// called from standardProcessTuyaDP in the commonLib for each Tuya dp report in a Zigbee message
// should always return true, as we are processing all the dp reports here
boolean customProcessTuyaDp(final Map descMap, final int dp, final int dp_id, final int fncmd, final int dp_len=0) {
logDebug "customProcessTuyaDp: dp=${dp} dp_id=${dp_id} fncmd=${fncmd} dp_len=${dp_len} descMap.data = ${descMap?.data}"
if (processTuyaDPfromDeviceProfile(descMap, dp, dp_id, fncmd, dp_len) == true) {
return true // sucessfuly processed from the deviceProfile
}
logWarn "NOT PROCESSED from deviceProfile Tuya cmd: dp=${dp} value=${fncmd} descMap.data = ${descMap?.data}"
localProcessTuyaDP(descMap, dp, dp_id, fncmd, dp_len)
return true
}
void localProcessTuyaDP(final Map descMap, final int dp, final int dp_id, final int fncmd, final int dp_len) {
switch (dp) {
case 0x01 : // motion state for almost of the Tuya PIR sensors
logDebug "(DP=0x01) motion event fncmd = ${fncmd}"
handleMotion(fncmd ? true : false)
break
case 0x04 : // battery level for TS0202 and TS0601 2in1 ; battery1 for Fantem 4-in-1 (100% or 0% ) Battery level for _TZE200_3towulqd (2in1)
logDebug "(DP=0x04) Tuya battery status report dp_id=${dp_id} dp=${dp} fncmd=${fncmd}"
handleTuyaBatteryLevel(fncmd)
break
case 0x07 : // temperature for 4-in-1 (no data)
logDebug "(DP=0x07) unexpected 4-in-1 temperature (dp=07) is ${fncmd / 10.0 } ${fncmd}"
temperatureEvent(fncmd / getTemperatureDiv())
break
case 0x08 : // humidity for 4-in-1 (no data)
logDebug "(DP=0x08) unexpected 4-in-1 humidity (dp=08) is ${fncmd} ${fncmd}"
humidityEvent(fncmd / getHumidityDiv())
break
case 0x09 : // sensitivity for TS0202 4-in-1 and 2in1 _TZE200_3towulqd
logInfo "(DP=0x09) unexpected received sensitivity : ${sensitivityOpts.options[fncmd]} (${fncmd})"
//device.updateSetting('sensitivity', [value:fncmd.toString(), type:'enum'])
break
case 0x0A : // (10) keep time for TS0202 4-in-1 and 2in1 _TZE200_3towulqd
logInfo "(DP=0x0A) unexpected Keep Time (dp=0x0A) is ${keepTimeIASOpts.options[fncmd]} (${fncmd})"
//device.updateSetting('keepTime', [value:fncmd.toString(), type:'enum'])
break
case 0x19 : // (25)
logDebug "(DP=0x19) unexpected battery status report dp_id=${dp_id} dp=${dp} fncmd=${fncmd}"
handleTuyaBatteryLevel(fncmd)
break
case 0x65 : // (101)
// Tuya 3 in 1 (101) -> motion (ocupancy) + TUYATEC
if (DEVICE?.device.isDepricated == true) {
logDebug '(DP=0x65) unexpected : ignored depricated device 0x65 event'
}
else {
logDebug "(DP=0x65) unexpected : motion event 0x65 fncmd = ${fncmd}"
handleMotion(fncmd ? true : false)
}
break
case 0x68 : // (104)
// 4in1 0x68 temperature compensation
int val = fncmd
// for negative values produce complimentary hex (equivalent to negative values)
if (val > 4294967295) { val = val - 4294967295 }
logInfo "(DP=0x68) unexpected : 4-in-1 temperature calibration is ${val / 10.0}"
break
case 0x69 : // (105)
// 4in1 0x69 humidity calibration (compensation)
int val = fncmd
if (val > 4294967295) val = val - 4294967295
logInfo "(DP=0x69) unexpected : 4-in-1 humidity calibration is ${val}"
break
case 0x6A : // (106)
// 4in1 0x6a lux calibration (compensation)
int val = fncmd
if (val > 4294967295) { val = val - 4294967295 }
logInfo "(DP=0x69) unexpected : 4-in-1 lux calibration is ${val}"
break
default :
logDebug "NOT PROCESSED Tuya cmd: dp=${dp} value=${fncmd} descMap.data = ${descMap?.data}"
break
}
}
// called from processFoundItem in the deviceProfileLib
void customProcessDeviceProfileEvent(final Map descMap, final String name, valueScaled, final String unitText, final String descText) {
logTrace "customProcessDeviceProfileEvent(${name}, ${valueScaled}) called"
boolean doNotTrace = isSpammyDPsToNotTrace(descMap)
Map eventMap = [name: name, value: valueScaled, unit: unitText, descriptionText: descText, type: 'physical', isStateChange: true]
switch (name) {
case 'motion' :
logTrace "customProcessDeviceProfileEvent: motion event received deviceProfile is ${getDeviceProfile()} valueScaled=${valueScaled} as boolean=${(valueScaled as boolean) }"
handleMotion(valueScaled == 'active' ? true : false) // TODO !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
break
case 'temperature' :
//temperatureEvent(value / getTemperatureDiv())
handleTemperatureEvent(valueScaled as Float)
break
case 'humidity' :
handleHumidityEvent(valueScaled)
break
case 'illuminance' :
case 'illuminance_lux' : // ignore the IAS Zone illuminance reports for HL0SS9OA and 2AAELWXK
//log.trace "illuminance event received deviceProfile is ${getDeviceProfile()} value=${value} valueScaled=${valueScaled} valueCorrected=${valueCorrected}"
handleIlluminanceEvent(valueScaled as int) // check !!!!!!!!!!
break
case 'pushed' : // used in 'TS0202_MOTION_SWITCH'
logDebug "button event received value=${value} valueScaled=${valueScaled} valueCorrected=${valueCorrected}"
buttonEvent(valueScaled)
break
default :
sendEvent(name : name, value : valueScaled, unit:unitText, descriptionText: descText, type: 'physical', isStateChange: true) // attribute value is changed - send an event !
if (!doNotTrace) {
logTrace "event ${name} sent w/ value ${valueScaled}"
logInfo "${descText}" // TODO - send info log only if the value has changed? // TODO - check whether Info log will be sent also for spammy clusterAttribute ?
}
break
}
}
List refreshFantem() {
List cmds = zigbee.command(0xEF00, 0x07, '00') // Fantem Tuya Magic
return cmds
}
List customRefresh() {
logDebug "customRefresh()"
List cmds = []
List devProfCmds = refreshFromDeviceProfileList()
if (devProfCmds != null && !devProfCmds.isEmpty()) {
cmds += devProfCmds
}
if (settings.allStatusTextEnable == true) {
runIn(3, 'formatAttrib', [overwrite: true])
}
return cmds
}
void customUpdated() {
logDebug "customUpdated()"
List cmds = []
if ('DistanceMeasurement' in DEVICE?.capabilities) {
if (settings?.ignoreDistance == true) {
device.deleteCurrentState('distance')
logDebug "customUpdated: deleted distance state"
}
else {
logDebug "customUpdated: ignoreDistance is ${settings?.ignoreDistance}"
}
}
if (settings?.allStatusTextEnable == false) {
device.deleteCurrentState('all')
}
if (settings?.forcedProfile != null) {
if (this.respondsTo('getProfileKey') == false) {
logWarn "getProfileKey() is not defined in the driver"
}
else {
logDebug "current state.deviceProfile=${state.deviceProfile}, settings.forcedProfile=${settings?.forcedProfile}, getProfileKey()=${getProfileKey(settings?.forcedProfile)}"
if (getProfileKey(settings?.forcedProfile) != state.deviceProfile) {
logInfo "changing the device profile from ${state.deviceProfile} to ${getProfileKey(settings?.forcedProfile)}"
state.deviceProfile = getProfileKey(settings?.forcedProfile)
initializeVars(fullInit = false)
resetPreferencesToDefaults(debug = true)
logInfo 'press F5 to refresh the page'
}
}
}
/* groovylint-disable-next-line EmptyElseBlock */
else {
logDebug "forcedProfile is not set"
}
if (DEVICE?.device?.isDepricated == true) {
logWarn 'The use of this driver with this device is depricated. Please update to the new driver!'
return
}
// Itterates through all settings
cmds += updateAllPreferences() // defined in deviceProfileLib
sendZigbeeCommands(cmds)
if (DEVICE?.preferences?.refreshOnSave == true) {
setRefreshRequest()
runIn(2, customRefresh, [overwrite: true])
}
if (settings.allStatusTextEnable == true) {
runIn(3, 'formatAttrib', [overwrite: true])
}
}
boolean isIAS() { DEVICE?.device?.isIAS == true }
List customConfigureDevice() {
logDebug "customConfigureDevice()"
List cmds = []
if (DEVICE?.device?.isDepricated == true) {
logWarn 'The use of this driver with this device is depricated. Please update to the new driver!'
return cmds
}
if (DEVICE?.device?.isIAS == true ) {
cmds += zigbee.enrollResponse(300) + zigbee.readAttribute(0x0500, 0x0000, [:], delay = 224)
logDebug 'Enrolling IAS device ...'
}
if (DEVICE?.configuration == null || DEVICE?.configuration.isEmpty()) {
logDebug 'No configuration found'
return cmds
}
int intMinTime = safeToInt(3600) // TODO: make it configurable
int intMaxTime = safeToInt(7200) // TODO: make it configurable
int delta = 0x02
// '0x0001':['bind':true, 'voltageReporting':[3600, 7200, 0x02], 'batteryReporting':[3600, 7200, 0x02] ], // TODO - use the reproting values
if ('0x0001' in DEVICE?.configuration) { // Power Configuration cluster
if (DEVICE?.configuration['0x0001']['bind'] == true) {
cmds += ["zdo bind 0x${device.deviceNetworkId} 0x01 0x01 0x0001 {${device.zigbeeId}} {}", 'delay 229', ]
logDebug "binding the device to the Power Configuration cluster"
}
else {
logDebug "no bind in 0x0001"
}
if ('voltageReporting' in DEVICE?.configuration['0x0001']) {
intMinTime = safeToInt(DEVICE?.configuration['0x0001']['voltageReporting' ][0])
intMaxTime = safeToInt(DEVICE?.configuration['0x0001']['voltageReporting' ][1])
delta = safeToInt(DEVICE?.configuration['0x0001']['voltageReporting' ][2])
logDebug "configuring the battery voltageReporting... (min=${intMinTime}, max=${intMaxTime}, delta=${delta})"
cmds += zigbee.configureReporting(0x0001, 0x20, DataType.UINT8, intMinTime, intMaxTime, delta, [:], delay = 226) // TEST - seems to be overwritten by the next line configuration?
cmds += zigbee.readAttribute(0x0001, 0x0020, [:], delay = 228) // try also battery voltage
}
if ('batteryReporting' in DEVICE?.configuration['0x0001']) {
intMinTime = safeToInt(DEVICE?.configuration['0x0001']['batteryReporting'][0])
intMaxTime = safeToInt(DEVICE?.configuration['0x0001']['batteryReporting'][1])
delta = safeToInt(DEVICE?.configuration['0x0001']['batteryReporting'][2])
logDebug "configuring the battery batteryReporting... (min=${intMinTime}, max=${intMaxTime}, delta=${delta})"
cmds += zigbee.configureReporting(0x0001, 0x21, DataType.UINT8, intMinTime, intMaxTime, 0x02, [:], delay = 225) // delta 0x02 = 1% change battery percentage remaining
cmds += zigbee.readAttribute(0x0001, 0x0021, [:], delay = 227) // battery percentage - SONOFF GW configures and reads only attr 0x0021 !
}
}
// '0x0500':['bind':false, 'sensitivity':false, 'keepTime':false], // TODO - use in update function
if ('0x0500' in DEVICE?.configuration && DEVICE?.configuration['0x0500']['bind'] == true) {
cmds += zigbee.configureReporting(0x0500, 0x0002, 0x19, 0, 3600, 0x00, [:], delay = 227)
}
if ('0x0400' in DEVICE?.configuration) {
cmds += ["zdo bind 0x${device.deviceNetworkId} 0x01 0x01 0x0400 {${device.zigbeeId}} {}", 'delay 229', ]
}
if ('0x0402' in DEVICE?.configuration) {
cmds += ["zdo bind 0x${device.deviceNetworkId} 0x01 0x01 0x0402 {${device.zigbeeId}} {}", 'delay 229', ]
}
if ('0x0405' in DEVICE?.configuration) {
cmds += ["zdo bind 0x${device.deviceNetworkId} 0x01 0x01 0x0405 {${device.zigbeeId}} {}", 'delay 229', ]
}
if ('0x0406' in DEVICE?.configuration) {
cmds += ["zdo bind 0x${device.deviceNetworkId} 0x01 0x01 0x0406 {${device.zigbeeId}} {}", 'delay 229', ] // OWON and SONOFF motion/occupancy cluster
}
if ('0xFC11' in DEVICE?.configuration) {
cmds += zigbee.configureReporting(0xFC11, 0x2001, DataType.UINT16, 0, 1440, 0x01, [:], delay = 230) // attribute 2001 - ??
}
if ('custom' in DEVICE?.configuration) {
String funcName = DEVICE?.configuration['custom']
if (this.respondsTo(funcName)) {
cmds += "$funcName"()
}
}
return cmds
}
List configureEspressif() {
logDebug "configureEspressif()"
List cmds = []
cmds += ["zdo bind 0x${device.deviceNetworkId} 0x0B 0x01 0x0402 {${device.zigbeeId}} {}", 'delay 229', ]
cmds += ["zdo bind 0x${device.deviceNetworkId} 0x0A 0x01 0x0406 {${device.zigbeeId}} {}", 'delay 229', ]
return cmds
}
void customInitializeVars(final boolean fullInit=false) {
logDebug "customInitializeVars(${fullInit})"
if (state.deviceProfile == null) {
setDeviceNameAndProfile() // in deviceProfileiLib.groovy
}
if (fullInit == true) {
resetPreferencesToDefaults()
}
if (fullInit == true || settings?.ignoreDistance == null) { device.updateSetting('ignoreDistance', true) }
if (fullInit == true || state.motionStarted == null) { state.motionStarted = unix2formattedDate(now()) }
// overwrite the default value of the invertMotion setting if the device is 2in1
if (fullInit == true || settings.invertMotion == null) device.updateSetting('invertMotion', is2in1() ? true : false)
if (fullInit == true || settings.allStatusTextEnable == null) device.updateSetting('allStatusTextEnable', false)
}
void customInitEvents(final boolean fullInit=false) {
logDebug "customInitEvents()"
if (getDeviceProfile() == 'TS0601_BLACK_SQUARE_RADAR') {
sendEvent(name: 'WARNING', value: 'EXTREMLY SPAMMY DEVICE!', descriptionText: 'This device bombards the hub every 4 seconds!')
}
}
void customParseIlluminanceCluster(final Map descMap) {
if (descMap.value == null || descMap.value == 'FFFF') { return } // invalid or unknown value
if (DEVICE?.device?.ignoreIAS == true) {
logDebug "customCustomParseIlluminanceCluster: ignoring IAS reporting device"
return
} // ignore IAS devices
standardParseIlluminanceCluster(descMap) // illuminance.lib
}
void customParseIASCluster(final Map descMap) {
final Integer value = safeToInt(hexStrToUnsignedInt(descMap.value))
logTrace "customParseIASCluster: zigbee received cluster 0x0500 attribute 0x${descMap.attrId} value ${value} (raw ${descMap.value})"
boolean result = processClusterAttributeFromDeviceProfile(descMap) // deviceProfileLib
if (result == false) {
logDebug "customParseIASCluster: received unknown 0x0500 attribute 0x${descMap.attrId} (value ${descMap.value})"
standardParseIASCluster(descMap)
}
}
void customParseFC11Cluster(final Map descMap) {
final Integer value = safeToInt(hexStrToUnsignedInt(descMap.value))
logTrace "customParseFC11Cluster: zigbee received 0xFC11 attribute 0x${descMap.attrId} value ${value} (raw ${descMap.value})"
boolean result = processClusterAttributeFromDeviceProfile(descMap) // deviceProfileLib
if (result == false) {
logWarn "customParseFC11Cluster: received unknown 0xFC11 attribute 0x${descMap.attrId} (value ${descMap.value})"
}
}
// ------------------------- formatAttrib() methods for the 4-in-1 driver -------------------------
void formatAttrib() {
if (settings.allStatusTextEnable == false) { // do not send empty html or text attributes
return
}
String attrStr = ''
attrStr += addToAttr('status', 'healthStatus')
attrStr += addToAttr('motion', 'motion')
if (DEVICE?.capabilities?.DistanceMeasurement == true && settings?.ignoreDistance == false) { attrStr += addToAttr('distance', 'distance') }
if (DEVICE?.capabilities?.Battery == true) { attrStr += addToAttr('battery', 'battery') }
if (DEVICE?.capabilities?.IlluminanceMeasurement == true) { attrStr += addToAttr('illuminance', 'illuminance') }
if (DEVICE?.capabilities?.TemperatureMeasurement == true) { attrStr += addToAttr('temperature', 'temperature') }
if (DEVICE?.capabilities?.RelativeHumidityMeasurement == true) { attrStr += addToAttr('humidity', 'humidity') }
attrStr = attrStr.substring(0, attrStr.length() - 3) // remove the ', '
updateAttr('all', attrStr)
if (attrStr.length() > 64) {
updateAttr('all', "Max Attribute Size Exceeded: ${attrStr.length()}")
}
}
/* groovylint-disable-next-line UnusedMethodParameter */
String addToAttr(String name, String key, String convert = 'none') {
String retResult = ''
String attrUnit = getUnitFromState(key)
if (attrUnit == null) { attrUnit = '' }
/* groovylint-disable-next-line NoDef */
def curVal = device.currentValue(key, true)
if (curVal != null) {
if (convert == 'int') {
retResult += safeToInt(curVal).toString() + '' + attrUnit
}
else if (convert == 'double') {
retResult += safeToDouble(curVal).toString() + '' + attrUnit
}
else {
retResult += curVal.toString() + '' + attrUnit
}
}
else {
retResult += 'n/a'
}
retResult += ', '
return retResult
}
String getUnitFromState(String attrName) {
return device.currentState(attrName)?.unit
}
void updateAttr(String aKey, String aValue, String aUnit = '') {
sendEvent(name:aKey, value:aValue, unit:aUnit, type: 'digital')
}
// ------------------------- end of formatAttrib() methods -------------------------
//@CompileStatic
void testFunc( par) {
parse('catchall: 0104 EF00 01 01 0040 00 7770 01 00 0000 02 01 00556701000100')
}
// catchall: 0104 EF00 01 01 0040 00 7770 01 00 0000 02 01 00556701000100
void test(String par) {
long startTime = now()
logDebug "test() started at ${startTime}"
//parse('catchall: 0104 EF00 01 01 0040 00 7770 01 00 0000 02 01 00556701000100')
def parpar = 'catchall: 0104 EF00 01 01 0040 00 7770 01 00 0000 02 01 00556701000100'
for (int i=0; i<100; i++) {
testFunc(parpar)
}
long endTime = now()
logDebug "test() ended at ${endTime} (duration ${endTime - startTime}ms)"
}
// /////////////////////////////////////////////////////////////////// Libraries //////////////////////////////////////////////////////////////////////
// ~~~~~ start include (142) kkossev.deviceProfileLib ~~~~~
/* groovylint-disable CompileStatic, CouldBeSwitchStatement, DuplicateListLiteral, DuplicateNumberLiteral, DuplicateStringLiteral, ImplicitClosureParameter, ImplicitReturnStatement, Instanceof, LineLength, MethodCount, MethodSize, NestedBlockDepth, NoDouble, NoFloat, NoWildcardImports, ParameterName, PublicMethodsBeforeNonPublicMethods, UnnecessaryElseStatement, UnnecessaryGetter, UnnecessaryPublicModifier, UnnecessarySetter, UnusedImport */ // library marker kkossev.deviceProfileLib, line 1
library( // library marker kkossev.deviceProfileLib, line 2
base: 'driver', author: 'Krassimir Kossev', category: 'zigbee', description: 'Device Profile Library', name: 'deviceProfileLib', namespace: 'kkossev', // library marker kkossev.deviceProfileLib, line 3
importUrl: 'https://raw.githubusercontent.com/kkossev/Hubitat/refs/heads/development/Libraries/deviceProfileLib.groovy', documentationLink: 'https://github.com/kkossev/Hubitat/wiki/libraries-deviceProfileLib', // library marker kkossev.deviceProfileLib, line 4
version: '3.5.2' // library marker kkossev.deviceProfileLib, line 5
) // library marker kkossev.deviceProfileLib, line 6
/* // library marker kkossev.deviceProfileLib, line 7
* Device Profile Library (V3) // library marker kkossev.deviceProfileLib, line 8
* // library marker kkossev.deviceProfileLib, line 9
* Licensed Virtual the Apache License, Version 2.0 (the "License"); you may not use this file except // library marker kkossev.deviceProfileLib, line 10
* in compliance with the License. You may obtain a copy of the License at: // library marker kkossev.deviceProfileLib, line 11
* // library marker kkossev.deviceProfileLib, line 12
* http://www.apache.org/licenses/LICENSE-2.0 // library marker kkossev.deviceProfileLib, line 13
* // library marker kkossev.deviceProfileLib, line 14
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed // library marker kkossev.deviceProfileLib, line 15
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License // library marker kkossev.deviceProfileLib, line 16
* for the specific language governing permissions and limitations under the License. // library marker kkossev.deviceProfileLib, line 17
* // library marker kkossev.deviceProfileLib, line 18
* ver. 1.0.0 2023-11-04 kkossev - added deviceProfileLib (based on Tuya 4 In 1 driver) // library marker kkossev.deviceProfileLib, line 19
* ver. 3.0.0 2023-11-27 kkossev - fixes for use with commonLib; added processClusterAttributeFromDeviceProfile() method; added validateAndFixPreferences() method; inputIt bug fix; signedInt Preproc method; // library marker kkossev.deviceProfileLib, line 20
* ver. 3.0.1 2023-12-02 kkossev - release candidate // library marker kkossev.deviceProfileLib, line 21
* ver. 3.0.2 2023-12-17 kkossev - inputIt moved to the preferences section; setfunction replaced by customSetFunction; Groovy Linting; // library marker kkossev.deviceProfileLib, line 22
* ver. 3.0.4 2024-03-30 kkossev - more Groovy Linting; processClusterAttributeFromDeviceProfile exception fix; // library marker kkossev.deviceProfileLib, line 23
* ver. 3.1.0 2024-04-03 kkossev - more Groovy Linting; deviceProfilesV3, enum pars bug fix; // library marker kkossev.deviceProfileLib, line 24
* ver. 3.1.1 2024-04-21 kkossev - deviceProfilesV3 bug fix; tuyaDPs list of maps bug fix; resetPreferencesToDefaults bug fix; // library marker kkossev.deviceProfileLib, line 25
* ver. 3.1.2 2024-05-05 kkossev - added isSpammyDeviceProfile() // library marker kkossev.deviceProfileLib, line 26
* ver. 3.1.3 2024-05-21 kkossev - skip processClusterAttributeFromDeviceProfile if cluster or attribute or value is missing // library marker kkossev.deviceProfileLib, line 27
* ver. 3.2.0 2024-05-25 kkossev - commonLib 3.2.0 allignment; // library marker kkossev.deviceProfileLib, line 28
* ver. 3.2.1 2024-06-06 kkossev - Tuya Multi Sensor 4 In 1 (V3) driver allignment (customProcessDeviceProfileEvent); getDeviceProfilesMap bug fix; forcedProfile is always shown in preferences; // library marker kkossev.deviceProfileLib, line 29
* ver. 3.3.0 2024-06-29 kkossev - empty preferences bug fix; zclWriteAttribute delay 50 ms; added advanced check in inputIt(); fixed 'Cannot get property 'rw' on null object' bug; fixed enum attributes first event numeric value bug; // library marker kkossev.deviceProfileLib, line 30
* ver. 3.3.1 2024-07-06 kkossev - added powerSource event in the initEventsDeviceProfile // library marker kkossev.deviceProfileLib, line 31
* ver. 3.3.2 2024-08-18 kkossev - release 3.3.2 // library marker kkossev.deviceProfileLib, line 32
* ver. 3.3.3 2024-08-18 kkossev - sendCommand and setPar commands commented out; must be declared in the main driver where really needed // library marker kkossev.deviceProfileLib, line 33
* ver. 3.3.4 2024-09-28 kkossev - fixed exceptions in resetPreferencesToDefaults() and initEventsDeviceProfile() // library marker kkossev.deviceProfileLib, line 34
* ver. 3.4.0 2025-02-02 kkossev - deviceProfilesV3 optimizations (defaultFingerprint); is2in1() mod // library marker kkossev.deviceProfileLib, line 35
* ver. 3.4.1 2025-02-02 kkossev - setPar help improvements; // library marker kkossev.deviceProfileLib, line 36
* ver. 3.4.2 2025-03-24 kkossev - added refreshFromConfigureReadList() method; documentation update; getDeviceNameAndProfile uses DEVICE.description instead of deviceJoinName // library marker kkossev.deviceProfileLib, line 37
* ver. 3.4.3 2025-04-25 kkossev - HE platfrom version 2.4.1.x decimal preferences patch/workaround. // library marker kkossev.deviceProfileLib, line 38
* ver. 3.5.0 2025-08-14 kkossev - zclWriteAttribute() support for forced destinationEndpoint in the attributes map // library marker kkossev.deviceProfileLib, line 39
* ver. 3.5.1 2025-09-15 kkossev - commonLib ver 4.0.0 allignment; log.trace leftover removed; // library marker kkossev.deviceProfileLib, line 40
* ver. 3.5.2 2025-10-04 kkossev - (dev. branch) SIMULATED_DEVICE_MODEL and SIMULATED_DEVICE_MANUFACTURER added (for testing with simulated devices) // library marker kkossev.deviceProfileLib, line 41
* // library marker kkossev.deviceProfileLib, line 42
* TODO - remove the 2-in-1 patch ! // library marker kkossev.deviceProfileLib, line 43
* TODO - add updateStateUnknownDPs (from the 4-in-1 driver) // library marker kkossev.deviceProfileLib, line 44
* TODO - when [refresh], send Info logs for parameters that are not events or preferences // library marker kkossev.deviceProfileLib, line 45
* TODO: refactor sendAttribute ! sendAttribute exception bug fix for virtual devices; check if String getObjectClassName(Object o) is in 2.3.3.137, can be used? // library marker kkossev.deviceProfileLib, line 46
* TODO: add _DEBUG command (for temporary switching the debug logs on/off) // library marker kkossev.deviceProfileLib, line 47
* TODO: allow NULL parameters default values in the device profiles // library marker kkossev.deviceProfileLib, line 48
* TODO: handle preferences of a type TEXT // library marker kkossev.deviceProfileLib, line 49
* // library marker kkossev.deviceProfileLib, line 50
*/ // library marker kkossev.deviceProfileLib, line 51
static String deviceProfileLibVersion() { '3.5.2' } // library marker kkossev.deviceProfileLib, line 53
static String deviceProfileLibStamp() { '2025/10/04 1:07 PM' } // library marker kkossev.deviceProfileLib, line 54
import groovy.json.* // library marker kkossev.deviceProfileLib, line 55
import groovy.transform.Field // library marker kkossev.deviceProfileLib, line 56
import hubitat.zigbee.clusters.iaszone.ZoneStatus // library marker kkossev.deviceProfileLib, line 57
import hubitat.zigbee.zcl.DataType // library marker kkossev.deviceProfileLib, line 58
import java.util.concurrent.ConcurrentHashMap // library marker kkossev.deviceProfileLib, line 59
import groovy.transform.CompileStatic // library marker kkossev.deviceProfileLib, line 61
metadata { // library marker kkossev.deviceProfileLib, line 63
// no capabilities // library marker kkossev.deviceProfileLib, line 64
// no attributes // library marker kkossev.deviceProfileLib, line 65
/* // library marker kkossev.deviceProfileLib, line 66
// copy the following commands to the main driver, if needed // library marker kkossev.deviceProfileLib, line 67
command 'sendCommand', [ // library marker kkossev.deviceProfileLib, line 68
[name:'command', type: 'STRING', description: 'command name', constraints: ['STRING']], // library marker kkossev.deviceProfileLib, line 69
[name:'val', type: 'STRING', description: 'command parameter value', constraints: ['STRING']] // library marker kkossev.deviceProfileLib, line 70
] // library marker kkossev.deviceProfileLib, line 71
command 'setPar', [ // library marker kkossev.deviceProfileLib, line 72
[name:'par', type: 'STRING', description: 'preference parameter name', constraints: ['STRING']], // library marker kkossev.deviceProfileLib, line 73
[name:'val', type: 'STRING', description: 'preference parameter value', constraints: ['STRING']] // library marker kkossev.deviceProfileLib, line 74
] // library marker kkossev.deviceProfileLib, line 75
*/ // library marker kkossev.deviceProfileLib, line 76
preferences { // library marker kkossev.deviceProfileLib, line 77
if (device) { // library marker kkossev.deviceProfileLib, line 78
input(name: 'forcedProfile', type: 'enum', title: 'Device Profile', description: 'Manually change the Device Profile, if the model/manufacturer was not recognized automatically.
Warning! Manually setting a device profile may not always work!', options: getDeviceProfilesMap()) // library marker kkossev.deviceProfileLib, line 79
// itterate over DEVICE.preferences map and inputIt all // library marker kkossev.deviceProfileLib, line 80
if (DEVICE != null && DEVICE?.preferences != null && DEVICE?.preferences != [:] && DEVICE?.device?.isDepricated != true) { // library marker kkossev.deviceProfileLib, line 81
(DEVICE?.preferences).each { key, value -> // library marker kkossev.deviceProfileLib, line 82
Map inputMap = inputIt(key) // library marker kkossev.deviceProfileLib, line 83
if (inputMap != null && inputMap != [:]) { // library marker kkossev.deviceProfileLib, line 84
input inputMap // library marker kkossev.deviceProfileLib, line 85
} // library marker kkossev.deviceProfileLib, line 86
} // library marker kkossev.deviceProfileLib, line 87
} // library marker kkossev.deviceProfileLib, line 88
} // library marker kkossev.deviceProfileLib, line 89
} // library marker kkossev.deviceProfileLib, line 90
} // library marker kkossev.deviceProfileLib, line 91
private boolean is2in1() { return getDeviceProfile().startsWith('TS0601_2IN1') } // patch! // library marker kkossev.deviceProfileLib, line 93
public String getDeviceProfile() { state?.deviceProfile ?: 'UNKNOWN' } // library marker kkossev.deviceProfileLib, line 95
public Map getDEVICE() { deviceProfilesV3 != null ? deviceProfilesV3[getDeviceProfile()] : deviceProfilesV2 != null ? deviceProfilesV2[getDeviceProfile()] : [:] } // library marker kkossev.deviceProfileLib, line 96
public Set getDeviceProfiles() { deviceProfilesV3 != null ? deviceProfilesV3?.keySet() : deviceProfilesV2 != null ? deviceProfilesV2?.keySet() : [] } // library marker kkossev.deviceProfileLib, line 97
public List getDeviceProfilesMap() { // library marker kkossev.deviceProfileLib, line 99
if (deviceProfilesV3 == null) { // library marker kkossev.deviceProfileLib, line 100
if (deviceProfilesV2 == null) { return [] } // library marker kkossev.deviceProfileLib, line 101
return deviceProfilesV2.values().description as List // library marker kkossev.deviceProfileLib, line 102
} // library marker kkossev.deviceProfileLib, line 103
List activeProfiles = [] // library marker kkossev.deviceProfileLib, line 104
deviceProfilesV3.each { profileName, profileMap -> // library marker kkossev.deviceProfileLib, line 105
if ((profileMap.device?.isDepricated ?: false) != true) { // library marker kkossev.deviceProfileLib, line 106
activeProfiles.add(profileMap.description ?: '---') // library marker kkossev.deviceProfileLib, line 107
} // library marker kkossev.deviceProfileLib, line 108
} // library marker kkossev.deviceProfileLib, line 109
return activeProfiles // library marker kkossev.deviceProfileLib, line 110
} // library marker kkossev.deviceProfileLib, line 111
// ---------------------------------- deviceProfilesV3 helper functions -------------------------------------------- // library marker kkossev.deviceProfileLib, line 113
/** // library marker kkossev.deviceProfileLib, line 115
* Returns the profile key for a given profile description. // library marker kkossev.deviceProfileLib, line 116
* @param valueStr The profile description to search for. // library marker kkossev.deviceProfileLib, line 117
* @return The profile key if found, otherwise null. // library marker kkossev.deviceProfileLib, line 118
*/ // library marker kkossev.deviceProfileLib, line 119
public String getProfileKey(final String valueStr) { // library marker kkossev.deviceProfileLib, line 120
if (deviceProfilesV3 != null) { return deviceProfilesV3.find { _, profileMap -> profileMap.description == valueStr }?.key } // library marker kkossev.deviceProfileLib, line 121
else if (deviceProfilesV2 != null) { return deviceProfilesV2.find { _, profileMap -> profileMap.description == valueStr }?.key } // library marker kkossev.deviceProfileLib, line 122
else { return null } // library marker kkossev.deviceProfileLib, line 123
} // library marker kkossev.deviceProfileLib, line 124
/** // library marker kkossev.deviceProfileLib, line 126
* Finds the preferences map for the given parameter. // library marker kkossev.deviceProfileLib, line 127
* @param param The parameter to find the preferences map for. // library marker kkossev.deviceProfileLib, line 128
* @param debug Whether or not to output debug logs. // library marker kkossev.deviceProfileLib, line 129
* @return returns either tuyaDPs or attributes map, depending on where the preference (param) is found // library marker kkossev.deviceProfileLib, line 130
* @return empty map [:] if param is not defined for this device. // library marker kkossev.deviceProfileLib, line 131
*/ // library marker kkossev.deviceProfileLib, line 132
private Map getPreferencesMapByName(final String param, boolean debug=false) { // library marker kkossev.deviceProfileLib, line 133
Map foundMap = [:] // library marker kkossev.deviceProfileLib, line 134
if (!(param in DEVICE?.preferences)) { if (debug) { log.warn "getPreferencesMapByName: preference ${param} not defined for this device!" } ; return [:] } // library marker kkossev.deviceProfileLib, line 135
/* groovylint-disable-next-line NoDef, VariableTypeRequired */ // library marker kkossev.deviceProfileLib, line 136
def preference // library marker kkossev.deviceProfileLib, line 137
try { // library marker kkossev.deviceProfileLib, line 138
preference = DEVICE?.preferences["$param"] // library marker kkossev.deviceProfileLib, line 139
if (debug) { log.debug "getPreferencesMapByName: preference ${param} found. value is ${preference}" } // library marker kkossev.deviceProfileLib, line 140
if (preference in [true, false]) { // library marker kkossev.deviceProfileLib, line 141
// find the preference in the tuyaDPs map // library marker kkossev.deviceProfileLib, line 142
logDebug "getPreferencesMapByName: preference ${param} is boolean" // library marker kkossev.deviceProfileLib, line 143
return [:] // no maps for predefined preferences ! // library marker kkossev.deviceProfileLib, line 144
} // library marker kkossev.deviceProfileLib, line 145
if (safeToInt(preference, -1) > 0) { //if (preference instanceof Number) { // library marker kkossev.deviceProfileLib, line 146
int dp = safeToInt(preference) // library marker kkossev.deviceProfileLib, line 147
//if (debug) log.trace "getPreferencesMapByName: param ${param} preference ${preference} is number (${dp})" // library marker kkossev.deviceProfileLib, line 148
foundMap = DEVICE?.tuyaDPs.find { it.dp == dp } // library marker kkossev.deviceProfileLib, line 149
} // library marker kkossev.deviceProfileLib, line 150
else { // cluster:attribute // library marker kkossev.deviceProfileLib, line 151
//if (debug) { log.trace "${DEVICE?.attributes}" } // library marker kkossev.deviceProfileLib, line 152
foundMap = DEVICE?.attributes.find { it.at == preference } // library marker kkossev.deviceProfileLib, line 153
} // library marker kkossev.deviceProfileLib, line 154
// TODO - could be also 'true' or 'false' ... // library marker kkossev.deviceProfileLib, line 155
} catch (e) { // library marker kkossev.deviceProfileLib, line 156
if (debug) { log.warn "getPreferencesMapByName: exception ${e} caught when getting preference ${param} !" } // library marker kkossev.deviceProfileLib, line 157
return [:] // library marker kkossev.deviceProfileLib, line 158
} // library marker kkossev.deviceProfileLib, line 159
if (debug) { log.debug "getPreferencesMapByName: foundMap = ${foundMap}" } // library marker kkossev.deviceProfileLib, line 160
return foundMap // library marker kkossev.deviceProfileLib, line 161
} // library marker kkossev.deviceProfileLib, line 162
public Map getAttributesMap(String attribName, boolean debug=false) { // library marker kkossev.deviceProfileLib, line 164
Map foundMap = [:] // library marker kkossev.deviceProfileLib, line 165
List