├── askhome108.png ├── askhome512.png ├── AskHome-Overview-Installation.pdf ├── AskHome What ├── patches └── OAUTH-page-patch.groovy ├── Lambda-node.js ├── README.md ├── Alexa-Interaction-Model.txt ├── AskHome-Bare.groovy └── AskHome.groovy /askhome108.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/n8xd/AskHome/HEAD/askhome108.png -------------------------------------------------------------------------------- /askhome512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/n8xd/AskHome/HEAD/askhome512.png -------------------------------------------------------------------------------- /AskHome-Overview-Installation.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/n8xd/AskHome/HEAD/AskHome-Overview-Installation.pdf -------------------------------------------------------------------------------- /AskHome What: -------------------------------------------------------------------------------- 1 | A few of the things AskHome will do for me. 2 | 3 | Alexa... 4 | 5 | ask home about the front door 6 | ask home is the front door locked 7 | ask home to unlock the front door 8 | ask home to unlock the front door (code) 9 | ask home to lock the front door 10 | ask home is the back door locked? 11 | ask home is the laundry done? 12 | ask home to go wolverine 13 | ask home to go sparty 14 | ask home to turn the water off 15 | ask home to open the water shutoff valve 16 | ask home about the star 17 | ask home to turn on the bedroom light 18 | ask home is the bedroom light on 19 | ask home about the basement 20 | ask home which batteries are low 21 | ask home about the kitchen air 22 | ask home about the stove 23 | ask home is the water boiling 24 | ask home is keith there 25 | ask home to turn on the pool stereo 26 | ask home about the pool 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /patches/OAUTH-page-patch.groovy: -------------------------------------------------------------------------------- 1 | // Use inputs to attach smartthings devices to this app 2 | preferences { 3 | page(name: "connectDevPage") 4 | } 5 | 6 | def connectDevPage() { 7 | dynamicPage(name: "connectDevPage", title:"Connect Devices", install: true uninstall: true ) { 8 | section(title: "Select Devices") { 9 | input "brlight", "capability.switch", title: "Select the Bedroom Light", required: true, multiple:false 10 | // ALL YOUR INPUTS go here 11 | 12 | } 13 | if (!state.tok) { try { state.tok = createAccessToken()} catch (error) { state.tok = null } } 14 | section(title: "Show the OAUTH ID/Token Pair") { 15 | paragraph " var STappID = '${app.id}';\n var STtoken = '${state.tok}';\n" 16 | } 17 | section([mobileOnly:true]) { 18 | label title: "Assign a name", required: false 19 | } 20 | } 21 | } 22 | 23 | 24 | -------------------------------------------------------------------------------- /Lambda-node.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | exports.handler = function( event, context ) { 3 | var https = require( 'https' ); 4 | 5 | var STappID = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'; // AppID from Apps Editor 6 | var STtoken = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'; //Token from Apps Editor 7 | //var STappID = '6f89555e-76f7-4264-b349-ffe05fe3ae39' // ID for Auto OAUTH 8 | //if (event.session.user.accessToken) {STtoken = event.session.user.accessToken; } 9 | 10 | if (event.request.intent.name == "Home") { 11 | var Operator = event.request.intent.slots.Operator.value; 12 | var Noun = event.request.intent.slots.Noun.value; 13 | var Operand = event.request.intent.slots.Operand.value; 14 | var Inquisitor = event.request.intent.slots.Inquisitor.value; 15 | if (!Operator) {Operator = "none";} 16 | if (!Noun) {Noun = "none";} 17 | if (!Operand) {Operand = "none";} 18 | if (!Inquisitor) {Inquisitor = "none";} 19 | var url = 'https://graph.api.smartthings.com/api/smartapps/installations/' + STappID + '/' + 20 | Noun + '/' + Operator + '/'+ Operand + '/' + Inquisitor +'?access_token=' + STtoken; 21 | console.log(url) 22 | https.get( url, function( response ) { 23 | response.on( 'data', function( data ) { 24 | var resJSON = JSON.parse(data); 25 | var speechText = 'The App on SmartThings did not return any message.' 26 | if (resJSON.talk2me) { speechText = resJSON.talk2me; } 27 | console.log(speechText); 28 | output(speechText, context); 29 | console.log("after the fact"); 30 | } ); 31 | } ); 32 | } else { 33 | output("Another intent goes here.", context) 34 | } 35 | }; 36 | 37 | 38 | function output( text, context ) { 39 | var response = { 40 | outputSpeech: { 41 | type: "PlainText", 42 | text: text 43 | }, 44 | card: { 45 | type: "Simple", 46 | title: "System Data", 47 | content: text 48 | }, 49 | shouldEndSession: true 50 | }; 51 | context.succeed( { response: response } ); 52 | } 53 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AskHome 2 | 3 | A programming platform that uses Amazon Alexa to work with SmartThings. https://youtu.be/OzNKj_LX0-Y 4 | 5 | It is unique in that it is Person, Place, Thing (noun) centric, not device centric. 6 | This SmartThings smartApp is for groovy programmers....all the logic is in the program 7 | not in "fill in the blanks". This gives tremendous freedom for those that know code 8 | to generate complex and intuitive interactions. 9 | 10 | For those that do not enjoy coding, AskHome is not for you. MichaelS is developing an 11 | called AskAlexa that lets end users do more with Alexa. (I'll add the link when Michael is ready)! 12 | Go get that App! 13 | 14 | The installation requires an Amazon Developer Account. You need to create an Alexa skill 15 | and a Lambda Function. I've included screen shots that will help you configure the Alexa Skill, 16 | and I've provided the code to talk to smartthings for Lambda, you only need to fill in a smartthings 17 | App id and token to make it work. 18 | 19 | There are 3 steps for making AskHome.groovy your own. 20 | 21 | 1. Decide what nouns you want to interact with. 22 | 2. Add the devices that control or report information about the noun 23 | 3. call capability and noun subroutines based on keywords 24 | 25 | Nouns: Person, Place, Thing -- Like: YOU, Living Room, Front Door 26 | 27 | Devices: SmartThings! -- Like: A presence sensor to detect YOU; Lights, 28 | switches, thermometers that control and sense a place, or a lock and a contact 29 | sensor that control and report on a door. 30 | 31 | Alexa gives you words...A Noun, and Operator, and sometimes an operand. A switch/case structure lists 32 | the Nouns, and performs subroutine calls based on the Operator. An example like setting the color in lighting. 33 | So "ask home to light up the outside with green" could give you the words Outside, light up, green. 34 | Yea, "light up" -- you can make up your own operators too, just like the Nouns. You don't have to only use 35 | on, off, open, close, that are associated with the capabilities. 36 | 37 | So if you like Alexa, and you want to ask her do anything...you're only limited by your imagination now. 38 | So what will YOUR version of AskHome do? 39 | 40 | -------------------------------------------------------------------------------- /Alexa-Interaction-Model.txt: -------------------------------------------------------------------------------- 1 | **** Intents **** 2 | 3 | Intents (cut and paste between the lines) 4 | ---------------------------------------------------------------------- 5 | { 6 | "intents": [ 7 | { 8 | "intent": "Home", 9 | "slots": [ 10 | { 11 | "name": "Operator", 12 | "type": "LIST_OF_OPERATORS" 13 | }, 14 | { 15 | "name": "Noun", 16 | "type": "LIST_OF_NOUNS" 17 | }, 18 | { 19 | "name": "Operand", 20 | "type": "LIST_OF_OPERANDS" 21 | }, 22 | { 23 | "name": "Inquisitor", 24 | "type": "LIST_OF_INQUISITORS" 25 | } 26 | ] 27 | } 28 | ] 29 | } 30 | ---------------------------------------------------------------------- 31 | 32 | 33 | Sample Utterances (cut and paste between the lines) 34 | ---------------------------------------------------------------------- 35 | Home about the {Noun} 36 | Home if the {Noun} are {Operator} 37 | Home {Inquisitor} {Noun} are {Operator} 38 | Home {Inquisitor} {Operator} is the {Noun} 39 | Home {Inquisitor} the {Noun} {Operator} 40 | Home {Inquisitor} the {Noun} is {Operator} 41 | Home {Inquisitor} the {Noun} is turned {Operator} 42 | Home for the {Operator} of the {Noun} 43 | Home for the {Noun} {Operator} 44 | Home for the {Operator} of {Noun} 45 | Home to turn {Operator} the {Noun} 46 | Home to turn the {Noun} {Operator} 47 | Home {Noun} {Operator} 48 | Home to {Operator} {Noun} 49 | Home {Inquisitor} {Noun} {Operator} 50 | Home to turn {Operator} on the {Noun} 51 | Home to check the {Operator} of {Noun} 52 | Home to check the {Operator} of the {Noun} 53 | Home to {Operator} of the {Noun} to {Operand} 54 | Home to {Operator} the {Noun} 55 | Home to {Operator} the {Noun} {Operand} 56 | Home to tell me {Inquisitor} the {Noun} is {Operator} 57 | Home {Operator} the {Noun} 58 | ---------------------------------------------------------------------- 59 | 60 | 61 | **** Custom Slots **** 62 | 63 | LIST_OF_INQUISITORS 64 | ---------------------------------------------------------------------- 65 | is 66 | are 67 | who 68 | what 69 | when 70 | where 71 | why 72 | how 73 | which 74 | ---------------------------------------------------------------------- 75 | 76 | 77 | LIST_OF_NOUNS (really, YOU will make up your own list here, examples) 78 | ---------------------------------------------------------------------- 79 | Bedroom Light 80 | Medic Button 81 | Water Shutoff Valve 82 | Kitchen Air 83 | Basement Air 84 | Hallway Air 85 | Pool 86 | Pool Stereo 87 | Pool Gate 88 | East Lamp Post 89 | West Lamp Post 90 | Star Sensor 91 | Front Door 92 | Back Door 93 | Basement Motion 94 | Porch Lamp 95 | Basement 96 | Water 97 | Batteries 98 | Weather 99 | Cloths 100 | Wash 101 | Laundry 102 | Sparty 103 | Wolverine 104 | Stove 105 | Keith 106 | 107 | LIST_OF_OPERATORS (be sure to include your own new operators) 108 | ---------------------------------------------------------------------- 109 | status 110 | on 111 | off 112 | presence 113 | present 114 | not present 115 | open 116 | close 117 | closed 118 | battery 119 | smoke 120 | co 121 | color 122 | lock 123 | unlock 124 | locked 125 | unlocked 126 | temperature 127 | motion 128 | cold 129 | hot 130 | warm 131 | cool 132 | dead 133 | low 134 | poll 135 | update 136 | high 137 | done 138 | finished 139 | go 140 | 141 | LIST_OF_OPERANDS 142 | ---------------------------------------------------------------------- 143 | black 144 | white 145 | blue 146 | green 147 | yellow 148 | orange 149 | purple 150 | pink 151 | red 152 | 0 153 | 1 154 | 2 155 | 3 156 | 4 157 | 5 158 | 6 159 | 7 160 | 8 161 | 9 162 | 10 163 | 11 164 | 12 165 | 13 166 | 14 167 | 15 168 | 16 169 | 17 170 | 18 171 | 19 172 | 20 173 | 21 174 | 22 175 | 23 176 | 24 177 | 25 178 | 26 179 | 27 180 | 28 181 | 29 182 | 30 183 | 31 184 | 32 185 | 33 186 | 34 187 | 35 188 | 36 189 | 37 190 | 38 191 | 39 192 | 40 193 | 41 194 | 42 195 | 43 196 | 44 197 | 45 198 | 46 199 | 47 200 | 48 201 | 49 202 | 50 203 | 51 204 | 52 205 | 53 206 | 54 207 | 55 208 | 56 209 | 57 210 | 58 211 | 59 212 | 60 213 | 61 214 | 62 215 | 63 216 | 64 217 | 65 218 | 66 219 | 67 220 | 68 221 | 69 222 | 70 223 | 71 224 | 72 225 | 73 226 | 74 227 | 75 228 | 76 229 | 77 230 | 78 231 | 79 232 | 80 233 | 81 234 | 82 235 | 83 236 | 84 237 | 85 238 | 86 239 | 87 240 | 88 241 | 89 242 | 90 243 | 91 244 | 92 245 | 93 246 | 94 247 | 95 248 | 96 249 | 97 250 | 98 251 | 99 252 | 100 253 | ---------------------------------------------------------------------- 254 | 255 | 256 | -------------------------------------------------------------------------------- /AskHome-Bare.groovy: -------------------------------------------------------------------------------- 1 | /** 2 | * Alexa and Lambda - Perform an operation on a device as requested by Alexa 3 | * 4 | * Copyright 2016 Keith DeLong (n8xd) 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 | * 5/4/16 KHD (n8xd) added OAUTH token connect inside app 16 | * 4/29/16 KHD (n8xd) adjusted some response wording for motion 17 | * 4/26/16 KHD (n8xd) call it anything, control anything, devices, rooms, etc 18 | * 4/22/16 KHD (n8xd) recoded centralCommand with capability subroutines 19 | * 4/20/16 KHD (n8xd) test jig for processing device and op commands sent through Alexa/Lambda 20 | */ 21 | 22 | definition( 23 | name: "AskHome", 24 | namespace: "n8xd", 25 | author: "n8xd", 26 | description: "Do what Alexa tells us to do", 27 | category: "My Apps", 28 | iconUrl: "https://raw.githubusercontent.com/n8xd/AskHome/master/askhome108.png", 29 | iconX2Url: "https://raw.githubusercontent.com/n8xd/AskHome/master/askhome512.png") 30 | 31 | 32 | preferences { 33 | page(name: "connectDevPage") 34 | } 35 | 36 | // Use inputs to attach smartthings devices to this app 37 | def connectDevPage() { 38 | dynamicPage(name: "connectDevPage", title:"Connect Devices", install: true, uninstall: true ) { 39 | section(title: "Select Devices") { 40 | input "brlight", "capability.switch", title: "Select the Bedroom Light", required: true, multiple:false 41 | // Add your inputs here 42 | } 43 | if (!state.tok) { try { state.tok = createAccessToken() } catch (error) { state.tok = null } } 44 | section(title: "Show the OAUTH ID/Token Pair") { 45 | paragraph " var STappID = '${app.id}';\n var STtoken = '${state.tok}';\n" 46 | } 47 | section([mobileOnly:true]) { 48 | label title: "Assign a name", required: false 49 | } 50 | } 51 | } 52 | 53 | 54 | mappings { path("/:noun/:operator/:operand/:inquiz"){ action: [GET: "centralCommand"] } } 55 | 56 | def installed() {} 57 | def updated() {} 58 | 59 | ////////////////////////////////////////////////////////////////////////////////////////////////////////// 60 | // Central Command 61 | // 62 | // take a device and operation the user has spoken to Alexa, 63 | // perform the operation on the device and confirm with words. 64 | ////////////////////////////////////////////////////////////////////////////////////////////////////////// 65 | 66 | def centralCommand() { 67 | log.debug params 68 | 69 | def noun = params.noun 70 | def op = params.operator 71 | def opa = params.operand 72 | def inq = params.inquiz 73 | 74 | log.debug "Central Command ${noun} ${op} ${opa} ${inq}" 75 | 76 | state.talk2me = "" 77 | 78 | // adjust for the english language, if the user uses inquiry words, then switch things to status in select cases 79 | // ask home if something is on -- is really a status check, not command to change something 80 | // ask home how hot is the basement -- is really a temperature check 81 | // so if there is an inquisitor, then check the status instead of doing a command. 82 | 83 | if (op == "none") { op = "status" } //if there is no op, status request 84 | if (["done","finished"].contains(op)) { op = "status" } //with or without inquisitor these are status 85 | if (inq != "none") { //with an inquisitor these are status 86 | if (["on","off","open","closed","locked","unlocked","about","running"].contains(op)) { op = "status" } 87 | else if (["hot","cold","warm","cool"].contains(op)) { op = "temperature" } 88 | } 89 | 90 | // nouns - persons places and things, based on which noun you want to work with, and which operation 91 | // you want to perform...take some actions on smartthings devices and their capabilities. 92 | // make sure you put your nouns in the Alexa Developer part under LIST_OF_NOUNS, same for any new 93 | // operations you make up...remember you can say "pop" and "cap" for open and closed if you 94 | // put them in the LIST_OF_OPERATORS and list it in the "op" cases below. 95 | // you can also use on and off with open and close devices...turn the water off (instead of open the city water valve) 96 | // simply by including them in the op cases. Adjust the capability to recognize the word, or make a new capability 97 | // that does the same thing, with the alternate operators and call it, instead. 98 | 99 | 100 | switch (noun) { 101 | case "bedroom light" : switch(op) { // simple on and off 102 | case "on" : 103 | case "off" : 104 | case "status" : switchResponse(brlight,noun,op); break 105 | default : defaultResponseUnkOp(noun,op) 106 | } 107 | break 108 | 109 | // Add your case statements here 110 | 111 | case "none" : defaultResponseWhat() 112 | break 113 | 114 | default : defaultResponseUnkNoun(noun,op) 115 | } 116 | 117 | return ["talk2me" : state.talk2me] 118 | } 119 | 120 | 121 | 122 | ////////////////////////////////////////////////////////////////////////////////////////////////////////// 123 | //capability responses - DO and Report 124 | // 125 | ////////////////////////////////////////////////////////////////////////////////////////////////////////// 126 | 127 | def defaultResponseWhat() 128 | { 129 | state.talk2me = state.talk2me + "Ask me about something, or to do something with something. " 130 | } 131 | 132 | // defaultResponse Unknown Device 133 | def defaultResponseUnkNoun(noun, op) 134 | { 135 | state.talk2me = state.talk2me + "I can't find an person, place, or thing called ${noun} in the smart app. " 136 | } 137 | 138 | 139 | // defaultResponse Unknown Operator for device 140 | def defaultResponseUnkOp(noun, op) 141 | { 142 | state.talk2me = state.talk2me + "I haven't been told how to do ${op} with ${noun} yet. " 143 | } 144 | 145 | 146 | //capability.switch ["on", "off"] 147 | def switchResponse(handle, noun, op) 148 | { 149 | def arg = handle.currentValue("switch") //value before change 150 | if (op == "on") { handle.on(); arg = "turning " + op;} //switch flips slow in state, so tell them we did Op 151 | else if (op == "off") { handle.off(); arg = "turning " +op; } // ...or it will report what it was, not what we want 152 | else if (op == "status") { } // dont report Op, report the real currentState 153 | state.talk2me = state.talk2me + "The ${noun} is ${arg}. " // talk2me : switch is on (or off) 154 | } 155 | 156 | 157 | //capability.presenceSensor ["present","not present"] 158 | def presenceSensorResponse(handle, noun, op) 159 | { 160 | def arg = handle.currentValue("presence") // lookup the current presence status 161 | state.talk2me = state.talk2me + "The ${noun} is ${arg}. " // talk2me : sensor is present (or not present) 162 | } 163 | 164 | 165 | //capability.valve ["open","closed"] 166 | def valveResponse(handle, noun, op) 167 | { 168 | def arg = handle.currentValue("contact") //value before change 169 | if (op == "open") { handle.open(); arg = op + "ing";} //switch flips slow in state, so tell them we did Op 170 | else if (op == "close") { handle.close(); arg = op + "ing"; } // ...or it will report what it was, not what we want 171 | else if (op == "status") { } // dont report Op, report the real currentState 172 | state.talk2me = state.talk2me + "The ${noun} is ${arg}. " // talk2me : valve is open or closed 173 | } 174 | 175 | 176 | //capability.valve ["on","off"] // example to mix it up (copied valve, replaced open/close 177 | def valveResponseOnOff(handle, noun, op) // with on/off 178 | { 179 | def arg = handle.currentValue("contact") //value before change 180 | if (op == "on") { handle.open(); arg = "turning " + op;} //switch flips slow in state, so tell them we did Op 181 | else if (op == "off") { handle.close(); arg = "turning " + op; } // ...or it will report what it was, not what we want 182 | else if (op == "status") { // dont report Op, report the real currentState 183 | if (arg == "open") { arg = "on" } // fix up a status report so it uses on / off 184 | else if (arg == "closed") { arg = "off" } 185 | } 186 | state.talk2me = state.talk2me + "The ${noun} is ${arg}. " // talk2me : valve is open or closed 187 | } 188 | 189 | 190 | //capability.battery ["%"] 191 | def batteryResponse(handle, noun, op) 192 | { 193 | def arg = handle.currentValue("battery") // lookup the current battery status 194 | state.talk2me = state.talk2me + "The ${noun} battery is at ${arg} percent. " // talk2me : battery is at xx% percent 195 | } 196 | 197 | //capability.battery ALL 198 | def batteryResponseMulti(handle, noun, op) // handle All battery check differently 199 | { 200 | state.talk2me = state.talk2me + "These batteries are low: " 201 | handle.each { 202 | def arg = it.currentValue("battery") 203 | if (arg) { 204 | if (arg.toInteger() < 85) { 205 | state.talk2me = state.talk2me + "${it} at ${arg}%. " 206 | } 207 | } 208 | } 209 | } 210 | 211 | //capability.smokeDetector ["clear", "detected", "tested"] 212 | def smokeDetectorResponse(handle, noun, op) 213 | { 214 | def arg = handle.currentValue("smoke") // lookup the current smoke detector status 215 | if (!arg) {arg == "clear"} // default if unreported and value doesn't exist yet 216 | state.talk2me = state.talk2me + "The ${noun} smoke is ${arg}. " // talk2me : detector is clear, detected 217 | } 218 | 219 | 220 | //capability.carbonMonoxideDetector ["clear", "detected", "tested"] **FIX** Returns null for currentValue 221 | def CODetectorResponse(handle, noun, op) 222 | { 223 | def arg = handle.currentValue("carbonMonoxide") // lookup the CO status 224 | if (!arg) {arg = "clear"} // default if unreported and value doesn't exist yet 225 | state.talk2me = state.talk2me + "The ${noun} carbon monoxide is ${arg}. " // talk2me : detector is clear, detected 226 | } 227 | 228 | 229 | //capability.contactSensor ["open", "closed"] 230 | def contactSensorResponse(handle, noun, op) 231 | { 232 | def arg = handle.currentValue("contact") // lookup the current contact status 233 | state.talk2me = state.talk2me + "The ${noun} is ${arg}. " // talk2me : contact is open or closed 234 | } 235 | 236 | 237 | //capability.threeAxis 238 | def threeAxisResponse(handle, noun, op) 239 | { 240 | def arg = handle.currentValue("threeAxis") // lookup the current threeAxis status 241 | def pos = getOrientation(handle,arg) // enumerate the position (optional) 242 | state.talk2me = state.talk2me + "The ${noun} is in position number ${pos}. " 243 | state.talk2me = state.talk2me + "The ${noun} is oriented where X equals ${arg.x}, Y equals ${arg.y}, and z equals ${arg.z}. " 244 | } 245 | 246 | 247 | //capability.lock ["locked", "unlocked"] 248 | def lockResponse(handle, noun, op, opa) 249 | { 250 | def arg = handle.currentValue("lock") // lookup the current lock 251 | if (op == "lock") { handle.lock(); arg = op+"ed";} //switch flips slow in state, so tell them we did Op 252 | else if (op == "unlock") { 253 | if (opa == passwd) { handle.unlock(); arg = op+"ed"; } 254 | else if (opa == "none") // and passwd did not equal none 255 | { state.talk2me = state.talk2me + "Repeat the unlock command but include your unlock password at the end. " } 256 | else { state.talk2me = state.talk2me + "Your password did not match. "} 257 | } 258 | else if (op == "status") { } // dont report Op, report the real currentState 259 | state.talk2me = state.talk2me + "The ${noun} is ${arg}. " // talk2me : lock is open or closed 260 | } 261 | 262 | 263 | //capability.temperatureMeasurement ["degrees"] 264 | def temperatureMeasurementResponse(handle, noun, op) 265 | { 266 | def arg = handle.currentValue("temperature") 267 | state.talk2me = state.talk2me + "The ${noun} temperature is ${arg} degrees. " 268 | } 269 | 270 | 271 | //capability.motionSensor ["active", "inactive"] 272 | def motionSensorResponse(handle, noun, op) 273 | { 274 | def arg = handle.currentValue("motion") 275 | if (arg == "active") { arg = "motion" } 276 | else if ( arg == "inactive") { arg = "no motion" } 277 | 278 | state.talk2me = state.talk2me + "There is ${arg} in the ${noun}. " 279 | } 280 | 281 | def motionSensorBoilingWaterResponse(handle,noun,op) 282 | { 283 | def arg = handle.currentValue("motion") 284 | def arg2 = "" 285 | 286 | if (arg == "active") { arg = "motion"; arg2 = "boiling"; } 287 | else if ( arg == "inactive") { arg = "no motion"; arg2 = "not boiling"; } 288 | state.talk2me = state.talk2me + "Their is ${arg}. The water is ${arg2}." 289 | } 290 | 291 | 292 | //capability.waterSensor ["wet","dry"] // **FIX** returns null for currentValue 293 | def waterSensorResponse(handle, noun, op) 294 | { 295 | def arg = handle.currentValue("water") 296 | if (!arg) { arg = "dry" } // default if value has not been reported yet 297 | state.talk2me = state.talk2me + "The ${noun} is ${arg}. " 298 | } 299 | 300 | 301 | //capability.polling ["poll", "update"] 302 | def pollingResponse(handle, noun, op) 303 | { 304 | handle.poll() 305 | state.talk2me = state.talk2me + "The ${noun} has been ${op}d. " 306 | } 307 | 308 | 309 | //capability.colorControl 310 | def colorControlResponse(handle, noun, op, opa) 311 | { 312 | if (op == "go") { 313 | def hueval = colorWordtoHue(opa) 314 | def satval = 0 315 | 316 | if (opa == "white") {satval = 20} else {satval = 100} 317 | 318 | if (hue == -2) {state.talk2me = state.talk2me + "I don't know that color yet. " } 319 | else if (hue == -1) { handle.off()} 320 | else { 321 | def map = [switch: "on", hue: hueval, saturation: satval, level: level as Integer ?: 100] 322 | handle.setColor(map) 323 | handle.on() 324 | } 325 | } 326 | state.talk2me = state.talk2me + "${op} on ${noun} to ${opa}. " 327 | } 328 | 329 | ////////////////////////////////////////////////////////////////////////////////////////////////////////// 330 | // Noun Responses -- no direct capability action, but may use capabilities 331 | // 332 | ////////////////////////////////////////////////////////////////////////////////////////////////////////// 333 | 334 | def clothsNounResponse(handlew,handled) 335 | { 336 | def wnot = "not" 337 | def dnot = "not" 338 | if (handlew.currentValue("power") > 0) { wnot = "" } 339 | if (handled.currentValue("power") > 0) { dnot = "" } 340 | state.talk2me = state.talk2me + "The washer is ${wnot} running, and the dryer is ${dnot} running. " 341 | } 342 | 343 | 344 | def stoveNounResponse(handlestovetemp, handleroomtemp, handlestovemotion) 345 | { 346 | def stemp = handlestovetemp.currentValue("temperature") 347 | def rtemp = handleroomtemp.currentValue("temperature") 348 | def diftemp = stemp - rtemp 349 | def hc = "hotter" 350 | if (diftemp < 0) {hc = "cooler"; diftemp = diftemp.abs()} 351 | 352 | motionSensorResponse(handlestovemotion,"stove","status") 353 | state.talk2me = state.talk2me + "Above the stove is ${stemp} degrees, and that is ${diftemp} degrees ${hc} than the room temperature of ${rtemp} degrees." 354 | } 355 | 356 | def poolNounResponse(gatehandle,stereohandle) 357 | { 358 | contactSensorResponse(gatehandle,"pool gate","status") 359 | switchResponse(stereohandle,"pool stereo","status") 360 | state.talk2me = state.talk2me + "The pool scene lights are awating installation. " 361 | state.talk2me = state.talk2me + "The replacment pump and filter are awaiting installation. " 362 | state.talk2me = state.talk2me + "The special water proof Echo swim suit is on order from Amazon so I can join you in the pool. " 363 | } 364 | 365 | def keithNounResponse() 366 | { 367 | state.talk2me = state.talk2me + "Keith created this Alexa to Smart Things connection. With it, you can assign " 368 | state.talk2me = state.talk2me + "nouns and operations to control and report on Smart Things. This is different " 369 | state.talk2me = state.talk2me + "because it is not centered around devices, but centers around people, places, or things. " 370 | state.talk2me = state.talk2me + "Keith is not responsible if it enables Alexa's self destruct code by accident. " 371 | state.talk2me = state.talk2me + "This program is available under the Apache 2.0 license...so hack it to fit your needs. " 372 | state.talk2me = state.talk2me + "Copyright Keith DeLong 2016, all rights reserved." 373 | } 374 | 375 | ////////////////////////////////////////////////////////////////////////////////////////////////////////// 376 | // special routine to calculate color hue from a color word for capability.colorControl 377 | // 378 | ////////////////////////////////////////////////////////////////////////////////////////////////////////// 379 | 380 | private colorWordtoHue(colorWord) 381 | { 382 | def hueColor = -2; // -2 not found, -1 black, 0-100 hue 383 | switch (colorWord) { 384 | case "black" : huecolor = -1; break 385 | case "white" : hueColor = 52; break 386 | case "blue" : hueColor = 70; break 387 | case "green" : hueColor = 39; break 388 | case "yellow": hueColor = 17; break 389 | case "maize" : hueColor = 17; break 390 | case "orange": hueColor = 10; break 391 | case "purple": hueColor = 75; break 392 | case "Pink" : hueColor = 83; break 393 | case "Red" : hueColor = 100; break 394 | } 395 | return hueColor 396 | } 397 | 398 | 399 | 400 | ////////////////////////////////////////////////////////////////////////////////////////////////////////// 401 | // special routine to enumerate a 3axis position 402 | // 403 | ////////////////////////////////////////////////////////////////////////////////////////////////////////// 404 | 405 | 406 | private getOrientation(handle, xyz) { 407 | 408 | def value = handle.currentValue("threeAxis") 409 | log.debug value 410 | def orientation = 0 411 | 412 | // This is the coordinates for a 5 point star 413 | if (isNear(value.y, 0) && isNear(value.z,1000)) { orientation = 1 } 414 | else if (isNear(value.y, 1000) && isNear(value.z,350)) { orientation = 2 } 415 | else if (isNear(value.y, 600) && isNear(value.z,-820)) { orientation = 3 } 416 | else if (isNear(value.y, -580) && isNear(value.z,-850)) { orientation = 4 } 417 | else if (isNear(value.y, -1000) && isNear(value.z,300)) { orientation = 5 } 418 | else if (isNear(value.x, 1000) && isNear(value.y,0) && isNear(value.z,0)) { orientation = 6 } 419 | 420 | log.debug "${value.x}, ${value.y}, ${value.z} = orientation ${orientation}" 421 | orientation 422 | } 423 | 424 | private isNear(w, d) 425 | { 426 | def tol = 200 427 | return Math.abs((w - d)) < tol 428 | } 429 | 430 | -------------------------------------------------------------------------------- /AskHome.groovy: -------------------------------------------------------------------------------- 1 | /** 2 | * Alexa and Lambda - Perform an operation on a device as requested by Alexa 3 | * 4 | * Copyright 2016 Keith DeLong (n8xd) 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 | * 5/4/16 KHD (n8xd) added OAUTH token connect inside app 16 | * 4/29/16 KHD (n8xd) adjusted some response wording for motion 17 | * 4/26/16 KHD (n8xd) call it anything, control anything, devices, rooms, etc 18 | * 4/22/16 KHD (n8xd) recoded centralCommand with capability subroutines 19 | * 4/20/16 KHD (n8xd) test jig for processing device and op commands sent through Alexa/Lambda 20 | */ 21 | 22 | definition( 23 | name: "AskHome", 24 | namespace: "n8xd", 25 | author: "n8xd", 26 | description: "Do what Alexa tells us to do", 27 | category: "My Apps", 28 | iconUrl: "https://raw.githubusercontent.com/n8xd/AskHome/master/askhome108.png", 29 | iconX2Url: "https://raw.githubusercontent.com/n8xd/AskHome/master/askhome512.png") 30 | 31 | 32 | preferences { 33 | page(name: "connectDevPage") 34 | 35 | } 36 | 37 | // Use inputs to attach smartthings devices to this app 38 | def connectDevPage() { 39 | dynamicPage(name: "connectDevPage", title:"Connect Devices", input: true, uninstall: true ) { 40 | section(title: "Select Devices") { 41 | input "brlight", "capability.switch", title: "Select the Bedroom Light", required: true, multiple:false 42 | input "cbpres","capability.presenceSensor", title: "Select the Iris Care Medic Button presence", required: true, multiple:false 43 | input "valv","capability.valve",title: "Select the water valve", required: true, multiple: false 44 | input "cbbat","capability.battery",title: "Select the care button battery", required: true, multiple: false 45 | input "allbat","capability.battery",title: "Select ALL batteries", required: true, multiple: true 46 | input "kssmok","capability.smokeDetector", title: "Select the kitchen smoke detector",required:true, multiple: false 47 | input "ksbat","capability.battery",title: "Select the kitchen smoke detector battery",required:true, multiple: false 48 | input "ksco","capability.carbonMonoxideDetector", title: "Select the kitchen CO detector",required:true, multiple: false 49 | input "hasmok","capability.smokeDetector", title: "Select the hallway smoke detector",required:true, multiple: false 50 | input "habat","capability.battery",title: "Select the hallway smoke detector battery",required:true, multiple: false 51 | input "haco","capability.carbonMonoxideDetector", title: "Select the hallway CO detector",required:true, multiple: false 52 | input "basmok","capability.smokeDetector", title: "Select the basement smoke detector",required:true, multiple: false 53 | input "babat","capability.battery",title: "Select the basement smoke detector battery",required:true, multiple: false 54 | input "baco","capability.carbonMonoxideDetector", title: "Select the basement CO detector",required:true, multiple: false 55 | input "pgcont","capability.contactSensor", title: "Select the pool gate sensor",required:true, multiple: false 56 | input "pstereo","capability.switch", title: "Select the pool stereo system", required:true, multiple: false 57 | input "edlight","capability.colorControl", title: "Select the East driveway light", required:true, multiple: false 58 | input "wdlight","capability.colorControl", title: "Select the West driveway light", required:true, multiple: false 59 | input "thax","capability.threeAxis", title: "Select the star sensor", required: true, mulitple: false 60 | input "fdlocker","capability.lock", title: "Select the front door lock", required: true, multiple: false 61 | input "fdlockcon","capability.contactSensor", title: "Select the front door contact sensor", required: true, multiple: false 62 | input "fdpasswd","string",title: "Unlock password for front door (2 digits)", required: true, multiple: false 63 | input "porchlt","capability.switch",title: "Select the porch light",required:true, multiple: false 64 | input "porchmot","capability.motionSensor",title: "Select the porch motion sensor",required:true, multiple: false 65 | input "bdlocker","capability.lock", title: "Select the back door lock", required: true, multiple: false 66 | input "bdlockcon","capability.contactSensor", title: "Select the back door contact sensor", required: true, multiple: false 67 | input "bdpasswd","string",title: "Unlock password for back door (2 digits)", required: true, multiple: false 68 | input "bmot","capability.motionSensor", title: "Select the basement motion sensor", required: true, multiple: false 69 | input "btherm","capability.temperatureMeasurement", title: "Select the basement thermometer", required: true, multiple: false 70 | input "bawater","capability.waterSensor", title: "Select the basement water sensor", required: true, multiple: false 71 | input "washer","capability.powerMeter",title: "Select the washer", required:true, multiple: false 72 | input "dryer","capability.powerMeter",title: "Select the dryer", required:true, mulitple: false 73 | input "kittemp","capability.temperatureMeasurement",title: "Select the kitchen temperature", required:true, multiple: false 74 | input "stovtemp","capability.temperatureMeasurement",title: "Select the above stove temperature", required:true, multiple: false 75 | input "stovmot","capability.motionSensor",title: "Select the above stove motion sensor", required: true, multiple: false 76 | input "poller","capability.polling", title: "Select the weather app to poll", required: true, multiple: false 77 | } 78 | if (!state.tok) { try { state.tok = createAccessToken()} catch (error) {state.tok = null }} 79 | section(title: "Show the OAUTH ID/Token Pair") { 80 | paragraph " var STappID = '${app.id}';\n var STtoken = '${state.tok}';\n" 81 | } 82 | section([mobileOnly:true]) { 83 | label title: "Assign a name", required: false 84 | } 85 | } 86 | } 87 | 88 | 89 | mappings { path("/:noun/:operator/:operand/:inquiz"){ action: [GET: "centralCommand"] } } 90 | 91 | def installed() {} 92 | def updated() {} 93 | 94 | ////////////////////////////////////////////////////////////////////////////////////////////////////////// 95 | // Central Command 96 | // 97 | // take a device and operation the user has spoken to Alexa, 98 | // perform the operation on the device and confirm with words. 99 | ////////////////////////////////////////////////////////////////////////////////////////////////////////// 100 | 101 | def centralCommand() { 102 | log.debug params 103 | 104 | def noun = params.noun 105 | def op = params.operator 106 | def opa = params.operand 107 | def inq = params.inquiz 108 | 109 | log.debug "Central Command ${noun} ${op} ${opa} ${inq}" 110 | 111 | state.talk2me = "" 112 | 113 | // adjust for the english language, if the user uses inquiry words, then switch things to status in select cases 114 | // ask home if something is on -- is really a status check, not command to change something 115 | // ask home how hot is the basement -- is really a temperature check 116 | // so if there is an inquisitor, then check the status instead of doing a command. 117 | 118 | if (op == "none") { op = "status" } //if there is no op, status request 119 | if (["done","finished"].contains(op)) { op = "status" } //with or without inquisitor these are status 120 | if (inq != "none") { //with an inquisitor these are status 121 | if (["on","off","open","closed","locked","unlocked","about","running"].contains(op)) { op = "status" } 122 | else if (["hot","cold","warm","cool"].contains(op)) { op = "temperature" } 123 | } 124 | 125 | // nouns - persons places and things, based on which noun you want to work with, and which operation 126 | // you want to perform...take some actions on smartthings devices and their capabilities. 127 | // make sure you put your nouns in the Alexa Developer part under LIST_OF_NOUNS, same for any new 128 | // operations you make up...remember you can say "pop" and "cap" for open and closed if you 129 | // put them in the LIST_OF_OPERATORS and list it in the "op" cases below. 130 | // you can also use on and off with open and close devices...turn the water off (instead of open the city water valve) 131 | // simply by including them in the op cases. Adjust the capability to recognize the word, or make a new capability 132 | // that does the same thing, with the alternate operators and call it, instead. 133 | 134 | 135 | switch (noun) { 136 | case "bedroom light" : switch(op) { // simple on and off 137 | case "on" : 138 | case "off" : 139 | case "status" : switchResponse(brlight,noun,op); break 140 | default : defaultResponseUnkOp(noun,op) 141 | } 142 | break 143 | 144 | case "medic button" : switch(op) { // single device, mulitiple capabilities 145 | case "presence" : 146 | case "present" : presenceSensorResponse(cbpres,noun,op); break 147 | case "battery" : batteryResponse(cbbat,noun,op); break 148 | case "status" : presenceSensorResponse(cbpres,noun,op); //do both 149 | batteryResponse(cbbat,noun,op); break 150 | default : defaultResponseUnkOp(noun,op) 151 | } 152 | break 153 | 154 | case "weather" : switch(op) { // poll something, anything 155 | case "poll" : 156 | case "update" : pollingResponse(poller,noun,op); break 157 | default : defaultResponseUnkOp(noun,op) 158 | } 159 | break 160 | 161 | case "batteries" : switch(op) { // check the status of all batteries 162 | case "dead" : 163 | case "low" : batteryResponseMulti(allbat,noun,op); break 164 | default : defaultResponseUnkOp(noun,op) 165 | } 166 | break 167 | 168 | case "water shutoff valve" : switch(op) { // simple open and close 169 | case "open" : 170 | case "close" : 171 | case "status" : valveResponse(valv,noun,op); break 172 | default : defaultResponseUnkOp(noun,op) 173 | } 174 | break 175 | 176 | case "water" : switch (op) { // alternate, turn water on or off 177 | case "on" : 178 | case "off" : 179 | case "status" : valveResponseOnOff(valv,noun,op); break 180 | //totally unrelated to the valve, but uses water 181 | case "boiling" : motionSensorBoilingWaterResponse(stovmot,noun,op); break 182 | default : defaultResponsibilityUnkOp(noun,op) 183 | } 184 | break 185 | 186 | case "kitchen air" : switch(op) { // single device, mulitiple capabilities 187 | case "smoke" : smokeDetectorResponse(kssmok,noun,op); break 188 | case "co" : CODetectorResponse(ksco,noun,op); break 189 | case "battery" : batteryResponse(ksbat,noun,op); break 190 | case "status" : smokeDetectorResponse(kssmok,noun,op); //do all 3 191 | CODetectorResponse(ksco,noun,op); 192 | temperatureMeasurementResponse(stovtemp,noun,op); break 193 | default : defaultResponseUnkOp(noun,op) 194 | } 195 | break 196 | 197 | case "hallway air" : switch(op) { // single device, mulitiple capabilities 198 | case "smoke" : smokeDetectorResponse(hasmok,noun,op); break 199 | case "co" : CODetectorResponse(haco,noun,op); break 200 | case "battery" : batteryResponse(habat,noun,op); break 201 | case "status" : smokeDetectorResponse(hasmok,noun,op); //do all 3 202 | CODetectorResponse(haco,noun,op); 203 | batteryResponse(habat,noun,op); break 204 | default : defaultResponseUnkOp(noun,op) 205 | } 206 | break 207 | 208 | case "basement air" : switch(op) { // single device, mulitiple capabilities 209 | case "smoke" : smokeDetectorResponse(basmok,noun,op); break 210 | case "co" : CODetectorResponse(baco,noun,op); break 211 | case "battery" : batteryResponse(babat,noun,op); break 212 | case "status" : smokeDetectorResponse(basmok,noun,op); //do all 3 213 | CODetectorResponse(baco,noun,op); 214 | temperatureMeasurementResponse(btherm,noun,op); break 215 | default : defaultResponseUnkOp(noun,op) 216 | } 217 | break 218 | 219 | case "pool" : switch(op) { // check on all the pool features 220 | case "status" : poolNounResponse(pgcont,pstereo); break 221 | default : defaultResponseUnkOp(noun,op) 222 | } 223 | break 224 | 225 | case "pool gate" : switch(op) { // simple open or closed 226 | case "status" : contactSensorResponse(pgcont,noun,op); break 227 | default : defaultResponseUnkOp(noun,op) 228 | } 229 | break 230 | 231 | case "pool stereo" : switch(op) { // simple on and off 232 | case "on" : 233 | case "off" : 234 | case "status" : switchResponse(pstereo,noun,op); break 235 | default : defaultResponseUnkOp(noun,op) 236 | } 237 | break 238 | 239 | case "sparty" : switch (op) { // change the lights out front to Michigan State colors 240 | case "go" : colorControlResponse(edlight,noun,op,"white"); 241 | colorControlResponse(wdlight,noun,op,"green"); 242 | break 243 | default : defaultResponseUnkOp(noun, op) 244 | } 245 | break 246 | 247 | case "wolverine" : switch (op) { // change the lights out front to University of Michigan colors 248 | case "go" : colorControlResponse(wdlight,noun,op,"maize"); 249 | colorControlResponse(edlight,noun,op,"blue"); 250 | break 251 | default : defaultResponseUnkOp(noun, op) 252 | } 253 | break 254 | 255 | case "star" : switch(op) { // one xyz and one calculated enumeration 256 | case "status" : threeAxisResponse(thax,noun,op); break 257 | default : defaultResponseUnkOp(noun,op) 258 | } 259 | break 260 | 261 | case "front door" : switch (op) { // multiple devices - lock and contact 262 | case "lock" : 263 | case "unlock" : lockResponse(fdlocker,noun,op,opa); break 264 | case "status" : lockResponse(fdlocker,noun,op,opa); // do both 265 | contactSensorResponse(fdlockcon,noun,op); 266 | switchResponse(porchlt,"porch light",op); 267 | motionSensorResponse(porchmot,"porch",op); 268 | break 269 | default : defaultResponseUnkOp(noun,op) 270 | } 271 | break 272 | 273 | case "back door" : switch (op) { // multiple devices - lock and contact 274 | case "lock" : 275 | case "unlock" : lockResponse(bdlocker,noun,op,opa); break 276 | case "status" : lockResponse(bdlocker,noun,op,opa); // do both 277 | contactSensorResponse(bdlockcon,noun,op); break 278 | default : defaultResponseUnkOp(noun,op) 279 | } 280 | break 281 | 282 | case "basement" : switch (op) { // multiple capabilities motion, temp, smoke, etc 283 | case "temperature": temperatureMeasurementResponse(btherm,noun,op); break 284 | case "motion" : motionSensorResponse(bmot,noun,op); break 285 | case "status" : temperatureMeasurementResponse(btherm,noun,op); 286 | motionSensorResponse(bmot,noun,op); 287 | smokeDetectorResponse(basmok,noun,op); 288 | CODetectorResponse(baco,noun,op); 289 | waterSensorResponse(bawater,noun,op); 290 | break 291 | default : defaultResponseUnkOp(noun,op) 292 | } 293 | break 294 | 295 | case "cloths" : 296 | case "wash" : 297 | case "laundry" : switch (op) { // is my laundry done yet? 298 | case "status" : clothsNounResponse(washer,dryer); break 299 | default : defaultResponseUnkOp(noun,op) 300 | } 301 | break 302 | 303 | case "stove" : switch (op) { // did I leave the stove on? 304 | case "status" : stoveNounResponse(stovtemp,kittemp,stovmot); break 305 | default : defaultResponseUnkOp(noun,op) 306 | } 307 | break 308 | 309 | case "Keith" : switch (op) { 310 | case "status" : keithNounResponse(); break 311 | case "there" : 312 | case "presence" : 313 | case "present" : presenceSensorResponse(cbpres,noun,op); break 314 | default : defaultResponseUnkOp(noun,op) 315 | } 316 | break 317 | 318 | case "none" : defaultResponseWhat() 319 | break 320 | 321 | default : defaultResponseUnkNoun(noun,op) 322 | } 323 | 324 | return ["talk2me" : state.talk2me] 325 | } 326 | 327 | 328 | 329 | ////////////////////////////////////////////////////////////////////////////////////////////////////////// 330 | //capability responses - DO and Report 331 | // 332 | ////////////////////////////////////////////////////////////////////////////////////////////////////////// 333 | 334 | def defaultResponseWhat() 335 | { 336 | state.talk2me = state.talk2me + "Ask me about something, or to do something with something. " 337 | } 338 | 339 | // defaultResponse Unknown Device 340 | def defaultResponseUnkNoun(noun, op) 341 | { 342 | state.talk2me = state.talk2me + "I can't find an person, place, or thing called ${noun} in the smart app. " 343 | } 344 | 345 | 346 | // defaultResponse Unknown Operator for device 347 | def defaultResponseUnkOp(noun, op) 348 | { 349 | state.talk2me = state.talk2me + "I haven't been told how to do ${op} with ${noun} yet. " 350 | } 351 | 352 | 353 | //capability.switch ["on", "off"] 354 | def switchResponse(handle, noun, op) 355 | { 356 | def arg = handle.currentValue("switch") //value before change 357 | if (op == "on") { handle.on(); arg = "turning " + op;} //switch flips slow in state, so tell them we did Op 358 | else if (op == "off") { handle.off(); arg = "turning " +op; } // ...or it will report what it was, not what we want 359 | else if (op == "status") { } // dont report Op, report the real currentState 360 | state.talk2me = state.talk2me + "The ${noun} is ${arg}. " // talk2me : switch is on (or off) 361 | } 362 | 363 | 364 | //capability.presenceSensor ["present","not present"] 365 | def presenceSensorResponse(handle, noun, op) 366 | { 367 | def arg = handle.currentValue("presence") // lookup the current presence status 368 | state.talk2me = state.talk2me + "The ${noun} is ${arg}. " // talk2me : sensor is present (or not present) 369 | } 370 | 371 | 372 | //capability.valve ["open","closed"] 373 | def valveResponse(handle, noun, op) 374 | { 375 | def arg = handle.currentValue("contact") //value before change 376 | if (op == "open") { handle.open(); arg = op + "ing";} //switch flips slow in state, so tell them we did Op 377 | else if (op == "close") { handle.close(); arg = op + "ing"; } // ...or it will report what it was, not what we want 378 | else if (op == "status") { } // dont report Op, report the real currentState 379 | state.talk2me = state.talk2me + "The ${noun} is ${arg}. " // talk2me : valve is open or closed 380 | } 381 | 382 | 383 | //capability.valve ["on","off"] // example to mix it up (copied valve, replaced open/close 384 | def valveResponseOnOff(handle, noun, op) // with on/off 385 | { 386 | def arg = handle.currentValue("contact") //value before change 387 | if (op == "on") { handle.open(); arg = "turning " + op;} //switch flips slow in state, so tell them we did Op 388 | else if (op == "off") { handle.close(); arg = "turning " + op; } // ...or it will report what it was, not what we want 389 | else if (op == "status") { // dont report Op, report the real currentState 390 | if (arg == "open") { arg = "on" } // fix up a status report so it uses on / off 391 | else if (arg == "closed") { arg = "off" } 392 | } 393 | state.talk2me = state.talk2me + "The ${noun} is ${arg}. " // talk2me : valve is open or closed 394 | } 395 | 396 | 397 | //capability.battery ["%"] 398 | def batteryResponse(handle, noun, op) 399 | { 400 | def arg = handle.currentValue("battery") // lookup the current battery status 401 | state.talk2me = state.talk2me + "The ${noun} battery is at ${arg} percent. " // talk2me : battery is at xx% percent 402 | } 403 | 404 | //capability.battery ALL 405 | def batteryResponseMulti(handle, noun, op) // handle All battery check differently 406 | { 407 | state.talk2me = state.talk2me + "These batteries are low: " 408 | handle.each { 409 | def arg = it.currentValue("battery") 410 | if (arg) { 411 | if (arg.toInteger() < 85) { 412 | state.talk2me = state.talk2me + "${it} at ${arg}%. " 413 | } 414 | } 415 | } 416 | } 417 | 418 | //capability.smokeDetector ["clear", "detected", "tested"] 419 | def smokeDetectorResponse(handle, noun, op) 420 | { 421 | def arg = handle.currentValue("smoke") // lookup the current smoke detector status 422 | if (!arg) {arg == "clear"} // default if unreported and value doesn't exist yet 423 | state.talk2me = state.talk2me + "The ${noun} smoke is ${arg}. " // talk2me : detector is clear, detected 424 | } 425 | 426 | 427 | //capability.carbonMonoxideDetector ["clear", "detected", "tested"] **FIX** Returns null for currentValue 428 | def CODetectorResponse(handle, noun, op) 429 | { 430 | def arg = handle.currentValue("carbonMonoxide") // lookup the CO status 431 | if (!arg) {arg = "clear"} // default if unreported and value doesn't exist yet 432 | state.talk2me = state.talk2me + "The ${noun} carbon monoxide is ${arg}. " // talk2me : detector is clear, detected 433 | } 434 | 435 | 436 | //capability.contactSensor ["open", "closed"] 437 | def contactSensorResponse(handle, noun, op) 438 | { 439 | def arg = handle.currentValue("contact") // lookup the current contact status 440 | state.talk2me = state.talk2me + "The ${noun} is ${arg}. " // talk2me : contact is open or closed 441 | } 442 | 443 | 444 | //capability.threeAxis 445 | def threeAxisResponse(handle, noun, op) 446 | { 447 | def arg = handle.currentValue("threeAxis") // lookup the current threeAxis status 448 | def pos = getOrientation(handle,arg) // enumerate the position (optional) 449 | state.talk2me = state.talk2me + "The ${noun} is in position number ${pos}. " 450 | state.talk2me = state.talk2me + "The ${noun} is oriented where X equals ${arg.x}, Y equals ${arg.y}, and z equals ${arg.z}. " 451 | } 452 | 453 | 454 | //capability.lock ["locked", "unlocked"] 455 | def lockResponse(handle, noun, op, opa) 456 | { 457 | def arg = handle.currentValue("lock") // lookup the current lock 458 | if (op == "lock") { handle.lock(); arg = op+"ed";} //switch flips slow in state, so tell them we did Op 459 | else if (op == "unlock") { 460 | if (opa == passwd) { handle.unlock(); arg = op+"ed"; } 461 | else if (opa == "none") // and passwd did not equal none 462 | { state.talk2me = state.talk2me + "Repeat the unlock command but include your unlock password at the end. " } 463 | else { state.talk2me = state.talk2me + "Your password did not match. "} 464 | } 465 | else if (op == "status") { } // dont report Op, report the real currentState 466 | state.talk2me = state.talk2me + "The ${noun} is ${arg}. " // talk2me : lock is open or closed 467 | } 468 | 469 | 470 | //capability.temperatureMeasurement ["degrees"] 471 | def temperatureMeasurementResponse(handle, noun, op) 472 | { 473 | def arg = handle.currentValue("temperature") 474 | state.talk2me = state.talk2me + "The ${noun} temperature is ${arg} degrees. " 475 | } 476 | 477 | 478 | //capability.motionSensor ["active", "inactive"] 479 | def motionSensorResponse(handle, noun, op) 480 | { 481 | def arg = handle.currentValue("motion") 482 | if (arg == "active") { arg = "motion" } 483 | else if ( arg == "inactive") { arg = "no motion" } 484 | 485 | state.talk2me = state.talk2me + "There is ${arg} in the ${noun}. " 486 | } 487 | 488 | def motionSensorBoilingWaterResponse(handle,noun,op) 489 | { 490 | def arg = handle.currentValue("motion") 491 | def arg2 = "" 492 | 493 | if (arg == "active") { arg = "motion"; arg2 = "boiling"; } 494 | else if ( arg == "inactive") { arg = "no motion"; arg2 = "not boiling"; } 495 | state.talk2me = state.talk2me + "Their is ${arg}. The water is ${arg2}." 496 | } 497 | 498 | 499 | //capability.waterSensor ["wet","dry"] // **FIX** returns null for currentValue 500 | def waterSensorResponse(handle, noun, op) 501 | { 502 | def arg = handle.currentValue("water") 503 | if (!arg) { arg = "dry" } // default if value has not been reported yet 504 | state.talk2me = state.talk2me + "The ${noun} is ${arg}. " 505 | } 506 | 507 | 508 | //capability.polling ["poll", "update"] 509 | def pollingResponse(handle, noun, op) 510 | { 511 | handle.poll() 512 | state.talk2me = state.talk2me + "The ${noun} has been ${op}d. " 513 | } 514 | 515 | 516 | //capability.colorControl 517 | def colorControlResponse(handle, noun, op, opa) 518 | { 519 | if (op == "go") { 520 | def hueval = colorWordtoHue(opa) 521 | def satval = 0 522 | 523 | if (opa == "white") {satval = 20} else {satval = 100} 524 | 525 | if (hue == -2) {state.talk2me = state.talk2me + "I don't know that color yet. " } 526 | else if (hue == -1) { handle.off()} 527 | else { 528 | def map = [switch: "on", hue: hueval, saturation: satval, level: level as Integer ?: 100] 529 | handle.setColor(map) 530 | handle.on() 531 | } 532 | } 533 | state.talk2me = state.talk2me + "${op} on ${noun} to ${opa}. " 534 | } 535 | 536 | ////////////////////////////////////////////////////////////////////////////////////////////////////////// 537 | // Noun Responses -- no direct capability action, but may use capabilities 538 | // 539 | ////////////////////////////////////////////////////////////////////////////////////////////////////////// 540 | 541 | def clothsNounResponse(handlew,handled) 542 | { 543 | def wnot = "not" 544 | def dnot = "not" 545 | if (handlew.currentValue("power") > 0) { wnot = "" } 546 | if (handled.currentValue("power") > 0) { dnot = "" } 547 | state.talk2me = state.talk2me + "The washer is ${wnot} running, and the dryer is ${dnot} running. " 548 | } 549 | 550 | 551 | def stoveNounResponse(handlestovetemp, handleroomtemp, handlestovemotion) 552 | { 553 | def stemp = handlestovetemp.currentValue("temperature") 554 | def rtemp = handleroomtemp.currentValue("temperature") 555 | def diftemp = stemp - rtemp 556 | def hc = "hotter" 557 | if (diftemp < 0) {hc = "cooler"; diftemp = diftemp.abs()} 558 | 559 | motionSensorResponse(handlestovemotion,"stove","status") 560 | state.talk2me = state.talk2me + "Above the stove is ${stemp} degrees, and that is ${diftemp} degrees ${hc} than the room temperature of ${rtemp} degrees." 561 | } 562 | 563 | def poolNounResponse(gatehandle,stereohandle) 564 | { 565 | contactSensorResponse(gatehandle,"pool gate","status") 566 | switchResponse(stereohandle,"pool stereo","status") 567 | state.talk2me = state.talk2me + "The pool scene lights are awating installation. " 568 | state.talk2me = state.talk2me + "The replacment pump and filter are awaiting installation. " 569 | state.talk2me = state.talk2me + "The special water proof Echo swim suit is on order from Amazon so I can join you in the pool. " 570 | } 571 | 572 | def keithNounResponse() 573 | { 574 | state.talk2me = state.talk2me + "Keith created this Alexa to Smart Things connection. With it, you can assign " 575 | state.talk2me = state.talk2me + "nouns and operations to control and report on Smart Things. This is different " 576 | state.talk2me = state.talk2me + "because it is not centered around devices, but centers around people, places, or things. " 577 | state.talk2me = state.talk2me + "Keith is not responsible if it enables Alexa's self destruct code by accident. " 578 | state.talk2me = state.talk2me + "This program is available under the Apache 2.0 license...so hack it to fit your needs. " 579 | state.talk2me = state.talk2me + "Copyright Keith DeLong 2016, all rights reserved." 580 | } 581 | 582 | ////////////////////////////////////////////////////////////////////////////////////////////////////////// 583 | // special routine to calculate color hue from a color word for capability.colorControl 584 | // 585 | ////////////////////////////////////////////////////////////////////////////////////////////////////////// 586 | 587 | private colorWordtoHue(colorWord) 588 | { 589 | def hueColor = -2; // -2 not found, -1 black, 0-100 hue 590 | switch (colorWord) { 591 | case "black" : huecolor = -1; break 592 | case "white" : hueColor = 52; break 593 | case "blue" : hueColor = 70; break 594 | case "green" : hueColor = 39; break 595 | case "yellow": hueColor = 17; break 596 | case "maize" : hueColor = 17; break 597 | case "orange": hueColor = 10; break 598 | case "purple": hueColor = 75; break 599 | case "Pink" : hueColor = 83; break 600 | case "Red" : hueColor = 100; break 601 | } 602 | return hueColor 603 | } 604 | 605 | 606 | 607 | ////////////////////////////////////////////////////////////////////////////////////////////////////////// 608 | // special routine to enumerate a 3axis position 609 | // 610 | ////////////////////////////////////////////////////////////////////////////////////////////////////////// 611 | 612 | 613 | private getOrientation(handle, xyz) { 614 | 615 | def value = handle.currentValue("threeAxis") 616 | log.debug value 617 | def orientation = 0 618 | 619 | // This is the coordinates for a 5 point star 620 | if (isNear(value.y, 0) && isNear(value.z,1000)) { orientation = 1 } 621 | else if (isNear(value.y, 1000) && isNear(value.z,350)) { orientation = 2 } 622 | else if (isNear(value.y, 600) && isNear(value.z,-820)) { orientation = 3 } 623 | else if (isNear(value.y, -580) && isNear(value.z,-850)) { orientation = 4 } 624 | else if (isNear(value.y, -1000) && isNear(value.z,300)) { orientation = 5 } 625 | else if (isNear(value.x, 1000) && isNear(value.y,0) && isNear(value.z,0)) { orientation = 6 } 626 | 627 | log.debug "${value.x}, ${value.y}, ${value.z} = orientation ${orientation}" 628 | orientation 629 | } 630 | 631 | private isNear(w, d) 632 | { 633 | def tol = 200 634 | return Math.abs((w - d)) < tol 635 | } 636 | 637 | --------------------------------------------------------------------------------