├── DeviceType_ms_w_vts.groovy ├── DigitalLoggersWebPowerSwitch7 ├── LICENSE ├── README.md ├── RaspberryPi2 ├── SmartApp_DLI_Web_Power_Switch_with_Virtual_Tiles ├── SmartApp_Powerstrip.groovy ├── SmartApp_SuperState.groovy ├── devicetypes ├── ashishagrawal │ └── foscam-universal-device-v3.src │ │ └── foscam-universal-device-v3.groovy ├── cmonroe │ └── everspring-st814.src │ │ └── everspring-st814.groovy ├── ledridge │ ├── foscam-hd.src │ │ └── foscam-hd.groovy │ ├── my-garage-door-opener.src │ │ └── my-garage-door-opener.groovy │ ├── raspberry-pi.src │ │ └── raspberry-pi.groovy │ ├── virtual-motion-detector.src │ │ └── virtual-motion-detector.groovy │ ├── vtile-dli.src │ │ └── vtile-dli.groovy │ ├── web-power-switch.src │ │ └── web-power-switch.groovy │ └── wifi-370-led-strip-controller.src │ │ └── wifi-370-led-strip-controller.groovy ├── mmaxwell │ └── remoteczfm80.src │ │ └── remoteczfm80.groovy ├── ms-w-vts │ └── vtile-ms.src │ │ └── vtile-ms.groovy ├── mujica │ └── dlna-player.src │ │ └── dlna-player.groovy ├── skp19 │ └── foscam-universal-device.src │ │ └── foscam-universal-device.groovy └── superuser │ ├── aeon-smart-strip-1.src │ └── aeon-smart-strip-1.groovy │ ├── aeon-smart-strip-2.src │ └── aeon-smart-strip-2.groovy │ └── poolswitch.src │ └── poolswitch.groovy └── vTile_DLI /DeviceType_ms_w_vts.groovy: -------------------------------------------------------------------------------- 1 | /** 2 | * V Tile device-type for ms_w_vts 3 | * 4 | * Needed for Multi Switch with Virtual Tiles to create virtual switch tiles in ST for devices that have multiple "switch[x]" 5 | * attributes within them and have on[x] and off[x] commands for each (fairly common device-types) 6 | * Also has support for device-label inside the name when on or off and polling occurs 7 | * Copyright 2014 Cooper Lee 8 | * 9 | */ 10 | metadata { 11 | definition (name: "vTile_ms", namespace: "ms_w_vts", author: "Cooper Lee") { 12 | capability "Switch" 13 | capability "relaySwitch" 14 | capability "Polling" 15 | capability "Refresh" 16 | 17 | attribute "lastOn", "string" 18 | attribute "lastOff", "string" 19 | } 20 | } 21 | 22 | preferences { 23 | tiles { 24 | standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) { 25 | state "name", label: '${currentValue}', action: "switch.on", icon: "http://cdn.flaticon.com/png/256/56724.png", backgroundColor: "#DDDDff", nextState: "turningOn" 26 | state "off", label: 'off', action: "switch.on", icon: "http://cdn.flaticon.com/png/256/56724.png", backgroundColor: "#DDDDff", nextState: "turningOn" 27 | state "on", label: 'on', action: "switch.off", icon: "http://cdn.flaticon.com/png/256/56724.png", backgroundColor: "#0088ff", nextState: "turningOff" 28 | state "turningOff", iconLabel:"http://cdn.flaticon.com/png/256/56413.png" , icon: "http://cdn.flaticon.com/png/256/56724.png", backgroundColor: "#FA5882", nextState: "on" 29 | state "turningOn", iconLabel:"http://cdn.flaticon.com/png/256/56498.png" , icon: "http://cdn.flaticon.com/png/256/56724.png", backgroundColor: "#F3F781", nextState: "off" 30 | } 31 | 32 | valueTile("lastOn", "device.lastOn", inactiveLabel: false, width: 3, height: 1, canChangeIcon: false, decoration:"flat") { 33 | state "default", label: 'Last On: ${currentValue}'} 34 | 35 | valueTile("lastOff", "device.lastOff", inactiveLabel: false, width: 3, height: 1, canChangeIcon: false, decoration:"flat") { 36 | state "default", label: 'Last Off: ${currentValue}'} 37 | 38 | 39 | main "switch" 40 | details(["switch", "lastOn", "lastOff"]) 41 | } 42 | } 43 | 44 | def parse(desc) { 45 | def results = [] 46 | if(desc=="updated") { log.debug "Device $device.label has been UPDATED"; poll() } 47 | } 48 | 49 | def on() { 50 | sendEvent([name: "switch", value: "on"]) 51 | parent.on(this) 52 | sendEvent([name: "lastOn", value: "${df(now())}"]) 53 | log.debug "$device.label is On" 54 | } 55 | 56 | def off() { 57 | sendEvent([name: "switch", value: "off"]) 58 | parent.off(this) 59 | sendEvent([name: "switch", value: "$device.label"]) 60 | sendEvent([name: "lastOff", value: "${df(now())}"]) 61 | log.debug "$device.label is Off" 62 | } 63 | 64 | def poll() { 65 | def current = device.currentValue("switch") 66 | log.debug "Polling - $device.label is $current" 67 | if(!current || current=="off") { sendEvent(name:"switch", value:"$device.label", isStateChange:true, displayed:false) } 68 | } 69 | 70 | def df(e) { 71 | // * df(e) - Date Format "E" 72 | // * Takes epoch time format and returns Date formatted in current timezone 73 | // * Copyright 2014 Cooper Lee 74 | def locale = getWeatherFeature("geolookup", zip); def tz = TimeZone.getTimeZone(locale.location.tz_long); def formatted 75 | if(e) { formatted = new Date(e).format("EEE, MMM d, 'at' hh:mm aaa", tz); return formatted } 76 | } 77 | -------------------------------------------------------------------------------- /DigitalLoggersWebPowerSwitch7: -------------------------------------------------------------------------------- 1 | /** 2 | * Web Power Switch 7 3 | */ 4 | 5 | import com.google.common.base.Splitter; 6 | import java.util.List; 7 | import java.util.Map; 8 | import java.util.Map.Entry; 9 | 10 | 11 | preferences { 12 | input("ip", "string", title:"IP Address", description: "192.168.1.3", defaultValue: "192.168.1.3" ,required: true, displayDuringSetup: true) 13 | input("port", "string", title:"Port", description: "80", defaultValue: "80" , required: true, displayDuringSetup: true) 14 | input("username", "string", title:"Username", description: "admin", defaultValue: "admin" , required: true, displayDuringSetup: true) 15 | input("password", "password", title:"Password", description: "password", defaultValue: "password" , required: true, displayDuringSetup: true) 16 | 17 | input("port1name", "string", title:"Port1 Name", description: "Name for port", defaultValue: "Port 1" , required: true, displayDuringSetup: true) 18 | input("port2name", "string", title:"Port2 Name", description: "Name for port", defaultValue: "Port 2" , required: true, displayDuringSetup: true) 19 | input("port3name", "string", title:"Port3 Name", description: "Name for port", defaultValue: "Port 3" , required: true, displayDuringSetup: true) 20 | input("port4name", "string", title:"Port4 Name", description: "Name for port", defaultValue: "Port 4" , required: true, displayDuringSetup: true) 21 | input("port5name", "string", title:"Port5 Name", description: "Name for port", defaultValue: "Port 5" , required: true, displayDuringSetup: true) 22 | input("port6name", "string", title:"Port6 Name", description: "Name for port", defaultValue: "Port 6" , required: true, displayDuringSetup: true) 23 | input("port7name", "string", title:"Port7 Name", description: "Name for port", defaultValue: "Port 7" , required: true, displayDuringSetup: true) 24 | input("port8name", "string", title:"Port8 Name", description: "Name for port", defaultValue: "Port 8" , required: true, displayDuringSetup: true) 25 | } 26 | 27 | metadata { 28 | definition (name: "Web Power Switch", namespace: "Ledridge", author: "Ledridge") { 29 | 30 | capability "Polling" 31 | capability "Refresh" 32 | capability "Switch" 33 | 34 | command "OA1ON" 35 | command "OA1OFF" 36 | command "OA1CCL" 37 | 38 | command "OA2ON" 39 | command "OA2OFF" 40 | command "OA2CCL" 41 | 42 | command "OA3ON" 43 | command "OA3OFF" 44 | command "OA3CCL" 45 | 46 | command "OA4ON" 47 | command "OA4OFF" 48 | command "OA4CCL" 49 | 50 | command "OA5ON" 51 | command "OA5OFF" 52 | command "OA5CCL" 53 | 54 | command "OA6ON" 55 | command "OA6OFF" 56 | command "OA6CCL" 57 | 58 | command "OA7ON" 59 | command "OA7OFF" 60 | command "OA7CCL" 61 | 62 | command "OA8ON" 63 | command "OA8OFF" 64 | command "OA8CCL" 65 | 66 | command "OutletAction", ["number", "string"] 67 | command "OutletStatus", ["number"] 68 | command "OutletName", ["number"] 69 | } 70 | 71 | simulator { 72 | // TODO: define status and reply messages here 73 | } 74 | 75 | tiles { 76 | 77 | valueTile("Label1", "device.Label1", decoration: "flat") {state "default", label:'${currentValue}'} 78 | standardTile("Outlet1", "device.Outlet1", width: 1, height: 1) { 79 | state "off", action: "OA1ON", label: 'Off', backgroundColor: "#ffffff", nextState: "on" 80 | state "on" , action: "OA1OFF", label: 'On', backgroundColor: "#79b821", nextState: "off" 81 | } 82 | standardTile("Cycle1", "device.Cycle1", width: 1, height: 1) { 83 | state "off", action: "OA1CCL", label: 'Off', backgroundColor: "#ffffff", nextState: "on", icon: "st.secondary.refresh" 84 | state "on" , label: 'On', backgroundColor: "#79b821", nextState: "off", icon: "st.secondary.refresh" 85 | } 86 | 87 | valueTile("Label2", "device.Label2", decoration: "flat") {state "default", label:'${currentValue}'} 88 | standardTile("Outlet2", "device.Outlet2", width: 1, height: 1) { 89 | state "off", action: "OA2ON", label: 'Off', backgroundColor: "#ffffff", ne2tState: "on" 90 | state "on" , action: "OA2OFF", label: 'On', backgroundColor: "#79b821", ne2tState: "off" 91 | } 92 | standardTile("Cycle2", "device.Cycle2", width: 1, height: 1) { 93 | state "off", action: "OA2CCL", label: 'Off', backgroundColor: "#ffffff", ne2tState: "on", icon: "st.secondary.refresh" 94 | state "on" , label: 'On', backgroundColor: "#79b821", ne2tState: "off", icon: "st.secondary.refresh" 95 | } 96 | 97 | valueTile("Label3", "device.Label3", decoration: "flat") {state "default", label:'${currentValue}'} 98 | standardTile("Outlet3", "device.Outlet3", width: 1, height: 1) { 99 | state "off", action: "OA3ON", label: 'Off', backgroundColor: "#ffffff", ne3tState: "on" 100 | state "on" , action: "OA3OFF", label: 'On', backgroundColor: "#79b821", ne3tState: "off" 101 | } 102 | standardTile("Cycle3", "device.Cycle3", width: 1, height: 1) { 103 | state "off", action: "OA3CCL", label: 'Off', backgroundColor: "#ffffff", ne3tState: "on", icon: "st.secondary.refresh" 104 | state "on" , label: 'On', backgroundColor: "#79b821", ne3tState: "off", icon: "st.secondary.refresh" 105 | } 106 | 107 | valueTile("Label4", "device.Label4", decoration: "flat") {state "default", label:'${currentValue}'} 108 | standardTile("Outlet4", "device.Outlet4", width: 1, height: 1) { 109 | state "off", action: "OA4ON", label: 'Off', backgroundColor: "#ffffff", ne4tState: "on" 110 | state "on" , action: "OA4OFF", label: 'On', backgroundColor: "#79b821", ne4tState: "off" 111 | } 112 | standardTile("Cycle4", "device.Cycle4", width: 1, height: 1) { 113 | state "off", action: "OA4CCL", label: 'Off', backgroundColor: "#ffffff", ne4tState: "on", icon: "st.secondary.refresh" 114 | state "on" , label: 'On', backgroundColor: "#79b821", ne4tState: "off", icon: "st.secondary.refresh" 115 | } 116 | 117 | valueTile("Label5", "device.Label5", decoration: "flat") {state "default", label:'${currentValue}'} 118 | standardTile("Outlet5", "device.Outlet5", width: 1, height: 1) { 119 | state "off", action: "OA5ON", label: 'Off', backgroundColor: "#ffffff", ne5tState: "on" 120 | state "on" , action: "OA5OFF", label: 'On', backgroundColor: "#79b821", ne5tState: "off" 121 | } 122 | standardTile("Cycle5", "device.Cycle5", width: 1, height: 1) { 123 | state "off", action: "OA5CCL", label: 'Off', backgroundColor: "#ffffff", ne5tState: "on", icon: "st.secondary.refresh" 124 | state "on" , label: 'On', backgroundColor: "#79b821", ne5tState: "off", icon: "st.secondary.refresh" 125 | } 126 | 127 | valueTile("Label6", "device.Label6", decoration: "flat") {state "default", label:'${currentValue}'} 128 | standardTile("Outlet6", "device.Outlet6", width: 1, height: 1) { 129 | state "off", action: "OA6ON", label: 'Off', backgroundColor: "#ffffff", ne6tState: "on" 130 | state "on" , action: "OA6OFF", label: 'On', backgroundColor: "#79b821", ne6tState: "off" 131 | } 132 | standardTile("Cycle6", "device.Cycle6", width: 1, height: 1) { 133 | state "off", action: "OA6CCL", label: 'Off', backgroundColor: "#ffffff", ne6tState: "on", icon: "st.secondary.refresh" 134 | state "on" , label: 'On', backgroundColor: "#79b821", ne6tState: "off", icon: "st.secondary.refresh" 135 | } 136 | 137 | valueTile("Label7", "device.Label7", decoration: "flat") {state "default", label:'${currentValue}'} 138 | standardTile("Outlet7", "device.Outlet7", width: 1, height: 1) { 139 | state "off", action: "OA7ON", label: 'Off', backgroundColor: "#ffffff", ne7tState: "on" 140 | state "on" , action: "OA7OFF", label: 'On', backgroundColor: "#79b821", ne7tState: "off" 141 | } 142 | standardTile("Cycle7", "device.Cycle7", width: 1, height: 1) { 143 | state "off", action: "OA7CCL", label: 'Off', backgroundColor: "#ffffff", ne7tState: "on", icon: "st.secondary.refresh" 144 | state "on" , label: 'On', backgroundColor: "#79b821", ne7tState: "off", icon: "st.secondary.refresh" 145 | } 146 | 147 | valueTile("Label8", "device.Label8", decoration: "flat") {state "default", label:'${currentValue}'} 148 | standardTile("Outlet8", "device.Outlet8", width: 1, height: 1) { 149 | state "off", action: "OA8ON", label: 'Off', backgroundColor: "#ffffff", ne8tState: "on" 150 | state "on" , action: "OA8OFF", label: 'On', backgroundColor: "#79b821", ne8tState: "off" 151 | } 152 | standardTile("Cycle8", "device.Cycle8", width: 1, height: 1) { 153 | state "off", action: "OA8CCL", label: 'Off', backgroundColor: "#ffffff", ne8tState: "on", icon: "st.secondary.refresh" 154 | state "on" , label: 'On', backgroundColor: "#79b821", ne8tState: "off", icon: "st.secondary.refresh" 155 | } 156 | 157 | standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat") { 158 | state "default", action:"refresh.refresh", icon: "st.secondary.refresh" 159 | } 160 | 161 | main "Outlet1" 162 | details([ "Label1", "Outlet1", "Cycle1" 163 | ,"Label2", "Outlet2", "Cycle2" 164 | ,"Label3", "Outlet3", "Cycle3" 165 | ,"Label4", "Outlet4", "Cycle4" 166 | ,"Label5", "Outlet5", "Cycle5" 167 | ,"Label6", "Outlet6", "Cycle6" 168 | ,"Label7", "Outlet7", "Cycle7" 169 | ,"Label8", "Outlet8", "Cycle8", "refresh"]) 170 | } 171 | } 172 | 173 | // ------------------------------------------------------------------ 174 | 175 | // parse events into attributes 176 | def parse(String description) { 177 | def map = [:] 178 | def descMap = parseDescriptionAsMap(description) 179 | //log.debug "descMap: ${descMap}" 180 | 181 | //log.debug description.decodeBase64().substring(50,10) 182 | 183 | def body = new String(descMap["body"].decodeBase64()) 184 | //log.debug "body: ${body}" 185 | 186 | def hexString = body.tokenize() 187 | //log.debug "0: ${hexString[0]}" 188 | //log.debug "1: ${hexString[1]}" 189 | //log.debug "2: ${hexString[2]}" 190 | //log.debug "3: ${hexString[3]}" 191 | //log.debug "4: ${hexString[4]}" 192 | //log.debug "5: ${hexString[5]}" 193 | //log.debug "6: ${hexString[6]}" 194 | //log.debug "7: ${hexString[7]}" 195 | //log.debug "8: ${hexString[8]}" 196 | 197 | if (hexString[1].contains("status")) 198 | { 199 | 200 | def a = hexString[3].toString().replace('id="state">',''); 201 | //log.debug "a: ${a}" 202 | 203 | char[] charArray = a.toCharArray(); 204 | 205 | //log.debug charArray[0]; 206 | //log.debug charArray[1]; 207 | 208 | 209 | def b = "${charArray[0]}${charArray[1]}" 210 | //log.debug "b: ${b}" 211 | 212 | def binaryString = hexToBin(b) 213 | //log.debug "Binary String: ${binaryString}" 214 | 215 | char[] StatusArray = binaryString.toString().toCharArray(); 216 | 217 | //log.debug StatusArray[0]; 218 | //log.debug StatusArray[1]; 219 | //log.debug StatusArray[2]; 220 | //log.debug StatusArray[3]; 221 | //log.debug StatusArray[4]; 222 | //log.debug StatusArray[5]; 223 | //log.debug StatusArray[6]; 224 | //log.debug StatusArray[7]; 225 | 226 | def o5 = device.latestValue('o5') 227 | 228 | if (StatusArray[7] == "0") 229 | sendEvent(name: "Outlet1", value: "off") 230 | else 231 | sendEvent(name: "Outlet1", value: "on") 232 | 233 | if (StatusArray[6] == "0") 234 | sendEvent(name: "Outlet2", value: "off") 235 | else 236 | sendEvent(name: "Outlet2", value: "on") 237 | 238 | if (StatusArray[5] == "0") 239 | sendEvent(name: "Outlet3", value: "off") 240 | else 241 | sendEvent(name: "Outlet3", value: "on") 242 | 243 | if (StatusArray[4] == "0") 244 | sendEvent(name: "Outlet4", value: "off") 245 | else 246 | sendEvent(name: "Outlet4", value: "on") 247 | 248 | if (StatusArray[3] == "0") 249 | sendEvent(name: "Outlet5", value: "off") 250 | else 251 | sendEvent(name: "Outlet5", value: "on") 252 | 253 | if (StatusArray[2] == "0") 254 | sendEvent(name: "Outlet6", value: "off") 255 | else 256 | sendEvent(name: "Outlet6", value: "on") 257 | 258 | if (StatusArray[1] == "0") 259 | sendEvent(name: "Outlet7", value: "off") 260 | else 261 | sendEvent(name: "Outlet7", value: "on") 262 | 263 | if (StatusArray[0] == "0") 264 | sendEvent(name: "Outlet8", value: "off") 265 | else 266 | sendEvent(name: "Outlet8", value: "on") 267 | 268 | } 269 | else 270 | log.debug "not status" 271 | 272 | 273 | //log.debug "Label1: ${port1name}" 274 | sendEvent(name: "Label1", value: port1name) 275 | 276 | //log.debug "Label2: ${port2name}" 277 | sendEvent(name: "Label2", value: port2name) 278 | 279 | //log.debug "Label3: ${port3name}" 280 | sendEvent(name: "Label3", value: port3name) 281 | 282 | //log.debug "Label4: ${port4name}" 283 | sendEvent(name: "Label4", value: port4name) 284 | 285 | //log.debug "Label5: ${port5name}" 286 | sendEvent(name: "Label5", value: port5name) 287 | 288 | //log.debug "Label6: ${port6name}" 289 | sendEvent(name: "Label6", value: port6name) 290 | 291 | //log.debug "Label7: ${port7name}" 292 | sendEvent(name: "Label7", value: port7name) 293 | 294 | //log.debug "Label8: ${port8name}" 295 | sendEvent(name: "Label8", value: port8name) 296 | 297 | } 298 | 299 | // handle commands 300 | def poll() { 301 | log.debug "Executing 'poll'" 302 | getRemoteData() 303 | } 304 | 305 | def refresh() { 306 | sendEvent(name: "switch", value: "off") 307 | log.debug "Executing 'refresh'" 308 | getRemoteData() 309 | } 310 | 311 | 312 | def OA1ON() {OutletAction(1,"ON")} 313 | def OA1OFF(){OutletAction(1,"OFF")} 314 | def OA1CCL(){OutletAction(1,"CCL")} 315 | 316 | def OA2ON() {OutletAction(2,"ON")} 317 | def OA2OFF(){OutletAction(2,"OFF")} 318 | def OA2CCL(){OutletAction(2,"CCL")} 319 | 320 | def OA3ON() {OutletAction(3,"ON")} 321 | def OA3OFF(){OutletAction(3,"OFF")} 322 | def OA3CCL(){OutletAction(3,"CCL")} 323 | 324 | def OA4ON() {OutletAction(4,"ON")} 325 | def OA4OFF(){OutletAction(4,"OFF")} 326 | def OA4CCL(){OutletAction(4,"CCL")} 327 | 328 | def OA5ON() {OutletAction(5,"ON")} 329 | def OA5OFF(){OutletAction(5,"OFF")} 330 | def OA5CCL(){OutletAction(5,"CCL")} 331 | 332 | def OA6ON() {OutletAction(6,"ON")} 333 | def OA6OFF(){OutletAction(6,"OFF")} 334 | def OA6CCL(){OutletAction(6,"CCL")} 335 | 336 | def OA7ON() {OutletAction(7,"ON")} 337 | def OA7OFF(){OutletAction(7,"OFF")} 338 | def OA7CCL(){OutletAction(7,"CCL")} 339 | 340 | def OA8ON() {OutletAction(8,"ON")} 341 | def OA8OFF(){OutletAction(8,"OFF")} 342 | def OA8CCL(){OutletAction(8,"CCL")} 343 | 344 | 345 | def OutletStatus(outlet){ 346 | //getRemoteData() 347 | 348 | def int o = outlet.toInteger() 349 | 350 | log.debug "Get Outlet ${o} Status" 351 | def outletStatus 352 | switch(o) { 353 | case 1: 354 | outletStatus = device.latestValue('Outlet1'); 355 | break; 356 | case 2: 357 | outletStatus = device.latestValue('Outlet2'); 358 | break; 359 | case 3: 360 | outletStatus = device.latestValue('Outlet3'); 361 | break; 362 | case 4: 363 | outletStatus = device.latestValue('Outlet4'); 364 | break; 365 | case 5: 366 | outletStatus = device.latestValue('Outlet5'); 367 | break; 368 | case 6: 369 | outletStatus = device.latestValue('Outlet6'); 370 | break; 371 | case 7: 372 | outletStatus = device.latestValue('Outlet7'); 373 | break; 374 | case 8: 375 | outletStatus = device.latestValue('Outlet8'); 376 | break; 377 | default: 378 | outletStatus = "Invalid Outlet Number: ${o}" 379 | } 380 | 381 | log.debug "Outlet Status: ${outletStatus}" 382 | return outletStatus; 383 | } 384 | 385 | def OutletName(outlet){ 386 | def int o = outlet.toInteger() 387 | 388 | log.debug "Get Outlet ${o} Name" 389 | def outletName 390 | switch(o) { 391 | case 1: 392 | outletName = port1name 393 | break; 394 | case 2: 395 | outletName = port2name 396 | break; 397 | case 3: 398 | outletName = port3name 399 | break; 400 | case 4: 401 | outletName = port4name 402 | break; 403 | case 5: 404 | outletName = port5name 405 | break; 406 | case 6: 407 | outletName = port6name 408 | break; 409 | case 7: 410 | outletName = port7name 411 | break; 412 | case 8: 413 | outletName = port8name 414 | break; 415 | default: 416 | outletName = "Invalid Outlet Number: ${o}" 417 | } 418 | 419 | log.debug "Outlet Name: ${outletName}" 420 | return outletName; 421 | } 422 | 423 | def OutletAction(outlet,action){ 424 | log.debug "Send Outlet ${outlet.toInteger().toString()} ${action}" 425 | def uri = "/outlet?${outlet.toInteger().toString()}=${action}" 426 | log.debug "URI: ${uri}" 427 | delayBetween([ 428 | postAction(uri), 429 | getRemoteData() 430 | ], 5000); 431 | } 432 | 433 | private getRemoteData() { 434 | def uri = "/status" 435 | postAction(uri) 436 | } 437 | 438 | // ------------------------------------------------------------------ 439 | 440 | private postAction(uri){ 441 | setDeviceNetworkId(ip,port) 442 | 443 | def userpass = encodeCredentials(username, password) 444 | //log.debug("userpass: " + userpass) 445 | 446 | def headers = getHeader(userpass) 447 | //log.debug("headders: " + headers) 448 | 449 | def hubAction = new physicalgraph.device.HubAction( 450 | method: "GET", 451 | path: uri, 452 | headers: headers 453 | ) 454 | log.debug("Executing hubAction on " + getHostAddress()) 455 | //log.debug hubAction 456 | hubAction 457 | } 458 | 459 | // ------------------------------------------------------------------ 460 | // Helper methods 461 | // ------------------------------------------------------------------ 462 | 463 | def parseDescriptionAsMap(description) { 464 | description.split(",").inject([:]) { map, param -> 465 | def nameAndValue = param.split(":") 466 | map += [(nameAndValue[0].trim()):nameAndValue[1].trim()] 467 | } 468 | } 469 | 470 | 471 | def toAscii(s){ 472 | StringBuilder sb = new StringBuilder(); 473 | String ascString = null; 474 | long asciiInt; 475 | for (int i = 0; i < s.length(); i++){ 476 | sb.append((int)s.charAt(i)); 477 | sb.append("|"); 478 | char c = s.charAt(i); 479 | } 480 | ascString = sb.toString(); 481 | asciiInt = Long.parseLong(ascString); 482 | return asciiInt; 483 | } 484 | 485 | private encodeCredentials(username, password){ 486 | log.debug "Encoding credentials" 487 | def userpassascii = "${username}:${password}" 488 | def userpass = "Basic " + userpassascii.encodeAsBase64().toString() 489 | //log.debug "ASCII credentials are ${userpassascii}" 490 | //log.debug "Credentials are ${userpass}" 491 | return userpass 492 | } 493 | 494 | private getHeader(userpass){ 495 | log.debug "Getting headers" 496 | def headers = [:] 497 | headers.put("HOST", getHostAddress()) 498 | headers.put("Authorization", userpass) 499 | //log.debug "Headers are ${headers}" 500 | return headers 501 | } 502 | 503 | private delayAction(long time) { 504 | new physicalgraph.device.HubAction("delay $time") 505 | } 506 | 507 | private setDeviceNetworkId(ip,port){ 508 | def iphex = convertIPtoHex(ip) 509 | def porthex = convertPortToHex(port) 510 | device.deviceNetworkId = "$iphex:$porthex" 511 | log.debug "Device Network Id set to ${iphex}:${porthex}" 512 | } 513 | 514 | private getHostAddress() { 515 | return "${ip}:${port}" 516 | } 517 | 518 | private String convertIPtoHex(ipAddress) { 519 | String hex = ipAddress.tokenize( '.' ).collect { String.format( '%02x', it.toInteger() ) }.join() 520 | return hex 521 | 522 | } 523 | 524 | private String convertPortToHex(port) { 525 | String hexport = port.toString().format( '%04x', port.toInteger() ) 526 | return hexport 527 | } 528 | 529 | private String hexToBin(String hex){ 530 | String bin = ""; 531 | String binFragment = ""; 532 | int iHex; 533 | hex = hex.trim(); 534 | hex = hex.replaceFirst("0x", ""); 535 | 536 | for(int i = 0; i < hex.length(); i++){ 537 | iHex = Integer.parseInt(""+hex.charAt(i),16); 538 | binFragment = Integer.toBinaryString(iHex); 539 | 540 | while(binFragment.length() < 4){ 541 | binFragment = "0" + binFragment; 542 | } 543 | bin += binFragment; 544 | } 545 | return bin; 546 | } 547 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | {description} 294 | Copyright (C) {year} {fullname} 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | {signature of Ty Coon}, 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | 341 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SmartThings 2 | SmartThings Code 3 | -------------------------------------------------------------------------------- /RaspberryPi2: -------------------------------------------------------------------------------- 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 | import com.google.common.base.Splitter; 24 | import java.util.List; 25 | import java.util.Map; 26 | import java.util.Map.Entry; 27 | 28 | 29 | preferences { 30 | input("ip", "string", title:"IP Address", description: "192.168.1.50", defaultValue: "192.168.1.50" ,required: true, displayDuringSetup: true) 31 | input("port", "string", title:"Port", description: "80", defaultValue: "80" , required: true, displayDuringSetup: true) 32 | input("username", "string", title:"Username", description: "pi", defaultValue: "pi" , required: true, displayDuringSetup: true) 33 | input("password", "password", title:"Password", description: "raspberry", defaultValue: "raspberry" , required: true, displayDuringSetup: true) 34 | } 35 | 36 | metadata { 37 | definition (name: "Raspberry Pi", namespace: "Ledridge", author: "Ledridge") { 38 | capability "Polling" 39 | capability "Refresh" 40 | capability "Temperature Measurement" 41 | capability "Switch" 42 | capability "Sensor" 43 | capability "Actuator" 44 | 45 | attribute "cpuPercentage", "string" 46 | attribute "memory", "string" 47 | attribute "diskUsage", "string" 48 | 49 | command "restart" 50 | } 51 | 52 | simulator { 53 | // TODO: define status and reply messages here 54 | } 55 | 56 | tiles { 57 | valueTile("temperature", "device.temperature", width: 1, height: 1) { 58 | state "temperature", label:'${currentValue}° CPU', unit: "F", 59 | backgroundColors:[ 60 | [value: 25, color: "#153591"], 61 | [value: 35, color: "#1e9cbb"], 62 | [value: 47, color: "#90d2a7"], 63 | [value: 59, color: "#44b621"], 64 | [value: 67, color: "#f1d801"], 65 | [value: 76, color: "#d04e00"], 66 | [value: 77, color: "#bc2323"] 67 | ] 68 | } 69 | standardTile("button", "device.switch", width: 1, height: 1, canChangeIcon: true) { 70 | state "off", label: 'Off', icon: "st.Electronics.electronics18", backgroundColor: "#ffffff", nextState: "on" 71 | state "on", label: 'On', icon: "st.Electronics.electronics18", backgroundColor: "#79b821", nextState: "off" 72 | } 73 | valueTile("cpuPercentage", "device.cpuPercentage", inactiveLabel: false) { 74 | state "default", label:'${currentValue}% CPU', unit:"Percentage", 75 | backgroundColors:[ 76 | [value: 31, color: "#153591"], 77 | [value: 44, color: "#1e9cbb"], 78 | [value: 59, color: "#90d2a7"], 79 | [value: 74, color: "#44b621"], 80 | [value: 84, color: "#f1d801"], 81 | [value: 95, color: "#d04e00"], 82 | [value: 96, color: "#bc2323"] 83 | ] 84 | } 85 | valueTile("memory", "device.memory", width: 1, height: 1) { 86 | state "default", label:'${currentValue} MB', unit:"MB", 87 | backgroundColors:[ 88 | [value: 353, color: "#153591"], 89 | [value: 287, color: "#1e9cbb"], 90 | [value: 210, color: "#90d2a7"], 91 | [value: 133, color: "#44b621"], 92 | [value: 82, color: "#f1d801"], 93 | [value: 26, color: "#d04e00"], 94 | [value: 20, color: "#bc2323"] 95 | ] 96 | } 97 | valueTile("diskUsage", "device.diskUsage", width: 1, height: 1) { 98 | state "default", label:'${currentValue}% Disk', unit:"Percent", 99 | backgroundColors:[ 100 | [value: 31, color: "#153591"], 101 | [value: 44, color: "#1e9cbb"], 102 | [value: 59, color: "#90d2a7"], 103 | [value: 74, color: "#44b621"], 104 | [value: 84, color: "#f1d801"], 105 | [value: 95, color: "#d04e00"], 106 | [value: 96, color: "#bc2323"] 107 | ] 108 | } 109 | standardTile("restart", "device.restart", inactiveLabel: false, decoration: "flat") { 110 | state "default", action:"restart", label: "Restart", displayName: "Restart" 111 | } 112 | standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat") { 113 | state "default", action:"refresh.refresh", icon: "st.secondary.refresh" 114 | } 115 | main "button" 116 | details(["button", "temperature", "cpuPercentage", "memory" , "diskUsage", "restart", "refresh"]) 117 | } 118 | } 119 | 120 | // ------------------------------------------------------------------ 121 | 122 | // parse events into attributes 123 | def parse(String description) { 124 | def map = [:] 125 | def descMap = parseDescriptionAsMap(description) 126 | log.debug "descMap: ${descMap}" 127 | 128 | def body = new String(descMap["body"].decodeBase64()) 129 | log.debug "body: ${body}" 130 | 131 | def slurper = new JsonSlurper() 132 | def result = slurper.parseText(body) 133 | 134 | log.debug "result: ${result}" 135 | 136 | if (result){ 137 | log.debug "Computer is up" 138 | sendEvent(name: "switch", value: "on") 139 | } 140 | 141 | log.debug "check temp..." 142 | if (result.containsKey("cpu_temp")) { 143 | log.debug "temp: ${result.cpu_temp}" 144 | log.debug "temp: ${celsiusToFahrenheit(result.cpu_temp.toDouble())} F" 145 | sendEvent(name: "temperature", value: celsiusToFahrenheit(result.cpu_temp.toDouble())) 146 | } 147 | 148 | if (result.containsKey("cpu_perc")) { 149 | sendEvent(name: "cpuPercentage", value: result.cpu_perc) 150 | } 151 | 152 | if (result.containsKey("mem_avail")) { 153 | log.debug "mem_avail: ${result.mem_avail}" 154 | sendEvent(name: "memory", value: result.mem_avail) 155 | } 156 | if (result.containsKey("disk_usage")) { 157 | log.debug "disk_usage: ${result.disk_usage}" 158 | sendEvent(name: "diskUsage", value: result.disk_usage) 159 | } 160 | 161 | } 162 | 163 | // handle commands 164 | def poll() { 165 | log.debug "Executing 'poll'" 166 | sendEvent(name: "switch", value: "off") 167 | getRPiData() 168 | } 169 | 170 | def refresh() { 171 | sendEvent(name: "switch", value: "off") 172 | log.debug "Executing 'refresh'" 173 | getRPiData() 174 | } 175 | 176 | def restart(){ 177 | log.debug "Restart was pressed" 178 | sendEvent(name: "switch", value: "off") 179 | def uri = "/api_command/reboot" 180 | postAction(uri) 181 | } 182 | 183 | // Get CPU percentage reading 184 | private getRPiData() { 185 | def uri = "/api_command/smartthings" 186 | postAction(uri) 187 | } 188 | 189 | // ------------------------------------------------------------------ 190 | 191 | private postAction(uri){ 192 | setDeviceNetworkId(ip,port) 193 | 194 | def userpass = encodeCredentials(username, password) 195 | //log.debug("userpass: " + userpass) 196 | 197 | def headers = getHeader(userpass) 198 | //log.debug("headders: " + headers) 199 | 200 | def hubAction = new physicalgraph.device.HubAction( 201 | method: "POST", 202 | path: uri, 203 | headers: headers 204 | )//,delayAction(1000), refresh()] 205 | log.debug("Executing hubAction on " + getHostAddress()) 206 | //log.debug hubAction 207 | hubAction 208 | } 209 | 210 | // ------------------------------------------------------------------ 211 | // Helper methods 212 | // ------------------------------------------------------------------ 213 | 214 | def parseDescriptionAsMap(description) { 215 | description.split(",").inject([:]) { map, param -> 216 | def nameAndValue = param.split(":") 217 | map += [(nameAndValue[0].trim()):nameAndValue[1].trim()] 218 | } 219 | } 220 | 221 | 222 | def toAscii(s){ 223 | StringBuilder sb = new StringBuilder(); 224 | String ascString = null; 225 | long asciiInt; 226 | for (int i = 0; i < s.length(); i++){ 227 | sb.append((int)s.charAt(i)); 228 | sb.append("|"); 229 | char c = s.charAt(i); 230 | } 231 | ascString = sb.toString(); 232 | asciiInt = Long.parseLong(ascString); 233 | return asciiInt; 234 | } 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | private encodeCredentials(username, password){ 246 | log.debug "Encoding credentials" 247 | def userpassascii = "${username}:${password}" 248 | def userpass = "Basic " + userpassascii.encodeAsBase64().toString() 249 | //log.debug "ASCII credentials are ${userpassascii}" 250 | //log.debug "Credentials are ${userpass}" 251 | return userpass 252 | } 253 | 254 | private getHeader(userpass){ 255 | log.debug "Getting headers" 256 | def headers = [:] 257 | headers.put("HOST", getHostAddress()) 258 | headers.put("Authorization", userpass) 259 | //log.debug "Headers are ${headers}" 260 | return headers 261 | } 262 | 263 | private delayAction(long time) { 264 | new physicalgraph.device.HubAction("delay $time") 265 | } 266 | 267 | private setDeviceNetworkId(ip,port){ 268 | def iphex = convertIPtoHex(ip) 269 | def porthex = convertPortToHex(port) 270 | device.deviceNetworkId = "$iphex:$porthex" 271 | log.debug "Device Network Id set to ${iphex}:${porthex}" 272 | } 273 | 274 | private getHostAddress() { 275 | return "${ip}:${port}" 276 | } 277 | 278 | private String convertIPtoHex(ipAddress) { 279 | String hex = ipAddress.tokenize( '.' ).collect { String.format( '%02x', it.toInteger() ) }.join() 280 | return hex 281 | 282 | } 283 | 284 | private String convertPortToHex(port) { 285 | String hexport = port.toString().format( '%04x', port.toInteger() ) 286 | return hexport 287 | } 288 | -------------------------------------------------------------------------------- /SmartApp_DLI_Web_Power_Switch_with_Virtual_Tiles: -------------------------------------------------------------------------------- 1 | /** 2 | * DLI Web Power Switch with Virtual Tiles 3 | * 4 | */ 5 | definition( 6 | name: "DLI Web Power Switch with Virtual Tiles.", 7 | namespace: "Ledridge", 8 | author: "Ledridge", 9 | description: "Use this app with a Digital Logger Inc. Web Power Switch and it will create virtual switches for each of the embedded switches.", 10 | category: "Convenience", 11 | iconUrl: "http://cdn.flaticon.com/png/256/25823.png", 12 | iconX2Url: "http://cdn.flaticon.com/png/256/25823.png", 13 | iconX3Url: "http://cdn.flaticon.com/png/256/25823.png") 14 | 15 | 16 | preferences { 17 | page(name: "mainDevice", uninstall: true, install:false) 18 | page(name: "virtualDetails", uninstall: true, install:true) 19 | page(name: "virtualSwitches", uninstall: true, install:true) 20 | } 21 | 22 | def mainDevice() { 23 | dynamicPage(name: "mainDevice", title: "Setup virtual app and multi-switch device", nextPage: "virtualDetails", uninstall: true, install:false) { 24 | section { 25 | input "master", "capability.switch", multiple: false, required: true, title: "Choose the device with multiple switches", image: "http://cdn.flaticon.com/png/256/61163.png" 26 | label title: "Assign a name for this virtual tile handler", required: false 27 | paragraph: "Assign switches to virtual tiles or real switches on next page" 28 | } 29 | } 30 | } 31 | 32 | def virtualDetails() { 33 | unsubscribe() 34 | syncChildDevices() 35 | dynamicPage(name: "virtualDetails", title: "Which virtual switches to create for $master.label?", uninstall: true, install:true) { 36 | 37 | section { 38 | input "switches", "enum", multiple: true, required: false, refreshAfterSelection:true, options:templates(), title:"Select Switches", description: "Choose which switches of $master.label you would like to have as separate switches", image: "http://cdn.flaticon.com/png/256/25823.png" 39 | } 40 | 41 | section("Current Virtual Switches of $master.label:") { 42 | def kids = getChildDevices() 43 | kids.each { paragraph "$it.label" } 44 | } 45 | } 46 | } 47 | 48 | 49 | def templates() { 50 | return [ "switch":"Web Power Switch 7", "Outlet1":"Outlet 1", "Outlet2":"Outlet 2", "Outlet3":"Outlet 3", "Outlet4":"Outlet 4", "Outlet5":"Outlet 5", "Outlet6":"Outlet6", "Outlet7":"Outlet 7", "Outlet8":"Outlet 8", "OutletA":"All Outlets"] 51 | } 52 | 53 | 54 | def installed() { 55 | syncChildDevices() 56 | def kids = getChildDevices() 57 | } 58 | 59 | def updated() { 60 | syncChildDevices() 61 | def kids = getChildDevices() 62 | unsubscribe() 63 | kids.each { subscribe(master, "$it.name", vswitch) } 64 | } 65 | 66 | def uninstalled() { 67 | removeChildDevices() 68 | } 69 | 70 | def vswitch(evt) { 71 | log.debug evt.descriptionText 72 | def vswitch = evt.descriptionText.find(/outlet./).replaceAll(" ","") 73 | log.debug "child switch to change: $vswitch event: $evt.value DDNI:${ddni(vswitch)}" 74 | def vkid = getChildDevice(ddni(vswitch)) 75 | log.debug "$evt.value $evt.deviceId $evt.description $evt.descriptionText" 76 | log.debug "vkid: $vkid" 77 | vkid."${evt.value}"() 78 | } 79 | 80 | def mswitch(evt) { log.debug "$evt.value $evt.deviceId $evt.description $evt.descriptionText" } 81 | 82 | def OutletStatus(childDevice) { 83 | log.debug "Parent OUTLET_STATUS: ${childDevice}" 84 | def num = childDevice.device.name.replaceAll("Outlet","") 85 | log.debug "Parent OUTLET_STATUS: ${num}" 86 | if(num) {return master.OutletStatus(num)} else { return "Unknown" ;log.debug "No switch number provided for off" } 87 | } 88 | 89 | def OutletName(childDevice) { 90 | log.debug "Parent OUTLET_NAME: ${childDevice}" 91 | def num = childDevice.device.name.replaceAll("Outlet","") 92 | log.debug "Parent OUTLET_NAME: ${num}" 93 | if(num) {return master.OutletName(num)} else { return "Unknown" ;log.debug "No switch number provided for off" } 94 | } 95 | 96 | def OutletAction(childDevice,action) { 97 | log.debug "Parent OUTLET_ACTION: ${childDevice} ${action}" 98 | def num = childDevice.device.name.replaceAll("Outlet","") 99 | log.debug "Parent OUTLET_ACTION: ${num} ${action}" 100 | if(num) { master.OutletAction(num,action)} else { master.off();log.debug "No switch number provided for off" } 101 | } 102 | 103 | 104 | private syncChildDevices() { 105 | switches.each { def e = getChildDevice(ddni(it)); if(!e) { createChild(it) } } 106 | def switchKids = getChildDevices() 107 | def removeKids = switchKids.name - switches 108 | log.debug "SWs: $switches del: $removeKids kids: $removeKids" 109 | removeKids.each { rem -> 110 | def delKid = getChildDevice(ddni(rem)) 111 | log.debug delKid.deviceNetworkId 112 | removeChildDevices(delKid) 113 | } 114 | } 115 | 116 | private createChild(vt) { 117 | def label = templates().getAt(vt) 118 | log.debug "Label $label" 119 | def addedvt = addChildDevice("Ledridge", "vTile_DLI", ddni(vt), null, [name:vt, label:label, completedSetup: true]) 120 | log.info "created Virtual Switch ${addedvt.displayName} with DNI ${addedvt.deviceNetworkId}" 121 | } 122 | 123 | private removeChildDevices(delete) { 124 | unsubscribe() 125 | if(!delete) { delete = getChildDevices() } 126 | delete.each { 127 | deleteChildDevice(it.deviceNetworkId) 128 | log.debug "deleted ${delete.displayName}" 129 | } 130 | } 131 | 132 | private ddni(id){ 133 | if(!state.appId) { state.appId = app.id } 134 | def ddni = state.appId + "/" + id 135 | return ddni 136 | } 137 | -------------------------------------------------------------------------------- /SmartApp_Powerstrip.groovy: -------------------------------------------------------------------------------- 1 | /** 2 | * Multi Switch with Virtual Tiles for Master Switch 3 | * 4 | * Copyright 2014 Cooper Lee 5 | * 6 | * NOTE - THIS ONLY WORKS WITH ATTRIBUTE TYPE switch and switch[x] (1..9) 7 | * 8 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 9 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License 10 | * for the specific language governing permissions and limitations under the License. 11 | * 12 | */ 13 | definition( 14 | name: "Multi Switch with Virtual Tiles for Master Switch", 15 | namespace: "ms_w_vts", 16 | author: "Cooper Lee", 17 | description: "Use this app with a device that has multiple switches embedded in the device type and it will create virtual switches for each of the various embedded switches.", 18 | category: "Convenience", 19 | iconUrl: "http://cdn.flaticon.com/png/256/25823.png", 20 | iconX2Url: "http://cdn.flaticon.com/png/256/25823.png", 21 | iconX3Url: "http://cdn.flaticon.com/png/256/25823.png") 22 | 23 | 24 | preferences { 25 | page(name: "mainDevice", uninstall: true, install:false) 26 | page(name: "virtualSwitches", uninstall: true, install:true) 27 | page(name: "virtualDetails", uninstall: true, install:true) 28 | } 29 | 30 | def mainDevice() { 31 | dynamicPage(name: "mainDevice", title: "Setup virtual app and multi-switch device", nextPage: "virtualDetails", uninstall: true, install:false) { 32 | section { 33 | input "master", "capability.switch", multiple: false, required: true, title: "Choose the device with multiple switches", image: "http://cdn.flaticon.com/png/256/61163.png" 34 | label title: "Assign a name for this virtual tile handler", required: false 35 | paragraph: "Assign switches to virtual tiles or real switches on next page" 36 | } 37 | } 38 | } 39 | 40 | def devTemplates(devType) { 41 | def templates = [ 42 | "base2" :"Device with 2 switches", 43 | "base9" :"Device with up to 9 switches", 44 | 45 | "aeon4" :"Aeon Power Strip", 46 | 47 | "iPool1PSV" :"PE653 - Pool & Spa w/o Booster (Variable speed pump)", // (blower on sw3 lights on sw2 48 | "iPool1PSVB":"PE653 - Pool & Spa w/ Booster (Variable speed pump)", // (booster on sw3 lights/blower on sw2 49 | 50 | "iPool1" :"PE653 - Pool and 4 Acc (1 speed pump)", 51 | "iPool1H" :"PE653 - Pool w/Heater and 3 Acc (1 speed pump)", 52 | "iPool1PS" :"PE653 - Pool & Spa w/o Booster (1 speed pump)", // blower on sw3 lights on sw2 53 | "iPool1PSB" :"PE653 - Pool & Spa w/ Booster (1 speed pump)", // (booster on sw3 lights/blower on sw2 54 | 55 | "iPool1S" :"PE653 - Spa Only (1 speed pump)", // blower on sw3 lights on sw2 56 | "iPool2S" :"PE653 - Spa Only (2 speed pump)", // blower on sw4 lights/booster on sw3 57 | 58 | "iPool2" :"PE653 - Pool and 3 Acc (2 speed pump)", 59 | "iPool2H" :"PE653 - Pool w/Heater and 2 Acc (2 speed pump)", 60 | "iPool2PS" :"PE653 - Pool & Spa w/o Booster (2 speed pump)", // blower on sw3 61 | "iPool2PB" :"PE653 - Pool w/ Booster and Lights (2 speed pump)", // (booster on sw3 lights on sw4 62 | ] 63 | if(devType) { return devTemplates.getAt(dev_type) } else { return templates } 64 | } 65 | 66 | 67 | def templates() { 68 | def templates = [ 69 | 'aeon4': [ "switch":"Power Strip", "switch1":"SW Outlet 1", "switch2":"SW Outlet 2", "switch3":"SW Outlet 3", "switch4":"SW Outlet 4", "switch5":"Outlet 1", "switch6":"Outlet 2", "switch7":"All SW Outlets"], 70 | 'base': [ "switch":"switch", "switch1":"Switch 1"], 71 | 'base2': [ "switch":"switch", "switch1":"Switch 1", "switch2":"Switch 2"], 72 | 'base9': [ "switch":"switch", "switch1":"Switch 1", "switch2":"Switch 2", "switch3":"Switch 3", "switch4":"Switch 4", "switch5":"Switch 5", "switch6":"Switch 6", "switch7":"Switch 7", "switch8":"Switch 8", "switch9":"Switch 9"], 73 | 'iPool1': [ "switch1":"Pool Pump", "switch2":"Switch 2", "switch3":"Switch 3", "switch4":"Switch 4", "switch5":"Switch 5"], 74 | 'iPool1H': [ "switch1":"Pool Pump", "switch2":"Switch 2", "switch3":"Switch 3", "switch4":"Switch 4", "switch5":"Pool Heater"], 75 | 'iPool1PS': [ "switch1":"Filter Pump", "switch2":"Water Lights", "switch3":"Spa Blower", "switch4":"Pool/Spa", "switch5":"Water Heater"], 76 | 'iPool1PSB':[ "switch1":"Filter Pump", "switch2":"Spa Blower", "switch3":"Booster Pump", "switch4":"Pool/Spa", "switch5":"Water Heater"], 77 | 'iPool1PSV':[ "switch1":"Filter Pump", "switch2":"Spa Blower", "switch3":"Booster Pump", "switch4":"Pool/Spa", "switch5":"Water Heater"], 78 | 'iPool1PSVB':[ "switch1":"Filter Pump", "switch2":"Pool Lights", "switch3":"Spa Blower", "switch4":"Pool/Spa", "switch5":"Water Heater"], 79 | 'iPool1S': [ "switch1":"Spa Pump", "switch2":"Spa Lights", "switch3":"Spa Blower", "switch4":"Spa Accessory", "switch5":"Spa Heater"], 80 | 81 | 'iPool2': [ "switch1":"Pool Pump High", "switch2":"Pool Pump Low", "switch3":"Switch 3", "switch4":"Switch 4", "switch5":"Switch 5"], 82 | 'iPool2H': [ "switch1":"Pool Pump High", "switch2":"Pool Pump Low", "switch3":"Switch 3", "switch4":"Switch 4", "switch5":"Pool Heater"], 83 | 'iPool2S': [ "switch1":"Spa Pump High", "switch2":"Spa Pump Low", "switch3":"Spa Lights", "switch4":"Spa Blower", "switch5":"Spa Heater"], 84 | 'iPool2PS': [ "switch1":"Filter Pump High", "switch2":"Filter Pump Low", "switch3":"Spa Blower", "switch4":"Pool/Spa", "switch5":"Water Heater"], 85 | 'iPool2PB': [ "switch1":"Pool Pump High", "switch2":"Pool Pump Low", "switch3":"Booster Pump", "switch4":"Pool Lights", "switch5":"Water Heater"], 86 | 87 | 88 | 'iPoolSpa': [ "switch1":"Pump", "switch2":"Switch 2", "switch3":"Switch 3", "switch4":"Switch 4", "switch5":"Pool Heater"], 89 | ] 90 | if(switch_template) { return templates.getAt(switch_template) } else { return [ "switch":"switch", "switch1":"Switch 1", "switch2":"Switch 2", "switch3":"Switch 3", "switch4":"Switch 4"] } 91 | } 92 | 93 | 94 | def virtualDetails() { 95 | unsubscribe() 96 | syncChildDevices() 97 | dynamicPage(name: "virtualDetails", title: "Which virtual switches to create for $master.label?", uninstall: true, install:true) { 98 | 99 | section { 100 | input "switch_template", "enum", multiple: false, required: false, refreshAfterSelection:true, options:devTemplates(), title:"Template to use", description: "What device template should to use for $master.label?", image: "http://cdn.flaticon.com/png/256/25823.png" 101 | } 102 | 103 | section { 104 | input "switches", "enum", multiple: true, required: false, refreshAfterSelection:true, options:templates(), title:"Select Switches", description: "Choose which switches of $master.label you would like to have as separate switches", image: "http://cdn.flaticon.com/png/256/25823.png" 105 | } 106 | 107 | section("Current Virtual Switches of $master.label:") { 108 | def kids = getChildDevices() 109 | kids.each { paragraph "$it.label" } 110 | } 111 | } 112 | } 113 | 114 | 115 | 116 | def installed() { 117 | syncChildDevices() 118 | def kids = getChildDevices() 119 | } 120 | 121 | def updated() { 122 | syncChildDevices() 123 | def kids = getChildDevices() 124 | unsubscribe() 125 | kids.each { subscribe(master, "$it.name", vswitch) } 126 | } 127 | 128 | def uninstalled() { 129 | removeChildDevices() 130 | } 131 | 132 | def vswitch(evt) { 133 | def vswitch = evt.descriptionText.find(/switch./).replaceAll(" ","") 134 | // log.debug "child switch to change: $vswitch event: $evt.value DDNI:${ddni(vswitch)}" 135 | def vkid = getChildDevice(ddni(vswitch)) 136 | // log.debug "$evt.value $evt.deviceId $evt.description $evt.descriptionText" 137 | // log.debug "vkid: $vkid" 138 | vkid."${evt.value}"() 139 | } 140 | 141 | //def mswitch(evt) { log.debug "$evt.value $evt.deviceId $evt.description $evt.descriptionText" } 142 | 143 | def on(childDevice) { 144 | def num = childDevice.device.name.replaceAll("switch","") 145 | if(num) { master."on${num}"() } else { master.on();log.debug "No switch number provided for on" } 146 | } 147 | 148 | def off(childDevice) { 149 | def num = childDevice.device.name.replaceAll("switch","") 150 | if(num) { master."off${num}"() } else { master.off();log.debug "No switch number provided for off" } 151 | } 152 | 153 | 154 | private syncChildDevices() { 155 | switches.each { def e = getChildDevice(ddni(it)); if(!e) { createChild(it) } } 156 | def switchKids = getChildDevices() 157 | def removeKids = switchKids.name - switches 158 | // log.debug "SWs: $switches del: $removeKids kids: $removeKids" 159 | removeKids.each { rem -> 160 | def delKid = getChildDevice(ddni(rem)) 161 | log.debug delKid.deviceNetworkId 162 | removeChildDevices(delKid) 163 | } 164 | } 165 | 166 | private createChild(vt) { 167 | def label = templates().getAt(vt) 168 | log.debug "Label $label" 169 | def addedvt = addChildDevice("ms_w_vts", "vTile_ms", ddni(vt), null, [name:vt, label:label, completedSetup: true]) 170 | log.info "created Virtual Switch ${addedvt.displayName} with DNI ${addedvt.deviceNetworkId}" 171 | } 172 | 173 | private removeChildDevices(delete) { 174 | unsubscribe() 175 | if(!delete) { delete = getChildDevices() } 176 | delete.each { 177 | deleteChildDevice(it.deviceNetworkId) 178 | log.debug "deleted ${delete.displayName}" 179 | } 180 | } 181 | 182 | private ddni(id){ 183 | if(!state.appId) { state.appId = app.id } 184 | def ddni = state.appId + "/" + id 185 | return ddni 186 | }/** 187 | * Multi Switch with Virtual Tiles for Master Switch 188 | * 189 | * Copyright 2014 Cooper Lee 190 | * 191 | * NOTE - THIS ONLY WORKS WITH ATTRIBUTE TYPE switch and switch[x] (1..9) 192 | * 193 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 194 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License 195 | * for the specific language governing permissions and limitations under the License. 196 | * 197 | */ 198 | definition( 199 | name: "Multi Switch with Virtual Tiles for Master Switch", 200 | namespace: "ms_w_vts", 201 | author: "Cooper Lee", 202 | description: "Use this app with a device that has multiple switches embedded in the device type and it will create virtual switches for each of the various embedded switches.", 203 | category: "Convenience", 204 | iconUrl: "http://cdn.flaticon.com/png/256/25823.png", 205 | iconX2Url: "http://cdn.flaticon.com/png/256/25823.png", 206 | iconX3Url: "http://cdn.flaticon.com/png/256/25823.png") 207 | 208 | 209 | preferences { 210 | page(name: "mainDevice", uninstall: true, install:false) 211 | page(name: "virtualSwitches", uninstall: true, install:true) 212 | page(name: "virtualDetails", uninstall: true, install:true) 213 | } 214 | 215 | def mainDevice() { 216 | dynamicPage(name: "mainDevice", title: "Setup virtual app and multi-switch device", nextPage: "virtualDetails", uninstall: true, install:false) { 217 | section { 218 | input "master", "capability.switch", multiple: false, required: true, title: "Choose the device with multiple switches", image: "http://cdn.flaticon.com/png/256/61163.png" 219 | label title: "Assign a name for this virtual tile handler", required: false 220 | paragraph: "Assign switches to virtual tiles or real switches on next page" 221 | } 222 | } 223 | } 224 | 225 | def devTemplates(devType) { 226 | def templates = [ 227 | "base2" :"Device with 2 switches", 228 | "base9" :"Device with up to 9 switches", 229 | 230 | "aeon4" :"Aeon Power Strip", 231 | 232 | "iPool1PSV" :"PE653 - Pool & Spa w/o Booster (Variable speed pump)", // (blower on sw3 lights on sw2 233 | "iPool1PSVB":"PE653 - Pool & Spa w/ Booster (Variable speed pump)", // (booster on sw3 lights/blower on sw2 234 | 235 | "iPool1" :"PE653 - Pool and 4 Acc (1 speed pump)", 236 | "iPool1H" :"PE653 - Pool w/Heater and 3 Acc (1 speed pump)", 237 | "iPool1PS" :"PE653 - Pool & Spa w/o Booster (1 speed pump)", // blower on sw3 lights on sw2 238 | "iPool1PSB" :"PE653 - Pool & Spa w/ Booster (1 speed pump)", // (booster on sw3 lights/blower on sw2 239 | 240 | "iPool1S" :"PE653 - Spa Only (1 speed pump)", // blower on sw3 lights on sw2 241 | "iPool2S" :"PE653 - Spa Only (2 speed pump)", // blower on sw4 lights/booster on sw3 242 | 243 | "iPool2" :"PE653 - Pool and 3 Acc (2 speed pump)", 244 | "iPool2H" :"PE653 - Pool w/Heater and 2 Acc (2 speed pump)", 245 | "iPool2PS" :"PE653 - Pool & Spa w/o Booster (2 speed pump)", // blower on sw3 246 | "iPool2PB" :"PE653 - Pool w/ Booster and Lights (2 speed pump)", // (booster on sw3 lights on sw4 247 | ] 248 | if(devType) { return devTemplates.getAt(dev_type) } else { return templates } 249 | } 250 | 251 | 252 | def templates() { 253 | def templates = [ 254 | 'aeon4': [ "switch":"Power Strip", "switch1":"SW Outlet 1", "switch2":"SW Outlet 2", "switch3":"SW Outlet 3", "switch4":"SW Outlet 4", "switch5":"Outlet 1", "switch6":"Outlet 2", "switch7":"All SW Outlets"], 255 | 'base': [ "switch":"switch", "switch1":"Switch 1"], 256 | 'base2': [ "switch":"switch", "switch1":"Switch 1", "switch2":"Switch 2"], 257 | 'base9': [ "switch":"switch", "switch1":"Switch 1", "switch2":"Switch 2", "switch3":"Switch 3", "switch4":"Switch 4", "switch5":"Switch 5", "switch6":"Switch 6", "switch7":"Switch 7", "switch8":"Switch 8", "switch9":"Switch 9"], 258 | 'iPool1': [ "switch1":"Pool Pump", "switch2":"Switch 2", "switch3":"Switch 3", "switch4":"Switch 4", "switch5":"Switch 5"], 259 | 'iPool1H': [ "switch1":"Pool Pump", "switch2":"Switch 2", "switch3":"Switch 3", "switch4":"Switch 4", "switch5":"Pool Heater"], 260 | 'iPool1PS': [ "switch1":"Filter Pump", "switch2":"Water Lights", "switch3":"Spa Blower", "switch4":"Pool/Spa", "switch5":"Water Heater"], 261 | 'iPool1PSB':[ "switch1":"Filter Pump", "switch2":"Spa Blower", "switch3":"Booster Pump", "switch4":"Pool/Spa", "switch5":"Water Heater"], 262 | 'iPool1PSV':[ "switch1":"Filter Pump", "switch2":"Spa Blower", "switch3":"Booster Pump", "switch4":"Pool/Spa", "switch5":"Water Heater"], 263 | 'iPool1PSVB':[ "switch1":"Filter Pump", "switch2":"Pool Lights", "switch3":"Spa Blower", "switch4":"Pool/Spa", "switch5":"Water Heater"], 264 | 'iPool1S': [ "switch1":"Spa Pump", "switch2":"Spa Lights", "switch3":"Spa Blower", "switch4":"Spa Accessory", "switch5":"Spa Heater"], 265 | 266 | 'iPool2': [ "switch1":"Pool Pump High", "switch2":"Pool Pump Low", "switch3":"Switch 3", "switch4":"Switch 4", "switch5":"Switch 5"], 267 | 'iPool2H': [ "switch1":"Pool Pump High", "switch2":"Pool Pump Low", "switch3":"Switch 3", "switch4":"Switch 4", "switch5":"Pool Heater"], 268 | 'iPool2S': [ "switch1":"Spa Pump High", "switch2":"Spa Pump Low", "switch3":"Spa Lights", "switch4":"Spa Blower", "switch5":"Spa Heater"], 269 | 'iPool2PS': [ "switch1":"Filter Pump High", "switch2":"Filter Pump Low", "switch3":"Spa Blower", "switch4":"Pool/Spa", "switch5":"Water Heater"], 270 | 'iPool2PB': [ "switch1":"Pool Pump High", "switch2":"Pool Pump Low", "switch3":"Booster Pump", "switch4":"Pool Lights", "switch5":"Water Heater"], 271 | 272 | 273 | 'iPoolSpa': [ "switch1":"Pump", "switch2":"Switch 2", "switch3":"Switch 3", "switch4":"Switch 4", "switch5":"Pool Heater"], 274 | ] 275 | if(switch_template) { return templates.getAt(switch_template) } else { return [ "switch":"switch", "switch1":"Switch 1", "switch2":"Switch 2", "switch3":"Switch 3", "switch4":"Switch 4"] } 276 | } 277 | 278 | 279 | def virtualDetails() { 280 | unsubscribe() 281 | syncChildDevices() 282 | dynamicPage(name: "virtualDetails", title: "Which virtual switches to create for $master.label?", uninstall: true, install:true) { 283 | 284 | section { 285 | input "switch_template", "enum", multiple: false, required: false, refreshAfterSelection:true, options:devTemplates(), title:"Template to use", description: "What device template should to use for $master.label?", image: "http://cdn.flaticon.com/png/256/25823.png" 286 | } 287 | 288 | section { 289 | input "switches", "enum", multiple: true, required: false, refreshAfterSelection:true, options:templates(), title:"Select Switches", description: "Choose which switches of $master.label you would like to have as separate switches", image: "http://cdn.flaticon.com/png/256/25823.png" 290 | } 291 | 292 | section("Current Virtual Switches of $master.label:") { 293 | def kids = getChildDevices() 294 | kids.each { paragraph "$it.label" } 295 | } 296 | } 297 | } 298 | 299 | 300 | 301 | def installed() { 302 | syncChildDevices() 303 | def kids = getChildDevices() 304 | } 305 | 306 | def updated() { 307 | syncChildDevices() 308 | def kids = getChildDevices() 309 | unsubscribe() 310 | kids.each { subscribe(master, "$it.name", vswitch) } 311 | } 312 | 313 | def uninstalled() { 314 | removeChildDevices() 315 | } 316 | 317 | def vswitch(evt) { 318 | def vswitch = evt.descriptionText.find(/switch./).replaceAll(" ","") 319 | // log.debug "child switch to change: $vswitch event: $evt.value DDNI:${ddni(vswitch)}" 320 | def vkid = getChildDevice(ddni(vswitch)) 321 | // log.debug "$evt.value $evt.deviceId $evt.description $evt.descriptionText" 322 | // log.debug "vkid: $vkid" 323 | vkid."${evt.value}"() 324 | } 325 | 326 | //def mswitch(evt) { log.debug "$evt.value $evt.deviceId $evt.description $evt.descriptionText" } 327 | 328 | def on(childDevice) { 329 | def num = childDevice.device.name.replaceAll("switch","") 330 | if(num) { master."on${num}"() } else { master.on();log.debug "No switch number provided for on" } 331 | } 332 | 333 | def off(childDevice) { 334 | def num = childDevice.device.name.replaceAll("switch","") 335 | if(num) { master."off${num}"() } else { master.off();log.debug "No switch number provided for off" } 336 | } 337 | 338 | 339 | private syncChildDevices() { 340 | switches.each { def e = getChildDevice(ddni(it)); if(!e) { createChild(it) } } 341 | def switchKids = getChildDevices() 342 | def removeKids = switchKids.name - switches 343 | // log.debug "SWs: $switches del: $removeKids kids: $removeKids" 344 | removeKids.each { rem -> 345 | def delKid = getChildDevice(ddni(rem)) 346 | log.debug delKid.deviceNetworkId 347 | removeChildDevices(delKid) 348 | } 349 | } 350 | 351 | private createChild(vt) { 352 | def label = templates().getAt(vt) 353 | log.debug "Label $label" 354 | def addedvt = addChildDevice("ms_w_vts", "vTile_ms", ddni(vt), null, [name:vt, label:label, completedSetup: true]) 355 | log.info "created Virtual Switch ${addedvt.displayName} with DNI ${addedvt.deviceNetworkId}" 356 | } 357 | 358 | private removeChildDevices(delete) { 359 | unsubscribe() 360 | if(!delete) { delete = getChildDevices() } 361 | delete.each { 362 | deleteChildDevice(it.deviceNetworkId) 363 | log.debug "deleted ${delete.displayName}" 364 | } 365 | } 366 | 367 | private ddni(id){ 368 | if(!state.appId) { state.appId = app.id } 369 | def ddni = state.appId + "/" + id 370 | return ddni 371 | } -------------------------------------------------------------------------------- /devicetypes/cmonroe/everspring-st814.src/everspring-st814.groovy: -------------------------------------------------------------------------------- 1 | /** 2 | * EverSpring ST814 3 | * 4 | * Copyright 2014 Ben (SmartThings) and Chad Monroe 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 | metadata 17 | { 18 | definition (name: "EverSpring ST814", namespace: "cmonroe", author: "@Ben chad@monroe.io") 19 | { 20 | capability "Battery" 21 | capability "Temperature Measurement" 22 | capability "Relative Humidity Measurement" 23 | capability "Configuration" 24 | capability "Alarm" 25 | capability "Sensor" 26 | 27 | fingerprint deviceId: "0x2101", inClusters: "0x31,0x60,0x86,0x72,0x85,0x84,0x80,0x70,0x20,0x71" 28 | 29 | /** 30 | * 0x31: COMMAND_CLASS_SENSOR_MULTILEVEL_V2 31 | * 0x60: COMMAND_CLASS_MULTI_CHANNEL_V2 32 | * 0x86: COMMAND_CLASS_VERSION 33 | * 0x72: COMMAND_CLASS_MANUFACTURER_SPECIFIC 34 | * 0x85: COMMAND_CLASS_ASSOCIATION_V2 35 | * 0x84: COMMAND_CLASS_WAKE_UP_V2 36 | * 0x80: COMMAND_CLASS_BATTERY 37 | * 0x70: COMMAND_CLASS_CONFIGURATION_V2 38 | * 0x20: COMMAND_CLASS_BASIC 39 | * 0x71: COMMAND_CLASS_ALARM 40 | **/ 41 | } 42 | 43 | simulator 44 | { 45 | /* messages the device returns in response to commands it receives */ 46 | for( int i = 0; i <= 100; i += 20 ) 47 | { 48 | status "temperature ${i}F": new physicalgraph.zwave.Zwave().sensorMultilevelV2.sensorMultilevelReport( 49 | scaledSensorValue: i, precision: 1, sensorType: 1, scale: 1).incomingMessage() 50 | } 51 | 52 | for( int i = 0; i <= 100; i += 20 ) 53 | { 54 | status "humidity ${i}%": new physicalgraph.zwave.Zwave().sensorMultilevelV2.sensorMultilevelReport( 55 | scaledSensorValue: i, precision: 0, sensorType: 5).incomingMessage() 56 | } 57 | 58 | for( int i = 0; i <= 100; i += 20 ) 59 | { 60 | status "battery ${i}%": new physicalgraph.zwave.Zwave().batteryV1.batteryReport( 61 | batteryLevel: i).incomingMessage() 62 | } 63 | } 64 | 65 | tiles 66 | { 67 | valueTile( "temperature", "device.temperature", inactiveLabel: false ) 68 | { 69 | state( "temperature", label:'${currentValue}°', 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 | } 81 | 82 | valueTile( "humidity", "device.humidity", inactiveLabel: false ) 83 | { 84 | state( "humidity", label:'${currentValue}% humidity', unit:"" ) 85 | } 86 | 87 | standardTile( "alarm", "device.alarm", inactiveLabel: false ) 88 | { 89 | state( "ok", label:'BAT OK', action:'alarm.on', icon:"st.alarm.alarm.alarm", backgroundColor:"#ffffff" ) 90 | state( "low", label:'BAT LOW', action:'alarm.off', icon:"st.alarm.alarm.alarm", backgroundColor:"#e86d13" ) 91 | } 92 | 93 | valueTile( "battery", "device.battery", inactiveLabel: false, decoration: "flat" ) 94 | { 95 | state( "battery", label:'${currentValue}% battery', unit:"" ) 96 | } 97 | 98 | standardTile( "configure", "device.configure", inactiveLabel: false, decoration: "flat" ) 99 | { 100 | state( "configure", label:'', action:"configuration.configure", icon:"st.secondary.configure" ) 101 | } 102 | 103 | main( ["temperature", "humidity"] ) 104 | details( ["temperature", "humidity", "alarm", "battery", "configure"] ) 105 | } 106 | } 107 | 108 | /** 109 | * parse incoming device messages and generate events 110 | **/ 111 | def parse(String description) 112 | { 113 | def result = [] 114 | def get_battery = false 115 | def cmd = null 116 | 117 | 118 | if ( description == "updated" ) 119 | { 120 | log.debug "event description: ${description} - updating battery status" 121 | get_battery = true 122 | } 123 | else 124 | { 125 | cmd = zwave.parse( description, [0x20: 1, 0x31: 2, 0x70: 1, 0x71: 1, 0x80: 1, 0x84: 2, 0x85: 2] ) 126 | } 127 | 128 | if ( cmd != null ) 129 | { 130 | if ( cmd.CMD == "8407" ) 131 | { 132 | result << zwaveEvent( cmd ) 133 | 134 | log.debug "cmd.CMD=8407; result=${result} - updating battery status" 135 | get_battery = true 136 | } 137 | else 138 | { 139 | result << createEvent( zwaveEvent( cmd ) ) 140 | } 141 | } 142 | 143 | if( get_battery == true ) 144 | { 145 | def last = device.currentState( "battery" ) 146 | 147 | /* device wakes up roughly every hour */ 148 | def age = last ? (new Date().time - last.date.time)/60000 : 10 149 | 150 | log.debug "Battery status was last checked ${age} minute(s) ago" 151 | 152 | /* don't check too often if woken up more frequently */ 153 | if( age >= 10 ) 154 | { 155 | log.debug "Battery status is outdated, requesting battery report" 156 | result << new physicalgraph.device.HubAction(zwave.batteryV1.batteryGet().format()) 157 | } 158 | 159 | result << new physicalgraph.device.HubAction(zwave.wakeUpV1.wakeUpNoMoreInformation().format()) 160 | } 161 | 162 | log.debug "Parse returned: ${result} for cmd=${cmd} description=${description}" 163 | return result 164 | } 165 | 166 | /** 167 | * event generation 168 | **/ 169 | def zwaveEvent(physicalgraph.zwave.commands.wakeupv1.WakeUpNotification cmd) 170 | { 171 | [descriptionText: "${device.displayName} woke up", isStateChange: false] 172 | } 173 | 174 | def zwaveEvent(physicalgraph.zwave.commands.wakeupv2.WakeUpNotification cmd) 175 | { 176 | [descriptionText: "${device.displayName} woke up", isStateChange: false] 177 | } 178 | 179 | def zwaveEvent(physicalgraph.zwave.commands.alarmv1.AlarmReport cmd) 180 | { 181 | def map = [:] 182 | 183 | log.debug "AlarmReport cmd: ${cmd.toString()}}" 184 | 185 | if(( cmd.alarmType == 2 ) && ( cmd.alarmLevel == 1 )) 186 | { 187 | log.info "${device.displayName} powered up!" 188 | return map 189 | } 190 | else 191 | { 192 | /* alarmType == 1 && alarmLevel == 255 means low battery, else ok */ 193 | map.value = cmd.alarmLevel == 255 ? "low" : "ok" 194 | map.name = "alarm" 195 | } 196 | 197 | map 198 | } 199 | 200 | def zwaveEvent(physicalgraph.zwave.commands.sensormultilevelv2.SensorMultilevelReport cmd) 201 | { 202 | log.debug "SensorMultilevelReport cmd: ${cmd.toString()}}" 203 | 204 | def map = [:] 205 | switch( cmd.sensorType ) 206 | { 207 | case 1: 208 | /* temperature */ 209 | def cmdScale = cmd.scale == 1 ? "F" : "C" 210 | map.value = convertTemperatureIfNeeded( cmd.scaledSensorValue, cmdScale, cmd.precision ) 211 | map.unit = getTemperatureScale() 212 | map.name = "temperature" 213 | break; 214 | case 5: 215 | /* humidity */ 216 | map.value = cmd.scaledSensorValue.toInteger().toString() 217 | map.unit = "%" 218 | map.name = "humidity" 219 | break; 220 | } 221 | 222 | map 223 | } 224 | 225 | def zwaveEvent(physicalgraph.zwave.commands.batteryv1.BatteryReport cmd) 226 | { 227 | def map = [:] 228 | 229 | map.name = "battery" 230 | map.value = cmd.batteryLevel > 0 ? cmd.batteryLevel.toString() : 1 231 | map.unit = "%" 232 | map.displayed = true 233 | 234 | map 235 | } 236 | 237 | def zwaveEvent(physicalgraph.zwave.Command cmd) 238 | { 239 | log.debug "Catchall reached for cmd: ${cmd.toString()}" 240 | [:] 241 | } 242 | 243 | def configure() 244 | { 245 | log.debug "sending config values..." 246 | 247 | delayBetween([ 248 | /* report in every 5 minute(s) */ 249 | zwave.configurationV1.configurationSet(parameterNumber: 6, size: 2, scaledConfigurationValue: 5).format(), 250 | 251 | /* report a temperature change of 1 degree C */ 252 | zwave.configurationV1.configurationSet(parameterNumber: 7, size: 1, scaledConfigurationValue: 1).format(), 253 | 254 | /* report a humidity change of 1 percent */ 255 | zwave.configurationV1.configurationSet(parameterNumber: 8, size: 1, scaledConfigurationValue: 1).format() 256 | 257 | ]) 258 | } -------------------------------------------------------------------------------- /devicetypes/ledridge/foscam-hd.src/foscam-hd.groovy: -------------------------------------------------------------------------------- 1 | /** 2 | * Foscam HD 3 | * 4 | * Author: skp19 5 | * Date: 6/18/14 6 | * 7 | * Example device type for HD Foscam cameras. 8 | * Code based off of Foscam device type by brian@bevey.org. 9 | * Heavily modified to work with the Foscam HD cameras. 10 | * 11 | * This device has the following functions: 12 | * - Take a snapshot 13 | * - Toggle the infrared lights 14 | * - Enable/Disable motion alarm 15 | * - Go to and set preset locations 16 | * - Enable cruise maps 17 | * - Control PTZ 18 | * - Reboot 19 | * 20 | * Capability: Image Capture, Polling 21 | * Custom Attributes: setStatus, alarmStatus, ledStatus 22 | * Custom Commands: alarmOn, alarmOff, toggleAlarm, left, right, up, down, 23 | * stop, set, preset, preset1, preset2, preset3, cruisemap1, 24 | * cruisemap2, cruise, toggleLED, ledOn, ledOff, ledAuto 25 | */ 26 | 27 | preferences { 28 | input("username", "text", title: "Username", description: "Your Foscam camera username") 29 | input("password", "password", title: "Password", description: "Your Foscam camera password") 30 | input("ip", "text", title: "IP address/Hostname", description: "The IP address or hostname of your Foscam camera") 31 | input("port", "text", title: "Port", description: "The port of your Foscam camera") 32 | input("preset1", "text", title: "Preset 1", description: "Name of your first preset position") 33 | input("preset2", "text", title: "Preset 2", description: "Name of your second preset position") 34 | input("preset3", "text", title: "Preset 3", description: "Name of your third preset position") 35 | input("cruisemap1", "text", title: "Cruise Map 1", description: "Name of your first cruise map") 36 | input("cruisemap2", "text", title: "Cruise Map 2", description: "Name of your second cruise map") 37 | } 38 | 39 | metadata { 40 | definition (name: "Foscam HD", namespace: "Ledridge", author: "Ledridge" ) { 41 | capability "Polling" 42 | capability "Image Capture" 43 | 44 | attribute "setStatus", "string" 45 | attribute "alarmStatus", "string" 46 | attribute "ledStatus", "string" 47 | 48 | command "alarmOn" 49 | command "alarmOff" 50 | command "toggleAlarm" 51 | command "left" 52 | command "right" 53 | command "up" 54 | command "down" 55 | command "stop" 56 | command "set" 57 | command "preset" 58 | command "preset1" 59 | command "preset2" 60 | command "preset3" 61 | command "cruisemap1" 62 | command "cruisemap2" 63 | command "cruise" 64 | command "toggleLED" 65 | command "ledOn" 66 | command "ledOff" 67 | command "ledAuto" 68 | command "reboot" 69 | } 70 | 71 | tiles { 72 | carouselTile("cameraDetails", "device.image", width: 3, height: 2) { } 73 | 74 | standardTile("foscam", "device.alarmStatus", width: 1, height: 1, canChangeIcon: true, inactiveLabel: true, canChangeBackground: false) { 75 | state "off", label: "off", action: "toggleAlarm", icon: "st.camera.dropcam-centered", backgroundColor: "#FFFFFF" 76 | state "on", label: "on", action: "toggleAlarm", icon: "st.camera.dropcam-centered", backgroundColor: "#53A7C0" 77 | } 78 | 79 | standardTile("camera", "device.image", width: 1, height: 1, canChangeIcon: false, inactiveLabel: true, canChangeBackground: false) { 80 | state "default", label: "", action: "Image Capture.take", icon: "st.camera.dropcam-centered", backgroundColor: "#FFFFFF" 81 | } 82 | 83 | standardTile("take", "device.image", width: 1, height: 1, canChangeIcon: false, inactiveLabel: true, canChangeBackground: false, decoration: "flat") { 84 | state "take", label: "", action: "Image Capture.take", icon: "st.secondary.take", nextState:"taking" 85 | } 86 | 87 | standardTile("up", "device.image", width: 1, height: 1, canChangeIcon: false, canChangeBackground: false, decoration: "flat") { 88 | state "up", label: "up", action: "up", icon: "st.thermostat.thermostat-up" 89 | } 90 | 91 | standardTile("alarmStatus", "device.alarmStatus", width: 1, height: 1, canChangeIcon: false, inactiveLabel: true, canChangeBackground: false) { 92 | state "off", label: "off", action: "toggleAlarm", icon: "st.quirky.spotter.quirky-spotter-sound-off", backgroundColor: "#FFFFFF" 93 | state "on", label: "on", action: "toggleAlarm", icon: "st.quirky.spotter.quirky-spotter-sound-on", backgroundColor: "#53A7C0" 94 | } 95 | 96 | standardTile("preset1", "device.image", width: 1, height: 1, canChangeIcon: false, canChangeBackground: false, decoration: "flat") { 97 | state "preset1", label: "preset 1", action: "preset1", icon: "" 98 | } 99 | 100 | standardTile("preset2", "device.image", width: 1, height: 1, canChangeIcon: false, canChangeBackground: false, decoration: "flat") { 101 | state "preset2", label: "preset 2", action: "preset2", icon: "" 102 | } 103 | 104 | standardTile("preset3", "device.image", width: 1, height: 1, canChangeIcon: false, canChangeBackground: false, decoration: "flat") { 105 | state "preset3", label: "preset 3", action: "preset3", icon: "" 106 | } 107 | 108 | standardTile("left", "device.image", width: 1, height: 1, canChangeIcon: false, canChangeBackground: false, decoration: "flat") { 109 | state "left", label: "left", action: "left", icon: "" 110 | } 111 | 112 | standardTile("stop", "device.image", width: 1, height: 1, canChangeIcon: false, canChangeBackground: false, decoration: "flat") { 113 | state "stop", label: "", action: "stop", icon: "st.sonos.stop-btn" 114 | } 115 | 116 | standardTile("right", "device.image", width: 1, height: 1, canChangeIcon: false, canChangeBackground: false, decoration: "flat") { 117 | state "right", label: "right", action: "right", icon: "" 118 | } 119 | 120 | standardTile("blank", "device.image", width: 1, height: 1, canChangeIcon: false, canChangeBackground: false, decoration: "flat") { 121 | state "blank", label: "", action: "stop", icon: "", backgroundColor: "#53A7C0" 122 | } 123 | 124 | standardTile("down", "device.image", width: 1, height: 1, canChangeIcon: false, canChangeBackground: false, decoration: "flat") { 125 | state "down", label: "down", action: "down", icon: "st.thermostat.thermostat-down" 126 | } 127 | 128 | standardTile("cruisemap1", "device.image", width: 1, height: 1, canChangeIcon: false, canChangeBackground: false, decoration: "flat") { 129 | state "cruisemap1", label: "Cruise Map 1", action: "cruisemap1", icon: "" 130 | } 131 | 132 | standardTile("cruisemap2", "device.image", width: 1, height: 1, canChangeIcon: false, canChangeBackground: false, decoration: "flat") { 133 | state "cruisemap2", label: "Cruise Map 2", action: "cruisemap2", icon: "" 134 | } 135 | 136 | standardTile("set", "device.setStatus", width: 1, height: 1, canChangeIcon: false, inactiveLabel: true, canChangeBackground: false) { 137 | state "set", label: "preset", action: "set", icon: "", backgroundColor: "#FFFFFF" 138 | state "add", label: "set mode", action: "set", icon: "", backgroundColor: "#53A7C0" 139 | } 140 | 141 | standardTile("refresh", "device.alarmStatus", inactiveLabel: false, decoration: "flat") { 142 | state "refresh", action:"polling.poll", icon:"st.secondary.refresh" 143 | } 144 | 145 | standardTile("ledStatus", "device.ledStatus", width: 1, height: 1, canChangeIcon: false, inactiveLabel: true, canChangeBackground: false) { 146 | state "auto", label: "auto", action: "toggleLED", icon: "st.Lighting.light13", backgroundColor: "#53A7C0" 147 | state "off", label: "off", action: "toggleLED", icon: "st.Lighting.light13", backgroundColor: "#FFFFFF" 148 | state "on", label: "on", action: "toggleLED", icon: "st.Lighting.light11", backgroundColor: "#FFFF00" 149 | state "manual", label: "manual", action: "toggleLED", icon: "st.Lighting.light13", backgroundColor: "#FFFF00" 150 | } 151 | 152 | standardTile("reboot", "device.image", inactiveLabel: false, decoration: "flat") { 153 | state "reboot", label: "reboot", action: "reboot", icon: "st.Health & Wellness.health8" 154 | } 155 | 156 | main "foscam" 157 | details(["cameraDetails", "take", "ledStatus", "alarmStatus", "preset1", "preset2", "preset3", "cruisemap1", "up", "cruisemap2", "left", "stop", "right", "blank", "down", ,"blank", "refresh", "set", "reboot"]) 158 | } 159 | } 160 | 161 | private getPictureName() { 162 | def pictureUuid = java.util.UUID.randomUUID().toString().replaceAll('-', '') 163 | "image" + "_$pictureUuid" + ".jpg" 164 | } 165 | 166 | def take() { 167 | log.debug("Take a photo") 168 | 169 | api("snapshot", "") { 170 | log.debug("Image captured") 171 | 172 | if(it.headers.'Content-Type'.contains("image/jpeg")) { 173 | if(it.data) { 174 | storeImage(getPictureName(), it.data) 175 | } 176 | } 177 | } 178 | } 179 | 180 | def toggleAlarm() { 181 | if(device.currentValue("alarmStatus") == "on") { 182 | alarmOff() 183 | } 184 | 185 | else { 186 | alarmOn() 187 | } 188 | } 189 | 190 | def alarmOn() { 191 | api("set_alarm", "isEnable=1") { 192 | log.debug("Alarm changed to: on") 193 | sendEvent(name: "alarmStatus", value: "on"); 194 | } 195 | } 196 | 197 | def alarmOff() { 198 | api("set_alarm", "isEnable=0") { 199 | log.debug("Alarm changed to: off") 200 | sendEvent(name: "alarmStatus", value: "off"); 201 | } 202 | } 203 | 204 | def left() { 205 | api("decoder_control", "cmd=ptzMoveLeft") { 206 | log.debug("Executing 'LEFT'") 207 | } 208 | stop() 209 | } 210 | 211 | def right() { 212 | api("decoder_control", "cmd=ptzMoveRight") { 213 | log.debug("Executing 'RIGHT'") 214 | } 215 | stop() 216 | } 217 | 218 | def up() { 219 | api("decoder_control", "cmd=ptzMoveUp") { 220 | log.debug("Executing 'UP'") 221 | } 222 | stop() 223 | } 224 | 225 | def down() { 226 | api("decoder_control", "cmd=ptzMoveDown") { 227 | log.debug("Executing 'DOWN'") 228 | } 229 | stop() 230 | } 231 | 232 | def stop() { 233 | api("decoder_control", "cmd=ptzStopRun") { 234 | log.debug("Executing 'STOP'") 235 | } 236 | } 237 | 238 | def preset1() { 239 | log.debug("Preset 1 Selected - ${preset1}") 240 | preset("${preset1}") 241 | } 242 | 243 | def preset2() { 244 | log.debug("Preset 2 Selected - ${preset2}") 245 | preset("${preset2}") 246 | } 247 | 248 | def preset3() { 249 | log.debug("Preset 3 Selected - ${preset3}") 250 | preset("${preset3}") 251 | } 252 | 253 | //Go to a preset location 254 | def preset(def presetname) { 255 | if(presetname == null) return 256 | 257 | if(device.currentValue("setStatus") == "add") { 258 | setPreset(presetname) 259 | } 260 | 261 | else { 262 | log.debug("Go To Preset Location - " + presetname) 263 | def cmd = "cmd=ptzGotoPresetPoint&name=" + presetname 264 | 265 | api("decoder_control", "${cmd}") {} 266 | } 267 | } 268 | 269 | //Set the selected preset to the current location 270 | def setPreset(def presetname) { 271 | log.debug("Set Preset - " + presetname) 272 | delPreset(presetname) 273 | addPreset(presetname) 274 | 275 | log.debug("Exit Add Preset Mode") 276 | sendEvent(name: "setStatus", value: "set"); 277 | } 278 | 279 | //Delete currently selected preset 280 | def delPreset(def presetname) { 281 | log.debug("Delete Preset Location - " + presetname) 282 | def cmd = "cmd=ptzDeletePresetPoint&name=" + presetname 283 | api("decoder_control", "${cmd}") {} 284 | } 285 | 286 | //Add currently selected preset 287 | def addPreset(def presetname) { 288 | log.debug("Add Preset Location - " + presetname) 289 | def cmd = "cmd=ptzAddPresetPoint&name=" + presetname 290 | api("decoder_control", "${cmd}") {} 291 | } 292 | 293 | //Toggle the the mode to set the preset 294 | def set() { 295 | if((device.currentValue("setStatus") == "set").or(device.currentValue("setStatus") == "")) { 296 | log.debug("Entering Add Preset Mode") 297 | sendEvent(name: "setStatus", value: "add"); 298 | } 299 | 300 | else { 301 | log.debug("Exit Add Preset Mode") 302 | sendEvent(name: "setStatus", value: "set"); 303 | } 304 | } 305 | 306 | def cruisemap1() { 307 | log.debug("Cruise Map 1 Selected - ${cruisemap1}") 308 | cruise("${cruisemap1}") 309 | } 310 | 311 | def cruisemap2() { 312 | log.debug("Cruise Map 2 Selected - ${cruisemap2}") 313 | cruise("${cruisemap2}") 314 | } 315 | 316 | //Start cruise 317 | def cruise(def cruisename) { 318 | 319 | log.debug("Start Cruise Map - " + cruisename) 320 | def cmd = "cmd=ptzStartCruise&mapName=" + cruisename 321 | 322 | api("decoder_control", "${cmd}") {} 323 | 324 | } 325 | 326 | //Toggle LED's 327 | def toggleLED() { 328 | log.debug("Toggle LED") 329 | 330 | if(device.currentValue("ledStatus") == "auto") { 331 | ledOn() 332 | } 333 | 334 | else if(device.currentValue("ledStatus") == "on") { 335 | ledOff() 336 | } 337 | 338 | else { 339 | ledAuto() 340 | } 341 | } 342 | 343 | def ledOn() { 344 | api("decoder_control", "cmd=setInfraLedConfig&mode=1") {} 345 | api("decoder_control", "cmd=openInfraLed") { 346 | log.debug("LED changed to: on") 347 | sendEvent(name: "ledStatus", value: "on"); 348 | } 349 | } 350 | 351 | def ledOff() { 352 | api("decoder_control", "cmd=setInfraLedConfig&mode=1") {} 353 | api("decoder_control", "cmd=closeInfraLed") { 354 | log.debug("LED changed to: off") 355 | sendEvent(name: "ledStatus", value: "off"); 356 | } 357 | } 358 | 359 | def ledAuto() { 360 | api("decoder_control", "cmd=setInfraLedConfig&mode=0") { 361 | log.debug("LED changed to: auto") 362 | sendEvent(name: "ledStatus", value: "auto"); 363 | } 364 | } 365 | 366 | def reboot() { 367 | api("reboot", "") { 368 | log.debug("Rebooting") 369 | } 370 | } 371 | 372 | def api(method, args = [], success = {}) { 373 | def methods = [ 374 | "decoder_control": [uri: "http://${ip}:${port}/cgi-bin/CGIProxy.fcgi${login()}&${args}", type: "get"], 375 | "snapshot": [uri: "http://${ip}:${port}/cgi-bin/CGIProxy.fcgi${login()}&cmd=snapPicture2", type: "get"], 376 | "set_alarm": [uri: "http://${ip}:${port}/cgi-bin/CGIProxy.fcgi${login()}&cmd=setMotionDetectConfig&${args}", type: "get"], 377 | "reboot": [uri: "http://${ip}:${port}/cgi-bin/CGIProxy.fcgi${login()}&cmd=rebootSystem", type: "get"], 378 | "camera_control": [uri: "http://${ip}:${port}/camera_control.cgi${login()}&${args}", type: "get"], 379 | "get_params": [uri: "http://${ip}:${port}/cgi-bin/CGIProxy.fcgi${login()}&${args}", type: "get"] 380 | ] 381 | 382 | def request = methods.getAt(method) 383 | 384 | doRequest(request.uri, request.type, success) 385 | } 386 | 387 | private doRequest(uri, type, success) { 388 | log.debug(uri) 389 | httpGet(uri, success) 390 | } 391 | 392 | private login() { 393 | return "?usr=${username}&pwd=${password}" 394 | } 395 | 396 | def poll() { 397 | //Poll Motion Alarm Status 398 | api("get_params", "cmd=getMotionDetectConfig") { 399 | def CGI_Result = new XmlParser().parse(it.data) 400 | def motionAlarm = CGI_Result.isEnable.text() 401 | 402 | if(motionAlarm == "0") { 403 | log.info("Polled: Alarm Off") 404 | sendEvent(name: "alarmStatus", value: "off"); 405 | } 406 | 407 | if(motionAlarm == "1") { 408 | log.info("Polled: Alarm On") 409 | sendEvent(name: "alarmStatus", value: "on"); 410 | } 411 | } 412 | 413 | //Poll IR LED Mode 414 | api("get_params", "cmd=getInfraLedConfig") { 415 | def CGI_Result = new XmlParser().parse(it.data) 416 | def ledMode = CGI_Result.mode.text() 417 | 418 | if(ledMode == "0") { 419 | log.info("Polled: LED Mode Auto") 420 | sendEvent(name: "ledStatus", value: "auto"); 421 | } 422 | 423 | if(ledMode == "1") { 424 | log.info("Polled: LED Mode Manual") 425 | sendEvent(name: "ledStatus", value: "manual"); 426 | } 427 | } 428 | 429 | //Reset 430 | } -------------------------------------------------------------------------------- /devicetypes/ledridge/my-garage-door-opener.src/my-garage-door-opener.groovy: -------------------------------------------------------------------------------- 1 | /** 2 | * Z-Wave Garage Door Opener 3 | * 4 | * Copyright 2014 SmartThings 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 7 | * in compliance with the License. You may obtain a copy of the License at: 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 12 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License 13 | * for the specific language governing permissions and limitations under the License. 14 | * 15 | */ 16 | metadata { 17 | definition (name: "My Garage Door Opener", namespace: "ledridge", author: "ledridge") { 18 | capability "Actuator" 19 | capability "Door Control" 20 | capability "Contact Sensor" 21 | capability "Refresh" 22 | capability "Sensor" 23 | capability "Polling" 24 | capability "Switch" 25 | capability "Momentary" 26 | capability "Relay Switch" 27 | capability "Garage Door Control" 28 | 29 | 30 | fingerprint deviceId: "0x4007", inClusters: "0x98" 31 | fingerprint deviceId: "0x4006", inClusters: "0x98" 32 | } 33 | 34 | simulator { 35 | status "closed": "command: 9881, payload: 00 66 03 00" 36 | status "opening": "command: 9881, payload: 00 66 03 FE" 37 | status "open": "command: 9881, payload: 00 66 03 FF" 38 | status "closing": "command: 9881, payload: 00 66 03 FC" 39 | status "unknown": "command: 9881, payload: 00 66 03 FD" 40 | 41 | reply "988100660100": "command: 9881, payload: 00 66 03 FC" 42 | reply "9881006601FF": "command: 9881, payload: 00 66 03 FE" 43 | } 44 | 45 | tiles { 46 | standardTile("toggle", "device.door", width: 2, height: 2) { 47 | state("unknown", label:'${name}', action:"refresh.refresh", icon:"st.doors.garage.garage-open", backgroundColor:"#ffa81e") 48 | state("closed", label:'${name}', action:"door control.open", icon:"st.doors.garage.garage-closed", backgroundColor:"#79b821", nextState:"opening") 49 | state("open", label:'${name}', action:"door control.close", icon:"st.doors.garage.garage-open", backgroundColor:"#ffa81e", nextState:"closing") 50 | state("opening", label:'${name}', icon:"st.doors.garage.garage-opening", backgroundColor:"#ffe71e") 51 | state("closing", label:'${name}', icon:"st.doors.garage.garage-closing", backgroundColor:"#ffe71e") 52 | 53 | } 54 | standardTile("open", "device.door", inactiveLabel: false, decoration: "flat") { 55 | state "default", label:'open', action:"door control.open", icon:"st.doors.garage.garage-opening" 56 | } 57 | standardTile("close", "device.door", inactiveLabel: false, decoration: "flat") { 58 | state "default", label:'close', action:"door control.close", icon:"st.doors.garage.garage-closing" 59 | } 60 | standardTile("refresh", "device.door", inactiveLabel: false, decoration: "flat") { 61 | state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh" 62 | } 63 | main "toggle" 64 | details(["toggle", "open", "close", "refresh"]) 65 | } 66 | } 67 | 68 | 69 | import physicalgraph.zwave.commands.barrieroperatorv1.* 70 | 71 | def parse(String description) { 72 | def result = null 73 | if (description.startsWith("Err")) { 74 | if (state.sec) { 75 | result = createEvent(descriptionText:description, displayed:false) 76 | } else { 77 | result = createEvent( 78 | descriptionText: "This device failed to complete the network security key exchange. If you are unable to control it via SmartThings, you must remove it from your network and add it again.", 79 | eventType: "ALERT", 80 | name: "secureInclusion", 81 | value: "failed", 82 | displayed: true, 83 | ) 84 | } 85 | } else { 86 | def cmd = zwave.parse(description, [ 0x98: 1, 0x72: 2 ]) 87 | if (cmd) { 88 | result = zwaveEvent(cmd) 89 | } 90 | } 91 | log.debug "\"$description\" parsed to ${result.inspect()}" 92 | result 93 | } 94 | 95 | def zwaveEvent(physicalgraph.zwave.commands.securityv1.SecurityMessageEncapsulation cmd) { 96 | def encapsulatedCommand = cmd.encapsulatedCommand([0x71: 3, 0x80: 1, 0x85: 2, 0x63: 1, 0x98: 1]) 97 | log.debug "encapsulated: $encapsulatedCommand" 98 | if (encapsulatedCommand) { 99 | zwaveEvent(encapsulatedCommand) 100 | } 101 | } 102 | 103 | def zwaveEvent(physicalgraph.zwave.commands.securityv1.NetworkKeyVerify cmd) { 104 | createEvent(name:"secureInclusion", value:"success", descriptionText:"Secure inclusion was successful") 105 | } 106 | 107 | def zwaveEvent(physicalgraph.zwave.commands.securityv1.SecurityCommandsSupportedReport cmd) { 108 | state.sec = cmd.commandClassSupport.collect { String.format("%02X ", it) }.join() 109 | if (cmd.commandClassControl) { 110 | state.secCon = cmd.commandClassControl.collect { String.format("%02X ", it) }.join() 111 | } 112 | log.debug "Security command classes: $state.sec" 113 | createEvent(name:"secureInclusion", value:"success", descriptionText:"$device.displayText is securely included") 114 | } 115 | 116 | def zwaveEvent(BarrierOperatorReport cmd) { 117 | def result = [] 118 | def map = [ name: "door" ] 119 | def switchMap = [ name: "switch" ] 120 | 121 | switch (cmd.barrierState) { 122 | case BarrierOperatorReport.BARRIER_STATE_CLOSED: 123 | map.value = "closed" 124 | result << createEvent(name: "contact", value: "closed", displayed: false) 125 | result << createEvent(name: "switch", value: "off", displayed: false) 126 | break 127 | case BarrierOperatorReport.BARRIER_STATE_UNKNOWN_POSITION_MOVING_TO_CLOSE: 128 | map.value = "closing" 129 | break 130 | case BarrierOperatorReport.BARRIER_STATE_UNKNOWN_POSITION_STOPPED: 131 | map.descriptionText = "$device.displayName door state is unknown" 132 | map.value = "unknown" 133 | break 134 | case BarrierOperatorReport.BARRIER_STATE_UNKNOWN_POSITION_MOVING_TO_OPEN: 135 | map.value = "opening" 136 | result << createEvent(name: "contact", value: "open", displayed: false) 137 | break 138 | case BarrierOperatorReport.BARRIER_STATE_OPEN: 139 | map.value = "open" 140 | result << createEvent(name: "contact", value: "open", displayed: false) 141 | result << createEvent(name: "switch", value: "on", displayed: false) 142 | break 143 | } 144 | result + createEvent(map) 145 | } 146 | 147 | def zwaveEvent(physicalgraph.zwave.commands.notificationv3.NotificationReport cmd) { 148 | def result = [] 149 | def map = [:] 150 | if (cmd.notificationType == 6) { 151 | map.displayed = true 152 | switch(cmd.event) { 153 | case 0x40: 154 | if (cmd.eventParameter[0]) { 155 | map.descriptionText = "$device.displayName performing initialization process" 156 | } else { 157 | map.descriptionText = "$device.displayName initialization process complete" 158 | } 159 | break 160 | case 0x41: 161 | map.descriptionText = "$device.displayName door operation force has been exceeded" 162 | break 163 | case 0x42: 164 | map.descriptionText = "$device.displayName motor has exceeded operational time limit" 165 | break 166 | case 0x43: 167 | map.descriptionText = "$device.displayName has exceeded physical mechanical limits" 168 | break 169 | case 0x44: 170 | map.descriptionText = "$device.displayName unable to perform requested operation (UL requirement)" 171 | break 172 | case 0x45: 173 | map.descriptionText = "$device.displayName remote operation disabled (UL requirement)" 174 | break 175 | case 0x46: 176 | map.descriptionText = "$device.displayName failed to perform operation due to device malfunction" 177 | break 178 | case 0x47: 179 | if (cmd.eventParameter[0]) { 180 | map.descriptionText = "$device.displayName vacation mode enabled" 181 | } else { 182 | map.descriptionText = "$device.displayName vacation mode disabled" 183 | } 184 | break 185 | case 0x48: 186 | if (cmd.eventParameter[0]) { 187 | map.descriptionText = "$device.displayName safety beam obstructed" 188 | } else { 189 | map.descriptionText = "$device.displayName safety beam obstruction cleared" 190 | } 191 | break 192 | case 0x49: 193 | if (cmd.eventParameter[0]) { 194 | map.descriptionText = "$device.displayName door sensor ${cmd.eventParameter[0]} not detected" 195 | } else { 196 | map.descriptionText = "$device.displayName door sensor not detected" 197 | } 198 | break 199 | case 0x4A: 200 | if (cmd.eventParameter[0]) { 201 | map.descriptionText = "$device.displayName door sensor ${cmd.eventParameter[0]} has a low battery" 202 | } else { 203 | map.descriptionText = "$device.displayName door sensor has a low battery" 204 | } 205 | result << createEvent(name: "battery", value: 1, unit: "%", descriptionText: map.descriptionText) 206 | break 207 | case 0x4B: 208 | map.descriptionText = "$device.displayName detected a short in wall station wires" 209 | break 210 | case 0x4C: 211 | map.descriptionText = "$device.displayName is associated with non-Z-Wave remote control" 212 | break 213 | default: 214 | map.descriptionText = "$device.displayName: access control alarm $cmd.event" 215 | map.displayed = false 216 | break 217 | } 218 | } else if (cmd.notificationType == 7) { 219 | switch (cmd.event) { 220 | case 1: 221 | case 2: 222 | map.descriptionText = "$device.displayName detected intrusion" 223 | break 224 | case 3: 225 | map.descriptionText = "$device.displayName tampering detected: product cover removed" 226 | break 227 | case 4: 228 | map.descriptionText = "$device.displayName tampering detected: incorrect code" 229 | break 230 | case 7: 231 | case 8: 232 | map.descriptionText = "$device.displayName detected motion" 233 | break 234 | default: 235 | map.descriptionText = "$device.displayName: security alarm $cmd.event" 236 | map.displayed = false 237 | } 238 | } else if (cmd.notificationType){ 239 | map.descriptionText = "$device.displayName: alarm type $cmd.notificationType event $cmd.event" 240 | } else { 241 | map.descriptionText = "$device.displayName: alarm $cmd.v1AlarmType is ${cmd.v1AlarmLevel == 255 ? 'active' : cmd.v1AlarmLevel ?: 'inactive'}" 242 | } 243 | result ? [createEvent(map), *result] : createEvent(map) 244 | } 245 | 246 | def zwaveEvent(physicalgraph.zwave.commands.batteryv1.BatteryReport cmd) { 247 | def map = [ name: "battery", unit: "%" ] 248 | if (cmd.batteryLevel == 0xFF) { 249 | map.value = 1 250 | map.descriptionText = "$device.displayName has a low battery" 251 | } else { 252 | map.value = cmd.batteryLevel 253 | } 254 | state.lastbatt = new Date().time 255 | createEvent(map) 256 | } 257 | 258 | def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerSpecificReport cmd) { 259 | def result = [] 260 | 261 | def msr = String.format("%04X-%04X-%04X", cmd.manufacturerId, cmd.productTypeId, cmd.productId) 262 | log.debug "msr: $msr" 263 | updateDataValue("MSR", msr) 264 | 265 | result << createEvent(descriptionText: "$device.displayName MSR: $msr", isStateChange: false) 266 | result 267 | } 268 | 269 | def zwaveEvent(physicalgraph.zwave.commands.versionv1.VersionReport cmd) { 270 | def fw = "${cmd.applicationVersion}.${cmd.applicationSubVersion}" 271 | updateDataValue("fw", fw) 272 | def text = "$device.displayName: firmware version: $fw, Z-Wave version: ${cmd.zWaveProtocolVersion}.${cmd.zWaveProtocolSubVersion}" 273 | createEvent(descriptionText: text, isStateChange: false) 274 | } 275 | 276 | def zwaveEvent(physicalgraph.zwave.commands.applicationstatusv1.ApplicationBusy cmd) { 277 | def msg = cmd.status == 0 ? "try again later" : 278 | cmd.status == 1 ? "try again in $cmd.waitTime seconds" : 279 | cmd.status == 2 ? "request queued" : "sorry" 280 | createEvent(displayed: true, descriptionText: "$device.displayName is busy, $msg") 281 | } 282 | 283 | def zwaveEvent(physicalgraph.zwave.commands.applicationstatusv1.ApplicationRejectedRequest cmd) { 284 | createEvent(displayed: true, descriptionText: "$device.displayName rejected the last request") 285 | } 286 | 287 | def zwaveEvent(physicalgraph.zwave.Command cmd) { 288 | createEvent(displayed: false, descriptionText: "$device.displayName: $cmd") 289 | } 290 | 291 | def open() { 292 | secure(zwave.barrierOperatorV1.barrierOperatorSet(requestedBarrierState: BarrierOperatorSet.REQUESTED_BARRIER_STATE_OPEN)) 293 | } 294 | 295 | def close() { 296 | secure(zwave.barrierOperatorV1.barrierOperatorSet(requestedBarrierState: BarrierOperatorSet.REQUESTED_BARRIER_STATE_CLOSE)) 297 | } 298 | 299 | 300 | def refresh() { 301 | secure(zwave.barrierOperatorV1.barrierOperatorGet()) 302 | } 303 | 304 | def poll() { 305 | secure(zwave.barrierOperatorV1.barrierOperatorGet()) 306 | } 307 | 308 | 309 | def on() { 310 | log.debug "on() was called and ignored" 311 | } 312 | 313 | def off() { 314 | log.debug "off() was called and ignored" 315 | } 316 | 317 | def push() { 318 | 319 | // get the current "door" attribute value 320 | // 321 | // For some reason, I can't use "device.doorState" or just "doorState". Not sure why not. 322 | 323 | def lastValue = device.latestValue("door"); 324 | 325 | // if its open, then close the door 326 | if (lastValue == "open") { 327 | return close() 328 | 329 | // if its closed, then open the door 330 | } else if (lastValue == "closed") { 331 | return open() 332 | 333 | } else { 334 | log.debug "push() called when door state is $lastValue - there's nothing push() can do" 335 | } 336 | 337 | } 338 | 339 | 340 | 341 | private secure(physicalgraph.zwave.Command cmd) { 342 | zwave.securityV1.securityMessageEncapsulation().encapsulate(cmd).format() 343 | } 344 | 345 | private secureSequence(commands, delay=200) { 346 | delayBetween(commands.collect{ secure(it) }, delay) 347 | } -------------------------------------------------------------------------------- /devicetypes/ledridge/raspberry-pi.src/raspberry-pi.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 | import com.google.common.base.Splitter; 24 | import java.util.List; 25 | import java.util.Map; 26 | import java.util.Map.Entry; 27 | 28 | 29 | preferences { 30 | input("ip", "string", title:"IP Address", description: "192.168.1.50", defaultValue: "192.168.1.50" ,required: true, displayDuringSetup: true) 31 | input("port", "string", title:"Port", description: "80", defaultValue: "80" , required: true, displayDuringSetup: true) 32 | input("username", "string", title:"Username", description: "pi", defaultValue: "pi" , required: true, displayDuringSetup: true) 33 | input("password", "password", title:"Password", description: "raspberry", defaultValue: "raspberry" , required: true, displayDuringSetup: true) 34 | } 35 | 36 | metadata { 37 | definition (name: "Raspberry Pi", namespace: "Ledridge", author: "Ledridge") { 38 | capability "Polling" 39 | capability "Refresh" 40 | capability "Temperature Measurement" 41 | capability "Switch" 42 | capability "Sensor" 43 | capability "Actuator" 44 | 45 | attribute "cpuPercentage", "string" 46 | attribute "memory", "string" 47 | attribute "diskUsage", "string" 48 | 49 | command "restart" 50 | } 51 | 52 | simulator { 53 | // TODO: define status and reply messages here 54 | } 55 | 56 | tiles { 57 | valueTile("temperature", "device.temperature", width: 1, height: 1) { 58 | state "temperature", label:'${currentValue}° CPU', unit: "F", 59 | backgroundColors:[ 60 | [value: 25, color: "#153591"], 61 | [value: 35, color: "#1e9cbb"], 62 | [value: 47, color: "#90d2a7"], 63 | [value: 59, color: "#44b621"], 64 | [value: 67, color: "#f1d801"], 65 | [value: 76, color: "#d04e00"], 66 | [value: 77, color: "#bc2323"] 67 | ] 68 | } 69 | standardTile("button", "device.switch", width: 1, height: 1, canChangeIcon: true) { 70 | state "off", label: 'Off', icon: "st.Electronics.electronics18", backgroundColor: "#ffffff", nextState: "on" 71 | state "on", label: 'On', icon: "st.Electronics.electronics18", backgroundColor: "#79b821", nextState: "off" 72 | } 73 | valueTile("cpuPercentage", "device.cpuPercentage", inactiveLabel: false, decoration: "flat") { 74 | state "default", label:'${currentValue}% CPU', unit:"Percentage", icon: "http://chart.apis.google.com/chart?cht=p&chs=120x120&chd=t:80,20&chco=ff000d&chf=bg,s,FFFFFF|c,s,FFFFFF", 75 | backgroundColors:[ 76 | [value: 31, color: "#153591"], 77 | [value: 44, color: "#1e9cbb"], 78 | [value: 59, color: "#90d2a7"], 79 | [value: 74, color: "#44b621"], 80 | [value: 84, color: "#f1d801"], 81 | [value: 95, color: "#d04e00"], 82 | [value: 96, color: "#bc2323"] 83 | ] 84 | } 85 | valueTile("memory", "device.memory", width: 1, height: 1) { 86 | state "default", label:'${currentValue} MB', unit:"MB", 87 | backgroundColors:[ 88 | [value: 353, color: "#153591"], 89 | [value: 287, color: "#1e9cbb"], 90 | [value: 210, color: "#90d2a7"], 91 | [value: 133, color: "#44b621"], 92 | [value: 82, color: "#f1d801"], 93 | [value: 26, color: "#d04e00"], 94 | [value: 20, color: "#bc2323"] 95 | ] 96 | } 97 | valueTile("diskUsage", "device.diskUsage", width: 1, height: 1) { 98 | state "default", label:'${currentValue}% Disk', unit:"Percent", 99 | backgroundColors:[ 100 | [value: 31, color: "#153591"], 101 | [value: 44, color: "#1e9cbb"], 102 | [value: 59, color: "#90d2a7"], 103 | [value: 74, color: "#44b621"], 104 | [value: 84, color: "#f1d801"], 105 | [value: 95, color: "#d04e00"], 106 | [value: 96, color: "#bc2323"] 107 | ] 108 | } 109 | standardTile("restart", "device.restart", inactiveLabel: false, decoration: "flat") { 110 | state "default", action:"restart", label: "Restart", displayName: "Restart" 111 | } 112 | standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat") { 113 | state "default", action:"refresh.refresh", icon: "st.secondary.refresh" 114 | } 115 | main "button" 116 | details(["button", "temperature", "cpuPercentage", "memory" , "diskUsage", "restart", "refresh"]) 117 | } 118 | } 119 | 120 | // ------------------------------------------------------------------ 121 | 122 | // parse events into attributes 123 | def parse(String description) { 124 | def map = [:] 125 | def descMap = parseDescriptionAsMap(description) 126 | log.debug "descMap: ${descMap}" 127 | 128 | def body = new String(descMap["body"].decodeBase64()) 129 | log.debug "body: ${body}" 130 | 131 | def slurper = new JsonSlurper() 132 | def result = slurper.parseText(body) 133 | 134 | log.debug "result: ${result}" 135 | 136 | if (result){ 137 | log.debug "Computer is up" 138 | sendEvent(name: "switch", value: "on") 139 | } 140 | 141 | log.debug "check temp..." 142 | if (result.containsKey("cpu_temp")) { 143 | log.debug "temp: ${result.cpu_temp}" 144 | log.debug "temp: ${celsiusToFahrenheit(result.cpu_temp.toDouble())} F" 145 | sendEvent(name: "temperature", value: celsiusToFahrenheit(result.cpu_temp.toDouble())) 146 | } 147 | 148 | if (result.containsKey("cpu_perc")) { 149 | sendEvent(name: "cpuPercentage", value: result.cpu_perc) 150 | } 151 | 152 | if (result.containsKey("mem_avail")) { 153 | log.debug "mem_avail: ${result.mem_avail}" 154 | sendEvent(name: "memory", value: result.mem_avail) 155 | } 156 | if (result.containsKey("disk_usage")) { 157 | log.debug "disk_usage: ${result.disk_usage}" 158 | sendEvent(name: "diskUsage", value: result.disk_usage) 159 | } 160 | 161 | } 162 | 163 | // handle commands 164 | def poll() { 165 | log.debug "Executing 'poll'" 166 | sendEvent(name: "switch", value: "off") 167 | getRPiData() 168 | } 169 | 170 | def refresh() { 171 | sendEvent(name: "switch", value: "off") 172 | log.debug "Executing 'refresh'" 173 | getRPiData() 174 | } 175 | 176 | def restart(){ 177 | log.debug "Restart was pressed" 178 | sendEvent(name: "switch", value: "off") 179 | def uri = "/api_command/reboot" 180 | postAction(uri) 181 | } 182 | 183 | // Get CPU percentage reading 184 | private getRPiData() { 185 | def uri = "/api_command/smartthings" 186 | postAction(uri) 187 | } 188 | 189 | // ------------------------------------------------------------------ 190 | 191 | private postAction(uri){ 192 | setDeviceNetworkId(ip,port) 193 | 194 | def userpass = encodeCredentials(username, password) 195 | //log.debug("userpass: " + userpass) 196 | 197 | def headers = getHeader(userpass) 198 | //log.debug("headders: " + headers) 199 | 200 | def hubAction = new physicalgraph.device.HubAction( 201 | method: "POST", 202 | path: uri, 203 | headers: headers 204 | )//,delayAction(1000), refresh()] 205 | log.debug("Executing hubAction on " + getHostAddress()) 206 | //log.debug hubAction 207 | hubAction 208 | } 209 | 210 | // ------------------------------------------------------------------ 211 | // Helper methods 212 | // ------------------------------------------------------------------ 213 | 214 | def parseDescriptionAsMap(description) { 215 | description.split(",").inject([:]) { map, param -> 216 | def nameAndValue = param.split(":") 217 | map += [(nameAndValue[0].trim()):nameAndValue[1].trim()] 218 | } 219 | } 220 | 221 | 222 | def toAscii(s){ 223 | StringBuilder sb = new StringBuilder(); 224 | String ascString = null; 225 | long asciiInt; 226 | for (int i = 0; i < s.length(); i++){ 227 | sb.append((int)s.charAt(i)); 228 | sb.append("|"); 229 | char c = s.charAt(i); 230 | } 231 | ascString = sb.toString(); 232 | asciiInt = Long.parseLong(ascString); 233 | return asciiInt; 234 | } 235 | 236 | 237 | private encodeCredentials(username, password){ 238 | log.debug "Encoding credentials" 239 | def userpassascii = "${username}:${password}" 240 | def userpass = "Basic " + userpassascii.encodeAsBase64().toString() 241 | //log.debug "ASCII credentials are ${userpassascii}" 242 | //log.debug "Credentials are ${userpass}" 243 | return userpass 244 | } 245 | 246 | private getHeader(userpass){ 247 | log.debug "Getting headers" 248 | def headers = [:] 249 | headers.put("HOST", getHostAddress()) 250 | headers.put("Authorization", userpass) 251 | //log.debug "Headers are ${headers}" 252 | return headers 253 | } 254 | 255 | private delayAction(long time) { 256 | new physicalgraph.device.HubAction("delay $time") 257 | } 258 | 259 | private setDeviceNetworkId(ip,port){ 260 | def iphex = convertIPtoHex(ip) 261 | def porthex = convertPortToHex(port) 262 | device.deviceNetworkId = "$iphex:$porthex" 263 | log.debug "Device Network Id set to ${iphex}:${porthex}" 264 | } 265 | 266 | private getHostAddress() { 267 | return "${ip}:${port}" 268 | } 269 | 270 | private String convertIPtoHex(ipAddress) { 271 | String hex = ipAddress.tokenize( '.' ).collect { String.format( '%02x', it.toInteger() ) }.join() 272 | return hex 273 | 274 | } 275 | 276 | private String convertPortToHex(port) { 277 | String hexport = port.toString().format( '%04x', port.toInteger() ) 278 | return hexport 279 | } -------------------------------------------------------------------------------- /devicetypes/ledridge/virtual-motion-detector.src/virtual-motion-detector.groovy: -------------------------------------------------------------------------------- 1 | metadata { 2 | // Automatically generated. Make future change here. 3 | definition (name: "Virtual Motion Detector", namespace: "Ledridge", author: "Ledridge") { 4 | capability "Motion Sensor" 5 | capability "Sensor" 6 | 7 | fingerprint profileId: "0104", deviceId: "0402", inClusters: "0000,0001,0003,0009,0500" 8 | } 9 | 10 | // simulator metadata 11 | simulator { 12 | status "active": "zone report :: type: 19 value: 0031" 13 | status "inactive": "zone report :: type: 19 value: 0030" 14 | } 15 | 16 | // UI tile definitions 17 | tiles { 18 | standardTile("motion", "device.motion", width: 2, height: 2) { 19 | state("active", label:'motion', icon:"st.motion.motion.active", backgroundColor:"#53a7c0") 20 | state("inactive", label:'no motion', icon:"st.motion.motion.inactive", backgroundColor:"#ffffff") 21 | } 22 | 23 | main "motion" 24 | details "motion" 25 | } 26 | } 27 | 28 | // Parse incoming device messages to generate events 29 | def parse(String description) { 30 | def name = null 31 | def value = description 32 | def descriptionText = null 33 | if (zigbee.isZoneType19(description)) { 34 | name = "motion" 35 | def isActive = zigbee.translateStatusZoneType19(description) 36 | value = isActive ? "active" : "inactive" 37 | descriptionText = isActive ? "${device.displayName} detected motion" : "${device.displayName} motion has stopped" 38 | } 39 | 40 | def result = createEvent( 41 | name: name, 42 | value: value, 43 | descriptionText: descriptionText 44 | ) 45 | 46 | log.debug "Parse returned ${result?.descriptionText}" 47 | return result 48 | } 49 | -------------------------------------------------------------------------------- /devicetypes/ledridge/vtile-dli.src/vtile-dli.groovy: -------------------------------------------------------------------------------- 1 | /** 2 | * V Tile device-type for Digital Loggers Inc. 3 | * 4 | * Needed for Multi Switch with Virtual Tiles to create virtual switch tiles in ST for devices that have multiple "switch[x]" 5 | * attributes within them and have on[x], off[x], and cycle[x] commands for each. 6 | * Also has support for device-label inside the name when on or off and polling occurs 7 | * 8 | */ 9 | metadata { 10 | definition (name: "vTile_DLI", namespace: "Ledridge", author: "Ledridge") { 11 | capability "Switch" 12 | capability "relaySwitch" 13 | capability "Polling" 14 | capability "Refresh" 15 | 16 | attribute "lastEvent", "string" 17 | 18 | command "cycle" 19 | } 20 | } 21 | 22 | preferences { 23 | tiles { 24 | standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true, canChangeBackground: true) { 25 | state "off", label:'${name}', action: "switch.on", icon:"st.switches.switch.off", backgroundColor: "#DDDDff", nextState: "turningOn" 26 | state "on", label:'${name}', action: "switch.off", icon:"st.switches.switch.on", backgroundColor: "#0088ff", nextState: "turningOff" 27 | state "turningOff", label:'${name}', action: "switch.on", icon:"st.switches.switch.off", backgroundColor: "#FA5882", nextState: "off" 28 | state "turningOn", label:'${name}', action: "switch.on", icon:"st.switches.switch.on", backgroundColor: "#F3F781", nextState: "on" 29 | state "cyclingOff", label:"Turning Off", icon:"st.switches.switch.off", backgroundColor: "#FA5882", nextState: "cyclingOn" 30 | state "cyclingOn", label:"Turning On", icon:"st.switches.switch.on", backgroundColor: "#F3F781", nextState: "on" 31 | } 32 | 33 | standardTile("Cycle", "device.switch", width: 1, height: 2, canChangeIcon: true) { 34 | state "default", action: "cycle", icon: "st.secondary.refresh-icon", backgroundColor: "#0088ff" 35 | } 36 | 37 | valueTile("lastEvent", "device.lastEvent", inactiveLabel: false, width: 3, height: 1, canChangeIcon: false, decoration:"flat") { 38 | state "default", label: 'Last Event: ${currentValue}'} 39 | 40 | main "switch" 41 | details(["switch", "Cycle", "lastEvent"]) 42 | } 43 | } 44 | 45 | def parse(desc) { 46 | def results = [] 47 | log.debug desc 48 | if(desc=="updated") { log.debug "Device $device.label has been UPDATED"; poll() } 49 | } 50 | 51 | def on() { 52 | sendEvent([name: "switch", value: "on"]) 53 | parent.OutletAction(this,"ON") 54 | sendEvent([name: "lastEvent", value: "${df(now())}"]) 55 | log.debug "$device.label is On" 56 | } 57 | 58 | def off() { 59 | sendEvent([name: "switch", value: "off"]) 60 | parent.OutletAction(this,"OFF") 61 | sendEvent([name: "switch", value: "$device.label"]) 62 | sendEvent([name: "lastEvent", value: "${df(now())}"]) 63 | log.debug "$device.label is Off" 64 | } 65 | 66 | def cycle() { 67 | log.debug "$device.label is Cycling" 68 | parent.OutletAction(this,"CCL") 69 | 70 | sendEvent([name: "switch", value: "cyclingOff"]) 71 | pause(6000) 72 | 73 | sendEvent([name: "switch", value: "cyclingOn"]) 74 | pause(5000) 75 | 76 | sendEvent([name: "switch", value: "on"]) 77 | 78 | sendEvent([name: "lastEvent", value: "${df(now())}"]) 79 | //log.debug "$device.label is Off" 80 | } 81 | 82 | def poll() { 83 | def current = device.currentValue("switch") 84 | log.debug "Polling - $device.label is $current" 85 | 86 | log.debug "This - $this" 87 | 88 | def outletStatus = parent.OutletStatus(this) 89 | log.debug "Polling - Status is $outletStatus" 90 | 91 | def OutletName = parent.OutletName(this) 92 | log.debug "Polling - Name is $OutletName" 93 | 94 | if(!current || current=="off") { sendEvent(name:"switch", value:"$device.label", isStateChange:true, displayed:false) } 95 | } 96 | 97 | def pause(millis) { 98 | def passed = 0 99 | def now = new Date().time 100 | log.debug "pausing... at Now: $now" 101 | /* This loop is an impolite busywait. We need to be given a true sleep() method, please. */ 102 | while ( passed < millis ) { 103 | passed = new Date().time - now 104 | } 105 | log.debug "... DONE pausing." 106 | } 107 | 108 | def df(e) { 109 | // * df(e) - Date Format "E" 110 | // * Takes epoch time format and returns Date formatted in current timezone 111 | def locale = getWeatherFeature("geolookup", zip); 112 | def tz = TimeZone.getTimeZone(locale.location.tz_long); 113 | def formatted 114 | if(e) { formatted = new Date(e).format("EEE, MMM d, 'at' hh:mm aaa", tz); return formatted } 115 | } -------------------------------------------------------------------------------- /devicetypes/ledridge/wifi-370-led-strip-controller.src/wifi-370-led-strip-controller.groovy: -------------------------------------------------------------------------------- 1 | /** 2 | * Foscam Universal Device 3 | * 4 | * Copyright 2014 skp19 5 | * 6 | */ 7 | metadata { 8 | definition (name: "WiFi 370 LED Strip Controller", namespace: "Ledridge", author: "Ledridge") { 9 | capability "Switch Level" 10 | capability "Actuator" 11 | capability "Color Control" 12 | capability "Switch" 13 | capability "Refresh" 14 | capability "Sensor" 15 | 16 | command "setAdjustedColor" 17 | command "refresh" 18 | } 19 | 20 | preferences { 21 | input("ip", "string", title:"Controller IP Address", description: "Controller IP Address", defaultValue: "192.168.1.69", required: true, displayDuringSetup: true) 22 | input("port", "string", title:"Controller Port", description: "Controller Port", defaultValue: 5577 , required: true, displayDuringSetup: true) 23 | input("username", "string", title:"Controller Username", description: "Controller Username", defaultValue: admin, required: true, displayDuringSetup: true) 24 | input("password", "password", title:"Controller Password", description: "Controller Password", defaultValue: nimda, required: true, displayDuringSetup: true) 25 | } 26 | 27 | standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) { 28 | state "on", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821" 29 | state "off", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff" 30 | } 31 | standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat") { 32 | state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh" 33 | } 34 | controlTile("rgbSelector", "device.color", "color", height: 3, width: 3, inactiveLabel: false) { 35 | state "color", action:"setAdjustedColor" 36 | } 37 | controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 2, inactiveLabel: false, range:"(0..100)") { 38 | state "level", action:"switch level.setLevel" 39 | } 40 | valueTile("level", "device.level", inactiveLabel: false, decoration: "flat") { 41 | state "level", label: 'Level ${currentValue}%' 42 | } 43 | controlTile("saturationSliderControl", "device.saturation", "slider", height: 1, width: 2, inactiveLabel: false) { 44 | state "saturation", action:"color control.setSaturation" 45 | } 46 | valueTile("saturation", "device.saturation", inactiveLabel: false, decoration: "flat") { 47 | state "saturation", label: 'Sat ${currentValue} ' 48 | } 49 | controlTile("hueSliderControl", "device.hue", "slider", height: 1, width: 2, inactiveLabel: false) { 50 | state "hue", action:"color control.setHue" 51 | } 52 | valueTile("hue", "device.hue", inactiveLabel: false, decoration: "flat") { 53 | state "hue", label: 'Hue ${currentValue} ' 54 | } 55 | 56 | main(["switch"]) 57 | details(["switch", "levelSliderControl", "rgbSelector", "refresh"]) 58 | 59 | } 60 | 61 | // parse events into attributes 62 | def parse(description) { 63 | log.debug "parse() - $description" 64 | def results = [] 65 | def map = description 66 | if (description instanceof String) { 67 | log.debug "Hue Bulb stringToMap - ${map}" 68 | map = stringToMap(description) 69 | } 70 | if (map?.name && map?.value) { 71 | results << createEvent(name: "${map?.name}", value: "${map?.value}") 72 | } 73 | results 74 | } 75 | 76 | // handle commands 77 | def on() { 78 | parent.on(this) 79 | sendEvent(name: "switch", value: "on") 80 | } 81 | 82 | def off() { 83 | parent.off(this) 84 | sendEvent(name: "switch", value: "off") 85 | } 86 | 87 | def nextLevel() { 88 | def level = device.latestValue("level") as Integer ?: 0 89 | if (level <= 100) { 90 | level = Math.min(25 * (Math.round(level / 25) + 1), 100) as Integer 91 | } 92 | else { 93 | level = 25 94 | } 95 | setLevel(level) 96 | } 97 | 98 | def setLevel(percent) { 99 | log.debug "Executing 'setLevel'" 100 | parent.setLevel(this, percent) 101 | sendEvent(name: "level", value: percent) 102 | } 103 | 104 | def setSaturation(percent) { 105 | log.debug "Executing 'setSaturation'" 106 | parent.setSaturation(this, percent) 107 | sendEvent(name: "saturation", value: percent) 108 | } 109 | 110 | def setHue(percent) { 111 | log.debug "Executing 'setHue'" 112 | parent.setHue(this, percent) 113 | sendEvent(name: "hue", value: percent) 114 | } 115 | 116 | def setColor(value) { 117 | log.debug "setColor: ${value}, $this" 118 | parent.setColor(this, value) 119 | if (value.hue) { sendEvent(name: "hue", value: value.hue)} 120 | if (value.saturation) { sendEvent(name: "saturation", value: value.saturation)} 121 | if (value.hex) { sendEvent(name: "color", value: value.hex)} 122 | if (value.level) { sendEvent(name: "level", value: value.level)} 123 | if (value.switch) { sendEvent(name: "switch", value: value.switch)} 124 | } 125 | 126 | def setAdjustedColor(value) { 127 | if (value) { 128 | log.debug "setAdjustedColor: ${value}" 129 | def adjusted = value + [:] 130 | adjusted.hue = adjustOutgoingHue(value.hue) 131 | // Needed because color picker always sends 100 132 | adjusted.level = null 133 | setColor(adjusted) 134 | } 135 | } 136 | 137 | def refresh() { 138 | log.debug "Executing 'refresh'" 139 | parent.poll() 140 | } 141 | 142 | def adjustOutgoingHue(percent) { 143 | def adjusted = percent 144 | if (percent > 31) { 145 | if (percent < 63.0) { 146 | adjusted = percent + (7 * (percent -30 ) / 32) 147 | } 148 | else if (percent < 73.0) { 149 | adjusted = 69 + (5 * (percent - 62) / 10) 150 | } 151 | else { 152 | adjusted = percent + (2 * (100 - percent) / 28) 153 | } 154 | } 155 | log.info "percent: $percent, adjusted: $adjusted" 156 | adjusted 157 | } 158 | 159 | 160 | 161 | //TAKE PICTURE 162 | def take() { 163 | log.debug("Taking Photo") 164 | sendEvent(name: "hubactionMode", value: "s3"); 165 | if(hdcamera == "true") { 166 | hubGet("cmd=snapPicture2") 167 | } 168 | else { 169 | hubGet("/snapshot.cgi?") 170 | } 171 | } 172 | //END TAKE PICTURE 173 | 174 | //ALARM ACTIONS 175 | def toggleAlarm() { 176 | log.debug "Toggling Alarm" 177 | if(device.currentValue("alarmStatus") == "on") { 178 | alarmOff() 179 | } 180 | else { 181 | alarmOn() 182 | } 183 | } 184 | 185 | def alarmOn() { 186 | log.debug "Enabling Alarm" 187 | sendEvent(name: "alarmStatus", value: "on"); 188 | if(hdcamera == "true") { 189 | hubGet("cmd=setMotionDetectConfig&isEnable=1") 190 | } 191 | else { 192 | hubGet("/set_alarm.cgi?motion_armed=1&") 193 | } 194 | } 195 | 196 | def alarmOff() { 197 | log.debug "Disabling Alarm" 198 | sendEvent(name: "alarmStatus", value: "off"); 199 | if(hdcamera == "true") { 200 | hubGet("cmd=setMotionDetectConfig&isEnable=0") 201 | } 202 | else { 203 | hubGet("/set_alarm.cgi?motion_armed=0&") 204 | } 205 | } 206 | //END ALARM ACTIONS 207 | 208 | //LED ACTIONS 209 | //Toggle LED's 210 | def toggleLED() { 211 | log.debug("Toggle LED") 212 | 213 | if(device.currentValue("ledStatus") == "auto") { 214 | ledOn() 215 | } 216 | 217 | else if(device.currentValue("ledStatus") == "on") { 218 | ledOff() 219 | } 220 | 221 | else { 222 | ledAuto() 223 | } 224 | } 225 | 226 | def ledOn() { 227 | log.debug("LED changed to: on") 228 | sendEvent(name: "ledStatus", value: "on"); 229 | if(hdcamera == "true") { 230 | delayBetween([hubGet("cmd=setInfraLedConfig&mode=1"), hubGet("cmd=openInfraLed")]) 231 | } 232 | else { 233 | hubGet("/decoder_control.cgi?command=95&") 234 | } 235 | } 236 | 237 | def ledOff() { 238 | log.debug("LED changed to: off") 239 | sendEvent(name: "ledStatus", value: "off"); 240 | if(hdcamera == "true") { 241 | delayBetween([hubGet("cmd=setInfraLedConfig&mode=1"), hubGet("cmd=closeInfraLed")]) 242 | } 243 | else { 244 | hubGet("/decoder_control.cgi?command=94&") 245 | } 246 | } 247 | 248 | def ledAuto() { 249 | log.debug("LED changed to: auto") 250 | sendEvent(name: "ledStatus", value: "auto"); 251 | if(hdcamera == "true") { 252 | hubGet("cmd=setInfraLedConfig&mode=0") 253 | } 254 | else { 255 | hubGet("/decoder_control.cgi?command=95&") 256 | } 257 | } 258 | //END LED ACTIONS 259 | 260 | //PRESET ACTIONS 261 | def preset1() { 262 | log.debug("Preset 1 Selected - ${preset1}") 263 | if(hdcamera == "true") { 264 | hubGet("cmd=ptzGotoPresetPoint&name=${preset1}") 265 | } 266 | else { 267 | hubGet("/decoder_control.cgi?command=31&") 268 | } 269 | } 270 | 271 | def preset2() { 272 | log.debug("Preset 2 Selected - ${preset2}") 273 | if(hdcamera == "true") { 274 | hubGet("cmd=ptzGotoPresetPoint&name=${preset2}") 275 | } 276 | else { 277 | hubGet("/decoder_control.cgi?command=33&") 278 | } 279 | } 280 | 281 | def preset3() { 282 | log.debug("Preset 3 Selected - ${preset3}") 283 | if(hdcamera == "true") { 284 | hubGet("cmd=ptzGotoPresetPoint&name=${preset3}") 285 | } 286 | else { 287 | hubGet("/decoder_control.cgi?command=35&") 288 | } 289 | } 290 | //END PRESET ACTIONS 291 | 292 | //CRUISE ACTIONS 293 | def cruisemap1() { 294 | log.debug("Cruise Map 1 Selected - ${cruisemap1}") 295 | if(hdcamera == "true") { 296 | hubGet("cmd=ptzStartCruise&mapName=${cruisemap1}") 297 | } 298 | else { 299 | hubGet("/decoder_control.cgi?command=28&") 300 | } 301 | } 302 | 303 | def cruisemap2() { 304 | log.debug("Cruise Map 2 Selected - ${cruisemap2}") 305 | if(hdcamera == "true") { 306 | hubGet("cmd=ptzStartCruise&mapName=${cruisemap2}") 307 | } 308 | else { 309 | hubGet("/decoder_control.cgi?command=26&") 310 | } 311 | } 312 | 313 | def stopCruise() { 314 | log.debug("Stop Cruise") 315 | if(hdcamera == "true") { 316 | hubGet("cmd=ptzStopRun") 317 | } 318 | else { 319 | delayBetween([hubGet("/decoder_control.cgi?command=29&"), hubGet("/decoder_control.cgi?command=27&")]) 320 | } 321 | } 322 | //END CRUISE ACTIONS 323 | 324 | //PTZ CONTROLS 325 | def left() { 326 | if(hdcamera == "true") { 327 | delayBetween([hubGet("cmd=ptzMoveLeft"), hubGet("cmd=ptzStopRun")]) 328 | } 329 | else { 330 | if(mirror == "true") { 331 | hubGet("/decoder_control.cgi?command=4&onestep=1&") 332 | } 333 | else { 334 | hubGet("/decoder_control.cgi?command=6&onestep=1&") 335 | } 336 | } 337 | } 338 | 339 | def right() { 340 | if(hdcamera == "true") { 341 | delayBetween([hubGet("cmd=ptzMoveRight"), hubGet("cmd=ptzStopRun")]) 342 | } 343 | else { 344 | if(mirror == "true") { 345 | hubGet("/decoder_control.cgi?command=6&onestep=1&") 346 | } 347 | else { 348 | hubGet("/decoder_control.cgi?command=4&onestep=1&") 349 | } 350 | } 351 | } 352 | 353 | def up() { 354 | if(hdcamera == "true") { 355 | delayBetween([hubGet("cmd=ptzMoveUp"), hubGet("cmd=ptzStopRun")]) 356 | } 357 | else { 358 | if(flip == "true") { 359 | hubGet("/decoder_control.cgi?command=2&onestep=1&") 360 | } 361 | else { 362 | hubGet("/decoder_control.cgi?command=0&onestep=1&") 363 | } 364 | } 365 | } 366 | 367 | def down() { 368 | if(hdcamera == "true") { 369 | delayBetween([hubGet("cmd=ptzMoveDown"), hubGet("cmd=ptzStopRun")]) 370 | } 371 | else { 372 | if(flip == "true") { 373 | hubGet("/decoder_control.cgi?command=0&onestep=1&") 374 | } 375 | else { 376 | hubGet("/decoder_control.cgi?command=2&onestep=1&") 377 | } 378 | } 379 | } 380 | //END PTZ CONTROLS 381 | 382 | def poll() { 383 | 384 | sendEvent(name: "hubactionMode", value: "local"); 385 | //Poll Motion Alarm Status and IR LED Mode 386 | if(hdcamera == "true") { 387 | delayBetween([hubGet("cmd=getMotionDetectConfig"), hubGet("cmd=getInfraLedConfig")]) 388 | } 389 | else { 390 | hubGet("/get_params.cgi?") 391 | } 392 | } 393 | 394 | private getLogin() { 395 | if(hdcamera == "true") { 396 | return "usr=${username}&pwd=${password}&" 397 | } 398 | else { 399 | return "user=${username}&pwd=${password}" 400 | } 401 | } 402 | 403 | private hubGet(def apiCommand) { 404 | //Setting Network Device Id 405 | def iphex = convertIPtoHex(ip) 406 | def porthex = convertPortToHex(port) 407 | device.deviceNetworkId = "$iphex:$porthex" 408 | log.debug "Device Network Id set to ${iphex}:${porthex}" 409 | 410 | log.debug("Executing hubaction on " + getHostAddress()) 411 | def uri = "" 412 | if(hdcamera == "true") { 413 | uri = "/cgi-bin/CGIProxy.fcgi?" + getLogin() + apiCommand 414 | } 415 | else { 416 | uri = apiCommand + getLogin() 417 | } 418 | log.debug uri 419 | def hubAction = new physicalgraph.device.HubAction( 420 | method: "GET", 421 | path: uri, 422 | headers: [HOST:getHostAddress()] 423 | ) 424 | if(device.currentValue("hubactionMode") == "s3") { 425 | hubAction.options = [outputMsgToS3:true] 426 | sendEvent(name: "hubactionMode", value: "local"); 427 | } 428 | hubAction 429 | } 430 | 431 | //Parse events into attributes 432 | def parse(String description) { 433 | log.debug "Parsing '${description}'" 434 | 435 | def map = [:] 436 | def retResult = [] 437 | def descMap = parseDescriptionAsMap(description) 438 | 439 | //Image 440 | if (descMap["bucket"] && descMap["key"]) { 441 | putImageInS3(descMap) 442 | } 443 | 444 | //Status Polling 445 | else if (descMap["headers"] && descMap["body"]) { 446 | def body = new String(descMap["body"].decodeBase64()) 447 | if(hdcamera == "true") { 448 | def langs = new XmlSlurper().parseText(body) 449 | 450 | def motionAlarm = "$langs.isEnable" 451 | def ledMode = "$langs.mode" 452 | 453 | //Get Motion Alarm Status 454 | if(motionAlarm == "0") { 455 | log.info("Polled: Alarm Off") 456 | sendEvent(name: "alarmStatus", value: "off"); 457 | } 458 | else if(motionAlarm == "1") { 459 | log.info("Polled: Alarm On") 460 | sendEvent(name: "alarmStatus", value: "on"); 461 | } 462 | 463 | //Get IR LED Mode 464 | if(ledMode == "0") { 465 | log.info("Polled: LED Mode Auto") 466 | sendEvent(name: "ledStatus", value: "auto") 467 | } 468 | else if(ledMode == "1") { 469 | log.info("Polled: LED Mode Manual") 470 | sendEvent(name: "ledStatus", value: "manual") 471 | } 472 | } 473 | else { 474 | if(body.find("alarm_motion_armed=0")) { 475 | log.info("Polled: Alarm Off") 476 | sendEvent(name: "alarmStatus", value: "off") 477 | } 478 | else if(body.find("alarm_motion_armed=1")) { 479 | log.info("Polled: Alarm On") 480 | sendEvent(name: "alarmStatus", value: "on") 481 | } 482 | //The API does not provide a way to poll for LED status on 8xxx series at the moment 483 | } 484 | } 485 | } 486 | 487 | def parseDescriptionAsMap(description) { 488 | description.split(",").inject([:]) { map, param -> 489 | def nameAndValue = param.split(":") 490 | map += [(nameAndValue[0].trim()):nameAndValue[1].trim()] 491 | } 492 | } 493 | 494 | def putImageInS3(map) { 495 | 496 | def s3ObjectContent 497 | 498 | try { 499 | def imageBytes = getS3Object(map.bucket, map.key + ".jpg") 500 | 501 | if(imageBytes) 502 | { 503 | s3ObjectContent = imageBytes.getObjectContent() 504 | def bytes = new ByteArrayInputStream(s3ObjectContent.bytes) 505 | storeImage(getPictureName(), bytes) 506 | } 507 | } 508 | catch(Exception e) { 509 | log.error e 510 | } 511 | finally { 512 | //Explicitly close the stream 513 | if (s3ObjectContent) { s3ObjectContent.close() } 514 | } 515 | } 516 | 517 | private getPictureName() { 518 | def pictureUuid = java.util.UUID.randomUUID().toString().replaceAll('-', '') 519 | "image" + "_$pictureUuid" + ".jpg" 520 | } 521 | 522 | private getHostAddress() { 523 | return "${ip}:${port}" 524 | } 525 | 526 | private String convertIPtoHex(ipAddress) { 527 | String hex = ipAddress.tokenize( '.' ).collect { String.format( '%02x', it.toInteger() ) }.join() 528 | return hex 529 | 530 | } 531 | 532 | private String convertPortToHex(port) { 533 | String hexport = port.toString().format( '%04x', port.toInteger() ) 534 | return hexport 535 | } -------------------------------------------------------------------------------- /devicetypes/mmaxwell/remoteczfm80.src/remoteczfm80.groovy: -------------------------------------------------------------------------------- 1 | /* Remotec ZFM-80 specific device V1.2 2 | * 3 | * Variation of the stock SmartThings Relay Switch 4 | * --auto re-configure after setting preferences 5 | * --preference settings for switch type and automatic shutoff features. 6 | * 7 | * 8 | * Mike Maxwell 9 | * madmax98087@yahoo.com 10 | * 2015-02-16 11 | * 12 | change log 13 | 2015-02-16 added delay between configuration changes, helps with devices further away from the hub. 14 | 2015-02-21 fixed null error on initial install 15 | */ 16 | 17 | metadata { 18 | 19 | definition (name: "remotecZFM80", namespace: "mmaxwell", author: "mike maxwell") { 20 | capability "Actuator" 21 | capability "Door Control" 22 | capability "Switch" 23 | capability "Momentary" 24 | capability "Polling" 25 | capability "Refresh" 26 | capability "Sensor" 27 | capability "Contact Sensor" 28 | capability "Relay Switch" 29 | 30 | fingerprint deviceId: "0x1003", inClusters: "0x20, 0x25, 0x27, 0x72, 0x86, 0x70, 0x85" 31 | } 32 | preferences { 33 | input name: "param1", type: "enum", title: "Set external switch mode:", description: "Switch type", required: true, options:["Disabled","Momentary NO","Momentary NC","Toggle NO","Toggle NC"] 34 | input name: "param2", type: "enum", title: "Auto shutoff minutes:", description: "Minutes?", required: false, options:["Never","1","5","30","60","90","120","240"] 35 | } 36 | 37 | // simulator metadata 38 | simulator { 39 | status "on": "command: 2003, payload: FF" 40 | status "off": "command: 2003, payload: 00" 41 | 42 | // reply messages 43 | reply "2001FF,delay 100,2502": "command: 2503, payload: FF" 44 | reply "200100,delay 100,2502": "command: 2503, payload: 00" 45 | } 46 | 47 | // tile definitions 48 | tiles { 49 | standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) { 50 | state "on", label: '${name}', action: "switch.off", icon: "st.switches.switch.on", backgroundColor: "#79b821" 51 | state "off", label: '${name}', action: "switch.on", icon: "st.switches.switch.off", backgroundColor: "#ffffff" 52 | } 53 | standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat") { 54 | state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh" 55 | } 56 | 57 | main "switch" 58 | details(["switch","refresh"]) 59 | } 60 | } 61 | 62 | def installed() { 63 | zwave.manufacturerSpecificV1.manufacturerSpecificGet().format() 64 | } 65 | 66 | def parse(String description) { 67 | def result = null 68 | def cmd = zwave.parse(description, [0x20: 1, 0x70: 1]) 69 | if (cmd) { 70 | result = createEvent(zwaveEvent(cmd)) 71 | } 72 | if (result?.name == 'hail' && hubFirmwareLessThan("000.011.00602")) { 73 | result = [result, response(zwave.basicV1.basicGet())] 74 | log.debug "Was hailed: requesting state update" 75 | } else { 76 | log.debug "Parse returned ${result?.descriptionText}" 77 | } 78 | return result 79 | } 80 | 81 | def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicReport cmd) { 82 | [name: "switch", value: cmd.value ? "on" : "off", type: "physical"] 83 | } 84 | 85 | def zwaveEvent(physicalgraph.zwave.commands.switchbinaryv1.SwitchBinaryReport cmd) { 86 | [name: "switch", value: cmd.value ? "on" : "off", type: "digital"] 87 | } 88 | 89 | //def zwaveEvent(physicalgraph.zwave.commands.configurationv1.ConfigurationReport cmd) { 90 | // def value = "when off" 91 | // if (cmd.configurationValue[0] == 1) {value = "when on"} 92 | // if (cmd.configurationValue[0] == 2) {value = "never"} 93 | // [name: "indicatorStatus", value: value, display: false] 94 | //} 95 | 96 | def zwaveEvent(physicalgraph.zwave.commands.hailv1.Hail cmd) { 97 | [name: "hail", value: "hail", descriptionText: "Switch button was pressed", displayed: false] 98 | } 99 | 100 | def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerSpecificReport cmd) { 101 | if (state.manufacturer != cmd.manufacturerName) { 102 | updateDataValue("manufacturer", cmd.manufacturerName) 103 | } 104 | 105 | final relays = [ 106 | //[manufacturerId:0x0113, productTypeId: 0x5246, productId: 0x3133, productName: "Evolve LFM-20"], 107 | //[manufacturerId:0x0113, productTypeId: 0x5246, productId: 0x3133, productName: "Linear FS20Z-1"], 108 | [manufacturerId:0x5254, productTypeId: 0x8000, productId: 0x0002, productName: "Remotec ZFM-80"] 109 | ] 110 | 111 | def productName = null 112 | for (it in relays) { 113 | if (it.manufacturerId == cmd.manufacturerId && it.productTypeId == cmd.productTypeId && it.productId == cmd.productId) { 114 | productName = it.productName 115 | break 116 | } 117 | } 118 | 119 | if (productName) { 120 | //log.debug "Relay found: $productName" 121 | updateDataValue("productName", productName) 122 | } 123 | else { 124 | //log.debug "Not a relay, retyping to Z-Wave Switch" 125 | setDeviceType("Z-Wave Switch") 126 | } 127 | [name: "manufacturer", value: cmd.manufacturerName] 128 | } 129 | 130 | def zwaveEvent(physicalgraph.zwave.Command cmd) { 131 | // Handles all Z-Wave commands we aren't interested in 132 | [:] 133 | } 134 | 135 | def on() { 136 | delayBetween([ 137 | zwave.basicV1.basicSet(value: 0xFF).format(), 138 | zwave.switchBinaryV1.switchBinaryGet().format() 139 | ]) 140 | } 141 | 142 | def off() { 143 | delayBetween([ 144 | zwave.basicV1.basicSet(value: 0x00).format(), 145 | zwave.switchBinaryV1.switchBinaryGet().format() 146 | ]) 147 | } 148 | 149 | def poll() { 150 | zwave.switchBinaryV1.switchBinaryGet().format() 151 | } 152 | 153 | def refresh() { 154 | delayBetween([ 155 | zwave.switchBinaryV1.switchBinaryGet().format(), 156 | zwave.manufacturerSpecificV1.manufacturerSpecificGet().format() 157 | ]) 158 | } 159 | //capture preference changes 160 | def updated() { 161 | //log.debug "before settings: ${settings.inspect()}, state: ${state.inspect()}" 162 | //"Disabled","Momentary NO","Momentary NC","Toggle NO","Toggle NC" 163 | 164 | //external switch function settings 165 | def Short p1 = 0 166 | switch (settings.param1) { 167 | case "Disabled": 168 | p1 = 0 169 | break 170 | case "Momentary NO": 171 | p1 = 1 172 | break 173 | case "Momentary NC": 174 | p1 = 2 175 | break 176 | case "Toggle NO": 177 | p1 = 3 178 | break 179 | case "Toggle NC": 180 | p1 = 4 181 | break 182 | } 183 | 184 | 185 | //auto off 186 | def Short p2 = 0 187 | if ("${settings.param2}" == "Never") { 188 | p2 = 0 189 | } else { 190 | p2 = (settings.param2 ?: 0).toInteger() 191 | } 192 | 193 | if (p1 != state.param1) { 194 | state.param1 = p1 195 | return response(zwave.configurationV1.configurationSet(parameterNumber: 1, size: 1, configurationValue: [p1]).format()) 196 | } 197 | 198 | if (p2 != state.param2) { 199 | state.param2 = p2 200 | if (p2 == 0) { 201 | return response (delayBetween([ 202 | zwave.configurationV1.configurationSet(parameterNumber: 2, size: 1, configurationValue: [0]).format(), 203 | zwave.configurationV1.configurationSet(parameterNumber: 3, size: 1, configurationValue: [0]).format(), 204 | zwave.configurationV1.configurationSet(parameterNumber: 4, size: 1, configurationValue: [0]).format() 205 | ])) 206 | } else { 207 | return response (delayBetween([ 208 | zwave.configurationV1.configurationSet(parameterNumber: 2, size: 1, configurationValue: [p2]).format(), 209 | zwave.configurationV1.configurationSet(parameterNumber: 3, size: 1, configurationValue: [232]).format(), 210 | zwave.configurationV1.configurationSet(parameterNumber: 4, size: 1, configurationValue: [0]).format() 211 | ])) 212 | } 213 | } 214 | 215 | //log.debug "after settings: ${settings.inspect()}, state: ${state.inspect()}" 216 | } 217 | 218 | def configure() { 219 | delayBetween([ 220 | zwave.configurationV1.configurationSet(parameterNumber: 1, size: 1, configurationValue: [3]).format(), 221 | zwave.configurationV1.configurationSet(parameterNumber: 2, size: 1, configurationValue: [0]).format(), 222 | zwave.configurationV1.configurationSet(parameterNumber: 3, size: 1, configurationValue: [0]).format(), 223 | zwave.configurationV1.configurationSet(parameterNumber: 4, size: 1, configurationValue: [0]).format() 224 | ]) 225 | } -------------------------------------------------------------------------------- /devicetypes/ms-w-vts/vtile-ms.src/vtile-ms.groovy: -------------------------------------------------------------------------------- 1 | /** 2 | * V Tile device-type for ms_w_vts 3 | * 4 | * Needed for Multi Switch with Virtual Tiles to create virtual switch tiles in ST for devices that have multiple "switch[x]" 5 | * attributes within them and have on[x] and off[x] commands for each (fairly common device-types) 6 | * Also has support for device-label inside the name when on or off and polling occurs 7 | * Copyright 2014 Cooper Lee 8 | * 9 | */ 10 | metadata { 11 | definition (name: "vTile_ms", namespace: "ms_w_vts", author: "Cooper Lee") { 12 | capability "Switch" 13 | capability "relaySwitch" 14 | capability "Polling" 15 | capability "Refresh" 16 | 17 | attribute "lastOn", "string" 18 | attribute "lastOff", "string" 19 | } 20 | } 21 | 22 | preferences { 23 | tiles { 24 | standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) { 25 | //state "name", label: '${currentValue}', action: "switch.on", icon: "http://cdn.flaticon.com/png/256/56724.png", backgroundColor: "#DDDDff", nextState: "turningOn" 26 | state "off", action: "switch.on", icon: "http://cdn.flaticon.com/png/256/56724.png", backgroundColor: "#DDDDff", nextState: "turningOn" 27 | state "on", action: "switch.off", icon: "http://cdn.flaticon.com/png/256/56724.png", backgroundColor: "#0088ff", nextState: "turningOff" 28 | state "turningOff", iconLabel:"http://cdn.flaticon.com/png/256/56413.png" , icon: "http://cdn.flaticon.com/png/256/56724.png", backgroundColor: "#FA5882", nextState: "on" 29 | state "turningOn", iconLabel:"http://cdn.flaticon.com/png/256/56498.png" , icon: "http://cdn.flaticon.com/png/256/56724.png", backgroundColor: "#F3F781", nextState: "off" 30 | } 31 | 32 | valueTile("lastOn", "device.lastOn", inactiveLabel: false, width: 3, height: 1, canChangeIcon: false, decoration:"flat") { 33 | state "default", label: 'Last On: ${currentValue}'} 34 | 35 | valueTile("lastOff", "device.lastOff", inactiveLabel: false, width: 3, height: 1, canChangeIcon: false, decoration:"flat") { 36 | state "default", label: 'Last Off: ${currentValue}'} 37 | 38 | main "switch" 39 | details(["switch", "lastOn", "lastOff"]) 40 | } 41 | } 42 | 43 | def parse(desc) { 44 | def results = [] 45 | if(desc=="updated") { log.debug "Device $device.label has been UPDATED"; poll() } 46 | } 47 | 48 | def on() { 49 | sendEvent([name: "switch", value: "on"]) 50 | parent.on(this) 51 | sendEvent([name: "lastOn", value: "${df(now())}"]) 52 | log.debug "$device.label is On" 53 | } 54 | 55 | def off() { 56 | sendEvent([name: "switch", value: "off"]) 57 | parent.off(this) 58 | sendEvent([name: "switch", value: "$device.label"]) 59 | sendEvent([name: "lastOff", value: "${df(now())}"]) 60 | log.debug "$device.label is Off" 61 | } 62 | 63 | def poll() { 64 | def current = device.currentValue("switch") 65 | log.debug "Polling - $device.label is $current" 66 | if(!current || current=="off") { sendEvent(name:"switch", value:"$device.label", isStateChange:true, displayed:false) } 67 | } 68 | 69 | def df(e) { 70 | // * df(e) - Date Format "E" 71 | // * Takes epoch time format and returns Date formatted in current timezone 72 | // * Copyright 2014 Cooper Lee 73 | def locale = getWeatherFeature("geolookup", zip); def tz = TimeZone.getTimeZone(locale.location.tz_long); def formatted 74 | if(e) { formatted = new Date(e).format("EEE, MMM d, 'at' hh:mm aaa", tz); return formatted } 75 | } -------------------------------------------------------------------------------- /devicetypes/skp19/foscam-universal-device.src/foscam-universal-device.groovy: -------------------------------------------------------------------------------- 1 | /** 2 | * Foscam Universal Device 3 | * 4 | * Copyright 2014 skp19 5 | * 6 | */ 7 | metadata { 8 | definition (name: "Foscam Universal Device", namespace: "skp19", author: "skp19") { 9 | capability "Polling" 10 | capability "Image Capture" 11 | 12 | attribute "alarmStatus", "string" 13 | attribute "ledStatus", "string" 14 | attribute "hubactionMode", "string" 15 | 16 | command "alarmOn" 17 | command "alarmOff" 18 | command "toggleAlarm" 19 | command "toggleLED" 20 | 21 | command "ledOn" 22 | command "ledOff" 23 | command "ledAuto" 24 | 25 | command "left" 26 | command "right" 27 | command "up" 28 | command "down" 29 | 30 | command "cruisemap1" 31 | command "cruisemap2" 32 | command "stopCruise" 33 | 34 | command "preset1" 35 | command "preset2" 36 | command "preset3" 37 | } 38 | 39 | preferences { 40 | input("ip", "string", title:"Camera IP Address", description: "Camera IP Address", required: true, displayDuringSetup: true) 41 | input("port", "string", title:"Camera Port", description: "Camera Port", defaultValue: 80 , required: true, displayDuringSetup: true) 42 | input("username", "string", title:"Camera Username", description: "Camera Username", required: true, displayDuringSetup: true) 43 | input("password", "password", title:"Camera Password", description: "Camera Password", required: true, displayDuringSetup: true) 44 | input("hdcamera", "bool", title:"HD Foscam Camera? (9xxx Series)", description: "Type of Foscam Camera", required: true, displayDuringSetup: true) 45 | input("mirror", "bool", title:"Mirror? (Not required for HD cameras)", description: "Camera Mirrored?") 46 | input("flip", "bool", title:"Flip? (Not required for HD cameras)", description: "Camera Flipped?") 47 | input("preset1", "text", title: "Preset 1 (For HD cameras only)", description: "Name of your first preset position") 48 | input("preset2", "text", title: "Preset 2 (For HD cameras only)", description: "Name of your second preset position") 49 | input("preset3", "text", title: "Preset 3 (For HD cameras only)", description: "Name of your third preset position") 50 | input("cruisemap1", "text", title: "Cruise Map 1 (For HD cameras only. Non-HD cameras will default to Horizontal.)", description: "Name of your first cruise map", defaultValue: "Horizontal") 51 | input("cruisemap2", "text", title: "Cruise Map 2 (For HD cameras only. Non-HD cameras will default to Vertical.)", description: "Name of your second cruise map", defaultValue: "Vertical") 52 | } 53 | 54 | tiles { 55 | carouselTile("cameraDetails", "device.image", width: 3, height: 2) { } 56 | 57 | standardTile("camera", "device.alarmStatus", width: 1, height: 1, canChangeIcon: true, inactiveLabel: true, canChangeBackground: true) { 58 | state "off", label: "off", action: "toggleAlarm", icon: "st.camera.dropcam-centered", backgroundColor: "#FFFFFF" 59 | state "on", label: "on", action: "toggleAlarm", icon: "st.camera.dropcam-centered", backgroundColor: "#53A7C0" 60 | } 61 | 62 | standardTile("take", "device.image", width: 1, height: 1, canChangeIcon: false, inactiveLabel: true, canChangeBackground: false) { 63 | state "take", label: "Take", action: "Image Capture.take", icon: "st.camera.camera", backgroundColor: "#FFFFFF", nextState:"taking" 64 | state "taking", label:'Taking', action: "", icon: "st.camera.take-photo", backgroundColor: "#53a7c0" 65 | state "image", label: "Take", action: "Image Capture.take", icon: "st.camera.camera", backgroundColor: "#FFFFFF", nextState:"taking" 66 | } 67 | 68 | standardTile("alarmStatus", "device.alarmStatus", width: 1, height: 1, canChangeIcon: false, inactiveLabel: true, canChangeBackground: false) { 69 | state "off", label: "off", action: "toggleAlarm", icon: "st.quirky.spotter.quirky-spotter-sound-off", backgroundColor: "#FFFFFF" 70 | state "on", label: "on", action: "toggleAlarm", icon: "st.quirky.spotter.quirky-spotter-sound-on", backgroundColor: "#53A7C0" 71 | } 72 | 73 | standardTile("ledStatus", "device.ledStatus", width: 1, height: 1, canChangeIcon: false, inactiveLabel: true, canChangeBackground: false) { 74 | state "auto", label: "auto", action: "toggleLED", icon: "st.Lighting.light13", backgroundColor: "#53A7C0" 75 | state "off", label: "off", action: "toggleLED", icon: "st.Lighting.light13", backgroundColor: "#FFFFFF" 76 | state "on", label: "on", action: "toggleLED", icon: "st.Lighting.light11", backgroundColor: "#FFFF00" 77 | state "manual", label: "manual", action: "toggleLED", icon: "st.Lighting.light13", backgroundColor: "#FFFF00" 78 | } 79 | 80 | standardTile("ledAuto", "device.ledStatus", width: 1, height: 1, canChangeIcon: false, inactiveLabel: true, canChangeBackground: false) { 81 | state "auto", label: "auto", action: "ledAuto", icon: "st.Lighting.light11", backgroundColor: "#53A7C0" 82 | state "off", label: "auto", action: "ledAuto", icon: "st.Lighting.light13", backgroundColor: "#FFFFFF" 83 | state "on", label: "auto", action: "ledAuto", icon: "st.Lighting.light13", backgroundColor: "#FFFFFF" 84 | state "manual", label: "auto", action: "ledAuto", icon: "st.Lighting.light13", backgroundColor: "#FFFFFF" 85 | } 86 | 87 | standardTile("ledOn", "device.ledStatus", width: 1, height: 1, canChangeIcon: false, inactiveLabel: true, canChangeBackground: false) { 88 | state "auto", label: "on", action: "ledOn", icon: "st.Lighting.light11", backgroundColor: "#FFFFFF" 89 | state "off", label: "on", action: "ledOn", icon: "st.Lighting.light11", backgroundColor: "#FFFFFF" 90 | state "on", label: "on", action: "ledOn", icon: "st.Lighting.light11", backgroundColor: "#FFFF00" 91 | state "manual", label: "on", action: "ledOn", icon: "st.Lighting.light11", backgroundColor: "#00FF00" 92 | } 93 | 94 | standardTile("ledOff", "device.ledStatus", width: 1, height: 1, canChangeIcon: false, inactiveLabel: true, canChangeBackground: false) { 95 | state "auto", label: "off", action: "ledOff", icon: "st.Lighting.light13", backgroundColor: "#FFFFFF" 96 | state "off", label: "off", action: "ledOff", icon: "st.Lighting.light13", backgroundColor: "#53A7C0" 97 | state "on", label: "off", action: "ledOff", icon: "st.Lighting.light13", backgroundColor: "#FFFFFF" 98 | state "manual", label: "off", action: "ledOff", icon: "st.Lighting.light13", backgroundColor: "#00FF00" 99 | } 100 | 101 | standardTile("preset1", "device.image", width: 1, height: 1, canChangeIcon: false, canChangeBackground: false, decoration: "flat") { 102 | state "preset1", label: "preset 1", action: "preset1", icon: "" 103 | } 104 | 105 | standardTile("preset2", "device.image", width: 1, height: 1, canChangeIcon: false, canChangeBackground: false, decoration: "flat") { 106 | state "preset2", label: "preset 2", action: "preset2", icon: "" 107 | } 108 | 109 | standardTile("preset3", "device.image", width: 1, height: 1, canChangeIcon: false, canChangeBackground: false, decoration: "flat") { 110 | state "preset3", label: "preset 3", action: "preset3", icon: "" 111 | } 112 | 113 | standardTile("cruisemap1", "device.image", width: 1, height: 1, canChangeIcon: false, canChangeBackground: false, decoration: "flat") { 114 | state "cruisemap1", label: "Cruise Map 1", action: "cruisemap1", icon: "" 115 | } 116 | 117 | standardTile("cruisemap2", "device.image", width: 1, height: 1, canChangeIcon: false, canChangeBackground: false, decoration: "flat") { 118 | state "cruisemap2", label: "Cruise Map 2", action: "cruisemap2", icon: "" 119 | } 120 | 121 | standardTile("stopcruise", "device.image", width: 1, height: 1, canChangeIcon: false, canChangeBackground: false, decoration: "flat") { 122 | state "stopcruise", label: "Stop Cruise", action: "stopCruise", icon: "" 123 | } 124 | 125 | standardTile("left", "device.image", width: 1, height: 1, canChangeIcon: false, canChangeBackground: false, decoration: "flat") { 126 | state "left", label: "left", action: "left", icon: "" 127 | } 128 | 129 | standardTile("right", "device.image", width: 1, height: 1, canChangeIcon: false, canChangeBackground: false, decoration: "flat") { 130 | state "right", label: "right", action: "right", icon: "" 131 | } 132 | 133 | standardTile("up", "device.image", width: 1, height: 1, canChangeIcon: false, canChangeBackground: false, decoration: "flat") { 134 | state "up", label: "up", action: "up", icon: "st.thermostat.thermostat-up" 135 | } 136 | 137 | standardTile("down", "device.image", width: 1, height: 1, canChangeIcon: false, canChangeBackground: false, decoration: "flat") { 138 | state "down", label: "down", action: "down", icon: "st.thermostat.thermostat-down" 139 | } 140 | 141 | standardTile("stop", "device.image", width: 1, height: 1, canChangeIcon: false, canChangeBackground: false, decoration: "flat") { 142 | state "stop", label: "", action: "stopCruise", icon: "st.sonos.stop-btn" 143 | } 144 | 145 | standardTile("refresh", "device.alarmStatus", inactiveLabel: false, decoration: "flat") { 146 | state "refresh", action:"polling.poll", icon:"st.secondary.refresh" 147 | } 148 | 149 | standardTile("blank", "device.image", width: 1, height: 1, canChangeIcon: false, canChangeBackground: false, decoration: "flat") { 150 | state "blank", label: "", action: "", icon: "", backgroundColor: "#FFFFFF" 151 | } 152 | 153 | main "camera" 154 | //details(["cameraDetails", "take", "blank", "alarmStatus", "ledAuto", "ledOn", "ledOff", "refresh"]) //**Uncomment this line and comment out the next line to hide the PTZ controls 155 | details(["cameraDetails", "take", "blank", "alarmStatus", "ledAuto", "ledOn", "ledOff", "preset1", "preset2", "preset3", "cruisemap1", "cruisemap2", "stopcruise", "blank", "up", "blank", "left", "stop", "right", "blank", "down", "blank", "refresh"]) 156 | } 157 | } 158 | 159 | //TAKE PICTURE 160 | def take() { 161 | log.debug("Taking Photo") 162 | sendEvent(name: "hubactionMode", value: "s3"); 163 | if(hdcamera == "true") { 164 | hubGet("cmd=snapPicture2") 165 | } 166 | else { 167 | hubGet("/snapshot.cgi?") 168 | } 169 | } 170 | //END TAKE PICTURE 171 | 172 | //ALARM ACTIONS 173 | def toggleAlarm() { 174 | log.debug "Toggling Alarm" 175 | if(device.currentValue("alarmStatus") == "on") { 176 | alarmOff() 177 | } 178 | else { 179 | alarmOn() 180 | } 181 | } 182 | 183 | def alarmOn() { 184 | log.debug "Enabling Alarm" 185 | sendEvent(name: "alarmStatus", value: "on"); 186 | if(hdcamera == "true") { 187 | hubGet("cmd=setMotionDetectConfig&isEnable=1") 188 | } 189 | else { 190 | hubGet("/set_alarm.cgi?motion_armed=1&") 191 | } 192 | } 193 | 194 | def alarmOff() { 195 | log.debug "Disabling Alarm" 196 | sendEvent(name: "alarmStatus", value: "off"); 197 | if(hdcamera == "true") { 198 | hubGet("cmd=setMotionDetectConfig&isEnable=0") 199 | } 200 | else { 201 | hubGet("/set_alarm.cgi?motion_armed=0&") 202 | } 203 | } 204 | //END ALARM ACTIONS 205 | 206 | //LED ACTIONS 207 | //Toggle LED's 208 | def toggleLED() { 209 | log.debug("Toggle LED") 210 | 211 | if(device.currentValue("ledStatus") == "auto") { 212 | ledOn() 213 | } 214 | 215 | else if(device.currentValue("ledStatus") == "on") { 216 | ledOff() 217 | } 218 | 219 | else { 220 | ledAuto() 221 | } 222 | } 223 | 224 | def ledOn() { 225 | log.debug("LED changed to: on") 226 | sendEvent(name: "ledStatus", value: "on"); 227 | if(hdcamera == "true") { 228 | delayBetween([hubGet("cmd=setInfraLedConfig&mode=1"), hubGet("cmd=openInfraLed")]) 229 | } 230 | else { 231 | hubGet("/decoder_control.cgi?command=95&") 232 | } 233 | } 234 | 235 | def ledOff() { 236 | log.debug("LED changed to: off") 237 | sendEvent(name: "ledStatus", value: "off"); 238 | if(hdcamera == "true") { 239 | delayBetween([hubGet("cmd=setInfraLedConfig&mode=1"), hubGet("cmd=closeInfraLed")]) 240 | } 241 | else { 242 | hubGet("/decoder_control.cgi?command=94&") 243 | } 244 | } 245 | 246 | def ledAuto() { 247 | log.debug("LED changed to: auto") 248 | sendEvent(name: "ledStatus", value: "auto"); 249 | if(hdcamera == "true") { 250 | hubGet("cmd=setInfraLedConfig&mode=0") 251 | } 252 | else { 253 | hubGet("/decoder_control.cgi?command=95&") 254 | } 255 | } 256 | //END LED ACTIONS 257 | 258 | //PRESET ACTIONS 259 | def preset1() { 260 | log.debug("Preset 1 Selected - ${preset1}") 261 | if(hdcamera == "true") { 262 | hubGet("cmd=ptzGotoPresetPoint&name=${preset1}") 263 | } 264 | else { 265 | hubGet("/decoder_control.cgi?command=31&") 266 | } 267 | } 268 | 269 | def preset2() { 270 | log.debug("Preset 2 Selected - ${preset2}") 271 | if(hdcamera == "true") { 272 | hubGet("cmd=ptzGotoPresetPoint&name=${preset2}") 273 | } 274 | else { 275 | hubGet("/decoder_control.cgi?command=33&") 276 | } 277 | } 278 | 279 | def preset3() { 280 | log.debug("Preset 3 Selected - ${preset3}") 281 | if(hdcamera == "true") { 282 | hubGet("cmd=ptzGotoPresetPoint&name=${preset3}") 283 | } 284 | else { 285 | hubGet("/decoder_control.cgi?command=35&") 286 | } 287 | } 288 | //END PRESET ACTIONS 289 | 290 | //CRUISE ACTIONS 291 | def cruisemap1() { 292 | log.debug("Cruise Map 1 Selected - ${cruisemap1}") 293 | if(hdcamera == "true") { 294 | hubGet("cmd=ptzStartCruise&mapName=${cruisemap1}") 295 | } 296 | else { 297 | hubGet("/decoder_control.cgi?command=28&") 298 | } 299 | } 300 | 301 | def cruisemap2() { 302 | log.debug("Cruise Map 2 Selected - ${cruisemap2}") 303 | if(hdcamera == "true") { 304 | hubGet("cmd=ptzStartCruise&mapName=${cruisemap2}") 305 | } 306 | else { 307 | hubGet("/decoder_control.cgi?command=26&") 308 | } 309 | } 310 | 311 | def stopCruise() { 312 | log.debug("Stop Cruise") 313 | if(hdcamera == "true") { 314 | hubGet("cmd=ptzStopRun") 315 | } 316 | else { 317 | delayBetween([hubGet("/decoder_control.cgi?command=29&"), hubGet("/decoder_control.cgi?command=27&")]) 318 | } 319 | } 320 | //END CRUISE ACTIONS 321 | 322 | //PTZ CONTROLS 323 | def left() { 324 | if(hdcamera == "true") { 325 | delayBetween([hubGet("cmd=ptzMoveLeft"), hubGet("cmd=ptzStopRun")]) 326 | } 327 | else { 328 | if(mirror == "true") { 329 | hubGet("/decoder_control.cgi?command=4&onestep=1&") 330 | } 331 | else { 332 | hubGet("/decoder_control.cgi?command=6&onestep=1&") 333 | } 334 | } 335 | } 336 | 337 | def right() { 338 | if(hdcamera == "true") { 339 | delayBetween([hubGet("cmd=ptzMoveRight"), hubGet("cmd=ptzStopRun")]) 340 | } 341 | else { 342 | if(mirror == "true") { 343 | hubGet("/decoder_control.cgi?command=6&onestep=1&") 344 | } 345 | else { 346 | hubGet("/decoder_control.cgi?command=4&onestep=1&") 347 | } 348 | } 349 | } 350 | 351 | def up() { 352 | if(hdcamera == "true") { 353 | delayBetween([hubGet("cmd=ptzMoveUp"), hubGet("cmd=ptzStopRun")]) 354 | } 355 | else { 356 | if(flip == "true") { 357 | hubGet("/decoder_control.cgi?command=2&onestep=1&") 358 | } 359 | else { 360 | hubGet("/decoder_control.cgi?command=0&onestep=1&") 361 | } 362 | } 363 | } 364 | 365 | def down() { 366 | if(hdcamera == "true") { 367 | delayBetween([hubGet("cmd=ptzMoveDown"), hubGet("cmd=ptzStopRun")]) 368 | } 369 | else { 370 | if(flip == "true") { 371 | hubGet("/decoder_control.cgi?command=0&onestep=1&") 372 | } 373 | else { 374 | hubGet("/decoder_control.cgi?command=2&onestep=1&") 375 | } 376 | } 377 | } 378 | //END PTZ CONTROLS 379 | 380 | def poll() { 381 | 382 | sendEvent(name: "hubactionMode", value: "local"); 383 | //Poll Motion Alarm Status and IR LED Mode 384 | if(hdcamera == "true") { 385 | delayBetween([hubGet("cmd=getMotionDetectConfig"), hubGet("cmd=getInfraLedConfig")]) 386 | } 387 | else { 388 | hubGet("/get_params.cgi?") 389 | } 390 | } 391 | 392 | private getLogin() { 393 | if(hdcamera == "true") { 394 | return "usr=${username}&pwd=${password}&" 395 | } 396 | else { 397 | return "user=${username}&pwd=${password}" 398 | } 399 | } 400 | 401 | private hubGet(def apiCommand) { 402 | //Setting Network Device Id 403 | def iphex = convertIPtoHex(ip) 404 | def porthex = convertPortToHex(port) 405 | device.deviceNetworkId = "$iphex:$porthex" 406 | log.debug "Device Network Id set to ${iphex}:${porthex}" 407 | 408 | log.debug("Executing hubaction on " + getHostAddress()) 409 | def uri = "" 410 | if(hdcamera == "true") { 411 | uri = "/cgi-bin/CGIProxy.fcgi?" + getLogin() + apiCommand 412 | } 413 | else { 414 | uri = apiCommand + getLogin() 415 | } 416 | log.debug uri 417 | def hubAction = new physicalgraph.device.HubAction( 418 | method: "GET", 419 | path: uri, 420 | headers: [HOST:getHostAddress()] 421 | ) 422 | if(device.currentValue("hubactionMode") == "s3") { 423 | hubAction.options = [outputMsgToS3:true] 424 | sendEvent(name: "hubactionMode", value: "local"); 425 | } 426 | hubAction 427 | } 428 | 429 | //Parse events into attributes 430 | def parse(String description) { 431 | log.debug "Parsing '${description}'" 432 | 433 | def map = [:] 434 | def retResult = [] 435 | def descMap = parseDescriptionAsMap(description) 436 | 437 | //Image 438 | if (descMap["bucket"] && descMap["key"]) { 439 | putImageInS3(descMap) 440 | } 441 | 442 | //Status Polling 443 | else if (descMap["headers"] && descMap["body"]) { 444 | def body = new String(descMap["body"].decodeBase64()) 445 | if(hdcamera == "true") { 446 | def langs = new XmlSlurper().parseText(body) 447 | 448 | def motionAlarm = "$langs.isEnable" 449 | def ledMode = "$langs.mode" 450 | 451 | //Get Motion Alarm Status 452 | if(motionAlarm == "0") { 453 | log.info("Polled: Alarm Off") 454 | sendEvent(name: "alarmStatus", value: "off"); 455 | } 456 | else if(motionAlarm == "1") { 457 | log.info("Polled: Alarm On") 458 | sendEvent(name: "alarmStatus", value: "on"); 459 | } 460 | 461 | //Get IR LED Mode 462 | if(ledMode == "0") { 463 | log.info("Polled: LED Mode Auto") 464 | sendEvent(name: "ledStatus", value: "auto") 465 | } 466 | else if(ledMode == "1") { 467 | log.info("Polled: LED Mode Manual") 468 | sendEvent(name: "ledStatus", value: "manual") 469 | } 470 | } 471 | else { 472 | if(body.find("alarm_motion_armed=0")) { 473 | log.info("Polled: Alarm Off") 474 | sendEvent(name: "alarmStatus", value: "off") 475 | } 476 | else if(body.find("alarm_motion_armed=1")) { 477 | log.info("Polled: Alarm On") 478 | sendEvent(name: "alarmStatus", value: "on") 479 | } 480 | //The API does not provide a way to poll for LED status on 8xxx series at the moment 481 | } 482 | } 483 | } 484 | 485 | def parseDescriptionAsMap(description) { 486 | description.split(",").inject([:]) { map, param -> 487 | def nameAndValue = param.split(":") 488 | map += [(nameAndValue[0].trim()):nameAndValue[1].trim()] 489 | } 490 | } 491 | 492 | def putImageInS3(map) { 493 | 494 | def s3ObjectContent 495 | 496 | try { 497 | def imageBytes = getS3Object(map.bucket, map.key + ".jpg") 498 | 499 | if(imageBytes) 500 | { 501 | s3ObjectContent = imageBytes.getObjectContent() 502 | def bytes = new ByteArrayInputStream(s3ObjectContent.bytes) 503 | storeImage(getPictureName(), bytes) 504 | } 505 | } 506 | catch(Exception e) { 507 | log.error e 508 | } 509 | finally { 510 | //Explicitly close the stream 511 | if (s3ObjectContent) { s3ObjectContent.close() } 512 | } 513 | } 514 | 515 | private getPictureName() { 516 | def pictureUuid = java.util.UUID.randomUUID().toString().replaceAll('-', '') 517 | "image" + "_$pictureUuid" + ".jpg" 518 | } 519 | 520 | private getHostAddress() { 521 | return "${ip}:${port}" 522 | } 523 | 524 | private String convertIPtoHex(ipAddress) { 525 | String hex = ipAddress.tokenize( '.' ).collect { String.format( '%02x', it.toInteger() ) }.join() 526 | return hex 527 | 528 | } 529 | 530 | private String convertPortToHex(port) { 531 | String hexport = port.toString().format( '%04x', port.toInteger() ) 532 | return hexport 533 | } -------------------------------------------------------------------------------- /devicetypes/superuser/aeon-smart-strip-2.src/aeon-smart-strip-2.groovy: -------------------------------------------------------------------------------- 1 | /** 2 | * Based on Aeon Smartstrip with inputs from @jwu 3 | * Devive handler for TKB TZ04 dual Relay insert 4 | * 5 | * By using BINARY_SWITCH_SET Command of Multi Channel Command Class Encapsulateion Command, 6 | * you can switch both Relay1 and Relay2 ON/OFF by setting endpoint to 1 or switch Relay1 ON/OFF 7 | * by setting endpoint to 2 or switch Relay1 ON/OFF by setting endpoint to 3 8 | * Status of Endpoint 1 returns ON when either relay 1 or 2 is ON or both or ON 9 | * 10 | * Device is capable of metering (combined when endpoint is set to 1) and individually when set to 2 or 3 11 | * 12 | 13 | */ 14 | // for the UI 15 | metadata { 16 | // Automatically generated. Make future change here. 17 | definition (name: "Aeon Smart Strip 2", author: "jjhamb@yahoo.com") { 18 | capability "Energy Meter" 19 | capability "Refresh" 20 | capability "Power Meter" 21 | capability "Switch" 22 | capability "Configuration" 23 | capability "Polling" 24 | 25 | attribute "power1", "string" 26 | attribute "power2", "string" 27 | attribute "power3", "string" 28 | attribute "energy1", "string" 29 | attribute "energy2", "string" 30 | attribute "energy3", "string" 31 | attribute "switch1", "string" 32 | attribute "switch2", "string" 33 | attribute "switch3", "string" 34 | 35 | command "on1" 36 | command "off1" 37 | command "on2" 38 | command "off2" 39 | command "on3" 40 | command "off3" 41 | command "testA" 42 | command "testB" 43 | } 44 | 45 | simulator { 46 | // TODO: define status and reply messages here 47 | } 48 | 49 | tiles { 50 | valueTile("power", "device.power", decoration: "flat") { 51 | state "default", label:'${currentValue} W' 52 | } 53 | valueTile("energy", "device.energy", decoration: "flat") { 54 | state "default", label:'${currentValue} kWh' 55 | } 56 | standardTile("switch1", "device.switch1",canChangeIcon: true) { 57 | state "on", label: "switch1", action: "off1", icon: "st.switches.switch.on", backgroundColor: "#79b821" 58 | state "off", label: "switch1", action: "on1", icon: "st.switches.switch.off", backgroundColor: "#ffffff" 59 | } 60 | standardTile("switch2", "device.switch2",canChangeIcon: true) { 61 | state "on", label: "switch2", action: "off2", icon: "st.switches.switch.on", backgroundColor: "#79b821" 62 | state "off", label: "switch2", action: "on2", icon: "st.switches.switch.off", backgroundColor: "#ffffff" 63 | } 64 | standardTile("switch3", "device.switch3",canChangeIcon: true) { 65 | state "on", label: "switch3", action: "off3", icon: "st.switches.switch.on", backgroundColor: "#79b821" 66 | state "off", label: "switch3", action:"on3", icon: "st.switches.switch.off", backgroundColor: "#ffffff" 67 | } 68 | standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat") { 69 | state "default", label:"", action:"refresh", icon:"st.secondary.refresh" 70 | } 71 | standardTile("reset", "device.switch", inactiveLabel: false, decoration: "flat") { 72 | state "default", label:"reset kWh", action:"reset" 73 | } 74 | standardTile("configure", "device.switch", inactiveLabel: false, decoration: "flat") { 75 | state "default", label:"", action:"configure", icon:"st.secondary.configure" 76 | } 77 | 78 | valueTile("power2", "device.power2", decoration: "flat") { 79 | state "default", label:'${currentValue} W' 80 | } 81 | valueTile("energy2", "device.energy2", decoration: "flat") { 82 | state "default", label:'${currentValue} kWh' 83 | } 84 | valueTile("power3", "device.power3", decoration: "flat") { 85 | state "default", label:'${currentValue} W' 86 | } 87 | valueTile("energy3", "device.energy3", decoration: "flat") { 88 | state "default", label:'${currentValue} kWh' 89 | } 90 | 91 | 92 | main(["switch1", "power", "energy"]) 93 | details(["switch1", "power", "energy", "switch2", "power2", "energy2", 94 | "switch3", "power3", "energy3", "refresh", "configure", "reset"]) 95 | } 96 | } 97 | 98 | // 0x25 0x32 0x27 0x70 0x85 0x72 0x86 0x60 0xEF 0x82 99 | 100 | // 0x25: switch binary 101 | // 0x32: meter 102 | // 0x27: switch all 103 | // 0x70: configuration 104 | // 0x85: association 105 | // 0x86: version 106 | // 0x60: multi-channel 107 | // 0xEF: mark 108 | // 0x82: hail 109 | 110 | // parse events into attributes 111 | def parse(String description) { 112 | log.debug "Parsing desc => '${description}'" 113 | 114 | def result = null 115 | def cmd = zwave.parse(description, [0x60:3, 0x25:1, 0x32:1, 0x70:1]) 116 | if (cmd) { 117 | result = createEvent(zwaveEvent(cmd)) 118 | } 119 | log.debug "Parsing result => '${result}'" 120 | return result 121 | } 122 | 123 | //Reports 124 | 125 | def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicReport cmd) { 126 | [name: "switch", value: cmd.value ? "on" : "off", type: "physical"] 127 | } 128 | 129 | def zwaveEvent(physicalgraph.zwave.commands.switchbinaryv1.SwitchBinaryReport cmd) { 130 | [name: "switch", value: cmd.value ? "on" : "off", type: "digital"] 131 | } 132 | 133 | def zwaveEvent(physicalgraph.zwave.commands.meterv1.MeterReport cmd) { 134 | def map = [] 135 | 136 | if (cmd.scale == 0) { 137 | map = [ name: "energy", value: cmd.scaledMeterValue, unit: "kWh" ] 138 | } 139 | else if (cmd.scale == 2) { 140 | map = [ name: "power", value: Math.round(cmd.scaledMeterValue), unit: "W" ] 141 | } 142 | 143 | map 144 | } 145 | 146 | def zwaveEvent(int endPoint, physicalgraph.zwave.commands.meterv1.MeterReport cmd) { 147 | // MeterReport(deltaTime: 1368, meterType: 1, meterValue: [0, 3, 29, 17], precision: 3, previousMeterValue: [0, 3, 29, 17], rateType: 1, reserved02: false, scale: 0, scaledMeterValue: 204.049, scaledPreviousMeterValue: 204.049, size: 4) 148 | log.debug "EndPoint $endPoint, MeterReport $cmd" 149 | def map = [] 150 | 151 | if (cmd.scale == 0) { 152 | map = [ name: "energy" + endPoint, value: cmd.scaledMeterValue, unit: "kWh" ] 153 | } 154 | else if (cmd.scale == 2) { 155 | map = [ name: "power" + endPoint, value: Math.round(cmd.scaledMeterValue), unit: "W" ] 156 | } 157 | 158 | map 159 | } 160 | 161 | /* 162 | def zwaveEvent(physicalgraph.zwave.commands.multichannelv3.MultiChannelCmdEncap cmd) { 163 | log.debug "MultiChannelCmdEncap $cmd" 164 | 165 | def map = [ name: "switch$cmd.sourceEndPoint" ] 166 | if (cmd.commandClass == 37){ 167 | if (cmd.parameter == [0]) { 168 | map.value = "off" 169 | } 170 | if (cmd.parameter == [255]) { 171 | map.value = "on" 172 | } 173 | map 174 | } 175 | else if (cmd.commandClass == 50) { 176 | // bitAddress: false, command: 2, commandClass: 50, destinationEndPoint: 1, parameter: [33, 100, 0, 0, 0, 0, 0, 94, 0, 0, 0, 0], res01: false, sourceEndPoint: 1 177 | def hex1 = { n -> String.format("%02X", n) } 178 | def desc = "command: ${hex1(cmd.commandClass)}${hex1(cmd.command)}, payload: " + cmd.parameter.collect{hex1(it)}.join(" ") 179 | // Re-assign source end point 3-6 to 1-4 and 1-2 to 5-6 to sync up with the switch end points. 180 | // Source end point in the message refers to always-on sockets. 181 | zwaveEvent((cmd.sourceEndPoint > 3) ? (cmd.sourceEndPoint-3) : (cmd.sourceEndPoint+3), zwave.parse(desc, [ 0x60:3, 0x25:1, 0x32:1, 0x70:1 ])) 182 | } 183 | } 184 | */ 185 | def zwaveEvent(physicalgraph.zwave.commands.multichannelv3.MultiChannelCmdEncap cmd) { 186 | log.debug "MultiChannelCmdEncap $cmd" 187 | 188 | def map = [ name: "switch$cmd.sourceEndPoint" ] 189 | if (cmd.commandClass == 37){ 190 | if (cmd.parameter == [0]) { 191 | map.value = "off" 192 | } 193 | if (cmd.parameter == [255]) { 194 | map.value = "on" 195 | } 196 | map 197 | } 198 | else if (cmd.commandClass == 50) { 199 | def hex1 = { n -> String.format("%02X", n) } 200 | def desc = "command: ${hex1(cmd.commandClass)}${hex1(cmd.command)}, payload: " + cmd.parameter.collect{hex1(it)}.join(" ") 201 | zwaveEvent(cmd.sourceEndPoint, zwave.parse(desc, [ 0x60:3, 0x25:1, 0x32:1, 0x70:1 ])) 202 | } 203 | } 204 | 205 | def zwaveEvent(physicalgraph.zwave.commands.multichannelv3.MultiChannelCapabilityReport cmd) { 206 | // [50, 37, 32], dynamic: false, endPoint: 1, genericDeviceClass: 16, specificDeviceClass: 1) 207 | log.debug "multichannelv3.MultiChannelCapabilityReport $cmd" 208 | } 209 | 210 | def zwaveEvent(physicalgraph.zwave.commands.configurationv1.ConfigurationReport cmd) { 211 | log.debug "Configuration Report for parameter ${cmd.parameterNumber}: Value is ${cmd.configurationValue}, Size is ${cmd.size}" 212 | } 213 | 214 | def zwaveEvent(physicalgraph.zwave.Command cmd) { 215 | // Handles all Z-Wave commands we aren't interested in 216 | [:] 217 | log.debug "Capture All $cmd" 218 | } 219 | 220 | // handle commands 221 | def refresh() { 222 | def cmds = [] 223 | 224 | for ( i in 1..3 ) 225 | cmds << zwave.multiChannelV3.multiChannelCmdEncap(sourceEndPoint:1, destinationEndPoint:i, commandClass:37, command:2).format() 226 | 227 | for ( i in 1..3 ) { 228 | cmds << zwave.multiChannelV3.multiChannelCmdEncap(sourceEndPoint:1, destinationEndPoint:i, commandClass:50, command:1, parameter:[0]).format() 229 | cmds << zwave.multiChannelV3.multiChannelCmdEncap(sourceEndPoint:1, destinationEndPoint:i, commandClass:50, command:1, parameter:[16]).format() 230 | } 231 | 232 | cmds << zwave.meterV2.meterGet(scale:0).format() 233 | cmds << zwave.meterV2.meterGet(scale:2).format() 234 | 235 | delayBetween(cmds) 236 | } 237 | 238 | def on(value) { 239 | log.debug "value $value" 240 | delayBetween([ 241 | zwave.multiChannelV3.multiChannelCmdEncap(sourceEndPoint:1, destinationEndPoint: value, commandClass:37, command:1, parameter:[255]).format(), 242 | zwave.multiChannelV3.multiChannelCmdEncap(sourceEndPoint:1, destinationEndPoint: value, commandClass:37, command:2).format() 243 | ]) 244 | } 245 | 246 | def off(value) { 247 | log.debug "value $value" 248 | delayBetween([ 249 | zwave.multiChannelV3.multiChannelCmdEncap(sourceEndPoint:1, destinationEndPoint: value, commandClass:37, command:1, parameter:[0]).format(), 250 | zwave.multiChannelV3.multiChannelCmdEncap(sourceEndPoint:1, destinationEndPoint: value, commandClass:37, command:2).format() 251 | ]) 252 | } 253 | 254 | def poll() { 255 | log.debug "Poll - Refreshing" 256 | refresh() 257 | } 258 | 259 | def configure() { 260 | log.debug "Executing 'configure'" 261 | delayBetween([ 262 | zwave.configurationV1.configurationSet(parameterNumber:101, size:4, configurationValue: [ 0, 0, 127, 127 ]).format(), // Report meter on all channels (6 sockets + master) 263 | zwave.configurationV1.configurationSet(parameterNumber:111, size:4, scaledConfigurationValue: 120).format(), // 120s interval for meter reports 264 | zwave.configurationV1.configurationSet(parameterNumber:4, configurationValue: [0]).format() // Report reguarly 265 | ]) 266 | } 267 | 268 | def on1() { 269 | delayBetween([ 270 | zwave.multiChannelV3.multiChannelCmdEncap(sourceEndPoint:1, destinationEndPoint:1, commandClass:37, command:1, parameter:[255]).format(), 271 | zwave.multiChannelV3.multiChannelCmdEncap(sourceEndPoint:1, destinationEndPoint:1, commandClass:37, command:2).format(), 272 | zwave.multiChannelV3.multiChannelCmdEncap(sourceEndPoint:1, destinationEndPoint:2, commandClass:37, command:2).format(), 273 | zwave.multiChannelV3.multiChannelCmdEncap(sourceEndPoint:1, destinationEndPoint:3, commandClass:37, command:2).format(), 274 | zwave.multiChannelV3.multiChannelCmdEncap(sourceEndPoint:1, destinationEndPoint:2, commandClass:50, command:1, parameter:[0]).format(), 275 | zwave.multiChannelV3.multiChannelCmdEncap(sourceEndPoint:1, destinationEndPoint:2, commandClass:50, command:1, parameter:[16]).format(), 276 | zwave.multiChannelV3.multiChannelCmdEncap(sourceEndPoint:1, destinationEndPoint:3, commandClass:50, command:1, parameter:[0]).format(), 277 | zwave.multiChannelV3.multiChannelCmdEncap(sourceEndPoint:1, destinationEndPoint:3, commandClass:50, command:1, parameter:[16]).format(), 278 | zwave.meterV2.meterGet(scale:0).format(), 279 | zwave.meterV2.meterGet(scale:2).format() 280 | ]) 281 | } 282 | 283 | def off1() { 284 | delayBetween([ 285 | zwave.multiChannelV3.multiChannelCmdEncap(sourceEndPoint:1, destinationEndPoint:1, commandClass:37, command:1, parameter:[0]).format(), 286 | zwave.multiChannelV3.multiChannelCmdEncap(sourceEndPoint:1, destinationEndPoint:1, commandClass:37, command:2).format(), 287 | zwave.multiChannelV3.multiChannelCmdEncap(sourceEndPoint:1, destinationEndPoint:2, commandClass:37, command:2).format(), 288 | zwave.multiChannelV3.multiChannelCmdEncap(sourceEndPoint:1, destinationEndPoint:3, commandClass:37, command:2).format(), 289 | zwave.multiChannelV3.multiChannelCmdEncap(sourceEndPoint:1, destinationEndPoint:2, commandClass:50, command:1, parameter:[0]).format(), 290 | zwave.multiChannelV3.multiChannelCmdEncap(sourceEndPoint:1, destinationEndPoint:2, commandClass:50, command:1, parameter:[16]).format(), 291 | zwave.multiChannelV3.multiChannelCmdEncap(sourceEndPoint:1, destinationEndPoint:3, commandClass:50, command:1, parameter:[0]).format(), 292 | zwave.multiChannelV3.multiChannelCmdEncap(sourceEndPoint:1, destinationEndPoint:3, commandClass:50, command:1, parameter:[16]).format(), 293 | zwave.meterV2.meterGet(scale:0).format(), 294 | zwave.meterV2.meterGet(scale:2).format() 295 | ]) 296 | } 297 | 298 | def on2() { 299 | delayBetween([ 300 | zwave.multiChannelV3.multiChannelCmdEncap(sourceEndPoint:1, destinationEndPoint:2, commandClass:37, command:1, parameter:[255]).format(), 301 | zwave.multiChannelV3.multiChannelCmdEncap(sourceEndPoint:1, destinationEndPoint:2, commandClass:37, command:2).format(), 302 | zwave.multiChannelV3.multiChannelCmdEncap(sourceEndPoint:1, destinationEndPoint:2, commandClass:50, command:1, parameter:[0]).format(), 303 | zwave.multiChannelV3.multiChannelCmdEncap(sourceEndPoint:1, destinationEndPoint:2, commandClass:50, command:1, parameter:[16]).format(), 304 | zwave.multiChannelV3.multiChannelCmdEncap(sourceEndPoint:1, destinationEndPoint:1, commandClass:37, command:2).format(), 305 | zwave.meterV2.meterGet(scale:0).format(), 306 | zwave.meterV2.meterGet(scale:2).format() 307 | ]) 308 | } 309 | 310 | def off2() { 311 | delayBetween([ 312 | zwave.multiChannelV3.multiChannelCmdEncap(sourceEndPoint:1, destinationEndPoint:2, commandClass:37, command:1, parameter:[0]).format(), 313 | zwave.multiChannelV3.multiChannelCmdEncap(sourceEndPoint:1, destinationEndPoint:2, commandClass:37, command:2).format(), 314 | zwave.multiChannelV3.multiChannelCmdEncap(sourceEndPoint:1, destinationEndPoint:2, commandClass:50, command:1, parameter:[0]).format(), 315 | zwave.multiChannelV3.multiChannelCmdEncap(sourceEndPoint:1, destinationEndPoint:2, commandClass:50, command:1, parameter:[16]).format(), 316 | zwave.multiChannelV3.multiChannelCmdEncap(sourceEndPoint:1, destinationEndPoint:1, commandClass:37, command:2).format(), 317 | zwave.meterV2.meterGet(scale:0).format(), 318 | zwave.meterV2.meterGet(scale:2).format() 319 | ]) 320 | } 321 | 322 | def on3() { 323 | delayBetween([ 324 | zwave.multiChannelV3.multiChannelCmdEncap(sourceEndPoint:1, destinationEndPoint:3, commandClass:37, command:1, parameter:[255]).format(), 325 | zwave.multiChannelV3.multiChannelCmdEncap(sourceEndPoint:1, destinationEndPoint:3, commandClass:37, command:2).format(), 326 | zwave.multiChannelV3.multiChannelCmdEncap(sourceEndPoint:1, destinationEndPoint:3, commandClass:50, command:1, parameter:[0]).format(), 327 | zwave.multiChannelV3.multiChannelCmdEncap(sourceEndPoint:1, destinationEndPoint:3, commandClass:50, command:1, parameter:[16]).format(), 328 | zwave.multiChannelV3.multiChannelCmdEncap(sourceEndPoint:1, destinationEndPoint:1, commandClass:37, command:2).format(), 329 | zwave.meterV2.meterGet(scale:0).format(), 330 | zwave.meterV2.meterGet(scale:2).format() 331 | ]) 332 | } 333 | 334 | def off3() { 335 | delayBetween([ 336 | zwave.multiChannelV3.multiChannelCmdEncap(sourceEndPoint:1, destinationEndPoint:3, commandClass:37, command:1, parameter:[0]).format(), 337 | zwave.multiChannelV3.multiChannelCmdEncap(sourceEndPoint:1, destinationEndPoint:3, commandClass:37, command:2).format(), 338 | zwave.multiChannelV3.multiChannelCmdEncap(sourceEndPoint:1, destinationEndPoint:3, commandClass:50, command:1, parameter:[0]).format(), 339 | zwave.multiChannelV3.multiChannelCmdEncap(sourceEndPoint:1, destinationEndPoint:3, commandClass:50, command:1, parameter:[16]).format(), 340 | zwave.multiChannelV3.multiChannelCmdEncap(sourceEndPoint:1, destinationEndPoint:1, commandClass:37, command:2).format(), 341 | zwave.meterV2.meterGet(scale:0).format(), 342 | zwave.meterV2.meterGet(scale:2).format() 343 | ]) 344 | } 345 | 346 | /** 347 | //test 348 | def testB() { 349 | def cmds = [] 350 | cmds << zwave.multiChannelV3.multiChannelCapabilityGet(endPoint:1).format() 351 | cmds << zwave.multiChannelV3.multiChannelCmdEncap(sourceEndPoint:1, destinationEndPoint:3, commandClass:50, command:1, parameter:[0]).format() 352 | cmds << zwave.multiChannelV3.multiChannelCmdEncap(sourceEndPoint:1, destinationEndPoint:3, commandClass:50, command:1, parameter:[16]).format() 353 | cmds << zwave.multiChannelV3.multiChannelEndPointFind(genericDeviceClass:32).format() 354 | cmds << zwave.multiChannelV3.multiChannelEndPointGet().format() 355 | cmds << zwave.meterV2.meterGet(scale:0).format() 356 | cmds << zwave.meterV2.meterGet(scale:2).format() 357 | cmds << zwave.configurationV1.configurationGet(parameterNumber:101).format() 358 | cmds << zwave.configurationV1.configurationGet(parameterNumber:4).format() 359 | cmds << zwave.configurationV1.configurationGet(parameterNumber:90).format() 360 | log.debug "Sending ${cmds.inspect()}" 361 | delayBetween(cmds, 2300) 362 | } 363 | 364 | def testA() { 365 | log.debug "testA" 366 | def cmds = [] 367 | // cmds << zwave.multiChannelV3.multiChannelCmdEncap(sourceEndPoint:1, destinationEndPoint:1, commandClass:50, command:1, parameter:[0]).format() 368 | // cmds << zwave.multiChannelV3.multiChannelCmdEncap(sourceEndPoint:1, destinationEndPoint:1, commandClass:50, command:1, parameter:[16]).format() 369 | // cmds << zwave.switchBinaryV1.switchBinaryGet().format() 370 | // cmds << zwave.meterV2.meterGet(scale: 0).format() 371 | cmds << zwave.meterV3.meterGet(scale: 0).format() 372 | log.debug "$cmds" 373 | delayBetween(cmds, 1000) 374 | } 375 | **/ 376 | //reset 377 | def reset() { 378 | def cmds = [] 379 | cmds << zwave.meterV2.meterReset().format() 380 | cmds << zwave.meterV2.meterGet(scale:0).format() 381 | for ( i in 1..6 ) 382 | cmds << zwave.multiChannelV3.multiChannelCmdEncap(sourceEndPoint:1, destinationEndPoint:i, commandClass:50, command:1, parameter:[0]).format() 383 | 384 | delayBetween(cmds) 385 | } -------------------------------------------------------------------------------- /devicetypes/superuser/poolswitch.src/poolswitch.groovy: -------------------------------------------------------------------------------- 1 | /** 2 | * PoolSwitch 3 | * 4 | * Copyright 2014 bigpunk6 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 | metadata { 17 | definition (name: "PowerStrip", author: "Ledridge") { 18 | capability "Actuator" 19 | capability "Switch" 20 | capability "Polling" 21 | capability "Configuration" 22 | capability "Refresh" 23 | capability "Temperature Measurement" 24 | capability "Sensor" 25 | 26 | attribute "switch1", "string" 27 | attribute "switch2", "string" 28 | attribute "switch3", "string" 29 | attribute "switch4", "string" 30 | 31 | command "onMulti" 32 | command "offMulti" 33 | command "on1" 34 | command "off1" 35 | command "on2" 36 | command "off2" 37 | command "on3" 38 | command "off3" 39 | command "on4" 40 | command "off4" 41 | } 42 | 43 | simulator { 44 | // TODO: define status and reply messages here 45 | } 46 | 47 | // tile definitions 48 | tiles { 49 | standardTile("switch1", "device.switch1",canChangeIcon: true) { 50 | state "on", label: "switch1", action: "off1", icon: "st.switches.switch.on", backgroundColor: "#79b821" 51 | state "off", label: "switch1", action: "on1", icon: "st.switches.switch.off", backgroundColor: "#ffffff" 52 | } 53 | standardTile("switch2", "device.switch2",canChangeIcon: true) { 54 | state "on", label: "switch2", action: "off2", icon: "st.switches.switch.on", backgroundColor: "#79b821" 55 | state "off", label: "switch2", action: "on2", icon: "st.switches.switch.off", backgroundColor: "#ffffff" 56 | } 57 | standardTile("switch3", "device.switch3",canChangeIcon: true) { 58 | state "on", label: "switch3", action: "off3", icon: "st.switches.switch.on", backgroundColor: "#79b821" 59 | state "off", label: "switch3", action:"on3", icon: "st.switches.switch.off", backgroundColor: "#ffffff" 60 | } 61 | standardTile("switch4", "device.switch4",canChangeIcon: true) { 62 | state "on", label: "switch4", action: "off4", icon: "st.switches.switch.on", backgroundColor: "#79b821" 63 | state "off", label: "switch4", action:"on4", icon: "st.switches.switch.off", backgroundColor: "#ffffff" 64 | } 65 | 66 | standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat") { 67 | state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh" 68 | } 69 | 70 | standardTile("configure", "device.configure", inactiveLabel: false, decoration: "flat") { 71 | state "configure", label:'', action:"configuration.configure", icon:"st.secondary.configure" 72 | } 73 | 74 | main(["switch1","switch2","switch3","switch4","temperature"]) 75 | details(["switch1","switch2","switch3","switch4","temperature","refresh"]) 76 | } 77 | } 78 | 79 | import physicalgraph.zwave.commands.* 80 | 81 | //Parse 82 | def parse(String description) { 83 | def result = null 84 | def cmd = zwave.parse(description, [0x20: 1, 0x70: 2, 0x86: 1, 0x60:3, 0x31:1, 0x25:1, 0x81:1]) 85 | if (cmd) { 86 | if( cmd.CMD == "6006" ) { 87 | def map = [ name: "switch$cmd.instance" ] 88 | if (cmd.commandClass == 37){ 89 | if (cmd.parameter == [0]) { 90 | map.value = "off" 91 | } 92 | if (cmd.parameter == [255]) { 93 | map.value = "on" 94 | } 95 | } 96 | result = createEvent(map) 97 | } else { 98 | result = createEvent(zwaveEvent(cmd)) 99 | } 100 | } 101 | log.debug "Parse returned ${result?.descriptionText}" 102 | return result 103 | } 104 | 105 | //Reports 106 | def zwaveEvent(sensormultilevelv1.SensorMultilevelReport cmd) 107 | { 108 | def map = [:] 109 | map.value = cmd.scaledSensorValue.toString() 110 | map.unit = cmd.scale == 1 ? "F" : "C" 111 | map.name = "temperature" 112 | map 113 | } 114 | 115 | def zwaveEvent(thermostatsetpointv2.ThermostatSetpointReport cmd) 116 | { 117 | def map = [:] 118 | map.value = cmd.scaledValue.toString() 119 | map.unit = cmd.scale == 1 ? "F" : "C" 120 | map.displayed = false 121 | switch (cmd.setpointType) { 122 | case 1: 123 | map.name = "poolSetpoint" 124 | break; 125 | case 7: 126 | map.name = "spaSetpoint" 127 | break; 128 | default: 129 | return [:] 130 | } 131 | // So we can respond with same format 132 | state.size = cmd.size 133 | state.scale = cmd.scale 134 | state.precision = cmd.precision 135 | map 136 | } 137 | 138 | def zwaveEvent(multichannelv3.MultiInstanceReport cmd) { 139 | log.debug "$cmd" 140 | } 141 | 142 | def zwaveEvent(multichannelv3.MultiChannelCapabilityReport cmd) { 143 | log.debug "$cmd" 144 | } 145 | 146 | def zwaveEvent(multichannelv3.MultiChannelEndPointReport cmd) { 147 | log.debug "$cmd" 148 | } 149 | 150 | def zwaveEvent(multichannelv3.MultiInstanceCmdEncap cmd) { 151 | log.debug "$cmd" 152 | def map = [ name: "switch$cmd.instance" ] 153 | if (cmd.commandClass == 37){ 154 | if (cmd.parameter == [0]) { 155 | map.value = "off" 156 | } 157 | if (cmd.parameter == [255]) { 158 | map.value = "on" 159 | } 160 | } 161 | createEvent(map) 162 | } 163 | 164 | def zwaveEvent(multichannelv3.MultiChannelCmdEncap cmd) { 165 | log.debug "$cmd" 166 | def map = [ name: "switch$cmd.destinationEndPoint" ] 167 | if (cmd.commandClass == 37){ 168 | if (cmd.parameter == [0]) { 169 | map.value = "off" 170 | } 171 | if (cmd.parameter == [255]) { 172 | map.value = "on" 173 | } 174 | } 175 | createEvent(map) 176 | } 177 | 178 | def zwaveEvent(cmd) { 179 | log.warn "Captured zwave command $cmd" 180 | } 181 | 182 | //Commands 183 | 184 | def setPoolSetpoint(degreesF) { 185 | setHeatingSetpoint(degreesF.toDouble()) 186 | } 187 | 188 | def setPoolSetpoint(Double degreesF) { 189 | def p = (state.precision == null) ? 1 : state.precision 190 | delayBetween([ 191 | zwave.thermostatSetpointV1.thermostatSetpointSet(setpointType: 1, scale: 1, precision: p, scaledValue: degreesF).format(), 192 | zwave.thermostatSetpointV1.thermostatSetpointGet(setpointType: 1).format() 193 | ]) 194 | } 195 | 196 | def setSpaSetpoint(degreesF) { 197 | setSpaSetpoint(degreesF.toDouble()) 198 | } 199 | 200 | def setSpaSetpoint(Double degreesF) { 201 | def p = (state.precision == null) ? 1 : state.precision 202 | delayBetween([ 203 | zwave.thermostatSetpointV1.thermostatSetpointSet(setpointType: 7, scale: 1, precision: p, scaledValue: degreesF).format(), 204 | zwave.thermostatSetpointV1.thermostatSetpointGet(setpointType: 7).format() 205 | ]) 206 | } 207 | 208 | def on() { 209 | delayBetween([ 210 | zwave.multiChannelV3.multiChannelCmdEncap(sourceEndPoint: 1, destinationEndPoint: 1, commandClass:37, command:1, parameter:[255]).format(), 211 | zwave.multiChannelV3.multiChannelCmdEncap(sourceEndPoint: 1, destinationEndPoint: 1, commandClass:37, command:2).format() 212 | ], 2300) 213 | } 214 | 215 | def off() { 216 | delayBetween([ 217 | zwave.multiChannelV3.multiChannelCmdEncap(sourceEndPoint: 1, destinationEndPoint: 1, commandClass:37, command:1, parameter:[0]).format(), 218 | zwave.multiChannelV3.multiChannelCmdEncap(sourceEndPoint: 1, destinationEndPoint: 1, commandClass:37, command:2).format() 219 | ], 2300) 220 | } 221 | 222 | //switch instance 223 | def onMulti(value) { 224 | delayBetween([ 225 | zwave.multiChannelV3.multiChannelCmdEncap(sourceEndPoint: value, destinationEndPoint: value, commandClass:37, command:1, parameter:[255]).format(), 226 | zwave.multiChannelV3.multiChannelCmdEncap(sourceEndPoint: value, destinationEndPoint: value, commandClass:37, command:2).format() 227 | ], 2300) 228 | } 229 | 230 | def offMulti(value) { 231 | delayBetween([ 232 | zwave.multiChannelV3.multiChannelCmdEncap(sourceEndPoint: value, destinationEndPoint: value, commandClass:37, command:1, parameter:[0]).format(), 233 | zwave.multiChannelV3.multiChannelCmdEncap(sourceEndPoint: value, destinationEndPoint: value, commandClass:37, command:2).format() 234 | ], 2300) 235 | } 236 | 237 | //switch1 238 | def on1() { 239 | onMulti(1) 240 | } 241 | 242 | def off1() { 243 | offMulti(1) 244 | } 245 | 246 | //switch2 247 | def on2() { 248 | onMulti(2) 249 | } 250 | 251 | def off2() { 252 | offMulti(2) 253 | } 254 | 255 | //switch3 256 | def on3() { 257 | onMulti(3) 258 | } 259 | 260 | def off3() { 261 | offMulti(3) 262 | } 263 | 264 | //switch4 265 | def on4() { 266 | onMulti(4) 267 | } 268 | 269 | def off4() { 270 | offMulti(4) 271 | } 272 | 273 | 274 | def poll() { 275 | zwave.sensorMultilevelV1.sensorMultilevelGet().format() 276 | } 277 | 278 | def refresh() { 279 | delayBetween([ 280 | zwave.multiChannelV3.multiChannelCmdEncap(sourceEndPoint:1, destinationEndPoint:1, commandClass:37, command:2).format(), 281 | zwave.multiChannelV3.multiChannelCmdEncap(sourceEndPoint:2, destinationEndPoint:2, commandClass:37, command:2).format(), 282 | zwave.multiChannelV3.multiChannelCmdEncap(sourceEndPoint:3, destinationEndPoint:3, commandClass:37, command:2).format(), 283 | zwave.multiChannelV3.multiChannelCmdEncap(sourceEndPoint:4, destinationEndPoint:4, commandClass:37, command:2).format(), 284 | zwave.sensorMultilevelV1.sensorMultilevelGet().format(), 285 | zwave.thermostatSetpointV1.thermostatSetpointGet(setpointType: 1).format(), 286 | zwave.thermostatSetpointV1.thermostatSetpointGet(setpointType: 7).format() 287 | ], 2500) 288 | } -------------------------------------------------------------------------------- /vTile_DLI: -------------------------------------------------------------------------------- 1 | /** 2 | * V Tile device-type for Digital Loggers Inc. 3 | * 4 | * Needed for Multi Switch with Virtual Tiles to create virtual switch tiles in ST for devices that have multiple "switch[x]" 5 | * attributes within them and have on[x], off[x], and cycle[x] commands for each. 6 | * Also has support for device-label inside the name when on or off and polling occurs 7 | * 8 | */ 9 | metadata { 10 | definition (name: "vTile_DLI", namespace: "Ledridge", author: "Ledridge") { 11 | capability "Switch" 12 | capability "relaySwitch" 13 | capability "Polling" 14 | capability "Refresh" 15 | 16 | attribute "lastEvent", "string" 17 | 18 | command "cycle" 19 | } 20 | } 21 | 22 | preferences { 23 | tiles { 24 | standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true, canChangeBackground: true) { 25 | state "off", label:'${name}', action: "switch.on", icon:"st.switches.switch.off", backgroundColor: "#DDDDff", nextState: "turningOn" 26 | state "on", label:'${name}', action: "switch.off", icon:"st.switches.switch.on", backgroundColor: "#0088ff", nextState: "turningOff" 27 | state "turningOff", label:'${name}', action: "switch.on", icon:"st.switches.switch.off", backgroundColor: "#FA5882", nextState: "off" 28 | state "turningOn", label:'${name}', action: "switch.on", icon:"st.switches.switch.on", backgroundColor: "#F3F781", nextState: "on" 29 | state "cyclingOff", label:"Turning Off", icon:"st.switches.switch.off", backgroundColor: "#FA5882", nextState: "cyclingOn" 30 | state "cyclingOn", label:"Turning On", icon:"st.switches.switch.on", backgroundColor: "#F3F781", nextState: "on" 31 | } 32 | 33 | standardTile("Cycle", "device.switch", width: 1, height: 2, canChangeIcon: true) { 34 | state "default", action: "cycle", icon: "st.secondary.refresh-icon", backgroundColor: "#0088ff" 35 | } 36 | 37 | valueTile("lastEvent", "device.lastEvent", inactiveLabel: false, width: 3, height: 1, canChangeIcon: false, decoration:"flat") { 38 | state "default", label: 'Last Event: ${currentValue}'} 39 | 40 | main "switch" 41 | details(["switch", "Cycle", "lastEvent"]) 42 | } 43 | } 44 | 45 | def parse(desc) { 46 | def results = [] 47 | log.debug desc 48 | if(desc=="updated") { log.debug "Device $device.label has been UPDATED"; poll() } 49 | } 50 | 51 | def on() { 52 | sendEvent([name: "switch", value: "on"]) 53 | parent.OutletAction(this,"ON") 54 | sendEvent([name: "lastEvent", value: "${df(now())}"]) 55 | log.debug "$device.label is On" 56 | } 57 | 58 | def off() { 59 | sendEvent([name: "switch", value: "off"]) 60 | parent.OutletAction(this,"OFF") 61 | sendEvent([name: "switch", value: "$device.label"]) 62 | sendEvent([name: "lastEvent", value: "${df(now())}"]) 63 | log.debug "$device.label is Off" 64 | } 65 | 66 | def cycle() { 67 | log.debug "$device.label is Cycling" 68 | parent.OutletAction(this,"CCL") 69 | 70 | sendEvent([name: "switch", value: "cyclingOff"]) 71 | pause(6000) 72 | 73 | sendEvent([name: "switch", value: "cyclingOn"]) 74 | pause(5000) 75 | 76 | sendEvent([name: "switch", value: "on"]) 77 | 78 | sendEvent([name: "lastEvent", value: "${df(now())}"]) 79 | //log.debug "$device.label is Off" 80 | } 81 | 82 | def poll() { 83 | def current = device.currentValue("switch") 84 | log.debug "Polling - $device.label is $current" 85 | 86 | log.debug "This - $this" 87 | 88 | def outletStatus = parent.OutletStatus(this) 89 | log.debug "Polling - Status is $outletStatus" 90 | 91 | def OutletName = parent.OutletName(this) 92 | log.debug "Polling - Name is $OutletName" 93 | 94 | if(!current || current=="off") { sendEvent(name:"switch", value:"$device.label", isStateChange:true, displayed:false) } 95 | } 96 | 97 | def pause(millis) { 98 | def passed = 0 99 | def now = new Date().time 100 | log.debug "pausing... at Now: $now" 101 | /* This loop is an impolite busywait. We need to be given a true sleep() method, please. */ 102 | while ( passed < millis ) { 103 | passed = new Date().time - now 104 | } 105 | log.debug "... DONE pausing." 106 | } 107 | 108 | def df(e) { 109 | // * df(e) - Date Format "E" 110 | // * Takes epoch time format and returns Date formatted in current timezone 111 | def locale = getWeatherFeature("geolookup", zip); 112 | def tz = TimeZone.getTimeZone(locale.location.tz_long); 113 | def formatted 114 | if(e) { formatted = new Date(e).format("EEE, MMM d, 'at' hh:mm aaa", tz); return formatted } 115 | } 116 | --------------------------------------------------------------------------------