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
}