Venstar T5800... Really stuck on my first Device-Type


(Convinced ST will never be unbroken…) #1

Hey All,

I am trying to write a device-type for a Venstar T-5800, and don’t seem to be getting a response from the thermostat.

If I “curl http://10.0.0.31:80/query/info” I get back the following response:

{"name":"Upstairs","mode":0,"state":0,"fan":0,"fanstate":0,"tempunits":0,"schedule":0,"schedulepart":255,"away":0,"spacetemp":73.0,"heattemp":50.0,"cooltemp":78.0,"cooltempmin":35.0,"cooltempmax":99.0,"heattempmin":35.00,"heattempmax":99.0,"setpointdelta":4.0,"availablemodes":1}

But when I call the get from my device-type I get nothing. Here is the code in question:

def parse(String description) {
	log.debug "Parsing '${description}'"
}

private getapi() {
  log.debug("Executing get api to " + getHostAddress())
  def uri = "/query/info"
  def hubAction = new physicalgraph.device.HubAction(
    method: "GET",
    path: uri,
    headers: [HOST:getHostAddress()]
  )
  hubAction
}

def refresh() {
	log.debug "Executing 'refresh'"
	getapi()
}

When I execute a refresh in the sim, this is what is returned in console:

8:43:32 AM: debug Executing get api to 10.0.0.31:80
8:43:32 AM: debug Executing 'refresh'

I even tried pointing the IP to my Philips Hue bridge, but “parse” is not triggered in this case either.

However, if I swap out the host IP to a simple webserver running on my LAN, console returns the following:

8:48:43 AM: debug Parsing 'index:01, mac:0016CBA27305, ip:0A00000C, port:1F90, headers:SFRUUC8xLjEgMjAwIE9LDQpDb250ZW50LVR5cGU6IHRleHQvaHRtbA0KQ29udGVudC1MZW5ndGg6IDA=, body:'
8:48:43 AM: debug Executing get api to 10.0.0.12:8080
8:48:43 AM: debug Executing 'refresh'

So “parse” is detecting a response in this case. I must be missing something really simple here but I have no idea what it may be.

Anybody know?


Suggest a Device
Better (different) Thermostat Device
(Ben Edwards) #2

Thanks for sharing this Scott. Is this in GitHub yet? Could you share your full code? Thanks.


(Convinced ST will never be unbroken…) #3

Hi Ben,

Code is below. Commands are not working yet as the Venstar API requires set points to be passed along with any mode change (else it returns an error), ala “mode=1&fan=0&heattemp=50&cooltemp=78”. But I can’t really go forward as I can’t seem to get a response from the Venstar from the hubAction.

 import groovy.json.JsonSlurper
/**
 *  Venstar T5800
 *
 *  Copyright 2014 Scottin Pollock
 *
 *  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.
 *
 */
metadata {
	definition (name: "Venstar T5800", namespace: "Thermostat", author: "Scottin Pollock") {
		capability "Polling"
		capability "Thermostat"
		capability "Refresh"
		capability "Temperature Measurement"
		capability "Sensor"
	}

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

	tiles {
    	valueTile("temperature", "device.temperature", width: 2, height: 2) {
			state("temperature", label:'${currentValue}°', 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("mode", "device.thermostatMode", inactiveLabel: false, decoration: "flat") {
			state "off", label:'${name}', action:"thermostat.setThermostatMode"
			state "cool", label:'${name}', action:"thermostat.setThermostatMode"
			state "heat", label:'${name}', action:"thermostat.setThermostatMode"
		}
		standardTile("fanMode", "device.thermostatFanMode", inactiveLabel: false, decoration: "flat") {
			state "fanAuto", label:'${name}', action:"thermostat.setThermostatFanMode"
			state "fanOn", label:'${name}', action:"thermostat.setThermostatFanMode"
		}
    	controlTile("heatSliderControl", "device.heatingSetpoint", "slider", height: 1, width: 2, inactiveLabel: false) {
			state "setHeatingSetpoint", action:"thermostat.setHeatingSetpoint", backgroundColor:"#d04e00"
		}
		valueTile("heatingSetpoint", "device.heatingSetpoint", inactiveLabel: false, decoration: "flat") {
			state "heat", label:'${currentValue}° heat', unit:"F", backgroundColor:"#ffffff"
		}
		controlTile("coolSliderControl", "device.coolingSetpoint", "slider", height: 1, width: 2, inactiveLabel: false) {
			state "setCoolingSetpoint", action:"thermostat.setCoolingSetpoint", backgroundColor: "#1e9cbb"
		}
		valueTile("coolingSetpoint", "device.coolingSetpoint", inactiveLabel: false, decoration: "flat") {
			state "cool", label:'${currentValue}° cool', unit:"F", backgroundColor:"#ffffff"
		}
		standardTile("refresh", "device.temperature", inactiveLabel: false, decoration: "flat") {
			state "default", action:"refresh.refresh", icon:"st.secondary.refresh"
		}
		main "temperature"
        details(["temperature", "mode", "fanMode", "heatSliderControl", "heatingSetpoint", "coolSliderControl", "coolingSetpoint", "refresh"])
	}
}

// parse events into attributes
def parse(String description) {
	log.debug "Parsing '${description}'"
    def map = [:]
    def retResult = []
    def descMap = parseDescriptionAsMap(description)
    log.debug "parse returns $descMap"
    def body = new String(descMap["body"].decodeBase64())
    def slurper = new JsonSlurper()
    def result = slurper.parseText(body)
    log.debug "json is: $result"
    if (result.containsKey("success")){
    	//Do nothing as nothing can be done. (runIn doesn't appear to work here and apparently you can't make outbound calls here)
        log.debug "returning now"
        return null
    }
    if (result.containsKey("mode")){
    	def mode = getModeMap()[result.mode]
        if(device.currentState("thermostatMode")?.value != mode){
            retResult << createEvent(name: "thermostatMode", value: mode)
        }
    }
    if (result.containsKey("fan")){
    	def fan = getFanModeMap()[result.fan]
        if (device.currentState("thermostatFanMode")?.value != fan){
            retResult << createEvent(name: "thermostatFanMode", value: fan)
        }
    }
    if (result.containsKey("cooltemp")){
    	def cooltemp = getTemperature(result.cooltemp)
    	if (device.currentState("coolingSetpoint")?.value != cooltemp.toString()){
            retResult << createEvent(name: "coolingSetpoint", value: cooltemp)
        }
    }
    if (result.containsKey("heattemp")){
    	def heattemp = getTemperature(result.heattemp)
    	if (device.currentState("heatingSetpoint")?.value != heattemp.toString()){
            retResult << createEvent(name: "heatingSetpoint", value: heattemp)
        }
    }
    if (result.containsKey("spacetemp")){
    	def temp = getTemperature(result.spacetemp)
    	if (device.currentState("temperature")?.value != spacetemp.toString()){
            retResult << createEvent(name: "temperature", value: spacetemp)
        }        
    }

    log.debug "Parse returned $retResult"
    if (retResult.size > 0){
		return retResult
    } else {
    	return null
    }

}
def poll() {
	log.debug "Executing 'poll'"
	sendEvent(descriptionText: "poll keep alive", isStateChange: false)  // workaround to keep polling from being shut off
	refresh()
}
def modes() {
	["off", "heat", "cool"]
}
def parseDescriptionAsMap(description) {
	description.split(",").inject([:]) { map, param ->
		def nameAndValue = param.split(":")
		map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
	}
}

def getModeMap() { [
	0:"off",
	2:"cool",
	1:"heat",
]}

def getFanModeMap() { [
	0:"fanAuto",
    1:"fanOn"
]}
def getTemperature(value) {
	if(getTemperatureScale() == "C"){
		return (((value-32)*5.0)/9.0)
	} else {
		return value
	}
}

// handle commands THINK WE NEED A STATE TO HOLD FAN,HEATTEMP,COOLTEMP VALUES
def setHeatingSetpoint(degrees) {
	def degreesInteger = degrees as Integer
	log.debug "Executing 'setHeatingSetpoint' with ${degreesInteger}"
    postapi("{\"it_heat\":${degreesInteger}}")
}

def setCoolingSetpoint(degrees) {
	def degreesInteger = degrees as Integer
	log.debug "Executing 'setCoolingSetpoint' with ${degreesInteger}"
    postapi("{\"it_cool\":${degreesInteger}}")
}

def off() {
	log.debug "Executing 'off'"
    postapi('{"tmode":0}')
}

def heat() {
	log.debug "Executing 'heat'"
	postapi('{"tmode":1}')
}

def cool() {
	log.debug "Executing 'cool'"
	postapi('{"tmode":2}')
}

def setThermostatMode() {
	log.debug "switching thermostatMode"
	def currentMode = device.currentState("thermostatMode")?.value
	def modeOrder = modes()
	def index = modeOrder.indexOf(currentMode)
	def next = index >= 0 && index < modeOrder.size() - 1 ? modeOrder[index + 1] : modeOrder[0]
	log.debug "switching mode from $currentMode to $next"
	"$next"()
}

def fanOn() {
	log.debug "Executing 'fanOn'"
	postapi('{"fmode":2}')
}

def fanAuto() {
	log.debug "Executing 'fanAuto'"
	postapi('{"fmode":0}')
}

def setThermostatFanMode() {
	log.debug "Switching fan mode"
	def currentFanMode = device.currentState("thermostatFanMode")?.value
	log.debug "switching fan from current mode: $currentFanMode"
	def returnCommand

	switch (currentFanMode) {
		case "fanAuto":
			returnCommand = fanOn()
			break
        case "fanOn":
			returnCommand = fanAuto()
			break
	}
	if(!currentFanMode) { returnCommand = fanAuto() }
	returnCommand
}

def auto() {
	log.debug "Executing 'auto'"
	postapi('{"tmode":3}')
}
def refresh() {
	log.debug "Executing 'refresh'"
	getapi()
}

private getapi() {
  log.debug("Executing get api to " + getHostAddress())
  def uri = "/"
  def hubAction = new physicalgraph.device.HubAction(
    method: "GET",
    path: uri,
    headers: [HOST:getHostAddress()]
  )
  hubAction
}
private postapi(command) {
	log.debug("Executing ${command}")
	def uri = "/control"
	def hubAction = [new physicalgraph.device.HubAction(
		method: "POST",
		path: uri,
		body: command,
		headers: [Host:getHostAddress(), "Content-Type":"application/x-www-form-urlencoded" ]
		), delayAction(1000), refresh()]
	hubAction
}

//helper methods
private delayAction(long time) {
	new physicalgraph.device.HubAction("delay $time")
}
private Integer convertHexToInt(hex) {
	Integer.parseInt(hex,16)
}
private String convertHexToIP(hex) {
	[convertHexToInt(hex[0..1]),convertHexToInt(hex[2..3]),convertHexToInt(hex[4..5]),convertHexToInt(hex[6..7])].join(".")
}

private getHostAddress() {
	def parts = device.deviceNetworkId.split(":")
	def ip = convertHexToIP(parts[0])
	def port = convertHexToInt(parts[1])
	return ip + ":" + port
}

Local POST Request for Device Type Help
(Adam Lanners) #4

Hey Scott,

I spent some time looking at the logs tonight and the good news is that I can see those requests and the appropriate response coming back from the Venstar T-5800. The question is why your device isn’t being found and the parse method not being called.

The first thing we do is look up a device by the dni in the response in this case that would be 0A00001F:0050.

Can you double check that the dni for that specific device is actually 0A00001F:0050? I noticed it is currently 0A00000C:1F90 which would be the 10.0.0.12:8080 test web server.

Try updating the dni to 0A00001F:0050 again and performing a GET and see if there’s any change. If you are still stuck let me know and we can schedule some time to debug while you’re testing.

Thanks,
Adam


(Convinced ST will never be unbroken…) #5

Thanks Adam…

I reset the device’s dni to 0A00001F:50 and got nothing. Then a little light came on as I noticed your response was “Try updating the dni to 0A00001F:0050”; added the 2 leading zeroes to the port and BINGO!

Odd that getHostAddress() was all the time returning the correct IP and port, but that I wasn’t getting a response without those leading zeroes. Thanks again… you have saved me considerable tooth enamel.

-sip


Raspberry Pi Device Type
(Convinced ST will never be unbroken…) #6

Success. I now have two Venstar T5800’s working in SmartThings. Not all features are supported yet, but the basics (mode, fan control, heat and cool setpoints) are there.

Thanks guys. Next I’ll take a whack at auto discovery.


(Adam Lanners) #7

That’s awesome Scott!

Excited to see another thermostat integrated on the SmartThings platform.


(Ben Edwards) #8

@scottinpollock — When you feel it’s ready you can publish this publicly. It is a nice looking device.


(Convinced ST will never be unbroken…) #9

@Ben

Sorry Ben, I’ve had a look, and a service manager is over my head given the current state of the documentation and examples. Someone else will have to tackle that part of it.


(DJinWI) #10

Hey all,

I just installed copy-ninja’s MyQ garage door opener devices and Smartapps so I don’t have to use the single-purpose app to manage my home’s MQ/Liftmaster setup. Now I’m ready to likewise fold my Venstar T5800 controls into Smartthings. Has the one from this thread been published yet?


(Jordan) #11

Hi @scottinpollock, I know nothing about all the coding and all above, but I also have a venstar that I would like to use with smarthings. Would you be willing to send me dummy instructions on how to get it to work?


(Jordan) #12

For anyone following along, after lots of internet searching I found a few key details… Im a newbie at this so forgive me if this is all common sense.

the Device Network ID should be in the form of HEX(IP) : HEX(PORT). Not the full IP:PORT converted to hex, but the IP converted, with a colon, with the PORT converted. But, this isn’t just a hex conversion, apparently IP addresses needs a special conversion. I used this site:
http://www.miniwebtool.com/ip-address-to-hex-converter/?ip=10.0.1.40

Next, used a regular decimal to hex conversion for the PORT, for example PORT 80 in HEX is 50.

Finally, my Venstar had hold firmware, I had to upgrade to version 4.08 from the manuf site. This is really important because you have to TURN ON THE LOCAL API, nothing in the above threads talked about that. If you don’t allow the thermostat to take local network commands this will never work. The option to turn that on, via the thermostats on-screen menus is Menu-Accessories-Local API.

You can test this is working by in a terminal window typing:
curl http://10.0.1.21:80/query/info

Sub in whatever your IP address and Port are obviously in the above command. It will return a bunch of variables and states about the thermostat if thats all up and running correctly.

{“name”:“MYTERM”,“mode”:2,“state”:0,“fan”:0,“fanstate”:0,“tempunits”:0,“schedule”:0,“schedulepart”:255,“away”:0,“spacetemp”:73.0,“heattemp”:71.0,“cooltemp”:72.0,“cooltempmin”:62.0,“cooltempmax”:99.0,“heattempmin”:35.00,“heattempmax”:78.0,“setpointdelta”:2.0,“availablemodes”:0}

That being said, I tried all that, and still my device is not working yet. I did change my port from the hex of 50, to 0050 as recommended above. Still nothing.

@Ben looks like you were so helpful with everything above, do you have any thoughts? @scottinpollock, any words of wisdom?

Thanks,
Jordan


(llcanada) #13

Look like Venstar has some new models? I been looking at these for sometime and now they look even more attractive. Voyager supports multiple protocols including Wi-Fi, Z-wave, and ZigBee? So i guess you can buy which ever protocol that you like. I hoping with the Zigbee or Zwave it will be support by Smartthings.


#14

really looking forward to get this to work…

so what are the steps? you add a device from code and then publish it to me?(do the ip gets written on the source code or when ever you add the device on smartthigs app?

then? what do i do after that on the smartthings app?


(Sami) #15

Hey guys, thanks for contributing! I have T5800 that I got way before things were smart :slight_smile: I kinda don’t want to spend more money and change my termo. So can you please help me get my T5800 working with my V2 Hub.

Someone in Tech support tried to use your code today to add my termo to the hub but I believe he got stuck in IP address. He was not getting any traction on the DeviceType.


#16

Hello, has anyone been able to publish or share how to get the Venstar T5800 working with SmartThings? I have inquired with Venstar Support and they tell me they plan to release an official ST driver for their thermostats. Problem is, they don’t say when that is planned for; could be months or years. Any help you can provide is much appreciated. Thanks!


(George) #17

I am stuck as well. Just got my ST hub and have not been able to find any method to integrate with the venstar.


(Aaron) #18

Has anyone gotten this code to work? I have a Venstar T7850 and a Venstar T7900. The T7900 is pretty much the same as the T7850 but has built in humidistat. They are pretty much the same as the T5800 other than they have Wi-Fi and Skyport build in and use the local weather as outdoor sensors.


(Preston) #19

Hey guys,

I have a Venstar T3700 and it took me all day but I finally got @scottinpollock 's code to work on my version. I don’t know that it’ll help with the other versions listed here but thought I’d share anyway.

My Ventar’s local IP is 192.168.50.150 so I’m using C0A83296:0050 in my Device Network ID for the Venstar Device. I used the link @Nanclus provided above to convert the IP to hex and like he said port 80 will be 0050.

One other mistake I made was that under my device “type”, I had chosen “Thermostat”. I didn’t realize I needed to select my Device Handler Name, which I named Venstar T3700.

To get data to pull in I had to change line 238 of the “getapi” function from:
def uri = “/”
to:
def uri = “/query/info”

I also changed line 114 from “def temp” to “def spacetemp”.

The majority of my time was spent trying to figure out why I kept receiving: “json is: [error:true, reason:API Settings Invalid, no valid settings found]” error in my log. Turns out I needed different syntax for each apipost. For example, I changed:
postapi(’{“tmode”:0}’)
to:
postapi(‘mode=0’)

Once I got rid of the " and {} and changed : to = for all of my postapi’s everything started working.

I haven’t tested very thoroughly yet, but so far it’s working. I can set temps and change modes from inside the smartthings app.

Hope this helps somebody.