library(
base: 'driver', author: 'Krassimir Kossev', category: 'zigbee', description: 'Device Profile Library', name: 'deviceProfileLib', namespace: 'kkossev',
importUrl: 'https://raw.githubusercontent.com/kkossev/Hubitat/refs/heads/development/Libraries/deviceProfileLib.groovy', documentationLink: 'https://github.com/kkossev/Hubitat/wiki/libraries-deviceProfileLib',
version: '3.5.4'
)
/*
* Device Profile Library (V3)
*
* Licensed Virtual 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.
*
* ver. 1.0.0 2023-11-04 kkossev - added deviceProfileLib (based on Tuya 4 In 1 driver)
* 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;
* ver. 3.0.1 2023-12-02 kkossev - release candidate
* ver. 3.0.2 2023-12-17 kkossev - inputIt moved to the preferences section; setfunction replaced by customSetFunction; Groovy Linting;
* ver. 3.0.4 2024-03-30 kkossev - more Groovy Linting; processClusterAttributeFromDeviceProfile exception fix;
* ver. 3.1.0 2024-04-03 kkossev - more Groovy Linting; deviceProfilesV3, enum pars bug fix;
* ver. 3.1.1 2024-04-21 kkossev - deviceProfilesV3 bug fix; tuyaDPs list of maps bug fix; resetPreferencesToDefaults bug fix;
* ver. 3.1.2 2024-05-05 kkossev - added isSpammyDeviceProfile()
* ver. 3.1.3 2024-05-21 kkossev - skip processClusterAttributeFromDeviceProfile if cluster or attribute or value is missing
* ver. 3.2.0 2024-05-25 kkossev - commonLib 3.2.0 allignment;
* 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;
* 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;
* ver. 3.3.1 2024-07-06 kkossev - added powerSource event in the initEventsDeviceProfile
* ver. 3.3.2 2024-08-18 kkossev - release 3.3.2
* ver. 3.3.3 2024-08-18 kkossev - sendCommand and setPar commands commented out; must be declared in the main driver where really needed
* ver. 3.3.4 2024-09-28 kkossev - fixed exceptions in resetPreferencesToDefaults() and initEventsDeviceProfile()
* ver. 3.4.0 2025-02-02 kkossev - deviceProfilesV3 optimizations (defaultFingerprint); is2in1() mod
* ver. 3.4.1 2025-02-02 kkossev - setPar help improvements;
* ver. 3.4.2 2025-03-24 kkossev - added refreshFromConfigureReadList() method; documentation update; getDeviceNameAndProfile uses DEVICE.description instead of deviceJoinName
* ver. 3.4.3 2025-04-25 kkossev - HE platfrom version 2.4.1.x decimal preferences patch/workaround.
* ver. 3.5.0 2025-08-14 kkossev - zclWriteAttribute() support for forced destinationEndpoint in the attributes map
* ver. 3.5.1 2025-09-15 kkossev - commonLib ver 4.0.0 allignment; log.trace leftover removed;
* ver. 3.5.2 2025-10-04 kkossev - SIMULATED_DEVICE_MODEL and SIMULATED_DEVICE_MANUFACTURER added (for testing with simulated devices)
* ver. 3.5.3 2025-12-06 kkossev - added digital/physical type to events in customProcessDeviceProfileEvent()
* ver. 3.5.4 2026-02-04 kkossev - changed inputIt min param rounding to floor instead of ceil
*
* TODO - remove the 2-in-1 patch !
* TODO - add updateStateUnknownDPs (from the 4-in-1 driver)
* TODO - when [refresh], send Info logs for parameters that are not events or preferences
* 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?
* TODO: add _DEBUG command (for temporary switching the debug logs on/off)
* TODO: allow NULL parameters default values in the device profiles
* TODO: handle preferences of a type TEXT
*
*/
static String deviceProfileLibVersion() { '3.5.4' }
static String deviceProfileLibStamp() { '2026/02/04 8:02 AM' }
import groovy.json.*
import groovy.transform.Field
import hubitat.zigbee.clusters.iaszone.ZoneStatus
import hubitat.zigbee.zcl.DataType
import java.util.concurrent.ConcurrentHashMap
import groovy.transform.CompileStatic
metadata {
// no capabilities
// no attributes
/*
// copy the following commands to the main driver, if needed
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']]
]
*/
preferences {
if (device) {
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())
// itterate over DEVICE.preferences map and inputIt all
if (DEVICE != null && DEVICE?.preferences != null && DEVICE?.preferences != [:] && DEVICE?.device?.isDepricated != true) {
(DEVICE?.preferences).each { key, value ->
Map inputMap = inputIt(key)
if (inputMap != null && inputMap != [:]) {
input inputMap
}
}
}
}
}
}
private boolean is2in1() { return getDeviceProfile().startsWith('TS0601_2IN1') } // patch!
public String getDeviceProfile() { state?.deviceProfile ?: 'UNKNOWN' }
public Map getDEVICE() { deviceProfilesV3 != null ? deviceProfilesV3[getDeviceProfile()] : deviceProfilesV2 != null ? deviceProfilesV2[getDeviceProfile()] : [:] }
public Set getDeviceProfiles() { deviceProfilesV3 != null ? deviceProfilesV3?.keySet() : deviceProfilesV2 != null ? deviceProfilesV2?.keySet() : [] }
public List getDeviceProfilesMap() {
if (deviceProfilesV3 == null) {
if (deviceProfilesV2 == null) { return [] }
return deviceProfilesV2.values().description as List
}
List activeProfiles = []
deviceProfilesV3.each { profileName, profileMap ->
if ((profileMap.device?.isDepricated ?: false) != true) {
activeProfiles.add(profileMap.description ?: '---')
}
}
return activeProfiles
}
// ---------------------------------- deviceProfilesV3 helper functions --------------------------------------------
/**
* Returns the profile key for a given profile description.
* @param valueStr The profile description to search for.
* @return The profile key if found, otherwise null.
*/
public String getProfileKey(final String valueStr) {
if (deviceProfilesV3 != null) { return deviceProfilesV3.find { _, profileMap -> profileMap.description == valueStr }?.key }
else if (deviceProfilesV2 != null) { return deviceProfilesV2.find { _, profileMap -> profileMap.description == valueStr }?.key }
else { return null }
}
/**
* Finds the preferences map for the given parameter.
* @param param The parameter to find the preferences map for.
* @param debug Whether or not to output debug logs.
* @return returns either tuyaDPs or attributes map, depending on where the preference (param) is found
* @return empty map [:] if param is not defined for this device.
*/
private Map getPreferencesMapByName(final String param, boolean debug=false) {
Map foundMap = [:]
if (!(param in DEVICE?.preferences)) { if (debug) { log.warn "getPreferencesMapByName: preference ${param} not defined for this device!" } ; return [:] }
/* groovylint-disable-next-line NoDef, VariableTypeRequired */
def preference
try {
preference = DEVICE?.preferences["$param"]
if (debug) { log.debug "getPreferencesMapByName: preference ${param} found. value is ${preference}" }
if (preference in [true, false]) {
// find the preference in the tuyaDPs map
logDebug "getPreferencesMapByName: preference ${param} is boolean"
return [:] // no maps for predefined preferences !
}
if (safeToInt(preference, -1) > 0) { //if (preference instanceof Number) {
int dp = safeToInt(preference)
//if (debug) log.trace "getPreferencesMapByName: param ${param} preference ${preference} is number (${dp})"
foundMap = DEVICE?.tuyaDPs.find { it.dp == dp }
}
else { // cluster:attribute
//if (debug) { log.trace "${DEVICE?.attributes}" }
foundMap = DEVICE?.attributes.find { it.at == preference }
}
// TODO - could be also 'true' or 'false' ...
} catch (e) {
if (debug) { log.warn "getPreferencesMapByName: exception ${e} caught when getting preference ${param} !" }
return [:]
}
if (debug) { log.debug "getPreferencesMapByName: foundMap = ${foundMap}" }
return foundMap
}
public Map getAttributesMap(String attribName, boolean debug=false) {
Map foundMap = [:]
List