2 * Double Tap Switch for "Hello, Home" Action
4 * Copyright 2014 George Sudarkoff
6 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
7 * in compliance with the License. You may obtain a copy of the License at:
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
12 * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
13 * for the specific language governing permissions and limitations under the License.
18 name: "Double Tap Switch for 'Hello, Home' Action",
19 namespace: "com.sudarkoff",
20 author: "George Sudarkoff",
21 description: "Execute a 'Hello, Home' action when an existing switch is tapped twice in a row.",
22 category: "Mode Magic",
23 iconUrl: "https://s3.amazonaws.com/smartthings-device-icons/Appliances/appliances17-icn.png",
24 iconX2Url: "https://s3.amazonaws.com/smartthings-device-icons/Appliances/appliances17-icn@2x.png"
28 page (name: "configApp")
30 page (name: "timeIntervalInput", title: "Only during a certain time") {
32 input "starting", "time", title: "Starting", required: false
33 input "ending", "time", title: "Ending", required: false
39 dynamicPage(name: "configApp", install: true, uninstall: true) {
40 section ("When this switch is double-tapped...") {
41 input "master", "capability.switch", required: true
44 def phrases = location.helloHome?.getPhrases()*.label
47 section("Perform this actions...") {
48 input "onPhrase", "enum", title: "ON action", required: false, options: phrases
49 input "offPhrase", "enum", title: "OFF action", required: false, options: phrases
53 section (title: "Notification method") {
54 input "sendPushMessage", "bool", title: "Send a push notification?"
57 section (title: "More Options", hidden: hideOptionsSection(), hideable: true) {
58 input "phone", "phone", title: "Additionally, also send a text message to:", required: false
59 input "flashLights", "capability.switch", title: "And flash these lights", multiple: true, required: false
61 def timeLabel = timeIntervalLabel()
62 href "timeIntervalInput", title: "Only during a certain time", description: timeLabel ?: "Tap to set", state: timeLabel ? "complete" : null
64 input "days", "enum", title: "Only on certain days of the week", multiple: true, required: false,
65 options: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
68 section([mobileOnly:true]) {
69 label title: "Assign a name", required: false
70 mode title: "Set for specific mode(s)", required: false
89 state.currentAppLabel = customName
91 subscribe(master, "switch", switchHandler, [filterEvents: false])
94 def switchHandler(evt) {
98 // use Event rather than DeviceState because we may be changing DeviceState to only store changed values
99 def recentStates = master.eventsSince(new Date(now() - 4000), [all:true, max: 10]).findAll{it.name == "switch"}
100 log.debug "${recentStates?.size()} STATES FOUND, LAST AT ${recentStates ? recentStates[0].dateCreated : ''}"
102 if (evt.isPhysical()) {
103 if (evt.value == "on" && lastTwoStatesWere("on", recentStates, evt)) {
104 log.debug "detected two taps, execute ON phrase"
105 location.helloHome.execute(settings.onPhrase)
106 def message = "${location.name} executed ${settings.onPhrase} because ${evt.title} was tapped twice."
109 } else if (evt.value == "off" && lastTwoStatesWere("off", recentStates, evt)) {
110 log.debug "detected two taps, execute OFF phrase"
111 location.helloHome.execute(settings.offPhrase)
112 def message = "${location.name} executed ${settings.offPhrase} because ${evt.title} was tapped twice."
118 log.trace "Skipping digital on/off event"
123 private lastTwoStatesWere(value, states, evt) {
126 log.trace "unfiltered: [${states.collect{it.dateCreated + ':' + it.value}.join(', ')}]"
127 def onOff = states.findAll { it.isPhysical() || !it.type }
128 log.trace "filtered: [${onOff.collect{it.dateCreated + ':' + it.value}.join(', ')}]"
130 // This test was needed before the change to use Event rather than DeviceState. It should never pass now.
131 if (onOff[0].date.before(evt.date)) {
132 log.warn "Last state does not reflect current event, evt.date: ${evt.dateCreated}, state.date: ${onOff[0].dateCreated}"
133 result = evt.value == value && onOff[0].value == value
136 result = onOff.size() > 1 && onOff[0].value == value && onOff[1].value == value
143 if (sendPushMessage != "No") {
154 private flashLights() {
156 def onFor = onFor ?: 200
157 def offFor = offFor ?: 200
158 def numFlashes = numFlashes ?: 2
160 if (state.lastActivated) {
161 def elapsed = now() - state.lastActivated
162 def sequenceTime = (numFlashes + 1) * (onFor + offFor)
163 doFlash = elapsed > sequenceTime
167 state.lastActivated = now()
168 def initialActionOn = flashLights.collect{it.currentSwitch != "on"}
171 flashLights.eachWithIndex {s, i ->
172 if (initialActionOn[i]) {
180 flashLights.eachWithIndex {s, i ->
181 if (initialActionOn[i]) {
193 // execution filter methods
195 modeOk && daysOk && timeOk
198 private getModeOk() {
199 def result = !modes || modes.contains(location.mode)
203 private getDaysOk() {
206 def df = new java.text.SimpleDateFormat("EEEE")
207 if (location.timeZone) {
208 df.setTimeZone(location.timeZone)
211 df.setTimeZone(TimeZone.getTimeZone("America/Los_Angeles"))
213 def day = df.format(new Date())
214 result = days.contains(day)
219 private getTimeOk() {
221 if (starting && ending) {
223 def start = timeToday(starting).time
224 def stop = timeToday(ending).time
225 result = start < stop ? currTime >= start && currTime <= stop : currTime <= stop || currTime >= start
227 log.trace "timeOk = $result"
231 private hideOptionsSection() {
232 (phone || starting || ending || flashLights || customName || days || modes) ? false : true
235 private hhmm(time, fmt = "h:mm a")
237 def t = timeToday(time, location.timeZone)
238 def f = new java.text.SimpleDateFormat(fmt)
239 f.setTimeZone(location.timeZone ?: timeZone(time))
243 private timeIntervalLabel() {
244 (starting && ending) ? hhmm(starting) + "-" + hhmm(ending, "h:mm a z") : ""