Anybody using a Sensibo?

Anybody have a look at the Sensibo driver yet? I took a look over on the ST forums and saw this (Sensibo Intergration) that looks great. I tried to port it over just to see what would happen. It ported straight over without any errors when saving but when I tried to run it, the box for the API Key doesn’t show up and I get this error…

app:5542018-05-01 08:06:14.336:errorCannot get property 'apikey' on null object on line 46
app:5542018-05-01 08:06:14.323:debugsetAPIKey()`

Also took a quick look but couldn’t find the developer over here…yet!

1 Like

I would be very pumped if the ST DTH for sensibo could be ported over to hubitat.

Unfortunately I probably can’t be of any use with adapting the code, but I’m happy to test it out if someone else is working on it.

I fixed the issues with Eric G's ST app and DTH. I've only tried a few of the functions, but it seems to be working.

App Code

/**
 *  Sensibo (Connect)
 *
 *  Copyright 2015 Eric Gosselin
 *
 *  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.
 *
 */

definition(
    name: "Sensibo (Connect)",
    namespace: "EricG66",
    author: "Eric Gosselin",
    description: "Connect your Sensibo Pod to Hubitat.",
    category: "Green Living",
    iconUrl: "https://image.ibb.co/f8gMFQ/on_color_large_sm.png",
    iconX2Url: "https://image.ibb.co/eOwA9k/on_color_large2x.png",
    iconX3Url: "https://image.ibb.co/cq9V9k/on_color_large3x.png",
    singleInstance: true) 

{
    appSetting "apikey"
}

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")
}

def getServerUrl() { "https://home.sensibo.com" }
def getapikey() { apiKey }

def setAPIKey()
{
	log.debug "setAPIKey()"
    
    if(appSettings)
    	def pod = appSettings.apikey
    else {
        def pod = ""
    }
    
    def p = dynamicPage(name: "SelectAPIKey", title: "Enter your API Key", uninstall: true) {
		section(""){
			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: pod)
		}
	}
    return p
}

def SensiboPodList()
{
	log.debug "SensiboPodList()"

	def stats = getSensiboPodList()
	log.debug "device list: $stats"
    
	def p = dynamicPage(name: "deviceList", title: "Select Your Sensibo Pods", 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)
		}
        
        // No push notifications on Hubitat
        /*
        section("Receive Pod sensors infos") {
        	input "boolnotifevery", "bool",submitOnChange: true, required: false, title: "Receive temperature, humidity and battery level notification every hour?"
            href(name: "toTimePageEvent",
                     page: "timePageEvent", title:"Only during a certain time", require: false)
        }
		
        section("Alert on sensors (threshold)") {
        	input "sendPushNotif", "bool",submitOnChange: true, required: false, title: "Receive alert on Sensibo Pod sensors based on threshold?"                       
        }

		if (sendPushNotif) {
           section("Select the temperature threshold",hideable: true) {
            	input "minTemperature", "decimal", title: "Min Temperature",required:false
            	input "maxTemperature", "decimal", title: "Max Temperature",required:false }
            section("Select the humidity threshold",hideable: true) {
            	input "minHumidity", "decimal", title: "Min Humidity level",required:false
            	input "maxHumidity", "decimal", title: "Max Humidity level",required:false }              
         
        	section("How frequently?") {
        		input(name:"days", title: "Only on certain days of the week", type: "enum", required:false, multiple: true, options: ["Monday", "Tuesday", "Wednesday","Thursday","Friday","Saturday","Sunday"])
        	}
        	section("") {
        		href(name: "toTimePage",
                	 page: "timePage", title:"Only during a certain time", require: false)
        	}
        }
        */
	}
	return p
}

// page def must include a parameter for the params map!
def timePage() {
    dynamicPage(name: "timePage", uninstall: false, install: false, title: "Only during a certain time") {
      section("") {
        input(name: "startTime", title: "Starting at : ", required:false, multiple: false, type:"time",)
        input(name: "endTime", title: "Ending at : ", required:false, multiple: false, type:"time")
      }
   }
}

// page def must include a parameter for the params map!
def timePageEvent() {
    dynamicPage(name: "timePageEvent", uninstall: false, install: false, title: "Only during a certain time") {
      section("") {
        input(name: "startTimeEvent", title: "Starting at : ", required:false, multiple: false, type:"time",)
        input(name: "endTimeEvent", title: "Ending at : ", required:false, multiple: false, type:"time")
      }
   }
}

def getSensiboPodList()
{
	log.debug "getting device list"
       
    def deviceListParams = [
    uri: "${getServerUrl()}",
    path: "/api/v2/users/me/pods",
    requestContentType: "application/json",
    query: [apiKey:"${getapikey()}", 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
				}
			}
	  }
    }
    catch(Exception e)
	{
		log.debug "Exception Get Json: " + e
		debugEvent ("Exception get JSON: " + e)
	}
    
    log.debug "Sensibo Pods: $pods"  
	
    return pods
}

def installed() {
	log.debug "Installed with settings: ${settings}"

	state.lastTemperaturePush = null
    state.lastHumidityPush = null
  
	initialize()
    
    def d = getAllChildDevices()

	if (boolnotifevery) {
    	//runEvery1Hour("hournotification")
        schedule("0 0 * * * ?", "hournotification")
	}
    
    //subscribe(d,"temperatureUnit",eTempUnitHandler)
    
    if (sendPushNotif) { 
    	subscribe(d, "temperature", eTemperatureHandler)
        subscribe(d, "humidity", eHumidityHandler)
    }
}

def updated() {
	log.debug "Updated with settings: ${settings}"

	unschedule()
    unsubscribe()
	
    state.lastTemperaturePush = null
    state.lastHumidityPush = null
    
    initialize()
    
    def d = getAllChildDevices()
    
    if (boolnotifevery) {
    	//runEvery1Hour("hournotification")
        schedule("0 0 * * * ?", "hournotification")
	}
    
    //subscribe(d,"temperatureUnit",eTempUnitHandler)
    
    if (sendPushNotif) {
    	subscribe(d, "temperature", eTemperatureHandler)
        subscribe(d, "humidity", eHumidityHandler)
    }
}

def hournotification() {
	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 = getAllChildDevices()
            devices.each { d ->
                log.debug "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 = getAllChildDevices()
            devices.each { d ->
                log.debug "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") {
//        log.debug "switch turned on!"
//    } else if (evt.value == "off") {
//        log.debug "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() {
	log.debug "refresh() called"

    unschedule()
    
	refreshDevices()
    runEvery15Minutes("refreshDevices")
}


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

def refreshDevices() {
	log.debug "refreshDevices() called"
	def devices = getAllChildDevices()
	devices.each { d ->
		log.debug "Calling refresh() on device: ${d.id}"
        
		d.refresh()
	}
}

def getChildNamespace() { "EricG66" }
def getChildTypeName() { "SensiboPod" }

def initialize() {
    log.debug "key "+ getapikey()
    
    atomicState.apikey = getapikey()
	
    log.debug "initialize"

	def devices = SelectedSensiboPods.collect { dni ->
		log.debug dni
		def d = getChildDevice(dni)

		if(!d)
			{
                
            	def name = getSensiboPodList().find( {key,value -> key == dni })
				log.debug "Pod : ${name.value} - Hub : ${location.hubs[0].name} - Type : " +  getChildTypeName() + " - Namespace : " + getChildNamespace()

				d = addChildDevice(getChildNamespace(), getChildTypeName(), dni, location.hubs[0].id, [
                	"label" : "Pod ${name.value}",
                    "name" : "Pod ${name.value}"
                    ])
                d.setIcon("on","on","https://image.ibb.co/bz9K25/on_color_large_on.png")
                d.setIcon("off","on","https://image.ibb.co/k5S6h5/on_color_large.png")
                d.save()              
                
				log.debug "created ${d.displayName} with id $dni"
			}
			else
			{
				log.debug "found ${d.displayName} with id $dni already exists"
			}

			return d
		}

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

	def delete
	// Delete any that are no longer in settings
	if(!SelectedSensiboPods)
	{
		log.debug "delete Sensibo"
		delete = getAllChildDevices()
	}
	else
	{
		delete = getChildDevices().findAll { !SelectedSensiboPods.contains(it.deviceNetworkId) }
	}

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

	def PodList = getAllChildDevices()
	
    pollHandler()
    
    refreshDevices()
    
    runEvery15Minutes("refreshDevices")
}


// Subscribe functions

def OnOffHandler(evt) {
	log.debug "on 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!")
        }
    }
}

def getPollRateMillis() { return 45 * 1000 }

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

	debugEvent ("Last Poll Millis = ${atomicState.lastPollMillis}")
	def last = atomicState.lastPollMillis ?: 0
	def next = last + pollRateMillis

	log.debug "pollChild( ${child.device.deviceNetworkId} ): $now > $next ?? w/ current state: ${atomicState.sensibo}"
	debugEvent ("pollChild( ${child.device.deviceNetworkId} ): $now > $next ?? w/ current state: ${atomicState.sensibo}")

	//if( now > next )
	if( true ) // for now let's always poll/refresh
	{
		log.debug "polling children because $now > $next"
		debugEvent("polling children because $now > $next")

		pollChildren(child.device.deviceNetworkId)

		log.debug "polled children and looking for ${child.device.deviceNetworkId} from ${atomicState.sensibo}"
		debugEvent ("polled children and looking for ${child.device.deviceNetworkId} from ${atomicState.sensibo}")

		def currentTime = new Date().time
		debugEvent ("Current Time = ${currentTime}")
		atomicState.lastPollMillis = currentTime

		def tData = atomicState.sensibo[child.device.deviceNetworkId]
        
        if (tData == null) return
        
        log.debug  "DEBUG - TDATA" + tData
        debugEvent ("Error in Poll ${tData.data.Error}",false)
        //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(atomicState.sensibo[child.device.deviceNetworkId] != null)
	{
		log.debug "not polling children, found child ${child.device.deviceNetworkId} "

		def tData = atomicState.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(atomicState.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 setACStates(child,String PodUid, on, mode, targetTemperature, fanLevel, swingM, sUnit)
{
	log.debug "SetACStates 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"
    
    log.debug "Target Temperature :" + targetTemperature
    
	def jsonRequestBody = '{"acState":{"on": ' + OnOff.toString() + ',"mode": "' + mode + '"'
    
    log.debug "Fan Level is :$fanLevel"
    log.debug "Swing is :$swingM"
    log.debug "Target Temperature is :$targetTemperature"
    
    if (fanLevel != "null") {
       log.debug "Fan Level info is present"
       jsonRequestBody += ',"fanLevel": "' + fanLevel + '"'
    }
    
    if (targetTemperature != 0) {
    	jsonRequestBody += ',"targetTemperature": '+ targetTemperature + ',"temperatureUnit": "' + sUnit + '"'       
    }
    if (swingM)
    {
        jsonRequestBody += ',"swing": "' + swingM + '"'
    }
    
    jsonRequestBody += '}}'
    
    log.debug "Mode Request Body = ${jsonRequestBody}"
	debugEvent ("Mode Request Body = ${jsonRequestBody}")

	def result = sendJson(PodUid, jsonRequestBody)

	if (result) {
		def tData = atomicState.sensibo[child.device.deviceNetworkId]
        
        if (tData == null) return false
        
		tData.data.fanLevel = fanLevel
        tData.data.thermostatFanMode = fanLevel
        tData.data.on = on
        tData.data.mode = mode
        log.debug "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 = atomicState.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)
{
	def data = [:]   
	def pollParams = [
    	uri: "${getServerUrl()}",
    	path: "/api/v2/pods/${PodUid}",
    	requestContentType: "application/json",
    	query: [apiKey:"${getapikey()}", type:"json", fields:"remoteCapabilities,productModel"]]
     try {

     	 httpGet(pollParams) { resp ->

         if (resp.data) {
         		log.debug "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"]]]]]]
                    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                        
                    }
                    return data
                }
                else {
                	log.debug "get remoteCapabilities Failed"
                    
                    data = [
						remoteCapabilities : "",
                        productModel : ""
                    ]                    
              		return data
                }
			}
         }
         return data
     }
     catch(Exception e) {     	
        log.debug "get remoteCapabilities Failed"
        
        data = [
        	remoteCapabilities : "",
            productModel : ""
        ]        
     	return data
     }
}

// Get the latest state from the Sensibo Pod
def getACState(PodUid)
{
	def data = [:]
	def pollParams = [
    	uri: "${getServerUrl()}",
    	path: "/api/v2/pods/${PodUid}/acStates",
    	requestContentType: "application/json",
    	query: [apiKey:"${getapikey()}", 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") {
                    	
                        log.debug "get ACState Success"
                        log.debug "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
                        }
                        
                        log.debug "product Model : " + stat.device.productModel
                        def battery = stat.device.productModel == "skyv1" ? "battery" : "mains"
                        
                        log.debug "swing Mode :" + stat.acState.swing
                        data = [
                            targetTemperature : stemp,
                            fanLevel : stat.acState.fanLevel,
                            mode : stat.acState.mode,
                            on : OnOff.toString(),
                            switch: OnOff.toString(),
                            thermostatMode: tMode,
                            thermostatFanMode : stat.acState.fanLevel,
                            coolingSetpoint : stemp,
                            heatingSetpoint : stemp,
                            thermostatSetpoint : stemp,
                            temperatureUnit : tempUnit,
                            swing : stat.acState.swing,
                            powerSource : battery,
                            productModel : stat.device.productModel,
                            firmwareVersion : stat.device.firmwareVersion,
                            Error : "Success"
                        ]

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

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

	def returnStatus = -1
    try{
       httpPost(cmdParams) { resp ->
			if(resp.status == 200) {
                log.debug "updated ${resp.data}"
				debugEvent("updated ${resp.data}")
				returnStatus = resp.status
				log.debug "Successful call to Sensibo API."
            }
           	else { log.debug "Failed call to Sensibo API." }
       }
    }
    catch(Exception e)
	{
		log.debug "Exception Sending Json: " + e
		debugEvent ("Exception Sending JSON: " + e)
		return false
	}
    
    if (returnStatus == 200)
		return true
	else
		return false
}

def pollChildren(PodUid)
{
	log.debug "polling children"
    
   def thermostatIdsString = PodUid

	log.debug "polling children: $thermostatIdsString"
    
	def pollParams = [
    	uri: "${getServerUrl()}",
    	path: "/api/v2/pods/${thermostatIdsString}/measurements",
    	requestContentType: "application/json",
    	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) {
				log.debug "poll results returned"                                

                log.debug "DEBUG DATA RESULT" + resp.data.result
                
                if (resp.data.result == null || resp.data.result.empty) 
                {
                	log.debug "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)
                if (setTemp.Error != "Failed") {
                
				 atomicState.sensibo = resp.data.result.inject([:]) { collector, stat ->

					def dni = thermostatIdsString
					
					log.debug "updating dni $dni"
                    
                    def stemp = stat.temperature.toDouble().round(1)
                    def shumidify =  stat.humidity.toDouble().round()

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

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

					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,
                        mode: setTemp.mode,
                        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,
                        Error: setTemp.Error
					]
                    
					debugEvent ("Event Data = ${data}",false)

					collector[dni] = [data:data]
                    
					return collector
				 }				
                }
                
				log.debug "updated ${atomicState.sensibo[thermostatIdsString].size()} stats: ${atomicState.sensibo[thermostatIdsString]}"
                debugEvent ("updated ${atomicState.sensibo[thermostatIdsString]}",false)
			}
			else
			{
				log.error "polling children & got http status ${resp.status}"		
			}
		}

	}
	catch(Exception e)
	{
		log.debug "___exception polling children: " + e
		debugEvent ("${e}")
	}
}

def pollHandler() {

	debugEvent ("in Poll() method.")
	
    // Hit the Sensibo API for update on all the Pod
	
    def PodList = getAllChildDevices()
    
    log.debug PodList
    PodList.each { 
    	log.debug "polling " + it.deviceNetworkId
        pollChildren(it.deviceNetworkId) }
	
    atomicState.sensibo.each {stat ->

		def dni = stat.key

		log.debug ("DNI = ${dni}")
		debugEvent ("DNI = ${dni}")

		def d = getChildDevice(dni)

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

def debugEvent(message, displayEvent = false) {

	def results = [
		name: "appdebug",
		descriptionText: message,
		displayed: displayEvent
	]
	log.debug "Generating AppDebug Event: ${results}"
	sendEvent (results)

}

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

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

DTH Code:

    /**
     *  Sensibo
     *
     *  Copyright 2015 Eric Gosselin
     *
     *  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.
     *
     */

    import groovyx.net.http.HTTPBuilder
    import groovyx.net.http.ContentType
    import groovyx.net.http.Method
    import groovyx.net.http.RESTClient

    preferences {
    }

    metadata {
        definition (name: "SensiboPod", namespace: "EricG66", author: "Eric Gosselin", oauth: false) {
            capability "Relative Humidity Measurement"
            capability "Temperature Measurement"
            capability "Polling"
            capability "Refresh"
            capability "Switch"
            capability "Thermostat"
            capability "Battery"
            capability "Actuator"
            capability "Sensor"
            capability "Health Check"
            capability "Power Source"
            capability "Voltage Measurement"
            
            attribute "swing", "String"
            attribute "temperatureUnit","String"
            attribute "productModel","String"
            attribute "firmwareVersion","String"
            attribute "targetTemperature","Double"
            attribute "off","String"
            attribute "on","String"
            attribute "statusText","String"
            attribute "mode","String"
            attribute "fanLevel","String"
            
            command "switchFanLevel"
            command "switchMode"
            command "raiseCoolSetpoint"
            command "lowerCoolSetpoint"
            command "raiseHeatSetpoint"
            command "lowerHeatSetpoint" 
            command "voltage"
            command "raiseTemperature"
            command "lowerTemperature"
            command "switchSwing"
            command "setThermostatMode"
            command "modeHeat"
            command "modeCool"
            command "fullfan"
            command "autofan"
            command "fullswing"
          
        }

        simulator {
            // TODO: define status and reply messages here
            
            // status messages
            status "on": "on/off: 1"
            status "off": "on/off: 0"
            status "temperature":"22"
            status "targetTemperature":"22"
            //status "humidity": "humidity"
            
            // reply messages
            reply "zcl on-off on": "on/off: 1"
            reply "zcl on-off off": "on/off: 0"
        }

        tiles(scale: 2) {
            multiAttributeTile(name:"thermostatMulti", type:"thermostat",, width:6, height:4) {
                tileAttribute("device.temperature", key: "PRIMARY_CONTROL") {
                    attributeState ("default", label:'${currentValue}°')//, 
                    //backgroundColors:[
                    //    [value: 15, color: "#153591"],
                    //    [value: 18, color: "#1e9cbb"],
                    //    [value: 21, color: "#90d2a7"],
                    //    [value: 24, color: "#44b621"],
                    //    [value: 27, color: "#f1d801"],
                    //    [value: 30, color: "#d04e00"],
                    //    [value: 33, color: "#bc2323"],
                    //    [value: 59, color: "#153591"],
                    //    [value: 64, color: "#1e9cbb"],
                    //    [value: 70, color: "#90d2a7"],
                    //    [value: 75, color: "#44b621"],
                    //    [value: 81, color: "#f1d801"],
                    //    [value: 86, color: "#d04e00"],
                    //    [value: 91, color: "#bc2323"]
                    //])
                }
                tileAttribute("device.targetTemperature", key: "VALUE_CONTROL") {
                    attributeState("VALUE_UP", action: "raiseTemperature")
                    attributeState("VALUE_DOWN", action: "lowerTemperature")                
                }
                tileAttribute("device.humidity", key: "SECONDARY_CONTROL") {
                    attributeState("default", label:'${currentValue}%', unit:"%")
                }
                tileAttribute("device.thermostatOperatingState", key: "OPERATING_STATE") {
                    attributeState("idle", backgroundColor:"#a9a9a9")
                    attributeState("heating", backgroundColor:"#e86d13")
                    attributeState("cooling", backgroundColor:"#00a0dc")
                    attributeState("fan only", backgroundColor:"#44b621")
                    attributeState("Dry", backgroundColor:"#A1E5E5")
                }
                tileAttribute("device.thermostatMode", key: "THERMOSTAT_MODE") {
                    attributeState("off", label:'${name}')
                    attributeState("heat", label:'${name}')
                    attributeState("cool", label:'${name}')                
                    attributeState("fan", label:'${name}')
                    attributeState("dry", label:'${name}')
                    attributeState("auto", label:'${name}')
                }            
                
                tileAttribute("device.heatingSetpoint", key: "HEATING_SETPOINT") {
                    attributeState("default", label:'${currentValue}')
                }
                tileAttribute("device.coolingSetpoint", key: "COOLING_SETPOINT") {
                    attributeState("default", label:'${currentValue}')
                }
            }
            
            valueTile("temperature", "device.temperature", width: 2, height: 2) {
                state("temperature", label:'Temp: ${currentValue}',backgroundColors:[])
            }
            
            valueTile("humidity", "device.humidity", width: 2, height: 2) {
                state("humidity", label:'Humidity: ${currentValue} %',
                    backgroundColors:[
                    ]
                )
            }
               
            valueTile("voltage", "device.voltage", width: 2, height: 2) {
                state("voltage", label:'${currentValue}',
                    backgroundColors:[
                        [value: 2700, color: "#CC0000"],
                        [value: 2800, color: "#FFFF00"],
                        [value: 2900, color: "#00FF00"]
                    ]
               )
            }
            
            valueTile("firmwareVersion", "device.firmwareVersion", width: 1, height: 1) {
                state("version", label:'Firmware: ${currentValue}',backgroundColors:[])
            }
            
            valueTile("productModel", "device.productModel", width: 1, height: 1) {
                state("mains", label:'Model: ${currentValue}',backgroundColors:[])
            }
            
            valueTile("powerSource", "device.powerSource", width: 1, height: 1) {
                state("mains", icon:"https://image.ibb.co/inKcN5/cable_power_cord_plug_circle_512.png", label:'Source: ${currentValue}',backgroundColors:[])
                state("battery", icon:"https://image.ibb.co/gFUNpk/battery.jpg", label:'Source: ${currentValue}',backgroundColors:[])
            }
            
            standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) {
                state "on", label:'${name}', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#00a0dc"
                state "off", label:'${name}', action:"switch.on", icon:"st.switches.switch.off", backgroundColor:"#ffffff"
            }
        
            standardTile("fanLevel", "device.fanLevel", width: 2, height: 2) {
                state "low", action:"switchFanLevel", backgroundColor:"#8C8C8D", icon:"https://image.ibb.co/gZxHpk/fan_low_2.png", nextState:"medium"
                state "medium", action:"switchFanLevel", backgroundColor:"#8C8C8D", icon:"https://image.ibb.co/cUa3Uk/fan_medium_2.png", nextState:"high"
                state "high", action:"switchFanLevel", backgroundColor:"#8C8C8D", icon:"https://image.ibb.co/fcfFaQ/fan_high_2.png", nextState:"auto"
                state "auto", action:"switchFanLevel", backgroundColor:"#8C8C8D", icon:"https://image.ibb.co/m8oq9k/fan_auto_2.png" , nextState:"quiet"
                state "quiet", action:"switchFanLevel", backgroundColor:"#8C8C8D", icon:"https://image.ibb.co/mC74wb/fan_quiet2.png" , nextState:"medium_high"
                state "medium_high", action:"switchFanLevel", backgroundColor:"#8C8C8D", icon:"https://image.ibb.co/j62DXR/fan_medium_3.png" , nextState:"medium_low"
                state "medium_low", action:"switchFanLevel", backgroundColor:"#8C8C8D", icon:"https://image.ibb.co/etX296/fan_low_3.png" , nextState:"strong"
                state "strong", action:"switchFanLevel", backgroundColor:"#8C8C8D", icon:"https://image.ibb.co/eaLeUw/fan_strong2.png" , nextState:"low"
            }
            
            standardTile("mode", "device.thermostatMode",  width: 2, height: 2) {
                state "heat", action:"switchMode", backgroundColor:"#e86d13", icon:"https://image.ibb.co/c7Grh5/sun.png", nextState:"cool"
                state "cool", action:"switchMode", backgroundColor:"#00a0dc", icon:"https://image.ibb.co/bZ56FQ/cold.png", nextState:"fan"
                state "fan", action:"switchMode", backgroundColor:"#e8e3d8", icon:"https://image.ibb.co/n1dhpk/status_message_fan.png", nextState:"dry"
                state "dry", action:"switchMode", backgroundColor:"#e8e3d8", icon:"https://image.ibb.co/k2ZNpk/dry_mode.png", nextState:"auto"
                state "auto", action:"switchMode", backgroundColor:"#e8e3d8", icon:"https://image.ibb.co/dwaRh5/auto_mode.png", nextState:"heat"               
            }
            
            standardTile("upCoolButtonControl", "device.targetTemperature", inactiveLabel: false, decoration: "flat", width: 1, height: 2) {
                state "setpoint", action:"raiseCoolSetpoint", icon:"st.thermostat.thermostat-up",label :"Up"
            }
            
            standardTile("downCoolButtonControl", "device.targetTemperature", inactiveLabel: false, decoration: "flat", width: 1, height: 2) {
                state "setpoint", action:"lowerCoolSetpoint", icon:"st.thermostat.thermostat-down", label :"Down"
            }
                   
            standardTile("swing", "device.swing",  width: 2, height: 2) {
                state "stopped", action:"switchSwing", backgroundColor:"#8C8C8D", icon:"https://image.ibb.co/iWhvaQ/stopped.png", nextState:"fixedTop"
                state "fixedTop", action:"switchSwing", backgroundColor:"#8C8C8D", icon:"https://image.ibb.co/nbV3Uk/fixedTop.png", nextState:"fixedMiddleTop"
                state "fixedMiddleTop", action:"switchSwing", backgroundColor:"#8C8C8D", icon:"https://image.ibb.co/chbcpk/fixed_Middle_Top.png", nextState:"fixedMiddle"
                state "fixedMiddle", action:"switchSwing", backgroundColor:"#8C8C8D", icon:"https://image.ibb.co/dxDe25/fixed_Middle.png", nextState:"fixedMiddleBottom"
                state "fixedMiddleBottom", action:"switchSwing", backgroundColor:"#8C8C8D", icon:"https://image.ibb.co/ebZmh5/fixed_Middle_Bottom.png", nextState:"fixedBottom"
                state "fixedBottom", action:"switchSwing", backgroundColor:"#8C8C8D", icon:"https://image.ibb.co/n2tCN5/fixed_Bottom.png", nextState:"rangeTop"
                state "rangeTop", action:"switchSwing", backgroundColor:"#8C8C8D", icon:"https://image.ibb.co/gYQsN5/rangeTop.png", nextState:"rangeMiddle"
                state "rangeMiddle", action:"switchSwing", backgroundColor:"#8C8C8D", icon:"https://image.ibb.co/dC1XN5/range_Middle.png", nextState:"rangeBottom"
                state "rangeBottom", action:"switchSwing", backgroundColor:"#8C8C8D", icon:"https://image.ibb.co/csTOUk/range_Bottom.png", nextState:"rangeFull"
                state "rangeFull", action:"switchSwing", backgroundColor:"#8C8C8D", icon:"https://image.ibb.co/hHK8vQ/range_Full.png", nextState:"horizontal"
                state "horizontal", action:"switchSwing", backgroundColor:"#8C8C8D", icon:"https://image.ibb.co/cAGQ2G/range_Horizontal2.png", nextState:"both"
                state "both", action:"switchSwing", backgroundColor:"#8C8C8D", icon:"https://image.ibb.co/dLUOpw/range_Both2.png", nextState:"stopped"
            }
            
            standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
                state "default", label:"Refresh", action:"refresh.refresh", icon:"st.secondary.refresh"
            }
            
            standardTile("coolmode", "device.thermostatMode",  width: 1, height: 1) {
                state "cool", action:"modeCool", backgroundColor:"#00a0dc", icon:"https://image.ibb.co/bZ56FQ/cold.png"
            }
            standardTile("heatmode", "device.thermostatMode",  width: 1, height: 1) {
                state "heat", action:"modeHeat", backgroundColor:"#e86d13", icon:"https://image.ibb.co/c7Grh5/sun.png"
            }
            standardTile("fullfan", "device.fanLevel",  width: 1, height: 1) {
                state "high", action:"fullfan", backgroundColor:"#8C8C8D", icon:"https://image.ibb.co/fcfFaQ/fan_high_2.png"
            }
            standardTile("autofan", "device.fanLevel",  width: 1, height: 1) {
                state "auto", action:"autofan", backgroundColor:"#8C8C8D", icon:"https://image.ibb.co/m8oq9k/fan_auto_2.png"
            }
            standardTile("fullswing", "device.swing",  width: 1, height: 1) {
                state "rangeFull", action:"fullswing", backgroundColor:"#8C8C8D", icon:"https://image.ibb.co/hHK8vQ/range_Full.png"
            }
            
            main (["switch"])
            details (["thermostatMulti","switch","fanLevel","mode","swing","voltage","refresh","coolmode","heatmode","fullfan","autofan","fullswing","powerSource","firmwareVersion","productModel"])    
        }
    }

    def fullfan()
    {
        dfanLevel("high")
    }

    def autofan()
    {
        dfanLevel("auto")
    }

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

    def temperatureDown(temp)
    {
        def sunit = device.currentValue("temperatureUnit")
        def capabilities = parent.getCapabilities(device.deviceNetworkId, device.currentState("mode").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}
           
        log.debug "Values retrieved : " + found
        
        if (found == null || found.empty) found = values.first()
        else found = found.last()
            
        log.debug "Temp before : " + temp               
        log.debug "Temp after : " + found
            
        temp = found
            
        return temp
    }

    def temperatureUp(temp)
    {
        def sunit = device.currentValue("temperatureUnit")
        def capabilities = parent.getCapabilities(device.deviceNetworkId, device.currentState("mode").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}

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

        log.debug "Temp before : " + temp               
        log.debug "Temp after : " + found

        temp = found
            
        return temp
    }

    void raiseTemperature() {
        def operMode = device.currentState("mode").value
        
        def Setpoint = device.currentValue("targetTemperature").toInteger()
        def theTemp = device.currentValue("temperatureUnit")
        
        log.debug "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;
        }
    }

    void lowerTemperature() {
        def operMode = device.currentState("mode").value
        
        def Setpoint = device.currentValue("targetTemperature").toInteger()
        def theTemp = device.currentValue("temperatureUnit")
        
        log.debug "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;
        }
    }

    void lowerCoolSetpoint() {
        log.debug "Lower SetPoint"
        
        def Setpoint = device.currentValue("targetTemperature").toInteger()
        def theTemp = device.currentValue("temperatureUnit")

        log.debug "Current target temperature = ${Setpoint}"
        
        Setpoint = temperatureDown(Setpoint)

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

        if (result) {
            if (device.currentState("on").value == "off") { generateSwitchEvent("on") }
            
            sendEvent(name: 'coolingSetpoint', value: Setpoint,  displayed: false)
            sendEvent(name: 'thermostatSetpoint', value: Setpoint,  displayed: false)
           
            generateSetTempEvent(Setpoint)
            
            log.debug "New target Temperature = ${Setpoint}"       
        }
        else {
            log.debug "error"
            generateErrorEvent()
        }
        generateStatusEvent()
        refresh()
    }


    void setThermostatMode(modes)
    { 
        log.debug "setThermostatMode"
        //def currentMode = device.currentState("mode")?.value
      
        log.debug "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 raiseCoolSetpoint() {
        log.debug "Raise SetPoint"
        
        def Setpoint = device.currentValue("targetTemperature").toInteger()
        def theTemp = device.currentValue("temperatureUnit")
        
        log.debug "Current target temperature = ${Setpoint}"

        Setpoint = temperatureUp(Setpoint)

        def result = parent.setACStates(this, device.deviceNetworkId , "on", device.currentState("mode").value, Setpoint, device.currentState("fanLevel").value, device.currentState("swing").value, device.currentState("temperatureUnit").value)
        if (result) {
            if (device.currentState("on").value == "off") { generateSwitchEvent("on") }
            
            sendEvent(name: 'coolingSetpoint', value: Setpoint, displayed: false)
            sendEvent(name: 'thermostatSetpoint', value: Setpoint, displayed: false)
            
            generateSetTempEvent(Setpoint)
            
            log.debug "New target Temperature = ${Setpoint}"       
        }
        else {
            generateErrorEvent()
        }
        generateStatusEvent()
        refresh()
    }

    void raiseHeatSetpoint() {
        log.debug "Raise SetPoint"
        
        def Setpoint = device.currentValue("targetTemperature").toInteger()
        def theTemp = device.currentValue("temperatureUnit")
        
        log.debug "Current target temperature = ${Setpoint}"

        Setpoint = temperatureUp(Setpoint)

        def result = parent.setACStates(this, device.deviceNetworkId , "on", device.currentState("mode").value, Setpoint, device.currentState("fanLevel").value, device.currentState("swing").value, device.currentState("temperatureUnit").value)
        if (result) {
            if (device.currentState("on").value == "off") { generateSwitchEvent("on") }
            
            sendEvent(name: 'heatingSetpoint', value: Setpoint, displayed: false)
            sendEvent(name: 'thermostatSetpoint', value: Setpoint, displayed: false)
            
            generateSetTempEvent(Setpoint)
            
            log.debug "New target Temperature = ${Setpoint}"       
        }
        else {
            generateErrorEvent()
        }
        generateStatusEvent()
        refresh()
    }

    void lowerHeatSetpoint() {
        log.debug "Raise SetPoint"
        
        def Setpoint = device.currentValue("targetTemperature").toInteger()
        def theTemp = device.currentValue("temperatureUnit")
        
        log.debug "Current target temperature = ${Setpoint}"

        Setpoint = temperatureDown(Setpoint)

        def result = parent.setACStates(this, device.deviceNetworkId , "on", device.currentState("mode").value, Setpoint, device.currentState("fanLevel").value, device.currentState("swing").value, device.currentState("temperatureUnit").value)
        if (result) {
            if (device.currentState("on").value == "off") { generateSwitchEvent("on") }
            
            sendEvent(name: 'heatingSetpoint', value: Setpoint, displayed: false)    
            sendEvent(name: 'thermostatSetpoint', value: Setpoint, displayed: false)
            
            generateSetTempEvent(Setpoint)
            
            log.debug "New target Temperature = ${Setpoint}"       
        }
        else {
            generateErrorEvent()
        }
        generateStatusEvent()
        refresh()
    }

    def refresh()
    {
      log.debug "refresh called"
      poll()
       
      log.debug "refresh ended"
    }

    // Set Temperature
    def setFanSetpoint(temp) {
        temp = temp.toInteger()
        log.debug "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) {    
            if (device.currentState("on").value == "off") { generateSwitchEvent("on") }
            generateModeEvent("fan")
             
            sendEvent(name: 'thermostatSetpoint', value: temp, displayed: false)
            generateSetTempEvent(temp)
        }
        else {
            generateErrorEvent()
        }
        
        generateStatusEvent()
        refresh()
    }

    // Set Temperature
    def setDrySetpoint(temp) {
        temp = temp.toInteger()
        log.debug "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) {    
            if (device.currentState("on").value == "off") { generateSwitchEvent("on") }
            generateModeEvent("dry")
             
            sendEvent(name: 'thermostatSetpoint', value: temp, displayed: false)
            generateSetTempEvent(temp)
        }
        else {
            generateErrorEvent()
        }
        
        generateStatusEvent()
        refresh()
    }


    // Set Temperature
    def setCoolingSetpoint(temp) {
        temp = temp.toInteger()
        log.debug "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) {    
            if (device.currentState("on").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)
        }
        else {
            generateErrorEvent()
        }
        
        generateStatusEvent()
        refresh()
    }

    def setHeatingSetpoint(temp) {
        temp = temp.toInteger()
        log.debug "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) { 
            if (device.currentState("on").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)
        }    
        else {
            generateErrorEvent()
        }    
        generateStatusEvent()
        refresh()
    }

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

    // Turn off or Turn on the AC
    def on() {
        log.debug "on called"
        
        def Setpoint = device.currentValue("targetTemperature").toInteger()
       
        log.debug "Temp Unit : " + device.currentState("temperatureUnit").value
        def result = parent.setACStates(this, device.deviceNetworkId, "on", device.currentState("mode").value, Setpoint, device.currentState("fanLevel").value, device.currentState("swing").value, device.currentState("temperatureUnit").value)
        if (result) {   
            sendEvent(name: 'thermostatMode', value: device.currentState("mode").value, displayed: false,isStateChange: true)
            //sendEvent(name: 'thermostatOperatingState', value: "idle",isStateChange: true)
            
            generateSwitchEvent("on")
        }
        else {
            generateErrorEvent()
        }        
        generateStatusEvent() 
        refresh()
    }

    def off() {
        log.debug "off called"
        
        def Setpoint = device.currentValue("targetTemperature").toInteger()

        log.debug "Temp Unit : " + device.currentState("temperatureUnit").value
        def result = parent.setACStates(this, device.deviceNetworkId, "off", device.currentState("mode").value, Setpoint, device.currentState("fanLevel").value, device.currentState("swing").value, device.currentState("temperatureUnit").value)

        if (result) { 
            sendEvent(name: 'thermostatMode', value: "off", displayed: false,isStateChange: true)
            //sendEvent(name: 'thermostatOperatingState', value: "idle",isStateChange: true)
            
            generateSwitchEvent("off")
         }
        else {
            generateErrorEvent()
        }         
        generateStatusEvent()
        refresh()
    }

    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}", displayed: true, isStateChange: true)   
       
       //refresh()
    }

    def dfanLevel(String newLevel){
        log.debug "fan " + newLevel
        
        def Setpoint = device.currentValue("targetTemperature").toInteger()
       
        def capabilities = parent.getCapabilities(device.deviceNetworkId, device.currentState("mode").value)      
        def Level = LevelBefore
        if (capabilities.remoteCapabilities != null) {
            def fanLevels = capabilities.remoteCapabilities.fanLevels
            log.debug capabilities.remoteCapabilities.fanLevels
            
            Level = GetNextFanLevel(newLevel,capabilities.remoteCapabilities.fanLevels)
            log.debug "Fan : " + Level
            
            def result = parent.setACStates(this, device.deviceNetworkId,"on", device.currentState("mode").value, Setpoint, Level, device.currentState("swing").value, device.currentState("temperatureUnit").value)
            if (result) {
                if (device.currentState("on").value == "off") { generateSwitchEvent("on") }
                if (Level == "low") {
                    sendEvent(name: 'thermostatFanMode', value: "circulate", displayed: false)
                }
                else {                
                    sendEvent(name: 'thermostatFanMode', value: "on", displayed: false)
                }
                generatefanLevelEvent(Level)
            }
            else {
                generateErrorEvent()
            }      
            generateStatusEvent()
            refresh()
        }
        else {
            //TODO when the mode do not exist
        }    
    }

    def generatefanLevelEvent(mode) {
       sendEvent(name: "fanLevel", value: mode, descriptionText: "$device.displayName fan level is now ${mode}", displayed: true, isStateChange: true)
    }

    def switchFanLevel() {
        log.debug "switchFanLevel"
        def currentFanMode = device.currentState("fanLevel")?.value
        log.debug "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 modeHeat()
    {
        modeMode("heat")
    }

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

    // To change the AC mode

    def switchMode() {
        log.debug "switchMode"
        def currentMode = device.currentState("mode")?.value
        log.debug "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){
        log.debug "mode " + 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
            log.debug capabilities.remoteCapabilities.fanLevels
            
            Level = GetNextFanLevel(LevelBefore,capabilities.remoteCapabilities.fanLevels)
            log.debug "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("on").value == "off") { generateSwitchEvent("on") }

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

           NextMode(sMode)
        }
    }

    def generateModeEvent(mode) {
       sendEvent(name: "mode", value: mode, descriptionText: "$device.displayName Thermostat mode is now ${mode}", displayed: true, isStateChange: true)   
    }
       
    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 GetNextFanLevel(fanLevel, fanLevels)
    {
        def listFanLevel = ['low','medium','high','auto','quiet','medium_high','medium_low','strong']    
        def newFanLevel = returnNext(listFanLevel, fanLevels,fanLevel)
        
        return newFanLevel
    }

    def GetNextMode(mode, modes)
    {
        def listMode = ['heat','cool','fan','dry','auto']    
        def newMode = returnNext(listMode, modes,mode)
        
        return newMode
    }

    def NextMode(sMode)
    {
        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){
        def listSwingMode = ['stopped','fixedTop','fixedMiddleTop','fixedMiddle','fixedMiddleBottom','fixedBottom','rangeTop','rangeMiddle','rangeBottom','rangeFull','horizontal','both']    
        def newSwingMode = returnNext(listSwingMode, swingModes,swingMode)
        
        return newSwingMode
    }

    def switchSwing() {
        log.debug "switchSwing"
        def currentMode = device.currentState("swing")?.value
        log.debug "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)
    {
        log.debug "modeSwing " + newSwing
        
        def Setpoint = device.currentValue("targetTemperature").toInteger()
       
        def SwingBefore = device.currentState("swing").value
        def capabilities = parent.getCapabilities(device.deviceNetworkId, device.currentState("mode").value)
        def Swing = SwingBefore
        if (capabilities.remoteCapabilities != null) {
            def fanLevels = capabilities.remoteCapabilities.swing
            log.debug capabilities.remoteCapabilities.swing
            
            Swing = GetNextSwingMode(newSwing,capabilities.remoteCapabilities.swing)
            log.debug "Swing : " + Swing
            
            def result = parent.setACStates(this, device.deviceNetworkId, "on", device.currentState("mode").value, Setpoint, device.currentState("fanLevel").value, Swing, device.currentState("temperatureUnit").value)
            if (result) {
                sendEvent(name: 'swing', value: Swing, displayed: false,isStateChange: true)
                if (device.currentState("on").value == "off") { generateSwitchEvent("on") }
                sendEvent(name: 'thermostatFanMode', value: "on", displayed: false)
                generateSwingModeEvent(Swing)
            }
            else {
                generateErrorEvent()
            }      
            generateStatusEvent()
            refresh()
        }
        else
        {
          //TODO
        }
    }

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

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


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

    def parseTempUnitEventData(Map results)
    {
        log.debug "parsing data $results"
        if(results)
        {
            results.each { name, value ->
                if (name=="temperatureUnit") { 
                    def linkText = getLinkText(device)
                    def isChange = true
                    def isDisplayed = false
                       
                    sendEvent(
                        name: name,
                        value: value,
                        //unit: value,
                        linkText: linkText,
                        descriptionText: "${name} = ${value}",
                        handlerName: "temperatureUnit",
                        isStateChange: isChange,
                        displayed: isDisplayed)
                }
            }
        }
    }

    def parseEventData(Map results)
    {
        log.debug "parsing Event data $results"
        if(results)
        {
            results.each { name, value -> 
     
                log.debug "name :" + name + " value :" + value
                def linkText = getLinkText(device)
                def isChange = false
                def isDisplayed = true
                                 
                if (name=="voltage") {                                
                     isChange = true //isTemperatureStateChange(device, name, value.toString())
                    isDisplayed = true
                      
                    sendEvent(
                        name: name,
                        value: value,
                        unit: "mA",
                        linkText: linkText,
                        descriptionText: getThermostatDescriptionText(name, value, linkText),
                        handlerName: name,
                        isStateChange: isChange,
                        displayed: isDisplayed)
                    }
                else if (name== "battery") {                                
                    isChange = true //isTemperatureStateChange(device, name, value.toString())
                    isDisplayed = true
                     
                    sendEvent(
                        name: name,
                        value: value,
                        //unit: "V",
                        linkText: linkText,
                        descriptionText: getThermostatDescriptionText(name, value, linkText),
                        handlerName: name,
                        isStateChange: isChange,
                        displayed: isDisplayed)
                    }
                else if (name== "powerSource") {                                
                    isChange = true //isTemperatureStateChange(device, name, value.toString())
                    isDisplayed = true
                      
                    sendEvent(
                        name: name,
                        value: value,
                        linkText: linkText,
                        descriptionText: getThermostatDescriptionText(name, value, linkText),
                        handlerName: name,
                        isStateChange: isChange,
                        displayed: isDisplayed)
                   }
                else if (name=="on") {                
                    isChange = true
                    isDisplayed = false
                       
                    sendEvent(
                        name: name,
                        value: value,
                        linkText: linkText,
                        descriptionText: getThermostatDescriptionText(name, value, linkText),
                        handlerName: name,
                        isStateChange: isChange,
                        displayed: isDisplayed)
                        
                    sendEvent(name: "switch", value: value)
                }
                else if (name=="thermostatMode") {
                    isChange = true //isTemperatureStateChange(device, name, value.toString())
                    isDisplayed = false
                     
                    if (value=="cool") {
                        sendEvent(name: 'thermostatOperatingState', value: "cooling", 
                        isStateChange: isChange,
                        displayed: isDisplayed)
                    } else if (value=="heat") {
                        sendEvent(name: 'thermostatOperatingState', value: "heating", 
                        isStateChange: isChange,
                        displayed: isDisplayed)
                    } else if (value=="fan") {
                        sendEvent(name: 'thermostatOperatingState', value: "fan only", 
                        isStateChange: isChange,
                        displayed: isDisplayed)
                    } else if (value=="dry") {
                        sendEvent(name: 'thermostatOperatingState', value: "Dry", 
                        isStateChange: isChange,
                        displayed: isDisplayed)
                    } else {
                        sendEvent(name: 'thermostatOperatingState', value: "idle", 
                        isStateChange: isChange,
                        displayed: isDisplayed)
                    }      
                    sendEvent(
                        name: name,
                        value: value,
                        linkText: linkText,
                        descriptionText: getThermostatDescriptionText(name, value, linkText),
                        handlerName: name,
                        isStateChange: isChange,
                        displayed: isDisplayed)
                    }
                else if (name=="coolingSetpoint" || name== "heatingSetpoint" || name == "thermostatSetpoint") {               
                    isChange = true //isTemperatureStateChange(device, name, value.toString())
                    isDisplayed = false
                    
                    sendEvent(
                        name: name,
                        value: value,
                        //unit : device.currentValue("temperatureUnit"),
                        linkText: linkText,
                        descriptionText: getThermostatDescriptionText(name, value, linkText),
                        handlerName: name,
                        isStateChange: isChange,
                        displayed: isDisplayed)
                    }
                else if (name=="temperatureUnit") { 
                    isChange = true
                    isDisplayed = true
                       
                    sendEvent(
                        name: name,
                        value: value,
                        //unit: value,
                        linkText: linkText,
                        descriptionText: getThermostatDescriptionText(name, value, linkText),
                        handlerName: name,
                        isStateChange: isChange,
                        displayed: isDisplayed)
                }
                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())
                    isDisplayed = false
                       
                    sendEvent(
                        name: name,
                        value: value,
                        linkText: linkText,
                        descriptionText: getThermostatDescriptionText(name, value, linkText),
                        handlerName: name,
                        isStateChange: isChange,
                        displayed: isDisplayed)
                    }
                else if (name=="swing") {
                    
                    isChange = true //isTemperatureStateChange(device, name, value.toString())
                    isDisplayed = true
                       
                    sendEvent(
                        name: name,
                        value: value,
                        linkText: linkText,
                        descriptionText: getThermostatDescriptionText(name, value, linkText),
                        handlerName: name,
                        isStateChange: isChange,
                        displayed: isDisplayed)
                    }
                else if (name=="temperature" || name== "lastTemperaturePush" || name== "lastHumidityPush") {
                    isChange = true //isTemperatureStateChange(device, name, value.toString())
                    isDisplayed = isChange
                    
                    sendEvent(
                        name: name,
                        value: value,
                        //unit: device.currentValue("temperatureUnit"),
                        linkText: linkText,
                        descriptionText: getThermostatDescriptionText(name, value, linkText),
                        handlerName: name,
                        isStateChange: isChange,
                        displayed: isDisplayed)
                        
                }
                else if (name=="humidity") {
                    isChange = true //isTemperatureStateChange(device, name, value.toString())
                    isDisplayed = isChange
                    
                    sendEvent(
                        name: name,
                        value: value,
                        linkText: linkText,
                        descriptionText: getThermostatDescriptionText(name, value, linkText),
                        handlerName: name,
                        isStateChange: isChange,
                        displayed: isDisplayed)                    
                }
                else {
                    isChange = true//isStateChange(device, name, value.toString())
                    isDisplayed = false//isChange
                    
                    sendEvent(
                        name: name,
                        value: value.toString(),
                        linkText: linkText,
                        descriptionText: getThermostatDescriptionText(name, value, linkText),
                        handlerName: name,
                        isStateChange: isChange,
                        displayed: isDisplayed)
                        
                }
            }          

            generateStatusEvent ()
        }
    }

    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 == "powerSource")
        {
            return "power source mode 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) {
        log.debug "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:")) {
            log.debug "Switch command"
            name = "switch"
            value = description?.endsWith(" 1") ? "on" : "off"
        }
       else if (description?.startsWith("temperature")) {
            log.debug "Temperature"
            name = "temperature"
            value = device.currentValue("temperature")
        }
        else if (description?.startsWith("humidity")) {
            log.debug "Humidity"
            name = "humidity"
            value = device.currentValue("humidity")
        }
        else if (description?.startsWith("targetTemperature")) {
            log.debug "targetTemperature"
            name = "targetTemperature"
            value = device.currentValue("targetTemperature")
        }
        else if (description?.startsWith("fanLevel")) {
            log.debug "fanLevel"
            name = "fanLevel"
            value = device.currentValue("fanLevel")
        }
        else if (description?.startsWith("mode")) {
            log.debug "mode"
            name = "mode"
            value = device.currentValue("mode")
        }
        else if (description?.startsWith("on")) {
            log.debug "on"
            name = "on"
            value = device.currentValue("on")
        }
        else if (description?.startsWith("switch")) {
            log.debug "switch"
            name = "switch"
            value = device.currentValue("on")
        }
        else if (description?.startsWith("temperatureUnit")) {
            log.debug "temperatureUnit"
            name = "temperatureUnit"
            value = device.currentValue("temperatureUnit")
        }
        else if (description?.startsWith("Error")) {
            log.debug "Error"
            name = "Error"
            value = device.currentValue("Error")
        }
        else if (description?.startsWith("voltage")) {
            log.debug "voltage"
            name = "voltage"
            value = device.currentValue("voltage")
        }
        else if (description?.startsWith("battery")) {
            log.debug "battery"
            name = "battery"
            value = device.currentValue("battery")
        }
        else if (description?.startsWith("swing")) {
            log.debug "swing"
            name = "swing"
            value = device.currentValue("swing")
        }
        
        def result = createEvent(name: name, value: value)
        log.debug "Parse returned ${result?.descriptionText}"
        return result
    }

    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("mode")
        def on = device.currentValue("on")
        def swing = device.currentValue("swing")
        def error = device.currentValue("Error")
                        
        def statusTextmsg = ""
        def sUnit = device.currentValue("temperatureUnit")

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

Awesome thanks, I’ll check it out.

Has anyone gotten this working with Sensibo? I've added the app code and device code but it doesn't seem to work.

I tried it once a while ago and it didn't work. Haven't look at it since.

Mark, did you ever get this working? It doesn't seem to work with my Sensibo Sky and I haven't had time to dig deeper to see why.

I never tested it out extensively, since it wasn’t getting a lot cooler here by the time the code in post #4 became available.

But from some brief testing, it looked like my sensibo sky pod was turning on/off and setting the temp appropriately from the HE Virtual device that was created when I installed the app.

You installed the code I posted? Seems to still be working fine for me after installing the app and specifying the API key.

Sorry for the delay. Yes, I installed the device handler and the smartapp you posted in this thread. I then went to the Sensibo site to get my current API key and plugged that into the smartapp. Do I need to generate a NEW API key for use with Hubitat? I'm using the same API key my SmartThings integration is currently using.

If I go into the Sensibo device in Hubitat and click ON, for example, nothing happens on the Sensibo - no lights and no action on the AC. I'm sure I'm missing something obvious but I'm not sure what. If I look at live logging and click the ON button in the Sensibo device, I see:

2019-02-12 06:23:13.913 pm [error]java.lang.NullPointerException: Cannot get property 'value' on null object on line 649 (on)

It probably didn't get the initial values when you installed from the integration app. You might need to click refresh to see if it populates the values. The Device should have something like this listed to the side of it:
image

Yeah, it has these values:

Current States

  • battery : 100
  • coolingSetpoint : 64
  • fanLevel : medium
  • firmwareVersion : IN010054
  • heatingSetpoint : 64
  • humidity : 30
  • mode : cool
  • on : off
  • powerSource : mains
  • productModel : skyv2
  • statusText : 28.0%
  • switch : off
  • targetTemperature : 64
  • temperature : 67.6
  • temperatureUnit : F
  • thermostatFanMode : medium
  • thermostatMode : off
  • thermostatOperatingState : idle
  • thermostatSetpoint : 64
  • voltage : 3000
  • Error : Success
  • swing : null

The device logs during a refresh look like this:

dev:1952019-02-13 06:50:05.346 am debugrefresh ended
dev:1952019-02-13 06:50:05.170 am debugname :on value :off
dev:1952019-02-13 06:50:05.168 am debugname :firmwareVersion value :IN010054
dev:1952019-02-13 06:50:05.166 am debugname :thermostatMode value :off
dev:1952019-02-13 06:50:05.165 am debugname :thermostatFanMode value :medium
dev:1952019-02-13 06:50:05.163 am debugname :fanLevel value :medium
dev:1952019-02-13 06:50:05.161 am debugname :humidity value :30
dev:1952019-02-13 06:50:05.159 am debugname :temperature value :67.6
dev:1952019-02-13 06:50:05.158 am debugname :temperatureUnit value :F
dev:1952019-02-13 06:50:05.156 am debugname :coolingSetpoint value :64
dev:1952019-02-13 06:50:05.154 am debugname :targetTemperature value :64
dev:1952019-02-13 06:50:05.153 am debugname :heatingSetpoint value :64
dev:1952019-02-13 06:50:05.151 am debugname :swing value :null
dev:1952019-02-13 06:50:05.149 am debugname :mode value :cool
dev:1952019-02-13 06:50:05.147 am debugname :voltage value :3000
dev:1952019-02-13 06:50:05.146 am debugname :switch value :off
dev:1952019-02-13 06:50:05.144 am debugname :battery value :100
dev:1952019-02-13 06:50:05.142 am debugname :thermostatSetpoint value :64
dev:1952019-02-13 06:50:05.140 am debugname :Error value :Success
dev:1952019-02-13 06:50:05.139 am debugname :powerSource value :mains
dev:1952019-02-13 06:50:05.137 am debugname :productModel value :skyv2
dev:1952019-02-13 06:50:05.135 am debugparsing Event data [productModel:skyv2, powerSource:mains, Error:Success, thermostatSetpoint:64, battery:100, switch:off, voltage:3000, mode:cool, swing:null, heatingSetpoint:64, targetTemperature:64, coolingSetpoint:64, temperatureUnit:F, temperature:67.6, humidity:30, fanLevel:medium, thermostatFanMode:medium, thermostatMode:off, firmwareVersion:IN010054, on:off]
dev:1952019-02-13 06:50:05.125 am debugparsing data [productModel:skyv2, powerSource:mains, Error:Success, thermostatSetpoint:64, battery:100, switch:off, voltage:3000, mode:cool, swing:null, heatingSetpoint:64, targetTemperature:64, coolingSetpoint:64, temperatureUnit:F, temperature:67.6, humidity:30, fanLevel:medium, thermostatFanMode:medium, thermostatMode:off, firmwareVersion:IN010054, on:off]
dev:1952019-02-13 06:49:58.725 am debugExecuting 'poll' using parent SmartApp
dev:1952019-02-13 06:49:58.722 am debugrefresh called

The app log looks like this:

app:2602019-02-13 06:50:05.123 am debugGenerating AppDebug Event: [name:appdebug, descriptionText:Error in Poll Success, displayed:false]
app:2602019-02-13 06:50:05.121 am debugDEBUG - TDATA[data:[productModel:skyv2, powerSource:mains, Error:Success, thermostatSetpoint:64, battery:100, switch:off, voltage:3000, mode:cool, swing:null, heatingSetpoint:64, targetTemperature:64, coolingSetpoint:64, temperatureUnit:F, temperature:67.6, humidity:30, fanLevel:medium, thermostatFanMode:medium, thermostatMode:off, firmwareVersion:IN010054, on:off]]
app:2602019-02-13 06:50:05.116 am debugGenerating AppDebug Event: [name:appdebug, descriptionText:Current Time = 1550058605109, displayed:false]
app:2602019-02-13 06:50:05.114 am debugGenerating AppDebug Event: [name:appdebug, descriptionText:polled children and looking for ynUqts5J from [ynUqts5J:[data:[productModel:skyv2, powerSource:mains, Error:Success, thermostatSetpoint:64, battery:100, switch:off, voltage:3000, mode:cool, swing:null, heatingSetpoint:64, targetTemperature:64, coolingSetpoint:64, temperatureUnit:F, temperature:67.6, humidity:30, fanLevel:medium, thermostatFanMode:medium, thermostatMode:off, firmwareVersion:IN010054, on:off]]], displayed:false]
app:2602019-02-13 06:50:05.107 am debugpolled children and looking for ynUqts5J from [ynUqts5J:[data:[productModel:skyv2, powerSource:mains, Error:Success, thermostatSetpoint:64, battery:100, switch:off, voltage:3000, mode:cool, swing:null, heatingSetpoint:64, targetTemperature:64, coolingSetpoint:64, temperatureUnit:F, temperature:67.6, humidity:30, fanLevel:medium, thermostatFanMode:medium, thermostatMode:off, firmwareVersion:IN010054, on:off]]]
app:2602019-02-13 06:50:05.104 am debugGenerating AppDebug Event: [name:appdebug, descriptionText:updated [data:[productModel:skyv2, powerSource:mains, Error:Success, thermostatSetpoint:64, battery:100, switch:off, voltage:3000, mode:cool, swing:null, heatingSetpoint:64, targetTemperature:64, coolingSetpoint:64, temperatureUnit:F, temperature:67.6, humidity:30, fanLevel:medium, thermostatFanMode:medium, thermostatMode:off, firmwareVersion:IN010054, on:off]], displayed:false]
app:2602019-02-13 06:50:05.094 am debugupdated 1 stats: [data:[productModel:skyv2, powerSource:mains, Error:Success, thermostatSetpoint:64, battery:100, switch:off, voltage:3000, mode:cool, swing:null, heatingSetpoint:64, targetTemperature:64, coolingSetpoint:64, temperatureUnit:F, temperature:67.6, humidity:30, fanLevel:medium, thermostatFanMode:medium, thermostatMode:off, firmwareVersion:IN010054, on:off]]
app:2602019-02-13 06:50:05.087 am debugGenerating AppDebug Event: [name:appdebug, descriptionText:Event Data = [temperature:67.6, humidity:30, targetTemperature:64, fanLevel:medium, mode:cool, on:off, switch:off, thermostatMode:off, thermostatFanMode:medium, coolingSetpoint:64, heatingSetpoint:64, thermostatSetpoint:64, temperatureUnit:F, voltage:3000, swing:null, battery:100, powerSource:mains, productModel:skyv2, firmwareVersion:IN010054, Error:Success], displayed:false]
app:2602019-02-13 06:50:05.085 am debugupdating dni ynUqts5J
app:2602019-02-13 06:50:05.083 am debugOn: off targetTemp: 64 fanLevel: medium Thermostat mode: cool swing: null
app:2602019-02-13 06:50:05.082 am debugswing Mode :null
app:2602019-02-13 06:50:05.080 am debugproduct Model : skyv2
app:2602019-02-13 06:50:05.078 am debugPodUID : ynUqts5J : [on:false, targetTemperature:64, temperatureUnit:F, mode:cool, fanLevel:medium]
app:2602019-02-13 06:50:05.076 am debugget ACState Success
app:2602019-02-13 06:50:05.075 am debugGenerating AppDebug Event: [name:appdebug, descriptionText:Response Status = 200, displayed:false]
app:2602019-02-13 06:50:05.072 am debugGenerating AppDebug Event: [name:appdebug, descriptionText:Response from Sensibo GET = [status:success, moreResults:true, result:[[status:Success, acState:[on:false, targetTemperature:64, temperatureUnit:F, mode:cool, fanLevel:medium], device:[configGroup:stable, cleanFiltersNotificationEnabled:false, room:[name:Office AC, icon:Kidsroom], firmwareType:cc3100_stm32f0, productModel:skyv2, sensorsCalibration:[temperature:0.0, humidity:0.0], temperatureUnit:F, isGeofenceOnExitEnabled:false, connectionStatus:[isAlive:true, lastSeen:[secondsAgo:79, time:2019-02-13T11:48:44.546000Z]], id:ynUqts5J, acState:[on:false, targetTemperature:64, temperatureUnit:F, mode:cool, fanLevel:medium], isBatteryLow:null, shouldShowFilterCleaningNotification:false, location:[latLon:[39.671316, -86.3807216], updateTime:null, country:United States, createTime:[secondsAgo:16464300, time:2018-08-06T22:25:04Z], address:[6, 2, 5], id:L8KNBxCTS5], currentlyAvailableFirmwareVersion:IN010054, isClimateReactGeofenceOnExitEnabled:false, remoteCapabilities:[modes:[fan:[temperatures:[C:[isNative:false, values:[16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32]], F:[isNative:true, values:[60, 61, 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, 87, 88, 89, 90]]], fanLevels:[low, medium, high]], cool:[temperatures:[C:[isNative:false, values:[16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32]], F:[isNative:true, values:[60, 61, 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, 87, 88, 89, 90]]], fanLevels:[low, medium, high]]]], serial:11180219, firmwareVersion:IN010054, measurements:[batteryVoltage:null, temperature:19.8, humidity:30.4, time:[secondsAgo:79, time:2019-02-13T11:48:44.546000Z], rssi:-24, piezo:[null, null]]]]]], displayed:false]
app:2602019-02-13 06:50:04.413 am debugDEBUG DATA RESULT[[batteryVoltage:null, time:[secondsAgo:79, time:2019-02-13T11:48:44.546000Z], temperature:19.8, humidity:30.4]]
app:2602019-02-13 06:50:04.408 am debugpoll results returned
app:2602019-02-13 06:50:04.402 am debugGenerating AppDebug Event: [name:appdebug, descriptionText:Response Status = 200, displayed:false]
app:2602019-02-13 06:50:04.400 am debugGenerating AppDebug Event: [name:appdebug, descriptionText:Response from Sensibo GET = [status:success, result:[[batteryVoltage:null, time:[secondsAgo:79, time:2019-02-13T11:48:44.546000Z], temperature:19.8, humidity:30.4]]], displayed:false]
app:2602019-02-13 06:49:58.783 am debugGenerating AppDebug Event: [name:appdebug, descriptionText:Before HTTPGET to Sensibo., displayed:false]
app:2602019-02-13 06:49:58.781 am debugpolling children: ynUqts5J
app:2602019-02-13 06:49:58.780 am debugpolling children
app:2602019-02-13 06:49:58.778 am debugGenerating AppDebug Event: [name:appdebug, descriptionText:polling children because 1550058598741 > 1550058494575, displayed:false]
app:2602019-02-13 06:49:58.776 am debugpolling children because 1550058598741 > 1550058494575
app:2602019-02-13 06:49:58.773 am debugGenerating AppDebug Event: [name:appdebug, descriptionText:pollChild( ynUqts5J ): 1550058598741 > 1550058494575 ?? w/ current state: [ynUqts5J:[data:[productModel:skyv2, powerSource:mains, Error:Success, thermostatSetpoint:64, battery:100, switch:off, voltage:3000, mode:cool, swing:null, heatingSetpoint:64, targetTemperature:64, coolingSetpoint:64, temperatureUnit:F, temperature:67.6, humidity:30, fanLevel:medium, thermostatFanMode:medium, thermostatMode:off, firmwareVersion:IN010054, on:off]]], displayed:false]
app:2602019-02-13 06:49:58.767 am debugpollChild( ynUqts5J ): 1550058598741 > 1550058494575 ?? w/ current state: [ynUqts5J:[data:[productModel:skyv2, powerSource:mains, Error:Success, thermostatSetpoint:64, battery:100, switch:off, voltage:3000, mode:cool, swing:null, heatingSetpoint:64, targetTemperature:64, coolingSetpoint:64, temperatureUnit:F, temperature:67.6, humidity:30, fanLevel:medium, thermostatFanMode:medium, thermostatMode:off, firmwareVersion:IN010054, on:off]]]
app:2602019-02-13 06:49:58.751 am debugGenerating AppDebug Event: [name:appdebug, descriptionText:Last Poll Millis = 1550058449575, displayed:false]
app:2602019-02-13 06:49:58.744 am debugGenerating AppDebug Event: [name:appdebug, descriptionText:poll child, displayed:false]
app:2602019-02-13 06:49:58.740 am debugpoll child

So unless I missed something in the logs, it does appear to be talking to the Sensibo, but commands don't seem to trigger. Not a huge deal, as I do have it working in ST, but it is weird.

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

That's the line of code that's breaking for you -- looks like your device doesn't support "swing" for some reason. The right way is to handle Sensibo not returning that data, but you could probably just hardcode "stopped" into line 665 of the SmartApp, or it could be the firmware version of your device? See if it can be updated.

Holy Moly, i got it working fine. Really wasn't expecting it to be that easy. I just removed the 4 import statements at the top, added an API key, and it works beautifully. Wow noice!!!

In case it helps anyone in the future, I found an issue if a Sensibo unit powers down (happens time by time for me, especially as I'm using them with local mini USB power supplies since I moved country and the supplied power units don't fit). It gets detached from the device in Hubitat. Go into the device on Hubitat and press the "refresh" box. It then seems to reconnect and everything works OK again. Test it by pressing the on and off boxes in the device panel.

Hi there @Angus_M. Are you or anyone else able to let me know which line of code I insert my API code into, and if that is for the Driver, the App, or both?

Also curious as to why you removed the four import statements?

Thanks - I'm a bit of a noob, and haven't touched code since the late 90's at Uni!

Ha ha ha, for sure I'm a bigger noob than you are on all this :slight_smile: I'm a complete novice :slight_smile:

So, yeah, what I did was take the text in one of the posts from @blink above for the app and cut/paste it into a new app (option is "apps code" on left of the main menu of the system). Then the same for the driver code in the other post ("drivers code" is the option). Put that into a new driver file. Save each one of course. But for the apps code, edit the 4 lines out of the app file as per the post above (they refer to "import" statements, which are not needed for the HE installation). Now run the app, and it will ask for the API code. You get this from your account on Sensibo.com (login is almost completely hidden right down the bottom of their page). Then use "menu" "create API key". Key it in to the HE app when asked and presto it started working.

The only isues I've found so far are (1) if the Sensibo powers down it will allocate a new IP address and not automatically reconnect. So maybe reserve fixed IPs for each Sensibo device. Or hit refresh in the device page also fixed it. Since I'm a complete noob I have no idea how to make it more robust than this. I did see in other posts on other subjects that it seems possible but I have no idea how to edit the code to achieve this.
(2) It seems as slow as IFTTT. That makes sense because the code accessed the devices via the Sensibo Cloud (hence the need for the API key). I wish it operated locally. Again, its likely possible but I have no idea how to code that either lol. So I see I have all the ideas but no ability :smiley:

Good luck! Hope you get it running and we can find a way to get it improved over time by users a lot smarter than us!!!

2 Likes

Thanks for the response - a huge help!

One note for those who come later - The 4 lines of code @Angus_M is referring to are at the top of the Driver code, not the App code.

oh sorry, yes my mistake.

1 Like