Adding more official apps gotten from https://github.com/SmartThingsCommunity/Code
authorrtrimana <rtrimana@uci.edu>
Fri, 20 Jul 2018 18:23:39 +0000 (11:23 -0700)
committerrtrimana <rtrimana@uci.edu>
Fri, 20 Jul 2018 18:23:39 +0000 (11:23 -0700)
17 files changed:
official/01-control-lights-and-locks-with-contact-sensor.groovy [new file with mode: 0644]
official/02-control-devices-with-an-HTTP-request.groovy [new file with mode: 0644]
official/03-sms-to-hue.groovy [new file with mode: 0644]
official/04-weatherunderground-connect.groovy [new file with mode: 0644]
official/Contact_Activated_Lighting.groovy [new file with mode: 0644]
official/Light_Rule.groovy [new file with mode: 0644]
official/control-switch-with-contact-sensor.groovy [new file with mode: 0644]
official/grannys-faucet.groovy [new file with mode: 0644]
official/hue-minimote.groovy [new file with mode: 0644]
official/page-params-by-href.groovy [new file with mode: 0644]
official/restful-switch.groovy [new file with mode: 0644]
official/turn-on-at-sunset.groovy [new file with mode: 0644]
official/turn-on-before-sunset.groovy [new file with mode: 0644]
official/turn-on-by-zip-code.groovy [new file with mode: 0644]
official/tweet-to-hue.groovy [new file with mode: 0644]
official/web-services-smartapp.groovy [new file with mode: 0644]
official/wunderground-pws-connect.groovy [new file with mode: 0644]

diff --git a/official/01-control-lights-and-locks-with-contact-sensor.groovy b/official/01-control-lights-and-locks-with-contact-sensor.groovy
new file mode 100644 (file)
index 0000000..7099bcb
--- /dev/null
@@ -0,0 +1,69 @@
+/**
+ *  Example: Control a switch with a contact sensor
+ *
+ *  Copyright 2014 Andrew Mager
+ *
+ *  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.
+ *
+ */
+definition(
+    name: "Example: Control a switch and lock with a contact sensor",
+    namespace: "com.smarthings.developers",
+    author: "Andrew Mager & Kris Schaller",
+    description: "Using a contact sensor, control a switch and a lock.",
+    category: "My Apps",
+    iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
+    iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png")
+
+
+// This is where a user will select devices to be used by this SmartApp
+preferences {
+    // You can create multiple sections to organize the configuration fields of your SmartApp
+    section(title: "Select Devices") {
+        // Inputs assign variables to a group of physical devices
+        input "contact", "capability.contactSensor", title: "Select a contact sensor", multiple: false
+        input "light", "capability.switch", title: "Select a light or outlet", required: true
+    }
+}
+
+// This function runs when the SmartApp is installed
+def installed() {
+    // This is a standard debug statement in Groovy
+    log.debug "Installed with settings: ${settings}"
+    initialize()
+}
+
+// This function runs when the SmartApp has been updated
+def updated() {
+    log.debug "Updated with settings: ${settings}"
+    // Notice that all event subscriptions are removed when a SmartApp is updated
+    unsubscribe()
+    initialize()
+}
+
+// This function is where you initialize callbacks for event listeners
+def initialize() {
+    // The subscribe function takes a input, a state, and a callback method
+    subscribe(contact, "contact.open", openHandler)
+    subscribe(contact, "contact.closed", closedHandler)
+}
+
+// These are our callback methods
+def openHandler(evt) {
+    log.debug "$evt.name: $evt.value"
+    // Turn the light on
+    light.on()
+}
+
+def closedHandler(evt) {
+    log.debug "$evt.name: $evt.value"
+    // Turn the light off and lock the lock
+    light.off()
+}
diff --git a/official/02-control-devices-with-an-HTTP-request.groovy b/official/02-control-devices-with-an-HTTP-request.groovy
new file mode 100644 (file)
index 0000000..4968621
--- /dev/null
@@ -0,0 +1,69 @@
+/**
+ *  Example: Control a switch with a contact sensor
+ *
+ *  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.
+ *
+ */
+definition(
+    name: "Example: Control a device with an API call",
+    namespace: "com.smarthings.developers",
+    author: "Andrew Mager & Kris Schaller",
+    description: "Make an HTTP request to a SmartApp to control devices.",
+    category: "My Apps",
+    iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
+    iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png")
+
+
+preferences {
+    section(title: "Select Devices") {
+        input "light", "capability.switch", title: "Select a light or outlet", required: true, multiple:false
+    }
+}
+
+// Since the SmartApp doesn't have any dependencies when it's installed or updated,
+// we don't need to worry about those states.
+def installed() {}
+def updated() {}
+
+
+// This block defines an endpoint, and which functions will fire depending on which type
+// of HTTP request you send
+mappings {
+    // The path is appended to the endpoint to make requests
+    path("/switch") {
+        // These actions link HTTP verbs to specific callback functions in your SmartApp
+        action: [
+            GET: "getSwitch", // "When an HTTP GET request is received, run getSwitch()"
+            PUT: "setSwitch"
+        ]
+    }
+}
+
+
+// Callback functions
+def getSwitch() {
+    // This returns the current state of the switch in JSON
+    return light.currentState("switch")
+}
+
+def setSwitch() {
+    switch(request.JSON.value) {
+        case "on":
+            light.on();
+            break;
+        case "off":
+            light.off();
+            break;
+        default:
+            break;
+    }
+}
diff --git a/official/03-sms-to-hue.groovy b/official/03-sms-to-hue.groovy
new file mode 100644 (file)
index 0000000..f244b9c
--- /dev/null
@@ -0,0 +1,115 @@
+/**
+ *  [Workshop Demo] SMS to Hue
+ *
+ *  Copyright 2015 Andrew Mager
+ *
+ *  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.
+ *
+ */
+definition(
+    name: "[Workshop Demo] SMS to Hue",
+    namespace: "com.smartthings.dev",
+    author: "Andrew Mager",
+    description: "Change the color of Hue bulbs from an SMS.",
+    category: "My Apps",
+    iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
+    iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
+    iconX3Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
+    oauth: true)
+
+
+preferences {
+  section("Control these hue bulbs...") {
+    input "hues", "capability.colorControl", title: "Which Hue Bulbs?", required:false, multiple:true
+  }
+}
+// This block defines an endpoint, and which functions will fire depending on which type
+// of HTTP request you send
+mappings {
+    // The path is appended to the endpoint to make request
+    path("/hue") {
+        action: [
+            PUT: "postHue" 
+        ]
+    }
+}
+
+
+def installed() {}
+def updated() {}
+
+
+
+/*
+  This function receives a JSON payload and parses out the color from a tweet. 
+  For example, someone tweets, "@SmartThingsDev #IoTWorld2015 color=blue". Then it sends the 
+  correct color as a string to setHueColor().
+*/
+def postHue() {
+    /*
+      "request.JSON?" checks to make sure that the object exists. And ".text" is the
+      key for the value that we're looking for. It's the body of the tweet.
+    */
+    def color = (request.JSON?.value).toLowerCase()
+    
+    try {
+        // Finds the text "color=[colorname]" and parses out the color name
+        setHueColor(color)     
+    }
+    catch (any) {
+        log.trace "Something went wrong."
+    }    
+}
+
+
+// This function takes a String of text and associates it with an Integer value for the color.
+private setHueColor(color) {
+
+    // Initaliaze values for hue, saturation, and level
+    def hueColor = 0
+    def saturation = 100
+    def level = 100
+
+    switch(color) {
+        case "white":
+            hueColor = 52
+            saturation = 19
+            break;
+        case "blue":
+            hueColor = 70
+            break;
+        case "green":
+            hueColor = 39
+            break;
+        case "yellow":
+            hueColor = 25
+            break;
+        case "orange":
+            hueColor = 10
+            break;
+        case "purple":
+            hueColor = 75
+            break;
+        case "pink":
+            hueColor = 83
+            break;
+        case "red":
+            hueColor = 100
+            break;
+    }
+
+    // Set the new value of hue, saturation, and level
+    def newValue = [hue: hueColor, saturation: saturation, level: level]
+
+    // Update each Hue bulb with the new values
+    hues*.setColor(newValue)
+}
diff --git a/official/04-weatherunderground-connect.groovy b/official/04-weatherunderground-connect.groovy
new file mode 100644 (file)
index 0000000..f927708
--- /dev/null
@@ -0,0 +1,112 @@
+/**
+ *  Weather Underground PWS Connect
+ *
+ *  Copyright 2015 Andrew Mager
+ *
+ *  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.
+ *
+ */
+
+// This imports the Java class "DecimalFormat"
+import java.text.DecimalFormat
+definition(
+    name: "Weather Underground PWS Connect",
+    namespace: "co.mager",
+    author: "Andrew Mager",
+    description: "Connect your SmartSense Temp/Humidity sensor to your Weather Underground Personal Weather Station.",
+    category: "Green Living",
+    iconUrl: "http://i.imgur.com/HU0ANBp.png",
+    iconX2Url: "http://i.imgur.com/HU0ANBp.png",
+    iconX3Url: "http://i.imgur.com/HU0ANBp.png",
+    oauth: true)
+
+
+preferences {
+    section("Select a sensor") {
+        input "temp", "capability.temperatureMeasurement", title: "Temperature", required: true
+        input "humidity", "capability.relativeHumidityMeasurement", title: "Humidity", required: true
+    }
+    section("Configure your Weather Underground credentials") {
+        input "weatherID", "text", title: "Weather Station ID", required: true
+        input "password", "password", title: "Weather Underground password", required: true
+    }
+}
+
+def installed() {
+    log.debug "Installed with settings: ${settings}"
+    initialize()
+}
+
+
+def updated() {
+    log.debug "Updated with settings: ${settings}"
+    unsubscribe()
+    initialize()
+}
+
+
+def initialize() {
+
+    /*
+      Check to see if the sensor is reporting temperature, then run the updateCurrentWeather
+      every 10 minutes
+    */
+    if (temp.currentTemperature) {
+        runEvery5Minutes(updateCurrentWeather)
+    }
+}
+
+
+/*
+  Updates the Weather Underground Personal Weather Station (PWS) Upload Protocol
+  Reference: http://wiki.wunderground.com/index.php/PWS_-_Upload_Protocol
+*/
+def updateCurrentWeather() {
+
+    // Logs of the current data from the sensor
+    log.trace "Temp: " + temp.currentTemperature
+    log.trace "Humidity: " + humidity.currentHumidity
+    log.trace "Dew Point: " + calculateDewPoint(temp.currentTemperature, humidity.currentHumidity)
+
+    // Builds the URL that will be sent to Weather Underground to update your PWS
+    def params = [
+        uri: "http://weatherstation.wunderground.com",
+        path: "/weatherstation/updateweatherstation.php",
+        query: [
+            "ID": weatherID,
+            "PASSWORD": password,
+            "dateutc": "now",
+            "tempf": temp.currentTemperature,
+            "humidity": humidity.currentHumidity,
+            "dewptf": calculateDewPoint(temp.currentTemperature, humidity.currentHumidity),
+            "action": "updateraw",
+            "softwaretype": "SmartThings"
+        ]
+    ]
+    
+    try {
+        // Make the HTTP request using httpGet()
+        httpGet(params) { resp -> // This is how we define the "return data". Can also use $it.
+            log.debug "response data: ${resp.data}"
+        }
+    } catch (e) {
+        log.error "something went wrong: $e"
+    }
+
+}
+
+// Calculates dewpoint based on temperature and humidity
+def calculateDewPoint(t, rh) {
+    def dp = 243.04 * ( Math.log(rh / 100) + ( (17.625 * t) / (243.04 + t) ) ) / (17.625 - Math.log(rh / 100) - ( (17.625 * t) / (243.04 + t) ) ) 
+    // Format the response for Weather Underground
+    return new DecimalFormat("##.##").format(dp)
+}
+
diff --git a/official/Contact_Activated_Lighting.groovy b/official/Contact_Activated_Lighting.groovy
new file mode 100644 (file)
index 0000000..5e210ee
--- /dev/null
@@ -0,0 +1,50 @@
+/**
+ *  Contact Activated Lighting
+ *
+ *  Copyright 2016 Tim Slagle
+ *
+ *  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.
+ *
+ */
+definition(
+    name: "Contact Activated Lighting",
+    namespace: "tslagle13",
+    author: "Tim Slagle",
+    description: "Create child apps that will use a contact sensor to turn on lights.",
+    category: "Convenience",
+    iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
+    iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
+    iconX3Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png")
+
+
+preferences {
+    page(name: "mainPage", title: "Child Apps", install: true, uninstall: true) {
+        section {
+            app(name: "childRule", appName: "Light Rule", namespace: "tslagle13", title: "New Lighting Rule", multiple: true)
+        }
+    }
+}
+
+def installed() {
+       log.debug "Installed with settings: ${settings}"
+
+       initialize()
+}
+
+def updated() {
+       log.debug "Updated with settings: ${settings}"
+
+       unsubscribe()
+       initialize()
+}
+
+def initialize() {
+       // TODO: subscribe to attributes, devices, locations, etc.
+}
\ No newline at end of file
diff --git a/official/Light_Rule.groovy b/official/Light_Rule.groovy
new file mode 100644 (file)
index 0000000..d9c090f
--- /dev/null
@@ -0,0 +1,110 @@
+/**
+ *  Light Rule
+ *
+ *  Copyright 2016 Tim Slagle
+ *
+ *  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.
+ *
+ */
+definition(
+    name: "Light Rule",
+    namespace: "tslagle13",
+    author: "Tim Slagle",
+    description: "Light rule child app for \"Motion Activated Light\"",
+    category: "Convenience",
+    parent: "tslagle13:Contact Activated Lighting",
+    iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
+    iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
+    iconX3Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png")
+
+
+preferences {
+       page name: "mainPage", title: "Automate Lights & Switches", install: false, uninstall: true, nextPage: "namePage"
+    page name: "namePage", title: "Automate Lights & Switches", install: true, uninstall: true
+}
+
+def mainPage() {
+       dynamicPage(name: "mainPage") {
+        section("Which contact sensor?") {
+            input "contactSensor", "capability.contactSensor", title: "Which contact sensor(s)?", multiple: true
+        }
+        section("Which light would you like to turn on?") {
+            input "light", "capability.switch", title: "Which light?", multiple: true
+        }
+        section("Extras") {
+            input "contactInactive", "bool", title: "Turn off if contacts closes?", submitOnChange: true
+            if (contactInactive) {
+               input "delay", "number", title: "After how many seconds?", required: false
+            }
+        }
+    }
+}
+
+def namePage() {
+       if (!overrideLabel) {
+        // if the user selects to not change the label, give a default label
+        def l = "$contactSensor turns on $light"
+        log.debug "will set default label of $l"
+        app.updateLabel(l)
+    }
+    dynamicPage(name: "namePage") {
+        if (overrideLabel) {
+            section("Automation name") {
+                label title: "Enter custom name", defaultValue: app.label, required: false
+            }
+        } else {
+            section("Automation name") {
+                paragraph app.label
+            }
+        }
+        section {
+            input "overrideLabel", "bool", title: "Edit automation name", defaultValue: "false", required: "false", submitOnChange: true
+        }
+    }
+}
+
+def installed() {
+       log.debug "Installed with settings: ${settings}"
+
+       initialize()
+}
+
+def updated() {
+       log.debug "Updated with settings: ${settings}"
+
+       unsubscribe()
+       initialize()
+}
+
+def initialize() {
+       subscribe (contactSensor, "contact" , contactHandler)
+}
+
+def contactHandler(evt){
+       log.debug evt.name
+    log.debug evt.value
+    log.debug evt.date
+    log.debug evt.isStateChange()
+    
+    if (evt.value == "open") {
+       turnLightOn()
+    }
+    else if (contactInactive) {
+       runIn(delay , turnLightOff)
+    }
+}
+
+def turnLightOn() {
+       light.on()
+}
+
+def turnLightOff() {
+       light.off()
+}
diff --git a/official/control-switch-with-contact-sensor.groovy b/official/control-switch-with-contact-sensor.groovy
new file mode 100644 (file)
index 0000000..e81d1a6
--- /dev/null
@@ -0,0 +1,59 @@
+/**
+ *  Example: Control a switch with a contact sensor
+ *
+ *  Copyright 2015 Andrew Mager
+ *
+ *  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.
+ *
+ */
+definition(
+    name: "Example: Control a switch with a contact sensor",
+    namespace: "com.smarthings.dev",
+    author: "Andrew Mager",
+    description: "Using a contact sensor, control a switch.",
+    category: "My Apps",
+    iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
+    iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png")
+
+
+preferences {
+    section("Select Devices") {
+        input "contact", "capability.contactSensor", title: "Select a contact sensor", multiple: false
+        input "light", "capability.switch", title: "Select a light or outlet"
+    }
+}
+
+def installed() {
+    log.debug "Installed with settings: ${settings}"
+    initialize()
+}
+
+def updated() {
+    log.debug "Updated with settings: ${settings}"
+    unsubscribe()
+    initialize()
+}
+
+def initialize() {
+    subscribe contact, "contact.open", openHandler
+    subscribe contact, "contact.closed", closedHandler
+}
+
+def openHandler(evt) {
+    log.debug "$evt.name: $evt.value"
+    light.on()
+}
+
+def closedHandler(evt) {
+    log.debug "$evt.name: $evt.value"
+    light.off()
+}
+
+// TODO: implement event handlers
diff --git a/official/grannys-faucet.groovy b/official/grannys-faucet.groovy
new file mode 100644 (file)
index 0000000..7307da9
--- /dev/null
@@ -0,0 +1,100 @@
+/**
+ *  Granny's Faucet
+ *  Notifies you when Granny is up and running
+ *  Let you know when she hasn't been around so you can check in on her
+ *
+ *  Copyright 2015 SmartThings Hack
+ *
+ *  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.
+ *
+ */
+definition(
+    name: "Granny's Faucet",
+    namespace: "com.firstbuild",
+    author: "SmartThings Hack",
+    description: "Check to see if Granny used the faucet in a 24 hour period and send a notification if she does.",
+    category: "My Apps",
+    iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
+    iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
+    iconX3Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png")
+
+
+preferences {
+    section("Which Faucets?") {
+        input "faucet", "capability.accelerationSensor", title: "Which faucet?", required:true
+     }
+     section("Who to text") {
+        input "phone", "phone", title: "Phone number?", required: true  
+     }
+     section("How often do you want grandma to check in?") {
+        input "minutes", "number", title: "Delay in minutes before we notify you", defaultValue: 1
+     }
+}
+
+def installed() {
+    log.trace "Installed with settings: ${settings}"
+    initialize()
+}
+
+def updated() {
+    log.trace "Updated with settings: ${settings}"
+    state.lastOpened= [date:now()] // now() is ms time built into SmartApps
+     log.debug "Last Updated Date: $state.lastOpened.date"
+
+    unsubscribe()
+    initialize()
+}
+
+def initialize() {
+     subscribe(faucet, "acceleration.active", faucetActiveHandler)
+     subscribe(faucet, "acceleration.inactive", faucetInactiveHandler)
+}
+
+def faucetInactiveHandler(evt) {
+    log.trace "#faucetClosedHandler#"
+    def inputSeconds = 60*minutes.toInteger()
+    log.debug "waiting...$inputSeconds"
+    runIn(inputSeconds, alertMe)
+}
+
+def faucetActiveHandler(evt) {
+    // Don't send a continuous stream of text messages
+     def inputSeconds = 60*minutes.toInteger()
+    def deltaSeconds = inputSeconds
+    def timeAgo = new Date(now() - (1000 * deltaSeconds)) // 61 seconds ago
+    def recentEvents = faucet.eventsSince(timeAgo)
+    log.trace "Found ${recentEvents?.size() ?: 0} events in the last $deltaSeconds seconds"
+     log.debug "Recent Events $recentEvents.value"
+    def alreadySentSms = recentEvents.count { 
+        it.value && it.value == "active"
+        } > 1
+    
+    if (alreadySentSms) {
+        log.debug "SMS already sent to $phone1 within the last $minutes minute"
+    } else {
+        //  
+        sendSms(phone, "Grandma opened faucet")
+        state.lastOpened.date = now()
+        log.debug "Grandma Opened Faucet: $state.lastOpened"
+
+    }   
+}
+
+def alertMe() {
+    log.trace "#alerting...#"
+     def targetTime = state.lastOpened.date + minutes.toInteger()*60*1000
+     log.debug "#alertMe: last: ${state.lastOpened.date} , now: ${now()}, targetTime: ${targetTime}} "
+    if ( now() > targetTime ){
+            log.debug "Grandma needs water badly"
+            sendSms(phone, "Grandma needs water badly")     
+    } else {
+        log.debug "Grandma's aight!"
+    }
+}
\ No newline at end of file
diff --git a/official/hue-minimote.groovy b/official/hue-minimote.groovy
new file mode 100644 (file)
index 0000000..5c46779
--- /dev/null
@@ -0,0 +1,153 @@
+/**
+ *  My Living Room Lighting
+ *
+ *  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.
+ *
+ */
+ definition(
+    name: "My Living Room Lighting",
+    namespace: "smartthings",
+    author: "smartthings",
+    description: "allow a button controller to control my hue lamps, tv lights, and floor lamp",
+    category: "My Apps",
+    iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
+    iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
+    iconX3Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png")
+
+
+ preferences {
+    section("Control which hue lamps?") {
+        input "hueLamps", "capability.colorControl", required: true, multiple: true
+    }
+    section("Control which TV lights?") {
+        input "tvLights", "capability.colorControl", required: false
+    }
+    section("Control other (non-hue) lamps?") {
+        input "otherLights", "capability.switch", required: false, multiple: true
+    }
+    section("Which button controller?") {
+        input "buttonDevice", "capability.button", required: true
+    }
+}
+
+def installed() {
+    log.debug "Installed with settings: ${settings}"
+    initialize()
+}
+
+def updated() {
+    log.debug "Updated with settings: ${settings}"
+    unsubscribe()
+    initialize()
+}
+
+def initialize() {
+    subscribe(buttonDevice, "button", buttonEvent)
+    hueLamps.each {
+        log.debug "lamp ${it.displayName} hue: ${it.currentHue}"
+        log.debug "lamp ${it.displayName} saturation: ${it.currentSaturation}"
+        log.debug "lamp ${it.displayName} color ${it.currentColor}"
+        log.debug "lamp ${it.displayName} level ${it.currentLevel}"
+    }
+
+    log.debug "tv hue: ${tvLights?.currentHue}"
+    log.debug "tv saturation: ${tvLights?.currentSaturation}"
+    log.debug "tv color ${tvLights?.currentColor}"
+    log.debug "tv level ${tvLights?.currentLevel}"
+}
+
+def buttonEvent(evt) {
+    def buttonState = evt.value // "pushed" or "held"
+    def buttonNumber = parseJson(evt.data)?.buttonNumber 
+    
+    log.debug "buttonState:  $buttonState"
+    log.debug "buttonNumber: $buttonNumber"
+    
+    if (!(1..4).contains(buttonNumber)) {
+        log.error "This app only supports four buttons. Invalid buttonNumber: $buttonNumber"
+        } else if (!(buttonState == "pushed" || buttonState == "held")) {
+            log.error "This app only supports button pushed and held values. Invalid button state: $buttonState"
+            } else { 
+                def meth = "handleButton" + buttonNumber + buttonState.capitalize()
+                log.debug "meth: $meth"
+                "$meth"()
+            }
+        }
+
+// normal "white" lighting
+def handleButton1Pushed() {
+    log.debug "handle1Pushed"
+    
+    hueLamps.setColor(level: 100, hue: 20, saturation: 80)
+    
+    // notice the "?." operator - tvLights may not be set (required: false).
+    tvLights?.setColor(level: 100, hue: 100, saturation: 100)
+    otherLights?.on()
+}
+
+// turn everything off
+def handleButton1Held() {
+    log.debug "handleButton1Held"
+    
+    hueLamps.off()
+    tvLights?.off()
+    otherLights?.off()
+}
+
+// soft, dim white light
+def handleButton2Pushed() {
+    log.debug "handleButton2Pushed"
+
+    hueLamps.setColor(level: 50, hue: 20, saturation: 80)
+    tvLights.setColor(level: 30, hue: 70, saturation: 70)
+    otherLights?.on()
+}
+
+// set to what you want!
+def handleButton2Held() {
+    log.debug "handleButton2Held"
+}
+
+// dim red light
+def handleButton3Pushed() {
+    hueLamps.setColor(level: 40, hue: 100, saturation: 100)
+    tvLights?.setColor(level: 30, hue: 100, saturation: 100)
+    otherLights?.off()
+}
+
+// set to what you want!
+def handleButton3Held() {
+    log.debug "handleButton3Held"
+}
+
+// dim blue light 
+def handleButton4Pushed() {
+    log.debug "handleButton4Pushed"
+    
+    hueLamps.setColor(level: 10, hue: 70, saturation: 100)
+    tvLights?.setColor(level: 10, hue: 70, saturation: 100)
+    otherLights?.off()
+}
+
+// debug information
+def handleButton4Held() {
+    hueLamps.each {
+        log.debug "lamp ${it.displayName} hue: ${it.currentHue}"
+        log.debug "lamp ${it.displayName} saturation: ${it.currentSaturation}"
+        log.debug "lamp ${it.displayName} level ${it.currentLevel}"
+    }
+
+    log.debug "tv hue: ${tvLights?.currentHue}"
+    log.debug "tv saturation: ${tvLights?.currentSaturation}"
+    log.debug "tv color ${tvLights?.currentColor}"
+    log.debug "tv level ${tvLights?.currentLevel}"
+}
diff --git a/official/page-params-by-href.groovy b/official/page-params-by-href.groovy
new file mode 100644 (file)
index 0000000..5c187cb
--- /dev/null
@@ -0,0 +1,144 @@
+/**
+ *  Example of passing params via href element to a dynamic page
+ *
+ *  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.
+ *
+ */
+definition(
+    name: "href params example",
+    namespace: "smartthings",
+    author: "SmartThings",
+    description: "passing params via href element to a dynamic page",
+    category: "",
+    iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
+    iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
+    iconX3Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png")
+
+
+// ========================================================
+// PAGES
+// ========================================================
+
+preferences {
+    page(name: "firstPage")
+    page(name: "secondPage")
+    page(name: "thirdPage")
+}
+
+def firstPage() {
+
+    def hrefParams = [
+        foo: "bar", 
+        thisIs: "totally working", 
+        install: checkSomeCustomLogic(),
+        nextPage: "thirdPage"
+    ]
+    
+    dynamicPage(name: "firstPage", uninstall: true) {
+        /* 
+        *  The href element accepts a key of `params` and expects a `Map` as the value.
+        *  Anything passed in `params` will be sent along with the request but will not be persisted in any way.
+        *  The params will be used going to the next page but will not be available when backing out of that page.
+        *  This, combined with the fact that pages refresh when navigating back to them, means that your params will not be
+        *  available if your user hits the back button.
+        */
+        section {        
+            href(
+                name: "toSecondPage", 
+                page: "secondPage", 
+                params: hrefParams, 
+                description: "includes params: ${hrefParams}",
+                state: hrefState()
+            )
+        }
+    }
+}
+
+def secondPage(params) {
+     /* 
+     * firstPage included `params` in the href element that navigated to here. 
+     * You must specify some variable name in the method declaration. (I used 'params' here, but it can be any variable name you want).
+     * If you do not specify a variable name, there is no way to get the params that you specified in your `href` element. 
+     */
+
+    log.debug "params: ${params}"
+
+    dynamicPage(name: "secondPage", uninstall: true, install: params?.install) {
+        if (params.nextPage) {
+            section {
+                href(
+                    name: "toNextPage", 
+                    page: params.nextPage, 
+                    description: hrefState() ? "You've already been to the third page": "go checkout the third page",
+                    state: hrefState()
+                )
+            }
+        } else {
+            section {
+                paragraph "There were no params included when fetching this page."
+            }
+        }
+    }
+}
+
+def thirdPage() {
+
+    state.reachedThirdPage = true
+
+    dynamicPage(name: "thirdPage") {
+        section {
+            image "https://placekitten.com/g/600/500"
+        }
+    }
+}
+
+// ========================================================
+// HELPERS
+// ========================================================
+
+
+def checkSomeCustomLogic() {
+    // ... 
+    false
+}
+
+def hrefState() {
+    /* 
+     * `state: "complete"` makes the right side of the href green. 
+     * It's a great way to show the user that they've already set up some stuff on that page. 
+     * In other words, it's a great way to show state ;) 
+     * If you're using hrefs, it's a good idea to set `state` when appropriate. 
+     */
+     state.reachedThirdPage ? "complete": ""
+}
+
+// ========================================================
+// HANDLERS
+// ========================================================
+
+
+def installed() {
+    log.debug "Installed with settings: ${settings}"
+    initialize()
+}
+
+def updated() {
+    log.debug "Updated with settings: ${settings}"
+    unsubscribe()
+    initialize()
+}
+
+def initialize() {
+    // TODO: subscribe to attributes, devices, locations, etc.
+}
+
+// TODO: implement event handlers
\ No newline at end of file
diff --git a/official/restful-switch.groovy b/official/restful-switch.groovy
new file mode 100644 (file)
index 0000000..dc0b768
--- /dev/null
@@ -0,0 +1,119 @@
+/**
+ *  Control a Switch with an API call
+ *
+ *  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.
+ *
+ */
+definition(
+    name: "Control a Switch with an API call",
+    namespace: "smartthings",
+    author: "SmartThings",
+    description: "V2 of 'RESTful Switch' example. Trying to make OAuth work properly.",
+    category: "My Apps",
+    iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
+    iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
+    oauth: true)
+
+preferences {
+    section("which switches?") {
+        input "theSwitches", "capability.switch", multiple: true
+    }
+}
+
+mappings {
+  path("/switches") {
+    // GET requests to /switches endpoint go to listSwitches.
+    // PUT requests go to updateSwitches
+    action: [
+      GET: "getSwitches",
+      PUT: "updateSwitches"
+    ]
+  }
+  
+  // GET requests to endpoint /switches/<id> go to getSwitch
+  // PUT requests to endpoint /switches/<id> go to updateSwitch
+  path("/switches/:id") {
+    action: [
+        GET: "getSwitch",
+        PUT: "updateSwitch"
+    ]
+  }
+}
+
+// return a map in the form of [switchName, switchStatus]
+// the returned value will be converted to JSON by the platform
+def getSwitches() {
+    def status = [:]
+    theSwitches.each {theSwitch ->
+        log.trace "will populate status map"
+        log.trace "theSwitch id: ${theSwitch.id}"
+        status.put(theSwitch.displayName, theSwitch.currentSwitch)
+    }
+    
+    log.debug "listSwitches returning: $status"
+    return status
+}
+
+def getSwitch() {
+    def theSwitch = theSwitches.find{it.id == params.id}
+    [theSwitch.displayName, theSwitch.currentSwitch]
+}
+
+// execute the command specified in the request
+// returns a 400 error if a non-supported command
+// is specified (only on, off, or toggle supported)
+// assumes request body with JSON in format {"command" : "<value>"}
+def updateSwitches() {
+    log.trace "updateSwitches: request: $request"
+    log.trace "updateSwitches: params: $params"
+    
+    theSwitches.each {
+        doCommand(it, request.JSON.command)
+    }
+}
+
+// execute the command specified in the request
+// return a 400 error if a non-supported command 
+// is specified (only on, off, or toggle supported)
+// assumes request body with JSON in format {"command" : "<value>"}
+def updateSwitch() {
+    log.trace "updateSwitch: look for swithc with id ${params.id}"
+    def theSwitch = theSwitches.find{it.id == params.id}
+    doCommand(theSwitch, request.JSON.command)
+}
+
+def doCommand(theSwitch, command) {
+    if (command == "toggle") {
+        if (theSwitch.currentSwitch == "on") {
+            log.debug "will try and turn switch ${theSwitch.displayName} on"
+            theSwitch.off()
+        } else {
+            log.debug "will try and turn switch ${theSwitch.displayName} off"
+            theSwitch.on()
+        }
+    } else if (command == "on" || command == "off") {
+        theSwitch."$command"()
+    } else {
+        httpError(400, "Unsupported command - only 'toggle', 'off', and 'on' supported")
+    }
+}
+
+// called when SmartApp is installed
+def installed() {
+    log.debug "Installed with settings: ${settings}"
+}
+
+// called when any preferences are changed in this SmartApp. 
+def updated() {
+    log.debug "Updated with settings: ${settings}"
+    unsubscribe()
+}
diff --git a/official/turn-on-at-sunset.groovy b/official/turn-on-at-sunset.groovy
new file mode 100644 (file)
index 0000000..ff2f100
--- /dev/null
@@ -0,0 +1,48 @@
+/**
+ *  Turn on at Sunset
+ *
+ *  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.
+ *
+ */
+definition(
+    name: "Turn on at Sunset",
+    namespace: "examples",
+    author: "SmartThings",
+    description: "Turn on lights at sunset, based on your location's geofence.",
+    category: "My Apps",
+    iconUrl: "https://s3.amazonaws.com/smartapp-icons/ModeMagic/rise-and-shine.png",
+    iconX2Url: "https://s3.amazonaws.com/smartapp-icons/ModeMagic/rise-and-shine@2x.png",
+    iconX3Url: "https://s3.amazonaws.com/smartapp-icons/ModeMagic/rise-and-shine@2x.png")
+
+preferences {
+    section("Lights") {
+        input "switches", "capability.switch", title: "Which lights to turn on?"
+    }
+}
+
+def installed() {
+    initialize()
+}
+
+def updated() {
+    unsubscribe()
+    initialize()
+}
+
+def initialize() {
+    subscribe(location, "sunset", sunsetHandler)
+}
+
+def sunsetHandler(evt) {
+    log.debug "turning on lights at sunset"
+    switches.on()
+}
diff --git a/official/turn-on-before-sunset.groovy b/official/turn-on-before-sunset.groovy
new file mode 100644 (file)
index 0000000..a4ed25c
--- /dev/null
@@ -0,0 +1,70 @@
+/**
+ *  Turn on before Sunset
+ *
+ *  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.
+ *
+ */
+definition(
+        name: "Turn on before Sunset",
+        namespace: "exmaples",
+        author: "SmartThings",
+        description: "Turn on lights a number of minutes before sunset, based on your location's geofence",
+        category: "My Apps",
+        iconUrl: "https://s3.amazonaws.com/smartapp-icons/ModeMagic/rise-and-shine.png",
+        iconX2Url: "https://s3.amazonaws.com/smartapp-icons/ModeMagic/rise-and-shine@2x.png",
+        iconX3Url: "https://s3.amazonaws.com/smartapp-icons/ModeMagic/rise-and-shine@2x.png")
+
+preferences {
+    section("Lights") {
+        input "switches", "capability.switch", title: "Which lights to turn on?"
+        input "offset", "number", title: "Turn on this many minutes before sunset"
+    }
+}
+
+def installed() {
+    initialize()
+}
+
+def updated() {
+    unsubscribe()
+    initialize()
+}
+
+def initialize() {
+    subscribe(location, "sunsetTime", sunsetTimeHandler)
+
+    //schedule it to run today too
+    scheduleTurnOn(location.currentValue("sunsetTime"))
+}
+
+def sunsetTimeHandler(evt) {
+    //when I find out the sunset time, schedule the lights to turn on with an offset
+    scheduleTurnOn(evt.value)
+}
+
+def scheduleTurnOn(sunsetString) {
+    //get the Date value for the string
+    def sunsetTime = Date.parse("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", sunsetString)
+
+    //calculate the offset
+    def timeBeforeSunset = new Date(sunsetTime.time - (offset * 60 * 1000))
+
+    log.debug "Scheduling for: $timeBeforeSunset (sunset is $sunsetTime)"
+
+    //schedule this to run one time
+    runOnce(timeBeforeSunset, turnOn)
+}
+
+def turnOn() {
+    log.debug "turning on lights"
+    switches.on()
+}
diff --git a/official/turn-on-by-zip-code.groovy b/official/turn-on-by-zip-code.groovy
new file mode 100644 (file)
index 0000000..ac71ccb
--- /dev/null
@@ -0,0 +1,84 @@
+/**
+ *  Turn on by ZIP code
+ *
+ *  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.
+ *
+ */
+definition(
+        name: "Turn on by ZIP code",
+        namespace: "examples",
+        author: "SmartThings",
+        description: "Turn on lights based on ZIP code",
+        category: "My Apps",
+        iconUrl: "https://s3.amazonaws.com/smartapp-icons/ModeMagic/rise-and-shine.png",
+        iconX2Url: "https://s3.amazonaws.com/smartapp-icons/ModeMagic/rise-and-shine@2x.png",
+        iconX3Url: "https://s3.amazonaws.com/smartapp-icons/ModeMagic/rise-and-shine@2x.png")
+
+preferences {
+    section("Lights") {
+        input "switches", "capability.switch", title: "Which lights to turn on?"
+    }
+    section("Sunset offset (optional)...") {
+        input "sunsetOffsetValue", "text", title: "HH:MM", required: false
+    }
+    section("Zip code") {
+        input "zipCode", "text", required: false
+    }
+}
+
+def installed() {
+    initialize()
+}
+
+def updated() {
+    unsubscribe()
+    initialize()
+}
+
+def initialize() {
+    scheduleNextSunset()
+}
+
+def scheduleNextSunset(date = null) {
+    def s = getSunriseAndSunset(zipCode: zipCode, sunsetOffset: getSunsetOffset(), date: date)
+    def now = new Date()
+    def setTime = s.sunset
+    log.debug "setTime: $setTime"
+
+    // use state to keep track of sunset times between executions
+    // if sunset time has changed, unschedule and reschedule handler with updated time
+    if(state.setTime != setTime.time) {
+        unschedule("sunsetHandler")
+
+        if(setTime.before(now)) {
+            setTime = setTime.next()
+        }
+
+        state.setTime = setTime.time
+
+        log.info "scheduling sunset handler for $setTime"
+        schedule(setTime, sunsetHandler)
+    }
+}
+
+def sunsetHandler() {
+    log.debug "turning on lights"
+    switches.on()
+
+    // schedule for tomorrow
+    scheduleNextSunset(new Date() + 1)
+}
+
+private getSunsetOffset() {
+    //if there is an offset, make negative since we only care about before
+    sunsetOffsetValue ? "-$sunsetOffsetValue" : null
+}
diff --git a/official/tweet-to-hue.groovy b/official/tweet-to-hue.groovy
new file mode 100644 (file)
index 0000000..046bf4f
--- /dev/null
@@ -0,0 +1,134 @@
+/**
+ *  Tweet to Hue
+ *
+ *  Copyright 2015 Andrew Mager & Kris Schaller
+ *
+ *  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.
+ *
+ */
+definition(
+    name: "Tweet to Hue",
+    namespace: "com.smartthings.dev",
+    author: "Andrew Mager & Kris Schaller",
+    description: "Update a Hue bulb's color based on a tweet.",
+    category: "Fun & Social",
+    iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
+    iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
+    iconX3Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
+    oauth: true)
+
+
+preferences {
+  section("Control these hue bulbs...") {
+    input "hues", "capability.colorControl", title: "Which Hue Bulbs?", required:false, multiple:true
+  }
+}
+
+/* This block defines which functions will fire when you hit certain endpoints. */
+
+mappings {
+  path("/hue") {
+    action: [
+        PUT: "postHue"
+    ]
+  }
+}
+
+def installed() {
+    log.debug "Installed with settings: ${settings}"
+
+    initialize()
+}
+
+def updated() {
+    log.debug "Updated with settings: ${settings}"
+
+    unsubscribe()
+    initialize()
+}
+
+def initialize() {
+
+}
+
+/**
+* Body: { color=[yourcolor] } to change color
+* Example:
+*     {
+*          "value" : " #smartthings is so color=blue"
+*     }
+*/
+
+def postHue() {
+    def tweetText = request.JSON.text
+    log.info "POST: $tweetText"
+    
+    try {
+        def tweetColor = (tweetText =~ /color=(\w+)/)[0][1].toLowerCase()
+        log.debug (tweetText =~ /color=(\w+)/)
+        setHueColor(tweetColor)     
+    }
+    catch (any) {
+        log.trace "POST: Check Body (e.g: @RT: #smartthings color=red)"
+     }    
+}
+
+private setHueColor(color) {
+
+    def hueColor = 0
+    def saturation = 100
+
+    switch(color) {
+        case "white":
+            hueColor = 52
+            saturation = 19
+            break;
+        case "blue":
+            hueColor = 70
+            break;
+        case "green":
+            hueColor = 39
+            break;
+        case "yellow":
+            hueColor = 25
+            break;
+        case "orange":
+            hueColor = 10
+            break;
+        case "purple":
+            hueColor = 75
+            break;
+        case "pink":
+            hueColor = 83
+            break;
+        case "red":
+            hueColor = 100
+            break;
+    }
+
+    state.previous = [:]
+
+    hues.each {
+        state.previous[it.id] = [
+            "switch": it.currentValue("switch"),
+            "level" : it.currentValue("level"),
+            "hue": it.currentValue("hue"),
+            "saturation": it.currentValue("saturation")
+        ]
+    }
+
+    log.debug "current values = $state.previous"
+
+    def newValue = [hue: hueColor, saturation: saturation, level: 100]
+    log.debug "new value = $newValue"
+
+    hues*.setColor(newValue)
+}
diff --git a/official/web-services-smartapp.groovy b/official/web-services-smartapp.groovy
new file mode 100644 (file)
index 0000000..8c650f3
--- /dev/null
@@ -0,0 +1,79 @@
+/**
+ *  Web Services Tutorial
+ *
+ *  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.
+ *
+ */
+definition(
+    name: "Web Services Tutorial",
+    namespace: "smartthings",
+    author: "SmartThings",
+    description: "web services tutorial",
+    category: "",
+    iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
+    iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
+    iconX3Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
+    oauth: [displayName: "web services tutorial ", displayLink: "http://localhost:4567"])
+
+
+preferences {
+  section ("Allow external service to control these things...") {
+    input "switches", "capability.switch", multiple: true, required: true
+  }
+}
+
+mappings {
+  path("/switches") {
+    action: [
+      GET: "listSwitches"
+    ]
+  }
+  path("/switches/:command") {
+    action: [
+      PUT: "updateSwitches"
+    ]
+  }
+}
+
+// returns a list like
+// [[name: "kitchen lamp", value: "off"], [name: "bathroom", value: "on"]]
+def listSwitches() {
+
+    def resp = []
+    switches.each {
+        resp << [name: it.displayName, value: it.currentValue("switch")]
+    }
+    return resp
+}
+
+void updateSwitches() {
+    // use the built-in request object to get the command parameter
+    def command = params.command
+
+    // all switches have the comand
+    // execute the command on all switches
+    // (note we can do this on the array - the command will be invoked on every element
+    switch(command) {
+        case "on":
+            switches.on()
+            break
+        case "off":
+            switches.off()
+            break
+        default:
+            httpError(400, "$command is not a valid command for all switches specified")
+    }
+
+}
+def installed() {}
+
+def updated() {}
diff --git a/official/wunderground-pws-connect.groovy b/official/wunderground-pws-connect.groovy
new file mode 100644 (file)
index 0000000..99eadaf
--- /dev/null
@@ -0,0 +1,99 @@
+/**
+ *  Weather Underground PWS Connect
+ *
+ *  Copyright 2015 Andrew Mager
+ *
+ *  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.
+ *
+ */
+import java.text.DecimalFormat
+definition(
+    name: "Weather Underground PWS Connect",
+    namespace: "co.mager",
+    author: "Andrew Mager",
+    description: "Connect your SmartSense Temp/Humidity sensor to your Weather Underground Personal Weather Station.",
+    category: "Green Living",
+    iconUrl: "http://i.imgur.com/HU0ANBp.png",
+    iconX2Url: "http://i.imgur.com/HU0ANBp.png",
+    iconX3Url: "http://i.imgur.com/HU0ANBp.png",
+    oauth: true)
+
+
+preferences {
+    section("Select a sensor") {
+        input "temp", "capability.temperatureMeasurement", title: "Temperature", required: true
+        input "humidity", "capability.relativeHumidityMeasurement", title: "Humidity", required: true
+    }
+    section("Configure your Weather Underground credentials") {
+        input "weatherID", "text", title: "Weather Station ID", required: true
+        input "password", "password", title: "Weather Underground password", required: true
+
+    }
+}
+
+def installed() {
+    log.debug "Installed with settings: ${settings}"
+    initialize()
+}
+
+
+def updated() {
+    log.debug "Updated with settings: ${settings}"
+    initialize()
+}
+
+
+def initialize() {
+    
+    runEvery10Minutes(updateCurrentWeather)
+    
+    log.trace "Temp: " + temp.currentTemperature
+    log.trace "Humidity: " + humidity.currentHumidity
+    log.trace "Dew Point: " + calculateDewPoint(temp.currentTemperature, humidity.currentHumidity)
+
+}
+
+
+def updateCurrentWeather() {
+    
+    def params = [
+        uri: "http://weatherstation.wunderground.com",
+        path: "/weatherstation/updateweatherstation.php",
+        query: [
+            "ID": weatherID,
+            "PASSWORD": password,
+            "dateutc": "now",
+            "tempf": temp.currentTemperature,
+            "humidity": humidity.currentHumidity,
+            "dewptf": calculateDewPoint(temp.currentTemperature, humidity.currentHumidity),
+            "action": "updateraw",
+            "softwaretype": "SmartThings"
+        ]
+    ]
+    
+    if (temp.currentTemperature) {
+        try {
+            httpGet(params) { resp ->   
+                log.debug "response data: ${resp.data}"
+            }
+        } catch (e) {
+            log.error "something went wrong: $e"
+        }
+    }
+
+}
+
+
+def calculateDewPoint(t, rh) {
+    def dp = 243.04 * ( Math.log(rh / 100) + ( (17.625 * t) / (243.04 + t) ) ) / (17.625 - Math.log(rh / 100) - ( (17.625 * t) / (243.04 + t) ) ) 
+    return new DecimalFormat("##.##").format(dp)
+}