Compare commits

...

16 Commits

Author SHA1 Message Date
Vinay Rao
a99e050c6b Merge pull request #1975 from jackchi/device-ux-remaining
[DVCSMP-2607] Standardize remaining SmartThingsPublic DTH colors
2017-05-11 13:35:42 -07:00
jackchi
5bf7caca0d [DVCSMP-2607] Standardize remaining SmartThingsPublic ON/CLOSED/TAMPERED & OPEN state colors 2017-05-11 12:23:38 -07:00
Aaron Miller
3eddd68532 Merge pull request #1978 from aaron-miller/DVCSMP-2612
[DVCSMP-2612] Unschedule execution for Left It Open when door is closed
2017-05-11 08:51:36 -05:00
Aaron Miller
8d79379bba Merge pull request #1927 from aaron-miller/DVCSMP-2595
[DVCSMP-2595] Set scheduled executions for Lights off with no motion SA to overwrite
2017-05-10 13:29:03 -05:00
Vinay Rao
dd4da29bcd Merge pull request #1989 from SmartThingsCommunity/staging
Rolling down staging to master
2017-05-09 16:03:53 -05:00
Vinay Rao
a3e9f1d2c1 Merge pull request #1984 from larsfinander/DVCSMP-2613_OpenT2T_Update_5_1_submission_staging
DVCSMP-2613OpenT2T: Update to 5/1 submission
2017-05-08 09:02:19 -07:00
Lars Finander
8bfc3f0c1c DVCSMP-2613OpenT2T: Update to 5/1 submission 2017-05-08 09:56:53 -06:00
Aaron Miller
b6136bf1d5 [DVCSMP-2612] Unschedule execution for Left It Open when door is closed 2017-05-05 13:22:46 -05:00
Vinay Rao
68f5cda945 Merge pull request #1977 from ccurtiST/PROB-1347
PROB-1347 Lux values showing as ${unit} in recently tab of Aeon Multipurpose
2017-05-05 11:06:46 -07:00
Christopher Curti
da42ee63fb Replaced ${unit}', unit:"lux" with lux', unit: "" to fix issue with seeing ${unit} in the recently tab of Aeon multisensor illuminance values. Tested all three DTHs and lux was displayed rather then ${unit} 2017-05-05 09:31:44 -07:00
Vinay Rao
c58132a69e Merge pull request #1974 from SmartThingsCommunity/staging
Rolling down staging to master
2017-05-03 11:54:21 -07:00
Vinay Rao
93544c4f60 Merge pull request #1973 from SmartThingsCommunity/production
Rolling down production to staging
2017-05-03 11:53:58 -07:00
Vinay Rao
7527fdd1bb Merge pull request #1967 from tpmanley/bugfix/color-temp-exception
DVCSMP-2606 Fix recent regression with setting color temperature
2017-05-03 11:23:02 -07:00
Tom Manley
b552bcc6f0 Fix recent regression with setting color temperature
This fixes a recently introduced bug that caused an exception while
attempting to set the color temperature on any of these three DTHs.

https://smartthings.atlassian.net/browse/DVCSMP-2606
2017-05-02 22:24:25 -05:00
Vinay Rao
f069ea3087 Merge pull request #1965 from SmartThingsCommunity/staging
Rolling down staging to master
2017-05-02 16:14:39 -07:00
Aaron Miller
5b7a7097b8 [DVCSMP-2595] Set scheduled executions for Lights off with no motion SA to overwrite 2017-04-20 13:20:55 -05:00
24 changed files with 335 additions and 265 deletions

View File

@@ -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
]
}

View File

@@ -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") {

View File

@@ -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"
}

View File

@@ -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)
}
}

View File

@@ -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)
}
}

View File

@@ -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()
])
}
}

View File

@@ -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:""

View File

@@ -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"

View File

@@ -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") {

View File

@@ -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")

View File

@@ -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")

View File

@@ -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")

View File

@@ -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") {

View File

@@ -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: ''

View File

@@ -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") {

View File

@@ -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") {

View File

@@ -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"
}
}

View File

@@ -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") {
@@ -71,6 +71,11 @@ metadata {
}
}
// Globals
private getMOVE_TO_COLOR_TEMPERATURE_COMMAND() { 0x0A }
private getCOLOR_CONTROL_CLUSTER() { 0x0300 }
private getATTRIBUTE_COLOR_TEMPERATURE() { 0x0007 }
// Parse incoming device messages to generate events
def parse(String description) {
log.debug "description is $description"
@@ -146,7 +151,7 @@ def setColorTemperature(value) {
def tempInMired = (1000000 / value) as Integer
def finalHex = zigbee.swapEndianHex(zigbee.convertToHexString(tempInMired, 4))
zigbee.command(COLOR_CONTROL_CLUSTER, 0x0A, "$finalHex 0000") +
zigbee.command(COLOR_CONTROL_CLUSTER, MOVE_TO_COLOR_TEMPERATURE_COMMAND, "$finalHex 0000") +
zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_COLOR_TEMPERATURE)
}

View File

@@ -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") {

View File

@@ -66,6 +66,11 @@ metadata {
}
}
// Globals
private getMOVE_TO_COLOR_TEMPERATURE_COMMAND() { 0x0A }
private getCOLOR_CONTROL_CLUSTER() { 0x0300 }
private getATTRIBUTE_COLOR_TEMPERATURE() { 0x0007 }
// Parse incoming device messages to generate events
def parse(String description) {
log.debug "description is $description"
@@ -152,7 +157,7 @@ def setColorTemperature(value) {
def tempInMired = (1000000 / value) as Integer
def finalHex = zigbee.swapEndianHex(zigbee.convertToHexString(tempInMired, 4))
zigbee.command(COLOR_CONTROL_CLUSTER, 0x0A, "$finalHex 0000") +
zigbee.command(COLOR_CONTROL_CLUSTER, MOVE_TO_COLOR_TEMPERATURE_COMMAND, "$finalHex 0000") +
zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_COLOR_TEMPERATURE)
}

View File

@@ -68,6 +68,11 @@ metadata {
}
}
// Globals
private getMOVE_TO_COLOR_TEMPERATURE_COMMAND() { 0x0A }
private getCOLOR_CONTROL_CLUSTER() { 0x0300 }
private getATTRIBUTE_COLOR_TEMPERATURE() { 0x0007 }
// Parse incoming device messages to generate events
def parse(String description) {
log.debug "description is $description"
@@ -147,7 +152,7 @@ def setColorTemperature(value) {
def tempInMired = (1000000 / value) as Integer
def finalHex = zigbee.swapEndianHex(zigbee.convertToHexString(tempInMired, 4))
zigbee.command(COLOR_CONTROL_CLUSTER, 0x0A, "$finalHex 0000") +
zigbee.command(COLOR_CONTROL_CLUSTER, MOVE_TO_COLOR_TEMPERATURE_COMMAND, "$finalHex 0000") +
zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_COLOR_TEMPERATURE)
}

View File

@@ -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"
}
}
}

View File

@@ -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]
}

View File

@@ -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
}
}
}