/* groovylint-disable CompileStatic, DuplicateListLiteral, DuplicateMapLiteral, DuplicateStringLiteral, ImplicitClosureParameter, MethodCount, MethodSize, NglParseError, NoDouble, PublicMethodsBeforeNonPublicMethods, StaticMethodsBeforeInstanceMethods, UnnecessaryGetter, UnnecessarySetter, UnusedImport */
/**
* Aqara E1 Thermostat - Device Driver for Hubitat Elevation
*
* https://community.hubitat.com/t/dynamic-capabilities-commands-and-attributes-for-drivers/98342
*
* 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.
*
* ver. 2.0.2 2023-05-29 kkossev - Aqara E1 thermostat driver initial version
* ver. 2.1.6 2023-11-06 kkossev - Aqara E1 thermostat improvements;
* ver. 3.3.0 2024-06-08 kkossev - new driver for Aqara E1 thermostat using thermostatLib
* ver. 3.4.0 2024-10-05 kkossev - added to HPM
* ver. 3.4.1 2025-03-04 kkossev - disabled 'cool' mode for the Aqara E1 thermostat
* ver. 3.5.0 2025-04-08 kkossev - urgent fix for java.lang.CloneNotSupportedException
* ver. 3.5.2 2025-05-25 kkossev - HE platfrom version 2.4.1.x decimal preferences patch/workaround.
* ver. 3.6.0 2025-09-15 kkossev - (dev. branch) commonLib 4.0.0 allignment;
*
* TODO:
*/
static String version() { '3.6.0' }
static String timeStamp() { '2025/09/15 8:11 PM' }
@Field static final Boolean _DEBUG = false
import groovy.transform.Field
import hubitat.device.HubMultiAction
import hubitat.device.Protocol
import hubitat.zigbee.zcl.DataType
import java.util.concurrent.ConcurrentHashMap
import groovy.json.JsonOutput
import java.math.RoundingMode
#include kkossev.commonLib
#include kkossev.onOffLib
#include kkossev.batteryLib
#include kkossev.temperatureLib
#include kkossev.xiaomiLib
#include kkossev.deviceProfileLib
#include kkossev.thermostatLib
deviceType = 'Thermostat'
@Field static final String DEVICE_TYPE = 'Thermostat'
metadata {
definition(
name: 'Aqara E1 Thermostat',
importUrl: 'https://raw.githubusercontent.com/kkossev/Hubitat/development/Drivers/Aqara%20E1%20Thermostat/Aqara_E1_Thermostat_lib_included.groovy',
namespace: 'kkossev', author: 'Krassimir Kossev', singleThreaded: true)
{
// capbilities are defined in the thermostatLib
// TODO - add all other models attributes possible values
attribute 'antiFreeze', 'enum', ['off', 'on'] // Tuya Saswell, AVATTO
attribute 'batteryVoltage', 'number'
attribute 'boostTime', 'number' // BRT-100
attribute 'calibrated', 'enum', ['false', 'true'] // Aqara E1
attribute 'calibrationTemp', 'number' // BRT-100, Sonoff
attribute 'childLock', 'enum', ['off', 'on'] // BRT-100, Aqara E1, Sonoff, AVATTO
attribute 'ecoMode', 'enum', ['off', 'on'] // BRT-100
attribute 'ecoTemp', 'number' // BRT-100
attribute 'emergencyHeating', 'enum', ['off', 'on'] // BRT-100
attribute 'emergencyHeatingTime', 'number' // BRT-100
attribute 'floorTemperature', 'number' // AVATTO/MOES floor thermostats
attribute 'frostProtectionTemperature', 'number' // Sonoff
attribute 'hysteresis', 'number' // AVATTO, Virtual thermostat
attribute 'level', 'number' // BRT-100
attribute 'maxHeatingSetpoint', 'number' // BRT-100, Sonoff, AVATTO
attribute 'minHeatingSetpoint', 'number' // BRT-100, Sonoff, AVATTO
attribute 'sensor', 'enum', ['internal', 'external', 'both'] // Aqara E1, AVATTO
attribute 'systemMode', 'enum', ['off', 'on'] // Aqara E1, AVATTO
attribute 'valveAlarm', 'enum', ['false', 'true'] // Aqara E1
attribute 'valveDetection', 'enum', ['off', 'on'] // Aqara E1
attribute 'weeklyProgram', 'number' // BRT-100
attribute 'windowOpenDetection', 'enum', ['off', 'on'] // BRT-100, Aqara E1, Sonoff
attribute 'windowsState', 'enum', ['open', 'closed'] // BRT-100, Aqara E1
attribute 'batteryLowAlarm', 'enum', ['batteryOK', 'batteryLow'] // TUYA_SASWELL
//attribute 'workingState', "enum", ["open", "closed"] // BRT-100
// Aqaura E1 attributes TODO - consolidate a common set of attributes
attribute 'preset', 'enum', ['manual', 'auto', 'away'] // TODO - remove?
attribute 'awayPresetTemperature', 'number'
if (_DEBUG) { command 'testT', [[name: 'testT', type: 'STRING', description: 'testT', defaultValue : '']] }
// itterate through all the figerprints and add them on the fly
deviceProfilesV3.each { profileName, profileMap ->
if (profileMap.fingerprints != null) {
profileMap.fingerprints.each {
fingerprint it
}
}
}
}
preferences {
input name: 'txtEnable', type: 'bool', title: 'Enable descriptionText logging', defaultValue: true, description: 'Enables command logging.'
input name: 'logEnable', type: 'bool', title: 'Enable debug logging', defaultValue: true, description: 'Turns on debug logging for 24 hours.'
// the rest of the preferences are inputed from the deviceProfile maps in the deviceProfileLib
}
}
@Field static final Map deviceProfilesV3 = [
// https://github.com/Koenkk/zigbee-herdsman-converters/blob/6339b6034de34f8a633e4f753dc6e506ac9b001c/src/devices/xiaomi.ts#L3197
// https://github.com/Smanar/deconz-rest-plugin/blob/6efd103c1a43eb300a19bf3bf3745742239e9fee/devices/xiaomi/xiaomi_lumi.airrtc.agl001.json
// https://github.com/dresden-elektronik/deconz-rest-plugin/issues/6351
'AQARA_E1_TRV' : [
description : 'Aqara E1 Thermostat model SRTS-A01',
device : [manufacturers: ['LUMI'], type: 'TRV', powerSource: 'battery', isSleepy:false],
capabilities : ['ThermostatHeatingSetpoint': true, 'ThermostatOperatingState': true, 'ThermostatSetpoint':true, 'ThermostatMode': true],
preferences : ['windowOpenDetection':'0xFCC0:0x0273', 'valveDetection':'0xFCC0:0x0274', 'childLock':'0xFCC0:0x0277', 'awayPresetTemperature':'0xFCC0:0x0279'],
fingerprints : [
[profileId:'0104', endpointId:'01', inClusters:'0000,0001,0003,FCC0,000A,0201', outClusters:'0003,FCC0,0201', model:'lumi.airrtc.agl001', manufacturer:'LUMI', deviceJoinName: 'Aqara E1 Thermostat']
],
commands : ['sendSupportedThermostatModes':'sendSupportedThermostatModes', 'autoPollThermostat':'autoPollThermostat', 'resetStats':'resetStats', 'refresh':'refresh', 'initialize':'initialize', 'updateAllPreferences': 'updateAllPreferences', 'resetPreferencesToDefaults':'resetPreferencesToDefaults', 'validateAndFixPreferences':'validateAndFixPreferences'],
tuyaDPs : [:],
attributes : [
[at:'0xFCC0:0x040A', name:'battery', type:'number', dt:'0x20', mfgCode:'0x115f', rw: 'ro', min:0, max:100, step:1, scale:1, unit:'%', description:'Battery percentage remaining'],
[at:'0xFCC0:0x0270', name:'unknown1', type:'enum', dt:'0x20', mfgCode:'0x115f', rw: 'rw', min:0, max:1, step:1, scale:1, map:[0: 'false', 1: 'true'], unit:'', title: 'Unknown 0x0270', description:'Unknown 0x0270'],
[at:'0xFCC0:0x0271', name:'systemMode', type:'enum', dt:'0x20', mfgCode:'0x115f', rw: 'rw', min:0, max:1, step:1, scale:1, map:[0: 'off', 1: 'on'], unit:'', title: 'System Mode', description:'Switch the TRV OFF or in operation (on)'], // result['system_mode'] = {1: 'heat', 0: 'off'}[value]; (heating state) - rw
[at:'0xFCC0:0x0272', name:'thermostatMode', type:'enum', dt:'0x20', mfgCode:'0x115f', rw: 'rw', min:0, max:2, step:1, scale:1, map:[0: 'heat', 1: 'auto', 2: 'away'], unit:'', title: 'Preset', description:'Preset'], // result['preset'] = {2: 'away', 1: 'auto', 0: 'manual'}[value]; - rw ['manual', 'auto', 'holiday']
[at:'0xFCC0:0x0273', name:'windowOpenDetection', type:'enum', dt:'0x20', mfgCode:'0x115f', rw: 'rw', min:0, max:1, defVal:'0', step:1, scale:1, map:[0: 'off', 1: 'on'], unit:'', title: 'Window Detection', description:'Window detection'], // result['window_detection'] = {1: 'ON', 0: 'OFF'}[value]; - rw
[at:'0xFCC0:0x0274', name:'valveDetection', type:'enum', dt:'0x20', mfgCode:'0x115f', rw: 'rw', min:0, max:1, defVal:'0', step:1, scale:1, map:[0: 'off', 1: 'on'], unit:'', title: 'Valve Detection', description:'Valve detection'], // result['valve_detection'] = {1: 'ON', 0: 'OFF'}[value]; -rw
[at:'0xFCC0:0x0275', name:'valveAlarm', type:'enum', dt:'0x23', mfgCode:'0x115f', rw: 'ro', min:0, max:1, defVal:'0', step:1, scale:1, map:[0: 'false', 1: 'true'], unit:'', title: 'Valve Alarm', description:'Valve alarm'], // result['valve_alarm'] = {1: true, 0: false}[value]; - read only!
[at:'0xFCC0:0x0276', name:'unknown2', type:'enum', dt:'0x41', mfgCode:'0x115f', rw: 'ro', min:0, max:1, step:1, scale:1, map:[0: 'false', 1: 'true'], unit:'', title: 'Unknown 0x0270', description:'Unknown 0x0270'],
[at:'0xFCC0:0x0277', name:'childLock', type:'enum', dt:'0x20', mfgCode:'0x115f', rw: 'rw', min:0, max:1, defVal:'0', step:1, scale:1, map:[0: 'unlock', 1: 'lock'], unit:'', title: 'Child Lock', description:'Child lock'], // result['child_lock'] = {1: 'LOCK', 0: 'UNLOCK'}[value]; - rw
[at:'0xFCC0:0x0278', name:'unknown3', type:'enum', dt:'0x20', mfgCode:'0x115f', rw: 'ow', min:0, max:1, defVal:'0', step:1, scale:1, map:[0: 'false', 1: 'true'], unit:'', title: 'Unknown 3', description:'Unknown 3'], // WRITE ONLY !
[at:'0xFCC0:0x0279', name:'awayPresetTemperature', type:'decimal', dt:'0x23', mfgCode:'0x115f', rw: 'rw', min:5.0, max:35.0, defVal:5.0, step:0.5, scale:100, unit:'°C', title: 'Away Preset Temperature', description:'Away preset temperature'], // result['away_preset_temperature'] = (value / 100).toFixed(1); - rw
[at:'0xFCC0:0x027A', name:'windowsState', type:'enum', dt:'0x20', mfgCode:'0x115f', rw: 'ro', min:0, max:1, defVal:'0', step:1, scale:1, map:[0: 'open', 1: 'closed'], unit:'', title: 'Window Open', description:'Window open'], // result['window_open'] = {1: true, 0: false}[value]; - read only
[at:'0xFCC0:0x027B', name:'calibrated', type:'enum', dt:'0x20', mfgCode:'0x115f', rw: 'ro', min:0, max:1, defVal:'0', step:1, scale:1, map:[0: 'false', 1: 'true'], unit:'', title: 'Calibrated', description:'Calibrated'], // result['calibrated'] = {1: true, 0: false}[value]; - read only
[at:'0xFCC0:0x027C', name:'unknown4', type:'enum', dt:'0x20', mfgCode:'0x115f', rw: 'ro', min:0, max:1, defVal:'0', step:1, scale:1, map:[0: 'false', 1: 'true'], unit:'', title: 'Unknown 4', description:'Unknown 4'],
[at:'0xFCC0:0x027D', name:'schedule', type:'enum', dt:'0x20', mfgCode:'0x115f', rw: 'ro', min:0, max:1, defVal:'0', step:1, scale:1, map:[0: 'off', 1: 'on'], unit:'', title: 'Schedule', description:'Schedule'],
[at:'0xFCC0:0x027E', name:'sensor', type:'enum', dt:'0x20', mfgCode:'0x115f', rw: 'ro', min:0, max:1, defVal:'0', step:1, scale:1, map:[0: 'internal', 1: 'external'], unit:'', title: 'Sensor', description:'Sensor'], // result['sensor'] = {1: 'EXTERNAL', 0: 'INTERNAL'}[value]; - read only
// 0xFCC0:0x027F ... 0xFCC0:0x0284 - unknown
[at:'0x0201:0x0000', name:'temperature', type:'decimal', dt:'0x29', rw: 'ro', min:5.0, max:35.0, step:0.5, scale:100, unit:'°C', title: 'Temperature', description:'Measured temperature'],
[at:'0x0201:0x0011', name:'coolingSetpoint', type:'decimal', dt:'0x29', rw: 'rw', min:5.0, max:35.0, step:0.5, scale:100, unit:'°C', title: 'Cooling Setpoint', description:'cooling setpoint'],
[at:'0x0201:0x0012', name:'heatingSetpoint', type:'decimal', dt:'0x29', rw: 'rw', min:5.0, max:35.0, step:0.5, scale:100, unit:'°C', title: 'Current Heating Setpoint', description:'Current heating setpoint'],
[at:'0x0201:0x001B', name:'thermostatOperatingState', type:'enum', dt:'0x30', rw:'rw', min:0, max:4, step:1, scale:1, map:[0: 'off', 1: 'heating', 2: 'unknown', 3: 'unknown3', 4: 'idle'], unit:'', description:'thermostatOperatingState (relay on/off status)'], // nothing happens when WRITING ????
// ^^^^ reporting only ?
[at:'0x0201:0x001C', name:'mode', type:'enum', dt:'0x30', rw: 'rw', min:0, max:1, step:1, scale:1, map:[0: 'off', 1: 'heat'], unit:'', title: ' Mode', description:'System Mode ?'],
// ^^^^ TODO - check if this is the same as system_mode
[at:'0x0201:0x001E', name:'thermostatRunMode', type:'enum', dt:'0x20', rw: 'rw', min:0, max:1, step:1, scale:1, map:[0: 'off', 1: 'heat'], unit:'', title: 'thermostatRunMode', description:'thermostatRunMode'],
// ^^ unsupported attribute? or reporting only ?
[at:'0x0201:0x0020', name:'battery2', type:'number', dt:'0x20', rw: 'ro', min:0, max:100, step:1, scale:1, unit:'%', description:'Battery percentage remaining'],
// ^^ unsupported attribute? or reporting only ?
[at:'0x0201:0x0023', name:'thermostatHoldMode', type:'enum', dt:'0x20', rw: 'rw', min:0, max:1, step:1, scale:1, map:[0: 'off', 1: 'heat'], unit:'', title: 'thermostatHoldMode', description:'thermostatHoldMode'],
// ^^ unsupported attribute? or reporting only ?
[at:'0x0201:0x0029', name:'thermostatOperatingState', type:'enum', dt:'0x20', rw: 'ow', min:0, max:1, step:1, scale:1, map:[0: 'idle', 1: 'heating'], unit:'', title: 'thermostatOperatingState', description:'thermostatOperatingState'],
// ^^ unsupported attribute? or reporting only ? encoding - 0x29 ?? ^^
[at:'0x0201:0xFFF2', name:'unknown', type:'number', dt:'0x21', rw: 'ro', min:0, max:100, step:1, scale:1, unit:'%', description:'Battery percentage remaining'],
// ^^ unsupported attribute? or reporting only ?
],
supportedThermostatModes: ['off', 'auto', 'heat', 'away'/*, "emergency heat"*/],
refresh: ['refreshAqaraE1'],
deviceJoinName: 'Aqara E1 Thermostat',
configuration : [:]
]
]
// called from parseXiaomiClusterLib in xiaomiLib.groovy (xiaomi cluster 0xFCC0 )
//
void parseXiaomiClusterThermostatLib(final Map descMap) {
logTrace "zigbee received Thermostat 0xFCC0 attribute 0x${descMap.attrId} (raw value = ${descMap.value})"
if ((descMap.attrInt as Integer) == 0x00F7 ) { // XIAOMI_SPECIAL_REPORT_ID: 0x00F7 sent every 55 minutes
final Map tags = decodeXiaomiTags(descMap.value)
parseXiaomiClusterThermostatTags(tags)
return
}
Boolean result = processClusterAttributeFromDeviceProfile(descMap)
if ( result == false ) {
logWarn "parseXiaomiClusterThermostatLib: received unknown Thermostat cluster (0xFCC0) attribute 0x${descMap.attrId} (value ${descMap.value})"
}
}
// XIAOMI_SPECIAL_REPORT_ID: 0x00F7 sent every 55 minutes
// called from parseXiaomiClusterThermostatLib
//
void parseXiaomiClusterThermostatTags(final Map tags) {
tags.each { final Integer tag, final Object value ->
switch (tag) {
case 0x01: // battery voltage
logDebug "xiaomi decode tag: 0x${intToHexStr(tag, 1)} battery voltage is ${value / 1000}V (raw=${value})"
break
case 0x03:
logDebug "xiaomi decode tag: 0x${intToHexStr(tag, 1)} device internal chip temperature is ${value}° (ignore it!)"
break
case 0x05:
logDebug "xiaomi decode tag: 0x${intToHexStr(tag, 1)} RSSI is ${value}"
break
case 0x06:
logDebug "xiaomi decode tag: 0x${intToHexStr(tag, 1)} LQI is ${value}"
break
case 0x08: // SWBUILD_TAG_ID:
final String swBuild = '0.0.0_' + (value & 0xFF).toString().padLeft(4, '0')
logDebug "xiaomi decode tag: 0x${intToHexStr(tag, 1)} swBuild is ${swBuild} (raw ${value})"
device.updateDataValue('aqaraVersion', swBuild)
break
case 0x0a:
String nwk = intToHexStr(value as Integer, 2)
if (state.health == null) { state.health = [:] }
String oldNWK = state.health['parentNWK'] ?: 'n/a'
logDebug "xiaomi decode tag: 0x${intToHexStr(tag, 1)} Parent NWK is ${nwk}"
if (oldNWK != nwk ) {
logWarn "parentNWK changed from ${oldNWK} to ${nwk}"
state.health['parentNWK'] = nwk
state.health['nwkCtr'] = (state.health['nwkCtr'] ?: 0) + 1
}
break
case 0x0d:
logDebug "xiaomi decode E1 thermostat unknown tag: 0x${intToHexStr(tag, 1)}=${value}"
break
case 0x11:
logDebug "xiaomi decode E1 thermostat unknown tag: 0x${intToHexStr(tag, 1)}=${value}"
break
case 0x64:
logDebug "xiaomi decode tag: 0x${intToHexStr(tag, 1)} temperature is ${value / 100} (raw ${value})" / Aqara TVOC
break
case 0x65:
logDebug "xiaomi decode E1 thermostat unknown tag: 0x${intToHexStr(tag, 1)}=${value}"
break
case 0x66:
logDebug "xiaomi decode E1 thermostat temperature tag: 0x${intToHexStr(tag, 1)}=${value}"
handleTemperatureEvent(value / 100.0)
break
case 0x67:
logDebug "xiaomi decode E1 thermostat heatingSetpoint tag: 0x${intToHexStr(tag, 1)}=${value}"
break
case 0x68:
logDebug "xiaomi decode E1 thermostat unknown tag: 0x${intToHexStr(tag, 1)}=${value}"
break
case 0x69:
logDebug "xiaomi decode E1 thermostat battery tag: 0x${intToHexStr(tag, 1)}=${value}"
break
case 0x6a:
logDebug "xiaomi decode E1 thermostat unknown tag: 0x${intToHexStr(tag, 1)}=${value}"
break
default:
logDebug "xiaomi decode unknown tag: 0x${intToHexStr(tag, 1)}=${value}"
}
}
}
/*
* -----------------------------------------------------------------------------
* thermostat cluster 0x0201
* called from parseThermostatCluster() in the main code ...
* -----------------------------------------------------------------------------
*/
void customParseThermostatCluster(final Map descMap) {
final Integer value = safeToInt(hexStrToUnsignedInt(descMap.value))
logTrace "customParseThermostatCluster: zigbee received Thermostat cluster (0x0201) attribute 0x${descMap.attrId} value ${value} (raw ${descMap.value})"
if (descMap == null || descMap == [:] || descMap.cluster == null || descMap.attrId == null || descMap.value == null) { logTrace 'descMap is missing cluster, attribute or value!'; return }
boolean result = processClusterAttributeFromDeviceProfile(descMap)
if ( result == false ) {
logWarn "parseThermostatClusterThermostat: received unknown Thermostat cluster (0x0201) attribute 0x${descMap.attrId} (value ${descMap.value})"
}
}
// TODO - configure in the deviceProfile
List pollAqara() {
return zigbee.readAttribute(0x0201, [0x0000, 0x0012, 0x001B, 0x001C], [:], delay = 3500) // 0x0000 = local temperature, 0x0012 = heating setpoint, 0x001B = controlledSequenceOfOperation, 0x001C = system mode (enum8 )
}
//
// called from updated() in the main code
void customUpdated() {
//ArrayList cmds = []
logDebug 'customUpdated: ...'
//
if (settings?.forcedProfile != null) {
//logDebug "current state.deviceProfile=${state.deviceProfile}, settings.forcedProfile=${settings?.forcedProfile}, getProfileKey()=${getProfileKey(settings?.forcedProfile)}"
if (getProfileKey(settings?.forcedProfile) != state.deviceProfile) {
logWarn "changing the device profile from ${state.deviceProfile} to ${getProfileKey(settings?.forcedProfile)}"
state.deviceProfile = getProfileKey(settings?.forcedProfile)
//initializeVars(fullInit = false)
customInitializeVars(fullInit = false)
resetPreferencesToDefaults(debug = true)
logInfo 'press F5 to refresh the page'
}
}
else {
logDebug 'forcedProfile is not set'
}
final int pollingInterval = (settings.temperaturePollingInterval as Integer) ?: 0
if (pollingInterval > 0) {
logInfo "updatedThermostat: scheduling temperature polling every ${pollingInterval} seconds"
scheduleThermostatPolling(pollingInterval)
}
else {
unScheduleThermostatPolling()
logInfo 'updatedThermostat: thermostat polling is disabled!'
}
// Itterates through all settings
logDebug 'updatedThermostat: updateAllPreferences()...'
updateAllPreferences()
}
// binding and reporting configuration for this Aqara E1 thermostat does nothing... We need polling mechanism for faster updates of the internal temperature readings.
List refreshAqaraE1() {
List cmds = []
cmds += zigbee.readAttribute(0x0201, [0x0000, 0x0011, 0x0012, 0x001B, 0x001C], [:], delay = 3500) // 0x0000 = local temperature, 0x0011 = cooling setpoint, 0x0012 = heating setpoint, 0x001B = controlledSequenceOfOperation, 0x001C = system mode (enum8 )
cmds += zigbee.readAttribute(0xFCC0, [0x0271, 0x0272, 0x0273, 0x0274, 0x0275, 0x0277, 0x0279, 0x027A, 0x027B, 0x027E], [mfgCode: 0x115F], delay = 3500)
cmds += zigbee.readAttribute(0xFCC0, 0x040a, [mfgCode: 0x115F], delay = 500)
return cmds
}
List customRefresh() {
List cmds = refreshFromDeviceProfileList()
logDebug "customRefresh: ${cmds} "
return cmds
}
List customConfigure() {
List cmds = []
logDebug "customConfigure() : ${cmds} (not implemented!)"
return cmds
}
List initializeAqara() {
List cmds = []
logDebug 'configuring cluster 0x0201 ...'
cmds += ["zdo bind 0x${device.deviceNetworkId} 0x01 0x01 0x0201 {${device.zigbeeId}} {}", 'delay 251', ]
//cmds += zigbee.configureReporting(0x0201, 0x0012, 0x29, intMinTime as int, intMaxTime as int, 0x01, [:], delay=541)
//cmds += zigbee.configureReporting(0x0201, 0x0000, 0x29, 20, 120, 0x01, [:], delay=542)
cmds += ["he cr 0x${device.deviceNetworkId} 0x01 0x0201 0x0012 0x29 1 600 {}", 'delay 551', ]
cmds += ["he cr 0x${device.deviceNetworkId} 0x01 0x0201 0x0000 0x29 20 300 {}", 'delay 551', ]
cmds += ["he cr 0x${device.deviceNetworkId} 0x01 0x0201 0x001C 0x30 1 600 {}", 'delay 551', ]
cmds += zigbee.reportingConfiguration(0x0201, 0x0012, [:], 551) // read it back - doesn't work
cmds += zigbee.reportingConfiguration(0x0201, 0x0000, [:], 552) // read it back - doesn't work
cmds += zigbee.reportingConfiguration(0x0201, 0x001C, [:], 552) // read it back - doesn't work
return cmds
}
// called from initializeDevice in the commonLib code
List customInitializeDevice() {
List cmds = []
logDebug 'customInitializeDevice() ...'
cmds = initializeAqara()
logDebug "initializeThermostat() : ${cmds}"
return cmds
}
void customInitializeVars(final boolean fullInit=false) {
logDebug "customInitializeVars(${fullInit})"
if (state.deviceProfile == null) {
setDeviceNameAndProfile() // in deviceProfileiLib.groovy
}
thermostatInitializeVars(fullInit)
if (fullInit == true) {
resetPreferencesToDefaults()
}
}
// called from initializeVars() in the main code ...
void customInitEvents(final boolean fullInit=false) {
logDebug "customInitEvents(${fullInit})"
thermostatInitEvents(fullInit)
}
List customAqaraBlackMagic() {
List cmds = []
if (isAqaraTRV_OLD()) {
cmds += ["he raw 0x${device.deviceNetworkId} 0 0 0x8002 {40 00 00 00 00 40 8f 5f 11 52 52 00 41 2c 52 00 00} {0x0000}", 'delay 200',]
cmds += "zdo bind 0x${device.deviceNetworkId} 0x01 0x01 0xFCC0 {${device.zigbeeId}} {}"
cmds += "zdo bind 0x${device.deviceNetworkId} 0x01 0x01 0x0406 {${device.zigbeeId}} {}"
cmds += zigbee.readAttribute(0x0001, 0x0020, [:], delay = 200) // TODO: check - battery voltage
logDebug 'customAqaraBlackMagic()'
}
return cmds
}
// called from processFoundItem (processTuyaDPfromDeviceProfile and ) processClusterAttributeFromDeviceProfile in deviceProfileLib when a Zigbee message was found defined in the device profile map
//
// (works for BRT-100, Sonoff TRVZV)
//
/* groovylint-disable-next-line MethodParameterTypeRequired, NoDef */
void customProcessDeviceProfileEvent(final Map descMap, final String name, final valueScaled, final String unitText, final String descText) {
logTrace "customProcessDeviceProfileEvent(${name}, ${valueScaled}) called"
Map eventMap = [name: name, value: valueScaled, unit: unitText, descriptionText: descText, type: 'physical', isStateChange: true]
switch (name) {
case 'temperature' :
handleTemperatureEvent(valueScaled as Float)
break
case 'heatingSetpoint' :
sendHeatingSetpointEvent(valueScaled)
break
case 'systemMode' : // Aqara E1 and AVATTO thermostat (off/on)
sendEvent(eventMap)
logInfo "${descText}"
if (valueScaled == 'on') { // should be initialized with 'unknown' value
String lastThermostatMode = state.lastThermostatMode
sendEvent(name: 'thermostatMode', value: lastThermostatMode, isStateChange: true, description: 'TRV systemMode is on', type: 'digital')
}
else {
sendEvent(name: 'thermostatMode', value: 'off', isStateChange: true, description: 'TRV systemMode is off', type: 'digital')
}
break
case 'thermostatMode' : // AVATTO send the thermostat mode a second after being switched off - ignore it !
if (device.currentValue('systemMode') == 'off' ) {
logWarn "customProcessDeviceProfileEvent: ignoring the thermostatMode ${valueScaled} event, because the systemMode is off"
}
else {
sendEvent(eventMap)
logInfo "${descText}"
state.lastThermostatMode = valueScaled
}
break
case 'ecoMode' : // BRT-100 - simulate OFF mode ?? or keep the ecoMode on ?
sendEvent(eventMap)
logInfo "${descText}"
if (valueScaled == 'on') { // ecoMode is on
sendEvent(name: 'thermostatMode', value: 'eco', isStateChange: true, description: 'BRT-100 ecoMode is on', type: 'digital')
sendEvent(name: 'thermostatOperatingState', value: 'idle', isStateChange: true, description: 'BRT-100 ecoMode is on', type: 'digital')
state.lastThermostatMode = 'eco'
}
else {
sendEvent(name: 'thermostatMode', value: 'heat', isStateChange: true, description: 'BRT-100 ecoMode is off')
state.lastThermostatMode = 'heat'
}
break
case 'emergencyHeating' : // BRT-100
sendEvent(eventMap)
logInfo "${descText}"
if (valueScaled == 'on') { // the valve shoud be completely open, however the level and the working states are NOT updated! :(
sendEvent(name: 'thermostatMode', value: 'emergency heat', isStateChange: true, description: 'BRT-100 emergencyHeating is on')
sendEvent(name: 'thermostatOperatingState', value: 'heating', isStateChange: true, description: 'BRT-100 emergencyHeating is on')
}
else {
sendEvent(name: 'thermostatMode', value: 'heat', isStateChange: true, description: 'BRT-100 emergencyHeating is off')
}
break
case 'level' : // BRT-100
sendEvent(eventMap)
logInfo "${descText}"
if (valueScaled == 0) { // the valve is closed
sendEvent(name: 'thermostatOperatingState', value: 'idle', isStateChange: true, description: 'BRT-100 valve is closed')
}
else {
sendEvent(name: 'thermostatOperatingState', value: 'heating', isStateChange: true, description: 'BRT-100 valve is open %{valueScaled} %')
}
break
/*
case "workingState" : // BRT-100 replaced with thermostatOperatingState
sendEvent(eventMap)
logInfo "${descText}"
if (valueScaled == "closed") { // the valve is closed
sendEvent(name: "thermostatOperatingState", value: "idle", isStateChange: true, description: "BRT-100 workingState is closed")
}
else {
sendEvent(name: "thermostatOperatingState", value: "heating", isStateChange: true, description: "BRT-100 workingState is open")
}
break
*/
default :
sendEvent(name : name, value : valueScaled, unit:unitText, descriptionText: descText, type: 'physical', isStateChange: true) // attribute value is changed - send an event !
//if (!doNotTrace) {
logDebug "event ${name} sent w/ value ${valueScaled}"
logInfo "${descText}" // send an Info log also (because value changed ) // TODO - check whether Info log will be sent also for spammy DPs ?
//}
break
}
}
void testT(String par) {
log.trace "testT(${par}) : DEVICE.preferences = ${DEVICE.preferences}"
log.trace "testT: ${settings}"
Map result
if (DEVICE != null && DEVICE.preferences != null && DEVICE.preferences != [:]) {
(DEVICE.preferences).each { key, value ->
log.trace "testT: ${key} = ${value}"
result = inputIt(key, debug = true)
logDebug "inputIt: ${result}"
}
}
}
// /////////////////////////////////////////////////////////////////// Libraries //////////////////////////////////////////////////////////////////////