extending a Lennox Icomfort Thermostat: Part 1

See part 2 in my investigation

I recently had installed a new air conditioning system and it came with a new Lennox iComfort Thermostat that talks to the cloud over a wifi connection. After spending a little time looking at messages that get passed back and forward from chrome to the cloud service using the chrome developer tool, it looks like a fairly straight forward sort of REST api.

I managed to get some ruby code that log in, then queries the status of the the thermostat.

The authentication is a pain, since you log into the website using an ASP.NET web application, so it needs the __VIEWSTATE and the __EVENTVALIDATION form fields to be present. I have hardcoded them for the moment, but intended to pull them out of the of the response on the first page. I should set up a proxy and see if the android app uses a simpler API for authenticating.

Once authenticated you can send a JSON message like

{"hidden_gateway_SN":"XXXXXX",
"pref_temp_units":"0",
"userid":"XXXXXX",
"Central_Zoned_Away":"2",
"Cancel_Away":"-1",
"current_prg":"2",
"current_mode":"1",
"CurrentBrowser":"chrome",
"zoneNumber":"0",
"Current_Thermostat":"0",
"alertViewTypes":"0",
"alertTypes":"1",
"reminderTypes":"0"}

and you get back

{"Equipment_List"=>
  [{"SystemID"=>XXXXXX,
    "Friendly_Name"=>"Air Handler",
    "Device_Descriptor"=>XXXXXXX,
    "Control_Model_Nbr"=>"Equipment Interface Module ? AHC",
    "Control_Serial_Number"=>"XXXX-xxxxxx",
    "ReturnStatus"=>nil},
   {"SystemID"=>XXXXXX,
    "Friendly_Name"=>"Thermostat",
    "Device_Descriptor"=>XXXXXX,
    "Control_Model_Nbr"=>"103445-04",
    "Control_Serial_Number"=>"XXXXXXXX",
    "ReturnStatus"=>nil},
   {"SystemID"=>72204,
    "Friendly_Name"=>"Wi-Fi Controller",
    "Device_Descriptor"=>XXXXXX,
    "Control_Model_Nbr"=>"GS1500M",
    "Control_Serial_Number"=>"Wi-XXXXXXXXX",
    "ReturnStatus"=>nil}],
 "AlertInfo_List"=>[],
 "TStatInfo_List"=>
  [{"GatewaySN"=>"XXXXXXXX",
    "Zone_Number"=>0,
    "Zone_Name"=>"Zone 1",
    "Program_Schedule_Selection"=>0,
    "Schedule_Name"=>"0|summer^1|winter^2|spring fall^3|save energy^4|custom",
    "Program_Schedule_Mode"=>"0",
    "Away_Mode"=>0,
    "Pref_Temp_Units"=>"0",
    "DateTime_Mark"=>"/Date(1404941631787)/",
    "Zone_Enabled"=>1,
    "System_Status"=>0,
    "Indoor_Temp"=>74.0,
    "Indoor_Humidity"=>40,
    "Operation_Mode"=>2,
    "Heat_Set_Point"=>57.0,
    "Cool_Set_Point"=>74.0,
    "Fan_Mode"=>2,
    "Golden_Table_Updated"=>true,
    "ConnectionStatus"=>"GOOD",
    "Central_Zoned_Away"=>2,
    "Zones_Installed"=>1,
    "GMT_To_Local"=>-18000}],
 "current_GatewayInfo"=>
  {"SystemID"=>"XXXXX",
   "Pref_Language_Nbr"=>0,
   "Pref_Temp_Unit"=>"0",
   "Cool_Set_Point_High_Limit"=>99.0,
   "Cool_Set_Point_Low_Limit"=>60.0,
   "Heat_Set_Point_High_Limit"=>90.0,
   "Heat_Set_Point_Low_Limit"=>40.0,
   "Heat_Cool_Dead_Band"=>3.0,
   "Daylight_Savings_Time"=>1,
   "ReturnStatus"=>"SUCCESS"},
 "program_List"=>
  {"00"=>
    {"GatewaySN"=>"XXXXXXXXX",
     "ScheduleNum"=>0,
     "Zone_Number"=>0,
     "Heat_Set_Point"=>68.0,
     "Cool_Set_Point"=>78.0,
     "Fan_Mode"=>0,
     "Central_Zoned_Away"=>0,
     "ReturnStatus"=>"SUCCESS"},
   "01"=>
    {"GatewaySN"=>"XXXXXXXX",
     "ScheduleNum"=>0,
     "Zone_Number"=>0,
     "Heat_Set_Point"=>20,
     "Cool_Set_Point"=>25.5,
     "Fan_Mode"=>0,
     "Central_Zoned_Away"=>0,
     "ReturnStatus"=>"SUCCESS"},
   "10"=>
    {"GatewaySN"=>"XXXXXXXX",
     "ScheduleNum"=>1,
     "Zone_Number"=>0,
     "Heat_Set_Point"=>68.0,
     "Cool_Set_Point"=>78.0,
     "Fan_Mode"=>0,
     "Central_Zoned_Away"=>0,
     "ReturnStatus"=>"SUCCESS"},
   "11"=>
    {"GatewaySN"=>"XXXXXXXXX",
     "ScheduleNum"=>1,
     "Zone_Number"=>0,
     "Heat_Set_Point"=>20,
     "Cool_Set_Point"=>25.5,
     "Fan_Mode"=>0,
     "Central_Zoned_Away"=>0,
     "ReturnStatus"=>"SUCCESS"},
   "20"=>
    {"GatewaySN"=>"XXXXXXXX",
     "ScheduleNum"=>2,
     "Zone_Number"=>0,
     "Heat_Set_Point"=>50.0,
     "Cool_Set_Point"=>78.0,
     "Fan_Mode"=>2,
     "Central_Zoned_Away"=>0,
     "ReturnStatus"=>"SUCCESS"},
   "21"=>
    {"GatewaySN"=>"XXXXXXXX",
     "ScheduleNum"=>2,
     "Zone_Number"=>0,
     "Heat_Set_Point"=>10,
     "Cool_Set_Point"=>25.5,
     "Fan_Mode"=>2,
     "Central_Zoned_Away"=>0,
     "ReturnStatus"=>"SUCCESS"},
   "30"=>
    {"GatewaySN"=>"XXXXXXXX",
     "ScheduleNum"=>3,
     "Zone_Number"=>0,
     "Heat_Set_Point"=>63.0,
     "Cool_Set_Point"=>76.0,
     "Fan_Mode"=>0,
     "Central_Zoned_Away"=>0,
     "ReturnStatus"=>"SUCCESS"},
   "31"=>
    {"GatewaySN"=>"XXXXXXXX",
     "ScheduleNum"=>3,
     "Zone_Number"=>0,
     "Heat_Set_Point"=>17,
     "Cool_Set_Point"=>24,
     "Fan_Mode"=>0,
     "Central_Zoned_Away"=>0,
     "ReturnStatus"=>"SUCCESS"},
   "40"=>
    {"GatewaySN"=>"XXXXXXX",
     "ScheduleNum"=>4,
     "Zone_Number"=>0,
     "Heat_Set_Point"=>67.0,
     "Cool_Set_Point"=>72.0,
     "Fan_Mode"=>0,
     "Central_Zoned_Away"=>0,
     "ReturnStatus"=>"SUCCESS"},
   "41"=>
    {"GatewaySN"=>"XXXXXXX",
     "ScheduleNum"=>4,
     "Zone_Number"=>0,
     "Heat_Set_Point"=>19,
     "Cool_Set_Point"=>22,
     "Fan_Mode"=>0,
     "Central_Zoned_Away"=>0,
     "ReturnStatus"=>"SUCCESS"}},
 "CurrentAvailableZones"=>1,
 "Zones_Installed"=>1,
 "GatewaySN"=>"XXXXXXX",
 "ZoneNumber"=>0,
 "CurrentZoneName"=>"Zone 1",
 "ServerUpdated"=>true,
 "CurrentDate"=>"Jul-09-2014 09:33:51 PM",
 "Central_Zoned_Away"=>2,
 "Indoor_Temp"=>"74",
 "fraction_Temp"=>"00",
 "Indoor_Humidity"=>"40",
 "Heat_Set_Point"=>"57",
 "Cool_Set_Point"=>"74",
 "Fan_Mode"=>2,
 "Away_Mode"=>0,
 "Operation_Mode"=>2,
 "Pref_Temp_Units"=>0,
 "System_Status"=>"idle",
 "System_Name"=>"my icomfort system",
 "ConnectionStatus"=>"GOOD",
 "Program_Schedule_Selection"=>0,
 "Program_Schedule_Mode"=>"0",
 "manualSetting"=>
  "cool only,2&heat only,1&heat or cool,3&off,0&emergency heat,4&"}

As you can see lots of useful information. Short term I am planning on collecting stats about how long it takes the system to cool and how long it is running. The cloud site only updates when there is a change event, so the actual data that will be written is fairly small. Long term I want to be able to hook it into my Home Automation system, which is currently OpenHab. Depending on how far I get I will post my ruby code online, but nothing I am doing is hard to replicate.

 

A request came in for the sample ruby code i wrote, I originally didn’t want to post this, since this was just something I hacked together. You will need to need to log into the icomfort website first, then use a tool like the gooogle chrome developer tools to grab the __VIEWSTATE, __EVENTVALIDATION cookie, and update ctl00$RightContent$txtUserName, ctl00$RightContent$txtPwd it, hidden_gateway_SN, userid. Again apologies in advance for my hacked together example.

require 'rest_client'
require 'json'
require 'uri'
require 'pp'

#RestClient.proxy = "http://localhost:8008"

response3 = RestClient.get 'https://www.myicomfort.com/Default.aspx'
response3.cookies
# => {"_applicatioN_session_id" => "1234"}
p response3.cookies
#p response3
@session_cookies = response3.cookies

response2 = RestClient.post(
  'https://www.myicomfort.com/Default.aspx',
  {
    "__LASTFOCUS" => "",
    "__EVENTTARGET=" => "",
    "__EVENTARGUMENT=" => "",
#    "__VIEWSTATE" => "",
    "__VIEWSTATE" => '<REPLACE WITH YOUR VEIW STATE>',
#    "__EVENTVALIDATION" => '',
    "__EVENTVALIDATION" => '<REPLACE WITH YOUR EVENTVALIDATION>',
    'ctl00$RightContent$hdnPwd' => "",
    'ctl00$RightContent$txtUserName' => "<REPLACE WITH YOUR USERNAME>",
    'ctl00$RightContent$txtPwd' => "<REPLACE WITH YOUR PASSWORD>",
    'ctl00$RightContent$chkRemember' => "on",
    'ctl00$RightContent$btnLogin' => "Log in"},
  {:cookies => @session_cookies}
) { |response, request, result, &block|
  if [301, 302, 307].include? response.code
    p response.cookies
    @session_cookies = response.cookies
    #response.follow_redirection(request, result, &block)
    response
  else
    p response
    response.return!(request, result, &block)
  end
}
#p response2.cookies
#p response2.code
p @session_cookies


my_hash = { 
:hidden_gateway_SN =>"<REPLACE WITH YOUR thermostat serial number>",
:pref_temp_units => "0",
:userid => "<REPLACE WITH YOUR USERNAME>",
:Central_Zoned_Away => "2",
:Cancel_Away => "-1",
:current_prg => "2",
:current_mode => "1",
:CurrentBrowser =>"chrome",
:zoneNumber => "0",
:Current_Thermostat=>"0",
:alertViewTypes=>"0",
:alertTypes => "1",
:reminderTypes => "0"}

myjson = JSON.generate(my_hash)

p "CurrentDate,Cool_Set_Point,Indoor_Temp,Fan_Mode,Indoor_Humidity,System_Status"
lastupdate=0

while 1 do
  response4 = RestClient.post(
    'https://www.myicomfort.com/Dashboard.aspx/NewGetManualInfo',
    myjson,
    {:content_type => :json, :accept => :json, :cookies => @session_cookies} )  
  #{ |response, request, result, &block|
  #  if [301, 302, 307].include? response.code
  #    p response.cookies
  #    @session_cookies = response.cookies
  #    #response.follow_redirection(request, result, &block)
  #    response
  #  else
  #    p response
  #    response.return!(request, result, &block)
  #  end
  #}

  json_object = JSON.parse(response4)
  d = JSON.parse(json_object['d'])
#  pp d
#  if #{lastupdate} != #{d['CurrentDate']}
   if lastupdate != d['CurrentDate']
    lastupdate = d['CurrentDate']
    p "#{d['CurrentDate']},#{d['Cool_Set_Point']},#{d['Indoor_Temp']},#{d['Fan_Mode']},#{d['Indoor_Humidity']},#{d['System_Status']}"
  end
  sleep 1
end

4 thoughts on “extending a Lennox Icomfort Thermostat: Part 1

  1. Tom

    Thanks for posting this. I’m trying to figure out how this works so I can try and get this integrated into an automation system. Any chance you can provide more details on the ruby code and getting the authentication information the system is using?

    Reply
    1. Ian Macdonald Post author

      Hi, I posted the very hacky code I was using to play with the icomfort website. I hoped to be able to go back and make a nicer version but I haven’t had a chance yet.

      Reply
  2. chudilo

    Does making this call wake up the thermostat? I am also interested in making a logger of some sort. In order to help determine if i can optimise the operation .
    So for example: I set the temp to be lower or higher (depending on the season) when we are at work. But, back to desired temp right before we come back. Would love to know how long it takes for it to change the temp, and hopefully track what it had to do to get there. Meaning did it just use the heatpump or was the furnace involved.
    That type of stuff.

    Reply
    1. Ian Macdonald Post author

      So my example code is talking to icomfort web site, not directly to the thermostat, so it should be possible to post a temp change to the website, then when the thermostat next polls the website it will pick up the change request. just manually playing with the app on my android phone it took a little while to update the thermostat, it was a little while back so I forget the exact time but i think it was seconds rather than minutes.

      One other thing that I just did was add anoutside thermostat to my system, I have a non communicating heatpump so it can’t get the outside temp from the heatpump as it would with more expensive communicating heatpumps. So now I have the ability to lock out the aux heat if the temperature is above 40’F. That way if I choose to bump the temperature up and down manually on the thermostat it wont use the AUX heat, (which costs about 4 time the cost of heatpump to run).

      Reply

Leave a Reply to Ian Macdonald Cancel reply

Your email address will not be published. Required fields are marked *