Anybody using a Sensibo?

It definitely needs some TLC. I really wish it were a local solution. I'm really thinking to switch out for RM3's instead - much cheaper, but not as elegant a solution out of the box of course. I've asked Sensibo for help to properly support Hubitat and they just don't give a fig, that much is clear.

Yeah, I've done the same and been given the usual "we'll look into it", followed by silence.

I suspect they are simply happy to aim at customers who use their mobile apps and don't see home automation hubs as a priority (Echo's excepted).

If you're ok with node-red and homebridge, it looks like sensibo is well supported via plugins.

1 Like

I've got HomeBridge already - I didnt realise Sensibo was supported - there's a ton of Plugins!

How do you see the Sensibo in HB from HE?

That's where node red comes in (for me anyway)

There are various HB HE plugins but as I've not tried them I'm not sure how they work. I use NR to link to HB devices (although I've still to do the sensibo one - I'll get round to it at some point) and then back to HE via maker API.

So as mentioned I'm not a Coder, but after having a squiz thru the Device code, it appears that every single time any value for any field changes, it refreshes them all via this chunk of code.

def generateStatusEvent() {
    def temperature = device.currentValue("temperature").toDouble()  
    def humidity = device.currentValue("humidity").toDouble() 
    def targetTemperature = device.currentValue("targetTemperature").split(' ')[0].toDouble()
    def fanLevel = device.currentValue("fanLevel")
    def mode = device.currentValue("currentmode")
    def on = device.currentValue("on")
    def swing = device.currentValue("swing")
    def ClimateReact = device.currentValue("Climate")
    
	def error = device.currentValue("Error")
                    
    def statusTextmsg = ""
    def sUnit = device.currentValue("temperatureUnit")

    statusTextmsg = "${humidity}%"
   
    sendEvent("name":"statusText", "value":statusTextmsg, "description":"", displayed: false, isStateChange: true)
}

I'm not certain but I think the main "update" check section is from Line 1463 to 1707

def parseEventData(Map results)

If I remove the call to generateStatusEvent () from the end of this big chunk and insert the event calls into the relevant section, it might only update the events when they change, instead of on each poll.

Nope, that wasnt it - a bit more digging reveals this section:

// parse events into attributes
def parse(String description) {
	displayDebugLog("Parsing '${description}'")
    
    def name = null
	def value = null
    def 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)

It just blats the data into the attributes regardless of if they have changed. :frowning:

Appreciate your efforts on it!

1 Like

I think I know how to fix it, I’ll have another crack at it today.

1 Like

Argh, I have to admit defeat - I simply can not find a way to prevent this crappy App/Driver from refreshing every variable, every time. :rage: I spent hours on it today and got nowhere. :cry:

Take a rest mate. Maybe one of the coders/developers here can eventually assist us. Agree it is kindofa crappy app from the perspective of hub load. I'm just gonna synchronise every 30 minutes now - really not ideal.

I see @bptworld posted above and he certainly knows his way around Groovy! Maybe he might take a look at it for us to see if the code can be improved, if it is asynchronous etc?

1 Like

That would be nice as I really suck at programming.

Im changing mine to 5 min intervals for now.

Sorry, I stopped using this device over a year ago. Never could get it to work properly. Device would lock up and need to be unplugged and then plugged back in. Never worked when I needed it. It now sits in the draw of shame.

Ah OK, no problem. I have 10. They work fine (although sometimes quite slow) but they definitely put a load on the hub according to the logs/stats. Maybe one day Sensibo will wake up and look after their customers with a healthy device driver and local support (ok, ok, I know, unlikely but I can live in hope).

Where do you change that? Im gonna set mine as well. Got it working sort of... but none of my temp adjustments actually push to the device.

Under apps, go into Sensibo Integration and re-run the setup.

OK, I have done a fair amount of work in this driver / app combo. I really don't like the way that it performs what are essentially driver functions within the app so I did start out to rewrite it completely but ran out of time so I ended up refactoring and cleaning up the code quite considerably. It no longer performs multiple refreshes and will poll after sending API changes to the Sensibo service so that it can refresh the attributes in the hub. 2 days later and I have something that is more rock solid and doesn't flood the log file with useless crap every time some happens.

NOTE: I have withdrawn the Actuator capability and the Thermostat capability so if anybody has managed to get standard Thermostat functions to work with the old code they will all break - YOU HAVE BEEN WARNED!! The reason for this is that the Thermostat feature wasn't compatible with the modes available through Hubitat and I am not clever enough to work out how to reset the modes available - it was easier to withdraw the capability. Maybe somebody can help me with setting custom modes for Thermostats - reintroducing the feature is fairly straightforward if we can get it to work.

The best way to interact with your Sensibo devices is via Custom Actions. I have essentially coded my own version of Climate React using RM and much better logic than the 2 level option in the standard Sensibo Climate React feature. Early testing looks like it is working quite well.

Ok, now for the code.... Happy for you guys to test and report back. If the fixes are straight forward I'll implement. I'll post it in two subsequent posts because ther is a word limit on the forum that I just discoverd!

Have fun guys - I am not really a coder (at least not any more) so this has been quite a project for me but I have enjoyed it :slight_smile:

3 Likes

App Code

/**
 *  Sensibo Integration for Hubitat
 *
 *  Copyright 2019 Bryan Li
 *
 *  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
 *  2019-04-27    Based on work by Eric Gosselin - Modified for Hubitat
 *  2019-07-23    Merge robert1's logging changes
 *  2021-02-11    Refactored and cleaned up code (Paul Hutton - VeloWulf)
 *  
 */

definition(
    name: "Sensibo Integration",
    namespace: "joyfulhouse",
    author: "Bryan Li",
    description: "Connect your Sensibo Pod to Hubitat.",
    category: "Green Living",
    iconUrl: "", // empty string - not implemented in Hubitat
    iconX2Url: "", // empty string - not implemented in Hubitat
    iconX3Url: "", // empty string - not implemented in Hubitat
    singleInstance: true) 

{
    appSetting "apikey"
    appSetting "logLevel"
}

preferences {
    page(name: "SelectAPIKey", title: "API Key", content: "setAPIKey", nextPage: "deviceList", install: false, uninstall: true)
    page(name: "deviceList", title: "Sensibo", content:"SensiboPodList", install:true, uninstall: true)
    page(name: "timePage")
    page(name: "timePageEvent")
}

// Initial variable declarations
def getServerUrl() { return "https://home.sensibo.com" }
def getChildNamespace() { return "joyfulhouse" }
def getChildTypeName() { return "SensiboPod" }
def getapikey() { return apiKey }
def getPollRateMillis() { return 45 * 1000 }
def getCapabilitiesRateMillis() {return 60 * 1000 }


// Logging and Event management
// debug logging set by logLevel application preference
def debugLog() { 
	if (logLevel == "DEBUG")
		return true
	else
		return false 
}

def traceLog() { 
	if (logLevel == "TRACE")
		return true
	else
		return false 
}

def debugEvent(value, message) {

	def results = [
            name: "appdebug",
            value: value,
            descriptionText: message,
	]
	displayDebugLog( "Generating AppDebug Event: ${results}")
        if (debugLog() || traceLog()) {
            sendEvent (results)
        }
}

def pollHandler() {
    displayTraceLog("pollHandler called")
    //debugEvent ("in Poll() method.")
	
    // Hit the Sensibo API for update on all the Pod
	
    def PodList = getChildDevices()
    
    // Report on pods being refreshed
    displayDebugLog(PodList)
    
    PodList.each { 
    	displayDebugLog( "polling " + it.deviceNetworkId)
        pollChildren(it.deviceNetworkId) }
	
        state.sensibo.each {stat ->
            def dni = stat.key

            displayDebugLog( ("DNI = ${dni}"))
            // debugEvent ("DNI = ${dni}")

            def d = getChildDevice(dni)

            if(d) {        
                displayDebugLog( ("Found Child Device."))
		// debugEvent ("Found Child Device.")
		// debugEvent("Event Data before generate event call = ${stat}")
		displayDebugLog( state.sensibo[dni])
		// d.generateEvent(state.sensibo[dni].data)
            }
	}
}

// Integration version - not currently used
public static String version() { return "Hubitat" }

// Output log messages
private def displayDebugLog(message) {
	if (debugLog() == true) log.debug "${message}"
        if (traceLog() == true) log.trace "${message}"
}

private def displayTraceLog(message) {
	if (traceLog() == true) log.trace  "${message}"
}

// Capture APIkey and logging preference
def setAPIKey() {
	displayTraceLog( "setAPIKey()")
    
	if(appSettings)
    	def keystring = appSettings.apikey
    else {
        def keystring = ""
    }
	
    def p = dynamicPage(name: "SelectAPIKey", title: "Enter your API Key", uninstall: true) {
		section("API Key"){
			paragraph "Please enter your API Key provided by Sensibo \n\nAvailable at: \nhttps://home.sensibo.com/me/api"
		input(name: "apiKey", title:"", type: "text", required:true, multiple:false, description: "", defaultValue: keystring)
		}
	    	section("Logging"){
			paragraph "Application Logging Level"
			input(name: "logLevel", type: "enum", title: "Logging Level", required:true, multiple:false, options: ["NONE","DEBUG","TRACE"], defaultValue: "NONE")
		}
	}
    return p
}

def SensiboPodList()
{
    displayTraceLog( "SensiboPodList()")
    displayDebugLog( apiKey )

    def stats = getSensiboPodList()
    // displayDebugLog( "device list: $stats")
    
    def p = dynamicPage(name: "deviceList", title: "Select Your Sensibo Pod", uninstall: true) {
        section(""){
            paragraph "Tap below to see the list of Sensibo Pods available in your Sensibo account and select the ones you want to connect to Hubitat."
            input(name: "SelectedSensiboPods", title:"Pods", type: "enum", required:true, multiple:true, description: "Tap to choose",  options: stats)
            }
        
        section("Refresh") {
            input(name:"refreshinminutes", title: "Refresh rates in minutes", type: "enum", required:false, multiple: false, options: ["1", "5", "10","15","30"])
        }
    }
    return p
}

def getSensiboPodList()
{
	displayTraceLog( "getSensiboPodList called")
       
    def deviceListParams = [
    uri: "${getServerUrl()}",
    path: "/api/v2/users/me/pods",
    requestContentType: "application/json",
    // query: [apiKey:"${getapikey()}", integration:"${version()}", type:"json",fields:"id,room" ]]
    query: [apiKey:apiKey, type:"json",fields:"id,room" ]]

	def pods = [:]
	
    try {
        httpGet(deviceListParams) { resp ->
            if(resp.status == 200) {
		resp.data.result.each { pod ->
                    def key = pod.id
                    def value = pod.room.name

                    pods[key] = value
                    }
                state.pods = pods
		}
        }
    }
    
    catch(Exception e) 	{
        displayDebugLog( "Exception Get Json: " + e)
        // debugEvent ("Exception get JSON: " + e)
    }
    
    displayDebugLog( "Sensibo Pods: $pods"  )
	
    return pods
}

// Function that runs when the app is first installed
def installed() {
	displayTraceLog( "Installed() called with settings: ${settings}")

    // state.lastTemperaturePush = null
    // state.lastHumidityPush = null
  
    initialize()
    
    /*def d = getChildDevices() // Can't find that this actually does anything - probably remove (d is returned by the initiatize function and is then overwritten by this definition

	if (boolnotifevery) {
    	//runEvery1Hour("hournotification")
        schedule("0 0 * * * ?", "hournotification")
	}
    
    displayDebugLog( "Configured health checkInterval when installed()")
	sendEvent(name: "checkInterval", value: 2 * 60 * 60 + 2 * 60, displayed: true)*/
    
    //subscribe(d,"temperatureUnit",eTempUnitHandler)
    
    /*if (sendPushNotif) { 
    	subscribe(d, "temperature", eTemperatureHandler)
        subscribe(d, "humidity", eHumidityHandler)
    }*/ // Can't find the sendPushNotif variable anywhere - probably remove code
}

// Function that runs when the app is updated
def updated() {
    displayTraceLog( "Updated() called with settings: ${settings}")

    unschedule() // Remove any scheduled tasks. If method is called without parameters, all schedules will be removed.
    // unsubscribe() // Unsubscribe from events sent from a device or all event subscriptions.
	
    // state.lastTemperaturePush = null
    // state.lastHumidityPush = null
    
    initialize()
    
    /*def d = getChildDevices() // Can't find that this actually does anything - probably remove (d is returned by the initiatize function and is then overwritten by this definition
    
    if (boolnotifevery) {
    	//runEvery1Hour("hournotification")
        schedule("0 0 * * * ?", "hournotification")
	}
    
    displayDebugLog( "Configured health checkInterval when installed()")
	sendEvent(name: "checkInterval", value: 2 * 60 * 60 + 2 * 60, displayed: true)*/
    
    //subscribe(d,"temperatureUnit",eTempUnitHandler)
    
    /*if (sendPushNotif) {
    	subscribe(d, "temperature", eTemperatureHandler)
        subscribe(d, "humidity", eHumidityHandler)
    }*/ // Can't find the sendPushNotif variable anywhere - probably remove code
}

// Function that runs when the app is removed (uninstalled)
def uninstalled() {
    // Needs code
    displayTraceLog ( "Uninstalled called with settings: ${settings}")
}

def ping() {

	displayTraceLog( "ping called")
    def returnStatus = true
    
    def deviceListParams = [
    uri: "${getServerUrl()}",
    path: "/api/v2/users/me/pods",
    requestContentType: "application/json",
    query: [apiKey:"${getapikey()}", integration:"${version()}", type:"json",fields:"id,room" ]]
	
    try {
      httpGet(deviceListParams) { resp ->
    	if(resp.status == 200)
			{
				returnStatus = true
			}
	  }
    }
    catch(Exception e)
	{
		displayDebugLog( "Exception Get Json: " + e)
		// debugEvent ("Exception get JSON: " + e)
		
		returnStatus = false
	}
    
    return returnStatus
}

def hournotification() {
	displayTraceLog( "hournotification() called")
    
	def hour = new Date()
	def curHour = hour.format("HH:mm",location.timeZone)
	def curDay = hour.format("EEEE",location.timeZone)
	// Check the time Threshold
    def stext = ""
	if (startTimeEvent && endTimeEvent) {
 		def minHour = new Date().parse(smartThingsDateFormat(), startTimeEvent)
    	def endHour = new Date().parse(smartThingsDateFormat(), endTimeEvent)

    	def minHourstr = minHour.format("HH:mm",location.timeZone)
    	def maxHourstr = endHour.format("HH:mm",location.timeZone)

    	if (curHour >= minHourstr && curHour <= maxHourstr) 
    	{
    		def devices = getChildDevices()
            devices.each { d ->
                displayTraceLog( "Notification every hour for device: ${d.id}")
                def currentPod = d.displayName
                def currentTemperature = d.currentState("temperature").value
                def currentHumidity = d.currentState("humidity").value
                def currentBattery = d.currentState("voltage").value
                def sunit = d.currentState("temperatureUnit").value
                stext = "${currentPod} - Temperature: ${currentTemperature} ${sunit} Humidity: ${currentHumidity}% Battery: ${currentBattery}"    
                
                sendPush(stext)
            }
    	}
    }
    else {
    	 	def devices = getChildDevices()
            devices.each { d ->
                displayTraceLog( "Notification every hour for device: ${d.id}")
                def currentPod = d.displayName
                def currentTemperature = d.currentState("temperature").value
                def currentHumidity = d.currentState("humidity").value
                def currentBattery = d.currentState("voltage").value
                def sunit = d.currentState("temperatureUnit").value
                stext = "${currentPod} - Temperature: ${currentTemperature} ${sunit} Humidity: ${currentHumidity}% Battery: ${currentBattery}"    
                
                sendPush(stext)
            }
    }
}

//def switchesHandler(evt)
//{
//  if (evt.value == "on") {
//        displayDebugLog( "switch turned on!")
//    } else if (evt.value == "off") {
//        displayDebugLog( "switch turned off!")
//    }
//}

def eTempUnitHandler(evt)
{
	//refreshOneDevice(evt.device.displayName)
}

def eTemperatureHandler(evt){
	def currentTemperature = evt.device.currentState("temperature").value
    def currentPod = evt.device.displayName
    def hour = new Date()
    
    if (inDateThreshold(evt,"temperature") == true) {
        if(maxTemperature != null){
            if(currentTemperature.toDouble() > maxTemperature)
            {
            	def stext = "Temperature level is too high at ${currentPod} : ${currentTemperature}"
				sendEvent(name: "lastTemperaturePush", value: "${stext}",  displayed : "true", descriptionText:"${stext}")
                sendPush(stext)

                state.lastTemperaturePush = hour
            }
        }
        if(minTemperature != null) {
            if(currentTemperature.toDouble() < minTemperature)
            {	
            	def stext = "Temperature level is too low at ${currentPod} : ${currentTemperature}"
                sendEvent(name: "lastTemperaturePush", value: "${stext}",  displayed : "true", descriptionText:"${stext}")
                sendPush(stext)

                state.lastTemperaturePush = hour
            }
        }
    } 
}

def eHumidityHandler(evt){
	def currentHumidity = evt.device.currentState("humidity").value
    def currentPod = evt.device.displayName
    def hour = new Date()
    if (inDateThreshold(evt,"humidity") == true) { 
        if(maxHumidity != null){
            if(currentHumidity.toDouble() > maxHumidity)
            {   
            	def stext = "Humidity level is too high at ${currentPod} : ${currentHumidity}"
                sendEvent(name: "lastHumidityPush", value: "${stext}", displayed : "true", descriptionText:"${stext}")
                sendPush(stext)
                
                state.lastHumidityPush = hour
            }
        }
        if(minHumidity != null) {
            if(currentHumidity.toDouble() < minHumidity)
            {
            	def stext = "Humidity level is too low at ${currentPod} : ${currentHumidity}"
                sendEvent(name: "lastHumidityPush", value: "${stext}", displayed : "true", descriptionText:"${stext}")
                sendPush(stext)
                
                state.lastHumidityPush = hour
            }
        }
    }
}

public smartThingsDateFormat() { "yyyy-MM-dd'T'HH:mm:ss.SSSZ" }
public smartThingsDateFormatNoMilli() { "yyyy-MM-dd'T'HH:mm:ssZ" }

def canPushNotification(currentPod, hour,sType) {
    // Check if the client already received a push
    if (sType == "temperature") {
        if (sfrequency.toString().isInteger()) {
            if (state.lastTemperaturePush != null) {
                long unxNow = hour.time

                def before = new Date().parse(smartThingsDateFormatNoMilli(),state.lastTemperaturePush)
                long unxEnd = before.time
                
                unxNow = unxNow/1000
                unxEnd = unxEnd/1000
                def timeDiff = Math.abs(unxNow-unxEnd)
                timeDiff = timeDiff/60
                if (timeDiff <= sfrequency)
                {
                    return false
                }
            }
    	}
    }
    else {
        if (sfrequency.toString().isInteger()) {
            if (state.lastHumidityPush != null) {
                long unxNow = hour.time
                
                def before = new Date().parse(smartThingsDateFormatNoMilli(),state.lastHumidityPush)
                long unxEnd = before.time

                unxNow = unxNow/1000
                unxEnd = unxEnd/1000
                def timeDiff = Math.abs(unxNow-unxEnd)
                timeDiff = timeDiff/60

                if (timeDiff <= sfrequency)
                {
                    return false
                }
            }
    	}
   	}

    return true
}

def inDateThreshold(evt,sType) {
	def hour = new Date()
	def curHour = hour.format("HH:mm",location.timeZone)
	def curDay = hour.format("EEEE",location.timeZone)
    def currentPod = evt.device.displayName
     
    // Check if the client already received a push
    
    def result = canPushNotification(currentPod,hour, sType)
    if (!result) { 
        return false 
    }
   
    // Check the day of the week
    if (days != null && !days.contains(curDay)) {
    	return false
    }
    
	// Check the time Threshold
	if (startTime && endTime) {
 		def minHour = new Date().parse(smartThingsDateFormat(), startTime)
    	def endHour = new Date().parse(smartThingsDateFormat(), endTime)

    	def minHourstr = minHour.format("HH:mm",location.timeZone)
    	def maxHourstr = endHour.format("HH:mm",location.timeZone)

    	if (curHour >= minHourstr && curHour < maxHourstr) 
    	{
    		return true
    	}
    	else
    	{ 
	    	return false
	    }
    }
    return true
}

def refresh() {
	displayTraceLog( "refresh() called with rate of " + refreshinminutes + " minutes")

    unschedule()
    
	refreshDevices()
    
    if (refreshinminutes == "1") 
    	runEvery1Minute("refreshDevices")
    else if (refreshinminutes == "5")
    	runEvery5Minutes("refreshDevices")
    else if (refreshinminutes == "10")
    	runEvery10Minutes("refreshDevices")
    else if (refreshinminutes == "15") 
    	runEvery15Minutes("refreshDevices")
    else if (refreshinminutes == "30")
    	runEvery30Minutes("refreshDevices")
    else
        runEvery10Minutes("refreshDevices")
}

def refreshOneDevice(dni) {
	displayTraceLog( "refreshOneDevice() called")
	def d = getChildDevice(dni)
	d.refresh()
}

def initialize() {
    displayTraceLog( "initialize() called")
    displayTraceLog( "key "+ getapikey())
    
    state.apikey = getapikey()
	  
	def devices = SelectedSensiboPods.collect { dni ->
            displayDebugLog("Initialising " + dni)
            def d = getChildDevice(dni) // grab child devices with the current collection entry

            if(!d) // the return is empty so no device exists in which case add it
                {
                def name = getSensiboPodList().find( {key,value -> key == dni })

                displayDebugLog( "Pod : ${name.value} - Hub : ${location.hubs[0].name} - Type : " +  getChildTypeName() + " - Namespace : " + getChildNamespace())

                d = addChildDevice(getChildNamespace(), getChildTypeName(), dni, location.hubs[0].id, [
                    "label" : "${name.value} Pod",
                    "name" : "${name.value} Pod"
                        ]
                )
                // d.setIcon("on","on","https://image.ibb.co/jgAMW8/sensibo-sky-off.png") // can't find eveidence that this does anything - probably remove
                // d.setIcon("off","on","https://image.ibb.co/jgAMW8/sensibo-sky-off.png")
                d.save()              

                displayTraceLog( "created ${d.displayName} with id $dni")
                            }
            else { // Tell the user that the device already exists
                displayTraceLog( "found ${d.displayName} with id $dni already exists")
            }

            return d
        }

    displayTraceLog( "created ${devices.size()} Sensibo Pod")

    def delete
    // Delete any that are no longer selected
	if(!SelectedSensiboPods) {
            displayDebugLog( "Removing all Sensibo devices")
            delete = getChildDevices()
	}
	else {
            delete = getChildDevices().findAll { !SelectedSensiboPods.contains(it.deviceNetworkId) }
	}

	displayTraceLog( "deleting ${delete.size()} Sensibo")
	delete.each { deleteChildDevice(it.deviceNetworkId) }

    // def PodList = getChildDevices()
	
    pollHandler()
    
    // refreshDevices() // devices are refreshed in pollhandler
    
    //Set up schedule for refreshing devices based on app preferences
    if (refreshinminutes == "1") 
    	runEvery1Minute("refreshDevices")
    else if (refreshinminutes == "5")
    	runEvery5Minutes("refreshDevices")
    else if (refreshinminutes == "10")
    	runEvery10Minutes("refreshDevices")
    else if (refreshinminutes == "15") 
    	runEvery15Minutes("refreshDevices")
    else if (refreshinminutes == "30")
    	runEvery30Minutes("refreshDevices")
    else
    	runEvery10Minutes("refreshDevices")
}

def refreshDevices() {
	displayTraceLog( "refreshDevices() called")
	def devices = getChildDevices()
	devices.each { d ->
            displayDebugLog( "Calling refresh() on device: ${d.id}")
            d.refresh()
	}
}

def pollChildren(PodUid) {
    displayTraceLog( "pollChildren() called")
    
    def thermostatIdsString = PodUid

    displayTraceLog( "polling children: $thermostatIdsString")
    
    def pollParams = [
    uri: "${getServerUrl()}",
    path: "/api/v2/pods/${thermostatIdsString}/measurements",
    requestContentType: "application/json",
    query: [apiKey:"${getapikey()}", integration:"${version()}", type:"json", fields:"batteryVoltage,temperature,humidity,time"]]
    // query: [apiKey:"${getapikey()}", type:"json", fields:"batteryVoltage,temperature,humidity,time"]]

    // debugEvent ("Before HTTPGET to Sensibo.")

    try {
        httpGet(pollParams) { resp ->

            if (resp.data) {
                // debugEvent ("Response from Sensibo GET = ${resp.data}")
                // debugEvent ("Response Status = ${resp.status}")
            }

            if(resp.status == 200) {
                displayTraceLog( "poll results returned")                           
                displayDebugLog( "DEBUG DATA RESULT" + resp.data.result)

                if (resp.data.result == null || resp.data.result.empty) {
                    displayDebugLog( "Cannot get measurement from the API, should ask Sensibo Support Team")
                    // debugEvent ("Cannot get measurement from the API, should ask Sensibo Support Team",true)
                }

                def setTemp = getACState(thermostatIdsString)
                def ClimateReact = getClimateReact(thermostatIdsString)

                if (setTemp.Error != "Failed") {

                    state.sensibo = resp.data.result.inject([:]) { collector, stat ->

                        def dni = thermostatIdsString
                        
                        displayDebugLog( "updating dni $dni")
                        
                        def stemp = stat.temperature ? stat.temperature.toDouble().round(1) : 0
                        def shumidify = stat.humidity ? stat.humidity.toDouble().round() : 0

                        if (setTemp.temperatureUnit == "F") {
                            stemp = cToF(stemp).round(1)
                        }

                        def tMode                        
                        if (setTemp.on=="off") {
                            tMode = "off"
                        }
                        else {
                            tMode = setTemp.currentmode
                        }

                        def battpourcentage = 100
                        def battVoltage = stat.batteryVoltage

                        if (battVoltage == null) {
                            battVoltage = 3000
                        }                    

                        if (battVoltage < 2850) battpourcentage = 10
                        if (battVoltage > 2850 && battVoltage < 2950) battpourcentage = 50

                        def data = [
                            temperature: stemp,
                            humidity: shumidify,
                            targetTemperature: setTemp.targetTemperature,
                            fanLevel: setTemp.fanLevel,
                            currentmode: setTemp.currentmode,
                            on: setTemp.on,
                            switch : setTemp.on,
                            // thermostatMode: tMode,
                            // thermostatFanMode: setTemp.fanLevel,
                            // coolingSetpoint: setTemp.targetTemperature,
                            // heatingSetpoint: setTemp.targetTemperature,
                            // thermostatSetpoint: setTemp.targetTemperature,
                            temperatureUnit : setTemp.temperatureUnit,
                            voltage : battVoltage,
                            swing : setTemp.swing,
                            battery : battpourcentage,
                            powerSource : setTemp.powerSource,
                            productModel : setTemp.productModel,
                            firmwareVersion : setTemp.firmwareVersion,
                            Climate : ClimateReact.Climate,
                            Error: setTemp.Error
                        ]

                        debugEvent ("pollChildren","Event Data = ${data}")

                        collector[dni] = [data:data]
                        return collector
                    }				
                }

                displayDebugLog( "updated ${state.sensibo[thermostatIdsString].size()} stats: ${state.sensibo[thermostatIdsString]}")
                debugEvent ("pollChildren","updated ${state.sensibo[thermostatIdsString]}")
            }
            else {
                log.error "polling children & got http status ${resp.status}"		
            }
        }
    }
	
    catch(Exception e) {
        displayDebugLog( "___exception polling children: " + e)
        // debugEvent ("pollChildren","${e}")
    }
}

// Poll Child is invoked from the Child Device itself as part of the Poll Capability
def pollChild( child ) {
	displayTraceLog( "pollChild() called")
	//debugEvent ("poll child","poll child")
	def now = new Date().time

	debugEvent ("poll child","Last Poll Millis = ${state.lastPollMillis}")
	def last = state.lastPollMillis ?: 0
	def next = last + pollRateMillis
	
	displayDebugLog( "pollChild( ${child.device.deviceNetworkId} ): $now > $next ?? w/ current state: ${state.sensibo}")
	// debugEvent ("poll child","pollChild( ${child.device.deviceNetworkId} ): $now > $next ?? w/ current state: ${state.sensibo}")

	//if( now > next ) {
	if( true ) { // for now let's always poll/refresh
            displayDebugLog( "polling children because $now > $next")
            debugEvent("poll child","polling children because $now > $next")

            pollChildren(child.device.deviceNetworkId)

            displayDebugLog( "polled children and looking for ${child.device.deviceNetworkId} from ${state.sensibo}")
            // debugEvent ("poll child","polled children and looking for ${child.device.deviceNetworkId} from ${state.sensibo}")

            def currentTime = new Date().time
            debugEvent ("poll child","Current Time = ${currentTime}")
            state.lastPollMillis = currentTime

            def tData = state.sensibo[child.device.deviceNetworkId]
        
            if (tData == null) return

            displayDebugLog("DEBUG - TDATA" + tData)
            //debugEvent ("poll child","Error in Poll ${tData.data.Error}")
            //tData.Error = false
            //tData.data.Error = "Failed"
            if(tData.data.Error != "Success") {
                log.error "ERROR: Device connection removed? no data for ${child.device.deviceNetworkId} after polling"

                // TODO: flag device as in error state
                // child.errorState = true

                return null
            }

            tData.updated = currentTime

            return tData.data
	}
	else if(state.sensibo[child.device.deviceNetworkId] != null) {
            displayDebugLog( "not polling children, found child ${child.device.deviceNetworkId} ")

            def tData = state.sensibo[child.device.deviceNetworkId]
            if(!tData.updated) {
                // we have pulled new data for this thermostat, but it has not asked us for it
                // track it and return the data
                tData.updated = new Date().time
                return tData.data
            }
            return null
	}
	else if(state.sensibo[child.device.deviceNetworkId] == null) {
            log.error "ERROR: Device connection removed? no data for ${child.device.deviceNetworkId}"

            // TODO: flag device as in error state
            // child.errorState = true

            return null
	}
	else {
            // it's not time to poll again and this thermostat already has its latest values
	}

	return null
}

/*
def configureClimateReact(child,String PodUid,String JsonString)
{
	displayTraceLog( "configureClimateReact() called for $PodUid with settings : $JsonString"  )
    
    def result = sendPostJsonClimate(PodUid, JsonString)
    
    if (result) {  
		def tData = state.sensibo[child.device.deviceNetworkId]      
        
        if (tData == null) {
        	pollChildren(child.device.deviceNetworkId)
            tData = state.sensibo[child.device.deviceNetworkId]
        }
        
        //tData.data.Climate = ClimateState        
        tData.data.Error = "Success"
    }
    else {
    	def tData = state.sensibo[child.device.deviceNetworkId]
        if (tData == null) return false
    	
        tData.data.Error = "Failed"
    }

    return(result)
}
*/

def setClimateReact(child,String PodUid, ClimateState) {
    displayTraceLog( "setClimateReact() called for $PodUid Climate React: $ClimateState"   )
    
    def ClimateReact = getClimateReact(PodUid)
    displayDebugLog( "DEBUG " + ClimateReact.Climate + " " + ClimateState)
    if (ClimateReact.Climate == "notdefined") {
    	def tData = state.sensibo[child.device.deviceNetworkId]      
        
        if (tData == null) {
            pollChildren(child.device.deviceNetworkId)
            tData = state.sensibo[child.device.deviceNetworkId]
        }
        
        tData.data.Climate = ClimateReact.Climate        
        tData.data.Error = "Success"
        
        return true
    }
    
    def jsonRequestBody
    if (ClimateState == "on") { 
    	jsonRequestBody = '{"enabled": true}' 
    }
    else {
    	jsonRequestBody = '{"enabled": false}' 
    }
    
    displayDebugLog( "Mode Request Body = ${jsonRequestBody}")
    
    def result = sendPutJson(PodUid, jsonRequestBody)
    
    if (result) {  
		def tData = state.sensibo[child.device.deviceNetworkId]      
        
        if (tData == null) {
        	pollChildren(child.device.deviceNetworkId)
            tData = state.sensibo[child.device.deviceNetworkId]
        }
        
        tData.data.Climate = ClimateState        
        tData.data.Error = "Success"
    }
    else {
    	def tData = state.sensibo[child.device.deviceNetworkId]
        if (tData == null) return false
    	
        tData.data.Error = "Failed"
    }

    return(result)
}

def setACStates(child,String PodUid, on, mode, targetTemperature, fanLevel, swingM, sUnit) {
    displayTraceLog( "setACStates() called for $PodUid ON: $on - MODE: $mode - Temp : $targetTemperature - FAN : $fanLevel - SWING MODE : $swingM - UNIT : $sUnit")
    
    //Return false if no values was read from Sensibo API
    if (on == "--") { return false }
    
    def OnOff = (on == "on") ? true : false
    //if (swingM == null) swingM = "stopped"
    
    displayTraceLog( "Target Temperature :" + targetTemperature)
    
    def jsonRequestBody = '{"acState":{"on": ' + OnOff.toString() + ',"mode": "' + mode + '"'
    
    displayDebugLog( "Fan Level is :$fanLevel")
    displayDebugLog( "Swing is :$swingM")
    displayDebugLog( "Target Temperature is :$targetTemperature")
    
    if (fanLevel != null) {
       displayDebugLog( "Fan Level info is present")
       jsonRequestBody += ',"fanLevel": "' + fanLevel + '"'
    }
    
    if (targetTemperature != 0) {
    	jsonRequestBody += ',"targetTemperature": '+ targetTemperature + ',"temperatureUnit": "' + sUnit + '"'       
    }
    if (swingM)
    {
        jsonRequestBody += ',"swing": "' + swingM + '"'
    }
    
    jsonRequestBody += '}}'
    
    displayDebugLog( "Mode Request Body = ${jsonRequestBody}")
    debugEvent ("setACStates","Mode Request Body = ${jsonRequestBody}")

	boolean result = true
	if(!sendJson(PodUid, jsonRequestBody)) {
            result = false 
        }
		
	if (result) {
            def tData = state.sensibo[child.device.deviceNetworkId]      
        
            if (tData == null) {
                pollChildren(child.device.deviceNetworkId)
                tData = state.sensibo[child.device.deviceNetworkId]
            }        

            displayDebugLog( "Device : " + child.device.deviceNetworkId + " state : " + tData)

            tData.data.fanLevel = fanLevel
            // tData.data.thermostatFanMode = fanLevel
            // tData.data.on = on
            tData.data.currentmode = mode
            displayDebugLog( "Thermostat mode " + on)
            /*if (on=="off") {
                tData.data.thermostatMode = "off"
            }
            else {
                tData.data.thermostatMode = mode
            }*/
            tData.data.targetTemperature = targetTemperature
            // tData.data.coolingSetpoint = targetTemperature
            // tData.data.heatingSetpoint = targetTemperature
            // tData.data.thermostatSetpoint = targetTemperature
            tData.data.temperatureUnit = sUnit
            tData.data.swing = swingM
            tData.data.Error = "Success"
	}
    else {
    	def tData = state.sensibo[child.device.deviceNetworkId]
        if (tData == null) return false
    	
        tData.data.Error = "Failed"
    }

	return(result)
}

//Get the capabilities of the A/C Unit
def getCapabilities(PodUid, mode) {
    displayTraceLog( "getCapabilities() called")
    def now = new Date().time
    
    def last = state.lastPollCapabilitiesMillis ?: 0
	def next = last + getCapabilitiesRateMillis()
    
    def data = [:] 
   
    if (state.capabilities == null || state.capabilities.$PodUid == null || now > next) {
    //if (true) {
    	displayDebugLog( "Now : " + now + " Next : " + next    	)
        
    	//def data = [:]   
        def pollParams = [
    	uri: "${getServerUrl()}",
    	path: "/api/v2/pods/${PodUid}",
    	requestContentType: "application/json",
    	query: [apiKey:"${getapikey()}", integration:"${version()}", type:"json", fields:"remoteCapabilities,productModel"]]
     
     	try {
            displayTraceLog( "getCapabilities() called - Request sent to Sensibo API(remoteCapabilities) for PODUid : $PodUid")
            
            httpGet(pollParams) { resp ->
                if (resp.data) {
                    displayDebugLog( "Status : " + resp.status)
                    if(resp.status == 200) {
                        //resp.data = [result: [remoteCapabilities: [modes: [heat: [swing: ["stopped", "fixedTop", "fixedMiddleTop", "fixedMiddle", "fixedMiddleBottom", "fixedBottom", "rangeTop", "rangeMiddle", "rangeBottom", "rangeFull"], temperatures: [C: ["isNative": true, "values": [16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30]], F: ["isNative": false, "values": [61, 63, 64, 66, 68, 70, 72, 73, 75, 77, 79, 81, 82, 84, 86]]], fanLevels: ["low", "medium", "high", "auto"]], fan: [swing: ["stopped", "fixedMiddleTop", "fixedMiddle", "fixedMiddleBottom", "fixedBottom", "rangeTop", "rangeMiddle", "rangeBottom", "rangeFull"], temperatures: [C: ["isNative": true, "values": [16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30]], F: ["isNative": false, "values": [61, 63, 64, 66, 68, 70, 72, 73, 75, 77, 79, 81, 82, 84, 86]]], fanLevels: ["low", "medium", "high", "auto"]], cool: [swing: ["stopped", "fixedTop", "fixedMiddleTop", "fixedMiddle", "fixedMiddleBottom", "fixedBottom", "rangeTop", "rangeMiddle", "rangeBottom", "rangeFull"], temperatures: ["C": ["isNative": true, "values": [16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30]], F: ["isNative": false, "values": [61, 63, 64, 66, 68, 70, 72, 73, 75, 77, 79, 81, 82, 84, 86]]], fanLevels: ["low", "high", "auto"]]]]]]
                        //resp.data = ["result": ["productModel": "skyv2", "remoteCapabilities": ["modes": ["dry": ["temperatures": ["C": ["isNative": false, "values": [17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30]], "F": ["isNative": true, "values": [62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86]]], "swing": ["stopped", "rangeFull"]], "auto": ["temperatures": ["C": ["isNative": false, "values": [17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30]], "F": ["isNative": true, "values": [62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86]]], "swing": ["stopped", "rangeFull"]], "heat": ["swing": ["stopped", "rangeFull"], "temperatures": ["C": ["isNative": false, "values": [17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30]], "F": ["isNative": true, "values": [62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86]]], "fanLevels": ["low", "medium", "high", "auto"]], "fan": ["swing": ["stopped", "rangeFull"], "temperatures": [], "fanLevels": ["low", "medium", "high", "auto"]], "cool": ["swing": ["stopped", "rangeFull"], "temperatures": ["C": ["isNative": false, "values": [17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30]], "F": ["isNative": true, "values": [62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86]]], "fanLevels": ["low", "medium", "high", "auto"]]]]]]

                        displayDebugLog( resp.data)

                        if (state.capabilities == null) { state.capabilities = [:] }

                        state.capabilities.$PodUid = resp.data
                        displayDebugLog( "Successfull read from Sensibo")
                        displayTraceLog( "Capabilities from Sensibo for ${PodUid} : " + state.capabilities.$PodUid)		

                        def currentTime = new Date().time
                        debugEvent ("getCapabilities","Current Time = ${currentTime}")
                        state.lastPollCapabilitiesMillis = currentTime

                        switch (mode){
                            case "dry":
                                data = [
                                    remoteCapabilities : resp.data.result.remoteCapabilities.modes.dry,
                                    productModel :  resp.data.result.productModel
                                ]	
                                break
                            case "cool":
                                data = [
                                    remoteCapabilities : resp.data.result.remoteCapabilities.modes.cool,
                                    productModel : resp.data.result.productModel
                                ]	
                                break
                            case "heat":
                                data = [
                                    remoteCapabilities : resp.data.result.remoteCapabilities.modes.heat,
                                    productModel : resp.data.result.productModel
                                ]	
                                break
                            case "fan":
                                data = [
                                    remoteCapabilities : resp.data.result.remoteCapabilities.modes.fan,
                                    productModel : resp.data.result.productModel
                                ]	
                                break
                            case "auto":
                                data = [
                                    remoteCapabilities : resp.data.result.remoteCapabilities.modes.auto,
                                    productModel : resp.data.result.productModel
                                ]	
                                break
                            case "modes":
                                data = [
                                    remoteCapabilities : resp.data.result.remoteCapabilities.modes,
                                    productModel : resp.data.result.productModel
                                ]	
                                break                        
                        }
                        displayTraceLog( "Returning remoteCapabilities from Sensibo")
                        return data
                    }
                    else {
                        displayDebugLog( "get remoteCapabilities Failed")

                        data = [
                            remoteCapabilities : "",
                            productModel : ""
                        ]                    
                        return data
                    }
                }
            }
            return data
     	}
     	
        catch(Exception e) {     	
            displayDebugLog( "get remoteCapabilities Failed" + e)
        
            data = [
                remoteCapabilities : "",
                productModel : ""
            ]        
            return data
     	}
    }
    
    else {
    	displayTraceLog( "Capabilities from local for ${PodUid} : " + state.capabilities.$PodUid)
        //return
    	switch (mode){
            case "dry":
                data = [
                    remoteCapabilities : state.capabilities.$PodUid.result.remoteCapabilities.modes.dry,
                    productModel : state.capabilities.$PodUid.result.productModel
                ]	
            break
            case "cool":
                data = [
                    remoteCapabilities : state.capabilities.$PodUid.result.remoteCapabilities.modes.cool,
                    productModel : state.capabilities.$PodUid.result.productModel
                ]	
            break
            case "heat":
                data = [
                    remoteCapabilities :state.capabilities.$PodUid.result.remoteCapabilities.modes.heat,
                    productModel : state.capabilities.$PodUid.result.productModel
                ]	
            break
            case "fan":
                data = [
                    remoteCapabilities : state.capabilities.$PodUid.result.remoteCapabilities.modes.fan,
                    productModel : state.capabilities.$PodUid.result.productModel
                ]	
            break
            case "auto":
                data = [
                    remoteCapabilities : state.capabilities.$PodUid.result.remoteCapabilities.modes.auto,
                    productModel : state.capabilities.$PodUid.result.productModel
                ]	
            break
            case "modes":
                data = [
                    remoteCapabilities : state.capabilities.$PodUid.result.remoteCapabilities.modes,
                    productModel : state.capabilities.$PodUid.result.productModel
                ]	
            break                        
        }
        displayTraceLog( "Returning remoteCapabilities from local")
        return data
    }                  
}

// Get Climate React settings
def getClimateReact(PodUid) {
    displayTraceLog( "getClimateReact() called")
    def data = [:]
    def pollParams = [
    uri: "${getServerUrl()}",
    path: "/api/v2/pods/${PodUid}/smartmode",
    requestContentType: "application/json",
    query: [apiKey:"${getapikey()}", integration:"${version()}", type:"json", fields:"*"]]
        
    try {
    
        httpGet(pollParams) { resp ->           
            /*if (resp.data) {
                debugEvent ("Response from Sensibo GET = ${resp.data}")
                debugEvent ("Response Status = ${resp.status}")
            }*/
			
            displayTraceLog( "Get ClimateReact " + resp.data.result)
            if(resp.status == 200) {
                if (!resp.data.result) {
                    data = [
                        Climate : "notdefined",
                        Error : "Success"
                    ]
                    
                    displayDebugLog( "Returning Climate React (not configured)")
                    return data
                }
            	resp.data.result.any { stat ->                	
                    displayTraceLog( "get ClimateReact Success")
                    displayDebugLog( "PodUID : $PodUid : " + PodUid			)		
                    
                    def OnOff = "off"
                    
                    if (resp.data.result.enabled != null) {
                    	OnOff = resp.data.result.enabled ? "on" : "off"
                    }

                    data = [
                        Climate : OnOff.toString(),
                        Error : "Success"
                    ]

                    displayDebugLog( "Climate: ${data.Climate}")
                    displayTraceLog( "Returning Climate React"   )                     
                    return data
               }
            }
            else {
                data = [
                    Climate : "notdefined",
                    Error : "Failed"
                ]
                    
                displayDebugLog( "get ClimateReact Failed")
                return data
            }
       }
       return data
    }
    
    catch(Exception e) 	{
        displayDebugLog( "Exception Get Json: " + e)
        // debugEvent ("Exception get JSON: " + e)
		
        data = [
            Climate : "notdefined",            
            Error : "Failed" 
	]
        displayDebugLog( "get ClimateReact Failed")
        return data
    }      
}

// Get the latest state from the Sensibo Pod
def getACState(PodUid) {
    displayTraceLog( "getACState() called")
    def data = [:]
    def pollParams = [
    uri: "${getServerUrl()}",
    path: "/api/v2/pods/${PodUid}/acStates",
    requestContentType: "application/json",
    query: [apiKey:"${getapikey()}", integration:"${version()}", type:"json", limit:1, fields:"status,acState,device"]]
    
    try {
        httpGet(pollParams) { resp ->

            /*if (resp.data) {
                    debugEvent ("Response from Sensibo GET = ${resp.data}")
                    debugEvent ("Response Status = ${resp.status}")
            }*/
			
            if(resp.status == 200) {
            	resp.data.result.any { stat ->
                    if (stat.status == "Success") {

                        displayTraceLog( "get ACState Success")
                        displayDebugLog( "PodUID : $PodUid : " + stat.acState)

                        def OnOff = stat.acState.on ? "on" : "off"
                        stat.acState.on = OnOff

                        def stemp
                        if (stat.acState.targetTemperature == null) {
                          stemp = stat.device.measurements.temperature.toInteger()
                        }
                        else {
                          stemp = stat.acState.targetTemperature.toInteger()
                        }

                        def tempUnit
                        if (stat.acState.temperatureUnit == null) {
                          tempUnit = stat.device.temperatureUnit
                        }
                        else {
                          tempUnit = stat.acState.temperatureUnit
                        }	

                        def tMode                        
                        if (OnOff=="off") {
                            tMode = "off"
                        }
                        else {
                            tMode = stat.acState.mode
                        }

                        def sMode
                        if (stat.acState.swing == null) {
                            sMode = "stopped"
                        }
                        else {
                            sMode = stat.acState.swing
                        }

                        displayDebugLog( "product Model : " + stat.device.productModel)
                        def battery = stat.device.productModel == "skyv1" ? "battery" : "mains"

                        displayDebugLog( "swing Mode :" + stat.acState.swing)
                        data = [
                            targetTemperature : stemp,
                            fanLevel : stat.acState.fanLevel,
                            currentmode : stat.acState.mode,
                            on : OnOff.toString(),
                            switch: OnOff.toString(),
                            // thermostatMode: tMode,
                            // thermostatFanMode : stat.acState.fanLevel,
                            // coolingSetpoint : stemp,
                            // heatingSetpoint : stemp,
                            // thermostatSetpoint : stemp,
                            temperatureUnit : tempUnit,
                            swing : sMode,
                            powerSource : battery,
                            productModel : stat.device.productModel,
                            firmwareVersion : stat.device.firmwareVersion,
                            Error : "Success"
                        ]

                            displayDebugLog( "On: ${data.on} targetTemp: ${data.targetTemperature} fanLevel: ${data.fanLevel} swing: ${data.swing}")
                            displayTraceLog( "Returning ACState")
                            return data
                    }
                    else {
                        displayDebugLog( "get ACState Failed") 
                    }
                }
            }
            else {
                data = [
                    targetTemperature : "0",
                    fanLevel : "--",
                    currentmode : "--",
                    on : "--",
                    switch : "--",
                    // thermostatMode: "--",
                    // thermostatFanMode : "--",
                    // coolingSetpoint : "0",
                    // heatingSetpoint : "0",
                    // thermostatSetpoint : "0",
                    temperatureUnit : "",
                    swing : "--",
                    powerSource : "",
                    productModel : "",
                    firmwareVersion : "",
                    Error : "Failed"
                ]
                displayDebugLog( "get ACState Failed")
                return data
            }
        }
        return data
    }
    catch(Exception e) {
        displayDebugLog( "Exception Get Json: " + e)
        // debugEvent ("Exception get JSON: " + e)
		
        data = [
            targetTemperature : "0",
            fanLevel : "--",
            currentmode : "--",
            on : "--",
            switch : "--",
            // thermostatMode: "--",
            // thermostatFanMode : "--",
            // coolingSetpoint : "0",
            // heatingSetpoint : "0",
            // thermostatSetpoint : "0",
            temperatureUnit : "",
            swing : "--",
            powerSource : "",
            productModel : "",
            firmwareVersion : "",
            Error : "Failed" 
		]
        displayDebugLog( "get ACState Failed")
        return data
    } 
}

def sendPutJson(String PodUid, String jsonBody) {
 	displayTraceLog( "sendPutJson() called - Request sent to Sensibo API(smartmode) for PODUid : $PodUid - ${version()} - $jsonBody")
	def cmdParams = [
            uri: "${getServerUrl()}",
            path: "/api/v2/pods/${PodUid}/smartmode",
            requestContentType: "application/json",
            // headers: ["Content-Type": "application/json"],
            query: [apiKey:"${getapikey()}", integration:"${version()}", type:"json"],
            body: jsonBody]

    try {
        httpPut(cmdParams) { resp ->
            if(resp.status == 200) {
                displayDebugLog( "updated ${resp.data}")
                // debugEvent("updated ${resp.data}")
                displayTraceLog( "Successful call to Sensibo API.")
				               
                displayDebugLog( "Returning True")
                return true
            }
            else { 
            	displayTraceLog( "Failed call to Sensibo API.")
                return false
            }
       }
    }    
    
    catch(Exception e) {
        displayDebugLog( "Exception Sending Json: " + e)
        // debugEvent ("Exception Sending JSON: " + e)
        return false
    }
}

def sendPostJsonClimate(String PodUid, String jsonBody) {
    displayTraceLog( "sendPostJsonClimate() called - Request sent to Sensibo API(smartmode) for PODUid : $PodUid - ${version()} - $jsonBody")
    def cmdParams = [
        uri: "${getServerUrl()}",
        path: "/api/v2/pods/${PodUid}/smartmode",
        headers: ["Content-Type": "application/json"],
        query: [apiKey:"${getapikey()}", integration:"${version()}", type:"json"],
        body: jsonBody
    ]

    try {
        httpPost(cmdParams) { resp ->
            if(resp.status == 200) {
                displayDebugLog( "updated ${resp.data}")
                //debugEvent("updated ${resp.data}")
                displayTraceLog( "Successful call to Sensibo API.")
                displayDebugLog( "Returning True")
                return true
            }
            else { 
            	displayTraceLog( "Failed call to Sensibo API.")
                return false
            }
       }
    }  
    
    catch(Exception e) {
        displayDebugLog( "Exception Sending Json: " + e)
        //debugEvent ("Exception Sending JSON: " + e)
        return false
    }
}

// Send state to the Sensibo Pod
def sendJson(String PodUid, String jsonBody) {
    displayTraceLog( "sendJson() called - Request sent to Sensibo API(acStates) for PODUid : $PodUid - ${version()} - $jsonBody")
    def cmdParams = [
        uri: "${getServerUrl()}",
        path: "/api/v2/pods/${PodUid}/acStates",
        headers: ["Content-Type": "application/json"],
        query: [apiKey:"${getapikey()}", integration:"${version()}", type:"json", fields:"acState"],
        body: jsonBody]

	def returnStatus = false
    
    try {
        httpPost(cmdParams) { resp ->
            displayDebugLog("response status: ${resp.status}")
            if(resp.status == 200) {
                displayDebugLog( "updated ${resp.data}")
		//debugEvent("updated ${resp.data}")
                displayTraceLog( "Successful call to Sensibo API.")
				
                //returnStatus = resp.status
                
                displayDebugLog( "Returning True")
                returnStatus = true
            }
           	else { 
            	displayTraceLog( "Failed call to Sensibo API.")
                returnStatus = false
            }
       }
    }
    
    catch(Exception e) {
        displayDebugLog( "Exception Sending Json: " + e)
        // debugEvent ("Exception Sending JSON: " + e)
        returnStatus = false
    }
    
    displayDebugLog( "Return Status: ${returnStatus}")
    return returnStatus
}





def cToF(temp) {
	return (temp * 1.8 + 32).toDouble()
}

def fToC(temp) {
	return ((temp - 32) / 1.8).toDouble()
}

// Subscribe functions
def OnOffHandler(evt) {
	displayTraceLog( "on off handler activated")
    debugEvent(evt.value)
    
	//def name = evt.device.displayName

    if (sendPush) {
        if (evt.value == "on") {
            //sendPush("The ${name} is turned on!")
        } else if (evt.value == "off") {
            //sendPush("The ${name} is turned off!")
        }
    }
}
1 Like

Driver Code

/**
 *  Sensibo Device Type Handler
 *
 *  Copyright 2019 Bryan Li
 *
 *  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
 *  2019-04-27    Based on work by Eric Gosselin - Modified for Hubitat
 *  2019-07-23    Merge robert1's logging changes
 *  2021-02-11    Refactored and cleaned up code (Paul Hutton - VeloWulf)
 *
 */

preferences {
    //Logging Message Config
    input name: "infoLogging", type: "bool", title: "Enable info message logging", description: ""
    input name: "debugLogging", type: "bool", title: "Enable debug message logging", description: ""
    input name: "traceLogging", type: "bool", title: "Enable trace message logging", description: ""
}

metadata {
	definition (name: "SensiboPod", namespace: "joyfulhouse", author: "Bryan Li", 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 "swing", "String"
            attribute "temperatureUnit","String"
            attribute "productModel","String"
            attribute "firmwareVersion","String"
            attribute "Climate","String"
            attribute "targetTemperature","Double"
            attribute "statusText","String"
            attribute "currentmode","String"
            attribute "fanLevel","String"
            //attribute "on","String"   // Added by request of NateG
        
        
            // command "setAll"
            // command "switchFanLevel"
            command "switchMode"
            //command "raiseCoolSetpoint"
            //command "lowerCoolSetpoint"
            //command "raiseHeatSetpoint"
            //command "lowerHeatSetpoint" 
            // command "voltage"
            command "raiseTemperature"
            command "lowerTemperature"
//            command "switchSwing"
//            command "modeSwing", [
//                [
//                    name:"Swing Mode", type: "ENUM", description: "Pick an option", constraints: [
//                        "fixedTop",
//                        "fixedMiddleTop",
//                        "fixedMiddle",
//                        "fixedMiddleBottom",
//                        "fixedBottom",
//                        "rangeTop",
//                        "rangeMiddle",
//                        "rangeBottom",
//                        "rangeFull",
//                        "horizontal",
//                        "both",
//                        "stopped"
//                    ] 
//                ]
//            ]
//            command "setThermostatMode"
            command "modeHeat"
            command "modeCool"
            command "modeDry"
            command "modeFan"
            command "modeAuto"
            command "lowFan"
            command "mediumFan"
            command "highFan"
            command "quietFan"
            command "strongFan"
//            command "fullswing"
            // command "setAirConditionerMode"
            command "toggleClimateReact"
            command "setCoolingSetpoint", ["number"]
            command "setHeatingSetpoint", ["number"]
            command "setClimateReact", [
                [
                    name:"State", type: "ENUM", constraints: [
                        "on",
                        "off"
                    ]
                ]
            ]
            // command "configureClimateReact"
	}
}

// determines the extent of the logging based upon device preferences
private def displayDebugLog(message) {
	if (debugLogging) log.debug "${device.displayName}: ${message}"
}
private def displayTraceLog(message) {
	if (traceLogging) log.trace "${device.displayName}: ${message}"
}
private def displayInfoLog(message) {
	if (infoLogging || state.prefsSetCount != 1)
		log.info "${device.displayName}: ${message}"
}

// Logging and event management
def generatefanLevelEvent(mode) {
   sendEvent(name: "fanLevel", value: mode, descriptionText: "Fan mode is now ${mode}")
}

def generateModeEvent(mode) {
   sendEvent(name: "Mode", value: mode, descriptionText: "AC mode is now ${mode}")   
}

def generateStatusEvent() {
    def temperature = device.currentValue("temperature").toDouble()  
    def humidity = device.currentValue("humidity").toDouble() 
    def targetTemperature = device.currentValue("targetTemperature").split(' ')[0].toDouble()
    def fanLevel = device.currentValue("fanLevel")
    def mode = device.currentValue("currentmode")
    // def on = device.currentValue("on")
    def swing = device.currentValue("swing")
    def ClimateReact = device.currentValue("Climate")
    def error = device.currentValue("Error")
    def sUnit = device.currentValue("temperatureUnit")                
    def statusTextmsg = ""
    
    statusTextmsg = "${humidity}%"
   
    // sendEvent("name":"statusText", "value":statusTextmsg, "description":"")
}

def debugEvent(value, message) {

	def results = [
            name: "devdebug",
            value: value,
            descriptionText: message,
	]
	displayDebugLog( "Generating DevDebug Event: ${results}")
	sendEvent (results)
}

def generateErrorEvent() {
   displayDebugLog("$device.displayName FAILED to set the AC State")
   sendEvent(name: "Error", value: "Error", descriptionText: "$device.displayName FAILED to set or get the AC State", isStateChange: true)  
}

def generateSetTempEvent(temp) {
   sendEvent(name: "targetTemperature", value: temp, descriptionText: "$device.displayName set temperature is now ${temp}", isStateChange: true)
}

def generateSwitchEvent(mode) {
   // sendEvent(name: "on", value: mode, descriptionText: "$device.displayName is now ${mode}", displayed: false, isStateChange: true)  
   sendEvent(name: "switch", value: mode, descriptionText: "$device.displayName is now ${mode}", isStateChange: true)   
   
   //refresh()
}

// Unit conversion
def fToc(temp) {
	return ((temp - 32) / 1.8).toDouble()
}

// AC Control functions start here
def switchMode() {
	displayTraceLog( "switchMode() called")
    
	def currentMode = device.currentState("currentmode")?.value
	displayDebugLog("switching AC mode from current mode: $currentMode")
	def returnCommand

	switch (currentMode) {
		case "heat":
			returnCommand = modeMode("cool")
			break
		case "cool":
			returnCommand = modeMode("fan")
			break		
		case "fan":
			returnCommand = modeMode("dry")
			break
        case "dry":
            returnCommand = modeMode("auto")
			break
        case "auto":
			returnCommand = modeMode("heat")
			break
	}

	returnCommand
}

def modeMode(String newMode){
    displayTraceLog( "modeMode() called with " + newMode)
    
    def Setpoint = device.currentValue("targetTemperature").toInteger()
        
    def LevelBefore = device.currentState("fanLevel").value
    def capabilities = parent.getCapabilities(device.deviceNetworkId,newMode)
    def Level = LevelBefore
    if (capabilities.remoteCapabilities != null) {
    	def fanLevels = capabilities.remoteCapabilities.fanLevels
    	
        displayDebugLog("Fan levels capabilities : " + capabilities.remoteCapabilities.fanLevels)
        
        Level = GetNextFanLevel(LevelBefore,capabilities.remoteCapabilities.fanLevels)
        
        //displayDebugLog("Fan : " + Level)
   
        def result = parent.setACStates(this, device.deviceNetworkId, "on", newMode, Setpoint, Level, device.currentState("swing").value, device.currentState("temperatureUnit").value)
        if (result) {
            displayInfoLog( "Mode changed to " + newMode + " for " + device.deviceNetworkId)
            
            if (LevelBefore != Level) {
                generatefanLevelEvent(Level)
            }
            sendEvent(name: 'thermostatMode', value: newMode,isStateChange: true)
            displayDebugLog("Current state: ${device.currentState("switch")}.value")
            if (device.currentState("switch").value == "off") { generateSwitchEvent("on") }

            generateModeEvent(newMode)
            // generateStatusEvent()
            refresh()
        }
        else {
            generateErrorEvent()
            // generateStatusEvent()
        }             
    }
    else {
        def themodes = parent.getCapabilities(device.deviceNetworkId,"modes")
        def sMode = GetNextMode(newMode,themodes)

        NextMode(sMode)
    }
}

def GetNextFanLevel(fanLevel, fanLevels) {
    displayTraceLog( "GetNextFanLevel called with " + fanLevel)
    
    if (fanLevels == null || fanLevels == "null") {
      return null
    }
    
    def listFanLevel = ['low','medium','high','auto','quiet','medium_high','medium_low','strong']	
    def newFanLevel = returnNext(listFanLevel,fanLevels,fanLevel)
    
    displayDebugLog("Next fanLevel = " + newFanLevel)
	
    return newFanLevel
}

def returnNext(liste1, liste2, val) throws Exception
{
    try {
    	def index = liste2.indexOf(val)
        
        if (index == -1) throw new Exception()
        else return liste2[liste2.indexOf(val)]
    }
    catch(Exception e) {
    	if (liste1.indexOf(val)+ 1 == liste1.size()) {
           val = liste1[0]
           }
         else {
           val = liste1[liste1.indexOf(val) + 1]
         }
         returnNext(liste1, liste2, val)
    }	
}

def refresh() {
  displayTraceLog( "refresh() called")
  poll()
  displayTraceLog( "refresh() ended")
}

void poll() {
    displayTraceLog( "Executing 'poll' using parent SmartApp")
	
    def results = parent.pollChild(this)
    def linkText = getLinkText(device)
                    
    parseTempUnitEventData(results)
    parseEventData(results)
}

def parseTempUnitEventData(Map results) {
    displayDebugLog("parsing data $results")
    
    if(results) {
        results.each { name, value ->
            if (name=="temperatureUnit") { 
                def linkText = getLinkText(device)
                def isChange = true

                sendEvent(
                    name: name,
                    value: value,
                    //unit: value,
                    descriptionText: "${name} = ${value}",
                    handlerName: "temperatureUnit",
                    isStateChange: isChange,)
            }
        }
    }
}

def parseEventData(Map results) {
    displayDebugLog("parsing Event data $results")
    
    if(results) {
        results.each { name, value -> 
 
            displayDebugLog("name :" + name + " value :" + value)
            
            def linkText = getLinkText(device)
            def isChange = false
                             
            if (name=="voltage") {
                isChange = true //isTemperatureStateChange(device, name, value.toString())
                                  
                sendEvent(
                    name: name,
                    value: value,
                    unit: "mA",
                    descriptionText: getThermostatDescriptionText(name, value, linkText),
                    handlerName: name,
                    isStateChange: isChange)
            }
            else if (name== "battery") {            	                
                isChange = true //isTemperatureStateChange(device, name, value.toString())
                 
                sendEvent(
                    name: name,
                    value: value,
                    //unit: "V",
                    descriptionText: getThermostatDescriptionText(name, value, linkText),
                    handlerName: name,
                    isStateChange: isChange)
            }
            else if (name== "powerSource") {            	                
                isChange = true //isTemperatureStateChange(device, name, value.toString())
                  
                sendEvent(
                    name: name,
                    value: value,
                    descriptionText: getThermostatDescriptionText(name, value, linkText),
                    handlerName: name,
                    isStateChange: isChange)
            }
            else if (name== "Climate") {            	                
                isChange = true
                  
                sendEvent(
                    name: name,
                    value: value,
                    descriptionText: getThermostatDescriptionText(name, value, linkText),
                    handlerName: name,
                    isStateChange: isChange)
            }
            else if (name=="on") {            	
                isChange = true
                   
                sendEvent(
                    name: name,
                    value: value,
                    descriptionText: getThermostatDescriptionText(name, value, linkText),
                    handlerName: name,
                    isStateChange: isChange)
                    
                sendEvent(name: "switch", value: value)
            }
            /*else if (name=="thermostatMode") {
                isChange = true //isTemperatureStateChange(device, name, value.toString())
                 
                if (value=="cool") {
                    sendEvent(name: 'airConditionerMode', value: "cool", 
                        isStateChange: isChange)
					
                    sendEvent(name: 'thermostatOperatingState', value: "cooling", 
                        isStateChange: isChange)
                } 
                else if (value=="heat") {
                    sendEvent(name: 'airConditionerMode', value: "heat", 
                        isStateChange: isChange)
                    
                    sendEvent(name: 'thermostatOperatingState', value: "heating", 
                        isStateChange: isChange)
                } 
                else if (value=="fan") {
                    sendEvent(name: 'airConditionerMode', value: "fanOnly", 
                        isStateChange: isChange)
                
                    sendEvent(name: 'thermostatOperatingState', value: "fan only", 
                        isStateChange: isChange)
                } 
                else if (value=="dry") {
                    sendEvent(name: 'airConditionerMode', value: "dry", 
                        isStateChange: isChange)
                    
                    sendEvent(name: 'thermostatOperatingState', value: "dry", 
                        isStateChange: isChange)
                 } 
                 else if (value=="auto") {
                    sendEvent(name: 'airConditionerMode', value: "auto", 
                        isStateChange: isChange)
                    
                    sendEvent(name: 'thermostatOperatingState', value: "auto", 
                        isStateChange: isChange)
                } 
                else {
                    sendEvent(name: 'thermostatOperatingState', value: "idle", 
                        isStateChange: isChange)
                }      
				
                sendEvent(
                    name: name,
                    value: value,
                    descriptionText: getThermostatDescriptionText(name, value, linkText),
                    handlerName: name,
                    isStateChange: isChange)
            }
            else if (name=="coolingSetpoint" || name== "heatingSetpoint" || name == "thermostatSetpoint") {           	
                isChange = true //isTemperatureStateChange(device, name, value.toString())
                
                sendEvent(
                    name: name,
                    value: value,
                    //unit : device.currentValue("temperatureUnit"),
                    descriptionText: getThermostatDescriptionText(name, value, linkText),
                    handlerName: name,
                    isStateChange: isChange)
            }*/
            else if (name=="temperatureUnit") { 
                isChange = true
                   
                sendEvent(
                    name: name,
                    value: value,
                    //unit: value,
                    descriptionText: getThermostatDescriptionText(name, value, linkText),
                    handlerName: name,
                    isStateChange: isChange)
            }
            /*else if (name=="thermostatFanMode") {
            	def mode = (value.toString() == "high" || value.toString() == "medium") ? "on" : value.toString()
                mode = (mode == "low") ? "circulate" : mode
               	
                isChange = true //isTemperatureStateChange(device, name, value.toString())
                   
                sendEvent(
                    name: name,
                    value: value,
                    descriptionText: getThermostatDescriptionText(name, value, linkText),
                    handlerName: name,
                    isStateChange: isChange)
            }*/
            else if (name=="swing") {
              	
                isChange = true //isTemperatureStateChange(device, name, value.toString())
                   
                sendEvent(
                    name: name,
                    value: value,
                    descriptionText: getThermostatDescriptionText(name, value, linkText),
                    handlerName: name,
                    isStateChange: isChange)
            }
            else if (name=="temperature" || name== "lastTemperaturePush" || name== "lastHumidityPush") {
                isChange = true //isTemperatureStateChange(device, name, value.toString())
				
                sendEvent(
                    name: name,
                    value: value,
                    //unit: device.currentValue("temperatureUnit"),
                    descriptionText: getThermostatDescriptionText(name, value, linkText),
                    handlerName: name,
                    isStateChange: isChange)
                    
            }
            else if (name=="humidity") {
                isChange = true //isTemperatureStateChange(device, name, value.toString())
				
                sendEvent(
                    name: name,
                    value: value,
                    descriptionText: getThermostatDescriptionText(name, value, linkText),
                    handlerName: name,
                    isStateChange: isChange)                    
            }
            else {
            	isChange = true//isStateChange(device, name, value.toString())
                
                sendEvent(
                    name: name,
                    value: value.toString(),
                    // linkText: linkText,
                    descriptionText: getThermostatDescriptionText(name, value, linkText),
                    handlerName: name,
                    isStateChange: isChange)
                    
            }
        }          

        // generateStatusEvent ()
    }
}

void raiseTemperature() {
    displayTraceLog( "raiseTemperature() called"	)

    def operMode = device.currentState("currentmode").value
    
    def Setpoint = device.currentValue("targetTemperature").toInteger()
    def theTemp = device.currentValue("temperatureUnit")
    
    displayDebugLog("Current target temperature = ${Setpoint}")

    Setpoint = temperatureUp(Setpoint)
    
    if (Setpoint == -1) { 
      return
    }
    
    switch (operMode) {
    	case "heat":
            setHeatingSetpoint(Setpoint)
            break;
        case "cool":
            setCoolingSetpoint(Setpoint)
            break;
        case "fan":
            setFanSetpoint(Setpoint)
            break;
         case "dry":
            setDrySetpoint(Setpoint)
            break;
        case "auto":
            setHeatingSetpoint(Setpoint)
            setCoolingSetpoint(Setpoint)
            break;
        default:
        	break;
    }
}

def temperatureUp(temp) {
    displayTraceLog( "temperatureUp() called with "+ temp)
    
    def sunit = device.currentValue("temperatureUnit")
    def capabilities = parent.getCapabilities(device.deviceNetworkId, device.currentState("currentmode").value)
    def values
    
    if (sunit == "F") {
    	if (capabilities.remoteCapabilities.temperatures.F == null) {
            return -1
    	}
        values = capabilities.remoteCapabilities.temperatures.F.values                
    }
    else {
    	if (capabilities.remoteCapabilities.temperatures.C == null) {
            return -1
    	}
    	values = capabilities.remoteCapabilities.temperatures.C.values
    }
    
    def found = values.findAll{number -> number > temp}

    displayDebugLog("Values retrieved : " + found)
    
    if (found == null || found.empty) found = values.last()
    else found = found.first()

    displayDebugLog("Temp before : " + temp    )          
    displayDebugLog("Temp after : " + found)

    temp = found
        
    return temp
}

// Set Temperature
def setHeatingSetpoint(temp) {
    displayTraceLog( "setHeatingSetpoint() called")

    temp = temp.toInteger()
    displayDebugLog("setTemperature : " + temp)
    
    def result = parent.setACStates(this, device.deviceNetworkId , "on", "heat", temp, device.currentState("fanLevel").value, device.currentState("swing").value, device.currentState("temperatureUnit").value)
    
    if (result) {
    	displayInfoLog( "Heating temperature changed to " + temp + " for " + device.deviceNetworkId)
        
        if (device.currentState("switch").value == "off") { generateSwitchEvent("on") }
    	generateModeEvent("heat")
    	//sendEvent(name: 'coolingSetpoint', value: temp, displayed: false)
    	sendEvent(name: 'heatingSetpoint', value: temp, displayed: false)
        sendEvent(name: 'thermostatSetpoint', value: temp, displayed: false)
    	generateSetTempEvent(temp)
        
        // generateStatusEvent()
    	refresh()
    }	
    else {
       	generateErrorEvent()
        // generateStatusEvent()
    }    
}

// Set Temperature
def setCoolingSetpoint(temp) {
    displayTraceLog( "setCoolingSetpoint() called")

    temp = temp.toInteger()
    displayDebugLog("setTemperature : " + temp  ) 
    
    def result = parent.setACStates(this, device.deviceNetworkId , "on", "cool", temp, device.currentState("fanLevel").value, device.currentState("swing").value, device.currentState("temperatureUnit").value)
    
    if (result) {
    	displayInfoLog( "Cooling temperature changed to " + temp + " for " + device.deviceNetworkId)
        
    	if (device.currentState("switch").value == "off") { generateSwitchEvent("on") }
        generateModeEvent("cool")
         
    	sendEvent(name: 'coolingSetpoint', value: temp, displayed: false)
        //sendEvent(name: 'thermostatSetpoint', value: temp, displayed: false)
    	//sendEvent(name: 'heatingSetpoint', value: temp, displayed: false)
    	generateSetTempEvent(temp)
        
        // generateStatusEvent()
    	refresh()
    }
    else {
       	generateErrorEvent()
        // generateStatusEvent()
    }
}

// Set Temperature
def setFanSetpoint(temp) {
    displayTraceLog( "setFanSetpoint() called")

    temp = temp.toInteger()
    displayDebugLog("setTemperature : " + temp  ) 
    
    def result = parent.setACStates(this, device.deviceNetworkId , "on", "fan", temp, device.currentState("fanLevel").value, device.currentState("swing").value, device.currentState("temperatureUnit").value)
    
    if (result) {
    	displayInfoLog( "Fan temperature changed to " + temp + " for " + device.deviceNetworkId)
        
    	if (device.currentState("switch").value == "off") { generateSwitchEvent("on") }
        generateModeEvent("fan")
         
        sendEvent(name: 'thermostatSetpoint', value: temp, displayed: false)
    	generateSetTempEvent(temp)
        
        // generateStatusEvent()
    	refresh()
    }
    else {
       	generateErrorEvent()
        // generateStatusEvent()
    }
}

// Set Temperature
def setDrySetpoint(temp) {
    displayTraceLog( "setDrySetpoint() called")

    temp = temp.toInteger()
    displayDebugLog("setTemperature : " + temp  ) 
    
    def result = parent.setACStates(this, device.deviceNetworkId , "on", "dry", temp, device.currentState("fanLevel").value, device.currentState("swing").value, device.currentState("temperatureUnit").value)
    
    if (result) {
    	displayInfoLog( "Dry temperature changed to " + temp + " for " + device.deviceNetworkId)
        
    	if (device.currentState("switch").value == "off") { generateSwitchEvent("on") }
        generateModeEvent("dry")
         
        sendEvent(name: 'thermostatSetpoint', value: temp, displayed: false)
    	generateSetTempEvent(temp)
        
        // generateStatusEvent()
    	refresh()
    }
    else {
       	generateErrorEvent()
        // generateStatusEvent()
    }   
}

void lowerTemperature() {
    displayTraceLog( "lowerTemperature() called")
    
    def operMode = device.currentState("currentmode").value
    
    def Setpoint = device.currentValue("targetTemperature").toInteger()
    def theTemp = device.currentValue("temperatureUnit")
    
    displayDebugLog("Current target temperature = ${Setpoint}")

    Setpoint = temperatureDown(Setpoint)
    
    if (Setpoint == -1) { 
      return
    }
    
    switch (operMode) {
    	case "heat":
            setHeatingSetpoint(Setpoint)
            break;
        case "cool":
            setCoolingSetpoint(Setpoint)
            break;
        case "fan":
            setFanSetpoint(Setpoint)
            break;
         case "dry":
            setDrySetpoint(Setpoint)
            break;
        case "auto":
            setHeatingSetpoint(Setpoint)
            setCoolingSetpoint(Setpoint)
            break;
        default:
        	break;
    }
}

def modeHeat() {
    displayTraceLog( "modeHeat() called")
    modeMode("heat")
}

def modeCool() {
    displayTraceLog( "modeCool() called")
    modeMode("cool")
}

def modeDry() {
    displayTraceLog( "modeDry() called")
    modeMode("dry")
}

def modeFan() {
    displayTraceLog( "modeFan() called")
    modeMode("fan")
}

def modeAuto() {
    displayTraceLog( "modeAuto() called")
    modeMode("auto")
}

def dfanLevel(String newLevel){
    displayTraceLog( "dfanLevel called with fan = " + newLevel)
    
    def Setpoint = device.currentValue("targetTemperature").toInteger()
    
    def capabilities = parent.getCapabilities(device.deviceNetworkId, device.currentState("currentmode").value)      
    def Level = LevelBefore
    if (capabilities.remoteCapabilities != null) {
    	def fanLevels = capabilities.remoteCapabilities.fanLevels
        
    	displayDebugLog("Fan levels capabilities : " + capabilities.remoteCapabilities.fanLevels)
        
        Level = GetNextFanLevel(newLevel,capabilities.remoteCapabilities.fanLevels)
        //displayDebugLog("Fan : " + Level)
        
        def result = parent.setACStates(this, device.deviceNetworkId,"on", device.currentState("currentmode").value, Setpoint, Level, device.currentState("swing").value, device.currentState("temperatureUnit").value)

        if (result) {
            displayInfoLog( "Fan level changed to " + Level + " for " + device.deviceNetworkId)
            
            if (device.currentState("switch").value == "off") { generateSwitchEvent("on") }
            if (Level == "low") {
            	sendEvent(name: 'thermostatFanMode', value: "circulate", displayed: false)
            }
            else {            	
                sendEvent(name: 'thermostatFanMode', value: "on", displayed: false)
            }
            generatefanLevelEvent(Level)
            
            // generateStatusEvent()
            refresh()
        }
        else {
            generateErrorEvent()
            // generateStatusEvent()
        }             
    }
    else {
    	displayDebugLog ("Fan mode does not exist")
        // other instructions may be required if mode does not exist
    }    
}

def lowFan() {
    displayTraceLog( "lowfan() called")
    dfanLevel("low")
}

def mediumFan() {
    displayTraceLog( "mediumfan() called")
    dfanLevel("medium")
}

def highFan() {
    displayTraceLog( "highfan() called")
    dfanLevel("high")
}

def quietFan() {
    displayTraceLog( "quietfan() called")
    dfanLevel("quiet")
}

def strongFan() {
    displayTraceLog( "strongfan() called")
    dfanLevel("strong")
}

def autoFan() {
    displayTraceLog( "autofan() called")
    dfanLevel("auto")
}

def setAll(newMode,temp,fan) {
    displayTraceLog( "setAll() called with " + newMode + "," + temp + "," + fan )
    
    def Setpoint = temp.toInteger()
    def LevelBefore = fan
    def capabilities = parent.getCapabilities(device.deviceNetworkId,newMode)
    def Level = LevelBefore
    if (capabilities.remoteCapabilities != null) {
    	def fanLevels = capabilities.remoteCapabilities.fanLevels
        
    	displayDebugLog("Fan levels capabilities : " + capabilities.remoteCapabilities.fanLevels)
        
        Level = GetNextFanLevel(LevelBefore,capabilities.remoteCapabilities.fanLevels)
        displayDebugLog("Fan : " + Level)
   
        def result = parent.setACStates(this, device.deviceNetworkId, "on", newMode, Setpoint, Level, device.currentState("swing").value, device.currentState("temperatureUnit").value)
       
        if (result) {
            if (LevelBefore != Level) {
                generatefanLevelEvent(Level)
            }
            sendEvent(name: 'thermostatMode', value: newMode, displayed: false,isStateChange: true)
            if (device.currentState("switch").value == "off") { generateSwitchEvent("on") }

            generateModeEvent(newMode)
            // generateStatusEvent()
            refresh()
        }
        else {
            generateErrorEvent()
            // generateStatusEvent()
        }              
    }
    else {       
    }
}

def fullswing()
{	
	displayTraceLog( "fullswing() called")
	modeSwing("rangeFull")
}

def temperatureDown(temp)
{
	displayTraceLog( "temperatureDown() called with "+ temp)
    
	def sunit = device.currentValue("temperatureUnit")
    def capabilities = parent.getCapabilities(device.deviceNetworkId, device.currentState("currentmode").value)
    def values
       
    if (sunit == "F") {
    	if (capabilities.remoteCapabilities.temperatures.F == null) {
       		return -1
    	}
    	values = capabilities.remoteCapabilities.temperatures.F.values                
    }
    else {
    	if (capabilities.remoteCapabilities.temperatures.C == null) {
       		return -1
    	}
    	values = capabilities.remoteCapabilities.temperatures.C.values
    }
    
    def found = values.findAll{number -> number < temp}
       
	displayDebugLog("Values retrieved : " + found)
    
    if (found == null || found.empty) found = values.first()
    else found = found.last()
        
    displayDebugLog("Temp before : " + temp   )            
    displayDebugLog("Temp after : " + found)
        
    temp = found
        
    return temp
}

void lowerCoolSetpoint() {
    displayTraceLog( "lowerCoolSetpoint() called")
    
    def Setpoint = device.currentValue("targetTemperature").toInteger()
    def theTemp = device.currentValue("temperatureUnit")

    displayDebugLog("Current target temperature = ${Setpoint}")
	
    Setpoint = temperatureDown(Setpoint)

    def result = parent.setACStates(this, device.deviceNetworkId , "on", device.currentState("currentmode").value, Setpoint, device.currentState("fanLevel").value, device.currentState("swing").value, device.currentState("temperatureUnit").value)

    if (result) {
    	displayInfoLog( "Cooling temperature changed to " + Setpoint + " for " + device.deviceNetworkId)
        
        if (device.currentState("switch").value == "off") { generateSwitchEvent("on") }
        
        sendEvent(name: 'coolingSetpoint', value: Setpoint,  displayed: false)
        //sendEvent(name: 'thermostatSetpoint', value: Setpoint,  displayed: false)
       
        generateSetTempEvent(Setpoint)
        
    	displayDebugLog("New target Temperature = ${Setpoint}")
        
        //generateStatusEvent()
    	refresh()
    }
	else {
    	displayDebugLog("error")
       	generateErrorEvent()
        
        //generateStatusEvent()
    }	
}

void setThermostatMode(modes) { 
    displayTraceLog( "setThermostatMode() called")

    def currentMode = device.currentState("currentmode").value

    displayDebugLog("switching AC mode from current mode: $currentMode")

    switch (modes) {
        case "cool":
            modeCool()
            break
        //case "fan":
        //	returnCommand = modeFan()
        //	break		
        //case "dry":
        //	returnCommand = modeDry()
        //	break
    case "auto":
        modeAuto()
        break
    case "heat":
        modeHeat()
        break
    case "off":
        off()
        break
    }
}

void setAirConditionerMode(modes)
{ 
	displayTraceLog( "setAirConditionerMode() called")
    
  	def currentMode = device.currentState("currentmode").value
  
  	displayDebugLog("switching AC mode from current mode: $currentMode")

  	switch (modes) {
		case "cool":
			modeCool()
			break
		case "fanOnly":
        case "fan":
			modeFan()
			break		
		case "dry":
			modeDry()
			break
        case "auto":
	        modeAuto()
			break
        case "heat":
			modeHeat()
			break
	}
}

void raiseCoolSetpoint() {
   	displayTraceLog( "raiseCoolSetpoint() called")
    
    def Setpoint = device.currentValue("targetTemperature").toInteger()
    def theTemp = device.currentValue("temperatureUnit")
    
    displayDebugLog("Current target temperature = ${Setpoint}")

	Setpoint = temperatureUp(Setpoint)

    def result = parent.setACStates(this, device.deviceNetworkId , "on", device.currentState("currentmode").value, Setpoint, device.currentState("fanLevel").value, device.currentState("swing").value, device.currentState("temperatureUnit").value)
    if (result) {
    	displayInfoLog( "Cooling temperature changed to " + Setpoint + " for " + device.deviceNetworkId)
        
        if (device.currentState("switch").value == "off") { generateSwitchEvent("on") }
        
        sendEvent(name: 'coolingSetpoint', value: Setpoint, displayed: false)
        sendEvent(name: 'thermostatSetpoint', value: Setpoint, displayed: false)
        
        generateSetTempEvent(Setpoint)
        
    	displayDebugLog("New target Temperature = ${Setpoint}")
        
        generateStatusEvent()
    	refresh()
    }
	else {
       	generateErrorEvent()
        
        generateStatusEvent()
    }	
}

void raiseHeatSetpoint() {
    displayTraceLog( "raiseHeatSetpoint() called")
    
    def Setpoint = device.currentValue("targetTemperature").toInteger()
    def theTemp = device.currentValue("temperatureUnit")
    
    displayDebugLog("Current target temperature = ${Setpoint}")

    Setpoint = temperatureUp(Setpoint)

    def result = parent.setACStates(this, device.deviceNetworkId , "on", device.currentState("currentmode").value, Setpoint, device.currentState("fanLevel").value, device.currentState("swing").value, device.currentState("temperatureUnit").value)
    if (result) {
    	displayInfoLog( "Heating temperature changed to " + Setpoint + " for " + device.deviceNetworkId)
        
        if (device.currentState("switch").value == "off") { generateSwitchEvent("on") }
        
        sendEvent(name: 'heatingSetpoint', value: Setpoint, displayed: false)
        //sendEvent(name: 'thermostatSetpoint', value: Setpoint, displayed: false)
        
        generateSetTempEvent(Setpoint)
        
    	displayDebugLog("New target Temperature = ${Setpoint}")
        
        //generateStatusEvent()
    	refresh()
    }
	else {
            generateErrorEvent()
        
        //generateStatusEvent()
    }
	
}

void lowerHeatSetpoint() {
    displayTraceLog( "lowerHeatSetpoint() called")
    
    def Setpoint = device.currentValue("targetTemperature").toInteger()
    def theTemp = device.currentValue("temperatureUnit")
    
    displayDebugLog("Current target temperature = ${Setpoint}")

    Setpoint = temperatureDown(Setpoint)

    def result = parent.setACStates(this, device.deviceNetworkId , "on", device.currentState("currentmode").value, Setpoint, device.currentState("fanLevel").value, device.currentState("swing").value, device.currentState("temperatureUnit").value)
    if (result) {
    	displayInfoLog( "Heating temperature changed to " + Setpoint + " for " + device.deviceNetworkId)
        
        if (device.currentState("switch").value == "off") { generateSwitchEvent("on") }
        
        sendEvent(name: 'heatingSetpoint', value: Setpoint, displayed: false)    
        //sendEvent(name: 'thermostatSetpoint', value: Setpoint, displayed: false)
        
        generateSetTempEvent(Setpoint)
        
    	displayDebugLog("New target Temperature = ${Setpoint}")
        
        //generateStatusEvent()
    	refresh()
    }
	else {
       	generateErrorEvent()
        
        //generateStatusEvent()
    }	
}

// Turn off or Turn on the AC
def on() {
    displayTraceLog( "on called")
    
    //def Setpoint = device.currentValue("targetTemperature").toInteger()
    def Setpoint = device.currentValue("targetTemperature")
   
    displayDebugLog("Temp Unit : " + device.currentState("temperatureUnit").value)
    displayDebugLog("Temp Unit (Setpoint) : " + Setpoint)
    def result = parent.setACStates(this, device.deviceNetworkId, "on", device.currentState("currentmode").value, Setpoint, device.currentState("fanLevel").value, device.currentState("swing").value, device.currentState("temperatureUnit").value)
    displayDebugLog("Result : " + result)
    if (result) {
    	displayInfoLog( "AC turned ON for " + device.deviceNetworkId)
    	sendEvent(name: 'thermostatMode', value: device.currentState("currentmode").value,isStateChange: true)
        //sendEvent(name: 'thermostatOperatingState', value: "idle",isStateChange: true)
    	
        generateSwitchEvent("on")
        
        // generateStatusEvent() 
    	refresh()
    }
    else {
       	generateErrorEvent()
        // generateStatusEvent() 
    }        
}

def off() {
    displayTraceLog( "off called")
    
    //def Setpoint = device.currentValue("targetTemperature").toInteger()
    def Setpoint = device.currentValue("targetTemperature")
   
    displayDebugLog("Temp Unit : " + device.currentState("temperatureUnit").value)
    displayDebugLog("Temp Unit (Setpoint) : " + Setpoint)
    def result = parent.setACStates(this, device.deviceNetworkId, "off", device.currentState("currentmode").value, Setpoint, device.currentState("fanLevel").value, device.currentState("swing").value, device.currentState("temperatureUnit").value)

    if (result) {
    	displayInfoLog( "AC turned OFF for " + device.deviceNetworkId)
        
    	sendEvent(name: 'thermostatMode', value: "off", isStateChange: true)
        //sendEvent(name: 'thermostatOperatingState', value: "idle",isStateChange: true)
    	
        generateSwitchEvent("off")
        
        // generateStatusEvent()
    	refresh()
     }
    else {
       	generateErrorEvent()
        // generateStatusEvent()
    }         
}

// toggle Climate React
def toggleClimateReact()
{
  	displayTraceLog( "toggleClimateReact() called")
    
	def currentClimateMode = device.currentState("Climate")?.value
    
    def returnCommand
    
    switch (currentClimateMode) {
    	case "off":
        	returnCommand = setClimateReact("on")
            break
        case "on":
        	returnCommand = setClimateReact("off")
            break            
    }
    
    if (!returnCommand) { returnCommand }
}

// Set Climate React
def setClimateReact(ClimateState) {

    ///////////////////////////////////////////////
    /// Parameter ClimateState : "on" or "off"
    ///////////////////////////////////////////////
    
	displayTraceLog( "setClimateReact() called")
    
	displayDebugLog("Climate : " + ClimateState   )
   
    def result = parent.setClimateReact(this, device.deviceNetworkId, ClimateState)
    
    if (result) {
    	displayInfoLog( "Climate React changed to " + ClimateState + " for " + device.deviceNetworkId)
              
        sendEvent(name: 'Climate', value: ClimateState, displayed: false)
    	//generateSetTempEvent(temp)
        
        // generateStatusEvent()
    	refresh()
    }
    else {
       	generateErrorEvent()
        //generateStatusEvent()
        //refresh()
    }
}

/* // Disabling this for the time being
def configureClimateReact(lowThres, highThres,stype,lowState,highState, on_off, ThresUnit)
{
    ///////////////////////////////////////////////
    // lowThres and highThres - Integer parameters
	// stype : possible values are "temperature", "humidity" or "feelsLike"
    // lowState and highState : 
    //    on, fanLevel, temperatureUnit, targetTemperature, mode      
    //
    //    like  "[true,'auto','C',21,'heat']"
    //    to turn off AC,first parameters = false : "[false,'auto','C',21,'heat']"
    // one_off : boolean value to enable/disable the Climate React
    // unit : Passing F for Farenheit or C for Celcius
    // 
    // 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
    // configureClimateReact(19, 24, ‘temperature’, ‘[true, ‘auto’, ‘C’, 22, ‘heat’]’, ‘[false, ‘auto’, ‘C’, 22, ‘heat’]’, true, ‘C’);
    //
    // 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
    // configureClimateReact(67, 68, ‘temperature’, ‘[true, ‘auto’, ‘F’, 68, ‘heat’]’, ‘[false, ‘auto’, ‘F’, 68, ‘heat’]’, true, ‘F’);
    //
    ///////////////////////////////////////////////
    
	displayTraceLog( "configureClimateReact() called")
    
    
    if (ThresUnit.toUpperCase() == "F")
    {
    	lowThres = fToc(lowThres).round(1)
    	highThres = fToc(highThres).round(1)
    }
    
    def json = new groovy.json.JsonBuilder()
    
    def lowStateMap = evaluate(lowState)
    def highStateMap = evaluate(highState)
        
    def lowStateJson
    def highStateJson
    
    if (lowStateMap) {
        lowStateJson = json {
            on lowStateMap[0]
            fanLevel lowStateMap[1]
            temperatureUnit lowStateMap[2]
            targetTemperature lowStateMap[3]
            mode lowStateMap[4]
        }
    }
    else { lowStateJson = null }
    
    if (highStateMap) {
        highStateJson = json {
            on highStateMap[0]
            fanLevel highStateMap[1]
            temperatureUnit highStateMap[2]
            targetTemperature highStateMap[3]
            mode highStateMap[4]
        }
    }
    else { highStateJson = null }
    
    def root = json {
    	deviceUid device.deviceNetworkId
        highTemperatureWebhook null
        highTemperatureThreshold highThres        
        lowTemperatureWebhook null
        type stype        
        lowTemperatureState lowStateJson
        enabled on_off
        highTemperatureState highStateJson
        lowTemperatureThreshold lowThres             
    }
    
    displayDebugLog("CLIMATE REACT STRING : " + JsonOutput.prettyPrint(json.toString()))
    def result = parent.configureClimateReact(this, device.deviceNetworkId, json.toString())
    
    if (result) {
    	displayInfoLog( "Climate React settings changed for " + device.deviceNetworkId)
              
        sendEvent(name: 'Climate', value: on_off, displayed: false)
        
        generateStatusEvent()
    	refresh()
    }
    else {
       	generateErrorEvent()
        
        generateStatusEvent()
    }
}*/

def switchFanLevel() {
	displayTraceLog( "switchFanLevel() called")
    
	def currentFanMode = device.currentState("fanLevel")?.value
	displayDebugLog("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("auto")
			break
        case "auto":
			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
}

def GetNextMode(mode, modes) {
    displayTraceLog( "GetNextMode called with " + mode)
        
    def listMode = ['heat','cool','fan','dry','auto']	
    def newMode = returnNext(listMode, modes,mode)
    
    displayDebugLog("Next Mode = " + newMode)
    
    return newMode
}

def NextMode(sMode) {
    displayTraceLog( "NextMode called()")

    if (sMode != null) {
        switch (sMode) {
            case "heat":
                modeHeat()
                break
            case "cool":
                modeCool()
                break
            case "fan":
                modeFan()
                break
            case "dry":
                modeDry()
                break
            case "auto":
                modeAuto()
                break                
        }
    }
    else 
    {
    	return null
    }
}

def GetNextSwingMode(swingMode, swingModes){
    displayTraceLog( "GetNextSwingMode() called with " + swingMode)
	
    if (swingModes == null || swingModes == "null") {
    	return null
    }
    
    def listSwingMode = ['stopped','fixedTop','fixedMiddleTop','fixedMiddle','fixedMiddleBottom','fixedBottom','rangeTop','rangeMiddle','rangeBottom','rangeFull','horizontal','both']	
    def newSwingMode = returnNext(listSwingMode, swingModes,swingMode)
    
    displayDebugLog("Next Swing Mode = " + newSwingMode)
    
    return newSwingMode
}

def switchSwing() {
    displayTraceLog( "switchSwing() called")

    def currentMode = device.currentState("swing")?.value
    displayDebugLog("switching Swing mode from current mode: $currentMode")
    def returnCommand
    switch (currentMode) {
        case "stopped":
            returnCommand = modeSwing("fixedTop")
            break
        case "fixedTop":
            returnCommand = modeSwing("fixedMiddleTop")
            break
        case "fixedMiddleTop":
            returnCommand = modeSwing("fixedMiddle")
            break
        case "fixedMiddle":
            returnCommand = modeSwing("fixedMiddleBottom")
            break
        case "fixedMiddleBottom":
            returnCommand = modeSwing("fixedBottom")
            break
        case "fixedBottom":
            returnCommand = modeSwing("rangeTop")
            break
        case "rangeTop":
            returnCommand = modeSwing("rangeMiddle")
            break        
        case "rangeMiddle":
            returnCommand = modeSwing("rangeBottom")
            break
        case "rangeBottom":
            returnCommand = modeSwing("rangeFull")
            break
        case "rangeFull":
            returnCommand = modeSwing("horizontal")
            break
        case "horizontal":
            returnCommand = modeSwing("both")
            break
        case "both":
            returnCommand = modeSwing("stopped")
            break
    }

    returnCommand
}
def modeSwing(String newSwing) {
    displayTraceLog( "modeSwing() called with " + newSwing)
    
    def Setpoint = device.currentValue("targetTemperature").toInteger()
   
    def SwingBefore = device.currentState("swing").value
    def capabilities = parent.getCapabilities(device.deviceNetworkId, device.currentState("currentmode").value)
    def Swing = SwingBefore
    if (capabilities.remoteCapabilities != null) {
    	def Swings = capabilities.remoteCapabilities.swing

        displayDebugLog("Swing capabilities : " + capabilities.remoteCapabilities.swing)

        Swing = GetNextSwingMode(newSwing,capabilities.remoteCapabilities.swing)
        //displayDebugLog("Swing : " + Swing)
        
        def result = parent.setACStates(this, device.deviceNetworkId, "on", device.currentState("currentmode").value, Setpoint, device.currentState("fanLevel").value, Swing, device.currentState("temperatureUnit").value)
        if (result) {
            displayInfoLog( "Swing mode changed to " + Swing + " for " + device.deviceNetworkId)
            
            sendEvent(name: 'swing', value: Swing, displayed: false,isStateChange: true)
            if (device.currentState("switch").value == "off") { generateSwitchEvent("on") }
            sendEvent(name: 'thermostatFanMode', value: "on", displayed: false)
            generateSwingModeEvent(Swing)
            
            // generateStatusEvent()
            refresh()
        }
        else {
            generateErrorEvent()
            // generateStatusEvent()
        }              
    }
    else {
      //TODO
    }
}

def generateSwingModeEvent(mode) {
   sendEvent(name: "swing", value: mode, descriptionText: "$device.displayName swing mode is now ${mode}", displayed: true, isStateChange: true)
}

private getThermostatDescriptionText(name, value, linkText) {
    if(name == "temperature") {
        return "$name was $value " + device.currentState("temperatureUnit").value
    }
    else if(name == "humidity") {
        return "$name was $value %"
    }
    else if(name == "targetTemperature") {
        return "latest temperature setpoint was $value " + device.currentState("temperatureUnit").value
    }
    else if(name == "fanLevel") {
        return "latest fan level was $value"
    }
    else if(name == "on") {
        return "latest switch was $value"
    }
    else if (name == "mode") {
        return "thermostat mode was ${value}"
    }
    else if (name == "currentmode") {
        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 == "thermostatMode")  {
        return "thermostat mode was ${value}"
    }
    else if (name == "temperatureUnit") {
    	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 == "swing") {
    	return "Swing mode was ${value}"
    }
    else if (name == "Error") {
    	def str = (value == "Failed") ? "failed" : "success"
        return "Last setACState was ${str}"
    }
    else {
        return "${name} = ${value}"
    }
}

// parse events into attributes
def parse(String description) {
    displayDebugLog("Parsing '${description}'")
    
    def name = null
    def value = null
    def 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:")) {
        displayDebugLog("Switch command")
        name = "switch"
        value = description?.endsWith(" 1") ? "on" : "off"
    }
    else if (description?.startsWith("temperature")) {
    	displayDebugLog("Temperature")
        name = "temperature"
        value = device.currentValue("temperature")
    }
    else if (description?.startsWith("humidity")) {
    	displayDebugLog("Humidity")
        name = "humidity"
        value = device.currentValue("humidity")
    }
    else if (description?.startsWith("targetTemperature")) {
    	displayDebugLog("targetTemperature")
        name = "targetTemperature"
        value = device.currentValue("targetTemperature")
    }
    else if (description?.startsWith("fanLevel")) {
    	displayDebugLog("fanLevel")
        name = "fanLevel"
        value = device.currentValue("fanLevel")
    }
    else if (description?.startsWith("currentmode")) {
    	displayDebugLog("mode")
        name = "currentmode"
        value = device.currentValue("currentmode")
    }
    else if (description?.startsWith("on")) {
    	displayDebugLog("on")
        name = "on"
        value = device.currentValue("on")
    }
    else if (description?.startsWith("switch")) {
    	displayDebugLog("switch")
        name = "switch"
        value = device.currentValue("on")
    }
    else if (description?.startsWith("temperatureUnit")) {
    	displayDebugLog("temperatureUnit")
        name = "temperatureUnit"
        value = device.currentValue("temperatureUnit")
    }
    else if (description?.startsWith("Error")) {
    	displayDebugLog("Error")
        name = "Error"
        value = device.currentValue("Error")
    }
    else if (description?.startsWith("voltage")) {
    	displayDebugLog("voltage")
        name = "voltage"
        value = device.currentValue("voltage")
    }
    else if (description?.startsWith("battery")) {
    	displayDebugLog("battery")
        name = "battery"
        value = device.currentValue("battery")
    }
    else if (description?.startsWith("swing")) {
    	displayDebugLog("swing")
        name = "swing"
        value = device.currentValue("swing")
    }
	
    def result = createEvent(name: name, value: value)
    displayDebugLog("Parse returned ${result?.descriptionText}")
    return result
}

def ping(){
	displayTraceLog( "calling parent ping()")
	return parent.ping()
}
3 Likes

Something that I have noticed is that there is potential to do more logical comparison to fire changes in a more controlled way. At the moment if anything changes it does a change on all attributes. I'll put it on the list

2 Likes