mirror of
https://github.com/mtan93/SmartThingsPublic.git
synced 2026-03-10 13:21:52 +00:00
Compare commits
1 Commits
DVCSMP-180
...
MSA-1349-1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ee5077aeda |
@@ -1,244 +0,0 @@
|
||||
/**
|
||||
* Copyright 2016 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.
|
||||
*
|
||||
*/
|
||||
metadata {
|
||||
definition (name: "Z-Wave Plus Window Shade", namespace: "smartthings", author: "SmartThings") {
|
||||
capability "Window Shade"
|
||||
capability "Battery"
|
||||
capability "Refresh"
|
||||
capability "Health Check"
|
||||
capability "Actuator"
|
||||
capability "Sensor"
|
||||
|
||||
command "stop"
|
||||
|
||||
capability "Switch Level" // until we get a Window Shade Level capability
|
||||
capability "Switch" // temporary for use with Routines
|
||||
|
||||
// This device handler is specifically for position-aware window coverings
|
||||
//
|
||||
fingerprint type: "0x1107", cc: "0x5E,0x26", deviceJoinName: "Window Shade"
|
||||
fingerprint type: "0x9A00", cc: "0x5E,0x26", deviceJoinName: "Window Shade"
|
||||
fingerprint mfr:"026E", prod:"4353", model:"5A31", deviceJoinName: "Window Blinds"
|
||||
fingerprint mfr:"026E", prod:"5253", model:"5A31", deviceJoinName: "Roller Shade"
|
||||
}
|
||||
|
||||
simulator {
|
||||
status "open": "command: 2603, payload: FF"
|
||||
status "closed": "command: 2603, payload: 00"
|
||||
status "10%": "command: 2603, payload: 0A"
|
||||
status "66%": "command: 2603, payload: 42"
|
||||
status "99%": "command: 2603, payload: 63"
|
||||
status "battery 100%": "command: 8003, payload: 64"
|
||||
status "battery low": "command: 8003, payload: FF"
|
||||
|
||||
// reply messages
|
||||
reply "2001FF,delay 1000,2602": "command: 2603, payload: 10 FF FE"
|
||||
reply "200100,delay 1000,2602": "command: 2603, payload: 60 00 FE"
|
||||
reply "200142,delay 1000,2602": "command: 2603, payload: 10 42 FE"
|
||||
reply "200163,delay 1000,2602": "command: 2603, payload: 10 63 FE"
|
||||
}
|
||||
|
||||
tiles(scale: 2) {
|
||||
multiAttributeTile(name:"windowShade", type: "lighting", width: 6, height: 4, canChangeIcon: true){
|
||||
tileAttribute ("device.windowShade", key: "PRIMARY_CONTROL") {
|
||||
attributeState "open", label:'${name}', action:"close", icon:"st.doors.garage.garage-open", backgroundColor:"#79b821", nextState:"closing"
|
||||
attributeState "closed", label:'${name}', action:"open", icon:"st.doors.garage.garage-closed", backgroundColor:"#ffffff", nextState:"opening"
|
||||
attributeState "partiallyOpen", label:'Open', action:"close", icon:"st.doors.garage.garage-open", backgroundColor:"#79b821", nextState:"closing"
|
||||
attributeState "opening", label:'${name}', action:"stop", icon:"st.doors.garage.garage-opening", backgroundColor:"#79b821", nextState:"partiallyOpen"
|
||||
attributeState "closing", label:'${name}', action:"stop", icon:"st.doors.garage.garage-closing", backgroundColor:"#ffffff", nextState:"partiallyOpen"
|
||||
}
|
||||
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
|
||||
attributeState "level", action:"setLevel"
|
||||
}
|
||||
}
|
||||
|
||||
standardTile("refresh", "device.refresh", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
|
||||
state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh", nextState: "disabled"
|
||||
state "disabled", label:'', action:"", icon:"st.secondary.refresh"
|
||||
}
|
||||
|
||||
valueTile("battery", "device.battery", decoration: "flat", inactiveLabel: false, width: 2, height: 2) {
|
||||
state "battery", label:'${currentValue}% battery', unit:""
|
||||
}
|
||||
|
||||
preferences {
|
||||
input "preset", "number", title: "Default half-open position (1-100)", defaultValue: 50, required: false, displayDuringSetup: false
|
||||
}
|
||||
|
||||
main(["windowShade"])
|
||||
details(["windowShade", "refresh", "battery"])
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
def parse(String description) {
|
||||
def result = null
|
||||
//if (description =~ /command: 2603, payload: ([0-9A-Fa-f]{6})/)
|
||||
// TODO: Workaround manual parsing of v4 multilevel report
|
||||
def cmd = zwave.parse(description, [0x20: 1, 0x26: 3]) // TODO: switch to SwitchMultilevel v4 and use target value
|
||||
if (cmd) {
|
||||
result = zwaveEvent(cmd)
|
||||
}
|
||||
log.debug "Parsed '$description' to ${result.inspect()}"
|
||||
return result
|
||||
}
|
||||
|
||||
def getCheckInterval() {
|
||||
// These are battery-powered devices, and it's not very critical
|
||||
// to know whether they're online or not – 12 hrs
|
||||
12 * 60 * 60
|
||||
}
|
||||
|
||||
def installed() {
|
||||
sendEvent(name: "checkInterval", value: checkInterval, displayed: false)
|
||||
}
|
||||
|
||||
def updated() {
|
||||
if (device.latestValue("checkInterval") != checkInterval) {
|
||||
sendEvent(name: "checkInterval", value: checkInterval, displayed: false)
|
||||
}
|
||||
if (!device.latestState("battery")) {
|
||||
response(zwave.batteryV1.batteryGet())
|
||||
}
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicReport cmd) {
|
||||
handleLevelReport(cmd)
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicSet cmd) {
|
||||
handleLevelReport(cmd)
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.switchmultilevelv3.SwitchMultilevelReport cmd) {
|
||||
handleLevelReport(cmd)
|
||||
}
|
||||
|
||||
private handleLevelReport(physicalgraph.zwave.Command cmd) {
|
||||
def descriptionText = null
|
||||
def shadeValue = null
|
||||
|
||||
def level = cmd.value as Integer
|
||||
if (level >= 99) {
|
||||
level = 99
|
||||
shadeValue = "open"
|
||||
} else if (level <= 0) {
|
||||
level = 0 // unlike dimmer switches, the level isn't saved when closed
|
||||
shadeValue = "closed"
|
||||
} else {
|
||||
shadeValue = "partiallyOpen"
|
||||
descriptionText = "${device.displayName} shade is ${level}% open"
|
||||
}
|
||||
def levelEvent = createEvent(name: "level", value: level, unit: "%", displayed: false)
|
||||
def stateEvent = createEvent(name: "windowShade", value: shadeValue, descriptionText: descriptionText, isStateChange: levelEvent.isStateChange)
|
||||
|
||||
def result = [stateEvent, levelEvent]
|
||||
if (!state.lastbatt || now() - state.lastbatt > 24 * 60 * 60 * 1000) {
|
||||
log.debug "requesting battery"
|
||||
state.lastbatt = (now() - 23 * 60 * 60 * 1000) // don't queue up multiple battery reqs in a row
|
||||
result << response(["delay 15000", zwave.batteryV1.batteryGet().format()])
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.switchmultilevelv3.SwitchMultilevelStopLevelChange cmd) {
|
||||
[ createEvent(name: "windowShade", value: "partiallyOpen", displayed: false, descriptionText: "$device.displayName shade stopped"),
|
||||
response(zwave.switchMultilevelV1.switchMultilevelGet().format()) ]
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerSpecificReport cmd) {
|
||||
def msr = String.format("%04X-%04X-%04X", cmd.manufacturerId, cmd.productTypeId, cmd.productId)
|
||||
updateDataValue("MSR", msr)
|
||||
if (cmd.manufacturerName) {
|
||||
updateDataValue("manufacturer", cmd.manufacturerName)
|
||||
}
|
||||
createEvent([descriptionText: "$device.displayName MSR: $msr", isStateChange: false])
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.batteryv1.BatteryReport cmd) {
|
||||
def map = [ name: "battery", unit: "%" ]
|
||||
if (cmd.batteryLevel == 0xFF) {
|
||||
map.value = 1
|
||||
map.descriptionText = "${device.displayName} has a low battery"
|
||||
map.isStateChange = true
|
||||
} else {
|
||||
map.value = cmd.batteryLevel
|
||||
}
|
||||
state.lastbatt = now()
|
||||
createEvent(map)
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.Command cmd) {
|
||||
log.debug "unhandled $cmd"
|
||||
return []
|
||||
}
|
||||
|
||||
def open() {
|
||||
log.debug "open()"
|
||||
/*delayBetween([
|
||||
zwave.basicV1.basicSet(value: 0xFF).format(),
|
||||
zwave.switchMultilevelV1.switchMultilevelGet().format()
|
||||
], 1000)*/
|
||||
zwave.basicV1.basicSet(value: 0xFF).format()
|
||||
}
|
||||
|
||||
def close() {
|
||||
log.debug "close()"
|
||||
/*delayBetween([
|
||||
zwave.basicV1.basicSet(value: 0x00).format(),
|
||||
zwave.switchMultilevelV1.switchMultilevelGet().format()
|
||||
], 1000)*/
|
||||
zwave.basicV1.basicSet(value: 0).format()
|
||||
}
|
||||
|
||||
def on() {
|
||||
open()
|
||||
}
|
||||
|
||||
def off() {
|
||||
close()
|
||||
}
|
||||
|
||||
def setLevel(value, duration = null) {
|
||||
log.debug "setLevel(${value.inspect()})"
|
||||
Integer level = value as Integer
|
||||
if (level < 0) level = 0
|
||||
if (level > 99) level = 99
|
||||
if (!preset && level > 0 && level < 95) state.preset = level
|
||||
delayBetween([
|
||||
zwave.basicV1.basicSet(value: level).format(),
|
||||
zwave.switchMultilevelV1.switchMultilevelGet().format()
|
||||
])
|
||||
}
|
||||
|
||||
def presetPosition() {
|
||||
setLevel(preset ?: state.preset ?: 50)
|
||||
}
|
||||
|
||||
def stop() {
|
||||
log.debug "stop()"
|
||||
zwave.switchMultilevelV3.switchMultilevelStopLevelChange().format()
|
||||
}
|
||||
|
||||
def ping() {
|
||||
zwave.switchMultilevelV1.switchMultilevelGet().format()
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
log.debug "refresh()"
|
||||
delayBetween([
|
||||
zwave.switchMultilevelV1.switchMultilevelGet().format(),
|
||||
zwave.batteryV1.batteryGet().format()
|
||||
], 1500)
|
||||
}
|
||||
@@ -0,0 +1,369 @@
|
||||
/**
|
||||
* Programmable Thermostat
|
||||
*
|
||||
* Copyright 2016 Raymond Ciarcia
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
definition(
|
||||
name: "Programmable Thermostat",
|
||||
namespace: "smartthings",
|
||||
author: "Raymond Ciarcia",
|
||||
description: "A full-featured, easy to use interface for programming your thermostat based on schedule setpoints and mode changes",
|
||||
category: "Convenience",
|
||||
iconUrl: "http://cdn.device-icons.smartthings.com/Home/home1-icn@2x.png",
|
||||
iconX2Url: "http://cdn.device-icons.smartthings.com/Home/home1-icn@2x.png",
|
||||
iconX3Url: "http://cdn.device-icons.smartthings.com/Home/home1-icn@2x.png"
|
||||
)
|
||||
|
||||
preferences {
|
||||
page(name:"Settings", title:"Settings", uninstall:true, install:true ) {
|
||||
section() {
|
||||
input (name:"thermostat", type: "capability.thermostat", title: "Select thermostat", required: true, multiple: false)
|
||||
}
|
||||
section ("Scheduled Setpoints") {
|
||||
input (name: "son", type: "bool", title: "Run scheduled setpoints", required:true)
|
||||
input (name:"numscheduled", title: "Number of scheduled setpoints", type: "number",refreshAfterSelection: true)
|
||||
href(title: "Schedule Setpoints", page: "ScheduledChanges")
|
||||
}
|
||||
section ("Mode-Based Setpoints") {
|
||||
input (name: "eon", type: "bool", title: "Run mode-based setpoints", required:true)
|
||||
input (name:"numevent", title: "Number of mode-based setpoints", type: "number",refreshAfterSelection: true)
|
||||
href(title: "Mode-Based Setpoints Setpoints", page: "EventBasedChanges")
|
||||
}
|
||||
section("Auto Thermostat Mode Control") {
|
||||
input (name: "auto", type: "enum", title: "Adjust thermostat heating/cooling mode based on current temperature and setpoint", required:true, multiple: false, options: ['Never','When setpoints are executed','Any time'])
|
||||
}
|
||||
section("Notifications") {
|
||||
input (name: "snotifications", type: "bool", title: "Notify when scheduled setpoints execute", required:true)
|
||||
input (name: "enotifications", type: "bool", title: "Notify when mode-based setpoints execute", required:true)
|
||||
input (name: "eventlogging", type: "enum", title: "Set the level of event logging in the notification feed", required:true, multiple: false, options: ['None','Normal','Detailed'])
|
||||
}
|
||||
section("Command Acknowledgement Failure Response and Notification") {
|
||||
input (name: "fnotifications", type: "bool", title: "Resend commands not acknowledged by the theromstat and notify after multiple failed attempts. Increases thermostat reliability but may not be compatible with all thermostats; disable if every command results in a failure notification.", required:true)
|
||||
}
|
||||
}
|
||||
page(name: "ScheduledChanges")
|
||||
page(name: "EventBasedChanges")
|
||||
}
|
||||
|
||||
def ScheduledChanges() {
|
||||
dynamicPage(name: "ScheduledChanges", uninstall: true, install: false) {
|
||||
for (int i = 1; i <= settings.numscheduled; i++) {
|
||||
section("Scheduled Setpoint $i") {
|
||||
input "stime${i}", "time", title: "At this time:", required: true
|
||||
input "sheatset${i}", "decimal", title: "Set this heating temperature:", required: true
|
||||
input "scoolset${i}", "decimal", title: "Set this cooling temperature:", required: true
|
||||
input "sdays${i}", "enum", title: "Only on these days (no selection is equivalent to selecting all):", required: false, multiple: true, options: ['Monday','Tuesday','Wednesday','Thursday','Friday','Saturday','Sunday']
|
||||
input "smodes${i}", "mode", title: "Only in these modes (no selection is equivalent to selecting all):", multiple: true, required: false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def EventBasedChanges() {
|
||||
dynamicPage(name: "EventBasedChanges", uninstall: true, install: false) {
|
||||
for (int i = 1; i <= settings.numevent; i++) {
|
||||
section("Mode-Based Setpoint $i") {
|
||||
input "emodes${i}", "mode", title: "On transition to this mode:", multiple: false, required: true
|
||||
input "eheatset${i}", "decimal", title: "Set this heating temperature:", required: true
|
||||
input "ecoolset${i}", "decimal", title: "Set this cooling temperature:", required: true
|
||||
input "edays${i}", "enum", title: "Only on these days (no selection is equivalent to selecting all):", required: false, multiple: true, options: ['Monday','Tuesday','Wednesday','Thursday','Friday','Saturday','Sunday']
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//---- INSTALL AND UPDATE
|
||||
|
||||
def installed() {initialize()}
|
||||
|
||||
def updated() {initialize()}
|
||||
|
||||
def initialize() {
|
||||
try {
|
||||
unschedule()
|
||||
} catch(e) {
|
||||
try {
|
||||
unschedule(SchedulerIntegrityChecker)
|
||||
unschedule(MidnightRunner)
|
||||
} catch(ev) {}
|
||||
}
|
||||
|
||||
unsubscribe()
|
||||
subscribe(settings.thermostat, "temperature", tempChangeHandler)
|
||||
if ((settings.numevent > 0) && (settings.eon)) {subscribe(location, modeChangeHandler)}
|
||||
|
||||
state.scheduledindex = 0
|
||||
state.pendingindex = 0
|
||||
state.dayoflastrun = TodayAsString()
|
||||
state.timeoflastevent = now()
|
||||
state.nextscheduledtime = now()
|
||||
state.failedcommandcount = 0
|
||||
state.schedulestring = ""
|
||||
state.checkcommandstring = ""
|
||||
|
||||
state.eventlogging = 0
|
||||
if (settings.eventlogging == "Normal"){state.notificationlevel = 1}
|
||||
if (settings.eventlogging == "Detailed"){state.notificationlevel = 2}
|
||||
|
||||
if ((settings.numscheduled > 0) && (settings.son)) {
|
||||
schedule(timeToday("2015-08-04T00:00:00.000",location.timeZone), MidnightRunner)
|
||||
SchedulerFunction()
|
||||
}
|
||||
log.debug "Programmable Thermostat: successfully initialized."
|
||||
if (state.notificationlevel>0) {sendNotificationEvent("Programmable Thermostat successfully initialized.$state.schedulestring.")}
|
||||
state.schedulestring = ""
|
||||
}
|
||||
|
||||
//---- SCHEDULING FUNCTIONS
|
||||
|
||||
//At midnight, runs scheduler function to set the first scheduled event of the new day
|
||||
def MidnightRunner() {
|
||||
state.dayoflastrun = TodayAsString()
|
||||
state.timeoflastevent = now()
|
||||
SchedulerFunction()
|
||||
def i = SearchSchedulePoints("2015-08-04T00:00:00.000")
|
||||
if (i>0) {ThermostatCommander(settings."sheatset${i}", settings."scoolset${i}", settings.snotifications, "per scheduled setpoint.$state.schedulestring")}
|
||||
}
|
||||
|
||||
//Determines and schedules the next scheduled setpoint
|
||||
def SchedulerFunction(){
|
||||
def mindiff = 60*60*1000*24*7
|
||||
def timeNow = now()
|
||||
def todaystring = TodayAsString()
|
||||
for (int i = 1; i <= settings.numscheduled; i++) {
|
||||
def ScheduledTime = timeToday(settings["stime$i"],location.timeZone)
|
||||
def ScheduledDays = settings["sdays$i"]
|
||||
if (ScheduledDays == null) {ScheduledDays = TodayAsString()}
|
||||
if (ScheduledTime != null) {
|
||||
if ((ScheduledTime.time >= timeNow) && (ScheduledDays.contains(TodayAsString())) && (ScheduledTime.time - timeNow < mindiff)){
|
||||
mindiff = ScheduledTime.time - timeNow
|
||||
state.scheduledindex = i
|
||||
}
|
||||
}
|
||||
}
|
||||
if (mindiff < 60*60*1000*24*7) {
|
||||
int i = state.scheduledindex
|
||||
def nextrun = timeToday(settings["stime$i"],location.timeZone)
|
||||
state.nextscheduledtime = nextrun.time
|
||||
def nextrunstring = DisplayTime(nextrun)
|
||||
runOnce(nextrun, ScheduleExecuter)
|
||||
if (state.notificationlevel>1) {state.schedulestring=" Next scheduled setpoint for $thermostat.label is today at $nextrunstring"}
|
||||
} else {
|
||||
state.nextscheduledtime = -1
|
||||
if (state.notificationlevel>1) {state.schedulestring=" There are no remaining scheduled setpoints for $thermostat.label today"}
|
||||
}
|
||||
state.timeoflastevent = now()
|
||||
}
|
||||
|
||||
def SearchSchedulePoints(time) {
|
||||
for (int i = 1; i <= settings.numscheduled; i++) {
|
||||
def Modes = settings["smodes$i"]
|
||||
if (Modes == null) {Modes = location.mode}
|
||||
def Days = settings["sdays$i"]
|
||||
if (Days == null) {Days = TodayAsString()}
|
||||
if(timeToday(settings["stime$i"],location.timeZone) == timeToday(time,location.timeZone) && Modes.contains(location.mode) && Days.contains(TodayAsString())) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
|
||||
//---- EXECUTION FUNCTIONS
|
||||
|
||||
//Runs at scheduled setpoints to determine whether a setpoint should be executed; if yes, calls thermostat commander to execute command
|
||||
def ScheduleExecuter() {
|
||||
int i = state.scheduledindex
|
||||
SchedulerFunction()
|
||||
state.timeoflastevent = now()
|
||||
def valid = false
|
||||
def Modes = settings["smodes$i"]
|
||||
if (Modes == null) {Modes = location.mode}
|
||||
if(Modes.contains(location.mode)){
|
||||
valid = true
|
||||
} else {
|
||||
i = SearchSchedulePoints(settings["stime$i"])
|
||||
if (i > 0) {valid = true}
|
||||
}
|
||||
if (valid) {
|
||||
state.failedcommandcount = 0
|
||||
state.pendingindex = i
|
||||
ThermostatCommander(settings."sheatset${i}", settings."scoolset${i}", settings.snotifications, "per scheduled setpoint.$state.schedulestring")
|
||||
} else {
|
||||
if (state.notificationlevel>1) {sendNotificationEvent("Scheduled setpoint for $thermostat.label not executed because the current home mode, $location.mode, does not match a setpoint mode.$state.schedulestring.")}
|
||||
}
|
||||
state.schedulestring = ""
|
||||
}
|
||||
|
||||
//Sends commands to the thermostat
|
||||
def ThermostatCommander(hvalue, cvalue, notifications, notificationphrase) {
|
||||
state.timeoflastevent = now()
|
||||
if((hvalue == null) || (cvalue == null)) {return}
|
||||
if (settings.auto != "Never") {ThermostatModeSetter(hvalue, cvalue, 0)}
|
||||
|
||||
def notificationstring = ""
|
||||
state.checkcommandstring = ""
|
||||
def thermMode = thermostat.currentValue("thermostatMode")
|
||||
def name = thermostat.label
|
||||
|
||||
def currentheatsetpoint = settings.thermostat.currentValue("heatingSetpoint")
|
||||
def currentcoolsetpoint = settings.thermostat.currentValue("coolingSetpoint")
|
||||
|
||||
if ("$currentcoolsetpoint" != "$cvalue") {state.checkcommandstring = "c"}
|
||||
if ("$currentheatsetpoint" != "$hvalue") {state.checkcommandstring = "h$state.checkcommandstring"}
|
||||
log.debug "Programmable Thermostat: check string is $state.checkcommandstring; values are $currentcoolsetpoint and $currentheatsetpoint"
|
||||
|
||||
def primarysetpoint = hvalue
|
||||
if (thermMode == "cool") {primarysetpoint = cvalue}
|
||||
if (thermMode == "heat" || thermMode == "cool") {notificationstring = "$name set to $primarysetpoint in $thermMode mode $notificationphrase."}
|
||||
else {notificationstring = "$name set to $hvalue / $cvalue $notificationphrase."}
|
||||
|
||||
if (settings.fnotifications && state.checkcommandstring != "") (runIn(10 + state.failedcommandcount*30, CommandIntegrityChecker))
|
||||
|
||||
if (hvalue!=0) {
|
||||
log.debug "Programmable Thermostat: Heat command set to $hvalue"
|
||||
thermostat.setHeatingSetpoint(hvalue)
|
||||
}
|
||||
if (cvalue!=0) {
|
||||
log.debug "Programmable Thermostat: Cool command set to $cvalue"
|
||||
thermostat.setCoolingSetpoint(cvalue)
|
||||
}
|
||||
if (notifications && state.failedcommandcount==0) {
|
||||
sendPush(notificationstring)
|
||||
} else if (state.notificationlevel>0 && state.failedcommandcount==0) {
|
||||
sendNotificationEvent(notificationstring)
|
||||
if (state.checkcommandstring == "" && state.notificationlevel>2) {sendNotificationEvent("$name confirmed that it was already set to $primarysetpoint in $thermMode mode.")}
|
||||
}
|
||||
if (state.checkcommandstring == "") {log.debug "Programmable Thermostat: $name confirmed that it was already set to $primarysetpoint in $thermMode mode"}
|
||||
}
|
||||
|
||||
//Auto Sets Thermostat Mode
|
||||
def ThermostatModeSetter(hvalue, cvalue, notifications) {
|
||||
if (hvalue==0 || cvalue==0) {return}
|
||||
def currentTemp = settings.thermostat.latestValue("temperature")
|
||||
if (currentTemp > cvalue && settings.thermostat.currentValue("thermostatMode") != "cool") {
|
||||
thermostat.cool()
|
||||
if (notifications > 0) {sendNotificationEvent("$thermostat.label mode changed to cooling when temperature reached $currentTemp")}
|
||||
} else if (currentTemp < hvalue && settings.thermostat.currentValue("thermostatMode") != "heat") {
|
||||
thermostat.heat()
|
||||
if (notifications > 0) {sendNotificationEvent("$thermostat.label mode changed to heating when temperature fell to $currentTemp")}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//---- INTEGRITY CHECKERS
|
||||
|
||||
//Determines whether the last scheduled setpoint was executed; if not, reinitializes or sends missed command
|
||||
def SchedulerIntegrityChecker() {
|
||||
def i = state.scheduledindex
|
||||
if ((settings.numscheduled == 0) || (settings.son == false)) {return}
|
||||
if (state.dayoflastrun != TodayAsString()) {initialize()}
|
||||
}
|
||||
|
||||
//Determines whether commands sent to the thermostat have been properly acknowledged; if not, calls thermostat commander to reissue failed command(s)
|
||||
def CommandIntegrityChecker() {
|
||||
state.timeoflastevent = now()
|
||||
if (state.pendingindex == 0) {return}
|
||||
def currentheatsetpoint = settings.thermostat.currentValue("heatingSetpoint")
|
||||
def currentcoolsetpoint = settings.thermostat.currentValue("coolingSetpoint")
|
||||
def thermMode = thermostat.currentValue("thermostatMode")
|
||||
def lastheatcommand = IndexLookUp("heat")
|
||||
def lastcoolcommand = IndexLookUp("cool")
|
||||
def failedstring = ""
|
||||
log.debug "Programmable Thermostat: $thermostat.label heating setpoint was commanded to $lastheatcommand and is currently $currentheatsetpoint; cooling setpoint was commanded to $lastcoolcommand and is currently $currentcoolsetpoint"
|
||||
|
||||
if (("$currentheatsetpoint" == "$lastheatcommand") && ("$currentcoolsetpoint" == "$lastcoolcommand")) {return}
|
||||
|
||||
state.failedcommandcount = state.failedcommandcount + 1
|
||||
if ("$currentheatsetpoint" != "$lastheatcommand" && "$currentcoolsetpoint" != "$lastcoolcommand" && state.checkcommandstring == "hc") {
|
||||
failedstring = "$thermostat.label is non-responsive to setpoint commands."
|
||||
ThermostatCommander(lastheatcommand, lastcoolcommand, false, "")
|
||||
} else if ("$currentheatsetpoint" != "$lastheatcommand" && (state.checkcommandstring == "hc" || state.checkcommandstring == "h")) {
|
||||
if (thermMode == "heat") {failedstring = "$thermostat.label is non-responsive to heat setpoint commands."}
|
||||
ThermostatCommander(lastheatcommand, 0, false, "")
|
||||
} else if ("$currentcoolsetpoint" != "$lastcoolcommand" && (state.checkcommandstring == "hc" || state.checkcommandstring == "c")) {
|
||||
if (thermMode == "cool") failedstring = "$thermostat.label is non-responsive to cool setpoint commands."
|
||||
ThermostatCommander(0, lastcoolcommand, false, "")
|
||||
}
|
||||
|
||||
if (state.failedcommandcount == 4) {
|
||||
state.failedcommandcount = 0
|
||||
state.pendingindex = 0
|
||||
if (failedstring != "") {sendPush(failedstring)}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//---- EVENT HANDLERS
|
||||
|
||||
//Runs every time a mode change is detected. Used to execute mode-based setpoints; also used to trigger schedule integrity checks in case all scheduled functions have failed
|
||||
def modeChangeHandler(evt) {
|
||||
if (state.notificationlevel>2) {sendNotificationEvent("Programmable Thermostat detected home mode change to $evt.value.")}
|
||||
for (int i = 1; i <= settings.numevent; i++) {
|
||||
def ScheduledDays = settings["edays$i"]
|
||||
if (ScheduledDays == null) {ScheduledDays = TodayAsString()}
|
||||
if ((evt.value == settings["emodes$i"]) && (ScheduledDays.contains(TodayAsString()))) {
|
||||
state.failedcommandcount = 0
|
||||
state.pendingindex = -i
|
||||
ThermostatCommander(settings."eheatset${i}", settings."ecoolset${i}", settings.enotifications, "with change to $evt.value")
|
||||
i = settings.numevent + 1
|
||||
}
|
||||
}
|
||||
SchedulerIntegrityChecker()
|
||||
}
|
||||
|
||||
//Runs every time the temperature reported by the thermostat changes. Used to trigger schedule integrity checks in case all scheduled functions have failed.
|
||||
def tempChangeHandler(evt) {
|
||||
SchedulerIntegrityChecker()
|
||||
if (settings.auto == "Any time") {ThermostatModeSetter(settings.thermostat.latestValue("heatingSetpoint"), settings.thermostat.latestValue("coolingSetpoint"), state.notificationlevel)}
|
||||
}
|
||||
|
||||
//---- OTHER
|
||||
|
||||
//Returns the setpoint temperature associated with a settings index
|
||||
def IndexLookUp(mode) {
|
||||
def result = 0
|
||||
if (mode == "cool") {
|
||||
if (state.pendingindex > 0) {result = settings."scoolset${state.pendingindex}"}
|
||||
if (state.pendingindex < 0) {result = settings."ecoolset${-state.pendingindex}"}
|
||||
} else if (mode == "heat") {
|
||||
if (state.pendingindex > 0) {result = settings."sheatset${state.pendingindex}"}
|
||||
if (state.pendingindex < 0) {result = settings."eheatset${-state.pendingindex}"}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
//Returns the current day of the week as a string
|
||||
def TodayAsString() {
|
||||
return (new Date(now())).format("EEEEEEE", location.timeZone)
|
||||
}
|
||||
|
||||
//Returns time as a string in 12 hour format
|
||||
def DisplayTime(time) {
|
||||
def tz = location.timeZone
|
||||
def hour = time.format("H",tz)
|
||||
def min = time.format("m",tz)
|
||||
def sec = time.format("s",tz)
|
||||
def ampm = "am"
|
||||
def hournum = hour.toInteger()
|
||||
def minnum = min.toInteger()
|
||||
if (hournum == 0) {hournum = 12}
|
||||
if (hournum > 12) {
|
||||
hournum = hournum - 12
|
||||
ampm = "pm"
|
||||
}
|
||||
if (minnum < 10) {min = "0$min"}
|
||||
return "$hournum:$min $ampm"
|
||||
}
|
||||
Reference in New Issue
Block a user