/**
* Sensibo Thermostat Device
*
* Copyright 2021 Paul Hutton
*
* 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.
*
* Date Comments
* 2021-02-15 Forked from Bryan Li's port from ST
* 2024-03-27 Significant updates, support thermostat capabilities
*
*/
//file:noinspection GroovySillyAssignment
//file:noinspection GrDeprecatedAPIUsage
//file:noinspection GroovyDoubleNegation
//file:noinspection GroovyUnusedAssignment
//file:noinspection unused
//file:noinspection SpellCheckingInspection
//file:noinspection GroovyFallthrough
//ffile:noinspection GrMethodMayBeStatic
//file:noinspection GroovyAssignabilityCheck
//file:noinspection UnnecessaryQualifiedReference
import groovy.json.*
import groovy.transform.Field
import groovy.transform.CompileStatic
preferences{
//Logging Message Config
input "logInfo", "bool", title: "Show Info Logs?", required: false, defaultValue: true
input "logWarn", "bool", title: "Show Warning Logs?", required: false, defaultValue: true
input "logError", "bool", title: "Show Error Logs?", required: false, defaultValue: true
input "logDebug", "bool", title: "Show Debug Logs?", description: "Only leave on when required", required: false, defaultValue: false
input "logTrace", "bool", title: "Show Detailed Logs?", description: "Only Enabled when asked by the developer", required: false, defaultValue: false
}
metadata{
definition (name: "SensiboPod", namespace: "velowulf", author: "Paul Hutton", oauth: false){
// capability "Actuator"
capability "Battery"
capability "Health Check"
capability "Polling"
capability "PowerSource"
capability "Refresh"
capability "RelativeHumidityMeasurement"
capability "Sensor"
capability "Switch"
capability "TemperatureMeasurement"
capability "Thermostat"
capability "Voltage Measurement"
attribute "minHeatTemp", "number"
attribute "maxHeatTemp", "number"
attribute "minCoolTemp", "number"
attribute "maxCoolTemp", "number"
attribute "minCoolingSetpoint", "number" //google alexa compatability
attribute "maxCoolingSetpoint", "number" //google alexa compatability
attribute "minHeatingSetpoint", "number" //google alexa compatability
attribute "maxHeatingSetpoint", "number" //google alexa compatability
// attribute "thermostatThreshold", "number"
// attribute "lastTempUpdate", "date"
// attribute "maxUpdateInterval", "number"
// attribute "thermostatTemperatureSetpoint", "String" //google
attribute sTEMPUNIT,"String"
attribute "productModel","String"
attribute "firmwareVersion","String"
attribute "Climate","String"
attribute sTARGTEMP,"Number"
attribute "feelsLike","Number"
attribute "Error","string"
attribute sSWING, "String"
attribute "airConditionerMode","String"
attribute "airConditionerFanMode","String"
attribute sCURM,"String"
attribute sFANMODE,"String"
// attribute "statusText","String"
command "setAll",[
[name:"Thermostat mode*", type: "ENUM", constraints: [
sCOOL,
"fan",
"dry",
sAUTO,
sHEAT,
sOFF ]
],
[name: "Temperature*", type: "NUMBER", description: ""],
[name:"Fan level*", type: "ENUM", constraints: [
sON,
"circulate",
sAUTO,
"quiet",
"low",
"medium",
"high",
"strong" ]
]
]
command "setMinCoolTemp", [[ name: "temperature*", type: "NUMBER"]]
command "setMaxCoolTemp", [[ name: "temperature*", type: "NUMBER"]]
command "setMinHeatTemp", [[ name: "temperature*", type: "NUMBER"]]
command "setMaxHeatTemp", [[ name: "temperature*", type: "NUMBER"]]
command "resetMinMax"
// command "switchFanLevel"
//command "switchMode"
//command "raiseCoolSetpoint"
//command "lowerCoolSetpoint"
//command "raiseHeatSetpoint"
//command "lowerHeatSetpoint"
//command "voltage"
command "raiseTemperature"
command "lowerTemperature"
//command "switchSwing"
command "setSwingMode", [
[ name:"Swing Mode*", type: "ENUM", description: "Pick an option", constraints: [
"fixedTop",
"fixedMiddleTop",
"fixedMiddle",
"fixedMiddleBottom",
"fixedBottom",
"rangeTop",
"rangeMiddle",
"rangeBottom",
"rangeFull",
"horizontal",
"both",
"stopped" ]
]
]
command "modeDry"
command "modeFan"
command "fanLow"
command "fanMedium"
command "fanHigh"
command "fanQuiet"
command "fanStrong"
// command "fullswing"
command "setAirConditionerMode", [
[ name:"State*", type: "ENUM", constraints: [
sCOOL,
"fan",
"dry",
sAUTO,
sHEAT,
sOFF ]
]
]
command "setAirConditionerFanMode", [
[ name:"State*", type: "ENUM", constraints: [
sON,
"circulate",
sAUTO,
"quiet",
"low",
"medium",
"high",
"strong" ]
]
]
// command "toggleClimateReact"
command "setClimateReact", [
[ name:"State*", type: "ENUM", constraints: [
sON,
sOFF
] ]
]
command "setClimateReactConfiguration",[
[name:"Set Climate React State*", type: "ENUM", constraints: [
sON, sOFF ]
],
[name:"Climate react trigger type*", type: "ENUM", constraints: [
sTEMP, sHUMIDITY, "feelsLike" ]
],
[name:"Low Temp or humidity*", type: "NUMBER", description: "Low Threshold"],
[name:"High Temp or humidity*", type: "NUMBER", description: "High Threshold"],
[name:"Low state*", type: "JSON_OBJECT", description: "What happens at low threshold JSON"],
[name:"High state*", type: "JSON_OBJECT", description: "What happens at high threshold JSON"]
]
}
}
@Field static final String devVersionFLD = '2.0.0.0'
@Field static final String sNULL = (String)null
@Field static final String sBLANK = ''
@Field static final String sSPACE = ' '
@Field static final String sLINEBR = '
'
@Field static final String sTRUE = 'true'
@Field static final String sFALSE = 'false'
@Field static final String sCLRRED = 'red'
@Field static final String sCLRGRY = 'gray'
@Field static final String sCLRORG = 'orange'
@Field static final String sON = 'on'
@Field static final String sOFF = 'off'
@Field static final String sTEMP = 'temperature'
@Field static final String sTARGTEMP = 'targetTemperature'
@Field static final String sSW = 'switch'
@Field static final String sCURM = 'currentmode'
@Field static final String sFANMODE = 'fanLevel'
@Field static final String sSWING = 'swing'
@Field static final String sCOOL = 'cool'
@Field static final String sHEAT = 'heat'
@Field static final String sAUTO = 'auto'
@Field static final String sTHERMMODE = 'thermostatMode'
@Field static final String sTHERMOPER = 'thermostatOperatingState'
@Field static final String sTHERMFANMODE = 'thermostatFanMode'
@Field static final String sHEATSP = 'heatingSetpoint'
@Field static final String sCOOLSP = 'coolingSetpoint'
@Field static final String sTHERMSP = 'thermostatSetpoint'
@Field static final String sHUMIDITY = 'humidity'
@Field static final String sTEMPUNIT = 'temperatureUnit'
@Field static final String sC = 'C'
@Field static final String sF = 'F'
def installed(){
logTrace("installed")
// Let's just set a few things before starting
String hubScale= gtLtScale()
// Let's set all base thermostat settings
if(hubScale == sC){
wsendEvent(name: "minCoolTemp", value: 15.5, unit: sC) // 60°F
wsendEvent(name: "minCoolingSetpoint", value: 15.5, unit: sC) // Google
wsendEvent(name: "maxCoolTemp", value: 35.0, unit: sC) // 95°F
wsendEvent(name: "maxCoolingSetpoint", value: 35.0, unit: sC) // Google
wsendEvent(name: "minHeatTemp", value: 1.5, unit: sC) // 35°F
wsendEvent(name: "minHeatingSetpoint", value: 1.5, unit: sC) // Google
wsendEvent(name: "maxHeatTemp", value: 26.5, unit: sC) // 80°F
wsendEvent(name: "maxHeatingSetpoint", value: 26.5, unit: sC) // Google
wsendEvent(name: sTEMP, value: 22.0, unit: sC) // 72°F
wsendEvent(name: sHEATSP, value: 21.0, unit: sC) // 70°F
wsendEvent(name: sCOOLSP, value: 24.5, unit: sC) // 76°F
wsendEvent(name: sTHERMSP, value: 21.0, unit: sC) // 70°F
wsendEvent(name: sTARGTEMP, value: 21.0, unit: sC) // 70°F
// wsendEvent(name: "thermostatThreshold", value: 0.5, unit: sC) // Set by user
}else{
wsendEvent(name: "minCoolTemp", value: 60, unit: sF) // 15.5°C
wsendEvent(name: "minCoolingSetpoint", value: 60, unit: sF) // Google
wsendEvent(name: "maxCoolTemp", value: 95, unit: sF) // 35°C
wsendEvent(name: "maxCoolingSetpoint", value: 95, unit: sF) // Google
wsendEvent(name: "minHeatTemp", value: 35, unit: sF) // 1.5°C
wsendEvent(name: "minHeatingSetpoint", value: 35, unit: sF) // Google
wsendEvent(name: "maxHeatTemp", value: 80, unit: sF) // 26.5°C
wsendEvent(name: "maxHeatingSetpoint", value: 80, unit: sF) // Google
wsendEvent(name: sTEMP, value: 72, unit: sF) // 22°C
wsendEvent(name: sHEATSP, value: 70, unit: sF) // 21°C
wsendEvent(name: sCOOLSP, value: 76, unit: sF) // 24.5°C
wsendEvent(name: sTHERMSP, value: 70, unit: sF) // 21°C
wsendEvent(name: sTARGTEMP, value: 70, unit: sF) // 21°C
// wsendEvent(name: "thermostatThreshold", value: 1.0, unit: sF) // Set by user
}
wsendEvent(name: sSW, value: sOFF)
wsendEvent(name: sTHERMFANMODE, value: sAUTO)
wsendEvent(name: sTHERMMODE, value: sOFF)
wsendEvent(name: sTHERMOPER, value: "idle")
wsendEvent(name: "supportedThermostatModes", value: [sHEAT, sCOOL, sAUTO, sOFF])
wsendEvent(name: 'supportedThermostatFanModes', value: [sON, "circulate", sAUTO])
// wsendEvent(name: "maxUpdateInterval", value: 65)
// wsendEvent(name: "lastTempUpdate", value: new Date() )
wsendEvent(name: sFANMODE, value: sAUTO)
wsendEvent(name: 'airConditionerFanMode', value: sAUTO)
wsendEvent(name: sSWING, value: "stopped")
wsendEvent(name: sCURM, value: sOFF)
wsendEvent(name: 'airConditionerMode', value: sOFF)
}
def updated(){
logTrace("updated")
//if(advLogsActive()){ runIn(1800, "logsOff") }
if(advLogsActive()){ runIn(28800, "logsOff") }
}
Boolean advLogsActive(){ return ((Boolean)settings.logDebug || (Boolean)settings.logTrace) }
void logsOff(){
device.updateSetting("logDebug",[value:sFALSE,type:"bool"])
device.updateSetting("logTrace",[value:sFALSE,type:"bool"])
log.debug "Disabling debug logs"
}
// Standard thermostat commands
def off(){
logTrace( "off()")
modeMode(sOFF)
}
def heat(){
logTrace( "heat()")
modeMode(sHEAT)
}
def cool(){
logTrace( "cool()")
modeMode(sCOOL)
}
def auto(){
logTrace( "auto()")
modeMode(sAUTO)
}
def fanAuto(){
logTrace( "fanAuto()")
dfanLevel(sAUTO)
}
def fanCirculate(){
logTrace( "fanCirculate()")
dfanLevel("low")
}
def fanOn(){
logTrace( "fanOn()")
dfanLevel("medium")
}
def setThermostatFanMode(String mode){
logTrace( "setThermostatFanMode($mode)")
switch (mode){
case sON:
fanOn()
break
case "circulate":
fanCirculate()
break
case sAUTO:
fanAuto()
break
default:
generateErrorEvent()
}
}
@CompileStatic
def setThermostatMode(mode){
logTrace( "setThermostatMode($mode)")
switch (mode){
case sCOOL:
cool()
break
//case "fan":
// returnCommand = modeFan()
// break
//case "dry":
// returnCommand = modeDry()
// break
case sAUTO:
auto()
break
case sHEAT:
heat()
break
case sOFF:
off()
break
default:
generateErrorEvent()
}
}
def setHeatingSetpoint(itemp){
logTrace( "setHeatingSetpoint($itemp)")
Integer temp
temp = itemp.toInteger()
logDebug("setTemperature : " + temp)
Boolean result = wsetACStates(sHEAT, temp, sON, null, null)
if(result){
logInfo( "Heating temperature changed to " + temp + " for " + gtDNI())
generateModeEvent(sHEAT)
wsendEvent(name: sHEATSP, value: temp)
generateSetTempEvent(temp)
}else{
generateErrorEvent()
}
cmdRefresh()
}
def setCoolingSetpoint(itemp){
logTrace( "setCoolingSetpoint($itemp)")
Integer temp
temp = itemp.toInteger()
logDebug("setTemperature : " + temp )
Boolean result = wsetACStates(sCOOL, temp, sON, null, null)
if(result){
logInfo( "Cooling temperature changed to " + temp + " for " + gtDNI())
generateModeEvent(sCOOL)
wsendEvent(name: sCOOLSP, value: temp)
generateSetTempEvent(temp)
}else{
generateErrorEvent()
}
cmdRefresh()
}
// standard because of switch capability
// Turn off or Turn on the AC
def on(){
logTrace( "on()")
Boolean result = wsetACStates( null, null, sON, null, null)
logDebug("Result : " + result)
if(result){
logInfo( "AC turned ON for " + gtDNI())
generateModeEvent(sdCV(sCURM))
}else{
generateErrorEvent()
}
cmdRefresh()
}
// non standard commands
def modeDry(){
logTrace( "dry()")
modeMode("dry")
}
def modeFan(){
logTrace( "modeFan()")
modeMode("fan")
}
def fanLow(){
logTrace( "fanLow()")
dfanLevel("low")
}
def fanMedium(){
logTrace( "fanMedium()")
dfanLevel("medium")
}
def fanHigh(){
logTrace( "fanHigh()")
dfanLevel("high")
}
def fanQuiet(){
logTrace( "fanQuiet()")
dfanLevel("quiet")
}
def fanStrong(){
logTrace( "fanStrong()")
dfanLevel("strong")
}
// Logging and event management
def generatefanLevelEvent(String Level){
String LevelBefore = sdCV(sFANMODE)
wsendEvent(name: sFANMODE, value: Level, descriptionText: "Fan mode is now ${Level}")
if(LevelBefore!=Level) logInfo( "Fan level changed to " + Level + " for " + gtDNI())
wsendEvent(name: 'airConditionerFanMode', value: Level)
String mode
mode = Level
mode = (mode in ["high", "medium", "strong"]) ? sON : mode
mode = (mode in ["low", "quiet"]) ? "circulate" : mode
mode = !(mode in [sON, "circulate"]) ? sAUTO : mode
wsendEvent(name: 'thermostatFanMode', value: mode)
}
void generateModeEvent(String mode, Boolean doSW=true){
if(mode != sOFF) wsendEvent(name: sCURM, value: mode, descriptionText: "AC mode is now ${mode}")
wsendEvent(name: 'airConditionerMode', value: mode)
String m
if(mode in [sHEAT,sCOOL,sAUTO,sOFF])
m= mode
else if(mode in ["dry"]) {
m= sCOOL
}else // 'fan'
m= sOFF
wsendEvent(name: sTHERMMODE, value: m, descriptionText: "AC mode is now ${m}")
m= sBLANK
if(mode in [sCOOL,"dry"]){
m= 'cooling'
}else if(mode in [sHEAT,sAUTO]){
m= 'heating'
}else if(mode=="fan"){
m= 'fan only'
}else{
m= 'idle'
}
wsendEvent(name: sTHERMOPER, value: m)
if(doSW) generateSwitchEvent(mode==sOFF ? sOFF : sON)
}
void generateErrorEvent(){
logError(gtDisplayName()+" FAILED to set the AC State")
// wsendEvent(name: "Error", value: "Error", descriptionText: gtDisplayName()+" FAILED to set or get the AC State")
}
def generateSetTempEvent(temp){
wsendEvent(name: sTHERMSP, value: temp, descriptionText: gtDisplayName()+" set temperature is now ${temp}")
wsendEvent(name: sTARGTEMP, value: temp, descriptionText: gtDisplayName()+" set temperature is now ${temp}")
}
void generateSwitchEvent(String status){
wsendEvent(name: sSW, value: status, descriptionText: gtDisplayName()+" is now ${status}")
}
// Unit conversion
static Double cToF(temp){
return (temp * 1.8D + 32.0D).toDouble()
}
static Double fToC(temp){
return ((temp - 32.0D) / 1.8D).toDouble()
}
// non standard command
void switchMode(){
logTrace( "switchMode()")
String currentMode = sdCV(sCURM)
logDebug("switching AC mode from current mode: $currentMode")
switch (currentMode){
case sHEAT:
modeMode(sCOOL)
break
case sCOOL:
modeMode("fan")
break
case "fan":
modeMode("dry")
break
case "dry":
modeMode(sAUTO)
break
case sAUTO:
modeMode(sHEAT)
break
}
}
void modeMode(String newMode){
logTrace( "modeMode() " + newMode)
String dni= gtDNI()
logInfo( "Mode change request " + newMode + " for " + dni)
Boolean result
String LevelBefore = sdCV(sFANMODE)
String Level; Level = LevelBefore
if(newMode==sOFF){ // off always exists
result = wsetACStates( null, null, sOFF, null, null)
}else{
Map capabilities = gtCapabilities(newMode)
if(capabilities.remoteCapabilities != null){
// see if fan level exists
List fanLevels = ((Map)capabilities.remoteCapabilities).fanLevels
//logDebug("Fan levels capabilities : " + fanLevels)
if(!(Level in fanLevels)){
Level = GetNextFanLevel(LevelBefore,fanLevels)
logWarn("Changing Fan : " + Level)
}
result = wsetACStates(newMode, null, sON, Level, null)
}else{ // the mode does not exist, so guess one
Map themodes = gtCapabilities("modes")
List lmodes; lmodes=[]
themodes.each{
lmodes= lmodes+[it.key] as List
}
if(!(newMode in lmodes))
logWarn("requested $newMode does not exist, try one of $lmodes")
// String sMode = GetNextMode(newMode,lmodes)
// NextMode(sMode)
return
}
}
if(result){
generateModeEvent(newMode)
if(LevelBefore != Level){
generatefanLevelEvent(Level)
}
logInfo( "AC turned $newMode for " + dni)
}else{
generateErrorEvent()
}
cmdRefresh()
}
String GetNextMode(String mode, Listmodes){
logTrace( "GetNextMode " + mode + " modes: $modes")
List listMode = [sHEAT,sCOOL,/*'fan','dry',*/ sAUTO,sOFF]
String newMode = returnNext(listMode,modes,mode)
logDebug("Next Mode = " + newMode)
return newMode
}
void NextMode(sMode){
logTrace( "NextMode($sMode)")
if(sMode != null){
switch (sMode){
case sHEAT:
heat()
break
case sCOOL:
cool()
break
case "fan":
modeFan()
break
case "dry":
modeDry()
break
case sAUTO:
auto()
break
case sOFF:
off()
break
}
}
}
String GetNextFanLevel(String fanLevel, ListfanLevels){
logTrace( "GetNextFanLevel " + fanLevel)
if(!fanLevels) return null
List listFanLevel = ['low','medium','high',sAUTO,'quiet','medium_high','medium_low','strong']
String newFanLevel = returnNext(listFanLevel,fanLevels,fanLevel)
logDebug("Next fanLevel = " + newFanLevel)
return newFanLevel
}
/**
* find val in liste2, if there return next value; if not there find val in liste1 and return next value
* @throws Exception
*/
String returnNext(List liste1, List liste2, String val) throws Exception{
try{
Integer index = liste2.indexOf(val)
if(index == -1) throw new Exception()
else return liste2[liste2.indexOf(val)]
}
catch(ignored){
String nval
if(liste1.indexOf(val)+ 1 == liste1.size()){
nval = liste1[0]
}else{
nval = liste1[liste1.indexOf(val) + 1]
}
returnNext(liste1, liste2, nval)
}
}
void cmdRefresh(){
logTrace( "cmdRefresh()")
parent.afterCmdRefresh()
}
def refresh(){
logTrace( "refresh()")
poll()
}
void poll(){
logTrace( "poll()")
Map results = (Map)parent.pollChild(this)
parseEventData(results)
}
def parseEventData(Map results){
logTrace( "parseEventData()")
logDebug("parseEventData $results")
if(results){
try{
results.each{ String name, value ->
//logDebug("name: " + name + " value: " + value)
String desc= getThermostatDescriptionText(name, value)
String unit; unit = sNULL
Boolean doit; doit= true
switch(name){
case sTEMP:
case "feelsLike":
case sHUMIDITY:
case "battery":
case "powerSource":
case "Climate":
case sTEMPUNIT:
case "productModel":
case "firmwareVersion":
case "Error":
break
case sON:
case sSW:
generateSwitchEvent(value as String)
if(value == sOFF) generateModeEvent(value as String,false)
doit= false
break
case sTHERMMODE:
case sCURM:
// this presumes switch was run first (above)
if(sdCV(sSW) != sOFF){
generateModeEvent(value as String,false)
} else if(sdCV(sCURM) != value)
wsendEvent(name: sCURM, value: value, descriptionText: "AC mode is now ${value}")
doit= false
break
case sTARGTEMP:
case sCOOLSP:
case sHEATSP:
case sTHERMSP:
generateSetTempEvent(value)
break
case sTHERMFANMODE:
case sFANMODE:
generatefanLevelEvent(value as String)
doit= false
break
case sSWING:
generateSwingModeEvent(value as String)
doit= false
break
case "updated":
doit= false
break
case "voltage":
unit="mA"
break
default:
logWarn("UNKNOWN name: " + name + " value: " + value)
}
if(doit){
Map evt= [
name: name,
value: value,
descriptionText: getThermostatDescriptionText(name, value),
] + (unit!=sNULL ? [unit: unit] : [:])
wsendEvent(evt)
}
}
String mode= sdCV(sCURM)
Integer Setpoint = idCV(sTARGTEMP).toInteger()
if(mode in [sCOOL,sAUTO])
wsendEvent(name: sCOOLSP, value: Setpoint)
if(mode in [sHEAT,sAUTO])
wsendEvent(name: sHEATSP, value: Setpoint)
}catch(e){
logError("parse error",e)
}
}
}
def setFanSetpoint(itemp){
logTrace( "setFanSetpoint($itemp)")
Integer temp
temp = itemp.toInteger()
logDebug("setTemperature : " + temp )
Boolean result = wsetACStates("fan", temp, sON, null, null)
if(result){
logInfo( "Fan temperature changed to " + temp + " for " + gtDNI())
generateModeEvent("fan")
generateSetTempEvent(temp)
}else{
generateErrorEvent()
}
cmdRefresh()
}
// Set Temperature
def setDrySetpoint(itemp){
logTrace( "setDrySetpoint($itemp)")
Integer temp
temp = itemp.toInteger()
logDebug("setTemperature : " + temp )
Boolean result = wsetACStates("dry", temp, sON, null, null)
if(result){
logInfo( "Dry temperature changed to " + temp + " for " + gtDNI())
generateModeEvent("dry")
generateSetTempEvent(temp)
}else{
generateErrorEvent()
}
cmdRefresh()
}
void lowerTemperature(){
logTrace( "lowerTemperature()")
String operMode = sdCV(sCURM)
Integer Setpoint
Setpoint = idCV(sTARGTEMP)
logDebug("Current target temperature = ${Setpoint}")
Setpoint = temperatureDown(Setpoint)
if(Setpoint == -1){
return
}
switch (operMode){
case sHEAT:
setHeatingSetpoint(Setpoint)
break
case sCOOL:
setCoolingSetpoint(Setpoint)
break
case "fan":
setFanSetpoint(Setpoint)
break
case "dry":
setDrySetpoint(Setpoint)
break
case sAUTO:
setHeatingSetpoint(Setpoint)
setCoolingSetpoint(Setpoint)
break
default:
break
}
}
void raiseTemperature(){
logTrace( "raiseTemperature()" )
String operMode = sdCV(sCURM)
Integer Setpoint
Setpoint = idCV(sTARGTEMP)
logDebug("Current target temperature = ${Setpoint}")
Setpoint = temperatureUp(Setpoint)
if(Setpoint == -1)
return
switch (operMode){
case sHEAT:
setHeatingSetpoint(Setpoint)
break
case sCOOL:
setCoolingSetpoint(Setpoint)
break
case "fan":
setFanSetpoint(Setpoint)
break
case "dry":
setDrySetpoint(Setpoint)
break
case sAUTO:
setHeatingSetpoint(Setpoint)
setCoolingSetpoint(Setpoint)
break
default:
break
}
}
Integer temperatureUp(Integer temp){
logTrace( "temperatureUp($temp)")
List values= GetTempValues()
if(values==null) return -1
List found
found = values.findAll{ number -> number > temp} as List
logDebug("Values retrieved : " + found)
Integer res
if(found == null || found.empty) res = values.last() as Integer
else res = found.first()
logDebug("Temp before: " + temp )
logDebug("Temp after : " + res)
return res
}
void raiseCoolSetpoint(){
logTrace( "raiseCoolSetpoint()")
Integer Setpoint
Setpoint = idCV(sTARGTEMP)
logDebug("Current target temperature = ${Setpoint}")
Setpoint = temperatureUp(Setpoint)
Boolean result = wsetACStates(null, Setpoint, sON, null, null)
if(result){
logInfo( "Cooling temperature changed to " + Setpoint + " for " + gtDNI())
if(sdCV(sSW) == sOFF){ generateSwitchEvent(sON) }
wsendEvent(name: sCOOLSP, value: Setpoint)
// todo auto?
generateSetTempEvent(Setpoint)
logDebug("New target Temperature = ${Setpoint}")
}else{
generateErrorEvent()
}
cmdRefresh()
}
void raiseHeatSetpoint(){
logTrace( "raiseHeatSetpoint()")
Integer Setpoint
Setpoint = idCV(sTARGTEMP)
String theTemp = gtTempUnit()
logDebug("Current target temperature = ${Setpoint}")
Setpoint = temperatureUp(Setpoint)
Boolean result = wsetACStates(null, Setpoint, sON, null, null)
if(result){
logInfo( "Heating temperature changed to " + Setpoint + " for " + gtDNI())
if(sdCV(sSW) == sOFF){ generateSwitchEvent(sON) }
wsendEvent(name: sHEATSP, value: Setpoint)
// todo auto?
generateSetTempEvent(Setpoint)
logDebug("New target Temperature = ${Setpoint}")
}else{
generateErrorEvent()
}
cmdRefresh()
}
List GetTempValues(String mode=sNULL){
String sunit = gtTempUnit()
Map capabilities = gtCapabilities( mode ?: sdCV(sCURM))
List values
if(sunit == sF){
if(((Map)capabilities.remoteCapabilities).temperatures.F == null){
return null
}
values = ((Map>>)capabilities.remoteCapabilities).temperatures.F.values
}else{
if(((Map)capabilities.remoteCapabilities).temperatures.C == null){
return null
}
values = ((Map>>)capabilities.remoteCapabilities).temperatures.C.values
}
return values
}
Integer temperatureDown(Integer temp){
logTrace( "temperatureDown($temp)")
List values= GetTempValues()
if(values==null) return -1
List found = values.findAll{ number -> number < temp} as List
logDebug("Values retrieved : " + found)
Integer res
if(found == null || found.empty) res = values.first() as Integer
else res = found.last()
logDebug("Temp before: " + temp )
logDebug("Temp after : " + res)
return res
}
void lowerCoolSetpoint(){
logTrace( "lowerCoolSetpoint()")
Integer Setpoint
Setpoint = idCV(sTARGTEMP)
logDebug("Current target temperature = ${Setpoint}")
Setpoint = temperatureDown(Setpoint)
Boolean result = wsetACStates(null, Setpoint, sON, null, null)
if(result){
logInfo( "Cooling temperature changed to " + Setpoint + " for " + gtDNI())
if(sdCV(sSW) == sOFF){ generateSwitchEvent(sON) }
wsendEvent(name: sCOOLSP, value: Setpoint)
// todo auto
generateSetTempEvent(Setpoint)
logDebug("New target Temperature = ${Setpoint}")
}else{
logDebug("error")
generateErrorEvent()
}
cmdRefresh()
}
void lowerHeatSetpoint(){
logTrace( "lowerHeatSetpoint()")
Integer Setpoint
Setpoint = idCV(sTARGTEMP)
logDebug("Current target temperature = ${Setpoint}")
Setpoint = temperatureDown(Setpoint)
Boolean result = wsetACStates(null, Setpoint, sON, null, null)
if(result){
logInfo( "Heating temperature changed to " + Setpoint + " for " + gtDNI())
if(sdCV(sSW) == sOFF){ generateSwitchEvent(sON) }
wsendEvent(name: sHEATSP, value: Setpoint)
generateSetTempEvent(Setpoint)
logDebug("New target Temperature = ${Setpoint}")
}else{
generateErrorEvent()
}
cmdRefresh()
}
def dfanLevel(String newLevel){
logTrace( "dfanLevel " + newLevel)
Map capabilities = gtCapabilities(sdCV(sCURM))
String Level; Level = newLevel
if(capabilities.remoteCapabilities != null){
// see if fan level exists
List fanLevels = ((Map)capabilities.remoteCapabilities).fanLevels
//logDebug("Fan levels capabilities : " + fanLevels)
if(!(Level in fanLevels)){
Level = GetNextFanLevel(Level,fanLevels)
logWarn("Changing Fan : " + Level)
}
Boolean result = wsetACStates( null, null, sON, Level, null)
if(result){
if(sdCV(sSW) == sOFF){ generateSwitchEvent(sON) }
generatefanLevelEvent(Level)
}else{
generateErrorEvent()
}
cmdRefresh()
}else{
logWarn("Fan mode $newLevel does not exist")
// other instructions may be required if mode does not exist
}
}
def setAll(String newMode,temp,String fan){
logTrace( "setAll() " + newMode + ",$temp," + fan )
Integer Setpoint = temp.toInteger()
String LevelBefore = fan
Map capabilities = gtCapabilities(newMode)
String Level
Level = LevelBefore
if(capabilities.remoteCapabilities != null){
// see if fan level exists
List fanLevels = ((Map)capabilities.remoteCapabilities).fanLevels
//logDebug("Fan levels capabilities : " + fanLevels)
if(!(Level in fanLevels)){
Level = GetNextFanLevel(Level,fanLevels)
logWarn("Changing Fan : " + Level)
}
Boolean result = wsetACStates(newMode, Setpoint, sON, Level, null)
if(result){
generateModeEvent(newMode)
if(LevelBefore != Level){
generatefanLevelEvent(Level)
}
}else{
generateErrorEvent()
}
cmdRefresh()
}else{
generateErrorEvent()
}
}
def fullswing(){
logTrace( "fullswing()")
setSwingMode("rangeFull")
}
Integer GetMinMax(String mode,Boolean min,Boolean isIn,value){
List values= GetTempValues(mode)
if(values==null || values.empty) return -1
Integer res
if(values.size() && !isIn) {
res= min ? values.first() : values.last()
return res
}
if(isIn) return (value.toInteger() in values) ? value.toInteger() : -1
else return -1
}
def resetMinMax(){
logTrace("resetMinMax()")
// reset these to what AC reports
setMinCoolTemp(null)
setMaxCoolTemp(null)
setMinHeatTemp(null)
setMaxHeatTemp(null)
}
def setMinCoolTemp(Double value=null){
logTrace("setMinCoolTemp($value)")
Integer v= GetMinMax('cool', true, (value!=null), value)
String units = gtLtScale()
if(v!= -1){
Integer t = idCV(sCOOLSP)
wsendEvent(name: "minCoolTemp", value: v, unit: units)
wsendEvent(name: "minCoolingSetpoint", value: v, unit: units)
if(t < value){
//setCoolingSetpoint(value) // this may turn on system
}
}else{
logWarn("invalid min cool temperature $value "+units)
}
}
def setMaxCoolTemp(Double value=null){
logTrace("setMaxCoolTemp($value)")
Integer v= GetMinMax('cool', false, (value!=null), value)
String units = gtLtScale()
if(v!= -1){
Integer t = idCV(sCOOLSP)
wsendEvent(name: "maxCoolTemp", value: v, unit: units)
wsendEvent(name: "maxCoolingSetpoint", value: v, unit: units)
if(t > value){
//setCoolingSetpoint(value) // this may turn on system
}
}else{
logWarn("invalid max cool temperature $value "+units)
}
}
def setMinHeatTemp(Double value=null){
logTrace("setMinHeatTemp($value)")
Integer v= GetMinMax('heat', true, (value!=null), value)
String units = gtLtScale()
if(v!= -1){
Integer t = idCV(sHEATSP)
wsendEvent(name: "minHeatTemp", value: v, unit: units)
wsendEvent(name: "minHeatingSetpoint", value: v, unit: units)
if(t < value){
//setHeatingSetpoint(value) // this may turn on system
}
}else{
logWarn("invalid min heat temperature $value "+units)
}
}
def setMaxHeatTemp(Double value=null){
logTrace("setMaxHeatTemp($value)")
Integer v= GetMinMax('heat', false, (value!=null), value)
String units = gtLtScale()
if(v!= -1){
Integer t = idCV(sHEATSP)
wsendEvent(name: "maxHeatTemp", value: v, unit: units)
wsendEvent(name: "maxHeatingSetpoint", value: v, unit: units)
if(t > value){
//setHeatingSetpoint(value) // this may turn on system
}
}else{
logWarn("invalid max heat temperature $value "+units)
}
}
void setAirConditionerMode(String modes){
logTrace( "setAirConditionerMode($modes)")
String currentMode = sdCV(sCURM)
logDebug("switching AC mode from current mode: $currentMode")
switch (modes){
case sCOOL:
cool()
break
case "fanOnly":
case "fan":
modeFan()
break
case "dry":
modeDry()
break
case sAUTO:
auto()
break
case sHEAT:
heat()
break
case sOFF:
off()
break
default:
generateErrorEvent()
}
}
def setAirConditionerFanMode(String mode){
logTrace( "setAirConditionerFanMode($mode)")
switch (mode){
case sON:
fanOn()
break
case "circulate":
fanCirculate()
break
case sAUTO:
fanAuto()
break
case "quiet":
fanQuiet()
break
case "low":
fanLow()
break
case "medium":
fanMedium()
break
case "high":
fanHigh()
break
case "strong":
fanStrong()
break
default:
generateErrorEvent()
}
}
// toggle Climate React
void toggleClimateReact(){
String currentClimateMode = sdCV("Climate")
logTrace( "toggleClimateReact() current Climate: $currentClimateMode")
switch (currentClimateMode){
case sOFF:
setClimateReact(sON)
break
case sON:
setClimateReact(sOFF)
break
}
}
// Set Climate React on/off
def setClimateReact(String ClimateState){
logTrace( "setClimateReact($ClimateState)")
Boolean result = (Boolean)parent.setClimateReact(this, gtDNI(), ClimateState)
if(result){
logInfo( "Climate React changed to " + ClimateState + " for " + gtDNI())
wsendEvent(name: 'Climate', value: ClimateState)
}else{
generateErrorEvent()
}
cmdRefresh()
}
def setClimateReactConfiguration(String on_off, String stype,ilowThres, ihighThres,String lowState,String highState){
///////////////////////////////////////////////
// on_off : enable climate react string on/off
// stype : possible values are sTEMP, sHUMIDITY or "feelsLike"
// lowThres and highThres - number parameters (temperature or humidity)
// lowState and highState are json MAP: (entries can be left out if not needed)
// to turn on AC:
// {"on": true, "targetTemperature": 21.0, "fanLevel":"auto", "temperatureUnit":"C", "mode":"heat", "swing": "stopped"}
// to turn off AC:
// {"on": false}
//
// Some examples:
//
// Range 19-24 Celcius, start to heat to 22 at auto fan if the temp is lower than 19 and stop the AC when higher than 24
// setClimateReactConfiguration('on','temperature',19, 24,
// '{"on": true, "targetTemperature": 22.0, "fanLevel":"auto", "temperatureUnit":"C", "mode":"heat", "swing": "stopped"}',
// '{"on": false}' )
//
// Range 67-68 Farenheit, start to heat to 68 at auto fan if the temp is lower than 67 and stop the AC when higher than 68
// setClimateReactConfiguration('on', 'temperature',67, 68,
// '{"on": true, "targetTemperature": 68.0, "fanLevel":"auto", "temperatureUnit":"F", "mode":"heat", "swing": "stopped"}',
// '{"on": false}' )
//
///////////////////////////////////////////////
logTrace( "setClimateReactConfiguration()")
Double lowThres, highThres
lowThres= ilowThres as Double
highThres= ihighThres as Double
if(gtLtScale() == sF){
lowThres = fToC(lowThres).round(1)
highThres = fToC(highThres).round(1)
}
Map lowStateMap = new JsonSlurper().parseText(lowState)
Map highStateMap = new JsonSlurper().parseText(highState)
Map lowStateJson, highStateJson
if(lowStateMap){
lowStateJson = lowStateMap
/* [
on: lowStateList[0],
fanLevel: lowStateList[1],
temperatureUnit: lowStateList[2],
targetTemperature: lowStateList[3],
mode: lowStateList[4]
] */
}else{ lowStateJson = null }
if(highStateMap){
highStateJson = highStateMap
/* [
on: highStateList[0],
fanLevel: highStateList[1],
temperatureUnit: highStateList[2],
targetTemperature: highStateList[3],
mode: highStateList[4]
] */
}else{ highStateJson = null }
Boolean on= (on_off==sON)
// smart_type ?
// low_temperature_threshold ?
// high_temperature_threshold ?
// enable_climate_react ??
Map root = [
enabled: on,
deviceUid: gtDNI(),
type: stype,
// highTemperatureWebhook: null,
highTemperatureThreshold: highThres,
highTemperatureState: highStateJson,
// lowTemperatureWebhook: null,
lowTemperatureState: lowStateJson,
lowTemperatureThreshold: lowThres
]
String json1= JsonOutput.toJson(root)
logDebug("CLIMATE REACT JSON STRING : " + JsonOutput.prettyPrint(json1))
Boolean result = parent.setClimateReactConfiguration(this, gtDNI(), json1)
if(result){
logInfo( "Climate React settings changed for " + gtDNI())
wsendEvent(name: 'Climate', value: on_off)
}else{
generateErrorEvent()
}
cmdRefresh()
}
/*
def switchFanLevel(){
logTrace( "switchFanLevel()")
def currentFanMode = sdCV(sFANMODE)
logDebug("switching fan level from current mode: $currentFanMode")
def returnCommand
switch (currentFanMode){
case "low":
returnCommand = dfanLevel("medium")
break
case "medium":
returnCommand = dfanLevel("high")
break
case "high":
returnCommand = dfanLevel(sAUTO)
break
case sAUTO:
returnCommand = dfanLevel("quiet")
break
case "quiet":
returnCommand = dfanLevel("medium_high")
break
case "medium_high":
returnCommand = dfanLevel("medium_low")
break
case "medium_low":
returnCommand = dfanLevel("strong")
break
case "strong":
returnCommand = dfanLevel("low")
break
}
returnCommand
} */
String GetNextSwingMode(String swingMode, ListswingModes){
logTrace( "GetNextSwingMode() " + swingMode)
if(!swingModes){
return (String)null
}
List listSwingMode = ['stopped','fixedTop','fixedMiddleTop','fixedMiddle','fixedMiddleBottom','fixedBottom','rangeTop','rangeMiddle','rangeBottom','rangeFull','horizontal','both']
String newSwingMode = returnNext(listSwingMode,swingModes,swingMode)
logDebug("Next Swing Mode = " + newSwingMode)
return newSwingMode
}
void switchSwing(){
logTrace( "switchSwing()")
String currentMode = sdCV(sSWING)
logDebug("switching Swing mode from current mode: $currentMode")
switch (currentMode){
case "stopped":
setSwingMode("fixedTop")
break
case "fixedTop":
setSwingMode("fixedMiddleTop")
break
case "fixedMiddleTop":
setSwingMode("fixedMiddle")
break
case "fixedMiddle":
setSwingMode("fixedMiddleBottom")
break
case "fixedMiddleBottom":
setSwingMode("fixedBottom")
break
case "fixedBottom":
setSwingMode("rangeTop")
break
case "rangeTop":
setSwingMode("rangeMiddle")
break
case "rangeMiddle":
setSwingMode("rangeBottom")
break
case "rangeBottom":
setSwingMode("rangeFull")
break
case "rangeFull":
setSwingMode("horizontal")
break
case "horizontal":
setSwingMode("both")
break
case "both":
setSwingMode("stopped")
break
}
}
def setSwingMode(String newSwing){
logTrace( "setSwingMode($newSwing)")
String SwingBefore = sdCV(sSWING)
Map capabilities = gtCapabilities(sdCV(sCURM))
String Swing
Swing = SwingBefore
if(capabilities.remoteCapabilities != null){
List Swings = ((Map)capabilities.remoteCapabilities).swing
logDebug("Swing capabilities : " + Swings)
if(!(Swing in Swings)){
Swing = GetNextSwingMode(newSwing,Swings)
logDebug("Changing Swing : " + Swing)
}
Boolean result = wsetACStates(null, null, sON, null, Swing)
if(result){
generateSwingModeEvent(Swing)
if(sdCV(sSW) == sOFF){ generateSwitchEvent(sON) }
}else{
generateErrorEvent()
}
cmdRefresh()
}else{
generateErrorEvent()
}
}
def generateSwingModeEvent(String mode){
String SwingBefore = sdCV(sSWING)
if(SwingBefore!=mode) logInfo( "Swing mode changed to " + mode + " for " + gtDNI())
wsendEvent(name: sSWING, value: mode, descriptionText: gtDisplayName()+" swing mode is now ${mode}")
}
String getThermostatDescriptionText(String name, value){
if(name in [sTEMP,sTARGTEMP,sTHERMSP,sCOOLSP,sHEATSP]){
return "$name is $value " + gtTempUnit()
}else if(name == sHUMIDITY){
return "$name is $value %"
}else if(name == sFANMODE){
return "fan level is $value"
}else if(name == sON){
return "switch is $value"
}else if(name in ["mode",sTHERMMODE, sTHERMOPER,sTHERMFANMODE]){
return "$name is ${value}"
}else if(name == sCURM){
return "thermostat mode was ${value}"
}else if(name == "powerSource"){
return "power source mode was ${value}"
}else if(name == "Climate"){
return "Climate React was ${value}"
}else if(name == sTEMPUNIT){
return "thermostat unit was ${value}"
}else if(name == "voltage"){
return "Battery voltage was ${value}"
}else if(name == "battery"){
return "Battery was ${value}"
}else if(name == "voltage" || name== "battery"){
return "Battery voltage was ${value}"
}else if(name == sSWING){
return "Swing mode was ${value}"
}else if(name == "Error"){
String str = (value == "Failed") ? "failed" : "success"
return "Last setACState was ${str}"
}else{
return "${name} = ${value}"
}
}
// parse events into attributes
def parse(String description){
logDebug("parse '${description}'")
// this does nothing for virtual device
/*
String name; name = null
def value; value = null
String statusTextmsg = ""
def msg = parseLanMessage(description)
def headersAsString = msg.header // => headers as a string
def headerMap = msg.headers // => headers as a Map
def body = msg.body // => request body as a string
def status = msg.status // => http status code of the response
def json = msg.json // => any JSON included in response body, as a data structure of lists and maps
def xml = msg.xml // => any XML included in response body, as a document tree structure
def data = msg.data // => either JSON or XML in response body (whichever is specified by content-type header in response)
if(description?.startsWith("on/off:")){
logDebug("Switch command")
name = sSW
value = description?.endsWith(" 1") ? sON : sOFF
}else if(description?.startsWith(sTEMP)){
logDebug("Temperature")
name = sTEMP
value = device.currentValue(sTEMP)
}else if(description?.startsWith(sHUMIDITY)){
logDebug("Humidity")
name = sHUMIDITY
value = idCV(sHUMIDITY)
}else if(description?.startsWith(sTARGTEMP)){
logDebug(sTARGTEMP)
name = sTARGTEMP
value = idCV(sTARGTEMP)
}else if(description?.startsWith(sFANMODE)){
logDebug(sFANMODE)
name = sFANMODE
value = sdCV(sFANMODE)
}else if(description?.startsWith(sCURM)){
logDebug("mode")
name = sCURM
value = sdCV(sCURM)
}else if(description?.startsWith(sON)){
logDebug(sON)
name = sON
value = sdCV(sON)
}else if(description?.startsWith(sSW)){
logDebug(sSW)
name = sSW
value = sdCV(sON)
}else if(description?.startsWith(sTEMPUNIT)){
logDebug(sTEMPUNIT)
name = sTEMPUNIT
value = gtTempUnit()
}else if(description?.startsWith("Error")){
logDebug("Error")
name = "Error"
value = sdCV("Error")
}else if(description?.startsWith("voltage")){
logDebug("voltage")
name = "voltage"
value = device.currentValue("voltage")
}else if(description?.startsWith("battery")){
logDebug("battery")
name = "battery"
value = idCV("battery")
}else if(description?.startsWith(sSWING)){
logDebug(sSWING)
name = sSWING
value = sdCV(sSWING)
}
def result = createEvent(name: name, value: value)
logDebug("Parse returned ${result?.descriptionText}")
return result */
}
def ping(){
logTrace( "calling parent ping()")
return parent.ping()
}
public void enableDebugLog(){ device.updateSetting("logDebug",[value:sTRUE,type:"bool"]); logInfo("Debug Logs Enabled From Main App...") }
public void disableDebugLog(){ device.updateSetting("logDebug",[value:sFALSE,type:"bool"]); logInfo("Debug Logs Disabled From Main App...") }
public void enableTraceLog(){ device.updateSetting("logTrace",[value:sTRUE,type:"bool"]); logInfo("Trace Logs Enabled From Main App...") }
public void disableTraceLog(){ device.updateSetting("logTrace",[value:sFALSE,type:"bool"]); logInfo("Trace Logs Disabled From Main App...") }
private void logDebug(String msg){ if((Boolean)settings.logDebug){ log.debug logPrefix(msg, "purple") } }
private void logInfo(String msg){ if((Boolean)settings.logInfo != false){ log.info logPrefix(msg, "#0299b1") } }
private void logTrace(String msg){ if((Boolean)settings.logTrace){ log.trace logPrefix(msg, sCLRGRY) } }
private void logWarn(String msg){ if((Boolean)settings.logWarn != false){ log.warn logPrefix(sSPACE + msg, sCLRORG) } }
private void logError(String msg, Exception ex=null){
if((Boolean)settings.logError != false){
log.error logPrefix(msg, sCLRRED)
String a
try{
if(ex) a = getExceptionMessageWithLine(ex)
}catch(ignored){ }
if(a) log.error logPrefix(a, sCLRRED)
}
}
static String span(String str, String clr=sNULL, String sz=sNULL, Boolean bld=false, Boolean br=false){ return str ? "${str}${br ? sLINEBR : sBLANK}" : sBLANK }
String logPrefix(String msg, String color = sNULL){
return span("Sensibo ${gtDisplayName()} (v" + devVersionFLD + ") | ", sCLRGRY) + span(msg, color)
}
static String getObjType(obj){
if(obj instanceof String){return "String"}
else if(obj instanceof GString){return "GString"}
else if(obj instanceof Map){return "Map"}
else if(obj instanceof LinkedHashMap){return "LinkedHashMap"}
else if(obj instanceof HashMap){return "HashMap"}
else if(obj instanceof List){return "List"}
else if(obj instanceof ArrayList){return "ArrayList"}
else if(obj instanceof Integer){return "Integer"}
else if(obj instanceof BigInteger){return "BigInteger"}
else if(obj instanceof Long){return "Long"}
else if(obj instanceof Boolean){return "Boolean"}
else if(obj instanceof BigDecimal){return "BigDecimal"}
else if(obj instanceof Float){return "Double"}
else if(obj instanceof Float){return "Float"}
else if(obj instanceof Byte){return "Byte"}
else if(obj instanceof com.hubitat.app.DeviceWrapper)return 'Device'
else{ return "unknown"}
}
void wsendEvent(Map prop){ sendEvent(prop) }
Boolean wsetACStates(String imode, targetTemperature, String ion, String fanLevel, String swingM){
String mode= imode?: sdCV(sCURM)
Integer Setpoint = targetTemperature!=null ? targetTemperature : idCV(sTARGTEMP)
String on= ion ?: sON
String fan = fanLevel ?: sdCV(sFANMODE)
String swing = swingM ?: sdCV(sSWING)
logDebug("Temp Unit (Setpoint) : " + Setpoint+" Temp Unit : " + gtTempUnit())
return (Boolean)parent.setACStates(
this,
gtDNI(),
on,
mode,
Setpoint,
fan,
swing,
gtTempUnit())
}
String gtTempUnit(){ return sdCV(sTEMPUNIT) }
private Integer idCV(String a){ return device.currentValue(a).toInteger() }
private String sdCV(String a){ return (String)device.currentValue(a) }
String gtDisplayName(){ return (String)device.displayName }
String gtDNI(){ return (String)device.deviceNetworkId }
private Map gtCapabilities(String mode){ return (Map)parent.getCapabilities(gtDNI(), mode) }
private String gtLtScale(){ return (String)location.getTemperatureScale() }
Double getThermostatResolution(){
return gtLtScale() == sC ? 0.5D : 1.0D
}
def roundDegrees(Double value){
return gtLtScale() == sC ? Math.round(value * 2.0D) / 2.0D : Math.round(value)
}