2 * Smart Home Ventilation
3 * Version 2.1.2 - 5/31/15
5 * Copyright 2015 Michael Struck
7 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
8 * in compliance with the License. You may obtain a copy of the License at:
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
13 * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
14 * for the specific language governing permissions and limitations under the License.
19 name: "Smart Home Ventilation",
20 namespace: "MichaelStruck",
21 author: "Michael Struck",
22 description: "Allows for setting up various schedule scenarios for turning on and off home ventilation switches.",
23 category: "Convenience",
24 iconUrl: "https://raw.githubusercontent.com/MichaelStruck/SmartThings/master/Other-SmartApps/Smart-Home-Ventilation/HomeVent.png",
25 iconX2Url: "https://raw.githubusercontent.com/MichaelStruck/SmartThings/master/Other-SmartApps/Smart-Home-Ventilation/HomeVent@2x.png",
26 iconX3Url: "https://raw.githubusercontent.com/MichaelStruck/SmartThings/master/Other-SmartApps/Smart-Home-Ventilation/HomeVent@2x.png")
33 dynamicPage(name: "mainPage", title: "", install: true, uninstall: true) {
34 section("Select ventilation switches..."){
35 input "switches", title: "Switches", "capability.switch", multiple: true
37 section ("Scheduling scenarios...") {
38 href(name: "toA_Scenario", page: "A_Scenario", title: getTitle (titleA, "A"), description: schedDesc(timeOnA1,timeOffA1,timeOnA2,timeOffA2,timeOnA3,timeOffA3,timeOnA4,timeOffA4, modeA, daysA), state: greyOut(timeOnA1,timeOnA2,timeOnA3,timeOnA4))
39 href(name: "toB_Scenario", page: "B_Scenario", title: getTitle (titleB, "B"), description: schedDesc(timeOnB1,timeOffB1,timeOnB2,timeOffB2,timeOnB3,timeOffB3,timeOnB4,timeOffB4, modeB, daysB), state: greyOut(timeOnB1,timeOnB2,timeOnB3,timeOnB4))
40 href(name: "toC_Scenario", page: "C_Scenario", title: getTitle (titleC, "C"), description: schedDesc(timeOnC1,timeOffC1,timeOnC2,timeOffC2,timeOnC3,timeOffC3,timeOnC4,timeOffC4, modeC, daysC), state: greyOut(timeOnC1,timeOnC2,timeOnC3,timeOnC4))
41 href(name: "toD_Scenario", page: "D_Scenario", title: getTitle (titleD, "D"), description: schedDesc(timeOnD1,timeOffD1,timeOnD2,timeOffD2,timeOnD3,timeOffD3,timeOnD4,timeOffD4, modeD, daysD), state: greyOut(timeOnD1,timeOnD2,timeOnD3,timeOnD4))
43 section([mobileOnly:true], "Options") {
44 label(title: "Assign a name", required: false, defaultValue: "Smart Home Ventilation")
45 href "pageAbout", title: "About ${textAppName()}", description: "Tap to get application version, license and instructions"
49 //----Scheduling Pages
50 page(name: "A_Scenario", title: getTitle (titleA, "A")) {
52 input "timeOnA1", title: "Schedule 1 time to turn on", "time", required: false
53 input "timeOffA1", title: "Schedule 1 time to turn off", "time", required: false
56 input "timeOnA2", title: "Schedule 2 time to turn on", "time", required: false
57 input "timeOffA2", title: "Schedule 2 time to turn off", "time", required: false
60 input "timeOnA3", title: "Schedule 3 time to turn on", "time", required: false
61 input "timeOffA3", title: "Schedule 3 time to turn off", "time", required: false
64 input "timeOnA4", title: "Schedule 4 time to turn on", "time", required: false
65 input "timeOffA4", title: "Schedule 4 time to turn off", "time", required: false
68 input "titleA", title: "Assign a scenario name", "text", required: false
69 input "modeA", "mode", required: false, multiple: true, title: "Run in specific mode(s)", description: "Choose Modes"
70 input "daysA", "enum", multiple: true, title: "Run on specific day(s)", description: "Choose Days", required: false, options: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
74 page(name: "B_Scenario", title: getTitle (titleB, "B")) {
76 input "timeOnB1", title: "Schedule 1 time to turn on", "time", required: false
77 input "timeOffB1", title: "Schedule 1 time to turn off", "time", required: false
80 input "timeOnB2", title: "Schedule 2 time to turn on", "time", required: false
81 input "timeOffB2", title: "Schedule 2 time to turn off", "time", required: false
84 input "timeOnB3", title: "Schedule 3 time to turn on", "time", required: false
85 input "timeOffB3", title: "Schedule 3 time to turn off", "time", required: false
88 input "timeOnB4", title: "Schedule 4 time to turn on", "time", required: false
89 input "timeOffB4", title: "Schedule 4 time to turn off", "time", required: false
92 input "titleB", title: "Assign a scenario name", "text", required: false
93 input "modeB", "mode", required: false, multiple: true, title: "Run in specific mode(s)", description: "Choose Modes"
94 input "daysB", "enum", multiple: true, title: "Run on specific day(s)", description: "Choose Days", required: false, options: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
98 page(name: "C_Scenario", title: getTitle (titleC, "C")) {
100 input "timeOnC1", title: "Schedule 1 time to turn on", "time", required: false
101 input "timeOffC1", title: "Schedule 1 time to turn off", "time", required: false
104 input "timeOnC2", title: "Schedule 2 time to turn on", "time", required: false
105 input "timeOffC2", title: "Schedule 2 time to turn off", "time", required: false
108 input "timeOnC3", title: "Schedule 3 time to turn on", "time", required: false
109 input "timeOffC3", title: "Schedule 3 time to turn off", "time", required: false
112 input "timeOnC4", title: "Schedule 4 time to turn on", "time", required: false
113 input "timeOffC4", title: "Schedule 4 time to turn off", "time", required: false
116 input "titleC", title: "Assign a scenario name", "text", required: false
117 input "modeC", "mode", required: false, multiple: true, title: "Run in specific mode(s)", description: "Choose Modes"
118 input "daysC", "enum", multiple: true, title: "Run on specific day(s)", description: "Choose Days", required: false, options: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
123 page(name: "D_Scenario", title: getTitle (titleD, "D")) {
125 input "timeOnD1", title: "Schedule 1 time to turn on", "time", required: false
126 input "timeOffD1", title: "Schedule 1 time to turn off", "time", required: false
129 input "timeOnD2", title: "Schedule 2 time to turn on", "time", required: false
130 input "timeOffD2", title: "Schedule 2 time to turn off", "time", required: false
133 input "timeOnD3", title: "Schedule 3 time to turn on", "time", required: false
134 input "timeOffD3", title: "Schedule 3 time to turn off", "time", required: false
137 input "timeOnD4", title: "Schedule 4 time to turn on", "time", required: false
138 input "timeOffD4", title: "Schedule 4 time to turn off", "time", required: false
141 input "titleD", title: "Assign a scenario name", "text", required: false
142 input "modeD", "mode", required: false, multiple: true, title: "Run in specific mode(s)", description: "Choose Modes"
143 input "daysD", "enum", multiple: true, title: "Run on specific day(s)", description: "Choose Days", required: false, options: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
148 page(name: "pageAbout", title: "About ${textAppName()}") {
150 paragraph "${textVersion()}\n${textCopyright()}\n\n${textLicense()}\n"
152 section("Instructions") {
157 // Install and initiate
160 log.debug "Installed with settings: ${settings}"
166 turnOffSwitch() //Turn off all switches if the schedules are changed while in mid-schedule
168 log.debug "Updated with settings: ${settings}"
173 def midnightTime = timeToday("2000-01-01T00:01:00.999-0000", location.timeZone)
174 schedule (midnightTime, midNight)
175 subscribe(location, "mode", locationHandler)
181 def startProcess () {
183 state.dayCount=state.data.size()
191 def start = convertEpoch(state.data[state.counter].start)
192 def stop = convertEpoch(state.data[state.counter].stop)
194 runOnce(start, turnOnSwitch, [overwrite: true])
195 runOnce(stop, incDay, [overwrite: true])
200 if (state.modeChange) {
204 state.counter = state.counter + 1
205 if (state.counter < state.dayCount) {
211 def locationHandler(evt) {
213 state.modeChange = true
215 if (it.currentValue("switch")=="on"){
230 log.debug "Home ventilation switches are on."
233 def turnOffSwitch() {
235 if (it.currentValue("switch")=="on"){
239 log.debug "Home ventilation switches are off."
242 def schedDesc(on1, off1, on2, off2, on3, off3, on4, off4, modeList, dayList) {
244 def dayListClean = "On "
245 def modeListClean ="Scenario runs in "
246 if (dayList && dayList.size() < 7) {
247 def dayListSize = dayList.size()
248 for (dayName in dayList) {
249 dayListClean = "${dayListClean}"+"${dayName}"
250 dayListSize = dayListSize -1
252 dayListClean = "${dayListClean}, "
257 dayListClean = "Every day"
260 def modeListSize = modeList.size()
261 def modePrefix ="modes"
262 if (modeListSize == 1) {
265 for (modeName in modeList) {
266 modeListClean = "${modeListClean}"+"'${modeName}'"
267 modeListSize = modeListSize -1
269 modeListClean = "${modeListClean}, "
272 modeListClean = "${modeListClean} ${modePrefix}"
277 modeListClean = "${modeListClean}all modes"
280 title += "Schedule 1: ${humanReadableTime(on1)} to ${humanReadableTime(off1)}"
283 title += "\nSchedule 2: ${humanReadableTime(on2)} to ${humanReadableTime(off2)}"
286 title += "\nSchedule 3: ${humanReadableTime(on3)} to ${humanReadableTime(off3)}"
289 title += "\nSchedule 4: ${humanReadableTime(on4)} to ${humanReadableTime(off4)}"
291 if (on1 || on2 || on3 || on4) {
292 title += "\n$modeListClean"
293 title += "\n$dayListClean"
296 if (!on1 && !on2 && !on3 && !on4) {
297 title="Click to configure scenario"
302 def greyOut(on1, on2, on3, on4){
303 def result = on1 || on2 || on3 || on4 ? "complete" : ""
306 public humanReadableTime(dateTxt) {
307 new Date().parse("yyyy-MM-dd'T'HH:mm:ss.SSSZ", dateTxt).format("h:mm a", timeZone(dateTxt))
310 public convertEpoch(epochDate) {
311 new Date(epochDate).format("yyyy-MM-dd'T'HH:mm:ss.SSSZ", location.timeZone)
314 private getTitle(txt, scenario) {
315 def title = txt ? txt : "Scenario ${scenario}"
318 private daysOk(dayList) {
321 def df = new java.text.SimpleDateFormat("EEEE")
322 if (location.timeZone) {
323 df.setTimeZone(location.timeZone)
326 df.setTimeZone(TimeZone.getTimeZone("America/New_York"))
328 def day = df.format(new Date())
329 result = dayList.contains(day)
334 private timeOk(starting, ending) {
335 if (starting && ending) {
337 def start = timeToday(starting).time
338 def stop = timeToday(ending).time
339 if (start < stop && start >= currTime && stop>=currTime) {
340 state.data << [start:start, stop:stop]
345 def createDayArray() {
346 state.modeChange = false
348 if (modeA && modeA.contains(location.mode)) {
350 timeOk(timeOnA1, timeOffA1)
351 timeOk(timeOnA2, timeOffA2)
352 timeOk(timeOnA3, timeOffA3)
353 timeOk(timeOnA4, timeOffA4)
356 if (modeB && modeB.contains(location.mode)) {
358 timeOk(timeOnB1, timeOffB1)
359 timeOk(timeOnB2, timeOffB2)
360 timeOk(timeOnB3, timeOffB3)
361 timeOk(timeOnB4, timeOffB4)
364 if (modeC && modeC.contains(location.mode)) {
366 timeOk(timeOnC1, timeOffC1)
367 timeOk(timeOnC2, timeOffC2)
368 timeOk(timeOnC3, timeOffC3)
369 timeOk(timeOnC4, timeOffC4)
372 if (modeD && modeD.contains(location.mode)) {
374 timeOk(timeOnD1, timeOffD1)
375 timeOk(timeOnD2, timeOffD2)
376 timeOk(timeOnD3, timeOffD3)
377 timeOk(timeOnD4, timeOffD4)
380 state.data.sort{it.start}
383 //Version/Copyright/Information/Help
385 private def textAppName() {
386 def text = "Smart Home Ventilation"
389 private def textVersion() {
390 def text = "Version 2.1.2 (05/31/2015)"
393 private def textCopyright() {
394 def text = "Copyright © 2015 Michael Struck"
397 private def textLicense() {
399 "Licensed under the Apache License, Version 2.0 (the 'License'); "+
400 "you may not use this file except in compliance with the License. "+
401 "You may obtain a copy of the License at"+
403 " http://www.apache.org/licenses/LICENSE-2.0"+
405 "Unless required by applicable law or agreed to in writing, software "+
406 "distributed under the License is distributed on an 'AS IS' BASIS, "+
407 "WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. "+
408 "See the License for the specific language governing permissions and "+
409 "limitations under the License."
412 private def textHelp() {
414 "Within each scenario, choose a start and end time for the ventilation fan. You can have up to 4 different " +
415 "venting scenarios, and 4 schedules within each scenario. Each scenario can be restricted to specific modes or certain days of the week. It is recommended "+
416 "that each scenario does not overlap and run in separate modes (i.e. Home, Out of town, etc). Also note that you should " +
417 "avoid scheduling the ventilation fan at exactly midnight; the app resets itself at that time. It is suggested to start any new schedule " +
418 "at 12:15 am or later."