├── CrockPot.v010.app.groovy ├── MimoPowerIsOut.groovy ├── NotifyMeWhen.groovy ├── README.md ├── ResetColorOnClose.groovy ├── ST Sample OAuth2.groovy ├── SocketPowerIsOut.app.groovy ├── Trigger-Fibaro-RGBW-Action-From-Contact-Sensor.groovy ├── Turn-on-Police-Light-When-It-Opens.groovy ├── Turn-on-Police-Light-When-Switch-Is-On.groovy ├── Water Off When Wet.groovy ├── dim-with-me.app.groovy └── scene-machine.app.groovy /CrockPot.v010.app.groovy: -------------------------------------------------------------------------------- 1 | /***************************************************************************** 2 | Device: CrockPot.v010.app.groovy 3 | Author: twack@wackware.net 4 | Version: 1.0 5 | Date: 2013-04-06 6 | Purpose: To control a crockpot using a switch. This app was written using 7 | GE outdoor lighting controller so there is functionality to see if 8 | the controller or power strip is running/plugged in. Typically the 9 | user will have a Zwave outlet. You need to use a dumb crockpot that 10 | only has a off/low/med/high physical switch. Set the crockpot to the 11 | desired level and plug into the switch. 12 | 13 | Use License: Non-Profit Open Software License version 3.0 (NPOSL-3.0) 14 | http://opensource.org/licenses/NPOSL-3.0 15 | 16 | ****************************************************************************** 17 | Change Log 18 | 19 | Version: 1.0 20 | Date: 20130406 21 | Change1: Initial Release 22 | 23 | ****************************************************************************** 24 | 25 | Device Types Supported: Switch 26 | 27 | To-Do's: Create a customer companion device that has tile with minutes 28 | left to cook. 29 | 30 | Other Info: This written to demo at San Francisco with SmartThings. 31 | 32 | ******************************************************************************/ 33 | 34 | 35 | // Automatically generated. Make future change here. 36 | definition( 37 | name: "SmartThings CrockPot Controller 1.0", 38 | namespace: "wackware", 39 | author: "todd@wackford.net", 40 | description: "CrockPots Will Win the Machine -v- Man Wars!", 41 | category: "My Apps", 42 | iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png", 43 | iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience%402x.png") 44 | 45 | preferences { 46 | section("Crockpot Cooking Info..."){ 47 | input "startTime", "time", title: "Start time...", required: true 48 | input "onDuration", "decimal", title: "For how long...", required: true 49 | input "meal", "text", title: "Meal name (optional)...", required: false 50 | } 51 | section("CrockPot Notifications") { 52 | input "phone", "phone", title: "Text me at (optional)...", multiple: false, required: false 53 | } 54 | section("CrockPot Controller Switch") { 55 | input "switch1", "capability.switch", multiple: false, required: true 56 | } 57 | } 58 | 59 | def installed() { 60 | log.debug "Installed with settings: ${settings}" 61 | startUp() 62 | } 63 | 64 | def updated() { 65 | log.debug "Updated with settings: ${settings}" 66 | unschedule() 67 | unsubscribe() 68 | startUp() 69 | } 70 | 71 | def startUp() { 72 | 73 | def usrMilliTime = timeToday(startTime) 74 | if(now() > usrMilliTime.time) { 75 | log.debug "start time is not ok ${startTime}" 76 | communicateError("CROCKPOT ERROR: Your Starting Time is in the Past. Please update Starting Time") 77 | return 78 | } else { 79 | log.debug "start time is ok ${startTime}" 80 | } 81 | 82 | state.onTimer = onDuration?.toDouble() * 60 83 | state.errorCount = 0 84 | log.debug "The user running time is: ${state.onTimer}" 85 | schedule(startTime, "turnOnDevice") 86 | subscribe(switch1, "switch.on", communicateOn) 87 | subscribe(switch1, "switch.off", communicateOff) 88 | } 89 | 90 | def shutdown(){ 91 | unschedule() 92 | unsubscribe() 93 | switch1.off() 94 | } 95 | 96 | def checkStatus() { 97 | if (state.onTimer > 0) { 98 | log.debug "Timer = ${state.onTimer}" 99 | if (switchOK("on") == true) { 100 | state.onTimer = state.onTimer - 1 101 | state.errorCount = 0 102 | //here's where someday we'll update the display on the app/device for minutes left 103 | } else { 104 | state.errorCount = state.errorCount + 1 105 | switch1.on() 106 | } 107 | } else { 108 | // we should not get here as the off event handler will kill this schedule 109 | log.debug "Timer = ${state.onTimer}" 110 | switch1.off() 111 | } 112 | log.debug "We had ${state.errorCount - 1} errors calling device on" 113 | if(state.errorCount > 3) { 114 | communicateCantStart() 115 | shutdown() 116 | } 117 | } 118 | 119 | def switchOK(value) 120 | { 121 | def result = false 122 | for (it in (switch1 ?: [])) { 123 | log.debug "SwitchState = ${it.currentSwitch}" 124 | if (it.currentSwitch == value) { 125 | result = true 126 | break 127 | } 128 | } 129 | result 130 | } 131 | 132 | def turnOnDevice() { 133 | schedule("0 * * * * ?", "checkStatus") // used to verify on and stay on for duration 134 | checkStatus() 135 | } 136 | 137 | 138 | def communicateError(msg) { 139 | if (phone != "") { 140 | sendSms(phone, msg) 141 | } 142 | 143 | def push = true 144 | if(push == true) { 145 | sendPush(msg) 146 | } 147 | } 148 | 149 | def communicate(msg){ 150 | log.debug "COMMUNICATING" 151 | 152 | if (phone != "") { 153 | sendSms(phone, msg) 154 | } 155 | 156 | def push = true //code like this so we can toggle it on or off during testing in program 157 | if(push == true) { 158 | sendPush(msg) 159 | } 160 | } 161 | 162 | def communicateOn(evt){ 163 | if (settings['meal'] == '""') { 164 | settings['meal'] = "Meal" 165 | } 166 | 167 | def msg = "A Smart CrockPot Says: I have started cooking the ${meal}. " 168 | msg+= "I will be finished in ${onDuration} hours." 169 | communicate(msg) 170 | } 171 | 172 | def communicateOff(evt) { 173 | if (settings['meal'] == '""') { 174 | settings['meal'] = "Meal" 175 | } 176 | 177 | def msg = "A Smart CrockPot Says: I have finished cooking the ${meal}. " 178 | msg+= "Bon Appetit!" 179 | communicate(msg) 180 | shutdown() 181 | } 182 | 183 | def communicateCantStart() { 184 | def msg = "Unable to talk to the Cloud. " 185 | msg+= "Please contact your CrockPot System Administrator. " 186 | msg+= "Or, check that I am plugged in." 187 | communicate(msg) 188 | } 189 | -------------------------------------------------------------------------------- /MimoPowerIsOut.groovy: -------------------------------------------------------------------------------- 1 | /** 2 | * MimoPowerIsOut.groovy 3 | * 4 | * Copyright 2016 SmartThings 5 | * 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: 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 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. 14 | * 15 | */ 16 | 17 | definition( 18 | name: "MimoLite Power Is Out", 19 | namespace: "smartthings", 20 | author: "Todd Wackford", 21 | description: "Alert me of a power loss on MimoLite. SmartThings hub and internet connection must be working so this does not work if whole house power is lost. The app works great for like a GFI or breaker trips in part of the house.", 22 | category: "Safety & Security", 23 | iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png", 24 | iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png", 25 | iconX3Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png") 26 | 27 | preferences { 28 | section("When there's AC power loss on..."){ 29 | input "myDevice", "capability.contactSensor", title: "Where?" 30 | } 31 | section("Via a push notification and a text message(optional)"){ 32 | input "pushAndPhone", "enum", title: "Send Text?", required: false, metadata: [values: ["Yes","No"]] 33 | input "phone1", "phone", title: "Phone Number (for Text, optional)", required: false 34 | 35 | } 36 | } 37 | 38 | def installed() 39 | { 40 | subscribe(myDevice, "powered.powerOff", lostPowerHandler) 41 | } 42 | 43 | def updated() 44 | { 45 | unsubscribe() 46 | subscribe(myDevice, "powered.powerOff", lostPowerHandler) 47 | } 48 | 49 | def lostPowerHandler(evt) { 50 | log.trace "$evt.value: $evt, $settings" 51 | def msg = "${myDevice.label ?: myDevice.name} detected loss of input power" 52 | 53 | log.debug "sending push" 54 | sendPush(msg) 55 | 56 | if ( phone1 && pushAndPhone ) { 57 | log.debug "sending SMS to ${phone1}" 58 | sendSms(phone1, msg) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /NotifyMeWhen.groovy: -------------------------------------------------------------------------------- 1 | /** 2 | * Notify Me When 3 | * 4 | * Author: SmartThings 5 | * Date: 2013-03-20 6 | * 7 | * Change Log: 8 | * 1. Todd Wackford 9 | * 2014-10-03: Added capability.button device picker and button.pushed event subscription. For Doorbell. 10 | */ 11 | definition( 12 | name: "Notify Me When", 13 | namespace: "smartthings", 14 | author: "SmartThings", 15 | description: "Get a push notification or text message when any of a variety of SmartThings is activated. Supports button push, motion, contact, acceleration, moisture and presence sensors as well as switches.", 16 | category: "Convenience", 17 | iconUrl: "https://s3.amazonaws.com/smartapp-icons/Meta/window_contact.png", 18 | iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Meta/window_contact@2x.png" 19 | ) 20 | 21 | preferences { 22 | section("Choose one or more, when..."){ 23 | input "button", "capability.button", title: "Button Pushed", required: false, multiple: true //tw 24 | input "motion", "capability.motionSensor", title: "Motion Here", required: false, multiple: true 25 | input "contact", "capability.contactSensor", title: "Contact Opens", required: false, multiple: true 26 | input "contactClosed", "capability.contactSensor", title: "Contact Closes", required: false, multiple: true 27 | input "acceleration", "capability.accelerationSensor", title: "Acceleration Detected", required: false, multiple: true 28 | input "mySwitch", "capability.switch", title: "Switch Turned On", required: false, multiple: true 29 | input "mySwitchOff", "capability.switch", title: "Switch Turned Off", required: false, multiple: true 30 | input "arrivalPresence", "capability.presenceSensor", title: "Arrival Of", required: false, multiple: true 31 | input "departurePresence", "capability.presenceSensor", title: "Departure Of", required: false, multiple: true 32 | input "smoke", "capability.smokeDetector", title: "Smoke Detected", required: false, multiple: true 33 | input "water", "capability.waterSensor", title: "Water Sensor Wet", required: false, multiple: true 34 | } 35 | section("Send this message (optional, sends standard status message if not specified)"){ 36 | input "messageText", "text", title: "Message Text", required: false 37 | } 38 | section("Via a push notification and/or an SMS message"){ 39 | input "phone", "phone", title: "Phone Number (for SMS, optional)", required: false 40 | input "pushAndPhone", "enum", title: "Both Push and SMS?", required: false, options: ["Yes","No"] 41 | } 42 | section("Minimum time between messages (optional, defaults to every message)") { 43 | input "frequency", "decimal", title: "Minutes", required: false 44 | } 45 | } 46 | 47 | def installed() { 48 | log.debug "Installed with settings: ${settings}" 49 | subscribeToEvents() 50 | } 51 | 52 | def updated() { 53 | log.debug "Updated with settings: ${settings}" 54 | unsubscribe() 55 | subscribeToEvents() 56 | } 57 | 58 | def subscribeToEvents() { 59 | subscribe(button, "button.pushed", eventHandler) //tw 60 | subscribe(contact, "contact.open", eventHandler) 61 | subscribe(contactClosed, "contact.closed", eventHandler) 62 | subscribe(acceleration, "acceleration.active", eventHandler) 63 | subscribe(motion, "motion.active", eventHandler) 64 | subscribe(mySwitch, "switch.on", eventHandler) 65 | subscribe(mySwitchOff, "switch.off", eventHandler) 66 | subscribe(arrivalPresence, "presence.present", eventHandler) 67 | subscribe(departurePresence, "presence.not present", eventHandler) 68 | subscribe(smoke, "smoke.detected", eventHandler) 69 | subscribe(smoke, "smoke.tested", eventHandler) 70 | subscribe(smoke, "carbonMonoxide.detected", eventHandler) 71 | subscribe(water, "water.wet", eventHandler) 72 | } 73 | 74 | def eventHandler(evt) { 75 | log.debug "Notify got evt ${evt}" 76 | if (frequency) { 77 | def lastTime = state[evt.deviceId] 78 | if (lastTime == null || now() - lastTime >= frequency * 60000) { 79 | sendMessage(evt) 80 | } 81 | } 82 | else { 83 | sendMessage(evt) 84 | } 85 | } 86 | 87 | private sendMessage(evt) { 88 | def msg = messageText ?: defaultText(evt) 89 | log.debug "$evt.name:$evt.value, pushAndPhone:$pushAndPhone, '$msg'" 90 | 91 | if (!phone || pushAndPhone != "No") { 92 | log.debug "sending push" 93 | sendPush(msg) 94 | } 95 | if (phone) { 96 | log.debug "sending SMS" 97 | sendSms(phone, msg) 98 | } 99 | if (frequency) { 100 | state[evt.deviceId] = now() 101 | } 102 | } 103 | 104 | private defaultText(evt) { 105 | if (evt.name == "presence") { 106 | if (evt.value == "present") { 107 | if (includeArticle) { 108 | "$evt.linkText has arrived at the $location.name" 109 | } 110 | else { 111 | "$evt.linkText has arrived at $location.name" 112 | } 113 | } 114 | else { 115 | if (includeArticle) { 116 | "$evt.linkText has left the $location.name" 117 | } 118 | else { 119 | "$evt.linkText has left $location.name" 120 | } 121 | } 122 | } 123 | else { 124 | evt.descriptionText 125 | } 126 | } 127 | 128 | private getIncludeArticle() { 129 | def name = location.name.toLowerCase() 130 | def segs = name.split(" ") 131 | !(["work","home"].contains(name) || (segs.size() > 1 && (["the","my","a","an"].contains(segs[0]) || segs[0].endsWith("'s")))) 132 | } 133 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | smarthings-apps 2 | =============== 3 | 4 | Files for SmartThings Apps 5 | -------------------------------------------------------------------------------- /ResetColorOnClose.groovy: -------------------------------------------------------------------------------- 1 | /** 2 | * Reset Color on Close 3 | * 4 | * Copyright 2015 smartthings 5 | * 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: 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 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. 14 | * 15 | */ 16 | 17 | definition ( 18 | name: "Reset Color on Close", 19 | namespace: "smartthings", 20 | author: "smartthings", 21 | description: "Return color bulbs to previous setting on closure of contact sensor(s).", 22 | category: "Convenience", 23 | iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png", 24 | iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png", 25 | iconX3Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png" 26 | ) 27 | 28 | preferences { 29 | section("When this/these contact(s) close...") { 30 | input "contacts", "capability.contactSensor", multiple: true 31 | } 32 | section("Return this light to the color at contact open...") { 33 | input "bulb", "capability.colorControl" 34 | } 35 | } 36 | 37 | def installed() { 38 | subscribe(contacts, "contact.open", contactOpenHandler) 39 | subscribe(contacts, "contact.closed", contactClosedHandler) 40 | } 41 | 42 | def updated() { 43 | unsubscribe() 44 | subscribe(contacts, "contact.open", contactOpenHandler) 45 | subscribe(contacts, "contact.closed", contactclosedHandler) 46 | } 47 | 48 | def contactOpenHandler(evt) { 49 | def values = [:] 50 | values = [ level: bulb.latestValue("level") as Integer, 51 | hex: bulb.latestValue("color"), 52 | saturation: bulb.latestValue("saturation"), 53 | hue: bulb.latestValue("hue")] 54 | 55 | atomicState.previousValues = values 56 | log.info "Previous values are: ${atomicState.previousValues}" 57 | } 58 | 59 | def contactclosedHandler(evt) { 60 | bulb.setColor(atomicState.previousValues) 61 | } 62 | -------------------------------------------------------------------------------- /ST Sample OAuth2.groovy: -------------------------------------------------------------------------------- 1 | /** 2 | * OAuth2 Credentials Service Manager 3 | * 4 | * Author: todd@wackford.net 5 | * Date: 2014-03-05 6 | */ 7 | 8 | // Automatically generated. Make future change here. 9 | definition( 10 | name: "OAuth2 Credentials Service Manager", 11 | namespace: "YourNameSpaceGoesHere", 12 | author: "barney@rubble.com", 13 | description: "Connect your Cloud Devices to SmartThings using OAuth2 credential methods.", 14 | category: "My Apps", 15 | iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png", 16 | iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience%402x.png", 17 | oauth: true) 18 | 19 | preferences { 20 | page(name: "Credentials", title: "Fetch OAuth2 Credentials", content: "authPage", install: false) 21 | } 22 | 23 | mappings { 24 | path("/receivedToken") { action: [ POST: "receivedToken", GET: "receivedToken"] } 25 | path("/receiveToken") { action: [ POST: "receiveToken", GET: "receiveToken"] } 26 | 27 | // This is where you get call backs and process posted events from the vendor. This code will get errors until 28 | // you define a legit endpoint (below). Error example "Service Manager DOES NOT RESPOND TO UPDATED HANDLER" 29 | 30 | //path("/vendorEvents") { action: [ POST: "vendorPostEventsHandler", GET: "vendorGetEventsHandler"] } 31 | } 32 | 33 | private getVendorName() { "Super Widgets" } 34 | private getVendorAuthPath() { "https://superapi.superwidgets.com/oauth2/authorize?" } 35 | private getVendorTokenPath(){ "https://superapi.superwidgets.com/oauth2/token?" } 36 | private getVendorIcon() { "https://s3.amazonaws.com/smartthings-device-icons/custom/super-widgets/beertap@2x.png" } 37 | private getClientId() { "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" } 38 | private getClientSecret() { "yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy" } 39 | 40 | private getServerUrl() { return "https://graph.api.smartthings.com" } 41 | 42 | 43 | def authPage() { 44 | log.debug "In authPage" 45 | 46 | def description = null 47 | 48 | if (state.vendorAccessToken == null) { 49 | log.debug "About to create access token." 50 | 51 | createAccessToken() 52 | description = "Tap to enter Credentials." 53 | 54 | def redirectUrl = oauthInitUrl() 55 | 56 | 57 | return dynamicPage(name: "Credentials", title: "Authorize Connection", nextPage: null, uninstall: false, install:false) { 58 | section { href url:redirectUrl, style:"embedded", required:false, title:"Connect to ${getVendorName()}:", description:description } 59 | } 60 | } else { 61 | description = "Press 'Done' to proceed" 62 | 63 | return dynamicPage(name: "Credentials", title: "Credentials Accepted!", nextPage: null, uninstall: true, install:true) { 64 | section { href url: buildRedirectUrl("receivedToken"), style:"embedded", required:false, title:"${getVendorName()} is now connected to SmartThings!", description:description } 65 | } 66 | } 67 | } 68 | 69 | def oauthInitUrl() { 70 | log.debug "In oauthInitUrl" 71 | 72 | /* OAuth Step 1: Request access code with our client ID */ 73 | 74 | state.oauthInitState = UUID.randomUUID().toString() 75 | 76 | def oauthParams = [ response_type: "code", 77 | client_id: getClientId(), 78 | state: state.oauthInitState, 79 | redirect_uri: buildRedirectUrl("receiveToken") ] 80 | 81 | return getVendorAuthPath() + toQueryString(oauthParams) 82 | } 83 | 84 | def buildRedirectUrl(endPoint) { 85 | log.debug "In buildRedirectUrl" 86 | 87 | return getServerUrl() + "/api/token/${state.accessToken}/smartapps/installations/${app.id}/${endPoint}" 88 | } 89 | 90 | def receiveToken() { 91 | log.debug "In receiveToken" 92 | 93 | def oauthParams = [ client_secret: getClientSecret(), 94 | grant_type: "authorization_code", 95 | code: params.code ] 96 | 97 | def tokenUrl = getVendorTokenPath() + toQueryString(oauthParams) 98 | def params = [ 99 | uri: tokenUrl, 100 | ] 101 | 102 | /* OAuth Step 2: Request access token with our client Secret and OAuth "Code" */ 103 | httpPost(params) { response -> 104 | 105 | def data = response.data.data 106 | 107 | state.vendorRefreshToken = data.refresh_token //these may need to be adjusted depending on depth of returned data 108 | state.vendorAccessToken = data.access_token 109 | } 110 | 111 | if ( !state.vendorAccessToken ) { //We didn't get an access token, bail on install 112 | return 113 | } 114 | 115 | /* OAuth Step 3: Use the access token to call into the vendor API throughout your code using state.vendorAccessToken. */ 116 | 117 | def html = """ 118 | 119 | 120 |
121 | 122 |We have located your """ + getVendorName() + """ account.
180 |Tap 'Done' to process your credentials.
181 |Your Quirky account is now connected to SmartThings. Tap 'Done' to continue to choose devices.
254 |