Honeywell Smart Wi-Fi Thermostat Compatibility

OK - I figured out where the log.debug data appears - its in the console window when operating the device simulator in the Device Type page of the IDE. Earlier I was trying to use the IDE while at my work and it didn’t work the same (there was nothing in the console). Now that I’m at home, the console is showing the debug log.

I think its showing the Honeywell API change problem. There’s an error in the login and authentication line, then errors when the code tries to use thermostat parameters that are nulls.

I am not 100% certain about that other code, but I think the hex device id was just in the first iterations. I have not used it in any of the code I currently have. My code is throwing auth errors when trying to run the getStatus(). Something about the device status url has changed.

So I have the Honeywell RTH9580WF. I have done all the steps and am having the same problem where even after I enter all the stuff it never connects to the thermostat. So are you saying that all your codes won’t work now because Honeywell changed their API?

1 Like

That is what it looks like at this point. The API is not public so I am just waiting with someone with a bit more knowledge of the honeywell API to chime in.

Is still working? Would love to get it working with my new thermostst.

Anyone know how this app works? Seems to be developed by a third party developer and is working currently on Windows and Windows Phone. Just tested it and can control my thermostat fine.

I have some python code that can access the honeywell api. I will work this weekend to try and figure out what they changed and create a new device type.

@ersantana3 @hgunther

Use this new device type. Most functionality is back.

Big Thanks to @ethomasii

I merged your code with the new fixes and now have a fully functional control panel for the honeywell wifi thermostat. The model I have does not have a humidity sensor, so I commented those bits out.

Regards,
Jody

Jody
Thanks. It’s back working!

Jody - Thanks.

I have the TH9320WF5003 which does have a humidity sensor, but only indoors. Slightly changing the commented out code and one line made mine work -

    def IndoorHumiditySensorAvailable = response.data.latestData.uiData.IndoorHumiditySensorAvailable
    //def OutdoorHumidityAvailable = response.data.latestData.uiData.OutdoorHumidityAvailable
    //def OutdoorHumidity = response.data.latestData.uiData.OutdoorHumidity
    def IndoorHumidity = response.data.latestData.uiData.IndoorHumidity
//def outHumidity = response.data.latestData.weather.Humidity
    def Humidity = IndoorHumidity

if (OutdoorHumiditySensorAvailable == 'true')
    	Humidity = OutdoorHumidity

    if (IndoorHumiditySensorAvailable == 'true')
    	Humidity = IndoorHumidity

You guys are great! I managed to get this working with my setup. A couple of questions/requests:

  1. I have an outdoor humidity reading. Would it be possible to display both indoor and outdoor humidity readings instead of just one? Maybe we can add a Parameter to specific if you have an outdoor humidity sensor? Also, what is this … def outHumidity = response.data.latestData.weather.Humidity … does Honeywell report another humidity value? I got an error when I uncommented this line.

  2. My thermometer is set to use Celsius. Is there any way to add this as an option so the display reads Celsius? Also the level up and level down ranges need to be adjusted for Celsius and support 0.5 degree increments.

I can try an hack my way through this but I’m new to ST and haven’t tried developing in ST yet.

The outdoor humidity is a setting provided by Honeywell. You will see it when you log into their web interface or view the Total Connect app on your phone. It always reports absurd numbers for me. Because of that and the fact that my thermo does not have a humidity sensor I did not work on either of the functions.

I am new at this, but try this for indoor and outdoor humidity…

2 Likes

Super! Thanks Bruce … this is really helpful. I’m just getting started with ST development but this helped me get the Indoor/Outdoor humidity working.

I made a couple of other changes to support Celsius, changed some icons etc.

The only remaining thing I’m trying to figure out is how to display decimals in the UI. The logs are showing the thermostat reporting a temperature of 24.5 degrees Celsius (Curent Temp: 24.5000) but the UI rounds this to 25 degrees Celsius.

Any ideas on how to change this???

/**
 *  Total Comfort API
 *
 *  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 {
    input("username", "text", title: "Username", description: "Your Total Comfort User Name")
    input("password", "password", title: "Password", description: "Your Total Comfort password")
    input("honeywelldevice", "text", title: "Device ID", description: "Your Device ID")
    
}
metadata {
	definition (name: "Honeywell Thermostat", author: "") {
		capability "Polling"
		capability "Thermostat"
		capability "Refresh"
		capability "Temperature Measurement"
		capability "Sensor"
        
    	command "heatLevelUp"
		command "heatLevelDown"
		command "coolLevelUp"
		command "coolLevelDown"
	}

	simulator {
		// TODO: define status and reply messages here
	}

   tiles {
        valueTile("temperature", "device.temperature", width: 2, height: 2, canChangeIcon: true) {
            state("temperature", label: '${currentValue}°C', unit:"C", backgroundColors: [
                    [value: 0,  color: "#153591"],
                    [value: 10, color: "#1e9cbb"],
                    [value: 20, color: "#90d2a7"],
                    [value: 22, color: "#44b621"],
                    [value: 26, color: "#f1d801"],
                    [value: 30, color: "#d04e00"],
                    [value: 35, color: "#bc2323"]
                ]
            )
        }


		//Thermostat Control
		standardTile("thermostatMode", "device.thermostatMode", canChangeIcon: true, inactiveLabel: false) {
            state "heat", label:'', action:"thermostat.off",  icon: "st.thermostat.heat", backgroundColor: '#E14902'
            state "off",  label:'', action:"thermostat.cool", icon: "st.thermostat.heating-cooling-off"
            state "cool", label:'', action:"thermostat.heat", icon: "st.thermostat.cool", backgroundColor: '#003CEC'
        }

		standardTile("thermostatFanMode", "device.thermostatFanMode", canChangeIcon: true, inactiveLabel: false) {
            state "auto",      label:'', action:"thermostat.fanOn", icon: "st.thermostat.fan-auto"
            state "on",        label:'', action:"thermostat.fanCirculate", icon: "st.thermostat.fan-on"
            state "circulate", label:'', action:"thermostat.fanAuto", icon: "st.thermostat.fan-circulate"
        }


		// Heat Temperature Setting
		standardTile("heatLevelDown", "device.heatingSetpoint", canChangeIcon: false, inactiveLabel: false) {
        	state "heatLevelDown", label:'Down', action:"heatLevelDown", icon:"st.thermostat.thermostat-down"
        }
	
    	valueTile("heatingSetpoint", "device.heatingSetpoint", inactiveLabel: false) 
		{
			state "default", label:'${currentValue}°C', unit:"C",
			 backgroundColors:
			 [
				[value: 0,  color: "#153591"],
				[value: 10, color: "#1e9cbb"],
				[value: 20, color: "#90d2a7"],
				[value: 22, color: "#44b621"],
				[value: 26, color: "#f1d801"],
				[value: 30, color: "#d04e00"],
				[value: 35, color: "#bc2323"]
			]   
		}
		
        standardTile("heatLevelUp", "device.heatingSetpoint", canChangeIcon: false, inactiveLabel: false) {
			state "heatLevelUp", label:'Up', action:"heatLevelUp", icon:"st.thermostat.thermostat-up"
        }


		// Cool Temperature Setting
        standardTile("coolLevelDown", "device.heatingSetpoint", canChangeIcon: false, inactiveLabel: false) {
			state "coolLevelDown", label:'Down', action:"coolLevelDown", icon:"st.thermostat.thermostat-down"
        }        
        
        valueTile("coolingSetpoint", "device.coolingSetpoint", inactiveLabel: false) 
		{
			state "default", label:'${currentValue}°C', unit:"C",
			 backgroundColors:
			 [
				[value: 0,  color: "#153591"],
				[value: 10, color: "#1e9cbb"],
				[value: 20, color: "#90d2a7"],
				[value: 22, color: "#44b621"],
				[value: 26, color: "#f1d801"],
				[value: 30, color: "#d04e00"],
				[value: 35, color: "#bc2323"]
			]   
		}
        
        standardTile("coolLevelUp", "device.heatingSetpoint", canChangeIcon: false, inactiveLabel: false) {
			state "coolLevelUp", label:'Up', action:"coolLevelUp", icon:"st.thermostat.thermostat-up"
        }


		//Humidity Reporting
        valueTile("indoorhumidity", "device.indoorhumidity", inactiveLabel: false, decoration: "flat") {
            state "default", label:'${currentValue}% Indoor Humidity', unit:"IndoorHumidity"
        }
        
        valueTile("outdoorhumidity", "device.outdoorhumidity", inactiveLabel: false, decoration: "flat") {
            state "default", label:'${currentValue}% Outdoor Humidity', unit:"OutdoorHumidity"
        }


        standardTile("refresh", "device.thermostatMode", decoration: "flat") {
            state "default", action:"polling.poll", icon:"st.secondary.refresh"
        }


        main "temperature"
        details(["temperature", "thermostatMode", "thermostatFanMode",   "heatLevelDown", "heatingSetpoint" , "heatLevelUp", "coolLevelDown","coolingSetpoint", "coolLevelUp", "indoorhumidity", "outdoorhumidity", "refresh",])
    }
}


// parse events into attributes
def parse(String description) {
    
}


def heatLevelDown(){
    def nextLevel = device.currentValue("heatingSetpoint") - 0.5
    
    if( nextLevel < 15){
    	nextLevel = 15
    }
    log.debug "Setting heat set point down to: ${nextLevel}"
    setHeatingSetpoint(nextLevel)
}

def heatLevelUp(){
    def nextLevel = device.currentValue("heatingSetpoint") + 0.5
    
    if( nextLevel > 30){
    	nextLevel = 30
    }
    log.debug "Setting heat set point up to: ${nextLevel}"
    setHeatingSetpoint(nextLevel)
}

def coolLevelDown(){
    def nextLevel = device.currentValue("coolingSetpoint") - 0.5
    
    if( nextLevel < 20){
    	nextLevel = 20
    }
    log.debug "Setting cool set point down to: ${nextLevel}"
    setCoolingSetpoint(nextLevel)
}

def coolLevelUp(){
    def nextLevel = device.currentValue("coolingSetpoint") + 0.5
    
    if( nextLevel > 35){
    	nextLevel = 35
    }
    log.debug "Setting cool set point up to: ${nextLevel}"
    setCoolingSetpoint(nextLevel)
}




// handle commands
def setHeatingSetpoint(temp) {
	data.SystemSwitch = 'null' 
    data.HeatSetpoint = temp
    data.CoolSetpoint = 'null'
    data.HeatNextPeriod = 'null'
    data.CoolNextPeriod = 'null'
    data.StatusHeat='1'
    data.StatusCool='1'
    data.FanMode = 'null'
	setStatus()

    if(data.SetStatus==1)
	{
        sendEvent(name: 'heatingSetpoint', value: temp)
    }
        
}

def setCoolingSetpoint(temp) {
	data.SystemSwitch = 'null' 
    data.HeatSetpoint = 'null'
    data.CoolSetpoint = temp
    data.HeatNextPeriod = 'null'
    data.CoolNextPeriod = 'null'
    data.StatusHeat='1'
    data.StatusCool='1'
    data.FanMode = 'null'
	setStatus()
    
    if(data.SetStatus==1)
	{
        sendEvent(name: 'coolingSetpoint', value: temp)
    }
}

def setTargetTemp(temp) {
	data.SystemSwitch = 'null' 
    data.HeatSetpoint = temp
    data.CoolSetpoint = temp
    data.HeatNextPeriod = 'null'
    data.CoolNextPeriod = 'null'
    data.StatusHeat='1'
    data.StatusCool='1'
    data.FanMode = 'null'
	setStatus()
}

def off() {
	setThermostatMode(2)
}

def heat() {
	setThermostatMode(1)
}

def cool() {
	setThermostatMode(3)
}

def auto() {
	setThermostatMode(4)
}

def emergencyHeat() {
	setThermostatMode(0)
}

def setThermostatMode(mode) {
	log.debug "-------------------------------------"
	log.debug "Executing 'setThermostateMode' $mode"
    
	data.SystemSwitch = mode 
    data.HeatSetpoint = 'null'
    data.CoolSetpoint = 'null'
    data.HeatNextPeriod = 'null'
    data.CoolNextPeriod = 'null'
    data.StatusHeat=1
    data.StatusCool=1
    data.FanMode = 'null'

	setStatus()
    	def switchPos

        if(mode==0)
        	switchPos = 'emergency_heat'
        if(mode==1)
        	switchPos = 'heat'
        if(mode==2)
        	switchPos = 'off'
        if(mode==3)
        	switchPos = 'cool'
        if(mode==4)
        	switchPos = 'auto'

		if(data.SetStatus==1)
		{
    	    sendEvent(name: 'thermostatMode', value: switchPos)
    	}
}

def fanOn() {
    setThermostatFanMode(1)
}

def fanAuto() {
    setThermostatFanMode(0)
}

def fanCirculate() {
    setThermostatFanMode(2)
}

def setThermostatFanMode(mode) {    
	log.debug "-------------------------------------"
	log.debug "Executing 'setThermostFanMode'"
    
    data.SystemSwitch = 'null' 
    data.HeatSetpoint = 'null'
    data.CoolSetpoint = 'null'
    data.HeatNextPeriod = 'null'
    data.CoolNextPeriod = 'null'
    data.StatusHeat='null'
    data.StatusCool='null'
    data.FanMode = mode

	setStatus()

	def fanMode

    if(mode==0)
     	fanMode = 'auto'
    if(mode==2)
    	fanMode = 'circulate'
    if(mode==1)
    	fanMode = 'on'

	if(data.SetStatus==1)
	{
    	sendEvent(name: 'thermostatFanMode', value: fanMode)    
    }
}


def poll() {
	refresh()
}

def setStatus() {
	log.debug "-------------------------------------"
	log.debug "Executing 'setStatus'"

	data.SetStatus = 0
    login()

    def params = [
        uri: "https://rs.alarmnet.com/TotalConnectComfort/Device/SubmitControlScreenChanges",
        headers: [
              'Accept': 'application/json, text/javascript, */*; q=0.01',
              'DNT': '1',
			  'Accept-Encoding': 'gzip,deflate,sdch',
              'Cache-Control': 'max-age=0',
              'Accept-Language': 'en-US,en,q=0.8',
              'Connection': 'keep-alive',
              'Host': 'rs.alarmnet.com',
              'Referer': "https://rs.alarmnet.com/TotalConnectComfort/Device/Control/${settings.honeywelldevice}",
              'X-Requested-With': 'XMLHttpRequest',
              'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.95 Safari/537.36',
              'Cookie': data.cookiess        ],
        body: [ DeviceID: "${settings.honeywelldevice}", SystemSwitch : data.SystemSwitch ,HeatSetpoint : data.HeatSetpoint, CoolSetpoint: data.CoolSetpoint, HeatNextPeriod: data.HeatNextPeriod,CoolNextPeriod:data.CoolNextPeriod,StatusHeat:data.StatusHeat,StatusCool:data.StatusCool,FanMode:data.FanMode]
	]

    httpPost(params) { response ->
	log.debug "'setStatus' Request was successful, $response.status"
    }
    
	log.debug "SetStatus is 1 now"
	log.debug "-------------------------------------"
    data.SetStatus = 1
}


def getStatus() {
	log.debug "-------------------------------------"
	log.debug "Executing 'getStatus'"

    def params = [
        uri: "https://rs.alarmnet.com/TotalConnectComfort/Device/CheckDataSession/${settings.honeywelldevice}",
        headers: [
              'Accept': '*/*',
              'DNT': '1',
              'Accept-Encoding': 'plain',
              'Cache-Control': 'max-age=0',
              'Accept-Language': 'en-US,en,q=0.8',
              'Connection': 'keep-alive',
              'Host': 'rs.alarmnet.com',
              'Referer': 'https://rs.alarmnet.com/TotalConnectComfort/',
              'X-Requested-With': 'XMLHttpRequest',
              'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.95 Safari/537.36',
              'Cookie': data.cookiess        ],
    ]

    httpGet(params) { response ->
	log.debug "'getStatus' Request was successful, $response.status"
        
		def curTemp = response.data.latestData.uiData.DispTemperature
		def coolSetPoint = response.data.latestData.uiData.CoolSetpoint
        def heatSetPoint = response.data.latestData.uiData.HeatSetpoint

		def fanMode = response.data.latestData.fanData.fanMode
		//fan mode 0=auto, 1=on, 2=circ
        if(fanMode==0)
        	fanMode = 'auto'
        if(fanMode==1)
        	fanMode = 'on'
        if(fanMode==2)
        	fanMode = 'circulate'

		def switchPos = response.data.latestData.uiData.SystemSwitchPosition
		//switchPos 1=heat, 2=off, 3=cool, 4=auto, 0=emergency heat
		if(switchPos==1)
        	switchPos = 'heat'
        if(switchPos==2)
        	switchPos = 'off'
        if(switchPos==3)
        	switchPos = 'cool'
        if(switchPos==4)
        	switchPos = 'auto'
        if(switchPos==0)
        	switchPos = 'emergency_heat'

		def IndoorHumiditySensorAvailable = response.data.latestData.uiData.IndoorHumiditySensorAvailable
        def IndoorHumidity = response.data.latestData.uiData.IndoorHumidity
		IndoorHumidity = IndoorHumidity

		def OutdoorHumidityAvailable = response.data.latestData.uiData.OutdoorHumidityAvailable
        def OutdoorHumidity = response.data.latestData.uiData.OutdoorHumidity
		OutdoorHumidity = OutdoorHumidity

		log.debug "-------------------------------------"
		log.debug "Thermometer Readings from Total Connect"
		log.debug "-------------------------------------"
		log.debug "Curent Temp:      $curTemp"
		log.debug "Fan Mode:         $fanMode"
		log.debug "Switch Pos:       $switchPos"
		log.debug "Cool Set Point:   $coolSetPoint"
		log.debug "Heat Set Point:   $heatSetPoint"
		log.debug "Indoor Humidity:  $IndoorHumidity"
		log.debug "Outdoor Humidity: $OutdoorHumidity"
		log.debug "-------------------------------------"
    
        sendEvent(name: 'thermostatFanMode', value: fanMode)
        sendEvent(name: 'thermostatMode', value: switchPos)
        sendEvent(name: 'coolingSetpoint', value: coolSetPoint)
        sendEvent(name: 'heatingSetpoint', value: heatSetPoint)
        sendEvent(name: 'temperature', value: curTemp as Double, state: switchPos)
        sendEvent(name: 'indoorhumidity', value: IndoorHumidity as Integer)
        sendEvent(name: 'outdoorhumidity', value: OutdoorHumidity as Integer)
    }
}

def api(method, args = [], success = {}) {

}

// Need to be logged in before this is called. So don't call this. Call api.
def doRequest(uri, args, type, success) {

}

def refresh() {
	log.debug "-------------------------------------"
	log.debug "Executing 'refresh'"

	login()
    getStatus()
}

def login() {  
	log.debug "-------------------------------------"
    log.debug "Executing 'login'"
        
    def params = [
        uri: 'https://rs.alarmnet.com/TotalConnectComfort/',
        headers: [
            'Content-Type': 'application/x-www-form-urlencoded',
            'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
            'Accept-Encoding': 'sdch',
            'Host': 'rs.alarmnet.com',
            'DNT': '1',
            'Origin': 'https://rs.alarmnet.com/TotalComfort/',
            'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.95 Safari/537.36'
        ],
        body: [timeOffset: '240', UserName: "${settings.username}", Password: "${settings.password}", RememberMe: 'false']
    ]

	data.cookiess = ''

    httpPost(params) { response ->
        log.debug "'login' Request was successful, $response.status"
        // log.debug response.headers
		response.getHeaders('Set-Cookie').each {
        	String cookie = it.value.split(';|,')[0]
			// log.debug "Adding cookie to collection: $cookie"
            if(cookie != ".ASPXAUTH_TH_A=") {
			data.cookiess = data.cookiess+cookie+';'
            }
        }
        //log.debug "cookies: $data.cookies"
    }
}

def isLoggedIn() {
    if(!data.auth) {
        log.debug "No data.auth"
        return false
    }
    
    def now = new Date().getTime();
    return data.auth.expires_in > now
}

See his code for how he got decimals in the tile.

I am not sure, but look where things are defined or specified as integers and try changing that to a decimal type of variable. In the meantime, maybe you can multiply the value by 10.

I only started playing with the IDE this week and I am not programmer, so this is really trial and error for me. All the heavy lifting was done by EThomas and JodyAlbritton and others.

Thanks for the suggestions but this did not work. I tried the approach in the other thread but that requires converting to a string format which breaks the temp up/down capability. I tried changing to a decimal using the below. The log shows the value as 24.5 but the UI still shows the rounded integer value of 25.

	curTemp = curTemp.toDouble()
	coolSetPoint = coolSetPoint.toDouble()
	heatSetPoint = heatSetPoint.toDouble()

Any other ideas?

I have this set up as a Device Type in my account, saved and published to myself. What do I need to do to have it show up in My Devices?
There is probably something very simple I’m missing. Thanks

You create a new device, under Devices, and then set this as the Device Type. Your custom Device Types usually show at the end of the list. Then when you first use the Thing on your IOS device, you’ll have to set the values for your Total Comfort login and Thermostat number (assigned by Honeywell, at the end of your URL when logged into Total Comfort website). Alternately, you can set these in the IDE after you create your device and re-look at it, after clicking it in the Devices list of the IDE.

[Also, you apparently set the Device Network ID, when defining the new device to the hex conversion of its IP address. You can find online IP-to-hex converters. I don’t know if this really matters, since the code never seems to talk to the device itself. Instead, it talks to the Honeywell TC website, which in turn talks to your thermostat.]

1 Like