From fba8ea199a3a880215fae5407e82d6da052f4acb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A0=95=EC=B0=BD=ED=99=98?= Date: Fri, 26 Feb 2016 00:04:04 -0600 Subject: [PATCH] MSA-903: test --- .../arrival-sensor.src/arrival-sensor.groovy | 588 ++++++++-------- .../button-controller.groovy | 644 +++++++++--------- 2 files changed, 616 insertions(+), 616 deletions(-) diff --git a/devicetypes/smartthings/arrival-sensor.src/arrival-sensor.groovy b/devicetypes/smartthings/arrival-sensor.src/arrival-sensor.groovy index a8703fc..ca690c1 100644 --- a/devicetypes/smartthings/arrival-sensor.src/arrival-sensor.groovy +++ b/devicetypes/smartthings/arrival-sensor.src/arrival-sensor.groovy @@ -1,294 +1,294 @@ -/** - * 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. - * - */ -metadata { - definition (name: "Arrival Sensor", namespace: "smartthings", author: "SmartThings") { - capability "Tone" - capability "Actuator" - capability "Signal Strength" - capability "Presence Sensor" - capability "Sensor" - capability "Battery" - - fingerprint profileId: "FC01", deviceId: "019A" - fingerprint profileId: "FC01", deviceId: "0131", inClusters: "0000,0003", outClusters: "0003" - fingerprint profileId: "FC01", deviceId: "0131", inClusters: "0000", outClusters: "0006" - } - - simulator { - status "present": "presence: 1" - status "not present": "presence: 0" - status "battery": "battery: 27, batteryDivisor: 0A, rssi: 100, lqi: 64" - } - - preferences { - section { - image(name: 'educationalcontent', multiple: true, images: [ - "http://cdn.device-gse.smartthings.com/Arrival/Arrival1.jpg", - "http://cdn.device-gse.smartthings.com/Arrival/Arrival2.jpg" - ]) - } - } - - tiles { - standardTile("presence", "device.presence", width: 2, height: 2, canChangeBackground: true) { - state "present", labelIcon:"st.presence.tile.present", backgroundColor:"#53a7c0" - state "not present", labelIcon:"st.presence.tile.not-present", backgroundColor:"#ebeef2" - } - standardTile("beep", "device.beep", decoration: "flat") { - state "beep", label:'', action:"tone.beep", icon:"st.secondary.beep", backgroundColor:"#ffffff" - } - valueTile("battery", "device.battery", decoration: "flat", inactiveLabel: false) { - state "battery", label:'${currentValue}% battery', unit:""/*, backgroundColors:[ - [value: 5, color: "#BC2323"], - [value: 10, color: "#D04E00"], - [value: 15, color: "#F1D801"], - [value: 16, color: "#FFFFFF"] - ]*/ - } - /* - valueTile("lqi", "device.lqi", decoration: "flat", inactiveLabel: false) { - state "lqi", label:'${currentValue}% signal', unit:"" - } - */ - - main "presence" - details(["presence", "beep", "battery"/*, "lqi"*/]) - } -} - -def beep() { - /* - You can make the speaker turn on for 0.5-second beeps by sending some CLI commands: - - Command: send raw, wait 7, send raw, wait 7, send raw - Future: new packet type "st.beep" - - raw 0xFC05 {15 0A 11 00 00 15 01} - send 0x2F7F 2 2 - - where "0xABCD" is the node ID of the Smart Tag, everything else above is a constant. Except - the "15 01" at the end of the first raw command, that sets the speaker's period (reciprocal - of frequency). You can play with this value up or down to experiment with loudness as the - loudness will be strongly dependent upon frequency and the enclosure that it's in. Note that - "15 01" represents the hex number 0x0115 so a lower frequency is "16 01" (longer period) and - a higher frequency is "14 01" (shorter period). Note that since the tag only checks its parent - for messages every 5 seconds (while at rest) or every 3 seconds (while in motion) it will take - up to this long from the time you send the message to the time you hear a sound. - */ - - [ - "raw 0xFC05 {15 0A 11 00 00 15 01}", - "delay 7000", - "raw 0xFC05 {15 0A 11 00 00 15 01}", - "delay 7000", - "raw 0xFC05 {15 0A 11 00 00 15 01}", - "delay 7000", - "raw 0xFC05 {15 0A 11 00 00 15 01}", - "delay 7000", - "raw 0xFC05 {15 0A 11 00 00 15 01}" - ] -} - -def parse(String description) { - def results - if (isBatteryMessage(description)) { - results = parseBatteryMessage(description) - } - else { - results = parsePresenceMessage(description) - } - - log.debug "Parse returned $results.descriptionText" - results -} - -private Map parsePresenceMessage(String description) { - def name = parseName(description) - def value = parseValue(description) - def linkText = getLinkText(device) - def descriptionText = parseDescriptionText(linkText, value, description) - def handlerName = getState(value) - def isStateChange = isStateChange(device, name, value) - - def results = [ - name: name, - value: value, - unit: null, - linkText: linkText, - descriptionText: descriptionText, - handlerName: handlerName, - isStateChange: isStateChange, - displayed: displayed(description, isStateChange) - ] - - results -} - -private String parseName(String description) { - if (description?.startsWith("presence: ")) { - return "presence" - } - null -} - -private String parseValue(String description) { - if (description?.startsWith("presence: ")) - { - if (description?.endsWith("1")) - { - return "present" - } - else if (description?.endsWith("0")) - { - return "not present" - } - } - - description -} - -private parseDescriptionText(String linkText, String value, String description) { - switch(value) { - case "present": return "$linkText has arrived" - case "not present": return "$linkText has left" - default: return value - } -} - -private getState(String value) { - def state = value - if (value == "present") { - state = "arrived" - } - else if (value == "not present") { - state = "left" - } - - state -} - -private Boolean isBatteryMessage(String description) { - // "raw:36EF1C, dni:36EF, battery:1B, rssi:, lqi:" - description ==~ /.*battery:.*rssi:.*lqi:.*/ -} - -private List parseBatteryMessage(String description) { - def results = [] - def parts = description.split(',') - parts.each { part -> - part = part.trim() - if (part.startsWith('battery:')) { - def batteryResult = getBatteryResult(part, description) - if (batteryResult) { - results << batteryResult - } - } - else if (part.startsWith('rssi:')) { - def rssiResult = getRssiResult(part, description) - if (rssiResult) { - results << rssiResult - } - } - else if (part.startsWith('lqi:')) { - def lqiResult = getLqiResult(part, description) - if (lqiResult) { - results << lqiResult - } - } - } - - results -} - -private getBatteryResult(part, description) { - def batteryDivisor = description.split(",").find {it.split(":")[0].trim() == "batteryDivisor"} ? description.split(",").find {it.split(":")[0].trim() == "batteryDivisor"}.split(":")[1].trim() : null - def name = "battery" - def value = zigbee.parseSmartThingsBatteryValue(part, batteryDivisor) - def unit = "%" - def linkText = getLinkText(device) - def descriptionText = "$linkText battery was ${value}${unit}" - def isStateChange = isStateChange(device, name, value) - - [ - name: name, - value: value, - unit: unit, - linkText: linkText, - descriptionText: descriptionText, - handlerName: name, - isStateChange: isStateChange, - //displayed: displayed(description, isStateChange) - displayed: false - ] -} - -private getRssiResult(part, description) { - def name = "rssi" - def parts = part.split(":") - if (parts.size() != 2) return null - - def valueString = parts[1].trim() - def valueInt = Integer.parseInt(valueString, 16) - def value = (valueInt - 128).toString() - def linkText = getLinkText(device) - def descriptionText = "$linkText was $value dBm" - def isStateChange = isStateChange(device, name, value) - - [ - name: name, - value: value, - unit: "dBm", - linkText: linkText, - descriptionText: descriptionText, - handlerName: null, - isStateChange: isStateChange, - //displayed: displayed(description, isStateChange) - displayed: false - ] -} - -/** - * Use LQI (Link Quality Indicator) as a measure of signal strength. The values - * are 0 to 255 (0x00 to 0xFF) and higher values represent higher signal - * strength. Return as a percentage of 255. - * - * Note: To make the signal strength indicator more accurate, we could combine - * LQI with RSSI. - */ -private getLqiResult(part, description) { - def name = "lqi" - def parts = part.split(":") - if (parts.size() != 2) return null - - def valueString = parts[1].trim() - def valueInt = Integer.parseInt(valueString, 16) - def percentageOf = 255 - def value = Math.round((valueInt / percentageOf * 100)).toString() - def unit = "%" - def linkText = getLinkText(device) - def descriptionText = "$linkText Signal (LQI) was ${value}${unit}" - def isStateChange = isStateChange(device, name, value) - - [ - name: name, - value: value, - unit: unit, - linkText: linkText, - descriptionText: descriptionText, - handlerName: null, - isStateChange: isStateChange, - //displayed: displayed(description, isStateChange) - displayed: false - ] -} +/** + * 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. + * + */ +metadata { + definition (name: "Arrival Sensor", namespace: "smartthings", author: "SmartThings") { + capability "Tone" + capability "Actuator" + capability "Signal Strength" + capability "Presence Sensor" + capability "Sensor" + capability "Battery" + + fingerprint profileId: "FC01", deviceId: "019A" + fingerprint profileId: "FC01", deviceId: "0131", inClusters: "0000,0003", outClusters: "0003" + fingerprint profileId: "FC01", deviceId: "0131", inClusters: "0000", outClusters: "0006" + } + + simulator { + status "present": "presence: 1" + status "not present": "presence: 0" + status "battery": "battery: 27, batteryDivisor: 0A, rssi: 100, lqi: 64" + } + + preferences { + section { + image(name: 'educationalcontent', multiple: true, images: [ + "http://cdn.device-gse.smartthings.com/Arrival/Arrival1.jpg", + "http://cdn.device-gse.smartthings.com/Arrival/Arrival2.jpg" + ]) + } + } + + tiles { + standardTile("presence", "device.presence", width: 2, height: 2, canChangeBackground: true) { + state "present", labelIcon:"st.presence.tile.present", backgroundColor:"#53a7c0" + state "not present", labelIcon:"st.presence.tile.not-present", backgroundColor:"#ebeef2" + } + standardTile("beep", "device.beep", decoration: "flat") { + state "beep", label:'', action:"tone.beep", icon:"st.secondary.beep", backgroundColor:"#ffffff" + } + valueTile("battery", "device.battery", decoration: "flat", inactiveLabel: false) { + state "battery", label:'${currentValue}% battery', unit:""/*, backgroundColors:[ + [value: 5, color: "#BC2323"], + [value: 10, color: "#D04E00"], + [value: 15, color: "#F1D801"], + [value: 16, color: "#FFFFFF"] + ]*/ + } + /* + valueTile("lqi", "device.lqi", decoration: "flat", inactiveLabel: false) { + state "lqi", label:'${currentValue}% signal', unit:"" + } + */ + + main "presence" + details(["presence", "beep", "battery"/*, "lqi"*/]) + } +} + +def beep() { + /* + You can make the speaker turn on for 0.5-second beeps by sending some CLI commands: + + Command: send raw, wait 7, send raw, wait 7, send raw + Future: new packet type "st.beep" + + raw 0xFC05 {15 0A 11 00 00 15 01} + send 0x2F7F 2 2 + + where "0xABCD" is the node ID of the Smart Tag, everything else above is a constant. Except + the "15 01" at the end of the first raw command, that sets the speaker's period (reciprocal + of frequency). You can play with this value up or down to experiment with loudness as the + loudness will be strongly dependent upon frequency and the enclosure that it's in. Note that + "15 01" represents the hex number 0x0115 so a lower frequency is "16 01" (longer period) and + a higher frequency is "14 01" (shorter period). Note that since the tag only checks its parent + for messages every 5 seconds (while at rest) or every 3 seconds (while in motion) it will take + up to this long from the time you send the message to the time you hear a sound. + */ + + [ + "raw 0xFC05 {15 0A 11 00 00 15 01}", + "delay 7000", + "raw 0xFC05 {15 0A 11 00 00 15 01}", + "delay 7000", + "raw 0xFC05 {15 0A 11 00 00 15 01}", + "delay 7000", + "raw 0xFC05 {15 0A 11 00 00 15 01}", + "delay 7000", + "raw 0xFC05 {15 0A 11 00 00 15 01}" + ] +} + +def parse(String description) { + def results + if (isBatteryMessage(description)) { + results = parseBatteryMessage(description) + } + else { + results = parsePresenceMessage(description) + } + + log.debug "Parse returned $results.descriptionText" + results +} + +private Map parsePresenceMessage(String description) { + def name = parseName(description) + def value = parseValue(description) + def linkText = getLinkText(device) + def descriptionText = parseDescriptionText(linkText, value, description) + def handlerName = getState(value) + def isStateChange = isStateChange(device, name, value) + + def results = [ + name: name, + value: value, + unit: null, + linkText: linkText, + descriptionText: descriptionText, + handlerName: handlerName, + isStateChange: isStateChange, + displayed: displayed(description, isStateChange) + ] + + results +} + +private String parseName(String description) { + if (description?.startsWith("presence: ")) { + return "presence" + } + null +} + +private String parseValue(String description) { + if (description?.startsWith("presence: ")) + { + if (description?.endsWith("1")) + { + return "present" + } + else if (description?.endsWith("0")) + { + return "not present" + } + } + + description +} + +private parseDescriptionText(String linkText, String value, String description) { + switch(value) { + case "present": return "$linkText has arrived" + case "not present": return "$linkText has left" + default: return value + } +} + +private getState(String value) { + def state = value + if (value == "present") { + state = "arrived" + } + else if (value == "not present") { + state = "left" + } + + state +} + +private Boolean isBatteryMessage(String description) { + // "raw:36EF1C, dni:36EF, battery:1B, rssi:, lqi:" + description ==~ /.*battery:.*rssi:.*lqi:.*/ +} + +private List parseBatteryMessage(String description) { + def results = [] + def parts = description.split(',') + parts.each { part -> + part = part.trim() + if (part.startsWith('battery:')) { + def batteryResult = getBatteryResult(part, description) + if (batteryResult) { + results << batteryResult + } + } + else if (part.startsWith('rssi:')) { + def rssiResult = getRssiResult(part, description) + if (rssiResult) { + results << rssiResult + } + } + else if (part.startsWith('lqi:')) { + def lqiResult = getLqiResult(part, description) + if (lqiResult) { + results << lqiResult + } + } + } + + results +} + +private getBatteryResult(part, description) { + def batteryDivisor = description.split(",").find {it.split(":")[0].trim() == "batteryDivisor"} ? description.split(",").find {it.split(":")[0].trim() == "batteryDivisor"}.split(":")[1].trim() : null + def name = "battery" + def value = zigbee.parseSmartThingsBatteryValue(part, batteryDivisor) + def unit = "%" + def linkText = getLinkText(device) + def descriptionText = "$linkText battery was ${value}${unit}" + def isStateChange = isStateChange(device, name, value) + + [ + name: name, + value: value, + unit: unit, + linkText: linkText, + descriptionText: descriptionText, + handlerName: name, + isStateChange: isStateChange, + //displayed: displayed(description, isStateChange) + displayed: false + ] +} + +private getRssiResult(part, description) { + def name = "rssi" + def parts = part.split(":") + if (parts.size() != 2) return null + + def valueString = parts[1].trim() + def valueInt = Integer.parseInt(valueString, 16) + def value = (valueInt - 128).toString() + def linkText = getLinkText(device) + def descriptionText = "$linkText was $value dBm" + def isStateChange = isStateChange(device, name, value) + + [ + name: name, + value: value, + unit: "dBm", + linkText: linkText, + descriptionText: descriptionText, + handlerName: null, + isStateChange: isStateChange, + //displayed: displayed(description, isStateChange) + displayed: false + ] +} + +/** + * Use LQI (Link Quality Indicator) as a measure of signal strength. The values + * are 0 to 255 (0x00 to 0xFF) and higher values represent higher signal + * strength. Return as a percentage of 255. + * + * Note: To make the signal strength indicator more accurate, we could combine + * LQI with RSSI. + */ +private getLqiResult(part, description) { + def name = "lqi" + def parts = part.split(":") + if (parts.size() != 2) return null + + def valueString = parts[1].trim() + def valueInt = Integer.parseInt(valueString, 16) + def percentageOf = 255 + def value = Math.round((valueInt / percentageOf * 100)).toString() + def unit = "%" + def linkText = getLinkText(device) + def descriptionText = "$linkText Signal (LQI) was ${value}${unit}" + def isStateChange = isStateChange(device, name, value) + + [ + name: name, + value: value, + unit: unit, + linkText: linkText, + descriptionText: descriptionText, + handlerName: null, + isStateChange: isStateChange, + //displayed: displayed(description, isStateChange) + displayed: false + ] +} \ No newline at end of file diff --git a/smartapps/smartthings/button-controller.src/button-controller.groovy b/smartapps/smartthings/button-controller.src/button-controller.groovy index 4759e54..d2cf5d0 100644 --- a/smartapps/smartthings/button-controller.src/button-controller.groovy +++ b/smartapps/smartthings/button-controller.src/button-controller.groovy @@ -1,322 +1,322 @@ -/** - * 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. - * - * Button Controller - * - * Author: SmartThings - * Date: 2014-5-21 - */ -definition( - name: "Button Controller", - namespace: "smartthings", - author: "SmartThings", - description: "Control devices with buttons like the Aeon Labs Minimote", - category: "Convenience", - iconUrl: "https://s3.amazonaws.com/smartapp-icons/MyApps/Cat-MyApps.png", - iconX2Url: "https://s3.amazonaws.com/smartapp-icons/MyApps/Cat-MyApps@2x.png" -) - -preferences { - page(name: "selectButton") - page(name: "configureButton1") - page(name: "configureButton2") - page(name: "configureButton3") - page(name: "configureButton4") - - page(name: "timeIntervalInput", title: "Only during a certain time") { - section { - input "starting", "time", title: "Starting", required: false - input "ending", "time", title: "Ending", required: false - } - } -} - -def selectButton() { - dynamicPage(name: "selectButton", title: "First, select your button device", nextPage: "configureButton1", uninstall: configured()) { - section { - input "buttonDevice", "capability.button", title: "Button", multiple: false, required: true - } - - section(title: "More options", hidden: hideOptionsSection(), hideable: true) { - - def timeLabel = timeIntervalLabel() - - href "timeIntervalInput", title: "Only during a certain time", description: timeLabel ?: "Tap to set", state: timeLabel ? "complete" : null - - input "days", "enum", title: "Only on certain days of the week", multiple: true, required: false, - options: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"] - - input "modes", "mode", title: "Only when mode is", multiple: true, required: false - } - } -} - -def configureButton1() { - dynamicPage(name: "configureButton1", title: "Now let's decide how to use the first button", - nextPage: "configureButton2", uninstall: configured(), getButtonSections(1)) -} -def configureButton2() { - dynamicPage(name: "configureButton2", title: "If you have a second button, set it up here", - nextPage: "configureButton3", uninstall: configured(), getButtonSections(2)) -} - -def configureButton3() { - dynamicPage(name: "configureButton3", title: "If you have a third button, you can do even more here", - nextPage: "configureButton4", uninstall: configured(), getButtonSections(3)) -} -def configureButton4() { - dynamicPage(name: "configureButton4", title: "If you have a fourth button, you rule, and can set it up here", - install: true, uninstall: true, getButtonSections(4)) -} - -def getButtonSections(buttonNumber) { - return { - section("Lights") { - input "lights_${buttonNumber}_pushed", "capability.switch", title: "Pushed", multiple: true, required: false - input "lights_${buttonNumber}_held", "capability.switch", title: "Held", multiple: true, required: false - } - section("Locks") { - input "locks_${buttonNumber}_pushed", "capability.lock", title: "Pushed", multiple: true, required: false - input "locks_${buttonNumber}_held", "capability.lock", title: "Held", multiple: true, required: false - } - section("Sonos") { - input "sonos_${buttonNumber}_pushed", "capability.musicPlayer", title: "Pushed", multiple: true, required: false - input "sonos_${buttonNumber}_held", "capability.musicPlayer", title: "Held", multiple: true, required: false - } - section("Modes") { - input "mode_${buttonNumber}_pushed", "mode", title: "Pushed", required: false - input "mode_${buttonNumber}_held", "mode", title: "Held", required: false - } - def phrases = location.helloHome?.getPhrases()*.label - if (phrases) { - section("Hello Home Actions") { - log.trace phrases - input "phrase_${buttonNumber}_pushed", "enum", title: "Pushed", required: false, options: phrases - input "phrase_${buttonNumber}_held", "enum", title: "Held", required: false, options: phrases - } - } - section("Sirens") { - input "sirens_${buttonNumber}_pushed","capability.alarm" ,title: "Pushed", multiple: true, required: false - input "sirens_${buttonNumber}_held", "capability.alarm", title: "Held", multiple: true, required: false - } - - section("Custom Message") { - input "textMessage_${buttonNumber}", "text", title: "Message", required: false - } - - section("Push Notifications") { - input "notifications_${buttonNumber}_pushed","bool" ,title: "Pushed", required: false, defaultValue: false - input "notifications_${buttonNumber}_held", "bool", title: "Held", required: false, defaultValue: false - } - - section("Sms Notifications") { - input "phone_${buttonNumber}_pushed","phone" ,title: "Pushed", required: false - input "phone_${buttonNumber}_held", "phone", title: "Held", required: false - } - } -} - -def installed() { - initialize() -} - -def updated() { - unsubscribe() - initialize() -} - -def initialize() { - subscribe(buttonDevice, "button", buttonEvent) -} - -def configured() { - return buttonDevice || buttonConfigured(1) || buttonConfigured(2) || buttonConfigured(3) || buttonConfigured(4) -} - -def buttonConfigured(idx) { - return settings["lights_$idx_pushed"] || - settings["locks_$idx_pushed"] || - settings["sonos_$idx_pushed"] || - settings["mode_$idx_pushed"] || - settings["notifications_$idx_pushed"] || - settings["sirens_$idx_pushed"] || - settings["notifications_$idx_pushed"] || - settings["phone_$idx_pushed"] -} - -def buttonEvent(evt){ - if(allOk) { - def buttonNumber = evt.data // why doesn't jsonData work? always returning [:] - def value = evt.value - log.debug "buttonEvent: $evt.name = $evt.value ($evt.data)" - log.debug "button: $buttonNumber, value: $value" - - def recentEvents = buttonDevice.eventsSince(new Date(now() - 3000)).findAll{it.value == evt.value && it.data == evt.data} - log.debug "Found ${recentEvents.size()?:0} events in past 3 seconds" - - if(recentEvents.size <= 1){ - switch(buttonNumber) { - case ~/.*1.*/: - executeHandlers(1, value) - break - case ~/.*2.*/: - executeHandlers(2, value) - break - case ~/.*3.*/: - executeHandlers(3, value) - break - case ~/.*4.*/: - executeHandlers(4, value) - break - } - } else { - log.debug "Found recent button press events for $buttonNumber with value $value" - } - } -} - -def executeHandlers(buttonNumber, value) { - log.debug "executeHandlers: $buttonNumber - $value" - - def lights = find('lights', buttonNumber, value) - if (lights != null) toggle(lights) - - def locks = find('locks', buttonNumber, value) - if (locks != null) toggle(locks) - - def sonos = find('sonos', buttonNumber, value) - if (sonos != null) toggle(sonos) - - def mode = find('mode', buttonNumber, value) - if (mode != null) changeMode(mode) - - def phrase = find('phrase', buttonNumber, value) - if (phrase != null) location.helloHome.execute(phrase) - - def textMessage = findMsg('textMessage', buttonNumber) - - def notifications = find('notifications', buttonNumber, value) - if (notifications?.toBoolean()) sendPush(textMessage ?: "Button $buttonNumber was pressed" ) - - def phone = find('phone', buttonNumber, value) - if (phone != null) sendSms(phone, textMessage ?:"Button $buttonNumber was pressed") - - def sirens = find('sirens', buttonNumber, value) - if (sirens != null) toggle(sirens) -} - -def find(type, buttonNumber, value) { - def preferenceName = type + "_" + buttonNumber + "_" + value - def pref = settings[preferenceName] - if(pref != null) { - log.debug "Found: $pref for $preferenceName" - } - - return pref -} - -def findMsg(type, buttonNumber) { - def preferenceName = type + "_" + buttonNumber - def pref = settings[preferenceName] - if(pref != null) { - log.debug "Found: $pref for $preferenceName" - } - - return pref -} - -def toggle(devices) { - log.debug "toggle: $devices = ${devices*.currentValue('switch')}" - - if (devices*.currentValue('switch').contains('on')) { - devices.off() - } - else if (devices*.currentValue('switch').contains('off')) { - devices.on() - } - else if (devices*.currentValue('lock').contains('locked')) { - devices.unlock() - } - else if (devices*.currentValue('lock').contains('unlocked')) { - devices.lock() - } - else if (devices*.currentValue('alarm').contains('off')) { - devices.siren() - } - else { - devices.on() - } -} - -def changeMode(mode) { - log.debug "changeMode: $mode, location.mode = $location.mode, location.modes = $location.modes" - - if (location.mode != mode && location.modes?.find { it.name == mode }) { - setLocationMode(mode) - } -} - -// execution filter methods -private getAllOk() { - modeOk && daysOk && timeOk -} - -private getModeOk() { - def result = !modes || modes.contains(location.mode) - log.trace "modeOk = $result" - result -} - -private getDaysOk() { - def result = true - if (days) { - def df = new java.text.SimpleDateFormat("EEEE") - if (location.timeZone) { - df.setTimeZone(location.timeZone) - } - else { - df.setTimeZone(TimeZone.getTimeZone("America/New_York")) - } - def day = df.format(new Date()) - result = days.contains(day) - } - log.trace "daysOk = $result" - result -} - -private getTimeOk() { - def result = true - if (starting && ending) { - def currTime = now() - def start = timeToday(starting).time - def stop = timeToday(ending).time - result = start < stop ? currTime >= start && currTime <= stop : currTime <= stop || currTime >= start - } - log.trace "timeOk = $result" - result -} - -private hhmm(time, fmt = "h:mm a") -{ - def t = timeToday(time, location.timeZone) - def f = new java.text.SimpleDateFormat(fmt) - f.setTimeZone(location.timeZone ?: timeZone(time)) - f.format(t) -} - -private hideOptionsSection() { - (starting || ending || days || modes) ? false : true -} - -private timeIntervalLabel() { - (starting && ending) ? hhmm(starting) + "-" + hhmm(ending, "h:mm a z") : "" -} +/** + * 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. + * + * Button Controller + * + * Author: SmartThings + * Date: 2014-5-21 + */ +definition( + name: "Button Controller", + namespace: "smartthings", + author: "SmartThings", + description: "Control devices with buttons like the Aeon Labs Minimote", + category: "Convenience", + iconUrl: "https://s3.amazonaws.com/smartapp-icons/MyApps/Cat-MyApps.png", + iconX2Url: "https://s3.amazonaws.com/smartapp-icons/MyApps/Cat-MyApps@2x.png" +) + +preferences { + page(name: "selectButton") + page(name: "configureButton1") + page(name: "configureButton2") + page(name: "configureButton3") + page(name: "configureButton4") + + page(name: "timeIntervalInput", title: "Only during a certain time") { + section { + input "starting", "time", title: "Starting", required: false + input "ending", "time", title: "Ending", required: false + } + } +} + +def selectButton() { + dynamicPage(name: "selectButton", title: "First, select your button device", nextPage: "configureButton1", uninstall: configured()) { + section { + input "buttonDevice", "capability.button", title: "Button", multiple: false, required: true + } + + section(title: "More options", hidden: hideOptionsSection(), hideable: true) { + + def timeLabel = timeIntervalLabel() + + href "timeIntervalInput", title: "Only during a certain time", description: timeLabel ?: "Tap to set", state: timeLabel ? "complete" : null + + input "days", "enum", title: "Only on certain days of the week", multiple: true, required: false, + options: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"] + + input "modes", "mode", title: "Only when mode is", multiple: true, required: false + } + } +} + +def configureButton1() { + dynamicPage(name: "configureButton1", title: "Now let's decide how to use the first button", + nextPage: "configureButton2", uninstall: configured(), getButtonSections(1)) +} +def configureButton2() { + dynamicPage(name: "configureButton2", title: "If you have a second button, set it up here", + nextPage: "configureButton3", uninstall: configured(), getButtonSections(2)) +} + +def configureButton3() { + dynamicPage(name: "configureButton3", title: "If you have a third button, you can do even more here", + nextPage: "configureButton4", uninstall: configured(), getButtonSections(3)) +} +def configureButton4() { + dynamicPage(name: "configureButton4", title: "If you have a fourth button, you rule, and can set it up here", + install: true, uninstall: true, getButtonSections(4)) +} + +def getButtonSections(buttonNumber) { + return { + section("Lights") { + input "lights_${buttonNumber}_pushed", "capability.switch", title: "Pushed", multiple: true, required: false + input "lights_${buttonNumber}_held", "capability.switch", title: "Held", multiple: true, required: false + } + section("Locks") { + input "locks_${buttonNumber}_pushed", "capability.lock", title: "Pushed", multiple: true, required: false + input "locks_${buttonNumber}_held", "capability.lock", title: "Held", multiple: true, required: false + } + section("Sonos") { + input "sonos_${buttonNumber}_pushed", "capability.musicPlayer", title: "Pushed", multiple: true, required: false + input "sonos_${buttonNumber}_held", "capability.musicPlayer", title: "Held", multiple: true, required: false + } + section("Modes") { + input "mode_${buttonNumber}_pushed", "mode", title: "Pushed", required: false + input "mode_${buttonNumber}_held", "mode", title: "Held", required: false + } + def phrases = location.helloHome?.getPhrases()*.label + if (phrases) { + section("Hello Home Actions") { + log.trace phrases + input "phrase_${buttonNumber}_pushed", "enum", title: "Pushed", required: false, options: phrases + input "phrase_${buttonNumber}_held", "enum", title: "Held", required: false, options: phrases + } + } + section("Sirens") { + input "sirens_${buttonNumber}_pushed","capability.alarm" ,title: "Pushed", multiple: true, required: false + input "sirens_${buttonNumber}_held", "capability.alarm", title: "Held", multiple: true, required: false + } + + section("Custom Message") { + input "textMessage_${buttonNumber}", "text", title: "Message", required: false + } + + section("Push Notifications") { + input "notifications_${buttonNumber}_pushed","bool" ,title: "Pushed", required: false, defaultValue: false + input "notifications_${buttonNumber}_held", "bool", title: "Held", required: false, defaultValue: false + } + + section("Sms Notifications") { + input "phone_${buttonNumber}_pushed","phone" ,title: "Pushed", required: false + input "phone_${buttonNumber}_held", "phone", title: "Held", required: false + } + } +} + +def installed() { + initialize() +} + +def updated() { + unsubscribe() + initialize() +} + +def initialize() { + subscribe(buttonDevice, "button", buttonEvent) +} + +def configured() { + return buttonDevice || buttonConfigured(1) || buttonConfigured(2) || buttonConfigured(3) || buttonConfigured(4) +} + +def buttonConfigured(idx) { + return settings["lights_$idx_pushed"] || + settings["locks_$idx_pushed"] || + settings["sonos_$idx_pushed"] || + settings["mode_$idx_pushed"] || + settings["notifications_$idx_pushed"] || + settings["sirens_$idx_pushed"] || + settings["notifications_$idx_pushed"] || + settings["phone_$idx_pushed"] +} + +def buttonEvent(evt){ + if(allOk) { + def buttonNumber = evt.data // why doesn't jsonData work? always returning [:] + def value = evt.value + log.debug "buttonEvent: $evt.name = $evt.value ($evt.data)" + log.debug "button: $buttonNumber, value: $value" + + def recentEvents = buttonDevice.eventsSince(new Date(now() - 3000)).findAll{it.value == evt.value && it.data == evt.data} + log.debug "Found ${recentEvents.size()?:0} events in past 3 seconds" + + if(recentEvents.size <= 1){ + switch(buttonNumber) { + case ~/.*1.*/: + executeHandlers(1, value) + break + case ~/.*2.*/: + executeHandlers(2, value) + break + case ~/.*3.*/: + executeHandlers(3, value) + break + case ~/.*4.*/: + executeHandlers(4, value) + break + } + } else { + log.debug "Found recent button press events for $buttonNumber with value $value" + } + } +} + +def executeHandlers(buttonNumber, value) { + log.debug "executeHandlers: $buttonNumber - $value" + + def lights = find('lights', buttonNumber, value) + if (lights != null) toggle(lights) + + def locks = find('locks', buttonNumber, value) + if (locks != null) toggle(locks) + + def sonos = find('sonos', buttonNumber, value) + if (sonos != null) toggle(sonos) + + def mode = find('mode', buttonNumber, value) + if (mode != null) changeMode(mode) + + def phrase = find('phrase', buttonNumber, value) + if (phrase != null) location.helloHome.execute(phrase) + + def textMessage = findMsg('textMessage', buttonNumber) + + def notifications = find('notifications', buttonNumber, value) + if (notifications?.toBoolean()) sendPush(textMessage ?: "Button $buttonNumber was pressed" ) + + def phone = find('phone', buttonNumber, value) + if (phone != null) sendSms(phone, textMessage ?:"Button $buttonNumber was pressed") + + def sirens = find('sirens', buttonNumber, value) + if (sirens != null) toggle(sirens) +} + +def find(type, buttonNumber, value) { + def preferenceName = type + "_" + buttonNumber + "_" + value + def pref = settings[preferenceName] + if(pref != null) { + log.debug "Found: $pref for $preferenceName" + } + + return pref +} + +def findMsg(type, buttonNumber) { + def preferenceName = type + "_" + buttonNumber + def pref = settings[preferenceName] + if(pref != null) { + log.debug "Found: $pref for $preferenceName" + } + + return pref +} + +def toggle(devices) { + log.debug "toggle: $devices = ${devices*.currentValue('switch')}" + + if (devices*.currentValue('switch').contains('on')) { + devices.off() + } + else if (devices*.currentValue('switch').contains('off')) { + devices.on() + } + else if (devices*.currentValue('lock').contains('locked')) { + devices.unlock() + } + else if (devices*.currentValue('lock').contains('unlocked')) { + devices.lock() + } + else if (devices*.currentValue('alarm').contains('off')) { + devices.siren() + } + else { + devices.on() + } +} + +def changeMode(mode) { + log.debug "changeMode: $mode, location.mode = $location.mode, location.modes = $location.modes" + + if (location.mode != mode && location.modes?.find { it.name == mode }) { + setLocationMode(mode) + } +} + +// execution filter methods +private getAllOk() { + modeOk && daysOk && timeOk +} + +private getModeOk() { + def result = !modes || modes.contains(location.mode) + log.trace "modeOk = $result" + result +} + +private getDaysOk() { + def result = true + if (days) { + def df = new java.text.SimpleDateFormat("EEEE") + if (location.timeZone) { + df.setTimeZone(location.timeZone) + } + else { + df.setTimeZone(TimeZone.getTimeZone("America/New_York")) + } + def day = df.format(new Date()) + result = days.contains(day) + } + log.trace "daysOk = $result" + result +} + +private getTimeOk() { + def result = true + if (starting && ending) { + def currTime = now() + def start = timeToday(starting).time + def stop = timeToday(ending).time + result = start < stop ? currTime >= start && currTime <= stop : currTime <= stop || currTime >= start + } + log.trace "timeOk = $result" + result +} + +private hhmm(time, fmt = "h:mm a") +{ + def t = timeToday(time, location.timeZone) + def f = new java.text.SimpleDateFormat(fmt) + f.setTimeZone(location.timeZone ?: timeZone(time)) + f.format(t) +} + +private hideOptionsSection() { + (starting || ending || days || modes) ? false : true +} + +private timeIntervalLabel() { + (starting && ending) ? hhmm(starting) + "-" + hhmm(ending, "h:mm a z") : "" +} \ No newline at end of file