https Authentication help: Honeywell Total Comfort Connect

I’ve noticed that there currently is no Honeywell WiFi plug-in, although there does appear to be some demand for one…including from me since I own a couple of these thermostats! (Yes, these were purchased before I even knew what Vera or z-wave was…)

So I’m willing to put in the time to create a plug-in for it.

This topic has come up before. The lack of an officially published API doesn’t help (I have registered with Honeywell but have not yet had any response). From that thread, someone has succesfully authenticated with a python script.

My own experience with understanding any web authentication scheme comes from my minimal contribtions to the MyQ plugin. (My contributions had nothing to do with the i/o from the web server, I was just utilizing what was previously put in place.) In other words…I know essentially nothing.

I’ve started to try and educate myself…I built a shell of a Vera device based off the authetication code in the MyQ code (and scrutinizing the phyton code), but I have so far not been successful in getting any response from the total comfort web service.

So I’m requesting that if anyone who is experienced with this sort of thing and would like to help me out…I’d appreciate it. Once I get authentication up and running and get get basic i/o going I feel I can ultimately build out the rest of the plug-in by observing network traffic from their web app.

Anyone in a charitable mood, please respond or PM. :slight_smile:

For starters, here’s the relevant code in the python script for the first phase of authentication (not even sending user names, passwords yet…just to get an initial session token:

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" } conn = httplib.HTTPSConnection("rs.alarmnet.com") conn.request("GET", "/TotalConnectComfort/",None,headers) r0 = conn.getresponse()

And this is the related code I have in lua:

[code]
local auth_response = {}
local TCC_URL=“https://rs.alarmnet.com/TotalConnectComfort/
local response, status, header = https.request{
url = TCC_URL,
method = “GET”,
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”
},
sink = ltn12.sink.table(auth_response)
}

log("auth_response" .. dump(auth_response))
log("RESPONSE:" .. dump(response))
log("STATUS:" .. dump(status))
log("HEADER:" .. dump(header))[/code]

But I’m getting nothing in any of the return variables / tables.

I eventually gave up and I currently use Smart Things (Java/Groovy) to control all my Honeywell’s (Mini-splits). I’d love to see a plugin for Vera so hoping this gives you some information. Also going to provide some log data of the session itself, if you need anything specific that I’ve blocked then let me know via PM.

/**
* Total Comfort API
*
* 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 Connect Comfort", namespace: "Honeywell", author: "Eric Thomas") {
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}?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: "st.Outdoor.outdoor19"
state "cool", label:'${name}', action:"thermostat.heat", icon: "st.Weather.weather7", backgroundColor: '#003CEC'
state "heat", label:'${name}', action:"thermostat.auto", icon: "st.Weather.weather14", backgroundColor: '#E14902'
state "auto", label:'${name}', action:"thermostat.off", icon: "st.Weather.weather3", backgroundColor: '#44b621'
}
standardTile("thermostatFanMode", "device.thermostatFanMode", inactiveLabel: false, canChangeIcon: true) {
state "auto", label:'${name}', action:"thermostat.fanOn", icon: "st.Appliances.appliances11"
state "on", label:'${name}', action:"thermostat.fanAuto", icon: "st.Appliances.appliances11", backgroundColor: '#44b621'
}
controlTile("coolSliderControl", "device.coolingSetpoint", "slider", height: 3, width: 1, inactiveLabel: false) {
state "setCoolingSetpoint", label:'Set temperarure to', action:"thermostat.setCoolingSetpoint",
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"]
]
}
valueTile("coolingSetpoint", "device.coolingSetpoint", inactiveLabel: false)
{
state "default", label:'Cool @${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"]
]
}
valueTile("heatingSetpoint", "device.heatingSetpoint", inactiveLabel: false)
{
state "default", label:'Heat @${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"]
]
}
//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 "heating", label:'${name}'
state "cooling", label:'${name}'
state "idle", label:'${name}'
}
standardTile("refresh", "device.thermostatMode", inactiveLabel: false, decoration: "flat") {
state "default", action:"polling.poll", icon:"st.secondary.refresh"
}
standardTile("heatLevelUp", "device.heatingSetpoint", canChangeIcon: false, inactiveLabel: false) {
state "heatLevelUp", label:' ', action:"heatLevelUp", icon:"st.thermostat.thermostat-up"
}
standardTile("heatLevelDown", "device.heatingSetpoint", canChangeIcon: false, inactiveLabel: false) {
state "heatLevelDown", label:' ', action:"heatLevelDown", icon:"st.thermostat.thermostat-down"
}
standardTile("coolLevelUp", "device.heatingSetpoint", canChangeIcon: false, inactiveLabel: false) {
state "coolLevelUp", label:' ', action:"coolLevelUp", icon:"st.thermostat.thermostat-up"
}
standardTile("coolLevelDown", "device.heatingSetpoint", canChangeIcon: false, inactiveLabel: false) {
state "coolLevelDown", label:' ', action:"coolLevelDown", icon:"st.thermostat.thermostat-down"
}
main "temperature"
details(["temperature", "thermostatMode", "thermostatFanMode", "heatLevelUp", "heatingSetpoint" , "heatLevelDown", "coolLevelUp","coolingSetpoint", "coolLevelDown" ,"thermostatOperatingState", "refresh",])
}
}
def coolLevelUp(){
int nextLevel = device.currentValue("coolingSetpoint") + 1
if( nextLevel > 80){
nextLevel = 80
}
log.debug "Setting cool set point up to: ${nextLevel}"
setCoolingSetpoint(nextLevel)
}
def coolLevelDown(){
int nextLevel = device.currentValue("coolingSetpoint") - 1
if( nextLevel < 70){
nextLevel = 70
}
log.debug "Setting cool set point down to: ${nextLevel}"
setCoolingSetpoint(nextLevel)
}
def heatLevelUp(){
int nextLevel = device.currentValue("heatingSetpoint") + 1
if( nextLevel > 75){
nextLevel = 75
}
log.debug "Setting heat set point up to: ${nextLevel}"
setHeatingSetpoint(nextLevel)
}
def heatLevelDown(){
int nextLevel = device.currentValue("heatingSetpoint") - 1
if( nextLevel < 60){
nextLevel = 60
}
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 fanCirculate() {
setThermostatFanMode(2)
}
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(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 operatingState = "idle"
if(statusCool == 1 && switchPos == 3) {
operatingState = "cooling"
} else if (statusHeat == 1 && switchPos == 1) {
operatingState = "heating"
}
log.debug curTemp
log.debug fanMode
log.debug switchPos
//fan mode 0=auto, 2=circ, 1=on
if(fanMode==0)
fanMode = 'auto'
if(fanMode==1)
fanMode = 'on'
if(switchPos==1)
switchPos = 'heat'
if(switchPos==2)
switchPos = 'off'
if(switchPos==3)
switchPos = 'cool'
if(switchPos==4)
switchPos = 'auto'
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)
}
}
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': '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 "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
}
5:02:31 PM: debug 1
5:02:31 PM: debug null
5:02:31 PM: debug 70.0000
5:02:31 PM: debug Request was successful, 200
5:02:31 PM: debug https://mytotalconnectcomfort.com/portal/Device/CheckDataSession/[removed by Cuda]?_=[removed by Cuda]
5:02:31 PM: debug Executing 'getStatus'
5:02:31 PM: debug cookies: EccMyTcc=[removed by Cuda];ASP.NET_SessionId=[removed by Cuda];RememberMe=;.ASPXAUTH_TH_A=855F9ABF226D5E084C65B200B05EE451B5203E02709717A58FF11041E64A2AE4F4F74A4E2479A319EA1D674EFD27E9E339563E2AD0F6DA6B7B2F2050966F64E5583D72B0AF21F1C0FD0D415D41D1198497F22AC2A7DD388EB34E980A9366DAF351082B7404510FA5A2BA52B09B551C62CA9A9D05580894DBB16841F58C648BEA9BC8CF3340A28E5FC1EB4BC68CA1252C384F807A8F2B847BF7377D2A8DA9CED40A3735879D79D9848B8073645628E045BE73CA66262DDA96715163833872F994C7E74D8445A29D73A975D0CA43AB55F6E2E9530A37764C93E0B74BE174581FA9B57AF33766D4B027AAE7E4E136580B1739920019F63F4FC420C471ADD2898C11FA0BFF4C1DCDCE84CF0C95C2FC193FA39A56E21A3D360BB982A7C0E2851AB6F786EAAD52722F613CC923A1B959C0A0501BE60AB32E8F3073D332B272D66FA3D102AAD6B1;thlang=en-US;
5:02:31 PM: debug Adding cookie to collection: thlang=en-US
5:02:31 PM: debug Adding cookie to collection: .ASPXAUTH_TH_A=855F9ABF226D5E084C65B200B05EE451B5203E02709717A58FF11041E64A2AE4F4F74A4E2479A319EA1D674EFD27E9E339563E2AD0F6DA6B7B2F2050966F64E5583D72B0AF21F1C0FD0D415D41D1198497F22AC2A7DD388EB34E980A9366DAF351082B7404510FA5A2BA52B09B551C62CA9A9D05580894DBB16841F58C648BEA9BC8CF3340A28E5FC1EB4BC68CA1252C384F807A8F2B847BF7377D2A8DA9CED40A3735879D79D9848B8073645628E045BE73CA66262DDA96715163833872F994C7E74D8445A29D73A975D0CA43AB55F6E2E9530A37764C93E0B74BE174581FA9B57AF33766D4B027AAE7E4E136580B1739920019F63F4FC420C471ADD2898C11FA0BFF4C1DCDCE84CF0C95C2FC193FA39A56E21A3D360BB982A7C0E2851AB6F786EAAD52722F613CC923A1B959C0A0501BE60AB32E8F3073D332B272D66FA3D102AAD6B1
5:02:31 PM: debug Adding cookie to collection: RememberMe=
5:02:31 PM: debug Adding cookie to collection: ASP.NET_SessionId=[removed by Cuda]
5:02:31 PM: debug Adding cookie to collection: .ASPXAUTH_TH_A=
5:02:31 PM: debug Adding cookie to collection: EccMyTcc=[removed by Cuda]
5:02:31 PM: debug groovyx.net.http.HttpResponseDecorator$HeadersDecorator@55746056
5:02:31 PM: debug Request was successful, 302
5:02:30 PM: debug Executing 'login'
5:02:30 PM: debug Executing 'refresh'

Thanks, CudaNet…gives me something new to try…

Well, I guess I’m getting closer…

This is the current lua code:

local TCC_URL="https://mytotalconnectcomfort.com/portal?timeOffset=300&UserName=[my encoded user name]&Password=[my encoded password]&RememberMe=false" local response, status, header = https.request{ url = TCC_URL, method = "GET", 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" }, sink = ltn12.sink.table(auth_response) }

I’m getting a status value now…but 200 (OK) instead of 302 (Found).

I tried using method = “POST”, but then I got a 411 status (requesting content length). Not sure how to calculate that, but based on network traffic from my web app login, the Content-Length = 75…so I added [“Content-Length”] = “75” to the header record and tried POST method, but that produces no response (seemed to time out after 2 minutes)

Yes, you’ll have to calculate the content length of the request body of your POST ? Like this for example:

     local req= "data your going to POST"

     local bodyparts = {}
     local x, status, headers = http.request {
                           method = "POST",
                           url = url,
                           ["Content-Type"]   = "application/x-www-form-urlencoded",
                           ["Content-Length"] = string.len(req),
                           source = ltn12.source.string(req),
                           sink = ltn12.sink.table(bodyparts)
                           }

[quote=“JoeyD, post:5, topic:185223”]Well, I guess I’m getting closer…

This is the current lua code:

local TCC_URL="https://mytotalconnectcomfort.com/portal?timeOffset=300&UserName=[my encoded user name]&Password=[my encoded password]&RememberMe=false" local response, status, header = https.request{ url = TCC_URL, method = "GET", 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" }, sink = ltn12.sink.table(auth_response) }

I’m getting a status value now…but 200 (OK) instead of 302 (Found).

I tried using method = “POST”, but then I got a 411 status (requesting content length). Not sure how to calculate that, but based on network traffic from my web app login, the Content-Length = 75…so I added [“Content-Length”] = “75” to the header record and tried POST method, but that produces no response (seemed to time out after 2 minutes)[/quote]

Alright…I think we’ve made it past step 1!

Your last post that showed the source as where to put the body of the post is what did it for me. That, and NOT encoding the body (I guess since it’s not in the URL itself) and I have now received a 302 response. At the end of this post is the successful lua.

So based on your original code the next step is to extract the cookie from the auth_response table to be utilized for subsequent requests. I’ll muddle through that in a most likely very inefficient (but in the end effective) way. :slight_smile:

I greatly appreciate your patience and assistance! I’ll report back again after additional progress (or if I get stuck).

    local auth_response = {}
    local TCC_URL="https://mytotalconnectcomfort.com/portal"
    local theBody = "timeOffset=300&UserName=[user name]&Password=[password]&RememberMe=false"
    local conLen = string.len(theBody)
   local response, status, header = https.request{
        url = TCC_URL,
        method = "POST",
        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",
	    ["Content-Length"] = "" .. tostring(conLen)
        },
	source = ltn12.source.string(theBody),
        sink = ltn12.sink.table(auth_response)
    }

@joeyD thanks for taking this on. I’m looking forward to the plugin.
I have allot of scripting experience. (not in lua)
so I’m not sure I’ll be of any help. but i can test and will review your code to see if i can offer any insight.

Good deal, let me know if you need anything as I’m bouncing around working on my Blue Iris server…

quote author=JoeyD link=topic=29938.msg212644#msg212644 date=1420861534]
Alright…I think we’ve made it past step 1!

Your last post that showed the source as where to put the body of the post is what did it for me. That, and NOT encoding the body (I guess since it’s not in the URL itself) and I have now received a 302 response. At the end of this post is the successful lua.

So based on your original code the next step is to extract the cookie from the auth_response table to be utilized for subsequent requests. I’ll muddle through that in a most likely very inefficient (but in the end effective) way. :slight_smile:

I greatly appreciate your patience and assistance! I’ll report back again after additional progress (or if I get stuck).

    local auth_response = {}
    local TCC_URL="https://mytotalconnectcomfort.com/portal"
    local theBody = "timeOffset=300&UserName=[user name]&Password=[password]&RememberMe=false"
    local conLen = string.len(theBody)
   local response, status, header = https.request{
        url = TCC_URL,
        method = "POST",
        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",
	    ["Content-Length"] = "" .. tostring(conLen)
        },
	source = ltn12.source.string(theBody),
        sink = ltn12.sink.table(auth_response)
    }

[/quote]

Since you asked… :wink:

I’m at a point now where I can successfully “get status.” So that of course implies that I have also successfully derived the required cookie from the results of the login. At least that’s progress!

The final hurdle is trying to post a change to the thermostat settings. At this point I’m making the post, but I’m getting a result of 500 (internal server error). I’m pretty sure my hang-up is with the payload / body and how I need to construct that in lua when I construct the http.request.

I have constructed the variable theBody as a string:

{"DeviceID":[6 digit device ID],"SystemSwitch":null,"HeatSetpoint":78,"CoolSetpoint":null,"HeatNextPeriod":null,"CoolNextPeriod":null,"StatusHeat":null,"StatusCool":null,"FanMode"=null}

So I don’t know enough about HTTP / sources and lua to know if that is the valid construction of the body. Anyone have any ideas?

Here’s the snippet of my code:

local response, status, header = https.request{ url = "https://mytotalconnectcomfort.com/portal/Device/SubmitControlScreenChanges", method = "POST", 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/" .. T_DEVICE_ID, ["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"] = LoginCookie, ["Content-Length"] = "" .. tostring(conLen) }, source = ltn12.source.string(theBody), sink = ltn12.sink.table(auth_response) }

First thing that jumps out is the “FanMode”=1. I don’t think that is valid json. I just completed the same plugin last night and am at the same point of getting status. The TC code was very helpful in getting this figured out.

Thanks, mikee…yup I goofed there! It’s been corrected but I’m still getting the 500 (internal server error) result.

your content-length seems to be a cut-n-paste from your login code. I think you want the use ‘#theBody’.

No, that’s not it…(I determined the value of conLen (#theBody) in code prior to the posted snippet).

This will be a quick reply so hopefully it will make sense… When posting a JSON body it typically looks something like this…

local json = require "json"
local req  = json.encode({name="cuda"})

The POST will contain the folowing body

{“name”:“cuda”}

Will check back later tonight when I get back…

Thanks…I’ll give that a try later tonight!

This snippet seems to work. I added content-type: application/json and my 500 errors went away …

req = {
[“DeviceID”] = tonumber(device_id),
[“SystemSwitch”] = nil,
[“HeatSetpoint”] = 56,
[“CoolSetpoint”] = nil,
[“HeatNextPeriod”] = nil,
[“StatusHeat”] = nil,
[“StatusCool”] = nil,
[“FanMode”] = nil,
}

body = json.encode(req)
error(body)

local res, code, header, status = https.request {
method = “POST”,
[“url”] = URL … “Device/SubmitControlScreenChanges”,
headers = {
[“accept”] = “application/json, text/javascript, .; q=0.01”,
[“accept-encoding”] = “gzip,deflate,sdch”,
[“accept-language”] = “en-US, en, q=0.8”,
[“cache-control”] = “max-age=0”,
[“connection”] = “keep-alive”,
[“host”] = HOST,
[“dnt”] = “1”,
[“referer”] = URL … “Device/Control/” … device_id,
[“x-requested-with”] = “XMLHttpRequest”,
[“user-agent”] = USER_AGENT,
[“content-type”] = “application/json”,
[“content-length”] = #body,
[“cookie”] = cookie_string,
},
source = body_source,
}

Thanks, mikee! Between that and CudaNet’s post, that should pretty much do it. I’ll be able to resume my own work in this in another hour or so.

Quick quesition…you have source=body_source. Is that supposed to be body…or did you operate on body?

Yeah, I didn’t know about ltn12.source.string so I implemented a simple one. body_source just takes the body and returns it on the first call, nill after that, same as ltn12.source.string(body). I’ll replace it with ltn12.source.string. Thanks for the pointer.

Subscribed