mirror of
https://github.com/mtan93/SmartThingsPublic.git
synced 2026-03-13 13:21:53 +00:00
Compare commits
12 Commits
MSA-1956-3
...
MSA-1969-1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6417957ab4 | ||
|
|
a99e050c6b | ||
|
|
5bf7caca0d | ||
|
|
3eddd68532 | ||
|
|
8d79379bba | ||
|
|
dd4da29bcd | ||
|
|
a3e9f1d2c1 | ||
|
|
8bfc3f0c1c | ||
|
|
b6136bf1d5 | ||
|
|
68f5cda945 | ||
|
|
da42ee63fb | ||
|
|
5b7a7097b8 |
@@ -38,9 +38,9 @@ metadata {
|
||||
tiles (scale: 2){
|
||||
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
|
||||
tileAttribute ("device.switch", key: "PRIMARY_CONTROL", icon: "st.Lighting.light18") {
|
||||
attributeState "on", label:'${name}', action:"switch.off", icon:"st.switches.light.on", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
attributeState "on", label:'${name}', action:"switch.off", icon:"st.switches.light.on", backgroundColor:"#00A0DC", nextState:"turningOff"
|
||||
attributeState "off", label:'${name}', action:"switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
attributeState "turningOn", label:'${name}', icon:"st.switches.light.on", backgroundColor:"#79b821"
|
||||
attributeState "turningOn", label:'${name}', icon:"st.switches.light.on", backgroundColor:"#00A0DC"
|
||||
attributeState "turningOff", label:'${name}', icon:"st.switches.light.off", backgroundColor:"#ffffff"
|
||||
}
|
||||
tileAttribute ("device.color", key: "COLOR_CONTROL") {
|
||||
@@ -52,20 +52,20 @@ metadata {
|
||||
}
|
||||
|
||||
standardTile("motion", "device.motion", width: 2, height: 2, canChangeIcon: true, canChangeBackground: true) {
|
||||
state "active", label:'motion', icon:"st.motion.motion.active", backgroundColor:"#53a7c0"
|
||||
state "inactive", label:'no motion', icon:"st.motion.motion.inactive", backgroundColor:"#ffffff"
|
||||
state "active", label:'motion', icon:"st.motion.motion.active", backgroundColor:"#00A0DC"
|
||||
state "inactive", label:'no motion', icon:"st.motion.motion.inactive", backgroundColor:"#cccccc"
|
||||
}
|
||||
valueTile("temperature", "device.temperature", width: 2, height: 2) {
|
||||
state "temperature", label:'${currentValue}°', unit:"F", icon:"", // would be better if the units would switch to the desired units of the system (imperial or metric)
|
||||
backgroundColors:[
|
||||
[value: 0, color: "#1010ff"], // blue=cold
|
||||
[value: 65, color: "#a0a0f0"],
|
||||
[value: 70, color: "#e0e050"],
|
||||
[value: 75, color: "#f0d030"], // yellow
|
||||
[value: 80, color: "#fbf020"],
|
||||
[value: 85, color: "#fbdc01"],
|
||||
[value: 90, color: "#fb3a01"],
|
||||
[value: 95, color: "#fb0801"] // red=hot
|
||||
[value: 0, color: "#153591"], // blue=cold
|
||||
[value: 65, color: "#44b621"], // green
|
||||
[value: 70, color: "#44b621"], // green
|
||||
[value: 75, color: "#f1d801"], // yellow
|
||||
[value: 80, color: "#f1d801"], // yellow
|
||||
[value: 85, color: "#f1d801"], // yellow
|
||||
[value: 90, color: "#d04e00"], // red
|
||||
[value: 95, color: "#bc2323"] // red=hot
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@@ -34,8 +34,8 @@ metadata {
|
||||
tiles(scale: 2) {
|
||||
multiAttributeTile(name:"FGMS", type:"lighting", width:6, height:4) {//with generic type secondary control text is not displayed in Android app
|
||||
tileAttribute("device.motion", key:"PRIMARY_CONTROL") {
|
||||
attributeState("inactive", label:"no motion", icon:"st.motion.motion.inactive", backgroundColor:"#79b821")
|
||||
attributeState("active", label:"motion", icon:"st.motion.motion.active", backgroundColor:"#ffa81e")
|
||||
attributeState("inactive", label:"no motion", icon:"st.motion.motion.inactive", backgroundColor:"#cccccc")
|
||||
attributeState("active", label:"motion", icon:"st.motion.motion.active", backgroundColor:"#00A0DC")
|
||||
}
|
||||
|
||||
tileAttribute("device.tamper", key:"SECONDARY_CONTROL") {
|
||||
|
||||
@@ -95,12 +95,12 @@ metadata {
|
||||
multiAttributeTile(name:"doorCtrl", type:"generic", width:6, height:4) {tileAttribute("device.doorState", key: "PRIMARY_CONTROL")
|
||||
{
|
||||
attributeState "unknown", label: '${name}', action:"openDoor", icon: "st.Outdoor.outdoor20", nextState:"Sent"
|
||||
attributeState "open", label: '${name}', action:"closeDoor", icon: "st.Outdoor.outdoor20", backgroundColor: "#0000ff" , nextState:"Sent"
|
||||
attributeState "opening", label: '${name}', action:"closeDoor", icon: "st.Outdoor.outdoor20", backgroundColor: "#ffa81e"
|
||||
attributeState "closed", label: '${name}', action:"openDoor", icon: "st.Outdoor.outdoor20", backgroundColor: "#79b821", nextState:"Sent"
|
||||
attributeState "closing", label: '${name}', action:"openDoor", icon: "st.Outdoor.outdoor20", backgroundColor: "#ffa81e"
|
||||
attributeState "jammed", label: '${name}', action:"closeDoorHiI", icon: "st.Outdoor.outdoor20", backgroundColor: "#ff0000", nextState:"Sent"
|
||||
attributeState "forced close", label: 'forced\rclose', action:"openDoor", icon: "st.Outdoor.outdoor20", backgroundColor: "#ff8000", nextState:"Sent"
|
||||
attributeState "open", label: '${name}', action:"closeDoor", icon: "st.Outdoor.outdoor20", backgroundColor: "#00A0DC" , nextState:"Sent"
|
||||
attributeState "opening", label: '${name}', action:"closeDoor", icon: "st.Outdoor.outdoor20", backgroundColor: "#00A0DC"
|
||||
attributeState "closed", label: '${name}', action:"openDoor", icon: "st.Outdoor.outdoor20", backgroundColor: "#ffffff", nextState:"Sent"
|
||||
attributeState "closing", label: '${name}', action:"openDoor", icon: "st.Outdoor.outdoor20", backgroundColor: "#ffffff"
|
||||
attributeState "jammed", label: '${name}', action:"closeDoorHiI", icon: "st.Outdoor.outdoor20", backgroundColor: "#ff0000", nextState:"Sent"
|
||||
attributeState "forced close", label: 'forced\rclose', action:"openDoor", icon: "st.Outdoor.outdoor20", backgroundColor: "#ff8000", nextState:"Sent"
|
||||
attributeState "fault", label: 'FAULT', action:"openDoor", icon: "st.Outdoor.outdoor20", backgroundColor: "#ff0000", nextState:"Sent"
|
||||
attributeState "Sent", label: 'wait', icon: "st.motion.motion.active", backgroundColor: "#ffa81e"
|
||||
}
|
||||
@@ -135,13 +135,13 @@ metadata {
|
||||
}
|
||||
|
||||
standardTile("autoClose", "device.autoCloseEnable", width: 2, height: 2){
|
||||
state "on", label: 'Auto', action:"autoCloseOff", icon: "st.doors.garage.garage-closing", backgroundColor: "#79b821", nextState:"Sent"
|
||||
state "on", label: 'Auto', action:"autoCloseOff", icon: "st.doors.garage.garage-closing", backgroundColor: "#00A0DC", nextState:"Sent"
|
||||
state "off", label: 'Auto', action:"autoCloseOn", icon: "st.doors.garage.garage-closing", nextState:"Sent"
|
||||
state "Sent", label: 'wait', icon: "st.motion.motion.active", backgroundColor: "#ffa81e"
|
||||
}
|
||||
|
||||
standardTile("autoOpen", "device.autoOpenEnable", width: 2, height: 2){
|
||||
state "on", label: 'Auto', action:"autoOpenOff", icon: "st.doors.garage.garage-opening", backgroundColor: "#79b821", nextState:"Sent"
|
||||
state "on", label: 'Auto', action:"autoOpenOff", icon: "st.doors.garage.garage-opening", backgroundColor: "#00A0DC", nextState:"Sent"
|
||||
state "off", label: 'Auto', action:"autoOpenOn", icon: "st.doors.garage.garage-opening", nextState:"Sent"
|
||||
state "Sent", label: 'wait', icon: "st.motion.motion.active", backgroundColor: "#ffa81e"
|
||||
}
|
||||
@@ -189,13 +189,13 @@ metadata {
|
||||
|
||||
standardTile("aux1", "device.Aux1", width: 2, height: 2, canChangeIcon: true) {
|
||||
state "off", label:'Aux 1', action:"Aux1On", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"Sent"
|
||||
state "on", label:'Aux 1', action:"Aux1Off", icon:"st.switches.switch.on", backgroundColor:"#79b821", nextState:"Sent"
|
||||
state "on", label:'Aux 1', action:"Aux1Off", icon:"st.switches.switch.on", backgroundColor:"#00A0DC", nextState:"Sent"
|
||||
state "Sent", label: 'wait', icon: "st.motion.motion.active", backgroundColor: "#ffa81e"
|
||||
}
|
||||
|
||||
standardTile("aux2", "device.Aux2", width: 2, height: 2, canChangeIcon: true) {
|
||||
state "off", label:'Aux 2', action:"Aux2On", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"Sent"
|
||||
state "on", label:'Aux 2', action:"Aux2Off", icon:"st.switches.switch.on", backgroundColor:"#79b821", nextState:"Sent"
|
||||
state "on", label:'Aux 2', action:"Aux2Off", icon:"st.switches.switch.on", backgroundColor:"#00A0DC", nextState:"Sent"
|
||||
state "Sent", label: 'wait', icon: "st.motion.motion.active", backgroundColor: "#ffa81e"
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,212 @@
|
||||
/**
|
||||
* Copyright 2017 Ian Lindsay
|
||||
*
|
||||
* Code snippets taken from Tim Slagle
|
||||
* https://community.smartthings.com/u/tslagle13
|
||||
* here:
|
||||
* https://community.smartthings.com/t/generic-camera-device-using-local-connection-new-version-now-available/3269/75?u=l0kiscot
|
||||
*
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
|
||||
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing permissions and limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
preferences {
|
||||
input("devicekey", "text", title: "Device Key", description: "Your OpenGarage.io device key")
|
||||
input("ipadd", "text", title: "IP address", description: "The IP address of your OpenGarage.io unit")
|
||||
input("port", "text", title: "Port", description: "The port of your OpenGarage.io unit")
|
||||
}
|
||||
|
||||
metadata {
|
||||
definition (name: "OpenGarage.io Handler", namespace: "littlegumSmartHome", author: "Ian Lindsay") {
|
||||
capability "Door Control"
|
||||
capability "Garage Door Control"
|
||||
capability "Refresh"
|
||||
}
|
||||
|
||||
tiles (scale: 2){
|
||||
standardTile("garagedoor", "device.garagedoor", width: 6, height: 4) {
|
||||
state "open", label: '${name}', action: "close", icon: "st.doors.garage.garage-open", backgroundColor: "#e54444"
|
||||
state "closed", label: '${name}', action: "open", icon: "st.doors.garage.garage-closed", backgroundColor: "#79b821"
|
||||
}
|
||||
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 6, height: 2) {
|
||||
state "default", action: "refresh.refresh", icon: "st.secondary.refresh"
|
||||
}
|
||||
|
||||
main("garagedoor")
|
||||
details(["garagedoor", "refresh"])
|
||||
}
|
||||
simulator {
|
||||
// simulator metadata
|
||||
}
|
||||
}
|
||||
|
||||
def installed() {
|
||||
initialize()
|
||||
}
|
||||
|
||||
def initialize() {
|
||||
log.debug "initialize triggered"
|
||||
// initialize state
|
||||
state.doorStatus = 1 // 1 means open, 0 means closed
|
||||
api("getstatus", [])
|
||||
}
|
||||
|
||||
def open() {
|
||||
log.debug "Executing 'close'"
|
||||
api("openclose", [])
|
||||
}
|
||||
|
||||
def close() {
|
||||
log.debug "Executing 'close'"
|
||||
api("openclose", [])
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
log.debug "Refreshing Values "
|
||||
|
||||
api("getstatus", [])
|
||||
}
|
||||
|
||||
def api(method, args = [], success = {}) {
|
||||
def methods = [
|
||||
"getstatus": [gdipadd: "${ipadd}", gdport:"${port}", gdpath:"/jc", gdtype: "get"],
|
||||
"openclose": [gdipadd: "${ipadd}", gdport:"${port}", gdpath:"/cc?dkey=${devicekey}&click=1", gdtype: "get"]
|
||||
]
|
||||
|
||||
def request = methods.getAt(method)
|
||||
|
||||
doRequest(request.gdipadd, request.gdport, request.gdpath, request.gdtype, success)
|
||||
}
|
||||
|
||||
private doRequest(gdipadd, gdport, gdpath, gdtype, success) {
|
||||
log.debug(gdipadd)
|
||||
|
||||
//if(type == "post") {
|
||||
// httpPost(uri , "", success)
|
||||
//}
|
||||
|
||||
//else if(type == "get") {
|
||||
// httpGet(uri, success)
|
||||
//}
|
||||
|
||||
def host = gdipadd
|
||||
def hosthex = convertIPToHex(host)
|
||||
def porthex = Long.toHexString(Long.parseLong((gdport)))
|
||||
if (porthex.length() < 4) { porthex = "00" + porthex }
|
||||
|
||||
//log.debug "Port in Hex is $porthex"
|
||||
//log.debug "Hosthex is : $hosthex"
|
||||
device.deviceNetworkId = "$hosthex:$porthex"
|
||||
|
||||
//log.debug "The device id configured is: $device.deviceNetworkId"
|
||||
|
||||
//def path = gdpath //"/SnapshotJPEG?Resolution=640x480&Quality=Clarity"
|
||||
log.debug "path is: $gdpath"
|
||||
|
||||
def headers = [:] //"HOST:" + getHostAddress() + ""
|
||||
headers.put("HOST", "$host:$gdport")
|
||||
|
||||
try {
|
||||
def hubAction = new physicalgraph.device.HubAction(
|
||||
method: method,
|
||||
path: gdpath,
|
||||
headers: headers
|
||||
)
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
log.debug "Hit Exception on $hubAction"
|
||||
log.debug e
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
def parse(description) {
|
||||
|
||||
def msg = parseLanMessage(description)
|
||||
|
||||
log.debug msg
|
||||
|
||||
def headersAsString = msg.header // => headers as a string
|
||||
def headerMap = msg.headers // => headers as a Map
|
||||
def body = msg.body // => request body as a string
|
||||
def status = msg.status // => http status code of the response
|
||||
//def json = msg.json // => any JSON included in response body, as a data structure of lists and maps
|
||||
//def xml = msg.xml // => any XML included in response body, as a document tree structure
|
||||
//def data = msg.data // => either JSON or XML in response body (whichever is specified by content-type header in response)
|
||||
|
||||
def slurper = new groovy.json.JsonSlurper()
|
||||
def json = slurper.parseText(msg.body)
|
||||
|
||||
log.debug json
|
||||
|
||||
def result
|
||||
log.debug "before state.doorStatus: $state.doorStatus"
|
||||
|
||||
// open / close event
|
||||
if(json.result){
|
||||
if(state.doorStatus){
|
||||
log.debug "door open - so closing"
|
||||
state.doorStatus = 0
|
||||
result = createEvent(name: "garagedoor", value: "closed")
|
||||
} else {
|
||||
log.debug "door closed - so opening"
|
||||
state.doorStatus = 1
|
||||
result = createEvent(name: "garagedoor", value: "open")
|
||||
}
|
||||
}
|
||||
//status update request
|
||||
if(json.mac){
|
||||
if(json.door){
|
||||
log.debug "door is open - refreshing setting"
|
||||
state.doorStatus = 1
|
||||
result = createEvent(name: "garagedoor", value: "open")
|
||||
} else {
|
||||
log.debug "door is closed - refreshing setting"
|
||||
state.doorStatus = 0
|
||||
result = createEvent(name: "garagedoor", value: "closed")
|
||||
}
|
||||
}
|
||||
|
||||
log.debug "after state.doorStatus: $state.doorStatus"
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
private Long converIntToLong(ipAddress) {
|
||||
long result = 0
|
||||
def parts = ipAddress.split("\\.")
|
||||
for (int i = 3; i >= 0; i--) {
|
||||
result |= (Long.parseLong(parts[3 - i]) << (i * 8));
|
||||
}
|
||||
|
||||
return result & 0xFFFFFFFF;
|
||||
}
|
||||
|
||||
private String convertIPToHex(ipAddress) {
|
||||
return Long.toHexString(converIntToLong(ipAddress));
|
||||
}
|
||||
|
||||
private Integer convertHexToInt(hex) {
|
||||
Integer.parseInt(hex,16)
|
||||
}
|
||||
private String convertHexToIP(hex) {
|
||||
log.debug("Convert hex to ip: $hex") // a0 00 01 6
|
||||
[convertHexToInt(hex[0..1]),convertHexToInt(hex[2..3]),convertHexToInt(hex[4..5]),convertHexToInt(hex[6..7])].join(".")
|
||||
}
|
||||
|
||||
private getHostAddress() {
|
||||
def parts = device.deviceNetworkId.split(":")
|
||||
log.debug device.deviceNetworkId
|
||||
def ip = convertHexToIP(parts[0])
|
||||
def port = convertHexToInt(parts[1])
|
||||
return ip + ":" + port
|
||||
}
|
||||
@@ -103,7 +103,7 @@ metadata {
|
||||
}
|
||||
|
||||
valueTile("illuminance", "device.illuminance", inactiveLabel: false, width: 2, height: 2) {
|
||||
state "illuminance", label:'${currentValue} ${unit}', unit:"lux"
|
||||
state "illuminance", label:'${currentValue} lux', unit:""
|
||||
}
|
||||
|
||||
valueTile("ultravioletIndex", "device.ultravioletIndex", inactiveLabel: false, width: 2, height: 2) {
|
||||
@@ -410,4 +410,4 @@ private command(physicalgraph.zwave.Command cmd) {
|
||||
private commands(commands, delay=200) {
|
||||
log.info "sending commands: ${commands}"
|
||||
delayBetween(commands.collect{ command(it) }, delay)
|
||||
}
|
||||
}
|
||||
@@ -86,7 +86,7 @@ metadata {
|
||||
state "humidity", label:'${currentValue}% humidity', unit:""
|
||||
}
|
||||
valueTile("illuminance", "device.illuminance", inactiveLabel: false, width: 2, height: 2) {
|
||||
state "luminosity", label:'${currentValue} ${unit}', unit:"lux"
|
||||
state "luminosity", label:'${currentValue} lux', unit:""
|
||||
}
|
||||
valueTile("battery", "device.battery", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||
state "battery", label:'${currentValue}% battery', unit:""
|
||||
@@ -282,5 +282,4 @@ private secure(physicalgraph.zwave.Command cmd) {
|
||||
|
||||
private secureSequence(commands, delay=200) {
|
||||
delayBetween(commands.collect{ secure(it) }, delay)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -79,7 +79,7 @@ metadata {
|
||||
state "humidity", label:'${currentValue}% humidity', unit:""
|
||||
}
|
||||
valueTile("illuminance", "device.illuminance", inactiveLabel: false, width: 2, height: 2) {
|
||||
state "luminosity", label:'${currentValue} ${unit}', unit:"lux"
|
||||
state "luminosity", label:'${currentValue} lux', unit:""
|
||||
}
|
||||
valueTile("battery", "device.battery", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||
state "battery", label:'${currentValue}% battery', unit:""
|
||||
@@ -193,4 +193,4 @@ def configure() {
|
||||
// set data reporting period to 5 minutes
|
||||
zwave.configurationV1.configurationSet(parameterNumber: 111, size: 4, scaledConfigurationValue: 300).format()
|
||||
])
|
||||
}
|
||||
}
|
||||
@@ -68,8 +68,8 @@
|
||||
|
||||
tiles {
|
||||
standardTile("contact", "device.contact", width: 2, height: 2) {
|
||||
state "open", label: '${name}', icon: "st.contact.contact.open", backgroundColor: "#ffa81e"
|
||||
state "closed", label: '${name}', icon: "st.contact.contact.closed", backgroundColor: "#79b821"
|
||||
state "open", label: '${name}', icon: "st.contact.contact.open", backgroundColor: "#e86d13"
|
||||
state "closed", label: '${name}', icon: "st.contact.contact.closed", backgroundColor: "#00A0DC"
|
||||
}
|
||||
valueTile("temperature", "device.temperature", inactiveLabel: false) {
|
||||
state "temperature", label:'${currentValue}°',
|
||||
@@ -86,7 +86,7 @@
|
||||
}
|
||||
standardTile("tamper", "device.alarm") {
|
||||
state("secure", label:'secure', icon:"st.locks.lock.locked", backgroundColor:"#ffffff")
|
||||
state("tampered", label:'tampered', icon:"st.locks.lock.unlocked", backgroundColor:"#53a7c0")
|
||||
state("tampered", label:'tampered', icon:"st.locks.lock.unlocked", backgroundColor:"#00a0dc")
|
||||
}
|
||||
valueTile("battery", "device.battery", inactiveLabel: false, decoration: "flat") {
|
||||
state "battery", label:'${currentValue}% battery', unit:""
|
||||
|
||||
@@ -62,8 +62,8 @@ metadata {
|
||||
state "off", label: '${name}', action: "on", icon: "st.switches.switch.off", backgroundColor: "#ffffff"
|
||||
}
|
||||
standardTile("contact", "device.contact", inactiveLabel: false) {
|
||||
state "open", label: '${name}', icon: "st.contact.contact.open", backgroundColor: "#ffa81e"
|
||||
state "closed", label: '${name}', icon: "st.contact.contact.closed", backgroundColor: "#79b821"
|
||||
state "open", label: '${name}', icon: "st.contact.contact.open", backgroundColor: "#e86d13"
|
||||
state "closed", label: '${name}', icon: "st.contact.contact.closed", backgroundColor: "#00A0DC"
|
||||
}
|
||||
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat") {
|
||||
state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||
|
||||
@@ -44,9 +44,9 @@ metadata {
|
||||
tiles(scale: 2) {
|
||||
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
|
||||
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
|
||||
attributeState "on", label:'${name}', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
attributeState "on", label:'${name}', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#00A0DC", nextState:"turningOff"
|
||||
attributeState "off", label:'${name}', action:"switch.on", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#00A0DC", nextState:"turningOff"
|
||||
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
}
|
||||
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
|
||||
|
||||
@@ -56,8 +56,8 @@ metadata {
|
||||
state("closing", label:'${name}', icon:"st.doors.garage.garage-closing", backgroundColor:"#00A0DC")
|
||||
}
|
||||
standardTile("contact", "device.contact") {
|
||||
state("open", label:'${name}', icon:"st.contact.contact.open", backgroundColor:"#ffa81e")
|
||||
state("closed", label:'${name}', icon:"st.contact.contact.closed", backgroundColor:"#79b821")
|
||||
state("open", label:'${name}', icon:"st.contact.contact.open", backgroundColor:"#e86d13")
|
||||
state("closed", label:'${name}', icon:"st.contact.contact.closed", backgroundColor:"#00A0DC")
|
||||
}
|
||||
standardTile("acceleration", "device.acceleration", decoration: "flat") {
|
||||
state("active", label:'${name}', icon:"st.motion.acceleration.active", backgroundColor:"#00A0DC")
|
||||
|
||||
@@ -22,16 +22,16 @@ metadata {
|
||||
|
||||
tiles {
|
||||
standardTile("contact", "device.contact", width: 2, height: 2) {
|
||||
state("closed", label:'${name}', icon:"st.contact.contact.closed", backgroundColor:"#79b821", action: "open")
|
||||
state("open", label:'${name}', icon:"st.contact.contact.open", backgroundColor:"#ffa81e", action: "close")
|
||||
state("closed", label:'${name}', icon:"st.contact.contact.closed", backgroundColor:"#00A0DC", action: "open")
|
||||
state("open", label:'${name}', icon:"st.contact.contact.open", backgroundColor:"#e86d13", action: "close")
|
||||
}
|
||||
standardTile("freezerDoor", "device.contact", width: 2, height: 2, decoration: "flat") {
|
||||
state("closed", label:'Freezer', icon:"st.contact.contact.closed", backgroundColor:"#79b821")
|
||||
state("open", label:'Freezer', icon:"st.contact.contact.open", backgroundColor:"#ffa81e")
|
||||
state("closed", label:'Freezer', icon:"st.contact.contact.closed", backgroundColor:"#00A0DC")
|
||||
state("open", label:'Freezer', icon:"st.contact.contact.open", backgroundColor:"#e86d13")
|
||||
}
|
||||
standardTile("mainDoor", "device.contact", width: 2, height: 2, decoration: "flat") {
|
||||
state("closed", label:'Fridge', icon:"st.contact.contact.closed", backgroundColor:"#79b821")
|
||||
state("open", label:'Fridge', icon:"st.contact.contact.open", backgroundColor:"#ffa81e")
|
||||
state("closed", label:'Fridge', icon:"st.contact.contact.closed", backgroundColor:"#00A0DC")
|
||||
state("open", label:'Fridge', icon:"st.contact.contact.open", backgroundColor:"#e86d13")
|
||||
}
|
||||
standardTile("control", "device.contact", width: 1, height: 1, decoration: "flat") {
|
||||
state("closed", label:'${name}', icon:"st.contact.contact.closed", action: "open")
|
||||
|
||||
@@ -25,8 +25,8 @@ metadata {
|
||||
|
||||
tiles(scale: 2) {
|
||||
standardTile("contact", "device.contact", width: 4, height: 4) {
|
||||
state("closed", label:'${name}', icon:"st.fridge.fridge-closed", backgroundColor:"#79b821")
|
||||
state("open", label:'${name}', icon:"st.fridge.fridge-open", backgroundColor:"#ffa81e")
|
||||
state("closed", label:'${name}', icon:"st.fridge.fridge-closed", backgroundColor:"#00A0DC")
|
||||
state("open", label:'${name}', icon:"st.fridge.fridge-open", backgroundColor:"#e86d13")
|
||||
}
|
||||
childDeviceTile("freezerDoor", "freezerDoor", height: 2, width: 2, childTileName: "freezerDoor")
|
||||
childDeviceTile("mainDoor", "mainDoor", height: 2, width: 2, childTileName: "mainDoor")
|
||||
|
||||
@@ -27,7 +27,7 @@ metadata {
|
||||
tileAttribute("device.switch", key: "PRIMARY_CONTROL") {
|
||||
attributeState "on", label:'${name}', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#00A0DC", nextState:"turningOff"
|
||||
attributeState "off", label:'${name}', action:"switch.on", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#00A0DC", nextState:"turningOff"
|
||||
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,7 @@ metadata {
|
||||
tileAttribute("device.switch", key: "PRIMARY_CONTROL") {
|
||||
attributeState "on", label:'${name}', backgroundColor:"#00A0DC", nextState:"turningOff"
|
||||
attributeState "off", label:'${name}', backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
attributeState "turningOn", label:'${name}', backgroundColor:"#79b821", nextState:"turningOff"
|
||||
attributeState "turningOn", label:'${name}', backgroundColor:"#00A0DC", nextState:"turningOff"
|
||||
attributeState "turningOff", label:'${name}', backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
}
|
||||
tileAttribute("device.level", key: "SECONDARY_CONTROL") {
|
||||
@@ -59,7 +59,7 @@ metadata {
|
||||
tileAttribute("device.switch", key: "SECONDARY_CONTROL") {
|
||||
attributeState "on", label:'${name}', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#00A0DC", nextState:"turningOff"
|
||||
attributeState "off", label:'${name}', action:"switch.on", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
attributeState "turningOn", label:'…', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
attributeState "turningOn", label:'…', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#00A0DC", nextState:"turningOff"
|
||||
attributeState "turningOff", label:'…', action:"switch.on", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
}
|
||||
tileAttribute("device.level", key: "VALUE_CONTROL") {
|
||||
|
||||
@@ -40,11 +40,11 @@ metadata {
|
||||
tiles(scale: 2) {
|
||||
multiAttributeTile(name:"rich-control", type: "switch", canChangeIcon: true){
|
||||
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
|
||||
attributeState "on", label:'${name}', action:"switch.off", icon:"st.Home.home30", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
attributeState "on", label:'${name}', action:"switch.off", icon:"st.Home.home30", backgroundColor:"#00A0DC", nextState:"turningOff"
|
||||
attributeState "off", label:'${name}', action:"switch.on", icon:"st.Home.home30", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.Home.home30", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.Home.home30", backgroundColor:"#00A0DC", nextState:"turningOff"
|
||||
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.Home.home30", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
attributeState "offline", label:'${name}', icon:"st.Home.home30", backgroundColor:"#ff0000"
|
||||
attributeState "offline", label:'${name}', icon:"st.Home.home30", backgroundColor:"#cccccc"
|
||||
}
|
||||
tileAttribute ("currentIP", key: "SECONDARY_CONTROL") {
|
||||
attributeState "currentIP", label: ''
|
||||
|
||||
@@ -38,11 +38,11 @@
|
||||
tiles(scale: 2) {
|
||||
multiAttributeTile(name:"rich-control", type: "switch", canChangeIcon: true){
|
||||
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
|
||||
attributeState "on", label:'${name}', action:"switch.off", icon:"st.switches.switch.off", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
attributeState "on", label:'${name}', action:"switch.off", icon:"st.switches.switch.off", backgroundColor:"#00A0DC", nextState:"turningOff"
|
||||
attributeState "off", label:'${name}', action:"switch.on", icon:"st.switches.switch.on", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.switch.off", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.switch.off", backgroundColor:"#00A0DC", nextState:"turningOff"
|
||||
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.switch.on", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
attributeState "offline", label:'${name}', icon:"st.switches.switch.off", backgroundColor:"#ff0000"
|
||||
attributeState "offline", label:'${name}', icon:"st.switches.switch.off", backgroundColor:"#cccccc"
|
||||
}
|
||||
tileAttribute ("currentIP", key: "SECONDARY_CONTROL") {
|
||||
attributeState "currentIP", label: ''
|
||||
@@ -50,11 +50,11 @@
|
||||
}
|
||||
|
||||
standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) {
|
||||
state "on", label:'${name}', action:"switch.off", icon:"st.switches.switch.off", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
state "on", label:'${name}', action:"switch.off", icon:"st.switches.switch.off", backgroundColor:"#00A0DC", nextState:"turningOff"
|
||||
state "off", label:'${name}', action:"switch.on", icon:"st.switches.switch.on", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
state "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.switch.off", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
state "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.switch.off", backgroundColor:"#00A0DC", nextState:"turningOff"
|
||||
state "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.switch.on", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
state "offline", label:'${name}', icon:"st.switches.switch.off", backgroundColor:"#ff0000"
|
||||
state "offline", label:'${name}', icon:"st.switches.switch.off", backgroundColor:"#cccccc"
|
||||
}
|
||||
|
||||
standardTile("refresh", "device.switch", inactiveLabel: false, height: 2, width: 2, decoration: "flat") {
|
||||
|
||||
@@ -46,9 +46,9 @@ metadata {
|
||||
tiles(scale: 2) {
|
||||
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
|
||||
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
|
||||
attributeState "on", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
attributeState "on", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#00A0DC", nextState:"turningOff"
|
||||
attributeState "off", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#00A0DC", nextState:"turningOff"
|
||||
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
}
|
||||
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
|
||||
|
||||
@@ -41,9 +41,9 @@ metadata {
|
||||
tiles(scale: 2) {
|
||||
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
|
||||
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
|
||||
attributeState "on", label:'${name}', action:"switch.off", icon:"st.switches.light.on", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
attributeState "on", label:'${name}', action:"switch.off", icon:"st.switches.light.on", backgroundColor:"#00A0DC", nextState:"turningOff"
|
||||
attributeState "off", label:'${name}', action:"switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.light.on", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.light.on", backgroundColor:"#00A0DC", nextState:"turningOff"
|
||||
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,9 +45,9 @@ metadata {
|
||||
tiles(scale: 2) {
|
||||
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
|
||||
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
|
||||
attributeState "on", label:'${name}', action:"switch.off", icon:"st.switches.light.on", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
attributeState "on", label:'${name}', action:"switch.off", icon:"st.switches.light.on", backgroundColor:"#00A0DC", nextState:"turningOff"
|
||||
attributeState "off", label:'${name}', action:"switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.light.on", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.light.on", backgroundColor:"#00A0DC", nextState:"turningOff"
|
||||
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
}
|
||||
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
|
||||
|
||||
@@ -49,9 +49,9 @@ metadata {
|
||||
tiles(scale: 2) {
|
||||
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
|
||||
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
|
||||
attributeState "on", label:'${name}', action:"switch.off", icon:"st.switches.light.on", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
attributeState "on", label:'${name}', action:"switch.off", icon:"st.switches.light.on", backgroundColor:"#00A0DC", nextState:"turningOff"
|
||||
attributeState "off", label:'${name}', action:"switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.light.on", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.light.on", backgroundColor:"#00A0DC", nextState:"turningOff"
|
||||
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
}
|
||||
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
|
||||
|
||||
@@ -1,273 +0,0 @@
|
||||
/**
|
||||
* Lloyds Banking Group Connect & Protect
|
||||
*
|
||||
* Copyright 2016 Domotz
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
|
||||
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing permissions and limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
definition(
|
||||
name: "Lloyds Banking Group Connect & Protect",
|
||||
namespace: "domotz.dev",
|
||||
author: "Domotz",
|
||||
description: "The Lloyds Connect & Protect SmartApp is a bridge between SmartThings Cloud and Lloyds Banking Group to enable advanced connected device monitoring and alerting features to be included in the offering to their customers for the connected home service",
|
||||
category: "Convenience",
|
||||
singleInstance: true,
|
||||
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
|
||||
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
|
||||
iconX3Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
|
||||
oauth: [displayName: "Lloyds Banking Group Connect & Protect", displayLink: ""]) {
|
||||
appSetting "endpointRetrievalUrl"
|
||||
appSetting "xApiKey"
|
||||
}
|
||||
|
||||
def getSupportedTypes() {
|
||||
return [
|
||||
[obj: switches, name: "switches", attribute: "switch", capability: "switch", title: "Switches"],
|
||||
[obj: motions, name: "motions", attribute: "motion", capability: "motionSensor", title: "Motion Sensors"],
|
||||
[obj: temperature, name: "temperature", attribute: "temperature", capability: "temperatureMeasurement", title: "Temperature Sensors"],
|
||||
[obj: contact, name: "contact", attribute: "contact", capability: "contactSensor", title: "Contact Sensors"],
|
||||
[obj: presence, name: "presence", attribute: "presence", capability: "presenceSensor", title: "Presence Sensors"],
|
||||
[obj: water, name: "water", attribute: "water", capability: "waterSensor", title: "Water Sensors"],
|
||||
[obj: smoke, name: "smoke", attribute: "smoke", capability: "smokeDetector", title: "Smoke Sensors"],
|
||||
[obj: battery, name: "battery", attribute: "battery", capability: "battery", title: "Batteries"]
|
||||
]
|
||||
}
|
||||
|
||||
preferences {
|
||||
section("Allow Lloyds Banking Group Connect & Protect service to monitor these devices") {
|
||||
for (type in getSupportedTypes()) {
|
||||
input type.get("name"), "capability.${type.get('capability')}", title: type.get('title'), multiple: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def installed() {
|
||||
initialize()
|
||||
}
|
||||
|
||||
def updated() {
|
||||
unsubscribe()
|
||||
initialize()
|
||||
hubUpdateHandler()
|
||||
|
||||
}
|
||||
|
||||
def getRequestHeaders() {
|
||||
return ['Accept': '*/*', 'X-API-KEY': appSettings.xApiKey]
|
||||
}
|
||||
|
||||
def subscribeToDeviceEvents() {
|
||||
for (type in getSupportedTypes()) {
|
||||
subscribe(type.get("obj"), "${type.get("attribute")}", genericDeviceEventHandler)
|
||||
}
|
||||
}
|
||||
|
||||
def initialize() {
|
||||
if (atomicState.endpoint != null) {
|
||||
log.debug "Detected endpoint: ${atomicState.endpoint}"
|
||||
subscribeToDeviceEvents()
|
||||
subscribe(location, "routineExecuted", modeChangeHandler)
|
||||
subscribe(location, "mode", modeChangeHandler)
|
||||
} else {
|
||||
log.debug "There is no endpoint, requesting domotz for a new one"
|
||||
requestNewEndpoint()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def getHubLocation() {
|
||||
def location_info = [:]
|
||||
location_info['uid'] = location.id
|
||||
//location_info['hubs'] = location.hubs
|
||||
location_info['latitude'] = location.latitude
|
||||
location_info['longitude'] = location.longitude
|
||||
location_info['current_mode'] = location.mode
|
||||
//location_info['modes'] = location.modes
|
||||
location_info['name'] = location.name
|
||||
location_info['temperature_scale'] = location.temperatureScale
|
||||
location_info['version'] = location.version
|
||||
location_info['channel_name'] = location.channelName
|
||||
location_info['zip_code'] = location.zipCode
|
||||
log.debug "Triggered getHubLocation with properties: ${location_info}"
|
||||
return location_info
|
||||
}
|
||||
|
||||
def modeChangeHandler(evt) {
|
||||
log.debug "mode changed to ${evt.value}"
|
||||
def url = null
|
||||
if (atomicState.endpoint != null) {
|
||||
url = atomicState.endpoint + '/hub-change'
|
||||
|
||||
httpPutJson(
|
||||
uri: url,
|
||||
body: getHubLocation(),
|
||||
headers: getRequestHeaders()
|
||||
)
|
||||
} else {
|
||||
log.debug "There is no endpoint, requesting domotz for a new one"
|
||||
requestNewEndpoint()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
def genericDeviceEventHandler(event) {
|
||||
log.debug "Device Event Handler, event properties: ${event.getProperties().toString()}"
|
||||
log.debug "Device Event Handler, value: ${event.value}"
|
||||
def resp = [:]
|
||||
def url = null
|
||||
def device = null
|
||||
|
||||
device = getDevice(event.device, resp)
|
||||
|
||||
url = atomicState.endpoint + "/device/" + device.provider_uid + "/${event.name}"
|
||||
|
||||
log.debug "Device Event Handler, put url: ${url}"
|
||||
log.debug "Device Event Handler, put body: value: ${event.value}\ndate: ${event.isoDate}"
|
||||
httpPutJson(
|
||||
uri: url,
|
||||
body: [
|
||||
"value": event.value,
|
||||
"time" : event.isoDate
|
||||
],
|
||||
headers: getRequestHeaders()
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
def hubUpdateHandler() {
|
||||
log.debug "Hub Update Handler, with settings: ${settings}"
|
||||
def url = null
|
||||
def deviceList = [:]
|
||||
|
||||
if (atomicState.endpoint != null) {
|
||||
url = atomicState.endpoint + '/device-list'
|
||||
log.debug "Hub Update Event Handler, put url: ${url}"
|
||||
deviceList = getDeviceList()
|
||||
httpPutJson(
|
||||
uri: url,
|
||||
body: deviceList,
|
||||
headers: getRequestHeaders()
|
||||
)
|
||||
} else {
|
||||
log.debug "There is no endpoint, requesting domotz for a new one"
|
||||
requestNewEndpoint()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
def getDeviceList() {
|
||||
try {
|
||||
|
||||
def resp = [:]
|
||||
def attribute = null
|
||||
|
||||
for (type in getSupportedTypes()) {
|
||||
type.get("obj").each {
|
||||
device = getDevice(it, resp)
|
||||
attribute = type.get("attribute")
|
||||
if (it.currentState(attribute)) {
|
||||
device['attributes'][attribute] = [
|
||||
"value": it.currentState(attribute).value,
|
||||
"time" : it.currentState(attribute).getIsoDate(),
|
||||
"unit" : it.currentState(attribute).unit
|
||||
]
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return resp
|
||||
} catch (e) {
|
||||
log.debug("caught exception", e)
|
||||
return [:]
|
||||
}
|
||||
}
|
||||
|
||||
def getDevice(it, resp) {
|
||||
if (resp[it.id]) {
|
||||
return resp[it.id]
|
||||
}
|
||||
resp[it.id] = [name: it.name, display_name: it.displayName, provider_uid: it.id, type: it.typeName, label: it.label, manufacturer_name: it.manufacturerName, model: it.modelName, attributes: [:]]
|
||||
|
||||
}
|
||||
|
||||
def activateMonitoring(resp) {
|
||||
unsubscribe()
|
||||
log.debug "Event monitoring activated for endpoint: ${request.JSON.endpoint}"
|
||||
atomicState.endpoint = request.JSON.endpoint
|
||||
log.debug "Event monitoring activated for endpoint: ${atomicState.endpoint}"
|
||||
initialize()
|
||||
}
|
||||
|
||||
def deactivateMonitoring() {
|
||||
log.debug "Event monitoring deactivated."
|
||||
atomicState.endpoint = null
|
||||
unsubscribe()
|
||||
}
|
||||
|
||||
def requestNewEndpoint() {
|
||||
log.debug "Requesting a new endpoint."
|
||||
def hubId = location.id
|
||||
def params = [
|
||||
uri : "${appSettings.endpointRetrievalUrl}/${hubId}/endpoint",
|
||||
headers: getRequestHeaders()
|
||||
]
|
||||
|
||||
try {
|
||||
httpGet(params) { response ->
|
||||
log.debug "Request was successful, received endpoint: ${response.data.endpoint}"
|
||||
atomicState.endpoint = response.data.endpoint
|
||||
subscribeToDeviceEvents()
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
log.debug "Unable to retrieve the endpoint"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
def handleClientUninstall() {
|
||||
log.info("Deactivated from client")
|
||||
try {
|
||||
app.delete()
|
||||
} catch (e) {
|
||||
unschedule()
|
||||
unsubscribe()
|
||||
httpError(500, "An error occurred during deleting SmartApp: ${e}")
|
||||
}
|
||||
}
|
||||
|
||||
mappings {
|
||||
path("/device") {
|
||||
action:
|
||||
[
|
||||
GET: getDeviceList
|
||||
]
|
||||
}
|
||||
path("/location") {
|
||||
action:
|
||||
[
|
||||
GET: getHubLocation
|
||||
]
|
||||
}
|
||||
path("/monitoring") {
|
||||
action:
|
||||
[
|
||||
POST : activateMonitoring,
|
||||
DELETE: deactivateMonitoring
|
||||
]
|
||||
}
|
||||
path("/uninstall") {
|
||||
action
|
||||
[GET: handleClientUninstall]
|
||||
}
|
||||
}
|
||||
@@ -15,66 +15,66 @@ definition(
|
||||
)
|
||||
|
||||
preferences {
|
||||
section("Light switches to turn off") {
|
||||
input "switches", "capability.switch", title: "Choose light switches", multiple: true
|
||||
}
|
||||
section("Turn off when there is no motion and presence") {
|
||||
input "motionSensor", "capability.motionSensor", title: "Choose motion sensor"
|
||||
input "presenceSensors", "capability.presenceSensor", title: "Choose presence sensors", multiple: true
|
||||
}
|
||||
section("Delay before turning off") {
|
||||
input "delayMins", "number", title: "Minutes of inactivity?"
|
||||
}
|
||||
section("Light switches to turn off") {
|
||||
input "switches", "capability.switch", title: "Choose light switches", multiple: true
|
||||
}
|
||||
section("Turn off when there is no motion and presence") {
|
||||
input "motionSensor", "capability.motionSensor", title: "Choose motion sensor"
|
||||
input "presenceSensors", "capability.presenceSensor", title: "Choose presence sensors", multiple: true
|
||||
}
|
||||
section("Delay before turning off") {
|
||||
input "delayMins", "number", title: "Minutes of inactivity?"
|
||||
}
|
||||
}
|
||||
|
||||
def installed() {
|
||||
subscribe(motionSensor, "motion", motionHandler)
|
||||
subscribe(presenceSensors, "presence", presenceHandler)
|
||||
subscribe(motionSensor, "motion", motionHandler)
|
||||
subscribe(presenceSensors, "presence", presenceHandler)
|
||||
}
|
||||
|
||||
def updated() {
|
||||
unsubscribe()
|
||||
subscribe(motionSensor, "motion", motionHandler)
|
||||
subscribe(presenceSensors, "presence", presenceHandler)
|
||||
unsubscribe()
|
||||
subscribe(motionSensor, "motion", motionHandler)
|
||||
subscribe(presenceSensors, "presence", presenceHandler)
|
||||
}
|
||||
|
||||
def motionHandler(evt) {
|
||||
log.debug "handler $evt.name: $evt.value"
|
||||
if (evt.value == "inactive") {
|
||||
runIn(delayMins * 60, scheduleCheck, [overwrite: false])
|
||||
}
|
||||
log.debug "handler $evt.name: $evt.value"
|
||||
if (evt.value == "inactive") {
|
||||
runIn(delayMins * 60, scheduleCheck, [overwrite: true])
|
||||
}
|
||||
}
|
||||
|
||||
def presenceHandler(evt) {
|
||||
log.debug "handler $evt.name: $evt.value"
|
||||
if (evt.value == "not present") {
|
||||
runIn(delayMins * 60, scheduleCheck, [overwrite: false])
|
||||
}
|
||||
log.debug "handler $evt.name: $evt.value"
|
||||
if (evt.value == "not present") {
|
||||
runIn(delayMins * 60, scheduleCheck, [overwrite: true])
|
||||
}
|
||||
}
|
||||
|
||||
def isActivePresence() {
|
||||
// check all the presence sensors, make sure none are present
|
||||
def noPresence = presenceSensors.find{it.currentPresence == "present"} == null
|
||||
!noPresence
|
||||
// check all the presence sensors, make sure none are present
|
||||
def noPresence = presenceSensors.find{it.currentPresence == "present"} == null
|
||||
!noPresence
|
||||
}
|
||||
|
||||
def scheduleCheck() {
|
||||
log.debug "scheduled check"
|
||||
def motionState = motionSensor.currentState("motion")
|
||||
log.debug "scheduled check"
|
||||
def motionState = motionSensor.currentState("motion")
|
||||
if (motionState.value == "inactive") {
|
||||
def elapsed = now() - motionState.rawDateCreated.time
|
||||
def threshold = 1000 * 60 * delayMins - 1000
|
||||
if (elapsed >= threshold) {
|
||||
if (!isActivePresence()) {
|
||||
log.debug "Motion has stayed inactive since last check ($elapsed ms) and no presence: turning lights off"
|
||||
switches.off()
|
||||
} else {
|
||||
log.debug "Presence is active: do nothing"
|
||||
}
|
||||
} else {
|
||||
log.debug "Motion has not stayed inactive long enough since last check ($elapsed ms): do nothing"
|
||||
def elapsed = now() - motionState.rawDateCreated.time
|
||||
def threshold = 1000 * 60 * delayMins - 1000
|
||||
if (elapsed >= threshold) {
|
||||
if (!isActivePresence()) {
|
||||
log.debug "Motion has stayed inactive since last check ($elapsed ms) and no presence: turning lights off"
|
||||
switches.off()
|
||||
} else {
|
||||
log.debug "Presence is active: do nothing"
|
||||
}
|
||||
} else {
|
||||
log.debug "Motion has not stayed inactive long enough since last check ($elapsed ms): do nothing"
|
||||
}
|
||||
} else {
|
||||
log.debug "Motion is active: do nothing"
|
||||
log.debug "Motion is active: do nothing"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.security.InvalidKeyException;
|
||||
|
||||
/**
|
||||
* OpenT2T SmartApp Test
|
||||
*
|
||||
@@ -39,7 +43,7 @@ definition(
|
||||
* garageDoors | door | open, close | unknown, closed, open, closing, opening
|
||||
* cameras | image | take | <String>
|
||||
* thermostats | thermostat | setHeatingSetpoint, | temperature, heatingSetpoint, coolingSetpoint,
|
||||
* | | setCoolingSetpoint, | thermostatSetpoint, thermostatMode,
|
||||
* | | setCoolingSetpoint, | thermostatSetpoint, thermostatMode,
|
||||
* | | off, heat, cool, auto,| thermostatFanMode, thermostatOperatingState
|
||||
* | | emergencyHeat, |
|
||||
* | | setThermostatMode, |
|
||||
@@ -55,7 +59,7 @@ preferences {
|
||||
input "contactSensors", "capability.contactSensor", title: "Which Contact Sensors", multiple: true, required: false, hideWhenEmpty: true
|
||||
input "garageDoors", "capability.garageDoorControl", title: "Which Garage Doors?", multiple: true, required: false, hideWhenEmpty: true
|
||||
input "locks", "capability.lock", title: "Which Locks?", multiple: true, required: false, hideWhenEmpty: true
|
||||
input "cameras", "capability.videoCapture", title: "Which Cameras?", multiple: true, required: false, hideWhenEmpty: true
|
||||
input "cameras", "capability.videoCapture", title: "Which Cameras?", multiple: true, required: false, hideWhenEmpty: true
|
||||
input "motionSensors", "capability.motionSensor", title: "Which Motion Sensors?", multiple: true, required: false, hideWhenEmpty: true
|
||||
input "presenceSensors", "capability.presenceSensor", title: "Which Presence Sensors", multiple: true, required: false, hideWhenEmpty: true
|
||||
input "switches", "capability.switch", title: "Which Switches and Lights?", multiple: true, required: false, hideWhenEmpty: true
|
||||
@@ -66,44 +70,49 @@ preferences {
|
||||
|
||||
def getInputs() {
|
||||
def inputList = []
|
||||
inputList += contactSensors?: []
|
||||
inputList += garageDoors?: []
|
||||
inputList += locks?: []
|
||||
inputList += cameras?: []
|
||||
inputList += motionSensors?: []
|
||||
inputList += presenceSensors?: []
|
||||
inputList += switches?: []
|
||||
inputList += thermostats?: []
|
||||
inputList += waterSensors?: []
|
||||
inputList += contactSensors ?: []
|
||||
inputList += garageDoors ?: []
|
||||
inputList += locks ?: []
|
||||
inputList += cameras ?: []
|
||||
inputList += motionSensors ?: []
|
||||
inputList += presenceSensors ?: []
|
||||
inputList += switches ?: []
|
||||
inputList += thermostats ?: []
|
||||
inputList += waterSensors ?: []
|
||||
return inputList
|
||||
}
|
||||
|
||||
//API external Endpoints
|
||||
mappings {
|
||||
path("/devices") {
|
||||
action: [
|
||||
action:
|
||||
[
|
||||
GET: "getDevices"
|
||||
]
|
||||
}
|
||||
path("/devices/:id") {
|
||||
action: [
|
||||
action:
|
||||
[
|
||||
GET: "getDevice"
|
||||
]
|
||||
}
|
||||
path("/update/:id") {
|
||||
action: [
|
||||
action:
|
||||
[
|
||||
PUT: "updateDevice"
|
||||
]
|
||||
}
|
||||
path("/deviceSubscription") {
|
||||
action: [
|
||||
POST: "registerDeviceChange",
|
||||
action:
|
||||
[
|
||||
POST : "registerDeviceChange",
|
||||
DELETE: "unregisterDeviceChange"
|
||||
]
|
||||
}
|
||||
path("/locationSubscription") {
|
||||
action: [
|
||||
POST: "registerDeviceGraph",
|
||||
action:
|
||||
[
|
||||
POST : "registerDeviceGraph",
|
||||
DELETE: "unregisterDeviceGraph"
|
||||
]
|
||||
}
|
||||
@@ -116,14 +125,21 @@ def installed() {
|
||||
|
||||
def updated() {
|
||||
log.debug "Updating with settings: ${settings}"
|
||||
if(state.deviceSubscriptionMap == null){
|
||||
|
||||
//Initialize state variables if didn't exist.
|
||||
if (state.deviceSubscriptionMap == null) {
|
||||
state.deviceSubscriptionMap = [:]
|
||||
log.debug "deviceSubscriptionMap created."
|
||||
}
|
||||
if( state.locationSubscriptionMap == null){
|
||||
if (state.locationSubscriptionMap == null) {
|
||||
state.locationSubscriptionMap = [:]
|
||||
log.debug "locationSubscriptionMap created."
|
||||
}
|
||||
if (state.verificationKeyMap == null) {
|
||||
state.verificationKeyMap = [:]
|
||||
log.debug "verificationKeyMap created."
|
||||
}
|
||||
|
||||
unsubscribe()
|
||||
registerAllDeviceSubscriptions()
|
||||
}
|
||||
@@ -132,9 +148,11 @@ def initialize() {
|
||||
log.debug "Initializing with settings: ${settings}"
|
||||
state.deviceSubscriptionMap = [:]
|
||||
log.debug "deviceSubscriptionMap created."
|
||||
registerAllDeviceSubscriptions()
|
||||
state.locationSubscriptionMap = [:]
|
||||
log.debug "locationSubscriptionMap created."
|
||||
state.verificationKeyMap = [:]
|
||||
log.debug "verificationKeyMap created."
|
||||
registerAllDeviceSubscriptions()
|
||||
}
|
||||
|
||||
/*** Subscription Functions ***/
|
||||
@@ -144,47 +162,43 @@ def registerAllDeviceSubscriptions() {
|
||||
registerChangeHandler(inputs)
|
||||
}
|
||||
|
||||
//Subscribe to events from a list of devices
|
||||
def registerChangeHandler(myList) {
|
||||
myList.each { myDevice ->
|
||||
def theAtts = myDevice.supportedAttributes
|
||||
theAtts.each {att ->
|
||||
subscribe(myDevice, att.name, deviceEventHandler)
|
||||
log.info "Registering for ${myDevice.displayName}.${att.name}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Endpoints function: Subscribe to events from a specific device
|
||||
def registerDeviceChange() {
|
||||
def subscriptionEndpt = params.subscriptionURL
|
||||
def deviceId = params.deviceId
|
||||
def myDevice = findDevice(deviceId)
|
||||
if( myDevice == null ){
|
||||
|
||||
if (myDevice == null) {
|
||||
httpError(404, "Cannot find device with device ID ${deviceId}.")
|
||||
}
|
||||
|
||||
def theAtts = myDevice.supportedAttributes
|
||||
try {
|
||||
theAtts.each {att ->
|
||||
theAtts.each { att ->
|
||||
subscribe(myDevice, att.name, deviceEventHandler)
|
||||
}
|
||||
log.info "Subscribing for ${myDevice.displayName}"
|
||||
|
||||
if(subscriptionEndpt != null){
|
||||
if(state.deviceSubscriptionMap[deviceId] == null){
|
||||
if (subscriptionEndpt != null) {
|
||||
if (state.deviceSubscriptionMap[deviceId] == null) {
|
||||
state.deviceSubscriptionMap.put(deviceId, [subscriptionEndpt])
|
||||
log.info "Added subscription URL: ${subscriptionEndpt} for ${myDevice.displayName}"
|
||||
} else if (!state.deviceSubscriptionMap[deviceId].contains(subscriptionEndpt)){
|
||||
} else if (!state.deviceSubscriptionMap[deviceId].contains(subscriptionEndpt)) {
|
||||
state.deviceSubscriptionMap[deviceId] << subscriptionEndpt
|
||||
log.info "Added subscription URL: ${subscriptionEndpt} for ${myDevice.displayName}"
|
||||
}
|
||||
|
||||
if (params.key != null) {
|
||||
state.verificationKeyMap[subscriptionEndpt] = params.key
|
||||
log.info "Added verification key: ${params.key} for ${subscriptionEndpt}"
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
httpError(500, "something went wrong: $e")
|
||||
}
|
||||
|
||||
log.info "Current subscription map is ${state.deviceSubscriptionMap}"
|
||||
log.info "Current verification key map is ${state.verificationKeyMap}"
|
||||
return ["succeed"]
|
||||
}
|
||||
|
||||
@@ -194,18 +208,19 @@ def unregisterDeviceChange() {
|
||||
def deviceId = params.deviceId
|
||||
def myDevice = findDevice(deviceId)
|
||||
|
||||
if( myDevice == null ){
|
||||
if (myDevice == null) {
|
||||
httpError(404, "Cannot find device with device ID ${deviceId}.")
|
||||
}
|
||||
|
||||
try {
|
||||
if(subscriptionEndpt != null && subscriptionEndpt != "undefined"){
|
||||
if (state.deviceSubscriptionMap[deviceId]?.contains(subscriptionEndpt)){
|
||||
if(state.deviceSubscriptionMap[deviceId].size() == 1){
|
||||
if (subscriptionEndpt != null && subscriptionEndpt != "undefined") {
|
||||
if (state.deviceSubscriptionMap[deviceId]?.contains(subscriptionEndpt)) {
|
||||
if (state.deviceSubscriptionMap[deviceId].size() == 1) {
|
||||
state.deviceSubscriptionMap.remove(deviceId)
|
||||
} else {
|
||||
state.deviceSubscriptionMap[deviceId].remove(subscriptionEndpt)
|
||||
}
|
||||
state.verificationKeyMap.remove(subscriptionEndpt)
|
||||
log.info "Removed subscription URL: ${subscriptionEndpt} for ${myDevice.displayName}"
|
||||
}
|
||||
} else {
|
||||
@@ -217,25 +232,33 @@ def unregisterDeviceChange() {
|
||||
}
|
||||
|
||||
log.info "Current subscription map is ${state.deviceSubscriptionMap}"
|
||||
log.info "Current verification key map is ${state.verificationKeyMap}"
|
||||
}
|
||||
|
||||
//Endpoints function: Subscribe to device additiona/removal updated in a location
|
||||
def registerDeviceGraph() {
|
||||
def subscriptionEndpt = params.subscriptionURL
|
||||
|
||||
if (subscriptionEndpt != null && subscriptionEndpt != "undefined"){
|
||||
if (subscriptionEndpt != null && subscriptionEndpt != "undefined") {
|
||||
subscribe(location, "DeviceCreated", locationEventHandler, [filterEvents: false])
|
||||
subscribe(location, "DeviceUpdated", locationEventHandler, [filterEvents: false])
|
||||
subscribe(location, "DeviceDeleted", locationEventHandler, [filterEvents: false])
|
||||
|
||||
if(state.locationSubscriptionMap[location.id] == null){
|
||||
if (state.locationSubscriptionMap[location.id] == null) {
|
||||
state.locationSubscriptionMap.put(location.id, [subscriptionEndpt])
|
||||
log.info "Added subscription URL: ${subscriptionEndpt} for Location ${location.name}"
|
||||
} else if (!state.locationSubscriptionMap[location.id].contains(subscriptionEndpt)){
|
||||
} else if (!state.locationSubscriptionMap[location.id].contains(subscriptionEndpt)) {
|
||||
state.locationSubscriptionMap[location.id] << subscriptionEndpt
|
||||
log.info "Added subscription URL: ${subscriptionEndpt} for Location ${location.name}"
|
||||
}
|
||||
|
||||
if (params.key != null) {
|
||||
state.verificationKeyMap[subscriptionEndpt] = params.key
|
||||
log.info "Added verification key: ${params.key} for ${subscriptionEndpt}"
|
||||
}
|
||||
|
||||
log.info "Current location subscription map is ${state.locationSubscriptionMap}"
|
||||
log.info "Current verification key map is ${state.verificationKeyMap}"
|
||||
return ["succeed"]
|
||||
} else {
|
||||
httpError(400, "missing input parameter: subscriptionURL")
|
||||
@@ -247,16 +270,17 @@ def unregisterDeviceGraph() {
|
||||
def subscriptionEndpt = params.subscriptionURL
|
||||
|
||||
try {
|
||||
if(subscriptionEndpt != null && subscriptionEndpt != "undefined"){
|
||||
if (state.locationSubscriptionMap[location.id]?.contains(subscriptionEndpt)){
|
||||
if(state.locationSubscriptionMap[location.id].size() == 1){
|
||||
if (subscriptionEndpt != null && subscriptionEndpt != "undefined") {
|
||||
if (state.locationSubscriptionMap[location.id]?.contains(subscriptionEndpt)) {
|
||||
if (state.locationSubscriptionMap[location.id].size() == 1) {
|
||||
state.locationSubscriptionMap.remove(location.id)
|
||||
} else {
|
||||
state.locationSubscriptionMap[location.id].remove(subscriptionEndpt)
|
||||
}
|
||||
state.verificationKeyMap.remove(subscriptionEndpt)
|
||||
log.info "Removed subscription URL: ${subscriptionEndpt} for Location ${location.name}"
|
||||
}
|
||||
}else{
|
||||
} else {
|
||||
httpError(400, "missing input parameter: subscriptionURL")
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -264,28 +288,40 @@ def unregisterDeviceGraph() {
|
||||
}
|
||||
|
||||
log.info "Current location subscription map is ${state.locationSubscriptionMap}"
|
||||
log.info "Current verification key map is ${state.verificationKeyMap}"
|
||||
}
|
||||
|
||||
//When events are triggered, send HTTP post to web socket servers
|
||||
def deviceEventHandler(evt) {
|
||||
def evt_device = evt.device
|
||||
def evt_deviceType = getDeviceType(evt_device)
|
||||
def deviceInfo
|
||||
def evtDevice = evt.device
|
||||
def evtDeviceType = getDeviceType(evtDevice)
|
||||
def deviceData = [];
|
||||
|
||||
def params = [ body: [deviceName: evt_device.displayName, deviceId: evt_device.id, locationId: location.id] ]
|
||||
|
||||
if(evt.data != null){
|
||||
if (evt.data != null) {
|
||||
def evtData = parseJson(evt.data)
|
||||
log.info "Received event for ${evt_device.displayName}, data: ${evtData}, description: ${evt.descriptionText}"
|
||||
log.info "Received event for ${evtDevice.displayName}, data: ${evtData}, description: ${evt.descriptionText}"
|
||||
}
|
||||
|
||||
if (evtDeviceType == "thermostat") {
|
||||
deviceData = [name: evtDevice.displayName, id: evtDevice.id, status: evtDevice.status, deviceType: evtDeviceType, manufacturer: evtDevice.manufacturerName, model: evtDevice.modelName, attributes: deviceAttributeList(evtDevice, evtDeviceType), locationMode: getLocationModeInfo(), locationId: location.id]
|
||||
} else {
|
||||
deviceData = [name: evtDevice.displayName, id: evtDevice.id, status: evtDevice.status, deviceType: evtDeviceType, manufacturer: evtDevice.manufacturerName, model: evtDevice.modelName, attributes: deviceAttributeList(evtDevice, evtDeviceType), locationId: location.id]
|
||||
}
|
||||
|
||||
def params = [body: deviceData]
|
||||
|
||||
//send event to all subscriptions urls
|
||||
log.debug "Current subscription urls for ${evt_device.displayName} is ${state.deviceSubscriptionMap[evt_device.id]}"
|
||||
state.deviceSubscriptionMap[evt_device.id].each {
|
||||
log.debug "Current subscription urls for ${evtDevice.displayName} is ${state.deviceSubscriptionMap[evtDevice.id]}"
|
||||
state.deviceSubscriptionMap[evtDevice.id].each {
|
||||
params.uri = "${it}"
|
||||
if (state.verificationKeyMap[it] != null) {
|
||||
def key = state.verificationKeyMap[it]
|
||||
params.header = [Signature: ComputHMACValue(key, groovy.json.JsonOutput.toJson(params.body))]
|
||||
}
|
||||
log.trace "POST URI: ${params.uri}"
|
||||
log.trace "Header: ${params.header}"
|
||||
log.trace "Payload: ${params.body}"
|
||||
try{
|
||||
try {
|
||||
httpPostJson(params) { resp ->
|
||||
log.trace "response status code: ${resp.status}"
|
||||
log.trace "response data: ${resp.data}"
|
||||
@@ -298,20 +334,27 @@ def deviceEventHandler(evt) {
|
||||
|
||||
def locationEventHandler(evt) {
|
||||
log.info "Received event for location ${location.name}/${location.id}, Event: ${evt.name}, description: ${evt.descriptionText}, apiServerUrl: ${apiServerUrl("")}"
|
||||
switch(evt.name){
|
||||
switch (evt.name) {
|
||||
case "DeviceCreated":
|
||||
case "DeviceDeleted":
|
||||
def evt_device = evt.device
|
||||
def evt_deviceType = getDeviceType(evt_device)
|
||||
log.info "DeviceName: ${evt_device.displayName}, DeviceID: ${evt_device.id}, deviceType: ${evt_deviceType}"
|
||||
def evtDevice = evt.device
|
||||
def evtDeviceType = getDeviceType(evtDevice)
|
||||
def params = [body: [eventType: evt.name, deviceId: evtDevice.id, locationId: location.id]]
|
||||
|
||||
def params = [ body: [ eventType:evt.name, deviceId: evt_device.id, locationId: location.id ] ]
|
||||
if (evt.name == "DeviceDeleted" && state.deviceSubscriptionMap[deviceId] != null) {
|
||||
state.deviceSubscriptionMap.remove(evtDevice.id)
|
||||
}
|
||||
|
||||
state.locationSubscriptionMap[location.id].each {
|
||||
params.uri = "${it}"
|
||||
if (state.verificationKeyMap[it] != null) {
|
||||
def key = state.verificationKeyMap[it]
|
||||
params.header = [Signature: ComputHMACValue(key, groovy.json.JsonOutput.toJson(params.body))]
|
||||
}
|
||||
log.trace "POST URI: ${params.uri}"
|
||||
log.trace "Header: ${params.header}"
|
||||
log.trace "Payload: ${params.body}"
|
||||
try{
|
||||
try {
|
||||
httpPostJson(params) { resp ->
|
||||
log.trace "response status code: ${resp.status}"
|
||||
log.trace "response data: ${resp.data}"
|
||||
@@ -326,6 +369,23 @@ def locationEventHandler(evt) {
|
||||
}
|
||||
}
|
||||
|
||||
private ComputHMACValue(key, data) {
|
||||
try {
|
||||
SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA1")
|
||||
Mac mac = Mac.getInstance("HmacSHA1")
|
||||
mac.init(secretKeySpec)
|
||||
byte[] digest = mac.doFinal(data.getBytes("UTF-8"))
|
||||
return byteArrayToString(digest)
|
||||
} catch (InvalidKeyException e) {
|
||||
log.error "Invalid key exception while converting to HMac SHA1"
|
||||
}
|
||||
}
|
||||
|
||||
private def byteArrayToString(byte[] data) {
|
||||
BigInteger bigInteger = new BigInteger(1, data)
|
||||
String hash = bigInteger.toString(16)
|
||||
return hash
|
||||
}
|
||||
|
||||
/*** Device Query/Update Functions ***/
|
||||
|
||||
@@ -334,10 +394,10 @@ def getDevices() {
|
||||
def deviceData = []
|
||||
inputs?.each {
|
||||
def deviceType = getDeviceType(it)
|
||||
if(deviceType == "thermostat") {
|
||||
deviceData << [name: it.displayName, id: it.id, status:it.status, deviceType:deviceType, manufacturer:it.manufacturerName, model:it.modelName, attributes: deviceAttributeList(it, deviceType), locationMode: getLocationModeInfo()]
|
||||
if (deviceType == "thermostat") {
|
||||
deviceData << [name: it.displayName, id: it.id, status: it.status, deviceType: deviceType, manufacturer: it.manufacturerName, model: it.modelName, attributes: deviceAttributeList(it, deviceType), locationMode: getLocationModeInfo()]
|
||||
} else {
|
||||
deviceData << [name: it.displayName, id: it.id, status:it.status, deviceType:deviceType, manufacturer:it.manufacturerName, model:it.modelName, attributes: deviceAttributeList(it, deviceType)]
|
||||
deviceData << [name: it.displayName, id: it.id, status: it.status, deviceType: deviceType, manufacturer: it.manufacturerName, model: it.modelName, attributes: deviceAttributeList(it, deviceType)]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -350,10 +410,10 @@ def getDevice() {
|
||||
def it = findDevice(params.id)
|
||||
def deviceType = getDeviceType(it)
|
||||
def device
|
||||
if(deviceType == "thermostat") {
|
||||
device = [name: it.displayName, id: it.id, status:it.status, deviceType:deviceType, manufacturer:it.manufacturerName, model:it.modelName, attributes: deviceAttributeList(it,deviceType), locationMode: getLocationModeInfo()]
|
||||
if (deviceType == "thermostat") {
|
||||
device = [name: it.displayName, id: it.id, status: it.status, deviceType: deviceType, manufacturer: it.manufacturerName, model: it.modelName, attributes: deviceAttributeList(it, deviceType), locationMode: getLocationModeInfo()]
|
||||
} else {
|
||||
device = [name: it.displayName, id: it.id, status:it.status, deviceType:deviceType, manufacturer:it.manufacturerName, model:it.modelName, attributes: deviceAttributeList(it, deviceType)]
|
||||
device = [name: it.displayName, id: it.id, status: it.status, deviceType: deviceType, manufacturer: it.manufacturerName, model: it.modelName, attributes: deviceAttributeList(it, deviceType)]
|
||||
}
|
||||
|
||||
log.debug "getDevice, return: ${device}"
|
||||
@@ -366,18 +426,18 @@ void updateDevice() {
|
||||
request.JSON.each {
|
||||
def command = it.key
|
||||
def value = it.value
|
||||
if (command){
|
||||
if (command) {
|
||||
def commandList = mapDeviceCommands(command, value)
|
||||
command = commandList[0]
|
||||
value = commandList[1]
|
||||
|
||||
if (command == "setAwayMode") {
|
||||
log.info "Setting away mode to ${value}"
|
||||
if (location.modes?.find {it.name == value}) {
|
||||
if (location.modes?.find { it.name == value }) {
|
||||
location.setMode(value)
|
||||
}
|
||||
}else if (command == "thermostatSetpoint"){
|
||||
switch(device.currentThermostatMode){
|
||||
} else if (command == "thermostatSetpoint") {
|
||||
switch (device.currentThermostatMode) {
|
||||
case "cool":
|
||||
log.info "Update: ${device.displayName}, [${command}, ${value}]"
|
||||
device.setCoolingSetpoint(value)
|
||||
@@ -391,7 +451,7 @@ void updateDevice() {
|
||||
httpError(501, "this mode: ${device.currentThermostatMode} does not allow changing thermostat setpoint.")
|
||||
break
|
||||
}
|
||||
}else if (!device) {
|
||||
} else if (!device) {
|
||||
log.error "updateDevice, Device not found"
|
||||
httpError(404, "Device not found")
|
||||
} else if (!device.hasCommand(command)) {
|
||||
@@ -401,11 +461,11 @@ void updateDevice() {
|
||||
if (command == "setColor") {
|
||||
log.info "Update: ${device.displayName}, [${command}, ${value}]"
|
||||
device."$command"(hex: value)
|
||||
} else if(value.isNumber()) {
|
||||
} else if (value.isNumber()) {
|
||||
def intValue = value as Integer
|
||||
log.info "Update: ${device.displayName}, [${command}, ${intValue}(int)]"
|
||||
device."$command"(intValue)
|
||||
} else if (value){
|
||||
} else if (value) {
|
||||
log.info "Update: ${device.displayName}, [${command}, ${value}]"
|
||||
device."$command"(value)
|
||||
} else {
|
||||
@@ -432,17 +492,16 @@ private getDeviceType(device) {
|
||||
log.debug "supported commands: [${device}, ${device.supportedCommands}]"
|
||||
|
||||
//Loop through the device capability list to determine the device type.
|
||||
capabilities.each {capability ->
|
||||
switch(capability.name.toLowerCase())
|
||||
{
|
||||
capabilities.each { capability ->
|
||||
switch (capability.name.toLowerCase()) {
|
||||
case "switch":
|
||||
deviceType = "switch"
|
||||
|
||||
//If the device also contains "Switch Level" capability, identify it as a "light" device.
|
||||
if (capabilities.any{it.name.toLowerCase() == "switch level"}){
|
||||
if (capabilities.any { it.name.toLowerCase() == "switch level" }) {
|
||||
|
||||
//If the device also contains "Power Meter" capability, identify it as a "dimmerSwitch" device.
|
||||
if (capabilities.any{it.name.toLowerCase() == "power meter"}){
|
||||
if (capabilities.any { it.name.toLowerCase() == "power meter" }) {
|
||||
deviceType = "dimmerSwitch"
|
||||
return deviceType
|
||||
} else {
|
||||
@@ -489,24 +548,24 @@ private deviceAttributeList(device, deviceType) {
|
||||
allAttributes.each { attribute ->
|
||||
try {
|
||||
def currentState = device.currentState(attribute.name)
|
||||
if(currentState != null ){
|
||||
switch(attribute.name){
|
||||
if (currentState != null) {
|
||||
switch (attribute.name) {
|
||||
case 'temperature':
|
||||
attributeList.putAll([ (attribute.name): currentState.value, 'temperatureScale':location.temperatureScale ])
|
||||
attributeList.putAll([(attribute.name): currentState.value, 'temperatureScale': location.temperatureScale])
|
||||
break;
|
||||
default:
|
||||
attributeList.putAll([(attribute.name): currentState.value ])
|
||||
attributeList.putAll([(attribute.name): currentState.value])
|
||||
break;
|
||||
}
|
||||
if( deviceType == "genericSensor" ){
|
||||
if (deviceType == "genericSensor") {
|
||||
def key = attribute.name + "_lastUpdated"
|
||||
attributeList.putAll([ (key): currentState.isoDate ])
|
||||
attributeList.putAll([(key): currentState.isoDate])
|
||||
}
|
||||
} else {
|
||||
attributeList.putAll([ (attribute.name): null ]);
|
||||
attributeList.putAll([(attribute.name): null]);
|
||||
}
|
||||
} catch(e) {
|
||||
attributeList.putAll([ (attribute.name): null ]);
|
||||
} catch (e) {
|
||||
attributeList.putAll([(attribute.name): null]);
|
||||
}
|
||||
}
|
||||
return attributeList
|
||||
@@ -579,8 +638,7 @@ private mapDeviceCommands(command, value) {
|
||||
if (value == 1 || value == "1" || value == "lock") {
|
||||
resultCommand = "lock"
|
||||
resultValue = ""
|
||||
}
|
||||
else if (value == 0 || value == "0" || value == "unlock") {
|
||||
} else if (value == 0 || value == "0" || value == "unlock") {
|
||||
resultCommand = "unlock"
|
||||
resultValue = ""
|
||||
}
|
||||
@@ -589,5 +647,5 @@ private mapDeviceCommands(command, value) {
|
||||
break
|
||||
}
|
||||
|
||||
return [resultCommand,resultValue]
|
||||
return [resultCommand, resultValue]
|
||||
}
|
||||
|
||||
@@ -27,84 +27,82 @@ definition(
|
||||
|
||||
preferences {
|
||||
|
||||
section("Monitor this door or window") {
|
||||
input "contact", "capability.contactSensor"
|
||||
}
|
||||
section("And notify me if it's open for more than this many minutes (default 10)") {
|
||||
input "openThreshold", "number", description: "Number of minutes", required: false
|
||||
}
|
||||
section("Delay between notifications (default 10 minutes") {
|
||||
input "frequency", "number", title: "Number of minutes", description: "", required: false
|
||||
section("Monitor this door or window") {
|
||||
input "contact", "capability.contactSensor"
|
||||
}
|
||||
|
||||
section("And notify me if it's open for more than this many minutes (default 10)") {
|
||||
input "openThreshold", "number", description: "Number of minutes", required: false
|
||||
}
|
||||
|
||||
section("Delay between notifications (default 10 minutes") {
|
||||
input "frequency", "number", title: "Number of minutes", description: "", required: false
|
||||
}
|
||||
|
||||
section("Via text message at this number (or via push notification if not specified") {
|
||||
input("recipients", "contact", title: "Send notifications to") {
|
||||
input "phone", "phone", title: "Phone number (optional)", required: false
|
||||
}
|
||||
section("Via text message at this number (or via push notification if not specified") {
|
||||
input("recipients", "contact", title: "Send notifications to") {
|
||||
input "phone", "phone", title: "Phone number (optional)", required: false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def installed() {
|
||||
log.trace "installed()"
|
||||
subscribe()
|
||||
log.trace "installed()"
|
||||
subscribe()
|
||||
}
|
||||
|
||||
def updated() {
|
||||
log.trace "updated()"
|
||||
unsubscribe()
|
||||
subscribe()
|
||||
log.trace "updated()"
|
||||
unsubscribe()
|
||||
subscribe()
|
||||
}
|
||||
|
||||
def subscribe() {
|
||||
subscribe(contact, "contact.open", doorOpen)
|
||||
subscribe(contact, "contact.closed", doorClosed)
|
||||
subscribe(contact, "contact.open", doorOpen)
|
||||
subscribe(contact, "contact.closed", doorClosed)
|
||||
}
|
||||
|
||||
def doorOpen(evt)
|
||||
{
|
||||
log.trace "doorOpen($evt.name: $evt.value)"
|
||||
def t0 = now()
|
||||
def delay = (openThreshold != null && openThreshold != "") ? openThreshold * 60 : 600
|
||||
runIn(delay, doorOpenTooLong, [overwrite: false])
|
||||
log.debug "scheduled doorOpenTooLong in ${now() - t0} msec"
|
||||
def doorOpen(evt) {
|
||||
log.trace "doorOpen($evt.name: $evt.value)"
|
||||
def delay = (openThreshold != null && openThreshold != "") ? openThreshold * 60 : 600
|
||||
runIn(delay, doorOpenTooLong, [overwrite: true])
|
||||
}
|
||||
|
||||
def doorClosed(evt)
|
||||
{
|
||||
log.trace "doorClosed($evt.name: $evt.value)"
|
||||
def doorClosed(evt) {
|
||||
log.trace "doorClosed($evt.name: $evt.value)"
|
||||
unschedule(doorOpenTooLong)
|
||||
}
|
||||
|
||||
def doorOpenTooLong() {
|
||||
def contactState = contact.currentState("contact")
|
||||
def freq = (frequency != null && frequency != "") ? frequency * 60 : 600
|
||||
def contactState = contact.currentState("contact")
|
||||
def freq = (frequency != null && frequency != "") ? frequency * 60 : 600
|
||||
|
||||
if (contactState.value == "open") {
|
||||
def elapsed = now() - contactState.rawDateCreated.time
|
||||
def threshold = ((openThreshold != null && openThreshold != "") ? openThreshold * 60000 : 60000) - 1000
|
||||
if (elapsed >= threshold) {
|
||||
log.debug "Contact has stayed open long enough since last check ($elapsed ms): calling sendMessage()"
|
||||
sendMessage()
|
||||
runIn(freq, doorOpenTooLong, [overwrite: false])
|
||||
} else {
|
||||
log.debug "Contact has not stayed open long enough since last check ($elapsed ms): doing nothing"
|
||||
}
|
||||
} else {
|
||||
log.warn "doorOpenTooLong() called but contact is closed: doing nothing"
|
||||
}
|
||||
if (contactState.value == "open") {
|
||||
def elapsed = now() - contactState.rawDateCreated.time
|
||||
def threshold = ((openThreshold != null && openThreshold != "") ? openThreshold * 60000 : 60000) - 1000
|
||||
if (elapsed >= threshold) {
|
||||
log.debug "Contact has stayed open long enough since last check ($elapsed ms): calling sendMessage()"
|
||||
sendMessage()
|
||||
runIn(freq, doorOpenTooLong, [overwrite: false])
|
||||
} else {
|
||||
log.debug "Contact has not stayed open long enough since last check ($elapsed ms): doing nothing"
|
||||
}
|
||||
} else {
|
||||
log.warn "doorOpenTooLong() called but contact is closed: doing nothing"
|
||||
}
|
||||
}
|
||||
|
||||
void sendMessage()
|
||||
{
|
||||
def minutes = (openThreshold != null && openThreshold != "") ? openThreshold : 10
|
||||
def msg = "${contact.displayName} has been left open for ${minutes} minutes."
|
||||
log.info msg
|
||||
if (location.contactBookEnabled) {
|
||||
sendNotificationToContacts(msg, recipients)
|
||||
}
|
||||
else {
|
||||
if (phone) {
|
||||
sendSms phone, msg
|
||||
} else {
|
||||
sendPush msg
|
||||
}
|
||||
void sendMessage() {
|
||||
def minutes = (openThreshold != null && openThreshold != "") ? openThreshold : 10
|
||||
def msg = "${contact.displayName} has been left open for ${minutes} minutes."
|
||||
log.info msg
|
||||
if (location.contactBookEnabled) {
|
||||
sendNotificationToContacts(msg, recipients)
|
||||
} else {
|
||||
if (phone) {
|
||||
sendSms phone, msg
|
||||
} else {
|
||||
sendPush msg
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user