Advertisement
Guest User

SmartThings Infintude Bryant/Carrier Device Handler

a guest
Apr 19th, 2020
67
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 30.06 KB | None | 0 0
  1. /*
  2. * Copyright 2015 SmartThings
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
  5. * in compliance with the License. You may obtain a copy of the License at:
  6. *
  7. * http://www.apache.org/licenses/LICENSE-2.0
  8. *
  9. * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
  10. * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
  11. * for the specific language governing permissions and limitations under the License.
  12. *
  13. * (Based on) Ecobee Thermostat
  14. *
  15. * Author: SmartThings
  16. * Date: 2013-06-13
  17. */
  18. metadata {
  19. definition (name: "Infinitude Thermostat", namespace: "SmartThingsMod", author: "SmartThingsMod") {
  20. capability "Actuator"
  21. capability "Thermostat"
  22. capability "Temperature Measurement"
  23. capability "Sensor"
  24. capability "Refresh"
  25. capability "Relative Humidity Measurement"
  26. capability "Health Check"
  27.  
  28. command "generateEvent"
  29. command "resumeProgram"
  30. command "switchMode"
  31. command "switchFanMode"
  32. command "lowerHeatingSetpoint"
  33. command "raiseHeatingSetpoint"
  34. command "lowerCoolSetpoint"
  35. command "raiseCoolSetpoint"
  36. // To satisfy some SA/rules that incorrectly using poll instead of Refresh
  37. command "poll"
  38. command "profileUpdate"
  39.  
  40. attribute "thermostat", "string"
  41. attribute "maxCoolingSetpoint", "number"
  42. attribute "minCoolingSetpoint", "number"
  43. attribute "maxHeatingSetpoint", "number"
  44. attribute "minHeatingSetpoint", "number"
  45. attribute "deviceTemperatureUnit", "string"
  46. attribute "deviceAlive", "enum", ["true", "false"]
  47. attribute "thermostatSchedule", "string"
  48. attribute "damperPosition", "number"
  49. attribute "holdStatus", "string"
  50. attribute "holdUntil", "string"
  51. attribute "outsideTemp", "number"
  52. attribute "zoneId", "string"
  53. }
  54.  
  55. tiles {
  56. multiAttributeTile(name:"temperature", type:"generic", width:3, height:2, canChangeIcon: true) {
  57. tileAttribute("device.temperature", key: "PRIMARY_CONTROL") {
  58. attributeState("temperature", label:'\n ${currentValue}°', icon: "st.alarm.temperature.normal",
  59. backgroundColors:[
  60. // Celsius
  61. [value: 0, color: "#153591"],
  62. [value: 7, color: "#1e9cbb"],
  63. [value: 15, color: "#90d2a7"],
  64. [value: 23, color: "#44b621"],
  65. [value: 28, color: "#f1d801"],
  66. [value: 35, color: "#d04e00"],
  67. [value: 37, color: "#bc2323"],
  68. // Fahrenheit
  69. [value: 40, color: "#153591"],
  70. [value: 44, color: "#1e9cbb"],
  71. [value: 59, color: "#90d2a7"],
  72. [value: 74, color: "#44b621"],
  73. [value: 84, color: "#f1d801"],
  74. [value: 95, color: "#d04e00"],
  75. [value: 96, color: "#bc2323"]
  76. ]
  77. )
  78. }
  79. tileAttribute("device.humidity", key: "SECONDARY_CONTROL") {
  80. attributeState "humidity", label:'${currentValue}%', icon:"st.Weather.weather12"
  81. }
  82. }
  83.  
  84. standardTile("raiseHeatingSetpoint", "device.heatingSetpoint", width:2, height:1, decoration: "flat") {
  85. state "heatingSetpoint", label:'Heat Up', action:"raiseHeatingSetpoint", icon:"st.thermostat.thermostat-up"
  86. }
  87. valueTile("thermostat", "device.thermostat", width:2, height:1, decoration: "flat") {
  88. state "thermostat", label:'Activity:\n${currentValue}', backgroundColor:"#ffffff"
  89. }
  90. standardTile("raiseCoolSetpoint", "device.heatingSetpoint", width:2, height:1, inactiveLabel: false, decoration: "flat") {
  91. state "heatingSetpoint", label:'Cool Up', action:"raiseCoolSetpoint", icon:"st.thermostat.thermostat-up"
  92. }
  93.  
  94. valueTile("heatingSetpoint", "device.heatingSetpoint", width:2, height:1, inactiveLabel: false, decoration: "flat") {
  95. state "heatingSetpoint", label:'${currentValue}° heat', backgroundColor: "#e86d13"
  96. }
  97.  
  98. standardTile("thermostatSchedule", "device.thermostatSchedule", width:2, height:1, inactiveLabel: false, decoration: "flat") {
  99. state "home", action:"profileUpdate", label:'Profile: Home', nextState:"changing", icon: "st.Home.home4"
  100. state "away", action:"profileUpdate", label:'Profile: Away', nextState:"changing", icon: "st.Home.home2"
  101. state "sleep", action:"profileUpdate", label:'Profile: Sleep', nextState:"changing", icon: "st.Home.home1"
  102. state "wake", action:"profileUpdate", label:'Profile: Wake', nextState:"changing", icon: "st.Home.home1"
  103. state "manual", action:"profileUpdate", label:'Profile: Manual', nextState:"changing", icon: "st.Home.home4"
  104. state "changing", label:'Updating...', icon: "st.motion.motion.active"
  105. }
  106.  
  107. valueTile("coolingSetpoint", "device.coolingSetpoint", width:2, height:1, inactiveLabel: false, decoration: "flat") {
  108. state "coolingSetpoint", label:'${currentValue}° cool', backgroundColor: "#00a0dc"
  109. }
  110.  
  111. standardTile("lowerHeatingSetpoint", "device.heatingSetpoint", width:2, height:1, inactiveLabel: false, decoration: "flat") {
  112. state "heatingSetpoint", label:'Heat Down', action:"lowerHeatingSetpoint", icon:"st.thermostat.thermostat-down"
  113. }
  114. standardTile("refresh", "device.thermostatMode", width:2, height:1, inactiveLabel: false, decoration: "flat") {
  115. state "default", action:"refresh.refresh", icon:"st.secondary.refresh"
  116. }
  117. standardTile("lowerCoolSetpoint", "device.coolingSetpoint", width:2, height:1, inactiveLabel: false, decoration: "flat") {
  118. state "coolingSetpoint", label:'Cool Down', action:"lowerCoolSetpoint", icon:"st.thermostat.thermostat-down"
  119. }
  120. valueTile("fanMode", "device.thermostatFanMode", width:2, height:1, inactiveLabel: false, decoration: "flat") {
  121. state "fanMode", label: 'Fan Mode:\n${currentValue}', backgroundColor: "ffffff"
  122. }
  123. valueTile("outsideTemp", "device.outsideAirTemp", width:2, height:1, inactiveLabel: false, decoration: "flat") {
  124. state "outsideTemp", label:'${currentValue}° outside', backgroundColor:"#ffffff", icon: "st.Weather.weather14"
  125. }
  126. valueTile("holdStatus", "device.thermostatHoldStatus", width:2, height:1, inactiveLabel: false, decoration: "flat") {
  127. state "holdStatus", label:'Hold:\n${currentValue}', backgroundColor:"#ffffff"
  128. }
  129. valueTile("holdUntil", "device.thermostatHoldUntil", width:2, height:1, inactiveLabel: false, decoration: "flat") {
  130. state "holdUntil", label:'Hold Until:\n${currentValue}', backgroundColor:"#ffffff"
  131. }
  132. valueTile("damperPosition", "device.thermostatDamper", width:2, height:1, inactiveLabel: false, decoration: "flat") {
  133. state "damperPosition", label:'Damper:\n${currentValue}', backgroundColor:"#ffffff"
  134. }
  135. valueTile("zoneId", "device.zoneId", width:2, height:1, inactiveLabel: false, decoration: "flat") {
  136. state "zoneId", label:'Zone ID:\n${currentValue}', backgroundColor:"#ffffff"
  137. }
  138.  
  139.  
  140.  
  141. // Not Displaying These
  142. standardTile("resumeProgram", "device.resumeProgram", width:2, height:1, inactiveLabel: false, decoration: "flat") {
  143. state "resume", action:"resumeProgram", nextState: "updating", label:'Resume', icon:"st.samsung.da.oven_ic_send"
  144. state "updating", label:"Working", icon: "st.secondary.secondary"
  145. }
  146. standardTile("mode", "device.thermostatMode", width:2, height:1, inactiveLabel: false, decoration: "flat") {
  147. state "off", action:"switchMode", nextState: "updating", icon: "st.thermostat.heating-cooling-off"
  148. state "heat", action:"switchMode", nextState: "updating", icon: "st.thermostat.heat"
  149. state "cool", action:"switchMode", nextState: "updating", icon: "st.thermostat.cool"
  150. state "auto", action:"switchMode", nextState: "updating", icon: "st.thermostat.auto"
  151. state "emergency heat", action:"switchMode", nextState: "updating", icon: "st.thermostat.emergency-heat"
  152. state "updating", label:"Updating...", icon: "st.secondary.secondary"
  153. }
  154. // Not Displaying These
  155.  
  156.  
  157.  
  158. main "temperature"
  159. details(["temperature", "thermostatSchedule", "raiseHeatingSetpoint", "raiseCoolSetpoint",
  160. "thermostat", "heatingSetpoint", "coolingSetpoint", "fanMode", "lowerHeatingSetpoint",
  161. "lowerCoolSetpoint", "damperPosition", "holdStatus", "holdUntil", "refresh", "outsideTemp",
  162. "zoneId"])
  163. }
  164.  
  165. preferences {
  166. //input "holdType", "enum", title: "Hold Type",
  167. // description: "When changing temperature, use Temporary (Until next transition) or Permanent hold (default)",
  168. // required: false, options:["Temporary", "Permanent"]
  169. //input "deadbandSetting", "number", title: "Minimum temperature difference between the desired Heat and Cool " +
  170. // "temperatures in Auto mode:\nNote! This must be the same as configured on the thermostat",
  171. // description: "temperature difference °F", defaultValue: 5,
  172. // required: false
  173. }
  174.  
  175. }
  176.  
  177. void installed() {
  178. // The device refreshes every 5 minutes by default so if we miss 2 refreshes we can consider it offline
  179. // Using 12 minutes because in testing, device health team found that there could be "jitter"
  180. sendEvent(name: "checkInterval", value: 60 * 12, data: [protocol: "cloud"], displayed: false)
  181. }
  182.  
  183. // Device Watch will ping the device to proactively determine if the device has gone offline
  184. // If the device was online the last time we refreshed, trigger another refresh as part of the ping.
  185. def ping() {
  186. def isAlive = device.currentValue("deviceAlive") == "true" ? true : false
  187. if (isAlive) {
  188. refresh()
  189. }
  190. }
  191.  
  192. // parse events into attributes
  193. def parse(String description) {
  194. log.debug "Parsing '${description}'"
  195. }
  196.  
  197. def refresh() {
  198. log.debug "refresh"
  199. sendEvent([name: "thermostat", value: "updating"])
  200. poll2()
  201. }
  202. void poll() {
  203. }
  204. void poll2() {
  205. log.debug "Executing poll using parent SmartApp"
  206. //log.debug "Id: " + device.id + ", Name: " + device.name + ", Label: " + device.label + ", NetworkId: " + device.deviceNetworkId
  207. //parent.refreshChild(device.deviceNetworkId)
  208. parent.syncSystem()
  209. }
  210.  
  211. def profileUpdate() {
  212. sendEvent([name: "thermostat", value: "updating"])
  213. def currentProfile = device.currentValue("thermostatSchedule")
  214. def currentZone = device.currentValue("zoneId")
  215. def nextProfile = "changing"
  216.  
  217. log.debug "-- Entering Profile Update -- " + currentProfile
  218.  
  219. if (currentProfile == "manual") {
  220. nextProfile = "home" }
  221. if (currentProfile == "home") {
  222. nextProfile = "away" }
  223. if (currentProfile == "away") {
  224. nextProfile = "sleep" }
  225. if (currentProfile == "sleep") {
  226. nextProfile = "awake" }
  227. if (currentProfile == "awake") {
  228. nextProfile = "home" }
  229.  
  230. log.debug "Profile Update for Zone : " + currentZone + " from: " + currentProfile + " to: " + nextProfile
  231. parent.changeProfile(currentZone, nextProfile)
  232. sendEvent([name: "thermostatSchedule", value: nextProfile])
  233. runin(15, "refresh")
  234. }
  235.  
  236. def zUpdate(temp, systemStatus, hum, hsp, csp, fan, currSched, oat, hold, otmr, damperposition, zoneid){
  237. log.debug "zupdate: " + temp + ", " + systemStatus + ", " + hum + ", " + hsp + ", " + csp + ", " + fan + ", " + currSched + ", " + oat + ", " + hold + ", " + otmr + ", " + damperposition + ", " + zoneid
  238. sendEvent([name: "temperature", value: temp, unit: "F"])
  239. sendEvent([name: "thermostat", value: systemStatus])
  240. sendEvent([name: "humidity", value: hum])
  241. sendEvent([name: "heatingSetpoint", value: hsp])
  242. sendEvent([name: "coolingSetpoint", value: csp])
  243. sendEvent([name: "thermostatFanMode", value: fan])
  244. sendEvent([name: "outsideAirTemp", value: oat])
  245. sendEvent([name: "thermostatSchedule", value: currSched])
  246. sendEvent([name: "thermostatHoldStatus", value: hold])
  247. sendEvent([name: "thermostatHoldUntil", value: otmr])
  248. sendEvent([name: "thermostatDamper", value: damperposition])
  249. sendEvent([name: "zoneId", value: zoneid])
  250.  
  251. }
  252. def generateEvent(Map results) {
  253. if(results) {
  254. def linkText = getLinkText(device)
  255. def supportedThermostatModes = ["off"]
  256. def thermostatMode = null
  257. def locationScale = getTemperatureScale()
  258.  
  259. results.each { name, value ->
  260. def event = [name: name, linkText: linkText, handlerName: name]
  261. def sendValue = value
  262.  
  263. if (name=="temperature" || name=="heatingSetpoint" || name=="coolingSetpoint" ) {
  264. sendValue = getTempInLocalScale(value, "F") // API return temperature values in F
  265. event << [value: sendValue, unit: locationScale]
  266. } else if (name=="maxCoolingSetpoint" || name=="minCoolingSetpoint" || name=="maxHeatingSetpoint" || name=="minHeatingSetpoint") {
  267. // Old attributes, keeping for backward compatibility
  268. sendValue = getTempInLocalScale(value, "F") // API return temperature values in F
  269. event << [value: sendValue, unit: locationScale, displayed: false]
  270. // Store min/max setpoint in device unit to avoid conversion rounding error when updating setpoints
  271. device.updateDataValue(name+"Fahrenheit", "${value}")
  272. } else if (name=="heatMode" || name=="coolMode" || name=="autoMode" || name=="auxHeatMode"){
  273. if (value == true) {
  274. supportedThermostatModes << ((name == "auxHeatMode") ? "emergency heat" : name - "Mode")
  275. }
  276. return // as we don't want to send this event here, proceed to next name/value pair
  277. } else if (name=="thermostatFanMode"){
  278. sendEvent(name: "supportedThermostatFanModes", value: fanModes(), displayed: false)
  279. event << [value: value, data:[supportedThermostatFanModes: fanModes()]]
  280. } else if (name=="humidity") {
  281. event << [value: value, displayed: false, unit: "%"]
  282. } else if (name == "deviceAlive") {
  283. event['displayed'] = false
  284. } else if (name == "thermostatMode") {
  285. thermostatMode = (value == "auxHeatOnly") ? "emergency heat" : value.toLowerCase()
  286. return // as we don't want to send this event here, proceed to next name/value pair
  287. } else {
  288. event << [value: value.toString()]
  289. }
  290. event << [descriptionText: getThermostatDescriptionText(name, sendValue, linkText)]
  291. sendEvent(event)
  292. }
  293. if (state.supportedThermostatModes != supportedThermostatModes) {
  294. state.supportedThermostatModes = supportedThermostatModes
  295. sendEvent(name: "supportedThermostatModes", value: supportedThermostatModes, displayed: false)
  296. }
  297. if (thermostatMode) {
  298. sendEvent(name: "thermostatMode", value: thermostatMode, data:[supportedThermostatModes:state.supportedThermostatModes], linkText: linkText,
  299. descriptionText: getThermostatDescriptionText("thermostatMode", thermostatMode, linkText), handlerName: "thermostatMode")
  300. }
  301. generateSetpointEvent ()
  302. generateStatusEvent ()
  303. }
  304. }
  305.  
  306. //return descriptionText to be shown on mobile activity feed
  307. private getThermostatDescriptionText(name, value, linkText) {
  308. if(name == "temperature") {
  309. return "temperature is ${value}°${location.temperatureScale}"
  310.  
  311. } else if(name == "heatingSetpoint") {
  312. return "heating setpoint is ${value}°${location.temperatureScale}"
  313.  
  314. } else if(name == "coolingSetpoint"){
  315. return "cooling setpoint is ${value}°${location.temperatureScale}"
  316.  
  317. } else if (name == "thermostatMode") {
  318. return "thermostat mode is ${value}"
  319.  
  320. } else if (name == "thermostatFanMode") {
  321. return "thermostat fan mode is ${value}"
  322.  
  323. } else if (name == "humidity") {
  324. return "humidity is ${value} %"
  325. } else {
  326. return "${name} = ${value}"
  327. }
  328. }
  329.  
  330. void setHeatingSetpoint(setpoint) {
  331. log.debug "***setHeatingSetpoint($setpoint)"
  332. if (setpoint) {
  333. state.heatingSetpoint = setpoint.toDouble()
  334. runIn(2, "updateSetpoints", [overwrite: true])
  335. }
  336. }
  337.  
  338. def setCoolingSetpoint(setpoint) {
  339. log.debug "***setCoolingSetpoint($setpoint)"
  340. if (setpoint) {
  341. state.coolingSetpoint = setpoint.toDouble()
  342. runIn(2, "updateSetpoints", [overwrite: true])
  343. }
  344. }
  345.  
  346. def updateSetpoints() {
  347. def deviceScale = "F" //API return/expects temperature values in F
  348. def data = [targetHeatingSetpoint: null, targetCoolingSetpoint: null]
  349. def heatingSetpoint = getTempInLocalScale("heatingSetpoint")
  350. def coolingSetpoint = getTempInLocalScale("coolingSetpoint")
  351. if (state.heatingSetpoint) {
  352. data = enforceSetpointLimits("heatingSetpoint", [targetValue: state.heatingSetpoint,
  353. heatingSetpoint: heatingSetpoint, coolingSetpoint: coolingSetpoint])
  354. }
  355. if (state.coolingSetpoint) {
  356. heatingSetpoint = data.targetHeatingSetpoint ? getTempInLocalScale(data.targetHeatingSetpoint, deviceScale) : heatingSetpoint
  357. coolingSetpoint = data.targetCoolingSetpoint ? getTempInLocalScale(data.targetCoolingSetpoint, deviceScale) : coolingSetpoint
  358. data = enforceSetpointLimits("coolingSetpoint", [targetValue: state.coolingSetpoint,
  359. heatingSetpoint: heatingSetpoint, coolingSetpoint: coolingSetpoint])
  360. }
  361. state.heatingSetpoint = null
  362. state.coolingSetpoint = null
  363. updateSetpoint(data)
  364. }
  365.  
  366. void resumeProgram() {
  367. log.debug "resumeProgram() is called"
  368.  
  369. sendEvent("name":"thermostat", "value":"resuming schedule", "description":statusText, displayed: false)
  370. def deviceId = device.deviceNetworkId.split(/\./).last()
  371. if (parent.resumeProgram(deviceId)) {
  372. sendEvent("name":"thermostat", "value":"setpoint is updating", "description":statusText, displayed: false)
  373. sendEvent("name":"resumeProgram", "value":"resume", descriptionText: "resumeProgram is done", displayed: false, isStateChange: true)
  374. } else {
  375. sendEvent("name":"thermostat", "value":"failed resume click refresh", "description":statusText, displayed: false)
  376. log.error "Error resumeProgram() check parent.resumeProgram(deviceId)"
  377. }
  378. //xyzrunIn(5, "refresh", [overwrite: true])
  379. }
  380.  
  381. def modes() {
  382. return state.supportedThermostatModes
  383. }
  384.  
  385. def fanModes() {
  386. // Ecobee does not report its supported fanModes; use hard coded values
  387. ["on", "auto"]
  388. }
  389.  
  390. def switchSchedule() {
  391. //TODO
  392. }
  393. def switchMode() {
  394. def currentMode = device.currentValue("thermostatMode")
  395. def modeOrder = modes()
  396. if (modeOrder) {
  397. def next = { modeOrder[modeOrder.indexOf(it) + 1] ?: modeOrder[0] }
  398. def nextMode = next(currentMode)
  399. switchToMode(nextMode)
  400. } else {
  401. log.warn "supportedThermostatModes not defined"
  402. }
  403. }
  404.  
  405. def switchToMode(mode) {
  406. log.debug "switchToMode: ${mode}"
  407. def deviceId = device.deviceNetworkId.split(/\./).last()
  408. // Thermostat's mode for "emergency heat" is "auxHeatOnly"
  409. if (!(parent.setMode(((mode == "emergency heat") ? "auxHeatOnly" : mode), deviceId))) {
  410. log.warn "Error setting mode:$mode"
  411. // Ensure the DTH tile is reset
  412. generateModeEvent(device.currentValue("thermostatMode"))
  413. }
  414. //XYZ runIn(5, "refresh", [overwrite: true])
  415. }
  416.  
  417. def switchFanMode() {
  418. def currentFanMode = device.currentValue("thermostatFanMode")
  419. def fanModeOrder = fanModes()
  420. def next = { fanModeOrder[fanModeOrder.indexOf(it) + 1] ?: fanModeOrder[0] }
  421. switchToFanMode(next(currentFanMode))
  422. }
  423.  
  424. def switchToFanMode(fanMode) {
  425. log.debug "switchToFanMode: $fanMode"
  426. def heatingSetpoint = getTempInDeviceScale("heatingSetpoint")
  427. def coolingSetpoint = getTempInDeviceScale("coolingSetpoint")
  428. def deviceId = device.deviceNetworkId.split(/\./).last()
  429. def sendHoldType = holdType ? ((holdType=="Temporary") ? "nextTransition" : "indefinite") : "indefinite"
  430.  
  431. if (!(parent.setFanMode(heatingSetpoint, coolingSetpoint, deviceId, sendHoldType, fanMode))) {
  432. log.warn "Error setting fanMode:fanMode"
  433. // Ensure the DTH tile is reset
  434. generateFanModeEvent(device.currentValue("thermostatFanMode"))
  435. }
  436. //XYZ runIn(5, "refresh", [overwrite: true])
  437. }
  438.  
  439. def getDataByName(String name) {
  440. state[name] ?: device.getDataValue(name)
  441. }
  442.  
  443. def setThermostatMode(String mode) {
  444. log.debug "setThermostatMode($mode)"
  445. def supportedModes = modes()
  446. if (supportedModes) {
  447. mode = mode.toLowerCase()
  448. def modeIdx = supportedModes.indexOf(mode)
  449. if (modeIdx < 0) {
  450. log.warn("Thermostat mode $mode not valid for this thermostat")
  451. return
  452. }
  453. mode = supportedModes[modeIdx]
  454. switchToMode(mode)
  455. } else {
  456. log.warn "supportedThermostatModes not defined"
  457. }
  458. }
  459.  
  460. def setThermostatFanMode(String mode) {
  461. log.debug "setThermostatFanMode($mode)"
  462. mode = mode.toLowerCase()
  463. def supportedFanModes = fanModes()
  464. def modeIdx = supportedFanModes.indexOf(mode)
  465. if (modeIdx < 0) {
  466. log.warn("Thermostat fan mode $mode not valid for this thermostat")
  467. return
  468. }
  469. mode = supportedFanModes[modeIdx]
  470. switchToFanMode(mode)
  471. }
  472.  
  473. def generateModeEvent(mode) {
  474. sendEvent(name: "thermostatMode", value: mode, data:[supportedThermostatModes: device.currentValue("supportedThermostatModes")],
  475. isStateChange: true, descriptionText: "$device.displayName is in ${mode} mode")
  476. }
  477.  
  478. def generateFanModeEvent(fanMode) {
  479. sendEvent(name: "thermostatFanMode", value: fanMode, data:[supportedThermostatFanModes: device.currentValue("supportedThermostatFanModes")],
  480. isStateChange: true, descriptionText: "$device.displayName fan is in ${fanMode} mode")
  481. }
  482.  
  483. def generateOperatingStateEvent(operatingState) {
  484. sendEvent(name: "thermostatOperatingState", value: operatingState, descriptionText: "$device.displayName is ${operatingState}", displayed: true)
  485. }
  486.  
  487. def off() { setThermostatMode("off") }
  488. def heat() { setThermostatMode("heat") }
  489. def emergencyHeat() { setThermostatMode("emergency heat") }
  490. def cool() { setThermostatMode("cool") }
  491. def auto() { setThermostatMode("auto") }
  492.  
  493. def fanOn() { setThermostatFanMode("on") }
  494. def fanAuto() { setThermostatFanMode("auto") }
  495. def fanCirculate() { setThermostatFanMode("circulate") }
  496.  
  497. // =============== Setpoints ===============
  498. def generateSetpointEvent() {
  499. def mode = device.currentValue("thermostatMode")
  500. def setpoint = getTempInLocalScale("heatingSetpoint") // (mode == "heat") || (mode == "emergency heat")
  501. def coolingSetpoint = getTempInLocalScale("coolingSetpoint")
  502.  
  503. if (mode == "cool") {
  504. setpoint = coolingSetpoint
  505. } else if ((mode == "auto") || (mode == "off")) {
  506. setpoint = roundC((setpoint + coolingSetpoint) / 2)
  507. } // else (mode == "heat") || (mode == "emergency heat")
  508. sendEvent("name":"thermostatSetpoint", "value":setpoint, "unit":location.temperatureScale)
  509. }
  510.  
  511. def raiseHeatingSetpoint() {
  512. sendEvent([name: "thermostat", value: "updating"])
  513. alterSetpoint(true, "heatingSetpoint")
  514. def heatingSetpoint = getTempInLocalScale("heatingSetpoint")
  515. def currentZone = device.currentValue("zoneId")
  516. log.debug "Raising Htsp on Zone: " + currentZone + " to: " + heatingSetpoint
  517. parent.changeHtsp(currentZone, heatingSetpoint)
  518. runin(15, "refresh")
  519. }
  520.  
  521. def lowerHeatingSetpoint() {
  522. sendEvent([name: "thermostat", value: "updating"])
  523. alterSetpoint(false, "heatingSetpoint")
  524. def heatingSetpoint = getTempInLocalScale("heatingSetpoint")
  525. def currentZone = device.currentValue("zoneId")
  526. log.debug "Lowering Htsp on Zone: " + currentZone + " to: " + heatingSetpoint
  527. parent.changeHtsp(currentZone, heatingSetpoint)
  528. runin(15, "refresh")
  529. }
  530.  
  531. def raiseCoolSetpoint() {
  532. sendEvent([name: "thermostat", value: "updating"])
  533. alterSetpoint(true, "coolingSetpoint")
  534. def coolingSetpoint = getTempInLocalScale("coolingSetpoint")
  535. def currentZone = device.currentValue("zoneId")
  536. log.debug "Raising Csp on Zone: " + currentZone + " to: " + coolingSetpoint
  537. parent.changeClsp(currentZone, coolingSetpoint)
  538. runin(15, "refresh")
  539. }
  540.  
  541. def lowerCoolSetpoint() {
  542. sendEvent([name: "thermostat", value: "updating"])
  543. alterSetpoint(false, "coolingSetpoint")
  544. def coolingSetpoint = getTempInLocalScale("coolingSetpoint")
  545. def currentZone = device.currentValue("zoneId")
  546. log.debug "Lowering Csp on Zone: " + currentZone + " to: " + coolingSetpoint
  547. parent.changeClsp(currentZone, coolingSetpoint)
  548. runin(15, "refresh")
  549. }
  550.  
  551.  
  552. // Adjusts nextHeatingSetpoint either .5° C/1° F) if raise true/false
  553. def alterSetpoint(raise, setpoint) {
  554. // don't allow setpoint change if thermostat is off
  555. if (device.currentValue("thermostatMode") == "off") {
  556. return
  557. }
  558. def locationScale = getTemperatureScale()
  559. def deviceScale = "F"
  560. def heatingSetpoint = getTempInLocalScale("heatingSetpoint")
  561. def coolingSetpoint = getTempInLocalScale("coolingSetpoint")
  562. def targetValue = (setpoint == "heatingSetpoint") ? heatingSetpoint : coolingSetpoint
  563. def delta = (locationScale == "F") ? 1 : 0.5
  564. targetValue += raise ? delta : - delta
  565.  
  566. def data = enforceSetpointLimits(setpoint,
  567. [targetValue: targetValue, heatingSetpoint: heatingSetpoint, coolingSetpoint: coolingSetpoint], raise)
  568. // update UI without waiting for the device to respond, this to give user a smoother UI experience
  569. // also, as runIn's have to overwrite and user can change heating/cooling setpoint separately separate runIn's have to be used
  570. if (data.targetHeatingSetpoint) {
  571. sendEvent("name": "heatingSetpoint", "value": getTempInLocalScale(data.targetHeatingSetpoint, deviceScale),
  572. unit: getTemperatureScale(), eventType: "ENTITY_UPDATE", displayed: false)
  573. }
  574. if (data.targetCoolingSetpoint) {
  575. sendEvent("name": "coolingSetpoint", "value": getTempInLocalScale(data.targetCoolingSetpoint, deviceScale),
  576. unit: getTemperatureScale(), eventType: "ENTITY_UPDATE", displayed: false)
  577. }
  578. runIn(5, "updateSetpoint", [data: data, overwrite: true])
  579. }
  580.  
  581. def enforceSetpointLimits(setpoint, data, raise = null) {
  582. def locationScale = getTemperatureScale()
  583. def minSetpoint = (setpoint == "heatingSetpoint") ? device.getDataValue("minHeatingSetpointFahrenheit") : device.getDataValue("minCoolingSetpointFahrenheit")
  584. def maxSetpoint = (setpoint == "heatingSetpoint") ? device.getDataValue("maxHeatingSetpointFahrenheit") : device.getDataValue("maxCoolingSetpointFahrenheit")
  585. minSetpoint = minSetpoint ? Double.parseDouble(minSetpoint) : ((setpoint == "heatingSetpoint") ? 45 : 65) // default 45 heat, 65 cool
  586. maxSetpoint = maxSetpoint ? Double.parseDouble(maxSetpoint) : ((setpoint == "heatingSetpoint") ? 79 : 92) // default 79 heat, 92 cool
  587. def deadband = deadbandSetting ? deadbandSetting : 5 // °F
  588. def delta = (locationScale == "F") ? 1 : 0.5
  589. def targetValue = getTempInDeviceScale(data.targetValue, locationScale)
  590. def heatingSetpoint = getTempInDeviceScale(data.heatingSetpoint, locationScale)
  591. def coolingSetpoint = getTempInDeviceScale(data.coolingSetpoint, locationScale)
  592. // Enforce min/mix for setpoints
  593. if (targetValue > maxSetpoint) {
  594. targetValue = maxSetpoint
  595. } else if (targetValue < minSetpoint) {
  596. targetValue = minSetpoint
  597. } else if ((raise != null) && ((setpoint == "heatingSetpoint" && targetValue == heatingSetpoint) ||
  598. (setpoint == "coolingSetpoint" && targetValue == coolingSetpoint))) {
  599. // Ensure targetValue differes from old. When location scale differs from device,
  600. // converting between C -> F -> C may otherwise result in no change.
  601. targetValue += raise ? delta : - delta
  602. }
  603. // Enforce deadband between setpoints
  604. if (setpoint == "heatingSetpoint") {
  605. heatingSetpoint = targetValue
  606. coolingSetpoint = (heatingSetpoint + deadband > coolingSetpoint) ? heatingSetpoint + deadband : coolingSetpoint
  607. }
  608. if (setpoint == "coolingSetpoint") {
  609. coolingSetpoint = targetValue
  610. heatingSetpoint = (coolingSetpoint - deadband < heatingSetpoint) ? coolingSetpoint - deadband : heatingSetpoint
  611. }
  612. return [targetHeatingSetpoint: heatingSetpoint, targetCoolingSetpoint: coolingSetpoint]
  613. }
  614.  
  615. def updateSetpoint(data) {
  616. def deviceId = device.deviceNetworkId.split(/\./).last()
  617. def sendHoldType = holdType ? ((holdType=="Temporary") ? "nextTransition" : "indefinite") : "indefinite"
  618.  
  619. /* if (parent.setHold(data.targetHeatingSetpoint, data.targetCoolingSetpoint, deviceId, sendHoldType)) {
  620. log.debug "alterSetpoint succeed to change setpoints:${data}"
  621. } else {
  622. log.error "Error alterSetpoint"
  623. }
  624. */
  625.  
  626. //XYZ runIn(5, "refresh", [overwrite: true])
  627. }
  628.  
  629. def generateStatusEvent() {
  630. def mode = device.currentValue("thermostatMode")
  631. def heatingSetpoint = device.currentValue("heatingSetpoint")
  632. def coolingSetpoint = device.currentValue("coolingSetpoint")
  633. def temperature = device.currentValue("temperature")
  634. def statusText = "Right Now: Idle"
  635. def operatingState = "idle"
  636.  
  637. if (mode == "heat" || mode == "emergency heat") {
  638. if (temperature < heatingSetpoint) {
  639. statusText = "Heating to ${heatingSetpoint}°${location.temperatureScale}"
  640. operatingState = "heating"
  641. }
  642. } else if (mode == "cool") {
  643. if (temperature > coolingSetpoint) {
  644. statusText = "Cooling to ${coolingSetpoint}°${location.temperatureScale}"
  645. operatingState = "cooling"
  646. }
  647. } else if (mode == "auto") {
  648. if (temperature < heatingSetpoint) {
  649. statusText = "Heating to ${heatingSetpoint}°${location.temperatureScale}"
  650. operatingState = "heating"
  651. } else if (temperature > coolingSetpoint) {
  652. statusText = "Cooling to ${coolingSetpoint}°${location.temperatureScale}"
  653. operatingState = "cooling"
  654. }
  655. } else if (mode == "off") {
  656. statusText = "Right Now: Off"
  657. } else {
  658. statusText = "?"
  659. }
  660.  
  661. sendEvent("name":"thermostat", "value":statusText, "description":statusText, displayed: true)
  662. sendEvent("name":"thermostatOperatingState", "value":operatingState, "description":operatingState, displayed: false)
  663. }
  664.  
  665. def generateActivityFeedsEvent(notificationMessage) {
  666. sendEvent(name: "notificationMessage", value: "$device.displayName $notificationMessage", descriptionText: "$device.displayName $notificationMessage", displayed: true)
  667. }
  668.  
  669. // Get stored temperature from currentState in current local scale
  670. def getTempInLocalScale(state) {
  671. def temp = device.currentState(state)
  672. def scaledTemp = convertTemperatureIfNeeded(temp.value.toBigDecimal(), temp.unit).toDouble()
  673. return (getTemperatureScale() == "F" ? scaledTemp.round(0).toInteger() : roundC(scaledTemp))
  674. }
  675.  
  676. // Get/Convert temperature to current local scale
  677. def getTempInLocalScale(temp, scale) {
  678. def scaledTemp = convertTemperatureIfNeeded(temp.toBigDecimal(), scale).toDouble()
  679. return (getTemperatureScale() == "F" ? scaledTemp.round(0).toInteger() : roundC(scaledTemp))
  680. }
  681.  
  682. // Get stored temperature from currentState in device scale
  683. def getTempInDeviceScale(state) {
  684. def temp = device.currentState(state)
  685. if (temp && temp.value && temp.unit) {
  686. return getTempInDeviceScale(temp.value.toBigDecimal(), temp.unit)
  687. }
  688. return 0
  689. }
  690.  
  691. def getTempInDeviceScale(temp, scale) {
  692. if (temp && scale) {
  693. //API return/expects temperature values in F
  694. return ("F" == scale) ? temp : celsiusToFahrenheit(temp).toDouble().round(0).toInteger()
  695. }
  696. return 0
  697. }
  698.  
  699. def roundC (tempC) {
  700. return (Math.round(tempC.toDouble() * 2))/2
  701. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement