Honeywell Smart Wi-Fi Thermostat Compatibility

If you use mytotalconnectcomfort.com to control your thermostat, the device type will likely give you some if not all functionality.

I’ve been playing with Honeywell’s “API” with python. I have a RTH9580WF. I don’t use smartthings, but this thread seems to have the most information. I’ve been investigating determining idle vs running. The current codebase look incorrect IMO.

statusCool/statusHeat=0 when set to follow schedule
statusCool/statusHeat=1 when set on temporary hold
statusCool/statusHeat=2 when set on permanent hold

SystemSwitchPosition=1 when set to heat mode
SystemSwitchPosition=2 when set to off mode
SystemSwitchPosition=3 when set to cool mode
SystemSwitchPosition=4 when set to auto mode

I hate to say it, but honeywell doesn’t publish running vs idle to their “API”. I put in a request for this in September 2013 but they still haven’t implemented it.

In regards to humidity, it should be fairly straight forward to code around these values;
“IndoorHumidity”: 29.0,
“IndoorHumiditySensorAvailable”: true,
“IndoorHumiditySensorNotFault”: true,

“OutdoorHumidity”: 92.0,
“OutdoorHumidityAvailable”: true,

Hope this helps.

1 Like

I am trying to get the circulate running all I have is on and auto. MY code now is below, any help would be appreciated.
}
standardTile(“thermostatFanMode”, “device.thermostatFanMode”, inactiveLabel: false, canChangeIcon: true) {
state “auto”, label:’${name}’, action:“thermostat.fanOn”, icon: "http://xxxxx.xxx/Systemfan1.png"
state “on”, label:’${name}’, action:“thermostat.fanAuto”, icon: "http://xxxxx.xxx/Systemfan1.png"
state “circulate”, label:’${name}’, action:“thermostat.fanAuto”, icon: “http://jxxxxx.xxx/Systemfan1.png”
}

I think line 4 is wrong, can someone look at it please? If anyone has circulate working please post your code so I can get my unit working again.

Just tested this, and you’re right. I might have to slap a multi somewhere on the unit itself to pick up vibrations for running/idle.

Or maybe I can rig something to the zone panel…

If there is someone out there that has the fan modes all working ON, AUTO, CIRCULATE on A Honeywell RTH9580WF please look at this code and let me know where I screwed it up.

/**

 *  Total Comfort API2D

 *

 *  Based on Code by Eric Thomas

 *

 *  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: "Total Comfort API2D", namespace: "Total Comfort
APIBH", author: "Eric Thomas") {

capability "Polling"

capability "Thermostat"

capability "Refresh"

capability "Temperature Measurement"

capability "Sensor"

capability "Relative Humidity Measurement"

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, canChangeBackground: true) {

state("temperature", label: '${currentValue}°F', unit:"F",
backgroundColors: [

[value: 31, color: "#153591"],

[value: 44, color: "#1e9cbb"],

[value: 59, color: "#90d2a7"],

[value: 74, color: "#44b621"],

[value: 84, color: "#f1d801"],

[value: 95, color: "#d04e00"],

[value: 96, color: "#bc2323"]

]

)

}

standardTile("thermostatMode", "device.thermostatMode",
inactiveLabel: false, canChangeIcon: true) {

state "off", label:'${name}', action:"thermostat.cool", icon:
"http://

xxxxx.xxx/Unitturnedoff.png"

state "cool", label:'${name}', action:"thermostat.heat", icon:
"http://

xxxxx.xxx/Coolingmode.png"

state "heat", label:'${name}', action:"thermostat.auto", icon:
"http://

xxxxx.xxx/Heatingmode.png",
backgroundColor: '#E14902'

state "auto", label:'${name}', action:"thermostat.off",  icon:
"http://

xxxxx.xxx/Automaticmode.png",
backgroundColor: '#44b621'

}

standardTile("thermostatFanMode", "device.thermostatFanMode",
inactiveLabel: false, canChangeIcon: true) {

state "auto", label:'${name}', action:"thermostat.fanOn", icon:
"http://

xxxxx.xxx/Systemfan1.png"

state "on", label:'${name}', action:"thermostat.fanAuto", icon:
"http://

xxxxx.xxx/Systemfan1.png",
backgroundColor: '#44b621'

state "circ", label:'${name}', action:"thermostat.fanCirc", icon:
"http://

xxxxx.xxx/Systemfan1.png"

}



controlTile("coolSliderControl", "device.coolingSetpoint", "slider",
height: 3, width: 1, inactiveLabel: false) {

state "setCoolingSetpoint", label:'Set temperarure to',
action:"thermostat.setCoolingSetpoint",

backgroundColors:[

[value: 31, color: "#214478"],

[value: 44, color: "#214478"],

[value: 59, color: "#214478"],

[value: 74, color: "#214478"],

[value: 84, color: "#214478"],

[value: 95, color: "#214478"],

[value: 96, color: "#214478"]

]

}

valueTile("coolingSetpoint", "device.coolingSetpoint",
inactiveLabel: false)

{

state "default", label:'Cool @${currentValue}°F', unit:"F",

backgroundColors:

[

[value: 31, color: "#214478"],

[value: 44, color: "#214478"],

[value: 59, color: "#214478"],

[value: 74, color: "#214478"],

[value: 84, color: "#214478"],

[value: 95, color: "#214478"],

[value: 96, color: "#214478"]

]

}

valueTile("heatingSetpoint", "device.heatingSetpoint",
inactiveLabel: false)

{

state "default", label:'Heat @${currentValue}°F', unit:"F",

backgroundColors:

[

[value: 31, color: "#840909"],

[value: 44, color: "#840909"],

[value: 59, color: "#840909"],

[value: 74, color: "#840909"],

[value: 84, color: "#840909"],

[value: 95, color: "#840909"],

[value: 96, color: "#840909"]

]

}





//tile added for operating state - Create the tiles for each
possible state, look at other examples if you wish to change the
icons here.



standardTile("thermostatOperatingState",
"device.thermostatOperatingState", inactiveLabel: false) {

state "running", label:'${name}', icon: "http://

xxxxx.xxx/Unitrunning.png"

state "idle", label:'${name}', icon: "http://

xxxxx.xxx/Unitoff.png"

}



standardTile("refresh", "device.thermostatMode", inactiveLabel:
false, decoration: "flat") {

state "default", action:"polling.poll", icon: "http://

xxxxx.xxx/Refresh2.png"

}



standardTile("heatLevelUp", "device.heatingSetpoint", canChangeIcon:
false, inactiveLabel: false) {

state "heatLevelUp", label:'Up', action:"heatLevelUp", icon:"http://

xxxxx.xxx/Heatup.png"

}

standardTile("heatLevelDown", "device.heatingSetpoint",
canChangeIcon: false, inactiveLabel: false) {

state "heatLevelDown", label:'Down', action:"heatLevelDown",
icon:"http://

xxxxx.xxx/Heatdown.png"

}

standardTile("coolLevelUp", "device.heatingSetpoint", canChangeIcon:
false, inactiveLabel: false) {

state "coolLevelUp", label:'Up', action:"coolLevelUp", icon:"http://

xxxxx.xxx/Coolup.png"

}

standardTile("coolLevelDown", "device.heatingSetpoint",
canChangeIcon: false, inactiveLabel: false) {

state "coolLevelDown", label:'Down', action:"coolLevelDown",
icon:"http://

xxxxx.xxx/Cooldown.png"

}

valueTile("relativeHumidity", "device.relativeHumidity",
inactiveLabel: false)

{

state "default", label:'Humidity ${currentValue}%', unit:"%",

backgroundColors:

[

[value: 31, color: "#447821"],

[value: 44, color: "#447821"],

[value: 59, color: "#447821"],

[value: 74, color: "#447821"],

[value: 84, color: "#447821"],

[value: 95, color: "#447821"],

[value: 96, color: "#447821"]

]

}



main "temperature"

details(["temperature", "thermostatMode", "thermostatFanMode",
"heatLevelUp", "heatingSetpoint" , "heatLevelDown",
"coolLevelUp","coolingSetpoint", "coolLevelDown"
,"refresh","relativeHumidity" ,"thermostatOperatingState"])

}

}



def coolLevelUp(){

int nextLevel = device.currentValue("coolingSetpoint") + 1

if( nextLevel > 99){

nextLevel = 99

}

log.debug "Setting cool set point up to: ${nextLevel}"

setCoolingSetpoint(nextLevel)

}



def coolLevelDown(){

int nextLevel = device.currentValue("coolingSetpoint") - 1

if( nextLevel < 50){

nextLevel = 50

}

log.debug "Setting cool set point down to: ${nextLevel}"

setCoolingSetpoint(nextLevel)

}



def heatLevelUp(){

int nextLevel = device.currentValue("heatingSetpoint") + 1

if( nextLevel > 90){

nextLevel = 90

}

log.debug "Setting heat set point up to: ${nextLevel}"

setHeatingSetpoint(nextLevel)

}



def heatLevelDown(){

int nextLevel = device.currentValue("heatingSetpoint") - 1

if( nextLevel < 40){

nextLevel = 40

}

log.debug "Setting heat set point down to: ${nextLevel}"

setHeatingSetpoint(nextLevel)

}

// parse events into attributes

def parse(String description) {

}

// 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 as Integer)

}

}

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 as Integer)

}

}

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 auto() {

setThermostatMode(4)

}

def heat() {

setThermostatMode(1)

}

def emergencyHeat() {

}



def cool() {

setThermostatMode(3)

}

def setThermostatMode(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==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 setThermostatFanMode(mode) {

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==1)

fanMode = 'on'

if(mode==2)

fanMode = 'circ'

if(data.SetStatus==1)

{

sendEvent(name: 'thermostatFanMode', value: fanMode)

}

}

def poll() {

refresh()

}

def setStatus() {

data.SetStatus = 0

login()

log.debug "Executing 'setStatus'"

def today= new Date()

log.debug

“https://mytotalconnectcomfort.com/portal/Device/SubmitControlScreenChanges”

def params = [

uri:

“https://mytotalconnectcomfort.com/portal/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://mytotalconnectcomfort.com/portal/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 "Request was successful, $response.status"

}

log.debug "SetStatus is 1 now"

data.SetStatus = 1

}

def getStatus() {

log.debug "Executing 'getStatus'"

def today= new Date()

log.debug

“https://mytotalconnectcomfort.com/portal/Device/CheckDataSession/${settings.honeywelldevice}?_=$today.time”

def params = [

uri:

“https://mytotalconnectcomfort.com/portal/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://mytotalconnectcomfort.com/portal',

'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 "Request was successful, $response.status"

def curTemp = response.data.latestData.uiData.DispTemperature

def fanMode = response.data.latestData.fanData.fanMode

def switchPos = response.data.latestData.uiData.SystemSwitchPosition

def coolSetPoint = response.data.latestData.uiData.CoolSetpoint

def heatSetPoint = response.data.latestData.uiData.HeatSetpoint

def statusCool = response.data.latestData.uiData.StatusCool

def statusHeat = response.data.latestData.uiData.StatusHeat

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

log.trace("IndoorHumidity:
${response.data.latestData.uiData.IndoorHumidity}")

log.trace("IndoorHumiditySensorAvailable:
${response.data.latestData.uiData.IndoorHumiditySensorAvailable}")

log.trace("IndoorHumiditySensorNotFault:
${response.data.latestData.uiData.IndoorHumiditySensorNotFault}")

log.trace("IndoorHumidStatus:
${response.data.latestData.uiData.IndoorHumidStatus}")



//Operating State Section

//Set the operating state to off

def operatingState = "idle"

if(statusCool == 1 ) {

operatingState = "running"

} else if (statusHeat == 1 ) {

operatingState = "running"

} else {

operatingState = "idle"

}

//End Operating State

log.debug curTemp

log.debug fanMode

log.debug switchPos

log.debug statusCool

log.debug statusHeat

//fan mode 0=auto, 2=circ, 1=on

if(fanMode==0)

fanMode = 'auto'

if(fanMode==1)

fanMode = 'on'

if(mode==2)

fanMode = 'circ'



if(switchPos==1)

switchPos = 'heat'

if(switchPos==2)

switchPos = 'off'

if(switchPos==3)

switchPos = 'cool'

if(switchPos==4)

switchPos = 'auto'

//Send events

sendEvent(name: 'thermostatOperatingState', value: operatingState)

sendEvent(name: 'thermostatFanMode', value: fanMode)

sendEvent(name: 'thermostatMode', value: switchPos)

sendEvent(name: 'coolingSetpoint', value: coolSetPoint as Integer)

sendEvent(name: 'heatingSetpoint', value: heatSetPoint as Integer)

sendEvent(name: 'temperature', value: curTemp as Integer, state:
switchPos)

sendEvent(name: 'relativeHumidity', value: curHumidity 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 "Executing 'refresh'"

login()

getStatus()

}

def login() {

log.debug "Executing 'login'"

def params = [

uri: 'https://mytotalconnectcomfort.com/portal',

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': 'mytotalconnectcomfort.com',

'DNT': '1',

'Origin': 'mytotalconnectcomfort.com/portal/',

'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 "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.cookiess"

}

}

def isLoggedIn() {

if(!data.auth) {

log.debug "No data.auth"

return false

}

def now = new Date().getTime();

return data.auth.expires_in > now

}

Thank you Robert for the help, found extra data regarding fan that I didn’t see before. Now it seems to work, have seen no errors. The one small problem is when I hit refresh and the fan is in circulate mode it changes to auto, but the fan is still in circulate. I checked by using the Honeywell app.

There’s a 4th fan mode for “follow schedule”. It’s value 3. It’s only accessible if the schedule actually contains different fan settings.

So what you saying if I am correct if you add a ST multi sensor you would interact with the Thermostat to say it was running like I do with the Temp/Hum sensor to adjust for temp drop in bedroom. And this screws things up by putting the thermostat in temp hold from 11:00 PM to 7:00AM just wish the API was just clear cut, bu Honeywell isn’t very forth coming.

Not sure what happened, but today I looked and it cleared itself.

Being a electrician adding a relay to the fan control board across the fan board output could give you a set of dry contacts, but how you would integrate that into the code I am lost. To me more important is the temp hold and the perm hold.

My idea would be to have the tile that has the Heat temp to have it say “Heat for normal schedule”, "Heat P "for Heat in Permanent hold and “Heat T” for temp hold. The same thing for the cooling. How that would get programmed that would be up to you guys that know how to code.

Yup

HeatNextPeriod/CoolNextPeriod determines the 15min interval of when the temporary schedule would end. For example, if statusCool/statusHeat=1 and CoolNextPeriod/HeatNextPeriod=69, that would indicate a temporary hold is in place until 5:15PM.

    // system switch enumeration - use with ModelProperty.systemSwitchPosition
    my.SystemSwitch = { EMHEAT: 0, HEAT: 1, OFF: 2, COOL: 3, AUTOHEAT: 4, AUTOCOOL: 5, SOUTHERN_AWAY: 6, UNKNOWN: 7 };

    // setpoint status enumeration - use with ModelProperty.statusHeat and ModelProperty.statusCool properties
    my.SetpointStatus = { SCHEDULED: 0, TEMPORARY: 1, HOLD: 2, VACATION_HOLD: 3 };

    // fan mode enumeration - use with ModelProperty.fanMode
    my.FanMode = { Auto: 0, On: 1, Circulate: 2, FollowSchedule: 3, Unknown: 4 };

1/4 = .25 or 00:15
30/4 = 7.5 or 07:30
69/4 = 17.25, or 17:15
95/4 = 23.75 or 23:45

Here’s my perl code to convert to normal time. It’s easy with sprintf’s. Hope you can adapt it or think of some logic. You could just delcare 0 = 00:00, 1=00:15, 2=00:30 … 95=23:45 . There’s only 96 to add. :smile:

$nexttime = 30;
$nexttime = $nexttime/4;  # 7.5
$nexttimehr = sprintf("%.02d", $nexttime);  # 07. sprintf converts the hours to 2 digit format and strips the decimals

# Subtract the hours to leave just the decimal places
$nexttimemn = $nexttime - $nexttimehr # .5
$nexttimemn = $nexttimemn * 4 * 15; # 30.  This converts .0 to 0, .25 to 15, .5 to 30, .75 to 45
$nexttimemn = sprintf("%.02d", $nexttimemn); # Sets 0 to 00

$time = "$nexttimehr:$nexttimemn";

Hello everyone, I can’t seem to get the thermostat tile to populate. Ive entered the username, password and device ID. I logged out and into the ST app to no avail. Any ideas on how to get this to work? I have three Honeywell wifi RTH9580WF that Im trying to integrate. Is there another code that I should have used. My understanding, the one listed below should have worked. Any help would be appreciated. I did read most of this tread. If i skimmed over the answer Im sorry.

This was the code I used.

( https://github.com/jodyalbritton/honeywell-rth6580wf/blob/master/thermostat.groovy )

@MDG

Hit refresh and check the activity feed. If you have items in the activity feed it is working and you need to log out of the ST app and back in. Sometimes the values are there and they don’t populate unless you force close the client.

It was that simple. Thank you. If its not painfully obvious, I’m new to ST.

This looks great, can I get a copy of your code?

Yes, i have the 9580. my address is Christopher@myblake.com

Honeywell has an API (https://rs.alarmnet.com/TotalConnectComfort/ws/MobileV2.asmx), but they only expose it to valid AppID’s (one is statically set in their iOS app). You can fuzz the SSL communications to pick it out; Honeywell can’t change this without breaking all older apps out there. It would work much more reliably (real sessions designed to be used by an App) if someone could convert it. I’ve done some work to get login/location info in Perl (https://github.com/bdwilson/redlink-api) but I don’t have the bandwidth to update these scripts. Ping me if you need hints regarding the AppID.

Hello - I have added the code to Device Types and added a honeywell device. It shows active on my account but the device does not show temp, etc in the ST app? Help