From 27fc3983aeeb5b3800ad923444427bdbb94dec0f Mon Sep 17 00:00:00 2001 From: Stuart Date: Thu, 1 Dec 2016 07:26:01 -0800 Subject: [PATCH] MSA-1626: Complete integration for all Tado Heating & Cooling products with SmartThings, this release includes discovery for the Tado Smart Thermostat, Smart Radiator Thermostat, Extension Kit & Smart AC Control Units --- .../tado-cooling-thermostat.groovy | 600 +++++++ .../tado-heating-thermostat.groovy | 270 +++ .../tado-heating-user-presence.groovy | 174 ++ .../tado-hot-water-control.groovy | 280 +++ .../tado-connect.src/tado-connect.groovy | 1545 +++++++++++++++++ 5 files changed, 2869 insertions(+) create mode 100644 devicetypes/fuzzysb/tado-cooling-thermostat.src/tado-cooling-thermostat.groovy create mode 100644 devicetypes/fuzzysb/tado-heating-thermostat.src/tado-heating-thermostat.groovy create mode 100644 devicetypes/fuzzysb/tado-heating-user-presence.src/tado-heating-user-presence.groovy create mode 100644 devicetypes/fuzzysb/tado-hot-water-control.src/tado-hot-water-control.groovy create mode 100644 smartapps/fuzzysb/tado-connect.src/tado-connect.groovy diff --git a/devicetypes/fuzzysb/tado-cooling-thermostat.src/tado-cooling-thermostat.groovy b/devicetypes/fuzzysb/tado-cooling-thermostat.src/tado-cooling-thermostat.groovy new file mode 100644 index 0000000..56c7308 --- /dev/null +++ b/devicetypes/fuzzysb/tado-cooling-thermostat.src/tado-cooling-thermostat.groovy @@ -0,0 +1,600 @@ +/** + * Copyright 2015 Stuart Buchanan + * + * 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. + * + * Tado AC Thermostat + * + * Author: Stuart Buchanan, Based on original work by Ian M with thanks. also source for icons was from @tonesto7's excellent Nest Manager. + * Date: 2016-11-28 v3.0 Moved all data collection functions into Tado (Connect) SmartApp, huge changes to device handler, existing devices and handler will need to be uninstalled before installing this version + * Date: 2016-07-13 v2.9 Quick dirty workaround to control zones with a single account. + * Date: 2016-05-07 v2.8 Corrected issue with Fan Speed commands not working. + * Date: 2016-04-25 v2.7 Minor changes to thermostatOperatingState to Show "idle" and "fan only" state + * Date: 2016-04-25 v2.6 Minor bug fix to correct issue with reading existing set point value. + * Date: 2016-04-09 v2.5 Major bug fix exercise, found lots and lots and lots.....now 100% conforms to ST Thermostat capability. main panel now shows colour of operating state. new attributes tadoMode and tadoFanSpeed created. + * Date: 2016-04-05 v2.4 Performed Testing with Thermostat Mode Director and found some deficiencies where this would not work correctly. i have now corrected, this now works fine and has been tested. + * Date: 2016-04-05 v2.3 added device preference for default temps for some commands as requested by @mitchell_lu66, also added some additional refreshes and error control for unsupported capabilities + * Date: 2016-04-05 v2.2 Added Fan Speed & Emergency Heat (1 Hour) Controls and also a manual Mode End function to fall back to Tado Control. + Also added preference for how long manual mode runs for either ends at Tado Mode Change (TADO_MODE) or User Control (MANUAL), + please ensure the default method is Set in the device properties + * Date: 2016-04-05 v2.1 Minor Bug Fixes & improved Icons + * Date: 2016-04-05 v2.0 Further Changes to MultiAttribute Tile + * Date: 2016-04-05 v1.9 Amended Device Handler Name + * Date: 2016-04-05 v1.8 Added all thermostat related capabilities + * Date: 2016-04-05 v1.7 Amended device to be capable of both Fahrenheit and celsius and amended the Device multiattribute tile + * Date: 2016-04-05 v1.6 switched API calls to new v2 calls as the old ones had been deprecated. + * Date: 2016-02-21 v1.5 switched around thermostatOperatingState & thermostatMode to get better compatibility with Home Remote + * Date: 2016-02-21 v1.4 added HeatingSetPoint & CoolingSetPoint to make compatible with SmartTiles + * Date: 2016-02-21 v1.3 amended the read thermostat properties to match the ST Thermostat Capability + * Date: 2016-02-14 v1.2 amended the thermostat properties to match the ST Capability.Thermostat values + * Date: 2016-01-23 v1.1 fixed error in Tado Mode detection + * Date: 2016-01-22 v1.1 Add Heating & Cooling Controls (initial offering, will need to look into adding all possible commands) + * Date: 2015-12-04 v1.0 Initial Release With Temperatures & Relative Humidity + */ + +import groovy.json.JsonOutput + +preferences { +} + +metadata { + definition (name: "Tado Cooling Thermostat", namespace: "fuzzysb", author: "Stuart Buchanan") { + capability "Actuator" + capability "Temperature Measurement" + capability "Thermostat Cooling Setpoint" + capability "Thermostat Heating Setpoint" + capability "Thermostat Mode" + capability "Thermostat Fan Mode" + capability "Thermostat Setpoint" + capability "Thermostat Operating State" + capability "Thermostat" + capability "Relative Humidity Measurement" + capability "Polling" + capability "Refresh" + + attribute "tadoMode", "string" + attribute "tadoFanSpeed", "string" + command "temperatureUp" + command "temperatureDown" + command "heatingSetpointUp" + command "heatingSetpointDown" + command "coolingSetpointUp" + command "coolingSetpointDown" + command "cmdFanSpeedAuto" + command "cmdFanSpeedHigh" + command "cmdFanSpeedMid" + command "cmdFanSpeedLow" + command "dry" + command "on" + command "fan" + command "endManualControl" + command "emergencyHeat" + } + + // simulator metadata + simulator { + // status messages + + // reply messages + } + + tiles(scale: 2){ + multiAttributeTile(name: "thermostat", type:"thermostat", width:6, height:4) { + tileAttribute("device.temperature", key:"PRIMARY_CONTROL", canChangeIcon: true, canChangeBackground: true){ + attributeState "default", label:'${currentValue}°', backgroundColor:"#fab907", icon:"st.Home.home1" + } + tileAttribute("device.temperature", key: "VALUE_CONTROL") { + attributeState("VALUE_UP", action: "temperatureUp") + attributeState("VALUE_DOWN", action: "temperatureDown") + } + tileAttribute("device.humidity", key: "SECONDARY_CONTROL") { + attributeState("default", label:'${currentValue}%', unit:"%") + } + tileAttribute("device.thermostatOperatingState", key: "OPERATING_STATE") { + attributeState("idle", backgroundColor:"#666666") + attributeState("heating", backgroundColor:"#ff471a") + attributeState("cooling", backgroundColor:"#1a75ff") + attributeState("emergency heat", backgroundColor:"#ff471a") + attributeState("drying", backgroundColor:"#c68c53") + attributeState("fan only", backgroundColor:"#39e600") + attributeState("heating|cooling", backgroundColor:"#ff9900") + } + tileAttribute("device.thermostatMode", key: "THERMOSTAT_MODE") { + attributeState("off", label:'${name}') + attributeState("heat", label:'${name}') + attributeState("cool", label:'${name}') + attributeState("auto", label:'${name}') + attributeState("fan", label:'${name}') + attributeState("dry", label:'${name}') + } + tileAttribute("device.heatingSetpoint", key: "HEATING_SETPOINT") { + attributeState("default", label:'${currentValue}', unit:"dF") + } + tileAttribute("device.coolingSetpoint", key: "COOLING_SETPOINT") { + attributeState("default", label:'${currentValue}', unit:"dF") + } + } + + standardTile("tadoMode", "device.tadoMode", width: 2, height: 2, canChangeIcon: true, canChangeBackground: true) { + state("SLEEP", label:'${name}', backgroundColor:"#0164a8", icon:"st.Bedroom.bedroom2") + state("HOME", label:'${name}', backgroundColor:"#fab907", icon:"st.Home.home2") + state("AWAY", label:'${name}', backgroundColor:"#62aa12", icon:"st.Outdoor.outdoor18") + state("OFF", label:'${name}', backgroundColor:"#ffffff", icon:"st.switches.switch.off", defaultState: true) + state("MANUAL", label:'${name}', backgroundColor:"#804000", icon:"st.Weather.weather1") + } + + standardTile("refresh", "device.switch", inactiveLabel: false, width: 2, height: 1, decoration: "flat") { + state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh" + } + + standardTile("thermostatMode", "device.thermostatMode", width: 2, height: 2, canChangeIcon: true, canChangeBackground: true) { + state("heat", label:'HEAT', backgroundColor:"#ea2a2a", icon:"https://raw.githubusercontent.com/fuzzysb/SmartThings/master/DeviceTypes/fuzzysb/tado-Cooling-AC.src/Images/heat_mode_icon.png") + state("emergency heat", label:'HEAT', backgroundColor:"#ea2a2a", icon:"https://raw.githubusercontent.com/fuzzysb/SmartThings/master/DeviceTypes/fuzzysb/tado-Cooling-AC.src/Images/heat_mode_icon.png") + state("cool", label:'COOL', backgroundColor:"#089afb", icon:"https://raw.githubusercontent.com/fuzzysb/SmartThings/master/DeviceTypes/fuzzysb/tado-Cooling-AC.src/Images/cool_mode_icon.png") + state("dry", label:'DRY', icon:"https://raw.githubusercontent.com/fuzzysb/SmartThings/master/DeviceTypes/fuzzysb/tado-Cooling-AC.src/Images/dry_mode_icon.png") + state("fan", label:'FAN', backgroundColor:"#ffffff", icon:"https://raw.githubusercontent.com/fuzzysb/SmartThings/master/DeviceTypes/fuzzysb/tado-Cooling-AC.src/Images/fan_mode_icononly.png") + state("auto", label:'AUTO', icon:"https://raw.githubusercontent.com/fuzzysb/SmartThings/master/DeviceTypes/fuzzysb/tado-Cooling-AC.src/Images/auto_mode_icon.png") + state("off", label:'', backgroundColor:"#ffffff", icon:"https://raw.githubusercontent.com/fuzzysb/SmartThings/master/DeviceTypes/fuzzysb/tado-Cooling-AC.src/Images/hvac_off.png", defaultState: true) + } + + valueTile("thermostatSetpoint", "device.thermostatSetpoint", width: 2, height: 1, decoration: "flat") { + state "default", label: 'Set Point\r\n\${currentValue}°' + } + + valueTile("heatingSetpoint", "device.heatingSetpoint", width: 2, height: 1, decoration: "flat") { + state "default", label: 'Set Point\r\n\${currentValue}°' + } + + valueTile("coolingSetpoint", "device.coolingSetpoint", width: 2, height: 1, decoration: "flat") { + state "default", label: 'Set Point\r\n\${currentValue}°' + } + + valueTile("outsidetemperature", "device.outsidetemperature", width: 2, height: 1, decoration: "flat") { + state "outsidetemperature", label: 'Outside Temp\r\n${currentValue}°' + } + + standardTile("tadoFanSpeed", "device.tadoFanSpeed", width: 2, height: 2, canChangeIcon: true, canChangeBackground: true, decoration: "flat") { + state("OFF", label:'', icon:"https://raw.githubusercontent.com/fuzzysb/SmartThings/master/DeviceTypes/fuzzysb/tado-Cooling-AC.src/Images/fan_off_icon.png", defaultState: true) + state("AUTO", label:'', icon:"https://raw.githubusercontent.com/fuzzysb/SmartThings/master/DeviceTypes/fuzzysb/tado-Cooling-AC.src/Images/fan_auto_icon.png") + state("HIGH", label:'', icon:"https://raw.githubusercontent.com/fuzzysb/SmartThings/master/DeviceTypes/fuzzysb/tado-Cooling-AC.src/Images/fan_high_icon.png") + state("MIDDLE", label:'', icon:"https://raw.githubusercontent.com/fuzzysb/SmartThings/master/DeviceTypes/fuzzysb/tado-Cooling-AC.src/Images/fan_med_icon.png") + state("LOW", label:'', icon:"https://raw.githubusercontent.com/fuzzysb/SmartThings/master/DeviceTypes/fuzzysb/tado-Cooling-AC.src/Images/fan_low_icon.png") + } + + standardTile("setAuto", "device.thermostat", width: 2, height: 1, decoration: "flat") { + state "default", label:"", action:"thermostat.auto", icon:"https://raw.githubusercontent.com/fuzzysb/SmartThings/master/DeviceTypes/fuzzysb/tado-Cooling-AC.src/Images/hvac_auto.png" + } + standardTile("setDry", "device.thermostat", width: 2, height: 1, decoration: "flat") { + state "default", label:"", action:"dry", icon:"https://raw.githubusercontent.com/fuzzysb/SmartThings/master/DeviceTypes/fuzzysb/tado-Cooling-AC.src/Images/hvac_dry.png" + } + standardTile("setOn", "device.thermostat", width: 2, height: 1, decoration: "flat") { + state "default", label:"", action:"on", icon:"https://raw.githubusercontent.com/fuzzysb/SmartThings/master/DeviceTypes/fuzzysb/tado-Cooling-AC.src/Images/hvac_on.png" + } + standardTile("setOff", "device.thermostat", width: 2, height: 1, decoration: "flat") { + state "default", label:"", action:"thermostat.off", icon:"https://raw.githubusercontent.com/fuzzysb/SmartThings/master/DeviceTypes/fuzzysb/tado-Cooling-AC.src/Images/hvac_off.png" + } + standardTile("cool", "device.thermostat", width: 2, height: 1, decoration: "flat") { + state "default", label:"", action:"thermostat.cool", icon:"https://raw.githubusercontent.com/fuzzysb/SmartThings/master/DeviceTypes/fuzzysb/tado-Cooling-AC.src/Images/hvac_cool.png" + } + standardTile("heat", "device.thermostat", width: 2, height: 1, decoration: "flat") { + state "default", label:"", action:"thermostat.heat", icon:"https://raw.githubusercontent.com/fuzzysb/SmartThings/master/DeviceTypes/fuzzysb/tado-Cooling-AC.src/Images/hvac_heat.png" + } + standardTile("emergencyHeat", "device.thermostat", width: 2, height: 1, decoration: "flat") { + state "default", label:"", action:"thermostat.emergencyHeat", icon:"https://raw.githubusercontent.com/fuzzysb/SmartThings/master/DeviceTypes/fuzzysb/tado-Cooling-AC.src/Images/emergencyHeat.png" + } + standardTile("fan", "device.thermostat", width: 2, height: 1, decoration: "flat") { + state "default", label:"", action:"thermostat.fan", icon:"https://raw.githubusercontent.com/fuzzysb/SmartThings/master/DeviceTypes/fuzzysb/tado-Cooling-AC.src/Images/fan_mode_icon.png" + } + standardTile("coolingSetpointUp", "device.coolingSetpoint", canChangeIcon: false, decoration: "flat") { + state "coolingSetpointUp", label:' ', action:"coolingSetpointUp", icon:"https://raw.githubusercontent.com/fuzzysb/SmartThings/master/DeviceTypes/fuzzysb/tado-Cooling-AC.src/Images/cool_arrow_up.png" + } + standardTile("coolingSetpointDown", "device.coolingSetpoint", canChangeIcon: false, decoration: "flat") { + state "coolingSetpointDown", label:' ', action:"coolingSetpointDown", icon:"https://raw.githubusercontent.com/fuzzysb/SmartThings/master/DeviceTypes/fuzzysb/tado-Cooling-AC.src/Images/cool_arrow_down.png" + } + standardTile("heatingSetpointUp", "device.heatingSetpoint", canChangeIcon: false, decoration: "flat") { + state "heatingSetpointUp", label:' ', action:"heatingSetpointUp", icon:"https://raw.githubusercontent.com/fuzzysb/SmartThings/master/DeviceTypes/fuzzysb/tado-Cooling-AC.src/Images/heat_arrow_up.png" + } + standardTile("heatingSetpointDown", "device.heatingSetpoint", canChangeIcon: false, decoration: "flat") { + state "heatingSetpointDown", label:' ', action:"heatingSetpointDown", icon:"https://raw.githubusercontent.com/fuzzysb/SmartThings/master/DeviceTypes/fuzzysb/tado-Cooling-AC.src/Images/heat_arrow_down.png" + } + standardTile("cmdFanSpeedAuto", "device.thermostat", width: 2, height: 1, canChangeIcon: false, canChangeBackground: true, decoration: "flat") { + state("default", label:'', action:"cmdFanSpeedAuto", icon:"https://raw.githubusercontent.com/fuzzysb/SmartThings/master/DeviceTypes/fuzzysb/tado-Cooling-AC.src/Images/fan_auto_icon.png") + } + standardTile("cmdFanSpeedHigh", "device.thermostat", width: 2, height: 1, canChangeIcon: false, canChangeBackground: true, decoration: "flat") { + state("default", label:'', action:"cmdFanSpeedHigh", icon:"https://raw.githubusercontent.com/fuzzysb/SmartThings/master/DeviceTypes/fuzzysb/tado-Cooling-AC.src/Images/fan_high_icon.png") + } + standardTile("cmdFanSpeedMid", "device.thermostat", width: 2, height: 1, canChangeIcon: false, canChangeBackground: true, decoration: "flat") { + state("default", label:'', action:"cmdFanSpeedMid", icon:"https://raw.githubusercontent.com/fuzzysb/SmartThings/master/DeviceTypes/fuzzysb/tado-Cooling-AC.src/Images/fan_med_icon.png") + } + standardTile("cmdFanSpeedLow", "device.thermostat", width: 2, height: 1, canChangeIcon: false, canChangeBackground: true, decoration: "flat") { + state("default", label:'', action:"cmdFanSpeedLow", icon:"https://raw.githubusercontent.com/fuzzysb/SmartThings/master/DeviceTypes/fuzzysb/tado-Cooling-AC.src/Images/fan_low_icon.png") + } + standardTile("endManualControl", "device.thermostat", width: 2, height: 1, canChangeIcon: false, canChangeBackground: true, decoration: "flat") { + state("default", label:'', action:"endManualControl", icon:"https://raw.githubusercontent.com/fuzzysb/SmartThings/master/DeviceTypes/fuzzysb/tado-Cooling-AC.src/Images/endManual.png") + } + + main(["thermostat"]) + details(["thermostat","thermostatMode","coolingSetpointUp","coolingSetpointDown","autoOperation","heatingSetpointUp","heatingSetpointDown","outsidetemperature","thermostatSetpoint","tadoMode","refresh","tadoFanSpeed","setAuto","setOn","setOff","fan","cool","heat","setDry","cmdFanSpeedAuto","emergencyHeat","endManualControl","cmdFanSpeedLow","cmdFanSpeedMid","cmdFanSpeedHigh"]) + } +} + +def setCapabilitytadoType(value){ + state.tadoType = value + log.debug("state.tadoType = ${state.tadoType}") +} + +def getCapabilitytadoType() { + def map = null + map = [name: "capabilityTadoType", value: state.tadoType] + return map +} + +def setCapabilitySupportsAuto(value){ + state.supportsAuto = value + log.debug("state.supportsAuto = ${state.supportsAuto}") +} + +def getCapabilitySupportsAuto() { + def map = null + map = [name: "capabilitySupportsAuto", value: state.supportsAuto] + return map +} + +def setCapabilitySupportsCool(value){ + state.supportsCool = value + log.debug("state.supportsCool = ${state.supportsCool}") +} + +def getCapabilitySupportsCool() { + def map = null + map = [name: "capabilitySupportsCool", value: state.supportsCool] + return map +} + +def setCapabilitySupportsCoolAutoFanSpeed(value){ + state.SupportsCoolAutoFanSpeed = value + log.debug("state.SupportsCoolAutoFanSpeed = ${state.SupportsCoolAutoFanSpeed}") +} + +def getCapabilitySupportsCoolAutoFanSpeed() { + def map = null + map = [name: "capabilitySupportsCoolAutoFanSpeed", value: state.supportsCoolAutoFanSpeed] + return map +} + +def setCapabilitySupportsDry(value){ + state.supportsDry = value + log.debug("state.supportsDry = ${state.supportsDry}") +} + +def getCapabilitySupportsDry() { + def map = null + map = [name: "capabilitySupportsDry", value: state.supportsDry] + return map +} + +def setCapabilitySupportsFan(value){ + state.supportsFan = value + log.debug("state.supportsFan = ${state.supportsFan}") +} + +def getCapabilitySupportsFan() { + def map = null + map = [name: "capabilitySupportsFan", value: state.supportsFan] + return map +} + +def setCapabilitySupportsHeat(value){ + state.supportsHeat = value + log.debug("state.supportsHeat = ${state.supportsHeat}") +} + +def getCapabilitySupportsHeat() { + def map = null + map = [name: "capabilitySupportsHeat", value: state.supportsHeat] + return map +} + +def setCapabilitySupportsHeatAutoFanSpeed(value){ + state.SupportsHeatAutoFanSpeed = value + log.debug("state.SupportsHeatAutoFanSpeed = ${state.SupportsHeatAutoFanSpeed}") +} + +def getCapabilitySupportsHeatAutoFanSpeed() { + def map = null + map = [name: "capabilitySupportsHeatAutoFanSpeed", value: state.SupportsHeatAutoFanSpeed] + return map +} + +def setCapabilityMaxCoolTemp(value){ + state.MaxCoolTemp = value + log.debug("set state.MaxCoolTemp to : " + state.MaxCoolTemp) +} + +def getCapabilityMaxCoolTemp() { + def map = null + map = [name: "capabilityMaxCoolTemp", value: state.MaxCoolTemp] + return map +} + +def setCapabilityMinCoolTemp(value){ + state.MinCoolTemp = value + log.debug("set state.MinCoolTemp to : " + state.MinCoolTemp) +} + +def getCapabilityMinCoolTemp() { + def map = null + map = [name: "capabilityMinCoolTemp", value: state.MinCoolTemp] + return map +} + +def setCapabilityMaxHeatTemp(value){ + state.MaxHeatTemp = value + log.debug("set state.MaxHeatTemp to : " + state.MaxHeatTemp) +} + +def getCapabilityMaxHeatTemp() { + def map = null + map = [name: "capabilityMaxHeatTemp", value: state.MaxHeatTemp] + return map +} + +def setCapabilityMinHeatTemp(value){ + state.MinHeatTemp = value + log.debug("set state.MinHeatTemp to : " + state.MinHeatTemp) +} + +def getCapabilityMinHeatTemp() { + def map = null + map = [name: "capabilityMinHeatTemp", value: state.MinHeatTemp] + return map +} + +def updated(){ + refresh() +} + +def installed(){ + refresh() +} + +def poll() { + log.debug "Executing 'poll'" + refresh() +} + +def refresh() { + log.debug "Executing 'refresh'" + parent.statusCommand(this) + getWeather() +} + +def getWeather(){ + parent.weatherStatusCommand(this) +} + +def auto() { + log.debug "Executing 'auto'" + parent.autoCommand(this) + parent.statusCommand(this) +} + +def on() { + log.debug "Executing 'on'" + parent.onCommand(this) + parent.statusCommand(this) +} + +def off() { + log.debug "Executing 'off'" + parent.offCommand(this) + parent.statusCommand(this) +} + +def dry() { + log.debug "Executing 'dry'" + parent.dryCommand(this) + parent.statusCommand(this) +} + +def setThermostatMode(requiredMode){ + switch (requiredMode) { + case "dry": + dry() + break + case "heat": + heat() + break + case "cool": + cool() + break + case "auto": + auto() + break + case "fan": + fan() + break + case "off": + off() + break + case "emergency heat": + emergencyHeat() + break + } +} + +def thermostatFanMode(requiredMode){ + switch (requiredMode) { + case "auto": + fan() + break + case "on": + fan() + break + case "circulate": + fan() + break + } +} + +def setHeatingSetpoint(targetTemperature) { + log.debug "Executing 'setHeatingSetpoint'" + log.debug "Target Temperature ${targetTemperature}" + parent.setHeatingTempCommand(this,targetTemperature) + refresh() +} + +def temperatureUp(){ + if (device.currentValue("thermostatMode") == "heat") { + heatingSetpointUp() + } else if (device.currentValue("thermostatMode") == "cool") { + coolingSetpointUp() + } else { + log.debug ("temperature setpoint not supported in the current thermostat mode") + } +} + +def temperatureDown(){ + if (device.currentValue("thermostatMode") == "heat") { + heatingSetpointDown() + } else if (device.currentValue("thermostatMode") == "cool") { + coolingSetpointDown() + } else { + log.debug ("temperature setpoint not supported in the current thermostat mode") + } +} + +def heatingSetpointUp(){ + def capabilitysupported = state.supportsHeat + if (capabilitysupported == "true"){ + log.debug "Current SetPoint Is " + (device.currentValue("thermostatSetpoint")).toString() + if ((device.currentValue("thermostatSetpoint").toInteger() - 1 ) < state.MinHeatTemp){ + log.debug("cannot decrease heat setpoint, its already at the minimum level of " + state.MinHeatTemp) + } else { + int newSetpoint = (device.currentValue("thermostatSetpoint")).toInteger() + 1 + log.debug "Setting heatingSetpoint up to: ${newSetpoint}" + setHeatingSetpoint(newSetpoint) + } + } else { + log.debug("Sorry Heat Capability not supported by your HVAC Device") + } +} + +def heatingSetpointDown(){ + def capabilitysupported = state.supportsHeat + if (capabilitysupported == "true"){ + log.debug "Current SetPoint Is " + (device.currentValue("thermostatSetpoint")).toString() + if ((device.currentValue("thermostatSetpoint").toInteger() + 1 ) > state.MaxHeatTemp){ + log.debug("cannot increase heat setpoint, its already at the maximum level of " + state.MaxHeatTemp) + } else { + int newSetpoint = (device.currentValue("thermostatSetpoint")).toInteger() - 1 + log.debug "Setting heatingSetpoint down to: ${newSetpoint}" + setHeatingSetpoint(newSetpoint) + } + } else { + log.debug("Sorry Heat Capability not supported by your HVAC Device") + } +} + +def setCoolingSetpoint(targetTemperature) { + log.debug "Executing 'setCoolingSetpoint'" + log.debug "Target Temperature ${targetTemperature}" + parent.setCoolingTempCommand(this,targetTemperature) + refresh() +} + +def coolingSetpointUp(){ + def capabilitysupported = state.supportsCool + if (capabilitysupported == "true"){ + log.debug "Current SetPoint Is " + (device.currentValue("thermostatSetpoint")).toString() + if ((device.currentValue("thermostatSetpoint").toInteger() + 1 ) > state.MaxCoolTemp){ + log.debug("cannot increase cool setpoint, its already at the maximum level of " + state.MaxCoolTemp) + } else { + int newSetpoint = (device.currentValue("thermostatSetpoint")).toInteger() + 1 + log.debug "Setting coolingSetpoint up to: ${newSetpoint}" + setCoolingSetpoint(newSetpoint) + } + } else { + log.debug("Sorry Cool Capability not supported by your HVAC Device") + } +} + +def coolingSetpointDown(){ + def capabilitysupported = state.supportsCool + if (capabilitysupported == "true"){ + log.debug "Current SetPoint Is " + (device.currentValue("thermostatSetpoint")).toString() + if ((device.currentValue("thermostatSetpoint").toInteger() - 1 ) < state.MinCoolTemp){ + log.debug("cannot decrease cool setpoint, its already at the minimum level of " + state.MinCoolTemp) + } else { + int newSetpoint = (device.currentValue("thermostatSetpoint")).toInteger() - 1 + log.debug "Setting coolingSetpoint down to: ${newSetpoint}" + setCoolingSetpoint(newSetpoint) + } + } else { + log.debug("Sorry Cool Capability not supported by your HVAC Device") + } +} + +def fanOn(){ + fan() +} + +def fanCirculate(){ + fan() +} + +def cool(){ + def capabilitysupported = state.supportsCool + if (capabilitysupported == "true"){ + parent.coolCommand(this) + parent.statusCommand(this) + } else { + log.debug("Sorry Cool Capability not supported by your HVAC Device") + } +} + +def heat(){ + def capabilitysupported = state.supportsHeat + if (capabilitysupported == "true"){ + parent.heatCommand(this) + parent.statusCommand(this) + } else { + log.debug("Sorry Heat Capability not supported by your HVAC Device") + } +} + +def fan(){ + parent.fanAuto(this) + refresh() +} + +def emergencyHeat(){ + parent.emergencyHeat(this) +} + +def cmdFanSpeedAuto(){ + parent.cmdFanSpeedAuto(this) +} + +def cmdFanSpeedHigh(){ + parent.cmdFanSpeedHigh(this) +} + +def cmdFanSpeedMed(){ + parent.cmdFanSpeedMed(this) +} + +def cmdFanSpeedLow(){ + parent.cmdFanSpeedLow(this) +} + +def endManualControl(){ + parent.endManualControl(this) +} diff --git a/devicetypes/fuzzysb/tado-heating-thermostat.src/tado-heating-thermostat.groovy b/devicetypes/fuzzysb/tado-heating-thermostat.src/tado-heating-thermostat.groovy new file mode 100644 index 0000000..823cf4d --- /dev/null +++ b/devicetypes/fuzzysb/tado-heating-thermostat.src/tado-heating-thermostat.groovy @@ -0,0 +1,270 @@ +/** + * Copyright 2015 SmartThings + * + * 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. + * + * Tado Thermostat + * + * Author: Stuart Buchanan, Based on original work by Ian M with thanks. also source for icons was from @tonesto7's excellent Nest Manager. + * + * Updates: + * Date: 2016-11-28 v1.9 Moved all data collection functions into Tado (Connect) SmartApp, huge changes to device handler, existing devices and handler will need to be uninstalled before installing this version + * Date: 2016-07-13 v1.8 Quick dirty workaround to control zones with a single account. + * Date: 2016-04-25 v1.7 Finally found time to update this with the lessons learnt from the Tado Cooling Device Type. will bring better support for RM and Thermostat Director + * Date: 2016-04-08 v1.6 added statusCommand calls to refresh more frequently, also improved compatibility with Rule Machine and Thermostat Mode Director in addition also added default heating temperature where you can set the default temperature for the mode commands. + * Date: 2016-04-05 v1.5 added improved icons and also a manual Mode End function to fall back to Tado Control. + Also added preference for how long manual mode runs for either ends at Tado Mode Change (TADO_MODE) or User Control (MANUAL), + please ensure the default method is Set in the device properties + * Date: 2016-04-05 v1.4 rewrite of complete functions to support Tado API v2 + * Date: 2016-01-20 v1.3 Updated hvacStatus to include include the correct HomeId for Humidity Value + * Date: 2016-01-15 v1.2 Refactored API request code and added querying/display of humidity + * Date: 2015-12-23 v1.1 Added functionality to change thermostat settings + * Date: 2015-12-04 v1.0 Initial release + */ + +preferences { + input("username", "text", title: "Username", description: "Your Tado username") + input("password", "password", title: "Password", description: "Your Tado password") + input("tadoZoneId", "number", title: "Enter Tado Zone ID?", required: true) + input("manualmode", "enum", title: "Default Manual Overide Method", options: ["TADO_MODE","MANUAL"], required: false, defaultValue:"TADO_MODE") + input("defHeatingTemp", "number", title: "Default Heating Temperature?", required: false, defaultValue: 21) +} + +metadata { + definition (name: "Tado Heating Thermostat", namespace: "fuzzysb", author: "Stuart Buchanan") { + capability "Actuator" + capability "Temperature Measurement" + capability "Thermostat Heating Setpoint" + capability "Thermostat Setpoint" + capability "Thermostat Mode" + capability "Thermostat Operating State" + capability "Thermostat" + capability "Relative Humidity Measurement" + capability "Polling" + capability "Refresh" + attribute "tadoMode", "string" + command "temperatureUp" + command "temperatureDown" + command "heatingSetpointUp" + command "heatingSetpointDown" + command "on" + command "endManualControl" + command "emergencyHeat" + + } + + // simulator metadata + simulator { + // status messages + + // reply messages + } + +tiles(scale: 2){ + multiAttributeTile(name: "thermostat", type:"thermostat", width:6, height:4) { + tileAttribute("device.temperature", key:"PRIMARY_CONTROL", canChangeIcon: true, canChangeBackground: true){ + attributeState "default", label:'${currentValue}°', backgroundColor:"#fab907", icon:"st.Home.home1" + } + tileAttribute("device.temperature", key: "VALUE_CONTROL") { + attributeState("VALUE_UP", action: "temperatureUp") + attributeState("VALUE_DOWN", action: "temperatureDown") + } + tileAttribute("device.humidity", key: "SECONDARY_CONTROL") { + attributeState("default", label:'${currentValue}%', unit:"%") + } + tileAttribute("device.thermostatOperatingState", key: "OPERATING_STATE") { + attributeState("idle", backgroundColor:"#666666") + attributeState("heating", backgroundColor:"#ff471a") + attributeState("emergency heat", backgroundColor:"#ff471a") + } + tileAttribute("device.thermostatMode", key: "THERMOSTAT_MODE") { + attributeState("off", label:'${name}') + attributeState("heat", label:'${name}') + } + tileAttribute("device.heatingSetpoint", key: "HEATING_SETPOINT") { + attributeState("default", label:'${currentValue}', unit:"dF") + } + } + + valueTile("heatingSetpoint", "device.heatingSetpoint", width: 2, height: 1, decoration: "flat") { + state "default", label: 'Set Point\r\n\${currentValue}°' + } + + standardTile("tadoMode", "device.tadoMode", width: 2, height: 2, canChangeIcon: true, canChangeBackground: true) { + state("SLEEP", label:'${name}', backgroundColor:"#0164a8", icon:"st.Bedroom.bedroom2") + state("HOME", label:'${name}', backgroundColor:"#fab907", icon:"st.Home.home2") + state("AWAY", label:'${name}', backgroundColor:"#62aa12", icon:"st.Outdoor.outdoor18") + state("OFF", label:'', backgroundColor:"#ffffff", icon:"https://raw.githubusercontent.com/fuzzysb/SmartThings/master/DeviceTypes/fuzzysb/tado.Heating.src/Images/hvac_off.png", defaultState: true) + state("MANUAL", label:'${name}', backgroundColor:"#804000", icon:"st.Weather.weather1") + } + + standardTile("thermostatMode", "device.thermostatMode", width: 2, height: 2, canChangeIcon: true, canChangeBackground: true) { + state("heat", label:'HEAT', backgroundColor:"#ea2a2a", icon:"https://raw.githubusercontent.com/fuzzysb/SmartThings/master/DeviceTypes/fuzzysb/tado.Heating.src/Images/heat_mode_icon.png") + state("off", label:'', backgroundColor:"#ffffff", icon:"https://raw.githubusercontent.com/fuzzysb/SmartThings/master/DeviceTypes/fuzzysb/tado.Heating.src/Images/hvac_off.png", defaultState: true) + } + + standardTile("refresh", "device.switch", width: 2, height: 1, decoration: "flat") { + state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh" + } + standardTile("Off", "device.thermostat", width: 2, height: 1, decoration: "flat") { + state "default", label:"", action:"thermostat.off", icon:"https://raw.githubusercontent.com/fuzzysb/SmartThings/master/DeviceTypes/fuzzysb/tado.Heating.src/Images/hvac_off.png" + } + standardTile("emergencyHeat", "device.thermostat", width: 2, height: 1, decoration: "flat") { + state "default", label:"", action:"thermostat.emergencyHeat", icon:"https://raw.githubusercontent.com/fuzzysb/SmartThings/master/DeviceTypes/fuzzysb/tado.Heating.src/Images/emergencyHeat.png" + } + valueTile("outsidetemperature", "device.outsidetemperature", width: 2, height: 1, decoration: "flat") { + state "outsidetemperature", label: 'Outside Temp\r\n${currentValue}°' + } + standardTile("heat", "device.thermostat", width: 2, height: 1, decoration: "flat") { + state "default", label:"", action:"thermostat.heat", icon:"https://raw.githubusercontent.com/fuzzysb/SmartThings/master/DeviceTypes/fuzzysb/tado.Heating.src/Images/hvac_heat.png" + } + standardTile("heatingSetpointUp", "device.heatingSetpoint", width: 1, height: 1, canChangeIcon: false, decoration: "flat") { + state "heatingSetpointUp", label:'', action:"heatingSetpointUp", icon:"https://raw.githubusercontent.com/fuzzysb/SmartThings/master/DeviceTypes/fuzzysb/tado.Heating.src/Images/heat_arrow_up.png" + } + standardTile("heatingSetpointDown", "device.heatingSetpoint", width: 1, height: 1, canChangeIcon: false, decoration: "flat") { + state "heatingSetpointDown", label:'', action:"heatingSetpointDown", icon:"https://raw.githubusercontent.com/fuzzysb/SmartThings/master/DeviceTypes/fuzzysb/tado.Heating.src/Images/heat_arrow_down.png" + } + standardTile("endManualControl", "device.thermostat", width: 2, height: 1, canChangeIcon: false, canChangeBackground: true, decoration: "flat") { + state("default", label:'', action:"endManualControl", icon:"https://raw.githubusercontent.com/fuzzysb/SmartThings/master/DeviceTypes/fuzzysb/tado.Heating.src/Images/endManual.png") + } + main "thermostat" + details (["thermostat","thermostatMode","outsidetemperature","heatingSetpoint","refresh","heatingSetpointUp","heatingSetpointDown","tadoMode","emergencyHeat","heat","Off","endManualControl"]) + } +} + + +def getWeather(){ + parent.weatherStatusCommand(this) +} + +def setCapabilitySupportsHeat(value){ + state.supportsHeat = value + log.debug("state.supportsHeat = ${state.supportsHeat}") +} + +def getCapabilitySupportsHeat() { + def map = null + map = [name: "capabilitySupportsHeat", value: state.supportsHeat] + return map +} + +def updated(){ + refresh() +} + +def installed(){ + refresh() +} + +def poll() { + log.debug "Executing 'poll'" + refresh() +} + +def refresh() { + log.debug "Executing 'refresh'" + parent.statusCommand(this) + getWeather() +} + +def auto() { + log.debug "Executing 'auto'" + parent.autoCommand(this) + parent.statusCommand(this) +} + +def on() { + log.debug "Executing 'on'" + parent.onCommand(this) + parent.statusCommand(this) +} + +def off() { + log.debug "Executing 'off'" + parent.offCommand(this) + parent.statusCommand(this) +} + +def setHeatingSetpoint(targetTemperature) { + log.debug "Executing 'setHeatingSetpoint'" + log.debug "Target Temperature ${targetTemperature}" + parent.setHeatingTempCommand(this,targetTemperature) + parent.statusCommand(this) +} + +def setThermostatMode(requiredMode){ + switch (requiredMode) { + case "heat": + heat() + break + case "auto": + auto() + break + case "off": + off() + break + case "emergency heat": + emergencyHeat() + break + } +} + +def temperatureUp(){ + if (device.currentValue("thermostatMode") == "heat") { + heatingSetpointUp() + } else { + log.debug ("temperature setpoint not supported in the current thermostat mode") + } +} + +def temperatureDown(){ + if (device.currentValue("thermostatMode") == "heat") { + heatingSetpointDown() + } else { + log.debug ("temperature setpoint not supported in the current thermostat mode") + } +} + +def heatingSetpointUp(){ + log.debug "Current SetPoint Is " + (device.currentValue("thermostatSetpoint")).toString() + if ((device.currentValue("thermostatSetpoint").toInteger() - 1 ) < state.MinHeatTemp){ + log.debug("cannot decrease heat setpoint, its already at the minimum level of " + state.MinHeatTemp) + } else { + int newSetpoint = (device.currentValue("thermostatSetpoint")).toInteger() + 1 + log.debug "Setting heatingSetpoint up to: ${newSetpoint}" + setHeatingSetpoint(newSetpoint) + } +} + +def heatingSetpointDown(){ + log.debug "Current SetPoint Is " + (device.currentValue("thermostatSetpoint")).toString() + if ((device.currentValue("thermostatSetpoint").toInteger() + 1 ) > state.MaxHeatTemp){ + log.debug("cannot increase heat setpoint, its already at the maximum level of " + state.MaxHeatTemp) + } else { + int newSetpoint = (device.currentValue("thermostatSetpoint")).toInteger() - 1 + log.debug "Setting heatingSetpoint down to: ${newSetpoint}" + setHeatingSetpoint(newSetpoint) + } +} + + +// Commands to device +def heat(){ + parent.heatCommand(this) + parent.statusCommand(this) +} + +def emergencyHeat(){ + parent.emergencyHeat(this) +} + +def endManualControl(){ + parent.endManualControl(this) +} diff --git a/devicetypes/fuzzysb/tado-heating-user-presence.src/tado-heating-user-presence.groovy b/devicetypes/fuzzysb/tado-heating-user-presence.src/tado-heating-user-presence.groovy new file mode 100644 index 0000000..f361c5a --- /dev/null +++ b/devicetypes/fuzzysb/tado-heating-user-presence.src/tado-heating-user-presence.groovy @@ -0,0 +1,174 @@ +/** + * Copyright 2015 Stuart Buchanan + * + * 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. + * + * Tado Thermostat + * + * Author: Stuart Buchanan, Based on original work by Ian M with thanks + * Date: 2015-04-28 v1.3 changed Presence tile as this was reporting a bug + * Date: 2015-04-28 v1.2 updated API call found issue where session was closed and nothing else was returned, now add number generator to input noCache statement in the query + * Date: 2015-04-27 v1.1 updated API call and added refresh function + * Date: 2015-12-04 v1.0 Initial Release + */ + +import groovy.json.JsonOutput +import groovy.json.JsonSlurper +import java.util.Random + +preferences { + input("username", "text", title: "Username", description: "Your Tado username") + input("password", "password", title: "Password", description: "Your Tado password") + input("tadouser", "text", title: "Tado User", description: "Your Tado User") +} + +metadata { + definition (name: "Tado Heating User Presence", namespace: "fuzzysb", author: "Stuart Buchanan") { + capability "Presence Sensor" + capability "Sensor" + capability "Polling" + capability "Refresh" + + command "arrived" + command "departed" + + } + + // simulator metadata + simulator { + status "present": "presence: present" + status "not present": "presence: not present" + } + + tiles { + standardTile("presence", "device.presence", width: 2, height: 2, canChangeBackground: true) { + state("present", labelIcon:"st.presence.tile.mobile-present", backgroundColor:"#53a7c0") + state("not present", labelIcon:"st.presence.tile.mobile-not-present", backgroundColor:"#ffffff") + } + standardTile("refresh", "device.refresh", width: 2, height: 1, decoration: "flat") { + state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh" + } + main "presence" + details (["presence","refresh"]) + } +} + +// Parse incoming device messages to generate events +private parseResponse(resp) { + def result + log.debug("Executing parseResponse: "+resp.data) + log.debug("Output status: "+resp.status) + if(resp.status == 200) { + log.debug("Executing parseResponse.successTrue") + def evtJson = new groovy.json.JsonOutput().toJson(resp.data) + def json = new JsonSlurper().parseText(evtJson) + def appuserarray = new groovy.json.JsonOutput().toJson(json.appUsers) + def list = new JsonSlurper().parseText(appuserarray) + list.each { + if ((it.nickname).capitalize() == (settings.tadouser).capitalize()) { + log.debug("Found Tado User : " + it.nickname) + if (it.geoTrackingEnabled == true) { + log.debug("Users GeoTracking is Enabled") + if (it.geolocationIsStale == false){ + log.debug("Users Current Relative Position is : " + it.relativePosition ) + if (it.relativePosition == 0) { + result = arrived() + }else{ + result = departed() + } + }else{ + log.debug("Geolocation is Stale Skipping") + } + }else{ + log.debug("Users GeoTracking Not Enabled") + } + } + } + }else if(resp.status == 201){ + log.debug("Something was created/updated") + } + return result +} + +def installed() { + log.debug "Executing 'installed'" + statusCommand() +} + +def updated() { + log.debug "Executing 'updated'" + statusCommand() +} + +def poll() { + log.debug "Executing 'poll'" + refresh() +} + +def refresh() { + log.debug "Executing 'refresh'" + statusCommand() +} + + +private sendCommand(method, args = []) { + def methods = [ + 'status': [ + uri: "https://my.tado.com", + path: "/mobile/1.6/getAppUsersRelativePositions", + requestContentType: "application/json", + query: [username:settings.username, password:settings.password, noCache:args[0], webapp:1] + ], + ] + + def request = methods.getAt(method) + + log.debug "Http Params ("+request+")" + + try{ + log.debug "Executing 'sendCommand'" + + if (method == "status"){ + httpGet(request) { resp -> + parseResponse(resp) + } + }else{ + httpGet(request) + } + } catch(Exception e){ + log.debug("___exception: " + e) + } +} + + + +// Commands +def statusCommand(){ + log.debug "Executing 'sendCommand.statusCommand'" + Random rand = new Random() + int min = 10000 + int max = 99999 + int randomNum = rand.nextInt((max - min) + 1) + min + sendCommand("status",[randomNum]) +} + +def arrived() { + log.trace "Executing 'arrived'" + def result = sendEvent(name: "presence", value: "present") + return result +} + + +def departed() { + log.trace "Executing 'departed'" + def result = sendEvent(name: "presence", value: "not present") + return result +} + diff --git a/devicetypes/fuzzysb/tado-hot-water-control.src/tado-hot-water-control.groovy b/devicetypes/fuzzysb/tado-hot-water-control.src/tado-hot-water-control.groovy new file mode 100644 index 0000000..014c8e3 --- /dev/null +++ b/devicetypes/fuzzysb/tado-hot-water-control.src/tado-hot-water-control.groovy @@ -0,0 +1,280 @@ +/** + * Copyright 2015 SmartThings + * + * 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. + * + * Tado Thermostat + * + * Author: Stuart Buchanan, Based on original work by Ian M with thanks. also source for icons was from @tonesto7's excellent Nest Manager. +* Date: 2016-11-28 v1.6 Moved all data collection functions into Tado (Connect) SmartApp, huge changes to device handler, existing devices and handler will need to be uninstalled before installing this version + * Date: 2016-07-13 v1.5 Quick dirty workaround to control zones with a single account. + * Date: 2016-04-25 v1.4 Tado Hot water does not actually return the current water temps, it only returns the Current set point temp. to get around this when the power is on for the hot water botht the temp and setpoint will both display the setpoint value, otherwise will display -- + * Date: 2016-04-25 v1.3 Finally found time to update this with the lessons learnt from the Tado Cooling Device Type. will bring better support for RM and Thermostat Director + * Date: 2016-04-08 v1.2 added setThermostatMode(mode) function to work better with Rule Machine and Thermostat Mode Director + * Date: 2016-04-05 v1.1 change of default Water Heating Temps can now be defined in device preferences (default Value is 90C). + * Date: 2016-04-05 v1.0 Initial release + */ + +preferences { + input("username", "text", title: "Username", description: "Your Tado username") + input("password", "password", title: "Password", description: "Your Tado password") + input("tadoZoneId", "number", title: "Enter Tado Zone ID?", required: true) + input("manualmode", "enum", title: "Default Manual Overide Method", options: ["TADO_MODE","MANUAL"], required: false, defaultValue:"TADO_MODE") + input("defWaterTemp", "number", title: "Default Water Heating Temperature", required: false, defaultValue: 90) +} + +metadata { + definition (name: "Tado Hot Water Control", namespace: "fuzzysb", author: "Stuart Buchanan") { + capability "Actuator" + capability "Temperature Measurement" + capability "Thermostat Heating Setpoint" + capability "Thermostat Setpoint" + capability "Thermostat Mode" + capability "Thermostat Operating State" + capability "Thermostat" + capability "Polling" + capability "Refresh" + attribute "tadoMode", "string" + command "temperatureUp" + command "temperatureDown" + command "heatingSetpointUp" + command "heatingSetpointDown" + command "on" + command "endManualControl" + command "emergencyHeat" + + } + + // simulator metadata + simulator { + // status messages + + // reply messages + } + +tiles(scale: 2){ + multiAttributeTile(name: "thermostat", type:"thermostat", width:6, height:4) { + tileAttribute("device.temperature", key:"PRIMARY_CONTROL", canChangeIcon: true, canChangeBackground: true){ + attributeState "default", label:'${currentValue}°', backgroundColor:"#fab907", icon:"https://raw.githubusercontent.com/fuzzysb/SmartThings/master/DeviceTypes/fuzzysb/tado.Hot.Water.src/Images/tap_icon.png" + } + tileAttribute("device.temperature", key: "VALUE_CONTROL") { + attributeState("VALUE_UP", action: "temperatureUp") + attributeState("VALUE_DOWN", action: "temperatureDown") + } + tileAttribute("device.thermostatOperatingState", key: "OPERATING_STATE") { + attributeState("idle", backgroundColor:"#666666") + attributeState("heating", backgroundColor:"#ff471a") + attributeState("emergency heat", backgroundColor:"#ff471a") + } + tileAttribute("device.thermostatMode", key: "THERMOSTAT_MODE") { + attributeState("off", label:'${name}') + attributeState("heat", label:'${name}') + } + tileAttribute("device.heatingSetpoint", key: "HEATING_SETPOINT") { + attributeState("default", label:'${currentValue}', unit:"dF") + } + } + + valueTile("heatingSetpoint", "device.heatingSetpoint", width: 2, height: 1, decoration: "flat") { + state "default", label: 'Set Point\r\n\${currentValue}°' + } + + standardTile("tadoMode", "device.tadoMode", width: 2, height: 2, canChangeIcon: true, canChangeBackground: true) { + state("SLEEP", label:'${name}', backgroundColor:"#0164a8", icon:"st.Bedroom.bedroom2") + state("HOME", label:'${name}', backgroundColor:"#fab907", icon:"st.Home.home2") + state("AWAY", label:'${name}', backgroundColor:"#62aa12", icon:"st.Outdoor.outdoor18") + state("OFF", label:'', backgroundColor:"#ffffff", icon:"https://raw.githubusercontent.com/fuzzysb/SmartThings/master/DeviceTypes/fuzzysb/tado.Hot.Water.src/Images/hvac_off.png", defaultState: true) + state("MANUAL", label:'${name}', backgroundColor:"#804000", icon:"st.Weather.weather1") + } + + standardTile("thermostatMode", "device.thermostatMode", width: 2, height: 2, canChangeIcon: true, canChangeBackground: true) { + state("heat", label:'HEAT', backgroundColor:"#ea2a2a", icon:"https://raw.githubusercontent.com/fuzzysb/SmartThings/master/DeviceTypes/fuzzysb/tado.Hot.Water.src/Images/heat_mode_icon.png") + state("off", label:'', backgroundColor:"#ffffff", icon:"https://raw.githubusercontent.com/fuzzysb/SmartThings/master/DeviceTypes/fuzzysb/tado.Hot.Water.src/Images/hvac_off.png", defaultState: true) + } + + standardTile("refresh", "device.switch", width: 2, height: 1, decoration: "flat") { + state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh" + } + standardTile("Off", "device.thermostat", width: 2, height: 1, decoration: "flat") { + state "default", label:"", action:"thermostat.off", icon:"https://raw.githubusercontent.com/fuzzysb/SmartThings/master/DeviceTypes/fuzzysb/tado.Hot.Water.src/Images/hvac_off.png" + } + standardTile("emergencyHeat", "device.thermostat", width: 2, height: 1, decoration: "flat") { + state "default", label:"", action:"thermostat.emergencyHeat", icon:"https://raw.githubusercontent.com/fuzzysb/SmartThings/master/DeviceTypes/fuzzysb/tado.Hot.Water.src/Images/emergencyHeat.png" + } + valueTile("outsidetemperature", "device.outsidetemperature", width: 2, height: 1, decoration: "flat") { + state "outsidetemperature", label: 'Outside Temp\r\n${currentValue}°' + } + standardTile("heat", "device.thermostat", width: 2, height: 1, decoration: "flat") { + state "default", label:"", action:"thermostat.heat", icon:"https://raw.githubusercontent.com/fuzzysb/SmartThings/master/DeviceTypes/fuzzysb/tado.Hot.Water.src/Images/hvac_heat.png" + } + standardTile("heatingSetpointUp", "device.heatingSetpoint", width: 1, height: 1, canChangeIcon: false, decoration: "flat") { + state "heatingSetpointUp", label:'', action:"heatingSetpointUp", icon:"https://raw.githubusercontent.com/fuzzysb/SmartThings/master/DeviceTypes/fuzzysb/tado.Hot.Water.src/Images/heat_arrow_up.png" + } + standardTile("heatingSetpointDown", "device.heatingSetpoint", width: 1, height: 1, canChangeIcon: false, decoration: "flat") { + state "heatingSetpointDown", label:'', action:"heatingSetpointDown", icon:"https://raw.githubusercontent.com/fuzzysb/SmartThings/master/DeviceTypes/fuzzysb/tado.Hot.Water.src/Images/heat_arrow_down.png" + } + standardTile("endManualControl", "device.thermostat", width: 2, height: 1, canChangeIcon: false, canChangeBackground: true, decoration: "flat") { + state("default", label:'', action:"endManualControl", icon:"https://raw.githubusercontent.com/fuzzysb/SmartThings/master/DeviceTypes/fuzzysb/tado.Hot.Water.src/Images/endManual.png") + } + main "thermostat" + details (["thermostat","thermostatMode","outsidetemperature","heatingSetpoint","refresh","heatingSetpointUp","heatingSetpointDown","tadoMode","emergencyHeat","heat","Off","endManualControl"]) + } +} + +def getWeather(){ + parent.weatherStatusCommand(this) +} + +def setCapabilitySupportsWater(value){ + state.supportsWater = value + log.debug("state.supportsWater = ${state.supportsWater}") +} + +def getCapabilitySupportsWater() { + def map = null + map = [name: "capabilitySupportsWater", value: state.supportsWater] + return map +} + +def setCapabilitySupportsWaterTempControl(value){ + state.supportsWaterTempControl = value + log.debug("state.supportsWaterTempControl = ${state.supportsWaterTempControl}") +} + +def getCapabilitySupportsWaterTempControl() { + def map = null + map = [name: "capabilitySupportsWaterTempControl", value: state.supportsWaterTempControl] + return map +} + +def updated(){ + refresh() +} + +def installed(){ + refresh() +} + +def poll() { + log.debug "Executing 'poll'" + refresh() +} + +def refresh() { + log.debug "Executing 'refresh'" + parent.statusCommand(this) + getWeather() +} + +def auto() { + log.debug "Executing 'auto'" + parent.autoCommand(this) + parent.statusCommand(this) +} + +def on() { + log.debug "Executing 'on'" + onCommand() + statusCommand() +} + +def off() { + log.debug "Executing 'off'" + offCommand() + statusCommand() +} + +def setHeatingSetpoint(targetTemperature) { + log.debug "Executing 'setHeatingSetpoint'" + log.debug "Target Temperature ${targetTemperature}" + setHeatingTempCommand(targetTemperature) + statusCommand() +} + +def temperatureUp(){ + if (device.currentValue("thermostatMode") == "heat") { + heatingSetpointUp() + } else { + log.debug ("temperature setpoint not supported in the current thermostat mode") + } +} + +def temperatureDown(){ + if (device.currentValue("thermostatMode") == "heat") { + heatingSetpointDown() + } else { + log.debug ("temperature setpoint not supported in the current thermostat mode") + } +} + +def heatingSetpointUp(){ + log.debug "Current SetPoint Is " + (device.currentValue("thermostatSetpoint")).toString() + if(state.supportsWaterTempControl == "true"){ + if ((device.currentValue("thermostatSetpoint").toInteger() - 1 ) < state.MinHeatTemp){ + log.debug("cannot decrease heat setpoint, its already at the minimum level of " + state.MinHeatTemp) + } else { + int newSetpoint = (device.currentValue("thermostatSetpoint")).toInteger() + 1 + log.debug "Setting heatingSetpoint up to: ${newSetpoint}" + setHeatingSetpoint(newSetpoint) + statusCommand() + } + } else { + log.debug "Hot Water Temperature Capability Not Supported" + } +} + +def heatingSetpointDown(){ + log.debug "Current SetPoint Is " + (device.currentValue("thermostatSetpoint")).toString() + if(state.supportsWaterTempControl == "true"){ + if ((device.currentValue("thermostatSetpoint").toInteger() + 1 ) > state.MaxHeatTemp){ + log.debug("cannot increase heat setpoint, its already at the maximum level of " + state.MaxHeatTemp) + } else { + int newSetpoint = (device.currentValue("thermostatSetpoint")).toInteger() - 1 + log.debug "Setting heatingSetpoint down to: ${newSetpoint}" + setHeatingSetpoint(newSetpoint) + statusCommand() + } + } else { + log.debug "Hot Water Temperature Capability Not Supported" + } +} + +// Commands to device + + +def setThermostatMode(requiredMode){ + switch (requiredMode) { + case "heat": + heat() + break + case "auto": + auto() + break + case "off": + off() + break + case "emergency heat": + emergencyHeat() + break + } +} + +def heat(){ + parent.heatCommand(this) + parent.statusCommand(this) +} + +def emergencyHeat(){ + parent.emergencyHeat(this) +} + +def endManualControl(){ + parent.endManualControl(this) +} diff --git a/smartapps/fuzzysb/tado-connect.src/tado-connect.groovy b/smartapps/fuzzysb/tado-connect.src/tado-connect.groovy new file mode 100644 index 0000000..f26e4fd --- /dev/null +++ b/smartapps/fuzzysb/tado-connect.src/tado-connect.groovy @@ -0,0 +1,1545 @@ +/** + * Tado Connect + * + * Copyright 2016 Stuart Buchanan + * + * 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. + * + * 26/11/2016 V1.0 initial release + */ + +import java.text.DecimalFormat +import groovy.json.JsonSlurper +import groovy.json.JsonOutput + +private apiUrl() { "https://my.tado.com" } +private getVendorName() { "Tado" } +private getVendorIcon() { "https://dl.dropboxusercontent.com/s/fvjrqcy5xjxsr31/tado_128.png" } +private getClientId() { appSettings.clientId } +private getClientSecret() { appSettings.clientSecret } +private getServerUrl() { if(!appSettings.serverUrl){return getApiServerUrl()} } + + // Automatically generated. Make future change here. +definition( + name: "Tado (Connect)", + namespace: "fuzzysb", + author: "Stuart Buchanan", + description: "Tado Integration, This SmartApp supports all Tado Products. (Heating Thermostats, Extension Kits, AC Cooling & Radiator Valves.)", + category: "SmartThings Labs", + iconUrl: "https://dl.dropboxusercontent.com/s/fvjrqcy5xjxsr31/tado_128.png", + iconX2Url: "https://dl.dropboxusercontent.com/s/jyad58wb28ibx2f/tado_256.png", + oauth: true, + singleInstance: true +) { + appSetting "clientId" + appSetting "clientSecret" + appSetting "serverUrl" +} + +preferences { + page(name: "startPage", title: "Tado (Connect) Integration", content: "startPage", install: false) + page(name: "Credentials", title: "Fetch Tado Credentials", content: "authPage", install: false) + page(name: "mainPage", title: "Tado (Connect) Integration", content: "mainPage") + page(name: "completePage", title: "${getVendorName()} is now connected to SmartThings!", content: "completePage") + page(name: "listDevices", title: "Tado Devices", content: "listDevices", install: false) + page(name: "advancedOptions", title: "Tado Advanced Options", content: "advancedOptions", install: false) + page(name: "badCredentials", title: "Invalid Credentials", content: "badAuthPage", install: false) +} +mappings { + path("/receivedHomeId"){action: [POST: "receivedHomeId", GET: "receivedHomeId"]} +} + +def startPage() { + if (state.homeId) { return mainPage() } + else { return authPage() } +} + +def authPage() { + log.debug "In authPage" + def description = null + log.debug "Prompting for Auth Details." + description = "Tap to enter Credentials." + return dynamicPage(name: "Credentials", title: "Authorize Connection", nextPage:mainPage, uninstall: false , install:false) { + section("Generate Username and Password") { + input "username", "text", title: "Your Tado Username", required: true + input "password", "password", title: "Your Tado Password", required: true + } + } +} + +def mainPage() { + if (!state.accessToken){ + createAccessToken() + getToken() + } + getidCommand() + getTempUnitCommand() + log.debug "Logging debug: ${state.homeId}" + if (state.homeId) { + return completePage() + } else { + return badAuthPage() + } +} + +def completePage(){ + def description = "Tap 'Next' to proceed" + return dynamicPage(name: "completePage", title: "Credentials Accepted!", uninstall:true, install:false,nextPage: listDevices) { + section { href url: buildRedirectUrl("receivedHomeId"), style:"embedded", required:false, title:"${getVendorName()} is now connected to SmartThings!", description:description } + } +} + +def badAuthPage(){ + log.debug "In badAuthPage" + log.error "login result false" + return dynamicPage(name: "badCredentials", title: "Bad Tado Credentials", install:false, uninstall:true, nextPage: Credentials) { + section("") { + paragraph "Please check your username and password" + } + } +} + +def advancedOptions() { + log.debug "In Advanced Options" + def options = getDeviceList() + dynamicPage(name: "advancedOptions", title: "Select Advanced Options", install:true) { + section("Default temperatures for thermostat actions. Please enter desired temperatures") { + input("defHeatingTemp", "number", title: "Default Heating Temperature?", required: true) + input("defCoolingTemp", "number", title: "Default Cooling Temperature?", required: true) + } + section("Tado Override Method") { + input("manualmode", "enum", title: "Default Tado Manual Overide Method", options: ["TADO_MODE","MANUAL"], required: true) + } + } +} + +def listDevices() { + log.debug "In listDevices" + def options = getDeviceList() + dynamicPage(name: "listDevices", title: "Choose devices", install:false, uninstall:true, nextPage: advancedOptions) { + section("Devices") { + input "devices", "enum", title: "Select Device(s)", required: false, multiple: true, options: options, submitOnChange: true + } + } +} + +def getToken(){ + if (!state.accessToken) { + try { + getAccessToken() + DEBUG("Creating new Access Token: $state.accessToken") + } catch (ex) { + DEBUG("Did you forget to enable OAuth in SmartApp IDE settings") + DEBUG(ex) + } + } +} + +def receivedHomeId() { + log.debug "In receivedToken" + + def html = """ + + + + + ${getVendorName()} Connection + + + +
+ Vendor icon + connected device icon + SmartThings logo +

Tap 'Done' to continue to Devices.

+
+ + + """ + render contentType: 'text/html', data: html +} + +def buildRedirectUrl(endPoint) { + log.debug "In buildRedirectUrl" + log.debug("returning: " + getServerUrl() + "/api/token/${state.accessToken}/smartapps/installations/${app.id}/${endPoint}") + return getServerUrl() + "/api/token/${state.accessToken}/smartapps/installations/${app.id}/${endPoint}" +} + +def getDeviceList() { + def TadoDevices = getZonesCommand() + return TadoDevices.sort() +} + +def installed() { + log.debug "Installed with settings: ${settings}" + initialize() +} + +def updated() { + log.debug "Updated with settings: ${settings}" + unsubscribe() + unschedule() + initialize() +} + +def uninstalled() { + log.debug "Uninstalling Tado (Connect)" + revokeAccessToken() + removeChildDevices(getChildDevices()) + log.debug "Tado (Connect) Uninstalled" +} + +def initialize() { + log.debug "Initialized with settings: ${settings}" + // Pull the latest device info into state + getDeviceList(); + def children = getChildDevices() + if(settings.devices) { + settings.devices.each { device -> + log.debug("Devices Inspected ${device.inspect()}") + def item = device.tokenize('|') + def deviceType = item[0] + def deviceId = item[1] + def deviceName = item[2] + def existingDevices = children.find{ d -> d.deviceNetworkId.contains(deviceId + "|" + deviceType) } + log.debug("existingDevices Inspected ${existingDevices.inspect()}") + if(!existingDevices) { + log.debug("Some Devices were not found....creating Child Device ${deviceName}") + try { + if (deviceType == "HOT_WATER") + { + log.debug("Creating Hot Water Device ${deviceName}") + createChildDevice("Tado Hot Water Control", deviceId + "|" + deviceType + "|" + state.accessToken, "${deviceName}", deviceName) + } + if (deviceType == "HEATING") + { + log.debug("Creating Heating Device ${deviceName}") + createChildDevice("Tado Heating Thermostat", deviceId + "|" + deviceType + "|" + state.accessToken, "${deviceName}", deviceName) + } + if (deviceType == "AIR_CONDITIONING") + { + log.debug("Creating Air Conditioning Device ${deviceName}") + createChildDevice("Tado Cooling Thermostat", deviceId + "|" + deviceType + "|" + state.accessToken, "${deviceName}", deviceName) + } + } catch (Exception e) { + log.error "Error creating device: ${e}" + } + getInititialDeviceInfo(existingDevices) + } else {getInititialDeviceInfo(existingDevices)} + } + } + // Do the initial poll + poll() + // Schedule it to run every 5 minutes + runEvery5Minutes("poll") +} + +def poll() { + log.debug "In Poll" + getDeviceList(); + def children = getChildDevices() + if(settings.devices) { + settings.devices.each { device -> + log.debug("Devices Inspected ${device.inspect()}") + def item = device.tokenize('|') + def deviceType = item[0] + def deviceId = item[1] + def deviceName = item[2] + def existingDevices = children.find{ d -> d.deviceNetworkId.contains(deviceId + "|" + deviceType) } + if(existingDevices) { + existingDevices.poll() + } + } + } +} + +def createChildDevice(deviceFile, dni, name, label) { + log.debug "In createChildDevice" + try{ + def childDevice = addChildDevice("fuzzysb", deviceFile, dni, null, [name: name, label: label, completedSetup: true]) + } catch (e) { + log.error "Error creating device: ${e}" + } +} + +private sendCommand(method,childDevice,args = []) { + def methods = [ + 'getid': [ + uri: apiUrl(), + path: "/api/v2/me", + requestContentType: "application/json", + query: [username:settings.username, password:settings.password] + ], + 'gettempunit': [ + uri: apiUrl(), + path: "/api/v2/homes/${state.homeId}", + requestContentType: "application/json", + query: [username:settings.username, password:settings.password] + ], + 'getzones': [ + uri: apiUrl(), + path: "/api/v2/homes/" + state.homeId + "/zones", + requestContentType: "application/json", + query: [username:settings.username, password:settings.password] + ], + 'getcapabilities': [ + uri: apiUrl(), + path: "/api/v2/homes/" + state.homeId + "/zones/" + args[0] + "/capabilities", + requestContentType: "application/json", + query: [username:settings.username, password:settings.password] + ], + 'status': [ + uri: apiUrl(), + path: "/api/v2/homes/" + state.homeId + "/zones/" + args[0] + "/state", + requestContentType: "application/json", + query: [username:settings.username, password:settings.password] + ], + 'temperature': [ + uri: apiUrl(), + path: "/api/v2/homes/" + state.homeId + "/zones/" + args[0] + "/overlay", + requestContentType: "application/json", + query: [username:settings.username, password:settings.password], + body: args[1] + ], + 'weatherStatus': [ + uri: apiUrl(), + path: "/api/v2/homes/" + state.homeId + "/weather", + requestContentType: "application/json", + query: [username:settings.username, password:settings.password] + ], + 'deleteEntry': [ + uri: apiUrl(), + path: "/api/v2/homes/" + state.homeId + "/zones/" + args[0] + "/overlay", + requestContentType: "application/json", + query: [username:settings.username, password:settings.password], + ] + ] + + def request = methods.getAt(method) + log.debug "Http Params ("+request+")" + try{ + log.debug "Executing 'sendCommand'" + if (method == "getid"){ + httpGet(request) { resp -> + parseMeResponse(resp) + } + }else if (method == "gettempunit"){ + httpGet(request) { resp -> + parseTempResponse(resp) + } + }else if (method == "getzones"){ + httpGet(request) { resp -> + parseZonesResponse(resp) + } + }else if (method == "getcapabilities"){ + httpGet(request) { resp -> + parseCapabilitiesResponse(resp,childDevice) + } + }else if (method == "status"){ + httpGet(request) { resp -> + parseResponse(resp,childDevice) + } + }else if (method == "temperature"){ + httpPut(request) { resp -> + parseputResponse(resp,childDevice) + } + }else if (method == "weatherStatus"){ + log.debug "calling weatherStatus Method" + httpGet(request) { resp -> + parseweatherResponse(resp,childDevice) + } + }else if (method == "deleteEntry"){ + httpDelete(request) { resp -> + parsedeleteResponse(resp,childDevice) + } + }else{ + httpGet(request) + } + } catch(Exception e){ + log.debug("___exception: " + e) + } +} + +// Parse incoming device messages to generate events +private parseMeResponse(resp) { + log.debug("Executing parseMeResponse: "+resp.data) + log.debug("Output status: "+resp.status) + if(resp.status == 200) { + log.debug("Executing parseMeResponse.successTrue") + state.homeId = resp.data.homes[0].id + log.debug("Got HomeID Value: " + state.homeId) + + }else if(resp.status == 201){ + log.debug("Something was created/updated") + } +} + +private parseputResponse(resp,childDevice) { + log.debug("Executing parseputResponse: "+resp.data) + log.debug("Output status: "+resp.status) +} + +private parsedeleteResponse(resp,childDevice) { + log.debug("Executing parsedeleteResponse: "+resp.data) + log.debug("Output status: "+resp.status) +} + +private parseResponse(resp,childDevice) { + def item = (childDevice.device.deviceNetworkId).tokenize('|') + def deviceId = item[0] + def deviceType = item[1] + def deviceToken = item[2] + if (deviceType == "AIR_CONDITIONING") + { + log.debug("Executing parseResponse: "+resp.data) + log.debug("Output status: "+resp.status) + def temperatureUnit = state.tempunit + log.debug("Temperature Unit is ${temperatureUnit}") + def humidityUnit = "%" + def ACMode + def ACFanSpeed + def ACFanMode = "off" + def thermostatSetpoint + def tOperatingState + if(resp.status == 200) { + log.debug("Executing parseResponse.successTrue") + def temperature + if (temperatureUnit == "C") { + temperature = (Math.round(resp.data.sensorDataPoints.insideTemperature.celsius *10 ) / 10) + } + else if(temperatureUnit == "F"){ + temperature = (Math.round(resp.data.sensorDataPoints.insideTemperature.fahrenheit * 10) / 10) + } + log.debug("Read temperature: " + temperature) + childDevice?.sendEvent(name:"temperature",value:temperature,unit:temperatureUnit) + log.debug("Send Temperature Event Fired") + def autoOperation = "OFF" + if(resp.data.overlayType == null){ + autoOperation = resp.data.tadoMode + } + else if(resp.data.overlayType == "NO_FREEZE"){ + autoOperation = "OFF" + }else if(resp.data.overlayType == "MANUAL"){ + autoOperation = "MANUAL" + } + log.debug("Read tadoMode: " + autoOperation) + childDevice?.sendEvent(name:"tadoMode",value:autoOperation) + log.debug("Send thermostatMode Event Fired") + + def humidity + if (resp.data.sensorDataPoints.humidity.percentage != null){ + humidity = resp.data.sensorDataPoints.humidity.percentage + }else{ + humidity = "--" + } + log.debug("Read humidity: " + humidity) + childDevice?.sendEvent(name:"humidity",value:humidity,unit:humidityUnit) + + if (resp.data.setting.power == "OFF"){ + tOperatingState = "idle" + ACMode = "off" + ACFanMode = "off" + log.debug("Read thermostatMode: " + ACMode) + ACFanSpeed = "OFF" + log.debug("Read tadoFanSpeed: " + ACFanSpeed) + thermostatSetpoint = "--" + log.debug("Read thermostatSetpoint: " + thermostatSetpoint) + } + else if (resp.data.setting.power == "ON"){ + ACMode = (resp.data.setting.mode).toLowerCase() + log.debug("thermostatMode: " + ACMode) + ACFanSpeed = resp.data.setting.fanSpeed + if (ACFanSpeed == null) { + ACFanSpeed = "--" + } + if (resp.data.overlay != null){ + if (resp.data.overlay.termination.type == "TIMER"){ + if (resp.data.overlay.termination.durationInSeconds == "3600"){ + ACMode = "emergency heat" + log.debug("thermostatMode is heat, however duration shows the state is: " + ACMode) + } + } + } + switch (ACMode) { + case "off": + tOperatingState = "idle" + break + case "heat": + tOperatingState = "heating" + break + case "emergency heat": + tOperatingState = "heating" + break + case "cool": + tOperatingState = "cooling" + break + case "dry": + tOperatingState = "drying" + break + case "fan": + tOperatingState = "fan only" + break + case "auto": + tOperatingState = "heating|cooling" + break + } + log.debug("Read thermostatOperatingState: " + tOperatingState) + log.debug("Read tadoFanSpeed: " + ACFanSpeed) + + if (ACMode == "dry" || ACMode == "auto" || ACMode == "fan"){ + thermostatSetpoint = "--" + }else if(ACMode == "fan") { + ACFanMode = "auto" + }else{ + if (temperatureUnit == "C") { + thermostatSetpoint = Math.round(resp.data.setting.temperature.celsius) + } + else if(temperatureUnit == "F"){ + thermostatSetpoint = Math.round(resp.data.setting.temperature.fahrenheit) + } + } + log.debug("Read thermostatSetpoint: " + thermostatSetpoint) + } + }else{ + log.debug("Executing parseResponse.successFalse") + } + childDevice?.sendEvent(name:"thermostatOperatingState",value:tOperatingState) + log.debug("Send thermostatOperatingState Event Fired") + childDevice?.sendEvent(name:"tadoFanSpeed",value:ACFanSpeed) + log.debug("Send tadoFanSpeed Event Fired") + childDevice?.sendEvent(name:"thermostatFanMode",value:ACFanMode) + log.debug("Send thermostatFanMode Event Fired") + childDevice?.sendEvent(name:"thermostatMode",value:ACMode) + log.debug("Send thermostatMode Event Fired") + childDevice?.sendEvent(name:"thermostatSetpoint",value:thermostatSetpoint,unit:temperatureUnit) + log.debug("Send thermostatSetpoint Event Fired") + childDevice?.sendEvent(name:"heatingSetpoint",value:thermostatSetpoint,unit:temperatureUnit) + log.debug("Send heatingSetpoint Event Fired") + childDevice?.sendEvent(name:"coolingSetpoint",value:thermostatSetpoint,unit:temperatureUnit) + log.debug("Send coolingSetpoint Event Fired") + } + if (deviceType == "HEATING") + { + log.debug("Executing parseResponse: "+resp.data) + log.debug("Output status: "+resp.status) + def temperatureUnit = state.tempunit + log.debug("Temperature Unit is ${temperatureUnit}") + def humidityUnit = "%" + def ACMode + def ACFanSpeed + def thermostatSetpoint + def tOperatingState + if(resp.status == 200) { + log.debug("Executing parseResponse.successTrue") + def temperature + if (temperatureUnit == "C") { + temperature = (Math.round(resp.data.sensorDataPoints.insideTemperature.celsius * 10 ) / 10) + } + else if(temperatureUnit == "F"){ + temperature = (Math.round(resp.data.sensorDataPoints.insideTemperature.fahrenheit * 10) / 10) + } + log.debug("Read temperature: " + temperature) + childDevice?.sendEvent(name: 'temperature', value: temperature, unit: temperatureUnit) + log.debug("Send Temperature Event Fired") + def autoOperation = "OFF" + if(resp.data.overlayType == null){ + autoOperation = resp.data.tadoMode + } + else if(resp.data.overlayType == "NO_FREEZE"){ + autoOperation = "OFF" + }else if(resp.data.overlayType == "MANUAL"){ + autoOperation = "MANUAL" + } + log.debug("Read tadoMode: " + autoOperation) + childDevice?.sendEvent(name: 'tadoMode', value: autoOperation) + + if (resp.data.setting.power == "ON"){ + childDevice?.sendEvent(name: 'thermostatMode', value: "heat") + childDevice?.sendEvent(name: 'thermostatOperatingState', value: "heating") + log.debug("Send thermostatMode Event Fired") + if (temperatureUnit == "C") { + thermostatSetpoint = resp.data.setting.temperature.celsius + } + else if(temperatureUnit == "F"){ + thermostatSetpoint = resp.data.setting.temperature.fahrenheit + } + log.debug("Read thermostatSetpoint: " + thermostatSetpoint) + } else if(resp.data.setting.power == "OFF"){ + thermostatSetpoint = "--" + childDevice?.sendEvent(name: 'thermostatMode', value: "off") + childDevice?.sendEvent(name: 'thermostatOperatingState', value: "idle") + log.debug("Send thermostatMode Event Fired") + } + + def humidity + if (resp.data.sensorDataPoints.humidity.percentage != null){ + humidity = resp.data.sensorDataPoints.humidity.percentage + }else{ + humidity = "--" + } + log.debug("Read humidity: " + humidity) + + childDevice?.sendEvent(name: 'humidity', value: humidity,unit: humidityUnit) + + } + + else{ + log.debug("Executing parseResponse.successFalse") + } + + childDevice?.sendEvent(name: 'thermostatSetpoint', value: thermostatSetpoint, unit: temperatureUnit) + log.debug("Send thermostatSetpoint Event Fired") + childDevice?.sendEvent(name: 'heatingSetpoint', value: thermostatSetpoint, unit: temperatureUnit) + log.debug("Send heatingSetpoint Event Fired") + } + if (deviceType == "HOT_WATER") + { + log.debug("Executing parseResponse: "+resp.data) + log.debug("Output status: "+resp.status) + def temperatureUnit = state.tempunit + log.debug("Temperature Unit is ${temperatureUnit}") + def humidityUnit = "%" + def ACMode + def ACFanSpeed + def thermostatSetpoint + def tOperatingState + if(resp.status == 200) { + log.debug("Executing parseResponse.successTrue") + def temperature + if (state.supportsWaterTempControl == "true" && resp.data.tadoMode != null && resp.data.setting.power != "OFF"){ + if (temperatureUnit == "C") { + temperature = (Math.round(resp.data.setting.temperature.celsius * 10 ) / 10) + } + else if(temperatureUnit == "F"){ + temperature = (Math.round(resp.data.setting.temperature.fahrenheit * 10) / 10) + } + log.debug("Read temperature: " + temperature) + childDevice?.sendEvent(name: 'temperature', value: temperature, unit: temperatureUnit) + log.debug("Send Temperature Event Fired") + } else { + childDevice?.sendEvent(name: 'temperature', value: "--", unit: temperatureUnit) + log.debug("Send Temperature Event Fired") + } + def autoOperation = "OFF" + if(resp.data.overlayType == null){ + autoOperation = resp.data.tadoMode + } + else if(resp.data.overlayType == "NO_FREEZE"){ + autoOperation = "OFF" + }else if(resp.data.overlayType == "MANUAL"){ + autoOperation = "MANUAL" + } + log.debug("Read tadoMode: " + autoOperation) + childDevice?.sendEvent(name: 'tadoMode', value: autoOperation) + + if (resp.data.setting.power == "ON"){ + childDevice?.sendEvent(name: 'thermostatMode', value: "heat") + childDevice?.sendEvent(name: 'thermostatOperatingState', value: "heating") + log.debug("Send thermostatMode Event Fired") + } else if(resp.data.setting.power == "OFF"){ + childDevice?.sendEvent(name: 'thermostatMode', value: "off") + childDevice?.sendEvent(name: 'thermostatOperatingState', value: "idle") + log.debug("Send thermostatMode Event Fired") + } + log.debug("Send thermostatMode Event Fired") + if (state.supportsWaterTempControl == "true" && resp.data.tadoMode != null && resp.data.setting.power != "OFF"){ + if (temperatureUnit == "C") { + thermostatSetpoint = resp.data.setting.temperature.celsius + } + else if(temperatureUnit == "F"){ + thermostatSetpoint = resp.data.setting.temperature.fahrenheit + } + log.debug("Read thermostatSetpoint: " + thermostatSetpoint) + } else { + thermostatSetpoint = "--" + } + } + + else{ + log.debug("Executing parseResponse.successFalse") + } + + childDevice?.sendEvent(name: 'thermostatSetpoint', value: thermostatSetpoint, unit: temperatureUnit) + log.debug("Send thermostatSetpoint Event Fired") + childDevice?.sendEvent(name: 'heatingSetpoint', value: thermostatSetpoint, unit: temperatureUnit) + log.debug("Send heatingSetpoint Event Fired") + } +} + +private parseTempResponse(resp) { + log.debug("Executing parseTempResponse: "+resp.data) + log.debug("Output status: "+resp.status) + if(resp.status == 200) { + log.debug("Executing parseTempResponse.successTrue") + def tempunitname = resp.data.temperatureUnit + if (tempunitname == "CELSIUS") { + log.debug("Setting Temp Unit to C") + state.tempunit = "C" + } + else if(tempunitname == "FAHRENHEIT"){ + log.debug("Setting Temp Unit to F") + state.tempunit = "F" + } + }else if(resp.status == 201){ + log.debug("Something was created/updated") + } +} + +private parseZonesResponse(resp) { + log.debug("Executing parseZonesResponse: "+resp.data) + log.debug("Output status: "+resp.status) + if(resp.status == 200) { + def restDevices = resp.data + def TadoDevices = [] + log.debug("Executing parseZoneResponse.successTrue") + restDevices.each { Tado -> TadoDevices << ["${Tado.type}|${Tado.id}|${Tado.name}":"${Tado.name}"] } + log.debug(TadoDevices) + return TadoDevices + }else if(resp.status == 201){ + log.debug("Something was created/updated") + } +} + +private parseCapabilitiesResponse(resp,childDevice) { + log.debug("Executing parseCapabilitiesResponse: "+resp.data) + log.debug("Output status: " + resp.status) + if(resp.status == 200) { + try + { + log.debug("Executing parseResponse.successTrue") + childDevice?.setCapabilitytadoType(resp.data.type) + log.debug("Tado Type is ${resp.data.type}") + if(resp.data.type == "AIR_CONDITIONING") + { + if(resp.data.AUTO || (resp.data.AUTO).toString() == "[:]"){ + log.debug("settingautocapability state true") + childDevice?.setCapabilitySupportsAuto("true") + } else { + log.debug("settingautocapability state false") + childDevice?.setCapabilitySupportsAuto("false") + } + if(resp.data.COOL || (resp.data.COOL).toString() == "[:]"){ + log.debug("setting COOL capability state true") + childDevice?.setCapabilitySupportsCool("true") + def coolfanmodelist = resp.data.COOL.fanSpeeds + if(coolfanmodelist.find { it == 'AUTO' }){ + log.debug("setting COOL Auto Fan Speed capability state true") + childDevice?.setCapabilitySupportsCoolAutoFanSpeed("true") + } else { + log.debug("setting COOL Auto Fan Speed capability state false") + childDevice?.setCapabilitySupportsCoolAutoFanSpeed("false") + } + if (state.tempunit == "C"){ + childDevice?.setCapabilityMaxCoolTemp(resp.data.COOL.temperatures.celsius.max) + childDevice?.setCapabilityMinCoolTemp(resp.data.COOL.temperatures.celsius.min) + } else if (state.tempunit == "F") { + childDevice?.setCapabilityMaxCoolTemp(resp.data.COOL.temperatures.fahrenheit.max) + childDevice?.setCapabilityMinCoolTemp(resp.data.COOL.temperatures.fahrenheit.min) + } + } else { + log.debug("setting COOL capability state false") + childDevice?.setCapabilitySupportsCool("false") + } + if(resp.data.DRY || (resp.data.DRY).toString() == "[:]"){ + log.debug("setting DRY capability state true") + childDevice?.setCapabilitySupportsDry("true") + } else { + log.debug("setting DRY capability state false") + childDevice?.setCapabilitySupportsDry("false") + } + if(resp.data.FAN || (resp.data.FAN).toString() == "[:]"){ + log.debug("setting FAN capability state true") + childDevice?.setCapabilitySupportsFan("true") + } else { + log.debug("setting FAN capability state false") + childDevice?.setCapabilitySupportsFan("false") + } + if(resp.data.HEAT || (resp.data.HEAT).toString() == "[:]"){ + log.debug("setting HEAT capability state true") + childDevice?.setCapabilitySupportsHeat("true") + def heatfanmodelist = resp.data.HEAT.fanSpeeds + if(heatfanmodelist.find { it == 'AUTO' }){ + log.debug("setting HEAT Auto Fan Speed capability state true") + childDevice?.setCapabilitySupportsHeatAutoFanSpeed("true") + } else { + log.debug("setting HEAT Auto Fan Speed capability state false") + childDevice?.setCapabilitySupportsHeatAutoFanSpeed("false") + } + if (state.tempunit == "C"){ + childDevice?.setCapabilityMaxHeatTemp(resp.data.HEAT.temperatures.celsius.max) + childDevice?.setCapabilityMinHeatTemp(resp.data.HEAT.temperatures.celsius.min) + } else if (state.tempunit == "F") { + childDevice?.setCapabilityMaxHeatTemp(resp.data.HEAT.temperatures.fahrenheit.max) + childDevice?.setCapabilityMinHeatTemp(resp.data.HEAT.temperatures.fahrenheit.min) + } + } else { + log.debug("setting HEAT capability state false") + childDevice?.setCapabilitySupportsHeat("false") + } + } + if(resp.data.type == "HEATING") + { + if(resp.data.type == "HEATING") + { + log.debug("setting HEAT capability state true") + childDevice?.setCapabilitySupportsHeat("true") + if (state.tempunit == "C") + { + childDevice?.setCapabilityMaxHeatTemp(resp.data.HEAT.temperatures.celsius.max) + childDevice?.setCapabilityMinHeatTemp(resp.data.HEAT.temperatures.celsius.min) + } + else if (state.tempunit == "F") + { + childDevice?.setCapabilityMaxHeatTemp(resp.data.HEAT.temperatures.fahrenheit.max) + childDevice?.setCapabilityMinHeatTemp(resp.data.HEAT.temperatures.fahrenheit.min) + } + } + else + { + log.debug("setting HEAT capability state false") + childDevice?.setCapabilitySupportsHeat("false") + } + } + if(resp.data.type == "HOT_WATER") + { + if(resp.data.type == "HOT_WATER"){ + log.debug("setting WATER capability state true") + dchildDevice?.setCapabilitySupportsWater("true") + if (resp.data.canSetTemperature == true){ + childDevice?.setCapabilitySupportsWaterTempControl("true") + if (state.tempunit == "C") + { + childDevice?.setCapabilityMaxHeatTemp(resp.data.HEAT.temperatures.celsius.max) + childDevice?.setCapabilityMinHeatTemp(resp.data.HEAT.temperatures.celsius.min) + } + else if (state.tempunit == "F") + { + childDevice?.setCapabilityMaxHeatTemp(resp.data.HEAT.temperatures.fahrenheit.max) + childDevice?.setCapabilityMinHeatTemp(resp.data.HEAT.temperatures.fahrenheit.min) + } + } + else + { + childDevice?.setCapabilitySupportsWaterTempControl("false") + } + } + else + { + log.debug("setting Water capability state false") + childDevice?.setCapabilitySupportsWater("false") + } + } + } + catch(Exception e) + { + log.debug("___exception: " + e) + } + } + else if(resp.status == 201) + { + log.debug("Something was created/updated") + } +} + +private parseweatherResponse(resp,childDevice) { + log.debug("Executing parseweatherResponse: "+resp.data) + log.debug("Output status: "+resp.status) + def temperatureUnit = state.tempunit + log.debug("Temperature Unit is ${temperatureUnit}") + if(resp.status == 200) { + log.debug("Executing parseResponse.successTrue") + def outsidetemperature + if (temperatureUnit == "C") { + outsidetemperature = resp.data.outsideTemperature.celsius + } + else if(temperatureUnit == "F"){ + outsidetemperature = resp.data.outsideTemperature.fahrenheit + } + log.debug("Read outside temperature: " + outsidetemperature) + childDevice?.sendEvent(name: 'outsidetemperature', value: outsidetemperature, unit: temperatureUnit) + log.debug("Send Outside Temperature Event Fired") + return result + + }else if(resp.status == 201){ + log.debug("Something was created/updated") + } +} + +def getInititialDeviceInfo(childDevice){ + getCapabilitiesCommand(childDevice, childDevice.device.deviceNetworkId) + statusCommand(childDevice) +} + +def getidCommand(){ + log.debug "Executing 'sendCommand.getidCommand'" + sendCommand("getid",null,[]) +} + +def getTempUnitCommand(){ + log.debug "Executing 'sendCommand.getidCommand'" + sendCommand("gettempunit",null,[]) +} + +def getZonesCommand(){ + log.debug "Executing 'sendCommand.getzones'" + sendCommand("getzones",null,[]) +} + +def weatherStatusCommand(childDevice){ + def item = (childDevice.device.deviceNetworkId).tokenize('|') + def deviceId = item[0] + def deviceType = item[1] + def deviceToken = item[2] + log.debug "Executing 'sendCommand.weatherStatusCommand'" + def result = sendCommand("weatherStatus",childDevice,[deviceId]) +} + +def getCapabilitiesCommand(childDevice, deviceDNI){ + def item = deviceDNI.tokenize('|') + def deviceId = item[0] + def deviceType = item[1] + def deviceToken = item[2] + log.debug "Executing 'sendCommand.getcapabilities'" + sendCommand("getcapabilities",childDevice,[deviceId]) +} + +private removeChildDevices(delete) { + try { + delete.each { + deleteChildDevice(it.deviceNetworkId) + log.info "Successfully Removed Child Device: ${it.displayName} (${it.deviceNetworkId})" + } + } + catch (e) { log.error "There was an error (${e}) when trying to delete the child device" } +} + +def parseCapabilityData(Map result){ + results.each { name, value -> + return value + } + //return value +} + +//Device Commands Below Here +def autoCommand(childDevice){ + log.debug "Executing 'sendCommand.autoCommand' on device ${childDevice.device.name}" + def terminationmode = settings.manualmode + def traperror + def item = (childDevice.device.deviceNetworkId).tokenize('|') + def deviceId = item[0] + def deviceType = item[1] + def deviceToken = item[2] + if (deviceType == "AIR_CONDITIONING") + { + def capabilitySupportsAuto = parseCapabilityData(childDevice.getCapabilitySupportsAuto()) + def capabilitysupported = capabilitySupportsAuto + if (capabilitysupported == "true"){ + log.debug "Executing 'sendCommand.autoCommand' on device ${childDevice.device.name}" + def jsonbody = new groovy.json.JsonOutput().toJson([setting:[mode:"AUTO", power:"ON", type:"AIR_CONDITIONING"], termination:[type:terminationmode]]) + sendCommand("temperature",dchildDevice,[deviceId,jsonbody]) + statusCommand(device) + } else { + log.debug("Sorry Auto Capability not supported on device ${childDevice.device.name}") + } + } + if(deviceType == "HEATING") + { + def initialsetpointtemp + try { + traperror = ((childDevice.device.currentValue("thermostatSetpoint")).intValue()) + } + catch (NumberFormatException e){ + traperror = 0 + } + if(traperror == 0){ + initialsetpointtemp = settings.defHeatingTemp + } else { + initialsetpointtemp = childDevice.device.currentValue("thermostatSetpoint") + } + def jsonbody = new groovy.json.JsonOutput().toJson([setting:[power:"ON", temperature:[celsius:initialsetpointtemp], type:"HEATING"], termination:[type:terminationmode]]) + sendCommand("temperature",childDevice,[deviceId,jsonbody]) + statusCommand(childDevice) + } + if (deviceType == "HOT_WATER") + { + log.debug "Executing 'sendCommand.autoCommand'" + def initialsetpointtemp + def jsonbody + def capabilitySupportsWaterTempControl = parseCapabilityData(childDevice.getCapabilitySupportsWaterTempControl()) + if(capabilitySupportsWaterTempControl == "true"){ + try { + traperror = ((childDevice.device.currentValue("thermostatSetpoint")).intValue()) + }catch (NumberFormatException e){ + traperror = 0 + } + if(traperror == 0){ + initialsetpointtemp = settings.defHeatingTemp + } else { + initialsetpointtemp = childDevice.device.currentValue("thermostatSetpoint") + } + jsonbody = new groovy.json.JsonOutput().toJson([setting:[power:"ON", temperature:[celsius:initialsetpointtemp], type:"HOT_WATER"], termination:[type:terminationmode]]) + } else { + jsonbody = new groovy.json.JsonOutput().toJson([setting:[power:"ON", type:"HOT_WATER"], termination:[type:terminationmode]]) + } + sendCommand("temperature",childDevice,[deviceId,jsonbody]) + statusCommand(childDevice) + } +} + +def dryCommand(childDevice){ + def item = (childDevice.device.deviceNetworkId).tokenize('|') + def deviceId = item[0] + def deviceType = item[1] + def deviceToken = item[2] + def capabilitySupportsDry = parseCapabilityData(childDevice.getCapabilitySupportsDry()) + def capabilitysupported = capabilitySupportsDry + if (capabilitysupported == "true"){ + def terminationmode = settings.manualmode + log.debug "Executing 'sendCommand.dryCommand' on device ${childDevice.device.name}" + def jsonbody = new groovy.json.JsonOutput().toJson([setting:[mode:"DRY", power:"ON", type:"AIR_CONDITIONING"], termination:[type:terminationmode]]) + sendCommand("temperature",childDevice,[deviceId,jsonbody]) + statusCommand(childDevice) + } else { + log.debug("Sorry Dry Capability not supported on device ${childDevice.device.name}") + } +} + +def fanAuto(childDevice){ + def item = (childDevice.device.deviceNetworkId).tokenize('|') + def deviceId = item[0] + def deviceType = item[1] + def deviceToken = item[2] + def capabilitySupportsFan = parseCapabilityData(childDevice.getCapabilitySupportsFan()) + def capabilitysupported = capabilitySupportsFan + if (capabilitysupported == "true"){ + def terminationmode = settings.manualmode + log.debug "Executing 'sendCommand.fanAutoCommand' on device ${childDevice.device.name}" + def jsonbody = new groovy.json.JsonOutput().toJson([setting:[mode:"FAN", power:"ON", type:"AIR_CONDITIONING"], termination:[type:terminationmode]]) + sendCommand("temperature",childDevice,[deviceId,jsonbody]) + statusCommand(childDevice) + } else { + log.debug("Sorry Fan Capability not supported by your HVAC Device") + } +} + +def endManualControl(childDevice){ + log.debug "Executing 'sendCommand.endManualControl' on device ${childDevice.device.name}" + def item = (childDevice.device.deviceNetworkId).tokenize('|') + def deviceId = item[0] + def deviceType = item[1] + def deviceToken = item[2] + sendCommand("deleteEntry",childDevice,[deviceId]) + statusCommand(childDevice) +} + +def cmdFanSpeedAuto(childDevice){ + def supportedfanspeed + def terminationmode = settings.manualmode + def item = (childDevice.device.dni).tokenize('|') + def deviceId = item[0] + def deviceType = item[1] + def deviceToken = item[2] + def jsonbody + def capabilitySupportsCool = parseCapabilityData(childDevice.getCapabilitySupportsCool()) + def capabilitysupported = capabilitySupportsCool + def capabilitySupportsCoolAutoFanSpeed = parseCapabilityData(childDevice.getCapabilitySupportsCoolAutoFanSpeed()) + def fancapabilitysupported = capabilitySupportsCoolAutoFanSpeed + if (fancapabilitysupported == "true"){ + supportedfanspeed = "AUTO" + } else { + supportedfanspeed = "HIGH" + } + def curSetTemp = (childDevice.device.currentValue("thermostatSetpoint")) + def curMode = ((childDevice.device.currentValue("thermostatMode")).toUpperCase()) + if (curMode == "COOL" || curMode == "HEAT"){ + if (state.tempunit == "C") { + jsonbody = new groovy.json.JsonOutput().toJson([setting:[fanSpeed:supportedfanspeed, mode:curMode, power:"ON", temperature:[celsius:curSetTemp], type:"AIR_CONDITIONING"], termination:[type:terminationmode]]) + } + else if(state.tempunit == "F"){ + jsonbody = new groovy.json.JsonOutput().toJson([setting:[fanSpeed:supportedfanspeed, mode:curMode, power:"ON", temperature:[fahrenheit:curSetTemp], type:"AIR_CONDITIONING"], termination:[type:terminationmode]]) + } + log.debug "Executing 'sendCommand.fanSpeedAuto' to ${supportedfanspeed}" + sendCommand("temperature",childDevice,[deviceId,jsonbody]) + statusCommand(childDevice) + } +} + +def cmdFanSpeedHigh(childDevice){ + def item = (childDevice.device.deviceNetworkId).tokenize('|') + def deviceId = item[0] + def deviceType = item[1] + def deviceToken = item[2] + def jsonbody + def supportedfanspeed = "HIGH" + def terminationmode = settings.manualmode + def curSetTemp = (childDevice.device.currentValue("thermostatSetpoint")) + def curMode = ((childDevice.device.currentValue("thermostatMode")).toUpperCase()) + if (curMode == "COOL" || curMode == "HEAT"){ + if (state.tempunit == "C") { + jsonbody = new groovy.json.JsonOutput().toJson([setting:[fanSpeed:supportedfanspeed, mode:curMode, power:"ON", temperature:[celsius:curSetTemp], type:"AIR_CONDITIONING"], termination:[type:terminationmode]]) + } + else if(state.tempunit == "F"){ + jsonbody = new groovy.json.JsonOutput().toJson([setting:[fanSpeed:supportedfanspeed, mode:curMode, power:"ON", temperature:[fahrenheit:curSetTemp], type:"AIR_CONDITIONING"], termination:[type:terminationmode]]) + } + log.debug "Executing 'sendCommand.fanSpeedAuto' to ${supportedfanspeed}" + sendCommand("temperature",childDevice,[deviceId,jsonbody]) + statusCommand(childDevice) + } +} + +def cmdFanSpeedMid(childDevice){ + def item = (childDevice.device.deviceNetworkId).tokenize('|') + def deviceId = item[0] + def deviceType = item[1] + def deviceToken = item[2] + def supportedfanspeed = "MIDDLE" + def terminationmode = settings.manualmode + def jsonbody + def curSetTemp = (childDevice.device.currentValue("thermostatSetpoint")) + def curMode = ((childDevice.device.currentValue("thermostatMode")).toUpperCase()) + if (curMode == "COOL" || curMode == "HEAT"){ + if (state.tempunit == "C") { + jsonbody = new groovy.json.JsonOutput().toJson([setting:[fanSpeed:supportedfanspeed, mode:curMode, power:"ON", temperature:[celsius:curSetTemp], type:"AIR_CONDITIONING"], termination:[type:terminationmode]]) + } + else if(state.tempunit == "F"){ + jsonbody = new groovy.json.JsonOutput().toJson([setting:[fanSpeed:supportedfanspeed, mode:curMode, power:"ON", temperature:[fahrenheit:curSetTemp], type:"AIR_CONDITIONING"], termination:[type:terminationmode]]) + } + log.debug "Executing 'sendCommand.fanSpeedMid' to ${supportedfanspeed}" + sendCommand("temperature",childDevice,[deviceId,jsonbody]) + statusCommand(childDevice) + } +} + +def cmdFanSpeedLow(childDevice){ + def item = (childDevice.device.deviceNetworkId).tokenize('|') + def deviceId = item[0] + def deviceType = item[1] + def deviceToken = item[2] + def supportedfanspeed = "LOW" + def terminationmode = settings.manualmode + def jsonbody + def curSetTemp = (childDevice.device.currentValue("thermostatSetpoint")) + def curMode = ((childDevice.device.currentValue("thermostatMode")).toUpperCase()) + if (curMode == "COOL" || curMode == "HEAT"){ + if (state.tempunit == "C") { + jsonbody = new groovy.json.JsonOutput().toJson([setting:[fanSpeed:supportedfanspeed, mode:curMode, power:"ON", temperature:[celsius:curSetTemp], type:"AIR_CONDITIONING"], termination:[type:terminationmode]]) + } + else if(state.tempunit == "F"){ + jsonbody = new groovy.json.JsonOutput().toJson([setting:[fanSpeed:supportedfanspeed, mode:curMode, power:"ON", temperature:[fahrenheit:curSetTemp], type:"AIR_CONDITIONING"], termination:[type:terminationmode]]) + } + log.debug "Executing 'sendCommand.fanSpeedLow' to ${supportedfanspeed}" + sendCommand("temperature",childDevice,[deviceId,jsonbody]) + statusCommand(childDevice) + } +} + +def setCoolingTempCommand(childDevice,targetTemperature){ + def terminationmode = settings.manualmode + def item = (childDevice.device.dni).tokenize('|') + def deviceId = item[0] + def deviceType = item[1] + def deviceToken = item[2] + def supportedfanspeed + def capabilitySupportsCool = parseCapabilityData(childDevice.getCapabilitySupportsCool()) + def capabilitysupported = capabilitySupportsCool + def capabilitySupportsCoolAutoFanSpeed = parseCapabilityData(childDevice.getCapabilitySupportsCoolAutoFanSpeed()) + def fancapabilitysupported = capabilitySupportsCoolAutoFanSpeed + def jsonbody + if (fancapabilitysupported == "true"){ + supportedfanspeed = "AUTO" + } else { + supportedfanspeed = "HIGH" + } + if (state.tempunit == "C") { + jsonbody = new groovy.json.JsonOutput().toJson([setting:[fanSpeed:supportedfanspeed, mode:"COOL", power:"ON", temperature:[celsius:targetTemperature], type:"AIR_CONDITIONING"], termination:[type:terminationmode]]) + } + else if(state.tempunit == "F"){ + jsonbody = new groovy.json.JsonOutput().toJson([setting:[fanSpeed:supportedfanspeed, mode:"COOL", power:"ON", temperature:[fahrenheit:targetTemperature], type:"AIR_CONDITIONING"], termination:[type:terminationmode]]) + } + log.debug "Executing 'sendCommand.setCoolingTempCommand' to ${targetTemperature} on device ${childDevice.device.name}" + sendCommand("temperature",childDevice,[deviceId,jsonbody]) +} + +def setHeatingTempCommand(childDevice,targetTemperature){ + def terminationmode = settings.manualmode + def item = (childDevice.device.deviceNetworkId).tokenize('|') + def deviceId = item[0] + def deviceType = item[1] + def deviceToken = item[2] + if(deviceType == "AIR_CONDITIONING") + { + def capabilitySupportsHeat = parseCapabilityData(childDevice.getCapabilitySupportsHeat()) + def capabilitysupported = capabilitySupportsHeat + def capabilitySupportsHeatAutoFanSpeed = parseCapabilityData(childDevice.getCapabilitySupportsHeatAutoFanSpeed()) + def fancapabilitysupported = capabilitySupportsHeatAutoFanSpeed + def supportedfanspeed + def jsonbody + if (fancapabilitysupported == "true") + { + supportedfanspeed = "AUTO" + } + else + { + supportedfanspeed = "HIGH" + } + if (state.tempunit == "C") { + jsonbody = new groovy.json.JsonOutput().toJson([setting:[fanSpeed:supportedfanspeed, mode:"HEAT", power:"ON", temperature:[celsius:targetTemperature], type:"AIR_CONDITIONING"], termination:[type:terminationmode]]) + } + else if(state.tempunit == "F"){ + jsonbody = new groovy.json.JsonOutput().toJson([setting:[fanSpeed:supportedfanspeed, mode:"HEAT", power:"ON", temperature:[fahrenheit:targetTemperature], type:"AIR_CONDITIONING"], termination:[type:terminationmode]]) + } + log.debug "Executing 'sendCommand.setHeatingTempCommand' to ${targetTemperature} on device ${childDevice.device.name}" + sendCommand("temperature",childDevice,[deviceId,jsonbody]) + } + if(deviceType == "HEATING") + { + def jsonbody + if (state.tempunit == "C") { + jsonbody = new groovy.json.JsonOutput().toJson([setting:[power:"ON", temperature:[celsius:targetTemperature], type:"HEATING"], termination:[type:terminationmode]]) + } + else if(state.tempunit == "F"){ + jsonbody = new groovy.json.JsonOutput().toJson([setting:[power:"ON", temperature:[fahrenheit:targetTemperature], type:"HEATING"], termination:[type:terminationmode]]) + } + log.debug "Executing 'sendCommand.setHeatingTempCommand' to ${targetTemperature} on device ${childDevice.device.name}" + sendCommand("temperature",childDevice,[deviceId,jsonbody]) + } + if(deviceType == "HOT_WATER") + { + def jsonbody + def capabilitySupportsWaterTempControl = parseCapabilityData(childDevice.getCapabilitySupportsWaterTempControl()) + if(capabilitySupportsWaterTempControl == "true"){ + if (state.tempunit == "C") { + jsonbody = new groovy.json.JsonOutput().toJson([setting:[power:"ON", temperature:[celsius:targetTemperature], type:"HOT_WATER"], termination:[type:terminationmode]]) + } + else if(state.tempunit == "F"){ + jsonbody = new groovy.json.JsonOutput().toJson([setting:[power:"ON", temperature:[fahrenheit:targetTemperature], type:"HOT_WATER"], termination:[type:terminationmode]]) + } + log.debug "Executing 'sendCommand.setHeatingTempCommand' to ${targetTemperature} on device ${childDevice.device.name}" + sendCommand("temperature",[jsonbody]) + } else { + log.debug "Hot Water Temperature Capability Not Supported on device ${childDevice.device.name}" + } + } +} + +def offCommand(childDevice){ + log.debug "Executing 'sendCommand.offCommand' on device ${childDevice.device.name}" + def terminationmode = settings.manualmode + def item = (childDevice.device.deviceNetworkId).tokenize('|') + def deviceId = item[0] + def deviceType = item[1] + def deviceToken = item[2] + def jsonbody = new groovy.json.JsonOutput().toJson([setting:[type:deviceType, power:"OFF"], termination:[type:terminationmode]]) + sendCommand("temperature",childDevice,[deviceId,jsonbody]) +} + +def onCommand(childDevice){ + log.debug "Executing 'sendCommand.onCommand'" + def item = (childDevice.device.deviceNetworkId).tokenize('|') + def deviceId = item[0] + def deviceType = item[1] + def deviceToken = item[2] + if(deviceType == "AIR_CONDITIONING") + { + coolCommand(childDevice) + } + if(deviceType == "HEATING" || deviceType == "HOT_WATER") + { + heatCommand(childDevice) + } +} + +def coolCommand(childDevice){ + log.debug "Executing 'sendCommand.coolCommand'" + def terminationmode = settings.manualmode + def item = (childDevice.device.dni).tokenize('|') + def deviceId = item[0] + def deviceType = item[1] + def deviceToken = item[2] + def initialsetpointtemp + def supportedfanspeed + def capabilitySupportsCool = parseCapabilityData(childDevice.getCapabilitySupportsCool()) + def capabilitysupported = capabilitySupportsCool + def capabilitySupportsCoolAutoFanSpeed = parseCapabilityData(childDevice.getCapabilitySupportsCoolAutoFanSpeed()) + def fancapabilitysupported = capabilitySupportsCoolAutoFanSpeed + def traperror + try { + traperror = ((childDevice.device.currentValue("thermostatSetpoint")).intValue()) + }catch (NumberFormatException e){ + traperror = 0 + } + if (fancapabilitysupported == "true"){ + supportedfanspeed = "AUTO" + } else { + supportedfanspeed = "HIGH" + } + if(traperror == 0){ + initialsetpointtemp = settings.defCoolingTemp + } else { + initialsetpointtemp = childDevice.device.currentValue("thermostatSetpoint") + } + def jsonbody = new groovy.json.JsonOutput().toJson([setting:[fanSpeed:supportedfanspeed, mode:"COOL", power:"ON", temperature:[celsius:initialsetpointtemp], type:"AIR_CONDITIONING"], termination:[type:terminationmode]]) + sendCommand("temperature",childDevice,[deviceId,jsonbody]) +} + +def heatCommand(childDevice){ + log.debug "Executing 'sendCommand.heatCommand' on device ${childDevice.device.name}" + def terminationmode = settings.manualmode + def item = (childDevice.device.deviceNetworkId).tokenize('|') + def deviceId = item[0] + def deviceType = item[1] + def deviceToken = item[2] + if(deviceType == "AIR_CONDITIONING") + { + def initialsetpointtemp + def supportedfanspeed + def traperror + def capabilitySupportsHeat = parseCapabilityData(childDevice.getCapabilitySupportsHeat()) + def capabilitysupported = capabilitySupportsHeat + def capabilitySupportsHeatAutoFanSpeed = parseCapabilityData(childDevice.getCapabilitySupportsHeatAutoFanSpeed()) + def fancapabilitysupported = capabilitySupportsHeatAutoFanSpeed + try + { + traperror = ((childDevice.device.currentValue("thermostatSetpoint")).intValue()) + } + catch (NumberFormatException e) + { + traperror = 0 + } + if (fancapabilitysupported == "true") + { + supportedfanspeed = "AUTO" + } + else + { + supportedfanspeed = "HIGH" + } + if(traperror == 0) + { + initialsetpointtemp = settings.defHeatingTemp + } + else + { + initialsetpointtemp = childDevice.device.currentValue("thermostatSetpoint") + } + def jsonbody = new groovy.json.JsonOutput().toJson([setting:[fanSpeed:supportedfanspeed, mode:"HEAT", power:"ON", temperature:[celsius:initialsetpointtemp], type:"AIR_CONDITIONING"], termination:[type:terminationmode]]) + sendCommand("temperature",childDevice,[deviceId,jsonbody]) + } + if(deviceType == "HEATING") + { + def initialsetpointtemp + def traperror + try + { + traperror = ((childDevice.device.currentValue("thermostatSetpoint")).intValue()) + } + catch (NumberFormatException e) + { + traperror = 0 + } + if(traperror == 0) + { + initialsetpointtemp = settings.defHeatingTemp + } + else + { + initialsetpointtemp = childDevice.device.currentValue("thermostatSetpoint") + } + def jsonbody = new groovy.json.JsonOutput().toJson([setting:[power:"ON", temperature:[celsius:initialsetpointtemp], type:"HEATING"], termination:[type:terminationmode]]) + sendCommand("temperature",childDevice,[deviceId,jsonbody]) + } + if(deviceType == "WATER") + { + def jsonbody + def initialsetpointtemp + def traperror + def capabilitySupportsWaterTempControl = parseCapabilityData(childDevice.getCapabilitySupportsWaterTempControl()) + if(capabilitySupportsWaterTempControl == "true"){ + try { + traperror = ((childDevice.device.currentValue("thermostatSetpoint")).intValue()) + }catch (NumberFormatException e){ + traperror = 0 + } + if(traperror == 0){ + initialsetpointtemp = settings.defHeatingTemp + } else { + initialsetpointtemp = childDevice.device.currentValue("thermostatSetpoint") + } + jsonbody = new groovy.json.JsonOutput().toJson([setting:[power:"ON", temperature:[celsius:initialsetpointtemp], type:"HOT_WATER"], termination:[type:terminationmode]]) + } else { + jsonbody = new groovy.json.JsonOutput().toJson([setting:[power:"ON", type:"HOT_WATER"], termination:[type:terminationmode]]) + } + sendCommand("temperature",childDevice,[deviceId,jsonbody]) + } +} + +def emergencyHeat(childDevice){ + log.debug "Executing 'sendCommand.heatCommand' on device ${childDevice.device.name}" + def traperror + def item = (childDevice.device.deviceNetworkId).tokenize('|') + def deviceId = item[0] + def deviceType = item[1] + def deviceToken = item[2] + if(deviceType == "AIR_CONDITIONING") + { + def capabilitySupportsHeat = parseCapabilityData(childDevice.getCapabilitySupportsHeat()) + def capabilitysupported = capabilitySupportsHeat + def capabilitySupportsHeatAutoFanSpeed = parseCapabilityData(childDevice.getCapabilitySupportsHeatAutoFanSpeed()) + def fancapabilitysupported = capabilitySupportsHeatAutoFanSpeed + try + { + traperror = Integer.parseInt(childDevice.device.currentValue("thermostatSetpoint")) + } + catch (NumberFormatException e) + { + traperror = 0 + } + if (capabilitysupported == "true") + { + def initialsetpointtemp + def supportedfanspeed + if (fancapabilitysupported == "true") + { + supportedfanspeed = "AUTO" + } + else + { + supportedfanspeed = "HIGH" + } + if(traperror == 0) + { + initialsetpointtemp = settings.defHeatingTemp + } + else + { + initialsetpointtemp = childDevice.device.currentValue("thermostatSetpoint") + } + def jsonbody = new groovy.json.JsonOutput().toJson([setting:[fanSpeed:supportedfanspeed, mode:"HEAT", power:"ON", temperature:[celsius:initialsetpointtemp], type:"AIR_CONDITIONING"], termination:[durationInSeconds:"3600", type:"TIMER"]]) + sendCommand("temperature",childDevice,[deviceId,jsonbody]) + statusCommand(device) + } + else + { + log.debug("Sorry Heat Capability not supported on device ${childDevice.device.name}") + } + } + if(deviceType == "HEATING") + { + def initialsetpointtemp + try + { + traperror = ((childDevice.device.currentValue("thermostatSetpoint")).intValue()) + } + catch (NumberFormatException e) + { + traperror = 0 + } + if(traperror == 0) + { + initialsetpointtemp = settings.defHeatingTemp + } + else + { + initialsetpointtemp = childDevice.device.currentValue("thermostatSetpoint") + } + def jsonbody = new groovy.json.JsonOutput().toJson([setting:[power:"ON", temperature:[celsius:initialsetpointtemp], type:"HEATING"], termination:[durationInSeconds:"3600", type:"TIMER"]]) + sendCommand("temperature",childDevice,[deviceId,jsonbody]) + statusCommand(childDevice) + } + (deviceType == "HOT_WATER") + { + def initialsetpointtemp + def jsonbody + def capabilitySupportsWaterTempControl = parseCapabilityData(childDevice.getCapabilitySupportsWaterTempControl()) + if(capabilitySupportsWaterTempControl == "true"){ + try + { + traperror = ((childDevice.device.currentValue("thermostatSetpoint")).intValue()) + } + catch (NumberFormatException e) + { + traperror = 0 + } + if(traperror == 0) + { + initialsetpointtemp = settings.defHeatingTemp + } + else + { + initialsetpointtemp = childDevice.device.currentValue("thermostatSetpoint") + } + jsonbody = new groovy.json.JsonOutput().toJson([setting:[power:"ON", temperature:[celsius:initialsetpointtemp], type:"HOT_WATER"], termination:[durationInSeconds:"3600", type:"TIMER"]]) + } + else + { + jsonbody = new groovy.json.JsonOutput().toJson([setting:[power:"ON", type:"HOT_WATER"], termination:[durationInSeconds:"3600", type:"TIMER"]]) + } + sendCommand("temperature",childDevice,[deviceId,jsonbody]) + statusCommand(childDevice) + } +} + +def statusCommand(childDevice){ + def item = (childDevice.device.deviceNetworkId).tokenize('|') + def deviceId = item[0] + def deviceType = item[1] + def deviceToken = item[2] + log.debug "Executing 'sendCommand.statusCommand'" + sendCommand("status",childDevice,[deviceId]) +}