├── README.md ├── device-types ├── README.md ├── momentary-button-tile │ ├── README.md │ └── momentary-button-tile.device.groovy ├── raspberry-pi │ ├── README.md │ ├── raspberry-pi.device.groovy │ └── raspberrypi.py ├── spark-core-illuminance-sensor │ ├── README.md │ ├── spark-core-illuminance-sensor.device.groovy │ └── spark-core-illuminance-sensor.ino ├── spark-core-relative-humidity-sensor │ ├── README.md │ ├── spark-core-relative-humidity-sensor.device.groovy │ └── spark-core-relative-humidity-sensor.ino └── spark-core-temperature-sensor │ ├── README.md │ ├── spark-core-temperature-sensor.device.groovy │ └── spark-core-temperature-sensor.ino ├── devicetypes └── nicholaswilde │ └── plug-in-zwave-dimmer.src │ └── plug-in-zwave-dimmer.groovy └── smartapps ├── README.md ├── device-poll ├── README.md └── device-poll.app.groovy ├── nicholaswilde └── samsung-tv-remote.src │ ├── Readme.md │ └── samsung-tv-remote.groovy ├── notify-when-on-for ├── README.md └── notify-when-on-for.app.groovy └── thingspeak-logger-ii ├── README.md ├── thingspeak-logger-ii.app.groovy ├── thingspeak.png └── thingspeak@2x.png /README.md: -------------------------------------------------------------------------------- 1 | SmartThings 2 | =========== 3 | 4 | This is a repository of all of my custom [SmartThings](http://www.smartthings.com/) programs, which include both [Device Types](http://docs.smartthings.com/en/latest/device-type-developers-guide/index.html) and [SmartApps](http://docs.smartthings.com/en/latest/smartapp-developers-guide/index.html). 5 | -------------------------------------------------------------------------------- /device-types/README.md: -------------------------------------------------------------------------------- 1 | Device Types 2 | ============ 3 | 4 | All of my custom [Device Types](http://docs.smartthings.com/en/latest/device-type-developers-guide/index.html). 5 | 6 | ## Sark Core Installation Instructions 7 | 1. For Spark Core Device Types, first install the Spark Core sketch (*.ino file) using [these instructions](http://docs.spark.io/start/#flash-apps-with-spark-build-flashing-your-first-app). 8 | 2. Install the 9 | -------------------------------------------------------------------------------- /device-types/momentary-button-tile/README.md: -------------------------------------------------------------------------------- 1 | Momentary Button Tile 2 | ==================== 3 | Creates a virtual momentary button tile which can be used to trigger events in SmartApps. 4 | 5 | One example is to use it with [Samsung TV Remote SmartApp to trigger](https://github.com/nicholaswilde/smartthings/blob/master/smartapps/samsung-tv-remote/samsung-tv-remote.app.groovy) a command on a Samsung TV. 6 | -------------------------------------------------------------------------------- /device-types/momentary-button-tile/momentary-button-tile.device.groovy: -------------------------------------------------------------------------------- 1 | /** 2 | * Momentary Button Tile 3 | * 4 | * Author: SmartThings 5 | * 6 | * Date: 2013-05-01 7 | */ 8 | metadata { 9 | // Automatically generated. Make future change here. 10 | definition (name: "Momentary Button Tile", namespace: "smartthings", author: "SmartThings") { 11 | capability "Actuator" 12 | capability "Switch" 13 | capability "Momentary" 14 | capability "Sensor" 15 | } 16 | 17 | // simulator metadata 18 | simulator { 19 | } 20 | 21 | // UI tile definitions 22 | tiles { 23 | standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) { 24 | state "off", label: 'Push', action: "momentary.push", backgroundColor: "#ffffff", nextState: "on" 25 | state "on", label: 'Push', action: "momentary.push", backgroundColor: "#53a7c0" 26 | } 27 | main "switch" 28 | details "switch" 29 | } 30 | } 31 | 32 | def parse(String description) { 33 | } 34 | 35 | def push() { 36 | sendEvent(name: "switch", value: "on", isStateChange: true, display: false) 37 | sendEvent(name: "switch", value: "off", isStateChange: true, display: false) 38 | sendEvent(name: "momentary", value: "pushed", isStateChange: true) 39 | } 40 | 41 | def on() { 42 | push() 43 | } 44 | 45 | def off() { 46 | push() 47 | } 48 | -------------------------------------------------------------------------------- /device-types/raspberry-pi/README.md: -------------------------------------------------------------------------------- 1 | ## Deprecated 2 | Try using [berryio-smartthings](https://github.com/nicholaswilde/berryio-smartthings). 3 | 4 | ## Raspberry Pi Device Type 5 | Monitor your [Raspberry Pi](http://www.raspberrypi.org/) with [SmartThings](http://www.smartthings.com/) and [WebIOPi](https://code.google.com/p/webiopi/). 6 | 7 | The setup requires three things; the SmartThings Raspberry Pi Device Type, WebIOPi, and the WebIOPi python script macro (raspberrypi.py). 8 | 9 | **Note:** If you're using a [Raspberry Pi 2](https://www.raspberrypi.org/products/raspberry-pi-2-model-b/), you might have to use [berryio](https://github.com/NeonHorizon/berryio). See [Ledridge's](https://github.com/Ledridge) [RaspberryPi2](https://github.com/Ledridge/SmartThings/blob/master/RaspberryPi2). 10 | 11 | ![screenshot_2014-09-13-00-00-54 2](https://cloud.githubusercontent.com/assets/600019/4259800/f5aa3e10-3b13-11e4-9a89-f1753b44e1ea.jpg) 12 | 13 | ### Check your SmartThings Hub firmware version 14 | Your firmware version needs to be greater than `000.010.00246`. Has been proven to work with `000.011.00603` and not with `000.010.00246`. 15 | - Check your hub firmware version [here](https://graph.api.smartthings.com/hub/list). 16 | 17 | ### WebIOPi Installation Instructions 18 | - Following [these instructions](https://code.google.com/p/webiopi/wiki/INSTALL?tm=6) for installing WebIOPi. 19 | 20 | ### WebIOPi Python Script Marco Installation Instructions 21 | The python script macro is used by WebIOPi to pipe out the information from the Raspberry Pi to SmartThings using a REST API. The script requires psutil for Python3 which also requires pip-3.2. 22 | - Install [pip-3.2](https://github.com/pypa/pip) 23 | ``` 24 | $ sudo apt-get install python3-pip 25 | ``` 26 | - Install [psutil](https://github.com/giampaolo/psutil) 27 | ``` 28 | $ sudo pip-3.2 install psutil 29 | ``` 30 | - Check that the install was successful 31 | ``` 32 | $ python3 33 | >>> import psutil 34 | >>> exit() 35 | ``` 36 | - Reboot your Raspberry Pi. 37 | ``` 38 | $ sudo reboot 39 | ``` 40 | - Create a directory and download the python script. See [this for reference](https://code.google.com/p/webiopi/wiki/Tutorial_Basis). 41 | ``` 42 | $ mkdir -p ~/myproject/python 43 | $ cd ~/myproject/python/ 44 | $ wget https://raw.githubusercontent.com/nicholaswilde/smartthings/master/device-types/raspberry-pi/raspberrypi.py 45 | $ sudo chmod +x raspberrypi.py 46 | ``` 47 | - Add the script to the WebIOPi configuration file. See [this for reference](https://code.google.com/p/webiopi/wiki/Tutorial_Basis). 48 | ``` 49 | $ sudo nano /etc/webiopi/config 50 | ``` 51 | ``` 52 | ... 53 | [SCRIPTS] 54 | myproject = /home/pi/myproject/python/raspberrypi.py 55 | ... 56 | ``` 57 | - Save the configuration file and restart the `webiopi` service 58 | ``` 59 | $ sudo service webiopi restart 60 | ``` 61 | 62 | ### Rasberry Pi SmartThings Device Type Installation Instructions 63 | - Create and account or log into the [SmartThings Web IDE](https://graph.api.smartthings.com/login/auth). 64 | - Click on `My Device Types` from the navigation menu. 65 | - Click on `+ New SmartDevice` button. 66 | - Fill in the `Name` field and click on the `Create` button. Don't worry about filling out everything else. 67 | - Paste the code from the [raspberry-pi.device.groovy](https://github.com/nicholaswilde/smartthings/blob/master/device-types/raspberry-pi/raspberry-pi.device.groovy) file. 68 | - Click on `Save` in the IDE. 69 | - Click on `Publish -> For Me` in the IDE. 70 | 71 | ### Raspberry Pi SmartThings Device Installation Instructions 72 | The device type is a generic template for a specific type of device. The Raspberry Pi device itself needs to be added to SmartThings. 73 | - Click on `My Devices` in the SmartThings Web IDE navigation menu. 74 | - Click on `+ New Device` button. 75 | - Specify the following fields: 76 | - Name: `Raspberry Pi` 77 | - Device Network Id: `123` (can be anything at the moment because the Raspberry Pi device type will change it for you). 78 | - Type: `Raspberry Pi` (Name of your new device type) 79 | - Version: `Published` 80 | - Location: your location (e.g. `Home`) 81 | - Hub: your hub (This is a must or else the Raspberry Pi device won't work!) 82 | - Group: Can leave blank. 83 | - Click on the `Create` button. 84 | - Click on the `Raspberry Pi` display name from the `My Devices` list. 85 | - Go to `Preference` and click `edit` 86 | - Enter in your settings; 87 | - IP address: RPi IP address 88 | - Port: `8000`(default for WebIOPi) 89 | - WebIOPi username: `webiopi` (default for WebIOPi) 90 | - WebIOPi password: `raspberry` (default for WebIOPi) 91 | - Click the `Save` button. 92 | 93 | **Congratulations. Your Raspberry Pi should now be listed in the `Things` category of your SmartThings mobile app!** 94 | 95 | ### Troubleshooting 96 | - Ensure that your SmartThings hub is up to date (see above). 97 | - [Forum topic](http://community.smartthings.com/t/raspberry-pi-device-type/4969) 98 | - Ensure that the `Device Network Id` and `Hub` fields are filled in under your Raspberry Pi device in your [Device List](https://graph.api.smartthings.com/device/list) 99 | -------------------------------------------------------------------------------- /device-types/raspberry-pi/raspberry-pi.device.groovy: -------------------------------------------------------------------------------- 1 | /** 2 | * Raspberry Pi 3 | * 4 | * Copyright 2014 Nicholas Wilde 5 | * 6 | * Monitor your Raspberry Pi using SmartThings and WebIOPi 7 | * 8 | * Companion WebIOPi python script can be found here: 9 | * 10 | * 11 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 12 | * in compliance with the License. You may obtain a copy of the License at: 13 | * 14 | * http://www.apache.org/licenses/LICENSE-2.0 15 | * 16 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 17 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License 18 | * for the specific language governing permissions and limitations under the License. 19 | * 20 | */ 21 | 22 | import groovy.json.JsonSlurper 23 | 24 | preferences { 25 | input("ip", "string", title:"IP Address", description: "192.168.1.150", required: true, displayDuringSetup: true) 26 | input("port", "string", title:"Port", description: "8000", defaultValue: 8000 , required: true, displayDuringSetup: true) 27 | input("username", "string", title:"Username", description: "webiopi", required: true, displayDuringSetup: true) 28 | input("password", "password", title:"Password", description: "Password", required: true, displayDuringSetup: true) 29 | } 30 | 31 | metadata { 32 | definition (name: "Raspberry Pi", namespace: "nicholaswilde/smartthings", author: "Nicholas Wilde") { 33 | capability "Polling" 34 | capability "Refresh" 35 | capability "Temperature Measurement" 36 | capability "Switch" 37 | capability "Sensor" 38 | capability "Actuator" 39 | 40 | attribute "cpuPercentage", "string" 41 | attribute "memory", "string" 42 | attribute "diskUsage", "string" 43 | 44 | command "restart" 45 | } 46 | 47 | simulator { 48 | // TODO: define status and reply messages here 49 | } 50 | 51 | tiles { 52 | valueTile("temperature", "device.temperature", width: 1, height: 1) { 53 | state "temperature", label:'${currentValue}° CPU', unit: "F", 54 | backgroundColors:[ 55 | [value: 25, color: "#153591"], 56 | [value: 35, color: "#1e9cbb"], 57 | [value: 47, color: "#90d2a7"], 58 | [value: 59, color: "#44b621"], 59 | [value: 67, color: "#f1d801"], 60 | [value: 76, color: "#d04e00"], 61 | [value: 77, color: "#bc2323"] 62 | ] 63 | } 64 | standardTile("button", "device.switch", width: 1, height: 1, canChangeIcon: true) { 65 | state "off", label: 'Off', icon: "st.Electronics.electronics18", backgroundColor: "#ffffff", nextState: "on" 66 | state "on", label: 'On', icon: "st.Electronics.electronics18", backgroundColor: "#79b821", nextState: "off" 67 | } 68 | valueTile("cpuPercentage", "device.cpuPercentage", inactiveLabel: false) { 69 | state "default", label:'${currentValue}% CPU', unit:"Percentage", 70 | backgroundColors:[ 71 | [value: 31, color: "#153591"], 72 | [value: 44, color: "#1e9cbb"], 73 | [value: 59, color: "#90d2a7"], 74 | [value: 74, color: "#44b621"], 75 | [value: 84, color: "#f1d801"], 76 | [value: 95, color: "#d04e00"], 77 | [value: 96, color: "#bc2323"] 78 | ] 79 | } 80 | valueTile("memory", "device.memory", width: 1, height: 1) { 81 | state "default", label:'${currentValue} MB', unit:"MB", 82 | backgroundColors:[ 83 | [value: 353, color: "#153591"], 84 | [value: 287, color: "#1e9cbb"], 85 | [value: 210, color: "#90d2a7"], 86 | [value: 133, color: "#44b621"], 87 | [value: 82, color: "#f1d801"], 88 | [value: 26, color: "#d04e00"], 89 | [value: 20, color: "#bc2323"] 90 | ] 91 | } 92 | valueTile("diskUsage", "device.diskUsage", width: 1, height: 1) { 93 | state "default", label:'${currentValue}% Disk', unit:"Percent", 94 | backgroundColors:[ 95 | [value: 31, color: "#153591"], 96 | [value: 44, color: "#1e9cbb"], 97 | [value: 59, color: "#90d2a7"], 98 | [value: 74, color: "#44b621"], 99 | [value: 84, color: "#f1d801"], 100 | [value: 95, color: "#d04e00"], 101 | [value: 96, color: "#bc2323"] 102 | ] 103 | } 104 | standardTile("restart", "device.restart", inactiveLabel: false, decoration: "flat") { 105 | state "default", action:"restart", label: "Restart", displayName: "Restart" 106 | } 107 | standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat") { 108 | state "default", action:"refresh.refresh", icon: "st.secondary.refresh" 109 | } 110 | main "button" 111 | details(["button", "temperature", "cpuPercentage", "memory" , "diskUsage", "restart", "refresh"]) 112 | } 113 | } 114 | 115 | // ------------------------------------------------------------------ 116 | 117 | // parse events into attributes 118 | def parse(String description) { 119 | def map = [:] 120 | def descMap = parseDescriptionAsMap(description) 121 | //log.debug descMap 122 | def body = new String(descMap["body"].decodeBase64()) 123 | //log.debug "body: ${body}" 124 | def slurper = new JsonSlurper() 125 | def result = slurper.parseText(body) 126 | log.debug "result: ${result}" 127 | if (result){ 128 | log.debug "Computer is up" 129 | sendEvent(name: "switch", value: "on") 130 | } 131 | 132 | 133 | if (result.containsKey("cpu_temp")) { 134 | sendEvent(name: "temperature", value: result.cpu_temp) 135 | } 136 | 137 | if (result.containsKey("cpu_perc")) { 138 | sendEvent(name: "cpuPercentage", value: result.cpu_perc) 139 | } 140 | 141 | if (result.containsKey("mem_avail")) { 142 | log.debug "mem_avail: ${result.mem_avail}" 143 | sendEvent(name: "memory", value: result.mem_avail) 144 | } 145 | if (result.containsKey("disk_usage")) { 146 | log.debug "disk_usage: ${result.disk_usage}" 147 | sendEvent(name: "diskUsage", value: result.disk_usage) 148 | } 149 | 150 | } 151 | 152 | // handle commands 153 | def poll() { 154 | log.debug "Executing 'poll'" 155 | sendEvent(name: "switch", value: "off") 156 | getRPiData() 157 | } 158 | 159 | def refresh() { 160 | sendEvent(name: "switch", value: "off") 161 | log.debug "Executing 'refresh'" 162 | getRPiData() 163 | } 164 | 165 | def restart(){ 166 | log.debug "Restart was pressed" 167 | sendEvent(name: "switch", value: "off") 168 | def uri = "/macros/reboot" 169 | postAction(uri) 170 | } 171 | 172 | // Get CPU percentage reading 173 | private getRPiData() { 174 | def uri = "/macros/getData" 175 | postAction(uri) 176 | } 177 | 178 | // ------------------------------------------------------------------ 179 | 180 | private postAction(uri){ 181 | setDeviceNetworkId(ip,port) 182 | 183 | def userpass = encodeCredentials(username, password) 184 | 185 | def headers = getHeader(userpass) 186 | 187 | def hubAction = new physicalgraph.device.HubAction( 188 | method: "POST", 189 | path: uri, 190 | headers: headers 191 | )//,delayAction(1000), refresh()] 192 | log.debug("Executing hubAction on " + getHostAddress()) 193 | //log.debug hubAction 194 | hubAction 195 | } 196 | 197 | // ------------------------------------------------------------------ 198 | // Helper methods 199 | // ------------------------------------------------------------------ 200 | 201 | def parseDescriptionAsMap(description) { 202 | description.split(",").inject([:]) { map, param -> 203 | def nameAndValue = param.split(":") 204 | map += [(nameAndValue[0].trim()):nameAndValue[1].trim()] 205 | } 206 | } 207 | 208 | private encodeCredentials(username, password){ 209 | log.debug "Encoding credentials" 210 | def userpassascii = "${username}:${password}" 211 | def userpass = "Basic " + userpassascii.encodeAsBase64().toString() 212 | //log.debug "ASCII credentials are ${userpassascii}" 213 | //log.debug "Credentials are ${userpass}" 214 | return userpass 215 | } 216 | 217 | private getHeader(userpass){ 218 | log.debug "Getting headers" 219 | def headers = [:] 220 | headers.put("HOST", getHostAddress()) 221 | headers.put("Authorization", userpass) 222 | //log.debug "Headers are ${headers}" 223 | return headers 224 | } 225 | 226 | private delayAction(long time) { 227 | new physicalgraph.device.HubAction("delay $time") 228 | } 229 | 230 | private setDeviceNetworkId(ip,port){ 231 | def iphex = convertIPtoHex(ip) 232 | def porthex = convertPortToHex(port) 233 | device.deviceNetworkId = "$iphex:$porthex" 234 | log.debug "Device Network Id set to ${iphex}:${porthex}" 235 | } 236 | 237 | private getHostAddress() { 238 | return "${ip}:${port}" 239 | } 240 | 241 | private String convertIPtoHex(ipAddress) { 242 | String hex = ipAddress.tokenize( '.' ).collect { String.format( '%02x', it.toInteger() ) }.join() 243 | return hex 244 | 245 | } 246 | 247 | private String convertPortToHex(port) { 248 | String hexport = port.toString().format( '%04x', port.toInteger() ) 249 | return hexport 250 | } 251 | -------------------------------------------------------------------------------- /device-types/raspberry-pi/raspberrypi.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # To be used with WebIOPi 4 | 5 | import webiopi 6 | import psutil 7 | import os 8 | import json 9 | from threading import Timer 10 | from time import sleep 11 | 12 | GPIO = webiopi.GPIO 13 | 14 | # setup function is automatically called at WebIOPi startup 15 | def setup(): 16 | # Do nothing 17 | pass 18 | 19 | # loop function is repeatedly called by WebIOPi 20 | def loop(): 21 | # Do Nothing 22 | sleep(0.1) # Prevent the CPU from running at 100% 23 | pass 24 | 25 | # destroy function is called at WebIOPi shutdown 26 | def destroy(): 27 | # do nothing 28 | pass 29 | 30 | @webiopi.macro 31 | def reboot(t=1): 32 | Timer(int(t)*60,os.system("sudo reboot")).start() 33 | return "The system is going DOWN for reboot in %d minute" % t 34 | 35 | @webiopi.macro 36 | def getData(): 37 | temp = round(int(open('/sys/class/thermal/thermal_zone0/temp').read()) / 1e3,1) 38 | perc = psutil.cpu_percent() 39 | memAvail = round(psutil.avail_phymem()/1000000,1) 40 | diskUsage = psutil.disk_usage('/').percent 41 | j = {'cpu_temp': temp, 'cpu_perc': perc, 'mem_avail': memAvail, 'disk_usage': diskUsage} 42 | return json.dumps(j,indent=4, separators=(',', ': ')) 43 | -------------------------------------------------------------------------------- /device-types/spark-core-illuminance-sensor/README.md: -------------------------------------------------------------------------------- 1 | Spark Core Illuminance Sensor 2 | ============================= 3 | 4 | Read an illuminance measurement from a [Spark Core](https://www.spark.io/). 5 | 6 | ![smarthings-spark-core-measurement-sensor_bb](https://cloud.githubusercontent.com/assets/600019/3581399/32243f88-0beb-11e4-9e7a-6866fbc852bf.png) 7 | ![smarthings-spark-core-measurement-sensor_schem](https://cloud.githubusercontent.com/assets/600019/3581401/357e6154-0beb-11e4-998b-5f8b1315d45a.png) 8 | -------------------------------------------------------------------------------- /device-types/spark-core-illuminance-sensor/spark-core-illuminance-sensor.device.groovy: -------------------------------------------------------------------------------- 1 | /** 2 | * Spark Core Illuminance Sensor 3 | * 4 | * Copyright 2014 Nicholas Wilde 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 7 | * in compliance with the License. You may obtain a copy of the License at: 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 12 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License 13 | * for the specific language governing permissions and limitations under the License. 14 | * 15 | */ 16 | 17 | preferences { 18 | input "deviceId", "text", title: "Device ID", required: true 19 | input "token", "password", title: "Access Token", required: true 20 | input "sparkVar", "text", title: "Spark Variable", required: true 21 | } 22 | 23 | metadata { 24 | definition (name: "Spark Core Illuminance Sensor", namespace: "nicholaswilde/smartthings", author: "Nicholas Wilde") { 25 | capability "Polling" 26 | capability "Refresh" 27 | capability "Illuminance Measurement" 28 | } 29 | 30 | simulator { 31 | // TODO: define status and reply messages here 32 | } 33 | 34 | tiles { 35 | valueTile("illuminance", "device.illuminance", width: 2, height: 2) { 36 | state "illuminance", label:'${currentValue}', unit: "LUX", 37 | backgroundColors:[ 38 | [value: 21, color: "#153591"], 39 | [value: 51, color: "#1e9cbb"], 40 | [value: 101, color: "#90d2a7"], 41 | [value: 401, color: "#44b621"], 42 | [value: 1001, color: "#f1d801"], 43 | [value: 10001, color: "#d04e00"], 44 | [value: 25001, color: "#bc2323"] 45 | ] 46 | } 47 | standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat") { 48 | state "default", action:"refresh.refresh", icon: "st.secondary.refresh" 49 | } 50 | main "illuminance" 51 | details(["illuminance", "refresh"]) 52 | } 53 | } 54 | 55 | // parse events into attributes 56 | def parse(String description) { 57 | log.debug "Parsing '${description}'" 58 | } 59 | 60 | // handle commands 61 | def poll() { 62 | log.debug "Executing 'poll'" 63 | getReading() 64 | } 65 | 66 | def refresh() { 67 | log.debug "Executing 'refresh'" 68 | getReading() 69 | } 70 | 71 | // Get the sensor reading 72 | private getReading() { 73 | //Spark Core API Call 74 | def readingClosure = { response -> 75 | log.debug "Reading request was successful, $response.data.result" 76 | sendEvent(name: "illuminance", value: response.data.result) 77 | } 78 | 79 | httpGet("https://api.spark.io/v1/devices/${deviceId}/${sparkVar}?access_token=${token}", readingClosure) 80 | } 81 | 82 | -------------------------------------------------------------------------------- /device-types/spark-core-illuminance-sensor/spark-core-illuminance-sensor.ino: -------------------------------------------------------------------------------- 1 | // Create a variable that will store the illuminance value 2 | int illuminance = 0; 3 | 4 | void setup() 5 | { 6 | // Register a Spark variable here 7 | Spark.variable("illuminance", &illuminance, INT); 8 | 9 | // Connect the illuminance sensor to A7 and configure it 10 | // to be an input 11 | pinMode(A0, INPUT); 12 | } 13 | 14 | void loop() 15 | { 16 | // Keep reading the temperature so when we make an API 17 | // call to read its value, we have the latest one 18 | illuminance = map(analogRead(A0), 0, 4095, 0, 99); 19 | } 20 | -------------------------------------------------------------------------------- /device-types/spark-core-relative-humidity-sensor/README.md: -------------------------------------------------------------------------------- 1 | Spark Core Relative Humidity Sensor 2 | ============================= 3 | 4 | Read an relative humidity measurement from a [Spark Core](https://www.spark.io/). 5 | -------------------------------------------------------------------------------- /device-types/spark-core-relative-humidity-sensor/spark-core-relative-humidity-sensor.device.groovy: -------------------------------------------------------------------------------- 1 | /** 2 | * Spark Core Relative Humidity Sensor 3 | * 4 | * Copyright 2014 Nicholas Wilde 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 7 | * in compliance with the License. You may obtain a copy of the License at: 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 12 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License 13 | * for the specific language governing permissions and limitations under the License. 14 | * 15 | */ 16 | 17 | preferences { 18 | input "deviceId", "text", title: "Device ID", required: true 19 | input "token", "password", title: "Access Token", required: true 20 | input "sparkVar", "text", title: "Spark Variable", required: true 21 | } 22 | 23 | metadata { 24 | definition (name: "Spark Core Relative Humidity Sensor", namespace: "nicholaswilde/smartthings", author: "Nicholas Wilde") { 25 | capability "Polling" 26 | capability "Refresh" 27 | capability "Relative Humidity Measurement" 28 | } 29 | 30 | simulator { 31 | // TODO: define status and reply messages here 32 | } 33 | 34 | tiles { 35 | valueTile("humidity", "device.humidity", width: 2, height: 2) { 36 | state "humidity", label:'${currentValue}%', unit: "", 37 | backgroundColors:[ 38 | [value: 16, color: "#5600A3"], 39 | [value: 31, color: "#153591"], 40 | [value: 44, color: "#1e9cbb"], 41 | [value: 59, color: "#90d2a7"], 42 | [value: 74, color: "#44b621"], 43 | [value: 84, color: "#f1d801"], 44 | [value: 95, color: "#d04e00"], 45 | [value: 96, color: "#bc2323"] 46 | ] 47 | } 48 | standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat") { 49 | state "default", action:"refresh.refresh", icon: "st.secondary.refresh" 50 | } 51 | main "humidity" 52 | details(["humidity", "refresh"]) 53 | } 54 | } 55 | 56 | // parse events into attributes 57 | def parse(String description) { 58 | log.debug "Parsing '${description}'" 59 | } 60 | 61 | // handle commands 62 | def poll() { 63 | log.debug "Executing 'poll'" 64 | getReading() 65 | } 66 | 67 | def refresh() { 68 | log.debug "Executing 'refresh'" 69 | getReading() 70 | } 71 | 72 | // Get the sensor reading 73 | private getReading() { 74 | //Spark Core API Call 75 | def readingClosure = { response -> 76 | log.debug "Reading request was successful, $response.data.result" 77 | sendEvent(name: "humidity", value: response.data.result) 78 | } 79 | 80 | httpGet("https://api.spark.io/v1/devices/${deviceId}/${sparkVar}?access_token=${token}", readingClosure) 81 | } 82 | 83 | -------------------------------------------------------------------------------- /device-types/spark-core-relative-humidity-sensor/spark-core-relative-humidity-sensor.ino: -------------------------------------------------------------------------------- 1 | // Create a variable that will store the humidity value 2 | int humidity = 0; 3 | 4 | void setup() 5 | { 6 | // Register a Spark variable here 7 | Spark.variable("humidity", &humidity, INT); 8 | 9 | // Connect the humidity sensor to A7 and configure it 10 | // to be an input 11 | pinMode(A0, INPUT); 12 | } 13 | 14 | void loop() 15 | { 16 | // Keep reading the humidity so when we make an API 17 | // call to read its value, we have the latest one 18 | humidity = map(analogRead(A0), 0, 4095, 0, 99); 19 | } 20 | -------------------------------------------------------------------------------- /device-types/spark-core-temperature-sensor/README.md: -------------------------------------------------------------------------------- 1 | Spark Core Temperature Sensor 2 | ============================= 3 | 4 | Read an temperature measurement from a [Spark Core](https://www.spark.io/). 5 | -------------------------------------------------------------------------------- /device-types/spark-core-temperature-sensor/spark-core-temperature-sensor.device.groovy: -------------------------------------------------------------------------------- 1 | /** 2 | * Spark Core Sensor 3 | * 4 | * Copyright 2014 Nicholas Wilde 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 7 | * in compliance with the License. You may obtain a copy of the License at: 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 12 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License 13 | * for the specific language governing permissions and limitations under the License. 14 | * 15 | */ 16 | 17 | preferences { 18 | input "deviceId", "text", title: "Device ID", required: true 19 | input "token", "password", title: "Access Token", required: true 20 | input "sparkVar", "text", title: "Spark Variable", required: true 21 | } 22 | 23 | metadata { 24 | definition (name: "Spark Core Temperature Sensor", namespace: "nicholaswilde/smartthings", author: "Nicholas Wilde") { 25 | capability "Polling" 26 | capability "Refresh" 27 | capability "Temperature Measurement" 28 | } 29 | 30 | simulator { 31 | // TODO: define status and reply messages here 32 | } 33 | 34 | tiles { 35 | valueTile("temperature", "device.temperature", width: 2, height: 2) { 36 | state "temperature", label:'${currentValue}°', unit: "F", 37 | backgroundColors:[ 38 | [value: 31, color: "#153591"], 39 | [value: 44, color: "#1e9cbb"], 40 | [value: 59, color: "#90d2a7"], 41 | [value: 74, color: "#44b621"], 42 | [value: 84, color: "#f1d801"], 43 | [value: 95, color: "#d04e00"], 44 | [value: 96, color: "#bc2323"] 45 | ] 46 | } 47 | standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat") { 48 | state "default", action:"refresh.refresh", icon: "st.secondary.refresh" 49 | } 50 | main "temperature" 51 | details(["temperature", "refresh"]) 52 | } 53 | } 54 | 55 | // parse events into attributes 56 | def parse(String description) { 57 | log.debug "Parsing '${description}'" 58 | } 59 | 60 | // handle commands 61 | def poll() { 62 | log.debug "Executing 'poll'" 63 | getReading() 64 | } 65 | 66 | def refresh() { 67 | log.debug "Executing 'refresh'" 68 | getReading() 69 | } 70 | 71 | // Get the sensor reading 72 | private getReading() { 73 | //Spark Core API Call 74 | def readingClosure = { response -> 75 | log.debug "Reading request was successful, $response.data.result" 76 | sendEvent(name: "temperature", value: response.data.result) 77 | } 78 | 79 | httpGet("https://api.spark.io/v1/devices/${deviceId}/${sparkVar}?access_token=${token}", readingClosure) 80 | } 81 | 82 | -------------------------------------------------------------------------------- /device-types/spark-core-temperature-sensor/spark-core-temperature-sensor.ino: -------------------------------------------------------------------------------- 1 | // Create a variable that will store the temperature value 2 | int temperature = 0; 3 | 4 | void setup() 5 | { 6 | // Register a Spark variable here 7 | Spark.variable("temperature", &temperature, INT); 8 | 9 | // Connect the temperature sensor to A7 and configure it 10 | // to be an input 11 | pinMode(A0, INPUT); 12 | } 13 | 14 | void loop() 15 | { 16 | // Keep reading the temperature so when we make an API 17 | // call to read its value, we have the latest one 18 | temperature = map(analogRead(A0), 0, 4095, 0, 99); 19 | } 20 | -------------------------------------------------------------------------------- /devicetypes/nicholaswilde/plug-in-zwave-dimmer.src/plug-in-zwave-dimmer.groovy: -------------------------------------------------------------------------------- 1 | /* Plug-In ZWave Dimmer 2 | * 3 | * Variation of the pmjoen's "Dimmer-Switch-Levels" 4 | * 5 | * Device type adds set levels low, medium, and High for preset values (Moods). 6 | * Adds increment up and down buttons with ability to set value. 7 | * Removes indicator from plug-in ZWave dimmer device 8 | * 9 | * Nicholas Wilde 10 | * 20160814 11 | * 12 | * v1.0 - Initial release of device handler 13 | * 14 | */ 15 | 16 | metadata { 17 | // Automatically generated. Make future change here. 18 | definition (name: "Plug-In ZWave Dimmer", namespace: "nicholaswilde", author: "SmartThings") { 19 | capability "Switch Level" 20 | capability "Actuator" 21 | //capability "Indicator" 22 | capability "Switch" 23 | capability "Polling" 24 | capability "Refresh" 25 | capability "Sensor" 26 | 27 | 28 | command "low" 29 | command "med" 30 | command "high" 31 | command "levelUp" 32 | command "levelDown" 33 | 34 | 35 | attribute "currentState", "string" 36 | attribute "switch", "string" 37 | 38 | //fingerprint deviceId: "0x1101", inClusters: "0x26, 0x27, 0x70, 0x86, 0x72" 39 | } 40 | 41 | 42 | tiles(scale: 2) { 43 | multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){ 44 | tileAttribute ("device.currentState", key: "PRIMARY_CONTROL") { 45 | attributeState "default", label:'ADJUSTING', action:"refresh.refresh",icon:"st.Lighting.light13", backgroundColor:"#2179b8", nextState: "turningOff" 46 | attributeState "HIGH", label:'HIGH', action:"switch.off", icon:"st.illuminance.illuminance.bright", backgroundColor:"#79b821", nextState:"turningOff" 47 | attributeState "MED", label:'MED', action:"switch.off", icon:"st.illuminance.illuminance.light", backgroundColor:"#79b821", nextState:"turningOff" 48 | attributeState "LOW", label:'LOW', action:"switch.off", icon:"st.Weather.weather4", backgroundColor:"#79b821", nextState:"turningOff" 49 | attributeState "OFF", label:'OFF', action:"switch.on", icon:"st.Lighting.light13", backgroundColor:"#ffffff", nextState: "turningOn" 50 | attributeState "on", label:'ON', action:"switch.off", icon:"st.Lighting.light11", backgroundColor:"#79b821", nextState:"turningOff" 51 | attributeState "turningOn", action:"switch.on", label:'TURNINGON', icon:"st.Lighting.light11", backgroundColor:"#2179b8", nextState: "turningOn" 52 | attributeState "turningOff", action:"switch.off", label:'TURNINGOFF', icon:"st.Lighting.light13", backgroundColor:"#2179b8", nextState: "turningOff" 53 | } 54 | 55 | tileAttribute("device.level", key: "VALUE_CONTROL") { 56 | attributeState("VALUE_UP", action: "levelUp") 57 | attributeState("VALUE_DOWN", action: "levelDown") 58 | } 59 | tileAttribute ("device.level", key: "SLIDER_CONTROL") { 60 | attributeState "level", action:"switch level.setLevel" 61 | } 62 | } 63 | 64 | 65 | /*standardTile("indicator", "device.indicatorStatus", width: 2, height: 2, inactiveLabel: false, decoration: "flat") { 66 | state "when off", action:"indicator.indicatorWhenOn", icon:"st.indicators.lit-when-off" 67 | state "when on", action:"indicator.indicatorNever", icon:"st.indicators.lit-when-on" 68 | state "never", action:"indicator.indicatorWhenOff", icon:"st.indicators.never-lit" 69 | }*/ 70 | 71 | 72 | standardTile("refresh", "device.switch", width: 3, height: 2, inactiveLabel: false, decoration: "flat") { 73 | state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh" 74 | } 75 | 76 | 77 | valueTile("level", "device.level", inactiveLabel: false, decoration: "flat", width: 3, height: 2) { 78 | state "level", label:'${currentValue} %', unit:"%", backgroundColor:"#ffffff" 79 | } 80 | 81 | 82 | standardTile("low", "device.currentState", inactiveLabel: false, width: 2, height: 2, canChangeBackground: false) { 83 | state "default", label: 'LOW', action: "low", icon:"st.Weather.weather4", backgroundColor: "#ffffff" 84 | state "LOW", label:'LOW', action: "low", icon:"st.Weather.weather4", backgroundColor: "#79b821" 85 | state "ADJUSTING.LOW", label:'LOW', action: "low", icon:"st.Weather.weather4", backgroundColor: "#2179b8" 86 | } 87 | 88 | standardTile("med", "device.currentState", inactiveLabel: false, width: 2, height: 2, canChangeBackground: false) { 89 | state "default", label: 'MED', action: "med", icon:"st.illuminance.illuminance.light", backgroundColor: "#ffffff" 90 | state "MED", label: 'MED', action: "med", icon:"st.illuminance.illuminance.light", backgroundColor: "#79b821" 91 | state "ADJUSTING.MED", label:'MED', action: "med", icon:"st.illuminance.illuminance.light", backgroundColor: "#2179b8" 92 | } 93 | 94 | standardTile("high", "device.currentState", inactiveLabel: false, width: 2, height: 2, canChangeBackground: false) { 95 | state "default", label: 'HIGH', action: "high", icon:"st.illuminance.illuminance.bright", backgroundColor: "#ffffff" 96 | state "HIGH", label: 'HIGH', action: "high", icon:"st.illuminance.illuminance.bright", backgroundColor: "#79b821" 97 | state "ADJUSTING.HIGH", label:'HIGH', action: "high", icon:"st.illuminance.illuminance.bright", backgroundColor: "#2179b8" 98 | } 99 | 100 | 101 | main(["switch"]) 102 | //details(["switch", "low", "med", "high", "level", "indicator", "refresh"]) 103 | details(["switch", "low", "med", "high", "level", "refresh"]) 104 | 105 | } 106 | preferences { 107 | section("Light Level Values") { 108 | input "lowThreshold", "number", title: "Low Button Light Value", range: "1..99" 109 | input "medThreshold", "number", title: "Medium Button Light Value", range: "1..99" 110 | input "highThreshold", "number", title: "High Button Light Value", range: "1..99" 111 | } 112 | section ("Interval Selection") { 113 | input "stepper", "enum", title: "Up/Down Light Interval Value", defaultValue: "10", options: ["5","10","20"] 114 | } 115 | } 116 | } 117 | 118 | 119 | def parse(String description) { 120 | def item1 = [ 121 | canBeCurrentState: false, 122 | linkText: getLinkText(device), 123 | isStateChange: false, 124 | displayed: false, 125 | descriptionText: description, 126 | value: description 127 | ] 128 | def result 129 | def cmd = zwave.parse(description, [0x20: 1, 0x26: 1, 0x70: 1]) 130 | if (cmd) { 131 | result = createEvent(cmd, item1) 132 | } 133 | else { 134 | item1.displayed = displayed(description, item1.isStateChange) 135 | result = [item1] 136 | } 137 | log.debug "Parse returned ${result?.descriptionText}" 138 | result 139 | } 140 | 141 | 142 | def createEvent(physicalgraph.zwave.commands.basicv1.BasicReport cmd, Map item1) { 143 | def result = doCreateEvent(cmd, item1) 144 | for (int i = 0; i < result.size(); i++) { 145 | result[i].type = "physical" 146 | } 147 | log.trace "BasicReport" 148 | result 149 | } 150 | 151 | 152 | def createEvent(physicalgraph.zwave.commands.basicv1.BasicSet cmd, Map item1) { 153 | def result = doCreateEvent(cmd, item1) 154 | for (int i = 0; i < result.size(); i++) { 155 | result[i].type = "physical" 156 | } 157 | log.trace "BasicSet" 158 | result 159 | } 160 | 161 | 162 | def createEvent(physicalgraph.zwave.commands.switchmultilevelv1.SwitchMultilevelStartLevelChange cmd, Map item1) { 163 | [] 164 | log.trace "StartLevel" 165 | } 166 | 167 | 168 | def createEvent(physicalgraph.zwave.commands.switchmultilevelv1.SwitchMultilevelStopLevelChange cmd, Map item1) { 169 | [response(zwave.basicV1.basicGet())] 170 | } 171 | 172 | 173 | def createEvent(physicalgraph.zwave.commands.switchmultilevelv1.SwitchMultilevelSet cmd, Map item1) { 174 | def result = doCreateEvent(cmd, item1) 175 | for (int i = 0; i < result.size(); i++) { 176 | result[i].type = "physical" 177 | } 178 | log.trace "SwitchMultiLevelSet" 179 | result 180 | } 181 | 182 | 183 | def createEvent(physicalgraph.zwave.commands.switchmultilevelv1.SwitchMultilevelReport cmd, Map item1) { 184 | def result = doCreateEvent(cmd, item1) 185 | result[0].descriptionText = "${item1.linkText} is ${item1.value}" 186 | result[0].handlerName = cmd.value ? "statusOn" : "statusOff" 187 | for (int i = 0; i < result.size(); i++) { 188 | result[i].type = "digital" 189 | } 190 | log.trace "SwitchMultilevelReport" 191 | result 192 | } 193 | 194 | 195 | def doCreateEvent(physicalgraph.zwave.Command cmd, Map item1) { 196 | def result = [item1] 197 | def lowThresholdvalue = (settings.lowThreshold != null && settings.lowThreshold != "") ? settings.lowThreshold.toInteger() : 33 198 | def medThresholdvalue = (settings.medThreshold != null && settings.medThreshold != "") ? settings.medThreshold.toInteger() : 67 199 | def highThresholdvalue = (settings.highThreshold != null && settings.highThreshold != "") ? settings.highThreshold.toInteger() : 99 200 | 201 | 202 | item1.name = "switch" 203 | item1.value = cmd.value ? "on" : "off" 204 | if (item1.value == "off") { 205 | sendEvent(name: "currentState", value: "OFF" as String) 206 | } 207 | item1.handlerName = item1.value 208 | item1.descriptionText = "${item1.linkText} was turned ${item1.value}" 209 | item1.canBeCurrentState = true 210 | item1.isStateChange = isStateChange(device, item1.name, item1.value) 211 | item1.displayed = false 212 | 213 | 214 | if (cmd.value) { 215 | def item2 = new LinkedHashMap(item1) 216 | item2.name = "level" 217 | item2.value = cmd.value as String 218 | item2.unit = "%" 219 | item2.descriptionText = "${item1.linkText} dimmed ${item2.value} %" 220 | item2.canBeCurrentState = true 221 | item2.isStateChange = isStateChange(device, item2.name, item2.value) 222 | item2.displayed = false 223 | 224 | if (item2.value.toInteger() == lowThresholdvalue) { sendEvent(name: "currentState", value: "LOW" as String) } 225 | else if (item2.value.toInteger() == medThresholdvalue) { sendEvent(name: "currentState", value: "MED" as String) } 226 | else if (item2.value.toInteger() == highThresholdvalue) { sendEvent(name: "currentState", value: "HIGH" as String) } 227 | else if (item2.value.toInteger() == 0) { sendEvent(name: "currentState", value: "OFF" as String) } 228 | else { sendEvent(name: "currentState", value: "on" as String) } 229 | 230 | 231 | result << item2 232 | } 233 | log.trace "doCreateEvent" 234 | result 235 | } 236 | 237 | 238 | /*def createEvent(physicalgraph.zwave.commands.configurationv1.ConfigurationReport cmd, Map map) { 239 | //def zwaveEvent(physicalgraph.zwave.commands.configurationv1.ConfigurationReport cmd) { 240 | def value = "when off" 241 | log.trace "ConfigurationReport" 242 | if (cmd.configurationValue[0] == 1) {value = "when on"} 243 | if (cmd.configurationValue[0] == 2) {value = "never"} 244 | [name: "indicatorStatus", value: value, display: false] 245 | }*/ 246 | 247 | 248 | def createEvent(physicalgraph.zwave.Command cmd, Map map) { 249 | // Handles any Z-Wave commands we aren't interested in 250 | log.debug "UNHANDLED COMMAND $cmd" 251 | } 252 | 253 | 254 | def on() { 255 | log.info "on" 256 | delayBetween([zwave.basicV1.basicSet(value: 0xFF).format(), zwave.switchMultilevelV1.switchMultilevelGet().format()], 5000) 257 | } 258 | 259 | 260 | def off() { 261 | log.info "off" 262 | delayBetween ([zwave.basicV1.basicSet(value: 0x00).format(), zwave.switchMultilevelV1.switchMultilevelGet().format()], 5000) 263 | } 264 | 265 | 266 | def setLevel(value) { 267 | def lowThresholdvalue = (settings.lowThreshold != null && settings.lowThreshold != "") ? settings.lowThreshold.toInteger() : 33 268 | def medThresholdvalue = (settings.medThreshold != null && settings.medThreshold != "") ? settings.medThreshold.toInteger() : 67 269 | def highThresholdvalue = (settings.highThreshold != null && settings.highThreshold != "") ? settings.highThreshold.toInteger() : 99 270 | 271 | if (value == "LOW") { value = lowThresholdvalue } 272 | if (value == "MED") { value = medThresholdvalue } 273 | if (value == "HIGH") { value = highThresholdvalue } 274 | 275 | 276 | def level = Math.min(value as Integer, 99) 277 | 278 | if (level == lowThresholdvalue) { sendEvent(name: "currentState", value: "ADJUSTING.LOW" as String, displayed: false) } 279 | else if (level == medThresholdvalue) { sendEvent(name: "currentState", value: "ADJUSTING.MED" as String, displayed: false) } 280 | else if (level == highThresholdvalue) { sendEvent(name: "currentState", value: "ADJUSTING.HIGH" as String, displayed: false) } 281 | else if (level == 0) { sendEvent(name: "currentState", value: "OFF" as String, displayed: false) } 282 | else { sendEvent(name: "currentState", value: "on" as String, displayed: false) } 283 | 284 | sendEvent(name: "level", value: level, unit: "%") 285 | 286 | log.trace "setLevelValue: ${value}" 287 | 288 | delayBetween ([zwave.basicV1.basicSet(value: level as Integer).format(), zwave.switchMultilevelV1.switchMultilevelGet().format()], 1000) 289 | } 290 | 291 | 292 | def resetLevel(value) { 293 | def level = Math.min(value as Integer, 99) 294 | delayBetween ([ 295 | delayBetween ([ zwave.basicV1.basicSet(value: level).format(), 296 | zwave.basicV1.basicSet(value: 0x00).format()], 10), 297 | zwave.switchMultilevelV1.switchMultilevelGet().format()], 5000) 298 | } 299 | 300 | 301 | def setLevel(value, duration) { 302 | def lowThresholdvalue = (settings.lowThreshold != null && settings.lowThreshold != "") ? settings.lowThreshold.toInteger() : 33 303 | def medThresholdvalue = (settings.medThreshold != null && settings.medThreshold != "") ? settings.medThreshold.toInteger() : 67 304 | def highThresholdvalue = (settings.highThreshold != null && settings.highThreshold != "") ? settings.highThreshold.toInteger() : 99 305 | 306 | 307 | if (value == "LOW") { value = lowThresholdvalue } 308 | if (value == "MED") { value = medThresholdvalue } 309 | if (value == "HIGH") { value = highThresholdvalue } 310 | 311 | 312 | def level = Math.min(value as Integer, 99) 313 | def dimmingDuration = duration < 128 ? duration : 128 + Math.round(duration / 60) 314 | 315 | if (level == lowThresholdvalue) { sendEvent(name: "currentState", value: "ADJUSTING.LOW" as String, displayed: false) } 316 | else if (level == medThresholdvalue) { sendEvent(name: "currentState", value: "ADJUSTING.MED" as String, displayed: false) } 317 | else if (level == highThresholdvalue) { sendEvent(name: "currentState", value: "ADJUSTING.HIGH" as String, displayed: false) } 318 | else if (level == 0) { sendEvent(name: "currentState", value: "OFF" as String, displayed: false) } 319 | else { sendEvent(name: "currentState", value: "on" as String, displayed: false) } 320 | 321 | 322 | log.trace "setLevelValueDuration: ${value}" 323 | 324 | zwave.switchMultilevelV2.switchMultilevelSet(value: level, dimmingDuration: dimmingDuration).format() 325 | } 326 | 327 | 328 | def levelUp(){ 329 | 330 | 331 | def steppervalue = (settings.stepper != null && settings.stepper != "") ? settings.stepper.toInteger() : 10 332 | int nextLevel = device.currentValue("level") + steppervalue 333 | sendEvent(name: "currentState", value: "default" as String, displayed: false) 334 | 335 | if( nextLevel > 100){ 336 | nextLevel = 100 337 | } 338 | 339 | log.trace "setLevelUp(value): ${level}" 340 | log.debug "Setting dimmer level up to: ${nextLevel}" 341 | delayBetween ([zwave.basicV1.basicSet(value: nextLevel).format(), zwave.switchMultilevelV1.switchMultilevelGet().format()], 1500) 342 | } 343 | 344 | def levelDown(){ 345 | 346 | 347 | def steppervalue = (settings.stepper != null && settings.stepper != "") ? settings.stepper.toInteger() : 10 348 | int nextLevel = device.currentValue("level") - steppervalue 349 | sendEvent(name: "currentState", value: "default" as String, displayed: false) 350 | 351 | if (nextLevel < 0){ 352 | nextLevel = 0 353 | } 354 | 355 | if (nextLevel == 0){ 356 | off() 357 | } 358 | 359 | else 360 | { 361 | log.trace "setLevelDown(value): ${level}" 362 | log.debug "Setting dimmer level down to: ${nextLevel}" 363 | delayBetween ([zwave.basicV1.basicSet(value: nextLevel).format(), zwave.switchMultilevelV1.switchMultilevelGet().format()], 1500) 364 | } 365 | } 366 | 367 | 368 | def low() { 369 | log.trace "setLow" 370 | setLevel("LOW") 371 | } 372 | 373 | 374 | def med() { 375 | log.trace "setMed" 376 | setLevel("MED") 377 | } 378 | 379 | 380 | def high() { 381 | log.trace "setHigh" 382 | setLevel("HIGH") 383 | } 384 | 385 | 386 | def poll() { 387 | zwave.switchMultilevelV1.switchMultilevelGet().format() 388 | } 389 | 390 | 391 | def refresh() { 392 | zwave.switchMultilevelV1.switchMultilevelGet().format() 393 | } 394 | 395 | 396 | /*def indicatorWhenOn() { 397 | sendEvent(name: "indicatorStatus", value: "when on", display: false) 398 | zwave.configurationV1.configurationSet(configurationValue: [1], parameterNumber: 3, size: 1).format() 399 | }*/ 400 | 401 | 402 | /*def indicatorWhenOff() { 403 | sendEvent(name: "indicatorStatus", value: "when off", display: false) 404 | zwave.configurationV1.configurationSet(configurationValue: [0], parameterNumber: 3, size: 1).format() 405 | }*/ 406 | 407 | 408 | /*def indicatorNever() { 409 | sendEvent(name: "indicatorStatus", value: "never", display: false) 410 | zwave.configurationV1.configurationSet(configurationValue: [2], parameterNumber: 3, size: 1).format() 411 | }*/ -------------------------------------------------------------------------------- /smartapps/README.md: -------------------------------------------------------------------------------- 1 | SmartApps 2 | ========= 3 | 4 | All of my [SmartThings](http://www.smartthings.com/) [SmartApps](http://docs.smartthings.com/en/latest/smartapp-developers-guide/index.html). 5 | -------------------------------------------------------------------------------- /smartapps/device-poll/README.md: -------------------------------------------------------------------------------- 1 | Device Poll 2 | =========== 3 | 4 | Poll particular devices to make sure readings are up to date. 5 | 6 | This is taken directly from [Stever Herrell's](mailto:steve.herrell@gmail.com) [post](http://community.smartthings.com/t/polling-not-working-for-custom-device-type/2787/14). 7 | 8 | -------------------------------------------------------------------------------- /smartapps/device-poll/device-poll.app.groovy: -------------------------------------------------------------------------------- 1 | /** 2 | * Device Poll 3 | * 4 | * Copyright 2014 Steve Herrell 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 7 | * in compliance with the License. You may obtain a copy of the License at: 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 12 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License 13 | * for the specific language governing permissions and limitations under the License. 14 | * 15 | */ 16 | definition( 17 | name: "Device Poll", 18 | namespace: "steve.herrell@gmail.com", 19 | author: "Steve Herrell", 20 | description: "Poll particular devices to make sure readings are up to date.", 21 | category: "Convenience", 22 | iconUrl: "https://s3.amazonaws.com/smartapp-icons/Meta/window_contact.png", 23 | iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Meta/window_contact@2x.png" 24 | ) 25 | 26 | preferences { 27 | section("Devices To Poll") { 28 | input "pollingDevices", "capability.polling", multiple: true, title: "Pollable Devices" 29 | } 30 | section("Polling Interval (defaults to 15 min)") { 31 | input "pollingInterval", "decimal", title: "Number of minutes", required: false 32 | } 33 | } 34 | 35 | def installed() { 36 | log.debug "Installed with settings: ${settings}" 37 | 38 | doPoll() 39 | } 40 | 41 | def updated() { 42 | log.debug "Updated with settings: ${settings}" 43 | 44 | doPoll() 45 | } 46 | 47 | def doPoll() { 48 | 49 | log.debug "running doPoll" 50 | 51 | pollingDevices.poll() 52 | 53 | def timeOut = (pollingInterval != null && pollingInterval != "") ? pollingInterval : 15 54 | 55 | runIn( timeOut * 60, "doPoll",) 56 | } 57 | -------------------------------------------------------------------------------- /smartapps/nicholaswilde/samsung-tv-remote.src/Readme.md: -------------------------------------------------------------------------------- 1 | Samsung TV Remote 2 | ================= 3 | 4 | Control Samsung Smart TVs using SmartThings 5 | 6 | Based on [Brad Butner's](https://github.com/bradmb/) [SmartThings Samsung TV Remote](https://github.com/bradmb/SmartThings-Samsung-TV-Remote) 7 | 8 | Commands taken from http://forum.samygo.tv/viewtopic.php?f=12&t=1792 9 | 10 | This currently only allows you to trigger actions on your TV if you press the app or trigger it 11 | with a virtual [momentary button tile](https://github.com/nicholaswilde/smartthings/blob/master/device-types/momentary-button-tile/momentary-button-tile.device.groovy). 12 | -------------------------------------------------------------------------------- /smartapps/nicholaswilde/samsung-tv-remote.src/samsung-tv-remote.groovy: -------------------------------------------------------------------------------- 1 | /** 2 | * Samsung TV Remote 3 | * 4 | * Author: Nicholas Wilde 5 | * Date: 2014-08-31 6 | * 7 | * Based on Brad Butner's SmartThings Samsung TV Remote 8 | * 9 | * Commands taken from http://forum.samygo.tv/viewtopic.php?f=12&t=1792 10 | * 11 | * This currently only allows you to trigger actions on your TV if you press the app or trigger it 12 | * with a virtual button 13 | * 14 | */ 15 | 16 | // Automatically generated. Make future change here. 17 | definition( 18 | name: "Samsung TV Remote", 19 | namespace: "nicholaswilde", 20 | author: "Nicholas Wilde", 21 | description: "Control Samsung Smart TVs using SmartThings", 22 | categorhy: "SmartThings Labs", 23 | iconUrl: "http://icons.iconarchive.com/icons/icons8/windows-8/512/Network-Remote-Control-icon.png", 24 | iconX2Url: "http://icons.iconarchive.com/icons/icons8/windows-8/512/Network-Remote-Control-icon.png") 25 | 26 | preferences { 27 | section("Samsung TV Settings") { 28 | def commands = [ 29 | "UP", 30 | "DOWN", 31 | "LEFT", 32 | "RIGHT", 33 | "3", 34 | "VOLUP", 35 | "4", 36 | "5", 37 | "6", 38 | "VOLDOWN", 39 | "7", 40 | "8", 41 | "9", 42 | "MUTE", 43 | "CHDOWN", 44 | "0", 45 | "CHUP", 46 | "PRECH", 47 | "GREEN", 48 | "YELLOW", 49 | "CYAN", 50 | "ADDDEL", 51 | "SOURCE", 52 | "INFO", 53 | "PIP_ONOFF", 54 | "PIP_SWAP", 55 | "PLUS100", 56 | "CAPTION", 57 | "PMODE", 58 | "TTX_MIX", 59 | "TV", 60 | "PICTURE_SIZE", 61 | "AD", 62 | "PIP_SIZE", 63 | "MAGIC_CHANNEL", 64 | "PIP_SCAN", 65 | "PIP_CHUP", 66 | "PIP_CHDOWN", 67 | "DEVICE_CONNECT", 68 | "HELP", 69 | "ANTENA", 70 | "CONVERGENCE", 71 | "11", 72 | "12", 73 | "AUTO_PROGRAM", 74 | "FACTORY", 75 | "3SPEED", 76 | "RSURF", 77 | "ASPECT", 78 | "TOPMENU", 79 | "GAME", 80 | "QUICK_REPLAY", 81 | "STILL_PICTURE", 82 | "DTV", 83 | "FAVCH", 84 | "REWIND", 85 | "STOP", 86 | "PLAY", 87 | "FF", 88 | "REC", 89 | "PAUSE", 90 | "TOOLS", 91 | "INSTANT_REPLAY", 92 | "LINK", 93 | "FF_", 94 | "GUIDE", 95 | "REWIND_", 96 | "ANGLE", 97 | "RESERVED1", 98 | "ZOOM1", 99 | "PROGRAM", 100 | "BOOKMARK", 101 | "DISC_MENU", 102 | "PRINT", 103 | "RETURN", 104 | "SUB_TITLE", 105 | "CLEAR", 106 | "VCHIP", 107 | "REPEAT", 108 | "DOOR", 109 | "OPEN", 110 | "WHEEL_LEFT", 111 | "POWER", 112 | "SLEEP", 113 | "2", 114 | "DMA", 115 | "TURBO", 116 | "1", 117 | "FM_RADIO", 118 | "DVR_MENU", 119 | "MTS", 120 | "PCMODE", 121 | "TTX_SUBFACE", 122 | "CH_LIST", 123 | "RED", 124 | "DNIe", 125 | "SRS", 126 | "CONVERT_AUDIO_MAINSUB", 127 | "MDC", 128 | "SEFFECT", 129 | "DVR", 130 | "DTV_SIGNAL", 131 | "LIVE", 132 | "PERPECT_FOCUS", 133 | "HOME", 134 | "ESAVING", 135 | "WHEEL_RIGHT", 136 | "CONTENTS", 137 | "VCR_MODE", 138 | "CATV_MODE", 139 | "DSS_MODE", 140 | "TV_MODE", 141 | "DVD_MODE", 142 | "STB_MODE", 143 | "CALLER_ID", 144 | "SCALE", 145 | "ZOOM_MOVE", 146 | "CLOCK_DISPLAY", 147 | "AV1", 148 | "SVIDEO1", 149 | "COMPONENT1", 150 | "SETUP_CLOCK_TIMER", 151 | "COMPONENT2", 152 | "MAGIC_BRIGHT", 153 | "DVI", 154 | "HDMI", 155 | "W_LINK", 156 | "DTV_LINK", 157 | "APP_LIST", 158 | "BACK_MHP", 159 | "ALT_MHP", 160 | "DNSe", 161 | "RSS", 162 | "ENTERTAINMENT", 163 | "ID_INPUT", 164 | "ID_SETUP", 165 | "ANYNET", 166 | "POWEROFF", 167 | "POWERON", 168 | "ANYVIEW", 169 | "MS", 170 | "MORE", 171 | "PANNEL_POWER", 172 | "PANNEL_CHUP", 173 | "PANNEL_CHDOWN", 174 | "PANNEL_VOLUP", 175 | "PANNEL_VOLDOW", 176 | "PANNEL_ENTER", 177 | "PANNEL_MENU", 178 | "PANNEL_SOURCE", 179 | "AV2", 180 | "AV3", 181 | "SVIDEO2", 182 | "SVIDEO3", 183 | "ZOOM2", 184 | "PANORAMA", 185 | "4_3", 186 | "16_9", 187 | "DYNAMIC", 188 | "STANDARD", 189 | "MOVIE1", 190 | "CUSTOM", 191 | "AUTO_ARC_RESET", 192 | "AUTO_ARC_LNA_ON", 193 | "AUTO_ARC_LNA_OFF", 194 | "AUTO_ARC_ANYNET_MODE_OK", 195 | "AUTO_ARC_ANYNET_AUTO_START", 196 | "AUTO_FORMAT", 197 | "DNET", 198 | "HDMI1", 199 | "AUTO_ARC_CAPTION_ON", 200 | "AUTO_ARC_CAPTION_OFF", 201 | "AUTO_ARC_PIP_DOUBLE", 202 | "AUTO_ARC_PIP_LARGE", 203 | "AUTO_ARC_PIP_SMALL", 204 | "AUTO_ARC_PIP_WIDE", 205 | "AUTO_ARC_PIP_LEFT_TOP", 206 | "AUTO_ARC_PIP_RIGHT_TOP", 207 | "AUTO_ARC_PIP_LEFT_BOTTOM", 208 | "AUTO_ARC_PIP_RIGHT_BOTTOM", 209 | "AUTO_ARC_PIP_CH_CHANGE", 210 | "AUTO_ARC_AUTOCOLOR_SUCCESS", 211 | "AUTO_ARC_AUTOCOLOR_FAIL", 212 | "AUTO_ARC_C_FORCE_AGING", 213 | "AUTO_ARC_USBJACK_INSPECT", 214 | "AUTO_ARC_JACK_IDENT", 215 | "NINE_SEPERATE", 216 | "ZOOM_IN", 217 | "ZOOM_OUT", 218 | "MIC", 219 | "HDMI2", 220 | "HDMI3", 221 | "AUTO_ARC_CAPTION_KOR", 222 | "AUTO_ARC_CAPTION_ENG", 223 | "AUTO_ARC_PIP_SOURCE_CHANGE", 224 | "HDMI4", 225 | "AUTO_ARC_ANTENNA_AIR", 226 | "AUTO_ARC_ANTENNA_CABLE", 227 | "AUTO_ARC_ANTENNA_SATELLITE", 228 | "EXT1", 229 | "EXT2", 230 | "EXT3", 231 | "EXT4", 232 | "EXT5", 233 | "EXT6", 234 | "EXT7", 235 | "EXT8", 236 | "EXT9", 237 | "EXT10", 238 | "EXT11", 239 | "EXT12", 240 | "EXT13", 241 | "EXT14", 242 | "EXT15", 243 | "EXT16", 244 | "EXT17", 245 | "EXT18", 246 | "EXT19", 247 | "EXT20", 248 | "EXT21", 249 | "EXT22", 250 | "EXT23", 251 | "EXT24", 252 | "EXT25", 253 | "EXT26", 254 | "EXT27", 255 | "EXT28", 256 | "EXT29", 257 | "EXT30", 258 | "EXT31", 259 | "EXT32", 260 | "EXT33", 261 | "EXT34", 262 | "EXT35", 263 | "EXT36", 264 | "EXT37", 265 | "EXT38", 266 | "EXT39", 267 | "EXT40", 268 | "EXT41" 269 | ] 270 | commands.sort() 271 | input "settingIpAddress", "text", title: "IP Address", description: "192.168.1.100", required: true 272 | input "settingMacAddress", "text", title: "MAC Address",description: "01:23:45:67:89:ab", required: true 273 | input "tvCommand", "enum", title: "Perform This Command", metadata:[values: commands], required: true 274 | } 275 | section("When this switch is turned pushed") { 276 | input "master", "capability.switch", title: "Which?", required: true 277 | } 278 | } 279 | 280 | def installed() { 281 | log.debug "Installed with settings: ${settings}" 282 | initialize() 283 | } 284 | 285 | def updated() { 286 | log.debug "Updated with settings: ${settings}" 287 | unsubscribe() 288 | initialize() 289 | } 290 | 291 | def initialize() { 292 | subscribe(app, appTouch) 293 | subscribe(master, "switch.on", switchHandler) 294 | tvAction("AUTHENTICATE") 295 | } 296 | 297 | def appTouch(evt) { 298 | log.debug "appTouch: $evt" 299 | tvAction(tvCommand) 300 | } 301 | 302 | def switchHandler(evt) { 303 | log.debug "${master} switch turned ${evt}" 304 | tvAction(tvCommand) 305 | } 306 | 307 | private tvAction(key) { 308 | log.debug "Executing ${tvCommand}" 309 | 310 | // Standard Connection Data 311 | def appString = "iphone..iapp.samsung" 312 | def appStringLength = appString.getBytes().size() 313 | 314 | def tvAppString = "iphone.UN60ES8000.iapp.samsung" 315 | def tvAppStringLength = tvAppString.getBytes().size() 316 | 317 | def remoteName = "SmartThings".encodeAsBase64().toString() 318 | def remoteNameLength = remoteName.getBytes().size() 319 | 320 | // Device Connection Data 321 | def ipAddress = settingIpAddress.encodeAsBase64().toString() 322 | def ipAddressLength = ipAddress.getBytes().size() 323 | def ipAddressHex = settingIpAddress.tokenize( '.' ).collect { String.format( '%02x', it.toInteger() ) }.join() 324 | log.debug "IP Address (HEX): ${ipAddressHex}" 325 | 326 | def macAddress = settingMacAddress.replaceAll(":","").encodeAsBase64().toString() 327 | def macAddressLength = macAddress.getBytes().size() 328 | 329 | // The Authentication Message 330 | def authenticationMessage = "${(char)0x64}${(char)0x00}${(char)ipAddressLength}${(char)0x00}${ipAddress}${(char)macAddressLength}${(char)0x00}${macAddress}${(char)remoteNameLength}${(char)0x00}${remoteName}" 331 | def authenticationMessageLength = authenticationMessage.getBytes().size() 332 | 333 | def authenticationPacket = "${(char)0x00}${(char)appStringLength}${(char)0x00}${appString}${(char)authenticationMessageLength}${(char)0x00}${authenticationMessage}" 334 | 335 | // If our initial run, just send the authentication packet so the prompt appears on screen 336 | if (key == "AUTHENTICATE") { 337 | sendHubCommand(new physicalgraph.device.HubAction(authenticationPacket, physicalgraph.device.Protocol.LAN, "${ipAddressHex}:D6D8")) 338 | } else { 339 | // Build the command we will send to the Samsung TV 340 | def command = "KEY_${key}".encodeAsBase64().toString() 341 | def commandLength = command.getBytes().size() 342 | 343 | def actionMessage = "${(char)0x00}${(char)0x00}${(char)0x00}${(char)commandLength}${(char)0x00}${command}" 344 | def actionMessageLength = actionMessage.getBytes().size() 345 | 346 | def actionPacket = "${(char)0x00}${(char)tvAppStringLength}${(char)0x00}${tvAppString}${(char)actionMessageLength}${(char)0x00}${actionMessage}" 347 | 348 | // Send both the authentication and action at the same time 349 | sendHubCommand(new physicalgraph.device.HubAction(authenticationPacket + actionPacket, physicalgraph.device.Protocol.LAN, "${ipAddressHex}:D6D8")) 350 | } 351 | } -------------------------------------------------------------------------------- /smartapps/notify-when-on-for/README.md: -------------------------------------------------------------------------------- 1 | Notify When On For 2 | ================== 3 | 4 | Notify after switch has been on for X minutes. If the switch/outlet is powered off prior to the 'time out,' then the scheduled time off is cancelled. 5 | -------------------------------------------------------------------------------- /smartapps/notify-when-on-for/notify-when-on-for.app.groovy: -------------------------------------------------------------------------------- 1 | /** 2 | * Notify When On For 3 | * 4 | * Author: Nicholas Wilde 5 | * 6 | * Based on Improved Power Allowance by chrisb 7 | * 8 | * This program sends a notification instead of turning off a switch. 9 | */ 10 | 11 | // Automatically generated. Make future change here. 12 | definition( 13 | name: "Notify When On For", 14 | namespace: "nicholaswilde/smartthings", 15 | author: "Nicholas Wilde", 16 | description: "Notify after switch has been on for X minutes. If the switch/outlet is powered off prior to the 'time out,' then the scheduled time off is cancelled.", 17 | category: "Green Living", 18 | iconUrl: "https://s3.amazonaws.com/smartapp-icons/Meta/window_contact.png", 19 | iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Meta/window_contact@2x.png" 20 | ) 21 | 22 | preferences { 23 | section("When a switch turns on...") { 24 | input "theSwitch", "capability.switch" 25 | } 26 | section("Notify how many minutes later? (optional, default 15 minutes)") { 27 | input "minutesLater", "number", title: "When?", required: false 28 | } 29 | section("Send this message (optional, sends standard status message if not specified)"){ 30 | input "messageText", "text", title: "Message Text", required: false 31 | } 32 | section("Via a push notification and/or an SMS message"){ 33 | input "phone", "phone", title: "Phone Number (for SMS, optional)", required: false 34 | input "pushAndPhone", "enum", title: "Both Push and SMS?", required: false, metadata: [values: ["Yes","No"]] 35 | } 36 | } 37 | 38 | def installed() { 39 | log.debug "Installed with settings: ${settings}" 40 | subscribe(theSwitch, "switch.on", switchOnHandler, [filterEvents: true]) 41 | } 42 | 43 | def updated() { 44 | log.debug "Updated with settings: ${settings}" 45 | unsubscribe() 46 | subscribe(theSwitch, "switch.on", switchOnHandler, [filterEvents: true]) 47 | } 48 | 49 | def switchOnHandler(evt) { 50 | log.debug "Switch ${theSwitch} turned: ${evt.value}" 51 | def minutesLater = minutesLater ? minutesLater: 15 52 | def delay = minutesLater * 60 53 | log.debug "Sending notification in ${minutesLater} minutes (${delay}s)" 54 | runIn(delay, sendMessage) 55 | } 56 | 57 | def sendMessage() { 58 | def msg = messageText ?: "${theSwitch} has been on for ${minutesLater} minutes." 59 | if (theSwitch.currentValue("switch") == "on"){ 60 | if (!phone || pushAndPhone != "No") { 61 | log.debug "sending push" 62 | sendPush(msg) 63 | } 64 | if (phone) { 65 | log.debug "sending SMS" 66 | sendSms(phone, msg) 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /smartapps/thingspeak-logger-ii/README.md: -------------------------------------------------------------------------------- 1 | ThingSpeak Logger II 2 | ==================== 3 | 4 | Log events to [ThingSpeak](https://thingspeak.com/). 5 | 6 | Based on *ThingSpeak Logger* by [florianz](http://community.smartthings.com/users/florianz/activity). 7 | -------------------------------------------------------------------------------- /smartapps/thingspeak-logger-ii/thingspeak-logger-ii.app.groovy: -------------------------------------------------------------------------------- 1 | /** 2 | * ThingSpeak Logger II 3 | * 4 | * Based on "ThingSpeak Logger by florianz 5 | * 6 | * Author: Nicholas Wilde 7 | * Date: 2014-07-08 8 | * 9 | * Changes from florianz's ThingSpeakLogger: 10 | * - Added illuminance & relative humidity sensors 11 | * - Convert device names and map values to lowercase before looking for field number. 12 | * 13 | * Create a ThingSpeak channel with a write key. The app must be given the channel id and key. 14 | * Then, create a field for each device and name the field according to the label given to the 15 | * device in SmartThings. 16 | * 17 | */ 18 | 19 | // Automatically generated. Make future change here. 20 | definition( 21 | name: "ThingSpeak Logger II", 22 | namespace: "nicholaswilde/smartthings", 23 | author: "Nicholas Wilde", 24 | description: "Log events to ThingSpeak", 25 | category: "Convenience", 26 | iconUrl: "https://lh6.googleusercontent.com/-LCt2nG3Npbo/AAAAAAAAAAI/AAAAAAAAAB8/O6sWrun4q6w/s50-c/photo.jpg", 27 | iconX2Url: "https://lh6.googleusercontent.com/-LCt2nG3Npbo/AAAAAAAAAAI/AAAAAAAAAB8/O6sWrun4q6w/s100-c/photo.jpg") 28 | 29 | preferences { 30 | section("Log devices...") { 31 | input "illuminants", "capability.illuminanceMeasurement", title: "Illuminants", required:false, multiple: true 32 | input "humidities", "capability.relativeHumidityMeasurement", title: "Relative Humidities", required:false, multiple: true 33 | input "temperatures", "capability.temperatureMeasurement", title: "Temperatures", required:false, multiple: true 34 | input "contacts", "capability.contactSensor", title: "Contacts", required: false, multiple: true 35 | input "accelerations", "capability.accelerationSensor", title: "Accelerations", required: false, multiple: true 36 | input "motions", "capability.motionSensor", title: "Motions", required: false, multiple: true 37 | input "switches", "capability.switch", title: "Switches", required: false, multiple: true 38 | } 39 | 40 | section ("ThinkSpeak channel id...") { 41 | input "channelId", "number", title: "Channel ID" 42 | } 43 | 44 | section ("ThinkSpeak write key...") { 45 | input "channelKey", "password", title: "Channel Key" 46 | } 47 | } 48 | 49 | def installed() { 50 | initialize() 51 | } 52 | 53 | def updated() { 54 | unsubscribe() 55 | initialize() 56 | } 57 | 58 | def initialize() { 59 | subscribe(illuminants, "illuminance", handleIlluminanceEvent) 60 | subscribe(humidities, "humidity", handleHumidityEvent) 61 | subscribe(temperatures, "temperature", handleTemperatureEvent) 62 | subscribe(contacts, "contact", handleContactEvent) 63 | subscribe(accelerations, "acceleration", handleAccelerationEvent) 64 | subscribe(motions, "motion", handleMotionEvent) 65 | subscribe(switches, "switch", handleSwitchEvent) 66 | 67 | updateChannelInfo() 68 | log.debug state.fieldMap 69 | } 70 | 71 | def handleIlluminanceEvent(evt) { 72 | logField(evt) { it.toString() } 73 | } 74 | 75 | def handleHumidityEvent(evt) { 76 | logField(evt) { it.toString() } 77 | } 78 | 79 | def handleTemperatureEvent(evt) { 80 | logField(evt) { it.toString() } 81 | } 82 | 83 | def handleContactEvent(evt) { 84 | logField(evt) { it == "open" ? "1" : "0" } 85 | } 86 | 87 | def handleAccelerationEvent(evt) { 88 | logField(evt) { it == "active" ? "1" : "0" } 89 | } 90 | 91 | def handleMotionEvent(evt) { 92 | logField(evt) { it == "active" ? "1" : "0" } 93 | } 94 | 95 | def handleSwitchEvent(evt) { 96 | logField(evt) { it == "on" ? "1" : "0" } 97 | } 98 | 99 | private getFieldMap(channelInfo) { 100 | def fieldMap = [:] 101 | channelInfo?.findAll { it.key?.startsWith("field") }.each { fieldMap[it.value?.trim().toLowerCase()] = it.key } 102 | return fieldMap 103 | } 104 | 105 | private updateChannelInfo() { 106 | log.debug "Retrieving channel info for ${channelId}" 107 | 108 | def url = "http://api.thingspeak.com/channels/${channelId}/feed.json?key=${channelKey}&results=0" 109 | httpGet(url) { 110 | response -> 111 | if (response.status != 200 ) { 112 | log.debug "ThingSpeak data retrieval failed, status = ${response.status}" 113 | } else { 114 | log.debug "ThingSpeak data retrieval successful, status = ${response.status}" 115 | state.channelInfo = response.data?.channel 116 | } 117 | } 118 | 119 | state.fieldMap = getFieldMap(state.channelInfo) 120 | } 121 | 122 | private logField(evt, Closure c) { 123 | def deviceName = evt.displayName.trim().toLowerCase() 124 | log.debug state.fieldMap 125 | def fieldNum = state.fieldMap[deviceName] 126 | if (!fieldNum) { 127 | log.debug "Device '${deviceName}' has no field" 128 | return 129 | } 130 | 131 | def value = c(evt.value) 132 | log.debug "Logging to channel ${channelId}, ${fieldNum}, value ${value}" 133 | 134 | def url = "http://api.thingspeak.com/update?key=${channelKey}&${fieldNum}=${value}" 135 | httpGet(url) { 136 | response -> 137 | if (response.status != 200 ) { 138 | log.debug "ThingSpeak logging failed, status = ${response.status}" 139 | } else { 140 | log.debug "ThingSpeak logging successful, status = ${response.status}" 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /smartapps/thingspeak-logger-ii/thingspeak.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicholaswilde/smartthings/250cbde85d63c7819a0cebed48bb538e0955b7e9/smartapps/thingspeak-logger-ii/thingspeak.png -------------------------------------------------------------------------------- /smartapps/thingspeak-logger-ii/thingspeak@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicholaswilde/smartthings/250cbde85d63c7819a0cebed48bb538e0955b7e9/smartapps/thingspeak-logger-ii/thingspeak@2x.png --------------------------------------------------------------------------------