Anybody using a Sensibo?

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

Hi there @blink, unfortunately I am getting the same error message as @blanghor:

dev:142019-04-26 12:42:38.815 errorjava.lang.NullPointerException: Cannot get property 'value' on null object on line 649 (on)

My reverse cycle unit does support swing settings (toggle button on the remote), but the Sensibo itself does not seem to - no options for such control in the App or the web interface.

I have looked into your firmware suggestion, and there doesn't appear to be a method for updating the Sensibo Firmware that I can see in their knowledge base or the App itself.

I have also tried hardcoding "stopped" into line 665 of your App code, although I'm not sure I have done this correctly. Currently I have substituted

tData.data.swing = swingM

for

tData.data.swing = stopped

Is this correct? Apologies but I'm not much of a coder. Keen to learn more though!

Any other potential solutions if this code adjustment has been done correctly?

I was never able to get it working even with hardcoding swing to stop. I had contacted Sensibo about a firmware upgrade and they told me mine was up to date.

My Sensibo works fine in SmartThings with the integration, which this code is based on. I believe in the ST logs I also see SwingM being null, but the integration works properly.

what happens if you remove the line completely?

You need to specify stopped a string, so probably:

tData.data.swing = "stopped"

I didn't write the code for this app/device handler - It's just a port of EricG's implementation for the ST Hub. Mostly just removing some imports and statements, or defining variables that Hubitat needs to function in a similar manner. I noticed some small issues with the code I previously pasted, and EricG has posted an update for the app and DTH. Here's an updated version that I'm using in Hubitat. I've added in a statement to try to catch the null swing modes. Not sure if that solves your problem, but it's worth a try.

Device Handler

/**
 *  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.
 *
 */

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 "Climate","String"
		attribute "targetTemperature","Double"
        attribute "statusText","String"
        attribute "currentmode","String"
        attribute "fanLevel","String"
        
        command "setAll"
        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 "modeDry"
        command "modeFan"
        command "modeAuto"
        command "lowfan"
        command "mediumfan"
        command "highfan"
        command "quietfan"
        command "strongfan"
        command "autofan"
        command "fullswing"
        command "setAirConditionerMode"
        command "toggleClimateReact"
        command "setClimateReact"
        command "configureClimateReact"
	}

	simulator {

	}

	tiles(scale: 2) {
    	multiAttributeTile(name:"thermostatMulti", type:"thermostat",, width:6, height:4,canChangeIcon: false) {
   			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: 1, height: 1) {
			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("currentmode", "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("Climate", "device.Climate", width: 2, height: 2) {
			state "on", label:'${name}', action:"toggleClimateReact", icon:"https://i.ibb.co/Z8ZzcHR/auto-fix-2.png", backgroundColor:"#00a0dc"
			state "off", label:'${name}', action:"toggleClimateReact", icon:"https://i.ibb.co/Z8ZzcHR/auto-fix-2.png", backgroundColor:"#ffffff"
            state "notdefined", label:'N/A', action:"toggleClimateReact", icon:"https://i.ibb.co/Z8ZzcHR/auto-fix-2.png", backgroundColor:"#e86d13"
		}
        
        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("drymode", "device.thermostatMode",  width: 1, height: 1) {
       		state "dry", action:"modeDry", backgroundColor:"#e8e3d8", icon:"https://image.ibb.co/k2ZNpk/dry_mode.png"
        }
        standardTile("fanmode", "device.thermostatMode",  width: 1, height: 1) {
       		state "fan", action:"modeFan", backgroundColor:"#e8e3d8", icon:"https://image.ibb.co/n1dhpk/status_message_fan.png"
        }        
        standardTile("highfan", "device.fanLevel",  width: 1, height: 1) {
       		state "high", action:"highfan", 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","currentmode","swing","Climate","refresh","coolmode","heatmode","fanmode","drymode","highfan","autofan","fullswing","firmwareVersion","productModel","powerSource","voltage"])    
	}
}

def setAll(newMode,temp,fan)
{
	log.trace "setAll() called with " + newMode + "," + temp + "," + fan
    
    def Setpoint = temp.toInteger()
    
    def LevelBefore = fan
    def capabilities = parent.getCapabilities(device.deviceNetworkId,newMode)
    def Level = LevelBefore
    if (capabilities.remoteCapabilities != null) {
    	def fanLevels = capabilities.remoteCapabilities.fanLevels
        
    	log.debug "Fan levels capabilities : " + 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)
            
            generateStatusEvent()
            refresh()
        }
        else {
            generateErrorEvent()
            
            generateStatusEvent()
        }              
	}
    else {       
    }
}

def lowfan()
{
	log.trace "lowfan() called"
	dfanLevel("low")
}

def mediumfan()
{
	log.trace "mediumfan() called"
	dfanLevel("medium")
}

def highfan()
{
	log.trace "highfan() called"
	dfanLevel("high")
}

def quietfan()
{
	log.trace "quietfan() called"
	dfanLevel("quiet")
}

def strongfan()
{
	log.trace "strongfan() called"
	dfanLevel("strong")
}

def autofan()
{
	log.trace "autofan() called"
	dfanLevel("auto")
}

def fullswing()
{	
	log.trace "fullswing() called"
	modeSwing("rangeFull")
}

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

    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() {
	log.trace "raiseTemperature() called"	

	def operMode = device.currentState("currentmode").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() {
	log.trace "lowerTemperature() called"
    
	def operMode = device.currentState("currentmode").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.trace "lowerCoolSetpoint() called"
    
    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("currentmode").value, Setpoint, device.currentState("fanLevel").value, device.currentState("swing").value, device.currentState("temperatureUnit").value)

    if (result) {
    	log.info "Cooling temperature changed to " + Setpoint + " for " + device.deviceNetworkId
        
        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}"
        
        generateStatusEvent()
    	refresh()
    }
	else {
    	log.debug "error"
       	generateErrorEvent()
        
        generateStatusEvent()
    }	
}


void setThermostatMode(modes)
{ 
	log.trace "setThermostatMode() called"
    
  	def currentMode = device.currentState("currentmode").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 setAirConditionerMode(modes)
{ 
	log.trace "setAirConditionerMode() called"
    
  	def currentMode = device.currentState("currentmode").value
  
  	log.debug "switching AC mode from current mode: $currentMode"

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

void raiseCoolSetpoint() {
   	log.trace "raiseCoolSetpoint() called"
    
    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("currentmode").value, Setpoint, device.currentState("fanLevel").value, device.currentState("swing").value, device.currentState("temperatureUnit").value)
    if (result) {
    	log.info "Cooling temperature changed to " + Setpoint + " for " + device.deviceNetworkId
        
        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}"
        
        generateStatusEvent()
    	refresh()
    }
	else {
       	generateErrorEvent()
        
        generateStatusEvent()
    }	
}

void raiseHeatSetpoint() {
	log.trace "raiseHeatSetpoint() called"
    
    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("currentmode").value, Setpoint, device.currentState("fanLevel").value, device.currentState("swing").value, device.currentState("temperatureUnit").value)
    if (result) {
    	log.info "Heating temperature changed to " + Setpoint + " for " + device.deviceNetworkId
        
        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}"
        
        generateStatusEvent()
    	refresh()
    }
	else {
       	generateErrorEvent()
        
        generateStatusEvent()
    }
	
}

void lowerHeatSetpoint() {
	log.trace "lowerHeatSetpoint() called"
    
    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("currentmode").value, Setpoint, device.currentState("fanLevel").value, device.currentState("swing").value, device.currentState("temperatureUnit").value)
    if (result) {
    	log.info "Heating temperature changed to " + Setpoint + " for " + device.deviceNetworkId
        
        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}"
        
        generateStatusEvent()
    	refresh()
    }
	else {
       	generateErrorEvent()
        
        generateStatusEvent()
    }	
}

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

// Set Temperature
def setFanSetpoint(temp) {
	log.trace "setFanSetpoint() called"
    
	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) {
    	log.info "Fan temperature changed to " + temp + " for " + device.deviceNetworkId
        
    	if (device.currentState("on").value == "off") { generateSwitchEvent("on") }
  		generateModeEvent("fan")
         
        sendEvent(name: 'thermostatSetpoint', value: temp, displayed: false)
    	generateSetTempEvent(temp)
        
        generateStatusEvent()
    	refresh()
    }
    else {
       	generateErrorEvent()
        
        generateStatusEvent()
    }
}

// Set Temperature
def setDrySetpoint(temp) {
	log.trace "setDrySetpoint() called"
    
	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) {
    	log.info "Dry temperature changed to " + temp + " for " + device.deviceNetworkId
        
    	if (device.currentState("on").value == "off") { generateSwitchEvent("on") }
  		generateModeEvent("dry")
         
        sendEvent(name: 'thermostatSetpoint', value: temp, displayed: false)
    	generateSetTempEvent(temp)
        
        generateStatusEvent()
    	refresh()
    }
    else {
       	generateErrorEvent()
        
        generateStatusEvent()
    }   
}


// Set Temperature
def setCoolingSetpoint(temp) {
	log.trace "setCoolingSetpoint() called"

	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) {
    	log.info "Cooling temperature changed to " + temp + " for " + device.deviceNetworkId
        
    	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)
        
        generateStatusEvent()
    	refresh()
    }
    else {
       	generateErrorEvent()
        
        generateStatusEvent()
    }
}

def setHeatingSetpoint(temp) {
	log.trace "setHeatingSetpoint() called"

	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) {
    	log.info "Heating temperature changed to " + temp + " for " + device.deviceNetworkId
        
        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)
        
        generateStatusEvent()
    	refresh()
	}	
    else {
       	generateErrorEvent()
        
        generateStatusEvent()
    }    
}

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.trace "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("currentmode").value, Setpoint, device.currentState("fanLevel").value, device.currentState("swing").value, device.currentState("temperatureUnit").value)
    log.debug "Result : " + result
    if (result) {
    	log.info "AC turned ON for " + device.deviceNetworkId
    	sendEvent(name: 'thermostatMode', value: device.currentState("currentmode").value, displayed: false,isStateChange: true)
        //sendEvent(name: 'thermostatOperatingState', value: "idle",isStateChange: true)
    	
        generateSwitchEvent("on")
        
        generateStatusEvent() 
    	refresh()
    }
    else {
       	generateErrorEvent()
        
        generateStatusEvent() 
    }        
}

def off() {
	log.trace "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("currentmode").value, Setpoint, device.currentState("fanLevel").value, device.currentState("swing").value, device.currentState("temperatureUnit").value)

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

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.trace "dfanLevel called with fan = " + newLevel
    
    def Setpoint = device.currentValue("targetTemperature").toInteger()
    
    def capabilities = parent.getCapabilities(device.deviceNetworkId, device.currentState("currentmode").value)      
    def Level = LevelBefore
    if (capabilities.remoteCapabilities != null) {
    	def fanLevels = capabilities.remoteCapabilities.fanLevels
        
    	log.debug "Fan levels capabilities : " + capabilities.remoteCapabilities.fanLevels
        
        Level = GetNextFanLevel(newLevel,capabilities.remoteCapabilities.fanLevels)
        //log.debug "Fan : " + Level
        
        def result = parent.setACStates(this, device.deviceNetworkId,"on", device.currentState("currentmode").value, Setpoint, Level, device.currentState("swing").value, device.currentState("temperatureUnit").value)

        if (result) {
        	log.info "Fan level changed to " + Level + " for " + device.deviceNetworkId
            
            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)
            
            generateStatusEvent()
        	refresh()
        }
        else {
            generateErrorEvent()
            
            generateStatusEvent()
        }             
	}
    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)
}

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

// Set Climate React
def setClimateReact(ClimateState) {

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

def configureClimateReact(lowThres, highThres,stype,lowState,highState, on_off, ThresUnit)
{
    ///////////////////////////////////////////////
    // lowThres and highThres - Integer parameters
	// stype : possible values are "temperature", "humidity" or "feelsLike"
    // lowState and highState : 
    //    on, fanLevel, temperatureUnit, targetTemperature, mode      
    //
    //    like  "[true,'auto','C',21,'heat']"
    //    to turn off AC,first parameters = false : "[false,'auto','C',21,'heat']"
    // one_off : boolean value to enable/disable the Climate React
    // unit : Passing F for Farenheit or C for Celcius
    // 
    // Some examples: 
    //  
    // Range 19-24 Celcius, start to heat to 22 at auto fan if the temp is lower than 19 and stop the AC when higher than 24
    // configureClimateReact(19, 24, ‘temperature’, ‘[true, ‘auto’, ‘C’, 22, ‘heat’]’, ‘[false, ‘auto’, ‘C’, 22, ‘heat’]’, true, ‘C’);
    //
    // Range 67-68 Farenheit, start to heat to 68 at auto fan if the temp is lower than 67 and stop the AC when higher than 68
    // configureClimateReact(67, 68, ‘temperature’, ‘[true, ‘auto’, ‘F’, 68, ‘heat’]’, ‘[false, ‘auto’, ‘F’, 68, ‘heat’]’, true, ‘F’);
    //
    ///////////////////////////////////////////////
    
	log.trace "configureClimateReact() called"
    
    
    if (ThresUnit.toUpperCase() == "F")
    {
    	lowThres = fToc(lowThres).round(1)
    	highThres = fToc(highThres).round(1)
    }
    
    def json = new groovy.json.JsonBuilder()
    
    def lowStateMap = evaluate(lowState)
    def highStateMap = evaluate(highState)
        
    def lowStateJson
    def highStateJson
    
    if (lowStateMap) {
        lowStateJson = json {
            on lowStateMap[0]
            fanLevel lowStateMap[1]
            temperatureUnit lowStateMap[2]
            targetTemperature lowStateMap[3]
            mode lowStateMap[4]
        }
    }
    else { lowStateJson = null }
    
    if (highStateMap) {
        highStateJson = json {
            on highStateMap[0]
            fanLevel highStateMap[1]
            temperatureUnit highStateMap[2]
            targetTemperature highStateMap[3]
            mode highStateMap[4]
        }
    }
    else { highStateJson = null }
    
    def root = json {
    	deviceUid device.deviceNetworkId
        highTemperatureWebhook null
        highTemperatureThreshold highThres        
        lowTemperatureWebhook null
        type stype        
        lowTemperatureState lowStateJson
        enabled on_off
        highTemperatureState highStateJson
        lowTemperatureThreshold lowThres             
    }
    
    log.debug "CLIMATE REACT STRING : " + JsonOutput.prettyPrint(json.toString())
    def result = parent.configureClimateReact(this, device.deviceNetworkId, json.toString())
    
    if (result) {
    	log.info "Climate React settings changed for " + device.deviceNetworkId
              
        sendEvent(name: 'Climate', value: on_off, displayed: false)
        
        generateStatusEvent()
    	refresh()
    }
    else {
       	generateErrorEvent()
        
        generateStatusEvent()
    }
}

def switchFanLevel() {
	log.trace "switchFanLevel() called"
    
	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()
{
	log.trace "modeHeat() called"
	modeMode("heat")
}

def modeCool()
{
	log.trace "modeCool() called"
	modeMode("cool")
}

def modeDry()
{
	log.trace "modeDry() called"
	modeMode("dry")
}

def modeFan()
{
	log.trace "modeFan() called"
	modeMode("fan")
}

def modeAuto()
{
	log.trace "modeAuto() called"
	modeMode("auto")
}

// To change the AC mode

def switchMode() {
	log.trace "switchMode() called"
    
	def currentMode = device.currentState("currentmode")?.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.trace "modeMode() called with " + newMode
    
    def Setpoint = device.currentValue("targetTemperature").toInteger()
    
    def LevelBefore = device.currentState("fanLevel").value
    def capabilities = parent.getCapabilities(device.deviceNetworkId,newMode)
    def Level = LevelBefore
    if (capabilities.remoteCapabilities != null) {
    	def fanLevels = capabilities.remoteCapabilities.fanLevels
    	
        log.debug "Fan levels capabilities : " + 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) {
        	log.info "Mode changed to " + newMode + " for " + device.deviceNetworkId
            
        	if (LevelBefore != Level) {
                generatefanLevelEvent(Level)
            }
            sendEvent(name: 'thermostatMode', value: newMode, displayed: false,isStateChange: true)
            if (device.currentState("on").value == "off") { generateSwitchEvent("on") }

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

	   NextMode(sMode)
    }
}

def generateModeEvent(mode) {
   sendEvent(name: "thermostatMode", 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)
{
	log.trace "GetNextFanLevel called with " + fanLevel
    
    if (fanLevels == null || fanLevels == "null") {
      return null
    }
    
	def listFanLevel = ['low','medium','high','auto','quiet','medium_high','medium_low','strong']	
    def newFanLevel = returnNext(listFanLevel, fanLevels,fanLevel)
    
    log.debug "Next fanLevel = " + newFanLevel
	
    return newFanLevel
}

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

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

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

def switchSwing() {
	log.trace "switchSwing() called"
    
	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.trace "modeSwing() called with " + newSwing
    
    def Setpoint = device.currentValue("targetTemperature").toInteger()
   
    def SwingBefore = device.currentState("swing").value
    def capabilities = parent.getCapabilities(device.deviceNetworkId, device.currentState("currentmode").value)
    def Swing = SwingBefore
    if (capabilities.remoteCapabilities != null) {
    	def Swings = capabilities.remoteCapabilities.swing

        log.debug "Swing capabilities : " + capabilities.remoteCapabilities.swing

        Swing = GetNextSwingMode(newSwing,capabilities.remoteCapabilities.swing)
        //log.debug "Swing : " + Swing
        
        def result = parent.setACStates(this, device.deviceNetworkId, "on", device.currentState("currentmode").value, Setpoint, device.currentState("fanLevel").value, Swing, device.currentState("temperatureUnit").value)
        if (result) {
        	log.info "Swing mode changed to " + Swing + " for " + device.deviceNetworkId
            
            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)
            
            generateStatusEvent()
        	refresh()
        }
        else {
            generateErrorEvent()
            
            generateStatusEvent()
        }              
	}
    else
    {
      //TODO
    }
}

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

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


void poll() {
	log.trace "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 = false
                  
				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 = false
                 
				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 = false
                  
				sendEvent(
					name: name,
					value: value,
					linkText: linkText,
					descriptionText: getThermostatDescriptionText(name, value, linkText),
					handlerName: name,
					isStateChange: isChange,
					displayed: isDisplayed)
               }
            else if (name== "Climate") {            	                
                isChange = true
                isDisplayed = false
                  
				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: 'airConditionerMode', value: "cool", 
					isStateChange: isChange,
					displayed: isDisplayed)
					
                    sendEvent(name: 'thermostatOperatingState', value: "cooling", 
					isStateChange: isChange,
					displayed: isDisplayed)
				} else if (value=="heat") {
					sendEvent(name: 'airConditionerMode', value: "heat", 
					isStateChange: isChange,
					displayed: isDisplayed)
                    
                    sendEvent(name: 'thermostatOperatingState', value: "heating", 
					isStateChange: isChange,
					displayed: isDisplayed)
				} else if (value=="fan") {
             		sendEvent(name: 'airConditionerMode', value: "fanOnly", 
					isStateChange: isChange,
					displayed: isDisplayed)
                
					sendEvent(name: 'thermostatOperatingState', value: "fan only", 
					isStateChange: isChange,
					displayed: isDisplayed)
                } else if (value=="dry") {
                    sendEvent(name: 'airConditionerMode', value: "dry", 
					isStateChange: isChange,
					displayed: isDisplayed)
                    
					sendEvent(name: 'thermostatOperatingState', value: "dry", 
					isStateChange: isChange,
					displayed: isDisplayed)
                 } else if (value=="auto") {
                    sendEvent(name: 'airConditionerMode', value: "auto", 
					isStateChange: isChange,
					displayed: isDisplayed)
                    
					sendEvent(name: 'thermostatOperatingState', value: "auto", 
					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 = false
                   
				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 = false
                   
				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 = 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=="humidity") {
				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 {
            	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 == "currentmode")
    {
        return "thermostat mode was ${value}"
    }
    else if (name == "powerSource")
    {
        return "power source mode was ${value}"
    }
    else if (name == "Climate")
    {
        return "Climate React was ${value}"
    }
    else if (name == "thermostatMode")
    {
        return "thermostat mode was ${value}"
    }
    else if (name == "temperatureUnit")
    {
    	return "thermostat unit was ${value}"
    }
    else if (name == "voltage")
    {
    	return "Battery voltage was ${value}"
    }
    else if (name == "battery")
    {
    	return "Battery was ${value}"
    }
    
    else if (name == "voltage" || name== "battery")
    {
    	return "Battery voltage was ${value}"
    }
    else if (name == "swing")
    {
    	return "Swing mode was ${value}"
    }
    else if (name == "Error")
    {
    	def str = (value == "Failed") ? "failed" : "success"
        return "Last setACState was ${str}"
    }
    else
    {
        return "${name} = ${value}"
    }
}

// parse events into attributes
def parse(String description) {
	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("currentmode")) {
    	log.debug "mode"
        name = "currentmode"
		value = device.currentValue("currentmode")
    }
    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("currentmode")
    def on = device.currentValue("on")
    def swing = device.currentValue("swing")
    def ClimateReact = device.currentValue("Climate")
    
	def error = device.currentValue("Error")
                    
    def statusTextmsg = ""
    def sUnit = device.currentValue("temperatureUnit")

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

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

def ping(){
	log.trace "calling parent ping()"
	return parent.ping()
}

Application

/**
 *  Sensibo Integration for Hubitat
 *
 *  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 Integration",
    namespace: "EricG66",
    author: "Eric Gosselin",
    description: "Connect your Sensibo Pod to Hubitat.",
    category: "Green Living",
    iconUrl: "https://image.ibb.co/j7qAPT/Sensibo_1x.png",
    iconX2Url: "https://image.ibb.co/coZtdo/Sensibo_2x.png",
    iconX3Url: "https://image.ibb.co/cBwTB8/Sensibo_3x.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 version() { "SmartThingsv1.5" }

public static String version() { return "SmartThingsv1.6" }

def setAPIKey()
{
	log.trace "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.trace "SensiboPodList()"

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

	 	// 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.trace "getSensiboPodList called"
       
    def deviceListParams = [
    uri: "${getServerUrl()}",
    path: "/api/v2/users/me/pods",
    requestContentType: "application/json",
    query: [apiKey:"${getapikey()}", integration:"${version()}", type:"json",fields:"id,room" ]]

	def pods = [:]
	
    try {
      httpGet(deviceListParams) { resp ->
    	if(resp.status == 200)
			{
				resp.data.result.each { pod ->
                    def key = pod.id
                    def value = pod.room.name
                        
					pods[key] = value
				}
                state.pods = pods
			}
	  }
    }
    catch(Exception e)
	{
		log.debug "Exception Get Json: " + e
		debugEvent ("Exception get JSON: " + e)
	}
    
    log.debug "Sensibo Pods: $pods"  
	
    return pods
}

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

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

	if (boolnotifevery) {
    	//runEvery1Hour("hournotification")
        schedule("0 0 * * * ?", "hournotification")
	}
    
    log.debug "Configured health checkInterval when installed()"
	sendEvent(name: "checkInterval", value: 2 * 60 * 60 + 2 * 60, displayed: true)
    
    //subscribe(d,"temperatureUnit",eTempUnitHandler)
    
    if (sendPushNotif) { 
    	subscribe(d, "temperature", eTemperatureHandler)
        subscribe(d, "humidity", eHumidityHandler)
    }
}

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

	unschedule()
    unsubscribe()
	
    state.lastTemperaturePush = null
    state.lastHumidityPush = null
    
    initialize()
    
    def d = getChildDevices()
    
    if (boolnotifevery) {
    	//runEvery1Hour("hournotification")
        schedule("0 0 * * * ?", "hournotification")
	}
    
    log.debug "Configured health checkInterval when installed()"
	sendEvent(name: "checkInterval", value: 2 * 60 * 60 + 2 * 60, displayed: true)
    
    //subscribe(d,"temperatureUnit",eTempUnitHandler)
    
    if (sendPushNotif) {
    	subscribe(d, "temperature", eTemperatureHandler)
        subscribe(d, "humidity", eHumidityHandler)
    }
}

def ping() {

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

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

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

    	if (curHour >= minHourstr && curHour <= maxHourstr) 
    	{
    		def devices = getChildDevices()
            devices.each { d ->
                log.trace "Notification every hour for device: ${d.id}"
                def currentPod = d.displayName
                def currentTemperature = d.currentState("temperature").value
                def currentHumidity = d.currentState("humidity").value
                def currentBattery = d.currentState("voltage").value
                def sunit = d.currentState("temperatureUnit").value
                stext = "${currentPod} - Temperature: ${currentTemperature} ${sunit} Humidity: ${currentHumidity}% Battery: ${currentBattery}"    
                
                sendPush(stext)
            }
    	}
    }
    else {
    	 	def devices = getChildDevices()
            devices.each { d ->
                log.trace "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.trace "refresh() called with rate of " + refreshinminutes + " minutes"

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


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

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

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

def initialize() {
    log.trace "initialize() called"
    log.trace "key "+ getapikey()
    
    state.apikey = getapikey()
	  
	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/jgAMW8/sensibo-sky-off.png")
                d.setIcon("off","on","https://image.ibb.co/jgAMW8/sensibo-sky-off.png")
                d.save()              
                
				log.trace "created ${d.displayName} with id $dni"
			}
			else
			{
				log.trace "found ${d.displayName} with id $dni already exists"
			}

			return d
		}

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

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

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

	def PodList = getChildDevices()
	
    pollHandler()
    
    refreshDevices()
    
    if (refreshinminutes == "1") 
    	runEvery1Minute("refreshDevices")
    else if (refreshinminutes == "5")
    	runEvery5Minutes("refreshDevices")
    else if (refreshinminutes == "10")
    	runEvery10Minutes("refreshDevices")
    else if (refreshinminutes == "15") 
    	runEvery15Minutes("refreshDevices")
    else if (refreshinminutes == "30")
    	runEvery30Minutes("refreshDevices")
    else
    	runEvery10Minutes("refreshDevices")
}


// Subscribe functions

def OnOffHandler(evt) {
	log.trace "on off handler activated"
    debugEvent(evt.value)
    
	//def name = evt.device.displayName

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

def getPollRateMillis() { return 45 * 1000 }
def getCapabilitiesRateMillis() {return 60 * 1000 }

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

	debugEvent ("Last Poll Millis = ${state.lastPollMillis}")
	def last = state.lastPollMillis ?: 0
	def next = last + pollRateMillis
	
	log.debug "pollChild( ${child.device.deviceNetworkId} ): $now > $next ?? w/ current state: ${state.sensibo}"
	debugEvent ("pollChild( ${child.device.deviceNetworkId} ): $now > $next ?? w/ current state: ${state.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 ${state.sensibo}"
		debugEvent ("polled children and looking for ${child.device.deviceNetworkId} from ${state.sensibo}")

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

		def tData = state.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(state.sensibo[child.device.deviceNetworkId] != null)
	{
		log.debug "not polling children, found child ${child.device.deviceNetworkId} "

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

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

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

	return null
}

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

    return(result)
}

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

    return(result)
}

def setACStates(child,String PodUid, on, mode, targetTemperature, fanLevel, swingM, sUnit)
{
	log.trace "setACStates() called for $PodUid ON: $on - MODE: $mode - Temp : $targetTemperature - FAN : $fanLevel - SWING MODE : $swingM - UNIT : $sUnit"
    
    //Return false if no values was read from Sensibo API
    if (on == "--") { return false }
    
    def OnOff = (on == "on") ? true : false
    //if (swingM == null) swingM = "stopped"
    
    log.trace "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}")

	boolean result = true
	if(!sendJson(PodUid, jsonRequestBody))
	{ result = false }
		
	if (result) {
		def tData = state.sensibo[child.device.deviceNetworkId]      
        
        if (tData == null) {
        	pollChildren(child.device.deviceNetworkId)
            tData = state.sensibo[child.device.deviceNetworkId]
        }        
        
        log.debug "Device : " + child.device.deviceNetworkId + " state : " + tData
        
		tData.data.fanLevel = fanLevel
        tData.data.thermostatFanMode = fanLevel
        tData.data.on = on
        tData.data.currentmode = 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 = state.sensibo[child.device.deviceNetworkId]
        if (tData == null) return false
    	
        tData.data.Error = "Failed"
    }

	return(result)
}

//Get the capabilities of the A/C Unit
def getCapabilities(PodUid, mode)
{
	log.trace "getCapabilities() called"
    def now = new Date().time
    
    def last = state.lastPollCapabilitiesMillis ?: 0
	def next = last + getCapabilitiesRateMillis()
    
    def data = [:] 
   
    if (state.capabilities == null || state.capabilities.$PodUid == null || now > next)
    //if (true)
	{
    	log.debug "Now : " + now + " Next : " + next    	
        
    	//def data = [:]   
		def pollParams = [
    	uri: "${getServerUrl()}",
    	path: "/api/v2/pods/${PodUid}",
    	requestContentType: "application/json",
    	query: [apiKey:"${getapikey()}", integration:"${version()}", type:"json", fields:"remoteCapabilities,productModel"]]
     
     	try {
			log.trace "getCapabilities() called - Request sent to Sensibo API(remoteCapabilities) for PODUid : $PodUid - ${version()}"
            
     		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"]]]]]]
                        
                        log.debug resp.data

                        if (state.capabilities == null) { state.capabilities = [:] }
                        
                        state.capabilities.$PodUid = resp.data
                        log.debug "Succes read from Sensibo"
                        log.trace "Capabilities from Sensibo for ${PodUid} : " + state.capabilities.$PodUid			
						
                        def currentTime = new Date().time
						debugEvent ("Current Time = ${currentTime}")
						state.lastPollCapabilitiesMillis = currentTime
                        
                        switch (mode){
                            case "dry":
                                data = [
                                    remoteCapabilities : resp.data.result.remoteCapabilities.modes.dry,
                                    productModel :  resp.data.result.productModel
                                ]	
                                break
                            case "cool":
                                data = [
                                    remoteCapabilities : resp.data.result.remoteCapabilities.modes.cool,
                                    productModel : resp.data.result.productModel
                                ]	
                                break
                            case "heat":
                                data = [
                                    remoteCapabilities : resp.data.result.remoteCapabilities.modes.heat,
                                    productModel : resp.data.result.productModel
                                ]	
                                break
                            case "fan":
                                data = [
                                    remoteCapabilities : resp.data.result.remoteCapabilities.modes.fan,
                                    productModel : resp.data.result.productModel
                                ]	
                                break
                            case "auto":
                                data = [
                                    remoteCapabilities : resp.data.result.remoteCapabilities.modes.auto,
                                    productModel : resp.data.result.productModel
                                ]	
                                break
                            case "modes":
                                data = [
                                    remoteCapabilities : resp.data.result.remoteCapabilities.modes,
                                    productModel : resp.data.result.productModel
                                ]	
                                break                        
                        }
                        log.trace "Returning remoteCapabilities from Sensibo"
                        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
     	}
    }
    
    else {
    	log.trace "Capabilities from local for ${PodUid} : " + state.capabilities.$PodUid
        //return
    	switch (mode){
            case "dry":
            data = [
                remoteCapabilities : state.capabilities.$PodUid.result.remoteCapabilities.modes.dry,
                productModel : state.capabilities.$PodUid.result.productModel
            ]	
            break
            case "cool":
            data = [
                remoteCapabilities : state.capabilities.$PodUid.result.remoteCapabilities.modes.cool,
                productModel : state.capabilities.$PodUid.result.productModel
            ]	
            break
            case "heat":
            data = [
                remoteCapabilities :state.capabilities.$PodUid.result.remoteCapabilities.modes.heat,
                productModel : state.capabilities.$PodUid.result.productModel
            ]	
            break
            case "fan":
            data = [
                remoteCapabilities : state.capabilities.$PodUid.result.remoteCapabilities.modes.fan,
                productModel : state.capabilities.$PodUid.result.productModel
            ]	
            break
            case "auto":
            data = [
                remoteCapabilities : state.capabilities.$PodUid.result.remoteCapabilities.modes.auto,
                productModel : state.capabilities.$PodUid.result.productModel
            ]	
            break
            case "modes":
            data = [
                remoteCapabilities : state.capabilities.$PodUid.result.remoteCapabilities.modes,
                productModel : state.capabilities.$PodUid.result.productModel
            ]	
            break                        
        }
        log.trace "Returning remoteCapabilities from local"
        return data
    }                  
}


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

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

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

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

			if (resp.data) {
				debugEvent ("Response from Sensibo GET = ${resp.data}")
				debugEvent ("Response Status = ${resp.status}")
			}
			
			if(resp.status == 200) {
            	resp.data.result.any { stat ->
                	
                	if (stat.status == "Success") {
                    	
                        log.trace "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
                        }
						
						def sMode
						if (stat.acState.swing == null) {
                          sMode = "stopped"
                        }
                        else {
                          sMode = stat.acState.swing
                        }
                        
                        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,
                            currentmode : stat.acState.mode,
                            on : OnOff.toString(),
                            switch: OnOff.toString(),
                            thermostatMode: tMode,
                            thermostatFanMode : stat.acState.fanLevel,
                            coolingSetpoint : stemp,
                            heatingSetpoint : stemp,
                            thermostatSetpoint : stemp,
                            temperatureUnit : tempUnit,
                            swing : sMode,
                            powerSource : battery,
                            productModel : stat.device.productModel,
                            firmwareVersion : stat.device.firmwareVersion,
                            Error : "Success"
                        ]

                        log.debug "On: ${data.on} targetTemp: ${data.targetTemperature} fanLevel: ${data.fanLevel} Thermostat mode: ${data.mode} swing: ${data.swing}"
                        log.trace "Returning ACState"
                        return data
                	}
                    else { log.debug "get ACState Failed"}
               }
           }
           else {
           	  data = [
                 targetTemperature : "0",
                 fanLevel : "--",
                 currentmode : "--",
                 on : "--",
                 switch : "--",
                 thermostatMode: "--",
                 thermostatFanMode : "--",
                 coolingSetpoint : "0",
                 heatingSetpoint : "0",
                 thermostatSetpoint : "0",
                 temperatureUnit : "",
                 swing : "--",
                 powerSource : "",
                 productModel : "",
                 firmwareVersion : "",
                 Error : "Failed"
			  ]
              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 : "--",
            currentmode : "--",
            on : "--",
            switch : "--",
            thermostatMode: "--",
            thermostatFanMode : "--",
            coolingSetpoint : "0",
            heatingSetpoint : "0",
            thermostatSetpoint : "0",
            temperatureUnit : "",
            swing : "--",
            powerSource : "",
            productModel : "",
            firmwareVersion : "",
            Error : "Failed" 
		]
        log.debug "get ACState Failed"
        return data
	} 
}

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

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

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

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

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

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

def pollChildren(PodUid)
{
    log.trace "pollChildren() called"
    
    def thermostatIdsString = PodUid

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

	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.trace "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)
                
                def ClimateReact = getClimateReact(thermostatIdsString)
           
                if (setTemp.Error != "Failed") {
                
				 state.sensibo = resp.data.result.inject([:]) { collector, stat ->

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

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

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

					def battpourcentage = 100
                    def battVoltage = stat.batteryVoltage
                    
					if (battVoltage == null) 
                    {
                    	battVoltage = 3000
                    }                    
                    
                    if (battVoltage < 2850) battpourcentage = 10
                    if (battVoltage > 2850 && battVoltage < 2950) battpourcentage = 50
                    
					def data = [
						temperature: stemp,
						humidity: shumidify,
                        targetTemperature: setTemp.targetTemperature,
                        fanLevel: setTemp.fanLevel,
                        currentmode: setTemp.currentmode,
                        on: setTemp.on,
                        switch : setTemp.on,
                        thermostatMode: tMode,
                        thermostatFanMode: setTemp.fanLevel,
                        coolingSetpoint: setTemp.targetTemperature,
                        heatingSetpoint: setTemp.targetTemperature,
                        thermostatSetpoint: setTemp.targetTemperature,
                        temperatureUnit : setTemp.temperatureUnit,
                        voltage : battVoltage,
                        swing : setTemp.swing,
                        battery : battpourcentage,
                        powerSource : setTemp.powerSource,
                        productModel : setTemp.productModel,
                        firmwareVersion : setTemp.firmwareVersion,
                        Climate : ClimateReact.Climate,
                        Error: setTemp.Error
					]
                    
					debugEvent ("Event Data = ${data}",false)

					collector[dni] = [data:data]
                    
					return collector
				 }				
                }
                
				log.debug "updated ${state.sensibo[thermostatIdsString].size()} stats: ${state.sensibo[thermostatIdsString]}"
                debugEvent ("updated ${state.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 = getChildDevices()
    
    log.debug PodList
    PodList.each { 
    	log.debug "polling " + it.deviceNetworkId
        pollChildren(it.deviceNetworkId) }
	
    state.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 state.sensibo[dni]
			d.generateEvent(state.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()
}

Thanks so much @blink. Unfortunately the updated code hasn't worked either although it seems to have moved on to other issues.
When I first tried to turn the sensibo on the logs showed the following:

dev:142019-04-28 01:55:55.204 errorjava.lang.NullPointerException: Cannot get property 'value' on null object on line 806 (on)

dev:142019-04-28 01:55:55.173 debugTemp Unit : C

dev:142019-04-28 01:55:55.162 debugon called

I then tried a Refresh, which returned no errors in the logs:

dev:142019-04-28 01:57:06.781 debugrefresh() ended

dev:142019-04-28 01:57:06.580 debugname :Error value :Success

dev:142019-04-28 01:57:06.579 debugname :Climate value :notdefined

dev:142019-04-28 01:57:06.578 debugname :firmwareVersion value :SKY30036

dev:142019-04-28 01:57:06.577 debugname :productModel value :skyv2

dev:142019-04-28 01:57:06.552 debugname :powerSource value :mains

dev:142019-04-28 01:57:06.549 debugname :battery value :100

dev:142019-04-28 01:57:06.547 debugname :swing value :stopped

dev:142019-04-28 01:57:06.546 debugname :voltage value :3000

dev:142019-04-28 01:57:06.545 debugname :temperatureUnit value :C

dev:142019-04-28 01:57:06.544 debugname :thermostatSetpoint value :18

dev:142019-04-28 01:57:06.543 debugname :heatingSetpoint value :18

dev:142019-04-28 01:57:06.537 debugname :coolingSetpoint value :18

dev:142019-04-28 01:57:06.536 debugname :thermostatFanMode value :null

dev:142019-04-28 01:57:06.535 debugname :thermostatMode value :off

dev:142019-04-28 01:57:06.534 debugname :switch value :off

dev:142019-04-28 01:57:06.533 debugname :on value :off

dev:142019-04-28 01:57:06.532 debugname :currentmode value :null

dev:142019-04-28 01:57:06.531 debugname :fanLevel value :null

dev:142019-04-28 01:57:06.529 debugname :targetTemperature value :18

dev:142019-04-28 01:57:06.514 debugname :humidity value :64

dev:142019-04-28 01:57:06.503 debugname :temperature value :18.9

dev:142019-04-28 01:57:06.494 debugparsing Event data [temperature:18.9, humidity:64, targetTemperature:18, fanLevel:null, currentmode:null, on:off, switch:off, thermostatMode:off, thermostatFanMode:null, coolingSetpoint:18, heatingSetpoint:18, thermostatSetpoint:18, temperatureUnit:C, voltage:3000, swing:stopped, battery:100, powerSource:mains, productModel:skyv2, firmwareVersion:SKY30036, Climate:notdefined, Error:Success]

dev:142019-04-28 01:57:06.419 debugparsing data [temperature:18.9, humidity:64, targetTemperature:18, fanLevel:null, currentmode:null, on:off, switch:off, thermostatMode:off, thermostatFanMode:null, coolingSetpoint:18, heatingSetpoint:18, thermostatSetpoint:18, temperatureUnit:C, voltage:3000, swing:stopped, battery:100, powerSource:mains, productModel:skyv2, firmwareVersion:SKY30036, Climate:notdefined, Error:Success]

dev:142019-04-28 01:57:01.889 debugExecuting 'poll' using parent SmartApp

dev:142019-04-28 01:57:01.881 debugrefresh() called

Although after the refresh, no further errors were reported when I tried to switch on again, but still no action from the Sensibo:

dev:142019-04-28 01:57:16.117 debugSensibo Pod Living Room A/C FAILED to set the AC State

dev:142019-04-28 01:57:16.115 debugResult : false

dev:142019-04-28 01:57:14.349 debugTemp Unit : C

dev:142019-04-28 01:57:14.337 debugon called

Not sure if you can help any further, and can't see EricG's username in these forums?

I'm not sure that he uses Hubitat - this code is posted over in SmartThings. It looks like a bunch of fan related status' are also null for you at this point: fanLevel, currentMode, and thermostatFanMode. Could you try turning on your sensibo and running a refresh to see if those values get populated or if they're still null?

Thank you, @blink, for your work on this. I just tried out your code for the first time, and it seems to be working fine for me straight away. There are a few errors showing in the logs, which might indicate a little bit of cleanup necessary in the code, but my AC is being successfully controlled from my Hubitat.

One thing I would love to see is a switch to enable/disable debug logging, like most other devices seem to have. Once the integration has been setup and tested, I don't really want to have all the debug log entries populating my logs.

I also want to thank @blink for his work and @Angus_M for describing the steps simply enough for me to follow. It seems to be working for me too.

thank you, I setup the driver, then the app, then added the app with API and found my 2 V1 sensibo devices. Love it.

How r u guys finding performance? I'm still finding its slow at times due to the Cloud I guess. Same with interaction using Google Home too. Wish we had a direct integration.

@blink Is it possible for you to create a set of device and driver code with no debug logging?
I found that they flood my log every 10 seconds or so and it is not ideal.

Or maybe someone can suggest an easy way for me to turn the debugging off?

1 Like

I have the same. Agree would be great to suppress all the unnecessary logging.or have an easy option for this.

@tonysosw how is the performance for you. Do you find they respond ok? I have patchy response.

@Angus_M I haven't encountered any problem yet, all commands seems to be functional and my aircon responded accordingly

same here with my 2 gen1's - no issues

I've got Sky v2, (three of them), and no problem with performance.

But, for the lolz, I've just taken the App and Driver and wrapped the Logging functions so you can turn on/off logging.

In the driver code, you can switch it off per device on the device screen (info and debug), but this generally doesn't generate much in the way of messages.

See below for App code (which is very noisy), and you can turn on/off logging (This code has logging turned off by default)

NOTE: I'm a total novice at this, but it seems to work for me, and has made the logs way cleaner, so enjoy!

Driver Code
(Revised - see post #54)

1 Like

App Code

Change Lines 44-46 from false to true if you want to turn logging on.

/**
 *  Sensibo Integration for Hubitat
 *
 *  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 Integration",
    namespace: "EricG66",
    author: "Eric Gosselin",
    description: "Connect your Sensibo Pod to Hubitat.",
    category: "Green Living",
    iconUrl: "https://image.ibb.co/j7qAPT/Sensibo_1x.png",
    iconX2Url: "https://image.ibb.co/coZtdo/Sensibo_2x.png",
    iconX3Url: "https://image.ibb.co/cBwTB8/Sensibo_3x.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 version() { "SmartThingsv1.5" }

// if you want logging, set these true, else false 
def debugLog() { return false }
def traceLog() { return false }

public static String version() { return "SmartThingsv1.6" }



private def displayDebugLog(message) {
	if (debugLog() == true) log.debug "${message}"
}

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


def setAPIKey()
{
	displayTraceLog( "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()
{
	displayTraceLog( "SensiboPodList()")

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

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

	def pods = [:]
	
    try {
      httpGet(deviceListParams) { resp ->
    	if(resp.status == 200)
			{
				resp.data.result.each { pod ->
                    def key = pod.id
                    def value = pod.room.name
                        
					pods[key] = value
				}
                state.pods = pods
			}
	  }
    }
    catch(Exception e)
	{
		displayDebugLog( "Exception Get Json: " + e)
		debugEvent ("Exception get JSON: " + e)
	}
    
    displayDebugLog( "Sensibo Pods: $pods"  )
	
    return pods
}

def installed() {
	displayTraceLog( "Installed() called with settings: ${settings}")

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

	if (boolnotifevery) {
    	//runEvery1Hour("hournotification")
        schedule("0 0 * * * ?", "hournotification")
	}
    
    displayDebugLog( "Configured health checkInterval when installed()")
	sendEvent(name: "checkInterval", value: 2 * 60 * 60 + 2 * 60, displayed: true)
    
    //subscribe(d,"temperatureUnit",eTempUnitHandler)
    
    if (sendPushNotif) { 
    	subscribe(d, "temperature", eTemperatureHandler)
        subscribe(d, "humidity", eHumidityHandler)
    }
}

def updated() {
	displayTraceLog( "Updated() called with settings: ${settings}")

	unschedule()
    unsubscribe()
	
    state.lastTemperaturePush = null
    state.lastHumidityPush = null
    
    initialize()
    
    def d = getChildDevices()
    
    if (boolnotifevery) {
    	//runEvery1Hour("hournotification")
        schedule("0 0 * * * ?", "hournotification")
	}
    
    displayDebugLog( "Configured health checkInterval when installed()")
	sendEvent(name: "checkInterval", value: 2 * 60 * 60 + 2 * 60, displayed: true)
    
    //subscribe(d,"temperatureUnit",eTempUnitHandler)
    
    if (sendPushNotif) {
    	subscribe(d, "temperature", eTemperatureHandler)
        subscribe(d, "humidity", eHumidityHandler)
    }
}

def ping() {

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

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

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

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

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

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

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

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

                state.lastTemperaturePush = hour
            }
        }
    } 
}

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

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

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

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

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

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

    return true
}

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

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

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

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

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


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

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

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

def initialize() {
    displayTraceLog( "initialize() called")
    displayTraceLog( "key "+ getapikey())
    
    state.apikey = getapikey()
	  
	def devices = SelectedSensiboPods.collect { dni ->
		displayDebugLog( dni)
		def d = getChildDevice(dni)

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

				d = addChildDevice(getChildNamespace(), getChildTypeName(), dni, location.hubs[0].id, [
                	"label" : "Pod ${name.value}",
                    "name" : "Pod ${name.value}"
                    ])
                d.setIcon("on","on","https://image.ibb.co/jgAMW8/sensibo-sky-off.png")
                d.setIcon("off","on","https://image.ibb.co/jgAMW8/sensibo-sky-off.png")
                d.save()              
                
				displayTraceLog( "created ${d.displayName} with id $dni")
			}
			else
			{
				displayTraceLog( "found ${d.displayName} with id $dni already exists")
			}

			return d
		}

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

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

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

	def PodList = getChildDevices()
	
    pollHandler()
    
    refreshDevices()
    
    if (refreshinminutes == "1") 
    	runEvery1Minute("refreshDevices")
    else if (refreshinminutes == "5")
    	runEvery5Minutes("refreshDevices")
    else if (refreshinminutes == "10")
    	runEvery10Minutes("refreshDevices")
    else if (refreshinminutes == "15") 
    	runEvery15Minutes("refreshDevices")
    else if (refreshinminutes == "30")
    	runEvery30Minutes("refreshDevices")
    else
    	runEvery10Minutes("refreshDevices")
}


// Subscribe functions

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

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

def getPollRateMillis() { return 45 * 1000 }
def getCapabilitiesRateMillis() {return 60 * 1000 }

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

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

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

		pollChildren(child.device.deviceNetworkId)

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

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

		def tData = state.sensibo[child.device.deviceNetworkId]
        
        if (tData == null) return
        
        displayDebugLog(  "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(state.sensibo[child.device.deviceNetworkId] != null)
	{
		displayDebugLog( "not polling children, found child ${child.device.deviceNetworkId} ")

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

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

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

	return null
}

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

    return(result)
}

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

    return(result)
}

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

	boolean result = true
	if(!sendJson(PodUid, jsonRequestBody))
	{ result = false }
		
	if (result) {
		def tData = state.sensibo[child.device.deviceNetworkId]      
        
        if (tData == null) {
        	pollChildren(child.device.deviceNetworkId)
            tData = state.sensibo[child.device.deviceNetworkId]
        }        
        
        displayDebugLog( "Device : " + child.device.deviceNetworkId + " state : " + tData)
        
		tData.data.fanLevel = fanLevel
        tData.data.thermostatFanMode = fanLevel
        tData.data.on = on
        tData.data.currentmode = mode
        displayDebugLog( "Thermostat mode " + on)
        if (on=="off") {
        	tData.data.thermostatMode = "off"
        }
        else {
        	 tData.data.thermostatMode = mode
        }
        tData.data.targetTemperature = targetTemperature
        tData.data.coolingSetpoint = targetTemperature
        tData.data.heatingSetpoint = targetTemperature
        tData.data.thermostatSetpoint = targetTemperature
        tData.data.temperatureUnit = sUnit
        tData.data.swing = swingM
        tData.data.Error = "Success"
	}
    else {
    	def tData = state.sensibo[child.device.deviceNetworkId]
        if (tData == null) return false
    	
        tData.data.Error = "Failed"
    }

	return(result)
}

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

                        if (state.capabilities == null) { state.capabilities = [:] }
                        
                        state.capabilities.$PodUid = resp.data
                        displayDebugLog( "Succes read from Sensibo")
                        displayTraceLog( "Capabilities from Sensibo for ${PodUid} : " + state.capabilities.$PodUid	)		
						
                        def currentTime = new Date().time
						debugEvent ("Current Time = ${currentTime}")
						state.lastPollCapabilitiesMillis = currentTime
                        
                        switch (mode){
                            case "dry":
                                data = [
                                    remoteCapabilities : resp.data.result.remoteCapabilities.modes.dry,
                                    productModel :  resp.data.result.productModel
                                ]	
                                break
                            case "cool":
                                data = [
                                    remoteCapabilities : resp.data.result.remoteCapabilities.modes.cool,
                                    productModel : resp.data.result.productModel
                                ]	
                                break
                            case "heat":
                                data = [
                                    remoteCapabilities : resp.data.result.remoteCapabilities.modes.heat,
                                    productModel : resp.data.result.productModel
                                ]	
                                break
                            case "fan":
                                data = [
                                    remoteCapabilities : resp.data.result.remoteCapabilities.modes.fan,
                                    productModel : resp.data.result.productModel
                                ]	
                                break
                            case "auto":
                                data = [
                                    remoteCapabilities : resp.data.result.remoteCapabilities.modes.auto,
                                    productModel : resp.data.result.productModel
                                ]	
                                break
                            case "modes":
                                data = [
                                    remoteCapabilities : resp.data.result.remoteCapabilities.modes,
                                    productModel : resp.data.result.productModel
                                ]	
                                break                        
                        }
                        displayTraceLog( "Returning remoteCapabilities from Sensibo")
                        return data
                    }
                    else {
                        displayDebugLog( "get remoteCapabilities Failed")

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


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

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

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

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

			if (resp.data) {
				debugEvent ("Response from Sensibo GET = ${resp.data}")
				debugEvent ("Response Status = ${resp.status}")
			}
			
			if(resp.status == 200) {
            	resp.data.result.any { stat ->
                	
                	if (stat.status == "Success") {
                    	
                        displayTraceLog( "get ACState Success")
                        displayDebugLog( "PodUID : $PodUid : " + stat.acState)
                        
                        def OnOff = stat.acState.on ? "on" : "off"
                        stat.acState.on = OnOff
						
						def stemp
                        if (stat.acState.targetTemperature == null) {
                          stemp = stat.device.measurements.temperature.toInteger()
                        }
                        else {
                          stemp = stat.acState.targetTemperature.toInteger()
                        }
                        
                        def tempUnit
                        if (stat.acState.temperatureUnit == null) {
                          tempUnit = stat.device.temperatureUnit
                        }
                        else {
                          tempUnit = stat.acState.temperatureUnit
                        }	
					
                        def tMode                        
                        if (OnOff=="off") {
        					tMode = "off"
        				}
				        else {
        	 				tMode = stat.acState.mode
                        }
						
						def sMode
						if (stat.acState.swing == null) {
                          sMode = "stopped"
                        }
                        else {
                          sMode = stat.acState.swing
                        }
                        
                        displayDebugLog( "product Model : " + stat.device.productModel)
                        def battery = stat.device.productModel == "skyv1" ? "battery" : "mains"
                        
                        displayDebugLog( "swing Mode :" + stat.acState.swing)
                        data = [
                            targetTemperature : stemp,
                            fanLevel : stat.acState.fanLevel,
                            currentmode : stat.acState.mode,
                            on : OnOff.toString(),
                            switch: OnOff.toString(),
                            thermostatMode: tMode,
                            thermostatFanMode : stat.acState.fanLevel,
                            coolingSetpoint : stemp,
                            heatingSetpoint : stemp,
                            thermostatSetpoint : stemp,
                            temperatureUnit : tempUnit,
                            swing : sMode,
                            powerSource : battery,
                            productModel : stat.device.productModel,
                            firmwareVersion : stat.device.firmwareVersion,
                            Error : "Success"
                        ]

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

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

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

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

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

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

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

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

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

	debugEvent ("Before HTTPGET to Sensibo.")

	try{
		httpGet(pollParams) { resp ->

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

			if(resp.status == 200) {
				displayTraceLog( "poll results returned"     )                           

                displayDebugLog( "DEBUG DATA RESULT" + resp.data.result)
                
                if (resp.data.result == null || resp.data.result.empty) 
                {
                	displayDebugLog( "Cannot get measurement from the API, should ask Sensibo Support Team")
                	debugEvent ("Cannot get measurement from the API, should ask Sensibo Support Team",true)
                }
                
                def setTemp = getACState(thermostatIdsString)
                
                def ClimateReact = getClimateReact(thermostatIdsString)
           
                if (setTemp.Error != "Failed") {
                
				 state.sensibo = resp.data.result.inject([:]) { collector, stat ->

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

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

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

					def battpourcentage = 100
                    def battVoltage = stat.batteryVoltage
                    
					if (battVoltage == null) 
                    {
                    	battVoltage = 3000
                    }                    
                    
                    if (battVoltage < 2850) battpourcentage = 10
                    if (battVoltage > 2850 && battVoltage < 2950) battpourcentage = 50
                    
					def data = [
						temperature: stemp,
						humidity: shumidify,
                        targetTemperature: setTemp.targetTemperature,
                        fanLevel: setTemp.fanLevel,
                        currentmode: setTemp.currentmode,
                        on: setTemp.on,
                        switch : setTemp.on,
                        thermostatMode: tMode,
                        thermostatFanMode: setTemp.fanLevel,
                        coolingSetpoint: setTemp.targetTemperature,
                        heatingSetpoint: setTemp.targetTemperature,
                        thermostatSetpoint: setTemp.targetTemperature,
                        temperatureUnit : setTemp.temperatureUnit,
                        voltage : battVoltage,
                        swing : setTemp.swing,
                        battery : battpourcentage,
                        powerSource : setTemp.powerSource,
                        productModel : setTemp.productModel,
                        firmwareVersion : setTemp.firmwareVersion,
                        Climate : ClimateReact.Climate,
                        Error: setTemp.Error
					]
                    
					debugEvent ("Event Data = ${data}",false)

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

	}
	catch(Exception e)
	{
		displayDebugLog( "___exception polling children: " + e)
		debugEvent ("${e}")
	}
}

def pollHandler() {

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

		def dni = stat.key

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

		def d = getChildDevice(dni)

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

def debugEvent(message, displayEvent = false) {

	def results = [
		name: "appdebug",
		descriptionText: message,
		displayed: displayEvent
	]
	displayDebugLog( "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